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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/CHANGELOG.md +1063 -0
  2. package/MIGRATING-from-OpenSpec.md +7 -4
  3. package/QUICKSTART.md +8 -3
  4. package/README.md +13 -7
  5. package/ROADMAP.md +285 -11
  6. package/SCHEMA.md +566 -45
  7. package/WORKFLOW.md +23 -10
  8. package/dist/commands/adopt/render.js +47 -8
  9. package/dist/commands/adopt/render.js.map +1 -1
  10. package/dist/commands/archive/plan/landscape.js +45 -1
  11. package/dist/commands/archive/plan/landscape.js.map +1 -1
  12. package/dist/commands/archive/plan/model/extending.js +85 -0
  13. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  14. package/dist/commands/archive/run.js +53 -23
  15. package/dist/commands/archive/run.js.map +1 -1
  16. package/dist/commands/context/print.js +11 -1
  17. package/dist/commands/context/print.js.map +1 -1
  18. package/dist/commands/doctor.js +46 -0
  19. package/dist/commands/doctor.js.map +1 -1
  20. package/dist/commands/explore.js +12 -2
  21. package/dist/commands/explore.js.map +1 -1
  22. package/dist/commands/list/views.js +5 -1
  23. package/dist/commands/list/views.js.map +1 -1
  24. package/dist/commands/seed/commit.js +34 -1
  25. package/dist/commands/seed/commit.js.map +1 -1
  26. package/dist/commands/seed/plan.js +158 -13
  27. package/dist/commands/seed/plan.js.map +1 -1
  28. package/dist/commands/seed/seed.js +7 -2
  29. package/dist/commands/seed/seed.js.map +1 -1
  30. package/dist/commands/show/feature.js +2 -1
  31. package/dist/commands/show/feature.js.map +1 -1
  32. package/dist/commands/show/marks.js +3 -5
  33. package/dist/commands/show/marks.js.map +1 -1
  34. package/dist/commands/show/service.js +39 -18
  35. package/dist/commands/show/service.js.map +1 -1
  36. package/dist/commands/subsystem/edit.js +4 -2
  37. package/dist/commands/subsystem/edit.js.map +1 -1
  38. package/dist/commands/subsystem/move.js +55 -17
  39. package/dist/commands/subsystem/move.js.map +1 -1
  40. package/dist/commands/subsystem/sync.js +238 -87
  41. package/dist/commands/subsystem/sync.js.map +1 -1
  42. package/dist/commands/subsystem/txn/exclude.js +132 -0
  43. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  44. package/dist/commands/subsystem/txn/report.js +174 -0
  45. package/dist/commands/subsystem/txn/report.js.map +1 -0
  46. package/dist/commands/subsystem/txn/txn.js +126 -22
  47. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  48. package/dist/commands/subsystem/txn/views.js +159 -19
  49. package/dist/commands/subsystem/txn/views.js.map +1 -1
  50. package/dist/commands/validate/arch-coverage.js +16 -9
  51. package/dist/commands/validate/arch-coverage.js.map +1 -1
  52. package/dist/commands/validate/checks/fleet-shape.js +30 -77
  53. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  54. package/dist/commands/validate/checks/health-deps.js +9 -6
  55. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  56. package/dist/commands/validate/checks/vocabulary.js +4 -19
  57. package/dist/commands/validate/checks/vocabulary.js.map +1 -1
  58. package/dist/commands/validate/feature.js +1 -1
  59. package/dist/commands/validate/feature.js.map +1 -1
  60. package/dist/commands/validate/fleet/census.js +37 -13
  61. package/dist/commands/validate/fleet/census.js.map +1 -1
  62. package/dist/commands/validate/fleet/landscape.js +131 -120
  63. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  64. package/dist/commands/validate/fleet/load.js +66 -0
  65. package/dist/commands/validate/fleet/load.js.map +1 -1
  66. package/dist/commands/validate/fleet/map/attest.js +102 -0
  67. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  68. package/dist/commands/validate/fleet/map/bindings.js +109 -0
  69. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  70. package/dist/commands/validate/fleet/map/consumers.js +219 -0
  71. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  72. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  73. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  74. package/dist/commands/validate/fleet/map/usecases.js +56 -0
  75. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  76. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  77. package/dist/commands/validate/fleet/usecases/steps.js +49 -17
  78. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
  79. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  80. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  81. package/dist/commands/validate/fleet/views/ids.js +45 -15
  82. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  83. package/dist/commands/validate/fleet/views/projects.js +258 -70
  84. package/dist/commands/validate/fleet/views/projects.js.map +1 -1
  85. package/dist/commands/validate/fleet/views/root-documents.js +99 -0
  86. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  87. package/dist/commands/validate/fleet/views/stale.js +32 -15
  88. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  89. package/dist/commands/validate/service/model/diverged.js +226 -0
  90. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  91. package/dist/commands/validate/service/model/grade.js +163 -0
  92. package/dist/commands/validate/service/model/grade.js.map +1 -0
  93. package/dist/commands/validate/service/service.js +110 -34
  94. package/dist/commands/validate/service/service.js.map +1 -1
  95. package/dist/commands/validate/service/specs.js +39 -5
  96. package/dist/commands/validate/service/specs.js.map +1 -1
  97. package/dist/commands/validate/service/spine.js +41 -3
  98. package/dist/commands/validate/service/spine.js.map +1 -1
  99. package/dist/commands/validate/service/usecases/tags.js +113 -0
  100. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  101. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  102. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  103. package/dist/commands/validate/validate.js +10 -24
  104. package/dist/commands/validate/validate.js.map +1 -1
  105. package/dist/core/agent/agents-md/artifacts.js +1 -0
  106. package/dist/core/agent/agents-md/artifacts.js.map +1 -1
  107. package/dist/core/agent/agents-md/command-map.js +19 -5
  108. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  109. package/dist/core/agent/agents-md/cycle.js +24 -9
  110. package/dist/core/agent/agents-md/cycle.js.map +1 -1
  111. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  112. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  113. package/dist/core/agent/agents-md/map/subsystem.js +70 -17
  114. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  115. package/dist/core/agent/agents-md/map/usecases.js +45 -3
  116. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  117. package/dist/core/agent/agents-md/refusals.js +5 -3
  118. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  119. package/dist/core/agent/agents-md.js +26 -7
  120. package/dist/core/agent/agents-md.js.map +1 -1
  121. package/dist/core/agent/support/report.js +39 -5
  122. package/dist/core/agent/support/report.js.map +1 -1
  123. package/dist/core/agent/workflows/adopt.js +123 -14
  124. package/dist/core/agent/workflows/adopt.js.map +1 -1
  125. package/dist/core/agent/workflows/check.js +34 -20
  126. package/dist/core/agent/workflows/check.js.map +1 -1
  127. package/dist/core/agent/workflows/closing.js +3 -1
  128. package/dist/core/agent/workflows/closing.js.map +1 -1
  129. package/dist/core/agent/workflows/reference/spine.js +12 -6
  130. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  131. package/dist/core/brief/brief.js +88 -19
  132. package/dist/core/brief/brief.js.map +1 -1
  133. package/dist/core/brief/checks.js +133 -7
  134. package/dist/core/brief/checks.js.map +1 -1
  135. package/dist/core/brief/landscape.js +212 -129
  136. package/dist/core/brief/landscape.js.map +1 -1
  137. package/dist/core/brief/map/extend.js +81 -0
  138. package/dist/core/brief/map/extend.js.map +1 -0
  139. package/dist/core/brief/map/owed.js +284 -0
  140. package/dist/core/brief/map/owed.js.map +1 -0
  141. package/dist/core/brief/targets.js +94 -29
  142. package/dist/core/brief/targets.js.map +1 -1
  143. package/dist/core/brief/unchecked.js +13 -3
  144. package/dist/core/brief/unchecked.js.map +1 -1
  145. package/dist/core/c4/likec4.js +23 -1
  146. package/dist/core/c4/likec4.js.map +1 -1
  147. package/dist/core/c4/parsed/deployment.js +5 -3
  148. package/dist/core/c4/parsed/deployment.js.map +1 -1
  149. package/dist/core/c4/parsed/view-ids.js +39 -1
  150. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  151. package/dist/core/c4/prefetch/fleet.js +142 -0
  152. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  153. package/dist/core/c4/project/architecture.js +77 -7
  154. package/dist/core/c4/project/architecture.js.map +1 -1
  155. package/dist/core/c4/project/batch.js +140 -0
  156. package/dist/core/c4/project/batch.js.map +1 -0
  157. package/dist/core/c4/project/documents.js +12 -6
  158. package/dist/core/c4/project/documents.js.map +1 -1
  159. package/dist/core/c4/project/load.js +85 -42
  160. package/dist/core/c4/project/load.js.map +1 -1
  161. package/dist/core/c4/project/staged.js +53 -15
  162. package/dist/core/c4/project/staged.js.map +1 -1
  163. package/dist/core/c4/resolve/attested.js +0 -0
  164. package/dist/core/c4/resolve/attested.js.map +1 -0
  165. package/dist/core/c4/resolve/service.js +8 -17
  166. package/dist/core/c4/resolve/service.js.map +1 -1
  167. package/dist/core/c4/resolve/steps.js +18 -0
  168. package/dist/core/c4/resolve/steps.js.map +1 -1
  169. package/dist/core/c4/root-project/exclude.js +352 -0
  170. package/dist/core/c4/root-project/exclude.js.map +1 -0
  171. package/dist/core/c4/service-model/documents.js +47 -0
  172. package/dist/core/c4/service-model/documents.js.map +1 -0
  173. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  174. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  175. package/dist/core/c4/service-model/load.js +165 -0
  176. package/dist/core/c4/service-model/load.js.map +1 -0
  177. package/dist/core/c4/service-model/renderer.js +128 -0
  178. package/dist/core/c4/service-model/renderer.js.map +1 -0
  179. package/dist/core/c4/service-model/shape.js +126 -0
  180. package/dist/core/c4/service-model/shape.js.map +1 -0
  181. package/dist/core/c4/service-model/slice.js +188 -0
  182. package/dist/core/c4/service-model/slice.js.map +1 -0
  183. package/dist/core/c4/source-scan.js +26 -0
  184. package/dist/core/c4/source-scan.js.map +1 -1
  185. package/dist/core/c4/splice/authored-source.js +60 -13
  186. package/dist/core/c4/splice/authored-source.js.map +1 -1
  187. package/dist/core/c4/splice/contract.js.map +1 -1
  188. package/dist/core/c4/splice/identity/declared.js +87 -0
  189. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  190. package/dist/core/c4/splice/identity/edges.js +17 -0
  191. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  192. package/dist/core/c4/splice/identity/existing.js +80 -0
  193. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  194. package/dist/core/c4/splice/identity/interior.js +80 -0
  195. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  196. package/dist/core/c4/splice/identity/owner.js +50 -0
  197. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  198. package/dist/core/c4/splice/landscape-merge.js +154 -100
  199. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  200. package/dist/core/c4/splice/model/merge.js +140 -0
  201. package/dist/core/c4/splice/model/merge.js.map +1 -0
  202. package/dist/core/c4/splice/placement.js +14 -4
  203. package/dist/core/c4/splice/placement.js.map +1 -1
  204. package/dist/core/c4/workspace.js +2 -1
  205. package/dist/core/c4/workspace.js.map +1 -1
  206. package/dist/core/coherence/coherence.js +7 -0
  207. package/dist/core/coherence/coherence.js.map +1 -1
  208. package/dist/core/delta/delta.js +9 -0
  209. package/dist/core/delta/delta.js.map +1 -1
  210. package/dist/core/deployment/delta.js +16 -10
  211. package/dist/core/deployment/delta.js.map +1 -1
  212. package/dist/core/diff/base-state.js +21 -5
  213. package/dist/core/diff/base-state.js.map +1 -1
  214. package/dist/core/diff/victims.js +10 -1
  215. package/dist/core/diff/victims.js.map +1 -1
  216. package/dist/core/docs.js +29 -24
  217. package/dist/core/docs.js.map +1 -1
  218. package/dist/core/doctor/config.js +7 -3
  219. package/dist/core/doctor/config.js.map +1 -1
  220. package/dist/core/doctor/doctor.js +36 -8
  221. package/dist/core/doctor/doctor.js.map +1 -1
  222. package/dist/core/doctor/report.js.map +1 -1
  223. package/dist/core/doctor/reports/scan.js +242 -0
  224. package/dist/core/doctor/reports/scan.js.map +1 -0
  225. package/dist/core/explain/refusals.js +1 -1
  226. package/dist/core/explain/refusals.js.map +1 -1
  227. package/dist/core/explore/explore.js +44 -9
  228. package/dist/core/explore/explore.js.map +1 -1
  229. package/dist/core/fleet-context.js +47 -0
  230. package/dist/core/fleet-context.js.map +1 -1
  231. package/dist/core/gate/checks.js +5 -1
  232. package/dist/core/gate/checks.js.map +1 -1
  233. package/dist/core/gate/partners.js +65 -26
  234. package/dist/core/gate/partners.js.map +1 -1
  235. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  236. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  237. package/dist/core/pack/living.js +47 -13
  238. package/dist/core/pack/living.js.map +1 -1
  239. package/dist/core/pack/pack.js +9 -6
  240. package/dist/core/pack/pack.js.map +1 -1
  241. package/dist/core/projection/arch-slice.js +2 -2
  242. package/dist/core/projection/arch-slice.js.map +1 -1
  243. package/dist/core/repo/paths.js +45 -38
  244. package/dist/core/repo/paths.js.map +1 -1
  245. package/dist/core/repo/tree/render/body.js +45 -6
  246. package/dist/core/repo/tree/render/body.js.map +1 -1
  247. package/dist/core/repo/tree/render/projects.js +93 -35
  248. package/dist/core/repo/tree/render/projects.js.map +1 -1
  249. package/dist/core/repo/tree/render/service/header.js +125 -0
  250. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  251. package/dist/core/repo/tree/render/service/peers.js +28 -0
  252. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  253. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  254. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  255. package/dist/core/repo/tree/render/service/views.js +78 -0
  256. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  257. package/dist/core/repo/tree/render/stale.js +20 -4
  258. package/dist/core/repo/tree/render/stale.js.map +1 -1
  259. package/dist/core/repo/tree/render/views.js +149 -32
  260. package/dist/core/repo/tree/render/views.js.map +1 -1
  261. package/dist/core/repo/tree/walk.js +45 -2
  262. package/dist/core/repo/tree/walk.js.map +1 -1
  263. package/dist/core/scaffold/landscape.js +219 -9
  264. package/dist/core/scaffold/landscape.js.map +1 -1
  265. package/dist/core/staging/writes.js.map +1 -1
  266. package/dist/core/usecases/delta/claims.js +1 -0
  267. package/dist/core/usecases/delta/claims.js.map +1 -1
  268. package/dist/core/usecases/delta/overlay.js +4 -0
  269. package/dist/core/usecases/delta/overlay.js.map +1 -1
  270. package/dist/core/usecases/fleet.js +18 -6
  271. package/dist/core/usecases/fleet.js.map +1 -1
  272. package/dist/core/usecases/operations.js +7 -0
  273. package/dist/core/usecases/operations.js.map +1 -1
  274. package/dist/core/usecases/service/exclude.js +119 -0
  275. package/dist/core/usecases/service/exclude.js.map +1 -0
  276. package/dist/core/usecases/service/flows.js +161 -0
  277. package/dist/core/usecases/service/flows.js.map +1 -0
  278. package/dist/core/verify/checklist.js +42 -10
  279. package/dist/core/verify/checklist.js.map +1 -1
  280. package/dist/core/vocabulary/issue.js.map +1 -1
  281. package/dist/core/vocabulary/maturity.js +51 -1
  282. package/dist/core/vocabulary/maturity.js.map +1 -1
  283. package/examples/README.md +30 -8
  284. package/examples/docs/architecture/landscape.likec4 +47 -0
  285. package/examples/docs/architecture/services.likec4 +70 -0
  286. package/examples/docs/architecture/subsystems.likec4 +3 -0
  287. package/examples/docs/architecture/usecases/order-notification.likec4 +5 -2
  288. package/examples/docs/likec4.config.json +0 -1
  289. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  290. package/examples/docs/services/order-service/arch.spec.md +13 -6
  291. package/examples/docs/services/order-service/model.likec4 +58 -70
  292. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  293. package/examples/docs/services/payment-service/model.likec4 +38 -39
  294. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  295. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  296. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  297. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  298. package/package.json +1 -1
  299. package/examples/docs/services/checkout-web/likec4.config.json +0 -4
  300. package/examples/docs/services/order-service/likec4.config.json +0 -4
  301. package/examples/docs/services/payment-service/likec4.config.json +0 -4
  302. package/examples/docs/services/platform/identity-service/likec4.config.json +0 -4
  303. package/examples/docs/services/platform/notification-service/likec4.config.json +0 -4
@@ -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,20 @@
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
+ export function relStatementKey(r) {
15
+ const spine = [r.op ?? "", r.publishes ?? "", r.consumes ?? ""];
16
+ return JSON.stringify([r.source, r.target, r.title ?? "", ...spine, [...r.tags].sort()]);
17
+ }
1
18
  /**
2
19
  * The order of loam-inserted relationships that share a landing region:
3
20
  * (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":"AAqBA;;;;;;;;;;;;GAYG;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,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3F,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.\n */\nimport type { Elem, Rel } from \"../../likec4.js\";\nimport type { ScannedRel } from \"../../source-scan.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 */\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, [...r.tags].sort()]);\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 } = req;
14
+ const modelScans = models.map((model) => ({ scan: scanModel(model.text) }));
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"existing.js","sourceRoot":"","sources":["../../../../../src/core/c4/splice/identity/existing.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which of a delta's tagged declarations the living fleet does NOT already have\n * — the third identity question of the archive's C4 merge, beside `./edges.ts`'s\n * \"which statement is this\" and `./owner.ts`'s \"which document owns it\".\n *\n * It moved out of `../landscape-merge.ts` when that file crossed the 400-line\n * limit, and this is the seam it wanted: everything here runs BEFORE a single\n * byte is placed, over parsed views and scans alone, and answers one question\n * that has nothing to do with where anything lands. What is left behind is the\n * splice itself.\n *\n * TWO JOINS, and the asymmetry between them is the whole subject. Ids are exact\n * and are checked against the map AND against every extending model, so\n * re-archiving is idempotent whichever document an addition landed in. Titles\n * are the id-less fallback every repository written before\n * `metadata { service }` relies on: global on the map (with the cross-service\n * collision refused rather than silently skipped) and scoped to one service in\n * the models, for the reason `./declared.ts` states.\n *\n * Relationships are matched by COUNT, not by membership: two edges the model\n * cannot tell apart are still two edges, and dropping the second one silently\n * loses a call the author drew.\n */\nimport type { Elem, Rel } from \"../../likec4.js\";\nimport { scanModel } from \"../../source-scan.js\";\nimport type { ExtendingModel } from \"../contract.js\";\nimport { modelDeclarations } from \"./declared.js\";\nimport { relKey } from \"./edges.js\";\n\nexport interface ExistingRequest {\n /** The living map's parsed elements. */\n livingEls: Elem[];\n /** The living map's parsed relationships. */\n livingRels: Rel[];\n /**\n * The corpus a service binding resolves against — the living map's elements\n * plus the delta's, so a declaration the delta introduces resolves the moment\n * its bytes would land.\n */\n bindEls: Elem[];\n /** EVERY element the delta declares — edge identity reads past the tagged ones. */\n deltaElements: Elem[];\n /** The delta's tagged elements and relationships: the candidate additions. */\n candidates: { els: Elem[]; rels: Rel[] };\n /** The fleet's extending models, whose declarations count as existence too. */\n models: readonly ExtendingModel[];\n}\n\nexport interface Additions {\n /** The tagged elements no living document already declares. */\n els: Elem[];\n /** The tagged relationships no living document already draws. */\n rels: Rel[];\n /**\n * Every id the extending models declare. Routing needs it as well: an\n * `extend` block's children are map fqns, so a container a model already draws\n * anchors an addition's id chain in the map's namespace exactly as a living\n * element does (`./owner.ts`).\n */\n declaredIds: Set<string>;\n /**\n * A title join the merge must refuse rather than take, present only when one\n * was found — the caller stops there and nothing is written.\n *\n * REPORTED, not thrown, and that is a package rule rather than a style\n * preference: `LandscapeSpliceError` lives in `../contract.ts`, and importing\n * it as a VALUE from here would make `splice/` and `splice/identity/` import\n * each other (`scripts/package-graph.mjs` refuses the cycle). The refusal text\n * belongs to the merge anyway — it is the one that decides to write.\n */\n collision?: { addition: Elem; living: Elem };\n}\n\n/**\n * Report (never guess) when a title join would cross two bound services: with\n * both sides carrying an explicit `metadata { service }` binding and every\n * binding disagreeing, they are provably DIFFERENT services' boxes sharing a\n * title ('API', 'Database'). Skipping would silently drop the addition, and any\n * delta edge into it would then refuse the whole archive at the merge's parse\n * net with a message about nothing.\n */\nexport function newAdditions(req: ExistingRequest): Additions {\n const { livingEls, livingRels, bindEls, deltaElements, candidates, models } = req;\n const modelScans = models.map((model) => ({ scan: scanModel(model.text) }));\n const declared = modelDeclarations(modelScans, bindEls);\n const haveIds = new Set([...livingEls.map((e) => e.id), ...declared.ids]);\n // The title join needs the matched element back, not just membership: the\n // cross-service guard below compares service BINDINGS across the join.\n const byTitle = new Map<string, Elem[]>();\n const seeTitle = (el: Elem): void => {\n byTitle.set(el.title, [...(byTitle.get(el.title) ?? []), el]);\n };\n for (const el of livingEls) seeTitle(el);\n const els: Elem[] = [];\n for (const e of candidates.els) {\n if (haveIds.has(e.id)) continue;\n // The addition's OWN service first, and before the map's global title\n // index: a box this service's model already draws is existence, whoever\n // else on the map happens to share the title.\n if (declared.declaresTitle(e.id, e.title)) continue;\n const sameTitle = byTitle.get(e.title);\n if (sameTitle !== undefined) {\n // A title match is the id-less fallback join, and skipping on it is only\n // safe when the two sides could be the same box.\n if (e.service !== undefined && sameTitle.every((m) => m.service !== undefined && m.service !== e.service)) {\n return { els, rels: [], declaredIds: new Set(declared.ids), collision: { addition: e, living: sameTitle[0]! } };\n }\n // KNOWN (narrowed by the guard above): with EITHER side unbound the title\n // join stays trusting — the unbound title-fallback is the legal legacy\n // pattern — so a cross-service collision hiding behind an unbound element\n // is still silently skipped here. Scoping titles per service is backlog.\n continue;\n }\n haveIds.add(e.id);\n seeTitle(e);\n els.push(e);\n }\n\n // An edge already in the landscape consumes one delta edge of the same\n // identity, which is what keeps re-archiving idempotent.\n const have = new Map<string, number>();\n for (const r of livingRels) {\n const k = relKey(livingEls, r);\n have.set(k, (have.get(k) ?? 0) + 1);\n }\n // An edge an extending model already draws consumes a delta edge exactly as a\n // living map edge does — keyed through the DELTA's elements, because the\n // model spells its endpoints in the map's fqn namespace and the delta must\n // too for the nesting to resolve at all.\n for (const { scan } of modelScans) {\n for (const r of scan?.rels ?? []) {\n const k = relKey(deltaElements, r);\n have.set(k, (have.get(k) ?? 0) + 1);\n }\n }\n const rels: Rel[] = [];\n for (const r of candidates.rels) {\n const k = relKey(deltaElements, r);\n const n = have.get(k) ?? 0;\n if (n > 0) {\n have.set(k, n - 1);\n continue;\n }\n rels.push(r);\n }\n return { els, rels, declaredIds: new Set(declared.ids) };\n}\n"]}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `./owner.ts`'s question one construct up: not "whose interior is this id" but
3
+ * "does this whole NEW BLOCK hand its interior over" — decided ONCE per block,
4
+ * and read by BOTH halves of the archive's routing.
5
+ *
6
+ * A service the feature INTRODUCES arrives as one tagged element whose authored
7
+ * bytes already hold its containers. Those containers carry no feature tag of
8
+ * their own — LikeC4 does not inherit tags — so per-addition routing cannot see
9
+ * them: they rode onto the map inside their parent, and the service's extending
10
+ * model could then never declare them, because writing the container the adopt
11
+ * brief prescribes made the map's copy a duplicate and the service `c4.invalid`
12
+ * (verification 2026-09-04, refutation of E1). So the box lands on the map
13
+ * without its interior and the children go into the model's `extend <fqn> { }`
14
+ * block, which `../model/merge.ts` creates.
15
+ *
16
+ * ONE DECISION, NOT TWO, and that is the whole reason this is a module rather
17
+ * than a closure inside `../landscape-merge.ts`. A child of such a block CAN
18
+ * carry the feature tag, and then both halves see it: per-addition routing reads
19
+ * its tag and sends it to the model, while the block decision leaves the same
20
+ * child's bytes on the map. Each half was right on its own and the merge
21
+ * declared the child twice — `merge-failed`, exit 1, nothing written, on a delta
22
+ * neither half was wrong about (re-verification 2026-09-04). `covering` is what
23
+ * makes the routing defer: a declaration written inside a block is the BLOCK's
24
+ * to place, whether or not it is tagged.
25
+ *
26
+ * A block holding a RELATIONSHIP statement rides whole. Such an edge names its
27
+ * endpoints by their LOCAL names inside the block, and the model merge anchors a
28
+ * relationship at the model block's top level — splitting the two apart would
29
+ * leave the map naming children it no longer declares, and `api -> worker` at a
30
+ * model's top level resolves to nothing. The edge rides with its block, for the
31
+ * same reason its siblings do.
32
+ */
33
+ import { parentIdOf } from "../../../kernel/ids/fqn/ancestors.js";
34
+ export function planInteriors(req) {
35
+ const { scan, deltaElements, owner, addedEls } = req;
36
+ const byId = new Map(scan.elements.map((e) => [e.id, e]));
37
+ const blocks = [];
38
+ const routed = [];
39
+ const omit = [];
40
+ for (const e of addedEls) {
41
+ // A block is an addition that lands on the MAP (nothing owns its parent)
42
+ // and whose own interior belongs to a model. An addition routed away is not
43
+ // a block: its children ride inside its bytes into the same model, which is
44
+ // `../model/merge.ts`'s own `rides` rule.
45
+ if (owner(parentIdOf(e.id)) !== undefined)
46
+ continue;
47
+ const model = owner(e.id);
48
+ if (model === undefined)
49
+ continue;
50
+ // Not in the scan: the splice loop refuses on its own, with the message that
51
+ // names the declaration it could not locate.
52
+ const src = byId.get(e.id);
53
+ if (src === undefined)
54
+ continue;
55
+ const kids = scan.elements.filter((c) => parentIdOf(c.id) === e.id);
56
+ if (kids.length === 0)
57
+ continue;
58
+ const els = kids
59
+ .map((c) => deltaElements.find((x) => x.id === c.id))
60
+ .filter((x) => x !== undefined);
61
+ // A child the parse does not carry is one the model merge could not splice
62
+ // either (it splices by id), so the whole block rides rather than losing it.
63
+ const splits = els.length === kids.length && !scan.rels.some((r) => r.start >= src.start && r.end <= src.end);
64
+ blocks.push({ range: { start: src.start, end: src.end }, fate: splits ? "split" : "whole" });
65
+ if (!splits)
66
+ continue;
67
+ routed.push({ model, els });
68
+ omit.push(...kids.map((c) => ({ start: c.start, end: c.end })));
69
+ }
70
+ return {
71
+ omit,
72
+ routed,
73
+ covering: (at) => blocks.find((b) => inside(at, b.range))?.fate ?? null,
74
+ };
75
+ }
76
+ /** Strictly inside: the ranges of two declarations nest or are disjoint, never overlap. */
77
+ function inside(at, block) {
78
+ return at.start >= block.start && at.end <= block.end && (at.start !== block.start || at.end !== block.end);
79
+ }
80
+ //# sourceMappingURL=interior.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interior.js","sourceRoot":"","sources":["../../../../../src/core/c4/splice/identity/interior.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `./owner.ts`'s question one construct up: not \"whose interior is this id\" but\n * \"does this whole NEW BLOCK hand its interior over\" — decided ONCE per block,\n * and read by BOTH halves of the archive's routing.\n *\n * A service the feature INTRODUCES arrives as one tagged element whose authored\n * bytes already hold its containers. Those containers carry no feature tag of\n * their own — LikeC4 does not inherit tags — so per-addition routing cannot see\n * them: they rode onto the map inside their parent, and the service's extending\n * model could then never declare them, because writing the container the adopt\n * brief prescribes made the map's copy a duplicate and the service `c4.invalid`\n * (verification 2026-09-04, refutation of E1). So the box lands on the map\n * without its interior and the children go into the model's `extend <fqn> { }`\n * block, which `../model/merge.ts` creates.\n *\n * ONE DECISION, NOT TWO, and that is the whole reason this is a module rather\n * than a closure inside `../landscape-merge.ts`. A child of such a block CAN\n * carry the feature tag, and then both halves see it: per-addition routing reads\n * its tag and sends it to the model, while the block decision leaves the same\n * child's bytes on the map. Each half was right on its own and the merge\n * declared the child twice — `merge-failed`, exit 1, nothing written, on a delta\n * neither half was wrong about (re-verification 2026-09-04). `covering` is what\n * makes the routing defer: a declaration written inside a block is the BLOCK's\n * to place, whether or not it is tagged.\n *\n * A block holding a RELATIONSHIP statement rides whole. Such an edge names its\n * endpoints by their LOCAL names inside the block, and the model merge anchors a\n * relationship at the model block's top level — splitting the two apart would\n * leave the map naming children it no longer declares, and `api -> worker` at a\n * model's top level resolves to nothing. The edge rides with its block, for the\n * same reason its siblings do.\n */\nimport { parentIdOf } from \"../../../kernel/ids/fqn/ancestors.js\";\nimport type { Elem } from \"../../likec4.js\";\nimport type { ScannedModel } from \"../../source-scan.js\";\nimport type { SourceRange } from \"../authored-source.js\";\nimport type { ExtendingModel } from \"../contract.js\";\n\n/** What one block decided about everything written inside it. */\nexport type BlockFate = \"whole\" | \"split\";\n\nexport interface InteriorRequest {\n /** The delta's declarations located in source — the bytes every decision is about. */\n scan: ScannedModel;\n /** EVERY element the delta declares: a child the parse does not carry cannot be routed. */\n deltaElements: readonly Elem[];\n /** Which document owns a given parent id's interior (`./owner.ts`). */\n owner: (parentId: string) => ExtendingModel | undefined;\n /** The delta's declarations this merge is adding — where the blocks are found. */\n addedEls: readonly Elem[];\n}\n\n/** One split block's share: what its service's model takes, and what the map must drop. */\nexport interface RoutedInterior {\n model: ExtendingModel;\n els: Elem[];\n}\n\nexport interface Interiors {\n /** Delta ranges the map's spliced bytes must leave behind — the split blocks' children. */\n readonly omit: readonly SourceRange[];\n /** What each split block hands the model that owns it. */\n readonly routed: readonly RoutedInterior[];\n /**\n * The decision of the block a statement is written INSIDE, or null when no\n * block holds it and ordinary routing applies. A block's own declaration is\n * not inside itself and answers null.\n */\n covering: (at: SourceRange) => BlockFate | null;\n}\n\nexport function planInteriors(req: InteriorRequest): Interiors {\n const { scan, deltaElements, owner, addedEls } = req;\n const byId = new Map(scan.elements.map((e) => [e.id, e]));\n const blocks: Array<{ range: SourceRange; fate: BlockFate }> = [];\n const routed: RoutedInterior[] = [];\n const omit: SourceRange[] = [];\n\n for (const e of addedEls) {\n // A block is an addition that lands on the MAP (nothing owns its parent)\n // and whose own interior belongs to a model. An addition routed away is not\n // a block: its children ride inside its bytes into the same model, which is\n // `../model/merge.ts`'s own `rides` rule.\n if (owner(parentIdOf(e.id)) !== undefined) continue;\n const model = owner(e.id);\n if (model === undefined) continue;\n // Not in the scan: the splice loop refuses on its own, with the message that\n // names the declaration it could not locate.\n const src = byId.get(e.id);\n if (src === undefined) continue;\n const kids = scan.elements.filter((c) => parentIdOf(c.id) === e.id);\n if (kids.length === 0) continue;\n const els = kids\n .map((c) => deltaElements.find((x) => x.id === c.id))\n .filter((x): x is Elem => x !== undefined);\n // A child the parse does not carry is one the model merge could not splice\n // either (it splices by id), so the whole block rides rather than losing it.\n const splits = els.length === kids.length && !scan.rels.some((r) => r.start >= src.start && r.end <= src.end);\n blocks.push({ range: { start: src.start, end: src.end }, fate: splits ? \"split\" : \"whole\" });\n if (!splits) continue;\n routed.push({ model, els });\n omit.push(...kids.map((c) => ({ start: c.start, end: c.end })));\n }\n\n return {\n omit,\n routed,\n covering: (at) => blocks.find((b) => inside(at, b.range))?.fate ?? null,\n };\n}\n\n/** Strictly inside: the ranges of two declarations nest or are disjoint, never overlap. */\nfunction inside(at: SourceRange, block: SourceRange): boolean {\n return at.start >= block.start && at.end <= block.end && (at.start !== block.start || at.end !== block.end);\n}\n"]}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Which DOCUMENT owns the home of a new declaration — the other identity
3
+ * question the archive's C4 merge asks, beside `./edges.ts`'s "which statement
4
+ * is this".
5
+ *
6
+ * A declaration nested under a service's element belongs to that service's
7
+ * `model.likec4` when the model EXTENDS the fleet map: that is the only
8
+ * document allowed to declare the service's interior (SCHEMA.md, "the map holds
9
+ * no service's interior"), and the map's copy becomes a duplicate the moment
10
+ * the model writes its own — which is exactly what the adopt brief tells a
11
+ * service to do, and exactly the failure this closes (verification 2026-09-04,
12
+ * E1).
13
+ *
14
+ * The PARENT is what resolves, never the element itself, so the caller hands in
15
+ * the parent id and this answers "whose interior is that". A service's own
16
+ * element on the map has a parent that stands for nothing — a grouping box, or
17
+ * the model root — so it resolves to no service with a model, stays on the map,
18
+ * and every service-level edge between two of them stays with it. Asking about a
19
+ * service element's OWN id is the same question one level down, and that is how
20
+ * the landscape merge learns where a NEW service's interior belongs.
21
+ *
22
+ * A service with a STANDALONE model, or none at all, is simply absent from the
23
+ * list and therefore owns nothing here: the map draws its containers, which is
24
+ * legal and is what every repository did before the extending shape existed.
25
+ */
26
+ import { ancestorIds } from "../../../kernel/ids/fqn/ancestors.js";
27
+ import { serviceResolver } from "../../resolve/service.js";
28
+ export function ownerOf(req) {
29
+ const { bindEls, living, declared, added, models } = req;
30
+ if (models.length === 0)
31
+ return () => undefined;
32
+ const byService = new Map(models.map((m) => [m.service, m]));
33
+ const resolve = serviceResolver(bindEls);
34
+ return (parentId) => {
35
+ if (parentId === "")
36
+ return undefined;
37
+ // Walk up until the chain reaches an id the map's namespace already holds.
38
+ // Everything below that point has to be a declaration this delta ADDS —
39
+ // a child of a container the same delta introduces, or the new service
40
+ // element itself — because those ids will exist once the merge lands.
41
+ for (const a of ancestorIds(parentId)) {
42
+ if (living.has(a) || declared.has(a))
43
+ break;
44
+ if (!added.has(a))
45
+ return undefined;
46
+ }
47
+ return byService.get(resolve(parentId));
48
+ };
49
+ }
50
+ //# sourceMappingURL=owner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner.js","sourceRoot":"","sources":["../../../../../src/core/c4/splice/identity/owner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAEnE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAsC3D,MAAM,UAAU,OAAO,CAAC,GAAiB;IACvC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;IAChD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,QAAgB,EAA8B,EAAE;QACtD,IAAI,QAAQ,KAAK,EAAE;YAAE,OAAO,SAAS,CAAC;QACtC,2EAA2E;QAC3E,wEAAwE;QACxE,uEAAuE;QACvE,sEAAsE;QACtE,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,MAAM;YAC5C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,OAAO,SAAS,CAAC;QACtC,CAAC;QACD,OAAO,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Which DOCUMENT owns the home of a new declaration — the other identity\n * question the archive's C4 merge asks, beside `./edges.ts`'s \"which statement\n * is this\".\n *\n * A declaration nested under a service's element belongs to that service's\n * `model.likec4` when the model EXTENDS the fleet map: that is the only\n * document allowed to declare the service's interior (SCHEMA.md, \"the map holds\n * no service's interior\"), and the map's copy becomes a duplicate the moment\n * the model writes its own — which is exactly what the adopt brief tells a\n * service to do, and exactly the failure this closes (verification 2026-09-04,\n * E1).\n *\n * The PARENT is what resolves, never the element itself, so the caller hands in\n * the parent id and this answers \"whose interior is that\". A service's own\n * element on the map has a parent that stands for nothing — a grouping box, or\n * the model root — so it resolves to no service with a model, stays on the map,\n * and every service-level edge between two of them stays with it. Asking about a\n * service element's OWN id is the same question one level down, and that is how\n * the landscape merge learns where a NEW service's interior belongs.\n *\n * A service with a STANDALONE model, or none at all, is simply absent from the\n * list and therefore owns nothing here: the map draws its containers, which is\n * legal and is what every repository did before the extending shape existed.\n */\nimport { ancestorIds } from \"../../../kernel/ids/fqn/ancestors.js\";\nimport type { Elem } from \"../../likec4.js\";\nimport { serviceResolver } from \"../../resolve/service.js\";\nimport type { ExtendingModel } from \"../contract.js\";\n\nexport interface OwnerRequest {\n /**\n * The corpus the parent id resolves against — the LIVING map's elements plus\n * the delta's own, so an element the delta introduces resolves the moment its\n * bytes would land.\n */\n bindEls: Elem[];\n /**\n * The ids the LIVING map declares. Reaching one is what proves the id chain is\n * spelled in the map's own namespace.\n */\n living: ReadonlySet<string>;\n /**\n * The ids the fleet's extending models declare inside their `extend` blocks.\n * Those are map fqns too — an `extend` frame reopens an element the map owns —\n * so a container already drawn in a model anchors the chain exactly as a living\n * one does, and an edge into a child of it still routes.\n */\n declared: ReadonlySet<string>;\n /**\n * The ids this merge is ADDING: the delta's declarations that survived the\n * existence joins. An id that is neither living, declared, nor added is one the\n * delta spells under its OWN name for an element the map already holds under\n * another — the legacy title join, which the landscape merge still merges for.\n * Routing it would open an `extend` block on an fqn the map has never heard of,\n * and the model's parse net would refuse an archive that used to merge\n * (verification 2026-09-04, refutation of E1: the guard used to accept any\n * chain whose ANCESTOR was living, so a title-joined name inside a group the\n * map declares — `marketplace.orderSvc` under the living `marketplace` — passed\n * it and every grouped fleet's legacy delta became `merge-failed`).\n */\n added: ReadonlySet<string>;\n models: readonly ExtendingModel[];\n}\n\nexport function ownerOf(req: OwnerRequest): (parentId: string) => ExtendingModel | undefined {\n const { bindEls, living, declared, added, models } = req;\n if (models.length === 0) return () => undefined;\n const byService = new Map(models.map((m) => [m.service, m]));\n const resolve = serviceResolver(bindEls);\n return (parentId: string): ExtendingModel | undefined => {\n if (parentId === \"\") return undefined;\n // Walk up until the chain reaches an id the map's namespace already holds.\n // Everything below that point has to be a declaration this delta ADDS —\n // a child of a container the same delta introduces, or the new service\n // element itself — because those ids will exist once the merge lands.\n for (const a of ancestorIds(parentId)) {\n if (living.has(a) || declared.has(a)) break;\n if (!added.has(a)) return undefined;\n }\n return byService.get(resolve(parentId));\n };\n}\n"]}