@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,670 @@
1
+ /**
2
+ * CI check — a published port's **shape**: it declares no optional method
3
+ * (D-97.3), the container name in its doc block is the name it is actually
4
+ * registered under (issue #192), a module resolves, cross-module, only a name
5
+ * some contract publishes (issue #196, D-98.2), and an interface a module
6
+ * declares for *another* module to implement is named at that provider's
7
+ * `implements` clause (D-171.1).
8
+ *
9
+ * Four signals over one population, because all four read the same thing: the
10
+ * doc block that makes an interface a published port, and what the module tree
11
+ * does with the name in it.
12
+ *
13
+ * ## The population, and why it is two places rather than one
14
+ *
15
+ * A published port was `packages/contracts/src` and nothing else until D-171.1.
16
+ * That was never the rule — it was the only place a port *could* be published,
17
+ * because a module in `backend/src` has no supported name for anything it
18
+ * declares. D-171 changed that: a module package's `./ports` subpath is contract
19
+ * surface, so an interface declared there is published in exactly the sense this
20
+ * check means — a consumer may name it, and the container name in its doc block
21
+ * is the literal that consumer copies into `lazyPort`.
22
+ *
23
+ * So the population is **every exported `interface` carrying the
24
+ * `Container name:` marker, in `packages/contracts/src` or on a module
25
+ * package's declared `./ports` subpath**. The second half is what
26
+ * {@link RESOLUTIONS_OF_UNPUBLISHED_NAMES}' own entries have named as their
27
+ * retiring condition since T048 — *"this check's own population —
28
+ * `packages/contracts/src` — is what has to widen for the entry to go"* — and
29
+ * widening it retires them as their owners are packaged.
30
+ *
31
+ * **The `exports` map decides membership, not the path**, and the difference is
32
+ * load-bearing rather than pedantic. D-171's designation is derived from the
33
+ * artefact, and *"a `./ports` subpath exists only on a package"*: a module still
34
+ * in the application tree has a `ports/` directory with no supported name, so
35
+ * its interface is not published and a consumer still reaches it relatively —
36
+ * which is the ledger entry that has not yet retired. Reading the directory
37
+ * instead would retire those entries the moment this check widened, recording a
38
+ * repair that had not happened. The path-text alternative is refused for a
39
+ * second reason too: D-171 says `surfaceOf` answers `'port'` for a relative
40
+ * `services/ports/foo.ts`, which is a private file, and that a path heuristic
41
+ * is *"correct for choosing a remedy sentence and wrong as a boundary
42
+ * decision"*.
43
+ *
44
+ * The population is therefore derived in {@link main} and handed in as
45
+ * {@link PortShapeInput.modulePorts}: nothing in the analysis below spells a
46
+ * path, so a fixture enters with the same standing a real run has.
47
+ *
48
+ * ## Signal 4 — an interface declared by one module and implemented by another
49
+ *
50
+ * D-171 §4 used to refuse consumer-side declaration outright, on the ground
51
+ * that `lazyPort<T>` is an unchecked cast. It is, and that is about the wrong
52
+ * seam: conformance is checked at the **provider's** `implements` clause
53
+ * (TS2420) and at its explicitly typed `providePort<T>` registration, whose
54
+ * `Registration<T> = Resolver<T>` puts `T` in return position (TS2345). Both
55
+ * resolve the interface wherever it was declared.
56
+ *
57
+ * D-171.1 therefore licenses the placement — for a mutual pair, on the side the
58
+ * binding manifest `dependencies` edge points to — **against a condition**, and
59
+ * this signal is the condition. What it refuses is
60
+ * `declared-elsewhere-without-implements`: an interface declared in module A's
61
+ * `ports/`, registered by a typed `providePort<T>` in module B ≠ A, which no
62
+ * class in B names at an `implements` clause. That is precisely D-77's rejected
63
+ * alternative — the consumer declares the seam *"in its own file, importing
64
+ * nothing"* and the provider names it **nowhere**, so the only relation between
65
+ * the two types is the cast, which is none.
66
+ *
67
+ * **No ledger, and for signal 1's reason.** An entry could only license the one
68
+ * arrangement the condition exists to refuse.
69
+ *
70
+ * Two limits, in the header rather than discovered later. It asks whether the
71
+ * **provider module** names the interface at an `implements` clause, not
72
+ * whether the class the registration resolves to does: reading the registration
73
+ * argument back to a class declaration is analysis this check does not have,
74
+ * and a provider that implements the interface on a class it does not register
75
+ * passes. And the complementary direction needs no code here — a registration
76
+ * that drops its explicit type argument stops being seen as a registration at
77
+ * all, so its interface lands in {@link PORTS_WITHOUT_A_REGISTRATION}, which is
78
+ * empty and two-way.
79
+ *
80
+ * ## Signal 3 — the resolution side of the same name (issue #196)
81
+ *
82
+ * Signals 2 and 3 are **complements, not overlaps**, and the tree is the proof.
83
+ * Signal 2 reads *contract doc → registration*: it starts at a published port's
84
+ * doc block and asks whether the name it gives is registered and gated. It
85
+ * found and corrected eight wrong doc lines. The three consumers that were
86
+ * resolving the wrong name **stayed wrong**, because nothing walked the other
87
+ * way. Signal 3 is that direction: *consumer resolution → publication*.
88
+ *
89
+ * The rule is **on the name, at the resolution site**, and it has to be,
90
+ * because there is nowhere else it could bite. `lazyPort<T>(ctx, name)` is
91
+ * `new Proxy({} as T, …)` (`src/kernel/lazy-port.ts`): `T` is a free type
92
+ * parameter asserted onto an empty object and `name` is a `string`, so nothing
93
+ * in the language relates the two. `tsc` is not satisfied because the entity
94
+ * happens to be assignable to the record — `tsc` is satisfied because it was
95
+ * never asked. That is why D-98.2 rejected branding the record types: a brand
96
+ * can only bite where the compiler compares a value to the branded type, and
97
+ * at a resolution it never does. What actually happens is that a **string gets
98
+ * copied**, so the string is what this checks.
99
+ *
100
+ * One shape is refused — `resolution-of-unpublished-name` — over the two-way
101
+ * {@link RESOLUTIONS_OF_UNPUBLISHED_NAMES} ledger, which states the population
102
+ * and the two deliberate exclusions in full.
103
+ *
104
+ * ## Signal 2 — the documented container name (issue #192)
105
+ *
106
+ * `port-publication.md` §1.2 makes the container name part of the contract: it
107
+ * is the literal a consumer copies into `lazyPort<T>(ctx, 'thatName')`. Nothing
108
+ * checked it, and on the tree this signal was written against it was wrong six
109
+ * times out of 97 — four naming a container nothing registers, and **two naming
110
+ * the ungated legacy service the port was published to replace**. That second
111
+ * pair is why this is a build failure rather than tidiness: a consumer following
112
+ * `AdminRolePort`'s doc resolved `adminRoleService`, a plain `di.register` with
113
+ * no presence gate, whose `list()` returns `AdminRole` **entities** — and
114
+ * `AdminRole` is structurally assignable to `AdminRoleRecord`, so `tsc` said
115
+ * nothing. A doc that teaches a Principle XVII violation is worse than no doc.
116
+ *
117
+ * Two shapes are refused:
118
+ *
119
+ * - **`container-name-unregistered`** — the documented name appears in no
120
+ * `di.register` and no `di.providePort` anywhere under `src/modules` or
121
+ * `src/apps`. A consumer copying it gets
122
+ * `[kernel] '…' is not registered in this composition` at first call.
123
+ * - **`container-name-not-the-gated-registration`** — the port type *is*
124
+ * registered, by a typed `di.providePort<T>('X', …)`, and the doc names some
125
+ * other container. Whatever `Y` is, it is not the gate; where it happens to
126
+ * exist it is the ungated twin, which is the fail-open trap above.
127
+ *
128
+ * The second shape needs the registration's **type argument**, so it sees only
129
+ * the 81 of 134 `providePort` calls that carry one. That is not a weakness of
130
+ * this check but the reason A12 wants `providePort<T>` made non-inferrable: an
131
+ * untyped registration compares nothing, here or in `tsc`.
132
+ *
133
+ * `PORTS_WITHOUT_A_REGISTRATION` is the ledger for the first shape, two-way and
134
+ * empty since D-98.5. It is not a debt list to grow — an entry says why a
135
+ * published interface with no provider is standing, and the check refuses a
136
+ * stale one.
137
+ *
138
+ * ## Signal 1 — why the optional-method rule is a type rule and not advice
139
+ *
140
+ * A consumer that wants to know whether a provider implements something asks
141
+ * the object. Through a port it cannot: `lazyPort` hands back a `Proxy` whose
142
+ * `get` trap answers *every* non-symbol property with a function
143
+ * (`src/kernel/lazy-port.ts`), so `x.maybe`, `!x.maybe` and
144
+ * `typeof x.maybe === 'function'` are all truthy whatever is registered, and
145
+ * `x.maybe?.()` therefore always calls — into a provider that does not
146
+ * implement it, where the forward throws `'…' is not a function`.
147
+ *
148
+ * **And the proxy cannot be repaired.** Making the trap honest means resolving
149
+ * the name at *property-access* time to look at the real object, and for a
150
+ * gated port that turns a `typeof` probe into a `ModuleDisabledError`: a
151
+ * presence check that throws when the module is absent is worse than the hazard
152
+ * it fixes, and deferring resolution to the call is the entire reason `lazyPort`
153
+ * exists. The runtime cannot be made to answer, so the type has to forbid the
154
+ * question.
155
+ *
156
+ * `tsc` is silent on both halves: the member is optional, and `lazyPort<T>`'s
157
+ * `T` is an assertion rather than a check. That is why a check reads the shape.
158
+ *
159
+ * The one occurrence this rule was written from —
160
+ * `catalog`'s `AttributeDefinitionSource`, widening the custom-field read port
161
+ * with `publishInvalidate?` — fired its recovery branch on every integrity
162
+ * error and turned a benign, self-healing cache window into a 500 on the
163
+ * attribute screens. D-97.1 deleted it, so this check is born green with red
164
+ * proofs behind it and **no ledger**: there is nothing to drain, and an entry
165
+ * would be a licence to re-open the only hole the rule has ever had.
166
+ *
167
+ * ## What it reads
168
+ *
169
+ * 1. **Published ports** — an exported `interface` in `packages/contracts/src`
170
+ * **or on a module package's declared `./ports` subpath** (D-171.1) whose
171
+ * doc block carries the `Container name:` line every port in the tree is
172
+ * introduced by. One parse, `portDocOf`, answers both "is this a port?" and
173
+ * "which container does it name?", so the signals cannot come to disagree
174
+ * about the population.
175
+ * 2. **Interfaces extending one** — an `interface X extends <port>` anywhere in
176
+ * `backend/src/modules` or `backend/src/apps`, or in the contracts package
177
+ * itself. A widening is the shape that actually happened; refusing it only
178
+ * at the published type would refuse the tidy half.
179
+ * 3. **Registrations** — `di.register` keys and `di.providePort` names in the
180
+ * same module sources, read through `check-port-dependencies.ts`'s
181
+ * `registeredNames` / `providedPorts`. Imported rather than re-derived: a
182
+ * second expression of "this call is a registration" is a rule that can go
183
+ * half-missing while the check still prints `violations=0`.
184
+ *
185
+ * A member is an optional method when it is a `foo?(…): T` signature **or** a
186
+ * `foo?: (…) => T` property, because those are the same promise written twice.
187
+ * Optional *parameters* and optional *data properties* are untouched — all 34
188
+ * optional members across the tree's ports are of that kind, and none is
189
+ * affected.
190
+ *
191
+ * Usage: `tsx scripts/check-port-shape.ts [--list]`
192
+ * Exit 0 = clean; exit 1 = at least one finding, of any signal;
193
+ * exit 2 = nothing was read. Six conditions, one per input any signal could be
194
+ * silently missing (issue #113), because a green must never be able to mean
195
+ * "not looking": no sources; no port type in the contracts package; **no
196
+ * registration in the module scan** — an empty registration map would report
197
+ * every published port as unregistered, and a reader who "fixed" that by
198
+ * widening the ledger would have turned the whole check off; **no published
199
+ * container name** — signal 3 compares a consumer's literal against that set,
200
+ * and an empty one makes every cross-module resolution in the tree a finding;
201
+ * **no `lazyPort` resolution at all** — signal 3's population, whose emptiness
202
+ * would otherwise read as a clean bill; and **no module-declared port**, which
203
+ * is signal 4's whole population and the half of signal 3's the widening added.
204
+ * That last one is the one to understand: the widening is what retires the
205
+ * resolution ledger's entries, so a walk that stopped producing module ports
206
+ * would report those entries stale and invite an author to delete them — the
207
+ * exact inverse of the repair.
208
+ *
209
+ * ## One analysis, two hosts
210
+ *
211
+ * This file is the analysis (`specs/101-endora-check/contracts/package-scope-layout.md`
212
+ * §6). `backend/scripts/check-port-shape.ts` hosts it over this repository's
213
+ * contracts sources, module walk roots and `./ports` subpaths, and holds both
214
+ * ledgers and the two platform-name sets: each is a statement about *this*
215
+ * composition and none of them travels. `endora check` hosts it over one module
216
+ * package, where the ledgers and the platform sets are empty and signal 3 is
217
+ * declared unevaluated — it asks whether a module resolves a container name no
218
+ * contract publishes, which needs the published surface of every installed peer.
219
+ */
220
+ import { existsSync, readdirSync, statSync } from 'node:fs';
221
+ import { join } from 'node:path';
222
+ import ts from 'typescript';
223
+ import { NO_HOST_RESIDENT_MODULES, } from '../lib/module-population.js';
224
+ import { moduleOf, providedPorts, registeredNames, resolvedNames, } from '../lib/port-registrations.js';
225
+ /** The line every port's doc block carries, and the only marker that finds one. */
226
+ /**
227
+ * The doc-block marker that makes an interface a published port
228
+ * (`contracts/port-shape.md` §1.2). Exported because both hosts read it: the
229
+ * marker is part of the rule, not of a host.
230
+ */
231
+ export const PORT_DOC_MARKER = 'Container name:';
232
+ function parse(file, text) {
233
+ return ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
234
+ }
235
+ /**
236
+ * The declaration's port doc block, or `null` when it has none.
237
+ *
238
+ * The single answer to "is this a published port?" — signal 1 asks it to build
239
+ * the population, signal 2 asks it for the name and signal 3 asks it for the
240
+ * set of published names, so none of the three can drift into its own idea of
241
+ * what a port is.
242
+ */
243
+ function portDocOf(node, text) {
244
+ const ranges = ts.getLeadingCommentRanges(text, node.pos) ?? [];
245
+ const block = ranges
246
+ .map((range) => text.slice(range.pos, range.end))
247
+ .find((comment) => comment.includes(PORT_DOC_MARKER));
248
+ if (block === undefined)
249
+ return null;
250
+ const containers = [...block.matchAll(/Container name:\s*`([^`]+)`/g)]
251
+ .map((match) => match[1])
252
+ .filter((name) => name !== undefined);
253
+ return { containers };
254
+ }
255
+ function interfaces(sf) {
256
+ const found = [];
257
+ const visit = (node) => {
258
+ if (ts.isInterfaceDeclaration(node))
259
+ found.push(node);
260
+ node.forEachChild(visit);
261
+ };
262
+ sf.forEachChild(visit);
263
+ return found;
264
+ }
265
+ /**
266
+ * Every interface name a class in this file names at an `implements` clause.
267
+ *
268
+ * Signal 4's evidence, and the one `tsc` acts on: `implements` is resolved
269
+ * against the type wherever it was declared, so a missing member is TS2420
270
+ * whichever package the interface came from. A class `extends` clause is
271
+ * deliberately not read — it is not a conformance claim about an interface.
272
+ */
273
+ function implementedNames(sf) {
274
+ const names = [];
275
+ const visit = (node) => {
276
+ if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
277
+ for (const clause of node.heritageClauses ?? []) {
278
+ if (clause.token !== ts.SyntaxKind.ImplementsKeyword)
279
+ continue;
280
+ for (const type of clause.types) {
281
+ if (ts.isIdentifier(type.expression))
282
+ names.push(type.expression.text);
283
+ }
284
+ }
285
+ }
286
+ node.forEachChild(visit);
287
+ };
288
+ sf.forEachChild(visit);
289
+ return names;
290
+ }
291
+ /**
292
+ * The module a source key belongs to.
293
+ *
294
+ * `moduleOf` keys on `/src/modules/<id>/` and falls back to the first
295
+ * `modules/<id>/` segment, while this function's inputs are keyed however the
296
+ * caller likes — so the key is normalised for it and the caller's own key is
297
+ * what gets reported. Normalising is not re-deciding: the id still comes from
298
+ * the one function that owns that question.
299
+ */
300
+ function moduleOfKey(file, hostResident = NO_HOST_RESIDENT_MODULES) {
301
+ return moduleOf(forOwnerLookup(file), hostResident);
302
+ }
303
+ /** The key spelling `moduleOf`, `registeredNames` and `resolvedNames` expect. */
304
+ function forOwnerLookup(file) {
305
+ return file.includes('/src/') ? file : `/src/${file.replace(/^\/+/, '')}`;
306
+ }
307
+ /** The names an interface extends, as written (type arguments dropped). */
308
+ function extendedNames(node) {
309
+ const names = [];
310
+ for (const clause of node.heritageClauses ?? []) {
311
+ if (clause.token !== ts.SyntaxKind.ExtendsKeyword)
312
+ continue;
313
+ for (const type of clause.types) {
314
+ if (ts.isIdentifier(type.expression))
315
+ names.push(type.expression.text);
316
+ }
317
+ }
318
+ return names;
319
+ }
320
+ /**
321
+ * The optional **methods** of an interface.
322
+ *
323
+ * Both spellings, because they are one promise: `foo?(): void` is a
324
+ * `MethodSignature` with a question token, `foo?: () => void` a
325
+ * `PropertySignature` whose type is a function. A `foo?: string` is a data
326
+ * property and is deliberately outside the population.
327
+ */
328
+ function optionalMethods(node) {
329
+ return node.members.filter((member) => {
330
+ if (member.questionToken === undefined)
331
+ return false;
332
+ if (ts.isMethodSignature(member))
333
+ return true;
334
+ if (ts.isPropertySignature(member) && member.type !== undefined) {
335
+ return ts.isFunctionTypeNode(member.type);
336
+ }
337
+ return false;
338
+ });
339
+ }
340
+ function memberName(member) {
341
+ const name = member.name;
342
+ if (name === undefined)
343
+ return '(computed)';
344
+ return ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : name.getText();
345
+ }
346
+ /**
347
+ * Every published port, and every optional method on one or on a widening
348
+ * of one.
349
+ *
350
+ * The input is **source text**, so a fixture enters exactly where a real run
351
+ * does (issue #130): nothing above this function classifies anything.
352
+ */
353
+ export function checkPortShape(input) {
354
+ const hostResident = input.hostResidentModules ?? NO_HOST_RESIDENT_MODULES;
355
+ const parsedContracts = new Map();
356
+ const portTypes = new Set();
357
+ /** Every published port, with where it is declared and what it documents. */
358
+ const published = [];
359
+ /**
360
+ * One sweep for both halves of the population — the contracts package and the
361
+ * modules' own `ports/` directories. `portDocOf` decides membership in both,
362
+ * so the two cannot come to disagree about what a published port is.
363
+ */
364
+ const collect = (file, text, declaringModule, remember) => {
365
+ const sf = parse(file, text);
366
+ remember(sf);
367
+ let found = 0;
368
+ for (const node of interfaces(sf)) {
369
+ const doc = portDocOf(node, text);
370
+ if (doc === null)
371
+ continue;
372
+ found += 1;
373
+ portTypes.add(node.name.text);
374
+ published.push({
375
+ portName: node.name.text,
376
+ file,
377
+ line: sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1,
378
+ containers: doc.containers,
379
+ declaringModule,
380
+ });
381
+ }
382
+ return found;
383
+ };
384
+ for (const [file, text] of input.contracts) {
385
+ collect(file, text, null, (sf) => parsedContracts.set(file, sf));
386
+ }
387
+ const modulePorts = input.modulePorts ?? new Map();
388
+ const parsedModulePorts = new Map();
389
+ let modulePortCount = 0;
390
+ for (const [file, text] of modulePorts) {
391
+ modulePortCount += collect(file, text, moduleOfKey(file, hostResident), (sf) => parsedModulePorts.set(file, sf));
392
+ }
393
+ const findings = [];
394
+ const scan = (file, sf) => {
395
+ for (const node of interfaces(sf)) {
396
+ const own = portTypes.has(node.name.text);
397
+ const extended = extendedNames(node).find((name) => portTypes.has(name));
398
+ if (!own && extended === undefined)
399
+ continue;
400
+ // A port type declaring an optional method is the stronger finding, so a
401
+ // type that is both a port and an extension is reported as the port.
402
+ const kind = own
403
+ ? 'optional-method-on-port'
404
+ : 'optional-method-on-port-extension';
405
+ const portName = own ? node.name.text : extended;
406
+ for (const member of optionalMethods(node)) {
407
+ findings.push({
408
+ file,
409
+ line: sf.getLineAndCharacterOfPosition(member.getStart(sf)).line + 1,
410
+ typeName: node.name.text,
411
+ portName,
412
+ member: memberName(member),
413
+ kind,
414
+ });
415
+ }
416
+ }
417
+ };
418
+ for (const [file, sf] of parsedContracts)
419
+ scan(file, sf);
420
+ // A ports file is walked by both halves of a real run — it is a module source
421
+ // and it is a port declaration — so signal 1 scans the union keyed by file,
422
+ // never the concatenation, which would report an optional method twice.
423
+ const moduleScan = new Map();
424
+ for (const [file, text] of input.modules)
425
+ moduleScan.set(file, parse(file, text));
426
+ for (const [file, sf] of parsedModulePorts)
427
+ if (!moduleScan.has(file))
428
+ moduleScan.set(file, sf);
429
+ for (const [file, sf] of moduleScan)
430
+ scan(file, sf);
431
+ findings.sort((a, b) => (a.file === b.file ? a.line - b.line : a.file.localeCompare(b.file)));
432
+ // --- signal 2: the documented container name -------------------------------
433
+ //
434
+ // Both registration readers come from `check-port-dependencies.ts`, which owns
435
+ // the "this call is a registration" predicate. `registeredNames` already
436
+ // covers `providePort` names, so `plainNames` is derived by subtraction rather
437
+ // than by a second parse deciding the same thing differently.
438
+ const allNames = new Set();
439
+ const gatedNames = new Set();
440
+ /** Container name a typed `providePort<T>` gives each port type. */
441
+ const gatedNameOfType = new Map();
442
+ /** The module that typed registration sits in — signal 4's other half. */
443
+ const gatedModuleOfType = new Map();
444
+ /** Module id → every interface name a class in it `implements`. */
445
+ const implementsByModule = new Map();
446
+ for (const [file, text] of input.modules) {
447
+ for (const name of registeredNames(text, file))
448
+ allNames.add(name);
449
+ for (const port of providedPorts(text, file)) {
450
+ gatedNames.add(port.name);
451
+ if (port.typeName !== null && !gatedNameOfType.has(port.typeName)) {
452
+ gatedNameOfType.set(port.typeName, port.name);
453
+ const owner = moduleOfKey(file, hostResident);
454
+ if (owner !== null)
455
+ gatedModuleOfType.set(port.typeName, owner);
456
+ }
457
+ }
458
+ }
459
+ for (const [file, sf] of moduleScan) {
460
+ const owner = moduleOfKey(file, hostResident);
461
+ if (owner === null)
462
+ continue;
463
+ for (const name of implementedNames(sf)) {
464
+ const claimed = implementsByModule.get(owner);
465
+ if (claimed)
466
+ claimed.add(name);
467
+ else
468
+ implementsByModule.set(owner, new Set([name]));
469
+ }
470
+ }
471
+ const ledger = input.unregisteredLedger ?? {};
472
+ const nameFindings = [];
473
+ const ledgerHits = new Set();
474
+ const registrationKindOf = (container) => gatedNames.has(container) ? 'gated' : allNames.has(container) ? 'plain' : null;
475
+ for (const port of published) {
476
+ if (port.containers.length === 0)
477
+ continue;
478
+ const gatedName = gatedNameOfType.get(port.portName);
479
+ const unregistered = port.containers.filter((name) => registrationKindOf(name) === null);
480
+ // "No registration behind this published interface at all" is the shape the
481
+ // ledger answers for, so it is asked once per port and not once per
482
+ // documented name: a port with two providers where one is missing is a
483
+ // wrong doc line, not an unprovided port.
484
+ if (unregistered.length === port.containers.length) {
485
+ if (ledger[port.portName] !== undefined) {
486
+ ledgerHits.add(port.portName);
487
+ continue;
488
+ }
489
+ }
490
+ for (const documented of unregistered) {
491
+ nameFindings.push({
492
+ file: port.file,
493
+ line: port.line,
494
+ portName: port.portName,
495
+ documented,
496
+ registered: gatedName ?? null,
497
+ documentedRegistrationKind: null,
498
+ kind: 'container-name-unregistered',
499
+ });
500
+ }
501
+ // The gated-registration shape asks whether the doc names the gate **at
502
+ // all**. With two providers only one of them can be the typed
503
+ // `providePort<T>` this map records, so requiring every documented name to
504
+ // be it would refuse the two-provider shape rather than the defect.
505
+ if (gatedName !== undefined && !port.containers.includes(gatedName)) {
506
+ const documented = port.containers[0];
507
+ nameFindings.push({
508
+ file: port.file,
509
+ line: port.line,
510
+ portName: port.portName,
511
+ documented,
512
+ registered: gatedName,
513
+ documentedRegistrationKind: registrationKindOf(documented),
514
+ kind: 'container-name-not-the-gated-registration',
515
+ });
516
+ }
517
+ }
518
+ nameFindings.sort((a, b) => a.portName.localeCompare(b.portName));
519
+ const staleLedgerEntries = Object.keys(ledger)
520
+ .filter((portName) => !ledgerHits.has(portName))
521
+ .sort();
522
+ // --- signal 4: the condition D-171.1 licenses the placement against ---------
523
+ //
524
+ // Only a port a **module** declares can be in this population: a port in
525
+ // `packages/contracts` belongs to no module, so "declared elsewhere" has no
526
+ // meaning for it. And only where the typed registration is in a *different*
527
+ // module — the ordinary case, where the owner declares and implements its own
528
+ // interface, is what D-171 §4 already describes and is untouched.
529
+ const declaredElsewhere = [];
530
+ for (const port of published) {
531
+ if (port.declaringModule === null)
532
+ continue;
533
+ const provider = gatedModuleOfType.get(port.portName);
534
+ if (provider === undefined || provider === port.declaringModule)
535
+ continue;
536
+ if (implementsByModule.get(provider)?.has(port.portName) === true)
537
+ continue;
538
+ declaredElsewhere.push({
539
+ portName: port.portName,
540
+ file: port.file,
541
+ line: port.line,
542
+ declaringModule: port.declaringModule,
543
+ providingModule: provider,
544
+ container: gatedNameOfType.get(port.portName),
545
+ kind: 'declared-elsewhere-without-implements',
546
+ });
547
+ }
548
+ declaredElsewhere.sort((a, b) => a.portName.localeCompare(b.portName));
549
+ // --- signal 3: the resolution side of the same name ------------------------
550
+ //
551
+ // Signal 2 above walks contract doc -> registration. This walks consumer
552
+ // resolution -> publication, which is a different edge in the other
553
+ // direction, and the one nothing in the tree could see: !698 corrected six
554
+ // doc blocks while three consumers went on resolving the class name.
555
+ //
556
+ // Every predicate it needs already exists. "Which module registers this
557
+ // name" is `registeredNames` + `moduleOf`; "this is a `lazyPort` resolution"
558
+ // is `resolvedNames`, through the `via` field it records; "this name is
559
+ // published" is `portDocOf`, the same parse signals 1 and 2 use. Nothing here
560
+ // decides any of those a second time.
561
+ const publishedContainers = new Set();
562
+ for (const port of published)
563
+ for (const name of port.containers)
564
+ publishedContainers.add(name);
565
+ /** Container name -> the module whose sources register it. */
566
+ const ownerOfName = new Map();
567
+ const lazyResolutions = [];
568
+ for (const [file, text] of input.modules) {
569
+ // `moduleOf` and `resolvedNames` read the module id out of the path, and
570
+ // they key on `/src/modules/<id>/` — while this function's inputs are keyed
571
+ // however the caller likes, which is what lets a fixture enter at the top.
572
+ // So the path is normalised for them and the caller's own key is what gets
573
+ // reported. Normalising is not re-deciding: the module id still comes from
574
+ // the one function that owns that question.
575
+ const lookupKey = forOwnerLookup(file);
576
+ const moduleId = moduleOf(lookupKey, hostResident);
577
+ if (moduleId === null)
578
+ continue;
579
+ for (const name of registeredNames(text, lookupKey))
580
+ ownerOfName.set(name, moduleId);
581
+ for (const resolution of resolvedNames(text, lookupKey)) {
582
+ if (resolution.via !== 'lazyPort')
583
+ continue;
584
+ lazyResolutions.push({
585
+ moduleId: resolution.moduleId,
586
+ name: resolution.name,
587
+ file,
588
+ line: resolution.line,
589
+ });
590
+ }
591
+ }
592
+ const resolutionLedger = input.unpublishedResolutionLedger ?? {};
593
+ const platformOwnedNames = input.platformOwnedNames ?? new Set();
594
+ const hostRegisteredPorts = input.hostRegisteredPorts ?? {};
595
+ const unpublishedResolutions = [];
596
+ const resolutionLedgerHits = new Set();
597
+ for (const resolution of lazyResolutions) {
598
+ // Platform names are supplied by a composition root or the kernel, neither
599
+ // of which has a contracts file to publish from; `HOST_REGISTERED_PORTS`
600
+ // is the same fact for a port a root still bridges on its owner's behalf.
601
+ if (platformOwnedNames.has(resolution.name))
602
+ continue;
603
+ if (hostRegisteredPorts[resolution.name] !== undefined)
604
+ continue;
605
+ const owner = ownerOfName.get(resolution.name);
606
+ // A name nothing registers is `check-port-dependencies`' `unowned-name`,
607
+ // and reporting it here as well would give one defect two voices.
608
+ if (owner === undefined)
609
+ continue;
610
+ // A module naming its own registration crosses no boundary.
611
+ if (owner === resolution.moduleId)
612
+ continue;
613
+ if (publishedContainers.has(resolution.name))
614
+ continue;
615
+ const key = `${resolution.moduleId}:${resolution.name}`;
616
+ if (resolutionLedger[key] !== undefined) {
617
+ resolutionLedgerHits.add(key);
618
+ continue;
619
+ }
620
+ unpublishedResolutions.push({
621
+ moduleId: resolution.moduleId,
622
+ owner,
623
+ name: resolution.name,
624
+ file: resolution.file,
625
+ line: resolution.line,
626
+ kind: 'resolution-of-unpublished-name',
627
+ });
628
+ }
629
+ unpublishedResolutions.sort((a, b) => a.file === b.file ? a.line - b.line : a.file.localeCompare(b.file));
630
+ const staleUnpublishedResolutions = Object.keys(resolutionLedger)
631
+ .filter((key) => !resolutionLedgerHits.has(key))
632
+ .sort();
633
+ return {
634
+ portTypes: [...portTypes].sort(),
635
+ findings,
636
+ nameFindings,
637
+ registeredNameCount: allNames.size,
638
+ staleLedgerEntries,
639
+ unpublishedResolutions,
640
+ publishedContainerCount: publishedContainers.size,
641
+ lazyPortResolutionCount: lazyResolutions.length,
642
+ staleUnpublishedResolutions,
643
+ declaredElsewhere,
644
+ modulePortCount,
645
+ };
646
+ }
647
+ /**
648
+ * Every TypeScript source under `dir`, with the rule's own prunes applied.
649
+ *
650
+ * Exported because the population is part of the rule: two hosts computing
651
+ * "which files this check reads" two ways is the shape that lets one of them go
652
+ * half-blind.
653
+ */
654
+ export function collectPortShapeSources(dir, out = []) {
655
+ if (!existsSync(dir))
656
+ return out;
657
+ for (const name of readdirSync(dir)) {
658
+ const full = join(dir, name);
659
+ if (statSync(full).isDirectory()) {
660
+ if (name === 'node_modules' || name === 'dist')
661
+ continue;
662
+ collectPortShapeSources(full, out);
663
+ }
664
+ else if (name.endsWith('.ts') && !name.endsWith('.test.ts') && !name.endsWith('.d.ts')) {
665
+ out.push(full);
666
+ }
667
+ }
668
+ return out;
669
+ }
670
+ //# sourceMappingURL=port-shape.js.map