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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (648) hide show
  1. package/CHANGELOG.md +1315 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +83 -7
  8. package/SCHEMA.md +569 -98
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +33 -11
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +21 -25
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/seed.js +6 -9
  118. package/dist/commands/seed/seed.js.map +1 -1
  119. package/dist/commands/show/show.js +3 -7
  120. package/dist/commands/show/show.js.map +1 -1
  121. package/dist/commands/status/print.js +13 -4
  122. package/dist/commands/status/print.js.map +1 -1
  123. package/dist/commands/status/status.js +3 -7
  124. package/dist/commands/status/status.js.map +1 -1
  125. package/dist/commands/steps/steps.js +3 -7
  126. package/dist/commands/steps/steps.js.map +1 -1
  127. package/dist/commands/subsystem/edit.js +3 -5
  128. package/dist/commands/subsystem/edit.js.map +1 -1
  129. package/dist/commands/subsystem/move.js +5 -8
  130. package/dist/commands/subsystem/move.js.map +1 -1
  131. package/dist/commands/subsystem/query.js +2 -4
  132. package/dist/commands/subsystem/query.js.map +1 -1
  133. package/dist/commands/subsystem/subsystem.js +4 -7
  134. package/dist/commands/subsystem/subsystem.js.map +1 -1
  135. package/dist/commands/subsystem/sync.js +14 -41
  136. package/dist/commands/subsystem/sync.js.map +1 -1
  137. package/dist/commands/subsystem/txn/txn.js +17 -38
  138. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  139. package/dist/commands/subsystem/txn/views.js +32 -5
  140. package/dist/commands/subsystem/txn/views.js.map +1 -1
  141. package/dist/commands/tasks/render.js +116 -0
  142. package/dist/commands/tasks/render.js.map +1 -0
  143. package/dist/commands/tasks/tasks.js +113 -0
  144. package/dist/commands/tasks/tasks.js.map +1 -0
  145. package/dist/commands/unarchive/manifest.js +6 -9
  146. package/dist/commands/unarchive/manifest.js.map +1 -1
  147. package/dist/commands/unarchive/restore.js +17 -28
  148. package/dist/commands/unarchive/restore.js.map +1 -1
  149. package/dist/commands/unarchive/unarchive.js +22 -30
  150. package/dist/commands/unarchive/unarchive.js.map +1 -1
  151. package/dist/commands/validate/arch-coverage.js +6 -15
  152. package/dist/commands/validate/arch-coverage.js.map +1 -1
  153. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  154. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  155. package/dist/commands/validate/checks/requirements.js +18 -8
  156. package/dist/commands/validate/checks/requirements.js.map +1 -1
  157. package/dist/commands/validate/checks/sources.js +6 -2
  158. package/dist/commands/validate/checks/sources.js.map +1 -1
  159. package/dist/commands/validate/feature.js +23 -19
  160. package/dist/commands/validate/feature.js.map +1 -1
  161. package/dist/commands/validate/fleet/landscape.js +23 -23
  162. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  163. package/dist/commands/validate/fleet/map/attest.js +6 -10
  164. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  165. package/dist/commands/validate/fleet/map/bindings.js +1 -3
  166. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  167. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  168. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  169. package/dist/commands/validate/fleet/obligations.js +1 -5
  170. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  171. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  172. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  173. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  174. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  175. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  176. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  177. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  178. package/dist/commands/validate/fleet/views/root-documents.js +8 -28
  179. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  180. package/dist/commands/validate/links/corpus.js +1 -4
  181. package/dist/commands/validate/links/corpus.js.map +1 -1
  182. package/dist/commands/validate/links/glossary.js.map +1 -1
  183. package/dist/commands/validate/report.js +5 -4
  184. package/dist/commands/validate/report.js.map +1 -1
  185. package/dist/commands/validate/scope.js +113 -0
  186. package/dist/commands/validate/scope.js.map +1 -0
  187. package/dist/commands/validate/service/api.js +16 -104
  188. package/dist/commands/validate/service/api.js.map +1 -1
  189. package/dist/commands/validate/service/contracts/generated.js +134 -0
  190. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  191. package/dist/commands/validate/service/events/events.js +1 -1
  192. package/dist/commands/validate/service/events/events.js.map +1 -1
  193. package/dist/commands/validate/service/events/message-producers.js +1 -1
  194. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  195. package/dist/commands/validate/service/evidence-pins.js +56 -10
  196. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  197. package/dist/commands/validate/service/service.js +16 -26
  198. package/dist/commands/validate/service/service.js.map +1 -1
  199. package/dist/commands/validate/service/specs.js +31 -38
  200. package/dist/commands/validate/service/specs.js.map +1 -1
  201. package/dist/commands/validate/service/spine.js +8 -11
  202. package/dist/commands/validate/service/spine.js.map +1 -1
  203. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  204. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  205. package/dist/commands/validate/validate.js +44 -26
  206. package/dist/commands/validate/validate.js.map +1 -1
  207. package/dist/commands/verify/cross/diff.js +2 -6
  208. package/dist/commands/verify/cross/diff.js.map +1 -1
  209. package/dist/commands/verify/evidence/contract.js +1 -1
  210. package/dist/commands/verify/evidence/contract.js.map +1 -1
  211. package/dist/commands/verify/evidence/read.js +12 -3
  212. package/dist/commands/verify/evidence/read.js.map +1 -1
  213. package/dist/commands/verify/frozen.js +45 -23
  214. package/dist/commands/verify/frozen.js.map +1 -1
  215. package/dist/commands/verify/record.js +69 -17
  216. package/dist/commands/verify/record.js.map +1 -1
  217. package/dist/commands/verify/report.js +27 -44
  218. package/dist/commands/verify/report.js.map +1 -1
  219. package/dist/commands/verify/results.js +112 -30
  220. package/dist/commands/verify/results.js.map +1 -1
  221. package/dist/commands/verify/verify.js +54 -51
  222. package/dist/commands/verify/verify.js.map +1 -1
  223. package/dist/commands/vouch/pack/pack.js +2 -2
  224. package/dist/commands/vouch/pack/pack.js.map +1 -1
  225. package/dist/commands/vouch/pack/print.js +1 -6
  226. package/dist/commands/vouch/pack/print.js.map +1 -1
  227. package/dist/commands/vouch/pack/sections.js +1 -2
  228. package/dist/commands/vouch/pack/sections.js.map +1 -1
  229. package/dist/commands/vouch/run.js +26 -81
  230. package/dist/commands/vouch/run.js.map +1 -1
  231. package/dist/commands/vouch/sample/plan.js +20 -34
  232. package/dist/commands/vouch/sample/plan.js.map +1 -1
  233. package/dist/commands/vouch/vet/verify.js +39 -7
  234. package/dist/commands/vouch/vet/verify.js.map +1 -1
  235. package/dist/commands/vouch/vouch.js +10 -17
  236. package/dist/commands/vouch/vouch.js.map +1 -1
  237. package/dist/core/agent/agents-md/command-map.js +56 -101
  238. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  239. package/dist/core/agent/agents-md/map/explain.js +4 -3
  240. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  241. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  242. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  243. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  244. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  245. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  246. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  247. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  248. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  249. package/dist/core/agent/agents-md/map/status.js +9 -3
  250. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  251. package/dist/core/agent/agents-md/map/subsystem.js +4 -3
  252. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  253. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  254. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  255. package/dist/core/agent/agents-md/refusals.js +32 -18
  256. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  257. package/dist/core/agent/agents-md.js +9 -3
  258. package/dist/core/agent/agents-md.js.map +1 -1
  259. package/dist/core/agent/agents-stamp.js +10 -0
  260. package/dist/core/agent/agents-stamp.js.map +1 -1
  261. package/dist/core/agent/scaffold.js +10 -15
  262. package/dist/core/agent/scaffold.js.map +1 -1
  263. package/dist/core/agent/tools/dialects.js +1 -0
  264. package/dist/core/agent/tools/dialects.js.map +1 -1
  265. package/dist/core/agent/tools/registry.js +41 -76
  266. package/dist/core/agent/tools/registry.js.map +1 -1
  267. package/dist/core/agent/workflows/adopt.js +5 -3
  268. package/dist/core/agent/workflows/adopt.js.map +1 -1
  269. package/dist/core/agent/workflows/check.js +20 -16
  270. package/dist/core/agent/workflows/check.js.map +1 -1
  271. package/dist/core/agent/workflows/closing.js +54 -21
  272. package/dist/core/agent/workflows/closing.js.map +1 -1
  273. package/dist/core/agent/workflows/feature.js +79 -11
  274. package/dist/core/agent/workflows/feature.js.map +1 -1
  275. package/dist/core/agent/workflows/implement.js +91 -13
  276. package/dist/core/agent/workflows/implement.js.map +1 -1
  277. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  278. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  279. package/dist/core/agent/workflows/reference/codes.js +8 -6
  280. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  281. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  282. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  283. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  284. package/dist/core/agent/workflows/reference/spine.js +50 -18
  285. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  286. package/dist/core/asyncapi/baseline/plan.js +14 -81
  287. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  288. package/dist/core/asyncapi/digest.js +30 -25
  289. package/dist/core/asyncapi/digest.js.map +1 -1
  290. package/dist/core/asyncapi/merge/components.js +5 -4
  291. package/dist/core/asyncapi/merge/components.js.map +1 -1
  292. package/dist/core/asyncapi/merge/error.js +7 -2
  293. package/dist/core/asyncapi/merge/error.js.map +1 -1
  294. package/dist/core/asyncapi/merge/markers.js +30 -50
  295. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  296. package/dist/core/asyncapi/merge/merge.js +31 -53
  297. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  298. package/dist/core/asyncapi/merge/pin.js +10 -36
  299. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  300. package/dist/core/asyncapi/model.js.map +1 -1
  301. package/dist/core/asyncapi/read.js +36 -12
  302. package/dist/core/asyncapi/read.js.map +1 -1
  303. package/dist/core/brief/brief.js +6 -18
  304. package/dist/core/brief/brief.js.map +1 -1
  305. package/dist/core/brief/landscape.js +8 -12
  306. package/dist/core/brief/landscape.js.map +1 -1
  307. package/dist/core/brief/map/extend.js +8 -5
  308. package/dist/core/brief/map/extend.js.map +1 -1
  309. package/dist/core/brief/map/owed.js +8 -26
  310. package/dist/core/brief/map/owed.js.map +1 -1
  311. package/dist/core/brief/targets.js +15 -5
  312. package/dist/core/brief/targets.js.map +1 -1
  313. package/dist/core/c4/likec4.js +147 -16
  314. package/dist/core/c4/likec4.js.map +1 -1
  315. package/dist/core/c4/locate/unlocated.js +150 -0
  316. package/dist/core/c4/locate/unlocated.js.map +1 -0
  317. package/dist/core/c4/parsed/values.js +54 -1
  318. package/dist/core/c4/parsed/values.js.map +1 -1
  319. package/dist/core/c4/prefetch/fleet.js +1 -9
  320. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  321. package/dist/core/c4/project/documents.js +31 -6
  322. package/dist/core/c4/project/documents.js.map +1 -1
  323. package/dist/core/c4/project/load.js +6 -17
  324. package/dist/core/c4/project/load.js.map +1 -1
  325. package/dist/core/c4/resolve/external.js +51 -0
  326. package/dist/core/c4/resolve/external.js.map +1 -0
  327. package/dist/core/c4/resolve/service.js +25 -1
  328. package/dist/core/c4/resolve/service.js.map +1 -1
  329. package/dist/core/c4/seed/template.js +15 -1
  330. package/dist/core/c4/seed/template.js.map +1 -1
  331. package/dist/core/c4/service-model/shape.js +2 -22
  332. package/dist/core/c4/service-model/shape.js.map +1 -1
  333. package/dist/core/c4/source-mask.js +288 -4
  334. package/dist/core/c4/source-mask.js.map +1 -1
  335. package/dist/core/c4/source-scan.js +203 -147
  336. package/dist/core/c4/source-scan.js.map +1 -1
  337. package/dist/core/c4/splice/authored-source.js +19 -0
  338. package/dist/core/c4/splice/authored-source.js.map +1 -1
  339. package/dist/core/c4/splice/delta-blocks.js +5 -18
  340. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  341. package/dist/core/c4/splice/landscape-merge.js +13 -8
  342. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  343. package/dist/core/c4/splice/model/merge.js +10 -6
  344. package/dist/core/c4/splice/model/merge.js.map +1 -1
  345. package/dist/core/c4/splice/placement.js +108 -18
  346. package/dist/core/c4/splice/placement.js.map +1 -1
  347. package/dist/core/c4/workspace.js +13 -22
  348. package/dist/core/c4/workspace.js.map +1 -1
  349. package/dist/core/capabilities/capabilities.js +6 -6
  350. package/dist/core/capabilities/capabilities.js.map +1 -1
  351. package/dist/core/capabilities/delta/doc.js +7 -10
  352. package/dist/core/capabilities/delta/doc.js.map +1 -1
  353. package/dist/core/capabilities/findings.js +15 -20
  354. package/dist/core/capabilities/findings.js.map +1 -1
  355. package/dist/core/capabilities/realizes/findings.js +12 -19
  356. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  357. package/dist/core/capabilities/realizes/join.js +0 -20
  358. package/dist/core/capabilities/realizes/join.js.map +1 -1
  359. package/dist/core/capabilities/rollup.js +6 -0
  360. package/dist/core/capabilities/rollup.js.map +1 -1
  361. package/dist/core/capabilities/tree.js +6 -28
  362. package/dist/core/capabilities/tree.js.map +1 -1
  363. package/dist/core/capabilities/usecase-join.js +3 -13
  364. package/dist/core/capabilities/usecase-join.js.map +1 -1
  365. package/dist/core/coherence/authoring/scaffold.js +11 -3
  366. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  367. package/dist/core/coherence/authoring/sentinels.js +2 -1
  368. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  369. package/dist/core/coherence/coherence.js +30 -16
  370. package/dist/core/coherence/coherence.js.map +1 -1
  371. package/dist/core/coherence/consumed/operations.js +70 -0
  372. package/dist/core/coherence/consumed/operations.js.map +1 -0
  373. package/dist/core/coherence/consumed/refusal.js +38 -0
  374. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  375. package/dist/core/coherence/declared.js +60 -34
  376. package/dist/core/coherence/declared.js.map +1 -1
  377. package/dist/core/coherence/events/declared.js +44 -59
  378. package/dist/core/coherence/events/declared.js.map +1 -1
  379. package/dist/core/coherence/events/events.js +119 -8
  380. package/dist/core/coherence/events/events.js.map +1 -1
  381. package/dist/core/coherence/events/grades.js +31 -8
  382. package/dist/core/coherence/events/grades.js.map +1 -1
  383. package/dist/core/coherence/events/lookups.js +14 -82
  384. package/dist/core/coherence/events/lookups.js.map +1 -1
  385. package/dist/core/coherence/lookups.js +18 -78
  386. package/dist/core/coherence/lookups.js.map +1 -1
  387. package/dist/core/coherence/retired/archived.js +285 -0
  388. package/dist/core/coherence/retired/archived.js.map +1 -0
  389. package/dist/core/coherence/retired/event-pair.js +62 -0
  390. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  391. package/dist/core/coherence/retired/merged.js +79 -0
  392. package/dist/core/coherence/retired/merged.js.map +1 -0
  393. package/dist/core/coherence/retired/retirement.js +49 -0
  394. package/dist/core/coherence/retired/retirement.js.map +1 -0
  395. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  396. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  397. package/dist/core/coherence/retired/revival/revived.js +297 -0
  398. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  399. package/dist/core/consumers/after.js +113 -0
  400. package/dist/core/consumers/after.js.map +1 -0
  401. package/dist/core/consumers/consumers.js +296 -0
  402. package/dist/core/consumers/consumers.js.map +1 -0
  403. package/dist/core/consumers/current.js +211 -0
  404. package/dist/core/consumers/current.js.map +1 -0
  405. package/dist/core/consumers/scan.js +46 -0
  406. package/dist/core/consumers/scan.js.map +1 -0
  407. package/dist/core/consumers/state.js +27 -0
  408. package/dist/core/consumers/state.js.map +1 -0
  409. package/dist/core/delta/delta.js +1 -1
  410. package/dist/core/delta/delta.js.map +1 -1
  411. package/dist/core/deployment/delta.js +3 -6
  412. package/dist/core/deployment/delta.js.map +1 -1
  413. package/dist/core/diff/base-git.js +94 -12
  414. package/dist/core/diff/base-git.js.map +1 -1
  415. package/dist/core/diff/base-state.js +28 -16
  416. package/dist/core/diff/base-state.js.map +1 -1
  417. package/dist/core/diff/joins.js +136 -0
  418. package/dist/core/diff/joins.js.map +1 -0
  419. package/dist/core/diff/scope/changed-paths.js +22 -6
  420. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  421. package/dist/core/diff/scope/edges.js +82 -0
  422. package/dist/core/diff/scope/edges.js.map +1 -0
  423. package/dist/core/diff/semantic.js +100 -31
  424. package/dist/core/diff/semantic.js.map +1 -1
  425. package/dist/core/doctor/agents.js +34 -18
  426. package/dist/core/doctor/agents.js.map +1 -1
  427. package/dist/core/doctor/report.js.map +1 -1
  428. package/dist/core/document/apply.js +10 -2
  429. package/dist/core/document/apply.js.map +1 -1
  430. package/dist/core/document/frontmatter.js +55 -0
  431. package/dist/core/document/frontmatter.js.map +1 -1
  432. package/dist/core/document/parse.js +29 -6
  433. package/dist/core/document/parse.js.map +1 -1
  434. package/dist/core/document/scenarios.js +17 -0
  435. package/dist/core/document/scenarios.js.map +1 -1
  436. package/dist/core/document/spec.js +4 -11
  437. package/dist/core/document/spec.js.map +1 -1
  438. package/dist/core/envelope/config.js +10 -0
  439. package/dist/core/envelope/config.js.map +1 -1
  440. package/dist/core/envelope/json.js +34 -0
  441. package/dist/core/envelope/json.js.map +1 -1
  442. package/dist/core/explain/families.js +17 -12
  443. package/dist/core/explain/families.js.map +1 -1
  444. package/dist/core/explain/refusals.js +3 -3
  445. package/dist/core/explain/refusals.js.map +1 -1
  446. package/dist/core/explain/terms.js +18 -9
  447. package/dist/core/explain/terms.js.map +1 -1
  448. package/dist/core/explore/explore.js +2 -7
  449. package/dist/core/explore/explore.js.map +1 -1
  450. package/dist/core/fleet-context.js +34 -95
  451. package/dist/core/fleet-context.js.map +1 -1
  452. package/dist/core/gate/checks.js +5 -2
  453. package/dist/core/gate/checks.js.map +1 -1
  454. package/dist/core/gate/partners.js +4 -2
  455. package/dist/core/gate/partners.js.map +1 -1
  456. package/dist/core/gate/report.js.map +1 -1
  457. package/dist/core/gate/verification.js +75 -12
  458. package/dist/core/gate/verification.js.map +1 -1
  459. package/dist/core/gherkin/stale.js +13 -37
  460. package/dist/core/gherkin/stale.js.map +1 -1
  461. package/dist/core/glossary/tree.js +6 -19
  462. package/dist/core/glossary/tree.js.map +1 -1
  463. package/dist/core/kernel/concurrency.js +19 -0
  464. package/dist/core/kernel/concurrency.js.map +1 -1
  465. package/dist/core/kernel/records.js +11 -0
  466. package/dist/core/kernel/records.js.map +1 -1
  467. package/dist/core/links/backlinks.js +1 -3
  468. package/dist/core/links/backlinks.js.map +1 -1
  469. package/dist/core/links/corpus.js +5 -36
  470. package/dist/core/links/corpus.js.map +1 -1
  471. package/dist/core/mcp/argv.js +101 -0
  472. package/dist/core/mcp/argv.js.map +1 -1
  473. package/dist/core/mcp/author-tools.js +2 -1
  474. package/dist/core/mcp/author-tools.js.map +1 -1
  475. package/dist/core/mcp/protocol.js +2 -2
  476. package/dist/core/mcp/protocol.js.map +1 -1
  477. package/dist/core/mcp/tools.js +43 -33
  478. package/dist/core/mcp/tools.js.map +1 -1
  479. package/dist/core/openapi/baseline/gate.js +53 -12
  480. package/dist/core/openapi/baseline/gate.js.map +1 -1
  481. package/dist/core/openapi/baseline/plan.js +22 -8
  482. package/dist/core/openapi/baseline/plan.js.map +1 -1
  483. package/dist/core/openapi/baseline/record.js +0 -1
  484. package/dist/core/openapi/baseline/record.js.map +1 -1
  485. package/dist/core/openapi/contract-text.js +33 -0
  486. package/dist/core/openapi/contract-text.js.map +1 -0
  487. package/dist/core/openapi/contract-tree.js +56 -0
  488. package/dist/core/openapi/contract-tree.js.map +1 -0
  489. package/dist/core/openapi/depth.js +38 -24
  490. package/dist/core/openapi/depth.js.map +1 -1
  491. package/dist/core/openapi/digest.js +52 -2
  492. package/dist/core/openapi/digest.js.map +1 -1
  493. package/dist/core/openapi/doc.js +12 -4
  494. package/dist/core/openapi/doc.js.map +1 -1
  495. package/dist/core/openapi/merge/components.js +25 -60
  496. package/dist/core/openapi/merge/components.js.map +1 -1
  497. package/dist/core/openapi/merge/error.js +3 -13
  498. package/dist/core/openapi/merge/error.js.map +1 -1
  499. package/dist/core/openapi/merge/markers.js +26 -30
  500. package/dist/core/openapi/merge/markers.js.map +1 -1
  501. package/dist/core/openapi/merge/merge.js +26 -52
  502. package/dist/core/openapi/merge/merge.js.map +1 -1
  503. package/dist/core/openapi/merge/pin.js +15 -35
  504. package/dist/core/openapi/merge/pin.js.map +1 -1
  505. package/dist/core/openspec/decide/verdict.js.map +1 -1
  506. package/dist/core/openspec/inventory.js +4 -5
  507. package/dist/core/openspec/inventory.js.map +1 -1
  508. package/dist/core/openspec/model/mapping.js +30 -28
  509. package/dist/core/openspec/model/mapping.js.map +1 -1
  510. package/dist/core/openspec/model/model.js +12 -7
  511. package/dist/core/openspec/model/model.js.map +1 -1
  512. package/dist/core/openspec/scan/changes.js +10 -4
  513. package/dist/core/openspec/scan/changes.js.map +1 -1
  514. package/dist/core/openspec/scan/config.js +7 -16
  515. package/dist/core/openspec/scan/config.js.map +1 -1
  516. package/dist/core/openspec/scan/shape.js +63 -19
  517. package/dist/core/openspec/scan/shape.js.map +1 -1
  518. package/dist/core/openspec/scan/walk.js +21 -38
  519. package/dist/core/openspec/scan/walk.js.map +1 -1
  520. package/dist/core/pack/living.js +2 -16
  521. package/dist/core/pack/living.js.map +1 -1
  522. package/dist/core/provenance/findings.js +48 -12
  523. package/dist/core/provenance/findings.js.map +1 -1
  524. package/dist/core/provenance/git.js +33 -57
  525. package/dist/core/provenance/git.js.map +1 -1
  526. package/dist/core/provenance/gitq/moves.js +71 -114
  527. package/dist/core/provenance/gitq/moves.js.map +1 -1
  528. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  529. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  530. package/dist/core/provenance/sources.js +23 -6
  531. package/dist/core/provenance/sources.js.map +1 -1
  532. package/dist/core/provenance/stamp.js +5 -17
  533. package/dist/core/provenance/stamp.js.map +1 -1
  534. package/dist/core/repo/entries.js.map +1 -1
  535. package/dist/core/repo/repo.js +24 -14
  536. package/dist/core/repo/repo.js.map +1 -1
  537. package/dist/core/repo/tree/find.js +12 -3
  538. package/dist/core/repo/tree/find.js.map +1 -1
  539. package/dist/core/repo/tree/fs.js +27 -1
  540. package/dist/core/repo/tree/fs.js.map +1 -1
  541. package/dist/core/repo/tree/walk.js +8 -30
  542. package/dist/core/repo/tree/walk.js.map +1 -1
  543. package/dist/core/results.js +47 -12
  544. package/dist/core/results.js.map +1 -1
  545. package/dist/core/staging/commit.js +10 -12
  546. package/dist/core/staging/commit.js.map +1 -1
  547. package/dist/core/staging/lock.js +74 -12
  548. package/dist/core/staging/lock.js.map +1 -1
  549. package/dist/core/staging/recovery/intent.js +2 -5
  550. package/dist/core/staging/recovery/intent.js.map +1 -1
  551. package/dist/core/staging/recovery/recover.js +60 -1
  552. package/dist/core/staging/recovery/recover.js.map +1 -1
  553. package/dist/core/staging/snapshot/manifest.js +152 -0
  554. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  555. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  556. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  557. package/dist/core/status/actions/execution.js +11 -0
  558. package/dist/core/status/actions/execution.js.map +1 -1
  559. package/dist/core/status/feature/artifacts.js +2 -2
  560. package/dist/core/status/feature/artifacts.js.map +1 -1
  561. package/dist/core/status/feature/feature.js +5 -11
  562. package/dist/core/status/feature/feature.js.map +1 -1
  563. package/dist/core/status/fleet/fleet.js +6 -2
  564. package/dist/core/status/fleet/fleet.js.map +1 -1
  565. package/dist/core/status/fleet/next.js +26 -3
  566. package/dist/core/status/fleet/next.js.map +1 -1
  567. package/dist/core/status/report.js.map +1 -1
  568. package/dist/core/status/verification.js +30 -15
  569. package/dist/core/status/verification.js.map +1 -1
  570. package/dist/core/tasks/packet.js +174 -0
  571. package/dist/core/tasks/packet.js.map +1 -0
  572. package/dist/core/tasks/record.js +43 -0
  573. package/dist/core/tasks/record.js.map +1 -0
  574. package/dist/core/tasks/sources.js +181 -0
  575. package/dist/core/tasks/sources.js.map +1 -0
  576. package/dist/core/tasks/stamps.js +76 -0
  577. package/dist/core/tasks/stamps.js.map +1 -0
  578. package/dist/core/tasks/unit.js +0 -0
  579. package/dist/core/tasks/unit.js.map +1 -0
  580. package/dist/core/usecases/capability.js +11 -14
  581. package/dist/core/usecases/capability.js.map +1 -1
  582. package/dist/core/usecases/delta/flows.js +5 -7
  583. package/dist/core/usecases/delta/flows.js.map +1 -1
  584. package/dist/core/usecases/fleet.js +178 -31
  585. package/dist/core/usecases/fleet.js.map +1 -1
  586. package/dist/core/usecases/operations.js +5 -4
  587. package/dist/core/usecases/operations.js.map +1 -1
  588. package/dist/core/usecases/service/flows.js +9 -6
  589. package/dist/core/usecases/service/flows.js.map +1 -1
  590. package/dist/core/usecases/suites/participants.js +83 -0
  591. package/dist/core/usecases/suites/participants.js.map +1 -0
  592. package/dist/core/usecases/suites/suites.js +45 -0
  593. package/dist/core/usecases/suites/suites.js.map +1 -0
  594. package/dist/core/verify/answers.js +12 -5
  595. package/dist/core/verify/answers.js.map +1 -1
  596. package/dist/core/verify/build.js +102 -82
  597. package/dist/core/verify/build.js.map +1 -1
  598. package/dist/core/verify/checklist.js +3 -0
  599. package/dist/core/verify/checklist.js.map +1 -1
  600. package/dist/core/verify/location.js +67 -0
  601. package/dist/core/verify/location.js.map +1 -0
  602. package/dist/core/verify/pins/lint.js +3 -2
  603. package/dist/core/verify/pins/lint.js.map +1 -1
  604. package/dist/core/verify/pins/pin.js +1 -1
  605. package/dist/core/verify/pins/pin.js.map +1 -1
  606. package/dist/core/verify/read/file.js +224 -0
  607. package/dist/core/verify/read/file.js.map +1 -0
  608. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  609. package/dist/core/verify/read/shape.js.map +1 -0
  610. package/dist/core/verify/read/union.js +105 -0
  611. package/dist/core/verify/read/union.js.map +1 -0
  612. package/dist/core/verify/record.js +35 -12
  613. package/dist/core/verify/record.js.map +1 -1
  614. package/dist/core/verify/store/commit.js +40 -30
  615. package/dist/core/verify/store/commit.js.map +1 -1
  616. package/dist/core/verify/store/render.js +8 -4
  617. package/dist/core/verify/store/render.js.map +1 -1
  618. package/dist/core/vocabulary/issue.js +1 -0
  619. package/dist/core/vocabulary/issue.js.map +1 -1
  620. package/dist/core/vocabulary/maturity.js +12 -2
  621. package/dist/core/vocabulary/maturity.js.map +1 -1
  622. package/examples/README.md +21 -6
  623. package/examples/docs/architecture/landscape.likec4 +8 -0
  624. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  625. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  626. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  627. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  628. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  629. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  630. package/package.json +1 -1
  631. package/dist/commands/validate/checks/vocabulary.js +0 -16
  632. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  633. package/dist/core/agent/agents-md/artifacts.js +0 -221
  634. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  635. package/dist/core/agent/agents-md/cycle.js +0 -180
  636. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  637. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  638. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  639. package/dist/core/agent/agents-md/spine.js +0 -40
  640. package/dist/core/agent/agents-md/spine.js.map +0 -1
  641. package/dist/core/coherence/events/merged.js +0 -40
  642. package/dist/core/coherence/events/merged.js.map +0 -1
  643. package/dist/core/diff/current-state.js +0 -96
  644. package/dist/core/diff/current-state.js.map +0 -1
  645. package/dist/core/diff/victims.js +0 -256
  646. package/dist/core/diff/victims.js.map +0 -1
  647. package/dist/core/staging/snapshot.js.map +0 -1
  648. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"print.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/scorecard/print.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet scorecard's one table — how the `--all` text report renders what\n * `./scorecard.js` derived. A module of its own for validate's own discipline\n * (validate.ts's header: the checks produce findings and none of them print;\n * printing happens after the last target is graded), and so the derivation\n * and the formatting do not share a file whose one seam is exactly that.\n *\n * One line per axis, ceiling before actual, read left to right the way the\n * numbers are read in a CI log — except the adoption row, which states\n * participation over the denominator (`requirements 3/40`): adoption is an\n * N-of-M spread, not a ceiling chased by an actual, and forcing it into the\n * arrow idiom would read as one. It prints under `--errors-only` too, on\n * purpose: like the summary footer it is a rollup, not a finding, and\n * `--errors-only` is a findings-rendering lever (report.ts).\n */\nimport { ARTIFACT_STATUSES } from \"../../../../core/status/report.js\";\nimport { ANSWERED_BY } from \"../../../../core/verify/answers.js\";\nimport { VERIFICATION_VERDICTS } from \"../../../../core/verify/record.js\";\nimport { MATURITY_LADDER } from \"../../../../core/vocabulary/maturity.js\";\nimport { ADOPTION_AXES } from \"./adoption.js\";\nimport { type Scorecard } from \"./scorecard.js\";\n\nexport function printScorecard(card: Scorecard): void {\n const { maturity, provenance: p, verification: v, operations: o, messages: m, c4, features: f } =\n card;\n console.log(\"\\nfleet scorecard (ceiling → actual)\");\n console.log(\n ` services ${card.services} — ` +\n MATURITY_LADDER.map((rung) => `${maturity[rung]} ${rung}`).join(\" · \"),\n );\n console.log(\n // The sampled count rides INSIDE the vouched number rather than after it,\n // because that is where it changes the reading: \"120 vouched\" and \"120\n // vouched (100 sampled)\" are different fleets, and a reader who stops at\n // the first number must not have been told the stronger of the two.\n ` provenance ${p.vouched} vouched${p.sampledVouched > 0 ? ` (${p.sampledVouched} sampled)` : \"\"} — ` +\n `${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`,\n );\n // The ceiling is the features axis's `active` — the payload deliberately\n // carries that denominator once, so this line reads it from there.\n console.log(\n ` verification ${f.active} feature(s) → ${v.recorded} recorded — ` +\n VERIFICATION_VERDICTS.map((verdict) => `${v.verdicts[verdict]} ${verdict}`).join(\" · \") +\n ` — claims ${v.claims.confirmed}/${v.claims.total} confirmed ` +\n // Every provenance, under its own `answered_by` spelling and always all\n // three — the same stable-shape rendering as the verdicts before it. Two\n // buckets could not name what they held: the line said \"runner\" over\n // claims no runner answered, and no label short of the real one fixes\n // that, because `external-runner` is neither of the other two.\n `(${ANSWERED_BY.map((who) => `${v.claims.answered[who]} ${who}`).join(\" · \")})`,\n );\n console.log(\n ` operations ${o.defined} defined → ${o.governed} governed · ` +\n `${o.deprecated} deprecated (${o.deprecatedStillConsumed} still consumed)`,\n );\n console.log(` messages ${m.defined} defined → ${m.linked} linked`);\n console.log(` c4 ${c4.elements} elements → ${c4.covered} covered`);\n // Participation per axis over the one denominator, then the same facts as\n // the sentence a standup reads out: which axes the fleet has started at\n // all. \"adopted\" here means at least one participating service — the\n // per-axis counts on the line above say how many.\n const a = card.adoption;\n console.log(\n ` adoption ` +\n ADOPTION_AXES.map((axis) => `${axis} ${a[axis]}/${card.services}`).join(\" · \"),\n );\n const adopted = ADOPTION_AXES.filter((axis) => a[axis] > 0);\n const notStarted = ADOPTION_AXES.filter((axis) => a[axis] === 0);\n console.log(\n ` axes adopted: ${adopted.length === 0 ? \"none\" : adopted.join(\", \")}` +\n (notStarted.length === 0 ? \"\" : ` · not started: ${notStarted.join(\", \")}`),\n );\n console.log(\n ` features ${f.active} active — ` +\n ARTIFACT_STATUSES.map((stage) => `${f.stages[stage]} ${stage}`).join(\" · \"),\n );\n}\n"]}
1
+ {"version":3,"file":"print.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/scorecard/print.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet scorecard's one table — how the `--all` text report renders what\n * `./scorecard.js` derived. A module of its own for validate's own discipline\n * (validate.ts's header: the checks produce findings and none of them print;\n * printing happens after the last target is graded), and so the derivation\n * and the formatting do not share a file whose one seam is exactly that.\n *\n * One line per axis, ceiling before actual, read left to right the way the\n * numbers are read in a CI log — except the adoption row, which states\n * participation over the denominator (`requirements 3/40`): adoption is an\n * N-of-M spread, not a ceiling chased by an actual, and forcing it into the\n * arrow idiom would read as one. It prints under `--errors-only` too, on\n * purpose: like the summary footer it is a rollup, not a finding, and\n * `--errors-only` is a findings-rendering lever (report.ts).\n */\nimport { ARTIFACT_STATUSES } from \"../../../../core/status/report.js\";\nimport { ANSWERED_BY } from \"../../../../core/verify/answers.js\";\nimport { VERIFICATION_VERDICTS } from \"../../../../core/verify/record.js\";\nimport { MATURITY_LADDER } from \"../../../../core/vocabulary/maturity.js\";\nimport { ADOPTION_AXES } from \"./adoption.js\";\nimport { type Scorecard } from \"./scorecard.js\";\n\nexport function printScorecard(card: Scorecard): void {\n const { maturity, provenance: p, verification: v, operations: o, messages: m, c4, features: f } =\n card;\n console.log(\"\\nfleet scorecard (ceiling → actual)\");\n console.log(\n ` services ${card.services} — ` +\n MATURITY_LADDER.map((rung) => `${maturity[rung]} ${rung}`).join(\" · \"),\n );\n console.log(\n // The sampled count rides INSIDE the vouched number rather than after it,\n // because that is where it changes the reading: \"120 vouched\" and \"120\n // vouched (100 sampled)\" are different fleets, and a reader who stops at\n // the first number must not have been told the stronger of the two.\n // The content count rides beside the digest count rather than inside the\n // vouched one: both are staleness, of the words and of the code, and\n // \"0 stale digest(s)\" alone read as \"nothing is stale\" in a run that had\n // just warned `content.stale`.\n ` provenance ${p.vouched} vouched${p.sampledVouched > 0 ? ` (${p.sampledVouched} sampled)` : \"\"} — ` +\n `${p.contentStale} stale content · ${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`,\n );\n // The ceiling is the features axis's `active` — the payload deliberately\n // carries that denominator once, so this line reads it from there.\n console.log(\n ` verification ${f.active} feature(s) → ${v.recorded} recorded — ` +\n VERIFICATION_VERDICTS.map((verdict) => `${v.verdicts[verdict]} ${verdict}`).join(\" · \") +\n ` — claims ${v.claims.confirmed}/${v.claims.total} confirmed ` +\n // Every provenance, under its own `answered_by` spelling and always all\n // three — the same stable-shape rendering as the verdicts before it. Two\n // buckets could not name what they held: the line said \"runner\" over\n // claims no runner answered, and no label short of the real one fixes\n // that, because `external-runner` is neither of the other two.\n `(${ANSWERED_BY.map((who) => `${v.claims.answered[who]} ${who}`).join(\" · \")})`,\n );\n console.log(\n ` operations ${o.defined} defined → ${o.governed} governed · ` +\n `${o.deprecated} deprecated (${o.deprecatedStillConsumed} still consumed)`,\n );\n console.log(` messages ${m.defined} defined → ${m.linked} linked`);\n console.log(` c4 ${c4.elements} elements → ${c4.covered} covered`);\n // Participation per axis over the one denominator, then the same facts as\n // the sentence a standup reads out: which axes the fleet has started at\n // all. \"adopted\" here means at least one participating service — the\n // per-axis counts on the line above say how many.\n const a = card.adoption;\n console.log(\n ` adoption ` +\n ADOPTION_AXES.map((axis) => `${axis} ${a[axis]}/${card.services}`).join(\" · \"),\n );\n const adopted = ADOPTION_AXES.filter((axis) => a[axis] > 0);\n const notStarted = ADOPTION_AXES.filter((axis) => a[axis] === 0);\n console.log(\n ` axes adopted: ${adopted.length === 0 ? \"none\" : adopted.join(\", \")}` +\n (notStarted.length === 0 ? \"\" : ` · not started: ${notStarted.join(\", \")}`),\n );\n console.log(\n ` features ${f.active} active — ` +\n ARTIFACT_STATUSES.map((stage) => `${f.stages[stage]} ${stage}`).join(\" · \"),\n );\n}\n"]}
@@ -101,12 +101,15 @@ async function derive(input) {
101
101
  const covered = eligible.filter((e) => covers.some((c) => coversElement(c, e))).length;
102
102
  // Verification: `list`'s own cells, so the two commands cannot disagree
103
103
  // about a record's verdict. Active features only — an archived record is
104
- // frozen history, not fleet debt. This fan-out is NOT memo hits:
105
- // readVerification is one small file read per feature, and a recorded cell
106
- // additionally derives its checklist (the same cost `list` pays for the same
107
- // column) — real filesystem work, so it runs through the bounded pool.
104
+ // frozen history, not fleet debt. This fan-out is NOT memo hits: reading a
105
+ // feature's record is a directory listing plus a small read per record file
106
+ // (the one-file `verification.yaml` and one `verification/<svc>.yaml` per
107
+ // attesting service), and a recorded cell additionally derives its checklist
108
+ // (the same cost `list` pays for the same column; only its enumerations and
109
+ // delta parse are memo hits through `fleet`) — real filesystem work, so it
110
+ // runs through the bounded pool.
108
111
  const features = await fleet.listFeatures(docsDir);
109
- const cells = await inOrder(features, (f) => featureVerification(docsDir, f));
112
+ const cells = await inOrder(features, (f) => featureVerification(docsDir, f, fleet));
110
113
  const recorded = cells.filter((c) => c !== null);
111
114
  const verdicts = Object.fromEntries(VERIFICATION_VERDICTS.map((v) => [v, 0]));
112
115
  for (const cell of recorded)
@@ -124,18 +127,21 @@ async function derive(input) {
124
127
  maturity,
125
128
  provenance: {
126
129
  vouched: maturity.vouched,
127
- // Both counts join through `subjectsWith` on the run's OWN findings, so
130
+ // Every count joins through `subjectsWith` on the run's OWN findings, so
128
131
  // each is structurally the same number as the report beside it — the
129
132
  // unverifiable count is the footer's (unverifiableSubjects is the same
130
133
  // call on the same code; this package cannot import the parent back).
131
- // Both codes are LITERALS — provenance/sources.ts's own spellings —
132
- // because the stable-code collector reads counting sites and refuses a
133
- // slot it cannot read; the collector is also what convicts a typo here.
134
+ // Every code is a LITERAL — the emitter's own spelling — because the
135
+ // stable-code collector reads counting sites and refuses a slot it
136
+ // cannot read; the collector is also what convicts a typo here.
134
137
  staleDigests: subjectsWith(targets, "sources.stale"),
135
138
  unverifiableFromHere: subjectsWith(targets, "sources.unverifiable-from-here"),
136
- // Doc-side, so unlike `staleDigests` this one is complete even from the
137
- // docs repo: `sources.sampled-vouch` needs no service repo to fire.
139
+ // Doc-side, so unlike `staleDigests` these two are complete even from
140
+ // the docs repo: neither `sources.sampled-vouch` nor `content.stale`
141
+ // needs a service repo to fire. `content.stale` names no subject, and
142
+ // `subjectsWith` falls back to the target id — the service, here.
138
143
  sampledVouched: subjectsWith(targets, "sources.sampled-vouch"),
144
+ contentStale: subjectsWith(targets, "content.stale"),
139
145
  },
140
146
  verification: {
141
147
  recorded: recorded.length,
@@ -1 +1 @@
1
- {"version":3,"file":"scorecard.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/scorecard/scorecard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet scorecard: ceiling-vs-actual aggregates for `validate --all`,\n * recomputed per invocation from the same memoized FleetContext reads the run\n * already paid for. loam stores no history — week-over-week tracking is the\n * caller's pipeline's job: capture the `scorecard` payload key per run into a\n * metrics store. Each axis is a pair of domain-named counts (`defined` vs\n * `governed`) rather than abstract ceiling/actual keys, because payload keys\n * freeze the moment they ship and the domain nouns survive axis nuance.\n *\n * This module derives and never prints; `./print.js` renders the one table\n * the `--all` text report appends, and `./contracts.js` owns the per-service\n * contract reads it sums.\n */\nimport { existsSync } from \"node:fs\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport { coversElement } from \"../../../../core/c4/arch.js\";\nimport { serviceResolver } from \"../../../../core/c4/resolve/service.js\";\nimport { type FleetContext } from \"../../../../core/fleet-context.js\";\nimport { type DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { landscapePath } from \"../../../../core/repo/paths.js\";\nimport { fleetStatus } from \"../../../../core/status/fleet/fleet.js\";\nimport { ARTIFACT_STATUSES, type ArtifactStatus } from \"../../../../core/status/report.js\";\nimport { ANSWERED_BY, type AnsweredBy } from \"../../../../core/verify/answers.js\";\nimport { VERIFICATION_VERDICTS, type VerificationVerdict } from \"../../../../core/verify/record.js\";\nimport { maturityRollup, type Maturity } from \"../../../../core/vocabulary/maturity.js\";\nimport { subjectsWith, type TargetReport } from \"../../../../core/vocabulary/report.js\";\nimport { featureVerification, serviceViews, type VerificationCell } from \"../../../list/views.js\";\nimport { drawnSystems } from \"../census.js\";\nimport { readLandscape } from \"../load.js\";\nimport { adoptionOf, type Adoption } from \"./adoption.js\";\nimport { contractCounts, type ContractCounts } from \"./contracts.js\";\n\n/** What `buildScorecard` needs of the run that is already in hand. */\nexport interface ScorecardInput {\n docsDir: DocsDir;\n /** The run's graded targets — the source for the one count only a repo-bound check can make (`sources.stale` walks the service's own repository, and must not run twice). */\n targets: TargetReport[];\n /** The run's memoized snapshot: every read below is a hit on it. */\n fleet: FleetContext;\n /** loam.json's `service` — the one repo whose `sources` are checkable from here (serviceViews' rule). */\n boundService: string | undefined;\n}\n\n/**\n * The additive `scorecard` payload key of `validate --all --json`, and the\n * `--all` text table. Shape frozen on ship, like every payload key: fields\n * may be added, never changed or removed.\n */\nexport interface Scorecard {\n /** Every `services/` directory the enumeration lists — the maturity rollup's denominator. */\n services: number;\n /** Counts per adoption-maturity rung, every rung present (core/vocabulary/maturity.ts). */\n maturity: Record<Maturity, number>;\n provenance: {\n /** Services at the `vouched` rung — a person stamped them. */\n vouched: number;\n /**\n * Of those, the ones where the person read a recorded SAMPLE of the\n * document rather than all of it, by subject off this run's\n * `sources.sampled-vouch` findings.\n *\n * Additive beside `vouched` rather than subtracted from it, because the\n * rung is real — a person did stamp it — but a dial reading\n * `vouched: 120` where 100 of them are sampled is exactly the\n * partial-trust-as-full-trust misreading the scope exists to prevent. A\n * dashboard that wants the stricter number subtracts this one knowingly.\n */\n sampledVouched: number;\n /**\n * Services whose stamped sources digest no longer matches the code, by\n * subject off this run's `sources.stale` findings — only what THIS run\n * could verify, so read it beside the count below, never alone.\n */\n staleDigests: number;\n /** Services whose declared `sources` only their own repos can check. */\n unverifiableFromHere: number;\n };\n verification: {\n /**\n * Active features with a verification record beside them. Its ceiling is\n * `features.active` — deliberately not repeated here: one denominator,\n * derived once, so the two axes cannot disagree about how many features\n * are in flight.\n */\n recorded: number;\n /** The recorded features' three-valued verdicts (core/verify/record.ts), every key present. */\n verdicts: Record<VerificationVerdict, number>;\n claims: {\n /** Questions asked across every recorded feature, `tallyRecord`'s counting. */\n total: number;\n confirmed: number;\n /**\n * Of the confirmed, one count per `answered_by` value (`ANSWERED_BY`),\n * every key present — the stable-shape rule `verdicts` and `stages`\n * follow, so a fourth provenance would appear rather than be absorbed.\n *\n * Read off the answers' own provenance, never by subtracting the\n * attested share: `attestedClaims` counts `scenario.tested` alone, so\n * `confirmed - attested` filed every OTHER kind an agent confirmed — a\n * permission, a capability, an exposed operation — under a test runner\n * that had not run. A record with four agent answers printed as three\n * runner and one agent, which is the reading this axis exists to give.\n */\n answered: Record<AnsweredBy, number>;\n };\n };\n /**\n * The HTTP axis, with a deliberate UNIT split: `defined`/`governed` count\n * contract SLOTS, exactly as the API axis's own findings count operations,\n * while `deprecated`/`deprecatedStillConsumed` count DISTINCT operationIds —\n * a duplicate-id slot pair (already its own warning) retires as ONE\n * operation, one migration owed.\n */\n operations: {\n /** Slots the living contracts define (removal markers are never operations). */\n defined: number;\n /** Slots (like `defined`) whose id a living requirement's `Operations:` line governs. */\n governed: number;\n /** Distinct deprecated operationIds. */\n deprecated: number;\n /** Distinct deprecated ids something still joins to — a living `Operations:` line, or an op-linked inbound landscape edge. */\n deprecatedStillConsumed: number;\n };\n messages: {\n /** Distinct message names the living async contracts declare. */\n defined: number;\n /** Of them, the ones a requirement's `Publishes:`/`Consumes:` line governs. */\n linked: number;\n };\n c4: {\n /** The drawn SYSTEMS — `drawnSystems`, the fleet map's own census, so the map's exemptions (actors, INFRA kinds the fleet runs rather than adopts, `#external`, groupings, containers) and this count cannot drift. An INFRA-kind element that DOES stand for a `services/<id>/` stays in, so adopting a store never shrinks the denominator. */\n elements: number;\n /** Of them, the ones some living arch requirement's `Covers:` entry names. */\n covered: number;\n };\n /**\n * Services PARTICIPATING in each contract axis — how far the staged\n * adoption has spread, axis by axis. The denominator is `services`, carried\n * once above like every other denominator here. One mechanical rule per\n * axis, per service: `requirements` — spec.md holds at least one\n * `### Requirement:` block (content, not file presence, so a fleet of empty\n * adopt scaffolds honestly reads \"not started\"); `arch` — the same for\n * arch.spec.md; `openapi` / `asyncapi` — the contract file exists;\n * `permissions` / `capabilities` — at least one non-REMOVED requirement in\n * either spec document carries a `Requires:` / `Capability:` entry (the\n * used-set rule the fleet vocabulary checks apply). An axis at 0 is \"not\n * started\" — distinct from partially adopted — and is what licenses the\n * text report's per-axis warning banner (./adoption.js). An unreadable\n * service counts as participating in nothing, the same zeros doctrine as\n * every count here — read these beside the run's `service.unreadable`\n * findings, and note the text renderer refuses to group warnings on a run\n * that has any (report.ts).\n */\n adoption: Adoption;\n features: {\n active: number;\n /**\n * The fleet form's stage per active feature — all five ARTIFACT_STATUSES\n * keys even though that form never grades `draft` (the maturityRollup\n * rule: a stable shape a dashboard can diff).\n */\n stages: Record<ArtifactStatus, number>;\n };\n}\n\n/**\n * Derive the scorecard, or decline. The failure story, per axis: a memoized\n * read that FAILED rejects again on every await, so the per-service fan-out\n * contains those per subject — an unreadable service contributes zeros while\n * its own `service.unreadable` finding names the cause — and every landscape\n * read goes through `../load.ts`'s containment, so an unreadable fleet map\n * zeroes the map-derived axes on every platform instead of deleting the card.\n * The catch is the last line of defence for the fleet-wide reads this module\n * does not own (the verification cells, the fleet status): a rollup that can\n * die must not take the graded report with it. No number rather than a wrong\n * number.\n */\nexport async function buildScorecard(input: ScorecardInput): Promise<Scorecard | null> {\n try {\n return await derive(input);\n } catch {\n // Unreadable is not zero: the axes cannot be counted, so no claim is made.\n return null;\n }\n}\n\nasync function derive(input: ScorecardInput): Promise<Scorecard> {\n const { docsDir, targets, fleet, boundService } = input;\n const services = await fleet.listServices(docsDir);\n\n // The landscape, through the run's memo and the fleet target's containment:\n // an unreadable file grades as one that did not parse, and a landscape that\n // proves nothing contributes no inbound edges and no element census —\n // fail-closed, exactly as the no-openapi grace reads the same absence.\n // Loaded FIRST and handed to serviceViews below, so the maturity rollup\n // costs no second parse of a document this run already holds.\n const lp = landscapePath(docsDir);\n const land = existsSync(lp) ? await readLandscape(() => fleet.loadLikeC4(lp)) : null;\n const proven = land !== null && land.errors.length === 0;\n\n const views = await serviceViews(docsDir, services, boundService, land);\n const maturity = maturityRollup(views);\n\n // Inbound op-linked edges per service, resolved exactly as the API axis\n // resolves them (service/api.ts): the container-aware resolver, so an edge\n // drawn into `paymentService.api` counts for the service that owns it.\n const known: ReadonlySet<string> = new Set(services.map((s) => s.id));\n const inbound = new Map<string, Set<string>>();\n if (proven) {\n const svcOf = serviceResolver(land.elements, known);\n for (const r of land.relationships) {\n if (r.op === undefined) continue;\n const owner: string = svcOf(r.target);\n const ops = inbound.get(owner) ?? new Set<string>();\n ops.add(r.op);\n inbound.set(owner, ops);\n }\n }\n\n // Every read in THIS fan-out is a memo hit — the service targets already\n // parsed both requirement documents and both contracts — so it spawns no new\n // filesystem work and needs no pool (unlike the verification fan-out below).\n const perService = await Promise.all(\n services.map((svc) => contractCounts(svc, fleet, inbound.get(svc.id) ?? new Set<string>())),\n );\n const sum = (pick: (c: ContractCounts) => number): number =>\n perService.reduce((n, c) => n + pick(c), 0);\n const operations = {\n defined: sum((c) => c.operations.defined),\n governed: sum((c) => c.operations.governed),\n deprecated: sum((c) => c.operations.deprecated),\n deprecatedStillConsumed: sum((c) => c.operations.deprecatedStillConsumed),\n };\n const messages = { defined: sum((c) => c.messages.defined), linked: sum((c) => c.messages.linked) };\n const adoption = adoptionOf(perService);\n\n // C4 coverage: the fleet map's own system census (`drawnSystems` — the set\n // the undocumented pass walks) against every living arch.spec.md's `Covers:`\n // entries. Element entries only — an edge entry names no element.\n const covers = perService.flatMap((c) => c.covers);\n const eligible = proven ? drawnSystems(land.elements, known) : [];\n const covered = eligible.filter((e) => covers.some((c) => coversElement(c, e))).length;\n\n // Verification: `list`'s own cells, so the two commands cannot disagree\n // about a record's verdict. Active features only — an archived record is\n // frozen history, not fleet debt. This fan-out is NOT memo hits:\n // readVerification is one small file read per feature, and a recorded cell\n // additionally derives its checklist (the same cost `list` pays for the same\n // column) — real filesystem work, so it runs through the bounded pool.\n const features = await fleet.listFeatures(docsDir);\n const cells = await inOrder(features, (f) => featureVerification(docsDir, f));\n const recorded = cells.filter((c): c is VerificationCell => c !== null);\n const verdicts = Object.fromEntries(VERIFICATION_VERDICTS.map((v) => [v, 0])) as Record<VerificationVerdict, number>;\n for (const cell of recorded) verdicts[cell.verdict] += 1;\n const confirmed = recorded.reduce((n, c) => n + c.confirmed, 0);\n const answered = Object.fromEntries(\n ANSWERED_BY.map((who) => [who, recorded.reduce((n, c) => n + c.answered[who], 0)]),\n ) as Record<AnsweredBy, number>;\n\n // The fleet form of `status`, on this run's own snapshot: presence and\n // dependency order, nothing graded — the stages an agent already branches on.\n const status = await fleetStatus(docsDir, { context: fleet });\n const stages = Object.fromEntries(ARTIFACT_STATUSES.map((s) => [s, 0])) as Record<ArtifactStatus, number>;\n for (const f of status.features) stages[f.stage] += 1;\n\n return {\n services: services.length,\n maturity,\n provenance: {\n vouched: maturity.vouched,\n // Both counts join through `subjectsWith` on the run's OWN findings, so\n // each is structurally the same number as the report beside it — the\n // unverifiable count is the footer's (unverifiableSubjects is the same\n // call on the same code; this package cannot import the parent back).\n // Both codes are LITERALS — provenance/sources.ts's own spellings —\n // because the stable-code collector reads counting sites and refuses a\n // slot it cannot read; the collector is also what convicts a typo here.\n staleDigests: subjectsWith(targets, \"sources.stale\"),\n unverifiableFromHere: subjectsWith(targets, \"sources.unverifiable-from-here\"),\n // Doc-side, so unlike `staleDigests` this one is complete even from the\n // docs repo: `sources.sampled-vouch` needs no service repo to fire.\n sampledVouched: subjectsWith(targets, \"sources.sampled-vouch\"),\n },\n verification: {\n recorded: recorded.length,\n verdicts,\n claims: {\n total: recorded.reduce((n, c) => n + c.claims, 0),\n confirmed,\n answered,\n },\n },\n operations,\n messages,\n c4: { elements: eligible.length, covered },\n adoption,\n features: { active: status.features.length, stages },\n };\n}\n"]}
1
+ {"version":3,"file":"scorecard.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/scorecard/scorecard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet scorecard: ceiling-vs-actual aggregates for `validate --all`,\n * recomputed per invocation from the same memoized FleetContext reads the run\n * already paid for. loam stores no history — week-over-week tracking is the\n * caller's pipeline's job: capture the `scorecard` payload key per run into a\n * metrics store. Each axis is a pair of domain-named counts (`defined` vs\n * `governed`) rather than abstract ceiling/actual keys, because payload keys\n * freeze the moment they ship and the domain nouns survive axis nuance.\n *\n * This module derives and never prints; `./print.js` renders the one table\n * the `--all` text report appends, and `./contracts.js` owns the per-service\n * contract reads it sums.\n */\nimport { existsSync } from \"node:fs\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport { coversElement } from \"../../../../core/c4/arch.js\";\nimport { serviceResolver } from \"../../../../core/c4/resolve/service.js\";\nimport { type FleetContext } from \"../../../../core/fleet-context.js\";\nimport { type DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { landscapePath } from \"../../../../core/repo/paths.js\";\nimport { fleetStatus } from \"../../../../core/status/fleet/fleet.js\";\nimport { ARTIFACT_STATUSES, type ArtifactStatus } from \"../../../../core/status/report.js\";\nimport { ANSWERED_BY, type AnsweredBy } from \"../../../../core/verify/answers.js\";\nimport { VERIFICATION_VERDICTS, type VerificationVerdict } from \"../../../../core/verify/record.js\";\nimport { maturityRollup, type Maturity } from \"../../../../core/vocabulary/maturity.js\";\nimport { subjectsWith, type TargetReport } from \"../../../../core/vocabulary/report.js\";\nimport { featureVerification, serviceViews, type VerificationCell } from \"../../../list/views.js\";\nimport { drawnSystems } from \"../census.js\";\nimport { readLandscape } from \"../load.js\";\nimport { adoptionOf, type Adoption } from \"./adoption.js\";\nimport { contractCounts, type ContractCounts } from \"./contracts.js\";\n\n/** What `buildScorecard` needs of the run that is already in hand. */\nexport interface ScorecardInput {\n docsDir: DocsDir;\n /** The run's graded targets — the source for the one count only a repo-bound check can make (`sources.stale` walks the service's own repository, and must not run twice). */\n targets: TargetReport[];\n /** The run's memoized snapshot: every read below is a hit on it. */\n fleet: FleetContext;\n /** loam.json's `service` — the one repo whose `sources` are checkable from here (serviceViews' rule). */\n boundService: string | undefined;\n}\n\n/**\n * The additive `scorecard` payload key of `validate --all --json`, and the\n * `--all` text table. Shape frozen on ship, like every payload key: fields\n * may be added, never changed or removed.\n */\nexport interface Scorecard {\n /** Every `services/` directory the enumeration lists — the maturity rollup's denominator. */\n services: number;\n /** Counts per adoption-maturity rung, every rung present (core/vocabulary/maturity.ts). */\n maturity: Record<Maturity, number>;\n provenance: {\n /** Services at the `vouched` rung — a person stamped them. */\n vouched: number;\n /**\n * Of those, the ones where the person read a recorded SAMPLE of the\n * document rather than all of it, by subject off this run's\n * `sources.sampled-vouch` findings.\n *\n * Additive beside `vouched` rather than subtracted from it, because the\n * rung is real — a person did stamp it — but a dial reading\n * `vouched: 120` where 100 of them are sampled is exactly the\n * partial-trust-as-full-trust misreading the scope exists to prevent. A\n * dashboard that wants the stricter number subtracts this one knowingly.\n */\n sampledVouched: number;\n /**\n * Services whose stamped sources digest no longer matches the code, by\n * subject off this run's `sources.stale` findings — only what THIS run\n * could verify, so read it beside the count below, never alone.\n */\n staleDigests: number;\n /** Services whose declared `sources` only their own repos can check. */\n unverifiableFromHere: number;\n /**\n * Services with a stamped document whose WORDS moved after the vouch, by\n * subject off this run's `content.stale` findings. The doc-side half of\n * staleness, so unlike `staleDigests` it is complete from the docs repo —\n * and its absence was the scorecard saying \"1 vouched — 0 stale\" in the\n * same run that warned `content.stale` one line above it. Additive beside\n * `vouched` for `sampledVouched`'s reason.\n */\n contentStale: number;\n };\n verification: {\n /**\n * Active features with a verification record beside them. Its ceiling is\n * `features.active` — deliberately not repeated here: one denominator,\n * derived once, so the two axes cannot disagree about how many features\n * are in flight.\n */\n recorded: number;\n /** The recorded features' three-valued verdicts (core/verify/record.ts), every key present. */\n verdicts: Record<VerificationVerdict, number>;\n claims: {\n /** Questions asked across every recorded feature, `tallyRecord`'s counting. */\n total: number;\n confirmed: number;\n /**\n * Of the confirmed, one count per `answered_by` value (`ANSWERED_BY`),\n * every key present — the stable-shape rule `verdicts` and `stages`\n * follow, so a fourth provenance would appear rather than be absorbed.\n *\n * Read off the answers' own provenance, never by subtracting the\n * attested share: `attestedClaims` counts `scenario.tested` alone, so\n * `confirmed - attested` filed every OTHER kind an agent confirmed — a\n * permission, a capability, an exposed operation — under a test runner\n * that had not run. A record with four agent answers printed as three\n * runner and one agent, which is the reading this axis exists to give.\n */\n answered: Record<AnsweredBy, number>;\n };\n };\n /**\n * The HTTP axis, with a deliberate UNIT split: `defined`/`governed` count\n * contract SLOTS, exactly as the API axis's own findings count operations,\n * while `deprecated`/`deprecatedStillConsumed` count DISTINCT operationIds —\n * a duplicate-id slot pair (already its own warning) retires as ONE\n * operation, one migration owed.\n */\n operations: {\n /** Slots the living contracts define (removal markers are never operations). */\n defined: number;\n /** Slots (like `defined`) whose id a living requirement's `Operations:` line governs. */\n governed: number;\n /** Distinct deprecated operationIds. */\n deprecated: number;\n /** Distinct deprecated ids something still joins to — a living `Operations:` line, or an op-linked inbound landscape edge. */\n deprecatedStillConsumed: number;\n };\n messages: {\n /** Distinct message names the living async contracts declare. */\n defined: number;\n /** Of them, the ones a requirement's `Publishes:`/`Consumes:` line governs. */\n linked: number;\n };\n c4: {\n /** The drawn SYSTEMS — `drawnSystems`, the fleet map's own census, so the map's exemptions (actors, INFRA kinds the fleet runs rather than adopts, `#external`, groupings, containers) and this count cannot drift. An INFRA-kind element that DOES stand for a `services/<id>/` stays in, so adopting a store never shrinks the denominator. */\n elements: number;\n /** Of them, the ones some living arch requirement's `Covers:` entry names. */\n covered: number;\n };\n /**\n * Services PARTICIPATING in each contract axis — how far the staged\n * adoption has spread, axis by axis. The denominator is `services`, carried\n * once above like every other denominator here. One mechanical rule per\n * axis, per service: `requirements` — spec.md holds at least one\n * `### Requirement:` block (content, not file presence, so a fleet of empty\n * adopt scaffolds honestly reads \"not started\"); `arch` — the same for\n * arch.spec.md; `openapi` / `asyncapi` — the contract file exists;\n * `permissions` / `capabilities` — at least one non-REMOVED requirement in\n * either spec document carries a `Requires:` / `Capability:` entry (the\n * used-set rule the fleet vocabulary checks apply). An axis at 0 is \"not\n * started\" — distinct from partially adopted — and is what licenses the\n * text report's per-axis warning banner (./adoption.js). An unreadable\n * service counts as participating in nothing, the same zeros doctrine as\n * every count here — read these beside the run's `service.unreadable`\n * findings, and note the text renderer refuses to group warnings on a run\n * that has any (report.ts).\n */\n adoption: Adoption;\n features: {\n active: number;\n /**\n * The fleet form's stage per active feature — all five ARTIFACT_STATUSES\n * keys even though that form never grades `draft` (the maturityRollup\n * rule: a stable shape a dashboard can diff).\n */\n stages: Record<ArtifactStatus, number>;\n };\n}\n\n/**\n * Derive the scorecard, or decline. The failure story, per axis: a memoized\n * read that FAILED rejects again on every await, so the per-service fan-out\n * contains those per subject — an unreadable service contributes zeros while\n * its own `service.unreadable` finding names the cause — and every landscape\n * read goes through `../load.ts`'s containment, so an unreadable fleet map\n * zeroes the map-derived axes on every platform instead of deleting the card.\n * The catch is the last line of defence for the fleet-wide reads this module\n * does not own (the verification cells, the fleet status): a rollup that can\n * die must not take the graded report with it. No number rather than a wrong\n * number.\n */\nexport async function buildScorecard(input: ScorecardInput): Promise<Scorecard | null> {\n try {\n return await derive(input);\n } catch {\n // Unreadable is not zero: the axes cannot be counted, so no claim is made.\n return null;\n }\n}\n\nasync function derive(input: ScorecardInput): Promise<Scorecard> {\n const { docsDir, targets, fleet, boundService } = input;\n const services = await fleet.listServices(docsDir);\n\n // The landscape, through the run's memo and the fleet target's containment:\n // an unreadable file grades as one that did not parse, and a landscape that\n // proves nothing contributes no inbound edges and no element census —\n // fail-closed, exactly as the no-openapi grace reads the same absence.\n // Loaded FIRST and handed to serviceViews below, so the maturity rollup\n // costs no second parse of a document this run already holds.\n const lp = landscapePath(docsDir);\n const land = existsSync(lp) ? await readLandscape(() => fleet.loadLikeC4(lp)) : null;\n const proven = land !== null && land.errors.length === 0;\n\n const views = await serviceViews(docsDir, services, boundService, land);\n const maturity = maturityRollup(views);\n\n // Inbound op-linked edges per service, resolved exactly as the API axis\n // resolves them (service/api.ts): the container-aware resolver, so an edge\n // drawn into `paymentService.api` counts for the service that owns it.\n const known: ReadonlySet<string> = new Set(services.map((s) => s.id));\n const inbound = new Map<string, Set<string>>();\n if (proven) {\n const svcOf = serviceResolver(land.elements, known);\n for (const r of land.relationships) {\n if (r.op === undefined) continue;\n const owner: string = svcOf(r.target);\n const ops = inbound.get(owner) ?? new Set<string>();\n ops.add(r.op);\n inbound.set(owner, ops);\n }\n }\n\n // Every read in THIS fan-out is a memo hit — the service targets already\n // parsed both requirement documents and both contracts — so it spawns no new\n // filesystem work and needs no pool (unlike the verification fan-out below).\n const perService = await Promise.all(\n services.map((svc) => contractCounts(svc, fleet, inbound.get(svc.id) ?? new Set<string>())),\n );\n const sum = (pick: (c: ContractCounts) => number): number =>\n perService.reduce((n, c) => n + pick(c), 0);\n const operations = {\n defined: sum((c) => c.operations.defined),\n governed: sum((c) => c.operations.governed),\n deprecated: sum((c) => c.operations.deprecated),\n deprecatedStillConsumed: sum((c) => c.operations.deprecatedStillConsumed),\n };\n const messages = { defined: sum((c) => c.messages.defined), linked: sum((c) => c.messages.linked) };\n const adoption = adoptionOf(perService);\n\n // C4 coverage: the fleet map's own system census (`drawnSystems` — the set\n // the undocumented pass walks) against every living arch.spec.md's `Covers:`\n // entries. Element entries only — an edge entry names no element.\n const covers = perService.flatMap((c) => c.covers);\n const eligible = proven ? drawnSystems(land.elements, known) : [];\n const covered = eligible.filter((e) => covers.some((c) => coversElement(c, e))).length;\n\n // Verification: `list`'s own cells, so the two commands cannot disagree\n // about a record's verdict. Active features only — an archived record is\n // frozen history, not fleet debt. This fan-out is NOT memo hits: reading a\n // feature's record is a directory listing plus a small read per record file\n // (the one-file `verification.yaml` and one `verification/<svc>.yaml` per\n // attesting service), and a recorded cell additionally derives its checklist\n // (the same cost `list` pays for the same column; only its enumerations and\n // delta parse are memo hits through `fleet`) — real filesystem work, so it\n // runs through the bounded pool.\n const features = await fleet.listFeatures(docsDir);\n const cells = await inOrder(features, (f) => featureVerification(docsDir, f, fleet));\n const recorded = cells.filter((c): c is VerificationCell => c !== null);\n const verdicts = Object.fromEntries(VERIFICATION_VERDICTS.map((v) => [v, 0])) as Record<VerificationVerdict, number>;\n for (const cell of recorded) verdicts[cell.verdict] += 1;\n const confirmed = recorded.reduce((n, c) => n + c.confirmed, 0);\n const answered = Object.fromEntries(\n ANSWERED_BY.map((who) => [who, recorded.reduce((n, c) => n + c.answered[who], 0)]),\n ) as Record<AnsweredBy, number>;\n\n // The fleet form of `status`, on this run's own snapshot: presence and\n // dependency order, nothing graded — the stages an agent already branches on.\n const status = await fleetStatus(docsDir, { context: fleet });\n const stages = Object.fromEntries(ARTIFACT_STATUSES.map((s) => [s, 0])) as Record<ArtifactStatus, number>;\n for (const f of status.features) stages[f.stage] += 1;\n\n return {\n services: services.length,\n maturity,\n provenance: {\n vouched: maturity.vouched,\n // Every count joins through `subjectsWith` on the run's OWN findings, so\n // each is structurally the same number as the report beside it — the\n // unverifiable count is the footer's (unverifiableSubjects is the same\n // call on the same code; this package cannot import the parent back).\n // Every code is a LITERAL — the emitter's own spelling — because the\n // stable-code collector reads counting sites and refuses a slot it\n // cannot read; the collector is also what convicts a typo here.\n staleDigests: subjectsWith(targets, \"sources.stale\"),\n unverifiableFromHere: subjectsWith(targets, \"sources.unverifiable-from-here\"),\n // Doc-side, so unlike `staleDigests` these two are complete even from\n // the docs repo: neither `sources.sampled-vouch` nor `content.stale`\n // needs a service repo to fire. `content.stale` names no subject, and\n // `subjectsWith` falls back to the target id — the service, here.\n sampledVouched: subjectsWith(targets, \"sources.sampled-vouch\"),\n contentStale: subjectsWith(targets, \"content.stale\"),\n },\n verification: {\n recorded: recorded.length,\n verdicts,\n claims: {\n total: recorded.reduce((n, c) => n + c.claims, 0),\n confirmed,\n answered,\n },\n },\n operations,\n messages,\n c4: { elements: eligible.length, covered },\n adoption,\n features: { active: status.features.length, stages },\n };\n}\n"]}
@@ -6,10 +6,12 @@
6
6
  * this ship into a fleet that already has diagrams. An untagged `dynamic view`
7
7
  * is somebody's hand-drawn sequence — drawn to explain something to a person,
8
8
  * not to be joined to a model — and grading it would turn every existing docs
9
- * repo red on upgrade over documents nobody promised anything about. A `#cap-`
10
- * or `#req-` tag is the author saying "this is a use case; hold it to the
11
- * model", and nothing else says it: a view with no tags and a view whose tags
12
- * are all somebody's own vocabulary are the same thing here.
9
+ * repo red on upgrade over documents nobody promised anything about. A `#cap-`,
10
+ * `#req-` or `#suite-` tag is the author saying "this is a use case; hold it to
11
+ * the model", and nothing else says it: a view with no tags and a view whose
12
+ * tags are all somebody's own vocabulary are the same thing here. A
13
+ * `#suite-`-only view resolves nothing below — it carries no claim — and its
14
+ * hops are graded like any other's.
13
15
  *
14
16
  * THE TAG OPTS A VIEW IN WHETHER OR NOT IT RESOLVES. A `#cap-` slug that names
15
17
  * no declared capability is a broken claim about the vocabulary, not a withdrawn
@@ -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"]}
@@ -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 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
+ {"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"]}
@@ -42,10 +42,10 @@
42
42
  * read as the map declared twice) and a package tree, never authored documents —
43
43
  * the same thing the scaffold's own `node_modules` glob says.
44
44
  */
45
- import { readdir } from "node:fs/promises";
46
- import { join, relative, resolve, sep } from "node:path";
45
+ import { relative, resolve, sep } from "node:path";
47
46
  import { excludingPath, readRootExclude } from "../../../../core/c4/root-project/exclude.js";
48
47
  import { generatedArchitectureDocuments } from "../../../../core/c4/project/architecture.js";
48
+ import { likec4DocumentsUnder } from "../../../../core/c4/project/documents.js";
49
49
  /** The directory name loam never walks, beside every dot-directory. */
50
50
  const PACKAGES = "node_modules";
51
51
  /** The one subtree loam drops whatever the root config says — see the banner. */
@@ -59,7 +59,6 @@ export async function rootProjectDocuments(docsDir, standalone) {
59
59
  const generated = new Set(generatedArchitectureDocuments(docsDir).map((path) => resolve(path)));
60
60
  const excluded = standalone.map((dir) => `${resolve(dir)}${sep}`);
61
61
  const root = resolve(docsDir);
62
- const found = [];
63
62
  const keeps = (path) => {
64
63
  if (generated.has(resolve(path)))
65
64
  return false;
@@ -70,30 +69,11 @@ export async function rootProjectDocuments(docsDir, standalone) {
70
69
  return false;
71
70
  return exclude === null || excludingPath(exclude, rel.join("/")) === null;
72
71
  };
73
- const walk = async (at) => {
74
- let entries;
75
- try {
76
- entries = await readdir(at, { withFileTypes: true });
77
- }
78
- catch {
79
- // A directory that cannot be read contributes nothing: this grade runs
80
- // inside `validate --all` over a whole fleet, and the one behaviour it may
81
- // never have is failing the run it was added to.
82
- return;
83
- }
84
- for (const entry of entries) {
85
- const path = join(at, entry.name);
86
- if (entry.isDirectory()) {
87
- if (entry.name.startsWith(".") || entry.name === PACKAGES)
88
- continue;
89
- await walk(path);
90
- }
91
- else if (entry.name.endsWith(".likec4") && keeps(path)) {
92
- found.push(path);
93
- }
94
- }
95
- };
96
- await walk(root);
97
- 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
+ });
98
78
  }
99
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":"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","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 { 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 { generatedArchitectureDocuments } from \"../../../../core/c4/project/architecture.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 found: string[] = [];\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 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"]}
@@ -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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"glossary.js","sourceRoot":"","sources":["../../../../src/commands/validate/links/glossary.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `glossary.unlinked` — a defined term that no document outside the glossary\n * cites.\n *\n * The mirror of `permissions.unenforced` and `capability.unrealized`, and it\n * earns its place for their reason: a vocabulary is worth what cites it. A term\n * nobody links to is either a word the fleet never adopted or a definition left\n * behind by a rename — both are drift, and neither is visible from inside the\n * file. WARN, because the honest answer is often \"not written up yet\", and\n * because a glossary must be safe to start: a fleet that defines twenty terms\n * on a Monday and cites four of them is adopting the axis, not failing it.\n *\n * A TERM CITED ONLY BY OTHER TERMS IS STILL UNLINKED, and this is the judgement\n * that keeps the warning worth anything. A glossary is a network of definitions,\n * so `order-line` linking to `order` is normal and correct — and it proves\n * nothing about whether the FLEET uses either word. The same reasoning\n * `capability.requirement-inert-join` applies one axis over: a join written\n * inside the tree is not evidence about the world outside it. The intra-glossary\n * citations are listed in `details` rather than hidden, because they are the\n * first thing a reader will otherwise go looking for.\n *\n * ONE FINDING FOR THE WHOLE GLOSSARY when nothing cites anything, and one per\n * term otherwise. A fleet that has just created `glossary/` and written its\n * first eight definitions would otherwise get eight identical warnings saying\n * the same thing about the same afternoon's work.\n */\nimport { backlinkIndex } from \"../../../core/links/backlinks.js\";\nimport { readGlossary } from \"../../../core/glossary/tree.js\";\nimport { glossaryDir } from \"../../../core/repo/authored/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 } from \"../../../core/kernel/ids/dirs.js\";\n\nexport async function glossaryFindings(docsDir: DocsDir, fleet?: FleetContext): Promise<Finding[]> {\n const root = glossaryDir(docsDir);\n const glossary = await readGlossary(root);\n // The directory's existence is the axis's opt-in, exactly as\n // `architecture/capabilities.yaml` and `capabilities/` are the business\n // axis's. A fleet without one pays a single `existsSync` and hears nothing —\n // and the whole fleet-wide link walk below is never built.\n if (!glossary.present || glossary.terms.length === 0) return [];\n\n const index = await backlinkIndex(docsDir, fleet);\n const inGlossary = (source: string): boolean => source.startsWith(`${repoPath(docsDir, root)}/`);\n const unlinked = glossary.terms\n .map((term) => ({ term, cited: index.linkersOf(term.path) }))\n .map(({ term, cited }) => ({ term, outside: cited.filter((s) => !inGlossary(s)), within: cited.filter(inGlossary) }))\n .filter(({ outside }) => outside.length === 0);\n if (unlinked.length === 0) return [];\n\n // The whole vocabulary uncited is one fact about one afternoon, not N facts.\n if (unlinked.length === glossary.terms.length && glossary.terms.length > 1) {\n return [\n {\n severity: \"warn\",\n code: \"glossary.unlinked\",\n message:\n `glossary: none of the ${glossary.terms.length} defined term(s) is linked from any document outside glossary/. ` +\n \"A term becomes checkable when a requirement, capability or intent cites it — \" +\n \"`[Order](../../glossary/order.md)` — and until then the definitions are a document nothing joins to.\",\n details: unlinked.map(({ term }) => term.id),\n },\n ];\n }\n return unlinked.map(({ term, within }) => ({\n severity: \"warn\" as const,\n code: \"glossary.unlinked\",\n subject: term.id,\n message:\n `glossary/${term.id}.md is linked from no document outside glossary/` +\n (within.length === 0\n ? \" — either a word the fleet has not adopted, or a definition left behind by a rename.\"\n : \", only from other terms — which says the glossary is consistent, not that the fleet uses the word.\") +\n ` Cite it as \\`[${term.id}](<relative>/glossary/${term.id}.md)\\` from the requirement or capability that uses it, or delete the definition.`,\n ...(within.length === 0 ? {} : { details: within }),\n }));\n}\n"]}
1
+ {"version":3,"file":"glossary.js","sourceRoot":"","sources":["../../../../src/commands/validate/links/glossary.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `glossary.unlinked` — a defined term that no document outside the glossary\n * cites.\n *\n * The mirror of `permissions.unenforced` and `capability.unrealized`, and it\n * earns its place for their reason: a vocabulary is worth what cites it. A term\n * nobody links to is either a word the fleet never adopted or a definition left\n * behind by a rename — both are drift, and neither is visible from inside the\n * file. WARN, because the honest answer is often \"not written up yet\", and\n * because a glossary must be safe to start: a fleet that defines twenty terms\n * on a Monday and cites four of them is adopting the axis, not failing it.\n *\n * A TERM CITED ONLY BY OTHER TERMS IS STILL UNLINKED, and this is the judgement\n * that keeps the warning worth anything. A glossary is a network of definitions,\n * so `order-line` linking to `order` is normal and correct — and it proves\n * nothing about whether the FLEET uses either word. The same reasoning\n * `capability.requirement-inert-join` applies one axis over: a join written\n * inside the tree is not evidence about the world outside it. The intra-glossary\n * citations are listed in `details` rather than hidden, because they are the\n * first thing a reader will otherwise go looking for.\n *\n * ONE FINDING FOR THE WHOLE GLOSSARY when nothing cites anything, and one per\n * term otherwise. A fleet that has just created `glossary/` and written its\n * first eight definitions would otherwise get eight identical warnings saying\n * the same thing about the same afternoon's work.\n */\nimport { backlinkIndex } from \"../../../core/links/backlinks.js\";\nimport { readGlossary } from \"../../../core/glossary/tree.js\";\nimport { glossaryDir } from \"../../../core/repo/authored/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 } from \"../../../core/kernel/ids/dirs.js\";\n\nexport async function glossaryFindings(docsDir: DocsDir, fleet: FleetContext): Promise<Finding[]> {\n const root = glossaryDir(docsDir);\n const glossary = await readGlossary(root);\n // The directory's existence is the axis's opt-in, exactly as\n // `architecture/capabilities.yaml` and `capabilities/` are the business\n // axis's. A fleet without one pays a single `existsSync` and hears nothing —\n // and the whole fleet-wide link walk below is never built.\n if (!glossary.present || glossary.terms.length === 0) return [];\n\n const index = await backlinkIndex(docsDir, fleet);\n const inGlossary = (source: string): boolean => source.startsWith(`${repoPath(docsDir, root)}/`);\n const unlinked = glossary.terms\n .map((term) => ({ term, cited: index.linkersOf(term.path) }))\n .map(({ term, cited }) => ({ term, outside: cited.filter((s) => !inGlossary(s)), within: cited.filter(inGlossary) }))\n .filter(({ outside }) => outside.length === 0);\n if (unlinked.length === 0) return [];\n\n // The whole vocabulary uncited is one fact about one afternoon, not N facts.\n if (unlinked.length === glossary.terms.length && glossary.terms.length > 1) {\n return [\n {\n severity: \"warn\",\n code: \"glossary.unlinked\",\n message:\n `glossary: none of the ${glossary.terms.length} defined term(s) is linked from any document outside glossary/. ` +\n \"A term becomes checkable when a requirement, capability or intent cites it — \" +\n \"`[Order](../../glossary/order.md)` — and until then the definitions are a document nothing joins to.\",\n details: unlinked.map(({ term }) => term.id),\n },\n ];\n }\n return unlinked.map(({ term, within }) => ({\n severity: \"warn\" as const,\n code: \"glossary.unlinked\",\n subject: term.id,\n message:\n `glossary/${term.id}.md is linked from no document outside glossary/` +\n (within.length === 0\n ? \" — either a word the fleet has not adopted, or a definition left behind by a rename.\"\n : \", only from other terms — which says the glossary is consistent, not that the fleet uses the word.\") +\n ` Cite it as \\`[${term.id}](<relative>/glossary/${term.id}.md)\\` from the requirement or capability that uses it, or delete the definition.`,\n ...(within.length === 0 ? {} : { details: within }),\n }));\n}\n"]}