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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1,26 +1,84 @@
1
1
  import { maskSource, matchBrace } from "../source-mask.js";
2
+ export function spliceSource(deltaText, decl, carry) {
3
+ const { featureId, indent: targetIndent, omit = [] } = carry;
4
+ const block = deltaText.slice(decl.start, decl.end);
5
+ const cuts = omit
6
+ .filter((r) => r.start >= decl.start && r.end <= decl.end)
7
+ .map((r) => wholeLines(block, r.start - decl.start, r.end - decl.start));
8
+ return reindent(stripFeatureTag(block, featureId, cuts), decl.indent, targetIndent);
9
+ }
2
10
  /**
3
- * A declaration's authored source, ready to land in the living landscape:
4
- * byte-verbatim except that the feature's own tag is stripped and the
5
- * indentation is rebased from where the block sat in the delta to where it
6
- * lands. A construct the strip leaves empty goes too — a line that held only
7
- * the tag disappears whole, and `x = kind 'y' { #FEAT-1 }` lands as
8
- * `x = kind 'y'`, not as a pair of empty braces.
11
+ * Widen a cut to the whole lines it sits on, when it has them to itself.
12
+ *
13
+ * A scanned declaration's span starts at its first character and ends one past
14
+ * its last brace, so cutting it verbatim would leave the leading indent and the
15
+ * trailing newline behind as a blank line — and the emptied-line rule below only
16
+ * fires on a removal a line contains. A cut sharing its line with other code is
17
+ * left exactly as asked.
9
18
  */
10
- export function spliceSource(deltaText, decl, featureId, targetIndent) {
11
- const block = deltaText.slice(decl.start, decl.end);
12
- return reindent(stripFeatureTag(block, featureId), decl.indent, targetIndent);
19
+ function wholeLines(block, start, end) {
20
+ const lineStart = block.lastIndexOf("\n", start - 1) + 1;
21
+ if (!/^[ \t]*$/.test(block.slice(lineStart, start)))
22
+ return [start, end];
23
+ const nl = block.indexOf("\n", end);
24
+ const to = nl === -1 ? block.length : nl;
25
+ if (!/^[ \t\r]*$/.test(block.slice(end, to)))
26
+ return [start, end];
27
+ return [lineStart, nl === -1 ? block.length : nl + 1];
28
+ }
29
+ /**
30
+ * Rewrite the line endings a merge INSERTED to the ones the document already
31
+ * uses.
32
+ *
33
+ * Splicing composes bytes from two files, and the delta's newlines are not
34
+ * necessarily the living document's: on a repository without `core.autocrlf`
35
+ * normalisation a CRLF landscape or model came back with a handful of bare-LF
36
+ * lines in the spliced region — two conventions in one file, noisy in an editor
37
+ * and in every diff (verification 2026-09-04, W-CRLF). One helper for both
38
+ * merges, because both had it.
39
+ *
40
+ * Only a document with ONE convention is corrected, and then in whichever
41
+ * direction it uses: a file that is already mixed has no convention to match, and
42
+ * guessing one would rewrite lines nobody touched.
43
+ */
44
+ export function matchLineEndings(document, merged) {
45
+ const crlf = (document.match(/\r\n/g) ?? []).length;
46
+ const bare = (document.match(/(?<!\r)\n/g) ?? []).length;
47
+ if (crlf > 0 && bare === 0)
48
+ return merged.replace(/(?<!\r)\n/g, "\r\n");
49
+ if (bare > 0 && crlf === 0)
50
+ return merged.replace(/\r\n/g, "\n");
51
+ return merged;
13
52
  }
14
- function stripFeatureTag(block, featureId) {
53
+ function stripFeatureTag(block, featureId, cuts) {
15
54
  const { code } = maskSource(block);
16
55
  const esc = featureId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17
- const removals = [];
56
+ const removals = cuts.map(([s, e]) => [s, e]);
18
57
  for (const m of block.matchAll(new RegExp(`#${esc}(?![\\w-])`, "g"))) {
19
58
  // Only tags in CODE count — a description quoting "#FEAT-1" is content.
20
59
  if (code.slice(m.index, m.index + m[0].length) !== m[0])
21
60
  continue;
22
61
  let s = m.index;
23
62
  let e = s + m[0].length;
63
+ // A tag list's separators go with the tag they separate. LikeC4 reads a
64
+ // list as `#a #b, #c;` — one or more tags first, then `,` groups, then an
65
+ // optional `;` — so `#FEAT-1, #x` stripped to `, #x` and `#FEAT-1;` to a
66
+ // bare `;`, and the parse net refused a delta LikeC4 accepts. The comma
67
+ // after the tag goes with it, else the comma before it; the `;` only when
68
+ // no tag is left in front of it.
69
+ const commaAfter = /^[ \t]*,[ \t]*/.exec(code.slice(e));
70
+ const commaBefore = /,[ \t]*$/.exec(code.slice(0, s));
71
+ const semi = /^[ \t]*;/.exec(code.slice(e));
72
+ if (commaAfter !== null) {
73
+ removals.push([s, e + commaAfter[0].length]);
74
+ continue;
75
+ }
76
+ if (commaBefore !== null) {
77
+ removals.push([commaBefore.index, e]);
78
+ continue;
79
+ }
80
+ if (semi !== null && !/#[\w-]+\s*$/.test(code.slice(0, s)))
81
+ e += semi[0].length;
24
82
  // Take the whitespace on ONE side with the token — trailing first, so
25
83
  // `#FEAT-1 #critical` leaves `#critical` at its own indent, and a token at
26
84
  // the end of a line does not leave a trailing space behind.
@@ -88,13 +146,21 @@ function applyRemovals(text, removals) {
88
146
  * Rebase a block's indentation: the `base` its lines carried in the delta
89
147
  * becomes `target`. The first line arrives with no leading whitespace (the
90
148
  * scanner's span starts at the declaration itself), so it only gains `target`.
149
+ *
150
+ * A CRLF delta's blank line is `"\r"`, not `""`, so splitting on `\n` alone used
151
+ * to hand a non-empty body to the rule below and every blank line inside a
152
+ * spliced block came out carrying the target indent as trailing whitespace. The
153
+ * carriage return is set aside and put back, which leaves the line's own ending
154
+ * exactly as authored for `matchLineEndings` to reconcile.
91
155
  */
92
156
  function reindent(block, base, target) {
93
157
  return block
94
158
  .split("\n")
95
159
  .map((line, k) => {
96
- const body = k === 0 ? line : line.startsWith(base) ? line.slice(base.length) : line;
97
- return body.length === 0 ? "" : target + body;
160
+ const cr = line.endsWith("\r");
161
+ const raw = cr ? line.slice(0, -1) : line;
162
+ const body = k === 0 ? raw : raw.startsWith(base) ? raw.slice(base.length) : raw;
163
+ return (body.length === 0 ? "" : target + body) + (cr ? "\r" : "");
98
164
  })
99
165
  .join("\n");
100
166
  }
@@ -1 +1 @@
1
- {"version":3,"file":"authored-source.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/authored-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { maskSource, matchBrace } from \"../source-mask.js\";\n\n/**\n * A declaration's authored source, ready to land in the living landscape:\n * byte-verbatim except that the feature's own tag is stripped and the\n * indentation is rebased from where the block sat in the delta to where it\n * lands. A construct the strip leaves empty goes too — a line that held only\n * the tag disappears whole, and `x = kind 'y' { #FEAT-1 }` lands as\n * `x = kind 'y'`, not as a pair of empty braces.\n */\nexport function spliceSource(\n deltaText: string,\n decl: { start: number; end: number; indent: string },\n featureId: string,\n targetIndent: string,\n): string {\n const block = deltaText.slice(decl.start, decl.end);\n return reindent(stripFeatureTag(block, featureId), decl.indent, targetIndent);\n}\n\nfunction stripFeatureTag(block: string, featureId: string): string {\n const { code } = maskSource(block);\n const esc = featureId.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const removals: Array<[number, number]> = [];\n for (const m of block.matchAll(new RegExp(`#${esc}(?![\\\\w-])`, \"g\"))) {\n // Only tags in CODE count — a description quoting \"#FEAT-1\" is content.\n if (code.slice(m.index, m.index + m[0].length) !== m[0]) continue;\n let s = m.index;\n let e = s + m[0].length;\n // Take the whitespace on ONE side with the token — trailing first, so\n // `#FEAT-1 #critical` leaves `#critical` at its own indent, and a token at\n // the end of a line does not leave a trailing space behind.\n if (block[e] === \" \" || block[e] === \"\\t\") {\n while (block[e] === \" \" || block[e] === \"\\t\") e += 1;\n } else {\n while (s > 0 && (block[s - 1] === \" \" || block[s - 1] === \"\\t\")) s -= 1;\n }\n removals.push([s, e]);\n }\n if (removals.length === 0) return block;\n\n // A line the strip emptied disappears whole, newline included.\n for (let lineStart = 0; lineStart < block.length; ) {\n const nl = block.indexOf(\"\\n\", lineStart);\n const lineEnd = nl === -1 ? block.length : nl + 1;\n if (\n removals.some(([s, e]) => s >= lineStart && e <= lineEnd) &&\n /^\\s*$/.test(residual(block, lineStart, nl === -1 ? block.length : nl, removals))\n ) {\n removals.push([lineStart, lineEnd]);\n }\n lineStart = lineEnd;\n }\n\n // A body the strip emptied goes too, braces and all — but only one the strip\n // emptied: braces that were authored empty are authored bytes and survive.\n for (let at = code.indexOf(\"{\"); at !== -1; at = code.indexOf(\"{\", at + 1)) {\n const closeAt = matchBrace(code, at);\n if (closeAt === -1) continue;\n if (!removals.some(([s, e]) => s > at && e <= closeAt)) continue;\n if (!/^\\s*$/.test(residual(block, at + 1, closeAt, removals))) continue;\n let s = at;\n while (s > 0 && /\\s/.test(block[s - 1]!)) s -= 1;\n removals.push([s, closeAt + 1]);\n }\n return applyRemovals(block, removals);\n}\n\n/** The text of [from, to) with the removal ranges cut out. */\nfunction residual(text: string, from: number, to: number, removals: Array<[number, number]>): string {\n let out = \"\";\n for (let i = from; i < to; i += 1) {\n if (!removals.some(([s, e]) => i >= s && i < e)) out += text[i];\n }\n return out;\n}\n\nfunction applyRemovals(text: string, removals: Array<[number, number]>): string {\n const sorted = [...removals].sort((a, b) => a[0] - b[0] || b[1] - a[1]);\n let out = \"\";\n let i = 0;\n for (const [s, e] of sorted) {\n if (s > i) out += text.slice(i, s);\n if (e > i) i = e;\n }\n return out + text.slice(i);\n}\n\n/**\n * Rebase a block's indentation: the `base` its lines carried in the delta\n * becomes `target`. The first line arrives with no leading whitespace (the\n * scanner's span starts at the declaration itself), so it only gains `target`.\n */\nfunction reindent(block: string, base: string, target: string): string {\n return block\n .split(\"\\n\")\n .map((line, k) => {\n const body = k === 0 ? line : line.startsWith(base) ? line.slice(base.length) : line;\n return body.length === 0 ? \"\" : target + body;\n })\n .join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"authored-source.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/authored-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { maskSource, matchBrace } from \"../source-mask.js\";\n\n/** A half-open byte range of `deltaText`, as the source scan reports one. */\nexport interface SourceRange {\n start: number;\n end: number;\n}\n\n/**\n * A declaration's authored source, ready to land in the living landscape:\n * byte-verbatim except that the feature's own tag is stripped (with the `,` or\n * `;` that set it apart in a tag list, which would otherwise be left stray) and\n * the indentation is rebased from where the block sat in the delta to where it\n * lands. A construct the strip leaves empty goes too — a line that held only\n * the tag disappears whole, and `x = kind 'y' { #FEAT-1 }` lands as\n * `x = kind 'y'`, not as a pair of empty braces.\n *\n * `omit` cuts named ranges of the delta out on the way over, and it exists for\n * one caller: a NEW service element whose interior belongs to that service's\n * extending model. The children come along in the parent's bytes — they carry no\n * feature tag of their own, because LikeC4 does not inherit tags, so nothing in\n * the addition set can see them — and the map must take the box without them or\n * the model can never declare them (verification 2026-09-04, refutation of E1).\n * The ranges join the tag's removals, so a body they empty collapses the same\n * way `#FEAT-1`-only braces do.\n */\n/** How a declaration is carried over: whose tag to drop, where it lands, and what to leave behind. */\nexport interface Carry {\n featureId: string;\n /** The indent of the position the block lands at. */\n indent: string;\n /** Delta ranges cut out on the way over — see the banner; `[]` when nothing rides separately. */\n omit?: readonly SourceRange[];\n}\n\nexport function spliceSource(\n deltaText: string,\n decl: { start: number; end: number; indent: string },\n carry: Carry,\n): string {\n const { featureId, indent: targetIndent, omit = [] } = carry;\n const block = deltaText.slice(decl.start, decl.end);\n const cuts = omit\n .filter((r) => r.start >= decl.start && r.end <= decl.end)\n .map((r) => wholeLines(block, r.start - decl.start, r.end - decl.start));\n return reindent(stripFeatureTag(block, featureId, cuts), decl.indent, targetIndent);\n}\n\n/**\n * Widen a cut to the whole lines it sits on, when it has them to itself.\n *\n * A scanned declaration's span starts at its first character and ends one past\n * its last brace, so cutting it verbatim would leave the leading indent and the\n * trailing newline behind as a blank line — and the emptied-line rule below only\n * fires on a removal a line contains. A cut sharing its line with other code is\n * left exactly as asked.\n */\nfunction wholeLines(block: string, start: number, end: number): [number, number] {\n const lineStart = block.lastIndexOf(\"\\n\", start - 1) + 1;\n if (!/^[ \\t]*$/.test(block.slice(lineStart, start))) return [start, end];\n const nl = block.indexOf(\"\\n\", end);\n const to = nl === -1 ? block.length : nl;\n if (!/^[ \\t\\r]*$/.test(block.slice(end, to))) return [start, end];\n return [lineStart, nl === -1 ? block.length : nl + 1];\n}\n\n/**\n * Rewrite the line endings a merge INSERTED to the ones the document already\n * uses.\n *\n * Splicing composes bytes from two files, and the delta's newlines are not\n * necessarily the living document's: on a repository without `core.autocrlf`\n * normalisation a CRLF landscape or model came back with a handful of bare-LF\n * lines in the spliced region — two conventions in one file, noisy in an editor\n * and in every diff (verification 2026-09-04, W-CRLF). One helper for both\n * merges, because both had it.\n *\n * Only a document with ONE convention is corrected, and then in whichever\n * direction it uses: a file that is already mixed has no convention to match, and\n * guessing one would rewrite lines nobody touched.\n */\nexport function matchLineEndings(document: string, merged: string): string {\n const crlf = (document.match(/\\r\\n/g) ?? []).length;\n const bare = (document.match(/(?<!\\r)\\n/g) ?? []).length;\n if (crlf > 0 && bare === 0) return merged.replace(/(?<!\\r)\\n/g, \"\\r\\n\");\n if (bare > 0 && crlf === 0) return merged.replace(/\\r\\n/g, \"\\n\");\n return merged;\n}\n\nfunction stripFeatureTag(block: string, featureId: string, cuts: ReadonlyArray<[number, number]>): string {\n const { code } = maskSource(block);\n const esc = featureId.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const removals: Array<[number, number]> = cuts.map(([s, e]) => [s, e]);\n for (const m of block.matchAll(new RegExp(`#${esc}(?![\\\\w-])`, \"g\"))) {\n // Only tags in CODE count — a description quoting \"#FEAT-1\" is content.\n if (code.slice(m.index, m.index + m[0].length) !== m[0]) continue;\n let s = m.index;\n let e = s + m[0].length;\n // A tag list's separators go with the tag they separate. LikeC4 reads a\n // list as `#a #b, #c;` — one or more tags first, then `,` groups, then an\n // optional `;` — so `#FEAT-1, #x` stripped to `, #x` and `#FEAT-1;` to a\n // bare `;`, and the parse net refused a delta LikeC4 accepts. The comma\n // after the tag goes with it, else the comma before it; the `;` only when\n // no tag is left in front of it.\n const commaAfter = /^[ \\t]*,[ \\t]*/.exec(code.slice(e));\n const commaBefore = /,[ \\t]*$/.exec(code.slice(0, s));\n const semi = /^[ \\t]*;/.exec(code.slice(e));\n if (commaAfter !== null) {\n removals.push([s, e + commaAfter[0].length]);\n continue;\n }\n if (commaBefore !== null) {\n removals.push([commaBefore.index, e]);\n continue;\n }\n if (semi !== null && !/#[\\w-]+\\s*$/.test(code.slice(0, s))) e += semi[0].length;\n // Take the whitespace on ONE side with the token — trailing first, so\n // `#FEAT-1 #critical` leaves `#critical` at its own indent, and a token at\n // the end of a line does not leave a trailing space behind.\n if (block[e] === \" \" || block[e] === \"\\t\") {\n while (block[e] === \" \" || block[e] === \"\\t\") e += 1;\n } else {\n while (s > 0 && (block[s - 1] === \" \" || block[s - 1] === \"\\t\")) s -= 1;\n }\n removals.push([s, e]);\n }\n if (removals.length === 0) return block;\n\n // A line the strip emptied disappears whole, newline included.\n for (let lineStart = 0; lineStart < block.length; ) {\n const nl = block.indexOf(\"\\n\", lineStart);\n const lineEnd = nl === -1 ? block.length : nl + 1;\n if (\n removals.some(([s, e]) => s >= lineStart && e <= lineEnd) &&\n /^\\s*$/.test(residual(block, lineStart, nl === -1 ? block.length : nl, removals))\n ) {\n removals.push([lineStart, lineEnd]);\n }\n lineStart = lineEnd;\n }\n\n // A body the strip emptied goes too, braces and all — but only one the strip\n // emptied: braces that were authored empty are authored bytes and survive.\n for (let at = code.indexOf(\"{\"); at !== -1; at = code.indexOf(\"{\", at + 1)) {\n const closeAt = matchBrace(code, at);\n if (closeAt === -1) continue;\n if (!removals.some(([s, e]) => s > at && e <= closeAt)) continue;\n if (!/^\\s*$/.test(residual(block, at + 1, closeAt, removals))) continue;\n let s = at;\n while (s > 0 && /\\s/.test(block[s - 1]!)) s -= 1;\n removals.push([s, closeAt + 1]);\n }\n return applyRemovals(block, removals);\n}\n\n/** The text of [from, to) with the removal ranges cut out. */\nfunction residual(text: string, from: number, to: number, removals: Array<[number, number]>): string {\n let out = \"\";\n for (let i = from; i < to; i += 1) {\n if (!removals.some(([s, e]) => i >= s && i < e)) out += text[i];\n }\n return out;\n}\n\nfunction applyRemovals(text: string, removals: Array<[number, number]>): string {\n const sorted = [...removals].sort((a, b) => a[0] - b[0] || b[1] - a[1]);\n let out = \"\";\n let i = 0;\n for (const [s, e] of sorted) {\n if (s > i) out += text.slice(i, s);\n if (e > i) i = e;\n }\n return out + text.slice(i);\n}\n\n/**\n * Rebase a block's indentation: the `base` its lines carried in the delta\n * becomes `target`. The first line arrives with no leading whitespace (the\n * scanner's span starts at the declaration itself), so it only gains `target`.\n *\n * A CRLF delta's blank line is `\"\\r\"`, not `\"\"`, so splitting on `\\n` alone used\n * to hand a non-empty body to the rule below and every blank line inside a\n * spliced block came out carrying the target indent as trailing whitespace. The\n * carriage return is set aside and put back, which leaves the line's own ending\n * exactly as authored for `matchLineEndings` to reconcile.\n */\nfunction reindent(block: string, base: string, target: string): string {\n return block\n .split(\"\\n\")\n .map((line, k) => {\n const cr = line.endsWith(\"\\r\");\n const raw = cr ? line.slice(0, -1) : line;\n const body = k === 0 ? raw : raw.startsWith(base) ? raw.slice(base.length) : raw;\n return (body.length === 0 ? \"\" : target + body) + (cr ? \"\\r\" : \"\");\n })\n .join(\"\\n\");\n}\n"]}
@@ -7,4 +7,24 @@
7
7
  */
8
8
  export class LandscapeSpliceError extends Error {
9
9
  }
10
+ /**
11
+ * Refuse a splice the splice map cannot see once it has landed — the document
12
+ * scanned the same number of statements after the insert as before it.
13
+ *
14
+ * Every insert adds at least one declaration or edge, so an unchanged count
15
+ * means the bytes went somewhere the scan reads as nothing: inside a comment or
16
+ * a string. LikeC4 reads it the same way, so the parse net passes a document
17
+ * that simply lacks the addition, and the archive printed "+1 relationship(s)"
18
+ * over an edge that was never there (review, 2026-10-22, on a block comment
19
+ * whose last line starts with `//`). Placement is meant never to do this; this
20
+ * is what turns a placement bug into `merge-failed` instead of silent loss.
21
+ */
22
+ export function assertSpliceVisible(before, after, what) {
23
+ const count = (s) => (s === null ? 0 : s.elements.length + s.rels.length);
24
+ if (count(after) > count(before))
25
+ return;
26
+ throw new LandscapeSpliceError(`placing '${what}' would land it where neither the splice map nor LikeC4 reads it — inside a comment or a string — ` +
27
+ `so the merge would lose it while reporting it merged; nothing was written. Move the comment it would land in, ` +
28
+ `then re-run, and report the shape`);
29
+ }
10
30
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/contract.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;CAAG","sourcesContent":["import type { Elem, Rel } from \"../likec4.js\";\n\n/**\n * A landscape merge the splicer could not compute. Every one is mechanical —\n * the input cannot be spliced as authored, never a judgment `--approve` may\n * override — and it is thrown at plan time, so nothing has been written.\n * `archiveErrorCode` (commands/archive/archive.ts) owns the mapping to `merge-failed`;\n * the splicer itself never names a CLI code.\n */\nexport class LandscapeSpliceError extends Error {}\n\nexport interface LandscapePlan {\n /** The merged landscape source, or null when everything was already there. */\n content: string | null;\n addedEls: Elem[];\n addedRels: Rel[];\n}\n\n/**\n * Everything the landscape splicer reads, as text and parsed views already in\n * hand. The merge is a pure text-to-text computation — reading the two files\n * (and deciding to write the result) stays with the caller, which is also what\n * keeps the splicer free of the staging layer.\n */\nexport interface LandscapeMergeRequest {\n /** The living landscape.likec4, decoded by the caller's readUtf8. */\n landscapeText: string;\n /** delta.likec4 as authored — the bytes the additions are spliced from. */\n deltaText: string;\n /** EVERY element the delta declares — the title join and the parent lookup read past the tagged ones. */\n deltaElements: Elem[];\n /** The delta's elements tagged with the feature: the candidate additions. */\n newEls: Elem[];\n /** The delta's relationships tagged with the feature. */\n newRels: Rel[];\n featureId: string;\n}\n"]}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/contract.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;CAAG;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA2B,EAAE,KAA0B,EAAE,IAAY;IACvG,MAAM,KAAK,GAAG,CAAC,CAAsB,EAAU,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvG,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO;IACzC,MAAM,IAAI,oBAAoB,CAC5B,YAAY,IAAI,oGAAoG;QAClH,gHAAgH;QAChH,mCAAmC,CACtC,CAAC;AACJ,CAAC","sourcesContent":["import type { Elem, Rel } from \"../likec4.js\";\nimport type { ScanKinds, ScannedModel } from \"../source-scan.js\";\n\n/**\n * A landscape merge the splicer could not compute. Every one is mechanical —\n * the input cannot be spliced as authored, never a judgment `--approve` may\n * override — and it is thrown at plan time, so nothing has been written.\n * `archiveErrorCode` (commands/archive/archive.ts) owns the mapping to `merge-failed`;\n * the splicer itself never names a CLI code.\n */\nexport class LandscapeSpliceError extends Error {}\n\n/**\n * Refuse a splice the splice map cannot see once it has landed — the document\n * scanned the same number of statements after the insert as before it.\n *\n * Every insert adds at least one declaration or edge, so an unchanged count\n * means the bytes went somewhere the scan reads as nothing: inside a comment or\n * a string. LikeC4 reads it the same way, so the parse net passes a document\n * that simply lacks the addition, and the archive printed \"+1 relationship(s)\"\n * over an edge that was never there (review, 2026-10-22, on a block comment\n * whose last line starts with `//`). Placement is meant never to do this; this\n * is what turns a placement bug into `merge-failed` instead of silent loss.\n */\nexport function assertSpliceVisible(before: ScannedModel | null, after: ScannedModel | null, what: string): void {\n const count = (s: ScannedModel | null): number => (s === null ? 0 : s.elements.length + s.rels.length);\n if (count(after) > count(before)) return;\n throw new LandscapeSpliceError(\n `placing '${what}' would land it where neither the splice map nor LikeC4 reads it — inside a comment or a string — ` +\n `so the merge would lose it while reporting it merged; nothing was written. Move the comment it would land in, ` +\n `then re-run, and report the shape`,\n );\n}\n\n/**\n * A service whose `model.likec4` EXTENDS the fleet map — the document that owns\n * that service's interior, and therefore the one a nested addition belongs in.\n *\n * The merge takes these as TEXT already in hand, exactly as it takes the\n * landscape: reading the fleet and writing the result stay with the caller.\n * A service with a standalone model, or none, is simply absent from the list,\n * and the merge then behaves as it did before the extending shape existed.\n */\nexport interface ExtendingModel {\n /** The `services/<…>/<svc>` directory name — what an element's binding resolves to. */\n service: string;\n /** Repo-relative and `/`-separated: what a message names and where the caller writes. */\n path: string;\n /** The living model source, as authored. */\n text: string;\n}\n\n/** What one extending model owes this feature — routed, not yet spliced. */\nexport interface ModelAdditions {\n model: ExtendingModel;\n els: Elem[];\n rels: Rel[];\n /**\n * The kinds the model borrows from the map it extends, read off the landscape\n * this merge spliced: a model declares none of its own, and its scan cannot\n * see a `kind name` declaration or an `a .kind b` edge without them.\n */\n borrowed: ScanKinds;\n}\n\n/**\n * The tags each relationship kind of the DELTA gives every edge of that kind —\n * its `specification.relationshipKindTags`. The parsed additions carry them and\n * the delta's statements do not, so the splice needs the table to match one to\n * the other (`./identity/edges.ts`, `scannedStatementKey`).\n */\nexport type KindTags = Readonly<Record<string, readonly string[]>>;\n\nexport interface LandscapePlan {\n /** The merged landscape source, or null when everything was already there. */\n content: string | null;\n addedEls: Elem[];\n addedRels: Rel[];\n /**\n * The additions this merge routed AWAY from the map, per model that owns\n * them. Empty when the request named no extending models — the shape every\n * caller had before the routing existed. `./model/merge.ts` turns each entry\n * into that model's merged text.\n */\n models: ModelAdditions[];\n}\n\n/**\n * Everything the landscape splicer reads, as text and parsed views already in\n * hand. The merge is a pure text-to-text computation — reading the two files\n * (and deciding to write the result) stays with the caller, which is also what\n * keeps the splicer free of the staging layer.\n */\nexport interface LandscapeMergeRequest {\n /** The living landscape.likec4, decoded by the caller's readUtf8. */\n landscapeText: string;\n /** delta.likec4 as authored — the bytes the additions are spliced from. */\n deltaText: string;\n /** EVERY element the delta declares — the title join and the parent lookup read past the tagged ones. */\n deltaElements: Elem[];\n /** The delta's elements tagged with the feature: the candidate additions. */\n newEls: Elem[];\n /** The delta's relationships tagged with the feature. */\n newRels: Rel[];\n featureId: string;\n /** The delta's relationship kinds' tags. Omitted reads as a delta whose kinds carry none. */\n deltaKindTags?: KindTags;\n /**\n * The fleet's extending models. Omitted (or empty) is the pre-extending\n * behaviour byte for byte: every addition lands on the map.\n */\n models?: readonly ExtendingModel[];\n}\n"]}
@@ -57,7 +57,7 @@
57
57
  * (`core/usecases/delta/flows.ts`), and it is a views-only document — which is
58
58
  * exactly why it stages beside the map when this one cannot.
59
59
  */
60
- import { maskSource, matchBrace } from "../source-mask.js";
60
+ import { maskSource, topLevelBlocks } from "../source-mask.js";
61
61
  import { LandscapeSpliceError } from "./contract.js";
62
62
  /**
63
63
  * Top-level blocks a delta can declare. `specification` and `model` are absent
@@ -74,6 +74,10 @@ function refuse(block, detail, home) {
74
74
  }
75
75
  /** Where the living documents keep what this delta cannot carry. */
76
76
  const LANDSCAPE_HOME = "architecture/landscape.likec4, where the living documents keep it,";
77
+ /** Where a FEATURE keeps TOPOLOGY — the third whole-file slot, added with the deployment axis. */
78
+ const DEPLOYMENT_HOME = "features/<FEAT>/deployment/<name>.likec4 — a document of its own, which `loam archive` copies into " +
79
+ "architecture/ and `loam unarchive` takes back; say `extend` in it to add to a region the living map " +
80
+ "already declares —";
77
81
  /** Where a FEATURE keeps a flow — a slot of its own, not the living tree. See the header. */
78
82
  const FLOW_HOME = "features/<FEAT>/usecases/<name>.likec4 — a views-only document of its own, which `loam archive` copies into " +
79
83
  "architecture/usecases/ and `loam unarchive` takes back —";
@@ -85,30 +89,26 @@ const FLOW_HOME = "features/<FEAT>/usecases/<name>.likec4 — a views-only docum
85
89
  * `scanModel` splices under, and for the same reason: this refuses an archive,
86
90
  * and a refusal a comment can provoke is worse than the silence it replaced.
87
91
  *
88
- * Brace depth is tracked in one forward pass rather than recounted per match:
89
- * only a block at depth 0 is a top-level declaration, and `deployment` at depth
90
- * 1 is an ordinary `specification { deploymentNode … }` or a view predicate.
92
+ * Only a block at depth 0 is a top-level declaration (`topLevelBlocks`), and
93
+ * `deployment` at depth 1 is an ordinary `specification { deploymentNode … }`
94
+ * or a view predicate.
91
95
  */
92
96
  export function assertMergeableDelta(deltaText) {
93
97
  const { code } = maskSource(deltaText);
94
- let depth = 0;
95
- let cursor = 0;
96
- for (const m of code.matchAll(TOP_LEVEL_BLOCK)) {
97
- for (; cursor < m.index; cursor += 1) {
98
- if (code[cursor] === "{")
99
- depth += 1;
100
- else if (code[cursor] === "}")
101
- depth -= 1;
102
- }
103
- if (depth !== 0)
104
- continue;
98
+ for (const { match: m, body: inner } of topLevelBlocks(code, TOP_LEVEL_BLOCK)) {
105
99
  const block = m[1];
106
100
  if (block !== "views") {
107
- refuse(`\`${block} { }\` block`, `a top-level \`${block} { }\` block`, LANDSCAPE_HOME);
101
+ refuse(`\`${block} { }\` block`, `a top-level \`${block} { }\` block`,
102
+ // The refusal is unchanged and always will be — this document
103
+ // re-declares the landscape's identifiers and carries its own
104
+ // `specification`, so it cannot be staged beside the map in one project.
105
+ // What changed is where it sends the author: a feature introducing
106
+ // topology WITH the change that makes it true now has a slot of its
107
+ // own, and pointing at the living landscape was the advice that made
108
+ // the axis's headline case a two-pull-request job. `global { }` has no
109
+ // such slot and keeps the living home.
110
+ block === "deployment" ? DEPLOYMENT_HOME : LANDSCAPE_HOME);
108
111
  }
109
- const open = m.index + m[0].length - 1;
110
- const close = matchBrace(code, open);
111
- const inner = code.slice(open + 1, close === -1 ? code.length : close);
112
112
  const authored = AUTHORED_VIEW.exec(inner);
113
113
  if (authored !== null) {
114
114
  const kind = authored[1];
@@ -119,7 +119,7 @@ export function assertMergeableDelta(deltaText) {
119
119
  // an author to the living landscape for one they are introducing WITH
120
120
  // this change was the advice that made the axis's headline case a
121
121
  // two-pull-request job.
122
- kind === "dynamic" ? FLOW_HOME : LANDSCAPE_HOME);
122
+ kind === "dynamic" ? FLOW_HOME : DEPLOYMENT_HOME);
123
123
  }
124
124
  }
125
125
  }
@@ -1 +1 @@
1
- {"version":3,"file":"delta-blocks.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/delta-blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD;;;;GAIG;AACH,MAAM,eAAe,GAAG,mCAAmC,CAAC;AAE5D,2FAA2F;AAC3F,MAAM,aAAa,GAAG,iCAAiC,CAAC;AAExD,SAAS,MAAM,CAAC,KAAa,EAAE,MAAc,EAAE,IAAY;IACzD,MAAM,IAAI,oBAAoB,CAC5B,yBAAyB,MAAM,wEAAwE;QACrG,sEAAsE,KAAK,QAAQ;QACnF,GAAG,IAAI,0BAA0B,CACpC,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,cAAc,GAAG,oEAAoE,CAAC;AAE5F,6FAA6F;AAC7F,MAAM,SAAS,GACb,8GAA8G;IAC9G,0DAA0D,CAAC;AAE7D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QAC/C,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;YACrC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG;gBAAE,KAAK,IAAI,CAAC,CAAC;iBAChC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG;gBAAE,KAAK,IAAI,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,KAAK,KAAK,CAAC;YAAE,SAAS;QAC1B,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QACpB,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACtB,MAAM,CAAC,KAAK,KAAK,cAAc,EAAE,iBAAiB,KAAK,cAAc,EAAE,cAAc,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;YAC1B,MAAM,CACJ,KAAK,IAAI,SAAS,EAClB,OAAO,IAAI,0FAA0F;gBACnG,sFAAsF;YACxF,yEAAyE;YACzE,wEAAwE;YACxE,sEAAsE;YACtE,kEAAkE;YAClE,wBAAwB;YACxB,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAChD,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["/**\n * What a delta may declare that the landscape merge cannot carry.\n *\n * `planLandscapeMerge` splices out of the delta's `model { }` block and nothing\n * else. That is correct — the living landscape is a model plus the views its own\n * authors chose — but it used to be SILENT: a delta declaring `deployment { }`\n * or a `dynamic view` archived at exit 0, reported `+N element(s), +M\n * relationship(s)` exactly as usual, and the block was simply gone. The merge's\n * own parse net cannot catch it either, because a landscape missing a deployment\n * model is perfectly legal — there is nothing to fail on. Nobody finds out until\n * somebody goes looking for a diagram that was archived months ago.\n *\n * So the loss is made a refusal, which is the same doctrine as the merge's other\n * mechanical refusals (an unlocatable declaration, two statements on one line):\n * the input cannot be spliced as authored, `--approve` deliberately does not\n * override it — that flag overrides loam's JUDGMENT about coherence, never its\n * ability to carry an axis — and nothing is written.\n *\n * ## Why views are graded and not simply banned\n *\n * `loam new` scaffolds a `views { view <feat> { include * } }` block into every\n * delta.likec4 (commands/new/templates.ts), so refusing the block outright would\n * refuse loam's own scaffold on its first archive. It is there to be RENDERED\n * while the feature is in flight, and it is meant not to travel.\n *\n * The line drawn here is recoverability, and it is the only line that survives\n * the scaffold:\n *\n * - A static `view` is a rendering OF the model. Once the merge lands, the\n * whole model is in landscape.likec4, so the author can restate the view\n * there at any time from information that still exists. Dropping it loses a\n * convenience.\n * - A `dynamic view`'s ordered steps, and a `deployment { }` block's topology,\n * exist in NO other document the fleet keeps. When the merge drops those,\n * the information is gone. That is what gets refused. Rule 26 sharpens the\n * stakes rather than the rule: a `dynamic view`'s steps are now something\n * loam READS, so dropping one silently would discard checked content, not\n * merely a convenience.\n *\n * A residual is accepted knowingly: a delta carrying a SECOND static view still\n * loses it without a word. Refusing that would mean telling an author with two\n * preview views to delete one, which is a false refusal on a harmless file, and\n * the loss there is recoverable by the argument above.\n *\n * ## The dynamic-view refusal is unchanged; its ADVICE moved\n *\n * A `dynamic view` still cannot ride inside `delta.likec4`, and the reason is\n * mechanical rather than a policy about where flows belong: this document\n * re-declares the landscape's own identifiers to anchor its new edges and\n * carries its own `specification` block because LikeC4 parses it standalone, so\n * it cannot be staged beside the map in one project — a duplicate specification\n * is an error blamed on both files, and a re-declared element is a duplicate id.\n * What changed is where the refusal sends the author. It used to say\n * `architecture/landscape.likec4`, which was the only honest answer while the\n * axis had no feature-delta path: a flow could not ship with the change that\n * made it true. `features/<FEAT>/usecases/<name>.likec4` is that path\n * (`core/usecases/delta/flows.ts`), and it is a views-only document — which is\n * exactly why it stages beside the map when this one cannot.\n */\nimport { maskSource, matchBrace } from \"../source-mask.js\";\nimport { LandscapeSpliceError } from \"./contract.js\";\n\n/**\n * Top-level blocks a delta can declare. `specification` and `model` are absent\n * on purpose: the specification is re-declared in every delta because LikeC4\n * parses each one standalone, and the model is the thing being spliced.\n */\nconst TOP_LEVEL_BLOCK = /\\b(deployment|global|views)\\s*\\{/g;\n\n/** A `dynamic view` / `deployment view` inside a views block — the irrecoverable kinds. */\nconst AUTHORED_VIEW = /\\b(dynamic|deployment)\\s+view\\b/;\n\nfunction refuse(block: string, detail: string, home: string): never {\n throw new LandscapeSpliceError(\n `delta.likec4 declares ${detail}, and the landscape merge splices only what is inside \\`model { }\\` — ` +\n `archiving would drop it silently, so nothing was written. Move the ${block} into ` +\n `${home} and re-run the archive.`,\n );\n}\n\n/** Where the living documents keep what this delta cannot carry. */\nconst LANDSCAPE_HOME = \"architecture/landscape.likec4, where the living documents keep it,\";\n\n/** Where a FEATURE keeps a flow — a slot of its own, not the living tree. See the header. */\nconst FLOW_HOME =\n \"features/<FEAT>/usecases/<name>.likec4 — a views-only document of its own, which `loam archive` copies into \" +\n \"architecture/usecases/ and `loam unarchive` takes back —\";\n\n/**\n * Refuse a delta whose non-model blocks the merge would discard.\n *\n * Runs over MASKED source, so a `deployment {` written inside a comment or a\n * description string cannot trigger it — the same masking discipline\n * `scanModel` splices under, and for the same reason: this refuses an archive,\n * and a refusal a comment can provoke is worse than the silence it replaced.\n *\n * Brace depth is tracked in one forward pass rather than recounted per match:\n * only a block at depth 0 is a top-level declaration, and `deployment` at depth\n * 1 is an ordinary `specification { deploymentNode … }` or a view predicate.\n */\nexport function assertMergeableDelta(deltaText: string): void {\n const { code } = maskSource(deltaText);\n let depth = 0;\n let cursor = 0;\n for (const m of code.matchAll(TOP_LEVEL_BLOCK)) {\n for (; cursor < m.index; cursor += 1) {\n if (code[cursor] === \"{\") depth += 1;\n else if (code[cursor] === \"}\") depth -= 1;\n }\n if (depth !== 0) continue;\n const block = m[1]!;\n if (block !== \"views\") {\n refuse(`\\`${block} { }\\` block`, `a top-level \\`${block} { }\\` block`, LANDSCAPE_HOME);\n }\n const open = m.index + m[0].length - 1;\n const close = matchBrace(code, open);\n const inner = code.slice(open + 1, close === -1 ? code.length : close);\n const authored = AUTHORED_VIEW.exec(inner);\n if (authored !== null) {\n const kind = authored[1]!;\n refuse(\n `\\`${kind} view\\``,\n `a \\`${kind} view\\` in its \\`views { }\\` block — a view of that kind describes ordering or topology ` +\n `recorded in no other document, unlike the static preview view \\`loam new\\` scaffolds`,\n // A deployment view is topology and belongs in the living map; a dynamic\n // view is a flow, and a flow now has a feature slot of its own. Sending\n // an author to the living landscape for one they are introducing WITH\n // this change was the advice that made the axis's headline case a\n // two-pull-request job.\n kind === \"dynamic\" ? FLOW_HOME : LANDSCAPE_HOME,\n );\n }\n }\n}\n"]}
1
+ {"version":3,"file":"delta-blocks.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/delta-blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD;;;;GAIG;AACH,MAAM,eAAe,GAAG,mCAAmC,CAAC;AAE5D,2FAA2F;AAC3F,MAAM,aAAa,GAAG,iCAAiC,CAAC;AAExD,SAAS,MAAM,CAAC,KAAa,EAAE,MAAc,EAAE,IAAY;IACzD,MAAM,IAAI,oBAAoB,CAC5B,yBAAyB,MAAM,wEAAwE;QACrG,sEAAsE,KAAK,QAAQ;QACnF,GAAG,IAAI,0BAA0B,CACpC,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,cAAc,GAAG,oEAAoE,CAAC;AAE5F,kGAAkG;AAClG,MAAM,eAAe,GACnB,qGAAqG;IACrG,sGAAsG;IACtG,oBAAoB,CAAC;AAEvB,6FAA6F;AAC7F,MAAM,SAAS,GACb,8GAA8G;IAC9G,0DAA0D,CAAC;AAE7D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACvC,KAAK,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,cAAc,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,CAAC;QAC9E,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QACpB,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACtB,MAAM,CACJ,KAAK,KAAK,cAAc,EACxB,iBAAiB,KAAK,cAAc;YACpC,8DAA8D;YAC9D,8DAA8D;YAC9D,yEAAyE;YACzE,mEAAmE;YACnE,oEAAoE;YACpE,qEAAqE;YACrE,uEAAuE;YACvE,uCAAuC;YACvC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,cAAc,CAC1D,CAAC;QACJ,CAAC;QACD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;YAC1B,MAAM,CACJ,KAAK,IAAI,SAAS,EAClB,OAAO,IAAI,0FAA0F;gBACnG,sFAAsF;YACxF,yEAAyE;YACzE,wEAAwE;YACxE,sEAAsE;YACtE,kEAAkE;YAClE,wBAAwB;YACxB,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CACjD,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["/**\n * What a delta may declare that the landscape merge cannot carry.\n *\n * `planLandscapeMerge` splices out of the delta's `model { }` block and nothing\n * else. That is correct — the living landscape is a model plus the views its own\n * authors chose — but it used to be SILENT: a delta declaring `deployment { }`\n * or a `dynamic view` archived at exit 0, reported `+N element(s), +M\n * relationship(s)` exactly as usual, and the block was simply gone. The merge's\n * own parse net cannot catch it either, because a landscape missing a deployment\n * model is perfectly legal — there is nothing to fail on. Nobody finds out until\n * somebody goes looking for a diagram that was archived months ago.\n *\n * So the loss is made a refusal, which is the same doctrine as the merge's other\n * mechanical refusals (an unlocatable declaration, two statements on one line):\n * the input cannot be spliced as authored, `--approve` deliberately does not\n * override it — that flag overrides loam's JUDGMENT about coherence, never its\n * ability to carry an axis — and nothing is written.\n *\n * ## Why views are graded and not simply banned\n *\n * `loam new` scaffolds a `views { view <feat> { include * } }` block into every\n * delta.likec4 (commands/new/templates.ts), so refusing the block outright would\n * refuse loam's own scaffold on its first archive. It is there to be RENDERED\n * while the feature is in flight, and it is meant not to travel.\n *\n * The line drawn here is recoverability, and it is the only line that survives\n * the scaffold:\n *\n * - A static `view` is a rendering OF the model. Once the merge lands, the\n * whole model is in landscape.likec4, so the author can restate the view\n * there at any time from information that still exists. Dropping it loses a\n * convenience.\n * - A `dynamic view`'s ordered steps, and a `deployment { }` block's topology,\n * exist in NO other document the fleet keeps. When the merge drops those,\n * the information is gone. That is what gets refused. Rule 26 sharpens the\n * stakes rather than the rule: a `dynamic view`'s steps are now something\n * loam READS, so dropping one silently would discard checked content, not\n * merely a convenience.\n *\n * A residual is accepted knowingly: a delta carrying a SECOND static view still\n * loses it without a word. Refusing that would mean telling an author with two\n * preview views to delete one, which is a false refusal on a harmless file, and\n * the loss there is recoverable by the argument above.\n *\n * ## The dynamic-view refusal is unchanged; its ADVICE moved\n *\n * A `dynamic view` still cannot ride inside `delta.likec4`, and the reason is\n * mechanical rather than a policy about where flows belong: this document\n * re-declares the landscape's own identifiers to anchor its new edges and\n * carries its own `specification` block because LikeC4 parses it standalone, so\n * it cannot be staged beside the map in one project — a duplicate specification\n * is an error blamed on both files, and a re-declared element is a duplicate id.\n * What changed is where the refusal sends the author. It used to say\n * `architecture/landscape.likec4`, which was the only honest answer while the\n * axis had no feature-delta path: a flow could not ship with the change that\n * made it true. `features/<FEAT>/usecases/<name>.likec4` is that path\n * (`core/usecases/delta/flows.ts`), and it is a views-only document — which is\n * exactly why it stages beside the map when this one cannot.\n */\nimport { maskSource, topLevelBlocks } from \"../source-mask.js\";\nimport { LandscapeSpliceError } from \"./contract.js\";\n\n/**\n * Top-level blocks a delta can declare. `specification` and `model` are absent\n * on purpose: the specification is re-declared in every delta because LikeC4\n * parses each one standalone, and the model is the thing being spliced.\n */\nconst TOP_LEVEL_BLOCK = /\\b(deployment|global|views)\\s*\\{/g;\n\n/** A `dynamic view` / `deployment view` inside a views block — the irrecoverable kinds. */\nconst AUTHORED_VIEW = /\\b(dynamic|deployment)\\s+view\\b/;\n\nfunction refuse(block: string, detail: string, home: string): never {\n throw new LandscapeSpliceError(\n `delta.likec4 declares ${detail}, and the landscape merge splices only what is inside \\`model { }\\` — ` +\n `archiving would drop it silently, so nothing was written. Move the ${block} into ` +\n `${home} and re-run the archive.`,\n );\n}\n\n/** Where the living documents keep what this delta cannot carry. */\nconst LANDSCAPE_HOME = \"architecture/landscape.likec4, where the living documents keep it,\";\n\n/** Where a FEATURE keeps TOPOLOGY — the third whole-file slot, added with the deployment axis. */\nconst DEPLOYMENT_HOME =\n \"features/<FEAT>/deployment/<name>.likec4 — a document of its own, which `loam archive` copies into \" +\n \"architecture/ and `loam unarchive` takes back; say `extend` in it to add to a region the living map \" +\n \"already declares —\";\n\n/** Where a FEATURE keeps a flow — a slot of its own, not the living tree. See the header. */\nconst FLOW_HOME =\n \"features/<FEAT>/usecases/<name>.likec4 — a views-only document of its own, which `loam archive` copies into \" +\n \"architecture/usecases/ and `loam unarchive` takes back —\";\n\n/**\n * Refuse a delta whose non-model blocks the merge would discard.\n *\n * Runs over MASKED source, so a `deployment {` written inside a comment or a\n * description string cannot trigger it — the same masking discipline\n * `scanModel` splices under, and for the same reason: this refuses an archive,\n * and a refusal a comment can provoke is worse than the silence it replaced.\n *\n * Only a block at depth 0 is a top-level declaration (`topLevelBlocks`), and\n * `deployment` at depth 1 is an ordinary `specification { deploymentNode … }`\n * or a view predicate.\n */\nexport function assertMergeableDelta(deltaText: string): void {\n const { code } = maskSource(deltaText);\n for (const { match: m, body: inner } of topLevelBlocks(code, TOP_LEVEL_BLOCK)) {\n const block = m[1]!;\n if (block !== \"views\") {\n refuse(\n `\\`${block} { }\\` block`,\n `a top-level \\`${block} { }\\` block`,\n // The refusal is unchanged and always will be — this document\n // re-declares the landscape's identifiers and carries its own\n // `specification`, so it cannot be staged beside the map in one project.\n // What changed is where it sends the author: a feature introducing\n // topology WITH the change that makes it true now has a slot of its\n // own, and pointing at the living landscape was the advice that made\n // the axis's headline case a two-pull-request job. `global { }` has no\n // such slot and keeps the living home.\n block === \"deployment\" ? DEPLOYMENT_HOME : LANDSCAPE_HOME,\n );\n }\n const authored = AUTHORED_VIEW.exec(inner);\n if (authored !== null) {\n const kind = authored[1]!;\n refuse(\n `\\`${kind} view\\``,\n `a \\`${kind} view\\` in its \\`views { }\\` block — a view of that kind describes ordering or topology ` +\n `recorded in no other document, unlike the static preview view \\`loam new\\` scaffolds`,\n // A deployment view is topology and belongs in the living map; a dynamic\n // view is a flow, and a flow now has a feature slot of its own. Sending\n // an author to the living landscape for one they are introducing WITH\n // this change was the advice that made the axis's headline case a\n // two-pull-request job.\n kind === \"dynamic\" ? FLOW_HOME : DEPLOYMENT_HOME,\n );\n }\n }\n}\n"]}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * What the fleet's extending models ALREADY declare — the half of the archive's
3
+ * existence check that does not live on the map.
4
+ *
5
+ * The check has two joins: by id, and by TITLE for a delta that spells an
6
+ * element under its own identifier rather than the map's. Only the id half ever
7
+ * learned about the models the merge now writes into, so a container a service
8
+ * already draws under a different id was spliced in beside itself and the
9
+ * service silently grew two boxes with one name — while the SAME delta against
10
+ * the same title ON THE MAP adds nothing (review of E1). One rule, two
11
+ * documents, two answers.
12
+ *
13
+ * THE MODEL HALF IS SCOPED TO ITS SERVICE, and the map's is deliberately not.
14
+ * The map's global title index is the id-less fallback for documents written
15
+ * before `metadata { service }` existed, and it carries a hazard its own
16
+ * comment states: two services' boxes sharing a title join to each other unless
17
+ * both sides are bound. A service's interior is exactly where that would bite —
18
+ * every extending model in a fleet names a container 'api' — so a model's
19
+ * declaration joins by title only against an addition resolving to the SAME
20
+ * explicitly bound service. Either side unbound means no title join at all: an
21
+ * addition landing beside a namesake is a duplicate the author can see, and a
22
+ * wrongly skipped one is a container that quietly never arrived.
23
+ *
24
+ * The reader is the SCAN and not a parse, for the reason the caller states: ids
25
+ * are exact inside the fqn namespace the map and its models share, and parsing
26
+ * every model here would cost one LikeC4 workspace per service to answer a
27
+ * question about text. Two kinds of declaration therefore carry no title, and
28
+ * neither loses anything: one whose head names no title (LikeC4 titles it after
29
+ * its own name, and a model's default and a delta's differ exactly when their
30
+ * ids do — which the id join already covers) and one writing its title as a
31
+ * body property.
32
+ */
33
+ import { ancestorIds } from "../../../kernel/ids/fqn/ancestors.js";
34
+ /**
35
+ * Index the scanned models.
36
+ *
37
+ * `bindEls` is the corpus the service binding resolves against — the living
38
+ * map's elements plus the delta's, the same one routing uses, because a model's
39
+ * declaration is bound by an ancestor the MAP owns and a delta's addition by an
40
+ * ancestor either document may.
41
+ */
42
+ export function modelDeclarations(scans, bindEls) {
43
+ const bound = new Map();
44
+ for (const e of bindEls)
45
+ if (e.service !== undefined)
46
+ bound.set(e.id, e.service);
47
+ // An EXPLICIT binding on the id or an ancestor, and nothing else.
48
+ // `serviceResolver`'s last rung answers with the element's own title or its
49
+ // raw id — the rung its own comment calls the one that lies — and here that
50
+ // would file two unbound services' containers under invented names and then
51
+ // let those names join.
52
+ const serviceOf = (id) => {
53
+ for (const candidate of ancestorIds(id)) {
54
+ const service = bound.get(candidate);
55
+ if (service !== undefined)
56
+ return service;
57
+ }
58
+ return undefined;
59
+ };
60
+ const ids = [];
61
+ const titles = new Set();
62
+ for (const { scan } of scans) {
63
+ for (const e of scan?.elements ?? []) {
64
+ // An `extend` frame declares nothing: it reopens an element the map owns.
65
+ if (e.extend === true)
66
+ continue;
67
+ ids.push(e.id);
68
+ if (e.title === undefined)
69
+ continue;
70
+ const service = serviceOf(e.id);
71
+ if (service !== undefined)
72
+ titles.add(titleKey(service, e.title));
73
+ }
74
+ }
75
+ return {
76
+ ids,
77
+ declaresTitle: (id, title) => {
78
+ const service = serviceOf(id);
79
+ return service !== undefined && titles.has(titleKey(service, title));
80
+ },
81
+ };
82
+ }
83
+ /** Length-prefixed: a service id and a title cannot spell another pair's key. */
84
+ function titleKey(service, title) {
85
+ return `${service.length}:${service}${title}`;
86
+ }
87
+ //# sourceMappingURL=declared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"declared.js","sourceRoot":"","sources":["../../../../../src/core/c4/splice/identity/declared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAenE;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAmD,EACnD,OAAwB;IAExB,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS;YAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IACjF,kEAAkE;IAClE,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAC5E,wBAAwB;IACxB,MAAM,SAAS,GAAG,CAAC,EAAU,EAA+B,EAAE;QAC5D,KAAK,MAAM,SAAS,IAAI,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;YACxC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACrC,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,OAAO,CAAC;QAC5C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IACF,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,QAAQ,IAAI,EAAE,EAAE,CAAC;YACrC,0EAA0E;YAC1E,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI;gBAAE,SAAS;YAChC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;gBAAE,SAAS;YACpC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAChC,IAAI,OAAO,KAAK,SAAS;gBAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IACD,OAAO;QACL,GAAG;QACH,aAAa,EAAE,CAAC,EAAE,EAAE,KAAK,EAAW,EAAE;YACpC,MAAM,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;YAC9B,OAAO,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QACvE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,SAAS,QAAQ,CAAC,OAAwB,EAAE,KAAa;IACvD,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,GAAG,KAAK,EAAE,CAAC;AAChD,CAAC","sourcesContent":["/**\n * What the fleet's extending models ALREADY declare — the half of the archive's\n * existence check that does not live on the map.\n *\n * The check has two joins: by id, and by TITLE for a delta that spells an\n * element under its own identifier rather than the map's. Only the id half ever\n * learned about the models the merge now writes into, so a container a service\n * already draws under a different id was spliced in beside itself and the\n * service silently grew two boxes with one name — while the SAME delta against\n * the same title ON THE MAP adds nothing (review of E1). One rule, two\n * documents, two answers.\n *\n * THE MODEL HALF IS SCOPED TO ITS SERVICE, and the map's is deliberately not.\n * The map's global title index is the id-less fallback for documents written\n * before `metadata { service }` existed, and it carries a hazard its own\n * comment states: two services' boxes sharing a title join to each other unless\n * both sides are bound. A service's interior is exactly where that would bite —\n * every extending model in a fleet names a container 'api' — so a model's\n * declaration joins by title only against an addition resolving to the SAME\n * explicitly bound service. Either side unbound means no title join at all: an\n * addition landing beside a namesake is a duplicate the author can see, and a\n * wrongly skipped one is a container that quietly never arrived.\n *\n * The reader is the SCAN and not a parse, for the reason the caller states: ids\n * are exact inside the fqn namespace the map and its models share, and parsing\n * every model here would cost one LikeC4 workspace per service to answer a\n * question about text. Two kinds of declaration therefore carry no title, and\n * neither loses anything: one whose head names no title (LikeC4 titles it after\n * its own name, and a model's default and a delta's differ exactly when their\n * ids do — which the id join already covers) and one writing its title as a\n * body property.\n */\nimport { ancestorIds } from \"../../../kernel/ids/fqn/ancestors.js\";\nimport type { DeclaredService } from \"../../../kernel/ids/service.js\";\nimport type { Elem } from \"../../likec4.js\";\nimport type { ScannedModel } from \"../../source-scan.js\";\n\nexport interface ModelDeclarations {\n /** Every id an extending model declares — the id join's share, and exact. */\n ids: string[];\n /**\n * True when some model already declares an element titled `title` inside the\n * service `id` resolves to. False whenever either side is unbound.\n */\n declaresTitle: (id: string, title: string) => boolean;\n}\n\n/**\n * Index the scanned models.\n *\n * `bindEls` is the corpus the service binding resolves against — the living\n * map's elements plus the delta's, the same one routing uses, because a model's\n * declaration is bound by an ancestor the MAP owns and a delta's addition by an\n * ancestor either document may.\n */\nexport function modelDeclarations(\n scans: ReadonlyArray<{ scan: ScannedModel | null }>,\n bindEls: readonly Elem[],\n): ModelDeclarations {\n const bound = new Map<string, DeclaredService>();\n for (const e of bindEls) if (e.service !== undefined) bound.set(e.id, e.service);\n // An EXPLICIT binding on the id or an ancestor, and nothing else.\n // `serviceResolver`'s last rung answers with the element's own title or its\n // raw id — the rung its own comment calls the one that lies — and here that\n // would file two unbound services' containers under invented names and then\n // let those names join.\n const serviceOf = (id: string): DeclaredService | undefined => {\n for (const candidate of ancestorIds(id)) {\n const service = bound.get(candidate);\n if (service !== undefined) return service;\n }\n return undefined;\n };\n const ids: string[] = [];\n const titles = new Set<string>();\n for (const { scan } of scans) {\n for (const e of scan?.elements ?? []) {\n // An `extend` frame declares nothing: it reopens an element the map owns.\n if (e.extend === true) continue;\n ids.push(e.id);\n if (e.title === undefined) continue;\n const service = serviceOf(e.id);\n if (service !== undefined) titles.add(titleKey(service, e.title));\n }\n }\n return {\n ids,\n declaresTitle: (id, title): boolean => {\n const service = serviceOf(id);\n return service !== undefined && titles.has(titleKey(service, title));\n },\n };\n}\n\n/** Length-prefixed: a service id and a title cannot spell another pair's key. */\nfunction titleKey(service: DeclaredService, title: string): string {\n return `${service.length}:${service}${title}`;\n}\n"]}
@@ -1,3 +1,40 @@
1
+ /**
2
+ * Which AUTHORED STATEMENT a parsed addition was written as — the key that
3
+ * matches a `Rel` back to its bytes in delta.likec4, so the splice carries the
4
+ * declaration verbatim instead of re-serializing it.
5
+ *
6
+ * Stricter than `relKey` below on purpose, and asking a different question:
7
+ * `relKey` decides whether the living document already HAS this edge, across
8
+ * two id namespaces, so it joins on titles; this one runs inside ONE document
9
+ * where ids are exact, and it must tell apart two statements the merge would
10
+ * otherwise hand each other's bytes. Both endpoints, the title, all three spine
11
+ * keys and the tags, because a pool entry shared by two additions gives the
12
+ * second one the first one's authored source.
13
+ *
14
+ * The tags are a SET, and a parsed `Rel`'s are not the ones written in the
15
+ * statement: since LikeC4 1.59.0 a relationship kind can carry tags
16
+ * (`relationship publishes { #async }`), and the parse hands every edge of the
17
+ * kind its kind's tags, deduplicated against the statement's own (measured at
18
+ * the 1.59.2 pin). A scanned statement carries only what is written in it, so
19
+ * `scannedStatementKey` adds its kind's tags before keying — keyed as written,
20
+ * every edge of a tagged kind found no statement, and archive refused a delta
21
+ * `validate --feature` and `status` had called ready (`merge-failed`, or
22
+ * `usecase.flow-invalid` when the feature had flows; 2026-10).
23
+ */
24
+ export function relStatementKey(r) {
25
+ const spine = [r.op ?? "", r.publishes ?? "", r.consumes ?? ""];
26
+ return JSON.stringify([r.source, r.target, r.title ?? "", ...spine, [...new Set(r.tags)].sort()]);
27
+ }
28
+ /**
29
+ * A scanned statement's `relStatementKey`, with the tags its kind gives it —
30
+ * what the parse of the same statement carries. `kindTags` is the document's
31
+ * `specification.relationshipKindTags` (`../../parsed/specification.ts`); a
32
+ * document whose kinds carry no tags keys exactly as `relStatementKey` does.
33
+ */
34
+ export function scannedStatementKey(s, kindTags) {
35
+ const inherited = s.kind === undefined ? [] : (kindTags[s.kind] ?? []);
36
+ return relStatementKey({ ...s, tags: [...s.tags, ...inherited] });
37
+ }
1
38
  /**
2
39
  * The order of loam-inserted relationships that share a landing region:
3
40
  * (source title, target title, the three spine keys, title) as one comparable
@@ -1 +1 @@
1
- {"version":3,"file":"edges.js","sourceRoot":"","sources":["../../../../../src/core/c4/splice/identity/edges.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,GAAW,EACX,CAAyG;IAEzG,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AACnG,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW,EAAE,CAAM;IACxC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AAC1H,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,QAAgB,EAAE,EAAU;IAClD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;AACxD,CAAC","sourcesContent":["/**\n * How the landscape merge decides that two relationships are the SAME EDGE,\n * and in what order equal ones are written.\n *\n * A subject of its own, and not a corner of placement: everything in\n * `../placement.ts` answers *where in the text* a statement goes, in byte\n * offsets, while everything here answers *which statement this is*, as a\n * comparable string. The merge asks the identity question first and the\n * placement question second, and getting the first one wrong is the failure\n * mode that is silent — a delta edge that hashes the same as a living one is\n * counted as already present and dropped at exit 0, with `+0 relationship(s)`\n * printed over a binding that never reached the map.\n *\n * Both keys are built from fields BOTH `Rel` and `ScannedRel` carry, because\n * the same edge must key identically as today's addition and as a statement in\n * the next archive's scan of the file it was written into. A field only one of\n * them has would make the merge disagree with itself one archive later.\n */\nimport type { Elem, Rel } from \"../../likec4.js\";\n\n/**\n * The order of loam-inserted relationships that share a landing region:\n * (source title, target title, the three spine keys, title) as one comparable\n * string. Titles, not ids — the namespaces differ. The same edge must sort\n * identically as today's addition and as a statement in the next archive's\n * scan, so every field is one BOTH `Rel` and `ScannedRel` carry.\n */\nexport function relSortKey(\n els: Elem[],\n r: { source: string; target: string; title?: string; op?: string; publishes?: string; consumes?: string },\n): string {\n const spine = [r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\"];\n return JSON.stringify([titleOf(els, r.source), titleOf(els, r.target), ...spine, r.title ?? \"\"]);\n}\n\n/**\n * What makes two edges the same edge. Endpoints are compared by TITLE, which is\n * stable across the delta's and the landscape's id namespaces.\n *\n * An edge carrying a SPINE KEY — `op`, `publishes` or `consumes` — IS that call\n * or that message, whatever it is titled: retitling it must not merge a second\n * copy. An edge with none has only its title. Separate namespaces, because they\n * are separate things: an op-less edge titled `authorizePayment` is not the edge\n * whose operationId is authorizePayment, and keying on `op ?? title` quietly\n * merged only one of them.\n *\n * ALL THREE keys, not just `op`. While the other two were out, a delta edge differing from a\n * living one only by `metadata { publishes 'x' }` hashed the same, counted as already present,\n * and was dropped — `+0 relationship(s)` at exit 0 over a binding that never reached the map.\n */\nexport function relKey(els: Elem[], r: Rel): string {\n const src = titleOf(els, r.source);\n const tgt = titleOf(els, r.target);\n const spine = [r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\"];\n return JSON.stringify(spine.some((v) => v !== \"\") ? [\"spine\", src, tgt, ...spine] : [\"title\", src, tgt, r.title ?? \"\"]);\n}\n\nexport function titleOf(elements: Elem[], id: string): string {\n return elements.find((e) => e.id === id)?.title ?? id;\n}\n"]}
1
+ {"version":3,"file":"edges.js","sourceRoot":"","sources":["../../../../../src/core/c4/splice/identity/edges.ts"],"names":[],"mappings":"AAwBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,eAAe,CAAC,CAAmB;IACjD,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACpG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAa,EAAE,QAAkB;IACnE,MAAM,SAAS,GAAG,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACvE,OAAO,eAAe,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,GAAW,EACX,CAAyG;IAEzG,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AACnG,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW,EAAE,CAAM;IACxC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AAC1H,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,QAAgB,EAAE,EAAU;IAClD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;AACxD,CAAC","sourcesContent":["/**\n * How the landscape merge decides that two relationships are the SAME EDGE,\n * and in what order equal ones are written.\n *\n * A subject of its own, and not a corner of placement: everything in\n * `../placement.ts` answers *where in the text* a statement goes, in byte\n * offsets, while everything here answers *which statement this is*, as a\n * comparable string. The merge asks the identity question first and the\n * placement question second, and getting the first one wrong is the failure\n * mode that is silent — a delta edge that hashes the same as a living one is\n * counted as already present and dropped at exit 0, with `+0 relationship(s)`\n * printed over a binding that never reached the map.\n *\n * Both keys are built from fields BOTH `Rel` and `ScannedRel` carry, because\n * the same edge must key identically as today's addition and as a statement in\n * the next archive's scan of the file it was written into. A field only one of\n * them has would make the merge disagree with itself one archive later. The one\n * field the two read differently — the tags, which the parse inherits from the\n * relationship's kind — is reconciled on the scanned side (`scannedStatementKey`).\n */\nimport type { Elem, Rel } from \"../../likec4.js\";\nimport type { ScannedRel } from \"../../source-scan.js\";\nimport type { KindTags } from \"../contract.js\";\n\n/**\n * Which AUTHORED STATEMENT a parsed addition was written as — the key that\n * matches a `Rel` back to its bytes in delta.likec4, so the splice carries the\n * declaration verbatim instead of re-serializing it.\n *\n * Stricter than `relKey` below on purpose, and asking a different question:\n * `relKey` decides whether the living document already HAS this edge, across\n * two id namespaces, so it joins on titles; this one runs inside ONE document\n * where ids are exact, and it must tell apart two statements the merge would\n * otherwise hand each other's bytes. Both endpoints, the title, all three spine\n * keys and the tags, because a pool entry shared by two additions gives the\n * second one the first one's authored source.\n *\n * The tags are a SET, and a parsed `Rel`'s are not the ones written in the\n * statement: since LikeC4 1.59.0 a relationship kind can carry tags\n * (`relationship publishes { #async }`), and the parse hands every edge of the\n * kind its kind's tags, deduplicated against the statement's own (measured at\n * the 1.59.2 pin). A scanned statement carries only what is written in it, so\n * `scannedStatementKey` adds its kind's tags before keying — keyed as written,\n * every edge of a tagged kind found no statement, and archive refused a delta\n * `validate --feature` and `status` had called ready (`merge-failed`, or\n * `usecase.flow-invalid` when the feature had flows; 2026-10).\n */\nexport function relStatementKey(r: Rel | ScannedRel): string {\n const spine = [r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\"];\n return JSON.stringify([r.source, r.target, r.title ?? \"\", ...spine, [...new Set(r.tags)].sort()]);\n}\n\n/**\n * A scanned statement's `relStatementKey`, with the tags its kind gives it —\n * what the parse of the same statement carries. `kindTags` is the document's\n * `specification.relationshipKindTags` (`../../parsed/specification.ts`); a\n * document whose kinds carry no tags keys exactly as `relStatementKey` does.\n */\nexport function scannedStatementKey(s: ScannedRel, kindTags: KindTags): string {\n const inherited = s.kind === undefined ? [] : (kindTags[s.kind] ?? []);\n return relStatementKey({ ...s, tags: [...s.tags, ...inherited] });\n}\n\n/**\n * The order of loam-inserted relationships that share a landing region:\n * (source title, target title, the three spine keys, title) as one comparable\n * string. Titles, not ids — the namespaces differ. The same edge must sort\n * identically as today's addition and as a statement in the next archive's\n * scan, so every field is one BOTH `Rel` and `ScannedRel` carry.\n */\nexport function relSortKey(\n els: Elem[],\n r: { source: string; target: string; title?: string; op?: string; publishes?: string; consumes?: string },\n): string {\n const spine = [r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\"];\n return JSON.stringify([titleOf(els, r.source), titleOf(els, r.target), ...spine, r.title ?? \"\"]);\n}\n\n/**\n * What makes two edges the same edge. Endpoints are compared by TITLE, which is\n * stable across the delta's and the landscape's id namespaces.\n *\n * An edge carrying a SPINE KEY — `op`, `publishes` or `consumes` — IS that call\n * or that message, whatever it is titled: retitling it must not merge a second\n * copy. An edge with none has only its title. Separate namespaces, because they\n * are separate things: an op-less edge titled `authorizePayment` is not the edge\n * whose operationId is authorizePayment, and keying on `op ?? title` quietly\n * merged only one of them.\n *\n * ALL THREE keys, not just `op`. While the other two were out, a delta edge differing from a\n * living one only by `metadata { publishes 'x' }` hashed the same, counted as already present,\n * and was dropped — `+0 relationship(s)` at exit 0 over a binding that never reached the map.\n */\nexport function relKey(els: Elem[], r: Rel): string {\n const src = titleOf(els, r.source);\n const tgt = titleOf(els, r.target);\n const spine = [r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\"];\n return JSON.stringify(spine.some((v) => v !== \"\") ? [\"spine\", src, tgt, ...spine] : [\"title\", src, tgt, r.title ?? \"\"]);\n}\n\nexport function titleOf(elements: Elem[], id: string): string {\n return elements.find((e) => e.id === id)?.title ?? id;\n}\n"]}
@@ -0,0 +1,80 @@
1
+ import { scanModel } from "../../source-scan.js";
2
+ import { modelDeclarations } from "./declared.js";
3
+ import { relKey } from "./edges.js";
4
+ /**
5
+ * Report (never guess) when a title join would cross two bound services: with
6
+ * both sides carrying an explicit `metadata { service }` binding and every
7
+ * binding disagreeing, they are provably DIFFERENT services' boxes sharing a
8
+ * title ('API', 'Database'). Skipping would silently drop the addition, and any
9
+ * delta edge into it would then refuse the whole archive at the merge's parse
10
+ * net with a message about nothing.
11
+ */
12
+ export function newAdditions(req) {
13
+ const { livingEls, livingRels, bindEls, deltaElements, candidates, models, borrowed } = req;
14
+ const modelScans = models.map((model) => ({ scan: scanModel(model.text, borrowed) }));
15
+ const declared = modelDeclarations(modelScans, bindEls);
16
+ const haveIds = new Set([...livingEls.map((e) => e.id), ...declared.ids]);
17
+ // The title join needs the matched element back, not just membership: the
18
+ // cross-service guard below compares service BINDINGS across the join.
19
+ const byTitle = new Map();
20
+ const seeTitle = (el) => {
21
+ byTitle.set(el.title, [...(byTitle.get(el.title) ?? []), el]);
22
+ };
23
+ for (const el of livingEls)
24
+ seeTitle(el);
25
+ const els = [];
26
+ for (const e of candidates.els) {
27
+ if (haveIds.has(e.id))
28
+ continue;
29
+ // The addition's OWN service first, and before the map's global title
30
+ // index: a box this service's model already draws is existence, whoever
31
+ // else on the map happens to share the title.
32
+ if (declared.declaresTitle(e.id, e.title))
33
+ continue;
34
+ const sameTitle = byTitle.get(e.title);
35
+ if (sameTitle !== undefined) {
36
+ // A title match is the id-less fallback join, and skipping on it is only
37
+ // safe when the two sides could be the same box.
38
+ if (e.service !== undefined && sameTitle.every((m) => m.service !== undefined && m.service !== e.service)) {
39
+ return { els, rels: [], declaredIds: new Set(declared.ids), collision: { addition: e, living: sameTitle[0] } };
40
+ }
41
+ // KNOWN (narrowed by the guard above): with EITHER side unbound the title
42
+ // join stays trusting — the unbound title-fallback is the legal legacy
43
+ // pattern — so a cross-service collision hiding behind an unbound element
44
+ // is still silently skipped here. Scoping titles per service is backlog.
45
+ continue;
46
+ }
47
+ haveIds.add(e.id);
48
+ seeTitle(e);
49
+ els.push(e);
50
+ }
51
+ // An edge already in the landscape consumes one delta edge of the same
52
+ // identity, which is what keeps re-archiving idempotent.
53
+ const have = new Map();
54
+ for (const r of livingRels) {
55
+ const k = relKey(livingEls, r);
56
+ have.set(k, (have.get(k) ?? 0) + 1);
57
+ }
58
+ // An edge an extending model already draws consumes a delta edge exactly as a
59
+ // living map edge does — keyed through the DELTA's elements, because the
60
+ // model spells its endpoints in the map's fqn namespace and the delta must
61
+ // too for the nesting to resolve at all.
62
+ for (const { scan } of modelScans) {
63
+ for (const r of scan?.rels ?? []) {
64
+ const k = relKey(deltaElements, r);
65
+ have.set(k, (have.get(k) ?? 0) + 1);
66
+ }
67
+ }
68
+ const rels = [];
69
+ for (const r of candidates.rels) {
70
+ const k = relKey(deltaElements, r);
71
+ const n = have.get(k) ?? 0;
72
+ if (n > 0) {
73
+ have.set(k, n - 1);
74
+ continue;
75
+ }
76
+ rels.push(r);
77
+ }
78
+ return { els, rels, declaredIds: new Set(declared.ids) };
79
+ }
80
+ //# sourceMappingURL=existing.js.map