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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (648) hide show
  1. package/CHANGELOG.md +1315 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +83 -7
  8. package/SCHEMA.md +569 -98
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +33 -11
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +21 -25
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/seed.js +6 -9
  118. package/dist/commands/seed/seed.js.map +1 -1
  119. package/dist/commands/show/show.js +3 -7
  120. package/dist/commands/show/show.js.map +1 -1
  121. package/dist/commands/status/print.js +13 -4
  122. package/dist/commands/status/print.js.map +1 -1
  123. package/dist/commands/status/status.js +3 -7
  124. package/dist/commands/status/status.js.map +1 -1
  125. package/dist/commands/steps/steps.js +3 -7
  126. package/dist/commands/steps/steps.js.map +1 -1
  127. package/dist/commands/subsystem/edit.js +3 -5
  128. package/dist/commands/subsystem/edit.js.map +1 -1
  129. package/dist/commands/subsystem/move.js +5 -8
  130. package/dist/commands/subsystem/move.js.map +1 -1
  131. package/dist/commands/subsystem/query.js +2 -4
  132. package/dist/commands/subsystem/query.js.map +1 -1
  133. package/dist/commands/subsystem/subsystem.js +4 -7
  134. package/dist/commands/subsystem/subsystem.js.map +1 -1
  135. package/dist/commands/subsystem/sync.js +14 -41
  136. package/dist/commands/subsystem/sync.js.map +1 -1
  137. package/dist/commands/subsystem/txn/txn.js +17 -38
  138. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  139. package/dist/commands/subsystem/txn/views.js +32 -5
  140. package/dist/commands/subsystem/txn/views.js.map +1 -1
  141. package/dist/commands/tasks/render.js +116 -0
  142. package/dist/commands/tasks/render.js.map +1 -0
  143. package/dist/commands/tasks/tasks.js +113 -0
  144. package/dist/commands/tasks/tasks.js.map +1 -0
  145. package/dist/commands/unarchive/manifest.js +6 -9
  146. package/dist/commands/unarchive/manifest.js.map +1 -1
  147. package/dist/commands/unarchive/restore.js +17 -28
  148. package/dist/commands/unarchive/restore.js.map +1 -1
  149. package/dist/commands/unarchive/unarchive.js +22 -30
  150. package/dist/commands/unarchive/unarchive.js.map +1 -1
  151. package/dist/commands/validate/arch-coverage.js +6 -15
  152. package/dist/commands/validate/arch-coverage.js.map +1 -1
  153. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  154. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  155. package/dist/commands/validate/checks/requirements.js +18 -8
  156. package/dist/commands/validate/checks/requirements.js.map +1 -1
  157. package/dist/commands/validate/checks/sources.js +6 -2
  158. package/dist/commands/validate/checks/sources.js.map +1 -1
  159. package/dist/commands/validate/feature.js +23 -19
  160. package/dist/commands/validate/feature.js.map +1 -1
  161. package/dist/commands/validate/fleet/landscape.js +23 -23
  162. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  163. package/dist/commands/validate/fleet/map/attest.js +6 -10
  164. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  165. package/dist/commands/validate/fleet/map/bindings.js +1 -3
  166. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  167. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  168. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  169. package/dist/commands/validate/fleet/obligations.js +1 -5
  170. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  171. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  172. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  173. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  174. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  175. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  176. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  177. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  178. package/dist/commands/validate/fleet/views/root-documents.js +8 -28
  179. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  180. package/dist/commands/validate/links/corpus.js +1 -4
  181. package/dist/commands/validate/links/corpus.js.map +1 -1
  182. package/dist/commands/validate/links/glossary.js.map +1 -1
  183. package/dist/commands/validate/report.js +5 -4
  184. package/dist/commands/validate/report.js.map +1 -1
  185. package/dist/commands/validate/scope.js +113 -0
  186. package/dist/commands/validate/scope.js.map +1 -0
  187. package/dist/commands/validate/service/api.js +16 -104
  188. package/dist/commands/validate/service/api.js.map +1 -1
  189. package/dist/commands/validate/service/contracts/generated.js +134 -0
  190. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  191. package/dist/commands/validate/service/events/events.js +1 -1
  192. package/dist/commands/validate/service/events/events.js.map +1 -1
  193. package/dist/commands/validate/service/events/message-producers.js +1 -1
  194. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  195. package/dist/commands/validate/service/evidence-pins.js +56 -10
  196. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  197. package/dist/commands/validate/service/service.js +16 -26
  198. package/dist/commands/validate/service/service.js.map +1 -1
  199. package/dist/commands/validate/service/specs.js +31 -38
  200. package/dist/commands/validate/service/specs.js.map +1 -1
  201. package/dist/commands/validate/service/spine.js +8 -11
  202. package/dist/commands/validate/service/spine.js.map +1 -1
  203. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  204. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  205. package/dist/commands/validate/validate.js +44 -26
  206. package/dist/commands/validate/validate.js.map +1 -1
  207. package/dist/commands/verify/cross/diff.js +2 -6
  208. package/dist/commands/verify/cross/diff.js.map +1 -1
  209. package/dist/commands/verify/evidence/contract.js +1 -1
  210. package/dist/commands/verify/evidence/contract.js.map +1 -1
  211. package/dist/commands/verify/evidence/read.js +12 -3
  212. package/dist/commands/verify/evidence/read.js.map +1 -1
  213. package/dist/commands/verify/frozen.js +45 -23
  214. package/dist/commands/verify/frozen.js.map +1 -1
  215. package/dist/commands/verify/record.js +69 -17
  216. package/dist/commands/verify/record.js.map +1 -1
  217. package/dist/commands/verify/report.js +27 -44
  218. package/dist/commands/verify/report.js.map +1 -1
  219. package/dist/commands/verify/results.js +112 -30
  220. package/dist/commands/verify/results.js.map +1 -1
  221. package/dist/commands/verify/verify.js +54 -51
  222. package/dist/commands/verify/verify.js.map +1 -1
  223. package/dist/commands/vouch/pack/pack.js +2 -2
  224. package/dist/commands/vouch/pack/pack.js.map +1 -1
  225. package/dist/commands/vouch/pack/print.js +1 -6
  226. package/dist/commands/vouch/pack/print.js.map +1 -1
  227. package/dist/commands/vouch/pack/sections.js +1 -2
  228. package/dist/commands/vouch/pack/sections.js.map +1 -1
  229. package/dist/commands/vouch/run.js +26 -81
  230. package/dist/commands/vouch/run.js.map +1 -1
  231. package/dist/commands/vouch/sample/plan.js +20 -34
  232. package/dist/commands/vouch/sample/plan.js.map +1 -1
  233. package/dist/commands/vouch/vet/verify.js +39 -7
  234. package/dist/commands/vouch/vet/verify.js.map +1 -1
  235. package/dist/commands/vouch/vouch.js +10 -17
  236. package/dist/commands/vouch/vouch.js.map +1 -1
  237. package/dist/core/agent/agents-md/command-map.js +56 -101
  238. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  239. package/dist/core/agent/agents-md/map/explain.js +4 -3
  240. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  241. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  242. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  243. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  244. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  245. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  246. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  247. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  248. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  249. package/dist/core/agent/agents-md/map/status.js +9 -3
  250. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  251. package/dist/core/agent/agents-md/map/subsystem.js +4 -3
  252. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  253. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  254. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  255. package/dist/core/agent/agents-md/refusals.js +32 -18
  256. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  257. package/dist/core/agent/agents-md.js +9 -3
  258. package/dist/core/agent/agents-md.js.map +1 -1
  259. package/dist/core/agent/agents-stamp.js +10 -0
  260. package/dist/core/agent/agents-stamp.js.map +1 -1
  261. package/dist/core/agent/scaffold.js +10 -15
  262. package/dist/core/agent/scaffold.js.map +1 -1
  263. package/dist/core/agent/tools/dialects.js +1 -0
  264. package/dist/core/agent/tools/dialects.js.map +1 -1
  265. package/dist/core/agent/tools/registry.js +41 -76
  266. package/dist/core/agent/tools/registry.js.map +1 -1
  267. package/dist/core/agent/workflows/adopt.js +5 -3
  268. package/dist/core/agent/workflows/adopt.js.map +1 -1
  269. package/dist/core/agent/workflows/check.js +20 -16
  270. package/dist/core/agent/workflows/check.js.map +1 -1
  271. package/dist/core/agent/workflows/closing.js +54 -21
  272. package/dist/core/agent/workflows/closing.js.map +1 -1
  273. package/dist/core/agent/workflows/feature.js +79 -11
  274. package/dist/core/agent/workflows/feature.js.map +1 -1
  275. package/dist/core/agent/workflows/implement.js +91 -13
  276. package/dist/core/agent/workflows/implement.js.map +1 -1
  277. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  278. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  279. package/dist/core/agent/workflows/reference/codes.js +8 -6
  280. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  281. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  282. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  283. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  284. package/dist/core/agent/workflows/reference/spine.js +50 -18
  285. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  286. package/dist/core/asyncapi/baseline/plan.js +14 -81
  287. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  288. package/dist/core/asyncapi/digest.js +30 -25
  289. package/dist/core/asyncapi/digest.js.map +1 -1
  290. package/dist/core/asyncapi/merge/components.js +5 -4
  291. package/dist/core/asyncapi/merge/components.js.map +1 -1
  292. package/dist/core/asyncapi/merge/error.js +7 -2
  293. package/dist/core/asyncapi/merge/error.js.map +1 -1
  294. package/dist/core/asyncapi/merge/markers.js +30 -50
  295. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  296. package/dist/core/asyncapi/merge/merge.js +31 -53
  297. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  298. package/dist/core/asyncapi/merge/pin.js +10 -36
  299. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  300. package/dist/core/asyncapi/model.js.map +1 -1
  301. package/dist/core/asyncapi/read.js +36 -12
  302. package/dist/core/asyncapi/read.js.map +1 -1
  303. package/dist/core/brief/brief.js +6 -18
  304. package/dist/core/brief/brief.js.map +1 -1
  305. package/dist/core/brief/landscape.js +8 -12
  306. package/dist/core/brief/landscape.js.map +1 -1
  307. package/dist/core/brief/map/extend.js +8 -5
  308. package/dist/core/brief/map/extend.js.map +1 -1
  309. package/dist/core/brief/map/owed.js +8 -26
  310. package/dist/core/brief/map/owed.js.map +1 -1
  311. package/dist/core/brief/targets.js +15 -5
  312. package/dist/core/brief/targets.js.map +1 -1
  313. package/dist/core/c4/likec4.js +147 -16
  314. package/dist/core/c4/likec4.js.map +1 -1
  315. package/dist/core/c4/locate/unlocated.js +150 -0
  316. package/dist/core/c4/locate/unlocated.js.map +1 -0
  317. package/dist/core/c4/parsed/values.js +54 -1
  318. package/dist/core/c4/parsed/values.js.map +1 -1
  319. package/dist/core/c4/prefetch/fleet.js +1 -9
  320. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  321. package/dist/core/c4/project/documents.js +31 -6
  322. package/dist/core/c4/project/documents.js.map +1 -1
  323. package/dist/core/c4/project/load.js +6 -17
  324. package/dist/core/c4/project/load.js.map +1 -1
  325. package/dist/core/c4/resolve/external.js +51 -0
  326. package/dist/core/c4/resolve/external.js.map +1 -0
  327. package/dist/core/c4/resolve/service.js +25 -1
  328. package/dist/core/c4/resolve/service.js.map +1 -1
  329. package/dist/core/c4/seed/template.js +15 -1
  330. package/dist/core/c4/seed/template.js.map +1 -1
  331. package/dist/core/c4/service-model/shape.js +2 -22
  332. package/dist/core/c4/service-model/shape.js.map +1 -1
  333. package/dist/core/c4/source-mask.js +288 -4
  334. package/dist/core/c4/source-mask.js.map +1 -1
  335. package/dist/core/c4/source-scan.js +203 -147
  336. package/dist/core/c4/source-scan.js.map +1 -1
  337. package/dist/core/c4/splice/authored-source.js +19 -0
  338. package/dist/core/c4/splice/authored-source.js.map +1 -1
  339. package/dist/core/c4/splice/delta-blocks.js +5 -18
  340. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  341. package/dist/core/c4/splice/landscape-merge.js +13 -8
  342. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  343. package/dist/core/c4/splice/model/merge.js +10 -6
  344. package/dist/core/c4/splice/model/merge.js.map +1 -1
  345. package/dist/core/c4/splice/placement.js +108 -18
  346. package/dist/core/c4/splice/placement.js.map +1 -1
  347. package/dist/core/c4/workspace.js +13 -22
  348. package/dist/core/c4/workspace.js.map +1 -1
  349. package/dist/core/capabilities/capabilities.js +6 -6
  350. package/dist/core/capabilities/capabilities.js.map +1 -1
  351. package/dist/core/capabilities/delta/doc.js +7 -10
  352. package/dist/core/capabilities/delta/doc.js.map +1 -1
  353. package/dist/core/capabilities/findings.js +15 -20
  354. package/dist/core/capabilities/findings.js.map +1 -1
  355. package/dist/core/capabilities/realizes/findings.js +12 -19
  356. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  357. package/dist/core/capabilities/realizes/join.js +0 -20
  358. package/dist/core/capabilities/realizes/join.js.map +1 -1
  359. package/dist/core/capabilities/rollup.js +6 -0
  360. package/dist/core/capabilities/rollup.js.map +1 -1
  361. package/dist/core/capabilities/tree.js +6 -28
  362. package/dist/core/capabilities/tree.js.map +1 -1
  363. package/dist/core/capabilities/usecase-join.js +3 -13
  364. package/dist/core/capabilities/usecase-join.js.map +1 -1
  365. package/dist/core/coherence/authoring/scaffold.js +11 -3
  366. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  367. package/dist/core/coherence/authoring/sentinels.js +2 -1
  368. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  369. package/dist/core/coherence/coherence.js +30 -16
  370. package/dist/core/coherence/coherence.js.map +1 -1
  371. package/dist/core/coherence/consumed/operations.js +70 -0
  372. package/dist/core/coherence/consumed/operations.js.map +1 -0
  373. package/dist/core/coherence/consumed/refusal.js +38 -0
  374. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  375. package/dist/core/coherence/declared.js +60 -34
  376. package/dist/core/coherence/declared.js.map +1 -1
  377. package/dist/core/coherence/events/declared.js +44 -59
  378. package/dist/core/coherence/events/declared.js.map +1 -1
  379. package/dist/core/coherence/events/events.js +119 -8
  380. package/dist/core/coherence/events/events.js.map +1 -1
  381. package/dist/core/coherence/events/grades.js +31 -8
  382. package/dist/core/coherence/events/grades.js.map +1 -1
  383. package/dist/core/coherence/events/lookups.js +14 -82
  384. package/dist/core/coherence/events/lookups.js.map +1 -1
  385. package/dist/core/coherence/lookups.js +18 -78
  386. package/dist/core/coherence/lookups.js.map +1 -1
  387. package/dist/core/coherence/retired/archived.js +285 -0
  388. package/dist/core/coherence/retired/archived.js.map +1 -0
  389. package/dist/core/coherence/retired/event-pair.js +62 -0
  390. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  391. package/dist/core/coherence/retired/merged.js +79 -0
  392. package/dist/core/coherence/retired/merged.js.map +1 -0
  393. package/dist/core/coherence/retired/retirement.js +49 -0
  394. package/dist/core/coherence/retired/retirement.js.map +1 -0
  395. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  396. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  397. package/dist/core/coherence/retired/revival/revived.js +297 -0
  398. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  399. package/dist/core/consumers/after.js +113 -0
  400. package/dist/core/consumers/after.js.map +1 -0
  401. package/dist/core/consumers/consumers.js +296 -0
  402. package/dist/core/consumers/consumers.js.map +1 -0
  403. package/dist/core/consumers/current.js +211 -0
  404. package/dist/core/consumers/current.js.map +1 -0
  405. package/dist/core/consumers/scan.js +46 -0
  406. package/dist/core/consumers/scan.js.map +1 -0
  407. package/dist/core/consumers/state.js +27 -0
  408. package/dist/core/consumers/state.js.map +1 -0
  409. package/dist/core/delta/delta.js +1 -1
  410. package/dist/core/delta/delta.js.map +1 -1
  411. package/dist/core/deployment/delta.js +3 -6
  412. package/dist/core/deployment/delta.js.map +1 -1
  413. package/dist/core/diff/base-git.js +94 -12
  414. package/dist/core/diff/base-git.js.map +1 -1
  415. package/dist/core/diff/base-state.js +28 -16
  416. package/dist/core/diff/base-state.js.map +1 -1
  417. package/dist/core/diff/joins.js +136 -0
  418. package/dist/core/diff/joins.js.map +1 -0
  419. package/dist/core/diff/scope/changed-paths.js +22 -6
  420. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  421. package/dist/core/diff/scope/edges.js +82 -0
  422. package/dist/core/diff/scope/edges.js.map +1 -0
  423. package/dist/core/diff/semantic.js +100 -31
  424. package/dist/core/diff/semantic.js.map +1 -1
  425. package/dist/core/doctor/agents.js +34 -18
  426. package/dist/core/doctor/agents.js.map +1 -1
  427. package/dist/core/doctor/report.js.map +1 -1
  428. package/dist/core/document/apply.js +10 -2
  429. package/dist/core/document/apply.js.map +1 -1
  430. package/dist/core/document/frontmatter.js +55 -0
  431. package/dist/core/document/frontmatter.js.map +1 -1
  432. package/dist/core/document/parse.js +29 -6
  433. package/dist/core/document/parse.js.map +1 -1
  434. package/dist/core/document/scenarios.js +17 -0
  435. package/dist/core/document/scenarios.js.map +1 -1
  436. package/dist/core/document/spec.js +4 -11
  437. package/dist/core/document/spec.js.map +1 -1
  438. package/dist/core/envelope/config.js +10 -0
  439. package/dist/core/envelope/config.js.map +1 -1
  440. package/dist/core/envelope/json.js +34 -0
  441. package/dist/core/envelope/json.js.map +1 -1
  442. package/dist/core/explain/families.js +17 -12
  443. package/dist/core/explain/families.js.map +1 -1
  444. package/dist/core/explain/refusals.js +3 -3
  445. package/dist/core/explain/refusals.js.map +1 -1
  446. package/dist/core/explain/terms.js +18 -9
  447. package/dist/core/explain/terms.js.map +1 -1
  448. package/dist/core/explore/explore.js +2 -7
  449. package/dist/core/explore/explore.js.map +1 -1
  450. package/dist/core/fleet-context.js +34 -95
  451. package/dist/core/fleet-context.js.map +1 -1
  452. package/dist/core/gate/checks.js +5 -2
  453. package/dist/core/gate/checks.js.map +1 -1
  454. package/dist/core/gate/partners.js +4 -2
  455. package/dist/core/gate/partners.js.map +1 -1
  456. package/dist/core/gate/report.js.map +1 -1
  457. package/dist/core/gate/verification.js +75 -12
  458. package/dist/core/gate/verification.js.map +1 -1
  459. package/dist/core/gherkin/stale.js +13 -37
  460. package/dist/core/gherkin/stale.js.map +1 -1
  461. package/dist/core/glossary/tree.js +6 -19
  462. package/dist/core/glossary/tree.js.map +1 -1
  463. package/dist/core/kernel/concurrency.js +19 -0
  464. package/dist/core/kernel/concurrency.js.map +1 -1
  465. package/dist/core/kernel/records.js +11 -0
  466. package/dist/core/kernel/records.js.map +1 -1
  467. package/dist/core/links/backlinks.js +1 -3
  468. package/dist/core/links/backlinks.js.map +1 -1
  469. package/dist/core/links/corpus.js +5 -36
  470. package/dist/core/links/corpus.js.map +1 -1
  471. package/dist/core/mcp/argv.js +101 -0
  472. package/dist/core/mcp/argv.js.map +1 -1
  473. package/dist/core/mcp/author-tools.js +2 -1
  474. package/dist/core/mcp/author-tools.js.map +1 -1
  475. package/dist/core/mcp/protocol.js +2 -2
  476. package/dist/core/mcp/protocol.js.map +1 -1
  477. package/dist/core/mcp/tools.js +43 -33
  478. package/dist/core/mcp/tools.js.map +1 -1
  479. package/dist/core/openapi/baseline/gate.js +53 -12
  480. package/dist/core/openapi/baseline/gate.js.map +1 -1
  481. package/dist/core/openapi/baseline/plan.js +22 -8
  482. package/dist/core/openapi/baseline/plan.js.map +1 -1
  483. package/dist/core/openapi/baseline/record.js +0 -1
  484. package/dist/core/openapi/baseline/record.js.map +1 -1
  485. package/dist/core/openapi/contract-text.js +33 -0
  486. package/dist/core/openapi/contract-text.js.map +1 -0
  487. package/dist/core/openapi/contract-tree.js +56 -0
  488. package/dist/core/openapi/contract-tree.js.map +1 -0
  489. package/dist/core/openapi/depth.js +38 -24
  490. package/dist/core/openapi/depth.js.map +1 -1
  491. package/dist/core/openapi/digest.js +52 -2
  492. package/dist/core/openapi/digest.js.map +1 -1
  493. package/dist/core/openapi/doc.js +12 -4
  494. package/dist/core/openapi/doc.js.map +1 -1
  495. package/dist/core/openapi/merge/components.js +25 -60
  496. package/dist/core/openapi/merge/components.js.map +1 -1
  497. package/dist/core/openapi/merge/error.js +3 -13
  498. package/dist/core/openapi/merge/error.js.map +1 -1
  499. package/dist/core/openapi/merge/markers.js +26 -30
  500. package/dist/core/openapi/merge/markers.js.map +1 -1
  501. package/dist/core/openapi/merge/merge.js +26 -52
  502. package/dist/core/openapi/merge/merge.js.map +1 -1
  503. package/dist/core/openapi/merge/pin.js +15 -35
  504. package/dist/core/openapi/merge/pin.js.map +1 -1
  505. package/dist/core/openspec/decide/verdict.js.map +1 -1
  506. package/dist/core/openspec/inventory.js +4 -5
  507. package/dist/core/openspec/inventory.js.map +1 -1
  508. package/dist/core/openspec/model/mapping.js +30 -28
  509. package/dist/core/openspec/model/mapping.js.map +1 -1
  510. package/dist/core/openspec/model/model.js +12 -7
  511. package/dist/core/openspec/model/model.js.map +1 -1
  512. package/dist/core/openspec/scan/changes.js +10 -4
  513. package/dist/core/openspec/scan/changes.js.map +1 -1
  514. package/dist/core/openspec/scan/config.js +7 -16
  515. package/dist/core/openspec/scan/config.js.map +1 -1
  516. package/dist/core/openspec/scan/shape.js +63 -19
  517. package/dist/core/openspec/scan/shape.js.map +1 -1
  518. package/dist/core/openspec/scan/walk.js +21 -38
  519. package/dist/core/openspec/scan/walk.js.map +1 -1
  520. package/dist/core/pack/living.js +2 -16
  521. package/dist/core/pack/living.js.map +1 -1
  522. package/dist/core/provenance/findings.js +48 -12
  523. package/dist/core/provenance/findings.js.map +1 -1
  524. package/dist/core/provenance/git.js +33 -57
  525. package/dist/core/provenance/git.js.map +1 -1
  526. package/dist/core/provenance/gitq/moves.js +71 -114
  527. package/dist/core/provenance/gitq/moves.js.map +1 -1
  528. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  529. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  530. package/dist/core/provenance/sources.js +23 -6
  531. package/dist/core/provenance/sources.js.map +1 -1
  532. package/dist/core/provenance/stamp.js +5 -17
  533. package/dist/core/provenance/stamp.js.map +1 -1
  534. package/dist/core/repo/entries.js.map +1 -1
  535. package/dist/core/repo/repo.js +24 -14
  536. package/dist/core/repo/repo.js.map +1 -1
  537. package/dist/core/repo/tree/find.js +12 -3
  538. package/dist/core/repo/tree/find.js.map +1 -1
  539. package/dist/core/repo/tree/fs.js +27 -1
  540. package/dist/core/repo/tree/fs.js.map +1 -1
  541. package/dist/core/repo/tree/walk.js +8 -30
  542. package/dist/core/repo/tree/walk.js.map +1 -1
  543. package/dist/core/results.js +47 -12
  544. package/dist/core/results.js.map +1 -1
  545. package/dist/core/staging/commit.js +10 -12
  546. package/dist/core/staging/commit.js.map +1 -1
  547. package/dist/core/staging/lock.js +74 -12
  548. package/dist/core/staging/lock.js.map +1 -1
  549. package/dist/core/staging/recovery/intent.js +2 -5
  550. package/dist/core/staging/recovery/intent.js.map +1 -1
  551. package/dist/core/staging/recovery/recover.js +60 -1
  552. package/dist/core/staging/recovery/recover.js.map +1 -1
  553. package/dist/core/staging/snapshot/manifest.js +152 -0
  554. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  555. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  556. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  557. package/dist/core/status/actions/execution.js +11 -0
  558. package/dist/core/status/actions/execution.js.map +1 -1
  559. package/dist/core/status/feature/artifacts.js +2 -2
  560. package/dist/core/status/feature/artifacts.js.map +1 -1
  561. package/dist/core/status/feature/feature.js +5 -11
  562. package/dist/core/status/feature/feature.js.map +1 -1
  563. package/dist/core/status/fleet/fleet.js +6 -2
  564. package/dist/core/status/fleet/fleet.js.map +1 -1
  565. package/dist/core/status/fleet/next.js +26 -3
  566. package/dist/core/status/fleet/next.js.map +1 -1
  567. package/dist/core/status/report.js.map +1 -1
  568. package/dist/core/status/verification.js +30 -15
  569. package/dist/core/status/verification.js.map +1 -1
  570. package/dist/core/tasks/packet.js +174 -0
  571. package/dist/core/tasks/packet.js.map +1 -0
  572. package/dist/core/tasks/record.js +43 -0
  573. package/dist/core/tasks/record.js.map +1 -0
  574. package/dist/core/tasks/sources.js +181 -0
  575. package/dist/core/tasks/sources.js.map +1 -0
  576. package/dist/core/tasks/stamps.js +76 -0
  577. package/dist/core/tasks/stamps.js.map +1 -0
  578. package/dist/core/tasks/unit.js +0 -0
  579. package/dist/core/tasks/unit.js.map +1 -0
  580. package/dist/core/usecases/capability.js +11 -14
  581. package/dist/core/usecases/capability.js.map +1 -1
  582. package/dist/core/usecases/delta/flows.js +5 -7
  583. package/dist/core/usecases/delta/flows.js.map +1 -1
  584. package/dist/core/usecases/fleet.js +178 -31
  585. package/dist/core/usecases/fleet.js.map +1 -1
  586. package/dist/core/usecases/operations.js +5 -4
  587. package/dist/core/usecases/operations.js.map +1 -1
  588. package/dist/core/usecases/service/flows.js +9 -6
  589. package/dist/core/usecases/service/flows.js.map +1 -1
  590. package/dist/core/usecases/suites/participants.js +83 -0
  591. package/dist/core/usecases/suites/participants.js.map +1 -0
  592. package/dist/core/usecases/suites/suites.js +45 -0
  593. package/dist/core/usecases/suites/suites.js.map +1 -0
  594. package/dist/core/verify/answers.js +12 -5
  595. package/dist/core/verify/answers.js.map +1 -1
  596. package/dist/core/verify/build.js +102 -82
  597. package/dist/core/verify/build.js.map +1 -1
  598. package/dist/core/verify/checklist.js +3 -0
  599. package/dist/core/verify/checklist.js.map +1 -1
  600. package/dist/core/verify/location.js +67 -0
  601. package/dist/core/verify/location.js.map +1 -0
  602. package/dist/core/verify/pins/lint.js +3 -2
  603. package/dist/core/verify/pins/lint.js.map +1 -1
  604. package/dist/core/verify/pins/pin.js +1 -1
  605. package/dist/core/verify/pins/pin.js.map +1 -1
  606. package/dist/core/verify/read/file.js +224 -0
  607. package/dist/core/verify/read/file.js.map +1 -0
  608. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  609. package/dist/core/verify/read/shape.js.map +1 -0
  610. package/dist/core/verify/read/union.js +105 -0
  611. package/dist/core/verify/read/union.js.map +1 -0
  612. package/dist/core/verify/record.js +35 -12
  613. package/dist/core/verify/record.js.map +1 -1
  614. package/dist/core/verify/store/commit.js +40 -30
  615. package/dist/core/verify/store/commit.js.map +1 -1
  616. package/dist/core/verify/store/render.js +8 -4
  617. package/dist/core/verify/store/render.js.map +1 -1
  618. package/dist/core/vocabulary/issue.js +1 -0
  619. package/dist/core/vocabulary/issue.js.map +1 -1
  620. package/dist/core/vocabulary/maturity.js +12 -2
  621. package/dist/core/vocabulary/maturity.js.map +1 -1
  622. package/examples/README.md +21 -6
  623. package/examples/docs/architecture/landscape.likec4 +8 -0
  624. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  625. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  626. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  627. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  628. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  629. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  630. package/package.json +1 -1
  631. package/dist/commands/validate/checks/vocabulary.js +0 -16
  632. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  633. package/dist/core/agent/agents-md/artifacts.js +0 -221
  634. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  635. package/dist/core/agent/agents-md/cycle.js +0 -180
  636. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  637. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  638. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  639. package/dist/core/agent/agents-md/spine.js +0 -40
  640. package/dist/core/agent/agents-md/spine.js.map +0 -1
  641. package/dist/core/coherence/events/merged.js +0 -40
  642. package/dist/core/coherence/events/merged.js.map +0 -1
  643. package/dist/core/diff/current-state.js +0 -96
  644. package/dist/core/diff/current-state.js.map +0 -1
  645. package/dist/core/diff/victims.js +0 -256
  646. package/dist/core/diff/victims.js.map +0 -1
  647. package/dist/core/staging/snapshot.js.map +0 -1
  648. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet target: `services/` and architecture/landscape.likec4 graded\n * against each other, plus the two exemptions that decide which drawn element\n * answers for a service directory at all.\n *\n * The landscape is the one artifact no service owns — parsed once for the whole\n * run and graded on a target of its own, outside the dispatcher's `guarded`.\n * That is why its IO containment lives in this package instead: see\n * `unreadableLandscape` in `./load.ts`.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { loadFile, type Elem, type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { landscapePath as landscapeFile, servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { readModelShapes } from \"../../../core/c4/service-model/shape.js\";\nimport { serviceIdFindings } from \"../../../core/repo/entries.js\";\nimport { listFleetTree, listServices } from \"../../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { landscapeConflictFinding } from \"../../../core/conflict-markers.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { capabilityFleetFindings, fleetShapeFindings, permissionFindings } from \"../checks/fleet-shape.js\";\nimport { fleetLinkFindings } from \"../links/corpus.js\";\nimport { glossaryFindings } from \"../links/glossary.js\";\nimport { obligationFindings, obligationVocabularyFindings } from \"./obligations.js\";\nimport { readObligations } from \"../../../core/obligations/obligations.js\";\nimport { obligationsPath } from \"../../../core/repo/paths.js\";\nimport { EXTERNAL_TAG } from \"../../../core/vocabulary/maturity.js\";\nimport { errorText } from \"../../../core/c4/likec4.js\";\nimport { serviceTreePath, type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { drawnSystems, serviceLevelElements, standsForService } from \"./census.js\";\nimport { unreadableLandscape } from \"./load.js\";\nimport { kindTagFindings } from \"./kind-tags.js\";\nimport { mapAttestation } from \"./map/attest.js\";\nimport { bindingFindings } from \"./map/bindings.js\";\nimport { consumerCensus, datastoreFindings } from \"./map/consumers.js\";\nimport { isolationFindings } from \"./map/isolation.js\";\nimport { fleetUseCaseFindings } from \"./map/usecases.js\";\nimport { viewIdFindings } from \"./views/ids.js\";\nimport { viewsStaleFindings } from \"./views/stale.js\";\nimport { projectFindings } from \"./views/projects.js\";\nimport { fleetProjectFindings } from \"./views/fleet-project.js\";\nimport type { ParsedView } from \"../../../core/c4/parsed/dynamic-views.js\";\nimport { isUseCase } from \"../../../core/usecases/fleet.js\";\n\n/**\n * The fleet's flows as a grade may read them, or `null` when loam cannot see\n * them at all — the same `null`-suspends idiom `gradableCapabilityIds` uses,\n * and here it guards one specific wrong answer.\n *\n * `capability.requirement-unrealized` says a business promise is kept by\n * nobody. A use case is one of the two things that can keep it, so a run that\n * could not read the flows must not make that claim: no preload (a single-target\n * run) and a project that did not parse are both \"loam did not look\", never\n * \"there is nothing there\". A preload that DID parse and declares no views is a\n * real, empty answer and grades normally.\n *\n * Filtered through `isUseCase` — the same opt-in `readUseCases` applies before\n * `loam list capabilities` asks this question — because the two surfaces must\n * never disagree about `keptBy`. Handing the unfiltered preload over was\n * harmless only by luck: an untagged view yields no claims, so the answers\n * matched for want of an input that could tell them apart. A `#req-`-only view\n * is exactly that input.\n */\nfunction gradableFlows(preloaded: LoadedDoc | null | undefined): readonly ParsedView[] | null {\n if (preloaded === null || preloaded === undefined || preloaded.errors.length > 0) return null;\n return (preloaded.views ?? []).filter(isUseCase);\n}\n\n/**\n * The fleet cross-check: `services/` and the landscape both claim to name the\n * fleet, and nothing used to compare them. A directory nobody drew and an element\n * with nothing behind it were equally invisible.\n *\n * The two directions are graded differently because the evidence differs. A\n * directory that exists is a fact, so a landscape missing it is an error — every\n * view derived from that landscape is then incomplete. An element with no\n * directory may legitimately be someone else's system, so it warns, and\n * `#external` says \"deliberately not ours\" and silences it. An explicit\n * `metadata { service '<id>' }` naming nothing is an error either way: a binding\n * is a claim about this repo, not a guess at one.\n *\n * An ABSENT landscape is a finding, not a skipped check. It used to return null\n * — no target, no findings, and a fleet gate that went green over a docs repo\n * with no fleet map at all, which is the single artifact every derived view and\n * every spine check is computed from. It is graded by what its absence proves:\n * with services in `services/` it is an ERROR (a fleet that exists is undrawn),\n * with none it is a WARNING (a docs repo before its first adopt legitimately\n * has nothing to draw, but the file still belongs there — `loam init` scaffolds\n * it, and a repo missing it will silently accept never getting one).\n *\n * `preloaded` is the already-parsed landscape under --all — the same doc every\n * service check gets.\n */\nexport async function validateLandscape(\n docsDir: DocsDir,\n preloaded?: LoadedDoc | null,\n fleet?: FleetContext,\n): Promise<TargetReport> {\n const path = landscapeFile(docsDir);\n const findings: Finding[] = [];\n const report: TargetReport = {\n kind: \"landscape\",\n id: \"landscape\",\n path: \"architecture/landscape.likec4\",\n findings,\n };\n\n // The SET of service directories is this target's other subject, so it is\n // graded before the map is even opened: `service.id-invalid` is a fact about\n // `services/` that holds whether or not a landscape exists or parses, and\n // both of those return early below. Emitted here and nowhere else — the\n // enumeration is what makes the id a question, and one finding per fleet is\n // what the rename fixes.\n const entries = await listServices(docsDir, fleet);\n findings.push(...serviceIdFindings(entries));\n // The tree walk's own findings ride the same fleet target, before the map's\n // early returns for the same reason `service.id-invalid` does: a stranded,\n // colliding or mismarked directory is a fact about `services/` that holds\n // whether or not a landscape exists or parses — and a broken tree must make\n // the fleet gate refuse while still naming every service it found.\n const tree = await listFleetTree(docsDir, fleet);\n findings.push(...tree.findings);\n // Which SHAPE each model has, read once for the whole target: it decides four\n // of the grades below (`views/projects.ts`) and which documents the merged\n // project holds (`views/fleet-project.ts`). Over the full enumeration, which\n // is what `entries` is here even under `--base` — `validate --all` narrows the\n // TARGETS it grades and never the fleet the renderer would load.\n const models = entries.map((entry) => servicePathsAt(entry.dir).model);\n const shapes = fleet === undefined ? await readModelShapes(models) : await fleet.modelShapes(models);\n // A model and the renderer's project files disagreeing is a fact about\n // `services/` too, graded here for `service.id-invalid`'s reason: it holds\n // whether or not the map parses. One warning per service per disagreement;\n // views/projects.ts says why a warning, why this run and not `doctor`, why\n // the root-file gate, and why the grade is KEPT rather than spread and\n // dropped — one of the five gates the merged-project load below, and\n // re-deriving that fact there would let a cause and its 161-warning cascade\n // disagree about which state the run is in.\n const projects = await projectFindings(docsDir, entries, shapes);\n findings.push(...projects.findings);\n // Before the landscape's own early returns: the authorization and capability\n // vocabularies are fleet facts that do not depend on the map existing or parsing.\n findings.push(...(await permissionFindings(docsDir, entries, fleet)));\n findings.push(...(await capabilityFleetFindings(docsDir, entries, fleet, gradableFlows(preloaded))));\n // The documents that belong to no service and no feature — the fleet's ADRs\n // and the living capability tree — are graded for their links here, once,\n // for the same reason the two vocabularies above are: a fleet fact repeated\n // on every service target is the report.\n findings.push(...(await fleetLinkFindings({ docsDir, fleet })));\n // The glossary is graded on who cites it, which is a question only the whole\n // repository answers — so it belongs here, beside the two vocabularies, and\n // nowhere else.\n findings.push(...(await glossaryFindings(docsDir, fleet)));\n // The architectural obligation vocabulary, read ONCE here and handed to the\n // map half below: its own two verdicts hold whether or not the landscape\n // exists or parses, and a run where somebody is fixing the map must still be\n // told that this file does not read.\n const obligations = await readObligations(obligationsPath(docsDir));\n findings.push(...obligationVocabularyFindings(docsDir, obligations));\n\n if (!existsSync(path)) {\n const count = entries.length;\n findings.push({\n severity: count > 0 ? \"error\" : \"warn\",\n code: \"landscape.missing\",\n message:\n `landscape: architecture/landscape.likec4 does not exist — ` +\n (count > 0\n ? `${count} service(s) are adopted and nothing draws the fleet. `\n : \"nothing draws the fleet. \") +\n \"It is the one map every derived view and the C4↔API spine are computed from: \" +\n \"write a `specification { element softwareSystem }` + `model { … }` document there \" +\n \"with one element per services/<id>/, bound with `metadata { service '<id>' }`.\",\n });\n return report;\n }\n\n // Conflict markers before the parse — `loam doctor`'s order, and for its\n // reason: the markers are the cause and the parse errors are the cascade.\n // Nothing may be concluded from a file that is two halves of two different\n // maps, least of all that a service nobody drew is unmodelled, so this\n // returns the way `landscape.invalid` does. An error, gating by the default\n // rule and deliberately not carrying `gates` — that field is coherence's,\n // and the archive gate has to ask this question itself (it does not yet).\n // Read plainly rather than through FleetContext: this target runs outside\n // `guarded`, and a document refused for its encoding would surface here as\n // the whole run's `repository-unavailable` instead of one finding.\n //\n // Both reads are contained for that same reason — see `unreadableLandscape`.\n // The conflict-marker read is the FIRST touch of the file, so it is the one\n // that fails when the file cannot be opened at all, and it runs before\n // `preloaded ??` can spare it.\n let land: LoadedDoc;\n try {\n const conflict = landscapeConflictFinding(\n \"architecture/landscape.likec4\",\n await readFile(path, \"utf8\"),\n );\n if (conflict !== null) {\n findings.push(conflict);\n return report;\n }\n land = preloaded ?? (fleet === undefined ? await loadFile(path) : await fleet.loadLikeC4(path));\n } catch (err) {\n land = unreadableLandscape(err);\n }\n if (land.errors.length > 0) {\n // Nothing may be concluded from a document that did not parse — in particular\n // not that every service is unmodelled.\n //\n // The fleet map is a PROJECT now, not a file: `architecture/landscape.likec4`\n // plus every `architecture/usecases/*.likec4`, merged the way the renderer\n // merges them. So the message names the FILES that broke rather than the\n // landscape by default — a use case with a typo'd element used to be\n // reported as the landscape having errors, which sent the author to the\n // wrong file with a line number that pointed at somebody else's text.\n const broken = [...new Set(land.errors.map((e) => e.sourceFsPath).filter((p): p is string => p !== undefined))];\n const spell = (abs: string): string => relative(docsDir, abs).split(/[\\\\/]/).join(\"/\");\n const named = broken.length === 0 ? \"architecture/landscape.likec4\" : broken.map(spell).sort().join(\", \");\n // The verb agrees with the LIST, not with the count after it: `named` is a\n // comma-joined series once two documents broke, and \"a, b has 6 error(s)\"\n // is a sentence a reader trips over. The service arm (`service/spine.ts`)\n // picks the verb the same way off the same list, and these two messages are\n // read side by side in one report — a run where the fleet line says \"has\"\n // and the service line says \"have\" about the same two files reads as two\n // different findings.\n const verb = broken.length > 1 ? \"have\" : \"has\";\n findings.push({\n severity: \"error\",\n code: \"landscape.invalid\",\n // The tail is general on purpose. It used to speak about use-case flows\n // alone, so an author whose MODEL EDGE named a container was told to move\n // a flow that does not exist (#01). Both shapes are the same defect: loam\n // grades the `architecture/` project, the renderer loads that project plus\n // every extending model, and a reference to a container only the model\n // declares therefore resolves there and not here.\n message:\n `landscape: ${named} ${verb} ${land.errors.length} error(s) — cross-check with services/ impossible. ` +\n \"An unresolved name that a service's extending model declares — a use-case hop or an edge \" +\n \"naming `<service-fqn>.<container>` — renders for the renderer and resolves for nobody here: \" +\n \"name the SERVICE on the map, and the container in that service's own model (a container-level \" +\n \"flow lives beside the model that declares it)\",\n // Every line carries its own file, because one project's errors can come\n // from more than one document and a bare `L8:` is unactionable then.\n details: land.errors.map((e) =>\n e.sourceFsPath === undefined || broken.length < 2 ? errorText(e) : `${spell(e.sourceFsPath)} ${errorText(e)}`,\n ),\n });\n return report;\n }\n\n // The generated views files, graded only now that the landscape has parsed:\n // the expected bytes are a function of (tree, landscape elements, the global\n // style ids the map declares) — the whole doc goes across so the grader and\n // `sync` read one record. There are TWO generated files now\n // (`subsystems.likec4` and `services.likec4`) and this pushes ONE error PER\n // STALE FILE, each naming its own file; `views/stale.ts` says why a content\n // compare, and why two files share one code but never one finding.\n findings.push(...(await viewsStaleFindings(docsDir, tree, land)));\n // The renderer's own reading of the tree: the map, every extending model and\n // every `.likec4` beside one, merged into ONE project. It answers two\n // questions at once and only one of them can be true at a time — either that\n // project does not parse (`c4.fleet-project-invalid`), or it parsed and its\n // view-id census is the real one.\n const merged = fleet === undefined\n ? { kind: \"skipped\" as const }\n : await fleetProjectFindings({\n docsDir, entries, shapes, fleet, tree,\n architecture: land,\n mapExcluded: projects.mapExcluded,\n known: new Set(entries.map((s) => s.id)),\n });\n if (merged.kind === \"invalid\") findings.push(...merged.findings);\n // Beside staleness, and for the same file: an authored view id that collides\n // with one loam mints into it takes the whole root project down in the\n // renderer while every check here stays green — see views/ids.ts. The census\n // is the merged project's when there is one; otherwise the `architecture/`\n // project's alone, whose claims are spelled relative to that directory and are\n // made docs-relative here.\n // `land` goes across for the SERVICE half of the reservation: an id is minted\n // per service the map DRAWS, so the census needs the render's own element join.\n const claims =\n merged.kind === \"clean\"\n ? merged.viewIds\n : land.viewIds?.map((claim) => ({ ...claim, sourcePath: `architecture/${claim.sourcePath}` }));\n findings.push(...viewIdFindings(claims, tree, land));\n // The three obligation questions that need the map — where each declared rule\n // is applied, which tags resolve to nothing, and which applications no living\n // arch requirement covers. Here rather than beside the vocabulary above\n // because all three are about the parsed landscape, and `land` is only\n // trustworthy past this point.\n findings.push(...(await obligationFindings({ docsDir, vocabulary: obligations, land, services: entries, fleet })));\n\n const services: ReadonlySet<string> = new Set(entries.map((s) => s.id));\n // Where each service actually sits, for every finding below that names a\n // directory a reader is meant to open. `services/<id>/` is right only for an\n // unfiled fleet; a filed service lives at `services/<subsystem>/…/<id>/`, and\n // a finding pointing at the root form sends the fix to a path that does not\n // exist. Built once here because three checks below need the same answer.\n const pathOf = (id: string): string => {\n const entry = entries.find((s) => s.id === id);\n return entry === undefined ? `services/${id}` : serviceTreePath(entry);\n };\n // Ahead of every exemption below, because it is the one defect that can make\n // all of them lie at once: a tag loam grades on, declared on a KIND, landing\n // on an element that stands for one of our own directories — see kind-tags.ts.\n findings.push(...kindTagFindings({ specification: land.specification, elements: land.elements, services }));\n const drawn = serviceLevelElements(land.elements, services);\n // Which services the landscape models, answered by the same resolver every\n // edge join uses — so \"modelled\" here and \"inbound edge\" in the spine check\n // can never disagree about what an element stands for.\n const landSvcOf = serviceResolver(land.elements, services);\n const modelled: ReadonlySet<string> = new Set(land.elements.map((e) => landSvcOf(e.id)));\n // The same set with `#external` removed — the isolation check's input, and\n // NOT `service-unmodelled`'s: a foreign box bound to one of our directories\n // still means the directory is drawn, but \"deliberately not ours\" must not\n // make it a subject of an advisory about our own map (R4/W12). The two\n // binding passes have always skipped the tag; this is the third.\n const external: ReadonlySet<string> = new Set(\n land.elements.filter((e) => e.tags.includes(EXTERNAL_TAG)).map((e) => e.id),\n );\n const ours: ReadonlySet<string> = new Set(\n land.elements.filter((e) => !external.has(e.id)).map((e) => landSvcOf(e.id)),\n );\n\n // The element↔directory passes: two boxes for one directory, a directory\n // nobody drew, a binding naming nothing, and an element nobody owns.\n const systems = drawnSystems(land.elements, services);\n findings.push(...bindingFindings({ elements: land.elements, drawn, systems, services, modelled, pathOf }));\n\n // What the fleet's own models say, read once for the two checks below that\n // need it: a map edge is not the only evidence that a service consumes\n // something, nor the only evidence that it calls out (map/attest.ts).\n const drawnIds: ReadonlySet<string> = new Set(drawn.map((e) => e.id));\n // `census.ts`'s predicate, handed down rather than respelled: a `fleet/map/`\n // module may not import `fleet/`, and the store grades were skipping a\n // datastore that IS a service in one of their two halves only.\n const isService = (e: Elem): boolean => standsForService(e, services);\n const attestation = await mapAttestation({\n docsDir,\n entries,\n services,\n resolve: landSvcOf,\n standsForService: isService,\n drawnIds,\n ...(fleet === undefined ? {} : { fleet }),\n });\n const attested = attestation.models;\n const consumers = consumerCensus({ relationships: land.relationships, services, resolve: landSvcOf, attested });\n // The shape advisories run LAST, over the same drawn set and resolver the\n // structural checks used. Any one of them suppresses `landscape.matched`\n // below on purpose: a map with a shape warning did not fully \"agree\".\n findings.push(...fleetShapeFindings({ drawn, services, consumers }));\n const { nestedStores } = attestation;\n findings.push(\n ...datastoreFindings({\n drawn,\n elements: land.elements,\n nestedStores,\n services,\n resolve: landSvcOf,\n standsForService: isService,\n consumers,\n }),\n );\n // A drawn service nothing reaches, graded only where its own model attests\n // a call across its boundary — map/isolation.ts says why the evidence gate.\n findings.push(...isolationFindings({ land, modelled: ours, external, attested, resolve: landSvcOf, pathOf }));\n\n // The use cases the SAME `architecture/` project declares — every\n // `dynamic view` in the landscape and in every `architecture/usecases/*.likec4`\n // — graded against the model above (`map/usecases.ts` holds the call and the\n // two vocabulary reads it needs; `usecases/usecases.ts` owns the opt-in and\n // the grades). They ride this target for `viewsStaleFindings`' reason: a view\n // belongs to no service, and only the fleet run has both the whole model and\n // the enumerated `services/` in view at once.\n findings.push(\n ...(await fleetUseCaseFindings({\n docsDir,\n views: preloaded?.views ?? [],\n elements: land.elements,\n relationships: land.relationships,\n services,\n resolve: landSvcOf,\n fleet,\n })),\n );\n\n if (findings.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"landscape.matched\",\n message: `landscape: ${services.size} service(s) modelled — architecture/landscape.likec4 and services/ agree`,\n });\n }\n return report;\n}\n"]}
1
+ {"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet target: `services/` and architecture/landscape.likec4 graded\n * against each other, plus the two exemptions that decide which drawn element\n * answers for a service directory at all.\n *\n * The landscape is the one artifact no service owns — parsed once for the whole\n * run and graded on a target of its own, outside the dispatcher's `guarded`.\n * That is why its IO containment lives in this package instead: see\n * `unreadableLandscape` in `./load.ts`.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { type Elem, type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { landscapePath as landscapeFile, servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { serviceIdFindings } from \"../../../core/repo/entries.js\";\nimport { listFleetTree, listServices } from \"../../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { landscapeConflictFinding } from \"../../../core/conflict-markers.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { capabilityFleetFindings, fleetShapeFindings, permissionFindings } from \"../checks/fleet-shape.js\";\nimport { fleetLinkFindings } from \"../links/corpus.js\";\nimport { glossaryFindings } from \"../links/glossary.js\";\nimport { obligationFindings, obligationVocabularyFindings } from \"./obligations.js\";\nimport { readObligations } from \"../../../core/obligations/obligations.js\";\nimport { obligationsPath } from \"../../../core/repo/paths.js\";\nimport { EXTERNAL_TAG } from \"../../../core/vocabulary/maturity.js\";\nimport { brokenDocumentPaths, errorText } from \"../../../core/c4/likec4.js\";\nimport { serviceTreePath, type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { drawnSystems, serviceLevelElements, standsForService } from \"./census.js\";\nimport { unreadableLandscape } from \"./load.js\";\nimport { kindTagFindings } from \"./kind-tags.js\";\nimport { mapAttestation } from \"./map/attest.js\";\nimport { bindingFindings } from \"./map/bindings.js\";\nimport { consumerCensus, datastoreFindings } from \"./map/consumers.js\";\nimport { isolationFindings } from \"./map/isolation.js\";\nimport { fleetUseCaseFindings } from \"./map/usecases.js\";\nimport { viewIdFindings } from \"./views/ids.js\";\nimport { viewsStaleFindings } from \"./views/stale.js\";\nimport { projectFindings } from \"./views/projects.js\";\nimport { fleetProjectFindings } from \"./views/fleet-project.js\";\nimport type { ParsedView } from \"../../../core/c4/parsed/dynamic-views.js\";\nimport { isUseCase } from \"../../../core/usecases/fleet.js\";\n\n/**\n * The fleet's flows as a grade may read them, or `null` when loam cannot see\n * them at all — the same `null`-suspends idiom `gradableCapabilityIds` uses,\n * and here it guards one specific wrong answer.\n *\n * `capability.requirement-unrealized` says a business promise is kept by\n * nobody. A use case is one of the two things that can keep it, so a run that\n * could not read the flows must not make that claim: no map at all (`null`)\n * and a project that did not parse are both \"loam did not look\", never\n * \"there is nothing there\". A preload that DID parse and declares no views is a\n * real, empty answer and grades normally.\n *\n * Filtered through `isUseCase` — the same opt-in `readUseCases` applies before\n * `loam list capabilities` asks this question — because the two surfaces must\n * never disagree about `keptBy`. Handing the unfiltered preload over was\n * harmless only by luck: an untagged view yields no claims, so the answers\n * matched for want of an input that could tell them apart. A `#req-`-only view\n * is exactly that input.\n */\nfunction gradableFlows(preloaded: LoadedDoc | null): readonly ParsedView[] | null {\n if (preloaded === null || preloaded.errors.length > 0) return null;\n return (preloaded.views ?? []).filter(isUseCase);\n}\n\n/**\n * The fleet cross-check: `services/` and the landscape both claim to name the\n * fleet, and nothing used to compare them. A directory nobody drew and an element\n * with nothing behind it were equally invisible.\n *\n * The two directions are graded differently because the evidence differs. A\n * directory that exists is a fact, so a landscape missing it is an error — every\n * view derived from that landscape is then incomplete. An element with no\n * directory may legitimately be someone else's system, so it warns, and\n * `#external` says \"deliberately not ours\" and silences it. An explicit\n * `metadata { service '<id>' }` naming nothing is an error either way: a binding\n * is a claim about this repo, not a guess at one.\n *\n * An ABSENT landscape is a finding, not a skipped check. It used to return null\n * — no target, no findings, and a fleet gate that went green over a docs repo\n * with no fleet map at all, which is the single artifact every derived view and\n * every spine check is computed from. It is graded by what its absence proves:\n * with services in `services/` it is an ERROR (a fleet that exists is undrawn),\n * with none it is a WARNING (a docs repo before its first adopt legitimately\n * has nothing to draw, but the file still belongs there — `loam init` scaffolds\n * it, and a repo missing it will silently accept never getting one).\n *\n * `preloaded` is the already-parsed landscape under --all — the same doc every\n * service check gets.\n */\nexport async function validateLandscape(\n docsDir: DocsDir,\n preloaded: LoadedDoc | null,\n fleet: FleetContext,\n): Promise<TargetReport> {\n const path = landscapeFile(docsDir);\n const findings: Finding[] = [];\n const report: TargetReport = {\n kind: \"landscape\",\n id: \"landscape\",\n path: \"architecture/landscape.likec4\",\n findings,\n };\n\n // The SET of service directories is this target's other subject, so it is\n // graded before the map is even opened: `service.id-invalid` is a fact about\n // `services/` that holds whether or not a landscape exists or parses, and\n // both of those return early below. Emitted here and nowhere else — the\n // enumeration is what makes the id a question, and one finding per fleet is\n // what the rename fixes.\n const entries = await listServices(docsDir, fleet);\n findings.push(...serviceIdFindings(entries));\n // The tree walk's own findings ride the same fleet target, before the map's\n // early returns for the same reason `service.id-invalid` does: a stranded,\n // colliding or mismarked directory is a fact about `services/` that holds\n // whether or not a landscape exists or parses — and a broken tree must make\n // the fleet gate refuse while still naming every service it found.\n const tree = await listFleetTree(docsDir, fleet);\n findings.push(...tree.findings);\n // Which SHAPE each model has, read once for the whole target: it decides four\n // of the grades below (`views/projects.ts`) and which documents the merged\n // project holds (`views/fleet-project.ts`). Over the full enumeration, which\n // is what `entries` is here even under `--base` — `validate --all` narrows the\n // TARGETS it grades and never the fleet the renderer would load.\n const models = entries.map((entry) => servicePathsAt(entry.dir).model);\n const shapes = await fleet.modelShapes(models);\n // A model and the renderer's project files disagreeing is a fact about\n // `services/` too, graded here for `service.id-invalid`'s reason: it holds\n // whether or not the map parses. One warning per service per disagreement;\n // views/projects.ts says why a warning, why this run and not `doctor`, why\n // the root-file gate, and why the grade is KEPT rather than spread and\n // dropped — one of the five gates the merged-project load below, and\n // re-deriving that fact there would let a cause and its 161-warning cascade\n // disagree about which state the run is in.\n const projects = await projectFindings(docsDir, entries, shapes);\n findings.push(...projects.findings);\n // Before the landscape's own early returns: the authorization and capability\n // vocabularies are fleet facts that do not depend on the map existing or parsing.\n findings.push(...(await permissionFindings(docsDir, entries, fleet)));\n findings.push(...(await capabilityFleetFindings(docsDir, entries, fleet, gradableFlows(preloaded))));\n // The documents that belong to no service and no feature — the fleet's ADRs\n // and the living capability tree — are graded for their links here, once,\n // for the same reason the two vocabularies above are: a fleet fact repeated\n // on every service target is the report.\n findings.push(...(await fleetLinkFindings({ docsDir, fleet })));\n // The glossary is graded on who cites it, which is a question only the whole\n // repository answers — so it belongs here, beside the two vocabularies, and\n // nowhere else.\n findings.push(...(await glossaryFindings(docsDir, fleet)));\n // The architectural obligation vocabulary, read ONCE here and handed to the\n // map half below: its own two verdicts hold whether or not the landscape\n // exists or parses, and a run where somebody is fixing the map must still be\n // told that this file does not read.\n const obligations = await readObligations(obligationsPath(docsDir));\n findings.push(...obligationVocabularyFindings(docsDir, obligations));\n\n if (!existsSync(path)) {\n const count = entries.length;\n findings.push({\n severity: count > 0 ? \"error\" : \"warn\",\n code: \"landscape.missing\",\n message:\n `landscape: architecture/landscape.likec4 does not exist — ` +\n (count > 0\n ? `${count} service(s) are adopted and nothing draws the fleet. `\n : \"nothing draws the fleet. \") +\n \"It is the one map every derived view and the C4↔API spine are computed from: \" +\n \"write a `specification { element softwareSystem }` + `model { … }` document there \" +\n \"with one element per services/<id>/, bound with `metadata { service '<id>' }`.\",\n });\n return report;\n }\n\n // Conflict markers before the parse — `loam doctor`'s order, and for its\n // reason: the markers are the cause and the parse errors are the cascade.\n // Nothing may be concluded from a file that is two halves of two different\n // maps, least of all that a service nobody drew is unmodelled, so this\n // returns the way `landscape.invalid` does. An error, gating by the default\n // rule and deliberately not carrying `gates` — that field is coherence's,\n // and the archive gate has to ask this question itself (it does not yet).\n // Read plainly rather than through FleetContext: this target runs outside\n // `guarded`, and a document refused for its encoding would surface here as\n // the whole run's `repository-unavailable` instead of one finding.\n //\n // Both reads are contained for that same reason — see `unreadableLandscape`.\n // The conflict-marker read is the FIRST touch of the file, so it is the one\n // that fails when the file cannot be opened at all, and it runs before\n // `preloaded ??` can spare it.\n let land: LoadedDoc;\n try {\n const conflict = landscapeConflictFinding(\n \"architecture/landscape.likec4\",\n await readFile(path, \"utf8\"),\n );\n if (conflict !== null) {\n findings.push(conflict);\n return report;\n }\n land = preloaded ?? (await fleet.loadLikeC4(path));\n } catch (err) {\n land = unreadableLandscape(err);\n }\n if (land.errors.length > 0) {\n // Nothing may be concluded from a document that did not parse — in particular\n // not that every service is unmodelled.\n //\n // The fleet map is a PROJECT now, not a file: `architecture/landscape.likec4`\n // plus every `architecture/usecases/*.likec4`, merged the way the renderer\n // merges them. So the message names the FILES that broke rather than the\n // landscape by default — a use case with a typo'd element used to be\n // reported as the landscape having errors, which sent the author to the\n // wrong file with a line number that pointed at somebody else's text.\n const broken = brokenDocumentPaths(docsDir, land.errors);\n const named = broken.length === 0 ? \"architecture/landscape.likec4\" : broken.join(\", \");\n // The verb agrees with the LIST, not with the count after it: `named` is a\n // comma-joined series once two documents broke, and \"a, b has 6 error(s)\"\n // is a sentence a reader trips over. The service arm (`service/spine.ts`)\n // picks the verb the same way off the same list, and these two messages are\n // read side by side in one report — a run where the fleet line says \"has\"\n // and the service line says \"have\" about the same two files reads as two\n // different findings.\n const verb = broken.length > 1 ? \"have\" : \"has\";\n findings.push({\n severity: \"error\",\n code: \"landscape.invalid\",\n // The tail is general on purpose. It used to speak about use-case flows\n // alone, so an author whose MODEL EDGE named a container was told to move\n // a flow that does not exist (#01). Both shapes are the same defect: loam\n // grades the `architecture/` project, the renderer loads that project plus\n // every extending model, and a reference to a container only the model\n // declares therefore resolves there and not here.\n message:\n `landscape: ${named} ${verb} ${land.errors.length} error(s) — cross-check with services/ impossible. ` +\n \"An unresolved name that a service's extending model declares — a use-case hop or an edge \" +\n \"naming `<service-fqn>.<container>` — renders for the renderer and resolves for nobody here: \" +\n \"name the SERVICE on the map, and the container in that service's own model (a container-level \" +\n \"flow lives beside the model that declares it)\",\n // Every line carries its own file, because one project's errors can come\n // from more than one document and a bare `L8:` is unactionable then. The\n // prefix is `brokenDocumentPaths`'s spelling of that one error's document,\n // so the message's file list and each line's prefix are one rule.\n details: land.errors.map((e) => {\n const [file] = broken.length < 2 ? [] : brokenDocumentPaths(docsDir, [e]);\n return file === undefined ? errorText(e) : `${file} ${errorText(e)}`;\n }),\n });\n return report;\n }\n\n // The generated views files, graded only now that the landscape has parsed:\n // the expected bytes are a function of (tree, landscape elements, the global\n // style ids the map declares) — the whole doc goes across so the grader and\n // `sync` read one record. There are TWO generated files now\n // (`subsystems.likec4` and `services.likec4`) and this pushes ONE error PER\n // STALE FILE, each naming its own file; `views/stale.ts` says why a content\n // compare, and why two files share one code but never one finding.\n findings.push(...(await viewsStaleFindings(docsDir, tree, land)));\n // The renderer's own reading of the tree: the map, every extending model and\n // every `.likec4` beside one, merged into ONE project. It answers two\n // questions at once and only one of them can be true at a time — either that\n // project does not parse (`c4.fleet-project-invalid`), or it parsed and its\n // view-id census is the real one.\n const merged = await fleetProjectFindings({\n docsDir, entries, shapes, fleet, tree,\n architecture: land,\n mapExcluded: projects.mapExcluded,\n known: new Set(entries.map((s) => s.id)),\n });\n if (merged.kind === \"invalid\") findings.push(...merged.findings);\n // Beside staleness, and for the same file: an authored view id that collides\n // with one loam mints into it takes the whole root project down in the\n // renderer while every check here stays green — see views/ids.ts. The census\n // is the merged project's when there is one; otherwise the `architecture/`\n // project's alone, whose claims are spelled relative to that directory and are\n // made docs-relative here.\n // `land` goes across for the SERVICE half of the reservation: an id is minted\n // per service the map DRAWS, so the census needs the render's own element join.\n const claims =\n merged.kind === \"clean\"\n ? merged.viewIds\n : land.viewIds?.map((claim) => ({ ...claim, sourcePath: `architecture/${claim.sourcePath}` }));\n findings.push(...viewIdFindings(claims, tree, land));\n // The three obligation questions that need the map — where each declared rule\n // is applied, which tags resolve to nothing, and which applications no living\n // arch requirement covers. Here rather than beside the vocabulary above\n // because all three are about the parsed landscape, and `land` is only\n // trustworthy past this point.\n findings.push(...(await obligationFindings({ docsDir, vocabulary: obligations, land, services: entries, fleet })));\n\n const services: ReadonlySet<string> = new Set(entries.map((s) => s.id));\n // Where each service actually sits, for every finding below that names a\n // directory a reader is meant to open. `services/<id>/` is right only for an\n // unfiled fleet; a filed service lives at `services/<subsystem>/…/<id>/`, and\n // a finding pointing at the root form sends the fix to a path that does not\n // exist. Built once here because three checks below need the same answer.\n const pathOf = (id: string): string => {\n const entry = entries.find((s) => s.id === id);\n return entry === undefined ? `services/${id}` : serviceTreePath(entry);\n };\n // Ahead of every exemption below, because it is the one defect that can make\n // all of them lie at once: a tag loam grades on, declared on a KIND, landing\n // on an element that stands for one of our own directories — see kind-tags.ts.\n findings.push(...kindTagFindings({ specification: land.specification, elements: land.elements, services }));\n const drawn = serviceLevelElements(land.elements, services);\n // Which services the landscape models, answered by the same resolver every\n // edge join uses — so \"modelled\" here and \"inbound edge\" in the spine check\n // can never disagree about what an element stands for.\n const landSvcOf = serviceResolver(land.elements, services);\n const modelled: ReadonlySet<string> = new Set(land.elements.map((e) => landSvcOf(e.id)));\n // The same set with `#external` removed — the isolation check's input, and\n // NOT `service-unmodelled`'s: a foreign box bound to one of our directories\n // still means the directory is drawn, but \"deliberately not ours\" must not\n // make it a subject of an advisory about our own map (R4/W12). The two\n // binding passes have always skipped the tag; this is the third.\n const external: ReadonlySet<string> = new Set(\n land.elements.filter((e) => e.tags.includes(EXTERNAL_TAG)).map((e) => e.id),\n );\n const ours: ReadonlySet<string> = new Set(\n land.elements.filter((e) => !external.has(e.id)).map((e) => landSvcOf(e.id)),\n );\n\n // The element↔directory passes: two boxes for one directory, a directory\n // nobody drew, a binding naming nothing, and an element nobody owns.\n const systems = drawnSystems(land.elements, services);\n findings.push(...bindingFindings({ elements: land.elements, drawn, systems, services, modelled, pathOf }));\n\n // What the fleet's own models say, read once for the two checks below that\n // need it: a map edge is not the only evidence that a service consumes\n // something, nor the only evidence that it calls out (map/attest.ts).\n const drawnIds: ReadonlySet<string> = new Set(drawn.map((e) => e.id));\n // `census.ts`'s predicate, handed down rather than respelled: a `fleet/map/`\n // module may not import `fleet/`, and the store grades were skipping a\n // datastore that IS a service in one of their two halves only.\n const isService = (e: Elem): boolean => standsForService(e, services);\n const attestation = await mapAttestation({\n docsDir,\n entries,\n services,\n resolve: landSvcOf,\n standsForService: isService,\n drawnIds,\n fleet,\n });\n const attested = attestation.models;\n const consumers = consumerCensus({ relationships: land.relationships, services, resolve: landSvcOf, attested });\n // The shape advisories run LAST, over the same drawn set and resolver the\n // structural checks used. Any one of them suppresses `landscape.matched`\n // below on purpose: a map with a shape warning did not fully \"agree\".\n findings.push(...fleetShapeFindings({ drawn, services, consumers }));\n const { nestedStores } = attestation;\n findings.push(\n ...datastoreFindings({\n drawn,\n elements: land.elements,\n nestedStores,\n services,\n resolve: landSvcOf,\n standsForService: isService,\n consumers,\n }),\n );\n // A drawn service nothing reaches, graded only where its own model attests\n // a call across its boundary — map/isolation.ts says why the evidence gate.\n findings.push(...isolationFindings({ land, modelled: ours, external, attested, resolve: landSvcOf, pathOf }));\n\n // The use cases the SAME `architecture/` project declares — every\n // `dynamic view` in the landscape and in every `architecture/usecases/*.likec4`\n // — graded against the model above (`map/usecases.ts` holds the call and the\n // two vocabulary reads it needs; `usecases/usecases.ts` owns the opt-in and\n // the grades). They ride this target for `viewsStaleFindings`' reason: a view\n // belongs to no service, and only the fleet run has both the whole model and\n // the enumerated `services/` in view at once.\n findings.push(\n ...(await fleetUseCaseFindings({\n docsDir,\n views: preloaded?.views ?? [],\n elements: land.elements,\n relationships: land.relationships,\n services,\n resolve: landSvcOf,\n fleet,\n })),\n );\n\n if (findings.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"landscape.matched\",\n message: `landscape: ${services.size} service(s) modelled — architecture/landscape.likec4 and services/ agree`,\n });\n }\n return report;\n}\n"]}
@@ -5,13 +5,10 @@
5
5
  * element (`../../checks/fleet-shape.ts`).
6
6
  *
7
7
  * One reader rather than two, because the two checks were answering the same
8
- * question from different evidence and disagreeing about the fleet. The
9
- * consumer census walked the MAP's relationships alone, so a store whose
10
- * consuming edges are all drawn in `services/<…>/model.likec4` had zero
11
- * consumers and earned nothing at all — and a store reached by three models plus
12
- * one map edge was graded against the single service the MAP happened to draw
13
- * (R1). A model is evidence about the fleet exactly as the map is, so it is read
14
- * here and handed to both.
8
+ * question from different evidence and disagreeing about the fleet — the
9
+ * map-only consumer census `./consumers.ts` tells the story of (R1). A model is
10
+ * evidence about the fleet exactly as the map is, so it is read here and handed
11
+ * to both.
15
12
  *
16
13
  * EXTENDING MODELS ONLY, for the consumer join. An extending model's ids ARE the
17
14
  * map's fully-qualified ids — one project, one id space — so `counterpartId`
@@ -45,7 +42,6 @@ export async function mapAttestation(input) {
45
42
  // as a standalone one attests the calls it draws in its own file. Memoised in
46
43
  // the index, so under `--all` these are hits. Pooled and joined on the entry,
47
44
  // never by index.
48
- const context = input.fleet ?? new FleetContext();
49
45
  const present = input.entries.filter((entry) => existsSync(servicePathsAt(entry.dir).model));
50
46
  // ONE workspace for the fleet's models rather than one per service. Both
51
47
  // answers below are about the WHOLE fleet — who else reaches this store is not
@@ -54,7 +50,7 @@ export async function mapAttestation(input) {
54
50
  // workspace per un-narrowed service. An accelerator with `prefetchLikeC4`'s
55
51
  // failure story exactly: a batch that cannot run seeds nothing, and every read
56
52
  // below falls back to its own load.
57
- await context.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));
53
+ await input.fleet.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));
58
54
  const loaded = await inOrder(present, async (entry) => ({
59
55
  entry,
60
56
  // CONTAINED, for `fleet/load.ts unreadableLandscape`'s reason: this runs on
@@ -64,7 +60,7 @@ export async function mapAttestation(input) {
64
60
  // no report at all for the ninety-nine services that are fine. A model that
65
61
  // could not be READ contributes nothing here, exactly as one that did not
66
62
  // parse does; `service.unreadable` is its own target's finding.
67
- model: await context.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),
63
+ model: await input.fleet.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),
68
64
  }));
69
65
  const models = [];
70
66
  const nestedStores = [];
@@ -1 +1 @@
1
- {"version":3,"file":"attest.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/attest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the fleet's own `model.likec4` files say, read ONCE for the two map\n * checks that need it: the calls each model attests across its boundary\n * (`./isolation.ts`), and the datastores that live nested inside a service's own\n * element (`../../checks/fleet-shape.ts`).\n *\n * One reader rather than two, because the two checks were answering the same\n * question from different evidence and disagreeing about the fleet. The\n * consumer census walked the MAP's relationships alone, so a store whose\n * consuming edges are all drawn in `services/<…>/model.likec4` had zero\n * consumers and earned nothing at all — and a store reached by three models plus\n * one map edge was graded against the single service the MAP happened to draw\n * (R1). A model is evidence about the fleet exactly as the map is, so it is read\n * here and handed to both.\n *\n * EXTENDING MODELS ONLY, for the consumer join. An extending model's ids ARE the\n * map's fully-qualified ids — one project, one id space — so `counterpartId`\n * joins to a drawn element exactly. A standalone model's ids are its own file's,\n * and `../../../../core/c4/resolve/attested.ts` says outright that a counterpart\n * is never matched to a landscape element: joining one would file a coincidence\n * of spelling as a coupling. Standalone models still contribute their attested\n * CALLS, because `landscape.service-isolated` asks only whether the model\n * declares any, which is shape-independent.\n *\n * A model that does not parse contributes nothing — neither calls nor stores.\n * `c4.invalid` is that file's finding, and a fleet-shape advisory computed from\n * a document nobody could read would be an inference about bytes.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it\n * imports NOTHING from `fleet/`, so the map facts it needs (the resolver, the\n * ids the map draws at fleet level) arrive on the input record.\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem } from \"../../../../core/c4/likec4.js\";\nimport { attestedCalls, type AttestedCall } from \"../../../../core/c4/resolve/attested.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { type ServiceEntry } from \"../../../../core/repo/entries.js\";\nimport { servicePathsAt } from \"../../../../core/repo/paths.js\";\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 context = input.fleet ?? new FleetContext();\n const present = input.entries.filter((entry) => existsSync(servicePathsAt(entry.dir).model));\n // ONE workspace for the fleet's models rather than one per service. Both\n // answers below are about the WHOLE fleet — who else reaches this store is not\n // a question a narrowed run may answer differently — while `--base` prefetches\n // only the scope it grades, so without this a scoped run would spin a LikeC4\n // workspace per un-narrowed service. An accelerator with `prefetchLikeC4`'s\n // failure story exactly: a batch that cannot run seeds nothing, and every read\n // below falls back to its own load.\n await context.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));\n const loaded = await inOrder(present, async (entry) => ({\n entry,\n // CONTAINED, for `fleet/load.ts unreadableLandscape`'s reason: this runs on\n // the landscape target, which sits outside the dispatcher's `guarded`, so a\n // `model.likec4` that is a directory or carries a permission bit would\n // otherwise become the whole run's `repository-unavailable` — one file, and\n // no report at all for the ninety-nine services that are fine. A model that\n // could not be READ contributes nothing here, exactly as one that did not\n // parse does; `service.unreadable` is its own target's finding.\n model: await context.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),\n }));\n\n const models: AttestedModel[] = [];\n const nestedStores: NestedStore[] = [];\n for (const { entry, model } of loaded) {\n // A model that does not parse attests nothing: `c4.invalid` is its finding.\n // An extending model whose MAP does not parse could not be read at all, and\n // neither reader runs in that state anyway — the fleet target returns on\n // `landscape.invalid` long before here.\n if (model === null || model.mapUnreadable || model.doc.errors.length > 0) continue;\n models.push({\n service: entry.id,\n extending: model.shape === \"extending\",\n calls: attestedCalls(model.doc, entry.id, input.services),\n });\n if (model.shape !== \"extending\") continue;\n for (const element of model.doc.elements) {\n if (element.kind.toLowerCase() !== DATASTORE_KIND) continue;\n // The slice carries the partners this model's own edges point at, and a\n // partner may be another service's store — so the store is kept only when\n // the MAP's resolver files it under this service, and only when the map\n // does not draw it as a peer of the services (which is the fleet-level\n // census's subject, and a different grade).\n if (input.standsForService(element)) continue;\n if (input.drawnIds.has(element.id) || input.resolve(element.id) !== (entry.id as string)) continue;\n nestedStores.push({ owner: entry.id, fqn: element.id, title: element.title });\n }\n }\n return { models, nestedStores };\n}\n"]}
1
+ {"version":3,"file":"attest.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/attest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the fleet's own `model.likec4` files say, read ONCE for the two map\n * checks that need it: the calls each model attests across its boundary\n * (`./isolation.ts`), and the datastores that live nested inside a service's own\n * element (`../../checks/fleet-shape.ts`).\n *\n * One reader rather than two, because the two checks were answering the same\n * question from different evidence and disagreeing about the fleet — the\n * map-only consumer census `./consumers.ts` tells the story of (R1). A model is\n * evidence about the fleet exactly as the map is, so it is read here and handed\n * to both.\n *\n * EXTENDING MODELS ONLY, for the consumer join. An extending model's ids ARE the\n * map's fully-qualified ids — one project, one id space — so `counterpartId`\n * joins to a drawn element exactly. A standalone model's ids are its own file's,\n * and `../../../../core/c4/resolve/attested.ts` says outright that a counterpart\n * is never matched to a landscape element: joining one would file a coincidence\n * of spelling as a coupling. Standalone models still contribute their attested\n * CALLS, because `landscape.service-isolated` asks only whether the model\n * declares any, which is shape-independent.\n *\n * A model that does not parse contributes nothing — neither calls nor stores.\n * `c4.invalid` is that file's finding, and a fleet-shape advisory computed from\n * a document nobody could read would be an inference about bytes.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it\n * imports NOTHING from `fleet/`, so the map facts it needs (the resolver, the\n * ids the map draws at fleet level) arrive on the input record.\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem } from \"../../../../core/c4/likec4.js\";\nimport { attestedCalls, type AttestedCall } from \"../../../../core/c4/resolve/attested.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { type ServiceEntry } from \"../../../../core/repo/entries.js\";\nimport { servicePathsAt } from \"../../../../core/repo/paths.js\";\nimport { DATASTORE_KIND } from \"../../../../core/vocabulary/maturity.js\";\n\n/** One service's model, as the map checks read it. */\nexport interface AttestedModel {\n /** The service directory this model belongs to. */\n service: string;\n /** EXTENDING — and therefore whether `counterpartId` is a map id at all. */\n extending: boolean;\n calls: readonly AttestedCall[];\n}\n\n/** A datastore drawn INSIDE the element that resolves to its owner, rather than beside it. */\nexport interface NestedStore {\n /** The service whose element holds it. */\n owner: string;\n /** The store's id as the one project spells it — `marketplace.orderService.db`. */\n fqn: string;\n title: string;\n}\n\nexport interface MapAttestation {\n models: readonly AttestedModel[];\n nestedStores: readonly NestedStore[];\n}\n\nexport interface AttestInput {\n docsDir: DocsDir;\n /** The enumerated services — every one of them, not a candidate subset: both readers ask about the whole fleet. */\n entries: readonly ServiceEntry[];\n /** The services/<id>/ directories that exist, as the resolver's `known`. */\n services: ReadonlySet<string>;\n /** The MAP's element→service resolver — what decides whose element a nested id sits under. */\n resolve: (id: string) => string;\n /**\n * `census.ts`'s one predicate for \"this element stands for a `services/<id>/`\",\n * injected because a `fleet/map/` module may not import `fleet/`. A `database`\n * that IS a service directory is the fleet itself, not a store: the binding\n * checks grade it, exactly as they do in `./consumers.ts`'s two halves.\n */\n standsForService: (e: Elem) => boolean;\n /** The ids the map draws at FLEET level: a datastore among them is a peer, and the peer census owns it. */\n drawnIds: ReadonlySet<string>;\n /** Under `--all` every model.likec4 is already in this memo, so the reads below are hits. */\n fleet: FleetContext;\n}\n\nexport async function mapAttestation(input: AttestInput): Promise<MapAttestation> {\n // Through the ONE reader of `model.likec4` (`core/c4/service-model/load.ts`),\n // so a model that EXTENDS the map attests the calls it draws inside the\n // element the map binds — read as a slice of the per-service project — exactly\n // as a standalone one attests the calls it draws in its own file. Memoised in\n // the index, so under `--all` these are hits. Pooled and joined on the entry,\n // never by index.\n const present = input.entries.filter((entry) => existsSync(servicePathsAt(entry.dir).model));\n // ONE workspace for the fleet's models rather than one per service. Both\n // answers below are about the WHOLE fleet — who else reaches this store is not\n // a question a narrowed run may answer differently — while `--base` prefetches\n // only the scope it grades, so without this a scoped run would spin a LikeC4\n // workspace per un-narrowed service. An accelerator with `prefetchLikeC4`'s\n // failure story exactly: a batch that cannot run seeds nothing, and every read\n // below falls back to its own load.\n await input.fleet.prefetchServiceModels(input.docsDir, present.map((entry) => servicePathsAt(entry.dir)));\n const loaded = await inOrder(present, async (entry) => ({\n entry,\n // CONTAINED, for `fleet/load.ts unreadableLandscape`'s reason: this runs on\n // the landscape target, which sits outside the dispatcher's `guarded`, so a\n // `model.likec4` that is a directory or carries a permission bit would\n // otherwise become the whole run's `repository-unavailable` — one file, and\n // no report at all for the ninety-nine services that are fine. A model that\n // could not be READ contributes nothing here, exactly as one that did not\n // parse does; `service.unreadable` is its own target's finding.\n model: await input.fleet.serviceModel(input.docsDir, servicePathsAt(entry.dir)).catch(() => null),\n }));\n\n const models: AttestedModel[] = [];\n const nestedStores: NestedStore[] = [];\n for (const { entry, model } of loaded) {\n // A model that does not parse attests nothing: `c4.invalid` is its finding.\n // An extending model whose MAP does not parse could not be read at all, and\n // neither reader runs in that state anyway — the fleet target returns on\n // `landscape.invalid` long before here.\n if (model === null || model.mapUnreadable || model.doc.errors.length > 0) continue;\n models.push({\n service: entry.id,\n extending: model.shape === \"extending\",\n calls: attestedCalls(model.doc, entry.id, input.services),\n });\n if (model.shape !== \"extending\") continue;\n for (const element of model.doc.elements) {\n if (element.kind.toLowerCase() !== DATASTORE_KIND) continue;\n // The slice carries the partners this model's own edges point at, and a\n // partner may be another service's store — so the store is kept only when\n // the MAP's resolver files it under this service, and only when the map\n // does not draw it as a peer of the services (which is the fleet-level\n // census's subject, and a different grade).\n if (input.standsForService(element)) continue;\n if (input.drawnIds.has(element.id) || input.resolve(element.id) !== (entry.id as string)) continue;\n nestedStores.push({ owner: entry.id, fqn: element.id, title: element.title });\n }\n }\n return { models, nestedStores };\n}\n"]}
@@ -11,9 +11,7 @@
11
11
  * caller's business, which is why this is one function rather than four.
12
12
  *
13
13
  * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule: it
14
- * imports NOTHING from `fleet/` itself — a child reaching back into its parent
15
- * while the parent calls the child is the package cycle
16
- * `scripts/package-graph.mjs` refuses — so the two census derivations arrive as
14
+ * imports NOTHING from `fleet/` itself, so the two census derivations arrive as
17
15
  * data on the input record rather than as an import of `../census.js`.
18
16
  */
19
17
  import {} from "../../../../core/c4/likec4.js";
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/bindings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The element↔directory passes of the fleet cross-check: which drawn box\n * answers for which `services/<id>/`, and what it means when the answer is\n * none, two, or one nobody owns.\n *\n * Lifted out of `../landscape.ts` when that module reached its line limit, and\n * the seam is a phase rather than a subject: everything here runs after the map\n * has parsed, reads nothing from disk, and grades exactly one join — the census\n * `../census.ts` derives against the enumeration `../landscape.ts` performed.\n * The four grades are ordered as the report reads them, and the ORDER is the\n * caller's business, which is why this is one function rather than four.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule: it\n * imports NOTHING from `fleet/` itself — a child reaching back into its parent\n * while the parent calls the child is the package cycle\n * `scripts/package-graph.mjs` refuses — so the two census derivations arrive as\n * data on the input record rather than as an import of `../census.js`.\n */\nimport { type Elem } from \"../../../../core/c4/likec4.js\";\nimport { elementService } from \"../../../../core/c4/resolve/service.js\";\nimport { EXTERNAL_TAG, INFRA_KINDS } from \"../../../../core/vocabulary/maturity.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\n\nexport interface BindingInput {\n /** Every element the map declares, at any depth — a binding answers for itself wherever it is written. */\n elements: Elem[];\n /** Service-LEVEL elements (`census.ts serviceLevelElements`) — the fleet map's own boxes. */\n drawn: Elem[];\n /** The drawn SYSTEMS (`census.ts drawnSystems`) — `drawn` minus what the map itself says is no service. */\n systems: Elem[];\n /** The services/<id>/ directories that exist. */\n services: ReadonlySet<string>;\n /** The services some element resolves to — the set `landscape.service-unmodelled` reads. */\n modelled: ReadonlySet<string>;\n /** Where a service's directory sits, repo-relative — the enumeration's answer, never `services/<id>`. */\n pathOf: (id: string) => string;\n}\n\nexport function bindingFindings(input: BindingInput): Finding[] {\n const { elements, drawn, services, pathOf } = input;\n const findings: Finding[] = [];\n\n // Two boxes standing for one service directory. Every join in loam is\n // `element -> service`, computed by picking the FIRST element that resolves —\n // so with two of them, which one wins is readdir order, and the edges of the\n // loser are attributed to a service they do not belong to. Silent until now,\n // and unfixable by staring at either element on its own.\n const perService = new Map<string, Elem[]>();\n for (const e of drawn) {\n if (e.tags.includes(EXTERNAL_TAG)) continue;\n const id = elementService(e);\n perService.set(id, [...(perService.get(id) ?? []), e]);\n }\n for (const [id, elems] of perService) {\n if (elems.length < 2) continue;\n // A collision only matters where it decides something: a real directory to\n // attribute to, or a binding somebody wrote down on purpose.\n if (!services.has(id) && !elems.some((e) => e.service !== undefined)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.binding-duplicate\",\n subject: id,\n message: `landscape: ${elems.length} elements resolve to service '${id}' (${elems.map((e) => e.id).join(\", \")}) — every element→service join picks one of them arbitrarily, so the others' edges are filed under a service that does not own them; keep one element per services/<id>/`,\n });\n }\n\n for (const id of services) {\n if (input.modelled.has(id)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.service-unmodelled\",\n subject: id,\n message: `landscape: ${pathOf(id)}/ exists but nothing in architecture/landscape.likec4 models it — add an element, or bind one with metadata { service '${id}' }`,\n });\n }\n\n // A binding is a claim about this repo wherever it is written — including\n // inside another element, which the old top-level filter never looked at, so\n // a typo one level down bound an edge to a service that does not exist and\n // nothing said so. Every element with a binding answers for it, at any depth.\n for (const e of elements) {\n if (e.tags.includes(EXTERNAL_TAG) || e.service === undefined) continue;\n if (services.has(e.service)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.binding-unknown\",\n subject: e.service,\n message: `landscape: '${e.title}' binds to service '${e.service}', but services/${e.service}/ does not exist`,\n });\n }\n\n // Walked over the SYSTEM census — the same derivation the scorecard counts,\n // so the map's exemptions and the fleet rollup cannot drift — with the two\n // residual skips that are not exemptions: a bound element is the binding\n // pass's subject above, and a title naming a real directory is documented.\n //\n // Infrastructure the fleet OWNS is out of `systems` upstream, in\n // `../census.ts drawnSystems` (INFRA_KINDS), and it had to be: this message\n // offers exactly two remedies and BOTH are false of an owned datastore — there\n // is no `services/<id>/` to bind, and `#external` would claim somebody else\n // runs it. Measured on a one-service fixture drawing an owned `database` at\n // fleet level (2026-09-07), it fired here and had no correct fix.\n for (const e of input.systems) {\n if (e.service !== undefined) continue; // graded by the binding pass above\n if (services.has(e.title)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.service-undocumented\",\n subject: e.title,\n // The third remedy is spelled from `INFRA_KINDS` rather than typed out,\n // for the reason the set exists at all: the hook is a kind NAME, so a\n // fleet whose specification spells its store `postgres` still lands here,\n // and this sentence is the only place that tells its author which\n // spellings loam reads. A prose copy would be a second list free to drift\n // from the one the census walks.\n message: `landscape: '${e.title}' has no services/${e.title}/ — bind it with metadata { service '<id>' }, tag it #${EXTERNAL_TAG} if it is not ours, or give it one of the kinds loam reads as infrastructure (${[...INFRA_KINDS].join(\", \")}) if it is a store, queue or bucket the fleet runs rather than a system somebody adopts`,\n });\n }\n\n return findings;\n}\n"]}
1
+ {"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"]}
@@ -10,15 +10,11 @@
10
10
  * the grades; this owns which inputs they are handed, which is the part that has
11
11
  * twice been got wrong and is worth stating in one place.
12
12
  *
13
- * A sub-package of `fleet/` for `map/isolation.ts`'s reason, and with its rule:
14
- * it imports NOTHING from `fleet/` itself — a child reaching back into its
15
- * parent while the parent calls the child is the package cycle
16
- * `scripts/package-graph.mjs` refuses — so everything it needs arrives in one
17
- * record the caller fills.
13
+ * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule:
14
+ * it imports NOTHING from `fleet/` itself, so everything it needs arrives in
15
+ * one record the caller fills.
18
16
  */
19
- import { readCapabilityVocabulary } from "../../../../core/capabilities/capabilities.js";
20
17
  import { capabilityRequirementIndex, gradableCapabilityIds } from "../../../../core/capabilities/findings.js";
21
- import { parseRequirements, readRequirementsDocument } from "../../../../core/document/parse.js";
22
18
  import { FleetContext } from "../../../../core/fleet-context.js";
23
19
  import { useCaseFindings } from "../usecases/usecases.js";
24
20
  /**
@@ -39,10 +35,8 @@ import { useCaseFindings } from "../usecases/usecases.js";
39
35
  */
40
36
  export async function fleetUseCaseFindings(input) {
41
37
  const { docsDir, fleet } = input;
42
- const vocabulary = fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);
43
- const capabilityReqs = await capabilityRequirementIndex(vocabulary, fleet === undefined
44
- ? async (p) => parseRequirements(await readRequirementsDocument(p))
45
- : (p) => fleet.readRequirements(p));
38
+ const vocabulary = await fleet.capabilities(docsDir);
39
+ const capabilityReqs = await capabilityRequirementIndex(vocabulary, (p) => fleet.readRequirements(p));
46
40
  return useCaseFindings({
47
41
  views: input.views,
48
42
  elements: input.elements,
@@ -1 +1 @@
1
- {"version":3,"file":"usecases.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/usecases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AACzF,OAAO,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAC9G,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AACjG,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAsB1D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAAoB;IAC7D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjC,MAAM,UAAU,GACd,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,wBAAwB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACpG,MAAM,cAAc,GAAG,MAAM,0BAA0B,CACrD,UAAU,EACV,KAAK,KAAK,SAAS;QACjB,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,MAAM,wBAAwB,CAAC,CAAC,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CACrC,CAAC;IACF,OAAO,eAAe,CAAC;QACrB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,YAAY,EAAE,qBAAqB,CAAC,UAAU,CAAC;QAC/C,cAAc,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC;KAC5E,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The fleet's use cases, graded — the CALL, with the two vocabulary reads it\n * needs, lifted out of `../landscape.ts` when that module reached its line\n * limit.\n *\n * The seam is a phase rather than a subject: everything here happens after the\n * map has parsed and grades documents the map does not own — every\n * `dynamic view` in `architecture/landscape.likec4` and in every\n * `architecture/usecases/*.likec4`. `../usecases/usecases.ts` owns the opt-in and\n * the grades; this owns which inputs they are handed, which is the part that has\n * twice been got wrong and is worth stating in one place.\n *\n * A sub-package of `fleet/` for `map/isolation.ts`'s reason, and with its rule:\n * it imports NOTHING from `fleet/` itself — a child reaching back into its\n * parent while the parent calls the child is the package cycle\n * `scripts/package-graph.mjs` refuses — so everything it needs arrives in one\n * record the caller fills.\n */\nimport { readCapabilityVocabulary } from \"../../../../core/capabilities/capabilities.js\";\nimport { capabilityRequirementIndex, gradableCapabilityIds } from \"../../../../core/capabilities/findings.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../../../core/document/parse.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport type { Elem, Rel } from \"../../../../core/c4/likec4.js\";\nimport type { ParsedView } from \"../../../../core/c4/parsed/dynamic-views.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\nimport { useCaseFindings } from \"../usecases/usecases.js\";\n\nexport interface FleetUseCases {\n docsDir: DocsDir;\n /**\n * The views come from the PROJECT load, never from a single-file fallback,\n * and that is the file-naming rule made mechanical instead of remembered:\n * only the project load gives a view the `sourcePath` a finding has to name,\n * while a single-file load calls every document `source.c4` — so a message\n * built from that load would send its reader to `architecture/source.c4`, a\n * file that has never existed. No project, no views, and nothing to get wrong.\n */\n views: readonly ParsedView[];\n /** The map's model — what a hop is resolved against. */\n elements: Elem[];\n relationships: Rel[];\n /** The enumerated fleet, and the resolver every other edge join in the target uses. */\n services: ReadonlySet<string>;\n resolve: (id: string) => string;\n fleet?: FleetContext;\n}\n\n/**\n * The capability ladder is APPLIED BY ITS OWN FUNCTION rather than re-spelled\n * here: an absent or unreadable capabilities.yaml means silence for the whole\n * family, not a fleet full of unresolved tags, so `null` travels rather than an\n * empty list — which the join would read as \"the fleet declares no capabilities\"\n * and grade every tag against. `gradableCapabilityIds`\n * (`core/capabilities/findings.ts`) is the one statement of that rule, and this\n * is a caller of it precisely so a fourth un-gradable vocabulary state cannot be\n * fixed in core while the command layer keeps handing the join a whole key set.\n *\n * Both reads are already paid for under `--all`: `capabilityFleetFindings` has\n * asked for the vocabulary, and every requirement document behind the `#req-`\n * tag was parsed by it, so the fleet memo answers from cache. The requirement\n * index is built here rather than inside the use-case package so that package\n * never learns what a capability document is.\n */\nexport async function fleetUseCaseFindings(input: FleetUseCases): Promise<Finding[]> {\n const { docsDir, fleet } = input;\n const vocabulary =\n fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);\n const capabilityReqs = await capabilityRequirementIndex(\n vocabulary,\n fleet === undefined\n ? async (p) => parseRequirements(await readRequirementsDocument(p))\n : (p) => fleet.readRequirements(p),\n );\n return useCaseFindings({\n views: input.views,\n elements: input.elements,\n relationships: input.relationships,\n services: input.services,\n resolve: input.resolve,\n capabilities: gradableCapabilityIds(vocabulary),\n requirementsOf: (capability) => capabilityReqs.byCapability.get(capability),\n });\n}\n"]}
1
+ {"version":3,"file":"usecases.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/usecases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAsB1D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAAoB;IAC7D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjC,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,MAAM,0BAA0B,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IACtG,OAAO,eAAe,CAAC;QACrB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,YAAY,EAAE,qBAAqB,CAAC,UAAU,CAAC;QAC/C,cAAc,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC;KAC5E,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The fleet's use cases, graded — the CALL, with the two vocabulary reads it\n * needs, lifted out of `../landscape.ts` when that module reached its line\n * limit.\n *\n * The seam is a phase rather than a subject: everything here happens after the\n * map has parsed and grades documents the map does not own — every\n * `dynamic view` in `architecture/landscape.likec4` and in every\n * `architecture/usecases/*.likec4`. `../usecases/usecases.ts` owns the opt-in and\n * the grades; this owns which inputs they are handed, which is the part that has\n * twice been got wrong and is worth stating in one place.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule:\n * it imports NOTHING from `fleet/` itself, so everything it needs arrives in\n * one record the caller fills.\n */\nimport { capabilityRequirementIndex, gradableCapabilityIds } from \"../../../../core/capabilities/findings.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport type { Elem, Rel } from \"../../../../core/c4/likec4.js\";\nimport type { ParsedView } from \"../../../../core/c4/parsed/dynamic-views.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\nimport { useCaseFindings } from \"../usecases/usecases.js\";\n\nexport interface FleetUseCases {\n docsDir: DocsDir;\n /**\n * The views come from the PROJECT load, never from a single-file fallback,\n * and that is the file-naming rule made mechanical instead of remembered:\n * only the project load gives a view the `sourcePath` a finding has to name,\n * while a single-file load calls every document `source.c4` — so a message\n * built from that load would send its reader to `architecture/source.c4`, a\n * file that has never existed. No project, no views, and nothing to get wrong.\n */\n views: readonly ParsedView[];\n /** The map's model — what a hop is resolved against. */\n elements: Elem[];\n relationships: Rel[];\n /** The enumerated fleet, and the resolver every other edge join in the target uses. */\n services: ReadonlySet<string>;\n resolve: (id: string) => string;\n fleet: FleetContext;\n}\n\n/**\n * The capability ladder is APPLIED BY ITS OWN FUNCTION rather than re-spelled\n * here: an absent or unreadable capabilities.yaml means silence for the whole\n * family, not a fleet full of unresolved tags, so `null` travels rather than an\n * empty list — which the join would read as \"the fleet declares no capabilities\"\n * and grade every tag against. `gradableCapabilityIds`\n * (`core/capabilities/findings.ts`) is the one statement of that rule, and this\n * is a caller of it precisely so a fourth un-gradable vocabulary state cannot be\n * fixed in core while the command layer keeps handing the join a whole key set.\n *\n * Both reads are already paid for under `--all`: `capabilityFleetFindings` has\n * asked for the vocabulary, and every requirement document behind the `#req-`\n * tag was parsed by it, so the fleet memo answers from cache. The requirement\n * index is built here rather than inside the use-case package so that package\n * never learns what a capability document is.\n */\nexport async function fleetUseCaseFindings(input: FleetUseCases): Promise<Finding[]> {\n const { docsDir, fleet } = input;\n const vocabulary = await fleet.capabilities(docsDir);\n const capabilityReqs = await capabilityRequirementIndex(vocabulary, (p) => fleet.readRequirements(p));\n return useCaseFindings({\n views: input.views,\n elements: input.elements,\n relationships: input.relationships,\n services: input.services,\n resolve: input.resolve,\n capabilities: gradableCapabilityIds(vocabulary),\n requirementsOf: (capability) => capabilityReqs.byCapability.get(capability),\n });\n}\n"]}
@@ -37,7 +37,6 @@ import { servicePathsAt } from "../../../core/repo/paths.js";
37
37
  import {} from "../../../core/repo/entries.js";
38
38
  import {} from "../../../core/vocabulary/report.js";
39
39
  import { FleetContext } from "../../../core/fleet-context.js";
40
- import { readRequirementsDocument, parseRequirements } from "../../../core/document/parse.js";
41
40
  import { coversEntries } from "../checks/requirements.js";
42
41
  /**
43
42
  * The half that does not depend on the map: the file reads, and its `adr:`
@@ -234,10 +233,7 @@ async function livingCovers(check) {
234
233
  for (const path of [paths.spec, paths.archSpec]) {
235
234
  if (!existsSync(path))
236
235
  continue;
237
- const reqs = check.fleet === undefined
238
- ? parseRequirements(await readRequirementsDocument(path))
239
- : await check.fleet.readRequirements(path);
240
- entries.push(...coversEntries(reqs));
236
+ entries.push(...coversEntries(await check.fleet.readRequirements(path)));
241
237
  }
242
238
  }
243
239
  return entries;
@@ -1 +1 @@
1
- {"version":3,"file":"obligations.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/obligations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architectural obligation axis, graded against the fleet it is about.\n *\n * `core/obligations/obligations.ts` states what the axis IS and why it has this\n * shape. This module is the four questions asked of a fleet that has one, and\n * the order they are asked in — the same division `checks/fleet-shape.ts` keeps\n * for the authorization and capability vocabularies, which this sits beside on\n * the `landscape` target for their reason: an obligation crosses services by\n * construction, so no service target can answer for it.\n *\n * THE FOUR ARE TWO PAIRS, and each pair is one join read in both directions.\n * A tag nothing declares is an ERROR (`obligation.unknown`) and a declaration\n * nothing tags is a WARNING (`obligation.unapplied`) — exactly the asymmetry\n * `permissions.unknown`/`permissions.unenforced` already carry, and for the\n * same reason: a typo in a tag reads like a rule, while a word nobody has\n * adopted yet is an honest state. Then a tagged object no requirement covers is\n * a WARNING (`obligation.uncovered`) and an `adr:` naming no file is an ERROR\n * (`obligation.adr-missing`): the team's work being outstanding is normal, and\n * a pointer at a decision nobody can read is not.\n *\n * `obligation.uncovered` IS THE PREREQUISITE THE ROADMAP NAMED. `c4.uncovered`\n * has always been able to say \"this architecture object owes a requirement\",\n * but only about a NEW tagged element in a feature's `delta.likec4` — never\n * about the map the fleet actually runs on. This asks the same question of the\n * LIVING landscape, against the union of every service's living `arch.spec.md`\n * `Covers:` lines, which is the index that did not exist before.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport {\n closeIds,\n coversDeployEdge,\n coversDeployNode,\n coversEdge,\n coversElement,\n type CoversEntry,\n} from \"../../../core/c4/arch.js\";\nimport { NO_DEPLOYMENT } from \"../../../core/c4/parsed/deployment.js\";\nimport { taggedDeployment, type TaggedDeployment } from \"../../../core/deployment/objects.js\";\nimport { elementService } from \"../../../core/c4/resolve/service.js\";\nimport { OBLIGATION_TAG_PREFIX, type ObligationVocabulary } from \"../../../core/obligations/obligations.js\";\nimport { servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { type ServiceEntry } from \"../../../core/repo/entries.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { readRequirementsDocument, parseRequirements } from \"../../../core/document/parse.js\";\nimport { coversEntries } from \"../checks/requirements.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport interface ObligationCheck {\n docsDir: DocsDir;\n vocabulary: ObligationVocabulary;\n /**\n * The living landscape, PARSED. Not nullable, and the absence is the point:\n * the landscape target returns before it reaches this call when the map is\n * missing or did not parse, so a run that could not read the map asks none of\n * the three questions below rather than answering them empty. \"Loam did not\n * look\" is never the same answer as \"there is nothing there\" — an unreadable\n * map graded as empty would report every declared obligation as applied\n * nowhere — and the suspension lives at the call site because that is where\n * the fact is known. It is also why the vocabulary half is a separate\n * function: those two verdicts must survive exactly those early returns.\n */\n land: LoadedDoc;\n services: ServiceEntry[];\n fleet?: FleetContext;\n}\n\n/**\n * The half that does not depend on the map: the file reads, and its `adr:`\n * pointers resolve.\n *\n * Separate from the map questions because the landscape target returns early\n * twice — an absent map, and one that did not parse — and a broken vocabulary\n * must still be named in exactly the runs where somebody is fixing something\n * else. The same placement `permissionFindings` and `capabilityFleetFindings`\n * already have, one level of precision finer.\n */\nexport function obligationVocabularyFindings(docsDir: DocsDir, vocabulary: ObligationVocabulary): Finding[] {\n if (!vocabulary.present) return [];\n if (vocabulary.invalid !== undefined) {\n // Reported ALONE, exactly as `permissions.invalid` and `capability.invalid`\n // are: every `#obl-` tag in the fleet resolves against this file, so grading\n // them on top of a broken vocabulary is a cascade, not a diagnosis.\n return [\n {\n severity: \"error\",\n code: \"obligation.invalid\",\n message:\n `landscape: architecture/obligations.yaml does not read as a vocabulary — ${vocabulary.invalid}. ` +\n \"Every `#obl-` tag on the fleet map resolves against this file, so none of them can be graded until it parses. \" +\n \"The shape is `obligations: {<id>: {description, adr}}`, ids spelled with letters, digits, `_` and `-` only.\",\n },\n ];\n }\n\n return adrFindings(docsDir, [...vocabulary.byId.values()]);\n}\n\n/** The three questions that need the map, suspended entirely when it could not be read. */\nexport async function obligationFindings(check: ObligationCheck): Promise<Finding[]> {\n const { vocabulary } = check;\n if (!vocabulary.present || vocabulary.invalid !== undefined) return [];\n const findings: Finding[] = [];\n\n // Which objects carry which obligation. Elements and edges are collected\n // together because every question below treats them alike: an obligation\n // applies to a thing on the map, and whether that thing is a box or an arrow\n // changes only how the message spells it and how `Covers:` matches it.\n // The enumerated fleet rides in for the deployment half of the walk: an\n // instance deploys a CONTAINER more often than a whole system, and resolving\n // which service owns one needs the ancestor walk `serviceResolver` performs.\n // It is the same set `uncoveredFindings` resolves edge endpoints with, or the\n // message would name one service and the coverage check count another.\n const known = new Set(check.services.map((s) => s.id));\n const tagged = taggedObjects(check.land, known);\n const applied = new Set(tagged.map((t) => t.obligation));\n\n for (const id of [...applied].sort()) {\n if (vocabulary.byId.has(id)) continue;\n const close = closeIds(id, [...vocabulary.byId.keys()]);\n findings.push({\n severity: \"error\",\n code: \"obligation.unknown\",\n subject: id,\n message:\n `landscape: #${OBLIGATION_TAG_PREFIX}${id} tags ${count(tagged.filter((t) => t.obligation === id).length, \"object\")} on the fleet map, ` +\n `and architecture/obligations.yaml declares no obligation '${id}'` +\n (close.length > 0 ? ` — did you mean: ${close.join(\", \")}?` : \"\") +\n \". A tag that resolves to nothing reads exactly like a rule the fleet keeps: declare it, or fix the tag.\",\n details: tagged.filter((t) => t.obligation === id).map((t) => t.where),\n });\n }\n\n const unapplied = [...vocabulary.byId.keys()].filter((id) => !applied.has(id)).sort();\n if (unapplied.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"obligation.unapplied\",\n message:\n `landscape: ${unapplied.length} declared obligation(s) that no \\`#${OBLIGATION_TAG_PREFIX}\\` tag applies anywhere on the fleet map — ` +\n \"either a decision that was reversed and left its word behind, or one nobody has placed yet. \" +\n \"Tag the elements and edges it governs, or drop the declaration; a vocabulary is worth what cites it.\",\n details: unapplied,\n });\n }\n\n // ONLY A RESOLVED APPLICATION IS ASKED ABOUT COVERAGE. A tag naming no\n // declared obligation has already earned its error above, and \"no requirement\n // covers #obl-typo\" is not a second breach — it is the same one restated in\n // words that ask the reader to write a requirement for a rule that does not\n // exist. The use-case axis draws this line in the same place: only a resolved\n // `#cap-` claim keeps a promise.\n findings.push(...(await uncoveredFindings(check, tagged.filter((t) => vocabulary.byId.has(t.obligation)))));\n return findings;\n}\n\n/** One place an obligation is applied: which obligation, on what, and how a message names it. */\ninterface TaggedObject {\n obligation: string;\n /** The element, the edge, or the deployment object — exactly one is set. */\n element?: Elem;\n edge?: Rel;\n deployed?: TaggedDeployment;\n /**\n * How the finding spells this object, and how a `Covers:` line has to: an\n * element id, `source -> target`, or the `node:` forms.\n */\n where: string;\n /**\n * What the finding is filed under — the service that owns it, where one\n * does. ABSENT for a region, a datacenter or a cluster: nobody-s service\n * runs one, and naming a team that did not ask for the work is worse than\n * leaving the sentence general.\n */\n subject?: string;\n}\n\n/**\n * Every `#obl-` tag on the living map, one entry per (obligation, object) pair.\n *\n * A bare `#obl-` slugs to the empty string and is kept as an application on\n * purpose, exactly as a bare `#cap-` is kept as a claim: the prefix is the\n * author's opt-in however little follows it, and dropping it as unreserved\n * would leave an object that asked to be graded silently ungraded. It then\n * resolves to `obligation.unknown` in every fleet that did not declare an empty\n * id, which is the honest answer.\n */\nfunction taggedObjects(land: LoadedDoc, known: ReadonlySet<string>): TaggedObject[] {\n const out: TaggedObject[] = [];\n for (const element of land.elements) {\n for (const tag of element.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n element,\n where: element.id,\n subject: elementService(element),\n });\n }\n }\n for (const edge of land.relationships) {\n for (const tag of edge.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n edge,\n where: `${edge.source} -> ${edge.target}`,\n subject: edge.target,\n });\n }\n }\n // The topology, walked by the same rule and for the reason the rule exists: a\n // tag is a claim about the object it sits on, and which MODEL that object\n // lives in is not something a reader of the tag can see. Until this walk the\n // same undeclared tag was an error on a container and silence on a\n // datacenter — fail-open, and the case FEAT-1's ARCH-LOAM-DEPLOY-TAGGED was\n // written about. A fleet that draws no topology contributes nothing here.\n for (const d of taggedDeployment(land.deployment ?? NO_DEPLOYMENT, land.elements, known, OBLIGATION_TAG_PREFIX)) {\n out.push({\n obligation: d.obligation,\n deployed: d,\n where: d.where,\n ...(d.service === undefined ? {} : { subject: d.service }),\n });\n }\n return out;\n}\n\n/**\n * `obligation.uncovered` — a tagged object no living arch requirement covers.\n *\n * The index is the union of EVERY service's living `arch.spec.md` `Covers:`\n * lines, because an obligation is placed by the architect on the map and met by\n * whichever team owns the thing it sits on — and a per-service index would\n * report an edge as uncovered because the requirement that covers it lives in\n * the service at the other end. `coversEntries` already drops REMOVED\n * requirements: a retired requirement covers nothing.\n *\n * ONE FINDING PER TAGGED OBJECT, subject = the service that owns it, so a\n * fleet-wide report reads as a worklist per team rather than as one line naming\n * forty edges.\n */\nasync function uncoveredFindings(check: ObligationCheck, tagged: TaggedObject[]): Promise<Finding[]> {\n if (tagged.length === 0) return [];\n const covers = await livingCovers(check);\n const elements = check.land.elements;\n const known = new Set(check.services.map((s) => s.id));\n const covered = (t: TaggedObject): boolean => {\n if (t.element !== undefined) return covers.some((c) => coversElement(c, t.element!));\n if (t.edge !== undefined) return covers.some((c) => coversEdge(c, t.edge!, elements, known));\n const d = t.deployed!;\n return d.edge === undefined\n ? covers.some((c) => coversDeployNode(c, d.id!))\n : covers.some((c) => coversDeployEdge(c, d.edge!.source, d.edge!.target));\n };\n return tagged\n .filter((t) => !covered(t))\n .map((t) => ({\n severity: \"warn\" as const,\n code: \"obligation.uncovered\",\n ...(t.subject === undefined ? {} : { subject: t.subject }),\n message:\n `${t.where} carries #${OBLIGATION_TAG_PREFIX}${t.obligation} and no living arch requirement covers it — ` +\n `the obligation is placed and nothing says it is met. Write the requirement in ` +\n // A region, a datacenter or a cluster is owned by no service, so the\n // sentence stays general rather than naming a team at random. Every\n // other object here has a real owner and keeps the actionable form.\n `${t.subject === undefined ? \"the arch.spec.md of whichever service answers for this topology\" : `${t.subject}'s arch.spec.md`}` +\n ` with \\`Covers: ${t.where}\\` and a scenario that proves it, or drop the tag if the decision no longer applies here.`,\n }));\n}\n\n/**\n * Every `Covers:` entry the LIVING fleet writes, across both requirement\n * documents of every service.\n *\n * Existence is checked for both files and for the same reason\n * `permissionFindings` checks it: `arch.spec.md` is optional and most of a\n * legacy fleet has none, while `spec.md` is missing on a half-adopted service —\n * and `FleetContext.readRequirements` throws ENOENT, which surfaces as\n * `repository-unavailable` and takes the whole `--all` run down. Both axes are\n * read, not only the architecture one: `Covers:` is parsed everywhere for one\n * grammar's sake, and a fleet that wrote its outbox requirement in `spec.md`\n * has said the thing loam is asking about.\n */\nasync function livingCovers(check: ObligationCheck): Promise<CoversEntry[]> {\n const entries: CoversEntry[] = [];\n for (const service of check.services) {\n const paths = servicePathsAt(service.dir);\n for (const path of [paths.spec, paths.archSpec]) {\n if (!existsSync(path)) continue;\n const reqs =\n check.fleet === undefined\n ? parseRequirements(await readRequirementsDocument(path))\n : await check.fleet.readRequirements(path);\n entries.push(...coversEntries(reqs));\n }\n }\n return entries;\n}\n\n/**\n * `obligation.adr-missing` — an `adr:` path that names no file.\n *\n * The path is relative to the docs repo root rather than to\n * `architecture/obligations.yaml`, because that is how a reader of a fleet\n * document thinks about a fleet path, and because the value is a YAML field\n * rather than a markdown link — `link.unresolved` grades links written in prose,\n * and this one is written in a slot loam defined.\n */\nfunction adrFindings(docsDir: DocsDir, obligations: readonly { id: string; adr?: string }[]): Finding[] {\n return obligations\n .filter((o) => o.adr !== undefined && !existsSync(join(docsDir, o.adr)))\n .map((o) => ({\n severity: \"error\" as const,\n code: \"obligation.adr-missing\",\n subject: o.id,\n message:\n `landscape: obligation '${o.id}' names \\`adr: ${o.adr!}\\` and no such file exists — ` +\n \"the ADR is where the decision itself is written, so a pointer at nothing leaves every tag citing a rule nobody can read. \" +\n \"Fix the path (it is relative to the docs repo root), write the record, or drop the field.\",\n }));\n}\n\n/** `1 object` / `3 objects` — the plural spelled once, because three messages count things. */\nfunction count(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n"]}
1
+ {"version":3,"file":"obligations.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/obligations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAuC,MAAM,4BAA4B,CAAC;AACjF,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,aAAa,GAEd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAyB,MAAM,qCAAqC,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAA6B,MAAM,0CAA0C,CAAC;AAC5G,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAqB,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAgB,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAsB1D;;;;;;;;;GASG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAAgB,EAAE,UAAgC;IAC7F,IAAI,CAAC,UAAU,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACrC,4EAA4E;QAC5E,6EAA6E;QAC7E,oEAAoE;QACpE,OAAO;YACL;gBACE,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EACL,4EAA4E,UAAU,CAAC,OAAO,IAAI;oBAClG,gHAAgH;oBAChH,6GAA6G;aAChH;SACF,CAAC;IACJ,CAAC;IAED,OAAO,WAAW,CAAC,OAAO,EAAE,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAsB;IAC7D,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IAC7B,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACvE,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,yEAAyE;IACzE,yEAAyE;IACzE,6EAA6E;IAC7E,uEAAuE;IACvE,wEAAwE;IACxE,6EAA6E;IAC7E,6EAA6E;IAC7E,8EAA8E;IAC9E,uEAAuE;IACvE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAEzD,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,SAAS;QACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,EAAE,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACxD,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,EAAE;YACX,OAAO,EACL,eAAe,qBAAqB,GAAG,EAAE,SAAS,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,qBAAqB;gBACxI,6DAA6D,EAAE,GAAG;gBAClE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,oBAAoB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjE,yGAAyG;YAC3G,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;SACvE,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtF,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EACL,cAAc,SAAS,CAAC,MAAM,sCAAsC,qBAAqB,6CAA6C;gBACtI,8FAA8F;gBAC9F,sGAAsG;YACxG,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC;IACL,CAAC;IAED,uEAAuE;IACvE,8EAA8E;IAC9E,4EAA4E;IAC5E,4EAA4E;IAC5E,8EAA8E;IAC9E,iCAAiC;IACjC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5G,OAAO,QAAQ,CAAC;AAClB,CAAC;AAuBD;;;;;;;;;GASG;AACH,SAAS,aAAa,CAAC,IAAe,EAAE,KAA0B;IAChE,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QACpC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,EAAE,CAAC;YAClF,GAAG,CAAC,IAAI,CAAC;gBACP,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,qBAAqB,CAAC,MAAM,CAAC;gBACnD,OAAO;gBACP,KAAK,EAAE,OAAO,CAAC,EAAE;gBACjB,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC;aACjC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACtC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,EAAE,CAAC;YAC/E,GAAG,CAAC,IAAI,CAAC;gBACP,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,qBAAqB,CAAC,MAAM,CAAC;gBACnD,IAAI;gBACJ,KAAK,EAAE,GAAG,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE;gBACzC,OAAO,EAAE,IAAI,CAAC,MAAM;aACrB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,0EAA0E;IAC1E,6EAA6E;IAC7E,mEAAmE;IACnE,4EAA4E;IAC5E,0EAA0E;IAC1E,KAAK,MAAM,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,UAAU,IAAI,aAAa,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,qBAAqB,CAAC,EAAE,CAAC;QAChH,GAAG,CAAC,IAAI,CAAC;YACP,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,QAAQ,EAAE,CAAC;YACX,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC3D,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,KAAK,UAAU,iBAAiB,CAAC,KAAsB,EAAE,MAAsB;IAC7E,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,CAAC,CAAe,EAAW,EAAE;QAC3C,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,OAAQ,CAAC,CAAC,CAAC;QACrF,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,IAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QAC7F,MAAM,CAAC,GAAG,CAAC,CAAC,QAAS,CAAC;QACtB,OAAO,CAAC,CAAC,IAAI,KAAK,SAAS;YACzB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAG,CAAC,CAAC;YAChD,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,IAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC;IACF,OAAO,MAAM;SACV,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;SAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE,MAAe;QACzB,IAAI,EAAE,sBAAsB;QAC5B,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,OAAO,EACL,GAAG,CAAC,CAAC,KAAK,aAAa,qBAAqB,GAAG,CAAC,CAAC,UAAU,8CAA8C;YACzG,gFAAgF;YAChF,qEAAqE;YACrE,oEAAoE;YACpE,oEAAoE;YACpE,GAAG,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,iEAAiE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,iBAAiB,EAAE;YAChI,mBAAmB,CAAC,CAAC,KAAK,2FAA2F;KACxH,CAAC,CAAC,CAAC;AACR,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,KAAK,UAAU,YAAY,CAAC,KAAsB;IAChD,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1C,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAChC,OAAO,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,WAAW,CAAC,OAAgB,EAAE,WAAoD;IACzF,OAAO,WAAW;SACf,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;SACvE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE,OAAgB;QAC1B,IAAI,EAAE,wBAAwB;QAC9B,OAAO,EAAE,CAAC,CAAC,EAAE;QACb,OAAO,EACL,0BAA0B,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,GAAI,+BAA+B;YACrF,2HAA2H;YAC3H,2FAA2F;KAC9F,CAAC,CAAC,CAAC;AACR,CAAC;AAED,+FAA+F;AAC/F,SAAS,KAAK,CAAC,CAAS,EAAE,IAAY;IACpC,OAAO,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC7C,CAAC","sourcesContent":["/**\n * The architectural obligation axis, graded against the fleet it is about.\n *\n * `core/obligations/obligations.ts` states what the axis IS and why it has this\n * shape. This module is the four questions asked of a fleet that has one, and\n * the order they are asked in — the same division `checks/fleet-shape.ts` keeps\n * for the authorization and capability vocabularies, which this sits beside on\n * the `landscape` target for their reason: an obligation crosses services by\n * construction, so no service target can answer for it.\n *\n * THE FOUR ARE TWO PAIRS, and each pair is one join read in both directions.\n * A tag nothing declares is an ERROR (`obligation.unknown`) and a declaration\n * nothing tags is a WARNING (`obligation.unapplied`) — exactly the asymmetry\n * `permissions.unknown`/`permissions.unenforced` already carry, and for the\n * same reason: a typo in a tag reads like a rule, while a word nobody has\n * adopted yet is an honest state. Then a tagged object no requirement covers is\n * a WARNING (`obligation.uncovered`) and an `adr:` naming no file is an ERROR\n * (`obligation.adr-missing`): the team's work being outstanding is normal, and\n * a pointer at a decision nobody can read is not.\n *\n * `obligation.uncovered` IS THE PREREQUISITE THE ROADMAP NAMED. `c4.uncovered`\n * has always been able to say \"this architecture object owes a requirement\",\n * but only about a NEW tagged element in a feature's `delta.likec4` — never\n * about the map the fleet actually runs on. This asks the same question of the\n * LIVING landscape, against the union of every service's living `arch.spec.md`\n * `Covers:` lines, which is the index that did not exist before.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport {\n closeIds,\n coversDeployEdge,\n coversDeployNode,\n coversEdge,\n coversElement,\n type CoversEntry,\n} from \"../../../core/c4/arch.js\";\nimport { NO_DEPLOYMENT } from \"../../../core/c4/parsed/deployment.js\";\nimport { taggedDeployment, type TaggedDeployment } from \"../../../core/deployment/objects.js\";\nimport { elementService } from \"../../../core/c4/resolve/service.js\";\nimport { OBLIGATION_TAG_PREFIX, type ObligationVocabulary } from \"../../../core/obligations/obligations.js\";\nimport { servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { type ServiceEntry } from \"../../../core/repo/entries.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { coversEntries } from \"../checks/requirements.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport interface ObligationCheck {\n docsDir: DocsDir;\n vocabulary: ObligationVocabulary;\n /**\n * The living landscape, PARSED. Not nullable, and the absence is the point:\n * the landscape target returns before it reaches this call when the map is\n * missing or did not parse, so a run that could not read the map asks none of\n * the three questions below rather than answering them empty. \"Loam did not\n * look\" is never the same answer as \"there is nothing there\" — an unreadable\n * map graded as empty would report every declared obligation as applied\n * nowhere — and the suspension lives at the call site because that is where\n * the fact is known. It is also why the vocabulary half is a separate\n * function: those two verdicts must survive exactly those early returns.\n */\n land: LoadedDoc;\n services: ServiceEntry[];\n fleet: FleetContext;\n}\n\n/**\n * The half that does not depend on the map: the file reads, and its `adr:`\n * pointers resolve.\n *\n * Separate from the map questions because the landscape target returns early\n * twice — an absent map, and one that did not parse — and a broken vocabulary\n * must still be named in exactly the runs where somebody is fixing something\n * else. The same placement `permissionFindings` and `capabilityFleetFindings`\n * already have, one level of precision finer.\n */\nexport function obligationVocabularyFindings(docsDir: DocsDir, vocabulary: ObligationVocabulary): Finding[] {\n if (!vocabulary.present) return [];\n if (vocabulary.invalid !== undefined) {\n // Reported ALONE, exactly as `permissions.invalid` and `capability.invalid`\n // are: every `#obl-` tag in the fleet resolves against this file, so grading\n // them on top of a broken vocabulary is a cascade, not a diagnosis.\n return [\n {\n severity: \"error\",\n code: \"obligation.invalid\",\n message:\n `landscape: architecture/obligations.yaml does not read as a vocabulary — ${vocabulary.invalid}. ` +\n \"Every `#obl-` tag on the fleet map resolves against this file, so none of them can be graded until it parses. \" +\n \"The shape is `obligations: {<id>: {description, adr}}`, ids spelled with letters, digits, `_` and `-` only.\",\n },\n ];\n }\n\n return adrFindings(docsDir, [...vocabulary.byId.values()]);\n}\n\n/** The three questions that need the map, suspended entirely when it could not be read. */\nexport async function obligationFindings(check: ObligationCheck): Promise<Finding[]> {\n const { vocabulary } = check;\n if (!vocabulary.present || vocabulary.invalid !== undefined) return [];\n const findings: Finding[] = [];\n\n // Which objects carry which obligation. Elements and edges are collected\n // together because every question below treats them alike: an obligation\n // applies to a thing on the map, and whether that thing is a box or an arrow\n // changes only how the message spells it and how `Covers:` matches it.\n // The enumerated fleet rides in for the deployment half of the walk: an\n // instance deploys a CONTAINER more often than a whole system, and resolving\n // which service owns one needs the ancestor walk `serviceResolver` performs.\n // It is the same set `uncoveredFindings` resolves edge endpoints with, or the\n // message would name one service and the coverage check count another.\n const known = new Set(check.services.map((s) => s.id));\n const tagged = taggedObjects(check.land, known);\n const applied = new Set(tagged.map((t) => t.obligation));\n\n for (const id of [...applied].sort()) {\n if (vocabulary.byId.has(id)) continue;\n const close = closeIds(id, [...vocabulary.byId.keys()]);\n findings.push({\n severity: \"error\",\n code: \"obligation.unknown\",\n subject: id,\n message:\n `landscape: #${OBLIGATION_TAG_PREFIX}${id} tags ${count(tagged.filter((t) => t.obligation === id).length, \"object\")} on the fleet map, ` +\n `and architecture/obligations.yaml declares no obligation '${id}'` +\n (close.length > 0 ? ` — did you mean: ${close.join(\", \")}?` : \"\") +\n \". A tag that resolves to nothing reads exactly like a rule the fleet keeps: declare it, or fix the tag.\",\n details: tagged.filter((t) => t.obligation === id).map((t) => t.where),\n });\n }\n\n const unapplied = [...vocabulary.byId.keys()].filter((id) => !applied.has(id)).sort();\n if (unapplied.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"obligation.unapplied\",\n message:\n `landscape: ${unapplied.length} declared obligation(s) that no \\`#${OBLIGATION_TAG_PREFIX}\\` tag applies anywhere on the fleet map — ` +\n \"either a decision that was reversed and left its word behind, or one nobody has placed yet. \" +\n \"Tag the elements and edges it governs, or drop the declaration; a vocabulary is worth what cites it.\",\n details: unapplied,\n });\n }\n\n // ONLY A RESOLVED APPLICATION IS ASKED ABOUT COVERAGE. A tag naming no\n // declared obligation has already earned its error above, and \"no requirement\n // covers #obl-typo\" is not a second breach — it is the same one restated in\n // words that ask the reader to write a requirement for a rule that does not\n // exist. The use-case axis draws this line in the same place: only a resolved\n // `#cap-` claim keeps a promise.\n findings.push(...(await uncoveredFindings(check, tagged.filter((t) => vocabulary.byId.has(t.obligation)))));\n return findings;\n}\n\n/** One place an obligation is applied: which obligation, on what, and how a message names it. */\ninterface TaggedObject {\n obligation: string;\n /** The element, the edge, or the deployment object — exactly one is set. */\n element?: Elem;\n edge?: Rel;\n deployed?: TaggedDeployment;\n /**\n * How the finding spells this object, and how a `Covers:` line has to: an\n * element id, `source -> target`, or the `node:` forms.\n */\n where: string;\n /**\n * What the finding is filed under — the service that owns it, where one\n * does. ABSENT for a region, a datacenter or a cluster: nobody-s service\n * runs one, and naming a team that did not ask for the work is worse than\n * leaving the sentence general.\n */\n subject?: string;\n}\n\n/**\n * Every `#obl-` tag on the living map, one entry per (obligation, object) pair.\n *\n * A bare `#obl-` slugs to the empty string and is kept as an application on\n * purpose, exactly as a bare `#cap-` is kept as a claim: the prefix is the\n * author's opt-in however little follows it, and dropping it as unreserved\n * would leave an object that asked to be graded silently ungraded. It then\n * resolves to `obligation.unknown` in every fleet that did not declare an empty\n * id, which is the honest answer.\n */\nfunction taggedObjects(land: LoadedDoc, known: ReadonlySet<string>): TaggedObject[] {\n const out: TaggedObject[] = [];\n for (const element of land.elements) {\n for (const tag of element.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n element,\n where: element.id,\n subject: elementService(element),\n });\n }\n }\n for (const edge of land.relationships) {\n for (const tag of edge.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n edge,\n where: `${edge.source} -> ${edge.target}`,\n subject: edge.target,\n });\n }\n }\n // The topology, walked by the same rule and for the reason the rule exists: a\n // tag is a claim about the object it sits on, and which MODEL that object\n // lives in is not something a reader of the tag can see. Until this walk the\n // same undeclared tag was an error on a container and silence on a\n // datacenter — fail-open, and the case FEAT-1's ARCH-LOAM-DEPLOY-TAGGED was\n // written about. A fleet that draws no topology contributes nothing here.\n for (const d of taggedDeployment(land.deployment ?? NO_DEPLOYMENT, land.elements, known, OBLIGATION_TAG_PREFIX)) {\n out.push({\n obligation: d.obligation,\n deployed: d,\n where: d.where,\n ...(d.service === undefined ? {} : { subject: d.service }),\n });\n }\n return out;\n}\n\n/**\n * `obligation.uncovered` — a tagged object no living arch requirement covers.\n *\n * The index is the union of EVERY service's living `arch.spec.md` `Covers:`\n * lines, because an obligation is placed by the architect on the map and met by\n * whichever team owns the thing it sits on — and a per-service index would\n * report an edge as uncovered because the requirement that covers it lives in\n * the service at the other end. `coversEntries` already drops REMOVED\n * requirements: a retired requirement covers nothing.\n *\n * ONE FINDING PER TAGGED OBJECT, subject = the service that owns it, so a\n * fleet-wide report reads as a worklist per team rather than as one line naming\n * forty edges.\n */\nasync function uncoveredFindings(check: ObligationCheck, tagged: TaggedObject[]): Promise<Finding[]> {\n if (tagged.length === 0) return [];\n const covers = await livingCovers(check);\n const elements = check.land.elements;\n const known = new Set(check.services.map((s) => s.id));\n const covered = (t: TaggedObject): boolean => {\n if (t.element !== undefined) return covers.some((c) => coversElement(c, t.element!));\n if (t.edge !== undefined) return covers.some((c) => coversEdge(c, t.edge!, elements, known));\n const d = t.deployed!;\n return d.edge === undefined\n ? covers.some((c) => coversDeployNode(c, d.id!))\n : covers.some((c) => coversDeployEdge(c, d.edge!.source, d.edge!.target));\n };\n return tagged\n .filter((t) => !covered(t))\n .map((t) => ({\n severity: \"warn\" as const,\n code: \"obligation.uncovered\",\n ...(t.subject === undefined ? {} : { subject: t.subject }),\n message:\n `${t.where} carries #${OBLIGATION_TAG_PREFIX}${t.obligation} and no living arch requirement covers it — ` +\n `the obligation is placed and nothing says it is met. Write the requirement in ` +\n // A region, a datacenter or a cluster is owned by no service, so the\n // sentence stays general rather than naming a team at random. Every\n // other object here has a real owner and keeps the actionable form.\n `${t.subject === undefined ? \"the arch.spec.md of whichever service answers for this topology\" : `${t.subject}'s arch.spec.md`}` +\n ` with \\`Covers: ${t.where}\\` and a scenario that proves it, or drop the tag if the decision no longer applies here.`,\n }));\n}\n\n/**\n * Every `Covers:` entry the LIVING fleet writes, across both requirement\n * documents of every service.\n *\n * Existence is checked for both files and for the same reason\n * `permissionFindings` checks it: `arch.spec.md` is optional and most of a\n * legacy fleet has none, while `spec.md` is missing on a half-adopted service —\n * and `FleetContext.readRequirements` throws ENOENT, which surfaces as\n * `repository-unavailable` and takes the whole `--all` run down. Both axes are\n * read, not only the architecture one: `Covers:` is parsed everywhere for one\n * grammar's sake, and a fleet that wrote its outbox requirement in `spec.md`\n * has said the thing loam is asking about.\n */\nasync function livingCovers(check: ObligationCheck): Promise<CoversEntry[]> {\n const entries: CoversEntry[] = [];\n for (const service of check.services) {\n const paths = servicePathsAt(service.dir);\n for (const path of [paths.spec, paths.archSpec]) {\n if (!existsSync(path)) continue;\n entries.push(...coversEntries(await check.fleet.readRequirements(path)));\n }\n }\n return entries;\n}\n\n/**\n * `obligation.adr-missing` — an `adr:` path that names no file.\n *\n * The path is relative to the docs repo root rather than to\n * `architecture/obligations.yaml`, because that is how a reader of a fleet\n * document thinks about a fleet path, and because the value is a YAML field\n * rather than a markdown link — `link.unresolved` grades links written in prose,\n * and this one is written in a slot loam defined.\n */\nfunction adrFindings(docsDir: DocsDir, obligations: readonly { id: string; adr?: string }[]): Finding[] {\n return obligations\n .filter((o) => o.adr !== undefined && !existsSync(join(docsDir, o.adr)))\n .map((o) => ({\n severity: \"error\" as const,\n code: \"obligation.adr-missing\",\n subject: o.id,\n message:\n `landscape: obligation '${o.id}' names \\`adr: ${o.adr!}\\` and no such file exists — ` +\n \"the ADR is where the decision itself is written, so a pointer at nothing leaves every tag citing a rule nobody can read. \" +\n \"Fix the path (it is relative to the docs repo root), write the record, or drop the field.\",\n }));\n}\n\n/** `1 object` / `3 objects` — the plural spelled once, because three messages count things. */\nfunction count(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n"]}
@@ -29,8 +29,12 @@ export function printScorecard(card) {
29
29
  // because that is where it changes the reading: "120 vouched" and "120
30
30
  // vouched (100 sampled)" are different fleets, and a reader who stops at
31
31
  // the first number must not have been told the stronger of the two.
32
+ // The content count rides beside the digest count rather than inside the
33
+ // vouched one: both are staleness, of the words and of the code, and
34
+ // "0 stale digest(s)" alone read as "nothing is stale" in a run that had
35
+ // just warned `content.stale`.
32
36
  ` provenance ${p.vouched} vouched${p.sampledVouched > 0 ? ` (${p.sampledVouched} sampled)` : ""} — ` +
33
- `${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`);
37
+ `${p.contentStale} stale content · ${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`);
34
38
  // The ceiling is the features axis's `active` — the payload deliberately
35
39
  // carries that denominator once, so this line reads it from there.
36
40
  console.log(` verification ${f.active} feature(s) → ${v.recorded} recorded — ` +