@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":"service.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The service target: one adopted service graded across every axis it owns.\n *\n * This module is the ORDER, not the checks. It reads what more than one axis\n * joins through — the C4 model, the living landscape and its resolver, the two\n * requirement documents — hands each of those to the axis that grades it, and\n * pushes the findings in the sequence the report is read in. The axes\n * themselves cannot see each other, which is the point: `./api.js` decides once\n * whether there is a contract to read, and `./spine.js` is handed the answer\n * instead of re-deriving it against a file it would open a second time.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { loadFile, type Elem, type Rel } from \"../../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile, permissionsPath } from \"../../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport { readVocabulary } from \"../../../core/permissions/permissions.js\";\nimport { readCapabilityVocabulary } from \"../../../core/capabilities/capabilities.js\";\nimport { capabilityRequirementIndex, capabilityUnknownFindings } from \"../../../core/capabilities/findings.js\";\nimport { realizesStaleFindings, realizesUnknownFindings } from \"../../../core/capabilities/realizes/findings.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../../core/document/parse.js\";\nimport { requiresUnknownFindings } from \"../checks/requirements.js\";\nimport { listServices } from \"../../../core/repo/repo.js\";\nimport { type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { closeIds } from \"../../../core/c4/arch.js\";\nimport { serviceProvenance } from \"../../../core/provenance/findings.js\";\nimport { gherkinFindings } from \"../../../core/gherkin/stale.js\";\nimport { gherkinRoot } from \"../../../core/gherkin/stamp.js\";\nimport { UnsafePathError } from \"../../../core/kernel/path-safety.js\";\nimport { interruptedCommitFinding } from \"../../../core/staging/recovery/finding.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { errorText } from \"../checks/vocabulary.js\";\nimport { serviceLinkFindings } from \"../links/corpus.js\";\nimport { sourceScopeFindings } from \"../checks/sources.js\";\nimport { apiAxisFindings, generatedContractFindings } from \"./api.js\";\nimport { evidencePinFindings } from \"./evidence-pins.js\";\nimport { spineFindings } from \"./spine.js\";\nimport { eventAxisFindings } from \"./events/events.js\";\nimport { archAxisFindings, readServiceSpecs } from \"./specs.js\";\nimport { unknownDirectiveFindings } from \"../../../core/document/grammar/directives.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\n/**\n * A service's absences are graded by what each one proves.\n *\n * `service.unknown` (error): the directory itself does not exist — the id is a\n * typo until proven otherwise, so the hint names ids that DO exist and never\n * `loam adopt`, which would faithfully document the misspelling.\n * `service.no-model` (error): the directory is real but the C4 center is not —\n * adopt is the right hint. Every check that does not read the model still runs\n * (spec coverage, the arch axis, provenance, gherkin staleness): archive\n * creates exactly this state for a new service, and those signals must not go\n * quiet there.\n * `service.no-spec` / `service.no-openapi` (warn): the adopt brief marks both\n * required, but a fleet mid-rollout legitimately has part-adopted services —\n * the absence must stay visible without gating CI for months. The openapi warn\n * keeps quiet when the landscape proves nobody calls an operation on this\n * service: a worker with no API is not missing one.\n * `api.ops-unlinked` (warn): an OpenAPI and requirements that never name each\n * other pass every cross-axis check vacuously — a repo migrated from OpenSpec\n * does exactly that by default, and vacuous is not the same as checked.\n */\ninterface ServiceCheck {\n docsDir: DocsDir;\n service: PathableService;\n /** The service's own repo, when loam is standing in it. Undefined from the docs repo. */\n repoDir?: string;\n /** The build outputs `loam.json` names, when this repo declares any. */\n contracts?: { openapi?: string };\n /** The living landscape under --all; undefined means \"load it if you need it\", null means \"there is none\". */\n preloaded?: LoadedDoc | null;\n gherkinDir?: string;\n fleet?: FleetContext;\n /**\n * True when this run ALSO emits a `landscape` target. It decides one thing:\n * whether a landscape parse error's details are repeated here. Under --all\n * they are not — the landscape target already carries them once, and N copies\n * of one parser's cascade is the whole report.\n */\n landscapeReported?: boolean;\n}\n\n\nexport async function validateService(check: ServiceCheck): Promise<TargetReport> {\n const { docsDir, service, repoDir, preloaded, gherkinDir, contracts, fleet } = check;\n // The spine and event joins below all ask the landscape's resolver one\n // question: does this edge's endpoint resolve into the directory being\n // validated? The resolver answers with DOCUMENT text and `service` is the\n // repository's own name — disjoint brands, so `===` between them is TS2367.\n // Widening to `string` is not a cast: membership questions cross the\n // provenance line through plain-string containers by design (kernel/ids/service.ts),\n // and nothing pathable ever comes back out of a comparison.\n const me: string = service;\n const findings: Finding[] = [];\n const paths = await locateServicePaths(docsDir, service, fleet);\n const report: TargetReport = {\n kind: \"service\",\n id: service,\n path: relative(docsDir, paths.dir).replaceAll(\"\\\\\", \"/\"),\n findings,\n };\n\n // A directory that does not exist is a different fact from a directory with\n // everything missing: validating a typo must say \"typo\", not \"unadopted\".\n if (!existsSync(paths.dir)) {\n const close = closeIds(service, (await listServices(docsDir, fleet)).map((s) => s.id));\n findings.push({\n severity: \"error\",\n code: \"service.unknown\",\n message:\n `No service directory at ${paths.dir}.` +\n (close.length > 0\n ? ` Did you mean: ${close.join(\", \")}?`\n : \" `loam list services` shows what exists.\"),\n text: { marker: false },\n });\n return report;\n }\n\n // C4 model. Its absence is an error — this is where `adopt` comes in — but it\n // must NOT silence the rest of the gate stack: `loam archive` of a feature\n // introducing a new service creates exactly this state (spec.md, arch.spec.md,\n // openapi.yaml, no model.likec4), and an early return here suspended arch\n // coverage, health.uncovered, provenance (content_digest included) and the\n // gherkin staleness chain for the very services vouch had just promised them\n // to. So the finding is emitted and the walk CONTINUES; only the checks that\n // read the model itself are guarded.\n const hasModel = existsSync(paths.model);\n let elements: Elem[] = [];\n let relationships: Rel[] = [];\n if (!hasModel) {\n findings.push({\n severity: \"error\",\n code: \"service.no-model\",\n message: `No C4 model at ${paths.model}. Run \\`loam adopt\\` for '${service}' first.`,\n text: { marker: false },\n });\n } else {\n const model = fleet === undefined ? await loadFile(paths.model) : await fleet.loadLikeC4(paths.model);\n elements = model.elements;\n relationships = model.relationships;\n if (model.errors.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"c4.invalid\",\n message: `${service}: C4 model has ${model.errors.length} error(s)`,\n details: model.errors.map(errorText),\n });\n } else {\n findings.push({\n severity: \"ok\",\n code: \"c4.valid\",\n message: `${service}: C4 model valid (${elements.length} elements · ${relationships.length} relationships)`,\n });\n }\n }\n\n // The living landscape, parsed at most once per run: under --all the caller\n // hands in the doc it already loaded, single-service runs load on demand. It\n // serves two checks below — the no-openapi grace and the spine.\n const land =\n preloaded ??\n (existsSync(landscapeFile(docsDir))\n ? fleet === undefined\n ? await loadFile(landscapeFile(docsDir))\n : await fleet.loadLikeC4(landscapeFile(docsDir))\n : null);\n\n // Which service directories actually exist — the positive evidence\n // `serviceOf` needs to resolve an edge drawn into a modelled CONTAINER\n // (`paymentService.api`) back to the service that owns it. Without it every\n // such edge resolves to the container's own title, i.e. to a service nobody\n // has ever adopted, and drops out of the spine unnoticed.\n const known = new Set((await listServices(docsDir, fleet)).map((s) => s.id));\n\n\n // The living landscape's element→service resolver, container-aware and\n // memoized. Every question below that asks \"does this edge point at me?\"\n // asks it through this one function, so the no-openapi grace, the HTTP spine\n // and the event spine can never disagree about which edges are inbound.\n const landSvcOf = land === null ? null : serviceResolver(land.elements, known);\n\n const specs = await readServiceSpecs({ service, paths, fleet });\n findings.push(...specs.findings);\n const { reqs, livingReqs, archText, archReqs } = specs;\n\n const api = await apiAxisFindings({\n service,\n me,\n paths,\n reqs,\n livingReqs,\n land,\n landSvcOf,\n fleet,\n });\n findings.push(...api.findings);\n findings.push(\n ...spineFindings({\n service,\n me,\n land,\n landSvcOf,\n contract: api.contract,\n landscapeReported: check.landscapeReported,\n }),\n );\n findings.push(\n ...(await eventAxisFindings({\n docsDir,\n service,\n me,\n paths,\n reqs,\n livingReqs,\n archReqs,\n land,\n landSvcOf,\n known,\n fleet,\n })),\n );\n findings.push(\n ...(await archAxisFindings({\n service,\n paths,\n archText,\n archReqs,\n elements,\n relationships,\n land,\n known,\n })),\n );\n\n // The authorization axis. Both requirement documents are graded against one\n // fleet vocabulary — a permission is a fleet fact, and an arch requirement\n // gates on one exactly as a business requirement does. The capability axis\n // rides the same loop against ITS fleet vocabulary; under --all the fleet\n // memo makes that one parse for the whole run.\n const vocabulary = await readVocabulary(permissionsPath(docsDir));\n const capabilities =\n fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);\n // The requirement-level index behind `Realizes:`. Built once for both axes,\n // and behind the same ladder `capabilityUnknownFindings` applies — an absent\n // or unreadable vocabulary means no reads and no grades at all, so a fleet\n // that has not adopted the axis pays nothing for a line it never wrote.\n const capabilityReqs = await capabilityRequirementIndex(\n capabilities,\n fleet === undefined ? async (p) => parseRequirements(await readRequirementsDocument(p)) : (p) => fleet.readRequirements(p),\n );\n for (const [label, docReqs] of [\n [\"spec.md\", reqs],\n [\"arch.spec.md\", archReqs],\n ] as const) {\n const target = { where: `${service}: ${label}`, subject: service };\n findings.push(...requiresUnknownFindings(docReqs, target, vocabulary));\n findings.push(...capabilityUnknownFindings(docReqs, target, capabilities));\n findings.push(...realizesUnknownFindings(docReqs, target, capabilityReqs));\n findings.push(...realizesStaleFindings(docReqs, target, capabilityReqs));\n // The grammar guard, last in the loop: it grades the KEYS the three checks\n // above resolve values from, so a line none of them saw is reported here\n // rather than staying invisible for having produced nothing to check.\n findings.push(...unknownDirectiveFindings(docReqs, target));\n }\n\n // The links this service's own documents write. Its place in the order is\n // deliberate: every axis above joins through a LINE loam defined, and this one\n // grades the join an author writes in prose — so it reads last among the\n // document checks and adds no input any of them depend on.\n findings.push(...(await serviceLinkFindings({ docsDir, subject: service, fleet }, paths)));\n\n // Provenance last: who vouched for this, and what code it was written from.\n findings.push(...(await serviceProvenance(docsDir, service, { repoDir, fleet })));\n findings.push(...(await sourceScopeFindings(docsDir, service, repoDir, fleet)));\n // The build's own contract against the committed copy. Service-repo only:\n // from the docs repo there is no build output to read, and the family stays\n // silent rather than guessing — the same rule `sources` provenance follows.\n if (repoDir !== undefined) {\n findings.push(...(await generatedContractFindings(service, repoDir, contracts?.openapi, paths.openapi)));\n }\n\n // The generated-gherkin freshness chain, service-repo-scoped like sources.*:\n // it needs the repo (the suite lives there), and it stays quiet until\n // <gherkinDir>/loam/ exists — a service that never generated has not opted in.\n findings.push(...(await gherkinFindings({ docsDir, service, repoDir, gherkinDir, fleet })));\n\n // The evidence-pin re-check, the same service-repo-scoped family: recorded\n // verification citations graded against THIS working tree, quiet from the\n // docs repo — sources.unverifiable-from-here already names that blind spot.\n findings.push(...(await evidencePinFindings(docsDir, service, repoDir, fleet)));\n\n // The one journal that does NOT live in the docs repo: gherkin commits into\n // the service repo's emission root. A half-committed suite graded by the\n // freshness chain above reads as merely stale — warn-severity, which never\n // gates — while doctor calls the same state a blocker. Same finding, same\n // code, led rather than appended, exactly as validate leads with the docs\n // repo's own journal.\n if (repoDir !== undefined) {\n try {\n const interrupted = await interruptedCommitFinding(gherkinRoot(repoDir, gherkinDir));\n if (interrupted !== null) findings.unshift(interrupted);\n } catch (err) {\n // An unsafe gherkinDir is `loam gherkin`'s refusal to make, with its\n // own sentence; grading declines to scan what it cannot trust.\n if (!(err instanceof UnsafePathError)) throw err;\n }\n }\n\n return report;\n}\n"]}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The service target: one adopted service graded across every axis it owns.\n *\n * This module is the ORDER, not the checks. It reads what more than one axis\n * joins through — the C4 model, the living landscape and its resolver, the two\n * requirement documents — hands each of those to the axis that grades it, and\n * pushes the findings in the sequence the report is read in. The axes\n * themselves cannot see each other, which is the point: `./api.js` decides once\n * whether there is a contract to read, and `./spine.js` is handed the answer\n * instead of re-deriving it against a file it would open a second time.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport { type ServiceModel } from \"../../../core/c4/service-model/load.js\";\nimport { serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile, permissionsPath } from \"../../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport { readVocabulary } from \"../../../core/permissions/permissions.js\";\nimport { capabilityRequirementIndex, capabilityUnknownIssues } from \"../../../core/capabilities/findings.js\";\nimport { realizesStaleFindings, realizesUnknownIssues } from \"../../../core/capabilities/realizes/findings.js\";\nimport { requiresUnknownFindings } from \"../checks/requirements.js\";\nimport { listServices } from \"../../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { serviceProvenance } from \"../../../core/provenance/findings.js\";\nimport { gherkinFindings } from \"../../../core/gherkin/stale.js\";\nimport { gherkinRoot } from \"../../../core/gherkin/stamp.js\";\nimport { UnsafePathError } from \"../../../core/kernel/path-safety.js\";\nimport { repoPath } from \"../../../core/envelope/json.js\";\nimport { interruptedCommitFinding } from \"../../../core/staging/recovery/finding.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { serviceLinkFindings } from \"../links/corpus.js\";\nimport { sourceScopeFindings } from \"../checks/sources.js\";\nimport { unknownServiceFinding } from \"../checks/introduced.js\";\nimport { apiAxisFindings } from \"./api.js\";\nimport { generatedContractFindings } from \"./contracts/generated.js\";\nimport { evidencePinFindings } from \"./evidence-pins.js\";\nimport { spineFindings } from \"./spine.js\";\nimport { eventAxisFindings } from \"./events/events.js\";\nimport { archAxisFindings, readServiceSpecs } from \"./specs.js\";\nimport { modelFindings } from \"./model/grade.js\";\nimport { divergedFindings } from \"./model/diverged.js\";\nimport { requirementIdsOf, serviceUseCaseFindings } from \"./usecases/usecases.js\";\nimport { readServiceFlows, type ServiceFlowScan } from \"../../../core/usecases/service/flows.js\";\nimport { unknownDirectiveFindings } from \"../../../core/document/grammar/directives.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\n/**\n * A service's absences are graded by what each one proves.\n *\n * `service.unknown` (error): the directory itself does not exist — the id is a\n * typo until proven otherwise, so the hint names ids that DO exist and never\n * `loam adopt`, which would faithfully document the misspelling. The proof\n * otherwise is a feature in flight that introduces it; the hint then names\n * that feature instead (`../checks/introduced.ts`).\n * `service.no-model` (error): the directory is real but the C4 center is not —\n * adopt is the right hint. Every check that does not read the model still runs\n * (spec coverage, the arch axis, provenance, gherkin staleness): archive\n * creates exactly this state for a new service, and those signals must not go\n * quiet there.\n * `service.no-spec` / `service.no-openapi` (warn): the adopt brief marks both\n * required, but a fleet mid-rollout legitimately has part-adopted services —\n * the absence must stay visible without gating CI for months. The openapi warn\n * keeps quiet when the landscape proves nobody calls an operation on this\n * service: a worker with no API is not missing one.\n * `api.ops-unlinked` (warn): an OpenAPI and requirements that never name each\n * other pass every cross-axis check vacuously — a repo migrated from OpenSpec\n * does exactly that by default, and vacuous is not the same as checked.\n */\ninterface ServiceCheck {\n docsDir: DocsDir;\n service: PathableService;\n /** The service's own repo, when loam is standing in it. Undefined from the docs repo. */\n repoDir?: string;\n /** loam's own files in `repoDir` (`loamOwnedFiles`) — outside the walk check's denominator. */\n loamOwned?: readonly string[];\n /** The build outputs `loam.json` names, when this repo declares any. */\n contracts?: { openapi?: string };\n /** The living landscape under --all; undefined means \"load it if you need it\", null means \"there is none\". */\n preloaded?: LoadedDoc | null;\n gherkinDir?: string;\n /**\n * The read index this target reads the fleet map and the model through.\n *\n * Required, and every validate mode builds one before it grades anything —\n * the single-service run included (`validate.ts`) — rather than this target\n * keeping a second reading rule for a run that arrived without one:\n * `serviceModel` needs the enumeration and a document memo to answer at\n * all, and both live there. Request-scoped like every other `FleetContext`\n * — created for the invocation, dropped with it.\n */\n fleet: FleetContext;\n /**\n * True when this run ALSO emits a `landscape` target. It decides one thing:\n * whether a landscape parse error's details are repeated here. Under --all\n * they are not — the landscape target already carries them once, and N copies\n * of one parser's cascade is the whole report. Under --all --base the\n * landscape is a target only when the branch changed `architecture/`, so the\n * caller states this per run (`validate.ts`'s `serviceTarget`) rather than\n * inferring it from `preloaded`: a map handed in is not a map reported.\n */\n landscapeReported?: boolean;\n}\n\nexport async function validateService(check: ServiceCheck): Promise<TargetReport> {\n const { docsDir, service, repoDir, preloaded, gherkinDir, contracts, fleet } = check;\n // The spine and event joins below all ask the landscape's resolver one\n // question: does this edge's endpoint resolve into the directory being\n // validated? The resolver answers with DOCUMENT text and `service` is the\n // repository's own name — disjoint brands, so `===` between them is TS2367.\n // Widening to `string` is not a cast: membership questions cross the\n // provenance line through plain-string containers by design (kernel/ids/service.ts),\n // and nothing pathable ever comes back out of a comparison.\n const me: string = service;\n const findings: Finding[] = [];\n const paths = await locateServicePaths(docsDir, service, fleet);\n // Repo-relative, `/`-separated: the report's own scope, and the spelling\n // every service-local use-case finding opens with.\n const treePath = relative(docsDir, paths.dir).replaceAll(\"\\\\\", \"/\");\n const report: TargetReport = {\n kind: \"service\",\n id: service,\n path: treePath,\n findings,\n };\n\n // A directory that does not exist is a different fact from a directory with\n // everything missing: validating a typo must say \"typo\", not \"unadopted\" —\n // and a service a feature in flight introduces is neither (checks/introduced.ts).\n if (!existsSync(paths.dir)) {\n findings.push(await unknownServiceFinding(docsDir, service, treePath, fleet));\n return report;\n }\n\n // Which service directories actually exist — the positive evidence\n // `serviceOf` needs to resolve an edge drawn into a modelled CONTAINER\n // (`paymentService.api`) back to the service that owns it. Without it every\n // such edge resolves to the container's own title, i.e. to a service nobody\n // has ever adopted, and drops out of the spine unnoticed.\n //\n // Read before the model rather than after it, because the model is now read\n // through the same resolver: an extending model's own slice is exactly the\n // elements this set makes resolvable.\n const known = new Set((await listServices(docsDir, fleet)).map((s) => s.id));\n\n // C4 model. Its absence is an error — this is where `adopt` comes in — but it\n // must NOT silence the rest of the gate stack: `loam archive` of a feature\n // introducing a new service creates exactly this state (spec.md, arch.spec.md,\n // openapi.yaml, no model.likec4), and an early return here suspended arch\n // coverage, health.uncovered, provenance (content_digest included) and the\n // gherkin staleness chain for the very services vouch had just promised them\n // to. So the finding is emitted and the walk CONTINUES; only the checks that\n // read the model itself are guarded.\n const hasModel = existsSync(paths.model);\n let elements: Elem[] = [];\n let relationships: Rel[] = [];\n // The model as its own SHAPE demands it be read — a file parsed alone, or a\n // slice of the project it extends (`core/c4/service-model/load.ts`). Kept for\n // the service's own use cases below, which need both its views and which\n // shape produced them.\n let model: ServiceModel | null = null;\n // The service's OWN use cases, scanned here rather than beside the grades it\n // feeds: `c4.valid` reports how many were graded, and that count is the only\n // surface a HEALTHY flow appears on. Null means no scan was made — the model\n // does not parse (`c4.invalid` owns that, and a second load repeats its\n // cascade under a use-case code) or its map does not (an extending model then\n // comes back with no errors AND no model, and the flow project would be\n // staged over the same broken map).\n let flows: ServiceFlowScan | null = null;\n if (!hasModel) {\n findings.push({\n severity: \"error\",\n code: \"service.no-model\",\n message: `No C4 model at ${repoPath(docsDir, paths.model)}. Run \\`loam adopt\\` for '${service}' first.`,\n });\n } else {\n model = await fleet.serviceModel(docsDir, paths);\n elements = model.doc.elements;\n relationships = model.doc.relationships;\n if (!model.mapUnreadable && model.doc.errors.length === 0) {\n flows = await readServiceFlows({\n paths,\n model: model.doc,\n known,\n // An extending model's siblings resolve against the fleet map, so their\n // project is the docs root's, never the service directory. The project\n // the model was READ in travels with it because that is the scope of a\n // view written inside model.likec4: `model.doc` is the service's own\n // slice, and a hop backed by one of the map's edges is backed wherever\n // the view sits.\n ...(model.shape === \"extending\" && model.project !== null\n ? { extending: { docsDir, project: model.project } }\n : {}),\n });\n }\n findings.push(\n ...(await modelFindings({\n service,\n treePath,\n docsDir,\n model,\n modelPath: paths.model,\n known,\n gradedFlows: flows?.kind === \"read\" ? flows.views.length : 0,\n })),\n );\n }\n\n // The fleet map as the PROJECT it is, in EVERY mode — landscape plus every\n // `architecture/**/*.likec4` minus the generated views file. Single-service\n // runs used to read `landscape.likec4` alone, which made three answers depend\n // on which flag the caller happened to pass: a use-case file that does not\n // parse was `spine.landscape-invalid` under `--all` and silence under\n // `--service`, an extending model was readable under one and not the other,\n // and `c4.declaration-diverged` would have compared against a different\n // document. Under `--all` the caller hands in the doc it already loaded.\n //\n // Still gated on the landscape FILE existing: `land === null` means \"this repo\n // draws no fleet\", and a repo with an `architecture/usecases/` and no map is\n // that state, not a map that loaded empty.\n const land =\n preloaded ?? (existsSync(landscapeFile(docsDir)) ? await fleet.architecture(docsDir) : null);\n\n // Where a standalone model's copies of the map's elements have drifted. After\n // `c4.valid` because it is a claim about a model that parsed, and silent for\n // an extending model, which has no copy to diverge.\n if (model !== null) findings.push(...divergedFindings({ service, model, architecture: land, known }));\n\n\n // The living landscape's element→service resolver, container-aware and\n // memoized. Every question below that asks \"does this edge point at me?\"\n // asks it through this one function, so the no-openapi grace, the HTTP spine\n // and the event spine can never disagree about which edges are inbound.\n const landSvcOf = land === null ? null : serviceResolver(land.elements, known);\n\n const specs = await readServiceSpecs({ docsDir, service, paths, fleet });\n findings.push(...specs.findings);\n const { reqs, livingReqs, archText, archReqs } = specs;\n\n const api = await apiAxisFindings({\n docsDir,\n service,\n me,\n paths,\n reqs,\n livingReqs,\n land,\n landSvcOf,\n fleet,\n });\n findings.push(...api.findings);\n findings.push(\n ...spineFindings({\n service,\n docsDir,\n treePath,\n me,\n land,\n landSvcOf,\n contract: api.contract,\n landscapeReported: check.landscapeReported,\n // An unreadable map takes an extending model with it, and the spine\n // finding is where that is said — see `Spine.modelExtends`.\n modelExtends: model?.shape === \"extending\",\n }),\n );\n findings.push(\n ...(await eventAxisFindings({\n docsDir,\n service,\n me,\n paths,\n reqs,\n livingReqs,\n archReqs,\n land,\n landSvcOf,\n known,\n fleet,\n })),\n );\n findings.push(\n ...(await archAxisFindings({\n service,\n paths,\n archText,\n archReqs,\n elements,\n relationships,\n // The one fact that tells \"this service models nothing\" apart from \"this\n // service's model could not be read\" — its map does not parse, or it does\n // not itself (`c4.invalid` above): both arrive here as empty lists, and\n // only the second one must silence `covers.unknown`.\n modelUnreadable: model !== null && (model.mapUnreadable || model.doc.errors.length > 0),\n land,\n known,\n docsDir,\n fleet,\n })),\n );\n\n // The service's own use cases, GRADED — scanned beside the model above. Here\n // rather than on the fleet target because the hops name this service's\n // containers, which only its project resolves; after the arch axis because a\n // `#req-` tag resolves against the ids both spec documents declare.\n if (flows !== null) {\n findings.push(\n ...serviceUseCaseFindings({\n service,\n treePath,\n docsDir,\n scan: flows,\n services: known,\n requirementIds: requirementIdsOf(paths, livingReqs, archReqs),\n }),\n );\n }\n\n // The authorization axis. Both requirement documents are graded against one\n // fleet vocabulary — a permission is a fleet fact, and an arch requirement\n // gates on one exactly as a business requirement does. The capability axis\n // rides the same loop against ITS fleet vocabulary; under --all the fleet\n // memo makes that one parse for the whole run.\n const vocabulary = await readVocabulary(permissionsPath(docsDir));\n const capabilities = await fleet.capabilities(docsDir);\n // The requirement-level index behind `Realizes:`. Built once for both axes,\n // and behind the same ladder `capabilityUnknownIssues` applies — an absent\n // or unreadable vocabulary means no reads and no grades at all, so a fleet\n // that has not adopted the axis pays nothing for a line it never wrote.\n const capabilityReqs = await capabilityRequirementIndex(capabilities, (p) => fleet.readRequirements(p));\n for (const [label, docReqs] of [\n [\"spec.md\", reqs],\n [\"arch.spec.md\", archReqs],\n ] as const) {\n const target = { where: `${service}: ${label}`, subject: service };\n findings.push(...requiresUnknownFindings(docReqs, target, vocabulary));\n findings.push(...capabilityUnknownIssues(docReqs, target, capabilities));\n findings.push(...realizesUnknownIssues(docReqs, target, capabilityReqs));\n findings.push(...realizesStaleFindings(docReqs, target, capabilityReqs));\n // The grammar guard, last in the loop: it grades the KEYS the three checks\n // above resolve values from, so a line none of them saw is reported here\n // rather than staying invisible for having produced nothing to check.\n findings.push(...unknownDirectiveFindings(docReqs, target));\n }\n\n // The links this service's own documents write. Its place in the order is\n // deliberate: every axis above joins through a LINE loam defined, and this one\n // grades the join an author writes in prose — so it reads last among the\n // document checks and adds no input any of them depend on.\n findings.push(...(await serviceLinkFindings({ docsDir, subject: service, fleet }, paths)));\n\n // Provenance last: who vouched for this, and what code it was written from.\n findings.push(...(await serviceProvenance(docsDir, service, { repoDir, loamOwned: check.loamOwned, fleet })));\n findings.push(...(await sourceScopeFindings(docsDir, service, repoDir, fleet)));\n // The build's own contract against the committed copy. Service-repo only:\n // from the docs repo there is no build output to read, and the family stays\n // silent rather than guessing — the same rule `sources` provenance follows.\n if (repoDir !== undefined) {\n findings.push(...(await generatedContractFindings(service, repoDir, contracts?.openapi, paths.openapi)));\n }\n\n // The generated-gherkin freshness chain, service-repo-scoped like sources.*:\n // it needs the repo (the suite lives there), and it stays quiet until\n // <gherkinDir>/loam/ exists — a service that never generated has not opted in.\n findings.push(...(await gherkinFindings({ docsDir, service, repoDir, gherkinDir, fleet })));\n\n // The evidence-pin re-check, the same service-repo-scoped family: recorded\n // verification citations graded against THIS working tree, quiet from the\n // docs repo — sources.unverifiable-from-here already names that blind spot.\n findings.push(...(await evidencePinFindings(docsDir, service, repoDir, fleet)));\n\n // The one journal that does NOT live in the docs repo: gherkin commits into\n // the service repo's emission root. A half-committed suite graded by the\n // freshness chain above reads as merely stale — warn-severity, which never\n // gates — while doctor calls the same state a blocker. Same finding, same\n // code, led rather than appended, exactly as validate leads with the docs\n // repo's own journal.\n if (repoDir !== undefined) {\n try {\n const interrupted = await interruptedCommitFinding(gherkinRoot(repoDir, gherkinDir));\n if (interrupted !== null) findings.unshift(interrupted);\n } catch (err) {\n // An unsafe gherkinDir is `loam gherkin`'s refusal to make, with its\n // own sentence; grading declines to scan what it cannot trust.\n if (!(err instanceof UnsafePathError)) throw err;\n }\n }\n\n return report;\n}\n"]}
@@ -16,28 +16,28 @@
16
16
  * on the business axis. One file, one rule.
17
17
  */
18
18
  import { existsSync } from "node:fs";
19
- import { readFile } from "node:fs/promises";
20
19
  import {} from "../../../core/c4/likec4.js";
21
20
  import {} from "../../../core/kernel/ids/service.js";
22
21
  import {} from "../../../core/repo/paths.js";
23
22
  import {} from "../../../core/vocabulary/report.js";
24
- import { parseRequirements } from "../../../core/document/parse.js";
25
- import { assertionlessFindings, examplesFindings, steplessFindings, } from "../../../core/document/scenarios.js";
26
23
  import {} from "../../../core/document/spec.js";
27
24
  import {} from "../../../core/c4/arch.js";
25
+ import { hasDeployment } from "../../../core/c4/parsed/deployment.js";
26
+ import {} from "../../../core/kernel/ids/dirs.js";
27
+ import { repoPath } from "../../../core/envelope/json.js";
28
28
  import { readHealth } from "../../../core/vocabulary/health.js";
29
29
  import { documentConflictFinding } from "../../../core/conflict-markers.js";
30
30
  import { FleetContext } from "../../../core/fleet-context.js";
31
31
  import { healthDependencyFindings } from "../checks/health-deps.js";
32
- import { coverageFinding, coversEntries, coversUnknownFindings, duplicateRequirementFindings, repeatedListLineFindings, requirementIdFindings, } from "../checks/requirements.js";
32
+ import { coverageFinding, coversEntries, coversUnknownFindings, duplicateRequirementFindings, repeatedListLineFindings, requirementIdFindings, scenarioShapeFindings, } from "../checks/requirements.js";
33
33
  export async function readServiceSpecs(input) {
34
- const { service, paths, fleet } = input;
34
+ const { docsDir, service, paths, fleet } = input;
35
35
  const findings = [];
36
36
  // Requirement coverage.
37
37
  let reqs = [];
38
38
  if (existsSync(paths.spec)) {
39
- const specText = fleet === undefined ? await readFile(paths.spec, "utf8") : await fleet.readText(paths.spec);
40
- reqs = fleet === undefined ? parseRequirements(specText) : await fleet.readRequirements(paths.spec);
39
+ const specText = await fleet.readText(paths.spec);
40
+ reqs = await fleet.readRequirements(paths.spec);
41
41
  // Conflict markers first, because they say that nothing below this line is
42
42
  // anyone's text. An error, gating by the default rule and deliberately
43
43
  // without `gates`: that field is coherence's (issue.ts) and archive does
@@ -74,9 +74,7 @@ export async function readServiceSpecs(input) {
74
74
  // satisfies the count and tests nothing — cucumber runs it vacuously green
75
75
  // and `loam verify --results` can never confirm it — so a fleet gate that
76
76
  // called it covered was certifying the absence of a test.
77
- findings.push(...steplessFindings(`${service}: requirements`, service, reqs));
78
- findings.push(...assertionlessFindings(`${service}: requirements`, service, reqs));
79
- findings.push(...examplesFindings(`${service}: requirements`, service, reqs));
77
+ findings.push(...scenarioShapeFindings(`${service}: requirements`, service, reqs));
80
78
  findings.push(...duplicateRequirementFindings(reqs, `${service}: spec.md`, service));
81
79
  findings.push(...requirementIdFindings(reqs, `${service}: spec.md`, service));
82
80
  findings.push(...repeatedListLineFindings(reqs, `${service}: spec.md`, service));
@@ -85,7 +83,7 @@ export async function readServiceSpecs(input) {
85
83
  findings.push({
86
84
  severity: "warn",
87
85
  code: "service.no-spec",
88
- message: `No living spec at ${paths.spec} — requirement coverage and API governance are unchecked`,
86
+ message: `No living spec at ${repoPath(docsDir, paths.spec)} — requirement coverage and API governance are unchecked`,
89
87
  });
90
88
  }
91
89
  // The requirements that still govern anything. A REMOVED requirement is on
@@ -105,20 +103,43 @@ export async function readServiceSpecs(input) {
105
103
  // are business — the outbox requirement in a service's arch.spec.md is the
106
104
  // canonical place a `Publishes:` line belongs — so handing the event axis only
107
105
  // spec.md would silently ignore the lines most authors write.
108
- const archText = existsSync(paths.archSpec)
109
- ? fleet === undefined
110
- ? await readFile(paths.archSpec, "utf8")
111
- : await fleet.readText(paths.archSpec)
112
- : null;
113
- const archReqs = archText === null
114
- ? []
115
- : fleet === undefined
116
- ? parseRequirements(archText)
117
- : await fleet.readRequirements(paths.archSpec);
106
+ const archText = existsSync(paths.archSpec) ? await fleet.readText(paths.archSpec) : null;
107
+ const archReqs = archText === null ? [] : await fleet.readRequirements(paths.archSpec);
118
108
  return { findings, reqs, livingReqs, archText, archReqs };
119
109
  }
110
+ /**
111
+ * The topology a `node:` Covers entry resolves against, loaded at most once and
112
+ * only when something asks for it.
113
+ *
114
+ * Under `--all` the caller hands in the doc it already loaded — the whole
115
+ * `architecture/` project — so the deployment model is already here and this
116
+ * returns it. A SINGLE-service run loads only `landscape.likec4`, and a fleet
117
+ * that keeps its `deployment { }` block in a file beside it, which is the
118
+ * ordinary layout once one landscape file is the fleet's convention, would then
119
+ * resolve one `Covers:` line two different ways depending on which command
120
+ * asked. `loam status`'s two forms had exactly that disagreement and it was a
121
+ * defect both times; this is the same class, prevented rather than found.
122
+ *
123
+ * The cost is paid only by a service whose `arch.spec.md` actually writes a
124
+ * `node:` entry. A fleet with no topology at all pays one project load per such
125
+ * service — a state in which the entry is a typo anyway, and one load is what
126
+ * buys it a message that names the real ids.
127
+ */
128
+ async function topology(land, archReqs, docsDir, fleet) {
129
+ if (land?.deployment !== undefined && hasDeployment(land.deployment))
130
+ return land.deployment;
131
+ const wanted = coversEntries(archReqs).some((e) => e.form === "node" || e.form === "node-edge");
132
+ if (!wanted)
133
+ return undefined;
134
+ // Through the index, always: `land` above is now that same project in every
135
+ // mode, so this fallback fires only for the caller that handed in nothing,
136
+ // and it must not be a second parse of documents already in the memo.
137
+ const project = await fleet.architecture(docsDir);
138
+ return project.errors.length === 0 ? project.deployment : undefined;
139
+ }
120
140
  export async function archAxisFindings(axis) {
121
- const { service, paths, archText, archReqs, elements, relationships, land, known } = axis;
141
+ const { service, paths, archText, archReqs, elements, relationships, land, known, docsDir } = axis;
142
+ const { fleet } = axis;
122
143
  const findings = [];
123
144
  // The architecture spec axis — the obligations a business spec never carries
124
145
  // (the transactional outbox, retries, metrics, alerts). arch.spec.md is
@@ -138,7 +159,18 @@ export async function archAxisFindings(axis) {
138
159
  // warn needs either a second nested element with no edge joining anything,
139
160
  // or dependencies declared in this service's own health.yaml. The health
140
161
  // half goes quiet when the file is unreadable — no claims on bad data.
141
- const nested = elements.filter((e) => e.id.includes(".")).length;
162
+ // "Nested" is a PARENT in this slice, not a dot in the id, and the two stopped
163
+ // meaning the same thing when a model could extend the map: an extending
164
+ // placeholder `extend marketplace.orderService { }` contributes one element
165
+ // whose id is dotted and whose parent belongs to the map, so a dot count read
166
+ // it as nested architecture that reaches nothing — the standalone placeholder
167
+ // `orderService = softwareSystem` was silent for the same shape. One rule for
168
+ // both: `a`/`a.x`/`kafka`/`kafka.t` still counts two.
169
+ const sliceIds = new Set(elements.map((e) => e.id));
170
+ const nested = elements.filter((e) => {
171
+ const dot = e.id.lastIndexOf(".");
172
+ return dot !== -1 && sliceIds.has(e.id.slice(0, dot));
173
+ }).length;
142
174
  const deps = health.unreadable ? [] : health.dependencies;
143
175
  if (elements.length > 0 && relationships.length === 0 && (nested > 1 || deps.length > 0)) {
144
176
  const evidence = [
@@ -165,9 +197,7 @@ export async function archAxisFindings(axis) {
165
197
  if (conflict !== null)
166
198
  findings.push(conflict);
167
199
  findings.push(coverageFinding(`${service}: arch requirements`, archReqs));
168
- findings.push(...steplessFindings(`${service}: arch requirements`, service, archReqs));
169
- findings.push(...assertionlessFindings(`${service}: arch requirements`, service, archReqs));
170
- findings.push(...examplesFindings(`${service}: arch requirements`, service, archReqs));
200
+ findings.push(...scenarioShapeFindings(`${service}: arch requirements`, service, archReqs));
171
201
  findings.push(...duplicateRequirementFindings(archReqs, `${service}: arch.spec.md`, service));
172
202
  findings.push(...requirementIdFindings(archReqs, `${service}: arch.spec.md`, service));
173
203
  findings.push(...repeatedListLineFindings(archReqs, `${service}: arch.spec.md`, service));
@@ -188,13 +218,43 @@ export async function archAxisFindings(axis) {
188
218
  });
189
219
  }
190
220
  const landParses = land !== null && land.errors.length === 0 ? land : null;
221
+ const deployment = await topology(landParses, archReqs, docsDir, fleet);
191
222
  const scope = {
192
223
  elements: [...elements, ...(landParses?.elements ?? [])],
193
224
  relationships: [...relationships, ...(landParses?.relationships ?? [])],
225
+ ...(deployment === undefined ? {} : { deployment }),
194
226
  health: health.ids,
195
227
  known,
196
228
  };
197
- findings.push(...coversUnknownFindings(archReqs, { where: `${service}: arch.spec.md`, subject: service }, scope, health.unreadable));
229
+ // `covers.unknown` is suspended entirely when the model could not be read —
230
+ // it EXTENDS a map that does not parse, or it does not parse itself — and
231
+ // this is the standing rule of the whole axis rather than a special case:
232
+ // nothing may be graded out of a document nobody could read
233
+ // (`core/c4/service-model/load.ts` states it; `model/grade.ts`, `model/diverged.ts`
234
+ // and the flow skip in `./service.ts` all honour it). Such a model comes back
235
+ // with no elements and no edges, so every `Covers:` line naming one of its
236
+ // containers would resolve to nothing. For an unreadable map the landscape
237
+ // half of the scope is empty too — one unparseable
238
+ // `architecture/usecases/x.likec4` convicting every Covers: line of every
239
+ // migrated service in the fleet as a typo, under a code whose whole job is to
240
+ // catch typos. For the model's own parse error it was one dropped quote in
241
+ // the example fleet's order-service: `c4.invalid` and six `covers.unknown`
242
+ // warnings, each sending the reader to arch.spec.md to fix an id that was
243
+ // spelled right. `c4.invalid` is the one finding; the grade comes back with
244
+ // the next run that parses.
245
+ //
246
+ // The alert:/sli: forms could still be answered out of health.yaml, which IS
247
+ // readable, and they are suspended with the rest on purpose: a verdict that
248
+ // grades two of the Covers: forms and drops the others is a report whose
249
+ // completeness depends on which form the author happened to write, and the one
250
+ // repair — fix the map, or the model — brings all of them back on the next
251
+ // run. The same holds for a STANDALONE model that does not parse, whose
252
+ // landscape half is still readable: grading only the entries that name the
253
+ // map would be that partial report again. The health axis keeps its own grade
254
+ // either way (`health.uncovered`, below).
255
+ if (axis.modelUnreadable !== true) {
256
+ findings.push(...coversUnknownFindings(archReqs, { where: `${service}: arch.spec.md`, subject: service }, scope, health.unreadable));
257
+ }
198
258
  const activeCovers = coversEntries(archReqs);
199
259
  for (const { form, ids } of [
200
260
  { form: "alert", ids: health.ids.alerts },
@@ -213,7 +273,7 @@ export async function archAxisFindings(axis) {
213
273
  }
214
274
  // The other direction health.yaml is read in: its dependencies must be the
215
275
  // model's dependencies. `deps` is already muted on an unreadable file above.
216
- findings.push(...healthDependencyFindings({ service, dependencies: deps, elements, modelPath: paths.model }));
276
+ findings.push(...healthDependencyFindings({ service, dependencies: deps, elements, modelPath: repoPath(docsDir, paths.model) }));
217
277
  return findings;
218
278
  }
219
279
  //# sourceMappingURL=specs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"specs.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/specs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * This service's requirement documents: spec.md, arch.spec.md, and the\n * obligations the second of them carries that the first never does.\n *\n * Two entry points because the two happen at different moments in a service's\n * grading and the order of findings is part of the report. `readServiceSpecs`\n * runs first — everything downstream joins through the requirements it parses —\n * while `archAxisFindings` runs last, after both spines, because `covers.unknown`\n * resolves against the C4 model and the landscape and cannot be answered until\n * those are in hand.\n *\n * The arch axis is advisory in what it ASKS for (covers.unknown,\n * health.uncovered are warnings; arch.spec.md's absence is not a finding at all,\n * because partial adoption is supported) but not in whether the file is\n * readable: a conflict marker or a stepless scenario is graded exactly as it is\n * on the business axis. One file, one rule.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type ServicePaths } from \"../../../core/repo/paths.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { parseRequirements } from \"../../../core/document/parse.js\";\nimport {\n assertionlessFindings,\n examplesFindings,\n steplessFindings,\n} from \"../../../core/document/scenarios.js\";\nimport { type Requirement } from \"../../../core/document/spec.js\";\nimport { type CoverageScope } from \"../../../core/c4/arch.js\";\nimport { readHealth } from \"../../../core/vocabulary/health.js\";\nimport { documentConflictFinding } from \"../../../core/conflict-markers.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { healthDependencyFindings } from \"../checks/health-deps.js\";\nimport {\n coverageFinding,\n coversEntries,\n coversUnknownFindings,\n duplicateRequirementFindings,\n repeatedListLineFindings,\n requirementIdFindings,\n} from \"../checks/requirements.js\";\n\n/** Both requirement documents, parsed once for every axis that joins through them. */\nexport interface ServiceSpecs {\n /** spec.md's findings, emitted before any cross-axis check runs. */\n findings: Finding[];\n /** Every requirement spec.md declares, REMOVED ones included. */\n reqs: Requirement[];\n /** The ones that still govern something. */\n livingReqs: Requirement[];\n /** arch.spec.md's text, or null when the service has none. */\n archText: string | null;\n archReqs: Requirement[];\n}\n\nexport async function readServiceSpecs(input: {\n service: PathableService;\n paths: ServicePaths;\n fleet?: FleetContext;\n}): Promise<ServiceSpecs> {\n const { service, paths, fleet } = input;\n const findings: Finding[] = [];\n\n // Requirement coverage.\n let reqs: Requirement[] = [];\n if (existsSync(paths.spec)) {\n const specText =\n fleet === undefined ? await readFile(paths.spec, \"utf8\") : await fleet.readText(paths.spec);\n reqs = fleet === undefined ? parseRequirements(specText) : await fleet.readRequirements(paths.spec);\n // Conflict markers first, because they say that nothing below this line is\n // anyone's text. An error, gating by the default rule and deliberately\n // without `gates`: that field is coherence's (issue.ts) and archive does\n // not read validate's findings, so claiming it here would be a promise\n // about a gate that is not wired. What makes it an error rather than a\n // warning is what the next merge does — `loam archive` rewrites the\n // requirements section of this very file and takes whichever marker lines\n // fall inside it, turning a conflict anyone can see into a document nobody\n // can tell is wrong.\n const conflict = documentConflictFinding(`${service}: spec.md`, service, specText);\n if (conflict !== null) findings.push(conflict);\n // A living spec with no `### Requirement:` block at all is the baseline\n // `loam adopt` scaffolds and nobody ever filled in. \"requirements covered\n // (0 requirements, all with scenarios)\" is true of it and says the opposite\n // of what a reader needs: every cross-axis check downstream — API\n // governance, the Operations: spine, gherkin generation — is vacuous here,\n // and the green tick is what let a whole fleet score `sourced` over empty\n // files.\n if (reqs.length === 0) {\n findings.push({\n severity: \"warn\",\n code: \"spec.no-requirements\",\n subject: service,\n message: `${service}: spec.md holds no '### Requirement:' blocks — every requirement-driven check below is vacuous, and nothing here can go out of date because nothing is written down`,\n });\n } else {\n findings.push(coverageFinding(`${service}: requirements`, reqs));\n }\n // The other half of the coverage question, and an error for the same\n // reason the first half is: the whole claim of coverage is that a\n // requirement has a scenario something can RUN. A heading with no steps\n // satisfies the count and tests nothing — cucumber runs it vacuously green\n // and `loam verify --results` can never confirm it — so a fleet gate that\n // called it covered was certifying the absence of a test.\n findings.push(...steplessFindings(`${service}: requirements`, service, reqs));\n findings.push(...assertionlessFindings(`${service}: requirements`, service, reqs));\n findings.push(...examplesFindings(`${service}: requirements`, service, reqs));\n findings.push(...duplicateRequirementFindings(reqs, `${service}: spec.md`, service));\n findings.push(...requirementIdFindings(reqs, `${service}: spec.md`, service));\n findings.push(...repeatedListLineFindings(reqs, `${service}: spec.md`, service));\n } else {\n findings.push({\n severity: \"warn\",\n code: \"service.no-spec\",\n message: `No living spec at ${paths.spec} — requirement coverage and API governance are unchecked`,\n });\n }\n\n // The requirements that still govern anything. A REMOVED requirement is on\n // its way out together with the operations it names: it makes no claim on the\n // contract and governs nothing once the retiring feature archives — the same\n // position coherence/coherence.ts takes on the delta side.\n //\n // Spelled once because it was spelled three times and dropped on the fourth:\n // `governedOps` filtered, `spec-api.op-undefined` filtered, and the\n // `api.covered` join did not — so an operation whose only requirement was\n // REMOVED counted as governed, and the one operation about to be left without\n // a requirement was the one the report called covered.\n const livingReqs = reqs.filter((r) => r.kind !== \"REMOVED\");\n\n // The architecture spec, read HERE with spec.md rather than with the rest of\n // its own axis below, because the event links are a requirement-level join and\n // BOTH requirement files carry them. Events are architecture as often as they\n // are business — the outbox requirement in a service's arch.spec.md is the\n // canonical place a `Publishes:` line belongs — so handing the event axis only\n // spec.md would silently ignore the lines most authors write.\n const archText = existsSync(paths.archSpec)\n ? fleet === undefined\n ? await readFile(paths.archSpec, \"utf8\")\n : await fleet.readText(paths.archSpec)\n : null;\n const archReqs =\n archText === null\n ? []\n : fleet === undefined\n ? parseRequirements(archText)\n : await fleet.readRequirements(paths.archSpec);\n\n return { findings, reqs, livingReqs, archText, archReqs };\n}\n\n/** What the arch axis needs once the model and the landscape have been read. */\nexport interface ArchAxis {\n service: PathableService;\n paths: ServicePaths;\n /** arch.spec.md's text, or null — its absence is not a finding. */\n archText: string | null;\n archReqs: Requirement[];\n /** The service's own C4 model, empty when it has none. */\n elements: Elem[];\n relationships: Rel[];\n /** The living landscape, or null when the repo has none. */\n land: LoadedDoc | null;\n /**\n * The enumerated fleet, for the Covers matcher's endpoint resolution — the\n * same set the caller's own spine resolution uses, so a `Covers:` line\n * naming a service matches an edge drawn into that service's container.\n */\n known?: ReadonlySet<string>;\n}\n\nexport async function archAxisFindings(axis: ArchAxis): Promise<Finding[]> {\n const { service, paths, archText, archReqs, elements, relationships, land, known } = axis;\n const findings: Finding[] = [];\n\n // The architecture spec axis — the obligations a business spec never carries\n // (the transactional outbox, retries, metrics, alerts). arch.spec.md is\n // optional and its ABSENCE is not a finding (partial adoption is supported);\n // what exists must hold together: every requirement needs a scenario, every\n // `Covers:` entry must resolve (covers.unknown), and every alert/SLI that\n // health.yaml declares wants a covering requirement (health.uncovered) — the\n // moment health.yaml stops being inert. Warnings, never gates: `--strict` is\n // the CI escalation.\n const health = await readHealth(paths.health);\n\n // A model that reaches nothing is almost never true — but only when\n // something else attests it should reach. A deliberately shallow synthetic\n // fixture can hold elements without relationships beside sources that name\n // dependencies, yet `0 errors, 0 warnings` would still read as done.\n // Evidence-gated on purpose: a bare root-plus-one-container baseline stays\n // silent (nothing PROVES it thin — the standard mid-adoption shape), the\n // warn needs either a second nested element with no edge joining anything,\n // or dependencies declared in this service's own health.yaml. The health\n // half goes quiet when the file is unreadable — no claims on bad data.\n const nested = elements.filter((e) => e.id.includes(\".\")).length;\n const deps = health.unreadable ? [] : health.dependencies;\n if (elements.length > 0 && relationships.length === 0 && (nested > 1 || deps.length > 0)) {\n const evidence = [\n ...(nested > 1 ? [`${nested} nested elements with no edge joining them`] : []),\n ...(deps.length > 0\n ? [`health.yaml declares ${deps.length} dependenc${deps.length === 1 ? \"y\" : \"ies\"} (${deps.join(\", \")})`]\n : []),\n ].join(\", and \");\n findings.push({\n severity: \"warn\",\n code: \"c4.no-relationships\",\n subject: service,\n message:\n `${service}: model.likec4 declares ${elements.length} element(s) and 0 relationships, yet ${evidence} — ` +\n `a model that reaches nothing is almost never true; draw the edges the service actually has ` +\n `(what its containers call, what reads and writes its stores, what it depends on at runtime)`,\n });\n }\n\n if (archText !== null) {\n // The arch axis is advisory in what it ASKS for (covers.unknown,\n // health.uncovered are warnings) but not in whether the file is readable:\n // both breaches below are about the document itself, and they are graded\n // exactly as they are on the business axis — one file, one rule.\n const conflict = documentConflictFinding(`${service}: arch.spec.md`, service, archText);\n if (conflict !== null) findings.push(conflict);\n findings.push(coverageFinding(`${service}: arch requirements`, archReqs));\n findings.push(...steplessFindings(`${service}: arch requirements`, service, archReqs));\n findings.push(...assertionlessFindings(`${service}: arch requirements`, service, archReqs));\n findings.push(...examplesFindings(`${service}: arch requirements`, service, archReqs));\n findings.push(...duplicateRequirementFindings(archReqs, `${service}: arch.spec.md`, service));\n findings.push(...requirementIdFindings(archReqs, `${service}: arch.spec.md`, service));\n findings.push(...repeatedListLineFindings(archReqs, `${service}: arch.spec.md`, service));\n }\n // A health.yaml that exists but does not read is reported once, and the\n // checks that read it go quiet BOTH ways: its ids are unknown, so no\n // health.uncovered obligation exists (the empty id set below is already\n // silent), and no `Covers: alert:/sli:` entry may be graded a typo against\n // ids nobody could read (coversUnknownFindings mutes on the flag). A warn,\n // not an error: the axis it feeds is advisory end to end.\n if (health.unreadable) {\n findings.push({\n severity: \"warn\",\n code: \"health.invalid\",\n subject: service,\n message: `${service}: health.yaml does not parse — alert/SLI ids are unreadable, so Covers: alert:/sli: entries and health coverage are unchecked`,\n ...(health.error === undefined ? {} : { details: [health.error] }),\n });\n }\n const landParses = land !== null && land.errors.length === 0 ? land : null;\n const scope: CoverageScope = {\n elements: [...elements, ...(landParses?.elements ?? [])],\n relationships: [...relationships, ...(landParses?.relationships ?? [])],\n health: health.ids,\n known,\n };\n findings.push(\n ...coversUnknownFindings(archReqs, { where: `${service}: arch.spec.md`, subject: service }, scope, health.unreadable),\n );\n const activeCovers = coversEntries(archReqs);\n for (const { form, ids } of [\n { form: \"alert\" as const, ids: health.ids.alerts },\n { form: \"sli\" as const, ids: health.ids.slis },\n ]) {\n for (const id of ids) {\n if (activeCovers.some((e) => e.form === form && e.id === id)) continue;\n findings.push({\n severity: \"warn\",\n code: \"health.uncovered\",\n subject: service,\n message: `${service}: health.yaml declares ${form === \"alert\" ? \"alert\" : \"SLI\"} '${id}' but no arch.spec.md requirement covers it — write one with 'Covers: ${form}:${id}', or the signal ships with nothing testing it`,\n });\n }\n }\n // The other direction health.yaml is read in: its dependencies must be the\n // model's dependencies. `deps` is already muted on an unreadable file above.\n findings.push(...healthDependencyFindings({ service, dependencies: deps, elements, modelPath: paths.model }));\n\n return findings;\n}\n"]}
1
+ {"version":3,"file":"specs.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/specs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * This service's requirement documents: spec.md, arch.spec.md, and the\n * obligations the second of them carries that the first never does.\n *\n * Two entry points because the two happen at different moments in a service's\n * grading and the order of findings is part of the report. `readServiceSpecs`\n * runs first — everything downstream joins through the requirements it parses —\n * while `archAxisFindings` runs last, after both spines, because `covers.unknown`\n * resolves against the C4 model and the landscape and cannot be answered until\n * those are in hand.\n *\n * The arch axis is advisory in what it ASKS for (covers.unknown,\n * health.uncovered are warnings; arch.spec.md's absence is not a finding at all,\n * because partial adoption is supported) but not in whether the file is\n * readable: a conflict marker or a stepless scenario is graded exactly as it is\n * on the business axis. One file, one rule.\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type ServicePaths } from \"../../../core/repo/paths.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { type Requirement } from \"../../../core/document/spec.js\";\nimport { type CoverageScope } from \"../../../core/c4/arch.js\";\nimport { hasDeployment, type DeploymentModel } from \"../../../core/c4/parsed/deployment.js\";\nimport { type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { repoPath } from \"../../../core/envelope/json.js\";\nimport { readHealth } from \"../../../core/vocabulary/health.js\";\nimport { documentConflictFinding } from \"../../../core/conflict-markers.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { healthDependencyFindings } from \"../checks/health-deps.js\";\nimport {\n coverageFinding,\n coversEntries,\n coversUnknownFindings,\n duplicateRequirementFindings,\n repeatedListLineFindings,\n requirementIdFindings,\n scenarioShapeFindings,\n} from \"../checks/requirements.js\";\n\n/** Both requirement documents, parsed once for every axis that joins through them. */\nexport interface ServiceSpecs {\n /** spec.md's findings, emitted before any cross-axis check runs. */\n findings: Finding[];\n /** Every requirement spec.md declares, REMOVED ones included. */\n reqs: Requirement[];\n /** The ones that still govern something. */\n livingReqs: Requirement[];\n /** arch.spec.md's text, or null when the service has none. */\n archText: string | null;\n archReqs: Requirement[];\n}\n\nexport async function readServiceSpecs(input: {\n docsDir: DocsDir;\n service: PathableService;\n paths: ServicePaths;\n fleet: FleetContext;\n}): Promise<ServiceSpecs> {\n const { docsDir, service, paths, fleet } = input;\n const findings: Finding[] = [];\n\n // Requirement coverage.\n let reqs: Requirement[] = [];\n if (existsSync(paths.spec)) {\n const specText = await fleet.readText(paths.spec);\n reqs = await fleet.readRequirements(paths.spec);\n // Conflict markers first, because they say that nothing below this line is\n // anyone's text. An error, gating by the default rule and deliberately\n // without `gates`: that field is coherence's (issue.ts) and archive does\n // not read validate's findings, so claiming it here would be a promise\n // about a gate that is not wired. What makes it an error rather than a\n // warning is what the next merge does — `loam archive` rewrites the\n // requirements section of this very file and takes whichever marker lines\n // fall inside it, turning a conflict anyone can see into a document nobody\n // can tell is wrong.\n const conflict = documentConflictFinding(`${service}: spec.md`, service, specText);\n if (conflict !== null) findings.push(conflict);\n // A living spec with no `### Requirement:` block at all is the baseline\n // `loam adopt` scaffolds and nobody ever filled in. \"requirements covered\n // (0 requirements, all with scenarios)\" is true of it and says the opposite\n // of what a reader needs: every cross-axis check downstream — API\n // governance, the Operations: spine, gherkin generation — is vacuous here,\n // and the green tick is what let a whole fleet score `sourced` over empty\n // files.\n if (reqs.length === 0) {\n findings.push({\n severity: \"warn\",\n code: \"spec.no-requirements\",\n subject: service,\n message: `${service}: spec.md holds no '### Requirement:' blocks — every requirement-driven check below is vacuous, and nothing here can go out of date because nothing is written down`,\n });\n } else {\n findings.push(coverageFinding(`${service}: requirements`, reqs));\n }\n // The other half of the coverage question, and an error for the same\n // reason the first half is: the whole claim of coverage is that a\n // requirement has a scenario something can RUN. A heading with no steps\n // satisfies the count and tests nothing — cucumber runs it vacuously green\n // and `loam verify --results` can never confirm it — so a fleet gate that\n // called it covered was certifying the absence of a test.\n findings.push(...scenarioShapeFindings(`${service}: requirements`, service, reqs));\n findings.push(...duplicateRequirementFindings(reqs, `${service}: spec.md`, service));\n findings.push(...requirementIdFindings(reqs, `${service}: spec.md`, service));\n findings.push(...repeatedListLineFindings(reqs, `${service}: spec.md`, service));\n } else {\n findings.push({\n severity: \"warn\",\n code: \"service.no-spec\",\n message: `No living spec at ${repoPath(docsDir, paths.spec)} — requirement coverage and API governance are unchecked`,\n });\n }\n\n // The requirements that still govern anything. A REMOVED requirement is on\n // its way out together with the operations it names: it makes no claim on the\n // contract and governs nothing once the retiring feature archives — the same\n // position coherence/coherence.ts takes on the delta side.\n //\n // Spelled once because it was spelled three times and dropped on the fourth:\n // `governedOps` filtered, `spec-api.op-undefined` filtered, and the\n // `api.covered` join did not — so an operation whose only requirement was\n // REMOVED counted as governed, and the one operation about to be left without\n // a requirement was the one the report called covered.\n const livingReqs = reqs.filter((r) => r.kind !== \"REMOVED\");\n\n // The architecture spec, read HERE with spec.md rather than with the rest of\n // its own axis below, because the event links are a requirement-level join and\n // BOTH requirement files carry them. Events are architecture as often as they\n // are business — the outbox requirement in a service's arch.spec.md is the\n // canonical place a `Publishes:` line belongs — so handing the event axis only\n // spec.md would silently ignore the lines most authors write.\n const archText = existsSync(paths.archSpec) ? await fleet.readText(paths.archSpec) : null;\n const archReqs = archText === null ? [] : await fleet.readRequirements(paths.archSpec);\n\n return { findings, reqs, livingReqs, archText, archReqs };\n}\n\n/** What the arch axis needs once the model and the landscape have been read. */\nexport interface ArchAxis {\n service: PathableService;\n paths: ServicePaths;\n /** arch.spec.md's text, or null — its absence is not a finding. */\n archText: string | null;\n archReqs: Requirement[];\n /** The service's own C4 model, empty when it has none. */\n elements: Elem[];\n relationships: Rel[];\n /**\n * True when this service's model could not be read — it does not parse\n * (`c4.invalid`), or it EXTENDS a fleet map that does not — so `elements` and\n * `relationships` above are empty for that reason and no other. See\n * `covers.unknown` below, which is suspended on it.\n */\n modelUnreadable?: boolean;\n /** The living landscape, or null when the repo has none. */\n land: LoadedDoc | null;\n /** The docs root, for the one lazy load below — see `deployment` in the scope. */\n docsDir: DocsDir;\n /**\n * The enumerated fleet, for the Covers matcher's endpoint resolution — the\n * same set the caller's own spine resolution uses, so a `Covers:` line\n * naming a service matches an edge drawn into that service's container.\n */\n known?: ReadonlySet<string>;\n /**\n * The run's read index, so the one lazy project load below is the same\n * memoised parse the rest of the target already made rather than a second\n * workspace spun for one `node:` entry.\n */\n fleet: FleetContext;\n}\n\n/**\n * The topology a `node:` Covers entry resolves against, loaded at most once and\n * only when something asks for it.\n *\n * Under `--all` the caller hands in the doc it already loaded — the whole\n * `architecture/` project — so the deployment model is already here and this\n * returns it. A SINGLE-service run loads only `landscape.likec4`, and a fleet\n * that keeps its `deployment { }` block in a file beside it, which is the\n * ordinary layout once one landscape file is the fleet's convention, would then\n * resolve one `Covers:` line two different ways depending on which command\n * asked. `loam status`'s two forms had exactly that disagreement and it was a\n * defect both times; this is the same class, prevented rather than found.\n *\n * The cost is paid only by a service whose `arch.spec.md` actually writes a\n * `node:` entry. A fleet with no topology at all pays one project load per such\n * service — a state in which the entry is a typo anyway, and one load is what\n * buys it a message that names the real ids.\n */\nasync function topology(\n land: LoadedDoc | null,\n archReqs: Requirement[],\n docsDir: DocsDir,\n fleet: FleetContext,\n): Promise<DeploymentModel | undefined> {\n if (land?.deployment !== undefined && hasDeployment(land.deployment)) return land.deployment;\n const wanted = coversEntries(archReqs).some((e) => e.form === \"node\" || e.form === \"node-edge\");\n if (!wanted) return undefined;\n // Through the index, always: `land` above is now that same project in every\n // mode, so this fallback fires only for the caller that handed in nothing,\n // and it must not be a second parse of documents already in the memo.\n const project = await fleet.architecture(docsDir);\n return project.errors.length === 0 ? project.deployment : undefined;\n}\n\nexport async function archAxisFindings(axis: ArchAxis): Promise<Finding[]> {\n const { service, paths, archText, archReqs, elements, relationships, land, known, docsDir } = axis;\n const { fleet } = axis;\n const findings: Finding[] = [];\n\n // The architecture spec axis — the obligations a business spec never carries\n // (the transactional outbox, retries, metrics, alerts). arch.spec.md is\n // optional and its ABSENCE is not a finding (partial adoption is supported);\n // what exists must hold together: every requirement needs a scenario, every\n // `Covers:` entry must resolve (covers.unknown), and every alert/SLI that\n // health.yaml declares wants a covering requirement (health.uncovered) — the\n // moment health.yaml stops being inert. Warnings, never gates: `--strict` is\n // the CI escalation.\n const health = await readHealth(paths.health);\n\n // A model that reaches nothing is almost never true — but only when\n // something else attests it should reach. A deliberately shallow synthetic\n // fixture can hold elements without relationships beside sources that name\n // dependencies, yet `0 errors, 0 warnings` would still read as done.\n // Evidence-gated on purpose: a bare root-plus-one-container baseline stays\n // silent (nothing PROVES it thin — the standard mid-adoption shape), the\n // warn needs either a second nested element with no edge joining anything,\n // or dependencies declared in this service's own health.yaml. The health\n // half goes quiet when the file is unreadable — no claims on bad data.\n // \"Nested\" is a PARENT in this slice, not a dot in the id, and the two stopped\n // meaning the same thing when a model could extend the map: an extending\n // placeholder `extend marketplace.orderService { }` contributes one element\n // whose id is dotted and whose parent belongs to the map, so a dot count read\n // it as nested architecture that reaches nothing — the standalone placeholder\n // `orderService = softwareSystem` was silent for the same shape. One rule for\n // both: `a`/`a.x`/`kafka`/`kafka.t` still counts two.\n const sliceIds = new Set(elements.map((e) => e.id));\n const nested = elements.filter((e) => {\n const dot = e.id.lastIndexOf(\".\");\n return dot !== -1 && sliceIds.has(e.id.slice(0, dot));\n }).length;\n const deps = health.unreadable ? [] : health.dependencies;\n if (elements.length > 0 && relationships.length === 0 && (nested > 1 || deps.length > 0)) {\n const evidence = [\n ...(nested > 1 ? [`${nested} nested elements with no edge joining them`] : []),\n ...(deps.length > 0\n ? [`health.yaml declares ${deps.length} dependenc${deps.length === 1 ? \"y\" : \"ies\"} (${deps.join(\", \")})`]\n : []),\n ].join(\", and \");\n findings.push({\n severity: \"warn\",\n code: \"c4.no-relationships\",\n subject: service,\n message:\n `${service}: model.likec4 declares ${elements.length} element(s) and 0 relationships, yet ${evidence} — ` +\n `a model that reaches nothing is almost never true; draw the edges the service actually has ` +\n `(what its containers call, what reads and writes its stores, what it depends on at runtime)`,\n });\n }\n\n if (archText !== null) {\n // The arch axis is advisory in what it ASKS for (covers.unknown,\n // health.uncovered are warnings) but not in whether the file is readable:\n // both breaches below are about the document itself, and they are graded\n // exactly as they are on the business axis — one file, one rule.\n const conflict = documentConflictFinding(`${service}: arch.spec.md`, service, archText);\n if (conflict !== null) findings.push(conflict);\n findings.push(coverageFinding(`${service}: arch requirements`, archReqs));\n findings.push(...scenarioShapeFindings(`${service}: arch requirements`, service, archReqs));\n findings.push(...duplicateRequirementFindings(archReqs, `${service}: arch.spec.md`, service));\n findings.push(...requirementIdFindings(archReqs, `${service}: arch.spec.md`, service));\n findings.push(...repeatedListLineFindings(archReqs, `${service}: arch.spec.md`, service));\n }\n // A health.yaml that exists but does not read is reported once, and the\n // checks that read it go quiet BOTH ways: its ids are unknown, so no\n // health.uncovered obligation exists (the empty id set below is already\n // silent), and no `Covers: alert:/sli:` entry may be graded a typo against\n // ids nobody could read (coversUnknownFindings mutes on the flag). A warn,\n // not an error: the axis it feeds is advisory end to end.\n if (health.unreadable) {\n findings.push({\n severity: \"warn\",\n code: \"health.invalid\",\n subject: service,\n message: `${service}: health.yaml does not parse — alert/SLI ids are unreadable, so Covers: alert:/sli: entries and health coverage are unchecked`,\n ...(health.error === undefined ? {} : { details: [health.error] }),\n });\n }\n const landParses = land !== null && land.errors.length === 0 ? land : null;\n const deployment = await topology(landParses, archReqs, docsDir, fleet);\n const scope: CoverageScope = {\n elements: [...elements, ...(landParses?.elements ?? [])],\n relationships: [...relationships, ...(landParses?.relationships ?? [])],\n ...(deployment === undefined ? {} : { deployment }),\n health: health.ids,\n known,\n };\n // `covers.unknown` is suspended entirely when the model could not be read —\n // it EXTENDS a map that does not parse, or it does not parse itself — and\n // this is the standing rule of the whole axis rather than a special case:\n // nothing may be graded out of a document nobody could read\n // (`core/c4/service-model/load.ts` states it; `model/grade.ts`, `model/diverged.ts`\n // and the flow skip in `./service.ts` all honour it). Such a model comes back\n // with no elements and no edges, so every `Covers:` line naming one of its\n // containers would resolve to nothing. For an unreadable map the landscape\n // half of the scope is empty too — one unparseable\n // `architecture/usecases/x.likec4` convicting every Covers: line of every\n // migrated service in the fleet as a typo, under a code whose whole job is to\n // catch typos. For the model's own parse error it was one dropped quote in\n // the example fleet's order-service: `c4.invalid` and six `covers.unknown`\n // warnings, each sending the reader to arch.spec.md to fix an id that was\n // spelled right. `c4.invalid` is the one finding; the grade comes back with\n // the next run that parses.\n //\n // The alert:/sli: forms could still be answered out of health.yaml, which IS\n // readable, and they are suspended with the rest on purpose: a verdict that\n // grades two of the Covers: forms and drops the others is a report whose\n // completeness depends on which form the author happened to write, and the one\n // repair — fix the map, or the model — brings all of them back on the next\n // run. The same holds for a STANDALONE model that does not parse, whose\n // landscape half is still readable: grading only the entries that name the\n // map would be that partial report again. The health axis keeps its own grade\n // either way (`health.uncovered`, below).\n if (axis.modelUnreadable !== true) {\n findings.push(\n ...coversUnknownFindings(archReqs, { where: `${service}: arch.spec.md`, subject: service }, scope, health.unreadable),\n );\n }\n const activeCovers = coversEntries(archReqs);\n for (const { form, ids } of [\n { form: \"alert\" as const, ids: health.ids.alerts },\n { form: \"sli\" as const, ids: health.ids.slis },\n ]) {\n for (const id of ids) {\n if (activeCovers.some((e) => e.form === form && e.id === id)) continue;\n findings.push({\n severity: \"warn\",\n code: \"health.uncovered\",\n subject: service,\n message: `${service}: health.yaml declares ${form === \"alert\" ? \"alert\" : \"SLI\"} '${id}' but no arch.spec.md requirement covers it — write one with 'Covers: ${form}:${id}', or the signal ships with nothing testing it`,\n });\n }\n }\n // The other direction health.yaml is read in: its dependencies must be the\n // model's dependencies. `deps` is already muted on an unreadable file above.\n findings.push(...healthDependencyFindings({ service, dependencies: deps, elements, modelPath: repoPath(docsDir, paths.model) }));\n\n return findings;\n}\n"]}
@@ -15,9 +15,10 @@
15
15
  */
16
16
  import {} from "../../../core/kernel/ids/service.js";
17
17
  import {} from "../../../core/c4/likec4.js";
18
+ import {} from "../../../core/kernel/ids/dirs.js";
18
19
  import {} from "../../../core/kernel/ids/service.js";
19
20
  import {} from "../../../core/vocabulary/report.js";
20
- import { errorText } from "../checks/vocabulary.js";
21
+ import { brokenDocumentPaths, errorText } from "../../../core/c4/likec4.js";
21
22
  import {} from "./api.js";
22
23
  export function spineFindings(spine) {
23
24
  const { service, me, land, landSvcOf, contract } = spine;
@@ -35,15 +36,49 @@ export function spineFindings(spine) {
35
36
  // becomes N copies of a dozen cascading diagnostics on a fleet of N
36
37
  // services — the report stops being readable at exactly the moment
37
38
  // somebody needs to read it, and the fix is one file either way.
39
+ // Which DOCUMENT broke, spelled exactly as the fleet arm spells it
40
+ // (`fleet/landscape.ts`): `land` is the whole `architecture/` project, so
41
+ // the broken file is as often a `usecases/*.likec4` as the map itself,
42
+ // and the filename used to be hardcoded here — an agent told to "fix the
43
+ // file the message names" opened a document that parses (verification
44
+ // 2026-09-04, W2).
45
+ const broken = brokenDocumentPaths(spine.docsDir, land.errors);
46
+ const named = broken.length === 0 ? "architecture/landscape.likec4" : broken.join(", ");
47
+ // The verb agrees with the LIST, not with the count after it: `named` is a
48
+ // comma-joined series once two documents broke, and "a, b, c has 6
49
+ // error(s)" is a sentence a reader trips over — the same care `remedy()`
50
+ // in model/diverged.ts takes over "the kind differ".
51
+ const verb = broken.length > 1 ? "have" : "has";
38
52
  findings.push({
39
53
  severity: "error",
40
54
  code: "spine.landscape-invalid",
41
55
  subject: service,
42
- message: `${service}: landscape.likec4 has ${land.errors.length} error(s) — spine check impossible` +
56
+ message: `${service}: ${named} ${verb} ${land.errors.length} error(s) — spine check impossible` +
57
+ (spine.modelExtends === true
58
+ ? " — and model.likec4 extends it, so the model cannot be read either"
59
+ : "") +
43
60
  (spine.landscapeReported === true
44
61
  ? "; the parser output is reported once, on the landscape target"
45
62
  : ""),
46
- ...(spine.landscapeReported === true ? {} : { details: land.errors.map(errorText) }),
63
+ ...(spine.landscapeReported === true
64
+ ? {}
65
+ : {
66
+ // Every line carries its own file once more than one document is
67
+ // broken, the fleet arm's rule verbatim: a bare `L8:` across two
68
+ // documents is unactionable. The prefix is `brokenDocumentPaths`'s
69
+ // spelling of that one error's document, as the message's list is.
70
+ details: land.errors.map((e) => {
71
+ const [file] = broken.length < 2 ? [] : brokenDocumentPaths(spine.docsDir, [e]);
72
+ return file === undefined ? errorText(e) : `${file} ${errorText(e)}`;
73
+ }),
74
+ }),
75
+ // The broken document first, the service second: the finding is about a
76
+ // file under `architecture/` and is FILED on this service, and without
77
+ // the pair the payload carried only the service directory.
78
+ locations: [
79
+ ...(broken.length === 0 ? [] : [{ path: broken[0], role: "primary" }]),
80
+ { path: spine.treePath, role: "scope" },
81
+ ],
47
82
  });
48
83
  }
49
84
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"spine.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/spine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The inbound half of the C4↔API spine: every cross-system edge the living\n * landscape draws INTO this service must resolve to a real operation in its\n * OpenAPI.\n *\n * Separate from `./api.js` because the two grade different documents and the\n * fix lives in different files — a broken edge is the fleet map's mistake, a\n * missing operation is the contract's — but it may conclude nothing without\n * `api.js`'s answer, which is why `ServiceContract` is a parameter rather than\n * a second read of openapi.yaml.\n *\n * The event-side mirror of this check is inside `./events.js`: there the\n * PRODUCER owns the contract and it lives in another repository, so the two\n * could not share a walk even though they share the shape.\n */\nimport { type DeclaredService } from \"../../../core/kernel/ids/service.js\";\nimport { type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { errorText } from \"../checks/vocabulary.js\";\nimport { type ServiceContract } from \"./api.js\";\n\n/** What the service target hands the spine check. */\nexport interface Spine {\n service: PathableService;\n /** `service` widened for `===` against the resolver's DOCUMENT text — see service.ts. */\n me: string;\n /** The living landscape, or null when the repo has none. */\n land: LoadedDoc | null;\n /** Its element→service resolver; null exactly when `land` is. */\n landSvcOf: ((id: string) => DeclaredService) | null;\n contract: ServiceContract;\n /** True when this run also emits a landscape target — see `ServiceCheck`. */\n landscapeReported?: boolean;\n}\n\nexport function spineFindings(spine: Spine): Finding[] {\n const { service, me, land, landSvcOf, contract } = spine;\n const findings: Finding[] = [];\n\n // Landscape spine: cross-system edges calling THIS service must resolve to a real\n // operation in its OpenAPI — the C4↔API contract, checked in the living landscape,\n // not only in feature mode. Catches dangling / de-linked op edges.\n if (land !== null) {\n if (land.errors.length > 0) {\n // A living landscape that does not parse disables the C4↔API spine check —\n // that is a broken source of truth, not a skippable detail.\n //\n // The parser's own output is attached ONCE per run, and not here when the\n // run has a landscape target to carry it. One syntax error in one file\n // becomes N copies of a dozen cascading diagnostics on a fleet of N\n // services — the report stops being readable at exactly the moment\n // somebody needs to read it, and the fix is one file either way.\n findings.push({\n severity: \"error\",\n code: \"spine.landscape-invalid\",\n subject: service,\n message:\n `${service}: landscape.likec4 has ${land.errors.length} error(s) — spine check impossible` +\n (spine.landscapeReported === true\n ? \"; the parser output is reported once, on the landscape target\"\n : \"\"),\n ...(spine.landscapeReported === true ? {} : { details: land.errors.map(errorText) }),\n });\n } else {\n // Which element IS this service is the binding's call, then a title that\n // names a real services/<id>/, and an edge into a modelled container\n // counts as an edge into its service — matching the exact id alone meant a\n // container edge left the spine without a word.\n const svcOf = landSvcOf!;\n const opset = new Set(contract.ops);\n let checked = 0;\n let broken = 0;\n for (const r of land.relationships) {\n if (svcOf(r.target) !== me) continue;\n if (r.op !== undefined) {\n // A contract that cannot be read — broken, or not there at all —\n // proves nothing about this edge, neither broken nor resolved.\n // Grading the edges against an empty operation set turned ONE root\n // cause (the file) into one `spine.op-undefined` per inbound edge, so\n // a service with twelve consumers reported twelve landscape defects\n // and never named the file. `openapi.invalid` / `service.no-openapi`\n // already did, from ./api.js; only op-link-missing (which never reads\n // the contract) stays live, and `checked` stays 0 so no false\n // spine.resolved is claimed either.\n if (contract.unreadable) continue;\n checked += 1;\n if (!opset.has(r.op)) {\n broken += 1;\n findings.push({\n severity: \"error\",\n code: \"spine.op-undefined\",\n message: `${service}: landscape edge ${svcOf(r.source)} → ${service} calls '${r.op}', not defined in ${service}'s OpenAPI`,\n });\n } else if (contract.deprecated.has(r.op)) {\n // The contract holds — the op is defined — but it is marked\n // `deprecated: true`: the consumer is standing on a contract being\n // retired, and should be migrating off it. Warn per inbound edge;\n // a deprecated op nobody calls raises no spine finding at all.\n findings.push({\n severity: \"warn\",\n code: \"spine.op-deprecated\",\n message: `${service}: landscape edge ${svcOf(r.source)} → ${service} calls '${r.op}', which ${service}'s OpenAPI marks deprecated — the consumer should migrate off it`,\n });\n }\n } else if ((r.title ?? \"\").toLowerCase().startsWith(\"call\")) {\n findings.push({\n severity: \"warn\",\n code: \"spine.op-link-missing\",\n message: `${service}: landscape edge ${svcOf(r.source)} → ${service} (\"${r.title}\") has no operation link (metadata { op })`,\n });\n }\n }\n if (broken === 0 && checked > 0) {\n findings.push({\n severity: \"ok\",\n code: \"spine.resolved\",\n message: `${service}: landscape spine (${checked} inbound call(s) resolve to OpenAPI)`,\n });\n }\n }\n }\n\n return findings;\n}\n"]}
1
+ {"version":3,"file":"spine.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/spine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The inbound half of the C4↔API spine: every cross-system edge the living\n * landscape draws INTO this service must resolve to a real operation in its\n * OpenAPI.\n *\n * Separate from `./api.js` because the two grade different documents and the\n * fix lives in different files — a broken edge is the fleet map's mistake, a\n * missing operation is the contract's — but it may conclude nothing without\n * `api.js`'s answer, which is why `ServiceContract` is a parameter rather than\n * a second read of openapi.yaml.\n *\n * The event-side mirror of this check is inside `./events.js`: there the\n * PRODUCER owns the contract and it lives in another repository, so the two\n * could not share a walk even though they share the shape.\n */\nimport { type DeclaredService } from \"../../../core/kernel/ids/service.js\";\nimport { type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { brokenDocumentPaths, errorText } from \"../../../core/c4/likec4.js\";\nimport { type ServiceContract } from \"./api.js\";\n\n/** What the service target hands the spine check. */\nexport interface Spine {\n service: PathableService;\n /** The docs root, so a project error's absolute path can be spelled repo-relative. */\n docsDir: DocsDir;\n /** The service directory, repo-relative and `/`-separated — the scope location beside the broken file. */\n treePath: string;\n /** `service` widened for `===` against the resolver's DOCUMENT text — see service.ts. */\n me: string;\n /**\n * The living landscape, or null when the repo has none. A `--base` run's\n * changed-edge grade (`../scope.ts`) hands in the same document narrowed to\n * the edges the branch wrote, which is why every verdict below is per edge\n * and none of them is about the edges that are absent.\n */\n land: LoadedDoc | null;\n /** Its element→service resolver; null exactly when `land` is. */\n landSvcOf: ((id: string) => DeclaredService) | null;\n contract: ServiceContract;\n /** True when this run also emits a landscape target — see `ServiceCheck`. */\n landscapeReported?: boolean;\n /**\n * True when this service's `model.likec4` EXTENDS the map rather than\n * standing alone (`core/c4/service-model/shape.ts`).\n *\n * It changes one sentence and no verdict. An extending model is parsed inside\n * the `architecture/` project, so a map that does not parse takes the model\n * with it: there is no `c4.invalid` and no `c4.valid` on the service target,\n * and without this clause the report shows a service whose model is simply\n * never mentioned. Said here rather than as a finding of its own because it is\n * the same fact — one unreadable document — and a second code would be a\n * second thing to fix for one repair.\n */\n modelExtends?: boolean;\n}\n\nexport function spineFindings(spine: Spine): Finding[] {\n const { service, me, land, landSvcOf, contract } = spine;\n const findings: Finding[] = [];\n\n // Landscape spine: cross-system edges calling THIS service must resolve to a real\n // operation in its OpenAPI — the C4↔API contract, checked in the living landscape,\n // not only in feature mode. Catches dangling / de-linked op edges.\n if (land !== null) {\n if (land.errors.length > 0) {\n // A living landscape that does not parse disables the C4↔API spine check —\n // that is a broken source of truth, not a skippable detail.\n //\n // The parser's own output is attached ONCE per run, and not here when the\n // run has a landscape target to carry it. One syntax error in one file\n // becomes N copies of a dozen cascading diagnostics on a fleet of N\n // services — the report stops being readable at exactly the moment\n // somebody needs to read it, and the fix is one file either way.\n // Which DOCUMENT broke, spelled exactly as the fleet arm spells it\n // (`fleet/landscape.ts`): `land` is the whole `architecture/` project, so\n // the broken file is as often a `usecases/*.likec4` as the map itself,\n // and the filename used to be hardcoded here — an agent told to \"fix the\n // file the message names\" opened a document that parses (verification\n // 2026-09-04, W2).\n const broken = brokenDocumentPaths(spine.docsDir, land.errors);\n const named = broken.length === 0 ? \"architecture/landscape.likec4\" : broken.join(\", \");\n // The verb agrees with the LIST, not with the count after it: `named` is a\n // comma-joined series once two documents broke, and \"a, b, c has 6\n // error(s)\" is a sentence a reader trips over — the same care `remedy()`\n // in model/diverged.ts takes over \"the kind differ\".\n const verb = broken.length > 1 ? \"have\" : \"has\";\n findings.push({\n severity: \"error\",\n code: \"spine.landscape-invalid\",\n subject: service,\n message:\n `${service}: ${named} ${verb} ${land.errors.length} error(s) — spine check impossible` +\n (spine.modelExtends === true\n ? \" — and model.likec4 extends it, so the model cannot be read either\"\n : \"\") +\n (spine.landscapeReported === true\n ? \"; the parser output is reported once, on the landscape target\"\n : \"\"),\n ...(spine.landscapeReported === true\n ? {}\n : {\n // Every line carries its own file once more than one document is\n // broken, the fleet arm's rule verbatim: a bare `L8:` across two\n // documents is unactionable. The prefix is `brokenDocumentPaths`'s\n // spelling of that one error's document, as the message's list is.\n details: land.errors.map((e) => {\n const [file] = broken.length < 2 ? [] : brokenDocumentPaths(spine.docsDir, [e]);\n return file === undefined ? errorText(e) : `${file} ${errorText(e)}`;\n }),\n }),\n // The broken document first, the service second: the finding is about a\n // file under `architecture/` and is FILED on this service, and without\n // the pair the payload carried only the service directory.\n locations: [\n ...(broken.length === 0 ? [] : [{ path: broken[0]!, role: \"primary\" as const }]),\n { path: spine.treePath, role: \"scope\" as const },\n ],\n });\n } else {\n // Which element IS this service is the binding's call, then a title that\n // names a real services/<id>/, and an edge into a modelled container\n // counts as an edge into its service — matching the exact id alone meant a\n // container edge left the spine without a word.\n const svcOf = landSvcOf!;\n const opset = new Set(contract.ops);\n let checked = 0;\n let broken = 0;\n for (const r of land.relationships) {\n if (svcOf(r.target) !== me) continue;\n if (r.op !== undefined) {\n // A contract that cannot be read — broken, or not there at all —\n // proves nothing about this edge, neither broken nor resolved.\n // Grading the edges against an empty operation set turned ONE root\n // cause (the file) into one `spine.op-undefined` per inbound edge, so\n // a service with twelve consumers reported twelve landscape defects\n // and never named the file. `openapi.invalid` / `service.no-openapi`\n // already did, from ./api.js; only op-link-missing (which never reads\n // the contract) stays live, and `checked` stays 0 so no false\n // spine.resolved is claimed either.\n if (contract.unreadable) continue;\n checked += 1;\n if (!opset.has(r.op)) {\n broken += 1;\n findings.push({\n severity: \"error\",\n code: \"spine.op-undefined\",\n message: `${service}: landscape edge ${svcOf(r.source)} → ${service} calls '${r.op}', not defined in ${service}'s OpenAPI`,\n });\n } else if (contract.deprecated.has(r.op)) {\n // The contract holds — the op is defined — but it is marked\n // `deprecated: true`: the consumer is standing on a contract being\n // retired, and should be migrating off it. Warn per inbound edge;\n // a deprecated op nobody calls raises no spine finding at all.\n findings.push({\n severity: \"warn\",\n code: \"spine.op-deprecated\",\n message: `${service}: landscape edge ${svcOf(r.source)} → ${service} calls '${r.op}', which ${service}'s OpenAPI marks deprecated — the consumer should migrate off it`,\n });\n }\n } else if ((r.title ?? \"\").toLowerCase().startsWith(\"call\")) {\n findings.push({\n severity: \"warn\",\n code: \"spine.op-link-missing\",\n message: `${service}: landscape edge ${svcOf(r.source)} → ${service} (\"${r.title}\") has no operation link (metadata { op })`,\n });\n }\n }\n if (broken === 0 && checked > 0) {\n findings.push({\n severity: \"ok\",\n code: \"spine.resolved\",\n message: `${service}: landscape spine (${checked} inbound call(s) resolve to OpenAPI)`,\n });\n }\n }\n }\n\n return findings;\n}\n"]}