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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joins.js","sourceRoot":"","sources":["../../../src/core/diff/joins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The cross-service joins `loam diff` reports appearing or going away between\n * the base ref and the working tree — `diff.consumer-added` /\n * `diff.consumer-removed`.\n *\n * These lived in `./victims.ts` beside the per-removal victim index until that\n * index moved down to `core/consumers/` to become the one consumer answer the\n * coherence gate and the archive planner read too. The joins below stayed: they\n * compare two WHOLE states, which only the diff has, while the index answers\n * about one fleet.\n */\nimport type { Requirement } from \"../document/spec.js\";\nimport type { ServiceState } from \"../consumers/state.js\";\nimport { ownsOperation, REQUIREMENT_AXES } from \"../consumers/consumers.js\";\n\n/** The requirements one service contributes to a join — both spec files, a REMOVED one governing nothing. */\nfunction reqsOf(state: ServiceState): Requirement[] {\n return REQUIREMENT_AXES.flatMap((axis) => {\n const a = state[axis];\n return a.kind === \"read\" ? a.value.filter((r) => r.kind !== \"REMOVED\") : [];\n });\n}\n\n/**\n * One cross-service join: `consumer`'s living requirements name a `token` that\n * `provider`'s contract defines. The same \"who depends on what\" question the\n * consumer index (`core/consumers/consumers.ts`) answers per removal, asked\n * here of a WHOLE state — base or current — so `./semantic.ts` can report\n * joins that appeared or went away.\n */\nexport interface ConsumerTuple {\n kind: \"op\" | \"message\";\n consumer: string;\n provider: string;\n token: string;\n}\n\n/** The map key a tuple is joined on between the two states. */\nfunction tupleKey(t: ConsumerTuple): string {\n return `${t.kind}\\0${t.consumer}\\0${t.provider}\\0${t.token}`;\n}\n\n/**\n * Every cross-service join one state holds, keyed for the base/current join.\n * The rules are the consumer index's, read off it rather than restated: both\n * spec files for `Operations:` and `Consumes:` alike (`REQUIREMENT_AXES`), and\n * an `Operations:` line naming an id the consumer's own contract defines is\n * that consumer's own operation, no join onto anybody else's\n * (`ownsOperation`). An unreadable axis simply contributes nothing here — no\n * line from a spec file nobody could read, no ownership from a contract nobody\n * could read — and `tupleVisible` below guards the GRADING, so a join is never\n * called added or removed off a side nobody could read, and `ownershipFlicker`\n * keeps one from being called so off a consumer's own contract that merely\n * stopped or started parsing.\n */\nexport function consumerTuples(states: ReadonlyMap<string, ServiceState>): Map<string, ConsumerTuple> {\n const providersOf = (pick: (s: ServiceState) => string[]): Map<string, string[]> => {\n const map = new Map<string, string[]>();\n for (const id of [...states.keys()].sort()) {\n for (const token of pick(states.get(id)!)) map.set(token, [...(map.get(token) ?? []), id]);\n }\n return map;\n };\n const opProviders = providersOf((s) => (s.openapi.kind === \"read\" ? s.openapi.value.map((op) => op.id) : []));\n const msgProviders = providersOf((s) => (s.asyncapi.kind === \"read\" ? s.asyncapi.value.map((m) => m.name) : []));\n const out = new Map<string, ConsumerTuple>();\n const add = (t: ConsumerTuple): void => {\n out.set(tupleKey(t), t);\n };\n for (const consumer of [...states.keys()].sort()) {\n const state = states.get(consumer)!;\n for (const r of reqsOf(state)) {\n for (const op of r.operations) {\n if (ownsOperation(state, op)) continue;\n for (const p of opProviders.get(op) ?? []) if (p !== consumer) add({ kind: \"op\", consumer, provider: p, token: op });\n }\n for (const name of r.consumes) {\n for (const p of msgProviders.get(name) ?? []) if (p !== consumer) add({ kind: \"message\", consumer, provider: p, token: name });\n }\n }\n }\n return out;\n}\n\n/** May a tuple be graded off this side? Absence answers; unreadable does not; an ambiguous base identity answers nothing. */\nfunction tupleVisible(states: ReadonlyMap<string, ServiceState>, ambiguous: ReadonlySet<string>, t: ConsumerTuple): boolean {\n if (ambiguous.has(t.consumer) || ambiguous.has(t.provider)) return false;\n const readable = (id: string, axes: (\"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\")[]): boolean => {\n const state = states.get(id);\n return state === undefined || axes.every((axis) => state[axis].kind !== \"unreadable\");\n };\n // Both spec files on the consumer's side, for either kind: a line in the file\n // nobody could read may be the very one the join came or went by. NOT the\n // consumer's own contract, although an operation join reads it: it is asked\n // only whether the line names the consumer's OWN operation, and an\n // unreadable contract proves no ownership (`ownsOperation`), so the line\n // counts on that side — the index's own rule. A side holding it unread can\n // only have MORE joins, never hide one, and suppressing the join there kept\n // `diff.consumer-added` / `-removed` off a report whose victims named that\n // very consumer (the contract review, finding 4). Those extra joins are a\n // change of their own only when the readable side does not prove the\n // ownership the unread one could not — `ownershipFlicker` below.\n return readable(t.consumer, [\"spec\", \"archSpec\"]) && readable(t.provider, [t.kind === \"op\" ? \"openapi\" : \"asyncapi\"]);\n}\n\n/**\n * Is this operation join held only because the consumer's own contract did not\n * parse on the side that holds it, while on the other side it reads and\n * defines the id? Then the line names the consumer's OWN operation — the\n * readable side says so — and nothing about the join moved but the contract's\n * readability: the line that never changed \"went away\" when the contract was\n * fixed and \"appeared\" when it broke, once per provider of a shared id such as\n * `healthCheck` (the verification of finding 4, N1). The unreadable contract\n * is already its own `unreadable[]` entry on the consumer, which carries the\n * run to exit 1; a second, false finding on every provider said nothing true.\n *\n * Only that shape is skipped. A readable side that defines no such id proves\n * no ownership either, so the join is the line's and is graded as before; a\n * message join asks no ownership at all (`ownsOperation` says why).\n */\nfunction ownershipFlicker(sides: { holding: ReadonlyMap<string, ServiceState>; other: ReadonlyMap<string, ServiceState> }, t: ConsumerTuple): boolean {\n if (t.kind !== \"op\" || sides.holding.get(t.consumer)?.openapi.kind !== \"unreadable\") return false;\n const other = sides.other.get(t.consumer);\n return other !== undefined && ownsOperation(other, t.token);\n}\n\n/** One graded cross-service join change between the two states. */\nexport interface ConsumerChange {\n t: ConsumerTuple;\n change: \"added\" | \"removed\";\n}\n\n/**\n * The joins that appeared or went away between base and current, sorted for\n * deterministic output (provider, kind, token, consumer). The visibility\n * guard is the containment rule: a join is never called \"added\" off a base\n * side nobody could read, nor \"removed\" off an unreadable working file — the\n * suspended axis already carries that fact. The flicker guard is its mirror on\n * the side that HOLDS the join: one held only because the consumer's own\n * contract did not read there, over an id the other side shows it owns, is no\n * change either.\n */\nexport function consumerJoinChanges(input: {\n base: ReadonlyMap<string, ServiceState>;\n baseAmbiguous: ReadonlySet<string>;\n current: ReadonlyMap<string, ServiceState>;\n currentAmbiguous: ReadonlySet<string>;\n}): ConsumerChange[] {\n const baseTuples = consumerTuples(input.base);\n const currentTuples = consumerTuples(input.current);\n const graded: ConsumerChange[] = [];\n const added = { holding: input.current, other: input.base };\n const removed = { holding: input.base, other: input.current };\n for (const [key, t] of currentTuples) {\n if (baseTuples.has(key) || ownershipFlicker(added, t)) continue;\n if (tupleVisible(input.base, input.baseAmbiguous, t)) graded.push({ t, change: \"added\" });\n }\n for (const [key, t] of baseTuples) {\n if (currentTuples.has(key) || ownershipFlicker(removed, t)) continue;\n if (tupleVisible(input.current, input.currentAmbiguous, t)) graded.push({ t, change: \"removed\" });\n }\n return graded.sort((a, b) => {\n const ka = `${a.t.provider}\\0${a.t.kind}\\0${a.t.token}\\0${a.t.consumer}\\0${a.change}`;\n const kb = `${b.t.provider}\\0${b.t.kind}\\0${b.t.token}\\0${b.t.consumer}\\0${b.change}`;\n return ka < kb ? -1 : 1;\n });\n}\n"]}
@@ -15,7 +15,9 @@
15
15
  * directory is at the five-file limit and because this is a different subject
16
16
  * from the semantic diff beside it: `diff` compares two states of the fleet and
17
17
  * reports what moved, this one only decides WHICH SUBJECTS a run should look
18
- * at. Nothing here reads a document.
18
+ * at. Nothing here reads a document; the one question that has to — which call
19
+ * edges on the fleet map are this branch's — is `./edges.ts`'s, and it reads
20
+ * only the map documents the branch changed.
19
21
  *
20
22
  * What it does NOT do, and the line is not negotiable: a diff-scoped validate
21
23
  * cannot see a victim boundary the change did not touch. Removing an operation
@@ -25,10 +27,17 @@
25
27
  * the two gates answer "did you document what you touched" and "did you break a
26
28
  * consumer", and neither substitutes for the other. Selling a green here that
27
29
  * hides a broken join would be worse than shipping no flag at all.
30
+ *
31
+ * A join the branch WROTE is a different case, and it is graded: an edge the
32
+ * branch added or edited on the fleet map is checked against its provider's
33
+ * contract even with the provider out of scope (`edges` below), because the
34
+ * edge is the document this branch touched and the provider is only where its
35
+ * answer lives.
28
36
  */
29
37
  import { declineDetail, gitText } from "../../provenance/git.js";
30
38
  import { repoPath } from "../../envelope/json.js";
31
39
  import { resolveBase } from "../base-git.js";
40
+ import { edgesSince } from "./edges.js";
32
41
  /**
33
42
  * Every path that differs between `ref` and the WORKING TREE, docs-relative.
34
43
  *
@@ -88,6 +97,10 @@ async function changedPaths(docsDir, commit, prefix) {
88
97
  function inside(path, dir) {
89
98
  return dir !== "" && (path === dir || path.startsWith(`${dir}/`));
90
99
  }
100
+ /** Did this branch change `rel` — a docs-relative file, or anything under a docs-relative directory? */
101
+ export function touched(scope, rel) {
102
+ return scope.changed.some((path) => inside(path, rel));
103
+ }
91
104
  /** The fleet's own documents — the landscape target's subject, which owns no service directory. */
92
105
  const ARCHITECTURE_DIR = "architecture";
93
106
  /**
@@ -134,15 +147,18 @@ export async function scopeSince(request) {
134
147
  };
135
148
  }
136
149
  const dirOf = (abs) => repoPath(docsDir, abs);
150
+ // `architecture/` is the landscape target's whole directory: the fleet map,
151
+ // the permission/capability/obligation vocabularies and the fleet ADRs all
152
+ // live there, and every one of them is graded by the landscape target
153
+ // rather than by any service.
154
+ const architecture = changed.filter((path) => inside(path, ARCHITECTURE_DIR));
137
155
  return {
138
156
  kind: "scope",
139
157
  ref,
140
158
  commit: base.commit,
141
- // `architecture/` is the landscape target's whole directory: the fleet map,
142
- // the permission/capability/obligation vocabularies and the fleet ADRs all
143
- // live there, and every one of them is graded by the landscape target
144
- // rather than by any service.
145
- landscape: changed.some((path) => inside(path, ARCHITECTURE_DIR)),
159
+ landscape: architecture.length > 0,
160
+ changed,
161
+ edges: await edgesSince({ docsDir, commit: base.commit, prefix: base.prefix }, architecture.filter((path) => path.endsWith(".likec4"))),
146
162
  services: request.services.filter((svc) => changed.some((path) => inside(path, dirOf(svc.dir)))),
147
163
  features: request.features.filter((feat) => changed.some((path) => inside(path, dirOf(feat.dir)))),
148
164
  totals: { services: request.services.length, features: request.features.length },
@@ -1 +1 @@
1
- {"version":3,"file":"changed-paths.js","sourceRoot":"","sources":["../../../../src/core/diff/scope/changed-paths.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which targets a base git ref puts in scope — the narrowing behind\n * `loam validate --all --base <ref>`.\n *\n * The defect this exists for: a partly-adopted fleet has no CI setting that is\n * both passing today and tightening over time. `--all` is green over eleven\n * undocumented services (they warn, and warnings do not gate) and `--strict` is\n * red from the first minute of adoption until the last boundary is written, so\n * a team installs either a gate that never notices adoption stalling at 8% or\n * one it cannot turn on. `--base` is the third setting: grade what this branch\n * TOUCHED, at full `--strict` severity if the caller wants it, and let the\n * untouched majority stay untouched.\n *\n * A SUB-PACKAGE of `core/diff/` rather than a sixth file in it, because that\n * directory is at the five-file limit and because this is a different subject\n * from the semantic diff beside it: `diff` compares two states of the fleet and\n * reports what moved, this one only decides WHICH SUBJECTS a run should look\n * at. Nothing here reads a document.\n *\n * What it does NOT do, and the line is not negotiable: a diff-scoped validate\n * cannot see a victim boundary the change did not touch. Removing an operation\n * from `payment-service` puts `payment-service` in scope and leaves\n * `checkout-web` — whose living requirement still names the operation — out of\n * it. That join is `loam diff --base`'s question and it stays `loam diff`'s;\n * the two gates answer \"did you document what you touched\" and \"did you break a\n * consumer\", and neither substitutes for the other. Selling a green here that\n * hides a broken join would be worse than shipping no flag at all.\n */\nimport { declineDetail, gitText } from \"../../provenance/git.js\";\nimport { repoPath, type ErrorCode } from \"../../envelope/json.js\";\nimport { resolveBase } from \"../base-git.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { FeatureEntry, ServiceEntry } from \"../../repo/entries.js\";\n\n/** Everything the narrowing needs: where to ask, what to ask about, and what there is to narrow. */\nexport interface ScopeRequest {\n docsDir: DocsDir;\n /** The ref as the caller spelled it — echoed in every message and in the payload. */\n ref: string;\n /** The run's full service enumeration, at whatever depth the tree walk found each one. */\n services: readonly ServiceEntry[];\n /** The run's full ACTIVE feature enumeration. */\n features: readonly FeatureEntry[];\n}\n\n/** The narrowed run: which targets the branch touched, and what it narrowed from. */\nexport interface ValidateScope {\n kind: \"scope\";\n ref: string;\n /** The 40-hex commit `ref` resolved to — what a rerun would have to pass to reproduce this scope. */\n commit: string;\n /** Did `architecture/` change? The landscape target is the fleet's own subject and has no directory of its own. */\n landscape: boolean;\n services: ServiceEntry[];\n features: FeatureEntry[];\n /** The denominators — what the run would have graded without `--base`. */\n totals: { services: number; features: number };\n}\n\n/**\n * A refusal, already spelled. The code is one `loam diff` already emits for the\n * same two conditions and the message is in the same register, deliberately: a\n * second stable code for \"that ref does not resolve\" would be a second thing to\n * document, branch on and keep in step, for a condition that is identical.\n *\n * The prose is built here rather than in the command because it is the same\n * sentence for every caller and because `base-git.ts`'s `detail` — git's own\n * words, bounded — is what makes it diagnosable; a command that only received a\n * `kind` would have to reinvent the sentence to say why.\n */\nexport interface ScopeRefusal {\n kind: \"refused\";\n code: ErrorCode;\n message: string;\n}\n\nexport type ScopeOutcome = ValidateScope | ScopeRefusal;\n\n/**\n * Every path that differs between `ref` and the WORKING TREE, docs-relative.\n *\n * Two questions, not one, and the second is not optional: `git diff` compares\n * the tree git tracks, so a service adopted five minutes ago — a brand new\n * `services/<id>/spec.md` nobody has committed — is invisible to it. `validate`\n * grades the working tree, so the scope has to be the working tree's, and an\n * untracked new boundary that the run then skipped would be the flag's very\n * first user reporting that it graded nothing.\n *\n * `--no-renames` on purpose: with rename detection on (git's default since\n * 2.9) a service moved into a subsystem reports only its DESTINATION, and the\n * checks that grade the fleet from the old placement would be out of scope.\n * Two paths for a move is the honest answer.\n *\n * `-z` because git quotes non-ASCII and space-bearing paths in line output —\n * the same reason `gitTrackedFiles` asks that way, and the same phantom\n * directory named `\"src` if it did not.\n */\nasync function changedPaths(docsDir: DocsDir, commit: string, prefix: string): Promise<string[] | { detail: string }> {\n // Both run from docsDir. `git diff` ignores cwd and answers for the whole\n // repository with root-relative paths; `git ls-files` restricts itself to\n // cwd, and `--full-name` makes ITS paths root-relative too — so one prefix\n // strip below is correct for both, exactly as `listBaseTree` needs one.\n const tracked = await gitText(docsDir, [\"diff\", \"--name-only\", \"--no-renames\", \"-z\", commit, \"--\"]);\n if (tracked.spawnError !== undefined || tracked.code !== 0 || tracked.overflowed) {\n return { detail: tracked.overflowed ? \"the changed-path listing exceeded the output cap.\" : declineDetail(tracked) };\n }\n const untracked = await gitText(docsDir, [\"ls-files\", \"--others\", \"--exclude-standard\", \"--full-name\", \"-z\"]);\n if (untracked.spawnError !== undefined || untracked.code !== 0 || untracked.overflowed) {\n return { detail: untracked.overflowed ? \"the untracked-path listing exceeded the output cap.\" : declineDetail(untracked) };\n }\n const seen = new Set<string>();\n for (const raw of [...tracked.out.split(\"\\0\"), ...untracked.out.split(\"\\0\")]) {\n // A docs repo checked out as a subdirectory of a larger repository answers\n // in the whole repository's paths; anything outside the docs dir is\n // somebody else's change and is not a target here.\n if (raw === \"\" || !raw.startsWith(prefix)) continue;\n const rel = raw.slice(prefix.length);\n if (rel !== \"\") seen.add(rel);\n }\n return [...seen].sort();\n}\n\n/**\n * Is `path` inside `dir`? Both docs-relative, forward-slashed.\n *\n * A prefix test on the directory plus a separator, never a split on \"/\" and a\n * read of the second segment: a service filed under a subsystem lives at\n * `services/platform/payment-service/`, not at `services/<id>/`, and a\n * path-splitting implementation drops every filed service out of scope\n * SILENTLY — the run stays green because it graded nothing, which is the exact\n * failure this flag is not allowed to have. The separator is what stops\n * `services/pay` from claiming `services/payments/spec.md`.\n */\nfunction inside(path: string, dir: string): boolean {\n return dir !== \"\" && (path === dir || path.startsWith(`${dir}/`));\n}\n\n/** The fleet's own documents — the landscape target's subject, which owns no service directory. */\nconst ARCHITECTURE_DIR = \"architecture\";\n\n/**\n * Narrow a `--all` run to what changed since `ref`.\n *\n * Targets are matched through the ENUMERATIONS' own directories\n * (`ServiceEntry.dir`, `FeatureEntry.dir`), turned docs-relative by the same\n * `repoPath` the JSON contract uses. That is the whole reason the enumerations\n * are passed in rather than re-derived: they already know where each service\n * sits, at whatever depth, and asking them is what makes a filed fleet work.\n */\nexport async function scopeSince(request: ScopeRequest): Promise<ScopeOutcome> {\n const { docsDir, ref } = request;\n const base = await resolveBase(docsDir, ref);\n if (base.kind === \"no-git\") {\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `The docs repo at ${docsDir} is not somewhere git can answer from (${base.detail}) — ` +\n `--base narrows the run to what changed since a ref, which it reads with read-only git questions ` +\n `(rev-parse/diff/ls-files). Clone the docs repo with its history, then re-run — or drop --base to grade every target.`,\n };\n }\n if (base.kind === \"no-ref\") {\n return {\n kind: \"refused\",\n code: \"unknown-target\",\n message:\n `--base ${ref} does not resolve to a commit in the docs repo at ${docsDir}` +\n `${base.detail === \"\" ? \"\" : ` (${base.detail})`}. ` +\n `Pass a ref that exists there — main, origin/main, a tag or a commit sha — fetching first if it lives on the remote.`,\n };\n }\n\n const changed = await changedPaths(docsDir, base.commit, base.prefix);\n if (!Array.isArray(changed)) {\n // Git resolved the ref and then would not say what moved. There is no\n // partial answer to give: a scope built from half an answer grades a\n // subset nobody chose and reports it as the branch's whole footprint.\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `git could not list what changed since ${ref} in the docs repo at ${docsDir} (${changed.detail}) — ` +\n `without that list there is no honest scope, and a run over a guessed subset would report a green ` +\n `for targets nobody graded. Re-run once git can answer, or drop --base to grade every target.`,\n };\n }\n\n const dirOf = (abs: string): string => repoPath(docsDir, abs);\n return {\n kind: \"scope\",\n ref,\n commit: base.commit,\n // `architecture/` is the landscape target's whole directory: the fleet map,\n // the permission/capability/obligation vocabularies and the fleet ADRs all\n // live there, and every one of them is graded by the landscape target\n // rather than by any service.\n landscape: changed.some((path) => inside(path, ARCHITECTURE_DIR)),\n services: request.services.filter((svc) => changed.some((path) => inside(path, dirOf(svc.dir)))),\n features: request.features.filter((feat) => changed.some((path) => inside(path, dirOf(feat.dir)))),\n totals: { services: request.services.length, features: request.features.length },\n };\n}\n\n/**\n * The `scope` object the `--json` payload carries — the run saying out loud\n * what it looked at.\n *\n * Additive, and emitted only under `--base`, so no consumer of today's payload\n * moves. It exists because the alternative is a payload that is byte-shaped\n * like a whole-fleet run and is not one: `summary.services` would report two\n * where the fleet has twelve, `valid: true` would read as a claim about the\n * fleet, and nothing in the document would say otherwise.\n */\nexport function scopeJson(scope: ValidateScope): Record<string, unknown> {\n return {\n base: scope.ref,\n commit: scope.commit,\n landscape: scope.landscape,\n services: scope.services.map((svc) => svc.id),\n features: scope.features.map((feat) => feat.id),\n totals: scope.totals,\n };\n}\n\n/**\n * The one line both the text view and a reader of a CI log need: how much of\n * the fleet this run actually graded, and against what.\n *\n * The \"nothing was graded\" clause is the load-bearing half. A `--base` run with\n * no targets in scope has nothing to report and exits 0 — correctly, the branch\n * touched no governed boundary — and a 0 that reads like a green over the\n * system is the exact failure the docs-repo readiness gate exists to prevent.\n * So the sentence says it, rather than letting an empty report say nothing.\n */\nexport function scopeLine(scope: ValidateScope): string {\n const graded = scope.services.length + scope.features.length + (scope.landscape ? 1 : 0);\n return (\n `${scope.services.length} of ${scope.totals.services} services, ` +\n `${scope.features.length} of ${scope.totals.features} features in scope since ${scope.ref}` +\n (scope.landscape ? \", plus the landscape\" : \"\") +\n (graded === 0 ? \" — nothing was graded\" : \"\")\n );\n}\n"]}
1
+ {"version":3,"file":"changed-paths.js","sourceRoot":"","sources":["../../../../src/core/diff/scope/changed-paths.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which targets a base git ref puts in scope — the narrowing behind\n * `loam validate --all --base <ref>`.\n *\n * The defect this exists for: a partly-adopted fleet has no CI setting that is\n * both passing today and tightening over time. `--all` is green over eleven\n * undocumented services (they warn, and warnings do not gate) and `--strict` is\n * red from the first minute of adoption until the last boundary is written, so\n * a team installs either a gate that never notices adoption stalling at 8% or\n * one it cannot turn on. `--base` is the third setting: grade what this branch\n * TOUCHED, at full `--strict` severity if the caller wants it, and let the\n * untouched majority stay untouched.\n *\n * A SUB-PACKAGE of `core/diff/` rather than a sixth file in it, because that\n * directory is at the five-file limit and because this is a different subject\n * from the semantic diff beside it: `diff` compares two states of the fleet and\n * reports what moved, this one only decides WHICH SUBJECTS a run should look\n * at. Nothing here reads a document; the one question that has to — which call\n * edges on the fleet map are this branch's — is `./edges.ts`'s, and it reads\n * only the map documents the branch changed.\n *\n * What it does NOT do, and the line is not negotiable: a diff-scoped validate\n * cannot see a victim boundary the change did not touch. Removing an operation\n * from `payment-service` puts `payment-service` in scope and leaves\n * `checkout-web` — whose living requirement still names the operation — out of\n * it. That join is `loam diff --base`'s question and it stays `loam diff`'s;\n * the two gates answer \"did you document what you touched\" and \"did you break a\n * consumer\", and neither substitutes for the other. Selling a green here that\n * hides a broken join would be worse than shipping no flag at all.\n *\n * A join the branch WROTE is a different case, and it is graded: an edge the\n * branch added or edited on the fleet map is checked against its provider's\n * contract even with the provider out of scope (`edges` below), because the\n * edge is the document this branch touched and the provider is only where its\n * answer lives.\n */\nimport { declineDetail, gitText } from \"../../provenance/git.js\";\nimport { repoPath, type ErrorCode } from \"../../envelope/json.js\";\nimport { resolveBase } from \"../base-git.js\";\nimport { edgesSince } from \"./edges.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { FeatureEntry, ServiceEntry } from \"../../repo/entries.js\";\n\n/** Everything the narrowing needs: where to ask, what to ask about, and what there is to narrow. */\nexport interface ScopeRequest {\n docsDir: DocsDir;\n /** The ref as the caller spelled it — echoed in every message and in the payload. */\n ref: string;\n /** The run's full service enumeration, at whatever depth the tree walk found each one. */\n services: readonly ServiceEntry[];\n /** The run's full ACTIVE feature enumeration. */\n features: readonly FeatureEntry[];\n}\n\n/** The narrowed run: which targets the branch touched, and what it narrowed from. */\nexport interface ValidateScope {\n kind: \"scope\";\n ref: string;\n /** The 40-hex commit `ref` resolved to — what a rerun would have to pass to reproduce this scope. */\n commit: string;\n /** Did `architecture/` change? The landscape target is the fleet's own subject and has no directory of its own. */\n landscape: boolean;\n /**\n * Every docs-relative path that differs from the base, untracked files\n * included. Kept because the landscape target grades more files than the\n * map — the fleet vocabularies, AGENTS.md — and `touched` is how the run asks\n * which of them this branch changed.\n */\n changed: readonly string[];\n /**\n * The fleet-map call edges this branch introduced or changed, as\n * `./edges.ts`'s `edgeKey`s. Empty unless `architecture/` changed.\n */\n edges: ReadonlySet<string>;\n services: ServiceEntry[];\n features: FeatureEntry[];\n /** The denominators — what the run would have graded without `--base`. */\n totals: { services: number; features: number };\n}\n\n/**\n * A refusal, already spelled. The code is one `loam diff` already emits for the\n * same two conditions and the message is in the same register, deliberately: a\n * second stable code for \"that ref does not resolve\" would be a second thing to\n * document, branch on and keep in step, for a condition that is identical.\n *\n * The prose is built here rather than in the command because it is the same\n * sentence for every caller and because `base-git.ts`'s `detail` — git's own\n * words, bounded — is what makes it diagnosable; a command that only received a\n * `kind` would have to reinvent the sentence to say why.\n */\nexport interface ScopeRefusal {\n kind: \"refused\";\n code: ErrorCode;\n message: string;\n}\n\nexport type ScopeOutcome = ValidateScope | ScopeRefusal;\n\n/**\n * Every path that differs between `ref` and the WORKING TREE, docs-relative.\n *\n * Two questions, not one, and the second is not optional: `git diff` compares\n * the tree git tracks, so a service adopted five minutes ago — a brand new\n * `services/<id>/spec.md` nobody has committed — is invisible to it. `validate`\n * grades the working tree, so the scope has to be the working tree's, and an\n * untracked new boundary that the run then skipped would be the flag's very\n * first user reporting that it graded nothing.\n *\n * `--no-renames` on purpose: with rename detection on (git's default since\n * 2.9) a service moved into a subsystem reports only its DESTINATION, and the\n * checks that grade the fleet from the old placement would be out of scope.\n * Two paths for a move is the honest answer.\n *\n * `-z` because git quotes non-ASCII and space-bearing paths in line output —\n * the same reason `gitTrackedFiles` asks that way, and the same phantom\n * directory named `\"src` if it did not.\n */\nasync function changedPaths(docsDir: DocsDir, commit: string, prefix: string): Promise<string[] | { detail: string }> {\n // Both run from docsDir. `git diff` ignores cwd and answers for the whole\n // repository with root-relative paths; `git ls-files` restricts itself to\n // cwd, and `--full-name` makes ITS paths root-relative too — so one prefix\n // strip below is correct for both, exactly as `listBaseTree` needs one.\n const tracked = await gitText(docsDir, [\"diff\", \"--name-only\", \"--no-renames\", \"-z\", commit, \"--\"]);\n if (tracked.spawnError !== undefined || tracked.code !== 0 || tracked.overflowed) {\n return { detail: tracked.overflowed ? \"the changed-path listing exceeded the output cap.\" : declineDetail(tracked) };\n }\n const untracked = await gitText(docsDir, [\"ls-files\", \"--others\", \"--exclude-standard\", \"--full-name\", \"-z\"]);\n if (untracked.spawnError !== undefined || untracked.code !== 0 || untracked.overflowed) {\n return { detail: untracked.overflowed ? \"the untracked-path listing exceeded the output cap.\" : declineDetail(untracked) };\n }\n const seen = new Set<string>();\n for (const raw of [...tracked.out.split(\"\\0\"), ...untracked.out.split(\"\\0\")]) {\n // A docs repo checked out as a subdirectory of a larger repository answers\n // in the whole repository's paths; anything outside the docs dir is\n // somebody else's change and is not a target here.\n if (raw === \"\" || !raw.startsWith(prefix)) continue;\n const rel = raw.slice(prefix.length);\n if (rel !== \"\") seen.add(rel);\n }\n return [...seen].sort();\n}\n\n/**\n * Is `path` inside `dir`? Both docs-relative, forward-slashed.\n *\n * A prefix test on the directory plus a separator, never a split on \"/\" and a\n * read of the second segment: a service filed under a subsystem lives at\n * `services/platform/payment-service/`, not at `services/<id>/`, and a\n * path-splitting implementation drops every filed service out of scope\n * SILENTLY — the run stays green because it graded nothing, which is the exact\n * failure this flag is not allowed to have. The separator is what stops\n * `services/pay` from claiming `services/payments/spec.md`.\n */\nfunction inside(path: string, dir: string): boolean {\n return dir !== \"\" && (path === dir || path.startsWith(`${dir}/`));\n}\n\n/** Did this branch change `rel` — a docs-relative file, or anything under a docs-relative directory? */\nexport function touched(scope: ValidateScope, rel: string): boolean {\n return scope.changed.some((path) => inside(path, rel));\n}\n\n/** The fleet's own documents — the landscape target's subject, which owns no service directory. */\nconst ARCHITECTURE_DIR = \"architecture\";\n\n/**\n * Narrow a `--all` run to what changed since `ref`.\n *\n * Targets are matched through the ENUMERATIONS' own directories\n * (`ServiceEntry.dir`, `FeatureEntry.dir`), turned docs-relative by the same\n * `repoPath` the JSON contract uses. That is the whole reason the enumerations\n * are passed in rather than re-derived: they already know where each service\n * sits, at whatever depth, and asking them is what makes a filed fleet work.\n */\nexport async function scopeSince(request: ScopeRequest): Promise<ScopeOutcome> {\n const { docsDir, ref } = request;\n const base = await resolveBase(docsDir, ref);\n if (base.kind === \"no-git\") {\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `The docs repo at ${docsDir} is not somewhere git can answer from (${base.detail}) — ` +\n `--base narrows the run to what changed since a ref, which it reads with read-only git questions ` +\n `(rev-parse/diff/ls-files). Clone the docs repo with its history, then re-run — or drop --base to grade every target.`,\n };\n }\n if (base.kind === \"no-ref\") {\n return {\n kind: \"refused\",\n code: \"unknown-target\",\n message:\n `--base ${ref} does not resolve to a commit in the docs repo at ${docsDir}` +\n `${base.detail === \"\" ? \"\" : ` (${base.detail})`}. ` +\n `Pass a ref that exists there — main, origin/main, a tag or a commit sha — fetching first if it lives on the remote.`,\n };\n }\n\n const changed = await changedPaths(docsDir, base.commit, base.prefix);\n if (!Array.isArray(changed)) {\n // Git resolved the ref and then would not say what moved. There is no\n // partial answer to give: a scope built from half an answer grades a\n // subset nobody chose and reports it as the branch's whole footprint.\n return {\n kind: \"refused\",\n code: \"repository-unavailable\",\n message:\n `git could not list what changed since ${ref} in the docs repo at ${docsDir} (${changed.detail}) — ` +\n `without that list there is no honest scope, and a run over a guessed subset would report a green ` +\n `for targets nobody graded. Re-run once git can answer, or drop --base to grade every target.`,\n };\n }\n\n const dirOf = (abs: string): string => repoPath(docsDir, abs);\n // `architecture/` is the landscape target's whole directory: the fleet map,\n // the permission/capability/obligation vocabularies and the fleet ADRs all\n // live there, and every one of them is graded by the landscape target\n // rather than by any service.\n const architecture = changed.filter((path) => inside(path, ARCHITECTURE_DIR));\n return {\n kind: \"scope\",\n ref,\n commit: base.commit,\n landscape: architecture.length > 0,\n changed,\n edges: await edgesSince(\n { docsDir, commit: base.commit, prefix: base.prefix },\n architecture.filter((path) => path.endsWith(\".likec4\")),\n ),\n services: request.services.filter((svc) => changed.some((path) => inside(path, dirOf(svc.dir)))),\n features: request.features.filter((feat) => changed.some((path) => inside(path, dirOf(feat.dir)))),\n totals: { services: request.services.length, features: request.features.length },\n };\n}\n\n/**\n * The `scope` object the `--json` payload carries — the run saying out loud\n * what it looked at.\n *\n * Additive, and emitted only under `--base`, so no consumer of today's payload\n * moves. It exists because the alternative is a payload that is byte-shaped\n * like a whole-fleet run and is not one: `summary.services` would report two\n * where the fleet has twelve, `valid: true` would read as a claim about the\n * fleet, and nothing in the document would say otherwise.\n */\nexport function scopeJson(scope: ValidateScope): Record<string, unknown> {\n return {\n base: scope.ref,\n commit: scope.commit,\n landscape: scope.landscape,\n services: scope.services.map((svc) => svc.id),\n features: scope.features.map((feat) => feat.id),\n totals: scope.totals,\n };\n}\n\n/**\n * The one line both the text view and a reader of a CI log need: how much of\n * the fleet this run actually graded, and against what.\n *\n * The \"nothing was graded\" clause is the load-bearing half. A `--base` run with\n * no targets in scope has nothing to report and exits 0 — correctly, the branch\n * touched no governed boundary — and a 0 that reads like a green over the\n * system is the exact failure the docs-repo readiness gate exists to prevent.\n * So the sentence says it, rather than letting an empty report say nothing.\n */\nexport function scopeLine(scope: ValidateScope): string {\n const graded = scope.services.length + scope.features.length + (scope.landscape ? 1 : 0);\n return (\n `${scope.services.length} of ${scope.totals.services} services, ` +\n `${scope.features.length} of ${scope.totals.features} features in scope since ${scope.ref}` +\n (scope.landscape ? \", plus the landscape\" : \"\") +\n (graded === 0 ? \" — nothing was graded\" : \"\")\n );\n}\n"]}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Which fleet-map call edges a branch introduced — the one half of the `--base`
3
+ * narrowing that reads a document.
4
+ *
5
+ * The defect this exists for: `spine.op-undefined` is graded on the PROVIDER's
6
+ * service target, and an `architecture/` change only ever put the landscape in
7
+ * scope. So the hand edit the spine-first path is built on — naming the
8
+ * operation on a call edge — was graded by no gate a PR runs: README's own
9
+ * one-letter example (`authorisePayment` against a contract spelling it
10
+ * `authorizePayment`) merged green through `loam diff --base` and through
11
+ * `validate --all --base --strict`, on the adopted example fleet and on a
12
+ * freshly seeded one alike. This module answers the question that grade needs
13
+ * — which edges are this branch's — and `commands/validate/scope.ts` grades
14
+ * exactly those against their providers' contracts.
15
+ *
16
+ * AN EDGE IS ITS (source, target, op) TUPLE. A tuple present now and absent at
17
+ * the base is new or changed — a new edge, a re-pointed one, a renamed
18
+ * endpoint, an op named or re-spelled — and every one of those is a join the
19
+ * branch wrote. A tuple present at both ends is not, wherever in the file it
20
+ * moved and however it was re-indented: the key is what the edge MEANS, so a
21
+ * reformat grades nothing.
22
+ *
23
+ * TEXT, NOT A PARSE, at both ends. The base side would otherwise need a second
24
+ * LikeC4 workspace over bytes read out of git — the most expensive thing loam
25
+ * does, paid on every map PR to answer a question about a handful of lines.
26
+ * `scanModel` is the archive's splice map and resolves endpoints the way LikeC4
27
+ * does; `test/likec4-scan-parity.test.ts` pins the two readings together, and
28
+ * the key below is built from the same three fields the parsed `Rel` carries, so
29
+ * a scanned key names the edge the parsed map holds.
30
+ *
31
+ * ONLY THE DOCUMENTS THAT CHANGED are read, at both ends: an edge in an
32
+ * untouched document is by construction not this branch's, and an edge moved
33
+ * between two documents changes both, so its base key is still read.
34
+ *
35
+ * Which way a failure falls, and why: a base document git will not show — new
36
+ * on this branch, or a git that declines — contributes no base keys, so every
37
+ * edge in it reads as new and is graded. Grading an edge the branch did not
38
+ * write costs a reader one finding; skipping one it did write is the green this
39
+ * module exists to remove.
40
+ */
41
+ import { existsSync } from "node:fs";
42
+ import { readFile } from "node:fs/promises";
43
+ import { join } from "node:path";
44
+ import { scanModel } from "../../c4/source-scan.js";
45
+ import { inOrder } from "../../kernel/concurrency.js";
46
+ import { showBaseFile } from "../base-git.js";
47
+ /**
48
+ * One edge's identity as a string a `Set` can hold. JSON rather than a joined
49
+ * string because an element id may legally carry any separator a join would
50
+ * pick, and two different tuples must never spell one key.
51
+ */
52
+ export function edgeKey(edge) {
53
+ return JSON.stringify([edge.source, edge.target, edge.op ?? null]);
54
+ }
55
+ function keysOf(text) {
56
+ return text === null ? [] : (scanModel(text)?.rels ?? []).map(edgeKey);
57
+ }
58
+ /** The document as the base commit held it, or null when git will not show it (see the banner for why that is safe). */
59
+ async function baseText(base, rel) {
60
+ const read = await showBaseFile(base, rel);
61
+ return read.kind === "ok" ? read.bytes.toString("utf8") : null;
62
+ }
63
+ /** The document as the working tree holds it, or null when the branch deleted it. */
64
+ async function currentText(base, rel) {
65
+ const abs = join(base.docsDir, rel);
66
+ return existsSync(abs) ? readFile(abs, "utf8") : null;
67
+ }
68
+ /**
69
+ * The `edgeKey`s of every edge `documents` hold now and did not hold at the
70
+ * base. `documents` are docs-relative `.likec4` paths the branch changed —
71
+ * the caller's filter, so this module never has to know which directory the
72
+ * fleet map lives in.
73
+ */
74
+ export async function edgesSince(base, documents) {
75
+ const sides = await inOrder(documents, async (rel) => {
76
+ const [before, after] = await Promise.all([baseText(base, rel), currentText(base, rel)]);
77
+ return { before: keysOf(before), after: keysOf(after) };
78
+ });
79
+ const before = new Set(sides.flatMap((side) => side.before));
80
+ return new Set(sides.flatMap((side) => side.after).filter((key) => !before.has(key)));
81
+ }
82
+ //# sourceMappingURL=edges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edges.js","sourceRoot":"","sources":["../../../../src/core/diff/scope/edges.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,YAAY,EAAqB,MAAM,gBAAgB,CAAC;AASjE;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,IAAe;IACrC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,MAAM,CAAC,IAAmB;IACjC,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACzE,CAAC;AAED,wHAAwH;AACxH,KAAK,UAAU,QAAQ,CAAC,IAAkB,EAAE,GAAW;IACrD,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjE,CAAC;AAED,qFAAqF;AACrF,KAAK,UAAU,WAAW,CAAC,IAAkB,EAAE,GAAW;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACpC,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAkB,EAAE,SAA4B;IAC/E,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnD,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACzF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACxF,CAAC","sourcesContent":["/**\n * Which fleet-map call edges a branch introduced — the one half of the `--base`\n * narrowing that reads a document.\n *\n * The defect this exists for: `spine.op-undefined` is graded on the PROVIDER's\n * service target, and an `architecture/` change only ever put the landscape in\n * scope. So the hand edit the spine-first path is built on — naming the\n * operation on a call edge — was graded by no gate a PR runs: README's own\n * one-letter example (`authorisePayment` against a contract spelling it\n * `authorizePayment`) merged green through `loam diff --base` and through\n * `validate --all --base --strict`, on the adopted example fleet and on a\n * freshly seeded one alike. This module answers the question that grade needs\n * — which edges are this branch's — and `commands/validate/scope.ts` grades\n * exactly those against their providers' contracts.\n *\n * AN EDGE IS ITS (source, target, op) TUPLE. A tuple present now and absent at\n * the base is new or changed — a new edge, a re-pointed one, a renamed\n * endpoint, an op named or re-spelled — and every one of those is a join the\n * branch wrote. A tuple present at both ends is not, wherever in the file it\n * moved and however it was re-indented: the key is what the edge MEANS, so a\n * reformat grades nothing.\n *\n * TEXT, NOT A PARSE, at both ends. The base side would otherwise need a second\n * LikeC4 workspace over bytes read out of git — the most expensive thing loam\n * does, paid on every map PR to answer a question about a handful of lines.\n * `scanModel` is the archive's splice map and resolves endpoints the way LikeC4\n * does; `test/likec4-scan-parity.test.ts` pins the two readings together, and\n * the key below is built from the same three fields the parsed `Rel` carries, so\n * a scanned key names the edge the parsed map holds.\n *\n * ONLY THE DOCUMENTS THAT CHANGED are read, at both ends: an edge in an\n * untouched document is by construction not this branch's, and an edge moved\n * between two documents changes both, so its base key is still read.\n *\n * Which way a failure falls, and why: a base document git will not show — new\n * on this branch, or a git that declines — contributes no base keys, so every\n * edge in it reads as new and is graded. Grading an edge the branch did not\n * write costs a reader one finding; skipping one it did write is the green this\n * module exists to remove.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { scanModel } from \"../../c4/source-scan.js\";\nimport { inOrder } from \"../../kernel/concurrency.js\";\nimport { showBaseFile, type ResolvedBase } from \"../base-git.js\";\n\n/** The fields of an edge that make it the same join — shared by a parsed `Rel` and a scanned one. */\ninterface EdgeTuple {\n source: string;\n target: string;\n op?: string;\n}\n\n/**\n * One edge's identity as a string a `Set` can hold. JSON rather than a joined\n * string because an element id may legally carry any separator a join would\n * pick, and two different tuples must never spell one key.\n */\nexport function edgeKey(edge: EdgeTuple): string {\n return JSON.stringify([edge.source, edge.target, edge.op ?? null]);\n}\n\nfunction keysOf(text: string | null): string[] {\n return text === null ? [] : (scanModel(text)?.rels ?? []).map(edgeKey);\n}\n\n/** The document as the base commit held it, or null when git will not show it (see the banner for why that is safe). */\nasync function baseText(base: ResolvedBase, rel: string): Promise<string | null> {\n const read = await showBaseFile(base, rel);\n return read.kind === \"ok\" ? read.bytes.toString(\"utf8\") : null;\n}\n\n/** The document as the working tree holds it, or null when the branch deleted it. */\nasync function currentText(base: ResolvedBase, rel: string): Promise<string | null> {\n const abs = join(base.docsDir, rel);\n return existsSync(abs) ? readFile(abs, \"utf8\") : null;\n}\n\n/**\n * The `edgeKey`s of every edge `documents` hold now and did not hold at the\n * base. `documents` are docs-relative `.likec4` paths the branch changed —\n * the caller's filter, so this module never has to know which directory the\n * fleet map lives in.\n */\nexport async function edgesSince(base: ResolvedBase, documents: readonly string[]): Promise<ReadonlySet<string>> {\n const sides = await inOrder(documents, async (rel) => {\n const [before, after] = await Promise.all([baseText(base, rel), currentText(base, rel)]);\n return { before: keysOf(before), after: keysOf(after) };\n });\n const before = new Set(sides.flatMap((side) => side.before));\n return new Set(sides.flatMap((side) => side.after).filter((key) => !before.has(key)));\n}\n"]}
@@ -1,21 +1,30 @@
1
1
  /**
2
2
  * The diff computation: join the base fleet (`./base-state.ts`) against the
3
- * working tree (`./current-state.ts`) and say, per service, what changed in
3
+ * working tree (`core/consumers/current.ts`) and say, per service, what changed in
4
4
  * FLEET-MEANINGFUL terms — requirements by identity and digest, operations
5
- * and messages by their join tokens, cross-service joins by tuple — with the
6
- * current consumers of every removal named (`./victims.ts`).
5
+ * and messages by their join tokens and their `$ref`-closure digests,
6
+ * cross-service joins by tuple — with the authored consumers of every
7
+ * removal, deprecation and modification named (`core/consumers/consumers.ts`),
8
+ * and the cross-service joins that appeared or went away (`./joins.ts`).
7
9
  *
8
10
  * Two disciplines rule every branch here. Removal is graded against who still
9
11
  * depends on it: a removal nobody names is a warning, a removal the fleet
10
- * still joins to is an error and sets `breaking`. And "nobody could look" is
12
+ * still joins to is an error and sets `breaking`. A MODIFICATION is never an
13
+ * error, whoever is joined to it: whether a changed schema breaks its readers
14
+ * is a field-level question loam hands to oasdiff on purpose, so the finding
15
+ * names the consumers and the tool, and stops there. And "nobody could look" is
11
16
  * never "nothing changed": an unreadable artifact on EITHER side suspends
12
17
  * that subject's axis — recorded in `unreadable[]`, counted in the summary,
13
18
  * carried into the exit code by the command — instead of letting an
14
19
  * unreadable base read as everything-added or an unreadable working file
15
- * read as everything-removed.
20
+ * read as everything-removed. The one thing that is not a suspension is a
21
+ * contract the base held byte-for-byte: somebody COULD look, at the bytes,
22
+ * and they did not change (`comparable`, `UNTOUCHED_SKIPS`).
16
23
  */
17
24
  import { requirementDigest } from "../document/spec.js";
18
- import { consumerJoinChanges } from "./victims.js";
25
+ import { rawServiceId } from "../kernel/ids/service.js";
26
+ import { consumerStance, scanDetails } from "../consumers/scan.js";
27
+ import { consumerJoinChanges } from "./joins.js";
19
28
  /** Severity is a fact about the code, not a call-site choice — one table, no drift. */
20
29
  const DIFF_SEVERITY = {
21
30
  "diff.service-added": "ok",
@@ -27,9 +36,11 @@ const DIFF_SEVERITY = {
27
36
  "diff.op-removed": "warn",
28
37
  "diff.op-removed-consumed": "error",
29
38
  "diff.op-deprecated": "warn",
39
+ "diff.op-modified": "warn",
30
40
  "diff.message-added": "ok",
31
41
  "diff.message-removed": "warn",
32
42
  "diff.message-removed-consumed": "error",
43
+ "diff.message-modified": "warn",
33
44
  "diff.consumer-added": "ok",
34
45
  "diff.consumer-removed": "ok",
35
46
  };
@@ -37,15 +48,43 @@ function found(code, message, details) {
37
48
  const carry = details !== undefined && details.length > 0;
38
49
  return { severity: DIFF_SEVERITY[code], code, message, ...(carry ? { details } : {}) };
39
50
  }
40
- /** A side the service does not exist on: absence everywhere, honestly. */
51
+ /**
52
+ * A side the service does not exist on: absence everywhere, honestly. The id is
53
+ * a key of one of the two sides' maps, each keyed by a directory's leaf name.
54
+ */
41
55
  const ABSENT = { kind: "absent" };
42
- const emptyState = (id) => ({ id, dir: "", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });
56
+ const emptyState = (id) => ({ id: rawServiceId(id), dir: "", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });
57
+ /**
58
+ * The axes on which an unreadable document the branch left byte-identical is
59
+ * SKIPPED rather than suspended: the two contracts. Identical bytes are a
60
+ * proof, not a guess — the same parser over the same bytes cannot read
61
+ * differently on the two sides, so there is nothing of this document's own
62
+ * to diff, and suspending it anyway made every `loam diff` exit 1 over a
63
+ * fleet that holds one AsyncAPI 2.x file (unreadable by policy since
64
+ * core/asyncapi/read.ts refused pre-3.0), whatever the branch changed.
65
+ *
66
+ * Not the spec axes, and that is the victims scan's doing, not an oversight:
67
+ * an unreadable spec.md or arch.spec.md is also the document ANOTHER
68
+ * service's removal could not be checked against (`core/consumers/consumers.ts`'s
69
+ * `scanRequirements`), and its `unreadable[]` entry here is what carries that
70
+ * suspension to exit 1 (`opsDiff`'s removal branch says so). No scan reads a
71
+ * contract it could not parse as evidence of anything — `producers` takes
72
+ * positive evidence only — so skipping one hides no other answer.
73
+ */
74
+ const UNTOUCHED_SKIPS = new Set(["openapi", "asyncapi"]);
43
75
  /**
44
76
  * Guard one axis pair: record unreadable sides on the sink and say whether
45
77
  * the axis may be compared at all. Reading one good side against a suspended
46
- * one would grade absence it never proved.
78
+ * one would grade absence it never proved. A contract unreadable on BOTH
79
+ * sides with one byte hash is the branch's untouched file: not compared and
80
+ * not recorded either (`UNTOUCHED_SKIPS`). A changed one, or one only one
81
+ * side cannot read, still suspends.
47
82
  */
48
83
  function comparable(sink, axis, pair) {
84
+ const { base, current } = pair;
85
+ if (UNTOUCHED_SKIPS.has(axis) && base.kind === "unreadable" && current.kind === "unreadable" &&
86
+ base.bytes !== undefined && base.bytes === current.bytes)
87
+ return false;
49
88
  for (const side of ["base", "current"]) {
50
89
  const state = pair[side];
51
90
  if (state.kind === "unreadable")
@@ -92,6 +131,13 @@ function requirementsDiff(sink, pair) {
92
131
  sink.findings.push(found("diff.requirement-removed", `living requirement removed since base: '${reqLabel(r)}'`));
93
132
  }
94
133
  }
134
+ /**
135
+ * The second half of every modification message. loam joins consumers to the
136
+ * thing that moved; it does not read what a schema SAYS (core/brief/unchecked.ts
137
+ * lists that as unchecked), so a breaking rename and a new optional field are
138
+ * the same finding here — the specialist diff tells them apart.
139
+ */
140
+ const notGraded = (tool) => `loam does not grade whether the change is breaking — run ${tool} on the two states of this contract`;
95
141
  async function opsDiff(sink, pair, victims) {
96
142
  if (!comparable(sink, "openapi", { base: pair.base.openapi, current: pair.current.openapi }))
97
143
  return;
@@ -104,11 +150,17 @@ async function opsDiff(sink, pair, victims) {
104
150
  }
105
151
  else if (!before.deprecated && op.deprecated) {
106
152
  const scan = await victims.opConsumers(sink.id, id);
107
- // Three honest phrasings — never a confident negative over a scan that skipped an unreadable consumer document.
108
- const stance = scan.victims.length > 0 ? `${scan.victims.length} current consumer(s) still name it`
109
- : scan.suspended.length > 0 ? "whether current consumers name it could not be fully answered"
110
- : "no current consumer names it";
111
- sink.findings.push(found("diff.op-deprecated", `operation '${id}' is deprecated since base — ${stance}`, [...scan.victims, ...scan.suspended]));
153
+ sink.findings.push(found("diff.op-deprecated", `operation '${id}' is deprecated since base — ${consumerStance(scan)}`, scanDetails(scan)));
154
+ }
155
+ else if (before.closureDigest !== op.closureDigest || before.path !== op.path || before.method !== op.method) {
156
+ // `else`, because setting the flag moves the digest by itself (it is a
157
+ // key of the node): a deprecation already names the same consumers, and
158
+ // a second finding over them would say nothing new. The digest covers
159
+ // the `$ref` closure — a field renamed in a shared component is exactly
160
+ // the change this code exists for, and the operation's own bytes never
161
+ // move for it. A relocated slot is a change too, though the node is not.
162
+ const scan = await victims.opConsumers(sink.id, id);
163
+ sink.findings.push(found("diff.op-modified", `operation '${id}' (${op.method.toUpperCase()} ${op.path}) changed since base, in its own definition or a schema it references — ${consumerStance(scan)}; ${notGraded("`oasdiff breaking`")}`, scanDetails(scan)));
112
164
  }
113
165
  }
114
166
  for (const [id] of [...base].sort(byKey)) {
@@ -116,16 +168,18 @@ async function opsDiff(sink, pair, victims) {
116
168
  continue;
117
169
  const scan = await victims.opConsumers(sink.id, id);
118
170
  if (scan.victims.length > 0) {
119
- sink.findings.push(found("diff.op-removed-consumed", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, [...scan.victims, ...scan.suspended]));
171
+ sink.findings.push(found("diff.op-removed-consumed", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, scanDetails(scan)));
120
172
  }
121
173
  else if (scan.suspended.length > 0) {
122
- // "Nobody could be scanned" is not "nobody names it" — the consumer's own
123
- // unreadable[] entry already carries the run to exit 1; this message must
124
- // not assert a negative the scan never proved.
125
- sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));
174
+ // "Nobody could be checked" is not "nobody names it", so this message
175
+ // must not assert a negative the scan never proved. An unreadable
176
+ // consumer document carries the run to exit 1 through its own
177
+ // unreadable[] entry; a caller drawn with no op does not — it stays this
178
+ // warning, so a seeded, op-less map does not fail every removal.
179
+ sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} possible consumer(s) could not be checked`, scanDetails(scan)));
126
180
  }
127
181
  else {
128
- sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; no current landscape edge or living requirement names it`));
182
+ sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; no current edge, requirement or use-case hop names it`));
129
183
  }
130
184
  }
131
185
  }
@@ -134,33 +188,46 @@ async function messagesDiff(sink, pair, victims) {
134
188
  return;
135
189
  const base = firstWins(valueOr(pair.base.asyncapi), (m) => m.name);
136
190
  const current = firstWins(valueOr(pair.current.asyncapi), (m) => m.name);
137
- for (const [name] of [...current].sort(byKey)) {
138
- if (!base.has(name))
191
+ for (const [name, message] of [...current].sort(byKey)) {
192
+ const before = base.get(name);
193
+ if (before === undefined) {
139
194
  sink.findings.push(found("diff.message-added", `message '${name}' added since base`));
195
+ }
196
+ else if (before.closureDigest !== message.closureDigest) {
197
+ const scan = await victims.messageConsumers(sink.id, name);
198
+ sink.findings.push(found("diff.message-modified", `message '${name}' changed since base, in its declaration or a schema its payload references — ${consumerStance(scan)}; ${notGraded("`asyncapi diff`")}`, scanDetails(scan)));
199
+ }
140
200
  }
141
201
  for (const [name] of [...base].sort(byKey)) {
142
202
  if (current.has(name))
143
203
  continue;
144
- // Fleet-global join: a name another current service still declares is not
145
- // orphaned (positive evidence only — an unreadable contract never vouches).
146
- const elsewhere = victims.declarers(name).filter((d) => d !== sink.id);
147
- if (elsewhere.length > 0) {
148
- sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; still declared by ${elsewhere.join(", ")}`));
204
+ // Fleet-global join: a name another current service still SENDS is not
205
+ // orphaned (positive evidence only — an unreadable contract never vouches,
206
+ // and a service that only receives it is one of its consumers, never its
207
+ // producer: `producers` says why that distinction is the gate's too). Nor
208
+ // is one an #external element still publishes, by validate's own rule
209
+ // (`externalProducer` says why): counting the fleet's senders alone made
210
+ // this removal an error that the next `validate --all` called clean.
211
+ const elsewhere = (await victims.producers(name)).filter((d) => d !== sink.id);
212
+ const outside = await victims.externalProducer(name);
213
+ const senders = outside === null ? elsewhere : [...elsewhere, `${outside} (#external)`];
214
+ if (senders.length > 0) {
215
+ sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; still sent by ${senders.join(", ")}`));
149
216
  continue;
150
217
  }
151
218
  const scan = await victims.messageConsumers(sink.id, name);
152
219
  if (scan.victims.length > 0) {
153
- sink.findings.push(found("diff.message-removed-consumed", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, [...scan.victims, ...scan.suspended]));
220
+ sink.findings.push(found("diff.message-removed-consumed", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, scanDetails(scan)));
154
221
  }
155
222
  else if (scan.suspended.length > 0) {
156
- sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));
223
+ sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scanDetails(scan)));
157
224
  }
158
225
  else {
159
226
  sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; nothing currently consumes it`));
160
227
  }
161
228
  }
162
229
  }
163
- /** The graded join changes (`victims.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */
230
+ /** The graded join changes (`./joins.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */
164
231
  function consumerFindings(input) {
165
232
  const changes = consumerJoinChanges({
166
233
  base: input.base.services,
@@ -233,7 +300,9 @@ export async function computeDiff(input) {
233
300
  summary: {
234
301
  added: count((f) => f.code.endsWith("-added")),
235
302
  removed: count((f) => f.code.endsWith("-removed") || f.code.endsWith("-removed-consumed")),
236
- modified: count((f) => f.code === "diff.requirement-modified"),
303
+ // Requirements, operations and messages alike: a reader scanning the one
304
+ // summary line must not see "0 modified" over a PR that renamed a field.
305
+ modified: count((f) => f.code.endsWith("-modified")),
237
306
  deprecated: count((f) => f.code === "diff.op-deprecated"),
238
307
  unreadable: services.reduce((n, s) => n + s.unreadable.length, 0) +
239
308
  services.filter((s) => s.ambiguous !== undefined).length,