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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -0,0 +1,116 @@
1
+ /**
2
+ * `landscape.service-isolated` — a service the fleet map draws and nothing
3
+ * reaches, graded on EVIDENCE rather than on the shape alone.
4
+ *
5
+ * The state is the one `loam seed --from fleet.yaml` leaves for every service
6
+ * nobody listed under `calls:`: a bound element with no edge. Nothing used to
7
+ * report it — `landscape.service-unmodelled` wants a missing element, and the
8
+ * element is present — so a service could be adopted with a full artifact set
9
+ * and stay invisible to every cross-service check while the run ended on
10
+ * `landscape.matched`.
11
+ *
12
+ * Why the evidence gate, and not "every edgeless bound element": a warning with
13
+ * no correct fix is the warning somebody silences. A worker or a cron that
14
+ * genuinely calls nothing has an edgeless element that is TRUE, and there is
15
+ * no tag loam reads for "deliberately isolated" (inventing one is the invented
16
+ * need the non-goals forbid). So this fires only when the service's own
17
+ * `model.likec4` parses and declares at least one call across its boundary —
18
+ * a JOIN between two authored documents: the model attests a call the map
19
+ * does not draw. The list comes from `attestedCalls`, the same derivation the
20
+ * adopt brief names those calls with, so the brief and this check can never
21
+ * disagree about which edges the map owes. Silent, on purpose, for a service
22
+ * with no model (seed's fleet), an unparseable model (`c4.invalid` owns it), a
23
+ * model reaching nothing, and an unmodelled service (`service-unmodelled` owns
24
+ * it).
25
+ *
26
+ * `#external` is never a SUBJECT, and that is now enforced rather than assumed.
27
+ * The claim used to rest on "an `#external` element is not an enumerated
28
+ * service" — true of the ordinary case and false of the reachable one: an
29
+ * element tagged `#external` and bound with `metadata { service '<id>' }` to a
30
+ * directory that exists resolved like any other, so a foreign box answered for
31
+ * one of our services (R4). The two neighbouring binding checks have always
32
+ * skipped the tag; the input set here does too, so "deliberately not ours"
33
+ * means the same thing in all three.
34
+ *
35
+ * The subject set ONLY, and that boundary is the whole of the rule. The filter
36
+ * also sat inside the `touched` walk for one round, which answers a different
37
+ * question — does the map draw any edge on this service — and got it wrong: a
38
+ * kind carrying the tag (`element topic { #external }`, the idiom
39
+ * `examples/docs/architecture/landscape.likec4` defends at length) puts
40
+ * `#external` on every element of that kind, so an edge drawn from a service's
41
+ * OWN topic stopped touching it and the warning fired on a service the map
42
+ * demonstrably reaches — while `loam adopt`, whose `landscape.touched` has no
43
+ * tag filter, said `touched: true` on the same tree. Two answers to one rule
44
+ * is the drift this file's banner promises there cannot be, so the walk below
45
+ * is the brief's predicate exactly, tag and all.
46
+ *
47
+ * A sub-package of its own because `fleet/` sits at the five-file cap, and it
48
+ * imports NOTHING from `fleet/`: a child reaching back into its parent while
49
+ * the parent calls the child is the package cycle `scripts/package-graph.mjs`
50
+ * refuses, which is why the input is one record the caller fills. The models
51
+ * are no longer read here either — `./attest.ts` reads each of them once for
52
+ * this check and the datastore census together.
53
+ */
54
+ import {} from "../../../../core/c4/likec4.js";
55
+ import { edgeTemplates, spellCall } from "../../../../core/c4/resolve/attested.js";
56
+ import {} from "../../../../core/vocabulary/report.js";
57
+ import {} from "./attest.js";
58
+ /** How many attested calls the message spells out before "…" — three reads; thirty is a wall. */
59
+ const NAMED_CALLS = 3;
60
+ export function isolationFindings(input) {
61
+ const { land, resolve, pathOf } = input;
62
+ // One pass over the relationships: a service is touched when EITHER endpoint
63
+ // resolves to it. An intra-service edge (`svc.api -> svc.db`) therefore
64
+ // counts, which is the brief's `landscape.touched` predicate exactly, and
65
+ // what keeps a self-model drawn as one service of container edges silent.
66
+ // NO tag filter here, deliberately — see the banner: an `#external` endpoint
67
+ // is still an edge the map draws on whatever it resolves to, and skipping one
68
+ // made this check contradict `loam adopt` on a service whose own topic (a
69
+ // kind-tagged `#external` element) was the endpoint.
70
+ const touched = new Set();
71
+ for (const r of land.relationships) {
72
+ for (const endpoint of [r.source, r.target])
73
+ touched.add(resolve(endpoint));
74
+ }
75
+ const findings = [];
76
+ for (const model of input.attested) {
77
+ if (!input.modelled.has(model.service) || touched.has(model.service))
78
+ continue;
79
+ const calls = model.calls;
80
+ if (calls.length === 0)
81
+ continue;
82
+ // `modelled.has` proves a non-external element resolves; the find is the
83
+ // same predicate spelled on the elements, and an empty answer fails closed
84
+ // rather than naming an element that is not there.
85
+ const element = land.elements.find((e) => resolve(e.id) === model.service && !input.external.has(e.id));
86
+ if (element === undefined)
87
+ continue;
88
+ const named = calls.slice(0, NAMED_CALLS).map(spellCall).join(", ") + (calls.length > NAMED_CALLS ? ", …" : "");
89
+ // The repair form follows the calls' direction (`edgeTemplates`): the
90
+ // outbound form alone, spelled here until 2026-09-03, sent an inbound
91
+ // call backwards.
92
+ const forms = edgeTemplates(element.id, calls)
93
+ .map((t) => `\`${t}\``)
94
+ .join(" or ");
95
+ findings.push({
96
+ severity: "warn",
97
+ code: "landscape.service-isolated",
98
+ subject: model.service,
99
+ message:
100
+ // "the fleet map (architecture/)", never the landscape FILE: the input
101
+ // is the whole PROJECT, so an edge added to `architecture/extra.likec4`
102
+ // and never to landscape.likec4 silences this finding — measured on both
103
+ // the seed lab and examples/docs. Naming one file told a reader the edge
104
+ // had to land there, which SCHEMA, `loam explain` and the brief all
105
+ // already contradicted (verification 2026-09-04).
106
+ `landscape: ${pathOf(model.service)}/ resolves to '${element.id}' and no edge in the fleet map ` +
107
+ `(architecture/) touches it, while ${pathOf(model.service)}/model.likec4 declares ${calls.length} ` +
108
+ `call(s) across its boundary ` +
109
+ `(${named}) — the service is drawn and invisible to every cross-service check. Draw each as one edge on ` +
110
+ `${element.id}: ${forms} for a call, ` +
111
+ `\`publishes\`/\`consumes\` for an event, naming the other party as the map already spells it`,
112
+ });
113
+ }
114
+ return findings;
115
+ }
116
+ //# sourceMappingURL=isolation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isolation.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/isolation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `landscape.service-isolated` — a service the fleet map draws and nothing\n * reaches, graded on EVIDENCE rather than on the shape alone.\n *\n * The state is the one `loam seed --from fleet.yaml` leaves for every service\n * nobody listed under `calls:`: a bound element with no edge. Nothing used to\n * report it — `landscape.service-unmodelled` wants a missing element, and the\n * element is present — so a service could be adopted with a full artifact set\n * and stay invisible to every cross-service check while the run ended on\n * `landscape.matched`.\n *\n * Why the evidence gate, and not \"every edgeless bound element\": a warning with\n * no correct fix is the warning somebody silences. A worker or a cron that\n * genuinely calls nothing has an edgeless element that is TRUE, and there is\n * no tag loam reads for \"deliberately isolated\" (inventing one is the invented\n * need the non-goals forbid). So this fires only when the service's own\n * `model.likec4` parses and declares at least one call across its boundary —\n * a JOIN between two authored documents: the model attests a call the map\n * does not draw. The list comes from `attestedCalls`, the same derivation the\n * adopt brief names those calls with, so the brief and this check can never\n * disagree about which edges the map owes. Silent, on purpose, for a service\n * with no model (seed's fleet), an unparseable model (`c4.invalid` owns it), a\n * model reaching nothing, and an unmodelled service (`service-unmodelled` owns\n * it).\n *\n * `#external` is never a SUBJECT, and that is now enforced rather than assumed.\n * The claim used to rest on \"an `#external` element is not an enumerated\n * service\" — true of the ordinary case and false of the reachable one: an\n * element tagged `#external` and bound with `metadata { service '<id>' }` to a\n * directory that exists resolved like any other, so a foreign box answered for\n * one of our services (R4). The two neighbouring binding checks have always\n * skipped the tag; the input set here does too, so \"deliberately not ours\"\n * means the same thing in all three.\n *\n * The subject set ONLY, and that boundary is the whole of the rule. The filter\n * also sat inside the `touched` walk for one round, which answers a different\n * question — does the map draw any edge on this service — and got it wrong: a\n * kind carrying the tag (`element topic { #external }`, the idiom\n * `examples/docs/architecture/landscape.likec4` defends at length) puts\n * `#external` on every element of that kind, so an edge drawn from a service's\n * OWN topic stopped touching it and the warning fired on a service the map\n * demonstrably reaches — while `loam adopt`, whose `landscape.touched` has no\n * tag filter, said `touched: true` on the same tree. Two answers to one rule\n * is the drift this file's banner promises there cannot be, so the walk below\n * is the brief's predicate exactly, tag and all.\n *\n * A sub-package of its own because `fleet/` sits at the five-file cap, and it\n * imports NOTHING from `fleet/`: a child reaching back into its parent while\n * the parent calls the child is the package cycle `scripts/package-graph.mjs`\n * refuses, which is why the input is one record the caller fills. The models\n * are no longer read here either — `./attest.ts` reads each of them once for\n * this check and the datastore census together.\n */\nimport { type LoadedDoc } from \"../../../../core/c4/likec4.js\";\nimport { edgeTemplates, spellCall } from \"../../../../core/c4/resolve/attested.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\nimport { type AttestedModel } from \"./attest.js\";\n\nexport interface IsolationInput {\n /** The parsed landscape. */\n land: LoadedDoc;\n /**\n * The services a NON-`#external` element resolves to — `service-unmodelled`'s\n * set with the tag filter applied, which is what keeps a foreign box from\n * answering for one of our directories.\n */\n modelled: ReadonlySet<string>;\n /** The ids of the map's `#external` elements: never a subject — and never what the message names. */\n external: ReadonlySet<string>;\n /** What each service's own model attests, read once by `./attest.ts` for this check and the store census. */\n attested: readonly AttestedModel[];\n /** The shared element→service resolver every edge join uses. */\n resolve: (id: string) => string;\n /** Where a service's directory sits, repo-relative — the enumeration's answer, never `services/<id>`. */\n pathOf: (id: string) => string;\n}\n\n/** How many attested calls the message spells out before \"…\" — three reads; thirty is a wall. */\nconst NAMED_CALLS = 3;\n\nexport function isolationFindings(input: IsolationInput): Finding[] {\n const { land, resolve, pathOf } = input;\n // One pass over the relationships: a service is touched when EITHER endpoint\n // resolves to it. An intra-service edge (`svc.api -> svc.db`) therefore\n // counts, which is the brief's `landscape.touched` predicate exactly, and\n // what keeps a self-model drawn as one service of container edges silent.\n // NO tag filter here, deliberately — see the banner: an `#external` endpoint\n // is still an edge the map draws on whatever it resolves to, and skipping one\n // made this check contradict `loam adopt` on a service whose own topic (a\n // kind-tagged `#external` element) was the endpoint.\n const touched = new Set<string>();\n for (const r of land.relationships) {\n for (const endpoint of [r.source, r.target]) touched.add(resolve(endpoint));\n }\n\n const findings: Finding[] = [];\n for (const model of input.attested) {\n if (!input.modelled.has(model.service) || touched.has(model.service)) continue;\n const calls = model.calls;\n if (calls.length === 0) continue;\n // `modelled.has` proves a non-external element resolves; the find is the\n // same predicate spelled on the elements, and an empty answer fails closed\n // rather than naming an element that is not there.\n const element = land.elements.find((e) => resolve(e.id) === model.service && !input.external.has(e.id));\n if (element === undefined) continue;\n const named = calls.slice(0, NAMED_CALLS).map(spellCall).join(\", \") + (calls.length > NAMED_CALLS ? \", …\" : \"\");\n // The repair form follows the calls' direction (`edgeTemplates`): the\n // outbound form alone, spelled here until 2026-09-03, sent an inbound\n // call backwards.\n const forms = edgeTemplates(element.id, calls)\n .map((t) => `\\`${t}\\``)\n .join(\" or \");\n findings.push({\n severity: \"warn\",\n code: \"landscape.service-isolated\",\n subject: model.service,\n message:\n // \"the fleet map (architecture/)\", never the landscape FILE: the input\n // is the whole PROJECT, so an edge added to `architecture/extra.likec4`\n // and never to landscape.likec4 silences this finding — measured on both\n // the seed lab and examples/docs. Naming one file told a reader the edge\n // had to land there, which SCHEMA, `loam explain` and the brief all\n // already contradicted (verification 2026-09-04).\n `landscape: ${pathOf(model.service)}/ resolves to '${element.id}' and no edge in the fleet map ` +\n `(architecture/) touches it, while ${pathOf(model.service)}/model.likec4 declares ${calls.length} ` +\n `call(s) across its boundary ` +\n `(${named}) — the service is drawn and invisible to every cross-service check. Draw each as one edge on ` +\n `${element.id}: ${forms} for a call, ` +\n `\\`publishes\\`/\\`consumes\\` for an event, naming the other party as the map already spells it`,\n });\n }\n return findings;\n}\n"]}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The fleet's use cases, graded — the CALL, with the two vocabulary reads it
3
+ * needs, lifted out of `../landscape.ts` when that module reached its line
4
+ * limit.
5
+ *
6
+ * The seam is a phase rather than a subject: everything here happens after the
7
+ * map has parsed and grades documents the map does not own — every
8
+ * `dynamic view` in `architecture/landscape.likec4` and in every
9
+ * `architecture/usecases/*.likec4`. `core/usecases/grade/usecases.ts` owns the opt-in and
10
+ * the grades; this owns which inputs they are handed, which is the part that has
11
+ * twice been got wrong and is worth stating in one place.
12
+ *
13
+ * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule:
14
+ * it imports NOTHING from `fleet/` itself, so everything it needs arrives in
15
+ * one record the caller fills — besides `../usecases/place.ts`, the fleet target's
16
+ * own name for where a view is, which the graders (`core/usecases/grade/`) take
17
+ * as an argument because core may not hold it.
18
+ */
19
+ import { capabilityRequirementIndex, gradableCapabilityIds } from "../../../../core/capabilities/findings.js";
20
+ import { FleetContext } from "../../../../core/fleet-context.js";
21
+ import { useCaseFindings } from "../../../../core/usecases/grade/usecases.js";
22
+ import { viewPlace } from "../usecases/place.js";
23
+ /**
24
+ * The capability ladder is APPLIED BY ITS OWN FUNCTION rather than re-spelled
25
+ * here: an absent or unreadable capabilities.yaml means silence for the whole
26
+ * family, not a fleet full of unresolved tags, so `null` travels rather than an
27
+ * empty list — which the join would read as "the fleet declares no capabilities"
28
+ * and grade every tag against. `gradableCapabilityIds`
29
+ * (`core/capabilities/findings.ts`) is the one statement of that rule, and this
30
+ * is a caller of it precisely so a fourth un-gradable vocabulary state cannot be
31
+ * fixed in core while the command layer keeps handing the join a whole key set.
32
+ *
33
+ * Both reads are already paid for under `--all`: `capabilityFleetFindings` has
34
+ * asked for the vocabulary, and every requirement document behind the `#req-`
35
+ * tag was parsed by it, so the fleet memo answers from cache. The requirement
36
+ * index is built here rather than inside the use-case package so that package
37
+ * never learns what a capability document is.
38
+ */
39
+ export async function fleetUseCaseFindings(input) {
40
+ const { docsDir, fleet } = input;
41
+ const vocabulary = await fleet.capabilities(docsDir);
42
+ const capabilityReqs = await capabilityRequirementIndex(vocabulary, (p) => fleet.readRequirements(p));
43
+ return useCaseFindings({
44
+ views: input.views,
45
+ elements: input.elements,
46
+ relationships: input.relationships,
47
+ services: input.services,
48
+ resolve: input.resolve,
49
+ capabilities: gradableCapabilityIds(vocabulary),
50
+ requirementsOf: (capability) => capabilityReqs.byCapability.get(capability),
51
+ place: viewPlace,
52
+ });
53
+ }
54
+ //# sourceMappingURL=usecases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usecases.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/usecases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAsBjD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAAoB;IAC7D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjC,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,MAAM,0BAA0B,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IACtG,OAAO,eAAe,CAAC;QACrB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,YAAY,EAAE,qBAAqB,CAAC,UAAU,CAAC;QAC/C,cAAc,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC;QAC3E,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The fleet's use cases, graded — the CALL, with the two vocabulary reads it\n * needs, lifted out of `../landscape.ts` when that module reached its line\n * limit.\n *\n * The seam is a phase rather than a subject: everything here happens after the\n * map has parsed and grades documents the map does not own — every\n * `dynamic view` in `architecture/landscape.likec4` and in every\n * `architecture/usecases/*.likec4`. `core/usecases/grade/usecases.ts` owns the opt-in and\n * the grades; this owns which inputs they are handed, which is the part that has\n * twice been got wrong and is worth stating in one place.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule:\n * it imports NOTHING from `fleet/` itself, so everything it needs arrives in\n * one record the caller fills — besides `../usecases/place.ts`, the fleet target's\n * own name for where a view is, which the graders (`core/usecases/grade/`) take\n * as an argument because core may not hold it.\n */\nimport { capabilityRequirementIndex, gradableCapabilityIds } from \"../../../../core/capabilities/findings.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport type { Elem, Rel } from \"../../../../core/c4/likec4.js\";\nimport type { ParsedView } from \"../../../../core/c4/parsed/dynamic-views.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\nimport { useCaseFindings } from \"../../../../core/usecases/grade/usecases.js\";\nimport { viewPlace } from \"../usecases/place.js\";\n\nexport interface FleetUseCases {\n docsDir: DocsDir;\n /**\n * The views come from the PROJECT load, never from a single-file fallback,\n * and that is the file-naming rule made mechanical instead of remembered:\n * only the project load gives a view the `sourcePath` a finding has to name,\n * while a single-file load calls every document `source.c4` — so a message\n * built from that load would send its reader to `architecture/source.c4`, a\n * file that has never existed. No project, no views, and nothing to get wrong.\n */\n views: readonly ParsedView[];\n /** The map's model — what a hop is resolved against. */\n elements: Elem[];\n relationships: Rel[];\n /** The enumerated fleet, and the resolver every other edge join in the target uses. */\n services: ReadonlySet<string>;\n resolve: (id: string) => string;\n fleet: FleetContext;\n}\n\n/**\n * The capability ladder is APPLIED BY ITS OWN FUNCTION rather than re-spelled\n * here: an absent or unreadable capabilities.yaml means silence for the whole\n * family, not a fleet full of unresolved tags, so `null` travels rather than an\n * empty list — which the join would read as \"the fleet declares no capabilities\"\n * and grade every tag against. `gradableCapabilityIds`\n * (`core/capabilities/findings.ts`) is the one statement of that rule, and this\n * is a caller of it precisely so a fourth un-gradable vocabulary state cannot be\n * fixed in core while the command layer keeps handing the join a whole key set.\n *\n * Both reads are already paid for under `--all`: `capabilityFleetFindings` has\n * asked for the vocabulary, and every requirement document behind the `#req-`\n * tag was parsed by it, so the fleet memo answers from cache. The requirement\n * index is built here rather than inside the use-case package so that package\n * never learns what a capability document is.\n */\nexport async function fleetUseCaseFindings(input: FleetUseCases): Promise<Finding[]> {\n const { docsDir, fleet } = input;\n const vocabulary = await fleet.capabilities(docsDir);\n const capabilityReqs = await capabilityRequirementIndex(vocabulary, (p) => fleet.readRequirements(p));\n return useCaseFindings({\n views: input.views,\n elements: input.elements,\n relationships: input.relationships,\n services: input.services,\n resolve: input.resolve,\n capabilities: gradableCapabilityIds(vocabulary),\n requirementsOf: (capability) => capabilityReqs.byCapability.get(capability),\n place: viewPlace,\n });\n}\n"]}
@@ -28,14 +28,15 @@
28
28
  import { existsSync } from "node:fs";
29
29
  import { join } from "node:path";
30
30
  import {} from "../../../core/c4/likec4.js";
31
- import { closeIds, coversEdge, coversElement } from "../../../core/c4/arch.js";
31
+ import { closeIds, coversDeployEdge, coversDeployNode, coversEdge, coversElement, } from "../../../core/c4/arch.js";
32
+ import { NO_DEPLOYMENT } from "../../../core/c4/parsed/deployment.js";
33
+ import { taggedDeployment } from "../../../core/deployment/objects.js";
32
34
  import { elementService } from "../../../core/c4/resolve/service.js";
33
35
  import { OBLIGATION_TAG_PREFIX } from "../../../core/obligations/obligations.js";
34
36
  import { servicePathsAt } from "../../../core/repo/paths.js";
35
37
  import {} from "../../../core/repo/entries.js";
36
38
  import {} from "../../../core/vocabulary/report.js";
37
39
  import { FleetContext } from "../../../core/fleet-context.js";
38
- import { readRequirementsDocument, parseRequirements } from "../../../core/document/parse.js";
39
40
  import { coversEntries } from "../checks/requirements.js";
40
41
  /**
41
42
  * The half that does not depend on the map: the file reads, and its `adr:`
@@ -76,7 +77,13 @@ export async function obligationFindings(check) {
76
77
  // together because every question below treats them alike: an obligation
77
78
  // applies to a thing on the map, and whether that thing is a box or an arrow
78
79
  // changes only how the message spells it and how `Covers:` matches it.
79
- const tagged = taggedObjects(check.land);
80
+ // The enumerated fleet rides in for the deployment half of the walk: an
81
+ // instance deploys a CONTAINER more often than a whole system, and resolving
82
+ // which service owns one needs the ancestor walk `serviceResolver` performs.
83
+ // It is the same set `uncoveredFindings` resolves edge endpoints with, or the
84
+ // message would name one service and the coverage check count another.
85
+ const known = new Set(check.services.map((s) => s.id));
86
+ const tagged = taggedObjects(check.land, known);
80
87
  const applied = new Set(tagged.map((t) => t.obligation));
81
88
  for (const id of [...applied].sort()) {
82
89
  if (vocabulary.byId.has(id))
@@ -123,7 +130,7 @@ export async function obligationFindings(check) {
123
130
  * resolves to `obligation.unknown` in every fleet that did not declare an empty
124
131
  * id, which is the honest answer.
125
132
  */
126
- function taggedObjects(land) {
133
+ function taggedObjects(land, known) {
127
134
  const out = [];
128
135
  for (const element of land.elements) {
129
136
  for (const tag of element.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {
@@ -145,6 +152,20 @@ function taggedObjects(land) {
145
152
  });
146
153
  }
147
154
  }
155
+ // The topology, walked by the same rule and for the reason the rule exists: a
156
+ // tag is a claim about the object it sits on, and which MODEL that object
157
+ // lives in is not something a reader of the tag can see. Until this walk the
158
+ // same undeclared tag was an error on a container and silence on a
159
+ // datacenter — fail-open, and the case FEAT-1's ARCH-LOAM-DEPLOY-TAGGED was
160
+ // written about. A fleet that draws no topology contributes nothing here.
161
+ for (const d of taggedDeployment(land.deployment ?? NO_DEPLOYMENT, land.elements, known, OBLIGATION_TAG_PREFIX)) {
162
+ out.push({
163
+ obligation: d.obligation,
164
+ deployed: d,
165
+ where: d.where,
166
+ ...(d.service === undefined ? {} : { subject: d.service }),
167
+ });
168
+ }
148
169
  return out;
149
170
  }
150
171
  /**
@@ -167,17 +188,29 @@ async function uncoveredFindings(check, tagged) {
167
188
  const covers = await livingCovers(check);
168
189
  const elements = check.land.elements;
169
190
  const known = new Set(check.services.map((s) => s.id));
191
+ const covered = (t) => {
192
+ if (t.element !== undefined)
193
+ return covers.some((c) => coversElement(c, t.element));
194
+ if (t.edge !== undefined)
195
+ return covers.some((c) => coversEdge(c, t.edge, elements, known));
196
+ const d = t.deployed;
197
+ return d.edge === undefined
198
+ ? covers.some((c) => coversDeployNode(c, d.id))
199
+ : covers.some((c) => coversDeployEdge(c, d.edge.source, d.edge.target));
200
+ };
170
201
  return tagged
171
- .filter((t) => t.element !== undefined
172
- ? !covers.some((c) => coversElement(c, t.element))
173
- : !covers.some((c) => coversEdge(c, t.edge, elements, known)))
202
+ .filter((t) => !covered(t))
174
203
  .map((t) => ({
175
204
  severity: "warn",
176
205
  code: "obligation.uncovered",
177
- subject: t.subject,
206
+ ...(t.subject === undefined ? {} : { subject: t.subject }),
178
207
  message: `${t.where} carries #${OBLIGATION_TAG_PREFIX}${t.obligation} and no living arch requirement covers it — ` +
179
- `the obligation is placed and nothing says it is met. Write the requirement in ${t.subject}'s arch.spec.md with ` +
180
- `\`Covers: ${t.where}\` and a scenario that proves it, or drop the tag if the decision no longer applies here.`,
208
+ `the obligation is placed and nothing says it is met. Write the requirement in ` +
209
+ // A region, a datacenter or a cluster is owned by no service, so the
210
+ // sentence stays general rather than naming a team at random. Every
211
+ // other object here has a real owner and keeps the actionable form.
212
+ `${t.subject === undefined ? "the arch.spec.md of whichever service answers for this topology" : `${t.subject}'s arch.spec.md`}` +
213
+ ` with \`Covers: ${t.where}\` and a scenario that proves it, or drop the tag if the decision no longer applies here.`,
181
214
  }));
182
215
  }
183
216
  /**
@@ -200,10 +233,7 @@ async function livingCovers(check) {
200
233
  for (const path of [paths.spec, paths.archSpec]) {
201
234
  if (!existsSync(path))
202
235
  continue;
203
- const reqs = check.fleet === undefined
204
- ? parseRequirements(await readRequirementsDocument(path))
205
- : await check.fleet.readRequirements(path);
206
- entries.push(...coversEntries(reqs));
236
+ entries.push(...coversEntries(await check.fleet.readRequirements(path)));
207
237
  }
208
238
  }
209
239
  return entries;
@@ -1 +1 @@
1
- {"version":3,"file":"obligations.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/obligations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architectural obligation axis, graded against the fleet it is about.\n *\n * `core/obligations/obligations.ts` states what the axis IS and why it has this\n * shape. This module is the four questions asked of a fleet that has one, and\n * the order they are asked in — the same division `checks/fleet-shape.ts` keeps\n * for the authorization and capability vocabularies, which this sits beside on\n * the `landscape` target for their reason: an obligation crosses services by\n * construction, so no service target can answer for it.\n *\n * THE FOUR ARE TWO PAIRS, and each pair is one join read in both directions.\n * A tag nothing declares is an ERROR (`obligation.unknown`) and a declaration\n * nothing tags is a WARNING (`obligation.unapplied`) — exactly the asymmetry\n * `permissions.unknown`/`permissions.unenforced` already carry, and for the\n * same reason: a typo in a tag reads like a rule, while a word nobody has\n * adopted yet is an honest state. Then a tagged object no requirement covers is\n * a WARNING (`obligation.uncovered`) and an `adr:` naming no file is an ERROR\n * (`obligation.adr-missing`): the team's work being outstanding is normal, and\n * a pointer at a decision nobody can read is not.\n *\n * `obligation.uncovered` IS THE PREREQUISITE THE ROADMAP NAMED. `c4.uncovered`\n * has always been able to say \"this architecture object owes a requirement\",\n * but only about a NEW tagged element in a feature's `delta.likec4` — never\n * about the map the fleet actually runs on. This asks the same question of the\n * LIVING landscape, against the union of every service's living `arch.spec.md`\n * `Covers:` lines, which is the index that did not exist before.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport { closeIds, coversEdge, coversElement, type CoversEntry } from \"../../../core/c4/arch.js\";\nimport { elementService } from \"../../../core/c4/resolve/service.js\";\nimport { OBLIGATION_TAG_PREFIX, type ObligationVocabulary } from \"../../../core/obligations/obligations.js\";\nimport { servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { type ServiceEntry } from \"../../../core/repo/entries.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { readRequirementsDocument, parseRequirements } from \"../../../core/document/parse.js\";\nimport { coversEntries } from \"../checks/requirements.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport interface ObligationCheck {\n docsDir: DocsDir;\n vocabulary: ObligationVocabulary;\n /**\n * The living landscape, PARSED. Not nullable, and the absence is the point:\n * the landscape target returns before it reaches this call when the map is\n * missing or did not parse, so a run that could not read the map asks none of\n * the three questions below rather than answering them empty. \"Loam did not\n * look\" is never the same answer as \"there is nothing there\" — an unreadable\n * map graded as empty would report every declared obligation as applied\n * nowhere — and the suspension lives at the call site because that is where\n * the fact is known. It is also why the vocabulary half is a separate\n * function: those two verdicts must survive exactly those early returns.\n */\n land: LoadedDoc;\n services: ServiceEntry[];\n fleet?: FleetContext;\n}\n\n/**\n * The half that does not depend on the map: the file reads, and its `adr:`\n * pointers resolve.\n *\n * Separate from the map questions because the landscape target returns early\n * twice — an absent map, and one that did not parse — and a broken vocabulary\n * must still be named in exactly the runs where somebody is fixing something\n * else. The same placement `permissionFindings` and `capabilityFleetFindings`\n * already have, one level of precision finer.\n */\nexport function obligationVocabularyFindings(docsDir: DocsDir, vocabulary: ObligationVocabulary): Finding[] {\n if (!vocabulary.present) return [];\n if (vocabulary.invalid !== undefined) {\n // Reported ALONE, exactly as `permissions.invalid` and `capability.invalid`\n // are: every `#obl-` tag in the fleet resolves against this file, so grading\n // them on top of a broken vocabulary is a cascade, not a diagnosis.\n return [\n {\n severity: \"error\",\n code: \"obligation.invalid\",\n message:\n `landscape: architecture/obligations.yaml does not read as a vocabulary — ${vocabulary.invalid}. ` +\n \"Every `#obl-` tag on the fleet map resolves against this file, so none of them can be graded until it parses. \" +\n \"The shape is `obligations: {<id>: {description, adr}}`, ids spelled with letters, digits, `_` and `-` only.\",\n },\n ];\n }\n\n return adrFindings(docsDir, [...vocabulary.byId.values()]);\n}\n\n/** The three questions that need the map, suspended entirely when it could not be read. */\nexport async function obligationFindings(check: ObligationCheck): Promise<Finding[]> {\n const { vocabulary } = check;\n if (!vocabulary.present || vocabulary.invalid !== undefined) return [];\n const findings: Finding[] = [];\n\n // Which objects carry which obligation. Elements and edges are collected\n // together because every question below treats them alike: an obligation\n // applies to a thing on the map, and whether that thing is a box or an arrow\n // changes only how the message spells it and how `Covers:` matches it.\n const tagged = taggedObjects(check.land);\n const applied = new Set(tagged.map((t) => t.obligation));\n\n for (const id of [...applied].sort()) {\n if (vocabulary.byId.has(id)) continue;\n const close = closeIds(id, [...vocabulary.byId.keys()]);\n findings.push({\n severity: \"error\",\n code: \"obligation.unknown\",\n subject: id,\n message:\n `landscape: #${OBLIGATION_TAG_PREFIX}${id} tags ${count(tagged.filter((t) => t.obligation === id).length, \"object\")} on the fleet map, ` +\n `and architecture/obligations.yaml declares no obligation '${id}'` +\n (close.length > 0 ? ` — did you mean: ${close.join(\", \")}?` : \"\") +\n \". A tag that resolves to nothing reads exactly like a rule the fleet keeps: declare it, or fix the tag.\",\n details: tagged.filter((t) => t.obligation === id).map((t) => t.where),\n });\n }\n\n const unapplied = [...vocabulary.byId.keys()].filter((id) => !applied.has(id)).sort();\n if (unapplied.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"obligation.unapplied\",\n message:\n `landscape: ${unapplied.length} declared obligation(s) that no \\`#${OBLIGATION_TAG_PREFIX}\\` tag applies anywhere on the fleet map — ` +\n \"either a decision that was reversed and left its word behind, or one nobody has placed yet. \" +\n \"Tag the elements and edges it governs, or drop the declaration; a vocabulary is worth what cites it.\",\n details: unapplied,\n });\n }\n\n // ONLY A RESOLVED APPLICATION IS ASKED ABOUT COVERAGE. A tag naming no\n // declared obligation has already earned its error above, and \"no requirement\n // covers #obl-typo\" is not a second breach — it is the same one restated in\n // words that ask the reader to write a requirement for a rule that does not\n // exist. The use-case axis draws this line in the same place: only a resolved\n // `#cap-` claim keeps a promise.\n findings.push(...(await uncoveredFindings(check, tagged.filter((t) => vocabulary.byId.has(t.obligation)))));\n return findings;\n}\n\n/** One place an obligation is applied: which obligation, on what, and how a message names it. */\ninterface TaggedObject {\n obligation: string;\n /** The element, or the edge — exactly one is set. */\n element?: Elem;\n edge?: Rel;\n /** How the finding spells this object: an element id, or `source -> target`. */\n where: string;\n /** What the finding is filed under — the service that owns it, where one does. */\n subject: string;\n}\n\n/**\n * Every `#obl-` tag on the living map, one entry per (obligation, object) pair.\n *\n * A bare `#obl-` slugs to the empty string and is kept as an application on\n * purpose, exactly as a bare `#cap-` is kept as a claim: the prefix is the\n * author's opt-in however little follows it, and dropping it as unreserved\n * would leave an object that asked to be graded silently ungraded. It then\n * resolves to `obligation.unknown` in every fleet that did not declare an empty\n * id, which is the honest answer.\n */\nfunction taggedObjects(land: LoadedDoc): TaggedObject[] {\n const out: TaggedObject[] = [];\n for (const element of land.elements) {\n for (const tag of element.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n element,\n where: element.id,\n subject: elementService(element),\n });\n }\n }\n for (const edge of land.relationships) {\n for (const tag of edge.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n edge,\n where: `${edge.source} -> ${edge.target}`,\n subject: edge.target,\n });\n }\n }\n return out;\n}\n\n/**\n * `obligation.uncovered` — a tagged object no living arch requirement covers.\n *\n * The index is the union of EVERY service's living `arch.spec.md` `Covers:`\n * lines, because an obligation is placed by the architect on the map and met by\n * whichever team owns the thing it sits on — and a per-service index would\n * report an edge as uncovered because the requirement that covers it lives in\n * the service at the other end. `coversEntries` already drops REMOVED\n * requirements: a retired requirement covers nothing.\n *\n * ONE FINDING PER TAGGED OBJECT, subject = the service that owns it, so a\n * fleet-wide report reads as a worklist per team rather than as one line naming\n * forty edges.\n */\nasync function uncoveredFindings(check: ObligationCheck, tagged: TaggedObject[]): Promise<Finding[]> {\n if (tagged.length === 0) return [];\n const covers = await livingCovers(check);\n const elements = check.land.elements;\n const known = new Set(check.services.map((s) => s.id));\n return tagged\n .filter((t) =>\n t.element !== undefined\n ? !covers.some((c) => coversElement(c, t.element!))\n : !covers.some((c) => coversEdge(c, t.edge!, elements, known)),\n )\n .map((t) => ({\n severity: \"warn\" as const,\n code: \"obligation.uncovered\",\n subject: t.subject,\n message:\n `${t.where} carries #${OBLIGATION_TAG_PREFIX}${t.obligation} and no living arch requirement covers it — ` +\n `the obligation is placed and nothing says it is met. Write the requirement in ${t.subject}'s arch.spec.md with ` +\n `\\`Covers: ${t.where}\\` and a scenario that proves it, or drop the tag if the decision no longer applies here.`,\n }));\n}\n\n/**\n * Every `Covers:` entry the LIVING fleet writes, across both requirement\n * documents of every service.\n *\n * Existence is checked for both files and for the same reason\n * `permissionFindings` checks it: `arch.spec.md` is optional and most of a\n * legacy fleet has none, while `spec.md` is missing on a half-adopted service —\n * and `FleetContext.readRequirements` throws ENOENT, which surfaces as\n * `repository-unavailable` and takes the whole `--all` run down. Both axes are\n * read, not only the architecture one: `Covers:` is parsed everywhere for one\n * grammar's sake, and a fleet that wrote its outbox requirement in `spec.md`\n * has said the thing loam is asking about.\n */\nasync function livingCovers(check: ObligationCheck): Promise<CoversEntry[]> {\n const entries: CoversEntry[] = [];\n for (const service of check.services) {\n const paths = servicePathsAt(service.dir);\n for (const path of [paths.spec, paths.archSpec]) {\n if (!existsSync(path)) continue;\n const reqs =\n check.fleet === undefined\n ? parseRequirements(await readRequirementsDocument(path))\n : await check.fleet.readRequirements(path);\n entries.push(...coversEntries(reqs));\n }\n }\n return entries;\n}\n\n/**\n * `obligation.adr-missing` — an `adr:` path that names no file.\n *\n * The path is relative to the docs repo root rather than to\n * `architecture/obligations.yaml`, because that is how a reader of a fleet\n * document thinks about a fleet path, and because the value is a YAML field\n * rather than a markdown link — `link.unresolved` grades links written in prose,\n * and this one is written in a slot loam defined.\n */\nfunction adrFindings(docsDir: DocsDir, obligations: readonly { id: string; adr?: string }[]): Finding[] {\n return obligations\n .filter((o) => o.adr !== undefined && !existsSync(join(docsDir, o.adr)))\n .map((o) => ({\n severity: \"error\" as const,\n code: \"obligation.adr-missing\",\n subject: o.id,\n message:\n `landscape: obligation '${o.id}' names \\`adr: ${o.adr!}\\` and no such file exists — ` +\n \"the ADR is where the decision itself is written, so a pointer at nothing leaves every tag citing a rule nobody can read. \" +\n \"Fix the path (it is relative to the docs repo root), write the record, or drop the field.\",\n }));\n}\n\n/** `1 object` / `3 objects` — the plural spelled once, because three messages count things. */\nfunction count(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n"]}
1
+ {"version":3,"file":"obligations.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/obligations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architectural obligation axis, graded against the fleet it is about.\n *\n * `core/obligations/obligations.ts` states what the axis IS and why it has this\n * shape. This module is the four questions asked of a fleet that has one, and\n * the order they are asked in — the same division `checks/fleet-shape.ts` keeps\n * for the authorization and capability vocabularies, which this sits beside on\n * the `landscape` target for their reason: an obligation crosses services by\n * construction, so no service target can answer for it.\n *\n * THE FOUR ARE TWO PAIRS, and each pair is one join read in both directions.\n * A tag nothing declares is an ERROR (`obligation.unknown`) and a declaration\n * nothing tags is a WARNING (`obligation.unapplied`) — exactly the asymmetry\n * `permissions.unknown`/`permissions.unenforced` already carry, and for the\n * same reason: a typo in a tag reads like a rule, while a word nobody has\n * adopted yet is an honest state. Then a tagged object no requirement covers is\n * a WARNING (`obligation.uncovered`) and an `adr:` naming no file is an ERROR\n * (`obligation.adr-missing`): the team's work being outstanding is normal, and\n * a pointer at a decision nobody can read is not.\n *\n * `obligation.uncovered` IS THE PREREQUISITE THE ROADMAP NAMED. `c4.uncovered`\n * has always been able to say \"this architecture object owes a requirement\",\n * but only about a NEW tagged element in a feature's `delta.likec4` — never\n * about the map the fleet actually runs on. This asks the same question of the\n * LIVING landscape, against the union of every service's living `arch.spec.md`\n * `Covers:` lines, which is the index that did not exist before.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport {\n closeIds,\n coversDeployEdge,\n coversDeployNode,\n coversEdge,\n coversElement,\n type CoversEntry,\n} from \"../../../core/c4/arch.js\";\nimport { NO_DEPLOYMENT } from \"../../../core/c4/parsed/deployment.js\";\nimport { taggedDeployment, type TaggedDeployment } from \"../../../core/deployment/objects.js\";\nimport { elementService } from \"../../../core/c4/resolve/service.js\";\nimport { OBLIGATION_TAG_PREFIX, type ObligationVocabulary } from \"../../../core/obligations/obligations.js\";\nimport { servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { type ServiceEntry } from \"../../../core/repo/entries.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { coversEntries } from \"../checks/requirements.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport interface ObligationCheck {\n docsDir: DocsDir;\n vocabulary: ObligationVocabulary;\n /**\n * The living landscape, PARSED. Not nullable, and the absence is the point:\n * the landscape target returns before it reaches this call when the map is\n * missing or did not parse, so a run that could not read the map asks none of\n * the three questions below rather than answering them empty. \"Loam did not\n * look\" is never the same answer as \"there is nothing there\" — an unreadable\n * map graded as empty would report every declared obligation as applied\n * nowhere — and the suspension lives at the call site because that is where\n * the fact is known. It is also why the vocabulary half is a separate\n * function: those two verdicts must survive exactly those early returns.\n */\n land: LoadedDoc;\n services: ServiceEntry[];\n fleet: FleetContext;\n}\n\n/**\n * The half that does not depend on the map: the file reads, and its `adr:`\n * pointers resolve.\n *\n * Separate from the map questions because the landscape target returns early\n * twice — an absent map, and one that did not parse — and a broken vocabulary\n * must still be named in exactly the runs where somebody is fixing something\n * else. The same placement `permissionFindings` and `capabilityFleetFindings`\n * already have, one level of precision finer.\n */\nexport function obligationVocabularyFindings(docsDir: DocsDir, vocabulary: ObligationVocabulary): Finding[] {\n if (!vocabulary.present) return [];\n if (vocabulary.invalid !== undefined) {\n // Reported ALONE, exactly as `permissions.invalid` and `capability.invalid`\n // are: every `#obl-` tag in the fleet resolves against this file, so grading\n // them on top of a broken vocabulary is a cascade, not a diagnosis.\n return [\n {\n severity: \"error\",\n code: \"obligation.invalid\",\n message:\n `landscape: architecture/obligations.yaml does not read as a vocabulary — ${vocabulary.invalid}. ` +\n \"Every `#obl-` tag on the fleet map resolves against this file, so none of them can be graded until it parses. \" +\n \"The shape is `obligations: {<id>: {description, adr}}`, ids spelled with letters, digits, `_` and `-` only.\",\n },\n ];\n }\n\n return adrFindings(docsDir, [...vocabulary.byId.values()]);\n}\n\n/** The three questions that need the map, suspended entirely when it could not be read. */\nexport async function obligationFindings(check: ObligationCheck): Promise<Finding[]> {\n const { vocabulary } = check;\n if (!vocabulary.present || vocabulary.invalid !== undefined) return [];\n const findings: Finding[] = [];\n\n // Which objects carry which obligation. Elements and edges are collected\n // together because every question below treats them alike: an obligation\n // applies to a thing on the map, and whether that thing is a box or an arrow\n // changes only how the message spells it and how `Covers:` matches it.\n // The enumerated fleet rides in for the deployment half of the walk: an\n // instance deploys a CONTAINER more often than a whole system, and resolving\n // which service owns one needs the ancestor walk `serviceResolver` performs.\n // It is the same set `uncoveredFindings` resolves edge endpoints with, or the\n // message would name one service and the coverage check count another.\n const known = new Set(check.services.map((s) => s.id));\n const tagged = taggedObjects(check.land, known);\n const applied = new Set(tagged.map((t) => t.obligation));\n\n for (const id of [...applied].sort()) {\n if (vocabulary.byId.has(id)) continue;\n const close = closeIds(id, [...vocabulary.byId.keys()]);\n findings.push({\n severity: \"error\",\n code: \"obligation.unknown\",\n subject: id,\n message:\n `landscape: #${OBLIGATION_TAG_PREFIX}${id} tags ${count(tagged.filter((t) => t.obligation === id).length, \"object\")} on the fleet map, ` +\n `and architecture/obligations.yaml declares no obligation '${id}'` +\n (close.length > 0 ? ` — did you mean: ${close.join(\", \")}?` : \"\") +\n \". A tag that resolves to nothing reads exactly like a rule the fleet keeps: declare it, or fix the tag.\",\n details: tagged.filter((t) => t.obligation === id).map((t) => t.where),\n });\n }\n\n const unapplied = [...vocabulary.byId.keys()].filter((id) => !applied.has(id)).sort();\n if (unapplied.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"obligation.unapplied\",\n message:\n `landscape: ${unapplied.length} declared obligation(s) that no \\`#${OBLIGATION_TAG_PREFIX}\\` tag applies anywhere on the fleet map — ` +\n \"either a decision that was reversed and left its word behind, or one nobody has placed yet. \" +\n \"Tag the elements and edges it governs, or drop the declaration; a vocabulary is worth what cites it.\",\n details: unapplied,\n });\n }\n\n // ONLY A RESOLVED APPLICATION IS ASKED ABOUT COVERAGE. A tag naming no\n // declared obligation has already earned its error above, and \"no requirement\n // covers #obl-typo\" is not a second breach — it is the same one restated in\n // words that ask the reader to write a requirement for a rule that does not\n // exist. The use-case axis draws this line in the same place: only a resolved\n // `#cap-` claim keeps a promise.\n findings.push(...(await uncoveredFindings(check, tagged.filter((t) => vocabulary.byId.has(t.obligation)))));\n return findings;\n}\n\n/** One place an obligation is applied: which obligation, on what, and how a message names it. */\ninterface TaggedObject {\n obligation: string;\n /** The element, the edge, or the deployment object — exactly one is set. */\n element?: Elem;\n edge?: Rel;\n deployed?: TaggedDeployment;\n /**\n * How the finding spells this object, and how a `Covers:` line has to: an\n * element id, `source -> target`, or the `node:` forms.\n */\n where: string;\n /**\n * What the finding is filed under — the service that owns it, where one\n * does. ABSENT for a region, a datacenter or a cluster: nobody-s service\n * runs one, and naming a team that did not ask for the work is worse than\n * leaving the sentence general.\n */\n subject?: string;\n}\n\n/**\n * Every `#obl-` tag on the living map, one entry per (obligation, object) pair.\n *\n * A bare `#obl-` slugs to the empty string and is kept as an application on\n * purpose, exactly as a bare `#cap-` is kept as a claim: the prefix is the\n * author's opt-in however little follows it, and dropping it as unreserved\n * would leave an object that asked to be graded silently ungraded. It then\n * resolves to `obligation.unknown` in every fleet that did not declare an empty\n * id, which is the honest answer.\n */\nfunction taggedObjects(land: LoadedDoc, known: ReadonlySet<string>): TaggedObject[] {\n const out: TaggedObject[] = [];\n for (const element of land.elements) {\n for (const tag of element.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n element,\n where: element.id,\n subject: elementService(element),\n });\n }\n }\n for (const edge of land.relationships) {\n for (const tag of edge.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n edge,\n where: `${edge.source} -> ${edge.target}`,\n subject: edge.target,\n });\n }\n }\n // The topology, walked by the same rule and for the reason the rule exists: a\n // tag is a claim about the object it sits on, and which MODEL that object\n // lives in is not something a reader of the tag can see. Until this walk the\n // same undeclared tag was an error on a container and silence on a\n // datacenter — fail-open, and the case FEAT-1's ARCH-LOAM-DEPLOY-TAGGED was\n // written about. A fleet that draws no topology contributes nothing here.\n for (const d of taggedDeployment(land.deployment ?? NO_DEPLOYMENT, land.elements, known, OBLIGATION_TAG_PREFIX)) {\n out.push({\n obligation: d.obligation,\n deployed: d,\n where: d.where,\n ...(d.service === undefined ? {} : { subject: d.service }),\n });\n }\n return out;\n}\n\n/**\n * `obligation.uncovered` — a tagged object no living arch requirement covers.\n *\n * The index is the union of EVERY service's living `arch.spec.md` `Covers:`\n * lines, because an obligation is placed by the architect on the map and met by\n * whichever team owns the thing it sits on — and a per-service index would\n * report an edge as uncovered because the requirement that covers it lives in\n * the service at the other end. `coversEntries` already drops REMOVED\n * requirements: a retired requirement covers nothing.\n *\n * ONE FINDING PER TAGGED OBJECT, subject = the service that owns it, so a\n * fleet-wide report reads as a worklist per team rather than as one line naming\n * forty edges.\n */\nasync function uncoveredFindings(check: ObligationCheck, tagged: TaggedObject[]): Promise<Finding[]> {\n if (tagged.length === 0) return [];\n const covers = await livingCovers(check);\n const elements = check.land.elements;\n const known = new Set(check.services.map((s) => s.id));\n const covered = (t: TaggedObject): boolean => {\n if (t.element !== undefined) return covers.some((c) => coversElement(c, t.element!));\n if (t.edge !== undefined) return covers.some((c) => coversEdge(c, t.edge!, elements, known));\n const d = t.deployed!;\n return d.edge === undefined\n ? covers.some((c) => coversDeployNode(c, d.id!))\n : covers.some((c) => coversDeployEdge(c, d.edge!.source, d.edge!.target));\n };\n return tagged\n .filter((t) => !covered(t))\n .map((t) => ({\n severity: \"warn\" as const,\n code: \"obligation.uncovered\",\n ...(t.subject === undefined ? {} : { subject: t.subject }),\n message:\n `${t.where} carries #${OBLIGATION_TAG_PREFIX}${t.obligation} and no living arch requirement covers it — ` +\n `the obligation is placed and nothing says it is met. Write the requirement in ` +\n // A region, a datacenter or a cluster is owned by no service, so the\n // sentence stays general rather than naming a team at random. Every\n // other object here has a real owner and keeps the actionable form.\n `${t.subject === undefined ? \"the arch.spec.md of whichever service answers for this topology\" : `${t.subject}'s arch.spec.md`}` +\n ` with \\`Covers: ${t.where}\\` and a scenario that proves it, or drop the tag if the decision no longer applies here.`,\n }));\n}\n\n/**\n * Every `Covers:` entry the LIVING fleet writes, across both requirement\n * documents of every service.\n *\n * Existence is checked for both files and for the same reason\n * `permissionFindings` checks it: `arch.spec.md` is optional and most of a\n * legacy fleet has none, while `spec.md` is missing on a half-adopted service —\n * and `FleetContext.readRequirements` throws ENOENT, which surfaces as\n * `repository-unavailable` and takes the whole `--all` run down. Both axes are\n * read, not only the architecture one: `Covers:` is parsed everywhere for one\n * grammar's sake, and a fleet that wrote its outbox requirement in `spec.md`\n * has said the thing loam is asking about.\n */\nasync function livingCovers(check: ObligationCheck): Promise<CoversEntry[]> {\n const entries: CoversEntry[] = [];\n for (const service of check.services) {\n const paths = servicePathsAt(service.dir);\n for (const path of [paths.spec, paths.archSpec]) {\n if (!existsSync(path)) continue;\n entries.push(...coversEntries(await check.fleet.readRequirements(path)));\n }\n }\n return entries;\n}\n\n/**\n * `obligation.adr-missing` — an `adr:` path that names no file.\n *\n * The path is relative to the docs repo root rather than to\n * `architecture/obligations.yaml`, because that is how a reader of a fleet\n * document thinks about a fleet path, and because the value is a YAML field\n * rather than a markdown link — `link.unresolved` grades links written in prose,\n * and this one is written in a slot loam defined.\n */\nfunction adrFindings(docsDir: DocsDir, obligations: readonly { id: string; adr?: string }[]): Finding[] {\n return obligations\n .filter((o) => o.adr !== undefined && !existsSync(join(docsDir, o.adr)))\n .map((o) => ({\n severity: \"error\" as const,\n code: \"obligation.adr-missing\",\n subject: o.id,\n message:\n `landscape: obligation '${o.id}' names \\`adr: ${o.adr!}\\` and no such file exists — ` +\n \"the ADR is where the decision itself is written, so a pointer at nothing leaves every tag citing a rule nobody can read. \" +\n \"Fix the path (it is relative to the docs repo root), write the record, or drop the field.\",\n }));\n}\n\n/** `1 object` / `3 objects` — the plural spelled once, because three messages count things. */\nfunction count(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n"]}
@@ -29,8 +29,12 @@ export function printScorecard(card) {
29
29
  // because that is where it changes the reading: "120 vouched" and "120
30
30
  // vouched (100 sampled)" are different fleets, and a reader who stops at
31
31
  // the first number must not have been told the stronger of the two.
32
+ // The content count rides beside the digest count rather than inside the
33
+ // vouched one: both are staleness, of the words and of the code, and
34
+ // "0 stale digest(s)" alone read as "nothing is stale" in a run that had
35
+ // just warned `content.stale`.
32
36
  ` provenance ${p.vouched} vouched${p.sampledVouched > 0 ? ` (${p.sampledVouched} sampled)` : ""} — ` +
33
- `${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`);
37
+ `${p.contentStale} stale content · ${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`);
34
38
  // The ceiling is the features axis's `active` — the payload deliberately
35
39
  // carries that denominator once, so this line reads it from there.
36
40
  console.log(` verification ${f.active} feature(s) → ${v.recorded} recorded — ` +
@@ -57,7 +61,11 @@ export function printScorecard(card) {
57
61
  const notStarted = ADOPTION_AXES.filter((axis) => a[axis] === 0);
58
62
  console.log(` axes adopted: ${adopted.length === 0 ? "none" : adopted.join(", ")}` +
59
63
  (notStarted.length === 0 ? "" : ` · not started: ${notStarted.join(", ")}`));
64
+ // The stage values are `loam status`'s branching vocabulary, and `ready` read
65
+ // alone as "ready to ship" — so the two that mislead are glossed in place
66
+ // (`loam explain ready` has the long form).
60
67
  console.log(` features ${f.active} active — ` +
61
- ARTIFACT_STATUSES.map((stage) => `${f.stages[stage]} ${stage}`).join(" · "));
68
+ ARTIFACT_STATUSES.map((stage) => `${f.stages[stage]} ${stage}`).join(" · ") +
69
+ (f.active > 0 ? " (ready = docs written, not yet verified; done = verified)" : ""));
62
70
  }
63
71
  //# sourceMappingURL=print.js.map
@@ -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 // The stage values are `loam status`'s branching vocabulary, and `ready` read\n // alone as \"ready to ship\" — so the two that mislead are glossed in place\n // (`loam explain ready` has the long form).\n console.log(\n ` features ${f.active} active — ` +\n ARTIFACT_STATUSES.map((stage) => `${f.stages[stage]} ${stage}`).join(\" · \") +\n (f.active > 0 ? \" (ready = docs written, not yet verified; done = verified)\" : \"\"),\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,