@hraness/ghostget 0.17.2

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 (500) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1359 -0
  4. package/bunfig.toml +2 -0
  5. package/dist/apple-photos-client.js +704 -0
  6. package/dist/beeper-client.js +4602 -0
  7. package/dist/client.js +1605 -0
  8. package/dist/index-26yq8q16.js +31 -0
  9. package/dist/index-9t13b4k7.js +508 -0
  10. package/dist/index-axwm9b3v.js +5 -0
  11. package/dist/index-gwk7rbyj.js +30 -0
  12. package/dist/index.js +701 -0
  13. package/dist/messaging.js +1130 -0
  14. package/dist/omni-client.js +1406 -0
  15. package/dist/whatsapp-client.js +355 -0
  16. package/docs/imessage-direct-provider.md +106 -0
  17. package/docs/rental-listings.md +94 -0
  18. package/package.json +600 -0
  19. package/skills/ghostget/SKILL.md +251 -0
  20. package/skills/ghostget/agents/openai.yaml +4 -0
  21. package/skills/ghostget/references/adapter-contract.md +181 -0
  22. package/skills/ghostget/references/apple-photos.md +115 -0
  23. package/skills/ghostget/references/article-drafts.md +235 -0
  24. package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
  25. package/skills/ghostget/references/cross-posting-video.md +139 -0
  26. package/skills/ghostget/references/cross-posting.md +82 -0
  27. package/skills/ghostget/references/derivation.md +390 -0
  28. package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
  29. package/skills/ghostget/references/install.md +66 -0
  30. package/skills/ghostget/references/linkedin-adapter.md +442 -0
  31. package/skills/ghostget/references/messaging.md +168 -0
  32. package/skills/ghostget/references/meta-comet-contract.md +153 -0
  33. package/skills/ghostget/references/platform-patterns.md +122 -0
  34. package/skills/ghostget/references/provider-plugins.md +496 -0
  35. package/skills/ghostget/references/reddit-flair.md +78 -0
  36. package/skills/ghostget/references/safety-and-state.md +214 -0
  37. package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
  38. package/skills/ghostget/references/social-platform-routing.md +66 -0
  39. package/skills/ghostget/references/social-profile-stats.md +195 -0
  40. package/skills/ghostget/references/social-video-platform-routing.md +89 -0
  41. package/skills/ghostget/references/whatsapp-adapter.md +101 -0
  42. package/skills/ghostget/references/x-adapter.md +332 -0
  43. package/skills/ghostget/references/x-ai-disclosure.md +71 -0
  44. package/src/apple-photos-cli.ts +639 -0
  45. package/src/apple-photos-client-types.ts +168 -0
  46. package/src/apple-photos-client.ts +201 -0
  47. package/src/apple-photos-contact-evidence.ts +838 -0
  48. package/src/apple-photos-local-source.ts +1131 -0
  49. package/src/args.ts +2053 -0
  50. package/src/article-draft-document.ts +417 -0
  51. package/src/article-draft-embeds.ts +123 -0
  52. package/src/article-draft-images.ts +214 -0
  53. package/src/assets/adapter-template/wrench-adapter.json +40 -0
  54. package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
  55. package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
  56. package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
  57. package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
  58. package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
  59. package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
  60. package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
  61. package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
  62. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
  63. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
  64. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
  65. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
  66. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
  67. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
  68. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
  69. package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
  70. package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
  71. package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
  72. package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
  73. package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
  74. package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
  75. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
  76. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
  77. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
  78. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
  79. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
  80. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
  81. package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
  82. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
  83. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
  84. package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
  85. package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
  86. package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
  87. package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
  88. package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
  89. package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
  90. package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
  91. package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
  92. package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
  93. package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
  94. package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
  95. package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
  96. package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
  97. package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
  98. package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
  99. package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
  100. package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
  101. package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
  102. package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
  103. package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
  104. package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
  105. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
  106. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
  107. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
  108. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
  109. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
  110. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
  111. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
  112. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
  113. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
  114. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
  115. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
  116. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
  117. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
  118. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
  119. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
  120. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
  121. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
  122. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
  123. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
  124. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
  125. package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
  126. package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
  127. package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
  128. package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
  129. package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
  130. package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
  131. package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
  132. package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
  133. package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
  134. package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
  135. package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
  136. package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
  137. package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
  138. package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
  139. package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
  140. package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
  141. package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
  142. package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
  143. package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
  144. package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
  145. package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
  146. package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
  147. package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
  148. package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
  149. package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
  150. package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
  151. package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
  152. package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
  153. package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
  154. package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
  155. package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
  156. package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
  157. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
  158. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
  159. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
  160. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
  161. package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
  162. package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
  163. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
  164. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
  165. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
  166. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
  167. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
  168. package/src/assets/adapters/x/wrench-adapter.json +749 -0
  169. package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
  170. package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
  171. package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
  172. package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
  173. package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
  174. package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
  175. package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
  176. package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
  177. package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
  178. package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
  179. package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
  180. package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
  181. package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
  182. package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
  183. package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
  184. package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
  185. package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
  186. package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
  187. package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
  188. package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
  189. package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
  190. package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
  191. package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
  192. package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
  193. package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
  194. package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
  195. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  196. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  197. package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
  198. package/src/assets.d.ts +4 -0
  199. package/src/auth.ts +1157 -0
  200. package/src/beeper-client-types.ts +169 -0
  201. package/src/beeper-client.ts +250 -0
  202. package/src/beeper-contact-interactions-cli.ts +180 -0
  203. package/src/beeper-contact-interactions.ts +1612 -0
  204. package/src/beeper-message-bundle-v1.ts +257 -0
  205. package/src/beeper-message-like-me-cli.ts +84 -0
  206. package/src/beeper-message-like-me-export.ts +2274 -0
  207. package/src/beeper-message-like-me-recovery.ts +931 -0
  208. package/src/beeper-message-like-me-source.ts +4034 -0
  209. package/src/browser-admission.ts +928 -0
  210. package/src/browser-snapshots.ts +260 -0
  211. package/src/browser.ts +3873 -0
  212. package/src/canonical-json.ts +60 -0
  213. package/src/catalog-cli.ts +638 -0
  214. package/src/cli.ts +208 -0
  215. package/src/client-types.ts +301 -0
  216. package/src/client.ts +2726 -0
  217. package/src/confirmed-write-failure.ts +44 -0
  218. package/src/confirmed-write-model.ts +72 -0
  219. package/src/confirmed-write-platform.ts +1055 -0
  220. package/src/confirmed-write-program.ts +224 -0
  221. package/src/confirmed-write-runtime.ts +16 -0
  222. package/src/cursor-token.ts +228 -0
  223. package/src/derivation-dnr-readiness.ts +367 -0
  224. package/src/derivation-file-chooser.ts +384 -0
  225. package/src/derivation-network-boundary.ts +835 -0
  226. package/src/derivation-network-guard.ts +782 -0
  227. package/src/derivation-network-proxy-helper.ts +440 -0
  228. package/src/derivation-network-proxy.ts +385 -0
  229. package/src/derive-command-helper.ts +693 -0
  230. package/src/derive-fixtures.ts +343 -0
  231. package/src/derive-review.ts +980 -0
  232. package/src/derive.ts +4754 -0
  233. package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
  234. package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
  235. package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
  236. package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
  237. package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
  238. package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
  239. package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
  240. package/src/ghostget.ts +3672 -0
  241. package/src/gmail-capture.ts +1244 -0
  242. package/src/har-internal.ts +1242 -0
  243. package/src/har.ts +743 -0
  244. package/src/index.ts +218 -0
  245. package/src/invocation-read-platform.ts +55 -0
  246. package/src/invocation-read-program.ts +93 -0
  247. package/src/invocation-read-runtime.ts +11 -0
  248. package/src/linked-device-lifecycle-admission.ts +450 -0
  249. package/src/linked-device-lifecycle-journal.ts +1560 -0
  250. package/src/linked-device-lifecycle-runtime.ts +1602 -0
  251. package/src/local-cli-admission.ts +135 -0
  252. package/src/local-cli-contracts.ts +271 -0
  253. package/src/local-cli-execution.ts +98 -0
  254. package/src/local-cli-surface-contract.ts +1510 -0
  255. package/src/local-cli-tool-identity.ts +336 -0
  256. package/src/media/archive.ts +2325 -0
  257. package/src/media/args.ts +303 -0
  258. package/src/media/cli.ts +287 -0
  259. package/src/media/doctor.ts +475 -0
  260. package/src/media/ffmpeg.ts +1330 -0
  261. package/src/media/http-capture.ts +711 -0
  262. package/src/media/http-probe.ts +593 -0
  263. package/src/media/http.ts +387 -0
  264. package/src/media/index.ts +10 -0
  265. package/src/media/local-transcription.ts +613 -0
  266. package/src/media/lock.ts +321 -0
  267. package/src/media/manifest.ts +1712 -0
  268. package/src/media/metadata.ts +906 -0
  269. package/src/media/process.ts +723 -0
  270. package/src/media/revision.ts +166 -0
  271. package/src/media/runtime-closure.ts +1063 -0
  272. package/src/media/source-router.ts +187 -0
  273. package/src/media/transcriber-config.ts +1122 -0
  274. package/src/media/transcript-persistence-model.ts +15 -0
  275. package/src/media/transcript-persistence-platform.ts +89 -0
  276. package/src/media/transcript-persistence-program.ts +39 -0
  277. package/src/media/transcript-persistence-runtime.ts +24 -0
  278. package/src/media/transcript.ts +616 -0
  279. package/src/media/utf8-order.ts +30 -0
  280. package/src/media/whisper-cpp.ts +464 -0
  281. package/src/media/whisper-language.ts +58 -0
  282. package/src/media/yt-dlp.ts +439 -0
  283. package/src/message-like-me-agentic-messaging.ts +882 -0
  284. package/src/messaging-action-store.ts +793 -0
  285. package/src/messaging-runtime.ts +2136 -0
  286. package/src/messaging-store.ts +539 -0
  287. package/src/messaging-types.ts +1663 -0
  288. package/src/messaging.ts +334 -0
  289. package/src/model.ts +2671 -0
  290. package/src/oauth-google.ts +888 -0
  291. package/src/omni-client-types.ts +306 -0
  292. package/src/omni-client.ts +2225 -0
  293. package/src/omni-limits.ts +34 -0
  294. package/src/omni-model.ts +1614 -0
  295. package/src/omni-request.ts +379 -0
  296. package/src/omni-runtime.ts +1632 -0
  297. package/src/operation-deadline.ts +189 -0
  298. package/src/path-helper.ts +1656 -0
  299. package/src/pinned-https.ts +194 -0
  300. package/src/plan-assets.ts +832 -0
  301. package/src/platform-catalog.ts +1832 -0
  302. package/src/plugins/beeper-linked-device/plugin.ts +310 -0
  303. package/src/plugins/bluesky-web/plugin.ts +178 -0
  304. package/src/plugins/clasificados-web/plugin.ts +66 -0
  305. package/src/plugins/github-web/plugin.ts +80 -0
  306. package/src/plugins/gmail-official/plugin.ts +110 -0
  307. package/src/plugins/hacker-news-web/plugin.ts +52 -0
  308. package/src/plugins/imessage-direct/plugin.ts +195 -0
  309. package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
  310. package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
  311. package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
  312. package/src/plugins/linkedin-official/plugin.ts +84 -0
  313. package/src/plugins/linkedin-web/plugin.ts +412 -0
  314. package/src/plugins/meta-web/plugin.ts +317 -0
  315. package/src/plugins/reddit-web/flair.ts +492 -0
  316. package/src/plugins/reddit-web/plugin.ts +477 -0
  317. package/src/plugins/substack-web/plugin.ts +124 -0
  318. package/src/plugins/tiktok-web/plugin.ts +67 -0
  319. package/src/plugins/twitch-web/plugin.ts +54 -0
  320. package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
  321. package/src/plugins/x-official/plugin.ts +64 -0
  322. package/src/plugins/x-web/plugin.ts +488 -0
  323. package/src/plugins/youtube-web/plugin.ts +101 -0
  324. package/src/portable-run-recovery.ts +466 -0
  325. package/src/process-identity.ts +501 -0
  326. package/src/profile-clone-helper.ts +104 -0
  327. package/src/provider-catalog-views.ts +51 -0
  328. package/src/provider-context.ts +69 -0
  329. package/src/provider-contract-definitions-x.ts +45 -0
  330. package/src/provider-contract-definitions.ts +948 -0
  331. package/src/provider-contract-input-gmail.ts +176 -0
  332. package/src/provider-contract-input-linkedin.ts +77 -0
  333. package/src/provider-contract-input-x.ts +304 -0
  334. package/src/provider-contract-planning.ts +23 -0
  335. package/src/provider-contract-semantic-identity.ts +54 -0
  336. package/src/provider-contracts.ts +251 -0
  337. package/src/provider-http.ts +723 -0
  338. package/src/provider-plugin-auth.ts +36 -0
  339. package/src/provider-plugin-builtins.ts +229 -0
  340. package/src/provider-plugin-cleanup-barrier.ts +229 -0
  341. package/src/provider-plugin-cleanup-execution.ts +79 -0
  342. package/src/provider-plugin-cleanup-resource.ts +470 -0
  343. package/src/provider-plugin-contract-identity.ts +707 -0
  344. package/src/provider-plugin-host.ts +1972 -0
  345. package/src/provider-plugin-identifiers.ts +60 -0
  346. package/src/provider-plugin-invocation-lease.ts +889 -0
  347. package/src/provider-plugin-lifecycle-kernel.ts +566 -0
  348. package/src/provider-plugin-lifecycle.ts +1455 -0
  349. package/src/provider-plugin-module-analysis.ts +336 -0
  350. package/src/provider-plugin-package.ts +2530 -0
  351. package/src/provider-plugin-portable-authority.ts +106 -0
  352. package/src/provider-plugin-portable-catalog.ts +94 -0
  353. package/src/provider-plugin-portable-identity.ts +315 -0
  354. package/src/provider-plugin-portable-runtime.ts +2057 -0
  355. package/src/provider-plugin-protocol.ts +1625 -0
  356. package/src/provider-plugin-registry.ts +3342 -0
  357. package/src/provider-plugin-store.ts +2155 -0
  358. package/src/provider-plugin-test-fixture.ts +5 -0
  359. package/src/provider-plugin.ts +4907 -0
  360. package/src/provider-plugins.generated.ts +43 -0
  361. package/src/provider-plugins.ts +8 -0
  362. package/src/provider-subject.ts +41 -0
  363. package/src/provider.ts +315 -0
  364. package/src/providers/beeper-local-runtime.ts +5426 -0
  365. package/src/providers/beeper-local.ts +2739 -0
  366. package/src/providers/beeper-messaging.ts +462 -0
  367. package/src/providers/beeper-omni.ts +1159 -0
  368. package/src/providers/bluesky-web-runtime.ts +2895 -0
  369. package/src/providers/bluesky-web.ts +1614 -0
  370. package/src/providers/clasificados-web-runtime.ts +255 -0
  371. package/src/providers/clasificados-web.ts +503 -0
  372. package/src/providers/contact-projection.ts +301 -0
  373. package/src/providers/github-read-model.ts +94 -0
  374. package/src/providers/github-read-platform.ts +165 -0
  375. package/src/providers/github-read-program.ts +97 -0
  376. package/src/providers/github-web-runtime.ts +320 -0
  377. package/src/providers/github-web.ts +403 -0
  378. package/src/providers/gmail-api.ts +2834 -0
  379. package/src/providers/gmail-omni.ts +830 -0
  380. package/src/providers/gmail.ts +1130 -0
  381. package/src/providers/hacker-news-web-runtime.ts +220 -0
  382. package/src/providers/hacker-news-web.ts +809 -0
  383. package/src/providers/imessage-direct-install.ts +354 -0
  384. package/src/providers/imessage-direct-messaging.ts +532 -0
  385. package/src/providers/imessage-direct-omni.ts +400 -0
  386. package/src/providers/imessage-direct-runtime.ts +1498 -0
  387. package/src/providers/imessage-direct.ts +351 -0
  388. package/src/providers/instagram-video-foundations.ts +913 -0
  389. package/src/providers/instagram-web-profile-browser.ts +600 -0
  390. package/src/providers/iso-bmff.ts +338 -0
  391. package/src/providers/linkedin-company-platform.ts +42 -0
  392. package/src/providers/linkedin-company-program.ts +88 -0
  393. package/src/providers/linkedin-contact-failure.ts +84 -0
  394. package/src/providers/linkedin-contact-platform.ts +45 -0
  395. package/src/providers/linkedin-contact-program.ts +91 -0
  396. package/src/providers/linkedin-profile-activity-failure.ts +76 -0
  397. package/src/providers/linkedin-profile-activity-platform.ts +41 -0
  398. package/src/providers/linkedin-profile-activity-program.ts +67 -0
  399. package/src/providers/linkedin-read-failure.ts +52 -0
  400. package/src/providers/linkedin-self-platform.ts +53 -0
  401. package/src/providers/linkedin-self-program.ts +122 -0
  402. package/src/providers/linkedin-web-article-browser.ts +794 -0
  403. package/src/providers/linkedin-web-bootstrap.ts +146 -0
  404. package/src/providers/linkedin-web-contact.ts +830 -0
  405. package/src/providers/linkedin-web-feed-browser.ts +832 -0
  406. package/src/providers/linkedin-web-feed.ts +864 -0
  407. package/src/providers/linkedin-web-post-browser.ts +653 -0
  408. package/src/providers/linkedin-web-profile-browser.ts +683 -0
  409. package/src/providers/linkedin-web-runtime.ts +2388 -0
  410. package/src/providers/linkedin-web.ts +3305 -0
  411. package/src/providers/linkedin.ts +1885 -0
  412. package/src/providers/meta-bootstrap.ts +1043 -0
  413. package/src/providers/meta-facebook-group.ts +690 -0
  414. package/src/providers/meta-marketplace-relay.ts +747 -0
  415. package/src/providers/meta-omni.ts +373 -0
  416. package/src/providers/meta-relay-bundle.ts +1049 -0
  417. package/src/providers/meta-web-descriptors.ts +1740 -0
  418. package/src/providers/meta-web-runtime.ts +3388 -0
  419. package/src/providers/meta-web.ts +2931 -0
  420. package/src/providers/read-failure.ts +139 -0
  421. package/src/providers/reddit-omni.ts +462 -0
  422. package/src/providers/reddit-web-runtime.ts +1929 -0
  423. package/src/providers/reddit-web.ts +1611 -0
  424. package/src/providers/rental-listings.ts +460 -0
  425. package/src/providers/substack-omni.ts +406 -0
  426. package/src/providers/substack-video-mp4.ts +33 -0
  427. package/src/providers/substack-web-runtime.ts +2812 -0
  428. package/src/providers/substack-web.ts +1502 -0
  429. package/src/providers/tiktok-video-mp4.ts +33 -0
  430. package/src/providers/tiktok-web-runtime.ts +770 -0
  431. package/src/providers/tiktok-web.ts +2786 -0
  432. package/src/providers/twitch-web-runtime.ts +226 -0
  433. package/src/providers/twitch-web.ts +274 -0
  434. package/src/providers/whatsapp-account-identity.ts +55 -0
  435. package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
  436. package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
  437. package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
  438. package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
  439. package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
  440. package/src/providers/whatsapp-messaging.ts +132 -0
  441. package/src/providers/whatsapp-omni.ts +586 -0
  442. package/src/providers/whatsapp-web-runtime.ts +3108 -0
  443. package/src/providers/whatsapp-web.ts +1037 -0
  444. package/src/providers/x-image-provenance.ts +523 -0
  445. package/src/providers/x-made-with-ai.ts +106 -0
  446. package/src/providers/x-transaction-id.ts +413 -0
  447. package/src/providers/x-web-runtime.ts +3485 -0
  448. package/src/providers/x-web.ts +2332 -0
  449. package/src/providers/x.ts +1889 -0
  450. package/src/providers/youtube-web-runtime.ts +1400 -0
  451. package/src/providers/youtube-web.ts +1221 -0
  452. package/src/read-admission-runtime.ts +91 -0
  453. package/src/read-client.ts +521 -0
  454. package/src/read-effect-platform.ts +7 -0
  455. package/src/read-effect-runtime.ts +14 -0
  456. package/src/read-effect.ts +52 -0
  457. package/src/read-projection-admission.ts +866 -0
  458. package/src/read-projections.ts +3151 -0
  459. package/src/recovery.ts +1492 -0
  460. package/src/reviewed-template.ts +146 -0
  461. package/src/run-journal.ts +1272 -0
  462. package/src/runtime.ts +5116 -0
  463. package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
  464. package/src/scripts/install-whatsapp-protocol.sh +232 -0
  465. package/src/scripts/recover-install-stages.ts +1491 -0
  466. package/src/scripts/resolve-state-home.ts +30 -0
  467. package/src/scripts/scaffold-web-provider.ts +973 -0
  468. package/src/scripts/sync-bundled-adapters.ts +695 -0
  469. package/src/session-secrets.ts +1183 -0
  470. package/src/state-helper.bunfig.toml +2 -0
  471. package/src/state-helper.ts +2914 -0
  472. package/src/storage.ts +3842 -0
  473. package/src/transport-policy.ts +2 -0
  474. package/src/usage.ts +182 -0
  475. package/src/version.ts +2 -0
  476. package/src/web-session-authentication-policy.ts +136 -0
  477. package/src/web-session-cleanup-admission.ts +2344 -0
  478. package/src/web-session-cleanup-join.ts +99 -0
  479. package/src/web-session-client.ts +996 -0
  480. package/src/web-session-contract-definitions.ts +820 -0
  481. package/src/web-session-contract-planning.ts +42 -0
  482. package/src/web-session-contracts.ts +228 -0
  483. package/src/web-session-cookies.ts +500 -0
  484. package/src/web-session-execution.ts +385 -0
  485. package/src/web-session-http.ts +373 -0
  486. package/src/web-session-read-errors.ts +19 -0
  487. package/src/web-session-read-runtime.ts +124 -0
  488. package/src/web-session-recovery.ts +818 -0
  489. package/src/web-session-sites.ts +58 -0
  490. package/src/web-session-template.ts +994 -0
  491. package/src/web-session.ts +93 -0
  492. package/src/whatsapp-client-binding.ts +21 -0
  493. package/src/whatsapp-client-types.ts +75 -0
  494. package/src/whatsapp-client.ts +395 -0
  495. package/src/whatsapp-export-coordinate.ts +17 -0
  496. package/src/whatsapp-message-bundle-v2.ts +195 -0
  497. package/src/whatsapp-message-like-me-cli.ts +195 -0
  498. package/src/whatsapp-message-like-me-export.ts +1072 -0
  499. package/src/whatsapp-message-like-me-source.ts +1012 -0
  500. package/tsconfig.json +67 -0
@@ -0,0 +1,3342 @@
1
+ import { createHash, timingSafeEqual } from "node:crypto";
2
+ import {
3
+ closeSync,
4
+ constants,
5
+ existsSync,
6
+ fstatSync,
7
+ lstatSync,
8
+ openSync,
9
+ opendirSync,
10
+ readSync,
11
+ realpathSync,
12
+ statSync,
13
+ type BigIntStats,
14
+ } from "node:fs";
15
+ import { builtinModules, createRequire } from "node:module";
16
+ import { homedir } from "node:os";
17
+ import {
18
+ basename,
19
+ dirname,
20
+ extname,
21
+ isAbsolute,
22
+ relative,
23
+ resolve,
24
+ sep,
25
+ } from "node:path";
26
+ import ts from "typescript";
27
+ import {
28
+ hasNonLiteralModuleLoad,
29
+ providerPluginScriptKind,
30
+ } from "./provider-plugin-module-analysis";
31
+
32
+ import {
33
+ reviewedBuiltInContractIdentity,
34
+ reviewedBuiltInContractIdentityIfPresent,
35
+ } from "./provider-plugin-contract-identity";
36
+ import {
37
+ MAX_PROVIDER_PLUGIN_BINDINGS,
38
+ MAX_PROVIDER_PLUGIN_IMPLEMENTATION_SOURCES,
39
+ MAX_PROVIDER_PLUGIN_OPERATIONS,
40
+ MAX_PROVIDER_PLUGIN_OPERATIONS_PER_BINDING,
41
+ PROVIDER_PLUGIN_API_VERSION,
42
+ bindProviderPluginRuntimeLoadIdentity,
43
+ classifyProviderPluginPhysicalPath,
44
+ defineProviderPlugin,
45
+ isProviderPluginInstalledBinaryDirectory,
46
+ isValidatedProviderPlugin,
47
+ portableProviderPluginAdapter,
48
+ portableProviderPluginArtifactSha256,
49
+ portableProviderPluginOperationIdentity,
50
+ providerPluginCanonicalInstalledBinaryLinkTarget,
51
+ providerPluginDurableInstalledPackageMode,
52
+ providerPluginInstalledGuardDirectorySnapshot,
53
+ providerPluginEvaluationInstalledPackageSha256,
54
+ providerPluginEvaluationSourceSha256,
55
+ providerPluginPackageRoot,
56
+ providerPluginRepositoryRoot,
57
+ type ProviderPluginBindingV1,
58
+ type LocalCliPluginOperationV1,
59
+ type ProviderApiPluginOperationV1,
60
+ type ProviderPluginDefinitionV1,
61
+ type ProviderPluginOperationV1,
62
+ type ProviderPluginTransport,
63
+ type ProviderPluginV1,
64
+ type ProviderPluginRuntimeLoadIdentityPhase,
65
+ type WebSessionPluginOperationV1,
66
+ } from "./provider-plugin";
67
+ import type { GhostgetManifest } from "./model";
68
+ import type {
69
+ PortableOperationIdentityV1,
70
+ } from "./provider-plugin-portable-identity";
71
+
72
+ export type ProviderPluginOperationResolutionV1 = {
73
+ readonly plugin: ProviderPluginV1;
74
+ readonly binding: ProviderPluginBindingV1;
75
+ readonly operation: ProviderPluginOperationV1;
76
+ /** Exact durable route version used for this lookup. */
77
+ readonly contractVersion: number;
78
+ /** Exact immutable package identity for portable operations only. */
79
+ readonly portableIdentity: PortableOperationIdentityV1 | null;
80
+ };
81
+
82
+ export type ProviderPluginBindingForTransportV1<
83
+ Transport extends ProviderPluginTransport,
84
+ > = Extract<ProviderPluginBindingV1, { readonly transport: Transport }>;
85
+
86
+ export type ProviderPluginOperationForTransportV1<
87
+ Transport extends ProviderPluginTransport,
88
+ > = Transport extends "provider-api"
89
+ ? ProviderApiPluginOperationV1
90
+ : Transport extends "local-cli"
91
+ ? LocalCliPluginOperationV1
92
+ : WebSessionPluginOperationV1;
93
+
94
+ export type ProviderPluginOperationResolutionForTransportV1<
95
+ Transport extends ProviderPluginTransport,
96
+ > = Omit<ProviderPluginOperationResolutionV1, "binding" | "operation"> & {
97
+ readonly binding: ProviderPluginBindingForTransportV1<Transport>;
98
+ readonly operation: ProviderPluginOperationForTransportV1<Transport>;
99
+ };
100
+
101
+ export type ProviderPluginSessionBindingV1 = Extract<
102
+ ProviderPluginBindingV1,
103
+ { readonly transport: "web-session-api" | "linked-device" }
104
+ >;
105
+
106
+ export type ProviderPluginRegistry = {
107
+ readonly list: () => readonly ProviderPluginV1[];
108
+ readonly get: (pluginId: string) => ProviderPluginV1 | undefined;
109
+ readonly resolveRoute: (
110
+ transport: ProviderPluginTransport,
111
+ surfaceId: string,
112
+ ) => ProviderPluginBindingV1 | undefined;
113
+ readonly requireRoute: (
114
+ transport: ProviderPluginTransport,
115
+ surfaceId: string,
116
+ ) => ProviderPluginBindingV1;
117
+ readonly resolveSessionRoute: (surfaceId: string) => ProviderPluginSessionBindingV1 | undefined;
118
+ readonly requireSessionRoute: (surfaceId: string) => ProviderPluginSessionBindingV1;
119
+ readonly resolveAccountRoute: (surfaceId: string) => ProviderPluginBindingV1 | undefined;
120
+ readonly requireAccountRoute: (surfaceId: string) => ProviderPluginBindingV1;
121
+ readonly resolveOperation: (
122
+ transport: ProviderPluginTransport,
123
+ surfaceId: string,
124
+ operation: string,
125
+ contractVersion: number,
126
+ ) => ProviderPluginBindingV1 | undefined;
127
+ readonly requireOperation: (
128
+ transport: ProviderPluginTransport,
129
+ surfaceId: string,
130
+ operation: string,
131
+ contractVersion: number,
132
+ ) => ProviderPluginBindingV1;
133
+ readonly resolveOperationDefinition: (
134
+ transport: ProviderPluginTransport,
135
+ surfaceId: string,
136
+ operation: string,
137
+ contractVersion: number,
138
+ ) => ProviderPluginOperationResolutionV1 | undefined;
139
+ readonly requireOperationDefinition: (
140
+ transport: ProviderPluginTransport,
141
+ surfaceId: string,
142
+ operation: string,
143
+ contractVersion: number,
144
+ ) => ProviderPluginOperationResolutionV1;
145
+ /** Current exact source/dependency/execution closure used for runtime loading. */
146
+ readonly implementationHash: (binding: ProviderPluginBindingV1) => Buffer;
147
+ /** Stable predecessor-compatible identity used by new durable contract writers. */
148
+ readonly contractImplementationHash: (binding: ProviderPluginBindingV1) => Buffer;
149
+ /** Bounded predecessor common-mode identities accepted only by durable readers. */
150
+ readonly legacyContractImplementationHashes: (
151
+ binding: ProviderPluginBindingV1,
152
+ operation: string,
153
+ contractVersion: number,
154
+ ) => readonly Buffer[];
155
+ /** Environment-independent exact current source/dependency closure identity. */
156
+ readonly implementationClosureHash: (
157
+ binding: ProviderPluginBindingV1,
158
+ ) => string;
159
+ readonly artifactSha256: (binding: ProviderPluginBindingV1) => string | null;
160
+ readonly listOwnedManifests: () => readonly GhostgetManifest[];
161
+ readonly resolveOwnedManifest: (adapterId: string) => GhostgetManifest | undefined;
162
+ };
163
+
164
+ function routeKey(transport: ProviderPluginTransport, surfaceId: string): string {
165
+ return `${transport}:${surfaceId}`;
166
+ }
167
+
168
+ function operationKey(
169
+ transport: ProviderPluginTransport,
170
+ surfaceId: string,
171
+ operation: string,
172
+ contractVersion: number,
173
+ ): string {
174
+ return `${routeKey(transport, surfaceId)}/${operation}@${contractVersion}`;
175
+ }
176
+
177
+ const MAX_PROVIDER_DEPENDENCY_FILE_BYTES = 16 * 1024 * 1024;
178
+ const MAX_INSTALLED_PACKAGE_FILES = 4_096;
179
+ const MAX_INSTALLED_PACKAGE_DIRECTORIES = 1_024;
180
+ const MAX_INSTALLED_PACKAGE_DEPTH = 32;
181
+ const MAX_INSTALLED_PACKAGE_PATH_BYTES = 1_024;
182
+ const MAX_INSTALLED_PACKAGE_TOTAL_BYTES = 128 * 1024 * 1024;
183
+ const MAX_INSTALLED_PACKAGES = 128;
184
+ const MAX_INSTALLED_PACKAGE_GRAPH_DEPTH = 32;
185
+ const MAX_INSTALLED_CLOSURE_FILES = 16_384;
186
+ const MAX_INSTALLED_CLOSURE_DIRECTORIES = 4_096;
187
+ const MAX_INSTALLED_EXECUTABLE_MODULES = 4_096;
188
+ const MAX_PACKAGE_MANIFEST_BYTES = 1024 * 1024;
189
+ const MAX_REGISTRY_INSTALLED_PACKAGES = 256;
190
+ const MAX_REGISTRY_INSTALLED_FILES = 32_768;
191
+ const MAX_REGISTRY_INSTALLED_DIRECTORIES = 8_192;
192
+ const MAX_REGISTRY_INSTALLED_BYTES = 256 * 1024 * 1024;
193
+ const MAX_REGISTRY_SOURCE_PLUGINS = 256;
194
+ export const MAX_PROVIDER_PLUGIN_REGISTRY_ROUTES = 4_096;
195
+ export const MAX_PROVIDER_PLUGIN_REGISTRY_OPERATION_CONTRACTS = 65_536;
196
+ const MAX_REGISTRY_IMPLEMENTATION_SOURCE_READS = 8_192;
197
+ const MAX_REGISTRY_IMPLEMENTATION_SOURCE_BYTES = 256 * 1024 * 1024;
198
+ const MAX_REGISTRY_DEPENDENCY_RECORDS = 65_536;
199
+ const MAX_REGISTRY_DEPENDENCY_RECORD_BYTES = 256 * 1024 * 1024;
200
+ const MAX_PLUGIN_DEPENDENCY_RECORDS = 32_768;
201
+ const MAX_PLUGIN_DEPENDENCY_RECORD_BYTES = 128 * 1024 * 1024;
202
+ const MAX_PROVIDER_EXECUTABLE_MODULE_BYTES = 512 * 1024;
203
+ const MAX_PROVIDER_MODULE_IMPORTS = 4_096;
204
+ const MAX_REGISTRY_ANALYZED_IMPORTS = 65_536;
205
+ const strictSemverPattern =
206
+ /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
207
+
208
+ function compareIdentityText(left: string, right: string): number {
209
+ return Buffer.compare(
210
+ Buffer.from(left, "utf8"),
211
+ Buffer.from(right, "utf8"),
212
+ );
213
+ }
214
+
215
+ function sameFileSnapshot(left: BigIntStats, right: BigIntStats): boolean {
216
+ return left.dev === right.dev
217
+ && left.ino === right.ino
218
+ && left.size === right.size
219
+ && left.mode === right.mode
220
+ && left.mtimeNs === right.mtimeNs
221
+ && left.ctimeNs === right.ctimeNs;
222
+ }
223
+
224
+ function ownedByCurrentUser(stats: BigIntStats): boolean {
225
+ const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
226
+ return uid === undefined || stats.uid === BigInt(uid);
227
+ }
228
+
229
+ function readStableDependencyFile(
230
+ path: string,
231
+ maximumBytes: number,
232
+ label: string,
233
+ ): Buffer {
234
+ let descriptor: number;
235
+ try {
236
+ descriptor = openSync(
237
+ path,
238
+ constants.O_RDONLY
239
+ | ("O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0)
240
+ | ("O_NONBLOCK" in constants ? constants.O_NONBLOCK : 0),
241
+ );
242
+ } catch {
243
+ throw new Error(`${label} is unreadable`);
244
+ }
245
+ try {
246
+ const before = fstatSync(descriptor, { bigint: true });
247
+ if (
248
+ !before.isFile()
249
+ || !ownedByCurrentUser(before)
250
+ || before.size < 0n
251
+ || before.size > BigInt(maximumBytes)
252
+ ) {
253
+ throw new Error(`${label} is not an owned bounded regular file`);
254
+ }
255
+ const bytes = Buffer.alloc(Number(before.size));
256
+ let offset = 0;
257
+ while (offset < bytes.byteLength) {
258
+ const count = readSync(
259
+ descriptor,
260
+ bytes,
261
+ offset,
262
+ bytes.byteLength - offset,
263
+ offset,
264
+ );
265
+ if (count === 0) break;
266
+ offset += count;
267
+ }
268
+ const after = fstatSync(descriptor, { bigint: true });
269
+ let current: BigIntStats;
270
+ try {
271
+ current = lstatSync(path, { bigint: true });
272
+ } catch {
273
+ throw new Error(`${label} changed while it was read`);
274
+ }
275
+ if (
276
+ offset !== bytes.byteLength
277
+ || current.isSymbolicLink()
278
+ || !current.isFile()
279
+ || !sameFileSnapshot(before, after)
280
+ || !sameFileSnapshot(after, current)
281
+ ) {
282
+ throw new Error(`${label} changed while it was read`);
283
+ }
284
+ return bytes;
285
+ } finally {
286
+ closeSync(descriptor);
287
+ }
288
+ }
289
+
290
+ function readImplementationSource(
291
+ pluginId: string,
292
+ source: ProviderPluginV1["implementationSources"][number],
293
+ ): Buffer {
294
+ return readStableDependencyFile(
295
+ source.path,
296
+ MAX_PROVIDER_DEPENDENCY_FILE_BYTES,
297
+ `provider plugin ${pluginId} implementation source ${source.label}`,
298
+ );
299
+ }
300
+
301
+ type ProviderPluginImplementationSourceSnapshot = ReadonlyMap<string, Buffer>;
302
+ type ProviderPluginPackageDependencySnapshot = readonly {
303
+ readonly label: string;
304
+ readonly bytes: Buffer;
305
+ }[];
306
+
307
+ type InstalledPackageFileSnapshot = {
308
+ readonly path: string;
309
+ readonly bytes: Buffer;
310
+ readonly mode: number;
311
+ };
312
+
313
+ type InstalledPackageDirectorySnapshot = {
314
+ readonly path: string;
315
+ readonly mode: number;
316
+ };
317
+
318
+ type InstalledPackageLinkSnapshot = {
319
+ readonly path: string;
320
+ readonly targetPath: string;
321
+ };
322
+
323
+ type InstalledPackageDependencyKind =
324
+ | "dependency"
325
+ | "optional-dependency"
326
+ | "peer-dependency"
327
+ | "optional-peer-dependency";
328
+
329
+ type InstalledPackageDependency = {
330
+ readonly kind: InstalledPackageDependencyKind;
331
+ readonly name: string;
332
+ };
333
+
334
+ type InstalledPackageSnapshot = {
335
+ readonly root: string;
336
+ readonly id: string;
337
+ readonly installName: string;
338
+ readonly name: string;
339
+ readonly version: string;
340
+ readonly treeSha256: string;
341
+ readonly files: readonly InstalledPackageFileSnapshot[];
342
+ readonly links: readonly InstalledPackageLinkSnapshot[];
343
+ readonly directories: readonly InstalledPackageDirectorySnapshot[];
344
+ readonly dependencies: readonly InstalledPackageDependency[];
345
+ readonly totalBytes: number;
346
+ readonly verificationWalk: InstalledPackageTreeWalk;
347
+ };
348
+
349
+ type InstalledModuleAnalysis = {
350
+ readonly imports: readonly {
351
+ readonly kind: string;
352
+ readonly path: string;
353
+ }[];
354
+ readonly nonLiteralModuleLoad: boolean;
355
+ };
356
+
357
+ type ProviderModuleAnalysisCache = {
358
+ readonly values: Map<string, InstalledModuleAnalysis>;
359
+ imports: number;
360
+ };
361
+
362
+ export type ProviderPluginRegistryDependencies = {
363
+ /**
364
+ * Test seam for proving canonical implementation sources are read once.
365
+ * Production callers must use the inode-bound default reader.
366
+ */
367
+ readonly readImplementationSource?: typeof readImplementationSource;
368
+ /**
369
+ * Test seam for dependency snapshot timing. Production callers must use
370
+ * the same inode-bound reader used for implementation sources.
371
+ */
372
+ readonly readDependencySource?: (path: string) => Buffer;
373
+ /** Deterministic test seam immediately before an uncached runtime identity check. */
374
+ readonly beforeRuntimeLoadIdentityCheck?: (
375
+ pluginId: string,
376
+ phase: ProviderPluginRuntimeLoadIdentityPhase,
377
+ ) => void | Promise<void>;
378
+ };
379
+
380
+ function snapshotProviderPluginImplementationSources(
381
+ plugin: ProviderPluginV1,
382
+ readSource: typeof readImplementationSource,
383
+ registrySnapshots: Map<string, Buffer>,
384
+ ): ProviderPluginImplementationSourceSnapshot {
385
+ const snapshots = new Map<string, Buffer>();
386
+ for (const source of plugin.implementationSources) {
387
+ if (snapshots.has(source.path)) continue;
388
+ let bytes = registrySnapshots.get(source.path);
389
+ if (bytes === undefined) {
390
+ bytes = Buffer.from(readSource(plugin.id, source));
391
+ registrySnapshots.set(source.path, bytes);
392
+ }
393
+ snapshots.set(source.path, bytes);
394
+ }
395
+ return snapshots;
396
+ }
397
+
398
+ function implementationSourceBytes(
399
+ plugin: ProviderPluginV1,
400
+ source: ProviderPluginV1["implementationSources"][number],
401
+ snapshots: ProviderPluginImplementationSourceSnapshot,
402
+ ): Buffer {
403
+ const bytes = snapshots.get(source.path);
404
+ if (bytes === undefined) {
405
+ throw new Error(
406
+ `provider plugin ${plugin.id} implementation source ${source.label} was not snapshotted`,
407
+ );
408
+ }
409
+ return bytes;
410
+ }
411
+
412
+ function updateLengthFramedHash(
413
+ hash: ReturnType<typeof createHash>,
414
+ label: string,
415
+ bytes: Buffer,
416
+ ): void {
417
+ const labelBytes = Buffer.from(label, "utf8");
418
+ const lengths = Buffer.alloc(12);
419
+ lengths.writeUInt32BE(labelBytes.byteLength, 0);
420
+ lengths.writeBigUInt64BE(BigInt(bytes.byteLength), 4);
421
+ hash.update(lengths).update(labelBytes).update(bytes);
422
+ }
423
+
424
+ function computeProviderPluginImplementationHash(
425
+ plugin: ProviderPluginV1,
426
+ snapshots: ProviderPluginImplementationSourceSnapshot,
427
+ packageDependencies: ProviderPluginPackageDependencySnapshot,
428
+ ): Buffer {
429
+ const artifactSha256 = portableProviderPluginArtifactSha256(plugin);
430
+ if (artifactSha256 !== null) return Buffer.from(artifactSha256, "hex");
431
+ const hash = createHash("sha256");
432
+ hash.update(
433
+ `provider-plugin-implementation-identity@4/api@${PROVIDER_PLUGIN_API_VERSION}\0`,
434
+ );
435
+ updateLengthFramedHash(
436
+ hash,
437
+ "evaluated-plugin-semantics",
438
+ providerPluginSemanticIdentity(plugin),
439
+ );
440
+ for (
441
+ const source of [...plugin.implementationSources]
442
+ .sort((left, right) => compareIdentityText(left.label, right.label))
443
+ ) {
444
+ updateLengthFramedHash(
445
+ hash,
446
+ `implementation-source/${source.label}`,
447
+ implementationSourceBytes(plugin, source, snapshots),
448
+ );
449
+ }
450
+ for (const dependency of packageDependencies) {
451
+ updateLengthFramedHash(hash, dependency.label, dependency.bytes);
452
+ }
453
+ return hash.digest();
454
+ }
455
+
456
+ function computeProviderPluginClosureHash(
457
+ plugin: ProviderPluginV1,
458
+ snapshots: ProviderPluginImplementationSourceSnapshot,
459
+ packageDependencies: ProviderPluginPackageDependencySnapshot,
460
+ ): string {
461
+ const artifactSha256 = portableProviderPluginArtifactSha256(plugin);
462
+ if (artifactSha256 !== null) return artifactSha256;
463
+ const hash = createHash("sha256");
464
+ hash.update(
465
+ `provider-plugin-reviewed-closure@1/api@${PROVIDER_PLUGIN_API_VERSION}\0`,
466
+ );
467
+ updateLengthFramedHash(
468
+ hash,
469
+ "evaluated-plugin-semantics",
470
+ providerPluginSemanticIdentity(plugin),
471
+ );
472
+ for (
473
+ const source of [...plugin.implementationSources]
474
+ .sort((left, right) => compareIdentityText(left.label, right.label))
475
+ ) {
476
+ updateLengthFramedHash(
477
+ hash,
478
+ `implementation-source/${source.label}`,
479
+ implementationSourceBytes(plugin, source, snapshots),
480
+ );
481
+ }
482
+ for (const dependency of packageDependencies) {
483
+ if (dependency.label.startsWith("execution-")) continue;
484
+ updateLengthFramedHash(hash, dependency.label, dependency.bytes);
485
+ }
486
+ return hash.digest("hex");
487
+ }
488
+
489
+ function providerPluginSemanticValue(
490
+ value: unknown,
491
+ ancestors: Set<object>,
492
+ ): unknown {
493
+ if (
494
+ value === null
495
+ || typeof value === "string"
496
+ || typeof value === "boolean"
497
+ ) return value;
498
+ if (typeof value === "number" && Number.isFinite(value)) return value;
499
+ if (typeof value === "function") return "@provider-plugin-function";
500
+ if (typeof value !== "object") {
501
+ throw new Error("provider plugin semantic identity is not JSON-compatible");
502
+ }
503
+ if (ancestors.has(value)) {
504
+ throw new Error("provider plugin semantic identity contains a cycle");
505
+ }
506
+ ancestors.add(value);
507
+ try {
508
+ if (Array.isArray(value)) {
509
+ return value.map((entry) =>
510
+ providerPluginSemanticValue(entry, ancestors));
511
+ }
512
+ const result: Record<string, unknown> = {};
513
+ for (const key of Object.keys(value).sort(compareIdentityText)) {
514
+ const entry = (value as Readonly<Record<string, unknown>>)[key];
515
+ if (entry === undefined) continue;
516
+ result[key] = providerPluginSemanticValue(entry, ancestors);
517
+ }
518
+ return result;
519
+ } finally {
520
+ ancestors.delete(value);
521
+ }
522
+ }
523
+
524
+ function providerPluginSemanticIdentity(plugin: ProviderPluginV1): Buffer {
525
+ const encoded = JSON.stringify(providerPluginSemanticValue({
526
+ apiVersion: plugin.apiVersion,
527
+ bindings: plugin.bindings,
528
+ displayName: plugin.displayName,
529
+ id: plugin.id,
530
+ sourceKind: plugin.sourceKind,
531
+ version: plugin.version,
532
+ }, new Set()));
533
+ return Buffer.from(encoded, "utf8");
534
+ }
535
+
536
+ function readDependencySourceFromDisk(path: string): Buffer {
537
+ return readImplementationSource(
538
+ "registry-dependency",
539
+ { label: relative(repositoryRoot, path), path },
540
+ );
541
+ }
542
+
543
+ const providerPluginImportScanners = Object.freeze({
544
+ js: new Bun.Transpiler({ loader: "js" }),
545
+ ts: new Bun.Transpiler({ loader: "ts" }),
546
+ });
547
+ const providerPluginModuleExtensions = new Set([
548
+ ".cjs",
549
+ ".cts",
550
+ ".js",
551
+ ".jsx",
552
+ ".mjs",
553
+ ".mts",
554
+ ".ts",
555
+ ".tsx",
556
+ ]);
557
+ const runtimeBuiltins = new Set(
558
+ builtinModules.flatMap((name) =>
559
+ name.startsWith("node:") ? [name, name.slice("node:".length)] : [name]),
560
+ );
561
+ const repositoryRoot = providerPluginRepositoryRoot;
562
+ const repositoryPackagePath = realpathSync(
563
+ resolve(repositoryRoot, "package.json"),
564
+ );
565
+ const repositoryTsconfigPath = realpathSync(
566
+ resolve(repositoryRoot, "tsconfig.json"),
567
+ );
568
+ const repositoryBunfigPath = realpathSync(
569
+ resolve(repositoryRoot, "bunfig.toml"),
570
+ );
571
+ const packageManifestPath = realpathSync(
572
+ resolve(providerPluginPackageRoot, "package.json"),
573
+ );
574
+ const packageTsconfigPath = realpathSync(
575
+ resolve(providerPluginPackageRoot, "tsconfig.json"),
576
+ );
577
+ const providerPluginsApiPath = realpathSync(
578
+ resolve(import.meta.dir, "provider-plugins.ts"),
579
+ );
580
+ const providerPluginAbsoluteImportTargets = new Set([
581
+ realpathSync(resolve(import.meta.dir, "provider-plugin.ts")),
582
+ ]);
583
+ const reviewedMetaDynamicInstalledModuleIdentities = Object.freeze([
584
+ "source-map-support@0.5.21\u0000source-map-support.js\u0000da6f90928140ff29ca0b72f4bf8299deb986ba45f055fc5eb51d50dea2e5364d",
585
+ "typescript@6.0.3\u0000lib/typescript.js\u0000569177652966bd528c319171c7dd22860dbf72bde116cbc4f644f1d02bb12e39",
586
+ ]);
587
+ const reviewedKbDynamicInstalledPackage = Object.freeze({
588
+ name: "@hraness/kb",
589
+ version: "0.19.6",
590
+ keyFile: "dist/index-5vdj4pae.js",
591
+ sha256:
592
+ "107b59e5a662171180d098e30469457f8b2ceeb6ec9d22971fec7076d855704a",
593
+ });
594
+ const reviewedKbDynamicResolutionPolicy =
595
+ "createRequire(parentUrl).resolve(`$" +
596
+ "{packageName}/package.json`) is reached only by resolvePackageDirectory(\"agent-browser\") at module initialization";
597
+ const reviewedDormantDynamicLoaderPolicy =
598
+ "reviewed dependency parser API is not called by the owning Ghostget composition";
599
+ const reviewedDynamicInstalledModuleIdentities =
600
+ reviewedMetaDynamicInstalledModuleIdentities;
601
+ const reviewedKbDynamicInstalledPluginIds = new Set([
602
+ "bluesky-web",
603
+ "clasificados-web",
604
+ "github-web",
605
+ "hacker-news-web",
606
+ "linkedin-web",
607
+ "meta-web",
608
+ "reddit-web",
609
+ "substack-web",
610
+ "tiktok-web",
611
+ "twitch-web",
612
+ "whatsapp-linked-device",
613
+ "x-web",
614
+ "youtube-web",
615
+ ]);
616
+ const reviewedBuiltInDynamicInstalledModules = new Set(
617
+ ["meta-web"].flatMap((pluginId) =>
618
+ reviewedMetaDynamicInstalledModuleIdentities.map(
619
+ (identity) => `${pluginId}\u0000${identity}`,
620
+ )),
621
+ );
622
+
623
+ function discoverReviewedKbDynamicInstalledModuleIdentity(
624
+ snapshot: InstalledPackageSnapshot,
625
+ ): string {
626
+ if (
627
+ snapshot.name !== reviewedKbDynamicInstalledPackage.name
628
+ || snapshot.version !== reviewedKbDynamicInstalledPackage.version
629
+ ) {
630
+ throw new Error(
631
+ `installed KB dynamic-resolution review requires ${reviewedKbDynamicInstalledPackage.name}@${reviewedKbDynamicInstalledPackage.version}, got ${snapshot.id}`,
632
+ );
633
+ }
634
+ const candidates: InstalledPackageFileSnapshot[] = [];
635
+ for (const file of snapshot.files) {
636
+ if (extname(file.path) !== ".js") continue;
637
+ let source: string;
638
+ try {
639
+ source = new TextDecoder("utf-8", { fatal: true }).decode(file.bytes);
640
+ } catch {
641
+ continue;
642
+ }
643
+ if (
644
+ source.match(
645
+ /createRequire\(parentUrl\)\.resolve\(`\$\{packageName\}\/package\.json`\)/gu,
646
+ )?.length !== 1
647
+ || source.match(/resolvePackageDirectory\("agent-browser"\)/gu)?.length !== 1
648
+ ) continue;
649
+ candidates.push(file);
650
+ }
651
+ if (candidates.length !== 1) {
652
+ throw new Error(
653
+ `installed ${snapshot.id} exposes ${String(candidates.length)} dynamic-resolution modules, expected exactly one`,
654
+ );
655
+ }
656
+ const candidate = candidates[0];
657
+ if (candidate === undefined) {
658
+ throw new Error(`installed ${snapshot.id} dynamic-resolution module disappeared`);
659
+ }
660
+ const sha256 = createHash("sha256").update(candidate.bytes).digest("hex");
661
+ if (
662
+ candidate.path !== reviewedKbDynamicInstalledPackage.keyFile
663
+ || sha256 !== reviewedKbDynamicInstalledPackage.sha256
664
+ ) {
665
+ throw new Error(
666
+ `installed ${snapshot.id} dynamic-resolution module ${candidate.path} has sha256 ${sha256}, expected ${reviewedKbDynamicInstalledPackage.keyFile} with sha256 ${reviewedKbDynamicInstalledPackage.sha256}`,
667
+ );
668
+ }
669
+ return `${snapshot.id}\u0000${candidate.path}\u0000${sha256}`;
670
+ }
671
+ const reviewedBuiltInDynamicRepositoryModules = new Set(
672
+ [
673
+ "bluesky-web",
674
+ "hacker-news-web",
675
+ "linkedin-web",
676
+ "meta-web",
677
+ "reddit-web",
678
+ "substack-web",
679
+ "tiktok-web",
680
+ "twitch-web",
681
+ "whatsapp-linked-device",
682
+ "x-web",
683
+ "youtube-web",
684
+ ].map(
685
+ (pluginId) =>
686
+ `${pluginId}\u0000packages/kb/src/clip/package-root.ts\u0000fede9141e7d8a9a900acfae25fd8954e4d09c7d4bdd7e3fa9187ab9722b8b199`,
687
+ ),
688
+ );
689
+
690
+ function valueImports(source: string, path: string): readonly {
691
+ readonly kind: string;
692
+ readonly path: string;
693
+ }[] {
694
+ const moduleSource = source.replace(/^#![^\r\n]*(?:\r?\n|$)/u, "");
695
+ const extension = extname(path);
696
+ if (extension === ".jsx" || extension === ".tsx") {
697
+ throw new Error(
698
+ `provider plugin implementation module ${path} uses configuration-dependent JSX or TSX; publish deterministic JavaScript or TypeScript instead`,
699
+ );
700
+ }
701
+ const scanner =
702
+ extension === ".ts" || extension === ".mts" || extension === ".cts"
703
+ ? providerPluginImportScanners.ts
704
+ : providerPluginImportScanners.js;
705
+ return scanner.scanImports(moduleSource);
706
+ }
707
+
708
+
709
+ function analyzeProviderModule(
710
+ bytes: Buffer,
711
+ path: string,
712
+ cache: ProviderModuleAnalysisCache,
713
+ options: {
714
+ readonly contentSha256?: string;
715
+ readonly reviewedLargeDynamicModule?: boolean;
716
+ } = {},
717
+ ): InstalledModuleAnalysis {
718
+ const contentSha256 = options.contentSha256
719
+ ?? createHash("sha256").update(bytes).digest("hex");
720
+ const reviewedLargeDynamicModule =
721
+ options.reviewedLargeDynamicModule === true;
722
+ if (
723
+ bytes.byteLength > MAX_PROVIDER_EXECUTABLE_MODULE_BYTES
724
+ && !reviewedLargeDynamicModule
725
+ ) {
726
+ throw new Error(
727
+ `provider plugin executable module ${path} exceeds its static-analysis byte bound`,
728
+ );
729
+ }
730
+ const analysisKey =
731
+ `${providerPluginScriptKind(path)}\0${contentSha256}\0${reviewedLargeDynamicModule ? "reviewed-dynamic" : "ordinary"}`;
732
+ const cached = cache.values.get(analysisKey);
733
+ if (cached !== undefined) return cached;
734
+
735
+ let source: string;
736
+ try {
737
+ source = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
738
+ } catch {
739
+ throw new Error(
740
+ `provider plugin executable module ${path} must be valid UTF-8`,
741
+ );
742
+ }
743
+ const imports = Object.freeze([...valueImports(source, path)]);
744
+ if (imports.length > MAX_PROVIDER_MODULE_IMPORTS) {
745
+ throw new Error(
746
+ `provider plugin executable module ${path} has too many static imports`,
747
+ );
748
+ }
749
+ if (cache.imports + imports.length > MAX_REGISTRY_ANALYZED_IMPORTS) {
750
+ throw new Error(
751
+ "provider plugin registry analyzed import closure exceeds its bound",
752
+ );
753
+ }
754
+ const analysis = Object.freeze({
755
+ imports,
756
+ nonLiteralModuleLoad: reviewedLargeDynamicModule
757
+ || hasNonLiteralModuleLoad(source, path),
758
+ });
759
+ cache.imports += imports.length;
760
+ cache.values.set(analysisKey, analysis);
761
+ return analysis;
762
+ }
763
+
764
+ function resolveLocalImport(
765
+ importerPath: string,
766
+ specifier: string,
767
+ importKind = "import-statement",
768
+ ): string | undefined {
769
+ return resolveBareDependencyIfPresent(
770
+ importerPath,
771
+ specifier,
772
+ importKind,
773
+ );
774
+ }
775
+
776
+ function isWithin(root: string, candidate: string): boolean {
777
+ const path = relative(root, candidate);
778
+ return path === ""
779
+ || (path !== ".." && !path.startsWith(`..${sep}`));
780
+ }
781
+
782
+ function isRuntimeBuiltin(specifier: string): boolean {
783
+ return specifier.startsWith("node:")
784
+ || specifier.startsWith("bun:")
785
+ || runtimeBuiltins.has(specifier);
786
+ }
787
+
788
+ function isMissingModuleResolutionError(error: unknown): boolean {
789
+ if (!recordValue(error)) return false;
790
+ const code = error.code;
791
+ const message = error.message;
792
+ return (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND")
793
+ && typeof message === "string"
794
+ && /^Cannot find (?:module|package) /u.test(message);
795
+ }
796
+
797
+ function resolveBareDependencyIfPresent(
798
+ importerPath: string,
799
+ specifier: string,
800
+ importKind = "import-statement",
801
+ ): string | undefined {
802
+ if (isAbsolute(specifier)) {
803
+ let directPath: string;
804
+ try {
805
+ directPath = realpathSync(specifier);
806
+ } catch (error) {
807
+ throw new Error(
808
+ `provider plugin implementation absolute dependency ${specifier} is unreadable`,
809
+ { cause: error },
810
+ );
811
+ }
812
+ if (providerPluginAbsoluteImportTargets.has(directPath)) return directPath;
813
+ throw new Error(
814
+ `provider plugin implementation imports unsupported absolute or URL dependency ${specifier}`,
815
+ );
816
+ }
817
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/u.test(specifier)) {
818
+ throw new Error(
819
+ `provider plugin implementation imports unsupported absolute or URL dependency ${specifier}`,
820
+ );
821
+ }
822
+ let resolvedPath: string;
823
+ try {
824
+ resolvedPath = importKind === "require-call"
825
+ ? createRequire(importerPath).resolve(specifier)
826
+ : Bun.resolveSync(specifier, dirname(importerPath));
827
+ } catch (error) {
828
+ if (isMissingModuleResolutionError(error)) return undefined;
829
+ throw new Error(
830
+ `provider plugin implementation dependency ${specifier} could not be resolved safely`,
831
+ { cause: error },
832
+ );
833
+ }
834
+ if (
835
+ /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(resolvedPath)
836
+ || !isAbsolute(resolvedPath)
837
+ ) {
838
+ throw new Error(
839
+ `provider plugin implementation dependency ${specifier} did not resolve to a local file`,
840
+ );
841
+ }
842
+ let path: string;
843
+ try {
844
+ path = realpathSync(resolvedPath);
845
+ } catch (error) {
846
+ throw new Error(
847
+ `provider plugin implementation dependency ${specifier} resolved to an unreadable path`,
848
+ { cause: error },
849
+ );
850
+ }
851
+ classifyProviderPluginPhysicalPath(path);
852
+ return path;
853
+ }
854
+
855
+ function resolvedBareDependency(
856
+ importerPath: string,
857
+ specifier: string,
858
+ importKind = "import-statement",
859
+ ): string {
860
+ const path = resolveBareDependencyIfPresent(
861
+ importerPath,
862
+ specifier,
863
+ importKind,
864
+ );
865
+ if (path === undefined) {
866
+ throw new Error(
867
+ `provider plugin implementation imports unresolved dependency ${specifier}`,
868
+ );
869
+ }
870
+ return path;
871
+ }
872
+
873
+ function recordValue(value: unknown): value is Record<string, unknown> {
874
+ return typeof value === "object"
875
+ && value !== null
876
+ && !Array.isArray(value);
877
+ }
878
+
879
+ function barePackageName(specifier: string): string | undefined {
880
+ if (specifier.startsWith("@")) {
881
+ const [scope, name] = specifier.split("/");
882
+ return scope !== undefined && name !== undefined
883
+ ? `${scope}/${name}`
884
+ : undefined;
885
+ }
886
+ const [name] = specifier.split("/");
887
+ return name === undefined || name === "" || name.startsWith("#")
888
+ ? undefined
889
+ : name;
890
+ }
891
+
892
+ function installedPackageRoot(
893
+ entryPath: string,
894
+ ): string {
895
+ const physicalPath = classifyProviderPluginPhysicalPath(entryPath);
896
+ if (physicalPath.kind === "installed-package") return physicalPath.root;
897
+ throw new Error(
898
+ "installed package entry has no physical node_modules owner",
899
+ );
900
+ }
901
+
902
+ function packageDependencyNames(
903
+ value: unknown,
904
+ label: string,
905
+ ): readonly string[] {
906
+ if (value === undefined) return Object.freeze([]);
907
+ if (!recordValue(value)) {
908
+ throw new Error(`installed package ${label} must be an object`);
909
+ }
910
+ const names = Object.keys(value).sort();
911
+ for (const name of names) {
912
+ if (
913
+ !/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u.test(name)
914
+ || name.length > 214
915
+ || typeof value[name] !== "string"
916
+ || value[name].length < 1
917
+ || value[name].length > 512
918
+ ) {
919
+ throw new Error(`installed package ${label} is invalid`);
920
+ }
921
+ }
922
+ return Object.freeze(names);
923
+ }
924
+
925
+ function parseInstalledPackageManifest(
926
+ bytes: Buffer,
927
+ root: string,
928
+ ): {
929
+ readonly name: string;
930
+ readonly version: string;
931
+ readonly dependencies: readonly InstalledPackageDependency[];
932
+ } {
933
+ let value: unknown;
934
+ try {
935
+ value = JSON.parse(
936
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes),
937
+ ) as unknown;
938
+ } catch {
939
+ throw new Error(`installed package manifest ${root} is invalid`);
940
+ }
941
+ if (!recordValue(value)) {
942
+ throw new Error(`installed package manifest ${root} must be an object`);
943
+ }
944
+ const name = value.name;
945
+ const version = value.version;
946
+ if (
947
+ typeof name !== "string"
948
+ || name.length > 214
949
+ || !/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u.test(name)
950
+ || typeof version !== "string"
951
+ || version.length > 128
952
+ || !strictSemverPattern.test(version)
953
+ ) {
954
+ throw new Error(`installed package manifest ${root} has invalid identity`);
955
+ }
956
+ const optional = new Set(
957
+ packageDependencyNames(
958
+ value.optionalDependencies,
959
+ `${name} optionalDependencies`,
960
+ ),
961
+ );
962
+ const dependencies: InstalledPackageDependency[] = [];
963
+ for (
964
+ const dependency of packageDependencyNames(
965
+ value.dependencies,
966
+ `${name} dependencies`,
967
+ )
968
+ ) {
969
+ if (optional.has(dependency)) continue;
970
+ dependencies.push({ kind: "dependency", name: dependency });
971
+ }
972
+ for (const dependency of [...optional].sort()) {
973
+ dependencies.push({ kind: "optional-dependency", name: dependency });
974
+ }
975
+ for (
976
+ const dependency of packageDependencyNames(
977
+ value.peerDependencies,
978
+ `${name} peerDependencies`,
979
+ )
980
+ ) {
981
+ let optionalPeer = false;
982
+ if (value.peerDependenciesMeta !== undefined) {
983
+ if (!recordValue(value.peerDependenciesMeta)) {
984
+ throw new Error(
985
+ `installed package ${name} peerDependenciesMeta must be an object`,
986
+ );
987
+ }
988
+ const metadata = value.peerDependenciesMeta[dependency];
989
+ if (metadata !== undefined) {
990
+ if (
991
+ !recordValue(metadata)
992
+ || (
993
+ metadata.optional !== undefined
994
+ && typeof metadata.optional !== "boolean"
995
+ )
996
+ ) {
997
+ throw new Error(
998
+ `installed package ${name} peerDependenciesMeta is invalid`,
999
+ );
1000
+ }
1001
+ optionalPeer = metadata.optional === true;
1002
+ }
1003
+ }
1004
+ dependencies.push({
1005
+ kind: optionalPeer ? "optional-peer-dependency" : "peer-dependency",
1006
+ name: dependency,
1007
+ });
1008
+ }
1009
+ dependencies.sort((left, right) =>
1010
+ compareIdentityText(left.kind, right.kind)
1011
+ || compareIdentityText(left.name, right.name));
1012
+ return {
1013
+ name,
1014
+ version,
1015
+ dependencies: Object.freeze(dependencies),
1016
+ };
1017
+ }
1018
+
1019
+ type InstalledPackageTreeEntry =
1020
+ | {
1021
+ readonly path: string;
1022
+ readonly kind: "directory" | "guard-directory" | "file";
1023
+ readonly stats: BigIntStats;
1024
+ }
1025
+ | {
1026
+ readonly path: string;
1027
+ readonly kind: "link";
1028
+ readonly stats: BigIntStats;
1029
+ readonly targetPath: string;
1030
+ };
1031
+
1032
+ type InstalledPackageTreeWalk = {
1033
+ readonly entries: readonly InstalledPackageTreeEntry[];
1034
+ readonly fileCount: number;
1035
+ readonly linkCount: number;
1036
+ readonly directoryCount: number;
1037
+ readonly totalBytes: number;
1038
+ };
1039
+
1040
+ function walkInstalledPackageTree(root: string): InstalledPackageTreeWalk {
1041
+ const entries: InstalledPackageTreeEntry[] = [];
1042
+ const capturedFiles = new Map<string, BigIntStats>();
1043
+ const capturedGuardDirectories = new Map<string, BigIntStats>();
1044
+ let fileCount = 0;
1045
+ let linkCount = 0;
1046
+ let directoryCount = 0;
1047
+ let totalBytes = 0;
1048
+ const captureFile = (
1049
+ relativePath: string,
1050
+ stats: BigIntStats,
1051
+ ): void => {
1052
+ const existing = capturedFiles.get(relativePath);
1053
+ if (existing !== undefined) {
1054
+ if (!sameFileSnapshot(existing, stats)) {
1055
+ throw new Error(
1056
+ `installed package file ${relativePath} changed while it was walked`,
1057
+ );
1058
+ }
1059
+ return;
1060
+ }
1061
+ if (
1062
+ !stats.isFile()
1063
+ || stats.isSymbolicLink()
1064
+ || !ownedByCurrentUser(stats)
1065
+ ) {
1066
+ throw new Error(
1067
+ `installed package tree contains unsupported entry ${relativePath}`,
1068
+ );
1069
+ }
1070
+ fileCount += 1;
1071
+ if (fileCount + linkCount > MAX_INSTALLED_PACKAGE_FILES) {
1072
+ throw new Error("installed package tree exceeds its file bound");
1073
+ }
1074
+ if (
1075
+ stats.size < 0n
1076
+ || stats.size > BigInt(MAX_PROVIDER_DEPENDENCY_FILE_BYTES)
1077
+ ) {
1078
+ throw new Error(
1079
+ `installed package file ${relativePath} exceeds its byte bound`,
1080
+ );
1081
+ }
1082
+ totalBytes += Number(stats.size);
1083
+ if (totalBytes > MAX_INSTALLED_PACKAGE_TOTAL_BYTES) {
1084
+ throw new Error("installed package tree exceeds its total byte bound");
1085
+ }
1086
+ capturedFiles.set(relativePath, stats);
1087
+ entries.push(Object.freeze({
1088
+ path: relativePath,
1089
+ kind: "file",
1090
+ stats,
1091
+ }));
1092
+ };
1093
+ const captureGuardDirectory = (relativePath: string): void => {
1094
+ const stats = providerPluginInstalledGuardDirectorySnapshot(
1095
+ resolve(root, relativePath),
1096
+ `installed package directory ${relativePath}`,
1097
+ );
1098
+ const existing = capturedGuardDirectories.get(relativePath);
1099
+ if (existing !== undefined) {
1100
+ if (!sameFileSnapshot(existing, stats)) {
1101
+ throw new Error(
1102
+ `installed package binary directory ${relativePath} changed while it was walked`,
1103
+ );
1104
+ }
1105
+ return;
1106
+ }
1107
+ directoryCount += 1;
1108
+ if (directoryCount > MAX_INSTALLED_PACKAGE_DIRECTORIES) {
1109
+ throw new Error("installed package tree exceeds its directory bound");
1110
+ }
1111
+ capturedGuardDirectories.set(relativePath, stats);
1112
+ entries.push(Object.freeze({
1113
+ path: relativePath,
1114
+ kind: "guard-directory",
1115
+ stats,
1116
+ }));
1117
+ };
1118
+ const captureTargetGuardDirectories = (targetPath: string): void => {
1119
+ const parent = dirname(targetPath).split(sep).join("/");
1120
+ if (parent === ".") return;
1121
+ const segments = parent.split("/");
1122
+ if (segments.length > MAX_INSTALLED_PACKAGE_DEPTH) {
1123
+ throw new Error("installed package tree exceeds its depth bound");
1124
+ }
1125
+ for (let index = 1; index <= segments.length; index += 1) {
1126
+ captureGuardDirectory(segments.slice(0, index).join("/"));
1127
+ }
1128
+ };
1129
+ const visit = (
1130
+ directoryPath: string,
1131
+ relativeDirectory: string,
1132
+ depth: number,
1133
+ ): void => {
1134
+ if (depth > MAX_INSTALLED_PACKAGE_DEPTH) {
1135
+ throw new Error("installed package tree exceeds its depth bound");
1136
+ }
1137
+ let before: BigIntStats;
1138
+ try {
1139
+ before = lstatSync(directoryPath, { bigint: true });
1140
+ } catch {
1141
+ throw new Error(
1142
+ `installed package directory ${relativeDirectory || "."} changed while it was walked`,
1143
+ );
1144
+ }
1145
+ if (
1146
+ before.isSymbolicLink()
1147
+ || !before.isDirectory()
1148
+ || !ownedByCurrentUser(before)
1149
+ || (before.mode & 0o022n) !== 0n
1150
+ ) {
1151
+ throw new Error(
1152
+ `installed package directory ${relativeDirectory || "."} is unsafe`,
1153
+ );
1154
+ }
1155
+ directoryCount += 1;
1156
+ if (directoryCount > MAX_INSTALLED_PACKAGE_DIRECTORIES) {
1157
+ throw new Error("installed package tree exceeds its directory bound");
1158
+ }
1159
+ entries.push(Object.freeze({
1160
+ path: relativeDirectory === "" ? "." : relativeDirectory,
1161
+ kind: "directory",
1162
+ stats: before,
1163
+ }));
1164
+ const descriptor = openSync(
1165
+ directoryPath,
1166
+ constants.O_RDONLY
1167
+ | ("O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0)
1168
+ | ("O_DIRECTORY" in constants ? constants.O_DIRECTORY : 0),
1169
+ );
1170
+ try {
1171
+ const bound = fstatSync(descriptor, { bigint: true });
1172
+ if (!bound.isDirectory() || !sameFileSnapshot(before, bound)) {
1173
+ throw new Error(
1174
+ `installed package directory ${relativeDirectory || "."} changed while it was bound`,
1175
+ );
1176
+ }
1177
+ const names: string[] = [];
1178
+ const directory = opendirSync(directoryPath);
1179
+ try {
1180
+ for (;;) {
1181
+ const entry = directory.readSync();
1182
+ if (entry === null) break;
1183
+ names.push(entry.name);
1184
+ if (
1185
+ names.length
1186
+ > MAX_INSTALLED_PACKAGE_FILES
1187
+ + MAX_INSTALLED_PACKAGE_DIRECTORIES
1188
+ || entries.length + names.length
1189
+ > MAX_INSTALLED_PACKAGE_FILES
1190
+ + MAX_INSTALLED_PACKAGE_DIRECTORIES
1191
+ ) {
1192
+ throw new Error("installed package tree exceeds its entry bound");
1193
+ }
1194
+ }
1195
+ } finally {
1196
+ directory.closeSync();
1197
+ }
1198
+ names.sort(compareIdentityText);
1199
+ for (const name of names) {
1200
+ const relativePath = relativeDirectory === ""
1201
+ ? name
1202
+ : `${relativeDirectory}/${name}`;
1203
+ if (
1204
+ name === "."
1205
+ || name === ".."
1206
+ || name.includes("/")
1207
+ || relativePath.includes("\\")
1208
+ || relativePath.includes("\u0000")
1209
+ || Buffer.byteLength(relativePath, "utf8")
1210
+ > MAX_INSTALLED_PACKAGE_PATH_BYTES
1211
+ ) {
1212
+ throw new Error("installed package tree contains an unsafe path");
1213
+ }
1214
+ const path = resolve(directoryPath, name);
1215
+ let stats: BigIntStats;
1216
+ try {
1217
+ stats = lstatSync(path, { bigint: true });
1218
+ } catch {
1219
+ throw new Error(
1220
+ `installed package entry ${relativePath} changed while it was walked`,
1221
+ );
1222
+ }
1223
+ if (stats.isSymbolicLink()) {
1224
+ if (!isProviderPluginInstalledBinaryDirectory(relativeDirectory)) {
1225
+ throw new Error(
1226
+ `installed package tree contains symlink ${relativePath}`,
1227
+ );
1228
+ }
1229
+ linkCount += 1;
1230
+ if (fileCount + linkCount > MAX_INSTALLED_PACKAGE_FILES) {
1231
+ throw new Error("installed package tree exceeds its file bound");
1232
+ }
1233
+ const target = providerPluginCanonicalInstalledBinaryLinkTarget(
1234
+ root,
1235
+ path,
1236
+ stats,
1237
+ MAX_INSTALLED_PACKAGE_PATH_BYTES,
1238
+ `installed package npm binary link ${relativePath}`,
1239
+ );
1240
+ totalBytes += Buffer.byteLength(target.path, "utf8");
1241
+ if (totalBytes > MAX_INSTALLED_PACKAGE_TOTAL_BYTES) {
1242
+ throw new Error("installed package tree exceeds its total byte bound");
1243
+ }
1244
+ entries.push(Object.freeze({
1245
+ path: relativePath,
1246
+ kind: "link",
1247
+ stats,
1248
+ targetPath: target.path,
1249
+ }));
1250
+ captureTargetGuardDirectories(target.path);
1251
+ captureFile(target.path, target.stats);
1252
+ continue;
1253
+ }
1254
+ if (stats.isDirectory()) {
1255
+ if (isProviderPluginInstalledBinaryDirectory(relativeDirectory)) {
1256
+ throw new Error(
1257
+ `installed package tree contains unsupported entry ${relativePath}`,
1258
+ );
1259
+ }
1260
+ // Only installer-owned binary shims and their canonical regular-file
1261
+ // targets contribute from nested node_modules. Statically imported
1262
+ // packages are snapshotted independently below.
1263
+ if (name === "node_modules") {
1264
+ const binaryDirectory = resolve(path, ".bin");
1265
+ const binaryStats = lstatSync(binaryDirectory, {
1266
+ bigint: true,
1267
+ throwIfNoEntry: false,
1268
+ });
1269
+ if (binaryStats !== undefined) {
1270
+ captureGuardDirectory(relativePath);
1271
+ visit(
1272
+ binaryDirectory,
1273
+ `${relativePath}/.bin`,
1274
+ depth + 2,
1275
+ );
1276
+ }
1277
+ continue;
1278
+ }
1279
+ visit(path, relativePath, depth + 1);
1280
+ continue;
1281
+ }
1282
+ captureFile(relativePath, stats);
1283
+ }
1284
+ const after = fstatSync(descriptor, { bigint: true });
1285
+ let current: BigIntStats;
1286
+ try {
1287
+ current = lstatSync(directoryPath, { bigint: true });
1288
+ } catch {
1289
+ throw new Error(
1290
+ `installed package directory ${relativeDirectory || "."} changed while it was walked`,
1291
+ );
1292
+ }
1293
+ if (
1294
+ current.isSymbolicLink()
1295
+ || !current.isDirectory()
1296
+ || !sameFileSnapshot(before, after)
1297
+ || !sameFileSnapshot(after, current)
1298
+ ) {
1299
+ throw new Error(
1300
+ `installed package directory ${relativeDirectory || "."} changed while it was walked`,
1301
+ );
1302
+ }
1303
+ } finally {
1304
+ closeSync(descriptor);
1305
+ }
1306
+ };
1307
+ visit(root, "", 0);
1308
+ entries.sort((left, right) =>
1309
+ compareIdentityText(left.path, right.path)
1310
+ || compareIdentityText(left.kind, right.kind));
1311
+ return Object.freeze({
1312
+ entries: Object.freeze(entries),
1313
+ fileCount,
1314
+ linkCount,
1315
+ directoryCount,
1316
+ totalBytes,
1317
+ });
1318
+ }
1319
+
1320
+ function sameInstalledPackageTree(
1321
+ before: InstalledPackageTreeWalk,
1322
+ after: InstalledPackageTreeWalk,
1323
+ ): boolean {
1324
+ if (
1325
+ before.entries.length !== after.entries.length
1326
+ || before.fileCount !== after.fileCount
1327
+ || before.linkCount !== after.linkCount
1328
+ || before.directoryCount !== after.directoryCount
1329
+ || before.totalBytes !== after.totalBytes
1330
+ ) return false;
1331
+ return before.entries.every((entry, index) => {
1332
+ const current = after.entries[index];
1333
+ return current !== undefined
1334
+ && entry.path === current.path
1335
+ && entry.kind === current.kind
1336
+ && (
1337
+ entry.kind !== "link"
1338
+ || (current.kind === "link" && entry.targetPath === current.targetPath)
1339
+ )
1340
+ && sameFileSnapshot(entry.stats, current.stats);
1341
+ });
1342
+ }
1343
+
1344
+ function assertInstalledPackageRegistryCacheBounds(
1345
+ snapshots: ReadonlyMap<string, InstalledPackageSnapshot>,
1346
+ ): void {
1347
+ if (snapshots.size > MAX_REGISTRY_INSTALLED_PACKAGES) {
1348
+ throw new Error("provider plugin registry installed package cache is too large");
1349
+ }
1350
+ let files = 0;
1351
+ let directories = 0;
1352
+ let bytes = 0;
1353
+ for (const snapshot of snapshots.values()) {
1354
+ files += snapshot.files.length + snapshot.links.length;
1355
+ directories += snapshot.verificationWalk.directoryCount;
1356
+ bytes += snapshot.totalBytes;
1357
+ }
1358
+ if (
1359
+ files > MAX_REGISTRY_INSTALLED_FILES
1360
+ || directories > MAX_REGISTRY_INSTALLED_DIRECTORIES
1361
+ ) {
1362
+ throw new Error(
1363
+ "provider plugin registry installed package cache exceeds its entry bound",
1364
+ );
1365
+ }
1366
+ if (bytes > MAX_REGISTRY_INSTALLED_BYTES) {
1367
+ throw new Error(
1368
+ "provider plugin registry installed package cache exceeds its byte bound",
1369
+ );
1370
+ }
1371
+ }
1372
+
1373
+ function snapshotInstalledPackage(
1374
+ entryPath: string,
1375
+ expectedPackageName: string,
1376
+ snapshotDependencySource: (path: string) => Buffer,
1377
+ ): InstalledPackageSnapshot {
1378
+ const root = installedPackageRoot(entryPath);
1379
+ const before = walkInstalledPackageTree(root);
1380
+ const files: InstalledPackageFileSnapshot[] = [];
1381
+ const links: InstalledPackageLinkSnapshot[] = [];
1382
+ const directories: InstalledPackageDirectorySnapshot[] = [];
1383
+ for (const entry of before.entries) {
1384
+ if (entry.kind === "guard-directory") continue;
1385
+ if (entry.kind === "directory") {
1386
+ directories.push(Object.freeze({
1387
+ path: entry.path,
1388
+ mode: Number(entry.stats.mode & 0o777n),
1389
+ }));
1390
+ continue;
1391
+ }
1392
+ if (entry.kind === "link") {
1393
+ links.push(Object.freeze({
1394
+ path: entry.path,
1395
+ targetPath: entry.targetPath,
1396
+ }));
1397
+ continue;
1398
+ }
1399
+ const path = resolve(root, entry.path);
1400
+ const bytes = Buffer.from(snapshotDependencySource(path));
1401
+ let current: BigIntStats;
1402
+ try {
1403
+ current = lstatSync(path, { bigint: true });
1404
+ } catch {
1405
+ throw new Error(
1406
+ `installed package file ${entry.path} changed while it was snapshotted`,
1407
+ );
1408
+ }
1409
+ if (
1410
+ bytes.byteLength !== Number(entry.stats.size)
1411
+ || current.isSymbolicLink()
1412
+ || !current.isFile()
1413
+ || !sameFileSnapshot(entry.stats, current)
1414
+ ) {
1415
+ throw new Error(
1416
+ `installed package file ${entry.path} changed while it was snapshotted`,
1417
+ );
1418
+ }
1419
+ files.push(Object.freeze({
1420
+ path: entry.path,
1421
+ bytes,
1422
+ mode: Number(entry.stats.mode & 0o777n),
1423
+ }));
1424
+ }
1425
+ const filesByPath = new Map(files.map((file) => [file.path, file]));
1426
+ for (const link of links) {
1427
+ if (!filesByPath.has(link.targetPath)) {
1428
+ throw new Error(
1429
+ `installed package npm binary link ${link.path} target is not part of the captured package tree`,
1430
+ );
1431
+ }
1432
+ }
1433
+ const after = walkInstalledPackageTree(root);
1434
+ if (!sameInstalledPackageTree(before, after)) {
1435
+ throw new Error("installed package tree changed while it was snapshotted");
1436
+ }
1437
+ const manifest = files.find((file) => file.path === "package.json");
1438
+ if (
1439
+ manifest === undefined
1440
+ || manifest.bytes.byteLength < 1
1441
+ || manifest.bytes.byteLength > MAX_PACKAGE_MANIFEST_BYTES
1442
+ ) {
1443
+ throw new Error("installed package tree has no bounded package.json");
1444
+ }
1445
+ const parsed = parseInstalledPackageManifest(manifest.bytes, root);
1446
+ const treeHash = createHash("sha256");
1447
+ treeHash.update("provider-plugin-installed-package-tree@2\0");
1448
+ updateLengthFramedHash(
1449
+ treeHash,
1450
+ "identity",
1451
+ Buffer.from(`${parsed.name}@${parsed.version}`, "utf8"),
1452
+ );
1453
+ for (const directory of directories) {
1454
+ const durableMode = providerPluginDurableInstalledPackageMode(
1455
+ "directory",
1456
+ directory.mode,
1457
+ );
1458
+ updateLengthFramedHash(
1459
+ treeHash,
1460
+ `directory/${directory.path}`,
1461
+ Buffer.from(`mode:${durableMode.toString(8)}`, "utf8"),
1462
+ );
1463
+ }
1464
+ for (const file of files) {
1465
+ const durableMode = providerPluginDurableInstalledPackageMode(
1466
+ "file",
1467
+ file.mode,
1468
+ );
1469
+ updateLengthFramedHash(
1470
+ treeHash,
1471
+ `file-mode/${file.path}`,
1472
+ Buffer.from(`mode:${durableMode.toString(8)}`, "utf8"),
1473
+ );
1474
+ updateLengthFramedHash(treeHash, `file/${file.path}`, file.bytes);
1475
+ }
1476
+ for (const link of links) {
1477
+ if (!filesByPath.has(link.targetPath)) {
1478
+ throw new Error(
1479
+ `installed package npm binary link ${link.path} target is unavailable`,
1480
+ );
1481
+ }
1482
+ updateLengthFramedHash(
1483
+ treeHash,
1484
+ `link/${link.path}`,
1485
+ Buffer.from(link.targetPath, "utf8"),
1486
+ );
1487
+ }
1488
+ return Object.freeze({
1489
+ root,
1490
+ id: `${parsed.name}@${parsed.version}`,
1491
+ installName: expectedPackageName,
1492
+ name: parsed.name,
1493
+ version: parsed.version,
1494
+ treeSha256: treeHash.digest("hex"),
1495
+ files: Object.freeze(files),
1496
+ links: Object.freeze(links),
1497
+ directories: Object.freeze(directories),
1498
+ dependencies: parsed.dependencies,
1499
+ totalBytes: before.totalBytes,
1500
+ verificationWalk: after,
1501
+ });
1502
+ }
1503
+
1504
+ /**
1505
+ * Bind package imports to an exact, relocation-stable runtime closure.
1506
+ * Repository workspace modules contribute only their recursively imported
1507
+ * source bytes. Installed packages contribute bounded atomic package trees
1508
+ * plus exact resolution edges for required, optional, and peer dependencies.
1509
+ */
1510
+ function providerPluginPackageDependencyIdentity(
1511
+ plugin: ProviderPluginV1,
1512
+ implementationSources: ProviderPluginImplementationSourceSnapshot,
1513
+ snapshotDependencySource: (path: string) => Buffer,
1514
+ installedPackageSnapshots: Map<string, InstalledPackageSnapshot> = new Map(),
1515
+ moduleAnalyses: ProviderModuleAnalysisCache = {
1516
+ values: new Map(),
1517
+ imports: 0,
1518
+ },
1519
+ snapshotExecutionConfigSource: (path: string) => Buffer =
1520
+ snapshotDependencySource,
1521
+ ): ProviderPluginPackageDependencySnapshot {
1522
+ const records = new Map<string, Buffer>();
1523
+ let recordBytes = 0;
1524
+ const addRecord = (label: string, value: Buffer | string): void => {
1525
+ const bytes = typeof value === "string"
1526
+ ? Buffer.from(value, "utf8")
1527
+ : value;
1528
+ const existing = records.get(label);
1529
+ if (existing !== undefined) {
1530
+ if (!existing.equals(bytes)) {
1531
+ throw new Error(
1532
+ `provider plugin ${plugin.id} dependency identity is ambiguous at ${label}`,
1533
+ );
1534
+ }
1535
+ return;
1536
+ }
1537
+ if (
1538
+ records.size >= MAX_PLUGIN_DEPENDENCY_RECORDS
1539
+ || recordBytes + bytes.byteLength > MAX_PLUGIN_DEPENDENCY_RECORD_BYTES
1540
+ ) {
1541
+ throw new Error(
1542
+ `provider plugin ${plugin.id} dependency identity exceeds its record bound`,
1543
+ );
1544
+ }
1545
+ records.set(label, Buffer.from(bytes));
1546
+ recordBytes += bytes.byteLength;
1547
+ };
1548
+ const addResolutionEdge = (
1549
+ importer: string,
1550
+ specifier: string,
1551
+ target: string,
1552
+ importKind: string,
1553
+ ): void => {
1554
+ const coordinate = JSON.stringify([importer, specifier, importKind]);
1555
+ addRecord(
1556
+ `dependency-edge/${Buffer.from(coordinate, "utf8").toString("base64url")}`,
1557
+ JSON.stringify({ importer, importKind, specifier, target }),
1558
+ );
1559
+ };
1560
+ const bindExecutionConfigFile = (
1561
+ label: string,
1562
+ path: string,
1563
+ ): Buffer => {
1564
+ const bytes = snapshotExecutionConfigSource(path);
1565
+ addRecord(`execution-config/${label}`, bytes);
1566
+ return bytes;
1567
+ };
1568
+ const stableExecutionConfigJson = (value: unknown): string => {
1569
+ if (
1570
+ value === null
1571
+ || typeof value === "boolean"
1572
+ || typeof value === "string"
1573
+ ) return JSON.stringify(value);
1574
+ if (typeof value === "number" && Number.isFinite(value)) {
1575
+ return JSON.stringify(value);
1576
+ }
1577
+ if (Array.isArray(value)) {
1578
+ return `[${value.map(stableExecutionConfigJson).join(",")}]`;
1579
+ }
1580
+ if (typeof value === "object") {
1581
+ const record = value as Readonly<Record<string, unknown>>;
1582
+ return `{${Object.keys(record).sort(compareIdentityText).map((key) =>
1583
+ `${JSON.stringify(key)}:${stableExecutionConfigJson(record[key])}`)
1584
+ .join(",")}}`;
1585
+ }
1586
+ throw new Error(
1587
+ `provider plugin ${plugin.id} execution configuration is not JSON-compatible`,
1588
+ );
1589
+ };
1590
+ const bindPackageExecutionConfig = (
1591
+ label: string,
1592
+ path: string,
1593
+ ): void => {
1594
+ const bytes = snapshotExecutionConfigSource(path);
1595
+ let parsed: unknown;
1596
+ try {
1597
+ parsed = JSON.parse(bytes.toString("utf8")) as unknown;
1598
+ } catch {
1599
+ throw new Error(
1600
+ `provider plugin ${plugin.id} execution package.json is invalid`,
1601
+ );
1602
+ }
1603
+ if (
1604
+ typeof parsed !== "object"
1605
+ || parsed === null
1606
+ || Array.isArray(parsed)
1607
+ ) {
1608
+ throw new Error(
1609
+ `provider plugin ${plugin.id} execution package.json must be an object`,
1610
+ );
1611
+ }
1612
+ const manifest = parsed as Readonly<Record<string, unknown>>;
1613
+ addRecord(
1614
+ `execution-config/${label}`,
1615
+ stableExecutionConfigJson({
1616
+ exports: manifest.exports ?? null,
1617
+ imports: manifest.imports ?? null,
1618
+ name: manifest.name ?? null,
1619
+ type: manifest.type ?? "commonjs",
1620
+ }),
1621
+ );
1622
+ };
1623
+ const requireSafeBunfig = (
1624
+ bytes: Buffer,
1625
+ label: string,
1626
+ ): void => {
1627
+ let parsed: unknown;
1628
+ try {
1629
+ parsed = Bun.TOML.parse(bytes.toString("utf8"));
1630
+ } catch {
1631
+ throw new Error(`provider plugin ${plugin.id} ${label} is invalid TOML`);
1632
+ }
1633
+ if (
1634
+ typeof parsed !== "object"
1635
+ || parsed === null
1636
+ || Array.isArray(parsed)
1637
+ ) {
1638
+ throw new Error(`provider plugin ${plugin.id} ${label} must be a table`);
1639
+ }
1640
+ const unsupported = Object.keys(parsed).filter((key) => key !== "install");
1641
+ if (unsupported.length > 0) {
1642
+ throw new Error(
1643
+ `provider plugin ${plugin.id} ${label} contains unsupported runtime configuration: ${unsupported.sort(compareIdentityText).join(", ")}`,
1644
+ );
1645
+ }
1646
+ };
1647
+ const resolveExtendedTsconfig = (
1648
+ configPath: string,
1649
+ specifier: string,
1650
+ ): string => {
1651
+ let candidate: string;
1652
+ if (specifier.startsWith(".") || isAbsolute(specifier)) {
1653
+ const base = resolve(dirname(configPath), specifier);
1654
+ const candidates = [
1655
+ base,
1656
+ ...(base.endsWith(".json") ? [] : [`${base}.json`]),
1657
+ ];
1658
+ const matched = candidates.find((path) => {
1659
+ try {
1660
+ return statSync(path).isFile();
1661
+ } catch {
1662
+ return false;
1663
+ }
1664
+ });
1665
+ if (matched === undefined) {
1666
+ throw new Error(
1667
+ `provider plugin ${plugin.id} tsconfig extends unresolved ${specifier}`,
1668
+ );
1669
+ }
1670
+ candidate = matched;
1671
+ } else {
1672
+ try {
1673
+ candidate = createRequire(configPath).resolve(specifier);
1674
+ } catch {
1675
+ throw new Error(
1676
+ `provider plugin ${plugin.id} tsconfig extends unresolved ${specifier}`,
1677
+ );
1678
+ }
1679
+ }
1680
+ const canonical = realpathSync(candidate);
1681
+ if (!isWithin(repositoryRoot, canonical)) {
1682
+ throw new Error(
1683
+ `provider plugin ${plugin.id} tsconfig extends outside the repository`,
1684
+ );
1685
+ }
1686
+ return canonical;
1687
+ };
1688
+ const bindTsconfigClosure = (
1689
+ entryPath: string,
1690
+ ): void => {
1691
+ const pendingConfigs = [entryPath];
1692
+ const visited = new Set<string>();
1693
+ while (pendingConfigs.length > 0) {
1694
+ if (visited.size >= 32) {
1695
+ throw new Error(
1696
+ `provider plugin ${plugin.id} tsconfig extends chain is too deep`,
1697
+ );
1698
+ }
1699
+ const configPath = pendingConfigs.pop();
1700
+ if (configPath === undefined || visited.has(configPath)) continue;
1701
+ visited.add(configPath);
1702
+ const relativePath = relative(repositoryRoot, configPath)
1703
+ .split(sep).join("/");
1704
+ const bytes = bindExecutionConfigFile(
1705
+ `typescript/${relativePath}`,
1706
+ configPath,
1707
+ );
1708
+ const parsed = ts.parseConfigFileTextToJson(
1709
+ configPath,
1710
+ bytes.toString("utf8"),
1711
+ );
1712
+ if (parsed.error !== undefined || parsed.config === undefined) {
1713
+ throw new Error(
1714
+ `provider plugin ${plugin.id} execution tsconfig ${relativePath} is invalid`,
1715
+ );
1716
+ }
1717
+ const rawExtends: unknown = (
1718
+ parsed.config as Readonly<Record<string, unknown>>
1719
+ ).extends;
1720
+ const specifiers =
1721
+ typeof rawExtends === "string"
1722
+ ? [rawExtends]
1723
+ : Array.isArray(rawExtends)
1724
+ ? rawExtends
1725
+ : [];
1726
+ if (
1727
+ specifiers.some((specifier) => typeof specifier !== "string")
1728
+ ) {
1729
+ throw new Error(
1730
+ `provider plugin ${plugin.id} execution tsconfig ${relativePath} has invalid extends`,
1731
+ );
1732
+ }
1733
+ for (const specifier of specifiers as string[]) {
1734
+ const extendedPath = resolveExtendedTsconfig(configPath, specifier);
1735
+ addRecord(
1736
+ `execution-config/typescript-extends/${Buffer.from(
1737
+ JSON.stringify([relativePath, specifier]),
1738
+ "utf8",
1739
+ ).toString("base64url")}`,
1740
+ JSON.stringify({
1741
+ from: relativePath,
1742
+ specifier,
1743
+ target: relative(repositoryRoot, extendedPath)
1744
+ .split(sep).join("/"),
1745
+ }),
1746
+ );
1747
+ pendingConfigs.push(extendedPath);
1748
+ }
1749
+ }
1750
+ };
1751
+ const assertNoAmbientRuntimeOverrides = (): void => {
1752
+ if (
1753
+ process.env.BUN_CONFIG_FILE !== undefined
1754
+ || process.env.BUN_OPTIONS !== undefined
1755
+ ) {
1756
+ throw new Error(
1757
+ `provider plugin ${plugin.id} refuses ambient Bun configuration overrides`,
1758
+ );
1759
+ }
1760
+ const semanticOption =
1761
+ /^(?:(?:--config|--conditions|--cwd|--define|--drop|--extension-order|--feature|--hot|--ignore-dce-annotations|--import|--install|--jsx-factory|--jsx-fragment|--jsx-import-source|--jsx-runtime|--jsx-side-effects|--loader|--main-fields|--no-addons|--no-macros|--prefer-latest|--preload|--preserve-symlinks|--preserve-symlinks-main|--require|--tsconfig-override|--watch)(?:=|$)|-[cdilr](?:=|.|$))/u;
1762
+ if (process.execArgv.some((argument) => semanticOption.test(argument))) {
1763
+ throw new Error(
1764
+ `provider plugin ${plugin.id} refuses Bun loader, define, preload, condition, or tsconfig CLI overrides`,
1765
+ );
1766
+ }
1767
+ if (
1768
+ process.env.NODE_OPTIONS !== undefined
1769
+ && /(?:--conditions|--experimental-loader|--import|--loader|--require|-r)(?:=|\s|$)/u
1770
+ .test(process.env.NODE_OPTIONS)
1771
+ ) {
1772
+ throw new Error(
1773
+ `provider plugin ${plugin.id} refuses semantic NODE_OPTIONS overrides`,
1774
+ );
1775
+ }
1776
+ };
1777
+ const bindProviderPluginExecutionConfig = (): void => {
1778
+ let invocationRoot: string;
1779
+ try {
1780
+ invocationRoot = realpathSync(process.cwd());
1781
+ } catch {
1782
+ throw new Error(
1783
+ `provider plugin ${plugin.id} cannot resolve its invocation root`,
1784
+ );
1785
+ }
1786
+ const standalonePackage = repositoryRoot === providerPluginPackageRoot;
1787
+ if (
1788
+ !standalonePackage
1789
+ && invocationRoot !== repositoryRoot
1790
+ && invocationRoot !== providerPluginPackageRoot
1791
+ ) {
1792
+ throw new Error(
1793
+ `provider plugin ${plugin.id} must start from the checked Ghostget package or repository root`,
1794
+ );
1795
+ }
1796
+ assertNoAmbientRuntimeOverrides();
1797
+ addRecord("execution-runtime/bun-version", Bun.version);
1798
+ addRecord(
1799
+ "execution-runtime/node-env",
1800
+ process.env.NODE_ENV ?? "",
1801
+ );
1802
+ bindPackageExecutionConfig(
1803
+ "repository-package.json",
1804
+ repositoryPackagePath,
1805
+ );
1806
+ if (packageManifestPath !== repositoryPackagePath) {
1807
+ bindPackageExecutionConfig(
1808
+ "wrench-package.json",
1809
+ packageManifestPath,
1810
+ );
1811
+ }
1812
+ const bunfig = bindExecutionConfigFile(
1813
+ "repository-bunfig.toml",
1814
+ repositoryBunfigPath,
1815
+ );
1816
+ requireSafeBunfig(bunfig, "repository bunfig.toml");
1817
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME;
1818
+ const globalBunfigPath =
1819
+ xdgConfigHome === undefined || xdgConfigHome === ""
1820
+ ? resolve(homedir(), ".bunfig.toml")
1821
+ : resolve(xdgConfigHome, ".bunfig.toml");
1822
+ if (existsSync(globalBunfigPath)) {
1823
+ const bytes = readStableDependencyFile(
1824
+ globalBunfigPath,
1825
+ MAX_PROVIDER_DEPENDENCY_FILE_BYTES,
1826
+ `provider plugin ${plugin.id} global bunfig`,
1827
+ );
1828
+ requireSafeBunfig(bytes, "global bunfig.toml");
1829
+ }
1830
+ bindTsconfigClosure(repositoryTsconfigPath);
1831
+ if (packageTsconfigPath !== repositoryTsconfigPath) {
1832
+ bindTsconfigClosure(packageTsconfigPath);
1833
+ }
1834
+ };
1835
+ const bindRepositoryPackageScope = (path: string): void => {
1836
+ if (extname(path) !== ".js") return;
1837
+ let directory = dirname(path);
1838
+ for (;;) {
1839
+ const manifestPath = resolve(directory, "package.json");
1840
+ if (existsSync(manifestPath)) {
1841
+ const canonical = realpathSync(manifestPath);
1842
+ if (!isWithin(repositoryRoot, canonical)) {
1843
+ throw new Error(
1844
+ `provider plugin ${plugin.id} JavaScript package scope escapes the repository`,
1845
+ );
1846
+ }
1847
+ bindPackageExecutionConfig(
1848
+ `javascript-package-scope/${relative(repositoryRoot, canonical)
1849
+ .split(sep).join("/")}`,
1850
+ canonical,
1851
+ );
1852
+ return;
1853
+ }
1854
+ if (directory === repositoryRoot) break;
1855
+ const parent = dirname(directory);
1856
+ if (parent === directory || !isWithin(repositoryRoot, parent)) break;
1857
+ directory = parent;
1858
+ }
1859
+ throw new Error(
1860
+ `provider plugin ${plugin.id} JavaScript module has no governing package.json`,
1861
+ );
1862
+ };
1863
+ bindProviderPluginExecutionConfig();
1864
+ type PendingRepositoryDependency = {
1865
+ readonly discoveredBy: string;
1866
+ readonly path: string;
1867
+ readonly logicalLabel: string;
1868
+ readonly packageDepth: number;
1869
+ };
1870
+ type InstalledPackageOccurrence = {
1871
+ readonly nodeId: string;
1872
+ readonly nodeIdSortKey: Buffer;
1873
+ readonly snapshot: InstalledPackageSnapshot;
1874
+ };
1875
+ type PendingInstalledModule = {
1876
+ readonly occurrence: InstalledPackageOccurrence;
1877
+ readonly path: string;
1878
+ readonly pathSortKey: Buffer;
1879
+ readonly packageDepth: number;
1880
+ };
1881
+ const pending: PendingRepositoryDependency[] = [];
1882
+ const pendingInstalledModules: PendingInstalledModule[] = [];
1883
+ const visitedRepositorySources = new Set<string>();
1884
+ const installedPackageFiles =
1885
+ new Map<string, ReadonlyMap<string, InstalledPackageFileSnapshot>>();
1886
+ const installedOccurrences = new Map<string, InstalledPackageOccurrence>();
1887
+ const reviewedKbDynamicInstalledModuleIdentities = new Map<string, string>();
1888
+ const usedInstalledPackageRoots = new Set<string>();
1889
+ const visitedInstalledModules = new Set<string>();
1890
+ let installedFiles = 0;
1891
+ let installedDirectories = 0;
1892
+ let installedBytes = 0;
1893
+ let repositoryBytes = 0;
1894
+ const implementationLabelsByPath = new Map(
1895
+ plugin.implementationSources.map((source) => [source.path, source.label]),
1896
+ );
1897
+
1898
+ const packageSnapshot = (
1899
+ entryPath: string,
1900
+ expectedPackageName: string,
1901
+ ): InstalledPackageSnapshot => {
1902
+ const root = installedPackageRoot(entryPath);
1903
+ let snapshot = installedPackageSnapshots.get(root);
1904
+ if (snapshot === undefined) {
1905
+ snapshot = snapshotInstalledPackage(
1906
+ entryPath,
1907
+ expectedPackageName,
1908
+ snapshotDependencySource,
1909
+ );
1910
+ installedPackageSnapshots.set(root, snapshot);
1911
+ } else if (snapshot.installName !== expectedPackageName) {
1912
+ throw new Error(
1913
+ `provider plugin ${plugin.id} dependency expected install key ${expectedPackageName} but reused ${snapshot.installName}`,
1914
+ );
1915
+ }
1916
+ usedInstalledPackageRoots.add(snapshot.root);
1917
+ return snapshot;
1918
+ };
1919
+
1920
+ const packageFile = (
1921
+ snapshot: InstalledPackageSnapshot,
1922
+ path: string,
1923
+ ): InstalledPackageFileSnapshot | undefined => {
1924
+ let files = installedPackageFiles.get(snapshot.root);
1925
+ if (files === undefined) {
1926
+ files = new Map(snapshot.files.map((file) => [file.path, file]));
1927
+ installedPackageFiles.set(snapshot.root, files);
1928
+ }
1929
+ return files.get(path);
1930
+ };
1931
+
1932
+ const scheduleInstalledModule = (
1933
+ occurrence: InstalledPackageOccurrence,
1934
+ entryRelativePath: string,
1935
+ packageDepth: number,
1936
+ ): void => {
1937
+ const extension = extname(entryRelativePath);
1938
+ if (
1939
+ extension !== ".json"
1940
+ && !providerPluginModuleExtensions.has(extension)
1941
+ ) {
1942
+ throw new Error(
1943
+ `provider plugin ${plugin.id} installed package ${occurrence.snapshot.id} resolves to unsupported executable ${entryRelativePath}`,
1944
+ );
1945
+ }
1946
+ if (providerPluginModuleExtensions.has(extension)) {
1947
+ pendingInstalledModules.push({
1948
+ occurrence,
1949
+ path: entryRelativePath,
1950
+ pathSortKey: Buffer.from(entryRelativePath, "utf8"),
1951
+ packageDepth,
1952
+ });
1953
+ }
1954
+ };
1955
+
1956
+ const resolveInstalledLocalImport = (
1957
+ snapshot: InstalledPackageSnapshot,
1958
+ importerRelativePath: string,
1959
+ specifier: string,
1960
+ importKind: string,
1961
+ ): InstalledPackageFileSnapshot | undefined => {
1962
+ const importerPath = resolve(snapshot.root, importerRelativePath);
1963
+ const resolvedPath = resolveBareDependencyIfPresent(
1964
+ importerPath,
1965
+ specifier,
1966
+ importKind,
1967
+ );
1968
+ if (resolvedPath === undefined) return undefined;
1969
+ if (!isWithin(snapshot.root, resolvedPath)) {
1970
+ throw new Error(
1971
+ `provider plugin ${plugin.id} installed package ${snapshot.id} import ${specifier} escapes its package root`,
1972
+ );
1973
+ }
1974
+ const relativePath = relative(snapshot.root, resolvedPath)
1975
+ .split(sep).join("/");
1976
+ return packageFile(snapshot, relativePath);
1977
+ };
1978
+
1979
+ const inferredInstalledPackageName = (
1980
+ entryPath: string,
1981
+ ): string | undefined => {
1982
+ const physicalPath = classifyProviderPluginPhysicalPath(entryPath);
1983
+ if (physicalPath.kind !== "installed-package") return undefined;
1984
+ const name = basename(physicalPath.root);
1985
+ const parent = basename(dirname(physicalPath.root));
1986
+ return parent.startsWith("@") ? `${parent}/${name}` : name;
1987
+ };
1988
+
1989
+ const bindResolvedTarget = (
1990
+ importerLabel: string,
1991
+ specifier: string,
1992
+ path: string,
1993
+ expectedPackageName: string | undefined,
1994
+ packageDepth: number,
1995
+ importKind: string,
1996
+ ): void => {
1997
+ const physicalPath = classifyProviderPluginPhysicalPath(path);
1998
+ if (
1999
+ path === providerPluginsApiPath
2000
+ ) {
2001
+ throw new Error(
2002
+ `provider plugin ${plugin.id} imports the private process-wide provider catalog`,
2003
+ );
2004
+ }
2005
+ if (providerPluginAbsoluteImportTargets.has(path)) {
2006
+ addResolutionEdge(
2007
+ importerLabel,
2008
+ specifier,
2009
+ `kernel/provider-plugin-api@${PROVIDER_PLUGIN_API_VERSION}`,
2010
+ importKind,
2011
+ );
2012
+ return;
2013
+ }
2014
+ if (physicalPath.kind === "repository") {
2015
+ const extension = extname(path);
2016
+ if (
2017
+ extension !== ".json"
2018
+ && extension !== ".toml"
2019
+ && !providerPluginModuleExtensions.has(extension)
2020
+ ) {
2021
+ throw new Error(
2022
+ `provider plugin ${plugin.id} repository dependency ${specifier} resolves to unsupported module extension ${extension || "<none>"}`,
2023
+ );
2024
+ }
2025
+ const relativePath = relative(repositoryRoot, path).split(sep).join("/");
2026
+ addResolutionEdge(
2027
+ importerLabel,
2028
+ specifier,
2029
+ `repository/${relativePath}`,
2030
+ importKind,
2031
+ );
2032
+ pending.push({
2033
+ discoveredBy: importerLabel,
2034
+ path,
2035
+ logicalLabel: `repository/${relativePath}`,
2036
+ packageDepth,
2037
+ });
2038
+ return;
2039
+ }
2040
+ if (packageDepth > MAX_INSTALLED_PACKAGE_GRAPH_DEPTH) {
2041
+ throw new Error(
2042
+ `provider plugin ${plugin.id} installed package graph exceeds its depth bound`,
2043
+ );
2044
+ }
2045
+ const packageName =
2046
+ inferredInstalledPackageName(path) ?? expectedPackageName;
2047
+ if (packageName === undefined) {
2048
+ throw new Error(
2049
+ `provider plugin ${plugin.id} dependency ${specifier} has no exact installed package owner`,
2050
+ );
2051
+ }
2052
+ const snapshot = packageSnapshot(path, packageName);
2053
+ const entryRelativePath = relative(snapshot.root, path)
2054
+ .split(sep).join("/");
2055
+ if (
2056
+ entryRelativePath === ""
2057
+ || entryRelativePath === ".."
2058
+ || entryRelativePath.startsWith("../")
2059
+ ) {
2060
+ throw new Error(
2061
+ `provider plugin ${plugin.id} dependency entry escapes ${snapshot.id}`,
2062
+ );
2063
+ }
2064
+ if (packageFile(snapshot, entryRelativePath) === undefined) {
2065
+ throw new Error(
2066
+ `provider plugin ${plugin.id} dependency entry ${entryRelativePath} is absent from captured package ${snapshot.id}`,
2067
+ );
2068
+ }
2069
+ let occurrence = installedOccurrences.get(snapshot.root);
2070
+ if (occurrence === undefined) {
2071
+ if (installedOccurrences.size >= MAX_INSTALLED_PACKAGES) {
2072
+ throw new Error(
2073
+ `provider plugin ${plugin.id} installed package closure is too large`,
2074
+ );
2075
+ }
2076
+ const discoveryCoordinate =
2077
+ JSON.stringify([importerLabel, specifier, importKind]);
2078
+ const nodeId = createHash("sha256")
2079
+ .update("provider-plugin-installed-package-node@1\0")
2080
+ .update(discoveryCoordinate)
2081
+ .digest("base64url");
2082
+ occurrence = Object.freeze({
2083
+ nodeId,
2084
+ nodeIdSortKey: Buffer.from(nodeId, "utf8"),
2085
+ snapshot,
2086
+ });
2087
+ installedOccurrences.set(snapshot.root, occurrence);
2088
+ installedFiles += snapshot.files.length + snapshot.links.length;
2089
+ installedDirectories += snapshot.verificationWalk.directoryCount;
2090
+ installedBytes += snapshot.totalBytes;
2091
+ if (
2092
+ installedFiles > MAX_INSTALLED_CLOSURE_FILES
2093
+ || installedDirectories > MAX_INSTALLED_CLOSURE_DIRECTORIES
2094
+ ) {
2095
+ throw new Error(
2096
+ `provider plugin ${plugin.id} installed package closure exceeds its entry bound`,
2097
+ );
2098
+ }
2099
+ if (installedBytes > MAX_INSTALLED_PACKAGE_TOTAL_BYTES) {
2100
+ throw new Error(
2101
+ `provider plugin ${plugin.id} installed package closure exceeds its byte bound`,
2102
+ );
2103
+ }
2104
+ addRecord(
2105
+ `dependency-package-node/${nodeId}`,
2106
+ JSON.stringify({
2107
+ installName: snapshot.installName,
2108
+ name: snapshot.name,
2109
+ treeSha256: snapshot.treeSha256,
2110
+ version: snapshot.version,
2111
+ }),
2112
+ );
2113
+ }
2114
+ addResolutionEdge(
2115
+ importerLabel,
2116
+ specifier,
2117
+ `package-node/${occurrence.nodeId}/${entryRelativePath}`,
2118
+ importKind,
2119
+ );
2120
+ scheduleInstalledModule(occurrence, entryRelativePath, packageDepth);
2121
+ // The package tree already binds its manifest, including every declared
2122
+ // dependency. Traverse only modules that its executable static imports
2123
+ // can reach. This keeps dormant optional/tooling graphs out of the runtime
2124
+ // identity while preserving exact resolution edges for code that can run.
2125
+ };
2126
+
2127
+ for (
2128
+ const source of [...plugin.implementationSources]
2129
+ .sort((left, right) => compareIdentityText(left.label, right.label))
2130
+ ) {
2131
+ bindRepositoryPackageScope(source.path);
2132
+ if (!providerPluginModuleExtensions.has(extname(source.path))) continue;
2133
+ const sourceBytes = implementationSourceBytes(
2134
+ plugin,
2135
+ source,
2136
+ implementationSources,
2137
+ );
2138
+ const analysis = analyzeProviderModule(
2139
+ sourceBytes,
2140
+ source.path,
2141
+ moduleAnalyses,
2142
+ );
2143
+ for (
2144
+ const dependency of [...analysis.imports]
2145
+ .sort((left, right) => compareIdentityText(left.path, right.path))
2146
+ ) {
2147
+ const specifier = dependency.path;
2148
+ if (isRuntimeBuiltin(specifier)) continue;
2149
+ if (specifier.startsWith(".")) {
2150
+ const child = resolveLocalImport(
2151
+ source.path,
2152
+ specifier,
2153
+ dependency.kind,
2154
+ );
2155
+ if (child === undefined) {
2156
+ throw new Error(
2157
+ `provider plugin ${plugin.id} implementation source ${source.label} imports unresolved local value dependency ${specifier}`,
2158
+ );
2159
+ }
2160
+ const declaredLabel = implementationLabelsByPath.get(child);
2161
+ if (declaredLabel !== undefined) {
2162
+ addResolutionEdge(
2163
+ `implementation/${source.label}`,
2164
+ specifier,
2165
+ `implementation/${declaredLabel}`,
2166
+ dependency.kind,
2167
+ );
2168
+ continue;
2169
+ }
2170
+ bindResolvedTarget(
2171
+ `implementation/${source.label}`,
2172
+ specifier,
2173
+ child,
2174
+ undefined,
2175
+ 0,
2176
+ dependency.kind,
2177
+ );
2178
+ continue;
2179
+ }
2180
+ const path = resolvedBareDependency(
2181
+ source.path,
2182
+ specifier,
2183
+ dependency.kind,
2184
+ );
2185
+ bindResolvedTarget(
2186
+ `implementation/${source.label}`,
2187
+ specifier,
2188
+ path,
2189
+ barePackageName(specifier),
2190
+ 0,
2191
+ dependency.kind,
2192
+ );
2193
+ }
2194
+ }
2195
+ for (;;) {
2196
+ while (pending.length > 0) {
2197
+ pending.sort((left, right) =>
2198
+ compareIdentityText(left.logicalLabel, right.logicalLabel)
2199
+ || compareIdentityText(left.path, right.path));
2200
+ const dependencySource = pending.pop();
2201
+ if (
2202
+ dependencySource === undefined
2203
+ || visitedRepositorySources.has(dependencySource.path)
2204
+ ) continue;
2205
+ if (visitedRepositorySources.size >= 2_000) {
2206
+ throw new Error(
2207
+ `provider plugin ${plugin.id} package dependency closure is too large`,
2208
+ );
2209
+ }
2210
+ visitedRepositorySources.add(dependencySource.path);
2211
+ bindRepositoryPackageScope(dependencySource.path);
2212
+ const relativePath = relative(repositoryRoot, dependencySource.path)
2213
+ .split(sep).join("/");
2214
+ const bytes =
2215
+ implementationSources.get(dependencySource.path)
2216
+ ?? snapshotDependencySource(dependencySource.path);
2217
+ repositoryBytes += bytes.byteLength;
2218
+ if (repositoryBytes > MAX_INSTALLED_PACKAGE_TOTAL_BYTES) {
2219
+ throw new Error(
2220
+ `provider plugin ${plugin.id} repository dependency closure exceeds its byte bound`,
2221
+ );
2222
+ }
2223
+ addRecord(`dependency-source/${relativePath}`, bytes);
2224
+ if (
2225
+ !providerPluginModuleExtensions.has(extname(dependencySource.path))
2226
+ ) continue;
2227
+ const analysis = analyzeProviderModule(
2228
+ bytes,
2229
+ dependencySource.path,
2230
+ moduleAnalyses,
2231
+ );
2232
+ if (analysis.nonLiteralModuleLoad) {
2233
+ const sourceSha256 = createHash("sha256").update(bytes).digest("hex");
2234
+ const reviewedDynamicModule =
2235
+ `${plugin.id}\0${relativePath}\0${sourceSha256}`;
2236
+ if (
2237
+ plugin.sourceKind !== "built-in"
2238
+ || !reviewedBuiltInDynamicRepositoryModules.has(reviewedDynamicModule)
2239
+ ) {
2240
+ throw new Error(
2241
+ `provider plugin ${plugin.id} package dependency ${relativePath} discovered by ${dependencySource.discoveredBy} contains a non-literal module load`,
2242
+ );
2243
+ }
2244
+ addRecord(
2245
+ `dependency-repository-dynamic-load-policy/${relativePath}`,
2246
+ `${reviewedDynamicModule}\0${reviewedKbDynamicResolutionPolicy}`,
2247
+ );
2248
+ }
2249
+ for (
2250
+ const dependency of [...analysis.imports]
2251
+ .sort((left, right) => compareIdentityText(left.path, right.path))
2252
+ ) {
2253
+ if (isRuntimeBuiltin(dependency.path)) continue;
2254
+ const child = dependency.path.startsWith(".")
2255
+ ? resolveLocalImport(
2256
+ dependencySource.path,
2257
+ dependency.path,
2258
+ dependency.kind,
2259
+ )
2260
+ : resolvedBareDependency(
2261
+ dependencySource.path,
2262
+ dependency.path,
2263
+ dependency.kind,
2264
+ );
2265
+ if (child === undefined) {
2266
+ throw new Error(
2267
+ `provider plugin ${plugin.id} package dependency ${relativePath} imports unresolved local value dependency ${dependency.path}`,
2268
+ );
2269
+ }
2270
+ bindResolvedTarget(
2271
+ dependencySource.logicalLabel,
2272
+ dependency.path,
2273
+ child,
2274
+ barePackageName(dependency.path),
2275
+ dependencySource.packageDepth,
2276
+ dependency.kind,
2277
+ );
2278
+ }
2279
+ }
2280
+ while (pendingInstalledModules.length > 0) {
2281
+ // Private UTF-8 keys preserve byte ordering without per-comparison encoding.
2282
+ pendingInstalledModules.sort((left, right) =>
2283
+ Buffer.compare(left.occurrence.nodeIdSortKey, right.occurrence.nodeIdSortKey)
2284
+ || Buffer.compare(left.pathSortKey, right.pathSortKey));
2285
+ const pendingModule = pendingInstalledModules.pop();
2286
+ if (pendingModule === undefined) continue;
2287
+ const moduleKey =
2288
+ `${pendingModule.occurrence.nodeId}\0${pendingModule.path}`;
2289
+ if (visitedInstalledModules.has(moduleKey)) continue;
2290
+ if (
2291
+ visitedInstalledModules.size >= MAX_INSTALLED_EXECUTABLE_MODULES
2292
+ ) {
2293
+ throw new Error(
2294
+ `provider plugin ${plugin.id} installed executable closure is too large`,
2295
+ );
2296
+ }
2297
+ visitedInstalledModules.add(moduleKey);
2298
+ const file = packageFile(
2299
+ pendingModule.occurrence.snapshot,
2300
+ pendingModule.path,
2301
+ );
2302
+ if (file === undefined) {
2303
+ throw new Error(
2304
+ `provider plugin ${plugin.id} installed executable ${pendingModule.path} disappeared from its captured tree`,
2305
+ );
2306
+ }
2307
+ const moduleSha256 = createHash("sha256")
2308
+ .update(file.bytes)
2309
+ .digest("hex");
2310
+ const dynamicModuleIdentity =
2311
+ `${pendingModule.occurrence.snapshot.id}\0${pendingModule.path}\0${moduleSha256}`;
2312
+ let reviewedKbDynamicInstalledModuleIdentity: string | undefined;
2313
+ if (
2314
+ pendingModule.occurrence.snapshot.name
2315
+ === reviewedKbDynamicInstalledPackage.name
2316
+ ) {
2317
+ reviewedKbDynamicInstalledModuleIdentity =
2318
+ reviewedKbDynamicInstalledModuleIdentities.get(
2319
+ pendingModule.occurrence.snapshot.root,
2320
+ );
2321
+ if (reviewedKbDynamicInstalledModuleIdentity === undefined) {
2322
+ reviewedKbDynamicInstalledModuleIdentity =
2323
+ discoverReviewedKbDynamicInstalledModuleIdentity(
2324
+ pendingModule.occurrence.snapshot,
2325
+ );
2326
+ reviewedKbDynamicInstalledModuleIdentities.set(
2327
+ pendingModule.occurrence.snapshot.root,
2328
+ reviewedKbDynamicInstalledModuleIdentity,
2329
+ );
2330
+ }
2331
+ }
2332
+ const analysis = analyzeProviderModule(
2333
+ file.bytes,
2334
+ pendingModule.path,
2335
+ moduleAnalyses,
2336
+ {
2337
+ contentSha256: moduleSha256,
2338
+ reviewedLargeDynamicModule:
2339
+ reviewedDynamicInstalledModuleIdentities.includes(
2340
+ dynamicModuleIdentity,
2341
+ ),
2342
+ },
2343
+ );
2344
+ if (analysis.nonLiteralModuleLoad) {
2345
+ const reviewedDynamicModule =
2346
+ `${plugin.id}\0${dynamicModuleIdentity}`;
2347
+ const reviewedKbDynamicModule =
2348
+ dynamicModuleIdentity === reviewedKbDynamicInstalledModuleIdentity
2349
+ && reviewedKbDynamicInstalledPluginIds.has(plugin.id);
2350
+ if (
2351
+ plugin.sourceKind !== "built-in"
2352
+ || (
2353
+ !reviewedBuiltInDynamicInstalledModules.has(reviewedDynamicModule)
2354
+ && !reviewedKbDynamicModule
2355
+ )
2356
+ ) {
2357
+ throw new Error(
2358
+ `provider plugin ${plugin.id} installed executable ${pendingModule.path} contains a non-literal module load`,
2359
+ );
2360
+ }
2361
+ // KB executes one exact, reviewed package-manifest resolution from the
2362
+ // literal agent-browser call site. The Meta parser dependencies only
2363
+ // expose dormant dynamic loader APIs. Bind either narrow policy and the
2364
+ // exact module identity into the closure. A new call site or changed
2365
+ // module must receive a new byte-level review.
2366
+ addRecord(
2367
+ `dependency-package-dynamic-load-policy/${pendingModule.occurrence.nodeId}/${pendingModule.path}`,
2368
+ `${reviewedDynamicModule}\0${
2369
+ reviewedKbDynamicModule
2370
+ ? reviewedKbDynamicResolutionPolicy
2371
+ : reviewedDormantDynamicLoaderPolicy
2372
+ }`,
2373
+ );
2374
+ }
2375
+ const importerLabel =
2376
+ `package-node/${pendingModule.occurrence.nodeId}/module/${pendingModule.path}`;
2377
+ for (
2378
+ const dependency of [...analysis.imports]
2379
+ .sort((left, right) => compareIdentityText(left.path, right.path))
2380
+ ) {
2381
+ const specifier = dependency.path;
2382
+ if (isRuntimeBuiltin(specifier)) continue;
2383
+ if (specifier.startsWith(".")) {
2384
+ const child = resolveInstalledLocalImport(
2385
+ pendingModule.occurrence.snapshot,
2386
+ pendingModule.path,
2387
+ specifier,
2388
+ dependency.kind,
2389
+ );
2390
+ if (child === undefined) {
2391
+ throw new Error(
2392
+ `provider plugin ${plugin.id} installed executable ${pendingModule.path} imports unresolved local dependency ${specifier}`,
2393
+ );
2394
+ }
2395
+ addResolutionEdge(
2396
+ importerLabel,
2397
+ specifier,
2398
+ `package-node/${pendingModule.occurrence.nodeId}/${child.path}`,
2399
+ dependency.kind,
2400
+ );
2401
+ scheduleInstalledModule(
2402
+ pendingModule.occurrence,
2403
+ child.path,
2404
+ pendingModule.packageDepth,
2405
+ );
2406
+ continue;
2407
+ }
2408
+ if (
2409
+ specifier.startsWith("/")
2410
+ || /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(specifier)
2411
+ ) {
2412
+ throw new Error(
2413
+ `provider plugin ${plugin.id} installed executable ${pendingModule.path} imports unsupported absolute or URL dependency ${specifier}`,
2414
+ );
2415
+ }
2416
+ const importerPath = resolve(
2417
+ pendingModule.occurrence.snapshot.root,
2418
+ pendingModule.path,
2419
+ );
2420
+ const dependencyPath = resolveBareDependencyIfPresent(
2421
+ importerPath,
2422
+ specifier,
2423
+ dependency.kind,
2424
+ );
2425
+ if (dependencyPath === undefined) {
2426
+ if (
2427
+ dependency.kind === "import-statement"
2428
+ || dependency.kind === "export-from"
2429
+ ) {
2430
+ throw new Error(
2431
+ `provider plugin ${plugin.id} installed executable ${pendingModule.path} imports unresolved static dependency ${specifier}`,
2432
+ );
2433
+ }
2434
+ addResolutionEdge(
2435
+ importerLabel,
2436
+ specifier,
2437
+ "absent",
2438
+ dependency.kind,
2439
+ );
2440
+ continue;
2441
+ }
2442
+ if (
2443
+ specifier.startsWith("#")
2444
+ && isWithin(
2445
+ pendingModule.occurrence.snapshot.root,
2446
+ dependencyPath,
2447
+ )
2448
+ ) {
2449
+ const relativePath = relative(
2450
+ pendingModule.occurrence.snapshot.root,
2451
+ dependencyPath,
2452
+ ).split(sep).join("/");
2453
+ const child = packageFile(
2454
+ pendingModule.occurrence.snapshot,
2455
+ relativePath,
2456
+ );
2457
+ if (child === undefined) {
2458
+ throw new Error(
2459
+ `provider plugin ${plugin.id} installed package import ${specifier} is absent from its captured tree`,
2460
+ );
2461
+ }
2462
+ addResolutionEdge(
2463
+ importerLabel,
2464
+ specifier,
2465
+ `package-node/${pendingModule.occurrence.nodeId}/${child.path}`,
2466
+ dependency.kind,
2467
+ );
2468
+ scheduleInstalledModule(
2469
+ pendingModule.occurrence,
2470
+ child.path,
2471
+ pendingModule.packageDepth,
2472
+ );
2473
+ continue;
2474
+ }
2475
+ bindResolvedTarget(
2476
+ importerLabel,
2477
+ specifier,
2478
+ dependencyPath,
2479
+ barePackageName(specifier),
2480
+ pendingModule.packageDepth + 1,
2481
+ dependency.kind,
2482
+ );
2483
+ }
2484
+ }
2485
+ if (pending.length === 0 && pendingInstalledModules.length === 0) break;
2486
+ }
2487
+ for (const root of usedInstalledPackageRoots) {
2488
+ const snapshot = installedPackageSnapshots.get(root);
2489
+ if (
2490
+ snapshot === undefined
2491
+ || !sameInstalledPackageTree(
2492
+ snapshot.verificationWalk,
2493
+ walkInstalledPackageTree(root),
2494
+ )
2495
+ ) {
2496
+ throw new Error(
2497
+ `provider plugin ${plugin.id} installed package closure changed while its identity was built`,
2498
+ );
2499
+ }
2500
+ }
2501
+ return Object.freeze(
2502
+ [...records.entries()]
2503
+ .sort(([left], [right]) => compareIdentityText(left, right))
2504
+ .map(([label, bytes]) => Object.freeze({ label, bytes })),
2505
+ );
2506
+ }
2507
+
2508
+ /**
2509
+ * Require every implementation root to have a statically discoverable,
2510
+ * repository-contained module graph. The identity builder recursively binds
2511
+ * undeclared local dependencies while treating the provider API as a kernel
2512
+ * boundary.
2513
+ */
2514
+ export function assertProviderPluginImplementationSourceClosure(
2515
+ plugin: ProviderPluginV1,
2516
+ snapshots: ProviderPluginImplementationSourceSnapshot =
2517
+ snapshotProviderPluginImplementationSources(
2518
+ plugin,
2519
+ readImplementationSource,
2520
+ new Map(),
2521
+ ),
2522
+ moduleAnalyses: ProviderModuleAnalysisCache = {
2523
+ values: new Map(),
2524
+ imports: 0,
2525
+ },
2526
+ ): void {
2527
+ const pluginEntry = plugin.implementationSources.find(
2528
+ (source) => source.label === "plugin.ts",
2529
+ );
2530
+ if (pluginEntry === undefined) {
2531
+ throw new Error(`provider plugin ${plugin.id} has no plugin.ts implementation source`);
2532
+ }
2533
+ for (const source of plugin.implementationSources) {
2534
+ if (!providerPluginModuleExtensions.has(extname(source.path))) continue;
2535
+ const sourceBytes = implementationSourceBytes(plugin, source, snapshots);
2536
+ const analysis = analyzeProviderModule(
2537
+ sourceBytes,
2538
+ source.path,
2539
+ moduleAnalyses,
2540
+ );
2541
+ if (analysis.nonLiteralModuleLoad) {
2542
+ throw new Error(
2543
+ `provider plugin ${plugin.id} implementation source ${source.label} contains a non-literal module load`,
2544
+ );
2545
+ }
2546
+ for (const dependency of analysis.imports) {
2547
+ const specifier = dependency.path;
2548
+ if (!specifier.startsWith(".")) {
2549
+ if (isRuntimeBuiltin(specifier)) continue;
2550
+ if (
2551
+ specifier.startsWith("/")
2552
+ || /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(specifier)
2553
+ ) {
2554
+ throw new Error(
2555
+ `provider plugin ${plugin.id} implementation source ${source.label} imports unsupported absolute or URL dependency ${specifier}`,
2556
+ );
2557
+ }
2558
+ // Resolve now so a missing package or a package outside the checked
2559
+ // repository cannot hide behind a lazy runtime import.
2560
+ resolvedBareDependency(source.path, specifier, dependency.kind);
2561
+ continue;
2562
+ }
2563
+ const candidate = resolveLocalImport(
2564
+ source.path,
2565
+ specifier,
2566
+ dependency.kind,
2567
+ );
2568
+ if (candidate === undefined) {
2569
+ throw new Error(
2570
+ `provider plugin ${plugin.id} implementation source ${source.label} imports unresolved local value dependency ${specifier}`,
2571
+ );
2572
+ }
2573
+ if (!isWithin(repositoryRoot, candidate)) {
2574
+ throw new Error(
2575
+ `provider plugin ${plugin.id} implementation source ${source.label} imports local dependency outside the repository`,
2576
+ );
2577
+ }
2578
+ }
2579
+ }
2580
+ }
2581
+
2582
+ function createProviderPluginRegistryInternal(
2583
+ definitions: readonly (ProviderPluginDefinitionV1 | ProviderPluginV1)[],
2584
+ dependencies: ProviderPluginRegistryDependencies,
2585
+ allowPortableProjections: boolean,
2586
+ ): ProviderPluginRegistry {
2587
+ if (definitions.length > MAX_REGISTRY_SOURCE_PLUGINS) {
2588
+ throw new Error("provider plugin registry exceeds its plugin bound");
2589
+ }
2590
+ const plugins = definitions.map((definition) => {
2591
+ const firstBinding = definition.bindings[0];
2592
+ if (firstBinding === undefined) {
2593
+ throw new Error(`provider plugin ${definition.id} must own at least one binding`);
2594
+ }
2595
+ if ("runtime" in firstBinding) {
2596
+ return defineProviderPlugin(definition as ProviderPluginDefinitionV1);
2597
+ }
2598
+ if (!isValidatedProviderPlugin(definition)) {
2599
+ throw new Error(`provider plugin ${definition.id} is not a validated definition`);
2600
+ }
2601
+ if (
2602
+ definition.sourceKind === "portable"
2603
+ && !allowPortableProjections
2604
+ ) {
2605
+ throw new Error(
2606
+ `portable provider plugin ${definition.id} requires the kernel-owned catalog extension boundary`,
2607
+ );
2608
+ }
2609
+ return definition;
2610
+ });
2611
+ plugins.sort((left, right) => compareIdentityText(left.id, right.id));
2612
+ for (let index = 1; index < plugins.length; index += 1) {
2613
+ if (plugins[index - 1]?.id === plugins[index]?.id) {
2614
+ throw new Error(`duplicate provider plugin ID: ${plugins[index]?.id}`);
2615
+ }
2616
+ }
2617
+ let registryRouteCount = 0;
2618
+ let registryOperationContractCount = 0;
2619
+ for (const plugin of plugins) {
2620
+ if (plugin.bindings.length > MAX_PROVIDER_PLUGIN_BINDINGS) {
2621
+ throw new Error(
2622
+ `provider plugin ${plugin.id} exceeds its binding bound`,
2623
+ );
2624
+ }
2625
+ let pluginOperationCount = 0;
2626
+ for (const binding of plugin.bindings) {
2627
+ if (
2628
+ binding.operations.length
2629
+ > MAX_PROVIDER_PLUGIN_OPERATIONS_PER_BINDING
2630
+ ) {
2631
+ throw new Error(
2632
+ `provider plugin ${plugin.id} surface ${binding.surfaceId} exceeds its operation bound`,
2633
+ );
2634
+ }
2635
+ pluginOperationCount += binding.operations.length;
2636
+ for (const operation of binding.operations) {
2637
+ registryOperationContractCount += operation.contractVersions.length;
2638
+ if (
2639
+ registryOperationContractCount
2640
+ > MAX_PROVIDER_PLUGIN_REGISTRY_OPERATION_CONTRACTS
2641
+ ) {
2642
+ throw new Error(
2643
+ "provider plugin registry exceeds its exact operation contract bound",
2644
+ );
2645
+ }
2646
+ }
2647
+ }
2648
+ if (pluginOperationCount > MAX_PROVIDER_PLUGIN_OPERATIONS) {
2649
+ throw new Error(
2650
+ `provider plugin ${plugin.id} exceeds its aggregate operation bound`,
2651
+ );
2652
+ }
2653
+ registryRouteCount += plugin.bindings.length;
2654
+ if (registryRouteCount > MAX_PROVIDER_PLUGIN_REGISTRY_ROUTES) {
2655
+ throw new Error("provider plugin registry exceeds its route bound");
2656
+ }
2657
+ }
2658
+
2659
+ const pluginsById = new Map(plugins.map((plugin) => [plugin.id, plugin]));
2660
+ const routes = new Map<string, ProviderPluginBindingV1>();
2661
+ const sessionRoutes = new Map<string, ProviderPluginSessionBindingV1>();
2662
+ const accountRoutes = new Map<string, ProviderPluginBindingV1>();
2663
+ const operations = new Map<string, ProviderPluginOperationResolutionV1>();
2664
+ const ownerByBinding = new Map<ProviderPluginBindingV1, ProviderPluginV1>();
2665
+ const implementationSourceSnapshots =
2666
+ new Map<ProviderPluginV1, ProviderPluginImplementationSourceSnapshot>();
2667
+ const packageDependencySnapshots =
2668
+ new Map<ProviderPluginV1, ProviderPluginPackageDependencySnapshot>();
2669
+ const registryInstalledPackageSnapshots =
2670
+ new Map<string, InstalledPackageSnapshot>();
2671
+ const registryModuleAnalyses: ProviderModuleAnalysisCache = {
2672
+ values: new Map(),
2673
+ imports: 0,
2674
+ };
2675
+ const readSource =
2676
+ dependencies.readImplementationSource ?? readImplementationSource;
2677
+ const readDependencySource =
2678
+ dependencies.readDependencySource
2679
+ ?? readDependencySourceFromDisk;
2680
+ const registryRepositorySourceSnapshots = new Map<string, Buffer>();
2681
+ const registryRepositorySourceReaders = new Map<string, {
2682
+ readonly label: string;
2683
+ readonly read: () => Buffer;
2684
+ }>();
2685
+ const snapshotRepositorySource = (
2686
+ path: string,
2687
+ label: string,
2688
+ read: () => Buffer,
2689
+ ): Buffer => {
2690
+ const existing = registryRepositorySourceSnapshots.get(path);
2691
+ if (existing !== undefined) return existing;
2692
+ const bytes = Buffer.from(read());
2693
+ registryRepositorySourceSnapshots.set(path, bytes);
2694
+ registryRepositorySourceReaders.set(path, { label, read });
2695
+ return bytes;
2696
+ };
2697
+ const readCoherentImplementationSource: typeof readImplementationSource = (
2698
+ pluginId,
2699
+ source,
2700
+ ) => snapshotRepositorySource(
2701
+ source.path,
2702
+ `provider plugin ${pluginId} implementation source ${source.label}`,
2703
+ () => readSource(pluginId, source),
2704
+ );
2705
+ const readCoherentDependencySource = (path: string): Buffer =>
2706
+ classifyProviderPluginPhysicalPath(path).kind === "installed-package"
2707
+ ? readDependencySource(path)
2708
+ : snapshotRepositorySource(
2709
+ path,
2710
+ `provider plugin repository dependency ${relative(repositoryRoot, path)}`,
2711
+ () => readDependencySource(path),
2712
+ );
2713
+ // Execution configuration is a single semantic closure even when an
2714
+ // extended tsconfig lives in node_modules. Capture those exact files in the
2715
+ // registry-wide snapshot so the final byte revalidation cannot return a
2716
+ // mixed catalog during an install or package update.
2717
+ const readCoherentExecutionConfigSource = (path: string): Buffer =>
2718
+ snapshotRepositorySource(
2719
+ path,
2720
+ `provider plugin execution configuration ${relative(repositoryRoot, path)}`,
2721
+ () => readDependencySource(path),
2722
+ );
2723
+ let registryImplementationSourceReads = 0;
2724
+ let registryImplementationSourceBytes = 0;
2725
+ let registryDependencyRecords = 0;
2726
+ let registryDependencyRecordBytes = 0;
2727
+
2728
+ for (const plugin of plugins) {
2729
+ if (plugin.sourceKind !== "portable") {
2730
+ if (
2731
+ plugin.implementationSources.length
2732
+ > MAX_PROVIDER_PLUGIN_IMPLEMENTATION_SOURCES
2733
+ ) {
2734
+ throw new Error(
2735
+ `provider plugin ${plugin.id} exceeds its implementation source bound`,
2736
+ );
2737
+ }
2738
+ const snapshots = snapshotProviderPluginImplementationSources(
2739
+ plugin,
2740
+ readCoherentImplementationSource,
2741
+ new Map(),
2742
+ );
2743
+ const evaluationSourceSha256 =
2744
+ providerPluginEvaluationSourceSha256(plugin);
2745
+ if (evaluationSourceSha256 === undefined) {
2746
+ throw new Error(
2747
+ `provider plugin ${plugin.id} has no evaluation-time source identity`,
2748
+ );
2749
+ }
2750
+ const evaluationInstalledPackageSha256 =
2751
+ providerPluginEvaluationInstalledPackageSha256(plugin);
2752
+ if (evaluationInstalledPackageSha256 === undefined) {
2753
+ throw new Error(
2754
+ `provider plugin ${plugin.id} has no evaluation-time installed package identity`,
2755
+ );
2756
+ }
2757
+ for (const [path, bytes] of snapshots) {
2758
+ const evaluated = evaluationSourceSha256.get(path);
2759
+ const current = createHash("sha256").update(bytes).digest("hex");
2760
+ if (evaluated === undefined || evaluated !== current) {
2761
+ throw new Error(
2762
+ `provider plugin ${plugin.id} implementation changed after its definition was evaluated`,
2763
+ );
2764
+ }
2765
+ }
2766
+ registryImplementationSourceReads += snapshots.size;
2767
+ for (const bytes of snapshots.values()) {
2768
+ registryImplementationSourceBytes += bytes.byteLength;
2769
+ }
2770
+ if (
2771
+ registryImplementationSourceReads
2772
+ > MAX_REGISTRY_IMPLEMENTATION_SOURCE_READS
2773
+ || registryImplementationSourceBytes
2774
+ > MAX_REGISTRY_IMPLEMENTATION_SOURCE_BYTES
2775
+ ) {
2776
+ throw new Error(
2777
+ "provider plugin registry implementation source closure exceeds its bound",
2778
+ );
2779
+ }
2780
+ implementationSourceSnapshots.set(plugin, snapshots);
2781
+ assertProviderPluginImplementationSourceClosure(
2782
+ plugin,
2783
+ snapshots,
2784
+ registryModuleAnalyses,
2785
+ );
2786
+ const dependencySnapshot = providerPluginPackageDependencyIdentity(
2787
+ plugin,
2788
+ snapshots,
2789
+ readCoherentDependencySource,
2790
+ registryInstalledPackageSnapshots,
2791
+ registryModuleAnalyses,
2792
+ readCoherentExecutionConfigSource,
2793
+ );
2794
+ // A custom dependency reader is an explicit test-only virtual filesystem
2795
+ // seam. Production always compares the evaluation closure to the same
2796
+ // inode-bound bytes used for the registry identity.
2797
+ if (dependencies.readDependencySource === undefined) {
2798
+ for (const [path, evaluated] of evaluationSourceSha256) {
2799
+ const bytes = registryRepositorySourceSnapshots.get(path);
2800
+ if (
2801
+ bytes === undefined
2802
+ || createHash("sha256").update(bytes).digest("hex") !== evaluated
2803
+ ) {
2804
+ throw new Error(
2805
+ `provider plugin ${plugin.id} implementation changed after its definition was evaluated`,
2806
+ );
2807
+ }
2808
+ }
2809
+ for (
2810
+ const [root, evaluated] of evaluationInstalledPackageSha256
2811
+ ) {
2812
+ const current = registryInstalledPackageSnapshots.get(root);
2813
+ if (
2814
+ current === undefined
2815
+ || current.treeSha256 !== evaluated
2816
+ ) {
2817
+ throw new Error(
2818
+ `provider plugin ${plugin.id} installed dependency changed after its definition was evaluated`,
2819
+ );
2820
+ }
2821
+ }
2822
+ }
2823
+ registryDependencyRecords += dependencySnapshot.length;
2824
+ for (const record of dependencySnapshot) {
2825
+ registryDependencyRecordBytes += record.bytes.byteLength;
2826
+ }
2827
+ if (
2828
+ registryDependencyRecords > MAX_REGISTRY_DEPENDENCY_RECORDS
2829
+ || registryDependencyRecordBytes
2830
+ > MAX_REGISTRY_DEPENDENCY_RECORD_BYTES
2831
+ ) {
2832
+ throw new Error(
2833
+ "provider plugin registry dependency closure exceeds its aggregate bound",
2834
+ );
2835
+ }
2836
+ packageDependencySnapshots.set(plugin, dependencySnapshot);
2837
+ assertInstalledPackageRegistryCacheBounds(
2838
+ registryInstalledPackageSnapshots,
2839
+ );
2840
+ }
2841
+ for (const binding of plugin.bindings) {
2842
+ const key = routeKey(binding.transport, binding.surfaceId);
2843
+ const existing = routes.get(key);
2844
+ if (existing !== undefined) {
2845
+ const existingOwner = ownerByBinding.get(existing);
2846
+ throw new Error(
2847
+ `duplicate provider plugin route ${key}: ${existingOwner?.id ?? "unknown"} and ${plugin.id}`,
2848
+ );
2849
+ }
2850
+ if (binding.transport !== "provider-api") {
2851
+ const existingAccount = accountRoutes.get(binding.surfaceId);
2852
+ if (existingAccount !== undefined) {
2853
+ const existingOwner = ownerByBinding.get(existingAccount);
2854
+ throw new Error(
2855
+ `duplicate provider plugin account route ${binding.surfaceId}: ${existingOwner?.id ?? "unknown"} and ${plugin.id}`,
2856
+ );
2857
+ }
2858
+ accountRoutes.set(binding.surfaceId, binding);
2859
+ }
2860
+ if (
2861
+ binding.transport === "web-session-api"
2862
+ || binding.transport === "linked-device"
2863
+ ) {
2864
+ const existingSession = sessionRoutes.get(binding.surfaceId);
2865
+ if (existingSession !== undefined) {
2866
+ const existingOwner = ownerByBinding.get(existingSession);
2867
+ throw new Error(
2868
+ `duplicate provider plugin session route ${binding.surfaceId}: ${existingOwner?.id ?? "unknown"} and ${plugin.id}`,
2869
+ );
2870
+ }
2871
+ sessionRoutes.set(binding.surfaceId, binding);
2872
+ }
2873
+ routes.set(key, binding);
2874
+ ownerByBinding.set(binding, plugin);
2875
+ for (const operation of binding.operations) {
2876
+ for (const contractVersion of operation.contractVersions) {
2877
+ const exactKey = operationKey(
2878
+ binding.transport,
2879
+ binding.surfaceId,
2880
+ operation.name,
2881
+ contractVersion,
2882
+ );
2883
+ if (operations.has(exactKey)) {
2884
+ throw new Error(`duplicate provider plugin exact contract ${exactKey}`);
2885
+ }
2886
+ const portableIdentity = portableProviderPluginOperationIdentity(
2887
+ binding,
2888
+ operation,
2889
+ contractVersion,
2890
+ );
2891
+ if (
2892
+ (plugin.sourceKind === "portable") !== (portableIdentity !== null)
2893
+ ) {
2894
+ throw new Error(
2895
+ `provider plugin ${plugin.id} portable operation identity is inconsistent`,
2896
+ );
2897
+ }
2898
+ operations.set(exactKey, Object.freeze({
2899
+ plugin,
2900
+ binding,
2901
+ operation,
2902
+ contractVersion,
2903
+ portableIdentity,
2904
+ }));
2905
+ }
2906
+ }
2907
+ }
2908
+ }
2909
+
2910
+ for (const plugin of plugins) {
2911
+ if (plugin.sourceKind !== "built-in") continue;
2912
+ const identity = reviewedBuiltInContractIdentityIfPresent(plugin.id, plugin.version);
2913
+ if (identity === null) continue;
2914
+ const ownedBindingCoordinates = new Set<string>();
2915
+ const ownedRouteCoordinates = new Map<string, number>();
2916
+ for (const binding of plugin.bindings) {
2917
+ for (const operation of binding.operations) {
2918
+ for (const contractVersion of operation.contractVersions) {
2919
+ ownedBindingCoordinates.add(operationKey(
2920
+ binding.transport,
2921
+ binding.surfaceId,
2922
+ operation.name,
2923
+ contractVersion,
2924
+ ));
2925
+ const routeCoordinate = `${operation.name}@${contractVersion}`;
2926
+ ownedRouteCoordinates.set(
2927
+ routeCoordinate,
2928
+ (ownedRouteCoordinates.get(routeCoordinate) ?? 0) + 1,
2929
+ );
2930
+ }
2931
+ }
2932
+ }
2933
+ for (const distribution of
2934
+ identity.legacyDistributionReadImplementationSha256 ?? []) {
2935
+ for (const route of distribution.routes) {
2936
+ if (!ownedBindingCoordinates.has(route)) {
2937
+ throw new Error(
2938
+ `built-in provider plugin ${plugin.id}@${plugin.version} legacy distribution owns uninstalled route ${route}`,
2939
+ );
2940
+ }
2941
+ }
2942
+ }
2943
+ for (const route of Object.keys(
2944
+ identity.legacyRouteReadImplementationSha256 ?? {},
2945
+ )) {
2946
+ if (ownedRouteCoordinates.get(route) !== 1) {
2947
+ throw new Error(
2948
+ `built-in provider plugin ${plugin.id}@${plugin.version} legacy identity requires one uniquely owned route ${route}`,
2949
+ );
2950
+ }
2951
+ }
2952
+ }
2953
+
2954
+ for (
2955
+ const [path, snapshot] of [...registryRepositorySourceSnapshots.entries()]
2956
+ .sort(([left], [right]) => compareIdentityText(left, right))
2957
+ ) {
2958
+ const reader = registryRepositorySourceReaders.get(path);
2959
+ if (reader === undefined) {
2960
+ throw new Error(
2961
+ `provider plugin registry source ${relative(repositoryRoot, path)} has no revalidation reader`,
2962
+ );
2963
+ }
2964
+ const current = Buffer.from(reader.read());
2965
+ if (
2966
+ current.byteLength !== snapshot.byteLength
2967
+ || !timingSafeEqual(current, snapshot)
2968
+ ) {
2969
+ throw new Error(`${reader.label} changed before registry startup completed`);
2970
+ }
2971
+ }
2972
+
2973
+ for (const snapshot of registryInstalledPackageSnapshots.values()) {
2974
+ if (
2975
+ !sameInstalledPackageTree(
2976
+ snapshot.verificationWalk,
2977
+ walkInstalledPackageTree(snapshot.root),
2978
+ )
2979
+ ) {
2980
+ throw new Error(
2981
+ `installed package ${snapshot.id} changed before registry startup completed`,
2982
+ );
2983
+ }
2984
+ }
2985
+
2986
+ const implementationHashes = new Map<ProviderPluginV1, Buffer>();
2987
+ const implementationClosureHashes = new Map<ProviderPluginV1, string>();
2988
+ for (const plugin of plugins) {
2989
+ if (plugin.sourceKind === "portable") {
2990
+ implementationHashes.set(
2991
+ plugin,
2992
+ computeProviderPluginImplementationHash(plugin, new Map(), []),
2993
+ );
2994
+ implementationClosureHashes.set(
2995
+ plugin,
2996
+ computeProviderPluginClosureHash(plugin, new Map(), []),
2997
+ );
2998
+ continue;
2999
+ }
3000
+ const sourceSnapshots = implementationSourceSnapshots.get(plugin);
3001
+ const dependencySnapshots = packageDependencySnapshots.get(plugin);
3002
+ if (sourceSnapshots === undefined || dependencySnapshots === undefined) {
3003
+ throw new Error(
3004
+ `provider plugin ${plugin.id} implementation snapshot is incomplete`,
3005
+ );
3006
+ }
3007
+ const expected = computeProviderPluginImplementationHash(
3008
+ plugin,
3009
+ sourceSnapshots,
3010
+ dependencySnapshots,
3011
+ );
3012
+ implementationHashes.set(plugin, expected);
3013
+ const implementationClosureHash = computeProviderPluginClosureHash(
3014
+ plugin,
3015
+ sourceSnapshots,
3016
+ dependencySnapshots,
3017
+ );
3018
+ implementationClosureHashes.set(plugin, implementationClosureHash);
3019
+ const verifyRuntimeIdentity = async (
3020
+ phase: ProviderPluginRuntimeLoadIdentityPhase,
3021
+ ): Promise<void> => {
3022
+ await dependencies.beforeRuntimeLoadIdentityCheck?.(plugin.id, phase);
3023
+ // This is a cooperative source-drift boundary, not a same-account
3024
+ // hostile-code sandbox. The pre-check prevents ordinary post-startup
3025
+ // replacement from loading; the post-check prevents a concurrently
3026
+ // changed module from reaching an operation. A writer that races inside
3027
+ // the module loader can still trigger top-level module side effects
3028
+ // before the post-check rejects, which is why source plugins remain
3029
+ // trusted in-process code.
3030
+ // These maps are intentionally new for every phase. A registry startup
3031
+ // cache must never make a live pre/post import identity check look clean.
3032
+ const currentSources = snapshotProviderPluginImplementationSources(
3033
+ plugin,
3034
+ readImplementationSource,
3035
+ new Map(),
3036
+ );
3037
+ assertProviderPluginImplementationSourceClosure(
3038
+ plugin,
3039
+ currentSources,
3040
+ );
3041
+ const currentDependencies = providerPluginPackageDependencyIdentity(
3042
+ plugin,
3043
+ currentSources,
3044
+ readDependencySourceFromDisk,
3045
+ );
3046
+ const current = computeProviderPluginImplementationHash(
3047
+ plugin,
3048
+ currentSources,
3049
+ currentDependencies,
3050
+ );
3051
+ if (
3052
+ current.byteLength !== expected.byteLength
3053
+ || !timingSafeEqual(current, expected)
3054
+ ) {
3055
+ throw new Error(
3056
+ `provider plugin ${plugin.id} implementation changed after registry startup; restart before loading its runtime`,
3057
+ );
3058
+ }
3059
+ };
3060
+ const token = `${plugin.id}@${plugin.version}:${expected.toString("hex")}`;
3061
+ for (const binding of plugin.bindings) {
3062
+ bindProviderPluginRuntimeLoadIdentity(
3063
+ binding.loadRuntime,
3064
+ Object.freeze({
3065
+ token,
3066
+ verify: verifyRuntimeIdentity,
3067
+ }),
3068
+ );
3069
+ }
3070
+ }
3071
+
3072
+ const frozenPlugins = Object.freeze(plugins);
3073
+ const implementationHash = (binding: ProviderPluginBindingV1): Buffer => {
3074
+ const plugin = ownerByBinding.get(binding);
3075
+ if (plugin === undefined) throw new Error("provider plugin binding is not registered");
3076
+ const existing = implementationHashes.get(plugin);
3077
+ if (existing === undefined) {
3078
+ throw new Error(
3079
+ `provider plugin ${plugin.id} implementation hash is unavailable`,
3080
+ );
3081
+ }
3082
+ return Buffer.from(existing);
3083
+ };
3084
+ const contractImplementationHash = (
3085
+ binding: ProviderPluginBindingV1,
3086
+ ): Buffer => {
3087
+ const plugin = ownerByBinding.get(binding);
3088
+ if (plugin === undefined) {
3089
+ throw new Error("provider plugin binding is not registered");
3090
+ }
3091
+ if (plugin.sourceKind !== "built-in") return implementationHash(binding);
3092
+ return Buffer.from(
3093
+ reviewedBuiltInContractIdentity(plugin.id, plugin.version)
3094
+ .implementationSha256,
3095
+ "hex",
3096
+ );
3097
+ };
3098
+ const legacyContractImplementationHashes = (
3099
+ binding: ProviderPluginBindingV1,
3100
+ operation: string,
3101
+ contractVersion: number,
3102
+ ): readonly Buffer[] => {
3103
+ const plugin = ownerByBinding.get(binding);
3104
+ if (plugin === undefined) {
3105
+ throw new Error("provider plugin binding is not registered");
3106
+ }
3107
+ if (!binding.operations.some((candidate) =>
3108
+ candidate.name === operation && candidate.contractVersions.includes(contractVersion)
3109
+ )) {
3110
+ throw new Error(
3111
+ `provider plugin ${plugin.id} does not own operation ${operation}@${contractVersion}`,
3112
+ );
3113
+ }
3114
+ if (plugin.sourceKind !== "built-in") return Object.freeze([]);
3115
+ const identity = reviewedBuiltInContractIdentity(plugin.id, plugin.version);
3116
+ const legacy = identity.legacyReadImplementationSha256;
3117
+ const e71Legacy = identity.legacyE71ReadImplementationSha256;
3118
+ const currentLegacy = identity.legacyCurrentReadImplementationSha256;
3119
+ const coordinate = `${operation}@${contractVersion}`;
3120
+ const bindingCoordinate = operationKey(
3121
+ binding.transport,
3122
+ binding.surfaceId,
3123
+ operation,
3124
+ contractVersion,
3125
+ );
3126
+ const distributionLegacy =
3127
+ identity.legacyDistributionReadImplementationSha256
3128
+ ?.filter((distribution) => distribution.routes.includes(bindingCoordinate))
3129
+ .map((distribution) => distribution.implementationSha256) ?? [];
3130
+ const routeLegacy = identity.legacyRouteReadImplementationSha256?.[
3131
+ coordinate
3132
+ ] ?? [];
3133
+ if (
3134
+ legacy === null
3135
+ && e71Legacy === null
3136
+ && currentLegacy.length === 0
3137
+ && distributionLegacy.length === 0
3138
+ && routeLegacy.length === 0
3139
+ ) {
3140
+ return Object.freeze([]);
3141
+ }
3142
+ if (legacy === null || e71Legacy === null) {
3143
+ if (legacy !== null || e71Legacy !== null) {
3144
+ throw new Error(
3145
+ `built-in provider plugin ${plugin.id}@${plugin.version} has an incomplete legacy contract identity`,
3146
+ );
3147
+ }
3148
+ return Object.freeze([...currentLegacy, ...distributionLegacy, ...routeLegacy]
3149
+ .map((value) => Buffer.from(value, "hex")));
3150
+ }
3151
+ return Object.freeze([
3152
+ Buffer.from(legacy.test, "hex"),
3153
+ Buffer.from(legacy.production, "hex"),
3154
+ Buffer.from(legacy.development, "hex"),
3155
+ Buffer.from(e71Legacy.default, "hex"),
3156
+ Buffer.from(e71Legacy.test, "hex"),
3157
+ Buffer.from(e71Legacy.production, "hex"),
3158
+ Buffer.from(e71Legacy.development, "hex"),
3159
+ ...currentLegacy.map((value) => Buffer.from(value, "hex")),
3160
+ ...distributionLegacy.map((value) => Buffer.from(value, "hex")),
3161
+ ...routeLegacy.map((value) => Buffer.from(value, "hex")),
3162
+ ]);
3163
+ };
3164
+ const implementationClosureHash = (
3165
+ binding: ProviderPluginBindingV1,
3166
+ ): string => {
3167
+ const plugin = ownerByBinding.get(binding);
3168
+ if (plugin === undefined) {
3169
+ throw new Error("provider plugin binding is not registered");
3170
+ }
3171
+ const hash = implementationClosureHashes.get(plugin);
3172
+ if (hash === undefined) {
3173
+ throw new Error(
3174
+ `provider plugin ${plugin.id} reviewed implementation closure is unavailable`,
3175
+ );
3176
+ }
3177
+ return hash;
3178
+ };
3179
+ const artifactSha256 = (
3180
+ binding: ProviderPluginBindingV1,
3181
+ ): string | null => {
3182
+ const plugin = ownerByBinding.get(binding);
3183
+ if (plugin === undefined) {
3184
+ throw new Error("provider plugin binding is not registered");
3185
+ }
3186
+ return portableProviderPluginArtifactSha256(plugin);
3187
+ };
3188
+ const ownedManifests = new Map<string, GhostgetManifest>();
3189
+ for (const plugin of plugins) {
3190
+ for (const binding of plugin.bindings) {
3191
+ const portable = portableProviderPluginAdapter(binding);
3192
+ if (portable === null) continue;
3193
+ if (ownedManifests.has(portable.adapterId)) {
3194
+ throw new Error(
3195
+ `duplicate portable provider plugin adapter ID: ${portable.adapterId}`,
3196
+ );
3197
+ }
3198
+ ownedManifests.set(portable.adapterId, portable.manifest);
3199
+ }
3200
+ }
3201
+ const frozenOwnedManifests = Object.freeze(
3202
+ [...ownedManifests.values()].sort((left, right) =>
3203
+ compareIdentityText(left.id, right.id)),
3204
+ );
3205
+
3206
+ const resolveRoute = (
3207
+ transport: ProviderPluginTransport,
3208
+ surfaceId: string,
3209
+ ): ProviderPluginBindingV1 | undefined => routes.get(routeKey(transport, surfaceId));
3210
+ const requireRoute = (
3211
+ transport: ProviderPluginTransport,
3212
+ surfaceId: string,
3213
+ ): ProviderPluginBindingV1 => {
3214
+ const binding = resolveRoute(transport, surfaceId);
3215
+ if (binding === undefined) throw new Error(`provider plugin route ${routeKey(transport, surfaceId)} is not installed`);
3216
+ return binding;
3217
+ };
3218
+ const resolveSessionRoute = (surfaceId: string): ProviderPluginSessionBindingV1 | undefined =>
3219
+ sessionRoutes.get(surfaceId);
3220
+ const requireSessionRoute = (surfaceId: string): ProviderPluginSessionBindingV1 => {
3221
+ const binding = resolveSessionRoute(surfaceId);
3222
+ if (binding === undefined) throw new Error(`provider plugin session route ${surfaceId} is not installed`);
3223
+ return binding;
3224
+ };
3225
+ const resolveAccountRoute = (surfaceId: string): ProviderPluginBindingV1 | undefined =>
3226
+ accountRoutes.get(surfaceId);
3227
+ const requireAccountRoute = (surfaceId: string): ProviderPluginBindingV1 => {
3228
+ const binding = resolveAccountRoute(surfaceId);
3229
+ if (binding === undefined) {
3230
+ throw new Error(`provider plugin account route ${surfaceId} is not installed`);
3231
+ }
3232
+ return binding;
3233
+ };
3234
+ const resolveOperation = (
3235
+ transport: ProviderPluginTransport,
3236
+ surfaceId: string,
3237
+ operation: string,
3238
+ contractVersion: number,
3239
+ ): ProviderPluginBindingV1 | undefined =>
3240
+ operations.get(operationKey(transport, surfaceId, operation, contractVersion))?.binding;
3241
+ const requireOperation = (
3242
+ transport: ProviderPluginTransport,
3243
+ surfaceId: string,
3244
+ operation: string,
3245
+ contractVersion: number,
3246
+ ): ProviderPluginBindingV1 => {
3247
+ const binding = resolveOperation(transport, surfaceId, operation, contractVersion);
3248
+ if (binding === undefined) {
3249
+ throw new Error(
3250
+ `provider plugin operation ${operationKey(transport, surfaceId, operation, contractVersion)} is not installed`,
3251
+ );
3252
+ }
3253
+ return binding;
3254
+ };
3255
+ const resolveOperationDefinition = (
3256
+ transport: ProviderPluginTransport,
3257
+ surfaceId: string,
3258
+ operation: string,
3259
+ contractVersion: number,
3260
+ ): ProviderPluginOperationResolutionV1 | undefined =>
3261
+ operations.get(operationKey(transport, surfaceId, operation, contractVersion));
3262
+ const requireOperationDefinition = (
3263
+ transport: ProviderPluginTransport,
3264
+ surfaceId: string,
3265
+ operation: string,
3266
+ contractVersion: number,
3267
+ ): ProviderPluginOperationResolutionV1 => {
3268
+ const resolution = resolveOperationDefinition(
3269
+ transport,
3270
+ surfaceId,
3271
+ operation,
3272
+ contractVersion,
3273
+ );
3274
+ if (resolution === undefined) {
3275
+ throw new Error(
3276
+ `provider plugin operation ${operationKey(transport, surfaceId, operation, contractVersion)} is not installed`,
3277
+ );
3278
+ }
3279
+ return resolution;
3280
+ };
3281
+
3282
+ return Object.freeze({
3283
+ list: () => frozenPlugins,
3284
+ get: (pluginId: string) => pluginsById.get(pluginId),
3285
+ resolveRoute,
3286
+ requireRoute,
3287
+ resolveSessionRoute,
3288
+ requireSessionRoute,
3289
+ resolveAccountRoute,
3290
+ requireAccountRoute,
3291
+ resolveOperation,
3292
+ requireOperation,
3293
+ resolveOperationDefinition,
3294
+ requireOperationDefinition,
3295
+ implementationHash,
3296
+ contractImplementationHash,
3297
+ legacyContractImplementationHashes,
3298
+ implementationClosureHash,
3299
+ artifactSha256,
3300
+ listOwnedManifests: () => frozenOwnedManifests,
3301
+ resolveOwnedManifest: (adapterId) => ownedManifests.get(adapterId),
3302
+ });
3303
+ }
3304
+
3305
+ /** Build a registry from reviewed built-in/source definitions only. */
3306
+ export function createProviderPluginRegistry(
3307
+ definitions: readonly (ProviderPluginDefinitionV1 | ProviderPluginV1)[],
3308
+ dependencies: ProviderPluginRegistryDependencies = {},
3309
+ ): ProviderPluginRegistry {
3310
+ return createProviderPluginRegistryInternal(
3311
+ definitions,
3312
+ dependencies,
3313
+ false,
3314
+ );
3315
+ }
3316
+
3317
+ /**
3318
+ * Extend one already validated source registry with kernel-authorized portable
3319
+ * projections. Generic source-registry construction never admits portable
3320
+ * objects, so a source plugin cannot masquerade as installed child-host code.
3321
+ */
3322
+ export function extendProviderPluginRegistryWithPortablePlugins(
3323
+ sourceRegistry: ProviderPluginRegistry,
3324
+ portablePlugins: readonly ProviderPluginV1[],
3325
+ ): ProviderPluginRegistry {
3326
+ const sourcePlugins = sourceRegistry.list();
3327
+ if (sourcePlugins.some((plugin) => plugin.sourceKind === "portable")) {
3328
+ throw new Error(
3329
+ "portable provider plugin catalog extension requires a source-only registry",
3330
+ );
3331
+ }
3332
+ if (portablePlugins.some((plugin) => plugin.sourceKind !== "portable")) {
3333
+ throw new Error(
3334
+ "portable provider plugin catalog extension accepts only portable projections",
3335
+ );
3336
+ }
3337
+ return createProviderPluginRegistryInternal(
3338
+ [...sourcePlugins, ...portablePlugins],
3339
+ {},
3340
+ true,
3341
+ );
3342
+ }