@ybotok/loam 0.2.0-alpha.6 → 0.2.0-alpha.9

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 (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  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/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
@@ -12,8 +12,7 @@
12
12
  * new architecture — is `./arch-coverage.js`.
13
13
  */
14
14
  import { existsSync } from "node:fs";
15
- import { readFile } from "node:fs/promises";
16
- import { loadFile } from "../../core/c4/likec4.js";
15
+ import {} from "../../core/c4/likec4.js";
17
16
  import { elementService } from "../../core/c4/resolve/service.js";
18
17
  import {} from "../../core/kernel/ids/service.js";
19
18
  import {} from "../../core/repo/entries.js";
@@ -22,21 +21,19 @@ import { enumeratedServiceIds } from "../../core/repo/service-target.js";
22
21
  import { docsRepoState } from "../../core/repo/state.js";
23
22
  import { featureSpecServices, listServices } from "../../core/repo/repo.js";
24
23
  import {} from "../../core/vocabulary/report.js";
25
- import { parseRequirements } from "../../core/document/parse.js";
26
- import { assertionlessFindings, examplesFindings, steplessFindings, } from "../../core/document/scenarios.js";
27
24
  import {} from "../../core/document/spec.js";
28
25
  import { featureCoherence } from "../../core/coherence/coherence.js";
29
26
  import { deltaServiceUnknownFinding, invalidSpecServiceFindings, } from "../../core/coherence/living.js";
30
27
  import { issueFinding } from "../../core/vocabulary/issue.js";
31
28
  import { featureProvenance } from "../../core/provenance/findings.js";
32
29
  import { documentConflictFinding } from "../../core/conflict-markers.js";
33
- import { featureCapabilityDeltas } from "../../core/capabilities/delta/tree.js";
34
30
  import { docMissingFindings } from "../../core/capabilities/findings.js";
35
31
  import { FleetContext } from "../../core/fleet-context.js";
36
32
  import { errorText } from "../../core/c4/likec4.js";
37
- import { coverageFinding, repeatedListLineFindings } from "./checks/requirements.js";
33
+ import { coverageFinding, repeatedListLineFindings, scenarioShapeFindings } from "./checks/requirements.js";
38
34
  import { deltaArchCoverage } from "./arch-coverage.js";
39
35
  import { featureLinkFindings } from "./links/corpus.js";
36
+ import { featureRecordFindings } from "./service/evidence-pins.js";
40
37
  export async function validateFeature(docsDir, feature, preloadedLand, fleet) {
41
38
  const findings = [];
42
39
  const featureDir = feature.dir;
@@ -51,7 +48,7 @@ export async function validateFeature(docsDir, feature, preloadedLand, fleet) {
51
48
  let deltaDoc;
52
49
  const deltaPath = featurePaths(featureDir).delta;
53
50
  if (existsSync(deltaPath)) {
54
- const res = fleet === undefined ? await loadFile(deltaPath) : await fleet.loadLikeC4(deltaPath);
51
+ const res = await fleet.loadLikeC4(deltaPath);
55
52
  deltaDoc = res;
56
53
  if (res.errors.length > 0) {
57
54
  findings.push({
@@ -74,6 +71,17 @@ export async function validateFeature(docsDir, feature, preloadedLand, fleet) {
74
71
  }
75
72
  }
76
73
  findings.push(...(await featureProvenance(featureDir, featureId)));
74
+ // The verification record, graded for READABILITY only — never for what it
75
+ // concludes. A conflict-marked or hand-broken `verification.yaml` /
76
+ // `verification/<svc>.yaml` was refused by `verify`, reported by `status`,
77
+ // `gate` and `tasks`, and invisible here: `validate --all` from the docs repo
78
+ // (and so the PR gate) stayed green over a record nobody can read, because
79
+ // the only target raising this code was the service one, which runs from a
80
+ // service repo. Same code and severity as that target
81
+ // (`./service/evidence-pins.ts`) — a warn, so `valid` and `loam archive` are
82
+ // untouched (verify never gates archive) and `--strict` is the lever. The
83
+ // verdict itself stays `verify`'s and `status`'s to state.
84
+ findings.push(...(await featureRecordFindings(docsDir, feature)));
77
85
  // Who this feature is allowed to address. `specs/<svc>/` is what the archive
78
86
  // materialises `services/<svc>/` from, and nothing used to ask whether that
79
87
  // name means anything: one wrong character in `--touches` passed
@@ -122,9 +130,9 @@ export async function validateFeature(docsDir, feature, preloadedLand, fleet) {
122
130
  for (const svc of featureServices) {
123
131
  const p = featureSpecPaths(featureDir, svc);
124
132
  if (existsSync(p.spec)) {
125
- const raw = fleet === undefined ? await readFile(p.spec, "utf8") : await fleet.readText(p.spec);
133
+ const raw = await fleet.readText(p.spec);
126
134
  scenarioText += "\n" + raw.toLowerCase();
127
- const reqs = fleet === undefined ? parseRequirements(raw) : await fleet.readRequirements(p.spec);
135
+ const reqs = await fleet.readRequirements(p.spec);
128
136
  // Both document-level breaches carry into the living spec through the
129
137
  // merge, so a delta is graded for them exactly as a living document is:
130
138
  // conflict markers merge as prose under someone's requirement, and a
@@ -134,25 +142,21 @@ export async function validateFeature(docsDir, feature, preloadedLand, fleet) {
134
142
  if (conflict !== null)
135
143
  findings.push(conflict);
136
144
  findings.push({ ...coverageFinding(`${svc}: requirements`, reqs), subject: svc });
137
- findings.push(...steplessFindings(`${svc}: requirements`, svc, reqs));
138
- findings.push(...assertionlessFindings(`${svc}: requirements`, svc, reqs));
139
- findings.push(...examplesFindings(`${svc}: requirements`, svc, reqs));
145
+ findings.push(...scenarioShapeFindings(`${svc}: requirements`, svc, reqs));
140
146
  // The keep-last quirk loses lines in a delta exactly as in a living spec
141
147
  // — and a delta's lost Operations: line then merges into the living one.
142
148
  findings.push(...repeatedListLineFindings(reqs, `${svc}: spec.md`, svc));
143
149
  }
144
150
  if (existsSync(p.archSpec)) {
145
- const raw = fleet === undefined ? await readFile(p.archSpec, "utf8") : await fleet.readText(p.archSpec);
151
+ const raw = await fleet.readText(p.archSpec);
146
152
  scenarioText += "\n" + raw.toLowerCase();
147
- const reqs = fleet === undefined ? parseRequirements(raw) : await fleet.readRequirements(p.archSpec);
153
+ const reqs = await fleet.readRequirements(p.archSpec);
148
154
  archDeltas.push({ service: svc, reqs });
149
155
  const conflict = documentConflictFinding(`${svc}: arch.spec.md`, svc, raw);
150
156
  if (conflict !== null)
151
157
  findings.push(conflict);
152
158
  findings.push({ ...coverageFinding(`${svc}: arch requirements`, reqs), subject: svc });
153
- findings.push(...steplessFindings(`${svc}: arch requirements`, svc, reqs));
154
- findings.push(...assertionlessFindings(`${svc}: arch requirements`, svc, reqs));
155
- findings.push(...examplesFindings(`${svc}: arch requirements`, svc, reqs));
159
+ findings.push(...scenarioShapeFindings(`${svc}: arch requirements`, svc, reqs));
156
160
  findings.push(...repeatedListLineFindings(reqs, `${svc}: arch.spec.md`, svc));
157
161
  }
158
162
  }
@@ -164,7 +168,7 @@ export async function validateFeature(docsDir, feature, preloadedLand, fleet) {
164
168
  // feature is archived, nothing walks the delta again, and the living
165
169
  // capability documents are only conflict-scanned while some feature still
166
170
  // carries a delta for them.
167
- const capabilityDeltas = fleet === undefined ? await featureCapabilityDeltas(featureDir) : await fleet.featureCapabilityDeltas(featureDir);
171
+ const capabilityDeltas = await fleet.featureCapabilityDeltas(featureDir);
168
172
  // The half-created directory, on the FEATURE side. The living tree has warned
169
173
  // about `capabilities/<id>/` with no document since the axis landed; the same
170
174
  // `mkdir` inside `features/<FEAT>/capabilities/` earned nothing, and it is the
@@ -175,7 +179,7 @@ export async function validateFeature(docsDir, feature, preloadedLand, fleet) {
175
179
  // message spelling `features/FEAT-1/` names a path nobody can open.
176
180
  findings.push(...docMissingFindings(capabilityDeltas, feature.dirName));
177
181
  for (const doc of capabilityDeltas.docs) {
178
- const raw = fleet === undefined ? await readFile(doc.spec, "utf8") : await fleet.readText(doc.spec);
182
+ const raw = await fleet.readText(doc.spec);
179
183
  const conflict = documentConflictFinding(`capability ${doc.id}: spec.md`, doc.id, raw);
180
184
  if (conflict !== null)
181
185
  findings.push(conflict);
@@ -1 +1 @@
1
- {"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../src/commands/validate/feature.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The feature target: a delta graded on its own, before anything merges.\n *\n * Everything here reads the feature directory — delta.likec4, the per-service\n * spec.md/arch.spec.md deltas, the provenance stamp — and grades the documents\n * a merge is about to fold into living ones. A delta is held to the same\n * document rules as the file it becomes (`./checks/requirements.js`), because\n * grading it more leniently is how a defect acquires a signature saying it was\n * reviewed.\n *\n * The one question that needs the LIVING fleet — what in this delta is actually\n * new architecture — is `./arch-coverage.js`.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { loadFile, type Elem, type LoadedDoc, type Rel } from \"../../core/c4/likec4.js\";\nimport { elementService } from \"../../core/c4/resolve/service.js\";\nimport { type PathableService } from \"../../core/kernel/ids/service.js\";\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { featurePaths, featureSpecPaths } from \"../../core/repo/paths.js\";\nimport { enumeratedServiceIds } from \"../../core/repo/service-target.js\";\nimport { docsRepoState } from \"../../core/repo/state.js\";\nimport { featureSpecServices, listServices } from \"../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } 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 { featureCoherence } from \"../../core/coherence/coherence.js\";\nimport {\n deltaServiceUnknownFinding,\n invalidSpecServiceFindings,\n} from \"../../core/coherence/living.js\";\nimport { issueFinding } from \"../../core/vocabulary/issue.js\";\nimport { featureProvenance } from \"../../core/provenance/findings.js\";\nimport { documentConflictFinding } from \"../../core/conflict-markers.js\";\nimport { featureCapabilityDeltas } from \"../../core/capabilities/delta/tree.js\";\nimport { docMissingFindings } from \"../../core/capabilities/findings.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { errorText } from \"../../core/c4/likec4.js\";\nimport { coverageFinding, repeatedListLineFindings } from \"./checks/requirements.js\";\nimport { deltaArchCoverage } from \"./arch-coverage.js\";\nimport { featureLinkFindings } from \"./links/corpus.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport async function validateFeature(\n docsDir: DocsDir,\n feature: FeatureEntry,\n preloadedLand?: LoadedDoc | null,\n fleet?: FleetContext,\n): Promise<TargetReport> {\n const findings: Finding[] = [];\n const featureDir = feature.dir;\n const featureId = feature.id;\n\n // delta.likec4 parse + collect tagged edges. The loaded doc is kept and\n // handed to featureCoherence below — loading it is a Langium workspace spin,\n // and paying it twice per feature was the dominant cost of `validate --all`.\n let taggedEls: Elem[] = [];\n let taggedRels: Rel[] = [];\n let elements: Elem[] = [];\n let deltaRels: Rel[] = [];\n let deltaDoc: LoadedDoc | undefined;\n const deltaPath = featurePaths(featureDir).delta;\n if (existsSync(deltaPath)) {\n const res = fleet === undefined ? await loadFile(deltaPath) : await fleet.loadLikeC4(deltaPath);\n deltaDoc = res;\n if (res.errors.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"delta.invalid\",\n message: `delta.likec4 has ${res.errors.length} error(s)`,\n details: res.errors.map(errorText),\n });\n } else {\n elements = res.elements;\n deltaRels = res.relationships;\n taggedEls = res.elements.filter((e) => e.tags.includes(featureId));\n taggedRels = res.relationships.filter((r) => r.tags.includes(featureId));\n findings.push({\n severity: \"ok\",\n code: \"delta.valid\",\n message: `delta.likec4 valid (${res.elements.length} elements · ${res.relationships.length} relationships)`,\n });\n }\n }\n\n findings.push(...(await featureProvenance(featureDir, featureId)));\n\n // Who this feature is allowed to address. `specs/<svc>/` is what the archive\n // materialises `services/<svc>/` from, and nothing used to ask whether that\n // name means anything: one wrong character in `--touches` passed\n // `validate --all` with zero errors, and archive then created the phantom\n // directory. A delta may legitimately name a service that does not exist yet\n // — but only one it INTRODUCES itself, in its own tagged C4. A delta that did\n // not parse proves neither (`delta.invalid` is that finding), so the question\n // is suspended there rather than answered by guessing.\n //\n // Existence is enumeration MEMBERSHIP through the no-services-tolerant\n // spelling, never `existsSync(services/<svc>/)` at the root: a service filed\n // into a subsystem exists wherever the tree walk found it, and the root\n // probe graded every filed service `delta.service-unknown`. The tolerance\n // carries the old carve-out forward — `validate --feature` is allowed to run\n // in a docs repo with no services/ at all, where enumerating is a refusal;\n // `enumeratedServiceIds` swallows exactly that refusal to an empty list, in\n // which every service is honestly absent.\n const featureServices = await featureSpecServices(featureDir, fleet);\n const introduces: ReadonlySet<string> = new Set(taggedEls.map(elementService));\n const deltaReadable = deltaDoc === undefined || deltaDoc.errors.length === 0;\n const enumerated = new Set<string>(await enumeratedServiceIds(docsDir, fleet));\n const unknownServices = deltaReadable\n ? featureServices.filter((svc) => !enumerated.has(svc) && !introduces.has(svc))\n : [];\n // The near-miss hint, on the same rule `service.unknown` uses — a typo is\n // only diagnosable against the ids that DO exist.\n const closeTo =\n unknownServices.length > 0 && docsRepoState(docsDir).kind === \"ok\"\n ? (await listServices(docsDir, fleet)).map((s) => s.id)\n : [];\n // The finding is coherence/living.ts's — the same words archive refuses with,\n // because it is the same conclusion about the same directory.\n for (const svc of unknownServices) findings.push(deltaServiceUnknownFinding(svc, closeTo));\n\n // The grammar half of the same guarantee. `delta.service-unknown` asks whether\n // the directory names a service anyone knows; this asks whether the NAME could\n // ever be one. The two are independent on purpose: a tagged element whose\n // title matches the directory answers the first question, which is exactly how\n // `specs/Payment Service/` used to validate green and archive into a directory\n // no loam command can address. Not suspended on an unreadable delta either —\n // no reading of the architecture axis can make the name legal.\n findings.push(...(await invalidSpecServiceFindings(featureDir, fleet)));\n\n // Requirement coverage across every per-service delta — the business spec and\n // the arch spec through the same check — and collect scenario text.\n let scenarioText = \"\";\n const archDeltas: Array<{ service: PathableService; reqs: Requirement[] }> = [];\n for (const svc of featureServices) {\n const p = featureSpecPaths(featureDir, svc);\n if (existsSync(p.spec)) {\n const raw = fleet === undefined ? await readFile(p.spec, \"utf8\") : await fleet.readText(p.spec);\n scenarioText += \"\\n\" + raw.toLowerCase();\n const reqs = fleet === undefined ? parseRequirements(raw) : await fleet.readRequirements(p.spec);\n // Both document-level breaches carry into the living spec through the\n // merge, so a delta is graded for them exactly as a living document is:\n // conflict markers merge as prose under someone's requirement, and a\n // stepless scenario merges as a requirement the coverage rule calls\n // covered forever after.\n const conflict = documentConflictFinding(`${svc}: spec.md`, svc, raw);\n if (conflict !== null) findings.push(conflict);\n findings.push({ ...coverageFinding(`${svc}: requirements`, reqs), subject: svc });\n findings.push(...steplessFindings(`${svc}: requirements`, svc, reqs));\n findings.push(...assertionlessFindings(`${svc}: requirements`, svc, reqs));\n findings.push(...examplesFindings(`${svc}: requirements`, svc, reqs));\n // The keep-last quirk loses lines in a delta exactly as in a living spec\n // — and a delta's lost Operations: line then merges into the living one.\n findings.push(...repeatedListLineFindings(reqs, `${svc}: spec.md`, svc));\n }\n if (existsSync(p.archSpec)) {\n const raw = fleet === undefined ? await readFile(p.archSpec, \"utf8\") : await fleet.readText(p.archSpec);\n scenarioText += \"\\n\" + raw.toLowerCase();\n const reqs = fleet === undefined ? parseRequirements(raw) : await fleet.readRequirements(p.archSpec);\n archDeltas.push({ service: svc, reqs });\n const conflict = documentConflictFinding(`${svc}: arch.spec.md`, svc, raw);\n if (conflict !== null) findings.push(conflict);\n findings.push({ ...coverageFinding(`${svc}: arch requirements`, reqs), subject: svc });\n findings.push(...steplessFindings(`${svc}: arch requirements`, svc, reqs));\n findings.push(...assertionlessFindings(`${svc}: arch requirements`, svc, reqs));\n findings.push(...examplesFindings(`${svc}: arch requirements`, svc, reqs));\n findings.push(...repeatedListLineFindings(reqs, `${svc}: arch.spec.md`, svc));\n }\n }\n\n // The BUSINESS corpus's delta documents, graded for the same document-level\n // breach as the service ones and for the same reason: a conflicted delta\n // parses as prose, so every other check reads it as a valid document, and\n // `loam archive` then re-serializes both sides of somebody's merge into the\n // LIVING capability document. It is worse there than one tree over — once the\n // feature is archived, nothing walks the delta again, and the living\n // capability documents are only conflict-scanned while some feature still\n // carries a delta for them.\n const capabilityDeltas =\n fleet === undefined ? await featureCapabilityDeltas(featureDir) : await fleet.featureCapabilityDeltas(featureDir);\n // The half-created directory, on the FEATURE side. The living tree has warned\n // about `capabilities/<id>/` with no document since the axis landed; the same\n // `mkdir` inside `features/<FEAT>/capabilities/` earned nothing, and it is the\n // quieter of the two — an empty directory is not a delta, so the walk finds\n // no document, the merge carries nothing, and the business change ships as\n // zero at exit 0. Same code, feature-scoped message (`../../core/capabilities/findings.ts`).\n // `dirName`, not the id: the directory is `features/FEAT-1-split/`, and a\n // message spelling `features/FEAT-1/` names a path nobody can open.\n findings.push(...docMissingFindings(capabilityDeltas, feature.dirName));\n for (const doc of capabilityDeltas.docs) {\n const raw = fleet === undefined ? await readFile(doc.spec, \"utf8\") : await fleet.readText(doc.spec);\n const conflict = documentConflictFinding(`capability ${doc.id}: spec.md`, doc.id, raw);\n if (conflict !== null) findings.push(conflict);\n }\n\n // The links this feature's own documents write — the intent, each service's\n // two spec deltas, its ADRs and its capability deltas. Same place in the\n // order as the service target's: after every join loam defined a line for.\n findings.push(...(await featureLinkFindings({ docsDir, subject: featureId, fleet }, featureDir)));\n\n findings.push(\n ...(await deltaArchCoverage({\n docsDir,\n elements,\n relationships: deltaRels,\n taggedEls,\n taggedRels,\n archDeltas,\n featureServices,\n scenarioText,\n preloadedLand,\n fleet,\n })),\n );\n\n\n // Coherence — cross-axis consistency (C4 ↔ requirements ↔ OpenAPI).\n const issues = await featureCoherence({ docsDir, featureDir, featureId, preloadedDelta: deltaDoc, context: fleet });\n if (issues.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"coherence.ok\",\n message: \"coherence: ✓ C4 · requirements · OpenAPI agree\",\n text: { indent: 2, marker: false },\n });\n } else {\n for (const i of issues) {\n findings.push({ ...issueFinding(i), text: { indent: 4, header: \"coherence:\" } });\n }\n }\n\n return { kind: \"feature\", id: featureId, path: `features/${feature.dirName}`, findings };\n}\n"]}
1
+ {"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../src/commands/validate/feature.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The feature target: a delta graded on its own, before anything merges.\n *\n * Everything here reads the feature directory — delta.likec4, the per-service\n * spec.md/arch.spec.md deltas, the provenance stamp — and grades the documents\n * a merge is about to fold into living ones. A delta is held to the same\n * document rules as the file it becomes (`./checks/requirements.js`), because\n * grading it more leniently is how a defect acquires a signature saying it was\n * reviewed.\n *\n * The one question that needs the LIVING fleet — what in this delta is actually\n * new architecture — is `./arch-coverage.js`.\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../core/c4/likec4.js\";\nimport { elementService } from \"../../core/c4/resolve/service.js\";\nimport { type PathableService } from \"../../core/kernel/ids/service.js\";\nimport { type FeatureEntry } from \"../../core/repo/entries.js\";\nimport { featurePaths, featureSpecPaths } from \"../../core/repo/paths.js\";\nimport { enumeratedServiceIds } from \"../../core/repo/service-target.js\";\nimport { docsRepoState } from \"../../core/repo/state.js\";\nimport { featureSpecServices, listServices } from \"../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } from \"../../core/vocabulary/report.js\";\nimport { type Requirement } from \"../../core/document/spec.js\";\nimport { featureCoherence } from \"../../core/coherence/coherence.js\";\nimport {\n deltaServiceUnknownFinding,\n invalidSpecServiceFindings,\n} from \"../../core/coherence/living.js\";\nimport { issueFinding } from \"../../core/vocabulary/issue.js\";\nimport { featureProvenance } from \"../../core/provenance/findings.js\";\nimport { documentConflictFinding } from \"../../core/conflict-markers.js\";\nimport { docMissingFindings } from \"../../core/capabilities/findings.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { errorText } from \"../../core/c4/likec4.js\";\nimport { coverageFinding, repeatedListLineFindings, scenarioShapeFindings } from \"./checks/requirements.js\";\nimport { deltaArchCoverage } from \"./arch-coverage.js\";\nimport { featureLinkFindings } from \"./links/corpus.js\";\nimport { featureRecordFindings } from \"./service/evidence-pins.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport async function validateFeature(\n docsDir: DocsDir,\n feature: FeatureEntry,\n preloadedLand: LoadedDoc | null | undefined,\n fleet: FleetContext,\n): Promise<TargetReport> {\n const findings: Finding[] = [];\n const featureDir = feature.dir;\n const featureId = feature.id;\n\n // delta.likec4 parse + collect tagged edges. The loaded doc is kept and\n // handed to featureCoherence below — loading it is a Langium workspace spin,\n // and paying it twice per feature was the dominant cost of `validate --all`.\n let taggedEls: Elem[] = [];\n let taggedRels: Rel[] = [];\n let elements: Elem[] = [];\n let deltaRels: Rel[] = [];\n let deltaDoc: LoadedDoc | undefined;\n const deltaPath = featurePaths(featureDir).delta;\n if (existsSync(deltaPath)) {\n const res = await fleet.loadLikeC4(deltaPath);\n deltaDoc = res;\n if (res.errors.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"delta.invalid\",\n message: `delta.likec4 has ${res.errors.length} error(s)`,\n details: res.errors.map(errorText),\n });\n } else {\n elements = res.elements;\n deltaRels = res.relationships;\n taggedEls = res.elements.filter((e) => e.tags.includes(featureId));\n taggedRels = res.relationships.filter((r) => r.tags.includes(featureId));\n findings.push({\n severity: \"ok\",\n code: \"delta.valid\",\n message: `delta.likec4 valid (${res.elements.length} elements · ${res.relationships.length} relationships)`,\n });\n }\n }\n\n findings.push(...(await featureProvenance(featureDir, featureId)));\n\n // The verification record, graded for READABILITY only — never for what it\n // concludes. A conflict-marked or hand-broken `verification.yaml` /\n // `verification/<svc>.yaml` was refused by `verify`, reported by `status`,\n // `gate` and `tasks`, and invisible here: `validate --all` from the docs repo\n // (and so the PR gate) stayed green over a record nobody can read, because\n // the only target raising this code was the service one, which runs from a\n // service repo. Same code and severity as that target\n // (`./service/evidence-pins.ts`) — a warn, so `valid` and `loam archive` are\n // untouched (verify never gates archive) and `--strict` is the lever. The\n // verdict itself stays `verify`'s and `status`'s to state.\n findings.push(...(await featureRecordFindings(docsDir, feature)));\n\n // Who this feature is allowed to address. `specs/<svc>/` is what the archive\n // materialises `services/<svc>/` from, and nothing used to ask whether that\n // name means anything: one wrong character in `--touches` passed\n // `validate --all` with zero errors, and archive then created the phantom\n // directory. A delta may legitimately name a service that does not exist yet\n // — but only one it INTRODUCES itself, in its own tagged C4. A delta that did\n // not parse proves neither (`delta.invalid` is that finding), so the question\n // is suspended there rather than answered by guessing.\n //\n // Existence is enumeration MEMBERSHIP through the no-services-tolerant\n // spelling, never `existsSync(services/<svc>/)` at the root: a service filed\n // into a subsystem exists wherever the tree walk found it, and the root\n // probe graded every filed service `delta.service-unknown`. The tolerance\n // carries the old carve-out forward — `validate --feature` is allowed to run\n // in a docs repo with no services/ at all, where enumerating is a refusal;\n // `enumeratedServiceIds` swallows exactly that refusal to an empty list, in\n // which every service is honestly absent.\n const featureServices = await featureSpecServices(featureDir, fleet);\n const introduces: ReadonlySet<string> = new Set(taggedEls.map(elementService));\n const deltaReadable = deltaDoc === undefined || deltaDoc.errors.length === 0;\n const enumerated = new Set<string>(await enumeratedServiceIds(docsDir, fleet));\n const unknownServices = deltaReadable\n ? featureServices.filter((svc) => !enumerated.has(svc) && !introduces.has(svc))\n : [];\n // The near-miss hint, on the same rule `service.unknown` uses — a typo is\n // only diagnosable against the ids that DO exist.\n const closeTo =\n unknownServices.length > 0 && docsRepoState(docsDir).kind === \"ok\"\n ? (await listServices(docsDir, fleet)).map((s) => s.id)\n : [];\n // The finding is coherence/living.ts's — the same words archive refuses with,\n // because it is the same conclusion about the same directory.\n for (const svc of unknownServices) findings.push(deltaServiceUnknownFinding(svc, closeTo));\n\n // The grammar half of the same guarantee. `delta.service-unknown` asks whether\n // the directory names a service anyone knows; this asks whether the NAME could\n // ever be one. The two are independent on purpose: a tagged element whose\n // title matches the directory answers the first question, which is exactly how\n // `specs/Payment Service/` used to validate green and archive into a directory\n // no loam command can address. Not suspended on an unreadable delta either —\n // no reading of the architecture axis can make the name legal.\n findings.push(...(await invalidSpecServiceFindings(featureDir, fleet)));\n\n // Requirement coverage across every per-service delta — the business spec and\n // the arch spec through the same check — and collect scenario text.\n let scenarioText = \"\";\n const archDeltas: Array<{ service: PathableService; reqs: Requirement[] }> = [];\n for (const svc of featureServices) {\n const p = featureSpecPaths(featureDir, svc);\n if (existsSync(p.spec)) {\n const raw = await fleet.readText(p.spec);\n scenarioText += \"\\n\" + raw.toLowerCase();\n const reqs = await fleet.readRequirements(p.spec);\n // Both document-level breaches carry into the living spec through the\n // merge, so a delta is graded for them exactly as a living document is:\n // conflict markers merge as prose under someone's requirement, and a\n // stepless scenario merges as a requirement the coverage rule calls\n // covered forever after.\n const conflict = documentConflictFinding(`${svc}: spec.md`, svc, raw);\n if (conflict !== null) findings.push(conflict);\n findings.push({ ...coverageFinding(`${svc}: requirements`, reqs), subject: svc });\n findings.push(...scenarioShapeFindings(`${svc}: requirements`, svc, reqs));\n // The keep-last quirk loses lines in a delta exactly as in a living spec\n // — and a delta's lost Operations: line then merges into the living one.\n findings.push(...repeatedListLineFindings(reqs, `${svc}: spec.md`, svc));\n }\n if (existsSync(p.archSpec)) {\n const raw = await fleet.readText(p.archSpec);\n scenarioText += \"\\n\" + raw.toLowerCase();\n const reqs = await fleet.readRequirements(p.archSpec);\n archDeltas.push({ service: svc, reqs });\n const conflict = documentConflictFinding(`${svc}: arch.spec.md`, svc, raw);\n if (conflict !== null) findings.push(conflict);\n findings.push({ ...coverageFinding(`${svc}: arch requirements`, reqs), subject: svc });\n findings.push(...scenarioShapeFindings(`${svc}: arch requirements`, svc, reqs));\n findings.push(...repeatedListLineFindings(reqs, `${svc}: arch.spec.md`, svc));\n }\n }\n\n // The BUSINESS corpus's delta documents, graded for the same document-level\n // breach as the service ones and for the same reason: a conflicted delta\n // parses as prose, so every other check reads it as a valid document, and\n // `loam archive` then re-serializes both sides of somebody's merge into the\n // LIVING capability document. It is worse there than one tree over — once the\n // feature is archived, nothing walks the delta again, and the living\n // capability documents are only conflict-scanned while some feature still\n // carries a delta for them.\n const capabilityDeltas = await fleet.featureCapabilityDeltas(featureDir);\n // The half-created directory, on the FEATURE side. The living tree has warned\n // about `capabilities/<id>/` with no document since the axis landed; the same\n // `mkdir` inside `features/<FEAT>/capabilities/` earned nothing, and it is the\n // quieter of the two — an empty directory is not a delta, so the walk finds\n // no document, the merge carries nothing, and the business change ships as\n // zero at exit 0. Same code, feature-scoped message (`../../core/capabilities/findings.ts`).\n // `dirName`, not the id: the directory is `features/FEAT-1-split/`, and a\n // message spelling `features/FEAT-1/` names a path nobody can open.\n findings.push(...docMissingFindings(capabilityDeltas, feature.dirName));\n for (const doc of capabilityDeltas.docs) {\n const raw = await fleet.readText(doc.spec);\n const conflict = documentConflictFinding(`capability ${doc.id}: spec.md`, doc.id, raw);\n if (conflict !== null) findings.push(conflict);\n }\n\n // The links this feature's own documents write — the intent, each service's\n // two spec deltas, its ADRs and its capability deltas. Same place in the\n // order as the service target's: after every join loam defined a line for.\n findings.push(...(await featureLinkFindings({ docsDir, subject: featureId, fleet }, featureDir)));\n\n findings.push(\n ...(await deltaArchCoverage({\n docsDir,\n elements,\n relationships: deltaRels,\n taggedEls,\n taggedRels,\n archDeltas,\n featureServices,\n scenarioText,\n preloadedLand,\n fleet,\n })),\n );\n\n\n // Coherence — cross-axis consistency (C4 ↔ requirements ↔ OpenAPI).\n const issues = await featureCoherence({ docsDir, featureDir, featureId, preloadedDelta: deltaDoc, context: fleet });\n if (issues.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"coherence.ok\",\n message: \"coherence: ✓ C4 · requirements · OpenAPI agree\",\n text: { indent: 2, marker: false },\n });\n } else {\n for (const i of issues) {\n findings.push({ ...issueFinding(i), text: { indent: 4, header: \"coherence:\" } });\n }\n }\n\n return { kind: \"feature\", id: featureId, path: `features/${feature.dirName}`, findings };\n}\n"]}
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import {} from "../../../core/c4/likec4.js";
13
13
  import { properAncestorIds } from "../../../core/kernel/ids/fqn/ancestors.js";
14
- import { ACTOR_KINDS, EXTERNAL_TAG } from "../../../core/vocabulary/maturity.js";
14
+ import { ACTOR_KINDS, EXTERNAL_TAG, INFRA_KINDS } from "../../../core/vocabulary/maturity.js";
15
15
  /**
16
16
  * Does this element stand for a `services/<id>/` directory — an explicit
17
17
  * `metadata { service }` binding, or a title naming a real one? The question
@@ -50,7 +50,8 @@ export function serviceLevelElements(elements, services) {
50
50
  }
51
51
  /**
52
52
  * The drawn SYSTEMS: service-level elements minus everything the map itself
53
- * says is not a service. Actor kinds model people; `#external` is somebody
53
+ * says is not a service. Actor kinds model people; INFRA kinds model the
54
+ * datastores, queues and buckets the fleet RUNS; `#external` is somebody
54
55
  * else's system; and an element that CONTAINS a service is a grouping — a
55
56
  * domain, a boundary, an enterprise — not a service nobody adopted. There is
56
57
  * nothing to bind on a grouping and nothing to tag #external, so asking for
@@ -59,12 +60,34 @@ export function serviceLevelElements(elements, services) {
59
60
  * the scorecard's `c4.elements` census counts it — one predicate, two readers,
60
61
  * zero drift.
61
62
  *
63
+ * The INFRA skip is the newest and the one with a measured cost behind it. A
64
+ * `database` the fleet OWNS and draws at fleet level is neither a person nor
65
+ * somebody else's system, so it used to earn `landscape.service-undocumented`
66
+ * — "bind it with `metadata { service '<id>' }`, or tag it `#external`" —
67
+ * whose two remedies are both FALSE of it: there is no directory to bind, and
68
+ * it is ours. Measured on a one-service fixture drawing an owned `database` at
69
+ * fleet level (2026-09-07): that warning plus `landscape.datastore-private`
70
+ * plus `gate.partner-undocumented` on its one consumer, three findings and no
71
+ * correct fix, which is why both `examples/docs` and this repo's own suite
72
+ * tagged an owned Redis `#external` to buy silence.
73
+ *
74
+ * `standsForService` wins over the kind, and that guard is load-bearing rather
75
+ * than defensive: a `database` bound with `metadata { service '<id>' }`, or one
76
+ * whose title names a real `services/<id>/`, IS an adopted service — dropping
77
+ * it here would take it out of the scorecard's `c4.elements` denominator and
78
+ * out of the binding passes at once. The kind is the fallback for an element
79
+ * nothing else answers for, never an override.
80
+ *
62
81
  * The `#external` skip here is exactly what `./kind-tags.ts` guards: read off a
63
- * tag a KIND declared, it can empty this census for a whole fleet at once.
82
+ * tag a KIND declared, it can empty this census for a whole fleet at once. The
83
+ * INFRA skip is NOT guarded there and does not need to be — a kind is what an
84
+ * element already is, not a claim declared over the fleet, and `#infrastructure`
85
+ * is deliberately not a tag loam grades on (`GRADED_TAGS`).
64
86
  */
65
87
  export function drawnSystems(elements, services) {
66
88
  return serviceLevelElements(elements, services).filter((e) => !e.tags.includes(EXTERNAL_TAG) &&
67
89
  !ACTOR_KINDS.has(e.kind.toLowerCase()) &&
90
+ !(INFRA_KINDS.has(e.kind.toLowerCase()) && !standsForService(e, services)) &&
68
91
  !elements.some((c) => c.id.startsWith(`${e.id}.`) && standsForService(c, services)));
69
92
  }
70
93
  //# sourceMappingURL=census.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"census.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/census.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAa,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAO,EAAE,QAA6B;IACrE,OAAO,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,QAA6B;IAClF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD;;;;;OAKG;IACH,MAAM,WAAW,GAAG,CAAC,CAAO,EAAU,EAAE,CACtC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5B,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IACL,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC5F,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,QAA6B;IAC1E,OAAO,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,MAAM,CACpD,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9B,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACtC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,gBAAgB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CACtF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The fleet map's census: which drawn elements answer for a `services/<id>/`.\n *\n * Three predicates, in one place because every exemption in the fleet target,\n * the scorecard's `c4.elements` count and the kind-tag check all have to answer\n * the same question the same way — a second spelling of \"is this element a\n * service\" is how a map and a rollup start disagreeing about the same fleet.\n * They lived in `./landscape.ts` beside their first reader until that file hit\n * its line limit; the seam was already there, since nothing here reads a file,\n * emits a finding, or knows what a target is.\n */\nimport { type Elem } from \"../../../core/c4/likec4.js\";\nimport { properAncestorIds } from \"../../../core/kernel/ids/fqn/ancestors.js\";\nimport { ACTOR_KINDS, EXTERNAL_TAG } from \"../../../core/vocabulary/maturity.js\";\n\n/**\n * Does this element stand for a `services/<id>/` directory — an explicit\n * `metadata { service }` binding, or a title naming a real one? The question\n * every exemption in `./landscape.ts`, the scorecard's census and\n * `./kind-tags.ts` all ask, exported so they cannot answer it differently.\n */\nexport function standsForService(e: Elem, services: ReadonlySet<string>): boolean {\n return e.service !== undefined || services.has(e.title);\n}\n\n/**\n * Service-LEVEL elements: everything not drawn inside something that is\n * already a service.\n *\n * Depth is not a fact about a service. This used to keep only top-level\n * elements (`!e.id.includes(\".\")`), which ordinary C4 breaks the moment it\n * groups services under a parent: every nested element was thrown away, so a\n * bound service read as unmodelled — an ERROR, on every service in the fleet\n * — and a binding written one level down was never checked at all. The tree\n * is walked instead, the way `serviceResolver` walks it for edges: a binding\n * wins at any depth, then a title naming a real services/<id>/, and what sits\n * INSIDE one of those is that service's container, not a service of its own.\n */\nexport function serviceLevelElements(elements: Elem[], services: ReadonlySet<string>): Elem[] {\n const byId = new Map(elements.map((e) => [e.id, e]));\n /**\n * The ancestors this document actually DECLARES, nearest first. The chain\n * itself is `core/kernel/ids/fqn/ancestors.ts`'s — the one spelling of it —\n * and the filter is this function's own: an id may name a level nothing\n * declares, and an undeclared level answers for no service.\n */\n const ancestorsOf = (e: Elem): Elem[] =>\n properAncestorIds(e.id).flatMap((id) => {\n const parent = byId.get(id);\n return parent === undefined ? [] : [parent];\n });\n return elements.filter((e) => !ancestorsOf(e).some((p) => standsForService(p, services)));\n}\n\n/**\n * The drawn SYSTEMS: service-level elements minus everything the map itself\n * says is not a service. Actor kinds model people; `#external` is somebody\n * else's system; and an element that CONTAINS a service is a grouping — a\n * domain, a boundary, an enterprise — not a service nobody adopted. There is\n * nothing to bind on a grouping and nothing to tag #external, so asking for\n * either would be a warning with no correct fix; the services under it answer\n * for themselves. `validateLandscape`'s undocumented pass walks this set, and\n * the scorecard's `c4.elements` census counts it — one predicate, two readers,\n * zero drift.\n *\n * The `#external` skip here is exactly what `./kind-tags.ts` guards: read off a\n * tag a KIND declared, it can empty this census for a whole fleet at once.\n */\nexport function drawnSystems(elements: Elem[], services: ReadonlySet<string>): Elem[] {\n return serviceLevelElements(elements, services).filter(\n (e) =>\n !e.tags.includes(EXTERNAL_TAG) &&\n !ACTOR_KINDS.has(e.kind.toLowerCase()) &&\n !elements.some((c) => c.id.startsWith(`${e.id}.`) && standsForService(c, services)),\n );\n}\n"]}
1
+ {"version":3,"file":"census.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/census.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAa,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAE9F;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAO,EAAE,QAA6B;IACrE,OAAO,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,QAA6B;IAClF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD;;;;;OAKG;IACH,MAAM,WAAW,GAAG,CAAC,CAAO,EAAU,EAAE,CACtC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5B,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IACL,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC5F,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,QAA6B;IAC1E,OAAO,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,MAAM,CACpD,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9B,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACtC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC1E,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,gBAAgB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CACtF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The fleet map's census: which drawn elements answer for a `services/<id>/`.\n *\n * Three predicates, in one place because every exemption in the fleet target,\n * the scorecard's `c4.elements` count and the kind-tag check all have to answer\n * the same question the same way — a second spelling of \"is this element a\n * service\" is how a map and a rollup start disagreeing about the same fleet.\n * They lived in `./landscape.ts` beside their first reader until that file hit\n * its line limit; the seam was already there, since nothing here reads a file,\n * emits a finding, or knows what a target is.\n */\nimport { type Elem } from \"../../../core/c4/likec4.js\";\nimport { properAncestorIds } from \"../../../core/kernel/ids/fqn/ancestors.js\";\nimport { ACTOR_KINDS, EXTERNAL_TAG, INFRA_KINDS } from \"../../../core/vocabulary/maturity.js\";\n\n/**\n * Does this element stand for a `services/<id>/` directory — an explicit\n * `metadata { service }` binding, or a title naming a real one? The question\n * every exemption in `./landscape.ts`, the scorecard's census and\n * `./kind-tags.ts` all ask, exported so they cannot answer it differently.\n */\nexport function standsForService(e: Elem, services: ReadonlySet<string>): boolean {\n return e.service !== undefined || services.has(e.title);\n}\n\n/**\n * Service-LEVEL elements: everything not drawn inside something that is\n * already a service.\n *\n * Depth is not a fact about a service. This used to keep only top-level\n * elements (`!e.id.includes(\".\")`), which ordinary C4 breaks the moment it\n * groups services under a parent: every nested element was thrown away, so a\n * bound service read as unmodelled — an ERROR, on every service in the fleet\n * — and a binding written one level down was never checked at all. The tree\n * is walked instead, the way `serviceResolver` walks it for edges: a binding\n * wins at any depth, then a title naming a real services/<id>/, and what sits\n * INSIDE one of those is that service's container, not a service of its own.\n */\nexport function serviceLevelElements(elements: Elem[], services: ReadonlySet<string>): Elem[] {\n const byId = new Map(elements.map((e) => [e.id, e]));\n /**\n * The ancestors this document actually DECLARES, nearest first. The chain\n * itself is `core/kernel/ids/fqn/ancestors.ts`'s — the one spelling of it —\n * and the filter is this function's own: an id may name a level nothing\n * declares, and an undeclared level answers for no service.\n */\n const ancestorsOf = (e: Elem): Elem[] =>\n properAncestorIds(e.id).flatMap((id) => {\n const parent = byId.get(id);\n return parent === undefined ? [] : [parent];\n });\n return elements.filter((e) => !ancestorsOf(e).some((p) => standsForService(p, services)));\n}\n\n/**\n * The drawn SYSTEMS: service-level elements minus everything the map itself\n * says is not a service. Actor kinds model people; INFRA kinds model the\n * datastores, queues and buckets the fleet RUNS; `#external` is somebody\n * else's system; and an element that CONTAINS a service is a grouping — a\n * domain, a boundary, an enterprise — not a service nobody adopted. There is\n * nothing to bind on a grouping and nothing to tag #external, so asking for\n * either would be a warning with no correct fix; the services under it answer\n * for themselves. `validateLandscape`'s undocumented pass walks this set, and\n * the scorecard's `c4.elements` census counts it — one predicate, two readers,\n * zero drift.\n *\n * The INFRA skip is the newest and the one with a measured cost behind it. A\n * `database` the fleet OWNS and draws at fleet level is neither a person nor\n * somebody else's system, so it used to earn `landscape.service-undocumented`\n * — \"bind it with `metadata { service '<id>' }`, or tag it `#external`\" —\n * whose two remedies are both FALSE of it: there is no directory to bind, and\n * it is ours. Measured on a one-service fixture drawing an owned `database` at\n * fleet level (2026-09-07): that warning plus `landscape.datastore-private`\n * plus `gate.partner-undocumented` on its one consumer, three findings and no\n * correct fix, which is why both `examples/docs` and this repo's own suite\n * tagged an owned Redis `#external` to buy silence.\n *\n * `standsForService` wins over the kind, and that guard is load-bearing rather\n * than defensive: a `database` bound with `metadata { service '<id>' }`, or one\n * whose title names a real `services/<id>/`, IS an adopted service — dropping\n * it here would take it out of the scorecard's `c4.elements` denominator and\n * out of the binding passes at once. The kind is the fallback for an element\n * nothing else answers for, never an override.\n *\n * The `#external` skip here is exactly what `./kind-tags.ts` guards: read off a\n * tag a KIND declared, it can empty this census for a whole fleet at once. The\n * INFRA skip is NOT guarded there and does not need to be — a kind is what an\n * element already is, not a claim declared over the fleet, and `#infrastructure`\n * is deliberately not a tag loam grades on (`GRADED_TAGS`).\n */\nexport function drawnSystems(elements: Elem[], services: ReadonlySet<string>): Elem[] {\n return serviceLevelElements(elements, services).filter(\n (e) =>\n !e.tags.includes(EXTERNAL_TAG) &&\n !ACTOR_KINDS.has(e.kind.toLowerCase()) &&\n !(INFRA_KINDS.has(e.kind.toLowerCase()) && !standsForService(e, services)) &&\n !elements.some((c) => c.id.startsWith(`${e.id}.`) && standsForService(c, services)),\n );\n}\n"]}
@@ -9,12 +9,10 @@
9
9
  * `unreadableLandscape` in `./load.ts`.
10
10
  */
11
11
  import { existsSync } from "node:fs";
12
- import { relative } from "node:path";
13
12
  import { readFile } from "node:fs/promises";
14
- import { loadFile } from "../../../core/c4/likec4.js";
13
+ import {} from "../../../core/c4/likec4.js";
15
14
  import { serviceResolver } from "../../../core/c4/resolve/service.js";
16
15
  import { landscapePath as landscapeFile, servicePathsAt } from "../../../core/repo/paths.js";
17
- import { readModelShapes } from "../../../core/c4/service-model/shape.js";
18
16
  import { serviceIdFindings } from "../../../core/repo/entries.js";
19
17
  import { listFleetTree, listServices } from "../../../core/repo/repo.js";
20
18
  import {} from "../../../core/vocabulary/report.js";
@@ -27,7 +25,7 @@ import { obligationFindings, obligationVocabularyFindings } from "./obligations.
27
25
  import { readObligations } from "../../../core/obligations/obligations.js";
28
26
  import { obligationsPath } from "../../../core/repo/paths.js";
29
27
  import { EXTERNAL_TAG } from "../../../core/vocabulary/maturity.js";
30
- import { errorText } from "../../../core/c4/likec4.js";
28
+ import { brokenDocumentPaths, errorText } from "../../../core/c4/likec4.js";
31
29
  import { serviceTreePath } from "../../../core/kernel/ids/dirs.js";
32
30
  import { drawnSystems, serviceLevelElements, standsForService } from "./census.js";
33
31
  import { unreadableLandscape } from "./load.js";
@@ -49,8 +47,8 @@ import { isUseCase } from "../../../core/usecases/fleet.js";
49
47
  *
50
48
  * `capability.requirement-unrealized` says a business promise is kept by
51
49
  * nobody. A use case is one of the two things that can keep it, so a run that
52
- * could not read the flows must not make that claim: no preload (a single-target
53
- * run) and a project that did not parse are both "loam did not look", never
50
+ * could not read the flows must not make that claim: no map at all (`null`)
51
+ * and a project that did not parse are both "loam did not look", never
54
52
  * "there is nothing there". A preload that DID parse and declares no views is a
55
53
  * real, empty answer and grades normally.
56
54
  *
@@ -62,7 +60,7 @@ import { isUseCase } from "../../../core/usecases/fleet.js";
62
60
  * is exactly that input.
63
61
  */
64
62
  function gradableFlows(preloaded) {
65
- if (preloaded === null || preloaded === undefined || preloaded.errors.length > 0)
63
+ if (preloaded === null || preloaded.errors.length > 0)
66
64
  return null;
67
65
  return (preloaded.views ?? []).filter(isUseCase);
68
66
  }
@@ -121,7 +119,7 @@ export async function validateLandscape(docsDir, preloaded, fleet) {
121
119
  // is what `entries` is here even under `--base` — `validate --all` narrows the
122
120
  // TARGETS it grades and never the fleet the renderer would load.
123
121
  const models = entries.map((entry) => servicePathsAt(entry.dir).model);
124
- const shapes = fleet === undefined ? await readModelShapes(models) : await fleet.modelShapes(models);
122
+ const shapes = await fleet.modelShapes(models);
125
123
  // A model and the renderer's project files disagreeing is a fact about
126
124
  // `services/` too, graded here for `service.id-invalid`'s reason: it holds
127
125
  // whether or not the map parses. One warning per service per disagreement;
@@ -188,7 +186,7 @@ export async function validateLandscape(docsDir, preloaded, fleet) {
188
186
  findings.push(conflict);
189
187
  return report;
190
188
  }
191
- land = preloaded ?? (fleet === undefined ? await loadFile(path) : await fleet.loadLikeC4(path));
189
+ land = preloaded ?? (await fleet.loadLikeC4(path));
192
190
  }
193
191
  catch (err) {
194
192
  land = unreadableLandscape(err);
@@ -203,9 +201,8 @@ export async function validateLandscape(docsDir, preloaded, fleet) {
203
201
  // landscape by default — a use case with a typo'd element used to be
204
202
  // reported as the landscape having errors, which sent the author to the
205
203
  // wrong file with a line number that pointed at somebody else's text.
206
- const broken = [...new Set(land.errors.map((e) => e.sourceFsPath).filter((p) => p !== undefined))];
207
- const spell = (abs) => relative(docsDir, abs).split(/[\\/]/).join("/");
208
- const named = broken.length === 0 ? "architecture/landscape.likec4" : broken.map(spell).sort().join(", ");
204
+ const broken = brokenDocumentPaths(docsDir, land.errors);
205
+ const named = broken.length === 0 ? "architecture/landscape.likec4" : broken.join(", ");
209
206
  // The verb agrees with the LIST, not with the count after it: `named` is a
210
207
  // comma-joined series once two documents broke, and "a, b has 6 error(s)"
211
208
  // is a sentence a reader trips over. The service arm (`service/spine.ts`)
@@ -229,30 +226,35 @@ export async function validateLandscape(docsDir, preloaded, fleet) {
229
226
  "name the SERVICE on the map, and the container in that service's own model (a container-level " +
230
227
  "flow lives beside the model that declares it)",
231
228
  // Every line carries its own file, because one project's errors can come
232
- // from more than one document and a bare `L8:` is unactionable then.
233
- details: land.errors.map((e) => e.sourceFsPath === undefined || broken.length < 2 ? errorText(e) : `${spell(e.sourceFsPath)} ${errorText(e)}`),
229
+ // from more than one document and a bare `L8:` is unactionable then. The
230
+ // prefix is `brokenDocumentPaths`'s spelling of that one error's document,
231
+ // so the message's file list and each line's prefix are one rule.
232
+ details: land.errors.map((e) => {
233
+ const [file] = broken.length < 2 ? [] : brokenDocumentPaths(docsDir, [e]);
234
+ return file === undefined ? errorText(e) : `${file} ${errorText(e)}`;
235
+ }),
234
236
  });
235
237
  return report;
236
238
  }
237
- // The generated subsystem views, graded only now that the landscape has
238
- // parsed: the expected bytes are a function of (tree, landscape elements,
239
- // the global style ids the map declares) — the whole doc goes across so the
240
- // grader and `sync` read one record; `views/stale.ts` says why a byte
241
- // compare and why exactly one finding.
239
+ // The generated views files, graded only now that the landscape has parsed:
240
+ // the expected bytes are a function of (tree, landscape elements, the global
241
+ // style ids the map declares) — the whole doc goes across so the grader and
242
+ // `sync` read one record. There are TWO generated files now
243
+ // (`subsystems.likec4` and `services.likec4`) and this pushes ONE error PER
244
+ // STALE FILE, each naming its own file; `views/stale.ts` says why a content
245
+ // compare, and why two files share one code but never one finding.
242
246
  findings.push(...(await viewsStaleFindings(docsDir, tree, land)));
243
247
  // The renderer's own reading of the tree: the map, every extending model and
244
248
  // every `.likec4` beside one, merged into ONE project. It answers two
245
249
  // questions at once and only one of them can be true at a time — either that
246
250
  // project does not parse (`c4.fleet-project-invalid`), or it parsed and its
247
251
  // view-id census is the real one.
248
- const merged = fleet === undefined
249
- ? { kind: "skipped" }
250
- : await fleetProjectFindings({
251
- docsDir, entries, shapes, fleet, tree,
252
- architecture: land,
253
- mapExcluded: projects.mapExcluded,
254
- known: new Set(entries.map((s) => s.id)),
255
- });
252
+ const merged = await fleetProjectFindings({
253
+ docsDir, entries, shapes, fleet, tree,
254
+ architecture: land,
255
+ mapExcluded: projects.mapExcluded,
256
+ known: new Set(entries.map((s) => s.id)),
257
+ });
256
258
  if (merged.kind === "invalid")
257
259
  findings.push(...merged.findings);
258
260
  // Beside staleness, and for the same file: an authored view id that collides
@@ -261,9 +263,12 @@ export async function validateLandscape(docsDir, preloaded, fleet) {
261
263
  // is the merged project's when there is one; otherwise the `architecture/`
262
264
  // project's alone, whose claims are spelled relative to that directory and are
263
265
  // made docs-relative here.
264
- findings.push(...viewIdFindings(merged.kind === "clean"
266
+ // `land` goes across for the SERVICE half of the reservation: an id is minted
267
+ // per service the map DRAWS, so the census needs the render's own element join.
268
+ const claims = merged.kind === "clean"
265
269
  ? merged.viewIds
266
- : land.viewIds?.map((claim) => ({ ...claim, sourcePath: `architecture/${claim.sourcePath}` })), tree));
270
+ : land.viewIds?.map((claim) => ({ ...claim, sourcePath: `architecture/${claim.sourcePath}` }));
271
+ findings.push(...viewIdFindings(claims, tree, land));
267
272
  // The three obligation questions that need the map — where each declared rule
268
273
  // is applied, which tags resolve to nothing, and which applications no living
269
274
  // arch requirement covers. Here rather than beside the vocabulary above
@@ -316,7 +321,7 @@ export async function validateLandscape(docsDir, preloaded, fleet) {
316
321
  resolve: landSvcOf,
317
322
  standsForService: isService,
318
323
  drawnIds,
319
- ...(fleet === undefined ? {} : { fleet }),
324
+ fleet,
320
325
  });
321
326
  const attested = attestation.models;
322
327
  const consumers = consumerCensus({ relationships: land.relationships, services, resolve: landSvcOf, attested });
@@ -332,9 +337,7 @@ export async function validateLandscape(docsDir, preloaded, fleet) {
332
337
  services,
333
338
  resolve: landSvcOf,
334
339
  standsForService: isService,
335
- attested,
336
340
  consumers,
337
- pathOf,
338
341
  }));
339
342
  // A drawn service nothing reaches, graded only where its own model attests
340
343
  // a call across its boundary — map/isolation.ts says why the evidence gate.