@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13

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 (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  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/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"load.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/load.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,OAAO;QACL,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACvE,QAAQ,EAAE,EAAE;QACZ,aAAa,EAAE,EAAE;KAClB,CAAC;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAA8B;IAChE,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;AACH,CAAC","sourcesContent":["/**\n * The landscape's contained load path — the one phase of the fleet target that\n * touches the filesystem before anything can be graded. Split from\n * `./landscape.ts` when the fleet target grew past the line limit; the\n * containment DOCTRINE below travelled with the functions it explains.\n *\n * The load ITSELF is no longer here. `loadArchitecture` moved to\n * `core/c4/project/architecture.ts` when the use-case axis grew a second reader\n * of it, and that module's banner records why; what stayed is the containment,\n * which is command-layer business because it is about which validate TARGET a\n * failed read is filed against.\n */\nimport type { LoadedDoc } from \"../../../core/c4/likec4.js\";\n\n/**\n * A landscape that could not be READ, shaped as one that did not PARSE.\n *\n * The landscape is the one artifact no target owns: it is read once for the\n * whole run and it is graded on a target of its own, which runs OUTSIDE\n * `guarded`. So a landscape.likec4 that is a directory, or that carries a\n * permission bit this process cannot open, escaped every per-target catch and\n * became the whole run's `repository-unavailable` — one file, and a fleet gate\n * that said nothing about the ninety-nine services that are fine. A DANGLING\n * symlink is not one of those shapes and never was: every read of the file is\n * gated on an `existsSync` that follows the link, so a broken one resolves as\n * `landscape.missing` long before anything opens it.\n *\n * What this contains is the FLEET-level reads, and only those: the `--all`\n * preload, and both reads inside `validateLandscape` — the conflict-marker\n * `readFile` and the parse. `loam validate --service <id>` and\n * `loam validate --feature <id>` hand in no preloaded doc, so `validateService`\n * and `validateFeature` open the same file again on demand, unwrapped and\n * INSIDE `guarded`: an EISDIR there is still reported as `service.unreadable`\n * or `feature.unreadable`, which files the fleet map's failure against the one\n * target the caller happened to name. That is the wrong subject on a finding\n * that does at least carry the offending path, and it costs those runs nothing\n * further — they grade a single target either way, so there is no report left\n * unwritten — which is why the remainder was left for the change that gives the\n * landscape one load path instead of three.\n *\n * `guarded` is deliberately NOT widened to cover the landscape target instead.\n * Its code ternary knows only services and features, so it would file the fleet\n * map's failure as `feature.unreadable`; and a guarded failure yields no\n * document, so all N services would go on to re-open the same broken file\n * inside their own guards and emit N copies of it. Containing the IO here needs\n * no new code and no new sentence: \"could not be read\" and \"did not parse\" have\n * the same consequence — nothing may be concluded from this file — and\n * `landscape.invalid` is already how that is said.\n */\nexport function unreadableLandscape(err: unknown): LoadedDoc {\n return {\n errors: [{ message: err instanceof Error ? err.message : String(err) }],\n elements: [],\n relationships: [],\n };\n}\n\n/** One landscape load, answering with `unreadableLandscape` rather than throwing. */\nexport async function readLandscape(load: () => Promise<LoadedDoc>): Promise<LoadedDoc> {\n try {\n return await load();\n } catch (err) {\n return unreadableLandscape(err);\n }\n}\n"]}
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/load.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAG9D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,OAAO;QACL,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACvE,QAAQ,EAAE,EAAE;QACZ,aAAa,EAAE,EAAE;KAClB,CAAC;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAA8B;IAChE,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,KAAoB;IAC/D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjC,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAClG,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,KAAK,CAAC,cAAc,CAAC;QACzB,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;QAC9F,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;KACzF,CAAC,CAAC;IACH,MAAM,KAAK,CAAC,qBAAqB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AACpD,CAAC","sourcesContent":["/**\n * The landscape's contained load path — the one phase of the fleet target that\n * touches the filesystem before anything can be graded. Split from\n * `./landscape.ts` when the fleet target grew past the line limit; the\n * containment DOCTRINE below travelled with the functions it explains.\n *\n * The load ITSELF is no longer here. `loadArchitecture` moved to\n * `core/c4/project/architecture.ts` when the use-case axis grew a second reader\n * of it, and that module's banner records why; what stayed is the containment,\n * which is command-layer business because it is about which validate TARGET a\n * failed read is filed against.\n */\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FeatureEntry, ServiceEntry } from \"../../../core/repo/entries.js\";\nimport { featurePaths, landscapePath, servicePathsAt } from \"../../../core/repo/paths.js\";\n\n/**\n * A landscape that could not be READ, shaped as one that did not PARSE.\n *\n * The landscape is the one artifact no target owns: it is read once for the\n * whole run and it is graded on a target of its own, which runs OUTSIDE\n * `guarded`. So a landscape.likec4 that is a directory, or that carries a\n * permission bit this process cannot open, escaped every per-target catch and\n * became the whole run's `repository-unavailable` — one file, and a fleet gate\n * that said nothing about the ninety-nine services that are fine. A DANGLING\n * symlink is not one of those shapes and never was: every read of the file is\n * gated on an `existsSync` that follows the link, so a broken one resolves as\n * `landscape.missing` long before anything opens it.\n *\n * What this contains is the FLEET-level reads, and only those: the `--all`\n * preload, and both reads inside `validateLandscape` — the conflict-marker\n * `readFile` and the parse. `loam validate --service <id>` and\n * `loam validate --feature <id>` hand in no preloaded doc, so `validateService`\n * and `validateFeature` open the same file again on demand, unwrapped and\n * INSIDE `guarded`: an EISDIR there is still reported as `service.unreadable`\n * or `feature.unreadable`, which files the fleet map's failure against the one\n * target the caller happened to name. That is the wrong subject on a finding\n * that does at least carry the offending path, and it costs those runs nothing\n * further — they grade a single target either way, so there is no report left\n * unwritten — which is why the remainder was left for the change that gives the\n * landscape one load path instead of three.\n *\n * `guarded` is deliberately NOT widened to cover the landscape target instead.\n * Its code ternary knows only services and features, so it would file the fleet\n * map's failure as `feature.unreadable`; and a guarded failure yields no\n * document, so all N services would go on to re-open the same broken file\n * inside their own guards and emit N copies of it. Containing the IO here needs\n * no new code and no new sentence: \"could not be read\" and \"did not parse\" have\n * the same consequence — nothing may be concluded from this file — and\n * `landscape.invalid` is already how that is said.\n */\nexport function unreadableLandscape(err: unknown): LoadedDoc {\n return {\n errors: [{ message: err instanceof Error ? err.message : String(err) }],\n elements: [],\n relationships: [],\n };\n}\n\n/** One landscape load, answering with `unreadableLandscape` rather than throwing. */\nexport async function readLandscape(load: () => Promise<LoadedDoc>): Promise<LoadedDoc> {\n try {\n return await load();\n } catch (err) {\n return unreadableLandscape(err);\n }\n}\n\n/** Everything a `--all` run parses up front, and the enumerations it was narrowed to. */\nexport interface FleetPrefetch {\n docsDir: DocsDir;\n fleet: FleetContext;\n /** The services this run grades — the `--base` narrowing when there is one. */\n services: readonly ServiceEntry[];\n /** The active features this run grades, likewise. */\n features: readonly FeatureEntry[];\n}\n\n/**\n * ONE workspace per document KIND for the whole `--all` run.\n *\n * The per-path load pays a fresh Langium workspace per document (~100 ms each\n * even warm), which made the fleet's main CI command O(documents) workspace\n * spins — 13.7 s median over the 120-service benchmark (docs/BENCHMARKS.md). So\n * `--all` enumerates its documents up front and batch-parses them into the\n * fleet context's memo; every load below, the landscape read included, is then a\n * seeded hit. The enumerations are the same memoised promises the target loops\n * reuse.\n *\n * TWO WORKSPACES, because the fleet's models no longer parse the same way as\n * each other: a model that EXTENDS the map is a PROJECT (the map plus that\n * file) and a model that stands alone is a document. So the shapes are read\n * first, the DOCUMENT batch is asked for everything that is one — the landscape,\n * every feature delta, every standalone model — and `prefetchServiceModels`\n * then batches the extending half as projects, finding its own standalone list\n * already memoised and asking for nothing.\n *\n * The order is load-bearing rather than tidy. `prefetchLikeC4` returns without a\n * workspace when fewer than two documents are missing (one document gains\n * nothing from batch isolation it already has), so splitting the documents\n * across two calls is how a fleet of one service and one feature ends up\n * batching the feature and parsing the model in a workspace of its own — the\n * exact per-document spin this function exists to remove, and what\n * `test/validate-batch-fallback.test.ts` counts.\n *\n * If a batch CANNOT run — a sandbox denying tmpdir writes — the prefetch seeds\n * nothing and every load falls back to today's per-path parse: identical\n * findings, the old speed. Single-service `validate` and `list` keep their\n * untouched code paths on purpose, so the ≤10% regression bound in\n * docs/BENCHMARKS.md holds by construction.\n *\n * It lives HERE rather than in `../validate.ts` because it is fleet-loading\n * business and that module is the argument grammar and the target dispatch; the\n * block also took that file to its line limit, which is the ceiling asking the\n * question it exists to ask.\n */\nexport async function prefetchFleetDocuments(input: FleetPrefetch): Promise<void> {\n const { docsDir, fleet } = input;\n const paths = input.services.filter((svc) => svc.has.model).map((svc) => servicePathsAt(svc.dir));\n const shapes = await fleet.modelShapes(paths.map((p) => p.model));\n const landscape = landscapePath(docsDir);\n await fleet.prefetchLikeC4([\n ...(existsSync(landscape) ? [landscape] : []),\n ...input.features.filter((feat) => feat.has.delta).map((feat) => featurePaths(feat.dir).delta),\n ...paths.filter((p) => shapes.get(resolve(p.model)) !== \"extending\").map((p) => p.model),\n ]);\n await fleet.prefetchServiceModels(docsDir, paths);\n}\n"]}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * What the fleet's own `model.likec4` files say, read ONCE for the two map
3
+ * checks that need it: the calls each model attests across its boundary
4
+ * (`./isolation.ts`), and the datastores that live nested inside a service's own
5
+ * element (`../../checks/fleet-shape.ts`).
6
+ *
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
+ * 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.
12
+ *
13
+ * EXTENDING MODELS ONLY, for the consumer join. An extending model's ids ARE the
14
+ * map's fully-qualified ids — one project, one id space — so `counterpartId`
15
+ * joins to a drawn element exactly. A standalone model's ids are its own file's,
16
+ * and `../../../../core/c4/resolve/attested.ts` says outright that a counterpart
17
+ * is never matched to a landscape element: joining one would file a coincidence
18
+ * of spelling as a coupling. Standalone models still contribute their attested
19
+ * CALLS, because `landscape.service-isolated` asks only whether the model
20
+ * declares any, which is shape-independent.
21
+ *
22
+ * A model that does not parse contributes nothing — neither calls nor stores.
23
+ * `c4.invalid` is that file's finding, and a fleet-shape advisory computed from
24
+ * a document nobody could read would be an inference about bytes.
25
+ *
26
+ * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it
27
+ * imports NOTHING from `fleet/`, so the map facts it needs (the resolver, the
28
+ * ids the map draws at fleet level) arrive on the input record.
29
+ */
30
+ import { existsSync } from "node:fs";
31
+ import {} from "../../../../core/c4/likec4.js";
32
+ import { attestedCalls } from "../../../../core/c4/resolve/attested.js";
33
+ import { FleetContext } from "../../../../core/fleet-context.js";
34
+ import { inOrder } from "../../../../core/kernel/concurrency.js";
35
+ import {} from "../../../../core/repo/entries.js";
36
+ import { servicePathsAt } from "../../../../core/repo/paths.js";
37
+ import { DATASTORE_KIND } from "../../../../core/vocabulary/maturity.js";
38
+ export async function mapAttestation(input) {
39
+ // Through the ONE reader of `model.likec4` (`core/c4/service-model/load.ts`),
40
+ // so a model that EXTENDS the map attests the calls it draws inside the
41
+ // element the map binds — read as a slice of the per-service project — exactly
42
+ // as a standalone one attests the calls it draws in its own file. Memoised in
43
+ // the index, so under `--all` these are hits. Pooled and joined on the entry,
44
+ // never by index.
45
+ const present = input.entries.filter((entry) => existsSync(servicePathsAt(entry.dir).model));
46
+ // ONE workspace for the fleet's models rather than one per service. Both
47
+ // answers below are about the WHOLE fleet — who else reaches this store is not
48
+ // a question a narrowed run may answer differently — while `--base` prefetches
49
+ // only the scope it grades, so without this a scoped run would spin a LikeC4
50
+ // workspace per un-narrowed service. An accelerator with `prefetchLikeC4`'s
51
+ // failure story exactly: a batch that cannot run seeds nothing, and every read
52
+ // below falls back to its own load.
53
+ await input.fleet.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));
54
+ const loaded = await inOrder(present, async (entry) => ({
55
+ entry,
56
+ // CONTAINED, for `fleet/load.ts unreadableLandscape`'s reason: this runs on
57
+ // the landscape target, which sits outside the dispatcher's `guarded`, so a
58
+ // `model.likec4` that is a directory or carries a permission bit would
59
+ // otherwise become the whole run's `repository-unavailable` — one file, and
60
+ // no report at all for the ninety-nine services that are fine. A model that
61
+ // could not be READ contributes nothing here, exactly as one that did not
62
+ // parse does; `service.unreadable` is its own target's finding.
63
+ model: await input.fleet.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),
64
+ }));
65
+ const models = [];
66
+ const nestedStores = [];
67
+ for (const { entry, model } of loaded) {
68
+ // A model that does not parse attests nothing: `c4.invalid` is its finding.
69
+ // An extending model whose MAP does not parse could not be read at all, and
70
+ // neither reader runs in that state anyway — the fleet target returns on
71
+ // `landscape.invalid` long before here.
72
+ if (model === null || model.mapUnreadable || model.doc.errors.length > 0)
73
+ continue;
74
+ models.push({
75
+ service: entry.id,
76
+ extending: model.shape === "extending",
77
+ calls: attestedCalls(model.doc, entry.id, input.services),
78
+ });
79
+ if (model.shape !== "extending")
80
+ continue;
81
+ for (const element of model.doc.elements) {
82
+ if (element.kind.toLowerCase() !== DATASTORE_KIND)
83
+ continue;
84
+ // The slice carries the partners this model's own edges point at, and a
85
+ // partner may be another service's store — so the store is kept only when
86
+ // the MAP's resolver files it under this service, and only when the map
87
+ // does not draw it as a peer of the services (which is the fleet-level
88
+ // census's subject, and a different grade).
89
+ if (input.standsForService(element))
90
+ continue;
91
+ if (input.drawnIds.has(element.id) || input.resolve(element.id) !== entry.id)
92
+ continue;
93
+ nestedStores.push({ owner: entry.id, fqn: element.id, title: element.title });
94
+ }
95
+ }
96
+ return { models, nestedStores };
97
+ }
98
+ //# sourceMappingURL=attest.js.map
@@ -0,0 +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 * 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"]}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The element↔directory passes of the fleet cross-check: which drawn box
3
+ * answers for which `services/<id>/`, and what it means when the answer is
4
+ * none, two, or one nobody owns.
5
+ *
6
+ * Lifted out of `../landscape.ts` when that module reached its line limit, and
7
+ * the seam is a phase rather than a subject: everything here runs after the map
8
+ * has parsed, reads nothing from disk, and grades exactly one join — the census
9
+ * `../census.ts` derives against the enumeration `../landscape.ts` performed.
10
+ * The four grades are ordered as the report reads them, and the ORDER is the
11
+ * caller's business, which is why this is one function rather than four.
12
+ *
13
+ * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule: it
14
+ * imports NOTHING from `fleet/` itself, so the two census derivations arrive as
15
+ * data on the input record rather than as an import of `../census.js`.
16
+ */
17
+ import {} from "../../../../core/c4/likec4.js";
18
+ import { elementService } from "../../../../core/c4/resolve/service.js";
19
+ import { EXTERNAL_TAG, INFRA_KINDS } from "../../../../core/vocabulary/maturity.js";
20
+ import {} from "../../../../core/vocabulary/report.js";
21
+ export function bindingFindings(input) {
22
+ const { elements, drawn, services, pathOf } = input;
23
+ const findings = [];
24
+ // Two boxes standing for one service directory. Every join in loam is
25
+ // `element -> service`, computed by picking the FIRST element that resolves —
26
+ // so with two of them, which one wins is readdir order, and the edges of the
27
+ // loser are attributed to a service they do not belong to. Silent until now,
28
+ // and unfixable by staring at either element on its own.
29
+ const perService = new Map();
30
+ for (const e of drawn) {
31
+ if (e.tags.includes(EXTERNAL_TAG))
32
+ continue;
33
+ const id = elementService(e);
34
+ perService.set(id, [...(perService.get(id) ?? []), e]);
35
+ }
36
+ for (const [id, elems] of perService) {
37
+ if (elems.length < 2)
38
+ continue;
39
+ // A collision only matters where it decides something: a real directory to
40
+ // attribute to, or a binding somebody wrote down on purpose.
41
+ if (!services.has(id) && !elems.some((e) => e.service !== undefined))
42
+ continue;
43
+ findings.push({
44
+ severity: "warn",
45
+ code: "landscape.binding-duplicate",
46
+ subject: id,
47
+ 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>/`,
48
+ });
49
+ }
50
+ for (const id of services) {
51
+ if (input.modelled.has(id))
52
+ continue;
53
+ findings.push({
54
+ severity: "error",
55
+ code: "landscape.service-unmodelled",
56
+ subject: id,
57
+ message: `landscape: ${pathOf(id)}/ exists but nothing in architecture/landscape.likec4 models it — add an element, or bind one with metadata { service '${id}' }`,
58
+ });
59
+ }
60
+ // A binding is a claim about this repo wherever it is written — including
61
+ // inside another element, which the old top-level filter never looked at, so
62
+ // a typo one level down bound an edge to a service that does not exist and
63
+ // nothing said so. Every element with a binding answers for it, at any depth.
64
+ for (const e of elements) {
65
+ if (e.tags.includes(EXTERNAL_TAG) || e.service === undefined)
66
+ continue;
67
+ if (services.has(e.service))
68
+ continue;
69
+ findings.push({
70
+ severity: "error",
71
+ code: "landscape.binding-unknown",
72
+ subject: e.service,
73
+ message: `landscape: '${e.title}' binds to service '${e.service}', but services/${e.service}/ does not exist`,
74
+ });
75
+ }
76
+ // Walked over the SYSTEM census — the same derivation the scorecard counts,
77
+ // so the map's exemptions and the fleet rollup cannot drift — with the two
78
+ // residual skips that are not exemptions: a bound element is the binding
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.
87
+ for (const e of input.systems) {
88
+ if (e.service !== undefined)
89
+ continue; // graded by the binding pass above
90
+ if (services.has(e.title))
91
+ continue;
92
+ findings.push({
93
+ severity: "warn",
94
+ code: "landscape.service-undocumented",
95
+ subject: e.title,
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`,
103
+ });
104
+ }
105
+ return findings;
106
+ }
107
+ //# sourceMappingURL=bindings.js.map
@@ -0,0 +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, 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"]}
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Who consumes a drawn element — and the datastore grade that reads the answer.
3
+ *
4
+ * The census used to walk the MAP's relationships alone, and under the extending
5
+ * shape that is a partial view of the fleet: a service draws the container-level
6
+ * call in its own `model.likec4` and the map carries the service-level twin, so a
7
+ * store whose consuming edges were all drawn in models had ZERO consumers and
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.
11
+ *
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.
20
+ *
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.
29
+ *
30
+ * The choice is cheap, and the surviving message says exactly HOW cheap, because
31
+ * "reversible" was false in the one state that used to assert it. The two
32
+ * placements do give the store the same id, `<owner-fqn>.<store>` — but the
33
+ * move is only free in ONE direction once a second service names it. Measured
34
+ * on `examples/docs`: moving `db` out of order-service's `extend` block into
35
+ * `orderService` on the map left the model at 12 elements · 8 relationships,
36
+ * left `Covers: marketplace.orderService.db` in its arch.spec.md resolving, and
37
+ * left checkout-web's container-level edge to it resolving too. Moving it BACK
38
+ * with that second edge drawn is two errors on checkout-web's own model
39
+ * ("Could not resolve reference to Referenceable named 'db'"), or
40
+ * `landscape.invalid` plus one `spine.landscape-invalid` per service when the
41
+ * second edge is drawn on the map — because an `extend` block is visible to its
42
+ * own service's project alone. `datastore-shared` printed the unconditional
43
+ * clause in 100% of the states it fires in, every one of which already has the
44
+ * second consumer that makes map→extend break (verification 2026-09-04). So the
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.
47
+ *
48
+ * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it
49
+ * imports NOTHING from `fleet/`, so the map facts it reads arrive on the input
50
+ * records the caller fills.
51
+ */
52
+ import {} from "../../../../core/c4/likec4.js";
53
+ import { infrastructureKind } from "../../../../core/c4/direction/direction.js";
54
+ import { DATASTORE_KIND } from "../../../../core/vocabulary/maturity.js";
55
+ import {} from "../../../../core/vocabulary/report.js";
56
+ import {} from "./attest.js";
57
+ /**
58
+ * Distinct consumer SERVICES of an element, its nested children included: an
59
+ * edge into `kafka.paymentEvents` is consumption of `kafka`.
60
+ *
61
+ * Two kinds of evidence, unioned. From the MAP: a relationship whose target is
62
+ * the element or a descendant and whose source the shared resolver files under a
63
+ * real `services/<id>/` — persons, other external systems and unresolved sources
64
+ * never count, and two edges from one service are one consumer. From the
65
+ * MODELS: a service whose own model attests an OUTBOUND call at the element or a
66
+ * descendant.
67
+ *
68
+ * EITHER DIRECTION, where the element's end of the edge is infrastructure
69
+ * (`infrastructureKind` in core/c4/direction/direction.ts, among the elements
70
+ * that stand for no service). A
71
+ * database, a topic or a bucket never initiates anything, so an edge between it
72
+ * and a service says that the service uses it, whichever way the arrow points.
73
+ * Fleets draw a read both ways, as a call (`service -> store`) or as the data's
74
+ * flow (`store -> service`), and loam's own event doctrine draws every consumer
75
+ * as `topic -> service`. The census used to read only the edges INTO the
76
+ * element, so a reader drawn as data flow stopped counting: the second
77
+ * consumer of a shared store vanished and `landscape.datastore-shared` with
78
+ * it, and a broker's consumers were never counted toward
79
+ * `landscape.platform-candidate` at all, which made the "three of our services
80
+ * reach it" in `examples/docs` false.
81
+ * Elsewhere the direction still means something (an external system calling
82
+ * INTO the fleet is not one the callee consumes), so it is still read there.
83
+ * The model half follows the same rule: an INBOUND attested call counts when
84
+ * its counterpart is infrastructure.
85
+ *
86
+ * The model half reads EXTENDING models only, and the reason is the id space. An
87
+ * extending model's ids are the map's fully-qualified ids — one project — so the
88
+ * join is exact. A standalone model's ids are its own file's, and
89
+ * `core/c4/resolve/attested.ts` says outright that a counterpart is never
90
+ * matched to a landscape element: joining one would file a coincidence of
91
+ * spelling as a coupling between two services.
92
+ */
93
+ export function consumerCensus(input) {
94
+ const reaches = (elementId, id) => id === elementId || id.startsWith(`${elementId}.`);
95
+ const infraKindOf = infrastructureKind(input.elements.filter((e) => !input.standsForService(e)));
96
+ const isInfra = (id) => infraKindOf(id) !== undefined;
97
+ return (elementId) => {
98
+ const out = new Set();
99
+ const count = (id) => {
100
+ const svc = input.resolve(id);
101
+ if (input.services.has(svc))
102
+ out.add(svc);
103
+ };
104
+ for (const r of input.relationships) {
105
+ if (reaches(elementId, r.target))
106
+ count(r.source);
107
+ else if (reaches(elementId, r.source) && isInfra(r.source))
108
+ count(r.target);
109
+ }
110
+ for (const model of input.attested) {
111
+ if (!model.extending)
112
+ continue;
113
+ const uses = model.calls.some((c) => reaches(elementId, c.counterpartId) && (c.direction === "out" || isInfra(c.counterpartId)));
114
+ if (uses)
115
+ out.add(model.service);
116
+ }
117
+ return [...out].sort();
118
+ };
119
+ }
120
+ /**
121
+ * A datastore two or more services reach is the strongest coupling two services
122
+ * can have and the hardest to undo, which deserves to be STATED rather than
123
+ * inferred. That is the whole of this grade now, and it applies to both
124
+ * placements alike: a fleet-level peer and a store nested under its owner earn
125
+ * the same `landscape.datastore-shared` at two consumers, and neither is graded
126
+ * at one.
127
+ *
128
+ * The one-consumer arm is gone (2026-09-07). A fleet-level peer with a single
129
+ * consumer used to be `landscape.datastore-private` — "drawn as a peer it reads
130
+ * as a system in its own right … delete this peer" — which made WHERE a store is
131
+ * drawn a restriction rather than the team's choice. It was never a fact about
132
+ * the fleet: the same store, nested, was silent, so the finding graded the
133
+ * drawing and not the architecture. The nested half was ALREADY silent at one
134
+ * consumer, and it is that half's rule that now holds for both.
135
+ */
136
+ export function datastoreFindings(input) {
137
+ const findings = [];
138
+ for (const e of input.drawn) {
139
+ // An element that stands for a real service is the fleet itself, not its
140
+ // shape; a datastore bound to a directory is graded by the binding checks.
141
+ if (input.standsForService(e))
142
+ continue;
143
+ if (e.kind.toLowerCase() !== DATASTORE_KIND)
144
+ continue;
145
+ const consumers = input.consumers(e.id);
146
+ // One consumer is a store that service owns, drawn where the team chose to
147
+ // draw it — a fact about the map's layout, not about the fleet. Zero is a
148
+ // store nothing reaches, which was never graded here either.
149
+ if (consumers.length < 2)
150
+ continue;
151
+ findings.push(sharedFinding(e.title, consumers, "", ""));
152
+ }
153
+ for (const store of nestedStoresOf(input)) {
154
+ // The owner is a consumer by construction — the store sits inside the element
155
+ // that resolves to it — so silence at one consumer IS the fixed state, and
156
+ // the only question left is whether a second service reaches the same data.
157
+ const consumers = [...new Set([store.owner, ...input.consumers(store.fqn)])].sort();
158
+ if (consumers.length < 2)
159
+ continue;
160
+ findings.push(sharedFinding(store.title, consumers,
161
+ // The store's own id, and said as such. The clause read "nested under
162
+ // '<fqn>'", which sends a reader looking for an element that HOLDS the
163
+ // store — when that id IS the store, and its container is its prefix.
164
+ ` — ${store.owner}'s own store, written as '${store.fqn}'`, reversibility(store, consumers.filter((c) => c !== store.owner))));
165
+ }
166
+ return findings;
167
+ }
168
+ /**
169
+ * Every store nested under the element that resolves to its owner, from both
170
+ * places the stance allows one to be written: inside the service's element ON
171
+ * THE MAP, and in the service's own extending model. Keyed by fqn, so a
172
+ * map-declared store — which the owner's model slice carries too, since a slice
173
+ * is everything resolving to the service — is one store and not two.
174
+ *
175
+ * Both sources are needed and neither is redundant. The map's covers a service
176
+ * whose model is standalone or absent (no slice to read); the model's covers a
177
+ * store the map never draws, which is the placement the adopt brief prescribes.
178
+ */
179
+ function nestedStoresOf(input) {
180
+ const drawnIds = new Set(input.drawn.map((e) => e.id));
181
+ const byFqn = new Map();
182
+ for (const e of input.elements) {
183
+ if (e.kind.toLowerCase() !== DATASTORE_KIND || drawnIds.has(e.id))
184
+ continue;
185
+ // The peer branch's guard, which this half was missing: a `database` bound
186
+ // with `metadata { service '<id>' }` and drawn one level down became a store
187
+ // "owned" by its own binding, so a second reader made it a shared datastore
188
+ // and the fix offered was to give each service its own copy of a service.
189
+ if (input.standsForService(e))
190
+ continue;
191
+ const owner = input.resolve(e.id);
192
+ if (!input.services.has(owner))
193
+ continue;
194
+ byFqn.set(e.id, { owner, fqn: e.id, title: e.title });
195
+ }
196
+ for (const store of input.nestedStores)
197
+ if (!byFqn.has(store.fqn))
198
+ byFqn.set(store.fqn, store);
199
+ return [...byFqn.values()];
200
+ }
201
+ /**
202
+ * Which way the store may still be moved, now that a second service reaches it.
203
+ *
204
+ * The clause this replaces said the two placements were interchangeable — "the
205
+ * two placements give it the same id … so moving the declaration between them
206
+ * rewrites no edge" — and that is false in every state `datastore-shared` fires
207
+ * in, because the finding needs a second consumer and a second consumer's edge
208
+ * resolves only against a store the MAP declares (a per-service project is
209
+ * `architecture/` plus one model). Measured: performing the move the sentence
210
+ * called free turned a clean `examples/docs` into `c4.invalid` on the consumer's
211
+ * model, or into `landscape.invalid` + one `spine.landscape-invalid` per service
212
+ * when the second edge is on the map (verification 2026-09-04).
213
+ *
214
+ * The direction is stated rather than the current placement, and it holds
215
+ * either way round: out of the owner's `extend` block onto the map is always
216
+ * safe, back in is not while another service names the store. (In practice a
217
+ * store this finding reaches is one the MAP declares — a second consumer's edge
218
+ * cannot resolve against an `extend` block, so it could never have been drawn
219
+ * — which is the same fact said from the other side.)
220
+ */
221
+ function reversibility(store, others) {
222
+ const named = others.length === 1 ? `${others[0]}'s edge` : `${others.join(", ")}'s edges`;
223
+ return (`. Both placements give it the same id, ${store.fqn}, but with a second consumer the move is ONE-WAY: ` +
224
+ `${named} resolves only against a declaration the MAP holds — a per-service project is \`architecture/\` ` +
225
+ `plus one model, so an \`extend\` block is visible to its own service's project alone. Out of ` +
226
+ `${store.owner}'s \`extend\` block onto ${store.owner}'s element on the map is always safe; moving it ` +
227
+ `back INTO the \`extend\` block breaks that edge — \`c4.invalid\` on the other service's model, or ` +
228
+ `\`landscape.invalid\` when the edge is drawn on the map`);
229
+ }
230
+ /** The two-or-more message, shared by the peer and the nested store — one word about coupling, once. */
231
+ function sharedFinding(title, consumers, where, tail) {
232
+ return {
233
+ severity: "warn",
234
+ code: "landscape.datastore-shared",
235
+ subject: title,
236
+ message: `landscape: '${title}' is a datastore shared by ${consumers.length} services ` +
237
+ `(${consumers.join(", ")})${where} — the strongest coupling two services can have, and the ` +
238
+ `hardest to undo. Shared means the same DATA: if they read the same tables or keys, keep it ` +
239
+ `where it is and let this warning state the coupling; if they only share a host or cluster ` +
240
+ `(two schemas, two lock paths), that is operational blast radius — a runbook fact — and the ` +
241
+ `honest model is one private store per service${tail}`,
242
+ };
243
+ }
244
+ //# sourceMappingURL=consumers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumers.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/consumers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Who consumes a drawn element — and the datastore grade that reads the answer.\n *\n * The census used to walk the MAP's relationships alone, and under the extending\n * shape that is a partial view of the fleet: a service draws the container-level\n * call in its own `model.likec4` and the map carries the service-level twin, so a\n * store whose consuming edges were all drawn in models had ZERO consumers and\n * was graded on the one service the map happened to draw (R1). A model is\n * evidence about the fleet exactly as the map is, so both are counted here,\n * once, by one derivation two checks share.\n *\n * WHERE A STORE LIVES IS THE TEAM'S CHOICE, and this module used to say the\n * opposite at length. Nested under the element that resolves to its owner — in\n * that service's `extend <fqn> { store = database … }` block, or inside its\n * element on the map — and drawn as a fleet-level PEER are both first-class\n * placements, and loam grades neither as wrong. `landscape.datastore-private`\n * existed to push every one-consumer store into the first, its whole message a\n * prescription to delete the peer; it is retired (2026-09-07), the arm below is\n * gone, and no message here prescribes a move.\n *\n * What survives the retirement is the one CONSEQUENCE, which is a fact about\n * LikeC4 rather than a preference: a per-service project is `architecture/` plus\n * that ONE model, so an edge naming `<owner-fqn>.<store>` resolves for a SECOND\n * service only when the MAP declares the store. Written in the owner's `extend`\n * block instead, the same edge is two errors on the consumer's own model\n * (\"Could not resolve reference to Referenceable named 'store'\"). That is the\n * load-bearing half, it is stated by `reversibility` below where it actually\n * bites, and SCHEMA carries the same sentence.\n *\n * The choice is cheap, and the surviving message says exactly HOW cheap, because\n * \"reversible\" was false in the one state that used to assert it. The two\n * placements do give the store the same id, `<owner-fqn>.<store>` — but the\n * move is only free in ONE direction once a second service names it. Measured\n * on `examples/docs`: moving `db` out of order-service's `extend` block into\n * `orderService` on the map left the model at 12 elements · 8 relationships,\n * left `Covers: marketplace.orderService.db` in its arch.spec.md resolving, and\n * left checkout-web's container-level edge to it resolving too. Moving it BACK\n * with that second edge drawn is two errors on checkout-web's own model\n * (\"Could not resolve reference to Referenceable named 'db'\"), or\n * `landscape.invalid` plus one `spine.landscape-invalid` per service when the\n * second edge is drawn on the map — because an `extend` block is visible to its\n * own service's project alone. `datastore-shared` printed the unconditional\n * clause in 100% of the states it fires in, every one of which already has the\n * second consumer that makes map→extend break (verification 2026-09-04). So the\n * rule the message states is directional: extend→map is always safe; map→extend\n * only while no other service's model or map edge names the store.\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 reads arrive on the input\n * records the caller fills.\n */\nimport { type Elem, type Rel } from \"../../../../core/c4/likec4.js\";\nimport { infrastructureKind } from \"../../../../core/c4/direction/direction.js\";\nimport { DATASTORE_KIND } from \"../../../../core/vocabulary/maturity.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\nimport { type AttestedModel, type NestedStore } from \"./attest.js\";\n\nexport interface CensusInput {\n /** The map's own edges — a service-level twin is what the map carries of a container-level call. */\n relationships: Rel[];\n /** Every element the map declares, at any depth: what says an endpoint is infrastructure. */\n elements: Elem[];\n /** The services/<id>/ directories that exist: a consumer is an edge's other end filed under a real one. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver every edge join uses. */\n resolve: (id: string) => string;\n /** `census.ts`'s predicate, for `DatastoreInput.standsForService`'s reason: a bound `database` is a service, not infrastructure. */\n standsForService: (e: Elem) => boolean;\n /** What each service's own model attests (`./attest.ts`). */\n attested: readonly AttestedModel[];\n}\n\n/**\n * Distinct consumer SERVICES of an element, its nested children included: an\n * edge into `kafka.paymentEvents` is consumption of `kafka`.\n *\n * Two kinds of evidence, unioned. From the MAP: a relationship whose target is\n * the element or a descendant and whose source the shared resolver files under a\n * real `services/<id>/` — persons, other external systems and unresolved sources\n * never count, and two edges from one service are one consumer. From the\n * MODELS: a service whose own model attests an OUTBOUND call at the element or a\n * descendant.\n *\n * EITHER DIRECTION, where the element's end of the edge is infrastructure\n * (`infrastructureKind` in core/c4/direction/direction.ts, among the elements\n * that stand for no service). A\n * database, a topic or a bucket never initiates anything, so an edge between it\n * and a service says that the service uses it, whichever way the arrow points.\n * Fleets draw a read both ways, as a call (`service -> store`) or as the data's\n * flow (`store -> service`), and loam's own event doctrine draws every consumer\n * as `topic -> service`. The census used to read only the edges INTO the\n * element, so a reader drawn as data flow stopped counting: the second\n * consumer of a shared store vanished and `landscape.datastore-shared` with\n * it, and a broker's consumers were never counted toward\n * `landscape.platform-candidate` at all, which made the \"three of our services\n * reach it\" in `examples/docs` false.\n * Elsewhere the direction still means something (an external system calling\n * INTO the fleet is not one the callee consumes), so it is still read there.\n * The model half follows the same rule: an INBOUND attested call counts when\n * its counterpart is infrastructure.\n *\n * The model half reads EXTENDING models only, and the reason is the id space. An\n * extending model's ids are the map's fully-qualified ids — one project — so the\n * join is exact. A standalone model's ids are its own file's, and\n * `core/c4/resolve/attested.ts` says outright that a counterpart is never\n * matched to a landscape element: joining one would file a coincidence of\n * spelling as a coupling between two services.\n */\nexport function consumerCensus(input: CensusInput): (elementId: string) => string[] {\n const reaches = (elementId: string, id: string): boolean => id === elementId || id.startsWith(`${elementId}.`);\n const infraKindOf = infrastructureKind(input.elements.filter((e) => !input.standsForService(e)));\n const isInfra = (id: string): boolean => infraKindOf(id) !== undefined;\n return (elementId: string): string[] => {\n const out = new Set<string>();\n const count = (id: string): void => {\n const svc = input.resolve(id);\n if (input.services.has(svc)) out.add(svc);\n };\n for (const r of input.relationships) {\n if (reaches(elementId, r.target)) count(r.source);\n else if (reaches(elementId, r.source) && isInfra(r.source)) count(r.target);\n }\n for (const model of input.attested) {\n if (!model.extending) continue;\n const uses = model.calls.some(\n (c) => reaches(elementId, c.counterpartId) && (c.direction === \"out\" || isInfra(c.counterpartId)),\n );\n if (uses) out.add(model.service);\n }\n return [...out].sort();\n };\n}\n\nexport interface DatastoreInput {\n /** Service-LEVEL elements — the fleet map's own boxes, where a store drawn as a peer is found. */\n drawn: Elem[];\n /** Every element the MAP declares, at any depth — the other place a nested store is written. */\n elements: Elem[];\n /** Stores an extending model declares inside its own element (`./attest.ts`). */\n nestedStores: readonly NestedStore[];\n /** The services/<id>/ directories that exist. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver, for naming the consumer's own element on the map. */\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/`. Both halves\n * of the grade below skip such an element: it is the fleet itself, not its\n * shape, and a store bound to a directory is the binding checks' subject.\n * `./attest.ts` applies the same predicate to the stores a MODEL declares, so\n * all three places answer it once.\n */\n standsForService: (e: Elem) => boolean;\n /** The census above, built once and shared with the platform-candidate advisory. */\n consumers: (elementId: string) => string[];\n}\n\n/**\n * A datastore two or more services reach is the strongest coupling two services\n * can have and the hardest to undo, which deserves to be STATED rather than\n * inferred. That is the whole of this grade now, and it applies to both\n * placements alike: a fleet-level peer and a store nested under its owner earn\n * the same `landscape.datastore-shared` at two consumers, and neither is graded\n * at one.\n *\n * The one-consumer arm is gone (2026-09-07). A fleet-level peer with a single\n * consumer used to be `landscape.datastore-private` — \"drawn as a peer it reads\n * as a system in its own right … delete this peer\" — which made WHERE a store is\n * drawn a restriction rather than the team's choice. It was never a fact about\n * the fleet: the same store, nested, was silent, so the finding graded the\n * drawing and not the architecture. The nested half was ALREADY silent at one\n * consumer, and it is that half's rule that now holds for both.\n */\nexport function datastoreFindings(input: DatastoreInput): Finding[] {\n const findings: Finding[] = [];\n for (const e of input.drawn) {\n // An element that stands for a real service is the fleet itself, not its\n // shape; a datastore bound to a directory is graded by the binding checks.\n if (input.standsForService(e)) continue;\n if (e.kind.toLowerCase() !== DATASTORE_KIND) continue;\n const consumers = input.consumers(e.id);\n // One consumer is a store that service owns, drawn where the team chose to\n // draw it — a fact about the map's layout, not about the fleet. Zero is a\n // store nothing reaches, which was never graded here either.\n if (consumers.length < 2) continue;\n findings.push(sharedFinding(e.title, consumers, \"\", \"\"));\n }\n for (const store of nestedStoresOf(input)) {\n // The owner is a consumer by construction — the store sits inside the element\n // that resolves to it — so silence at one consumer IS the fixed state, and\n // the only question left is whether a second service reaches the same data.\n const consumers = [...new Set([store.owner, ...input.consumers(store.fqn)])].sort();\n if (consumers.length < 2) continue;\n findings.push(\n sharedFinding(\n store.title,\n consumers,\n // The store's own id, and said as such. The clause read \"nested under\n // '<fqn>'\", which sends a reader looking for an element that HOLDS the\n // store — when that id IS the store, and its container is its prefix.\n ` — ${store.owner}'s own store, written as '${store.fqn}'`,\n reversibility(store, consumers.filter((c) => c !== store.owner)),\n ),\n );\n }\n\n return findings;\n}\n\n/**\n * Every store nested under the element that resolves to its owner, from both\n * places the stance allows one to be written: inside the service's element ON\n * THE MAP, and in the service's own extending model. Keyed by fqn, so a\n * map-declared store — which the owner's model slice carries too, since a slice\n * is everything resolving to the service — is one store and not two.\n *\n * Both sources are needed and neither is redundant. The map's covers a service\n * whose model is standalone or absent (no slice to read); the model's covers a\n * store the map never draws, which is the placement the adopt brief prescribes.\n */\nfunction nestedStoresOf(input: DatastoreInput): NestedStore[] {\n const drawnIds = new Set(input.drawn.map((e) => e.id));\n const byFqn = new Map<string, NestedStore>();\n for (const e of input.elements) {\n if (e.kind.toLowerCase() !== DATASTORE_KIND || drawnIds.has(e.id)) continue;\n // The peer branch's guard, which this half was missing: a `database` bound\n // with `metadata { service '<id>' }` and drawn one level down became a store\n // \"owned\" by its own binding, so a second reader made it a shared datastore\n // and the fix offered was to give each service its own copy of a service.\n if (input.standsForService(e)) continue;\n const owner = input.resolve(e.id);\n if (!input.services.has(owner)) continue;\n byFqn.set(e.id, { owner, fqn: e.id, title: e.title });\n }\n for (const store of input.nestedStores) if (!byFqn.has(store.fqn)) byFqn.set(store.fqn, store);\n return [...byFqn.values()];\n}\n\n/**\n * Which way the store may still be moved, now that a second service reaches it.\n *\n * The clause this replaces said the two placements were interchangeable — \"the\n * two placements give it the same id … so moving the declaration between them\n * rewrites no edge\" — and that is false in every state `datastore-shared` fires\n * in, because the finding needs a second consumer and a second consumer's edge\n * resolves only against a store the MAP declares (a per-service project is\n * `architecture/` plus one model). Measured: performing the move the sentence\n * called free turned a clean `examples/docs` into `c4.invalid` on the consumer's\n * model, or into `landscape.invalid` + one `spine.landscape-invalid` per service\n * when the second edge is on the map (verification 2026-09-04).\n *\n * The direction is stated rather than the current placement, and it holds\n * either way round: out of the owner's `extend` block onto the map is always\n * safe, back in is not while another service names the store. (In practice a\n * store this finding reaches is one the MAP declares — a second consumer's edge\n * cannot resolve against an `extend` block, so it could never have been drawn\n * — which is the same fact said from the other side.)\n */\nfunction reversibility(store: NestedStore, others: string[]): string {\n const named = others.length === 1 ? `${others[0]!}'s edge` : `${others.join(\", \")}'s edges`;\n return (\n `. Both placements give it the same id, ${store.fqn}, but with a second consumer the move is ONE-WAY: ` +\n `${named} resolves only against a declaration the MAP holds — a per-service project is \\`architecture/\\` ` +\n `plus one model, so an \\`extend\\` block is visible to its own service's project alone. Out of ` +\n `${store.owner}'s \\`extend\\` block onto ${store.owner}'s element on the map is always safe; moving it ` +\n `back INTO the \\`extend\\` block breaks that edge — \\`c4.invalid\\` on the other service's model, or ` +\n `\\`landscape.invalid\\` when the edge is drawn on the map`\n );\n}\n\n/** The two-or-more message, shared by the peer and the nested store — one word about coupling, once. */\nfunction sharedFinding(title: string, consumers: string[], where: string, tail: string): Finding {\n return {\n severity: \"warn\",\n code: \"landscape.datastore-shared\",\n subject: title,\n message:\n `landscape: '${title}' is a datastore shared by ${consumers.length} services ` +\n `(${consumers.join(\", \")})${where} — the strongest coupling two services can have, and the ` +\n `hardest to undo. Shared means the same DATA: if they read the same tables or keys, keep it ` +\n `where it is and let this warning state the coupling; if they only share a host or cluster ` +\n `(two schemas, two lock paths), that is operational blast radius — a runbook fact — and the ` +\n `honest model is one private store per service${tail}`,\n };\n}\n"]}