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

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 (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet target: `services/` and architecture/landscape.likec4 graded\n * against each other, plus the two exemptions that decide which drawn element\n * answers for a service directory at all.\n *\n * The landscape is the one artifact no service owns — parsed once for the whole\n * run and graded on a target of its own, outside the dispatcher's `guarded`.\n * That is why its IO containment lives in this package instead: see\n * `unreadableLandscape` in `./load.ts`.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { loadFile, type Elem, type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { landscapePath as landscapeFile, servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { readModelShapes } from \"../../../core/c4/service-model/shape.js\";\nimport { serviceIdFindings } from \"../../../core/repo/entries.js\";\nimport { listFleetTree, listServices } from \"../../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { landscapeConflictFinding } from \"../../../core/conflict-markers.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { capabilityFleetFindings, fleetShapeFindings, permissionFindings } from \"../checks/fleet-shape.js\";\nimport { fleetLinkFindings } from \"../links/corpus.js\";\nimport { glossaryFindings } from \"../links/glossary.js\";\nimport { obligationFindings, obligationVocabularyFindings } from \"./obligations.js\";\nimport { readObligations } from \"../../../core/obligations/obligations.js\";\nimport { obligationsPath } from \"../../../core/repo/paths.js\";\nimport { EXTERNAL_TAG } from \"../../../core/vocabulary/maturity.js\";\nimport { errorText } from \"../../../core/c4/likec4.js\";\nimport { serviceTreePath, type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { drawnSystems, serviceLevelElements, standsForService } from \"./census.js\";\nimport { unreadableLandscape } from \"./load.js\";\nimport { kindTagFindings } from \"./kind-tags.js\";\nimport { mapAttestation } from \"./map/attest.js\";\nimport { bindingFindings } from \"./map/bindings.js\";\nimport { consumerCensus, datastoreFindings } from \"./map/consumers.js\";\nimport { isolationFindings } from \"./map/isolation.js\";\nimport { fleetUseCaseFindings } from \"./map/usecases.js\";\nimport { viewIdFindings } from \"./views/ids.js\";\nimport { viewsStaleFindings } from \"./views/stale.js\";\nimport { projectFindings } from \"./views/projects.js\";\nimport { fleetProjectFindings } from \"./views/fleet-project.js\";\nimport type { ParsedView } from \"../../../core/c4/parsed/dynamic-views.js\";\nimport { isUseCase } from \"../../../core/usecases/fleet.js\";\n\n/**\n * The fleet's flows as a grade may read them, or `null` when loam cannot see\n * them at all — the same `null`-suspends idiom `gradableCapabilityIds` uses,\n * and here it guards one specific wrong answer.\n *\n * `capability.requirement-unrealized` says a business promise is kept by\n * nobody. A use case is one of the two things that can keep it, so a run that\n * could not read the flows must not make that claim: no preload (a single-target\n * run) and a project that did not parse are both \"loam did not look\", never\n * \"there is nothing there\". A preload that DID parse and declares no views is a\n * real, empty answer and grades normally.\n *\n * Filtered through `isUseCase` — the same opt-in `readUseCases` applies before\n * `loam list capabilities` asks this question — because the two surfaces must\n * never disagree about `keptBy`. Handing the unfiltered preload over was\n * harmless only by luck: an untagged view yields no claims, so the answers\n * matched for want of an input that could tell them apart. A `#req-`-only view\n * is exactly that input.\n */\nfunction gradableFlows(preloaded: LoadedDoc | null | undefined): readonly ParsedView[] | null {\n if (preloaded === null || preloaded === undefined || preloaded.errors.length > 0) return null;\n return (preloaded.views ?? []).filter(isUseCase);\n}\n\n/**\n * The fleet cross-check: `services/` and the landscape both claim to name the\n * fleet, and nothing used to compare them. A directory nobody drew and an element\n * with nothing behind it were equally invisible.\n *\n * The two directions are graded differently because the evidence differs. A\n * directory that exists is a fact, so a landscape missing it is an error — every\n * view derived from that landscape is then incomplete. An element with no\n * directory may legitimately be someone else's system, so it warns, and\n * `#external` says \"deliberately not ours\" and silences it. An explicit\n * `metadata { service '<id>' }` naming nothing is an error either way: a binding\n * is a claim about this repo, not a guess at one.\n *\n * An ABSENT landscape is a finding, not a skipped check. It used to return null\n * — no target, no findings, and a fleet gate that went green over a docs repo\n * with no fleet map at all, which is the single artifact every derived view and\n * every spine check is computed from. It is graded by what its absence proves:\n * with services in `services/` it is an ERROR (a fleet that exists is undrawn),\n * with none it is a WARNING (a docs repo before its first adopt legitimately\n * has nothing to draw, but the file still belongs there — `loam init` scaffolds\n * it, and a repo missing it will silently accept never getting one).\n *\n * `preloaded` is the already-parsed landscape under --all — the same doc every\n * service check gets.\n */\nexport async function validateLandscape(\n docsDir: DocsDir,\n preloaded?: LoadedDoc | null,\n fleet?: FleetContext,\n): Promise<TargetReport> {\n const path = landscapeFile(docsDir);\n const findings: Finding[] = [];\n const report: TargetReport = {\n kind: \"landscape\",\n id: \"landscape\",\n path: \"architecture/landscape.likec4\",\n findings,\n };\n\n // The SET of service directories is this target's other subject, so it is\n // graded before the map is even opened: `service.id-invalid` is a fact about\n // `services/` that holds whether or not a landscape exists or parses, and\n // both of those return early below. Emitted here and nowhere else — the\n // enumeration is what makes the id a question, and one finding per fleet is\n // what the rename fixes.\n const entries = await listServices(docsDir, fleet);\n findings.push(...serviceIdFindings(entries));\n // The tree walk's own findings ride the same fleet target, before the map's\n // early returns for the same reason `service.id-invalid` does: a stranded,\n // colliding or mismarked directory is a fact about `services/` that holds\n // whether or not a landscape exists or parses — and a broken tree must make\n // the fleet gate refuse while still naming every service it found.\n const tree = await listFleetTree(docsDir, fleet);\n findings.push(...tree.findings);\n // Which SHAPE each model has, read once for the whole target: it decides four\n // of the grades below (`views/projects.ts`) and which documents the merged\n // project holds (`views/fleet-project.ts`). Over the full enumeration, which\n // is what `entries` is here even under `--base` — `validate --all` narrows the\n // TARGETS it grades and never the fleet the renderer would load.\n const models = entries.map((entry) => servicePathsAt(entry.dir).model);\n const shapes = fleet === undefined ? await readModelShapes(models) : await fleet.modelShapes(models);\n // A model and the renderer's project files disagreeing is a fact about\n // `services/` too, graded here for `service.id-invalid`'s reason: it holds\n // whether or not the map parses. One warning per service per disagreement;\n // views/projects.ts says why a warning, why this run and not `doctor`, why\n // the root-file gate, and why the grade is KEPT rather than spread and\n // dropped — one of the five gates the merged-project load below, and\n // re-deriving that fact there would let a cause and its 161-warning cascade\n // disagree about which state the run is in.\n const projects = await projectFindings(docsDir, entries, shapes);\n findings.push(...projects.findings);\n // Before the landscape's own early returns: the authorization and capability\n // vocabularies are fleet facts that do not depend on the map existing or parsing.\n findings.push(...(await permissionFindings(docsDir, entries, fleet)));\n findings.push(...(await capabilityFleetFindings(docsDir, entries, fleet, gradableFlows(preloaded))));\n // The documents that belong to no service and no feature — the fleet's ADRs\n // and the living capability tree — are graded for their links here, once,\n // for the same reason the two vocabularies above are: a fleet fact repeated\n // on every service target is the report.\n findings.push(...(await fleetLinkFindings({ docsDir, fleet })));\n // The glossary is graded on who cites it, which is a question only the whole\n // repository answers — so it belongs here, beside the two vocabularies, and\n // nowhere else.\n findings.push(...(await glossaryFindings(docsDir, fleet)));\n // The architectural obligation vocabulary, read ONCE here and handed to the\n // map half below: its own two verdicts hold whether or not the landscape\n // exists or parses, and a run where somebody is fixing the map must still be\n // told that this file does not read.\n const obligations = await readObligations(obligationsPath(docsDir));\n findings.push(...obligationVocabularyFindings(docsDir, obligations));\n\n if (!existsSync(path)) {\n const count = entries.length;\n findings.push({\n severity: count > 0 ? \"error\" : \"warn\",\n code: \"landscape.missing\",\n message:\n `landscape: architecture/landscape.likec4 does not exist — ` +\n (count > 0\n ? `${count} service(s) are adopted and nothing draws the fleet. `\n : \"nothing draws the fleet. \") +\n \"It is the one map every derived view and the C4↔API spine are computed from: \" +\n \"write a `specification { element softwareSystem }` + `model { … }` document there \" +\n \"with one element per services/<id>/, bound with `metadata { service '<id>' }`.\",\n });\n return report;\n }\n\n // Conflict markers before the parse — `loam doctor`'s order, and for its\n // reason: the markers are the cause and the parse errors are the cascade.\n // Nothing may be concluded from a file that is two halves of two different\n // maps, least of all that a service nobody drew is unmodelled, so this\n // returns the way `landscape.invalid` does. An error, gating by the default\n // rule and deliberately not carrying `gates` — that field is coherence's,\n // and the archive gate has to ask this question itself (it does not yet).\n // Read plainly rather than through FleetContext: this target runs outside\n // `guarded`, and a document refused for its encoding would surface here as\n // the whole run's `repository-unavailable` instead of one finding.\n //\n // Both reads are contained for that same reason — see `unreadableLandscape`.\n // The conflict-marker read is the FIRST touch of the file, so it is the one\n // that fails when the file cannot be opened at all, and it runs before\n // `preloaded ??` can spare it.\n let land: LoadedDoc;\n try {\n const conflict = landscapeConflictFinding(\n \"architecture/landscape.likec4\",\n await readFile(path, \"utf8\"),\n );\n if (conflict !== null) {\n findings.push(conflict);\n return report;\n }\n land = preloaded ?? (fleet === undefined ? await loadFile(path) : await fleet.loadLikeC4(path));\n } catch (err) {\n land = unreadableLandscape(err);\n }\n if (land.errors.length > 0) {\n // Nothing may be concluded from a document that did not parse — in particular\n // not that every service is unmodelled.\n //\n // The fleet map is a PROJECT now, not a file: `architecture/landscape.likec4`\n // plus every `architecture/usecases/*.likec4`, merged the way the renderer\n // merges them. So the message names the FILES that broke rather than the\n // landscape by default — a use case with a typo'd element used to be\n // reported as the landscape having errors, which sent the author to the\n // wrong file with a line number that pointed at somebody else's text.\n const broken = [...new Set(land.errors.map((e) => e.sourceFsPath).filter((p): p is string => p !== undefined))];\n const spell = (abs: string): string => relative(docsDir, abs).split(/[\\\\/]/).join(\"/\");\n const named = broken.length === 0 ? \"architecture/landscape.likec4\" : broken.map(spell).sort().join(\", \");\n // The verb agrees with the LIST, not with the count after it: `named` is a\n // comma-joined series once two documents broke, and \"a, b has 6 error(s)\"\n // is a sentence a reader trips over. The service arm (`service/spine.ts`)\n // picks the verb the same way off the same list, and these two messages are\n // read side by side in one report — a run where the fleet line says \"has\"\n // and the service line says \"have\" about the same two files reads as two\n // different findings.\n const verb = broken.length > 1 ? \"have\" : \"has\";\n findings.push({\n severity: \"error\",\n code: \"landscape.invalid\",\n // The tail is general on purpose. It used to speak about use-case flows\n // alone, so an author whose MODEL EDGE named a container was told to move\n // a flow that does not exist (#01). Both shapes are the same defect: loam\n // grades the `architecture/` project, the renderer loads that project plus\n // every extending model, and a reference to a container only the model\n // declares therefore resolves there and not here.\n message:\n `landscape: ${named} ${verb} ${land.errors.length} error(s) — cross-check with services/ impossible. ` +\n \"An unresolved name that a service's extending model declares — a use-case hop or an edge \" +\n \"naming `<service-fqn>.<container>` — renders for the renderer and resolves for nobody here: \" +\n \"name the SERVICE on the map, and the container in that service's own model (a container-level \" +\n \"flow lives beside the model that declares it)\",\n // Every line carries its own file, because one project's errors can come\n // from more than one document and a bare `L8:` is unactionable then.\n details: land.errors.map((e) =>\n e.sourceFsPath === undefined || broken.length < 2 ? errorText(e) : `${spell(e.sourceFsPath)} ${errorText(e)}`,\n ),\n });\n return report;\n }\n\n // The generated subsystem views, graded only now that the landscape has\n // parsed: the expected bytes are a function of (tree, landscape elements,\n // the global style ids the map declares) — the whole doc goes across so the\n // grader and `sync` read one record; `views/stale.ts` says why a byte\n // compare and why exactly one finding.\n findings.push(...(await viewsStaleFindings(docsDir, tree, land)));\n // The renderer's own reading of the tree: the map, every extending model and\n // every `.likec4` beside one, merged into ONE project. It answers two\n // questions at once and only one of them can be true at a time — either that\n // project does not parse (`c4.fleet-project-invalid`), or it parsed and its\n // view-id census is the real one.\n const merged = fleet === undefined\n ? { kind: \"skipped\" as const }\n : await fleetProjectFindings({\n docsDir, entries, shapes, fleet, tree,\n architecture: land,\n mapExcluded: projects.mapExcluded,\n known: new Set(entries.map((s) => s.id)),\n });\n if (merged.kind === \"invalid\") findings.push(...merged.findings);\n // Beside staleness, and for the same file: an authored view id that collides\n // with one loam mints into it takes the whole root project down in the\n // renderer while every check here stays green — see views/ids.ts. The census\n // is the merged project's when there is one; otherwise the `architecture/`\n // project's alone, whose claims are spelled relative to that directory and are\n // made docs-relative here.\n findings.push(\n ...viewIdFindings(\n merged.kind === \"clean\"\n ? merged.viewIds\n : land.viewIds?.map((claim) => ({ ...claim, sourcePath: `architecture/${claim.sourcePath}` })),\n tree,\n ),\n );\n // The three obligation questions that need the map — where each declared rule\n // is applied, which tags resolve to nothing, and which applications no living\n // arch requirement covers. Here rather than beside the vocabulary above\n // because all three are about the parsed landscape, and `land` is only\n // trustworthy past this point.\n findings.push(...(await obligationFindings({ docsDir, vocabulary: obligations, land, services: entries, fleet })));\n\n const services: ReadonlySet<string> = new Set(entries.map((s) => s.id));\n // Where each service actually sits, for every finding below that names a\n // directory a reader is meant to open. `services/<id>/` is right only for an\n // unfiled fleet; a filed service lives at `services/<subsystem>/…/<id>/`, and\n // a finding pointing at the root form sends the fix to a path that does not\n // exist. Built once here because three checks below need the same answer.\n const pathOf = (id: string): string => {\n const entry = entries.find((s) => s.id === id);\n return entry === undefined ? `services/${id}` : serviceTreePath(entry);\n };\n // Ahead of every exemption below, because it is the one defect that can make\n // all of them lie at once: a tag loam grades on, declared on a KIND, landing\n // on an element that stands for one of our own directories — see kind-tags.ts.\n findings.push(...kindTagFindings({ specification: land.specification, elements: land.elements, services }));\n const drawn = serviceLevelElements(land.elements, services);\n // Which services the landscape models, answered by the same resolver every\n // edge join uses — so \"modelled\" here and \"inbound edge\" in the spine check\n // can never disagree about what an element stands for.\n const landSvcOf = serviceResolver(land.elements, services);\n const modelled: ReadonlySet<string> = new Set(land.elements.map((e) => landSvcOf(e.id)));\n // The same set with `#external` removed — the isolation check's input, and\n // NOT `service-unmodelled`'s: a foreign box bound to one of our directories\n // still means the directory is drawn, but \"deliberately not ours\" must not\n // make it a subject of an advisory about our own map (R4/W12). The two\n // binding passes have always skipped the tag; this is the third.\n const external: ReadonlySet<string> = new Set(\n land.elements.filter((e) => e.tags.includes(EXTERNAL_TAG)).map((e) => e.id),\n );\n const ours: ReadonlySet<string> = new Set(\n land.elements.filter((e) => !external.has(e.id)).map((e) => landSvcOf(e.id)),\n );\n\n // The element↔directory passes: two boxes for one directory, a directory\n // nobody drew, a binding naming nothing, and an element nobody owns.\n const systems = drawnSystems(land.elements, services);\n findings.push(...bindingFindings({ elements: land.elements, drawn, systems, services, modelled, pathOf }));\n\n // What the fleet's own models say, read once for the two checks below that\n // need it: a map edge is not the only evidence that a service consumes\n // something, nor the only evidence that it calls out (map/attest.ts).\n const drawnIds: ReadonlySet<string> = new Set(drawn.map((e) => e.id));\n // `census.ts`'s predicate, handed down rather than respelled: a `fleet/map/`\n // module may not import `fleet/`, and the store grades were skipping a\n // datastore that IS a service in one of their two halves only.\n const isService = (e: Elem): boolean => standsForService(e, services);\n const attestation = await mapAttestation({\n docsDir,\n entries,\n services,\n resolve: landSvcOf,\n standsForService: isService,\n drawnIds,\n ...(fleet === undefined ? {} : { fleet }),\n });\n const attested = attestation.models;\n const consumers = consumerCensus({ relationships: land.relationships, services, resolve: landSvcOf, attested });\n // The shape advisories run LAST, over the same drawn set and resolver the\n // structural checks used. Any one of them suppresses `landscape.matched`\n // below on purpose: a map with a shape warning did not fully \"agree\".\n findings.push(...fleetShapeFindings({ drawn, services, consumers }));\n const { nestedStores } = attestation;\n findings.push(\n ...datastoreFindings({\n drawn,\n elements: land.elements,\n nestedStores,\n services,\n resolve: landSvcOf,\n standsForService: isService,\n attested,\n consumers,\n pathOf,\n }),\n );\n // A drawn service nothing reaches, graded only where its own model attests\n // a call across its boundary — map/isolation.ts says why the evidence gate.\n findings.push(...isolationFindings({ land, modelled: ours, external, attested, resolve: landSvcOf, pathOf }));\n\n // The use cases the SAME `architecture/` project declares — every\n // `dynamic view` in the landscape and in every `architecture/usecases/*.likec4`\n // — graded against the model above (`map/usecases.ts` holds the call and the\n // two vocabulary reads it needs; `usecases/usecases.ts` owns the opt-in and\n // the grades). They ride this target for `viewsStaleFindings`' reason: a view\n // belongs to no service, and only the fleet run has both the whole model and\n // the enumerated `services/` in view at once.\n findings.push(\n ...(await fleetUseCaseFindings({\n docsDir,\n views: preloaded?.views ?? [],\n elements: land.elements,\n relationships: land.relationships,\n services,\n resolve: landSvcOf,\n fleet,\n })),\n );\n\n if (findings.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"landscape.matched\",\n message: `landscape: ${services.size} service(s) modelled — architecture/landscape.likec4 and services/ agree`,\n });\n }\n return report;\n}\n"]}
1
+ {"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet target: `services/` and architecture/landscape.likec4 graded\n * against each other, plus the two exemptions that decide which drawn element\n * answers for a service directory at all.\n *\n * The landscape is the one artifact no service owns — parsed once for the whole\n * run and graded on a target of its own, outside the dispatcher's `guarded`.\n * That is why its IO containment lives in this package instead: see\n * `unreadableLandscape` in `./load.ts`.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { type Elem, type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { landscapePath as landscapeFile, servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { serviceIdFindings } from \"../../../core/repo/entries.js\";\nimport { listFleetTree, listServices } from \"../../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { landscapeConflictFinding } from \"../../../core/conflict-markers.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { capabilityFleetFindings, fleetShapeFindings, permissionFindings } from \"../checks/fleet-shape.js\";\nimport { fleetLinkFindings } from \"../links/corpus.js\";\nimport { glossaryFindings } from \"../links/glossary.js\";\nimport { obligationFindings, obligationVocabularyFindings } from \"./obligations.js\";\nimport { readObligations } from \"../../../core/obligations/obligations.js\";\nimport { obligationsPath } from \"../../../core/repo/paths.js\";\nimport { EXTERNAL_TAG } from \"../../../core/vocabulary/maturity.js\";\nimport { brokenDocumentPaths, errorText } from \"../../../core/c4/likec4.js\";\nimport { serviceTreePath, type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { drawnSystems, serviceLevelElements, standsForService } from \"./census.js\";\nimport { unreadableLandscape } from \"./load.js\";\nimport { kindTagFindings } from \"./kind-tags.js\";\nimport { mapAttestation } from \"./map/attest.js\";\nimport { bindingFindings } from \"./map/bindings.js\";\nimport { consumerCensus, datastoreFindings } from \"./map/consumers.js\";\nimport { isolationFindings } from \"./map/isolation.js\";\nimport { fleetUseCaseFindings } from \"./map/usecases.js\";\nimport { viewIdFindings } from \"./views/ids.js\";\nimport { viewsStaleFindings } from \"./views/stale.js\";\nimport { projectFindings } from \"./views/projects.js\";\nimport { fleetProjectFindings } from \"./views/fleet-project.js\";\nimport type { ParsedView } from \"../../../core/c4/parsed/dynamic-views.js\";\nimport { isUseCase } from \"../../../core/usecases/fleet.js\";\n\n/**\n * The fleet's flows as a grade may read them, or `null` when loam cannot see\n * them at all — the same `null`-suspends idiom `gradableCapabilityIds` uses,\n * and here it guards one specific wrong answer.\n *\n * `capability.requirement-unrealized` says a business promise is kept by\n * nobody. A use case is one of the two things that can keep it, so a run that\n * could not read the flows must not make that claim: no map at all (`null`)\n * and a project that did not parse are both \"loam did not look\", never\n * \"there is nothing there\". A preload that DID parse and declares no views is a\n * real, empty answer and grades normally.\n *\n * Filtered through `isUseCase` — the same opt-in `readUseCases` applies before\n * `loam list capabilities` asks this question — because the two surfaces must\n * never disagree about `keptBy`. Handing the unfiltered preload over was\n * harmless only by luck: an untagged view yields no claims, so the answers\n * matched for want of an input that could tell them apart. A `#req-`-only view\n * is exactly that input.\n */\nfunction gradableFlows(preloaded: LoadedDoc | null): readonly ParsedView[] | null {\n if (preloaded === null || preloaded.errors.length > 0) return null;\n return (preloaded.views ?? []).filter(isUseCase);\n}\n\n/**\n * The fleet cross-check: `services/` and the landscape both claim to name the\n * fleet, and nothing used to compare them. A directory nobody drew and an element\n * with nothing behind it were equally invisible.\n *\n * The two directions are graded differently because the evidence differs. A\n * directory that exists is a fact, so a landscape missing it is an error — every\n * view derived from that landscape is then incomplete. An element with no\n * directory may legitimately be someone else's system, so it warns, and\n * `#external` says \"deliberately not ours\" and silences it. An explicit\n * `metadata { service '<id>' }` naming nothing is an error either way: a binding\n * is a claim about this repo, not a guess at one.\n *\n * An ABSENT landscape is a finding, not a skipped check. It used to return null\n * — no target, no findings, and a fleet gate that went green over a docs repo\n * with no fleet map at all, which is the single artifact every derived view and\n * every spine check is computed from. It is graded by what its absence proves:\n * with services in `services/` it is an ERROR (a fleet that exists is undrawn),\n * with none it is a WARNING (a docs repo before its first adopt legitimately\n * has nothing to draw, but the file still belongs there — `loam init` scaffolds\n * it, and a repo missing it will silently accept never getting one).\n *\n * `preloaded` is the already-parsed landscape under --all — the same doc every\n * service check gets.\n */\nexport async function validateLandscape(\n docsDir: DocsDir,\n preloaded: LoadedDoc | null,\n fleet: FleetContext,\n): Promise<TargetReport> {\n const path = landscapeFile(docsDir);\n const findings: Finding[] = [];\n const report: TargetReport = {\n kind: \"landscape\",\n id: \"landscape\",\n path: \"architecture/landscape.likec4\",\n findings,\n };\n\n // The SET of service directories is this target's other subject, so it is\n // graded before the map is even opened: `service.id-invalid` is a fact about\n // `services/` that holds whether or not a landscape exists or parses, and\n // both of those return early below. Emitted here and nowhere else — the\n // enumeration is what makes the id a question, and one finding per fleet is\n // what the rename fixes.\n const entries = await listServices(docsDir, fleet);\n findings.push(...serviceIdFindings(entries));\n // The tree walk's own findings ride the same fleet target, before the map's\n // early returns for the same reason `service.id-invalid` does: a stranded,\n // colliding or mismarked directory is a fact about `services/` that holds\n // whether or not a landscape exists or parses — and a broken tree must make\n // the fleet gate refuse while still naming every service it found.\n const tree = await listFleetTree(docsDir, fleet);\n findings.push(...tree.findings);\n // Which SHAPE each model has, read once for the whole target: it decides four\n // of the grades below (`views/projects.ts`) and which documents the merged\n // project holds (`views/fleet-project.ts`). Over the full enumeration, which\n // is what `entries` is here even under `--base` — `validate --all` narrows the\n // TARGETS it grades and never the fleet the renderer would load.\n const models = entries.map((entry) => servicePathsAt(entry.dir).model);\n const shapes = await fleet.modelShapes(models);\n // A model and the renderer's project files disagreeing is a fact about\n // `services/` too, graded here for `service.id-invalid`'s reason: it holds\n // whether or not the map parses. One warning per service per disagreement;\n // views/projects.ts says why a warning, why this run and not `doctor`, why\n // the root-file gate, and why the grade is KEPT rather than spread and\n // dropped — one of the five gates the merged-project load below, and\n // re-deriving that fact there would let a cause and its 161-warning cascade\n // disagree about which state the run is in.\n const projects = await projectFindings(docsDir, entries, shapes);\n findings.push(...projects.findings);\n // Before the landscape's own early returns: the authorization and capability\n // vocabularies are fleet facts that do not depend on the map existing or parsing.\n findings.push(...(await permissionFindings(docsDir, entries, fleet)));\n findings.push(...(await capabilityFleetFindings(docsDir, entries, fleet, gradableFlows(preloaded))));\n // The documents that belong to no service and no feature — the fleet's ADRs\n // and the living capability tree — are graded for their links here, once,\n // for the same reason the two vocabularies above are: a fleet fact repeated\n // on every service target is the report.\n findings.push(...(await fleetLinkFindings({ docsDir, fleet })));\n // The glossary is graded on who cites it, which is a question only the whole\n // repository answers — so it belongs here, beside the two vocabularies, and\n // nowhere else.\n findings.push(...(await glossaryFindings(docsDir, fleet)));\n // The architectural obligation vocabulary, read ONCE here and handed to the\n // map half below: its own two verdicts hold whether or not the landscape\n // exists or parses, and a run where somebody is fixing the map must still be\n // told that this file does not read.\n const obligations = await readObligations(obligationsPath(docsDir));\n findings.push(...obligationVocabularyFindings(docsDir, obligations));\n\n if (!existsSync(path)) {\n const count = entries.length;\n findings.push({\n severity: count > 0 ? \"error\" : \"warn\",\n code: \"landscape.missing\",\n message:\n `landscape: architecture/landscape.likec4 does not exist — ` +\n (count > 0\n ? `${count} service(s) are adopted and nothing draws the fleet. `\n : \"nothing draws the fleet. \") +\n \"It is the one map every derived view and the C4↔API spine are computed from: \" +\n \"write a `specification { element softwareSystem }` + `model { … }` document there \" +\n \"with one element per services/<id>/, bound with `metadata { service '<id>' }`.\",\n });\n return report;\n }\n\n // Conflict markers before the parse — `loam doctor`'s order, and for its\n // reason: the markers are the cause and the parse errors are the cascade.\n // Nothing may be concluded from a file that is two halves of two different\n // maps, least of all that a service nobody drew is unmodelled, so this\n // returns the way `landscape.invalid` does. An error, gating by the default\n // rule and deliberately not carrying `gates` — that field is coherence's,\n // and the archive gate has to ask this question itself (it does not yet).\n // Read plainly rather than through FleetContext: this target runs outside\n // `guarded`, and a document refused for its encoding would surface here as\n // the whole run's `repository-unavailable` instead of one finding.\n //\n // Both reads are contained for that same reason — see `unreadableLandscape`.\n // The conflict-marker read is the FIRST touch of the file, so it is the one\n // that fails when the file cannot be opened at all, and it runs before\n // `preloaded ??` can spare it.\n let land: LoadedDoc;\n try {\n const conflict = landscapeConflictFinding(\n \"architecture/landscape.likec4\",\n await readFile(path, \"utf8\"),\n );\n if (conflict !== null) {\n findings.push(conflict);\n return report;\n }\n land = preloaded ?? (await fleet.loadLikeC4(path));\n } catch (err) {\n land = unreadableLandscape(err);\n }\n if (land.errors.length > 0) {\n // Nothing may be concluded from a document that did not parse — in particular\n // not that every service is unmodelled.\n //\n // The fleet map is a PROJECT now, not a file: `architecture/landscape.likec4`\n // plus every `architecture/usecases/*.likec4`, merged the way the renderer\n // merges them. So the message names the FILES that broke rather than the\n // landscape by default — a use case with a typo'd element used to be\n // reported as the landscape having errors, which sent the author to the\n // wrong file with a line number that pointed at somebody else's text.\n const broken = brokenDocumentPaths(docsDir, land.errors);\n const named = broken.length === 0 ? \"architecture/landscape.likec4\" : broken.join(\", \");\n // The verb agrees with the LIST, not with the count after it: `named` is a\n // comma-joined series once two documents broke, and \"a, b has 6 error(s)\"\n // is a sentence a reader trips over. The service arm (`service/spine.ts`)\n // picks the verb the same way off the same list, and these two messages are\n // read side by side in one report — a run where the fleet line says \"has\"\n // and the service line says \"have\" about the same two files reads as two\n // different findings.\n const verb = broken.length > 1 ? \"have\" : \"has\";\n findings.push({\n severity: \"error\",\n code: \"landscape.invalid\",\n // The tail is general on purpose. It used to speak about use-case flows\n // alone, so an author whose MODEL EDGE named a container was told to move\n // a flow that does not exist (#01). Both shapes are the same defect: loam\n // grades the `architecture/` project, the renderer loads that project plus\n // every extending model, and a reference to a container only the model\n // declares therefore resolves there and not here.\n message:\n `landscape: ${named} ${verb} ${land.errors.length} error(s) — cross-check with services/ impossible. ` +\n \"An unresolved name that a service's extending model declares — a use-case hop or an edge \" +\n \"naming `<service-fqn>.<container>` — renders for the renderer and resolves for nobody here: \" +\n \"name the SERVICE on the map, and the container in that service's own model (a container-level \" +\n \"flow lives beside the model that declares it)\",\n // Every line carries its own file, because one project's errors can come\n // from more than one document and a bare `L8:` is unactionable then. The\n // prefix is `brokenDocumentPaths`'s spelling of that one error's document,\n // so the message's file list and each line's prefix are one rule.\n details: land.errors.map((e) => {\n const [file] = broken.length < 2 ? [] : brokenDocumentPaths(docsDir, [e]);\n return file === undefined ? errorText(e) : `${file} ${errorText(e)}`;\n }),\n });\n return report;\n }\n\n // The generated views files, graded only now that the landscape has parsed:\n // the expected bytes are a function of (tree, landscape elements, the global\n // style ids the map declares) — the whole doc goes across so the grader and\n // `sync` read one record. There are TWO generated files now\n // (`subsystems.likec4` and `services.likec4`) and this pushes ONE error PER\n // STALE FILE, each naming its own file; `views/stale.ts` says why a content\n // compare, and why two files share one code but never one finding.\n findings.push(...(await viewsStaleFindings(docsDir, tree, land)));\n // The renderer's own reading of the tree: the map, every extending model and\n // every `.likec4` beside one, merged into ONE project. It answers two\n // questions at once and only one of them can be true at a time — either that\n // project does not parse (`c4.fleet-project-invalid`), or it parsed and its\n // view-id census is the real one.\n const merged = await fleetProjectFindings({\n docsDir, entries, shapes, fleet, tree,\n architecture: land,\n mapExcluded: projects.mapExcluded,\n known: new Set(entries.map((s) => s.id)),\n });\n if (merged.kind === \"invalid\") findings.push(...merged.findings);\n // Beside staleness, and for the same file: an authored view id that collides\n // with one loam mints into it takes the whole root project down in the\n // renderer while every check here stays green — see views/ids.ts. The census\n // is the merged project's when there is one; otherwise the `architecture/`\n // project's alone, whose claims are spelled relative to that directory and are\n // made docs-relative here.\n // `land` goes across for the SERVICE half of the reservation: an id is minted\n // per service the map DRAWS, so the census needs the render's own element join.\n const claims =\n merged.kind === \"clean\"\n ? merged.viewIds\n : land.viewIds?.map((claim) => ({ ...claim, sourcePath: `architecture/${claim.sourcePath}` }));\n findings.push(...viewIdFindings(claims, tree, land));\n // The three obligation questions that need the map — where each declared rule\n // is applied, which tags resolve to nothing, and which applications no living\n // arch requirement covers. Here rather than beside the vocabulary above\n // because all three are about the parsed landscape, and `land` is only\n // trustworthy past this point.\n findings.push(...(await obligationFindings({ docsDir, vocabulary: obligations, land, services: entries, fleet })));\n\n const services: ReadonlySet<string> = new Set(entries.map((s) => s.id));\n // Where each service actually sits, for every finding below that names a\n // directory a reader is meant to open. `services/<id>/` is right only for an\n // unfiled fleet; a filed service lives at `services/<subsystem>/…/<id>/`, and\n // a finding pointing at the root form sends the fix to a path that does not\n // exist. Built once here because three checks below need the same answer.\n const pathOf = (id: string): string => {\n const entry = entries.find((s) => s.id === id);\n return entry === undefined ? `services/${id}` : serviceTreePath(entry);\n };\n // Ahead of every exemption below, because it is the one defect that can make\n // all of them lie at once: a tag loam grades on, declared on a KIND, landing\n // on an element that stands for one of our own directories — see kind-tags.ts.\n findings.push(...kindTagFindings({ specification: land.specification, elements: land.elements, services }));\n const drawn = serviceLevelElements(land.elements, services);\n // Which services the landscape models, answered by the same resolver every\n // edge join uses — so \"modelled\" here and \"inbound edge\" in the spine check\n // can never disagree about what an element stands for.\n const landSvcOf = serviceResolver(land.elements, services);\n const modelled: ReadonlySet<string> = new Set(land.elements.map((e) => landSvcOf(e.id)));\n // The same set with `#external` removed — the isolation check's input, and\n // NOT `service-unmodelled`'s: a foreign box bound to one of our directories\n // still means the directory is drawn, but \"deliberately not ours\" must not\n // make it a subject of an advisory about our own map (R4/W12). The two\n // binding passes have always skipped the tag; this is the third.\n const external: ReadonlySet<string> = new Set(\n land.elements.filter((e) => e.tags.includes(EXTERNAL_TAG)).map((e) => e.id),\n );\n const ours: ReadonlySet<string> = new Set(\n land.elements.filter((e) => !external.has(e.id)).map((e) => landSvcOf(e.id)),\n );\n\n // The element↔directory passes: two boxes for one directory, a directory\n // nobody drew, a binding naming nothing, and an element nobody owns.\n const systems = drawnSystems(land.elements, services);\n findings.push(...bindingFindings({ elements: land.elements, drawn, systems, services, modelled, pathOf }));\n\n // What the fleet's own models say, read once for the two checks below that\n // need it: a map edge is not the only evidence that a service consumes\n // something, nor the only evidence that it calls out (map/attest.ts).\n const drawnIds: ReadonlySet<string> = new Set(drawn.map((e) => e.id));\n // `census.ts`'s predicate, handed down rather than respelled: a `fleet/map/`\n // module may not import `fleet/`, and the store grades were skipping a\n // datastore that IS a service in one of their two halves only.\n const isService = (e: Elem): boolean => standsForService(e, services);\n const attestation = await mapAttestation({\n docsDir,\n entries,\n services,\n resolve: landSvcOf,\n standsForService: isService,\n drawnIds,\n fleet,\n });\n const attested = attestation.models;\n const consumers = consumerCensus({ relationships: land.relationships, services, resolve: landSvcOf, attested });\n // The shape advisories run LAST, over the same drawn set and resolver the\n // structural checks used. Any one of them suppresses `landscape.matched`\n // below on purpose: a map with a shape warning did not fully \"agree\".\n findings.push(...fleetShapeFindings({ drawn, services, consumers }));\n const { nestedStores } = attestation;\n findings.push(\n ...datastoreFindings({\n drawn,\n elements: land.elements,\n nestedStores,\n services,\n resolve: landSvcOf,\n standsForService: isService,\n consumers,\n }),\n );\n // A drawn service nothing reaches, graded only where its own model attests\n // a call across its boundary — map/isolation.ts says why the evidence gate.\n findings.push(...isolationFindings({ land, modelled: ours, external, attested, resolve: landSvcOf, pathOf }));\n\n // The use cases the SAME `architecture/` project declares — every\n // `dynamic view` in the landscape and in every `architecture/usecases/*.likec4`\n // — graded against the model above (`map/usecases.ts` holds the call and the\n // two vocabulary reads it needs; `usecases/usecases.ts` owns the opt-in and\n // the grades). They ride this target for `viewsStaleFindings`' reason: a view\n // belongs to no service, and only the fleet run has both the whole model and\n // the enumerated `services/` in view at once.\n findings.push(\n ...(await fleetUseCaseFindings({\n docsDir,\n views: preloaded?.views ?? [],\n elements: land.elements,\n relationships: land.relationships,\n services,\n resolve: landSvcOf,\n fleet,\n })),\n );\n\n if (findings.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"landscape.matched\",\n message: `landscape: ${services.size} service(s) modelled — architecture/landscape.likec4 and services/ agree`,\n });\n }\n return report;\n}\n"]}
@@ -5,13 +5,10 @@
5
5
  * element (`../../checks/fleet-shape.ts`).
6
6
  *
7
7
  * One reader rather than two, because the two checks were answering the same
8
- * question from different evidence and disagreeing about the fleet. The
9
- * consumer census walked the MAP's relationships alone, so a store whose
10
- * consuming edges are all drawn in `services/<…>/model.likec4` had zero
11
- * consumers and earned nothing at all — and a store reached by three models plus
12
- * one map edge was reported `landscape.datastore-private` naming the single
13
- * service the MAP happened to draw (R1). A model is evidence about the fleet
14
- * exactly as the map is, so it is read here and handed to both.
8
+ * question from different evidence and disagreeing about the fleet — the
9
+ * map-only consumer census `./consumers.ts` tells the story of (R1). A model is
10
+ * evidence about the fleet exactly as the map is, so it is read here and handed
11
+ * to both.
15
12
  *
16
13
  * EXTENDING MODELS ONLY, for the consumer join. An extending model's ids ARE the
17
14
  * map's fully-qualified ids — one project, one id space — so `counterpartId`
@@ -37,8 +34,7 @@ import { FleetContext } from "../../../../core/fleet-context.js";
37
34
  import { inOrder } from "../../../../core/kernel/concurrency.js";
38
35
  import {} from "../../../../core/repo/entries.js";
39
36
  import { servicePathsAt } from "../../../../core/repo/paths.js";
40
- /** The element kind read as a datastore, compared case-insensitively — `fleet-shape.ts`'s spelling. */
41
- const DATASTORE_KIND = "database";
37
+ import { DATASTORE_KIND } from "../../../../core/vocabulary/maturity.js";
42
38
  export async function mapAttestation(input) {
43
39
  // Through the ONE reader of `model.likec4` (`core/c4/service-model/load.ts`),
44
40
  // so a model that EXTENDS the map attests the calls it draws inside the
@@ -46,7 +42,6 @@ export async function mapAttestation(input) {
46
42
  // as a standalone one attests the calls it draws in its own file. Memoised in
47
43
  // the index, so under `--all` these are hits. Pooled and joined on the entry,
48
44
  // never by index.
49
- const context = input.fleet ?? new FleetContext();
50
45
  const present = input.entries.filter((entry) => existsSync(servicePathsAt(entry.dir).model));
51
46
  // ONE workspace for the fleet's models rather than one per service. Both
52
47
  // answers below are about the WHOLE fleet — who else reaches this store is not
@@ -55,7 +50,7 @@ export async function mapAttestation(input) {
55
50
  // workspace per un-narrowed service. An accelerator with `prefetchLikeC4`'s
56
51
  // failure story exactly: a batch that cannot run seeds nothing, and every read
57
52
  // below falls back to its own load.
58
- await context.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));
53
+ await input.fleet.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));
59
54
  const loaded = await inOrder(present, async (entry) => ({
60
55
  entry,
61
56
  // CONTAINED, for `fleet/load.ts unreadableLandscape`'s reason: this runs on
@@ -65,7 +60,7 @@ export async function mapAttestation(input) {
65
60
  // no report at all for the ninety-nine services that are fine. A model that
66
61
  // could not be READ contributes nothing here, exactly as one that did not
67
62
  // parse does; `service.unreadable` is its own target's finding.
68
- model: await context.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),
63
+ model: await input.fleet.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),
69
64
  }));
70
65
  const models = [];
71
66
  const nestedStores = [];
@@ -1 +1 @@
1
- {"version":3,"file":"attest.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/attest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the fleet's own `model.likec4` files say, read ONCE for the two map\n * checks that need it: the calls each model attests across its boundary\n * (`./isolation.ts`), and the datastores that live nested inside a service's own\n * element (`../../checks/fleet-shape.ts`).\n *\n * One reader rather than two, because the two checks were answering the same\n * question from different evidence and disagreeing about the fleet. The\n * consumer census walked the MAP's relationships alone, so a store whose\n * consuming edges are all drawn in `services/<…>/model.likec4` had zero\n * consumers and earned nothing at all — and a store reached by three models plus\n * one map edge was reported `landscape.datastore-private` naming the single\n * service the MAP happened to draw (R1). A model is evidence about the fleet\n * exactly as the map is, so it is read here and handed to both.\n *\n * EXTENDING MODELS ONLY, for the consumer join. An extending model's ids ARE the\n * map's fully-qualified ids — one project, one id space — so `counterpartId`\n * joins to a drawn element exactly. A standalone model's ids are its own file's,\n * and `../../../../core/c4/resolve/attested.ts` says outright that a counterpart\n * is never matched to a landscape element: joining one would file a coincidence\n * of spelling as a coupling. Standalone models still contribute their attested\n * CALLS, because `landscape.service-isolated` asks only whether the model\n * declares any, which is shape-independent.\n *\n * A model that does not parse contributes nothing — neither calls nor stores.\n * `c4.invalid` is that file's finding, and a fleet-shape advisory computed from\n * a document nobody could read would be an inference about bytes.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it\n * imports NOTHING from `fleet/`, so the map facts it needs (the resolver, the\n * ids the map draws at fleet level) arrive on the input record.\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem } from \"../../../../core/c4/likec4.js\";\nimport { attestedCalls, type AttestedCall } from \"../../../../core/c4/resolve/attested.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { type ServiceEntry } from \"../../../../core/repo/entries.js\";\nimport { servicePathsAt } from \"../../../../core/repo/paths.js\";\n\n/** The element kind read as a datastore, compared case-insensitively — `fleet-shape.ts`'s spelling. */\nconst DATASTORE_KIND = \"database\";\n\n/** One service's model, as the map checks read it. */\nexport interface AttestedModel {\n /** The service directory this model belongs to. */\n service: string;\n /** EXTENDING — and therefore whether `counterpartId` is a map id at all. */\n extending: boolean;\n calls: readonly AttestedCall[];\n}\n\n/** A datastore drawn INSIDE the element that resolves to its owner, rather than beside it. */\nexport interface NestedStore {\n /** The service whose element holds it. */\n owner: string;\n /** The store's id as the one project spells it — `marketplace.orderService.db`. */\n fqn: string;\n title: string;\n}\n\nexport interface MapAttestation {\n models: readonly AttestedModel[];\n nestedStores: readonly NestedStore[];\n}\n\nexport interface AttestInput {\n docsDir: DocsDir;\n /** The enumerated services — every one of them, not a candidate subset: both readers ask about the whole fleet. */\n entries: readonly ServiceEntry[];\n /** The services/<id>/ directories that exist, as the resolver's `known`. */\n services: ReadonlySet<string>;\n /** The MAP's element→service resolver — what decides whose element a nested id sits under. */\n resolve: (id: string) => string;\n /**\n * `census.ts`'s one predicate for \"this element stands for a `services/<id>/`\",\n * injected because a `fleet/map/` module may not import `fleet/`. A `database`\n * that IS a service directory is the fleet itself, not a store: the binding\n * checks grade it, exactly as they do in `./consumers.ts`'s two halves.\n */\n standsForService: (e: Elem) => boolean;\n /** The ids the map draws at FLEET level: a datastore among them is a peer, and the peer census owns it. */\n drawnIds: ReadonlySet<string>;\n /** Under `--all` every model.likec4 is already in this memo, so the reads below are hits. */\n fleet?: FleetContext;\n}\n\nexport async function mapAttestation(input: AttestInput): Promise<MapAttestation> {\n // Through the ONE reader of `model.likec4` (`core/c4/service-model/load.ts`),\n // so a model that EXTENDS the map attests the calls it draws inside the\n // element the map binds — read as a slice of the per-service project — exactly\n // as a standalone one attests the calls it draws in its own file. Memoised in\n // the index, so under `--all` these are hits. Pooled and joined on the entry,\n // never by index.\n const context = input.fleet ?? new FleetContext();\n const present = input.entries.filter((entry) => existsSync(servicePathsAt(entry.dir).model));\n // ONE workspace for the fleet's models rather than one per service. Both\n // answers below are about the WHOLE fleet — who else reaches this store is not\n // a question a narrowed run may answer differently — while `--base` prefetches\n // only the scope it grades, so without this a scoped run would spin a LikeC4\n // workspace per un-narrowed service. An accelerator with `prefetchLikeC4`'s\n // failure story exactly: a batch that cannot run seeds nothing, and every read\n // below falls back to its own load.\n await context.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));\n const loaded = await inOrder(present, async (entry) => ({\n entry,\n // CONTAINED, for `fleet/load.ts unreadableLandscape`'s reason: this runs on\n // the landscape target, which sits outside the dispatcher's `guarded`, so a\n // `model.likec4` that is a directory or carries a permission bit would\n // otherwise become the whole run's `repository-unavailable` — one file, and\n // no report at all for the ninety-nine services that are fine. A model that\n // could not be READ contributes nothing here, exactly as one that did not\n // parse does; `service.unreadable` is its own target's finding.\n model: await context.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),\n }));\n\n const models: AttestedModel[] = [];\n const nestedStores: NestedStore[] = [];\n for (const { entry, model } of loaded) {\n // A model that does not parse attests nothing: `c4.invalid` is its finding.\n // An extending model whose MAP does not parse could not be read at all, and\n // neither reader runs in that state anyway — the fleet target returns on\n // `landscape.invalid` long before here.\n if (model === null || model.mapUnreadable || model.doc.errors.length > 0) continue;\n models.push({\n service: entry.id,\n extending: model.shape === \"extending\",\n calls: attestedCalls(model.doc, entry.id, input.services),\n });\n if (model.shape !== \"extending\") continue;\n for (const element of model.doc.elements) {\n if (element.kind.toLowerCase() !== DATASTORE_KIND) continue;\n // The slice carries the partners this model's own edges point at, and a\n // partner may be another service's store — so the store is kept only when\n // the MAP's resolver files it under this service, and only when the map\n // does not draw it as a peer of the services (which is the fleet-level\n // census's subject, and a different grade).\n if (input.standsForService(element)) continue;\n if (input.drawnIds.has(element.id) || input.resolve(element.id) !== (entry.id as string)) continue;\n nestedStores.push({ owner: entry.id, fqn: element.id, title: element.title });\n }\n }\n return { models, nestedStores };\n}\n"]}
1
+ {"version":3,"file":"attest.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/attest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the fleet's own `model.likec4` files say, read ONCE for the two map\n * checks that need it: the calls each model attests across its boundary\n * (`./isolation.ts`), and the datastores that live nested inside a service's own\n * element (`../../checks/fleet-shape.ts`).\n *\n * One reader rather than two, because the two checks were answering the same\n * question from different evidence and disagreeing about the fleet — the\n * map-only consumer census `./consumers.ts` tells the story of (R1). A model is\n * evidence about the fleet exactly as the map is, so it is read here and handed\n * to both.\n *\n * EXTENDING MODELS ONLY, for the consumer join. An extending model's ids ARE the\n * map's fully-qualified ids — one project, one id space — so `counterpartId`\n * joins to a drawn element exactly. A standalone model's ids are its own file's,\n * and `../../../../core/c4/resolve/attested.ts` says outright that a counterpart\n * is never matched to a landscape element: joining one would file a coincidence\n * of spelling as a coupling. Standalone models still contribute their attested\n * CALLS, because `landscape.service-isolated` asks only whether the model\n * declares any, which is shape-independent.\n *\n * A model that does not parse contributes nothing — neither calls nor stores.\n * `c4.invalid` is that file's finding, and a fleet-shape advisory computed from\n * a document nobody could read would be an inference about bytes.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it\n * imports NOTHING from `fleet/`, so the map facts it needs (the resolver, the\n * ids the map draws at fleet level) arrive on the input record.\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem } from \"../../../../core/c4/likec4.js\";\nimport { attestedCalls, type AttestedCall } from \"../../../../core/c4/resolve/attested.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { type ServiceEntry } from \"../../../../core/repo/entries.js\";\nimport { servicePathsAt } from \"../../../../core/repo/paths.js\";\nimport { DATASTORE_KIND } from \"../../../../core/vocabulary/maturity.js\";\n\n/** One service's model, as the map checks read it. */\nexport interface AttestedModel {\n /** The service directory this model belongs to. */\n service: string;\n /** EXTENDING — and therefore whether `counterpartId` is a map id at all. */\n extending: boolean;\n calls: readonly AttestedCall[];\n}\n\n/** A datastore drawn INSIDE the element that resolves to its owner, rather than beside it. */\nexport interface NestedStore {\n /** The service whose element holds it. */\n owner: string;\n /** The store's id as the one project spells it — `marketplace.orderService.db`. */\n fqn: string;\n title: string;\n}\n\nexport interface MapAttestation {\n models: readonly AttestedModel[];\n nestedStores: readonly NestedStore[];\n}\n\nexport interface AttestInput {\n docsDir: DocsDir;\n /** The enumerated services — every one of them, not a candidate subset: both readers ask about the whole fleet. */\n entries: readonly ServiceEntry[];\n /** The services/<id>/ directories that exist, as the resolver's `known`. */\n services: ReadonlySet<string>;\n /** The MAP's element→service resolver — what decides whose element a nested id sits under. */\n resolve: (id: string) => string;\n /**\n * `census.ts`'s one predicate for \"this element stands for a `services/<id>/`\",\n * injected because a `fleet/map/` module may not import `fleet/`. A `database`\n * that IS a service directory is the fleet itself, not a store: the binding\n * checks grade it, exactly as they do in `./consumers.ts`'s two halves.\n */\n standsForService: (e: Elem) => boolean;\n /** The ids the map draws at FLEET level: a datastore among them is a peer, and the peer census owns it. */\n drawnIds: ReadonlySet<string>;\n /** Under `--all` every model.likec4 is already in this memo, so the reads below are hits. */\n fleet: FleetContext;\n}\n\nexport async function mapAttestation(input: AttestInput): Promise<MapAttestation> {\n // Through the ONE reader of `model.likec4` (`core/c4/service-model/load.ts`),\n // so a model that EXTENDS the map attests the calls it draws inside the\n // element the map binds — read as a slice of the per-service project — exactly\n // as a standalone one attests the calls it draws in its own file. Memoised in\n // the index, so under `--all` these are hits. Pooled and joined on the entry,\n // never by index.\n const present = input.entries.filter((entry) => existsSync(servicePathsAt(entry.dir).model));\n // ONE workspace for the fleet's models rather than one per service. Both\n // answers below are about the WHOLE fleet — who else reaches this store is not\n // a question a narrowed run may answer differently — while `--base` prefetches\n // only the scope it grades, so without this a scoped run would spin a LikeC4\n // workspace per un-narrowed service. An accelerator with `prefetchLikeC4`'s\n // failure story exactly: a batch that cannot run seeds nothing, and every read\n // below falls back to its own load.\n await input.fleet.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));\n const loaded = await inOrder(present, async (entry) => ({\n entry,\n // CONTAINED, for `fleet/load.ts unreadableLandscape`'s reason: this runs on\n // the landscape target, which sits outside the dispatcher's `guarded`, so a\n // `model.likec4` that is a directory or carries a permission bit would\n // otherwise become the whole run's `repository-unavailable` — one file, and\n // no report at all for the ninety-nine services that are fine. A model that\n // could not be READ contributes nothing here, exactly as one that did not\n // parse does; `service.unreadable` is its own target's finding.\n model: await input.fleet.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),\n }));\n\n const models: AttestedModel[] = [];\n const nestedStores: NestedStore[] = [];\n for (const { entry, model } of loaded) {\n // A model that does not parse attests nothing: `c4.invalid` is its finding.\n // An extending model whose MAP does not parse could not be read at all, and\n // neither reader runs in that state anyway — the fleet target returns on\n // `landscape.invalid` long before here.\n if (model === null || model.mapUnreadable || model.doc.errors.length > 0) continue;\n models.push({\n service: entry.id,\n extending: model.shape === \"extending\",\n calls: attestedCalls(model.doc, entry.id, input.services),\n });\n if (model.shape !== \"extending\") continue;\n for (const element of model.doc.elements) {\n if (element.kind.toLowerCase() !== DATASTORE_KIND) continue;\n // The slice carries the partners this model's own edges point at, and a\n // partner may be another service's store — so the store is kept only when\n // the MAP's resolver files it under this service, and only when the map\n // does not draw it as a peer of the services (which is the fleet-level\n // census's subject, and a different grade).\n if (input.standsForService(element)) continue;\n if (input.drawnIds.has(element.id) || input.resolve(element.id) !== (entry.id as string)) continue;\n nestedStores.push({ owner: entry.id, fqn: element.id, title: element.title });\n }\n }\n return { models, nestedStores };\n}\n"]}
@@ -11,14 +11,12 @@
11
11
  * caller's business, which is why this is one function rather than four.
12
12
  *
13
13
  * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule: it
14
- * imports NOTHING from `fleet/` itself — a child reaching back into its parent
15
- * while the parent calls the child is the package cycle
16
- * `scripts/package-graph.mjs` refuses — so the two census derivations arrive as
14
+ * imports NOTHING from `fleet/` itself, so the two census derivations arrive as
17
15
  * data on the input record rather than as an import of `../census.js`.
18
16
  */
19
17
  import {} from "../../../../core/c4/likec4.js";
20
18
  import { elementService } from "../../../../core/c4/resolve/service.js";
21
- import { EXTERNAL_TAG } from "../../../../core/vocabulary/maturity.js";
19
+ import { EXTERNAL_TAG, INFRA_KINDS } from "../../../../core/vocabulary/maturity.js";
22
20
  import {} from "../../../../core/vocabulary/report.js";
23
21
  export function bindingFindings(input) {
24
22
  const { elements, drawn, services, pathOf } = input;
@@ -79,6 +77,13 @@ export function bindingFindings(input) {
79
77
  // so the map's exemptions and the fleet rollup cannot drift — with the two
80
78
  // residual skips that are not exemptions: a bound element is the binding
81
79
  // pass's subject above, and a title naming a real directory is documented.
80
+ //
81
+ // Infrastructure the fleet OWNS is out of `systems` upstream, in
82
+ // `../census.ts drawnSystems` (INFRA_KINDS), and it had to be: this message
83
+ // offers exactly two remedies and BOTH are false of an owned datastore — there
84
+ // is no `services/<id>/` to bind, and `#external` would claim somebody else
85
+ // runs it. Measured on a one-service fixture drawing an owned `database` at
86
+ // fleet level (2026-09-07), it fired here and had no correct fix.
82
87
  for (const e of input.systems) {
83
88
  if (e.service !== undefined)
84
89
  continue; // graded by the binding pass above
@@ -88,7 +93,13 @@ export function bindingFindings(input) {
88
93
  severity: "warn",
89
94
  code: "landscape.service-undocumented",
90
95
  subject: e.title,
91
- message: `landscape: '${e.title}' has no services/${e.title}/ — bind it with metadata { service '<id>' }, or tag it #${EXTERNAL_TAG} if it is not ours`,
96
+ // The third remedy is spelled from `INFRA_KINDS` rather than typed out,
97
+ // for the reason the set exists at all: the hook is a kind NAME, so a
98
+ // fleet whose specification spells its store `postgres` still lands here,
99
+ // and this sentence is the only place that tells its author which
100
+ // spellings loam reads. A prose copy would be a second list free to drift
101
+ // from the one the census walks.
102
+ message: `landscape: '${e.title}' has no services/${e.title}/ — bind it with metadata { service '<id>' }, tag it #${EXTERNAL_TAG} if it is not ours, or give it one of the kinds loam reads as infrastructure (${[...INFRA_KINDS].join(", ")}) if it is a store, queue or bucket the fleet runs rather than a system somebody adopts`,
92
103
  });
93
104
  }
94
105
  return findings;
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/bindings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The element↔directory passes of the fleet cross-check: which drawn box\n * answers for which `services/<id>/`, and what it means when the answer is\n * none, two, or one nobody owns.\n *\n * Lifted out of `../landscape.ts` when that module reached its line limit, and\n * the seam is a phase rather than a subject: everything here runs after the map\n * has parsed, reads nothing from disk, and grades exactly one join — the census\n * `../census.ts` derives against the enumeration `../landscape.ts` performed.\n * The four grades are ordered as the report reads them, and the ORDER is the\n * caller's business, which is why this is one function rather than four.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule: it\n * imports NOTHING from `fleet/` itself — a child reaching back into its parent\n * while the parent calls the child is the package cycle\n * `scripts/package-graph.mjs` refuses — so the two census derivations arrive as\n * data on the input record rather than as an import of `../census.js`.\n */\nimport { type Elem } from \"../../../../core/c4/likec4.js\";\nimport { elementService } from \"../../../../core/c4/resolve/service.js\";\nimport { EXTERNAL_TAG } from \"../../../../core/vocabulary/maturity.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\n\nexport interface BindingInput {\n /** Every element the map declares, at any depth — a binding answers for itself wherever it is written. */\n elements: Elem[];\n /** Service-LEVEL elements (`census.ts serviceLevelElements`) — the fleet map's own boxes. */\n drawn: Elem[];\n /** The drawn SYSTEMS (`census.ts drawnSystems`) — `drawn` minus what the map itself says is no service. */\n systems: Elem[];\n /** The services/<id>/ directories that exist. */\n services: ReadonlySet<string>;\n /** The services some element resolves to — the set `landscape.service-unmodelled` reads. */\n modelled: ReadonlySet<string>;\n /** Where a service's directory sits, repo-relative — the enumeration's answer, never `services/<id>`. */\n pathOf: (id: string) => string;\n}\n\nexport function bindingFindings(input: BindingInput): Finding[] {\n const { elements, drawn, services, pathOf } = input;\n const findings: Finding[] = [];\n\n // Two boxes standing for one service directory. Every join in loam is\n // `element -> service`, computed by picking the FIRST element that resolves —\n // so with two of them, which one wins is readdir order, and the edges of the\n // loser are attributed to a service they do not belong to. Silent until now,\n // and unfixable by staring at either element on its own.\n const perService = new Map<string, Elem[]>();\n for (const e of drawn) {\n if (e.tags.includes(EXTERNAL_TAG)) continue;\n const id = elementService(e);\n perService.set(id, [...(perService.get(id) ?? []), e]);\n }\n for (const [id, elems] of perService) {\n if (elems.length < 2) continue;\n // A collision only matters where it decides something: a real directory to\n // attribute to, or a binding somebody wrote down on purpose.\n if (!services.has(id) && !elems.some((e) => e.service !== undefined)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.binding-duplicate\",\n subject: id,\n message: `landscape: ${elems.length} elements resolve to service '${id}' (${elems.map((e) => e.id).join(\", \")}) — every element→service join picks one of them arbitrarily, so the others' edges are filed under a service that does not own them; keep one element per services/<id>/`,\n });\n }\n\n for (const id of services) {\n if (input.modelled.has(id)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.service-unmodelled\",\n subject: id,\n message: `landscape: ${pathOf(id)}/ exists but nothing in architecture/landscape.likec4 models it — add an element, or bind one with metadata { service '${id}' }`,\n });\n }\n\n // A binding is a claim about this repo wherever it is written — including\n // inside another element, which the old top-level filter never looked at, so\n // a typo one level down bound an edge to a service that does not exist and\n // nothing said so. Every element with a binding answers for it, at any depth.\n for (const e of elements) {\n if (e.tags.includes(EXTERNAL_TAG) || e.service === undefined) continue;\n if (services.has(e.service)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.binding-unknown\",\n subject: e.service,\n message: `landscape: '${e.title}' binds to service '${e.service}', but services/${e.service}/ does not exist`,\n });\n }\n\n // Walked over the SYSTEM census — the same derivation the scorecard counts,\n // so the map's exemptions and the fleet rollup cannot drift — with the two\n // residual skips that are not exemptions: a bound element is the binding\n // pass's subject above, and a title naming a real directory is documented.\n for (const e of input.systems) {\n if (e.service !== undefined) continue; // graded by the binding pass above\n if (services.has(e.title)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.service-undocumented\",\n subject: e.title,\n message: `landscape: '${e.title}' has no services/${e.title}/ — bind it with metadata { service '<id>' }, or tag it #${EXTERNAL_TAG} if it is not ours`,\n });\n }\n\n return findings;\n}\n"]}
1
+ {"version":3,"file":"bindings.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/bindings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The element↔directory passes of the fleet cross-check: which drawn box\n * answers for which `services/<id>/`, and what it means when the answer is\n * none, two, or one nobody owns.\n *\n * Lifted out of `../landscape.ts` when that module reached its line limit, and\n * the seam is a phase rather than a subject: everything here runs after the map\n * has parsed, reads nothing from disk, and grades exactly one join — the census\n * `../census.ts` derives against the enumeration `../landscape.ts` performed.\n * The four grades are ordered as the report reads them, and the ORDER is the\n * caller's business, which is why this is one function rather than four.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule: it\n * imports NOTHING from `fleet/` itself, so the two census derivations arrive as\n * data on the input record rather than as an import of `../census.js`.\n */\nimport { type Elem } from \"../../../../core/c4/likec4.js\";\nimport { elementService } from \"../../../../core/c4/resolve/service.js\";\nimport { EXTERNAL_TAG, INFRA_KINDS } from \"../../../../core/vocabulary/maturity.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\n\nexport interface BindingInput {\n /** Every element the map declares, at any depth — a binding answers for itself wherever it is written. */\n elements: Elem[];\n /** Service-LEVEL elements (`census.ts serviceLevelElements`) — the fleet map's own boxes. */\n drawn: Elem[];\n /** The drawn SYSTEMS (`census.ts drawnSystems`) — `drawn` minus what the map itself says is no service. */\n systems: Elem[];\n /** The services/<id>/ directories that exist. */\n services: ReadonlySet<string>;\n /** The services some element resolves to — the set `landscape.service-unmodelled` reads. */\n modelled: ReadonlySet<string>;\n /** Where a service's directory sits, repo-relative — the enumeration's answer, never `services/<id>`. */\n pathOf: (id: string) => string;\n}\n\nexport function bindingFindings(input: BindingInput): Finding[] {\n const { elements, drawn, services, pathOf } = input;\n const findings: Finding[] = [];\n\n // Two boxes standing for one service directory. Every join in loam is\n // `element -> service`, computed by picking the FIRST element that resolves —\n // so with two of them, which one wins is readdir order, and the edges of the\n // loser are attributed to a service they do not belong to. Silent until now,\n // and unfixable by staring at either element on its own.\n const perService = new Map<string, Elem[]>();\n for (const e of drawn) {\n if (e.tags.includes(EXTERNAL_TAG)) continue;\n const id = elementService(e);\n perService.set(id, [...(perService.get(id) ?? []), e]);\n }\n for (const [id, elems] of perService) {\n if (elems.length < 2) continue;\n // A collision only matters where it decides something: a real directory to\n // attribute to, or a binding somebody wrote down on purpose.\n if (!services.has(id) && !elems.some((e) => e.service !== undefined)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.binding-duplicate\",\n subject: id,\n message: `landscape: ${elems.length} elements resolve to service '${id}' (${elems.map((e) => e.id).join(\", \")}) — every element→service join picks one of them arbitrarily, so the others' edges are filed under a service that does not own them; keep one element per services/<id>/`,\n });\n }\n\n for (const id of services) {\n if (input.modelled.has(id)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.service-unmodelled\",\n subject: id,\n message: `landscape: ${pathOf(id)}/ exists but nothing in architecture/landscape.likec4 models it — add an element, or bind one with metadata { service '${id}' }`,\n });\n }\n\n // A binding is a claim about this repo wherever it is written — including\n // inside another element, which the old top-level filter never looked at, so\n // a typo one level down bound an edge to a service that does not exist and\n // nothing said so. Every element with a binding answers for it, at any depth.\n for (const e of elements) {\n if (e.tags.includes(EXTERNAL_TAG) || e.service === undefined) continue;\n if (services.has(e.service)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.binding-unknown\",\n subject: e.service,\n message: `landscape: '${e.title}' binds to service '${e.service}', but services/${e.service}/ does not exist`,\n });\n }\n\n // Walked over the SYSTEM census — the same derivation the scorecard counts,\n // so the map's exemptions and the fleet rollup cannot drift — with the two\n // residual skips that are not exemptions: a bound element is the binding\n // pass's subject above, and a title naming a real directory is documented.\n //\n // Infrastructure the fleet OWNS is out of `systems` upstream, in\n // `../census.ts drawnSystems` (INFRA_KINDS), and it had to be: this message\n // offers exactly two remedies and BOTH are false of an owned datastore — there\n // is no `services/<id>/` to bind, and `#external` would claim somebody else\n // runs it. Measured on a one-service fixture drawing an owned `database` at\n // fleet level (2026-09-07), it fired here and had no correct fix.\n for (const e of input.systems) {\n if (e.service !== undefined) continue; // graded by the binding pass above\n if (services.has(e.title)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.service-undocumented\",\n subject: e.title,\n // The third remedy is spelled from `INFRA_KINDS` rather than typed out,\n // for the reason the set exists at all: the hook is a kind NAME, so a\n // fleet whose specification spells its store `postgres` still lands here,\n // and this sentence is the only place that tells its author which\n // spellings loam reads. A prose copy would be a second list free to drift\n // from the one the census walks.\n message: `landscape: '${e.title}' has no services/${e.title}/ — bind it with metadata { service '<id>' }, tag it #${EXTERNAL_TAG} if it is not ours, or give it one of the kinds loam reads as infrastructure (${[...INFRA_KINDS].join(\", \")}) if it is a store, queue or bucket the fleet runs rather than a system somebody adopts`,\n });\n }\n\n return findings;\n}\n"]}
@@ -1,35 +1,33 @@
1
1
  /**
2
- * Who consumes a drawn element — and the datastore grades that read the answer.
2
+ * Who consumes a drawn element — and the datastore grade that reads the answer.
3
3
  *
4
4
  * The census used to walk the MAP's relationships alone, and under the extending
5
5
  * shape that is a partial view of the fleet: a service draws the container-level
6
6
  * call in its own `model.likec4` and the map carries the service-level twin, so a
7
7
  * store whose consuming edges were all drawn in models had ZERO consumers and
8
- * earned nothing at all, while a store reached by three models and one map edge
9
- * was reported `landscape.datastore-private` naming the one service the map
10
- * happened to draw (R1). A model is evidence about the fleet exactly as the map
11
- * is, so both are counted here, once, by one derivation two checks share.
8
+ * was graded on the one service the map happened to draw (R1). A model is
9
+ * evidence about the fleet exactly as the map is, so both are counted here,
10
+ * once, by one derivation two checks share.
12
11
  *
13
- * WHERE A PRIVATE STORE LIVES, stated once because three surfaces used to answer
14
- * it differently: a store one service owns is NESTED UNDER THAT SERVICE'S
15
- * ELEMENT — in the service's extending model (`extend <fqn> { store = database
16
- * … }`), or inside the service's element on the map when the map draws that
17
- * service's containers. A second service reaching it later is drawn where that
18
- * service draws every other call: a container-level edge in ITS model naming
19
- * `<owner-fqn>.<store>`, carried up to the map as a service-level edge to the
20
- * owning service. Nothing moves back, and nothing goes ungraded — a nested store
21
- * two services reach is `landscape.datastore-shared` here (E3).
12
+ * WHERE A STORE LIVES IS THE TEAM'S CHOICE, and this module used to say the
13
+ * opposite at length. Nested under the element that resolves to its owner — in
14
+ * that service's `extend <fqn> { store = database … }` block, or inside its
15
+ * element on the map — and drawn as a fleet-level PEER are both first-class
16
+ * placements, and loam grades neither as wrong. `landscape.datastore-private`
17
+ * existed to push every one-consumer store into the first, its whole message a
18
+ * prescription to delete the peer; it is retired (2026-09-07), the arm below is
19
+ * gone, and no message here prescribes a move.
22
20
  *
23
- * WHICH OF THE TWO PLACEMENTS, and this is measured rather than assumed: a
24
- * per-service project is `architecture/` plus that ONE model, so an edge naming
25
- * `<owner-fqn>.<store>` resolves for the second service only when the MAP
26
- * declares the store. Written in the owner's `extend` block instead, the same
27
- * edge is two errors on the consumer's own model ("Could not resolve reference
28
- * to Referenceable named 'store'"), so that store is private for good and its
29
- * second consumer can only ever be drawn at service level. The message below
30
- * says so; SCHEMA carries the same sentence.
21
+ * What survives the retirement is the one CONSEQUENCE, which is a fact about
22
+ * LikeC4 rather than a preference: a per-service project is `architecture/` plus
23
+ * that ONE model, so an edge naming `<owner-fqn>.<store>` resolves for a SECOND
24
+ * service only when the MAP declares the store. Written in the owner's `extend`
25
+ * block instead, the same edge is two errors on the consumer's own model
26
+ * ("Could not resolve reference to Referenceable named 'store'"). That is the
27
+ * load-bearing half, it is stated by `reversibility` below where it actually
28
+ * bites, and SCHEMA carries the same sentence.
31
29
  *
32
- * The choice is cheap, and both messages now say exactly HOW cheap, because
30
+ * The choice is cheap, and the surviving message says exactly HOW cheap, because
33
31
  * "reversible" was false in the one state that used to assert it. The two
34
32
  * placements do give the store the same id, `<owner-fqn>.<store>` — but the
35
33
  * move is only free in ONE direction once a second service names it. Measured
@@ -44,24 +42,17 @@
44
42
  * own service's project alone. `datastore-shared` printed the unconditional
45
43
  * clause in 100% of the states it fires in, every one of which already has the
46
44
  * second consumer that makes map→extend break (verification 2026-09-04). So the
47
- * rule both messages now state is directional: extend→map is always safe;
48
- * map→extend only while no other service's model or map edge names the store.
49
- *
50
- * And the remedy is named for the consumer's own model SHAPE rather than
51
- * assumed: `extend <fqn>` in a STANDALONE model (one that declares its own
52
- * `specification`) resolves nothing, because such a model is parsed alone — so
53
- * prescribing it there turns a clean run into `c4.invalid`. The shape comes off
54
- * the same `./attest.ts` record the consumer join reads.
45
+ * rule the message states is directional: extend→map is always safe; map→extend
46
+ * only while no other service's model or map edge names the store.
55
47
  *
56
48
  * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it
57
49
  * imports NOTHING from `fleet/`, so the map facts it reads arrive on the input
58
50
  * records the caller fills.
59
51
  */
60
52
  import {} from "../../../../core/c4/likec4.js";
53
+ import { DATASTORE_KIND } from "../../../../core/vocabulary/maturity.js";
61
54
  import {} from "../../../../core/vocabulary/report.js";
62
55
  import {} from "./attest.js";
63
- /** The element kind read as a datastore, compared case-insensitively. */
64
- const DATASTORE_KIND = "database";
65
56
  /**
66
57
  * Distinct consumer SERVICES of an element, its nested children included: an
67
58
  * edge into `kafka.paymentEvents` is consumption of `kafka`.
@@ -102,12 +93,20 @@ export function consumerCensus(input) {
102
93
  };
103
94
  }
104
95
  /**
105
- * A datastore drawn as a fleet-level peer makes a claim its consumer count
106
- * either supports or refutes: one consumer means the drawing is false — the
107
- * store is that service's internals, not a system in its own right — while two
108
- * or more mean the strongest coupling two services can have, which deserves to
109
- * be stated rather than inferred. A NESTED store is private by construction, so
110
- * it is silent at one consumer and earns the same `datastore-shared` at two.
96
+ * A datastore two or more services reach is the strongest coupling two services
97
+ * can have and the hardest to undo, which deserves to be STATED rather than
98
+ * inferred. That is the whole of this grade now, and it applies to both
99
+ * placements alike: a fleet-level peer and a store nested under its owner earn
100
+ * the same `landscape.datastore-shared` at two consumers, and neither is graded
101
+ * at one.
102
+ *
103
+ * The one-consumer arm is gone (2026-09-07). A fleet-level peer with a single
104
+ * consumer used to be `landscape.datastore-private` — "drawn as a peer it reads
105
+ * as a system in its own right … delete this peer" — which made WHERE a store is
106
+ * drawn a restriction rather than the team's choice. It was never a fact about
107
+ * the fleet: the same store, nested, was silent, so the finding graded the
108
+ * drawing and not the architecture. The nested half was ALREADY silent at one
109
+ * consumer, and it is that half's rule that now holds for both.
111
110
  */
112
111
  export function datastoreFindings(input) {
113
112
  const findings = [];
@@ -119,9 +118,12 @@ export function datastoreFindings(input) {
119
118
  if (e.kind.toLowerCase() !== DATASTORE_KIND)
120
119
  continue;
121
120
  const consumers = input.consumers(e.id);
122
- if (consumers.length === 0)
121
+ // One consumer is a store that service owns, drawn where the team chose to
122
+ // draw it — a fact about the map's layout, not about the fleet. Zero is a
123
+ // store nothing reaches, which was never graded here either.
124
+ if (consumers.length < 2)
123
125
  continue;
124
- findings.push(consumers.length === 1 ? privateFinding(e, consumers[0], input) : sharedFinding(e.title, consumers, "", ""));
126
+ findings.push(sharedFinding(e.title, consumers, "", ""));
125
127
  }
126
128
  for (const store of nestedStoresOf(input)) {
127
129
  // The owner is a consumer by construction — the store sits inside the element
@@ -200,87 +202,6 @@ function reversibility(store, others) {
200
202
  `back INTO the \`extend\` block breaks that edge — \`c4.invalid\` on the other service's model, or ` +
201
203
  `\`landscape.invalid\` when the edge is drawn on the map`);
202
204
  }
203
- /**
204
- * Where the store goes, named for the consumer's own model SHAPE — which this
205
- * sentence used to assert instead of reading. It always prescribed the
206
- * extending-shape remedy, and following it on a standalone consumer turns a
207
- * clean run into `c4.invalid`: a model that declares its own `specification` is
208
- * parsed alone, so `extend <fqn>` there resolves nothing at all (measured — one
209
- * error, "Could not resolve reference to Element named '<fqn>'"). The two
210
- * placements otherwise give the store the SAME id, which is the fact that makes
211
- * the choice cheap: measured on examples/docs, moving `db` out of
212
- * order-service's extend block into `orderService` on the map left the model at
213
- * 12 elements · 8 relationships, left `Covers: marketplace.orderService.db` in
214
- * its arch.spec.md resolving, and left checkout-web's edge to it resolving too.
215
- */
216
- function placement(at) {
217
- // Direction, not symmetry. The clause used to read "moving the declaration
218
- // between them later rewrites no edge", which is true only while this store
219
- // has ONE consumer — the state this finding is in — and false the moment the
220
- // second consumer it goes on to promise arrives: that consumer's edge resolves
221
- // against the map's declaration alone. Saying it unconditionally here is what
222
- // made `datastore-shared` inherit a sentence that was false in every state it
223
- // printed in (verification 2026-09-04).
224
- const same = `the two placements give it the same id, ${at.fqn}.${at.local}, and with one consumer either move ` +
225
- `is free — out of the \`extend\` block onto the map always is, and back into it while no other ` +
226
- `service's model or map edge names the store, an \`extend\` block being visible to its own service's ` +
227
- `project alone`;
228
- if (at.shape === "extending") {
229
- return (`A store one service owns is nested UNDER that service's element: write it in the ` +
230
- `\`extend ${at.fqn} { … }\` block of ${at.path}, or inside '${at.fqn}' here when the map draws ` +
231
- `that service's containers, and delete this peer — ${same}.`);
232
- }
233
- // The two absent-extending-model arms differ in what there is to do: a
234
- // STANDALONE model exists and can be migrated; with no model at all there is
235
- // nothing to migrate, and "migrate the model" sent a reader looking for a file
236
- // loam had just said it could not find (verification 2026-09-04).
237
- const otherwise = at.shape === "standalone"
238
- ? `${at.path} declares its own \`specification\`, so it is parsed alone and \`extend ${at.fqn}\` ` +
239
- `resolves nothing there`
240
- : `loam read no extending model at ${at.path}`;
241
- const migrate = at.shape === "standalone"
242
- ? `migrate the model to the extending shape (SCHEMA.md, "Two shapes of a service model")`
243
- : `write an extending model at ${at.path} (SCHEMA.md, "Two shapes of a service model")`;
244
- return (`A store one service owns is nested UNDER that service's element, and this consumer has no ` +
245
- `extending model to nest it in (${otherwise}): write it inside '${at.fqn}' here, where the map draws ` +
246
- `that service's containers, and delete this peer — or ${migrate} and write it in an ` +
247
- `\`extend ${at.fqn} { … }\` block there, where ${same}.`);
248
- }
249
- /**
250
- * The one-consumer message, and it names the PLACEMENT rather than a directory:
251
- * the remedy used to read "move it into services/<id>/model.likec4 as a nested
252
- * container, or add the second consumer's edge", which offered two remedies as
253
- * equals and left the second one impossible — an edge naming the moved store is
254
- * `landscape.invalid` for loam, so an author who took the first remedy believed
255
- * they had shut the second door (#01). They had not, and the sentence now says
256
- * how: a later consumer is drawn in ITS OWN model, and loam counts that — as
257
- * long as that model EXTENDS the map, which the caveat below is careful to say.
258
- */
259
- function privateFinding(store, consumer, input) {
260
- const fqn = input.drawn.find((e) => input.resolve(e.id) === consumer)?.id ?? consumer;
261
- const local = store.id.split(".").at(-1) ?? store.id;
262
- const model = input.attested.find((m) => m.service === consumer);
263
- const shape = model === undefined ? null : model.extending ? "extending" : "standalone";
264
- return {
265
- severity: "warn",
266
- code: "landscape.datastore-private",
267
- subject: store.title,
268
- message: `landscape: '${store.title}' is a datastore with a single consumer at fleet level ` +
269
- `('${consumer}') — drawn as a peer it reads as a system in its own right, available to be ` +
270
- `depended on. ${placement({ fqn, local, path: `${input.pathOf(consumer)}/model.likec4`, shape })} ` +
271
- `Nesting closes no door: a second service reaching the same data later draws a container-level ` +
272
- `edge to ${fqn}.${local} in ITS OWN model plus a service-level edge to '${fqn}' here, and loam ` +
273
- `counts a consumer an EXTENDING model attests exactly as it counts one drawn here — so that ` +
274
- `state is \`landscape.datastore-shared\`, never silence. A consumer whose model stands alone is ` +
275
- `not counted (its ids are its own file's, never the map's), so its edge belongs on the map. ` +
276
- `Which placement: that edge RESOLVES only against a store the map declares (a per-service ` +
277
- `project is architecture/ plus one model), so ` +
278
- (shape === "extending"
279
- ? `nest it here when a second consumer is foreseeable and in the model when it is not. `
280
- : `the map is the placement that keeps that door open. `) +
281
- `If another service reaches the same data today, draw its edge and this becomes that warning now`,
282
- };
283
- }
284
205
  /** The two-or-more message, shared by the peer and the nested store — one word about coupling, once. */
285
206
  function sharedFinding(title, consumers, where, tail) {
286
207
  return {