@ybotok/loam 0.2.0-alpha.5 → 0.2.0-alpha.7

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 (303) hide show
  1. package/CHANGELOG.md +1063 -0
  2. package/MIGRATING-from-OpenSpec.md +7 -4
  3. package/QUICKSTART.md +8 -3
  4. package/README.md +13 -7
  5. package/ROADMAP.md +285 -11
  6. package/SCHEMA.md +566 -45
  7. package/WORKFLOW.md +23 -10
  8. package/dist/commands/adopt/render.js +47 -8
  9. package/dist/commands/adopt/render.js.map +1 -1
  10. package/dist/commands/archive/plan/landscape.js +45 -1
  11. package/dist/commands/archive/plan/landscape.js.map +1 -1
  12. package/dist/commands/archive/plan/model/extending.js +85 -0
  13. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  14. package/dist/commands/archive/run.js +53 -23
  15. package/dist/commands/archive/run.js.map +1 -1
  16. package/dist/commands/context/print.js +11 -1
  17. package/dist/commands/context/print.js.map +1 -1
  18. package/dist/commands/doctor.js +46 -0
  19. package/dist/commands/doctor.js.map +1 -1
  20. package/dist/commands/explore.js +12 -2
  21. package/dist/commands/explore.js.map +1 -1
  22. package/dist/commands/list/views.js +5 -1
  23. package/dist/commands/list/views.js.map +1 -1
  24. package/dist/commands/seed/commit.js +34 -1
  25. package/dist/commands/seed/commit.js.map +1 -1
  26. package/dist/commands/seed/plan.js +158 -13
  27. package/dist/commands/seed/plan.js.map +1 -1
  28. package/dist/commands/seed/seed.js +7 -2
  29. package/dist/commands/seed/seed.js.map +1 -1
  30. package/dist/commands/show/feature.js +2 -1
  31. package/dist/commands/show/feature.js.map +1 -1
  32. package/dist/commands/show/marks.js +3 -5
  33. package/dist/commands/show/marks.js.map +1 -1
  34. package/dist/commands/show/service.js +39 -18
  35. package/dist/commands/show/service.js.map +1 -1
  36. package/dist/commands/subsystem/edit.js +4 -2
  37. package/dist/commands/subsystem/edit.js.map +1 -1
  38. package/dist/commands/subsystem/move.js +55 -17
  39. package/dist/commands/subsystem/move.js.map +1 -1
  40. package/dist/commands/subsystem/sync.js +238 -87
  41. package/dist/commands/subsystem/sync.js.map +1 -1
  42. package/dist/commands/subsystem/txn/exclude.js +132 -0
  43. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  44. package/dist/commands/subsystem/txn/report.js +174 -0
  45. package/dist/commands/subsystem/txn/report.js.map +1 -0
  46. package/dist/commands/subsystem/txn/txn.js +126 -22
  47. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  48. package/dist/commands/subsystem/txn/views.js +159 -19
  49. package/dist/commands/subsystem/txn/views.js.map +1 -1
  50. package/dist/commands/validate/arch-coverage.js +16 -9
  51. package/dist/commands/validate/arch-coverage.js.map +1 -1
  52. package/dist/commands/validate/checks/fleet-shape.js +30 -77
  53. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  54. package/dist/commands/validate/checks/health-deps.js +9 -6
  55. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  56. package/dist/commands/validate/checks/vocabulary.js +4 -19
  57. package/dist/commands/validate/checks/vocabulary.js.map +1 -1
  58. package/dist/commands/validate/feature.js +1 -1
  59. package/dist/commands/validate/feature.js.map +1 -1
  60. package/dist/commands/validate/fleet/census.js +37 -13
  61. package/dist/commands/validate/fleet/census.js.map +1 -1
  62. package/dist/commands/validate/fleet/landscape.js +131 -120
  63. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  64. package/dist/commands/validate/fleet/load.js +66 -0
  65. package/dist/commands/validate/fleet/load.js.map +1 -1
  66. package/dist/commands/validate/fleet/map/attest.js +102 -0
  67. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  68. package/dist/commands/validate/fleet/map/bindings.js +109 -0
  69. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  70. package/dist/commands/validate/fleet/map/consumers.js +219 -0
  71. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  72. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  73. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  74. package/dist/commands/validate/fleet/map/usecases.js +56 -0
  75. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  76. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  77. package/dist/commands/validate/fleet/usecases/steps.js +49 -17
  78. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
  79. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  80. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  81. package/dist/commands/validate/fleet/views/ids.js +45 -15
  82. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  83. package/dist/commands/validate/fleet/views/projects.js +258 -70
  84. package/dist/commands/validate/fleet/views/projects.js.map +1 -1
  85. package/dist/commands/validate/fleet/views/root-documents.js +99 -0
  86. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  87. package/dist/commands/validate/fleet/views/stale.js +32 -15
  88. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  89. package/dist/commands/validate/service/model/diverged.js +226 -0
  90. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  91. package/dist/commands/validate/service/model/grade.js +163 -0
  92. package/dist/commands/validate/service/model/grade.js.map +1 -0
  93. package/dist/commands/validate/service/service.js +110 -34
  94. package/dist/commands/validate/service/service.js.map +1 -1
  95. package/dist/commands/validate/service/specs.js +39 -5
  96. package/dist/commands/validate/service/specs.js.map +1 -1
  97. package/dist/commands/validate/service/spine.js +41 -3
  98. package/dist/commands/validate/service/spine.js.map +1 -1
  99. package/dist/commands/validate/service/usecases/tags.js +113 -0
  100. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  101. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  102. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  103. package/dist/commands/validate/validate.js +10 -24
  104. package/dist/commands/validate/validate.js.map +1 -1
  105. package/dist/core/agent/agents-md/artifacts.js +1 -0
  106. package/dist/core/agent/agents-md/artifacts.js.map +1 -1
  107. package/dist/core/agent/agents-md/command-map.js +19 -5
  108. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  109. package/dist/core/agent/agents-md/cycle.js +24 -9
  110. package/dist/core/agent/agents-md/cycle.js.map +1 -1
  111. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  112. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  113. package/dist/core/agent/agents-md/map/subsystem.js +70 -17
  114. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  115. package/dist/core/agent/agents-md/map/usecases.js +45 -3
  116. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  117. package/dist/core/agent/agents-md/refusals.js +5 -3
  118. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  119. package/dist/core/agent/agents-md.js +26 -7
  120. package/dist/core/agent/agents-md.js.map +1 -1
  121. package/dist/core/agent/support/report.js +39 -5
  122. package/dist/core/agent/support/report.js.map +1 -1
  123. package/dist/core/agent/workflows/adopt.js +123 -14
  124. package/dist/core/agent/workflows/adopt.js.map +1 -1
  125. package/dist/core/agent/workflows/check.js +34 -20
  126. package/dist/core/agent/workflows/check.js.map +1 -1
  127. package/dist/core/agent/workflows/closing.js +3 -1
  128. package/dist/core/agent/workflows/closing.js.map +1 -1
  129. package/dist/core/agent/workflows/reference/spine.js +12 -6
  130. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  131. package/dist/core/brief/brief.js +88 -19
  132. package/dist/core/brief/brief.js.map +1 -1
  133. package/dist/core/brief/checks.js +133 -7
  134. package/dist/core/brief/checks.js.map +1 -1
  135. package/dist/core/brief/landscape.js +212 -129
  136. package/dist/core/brief/landscape.js.map +1 -1
  137. package/dist/core/brief/map/extend.js +81 -0
  138. package/dist/core/brief/map/extend.js.map +1 -0
  139. package/dist/core/brief/map/owed.js +284 -0
  140. package/dist/core/brief/map/owed.js.map +1 -0
  141. package/dist/core/brief/targets.js +94 -29
  142. package/dist/core/brief/targets.js.map +1 -1
  143. package/dist/core/brief/unchecked.js +13 -3
  144. package/dist/core/brief/unchecked.js.map +1 -1
  145. package/dist/core/c4/likec4.js +23 -1
  146. package/dist/core/c4/likec4.js.map +1 -1
  147. package/dist/core/c4/parsed/deployment.js +5 -3
  148. package/dist/core/c4/parsed/deployment.js.map +1 -1
  149. package/dist/core/c4/parsed/view-ids.js +39 -1
  150. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  151. package/dist/core/c4/prefetch/fleet.js +142 -0
  152. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  153. package/dist/core/c4/project/architecture.js +77 -7
  154. package/dist/core/c4/project/architecture.js.map +1 -1
  155. package/dist/core/c4/project/batch.js +140 -0
  156. package/dist/core/c4/project/batch.js.map +1 -0
  157. package/dist/core/c4/project/documents.js +12 -6
  158. package/dist/core/c4/project/documents.js.map +1 -1
  159. package/dist/core/c4/project/load.js +85 -42
  160. package/dist/core/c4/project/load.js.map +1 -1
  161. package/dist/core/c4/project/staged.js +53 -15
  162. package/dist/core/c4/project/staged.js.map +1 -1
  163. package/dist/core/c4/resolve/attested.js +0 -0
  164. package/dist/core/c4/resolve/attested.js.map +1 -0
  165. package/dist/core/c4/resolve/service.js +8 -17
  166. package/dist/core/c4/resolve/service.js.map +1 -1
  167. package/dist/core/c4/resolve/steps.js +18 -0
  168. package/dist/core/c4/resolve/steps.js.map +1 -1
  169. package/dist/core/c4/root-project/exclude.js +352 -0
  170. package/dist/core/c4/root-project/exclude.js.map +1 -0
  171. package/dist/core/c4/service-model/documents.js +47 -0
  172. package/dist/core/c4/service-model/documents.js.map +1 -0
  173. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  174. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  175. package/dist/core/c4/service-model/load.js +165 -0
  176. package/dist/core/c4/service-model/load.js.map +1 -0
  177. package/dist/core/c4/service-model/renderer.js +128 -0
  178. package/dist/core/c4/service-model/renderer.js.map +1 -0
  179. package/dist/core/c4/service-model/shape.js +126 -0
  180. package/dist/core/c4/service-model/shape.js.map +1 -0
  181. package/dist/core/c4/service-model/slice.js +188 -0
  182. package/dist/core/c4/service-model/slice.js.map +1 -0
  183. package/dist/core/c4/source-scan.js +26 -0
  184. package/dist/core/c4/source-scan.js.map +1 -1
  185. package/dist/core/c4/splice/authored-source.js +60 -13
  186. package/dist/core/c4/splice/authored-source.js.map +1 -1
  187. package/dist/core/c4/splice/contract.js.map +1 -1
  188. package/dist/core/c4/splice/identity/declared.js +87 -0
  189. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  190. package/dist/core/c4/splice/identity/edges.js +17 -0
  191. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  192. package/dist/core/c4/splice/identity/existing.js +80 -0
  193. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  194. package/dist/core/c4/splice/identity/interior.js +80 -0
  195. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  196. package/dist/core/c4/splice/identity/owner.js +50 -0
  197. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  198. package/dist/core/c4/splice/landscape-merge.js +154 -100
  199. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  200. package/dist/core/c4/splice/model/merge.js +140 -0
  201. package/dist/core/c4/splice/model/merge.js.map +1 -0
  202. package/dist/core/c4/splice/placement.js +14 -4
  203. package/dist/core/c4/splice/placement.js.map +1 -1
  204. package/dist/core/c4/workspace.js +2 -1
  205. package/dist/core/c4/workspace.js.map +1 -1
  206. package/dist/core/coherence/coherence.js +7 -0
  207. package/dist/core/coherence/coherence.js.map +1 -1
  208. package/dist/core/delta/delta.js +9 -0
  209. package/dist/core/delta/delta.js.map +1 -1
  210. package/dist/core/deployment/delta.js +16 -10
  211. package/dist/core/deployment/delta.js.map +1 -1
  212. package/dist/core/diff/base-state.js +21 -5
  213. package/dist/core/diff/base-state.js.map +1 -1
  214. package/dist/core/diff/victims.js +10 -1
  215. package/dist/core/diff/victims.js.map +1 -1
  216. package/dist/core/docs.js +29 -24
  217. package/dist/core/docs.js.map +1 -1
  218. package/dist/core/doctor/config.js +7 -3
  219. package/dist/core/doctor/config.js.map +1 -1
  220. package/dist/core/doctor/doctor.js +36 -8
  221. package/dist/core/doctor/doctor.js.map +1 -1
  222. package/dist/core/doctor/report.js.map +1 -1
  223. package/dist/core/doctor/reports/scan.js +242 -0
  224. package/dist/core/doctor/reports/scan.js.map +1 -0
  225. package/dist/core/explain/refusals.js +1 -1
  226. package/dist/core/explain/refusals.js.map +1 -1
  227. package/dist/core/explore/explore.js +44 -9
  228. package/dist/core/explore/explore.js.map +1 -1
  229. package/dist/core/fleet-context.js +47 -0
  230. package/dist/core/fleet-context.js.map +1 -1
  231. package/dist/core/gate/checks.js +5 -1
  232. package/dist/core/gate/checks.js.map +1 -1
  233. package/dist/core/gate/partners.js +65 -26
  234. package/dist/core/gate/partners.js.map +1 -1
  235. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  236. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  237. package/dist/core/pack/living.js +47 -13
  238. package/dist/core/pack/living.js.map +1 -1
  239. package/dist/core/pack/pack.js +9 -6
  240. package/dist/core/pack/pack.js.map +1 -1
  241. package/dist/core/projection/arch-slice.js +2 -2
  242. package/dist/core/projection/arch-slice.js.map +1 -1
  243. package/dist/core/repo/paths.js +45 -38
  244. package/dist/core/repo/paths.js.map +1 -1
  245. package/dist/core/repo/tree/render/body.js +45 -6
  246. package/dist/core/repo/tree/render/body.js.map +1 -1
  247. package/dist/core/repo/tree/render/projects.js +93 -35
  248. package/dist/core/repo/tree/render/projects.js.map +1 -1
  249. package/dist/core/repo/tree/render/service/header.js +125 -0
  250. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  251. package/dist/core/repo/tree/render/service/peers.js +28 -0
  252. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  253. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  254. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  255. package/dist/core/repo/tree/render/service/views.js +78 -0
  256. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  257. package/dist/core/repo/tree/render/stale.js +20 -4
  258. package/dist/core/repo/tree/render/stale.js.map +1 -1
  259. package/dist/core/repo/tree/render/views.js +149 -32
  260. package/dist/core/repo/tree/render/views.js.map +1 -1
  261. package/dist/core/repo/tree/walk.js +45 -2
  262. package/dist/core/repo/tree/walk.js.map +1 -1
  263. package/dist/core/scaffold/landscape.js +219 -9
  264. package/dist/core/scaffold/landscape.js.map +1 -1
  265. package/dist/core/staging/writes.js.map +1 -1
  266. package/dist/core/usecases/delta/claims.js +1 -0
  267. package/dist/core/usecases/delta/claims.js.map +1 -1
  268. package/dist/core/usecases/delta/overlay.js +4 -0
  269. package/dist/core/usecases/delta/overlay.js.map +1 -1
  270. package/dist/core/usecases/fleet.js +18 -6
  271. package/dist/core/usecases/fleet.js.map +1 -1
  272. package/dist/core/usecases/operations.js +7 -0
  273. package/dist/core/usecases/operations.js.map +1 -1
  274. package/dist/core/usecases/service/exclude.js +119 -0
  275. package/dist/core/usecases/service/exclude.js.map +1 -0
  276. package/dist/core/usecases/service/flows.js +161 -0
  277. package/dist/core/usecases/service/flows.js.map +1 -0
  278. package/dist/core/verify/checklist.js +42 -10
  279. package/dist/core/verify/checklist.js.map +1 -1
  280. package/dist/core/vocabulary/issue.js.map +1 -1
  281. package/dist/core/vocabulary/maturity.js +51 -1
  282. package/dist/core/vocabulary/maturity.js.map +1 -1
  283. package/examples/README.md +30 -8
  284. package/examples/docs/architecture/landscape.likec4 +47 -0
  285. package/examples/docs/architecture/services.likec4 +70 -0
  286. package/examples/docs/architecture/subsystems.likec4 +3 -0
  287. package/examples/docs/architecture/usecases/order-notification.likec4 +5 -2
  288. package/examples/docs/likec4.config.json +0 -1
  289. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  290. package/examples/docs/services/order-service/arch.spec.md +13 -6
  291. package/examples/docs/services/order-service/model.likec4 +58 -70
  292. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  293. package/examples/docs/services/payment-service/model.likec4 +38 -39
  294. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  295. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  296. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  297. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  298. package/package.json +1 -1
  299. package/examples/docs/services/checkout-web/likec4.config.json +0 -4
  300. package/examples/docs/services/order-service/likec4.config.json +0 -4
  301. package/examples/docs/services/payment-service/likec4.config.json +0 -4
  302. package/examples/docs/services/platform/identity-service/likec4.config.json +0 -4
  303. package/examples/docs/services/platform/notification-service/likec4.config.json +0 -4
@@ -1 +1 @@
1
- {"version":3,"file":"checklist.js","sourceRoot":"","sources":["../../../src/core/verify/checklist.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The questions: what a feature's own artifacts promised, derived mechanically.\n *\n * The original plan was to extract C4 from the built code and diff it against\n * the delta. There is no such extractor and there will not be one — nothing\n * deterministic reads a service and says what its architecture MEANS, and two\n * generated models of the same code disagree in wording every run, so the diff\n * would flap and be switched off inside a week.\n *\n * What survives of the idea is the part that was always deterministic, and it\n * is this module: a feature says this service will exist, it will expose this\n * operation, that service will call it, this scenario will have a test. The\n * list comes from the same files `validate` already reads.\n *\n * The claim ids are a function of the claim and of nothing else — so two runs\n * are diffable, reordering the delta renames nothing, and rewording a scenario\n * DOES rename its claim, because an answer about text nobody wrote must not\n * carry over.\n *\n * `scenarioBodyHash` lives in `core/gherkin/digest.ts` — one recipe shared\n * with `core/gherkin/stamp.ts`, which stamps the same digest as a tag on every\n * generated scenario so `--results` can match a cucumber report back to a\n * claim. A second spelling of the hash would silently stop every scenario\n * claim from being answerable by a run.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { readAsyncapi } from \"../asyncapi/read.js\";\nimport { type Elem } from \"../c4/likec4.js\";\nimport { elementService, serviceResolver } from \"../c4/resolve/service.js\";\nimport { ACTOR_KINDS } from \"../vocabulary/maturity.js\";\nimport { operationIds, operations } from \"../openapi/doc.js\";\nimport { featurePaths, featureSpecPaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport { featureSpecServices } from \"../repo/repo.js\";\nimport { enumeratedServiceIds } from \"../repo/service-target.js\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport { scenarioBodyHash } from \"../gherkin/digest.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\n// The kind vocabulary and both hash recipes live in ./claims/identity.ts —\n// one module for everything that decides whether two runs ask the same\n// question, and this one for how the questions are derived.\nimport {\n checklistDigest,\n claimId,\n DIGEST_LENGTH,\n ID_LENGTH,\n type ClaimKind,\n} from \"./claims/identity.js\";\n\nexport interface Claim {\n /** `<kind>-<8 hex>` — stable for the life of the claim. See `claimId`. */\n id: string;\n kind: ClaimKind;\n /** The service whose code answers this. A caller's claim is filed under the caller. */\n subject: string;\n /** The claim in one line, answerable without reading the feature's files. */\n claim: string;\n /**\n * `scenario.tested` only: the first {@link DIGEST_LENGTH} hex of the\n * scenario's body hash — the exact digest `loam gherkin` stamps as\n * `@loam-digest-…`, which is what a cucumber report scenario carries and\n * what `--results` matches on. Absent on every other kind.\n */\n digest?: string;\n /**\n * `api.exposes` only: the operationId the claim asserts, structurally — the\n * join key `--contract-results` matches a contract report's entries on,\n * exactly as `digest` is `--results`'s. Never parsed back out of the claim\n * text, which is prose and may be reworded. Absent on every other kind.\n */\n operation?: string;\n /**\n * The literal string the claim asserts of the cited artifact — the\n * operationId for `api.exposes`, the message name for `event.declares`, the\n * edge's op for `c4.calls` — carried structurally so evidence pins\n * (`./pins/pin.ts`) can stamp it at record time and the read side never\n * parses it back out of claim prose. A separate field from `operation` on\n * purpose: `contestedOperations` filters on `operation !== undefined` and\n * must not start seeing `c4.calls` claims. Absent where a claim asserts no\n * literal (`service.exists`, `scenario.tested`).\n */\n token?: string;\n}\n\nexport interface Checklist {\n feature: string;\n claims: Claim[];\n /** A digest of the claim id SET — what says a record still answers this feature. */\n digest: string;\n}\n\n/**\n * Derive the checklist from a feature's own artifacts. No code is read here and\n * none should be: the whole design rests on the questions being computed from\n * documents that do not change between runs.\n */\nexport async function featureChecklist(\n docsDir: DocsDir,\n featureDir: FeatureDir,\n featureId: string,\n fleet?: FleetContext,\n): Promise<Checklist> {\n // The per-service loop below resolves living contracts through the\n // enumeration twice per service; without a shared context each of those is\n // a full fleet walk, so deriving one checklist cost O(services²) reads. A\n // caller-supplied context is used when given; otherwise one is created for\n // this derivation alone — the loop still pays a single walk either way.\n const ctx = fleet ?? new FleetContext();\n const seen = new Map<string, number>();\n const claim = (kind: ClaimKind, subject: string, parts: string[], text: string): Claim => ({\n id: claimId(featureId, kind, parts, seen),\n kind,\n subject,\n claim: text,\n });\n\n const exists: Claim[] = [];\n const exposes: Claim[] = [];\n const declares: Claim[] = [];\n const calls: Claim[] = [];\n const scenarios: Claim[] = [];\n\n const specServices = await featureSpecServices(featureDir, ctx);\n\n // Architecture — what the delta promised the fleet would look like. Loaded\n // through the context rather than a bare parse, so a caller that derives\n // several checklists in one invocation (`loam gate`'s verification check,\n // the fleet forms) shares one parse per delta — and a batch prefetch\n // (`FleetContext.prefetchLikeC4`) genuinely seeds this load.\n const deltaPath = featurePaths(featureDir).delta;\n if (existsSync(deltaPath)) {\n const res = await ctx.loadLikeC4(deltaPath);\n // A delta nobody can parse promises nothing checkable. `validate` already\n // reports it; inventing claims out of a broken document would only add noise.\n if (res.errors.length === 0) {\n const elements: Elem[] = res.elements;\n for (const e of elements) {\n if (!e.tags.includes(featureId)) continue;\n // A dotted id is a container INSIDE a service, and an actor is nobody's\n // repository. Neither is a service that has to exist.\n if (e.id.includes(\".\") || ACTOR_KINDS.has(e.kind.toLowerCase())) continue;\n const svc = elementService(e);\n exists.push(claim(\"service.exists\", svc, [svc], `service '${svc}' exists`));\n }\n // The enumerated fleet — `services/` plus this feature's own `specs/`\n // directories, the only place a service the feature INTRODUCES has a\n // directory at all — rides into the resolver so a claim about an edge\n // drawn into a modelled container names the service that owns it. A\n // claim against a service called \"api\" that has never existed is one no\n // evidence in any repository could ever answer, and the fleet half alone\n // still derived exactly that claim for an introduced service's own\n // container. Unenumerable services/ degrades to the feature's names.\n const svcOf = serviceResolver(\n elements,\n new Set<string>([...(await enumeratedServiceIds(docsDir, ctx)), ...specServices]),\n );\n for (const r of res.relationships) {\n // Untagged edges are context for the diagram; an edge with no operation\n // names nothing specific enough to look for in the code.\n if (!r.tags.includes(featureId) || r.op === undefined) continue;\n const from = svcOf(r.source);\n const to = svcOf(r.target);\n calls.push({\n ...claim(\"c4.calls\", from, [from, to, r.op], `${from} calls '${r.op}' on ${to}`),\n // The same string as the provider's api.exposes token, scanned in the\n // CALLER's cited file — a call site plausibly spells the operationId.\n token: r.op,\n });\n }\n }\n }\n\n for (const svc of specServices) {\n const paths = featureSpecPaths(featureDir, svc);\n\n // Contract — only what is NEW. A delta's openapi.yaml is a whole document,\n // not a patch, so authors restate the living API inside it; asking whether a\n // service still exposes what it already exposed is noise.\n const featOps = (await operations(paths.openapi))\n .filter((operation) => !operation.remove)\n .map((operation) => operation.id);\n if (featOps.length > 0) {\n const living = new Set(await operationIds((await locateServicePaths(docsDir, svc, ctx)).openapi));\n for (const op of featOps) {\n if (living.has(op)) continue;\n exposes.push({\n ...claim(\"api.exposes\", svc, [svc, op], `${svc} exposes operationId '${op}'`),\n operation: op,\n token: op,\n });\n }\n }\n\n // Events — the same NEW-only discipline on the async axis: a delta's\n // asyncapi.yaml is a complete document too, so what the living contract\n // already sends or receives is restatement, not a promise. Removals are\n // not questions either — the reader already keeps marked declarations out\n // of the sent/received sets — and an unreadable delta promises nothing\n // checkable, exactly like the broken C4 delta above.\n const events = await readAsyncapi(paths.asyncapi);\n if (events.sent.length > 0 || events.received.length > 0) {\n const living = await readAsyncapi((await locateServicePaths(docsDir, svc, ctx)).asyncapi);\n const directions = [\n { direction: \"sends\", feat: events.sent, known: new Set(living.sent) },\n { direction: \"receives\", feat: events.received, known: new Set(living.received) },\n ] as const;\n for (const d of directions) {\n for (const name of d.feat) {\n if (d.known.has(name)) continue;\n declares.push({\n ...claim(\n \"event.declares\",\n svc,\n [svc, d.direction, name],\n `${svc} declares it ${d.direction} message '${name}'`,\n ),\n token: name,\n });\n }\n }\n }\n\n // Behaviour — the scenarios of the requirements this feature CHANGES. A BASE\n // requirement is the living state quoted inside the delta, and a REMOVED one\n // is being retired: neither is work anybody has to have done.\n if (existsSync(paths.spec)) {\n for (const r of parseRequirements(await readFile(paths.spec, \"utf8\"))) {\n if (r.kind === \"BASE\" || r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n const body = scenarioBodyHash(svc, s.lines);\n scenarios.push({\n ...claim(\n \"scenario.tested\",\n svc,\n [svc, r.id ?? r.name, s.name, body.slice(0, ID_LENGTH)],\n `scenario '${s.name}' of requirement '${r.name}' (${svc}) is covered by a test`,\n ),\n digest: body.slice(0, DIGEST_LENGTH),\n });\n }\n }\n }\n\n // The architecture spec axis, same walk: an arch scenario is a promise like\n // any other — the outbox test nobody was going to write IS the point of the\n // axis. The source file rides in the id tuple AND in the body hash itself\n // (two axes are two namespaces: identically-worded scenarios must stay two\n // questions, and the axis-salted digest is what keeps a business-only test\n // run from answering the arch claim) and in the claim text, so the\n // answering agent knows it is being asked for an integration/ops test, not\n // an acceptance test.\n if (existsSync(paths.archSpec)) {\n for (const r of parseRequirements(await readFile(paths.archSpec, \"utf8\"))) {\n if (r.kind === \"BASE\" || r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n const body = scenarioBodyHash(svc, s.lines, \"arch\");\n scenarios.push({\n ...claim(\n \"scenario.tested\",\n svc,\n [svc, \"arch.spec.md\", r.id ?? r.name, s.name, body.slice(0, ID_LENGTH)],\n `scenario '${s.name}' of arch requirement '${r.name}' (${svc}, arch.spec.md) is covered by a test`,\n ),\n digest: body.slice(0, DIGEST_LENGTH),\n });\n }\n }\n }\n }\n\n const claims = [...exists, ...exposes, ...declares, ...calls, ...scenarios];\n return { feature: featureId, claims, digest: checklistDigest(claims.map((c) => c.id)) };\n}\n"]}
1
+ {"version":3,"file":"checklist.js","sourceRoot":"","sources":["../../../src/core/verify/checklist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAa,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,2EAA2E;AAC3E,uEAAuE;AACvE,4DAA4D;AAC5D,OAAO,EACL,eAAe,EACf,OAAO,EACP,aAAa,EACb,SAAS,GAEV,MAAM,sBAAsB,CAAC;AA4C9B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgB,EAChB,UAAsB,EACtB,SAAiB,EACjB,KAAoB;IAEpB,mEAAmE;IACnE,2EAA2E;IAC3E,0EAA0E;IAC1E,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,GAAG,GAAG,KAAK,IAAI,IAAI,YAAY,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,KAAK,GAAG,CAAC,IAAe,EAAE,OAAe,EAAE,KAAe,EAAE,IAAY,EAAS,EAAE,CAAC,CAAC;QACzF,EAAE,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC;QACzC,IAAI;QACJ,OAAO;QACP,KAAK,EAAE,IAAI;KACZ,CAAC,CAAC;IAEH,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAY,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAY,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAY,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAY,EAAE,CAAC;IAE9B,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAEhE,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,qEAAqE;IACrE,6DAA6D;IAC7D,MAAM,SAAS,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC;IACjD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC5C,0EAA0E;QAC1E,8EAA8E;QAC9E,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAW,GAAG,CAAC,QAAQ,CAAC;YACtC,sEAAsE;YACtE,qEAAqE;YACrE,uEAAuE;YACvE,wEAAwE;YACxE,uEAAuE;YACvE,sEAAsE;YACtE,+DAA+D;YAC/D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS;gBAC5B,GAAG,CAAC,MAAM,oBAAoB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBAC7C,GAAG,YAAY;aAChB,CAAC,CAAC;YACH,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;oBAAE,SAAS;gBAC1C,oEAAoE;gBACpE,iEAAiE;gBACjE,oEAAoE;gBACpE,oDAAoD;gBACpD,EAAE;gBACF,mEAAmE;gBACnE,wEAAwE;gBACxE,sEAAsE;gBACtE,uEAAuE;gBACvE,qEAAqE;gBACrE,+CAA+C;gBAC/C,EAAE;gBACF,mEAAmE;gBACnE,qEAAqE;gBACrE,sEAAsE;gBACtE,qEAAqE;gBACrE,sEAAsE;gBACtE,sEAAsE;gBACtE,uEAAuE;gBACvE,wEAAwE;gBACxE,uEAAuE;gBACvE,qEAAqE;gBACrE,qEAAqE;gBACrE,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBAChE,MAAM,KAAK,GAAG,CAAC,SAAS,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;gBAClE,IAAI,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,KAAK;oBAAE,SAAS;gBACnF,MAAM,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;gBAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC;YAC9E,CAAC;YACD,uEAAuE;YACvE,oEAAoE;YACpE,kEAAkE;YAClE,yEAAyE;YACzE,mEAAmE;YACnE,qEAAqE;YACrE,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;gBAClC,wEAAwE;gBACxE,yDAAyD;gBACzD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,SAAS;oBAAE,SAAS;gBAChE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBAC7B,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBAC3B,KAAK,CAAC,IAAI,CAAC;oBACT,GAAG,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC;oBAChF,sEAAsE;oBACtE,sEAAsE;oBACtE,KAAK,EAAE,CAAC,CAAC,EAAE;iBACZ,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,gBAAgB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAEhD,2EAA2E;QAC3E,6EAA6E;QAC7E,0DAA0D;QAC1D,MAAM,OAAO,GAAG,CAAC,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;aAC9C,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC;aACxC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,YAAY,CAAC,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YAClG,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;gBACzB,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC7B,OAAO,CAAC,IAAI,CAAC;oBACX,GAAG,KAAK,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,GAAG,GAAG,yBAAyB,EAAE,GAAG,CAAC;oBAC7E,SAAS,EAAE,EAAE;oBACb,KAAK,EAAE,EAAE;iBACV,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,qEAAqE;QACrE,wEAAwE;QACxE,wEAAwE;QACxE,0EAA0E;QAC1E,uEAAuE;QACvE,qDAAqD;QACrD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC1F,MAAM,UAAU,GAAG;gBACjB,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;gBACtE,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;aACzE,CAAC;YACX,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;gBAC3B,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC1B,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAChC,QAAQ,CAAC,IAAI,CAAC;wBACZ,GAAG,KAAK,CACN,gBAAgB,EAChB,GAAG,EACH,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,EACxB,GAAG,GAAG,gBAAgB,CAAC,CAAC,SAAS,aAAa,IAAI,GAAG,CACtD;wBACD,KAAK,EAAE,IAAI;qBACZ,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,6EAA6E;QAC7E,6EAA6E;QAC7E,8DAA8D;QAC9D,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,KAAK,MAAM,CAAC,IAAI,iBAAiB,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;gBACtE,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;oBAAE,SAAS;gBACxD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;oBAC5B,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;oBAC5C,SAAS,CAAC,IAAI,CAAC;wBACb,GAAG,KAAK,CACN,iBAAiB,EACjB,GAAG,EACH,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,EACvD,aAAa,CAAC,CAAC,IAAI,qBAAqB,CAAC,CAAC,IAAI,MAAM,GAAG,wBAAwB,CAChF;wBACD,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC;qBACrC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,4EAA4E;QAC5E,4EAA4E;QAC5E,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,mEAAmE;QACnE,2EAA2E;QAC3E,sBAAsB;QACtB,IAAI,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,KAAK,MAAM,CAAC,IAAI,iBAAiB,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;gBAC1E,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;oBAAE,SAAS;gBACxD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;oBAC5B,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;oBACpD,SAAS,CAAC,IAAI,CAAC;wBACb,GAAG,KAAK,CACN,iBAAiB,EACjB,GAAG,EACH,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,EACvE,aAAa,CAAC,CAAC,IAAI,0BAA0B,CAAC,CAAC,IAAI,MAAM,GAAG,sCAAsC,CACnG;wBACD,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC;qBACrC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,GAAG,SAAS,CAAC,CAAC;IAC5E,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AAC1F,CAAC","sourcesContent":["/**\n * The questions: what a feature's own artifacts promised, derived mechanically.\n *\n * The original plan was to extract C4 from the built code and diff it against\n * the delta. There is no such extractor and there will not be one — nothing\n * deterministic reads a service and says what its architecture MEANS, and two\n * generated models of the same code disagree in wording every run, so the diff\n * would flap and be switched off inside a week.\n *\n * What survives of the idea is the part that was always deterministic, and it\n * is this module: a feature says this service will exist, it will expose this\n * operation, that service will call it, this scenario will have a test. The\n * list comes from the same files `validate` already reads.\n *\n * The claim ids are a function of the claim and of nothing else — so two runs\n * are diffable, reordering the delta renames nothing, and rewording a scenario\n * DOES rename its claim, because an answer about text nobody wrote must not\n * carry over.\n *\n * `scenarioBodyHash` lives in `core/gherkin/digest.ts` — one recipe shared\n * with `core/gherkin/stamp.ts`, which stamps the same digest as a tag on every\n * generated scenario so `--results` can match a cucumber report back to a\n * claim. A second spelling of the hash would silently stop every scenario\n * claim from being answerable by a run.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { readAsyncapi } from \"../asyncapi/read.js\";\nimport { type Elem } from \"../c4/likec4.js\";\nimport { elementService, serviceResolver } from \"../c4/resolve/service.js\";\nimport { ACTOR_KINDS, INFRA_KINDS } from \"../vocabulary/maturity.js\";\nimport { operationIds, operations } from \"../openapi/doc.js\";\nimport { featurePaths, featureSpecPaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport { featureSpecServices } from \"../repo/repo.js\";\nimport { enumeratedServiceIds } from \"../repo/service-target.js\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport { scenarioBodyHash } from \"../gherkin/digest.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\n// The kind vocabulary and both hash recipes live in ./claims/identity.ts —\n// one module for everything that decides whether two runs ask the same\n// question, and this one for how the questions are derived.\nimport {\n checklistDigest,\n claimId,\n DIGEST_LENGTH,\n ID_LENGTH,\n type ClaimKind,\n} from \"./claims/identity.js\";\n\nexport interface Claim {\n /** `<kind>-<8 hex>` — stable for the life of the claim. See `claimId`. */\n id: string;\n kind: ClaimKind;\n /** The service whose code answers this. A caller's claim is filed under the caller. */\n subject: string;\n /** The claim in one line, answerable without reading the feature's files. */\n claim: string;\n /**\n * `scenario.tested` only: the first {@link DIGEST_LENGTH} hex of the\n * scenario's body hash — the exact digest `loam gherkin` stamps as\n * `@loam-digest-…`, which is what a cucumber report scenario carries and\n * what `--results` matches on. Absent on every other kind.\n */\n digest?: string;\n /**\n * `api.exposes` only: the operationId the claim asserts, structurally — the\n * join key `--contract-results` matches a contract report's entries on,\n * exactly as `digest` is `--results`'s. Never parsed back out of the claim\n * text, which is prose and may be reworded. Absent on every other kind.\n */\n operation?: string;\n /**\n * The literal string the claim asserts of the cited artifact — the\n * operationId for `api.exposes`, the message name for `event.declares`, the\n * edge's op for `c4.calls` — carried structurally so evidence pins\n * (`./pins/pin.ts`) can stamp it at record time and the read side never\n * parses it back out of claim prose. A separate field from `operation` on\n * purpose: `contestedOperations` filters on `operation !== undefined` and\n * must not start seeing `c4.calls` claims. Absent where a claim asserts no\n * literal (`service.exists`, `scenario.tested`).\n */\n token?: string;\n}\n\nexport interface Checklist {\n feature: string;\n claims: Claim[];\n /** A digest of the claim id SET — what says a record still answers this feature. */\n digest: string;\n}\n\n/**\n * Derive the checklist from a feature's own artifacts. No code is read here and\n * none should be: the whole design rests on the questions being computed from\n * documents that do not change between runs.\n */\nexport async function featureChecklist(\n docsDir: DocsDir,\n featureDir: FeatureDir,\n featureId: string,\n fleet?: FleetContext,\n): Promise<Checklist> {\n // The per-service loop below resolves living contracts through the\n // enumeration twice per service; without a shared context each of those is\n // a full fleet walk, so deriving one checklist cost O(services²) reads. A\n // caller-supplied context is used when given; otherwise one is created for\n // this derivation alone — the loop still pays a single walk either way.\n const ctx = fleet ?? new FleetContext();\n const seen = new Map<string, number>();\n const claim = (kind: ClaimKind, subject: string, parts: string[], text: string): Claim => ({\n id: claimId(featureId, kind, parts, seen),\n kind,\n subject,\n claim: text,\n });\n\n const exists: Claim[] = [];\n const exposes: Claim[] = [];\n const declares: Claim[] = [];\n const calls: Claim[] = [];\n const scenarios: Claim[] = [];\n\n const specServices = await featureSpecServices(featureDir, ctx);\n\n // Architecture — what the delta promised the fleet would look like. Loaded\n // through the context rather than a bare parse, so a caller that derives\n // several checklists in one invocation (`loam gate`'s verification check,\n // the fleet forms) shares one parse per delta — and a batch prefetch\n // (`FleetContext.prefetchLikeC4`) genuinely seeds this load.\n const deltaPath = featurePaths(featureDir).delta;\n if (existsSync(deltaPath)) {\n const res = await ctx.loadLikeC4(deltaPath);\n // A delta nobody can parse promises nothing checkable. `validate` already\n // reports it; inventing claims out of a broken document would only add noise.\n if (res.errors.length === 0) {\n const elements: Elem[] = res.elements;\n // The enumerated fleet — `services/` plus this feature's own `specs/`\n // directories, the only place a service the feature INTRODUCES has a\n // directory at all. Built once, above both readers below, because they\n // must not disagree: the skip loop asks whether an element STANDS FOR a\n // service, and the resolver asks WHICH service an endpoint belongs to,\n // and a set that answered the second question only left the first one\n // reading a title as unknown that the very next line resolved.\n const known = new Set<string>([\n ...(await enumeratedServiceIds(docsDir, ctx)),\n ...specServices,\n ]);\n for (const e of elements) {\n if (!e.tags.includes(featureId)) continue;\n // A dotted id is a container INSIDE a service, an actor is nobody's\n // repository, and the fleet's own infrastructure — a `database`,\n // `queue`, `topic`, `cache` or `bucket` — has no repository either.\n // None of the three is a service that has to exist.\n //\n // The infra skip closes a claim no repository could ever answer: a\n // delta that draws a fleet-level `database` minted `service.exists` for\n // it, and `loam verify` then asked somebody to point at the code that\n // proves a Postgres has a services/ directory. It is the SAME shape as\n // the container-and-actor skips beside it — an element that is not a\n // team's system — not a new kind of exemption.\n //\n // STANDING FOR A SERVICE still wins, because that is a claim about\n // this repo rather than a guess at one, and it is the same predicate\n // every other surface applies — `commands/validate/fleet/census.ts`'s\n // `standsForService`, spelled here rather than imported because core\n // may not reach into commands. Both of its rungs matter: a `database`\n // carrying `metadata { service '<id>' }` names a directory the author\n // says exists, and one whose TITLE names a real `services/<id>/` names\n // a directory the enumeration just read. Testing the binding alone made\n // an adopted store a service on every other surface and infrastructure\n // here, so its `service.exists` claim silently went missing from the\n // checklist that is supposed to hold every promise the feature made.\n const standsFor = e.service !== undefined || known.has(e.title);\n const infra = !standsFor && INFRA_KINDS.has(e.kind.toLowerCase());\n if (e.id.includes(\".\") || ACTOR_KINDS.has(e.kind.toLowerCase()) || infra) continue;\n const svc = elementService(e);\n exists.push(claim(\"service.exists\", svc, [svc], `service '${svc}' exists`));\n }\n // That same set rides into the resolver so a claim about an edge drawn\n // into a modelled container names the service that owns it. A claim\n // against a service called \"api\" that has never existed is one no\n // evidence in any repository could ever answer, and the fleet half alone\n // still derived exactly that claim for an introduced service's own\n // container. Unenumerable services/ degrades to the feature's names.\n const svcOf = serviceResolver(elements, known);\n for (const r of res.relationships) {\n // Untagged edges are context for the diagram; an edge with no operation\n // names nothing specific enough to look for in the code.\n if (!r.tags.includes(featureId) || r.op === undefined) continue;\n const from = svcOf(r.source);\n const to = svcOf(r.target);\n calls.push({\n ...claim(\"c4.calls\", from, [from, to, r.op], `${from} calls '${r.op}' on ${to}`),\n // The same string as the provider's api.exposes token, scanned in the\n // CALLER's cited file — a call site plausibly spells the operationId.\n token: r.op,\n });\n }\n }\n }\n\n for (const svc of specServices) {\n const paths = featureSpecPaths(featureDir, svc);\n\n // Contract — only what is NEW. A delta's openapi.yaml is a whole document,\n // not a patch, so authors restate the living API inside it; asking whether a\n // service still exposes what it already exposed is noise.\n const featOps = (await operations(paths.openapi))\n .filter((operation) => !operation.remove)\n .map((operation) => operation.id);\n if (featOps.length > 0) {\n const living = new Set(await operationIds((await locateServicePaths(docsDir, svc, ctx)).openapi));\n for (const op of featOps) {\n if (living.has(op)) continue;\n exposes.push({\n ...claim(\"api.exposes\", svc, [svc, op], `${svc} exposes operationId '${op}'`),\n operation: op,\n token: op,\n });\n }\n }\n\n // Events — the same NEW-only discipline on the async axis: a delta's\n // asyncapi.yaml is a complete document too, so what the living contract\n // already sends or receives is restatement, not a promise. Removals are\n // not questions either — the reader already keeps marked declarations out\n // of the sent/received sets — and an unreadable delta promises nothing\n // checkable, exactly like the broken C4 delta above.\n const events = await readAsyncapi(paths.asyncapi);\n if (events.sent.length > 0 || events.received.length > 0) {\n const living = await readAsyncapi((await locateServicePaths(docsDir, svc, ctx)).asyncapi);\n const directions = [\n { direction: \"sends\", feat: events.sent, known: new Set(living.sent) },\n { direction: \"receives\", feat: events.received, known: new Set(living.received) },\n ] as const;\n for (const d of directions) {\n for (const name of d.feat) {\n if (d.known.has(name)) continue;\n declares.push({\n ...claim(\n \"event.declares\",\n svc,\n [svc, d.direction, name],\n `${svc} declares it ${d.direction} message '${name}'`,\n ),\n token: name,\n });\n }\n }\n }\n\n // Behaviour — the scenarios of the requirements this feature CHANGES. A BASE\n // requirement is the living state quoted inside the delta, and a REMOVED one\n // is being retired: neither is work anybody has to have done.\n if (existsSync(paths.spec)) {\n for (const r of parseRequirements(await readFile(paths.spec, \"utf8\"))) {\n if (r.kind === \"BASE\" || r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n const body = scenarioBodyHash(svc, s.lines);\n scenarios.push({\n ...claim(\n \"scenario.tested\",\n svc,\n [svc, r.id ?? r.name, s.name, body.slice(0, ID_LENGTH)],\n `scenario '${s.name}' of requirement '${r.name}' (${svc}) is covered by a test`,\n ),\n digest: body.slice(0, DIGEST_LENGTH),\n });\n }\n }\n }\n\n // The architecture spec axis, same walk: an arch scenario is a promise like\n // any other — the outbox test nobody was going to write IS the point of the\n // axis. The source file rides in the id tuple AND in the body hash itself\n // (two axes are two namespaces: identically-worded scenarios must stay two\n // questions, and the axis-salted digest is what keeps a business-only test\n // run from answering the arch claim) and in the claim text, so the\n // answering agent knows it is being asked for an integration/ops test, not\n // an acceptance test.\n if (existsSync(paths.archSpec)) {\n for (const r of parseRequirements(await readFile(paths.archSpec, \"utf8\"))) {\n if (r.kind === \"BASE\" || r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n const body = scenarioBodyHash(svc, s.lines, \"arch\");\n scenarios.push({\n ...claim(\n \"scenario.tested\",\n svc,\n [svc, \"arch.spec.md\", r.id ?? r.name, s.name, body.slice(0, ID_LENGTH)],\n `scenario '${s.name}' of arch requirement '${r.name}' (${svc}, arch.spec.md) is covered by a test`,\n ),\n digest: body.slice(0, DIGEST_LENGTH),\n });\n }\n }\n }\n }\n\n const claims = [...exists, ...exposes, ...declares, ...calls, ...scenarios];\n return { feature: featureId, claims, digest: checklistDigest(claims.map((c) => c.id)) };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../src/core/vocabulary/issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuBH,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAC3C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;QACtB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QAChE,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,iBAAiB,GAA2B,IAAI,GAAG,CAAY;IACnE,4BAA4B;IAC5B,sBAAsB;IACtB,qBAAqB;IACrB,uBAAuB;IACvB,wBAAwB;IACxB,yBAAyB;IACzB,sBAAsB;CACvB,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,CAAQ;IACvC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC","sourcesContent":["/**\n * A coherence breach: something that makes a feature unsafe to merge into the\n * living docs. Shared by every check the archive gate runs.\n */\n\nimport type { Finding, FindingLocation } from \"./report.js\";\n\nexport interface Issue {\n severity: \"error\" | \"warn\";\n /** Stable machine identifier for the breach — see the labels below. */\n code: IssueCode;\n /** What the breach is about when that is narrower than the feature — usually a service. */\n subject?: string;\n message: string;\n locations?: FindingLocation[];\n /**\n * Does this issue stop `loam archive`? Severity and gating answer two\n * different questions: severity says whether the DOCUMENT is valid (`loam\n * validate` fails on errors), gating says whether the MERGE is safe. They\n * usually agree — errors gate, warnings do not — so the field is stated only\n * where they diverge: a legal document whose merge would drop authored\n * content is a warning that gates.\n */\n gates?: boolean;\n}\n\n/** Whether `loam archive` refuses on this issue without `--approve`. */\nexport function gatesArchive(i: Issue): boolean {\n return i.gates ?? i.severity === \"error\";\n}\n\n/** One issue in the shared validation/status finding shape. */\nexport function issueFinding(i: Issue): Finding {\n return {\n severity: i.severity,\n code: i.code,\n gates: gatesArchive(i),\n ...(i.subject === undefined ? {} : { subject: i.subject }),\n ...(i.locations === undefined ? {} : { locations: i.locations }),\n message: i.message,\n };\n}\n\n/**\n * Whether `--approve` can move this issue when it gates. A third axis beside\n * severity and gating: severity answers \"is the DOCUMENT valid\", gating\n * answers \"is the MERGE safe\", and this one answers whose call the refusal is\n * — a human's or the grammar's. Almost every gating issue is a judgment about\n * the feature, which is exactly the judgment `--approve` exists to override.\n *\n * The exceptions are the ones where approval could not change what happens.\n * `c4.service-binding-invalid`: the binding IS the path the merge would write,\n * so the fix is the name, never the flag. `glossary.term-exists`: the merge is\n * a whole-file copy over an authored definition, so approving it is approving a\n * deletion nobody described — and the alternative the message names (edit the\n * living definition in the same change) costs one `git mv` and loses nothing.\n * `usecase.flow-exists` is the same act one axis over: the merge would copy a\n * feature's flow over a living one, and an ordered hop sequence is recorded\n * nowhere else either. `usecase.flow-invalid` is the other kind of exception —\n * not a deletion but a BLINDNESS: loam could not read the feature's flows\n * against the map its own merge would leave, so it graded nothing, and\n * `--approve` overrides loam's judgment about coherence and never its ability\n * to read an axis. That is the same line `delta.likec4`'s unparseable case is\n * refused on, one merge over.\n *\n * `archive` refuses these under the same `not-coherent` envelope and spells\n * this verdict per issue as the additive `overridable` key, so a `--json`\n * consumer branches on data rather than on a code list.\n */\nconst NEVER_OVERRIDABLE: ReadonlySet<IssueCode> = new Set<IssueCode>([\n \"c4.service-binding-invalid\",\n \"glossary.term-exists\",\n \"usecase.flow-exists\",\n \"deployment.doc-exists\",\n \"deployment.doc-invalid\",\n \"deployment.doc-reserved\",\n \"usecase.flow-invalid\",\n]);\n\nexport function approveOverrides(i: Issue): boolean {\n return !NEVER_OVERRIDABLE.has(i.code);\n}\n\n/**\n * The three grades a CAPABILITY DOCUMENT earns on its own terms, named as a\n * union of their own because both corpora emit them and the two must stay one\n * list: `validate --all` grades the living `capabilities/<id>/spec.md` and\n * files them as Findings, while the archive gate grades a feature's\n * `features/<FEAT>/capabilities/<id>/spec.md` and files the SAME three as\n * archive-gating Issues.\n *\n * Grading the delta is not belt-and-braces. Without it, a capability\n * requirement with no `Requirement-ID:` — or one carrying `Operations:`, the\n * service-altitude line the whole corpus exists to keep out — merges into the\n * living tree and only THEN earns its error, against a document whoever reads\n * the finding did not write. The feature-local delta must not become a second\n * way to write service requirements, and this is the rule that stops it.\n *\n * No code is added to the product here: all three already shipped, are already\n * emitted from `core/capabilities/findings.ts` with literal `code:` values, and\n * are already documented in the `/loam-check` table. What the union member adds\n * is the compiler's permission to file the same finding at the archive gate.\n */\nexport type CapabilityDocCode =\n /** a requirement in a capability document with no `Requirement-ID:` — identity by heading, which every join to it breaks on */\n | \"capability.requirement-unidentified\"\n /** a capability requirement carrying `Operations:`/`Covers:`/`Publishes:`/`Consumes:` — a service requirement filed at the wrong altitude */\n | \"capability.requirement-service-scoped\"\n /** a capability requirement carrying `Capability:` or `Realizes:` — the axis's own joins, which point into the tree and so do nothing written inside it */\n | \"capability.requirement-inert-join\";\n\nexport type IssueCode =\n /* --- cross-axis: C4 <-> requirements <-> OpenAPI --- */\n /** the architecture axis could not be read at all */\n | \"delta.invalid\"\n /** the delta declares elements or edges but none carry the feature tag — loam cannot see any of it */\n | \"delta.nothing-tagged\"\n /** E1 — a requirement governs an operation its service's OpenAPI does not define */\n | \"spec-api.op-undefined\"\n /** E1, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"spec-api.op-pending\"\n /** E2 — a C4 edge calls an operation the target's OpenAPI does not define */\n | \"c4-api.op-undefined\"\n /** E2, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"c4-api.op-pending\"\n /** E2's lifecycle shadow — a NEW tagged edge consumes an operation the living provider contract marks `deprecated: true`; advisory, never a gate */\n | \"c4-api.op-deprecated\"\n /** a NEW tagged edge consumes an operation this same feature removes */\n | \"c4-api.op-removing\"\n /** an operation-removal marker addresses no living path+method */\n | \"openapi.remove-target-missing\"\n /** an operation-removal marker's id differs from the living operation at that path+method */\n | \"openapi.remove-target-mismatch\"\n /** a REMOVED requirement governs an operation but the feature has no matching removal marker */\n | \"openapi.remove-marker-missing\"\n /** an operation-removal marker is not justified by a REMOVED requirement */\n | \"openapi.remove-marker-unjustified\"\n /* --- the event axis: C4 <-> requirements <-> AsyncAPI, joined on the message name --- */\n /** the FEATURE's own asyncapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every event-axis check for the service is suspended */\n | \"asyncapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or one on a slot the living contract does not have; a malformed `x-loam-baselines` record, an entry of it naming a component surface the delta does not declare, or one whose living counterpart is gone */\n | \"asyncapi.baseline-invalid\"\n /** a feature slot or component surface with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it; warn that gates, counted per service */\n | \"asyncapi.baseline-missing\"\n /** the living slot or component surface changed since this delta pinned it — merging would discard whoever landed in between */\n | \"asyncapi.baseline-stale\"\n /** a slot-removal marker addresses a (section, key) the living contract does not have */\n | \"asyncapi.remove-target-missing\"\n /** a message-removal marker's name differs from the living declaration at that key */\n | \"asyncapi.remove-target-mismatch\"\n /** a REMOVED requirement's Publishes:/Consumes: line names a message the living contract still declares, with no matching removal marker in the feature */\n | \"asyncapi.remove-marker-missing\"\n /** a message-removal marker no REMOVED requirement's Publishes:/Consumes: line justifies — message slots only; channel and operation slots need exactness alone */\n | \"asyncapi.remove-marker-unjustified\"\n /** the feature retires a message the LIVING fleet still consumes — a landscape consumes-edge, or another service's living requirement */\n | \"asyncapi.remove-message-consumed\"\n /** another feature in flight adds or edits the same (service, message) — whichever archives second replaces the other's declaration wholesale */\n | \"asyncapi.message-conflict\"\n /** internal `$ref`(s) resolving to nothing — validate's warn on a living contract; a gating plan issue at archive when the MERGED document would carry the dangling reference */\n | \"asyncapi.ref-unresolved\"\n /** a tagged edge publishes/consumes a message its bound service's contract — as the feature's merge would leave it, feature ∪ living where no delta exists — declares no matching send/receive operation for */\n | \"c4-event.message-undefined\"\n /** softened — the message is introduced by another feature still in flight; archive that one first */\n | \"c4-event.message-pending\"\n /** a delta requirement's Publishes:/Consumes: line names a message the service's contract — as the feature's merge would leave it, so a retired declaration stops answering — does not declare in that direction; validate's living-side code, graded in feature scope too */\n | \"spec-event.message-undefined\"\n /** softened — the named message is introduced by another feature still in flight */\n | \"spec-event.message-pending\"\n /** W1 — an operation is called but no requirement governs it */\n | \"c4.op-ungoverned\"\n /** W2 — the feature adds an operation no architecture edge consumes */\n | \"api.op-unconsumed\"\n /** W3 — a new service arrives with no requirement delta */\n | \"service.no-requirement-delta\"\n /** W4 — a \"Calls\" edge carries no operation link */\n | \"c4.op-link-missing\"\n /** an explicit `metadata { service }` binding — a tagged element's, or one riding anywhere inside its authored block — breaks the service-id grammar; the merge would splice the name into the living landscape verbatim, and probe services/<id>/ with it; never overridable, the path is a mechanical fact */\n | \"c4.service-binding-invalid\"\n /* --- delta shape: does the diff apply to the living spec it claims to change? --- */\n /** a heading that nearly matches the delta grammar — its requirements merge as nothing */\n | \"delta.unknown-section\"\n /** a delta file with requirements but no delta section anywhere — the whole file would merge as nothing */\n | \"delta.no-delta-sections\"\n /** a requirement stranded under a prose heading — archive will not merge it */\n | \"delta.requirement-not-merged\"\n /** a Requirement-ID in a delta violates the stable-id grammar */\n | \"delta.requirement-id-invalid\"\n /** one delta requirement declares Requirement-ID more than once */\n | \"delta.requirement-id-repeated\"\n /** one Requirement-ID identifies multiple requirements in a delta */\n | \"delta.requirement-id-duplicate\"\n /** the living spec has malformed/ambiguous IDs and cannot be selected safely */\n | \"delta.living-requirement-id-invalid\"\n /** a delta's stable ID and heading select different living requirements */\n | \"delta.requirement-identity-collision\"\n /** MODIFIED a requirement the living spec does not have */\n | \"delta.modified-unknown\"\n /** REMOVED a requirement the living spec does not have */\n | \"delta.removed-unknown\"\n /** ADDED a requirement the living spec already has — the merge would replace it */\n | \"delta.added-duplicate\"\n /** ADDED a name that differs only in case from a living requirement — the merge matches exactly, so both would coexist */\n | \"delta.added-near-duplicate\"\n /** MODIFIED something another feature in flight introduces — an ordering dependency */\n | \"delta.modified-pending\"\n /** REMOVED something another feature in flight introduces */\n | \"delta.removed-pending\"\n /** two features in flight add the same requirement to the same service */\n | \"delta.added-conflict\"\n /* --- archive plan: breaches only the merge computation itself can see --- */\n /** a LIVING requirement outside `## Requirements` — the merge rewrites only that section, so it would land twice */\n | \"living.requirement-outside-requirements\"\n /** the delta redefines an operation the living OpenAPI already has — the merge overwrites it wholesale */\n | \"openapi.op-modified\"\n /** a component the merge carries — reached by a merged operation, or declared by a delta whose whole change is components — already exists in the living OpenAPI with different content, and the merge overwrites it wholesale */\n | \"openapi.component-modified\"\n /** a $ref reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — merging would write a dangling reference */\n | \"openapi.ref-unresolved\"\n /** the delta redefines a PATH-level key (parameters, servers, summary) the living OpenAPI already has — the overwrite applies to every operation on that path */\n | \"openapi.path-item-modified\"\n /** the delta redefines a `components.messages` slot the living AsyncAPI already has — the merge overwrites the living message wholesale */\n | \"asyncapi.message-modified\"\n /** the delta redefines a `channels` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included */\n | \"asyncapi.channel-modified\"\n /** the delta redefines an `operations` slot the living AsyncAPI already has — the merge overwrites it wholesale */\n | \"asyncapi.operation-modified\"\n /** the delta declares a component SURFACE outside `components.messages` — a schema, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale */\n | \"asyncapi.component-modified\"\n /** an `x-loam-remove: true` marker with no operationId — loam cannot tell which operation it retires, and the marker itself would reach the living contract */\n | \"openapi.remove-marker-anonymous\"\n /** an `x-loam-remove: true` written at PATH level, beside the methods — it addresses no operation, so it retires nothing and is not a contract key either */\n | \"openapi.remove-marker-path-level\"\n /** an `x-loam-remove: true` nested on an INLINE channel message — inline messages are channel interior, never slots, so it retires nothing; when the channel is otherwise unchanged the marker surfaces nowhere else at all */\n | \"asyncapi.remove-marker-inline\"\n /** the living OpenAPI defines one operationId in two (path, method) slots — every join on the id picks one arbitrarily */\n | \"openapi.duplicate-operationid\"\n /** the FEATURE's own openapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every contract-axis check for the service is suspended, and the archive plan refuses the merge mechanically */\n | \"openapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or sits on an operation the living contract does not have; a malformed `x-loam-baselines` record, an entry of it pinning a surface the delta does not restate, or one whose living counterpart is gone */\n | \"openapi.baseline-invalid\"\n /** a feature operation, restated path-level key or component with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it */\n | \"openapi.baseline-missing\"\n /** the living operation, path-level key or component changed since this delta pinned it — merging would discard whoever landed in between */\n | \"openapi.baseline-stale\"\n /** the feature retires an operation the LIVING fleet still consumes — a landscape edge's `metadata { op }`, or another service's living requirement */\n | \"openapi.remove-op-consumed\"\n /** two requirements in the LIVING document share one heading — MODIFIED rewrites the first, REMOVED deletes both, so no delta applies predictably */\n | \"delta.living-duplicate-requirement\"\n /** another feature in flight MODIFIES/REMOVES the same living requirement — whichever archives second replaces the other's text wholesale */\n | \"delta.modified-conflict\"\n /** a `Based-On:` line whose value is not a digest, or declared twice — the pin cannot be compared, so it protects nothing */\n | \"delta.baseline-invalid\"\n /** a MODIFIED/REMOVED requirement with no `Based-On:` — nothing can tell whether the living text moved under it */\n | \"delta.baseline-missing\"\n /** the living requirement changed since this delta was written — merging it would silently discard whoever landed in between */\n | \"delta.baseline-stale\"\n /** the archive creates `services/<id>/` but nothing writes its `model.likec4` — the fleet gate will report the service incomplete */\n | \"service.no-model\"\n /* --- capability axis: the Capability: join against architecture/capabilities.yaml --- */\n /** a delta requirement's `Capability:` entry that the fleet vocabulary does not declare — the merge would land a join that resolves to nothing, so it blocks archive like other errors, and `--approve` overrides it */\n | \"capability.unknown\"\n /** a delta requirement's `Realizes:` entry that names no capability requirement — same argument as `capability.unknown` one join over: the merge would land a pointer at a promise that does not exist, looking exactly like a working one */\n | \"capability.realizes-unknown\"\n /** a capability requirement this feature ADDS that no `Realizes:` line in the same feature's service deltas names — the merge would land a business promise nothing in this change keeps. A warning that GATES, `--approve` overrides: the document is legal (writing ahead of the fleet is the intended use, which is why `capability.requirement-unrealized` warns and never gates), the MERGE is what is unsafe. Joined by `Realizes:`, NOT by `Covers:` — the name is the roadmap's, the join is the other one */\n | \"capability.uncovered\"\n /** the same join in the removal direction — this feature RETIRES a capability requirement that something the merge leaves behind still realizes, so the archive would leave `capability.realizes-unknown` standing against a document nobody in the feature touched. An error, exactly as `openapi.remove-op-consumed` is for the identical shape one axis over */\n | \"capability.remove-requirement-realized\"\n | CapabilityDocCode\n /* --- the domain glossary: a term a feature introduces --- */\n /** a `features/<FEAT>/glossary/<term>.md` whose term the living glossary already defines — the merge is a whole-file copy, so it would replace an authored definition wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local glossary document INTRODUCES a term, and rewriting one belongs in a pull request where git produces the conflict */\n | \"glossary.term-exists\"\n /* --- the use-case axis: a flow a feature introduces --- */\n /** a `features/<FEAT>/usecases/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The flows were not graded, and archiving would copy them into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it */\n | \"usecase.flow-invalid\"\n /** a `features/<FEAT>/usecases/<name>.likec4` whose flow the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored hop sequence wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local flow INTRODUCES a use case, and rewriting one belongs in a pull request where git produces the conflict */\n | \"usecase.flow-exists\"\n /* --- the deployment axis: the topology a feature introduces --- */\n /** a `features/<FEAT>/deployment/<name>.likec4` whose file the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored topology wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local deployment document INTRODUCES topology, and rewriting one belongs in a pull request where git produces the conflict. The same act, and the same severity, as `usecase.flow-exists` one axis over */\n | \"deployment.doc-exists\"\n /** a `features/<FEAT>/deployment/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The topology was not graded, and archiving would copy it into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it; the twin of `usecase.flow-invalid` one axis over */\n | \"deployment.doc-invalid\"\n /** a `features/<FEAT>/deployment/<name>.likec4` whose name is one loam owns inside `architecture/` — `subsystems.likec4` (generated, overwritten wholesale by `loam subsystem sync`, and parsed by nothing, so anything landing there is invisible from the moment it archives), `landscape.likec4` (spliced into, never replaced), or a path under `usecases/` (the flow tree). Refused whether or not the target exists, which is the point: two of the three fail silently when it does not. An error with no legal reading, so `--approve` changes nothing about it */\n | \"deployment.doc-reserved\"\n /* --- authoring: did a person actually write this? --- */\n /** a document `loam new` scaffolded still carries its exact placeholder text — the merge would publish a requirement, scenario or description nobody authored */\n | \"scaffold.placeholder\"\n /** intent.md is missing, or says nothing outside the scaffold's own comments — nothing states why the feature exists */\n | \"intent.empty\"\n /* --- docs-repo contract: --all only, never a merge question --- */\n /** AGENTS.md carries no version stamp, or one older than the running binary — the agent contract may have drifted */\n | \"agents.stale\";\n"]}
1
+ {"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../src/core/vocabulary/issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuBH,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAC3C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;QACtB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QAChE,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,iBAAiB,GAA2B,IAAI,GAAG,CAAY;IACnE,4BAA4B;IAC5B,sBAAsB;IACtB,qBAAqB;IACrB,uBAAuB;IACvB,wBAAwB;IACxB,yBAAyB;IACzB,sBAAsB;CACvB,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,CAAQ;IACvC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC","sourcesContent":["/**\n * A coherence breach: something that makes a feature unsafe to merge into the\n * living docs. Shared by every check the archive gate runs.\n */\n\nimport type { Finding, FindingLocation } from \"./report.js\";\n\nexport interface Issue {\n severity: \"error\" | \"warn\";\n /** Stable machine identifier for the breach — see the labels below. */\n code: IssueCode;\n /** What the breach is about when that is narrower than the feature — usually a service. */\n subject?: string;\n message: string;\n locations?: FindingLocation[];\n /**\n * Does this issue stop `loam archive`? Severity and gating answer two\n * different questions: severity says whether the DOCUMENT is valid (`loam\n * validate` fails on errors), gating says whether the MERGE is safe. They\n * usually agree — errors gate, warnings do not — so the field is stated only\n * where they diverge: a legal document whose merge would drop authored\n * content is a warning that gates.\n */\n gates?: boolean;\n}\n\n/** Whether `loam archive` refuses on this issue without `--approve`. */\nexport function gatesArchive(i: Issue): boolean {\n return i.gates ?? i.severity === \"error\";\n}\n\n/** One issue in the shared validation/status finding shape. */\nexport function issueFinding(i: Issue): Finding {\n return {\n severity: i.severity,\n code: i.code,\n gates: gatesArchive(i),\n ...(i.subject === undefined ? {} : { subject: i.subject }),\n ...(i.locations === undefined ? {} : { locations: i.locations }),\n message: i.message,\n };\n}\n\n/**\n * Whether `--approve` can move this issue when it gates. A third axis beside\n * severity and gating: severity answers \"is the DOCUMENT valid\", gating\n * answers \"is the MERGE safe\", and this one answers whose call the refusal is\n * — a human's or the grammar's. Almost every gating issue is a judgment about\n * the feature, which is exactly the judgment `--approve` exists to override.\n *\n * The exceptions are the ones where approval could not change what happens.\n * `c4.service-binding-invalid`: the binding IS the path the merge would write,\n * so the fix is the name, never the flag. `glossary.term-exists`: the merge is\n * a whole-file copy over an authored definition, so approving it is approving a\n * deletion nobody described — and the alternative the message names (edit the\n * living definition in the same change) costs one `git mv` and loses nothing.\n * `usecase.flow-exists` is the same act one axis over: the merge would copy a\n * feature's flow over a living one, and an ordered hop sequence is recorded\n * nowhere else either. `usecase.flow-invalid` is the other kind of exception —\n * not a deletion but a BLINDNESS: loam could not read the feature's flows\n * against the map its own merge would leave, so it graded nothing, and\n * `--approve` overrides loam's judgment about coherence and never its ability\n * to read an axis. That is the same line `delta.likec4`'s unparseable case is\n * refused on, one merge over.\n *\n * `archive` refuses these under the same `not-coherent` envelope and spells\n * this verdict per issue as the additive `overridable` key, so a `--json`\n * consumer branches on data rather than on a code list.\n */\nconst NEVER_OVERRIDABLE: ReadonlySet<IssueCode> = new Set<IssueCode>([\n \"c4.service-binding-invalid\",\n \"glossary.term-exists\",\n \"usecase.flow-exists\",\n \"deployment.doc-exists\",\n \"deployment.doc-invalid\",\n \"deployment.doc-reserved\",\n \"usecase.flow-invalid\",\n]);\n\nexport function approveOverrides(i: Issue): boolean {\n return !NEVER_OVERRIDABLE.has(i.code);\n}\n\n/**\n * The three grades a CAPABILITY DOCUMENT earns on its own terms, named as a\n * union of their own because both corpora emit them and the two must stay one\n * list: `validate --all` grades the living `capabilities/<id>/spec.md` and\n * files them as Findings, while the archive gate grades a feature's\n * `features/<FEAT>/capabilities/<id>/spec.md` and files the SAME three as\n * archive-gating Issues.\n *\n * Grading the delta is not belt-and-braces. Without it, a capability\n * requirement with no `Requirement-ID:` — or one carrying `Operations:`, the\n * service-altitude line the whole corpus exists to keep out — merges into the\n * living tree and only THEN earns its error, against a document whoever reads\n * the finding did not write. The feature-local delta must not become a second\n * way to write service requirements, and this is the rule that stops it.\n *\n * No code is added to the product here: all three already shipped, are already\n * emitted from `core/capabilities/findings.ts` with literal `code:` values, and\n * are already documented in the `/loam-check` table. What the union member adds\n * is the compiler's permission to file the same finding at the archive gate.\n */\nexport type CapabilityDocCode =\n /** a requirement in a capability document with no `Requirement-ID:` — identity by heading, which every join to it breaks on */\n | \"capability.requirement-unidentified\"\n /** a capability requirement carrying `Operations:`/`Covers:`/`Publishes:`/`Consumes:` — a service requirement filed at the wrong altitude */\n | \"capability.requirement-service-scoped\"\n /** a capability requirement carrying `Capability:` or `Realizes:` — the axis's own joins, which point into the tree and so do nothing written inside it */\n | \"capability.requirement-inert-join\";\n\nexport type IssueCode =\n /* --- cross-axis: C4 <-> requirements <-> OpenAPI --- */\n /** the architecture axis could not be read at all */\n | \"delta.invalid\"\n /** the delta declares elements or edges but none carry the feature tag — loam cannot see any of it */\n | \"delta.nothing-tagged\"\n /** E1 — a requirement governs an operation its service's OpenAPI does not define */\n | \"spec-api.op-undefined\"\n /** E1, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"spec-api.op-pending\"\n /** E2 — a C4 edge calls an operation the target's OpenAPI does not define */\n | \"c4-api.op-undefined\"\n /** E2, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"c4-api.op-pending\"\n /** E2's lifecycle shadow — a NEW tagged edge consumes an operation the living provider contract marks `deprecated: true`; advisory, never a gate */\n | \"c4-api.op-deprecated\"\n /** a NEW tagged edge consumes an operation this same feature removes */\n | \"c4-api.op-removing\"\n /** an operation-removal marker addresses no living path+method */\n | \"openapi.remove-target-missing\"\n /** an operation-removal marker's id differs from the living operation at that path+method */\n | \"openapi.remove-target-mismatch\"\n /** a REMOVED requirement governs an operation but the feature has no matching removal marker */\n | \"openapi.remove-marker-missing\"\n /** an operation-removal marker is not justified by a REMOVED requirement */\n | \"openapi.remove-marker-unjustified\"\n /* --- the event axis: C4 <-> requirements <-> AsyncAPI, joined on the message name --- */\n /** the FEATURE's own asyncapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every event-axis check for the service is suspended */\n | \"asyncapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or one on a slot the living contract does not have; a malformed `x-loam-baselines` record, an entry of it naming a component surface the delta does not declare, or one whose living counterpart is gone */\n | \"asyncapi.baseline-invalid\"\n /** a feature slot or component surface with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it; warn that gates, counted per service */\n | \"asyncapi.baseline-missing\"\n /** the living slot or component surface changed since this delta pinned it — merging would discard whoever landed in between */\n | \"asyncapi.baseline-stale\"\n /** a slot-removal marker addresses a (section, key) the living contract does not have */\n | \"asyncapi.remove-target-missing\"\n /** a message-removal marker's name differs from the living declaration at that key */\n | \"asyncapi.remove-target-mismatch\"\n /** a REMOVED requirement's Publishes:/Consumes: line names a message the living contract still declares, with no matching removal marker in the feature */\n | \"asyncapi.remove-marker-missing\"\n /** a message-removal marker no REMOVED requirement's Publishes:/Consumes: line justifies — message slots only; channel and operation slots need exactness alone */\n | \"asyncapi.remove-marker-unjustified\"\n /** the feature retires a message the LIVING fleet still consumes — a landscape consumes-edge, or another service's living requirement */\n | \"asyncapi.remove-message-consumed\"\n /** another feature in flight adds or edits the same (service, message) — whichever archives second replaces the other's declaration wholesale */\n | \"asyncapi.message-conflict\"\n /** internal `$ref`(s) resolving to nothing — validate's warn on a living contract; a gating plan issue at archive when the MERGED document would carry the dangling reference */\n | \"asyncapi.ref-unresolved\"\n /** a tagged edge publishes/consumes a message its bound service's contract — as the feature's merge would leave it, feature ∪ living where no delta exists — declares no matching send/receive operation for */\n | \"c4-event.message-undefined\"\n /** softened — the message is introduced by another feature still in flight; archive that one first */\n | \"c4-event.message-pending\"\n /** a delta requirement's Publishes:/Consumes: line names a message the service's contract — as the feature's merge would leave it, so a retired declaration stops answering — does not declare in that direction; validate's living-side code, graded in feature scope too */\n | \"spec-event.message-undefined\"\n /** softened — the named message is introduced by another feature still in flight */\n | \"spec-event.message-pending\"\n /** W1 — an operation is called but no requirement governs it */\n | \"c4.op-ungoverned\"\n /** W2 — the feature adds an operation no architecture edge consumes */\n | \"api.op-unconsumed\"\n /** W3 — a new service arrives with no requirement delta */\n | \"service.no-requirement-delta\"\n /** W4 — a \"Calls\" edge carries no operation link */\n | \"c4.op-link-missing\"\n /** an explicit `metadata { service }` binding — a tagged element's, or one riding anywhere inside its authored block — breaks the service-id grammar; the merge would splice the name into the living landscape verbatim, and probe services/<id>/ with it; never overridable, the path is a mechanical fact */\n | \"c4.service-binding-invalid\"\n /* --- delta shape: does the diff apply to the living spec it claims to change? --- */\n /** a heading that nearly matches the delta grammar — its requirements merge as nothing */\n | \"delta.unknown-section\"\n /** a delta file with requirements but no delta section anywhere — the whole file would merge as nothing */\n | \"delta.no-delta-sections\"\n /** a requirement stranded under a prose heading — archive will not merge it */\n | \"delta.requirement-not-merged\"\n /** a Requirement-ID in a delta violates the stable-id grammar */\n | \"delta.requirement-id-invalid\"\n /** one delta requirement declares Requirement-ID more than once */\n | \"delta.requirement-id-repeated\"\n /** one Requirement-ID identifies multiple requirements in a delta */\n | \"delta.requirement-id-duplicate\"\n /** the living spec has malformed/ambiguous IDs and cannot be selected safely */\n | \"delta.living-requirement-id-invalid\"\n /** a delta's stable ID and heading select different living requirements */\n | \"delta.requirement-identity-collision\"\n /** MODIFIED a requirement the living spec does not have */\n | \"delta.modified-unknown\"\n /** REMOVED a requirement the living spec does not have */\n | \"delta.removed-unknown\"\n /** ADDED a requirement the living spec already has — the merge would replace it */\n | \"delta.added-duplicate\"\n /** ADDED a name that differs only in case from a living requirement — the merge matches exactly, so both would coexist */\n | \"delta.added-near-duplicate\"\n /** MODIFIED something another feature in flight introduces — an ordering dependency */\n | \"delta.modified-pending\"\n /** REMOVED something another feature in flight introduces */\n | \"delta.removed-pending\"\n /** two features in flight add the same requirement to the same service */\n | \"delta.added-conflict\"\n /* --- archive plan: breaches only the merge computation itself can see --- */\n /** a LIVING requirement outside `## Requirements` — the merge rewrites only that section, so it would land twice */\n | \"living.requirement-outside-requirements\"\n /** the delta redefines an operation the living OpenAPI already has — the merge overwrites it wholesale */\n | \"openapi.op-modified\"\n /** a component the merge carries — reached by a merged operation, or declared by a delta whose whole change is components — already exists in the living OpenAPI with different content, and the merge overwrites it wholesale */\n | \"openapi.component-modified\"\n /** a $ref reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — merging would write a dangling reference */\n | \"openapi.ref-unresolved\"\n /** the delta redefines a PATH-level key (parameters, servers, summary) the living OpenAPI already has — the overwrite applies to every operation on that path */\n | \"openapi.path-item-modified\"\n /** the delta redefines a `components.messages` slot the living AsyncAPI already has — the merge overwrites the living message wholesale */\n | \"asyncapi.message-modified\"\n /** the delta redefines a `channels` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included */\n | \"asyncapi.channel-modified\"\n /** the delta redefines an `operations` slot the living AsyncAPI already has — the merge overwrites it wholesale */\n | \"asyncapi.operation-modified\"\n /** the delta declares a component SURFACE outside `components.messages` — a schema, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale */\n | \"asyncapi.component-modified\"\n /** an `x-loam-remove: true` marker with no operationId — loam cannot tell which operation it retires, and the marker itself would reach the living contract */\n | \"openapi.remove-marker-anonymous\"\n /** an `x-loam-remove: true` written at PATH level, beside the methods — it addresses no operation, so it retires nothing and is not a contract key either */\n | \"openapi.remove-marker-path-level\"\n /** an `x-loam-remove: true` nested on an INLINE channel message — inline messages are channel interior, never slots, so it retires nothing; when the channel is otherwise unchanged the marker surfaces nowhere else at all */\n | \"asyncapi.remove-marker-inline\"\n /** the living OpenAPI defines one operationId in two (path, method) slots — every join on the id picks one arbitrarily */\n | \"openapi.duplicate-operationid\"\n /** the FEATURE's own openapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every contract-axis check for the service is suspended, and the archive plan refuses the merge mechanically */\n | \"openapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or sits on an operation the living contract does not have; a malformed `x-loam-baselines` record, an entry of it pinning a surface the delta does not restate, or one whose living counterpart is gone */\n | \"openapi.baseline-invalid\"\n /** a feature operation, restated path-level key or component with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it */\n | \"openapi.baseline-missing\"\n /** the living operation, path-level key or component changed since this delta pinned it — merging would discard whoever landed in between */\n | \"openapi.baseline-stale\"\n /** the feature retires an operation the LIVING fleet still consumes — a landscape edge's `metadata { op }`, or another service's living requirement */\n | \"openapi.remove-op-consumed\"\n /** two requirements in the LIVING document share one heading — MODIFIED rewrites the first, REMOVED deletes both, so no delta applies predictably */\n | \"delta.living-duplicate-requirement\"\n /** another feature in flight MODIFIES/REMOVES the same living requirement — whichever archives second replaces the other's text wholesale */\n | \"delta.modified-conflict\"\n /** a `Based-On:` line whose value is not a digest, or declared twice — the pin cannot be compared, so it protects nothing */\n | \"delta.baseline-invalid\"\n /** a MODIFIED/REMOVED requirement with no `Based-On:` — nothing can tell whether the living text moved under it */\n | \"delta.baseline-missing\"\n /** the living requirement changed since this delta was written — merging it would silently discard whoever landed in between */\n | \"delta.baseline-stale\"\n /** the archive creates `services/<id>/` but nothing writes its `model.likec4` — the fleet gate will report the service incomplete */\n | \"service.no-model\"\n /* --- capability axis: the Capability: join against architecture/capabilities.yaml --- */\n /** a delta requirement's `Capability:` entry that the fleet vocabulary does not declare — the merge would land a join that resolves to nothing, so it blocks archive like other errors, and `--approve` overrides it */\n | \"capability.unknown\"\n /** a delta requirement's `Realizes:` entry that names no capability requirement — same argument as `capability.unknown` one join over: the merge would land a pointer at a promise that does not exist, looking exactly like a working one */\n | \"capability.realizes-unknown\"\n /** a capability requirement this feature ADDS that no `Realizes:` line in the same feature's service deltas names — the merge would land a business promise nothing in this change keeps. A warning that GATES, `--approve` overrides: the document is legal (writing ahead of the fleet is the intended use, which is why `capability.requirement-unrealized` warns and never gates), the MERGE is what is unsafe. Joined by `Realizes:`, NOT by `Covers:` — the name is the roadmap's, the join is the other one */\n | \"capability.uncovered\"\n /** the same join in the removal direction — this feature RETIRES a capability requirement that something the merge leaves behind still realizes, so the archive would leave `capability.realizes-unknown` standing against a document nobody in the feature touched. An error, exactly as `openapi.remove-op-consumed` is for the identical shape one axis over */\n | \"capability.remove-requirement-realized\"\n | CapabilityDocCode\n /* --- the domain glossary: a term a feature introduces --- */\n /** a `features/<FEAT>/glossary/<term>.md` whose term the living glossary already defines — the merge is a whole-file copy, so it would replace an authored definition wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local glossary document INTRODUCES a term, and rewriting one belongs in a pull request where git produces the conflict */\n | \"glossary.term-exists\"\n /* --- the use-case axis: a flow a feature introduces --- */\n /** a `features/<FEAT>/usecases/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The flows were not graded, and archiving would copy them into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it */\n | \"usecase.flow-invalid\"\n /** a `features/<FEAT>/usecases/<name>.likec4` whose flow the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored hop sequence wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local flow INTRODUCES a use case, and rewriting one belongs in a pull request where git produces the conflict */\n | \"usecase.flow-exists\"\n /* --- the deployment axis: the topology a feature introduces --- */\n /** a `features/<FEAT>/deployment/<name>.likec4` whose file the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored topology wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local deployment document INTRODUCES topology, and rewriting one belongs in a pull request where git produces the conflict. The same act, and the same severity, as `usecase.flow-exists` one axis over */\n | \"deployment.doc-exists\"\n /** a `features/<FEAT>/deployment/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The topology was not graded, and archiving would copy it into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it; the twin of `usecase.flow-invalid` one axis over */\n | \"deployment.doc-invalid\"\n /** a `features/<FEAT>/deployment/<name>.likec4` whose name is one loam owns inside `architecture/` — `subsystems.likec4` and `services.likec4` (both generated, overwritten wholesale by `loam subsystem sync`, and parsed by nothing, so anything landing in either is invisible from the moment it archives), `landscape.likec4` (spliced into, never replaced), or a path under `usecases/` (the flow tree). Refused whether or not the target exists, which is the point: three of the four fail silently when it does not. An error with no legal reading, so `--approve` changes nothing about it */\n | \"deployment.doc-reserved\"\n /* --- authoring: did a person actually write this? --- */\n /** a document `loam new` scaffolded still carries its exact placeholder text — the merge would publish a requirement, scenario or description nobody authored */\n | \"scaffold.placeholder\"\n /** intent.md is missing, or says nothing outside the scaffold's own comments — nothing states why the feature exists */\n | \"intent.empty\"\n /* --- docs-repo contract: --all only, never a merge question --- */\n /** AGENTS.md carries no version stamp, or one older than the running binary — the agent contract may have drifted */\n | \"agents.stale\";\n"]}
@@ -70,7 +70,8 @@ export function maturityRollup(graded) {
70
70
  * C4 kinds that model people, and the tag marking an element as somebody
71
71
  * else's system. A person is never a service directory, and `#external` is
72
72
  * undocumented on purpose — every census and exemption over the fleet map
73
- * asks both questions.
73
+ * asks both questions, and `INFRA_KINDS` below is the third they now ask
74
+ * beside them.
74
75
  *
75
76
  * Extracted on the third strike: `commands/validate/checks/vocabulary.ts`,
76
77
  * `core/gate/partners.ts` and `core/verify/checklist.ts` each carried a copy.
@@ -88,6 +89,45 @@ export function maturityRollup(graded) {
88
89
  * in one place anyway — that list IS what the kind-tag check is about.
89
90
  */
90
91
  export const ACTOR_KINDS = new Set(["person", "actor", "user"]);
92
+ /**
93
+ * C4 kinds that model INFRASTRUCTURE the fleet runs rather than a system a team
94
+ * adopts. A person is never a `services/<id>/`, and neither is a datastore: a
95
+ * Postgres, a Kafka topic or an S3 bucket has no spec.md to write, no OpenAPI to
96
+ * publish, no maturity rung to climb and nobody to adopt it.
97
+ *
98
+ * The third census category, beside `ACTOR_KINDS` and `EXTERNAL_TAG`, and it
99
+ * exists because the fleet had only two. A store the fleet OWNS and draws at
100
+ * fleet level was neither a person nor somebody else's system, so it earned
101
+ * `landscape.service-undocumented` ("bind it with `metadata { service }`, or tag
102
+ * it `#external`") — two remedies, neither of them true of it — and
103
+ * `gate.partner-undocumented` on every service that reads it. `examples/docs`
104
+ * and this repo's own suite both tagged an owned Redis `#external` purely to buy
105
+ * that silence, which is a map that lies about ownership to satisfy a check.
106
+ *
107
+ * The LIMITATION is `ACTOR_KINDS`' exactly, and is stated here so the next
108
+ * reader does not have to measure it: the hook is a kind NAME, compared
109
+ * case-insensitively, so a fleet whose specification spells its kind `postgres`,
110
+ * `store` or `datastore` is not covered. That is deliberate rather than
111
+ * unfinished — a tag (`#infrastructure`) can be added later as a SECOND spelling
112
+ * without moving anything, exactly as `#external` sits beside `ACTOR_KINDS`
113
+ * today. An element of one of these kinds that stands for a real
114
+ * `services/<id>/` — a bound `database`, a title naming a directory — is a
115
+ * service and every reader here must say so first; the kind is the fallback,
116
+ * never the override.
117
+ */
118
+ export const INFRA_KINDS = new Set(["database", "queue", "topic", "cache", "bucket"]);
119
+ /**
120
+ * The one element kind read as a datastore, compared case-insensitively — the
121
+ * subject of `landscape.datastore-shared`.
122
+ *
123
+ * It was a bare string declared twice, in `commands/validate/fleet/map/consumers.ts`
124
+ * and `commands/validate/fleet/map/attest.ts`, each with a comment pointing at
125
+ * the other. Two literals free to drift about what a store IS are exactly the
126
+ * failure the trio above was extracted to stop: the peer census and the nested
127
+ * census would have graded different fleets the day one of them learned a second
128
+ * spelling.
129
+ */
130
+ export const DATASTORE_KIND = "database";
91
131
  export const EXTERNAL_TAG = "external";
92
132
  /** Tag marking ubiquitous infrastructure; the scaffolded fleet view excludes it. */
93
133
  export const PLATFORM_TAG = "platform";
@@ -129,6 +169,16 @@ export function landscapeEvidence(req) {
129
169
  const inbound = [];
130
170
  const outbound = [];
131
171
  for (const r of relationships) {
172
+ // An edge whose BOTH endpoints resolve to this service is its own internal
173
+ // wiring (`svc.web -> svc.db`): filed as inbound it made the service its own
174
+ // caller — `← <id> "reads"` in `loam context` (verification 2026-09-04) —
175
+ // and, through `apiExpected` below, told a service nobody calls that it owed
176
+ // an OpenAPI contract. `core/brief/landscape.ts` drops it for the same
177
+ // reason. It still TOUCHES the service, which is why the brief's `touched`
178
+ // and `landscape.service-isolated` read the relationships rather than these
179
+ // two lists.
180
+ if (svcOf(r.source) === id && svcOf(r.target) === id)
181
+ continue;
132
182
  const edge = { op: r.op ?? null, title: r.title ?? null };
133
183
  if (svcOf(r.target) === id)
134
184
  inbound.push({ service: svcOf(r.source), ...edge });
@@ -1 +1 @@
1
- {"version":3,"file":"maturity.js","sourceRoot":"","sources":["../../../src/core/vocabulary/maturity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The adoption-maturity ladder: one monotone word for how far a service's\n * documentation has got, and what stands between it and the next rung.\n *\n * This lived inside `commands/list.ts` while `list` was the only caller. It\n * moved here when `explore` needed the same answer, because the alternative is\n * the failure `core/kernel/ids/service.ts` already records for the service-id grammar: a\n * second, subtly different copy, so two commands grade the same directory\n * differently and nobody can tell which one is lying. The rung is a fleet dial\n * — `list` prints it per service and in a rollup, `explore` prints it for a\n * service somebody is about to build against — and a dial with two readings is\n * not a dial.\n */\nimport type { ServiceEntry } from \"../repo/entries.js\";\n\n/** The ladder, in order. Each rung stands on every rung below it. */\nexport const MATURITY_LADDER = [\"empty\", \"partial\", \"documented\", \"sourced\", \"vouched\"] as const;\nexport type Maturity = (typeof MATURITY_LADDER)[number];\n\n/**\n * A service entry plus the two facts the directory enumeration cannot know on\n * its own. Both change what a rung MEANS, and both are fleet-level: they need\n * the landscape, not `services/<id>/`.\n */\nexport interface MaturityInput {\n entry: ServiceEntry;\n /** `arch.spec.md` — the architecture-obligations axis, absent from ServiceEntry.has. */\n archSpec: boolean;\n /**\n * Does anything in the fleet call an operation on this service? True unless\n * the landscape PROVES otherwise, exactly as `validate`'s no-openapi grace\n * reads it: a missing or unparseable landscape proves nothing.\n */\n apiExpected: boolean;\n}\n\n/**\n * How far a service's documentation has got. Derived from artifact PRESENCE and\n * provenance state only — the data an enumeration already holds — never from\n * what the artifacts say. COMPLETENESS of adopted docs is explicitly on the\n * unchecked list (brief/unchecked.ts): a service with one endpoint documented out of\n * thirty climbs this ladder exactly as fast as a thorough one, which is why no\n * rung is called \"adopted\".\n *\n * empty services/<id>/ exists, no artifact is in it\n * partial some artifacts, but not the required set\n * documented the artifacts the adopt brief marks required are present\n * sourced the living spec declares `sources` — something ties it to code\n * vouched status: verified with a sources_digest behind it — a person\n * stamped it. `verified` with no digest is a claim with nothing\n * behind it and stays below this rung.\n *\n * `openapi.yaml` is required ONLY where an API is expected. The rung used to\n * demand it of everything, which permanently pinned every worker, cron and\n * consumer in the fleet at `partial` — services that are fully documented and\n * vouched for, reported as unfinished forever, leaving the rollup permanently\n * below its true documentation state. The evidence is the same one\n * `validate` uses to keep `service.no-openapi` quiet, so a service cannot be\n * green in one command and unfinished in the other.\n */\nexport function serviceMaturity(v: MaturityInput): Maturity {\n const s = v.entry;\n if (!Object.values(s.has).some(Boolean) && !v.archSpec && s.adrs === 0) return \"empty\";\n if (!(s.has.model && s.has.spec && (!v.apiExpected || s.has.openapi))) return \"partial\";\n if (!s.sources.declared) return \"documented\";\n if (!(s.status === \"verified\" && s.sources.stamped)) return \"sourced\";\n return \"vouched\";\n}\n\n/**\n * What stands between this service and `vouched`, named as the thing to go and\n * do. One rung's worth at a time: telling somebody at `empty` that they also\n * need a vouch is noise, and the vouch is not even possible yet.\n */\nexport function maturityGaps(v: MaturityInput): string[] {\n const s = v.entry;\n const files = [\n ...(s.has.model ? [] : [\"model.likec4\"]),\n ...(s.has.spec ? [] : [\"spec.md\"]),\n ...(v.apiExpected && !s.has.openapi ? [\"openapi.yaml\"] : []),\n ];\n if (files.length > 0) return files;\n if (!s.sources.declared) return [\"sources: in the spec.md frontmatter\"];\n if (!(s.status === \"verified\" && s.sources.stamped)) return [`\\`loam vouch --service ${s.id}\\``];\n return [];\n}\n\n/** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */\nexport function maturityRollup(graded: readonly { maturity: Maturity }[]): Record<Maturity, number> {\n const out = Object.fromEntries(MATURITY_LADDER.map((m) => [m, 0])) as Record<Maturity, number>;\n for (const v of graded) out[v.maturity] += 1;\n return out;\n}\n\n/* ------------------------------------------------------------------ */\n/* What the fleet map proves about one service */\n/* ------------------------------------------------------------------ */\n\n/**\n * C4 kinds that model people, and the tag marking an element as somebody\n * else's system. A person is never a service directory, and `#external` is\n * undocumented on purpose — every census and exemption over the fleet map\n * asks both questions.\n *\n * Extracted on the third strike: `commands/validate/checks/vocabulary.ts`,\n * `core/gate/partners.ts` and `core/verify/checklist.ts` each carried a copy.\n * Their real home is `core/c4/likec4.ts` beside the model they describe, but\n * that file sits against the 300-line limit and its package holds five files\n * — so the trio lives here, beside `landscapeEvidence`, which reads the same\n * map for the same per-service questions.\n *\n * `PLATFORM_TAG` joined them from `commands/validate/checks/fleet-shape.ts`,\n * where it was declared beside its one reader. It moved for the rule above the\n * others came for: `fleet/kind-tags.ts` needs to know which tag names loam\n * grades on, and reaching into fleet-shape.ts for a five-character constant\n * would import a module that reads permissions, capabilities and every\n * service's requirements to get it. The two tags loam gives meaning to belong\n * in one place anyway — that list IS what the kind-tag check is about.\n */\nexport const ACTOR_KINDS = new Set([\"person\", \"actor\", \"user\"]);\nexport const EXTERNAL_TAG = \"external\";\n/** Tag marking ubiquitous infrastructure; the scaffolded fleet view excludes it. */\nexport const PLATFORM_TAG = \"platform\";\n\n/**\n * The tags loam GRADES on, as opposed to the ones it merely carries.\n *\n * A tag in this list changes what loam concludes about a fleet: `#external`\n * exempts an element from the landscape↔services reconciliation entirely, and\n * `#platform` silences the hub warning. Every other tag on an element is the\n * author's own vocabulary and loam does not read it.\n *\n * The list exists because since LikeC4 1.59.0 a tag can be declared on a KIND\n * — `specification { element softwareSystem { #external } }` — and LikeC4\n * applies it to every element of that kind. A kind-wide declaration of a tag on\n * this list switches loam's grading off for the whole fleet at once, which is\n * what `kindTagFindings` refuses. A tag NOT on this list is safe to declare\n * kind-wide, so the check must ask exactly this question and no broader one.\n */\nexport const GRADED_TAGS: readonly string[] = [EXTERNAL_TAG, PLATFORM_TAG];\n\n/** One end of a call the fleet map draws, as every per-service view spells it. */\nexport interface LandscapeEdge {\n service: string;\n op: string | null;\n title: string | null;\n}\n\n/**\n * The slice of a parsed relationship the partition reads. Structural on\n * purpose — the smallest parameter type the derivation needs — so this module\n * stays a leaf: `core/c4/likec4.ts`'s `Rel` satisfies it without an import\n * edge from the vocabulary package into the parser.\n */\nexport interface EdgeSource {\n source: string;\n target: string;\n op?: string;\n title?: string;\n}\n\nexport interface LandscapeEvidenceRequest {\n /** The service being asked about. */\n id: string;\n /** Whether the landscape parsed — what makes its silence evidence rather than absence. */\n parses: boolean;\n /** The parsed relationships; empty when the landscape is absent or unparseable. */\n relationships: readonly EdgeSource[];\n /** Every element id in the map, for the `modelled` probe. */\n elementIds: readonly string[];\n /** The shared resolver (`core/c4/resolve/service.ts` `serviceResolver`), widened to plain strings. */\n svcOf: (id: string) => string;\n}\n\nexport interface LandscapeEvidence {\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n /** Whether anything in the fleet map resolves to this service. */\n modelled: boolean;\n /** The `apiExpected` input the ladder above consumes. */\n apiExpected: boolean;\n}\n\n/**\n * The fleet map's evidence about one service: the edges into and out of it,\n * whether the map draws it at all, and whether an API is expected of it.\n *\n * One spelling, because it had grown three. `explore`'s describe and the\n * context pack's living slice partitioned the same relationships with the\n * same positive-evidence rule line for line — an inbound edge carrying an\n * operation proves somebody calls the service; a landscape that is absent or\n * does not parse proves NOTHING about who calls it, so the contract is still\n * owed — and a rule spelled twice is how the same service grades `partial` in\n * one command and `documented` in another. `list`'s serviceViews\n * (`commands/list/views.ts`) derives the same rule fleet-wide in ONE pass —\n * a single `called` set for every service, not a partition per service —\n * so that spelling deliberately stays there, cross-referenced from both\n * sides: calling this per service inside a fleet loop would re-walk the\n * whole relationship list once per service.\n */\nexport function landscapeEvidence(req: LandscapeEvidenceRequest): LandscapeEvidence {\n const { id, parses, relationships, svcOf } = req;\n const inbound: LandscapeEdge[] = [];\n const outbound: LandscapeEdge[] = [];\n for (const r of relationships) {\n const edge = { op: r.op ?? null, title: r.title ?? null };\n if (svcOf(r.target) === id) inbound.push({ service: svcOf(r.source), ...edge });\n else if (svcOf(r.source) === id) outbound.push({ service: svcOf(r.target), ...edge });\n }\n return {\n inbound,\n outbound,\n modelled: req.elementIds.some((e) => svcOf(e) === id),\n apiExpected: !parses || inbound.some((e) => e.op !== null),\n };\n}\n"]}
1
+ {"version":3,"file":"maturity.js","sourceRoot":"","sources":["../../../src/core/vocabulary/maturity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The adoption-maturity ladder: one monotone word for how far a service's\n * documentation has got, and what stands between it and the next rung.\n *\n * This lived inside `commands/list.ts` while `list` was the only caller. It\n * moved here when `explore` needed the same answer, because the alternative is\n * the failure `core/kernel/ids/service.ts` already records for the service-id grammar: a\n * second, subtly different copy, so two commands grade the same directory\n * differently and nobody can tell which one is lying. The rung is a fleet dial\n * — `list` prints it per service and in a rollup, `explore` prints it for a\n * service somebody is about to build against — and a dial with two readings is\n * not a dial.\n */\nimport type { ServiceEntry } from \"../repo/entries.js\";\n\n/** The ladder, in order. Each rung stands on every rung below it. */\nexport const MATURITY_LADDER = [\"empty\", \"partial\", \"documented\", \"sourced\", \"vouched\"] as const;\nexport type Maturity = (typeof MATURITY_LADDER)[number];\n\n/**\n * A service entry plus the two facts the directory enumeration cannot know on\n * its own. Both change what a rung MEANS, and both are fleet-level: they need\n * the landscape, not `services/<id>/`.\n */\nexport interface MaturityInput {\n entry: ServiceEntry;\n /** `arch.spec.md` — the architecture-obligations axis, absent from ServiceEntry.has. */\n archSpec: boolean;\n /**\n * Does anything in the fleet call an operation on this service? True unless\n * the landscape PROVES otherwise, exactly as `validate`'s no-openapi grace\n * reads it: a missing or unparseable landscape proves nothing.\n */\n apiExpected: boolean;\n}\n\n/**\n * How far a service's documentation has got. Derived from artifact PRESENCE and\n * provenance state only — the data an enumeration already holds — never from\n * what the artifacts say. COMPLETENESS of adopted docs is explicitly on the\n * unchecked list (brief/unchecked.ts): a service with one endpoint documented out of\n * thirty climbs this ladder exactly as fast as a thorough one, which is why no\n * rung is called \"adopted\".\n *\n * empty services/<id>/ exists, no artifact is in it\n * partial some artifacts, but not the required set\n * documented the artifacts the adopt brief marks required are present\n * sourced the living spec declares `sources` — something ties it to code\n * vouched status: verified with a sources_digest behind it — a person\n * stamped it. `verified` with no digest is a claim with nothing\n * behind it and stays below this rung.\n *\n * `openapi.yaml` is required ONLY where an API is expected. The rung used to\n * demand it of everything, which permanently pinned every worker, cron and\n * consumer in the fleet at `partial` — services that are fully documented and\n * vouched for, reported as unfinished forever, leaving the rollup permanently\n * below its true documentation state. The evidence is the same one\n * `validate` uses to keep `service.no-openapi` quiet, so a service cannot be\n * green in one command and unfinished in the other.\n */\nexport function serviceMaturity(v: MaturityInput): Maturity {\n const s = v.entry;\n if (!Object.values(s.has).some(Boolean) && !v.archSpec && s.adrs === 0) return \"empty\";\n if (!(s.has.model && s.has.spec && (!v.apiExpected || s.has.openapi))) return \"partial\";\n if (!s.sources.declared) return \"documented\";\n if (!(s.status === \"verified\" && s.sources.stamped)) return \"sourced\";\n return \"vouched\";\n}\n\n/**\n * What stands between this service and `vouched`, named as the thing to go and\n * do. One rung's worth at a time: telling somebody at `empty` that they also\n * need a vouch is noise, and the vouch is not even possible yet.\n */\nexport function maturityGaps(v: MaturityInput): string[] {\n const s = v.entry;\n const files = [\n ...(s.has.model ? [] : [\"model.likec4\"]),\n ...(s.has.spec ? [] : [\"spec.md\"]),\n ...(v.apiExpected && !s.has.openapi ? [\"openapi.yaml\"] : []),\n ];\n if (files.length > 0) return files;\n if (!s.sources.declared) return [\"sources: in the spec.md frontmatter\"];\n if (!(s.status === \"verified\" && s.sources.stamped)) return [`\\`loam vouch --service ${s.id}\\``];\n return [];\n}\n\n/** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */\nexport function maturityRollup(graded: readonly { maturity: Maturity }[]): Record<Maturity, number> {\n const out = Object.fromEntries(MATURITY_LADDER.map((m) => [m, 0])) as Record<Maturity, number>;\n for (const v of graded) out[v.maturity] += 1;\n return out;\n}\n\n/* ------------------------------------------------------------------ */\n/* What the fleet map proves about one service */\n/* ------------------------------------------------------------------ */\n\n/**\n * C4 kinds that model people, and the tag marking an element as somebody\n * else's system. A person is never a service directory, and `#external` is\n * undocumented on purpose — every census and exemption over the fleet map\n * asks both questions, and `INFRA_KINDS` below is the third they now ask\n * beside them.\n *\n * Extracted on the third strike: `commands/validate/checks/vocabulary.ts`,\n * `core/gate/partners.ts` and `core/verify/checklist.ts` each carried a copy.\n * Their real home is `core/c4/likec4.ts` beside the model they describe, but\n * that file sits against the 300-line limit and its package holds five files\n * — so the trio lives here, beside `landscapeEvidence`, which reads the same\n * map for the same per-service questions.\n *\n * `PLATFORM_TAG` joined them from `commands/validate/checks/fleet-shape.ts`,\n * where it was declared beside its one reader. It moved for the rule above the\n * others came for: `fleet/kind-tags.ts` needs to know which tag names loam\n * grades on, and reaching into fleet-shape.ts for a five-character constant\n * would import a module that reads permissions, capabilities and every\n * service's requirements to get it. The two tags loam gives meaning to belong\n * in one place anyway — that list IS what the kind-tag check is about.\n */\nexport const ACTOR_KINDS = new Set([\"person\", \"actor\", \"user\"]);\n\n/**\n * C4 kinds that model INFRASTRUCTURE the fleet runs rather than a system a team\n * adopts. A person is never a `services/<id>/`, and neither is a datastore: a\n * Postgres, a Kafka topic or an S3 bucket has no spec.md to write, no OpenAPI to\n * publish, no maturity rung to climb and nobody to adopt it.\n *\n * The third census category, beside `ACTOR_KINDS` and `EXTERNAL_TAG`, and it\n * exists because the fleet had only two. A store the fleet OWNS and draws at\n * fleet level was neither a person nor somebody else's system, so it earned\n * `landscape.service-undocumented` (\"bind it with `metadata { service }`, or tag\n * it `#external`\") — two remedies, neither of them true of it — and\n * `gate.partner-undocumented` on every service that reads it. `examples/docs`\n * and this repo's own suite both tagged an owned Redis `#external` purely to buy\n * that silence, which is a map that lies about ownership to satisfy a check.\n *\n * The LIMITATION is `ACTOR_KINDS`' exactly, and is stated here so the next\n * reader does not have to measure it: the hook is a kind NAME, compared\n * case-insensitively, so a fleet whose specification spells its kind `postgres`,\n * `store` or `datastore` is not covered. That is deliberate rather than\n * unfinished — a tag (`#infrastructure`) can be added later as a SECOND spelling\n * without moving anything, exactly as `#external` sits beside `ACTOR_KINDS`\n * today. An element of one of these kinds that stands for a real\n * `services/<id>/` — a bound `database`, a title naming a directory — is a\n * service and every reader here must say so first; the kind is the fallback,\n * never the override.\n */\nexport const INFRA_KINDS = new Set([\"database\", \"queue\", \"topic\", \"cache\", \"bucket\"]);\n\n/**\n * The one element kind read as a datastore, compared case-insensitively — the\n * subject of `landscape.datastore-shared`.\n *\n * It was a bare string declared twice, in `commands/validate/fleet/map/consumers.ts`\n * and `commands/validate/fleet/map/attest.ts`, each with a comment pointing at\n * the other. Two literals free to drift about what a store IS are exactly the\n * failure the trio above was extracted to stop: the peer census and the nested\n * census would have graded different fleets the day one of them learned a second\n * spelling.\n */\nexport const DATASTORE_KIND = \"database\";\nexport const EXTERNAL_TAG = \"external\";\n/** Tag marking ubiquitous infrastructure; the scaffolded fleet view excludes it. */\nexport const PLATFORM_TAG = \"platform\";\n\n/**\n * The tags loam GRADES on, as opposed to the ones it merely carries.\n *\n * A tag in this list changes what loam concludes about a fleet: `#external`\n * exempts an element from the landscape↔services reconciliation entirely, and\n * `#platform` silences the hub warning. Every other tag on an element is the\n * author's own vocabulary and loam does not read it.\n *\n * The list exists because since LikeC4 1.59.0 a tag can be declared on a KIND\n * — `specification { element softwareSystem { #external } }` — and LikeC4\n * applies it to every element of that kind. A kind-wide declaration of a tag on\n * this list switches loam's grading off for the whole fleet at once, which is\n * what `kindTagFindings` refuses. A tag NOT on this list is safe to declare\n * kind-wide, so the check must ask exactly this question and no broader one.\n */\nexport const GRADED_TAGS: readonly string[] = [EXTERNAL_TAG, PLATFORM_TAG];\n\n/** One end of a call the fleet map draws, as every per-service view spells it. */\nexport interface LandscapeEdge {\n service: string;\n op: string | null;\n title: string | null;\n}\n\n/**\n * The slice of a parsed relationship the partition reads. Structural on\n * purpose — the smallest parameter type the derivation needs — so this module\n * stays a leaf: `core/c4/likec4.ts`'s `Rel` satisfies it without an import\n * edge from the vocabulary package into the parser.\n */\nexport interface EdgeSource {\n source: string;\n target: string;\n op?: string;\n title?: string;\n}\n\nexport interface LandscapeEvidenceRequest {\n /** The service being asked about. */\n id: string;\n /** Whether the landscape parsed — what makes its silence evidence rather than absence. */\n parses: boolean;\n /** The parsed relationships; empty when the landscape is absent or unparseable. */\n relationships: readonly EdgeSource[];\n /** Every element id in the map, for the `modelled` probe. */\n elementIds: readonly string[];\n /** The shared resolver (`core/c4/resolve/service.ts` `serviceResolver`), widened to plain strings. */\n svcOf: (id: string) => string;\n}\n\nexport interface LandscapeEvidence {\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n /** Whether anything in the fleet map resolves to this service. */\n modelled: boolean;\n /** The `apiExpected` input the ladder above consumes. */\n apiExpected: boolean;\n}\n\n/**\n * The fleet map's evidence about one service: the edges into and out of it,\n * whether the map draws it at all, and whether an API is expected of it.\n *\n * One spelling, because it had grown three. `explore`'s describe and the\n * context pack's living slice partitioned the same relationships with the\n * same positive-evidence rule line for line — an inbound edge carrying an\n * operation proves somebody calls the service; a landscape that is absent or\n * does not parse proves NOTHING about who calls it, so the contract is still\n * owed — and a rule spelled twice is how the same service grades `partial` in\n * one command and `documented` in another. `list`'s serviceViews\n * (`commands/list/views.ts`) derives the same rule fleet-wide in ONE pass —\n * a single `called` set for every service, not a partition per service —\n * so that spelling deliberately stays there, cross-referenced from both\n * sides: calling this per service inside a fleet loop would re-walk the\n * whole relationship list once per service.\n */\nexport function landscapeEvidence(req: LandscapeEvidenceRequest): LandscapeEvidence {\n const { id, parses, relationships, svcOf } = req;\n const inbound: LandscapeEdge[] = [];\n const outbound: LandscapeEdge[] = [];\n for (const r of relationships) {\n // An edge whose BOTH endpoints resolve to this service is its own internal\n // wiring (`svc.web -> svc.db`): filed as inbound it made the service its own\n // caller — `← <id> \"reads\"` in `loam context` (verification 2026-09-04) —\n // and, through `apiExpected` below, told a service nobody calls that it owed\n // an OpenAPI contract. `core/brief/landscape.ts` drops it for the same\n // reason. It still TOUCHES the service, which is why the brief's `touched`\n // and `landscape.service-isolated` read the relationships rather than these\n // two lists.\n if (svcOf(r.source) === id && svcOf(r.target) === id) continue;\n const edge = { op: r.op ?? null, title: r.title ?? null };\n if (svcOf(r.target) === id) inbound.push({ service: svcOf(r.source), ...edge });\n else if (svcOf(r.source) === id) outbound.push({ service: svcOf(r.target), ...edge });\n }\n return {\n inbound,\n outbound,\n modelled: req.elementIds.some((e) => svcOf(e) === id),\n apiExpected: !parses || inbound.some((e) => e.op !== null),\n };\n}\n"]}
@@ -143,17 +143,39 @@ while the other three sit unfiled — the permanent, normal state a partially or
143
143
  in, counted by `loam list` and never a finding. Placement is not identity: both services' specs,
144
144
  contracts and digests are byte-identical to their unfiled days, every command addresses them by bare
145
145
  id, and `architecture/subsystems.likec4` — GENERATED by `loam subsystem sync`, one view per
146
- subsystem, one `include` per line — is the only file the grouping added. Edit the tree with
147
- `loam subsystem move|rename|rm` and the views file travels in the same transaction; edit it by hand
148
- and `validate --all` answers `subsystem.views-stale`. Each of the five services also carries a
149
- `likec4.config.json` — written by `loam subsystem sync`, create-only, never graded for content —
150
- because the root project excludes `services/**` and that file is what registers a service's
151
- `model.likec4` as a LikeC4 project of its own: `npx likec4 validate examples/docs` names six
152
- projects, `fleet` and the five services, and with more than one project it needs `--project fleet`
153
- (or a service's name) to grade one. (`order-service` and `payment-service` stay
146
+ subsystem, one `include` per line — is the only file the grouping added. Its sibling
147
+ `architecture/services.likec4` is the other generated views file and owes the grouping nothing: one
148
+ overview view per service the fleet map draws, filed or not, each declared `of` its service's
149
+ element so every other view drills into it, and each titled by its path so the two files fold into
150
+ one folder tree. Edit the tree with
151
+ `loam subsystem move|rename|rm` and both views files travel in the same transaction; edit either by
152
+ hand and `validate --all` answers `subsystem.views-stale`, naming the file that drifted. (`order-service` and `payment-service` stay
154
153
  unfiled deliberately: FEAT-088's committed version-2 snapshot restores them by literal path, and the
155
154
  `loam unarchive FEAT-088` walkthrough must keep working byte-for-byte.)
156
155
 
156
+ **All five models are EXTENDING models, and that is the shape this example teaches.** None of them
157
+ declares a `specification` block, none re-declares a partner, and none has a `likec4.config.json`
158
+ beside it. Each one says what is inside its service with `extend marketplace.<svc> { … }` on the
159
+ element `architecture/landscape.likec4` already binds to its directory, so `kafka`, `stripe`,
160
+ `notification-provider` and every peer service are declared exactly once — in the map — and no two
161
+ documents can disagree about their tags. The root `likec4.config.json` therefore excludes only
162
+ `**/node_modules/**` and `features/**`: `npx likec4 validate examples/docs` names ONE project,
163
+ `fleet`, holding the map and all five models, and needs no `--project` flag. The other shape — a
164
+ model that declares its own `specification` and is parsed alone, which is what every model here was
165
+ before — is still legal and still supported; SCHEMA.md's "Two shapes of a service model" has both,
166
+ the eight grades in its table — seven that came with the pair, plus the one it narrowed
167
+ (`service.likec4-config-missing`, now standalone-only) — and the
168
+ ordered recipe for migrating between them.
169
+
170
+ Two consequences are visible in the tree and worth reading for. Element ids carry their full path,
171
+ so `services/order-service/arch.spec.md` writes `Covers: marketplace.orderService.db` — the
172
+ container has no shorter name, because the model extends an element nested in the `marketplace`
173
+ group. And a cross-boundary call is written **twice** on purpose: container-level in the model
174
+ (`marketplace.notificationService.sender -> notificationProvider`) and service-level in the map
175
+ (`marketplace.notificationService -> notificationProvider`), because every fleet check — the spine,
176
+ `landscape.service-isolated`, the platform census — reads the map alone. ROADMAP.md keeps that
177
+ remainder written down rather than hidden.
178
+
157
179
  Plus one count rather than a finding: `sourcesUnverifiableFromHere: 4`. Four specs name paths in
158
180
  their own service repositories, and this is none of those repositories — the fleet gate reports the
159
181
  blind spot instead of resolving it. Running `loam validate --service <id>` inside each service repo
@@ -8,6 +8,16 @@
8
8
  // service needs a directory (warning). An element is tied to its directory by
9
9
  // `metadata { service '<id>' }`, or by its title when there is no binding.
10
10
  // Systems that are not ours carry #external and are exempt.
11
+ //
12
+ // This file is also the SPECIFICATION every service model borrows. Each
13
+ // `services/<…>/<svc>/model.likec4` here is an EXTENDING model: it declares no
14
+ // kinds and no partners, and says what is inside its service with
15
+ // `extend marketplace.<svc> { … }` on the element below that binds to its
16
+ // directory. The root `likec4.config.json` therefore does NOT exclude
17
+ // `services/**` — the map and every model are ONE LikeC4 project, so an element
18
+ // is declared exactly once and no two documents can disagree about its tags.
19
+ // SCHEMA.md, "Two shapes of a service model", has both shapes and the rule that
20
+ // picks between them; a kind or tag a model needs is added HERE, once.
11
21
  specification {
12
22
  // A grouping element — it stands for no service and owns no directory. The
13
23
  // service checks WALK the tree rather than reading only top-level elements:
@@ -20,7 +30,24 @@ specification {
20
30
  // Declared here because archive SPLICES a delta's additions verbatim: FEAT-101's
21
31
  // new system carries its `api` container along, and the kind has to be legal in
22
32
  // THIS document for the merged landscape to parse (archive refuses otherwise).
33
+ // Only a NEW service arrives this way — one that already has an extending model
34
+ // takes the addition into that model instead, and this file never grows an
35
+ // interior.
23
36
  element container
37
+ // Declared here for the SERVICE MODELS, and nothing in this file uses it.
38
+ // This fleet writes every `database` element inside the service that owns it,
39
+ // in a `services/<svc>/model.likec4` under `extend marketplace.<svc>` — a
40
+ // CHOICE this example makes, not a rule loam enforces. Drawing an owned store
41
+ // beside the services is equally first-class and earns no finding; what the
42
+ // placement decides is one thing, and it is a fact about LikeC4: a second
43
+ // service's edge naming `marketplace.<svc>.<store>` resolves only against a
44
+ // declaration the MAP holds, because a per-service project is `architecture/`
45
+ // plus one model. So a store a second consumer will name belongs on the map,
46
+ // and one nobody else reaches may live wherever the team finds it clearest.
47
+ // An extending model declares no kinds of its own, so a kind it needs and
48
+ // this block does not declare is ONE parse error on the model — which is what
49
+ // this line prevents.
50
+ element database
24
51
  element person
25
52
  // A topic is an element, and the kind carries #external rather than each
26
53
  // instance doing it: LikeC4 does NOT inherit tags, so a topic nested inside
@@ -137,6 +164,18 @@ model {
137
164
  description 'CRM of record — the fleet reads its events and writes none'
138
165
  }
139
166
 
167
+ // Declared HERE rather than in notification-service's model, and that is the
168
+ // rule an extending model turns on: a system a service reaches is the fleet's
169
+ // element, declared once, and a model that declared its own copy would be
170
+ // `c4.element-unowned` (an extending model may only add elements under the
171
+ // element that resolves to its own service). One consumer, so it never
172
+ // becomes `landscape.platform-candidate`; #external, so no
173
+ // services/notification-provider/ is asked for.
174
+ notificationProvider = softwareSystem 'notification-provider' {
175
+ #external
176
+ description 'Third-party email and SMS gateway — quota-limited, and outside this fleet'
177
+ }
178
+
140
179
  customer -> marketplace.checkoutWeb 'Uses'
141
180
 
142
181
  // The synchronous spine. Every one of these resolves against the TARGET's
@@ -185,6 +224,14 @@ model {
185
224
  // it raises no warning because its title does not claim to be a call into a
186
225
  // fleet service.
187
226
  marketplace.paymentService -> stripe 'Authorizes cards'
227
+ // The second half of the rule the extending shape keeps: the container that
228
+ // really makes the call is drawn in the service's own model
229
+ // (`marketplace.notificationService.sender -> notificationProvider`), and the
230
+ // SERVICE-level twin is drawn here, because every fleet check — the spine,
231
+ // `landscape.service-isolated`, the platform census — reads this file alone.
232
+ // The edge exists twice on purpose; SCHEMA.md says so where it says the map
233
+ // is the one document holding every cross-service edge.
234
+ marketplace.notificationService -> notificationProvider 'Sends email and SMS'
188
235
 
189
236
  // The event spine, the async mirror of `metadata { op }`: the name must be a
190
237
  // message the PRODUCER's own asyncapi.yaml declares an `action: send` for, or
@@ -0,0 +1,70 @@
1
+ // generated by loam — one overview view per service under services/, drawn from the fleet map.
2
+ // Do not edit: `loam subsystem sync` regenerates this file from the directories and the map,
3
+ // and `loam validate --all` reports `subsystem.views-stale` when it drifts.
4
+ // A navigation aid, not the fleet's architecture: author your own diagrams elsewhere.
5
+
6
+ views {
7
+ view service_checkout_2dweb of marketplace.checkoutWeb {
8
+ title 'checkout-web'
9
+ include *
10
+ include customer
11
+ include marketplace.orderService
12
+ include marketplace.paymentService
13
+ exclude * -> *
14
+ include -> marketplace.checkoutWeb
15
+ include marketplace.checkoutWeb ->
16
+ include marketplace.checkoutWeb.* -> marketplace.checkoutWeb.*
17
+ }
18
+
19
+ view service_identity_2dservice of marketplace.identityService {
20
+ title 'Platform / identity-service'
21
+ include *
22
+ include marketplace.orderService
23
+ include marketplace.paymentService
24
+ exclude * -> *
25
+ include -> marketplace.identityService
26
+ include marketplace.identityService ->
27
+ include marketplace.identityService.* -> marketplace.identityService.*
28
+ }
29
+
30
+ view service_notification_2dservice of marketplace.notificationService {
31
+ title 'Platform / notification-service'
32
+ include *
33
+ include kafka.crmEvents
34
+ include kafka.orderEvents
35
+ include kafka.paymentEvents
36
+ include notificationProvider
37
+ exclude * -> *
38
+ include -> marketplace.notificationService
39
+ include marketplace.notificationService ->
40
+ include marketplace.notificationService.* -> marketplace.notificationService.*
41
+ }
42
+
43
+ view service_order_2dservice of marketplace.orderService {
44
+ title 'order-service'
45
+ include *
46
+ include kafka.orderEvents
47
+ include kafka.paymentEvents
48
+ include marketplace.checkoutWeb
49
+ include marketplace.identityService
50
+ include marketplace.paymentService
51
+ exclude * -> *
52
+ include -> marketplace.orderService
53
+ include marketplace.orderService ->
54
+ include marketplace.orderService.* -> marketplace.orderService.*
55
+ }
56
+
57
+ view service_payment_2dservice of marketplace.paymentService {
58
+ title 'payment-service'
59
+ include *
60
+ include kafka.paymentEvents
61
+ include marketplace.checkoutWeb
62
+ include marketplace.identityService
63
+ include marketplace.orderService
64
+ include stripe
65
+ exclude * -> *
66
+ include -> marketplace.paymentService
67
+ include marketplace.paymentService ->
68
+ include marketplace.paymentService.* -> marketplace.paymentService.*
69
+ }
70
+ }
@@ -11,5 +11,8 @@ views {
11
11
  description 'Cross-cutting identity and messaging'
12
12
  include marketplace.identityService
13
13
  include marketplace.notificationService
14
+
15
+ include marketplace.identityService -> *, * -> marketplace.identityService
16
+ include marketplace.notificationService -> *, * -> marketplace.notificationService
14
17
  }
15
18
  }
@@ -14,7 +14,10 @@
14
14
  // read as ONE LikeC4 project, so the steps below resolve against the landscape's
15
15
  // elements even though this file declares none — which also means exactly one
16
16
  // file in this directory declares the `specification` block, and it is not this
17
- // one.
17
+ // one. (The renderer's root project is WIDER than what loam grades here: it also
18
+ // holds every `services/<svc>/model.likec4`, because each of them extends the
19
+ // map. A tag declared twice across that whole set is `c4.fleet-project-invalid`,
20
+ // which is why a tag belongs in the landscape's one block.)
18
21
  //
19
22
  // THE TAG MUST BE THE FIRST THING IN THE VIEW BODY. Written after `title` it is
20
23
  // five parse errors, none of which mentions tags. That is the mistake every
@@ -30,7 +33,7 @@
30
33
  // `usecase.step-unlinked` is met except the one that matters, which is that a
31
34
  // person is not a caller. Somebody clicking a button owes no operationId. Delete
32
35
  // that exemption from the checker and this file is what notices: the example
33
- // fleet's warning count moves from eight to nine and test/examples.test.ts
36
+ // fleet's warning count moves from ten to eleven and test/examples.test.ts
34
37
  // fails.
35
38
  //
36
39
  // The hop from checkout-web to order-service is DELIBERATELY NOT DRAWN, and its