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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../../src/core/verify/store/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The verification record's commit: staged beside the target, compared against\n * the byte pre-image the merge consumed, swapped in by one rename(2).\n *\n * The record used to go down as a plain `writeFile`, which cost three ways at\n * once: two `--record` runs read the same pre-image and the last writer\n * silently discarded the other service's attestation; a process killed\n * mid-write left the ONLY copy truncated, unreadable, and blocking every later\n * record; and an edit that landed between the read and the write was buried\n * under bytes computed from a document that no longer existed. The docs lock\n * (taken by `loam verify` before its authoritative read) closes the first;\n * this module closes the other two, and backstops the lock itself for a writer\n * that never took it.\n *\n * A sub-package of core/verify rather than a sixth file in it, and a consumer\n * of core/staging's primitives rather than a re-implementation: `stageWrites`\n * and `swapStaged` already do temp-beside-target, whole-content compare-and-set\n * and same-directory rename — vouch commits through exactly this shape.\n */\nimport { message, rollbackMessage, rollbackStaged, stageWrites, StagingRaceError, swapStaged } from \"../../staging/commit.js\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { type PlannedWrite, type StagedWrite } from \"../../staging/writes.js\";\nimport { renderVerification } from \"./render.js\";\nimport { verificationPath, type Verification } from \"../record.js\";\n\n/**\n * `record-raced` re-runs cleanly: the merge is recomputed over whatever the\n * other writer left. The other two are staging's own vocabulary, with staging's\n * meanings — `merge-failed` trusts the repo, `rollback-incomplete` does not.\n */\nexport type VerificationCommit =\n | { ok: true; path: string }\n | { ok: false; code: \"record-raced\" | \"merge-failed\" | \"rollback-incomplete\"; message: string };\n\n/**\n * `preImage` is the exact bytes the caller's merge was computed from, or null\n * when the record was absent at the authoritative read — in which case the\n * commit may only CREATE (link(2) refuses a file that appeared since), never\n * replace one it has not read.\n */\nexport async function commitVerification(\n featureDir: string,\n v: Verification,\n preImage: Buffer | null,\n docsDir: string,\n): Promise<VerificationCommit> {\n const path = verificationPath(featureDir);\n const display = repoPath(docsDir, path);\n const write: PlannedWrite =\n preImage === null ? { path, content: renderVerification(v), exclusive: true } : { path, content: renderVerification(v) };\n // Staging touches the filesystem — a read-only feature directory, a full\n // disk, a pre-image that will not read. None of that is a bug in loam, and\n // letting it escape as `internal` sends the reader looking for one (archive\n // draws the same line around its own `stageWrites`). Nothing was written.\n let staged: StagedWrite[];\n try {\n staged = await stageWrites([write]);\n } catch (err) {\n return { ok: false, code: \"merge-failed\", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };\n }\n // Joined by path, not by index — vouch's rule, for vouch's reason: one entry\n // per planned write in order is true today and promised nowhere, and a\n // compare-and-set over the wrong pair would vouch for bytes the merge never\n // saw. A missing entry fails closed as a merge that never happened.\n const s = staged.find((x) => x.write.path === path);\n if (s === undefined) {\n await rollbackStaged(staged);\n return { ok: false, code: \"merge-failed\", message: `staging returned no entry for ${display} — nothing was recorded` };\n }\n // The compare the read promised: the file as staging found it must be the\n // file the merge consumed. `swapStaged` re-checks against ITS read just\n // before the rename, so together the two compares cover the whole window\n // from the authoritative read to the swap.\n const edited = preImage === null ? s.before !== null : s.before === null || !s.before.equals(preImage);\n if (edited) {\n await rollbackStaged(staged);\n return { ok: false, code: \"record-raced\", message: racedMessage(display) };\n }\n try {\n await swapStaged(staged);\n } catch (err) {\n const failures = await rollbackStaged(staged);\n if (failures.length > 0) {\n return {\n ok: false,\n code: \"rollback-incomplete\",\n message: rollbackMessage(err, failures, \"written\"),\n };\n }\n // EEXIST is the exclusive create losing its race — same fact as the CAS\n // refusal, discovered by link(2) instead of by the compare.\n if (err instanceof StagingRaceError || (err as NodeJS.ErrnoException).code === \"EEXIST\") {\n return { ok: false, code: \"record-raced\", message: racedMessage(display) };\n }\n return { ok: false, code: \"merge-failed\", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };\n }\n return { ok: true, path };\n}\n\nfunction racedMessage(display: string): string {\n return (\n `${display} changed while this record was being written — another writer or an editor landed between the read and the write. ` +\n \"Nothing was written and their bytes are untouched. Re-run: the merge is recomputed over the record as it now stands.\"\n );\n}\n"]}
1
+ {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../../src/core/verify/store/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The verification record's commit: staged beside the target, compared against\n * the byte pre-image the merge consumed, swapped in by one rename(2) per file.\n * A write is one file, or two: a service's own `verification/<svc>.yaml`, and\n * — once nothing in it counts any more — the removal of the one-file record it\n * supersedes. Never another service's file: those are not this run's to touch.\n *\n * The record used to go down as a plain `writeFile`, which cost three ways at\n * once: two `--record` runs read the same pre-image and the last writer\n * silently discarded the other service's attestation; a process killed\n * mid-write left the ONLY copy truncated, unreadable, and blocking every later\n * record; and an edit that landed between the read and the write was buried\n * under bytes computed from a document that no longer existed. The docs lock\n * (taken by `loam verify` before its authoritative read) closes the first;\n * this module closes the other two, and backstops the lock itself for a writer\n * that never took it.\n *\n * A sub-package of core/verify rather than a sixth file in it, and a consumer\n * of core/staging's primitives rather than a re-implementation: `stageWrites`\n * and `swapStaged` already do temp-beside-target, whole-content compare-and-set\n * and same-directory rename — vouch commits through exactly this shape.\n */\nimport { message, rollbackMessage, rollbackStaged, stageWrites, StagingRaceError, swapStaged } from \"../../staging/commit.js\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { type PlannedWrite, type StagedWrite } from \"../../staging/writes.js\";\nimport { renderVerification } from \"./render.js\";\nimport { type Verification } from \"../record.js\";\n\n/**\n * `record-raced` re-runs cleanly: the merge is recomputed over whatever the\n * other writer left. The other two are staging's own vocabulary, with staging's\n * meanings — `merge-failed` trusts the repo, `rollback-incomplete` does not.\n */\nexport type VerificationCommit =\n | { ok: true }\n | { ok: false; code: \"record-raced\" | \"merge-failed\" | \"rollback-incomplete\"; message: string };\n\n/**\n * One file of the record this commit writes or removes.\n *\n * `preImage` is the exact bytes the caller's merge was computed from, or null\n * when the file was absent at the authoritative read — in which case the\n * commit may only CREATE it (link(2) refuses a file that appeared since), never\n * replace one it has not read. `record: null` removes the file, and only over\n * the bytes that read saw: a delete is a replace with nothing, and it is held\n * to the same compare.\n */\nexport interface RecordWrite {\n path: string;\n record: Verification | null;\n preImage: Buffer | null;\n}\n\n/**\n * Writes land in the order given, so a caller puts what must survive a kill\n * FIRST: a service's own file before the one-file record it supersedes, so a\n * process killed between the two renames leaves a record that still reads\n * whole — the new file counted, the old one superseded by it — rather than one\n * with the old file gone and nothing in its place.\n */\nexport async function commitVerification(writes: RecordWrite[], docsDir: string): Promise<VerificationCommit> {\n const planned = writes.map(\n (w): PlannedWrite =>\n w.record === null\n ? { path: w.path, content: null }\n : w.preImage === null\n ? { path: w.path, content: renderVerification(w.record), exclusive: true }\n : { path: w.path, content: renderVerification(w.record) },\n );\n // Staging touches the filesystem — a read-only feature directory, a full\n // disk, a pre-image that will not read. None of that is a bug in loam, and\n // letting it escape as `internal` sends the reader looking for one (archive\n // draws the same line around its own `stageWrites`). Nothing was written.\n let staged: StagedWrite[];\n try {\n staged = await stageWrites(planned);\n } catch (err) {\n return { ok: false, code: \"merge-failed\", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };\n }\n for (const w of writes) {\n const display = repoPath(docsDir, w.path);\n // Joined by path, not by index — vouch's rule, for vouch's reason: one\n // entry per planned write in order is true today and promised nowhere, and\n // a compare-and-set over the wrong pair would vouch for bytes the merge\n // never saw. A missing entry fails closed as a merge that never happened.\n const s = staged.find((x) => x.write.path === w.path);\n if (s === undefined) {\n await rollbackStaged(staged);\n return { ok: false, code: \"merge-failed\", message: `staging returned no entry for ${display} — nothing was recorded` };\n }\n // The compare the read promised: the file as staging found it must be the\n // file the merge consumed. `swapStaged` re-checks against ITS read just\n // before the rename, so together the two compares cover the whole window\n // from the authoritative read to the swap.\n const edited = w.preImage === null ? s.before !== null : s.before === null || !s.before.equals(w.preImage);\n if (edited) {\n await rollbackStaged(staged);\n return { ok: false, code: \"record-raced\", message: racedMessage(display) };\n }\n }\n try {\n await swapStaged(staged);\n } catch (err) {\n const failures = await rollbackStaged(staged);\n if (failures.length > 0) {\n return {\n ok: false,\n code: \"rollback-incomplete\",\n message: rollbackMessage(err, failures, \"written\"),\n };\n }\n // EEXIST is the exclusive create losing its race — same fact as the CAS\n // refusal, discovered by link(2) instead of by the compare.\n if (err instanceof StagingRaceError || (err as NodeJS.ErrnoException).code === \"EEXIST\") {\n const path = err instanceof StagingRaceError ? err.path : (writes.find((w) => w.preImage === null)?.path ?? writes[0]!.path);\n return { ok: false, code: \"record-raced\", message: racedMessage(repoPath(docsDir, path)) };\n }\n return { ok: false, code: \"merge-failed\", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };\n }\n return { ok: true };\n}\n\nfunction racedMessage(display: string): string {\n return (\n `${display} changed while this record was being written — another writer or an editor landed between the read and the write. ` +\n \"Nothing was written and their bytes are untouched. Re-run: the merge is recomputed over the record as it now stands.\"\n );\n}\n"]}
@@ -6,8 +6,10 @@
6
6
  * re-graded on the way in, because anything may have edited it since), so the
7
7
  * two were cohabiting rather than forming a codec, while the RENDER and the
8
8
  * COMMIT genuinely are one concern — this module produces exactly the bytes
9
- * whose atomic swap that one performs. `../file.ts` keeps the reading and the
10
- * grading, and its header says where the render went.
9
+ * whose atomic swap that one performs. The reading went on to a package of its
10
+ * own, `../read/` (`file.ts` finds and grades the files, `shape.ts` says what a
11
+ * file must hold, `union.ts` adds the files up), and `../read/file.ts`'s header
12
+ * says where the render went.
11
13
  */
12
14
  import { stringify } from "yaml";
13
15
  import {} from "../record.js";
@@ -42,8 +44,10 @@ export function renderVerification(v) {
42
44
  ? [
43
45
  "#",
44
46
  "# Schema 2 is federated: each service entry under `attestations` binds its claim ids",
45
- "# and file:line evidence to that repository's git commit. Missing claims are honestly",
46
- "# unanswered; another service run may add them without rewriting existing attestations.",
47
+ "# and file:line evidence to that repository's git commit. Each attesting service has a",
48
+ "# file of its own, verification/<svc>.yaml, and the record is the directory: claims",
49
+ "# another service owns are honestly unanswered in this file and answered in that",
50
+ "# service's own, and loam adds the files up when it reads them.",
47
51
  "# `evidence_pins` records each cited file's sha256 (CRLF-normalized) and line text at",
48
52
  "# the attested commit, so `loam validate` in that repo can convict drift later; a pin",
49
53
  "# identifies what was cited — it does not prove the claim true.",
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../../src/core/verify/store/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAqB,MAAM,cAAc,CAAC;AAEjD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAe;IAChD,MAAM,MAAM,GAAG;QACb,6BAA6B,CAAC,CAAC,OAAO,+BAA+B,CAAC,CAAC,OAAO,iDAAiD;QAC/H,GAAG;QACH,iFAAiF;QACjF,gFAAgF;QAChF,mFAAmF;QACnF,mFAAmF;QACnF,qFAAqF;QACrF,qFAAqF;QACrF,kFAAkF;QAClF,mFAAmF;QACnF,4CAA4C;QAC5C,GAAG;QACH,8EAA8E;QAC9E,8EAA8E;QAC9E,kFAAkF;QAClF,kFAAkF;QAClF,2BAA2B;QAC3B,GAAG;QACH,uFAAuF;QACvF,iFAAiF;QACjF,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC;gBACE,GAAG;gBACH,sFAAsF;gBACtF,uFAAuF;gBACvF,yFAAyF;gBACzF,uFAAuF;gBACvF,uFAAuF;gBACvF,iEAAiE;aAClE;YACH,CAAC,CAAC,EAAE,CAAC;QACP,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,qFAAqF;IACrF,OAAO,MAAM,GAAG,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/**\n * The record as bytes: the header and the YAML that `store/commit.ts` swaps\n * into place. Moved here from `../file.ts` when the contract-report validation\n * pushed that module over the line limit — and the seam is real, not\n * arithmetic: reading was never the inverse of rendering (a record is\n * re-graded on the way in, because anything may have edited it since), so the\n * two were cohabiting rather than forming a codec, while the RENDER and the\n * COMMIT genuinely are one concern — this module produces exactly the bytes\n * whose atomic swap that one performs. `../file.ts` keeps the reading and the\n * grading, and its header says where the render went.\n */\nimport { stringify } from \"yaml\";\nimport { type Verification } from \"../record.js\";\n\n/**\n * The record as a file. The header explains what the reader is looking at,\n * because the whole point is that this is legible to someone who has never run\n * loam — including the part loam cannot vouch for.\n */\nexport function renderVerification(v: Verification): string {\n const header = [\n `# Verification record for ${v.feature} — written by \\`loam verify ${v.feature}\\` (--results / --contract-results / --record).`,\n \"#\",\n \"# Every claim below was derived mechanically from this feature's own artifacts:\",\n \"# delta.likec4, specs/<svc>/spec.md, specs/<svc>/arch.spec.md and specs/<svc>/\",\n \"# openapi.yaml. Each verdict names who answered it: `answered_by: runner` means a\",\n \"# test report's digest-tagged scenarios answered it mechanically — cucumber JSON,\",\n \"# or loam's own {\\\"loamScenarioReport\\\": 1, ...} shape that any runner adapts into;\",\n \"# `answered_by: external-runner` means a contract-test report's operationId-matched\",\n \"# entries did, and `contractReport:` records which file a --contract-results run\",\n \"# read; `answered_by: agent` means somebody's word about the code, which loam did\",\n \"# not check. Nothing gates on any of them.\",\n \"#\",\n \"# A `scenario.tested` claim confirmed by an agent is ATTESTED, not run: loam\",\n \"# reports it as `verify.scenario-attested` and the feature does not count as\",\n \"# verified until a report answers it. `report:` records the file a --results run\",\n \"# read — its sha256 and mtime say WHICH file, not that it came from that commit;\",\n \"# no digest can say that.\",\n \"#\",\n \"# `checklist` is a digest of the claim ids. If `loam verify` stops reporting the same\",\n \"# one, the feature changed after this was recorded and these answers are stale.\",\n ...(v.schema === 2\n ? [\n \"#\",\n \"# Schema 2 is federated: each service entry under `attestations` binds its claim ids\",\n \"# and file:line evidence to that repository's git commit. Missing claims are honestly\",\n \"# unanswered; another service run may add them without rewriting existing attestations.\",\n \"# `evidence_pins` records each cited file's sha256 (CRLF-normalized) and line text at\",\n \"# the attested commit, so `loam validate` in that repo can convict drift later; a pin\",\n \"# identifies what was cited — it does not prove the claim true.\",\n ]\n : []),\n \"\",\n ].join(\"\\n\");\n // lineWidth 0: never fold a claim onto a second line — these are grepped and diffed.\n return header + stringify(v, { lineWidth: 0 });\n}\n"]}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../../src/core/verify/store/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAqB,MAAM,cAAc,CAAC;AAEjD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAe;IAChD,MAAM,MAAM,GAAG;QACb,6BAA6B,CAAC,CAAC,OAAO,+BAA+B,CAAC,CAAC,OAAO,iDAAiD;QAC/H,GAAG;QACH,iFAAiF;QACjF,gFAAgF;QAChF,mFAAmF;QACnF,mFAAmF;QACnF,qFAAqF;QACrF,qFAAqF;QACrF,kFAAkF;QAClF,mFAAmF;QACnF,4CAA4C;QAC5C,GAAG;QACH,8EAA8E;QAC9E,8EAA8E;QAC9E,kFAAkF;QAClF,kFAAkF;QAClF,2BAA2B;QAC3B,GAAG;QACH,uFAAuF;QACvF,iFAAiF;QACjF,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC;gBACE,GAAG;gBACH,sFAAsF;gBACtF,wFAAwF;gBACxF,qFAAqF;gBACrF,kFAAkF;gBAClF,iEAAiE;gBACjE,uFAAuF;gBACvF,uFAAuF;gBACvF,iEAAiE;aAClE;YACH,CAAC,CAAC,EAAE,CAAC;QACP,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,qFAAqF;IACrF,OAAO,MAAM,GAAG,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/**\n * The record as bytes: the header and the YAML that `store/commit.ts` swaps\n * into place. Moved here from `../file.ts` when the contract-report validation\n * pushed that module over the line limit — and the seam is real, not\n * arithmetic: reading was never the inverse of rendering (a record is\n * re-graded on the way in, because anything may have edited it since), so the\n * two were cohabiting rather than forming a codec, while the RENDER and the\n * COMMIT genuinely are one concern — this module produces exactly the bytes\n * whose atomic swap that one performs. The reading went on to a package of its\n * own, `../read/` (`file.ts` finds and grades the files, `shape.ts` says what a\n * file must hold, `union.ts` adds the files up), and `../read/file.ts`'s header\n * says where the render went.\n */\nimport { stringify } from \"yaml\";\nimport { type Verification } from \"../record.js\";\n\n/**\n * The record as a file. The header explains what the reader is looking at,\n * because the whole point is that this is legible to someone who has never run\n * loam — including the part loam cannot vouch for.\n */\nexport function renderVerification(v: Verification): string {\n const header = [\n `# Verification record for ${v.feature} — written by \\`loam verify ${v.feature}\\` (--results / --contract-results / --record).`,\n \"#\",\n \"# Every claim below was derived mechanically from this feature's own artifacts:\",\n \"# delta.likec4, specs/<svc>/spec.md, specs/<svc>/arch.spec.md and specs/<svc>/\",\n \"# openapi.yaml. Each verdict names who answered it: `answered_by: runner` means a\",\n \"# test report's digest-tagged scenarios answered it mechanically — cucumber JSON,\",\n \"# or loam's own {\\\"loamScenarioReport\\\": 1, ...} shape that any runner adapts into;\",\n \"# `answered_by: external-runner` means a contract-test report's operationId-matched\",\n \"# entries did, and `contractReport:` records which file a --contract-results run\",\n \"# read; `answered_by: agent` means somebody's word about the code, which loam did\",\n \"# not check. Nothing gates on any of them.\",\n \"#\",\n \"# A `scenario.tested` claim confirmed by an agent is ATTESTED, not run: loam\",\n \"# reports it as `verify.scenario-attested` and the feature does not count as\",\n \"# verified until a report answers it. `report:` records the file a --results run\",\n \"# read — its sha256 and mtime say WHICH file, not that it came from that commit;\",\n \"# no digest can say that.\",\n \"#\",\n \"# `checklist` is a digest of the claim ids. If `loam verify` stops reporting the same\",\n \"# one, the feature changed after this was recorded and these answers are stale.\",\n ...(v.schema === 2\n ? [\n \"#\",\n \"# Schema 2 is federated: each service entry under `attestations` binds its claim ids\",\n \"# and file:line evidence to that repository's git commit. Each attesting service has a\",\n \"# file of its own, verification/<svc>.yaml, and the record is the directory: claims\",\n \"# another service owns are honestly unanswered in this file and answered in that\",\n \"# service's own, and loam adds the files up when it reads them.\",\n \"# `evidence_pins` records each cited file's sha256 (CRLF-normalized) and line text at\",\n \"# the attested commit, so `loam validate` in that repo can convict drift later; a pin\",\n \"# identifies what was cited — it does not prove the claim true.\",\n ]\n : []),\n \"\",\n ].join(\"\\n\");\n // lineWidth 0: never fold a claim onto a second line — these are grepped and diffed.\n return header + stringify(v, { lineWidth: 0 });\n}\n"]}
@@ -14,6 +14,7 @@ export function issueFinding(i) {
14
14
  gates: gatesArchive(i),
15
15
  ...(i.subject === undefined ? {} : { subject: i.subject }),
16
16
  ...(i.locations === undefined ? {} : { locations: i.locations }),
17
+ ...(i.details === undefined ? {} : { details: i.details }),
17
18
  message: i.message,
18
19
  };
19
20
  }
@@ -47,6 +48,9 @@ const NEVER_OVERRIDABLE = new Set([
47
48
  "c4.service-binding-invalid",
48
49
  "glossary.term-exists",
49
50
  "usecase.flow-exists",
51
+ "deployment.doc-exists",
52
+ "deployment.doc-invalid",
53
+ "deployment.doc-reserved",
50
54
  "usecase.flow-invalid",
51
55
  ]);
52
56
  export function approveOverrides(i) {
@@ -1 +1 @@
1
- {"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../src/core/vocabulary/issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuBH,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAC3C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;QACtB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QAChE,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,iBAAiB,GAA2B,IAAI,GAAG,CAAY;IACnE,4BAA4B;IAC5B,sBAAsB;IACtB,qBAAqB;IACrB,sBAAsB;CACvB,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,CAAQ;IACvC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC","sourcesContent":["/**\n * A coherence breach: something that makes a feature unsafe to merge into the\n * living docs. Shared by every check the archive gate runs.\n */\n\nimport type { Finding, FindingLocation } from \"./report.js\";\n\nexport interface Issue {\n severity: \"error\" | \"warn\";\n /** Stable machine identifier for the breach — see the labels below. */\n code: IssueCode;\n /** What the breach is about when that is narrower than the feature — usually a service. */\n subject?: string;\n message: string;\n locations?: FindingLocation[];\n /**\n * Does this issue stop `loam archive`? Severity and gating answer two\n * different questions: severity says whether the DOCUMENT is valid (`loam\n * validate` fails on errors), gating says whether the MERGE is safe. They\n * usually agree — errors gate, warnings do not — so the field is stated only\n * where they diverge: a legal document whose merge would drop authored\n * content is a warning that gates.\n */\n gates?: boolean;\n}\n\n/** Whether `loam archive` refuses on this issue without `--approve`. */\nexport function gatesArchive(i: Issue): boolean {\n return i.gates ?? i.severity === \"error\";\n}\n\n/** One issue in the shared validation/status finding shape. */\nexport function issueFinding(i: Issue): Finding {\n return {\n severity: i.severity,\n code: i.code,\n gates: gatesArchive(i),\n ...(i.subject === undefined ? {} : { subject: i.subject }),\n ...(i.locations === undefined ? {} : { locations: i.locations }),\n message: i.message,\n };\n}\n\n/**\n * Whether `--approve` can move this issue when it gates. A third axis beside\n * severity and gating: severity answers \"is the DOCUMENT valid\", gating\n * answers \"is the MERGE safe\", and this one answers whose call the refusal is\n * — a human's or the grammar's. Almost every gating issue is a judgment about\n * the feature, which is exactly the judgment `--approve` exists to override.\n *\n * The exceptions are the ones where approval could not change what happens.\n * `c4.service-binding-invalid`: the binding IS the path the merge would write,\n * so the fix is the name, never the flag. `glossary.term-exists`: the merge is\n * a whole-file copy over an authored definition, so approving it is approving a\n * deletion nobody described — and the alternative the message names (edit the\n * living definition in the same change) costs one `git mv` and loses nothing.\n * `usecase.flow-exists` is the same act one axis over: the merge would copy a\n * feature's flow over a living one, and an ordered hop sequence is recorded\n * nowhere else either. `usecase.flow-invalid` is the other kind of exception —\n * not a deletion but a BLINDNESS: loam could not read the feature's flows\n * against the map its own merge would leave, so it graded nothing, and\n * `--approve` overrides loam's judgment about coherence and never its ability\n * to read an axis. That is the same line `delta.likec4`'s unparseable case is\n * refused on, one merge over.\n *\n * `archive` refuses these under the same `not-coherent` envelope and spells\n * this verdict per issue as the additive `overridable` key, so a `--json`\n * consumer branches on data rather than on a code list.\n */\nconst NEVER_OVERRIDABLE: ReadonlySet<IssueCode> = new Set<IssueCode>([\n \"c4.service-binding-invalid\",\n \"glossary.term-exists\",\n \"usecase.flow-exists\",\n \"usecase.flow-invalid\",\n]);\n\nexport function approveOverrides(i: Issue): boolean {\n return !NEVER_OVERRIDABLE.has(i.code);\n}\n\n/**\n * The three grades a CAPABILITY DOCUMENT earns on its own terms, named as a\n * union of their own because both corpora emit them and the two must stay one\n * list: `validate --all` grades the living `capabilities/<id>/spec.md` and\n * files them as Findings, while the archive gate grades a feature's\n * `features/<FEAT>/capabilities/<id>/spec.md` and files the SAME three as\n * archive-gating Issues.\n *\n * Grading the delta is not belt-and-braces. Without it, a capability\n * requirement with no `Requirement-ID:` — or one carrying `Operations:`, the\n * service-altitude line the whole corpus exists to keep out — merges into the\n * living tree and only THEN earns its error, against a document whoever reads\n * the finding did not write. The feature-local delta must not become a second\n * way to write service requirements, and this is the rule that stops it.\n *\n * No code is added to the product here: all three already shipped, are already\n * emitted from `core/capabilities/findings.ts` with literal `code:` values, and\n * are already documented in the `/loam-check` table. What the union member adds\n * is the compiler's permission to file the same finding at the archive gate.\n */\nexport type CapabilityDocCode =\n /** a requirement in a capability document with no `Requirement-ID:` — identity by heading, which every join to it breaks on */\n | \"capability.requirement-unidentified\"\n /** a capability requirement carrying `Operations:`/`Covers:`/`Publishes:`/`Consumes:` — a service requirement filed at the wrong altitude */\n | \"capability.requirement-service-scoped\"\n /** a capability requirement carrying `Capability:` or `Realizes:` — the axis's own joins, which point into the tree and so do nothing written inside it */\n | \"capability.requirement-inert-join\";\n\nexport type IssueCode =\n /* --- cross-axis: C4 <-> requirements <-> OpenAPI --- */\n /** the architecture axis could not be read at all */\n | \"delta.invalid\"\n /** the delta declares elements or edges but none carry the feature tag — loam cannot see any of it */\n | \"delta.nothing-tagged\"\n /** E1 — a requirement governs an operation its service's OpenAPI does not define */\n | \"spec-api.op-undefined\"\n /** E1, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"spec-api.op-pending\"\n /** E2 — a C4 edge calls an operation the target's OpenAPI does not define */\n | \"c4-api.op-undefined\"\n /** E2, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"c4-api.op-pending\"\n /** E2's lifecycle shadow — a NEW tagged edge consumes an operation the living provider contract marks `deprecated: true`; advisory, never a gate */\n | \"c4-api.op-deprecated\"\n /** a NEW tagged edge consumes an operation this same feature removes */\n | \"c4-api.op-removing\"\n /** an operation-removal marker addresses no living path+method */\n | \"openapi.remove-target-missing\"\n /** an operation-removal marker's id differs from the living operation at that path+method */\n | \"openapi.remove-target-mismatch\"\n /** a REMOVED requirement governs an operation but the feature has no matching removal marker */\n | \"openapi.remove-marker-missing\"\n /** an operation-removal marker is not justified by a REMOVED requirement */\n | \"openapi.remove-marker-unjustified\"\n /* --- the event axis: C4 <-> requirements <-> AsyncAPI, joined on the message name --- */\n /** the FEATURE's own asyncapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every event-axis check for the service is suspended */\n | \"asyncapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or one on a slot the living contract does not have; a malformed `x-loam-baselines` record, an entry of it naming a component surface the delta does not declare, or one whose living counterpart is gone */\n | \"asyncapi.baseline-invalid\"\n /** a feature slot or component surface with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it; warn that gates, counted per service */\n | \"asyncapi.baseline-missing\"\n /** the living slot or component surface changed since this delta pinned it — merging would discard whoever landed in between */\n | \"asyncapi.baseline-stale\"\n /** a slot-removal marker addresses a (section, key) the living contract does not have */\n | \"asyncapi.remove-target-missing\"\n /** a message-removal marker's name differs from the living declaration at that key */\n | \"asyncapi.remove-target-mismatch\"\n /** a REMOVED requirement's Publishes:/Consumes: line names a message the living contract still declares, with no matching removal marker in the feature */\n | \"asyncapi.remove-marker-missing\"\n /** a message-removal marker no REMOVED requirement's Publishes:/Consumes: line justifies — message slots only; channel and operation slots need exactness alone */\n | \"asyncapi.remove-marker-unjustified\"\n /** the feature retires a message the LIVING fleet still consumes — a landscape consumes-edge, or another service's living requirement */\n | \"asyncapi.remove-message-consumed\"\n /** another feature in flight adds or edits the same (service, message) — whichever archives second replaces the other's declaration wholesale */\n | \"asyncapi.message-conflict\"\n /** internal `$ref`(s) resolving to nothing — validate's warn on a living contract; a gating plan issue at archive when the MERGED document would carry the dangling reference */\n | \"asyncapi.ref-unresolved\"\n /** a tagged edge publishes/consumes a message its bound service's contract — as the feature's merge would leave it, feature ∪ living where no delta exists — declares no matching send/receive operation for */\n | \"c4-event.message-undefined\"\n /** softened — the message is introduced by another feature still in flight; archive that one first */\n | \"c4-event.message-pending\"\n /** a delta requirement's Publishes:/Consumes: line names a message the service's contract — as the feature's merge would leave it, so a retired declaration stops answering — does not declare in that direction; validate's living-side code, graded in feature scope too */\n | \"spec-event.message-undefined\"\n /** softened — the named message is introduced by another feature still in flight */\n | \"spec-event.message-pending\"\n /** W1 — an operation is called but no requirement governs it */\n | \"c4.op-ungoverned\"\n /** W2 — the feature adds an operation no architecture edge consumes */\n | \"api.op-unconsumed\"\n /** W3 — a new service arrives with no requirement delta */\n | \"service.no-requirement-delta\"\n /** W4 — a \"Calls\" edge carries no operation link */\n | \"c4.op-link-missing\"\n /** an explicit `metadata { service }` binding — a tagged element's, or one riding anywhere inside its authored block — breaks the service-id grammar; the merge would splice the name into the living landscape verbatim, and probe services/<id>/ with it; never overridable, the path is a mechanical fact */\n | \"c4.service-binding-invalid\"\n /* --- delta shape: does the diff apply to the living spec it claims to change? --- */\n /** a heading that nearly matches the delta grammar — its requirements merge as nothing */\n | \"delta.unknown-section\"\n /** a delta file with requirements but no delta section anywhere — the whole file would merge as nothing */\n | \"delta.no-delta-sections\"\n /** a requirement stranded under a prose heading — archive will not merge it */\n | \"delta.requirement-not-merged\"\n /** a Requirement-ID in a delta violates the stable-id grammar */\n | \"delta.requirement-id-invalid\"\n /** one delta requirement declares Requirement-ID more than once */\n | \"delta.requirement-id-repeated\"\n /** one Requirement-ID identifies multiple requirements in a delta */\n | \"delta.requirement-id-duplicate\"\n /** the living spec has malformed/ambiguous IDs and cannot be selected safely */\n | \"delta.living-requirement-id-invalid\"\n /** a delta's stable ID and heading select different living requirements */\n | \"delta.requirement-identity-collision\"\n /** MODIFIED a requirement the living spec does not have */\n | \"delta.modified-unknown\"\n /** REMOVED a requirement the living spec does not have */\n | \"delta.removed-unknown\"\n /** ADDED a requirement the living spec already has — the merge would replace it */\n | \"delta.added-duplicate\"\n /** ADDED a name that differs only in case from a living requirement — the merge matches exactly, so both would coexist */\n | \"delta.added-near-duplicate\"\n /** MODIFIED something another feature in flight introduces — an ordering dependency */\n | \"delta.modified-pending\"\n /** REMOVED something another feature in flight introduces */\n | \"delta.removed-pending\"\n /** two features in flight add the same requirement to the same service */\n | \"delta.added-conflict\"\n /* --- archive plan: breaches only the merge computation itself can see --- */\n /** a LIVING requirement outside `## Requirements` — the merge rewrites only that section, so it would land twice */\n | \"living.requirement-outside-requirements\"\n /** the delta redefines an operation the living OpenAPI already has — the merge overwrites it wholesale */\n | \"openapi.op-modified\"\n /** a component the merge carries — reached by a merged operation, or declared by a delta whose whole change is components — already exists in the living OpenAPI with different content, and the merge overwrites it wholesale */\n | \"openapi.component-modified\"\n /** a $ref reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — merging would write a dangling reference */\n | \"openapi.ref-unresolved\"\n /** the delta redefines a PATH-level key (parameters, servers, summary) the living OpenAPI already has — the overwrite applies to every operation on that path */\n | \"openapi.path-item-modified\"\n /** the delta redefines a `components.messages` slot the living AsyncAPI already has — the merge overwrites the living message wholesale */\n | \"asyncapi.message-modified\"\n /** the delta redefines a `channels` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included */\n | \"asyncapi.channel-modified\"\n /** the delta redefines an `operations` slot the living AsyncAPI already has — the merge overwrites it wholesale */\n | \"asyncapi.operation-modified\"\n /** the delta declares a component SURFACE outside `components.messages` — a schema, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale */\n | \"asyncapi.component-modified\"\n /** an `x-loam-remove: true` marker with no operationId — loam cannot tell which operation it retires, and the marker itself would reach the living contract */\n | \"openapi.remove-marker-anonymous\"\n /** an `x-loam-remove: true` written at PATH level, beside the methods — it addresses no operation, so it retires nothing and is not a contract key either */\n | \"openapi.remove-marker-path-level\"\n /** an `x-loam-remove: true` nested on an INLINE channel message — inline messages are channel interior, never slots, so it retires nothing; when the channel is otherwise unchanged the marker surfaces nowhere else at all */\n | \"asyncapi.remove-marker-inline\"\n /** the living OpenAPI defines one operationId in two (path, method) slots — every join on the id picks one arbitrarily */\n | \"openapi.duplicate-operationid\"\n /** the FEATURE's own openapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every contract-axis check for the service is suspended, and the archive plan refuses the merge mechanically */\n | \"openapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or sits on an operation the living contract does not have; a malformed `x-loam-baselines` record, an entry of it pinning a surface the delta does not restate, or one whose living counterpart is gone */\n | \"openapi.baseline-invalid\"\n /** a feature operation, restated path-level key or component with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it */\n | \"openapi.baseline-missing\"\n /** the living operation, path-level key or component changed since this delta pinned it — merging would discard whoever landed in between */\n | \"openapi.baseline-stale\"\n /** the feature retires an operation the LIVING fleet still consumes — a landscape edge's `metadata { op }`, or another service's living requirement */\n | \"openapi.remove-op-consumed\"\n /** two requirements in the LIVING document share one heading — MODIFIED rewrites the first, REMOVED deletes both, so no delta applies predictably */\n | \"delta.living-duplicate-requirement\"\n /** another feature in flight MODIFIES/REMOVES the same living requirement — whichever archives second replaces the other's text wholesale */\n | \"delta.modified-conflict\"\n /** a `Based-On:` line whose value is not a digest, or declared twice — the pin cannot be compared, so it protects nothing */\n | \"delta.baseline-invalid\"\n /** a MODIFIED/REMOVED requirement with no `Based-On:` — nothing can tell whether the living text moved under it */\n | \"delta.baseline-missing\"\n /** the living requirement changed since this delta was written — merging it would silently discard whoever landed in between */\n | \"delta.baseline-stale\"\n /** the archive creates `services/<id>/` but nothing writes its `model.likec4` — the fleet gate will report the service incomplete */\n | \"service.no-model\"\n /* --- capability axis: the Capability: join against architecture/capabilities.yaml --- */\n /** a delta requirement's `Capability:` entry that the fleet vocabulary does not declare — the merge would land a join that resolves to nothing, so it blocks archive like other errors, and `--approve` overrides it */\n | \"capability.unknown\"\n /** a delta requirement's `Realizes:` entry that names no capability requirement — same argument as `capability.unknown` one join over: the merge would land a pointer at a promise that does not exist, looking exactly like a working one */\n | \"capability.realizes-unknown\"\n /** a capability requirement this feature ADDS that no `Realizes:` line in the same feature's service deltas names — the merge would land a business promise nothing in this change keeps. A warning that GATES, `--approve` overrides: the document is legal (writing ahead of the fleet is the intended use, which is why `capability.requirement-unrealized` warns and never gates), the MERGE is what is unsafe. Joined by `Realizes:`, NOT by `Covers:` — the name is the roadmap's, the join is the other one */\n | \"capability.uncovered\"\n /** the same join in the removal direction — this feature RETIRES a capability requirement that something the merge leaves behind still realizes, so the archive would leave `capability.realizes-unknown` standing against a document nobody in the feature touched. An error, exactly as `openapi.remove-op-consumed` is for the identical shape one axis over */\n | \"capability.remove-requirement-realized\"\n | CapabilityDocCode\n /* --- the domain glossary: a term a feature introduces --- */\n /** a `features/<FEAT>/glossary/<term>.md` whose term the living glossary already defines — the merge is a whole-file copy, so it would replace an authored definition wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local glossary document INTRODUCES a term, and rewriting one belongs in a pull request where git produces the conflict */\n | \"glossary.term-exists\"\n /* --- the use-case axis: a flow a feature introduces --- */\n /** a `features/<FEAT>/usecases/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The flows were not graded, and archiving would copy them into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it */\n | \"usecase.flow-invalid\"\n /** a `features/<FEAT>/usecases/<name>.likec4` whose flow the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored hop sequence wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local flow INTRODUCES a use case, and rewriting one belongs in a pull request where git produces the conflict */\n | \"usecase.flow-exists\"\n /* --- authoring: did a person actually write this? --- */\n /** a document `loam new` scaffolded still carries its exact placeholder text — the merge would publish a requirement, scenario or description nobody authored */\n | \"scaffold.placeholder\"\n /** intent.md is missing, or says nothing outside the scaffold's own comments — nothing states why the feature exists */\n | \"intent.empty\"\n /* --- docs-repo contract: --all only, never a merge question --- */\n /** AGENTS.md carries no version stamp, or one older than the running binary — the agent contract may have drifted */\n | \"agents.stale\";\n"]}
1
+ {"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../src/core/vocabulary/issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+BH,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAC3C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;QACtB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QAChE,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,iBAAiB,GAA2B,IAAI,GAAG,CAAY;IACnE,4BAA4B;IAC5B,sBAAsB;IACtB,qBAAqB;IACrB,uBAAuB;IACvB,wBAAwB;IACxB,yBAAyB;IACzB,sBAAsB;CACvB,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,CAAQ;IACvC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC","sourcesContent":["/**\n * A coherence breach: something that makes a feature unsafe to merge into the\n * living docs. Shared by every check the archive gate runs.\n */\n\nimport type { Finding, FindingLocation } from \"./report.js\";\n\nexport interface Issue {\n severity: \"error\" | \"warn\";\n /** Stable machine identifier for the breach — see the labels below. */\n code: IssueCode;\n /** What the breach is about when that is narrower than the feature — usually a service. */\n subject?: string;\n message: string;\n /**\n * The evidence lines behind the message, when it has a list to show — the\n * `details` every `Finding` already carries. `loam archive`'s overwrite\n * warnings put the current consumers of the overwritten operation or\n * message here, one per line, so the message stays one sentence however\n * many there are.\n */\n details?: string[];\n locations?: FindingLocation[];\n /**\n * Does this issue stop `loam archive`? Severity and gating answer two\n * different questions: severity says whether the DOCUMENT is valid (`loam\n * validate` fails on errors), gating says whether the MERGE is safe. They\n * usually agree — errors gate, warnings do not — so the field is stated only\n * where they diverge: a legal document whose merge would drop authored\n * content is a warning that gates.\n */\n gates?: boolean;\n}\n\n/** Whether `loam archive` refuses on this issue without `--approve`. */\nexport function gatesArchive(i: Issue): boolean {\n return i.gates ?? i.severity === \"error\";\n}\n\n/** One issue in the shared validation/status finding shape. */\nexport function issueFinding(i: Issue): Finding {\n return {\n severity: i.severity,\n code: i.code,\n gates: gatesArchive(i),\n ...(i.subject === undefined ? {} : { subject: i.subject }),\n ...(i.locations === undefined ? {} : { locations: i.locations }),\n ...(i.details === undefined ? {} : { details: i.details }),\n message: i.message,\n };\n}\n\n/**\n * Whether `--approve` can move this issue when it gates. A third axis beside\n * severity and gating: severity answers \"is the DOCUMENT valid\", gating\n * answers \"is the MERGE safe\", and this one answers whose call the refusal is\n * — a human's or the grammar's. Almost every gating issue is a judgment about\n * the feature, which is exactly the judgment `--approve` exists to override.\n *\n * The exceptions are the ones where approval could not change what happens.\n * `c4.service-binding-invalid`: the binding IS the path the merge would write,\n * so the fix is the name, never the flag. `glossary.term-exists`: the merge is\n * a whole-file copy over an authored definition, so approving it is approving a\n * deletion nobody described — and the alternative the message names (edit the\n * living definition in the same change) costs one `git mv` and loses nothing.\n * `usecase.flow-exists` is the same act one axis over: the merge would copy a\n * feature's flow over a living one, and an ordered hop sequence is recorded\n * nowhere else either. `usecase.flow-invalid` is the other kind of exception —\n * not a deletion but a BLINDNESS: loam could not read the feature's flows\n * against the map its own merge would leave, so it graded nothing, and\n * `--approve` overrides loam's judgment about coherence and never its ability\n * to read an axis. That is the same line `delta.likec4`'s unparseable case is\n * refused on, one merge over.\n *\n * `archive` refuses these under the same `not-coherent` envelope and spells\n * this verdict per issue as the additive `overridable` key, so a `--json`\n * consumer branches on data rather than on a code list.\n */\nconst NEVER_OVERRIDABLE: ReadonlySet<IssueCode> = new Set<IssueCode>([\n \"c4.service-binding-invalid\",\n \"glossary.term-exists\",\n \"usecase.flow-exists\",\n \"deployment.doc-exists\",\n \"deployment.doc-invalid\",\n \"deployment.doc-reserved\",\n \"usecase.flow-invalid\",\n]);\n\nexport function approveOverrides(i: Issue): boolean {\n return !NEVER_OVERRIDABLE.has(i.code);\n}\n\n/**\n * The three grades a CAPABILITY DOCUMENT earns on its own terms, named as a\n * union of their own because both corpora emit them and the two must stay one\n * list: `validate --all` grades the living `capabilities/<id>/spec.md` and\n * files them as Findings, while the archive gate grades a feature's\n * `features/<FEAT>/capabilities/<id>/spec.md` and files the SAME three as\n * archive-gating Issues.\n *\n * Grading the delta is not belt-and-braces. Without it, a capability\n * requirement with no `Requirement-ID:` — or one carrying `Operations:`, the\n * service-altitude line the whole corpus exists to keep out — merges into the\n * living tree and only THEN earns its error, against a document whoever reads\n * the finding did not write. The feature-local delta must not become a second\n * way to write service requirements, and this is the rule that stops it.\n *\n * No code is added to the product here: all three already shipped, are already\n * emitted from `core/capabilities/findings.ts` with literal `code:` values, and\n * are already documented in the `/loam-check` table. What the union member adds\n * is the compiler's permission to file the same finding at the archive gate.\n */\nexport type CapabilityDocCode =\n /** a requirement in a capability document with no `Requirement-ID:` — identity by heading, which every join to it breaks on */\n | \"capability.requirement-unidentified\"\n /** a capability requirement carrying `Operations:`/`Covers:`/`Publishes:`/`Consumes:` — a service requirement filed at the wrong altitude */\n | \"capability.requirement-service-scoped\"\n /** a capability requirement carrying `Capability:` or `Realizes:` — the axis's own joins, which point into the tree and so do nothing written inside it */\n | \"capability.requirement-inert-join\";\n\nexport type IssueCode =\n /* --- cross-axis: C4 <-> requirements <-> OpenAPI --- */\n /** the architecture axis could not be read at all */\n | \"delta.invalid\"\n /** the delta declares elements or edges but none carry the feature tag — loam cannot see any of it */\n | \"delta.nothing-tagged\"\n /** E1 — a requirement governs an operation its service's OpenAPI does not define */\n | \"spec-api.op-undefined\"\n /** E1, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"spec-api.op-pending\"\n /** E2 — a C4 edge calls an operation the target's OpenAPI does not define */\n | \"c4-api.op-undefined\"\n /** E2, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"c4-api.op-pending\"\n /** E2's lifecycle shadow — a NEW tagged edge consumes an operation the living provider contract marks `deprecated: true`; advisory, never a gate */\n | \"c4-api.op-deprecated\"\n /** a NEW tagged edge consumes an operation this same feature removes — retires with a marker, or replaces at its method and path so the merge leaves it defined nowhere */\n | \"c4-api.op-removing\"\n /** an operation-removal marker addresses no living path+method */\n | \"openapi.remove-target-missing\"\n /** an operation-removal marker's id differs from the living operation at that path+method */\n | \"openapi.remove-target-mismatch\"\n /** a REMOVED requirement governs an operation its service's living contract still defines, and the feature has no matching removal marker — owed only while there is one to retire: not for another service's id, not for one this delta already replaces at its address */\n | \"openapi.remove-marker-missing\"\n /** an operation-removal marker is not justified by a REMOVED requirement */\n | \"openapi.remove-marker-unjustified\"\n /* --- the event axis: C4 <-> requirements <-> AsyncAPI, joined on the message name --- */\n /** the FEATURE's own asyncapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every event-axis check for the service is suspended */\n | \"asyncapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or one on a slot the living contract does not have; a malformed `x-loam-baselines` record, an entry of it naming a component surface the delta does not declare, or one whose living counterpart is gone */\n | \"asyncapi.baseline-invalid\"\n /** a feature slot or component surface with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it; warn that gates, counted per service */\n | \"asyncapi.baseline-missing\"\n /** the living slot or component surface changed since this delta pinned it — merging would discard whoever landed in between */\n | \"asyncapi.baseline-stale\"\n /** a `channels`/`operations` slot, or a message by name, that this feature declares, the living contract no longer has, and an ARCHIVED feature's removal retired — or a message declared at a `components.messages` key an archived feature moved it away from — the merge cannot tell a quote of a retired or moved slot from an addition, so archiving would put it back. A warning that GATES; `--approve` re-adds it on purpose */\n | \"asyncapi.slot-revived\"\n /** a slot-removal marker addresses a (section, key) the living contract does not have */\n | \"asyncapi.remove-target-missing\"\n /** a message-removal marker's name differs from the living declaration at that key */\n | \"asyncapi.remove-target-mismatch\"\n /** a REMOVED requirement's Publishes:/Consumes: line names a message the living contract still declares and the merged contract would too, with no matching removal marker in the feature */\n | \"asyncapi.remove-marker-missing\"\n /** a message-removal marker no REMOVED requirement's Publishes:/Consumes: line justifies — message slots only; channel and operation slots need exactness alone */\n | \"asyncapi.remove-marker-unjustified\"\n /** the feature retires a message, or stops sending it, while the fleet would still name it after the merge — the consumer index's consumes-edges, other services' `Consumes:` lines and use-case hops while nothing would still send it after the merge (no service, no `#external` element publishing it), plus the provider's own `Publishes:` lines and `publishes` edges whoever else sends it (core/consumers/) */\n | \"asyncapi.remove-message-consumed\"\n /** another feature in flight adds or edits the same (service, message) — whichever archives second replaces the other's declaration wholesale */\n | \"asyncapi.message-conflict\"\n /** internal `$ref`(s) resolving to nothing — validate's warn on a living contract; a gating plan issue at archive when the MERGED document would carry the dangling reference */\n | \"asyncapi.ref-unresolved\"\n /** a tagged edge publishes/consumes a message its bound service's contract — as the feature's merge would leave it, feature ∪ living where no delta exists — declares no matching send/receive operation for */\n | \"c4-event.message-undefined\"\n /** softened — the message is introduced by another feature still in flight; archive that one first. Also: a tagged `consumes` edge whose message only an in-flight feature's delta sends */\n | \"c4-event.message-pending\"\n /** a delta requirement's Publishes:/Consumes: line names a message the service's contract — as the feature's merge would leave it, so a retired declaration stops answering — does not declare in that direction; validate's living-side code, graded in feature scope too */\n | \"spec-event.message-undefined\"\n /** softened — the named message is introduced by another feature still in flight. Also: a delta `Consumes:` line whose message only an in-flight feature's delta sends — archive that one first */\n | \"spec-event.message-pending\"\n /** a delta's `Consumes:` line or tagged `consumes` edge names a message nothing would send after the merge — no service's contract as the merge leaves it, no #external element on the living map or in the delta; validate's living-side code, graded in feature scope so the archive cannot land the consumer it would report */\n | \"spine.message-unproduced\"\n /** a tagged edge's `publishes`/`consumes` key sits on an end that is infrastructure (a topic, queue or store, which neither publishes nor consumes) while a service sits at the other end — `publishes` binds the SOURCE and `consumes` the TARGET, so the key binds nothing and nothing grades it; an `#external` system at that end is an outside party and never raises it; validate's living-side code, graded in feature scope too. Warn: the drawing is legal, the link is lost */\n | \"spine.message-unbound\"\n /** W1 — an operation is called but no requirement governs it */\n | \"c4.op-ungoverned\"\n /** W2 — the feature adds an operation no architecture edge consumes */\n | \"api.op-unconsumed\"\n /** W3 — a new service arrives with no requirement delta */\n | \"service.no-requirement-delta\"\n /** W4 — a \"Calls\" edge carries no operation link */\n | \"c4.op-link-missing\"\n /** an explicit `metadata { service }` binding — a tagged element's, or one riding anywhere inside its authored block — breaks the service-id grammar; the merge would splice the name into the living landscape verbatim, and probe services/<id>/ with it; never overridable, the path is a mechanical fact */\n | \"c4.service-binding-invalid\"\n /* --- delta shape: does the diff apply to the living spec it claims to change? --- */\n /** a heading that nearly matches the delta grammar — its requirements merge as nothing */\n | \"delta.unknown-section\"\n /** a delta file with requirements but no delta section anywhere — the whole file would merge as nothing */\n | \"delta.no-delta-sections\"\n /** a requirement stranded under a prose heading — archive will not merge it */\n | \"delta.requirement-not-merged\"\n /** a Requirement-ID in a delta violates the stable-id grammar */\n | \"delta.requirement-id-invalid\"\n /** one delta requirement declares Requirement-ID more than once */\n | \"delta.requirement-id-repeated\"\n /** one Requirement-ID identifies multiple requirements in a delta */\n | \"delta.requirement-id-duplicate\"\n /** the living spec has malformed/ambiguous IDs and cannot be selected safely */\n | \"delta.living-requirement-id-invalid\"\n /** a delta's stable ID and heading select different living requirements */\n | \"delta.requirement-identity-collision\"\n /** MODIFIED a requirement the living spec does not have */\n | \"delta.modified-unknown\"\n /** REMOVED a requirement the living spec does not have */\n | \"delta.removed-unknown\"\n /** ADDED a requirement the living spec already has — the merge would replace it */\n | \"delta.added-duplicate\"\n /** ADDED a name that differs only in case from a living requirement — the merge matches exactly, so both would coexist */\n | \"delta.added-near-duplicate\"\n /** MODIFIED something another feature in flight introduces — an ordering dependency */\n | \"delta.modified-pending\"\n /** REMOVED something another feature in flight introduces */\n | \"delta.removed-pending\"\n /** two features in flight add the same requirement to the same service */\n | \"delta.added-conflict\"\n /* --- archive plan: breaches only the merge computation itself can see --- */\n /** a LIVING requirement outside `## Requirements` — the merge rewrites only that section, so it would land twice */\n | \"living.requirement-outside-requirements\"\n /** the delta redefines an operation the living OpenAPI already has — the merge overwrites it wholesale */\n | \"openapi.op-modified\"\n /** a component the merge carries — reached by a merged operation, or declared by a delta whose whole change is components — already exists in the living OpenAPI with different content, and the merge overwrites it wholesale */\n | \"openapi.component-modified\"\n /** a $ref reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — merging would write a dangling reference */\n | \"openapi.ref-unresolved\"\n /** the delta redefines a PATH-level key (parameters, servers, summary) the living OpenAPI already has — the overwrite applies to every operation on that path */\n | \"openapi.path-item-modified\"\n /** the delta redefines a `components.messages` slot the living AsyncAPI already has — the merge overwrites the living message wholesale */\n | \"asyncapi.message-modified\"\n /** the delta redefines a `channels` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included */\n | \"asyncapi.channel-modified\"\n /** the delta redefines an `operations` slot the living AsyncAPI already has — the merge overwrites it wholesale */\n | \"asyncapi.operation-modified\"\n /** the delta declares a component SURFACE outside `components.messages` — a schema, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale */\n | \"asyncapi.component-modified\"\n /** the delta's OpenAPI spells an `info` key differently from the living contract — the merge carries paths and components and never `info`, so the living contract keeps its own and the delta's version or description is lost unless the author edits the living file in the same change */\n | \"openapi.info-not-merged\"\n /** the delta's AsyncAPI spells an `info` key differently from the living contract — the merge carries channels, operations and components and never `info`; the event-axis twin of `openapi.info-not-merged` */\n | \"asyncapi.info-not-merged\"\n /** an `x-loam-remove: true` marker with no operationId — loam cannot tell which operation it retires, and the marker itself would reach the living contract */\n | \"openapi.remove-marker-anonymous\"\n /** an `x-loam-remove: true` written at PATH level, beside the methods — it addresses no operation, so it retires nothing and is not a contract key either */\n | \"openapi.remove-marker-path-level\"\n /** an `x-loam-remove: true` nested on an INLINE channel message — inline messages are channel interior, never slots, so it retires nothing; when the channel is otherwise unchanged the marker surfaces nowhere else at all */\n | \"asyncapi.remove-marker-inline\"\n /** the living OpenAPI defines one operationId in two (path, method) slots — every join on the id picks one arbitrarily */\n | \"openapi.duplicate-operationid\"\n /** the FEATURE's own openapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every contract-axis check for the service is suspended, and the archive plan refuses the merge mechanically */\n | \"openapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or sits on an operation the living contract does not have; a malformed `x-loam-baselines` record, an entry of it pinning a surface the delta does not restate, or one whose living counterpart is gone */\n | \"openapi.baseline-invalid\"\n /** a feature operation, restated path-level key or component with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it */\n | \"openapi.baseline-missing\"\n /** the living operation, path-level key or component changed since this delta pinned it — merging would discard whoever landed in between */\n | \"openapi.baseline-stale\"\n /** an operation this feature spells, the living contract no longer has, and an ARCHIVED feature's removal marker retired — or spells at an address an archived feature moved it away from — the merge cannot tell a quote of a retired or moved operation from an addition, so archiving would put it back (beside its new address, for a move). A warning that GATES; `--approve` re-adds it on purpose */\n | \"openapi.op-revived\"\n /** the feature takes out of the contract an operation the fleet would still consume after the merge — the consumer index's edges of the `architecture/` project, other services' `Operations:` lines (not one naming its own service's operation of the same id), use-case hops, plus the provider's own requirements and self-edges (core/consumers/). Two shapes: a removal marker retires it, or the delta defines another operation at its method and path and the contract the merge would write defines it nowhere */\n | \"openapi.remove-op-consumed\"\n /** two requirements in the LIVING document share one heading — MODIFIED rewrites the first, REMOVED deletes both, so no delta applies predictably */\n | \"delta.living-duplicate-requirement\"\n /** another feature in flight MODIFIES/REMOVES the same living requirement — whichever archives second replaces the other's text wholesale */\n | \"delta.modified-conflict\"\n /** a `Based-On:` line whose value is not a digest, or declared twice — the pin cannot be compared, so it protects nothing */\n | \"delta.baseline-invalid\"\n /** a MODIFIED/REMOVED requirement with no `Based-On:` — nothing can tell whether the living text moved under it */\n | \"delta.baseline-missing\"\n /** the living requirement changed since this delta was written — merging it would silently discard whoever landed in between */\n | \"delta.baseline-stale\"\n /** the archive creates `services/<id>/` but nothing writes its `model.likec4` — the fleet gate will report the service incomplete. An ERROR, the severity `validate` gives the same directory, that does NOT gate: the merge is correct, the fleet it leaves is not yet whole */\n | \"service.no-model\"\n /** a health.yaml alert or SLI a living arch.spec.md requirement covers today that this feature's arch.spec.md delta would leave covered by nothing — validate's service-scope warning, predicted before the merge */\n | \"health.uncovered\"\n /* --- capability axis: the Capability: join against architecture/capabilities.yaml --- */\n /** a delta requirement's `Capability:` entry that the fleet vocabulary does not declare — the merge would land a join that resolves to nothing, so it blocks archive like other errors, and `--approve` overrides it */\n | \"capability.unknown\"\n /** a delta requirement's `Realizes:` entry that names no capability requirement — same argument as `capability.unknown` one join over: the merge would land a pointer at a promise that does not exist, looking exactly like a working one */\n | \"capability.realizes-unknown\"\n /** a capability requirement this feature ADDS that no `Realizes:` line in the same feature's service deltas names — the merge would land a business promise nothing in this change keeps. A warning that GATES, `--approve` overrides: the document is legal (writing ahead of the fleet is the intended use, which is why `capability.requirement-unrealized` warns and never gates), the MERGE is what is unsafe. Joined by `Realizes:`, NOT by `Covers:` — the name is the roadmap's, the join is the other one */\n | \"capability.uncovered\"\n /** the same join in the removal direction — this feature RETIRES a capability requirement that something the merge leaves behind still realizes, so the archive would leave `capability.realizes-unknown` standing against a document nobody in the feature touched. An error, exactly as `openapi.remove-op-consumed` is for the identical shape one axis over */\n | \"capability.remove-requirement-realized\"\n | CapabilityDocCode\n /* --- the domain glossary: a term a feature introduces --- */\n /** a `features/<FEAT>/glossary/<term>.md` whose term the living glossary already defines — the merge is a whole-file copy, so it would replace an authored definition wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local glossary document INTRODUCES a term, and rewriting one belongs in a pull request where git produces the conflict */\n | \"glossary.term-exists\"\n /* --- the use-case axis: a flow a feature introduces --- */\n /** a `features/<FEAT>/usecases/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The flows were not graded, and archiving would copy them into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it */\n | \"usecase.flow-invalid\"\n /** a hop of a feature's own flow that no relationship on the map its merge would leave backs — validate's fleet-scope code, graded in feature scope against the merge preview, so the archive cannot land a flow `validate --all` then convicts */\n | \"usecase.step-unbacked\"\n /** a hop of a feature's own flow whose candidate relationships on the post-merge map disagree about the operation — validate's fleet-scope warning, graded in feature scope */\n | \"usecase.step-contested\"\n /** a cross-service hop of a feature's own flow backed by a relationship carrying no operation — validate's fleet-scope warning, graded in feature scope */\n | \"usecase.step-unlinked\"\n /** a `#cap-` tag on a feature's own flow that resolves to no capability the merge would leave declared — validate's fleet-scope code, graded in feature scope */\n | \"usecase.capability-unresolved\"\n /** a `#req-` tag on a feature's own flow that resolves to no requirement of the capability its `#cap-` tag names, as the merge would leave that document — or a flow with no resolving `#cap-` at all; validate's fleet-scope code, graded in feature scope. Also a LIVING flow — in `architecture/` or a service's own — whose resolving `#req-` tag this feature's merge breaks — by REMOVING the requirement it claims, or, beside a MODIFIED of it, by adding an id that flattens to the same tag: the fleet's error one archive early */\n | \"usecase.requirement-unresolved\"\n /** a LIVING flow — in `architecture/` or a service's own — whose `#req-` tag claims a requirement — a capability's, or that service's own — this feature MODIFIES: the tag still resolves after the archive (unless an id the feature adds flattens to it too — then it is `usecase.requirement-unresolved` instead) and nothing grades the drawing against the new wording, so the author reads the flow against the replacement. A warning that never gates */\n | \"usecase.requirement-modified\"\n /** a `features/<FEAT>/usecases/<name>.likec4` whose flow the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored hop sequence wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local flow INTRODUCES a use case, and rewriting one belongs in a pull request where git produces the conflict */\n | \"usecase.flow-exists\"\n /* --- the deployment axis: the topology a feature introduces --- */\n /** a `features/<FEAT>/deployment/<name>.likec4` whose file the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored topology wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local deployment document INTRODUCES topology, and rewriting one belongs in a pull request where git produces the conflict. The same act, and the same severity, as `usecase.flow-exists` one axis over */\n | \"deployment.doc-exists\"\n /** a `features/<FEAT>/deployment/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The topology was not graded, and archiving would copy it into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it; the twin of `usecase.flow-invalid` one axis over */\n | \"deployment.doc-invalid\"\n /** a `features/<FEAT>/deployment/<name>.likec4` whose name is one loam owns inside `architecture/` — `subsystems.likec4` and `services.likec4` (both generated, overwritten wholesale by `loam subsystem sync`, and parsed by nothing, so anything landing in either is invisible from the moment it archives), `landscape.likec4` (spliced into, never replaced), or a path under `usecases/` (the flow tree). Refused whether or not the target exists, which is the point: three of the four fail silently when it does not. An error with no legal reading, so `--approve` changes nothing about it */\n | \"deployment.doc-reserved\"\n /* --- authoring: did a person actually write this? --- */\n /** a document `loam new` scaffolded still carries its exact placeholder text — the merge would publish a requirement, scenario or description nobody authored */\n | \"scaffold.placeholder\"\n /** intent.md is missing, or says nothing outside the scaffold's own comments — nothing states why the feature exists */\n | \"intent.empty\"\n /* --- docs-repo contract: --all only, never a merge question --- */\n /** AGENTS.md carries no version stamp, or one older than the running binary — the agent contract may have drifted */\n | \"agents.stale\";\n"]}
@@ -16,6 +16,14 @@ export const MATURITY_LADDER = ["empty", "partial", "documented", "sourced", "vo
16
16
  * stamped it. `verified` with no digest is a claim with nothing
17
17
  * behind it and stays below this rung.
18
18
  *
19
+ * A spec.md marked `adoption: partial` does not count toward `documented`.
20
+ * That marker is not a completeness grade — completeness stays unchecked, as
21
+ * above — but a PROVENANCE fact: `loam archive` wrote the file from one
22
+ * feature's slice into a service that existed and was never adopted, so the
23
+ * document says what that feature added and nothing about what the service
24
+ * already did. Counting it made one archive move a service from `partial` to
25
+ * `documented` with no adoption having happened.
26
+ *
19
27
  * `openapi.yaml` is required ONLY where an API is expected. The rung used to
20
28
  * demand it of everything, which permanently pinned every worker, cron and
21
29
  * consumer in the fleet at `partial` — services that are fully documented and
@@ -28,7 +36,7 @@ export function serviceMaturity(v) {
28
36
  const s = v.entry;
29
37
  if (!Object.values(s.has).some(Boolean) && !v.archSpec && s.adrs === 0)
30
38
  return "empty";
31
- if (!(s.has.model && s.has.spec && (!v.apiExpected || s.has.openapi)))
39
+ if (!(s.has.model && s.has.spec && s.adoption === null && (!v.apiExpected || s.has.openapi)))
32
40
  return "partial";
33
41
  if (!s.sources.declared)
34
42
  return "documented";
@@ -40,12 +48,22 @@ export function serviceMaturity(v) {
40
48
  * What stands between this service and `vouched`, named as the thing to go and
41
49
  * do. One rung's worth at a time: telling somebody at `empty` that they also
42
50
  * need a vouch is noise, and the vouch is not even possible yet.
51
+ *
52
+ * At the top rung, the one thing a vouch can still owe: a re-vouch, when a
53
+ * stamped document's words moved after the person read them
54
+ * (`ServiceEntry.contentStale`). The RUNG does not move — the stamp is real —
55
+ * but "nothing missing" over words nobody has read since is the false green
56
+ * this list exists to prevent, and `list --needs-work`, `explore` and the
57
+ * context pack all read their worklist from here.
43
58
  */
44
59
  export function maturityGaps(v) {
45
60
  const s = v.entry;
46
61
  const files = [
47
62
  ...(s.has.model ? [] : ["model.likec4"]),
48
63
  ...(s.has.spec ? [] : ["spec.md"]),
64
+ // Named as the step that discharges it, because the file it is about is
65
+ // already there: what is missing is the adoption, not the document.
66
+ ...(s.has.spec && s.adoption !== null ? [`\`loam adopt --service ${s.id}\` (spec.md is adoption: partial — one feature's slice)`] : []),
49
67
  ...(v.apiExpected && !s.has.openapi ? ["openapi.yaml"] : []),
50
68
  ];
51
69
  if (files.length > 0)
@@ -54,6 +72,8 @@ export function maturityGaps(v) {
54
72
  return ["sources: in the spec.md frontmatter"];
55
73
  if (!(s.status === "verified" && s.sources.stamped))
56
74
  return [`\`loam vouch --service ${s.id}\``];
75
+ if (s.contentStale.length > 0)
76
+ return [`re-vouch (${s.contentStale.join(", ")} changed since the vouch)`];
57
77
  return [];
58
78
  }
59
79
  /** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */
@@ -70,10 +90,12 @@ export function maturityRollup(graded) {
70
90
  * C4 kinds that model people, and the tag marking an element as somebody
71
91
  * else's system. A person is never a service directory, and `#external` is
72
92
  * undocumented on purpose — every census and exemption over the fleet map
73
- * asks both questions.
93
+ * asks both questions, and `INFRA_KINDS` below is the third they now ask
94
+ * beside them.
74
95
  *
75
- * Extracted on the third strike: `commands/validate/checks/vocabulary.ts`,
76
- * `core/gate/partners.ts` and `core/verify/checklist.ts` each carried a copy.
96
+ * Extracted on the third strike: `commands/validate/checks/vocabulary.ts` (a
97
+ * module since deleted), `core/gate/partners.ts` and `core/verify/checklist.ts`
98
+ * each carried a copy.
77
99
  * Their real home is `core/c4/likec4.ts` beside the model they describe, but
78
100
  * that file sits against the 300-line limit and its package holds five files
79
101
  * — so the trio lives here, beside `landscapeEvidence`, which reads the same
@@ -88,6 +110,45 @@ export function maturityRollup(graded) {
88
110
  * in one place anyway — that list IS what the kind-tag check is about.
89
111
  */
90
112
  export const ACTOR_KINDS = new Set(["person", "actor", "user"]);
113
+ /**
114
+ * C4 kinds that model INFRASTRUCTURE the fleet runs rather than a system a team
115
+ * adopts. A person is never a `services/<id>/`, and neither is a datastore: a
116
+ * Postgres, a Kafka topic or an S3 bucket has no spec.md to write, no OpenAPI to
117
+ * publish, no maturity rung to climb and nobody to adopt it.
118
+ *
119
+ * The third census category, beside `ACTOR_KINDS` and `EXTERNAL_TAG`, and it
120
+ * exists because the fleet had only two. A store the fleet OWNS and draws at
121
+ * fleet level was neither a person nor somebody else's system, so it earned
122
+ * `landscape.service-undocumented` ("bind it with `metadata { service }`, or tag
123
+ * it `#external`") — two remedies, neither of them true of it — and
124
+ * `gate.partner-undocumented` on every service that reads it. `examples/docs`
125
+ * and this repo's own suite both tagged an owned Redis `#external` purely to buy
126
+ * that silence, which is a map that lies about ownership to satisfy a check.
127
+ *
128
+ * The LIMITATION is `ACTOR_KINDS`' exactly, and is stated here so the next
129
+ * reader does not have to measure it: the hook is a kind NAME, compared
130
+ * case-insensitively, so a fleet whose specification spells its kind `postgres`,
131
+ * `store` or `datastore` is not covered. That is deliberate rather than
132
+ * unfinished — a tag (`#infrastructure`) can be added later as a SECOND spelling
133
+ * without moving anything, exactly as `#external` sits beside `ACTOR_KINDS`
134
+ * today. An element of one of these kinds that stands for a real
135
+ * `services/<id>/` — a bound `database`, a title naming a directory — is a
136
+ * service and every reader here must say so first; the kind is the fallback,
137
+ * never the override.
138
+ */
139
+ export const INFRA_KINDS = new Set(["database", "queue", "topic", "cache", "bucket"]);
140
+ /**
141
+ * The one element kind read as a datastore, compared case-insensitively — the
142
+ * subject of `landscape.datastore-shared`.
143
+ *
144
+ * It was a bare string declared twice, in `commands/validate/fleet/map/consumers.ts`
145
+ * and `commands/validate/fleet/map/attest.ts`, each with a comment pointing at
146
+ * the other. Two literals free to drift about what a store IS are exactly the
147
+ * failure the trio above was extracted to stop: the peer census and the nested
148
+ * census would have graded different fleets the day one of them learned a second
149
+ * spelling.
150
+ */
151
+ export const DATASTORE_KIND = "database";
91
152
  export const EXTERNAL_TAG = "external";
92
153
  /** Tag marking ubiquitous infrastructure; the scaffolded fleet view excludes it. */
93
154
  export const PLATFORM_TAG = "platform";
@@ -129,6 +190,16 @@ export function landscapeEvidence(req) {
129
190
  const inbound = [];
130
191
  const outbound = [];
131
192
  for (const r of relationships) {
193
+ // An edge whose BOTH endpoints resolve to this service is its own internal
194
+ // wiring (`svc.web -> svc.db`): filed as inbound it made the service its own
195
+ // caller — `← <id> "reads"` in `loam context` (verification 2026-09-04) —
196
+ // and, through `apiExpected` below, told a service nobody calls that it owed
197
+ // an OpenAPI contract. `core/brief/landscape.ts` drops it for the same
198
+ // reason. It still TOUCHES the service, which is why the brief's `touched`
199
+ // and `landscape.service-isolated` read the relationships rather than these
200
+ // two lists.
201
+ if (svcOf(r.source) === id && svcOf(r.target) === id)
202
+ continue;
132
203
  const edge = { op: r.op ?? null, title: r.title ?? null };
133
204
  if (svcOf(r.target) === id)
134
205
  inbound.push({ service: svcOf(r.source), ...edge });
@@ -1 +1 @@
1
- {"version":3,"file":"maturity.js","sourceRoot":"","sources":["../../../src/core/vocabulary/maturity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The adoption-maturity ladder: one monotone word for how far a service's\n * documentation has got, and what stands between it and the next rung.\n *\n * This lived inside `commands/list.ts` while `list` was the only caller. It\n * moved here when `explore` needed the same answer, because the alternative is\n * the failure `core/kernel/ids/service.ts` already records for the service-id grammar: a\n * second, subtly different copy, so two commands grade the same directory\n * differently and nobody can tell which one is lying. The rung is a fleet dial\n * — `list` prints it per service and in a rollup, `explore` prints it for a\n * service somebody is about to build against — and a dial with two readings is\n * not a dial.\n */\nimport type { ServiceEntry } from \"../repo/entries.js\";\n\n/** The ladder, in order. Each rung stands on every rung below it. */\nexport const MATURITY_LADDER = [\"empty\", \"partial\", \"documented\", \"sourced\", \"vouched\"] as const;\nexport type Maturity = (typeof MATURITY_LADDER)[number];\n\n/**\n * A service entry plus the two facts the directory enumeration cannot know on\n * its own. Both change what a rung MEANS, and both are fleet-level: they need\n * the landscape, not `services/<id>/`.\n */\nexport interface MaturityInput {\n entry: ServiceEntry;\n /** `arch.spec.md` — the architecture-obligations axis, absent from ServiceEntry.has. */\n archSpec: boolean;\n /**\n * Does anything in the fleet call an operation on this service? True unless\n * the landscape PROVES otherwise, exactly as `validate`'s no-openapi grace\n * reads it: a missing or unparseable landscape proves nothing.\n */\n apiExpected: boolean;\n}\n\n/**\n * How far a service's documentation has got. Derived from artifact PRESENCE and\n * provenance state only — the data an enumeration already holds — never from\n * what the artifacts say. COMPLETENESS of adopted docs is explicitly on the\n * unchecked list (brief/unchecked.ts): a service with one endpoint documented out of\n * thirty climbs this ladder exactly as fast as a thorough one, which is why no\n * rung is called \"adopted\".\n *\n * empty services/<id>/ exists, no artifact is in it\n * partial some artifacts, but not the required set\n * documented the artifacts the adopt brief marks required are present\n * sourced the living spec declares `sources` — something ties it to code\n * vouched status: verified with a sources_digest behind it — a person\n * stamped it. `verified` with no digest is a claim with nothing\n * behind it and stays below this rung.\n *\n * `openapi.yaml` is required ONLY where an API is expected. The rung used to\n * demand it of everything, which permanently pinned every worker, cron and\n * consumer in the fleet at `partial` — services that are fully documented and\n * vouched for, reported as unfinished forever, leaving the rollup permanently\n * below its true documentation state. The evidence is the same one\n * `validate` uses to keep `service.no-openapi` quiet, so a service cannot be\n * green in one command and unfinished in the other.\n */\nexport function serviceMaturity(v: MaturityInput): Maturity {\n const s = v.entry;\n if (!Object.values(s.has).some(Boolean) && !v.archSpec && s.adrs === 0) return \"empty\";\n if (!(s.has.model && s.has.spec && (!v.apiExpected || s.has.openapi))) return \"partial\";\n if (!s.sources.declared) return \"documented\";\n if (!(s.status === \"verified\" && s.sources.stamped)) return \"sourced\";\n return \"vouched\";\n}\n\n/**\n * What stands between this service and `vouched`, named as the thing to go and\n * do. One rung's worth at a time: telling somebody at `empty` that they also\n * need a vouch is noise, and the vouch is not even possible yet.\n */\nexport function maturityGaps(v: MaturityInput): string[] {\n const s = v.entry;\n const files = [\n ...(s.has.model ? [] : [\"model.likec4\"]),\n ...(s.has.spec ? [] : [\"spec.md\"]),\n ...(v.apiExpected && !s.has.openapi ? [\"openapi.yaml\"] : []),\n ];\n if (files.length > 0) return files;\n if (!s.sources.declared) return [\"sources: in the spec.md frontmatter\"];\n if (!(s.status === \"verified\" && s.sources.stamped)) return [`\\`loam vouch --service ${s.id}\\``];\n return [];\n}\n\n/** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */\nexport function maturityRollup(graded: readonly { maturity: Maturity }[]): Record<Maturity, number> {\n const out = Object.fromEntries(MATURITY_LADDER.map((m) => [m, 0])) as Record<Maturity, number>;\n for (const v of graded) out[v.maturity] += 1;\n return out;\n}\n\n/* ------------------------------------------------------------------ */\n/* What the fleet map proves about one service */\n/* ------------------------------------------------------------------ */\n\n/**\n * C4 kinds that model people, and the tag marking an element as somebody\n * else's system. A person is never a service directory, and `#external` is\n * undocumented on purpose — every census and exemption over the fleet map\n * asks both questions.\n *\n * Extracted on the third strike: `commands/validate/checks/vocabulary.ts`,\n * `core/gate/partners.ts` and `core/verify/checklist.ts` each carried a copy.\n * Their real home is `core/c4/likec4.ts` beside the model they describe, but\n * that file sits against the 300-line limit and its package holds five files\n * — so the trio lives here, beside `landscapeEvidence`, which reads the same\n * map for the same per-service questions.\n *\n * `PLATFORM_TAG` joined them from `commands/validate/checks/fleet-shape.ts`,\n * where it was declared beside its one reader. It moved for the rule above the\n * others came for: `fleet/kind-tags.ts` needs to know which tag names loam\n * grades on, and reaching into fleet-shape.ts for a five-character constant\n * would import a module that reads permissions, capabilities and every\n * service's requirements to get it. The two tags loam gives meaning to belong\n * in one place anyway — that list IS what the kind-tag check is about.\n */\nexport const ACTOR_KINDS = new Set([\"person\", \"actor\", \"user\"]);\nexport const EXTERNAL_TAG = \"external\";\n/** Tag marking ubiquitous infrastructure; the scaffolded fleet view excludes it. */\nexport const PLATFORM_TAG = \"platform\";\n\n/**\n * The tags loam GRADES on, as opposed to the ones it merely carries.\n *\n * A tag in this list changes what loam concludes about a fleet: `#external`\n * exempts an element from the landscape↔services reconciliation entirely, and\n * `#platform` silences the hub warning. Every other tag on an element is the\n * author's own vocabulary and loam does not read it.\n *\n * The list exists because since LikeC4 1.59.0 a tag can be declared on a KIND\n * — `specification { element softwareSystem { #external } }` — and LikeC4\n * applies it to every element of that kind. A kind-wide declaration of a tag on\n * this list switches loam's grading off for the whole fleet at once, which is\n * what `kindTagFindings` refuses. A tag NOT on this list is safe to declare\n * kind-wide, so the check must ask exactly this question and no broader one.\n */\nexport const GRADED_TAGS: readonly string[] = [EXTERNAL_TAG, PLATFORM_TAG];\n\n/** One end of a call the fleet map draws, as every per-service view spells it. */\nexport interface LandscapeEdge {\n service: string;\n op: string | null;\n title: string | null;\n}\n\n/**\n * The slice of a parsed relationship the partition reads. Structural on\n * purpose — the smallest parameter type the derivation needs — so this module\n * stays a leaf: `core/c4/likec4.ts`'s `Rel` satisfies it without an import\n * edge from the vocabulary package into the parser.\n */\nexport interface EdgeSource {\n source: string;\n target: string;\n op?: string;\n title?: string;\n}\n\nexport interface LandscapeEvidenceRequest {\n /** The service being asked about. */\n id: string;\n /** Whether the landscape parsed — what makes its silence evidence rather than absence. */\n parses: boolean;\n /** The parsed relationships; empty when the landscape is absent or unparseable. */\n relationships: readonly EdgeSource[];\n /** Every element id in the map, for the `modelled` probe. */\n elementIds: readonly string[];\n /** The shared resolver (`core/c4/resolve/service.ts` `serviceResolver`), widened to plain strings. */\n svcOf: (id: string) => string;\n}\n\nexport interface LandscapeEvidence {\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n /** Whether anything in the fleet map resolves to this service. */\n modelled: boolean;\n /** The `apiExpected` input the ladder above consumes. */\n apiExpected: boolean;\n}\n\n/**\n * The fleet map's evidence about one service: the edges into and out of it,\n * whether the map draws it at all, and whether an API is expected of it.\n *\n * One spelling, because it had grown three. `explore`'s describe and the\n * context pack's living slice partitioned the same relationships with the\n * same positive-evidence rule line for line — an inbound edge carrying an\n * operation proves somebody calls the service; a landscape that is absent or\n * does not parse proves NOTHING about who calls it, so the contract is still\n * owed — and a rule spelled twice is how the same service grades `partial` in\n * one command and `documented` in another. `list`'s serviceViews\n * (`commands/list/views.ts`) derives the same rule fleet-wide in ONE pass —\n * a single `called` set for every service, not a partition per service —\n * so that spelling deliberately stays there, cross-referenced from both\n * sides: calling this per service inside a fleet loop would re-walk the\n * whole relationship list once per service.\n */\nexport function landscapeEvidence(req: LandscapeEvidenceRequest): LandscapeEvidence {\n const { id, parses, relationships, svcOf } = req;\n const inbound: LandscapeEdge[] = [];\n const outbound: LandscapeEdge[] = [];\n for (const r of relationships) {\n const edge = { op: r.op ?? null, title: r.title ?? null };\n if (svcOf(r.target) === id) inbound.push({ service: svcOf(r.source), ...edge });\n else if (svcOf(r.source) === id) outbound.push({ service: svcOf(r.target), ...edge });\n }\n return {\n inbound,\n outbound,\n modelled: req.elementIds.some((e) => svcOf(e) === id),\n apiExpected: !parses || inbound.some((e) => e.op !== null),\n };\n}\n"]}
1
+ {"version":3,"file":"maturity.js","sourceRoot":"","sources":["../../../src/core/vocabulary/maturity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The adoption-maturity ladder: one monotone word for how far a service's\n * documentation has got, and what stands between it and the next rung.\n *\n * This lived inside `commands/list.ts` while `list` was the only caller. It\n * moved here when `explore` needed the same answer, because the alternative is\n * the failure `core/kernel/ids/service.ts` already records for the service-id grammar: a\n * second, subtly different copy, so two commands grade the same directory\n * differently and nobody can tell which one is lying. The rung is a fleet dial\n * — `list` prints it per service and in a rollup, `explore` prints it for a\n * service somebody is about to build against — and a dial with two readings is\n * not a dial.\n */\nimport type { ServiceEntry } from \"../repo/entries.js\";\n\n/** The ladder, in order. Each rung stands on every rung below it. */\nexport const MATURITY_LADDER = [\"empty\", \"partial\", \"documented\", \"sourced\", \"vouched\"] as const;\nexport type Maturity = (typeof MATURITY_LADDER)[number];\n\n/**\n * A service entry plus the two facts the directory enumeration cannot know on\n * its own. Both change what a rung MEANS, and both are fleet-level: they need\n * the landscape, not `services/<id>/`.\n */\nexport interface MaturityInput {\n entry: ServiceEntry;\n /** `arch.spec.md` — the architecture-obligations axis, absent from ServiceEntry.has. */\n archSpec: boolean;\n /**\n * Does anything in the fleet call an operation on this service? True unless\n * the landscape PROVES otherwise, exactly as `validate`'s no-openapi grace\n * reads it: a missing or unparseable landscape proves nothing.\n */\n apiExpected: boolean;\n}\n\n/**\n * How far a service's documentation has got. Derived from artifact PRESENCE and\n * provenance state only — the data an enumeration already holds — never from\n * what the artifacts say. COMPLETENESS of adopted docs is explicitly on the\n * unchecked list (brief/unchecked.ts): a service with one endpoint documented out of\n * thirty climbs this ladder exactly as fast as a thorough one, which is why no\n * rung is called \"adopted\".\n *\n * empty services/<id>/ exists, no artifact is in it\n * partial some artifacts, but not the required set\n * documented the artifacts the adopt brief marks required are present\n * sourced the living spec declares `sources` — something ties it to code\n * vouched status: verified with a sources_digest behind it — a person\n * stamped it. `verified` with no digest is a claim with nothing\n * behind it and stays below this rung.\n *\n * A spec.md marked `adoption: partial` does not count toward `documented`.\n * That marker is not a completeness grade — completeness stays unchecked, as\n * above — but a PROVENANCE fact: `loam archive` wrote the file from one\n * feature's slice into a service that existed and was never adopted, so the\n * document says what that feature added and nothing about what the service\n * already did. Counting it made one archive move a service from `partial` to\n * `documented` with no adoption having happened.\n *\n * `openapi.yaml` is required ONLY where an API is expected. The rung used to\n * demand it of everything, which permanently pinned every worker, cron and\n * consumer in the fleet at `partial` — services that are fully documented and\n * vouched for, reported as unfinished forever, leaving the rollup permanently\n * below its true documentation state. The evidence is the same one\n * `validate` uses to keep `service.no-openapi` quiet, so a service cannot be\n * green in one command and unfinished in the other.\n */\nexport function serviceMaturity(v: MaturityInput): Maturity {\n const s = v.entry;\n if (!Object.values(s.has).some(Boolean) && !v.archSpec && s.adrs === 0) return \"empty\";\n if (!(s.has.model && s.has.spec && s.adoption === null && (!v.apiExpected || s.has.openapi))) return \"partial\";\n if (!s.sources.declared) return \"documented\";\n if (!(s.status === \"verified\" && s.sources.stamped)) return \"sourced\";\n return \"vouched\";\n}\n\n/**\n * What stands between this service and `vouched`, named as the thing to go and\n * do. One rung's worth at a time: telling somebody at `empty` that they also\n * need a vouch is noise, and the vouch is not even possible yet.\n *\n * At the top rung, the one thing a vouch can still owe: a re-vouch, when a\n * stamped document's words moved after the person read them\n * (`ServiceEntry.contentStale`). The RUNG does not move — the stamp is real —\n * but \"nothing missing\" over words nobody has read since is the false green\n * this list exists to prevent, and `list --needs-work`, `explore` and the\n * context pack all read their worklist from here.\n */\nexport function maturityGaps(v: MaturityInput): string[] {\n const s = v.entry;\n const files = [\n ...(s.has.model ? [] : [\"model.likec4\"]),\n ...(s.has.spec ? [] : [\"spec.md\"]),\n // Named as the step that discharges it, because the file it is about is\n // already there: what is missing is the adoption, not the document.\n ...(s.has.spec && s.adoption !== null ? [`\\`loam adopt --service ${s.id}\\` (spec.md is adoption: partial — one feature's slice)`] : []),\n ...(v.apiExpected && !s.has.openapi ? [\"openapi.yaml\"] : []),\n ];\n if (files.length > 0) return files;\n if (!s.sources.declared) return [\"sources: in the spec.md frontmatter\"];\n if (!(s.status === \"verified\" && s.sources.stamped)) return [`\\`loam vouch --service ${s.id}\\``];\n if (s.contentStale.length > 0) return [`re-vouch (${s.contentStale.join(\", \")} changed since the vouch)`];\n return [];\n}\n\n/** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */\nexport function maturityRollup(graded: readonly { maturity: Maturity }[]): Record<Maturity, number> {\n const out = Object.fromEntries(MATURITY_LADDER.map((m) => [m, 0])) as Record<Maturity, number>;\n for (const v of graded) out[v.maturity] += 1;\n return out;\n}\n\n/* ------------------------------------------------------------------ */\n/* What the fleet map proves about one service */\n/* ------------------------------------------------------------------ */\n\n/**\n * C4 kinds that model people, and the tag marking an element as somebody\n * else's system. A person is never a service directory, and `#external` is\n * undocumented on purpose — every census and exemption over the fleet map\n * asks both questions, and `INFRA_KINDS` below is the third they now ask\n * beside them.\n *\n * Extracted on the third strike: `commands/validate/checks/vocabulary.ts` (a\n * module since deleted), `core/gate/partners.ts` and `core/verify/checklist.ts`\n * each carried a copy.\n * Their real home is `core/c4/likec4.ts` beside the model they describe, but\n * that file sits against the 300-line limit and its package holds five files\n * — so the trio lives here, beside `landscapeEvidence`, which reads the same\n * map for the same per-service questions.\n *\n * `PLATFORM_TAG` joined them from `commands/validate/checks/fleet-shape.ts`,\n * where it was declared beside its one reader. It moved for the rule above the\n * others came for: `fleet/kind-tags.ts` needs to know which tag names loam\n * grades on, and reaching into fleet-shape.ts for a five-character constant\n * would import a module that reads permissions, capabilities and every\n * service's requirements to get it. The two tags loam gives meaning to belong\n * in one place anyway — that list IS what the kind-tag check is about.\n */\nexport const ACTOR_KINDS = new Set([\"person\", \"actor\", \"user\"]);\n\n/**\n * C4 kinds that model INFRASTRUCTURE the fleet runs rather than a system a team\n * adopts. A person is never a `services/<id>/`, and neither is a datastore: a\n * Postgres, a Kafka topic or an S3 bucket has no spec.md to write, no OpenAPI to\n * publish, no maturity rung to climb and nobody to adopt it.\n *\n * The third census category, beside `ACTOR_KINDS` and `EXTERNAL_TAG`, and it\n * exists because the fleet had only two. A store the fleet OWNS and draws at\n * fleet level was neither a person nor somebody else's system, so it earned\n * `landscape.service-undocumented` (\"bind it with `metadata { service }`, or tag\n * it `#external`\") — two remedies, neither of them true of it — and\n * `gate.partner-undocumented` on every service that reads it. `examples/docs`\n * and this repo's own suite both tagged an owned Redis `#external` purely to buy\n * that silence, which is a map that lies about ownership to satisfy a check.\n *\n * The LIMITATION is `ACTOR_KINDS`' exactly, and is stated here so the next\n * reader does not have to measure it: the hook is a kind NAME, compared\n * case-insensitively, so a fleet whose specification spells its kind `postgres`,\n * `store` or `datastore` is not covered. That is deliberate rather than\n * unfinished — a tag (`#infrastructure`) can be added later as a SECOND spelling\n * without moving anything, exactly as `#external` sits beside `ACTOR_KINDS`\n * today. An element of one of these kinds that stands for a real\n * `services/<id>/` — a bound `database`, a title naming a directory — is a\n * service and every reader here must say so first; the kind is the fallback,\n * never the override.\n */\nexport const INFRA_KINDS = new Set([\"database\", \"queue\", \"topic\", \"cache\", \"bucket\"]);\n\n/**\n * The one element kind read as a datastore, compared case-insensitively — the\n * subject of `landscape.datastore-shared`.\n *\n * It was a bare string declared twice, in `commands/validate/fleet/map/consumers.ts`\n * and `commands/validate/fleet/map/attest.ts`, each with a comment pointing at\n * the other. Two literals free to drift about what a store IS are exactly the\n * failure the trio above was extracted to stop: the peer census and the nested\n * census would have graded different fleets the day one of them learned a second\n * spelling.\n */\nexport const DATASTORE_KIND = \"database\";\nexport const EXTERNAL_TAG = \"external\";\n/** Tag marking ubiquitous infrastructure; the scaffolded fleet view excludes it. */\nexport const PLATFORM_TAG = \"platform\";\n\n/**\n * The tags loam GRADES on, as opposed to the ones it merely carries.\n *\n * A tag in this list changes what loam concludes about a fleet: `#external`\n * exempts an element from the landscape↔services reconciliation entirely, and\n * `#platform` silences the hub warning. Every other tag on an element is the\n * author's own vocabulary and loam does not read it.\n *\n * The list exists because since LikeC4 1.59.0 a tag can be declared on a KIND\n * — `specification { element softwareSystem { #external } }` — and LikeC4\n * applies it to every element of that kind. A kind-wide declaration of a tag on\n * this list switches loam's grading off for the whole fleet at once, which is\n * what `kindTagFindings` refuses. A tag NOT on this list is safe to declare\n * kind-wide, so the check must ask exactly this question and no broader one.\n */\nexport const GRADED_TAGS: readonly string[] = [EXTERNAL_TAG, PLATFORM_TAG];\n\n/** One end of a call the fleet map draws, as every per-service view spells it. */\nexport interface LandscapeEdge {\n service: string;\n op: string | null;\n title: string | null;\n}\n\n/**\n * The slice of a parsed relationship the partition reads. Structural on\n * purpose — the smallest parameter type the derivation needs — so this module\n * stays a leaf: `core/c4/likec4.ts`'s `Rel` satisfies it without an import\n * edge from the vocabulary package into the parser.\n */\nexport interface EdgeSource {\n source: string;\n target: string;\n op?: string;\n title?: string;\n}\n\nexport interface LandscapeEvidenceRequest {\n /** The service being asked about. */\n id: string;\n /** Whether the landscape parsed — what makes its silence evidence rather than absence. */\n parses: boolean;\n /** The parsed relationships; empty when the landscape is absent or unparseable. */\n relationships: readonly EdgeSource[];\n /** Every element id in the map, for the `modelled` probe. */\n elementIds: readonly string[];\n /** The shared resolver (`core/c4/resolve/service.ts` `serviceResolver`), widened to plain strings. */\n svcOf: (id: string) => string;\n}\n\nexport interface LandscapeEvidence {\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n /** Whether anything in the fleet map resolves to this service. */\n modelled: boolean;\n /** The `apiExpected` input the ladder above consumes. */\n apiExpected: boolean;\n}\n\n/**\n * The fleet map's evidence about one service: the edges into and out of it,\n * whether the map draws it at all, and whether an API is expected of it.\n *\n * One spelling, because it had grown three. `explore`'s describe and the\n * context pack's living slice partitioned the same relationships with the\n * same positive-evidence rule line for line — an inbound edge carrying an\n * operation proves somebody calls the service; a landscape that is absent or\n * does not parse proves NOTHING about who calls it, so the contract is still\n * owed — and a rule spelled twice is how the same service grades `partial` in\n * one command and `documented` in another. `list`'s serviceViews\n * (`commands/list/views.ts`) derives the same rule fleet-wide in ONE pass —\n * a single `called` set for every service, not a partition per service —\n * so that spelling deliberately stays there, cross-referenced from both\n * sides: calling this per service inside a fleet loop would re-walk the\n * whole relationship list once per service.\n */\nexport function landscapeEvidence(req: LandscapeEvidenceRequest): LandscapeEvidence {\n const { id, parses, relationships, svcOf } = req;\n const inbound: LandscapeEdge[] = [];\n const outbound: LandscapeEdge[] = [];\n for (const r of relationships) {\n // An edge whose BOTH endpoints resolve to this service is its own internal\n // wiring (`svc.web -> svc.db`): filed as inbound it made the service its own\n // caller — `← <id> \"reads\"` in `loam context` (verification 2026-09-04) —\n // and, through `apiExpected` below, told a service nobody calls that it owed\n // an OpenAPI contract. `core/brief/landscape.ts` drops it for the same\n // reason. It still TOUCHES the service, which is why the brief's `touched`\n // and `landscape.service-isolated` read the relationships rather than these\n // two lists.\n if (svcOf(r.source) === id && svcOf(r.target) === id) continue;\n const edge = { op: r.op ?? null, title: r.title ?? null };\n if (svcOf(r.target) === id) inbound.push({ service: svcOf(r.source), ...edge });\n else if (svcOf(r.source) === id) outbound.push({ service: svcOf(r.target), ...edge });\n }\n return {\n inbound,\n outbound,\n modelled: req.elementIds.some((e) => svcOf(e) === id),\n apiExpected: !parses || inbound.some((e) => e.op !== null),\n };\n}\n"]}
@@ -13,9 +13,24 @@
13
13
  * everything else fall to ⚠ — and a reader who learns the marks from one
14
14
  * command's output has to be able to read them in the next. It lives beside
15
15
  * `Severity` because it is a fact about the union, not about any one renderer;
16
- * `TextHint.marker` above already describes it as "the ✓/⚠/✗ marker".
16
+ * `TextHint.marker` below is the switch that prints it, or a different mark.
17
17
  */
18
18
  export const SEVERITY_MARK = { ok: "✓", warn: "⚠", error: "✗" };
19
+ /**
20
+ * The glyph for an `ok` finding whose check did NOT run here — printed in
21
+ * place of ✓, never in place of ⚠ or ✗.
22
+ *
23
+ * Not a fourth severity, on purpose: `sources.unverifiable-from-here` is `ok`
24
+ * because nothing is wrong with the docs, and a severity of its own would move
25
+ * the footer's counts, `--strict` and the frozen `--json` vocabulary for what
26
+ * is only a question of how the line reads. But ✓ is the one mark that says
27
+ * "checked, fine", and a person reading a docs-repo run saw a column of them
28
+ * beside the sentence "nothing here can resolve them" — a pass for a check
29
+ * that was skipped. Defined here beside SEVERITY_MARK for the same reason that
30
+ * table is: a reader who learns it from one command must be able to read it in
31
+ * the next.
32
+ */
33
+ export const NOT_CHECKED_MARK = "○";
19
34
  /** A target is valid when nothing in it is an error. Warnings never gate. */
20
35
  export function targetValid(target) {
21
36
  return !target.findings.some((f) => f.severity === "error");
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/core/vocabulary/report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The validation report — what `loam validate` produces before anything is\n * printed.\n *\n * Checks build findings; renderers turn them into a human view or into the JSON\n * contract. Keeping the two apart is what makes `--all` and `--json` possible\n * without a second implementation of every check, and it is why a finding\n * carries a stable `code`: prose is for people, codes are for callers.\n */\n\nexport type Severity = \"ok\" | \"warn\" | \"error\";\n\n/**\n * The glyph each severity is printed with. Five sites had written it out\n * themselves — four as a two-armed ternary that spelled `error` and let\n * everything else fall to ⚠ — and a reader who learns the marks from one\n * command's output has to be able to read them in the next. It lives beside\n * `Severity` because it is a fact about the union, not about any one renderer;\n * `TextHint.marker` above already describes it as \"the ✓/⚠/✗ marker\".\n */\nexport const SEVERITY_MARK: Record<Severity, string> = { ok: \"✓\", warn: \"⚠\", error: \"✗\" };\n\n/** How the text renderer should lay a finding out. The JSON contract ignores this. */\nexport interface TextHint {\n /** Leading spaces. Default 0. */\n indent?: number;\n /** Print the ✓/⚠/✗ marker. Default true. */\n marker?: boolean;\n /** Block header printed once, before the first finding that carries it. */\n header?: string;\n /** Prefix for each detail line (e.g. \"- \" for a list of names). */\n detailPrefix?: string;\n}\n\nexport interface FindingLocation {\n /** Portable path relative to the docs repo whenever the finding is about one. */\n path: string;\n /** `primary` is exact; `scope` is the smallest directory the check can prove. */\n role: \"primary\" | \"related\" | \"scope\";\n line?: number;\n column?: number;\n /** A stable structural address when a line is not available. */\n pointer?: string;\n}\n\nexport interface Finding {\n severity: Severity;\n /** Stable machine identifier — prose may change, this may not. */\n code: string;\n message: string;\n /**\n * What the finding is about, when that is narrower than the target: the\n * service a feature's per-service check ran on, say. Without it a caller\n * would have to read the service name back out of the message.\n */\n subject?: string;\n details?: string[];\n locations?: FindingLocation[];\n /**\n * Coherence findings only: whether `loam archive` refuses on this issue\n * without `--approve` (issue.ts explains why severity alone cannot say).\n * Absent on findings that never face the archive gate.\n */\n gates?: boolean;\n text?: TextHint;\n}\n\nexport interface TargetReport {\n /**\n * What was validated. `landscape` is the fleet itself — the checks that need\n * every service and every element in view at once, so they belong to no single\n * service and only run under `--all`.\n */\n kind: \"service\" | \"feature\" | \"landscape\";\n id: string;\n /** The exact repo-relative scope this target was read from. */\n path?: string;\n findings: Finding[];\n}\n\n/** A target is valid when nothing in it is an error. Warnings never gate. */\nexport function targetValid(target: TargetReport): boolean {\n return !target.findings.some((f) => f.severity === \"error\");\n}\n\nexport function reportValid(targets: TargetReport[]): boolean {\n return targets.every(targetValid);\n}\n\nexport function countSeverity(targets: TargetReport[], severity: Severity): number {\n return targets.reduce((n, t) => n + t.findings.filter((f) => f.severity === severity).length, 0);\n}\n\n/**\n * DISTINCT subjects carrying a finding with `code`, across the whole report.\n *\n * By SUBJECT, not by finding, and that is the rule every rollup derived from\n * findings must share: one service can raise the same code from two documents\n * — each document has its own list and its own answer — but it is one service,\n * and a count of findings sends the reader looking for services that do not\n * exist. A finding that names no subject is about its target, so the target id\n * is the fallback key.\n */\nexport function subjectsWith(targets: TargetReport[], code: string): number {\n return new Set(\n targets.flatMap((t) =>\n t.findings.filter((f) => f.code === code).map((f) => f.subject ?? t.id),\n ),\n ).size;\n}\n\nexport function findingJson(f: Finding, fallback?: FindingLocation): Record<string, unknown> {\n return {\n severity: f.severity,\n code: f.code,\n ...(f.subject === undefined ? {} : { subject: f.subject }),\n ...(f.gates === undefined ? {} : { gates: f.gates }),\n message: f.message,\n details: f.details ?? [],\n locations: f.locations ?? (fallback === undefined ? [] : [fallback]),\n };\n}\n\nfunction targetScope(t: TargetReport): FindingLocation {\n const path =\n t.path ??\n (t.kind === \"service\"\n ? `services/${t.id}`\n : t.kind === \"feature\"\n ? \"features\"\n : \"architecture/landscape.likec4\");\n return {\n path,\n role: t.kind === \"landscape\" ? \"primary\" : \"scope\",\n ...(t.kind === \"feature\" && t.path === undefined ? { pointer: t.id } : {}),\n };\n}\n\nexport function targetJson(t: TargetReport): Record<string, unknown> {\n return {\n kind: t.kind,\n id: t.id,\n ...(t.path === undefined ? {} : { path: t.path }),\n valid: targetValid(t),\n findings: t.findings.map((finding) => findingJson(finding, targetScope(t))),\n };\n}\n"]}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/core/vocabulary/report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The validation report — what `loam validate` produces before anything is\n * printed.\n *\n * Checks build findings; renderers turn them into a human view or into the JSON\n * contract. Keeping the two apart is what makes `--all` and `--json` possible\n * without a second implementation of every check, and it is why a finding\n * carries a stable `code`: prose is for people, codes are for callers.\n */\n\nexport type Severity = \"ok\" | \"warn\" | \"error\";\n\n/**\n * The glyph each severity is printed with. Five sites had written it out\n * themselves — four as a two-armed ternary that spelled `error` and let\n * everything else fall to ⚠ — and a reader who learns the marks from one\n * command's output has to be able to read them in the next. It lives beside\n * `Severity` because it is a fact about the union, not about any one renderer;\n * `TextHint.marker` below is the switch that prints it, or a different mark.\n */\nexport const SEVERITY_MARK: Record<Severity, string> = { ok: \"✓\", warn: \"⚠\", error: \"✗\" };\n\n/**\n * The glyph for an `ok` finding whose check did NOT run here — printed in\n * place of ✓, never in place of ⚠ or ✗.\n *\n * Not a fourth severity, on purpose: `sources.unverifiable-from-here` is `ok`\n * because nothing is wrong with the docs, and a severity of its own would move\n * the footer's counts, `--strict` and the frozen `--json` vocabulary for what\n * is only a question of how the line reads. But ✓ is the one mark that says\n * \"checked, fine\", and a person reading a docs-repo run saw a column of them\n * beside the sentence \"nothing here can resolve them\" — a pass for a check\n * that was skipped. Defined here beside SEVERITY_MARK for the same reason that\n * table is: a reader who learns it from one command must be able to read it in\n * the next.\n */\nexport const NOT_CHECKED_MARK = \"○\";\n\n/** How the text renderer should lay a finding out. The JSON contract ignores this. */\nexport interface TextHint {\n /** Leading spaces. Default 0. */\n indent?: number;\n /**\n * Which marker leads the line. Absent prints the severity's ✓/⚠/✗.\n *\n * `false` prints none, and only a line that is genuinely a sub-header may\n * ask for it — `coherence.ok`, whose message carries its own ✓ under the\n * feature it belongs to. An error or a warning that stands alone with no\n * marker reads as prose between findings: `service.no-model` printed that\n * way, and a run that exited 1 showed no ✗ anywhere.\n *\n * `\"not-checked\"` prints NOT_CHECKED_MARK in place of ✓ — see there.\n */\n marker?: false | \"not-checked\";\n /** Block header printed once, before the first finding that carries it. */\n header?: string;\n /** Prefix for each detail line (e.g. \"- \" for a list of names). */\n detailPrefix?: string;\n /**\n * Print this finding before every target's block on a fleet run, rather\n * than inside its own target's. For a finding that says nothing below it\n * can be trusted (`docs.commit-interrupted`): it has to be attached to SOME\n * target, and the fleet report orders targets by kind, so without the hint\n * it would print wherever its host target happened to land.\n */\n lead?: true;\n}\n\nexport interface FindingLocation {\n /** Portable path relative to the docs repo whenever the finding is about one. */\n path: string;\n /** `primary` is exact; `scope` is the smallest directory the check can prove. */\n role: \"primary\" | \"related\" | \"scope\";\n line?: number;\n column?: number;\n /** A stable structural address when a line is not available. */\n pointer?: string;\n}\n\nexport interface Finding {\n severity: Severity;\n /** Stable machine identifier — prose may change, this may not. */\n code: string;\n message: string;\n /**\n * What the finding is about, when that is narrower than the target: the\n * service a feature's per-service check ran on, say. Without it a caller\n * would have to read the service name back out of the message.\n */\n subject?: string;\n details?: string[];\n locations?: FindingLocation[];\n /**\n * Coherence findings only: whether `loam archive` refuses on this issue\n * without `--approve` (issue.ts explains why severity alone cannot say).\n * Absent on findings that never face the archive gate.\n */\n gates?: boolean;\n text?: TextHint;\n}\n\nexport interface TargetReport {\n /**\n * What was validated. `landscape` is the fleet itself — the checks that need\n * every service and every element in view at once, so they belong to no single\n * service and only run under `--all`.\n */\n kind: \"service\" | \"feature\" | \"landscape\";\n id: string;\n /** The exact repo-relative scope this target was read from. */\n path?: string;\n findings: Finding[];\n}\n\n/** A target is valid when nothing in it is an error. Warnings never gate. */\nexport function targetValid(target: TargetReport): boolean {\n return !target.findings.some((f) => f.severity === \"error\");\n}\n\nexport function reportValid(targets: TargetReport[]): boolean {\n return targets.every(targetValid);\n}\n\nexport function countSeverity(targets: TargetReport[], severity: Severity): number {\n return targets.reduce((n, t) => n + t.findings.filter((f) => f.severity === severity).length, 0);\n}\n\n/**\n * DISTINCT subjects carrying a finding with `code`, across the whole report.\n *\n * By SUBJECT, not by finding, and that is the rule every rollup derived from\n * findings must share: one service can raise the same code from two documents\n * — each document has its own list and its own answer — but it is one service,\n * and a count of findings sends the reader looking for services that do not\n * exist. A finding that names no subject is about its target, so the target id\n * is the fallback key.\n */\nexport function subjectsWith(targets: TargetReport[], code: string): number {\n return new Set(\n targets.flatMap((t) =>\n t.findings.filter((f) => f.code === code).map((f) => f.subject ?? t.id),\n ),\n ).size;\n}\n\nexport function findingJson(f: Finding, fallback?: FindingLocation): Record<string, unknown> {\n return {\n severity: f.severity,\n code: f.code,\n ...(f.subject === undefined ? {} : { subject: f.subject }),\n ...(f.gates === undefined ? {} : { gates: f.gates }),\n message: f.message,\n details: f.details ?? [],\n locations: f.locations ?? (fallback === undefined ? [] : [fallback]),\n };\n}\n\nfunction targetScope(t: TargetReport): FindingLocation {\n const path =\n t.path ??\n (t.kind === \"service\"\n ? `services/${t.id}`\n : t.kind === \"feature\"\n ? \"features\"\n : \"architecture/landscape.likec4\");\n return {\n path,\n role: t.kind === \"landscape\" ? \"primary\" : \"scope\",\n ...(t.kind === \"feature\" && t.path === undefined ? { pointer: t.id } : {}),\n };\n}\n\nexport function targetJson(t: TargetReport): Record<string, unknown> {\n return {\n kind: t.kind,\n id: t.id,\n ...(t.path === undefined ? {} : { path: t.path }),\n valid: targetValid(t),\n findings: t.findings.map((finding) => findingJson(finding, targetScope(t))),\n };\n}\n"]}