@endora-commerce/cli 0.100.0

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 (356) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/bin/endora.d.ts +28 -0
  4. package/dist/bin/endora.d.ts.map +1 -0
  5. package/dist/bin/endora.js +926 -0
  6. package/dist/bin/endora.js.map +1 -0
  7. package/dist/check/estate.d.ts +189 -0
  8. package/dist/check/estate.d.ts.map +1 -0
  9. package/dist/check/estate.js +1037 -0
  10. package/dist/check/estate.js.map +1 -0
  11. package/dist/check/hosts.d.ts +25 -0
  12. package/dist/check/hosts.d.ts.map +1 -0
  13. package/dist/check/hosts.js +1347 -0
  14. package/dist/check/hosts.js.map +1 -0
  15. package/dist/check/index.d.ts +60 -0
  16. package/dist/check/index.d.ts.map +1 -0
  17. package/dist/check/index.js +111 -0
  18. package/dist/check/index.js.map +1 -0
  19. package/dist/check/layout.d.ts +136 -0
  20. package/dist/check/layout.d.ts.map +1 -0
  21. package/dist/check/layout.js +262 -0
  22. package/dist/check/layout.js.map +1 -0
  23. package/dist/check/ledger.d.ts +98 -0
  24. package/dist/check/ledger.d.ts.map +1 -0
  25. package/dist/check/ledger.js +173 -0
  26. package/dist/check/ledger.js.map +1 -0
  27. package/dist/check/peer-owners.d.ts +116 -0
  28. package/dist/check/peer-owners.d.ts.map +1 -0
  29. package/dist/check/peer-owners.js +225 -0
  30. package/dist/check/peer-owners.js.map +1 -0
  31. package/dist/check/report.d.ts +33 -0
  32. package/dist/check/report.d.ts.map +1 -0
  33. package/dist/check/report.js +107 -0
  34. package/dist/check/report.js.map +1 -0
  35. package/dist/check/run.d.ts +147 -0
  36. package/dist/check/run.d.ts.map +1 -0
  37. package/dist/check/run.js +111 -0
  38. package/dist/check/run.js.map +1 -0
  39. package/dist/checks.d.ts +17 -0
  40. package/dist/checks.d.ts.map +1 -0
  41. package/dist/checks.js +17 -0
  42. package/dist/checks.js.map +1 -0
  43. package/dist/dev/index.d.ts +83 -0
  44. package/dist/dev/index.d.ts.map +1 -0
  45. package/dist/dev/index.js +298 -0
  46. package/dist/dev/index.js.map +1 -0
  47. package/dist/generate/divergence.d.ts +38 -0
  48. package/dist/generate/divergence.d.ts.map +1 -0
  49. package/dist/generate/divergence.js +237 -0
  50. package/dist/generate/divergence.js.map +1 -0
  51. package/dist/generate/index.d.ts +90 -0
  52. package/dist/generate/index.d.ts.map +1 -0
  53. package/dist/generate/index.js +369 -0
  54. package/dist/generate/index.js.map +1 -0
  55. package/dist/index.d.ts +23 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +35 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/inputs/declaration.d.ts +18 -0
  60. package/dist/inputs/declaration.d.ts.map +1 -0
  61. package/dist/inputs/declaration.js +64 -0
  62. package/dist/inputs/declaration.js.map +1 -0
  63. package/dist/inputs/env-file.d.ts +73 -0
  64. package/dist/inputs/env-file.d.ts.map +1 -0
  65. package/dist/inputs/env-file.js +134 -0
  66. package/dist/inputs/env-file.js.map +1 -0
  67. package/dist/inputs/prompt.d.ts +21 -0
  68. package/dist/inputs/prompt.d.ts.map +1 -0
  69. package/dist/inputs/prompt.js +59 -0
  70. package/dist/inputs/prompt.js.map +1 -0
  71. package/dist/inputs/resolve.d.ts +163 -0
  72. package/dist/inputs/resolve.d.ts.map +1 -0
  73. package/dist/inputs/resolve.js +290 -0
  74. package/dist/inputs/resolve.js.map +1 -0
  75. package/dist/install/host.d.ts +27 -0
  76. package/dist/install/host.d.ts.map +1 -0
  77. package/dist/install/host.js +90 -0
  78. package/dist/install/host.js.map +1 -0
  79. package/dist/install/index.d.ts +173 -0
  80. package/dist/install/index.d.ts.map +1 -0
  81. package/dist/install/index.js +793 -0
  82. package/dist/install/index.js.map +1 -0
  83. package/dist/install/wizard.d.ts +144 -0
  84. package/dist/install/wizard.d.ts.map +1 -0
  85. package/dist/install/wizard.js +362 -0
  86. package/dist/install/wizard.js.map +1 -0
  87. package/dist/lib/admin-artefacts.d.ts +70 -0
  88. package/dist/lib/admin-artefacts.d.ts.map +1 -0
  89. package/dist/lib/admin-artefacts.js +354 -0
  90. package/dist/lib/admin-artefacts.js.map +1 -0
  91. package/dist/lib/admin-surfaces.d.ts +298 -0
  92. package/dist/lib/admin-surfaces.d.ts.map +1 -0
  93. package/dist/lib/admin-surfaces.js +669 -0
  94. package/dist/lib/admin-surfaces.js.map +1 -0
  95. package/dist/lib/delegated-composer.d.ts +85 -0
  96. package/dist/lib/delegated-composer.d.ts.map +1 -0
  97. package/dist/lib/delegated-composer.js +241 -0
  98. package/dist/lib/delegated-composer.js.map +1 -0
  99. package/dist/lib/divergence-artefacts.d.ts +305 -0
  100. package/dist/lib/divergence-artefacts.d.ts.map +1 -0
  101. package/dist/lib/divergence-artefacts.js +828 -0
  102. package/dist/lib/divergence-artefacts.js.map +1 -0
  103. package/dist/lib/divergence.d.ts +337 -0
  104. package/dist/lib/divergence.d.ts.map +1 -0
  105. package/dist/lib/divergence.js +1005 -0
  106. package/dist/lib/divergence.js.map +1 -0
  107. package/dist/lib/docs-artefacts.d.ts +395 -0
  108. package/dist/lib/docs-artefacts.d.ts.map +1 -0
  109. package/dist/lib/docs-artefacts.js +781 -0
  110. package/dist/lib/docs-artefacts.js.map +1 -0
  111. package/dist/lib/emitted-exports.d.ts +21 -0
  112. package/dist/lib/emitted-exports.d.ts.map +1 -0
  113. package/dist/lib/emitted-exports.js +96 -0
  114. package/dist/lib/emitted-exports.js.map +1 -0
  115. package/dist/lib/emitted-freshness.d.ts +112 -0
  116. package/dist/lib/emitted-freshness.d.ts.map +1 -0
  117. package/dist/lib/emitted-freshness.js +288 -0
  118. package/dist/lib/emitted-freshness.js.map +1 -0
  119. package/dist/lib/entity-index-artefact.d.ts +95 -0
  120. package/dist/lib/entity-index-artefact.d.ts.map +1 -0
  121. package/dist/lib/entity-index-artefact.js +210 -0
  122. package/dist/lib/entity-index-artefact.js.map +1 -0
  123. package/dist/lib/instance-build-inputs.d.ts +108 -0
  124. package/dist/lib/instance-build-inputs.d.ts.map +1 -0
  125. package/dist/lib/instance-build-inputs.js +165 -0
  126. package/dist/lib/instance-build-inputs.js.map +1 -0
  127. package/dist/lib/module-docs.d.ts +473 -0
  128. package/dist/lib/module-docs.d.ts.map +1 -0
  129. package/dist/lib/module-docs.js +711 -0
  130. package/dist/lib/module-docs.js.map +1 -0
  131. package/dist/lib/module-package-subpaths.d.ts +56 -0
  132. package/dist/lib/module-package-subpaths.d.ts.map +1 -0
  133. package/dist/lib/module-package-subpaths.js +223 -0
  134. package/dist/lib/module-package-subpaths.js.map +1 -0
  135. package/dist/lib/module-packages.d.ts +200 -0
  136. package/dist/lib/module-packages.d.ts.map +1 -0
  137. package/dist/lib/module-packages.js +580 -0
  138. package/dist/lib/module-packages.js.map +1 -0
  139. package/dist/lib/module-population.d.ts +129 -0
  140. package/dist/lib/module-population.d.ts.map +1 -0
  141. package/dist/lib/module-population.js +172 -0
  142. package/dist/lib/module-population.js.map +1 -0
  143. package/dist/lib/module-roots.d.ts +337 -0
  144. package/dist/lib/module-roots.d.ts.map +1 -0
  145. package/dist/lib/module-roots.js +586 -0
  146. package/dist/lib/module-roots.js.map +1 -0
  147. package/dist/lib/nested-checkouts.d.ts +33 -0
  148. package/dist/lib/nested-checkouts.d.ts.map +1 -0
  149. package/dist/lib/nested-checkouts.js +160 -0
  150. package/dist/lib/nested-checkouts.js.map +1 -0
  151. package/dist/lib/platform-root.d.ts +43 -0
  152. package/dist/lib/platform-root.d.ts.map +1 -0
  153. package/dist/lib/platform-root.js +134 -0
  154. package/dist/lib/platform-root.js.map +1 -0
  155. package/dist/lib/platform-surface.d.ts +235 -0
  156. package/dist/lib/platform-surface.d.ts.map +1 -0
  157. package/dist/lib/platform-surface.js +393 -0
  158. package/dist/lib/platform-surface.js.map +1 -0
  159. package/dist/lib/port-registrations.d.ts +223 -0
  160. package/dist/lib/port-registrations.d.ts.map +1 -0
  161. package/dist/lib/port-registrations.js +532 -0
  162. package/dist/lib/port-registrations.js.map +1 -0
  163. package/dist/lib/read-size.d.ts +154 -0
  164. package/dist/lib/read-size.d.ts.map +1 -0
  165. package/dist/lib/read-size.js +182 -0
  166. package/dist/lib/read-size.js.map +1 -0
  167. package/dist/lib/registration-owners.d.ts +79 -0
  168. package/dist/lib/registration-owners.d.ts.map +1 -0
  169. package/dist/lib/registration-owners.js +77 -0
  170. package/dist/lib/registration-owners.js.map +1 -0
  171. package/dist/lib/release-index.d.ts +53 -0
  172. package/dist/lib/release-index.d.ts.map +1 -0
  173. package/dist/lib/release-index.js +162 -0
  174. package/dist/lib/release-index.js.map +1 -0
  175. package/dist/lib/repeating-timers.d.ts +79 -0
  176. package/dist/lib/repeating-timers.d.ts.map +1 -0
  177. package/dist/lib/repeating-timers.js +189 -0
  178. package/dist/lib/repeating-timers.js.map +1 -0
  179. package/dist/lib/source-text.d.ts +34 -0
  180. package/dist/lib/source-text.d.ts.map +1 -0
  181. package/dist/lib/source-text.js +80 -0
  182. package/dist/lib/source-text.js.map +1 -0
  183. package/dist/lib/specifiers.d.ts +20 -0
  184. package/dist/lib/specifiers.d.ts.map +1 -0
  185. package/dist/lib/specifiers.js +130 -0
  186. package/dist/lib/specifiers.js.map +1 -0
  187. package/dist/lib/sql-tables.d.ts +166 -0
  188. package/dist/lib/sql-tables.d.ts.map +1 -0
  189. package/dist/lib/sql-tables.js +464 -0
  190. package/dist/lib/sql-tables.js.map +1 -0
  191. package/dist/lib/switchable-modules.d.ts +54 -0
  192. package/dist/lib/switchable-modules.d.ts.map +1 -0
  193. package/dist/lib/switchable-modules.js +104 -0
  194. package/dist/lib/switchable-modules.js.map +1 -0
  195. package/dist/lib/tailwind-sources.d.ts +136 -0
  196. package/dist/lib/tailwind-sources.d.ts.map +1 -0
  197. package/dist/lib/tailwind-sources.js +307 -0
  198. package/dist/lib/tailwind-sources.js.map +1 -0
  199. package/dist/lib/ui-layer.d.ts +54 -0
  200. package/dist/lib/ui-layer.d.ts.map +1 -0
  201. package/dist/lib/ui-layer.js +57 -0
  202. package/dist/lib/ui-layer.js.map +1 -0
  203. package/dist/lib/workspace-packages.d.ts +186 -0
  204. package/dist/lib/workspace-packages.d.ts.map +1 -0
  205. package/dist/lib/workspace-packages.js +351 -0
  206. package/dist/lib/workspace-packages.js.map +1 -0
  207. package/dist/new-instance/deploy.d.ts +211 -0
  208. package/dist/new-instance/deploy.d.ts.map +1 -0
  209. package/dist/new-instance/deploy.js +1381 -0
  210. package/dist/new-instance/deploy.js.map +1 -0
  211. package/dist/new-instance/docs-toolchain.d.ts +66 -0
  212. package/dist/new-instance/docs-toolchain.d.ts.map +1 -0
  213. package/dist/new-instance/docs-toolchain.js +69 -0
  214. package/dist/new-instance/docs-toolchain.js.map +1 -0
  215. package/dist/new-instance/host.d.ts +124 -0
  216. package/dist/new-instance/host.d.ts.map +1 -0
  217. package/dist/new-instance/host.js +276 -0
  218. package/dist/new-instance/host.js.map +1 -0
  219. package/dist/new-instance/index.d.ts +118 -0
  220. package/dist/new-instance/index.d.ts.map +1 -0
  221. package/dist/new-instance/index.js +567 -0
  222. package/dist/new-instance/index.js.map +1 -0
  223. package/dist/new-instance/modules.d.ts +188 -0
  224. package/dist/new-instance/modules.d.ts.map +1 -0
  225. package/dist/new-instance/modules.js +392 -0
  226. package/dist/new-instance/modules.js.map +1 -0
  227. package/dist/new-instance/template.d.ts +505 -0
  228. package/dist/new-instance/template.d.ts.map +1 -0
  229. package/dist/new-instance/template.js +1886 -0
  230. package/dist/new-instance/template.js.map +1 -0
  231. package/dist/new-module/emit.d.ts +67 -0
  232. package/dist/new-module/emit.d.ts.map +1 -0
  233. package/dist/new-module/emit.js +1393 -0
  234. package/dist/new-module/emit.js.map +1 -0
  235. package/dist/new-module/host.d.ts +67 -0
  236. package/dist/new-module/host.d.ts.map +1 -0
  237. package/dist/new-module/host.js +224 -0
  238. package/dist/new-module/host.js.map +1 -0
  239. package/dist/new-module/index.d.ts +32 -0
  240. package/dist/new-module/index.d.ts.map +1 -0
  241. package/dist/new-module/index.js +193 -0
  242. package/dist/new-module/index.js.map +1 -0
  243. package/dist/new-module/spec.d.ts +188 -0
  244. package/dist/new-module/spec.d.ts.map +1 -0
  245. package/dist/new-module/spec.js +404 -0
  246. package/dist/new-module/spec.js.map +1 -0
  247. package/dist/new-module/text.d.ts +15 -0
  248. package/dist/new-module/text.d.ts.map +1 -0
  249. package/dist/new-module/text.js +22 -0
  250. package/dist/new-module/text.js.map +1 -0
  251. package/dist/new-storefront/dockerfile.d.ts +20 -0
  252. package/dist/new-storefront/dockerfile.d.ts.map +1 -0
  253. package/dist/new-storefront/dockerfile.js +131 -0
  254. package/dist/new-storefront/dockerfile.js.map +1 -0
  255. package/dist/new-storefront/gitignore.d.ts +23 -0
  256. package/dist/new-storefront/gitignore.d.ts.map +1 -0
  257. package/dist/new-storefront/gitignore.js +39 -0
  258. package/dist/new-storefront/gitignore.js.map +1 -0
  259. package/dist/new-storefront/index.d.ts +93 -0
  260. package/dist/new-storefront/index.d.ts.map +1 -0
  261. package/dist/new-storefront/index.js +329 -0
  262. package/dist/new-storefront/index.js.map +1 -0
  263. package/dist/new-storefront/npmrc.d.ts +98 -0
  264. package/dist/new-storefront/npmrc.d.ts.map +1 -0
  265. package/dist/new-storefront/npmrc.js +217 -0
  266. package/dist/new-storefront/npmrc.js.map +1 -0
  267. package/dist/new-storefront/reference.d.ts +189 -0
  268. package/dist/new-storefront/reference.d.ts.map +1 -0
  269. package/dist/new-storefront/reference.js +430 -0
  270. package/dist/new-storefront/reference.js.map +1 -0
  271. package/dist/new-storefront/rewrite.d.ts +171 -0
  272. package/dist/new-storefront/rewrite.d.ts.map +1 -0
  273. package/dist/new-storefront/rewrite.js +701 -0
  274. package/dist/new-storefront/rewrite.js.map +1 -0
  275. package/dist/release-index.json +284 -0
  276. package/dist/rules/action-route-permissions.d.ts +141 -0
  277. package/dist/rules/action-route-permissions.d.ts.map +1 -0
  278. package/dist/rules/action-route-permissions.js +556 -0
  279. package/dist/rules/action-route-permissions.js.map +1 -0
  280. package/dist/rules/bundle-pairing.d.ts +74 -0
  281. package/dist/rules/bundle-pairing.d.ts.map +1 -0
  282. package/dist/rules/bundle-pairing.js +281 -0
  283. package/dist/rules/bundle-pairing.js.map +1 -0
  284. package/dist/rules/channel-resolution.d.ts +17 -0
  285. package/dist/rules/channel-resolution.d.ts.map +1 -0
  286. package/dist/rules/channel-resolution.js +382 -0
  287. package/dist/rules/channel-resolution.js.map +1 -0
  288. package/dist/rules/command-coverage.d.ts +210 -0
  289. package/dist/rules/command-coverage.d.ts.map +1 -0
  290. package/dist/rules/command-coverage.js +714 -0
  291. package/dist/rules/command-coverage.js.map +1 -0
  292. package/dist/rules/container-imports.d.ts +60 -0
  293. package/dist/rules/container-imports.d.ts.map +1 -0
  294. package/dist/rules/container-imports.js +158 -0
  295. package/dist/rules/container-imports.js.map +1 -0
  296. package/dist/rules/default-language-prose.d.ts +212 -0
  297. package/dist/rules/default-language-prose.d.ts.map +1 -0
  298. package/dist/rules/default-language-prose.js +710 -0
  299. package/dist/rules/default-language-prose.js.map +1 -0
  300. package/dist/rules/diacritic-folds.d.ts +238 -0
  301. package/dist/rules/diacritic-folds.d.ts.map +1 -0
  302. package/dist/rules/diacritic-folds.js +681 -0
  303. package/dist/rules/diacritic-folds.js.map +1 -0
  304. package/dist/rules/entity-tenant-classification.d.ts +171 -0
  305. package/dist/rules/entity-tenant-classification.d.ts.map +1 -0
  306. package/dist/rules/entity-tenant-classification.js +323 -0
  307. package/dist/rules/entity-tenant-classification.js.map +1 -0
  308. package/dist/rules/entry-presence.d.ts +142 -0
  309. package/dist/rules/entry-presence.d.ts.map +1 -0
  310. package/dist/rules/entry-presence.js +339 -0
  311. package/dist/rules/entry-presence.js.map +1 -0
  312. package/dist/rules/entry-scope.d.ts +91 -0
  313. package/dist/rules/entry-scope.d.ts.map +1 -0
  314. package/dist/rules/entry-scope.js +404 -0
  315. package/dist/rules/entry-scope.js.map +1 -0
  316. package/dist/rules/env-inputs.d.ts +222 -0
  317. package/dist/rules/env-inputs.d.ts.map +1 -0
  318. package/dist/rules/env-inputs.js +951 -0
  319. package/dist/rules/env-inputs.js.map +1 -0
  320. package/dist/rules/kernel-boundary.d.ts +37 -0
  321. package/dist/rules/kernel-boundary.d.ts.map +1 -0
  322. package/dist/rules/kernel-boundary.js +195 -0
  323. package/dist/rules/kernel-boundary.js.map +1 -0
  324. package/dist/rules/nul-bytes.d.ts +233 -0
  325. package/dist/rules/nul-bytes.d.ts.map +1 -0
  326. package/dist/rules/nul-bytes.js +332 -0
  327. package/dist/rules/nul-bytes.js.map +1 -0
  328. package/dist/rules/platform-surface.d.ts +479 -0
  329. package/dist/rules/platform-surface.d.ts.map +1 -0
  330. package/dist/rules/platform-surface.js +749 -0
  331. package/dist/rules/platform-surface.js.map +1 -0
  332. package/dist/rules/port-catches.d.ts +225 -0
  333. package/dist/rules/port-catches.d.ts.map +1 -0
  334. package/dist/rules/port-catches.js +1374 -0
  335. package/dist/rules/port-catches.js.map +1 -0
  336. package/dist/rules/port-shape.d.ts +213 -0
  337. package/dist/rules/port-shape.d.ts.map +1 -0
  338. package/dist/rules/port-shape.js +670 -0
  339. package/dist/rules/port-shape.js.map +1 -0
  340. package/dist/rules/queue-names.d.ts +108 -0
  341. package/dist/rules/queue-names.d.ts.map +1 -0
  342. package/dist/rules/queue-names.js +395 -0
  343. package/dist/rules/queue-names.js.map +1 -0
  344. package/dist/rules/singleton-identity.d.ts +205 -0
  345. package/dist/rules/singleton-identity.d.ts.map +1 -0
  346. package/dist/rules/singleton-identity.js +830 -0
  347. package/dist/rules/singleton-identity.js.map +1 -0
  348. package/dist/rules/subscribe-seam.d.ts +121 -0
  349. package/dist/rules/subscribe-seam.d.ts.map +1 -0
  350. package/dist/rules/subscribe-seam.js +594 -0
  351. package/dist/rules/subscribe-seam.js.map +1 -0
  352. package/dist/rules/transaction-context.d.ts +40 -0
  353. package/dist/rules/transaction-context.d.ts.map +1 -0
  354. package/dist/rules/transaction-context.js +294 -0
  355. package/dist/rules/transaction-context.js.map +1 -0
  356. package/package.json +59 -0
@@ -0,0 +1,828 @@
1
+ // The deployment divergence report — the two renderings, from one derivation,
2
+ // and the assembly the report's two hosts share.
3
+ //
4
+ // ## Why this is in the package
5
+ //
6
+ // It was `backend/src/overlay/divergence-report.ts` until
7
+ // `specs/110-instance-repository/` T138a. The derivation beside it
8
+ // (`divergence.ts`) moved out of `backend/scripts/` in the same task and for the
9
+ // same reason: the report has **two hosts** — `overlay:divergence` over this
10
+ // repository, and `endora generate` inside a client's instance, whose
11
+ // `apps/<deployment>/` tree is the very thing `instance-tree.md` §2.2 writes and
12
+ // which nothing could render a report over. `instance-repository.md` R3.5 is
13
+ // that the population is a parameter and the renderer is one program, which is
14
+ // what `admin-artefacts.ts` and `docs-artefacts.ts` already do for the two other
15
+ // artefact families an instance builds from.
16
+ //
17
+ // `backend/test/unit/kernel/host-residue-partition.test.ts` had already
18
+ // classified the file as platform-shaped residue and named its retiring
19
+ // condition as *"the `overlay/` half of FR-013"* — the platform. That premise is
20
+ // **corrected rather than met**: the platform is a runtime dependency of every
21
+ // instance and a renderer has no runtime reader, while the CLI already has the
22
+ // compiler, is a `devDependency` and is the tool that generates an instance's
23
+ // other artefacts. The ledger entry retires here.
24
+ //
25
+ // **One derivation, two renders** (`data-model.md` §3). The alternative — a
26
+ // second derivation for the human rendering — was rejected as two answers to one
27
+ // question waiting to disagree; `serializeManifestModule` already had this shape
28
+ // and feature 100's generated module map is the precedent for the `.md`.
29
+ //
30
+ // The report supersedes the v2 override manifest, which recorded exactly one
31
+ // fact: which overlay modules a deployment adds. That field survives here as
32
+ // `overlayModules`, and it is legitimately empty for a deployment shipping no
33
+ // overlay module — so `overlay:check`'s `empty` verdict does not apply to either
34
+ // rendering. A deployment that diverges by nothing says so explicitly, which is
35
+ // the whole difference between an empty report and an absent one.
36
+ //
37
+ // ## The headers are parameters, on `admin-artefacts.ts`' precedent
38
+ //
39
+ // The two hosts tell a reader different things to run — `overlay:divergence`
40
+ // here, `endora generate` in an instance — and an artefact naming the wrong one
41
+ // would send a client to a script their tree does not have. The defaults are
42
+ // this repository's exact strings, so its six committed artefacts are
43
+ // byte-identical across the move.
44
+ //
45
+ // **It reaches for nothing outside itself since `specs/110-instance-repository/`
46
+ // T114a.** Its one application dependency was `repoRoot`, imported for exactly
47
+ // one consumer — `repoRelativePath`, an export with no caller anywhere in the
48
+ // repository. (The four apparent hits in `check-module-boundary.ts` are an
49
+ // unrelated *parameter* name, `(repoRelativePath: string) => boolean`.) A dead
50
+ // export is the cheapest possible thing to be blocked on, and leaving it
51
+ // standing is what made this file look blocked: `contracts/application-root-supplier.md`
52
+ // §5.
53
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
54
+ import { dirname, join, relative, sep } from 'node:path';
55
+ import ts from 'typescript';
56
+ import { claimFileOnce, deriveDivergence, moduleContextSeams, routeIdentities, } from './divergence.js';
57
+ import { mergeRegistrationOwners, rootSuppliedNames, } from './registration-owners.js';
58
+ import { providedPortNames, registeredNames, rootRegisteredNames, } from './port-registrations.js';
59
+ /**
60
+ * What each rung costs, in the words a reader who has never seen this repository
61
+ * needs (SC-001).
62
+ *
63
+ * The human rendering spells the **cost** rather than the number, because a
64
+ * number is only meaningful to somebody who has the ladder open beside them —
65
+ * and the reader this artefact exists for is a client's developer who has not.
66
+ * The full ladder is `docs/docs/architecture/customisation-ladder.md`; these are
67
+ * its one-line summaries and they are kept in step by
68
+ * `test/unit/overlay/divergence-render.test.ts`.
69
+ */
70
+ export const RUNG_COSTS = {
71
+ 0: 'rung 0 — configuration. Costs nothing and survives every upgrade, because it is data.',
72
+ 1: 'rung 1 — subscribe to what the owner already emits. Costs nothing at the seam; you observe, you do not change what the owner did.',
73
+ 2: 'rung 2 — run before or after what the owner already serves. Costs a coupling to an endpoint identity, which is versioned public API.',
74
+ 3: 'rung 3 — a strategy port the owner published. Costs a dependency edge in your manifest; the owner keeps the seam and keeps fixing it behind you.',
75
+ 4: 'rung 4 — change what the container hands out. Costs a contract-version coupling to a shape nothing checks for you: `ctx.di.decorate<T>` asserts `T` at the call site and compares it to nothing.',
76
+ 5: 'rung 5 — fork. Costs everything, permanently.',
77
+ };
78
+ /** The heading each kind gets in the human rendering. */
79
+ const KIND_HEADINGS = {
80
+ omission: 'Modules this deployment does not ship',
81
+ registration: 'Services this deployment adds to the container',
82
+ 'port-provided': 'Ports this deployment publishes',
83
+ 'port-consumed': 'Ports this deployment consumes',
84
+ subscription: 'Events this deployment subscribes to',
85
+ interceptor: 'Endpoints this deployment intercepts',
86
+ decoration: 'Registrations this deployment wraps',
87
+ 'root-plugin': 'Plugins this deployment mounts at the server root',
88
+ worker: 'Queues this deployment consumes',
89
+ };
90
+ /** Stable order for the human rendering — most invasive last is not the point;
91
+ * a reader wants the same order every time, so it is the ladder's own. */
92
+ const KIND_ORDER = [
93
+ 'omission',
94
+ 'subscription',
95
+ 'interceptor',
96
+ 'port-provided',
97
+ 'port-consumed',
98
+ 'decoration',
99
+ 'root-plugin',
100
+ 'registration',
101
+ 'worker',
102
+ ];
103
+ /**
104
+ * The "do not edit" headers this repository's own generator writes.
105
+ *
106
+ * Parameters rather than literals, on `admin-artefacts.ts`' and
107
+ * `docs-artefacts.ts`' precedent: the two hosts tell a reader different things
108
+ * to run, and an artefact naming the wrong one would send a client to a script
109
+ * their tree does not have. The default is this repository's, so its committed
110
+ * artefacts are byte-identical across the move and a host that means the other
111
+ * one says so.
112
+ */
113
+ export const COMPOSER_DIVERGENCE_MODULE_HEADER = `// AUTO-GENERATED by scripts/generate-divergence.ts — DO NOT EDIT.\n` +
114
+ `// Run \`pnpm --filter backend run overlay:divergence\` (or rebuild) to refresh.\n` +
115
+ `// Deterministic record of this deployment's divergence from core.\n`;
116
+ /** The same, for the human rendering, whose comment syntax is HTML's. */
117
+ export const COMPOSER_DIVERGENCE_MARKDOWN_HEADER = [
118
+ '<!-- AUTO-GENERATED by scripts/generate-divergence.ts \u2014 DO NOT EDIT. -->',
119
+ '<!-- Run `pnpm --filter backend run overlay:divergence` (or rebuild) to refresh. -->',
120
+ ];
121
+ /**
122
+ * Emit the report as a committed, typed TS module (mirrors
123
+ * `manifest-index.generated.ts`).
124
+ *
125
+ * `typesImportSpecifier` is the module-resolvable path from the emitted file's
126
+ * directory to `src/overlay/types.js` — it differs between the core output
127
+ * (`./types.js`) and a deployment output (`../../overlay/types.js`), so the
128
+ * caller computes and passes it.
129
+ */
130
+ export function serializeDivergenceModule(report, typesImportSpecifier, header = COMPOSER_DIVERGENCE_MODULE_HEADER) {
131
+ const body = JSON.stringify(report, null, 2);
132
+ return `${header}
133
+ import type { DivergenceReport } from '${typesImportSpecifier}';
134
+
135
+ export const DIVERGENCE_REPORT: DivergenceReport = ${body};
136
+ `;
137
+ }
138
+ /** Stable JSON serialization, for a caller that wants the shape and no module. */
139
+ export function serializeDivergenceJson(report) {
140
+ return `${JSON.stringify(report, null, 2)}\n`;
141
+ }
142
+ function escapeCell(text) {
143
+ return text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
144
+ }
145
+ function ownerCell(entry) {
146
+ if (entry.kind === 'omission')
147
+ return '—';
148
+ return entry.owner === null ? 'a composition root (no module owns it)' : `\`${entry.owner}\``;
149
+ }
150
+ function subjectCell(entry) {
151
+ if (entry.kind === 'interceptor' && entry.detail.kind === 'interceptor') {
152
+ return `\`${entry.subject}\` (${entry.detail.phase})`;
153
+ }
154
+ return `\`${entry.subject}\``;
155
+ }
156
+ /**
157
+ * The human rendering — D-30's own word for this artefact is *report*.
158
+ *
159
+ * Three rules, all of them SC-001's: no key strings (a reader is not looking
160
+ * anything up), no repository-relative paths (the reader's tree is not this
161
+ * one), and the rung's **cost** spelled out rather than its number.
162
+ */
163
+ export function renderDivergenceMarkdown(report, header = COMPOSER_DIVERGENCE_MARKDOWN_HEADER) {
164
+ const lines = [];
165
+ const isCore = report.deployment === 'core';
166
+ lines.push(...header);
167
+ lines.push('');
168
+ lines.push(isCore
169
+ ? '# Divergence from core: bare core'
170
+ : `# Divergence from core: \`${report.deployment}\``);
171
+ lines.push('');
172
+ lines.push('Every way this deployment differs from the platform it was built on, derived from its own', 'tree. It is the first thing to consult when an upgrade changes behaviour you depended on.', '', 'Each entry names **what** was changed, **which of this deployment’s modules** changed it,', '**which module owns** the thing that was changed, what the change **costs**, and the', 'sentence this deployment wrote when it made the change.', '');
173
+ if (isCore) {
174
+ lines.push('This is the bare-core build: no deployment is selected, so there is nothing to diverge', 'from core *with*. It is generated and committed anyway, because an absent report and a', 'report that says "nothing" are indistinguishable, and only one of them is a statement.', '');
175
+ }
176
+ lines.push('## What this deployment adds');
177
+ lines.push('');
178
+ if (report.overlayModules.length === 0) {
179
+ lines.push('No overlay modules. This deployment ships the platform’s own module set.');
180
+ }
181
+ else {
182
+ lines.push('| Overlay module |');
183
+ lines.push('| --- |');
184
+ for (const moduleId of report.overlayModules)
185
+ lines.push(`| \`${moduleId}\` |`);
186
+ }
187
+ lines.push('');
188
+ lines.push('## What this deployment changes');
189
+ lines.push('');
190
+ if (report.entries.length === 0) {
191
+ lines.push('Nothing. This deployment changes no registration, intercepts no endpoint, subscribes to no', 'event, publishes and consumes no port, mounts no root plugin and omits no module.', '', 'That is a statement rather than an absence: the derivation ran over this deployment’s own', 'tree and found no divergence of any recorded kind.');
192
+ lines.push('');
193
+ }
194
+ else {
195
+ for (const kind of KIND_ORDER) {
196
+ const entries = report.entries.filter((entry) => entry.kind === kind);
197
+ if (entries.length === 0)
198
+ continue;
199
+ lines.push(`### ${KIND_HEADINGS[kind]}`);
200
+ lines.push('');
201
+ const rungs = [...new Set(entries.map((entry) => entry.rung))].filter((rung) => rung !== null);
202
+ for (const rung of rungs.sort((a, b) => a - b)) {
203
+ const cost = RUNG_COSTS[rung];
204
+ if (cost !== undefined)
205
+ lines.push(`${cost}`, '');
206
+ }
207
+ lines.push('| What | Changed by | Owned by | Why |');
208
+ lines.push('| --- | --- | --- | --- |');
209
+ for (const entry of entries) {
210
+ const changedBy = entry.kind === 'omission' ? '—' : `\`${entry.module}\``;
211
+ lines.push(`| ${escapeCell(subjectCell(entry))} | ${changedBy} | ${escapeCell(ownerCell(entry))} | ` +
212
+ `${escapeCell(entry.reason.length === 0 ? '**not declared**' : entry.reason)} |`);
213
+ }
214
+ lines.push('');
215
+ }
216
+ }
217
+ lines.push('## What this report does not cover');
218
+ lines.push('');
219
+ lines.push('A report that lists what it records and says nothing about the rest is indistinguishable', 'from a complete one. These are the seams that exist and are deliberately not divergences,', 'and the facts only a running instance can answer.', '');
220
+ lines.push('### Seams that are a module’s own business');
221
+ lines.push('');
222
+ lines.push('| Seam | Why it is not a divergence |');
223
+ lines.push('| --- | --- |');
224
+ for (const entry of report.boundary.notRecorded) {
225
+ lines.push(`| \`${escapeCell(entry.seam)}\` | ${escapeCell(entry.why)} |`);
226
+ }
227
+ lines.push('');
228
+ lines.push('### Facts only a running instance knows');
229
+ lines.push('');
230
+ lines.push('| Fact | Why it is not here |');
231
+ lines.push('| --- | --- |');
232
+ for (const entry of report.boundary.runtimeOnly) {
233
+ lines.push(`| ${escapeCell(entry.fact)} | ${escapeCell(entry.why)} |`);
234
+ }
235
+ lines.push('');
236
+ return `${lines.join('\n')}\n`;
237
+ }
238
+ // ---------------------------------------------------------------------------
239
+ // The assembly both hosts share
240
+ // ---------------------------------------------------------------------------
241
+ /**
242
+ * Every TypeScript or emitted-JavaScript source under a root, recursively.
243
+ *
244
+ * One walk rather than one per host, because the two hosts read two *different
245
+ * dialects of the same tree* — this repository's modules are sources and an
246
+ * instance's are the `dist` those sources compile to — and the analysis is the
247
+ * compiler API either way (`registeredNames` and `routeIdentities` both parse
248
+ * with `ts.createSourceFile`, which reads emitted JavaScript as readily as it
249
+ * reads TypeScript). Measured on `packages/modules/blog`: the six container
250
+ * names and the twenty-nine route identities its sources yield are the same six
251
+ * and the same twenty-nine its `dist/backend` yields.
252
+ *
253
+ * `.d.ts` is excluded here and asked for separately by {@link seamsFromKernel},
254
+ * which is the one input whose instance answer is a declaration file.
255
+ */
256
+ export function walkAnalysableSources(root, out = []) {
257
+ let entries;
258
+ try {
259
+ entries = readdirSync(root, { withFileTypes: true });
260
+ }
261
+ catch {
262
+ return out;
263
+ }
264
+ for (const entry of entries) {
265
+ const full = join(root, entry.name);
266
+ if (entry.isDirectory()) {
267
+ if (entry.name === 'node_modules' || entry.name.startsWith('.'))
268
+ continue;
269
+ walkAnalysableSources(full, out);
270
+ }
271
+ else if (/\.(?:[cm]?js|ts)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
272
+ out.push(full);
273
+ }
274
+ }
275
+ return out;
276
+ }
277
+ /**
278
+ * A deployment's own overlay sources, attributed to the overlay module that
279
+ * owns them.
280
+ *
281
+ * `base` is the root the reported path is relative to — this repository's root
282
+ * here, the instance's root there — so an entry names a path in the reader's own
283
+ * tree and never in ours (SC-001's second rule).
284
+ */
285
+ export function overlaySourcesUnder(overlayRoot, moduleIds, base) {
286
+ if (overlayRoot === null)
287
+ return [];
288
+ const sources = [];
289
+ for (const moduleId of moduleIds) {
290
+ for (const file of walkAnalysableSources(join(overlayRoot, moduleId))) {
291
+ sources.push({
292
+ moduleId,
293
+ file: relative(base, file).split(sep).join('/'),
294
+ text: readFileSync(file, 'utf8'),
295
+ });
296
+ }
297
+ }
298
+ return sources;
299
+ }
300
+ /**
301
+ * One derivation, N renderings (`data-model.md` §3).
302
+ *
303
+ * The loop is here rather than in either host for the reason the file header
304
+ * gives: a second derivation for a second rendering is two answers to one
305
+ * question waiting to disagree, and the moment there were three renderings
306
+ * across two hosts that stopped being hypothetical.
307
+ */
308
+ export function renderDivergenceArtefacts(input, specs) {
309
+ const result = deriveDivergence(input);
310
+ const renderings = specs.map((spec) => {
311
+ switch (spec.rendering) {
312
+ case 'module':
313
+ return {
314
+ outputPath: spec.outputPath,
315
+ content: serializeDivergenceModule(result.report, spec.typesImportSpecifier, spec.header ?? COMPOSER_DIVERGENCE_MODULE_HEADER),
316
+ label: spec.label,
317
+ };
318
+ case 'markdown':
319
+ return {
320
+ outputPath: spec.outputPath,
321
+ content: renderDivergenceMarkdown(result.report, spec.header ?? COMPOSER_DIVERGENCE_MARKDOWN_HEADER),
322
+ label: spec.label,
323
+ };
324
+ case 'json':
325
+ return {
326
+ outputPath: spec.outputPath,
327
+ content: serializeDivergenceJson(result.report),
328
+ label: spec.label,
329
+ };
330
+ }
331
+ });
332
+ return { result, renderings };
333
+ }
334
+ /**
335
+ * Does the overlay tree **spell** a seam call at all?
336
+ *
337
+ * A second author for the "no seam call of any kind read" refusal
338
+ * (`divergence-report.md` §5, refusal 3), and the reason it is a text probe
339
+ * rather than a second walk: what has to be caught is the *syntax walk* going
340
+ * blind, and a second syntax walk would go blind with it. With two overlay
341
+ * modules in this repository, a resolver that stopped recognising
342
+ * `ctx.di.decorate` prints a clean report over a tree full of decorations, and
343
+ * `sites=0` is indistinguishable from a deployment that only registers routes —
344
+ * which is a legal thing for an overlay module to do. This tells the two apart.
345
+ */
346
+ export function overlayTreeSpellsASeamCall(sources) {
347
+ const spellings = [
348
+ 'di.register(',
349
+ 'di.providePort(',
350
+ 'di.decorate(',
351
+ '.subscribe(',
352
+ '.interceptors(',
353
+ '.rootPlugin(',
354
+ '.worker(',
355
+ 'lazyPort(',
356
+ 'lazyPort<',
357
+ ];
358
+ return sources.some((source) => spellings.some((spelling) => source.text.includes(spelling)));
359
+ }
360
+ // ---------------------------------------------------------------------------
361
+ // The instance host's half: a composition made of installed packages
362
+ // ---------------------------------------------------------------------------
363
+ /**
364
+ * What an instance's report cannot derive, each with a reason (FR-018).
365
+ *
366
+ * **Every one of the nine kinds is derived in an instance exactly as it is
367
+ * here**, because the population of all nine is the deployment's own overlay
368
+ * tree — `apps/<deployment>/modules/**` — which is the client's own source and
369
+ * is read by the same walk. What differs is **attribution**, in one place, and
370
+ * that one place is written down here rather than left to be discovered by a
371
+ * client reading a report with a silent hole in it.
372
+ *
373
+ * `HOST_REGISTERED_PORTS` is the bridging table `check:port-dependencies`
374
+ * carries: names this repository's composition roots register *on an unconverted
375
+ * module's behalf*, each with its own retiring condition. It is a judgement about
376
+ * this repository's roots and nothing derives it, so it has no instance
377
+ * equivalent — in an instance those names are registered by `composeApp` inside
378
+ * `@endora-commerce/platform`, the walk finds them there, and they come out as
379
+ * root-supplied. That answer is *true of the instance*; it is simply less
380
+ * specific than ours, and a client who did not know that would read `a
381
+ * composition root (no module owns it)` and conclude nothing owns it.
382
+ */
383
+ export const INSTANCE_BOUNDARY_NOTES = [
384
+ {
385
+ seam: 'the module behind a container name the platform registers on its behalf',
386
+ why: "such a name is reported as a composition root's rather than as that module's. The " +
387
+ 'mapping is a judgement about a composition root, not a fact a walk produces, and this ' +
388
+ 'instance has no composition root of its own — `composeApp` is the platform’s. Every ' +
389
+ 'name a module registers for itself is attributed to that module — this deployment’s ' +
390
+ 'own overlay modules included, from their own sources — which is every name ' +
391
+ 'an overlay module is likely to decorate',
392
+ },
393
+ {
394
+ seam: 'what an installed module package would register if it shipped its sources',
395
+ why: 'container names and route identities are read from each package’s published `./backend` ' +
396
+ 'artefact, which is the module version this instance installed. That is the composition ' +
397
+ 'this deployment actually runs; it is not the module’s source tree, and a name reachable ' +
398
+ 'only from a layer the package does not publish is in no owner map',
399
+ },
400
+ ];
401
+ /** The absolute path a package's declared `exports` subpath resolves to. */
402
+ function subpathTargetOf(pkg, subpath) {
403
+ const target = pkg.exports.get(subpath);
404
+ if (target === undefined)
405
+ return null;
406
+ return join(pkg.dir, target.replace(/^\.\//, ''));
407
+ }
408
+ /**
409
+ * `ModuleContext`'s members, out of whatever file beside the platform's
410
+ * `./kernel` entry point declares the interface.
411
+ *
412
+ * A **search** rather than a spelled path, and deliberately: the entry point is
413
+ * a barrel and re-exports the interface rather than declaring it, so the
414
+ * declaration is in a sibling — and which sibling is the platform's own business
415
+ * and its build layout's, neither of which this file may assert (D-100). The
416
+ * interface *name* is the one thing that is contract here, and it is already
417
+ * spelled exactly once, in `moduleContextSeams`.
418
+ */
419
+ export function seamsFromKernel(kernelEntry) {
420
+ const directory = dirname(kernelEntry);
421
+ let names;
422
+ try {
423
+ names = readdirSync(directory).sort();
424
+ }
425
+ catch {
426
+ return [];
427
+ }
428
+ for (const name of names) {
429
+ if (!/\.ts$/.test(name))
430
+ continue;
431
+ const full = join(directory, name);
432
+ try {
433
+ if (!statSync(full).isFile())
434
+ continue;
435
+ }
436
+ catch {
437
+ continue;
438
+ }
439
+ const seams = moduleContextSeams(readFileSync(full, 'utf8'));
440
+ if (seams.length > 0)
441
+ return seams;
442
+ }
443
+ return [];
444
+ }
445
+ /**
446
+ * The composition an instance runs, read out of what it installed.
447
+ *
448
+ * Three of the four facts come from the same two walks: each module package's
449
+ * published `./backend` layer, and the platform's own. The fourth —
450
+ * `ModuleContext`'s members — comes from the platform's shipped kernel
451
+ * declarations, which yield byte-identically what this repository's source does
452
+ * (measured: the same sixteen members, in the same order).
453
+ *
454
+ * A package that publishes no `./backend` subpath is an admin- or
455
+ * storefront-only module: it registers nothing and serves nothing, so it is
456
+ * *readable and empty* rather than unreadable, exactly as
457
+ * `package-declarations.ts` classifies the same state. A package that publishes
458
+ * one whose target the walk cannot open is **named**, because an unread package
459
+ * is a package whose registrations are in no owner map and every decoration of
460
+ * one of its names would read `unowned-subject` — a finding about the run
461
+ * dressed as a finding about the tree.
462
+ */
463
+ export function instanceComposition(input) {
464
+ const claims = [];
465
+ const platformNames = new Set();
466
+ const routeSources = [];
467
+ const unreadable = [];
468
+ const claimFile = claimFileOnce();
469
+ let filesRead = 0;
470
+ let covered = 0;
471
+ let expected = 0;
472
+ for (const pkg of input.packages) {
473
+ const backend = subpathTargetOf(pkg, './backend');
474
+ if (backend === null)
475
+ continue;
476
+ expected += 1;
477
+ const files = walkAnalysableSources(dirname(backend));
478
+ if (files.length === 0) {
479
+ unreadable.push({
480
+ packageName: pkg.name,
481
+ at: backend,
482
+ reason: 'it publishes a "./backend" subpath beside which the walk found no source at all, so ' +
483
+ 'the container names and the routes it owns are in no map',
484
+ });
485
+ continue;
486
+ }
487
+ covered += 1;
488
+ for (const file of files) {
489
+ const text = readFileSync(file, 'utf8');
490
+ filesRead += 1;
491
+ if (claimFile(file))
492
+ routeSources.push({ file, text, moduleId: pkg.moduleId });
493
+ for (const name of registeredNames(text, file))
494
+ claims.push({ name, moduleId: pkg.moduleId });
495
+ for (const name of providedPortNames(text, file)) {
496
+ claims.push({ name, moduleId: pkg.moduleId });
497
+ }
498
+ }
499
+ }
500
+ let seams = [];
501
+ if (input.platform !== null) {
502
+ for (const file of walkAnalysableSources(input.platform.dir)) {
503
+ const text = readFileSync(file, 'utf8');
504
+ filesRead += 1;
505
+ if (claimFile(file))
506
+ routeSources.push({ file, text, moduleId: null });
507
+ for (const name of registeredNames(text, file))
508
+ platformNames.add(name);
509
+ for (const name of providedPortNames(text, file))
510
+ platformNames.add(name);
511
+ // The **root** spelling as well, and it is not an optimisation: in an
512
+ // instance `composeApp` is the composition root and it writes
513
+ // `registerValues(container, { … })`, which `registeredNames` does not
514
+ // read. Measured on a real scaffolded instance with only the two module
515
+ // spellings: an overlay module decorating `commandBus` was reported
516
+ // `unowned-subject`, which is the exact state `rootSuppliedNames`' own doc
517
+ // block says it exists to prevent — a finding about the run dressed as one
518
+ // about the tree.
519
+ for (const name of rootRegisteredNames(text, file))
520
+ platformNames.add(name);
521
+ }
522
+ const kernel = subpathTargetOf(input.platform, './kernel');
523
+ if (kernel !== null)
524
+ seams = seamsFromKernel(kernel);
525
+ }
526
+ // `hostRegistered` is empty on purpose and the emptiness is the narrowing
527
+ // `INSTANCE_BOUNDARY_NOTES` states: the bridging table is a judgement about
528
+ // *this repository's* composition roots, and an instance has none of its own.
529
+ const owners = mergeRegistrationOwners({
530
+ hostRegistered: {},
531
+ treeClaims: [],
532
+ packageClaims: claims,
533
+ });
534
+ return {
535
+ environment: {
536
+ owners,
537
+ // The platform's own registrations are both halves at once here: it is the
538
+ // kernel *and* the composition root an instance runs, `composeApp` being
539
+ // the platform's. `rootSuppliedNames` drops every name a module claimed,
540
+ // so a module that registers a name the platform also spells keeps it.
541
+ rootSupplied: rootSuppliedNames({ rootNames: [], kernelNames: platformNames, owners }),
542
+ routes: routeIdentities(routeSources),
543
+ seams,
544
+ filesRead,
545
+ },
546
+ unreadable,
547
+ covered,
548
+ expected,
549
+ };
550
+ }
551
+ /**
552
+ * The scan's environment, with the **deployment's own** overlay registrations
553
+ * merged into the owner map (`specs/124-instance-customisation-gap/` FR-005,
554
+ * FR-006, FR-007).
555
+ *
556
+ * ## Why it is a second step rather than a fourth input to `instanceComposition`
557
+ *
558
+ * That function is documented as *"everything one run needs that does not change
559
+ * between deployments"*, and the overlay claim set is **per deployment** — a
560
+ * report is rendered once per directory under `apps/`. The package half is where
561
+ * the scan's cost is (one platform walk plus one per installed package, against
562
+ * one small overlay tree), so hoisting it and merging here is the shape that
563
+ * repairs the defect without making the expensive half run once per deployment.
564
+ *
565
+ * ## Why it is a conformance repair and not a widening
566
+ *
567
+ * `contracts/divergence-report.md` §3.2 specifies the owner map as *"module
568
+ * sources plus each installed package's `./backend` artefact"*, and this
569
+ * repository's own host already obeys it: `moduleWalkRoots` contains
570
+ * `overlayRoot`. The instance host was the one out of conformance — it built
571
+ * `owners` from installed packages and `rootSupplied` from the platform walk,
572
+ * and the client's own overlay tree was in neither. Measured by A8 of the
573
+ * instance acceptance criterion: one rendering listing
574
+ * `registration:<overlay>:<name>` and reporting `unowned-subject` for `<name>`,
575
+ * whose remedy sentence — *"Composition throws for it at boot"* — was untrue of
576
+ * a tree that had just booted.
577
+ *
578
+ * ## The claim is keyed from `OverlaySource.moduleId`, never from `moduleOf`
579
+ *
580
+ * FR-006, and it is not a preference: `moduleOf`'s overlay branch requires
581
+ * `/src/apps/` in the path and an instance's overlay root is
582
+ * `<root>/apps/<deployment>/modules/`, so a claim placed by path would attribute
583
+ * nothing at all here. `overlaySourcesUnder` already carries the id, from the
584
+ * directory the walk descended into.
585
+ *
586
+ * The precedence is `registration-owners.ts`': the deployment's own tree is the
587
+ * **tree** half and overwrites, an installed package claims only what nothing
588
+ * above claimed. That is the right way round — a name the client registers in
589
+ * their own overlay module is theirs — and it is the same rule this repository's
590
+ * host applies to its own `backend/src/apps/` sources.
591
+ */
592
+ export function withOverlayRegistrationOwners(environment, sources) {
593
+ const overlayClaims = [];
594
+ for (const source of sources) {
595
+ for (const name of registeredNames(source.text, source.file)) {
596
+ overlayClaims.push({ name, moduleId: source.moduleId });
597
+ }
598
+ for (const name of providedPortNames(source.text, source.file)) {
599
+ overlayClaims.push({ name, moduleId: source.moduleId });
600
+ }
601
+ }
602
+ if (overlayClaims.length === 0)
603
+ return environment;
604
+ const owners = mergeRegistrationOwners({
605
+ // The scan's map seeds this one, in the slot the merge rule gives to a
606
+ // claim already settled: the packages have been merged against each other
607
+ // and the question here is only what the deployment adds on top.
608
+ hostRegistered: Object.fromEntries(environment.owners),
609
+ treeClaims: overlayClaims,
610
+ packageClaims: [],
611
+ });
612
+ return {
613
+ ...environment,
614
+ owners,
615
+ // A name a module owns is not root-supplied, and the deployment's overlay
616
+ // module is a module: without this, a client registering a name the platform
617
+ // also spells would have it in both, and `deriveDivergence` reads
618
+ // `rootSupplied` only to decide that an absent owner is legitimate.
619
+ rootSupplied: new Set([...environment.rootSupplied].filter((name) => owners.get(name) === undefined)),
620
+ };
621
+ }
622
+ /** The refusal sentence, or `null` when every package this run needed was readable. */
623
+ export function unreadableCompositionReason(scan) {
624
+ if (scan.unreadable.length === 0)
625
+ return null;
626
+ const named = scan.unreadable
627
+ .map((entry) => `${entry.packageName}: ${entry.reason} (${entry.at})`)
628
+ .join('; ');
629
+ return (`${scan.unreadable.length} of the ${scan.expected} installed module package(s) publishing a ` +
630
+ `"./backend" subpath could not be read, so what they register is in no owner map and every ` +
631
+ `decoration of one of their names would be reported as owned by nobody — ${named}`);
632
+ }
633
+ /** The empty declaration — what a deployment that has declared nothing says. */
634
+ export const EMPTY_DECLARATION = {
635
+ omittedModules: [],
636
+ decorationOrder: {},
637
+ reasons: {},
638
+ };
639
+ /** A string written as a literal, a plain template, or a `+` chain of those. */
640
+ function stringLiteralOf(node) {
641
+ if (node === undefined)
642
+ return null;
643
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
644
+ return node.text;
645
+ if (ts.isParenthesizedExpression(node))
646
+ return stringLiteralOf(node.expression);
647
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
648
+ const left = stringLiteralOf(node.left);
649
+ const right = stringLiteralOf(node.right);
650
+ return left === null || right === null ? null : left + right;
651
+ }
652
+ return null;
653
+ }
654
+ /** An object-literal or string-literal property name, as written. */
655
+ function propertyNameOf(name) {
656
+ if (name === undefined)
657
+ return null;
658
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name))
659
+ return name.text;
660
+ if (ts.isNoSubstitutionTemplateLiteral(name))
661
+ return name.text;
662
+ return null;
663
+ }
664
+ function unwrap(node) {
665
+ let current = node;
666
+ for (;;) {
667
+ if (ts.isAsExpression(current) || ts.isSatisfiesExpression(current)) {
668
+ current = current.expression;
669
+ }
670
+ else if (ts.isParenthesizedExpression(current)) {
671
+ current = current.expression;
672
+ }
673
+ else {
674
+ return current;
675
+ }
676
+ }
677
+ }
678
+ /**
679
+ * A deployment's declaration, out of the file's **source text**.
680
+ *
681
+ * The running platform `import()`s this file; this run may not, and the reason
682
+ * is not a preference. `endora` runs as compiled JavaScript under plain `node`,
683
+ * and an instance's `apps/<deployment>/divergence.ts` is TypeScript — there is
684
+ * no loader in the process that could evaluate it, and adding one would make a
685
+ * scaffolding tool evaluate a client's code in order to describe it. Reading the
686
+ * text is also what the rest of this analysis does: the whole derivation is
687
+ * *"every subject is read as a literal"* (§3.3), and the declaration is held to
688
+ * the same rule as the tree it describes.
689
+ *
690
+ * What that costs is stated rather than discovered: a declaration whose fields
691
+ * are computed — spread from another module, built in a loop — resolves to
692
+ * nothing and is **named** in {@link DeclarationReading.unresolved}, never
693
+ * silently read as empty.
694
+ */
695
+ export function readDivergenceDeclaration(source, file) {
696
+ const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);
697
+ const unresolved = [];
698
+ let literal = null;
699
+ const visit = (node) => {
700
+ if (literal !== null)
701
+ return;
702
+ if (ts.isVariableStatement(node)) {
703
+ for (const declaration of node.declarationList.declarations) {
704
+ if (ts.isIdentifier(declaration.name) &&
705
+ declaration.name.text === 'divergence' &&
706
+ declaration.initializer !== undefined) {
707
+ const initializer = unwrap(declaration.initializer);
708
+ if (ts.isObjectLiteralExpression(initializer))
709
+ literal = initializer;
710
+ else
711
+ unresolved.push('divergence (the declaration is not an object literal)');
712
+ }
713
+ }
714
+ }
715
+ if (ts.isExportAssignment(node) && node.isExportEquals !== true) {
716
+ const expression = unwrap(node.expression);
717
+ if (ts.isObjectLiteralExpression(expression))
718
+ literal = expression;
719
+ }
720
+ node.forEachChild(visit);
721
+ };
722
+ sf.forEachChild(visit);
723
+ if (literal === null)
724
+ return { declaration: EMPTY_DECLARATION, unresolved };
725
+ const fields = new Map();
726
+ for (const property of literal.properties) {
727
+ if (ts.isPropertyAssignment(property)) {
728
+ const name = propertyNameOf(property.name);
729
+ if (name !== null)
730
+ fields.set(name, unwrap(property.initializer));
731
+ continue;
732
+ }
733
+ // A spread, a shorthand or a method: the field's value is somewhere else,
734
+ // which is exactly the state that must not read as "declares nothing".
735
+ unresolved.push('divergence (a property whose value is not written in place)');
736
+ }
737
+ const omittedModules = [];
738
+ const omitted = fields.get('omittedModules');
739
+ if (omitted !== undefined) {
740
+ if (!ts.isArrayLiteralExpression(omitted)) {
741
+ unresolved.push('omittedModules (not an array literal)');
742
+ }
743
+ else {
744
+ for (const element of omitted.elements) {
745
+ const entry = unwrap(element);
746
+ if (!ts.isObjectLiteralExpression(entry)) {
747
+ unresolved.push('omittedModules (an entry that is not an object literal)');
748
+ continue;
749
+ }
750
+ let moduleId = null;
751
+ let reason = null;
752
+ for (const property of entry.properties) {
753
+ if (!ts.isPropertyAssignment(property))
754
+ continue;
755
+ const name = propertyNameOf(property.name);
756
+ if (name === 'moduleId')
757
+ moduleId = stringLiteralOf(property.initializer);
758
+ if (name === 'reason')
759
+ reason = stringLiteralOf(property.initializer);
760
+ }
761
+ if (moduleId === null) {
762
+ unresolved.push('omittedModules (an entry with no literal moduleId)');
763
+ }
764
+ else {
765
+ omittedModules.push({ moduleId, reason: reason ?? '' });
766
+ }
767
+ }
768
+ }
769
+ }
770
+ const decorationOrder = {};
771
+ const order = fields.get('decorationOrder');
772
+ if (order !== undefined) {
773
+ if (!ts.isObjectLiteralExpression(order)) {
774
+ unresolved.push('decorationOrder (not an object literal)');
775
+ }
776
+ else {
777
+ for (const property of order.properties) {
778
+ if (!ts.isPropertyAssignment(property)) {
779
+ unresolved.push('decorationOrder (a property whose value is not written in place)');
780
+ continue;
781
+ }
782
+ const key = propertyNameOf(property.name);
783
+ const value = unwrap(property.initializer);
784
+ if (key === null || !ts.isArrayLiteralExpression(value)) {
785
+ unresolved.push(`decorationOrder.${key ?? '<computed>'} (not a literal array of ids)`);
786
+ continue;
787
+ }
788
+ const ids = [];
789
+ let readable = true;
790
+ for (const element of value.elements) {
791
+ const id = stringLiteralOf(element);
792
+ if (id === null)
793
+ readable = false;
794
+ else
795
+ ids.push(id);
796
+ }
797
+ if (!readable)
798
+ unresolved.push(`decorationOrder.${key} (a member that is not a literal)`);
799
+ else
800
+ decorationOrder[key] = ids;
801
+ }
802
+ }
803
+ }
804
+ const reasons = {};
805
+ const declaredReasons = fields.get('reasons');
806
+ if (declaredReasons !== undefined) {
807
+ if (!ts.isObjectLiteralExpression(declaredReasons)) {
808
+ unresolved.push('reasons (not an object literal)');
809
+ }
810
+ else {
811
+ for (const property of declaredReasons.properties) {
812
+ if (!ts.isPropertyAssignment(property)) {
813
+ unresolved.push('reasons (a property whose value is not written in place)');
814
+ continue;
815
+ }
816
+ const key = propertyNameOf(property.name);
817
+ const value = stringLiteralOf(property.initializer);
818
+ if (key === null || value === null) {
819
+ unresolved.push(`reasons.${key ?? '<computed>'} (not a literal sentence)`);
820
+ continue;
821
+ }
822
+ reasons[key] = value;
823
+ }
824
+ }
825
+ }
826
+ return { declaration: { omittedModules, decorationOrder, reasons }, unresolved };
827
+ }
828
+ //# sourceMappingURL=divergence-artefacts.js.map