@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
@@ -13,51 +13,76 @@
13
13
  * letting it read as "no partners".
14
14
  */
15
15
  import { existsSync } from "node:fs";
16
- import {} from "../c4/likec4.js";
16
+ import { errorText } from "../c4/likec4.js";
17
17
  import { serviceResolver } from "../c4/resolve/service.js";
18
- import { ACTOR_KINDS, EXTERNAL_TAG, landscapeEvidence, maturityGaps, serviceMaturity, MATURITY_LADDER, } from "../vocabulary/maturity.js";
18
+ import { ACTOR_KINDS, EXTERNAL_TAG, INFRA_KINDS, landscapeEvidence, maturityGaps, serviceMaturity, MATURITY_LADDER, } from "../vocabulary/maturity.js";
19
19
  import { compareIds } from "../repo/entries.js";
20
20
  import { landscapePath, servicePathsAt } from "../repo/paths.js";
21
21
  import { enumeratedServices } from "../repo/service-target.js";
22
+ import { ancestorIds } from "../kernel/ids/fqn/ancestors.js";
22
23
  /** Is this rung at or above `documented` — the fixed threshold check 1 grades against? */
23
24
  export function atLeastDocumented(m) {
24
25
  return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf("documented");
25
26
  }
26
- /** The id itself, then each dotted ancestor — the same walk `serviceResolver` resolves through. */
27
- function selfAndAncestors(id) {
28
- const out = [id];
29
- for (let dot = id.lastIndexOf("."); dot !== -1; dot = id.lastIndexOf(".", dot - 1)) {
30
- out.push(id.slice(0, dot));
31
- }
32
- return out;
33
- }
34
27
  /**
35
- * What the map says about one edge END: does it stand for a person, and is it
36
- * tagged `#external`? Walked up the ancestor chain because both facts are
37
- * usually written on the containing system while the edge is drawn from a
38
- * nested element — but the walk STOPS at the first element that stands for a
39
- * service (an explicit binding or a title naming a real directory,
40
- * `serviceResolver`'s own first two rungs, checked after that element's own
41
- * facts are read). What sits above the service boundary is a grouping, and a
42
- * grouping's `#external` is the grouping's claim, not the service's: without
43
- * the stop, an adopted service drawn inside an `#external` zone was exempted
44
- * from `gate.partner-undocumented` and its real rung hidden.
28
+ * What the map says about one edge END: does it stand for a person, is it
29
+ * INFRASTRUCTURE, and is it tagged `#external`? Walked up the ancestor chain
30
+ * because all three facts are usually written on the containing system while
31
+ * the edge is drawn from a nested element — but the walk STOPS at the first
32
+ * element that stands for a service (an explicit binding or a title naming a
33
+ * real directory, `serviceResolver`'s own first two rungs, checked after that
34
+ * element's own facts are read). What sits above the service boundary is a
35
+ * grouping, and a grouping's `#external` is the grouping's claim, not the
36
+ * service's: without the stop, an adopted service drawn inside an `#external`
37
+ * zone was exempted from `gate.partner-undocumented` and its real rung hidden.
38
+ *
39
+ * `infra` is read only off an element that stands for NO service AND sits
40
+ * inside none, which is the asymmetry the `standsFor` local exists for. That is
41
+ * `commands/validate/fleet/census.ts` in two steps and in its order:
42
+ * `serviceLevelElements` drops everything drawn inside a service BEFORE
43
+ * `drawnSystems` ever applies the kind test, so a service's own interior never
44
+ * reaches it. Both halves are load-bearing. A `database` bound with
45
+ * `metadata { service '<id>' }` is an adopted service that happens to be drawn
46
+ * as a store — its side of the join IS recorded, and dropping it would hide a
47
+ * real partner's rung. And a store drawn INSIDE its owner — `orderService.db`,
48
+ * the ordinary way a team draws a private datastore — is that service's
49
+ * interior: reading the kind off it convicted the WHOLE endpoint as
50
+ * infrastructure, and `partnerScan` then dropped order-service from the partner
51
+ * set, so a service this gate exists to grade vanished because of how somebody
52
+ * drew its storage. The kind is the fallback for an element nothing else
53
+ * answers for, never the override.
45
54
  */
46
55
  function endFacts(byId, known, id) {
47
56
  let actor = false;
57
+ let infra = false;
48
58
  let external = false;
49
- for (const candidate of selfAndAncestors(id)) {
59
+ // The id itself, then each dotted ancestor — the same walk `serviceResolver`
60
+ // resolves through, out of the one module that spells it
61
+ // (`core/kernel/ids/fqn/ancestors.ts`).
62
+ for (const candidate of ancestorIds(id)) {
50
63
  const e = byId.get(candidate);
51
64
  if (e === undefined)
52
65
  continue;
66
+ const standsFor = e.service !== undefined || known.has(e.title);
53
67
  if (ACTOR_KINDS.has(e.kind.toLowerCase()))
54
68
  actor = true;
69
+ if (!standsFor && INFRA_KINDS.has(e.kind.toLowerCase()))
70
+ infra = true;
55
71
  if (e.tags.includes(EXTERNAL_TAG))
56
72
  external = true;
57
- if (e.service !== undefined || known.has(e.title))
73
+ if (standsFor) {
74
+ // Everything the walk has visited so far was drawn INSIDE this service,
75
+ // so nothing visited so far was service-LEVEL and no kind read off it
76
+ // answers for a service — `serviceLevelElements` filtering ahead of the
77
+ // kind test, spelled as a walk. `actor` and `external` are deliberately
78
+ // left standing: a person or an `#external` tag written on a container is
79
+ // still a claim about this endpoint, while the store shape is a claim
80
+ // about the owner's interior.
81
+ infra = false;
58
82
  break;
83
+ }
59
84
  }
60
- return { actor, external };
85
+ return { actor, infra, external };
61
86
  }
62
87
  /** How one edge joins the two ends, in the words the finding and the payload both use. */
63
88
  function viaOf(r) {
@@ -91,9 +116,11 @@ export async function partnerScan(req) {
91
116
  if (doc.errors.length > 0) {
92
117
  landscape = "invalid";
93
118
  const first = doc.errors[0];
119
+ // Through `errorText` — the raw message had no line, and a dropped
120
+ // quote's is a parser token list of thousands of lines.
94
121
  landscapeProblem =
95
122
  `${doc.errors.length} parse error(s)` +
96
- (first === undefined ? "" : ` — first: ${first.message}`);
123
+ (first === undefined ? "" : ` — first: ${errorText(first)}`);
97
124
  }
98
125
  else {
99
126
  landscape = "read";
@@ -162,14 +189,28 @@ export async function partnerScan(req) {
162
189
  continue;
163
190
  if (target === service) {
164
191
  const facts = endFacts(byId, known, r.source);
165
- // A person is never a deploy participant: the actor exemption every
166
- // landscape census applies (drawnSystems, commands/validate/fleet/census.ts).
167
- if (!facts.actor)
192
+ // A person is never a deploy participant, and neither is the fleet's own
193
+ // infrastructure: the two kind exemptions every landscape census applies
194
+ // (drawnSystems, commands/validate/fleet/census.ts), spelled here off the
195
+ // same two sets so a gate and a validate run cannot disagree about what a
196
+ // partner IS.
197
+ //
198
+ // The infra half is the newer one, and it had a measured cost. A
199
+ // `database` the fleet owns and draws at fleet level joined every service
200
+ // that reads it, with `maturity: null` because there is no
201
+ // `services/<title>/` — so `gate.partner-undocumented` told the deployer
202
+ // to "adopt it, bind it … or tag it #external", three remedies none of
203
+ // which is true of a Postgres (measured on a one-service fixture,
204
+ // 2026-09-07). It is dropped from the partner SET rather than exempted
205
+ // from the finding, which is the actor precedent exactly: `partners` is
206
+ // the list of counterparties whose DOCS the gate grades, and a store has
207
+ // none to grade.
208
+ if (!facts.actor && !facts.infra)
168
209
  join(source, "consumer", r, facts.external);
169
210
  }
170
211
  else if (source === service) {
171
212
  const facts = endFacts(byId, known, r.target);
172
- if (!facts.actor)
213
+ if (!facts.actor && !facts.infra)
173
214
  join(target, "provider", r, facts.external);
174
215
  }
175
216
  }
@@ -1 +1 @@
1
- {"version":3,"file":"partners.js","sourceRoot":"","sources":["../../../src/core/gate/partners.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The direct join partners of one service, derived from the LIVING landscape,\n * and the maturity grading of everything on either end of a join.\n *\n * The landscape is the only place the fleet's joins are drawn, so the partner\n * set is exactly the edges whose resolved source or target is the gated\n * service — resolved through `serviceResolver` with the ENUMERATED fleet\n * riding in, the same ride that keeps container-modelling landscapes attached\n * everywhere else (an edge into `paymentService.api` is an edge into the\n * service that owns it). A landscape that is absent or does not parse yields\n * NO partner set, never an empty one: `landscape` says which, and the check\n * layer turns \"could not look\" into `gate.partners-unknown` rather than\n * letting it read as \"no partners\".\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem, type Rel } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport {\n ACTOR_KINDS,\n EXTERNAL_TAG,\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n MATURITY_LADDER,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { compareIds, type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport { enumeratedServices } from \"../repo/service-target.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport type { GatePartner, LandscapeRead, PartnerRole } from \"./report.js\";\n\nexport interface PartnerScanRequest {\n docsDir: DocsDir;\n /** The gated service — a name the enumeration approved (`resolveServiceTarget`). */\n service: RawServiceId;\n fleet: FleetContext;\n}\n\n/** One service graded on the adoption ladder, with what the next rung wants. */\nexport interface GradedService {\n maturity: Maturity;\n gaps: string[];\n}\n\nexport interface PartnerScan {\n landscape: LandscapeRead;\n /**\n * Why the landscape graded `invalid` — the parse-error tally or the read\n * failure's own words. Null on `read` and `absent`; the finding that fails\n * the gate must say what broke, not merely that something did.\n */\n landscapeProblem: string | null;\n /**\n * The gated service's own rung, graded under the landscape's evidence —\n * `gate.service-undocumented`'s subject. Fail-closed on the api question:\n * with no readable map, `landscapeEvidence` assumes an API is expected.\n */\n target: GradedService;\n /**\n * The same service graded with NO api expected of it — the reading that\n * does not depend on the landscape at all. The check layer answers from\n * THIS one when the map is absent or broken: the api question is then\n * unanswerable, and one unanswerable fact must not convict twice (the\n * landscape already carries its own finding).\n */\n targetWithoutApi: GradedService;\n /**\n * The gated service's own enumerated entry — carried so a finding can name\n * the directory it actually occupies. `services/<id>/` is right only for an\n * unfiled fleet, and a check that sends a reader to a directory they cannot\n * open has answered worse than not at all.\n */\n targetEntry: ServiceEntry;\n partners: GatePartner[];\n /**\n * The enumerated entries behind the partners that HAVE a `services/`\n * directory, in `partners` order — what the freshness check walks. Kept as\n * entries rather than names so the ids that travel on carry the\n * enumeration's provenance (`RawServiceId`), never a document's.\n */\n partnerEntries: ServiceEntry[];\n}\n\n/** Is this rung at or above `documented` — the fixed threshold check 1 grades against? */\nexport function atLeastDocumented(m: Maturity): boolean {\n return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf(\"documented\");\n}\n\n/** The id itself, then each dotted ancestor — the same walk `serviceResolver` resolves through. */\nfunction selfAndAncestors(id: string): string[] {\n const out = [id];\n for (let dot = id.lastIndexOf(\".\"); dot !== -1; dot = id.lastIndexOf(\".\", dot - 1)) {\n out.push(id.slice(0, dot));\n }\n return out;\n}\n\n/**\n * What the map says about one edge END: does it stand for a person, and is it\n * tagged `#external`? Walked up the ancestor chain because both facts are\n * usually written on the containing system while the edge is drawn from a\n * nested element — but the walk STOPS at the first element that stands for a\n * service (an explicit binding or a title naming a real directory,\n * `serviceResolver`'s own first two rungs, checked after that element's own\n * facts are read). What sits above the service boundary is a grouping, and a\n * grouping's `#external` is the grouping's claim, not the service's: without\n * the stop, an adopted service drawn inside an `#external` zone was exempted\n * from `gate.partner-undocumented` and its real rung hidden.\n */\nfunction endFacts(\n byId: Map<string, Elem>,\n known: ReadonlySet<string>,\n id: string,\n): { actor: boolean; external: boolean } {\n let actor = false;\n let external = false;\n for (const candidate of selfAndAncestors(id)) {\n const e = byId.get(candidate);\n if (e === undefined) continue;\n if (ACTOR_KINDS.has(e.kind.toLowerCase())) actor = true;\n if (e.tags.includes(EXTERNAL_TAG)) external = true;\n if (e.service !== undefined || known.has(e.title)) break;\n }\n return { actor, external };\n}\n\n/** How one edge joins the two ends, in the words the finding and the payload both use. */\nfunction viaOf(r: Rel): string[] {\n const typed = [\n ...(r.op === undefined ? [] : [`operation ${r.op}`]),\n ...(r.publishes === undefined ? [] : [`message ${r.publishes}`]),\n ...(r.consumes === undefined ? [] : [`message ${r.consumes}`]),\n ];\n if (typed.length > 0) return typed;\n return [r.title === undefined ? \"untyped edge\" : `edge '${r.title}'`];\n}\n\ninterface PartnerAccumulator {\n consumer: boolean;\n provider: boolean;\n external: boolean;\n via: Set<string>;\n}\n\nexport async function partnerScan(req: PartnerScanRequest): Promise<PartnerScan> {\n const { docsDir, service, fleet } = req;\n // The same memoized enumeration the command's resolveServiceTarget already\n // performed on this FleetContext — one walk answers both questions.\n const entries = await enumeratedServices(docsDir, fleet);\n // Keyed as plain strings on purpose: the lookups below arrive from the\n // resolver, whose names are document-asserted, and only a hit proves a\n // directory. The VALUES keep the enumeration's provenance.\n const entryById = new Map<string, ServiceEntry>(entries.map((e) => [e.id, e]));\n const known: ReadonlySet<string> = new Set(entries.map((e) => e.id));\n\n const lp = landscapePath(docsDir);\n let landscape: LandscapeRead = \"absent\";\n let landscapeProblem: string | null = null;\n let elements: Elem[] = [];\n let relationships: Rel[] = [];\n if (existsSync(lp)) {\n try {\n const doc = await fleet.loadLikeC4(lp);\n if (doc.errors.length > 0) {\n landscape = \"invalid\";\n const first = doc.errors[0];\n landscapeProblem =\n `${doc.errors.length} parse error(s)` +\n (first === undefined ? \"\" : ` — first: ${first.message}`);\n } else {\n landscape = \"read\";\n elements = doc.elements;\n relationships = doc.relationships;\n }\n } catch (err) {\n // A landscape that could not be READ, graded as one that did not PARSE —\n // commands/validate/fleet/load.ts's containment doctrine, spelled here\n // because core cannot import it: \"could not be read\" and \"did not parse\"\n // have the same consequence (nothing may be concluded from this file).\n // The reason travels into the finding, message-preserving as\n // `unreadableLandscape` is; the read error must not kill the run — the\n // other three checks still have answers.\n landscape = \"invalid\";\n landscapeProblem = err instanceof Error ? err.message : String(err);\n }\n }\n const parses = landscape === \"read\";\n const svcOf: (id: string) => string = parses\n ? serviceResolver(elements, known)\n : (id: string) => id;\n const elementIds = elements.map((e) => e.id);\n\n // One service's ladder rung. `landscapeEvidence` is the ONE shared spelling\n // of the positive-evidence apiExpected rule (core/vocabulary/maturity.ts);\n // its own docblock warns against calling it per service inside a FLEET loop,\n // and this loop is not one — it runs over the gated service and its direct\n // partners, a handful, never the enumeration.\n const gradeWith = (entry: ServiceEntry, apiExpected: boolean): GradedService => {\n const input = {\n entry,\n archSpec: existsSync(servicePathsAt(entry.dir).archSpec),\n apiExpected,\n };\n return { maturity: serviceMaturity(input), gaps: maturityGaps(input) };\n };\n const grade = (entry: ServiceEntry): GradedService =>\n gradeWith(\n entry,\n landscapeEvidence({ id: entry.id, parses, relationships, elementIds, svcOf }).apiExpected,\n );\n\n // Unreachable by construction: the command layer resolved `service` against\n // the same memoized enumeration this scan just read, and refused\n // `unknown-service` when no directory answered. Failing closed here beats\n // grading a service nobody enumerated as if its absence were a rung.\n const targetEntry = entryById.get(service);\n if (targetEntry === undefined) {\n throw new Error(`gate: '${service}' vanished from the enumeration between resolve and scan`);\n }\n\n const byId = new Map(elements.map((e) => [e.id, e]));\n const acc = new Map<string, PartnerAccumulator>();\n const join = (name: string, role: PartnerRole, r: Rel, external: boolean): void => {\n const slot = acc.get(name) ?? { consumer: false, provider: false, external: false, via: new Set<string>() };\n if (role === \"consumer\") slot.consumer = true;\n else slot.provider = true;\n slot.external = slot.external || external;\n for (const v of viaOf(r)) slot.via.add(v);\n acc.set(name, slot);\n };\n for (const r of relationships) {\n const source = svcOf(r.source);\n const target = svcOf(r.target);\n // A self-edge (both ends resolve to the gated service — its own containers\n // talking to each other) names no partner.\n if (source === target) continue;\n if (target === service) {\n const facts = endFacts(byId, known, r.source);\n // A person is never a deploy participant: the actor exemption every\n // landscape census applies (drawnSystems, commands/validate/fleet/census.ts).\n if (!facts.actor) join(source, \"consumer\", r, facts.external);\n } else if (source === service) {\n const facts = endFacts(byId, known, r.target);\n if (!facts.actor) join(target, \"provider\", r, facts.external);\n }\n }\n\n // Sorted by id so identical state yields identical bytes — the accumulator's\n // order is the landscape's edge order, which a reformat may shuffle.\n const joined = [...acc.entries()].sort((a, b) => compareIds(a[0], b[0]));\n const partners: GatePartner[] = joined.map(([name, slot]) => {\n const entry = entryById.get(name);\n return {\n service: name,\n maturity: entry === undefined ? null : grade(entry).maturity,\n role: slot.consumer && slot.provider ? \"both\" : slot.consumer ? \"consumer\" : \"provider\",\n via: [...slot.via].sort(),\n // An adopted service is OURS whatever a zone's tag says: `external` and\n // a maturity rung together must be unrepresentable, or the rung the\n // payload carries is one the exemption hides and the human view drops.\n external: entry === undefined && slot.external,\n };\n });\n\n return {\n landscape,\n landscapeProblem,\n targetEntry,\n target: grade(targetEntry),\n targetWithoutApi: gradeWith(targetEntry, false),\n partners,\n partnerEntries: joined.flatMap(([name]) => {\n const entry = entryById.get(name);\n return entry === undefined ? [] : [entry];\n }),\n };\n}\n"]}
1
+ {"version":3,"file":"partners.js","sourceRoot":"","sources":["../../../src/core/gate/partners.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The direct join partners of one service, derived from the LIVING landscape,\n * and the maturity grading of everything on either end of a join.\n *\n * The landscape is the only place the fleet's joins are drawn, so the partner\n * set is exactly the edges whose resolved source or target is the gated\n * service — resolved through `serviceResolver` with the ENUMERATED fleet\n * riding in, the same ride that keeps container-modelling landscapes attached\n * everywhere else (an edge into `paymentService.api` is an edge into the\n * service that owns it). A landscape that is absent or does not parse yields\n * NO partner set, never an empty one: `landscape` says which, and the check\n * layer turns \"could not look\" into `gate.partners-unknown` rather than\n * letting it read as \"no partners\".\n */\nimport { existsSync } from \"node:fs\";\nimport { errorText, type Elem, type Rel } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport {\n ACTOR_KINDS,\n EXTERNAL_TAG,\n INFRA_KINDS,\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n MATURITY_LADDER,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { compareIds, type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport { enumeratedServices } from \"../repo/service-target.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport { ancestorIds } from \"../kernel/ids/fqn/ancestors.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport type { GatePartner, LandscapeRead, PartnerRole } from \"./report.js\";\n\nexport interface PartnerScanRequest {\n docsDir: DocsDir;\n /** The gated service — a name the enumeration approved (`resolveServiceTarget`). */\n service: RawServiceId;\n fleet: FleetContext;\n}\n\n/** One service graded on the adoption ladder, with what the next rung wants. */\nexport interface GradedService {\n maturity: Maturity;\n gaps: string[];\n}\n\nexport interface PartnerScan {\n landscape: LandscapeRead;\n /**\n * Why the landscape graded `invalid` — the parse-error tally or the read\n * failure's own words. Null on `read` and `absent`; the finding that fails\n * the gate must say what broke, not merely that something did.\n */\n landscapeProblem: string | null;\n /**\n * The gated service's own rung, graded under the landscape's evidence —\n * `gate.service-undocumented`'s subject. Fail-closed on the api question:\n * with no readable map, `landscapeEvidence` assumes an API is expected.\n */\n target: GradedService;\n /**\n * The same service graded with NO api expected of it — the reading that\n * does not depend on the landscape at all. The check layer answers from\n * THIS one when the map is absent or broken: the api question is then\n * unanswerable, and one unanswerable fact must not convict twice (the\n * landscape already carries its own finding).\n */\n targetWithoutApi: GradedService;\n /**\n * The gated service's own enumerated entry — carried so a finding can name\n * the directory it actually occupies. `services/<id>/` is right only for an\n * unfiled fleet, and a check that sends a reader to a directory they cannot\n * open has answered worse than not at all.\n */\n targetEntry: ServiceEntry;\n partners: GatePartner[];\n /**\n * The enumerated entries behind the partners that HAVE a `services/`\n * directory, in `partners` order — what the freshness check walks. Kept as\n * entries rather than names so the ids that travel on carry the\n * enumeration's provenance (`RawServiceId`), never a document's.\n */\n partnerEntries: ServiceEntry[];\n}\n\n/** Is this rung at or above `documented` — the fixed threshold check 1 grades against? */\nexport function atLeastDocumented(m: Maturity): boolean {\n return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf(\"documented\");\n}\n\n/**\n * What the map says about one edge END: does it stand for a person, is it\n * INFRASTRUCTURE, and is it tagged `#external`? Walked up the ancestor chain\n * because all three facts are usually written on the containing system while\n * the edge is drawn from a nested element — but the walk STOPS at the first\n * element that stands for a service (an explicit binding or a title naming a\n * real directory, `serviceResolver`'s own first two rungs, checked after that\n * element's own facts are read). What sits above the service boundary is a\n * grouping, and a grouping's `#external` is the grouping's claim, not the\n * service's: without the stop, an adopted service drawn inside an `#external`\n * zone was exempted from `gate.partner-undocumented` and its real rung hidden.\n *\n * `infra` is read only off an element that stands for NO service AND sits\n * inside none, which is the asymmetry the `standsFor` local exists for. That is\n * `commands/validate/fleet/census.ts` in two steps and in its order:\n * `serviceLevelElements` drops everything drawn inside a service BEFORE\n * `drawnSystems` ever applies the kind test, so a service's own interior never\n * reaches it. Both halves are load-bearing. A `database` bound with\n * `metadata { service '<id>' }` is an adopted service that happens to be drawn\n * as a store — its side of the join IS recorded, and dropping it would hide a\n * real partner's rung. And a store drawn INSIDE its owner — `orderService.db`,\n * the ordinary way a team draws a private datastore — is that service's\n * interior: reading the kind off it convicted the WHOLE endpoint as\n * infrastructure, and `partnerScan` then dropped order-service from the partner\n * set, so a service this gate exists to grade vanished because of how somebody\n * drew its storage. The kind is the fallback for an element nothing else\n * answers for, never the override.\n */\nfunction endFacts(\n byId: Map<string, Elem>,\n known: ReadonlySet<string>,\n id: string,\n): { actor: boolean; infra: boolean; external: boolean } {\n let actor = false;\n let infra = false;\n let external = false;\n // The id itself, then each dotted ancestor — the same walk `serviceResolver`\n // resolves through, out of the one module that spells it\n // (`core/kernel/ids/fqn/ancestors.ts`).\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e === undefined) continue;\n const standsFor = e.service !== undefined || known.has(e.title);\n if (ACTOR_KINDS.has(e.kind.toLowerCase())) actor = true;\n if (!standsFor && INFRA_KINDS.has(e.kind.toLowerCase())) infra = true;\n if (e.tags.includes(EXTERNAL_TAG)) external = true;\n if (standsFor) {\n // Everything the walk has visited so far was drawn INSIDE this service,\n // so nothing visited so far was service-LEVEL and no kind read off it\n // answers for a service — `serviceLevelElements` filtering ahead of the\n // kind test, spelled as a walk. `actor` and `external` are deliberately\n // left standing: a person or an `#external` tag written on a container is\n // still a claim about this endpoint, while the store shape is a claim\n // about the owner's interior.\n infra = false;\n break;\n }\n }\n return { actor, infra, external };\n}\n\n/** How one edge joins the two ends, in the words the finding and the payload both use. */\nfunction viaOf(r: Rel): string[] {\n const typed = [\n ...(r.op === undefined ? [] : [`operation ${r.op}`]),\n ...(r.publishes === undefined ? [] : [`message ${r.publishes}`]),\n ...(r.consumes === undefined ? [] : [`message ${r.consumes}`]),\n ];\n if (typed.length > 0) return typed;\n return [r.title === undefined ? \"untyped edge\" : `edge '${r.title}'`];\n}\n\ninterface PartnerAccumulator {\n consumer: boolean;\n provider: boolean;\n external: boolean;\n via: Set<string>;\n}\n\nexport async function partnerScan(req: PartnerScanRequest): Promise<PartnerScan> {\n const { docsDir, service, fleet } = req;\n // The same memoized enumeration the command's resolveServiceTarget already\n // performed on this FleetContext — one walk answers both questions.\n const entries = await enumeratedServices(docsDir, fleet);\n // Keyed as plain strings on purpose: the lookups below arrive from the\n // resolver, whose names are document-asserted, and only a hit proves a\n // directory. The VALUES keep the enumeration's provenance.\n const entryById = new Map<string, ServiceEntry>(entries.map((e) => [e.id, e]));\n const known: ReadonlySet<string> = new Set(entries.map((e) => e.id));\n\n const lp = landscapePath(docsDir);\n let landscape: LandscapeRead = \"absent\";\n let landscapeProblem: string | null = null;\n let elements: Elem[] = [];\n let relationships: Rel[] = [];\n if (existsSync(lp)) {\n try {\n const doc = await fleet.loadLikeC4(lp);\n if (doc.errors.length > 0) {\n landscape = \"invalid\";\n const first = doc.errors[0];\n // Through `errorText` — the raw message had no line, and a dropped\n // quote's is a parser token list of thousands of lines.\n landscapeProblem =\n `${doc.errors.length} parse error(s)` +\n (first === undefined ? \"\" : ` — first: ${errorText(first)}`);\n } else {\n landscape = \"read\";\n elements = doc.elements;\n relationships = doc.relationships;\n }\n } catch (err) {\n // A landscape that could not be READ, graded as one that did not PARSE —\n // commands/validate/fleet/load.ts's containment doctrine, spelled here\n // because core cannot import it: \"could not be read\" and \"did not parse\"\n // have the same consequence (nothing may be concluded from this file).\n // The reason travels into the finding, message-preserving as\n // `unreadableLandscape` is; the read error must not kill the run — the\n // other three checks still have answers.\n landscape = \"invalid\";\n landscapeProblem = err instanceof Error ? err.message : String(err);\n }\n }\n const parses = landscape === \"read\";\n const svcOf: (id: string) => string = parses\n ? serviceResolver(elements, known)\n : (id: string) => id;\n const elementIds = elements.map((e) => e.id);\n\n // One service's ladder rung. `landscapeEvidence` is the ONE shared spelling\n // of the positive-evidence apiExpected rule (core/vocabulary/maturity.ts);\n // its own docblock warns against calling it per service inside a FLEET loop,\n // and this loop is not one — it runs over the gated service and its direct\n // partners, a handful, never the enumeration.\n const gradeWith = (entry: ServiceEntry, apiExpected: boolean): GradedService => {\n const input = {\n entry,\n archSpec: existsSync(servicePathsAt(entry.dir).archSpec),\n apiExpected,\n };\n return { maturity: serviceMaturity(input), gaps: maturityGaps(input) };\n };\n const grade = (entry: ServiceEntry): GradedService =>\n gradeWith(\n entry,\n landscapeEvidence({ id: entry.id, parses, relationships, elementIds, svcOf }).apiExpected,\n );\n\n // Unreachable by construction: the command layer resolved `service` against\n // the same memoized enumeration this scan just read, and refused\n // `unknown-service` when no directory answered. Failing closed here beats\n // grading a service nobody enumerated as if its absence were a rung.\n const targetEntry = entryById.get(service);\n if (targetEntry === undefined) {\n throw new Error(`gate: '${service}' vanished from the enumeration between resolve and scan`);\n }\n\n const byId = new Map(elements.map((e) => [e.id, e]));\n const acc = new Map<string, PartnerAccumulator>();\n const join = (name: string, role: PartnerRole, r: Rel, external: boolean): void => {\n const slot = acc.get(name) ?? { consumer: false, provider: false, external: false, via: new Set<string>() };\n if (role === \"consumer\") slot.consumer = true;\n else slot.provider = true;\n slot.external = slot.external || external;\n for (const v of viaOf(r)) slot.via.add(v);\n acc.set(name, slot);\n };\n for (const r of relationships) {\n const source = svcOf(r.source);\n const target = svcOf(r.target);\n // A self-edge (both ends resolve to the gated service — its own containers\n // talking to each other) names no partner.\n if (source === target) continue;\n if (target === service) {\n const facts = endFacts(byId, known, r.source);\n // A person is never a deploy participant, and neither is the fleet's own\n // infrastructure: the two kind exemptions every landscape census applies\n // (drawnSystems, commands/validate/fleet/census.ts), spelled here off the\n // same two sets so a gate and a validate run cannot disagree about what a\n // partner IS.\n //\n // The infra half is the newer one, and it had a measured cost. A\n // `database` the fleet owns and draws at fleet level joined every service\n // that reads it, with `maturity: null` because there is no\n // `services/<title>/` — so `gate.partner-undocumented` told the deployer\n // to \"adopt it, bind it … or tag it #external\", three remedies none of\n // which is true of a Postgres (measured on a one-service fixture,\n // 2026-09-07). It is dropped from the partner SET rather than exempted\n // from the finding, which is the actor precedent exactly: `partners` is\n // the list of counterparties whose DOCS the gate grades, and a store has\n // none to grade.\n if (!facts.actor && !facts.infra) join(source, \"consumer\", r, facts.external);\n } else if (source === service) {\n const facts = endFacts(byId, known, r.target);\n if (!facts.actor && !facts.infra) join(target, \"provider\", r, facts.external);\n }\n }\n\n // Sorted by id so identical state yields identical bytes — the accumulator's\n // order is the landscape's edge order, which a reformat may shuffle.\n const joined = [...acc.entries()].sort((a, b) => compareIds(a[0], b[0]));\n const partners: GatePartner[] = joined.map(([name, slot]) => {\n const entry = entryById.get(name);\n return {\n service: name,\n maturity: entry === undefined ? null : grade(entry).maturity,\n role: slot.consumer && slot.provider ? \"both\" : slot.consumer ? \"consumer\" : \"provider\",\n via: [...slot.via].sort(),\n // An adopted service is OURS whatever a zone's tag says: `external` and\n // a maturity rung together must be unrepresentable, or the rung the\n // payload carries is one the exemption hides and the human view drops.\n external: entry === undefined && slot.external,\n };\n });\n\n return {\n landscape,\n landscapeProblem,\n targetEntry,\n target: grade(targetEntry),\n targetWithoutApi: gradeWith(targetEntry, false),\n partners,\n partnerEntries: joined.flatMap(([name]) => {\n const entry = entryById.get(name);\n return entry === undefined ? [] : [entry];\n }),\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/core/gate/report.ts"],"names":[],"mappings":"AAeA,8FAA8F;AAC9F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,CAAU,CAAC;AA4D7F;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AAC9F,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,MAAM,EAAE,GAAG,CAAC,QAA0B,EAAU,EAAE,CAChD,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3F,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA2B,EAAE,EAAU,EAAE,GAAY;IACrF,MAAM,CAAC,GAAG,GAA4B,CAAC;IACvC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,MAAM,GAAG,CAAC;IACnE,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChE,MAAM,WAAW,GACf,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,mCAAmC,CAAC;IACxF,OAAO;QACL,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,oBAAoB;QACtE,OAAO,EAAE,EAAE;QACX,OAAO,EACL,GAAG,EAAE,KAAK,CAAC,CAAC,IAAI,IAAI,sBAAsB,0BAA0B,WAAW,uEAAuE,MAAM,EAAE;YAC9J,sEAAsE;YACtE,sEAAsE;YACtE,wDAAwD;YACxD,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS;gBACnB,CAAC,CAAC,iGAAiG;gBACnG,CAAC,CAAC,EAAE,CAAC;KACV,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The gate report — what `loam gate` computes before anything is printed.\n *\n * Shapes, verdict arithmetic and the per-subject containment classifier: no\n * filesystem, no process access, no printing. The four checks build findings\n * (`./checks.ts`, `./verification.ts`), the command layer renders them, and\n * this module is what the two agree through — the same split\n * `core/vocabulary/report.ts` gives `loam validate`, whose `Finding` shape\n * the checks reuse so one breach arrives in one shape whichever command\n * reports it.\n */\nimport type { Finding } from \"../vocabulary/report.js\";\nimport type { Maturity } from \"../vocabulary/maturity.js\";\nimport type { VerificationState } from \"../status/report.js\";\n\n/** The four checks, in report order. A closed set — the payload's `checks[].check` values. */\nexport const GATE_CHECKS = [\"partners\", \"freshness\", \"verification\", \"interrupted\"] as const;\nexport type GateCheckName = (typeof GATE_CHECKS)[number];\n\nexport interface GateCheck {\n check: GateCheckName;\n findings: Finding[];\n}\n\n/**\n * How the landscape read came out. `absent` and `invalid` are kept apart from\n * `read` because the partner set derived under either is NOT \"no partners\" —\n * it is \"could not look\", and `gate.partners-unknown` exists to keep a\n * pipeline from reading the empty list as a clean one.\n */\nexport type LandscapeRead = \"read\" | \"absent\" | \"invalid\";\n\n/**\n * What the partner is to the gated service: a `consumer` calls into it, a\n * `provider` is called by it, `both` when the landscape draws edges in both\n * directions.\n */\nexport type PartnerRole = \"consumer\" | \"provider\" | \"both\";\n\nexport interface GatePartner {\n /** The partner's resolved service name — a directory id when one exists, the drawn name otherwise. */\n service: string;\n /**\n * The partner's rung on the adoption ladder, or null when no `services/`\n * directory answers to the name at all — the state `gate.partner-undocumented`\n * names. Null is deliberately not a fifth rung: `empty` means \"exists,\n * nothing in it\", and a partner nobody adopted is a different fact.\n */\n maturity: Maturity | null;\n role: PartnerRole;\n /** The joins, sorted: `operation <op>`, `message <name>`, or the untyped edge's title. */\n via: string[];\n /** The element is tagged `#external` — somebody else's system, undocumented on purpose. */\n external: boolean;\n}\n\n/**\n * One active feature touching the gated service, with the verification\n * record's own projection beside it. Every field but `id` is\n * `verificationState`'s (`core/status/verification.ts`) — never a second\n * derivation of \"verified\".\n */\nexport interface GateFeature extends VerificationState {\n id: string;\n}\n\nexport interface GateReport {\n service: string;\n landscape: LandscapeRead;\n partners: GatePartner[];\n features: GateFeature[];\n checks: GateCheck[];\n}\n\nexport type GateVerdict = \"pass\" | \"fail\";\n\n/**\n * Fail iff any check carries an error-severity finding. `--strict` never moves\n * this — like validate's `valid`, the verdict means the same thing in text and\n * JSON whatever the exit code does, so two pipelines reading one repo can\n * grade the same report differently and both be telling the truth\n * (commands/validate/validate.ts documents the stance; gate copies it).\n */\nexport function gateVerdict(checks: readonly GateCheck[]): GateVerdict {\n return checks.some((c) => c.findings.some((f) => f.severity === \"error\")) ? \"fail\" : \"pass\";\n}\n\nexport function gateSummary(checks: readonly GateCheck[]): { errors: number; warnings: number } {\n const of = (severity: \"error\" | \"warn\"): number =>\n checks.reduce((n, c) => n + c.findings.filter((f) => f.severity === severity).length, 0);\n return { errors: of(\"error\"), warnings: of(\"warn\") };\n}\n\n/**\n * Per-subject containment: one unreadable sibling degrades ONE subject's\n * answer, with its path recorded, instead of killing the report the other\n * subjects still have. The code is validate's own containment code\n * (`guarded`, commands/validate/report.ts), reused so one breach is spelled\n * one way — and it is an ERROR: an unanswerable check fails closed, because\n * \"nobody could look\" must be distinguishable from \"nothing is wrong\" in the\n * exit code as well as the payload. Anything carrying no errno and no path is\n * a real bug, and still escapes untouched (reportRepositoryUnavailable's\n * recognizer).\n */\nexport function unreadableSubject(kind: \"service\" | \"feature\", id: string, err: unknown): Finding {\n const e = err as NodeJS.ErrnoException;\n if (e.path === undefined && typeof e.errno !== \"number\") throw err;\n const reason = err instanceof Error ? err.message : String(err);\n const consequence =\n kind === \"service\" ? \"its freshness is unknown\" : \"its verification state is unknown\";\n return {\n severity: \"error\",\n code: kind === \"service\" ? \"service.unreadable\" : \"feature.unreadable\",\n subject: id,\n message:\n `${id}: ${e.path ?? \"one of its documents\"} could not be read, so ${consequence} — nobody could look, which is not the same as nothing being wrong. ${reason}` +\n // The same sentence reportRepositoryUnavailable carries, for the same\n // reason: Node reports EISDIR from read() with no path at all, so the\n // commonest malformed-docs shape arrives here nameless.\n (e.path === undefined\n ? \" The failure named no path — that is how Node reports a directory sitting where a file belongs.\"\n : \"\"),\n };\n}\n"]}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/core/gate/report.ts"],"names":[],"mappings":"AAeA,8FAA8F;AAC9F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,CAAU,CAAC;AAoG7F;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AAC9F,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,MAAM,EAAE,GAAG,CAAC,QAA0B,EAAU,EAAE,CAChD,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3F,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA2B,EAAE,EAAU,EAAE,GAAY;IACrF,MAAM,CAAC,GAAG,GAA4B,CAAC;IACvC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,MAAM,GAAG,CAAC;IACnE,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChE,MAAM,WAAW,GACf,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,mCAAmC,CAAC;IACxF,OAAO;QACL,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,oBAAoB;QACtE,OAAO,EAAE,EAAE;QACX,OAAO,EACL,GAAG,EAAE,KAAK,CAAC,CAAC,IAAI,IAAI,sBAAsB,0BAA0B,WAAW,uEAAuE,MAAM,EAAE;YAC9J,sEAAsE;YACtE,sEAAsE;YACtE,wDAAwD;YACxD,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS;gBACnB,CAAC,CAAC,iGAAiG;gBACnG,CAAC,CAAC,EAAE,CAAC;KACV,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The gate report — what `loam gate` computes before anything is printed.\n *\n * Shapes, verdict arithmetic and the per-subject containment classifier: no\n * filesystem, no process access, no printing. The four checks build findings\n * (`./checks.ts`, `./verification.ts`), the command layer renders them, and\n * this module is what the two agree through — the same split\n * `core/vocabulary/report.ts` gives `loam validate`, whose `Finding` shape\n * the checks reuse so one breach arrives in one shape whichever command\n * reports it.\n */\nimport type { Finding } from \"../vocabulary/report.js\";\nimport type { Maturity } from \"../vocabulary/maturity.js\";\nimport type { VerificationState } from \"../status/report.js\";\n\n/** The four checks, in report order. A closed set — the payload's `checks[].check` values. */\nexport const GATE_CHECKS = [\"partners\", \"freshness\", \"verification\", \"interrupted\"] as const;\nexport type GateCheckName = (typeof GATE_CHECKS)[number];\n\nexport interface GateCheck {\n check: GateCheckName;\n findings: Finding[];\n}\n\n/**\n * How the landscape read came out. `absent` and `invalid` are kept apart from\n * `read` because the partner set derived under either is NOT \"no partners\" —\n * it is \"could not look\", and `gate.partners-unknown` exists to keep a\n * pipeline from reading the empty list as a clean one.\n */\nexport type LandscapeRead = \"read\" | \"absent\" | \"invalid\";\n\n/**\n * What the partner is to the gated service: a `consumer` calls into it, a\n * `provider` is called by it, `both` when the landscape draws edges in both\n * directions.\n */\nexport type PartnerRole = \"consumer\" | \"provider\" | \"both\";\n\nexport interface GatePartner {\n /** The partner's resolved service name — a directory id when one exists, the drawn name otherwise. */\n service: string;\n /**\n * The partner's rung on the adoption ladder, or null when no `services/`\n * directory answers to the name at all — the state `gate.partner-undocumented`\n * names. Null is deliberately not a fifth rung: `empty` means \"exists,\n * nothing in it\", and a partner nobody adopted is a different fact.\n */\n maturity: Maturity | null;\n role: PartnerRole;\n /** The joins, sorted: `operation <op>`, `message <name>`, or the untyped edge's title. */\n via: string[];\n /** The element is tagged `#external` — somebody else's system, undocumented on purpose. */\n external: boolean;\n}\n\n/**\n * One feature touching the gated service — every active one, plus, under\n * `--since`, every one archived after the ref — with the verification\n * record's own projection beside it. Every field but `id` and `archived` is\n * `verificationState`'s (`core/status/verification.ts`) — never a second\n * derivation of \"verified\".\n */\nexport interface GateFeature extends VerificationState {\n id: string;\n /**\n * Present, and true, only on a row `--since` added: the feature shipped\n * after the ref, so its record is frozen history. Absent rather than\n * `false` on the in-flight rows, so a payload without `--since` is byte for\n * byte the one it was before the flag existed.\n */\n archived?: true;\n /**\n * Present only beside `archived`: the checklist digest `loam archive`\n * captured as the feature merged — what its frozen record was graded\n * against, so a record that had stopped answering the feature before it\n * shipped reads `stale` — or null when that archive captured none (a loam\n * that predates the capture, or a feature that had no record then). Under\n * null a record is read as frozen history, exactly as before the capture\n * existed, and the row says so rather than letting that reading pass for a\n * checked one.\n */\n checklistAtArchive?: string | null;\n}\n\n/**\n * `--since <ref>`, resolved: the docs-repo commit a deploy pipeline last\n * shipped from, and the `features/archive/` directory names that already\n * existed there AND still hold what they held then. Without it the\n * verification check reads in-flight features only, and a feature drops out of\n * the gate the moment it is archived — which under `--strict` made archiving\n * the cheapest way past an unverified-feature warning. With it, every feature\n * archived AFTER the ref is graded too: its directory absent there, or changed\n * since — a feature unarchived, edited and archived again ships again, at the\n * same path. Nothing is stored: the pipeline already knows its last deployed\n * docs commit, and git already knows what was archived at it.\n */\nexport interface GateSince {\n ref: string;\n commit: string;\n /** Archive directory names an earlier deploy already shipped exactly as they stand — the ones this gate leaves out. */\n shippedAtRef: ReadonlySet<string>;\n}\n\nexport interface GateReport {\n service: string;\n /** The `--since` bound the report was computed under, or null for in-flight features only. */\n since: { ref: string; commit: string } | null;\n landscape: LandscapeRead;\n partners: GatePartner[];\n features: GateFeature[];\n checks: GateCheck[];\n}\n\nexport type GateVerdict = \"pass\" | \"fail\";\n\n/**\n * Fail iff any check carries an error-severity finding. `--strict` never moves\n * this — like validate's `valid`, the verdict means the same thing in text and\n * JSON whatever the exit code does, so two pipelines reading one repo can\n * grade the same report differently and both be telling the truth\n * (commands/validate/validate.ts documents the stance; gate copies it).\n */\nexport function gateVerdict(checks: readonly GateCheck[]): GateVerdict {\n return checks.some((c) => c.findings.some((f) => f.severity === \"error\")) ? \"fail\" : \"pass\";\n}\n\nexport function gateSummary(checks: readonly GateCheck[]): { errors: number; warnings: number } {\n const of = (severity: \"error\" | \"warn\"): number =>\n checks.reduce((n, c) => n + c.findings.filter((f) => f.severity === severity).length, 0);\n return { errors: of(\"error\"), warnings: of(\"warn\") };\n}\n\n/**\n * Per-subject containment: one unreadable sibling degrades ONE subject's\n * answer, with its path recorded, instead of killing the report the other\n * subjects still have. The code is validate's own containment code\n * (`guarded`, commands/validate/report.ts), reused so one breach is spelled\n * one way — and it is an ERROR: an unanswerable check fails closed, because\n * \"nobody could look\" must be distinguishable from \"nothing is wrong\" in the\n * exit code as well as the payload. Anything carrying no errno and no path is\n * a real bug, and still escapes untouched (reportRepositoryUnavailable's\n * recognizer).\n */\nexport function unreadableSubject(kind: \"service\" | \"feature\", id: string, err: unknown): Finding {\n const e = err as NodeJS.ErrnoException;\n if (e.path === undefined && typeof e.errno !== \"number\") throw err;\n const reason = err instanceof Error ? err.message : String(err);\n const consequence =\n kind === \"service\" ? \"its freshness is unknown\" : \"its verification state is unknown\";\n return {\n severity: \"error\",\n code: kind === \"service\" ? \"service.unreadable\" : \"feature.unreadable\",\n subject: id,\n message:\n `${id}: ${e.path ?? \"one of its documents\"} could not be read, so ${consequence} — nobody could look, which is not the same as nothing being wrong. ${reason}` +\n // The same sentence reportRepositoryUnavailable carries, for the same\n // reason: Node reports EISDIR from read() with no path at all, so the\n // commonest malformed-docs shape arrives here nameless.\n (e.path === undefined\n ? \" The failure named no path — that is how Node reports a directory sitting where a file belongs.\"\n : \"\"),\n };\n}\n"]}
@@ -6,25 +6,52 @@
6
6
  * limit; this is the one check with a per-feature phase of its own, so it is
7
7
  * the seam.
8
8
  *
9
+ * Without `--since` it reads IN-FLIGHT features only, so a feature leaves the
10
+ * check the moment it is archived — and archive reads no verification record,
11
+ * deliberately (verify never gates the merge). Under `--strict` that made
12
+ * archiving the cheapest way past this check's warning, on a draft service
13
+ * and on a vouched one alike when the feature changed only a contract
14
+ * (`content_digest` covers the spec bodies, not openapi.yaml/asyncapi.yaml, so
15
+ * freshness cannot catch it either). `--since <ref>` closes it without any
16
+ * deployment state: every feature archived after the docs commit the pipeline last
17
+ * deployed from is graded too, by the same `verificationState`, under the
18
+ * same code, its row marked `archived: true` — "archived after" meaning its
19
+ * archive directory was absent at the ref OR differs from what it held there,
20
+ * since a feature unarchived, edited and archived again ships again from the
21
+ * same path. Its record is frozen, but not ungraded: it is held to the
22
+ * checklist `loam archive` captured as the feature merged
23
+ * (`readSnapshotChecklist`), so a record that had stopped answering the
24
+ * feature before it shipped is `stale` — archiving it no longer reads it
25
+ * `verified` again. An archive that captured none (an older loam) is read as
26
+ * frozen history, as before, and the row says so.
27
+ *
9
28
  * Report, never fail: every finding here is a warning, by the item's own
10
29
  * design — a deploy gate that hard-failed on unverified work would teach
11
30
  * everyone that the cheapest way past it is an empty record.
12
31
  */
13
32
  import { inOrder } from "../kernel/concurrency.js";
14
33
  import { verificationState } from "../status/verification.js";
34
+ import { readSnapshotChecklist } from "../staging/snapshot/manifest.js";
15
35
  import { listFeatures } from "../repo/repo.js";
16
36
  import { featurePaths } from "../repo/paths.js";
17
37
  import { compareIds } from "../repo/entries.js";
18
38
  import { unreadableSubject } from "./report.js";
19
39
  export async function verificationCheck(req) {
20
- const active = await listFeatures(req.docsDir, {}, req.fleet);
40
+ const { since } = req;
41
+ const all = await listFeatures(req.docsDir, { includeArchived: since !== undefined }, req.fleet);
21
42
  // "Touching" = carries a specs/<svc>/ delta — the same definition
22
43
  // `status --service` answers with. A feature whose C4 delta merely draws an
23
44
  // edge into the service is deliberately out: counting it needs a delta parse
24
- // per active feature, and it can be added additively if a fleet asks.
25
- const touching = active
45
+ // per active feature, and it can be added additively if a fleet asks. An
46
+ // archived feature keeps its specs/ under features/archive/, so the one
47
+ // definition answers for both sides. "Archived after the ref" is "its
48
+ // archive directory was not there at the ref, or is not what it was there";
49
+ // a feature whose directory was there UNCHANGED shipped in an earlier
50
+ // deploy and is not this one's to answer for (`GateSince`).
51
+ const touching = all
26
52
  .filter((f) => f.services.includes(req.service))
27
- .sort((a, b) => compareIds(a.id, b.id));
53
+ .filter((f) => !f.archived || (since !== undefined && !since.shippedAtRef.has(f.dirName)))
54
+ .sort((a, b) => compareIds(a.id, b.id) || compareIds(a.dirName, b.dirName));
28
55
  // COST ACCEPTED: each touching feature with a record derives its checklist,
29
56
  // which parses its delta. That is the per-feature cost
30
57
  // core/status/fleet/fleet.ts refuses for the WHOLE fleet; a deploy-time
@@ -34,12 +61,20 @@ export async function verificationCheck(req) {
34
61
  // run, not one per feature) and the batch prefetch below genuinely seeds
35
62
  // the delta parses. The prefetch is an accelerator only: a host that cannot
36
63
  // batch degrades to per-feature parses with identical findings.
37
- await req.fleet.prefetchLikeC4(touching.filter((f) => f.has.delta).map((f) => featurePaths(f.dir).delta));
64
+ // An archived feature never derives a checklist (its record is frozen, see
65
+ // `verificationState`, and held to the one its archive captured), so
66
+ // prefetching its delta would parse for nobody.
67
+ await req.fleet.prefetchLikeC4(touching.filter((f) => !f.archived && f.has.delta).map((f) => featurePaths(f.dir).delta));
38
68
  // inOrder caps the workspaces in flight and keeps the rows in feature order.
39
69
  const rows = await inOrder(touching, async (f) => {
40
70
  try {
41
- const { state, notice } = await verificationState(req.docsDir, f, req.fleet);
42
- return { kind: "read", feature: f, state, notice };
71
+ if (!f.archived) {
72
+ const { state, notice } = await verificationState(req.docsDir, f, { fleet: req.fleet });
73
+ return { kind: "read", feature: f, state, notice };
74
+ }
75
+ const shipped = await readSnapshotChecklist(f.dir);
76
+ const { state, notice } = await verificationState(req.docsDir, f, { fleet: req.fleet, shipped });
77
+ return { kind: "read", feature: f, state, notice, shipped };
43
78
  }
44
79
  catch (err) {
45
80
  return { kind: "unreadable", finding: unreadableSubject("feature", f.id, err) };
@@ -52,12 +87,16 @@ export async function verificationCheck(req) {
52
87
  findings.push(row.finding);
53
88
  continue;
54
89
  }
55
- features.push({ id: row.feature.id, ...row.state });
56
- findings.push(...featureFindings(row, req.service));
90
+ features.push({
91
+ id: row.feature.id,
92
+ ...row.state,
93
+ ...(row.shipped === undefined ? {} : { archived: true, checklistAtArchive: row.shipped }),
94
+ });
95
+ findings.push(...featureFindings(row, req.service, since?.ref));
57
96
  }
58
97
  return { features, findings };
59
98
  }
60
- function featureFindings(row, service) {
99
+ function featureFindings(row, service, since) {
61
100
  const { feature, state, notice } = row;
62
101
  if (state.verdict === "verified")
63
102
  return [];
@@ -80,18 +119,42 @@ function featureFindings(row, service) {
80
119
  }
81
120
  const verify = `\`loam verify ${feature.id} --json\``;
82
121
  const open = state.unconfirmed + state.unanswered;
122
+ const claimsOpen = `${open} of ${state.claims} claim(s) on ${feature.id} are open (${state.unconfirmed} unconfirmed, ${state.unanswered} unanswered)`;
83
123
  const message = () => {
124
+ // A row `--since` added. Its record is frozen history: `loam verify` reads
125
+ // it back and refuses to re-record it (commands/verify/verify.ts), so the
126
+ // in-flight remedies below — derive, answer, re-derive — would send a
127
+ // deploy pipeline to a command that cannot run. What it CAN be told is
128
+ // that this deploy carries the feature unchecked, and the one way to
129
+ // reopen it. An archived record is `stale` only against the checklist its
130
+ // archive captured — the feature had moved under it before it shipped.
131
+ if (feature.archived) {
132
+ const shipped = `${feature.id} touches '${service}' and was archived after ${since ?? "the --since ref"}`;
133
+ const reopen = `Its record is frozen history: ${verify} reads it back, and \`loam unarchive ${feature.id}\` re-opens the feature if its claims must be answered before this deploy.`;
134
+ if (state.state === "absent")
135
+ return `${shipped} with no verification record — nothing was checked before it shipped. ${reopen}`;
136
+ // The file that failed may be one service's `verification/<svc>.yaml`
137
+ // as well as the one-file record; `loam verify` names which.
138
+ if (state.state === "unreadable")
139
+ return `${shipped} with a verification record that does not read as a record — ${verify} names the file that failed. ${reopen}`;
140
+ if (state.state === "stale") {
141
+ return `${shipped}, and it had moved after its record was written — the record answers a checklist that was no longer the one it asked when it was archived, so what shipped was not what was checked. ${reopen}`;
142
+ }
143
+ return state.claims === 0
144
+ ? `${shipped} with a record that answers no claims at all. ${reopen}`
145
+ : `${shipped}, and ${claimsOpen}. ${reopen}`;
146
+ }
84
147
  switch (state.state) {
85
148
  case "absent":
86
149
  return `${feature.id} touches '${service}' and has no verification record — nothing has been checked. Derive the checklist and answer it: ${verify}.`;
87
150
  case "unreadable":
88
- return `${feature.id}'s verification.yaml exists but does not read as a record — repair or delete it, then re-run ${verify}; nothing will overwrite it while it is unreadable.`;
151
+ return `${feature.id}'s verification record does not read as a record — ${verify} names the file that failed and its safe repair (never delete a file other services' attestations may be in); nothing will overwrite it while it is unreadable.`;
89
152
  case "stale":
90
153
  return `${feature.id} moved after its record was written — the answers answer a checklist that is no longer the one being asked. Re-derive and re-answer: ${verify}.`;
91
154
  case "recorded":
92
155
  return state.claims === 0
93
156
  ? `${feature.id}'s record answers no claims at all — nothing was asked, so nothing was checked. ${verify} re-derives the checklist.`
94
- : `${open} of ${state.claims} claim(s) on ${feature.id} are open (${state.unconfirmed} unconfirmed, ${state.unanswered} unanswered) — close them with evidence: ${verify}.`;
157
+ : `${claimsOpen} — close them with evidence: ${verify}.`;
95
158
  default: {
96
159
  // A fifth record state must land here as a build failure, not fall
97
160
  // through into some rung nobody decided (verificationStatus holds the
@@ -1 +1 @@
1
- {"version":3,"file":"verification.js","sourceRoot":"","sources":["../../../src/core/gate/verification.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Check 3 of `loam gate`: the verification records of every active feature\n * touching the gated service, read through `verificationState`'s own graded\n * verdicts — never a re-derivation of \"verified\" (that module's header is the\n * contract). Split from `./checks.ts` when the four checks outgrew the line\n * limit; this is the one check with a per-feature phase of its own, so it is\n * the seam.\n *\n * Report, never fail: every finding here is a warning, by the item's own\n * design — a deploy gate that hard-failed on unverified work would teach\n * everyone that the cheapest way past it is an empty record.\n */\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { verificationState } from \"../status/verification.js\";\nimport { listFeatures } from \"../repo/repo.js\";\nimport { featurePaths } from \"../repo/paths.js\";\nimport { compareIds, type FeatureEntry } from \"../repo/entries.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport type { VerificationState } from \"../status/report.js\";\nimport type { VerifyNotice } from \"../verify/record.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport { unreadableSubject, type GateFeature } from \"./report.js\";\n\ntype VerificationRow =\n | { kind: \"read\"; feature: FeatureEntry; state: VerificationState; notice: VerifyNotice | null }\n | { kind: \"unreadable\"; finding: Finding };\n\nexport interface VerificationCheckRequest {\n docsDir: DocsDir;\n service: RawServiceId;\n fleet: FleetContext;\n}\n\nexport async function verificationCheck(\n req: VerificationCheckRequest,\n): Promise<{ features: GateFeature[]; findings: Finding[] }> {\n const active = await listFeatures(req.docsDir, {}, req.fleet);\n // \"Touching\" = carries a specs/<svc>/ delta — the same definition\n // `status --service` answers with. A feature whose C4 delta merely draws an\n // edge into the service is deliberately out: counting it needs a delta parse\n // per active feature, and it can be added additively if a fleet asks.\n const touching = active\n .filter((f) => f.services.includes(req.service))\n .sort((a, b) => compareIds(a.id, b.id));\n // COST ACCEPTED: each touching feature with a record derives its checklist,\n // which parses its delta. That is the per-feature cost\n // core/status/fleet/fleet.ts refuses for the WHOLE fleet; a deploy-time\n // command scoped to one service pays it for that service's features only —\n // with the invocation's context threaded through `verificationState` into\n // the derivation, so its enumerations are memo hits (one fleet walk per\n // run, not one per feature) and the batch prefetch below genuinely seeds\n // the delta parses. The prefetch is an accelerator only: a host that cannot\n // batch degrades to per-feature parses with identical findings.\n await req.fleet.prefetchLikeC4(\n touching.filter((f) => f.has.delta).map((f) => featurePaths(f.dir).delta),\n );\n // inOrder caps the workspaces in flight and keeps the rows in feature order.\n const rows = await inOrder(touching, async (f): Promise<VerificationRow> => {\n try {\n const { state, notice } = await verificationState(req.docsDir, f, req.fleet);\n return { kind: \"read\", feature: f, state, notice };\n } catch (err) {\n return { kind: \"unreadable\", finding: unreadableSubject(\"feature\", f.id, err) };\n }\n });\n const features: GateFeature[] = [];\n const findings: Finding[] = [];\n for (const row of rows) {\n if (row.kind === \"unreadable\") {\n findings.push(row.finding);\n continue;\n }\n features.push({ id: row.feature.id, ...row.state });\n findings.push(...featureFindings(row, req.service));\n }\n return { features, findings };\n}\n\nfunction featureFindings(\n row: { feature: FeatureEntry; state: VerificationState; notice: VerifyNotice | null },\n service: string,\n): Finding[] {\n const { feature, state, notice } = row;\n if (state.verdict === \"verified\") return [];\n if (state.verdict === \"attested\") {\n // attestedNotice's own words — the one notice verify, list and status all\n // show, converted to a Finding rather than re-derived. The code string\n // (`verify.scenario-attested`) is spelled literally at its one emit site,\n // core/verify/record.ts, where the drift guard collects it.\n return notice === null\n ? []\n : [\n {\n severity: notice.severity,\n code: notice.code,\n subject: feature.id,\n message: notice.message,\n ...(notice.claims === undefined ? {} : { details: notice.claims }),\n },\n ];\n }\n const verify = `\\`loam verify ${feature.id} --json\\``;\n const open = state.unconfirmed + state.unanswered;\n const message = (): string => {\n switch (state.state) {\n case \"absent\":\n return `${feature.id} touches '${service}' and has no verification record — nothing has been checked. Derive the checklist and answer it: ${verify}.`;\n case \"unreadable\":\n return `${feature.id}'s verification.yaml exists but does not read as a record — repair or delete it, then re-run ${verify}; nothing will overwrite it while it is unreadable.`;\n case \"stale\":\n return `${feature.id} moved after its record was written — the answers answer a checklist that is no longer the one being asked. Re-derive and re-answer: ${verify}.`;\n case \"recorded\":\n return state.claims === 0\n ? `${feature.id}'s record answers no claims at all — nothing was asked, so nothing was checked. ${verify} re-derives the checklist.`\n : `${open} of ${state.claims} claim(s) on ${feature.id} are open (${state.unconfirmed} unconfirmed, ${state.unanswered} unanswered) — close them with evidence: ${verify}.`;\n default: {\n // A fifth record state must land here as a build failure, not fall\n // through into some rung nobody decided (verificationStatus holds the\n // same guard for the same reason).\n const unreachable: never = state.state;\n throw new Error(`gate: no wording for verification state '${String(unreachable)}'`);\n }\n }\n };\n return [\n {\n severity: \"warn\",\n code: \"gate.feature-unverified\",\n subject: feature.id,\n message: message(),\n },\n ];\n}\n"]}
1
+ {"version":3,"file":"verification.js","sourceRoot":"","sources":["../../../src/core/gate/verification.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Check 3 of `loam gate`: the verification records of every active feature\n * touching the gated service, read through `verificationState`'s own graded\n * verdicts — never a re-derivation of \"verified\" (that module's header is the\n * contract). Split from `./checks.ts` when the four checks outgrew the line\n * limit; this is the one check with a per-feature phase of its own, so it is\n * the seam.\n *\n * Without `--since` it reads IN-FLIGHT features only, so a feature leaves the\n * check the moment it is archived — and archive reads no verification record,\n * deliberately (verify never gates the merge). Under `--strict` that made\n * archiving the cheapest way past this check's warning, on a draft service\n * and on a vouched one alike when the feature changed only a contract\n * (`content_digest` covers the spec bodies, not openapi.yaml/asyncapi.yaml, so\n * freshness cannot catch it either). `--since <ref>` closes it without any\n * deployment state: every feature archived after the docs commit the pipeline last\n * deployed from is graded too, by the same `verificationState`, under the\n * same code, its row marked `archived: true` — \"archived after\" meaning its\n * archive directory was absent at the ref OR differs from what it held there,\n * since a feature unarchived, edited and archived again ships again from the\n * same path. Its record is frozen, but not ungraded: it is held to the\n * checklist `loam archive` captured as the feature merged\n * (`readSnapshotChecklist`), so a record that had stopped answering the\n * feature before it shipped is `stale` — archiving it no longer reads it\n * `verified` again. An archive that captured none (an older loam) is read as\n * frozen history, as before, and the row says so.\n *\n * Report, never fail: every finding here is a warning, by the item's own\n * design — a deploy gate that hard-failed on unverified work would teach\n * everyone that the cheapest way past it is an empty record.\n */\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { verificationState } from \"../status/verification.js\";\nimport { readSnapshotChecklist } from \"../staging/snapshot/manifest.js\";\nimport { listFeatures } from \"../repo/repo.js\";\nimport { featurePaths } from \"../repo/paths.js\";\nimport { compareIds, type FeatureEntry } from \"../repo/entries.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport type { VerificationState } from \"../status/report.js\";\nimport type { VerifyNotice } from \"../verify/record.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport { unreadableSubject, type GateFeature, type GateSince } from \"./report.js\";\n\n/**\n * One graded feature. `shipped` is set only on an archived row: the checklist\n * digest its archive captured, or null when it captured none.\n */\ninterface ReadRow {\n kind: \"read\";\n feature: FeatureEntry;\n state: VerificationState;\n notice: VerifyNotice | null;\n shipped?: string | null;\n}\n\ntype VerificationRow = ReadRow | { kind: \"unreadable\"; finding: Finding };\n\nexport interface VerificationCheckRequest {\n docsDir: DocsDir;\n service: RawServiceId;\n fleet: FleetContext;\n since?: GateSince;\n}\n\nexport async function verificationCheck(\n req: VerificationCheckRequest,\n): Promise<{ features: GateFeature[]; findings: Finding[] }> {\n const { since } = req;\n const all = await listFeatures(req.docsDir, { includeArchived: since !== undefined }, req.fleet);\n // \"Touching\" = carries a specs/<svc>/ delta — the same definition\n // `status --service` answers with. A feature whose C4 delta merely draws an\n // edge into the service is deliberately out: counting it needs a delta parse\n // per active feature, and it can be added additively if a fleet asks. An\n // archived feature keeps its specs/ under features/archive/, so the one\n // definition answers for both sides. \"Archived after the ref\" is \"its\n // archive directory was not there at the ref, or is not what it was there\";\n // a feature whose directory was there UNCHANGED shipped in an earlier\n // deploy and is not this one's to answer for (`GateSince`).\n const touching = all\n .filter((f) => f.services.includes(req.service))\n .filter((f) => !f.archived || (since !== undefined && !since.shippedAtRef.has(f.dirName)))\n .sort((a, b) => compareIds(a.id, b.id) || compareIds(a.dirName, b.dirName));\n // COST ACCEPTED: each touching feature with a record derives its checklist,\n // which parses its delta. That is the per-feature cost\n // core/status/fleet/fleet.ts refuses for the WHOLE fleet; a deploy-time\n // command scoped to one service pays it for that service's features only —\n // with the invocation's context threaded through `verificationState` into\n // the derivation, so its enumerations are memo hits (one fleet walk per\n // run, not one per feature) and the batch prefetch below genuinely seeds\n // the delta parses. The prefetch is an accelerator only: a host that cannot\n // batch degrades to per-feature parses with identical findings.\n // An archived feature never derives a checklist (its record is frozen, see\n // `verificationState`, and held to the one its archive captured), so\n // prefetching its delta would parse for nobody.\n await req.fleet.prefetchLikeC4(\n touching.filter((f) => !f.archived && f.has.delta).map((f) => featurePaths(f.dir).delta),\n );\n // inOrder caps the workspaces in flight and keeps the rows in feature order.\n const rows = await inOrder(touching, async (f): Promise<VerificationRow> => {\n try {\n if (!f.archived) {\n const { state, notice } = await verificationState(req.docsDir, f, { fleet: req.fleet });\n return { kind: \"read\", feature: f, state, notice };\n }\n const shipped = await readSnapshotChecklist(f.dir);\n const { state, notice } = await verificationState(req.docsDir, f, { fleet: req.fleet, shipped });\n return { kind: \"read\", feature: f, state, notice, shipped };\n } catch (err) {\n return { kind: \"unreadable\", finding: unreadableSubject(\"feature\", f.id, err) };\n }\n });\n const features: GateFeature[] = [];\n const findings: Finding[] = [];\n for (const row of rows) {\n if (row.kind === \"unreadable\") {\n findings.push(row.finding);\n continue;\n }\n features.push({\n id: row.feature.id,\n ...row.state,\n ...(row.shipped === undefined ? {} : { archived: true, checklistAtArchive: row.shipped }),\n });\n findings.push(...featureFindings(row, req.service, since?.ref));\n }\n return { features, findings };\n}\n\nfunction featureFindings(\n row: ReadRow,\n service: string,\n since: string | undefined,\n): Finding[] {\n const { feature, state, notice } = row;\n if (state.verdict === \"verified\") return [];\n if (state.verdict === \"attested\") {\n // attestedNotice's own words — the one notice verify, list and status all\n // show, converted to a Finding rather than re-derived. The code string\n // (`verify.scenario-attested`) is spelled literally at its one emit site,\n // core/verify/record.ts, where the drift guard collects it.\n return notice === null\n ? []\n : [\n {\n severity: notice.severity,\n code: notice.code,\n subject: feature.id,\n message: notice.message,\n ...(notice.claims === undefined ? {} : { details: notice.claims }),\n },\n ];\n }\n const verify = `\\`loam verify ${feature.id} --json\\``;\n const open = state.unconfirmed + state.unanswered;\n const claimsOpen = `${open} of ${state.claims} claim(s) on ${feature.id} are open (${state.unconfirmed} unconfirmed, ${state.unanswered} unanswered)`;\n const message = (): string => {\n // A row `--since` added. Its record is frozen history: `loam verify` reads\n // it back and refuses to re-record it (commands/verify/verify.ts), so the\n // in-flight remedies below — derive, answer, re-derive — would send a\n // deploy pipeline to a command that cannot run. What it CAN be told is\n // that this deploy carries the feature unchecked, and the one way to\n // reopen it. An archived record is `stale` only against the checklist its\n // archive captured — the feature had moved under it before it shipped.\n if (feature.archived) {\n const shipped = `${feature.id} touches '${service}' and was archived after ${since ?? \"the --since ref\"}`;\n const reopen = `Its record is frozen history: ${verify} reads it back, and \\`loam unarchive ${feature.id}\\` re-opens the feature if its claims must be answered before this deploy.`;\n if (state.state === \"absent\") return `${shipped} with no verification record — nothing was checked before it shipped. ${reopen}`;\n // The file that failed may be one service's `verification/<svc>.yaml`\n // as well as the one-file record; `loam verify` names which.\n if (state.state === \"unreadable\") return `${shipped} with a verification record that does not read as a record — ${verify} names the file that failed. ${reopen}`;\n if (state.state === \"stale\") {\n return `${shipped}, and it had moved after its record was written — the record answers a checklist that was no longer the one it asked when it was archived, so what shipped was not what was checked. ${reopen}`;\n }\n return state.claims === 0\n ? `${shipped} with a record that answers no claims at all. ${reopen}`\n : `${shipped}, and ${claimsOpen}. ${reopen}`;\n }\n switch (state.state) {\n case \"absent\":\n return `${feature.id} touches '${service}' and has no verification record — nothing has been checked. Derive the checklist and answer it: ${verify}.`;\n case \"unreadable\":\n return `${feature.id}'s verification record does not read as a record — ${verify} names the file that failed and its safe repair (never delete a file other services' attestations may be in); nothing will overwrite it while it is unreadable.`;\n case \"stale\":\n return `${feature.id} moved after its record was written — the answers answer a checklist that is no longer the one being asked. Re-derive and re-answer: ${verify}.`;\n case \"recorded\":\n return state.claims === 0\n ? `${feature.id}'s record answers no claims at all — nothing was asked, so nothing was checked. ${verify} re-derives the checklist.`\n : `${claimsOpen} — close them with evidence: ${verify}.`;\n default: {\n // A fifth record state must land here as a build failure, not fall\n // through into some rung nobody decided (verificationStatus holds the\n // same guard for the same reason).\n const unreachable: never = state.state;\n throw new Error(`gate: no wording for verification state '${String(unreachable)}'`);\n }\n }\n };\n return [\n {\n severity: \"warn\",\n code: \"gate.feature-unverified\",\n subject: feature.id,\n message: message(),\n },\n ];\n}\n"]}
@@ -7,13 +7,13 @@
7
7
  * whole comparison is by digest rather than by name, so a reworded scenario
8
8
  * reads as stale rather than as missing plus new.
9
9
  */
10
- import { existsSync, statSync } from "node:fs";
11
- import { readdir, readFile, realpath } from "node:fs/promises";
10
+ import { existsSync } from "node:fs";
11
+ import { readdir, readFile } from "node:fs/promises";
12
12
  import { join, relative, sep } from "node:path";
13
- import { parseRequirements } from "../document/parse.js";
14
13
  import { SPEC_AXES } from "../repo/paths.js";
15
14
  import { locateServicePaths } from "../repo/service-target.js";
16
15
  import { listFeatures } from "../repo/repo.js";
16
+ import { entryIs, markDescended } from "../repo/tree/fs.js";
17
17
  import { UnsafePathError } from "../kernel/path-safety.js";
18
18
  import { gherkinRoot, scenarioDigest } from "./stamp.js";
19
19
  import { axisLabel } from "./emit.js";
@@ -88,10 +88,7 @@ export async function gherkinFindings(ctx) {
88
88
  const state = { file: axis.file, reqNames: new Set(), digests: new Set(), scenarios: [] };
89
89
  const path = paths[axis.key];
90
90
  if (existsSync(path)) {
91
- const reqs = ctx.fleet === undefined
92
- ? parseRequirements(await readFile(path, "utf8"))
93
- : await ctx.fleet.readRequirements(path);
94
- for (const r of reqs) {
91
+ for (const r of await ctx.fleet.readRequirements(path)) {
95
92
  if (r.kind === "REMOVED")
96
93
  continue;
97
94
  state.reqNames.add(r.name);
@@ -174,9 +171,10 @@ export async function gherkinFindings(ctx) {
174
171
  * enumeration is part of the output contract, so the order cannot depend on
175
172
  * the filesystem's. Dot-entries are skipped, like every other walk in loam.
176
173
  *
177
- * Symlinks are FOLLOWED — repo.ts's `entryIs` and brief/brief.ts's `hasMarkdown` are
178
- * the same rule for the same reason. A `Dirent` from `readdir(withFileTypes)`
179
- * never follows one, so `isFile()` and `isDirectory()` are both false for a
174
+ * Symlinks are FOLLOWED, through `repo/tree/fs.ts`'s `entryIs` — the rule
175
+ * every enumeration shares (brief/brief.ts's `hasEntry` too), for the same
176
+ * reason. A `Dirent` from `readdir(withFileTypes)` never follows one, so
177
+ * `isFile()` and `isDirectory()` are both false for a
180
178
  * symlinked `.feature`: it was invisible to the orphan scan (`loam gherkin`
181
179
  * left it behind forever) and invisible to the staleness scan (it graded as no
182
180
  * coverage at all). Composing a suite out of links is legitimate, so they are
@@ -186,29 +184,19 @@ export async function gherkinFindings(ctx) {
186
184
  export async function featureFilesUnder(root) {
187
185
  const out = [];
188
186
  // Following links means a cycle is now possible; every directory is entered
189
- // once by its resolved path, so a loop terminates instead of hanging.
187
+ // once by its resolved path (`markDescended`), so a loop terminates instead
188
+ // of hanging.
190
189
  const entered = new Set();
191
190
  const walk = async (dir) => {
192
- if (!existsSync(dir))
191
+ if (!markDescended(dir, entered))
193
192
  return;
194
- let resolved;
195
- try {
196
- resolved = await realpath(dir);
197
- }
198
- catch {
199
- return;
200
- }
201
- if (entered.has(resolved))
202
- return;
203
- entered.add(resolved);
204
193
  for (const entry of await readdir(dir, { withFileTypes: true })) {
205
194
  if (entry.name.startsWith("."))
206
195
  continue;
207
196
  const abs = join(dir, entry.name);
208
- const what = entryKind(abs, entry);
209
- if (what === "dir")
197
+ if (entryIs(dir, entry, "dir"))
210
198
  await walk(abs);
211
- else if (what === "file" && entry.name.endsWith(".feature"))
199
+ else if (entry.name.endsWith(".feature") && entryIs(dir, entry, "file"))
212
200
  out.push(abs);
213
201
  }
214
202
  };
@@ -219,16 +207,4 @@ export async function featureFilesUnder(root) {
219
207
  return ra < rb ? -1 : ra > rb ? 1 : 0;
220
208
  });
221
209
  }
222
- /** What an entry IS, resolving a symlink to find out. Nothing when it dangles. */
223
- function entryKind(abs, e) {
224
- if (!e.isSymbolicLink())
225
- return e.isDirectory() ? "dir" : e.isFile() ? "file" : null;
226
- try {
227
- const s = statSync(abs);
228
- return s.isDirectory() ? "dir" : s.isFile() ? "file" : null;
229
- }
230
- catch {
231
- return null;
232
- }
233
- }
234
210
  //# sourceMappingURL=stale.js.map