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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"pin.js","sourceRoot":"","sources":["../../../../src/core/asyncapi/merge/pin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pin that tells a QUOTE from an EDIT, on the event axis.\n *\n * A feature's asyncapi.yaml is a complete document, so most of what it spells\n * is quotation: the author restated the living contract around the slot they\n * were changing. Without a pin the merge cannot tell the two apart and\n * upserts both — the exact silent-revert the OpenAPI axis's pin closed, with\n * no overlap between two features required, just two deltas over one service.\n *\n * Separate from `./merge.ts` because this is `loam rebase`'s half: it plans\n * the pins a delta is missing, while the merge only ever reads pins already\n * written. Slot-keyed over the three named sections exactly as the merge\n * upserts (core/asyncapi/digest.ts owns the walker and the section→path\n * spelling); an inline channel message is channel-slot interior and is never\n * pinned on its own — SCHEMA.md's recorded decision.\n */\nimport { isMap, parseDocument } from \"yaml\";\nimport { AsyncapiMergeError, errorMessage } from \"./error.js\";\nimport { refuseAmbiguousSlotKeys, slotPair } from \"./markers.js\";\nimport {\n ASYNCAPI_BASELINE_KEY,\n asyncapiSlots,\n slotDigest,\n type AsyncapiSection,\n} from \"../digest.js\";\n\n/** What happened to one slot's `x-loam-based-on`. */\nexport interface AsyncapiSlotPin {\n section: AsyncapiSection;\n key: string;\n status:\n /** It had no pin and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no slot at this (section, key): this feature is adding it. */\n | \"unresolved\"\n /** Written as a YAML alias (one shared value backs every use) or under a key no string can address — loam will not stamp through either. */\n | \"unwritable\";\n from: string | null;\n to: string | null;\n}\n\nexport interface AsyncapiPinPlan {\n /** The rewritten contract, or null when no pin changes. */\n text: string | null;\n pins: AsyncapiSlotPin[];\n}\n\n/**\n * Pin every slot in a feature contract against the living one — what\n * `loam rebase` writes on this axis.\n *\n * The pin is always `slotDigest` of the LIVING slot, never of the delta's\n * own, and that single rule produces both merge verdicts by itself: a slot\n * the author only QUOTED is byte-equal to living, so its pin equals its own\n * content and classifies as a quote; one the author EDITED differs from its\n * pin, so the merge writes it. A slot the living document lacks is\n * `unresolved` and correct — there is no living version of a channel,\n * operation or message the contract does not carry yet.\n */\nexport function pinAsyncapiSlots(featureText: string, livingText: string, service: string): AsyncapiPinPlan {\n const doc = parseDocument(featureText);\n if (doc.errors.length > 0) throw new AsyncapiMergeError(\"feature\", service, doc.errors[0]!.message);\n refuseAmbiguousSlotKeys(doc, \"feature\", service);\n let featPlain: unknown;\n let livingPlain: unknown;\n try {\n featPlain = doc.toJS() ?? {};\n } catch (error) {\n throw new AsyncapiMergeError(\"feature\", service, errorMessage(error));\n }\n try {\n const parsed = parseDocument(livingText);\n if (parsed.errors.length > 0) throw new AsyncapiMergeError(\"living\", service, parsed.errors[0]!.message);\n refuseAmbiguousSlotKeys(parsed, \"living\", service);\n livingPlain = parsed.toJS() ?? {};\n } catch (error) {\n if (error instanceof AsyncapiMergeError) throw error;\n throw new AsyncapiMergeError(\"living\", service, errorMessage(error));\n }\n\n const livingBySlot = new Map(asyncapiSlots(livingPlain).map((slot) => [`${slot.section}\\0${slot.key}`, slot]));\n const pins: AsyncapiSlotPin[] = [];\n let edited = false;\n for (const slot of asyncapiSlots(featPlain)) {\n // A removal marker asserts a slot rather than restating it, so it has\n // nothing to be based on; `asyncapi.remove-target-missing/-mismatch`\n // are what guard it against a living slot that moved under it.\n if (slot.remove) continue;\n\n const at = { section: slot.section, key: slot.key };\n const from = slot.basedOn ?? null;\n const living = livingBySlot.get(`${slot.section}\\0${slot.key}`);\n if (living === undefined || living.node === null || typeof living.node !== \"object\" || Array.isArray(living.node)) {\n pins.push({ ...at, status: \"unresolved\", from, to: null });\n continue;\n }\n const digest = slotDigest(living.node);\n if (from === digest) {\n pins.push({ ...at, status: \"unchanged\", from: digest, to: digest });\n continue;\n }\n // Located string-matched through the same door as every other slot\n // access (markers.ts's slotPair): a channel written `404:` is the YAML\n // number 404, which a raw `getIn(\"404\")` misses — the miss used to grade\n // every non-string-keyed slot `unwritable`, with an alias diagnosis\n // nothing in the document backed, and the gate's own remedy was the\n // command that had just refused. What stays refused: an aliased slot\n // value (one shared value backs every use of an anchor, so stamping\n // through it would pin every other use too — the OpenAPI pin's refusal,\n // for the same data-loss reason) and a slot whose key is not a scalar,\n // which no string can address.\n const pair = slotPair(doc, slot.section, slot.key);\n if (pair === undefined || !isMap(pair.value)) {\n pins.push({ ...at, status: \"unwritable\", from, to: null });\n continue;\n }\n pins.push({ ...at, status: from === null ? \"pinned\" : \"repinned\", from, to: digest });\n pair.value.set(ASYNCAPI_BASELINE_KEY, digest);\n edited = true;\n }\n\n if (!edited) return { text: null, pins };\n try {\n return { text: doc.toString(), pins };\n } catch (error) {\n throw new AsyncapiMergeError(\"feature\", service, errorMessage(error));\n }\n}\n"]}
1
+ {"version":3,"file":"pin.js","sourceRoot":"","sources":["../../../../src/core/asyncapi/merge/pin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pin that tells a QUOTE from an EDIT, on the event axis.\n *\n * A feature's asyncapi.yaml is a complete document, so most of what it spells\n * is quotation: the author restated the living contract around the slot they\n * were changing. Without a pin the merge cannot tell the two apart and\n * upserts both — the exact silent-revert the OpenAPI axis's pin closed, with\n * no overlap between two features required, just two deltas over one service.\n *\n * Separate from `./merge.ts` because this is `loam rebase`'s half: it plans\n * the pins a delta is missing, while the merge only ever reads pins already\n * written. Slot-keyed over the three named sections exactly as the merge\n * upserts (core/asyncapi/digest.ts owns the walker and the section→path\n * spelling); an inline channel message is channel-slot interior and is never\n * pinned on its own — SCHEMA.md's recorded decision.\n */\nimport { isMap } from \"yaml\";\nimport { writeTreeOver } from \"../../openapi/contract-tree.js\";\nimport { asyncapiRefusal } from \"./error.js\";\nimport { readEventTree, slotPair } from \"./markers.js\";\nimport {\n ASYNCAPI_BASELINE_KEY,\n asyncapiSlots,\n slotDigest,\n slotKey,\n slotsByKey,\n type AsyncapiSection,\n} from \"../digest.js\";\n\n/** What happened to one slot's `x-loam-based-on`. */\nexport interface AsyncapiSlotPin {\n section: AsyncapiSection;\n key: string;\n status:\n /** It had no pin and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no slot at this (section, key): this feature is adding it. */\n | \"unresolved\"\n /** Written as a YAML alias (one shared value backs every use) or under a key no string can address — loam will not stamp through either. */\n | \"unwritable\";\n from: string | null;\n to: string | null;\n}\n\nexport interface AsyncapiPinPlan {\n /** The rewritten contract, or null when no pin changes. */\n text: string | null;\n pins: AsyncapiSlotPin[];\n}\n\n/**\n * Pin every slot in a feature contract against the living one — what\n * `loam rebase` writes on this axis.\n *\n * The pin is always `slotDigest` of the LIVING slot, never of the delta's\n * own, and that single rule produces both merge verdicts by itself: a slot\n * the author only QUOTED is byte-equal to living, so its pin equals its own\n * content and classifies as a quote; one the author EDITED differs from its\n * pin, so the merge writes it. A slot the living document lacks is\n * `unresolved` and correct — there is no living version of a channel,\n * operation or message the contract does not carry yet.\n */\nexport function pinAsyncapiSlots(featureText: string, livingText: string, service: string): AsyncapiPinPlan {\n const { doc, plain: featPlain } = readEventTree(featureText, \"feature\", service);\n const { plain: livingPlain } = readEventTree(livingText, \"living\", service);\n\n const livingBySlot = slotsByKey(asyncapiSlots(livingPlain));\n const pins: AsyncapiSlotPin[] = [];\n let edited = false;\n for (const slot of asyncapiSlots(featPlain)) {\n // A removal marker asserts a slot rather than restating it, so it has\n // nothing to be based on; `asyncapi.remove-target-missing/-mismatch`\n // are what guard it against a living slot that moved under it.\n if (slot.remove) continue;\n\n const at = { section: slot.section, key: slot.key };\n const from = slot.basedOn ?? null;\n const living = livingBySlot.get(slotKey(slot));\n if (living === undefined || living.node === null || typeof living.node !== \"object\" || Array.isArray(living.node)) {\n pins.push({ ...at, status: \"unresolved\", from, to: null });\n continue;\n }\n const digest = slotDigest(living.node);\n if (from === digest) {\n pins.push({ ...at, status: \"unchanged\", from: digest, to: digest });\n continue;\n }\n // Located string-matched through the same door as every other slot\n // access (markers.ts's slotPair): a channel written `404:` is the YAML\n // number 404, which a raw `getIn(\"404\")` misses — the miss used to grade\n // every non-string-keyed slot `unwritable`, with an alias diagnosis\n // nothing in the document backed, and the gate's own remedy was the\n // command that had just refused. What stays refused: an aliased slot\n // value (one shared value backs every use of an anchor, so stamping\n // through it would pin every other use too — the OpenAPI pin's refusal,\n // for the same data-loss reason) and a slot whose key is not a scalar,\n // which no string can address.\n const pair = slotPair(doc, slot.section, slot.key);\n if (pair === undefined || !isMap(pair.value)) {\n pins.push({ ...at, status: \"unwritable\", from, to: null });\n continue;\n }\n pins.push({ ...at, status: from === null ? \"pinned\" : \"repinned\", from, to: digest });\n pair.value.set(ASYNCAPI_BASELINE_KEY, digest);\n edited = true;\n }\n\n if (!edited) return { text: null, pins };\n // Over the author's text, so the pins are the only lines this run changes\n // (`writeTreeOver` says what re-serializing cost).\n return { text: writeTreeOver(doc, featureText, asyncapiRefusal(\"feature\", service)), pins };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/core/asyncapi/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG","sourcesContent":["/**\n * The read model of one AsyncAPI document — what `./read.ts`'s walk produces\n * and every consumer of the axis branches on. Split from `read.ts` when the\n * `asyncapiFromText` extraction pushed that module past the 300-line ceiling:\n * the walk and its result are two subjects (`read.ts` says HOW a document is\n * read, this file says WHAT a read answers), and the type block is the seam\n * that already existed. The doctrine comments travel with the fields they\n * explain — they are the documentation of the model, not of the walk.\n */\n\n/** One message declaration, as the reader sees it. */\nexport interface EventMessage {\n /**\n * The join token — what a landscape edge's `metadata { publishes }` and a\n * requirement's `Publishes:` line name.\n *\n * The Message Object's own `name` when it has one, otherwise the key it is\n * declared under. Both are legal AsyncAPI, and the key is what an author who\n * never wrote a `name` would reasonably expect to reference; falling back to\n * it means a minimal hand-written contract still joins to the spine.\n */\n name: string;\n /**\n * Where it was declared — `components.messages.<key>` or\n * `channels.<key>.messages.<key>`. Carried so `asyncapi.duplicate-message` can\n * name both slots instead of only saying that a name repeats, the way\n * `openapi.duplicate-operationid` names both `METHOD /path` slots.\n */\n slot: string;\n /**\n * Present when the declaration's payload defines no shape a consumer could\n * code against (`payloadUndeclared`, depth.ts). Set only when true, and\n * never set for a payload declaring a non-JSON `schemaFormat`.\n */\n payloadEmpty?: true;\n /**\n * Present when the declaration carries `x-loam-remove: true` — a FEATURE\n * delta retiring it. A marker asserts a slot rather than declaring a\n * message, so it joins nothing: excluded from sent/received and from\n * duplicate counting, the discipline `core/openapi/doc.ts` set for\n * operation markers. Set only when true, like `payloadEmpty`.\n */\n remove?: true;\n}\n\n/** The parse of one AsyncAPI document: what it declares, and whether it could be read at all. */\nexport interface AsyncapiDoc {\n /** Every message declaration in the document, in document order. */\n messages: EventMessage[];\n /** Message names reachable from an `action: send` operation — what this service PRODUCES. */\n sent: string[];\n /** Message names reachable from an `action: receive` operation — what this service CONSUMES. */\n received: string[];\n /**\n * Names declared in more than one slot. Every join on this axis is by name, so\n * two slots claiming one name is the same ambiguity `openapi.duplicate-operationid`\n * grades: the join picks one arbitrarily and the other declaration is invisible.\n */\n duplicateNames: string[];\n /**\n * True when the file EXISTS but cannot be read as an AsyncAPI document —\n * broken YAML, non-UTF-8 bytes, or a document that is not a mapping. A missing\n * file is not unreadable (absence is `service.no-asyncapi`'s question), and an\n * empty one parses to null and honestly declares nothing.\n */\n unreadable: boolean;\n /** The parser's own message, when there is one to quote back. */\n error?: string;\n /** Internal `#/` refs that resolve to nothing in this document (depth.ts). */\n danglingRefs: string[];\n /**\n * Where `x-loam-remove: true` appears — at ANY depth (digest.ts's\n * `removalMarkerPaths`): the three slot depths the format spec gives the\n * key meaning at, inline channel messages (channel-slot interior the merge\n * strips at that nested depth), and every place the key means nothing —\n * the document root, `info`, a `components` sibling. Feature-only\n * bookkeeping wherever it sits: `validate` grades any of these in a LIVING\n * contract as `asyncapi.remove-marker-living`, and the sweep is as deep as\n * the strip so a leaked marker can never be invisible to it.\n */\n markers: string[];\n}\n"]}
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/core/asyncapi/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG","sourcesContent":["/**\n * The read model of one AsyncAPI document — what `./read.ts`'s walk produces\n * and every consumer of the axis branches on. Split from `read.ts` when the\n * `asyncapiFromText` extraction pushed that module past the 300-line ceiling:\n * the walk and its result are two subjects (`read.ts` says HOW a document is\n * read, this file says WHAT a read answers), and the type block is the seam\n * that already existed. The doctrine comments travel with the fields they\n * explain — they are the documentation of the model, not of the walk.\n */\n\n/** One message declaration, as the reader sees it. */\nexport interface EventMessage {\n /**\n * The join token — what a landscape edge's `metadata { publishes }` and a\n * requirement's `Publishes:` line name.\n *\n * The Message Object's own `name` when it has one, otherwise the key it is\n * declared under. Both are legal AsyncAPI, and the key is what an author who\n * never wrote a `name` would reasonably expect to reference; falling back to\n * it means a minimal hand-written contract still joins to the spine.\n */\n name: string;\n /**\n * Where it was declared — `components.messages.<key>` or\n * `channels.<key>.messages.<key>`. Carried so `asyncapi.duplicate-message` can\n * name both slots instead of only saying that a name repeats, the way\n * `openapi.duplicate-operationid` names both `METHOD /path` slots.\n */\n slot: string;\n /**\n * The declaration plus every internal `$ref` it reaches, transitively — the\n * OpenAPI axis's `closureDigester` (core/openapi/digest.ts), so a payload\n * schema factored into `components.schemas` still moves it. Content equality\n * only, and so inside the stance read.ts states: what `loam diff` compares to\n * say a message changed, never a join on a payload field. Absent on a removal\n * marker, which declares nothing to compare.\n */\n closureDigest?: string;\n /**\n * Present when the declaration's payload defines no shape a consumer could\n * code against (`payloadUndeclared`, depth.ts). Set only when true, and\n * never set for a payload declaring a non-JSON `schemaFormat`.\n */\n payloadEmpty?: true;\n /**\n * Present when the declaration carries `x-loam-remove: true` — a FEATURE\n * delta retiring it. A marker asserts a slot rather than declaring a\n * message, so it joins nothing: excluded from sent/received and from\n * duplicate counting, the discipline `core/openapi/doc.ts` set for\n * operation markers. Set only when true, like `payloadEmpty`.\n */\n remove?: true;\n}\n\n/** The parse of one AsyncAPI document: what it declares, and whether it could be read at all. */\nexport interface AsyncapiDoc {\n /** Every message declaration in the document, in document order. */\n messages: EventMessage[];\n /** Message names reachable from an `action: send` operation — what this service PRODUCES. */\n sent: string[];\n /** Message names reachable from an `action: receive` operation — what this service CONSUMES. */\n received: string[];\n /**\n * Names declared in more than one slot. Every join on this axis is by name, so\n * two slots claiming one name is the same ambiguity `openapi.duplicate-operationid`\n * grades: the join picks one arbitrarily and the other declaration is invisible.\n */\n duplicateNames: string[];\n /**\n * True when the file EXISTS but cannot be read as an AsyncAPI document —\n * broken YAML, non-UTF-8 bytes, or a document that is not a mapping. A missing\n * file is not unreadable (absence is `service.no-asyncapi`'s question), and an\n * empty one parses to null and honestly declares nothing.\n */\n unreadable: boolean;\n /** The parser's own message, when there is one to quote back. */\n error?: string;\n /** Internal `#/` refs that resolve to nothing in this document (depth.ts). */\n danglingRefs: string[];\n /**\n * Where `x-loam-remove: true` appears — at ANY depth (digest.ts's\n * `removalMarkerPaths`): the three slot depths the format spec gives the\n * key meaning at, inline channel messages (channel-slot interior the merge\n * strips at that nested depth), and every place the key means nothing —\n * the document root, `info`, a `components` sibling. Feature-only\n * bookkeeping wherever it sits: `validate` grades any of these in a LIVING\n * contract as `asyncapi.remove-marker-living`, and the sweep is as deep as\n * the strip so a leaked marker can never be invisible to it.\n */\n markers: string[];\n}\n"]}
@@ -3,7 +3,8 @@ import { existsSync } from "node:fs";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { parse } from "yaml";
5
5
  import { danglingRefs, deref, payloadUndeclared } from "./depth.js";
6
- import { isSlotRemoval, removalMarkerPaths } from "./digest.js";
6
+ import { entriesOf, isSlotRemoval, removalMarkerPaths } from "./digest.js";
7
+ import { closureDigester } from "../openapi/digest.js";
7
8
  /**
8
9
  * The async contract axis: AsyncAPI 3.0, read the way `core/openapi/doc.ts` reads
9
10
  * OpenAPI — a shallow structural walk over the `yaml` parse, never a real
@@ -15,9 +16,20 @@ import { isSlotRemoval, removalMarkerPaths } from "./digest.js";
15
16
  * `action: send|receive`, which is the exact analog of an `operationId`; 2.x
16
17
  * spells the same thing as `publish`/`subscribe` nested under a channel, where
17
18
  * the two words are notoriously ambiguous about whose perspective they take.
18
- * Supporting one dialect is cheaper than supporting two, and a 2.x document
19
- * declares no `operations` at all — so it reads as a contract with no messages
20
- * rather than as a silently mis-parsed one.
19
+ * Supporting one dialect is cheaper than supporting two. A 2.x (or 1.x)
20
+ * document declares no `operations` at all, and it used to read as a contract
21
+ * with no messages: one `asyncapi.yaml` copied in from an older service turned
22
+ * into a `spine.message-undefined` per landscape edge, a
23
+ * `spec-event.message-undefined` per requirement and a knock-on
24
+ * `spine.message-unproduced` on every consumer, none of them naming the
25
+ * version and every fix text saying "declare the operation" about a file that
26
+ * does declare it, as `publish`/`subscribe`. That is exactly the false
27
+ * diagnosis the unreadable policy below exists to prevent, so an older
28
+ * version now reads as UNREADABLE (`asyncapiDocOf`): one `asyncapi.invalid`
29
+ * naming the version and the upgrade command, and nothing graded against it.
30
+ * `loam diff` skips such a document when the base ref holds the same bytes
31
+ * (core/diff/semantic.ts's `UNTOUCHED_SKIPS`); without that, one unconverted
32
+ * file suspended the event axis on every run and failed every PR's diff.
21
33
  *
22
34
  * WHAT THIS READER MUST NEVER DO: join on anything inside a message's `payload`.
23
35
  * The payload is JSON Schema today and may be Avro later, and that switch is
@@ -32,7 +44,9 @@ import { isSlotRemoval, removalMarkerPaths } from "./digest.js";
32
44
  * slot digests (./digest.ts) are COMPATIBLE with it, not an exception: they
33
45
  * hash payload bytes as content equality — a changed payload moves the slot's
34
46
  * identity — and content equality is not a join; nothing a digest reads ever
35
- * contributes a name to the spine.
47
+ * contributes a name to the spine. The per-message `closureDigest` `loam diff`
48
+ * compares is the same kind of read followed through `$ref`s: it hashes what
49
+ * the payload points at, transitively, and still never names a field.
36
50
  */
37
51
  /*
38
52
  * The `EventMessage`/`AsyncapiDoc` read model lives in `./model.ts` — split
@@ -54,12 +68,6 @@ export function messageName(node, key) {
54
68
  }
55
69
  return key;
56
70
  }
57
- /** Entries of a mapping node, or nothing — every structural read here goes through it. */
58
- function entriesOf(node) {
59
- if (node === null || typeof node !== "object" || Array.isArray(node))
60
- return [];
61
- return Object.entries(node);
62
- }
63
71
  /**
64
72
  * Read an AsyncAPI 3.0 document into the slice loam joins on: the messages it
65
73
  * declares, and which of them it sends and receives.
@@ -125,8 +133,24 @@ export function asyncapiDocOf(doc) {
125
133
  if (doc === null)
126
134
  return empty();
127
135
  const root = doc;
136
+ // A pre-3.0 document is not a 3.0 document with nothing in it, so it takes
137
+ // the unreadable path rather than the empty one (see the module header).
138
+ // Only a version that SAYS 1.x or 2.x is refused: a file with no `asyncapi:`
139
+ // key at all (a hand-trimmed feature delta that restates slots and not the
140
+ // header) keeps reading as 3.0, and so does any later major, which is a
141
+ // question for the day it exists rather than a guess made now. A bare `2`
142
+ // or `2.6` parses as a YAML number, hence the String() and the `$`.
143
+ const version = root["asyncapi"];
144
+ if ((typeof version === "string" || typeof version === "number") && /^[12](\.|$)/.test(String(version))) {
145
+ return {
146
+ ...empty(),
147
+ unreadable: true,
148
+ error: `AsyncAPI ${String(version)}: loam reads AsyncAPI 3.0 only; \`asyncapi convert --target-version=3.0.0\` upgrades it`,
149
+ };
150
+ }
128
151
  const messages = [];
129
152
  const seen = new Map();
153
+ const closureDigest = closureDigester(root);
130
154
  const declare = (name, slot, node) => {
131
155
  // A declaration carrying the removal marker asserts a slot rather than
132
156
  // declaring a message: it is listed (with `remove`) so the gate can see
@@ -135,7 +159,7 @@ export function asyncapiDocOf(doc) {
135
159
  messages.push({ name, slot, remove: true });
136
160
  return;
137
161
  }
138
- messages.push({ name, slot, ...(payloadUndeclared(root, node) ? { payloadEmpty: true } : {}) });
162
+ messages.push({ name, slot, closureDigest: closureDigest(node), ...(payloadUndeclared(root, node) ? { payloadEmpty: true } : {}) });
139
163
  seen.set(name, [...(seen.get(name) ?? []), slot]);
140
164
  };
141
165
  // `components.messages` — the ordinary place a message is declared.
@@ -1 +1 @@
1
- {"version":3,"file":"read.js","sourceRoot":"","sources":["../../../src/core/asyncapi/read.ts"],"names":[],"mappings":"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","sourcesContent":["import { isUtf8 } from \"node:buffer\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\nimport { danglingRefs, deref, payloadUndeclared } from \"./depth.js\";\nimport { isSlotRemoval, removalMarkerPaths } from \"./digest.js\";\nimport type { AsyncapiDoc, EventMessage } from \"./model.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\n\n/**\n * The async contract axis: AsyncAPI 3.0, read the way `core/openapi/doc.ts` reads\n * OpenAPI — a shallow structural walk over the `yaml` parse, never a real\n * AsyncAPI parser. `@asyncapi/parser` would be a fourth runtime dependency\n * (AGENTS.md: adding one is a product decision), and loam needs a slice of the\n * document so thin that the parser's whole model would be dead weight.\n *\n * 3.0 only, deliberately. Its operations are named top-level objects carrying\n * `action: send|receive`, which is the exact analog of an `operationId`; 2.x\n * spells the same thing as `publish`/`subscribe` nested under a channel, where\n * the two words are notoriously ambiguous about whose perspective they take.\n * Supporting one dialect is cheaper than supporting two, and a 2.x document\n * declares no `operations` at all — so it reads as a contract with no messages\n * rather than as a silently mis-parsed one.\n *\n * WHAT THIS READER MUST NEVER DO: join on anything inside a message's `payload`.\n * The payload is JSON Schema today and may be Avro later, and that switch is\n * meant to be a line in the document (`schemaFormat`) rather than a branch in\n * loam. Every check on this axis joins on a message NAME; nothing joins on a\n * field, and nothing under `payload` may ever contribute one. The day\n * something here reads a `required` array or a `fields` list INTO A JOIN, the\n * Avro migration stops being free. The one look the reader takes inside is\n * `payloadUndeclared` (depth.ts) — a presence probe that checks whether any\n * shape was declared at all, reads key existence and never a value, and skips\n * any payload declaring a non-JSON `schemaFormat`, so the stance holds. The\n * slot digests (./digest.ts) are COMPATIBLE with it, not an exception: they\n * hash payload bytes as content equality — a changed payload moves the slot's\n * identity — and content equality is not a join; nothing a digest reads ever\n * contributes a name to the spine.\n */\n\n/*\n * The `EventMessage`/`AsyncapiDoc` read model lives in `./model.ts` — split\n * there when the `asyncapiFromText` extraction pushed this module past the\n * 300-line ceiling. This module keeps the walk; that one keeps what the walk\n * answers.\n */\n\n/**\n * A message's join token: its `name`, or the key it is declared under.\n * Exported for the merge (./merge/merge.ts), whose removal exactness compares\n * this same spelling — a second definition of the fallback rule is how a\n * marker would delete a message the reader joins under a different name.\n */\nexport function messageName(node: unknown, key: string): string {\n if (node !== null && typeof node === \"object\" && !Array.isArray(node)) {\n const name = (node as Record<string, unknown>)[\"name\"];\n if (typeof name === \"string\" && name.trim().length > 0) return name.trim();\n }\n return key;\n}\n\n/** Entries of a mapping node, or nothing — every structural read here goes through it. */\nfunction entriesOf(node: unknown): [string, unknown][] {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return [];\n return Object.entries(node as Record<string, unknown>);\n}\n\n/**\n * Read an AsyncAPI 3.0 document into the slice loam joins on: the messages it\n * declares, and which of them it sends and receives.\n *\n * The send/receive sets are what make the spine directional, and the direction\n * is not decoration: on the HTTP axis the PROVIDER owns the contract and a\n * consumer's edge is checked against it, while here the PRODUCER owns the\n * message and a consumer joins to it from another repository entirely. That\n * inversion is why `spine.message-unproduced` has no OpenAPI analog.\n *\n * An unreadable document yields empty sets PLUS the `unreadable` flag, the\n * policy every reader here follows: parsers never diagnose. Swallowing the\n * failure into empty sets would make a broken contract indistinguishable from\n * one that declares nothing, and every inbound edge would be graded\n * `spine.message-undefined` — a false diagnosis pointing at the landscape when\n * the truth is this file.\n */\nexport async function readAsyncapi(asyncapiPath: string, context?: FleetContext): Promise<AsyncapiDoc> {\n if (context !== undefined) return context.readAsyncapi(asyncapiPath);\n if (!existsSync(asyncapiPath)) return empty();\n const bytes = await readFile(asyncapiPath);\n // Bytes that are not UTF-8 are as unreadable as broken YAML and for a worse\n // reason: `toString(\"utf8\")` substitutes U+FFFD and hands the parser a\n // document nobody wrote. The openapi axis learned this the same way.\n if (!isUtf8(bytes)) return { ...empty(), unreadable: true, error: \"file is not valid UTF-8\" };\n return asyncapiFromText(bytes.toString(\"utf8\"));\n}\n\n/**\n * The same read over text already in hand — the parse half of `readAsyncapi`,\n * split out so `loam diff` can read a contract fetched from a base git ref\n * (core/diff/base-state.ts) without writing it to disk. The YAML-throw catch\n * lives here and nowhere else; `asyncapiDocOf` below owns everything after\n * the parse, and the byte concerns (existence, UTF-8) stay with the reader\n * that holds the bytes.\n */\nexport function asyncapiFromText(text: string): AsyncapiDoc {\n let doc: unknown;\n try {\n doc = parse(text);\n } catch (e) {\n return { ...empty(), unreadable: true, error: e instanceof Error ? e.message : String(e) };\n }\n return asyncapiDocOf(doc);\n}\n\n/**\n * The same read over an already-parsed root — what the coherence gate's\n * merge simulation grades (core/coherence/events/), where the \"file\" is a\n * document the merge computed and never wrote. One spelling of the walk for\n * both entries, so a simulated contract can never read differently from the\n * living file the archive would produce from it.\n */\nexport function asyncapiDocOf(doc: unknown): AsyncapiDoc {\n // A scalar or sequence document has no mapping to look `channels` up in, so\n // nothing can be concluded from it. null (an empty file) stays readable — it\n // declares nothing, and says so honestly.\n if (doc !== null && (typeof doc !== \"object\" || Array.isArray(doc))) {\n return { ...empty(), unreadable: true, error: \"document is not a YAML mapping\" };\n }\n if (doc === null) return empty();\n\n const root = doc as Record<string, unknown>;\n const messages: EventMessage[] = [];\n const seen = new Map<string, string[]>();\n const declare = (name: string, slot: string, node: unknown): void => {\n // A declaration carrying the removal marker asserts a slot rather than\n // declaring a message: it is listed (with `remove`) so the gate can see\n // it, but it joins nothing — not sent/received, not the duplicate count.\n if (isSlotRemoval(node)) {\n messages.push({ name, slot, remove: true as const });\n return;\n }\n messages.push({ name, slot, ...(payloadUndeclared(root, node) ? { payloadEmpty: true as const } : {}) });\n seen.set(name, [...(seen.get(name) ?? []), slot]);\n };\n\n // `components.messages` — the ordinary place a message is declared.\n for (const [key, node] of entriesOf((root[\"components\"] as Record<string, unknown> | undefined)?.[\"messages\"])) {\n declare(messageName(node, key), `components.messages.${key}`, node);\n }\n\n // `channels.<ck>.messages.<mk>` — an entry here is either an ALIAS of a\n // components declaration (a `$ref`) or an inline declaration of its own. Only\n // the second is a new slot: counting an alias would report every properly\n // factored document as declaring each message twice, which is the shape the\n // spec's own examples use.\n const channelMessages = new Map<string, string[]>();\n for (const [ck, channel] of entriesOf(root[\"channels\"])) {\n // A channel-slot removal retires the whole channel, inline interior\n // included: nothing under it declares or joins anything any more.\n if (isSlotRemoval(channel)) {\n channelMessages.set(ck, []);\n continue;\n }\n const names: string[] = [];\n for (const [mk, entry] of entriesOf((channel as Record<string, unknown> | null)?.[\"messages\"])) {\n const isAlias =\n entry !== null && typeof entry === \"object\" && !Array.isArray(entry) && \"$ref\" in (entry as object);\n const { node, key } = deref(root, entry, mk);\n const name = messageName(node, key);\n // A name whose declaration is being retired stays off the channel's\n // list, whether the marker sits on the aliased components entry or on\n // the inline declaration itself — the operation fallback below must\n // not join through a message the feature is removing.\n if (!isSlotRemoval(node)) names.push(name);\n if (!isAlias) declare(name, `channels.${ck}.messages.${mk}`, node);\n }\n channelMessages.set(ck, names);\n }\n\n const sent = new Set<string>();\n const received = new Set<string>();\n for (const [, operation] of entriesOf(root[\"operations\"])) {\n const op = operation as Record<string, unknown> | null;\n // An operation-slot removal retires the operation: it contributes no\n // send/receive whatever its `action` says.\n if (isSlotRemoval(op)) continue;\n const action = op?.[\"action\"];\n if (action !== \"send\" && action !== \"receive\") continue;\n const into = action === \"send\" ? sent : received;\n // The channel this operation acts on, by pointer — its key is what the\n // fallback below indexes.\n const channelRef = (op?.[\"channel\"] as Record<string, unknown> | undefined)?.[\"$ref\"];\n const channelKey = typeof channelRef === \"string\" ? channelRef.slice(channelRef.lastIndexOf(\"/\") + 1) : undefined;\n const listed = op?.[\"messages\"];\n if (Array.isArray(listed) && listed.length > 0) {\n for (const entry of listed) {\n const { node, key } = deref(root, entry, \"\");\n // A pointer that resolves nowhere contributes no name rather than an\n // empty one: a phantom \"\" would join to nothing and read as a message\n // the service produces.\n if (node === undefined && key === \"\") continue;\n // A pointer into a declaration being retired joins nothing either —\n // the marker means the message is leaving the contract.\n if (isSlotRemoval(node)) continue;\n const name = messageName(node, key);\n if (name.length > 0) into.add(name);\n }\n continue;\n }\n // AsyncAPI 3: an operation that lists no messages applies to every message\n // of its channel. Omitting the list is the normal shape of a minimal\n // hand-written contract — exactly what a fleet with no registry writes — so\n // reading it as \"produces nothing\" would report the common case broken.\n if (channelKey !== undefined) for (const name of channelMessages.get(channelKey) ?? []) into.add(name);\n }\n\n const duplicateNames = [...seen].filter(([, slots]) => slots.length > 1).map(([name]) => name);\n return {\n messages,\n sent: [...sent],\n received: [...received],\n duplicateNames,\n unreadable: false,\n danglingRefs: danglingRefs(root),\n // One deep scan rather than pushes scattered through the walk above: the\n // walk only visits the depths that JOIN, and a marker leaked anywhere\n // else (the root, `info`) was invisible to the living sweep while the\n // strip still owed its removal.\n markers: removalMarkerPaths(root, \"\"),\n };\n}\n\nfunction empty(): AsyncapiDoc {\n return { messages: [], sent: [], received: [], duplicateNames: [], unreadable: false, danglingRefs: [], markers: [] };\n}\n\n/** The slots one message name is declared in — what `asyncapi.duplicate-message` names. */\nexport function slotsOf(doc: AsyncapiDoc, name: string): string[] {\n return doc.messages.filter((m) => m.name === name).map((m) => m.slot);\n}\n\n"]}
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../../src/core/asyncapi/read.ts"],"names":[],"mappings":"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","sourcesContent":["import { isUtf8 } from \"node:buffer\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\nimport { danglingRefs, deref, payloadUndeclared } from \"./depth.js\";\nimport { entriesOf, isSlotRemoval, removalMarkerPaths } from \"./digest.js\";\nimport { closureDigester } from \"../openapi/digest.js\";\nimport type { AsyncapiDoc, EventMessage } from \"./model.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\n\n/**\n * The async contract axis: AsyncAPI 3.0, read the way `core/openapi/doc.ts` reads\n * OpenAPI — a shallow structural walk over the `yaml` parse, never a real\n * AsyncAPI parser. `@asyncapi/parser` would be a fourth runtime dependency\n * (AGENTS.md: adding one is a product decision), and loam needs a slice of the\n * document so thin that the parser's whole model would be dead weight.\n *\n * 3.0 only, deliberately. Its operations are named top-level objects carrying\n * `action: send|receive`, which is the exact analog of an `operationId`; 2.x\n * spells the same thing as `publish`/`subscribe` nested under a channel, where\n * the two words are notoriously ambiguous about whose perspective they take.\n * Supporting one dialect is cheaper than supporting two. A 2.x (or 1.x)\n * document declares no `operations` at all, and it used to read as a contract\n * with no messages: one `asyncapi.yaml` copied in from an older service turned\n * into a `spine.message-undefined` per landscape edge, a\n * `spec-event.message-undefined` per requirement and a knock-on\n * `spine.message-unproduced` on every consumer, none of them naming the\n * version and every fix text saying \"declare the operation\" about a file that\n * does declare it, as `publish`/`subscribe`. That is exactly the false\n * diagnosis the unreadable policy below exists to prevent, so an older\n * version now reads as UNREADABLE (`asyncapiDocOf`): one `asyncapi.invalid`\n * naming the version and the upgrade command, and nothing graded against it.\n * `loam diff` skips such a document when the base ref holds the same bytes\n * (core/diff/semantic.ts's `UNTOUCHED_SKIPS`); without that, one unconverted\n * file suspended the event axis on every run and failed every PR's diff.\n *\n * WHAT THIS READER MUST NEVER DO: join on anything inside a message's `payload`.\n * The payload is JSON Schema today and may be Avro later, and that switch is\n * meant to be a line in the document (`schemaFormat`) rather than a branch in\n * loam. Every check on this axis joins on a message NAME; nothing joins on a\n * field, and nothing under `payload` may ever contribute one. The day\n * something here reads a `required` array or a `fields` list INTO A JOIN, the\n * Avro migration stops being free. The one look the reader takes inside is\n * `payloadUndeclared` (depth.ts) — a presence probe that checks whether any\n * shape was declared at all, reads key existence and never a value, and skips\n * any payload declaring a non-JSON `schemaFormat`, so the stance holds. The\n * slot digests (./digest.ts) are COMPATIBLE with it, not an exception: they\n * hash payload bytes as content equality — a changed payload moves the slot's\n * identity — and content equality is not a join; nothing a digest reads ever\n * contributes a name to the spine. The per-message `closureDigest` `loam diff`\n * compares is the same kind of read followed through `$ref`s: it hashes what\n * the payload points at, transitively, and still never names a field.\n */\n\n/*\n * The `EventMessage`/`AsyncapiDoc` read model lives in `./model.ts` — split\n * there when the `asyncapiFromText` extraction pushed this module past the\n * 300-line ceiling. This module keeps the walk; that one keeps what the walk\n * answers.\n */\n\n/**\n * A message's join token: its `name`, or the key it is declared under.\n * Exported for the merge (./merge/merge.ts), whose removal exactness compares\n * this same spelling — a second definition of the fallback rule is how a\n * marker would delete a message the reader joins under a different name.\n */\nexport function messageName(node: unknown, key: string): string {\n if (node !== null && typeof node === \"object\" && !Array.isArray(node)) {\n const name = (node as Record<string, unknown>)[\"name\"];\n if (typeof name === \"string\" && name.trim().length > 0) return name.trim();\n }\n return key;\n}\n\n/**\n * Read an AsyncAPI 3.0 document into the slice loam joins on: the messages it\n * declares, and which of them it sends and receives.\n *\n * The send/receive sets are what make the spine directional, and the direction\n * is not decoration: on the HTTP axis the PROVIDER owns the contract and a\n * consumer's edge is checked against it, while here the PRODUCER owns the\n * message and a consumer joins to it from another repository entirely. That\n * inversion is why `spine.message-unproduced` has no OpenAPI analog.\n *\n * An unreadable document yields empty sets PLUS the `unreadable` flag, the\n * policy every reader here follows: parsers never diagnose. Swallowing the\n * failure into empty sets would make a broken contract indistinguishable from\n * one that declares nothing, and every inbound edge would be graded\n * `spine.message-undefined` — a false diagnosis pointing at the landscape when\n * the truth is this file.\n */\nexport async function readAsyncapi(asyncapiPath: string, context?: FleetContext): Promise<AsyncapiDoc> {\n if (context !== undefined) return context.readAsyncapi(asyncapiPath);\n if (!existsSync(asyncapiPath)) return empty();\n const bytes = await readFile(asyncapiPath);\n // Bytes that are not UTF-8 are as unreadable as broken YAML and for a worse\n // reason: `toString(\"utf8\")` substitutes U+FFFD and hands the parser a\n // document nobody wrote. The openapi axis learned this the same way.\n if (!isUtf8(bytes)) return { ...empty(), unreadable: true, error: \"file is not valid UTF-8\" };\n return asyncapiFromText(bytes.toString(\"utf8\"));\n}\n\n/**\n * The same read over text already in hand — the parse half of `readAsyncapi`,\n * split out so `loam diff` can read a contract fetched from a base git ref\n * (core/diff/base-state.ts) without writing it to disk. The YAML-throw catch\n * lives here and nowhere else; `asyncapiDocOf` below owns everything after\n * the parse, and the byte concerns (existence, UTF-8) stay with the reader\n * that holds the bytes.\n */\nexport function asyncapiFromText(text: string): AsyncapiDoc {\n let doc: unknown;\n try {\n doc = parse(text);\n } catch (e) {\n return { ...empty(), unreadable: true, error: e instanceof Error ? e.message : String(e) };\n }\n return asyncapiDocOf(doc);\n}\n\n/**\n * The same read over an already-parsed root — what the coherence gate's\n * merge simulation grades (core/coherence/events/), where the \"file\" is a\n * document the merge computed and never wrote. One spelling of the walk for\n * both entries, so a simulated contract can never read differently from the\n * living file the archive would produce from it.\n */\nexport function asyncapiDocOf(doc: unknown): AsyncapiDoc {\n // A scalar or sequence document has no mapping to look `channels` up in, so\n // nothing can be concluded from it. null (an empty file) stays readable — it\n // declares nothing, and says so honestly.\n if (doc !== null && (typeof doc !== \"object\" || Array.isArray(doc))) {\n return { ...empty(), unreadable: true, error: \"document is not a YAML mapping\" };\n }\n if (doc === null) return empty();\n\n const root = doc as Record<string, unknown>;\n // A pre-3.0 document is not a 3.0 document with nothing in it, so it takes\n // the unreadable path rather than the empty one (see the module header).\n // Only a version that SAYS 1.x or 2.x is refused: a file with no `asyncapi:`\n // key at all (a hand-trimmed feature delta that restates slots and not the\n // header) keeps reading as 3.0, and so does any later major, which is a\n // question for the day it exists rather than a guess made now. A bare `2`\n // or `2.6` parses as a YAML number, hence the String() and the `$`.\n const version = root[\"asyncapi\"];\n if ((typeof version === \"string\" || typeof version === \"number\") && /^[12](\\.|$)/.test(String(version))) {\n return {\n ...empty(),\n unreadable: true,\n error: `AsyncAPI ${String(version)}: loam reads AsyncAPI 3.0 only; \\`asyncapi convert --target-version=3.0.0\\` upgrades it`,\n };\n }\n const messages: EventMessage[] = [];\n const seen = new Map<string, string[]>();\n const closureDigest = closureDigester(root);\n const declare = (name: string, slot: string, node: unknown): void => {\n // A declaration carrying the removal marker asserts a slot rather than\n // declaring a message: it is listed (with `remove`) so the gate can see\n // it, but it joins nothing — not sent/received, not the duplicate count.\n if (isSlotRemoval(node)) {\n messages.push({ name, slot, remove: true as const });\n return;\n }\n messages.push({ name, slot, closureDigest: closureDigest(node), ...(payloadUndeclared(root, node) ? { payloadEmpty: true as const } : {}) });\n seen.set(name, [...(seen.get(name) ?? []), slot]);\n };\n\n // `components.messages` — the ordinary place a message is declared.\n for (const [key, node] of entriesOf((root[\"components\"] as Record<string, unknown> | undefined)?.[\"messages\"])) {\n declare(messageName(node, key), `components.messages.${key}`, node);\n }\n\n // `channels.<ck>.messages.<mk>` — an entry here is either an ALIAS of a\n // components declaration (a `$ref`) or an inline declaration of its own. Only\n // the second is a new slot: counting an alias would report every properly\n // factored document as declaring each message twice, which is the shape the\n // spec's own examples use.\n const channelMessages = new Map<string, string[]>();\n for (const [ck, channel] of entriesOf(root[\"channels\"])) {\n // A channel-slot removal retires the whole channel, inline interior\n // included: nothing under it declares or joins anything any more.\n if (isSlotRemoval(channel)) {\n channelMessages.set(ck, []);\n continue;\n }\n const names: string[] = [];\n for (const [mk, entry] of entriesOf((channel as Record<string, unknown> | null)?.[\"messages\"])) {\n const isAlias =\n entry !== null && typeof entry === \"object\" && !Array.isArray(entry) && \"$ref\" in (entry as object);\n const { node, key } = deref(root, entry, mk);\n const name = messageName(node, key);\n // A name whose declaration is being retired stays off the channel's\n // list, whether the marker sits on the aliased components entry or on\n // the inline declaration itself — the operation fallback below must\n // not join through a message the feature is removing.\n if (!isSlotRemoval(node)) names.push(name);\n if (!isAlias) declare(name, `channels.${ck}.messages.${mk}`, node);\n }\n channelMessages.set(ck, names);\n }\n\n const sent = new Set<string>();\n const received = new Set<string>();\n for (const [, operation] of entriesOf(root[\"operations\"])) {\n const op = operation as Record<string, unknown> | null;\n // An operation-slot removal retires the operation: it contributes no\n // send/receive whatever its `action` says.\n if (isSlotRemoval(op)) continue;\n const action = op?.[\"action\"];\n if (action !== \"send\" && action !== \"receive\") continue;\n const into = action === \"send\" ? sent : received;\n // The channel this operation acts on, by pointer — its key is what the\n // fallback below indexes.\n const channelRef = (op?.[\"channel\"] as Record<string, unknown> | undefined)?.[\"$ref\"];\n const channelKey = typeof channelRef === \"string\" ? channelRef.slice(channelRef.lastIndexOf(\"/\") + 1) : undefined;\n const listed = op?.[\"messages\"];\n if (Array.isArray(listed) && listed.length > 0) {\n for (const entry of listed) {\n const { node, key } = deref(root, entry, \"\");\n // A pointer that resolves nowhere contributes no name rather than an\n // empty one: a phantom \"\" would join to nothing and read as a message\n // the service produces.\n if (node === undefined && key === \"\") continue;\n // A pointer into a declaration being retired joins nothing either —\n // the marker means the message is leaving the contract.\n if (isSlotRemoval(node)) continue;\n const name = messageName(node, key);\n if (name.length > 0) into.add(name);\n }\n continue;\n }\n // AsyncAPI 3: an operation that lists no messages applies to every message\n // of its channel. Omitting the list is the normal shape of a minimal\n // hand-written contract — exactly what a fleet with no registry writes — so\n // reading it as \"produces nothing\" would report the common case broken.\n if (channelKey !== undefined) for (const name of channelMessages.get(channelKey) ?? []) into.add(name);\n }\n\n const duplicateNames = [...seen].filter(([, slots]) => slots.length > 1).map(([name]) => name);\n return {\n messages,\n sent: [...sent],\n received: [...received],\n duplicateNames,\n unreadable: false,\n danglingRefs: danglingRefs(root),\n // One deep scan rather than pushes scattered through the walk above: the\n // walk only visits the depths that JOIN, and a marker leaked anywhere\n // else (the root, `info`) was invisible to the living sweep while the\n // strip still owed its removal.\n markers: removalMarkerPaths(root, \"\"),\n };\n}\n\nfunction empty(): AsyncapiDoc {\n return { messages: [], sent: [], received: [], duplicateNames: [], unreadable: false, danglingRefs: [], markers: [] };\n}\n\n/** The slots one message name is declared in — what `asyncapi.duplicate-message` names. */\nexport function slotsOf(doc: AsyncapiDoc, name: string): string[] {\n return doc.messages.filter((m) => m.name === name).map((m) => m.slot);\n}\n\n"]}
@@ -19,15 +19,20 @@
19
19
  * ties the result to a repository, which is why the brief argues for it instead
20
20
  * of merely listing it.
21
21
  */
22
- import { existsSync, statSync } from "node:fs";
22
+ import { existsSync } from "node:fs";
23
23
  import { readdir } from "node:fs/promises";
24
24
  import { join, relative } from "node:path";
25
25
  import { landscapePath } from "../repo/paths.js";
26
26
  import { locateServicePaths } from "../repo/service-target.js";
27
+ import { specAdoption } from "../repo/repo.js";
28
+ import { entryIs } from "../repo/tree/fs.js";
27
29
  import { VALIDATE_CHECKS } from "./checks.js";
28
30
  import { UNCHECKED } from "./unchecked.js";
29
- import { landscapeArtifact, landscapeContext } from "./landscape.js";
30
- import { ARTIFACTS } from "./targets.js";
31
+ import { landscapeContext } from "./landscape.js";
32
+ import { landscapeArtifact } from "./map/owed.js";
33
+ import { exampleFor, extendPoint, FQN } from "./map/extend.js";
34
+ import { ARTIFACTS, USECASES_DIR } from "./targets.js";
35
+ import { EXTERNAL_TAG } from "../vocabulary/maturity.js";
31
36
  /**
32
37
  * The architecture walk: the order a service is read in, and what each stop is
33
38
  * for.
@@ -135,32 +140,87 @@ export async function serviceBrief(docsDir, service, at) {
135
140
  const paths = at ?? (await locateServicePaths(docsDir, service));
136
141
  const rel = (abs) => relative(docsDir, abs).split(/[\\/]/).join("/");
137
142
  // Read before the artifact loop, not after it: what the fleet already says
138
- // about this service decides whether it owes an API contract at all.
139
- const landscape = await landscapeContext(docsDir, service);
143
+ // about this service decides whether it owes an API contract at all. The
144
+ // model path travels with it — located through the enumeration above, never
145
+ // joined at `services/<id>/` — so the edgeless state can name the calls the
146
+ // service's own model attests.
147
+ const landscape = await landscapeContext(docsDir, service, paths);
140
148
  const owesApi = apiExpected(landscape);
149
+ // The one id the model target's grammar and example are written against,
150
+ // resolved here because `ARTIFACTS` is static on purpose (a target list that
151
+ // varied per repository would be a second thing to keep true) and because this
152
+ // is the only place the fleet map has already been read.
153
+ const extend = extendPoint(landscape);
141
154
  const targets = [];
142
155
  for (const { key, ...artifact } of ARTIFACTS) {
143
- // `adrs/` is a directory, and an empty one is not an existing artifact —
144
- // reporting it as present would tell the agent to diff against nothing.
145
- const dir = key === "adrsDir";
146
- const exists = dir ? await hasMarkdown(paths[key]) : existsSync(paths[key]);
156
+ // `adrs/` and `usecases/` are DIRECTORIES, and an empty one is not an
157
+ // existing artifact — reporting it as present would tell the agent to diff
158
+ // against nothing, and for `usecases/` it would also tell the agent an
159
+ // optional slot was already taken.
160
+ //
161
+ // `usecases/` is the one target with no `ServicePaths` entry, and that is
162
+ // deliberate rather than an omission: nothing loam checks JOINS on that
163
+ // path. The slot a service's flows are graded out of is its whole LikeC4
164
+ // project (`core/usecases/service/flows.ts` reads every `.likec4` under the
165
+ // directory), so `usecases/<name>.likec4` is a convention this brief
166
+ // publishes, not a path a writer writes — and `targets.ts` is where a
167
+ // convention belongs.
168
+ const ext = key === "adrsDir" ? ".md" : key === "usecasesDir" ? ".likec4" : null;
169
+ const at = key === "usecasesDir" ? join(paths.dir, USECASES_DIR) : paths[key];
170
+ const exists = ext === null ? existsSync(at) : await hasEntry(at, ext);
171
+ const partial = key === "spec" && exists && (await specAdoption(at)) === "partial";
147
172
  targets.push({
148
173
  ...artifact,
174
+ ...(partial ? { partial: true } : {}),
149
175
  ...(key === "openapi" ? { required: owesApi } : {}),
150
- path: dir ? `${rel(paths[key])}/` : rel(paths[key]),
176
+ // Every `<fqn>` in the model target, replaced by the element this service
177
+ // actually extends. The placeholder is left literal in `targets.ts` — the
178
+ // brief is handed to an agent that will WRITE the file, and
179
+ // `extend <fqn> {` copied verbatim is a document that does not parse.
180
+ shape: artifact.shape.map((rule) => rule.replaceAll(FQN, extend.id)),
181
+ ...(artifact.example === undefined ? {} : { example: exampleFor(artifact.example, extend, service) }),
182
+ path: ext === null ? rel(at) : `${rel(at)}/`,
151
183
  exists,
152
184
  action: exists ? "diff" : "create",
153
185
  });
154
186
  }
155
- // The eighth target is the fleet map, and it is conditional: a service the
156
- // landscape ALREADY resolves an element to owes it nothing, and briefing an
157
- // edit there would invite an agent to draw a second box for a service that
158
- // has one. Everything else about the target is unconditional — the file is
187
+ // The eighth target is the fleet map, and it is conditional on TWO facts: an
188
+ // element resolving to the service, and an edge touching it. A service the
189
+ // landscape already draws AND reaches owes it nothing, and briefing an edit
190
+ // there would invite an agent to draw a second box for a service that has
191
+ // one. A service with a box and no edge — what `loam seed` leaves for every
192
+ // service nobody listed under `calls:` — owes it the edges, and until
193
+ // 2026-09-03 element existence alone read as "the map is complete" here, so
194
+ // the brief declared the map finished for exactly the services it had not
195
+ // reached. Everything else about the target is unconditional — the file is
159
196
  // shared, so `action` is `edit` whenever it exists and `create` only for the
160
197
  // very first service of a brand-new docs repo.
161
- if (landscape.modelled !== true) {
198
+ //
199
+ // `modelled === false` and not `!== true`: `null` is "loam could not read the
200
+ // map", and it fell through the same branch as "the map genuinely draws
201
+ // nothing for this service". The brief then handed over the UNDRAWN target —
202
+ // whose shape rule is "add one top-level element bound to its directory" and
203
+ // whose example is a block to splice — for services the map already declares
204
+ // and binds, and an agent that spliced it earned `landscape.binding-duplicate`
205
+ // for doing exactly what the brief asked. With the map unreadable there is no
206
+ // write to ask for; `landscape.instruction` carries the whole answer and names
207
+ // the documents that failed.
208
+ if (landscape.modelled === false || landscape.touched === false) {
162
209
  targets.push({
163
- ...landscapeArtifact(service, landscape.expects, landscape.present, rel(paths.dir)),
210
+ ...landscapeArtifact({
211
+ service,
212
+ expects: landscape.expects,
213
+ present: landscape.present,
214
+ servicePath: rel(paths.dir),
215
+ // The existing element's id, in the edgeless state only: it switches
216
+ // the example from a block to edges on that element.
217
+ // …and whether it is `#external`, which decides what the shape rule may
218
+ // promise: `landscape.service-isolated` never fires on such an element.
219
+ ...(landscape.touched === false && landscape.elements[0] !== undefined
220
+ ? { elementId: landscape.elements[0].id, external: landscape.elements[0].tags.includes(EXTERNAL_TAG) }
221
+ : {}),
222
+ attested: landscape.attested,
223
+ }),
164
224
  path: rel(landscapePath(docsDir)),
165
225
  exists: landscape.present,
166
226
  action: landscape.present ? "edit" : "create",
@@ -180,27 +240,20 @@ export async function serviceBrief(docsDir, service, at) {
180
240
  rule: NEVER_OVERWRITE,
181
241
  };
182
242
  }
183
- async function hasMarkdown(dir) {
243
+ /**
244
+ * Does this directory already hold an artifact of the given extension? The
245
+ * "exists" question for the two DIRECTORY targets — `adrs/` (`.md`) and
246
+ * `usecases/` (`.likec4`) — where an empty or absent directory is the same
247
+ * answer, because neither is something to diff against.
248
+ */
249
+ async function hasEntry(dir, ext) {
184
250
  if (!existsSync(dir))
185
251
  return false;
186
252
  const entries = await readdir(dir, { withFileTypes: true });
187
- // `statSync` for the symlink case: a Dirent never follows one, so `isFile()`
188
- // is false for a symlinked ADR and the directory reads as empty (repo.ts's
189
- // `entryIs` is the same rule for `services/` and `features/`).
190
- return entries.some((e) => {
191
- if (!e.name.endsWith(".md"))
192
- return false;
193
- if (e.isFile())
194
- return true;
195
- if (!e.isSymbolicLink())
196
- return false;
197
- try {
198
- return statSync(join(dir, e.name)).isFile();
199
- }
200
- catch {
201
- return false;
202
- }
203
- });
253
+ // `entryIs` for the symlink case: a Dirent never follows one, so `isFile()`
254
+ // is false for a symlinked ADR and the directory would read as empty — the
255
+ // same rule `services/` and `features/` are enumerated by.
256
+ return entries.some((e) => e.name.endsWith(ext) && entryIs(dir, e, "file"));
204
257
  }
205
258
  /**
206
259
  * Does the fleet expect an API of this service?
@@ -215,11 +268,18 @@ async function hasMarkdown(dir) {
215
268
  * in `openapi.yaml`'s shape rules (`ARTIFACTS`, targets.ts).
216
269
  *
217
270
  * Positive evidence only, exactly as `list` reads it: a landscape that is
218
- * absent or does not parse proves nothing about who calls this service, so the
271
+ * absent or unreadable proves nothing about who calls this service, so the
219
272
  * contract is still owed. `expects` is the ops on inbound edges, which is
220
273
  * `list`'s `called` set seen from one service.
274
+ *
275
+ * `callersKnown` and not `parses`, because since W4 `parses` answers about the
276
+ * whole `architecture/` PROJECT: a broken palette or use case beside a landscape
277
+ * loam reads perfectly made this discard the map and demand an OpenAPI contract
278
+ * of a browser UI — inventing an API into the source of truth, which is the one
279
+ * class of guess this module exists to refuse. `callersKnown` is the narrower
280
+ * fact this question actually needs, and it is the one `list` and `context` read.
221
281
  */
222
282
  function apiExpected(landscape) {
223
- return !landscape.parses || landscape.expects.length > 0;
283
+ return !landscape.callersKnown || landscape.expects.length > 0;
224
284
  }
225
285
  //# sourceMappingURL=brief.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"brief.js","sourceRoot":"","sources":["../../../src/core/brief/brief.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The adoption brief: what an agent needs in order to write one service's\n * baseline documentation, and what will be checked when it does.\n *\n * `adopt` was specified for years as \"read the code, emit the C4\". It is not\n * being built, because it cannot be: nothing deterministic reads a legacy\n * service and says what its architecture MEANS, and a guessed model is worse\n * than no model — everyone downstream has to re-derive it to know whether to\n * believe it.\n *\n * So loam takes the half of the job that IS deterministic. It states the work\n * precisely (which files, in which grammar, bound to which existing elements),\n * it says exactly which checks the result will face, and then it says which\n * checks do not exist — because everything in that last list is honesty no\n * validator will ever supply, and an agent that does not know where the checking\n * stops will assume it never does.\n *\n * Nothing here reads code. `sources` in the frontmatter is the only line that\n * ties the result to a repository, which is why the brief argues for it instead\n * of merely listing it.\n */\nimport { existsSync, statSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport { landscapePath, type ServicePaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport { VALIDATE_CHECKS, type BriefCheck } from \"./checks.js\";\nimport { UNCHECKED } from \"./unchecked.js\";\nimport { landscapeArtifact, landscapeContext, type LandscapeContext } from \"./landscape.js\";\nimport { ARTIFACTS, type BriefTarget } from \"./targets.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\n/* ------------------------------------------------------------------ */\n/* The walk — what to read, in what order, and where each finding lands */\n/* ------------------------------------------------------------------ */\n\nexport interface WalkStop {\n /** What to open, named as a thing to go and find rather than a file name. */\n where: string;\n /** What to take out of it. */\n find: string;\n /** The artifacts this stop feeds, by `artifact` name from `targets[]`. */\n lands: string[];\n}\n\n/**\n * The architecture walk: the order a service is read in, and what each stop is\n * for.\n *\n * The brief said \"read the code: entry points, HTTP routes and handlers,\n * published events, config, deploy manifests, tests\" — one sentence, six nouns,\n * no order and no landing site. What came back matched the sentence: the HTTP\n * surface documented well, because it is the noun an agent recognises fastest,\n * and the scheduler, the consumer group and the outbox missing entirely. Not\n * because they were judged unimportant — because nothing said to go and look,\n * and every check loam owns is computed from what was written rather than from\n * what is there. `UNCHECKED` has said COMPLETENESS is unverifiable since the\n * beginning; that is an argument for stating the sweep, not for leaving it\n * implied.\n *\n * The order is deliberate and it is not the order a reader browses in. Stops 1\n * and 2 fix the SHAPE of the service — which processes exist at all — before\n * any surface is enumerated, because an agent that starts at the HTTP routes\n * has already concluded the service is an API by the time it meets the\n * scheduler. Everything after them is one surface each.\n *\n * `lands` names artifacts rather than describing them, because the two lists\n * are joined: test/agent-contract.test.ts requires every name here to be a\n * target the same brief hands over, so a stop can never feed a file nobody was\n * asked to write.\n */\nexport const WALK: WalkStop[] = [\n {\n where: \"the build and dependency manifests — pom.xml, build.gradle, package.json, go.mod, requirements.txt, and whatever the repo's own README calls the build\",\n find: \"what this service is built from, and every client library it links against. A broker client, an HTTP client, a database driver: each one is a dependency that has to appear as an edge or a container later, and this list is the only place they are all named at once. A library whose SEMANTICS decide behaviour — a retry budget, a delivery guarantee, a serialization format — is an arch requirement (arch.spec.md), not a fact to leave in your head.\",\n lands: [\"model.likec4\", \"runbook.md\", \"arch.spec.md\"],\n },\n {\n where: \"the entry points — main/Application/server bootstrap, the DI or module wiring, and anything the deploy manifests actually start\",\n find: \"how many PROCESSES this service is, and what each one is. An API, a worker, a scheduler, a consumer — a service that is three of those and documented as one is the most common way a baseline is wrong, and it is wrong in a shape no later check can see.\",\n lands: [\"model.likec4\"],\n },\n {\n where: \"the HTTP surface — every route, controller, handler and middleware, counted\",\n find: \"the operation set, in full, and inside each operation the DECISIONS its code makes: the permission and role checks — each one is a `Requires:` entry, and a name `architecture/permissions.yaml` does not declare yet is a line to ADD to that shared file, never to invent locally — the fields required only when another field is present, the defaults and normalisations applied before anything is stored, the transitions a request is refused for. Count both — operations, and branches per operation — before you write, and say both counts in the hand-back. `api.ungoverned` can tell you an operation you WROTE has no requirement; nothing at all can tell you about the twelve you never wrote down, or about the thirty branches inside the one you did. A guard is not implementation detail: what a service REFUSES is behaviour its caller observes and a test pins, and it is the half of a legacy service that lives only in the code and in one developer's head.\",\n lands: [\"openapi.yaml\", \"spec.md\"],\n },\n {\n where: \"the message surface — producers, consumers, listener annotations, topic and queue names, the outbox table and its relay\",\n find: \"every message this service puts on and takes off the bus, with its direction, and — on the consuming side — the DECISIONS the handler makes about a message before it acts on one. The direction is the half that gets lost: a consumer documented as a producer is a contract the whole fleet then joins against backwards. The guards are the half nobody looks for at all: which messages are filtered out and on what field, what happens to one whose entity has since been deleted, what is idempotency-keyed against a replay, what is dead-lettered rather than retried. Each of those is a scenario, and none of them appears anywhere in the contract — asyncapi.yaml describes the message, never what this service refuses to do with it.\",\n lands: [\"asyncapi.yaml\", \"landscape.likec4\"],\n },\n {\n where: \"the scheduled and background work — cron entries, timers, retry loops, reconcilers, migrations that run at boot\",\n find: \"behaviour no request ever triggers, and the conditions under which it does or does not run. It has requirements like anything else, and it is invisible to every surface above — which is why a baseline written from routes alone describes a service that does half of what it does. Take the decisions with it: which rows a reconciler SELECTS and which it skips, what a retry loop gives up on and after how long, what a migration does when it finds the state it is meant to fix already fixed. A scheduled job documented as 'runs nightly' is a requirement with no behaviour in it.\",\n lands: [\"spec.md\", \"arch.spec.md\"],\n },\n {\n where: \"the state — datastores, caches, buckets, and the schema or migration directory that says what is in them\",\n find: \"what this service OWNS versus what it borrows. Ownership is the fact the C4 is for, and it is the one nobody can reconstruct later from the code without re-reading all of it.\",\n lands: [\"model.likec4\"],\n },\n {\n where: \"the outbound calls — HTTP clients, generated stubs, gRPC channels, and the config that says which host each one points at\",\n find: \"who this service calls, and which operation each call names. These become the fleet map's edges; an edge missing here is a dependency the fleet cannot see, and `loam explore` will under-report the blast radius of every future change to the callee. An edge that only configuration attests is PROVISIONAL until the runtime stop: hold it until the deploy manifests confirm nothing switches that client off.\",\n lands: [\"landscape.likec4\"],\n },\n {\n where: \"the runtime — config and env vars, deploy manifests, pipelines, alert rules, dashboards\",\n find: \"how it is run and what pages whom. Alerts are the honest source for health.yaml: an SLO nobody agreed to is invention, but an alert that already fires is a fact somebody wrote down. Deployment manifests OVERRIDE configuration files, and they usually live in another repository — go and find them before any dependency that only configuration attests goes in the model, and check for an environment variable that disables the feature. A capability switched off at deploy time is not a dependency, however completely the config wires it: delete its edge and container, and record it in runbook.md's 'configured but not a dependency, and why' list. A manifest read from another repository is named in the runbook's prose — never in `sources`, which may only hold paths of this service's own repository. An EFFECTIVE configuration value behaviour depends on — the timeout the chart sets, the flag that switches a code path — is an arch requirement in arch.spec.md, with a Covers: line naming what it decides; loam holds its coverage even though it can never check the value.\",\n lands: [\"runbook.md\", \"health.yaml\", \"arch.spec.md\"],\n },\n {\n where: \"the tests, integration and component ones first\",\n find: \"the behaviours somebody already thought worth pinning. This is the PRIMARY source for scenarios, not the last sweep: a behaviour a test already pins is written FROM that test, and the code is read only for what no test covers. A test suite is the only part of a legacy service that states intent rather than mechanism, and a scenario derived from one is the only kind that starts out true. Carry the shape across as well as the content — a parameterised test (`@ParameterizedTest`, `@parametrize`, `test.each`) is one scenario with a table, not one scenario per case. Then say in the hand-back how well this service is covered and how many of its tests became scenarios: that number is the ceiling on what this whole baseline can be worth, and nothing downstream can compute it.\",\n lands: [\"spec.md\", \"arch.spec.md\"],\n },\n];\n\n/**\n * The stop that closes the walk. Separate from `WALK` because it is not a place\n * to go — it is the question asked once the walking is done, and the only\n * defence against a sweep that felt thorough because the parts it covered were\n * covered well.\n */\nexport const WALK_CLOSE =\n \"Then list the directories you did NOT open, and say why for each. `loam validate --service <id>`, run inside the service's own repo, reports the same list back as `sources.unwalked` — it compares your `sources` against the files git tracks. A directory you deliberately skipped is a sentence in the hand-back; a directory you skipped without noticing is the gap this walk exists to find.\";\n\n/* ------------------------------------------------------------------ */\n/* The frontmatter */\n/* ------------------------------------------------------------------ */\n\nexport interface FrontmatterBrief {\n /** Field -> what to write in it. */\n fields: Record<string, string>;\n /** Fields to leave alone entirely. */\n never: string[];\n /** Why `sources` carries the weight. */\n why: string;\n}\n\nconst FRONTMATTER_BRIEF: FrontmatterBrief = {\n fields: {\n service: \"the directory this file lives under, exactly. A mismatch is an error (`frontmatter.field-mismatch`).\",\n status:\n \"`draft`, always. `verified` is a human's word, stamped by `loam vouch --service <id>` run inside the service's own repo — never written by hand.\",\n owner: \"the team or person who answers for this service.\",\n sources:\n \"the paths in THIS SERVICE'S repository you actually read to write the document — files and directories only, a directory covering everything beneath it; glob patterns are refused. Not the paths a reader would expect to have been read. This is NOT a reading list for a human: nobody follows these, and nothing about the document asks them to.\",\n },\n // `vouch_scope` is in this list for a sharper reason than its siblings: the\n // others are stamps an agent could only forge, while this one is a stamp an\n // agent could DELETE — and deleting it turns a vouch that read four sections\n // into one that read the document, on every surface at once.\n never: [\"last_verified\", \"sources_digest\", \"content_digest\", \"sources_files\", \"vouch_scope\"],\n why:\n \"`sources` is digest input, not a citation. It is the only mechanical tie between this document and the code — everything else loam checks is internal consistency, and a corpus can agree with itself perfectly while describing nothing that exists. `loam validate`, run inside the service's repo, checks every listed path is still there; `loam vouch` hashes their CONTENT so that a later `validate` can say the code has moved since anyone read it. That hash is the entire point, and it is why padding the list costs something real rather than merely being untidy: a path nobody read still gets hashed, so its next change reports as drift in a document it never described, and a path that WAS read and went unlisted moves under the document in silence. Neither failure is visible to a reader; both are decided by whether this list is honest.\",\n};\n\nexport interface Brief {\n service: string;\n /** Absolute docs repo root — the anchor the repo-relative paths hang off. */\n docsDir: DocsDir;\n /** Repo-relative path of services/<id>/. */\n path: string;\n targets: BriefTarget[];\n /** The order to read the service in, and what each stop feeds. */\n walk: WalkStop[];\n /** The question the walk ends on — see `WALK_CLOSE`. */\n walkClose: string;\n landscape: LandscapeContext;\n frontmatter: FrontmatterBrief;\n checks: BriefCheck[];\n unchecked: string[];\n /** The one instruction that outranks the rest. */\n rule: string;\n}\n\nconst NEVER_OVERWRITE =\n \"Do not overwrite an artifact that already exists. Read it, diff your findings against it, and report what disagrees — a document somebody wrote is evidence, and replacing it destroys the only copy of what they knew.\";\n\n/**\n * Assemble the brief for one service. Reads the docs repo; writes nothing.\n * `at` overrides where the artifacts are briefed to land — `adopt\n * --subsystem`'s target inside a group — and is only ever passed for a\n * service the enumeration does NOT answer: an existing service is briefed\n * where it already lives, because the brief's one rule is to never overwrite,\n * and a second address would brief a duplicate.\n */\nexport async function serviceBrief(\n docsDir: DocsDir,\n service: PathableService,\n at?: ServicePaths,\n): Promise<Brief> {\n const paths = at ?? (await locateServicePaths(docsDir, service));\n const rel = (abs: string): string => relative(docsDir, abs).split(/[\\\\/]/).join(\"/\");\n\n // Read before the artifact loop, not after it: what the fleet already says\n // about this service decides whether it owes an API contract at all.\n const landscape = await landscapeContext(docsDir, service);\n const owesApi = apiExpected(landscape);\n\n const targets: BriefTarget[] = [];\n for (const { key, ...artifact } of ARTIFACTS) {\n // `adrs/` is a directory, and an empty one is not an existing artifact —\n // reporting it as present would tell the agent to diff against nothing.\n const dir = key === \"adrsDir\";\n const exists = dir ? await hasMarkdown(paths[key]) : existsSync(paths[key]);\n targets.push({\n ...artifact,\n ...(key === \"openapi\" ? { required: owesApi } : {}),\n path: dir ? `${rel(paths[key])}/` : rel(paths[key]),\n exists,\n action: exists ? \"diff\" : \"create\",\n });\n }\n\n // The eighth target is the fleet map, and it is conditional: a service the\n // landscape ALREADY resolves an element to owes it nothing, and briefing an\n // edit there would invite an agent to draw a second box for a service that\n // has one. Everything else about the target is unconditional — the file is\n // shared, so `action` is `edit` whenever it exists and `create` only for the\n // very first service of a brand-new docs repo.\n if (landscape.modelled !== true) {\n targets.push({\n ...landscapeArtifact(service, landscape.expects, landscape.present, rel(paths.dir)),\n path: rel(landscapePath(docsDir)),\n exists: landscape.present,\n action: landscape.present ? \"edit\" : \"create\",\n });\n }\n\n return {\n service,\n docsDir,\n path: rel(paths.dir),\n targets,\n walk: WALK,\n walkClose: WALK_CLOSE,\n landscape,\n frontmatter: FRONTMATTER_BRIEF,\n checks: VALIDATE_CHECKS,\n unchecked: UNCHECKED,\n rule: NEVER_OVERWRITE,\n };\n}\n\nasync function hasMarkdown(dir: string): Promise<boolean> {\n if (!existsSync(dir)) return false;\n const entries = await readdir(dir, { withFileTypes: true });\n // `statSync` for the symlink case: a Dirent never follows one, so `isFile()`\n // is false for a symlinked ADR and the directory reads as empty (repo.ts's\n // `entryIs` is the same rule for `services/` and `features/`).\n return entries.some((e) => {\n if (!e.name.endsWith(\".md\")) return false;\n if (e.isFile()) return true;\n if (!e.isSymbolicLink()) return false;\n try {\n return statSync(join(dir, e.name)).isFile();\n } catch {\n return false;\n }\n });\n}\n\n/**\n * Does the fleet expect an API of this service?\n *\n * The brief marked `openapi.yaml` required unconditionally, and printed the\n * absence as `MISSING` in capitals — while `loam validate` and `loam list`\n * derive the opposite from the same landscape (`service.no-openapi` stays quiet\n * when nothing calls the service, and `list`'s maturity ladder stops demanding\n * a contract). For a UI, a worker or a cron the brief was therefore telling an\n * agent to INVENT an API contract into the source of truth — the one class of\n * guess this whole module exists to refuse, and the one the brief itself names\n * in `openapi.yaml`'s shape rules (`ARTIFACTS`, targets.ts).\n *\n * Positive evidence only, exactly as `list` reads it: a landscape that is\n * absent or does not parse proves nothing about who calls this service, so the\n * contract is still owed. `expects` is the ops on inbound edges, which is\n * `list`'s `called` set seen from one service.\n */\nfunction apiExpected(landscape: LandscapeContext): boolean {\n return !landscape.parses || landscape.expects.length > 0;\n}\n"]}
1
+ {"version":3,"file":"brief.js","sourceRoot":"","sources":["../../../src/core/brief/brief.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The adoption brief: what an agent needs in order to write one service's\n * baseline documentation, and what will be checked when it does.\n *\n * `adopt` was specified for years as \"read the code, emit the C4\". It is not\n * being built, because it cannot be: nothing deterministic reads a legacy\n * service and says what its architecture MEANS, and a guessed model is worse\n * than no model — everyone downstream has to re-derive it to know whether to\n * believe it.\n *\n * So loam takes the half of the job that IS deterministic. It states the work\n * precisely (which files, in which grammar, bound to which existing elements),\n * it says exactly which checks the result will face, and then it says which\n * checks do not exist — because everything in that last list is honesty no\n * validator will ever supply, and an agent that does not know where the checking\n * stops will assume it never does.\n *\n * Nothing here reads code. `sources` in the frontmatter is the only line that\n * ties the result to a repository, which is why the brief argues for it instead\n * of merely listing it.\n */\nimport { existsSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport { landscapePath, type ServicePaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport { specAdoption } from \"../repo/repo.js\";\nimport { entryIs } from \"../repo/tree/fs.js\";\nimport { VALIDATE_CHECKS, type BriefCheck } from \"./checks.js\";\nimport { UNCHECKED } from \"./unchecked.js\";\nimport { landscapeContext, type LandscapeContext } from \"./landscape.js\";\nimport { landscapeArtifact } from \"./map/owed.js\";\nimport { exampleFor, extendPoint, FQN } from \"./map/extend.js\";\nimport { ARTIFACTS, USECASES_DIR, type BriefTarget } from \"./targets.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { EXTERNAL_TAG } from \"../vocabulary/maturity.js\";\n\n/* ------------------------------------------------------------------ */\n/* The walk — what to read, in what order, and where each finding lands */\n/* ------------------------------------------------------------------ */\n\nexport interface WalkStop {\n /** What to open, named as a thing to go and find rather than a file name. */\n where: string;\n /** What to take out of it. */\n find: string;\n /** The artifacts this stop feeds, by `artifact` name from `targets[]`. */\n lands: string[];\n}\n\n/**\n * The architecture walk: the order a service is read in, and what each stop is\n * for.\n *\n * The brief said \"read the code: entry points, HTTP routes and handlers,\n * published events, config, deploy manifests, tests\" — one sentence, six nouns,\n * no order and no landing site. What came back matched the sentence: the HTTP\n * surface documented well, because it is the noun an agent recognises fastest,\n * and the scheduler, the consumer group and the outbox missing entirely. Not\n * because they were judged unimportant — because nothing said to go and look,\n * and every check loam owns is computed from what was written rather than from\n * what is there. `UNCHECKED` has said COMPLETENESS is unverifiable since the\n * beginning; that is an argument for stating the sweep, not for leaving it\n * implied.\n *\n * The order is deliberate and it is not the order a reader browses in. Stops 1\n * and 2 fix the SHAPE of the service — which processes exist at all — before\n * any surface is enumerated, because an agent that starts at the HTTP routes\n * has already concluded the service is an API by the time it meets the\n * scheduler. Everything after them is one surface each.\n *\n * `lands` names artifacts rather than describing them, because the two lists\n * are joined: test/agent-contract.test.ts requires every name here to be a\n * target the same brief hands over, so a stop can never feed a file nobody was\n * asked to write.\n */\nexport const WALK: WalkStop[] = [\n {\n where: \"the build and dependency manifests — pom.xml, build.gradle, package.json, go.mod, requirements.txt, and whatever the repo's own README calls the build\",\n find: \"what this service is built from, and every client library it links against. A broker client, an HTTP client, a database driver: each one is a dependency that has to appear as an edge or a container later, and this list is the only place they are all named at once. A library whose SEMANTICS decide behaviour — a retry budget, a delivery guarantee, a serialization format — is an arch requirement (arch.spec.md), not a fact to leave in your head.\",\n lands: [\"model.likec4\", \"runbook.md\", \"arch.spec.md\"],\n },\n {\n where: \"the entry points — main/Application/server bootstrap, the DI or module wiring, and anything the deploy manifests actually start\",\n find: \"how many PROCESSES this service is, and what each one is. An API, a worker, a scheduler, a consumer — a service that is three of those and documented as one is the most common way a baseline is wrong, and it is wrong in a shape no later check can see.\",\n lands: [\"model.likec4\"],\n },\n {\n where: \"the HTTP surface — every route, controller, handler and middleware, counted\",\n find: \"the operation set, in full, and inside each operation the DECISIONS its code makes: the permission and role checks — each one is a `Requires:` entry, and a name `architecture/permissions.yaml` does not declare yet is a line to ADD to that shared file, never to invent locally — the fields required only when another field is present, the defaults and normalisations applied before anything is stored, the transitions a request is refused for. Count both — operations, and branches per operation — before you write, and say both counts in the hand-back. `api.ungoverned` can tell you an operation you WROTE has no requirement; nothing at all can tell you about the twelve you never wrote down, or about the thirty branches inside the one you did. A guard is not implementation detail: what a service REFUSES is behaviour its caller observes and a test pins, and it is the half of a legacy service that lives only in the code and in one developer's head.\",\n lands: [\"openapi.yaml\", \"spec.md\"],\n },\n {\n where: \"the message surface — producers, consumers, listener annotations, topic and queue names, the outbox table and its relay\",\n find: \"every message this service puts on and takes off the bus, with its direction, and — on the consuming side — the DECISIONS the handler makes about a message before it acts on one. The direction is the half that gets lost: a consumer documented as a producer is a contract the whole fleet then joins against backwards. The guards are the half nobody looks for at all: which messages are filtered out and on what field, what happens to one whose entity has since been deleted, what is idempotency-keyed against a replay, what is dead-lettered rather than retried. Each of those is a scenario, and none of them appears anywhere in the contract — asyncapi.yaml describes the message, never what this service refuses to do with it.\",\n lands: [\"asyncapi.yaml\", \"landscape.likec4\"],\n },\n {\n where: \"the scheduled and background work — cron entries, timers, retry loops, reconcilers, migrations that run at boot\",\n find: \"behaviour no request ever triggers, and the conditions under which it does or does not run. It has requirements like anything else, and it is invisible to every surface above — which is why a baseline written from routes alone describes a service that does half of what it does. Take the decisions with it: which rows a reconciler SELECTS and which it skips, what a retry loop gives up on and after how long, what a migration does when it finds the state it is meant to fix already fixed. A scheduled job documented as 'runs nightly' is a requirement with no behaviour in it.\",\n lands: [\"spec.md\", \"arch.spec.md\"],\n },\n {\n where: \"the state — datastores, caches, buckets, and the schema or migration directory that says what is in them\",\n find: \"what this service OWNS versus what it borrows. Ownership is the fact the C4 is for, and it is the one nobody can reconstruct later from the code without re-reading all of it.\",\n lands: [\"model.likec4\"],\n },\n {\n where: \"the outbound calls — HTTP clients, generated stubs, gRPC channels, and the config that says which host each one points at\",\n find: \"who this service calls, and which operation each call names. These become the fleet map's edges; an edge missing here is a dependency the fleet cannot see, and `loam explore` will under-report the blast radius of every future change to the callee. An edge that only configuration attests is PROVISIONAL until the runtime stop: hold it until the deploy manifests confirm nothing switches that client off.\",\n lands: [\"landscape.likec4\"],\n },\n {\n where: \"the runtime — config and env vars, deploy manifests, pipelines, alert rules, dashboards\",\n find: \"how it is run and what pages whom. Alerts are the honest source for health.yaml: an SLO nobody agreed to is invention, but an alert that already fires is a fact somebody wrote down. Deployment manifests OVERRIDE configuration files, and they usually live in another repository — go and find them before any dependency that only configuration attests goes in the model, and check for an environment variable that disables the feature. A capability switched off at deploy time is not a dependency, however completely the config wires it: delete its edge and container, and record it in runbook.md's 'configured but not a dependency, and why' list. A manifest read from another repository is named in the runbook's prose — never in `sources`, which may only hold paths of this service's own repository. An EFFECTIVE configuration value behaviour depends on — the timeout the chart sets, the flag that switches a code path — is an arch requirement in arch.spec.md, with a Covers: line naming what it decides; loam holds its coverage even though it can never check the value.\",\n lands: [\"runbook.md\", \"health.yaml\", \"arch.spec.md\"],\n },\n {\n where: \"the tests, integration and component ones first\",\n find: \"the behaviours somebody already thought worth pinning. This is the PRIMARY source for scenarios, not the last sweep: a behaviour a test already pins is written FROM that test, and the code is read only for what no test covers. A test suite is the only part of a legacy service that states intent rather than mechanism, and a scenario derived from one is the only kind that starts out true. Carry the shape across as well as the content — a parameterised test (`@ParameterizedTest`, `@parametrize`, `test.each`) is one scenario with a table, not one scenario per case. Then say in the hand-back how well this service is covered and how many of its tests became scenarios: that number is the ceiling on what this whole baseline can be worth, and nothing downstream can compute it.\",\n lands: [\"spec.md\", \"arch.spec.md\"],\n },\n];\n\n/**\n * The stop that closes the walk. Separate from `WALK` because it is not a place\n * to go — it is the question asked once the walking is done, and the only\n * defence against a sweep that felt thorough because the parts it covered were\n * covered well.\n */\nexport const WALK_CLOSE =\n \"Then list the directories you did NOT open, and say why for each. `loam validate --service <id>`, run inside the service's own repo, reports the same list back as `sources.unwalked` — it compares your `sources` against the files git tracks. A directory you deliberately skipped is a sentence in the hand-back; a directory you skipped without noticing is the gap this walk exists to find.\";\n\n/* ------------------------------------------------------------------ */\n/* The frontmatter */\n/* ------------------------------------------------------------------ */\n\nexport interface FrontmatterBrief {\n /** Field -> what to write in it. */\n fields: Record<string, string>;\n /** Fields to leave alone entirely. */\n never: string[];\n /** Why `sources` carries the weight. */\n why: string;\n}\n\nconst FRONTMATTER_BRIEF: FrontmatterBrief = {\n fields: {\n service: \"the directory this file lives under, exactly. A mismatch is an error (`frontmatter.field-mismatch`).\",\n status:\n \"`draft`, always. `verified` is a human's word, stamped by `loam vouch --service <id>` run inside the service's own repo — never written by hand.\",\n owner: \"the team or person who answers for this service.\",\n sources:\n \"the paths in THIS SERVICE'S repository you actually read to write the document — files and directories only, a directory covering everything beneath it; glob patterns are refused. Not the paths a reader would expect to have been read. This is NOT a reading list for a human: nobody follows these, and nothing about the document asks them to.\",\n },\n // `vouch_scope` is in this list for a sharper reason than its siblings: the\n // others are stamps an agent could only forge, while this one is a stamp an\n // agent could DELETE — and deleting it turns a vouch that read four sections\n // into one that read the document, on every surface at once.\n never: [\"last_verified\", \"sources_digest\", \"content_digest\", \"sources_files\", \"vouch_scope\"],\n why:\n \"`sources` is digest input, not a citation. It is the only mechanical tie between this document and the code — everything else loam checks is internal consistency, and a corpus can agree with itself perfectly while describing nothing that exists. `loam validate`, run inside the service's repo, checks every listed path is still there; `loam vouch` hashes their CONTENT so that a later `validate` can say the code has moved since anyone read it. That hash is the entire point, and it is why padding the list costs something real rather than merely being untidy: a path nobody read still gets hashed, so its next change reports as drift in a document it never described, and a path that WAS read and went unlisted moves under the document in silence. Neither failure is visible to a reader; both are decided by whether this list is honest.\",\n};\n\nexport interface Brief {\n service: string;\n /** Absolute docs repo root — the anchor the repo-relative paths hang off. */\n docsDir: DocsDir;\n /** Repo-relative path of services/<id>/. */\n path: string;\n targets: BriefTarget[];\n /** The order to read the service in, and what each stop feeds. */\n walk: WalkStop[];\n /** The question the walk ends on — see `WALK_CLOSE`. */\n walkClose: string;\n landscape: LandscapeContext;\n frontmatter: FrontmatterBrief;\n checks: BriefCheck[];\n unchecked: string[];\n /** The one instruction that outranks the rest. */\n rule: string;\n}\n\nconst NEVER_OVERWRITE =\n \"Do not overwrite an artifact that already exists. Read it, diff your findings against it, and report what disagrees — a document somebody wrote is evidence, and replacing it destroys the only copy of what they knew.\";\n\n/**\n * Assemble the brief for one service. Reads the docs repo; writes nothing.\n * `at` overrides where the artifacts are briefed to land — `adopt\n * --subsystem`'s target inside a group — and is only ever passed for a\n * service the enumeration does NOT answer: an existing service is briefed\n * where it already lives, because the brief's one rule is to never overwrite,\n * and a second address would brief a duplicate.\n */\nexport async function serviceBrief(\n docsDir: DocsDir,\n service: PathableService,\n at?: ServicePaths,\n): Promise<Brief> {\n const paths = at ?? (await locateServicePaths(docsDir, service));\n const rel = (abs: string): string => relative(docsDir, abs).split(/[\\\\/]/).join(\"/\");\n\n // Read before the artifact loop, not after it: what the fleet already says\n // about this service decides whether it owes an API contract at all. The\n // model path travels with it — located through the enumeration above, never\n // joined at `services/<id>/` — so the edgeless state can name the calls the\n // service's own model attests.\n const landscape = await landscapeContext(docsDir, service, paths);\n const owesApi = apiExpected(landscape);\n\n // The one id the model target's grammar and example are written against,\n // resolved here because `ARTIFACTS` is static on purpose (a target list that\n // varied per repository would be a second thing to keep true) and because this\n // is the only place the fleet map has already been read.\n const extend = extendPoint(landscape);\n\n const targets: BriefTarget[] = [];\n for (const { key, ...artifact } of ARTIFACTS) {\n // `adrs/` and `usecases/` are DIRECTORIES, and an empty one is not an\n // existing artifact — reporting it as present would tell the agent to diff\n // against nothing, and for `usecases/` it would also tell the agent an\n // optional slot was already taken.\n //\n // `usecases/` is the one target with no `ServicePaths` entry, and that is\n // deliberate rather than an omission: nothing loam checks JOINS on that\n // path. The slot a service's flows are graded out of is its whole LikeC4\n // project (`core/usecases/service/flows.ts` reads every `.likec4` under the\n // directory), so `usecases/<name>.likec4` is a convention this brief\n // publishes, not a path a writer writes — and `targets.ts` is where a\n // convention belongs.\n const ext = key === \"adrsDir\" ? \".md\" : key === \"usecasesDir\" ? \".likec4\" : null;\n const at = key === \"usecasesDir\" ? join(paths.dir, USECASES_DIR) : paths[key];\n const exists = ext === null ? existsSync(at) : await hasEntry(at, ext);\n const partial = key === \"spec\" && exists && (await specAdoption(at)) === \"partial\";\n targets.push({\n ...artifact,\n ...(partial ? { partial: true as const } : {}),\n ...(key === \"openapi\" ? { required: owesApi } : {}),\n // Every `<fqn>` in the model target, replaced by the element this service\n // actually extends. The placeholder is left literal in `targets.ts` — the\n // brief is handed to an agent that will WRITE the file, and\n // `extend <fqn> {` copied verbatim is a document that does not parse.\n shape: artifact.shape.map((rule) => rule.replaceAll(FQN, extend.id)),\n ...(artifact.example === undefined ? {} : { example: exampleFor(artifact.example, extend, service) }),\n path: ext === null ? rel(at) : `${rel(at)}/`,\n exists,\n action: exists ? \"diff\" : \"create\",\n });\n }\n\n // The eighth target is the fleet map, and it is conditional on TWO facts: an\n // element resolving to the service, and an edge touching it. A service the\n // landscape already draws AND reaches owes it nothing, and briefing an edit\n // there would invite an agent to draw a second box for a service that has\n // one. A service with a box and no edge — what `loam seed` leaves for every\n // service nobody listed under `calls:` — owes it the edges, and until\n // 2026-09-03 element existence alone read as \"the map is complete\" here, so\n // the brief declared the map finished for exactly the services it had not\n // reached. Everything else about the target is unconditional — the file is\n // shared, so `action` is `edit` whenever it exists and `create` only for the\n // very first service of a brand-new docs repo.\n //\n // `modelled === false` and not `!== true`: `null` is \"loam could not read the\n // map\", and it fell through the same branch as \"the map genuinely draws\n // nothing for this service\". The brief then handed over the UNDRAWN target —\n // whose shape rule is \"add one top-level element bound to its directory\" and\n // whose example is a block to splice — for services the map already declares\n // and binds, and an agent that spliced it earned `landscape.binding-duplicate`\n // for doing exactly what the brief asked. With the map unreadable there is no\n // write to ask for; `landscape.instruction` carries the whole answer and names\n // the documents that failed.\n if (landscape.modelled === false || landscape.touched === false) {\n targets.push({\n ...landscapeArtifact({\n service,\n expects: landscape.expects,\n present: landscape.present,\n servicePath: rel(paths.dir),\n // The existing element's id, in the edgeless state only: it switches\n // the example from a block to edges on that element.\n // …and whether it is `#external`, which decides what the shape rule may\n // promise: `landscape.service-isolated` never fires on such an element.\n ...(landscape.touched === false && landscape.elements[0] !== undefined\n ? { elementId: landscape.elements[0].id, external: landscape.elements[0].tags.includes(EXTERNAL_TAG) }\n : {}),\n attested: landscape.attested,\n }),\n path: rel(landscapePath(docsDir)),\n exists: landscape.present,\n action: landscape.present ? \"edit\" : \"create\",\n });\n }\n\n return {\n service,\n docsDir,\n path: rel(paths.dir),\n targets,\n walk: WALK,\n walkClose: WALK_CLOSE,\n landscape,\n frontmatter: FRONTMATTER_BRIEF,\n checks: VALIDATE_CHECKS,\n unchecked: UNCHECKED,\n rule: NEVER_OVERWRITE,\n };\n}\n\n/**\n * Does this directory already hold an artifact of the given extension? The\n * \"exists\" question for the two DIRECTORY targets — `adrs/` (`.md`) and\n * `usecases/` (`.likec4`) — where an empty or absent directory is the same\n * answer, because neither is something to diff against.\n */\nasync function hasEntry(dir: string, ext: string): Promise<boolean> {\n if (!existsSync(dir)) return false;\n const entries = await readdir(dir, { withFileTypes: true });\n // `entryIs` for the symlink case: a Dirent never follows one, so `isFile()`\n // is false for a symlinked ADR and the directory would read as empty — the\n // same rule `services/` and `features/` are enumerated by.\n return entries.some((e) => e.name.endsWith(ext) && entryIs(dir, e, \"file\"));\n}\n\n/**\n * Does the fleet expect an API of this service?\n *\n * The brief marked `openapi.yaml` required unconditionally, and printed the\n * absence as `MISSING` in capitals — while `loam validate` and `loam list`\n * derive the opposite from the same landscape (`service.no-openapi` stays quiet\n * when nothing calls the service, and `list`'s maturity ladder stops demanding\n * a contract). For a UI, a worker or a cron the brief was therefore telling an\n * agent to INVENT an API contract into the source of truth — the one class of\n * guess this whole module exists to refuse, and the one the brief itself names\n * in `openapi.yaml`'s shape rules (`ARTIFACTS`, targets.ts).\n *\n * Positive evidence only, exactly as `list` reads it: a landscape that is\n * absent or unreadable proves nothing about who calls this service, so the\n * contract is still owed. `expects` is the ops on inbound edges, which is\n * `list`'s `called` set seen from one service.\n *\n * `callersKnown` and not `parses`, because since W4 `parses` answers about the\n * whole `architecture/` PROJECT: a broken palette or use case beside a landscape\n * loam reads perfectly made this discard the map and demand an OpenAPI contract\n * of a browser UI — inventing an API into the source of truth, which is the one\n * class of guess this module exists to refuse. `callersKnown` is the narrower\n * fact this question actually needs, and it is the one `list` and `context` read.\n */\nfunction apiExpected(landscape: LandscapeContext): boolean {\n return !landscape.callersKnown || landscape.expects.length > 0;\n}\n"]}