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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"usecases.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/usecases/usecases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,+CAA+C,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAI/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAoB,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AA8C9D;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,WAAW,CAAC,KAA4B;IAC/C,OAAO,KAAK;SACT,MAAM,CAAC,SAAS,CAAC;SACjB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,KAAmB;IACjD,MAAM,OAAO,GAAgB;QAC3B,mEAAmE;QACnE,0EAA0E;QAC1E,8DAA8D;QAC9D,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE;QAC9F,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC;IACF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5C,4EAA4E;QAC5E,6EAA6E;QAC7E,sEAAsE;QACtE,gDAAgD;QAChD,EAAE;QACF,wEAAwE;QACxE,2EAA2E;QAC3E,wEAAwE;QACxE,0EAA0E;QAC1E,oEAAoE;QACpE,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;YACpE,QAAQ,CAAC,IAAI,CAAC,GAAG,qBAAqB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACtD,QAAQ,CAAC,IAAI,CAAC,GAAG,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;QAC/E,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * The use-case axis of `validate --all`: the `dynamic view`s a fleet declares,\n * graded as business flows against the model they are drawn over.\n *\n * ONLY VIEWS CARRYING A RESERVED TAG ARE GRADED, and that opt-in is what lets\n * this ship into a fleet that already has diagrams. An untagged `dynamic view`\n * is somebody's hand-drawn sequence — drawn to explain something to a person,\n * not to be joined to a model — and grading it would turn every existing docs\n * repo red on upgrade over documents nobody promised anything about. A `#cap-`\n * or `#req-` tag is the author saying \"this is a use case; hold it to the\n * model\", and nothing else says it: a view with no tags and a view whose tags\n * are all somebody's own vocabulary are the same thing here.\n *\n * THE TAG OPTS A VIEW IN WHETHER OR NOT IT RESOLVES. A `#cap-` slug that names\n * no declared capability is a broken claim about the vocabulary, not a withdrawn\n * one, so its steps are still graded — and the step grades read\n * `architecture/capabilities.yaml` nowhere at all. Coupling them would mean a\n * fleet that has not adopted the capability axis could never have a use case\n * checked, which inverts the adoption order the whole axis is built on: draw the\n * flow first, name the capability when the vocabulary exists.\n *\n * Everything here is LINEAR IN STEPS over documents `validate --all` has already\n * parsed, and nothing in it reads a file or throws — a fleet with no tagged view\n * produces exactly nothing. Linear in steps is not free per step: `attributeStep`\n * scans `relationships` once for the exact tier and again for the resolved one,\n * and `isActor` scans `elements`, so the real shape is O(steps × relationships)\n * plus O(steps × elements). That is why `StepGrading` is built once below rather\n * than per hop — the per-hop cost is a scan, so anything rebuilt per hop is paid\n * against every hop in the fleet.\n */\nimport { compareIds } from \"../../../../core/repo/entries.js\";\nimport { resolveCapabilityTags } from \"../../../../core/capabilities/usecase-join.js\";\nimport { isUseCase } from \"../../../../core/usecases/fleet.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 { capabilityTagFindings } from \"./capability-tag.js\";\nimport { requirementTagFindings } from \"./requirement-tag.js\";\nimport { stepFindings, type StepGrading } from \"./steps.js\";\nimport { viewFile } from \"../../../../core/usecases/place.js\";\n\n/** What the use-case grades run over: the views, the model they are drawn on, and the fleet. */\nexport interface UseCaseScope {\n /**\n * Every `dynamic view` the `architecture/` PROJECT declares, each carrying the\n * file it was written in.\n *\n * The caller must pass views that came from `loadProject` and no others. Only\n * that loader gives a view a `sourcePath` relative to `architecture/`; the\n * single-file loaders name every document after themselves (`source.c4`), and\n * a finding built from one of those would name a file that has never existed\n * — see `./place.ts`, which is where the path is spelled.\n */\n views: readonly ParsedView[];\n elements: Elem[];\n relationships: Rel[];\n /** The `services/<id>/` directories that exist. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver — the same one every other edge join uses. */\n resolve: (id: string) => string;\n /**\n * The declared capability ids, or `null` when `architecture/capabilities.yaml`\n * is absent or does not read as a vocabulary.\n *\n * `null` and `[]` are deliberately different answers, and the join at\n * `core/capabilities/usecase-join.ts` says why it cannot tell them apart\n * itself: an empty list means \"the fleet declares no capabilities\", which is a\n * real verdict every tag then fails against, while `null` means \"there is no\n * vocabulary to grade against\" and suspends the tag grade entirely. The\n * caller applies the ladder because the caller is the one that read the file.\n */\n capabilities: readonly string[] | null;\n /**\n * The requirement ids one capability's document declares, or `undefined` for a\n * capability with no document — the scope a `#req-` tag is resolved in.\n *\n * A function rather than the map itself, so this package never learns what a\n * capability document is: the fleet target reads them once\n * (`capabilityRequirementIndex`) and hands the lookup over. `undefined` and an\n * empty set are different answers here, exactly as they are one join over —\n * no document at all, against a document declaring no promises yet.\n */\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined;\n}\n\n/**\n * The views that opted in, in a stable order.\n *\n * The opt-in itself is `isUseCase` in `core/usecases/fleet.ts` and is not\n * restated here. It used to be — the same `startsWith` pair, spelled a second\n * time — and the two spellings drifted: this one took either reserved prefix\n * while the reader-side one took `#cap-` alone, so `validate` graded a\n * `#req-`-only flow that `loam diff`, `delta`, `status`, the packs and `list\n * capabilities` could not see. What is left here is the ORDER, which is this\n * package's own concern.\n *\n * Sorted by (file, view id) rather than reported in LikeC4's own record order:\n * nothing in loam has measured that the parse preserves declaration order, so a\n * report whose row order depends on that would reorder under a dependency bump\n * — the diff-stability rule `core/capabilities/rollup.ts` states for its rows,\n * applied to findings.\n */\nfunction gradedViews(views: readonly ParsedView[]): ParsedView[] {\n return views\n .filter(isUseCase)\n .sort((a, b) => compareIds(viewFile(a), viewFile(b)) || compareIds(a.id, b.id));\n}\n\n/**\n * Every use-case finding a fleet's declared views earn.\n *\n * The grading record is built once and shared across every step of every view:\n * `attributeStep` caches its element→service resolver on the identity of the\n * elements array and the fleet set, so handing it one `StepScope` for the whole\n * run is what keeps the fleet's use cases from rebuilding that id map per hop.\n */\nexport function useCaseFindings(scope: UseCaseScope): Finding[] {\n const grading: StepGrading = {\n // `known` and `services` come from ONE value, so the fleet set the\n // attribution resolves against and the fleet set the provider guard tests\n // cannot drift apart into two answers about the same element.\n model: { elements: scope.elements, relationships: scope.relationships, known: scope.services },\n services: scope.services,\n resolve: scope.resolve,\n };\n const findings: Finding[] = [];\n for (const view of gradedViews(scope.views)) {\n // ONE resolution of this view's capability tags, shared by both grades. The\n // `#req-` tag is scoped by exactly the capabilities the `#cap-` tags resolve\n // to, so resolving twice would be two chances to disagree about which\n // document a `Requirement-ID` is unique inside.\n //\n // THE LADDER IS THE `null`, NOT AN EMPTY LIST, and the distinction is a\n // wrong answer away: a suspended vocabulary handed on as `[]` reads to the\n // requirement grade as \"this view resolves no capability\", which is its\n // `unscoped` ERROR — so every `#req-` tag in a fleet that has not adopted\n // the capability axis would be convicted for the axis not existing.\n if (scope.capabilities !== null) {\n const claims = resolveCapabilityTags(view.tags, scope.capabilities);\n findings.push(...capabilityTagFindings(view, claims));\n findings.push(...requirementTagFindings(view, claims, scope.requirementsOf));\n }\n for (const step of view.steps) findings.push(...stepFindings({ view, step }, grading));\n }\n return findings;\n}\n"]}
1
+ {"version":3,"file":"usecases.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/usecases/usecases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,+CAA+C,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAI/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAoB,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AA8C9D;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,WAAW,CAAC,KAA4B;IAC/C,OAAO,KAAK;SACT,MAAM,CAAC,SAAS,CAAC;SACjB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,KAAmB;IACjD,MAAM,OAAO,GAAgB;QAC3B,mEAAmE;QACnE,0EAA0E;QAC1E,8DAA8D;QAC9D,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE;QAC9F,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC;IACF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5C,4EAA4E;QAC5E,6EAA6E;QAC7E,sEAAsE;QACtE,gDAAgD;QAChD,EAAE;QACF,wEAAwE;QACxE,2EAA2E;QAC3E,wEAAwE;QACxE,0EAA0E;QAC1E,oEAAoE;QACpE,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;YACpE,QAAQ,CAAC,IAAI,CAAC,GAAG,qBAAqB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACtD,QAAQ,CAAC,IAAI,CAAC,GAAG,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;QAC/E,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * The use-case axis of `validate --all`: the `dynamic view`s a fleet declares,\n * graded as business flows against the model they are drawn over.\n *\n * ONLY VIEWS CARRYING A RESERVED TAG ARE GRADED, and that opt-in is what lets\n * this ship into a fleet that already has diagrams. An untagged `dynamic view`\n * is somebody's hand-drawn sequence — drawn to explain something to a person,\n * not to be joined to a model — and grading it would turn every existing docs\n * repo red on upgrade over documents nobody promised anything about. A `#cap-`,\n * `#req-` or `#suite-` tag is the author saying \"this is a use case; hold it to\n * the model\", and nothing else says it: a view with no tags and a view whose\n * tags are all somebody's own vocabulary are the same thing here. A\n * `#suite-`-only view resolves nothing below — it carries no claim — and its\n * hops are graded like any other's.\n *\n * THE TAG OPTS A VIEW IN WHETHER OR NOT IT RESOLVES. A `#cap-` slug that names\n * no declared capability is a broken claim about the vocabulary, not a withdrawn\n * one, so its steps are still graded — and the step grades read\n * `architecture/capabilities.yaml` nowhere at all. Coupling them would mean a\n * fleet that has not adopted the capability axis could never have a use case\n * checked, which inverts the adoption order the whole axis is built on: draw the\n * flow first, name the capability when the vocabulary exists.\n *\n * Everything here is LINEAR IN STEPS over documents `validate --all` has already\n * parsed, and nothing in it reads a file or throws — a fleet with no tagged view\n * produces exactly nothing. Linear in steps is not free per step: `attributeStep`\n * scans `relationships` once for the exact tier and again for the resolved one,\n * and `isActor` scans `elements`, so the real shape is O(steps × relationships)\n * plus O(steps × elements). That is why `StepGrading` is built once below rather\n * than per hop — the per-hop cost is a scan, so anything rebuilt per hop is paid\n * against every hop in the fleet.\n */\nimport { compareIds } from \"../../../../core/repo/entries.js\";\nimport { resolveCapabilityTags } from \"../../../../core/capabilities/usecase-join.js\";\nimport { isUseCase } from \"../../../../core/usecases/fleet.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 { capabilityTagFindings } from \"./capability-tag.js\";\nimport { requirementTagFindings } from \"./requirement-tag.js\";\nimport { stepFindings, type StepGrading } from \"./steps.js\";\nimport { viewFile } from \"../../../../core/usecases/place.js\";\n\n/** What the use-case grades run over: the views, the model they are drawn on, and the fleet. */\nexport interface UseCaseScope {\n /**\n * Every `dynamic view` the `architecture/` PROJECT declares, each carrying the\n * file it was written in.\n *\n * The caller must pass views that came from `loadProject` and no others. Only\n * that loader gives a view a `sourcePath` relative to `architecture/`; the\n * single-file loaders name every document after themselves (`source.c4`), and\n * a finding built from one of those would name a file that has never existed\n * — see `./place.ts`, which is where the path is spelled.\n */\n views: readonly ParsedView[];\n elements: Elem[];\n relationships: Rel[];\n /** The `services/<id>/` directories that exist. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver — the same one every other edge join uses. */\n resolve: (id: string) => string;\n /**\n * The declared capability ids, or `null` when `architecture/capabilities.yaml`\n * is absent or does not read as a vocabulary.\n *\n * `null` and `[]` are deliberately different answers, and the join at\n * `core/capabilities/usecase-join.ts` says why it cannot tell them apart\n * itself: an empty list means \"the fleet declares no capabilities\", which is a\n * real verdict every tag then fails against, while `null` means \"there is no\n * vocabulary to grade against\" and suspends the tag grade entirely. The\n * caller applies the ladder because the caller is the one that read the file.\n */\n capabilities: readonly string[] | null;\n /**\n * The requirement ids one capability's document declares, or `undefined` for a\n * capability with no document — the scope a `#req-` tag is resolved in.\n *\n * A function rather than the map itself, so this package never learns what a\n * capability document is: the fleet target reads them once\n * (`capabilityRequirementIndex`) and hands the lookup over. `undefined` and an\n * empty set are different answers here, exactly as they are one join over —\n * no document at all, against a document declaring no promises yet.\n */\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined;\n}\n\n/**\n * The views that opted in, in a stable order.\n *\n * The opt-in itself is `isUseCase` in `core/usecases/fleet.ts` and is not\n * restated here. It used to be — the same `startsWith` pair, spelled a second\n * time — and the two spellings drifted: this one took either reserved prefix\n * while the reader-side one took `#cap-` alone, so `validate` graded a\n * `#req-`-only flow that `loam diff`, `delta`, `status`, the packs and `list\n * capabilities` could not see. What is left here is the ORDER, which is this\n * package's own concern.\n *\n * Sorted by (file, view id) rather than reported in LikeC4's own record order:\n * nothing in loam has measured that the parse preserves declaration order, so a\n * report whose row order depends on that would reorder under a dependency bump\n * — the diff-stability rule `core/capabilities/rollup.ts` states for its rows,\n * applied to findings.\n */\nfunction gradedViews(views: readonly ParsedView[]): ParsedView[] {\n return views\n .filter(isUseCase)\n .sort((a, b) => compareIds(viewFile(a), viewFile(b)) || compareIds(a.id, b.id));\n}\n\n/**\n * Every use-case finding a fleet's declared views earn.\n *\n * The grading record is built once and shared across every step of every view:\n * `attributeStep` caches its element→service resolver on the identity of the\n * elements array and the fleet set, so handing it one `StepScope` for the whole\n * run is what keeps the fleet's use cases from rebuilding that id map per hop.\n */\nexport function useCaseFindings(scope: UseCaseScope): Finding[] {\n const grading: StepGrading = {\n // `known` and `services` come from ONE value, so the fleet set the\n // attribution resolves against and the fleet set the provider guard tests\n // cannot drift apart into two answers about the same element.\n model: { elements: scope.elements, relationships: scope.relationships, known: scope.services },\n services: scope.services,\n resolve: scope.resolve,\n };\n const findings: Finding[] = [];\n for (const view of gradedViews(scope.views)) {\n // ONE resolution of this view's capability tags, shared by both grades. The\n // `#req-` tag is scoped by exactly the capabilities the `#cap-` tags resolve\n // to, so resolving twice would be two chances to disagree about which\n // document a `Requirement-ID` is unique inside.\n //\n // THE LADDER IS THE `null`, NOT AN EMPTY LIST, and the distinction is a\n // wrong answer away: a suspended vocabulary handed on as `[]` reads to the\n // requirement grade as \"this view resolves no capability\", which is its\n // `unscoped` ERROR — so every `#req-` tag in a fleet that has not adopted\n // the capability axis would be convicted for the axis not existing.\n if (scope.capabilities !== null) {\n const claims = resolveCapabilityTags(view.tags, scope.capabilities);\n findings.push(...capabilityTagFindings(view, claims));\n findings.push(...requirementTagFindings(view, claims, scope.requirementsOf));\n }\n for (const step of view.steps) findings.push(...stepFindings({ view, step }, grading));\n }\n return findings;\n}\n"]}
@@ -118,7 +118,7 @@ export async function fleetProjectFindings(check) {
118
118
  details: [file === null ? errorText(err) : `${file}: ${errorText(err)}`],
119
119
  message: `fleet project: ${file ?? "the merged project"}: ${errorText(err)} — each document reads clean where ` +
120
120
  "loam grades it, but the renderer merges every `.likec4` the root project reads except the generated " +
121
- "subsystems.likec4 — the map, every extending model and every `.likec4` beside one — into ONE project, " +
121
+ "subsystems.likec4 and services.likec4 — the map, every extending model and every `.likec4` beside one — into ONE project, " +
122
122
  `and that project does not parse. ${remedy(err.message)}`,
123
123
  });
124
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fleet-project.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/fleet-project.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `c4.fleet-project-invalid` — the one thing no per-document grade can see:\n * every document loam reads is clean, and the project the RENDERER builds out of\n * them is not.\n *\n * loam grades a fleet document by document, and that is deliberate: a service's\n * model is graded on its own merits, and one broken model must not blank the\n * fleet. The renderer has no such rule. Point `npx likec4 start` at the docs\n * root and it merges every `.likec4` the root project reads into ONE model — the\n * map, every extending model, and every `.likec4` sitting beside one — so a\n * declaration made in two of those documents is a duplicate there and nowhere\n * else. Measured at the 1.59.2 pin: a tags-only `specification { tag req-X }` in\n * an extending model is legal, and the SAME tag declared in a second extending\n * model is a duplicate blamed on both files. Two services, two clean grades, and\n * a renderer that draws nothing.\n *\n * So this is one extra load, and it is the renderer's reading rather than\n * loam's. What it reports is the SET DIFFERENCE: an error already reported where\n * loam grades that document — the map's own parse, a per-service project's\n * `c4.invalid`, a service flow project's use-case failure — is not repeated\n * here, because the fix is one file either way and N copies of one cascade is\n * the report. What is left over is exactly the class above: errors that exist\n * only because these documents were merged.\n *\n * WHICH DOCUMENTS ARE IN IT is `./root-documents.ts`'s question, and it is a\n * walk of the docs root rather than a list of the shapes loam knows about: a\n * `.likec4` in a SUBSYSTEM directory — the parent of service directories — was\n * in none of the three roots this used to merge, and went unreported while the\n * renderer refused the whole project over it (verification 2026-09-04, R2). That\n * module also carries why the generated `architecture/subsystems.likec4`,\n * every standalone service directory and `features/` stay out.\n *\n * `--all` ONLY, and only when at least one model extends the map, the map itself\n * parses, and the root `exclude` does not hide it. A single-target run has not\n * enumerated the fleet, so it cannot build the project at all; a fleet whose\n * models all stand alone has no such project (every one of them is excluded from\n * the root); and a map that does not parse would report its own cascade here\n * under a second code while `landscape.invalid` already names the file.\n *\n * THE LAST OF THOSE GATES IS THE CASCADE RULE, the one `permissions.invalid` and\n * `capability.invalid` already state: a grade that resolves against a\n * document nobody loaded is not a diagnosis. `architectureProjectDocuments`\n * keeps the map whatever the root `exclude` says — that floor is what stopped\n * loam asserting a fleet's map declares nothing — but this load has no such\n * floor ON PURPOSE, because its whole claim is to read what the renderer reads.\n * So an entry covering `architecture/landscape.likec4` leaves every extending\n * model in the project with its `extend <fqn>` resolving against nothing:\n * measured on `examples/docs` with `architecture/*.likec4` in the list, one\n * authored line came back as 161 warnings under this code. `landscape.excluded`\n * (`./projects.ts`) is the one finding that names the cause, and it says the\n * fleet project is not graded until the entry goes. The fact arrives as\n * `mapExcluded` rather than being re-read here, so the two can never disagree\n * about which state they are in.\n *\n * WHY A WARNING. Nothing loam concludes changes: every model still parses where\n * loam reads it, every binding still resolves, the spine is unaffected. What is\n * broken is the picture, which is what `service.likec4-config-missing` and its\n * three siblings in `./projects.ts` also grade, at the same severity.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative, resolve, sep } from \"node:path\";\nimport { errorText, type LikeC4Error, type LoadedDoc } from \"../../../../core/c4/likec4.js\";\nimport { asLoadedDoc, loadProject } from \"../../../../core/c4/project/load.js\";\nimport type { ViewIdClaim } from \"../../../../core/c4/parsed/view-ids.js\";\nimport type { ModelShape } from \"../../../../core/c4/service-model/shape.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, type ServicePaths } from \"../../../../core/repo/paths.js\";\nimport type { FleetTree } from \"../../../../core/repo/tree/walk.js\";\nimport { readServiceFlows } from \"../../../../core/usecases/service/flows.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\nimport { rootProjectDocuments } from \"./root-documents.js\";\n\nexport interface FleetProjectCheck {\n docsDir: DocsDir;\n /** The FULL enumeration — the project the renderer builds holds every model, not a narrowed run's subset. */\n entries: readonly ServiceEntry[];\n /** Each model's shape, keyed by resolved path (`FleetContext.modelShapes`). */\n shapes: ReadonlyMap<string, ModelShape>;\n /** The run's read index: the per-service projects it already parsed are half of the set difference. */\n fleet: FleetContext;\n /** The map parsed ALONE — the gate, and the other half of the difference. */\n architecture: LoadedDoc;\n /**\n * Whether the root `exclude` hides the map itself — `landscape.excluded`'s own\n * condition, computed once in `./projects.ts` and carried here rather than\n * re-derived. It is a GATE, for the reason the banner's cascade-rule paragraph\n * gives: this load has no floor under the map, so the entry that hides it\n * turns one authored line into an unresolved reference in every extending\n * model.\n */\n mapExcluded: boolean;\n /** The walked tree, for the view-id census this load makes answerable. */\n tree: FleetTree;\n /** Every service directory that exists — the flow scan's resolver evidence. */\n known: ReadonlySet<string>;\n}\n\n/**\n * What the merged load found, as three answers rather than one list — because\n * the caller has a second question to ask of it and the answers are mutually\n * exclusive.\n *\n * `skipped` is \"there is no such project, or loam declines to read one\": no model\n * extends the map, the map does not parse, the root `exclude` hides the map\n * (`landscape.excluded` names that entry and this grade stands down behind it),\n * or the run has no fleet index. `clean` carries the view-id\n * CENSUS, which is the only thing a caller may take from a project that parsed —\n * and the reason this is not just an empty finding list: an empty list and a\n * clean census are the same thing to `.length`, and one of them licenses\n * `subsystem.view-id-collision` to be graded over every extending model's\n * authored ids while the other does not.\n */\nexport type FleetProjectGrade =\n | { kind: \"skipped\" }\n | { kind: \"invalid\"; findings: Finding[] }\n | { kind: \"clean\"; viewIds: ViewIdClaim[] };\n\nexport async function fleetProjectFindings(check: FleetProjectCheck): Promise<FleetProjectGrade> {\n // A map that did not parse is `landscape.invalid`'s, and this load would only\n // repeat its cascade under a second code.\n if (check.architecture.errors.length > 0) return { kind: \"skipped\" };\n // A map the root `exclude` hides is `landscape.excluded`'s, for the same\n // reason one line up — see the banner's cascade-rule paragraph. The view-id\n // census goes with it: `clean` is a claim about a project that parsed, and\n // there is none.\n if (check.mapExcluded) return { kind: \"skipped\" };\n const shapeOf = (entry: ServiceEntry): ModelShape | null => {\n const model = servicePathsAt(entry.dir).model;\n return existsSync(model) ? (check.shapes.get(resolve(model)) ?? null) : null;\n };\n const extending = check.entries.filter((entry) => shapeOf(entry) === \"extending\");\n if (extending.length === 0) return { kind: \"skipped\" };\n\n const paths = extending.map((entry) => servicePathsAt(entry.dir));\n // The TREE, not three roots. `./root-documents.ts` carries what comes out of\n // it and why; the standalone directories are handed in because their shape is\n // this run's memo and re-reading every model here would be a second scanner.\n const documents = await rootProjectDocuments(\n check.docsDir,\n check.entries.filter((entry) => shapeOf(entry) === \"standalone\").map((entry) => entry.dir),\n );\n const project = await loadProject(check.docsDir, documents);\n\n if (project.clean) {\n // The census `subsystem.view-id-collision` needs and could not have until\n // now: an extending model's authored view ids live in the SAME flat\n // namespace as the map's, because both documents are in this project. The\n // claims arrive docs-relative, which is the spelling `viewIdFindings` takes.\n return { kind: \"clean\", viewIds: project.viewIds };\n }\n\n const reported = await alreadyReported(check, paths);\n const findings: Finding[] = [];\n for (const err of asLoadedDoc(project).errors) {\n if (reported.has(errorKey(err))) continue;\n const owner = extending.find(\n (entry) => err.sourceFsPath !== undefined && err.sourceFsPath.startsWith(`${entry.dir}${sep}`),\n );\n const file =\n err.sourceFsPath === undefined ? null : relative(check.docsDir, err.sourceFsPath).split(/[\\\\/]/).join(\"/\");\n findings.push({\n severity: \"warn\",\n code: \"c4.fleet-project-invalid\",\n ...(owner === undefined ? {} : { subject: owner.id }),\n // The file the error is IN, never the map by default: `locations[0].path`\n // was `architecture/landscape.likec4` on every one of these, so a reader\n // acting on the payload opened a document that was not the one at fault\n // (verification 2026-09-04, D10). Absent when LikeC4 declined to attribute\n // the error to a document at all — a location loam cannot spell is left\n // unspelled rather than guessed.\n ...(file === null ? {} : { locations: [{ path: file, role: \"primary\" as const }] }),\n details: [file === null ? errorText(err) : `${file}: ${errorText(err)}`],\n message:\n `fleet project: ${file ?? \"the merged project\"}: ${errorText(err)} — each document reads clean where ` +\n \"loam grades it, but the renderer merges every `.likec4` the root project reads except the generated \" +\n \"subsystems.likec4 — the map, every extending model and every `.likec4` beside one — into ONE project, \" +\n `and that project does not parse. ${remedy(err.message)}`,\n });\n }\n return { kind: \"invalid\", findings };\n}\n\n/**\n * The tail that names WHICH failure this is.\n *\n * It used to say \"a tag or an element declared in two of those documents is\n * declared twice there\" for every error, including the ones that are not a\n * double declaration at all (verification 2026-09-04, D10) — an unresolved\n * reference sent its reader hunting for a second declaration that does not\n * exist. LikeC4's own wording is the evidence, matched loosely on purpose: an\n * unrecognised message falls to the third arm, which asserts nothing beyond\n * where to look.\n */\nfunction remedy(message: string): string {\n if (/duplicat|already (declared|defined)|declared (more than once|twice)/i.test(message)) {\n return (\n \"A tag or an element declared in two of those documents is declared twice there; declare it once \" +\n \"(the map, or the one service that owns it)\"\n );\n }\n if (/could not resolve|unresolved|unknown/i.test(message)) {\n return (\n \"A name resolves against ALL of those documents here, so a reference the renderer cannot place is one \" +\n \"no document in the project declares; declare it where the reader can see it, or drop the reference\"\n );\n }\n return \"Open the file and line named above — every reader who points a renderer at the docs root sees this\";\n}\n\n/**\n * Every parse error this run has ALREADY reported against one of these\n * documents, keyed so the merged project's copy can be recognised.\n *\n * Two sources, and both are loads the run made anyway or would make: each\n * extending model's own per-service project (`c4.invalid` on the service\n * target) and, where nothing was wrong there, each service's FLOW project\n * (`readServiceFlows` — the use-case grade names the file). The flow read goes\n * through that function rather than a second `loadProject` here so the OPT-IN\n * gate is the same one: a broken sibling that nothing tagged is loaded by\n * neither, which is exactly why the merged project has to report it.\n *\n * Computed only when the merged project actually failed, which is why it is a\n * function rather than an input: on a healthy fleet this whole file costs one\n * load and nothing else.\n *\n * THE FLOW PROJECTS ARE PARSED AGAIN by `validateService` later in the same run,\n * and that is accepted rather than unnoticed: it happens only on the path where\n * the merged project already failed, so a healthy fleet pays nothing, and the\n * `ServiceModel` half above IS the run's memo. If that path ever becomes hot,\n * the follow-up is to memoise the flow scan on `FleetContext` beside the\n * per-service projects — not to hand the scan in here, which would make this\n * grade depend on an argument the caller has no reason to have loaded.\n */\nasync function alreadyReported(\n check: FleetProjectCheck,\n paths: readonly ServicePaths[],\n): Promise<ReadonlySet<string>> {\n // Both sets reach the same documents through `architectureProjectDocuments`,\n // which now drops what the root `exclude` covers exactly as the merged load's\n // own walk does — so a document out of one is out of both, and the generated\n // `subsystems.likec4` is out of all of them without being remembered twice.\n //\n // Pooled rather than awaited one at a time: each entry is an independent\n // LikeC4 workspace over one service's own documents, which is exactly the\n // work `inOrder` rations — and a serial loop here made the ONE path that\n // already costs a fleet its whole set of flow projects pay for them in\n // sequence.\n const perService = await inOrder(paths, async (service) => {\n const model = await check.fleet.serviceModel(check.docsDir, service);\n const errors = [...model.doc.errors];\n if (errors.length > 0 || model.mapUnreadable) return errors;\n // `model.project` is non-null here by construction — these are extending\n // models whose own load came back clean, which is the arm that carries it —\n // and the guard is the compiler's rather than an assertion: a null would\n // mean grading a flow against a document that did not load.\n if (model.project === null) return [];\n const flows = await readServiceFlows({\n paths: service,\n model: model.doc,\n known: check.known,\n extending: { docsDir: check.docsDir, project: model.project },\n });\n return flows.kind === \"unreadable\" ? flows.errors : [];\n });\n return new Set(perService.flat().map(errorKey));\n}\n\n/**\n * One error's identity across two loads of overlapping document sets: the real\n * file, the line, and the message.\n *\n * There is no id to join on — LikeC4 mints diagnostics per parse — and the\n * message has to be part of the key: two DIFFERENT problems on one line of one\n * file are two errors, and collapsing them would let a merged-project-only\n * failure hide behind a per-service one that happens to share a line.\n */\nfunction errorKey(err: LikeC4Error): string {\n return JSON.stringify([err.sourceFsPath ?? \"\", err.line ?? -1, err.message]);\n}\n"]}
1
+ {"version":3,"file":"fleet-project.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/fleet-project.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `c4.fleet-project-invalid` — the one thing no per-document grade can see:\n * every document loam reads is clean, and the project the RENDERER builds out of\n * them is not.\n *\n * loam grades a fleet document by document, and that is deliberate: a service's\n * model is graded on its own merits, and one broken model must not blank the\n * fleet. The renderer has no such rule. Point `npx likec4 start` at the docs\n * root and it merges every `.likec4` the root project reads into ONE model — the\n * map, every extending model, and every `.likec4` sitting beside one — so a\n * declaration made in two of those documents is a duplicate there and nowhere\n * else. Measured at the 1.59.2 pin: a tags-only `specification { tag req-X }` in\n * an extending model is legal, and the SAME tag declared in a second extending\n * model is a duplicate blamed on both files. Two services, two clean grades, and\n * a renderer that draws nothing.\n *\n * So this is one extra load, and it is the renderer's reading rather than\n * loam's. What it reports is the SET DIFFERENCE: an error already reported where\n * loam grades that document — the map's own parse, a per-service project's\n * `c4.invalid`, a service flow project's use-case failure — is not repeated\n * here, because the fix is one file either way and N copies of one cascade is\n * the report. What is left over is exactly the class above: errors that exist\n * only because these documents were merged.\n *\n * WHICH DOCUMENTS ARE IN IT is `./root-documents.ts`'s question, and it is a\n * walk of the docs root rather than a list of the shapes loam knows about: a\n * `.likec4` in a SUBSYSTEM directory — the parent of service directories — was\n * in none of the three roots this used to merge, and went unreported while the\n * renderer refused the whole project over it (verification 2026-09-04, R2). That\n * module also carries why the generated `architecture/subsystems.likec4`,\n * every standalone service directory and `features/` stay out.\n *\n * `--all` ONLY, and only when at least one model extends the map, the map itself\n * parses, and the root `exclude` does not hide it. A single-target run has not\n * enumerated the fleet, so it cannot build the project at all; a fleet whose\n * models all stand alone has no such project (every one of them is excluded from\n * the root); and a map that does not parse would report its own cascade here\n * under a second code while `landscape.invalid` already names the file.\n *\n * THE LAST OF THOSE GATES IS THE CASCADE RULE, the one `permissions.invalid` and\n * `capability.invalid` already state: a grade that resolves against a\n * document nobody loaded is not a diagnosis. `architectureProjectDocuments`\n * keeps the map whatever the root `exclude` says — that floor is what stopped\n * loam asserting a fleet's map declares nothing — but this load has no such\n * floor ON PURPOSE, because its whole claim is to read what the renderer reads.\n * So an entry covering `architecture/landscape.likec4` leaves every extending\n * model in the project with its `extend <fqn>` resolving against nothing:\n * measured on `examples/docs` with `architecture/*.likec4` in the list, one\n * authored line came back as 161 warnings under this code. `landscape.excluded`\n * (`./projects.ts`) is the one finding that names the cause, and it says the\n * fleet project is not graded until the entry goes. The fact arrives as\n * `mapExcluded` rather than being re-read here, so the two can never disagree\n * about which state they are in.\n *\n * WHY A WARNING. Nothing loam concludes changes: every model still parses where\n * loam reads it, every binding still resolves, the spine is unaffected. What is\n * broken is the picture, which is what `service.likec4-config-missing` and its\n * three siblings in `./projects.ts` also grade, at the same severity.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative, resolve, sep } from \"node:path\";\nimport { errorText, type LikeC4Error, type LoadedDoc } from \"../../../../core/c4/likec4.js\";\nimport { asLoadedDoc, loadProject } from \"../../../../core/c4/project/load.js\";\nimport type { ViewIdClaim } from \"../../../../core/c4/parsed/view-ids.js\";\nimport type { ModelShape } from \"../../../../core/c4/service-model/shape.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, type ServicePaths } from \"../../../../core/repo/paths.js\";\nimport type { FleetTree } from \"../../../../core/repo/tree/walk.js\";\nimport { readServiceFlows } from \"../../../../core/usecases/service/flows.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\nimport { rootProjectDocuments } from \"./root-documents.js\";\n\nexport interface FleetProjectCheck {\n docsDir: DocsDir;\n /** The FULL enumeration — the project the renderer builds holds every model, not a narrowed run's subset. */\n entries: readonly ServiceEntry[];\n /** Each model's shape, keyed by resolved path (`FleetContext.modelShapes`). */\n shapes: ReadonlyMap<string, ModelShape>;\n /** The run's read index: the per-service projects it already parsed are half of the set difference. */\n fleet: FleetContext;\n /** The map parsed ALONE — the gate, and the other half of the difference. */\n architecture: LoadedDoc;\n /**\n * Whether the root `exclude` hides the map itself — `landscape.excluded`'s own\n * condition, computed once in `./projects.ts` and carried here rather than\n * re-derived. It is a GATE, for the reason the banner's cascade-rule paragraph\n * gives: this load has no floor under the map, so the entry that hides it\n * turns one authored line into an unresolved reference in every extending\n * model.\n */\n mapExcluded: boolean;\n /** The walked tree, for the view-id census this load makes answerable. */\n tree: FleetTree;\n /** Every service directory that exists — the flow scan's resolver evidence. */\n known: ReadonlySet<string>;\n}\n\n/**\n * What the merged load found, as three answers rather than one list — because\n * the caller has a second question to ask of it and the answers are mutually\n * exclusive.\n *\n * `skipped` is \"there is no such project, or loam declines to read one\": no model\n * extends the map, the map does not parse, or the root `exclude` hides the map\n * (`landscape.excluded` names that entry and this grade stands down behind it).\n * `clean` carries the view-id\n * CENSUS, which is the only thing a caller may take from a project that parsed —\n * and the reason this is not just an empty finding list: an empty list and a\n * clean census are the same thing to `.length`, and one of them licenses\n * `subsystem.view-id-collision` to be graded over every extending model's\n * authored ids while the other does not.\n */\nexport type FleetProjectGrade =\n | { kind: \"skipped\" }\n | { kind: \"invalid\"; findings: Finding[] }\n | { kind: \"clean\"; viewIds: ViewIdClaim[] };\n\nexport async function fleetProjectFindings(check: FleetProjectCheck): Promise<FleetProjectGrade> {\n // A map that did not parse is `landscape.invalid`'s, and this load would only\n // repeat its cascade under a second code.\n if (check.architecture.errors.length > 0) return { kind: \"skipped\" };\n // A map the root `exclude` hides is `landscape.excluded`'s, for the same\n // reason one line up — see the banner's cascade-rule paragraph. The view-id\n // census goes with it: `clean` is a claim about a project that parsed, and\n // there is none.\n if (check.mapExcluded) return { kind: \"skipped\" };\n const shapeOf = (entry: ServiceEntry): ModelShape | null => {\n const model = servicePathsAt(entry.dir).model;\n return existsSync(model) ? (check.shapes.get(resolve(model)) ?? null) : null;\n };\n const extending = check.entries.filter((entry) => shapeOf(entry) === \"extending\");\n if (extending.length === 0) return { kind: \"skipped\" };\n\n const paths = extending.map((entry) => servicePathsAt(entry.dir));\n // The TREE, not three roots. `./root-documents.ts` carries what comes out of\n // it and why; the standalone directories are handed in because their shape is\n // this run's memo and re-reading every model here would be a second scanner.\n const documents = await rootProjectDocuments(\n check.docsDir,\n check.entries.filter((entry) => shapeOf(entry) === \"standalone\").map((entry) => entry.dir),\n );\n const project = await loadProject(check.docsDir, documents);\n\n if (project.clean) {\n // The census `subsystem.view-id-collision` needs and could not have until\n // now: an extending model's authored view ids live in the SAME flat\n // namespace as the map's, because both documents are in this project. The\n // claims arrive docs-relative, which is the spelling `viewIdFindings` takes.\n return { kind: \"clean\", viewIds: project.viewIds };\n }\n\n const reported = await alreadyReported(check, paths);\n const findings: Finding[] = [];\n for (const err of asLoadedDoc(project).errors) {\n if (reported.has(errorKey(err))) continue;\n const owner = extending.find(\n (entry) => err.sourceFsPath !== undefined && err.sourceFsPath.startsWith(`${entry.dir}${sep}`),\n );\n const file =\n err.sourceFsPath === undefined ? null : relative(check.docsDir, err.sourceFsPath).split(/[\\\\/]/).join(\"/\");\n findings.push({\n severity: \"warn\",\n code: \"c4.fleet-project-invalid\",\n ...(owner === undefined ? {} : { subject: owner.id }),\n // The file the error is IN, never the map by default: `locations[0].path`\n // was `architecture/landscape.likec4` on every one of these, so a reader\n // acting on the payload opened a document that was not the one at fault\n // (verification 2026-09-04, D10). Absent when LikeC4 declined to attribute\n // the error to a document at all — a location loam cannot spell is left\n // unspelled rather than guessed.\n ...(file === null ? {} : { locations: [{ path: file, role: \"primary\" as const }] }),\n details: [file === null ? errorText(err) : `${file}: ${errorText(err)}`],\n message:\n `fleet project: ${file ?? \"the merged project\"}: ${errorText(err)} — each document reads clean where ` +\n \"loam grades it, but the renderer merges every `.likec4` the root project reads except the generated \" +\n \"subsystems.likec4 and services.likec4 — the map, every extending model and every `.likec4` beside one — into ONE project, \" +\n `and that project does not parse. ${remedy(err.message)}`,\n });\n }\n return { kind: \"invalid\", findings };\n}\n\n/**\n * The tail that names WHICH failure this is.\n *\n * It used to say \"a tag or an element declared in two of those documents is\n * declared twice there\" for every error, including the ones that are not a\n * double declaration at all (verification 2026-09-04, D10) — an unresolved\n * reference sent its reader hunting for a second declaration that does not\n * exist. LikeC4's own wording is the evidence, matched loosely on purpose: an\n * unrecognised message falls to the third arm, which asserts nothing beyond\n * where to look.\n */\nfunction remedy(message: string): string {\n if (/duplicat|already (declared|defined)|declared (more than once|twice)/i.test(message)) {\n return (\n \"A tag or an element declared in two of those documents is declared twice there; declare it once \" +\n \"(the map, or the one service that owns it)\"\n );\n }\n if (/could not resolve|unresolved|unknown/i.test(message)) {\n return (\n \"A name resolves against ALL of those documents here, so a reference the renderer cannot place is one \" +\n \"no document in the project declares; declare it where the reader can see it, or drop the reference\"\n );\n }\n return \"Open the file and line named above — every reader who points a renderer at the docs root sees this\";\n}\n\n/**\n * Every parse error this run has ALREADY reported against one of these\n * documents, keyed so the merged project's copy can be recognised.\n *\n * Two sources, and both are loads the run made anyway or would make: each\n * extending model's own per-service project (`c4.invalid` on the service\n * target) and, where nothing was wrong there, each service's FLOW project\n * (`readServiceFlows` — the use-case grade names the file). The flow read goes\n * through that function rather than a second `loadProject` here so the OPT-IN\n * gate is the same one: a broken sibling that nothing tagged is loaded by\n * neither, which is exactly why the merged project has to report it.\n *\n * Computed only when the merged project actually failed, which is why it is a\n * function rather than an input: on a healthy fleet this whole file costs one\n * load and nothing else.\n *\n * THE FLOW PROJECTS ARE PARSED AGAIN by `validateService` later in the same run,\n * and that is accepted rather than unnoticed: it happens only on the path where\n * the merged project already failed, so a healthy fleet pays nothing, and the\n * `ServiceModel` half above IS the run's memo. If that path ever becomes hot,\n * the follow-up is to memoise the flow scan on `FleetContext` beside the\n * per-service projects — not to hand the scan in here, which would make this\n * grade depend on an argument the caller has no reason to have loaded.\n */\nasync function alreadyReported(\n check: FleetProjectCheck,\n paths: readonly ServicePaths[],\n): Promise<ReadonlySet<string>> {\n // Both sets reach the same documents through `architectureProjectDocuments`,\n // which now drops what the root `exclude` covers exactly as the merged load's\n // own walk does — so a document out of one is out of both, and the generated\n // `subsystems.likec4` is out of all of them without being remembered twice.\n //\n // Pooled rather than awaited one at a time: each entry is an independent\n // LikeC4 workspace over one service's own documents, which is exactly the\n // work `inOrder` rations — and a serial loop here made the ONE path that\n // already costs a fleet its whole set of flow projects pay for them in\n // sequence.\n const perService = await inOrder(paths, async (service) => {\n const model = await check.fleet.serviceModel(check.docsDir, service);\n const errors = [...model.doc.errors];\n if (errors.length > 0 || model.mapUnreadable) return errors;\n // `model.project` is non-null here by construction — these are extending\n // models whose own load came back clean, which is the arm that carries it —\n // and the guard is the compiler's rather than an assertion: a null would\n // mean grading a flow against a document that did not load.\n if (model.project === null) return [];\n const flows = await readServiceFlows({\n paths: service,\n model: model.doc,\n known: check.known,\n extending: { docsDir: check.docsDir, project: model.project },\n });\n return flows.kind === \"unreadable\" ? flows.errors : [];\n });\n return new Set(perService.flat().map(errorKey));\n}\n\n/**\n * One error's identity across two loads of overlapping document sets: the real\n * file, the line, and the message.\n *\n * There is no id to join on — LikeC4 mints diagnostics per parse — and the\n * message has to be part of the key: two DIFFERENT problems on one line of one\n * file are two errors, and collapsing them would let a merged-project-only\n * failure hide behind a per-service one that happens to share a line.\n */\nfunction errorKey(err: LikeC4Error): string {\n return JSON.stringify([err.sourceFsPath ?? \"\", err.line ?? -1, err.message]);\n}\n"]}
@@ -1,3 +1,5 @@
1
+ import { renderContext } from "../../../../core/repo/tree/render/body.js";
2
+ import { SERVICE_VIEW_PREFIX, serviceViewId } from "../../../../core/repo/tree/render/service/view-id.js";
1
3
  import { SUBSYSTEM_VIEW_PREFIX, subsystemViewId } from "../../../../core/repo/tree/render/views.js";
2
4
  /**
3
5
  * `authored` is every view id the project claims, each with the DOCS-RELATIVE
@@ -6,10 +8,34 @@ import { SUBSYSTEM_VIEW_PREFIX, subsystemViewId } from "../../../../core/repo/tr
6
8
  * parse, or an older loader did not read them) is treated as "nothing to
7
9
  * compare", never as "nothing collides".
8
10
  */
9
- export function viewIdFindings(authored, tree) {
11
+ export function viewIdFindings(authored, tree, map) {
10
12
  if (authored === undefined || authored.length === 0)
11
13
  return [];
12
- const generated = new Map(tree.subsystems.map((sub) => [subsystemViewId(sub), sub.path.join("/")]));
14
+ const generated = new Map(tree.subsystems.map((sub) => [
15
+ subsystemViewId(sub),
16
+ { owner: sub.path.join("/"), file: "architecture/subsystems.likec4", prefix: SUBSYSTEM_VIEW_PREFIX },
17
+ ]));
18
+ // The per-service ids, and ONLY for a service the map actually resolves —
19
+ // which is why the map comes in at all. `architecture/services.likec4` draws a
20
+ // view for a service exactly when the element→service join answers, so a
21
+ // service the map does not draw mints NOTHING and an authored `service_<id>`
22
+ // beside it breaks nothing today. Reserving it anyway would be this check
23
+ // inventing a rule about a name that harms no one — the same line the module
24
+ // banner already holds for `subsystem_overview` with no `overview` subsystem —
25
+ // and the latent case is not lost: the run after somebody puts that service on
26
+ // the map, the generator mints the id and this check fires. The join arrives
27
+ // through `renderContext`, the generator's OWN, so the census and the render
28
+ // can never disagree about which services are drawn.
29
+ const drawn = renderContext(tree, map.elements);
30
+ for (const service of tree.services) {
31
+ if (!drawn.elementOf.has(service.id))
32
+ continue;
33
+ generated.set(serviceViewId(service.id), {
34
+ owner: [...service.subsystem, service.id].join("/"),
35
+ file: "architecture/services.likec4",
36
+ prefix: SERVICE_VIEW_PREFIX,
37
+ });
38
+ }
13
39
  const findings = [];
14
40
  // Sorted and de-duplicated by id so a project claiming the same id twice —
15
41
  // itself already a parse error, but reachable through a partial read — yields
@@ -21,8 +47,8 @@ export function viewIdFindings(authored, tree) {
21
47
  if (!claimants.has(claim.id))
22
48
  claimants.set(claim.id, claim.sourcePath);
23
49
  for (const [id, file] of [...claimants].sort(([a], [b]) => (a < b ? -1 : 1))) {
24
- const owner = generated.get(id);
25
- if (owner === undefined)
50
+ const mint = generated.get(id);
51
+ if (mint === undefined)
26
52
  continue;
27
53
  findings.push({
28
54
  severity: "error",
@@ -34,14 +60,14 @@ export function viewIdFindings(authored, tree) {
34
60
  // view in when the claim came from a document of their own.
35
61
  locations: [{ path: file, role: "primary" }],
36
62
  message: `subsystems: ${file} declares \`view ${id}\`, which is the id loam generates ` +
37
- `into architecture/subsystems.likec4 for services/${owner}/ — LikeC4 merges both files into one project, ` +
63
+ `into ${mint.file} for services/${mint.owner}/ — LikeC4 merges both files into one project, ` +
38
64
  // The ROOT project, not `architecture/`: the claimant may be a model's
39
65
  // own view now (an extending model is a document of the root project),
40
66
  // and telling that author the `architecture/` project is refused sends
41
67
  // them to look for the breakage in a directory they never touched.
42
68
  `so it refuses the whole root project and renders nothing. ` +
43
- `Rename the view in ${file}; ids beginning \`${SUBSYSTEM_VIEW_PREFIX}\` are loam's ` +
44
- "to mint, and architecture/subsystems.likec4 is generated (`loam subsystem sync`) and must never be hand-edited.",
69
+ `Rename the view in ${file}; ids beginning \`${mint.prefix}\` are loam's ` +
70
+ `to mint, and ${mint.file} is generated (\`loam subsystem sync\`) and must never be hand-edited.`,
45
71
  });
46
72
  }
47
73
  return findings;
@@ -1 +1 @@
1
- {"version":3,"file":"ids.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/ids.ts"],"names":[],"mappings":"AAqDA,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAIpG;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,QAAmC,EAAE,IAAe;IACjF,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpG,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,8EAA8E;IAC9E,+EAA+E;IAC/E,2EAA2E;IAC3E,iDAAiD;IACjD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,KAAK,MAAM,KAAK,IAAI,QAAQ;QAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IACtG,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,6BAA6B;YACnC,OAAO,EAAE,EAAE;YACX,wEAAwE;YACxE,uEAAuE;YACvE,wEAAwE;YACxE,4DAA4D;YAC5D,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAkB,EAAE,CAAC;YACrD,OAAO,EACL,eAAe,IAAI,oBAAoB,EAAE,qCAAqC;gBAC9E,oDAAoD,KAAK,iDAAiD;gBAC1G,uEAAuE;gBACvE,uEAAuE;gBACvE,uEAAuE;gBACvE,mEAAmE;gBACnE,4DAA4D;gBAC5D,sBAAsB,IAAI,qBAAqB,qBAAqB,gBAAgB;gBACpF,iHAAiH;SACpH,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * An authored view id that collides with one loam GENERATES into\n * `architecture/subsystems.likec4`.\n *\n * This closes a green-while-broken hole, and the shape of it is measured rather\n * than argued. The LikeC4 renderer merges every `.likec4` file in a project into\n * one model, and `architecture/landscape.likec4` and the generated\n * `architecture/subsystems.likec4` are two documents of the ROOT project — so\n * they share ONE flat view-id namespace. (This file used to say the reason was\n * that `likec4.config.json` scopes the root project to `architecture/`; it does\n * not, and never had to for the collision to exist. The scaffold's `exclude`\n * names `features/**` and node_modules, and a fleet whose models extend the map\n * has `services/` in the root project too — see `./fleet-project.ts`.) Author\n * `view subsystem_commerce { }` in the landscape while the tree has a `commerce`\n * subsystem and the renderer refuses the ENTIRE root project with two\n * `Duplicate view` diagnostics, one per file — while `loam validate --all`\n * printed `landscape: N service(s) modelled — architecture/landscape.likec4 and\n * services/ agree`. Reproduced at the 1.59.2 pin.\n *\n * WHICH DOCUMENTS CLAIM IDS IN THAT NAMESPACE, corrected. A duplicate INSIDE one\n * document is already 2 LikeC4 errors, so `landscape.invalid` has it (measured).\n * A feature's `delta.likec4` is its own LikeC4 project — the root excludes\n * `features/**` — and a delta's views never travel through `archive` anyway. A\n * service's `model.likec4` used to be in the same position and no longer is:\n * this file's original premise, that `likec4.config.json` excludes `services/**`\n * so a model's view ids cannot collide with the landscape's, holds only for a\n * model that STANDS ALONE. A model that EXTENDS the map lives in the root\n * project beside it, so its authored view ids — and those of every `.likec4`\n * beside it — share exactly this namespace. That census is made by the merged\n * load in `./fleet-project.ts` and handed here; the landscape's own claims are\n * still graded from the `architecture/` project, which is the only census a run\n * with no extending model can make.\n *\n * The claims therefore arrive spelled DOCS-RELATIVE, and the caller does the\n * spelling: the `architecture/` project reports a view's `sourcePath` relative\n * to `architecture/`, the merged project reports it relative to the docs root,\n * and a prefix pasted on here would name `architecture/services/…` for every\n * model. The message and the location both print the path verbatim.\n *\n * ONLY the real collision is refused, never the merely reserved-looking name.\n * A landscape view called `subsystem_overview` with no `overview` subsystem\n * breaks nothing today, and refusing it would be loam inventing a rule about a\n * name that harms no one. The latent case is not lost: the moment somebody\n * creates that subsystem, the generator mints the id, and this same check fires\n * on the same run. The message names the reservation anyway, so an author fixing\n * one collision learns not to make the next.\n *\n * Graded on `validate --all` only, and the caller holds that gate together with\n * the landscape-parsed one: under a single-target run the fleet tree is not\n * walked and the generated ids are unknown, so the question is unanswerable and\n * the check stays silent rather than half-answering.\n */\nimport type { ViewIdClaim } from \"../../../../core/c4/parsed/view-ids.js\";\nimport { SUBSYSTEM_VIEW_PREFIX, subsystemViewId } from \"../../../../core/repo/tree/render/views.js\";\nimport type { FleetTree } from \"../../../../core/repo/tree/walk.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\n\n/**\n * `authored` is every view id the project claims, each with the DOCS-RELATIVE\n * path of the file that claims it — static views included, because a static view\n * claims an id exactly as firmly as a dynamic one. Absent (the document did not\n * parse, or an older loader did not read them) is treated as \"nothing to\n * compare\", never as \"nothing collides\".\n */\nexport function viewIdFindings(authored: ViewIdClaim[] | undefined, tree: FleetTree): Finding[] {\n if (authored === undefined || authored.length === 0) return [];\n const generated = new Map(tree.subsystems.map((sub) => [subsystemViewId(sub), sub.path.join(\"/\")]));\n const findings: Finding[] = [];\n // Sorted and de-duplicated by id so a project claiming the same id twice —\n // itself already a parse error, but reachable through a partial read — yields\n // one finding per id rather than one per occurrence. The first claimant's file\n // is the one named: with a duplicate the project does not parse at all, so\n // there is never a second one to choose between.\n const claimants = new Map<string, string>();\n for (const claim of authored) if (!claimants.has(claim.id)) claimants.set(claim.id, claim.sourcePath);\n for (const [id, file] of [...claimants].sort(([a], [b]) => (a < b ? -1 : 1))) {\n const owner = generated.get(id);\n if (owner === undefined) continue;\n findings.push({\n severity: \"error\",\n code: \"subsystem.view-id-collision\",\n subject: id,\n // The CLAIMANT file — the one the message tells the reader to edit, and\n // the one the report's target fallback gets wrong: it files this under\n // the landscape, which is exactly the file a reader must NOT rename the\n // view in when the claim came from a document of their own.\n locations: [{ path: file, role: \"primary\" as const }],\n message:\n `subsystems: ${file} declares \\`view ${id}\\`, which is the id loam generates ` +\n `into architecture/subsystems.likec4 for services/${owner}/ — LikeC4 merges both files into one project, ` +\n // The ROOT project, not `architecture/`: the claimant may be a model's\n // own view now (an extending model is a document of the root project),\n // and telling that author the `architecture/` project is refused sends\n // them to look for the breakage in a directory they never touched.\n `so it refuses the whole root project and renders nothing. ` +\n `Rename the view in ${file}; ids beginning \\`${SUBSYSTEM_VIEW_PREFIX}\\` are loam's ` +\n \"to mint, and architecture/subsystems.likec4 is generated (`loam subsystem sync`) and must never be hand-edited.\",\n });\n }\n return findings;\n}\n"]}
1
+ {"version":3,"file":"ids.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/ids.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * An authored view id that collides with one loam GENERATES into\n * `architecture/subsystems.likec4` or `architecture/services.likec4`.\n *\n * TWO RESERVED PREFIXES, one check: `subsystem_<name>` for a subsystem's view\n * and `service_<id>` for a service's generated overview, both minted through the\n * one encoder (`render/service/view-id.ts`) so no two distinct names can fold\n * onto one id across BOTH files. The failure they guard is identical — the\n * renderer merges every document of the root project into one flat view-id\n * namespace and refuses the whole project over a duplicate — so they are one\n * finding code with the right file named in each message, not two codes.\n *\n * This closes a green-while-broken hole, and the shape of it is measured rather\n * than argued. The LikeC4 renderer merges every `.likec4` file in a project into\n * one model, and `architecture/landscape.likec4` and the generated\n * `architecture/subsystems.likec4` are two documents of the ROOT project — so\n * they share ONE flat view-id namespace. (This file used to say the reason was\n * that `likec4.config.json` scopes the root project to `architecture/`; it does\n * not, and never had to for the collision to exist. The scaffold's `exclude`\n * names `features/**` and node_modules, and a fleet whose models extend the map\n * has `services/` in the root project too — see `./fleet-project.ts`.) Author\n * `view subsystem_commerce { }` in the landscape while the tree has a `commerce`\n * subsystem and the renderer refuses the ENTIRE root project with two\n * `Duplicate view` diagnostics, one per file — while `loam validate --all`\n * printed `landscape: N service(s) modelled — architecture/landscape.likec4 and\n * services/ agree`. Reproduced at the 1.59.2 pin.\n *\n * WHICH DOCUMENTS CLAIM IDS IN THAT NAMESPACE, corrected. A duplicate INSIDE one\n * document is already 2 LikeC4 errors, so `landscape.invalid` has it (measured).\n * A feature's `delta.likec4` is its own LikeC4 project — the root excludes\n * `features/**` — and a delta's views never travel through `archive` anyway. A\n * service's `model.likec4` used to be in the same position and no longer is:\n * this file's original premise, that `likec4.config.json` excludes `services/**`\n * so a model's view ids cannot collide with the landscape's, holds only for a\n * model that STANDS ALONE. A model that EXTENDS the map lives in the root\n * project beside it, so its authored view ids — and those of every `.likec4`\n * beside it — share exactly this namespace. That census is made by the merged\n * load in `./fleet-project.ts` and handed here; the landscape's own claims are\n * still graded from the `architecture/` project, which is the only census a run\n * with no extending model can make.\n *\n * The claims therefore arrive spelled DOCS-RELATIVE, and the caller does the\n * spelling: the `architecture/` project reports a view's `sourcePath` relative\n * to `architecture/`, the merged project reports it relative to the docs root,\n * and a prefix pasted on here would name `architecture/services/…` for every\n * model. The message and the location both print the path verbatim.\n *\n * ONLY the real collision is refused, never the merely reserved-looking name.\n * A landscape view called `subsystem_overview` with no `overview` subsystem\n * breaks nothing today, and refusing it would be loam inventing a rule about a\n * name that harms no one. The latent case is not lost: the moment somebody\n * creates that subsystem, the generator mints the id, and this same check fires\n * on the same run. The message names the reservation anyway, so an author fixing\n * one collision learns not to make the next. The service half holds the same\n * line one step further in: an id is minted only for a service the MAP DRAWS,\n * because that is the render's own gate, so a `services/<id>/` the map does not\n * resolve reserves nothing until it is modelled.\n *\n * Graded on `validate --all` only, and the caller holds that gate together with\n * the landscape-parsed one: under a single-target run the fleet tree is not\n * walked and the generated ids are unknown, so the question is unanswerable and\n * the check stays silent rather than half-answering.\n */\nimport type { ViewIdClaim } from \"../../../../core/c4/parsed/view-ids.js\";\nimport { renderContext } from \"../../../../core/repo/tree/render/body.js\";\nimport { SERVICE_VIEW_PREFIX, serviceViewId } from \"../../../../core/repo/tree/render/service/view-id.js\";\nimport { type MapFacts, SUBSYSTEM_VIEW_PREFIX, subsystemViewId } from \"../../../../core/repo/tree/render/views.js\";\nimport type { FleetTree } from \"../../../../core/repo/tree/walk.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\n\n/**\n * `authored` is every view id the project claims, each with the DOCS-RELATIVE\n * path of the file that claims it — static views included, because a static view\n * claims an id exactly as firmly as a dynamic one. Absent (the document did not\n * parse, or an older loader did not read them) is treated as \"nothing to\n * compare\", never as \"nothing collides\".\n */\nexport function viewIdFindings(authored: ViewIdClaim[] | undefined, tree: FleetTree, map: MapFacts): Finding[] {\n if (authored === undefined || authored.length === 0) return [];\n const generated = new Map<string, Minted>(\n tree.subsystems.map((sub) => [\n subsystemViewId(sub),\n { owner: sub.path.join(\"/\"), file: \"architecture/subsystems.likec4\", prefix: SUBSYSTEM_VIEW_PREFIX },\n ]),\n );\n // The per-service ids, and ONLY for a service the map actually resolves —\n // which is why the map comes in at all. `architecture/services.likec4` draws a\n // view for a service exactly when the element→service join answers, so a\n // service the map does not draw mints NOTHING and an authored `service_<id>`\n // beside it breaks nothing today. Reserving it anyway would be this check\n // inventing a rule about a name that harms no one — the same line the module\n // banner already holds for `subsystem_overview` with no `overview` subsystem —\n // and the latent case is not lost: the run after somebody puts that service on\n // the map, the generator mints the id and this check fires. The join arrives\n // through `renderContext`, the generator's OWN, so the census and the render\n // can never disagree about which services are drawn.\n const drawn = renderContext(tree, map.elements);\n for (const service of tree.services) {\n if (!drawn.elementOf.has(service.id)) continue;\n generated.set(serviceViewId(service.id), {\n owner: [...service.subsystem, service.id].join(\"/\"),\n file: \"architecture/services.likec4\",\n prefix: SERVICE_VIEW_PREFIX,\n });\n }\n const findings: Finding[] = [];\n // Sorted and de-duplicated by id so a project claiming the same id twice —\n // itself already a parse error, but reachable through a partial read — yields\n // one finding per id rather than one per occurrence. The first claimant's file\n // is the one named: with a duplicate the project does not parse at all, so\n // there is never a second one to choose between.\n const claimants = new Map<string, string>();\n for (const claim of authored) if (!claimants.has(claim.id)) claimants.set(claim.id, claim.sourcePath);\n for (const [id, file] of [...claimants].sort(([a], [b]) => (a < b ? -1 : 1))) {\n const mint = generated.get(id);\n if (mint === undefined) continue;\n findings.push({\n severity: \"error\",\n code: \"subsystem.view-id-collision\",\n subject: id,\n // The CLAIMANT file — the one the message tells the reader to edit, and\n // the one the report's target fallback gets wrong: it files this under\n // the landscape, which is exactly the file a reader must NOT rename the\n // view in when the claim came from a document of their own.\n locations: [{ path: file, role: \"primary\" as const }],\n message:\n `subsystems: ${file} declares \\`view ${id}\\`, which is the id loam generates ` +\n `into ${mint.file} for services/${mint.owner}/ — LikeC4 merges both files into one project, ` +\n // The ROOT project, not `architecture/`: the claimant may be a model's\n // own view now (an extending model is a document of the root project),\n // and telling that author the `architecture/` project is refused sends\n // them to look for the breakage in a directory they never touched.\n `so it refuses the whole root project and renders nothing. ` +\n `Rename the view in ${file}; ids beginning \\`${mint.prefix}\\` are loam's ` +\n `to mint, and ${mint.file} is generated (\\`loam subsystem sync\\`) and must never be hand-edited.`,\n });\n }\n return findings;\n}\n\n/**\n * What loam mints one reserved id FOR — which subject, into which generated\n * file, under which reserved prefix.\n *\n * A record rather than the bare owner path the map used to hold, because the\n * message has to name the right FILE and the right PREFIX: telling the author of\n * a colliding `service_checkout` that `subsystem_` is loam's and that\n * `subsystems.likec4` is the generated file sends them to look at a file their\n * name has nothing to do with.\n */\ninterface Minted {\n /** The tree path of the subsystem or service the id belongs to, under `services/`. */\n owner: string;\n /** The generated document loam writes that id into, docs-relative. */\n file: string;\n /** The reserved prefix the author is told not to mint under. */\n prefix: string;\n}\n"]}
@@ -16,10 +16,12 @@
16
16
  *
17
17
  * FOUR THINGS COME OUT, and each is a rule rather than a filter of convenience:
18
18
  *
19
- * - The GENERATED `architecture/subsystems.likec4`, for `c4/project/documents.ts`'s
20
- * reason — docs/DESIGN.md rule 26 makes its staleness a byte compare, and a
21
- * stale one naming a removed element would blank this load and report a
22
- * cascade whose repair is `loam subsystem sync`.
19
+ * - Every GENERATED `architecture/` document — `subsystems.likec4` and
20
+ * `services.likec4`, taken from the ONE list every project read shares
21
+ * (`c4/project/architecture.ts`) — for `c4/project/documents.ts`'s reason:
22
+ * docs/DESIGN.md rule 26 makes their staleness a byte compare, and a stale
23
+ * one naming a removed element would blank this load and report a cascade
24
+ * whose repair is `loam subsystem sync`.
23
25
  * - Anything the root `exclude` covers, read through `c4/root-project/exclude.ts`
24
26
  * — the renderer does not load it, so grading it would be loam reporting a
25
27
  * failure nobody can see. A root config loam cannot read an `exclude` list out
@@ -40,10 +42,10 @@
40
42
  * read as the map declared twice) and a package tree, never authored documents —
41
43
  * the same thing the scaffold's own `node_modules` glob says.
42
44
  */
43
- import { readdir } from "node:fs/promises";
44
- import { join, relative, resolve, sep } from "node:path";
45
+ import { relative, resolve, sep } from "node:path";
45
46
  import { excludingPath, readRootExclude } from "../../../../core/c4/root-project/exclude.js";
46
- import { subsystemViewsPath } from "../../../../core/repo/paths.js";
47
+ import { generatedArchitectureDocuments } from "../../../../core/c4/project/architecture.js";
48
+ import { likec4DocumentsUnder } from "../../../../core/c4/project/documents.js";
47
49
  /** The directory name loam never walks, beside every dot-directory. */
48
50
  const PACKAGES = "node_modules";
49
51
  /** The one subtree loam drops whatever the root config says — see the banner. */
@@ -54,12 +56,11 @@ const FEATURES = "features";
54
56
  */
55
57
  export async function rootProjectDocuments(docsDir, standalone) {
56
58
  const exclude = await readRootExclude(docsDir);
57
- const generated = resolve(subsystemViewsPath(docsDir));
59
+ const generated = new Set(generatedArchitectureDocuments(docsDir).map((path) => resolve(path)));
58
60
  const excluded = standalone.map((dir) => `${resolve(dir)}${sep}`);
59
61
  const root = resolve(docsDir);
60
- const found = [];
61
62
  const keeps = (path) => {
62
- if (resolve(path) === generated)
63
+ if (generated.has(resolve(path)))
63
64
  return false;
64
65
  if (excluded.some((dir) => resolve(path).startsWith(dir)))
65
66
  return false;
@@ -68,30 +69,11 @@ export async function rootProjectDocuments(docsDir, standalone) {
68
69
  return false;
69
70
  return exclude === null || excludingPath(exclude, rel.join("/")) === null;
70
71
  };
71
- const walk = async (at) => {
72
- let entries;
73
- try {
74
- entries = await readdir(at, { withFileTypes: true });
75
- }
76
- catch {
77
- // A directory that cannot be read contributes nothing: this grade runs
78
- // inside `validate --all` over a whole fleet, and the one behaviour it may
79
- // never have is failing the run it was added to.
80
- return;
81
- }
82
- for (const entry of entries) {
83
- const path = join(at, entry.name);
84
- if (entry.isDirectory()) {
85
- if (entry.name.startsWith(".") || entry.name === PACKAGES)
86
- continue;
87
- await walk(path);
88
- }
89
- else if (entry.name.endsWith(".likec4") && keeps(path)) {
90
- found.push(path);
91
- }
92
- }
93
- };
94
- await walk(root);
95
- return found.sort();
72
+ // An unreadable directory contributes nothing — `likec4DocumentsUnder`
73
+ // records why a grade inside `validate --all` may never fail the run.
74
+ return likec4DocumentsUnder(root, {
75
+ enters: (name) => !name.startsWith(".") && name !== PACKAGES,
76
+ keeps,
77
+ });
96
78
  }
97
79
  //# sourceMappingURL=root-documents.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"root-documents.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/root-documents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AAE7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE,uEAAuE;AACvE,MAAM,QAAQ,GAAG,cAAc,CAAC;AAEhC,iFAAiF;AACjF,MAAM,QAAQ,GAAG,UAAU,CAAC;AAE5B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAgB,EAAE,UAA6B;IACxF,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,IAAY,EAAW,EAAE;QACtC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QACxE,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACtC,OAAO,OAAO,KAAK,IAAI,IAAI,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;IAC5E,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,EAAE,EAAU,EAAiB,EAAE;QAC/C,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,2EAA2E;YAC3E,iDAAiD;YACjD,OAAO;QACT,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;oBAAE,SAAS;gBACpE,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC","sourcesContent":["/**\n * The documents the RENDERER's root project loads, enumerated the way the\n * renderer enumerates them: everything under the docs root that the root\n * `likec4.config.json` does not exclude.\n *\n * It exists because `./fleet-project.ts` used to build the set BY SHAPE — the\n * `architecture/` project, each extending `model.likec4`, and every `.likec4`\n * inside a service directory — and that is three roots, not a tree. A `.likec4`\n * one directory ABOVE a service (`services/platform/notes.likec4`, the parent of\n * service directories) was in none of them: `loam validate --all` came back\n * byte-identical to baseline and never named the file, while `npx likec4\n * validate --project fleet .` reported Invalid over nine files and thirteen\n * errors (verification 2026-09-04, R2). The same file one directory lower was a\n * loud error. SCHEMA, `loam-codes` and `explain` all said loam loads \"every\n * `.likec4` the root project reads\"; this is that sentence, executed.\n *\n * FOUR THINGS COME OUT, and each is a rule rather than a filter of convenience:\n *\n * - The GENERATED `architecture/subsystems.likec4`, for `c4/project/documents.ts`'s\n * reason — docs/DESIGN.md rule 26 makes its staleness a byte compare, and a\n * stale one naming a removed element would blank this load and report a\n * cascade whose repair is `loam subsystem sync`.\n * - Anything the root `exclude` covers, read through `c4/root-project/exclude.ts`\n * — the renderer does not load it, so grading it would be loam reporting a\n * failure nobody can see. A root config loam cannot read an `exclude` list out\n * of covers nothing: guessing an exclusion is worse than loading one document\n * too many.\n * - Every STANDALONE service directory. Such a model declares its own kinds, so\n * the root project must exclude it and `service.model-unexcluded` is the grade\n * for a root that does not — one finding naming the entry to add, rather than\n * a duplicate-kind cascade under a second code.\n * - `features/`. A feature's `delta.likec4` carries a `specification` block of\n * its own and the directory is transient — the scaffolded root config excludes\n * it for exactly that reason, and a fleet mid-feature would otherwise read as\n * a fleet whose map is declared twice. loam drops it whether or not the entry\n * is still there; `loam validate --feature` is where a delta is graded.\n *\n * Dot-directories and `node_modules` are not walked at all. Those are loam's own\n * machinery (`.loam-before` holds a verbatim copy of the landscape, which would\n * read as the map declared twice) and a package tree, never authored documents —\n * the same thing the scaffold's own `node_modules` glob says.\n */\nimport { readdir } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport { excludingPath, readRootExclude } from \"../../../../core/c4/root-project/exclude.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { subsystemViewsPath } from \"../../../../core/repo/paths.js\";\n\n/** The directory name loam never walks, beside every dot-directory. */\nconst PACKAGES = \"node_modules\";\n\n/** The one subtree loam drops whatever the root config says — see the banner. */\nconst FEATURES = \"features\";\n\n/**\n * Every `.likec4` the renderer's root project reads and loam can grade, absolute\n * and sorted. `standalone` is each standalone service's directory, absolute.\n */\nexport async function rootProjectDocuments(docsDir: DocsDir, standalone: readonly string[]): Promise<string[]> {\n const exclude = await readRootExclude(docsDir);\n const generated = resolve(subsystemViewsPath(docsDir));\n const excluded = standalone.map((dir) => `${resolve(dir)}${sep}`);\n const root = resolve(docsDir);\n const found: string[] = [];\n const keeps = (path: string): boolean => {\n if (resolve(path) === generated) return false;\n if (excluded.some((dir) => resolve(path).startsWith(dir))) return false;\n const rel = relative(root, path).split(/[\\\\/]/);\n if (rel[0] === FEATURES) return false;\n return exclude === null || excludingPath(exclude, rel.join(\"/\")) === null;\n };\n const walk = async (at: string): Promise<void> => {\n let entries;\n try {\n entries = await readdir(at, { withFileTypes: true });\n } catch {\n // A directory that cannot be read contributes nothing: this grade runs\n // inside `validate --all` over a whole fleet, and the one behaviour it may\n // never have is failing the run it was added to.\n return;\n }\n for (const entry of entries) {\n const path = join(at, entry.name);\n if (entry.isDirectory()) {\n if (entry.name.startsWith(\".\") || entry.name === PACKAGES) continue;\n await walk(path);\n } else if (entry.name.endsWith(\".likec4\") && keeps(path)) {\n found.push(path);\n }\n }\n };\n await walk(root);\n return found.sort();\n}\n"]}
1
+ {"version":3,"file":"root-documents.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/root-documents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AAE7F,OAAO,EAAE,8BAA8B,EAAE,MAAM,6CAA6C,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAEhF,uEAAuE;AACvE,MAAM,QAAQ,GAAG,cAAc,CAAC;AAEhC,iFAAiF;AACjF,MAAM,QAAQ,GAAG,UAAU,CAAC;AAE5B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAgB,EAAE,UAA6B;IACxF,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,8BAA8B,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChG,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,CAAC,IAAY,EAAW,EAAE;QACtC,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/C,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QACxE,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACtC,OAAO,OAAO,KAAK,IAAI,IAAI,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;IAC5E,CAAC,CAAC;IACF,uEAAuE;IACvE,sEAAsE;IACtE,OAAO,oBAAoB,CAAC,IAAI,EAAE;QAChC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,QAAQ;QAC5D,KAAK;KACN,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The documents the RENDERER's root project loads, enumerated the way the\n * renderer enumerates them: everything under the docs root that the root\n * `likec4.config.json` does not exclude.\n *\n * It exists because `./fleet-project.ts` used to build the set BY SHAPE — the\n * `architecture/` project, each extending `model.likec4`, and every `.likec4`\n * inside a service directory — and that is three roots, not a tree. A `.likec4`\n * one directory ABOVE a service (`services/platform/notes.likec4`, the parent of\n * service directories) was in none of them: `loam validate --all` came back\n * byte-identical to baseline and never named the file, while `npx likec4\n * validate --project fleet .` reported Invalid over nine files and thirteen\n * errors (verification 2026-09-04, R2). The same file one directory lower was a\n * loud error. SCHEMA, `loam-codes` and `explain` all said loam loads \"every\n * `.likec4` the root project reads\"; this is that sentence, executed.\n *\n * FOUR THINGS COME OUT, and each is a rule rather than a filter of convenience:\n *\n * - Every GENERATED `architecture/` document — `subsystems.likec4` and\n * `services.likec4`, taken from the ONE list every project read shares\n * (`c4/project/architecture.ts`) — for `c4/project/documents.ts`'s reason:\n * docs/DESIGN.md rule 26 makes their staleness a byte compare, and a stale\n * one naming a removed element would blank this load and report a cascade\n * whose repair is `loam subsystem sync`.\n * - Anything the root `exclude` covers, read through `c4/root-project/exclude.ts`\n * — the renderer does not load it, so grading it would be loam reporting a\n * failure nobody can see. A root config loam cannot read an `exclude` list out\n * of covers nothing: guessing an exclusion is worse than loading one document\n * too many.\n * - Every STANDALONE service directory. Such a model declares its own kinds, so\n * the root project must exclude it and `service.model-unexcluded` is the grade\n * for a root that does not — one finding naming the entry to add, rather than\n * a duplicate-kind cascade under a second code.\n * - `features/`. A feature's `delta.likec4` carries a `specification` block of\n * its own and the directory is transient — the scaffolded root config excludes\n * it for exactly that reason, and a fleet mid-feature would otherwise read as\n * a fleet whose map is declared twice. loam drops it whether or not the entry\n * is still there; `loam validate --feature` is where a delta is graded.\n *\n * Dot-directories and `node_modules` are not walked at all. Those are loam's own\n * machinery (`.loam-before` holds a verbatim copy of the landscape, which would\n * read as the map declared twice) and a package tree, never authored documents —\n * the same thing the scaffold's own `node_modules` glob says.\n */\nimport { relative, resolve, sep } from \"node:path\";\nimport { excludingPath, readRootExclude } from \"../../../../core/c4/root-project/exclude.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { generatedArchitectureDocuments } from \"../../../../core/c4/project/architecture.js\";\nimport { likec4DocumentsUnder } from \"../../../../core/c4/project/documents.js\";\n\n/** The directory name loam never walks, beside every dot-directory. */\nconst PACKAGES = \"node_modules\";\n\n/** The one subtree loam drops whatever the root config says — see the banner. */\nconst FEATURES = \"features\";\n\n/**\n * Every `.likec4` the renderer's root project reads and loam can grade, absolute\n * and sorted. `standalone` is each standalone service's directory, absolute.\n */\nexport async function rootProjectDocuments(docsDir: DocsDir, standalone: readonly string[]): Promise<string[]> {\n const exclude = await readRootExclude(docsDir);\n const generated = new Set(generatedArchitectureDocuments(docsDir).map((path) => resolve(path)));\n const excluded = standalone.map((dir) => `${resolve(dir)}${sep}`);\n const root = resolve(docsDir);\n const keeps = (path: string): boolean => {\n if (generated.has(resolve(path))) return false;\n if (excluded.some((dir) => resolve(path).startsWith(dir))) return false;\n const rel = relative(root, path).split(/[\\\\/]/);\n if (rel[0] === FEATURES) return false;\n return exclude === null || excludingPath(exclude, rel.join(\"/\")) === null;\n };\n // An unreadable directory contributes nothing — `likec4DocumentsUnder`\n // records why a grade inside `validate --all` may never fail the run.\n return likec4DocumentsUnder(root, {\n enters: (name) => !name.startsWith(\".\") && name !== PACKAGES,\n keeps,\n });\n}\n"]}
@@ -1,19 +1,37 @@
1
- import { subsystemViewsPath } from "../../../../core/repo/paths.js";
2
- import { viewsState } from "../../../../core/repo/tree/render/stale.js";
1
+ import { serviceViewsPath, subsystemViewsPath } from "../../../../core/repo/paths.js";
2
+ import { serviceViewsState, viewsState } from "../../../../core/repo/tree/render/stale.js";
3
3
  export async function viewsStaleFindings(docsDir, tree, map) {
4
- const { actual, expected, agrees } = await viewsState(subsystemViewsPath(docsDir), tree, map);
5
- if (agrees)
4
+ const subsystems = await viewsState(subsystemViewsPath(docsDir), tree, map);
5
+ const services = await serviceViewsState(serviceViewsPath(docsDir), tree, map);
6
+ return [
7
+ ...staleFinding(subsystems, {
8
+ file: "architecture/subsystems.likec4",
9
+ absent: "does not exist, but the tree has subsystems to mirror",
10
+ leftover: "exists, but the tree has no subsystems — the generated file must be absent",
11
+ what: "one view per subsystem, and every derived scope drawn from it is wrong until it agrees",
12
+ }),
13
+ ...staleFinding(services, {
14
+ file: "architecture/services.likec4",
15
+ absent: "does not exist, but the fleet map draws services this file must mirror",
16
+ leftover: "exists, but the fleet map draws none of the services under services/ — the generated file must be absent",
17
+ what: "one overview view per service the map draws, and every drill-down into a service goes through it, so a " +
18
+ "reader following the map lands on a picture that is wrong until it agrees",
19
+ }),
20
+ ];
21
+ }
22
+ /**
23
+ * One finding, or none — the shape both generated files share, spelled once so
24
+ * they cannot drift into two dialects of the same sentence.
25
+ */
26
+ function staleFinding(state, file) {
27
+ if (state.agrees)
6
28
  return [];
7
- const state = actual === null
8
- ? "does not exist, but the tree has subsystems to mirror"
9
- : expected === null
10
- ? "exists, but the tree has no subsystems — the generated file must be absent"
11
- : "does not match the tree";
29
+ const said = state.actual === null ? file.absent : state.expected === null ? file.leftover : "does not match the tree and the map";
12
30
  return [
13
31
  {
14
32
  severity: "error",
15
33
  code: "subsystem.views-stale",
16
- subject: "architecture/subsystems.likec4",
34
+ subject: file.file,
17
35
  // The file the message names, so an editor jumping to the finding lands
18
36
  // on it. Without a location the report's fallback files this under the
19
37
  // TARGET, which is the landscape — a reader was sent to
@@ -22,12 +40,11 @@ export async function viewsStaleFindings(docsDir, tree, map) {
22
40
  // yet cannot be `primary`: the actionable place is then the directory
23
41
  // the regenerator will write into.
24
42
  locations: [
25
- actual === null
43
+ state.actual === null
26
44
  ? { path: "architecture", role: "scope" }
27
- : { path: "architecture/subsystems.likec4", role: "primary" },
45
+ : { path: file.file, role: "primary" },
28
46
  ],
29
- message: `subsystems: architecture/subsystems.likec4 ${state} — ` +
30
- `it is generated, one view per subsystem, and every derived scope drawn from it is wrong until it agrees. ` +
47
+ message: `subsystems: ${file.file} ${said} — it is generated, ${file.what}. ` +
31
48
  "Run `loam subsystem sync` to regenerate it; never edit it by hand.",
32
49
  },
33
50
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"stale.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/stale.ts"],"names":[],"mappings":"AAuCA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAC;AAKxE,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAgB,EAChB,IAAe,EACf,GAAa;IAEb,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,UAAU,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC9F,IAAI,MAAM;QAAE,OAAO,EAAE,CAAC;IACtB,MAAM,KAAK,GACT,MAAM,KAAK,IAAI;QACb,CAAC,CAAC,uDAAuD;QACzD,CAAC,CAAC,QAAQ,KAAK,IAAI;YACjB,CAAC,CAAC,4EAA4E;YAC9E,CAAC,CAAC,yBAAyB,CAAC;IAClC,OAAO;QACL;YACE,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,gCAAgC;YACzC,wEAAwE;YACxE,uEAAuE;YACvE,wDAAwD;YACxD,qEAAqE;YACrE,sEAAsE;YACtE,sEAAsE;YACtE,mCAAmC;YACnC,SAAS,EAAE;gBACT,MAAM,KAAK,IAAI;oBACb,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,OAAgB,EAAE;oBAClD,CAAC,CAAC,EAAE,IAAI,EAAE,gCAAgC,EAAE,IAAI,EAAE,SAAkB,EAAE;aACzE;YACD,OAAO,EACL,8CAA8C,KAAK,KAAK;gBACxD,2GAA2G;gBAC3G,oEAAoE;SACvE;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Staleness of the GENERATED `architecture/subsystems.likec4` against the\n * tree: exactly ONE error on exactly one file, repaired by exactly one\n * command (`loam subsystem sync`).\n *\n * A content compare, deliberately — never a parse. A views-only document does\n * not parse standalone (its `include` lines reference elements the landscape\n * defines; the spike in `core/repo/tree/render/views.ts` records it), and no check\n * anywhere reads the file's MEANING: the file is a scoping convenience for\n * the LikeC4 renderer, so the only question loam owes it is \"is this what the\n * tree renders to\". Rule 26 leaves this untouched: what loam may\n * read is a `dynamic view`'s declared steps out of a document it already\n * parses — never a static view's `include` predicates, and never this\n * generated file at all. Absent counts as a state of its own: a fleet\n * with subsystems and no file is stale, and a fleet with NO subsystems and a\n * leftover file is stale too — the render contract says the file must then\n * not exist, or a group deleted months ago keeps a view forever.\n *\n * The compare is over CONTENT, not BYTES, and `viewsAgree` in\n * `core/repo/tree/render/views.ts` both performs it and records why: an ordinary\n * Windows clone hands this file back with CRLF and not one fact in it changed,\n * and under a byte compare the advertised repair is a loop rather than a\n * diagnosis. The rule lives beside the generator because the module that mints\n * the bytes is the one that owes an answer about them.\n *\n * Graded only when the landscape PARSED (the caller holds that gate): the\n * expected bytes resolve members through the landscape's element→service\n * join, and grading them against a map that is missing or unreadable would\n * cascade a second error behind `landscape.missing`/`landscape.invalid`,\n * pointing at a file whose repair command would only re-derive the same\n * incomplete answer.\n *\n * The map arrives as the parsed document's facts — its elements AND the\n * global style ids it declares — because the expected bytes now depend on\n * both: a view references `global style subsystems` exactly when the project\n * declares that id, so a fleet that declares it sees this finding once, until\n * the next `sync` writes the line.\n */\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { subsystemViewsPath } from \"../../../../core/repo/paths.js\";\nimport { viewsState } from \"../../../../core/repo/tree/render/stale.js\";\nimport type { MapFacts } from \"../../../../core/repo/tree/render/views.js\";\nimport type { FleetTree } from \"../../../../core/repo/tree/walk.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\n\nexport async function viewsStaleFindings(\n docsDir: DocsDir,\n tree: FleetTree,\n map: MapFacts,\n): Promise<Finding[]> {\n const { actual, expected, agrees } = await viewsState(subsystemViewsPath(docsDir), tree, map);\n if (agrees) return [];\n const state =\n actual === null\n ? \"does not exist, but the tree has subsystems to mirror\"\n : expected === null\n ? \"exists, but the tree has no subsystems — the generated file must be absent\"\n : \"does not match the tree\";\n return [\n {\n severity: \"error\",\n code: \"subsystem.views-stale\",\n subject: \"architecture/subsystems.likec4\",\n // The file the message names, so an editor jumping to the finding lands\n // on it. Without a location the report's fallback files this under the\n // TARGET, which is the landscape — a reader was sent to\n // architecture/landscape.likec4 to fix a file the sentence beside it\n // named as architecture/subsystems.likec4. A file that does not exist\n // yet cannot be `primary`: the actionable place is then the directory\n // the regenerator will write into.\n locations: [\n actual === null\n ? { path: \"architecture\", role: \"scope\" as const }\n : { path: \"architecture/subsystems.likec4\", role: \"primary\" as const },\n ],\n message:\n `subsystems: architecture/subsystems.likec4 ${state} — ` +\n `it is generated, one view per subsystem, and every derived scope drawn from it is wrong until it agrees. ` +\n \"Run `loam subsystem sync` to regenerate it; never edit it by hand.\",\n },\n ];\n}\n"]}
1
+ {"version":3,"file":"stale.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/stale.ts"],"names":[],"mappings":"AAmDA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACtF,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAmB,MAAM,4CAA4C,CAAC;AAK5G,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAgB,EAAE,IAAe,EAAE,GAAa;IACvF,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/E,OAAO;QACL,GAAG,YAAY,CAAC,UAAU,EAAE;YAC1B,IAAI,EAAE,gCAAgC;YACtC,MAAM,EAAE,uDAAuD;YAC/D,QAAQ,EAAE,4EAA4E;YACtF,IAAI,EAAE,wFAAwF;SAC/F,CAAC;QACF,GAAG,YAAY,CAAC,QAAQ,EAAE;YACxB,IAAI,EAAE,8BAA8B;YACpC,MAAM,EAAE,wEAAwE;YAChF,QAAQ,EACN,0GAA0G;YAC5G,IAAI,EACF,yGAAyG;gBACzG,2EAA2E;SAC9E,CAAC;KACH,CAAC;AACJ,CAAC;AAcD;;;GAGG;AACH,SAAS,YAAY,CAAC,KAAiB,EAAE,IAAe;IACtD,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC5B,MAAM,IAAI,GACR,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,qCAAqC,CAAC;IACxH,OAAO;QACL;YACE,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,IAAI,CAAC,IAAI;YAClB,wEAAwE;YACxE,uEAAuE;YACvE,wDAAwD;YACxD,qEAAqE;YACrE,sEAAsE;YACtE,sEAAsE;YACtE,mCAAmC;YACnC,SAAS,EAAE;gBACT,KAAK,CAAC,MAAM,KAAK,IAAI;oBACnB,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,OAAgB,EAAE;oBAClD,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,SAAkB,EAAE;aAClD;YACD,OAAO,EACL,eAAe,IAAI,CAAC,IAAI,IAAI,IAAI,uBAAuB,IAAI,CAAC,IAAI,IAAI;gBACpE,oEAAoE;SACvE;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Staleness of the GENERATED documents under `architecture/` against the tree\n * and the map: ONE error per stale file, naming that file, repaired by exactly\n * one command (`loam subsystem sync`).\n *\n * TWO FILES, ONE CODE, and that is a decision rather than an omission.\n * `subsystems.likec4` (one view per subsystem) and `services.likec4` (one\n * overview view per service the map draws) are written by the same command in\n * the same commit, drift for overlapping reasons — a `subsystem move` restales\n * both, because a per-service view's title is `<subsystem path> / <service\n * id>` — and are repaired by the same run. A second code would buy a reader\n * nothing the `subject` and the message do not already say, and every code costs\n * a row in `core/agent/workflows/check.ts`, a `loam explain` entry and a place in\n * the vocabulary. What DOES have to stay apart is which file drifted, so the two\n * are graded separately and each finding carries its own path — folding them\n * into one finding would lose the only thing the message has to say.\n *\n * A content compare, deliberately — never a parse. A views-only document does\n * not parse standalone (its `include` lines reference elements the landscape\n * defines; the spike in `core/repo/tree/render/views.ts` records it), and no check\n * anywhere reads the file's MEANING: the file is a scoping convenience for\n * the LikeC4 renderer, so the only question loam owes it is \"is this what the\n * tree renders to\". Rule 26 leaves this untouched: what loam may\n * read is a `dynamic view`'s declared steps out of a document it already\n * parses — never a static view's `include` predicates, and never this\n * generated file at all. Absent counts as a state of its own: a fleet\n * with subsystems and no file is stale, and a fleet with NO subsystems and a\n * leftover file is stale too — the render contract says the file must then\n * not exist, or a group deleted months ago keeps a view forever.\n *\n * The compare is over CONTENT, not BYTES, and `viewsAgree` in\n * `core/repo/tree/render/views.ts` both performs it and records why: an ordinary\n * Windows clone hands this file back with CRLF and not one fact in it changed,\n * and under a byte compare the advertised repair is a loop rather than a\n * diagnosis. The rule lives beside the generator because the module that mints\n * the bytes is the one that owes an answer about them.\n *\n * Graded only when the landscape PARSED (the caller holds that gate): the\n * expected bytes resolve members through the landscape's element→service\n * join, and grading them against a map that is missing or unreadable would\n * cascade a second error behind `landscape.missing`/`landscape.invalid`,\n * pointing at a file whose repair command would only re-derive the same\n * incomplete answer.\n *\n * The map arrives as the parsed document's facts — its elements AND the\n * global style ids it declares — because the expected bytes now depend on\n * both: a view references `global style subsystems` exactly when the project\n * declares that id, so a fleet that declares it sees this finding once, until\n * the next `sync` writes the line.\n */\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { serviceViewsPath, subsystemViewsPath } from \"../../../../core/repo/paths.js\";\nimport { serviceViewsState, viewsState, type ViewsState } from \"../../../../core/repo/tree/render/stale.js\";\nimport type { MapFacts } from \"../../../../core/repo/tree/render/views.js\";\nimport type { FleetTree } from \"../../../../core/repo/tree/walk.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\n\nexport async function viewsStaleFindings(docsDir: DocsDir, tree: FleetTree, map: MapFacts): Promise<Finding[]> {\n const subsystems = await viewsState(subsystemViewsPath(docsDir), tree, map);\n const services = await serviceViewsState(serviceViewsPath(docsDir), tree, map);\n return [\n ...staleFinding(subsystems, {\n file: \"architecture/subsystems.likec4\",\n absent: \"does not exist, but the tree has subsystems to mirror\",\n leftover: \"exists, but the tree has no subsystems — the generated file must be absent\",\n what: \"one view per subsystem, and every derived scope drawn from it is wrong until it agrees\",\n }),\n ...staleFinding(services, {\n file: \"architecture/services.likec4\",\n absent: \"does not exist, but the fleet map draws services this file must mirror\",\n leftover:\n \"exists, but the fleet map draws none of the services under services/ — the generated file must be absent\",\n what:\n \"one overview view per service the map draws, and every drill-down into a service goes through it, so a \" +\n \"reader following the map lands on a picture that is wrong until it agrees\",\n }),\n ];\n}\n\n/** The four sentences one file's finding differs by — the rest of it is shared. */\ninterface StaleFile {\n /** The docs-relative path, which is the subject, the location and the head of the message. */\n file: string;\n /** Why an absent file is stale. */\n absent: string;\n /** Why a present file that must not exist is stale. */\n leftover: string;\n /** What the file holds, and what is wrong while it disagrees. */\n what: string;\n}\n\n/**\n * One finding, or none — the shape both generated files share, spelled once so\n * they cannot drift into two dialects of the same sentence.\n */\nfunction staleFinding(state: ViewsState, file: StaleFile): Finding[] {\n if (state.agrees) return [];\n const said =\n state.actual === null ? file.absent : state.expected === null ? file.leftover : \"does not match the tree and the map\";\n return [\n {\n severity: \"error\",\n code: \"subsystem.views-stale\",\n subject: file.file,\n // The file the message names, so an editor jumping to the finding lands\n // on it. Without a location the report's fallback files this under the\n // TARGET, which is the landscape — a reader was sent to\n // architecture/landscape.likec4 to fix a file the sentence beside it\n // named as architecture/subsystems.likec4. A file that does not exist\n // yet cannot be `primary`: the actionable place is then the directory\n // the regenerator will write into.\n locations: [\n state.actual === null\n ? { path: \"architecture\", role: \"scope\" as const }\n : { path: file.file, role: \"primary\" as const },\n ],\n message:\n `subsystems: ${file.file} ${said} — it is generated, ${file.what}. ` +\n \"Run `loam subsystem sync` to regenerate it; never edit it by hand.\",\n },\n ];\n}\n"]}
@@ -10,12 +10,10 @@
10
10
  * and refusing to let one unreadable file take a whole target down with it.
11
11
  */
12
12
  import { existsSync } from "node:fs";
13
- import { readFile } from "node:fs/promises";
14
13
  import { pathCaseIndex } from "../../../core/links/case.js";
15
14
  import { featureDocuments, fleetDocuments, serviceDocuments } from "../../../core/links/corpus.js";
16
15
  import { unresolvedLinkFindings } from "../../../core/links/findings.js";
17
16
  import { featureGlossaryOverlay } from "../../../core/glossary/delta.js";
18
- import { decodeDocument } from "../../../core/kernel/document-bytes.js";
19
17
  import {} from "../../../core/repo/paths.js";
20
18
  import { repoPath } from "../../../core/envelope/json.js";
21
19
  import { FleetContext } from "../../../core/fleet-context.js";
@@ -72,8 +70,7 @@ async function documentFindings(paths, check) {
72
70
  continue;
73
71
  let text;
74
72
  try {
75
- text =
76
- check.fleet === undefined ? decodeDocument(await readFile(path), path) : await check.fleet.readText(path);
73
+ text = await check.fleet.readText(path);
77
74
  }
78
75
  catch (err) {
79
76
  findings.push({
@@ -1 +1 @@
1
- {"version":3,"file":"corpus.js","sourceRoot":"","sources":["../../../../src/commands/validate/links/corpus.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where each target's link findings are filed — the ORDER, not the rule.\n *\n * The rule lives in `core/links/`: extract the links, resolve them, name the\n * ones that point at nothing. WHICH documents belong to a service, a feature or\n * the fleet lives there too (`core/links/corpus.ts`), because the glossary's\n * backlink index reads the same list and a second enumeration would be free to\n * disagree with this one. What is left here is the part that belongs to\n * `validate`: reading each document, deciding which target carries the finding,\n * and refusing to let one unreadable file take a whole target down with it.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { pathCaseIndex } from \"../../../core/links/case.js\";\nimport { featureDocuments, fleetDocuments, serviceDocuments } from \"../../../core/links/corpus.js\";\nimport { unresolvedLinkFindings, type LinkScope } from \"../../../core/links/findings.js\";\nimport { featureGlossaryOverlay } from \"../../../core/glossary/delta.js\";\nimport { decodeDocument } from \"../../../core/kernel/document-bytes.js\";\nimport { type ServicePaths } from \"../../../core/repo/paths.js\";\nimport { repoPath } from \"../../../core/envelope/json.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport type { DocsDir, FeatureDir } from \"../../../core/kernel/ids/dirs.js\";\n\n/** Where the findings are filed, and how the documents are read. */\nexport interface LinkCheck {\n docsDir: DocsDir;\n /** The service or feature the report is about; absent at fleet scope. */\n subject?: string;\n fleet?: FleetContext;\n /** Paths an in-flight feature will create — `LinkScope.overlay` says why. */\n overlay?: ReadonlySet<string>;\n}\n\n/** One service's documents, graded on the service target. */\nexport async function serviceLinkFindings(check: LinkCheck, paths: ServicePaths): Promise<Finding[]> {\n return documentFindings(await serviceDocuments(paths), check);\n}\n\n/**\n * One feature's documents, graded on the feature target — with the glossary\n * terms it introduces treated as already living. See `LinkScope.overlay`: a\n * feature cites the word it is adding at that word's future path, and an index\n * built from the living tree alone would refuse the axis's own headline case.\n */\nexport async function featureLinkFindings(check: LinkCheck, featureDir: FeatureDir): Promise<Finding[]> {\n return documentFindings(await featureDocuments(featureDir, check.fleet), {\n ...check,\n overlay: await featureGlossaryOverlay(check.docsDir, featureDir),\n });\n}\n\n/**\n * The fleet's own documents — its ADRs, the living capability tree and the\n * glossary — graded once on the landscape target. A single-target run says\n * nothing about them, the rule `permissions.unenforced` already follows: a\n * finding about the fleet repeated on every service target is the report.\n */\nexport async function fleetLinkFindings(check: LinkCheck): Promise<Finding[]> {\n return documentFindings(await fleetDocuments(check.docsDir, check.fleet), check);\n}\n\n/**\n * Grade a list of documents, skipping the ones that are not there.\n *\n * ABSENCE IS NOT THIS CHECK'S BUSINESS: `service.no-spec` already names a\n * missing `spec.md`, and most services have no runbook and no ADRs at all.\n *\n * AN UNREADABLE DOCUMENT IS NAMED HERE RATHER THAN THROWN, and that is what\n * `link.unreadable` is for. Every other reader of a service's documents is\n * wrapped by `report.ts`'s `guarded`, which turns an IO failure into\n * `service.unreadable` — \"nothing about this service was checked\". That verdict\n * is right for `spec.md` and wrong for an ADR: this check is the first to read\n * those files at all, and a runbook saved as UTF-16 must not be able to blank\n * a service's entire report when the only thing that could not be graded is one\n * document's links.\n */\nasync function documentFindings(paths: string[], check: LinkCheck): Promise<Finding[]> {\n const findings: Finding[] = [];\n // One directory-listing memo for this target's whole corpus. Built here\n // rather than per document because a service's ADRs all resolve through the\n // same two or three directories, and rather than at module scope because a\n // listing that outlived the command would answer for a tree that has moved.\n const scope: LinkScope = { ...check, cases: pathCaseIndex() };\n for (const path of paths) {\n if (!existsSync(path)) continue;\n let text: string;\n try {\n text =\n check.fleet === undefined ? decodeDocument(await readFile(path), path) : await check.fleet.readText(path);\n } catch (err) {\n findings.push({\n severity: \"error\",\n code: \"link.unreadable\",\n ...(check.subject === undefined ? {} : { subject: check.subject }),\n message:\n `${repoPath(check.docsDir, path)} could not be read, so its links were not checked — ` +\n `${err instanceof Error ? err.message : String(err)}`,\n });\n continue;\n }\n findings.push(...unresolvedLinkFindings({ path, text }, scope));\n }\n return findings;\n}\n"]}
1
+ {"version":3,"file":"corpus.js","sourceRoot":"","sources":["../../../../src/commands/validate/links/corpus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACnG,OAAO,EAAE,sBAAsB,EAAkB,MAAM,iCAAiC,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAqB,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAgB,MAAM,oCAAoC,CAAC;AAalE,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,KAAgB,EAAE,KAAmB;IAC7E,OAAO,gBAAgB,CAAC,MAAM,gBAAgB,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,KAAgB,EAAE,UAAsB;IAChF,OAAO,gBAAgB,CAAC,MAAM,gBAAgB,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;QACvE,GAAG,KAAK;QACR,OAAO,EAAE,MAAM,sBAAsB,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC;KACjE,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAgB;IACtD,OAAO,gBAAgB,CAAC,MAAM,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;AACnF,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,KAAK,UAAU,gBAAgB,CAAC,KAAe,EAAE,KAAgB;IAC/D,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,wEAAwE;IACxE,4EAA4E;IAC5E,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAM,KAAK,GAAc,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CAAC;IAC9D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAChC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,iBAAiB;gBACvB,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;gBAClE,OAAO,EACL,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,sDAAsD;oBACtF,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aACxD,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,GAAG,sBAAsB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Where each target's link findings are filed — the ORDER, not the rule.\n *\n * The rule lives in `core/links/`: extract the links, resolve them, name the\n * ones that point at nothing. WHICH documents belong to a service, a feature or\n * the fleet lives there too (`core/links/corpus.ts`), because the glossary's\n * backlink index reads the same list and a second enumeration would be free to\n * disagree with this one. What is left here is the part that belongs to\n * `validate`: reading each document, deciding which target carries the finding,\n * and refusing to let one unreadable file take a whole target down with it.\n */\nimport { existsSync } from \"node:fs\";\nimport { pathCaseIndex } from \"../../../core/links/case.js\";\nimport { featureDocuments, fleetDocuments, serviceDocuments } from \"../../../core/links/corpus.js\";\nimport { unresolvedLinkFindings, type LinkScope } from \"../../../core/links/findings.js\";\nimport { featureGlossaryOverlay } from \"../../../core/glossary/delta.js\";\nimport { type ServicePaths } from \"../../../core/repo/paths.js\";\nimport { repoPath } from \"../../../core/envelope/json.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport type { DocsDir, FeatureDir } from \"../../../core/kernel/ids/dirs.js\";\n\n/** Where the findings are filed, and how the documents are read. */\nexport interface LinkCheck {\n docsDir: DocsDir;\n /** The service or feature the report is about; absent at fleet scope. */\n subject?: string;\n fleet: FleetContext;\n /** Paths an in-flight feature will create — `LinkScope.overlay` says why. */\n overlay?: ReadonlySet<string>;\n}\n\n/** One service's documents, graded on the service target. */\nexport async function serviceLinkFindings(check: LinkCheck, paths: ServicePaths): Promise<Finding[]> {\n return documentFindings(await serviceDocuments(paths), check);\n}\n\n/**\n * One feature's documents, graded on the feature target — with the glossary\n * terms it introduces treated as already living. See `LinkScope.overlay`: a\n * feature cites the word it is adding at that word's future path, and an index\n * built from the living tree alone would refuse the axis's own headline case.\n */\nexport async function featureLinkFindings(check: LinkCheck, featureDir: FeatureDir): Promise<Finding[]> {\n return documentFindings(await featureDocuments(featureDir, check.fleet), {\n ...check,\n overlay: await featureGlossaryOverlay(check.docsDir, featureDir),\n });\n}\n\n/**\n * The fleet's own documents — its ADRs, the living capability tree and the\n * glossary — graded once on the landscape target. A single-target run says\n * nothing about them, the rule `permissions.unenforced` already follows: a\n * finding about the fleet repeated on every service target is the report.\n */\nexport async function fleetLinkFindings(check: LinkCheck): Promise<Finding[]> {\n return documentFindings(await fleetDocuments(check.docsDir, check.fleet), check);\n}\n\n/**\n * Grade a list of documents, skipping the ones that are not there.\n *\n * ABSENCE IS NOT THIS CHECK'S BUSINESS: `service.no-spec` already names a\n * missing `spec.md`, and most services have no runbook and no ADRs at all.\n *\n * AN UNREADABLE DOCUMENT IS NAMED HERE RATHER THAN THROWN, and that is what\n * `link.unreadable` is for. Every other reader of a service's documents is\n * wrapped by `report.ts`'s `guarded`, which turns an IO failure into\n * `service.unreadable` — \"nothing about this service was checked\". That verdict\n * is right for `spec.md` and wrong for an ADR: this check is the first to read\n * those files at all, and a runbook saved as UTF-16 must not be able to blank\n * a service's entire report when the only thing that could not be graded is one\n * document's links.\n */\nasync function documentFindings(paths: string[], check: LinkCheck): Promise<Finding[]> {\n const findings: Finding[] = [];\n // One directory-listing memo for this target's whole corpus. Built here\n // rather than per document because a service's ADRs all resolve through the\n // same two or three directories, and rather than at module scope because a\n // listing that outlived the command would answer for a tree that has moved.\n const scope: LinkScope = { ...check, cases: pathCaseIndex() };\n for (const path of paths) {\n if (!existsSync(path)) continue;\n let text: string;\n try {\n text = await check.fleet.readText(path);\n } catch (err) {\n findings.push({\n severity: \"error\",\n code: \"link.unreadable\",\n ...(check.subject === undefined ? {} : { subject: check.subject }),\n message:\n `${repoPath(check.docsDir, path)} could not be read, so its links were not checked — ` +\n `${err instanceof Error ? err.message : String(err)}`,\n });\n continue;\n }\n findings.push(...unresolvedLinkFindings({ path, text }, scope));\n }\n return findings;\n}\n"]}