@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,4907 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ closeSync,
4
+ constants,
5
+ existsSync,
6
+ fstatSync,
7
+ lstatSync,
8
+ openSync,
9
+ opendirSync,
10
+ readFileSync,
11
+ readlinkSync,
12
+ readSync,
13
+ realpathSync,
14
+ type BigIntStats,
15
+ } from "node:fs";
16
+ import { builtinModules, createRequire } from "node:module";
17
+ import {
18
+ basename,
19
+ dirname,
20
+ extname,
21
+ isAbsolute,
22
+ parse,
23
+ relative,
24
+ resolve,
25
+ sep,
26
+ } from "node:path";
27
+ import { fileURLToPath } from "node:url";
28
+
29
+ import type { GhostgetAuth } from "./auth";
30
+ import type { MessageLikeMeSourceConversationCoordinateBindingV1 } from "./message-like-me-agentic-messaging";
31
+ import type {
32
+ BrowserDispatchPlan,
33
+ ArrayInputField,
34
+ FileInputField,
35
+ IdempotencyKind,
36
+ InputField,
37
+ InputSchema,
38
+ OperationInput,
39
+ OperationRisk,
40
+ GhostgetManifest,
41
+ ScalarInputField,
42
+ } from "./model";
43
+ import type { ProviderActionContext } from "./provider-context";
44
+ import type {
45
+ ProviderConversationV1,
46
+ ProviderMaterializedPageV1,
47
+ ProviderMessageV1,
48
+ } from "./omni-model";
49
+ import type { LocalCliOperationExecutor } from "./local-cli-execution";
50
+ import type { OperationDeadline } from "./operation-deadline";
51
+ import {
52
+ localCliToolArtifactForCurrentRuntime,
53
+ parseLocalCliToolIdentityV1,
54
+ type LocalCliToolArtifactIdentityV1,
55
+ type LocalCliToolIdentityV1,
56
+ } from "./local-cli-tool-identity";
57
+ import {
58
+ isProviderPluginId,
59
+ isProviderPluginOperationName,
60
+ isProviderPluginSurfaceId,
61
+ } from "./provider-plugin-identifiers";
62
+ import {
63
+ createPortableOperationIdentityV1,
64
+ type PortableOperationIdentityV1,
65
+ } from "./provider-plugin-portable-identity";
66
+ import {
67
+ isVerifiedPortableProviderPluginPackage,
68
+ type PortableProviderPluginBindingV1 as PortablePackageBindingV1,
69
+ type VerifiedPortableProviderPluginPackage,
70
+ } from "./provider-plugin-package";
71
+ import {
72
+ assertKernelPortableProviderPluginBindingProjection,
73
+ } from "./provider-plugin-portable-authority";
74
+ import {
75
+ inspectLocalCliCleanupFilesystemReadiness,
76
+ } from "./provider-plugin-cleanup-resource";
77
+ import type {
78
+ ProviderPluginCleanupBarrierRegistrar,
79
+ PublicWebSessionOperationExecutor,
80
+ WebSessionOperationExecutor,
81
+ } from "./web-session-execution";
82
+
83
+ export const PROVIDER_PLUGIN_API_VERSION = 1 as const;
84
+
85
+ export const providerPluginTransports = [
86
+ "provider-api",
87
+ "web-session-api",
88
+ "linked-device",
89
+ "local-cli",
90
+ ] as const;
91
+
92
+ export type ProviderPluginTransport = (typeof providerPluginTransports)[number];
93
+ export type ProviderPluginAuthKind = GhostgetAuth["kind"];
94
+ export type ProviderPluginContractStateV1 = "observed" | "capture-required";
95
+
96
+ type ProviderPluginOperationDefinitionBaseV1 = {
97
+ readonly name: string;
98
+ /** Exact durable contract identity owned by this descriptor. */
99
+ readonly contractVersion: number;
100
+ /**
101
+ * Built-in-only durable routing aliases for contracts whose reviewed
102
+ * semantics remain compatible with this active descriptor.
103
+ */
104
+ readonly historicalContractVersions?: readonly number[];
105
+ readonly risk: OperationRisk;
106
+ readonly input: InputSchema;
107
+ readonly sideEffect: string;
108
+ readonly idempotency: IdempotencyKind;
109
+ readonly dedupeWindowMs: number;
110
+ readonly state: ProviderPluginContractStateV1;
111
+ /** Built-in web-session-only marker for one reviewed public read. */
112
+ readonly access?: "public";
113
+ readonly dispatch: "none" | "single" | "thread-items" | "bounded-items";
114
+ readonly implementation: string;
115
+ readonly planDispatches: (input: OperationInput) => readonly BrowserDispatchPlan[];
116
+ readonly validateInput: (input: OperationInput) => readonly string[];
117
+ /**
118
+ * Optional provider-owned relationship between the bound auth subject and
119
+ * operation input (for example, an explicit actor field). The kernel still
120
+ * requires the binding-level subject matcher for every authenticated write.
121
+ */
122
+ readonly validateSubjectInput?: (
123
+ input: OperationInput,
124
+ subject: string,
125
+ ) => readonly string[];
126
+ /**
127
+ * Declares that this exact operation supports a read-only boolean
128
+ * desired-state reconciliation. Runtime code supplies the observation; the
129
+ * kernel compares it to this pure desired-state projection.
130
+ */
131
+ readonly reconciliation?: ProviderPluginReconciliationDefinitionV1;
132
+ /**
133
+ * Provider-owned interpretation of one exact inbox read. Every installed
134
+ * messaging list/read contract must either supply a pure, versioned
135
+ * materializer or state why normalization is unsafe. The kernel reparses
136
+ * supported output from unknown before it reaches durable state.
137
+ */
138
+ readonly omni?: ProviderPluginOmniDefinitionV1;
139
+ };
140
+
141
+ export type ProviderPluginReconciliationDefinitionV1 =
142
+ | {
143
+ readonly kind: "boolean-desired-state";
144
+ readonly desiredState: (input: OperationInput) => boolean;
145
+ }
146
+ | {
147
+ /** Reconcile a create from its encrypted response-derived exact target. */
148
+ readonly kind: "provider-accepted-target-presence";
149
+ }
150
+ | {
151
+ /**
152
+ * Reconcile a deletion from an encrypted exact target bound by a strict
153
+ * provider read before the mutation request leaves the process.
154
+ */
155
+ readonly kind: "provider-bound-target-desired-state";
156
+ readonly desiredState: false;
157
+ };
158
+
159
+ export type ProviderPluginOmniDefinitionV1 =
160
+ | {
161
+ readonly state: "supported";
162
+ readonly schemaVersion: 1;
163
+ readonly materializerId: string;
164
+ readonly materializerVersion: number;
165
+ readonly materialize: (
166
+ input: OperationInput,
167
+ output: unknown,
168
+ ) => unknown;
169
+ }
170
+ | {
171
+ readonly state: "unsupported";
172
+ readonly reason: string;
173
+ };
174
+
175
+ export type ProviderPluginMessagingTargetV1 = Readonly<
176
+ Record<string, string>
177
+ >;
178
+
179
+ export type ProviderPluginMessagingRouteCandidateV1 = {
180
+ /** Exact provider coordinates retained only in Ghostget private state. */
181
+ readonly target: ProviderPluginMessagingTargetV1;
182
+ /** Exact provider conversation identity used to bind normalized reads. */
183
+ readonly conversationProviderId: string;
184
+ readonly conversationKind: "single" | "group" | "unknown";
185
+ readonly title: string | null;
186
+ readonly participants: ProviderConversationV1["participants"];
187
+ readonly providerRevision: string | null;
188
+ };
189
+
190
+ export type ProviderPluginMessagingTurnPartV1 = {
191
+ readonly partId: string;
192
+ readonly text: string;
193
+ /** Exact provider message identity recovered from one current context ref. */
194
+ readonly replyToProviderId: string | null;
195
+ };
196
+
197
+ export type ProviderPluginMessagingAcceptedResultV1 = {
198
+ /** Provider accepted/submitted work; delivery and read remain unproven. */
199
+ readonly state: "submitted";
200
+ readonly providerMessageId: string;
201
+ readonly providerRevision: string | null;
202
+ };
203
+
204
+ export type ProviderPluginMessagingExpectedOwnPrefixV1 = {
205
+ readonly base: {
206
+ readonly exactDataRevision: string;
207
+ readonly latestMessageRevision: string;
208
+ readonly contextLimit: number;
209
+ /** Ordered, bounded hashes of the exact normalized context at preview. */
210
+ readonly messages: readonly {
211
+ readonly providerMessageId: string;
212
+ readonly providerRevision: string | null;
213
+ readonly orderedAt: string | null;
214
+ readonly messageSha256: string;
215
+ }[];
216
+ };
217
+ readonly current: {
218
+ readonly exactDataRevision: string;
219
+ readonly latestMessageRevision: string;
220
+ readonly messages: readonly ProviderMessageV1[];
221
+ };
222
+ readonly accepted: readonly {
223
+ readonly providerMessageId: string;
224
+ readonly providerRevision: string | null;
225
+ readonly direction: "outgoing";
226
+ readonly bodySha256: string;
227
+ readonly replyToProviderId: string | null;
228
+ }[];
229
+ };
230
+
231
+ export type ProviderPluginMessagingExpectedOwnPrefixProofV1 =
232
+ | {
233
+ readonly state: "proven";
234
+ /** Total accepted prefix represented by this exact live window. */
235
+ readonly matchedAcceptedPrefixCount: number;
236
+ }
237
+ | { readonly state: "drift" };
238
+
239
+ export type ProviderPluginMessagingLiveRouteStateV1 = {
240
+ readonly conversationProviderId: string;
241
+ /** Fresh provider-native network shown in the confirmation recipient. */
242
+ readonly network: string;
243
+ readonly conversation: {
244
+ readonly kind: "single" | "group" | "unknown";
245
+ readonly title: string | null;
246
+ readonly participantCount: number;
247
+ };
248
+ readonly sourceConversationCoordinate:
249
+ MessageLikeMeSourceConversationCoordinateBindingV1;
250
+ readonly participantFingerprint: string;
251
+ readonly providerRevision: string | null;
252
+ };
253
+
254
+ export type ProviderPluginMessagingReconciliationRequestV1 = {
255
+ readonly operation: string;
256
+ readonly input: OperationInput;
257
+ };
258
+
259
+ export type ProviderPluginMessagingActionDeadlineV1 = Pick<
260
+ OperationDeadline,
261
+ "signal" | "remainingTimeMs" | "run" | "throwIfUnavailable"
262
+ >;
263
+
264
+ export type ProviderPluginMessagingActionAttemptV1 = {
265
+ /**
266
+ * The provider runtime must await this durable fence immediately before its
267
+ * first effect-capable call or child process. It may perform bounded reads
268
+ * before the fence, but no mutation and no retry may happen before or after it.
269
+ */
270
+ readonly beforeExternalBegin: () => Promise<void>;
271
+ /** One kernel-owned total budget for lazy loading and every provider step. */
272
+ readonly operationDeadline: ProviderPluginMessagingActionDeadlineV1;
273
+ readonly signal: AbortSignal;
274
+ /**
275
+ * Durably terminalize this fenced attempt with one provider-owned symbolic
276
+ * diagnostic. This never authorizes retry and accepts no message data or IDs.
277
+ */
278
+ readonly recordPrivateIndeterminateOutcome: (code: string) => Promise<void>;
279
+ readonly environment: Readonly<Record<string, string | undefined>>;
280
+ /** Kernel-owned durable cleanup publication for provider-private resources. */
281
+ readonly registerCleanupBarrier?: ProviderPluginCleanupBarrierRegistrar;
282
+ };
283
+
284
+ export type ProviderPluginMessagingActionExecutorV1 = (
285
+ operation: string,
286
+ input: OperationInput,
287
+ auth: GhostgetAuth,
288
+ attempt: ProviderPluginMessagingActionAttemptV1,
289
+ ) => Promise<unknown>;
290
+
291
+ export type ProviderPluginMessagingActionDefinitionV1 =
292
+ | {
293
+ readonly state: "unavailable";
294
+ readonly reason: string;
295
+ readonly reply: "unsupported";
296
+ }
297
+ | {
298
+ readonly state: "supported";
299
+ readonly operation: string;
300
+ readonly reply: "supported" | "unsupported";
301
+ readonly livePreflight: {
302
+ readonly operation: string;
303
+ readonly input: (target: ProviderPluginMessagingTargetV1) => OperationInput;
304
+ readonly snapshot: (
305
+ output: unknown,
306
+ expectedAccountSubject: string,
307
+ ) => ProviderPluginMessagingLiveRouteStateV1;
308
+ };
309
+ readonly compileTurnPart: (
310
+ target: ProviderPluginMessagingTargetV1,
311
+ part: ProviderPluginMessagingTurnPartV1,
312
+ ) => OperationInput;
313
+ readonly mapAcceptedResult: (
314
+ output: unknown,
315
+ ) => ProviderPluginMessagingAcceptedResultV1;
316
+ /** Prove that live state contains exactly the accepted own-message prefix and no drift. */
317
+ readonly proveExpectedOwnPrefix: (
318
+ value: ProviderPluginMessagingExpectedOwnPrefixV1,
319
+ ) => ProviderPluginMessagingExpectedOwnPrefixProofV1;
320
+ /**
321
+ * Build one explicit checked provider read. The generic facade never
322
+ * invokes this implicitly and never changes existing runs reconciliation.
323
+ */
324
+ readonly reconciliation: (
325
+ target: ProviderPluginMessagingTargetV1,
326
+ accepted: ProviderPluginMessagingAcceptedResultV1,
327
+ ) => ProviderPluginMessagingReconciliationRequestV1;
328
+ };
329
+
330
+ /**
331
+ * Provider-owned exact messaging codec. The kernel owns lifecycle, storage,
332
+ * confirmation, and execution; this SPI owns provider coordinate semantics.
333
+ */
334
+ export type ProviderPluginMessagingDefinitionV1 = {
335
+ readonly schemaVersion: 1;
336
+ readonly contractId: string;
337
+ readonly network: string;
338
+ /** Whether an exact provider read proves current remote state. */
339
+ readonly contextLiveness:
340
+ | "fresh-as-of-live-preflight"
341
+ | "freshness-unproven";
342
+ readonly listOperation: "messaging.list";
343
+ readonly contextOperation: "messaging.read";
344
+ readonly enumerateRoutes: (
345
+ input: OperationInput,
346
+ page: ProviderMaterializedPageV1,
347
+ ) => readonly ProviderPluginMessagingRouteCandidateV1[];
348
+ /** Exact stored-target resolution, independent from bounded list pagination. */
349
+ readonly resolveRoute: {
350
+ readonly operation: string;
351
+ readonly input: (
352
+ target: ProviderPluginMessagingTargetV1,
353
+ ) => OperationInput;
354
+ readonly candidates: (
355
+ target: ProviderPluginMessagingTargetV1,
356
+ output: unknown,
357
+ ) => readonly ProviderPluginMessagingRouteCandidateV1[];
358
+ /** Canonical source coordinate proved only by the exact resolver output. */
359
+ readonly sourceConversationCoordinate: (
360
+ target: ProviderPluginMessagingTargetV1,
361
+ output: unknown,
362
+ expectedAccountSubject: string,
363
+ ) => MessageLikeMeSourceConversationCoordinateBindingV1 | null;
364
+ };
365
+ readonly parseTarget: (value: unknown) => ProviderPluginMessagingTargetV1;
366
+ readonly contextInput: (
367
+ target: ProviderPluginMessagingTargetV1,
368
+ limit: number,
369
+ ) => OperationInput;
370
+ readonly action: ProviderPluginMessagingActionDefinitionV1;
371
+ };
372
+
373
+ export type ProviderApiPluginOperationDefinitionV1 =
374
+ ProviderPluginOperationDefinitionBaseV1 & {
375
+ readonly requiredScopeSets: readonly (readonly string[])[];
376
+ /** Bounded provider-owned coverage labels; not a kernel-owned closed union. */
377
+ readonly coverage: readonly string[];
378
+ };
379
+
380
+ export type WebSessionPluginOperationDefinitionV1 =
381
+ ProviderPluginOperationDefinitionBaseV1 & {
382
+ readonly requiredScopeSets?: never;
383
+ readonly coverage?: never;
384
+ };
385
+
386
+ export type LocalCliPluginOperationDefinitionV1 =
387
+ WebSessionPluginOperationDefinitionV1;
388
+
389
+ export type ProviderPluginOperationDefinitionV1 =
390
+ | ProviderApiPluginOperationDefinitionV1
391
+ | WebSessionPluginOperationDefinitionV1
392
+ | LocalCliPluginOperationDefinitionV1;
393
+
394
+ /** Validated registry projection. `contractVersions` is CLI compatibility only. */
395
+ export type ProviderApiPluginOperationV1 =
396
+ ProviderApiPluginOperationDefinitionV1 & {
397
+ readonly contractVersions: readonly number[];
398
+ };
399
+
400
+ export type WebSessionPluginOperationV1 =
401
+ WebSessionPluginOperationDefinitionV1 & {
402
+ readonly contractVersions: readonly number[];
403
+ };
404
+
405
+ export type LocalCliPluginOperationV1 = WebSessionPluginOperationV1;
406
+
407
+ export type ProviderPluginOperationV1 =
408
+ | ProviderApiPluginOperationV1
409
+ | WebSessionPluginOperationV1
410
+ | LocalCliPluginOperationV1;
411
+
412
+ export const MAX_PROVIDER_PLUGIN_PLAN_DISPATCHES = 25;
413
+ export const MAX_PROVIDER_PLUGIN_PLAN_BYTES = 16 * 1024;
414
+ /** Trusted-source definitions match the portable manifest's V1 shape bounds. */
415
+ export const MAX_PROVIDER_PLUGIN_IMPLEMENTATION_SOURCES = 512;
416
+ export const MAX_PROVIDER_PLUGIN_BINDINGS = 64;
417
+ export const MAX_PROVIDER_PLUGIN_OPERATIONS_PER_BINDING = 256;
418
+ export const MAX_PROVIDER_PLUGIN_CONTRACT_VERSIONS_PER_OPERATION = 256;
419
+ /** A plugin may use many bindings without reaching the full 64 × 256 product. */
420
+ export const MAX_PROVIDER_PLUGIN_OPERATIONS = 4_096;
421
+
422
+ export type ProviderPluginPlanOperationV1 = Pick<
423
+ ProviderPluginOperationDefinitionV1,
424
+ "name" | "risk" | "dispatch" | "planDispatches"
425
+ >;
426
+
427
+ const providerPluginDispatchIdPattern =
428
+ /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*(?:\[[1-9][0-9]*\])?$/u;
429
+ const conformedProviderPluginPlanHooks =
430
+ new WeakSet<ProviderPluginPlanOperationV1["planDispatches"]>();
431
+
432
+ function hasUnpairedSurrogate(value: string): boolean {
433
+ for (let index = 0; index < value.length; index += 1) {
434
+ const code = value.charCodeAt(index);
435
+ if (code >= 0xd800 && code <= 0xdbff) {
436
+ const next = value.charCodeAt(index + 1);
437
+ if (!Number.isFinite(next) || next < 0xdc00 || next > 0xdfff) return true;
438
+ index += 1;
439
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
440
+ return true;
441
+ }
442
+ }
443
+ return false;
444
+ }
445
+
446
+ function ownDataProperty(
447
+ value: object,
448
+ key: string,
449
+ label: string,
450
+ ): unknown {
451
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
452
+ if (
453
+ descriptor === undefined
454
+ || !descriptor.enumerable
455
+ || !("value" in descriptor)
456
+ ) {
457
+ throw new Error(`${label} must be an enumerable data property`);
458
+ }
459
+ return descriptor.value as unknown;
460
+ }
461
+
462
+ function requirePlainRecord(value: object, label: string): void {
463
+ const prototype = Object.getPrototypeOf(value) as unknown;
464
+ if (prototype !== Object.prototype && prototype !== null) {
465
+ throw new Error(`${label} must be a plain JSON object`);
466
+ }
467
+ }
468
+
469
+ function cloneFrozenPlanInputValue(
470
+ value: unknown,
471
+ label: string,
472
+ ): OperationInput[string] {
473
+ if (typeof value === "string" || typeof value === "boolean") return value;
474
+ if (typeof value === "number" && Number.isFinite(value)) return value;
475
+ if (Array.isArray(value)) {
476
+ if ((Object.getPrototypeOf(value) as unknown) !== Array.prototype) {
477
+ throw new Error(`${label} must be a plain JSON array`);
478
+ }
479
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
480
+ const rawLength: unknown = lengthDescriptor !== undefined
481
+ && "value" in lengthDescriptor
482
+ ? lengthDescriptor.value as unknown
483
+ : Number.NaN;
484
+ if (
485
+ typeof rawLength !== "number"
486
+ || !Number.isSafeInteger(rawLength)
487
+ || rawLength < 0
488
+ || rawLength > 1_000
489
+ ) {
490
+ throw new Error(`${label} must be a bounded dense array`);
491
+ }
492
+ const length = rawLength;
493
+ const expectedKeys = new Set<PropertyKey>([
494
+ "length",
495
+ ...Array.from({ length }, (_unused, index) => String(index)),
496
+ ]);
497
+ const actualKeys = Reflect.ownKeys(value);
498
+ if (
499
+ actualKeys.length !== expectedKeys.size
500
+ || actualKeys.some((key) => !expectedKeys.has(key))
501
+ ) {
502
+ throw new Error(`${label} must be a bounded dense array`);
503
+ }
504
+ return Object.freeze(Array.from(
505
+ { length },
506
+ (_unused, index) =>
507
+ cloneFrozenPlanInputScalar(
508
+ ownDataProperty(value, String(index), `${label}[${index}]`),
509
+ `${label}[${index}]`,
510
+ ),
511
+ ));
512
+ }
513
+ return cloneFrozenPlanInputFile(value, label);
514
+ }
515
+
516
+ function cloneFrozenPlanInputScalar(
517
+ value: unknown,
518
+ label: string,
519
+ ): string | number | boolean | { readonly kind: "file"; readonly reference: string } {
520
+ if (typeof value === "string" || typeof value === "boolean") return value;
521
+ if (typeof value === "number" && Number.isFinite(value)) return value;
522
+ return cloneFrozenPlanInputFile(value, label);
523
+ }
524
+
525
+ function cloneFrozenPlanInputFile(
526
+ value: unknown,
527
+ label: string,
528
+ ): { readonly kind: "file"; readonly reference: string } {
529
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
530
+ throw new Error(`${label} is not JSON-compatible plugin input`);
531
+ }
532
+ requirePlainRecord(value, label);
533
+ const keys = Reflect.ownKeys(value);
534
+ if (
535
+ keys.length !== 2
536
+ || !keys.includes("kind")
537
+ || !keys.includes("reference")
538
+ ) {
539
+ throw new Error(`${label} is not a valid file input`);
540
+ }
541
+ const kind = ownDataProperty(value, "kind", `${label}.kind`);
542
+ const reference = ownDataProperty(value, "reference", `${label}.reference`);
543
+ if (kind !== "file" || typeof reference !== "string") {
544
+ throw new Error(`${label} is not a valid file input`);
545
+ }
546
+ return Object.freeze({ kind: "file" as const, reference });
547
+ }
548
+
549
+ function detachedFrozenPlanInput(input: OperationInput): OperationInput {
550
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
551
+ throw new Error("provider plugin plan input must be a JSON object");
552
+ }
553
+ requirePlainRecord(input, "provider plugin plan input");
554
+ const keys = Reflect.ownKeys(input);
555
+ if (
556
+ keys.length > 100
557
+ || keys.some((key) => typeof key !== "string")
558
+ ) {
559
+ throw new Error("provider plugin plan input must be a bounded JSON object");
560
+ }
561
+ const result: Record<string, OperationInput[string]> = {};
562
+ for (const key of keys as string[]) {
563
+ Object.defineProperty(result, key, {
564
+ configurable: true,
565
+ enumerable: true,
566
+ writable: true,
567
+ value: cloneFrozenPlanInputValue(
568
+ ownDataProperty(input, key, `provider plugin plan input.${key}`),
569
+ `provider plugin plan input.${key}`,
570
+ ),
571
+ });
572
+ }
573
+ return Object.freeze(result);
574
+ }
575
+
576
+ function parseProviderPluginDispatches(
577
+ value: unknown,
578
+ operation: ProviderPluginPlanOperationV1,
579
+ input: OperationInput,
580
+ ): readonly BrowserDispatchPlan[] {
581
+ const label = `provider plugin operation ${operation.name} planDispatches`;
582
+ if (
583
+ !Array.isArray(value)
584
+ || (Object.getPrototypeOf(value) as unknown) !== Array.prototype
585
+ ) {
586
+ throw new Error(`${label} must return a plain JSON array`);
587
+ }
588
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
589
+ const rawLength: unknown = lengthDescriptor !== undefined
590
+ && "value" in lengthDescriptor
591
+ ? lengthDescriptor.value as unknown
592
+ : Number.NaN;
593
+ if (
594
+ typeof rawLength !== "number"
595
+ || !Number.isSafeInteger(rawLength)
596
+ || rawLength < 0
597
+ || rawLength > MAX_PROVIDER_PLUGIN_PLAN_DISPATCHES
598
+ ) {
599
+ throw new Error(
600
+ `${label} may return at most ${MAX_PROVIDER_PLUGIN_PLAN_DISPATCHES} dispatches`,
601
+ );
602
+ }
603
+ const length = rawLength;
604
+ const expectedKeys = new Set<PropertyKey>([
605
+ "length",
606
+ ...Array.from({ length }, (_unused, index) => String(index)),
607
+ ]);
608
+ const actualKeys = Reflect.ownKeys(value);
609
+ if (
610
+ actualKeys.length !== expectedKeys.size
611
+ || actualKeys.some((key) => !expectedKeys.has(key))
612
+ ) {
613
+ throw new Error(`${label} must return a dense array without extra properties`);
614
+ }
615
+ const dispatches: BrowserDispatchPlan[] = [];
616
+ const ids = new Set<string>();
617
+ for (let index = 0; index < length; index += 1) {
618
+ const candidate = ownDataProperty(
619
+ value,
620
+ String(index),
621
+ `${label}[${index}]`,
622
+ );
623
+ if (
624
+ typeof candidate !== "object"
625
+ || candidate === null
626
+ || Array.isArray(candidate)
627
+ ) {
628
+ throw new Error(`${label}[${index}] must be a plain JSON object`);
629
+ }
630
+ requirePlainRecord(candidate, `${label}[${index}]`);
631
+ const keys = Reflect.ownKeys(candidate);
632
+ if (
633
+ keys.length !== 2
634
+ || !keys.includes("id")
635
+ || !keys.includes("description")
636
+ ) {
637
+ throw new Error(
638
+ `${label}[${index}] must contain only id and description`,
639
+ );
640
+ }
641
+ const id = ownDataProperty(candidate, "id", `${label}[${index}].id`);
642
+ const description = ownDataProperty(
643
+ candidate,
644
+ "description",
645
+ `${label}[${index}].description`,
646
+ );
647
+ if (
648
+ typeof id !== "string"
649
+ || id.length > 80
650
+ || !providerPluginDispatchIdPattern.test(id)
651
+ ) {
652
+ throw new Error(`${label}[${index}] has an invalid dispatch ID`);
653
+ }
654
+ if (ids.has(id)) {
655
+ throw new Error(`${label} returned duplicate dispatch ID ${id}`);
656
+ }
657
+ if (
658
+ typeof description !== "string"
659
+ || description.length < 1
660
+ || description.length > 500
661
+ || hasControlCharacters(description)
662
+ || hasUnpairedSurrogate(description)
663
+ ) {
664
+ throw new Error(`${label}[${index}] has an invalid description`);
665
+ }
666
+ ids.add(id);
667
+ dispatches.push(Object.freeze({ id, description }));
668
+ }
669
+
670
+ if (operation.risk === "R1" && dispatches.length !== 0) {
671
+ throw new Error("R1 operations must not schedule remote dispatches");
672
+ }
673
+ if (
674
+ (operation.risk === "R2" || operation.risk === "R3")
675
+ && dispatches.length === 0
676
+ ) {
677
+ throw new Error(`${label} must return at least one dispatch for ${operation.risk}`);
678
+ }
679
+ if (operation.dispatch === "none" && dispatches.length !== 0) {
680
+ throw new Error(`${label} must return no dispatches for dispatch policy none`);
681
+ }
682
+ if (operation.dispatch === "single" && dispatches.length !== 1) {
683
+ throw new Error(`${label} must return exactly one dispatch for dispatch policy single`);
684
+ }
685
+ if (operation.dispatch === "thread-items") {
686
+ const items = input.items;
687
+ if (
688
+ !Array.isArray(items)
689
+ || items.length < 1
690
+ || dispatches.length !== items.length
691
+ ) {
692
+ throw new Error(
693
+ `${label} must return exactly one dispatch for each input.items value`,
694
+ );
695
+ }
696
+ }
697
+ const result = Object.freeze(dispatches);
698
+ const encoded = JSON.stringify(result);
699
+ if (Buffer.byteLength(encoded, "utf8") > MAX_PROVIDER_PLUGIN_PLAN_BYTES) {
700
+ throw new Error(
701
+ `${label} exceeds its ${MAX_PROVIDER_PLUGIN_PLAN_BYTES}-byte canonical JSON bound`,
702
+ );
703
+ }
704
+ return result;
705
+ }
706
+
707
+ /**
708
+ * Run one pure planner twice at its trust boundary and return only detached,
709
+ * deeply frozen kernel-owned data. Built-in and portable plugin hosts share
710
+ * this exact conformance check.
711
+ */
712
+ function runUntrustedProviderPluginPlanConformance(
713
+ operation: ProviderPluginPlanOperationV1,
714
+ input: OperationInput,
715
+ ): readonly BrowserDispatchPlan[] {
716
+ const plan = (): readonly BrowserDispatchPlan[] => {
717
+ const detachedInput = detachedFrozenPlanInput(input);
718
+ const returned: unknown = operation.planDispatches(detachedInput);
719
+ return parseProviderPluginDispatches(returned, operation, detachedInput);
720
+ };
721
+ const first = plan();
722
+ const second = plan();
723
+ if (JSON.stringify(first) !== JSON.stringify(second)) {
724
+ throw new Error(
725
+ `provider plugin operation ${operation.name} planDispatches is unstable for identical input`,
726
+ );
727
+ }
728
+ return first;
729
+ }
730
+
731
+ export function runProviderPluginPlanConformance(
732
+ operation: ProviderPluginPlanOperationV1,
733
+ input: OperationInput,
734
+ ): readonly BrowserDispatchPlan[] {
735
+ if (conformedProviderPluginPlanHooks.has(operation.planDispatches)) {
736
+ return operation.planDispatches(input);
737
+ }
738
+ return runUntrustedProviderPluginPlanConformance(operation, input);
739
+ }
740
+
741
+ function conformingProviderPluginPlanDispatches(
742
+ operation: ProviderPluginOperationDefinitionV1,
743
+ ): ProviderPluginOperationDefinitionV1["planDispatches"] {
744
+ const cache = new WeakMap<OperationInput, readonly BrowserDispatchPlan[]>();
745
+ const planDispatches = (input: OperationInput): readonly BrowserDispatchPlan[] => {
746
+ const existing = cache.get(input);
747
+ if (existing !== undefined) return existing;
748
+ const planned = runUntrustedProviderPluginPlanConformance(operation, input);
749
+ cache.set(input, planned);
750
+ return planned;
751
+ };
752
+ conformedProviderPluginPlanHooks.add(planDispatches);
753
+ return planDispatches;
754
+ }
755
+
756
+ export type ProviderPluginSubjectDefinitionV1 = {
757
+ /** Human-readable, value-free description of the accepted subject form. */
758
+ readonly format: string;
759
+ readonly matches: (value: string) => boolean;
760
+ };
761
+
762
+ export type ProviderPluginSubjectProbeOptionsV1 = {
763
+ /** Caller-owned cancellation propagated through probe network/process work. */
764
+ readonly signal?: AbortSignal;
765
+ readonly environment?: Readonly<Record<string, string | undefined>>;
766
+ /** Kernel-owned durable publication for operation-private resources. */
767
+ readonly registerCleanupBarrier?: ProviderPluginCleanupBarrierRegistrar;
768
+ };
769
+
770
+ export type ProviderPluginReconciliationOptionsV1 = {
771
+ readonly signal?: AbortSignal;
772
+ readonly environment?: Readonly<Record<string, string | undefined>>;
773
+ readonly registerCleanupBarrier?: ProviderPluginCleanupBarrierRegistrar;
774
+ };
775
+
776
+ export type ProviderPluginSubjectV1 = ProviderPluginSubjectDefinitionV1 & {
777
+ /** Lazy compatibility hook synthesized from the binding runtime. */
778
+ readonly probe?: (
779
+ auth: GhostgetAuth,
780
+ options?: ProviderPluginSubjectProbeOptionsV1,
781
+ ) => Promise<string>;
782
+ };
783
+
784
+ export type ProviderPluginImplementationSourceDefinitionV1 = {
785
+ /** Stable plugin-relative identity included in implementation hashes. */
786
+ readonly label: string;
787
+ /** A reviewed source file. V1 accepts repository-owned file URLs only. */
788
+ readonly url: URL;
789
+ };
790
+
791
+ export type ProviderPluginImplementationSourceV1 = {
792
+ /** Stable plugin-relative identity included in implementation hashes. */
793
+ readonly label: string;
794
+ /** Canonical immutable path validated under Ghostget's source root. */
795
+ readonly path: string;
796
+ };
797
+
798
+ export type ProviderApiPluginRuntimeV1 = {
799
+ readonly execute: (context: ProviderActionContext) => Promise<void>;
800
+ readonly executeMessagingPart?: ProviderPluginMessagingActionExecutorV1;
801
+ };
802
+
803
+ export type ProviderPluginReconciliationReadbackV1 = {
804
+ readonly actualState: boolean;
805
+ readonly reason: string;
806
+ };
807
+
808
+ export type ProviderPluginAcceptedTargetReconciliationContextV1 = {
809
+ readonly schemaVersion: 1;
810
+ readonly kind: "provider-accepted-target-presence";
811
+ readonly dispatch: {
812
+ readonly id: string;
813
+ readonly index: number;
814
+ readonly planned: number;
815
+ };
816
+ readonly target: {
817
+ readonly schemaVersion: 1;
818
+ readonly identifier: string;
819
+ };
820
+ };
821
+
822
+ export type ProviderPluginBoundTargetDesiredStateReconciliationContextV1 =
823
+ Omit<ProviderPluginAcceptedTargetReconciliationContextV1, "kind"> & {
824
+ readonly kind: "provider-bound-target-desired-state";
825
+ };
826
+
827
+ export type ProviderPluginReconciliationContextV1 =
828
+ | ProviderPluginAcceptedTargetReconciliationContextV1
829
+ | ProviderPluginBoundTargetDesiredStateReconciliationContextV1;
830
+
831
+ export type ProviderPluginLinkedDeviceRuntimeStatusV1 = {
832
+ readonly ready: boolean;
833
+ readonly implementation: string;
834
+ readonly version: string;
835
+ readonly integrity: string;
836
+ readonly setupCommand?: string;
837
+ };
838
+
839
+ export type ProviderPluginLinkedDeviceSyncResultV1 = {
840
+ readonly itemsStored: number;
841
+ readonly projection: string;
842
+ readonly emitsProtocolAcknowledgements: boolean;
843
+ };
844
+
845
+ export type ProviderPluginLinkedDeviceAttemptBoundaryV1 = {
846
+ /** Durable attempt identity retained for later reconciliation. */
847
+ readonly journalId: string;
848
+ /**
849
+ * Bounded preflight that is contractually incapable of provider mutation or
850
+ * protocol acknowledgement may run first. This callback must be awaited
851
+ * before the first effect-capable process or externally observable action.
852
+ */
853
+ readonly beforeExternalBegin: () => Promise<void>;
854
+ };
855
+
856
+ export type ProviderPluginLinkedDeviceLifecycleRuntimeV1 = {
857
+ readonly inspect: (
858
+ environment: Readonly<Record<string, string | undefined>>,
859
+ ) => Promise<ProviderPluginLinkedDeviceRuntimeStatusV1>;
860
+ readonly pair: (
861
+ auth: GhostgetAuth,
862
+ options: {
863
+ readonly phone?: string;
864
+ readonly environment: Readonly<Record<string, string | undefined>>;
865
+ readonly attempt: ProviderPluginLinkedDeviceAttemptBoundaryV1;
866
+ },
867
+ ) => Promise<string>;
868
+ readonly syncOnce: (
869
+ auth: GhostgetAuth,
870
+ options: {
871
+ readonly environment: Readonly<Record<string, string | undefined>>;
872
+ readonly attempt: ProviderPluginLinkedDeviceAttemptBoundaryV1;
873
+ },
874
+ ) => Promise<ProviderPluginLinkedDeviceSyncResultV1>;
875
+ };
876
+
877
+ export type WebSessionPluginRuntimeV1 = {
878
+ readonly probe: (
879
+ auth: GhostgetAuth,
880
+ options?: ProviderPluginSubjectProbeOptionsV1,
881
+ ) => Promise<string>;
882
+ readonly execute: WebSessionOperationExecutor;
883
+ readonly executeMessagingPart?: ProviderPluginMessagingActionExecutorV1;
884
+ readonly executePublic?: PublicWebSessionOperationExecutor;
885
+ readonly reconcile?: (
886
+ operation: string,
887
+ input: OperationInput,
888
+ auth: GhostgetAuth,
889
+ context?: ProviderPluginReconciliationContextV1,
890
+ ) => Promise<ProviderPluginReconciliationReadbackV1>;
891
+ readonly linkedDeviceLifecycle?: ProviderPluginLinkedDeviceLifecycleRuntimeV1;
892
+ };
893
+
894
+ export type LocalCliPluginRuntimeV1 = {
895
+ readonly inspect: (
896
+ environment: Readonly<Record<string, string | undefined>>,
897
+ options?: {
898
+ readonly registerCleanupBarrier?: ProviderPluginCleanupBarrierRegistrar;
899
+ },
900
+ ) => Promise<LocalCliPluginRuntimeStatusV1>;
901
+ readonly probe: (
902
+ auth: GhostgetAuth,
903
+ options?: ProviderPluginSubjectProbeOptionsV1,
904
+ ) => Promise<string>;
905
+ readonly execute: LocalCliOperationExecutor;
906
+ readonly executeMessagingPart?: ProviderPluginMessagingActionExecutorV1;
907
+ readonly reconcile?: (
908
+ operation: string,
909
+ input: OperationInput,
910
+ auth: GhostgetAuth,
911
+ context?: ProviderPluginReconciliationContextV1,
912
+ options?: ProviderPluginReconciliationOptionsV1,
913
+ ) => Promise<ProviderPluginReconciliationReadbackV1>;
914
+ };
915
+
916
+ export type LocalCliPluginRuntimeStatusV1 = {
917
+ readonly ready: boolean;
918
+ readonly platform: string;
919
+ readonly arch: string;
920
+ readonly version: string | null;
921
+ readonly executableSha256: string | null;
922
+ readonly reason: string | null;
923
+ };
924
+
925
+ export type ProviderApiPluginRuntimeHooksV1 = {
926
+ readonly loadRuntime: () => Promise<ProviderApiPluginRuntimeV1>;
927
+ };
928
+
929
+ export type WebSessionPluginRuntimeHooksV1 = {
930
+ readonly loadRuntime: () => Promise<WebSessionPluginRuntimeV1>;
931
+ };
932
+
933
+ export type LocalCliPluginRuntimeHooksV1 = {
934
+ readonly loadRuntime: () => Promise<LocalCliPluginRuntimeV1>;
935
+ };
936
+
937
+ type ProviderPluginBindingDefinitionBaseV1 = {
938
+ readonly surfaceId: string;
939
+ /**
940
+ * Canonical provider/service authority used by manifests, receipts, and
941
+ * account realms. For network transports this is also the primary endpoint;
942
+ * local-cli bindings keep child/loopback endpoints private to code-owned
943
+ * runtime logic and never expose them as manifest authority.
944
+ */
945
+ readonly origin: `https://${string}`;
946
+ /**
947
+ * Exact public origins an adapter manifest must declare. Defaults to
948
+ * `[origin]`; official APIs can bind a public product origin separately
949
+ * from their API transport endpoint.
950
+ */
951
+ readonly manifestOrigins?: readonly `https://${string}`[];
952
+ /**
953
+ * Hostname suffixes reserved from generic browser/template transports.
954
+ * Defaults to the exact transport and manifest hostnames.
955
+ */
956
+ readonly protectedHostnameFamilies?: readonly string[];
957
+ readonly authKinds: readonly ProviderPluginAuthKind[];
958
+ readonly subject: ProviderPluginSubjectDefinitionV1;
959
+ readonly messaging?: ProviderPluginMessagingDefinitionV1;
960
+ };
961
+
962
+ export type ProviderApiPluginBindingDefinitionV1 =
963
+ ProviderPluginBindingDefinitionBaseV1 & {
964
+ readonly transport: "provider-api";
965
+ /**
966
+ * Every exact HTTPS origin that may receive this binding's OAuth bearer
967
+ * token. Defaults to `[origin]`; declare auxiliary provider APIs explicitly.
968
+ */
969
+ readonly runtimeOrigins?: readonly `https://${string}`[];
970
+ readonly operations: readonly ProviderApiPluginOperationDefinitionV1[];
971
+ readonly runtime: ProviderApiPluginRuntimeHooksV1;
972
+ };
973
+
974
+ export type WebSessionApiPluginBindingDefinitionV1 =
975
+ ProviderPluginBindingDefinitionBaseV1 & {
976
+ readonly transport: "web-session-api";
977
+ readonly operations: readonly WebSessionPluginOperationDefinitionV1[];
978
+ readonly runtime: WebSessionPluginRuntimeHooksV1;
979
+ };
980
+
981
+ export type LinkedDevicePluginBindingDefinitionV1 =
982
+ ProviderPluginBindingDefinitionBaseV1 & {
983
+ readonly transport: "linked-device";
984
+ readonly operations: readonly WebSessionPluginOperationDefinitionV1[];
985
+ readonly linkedDeviceLifecycle?: {
986
+ readonly pair: true;
987
+ readonly syncOnce: true;
988
+ readonly inspect: true;
989
+ };
990
+ readonly runtime: WebSessionPluginRuntimeHooksV1;
991
+ };
992
+
993
+ export type LocalCliPluginBindingDefinitionV1 =
994
+ ProviderPluginBindingDefinitionBaseV1 & {
995
+ readonly transport: "local-cli";
996
+ readonly tool: LocalCliToolIdentityV1;
997
+ readonly operations: readonly LocalCliPluginOperationDefinitionV1[];
998
+ readonly runtime: LocalCliPluginRuntimeHooksV1;
999
+ };
1000
+
1001
+ export type ProviderPluginBindingDefinitionV1 =
1002
+ | ProviderApiPluginBindingDefinitionV1
1003
+ | WebSessionApiPluginBindingDefinitionV1
1004
+ | LinkedDevicePluginBindingDefinitionV1
1005
+ | LocalCliPluginBindingDefinitionV1;
1006
+
1007
+ export type ProviderPluginDefinitionV1 = {
1008
+ readonly apiVersion: typeof PROVIDER_PLUGIN_API_VERSION;
1009
+ readonly id: string;
1010
+ readonly version: string;
1011
+ readonly displayName: string;
1012
+ readonly sourceKind: "built-in" | "source";
1013
+ readonly implementationSources: readonly ProviderPluginImplementationSourceDefinitionV1[];
1014
+ readonly bindings: readonly ProviderPluginBindingDefinitionV1[];
1015
+ };
1016
+
1017
+ export type PortableProviderPluginProjectionDefinitionV1 = {
1018
+ readonly apiVersion: typeof PROVIDER_PLUGIN_API_VERSION;
1019
+ readonly id: string;
1020
+ readonly version: string;
1021
+ readonly displayName: string;
1022
+ readonly sourceKind: "portable";
1023
+ readonly package: VerifiedPortableProviderPluginPackage;
1024
+ readonly bindings: readonly {
1025
+ readonly adapterId: string;
1026
+ readonly manifest: GhostgetManifest;
1027
+ readonly portableBinding: PortablePackageBindingV1;
1028
+ readonly binding: ProviderPluginBindingDefinitionV1;
1029
+ }[];
1030
+ };
1031
+
1032
+ type ProviderPluginBindingBaseV1 = Omit<
1033
+ ProviderPluginBindingDefinitionBaseV1,
1034
+ "manifestOrigins" | "protectedHostnameFamilies" | "subject" | "messaging"
1035
+ > & {
1036
+ readonly manifestOrigins: readonly `https://${string}`[];
1037
+ readonly protectedHostnameFamilies: readonly string[];
1038
+ readonly subject: ProviderPluginSubjectV1;
1039
+ readonly messaging?: ProviderPluginMessagingDefinitionV1;
1040
+ };
1041
+
1042
+ export type ProviderApiPluginBindingV1 = ProviderPluginBindingBaseV1 & {
1043
+ readonly transport: "provider-api";
1044
+ readonly runtimeOrigins: readonly `https://${string}`[];
1045
+ readonly operations: readonly ProviderApiPluginOperationV1[];
1046
+ readonly loadRuntime: ProviderApiPluginRuntimeHooksV1["loadRuntime"];
1047
+ readonly execute: ProviderApiPluginRuntimeV1["execute"];
1048
+ readonly executeMessagingPart?: ProviderPluginMessagingActionExecutorV1;
1049
+ };
1050
+
1051
+ export type WebSessionApiPluginBindingV1 = ProviderPluginBindingBaseV1 & {
1052
+ readonly transport: "web-session-api";
1053
+ readonly operations: readonly WebSessionPluginOperationV1[];
1054
+ readonly loadRuntime: WebSessionPluginRuntimeHooksV1["loadRuntime"];
1055
+ readonly execute: WebSessionPluginRuntimeV1["execute"];
1056
+ readonly executeMessagingPart?: ProviderPluginMessagingActionExecutorV1;
1057
+ readonly executePublic?: NonNullable<WebSessionPluginRuntimeV1["executePublic"]>;
1058
+ readonly reconcile?: NonNullable<WebSessionPluginRuntimeV1["reconcile"]>;
1059
+ };
1060
+
1061
+ export type LinkedDevicePluginBindingV1 = ProviderPluginBindingBaseV1 & {
1062
+ readonly transport: "linked-device";
1063
+ readonly operations: readonly WebSessionPluginOperationV1[];
1064
+ readonly loadRuntime: WebSessionPluginRuntimeHooksV1["loadRuntime"];
1065
+ readonly execute: WebSessionPluginRuntimeV1["execute"];
1066
+ readonly executeMessagingPart?: ProviderPluginMessagingActionExecutorV1;
1067
+ readonly reconcile?: NonNullable<WebSessionPluginRuntimeV1["reconcile"]>;
1068
+ readonly linkedDeviceLifecycle?: ProviderPluginLinkedDeviceLifecycleRuntimeV1;
1069
+ };
1070
+
1071
+ export type LocalCliPluginBindingV1 = ProviderPluginBindingBaseV1 & {
1072
+ readonly transport: "local-cli";
1073
+ readonly tool: LocalCliToolIdentityV1;
1074
+ readonly operations: readonly LocalCliPluginOperationV1[];
1075
+ readonly loadRuntime: LocalCliPluginRuntimeHooksV1["loadRuntime"];
1076
+ readonly inspect: LocalCliPluginRuntimeV1["inspect"];
1077
+ readonly execute: LocalCliPluginRuntimeV1["execute"];
1078
+ readonly executeMessagingPart?: ProviderPluginMessagingActionExecutorV1;
1079
+ readonly reconcile?: NonNullable<LocalCliPluginRuntimeV1["reconcile"]>;
1080
+ };
1081
+
1082
+ export type ProviderPluginBindingV1 =
1083
+ | ProviderApiPluginBindingV1
1084
+ | WebSessionApiPluginBindingV1
1085
+ | LinkedDevicePluginBindingV1
1086
+ | LocalCliPluginBindingV1;
1087
+
1088
+
1089
+ export type ProviderPluginV1 = Omit<
1090
+ ProviderPluginDefinitionV1,
1091
+ "bindings" | "implementationSources" | "sourceKind"
1092
+ > & {
1093
+ readonly sourceKind: "built-in" | "source" | "portable";
1094
+ readonly implementationSources: readonly ProviderPluginImplementationSourceV1[];
1095
+ readonly bindings: readonly ProviderPluginBindingV1[];
1096
+ };
1097
+
1098
+ const pluginVersionPattern = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/u;
1099
+ const sourceLabelPattern = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$/u;
1100
+ const authKinds = new Set<ProviderPluginAuthKind>([
1101
+ "cookie-source",
1102
+ "cookies-file",
1103
+ "browser-profile",
1104
+ "oauth-token-file",
1105
+ "linked-device-store",
1106
+ ]);
1107
+ const validatedProviderPlugins = new WeakSet<object>();
1108
+ const portableProviderPluginArtifacts = new WeakMap<object, string>();
1109
+ const portableProviderPluginAdapters = new WeakMap<
1110
+ ProviderPluginBindingV1,
1111
+ { readonly adapterId: string; readonly manifest: GhostgetManifest }
1112
+ >();
1113
+ const portableProviderPluginOperationIdentities = new WeakMap<
1114
+ ProviderPluginBindingV1,
1115
+ ReadonlyMap<string, PortableOperationIdentityV1>
1116
+ >();
1117
+ const portableProviderPluginSubjectProbeIdentities = new WeakMap<
1118
+ ProviderPluginBindingV1,
1119
+ PortableOperationIdentityV1
1120
+ >();
1121
+ const providerPluginEvaluationSourceDigests = new WeakMap<
1122
+ ProviderPluginV1,
1123
+ ReadonlyMap<string, string>
1124
+ >();
1125
+ const providerPluginEvaluationInstalledPackageDigests = new WeakMap<
1126
+ ProviderPluginV1,
1127
+ ReadonlyMap<string, string>
1128
+ >();
1129
+ const providerPluginSourceRoot = realpathSync(fileURLToPath(new URL(".", import.meta.url)));
1130
+
1131
+ function recordValue(value: unknown): value is Readonly<Record<string, unknown>> {
1132
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1133
+ }
1134
+
1135
+ function packageUsesWorkspaceDependencies(packageRoot: string): boolean {
1136
+ let value: unknown;
1137
+ try {
1138
+ value = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8")) as unknown;
1139
+ } catch {
1140
+ return false;
1141
+ }
1142
+ if (!recordValue(value)) return false;
1143
+ const protocol = ["workspace", ":"].join("");
1144
+ return ["dependencies", "devDependencies", "optionalDependencies"]
1145
+ .some((field) => {
1146
+ const dependencies = value[field];
1147
+ return recordValue(dependencies)
1148
+ && Object.values(dependencies).some(
1149
+ (specifier) => typeof specifier === "string" && specifier.startsWith(protocol),
1150
+ );
1151
+ });
1152
+ }
1153
+
1154
+ function enclosingWorkspaceRoot(packageRoot: string): string {
1155
+ if (!packageUsesWorkspaceDependencies(packageRoot)) return packageRoot;
1156
+ let candidate = dirname(packageRoot);
1157
+ for (;;) {
1158
+ const manifestPath = resolve(candidate, "package.json");
1159
+ if (existsSync(manifestPath)) {
1160
+ try {
1161
+ const value = JSON.parse(readFileSync(manifestPath, "utf8")) as unknown;
1162
+ if (
1163
+ recordValue(value)
1164
+ && (Array.isArray(value.workspaces) || recordValue(value.workspaces))
1165
+ ) {
1166
+ return realpathSync(candidate);
1167
+ }
1168
+ } catch {
1169
+ // A malformed ancestor manifest cannot define the trusted boundary.
1170
+ }
1171
+ }
1172
+ const parent = dirname(candidate);
1173
+ if (parent === candidate) {
1174
+ throw new Error("Ghostget workspace dependencies have no enclosing workspace root");
1175
+ }
1176
+ candidate = parent;
1177
+ }
1178
+ }
1179
+
1180
+ function isWithinProviderPluginPhysicalRoot(
1181
+ root: string,
1182
+ candidate: string,
1183
+ ): boolean {
1184
+ const path = relative(root, candidate);
1185
+ return path === ""
1186
+ || (path !== ".." && !path.startsWith(`..${sep}`));
1187
+ }
1188
+
1189
+ type PhysicalNodeModulesPackage = {
1190
+ readonly nodeModulesDirectory: string;
1191
+ readonly root: string;
1192
+ };
1193
+
1194
+ function physicalNodeModulesPackage(
1195
+ entryPath: string,
1196
+ ): PhysicalNodeModulesPackage | undefined {
1197
+ const parsed = parse(entryPath);
1198
+ const segments = entryPath.slice(parsed.root.length).split(sep);
1199
+ for (let index = segments.length - 1; index >= 0; index -= 1) {
1200
+ if (segments[index] !== "node_modules") continue;
1201
+ const first = segments[index + 1];
1202
+ if (first === undefined || first === "") continue;
1203
+ const packageSegments = first.startsWith("@")
1204
+ ? (
1205
+ segments[index + 2] === undefined
1206
+ ? undefined
1207
+ : [first, segments[index + 2]!]
1208
+ )
1209
+ : [first];
1210
+ if (packageSegments === undefined) continue;
1211
+ const root = resolve(
1212
+ parsed.root,
1213
+ ...segments.slice(0, index + 1 + packageSegments.length),
1214
+ );
1215
+ if (
1216
+ isWithinProviderPluginPhysicalRoot(root, entryPath)
1217
+ && existsSync(resolve(root, "package.json"))
1218
+ ) {
1219
+ return Object.freeze({
1220
+ nodeModulesDirectory: resolve(
1221
+ parsed.root,
1222
+ ...segments.slice(0, index + 1),
1223
+ ),
1224
+ root,
1225
+ });
1226
+ }
1227
+ }
1228
+ return undefined;
1229
+ }
1230
+
1231
+ function enclosingNodeModulesBoundary(packageRoot: string): string | undefined {
1232
+ const owner = physicalNodeModulesPackage(packageRoot);
1233
+ if (owner === undefined || owner.root !== packageRoot) return undefined;
1234
+ const bunInstanceDirectory = dirname(owner.nodeModulesDirectory);
1235
+ const bunStoreDirectory = dirname(bunInstanceDirectory);
1236
+ const bunInstallNodeModules = dirname(bunStoreDirectory);
1237
+ return basename(bunStoreDirectory) === ".bun"
1238
+ && basename(bunInstallNodeModules) === "node_modules"
1239
+ ? bunInstallNodeModules
1240
+ : owner.nodeModulesDirectory;
1241
+ }
1242
+
1243
+ /** Checked source boundary: package root standalone, enclosing workspace in development. */
1244
+ export const providerPluginPackageRoot = realpathSync(
1245
+ resolve(providerPluginSourceRoot, ".."),
1246
+ );
1247
+ export const providerPluginRepositoryRoot = enclosingWorkspaceRoot(
1248
+ providerPluginPackageRoot,
1249
+ );
1250
+ const providerPluginInstallationNodeModulesBoundary =
1251
+ enclosingNodeModulesBoundary(providerPluginPackageRoot);
1252
+
1253
+ type ProviderPluginPhysicalPath =
1254
+ | { readonly kind: "repository" }
1255
+ | { readonly kind: "installed-package"; readonly root: string };
1256
+
1257
+ export function isProviderPluginRepositorySourcePath(
1258
+ repositoryRoot: string,
1259
+ path: string,
1260
+ ): boolean {
1261
+ const repositoryRelative = relative(repositoryRoot, path);
1262
+ return (
1263
+ repositoryRelative === ""
1264
+ || (
1265
+ repositoryRelative !== ".."
1266
+ && !repositoryRelative.startsWith(`..${sep}`)
1267
+ )
1268
+ )
1269
+ && (
1270
+ repositoryRelative === ""
1271
+ || !repositoryRelative.split(sep).includes("node_modules")
1272
+ );
1273
+ }
1274
+
1275
+ /**
1276
+ * Classify a real module path without confusing an installed Ghostget package's
1277
+ * own outer `node_modules` segments with one of its dependencies. Standalone
1278
+ * dependencies may be hoisted beside Ghostget, while development dependencies
1279
+ * remain constrained to the checked repository.
1280
+ */
1281
+ export function classifyProviderPluginPhysicalPath(
1282
+ path: string,
1283
+ ): ProviderPluginPhysicalPath {
1284
+ const repositoryRelative = relative(providerPluginRepositoryRoot, path);
1285
+ const withinRepository = repositoryRelative === ""
1286
+ || (
1287
+ repositoryRelative !== ".."
1288
+ && !repositoryRelative.startsWith(`..${sep}`)
1289
+ );
1290
+ if (isProviderPluginRepositorySourcePath(
1291
+ providerPluginRepositoryRoot,
1292
+ path,
1293
+ )) {
1294
+ return Object.freeze({ kind: "repository" });
1295
+ }
1296
+ if (
1297
+ !withinRepository
1298
+ && (
1299
+ providerPluginInstallationNodeModulesBoundary === undefined
1300
+ || !isWithinProviderPluginPhysicalRoot(
1301
+ providerPluginInstallationNodeModulesBoundary,
1302
+ path,
1303
+ )
1304
+ )
1305
+ ) {
1306
+ throw new Error(
1307
+ "provider plugin physical path resolves outside the repository and checked package installation",
1308
+ );
1309
+ }
1310
+ const owner = physicalNodeModulesPackage(path);
1311
+ if (
1312
+ owner === undefined
1313
+ || (
1314
+ providerPluginInstallationNodeModulesBoundary !== undefined
1315
+ && !withinRepository
1316
+ && !isWithinProviderPluginPhysicalRoot(
1317
+ providerPluginInstallationNodeModulesBoundary,
1318
+ owner.root,
1319
+ )
1320
+ )
1321
+ ) {
1322
+ throw new Error(
1323
+ "provider plugin physical path has no physical node_modules owner",
1324
+ );
1325
+ }
1326
+ return Object.freeze({ kind: "installed-package", root: owner.root });
1327
+ }
1328
+ const providerPluginApiBoundaryPath = realpathSync(
1329
+ resolve(providerPluginSourceRoot, "provider-plugin.ts"),
1330
+ );
1331
+ const MAX_PROVIDER_PLUGIN_EVALUATION_SOURCE_BYTES = 16 * 1024 * 1024;
1332
+ const MAX_PROVIDER_PLUGIN_EVALUATION_CLOSURE_SOURCES = 2_000;
1333
+ const MAX_PROVIDER_PLUGIN_EVALUATION_CLOSURE_BYTES = 128 * 1024 * 1024;
1334
+ const MAX_PROVIDER_PLUGIN_EVALUATION_IMPORTS_PER_MODULE = 4_096;
1335
+ const MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGES = 128;
1336
+ const MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_FILES = 4_096;
1337
+ const MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_DIRECTORIES = 1_024;
1338
+ const MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_DEPTH = 32;
1339
+ const MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_PATH_BYTES = 1_024;
1340
+ const MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_BYTES = 128 * 1024 * 1024;
1341
+ const MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_CACHE_ENTRIES = 256;
1342
+ const providerPluginEvaluationImportScanners = Object.freeze({
1343
+ js: new Bun.Transpiler({ loader: "js" }),
1344
+ ts: new Bun.Transpiler({ loader: "ts" }),
1345
+ });
1346
+ const providerPluginEvaluationModuleExtensions = new Set([
1347
+ ".cjs",
1348
+ ".cts",
1349
+ ".js",
1350
+ ".mjs",
1351
+ ".mts",
1352
+ ".ts",
1353
+ ]);
1354
+ const providerPluginEvaluationRuntimeBuiltins = new Set(
1355
+ builtinModules.flatMap((name) =>
1356
+ name.startsWith("node:") ? [name, name.slice("node:".length)] : [name]),
1357
+ );
1358
+
1359
+ type ProviderPluginEvaluationPackageTreeEntry =
1360
+ | {
1361
+ readonly path: string;
1362
+ readonly kind: "directory" | "guard-directory" | "file";
1363
+ readonly stats: BigIntStats;
1364
+ }
1365
+ | {
1366
+ readonly path: string;
1367
+ readonly kind: "link";
1368
+ readonly stats: BigIntStats;
1369
+ readonly targetPath: string;
1370
+ };
1371
+
1372
+ type ProviderPluginEvaluationPackageTreeWalk = {
1373
+ readonly entries: readonly ProviderPluginEvaluationPackageTreeEntry[];
1374
+ readonly fileCount: number;
1375
+ readonly linkCount: number;
1376
+ readonly directoryCount: number;
1377
+ readonly totalBytes: number;
1378
+ };
1379
+
1380
+ type ProviderPluginEvaluationPackageSnapshot = {
1381
+ readonly treeSha256: string;
1382
+ readonly totalBytes: number;
1383
+ readonly verificationWalk: ProviderPluginEvaluationPackageTreeWalk;
1384
+ };
1385
+
1386
+ const providerPluginEvaluationPackageCache =
1387
+ new Map<string, ProviderPluginEvaluationPackageSnapshot>();
1388
+
1389
+ function sameEvaluationSourceSnapshot(
1390
+ left: BigIntStats,
1391
+ right: BigIntStats,
1392
+ ): boolean {
1393
+ return left.dev === right.dev
1394
+ && left.ino === right.ino
1395
+ && left.size === right.size
1396
+ && left.mode === right.mode
1397
+ && left.mtimeNs === right.mtimeNs
1398
+ && left.ctimeNs === right.ctimeNs;
1399
+ }
1400
+
1401
+ export function isProviderPluginInstalledBinaryDirectory(
1402
+ relativeDirectory: string,
1403
+ ): boolean {
1404
+ return relativeDirectory === "node_modules/.bin"
1405
+ || relativeDirectory.endsWith("/node_modules/.bin");
1406
+ }
1407
+
1408
+ export type ProviderPluginInstalledBinaryLinkTarget = Readonly<{
1409
+ path: string;
1410
+ stats: BigIntStats;
1411
+ }>;
1412
+
1413
+ /**
1414
+ * Bind a directory that is security-relevant to an installed binary target
1415
+ * without making installer topology part of the package's durable identity.
1416
+ */
1417
+ export function providerPluginInstalledGuardDirectorySnapshot(
1418
+ path: string,
1419
+ label: string,
1420
+ ): BigIntStats {
1421
+ let before: BigIntStats;
1422
+ try {
1423
+ before = lstatSync(path, { bigint: true });
1424
+ } catch {
1425
+ throw new Error(`${label} changed while it was bound`);
1426
+ }
1427
+ const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
1428
+ if (
1429
+ before.isSymbolicLink()
1430
+ || !before.isDirectory()
1431
+ || (uid !== undefined && before.uid !== BigInt(uid))
1432
+ || (before.mode & 0o022n) !== 0n
1433
+ ) {
1434
+ throw new Error(`${label} is unsafe`);
1435
+ }
1436
+ let descriptor: number;
1437
+ try {
1438
+ descriptor = openSync(
1439
+ path,
1440
+ constants.O_RDONLY
1441
+ | ("O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0)
1442
+ | ("O_DIRECTORY" in constants ? constants.O_DIRECTORY : 0),
1443
+ );
1444
+ } catch {
1445
+ throw new Error(`${label} changed while it was bound`);
1446
+ }
1447
+ try {
1448
+ const bound = fstatSync(descriptor, { bigint: true });
1449
+ let current: BigIntStats;
1450
+ try {
1451
+ current = lstatSync(path, { bigint: true });
1452
+ } catch {
1453
+ throw new Error(`${label} changed while it was bound`);
1454
+ }
1455
+ if (
1456
+ current.isSymbolicLink()
1457
+ || !current.isDirectory()
1458
+ || !sameEvaluationSourceSnapshot(before, bound)
1459
+ || !sameEvaluationSourceSnapshot(bound, current)
1460
+ ) {
1461
+ throw new Error(`${label} changed while it was bound`);
1462
+ }
1463
+ return before;
1464
+ } finally {
1465
+ closeSync(descriptor);
1466
+ }
1467
+ }
1468
+
1469
+ /**
1470
+ * Bind an installer-generated npm binary link to one canonical regular file
1471
+ * inside the same captured package tree. Wrapper files remain ordinary files.
1472
+ */
1473
+ export function providerPluginCanonicalInstalledBinaryLinkTarget(
1474
+ root: string,
1475
+ linkPath: string,
1476
+ initialStats: BigIntStats,
1477
+ maximumPathBytes: number,
1478
+ label: string,
1479
+ ): ProviderPluginInstalledBinaryLinkTarget {
1480
+ const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
1481
+ if (
1482
+ !initialStats.isSymbolicLink()
1483
+ || (uid !== undefined && initialStats.uid !== BigInt(uid))
1484
+ ) {
1485
+ throw new Error(`${label} is not an owned symbolic link`);
1486
+ }
1487
+ let firstTargetBytes: Buffer;
1488
+ let secondTargetBytes: Buffer;
1489
+ let middleStats: BigIntStats;
1490
+ let finalStats: BigIntStats;
1491
+ try {
1492
+ firstTargetBytes = readlinkSync(linkPath, { encoding: "buffer" });
1493
+ middleStats = lstatSync(linkPath, { bigint: true });
1494
+ secondTargetBytes = readlinkSync(linkPath, { encoding: "buffer" });
1495
+ finalStats = lstatSync(linkPath, { bigint: true });
1496
+ } catch {
1497
+ throw new Error(`${label} changed while it was read`);
1498
+ }
1499
+ if (
1500
+ firstTargetBytes.byteLength < 1
1501
+ || firstTargetBytes.byteLength > maximumPathBytes
1502
+ || !firstTargetBytes.equals(secondTargetBytes)
1503
+ || !middleStats.isSymbolicLink()
1504
+ || !finalStats.isSymbolicLink()
1505
+ || !sameEvaluationSourceSnapshot(initialStats, middleStats)
1506
+ || !sameEvaluationSourceSnapshot(middleStats, finalStats)
1507
+ ) {
1508
+ throw new Error(`${label} changed while it was read`);
1509
+ }
1510
+ let targetText: string;
1511
+ try {
1512
+ targetText = new TextDecoder("utf-8", { fatal: true }).decode(
1513
+ firstTargetBytes,
1514
+ );
1515
+ } catch {
1516
+ throw new Error(`${label} has invalid target text`);
1517
+ }
1518
+ if (
1519
+ targetText.includes("\u0000")
1520
+ || targetText.includes("\\")
1521
+ || isAbsolute(targetText)
1522
+ ) {
1523
+ throw new Error(`${label} has an unsafe target`);
1524
+ }
1525
+ const lexicalTarget = resolve(dirname(linkPath), targetText);
1526
+ const targetPath = relative(root, lexicalTarget).split(sep).join("/");
1527
+ const canonicalTargetText = relative(dirname(linkPath), lexicalTarget)
1528
+ .split(sep).join("/");
1529
+ if (
1530
+ targetText !== canonicalTargetText
1531
+ || targetPath === ""
1532
+ || targetPath === "."
1533
+ || targetPath.includes("\\")
1534
+ || targetPath.includes("\u0000")
1535
+ || Buffer.byteLength(targetPath, "utf8") > maximumPathBytes
1536
+ || !isWithinProviderPluginPhysicalRoot(root, lexicalTarget)
1537
+ ) {
1538
+ throw new Error(`${label} has a noncanonical or escaping target`);
1539
+ }
1540
+ let physicalTarget: string;
1541
+ let targetStats: BigIntStats;
1542
+ try {
1543
+ physicalTarget = realpathSync(linkPath);
1544
+ targetStats = lstatSync(lexicalTarget, { bigint: true });
1545
+ } catch {
1546
+ throw new Error(`${label} target is missing`);
1547
+ }
1548
+ if (
1549
+ physicalTarget !== lexicalTarget
1550
+ || !targetStats.isFile()
1551
+ || targetStats.isSymbolicLink()
1552
+ || (uid !== undefined && targetStats.uid !== BigInt(uid))
1553
+ ) {
1554
+ throw new Error(`${label} target is not one canonical owned regular file`);
1555
+ }
1556
+ return Object.freeze({ path: targetPath, stats: targetStats });
1557
+ }
1558
+
1559
+ function readOwnedBoundedProviderPluginEvaluationFile(
1560
+ path: string,
1561
+ maximumBytes: number,
1562
+ label: string,
1563
+ ): Buffer {
1564
+ let descriptor: number;
1565
+ try {
1566
+ descriptor = openSync(
1567
+ path,
1568
+ constants.O_RDONLY
1569
+ | ("O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0)
1570
+ | ("O_NONBLOCK" in constants ? constants.O_NONBLOCK : 0),
1571
+ );
1572
+ } catch {
1573
+ throw new Error(`${label} is unreadable`);
1574
+ }
1575
+ try {
1576
+ const before = fstatSync(descriptor, { bigint: true });
1577
+ const uid =
1578
+ typeof process.getuid === "function" ? process.getuid() : undefined;
1579
+ if (
1580
+ !before.isFile()
1581
+ || (uid !== undefined && before.uid !== BigInt(uid))
1582
+ || before.size < 0n
1583
+ || before.size > BigInt(maximumBytes)
1584
+ ) {
1585
+ throw new Error(`${label} is not an owned bounded regular file`);
1586
+ }
1587
+ const bytes = Buffer.alloc(Number(before.size));
1588
+ let offset = 0;
1589
+ while (offset < bytes.byteLength) {
1590
+ const count = readSync(
1591
+ descriptor,
1592
+ bytes,
1593
+ offset,
1594
+ bytes.byteLength - offset,
1595
+ offset,
1596
+ );
1597
+ if (count === 0) break;
1598
+ offset += count;
1599
+ }
1600
+ const after = fstatSync(descriptor, { bigint: true });
1601
+ let current: BigIntStats;
1602
+ try {
1603
+ current = lstatSync(path, { bigint: true });
1604
+ } catch {
1605
+ throw new Error(`${label} changed while its definition was evaluated`);
1606
+ }
1607
+ if (
1608
+ offset !== bytes.byteLength
1609
+ || current.isSymbolicLink()
1610
+ || !current.isFile()
1611
+ || !sameEvaluationSourceSnapshot(before, after)
1612
+ || !sameEvaluationSourceSnapshot(after, current)
1613
+ ) {
1614
+ throw new Error(`${label} changed while its definition was evaluated`);
1615
+ }
1616
+ return bytes;
1617
+ } finally {
1618
+ closeSync(descriptor);
1619
+ }
1620
+ }
1621
+
1622
+ function readProviderPluginEvaluationSource(
1623
+ pluginId: string,
1624
+ path: string,
1625
+ label: string,
1626
+ ): Buffer {
1627
+ return readOwnedBoundedProviderPluginEvaluationFile(
1628
+ path,
1629
+ MAX_PROVIDER_PLUGIN_EVALUATION_SOURCE_BYTES,
1630
+ `provider plugin ${pluginId} implementation source ${label}`,
1631
+ );
1632
+ }
1633
+
1634
+ function compareProviderPluginEvaluationText(
1635
+ left: string,
1636
+ right: string,
1637
+ ): number {
1638
+ return Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8"));
1639
+ }
1640
+
1641
+ function walkProviderPluginEvaluationPackageTree(
1642
+ root: string,
1643
+ ): ProviderPluginEvaluationPackageTreeWalk {
1644
+ const entries: ProviderPluginEvaluationPackageTreeEntry[] = [];
1645
+ const capturedFiles = new Map<string, BigIntStats>();
1646
+ const capturedGuardDirectories = new Map<string, BigIntStats>();
1647
+ let fileCount = 0;
1648
+ let linkCount = 0;
1649
+ let directoryCount = 0;
1650
+ let totalBytes = 0;
1651
+ const captureFile = (
1652
+ relativePath: string,
1653
+ stats: BigIntStats,
1654
+ ): void => {
1655
+ const existing = capturedFiles.get(relativePath);
1656
+ if (existing !== undefined) {
1657
+ if (!sameEvaluationSourceSnapshot(existing, stats)) {
1658
+ throw new Error(
1659
+ `provider plugin evaluation package file ${relativePath} changed while it was walked`,
1660
+ );
1661
+ }
1662
+ return;
1663
+ }
1664
+ const uid =
1665
+ typeof process.getuid === "function" ? process.getuid() : undefined;
1666
+ if (
1667
+ !stats.isFile()
1668
+ || stats.isSymbolicLink()
1669
+ || (uid !== undefined && stats.uid !== BigInt(uid))
1670
+ ) {
1671
+ throw new Error(
1672
+ `provider plugin evaluation package tree contains unsupported entry ${relativePath}`,
1673
+ );
1674
+ }
1675
+ fileCount += 1;
1676
+ if (
1677
+ fileCount + linkCount
1678
+ > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_FILES
1679
+ ) {
1680
+ throw new Error(
1681
+ "provider plugin evaluation package tree exceeds its file bound",
1682
+ );
1683
+ }
1684
+ if (
1685
+ stats.size < 0n
1686
+ || stats.size
1687
+ > BigInt(MAX_PROVIDER_PLUGIN_EVALUATION_SOURCE_BYTES)
1688
+ ) {
1689
+ throw new Error(
1690
+ `provider plugin evaluation package file ${relativePath} exceeds its byte bound`,
1691
+ );
1692
+ }
1693
+ totalBytes += Number(stats.size);
1694
+ if (totalBytes > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_BYTES) {
1695
+ throw new Error(
1696
+ "provider plugin evaluation package tree exceeds its total byte bound",
1697
+ );
1698
+ }
1699
+ capturedFiles.set(relativePath, stats);
1700
+ entries.push(Object.freeze({
1701
+ path: relativePath,
1702
+ kind: "file",
1703
+ stats,
1704
+ }));
1705
+ };
1706
+ const captureGuardDirectory = (relativePath: string): void => {
1707
+ const stats = providerPluginInstalledGuardDirectorySnapshot(
1708
+ resolve(root, relativePath),
1709
+ `provider plugin evaluation package directory ${relativePath}`,
1710
+ );
1711
+ const existing = capturedGuardDirectories.get(relativePath);
1712
+ if (existing !== undefined) {
1713
+ if (!sameEvaluationSourceSnapshot(existing, stats)) {
1714
+ throw new Error(
1715
+ `provider plugin evaluation installed binary directory ${relativePath} changed while it was walked`,
1716
+ );
1717
+ }
1718
+ return;
1719
+ }
1720
+ directoryCount += 1;
1721
+ if (
1722
+ directoryCount
1723
+ > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_DIRECTORIES
1724
+ ) {
1725
+ throw new Error(
1726
+ "provider plugin evaluation package tree exceeds its directory bound",
1727
+ );
1728
+ }
1729
+ capturedGuardDirectories.set(relativePath, stats);
1730
+ entries.push(Object.freeze({
1731
+ path: relativePath,
1732
+ kind: "guard-directory",
1733
+ stats,
1734
+ }));
1735
+ };
1736
+ const captureTargetGuardDirectories = (targetPath: string): void => {
1737
+ const parent = dirname(targetPath).split(sep).join("/");
1738
+ if (parent === ".") return;
1739
+ const segments = parent.split("/");
1740
+ if (segments.length > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_DEPTH) {
1741
+ throw new Error(
1742
+ "provider plugin evaluation package tree exceeds its depth bound",
1743
+ );
1744
+ }
1745
+ for (let index = 1; index <= segments.length; index += 1) {
1746
+ captureGuardDirectory(segments.slice(0, index).join("/"));
1747
+ }
1748
+ };
1749
+ const visit = (
1750
+ directoryPath: string,
1751
+ relativeDirectory: string,
1752
+ depth: number,
1753
+ ): void => {
1754
+ if (depth > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_DEPTH) {
1755
+ throw new Error("provider plugin evaluation package tree exceeds its depth bound");
1756
+ }
1757
+ let before: BigIntStats;
1758
+ try {
1759
+ before = lstatSync(directoryPath, { bigint: true });
1760
+ } catch {
1761
+ throw new Error(
1762
+ `provider plugin evaluation package directory ${relativeDirectory || "."} changed while it was walked`,
1763
+ );
1764
+ }
1765
+ const uid =
1766
+ typeof process.getuid === "function" ? process.getuid() : undefined;
1767
+ if (
1768
+ before.isSymbolicLink()
1769
+ || !before.isDirectory()
1770
+ || (uid !== undefined && before.uid !== BigInt(uid))
1771
+ || (before.mode & 0o022n) !== 0n
1772
+ ) {
1773
+ throw new Error(
1774
+ `provider plugin evaluation package directory ${relativeDirectory || "."} is unsafe`,
1775
+ );
1776
+ }
1777
+ directoryCount += 1;
1778
+ if (
1779
+ directoryCount
1780
+ > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_DIRECTORIES
1781
+ ) {
1782
+ throw new Error(
1783
+ "provider plugin evaluation package tree exceeds its directory bound",
1784
+ );
1785
+ }
1786
+ entries.push(Object.freeze({
1787
+ path: relativeDirectory === "" ? "." : relativeDirectory,
1788
+ kind: "directory",
1789
+ stats: before,
1790
+ }));
1791
+ const descriptor = openSync(
1792
+ directoryPath,
1793
+ constants.O_RDONLY
1794
+ | ("O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0)
1795
+ | ("O_DIRECTORY" in constants ? constants.O_DIRECTORY : 0),
1796
+ );
1797
+ try {
1798
+ const bound = fstatSync(descriptor, { bigint: true });
1799
+ if (!bound.isDirectory() || !sameEvaluationSourceSnapshot(before, bound)) {
1800
+ throw new Error(
1801
+ `provider plugin evaluation package directory ${relativeDirectory || "."} changed while it was bound`,
1802
+ );
1803
+ }
1804
+ const names: string[] = [];
1805
+ const directory = opendirSync(directoryPath);
1806
+ try {
1807
+ for (;;) {
1808
+ const entry = directory.readSync();
1809
+ if (entry === null) break;
1810
+ names.push(entry.name);
1811
+ if (
1812
+ names.length
1813
+ > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_FILES
1814
+ + MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_DIRECTORIES
1815
+ || entries.length + names.length
1816
+ > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_FILES
1817
+ + MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_DIRECTORIES
1818
+ ) {
1819
+ throw new Error(
1820
+ "provider plugin evaluation package tree exceeds its entry bound",
1821
+ );
1822
+ }
1823
+ }
1824
+ } finally {
1825
+ directory.closeSync();
1826
+ }
1827
+ names.sort(compareProviderPluginEvaluationText);
1828
+ for (const name of names) {
1829
+ const relativePath = relativeDirectory === ""
1830
+ ? name
1831
+ : `${relativeDirectory}/${name}`;
1832
+ if (
1833
+ name === "."
1834
+ || name === ".."
1835
+ || name.includes("/")
1836
+ || relativePath.includes("\\")
1837
+ || relativePath.includes("\u0000")
1838
+ || Buffer.byteLength(relativePath, "utf8")
1839
+ > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_PATH_BYTES
1840
+ ) {
1841
+ throw new Error(
1842
+ "provider plugin evaluation package tree contains an unsafe path",
1843
+ );
1844
+ }
1845
+ const path = resolve(directoryPath, name);
1846
+ let stats: BigIntStats;
1847
+ try {
1848
+ stats = lstatSync(path, { bigint: true });
1849
+ } catch {
1850
+ throw new Error(
1851
+ `provider plugin evaluation package entry ${relativePath} changed while it was walked`,
1852
+ );
1853
+ }
1854
+ if (stats.isSymbolicLink()) {
1855
+ if (!isProviderPluginInstalledBinaryDirectory(relativeDirectory)) {
1856
+ throw new Error(
1857
+ `provider plugin evaluation package tree contains symlink ${relativePath}`,
1858
+ );
1859
+ }
1860
+ linkCount += 1;
1861
+ if (
1862
+ fileCount + linkCount
1863
+ > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_FILES
1864
+ ) {
1865
+ throw new Error(
1866
+ "provider plugin evaluation package tree exceeds its file bound",
1867
+ );
1868
+ }
1869
+ const target = providerPluginCanonicalInstalledBinaryLinkTarget(
1870
+ root,
1871
+ path,
1872
+ stats,
1873
+ MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_PATH_BYTES,
1874
+ `provider plugin evaluation npm binary link ${relativePath}`,
1875
+ );
1876
+ totalBytes += Buffer.byteLength(target.path, "utf8");
1877
+ if (totalBytes > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_BYTES) {
1878
+ throw new Error(
1879
+ "provider plugin evaluation package tree exceeds its total byte bound",
1880
+ );
1881
+ }
1882
+ entries.push(Object.freeze({
1883
+ path: relativePath,
1884
+ kind: "link",
1885
+ stats,
1886
+ targetPath: target.path,
1887
+ }));
1888
+ captureTargetGuardDirectories(target.path);
1889
+ captureFile(target.path, target.stats);
1890
+ continue;
1891
+ }
1892
+ if (stats.isDirectory()) {
1893
+ if (isProviderPluginInstalledBinaryDirectory(relativeDirectory)) {
1894
+ throw new Error(
1895
+ `provider plugin evaluation package tree contains unsupported entry ${relativePath}`,
1896
+ );
1897
+ }
1898
+ // Only installer-owned binary shims and their canonical regular-file
1899
+ // targets contribute from nested node_modules. Statically imported
1900
+ // packages are snapshotted independently below.
1901
+ if (name === "node_modules") {
1902
+ const binaryDirectory = resolve(path, ".bin");
1903
+ const binaryStats = lstatSync(binaryDirectory, {
1904
+ bigint: true,
1905
+ throwIfNoEntry: false,
1906
+ });
1907
+ if (binaryStats !== undefined) {
1908
+ captureGuardDirectory(relativePath);
1909
+ visit(
1910
+ binaryDirectory,
1911
+ `${relativePath}/.bin`,
1912
+ depth + 2,
1913
+ );
1914
+ }
1915
+ continue;
1916
+ }
1917
+ visit(path, relativePath, depth + 1);
1918
+ continue;
1919
+ }
1920
+ captureFile(relativePath, stats);
1921
+ }
1922
+ const after = fstatSync(descriptor, { bigint: true });
1923
+ let current: BigIntStats;
1924
+ try {
1925
+ current = lstatSync(directoryPath, { bigint: true });
1926
+ } catch {
1927
+ throw new Error(
1928
+ `provider plugin evaluation package directory ${relativeDirectory || "."} changed while it was walked`,
1929
+ );
1930
+ }
1931
+ if (
1932
+ current.isSymbolicLink()
1933
+ || !current.isDirectory()
1934
+ || !sameEvaluationSourceSnapshot(before, after)
1935
+ || !sameEvaluationSourceSnapshot(after, current)
1936
+ ) {
1937
+ throw new Error(
1938
+ `provider plugin evaluation package directory ${relativeDirectory || "."} changed while it was walked`,
1939
+ );
1940
+ }
1941
+ } finally {
1942
+ closeSync(descriptor);
1943
+ }
1944
+ };
1945
+ visit(root, "", 0);
1946
+ entries.sort((left, right) =>
1947
+ compareProviderPluginEvaluationText(left.path, right.path)
1948
+ || compareProviderPluginEvaluationText(left.kind, right.kind));
1949
+ return Object.freeze({
1950
+ entries: Object.freeze(entries),
1951
+ fileCount,
1952
+ linkCount,
1953
+ directoryCount,
1954
+ totalBytes,
1955
+ });
1956
+ }
1957
+
1958
+ function sameProviderPluginEvaluationPackageTree(
1959
+ left: ProviderPluginEvaluationPackageTreeWalk,
1960
+ right: ProviderPluginEvaluationPackageTreeWalk,
1961
+ ): boolean {
1962
+ return left.fileCount === right.fileCount
1963
+ && left.linkCount === right.linkCount
1964
+ && left.directoryCount === right.directoryCount
1965
+ && left.totalBytes === right.totalBytes
1966
+ && left.entries.length === right.entries.length
1967
+ && left.entries.every((entry, index) => {
1968
+ const current = right.entries[index];
1969
+ return current !== undefined
1970
+ && entry.path === current.path
1971
+ && entry.kind === current.kind
1972
+ && (
1973
+ entry.kind !== "link"
1974
+ || (current.kind === "link" && entry.targetPath === current.targetPath)
1975
+ )
1976
+ && sameEvaluationSourceSnapshot(entry.stats, current.stats);
1977
+ });
1978
+ }
1979
+
1980
+ function updateProviderPluginEvaluationTreeHash(
1981
+ hash: ReturnType<typeof createHash>,
1982
+ label: string,
1983
+ bytes: Buffer,
1984
+ ): void {
1985
+ const labelBytes = Buffer.from(label, "utf8");
1986
+ const lengths = Buffer.alloc(12);
1987
+ lengths.writeUInt32BE(labelBytes.byteLength, 0);
1988
+ lengths.writeBigUInt64BE(BigInt(bytes.byteLength), 4);
1989
+ hash.update(lengths).update(labelBytes).update(bytes);
1990
+ }
1991
+
1992
+ /**
1993
+ * Canonical extraction mode for relocation-stable installed package identity.
1994
+ * Exact observed modes stay in stat snapshots so TOCTOU checks remain exact.
1995
+ */
1996
+ export function providerPluginDurableInstalledPackageMode(
1997
+ kind: "directory" | "file",
1998
+ mode: number,
1999
+ ): 0o644 | 0o755 {
2000
+ if (kind === "directory") return 0o755;
2001
+ return (mode & 0o111) === 0 ? 0o644 : 0o755;
2002
+ }
2003
+
2004
+ function snapshotProviderPluginEvaluationPackage(
2005
+ root: string,
2006
+ ): ProviderPluginEvaluationPackageSnapshot {
2007
+ const firstWalk = walkProviderPluginEvaluationPackageTree(root);
2008
+ const cached = providerPluginEvaluationPackageCache.get(root);
2009
+ if (
2010
+ cached !== undefined
2011
+ && sameProviderPluginEvaluationPackageTree(
2012
+ cached.verificationWalk,
2013
+ firstWalk,
2014
+ )
2015
+ ) return cached;
2016
+
2017
+ const files: {
2018
+ readonly path: string;
2019
+ readonly bytes: Buffer;
2020
+ readonly mode: number;
2021
+ }[] = [];
2022
+ const links: { readonly path: string; readonly targetPath: string }[] = [];
2023
+ const directories: { readonly path: string; readonly mode: number }[] = [];
2024
+ for (const entry of firstWalk.entries) {
2025
+ if (entry.kind === "guard-directory") continue;
2026
+ if (entry.kind === "directory") {
2027
+ directories.push(Object.freeze({
2028
+ path: entry.path,
2029
+ mode: Number(entry.stats.mode & 0o777n),
2030
+ }));
2031
+ continue;
2032
+ }
2033
+ if (entry.kind === "link") {
2034
+ links.push(Object.freeze({
2035
+ path: entry.path,
2036
+ targetPath: entry.targetPath,
2037
+ }));
2038
+ continue;
2039
+ }
2040
+ const path = resolve(root, entry.path);
2041
+ const bytes = readOwnedBoundedProviderPluginEvaluationFile(
2042
+ path,
2043
+ MAX_PROVIDER_PLUGIN_EVALUATION_SOURCE_BYTES,
2044
+ `provider plugin evaluation package file ${entry.path}`,
2045
+ );
2046
+ let current: BigIntStats;
2047
+ try {
2048
+ current = lstatSync(path, { bigint: true });
2049
+ } catch {
2050
+ throw new Error(
2051
+ `provider plugin evaluation package file ${entry.path} changed while it was snapshotted`,
2052
+ );
2053
+ }
2054
+ if (
2055
+ bytes.byteLength !== Number(entry.stats.size)
2056
+ || current.isSymbolicLink()
2057
+ || !current.isFile()
2058
+ || !sameEvaluationSourceSnapshot(entry.stats, current)
2059
+ ) {
2060
+ throw new Error(
2061
+ `provider plugin evaluation package file ${entry.path} changed while it was snapshotted`,
2062
+ );
2063
+ }
2064
+ files.push(Object.freeze({
2065
+ path: entry.path,
2066
+ bytes,
2067
+ mode: Number(entry.stats.mode & 0o777n),
2068
+ }));
2069
+ }
2070
+ const filesByPath = new Map(files.map((file) => [file.path, file]));
2071
+ for (const link of links) {
2072
+ if (!filesByPath.has(link.targetPath)) {
2073
+ throw new Error(
2074
+ `provider plugin evaluation npm binary link ${link.path} target is not part of the captured package tree`,
2075
+ );
2076
+ }
2077
+ }
2078
+ const secondWalk = walkProviderPluginEvaluationPackageTree(root);
2079
+ if (!sameProviderPluginEvaluationPackageTree(firstWalk, secondWalk)) {
2080
+ throw new Error(
2081
+ "provider plugin evaluation package tree changed while it was snapshotted",
2082
+ );
2083
+ }
2084
+ const manifest = files.find((file) => file.path === "package.json");
2085
+ let manifestValue: unknown;
2086
+ try {
2087
+ manifestValue = manifest === undefined
2088
+ ? undefined
2089
+ : JSON.parse(
2090
+ new TextDecoder("utf-8", { fatal: true }).decode(manifest.bytes),
2091
+ ) as unknown;
2092
+ } catch {
2093
+ throw new Error(
2094
+ `provider plugin evaluation package manifest ${root} is invalid`,
2095
+ );
2096
+ }
2097
+ if (
2098
+ typeof manifestValue !== "object"
2099
+ || manifestValue === null
2100
+ || Array.isArray(manifestValue)
2101
+ ) {
2102
+ throw new Error(
2103
+ `provider plugin evaluation package manifest ${root} has no identity`,
2104
+ );
2105
+ }
2106
+ const packageName = (manifestValue as Record<string, unknown>).name;
2107
+ const packageVersion = (manifestValue as Record<string, unknown>).version;
2108
+ if (typeof packageName !== "string" || typeof packageVersion !== "string") {
2109
+ throw new Error(
2110
+ `provider plugin evaluation package manifest ${root} has no identity`,
2111
+ );
2112
+ }
2113
+ const treeHash = createHash("sha256");
2114
+ treeHash.update("provider-plugin-installed-package-tree@2\0");
2115
+ updateProviderPluginEvaluationTreeHash(
2116
+ treeHash,
2117
+ "identity",
2118
+ Buffer.from(`${packageName}@${packageVersion}`, "utf8"),
2119
+ );
2120
+ for (const directory of directories) {
2121
+ const durableMode = providerPluginDurableInstalledPackageMode(
2122
+ "directory",
2123
+ directory.mode,
2124
+ );
2125
+ updateProviderPluginEvaluationTreeHash(
2126
+ treeHash,
2127
+ `directory/${directory.path}`,
2128
+ Buffer.from(`mode:${durableMode.toString(8)}`, "utf8"),
2129
+ );
2130
+ }
2131
+ for (const file of files) {
2132
+ const durableMode = providerPluginDurableInstalledPackageMode(
2133
+ "file",
2134
+ file.mode,
2135
+ );
2136
+ updateProviderPluginEvaluationTreeHash(
2137
+ treeHash,
2138
+ `file-mode/${file.path}`,
2139
+ Buffer.from(`mode:${durableMode.toString(8)}`, "utf8"),
2140
+ );
2141
+ updateProviderPluginEvaluationTreeHash(
2142
+ treeHash,
2143
+ `file/${file.path}`,
2144
+ file.bytes,
2145
+ );
2146
+ }
2147
+ for (const link of links) {
2148
+ if (!filesByPath.has(link.targetPath)) {
2149
+ throw new Error(
2150
+ `provider plugin evaluation npm binary link ${link.path} target is unavailable`,
2151
+ );
2152
+ }
2153
+ updateProviderPluginEvaluationTreeHash(
2154
+ treeHash,
2155
+ `link/${link.path}`,
2156
+ Buffer.from(link.targetPath, "utf8"),
2157
+ );
2158
+ }
2159
+ const snapshot = Object.freeze({
2160
+ treeSha256: treeHash.digest("hex"),
2161
+ totalBytes: firstWalk.totalBytes,
2162
+ verificationWalk: secondWalk,
2163
+ });
2164
+ if (
2165
+ !providerPluginEvaluationPackageCache.has(root)
2166
+ && providerPluginEvaluationPackageCache.size
2167
+ >= MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_CACHE_ENTRIES
2168
+ ) {
2169
+ providerPluginEvaluationPackageCache.clear();
2170
+ }
2171
+ providerPluginEvaluationPackageCache.set(root, snapshot);
2172
+ return snapshot;
2173
+ }
2174
+
2175
+ function providerPluginEvaluationValueImports(
2176
+ bytes: Buffer,
2177
+ path: string,
2178
+ ): readonly { readonly kind: string; readonly path: string }[] {
2179
+ const extension = extname(path);
2180
+ if (extension === ".jsx" || extension === ".tsx") {
2181
+ throw new Error(
2182
+ `provider plugin evaluation module ${path} uses configuration-dependent JSX or TSX`,
2183
+ );
2184
+ }
2185
+ if (!providerPluginEvaluationModuleExtensions.has(extension)) {
2186
+ return Object.freeze([]);
2187
+ }
2188
+ let source: string;
2189
+ try {
2190
+ source = new TextDecoder("utf-8", { fatal: true }).decode(bytes)
2191
+ .replace(/^#![^\r\n]*(?:\r?\n|$)/u, "");
2192
+ } catch {
2193
+ throw new Error(
2194
+ `provider plugin evaluation module ${path} must be valid UTF-8`,
2195
+ );
2196
+ }
2197
+ const scanner =
2198
+ extension === ".ts" || extension === ".mts" || extension === ".cts"
2199
+ ? providerPluginEvaluationImportScanners.ts
2200
+ : providerPluginEvaluationImportScanners.js;
2201
+ const imports = Object.freeze([...scanner.scanImports(source)]);
2202
+ if (imports.length > MAX_PROVIDER_PLUGIN_EVALUATION_IMPORTS_PER_MODULE) {
2203
+ throw new Error(
2204
+ `provider plugin evaluation module ${path} has too many static imports`,
2205
+ );
2206
+ }
2207
+ return imports;
2208
+ }
2209
+
2210
+ function resolveProviderPluginEvaluationImport(
2211
+ importerPath: string,
2212
+ specifier: string,
2213
+ importKind: string,
2214
+ ): string | undefined {
2215
+ if (
2216
+ specifier.startsWith("node:")
2217
+ || specifier.startsWith("bun:")
2218
+ || providerPluginEvaluationRuntimeBuiltins.has(specifier)
2219
+ ) return undefined;
2220
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/u.test(specifier)) {
2221
+ throw new Error(
2222
+ `provider plugin evaluation module ${importerPath} imports unsupported absolute or URL dependency ${specifier}`,
2223
+ );
2224
+ }
2225
+ let resolvedPath: string;
2226
+ try {
2227
+ if (isAbsolute(specifier)) {
2228
+ resolvedPath = specifier;
2229
+ } else {
2230
+ resolvedPath = importKind === "require-call"
2231
+ ? createRequire(importerPath).resolve(specifier)
2232
+ : Bun.resolveSync(specifier, dirname(importerPath));
2233
+ }
2234
+ } catch (error) {
2235
+ throw new Error(
2236
+ `provider plugin evaluation module ${importerPath} has unresolved static dependency ${specifier}`,
2237
+ { cause: error },
2238
+ );
2239
+ }
2240
+ if (
2241
+ /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(resolvedPath)
2242
+ || !isAbsolute(resolvedPath)
2243
+ ) {
2244
+ throw new Error(
2245
+ `provider plugin evaluation dependency ${specifier} did not resolve to a local file`,
2246
+ );
2247
+ }
2248
+ let path: string;
2249
+ try {
2250
+ path = realpathSync(resolvedPath);
2251
+ } catch (error) {
2252
+ throw new Error(
2253
+ `provider plugin evaluation dependency ${specifier} resolved to an unreadable path`,
2254
+ { cause: error },
2255
+ );
2256
+ }
2257
+ classifyProviderPluginPhysicalPath(path);
2258
+ if (path === providerPluginApiBoundaryPath) return undefined;
2259
+ return path;
2260
+ }
2261
+
2262
+ function captureProviderPluginEvaluationIdentity(
2263
+ pluginId: string,
2264
+ sources: readonly ProviderPluginImplementationSourceV1[],
2265
+ ): {
2266
+ readonly sourceDigests: ReadonlyMap<string, string>;
2267
+ readonly installedPackageDigests: ReadonlyMap<string, string>;
2268
+ } {
2269
+ const sourceDigests = new Map<string, string>();
2270
+ const sourceBytes = new Map<string, Buffer>();
2271
+ let totalSourceBytes = 0;
2272
+ const captureRepositorySource = (
2273
+ path: string,
2274
+ label: string,
2275
+ ): Buffer => {
2276
+ const existing = sourceBytes.get(path);
2277
+ if (existing !== undefined) return existing;
2278
+ if (
2279
+ sourceDigests.size >= MAX_PROVIDER_PLUGIN_EVALUATION_CLOSURE_SOURCES
2280
+ ) {
2281
+ throw new Error(
2282
+ `provider plugin ${pluginId} evaluation source closure exceeds its file bound`,
2283
+ );
2284
+ }
2285
+ const bytes = readProviderPluginEvaluationSource(pluginId, path, label);
2286
+ if (
2287
+ totalSourceBytes + bytes.byteLength
2288
+ > MAX_PROVIDER_PLUGIN_EVALUATION_CLOSURE_BYTES
2289
+ ) {
2290
+ throw new Error(
2291
+ `provider plugin ${pluginId} evaluation source closure exceeds its byte bound`,
2292
+ );
2293
+ }
2294
+ totalSourceBytes += bytes.byteLength;
2295
+ sourceBytes.set(path, bytes);
2296
+ sourceDigests.set(
2297
+ path,
2298
+ createHash("sha256").update(bytes).digest("hex"),
2299
+ );
2300
+ return bytes;
2301
+ };
2302
+ for (const source of sources) {
2303
+ captureRepositorySource(source.path, source.label);
2304
+ }
2305
+
2306
+ const pluginEntry = sources.find((source) => source.label === "plugin.ts");
2307
+ if (pluginEntry === undefined) {
2308
+ throw new Error(
2309
+ `provider plugin ${pluginId} must bind its plugin.ts implementation source`,
2310
+ );
2311
+ }
2312
+ const pending = [pluginEntry.path];
2313
+ const analyzedModules = new Set<string>();
2314
+ const installedPackageDigests = new Map<string, string>();
2315
+ let installedPackageBytes = 0;
2316
+ while (pending.length > 0) {
2317
+ pending.sort(compareProviderPluginEvaluationText);
2318
+ const path = pending.pop();
2319
+ if (path === undefined || analyzedModules.has(path)) continue;
2320
+ analyzedModules.add(path);
2321
+
2322
+ let bytes: Buffer;
2323
+ const physicalPath = classifyProviderPluginPhysicalPath(path);
2324
+ if (physicalPath.kind === "installed-package") {
2325
+ const root = physicalPath.root;
2326
+ if (!installedPackageDigests.has(root)) {
2327
+ if (
2328
+ installedPackageDigests.size
2329
+ >= MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGES
2330
+ ) {
2331
+ throw new Error(
2332
+ `provider plugin ${pluginId} evaluation package closure exceeds its package bound`,
2333
+ );
2334
+ }
2335
+ const snapshot = snapshotProviderPluginEvaluationPackage(root);
2336
+ if (
2337
+ installedPackageBytes + snapshot.totalBytes
2338
+ > MAX_PROVIDER_PLUGIN_EVALUATION_PACKAGE_BYTES
2339
+ ) {
2340
+ throw new Error(
2341
+ `provider plugin ${pluginId} evaluation package closure exceeds its byte bound`,
2342
+ );
2343
+ }
2344
+ installedPackageBytes += snapshot.totalBytes;
2345
+ installedPackageDigests.set(root, snapshot.treeSha256);
2346
+ }
2347
+ bytes = readOwnedBoundedProviderPluginEvaluationFile(
2348
+ path,
2349
+ MAX_PROVIDER_PLUGIN_EVALUATION_SOURCE_BYTES,
2350
+ `provider plugin ${pluginId} evaluation package module ${relative(providerPluginRepositoryRoot, path)}`,
2351
+ );
2352
+ } else {
2353
+ bytes = captureRepositorySource(
2354
+ path,
2355
+ relative(providerPluginRepositoryRoot, path).split(sep).join("/"),
2356
+ );
2357
+ }
2358
+ for (const dependency of providerPluginEvaluationValueImports(bytes, path)) {
2359
+ // Literal dynamic imports can execute at module top level before the
2360
+ // definition is frozen. Bind them here even when they also sit behind a
2361
+ // branded lazy loader; the later runtime check is intentionally a
2362
+ // second guard, not a substitute for evaluation-time provenance.
2363
+ const child = resolveProviderPluginEvaluationImport(
2364
+ path,
2365
+ dependency.path,
2366
+ dependency.kind,
2367
+ );
2368
+ if (child !== undefined && !analyzedModules.has(child)) {
2369
+ pending.push(child);
2370
+ }
2371
+ }
2372
+ }
2373
+ return Object.freeze({
2374
+ sourceDigests: new Map(
2375
+ [...sourceDigests.entries()].sort(([left], [right]) =>
2376
+ compareProviderPluginEvaluationText(left, right)),
2377
+ ),
2378
+ installedPackageDigests: new Map(
2379
+ [...installedPackageDigests.entries()].sort(([left], [right]) =>
2380
+ compareProviderPluginEvaluationText(left, right)),
2381
+ ),
2382
+ });
2383
+ }
2384
+
2385
+ /** Internal registry guard against forged post-validation projections. */
2386
+ export function isValidatedProviderPlugin(
2387
+ value: ProviderPluginDefinitionV1 | ProviderPluginV1,
2388
+ ): value is ProviderPluginV1 {
2389
+ return validatedProviderPlugins.has(value);
2390
+ }
2391
+
2392
+ /**
2393
+ * Registry-only evidence captured when a trusted source definition becomes an
2394
+ * executable object. It detects ordinary drift in every declared source and
2395
+ * the recursively imported repository descriptor closure before the registry's
2396
+ * later coherent snapshot. It cannot turn trusted in-process code into a
2397
+ * same-account hostile-writer sandbox or prove what Bun loaded during a race.
2398
+ */
2399
+ export function providerPluginEvaluationSourceSha256(
2400
+ plugin: ProviderPluginV1,
2401
+ ): ReadonlyMap<string, string> | undefined {
2402
+ return providerPluginEvaluationSourceDigests.get(plugin);
2403
+ }
2404
+
2405
+ /**
2406
+ * Registry-only exact package-tree identities for every statically
2407
+ * discoverable value import, including literal dynamic imports that could run
2408
+ * at module top level before the definition is frozen.
2409
+ */
2410
+ export function providerPluginEvaluationInstalledPackageSha256(
2411
+ plugin: ProviderPluginV1,
2412
+ ): ReadonlyMap<string, string> | undefined {
2413
+ return providerPluginEvaluationInstalledPackageDigests.get(plugin);
2414
+ }
2415
+
2416
+ /** Kernel-only immutable artifact identity for a validated portable projection. */
2417
+ export function portableProviderPluginArtifactSha256(
2418
+ plugin: ProviderPluginV1,
2419
+ ): string | null {
2420
+ return portableProviderPluginArtifacts.get(plugin) ?? null;
2421
+ }
2422
+
2423
+ /** Kernel-only virtual adapter owned by one validated portable binding. */
2424
+ export function portableProviderPluginAdapter(
2425
+ binding: ProviderPluginBindingV1,
2426
+ ): { readonly adapterId: string; readonly manifest: GhostgetManifest } | null {
2427
+ return portableProviderPluginAdapters.get(binding) ?? null;
2428
+ }
2429
+
2430
+ /** Kernel-only exact portable identity for one validated operation route. */
2431
+ export function portableProviderPluginOperationIdentity(
2432
+ binding: ProviderPluginBindingV1,
2433
+ operation: ProviderPluginOperationV1,
2434
+ contractVersion: number,
2435
+ ): PortableOperationIdentityV1 | null {
2436
+ return portableProviderPluginOperationIdentities.get(binding)?.get(
2437
+ `${operation.name}@${contractVersion}`,
2438
+ ) ?? null;
2439
+ }
2440
+
2441
+ /** Exact immutable package operation that implements a portable subject probe. */
2442
+ export function portableProviderPluginSubjectProbeIdentity(
2443
+ binding: ProviderPluginBindingV1,
2444
+ ): PortableOperationIdentityV1 | null {
2445
+ return portableProviderPluginSubjectProbeIdentities.get(binding) ?? null;
2446
+ }
2447
+
2448
+ function requireExactKeys(
2449
+ value: object,
2450
+ allowed: readonly string[],
2451
+ label: string,
2452
+ ): void {
2453
+ const allowedKeys = new Set(allowed);
2454
+ const unexpected = Object.keys(value).filter((key) => !allowedKeys.has(key)).sort();
2455
+ if (unexpected.length > 0) {
2456
+ throw new Error(`${label} contains unsupported keys: ${unexpected.join(", ")}`);
2457
+ }
2458
+ }
2459
+
2460
+ function snapshotExactEnumerableDataProperties(
2461
+ value: unknown,
2462
+ keys: readonly string[],
2463
+ label: string,
2464
+ ): readonly unknown[] {
2465
+ if (
2466
+ typeof value !== "object"
2467
+ || value === null
2468
+ || Array.isArray(value)
2469
+ ) {
2470
+ throw new Error(`${label} must be a plain data object`);
2471
+ }
2472
+ const prototype = Object.getPrototypeOf(value) as unknown;
2473
+ if (prototype !== Object.prototype && prototype !== null) {
2474
+ throw new Error(`${label} must be a plain data object`);
2475
+ }
2476
+ const expectedKeys = new Set<PropertyKey>(keys);
2477
+ const actualKeys = Reflect.ownKeys(value);
2478
+ if (
2479
+ actualKeys.length !== expectedKeys.size
2480
+ || actualKeys.some((key) => !expectedKeys.has(key))
2481
+ ) {
2482
+ throw new Error(`${label} must contain exactly ${keys.join(", ")}`);
2483
+ }
2484
+ return Object.freeze(keys.map((key) =>
2485
+ ownDataProperty(value, key, `${label}.${key}`)));
2486
+ }
2487
+
2488
+ export function parseProviderPluginReconciliationContextV1(
2489
+ value: unknown,
2490
+ ): ProviderPluginReconciliationContextV1 {
2491
+ const [schemaVersion, kind, dispatchValue, targetValue] =
2492
+ snapshotExactEnumerableDataProperties(
2493
+ value,
2494
+ ["schemaVersion", "kind", "dispatch", "target"],
2495
+ "provider plugin reconciliation context",
2496
+ );
2497
+ if (
2498
+ schemaVersion !== 1
2499
+ || (
2500
+ kind !== "provider-accepted-target-presence"
2501
+ && kind !== "provider-bound-target-desired-state"
2502
+ )
2503
+ ) {
2504
+ throw new Error("provider plugin reconciliation context is malformed");
2505
+ }
2506
+ const [dispatchId, dispatchIndex, dispatchPlanned] =
2507
+ snapshotExactEnumerableDataProperties(
2508
+ dispatchValue,
2509
+ ["id", "index", "planned"],
2510
+ "provider plugin reconciliation context dispatch",
2511
+ );
2512
+ if (
2513
+ typeof dispatchId !== "string"
2514
+ || dispatchId.length > 256
2515
+ || !providerPluginDispatchIdPattern.test(dispatchId)
2516
+ || !Number.isSafeInteger(dispatchIndex)
2517
+ || !Number.isSafeInteger(dispatchPlanned)
2518
+ || (dispatchIndex as number) < 1
2519
+ || (dispatchPlanned as number) !== 1
2520
+ || dispatchIndex !== dispatchPlanned
2521
+ ) {
2522
+ throw new Error(
2523
+ "provider plugin reconciliation context dispatch is malformed",
2524
+ );
2525
+ }
2526
+ const [targetSchemaVersion, targetIdentifier] =
2527
+ snapshotExactEnumerableDataProperties(
2528
+ targetValue,
2529
+ ["schemaVersion", "identifier"],
2530
+ "provider plugin reconciliation context target",
2531
+ );
2532
+ if (
2533
+ targetSchemaVersion !== 1
2534
+ || typeof targetIdentifier !== "string"
2535
+ || targetIdentifier.length < 1
2536
+ || Buffer.byteLength(targetIdentifier, "utf8") > 8 * 1024
2537
+ || hasUnpairedSurrogate(targetIdentifier)
2538
+ || [...targetIdentifier].some((character) => {
2539
+ const codePoint = character.codePointAt(0);
2540
+ return codePoint === undefined
2541
+ || codePoint === 0x7f
2542
+ || codePoint < 0x20;
2543
+ })
2544
+ ) {
2545
+ throw new Error(
2546
+ "provider plugin reconciliation context target is malformed",
2547
+ );
2548
+ }
2549
+ return Object.freeze({
2550
+ schemaVersion: 1,
2551
+ kind,
2552
+ dispatch: Object.freeze({
2553
+ id: dispatchId,
2554
+ index: dispatchIndex as number,
2555
+ planned: dispatchPlanned as number,
2556
+ }),
2557
+ target: Object.freeze({
2558
+ schemaVersion: 1,
2559
+ identifier: targetIdentifier,
2560
+ }),
2561
+ });
2562
+ }
2563
+
2564
+ function snapshotBoundedDenseArray(
2565
+ value: unknown,
2566
+ label: string,
2567
+ maximumItems: number,
2568
+ ): readonly unknown[] {
2569
+ if (
2570
+ !Array.isArray(value)
2571
+ || (Object.getPrototypeOf(value) as unknown) !== Array.prototype
2572
+ ) {
2573
+ throw new Error(`${label} must be a bounded dense array`);
2574
+ }
2575
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
2576
+ const rawLength: unknown = lengthDescriptor !== undefined
2577
+ && "value" in lengthDescriptor
2578
+ ? lengthDescriptor.value as unknown
2579
+ : Number.NaN;
2580
+ if (
2581
+ typeof rawLength !== "number"
2582
+ || !Number.isSafeInteger(rawLength)
2583
+ || rawLength < 1
2584
+ || rawLength > maximumItems
2585
+ ) {
2586
+ throw new Error(
2587
+ `${label} must contain between 1 and ${maximumItems} items`,
2588
+ );
2589
+ }
2590
+ const expectedKeys = new Set<PropertyKey>([
2591
+ "length",
2592
+ ...Array.from({ length: rawLength }, (_unused, index) => String(index)),
2593
+ ]);
2594
+ const actualKeys = Reflect.ownKeys(value);
2595
+ if (
2596
+ actualKeys.length !== expectedKeys.size
2597
+ || actualKeys.some((key) => !expectedKeys.has(key))
2598
+ ) {
2599
+ throw new Error(`${label} must be a bounded dense array`);
2600
+ }
2601
+ return Object.freeze(Array.from(
2602
+ { length: rawLength },
2603
+ (_unused, index) =>
2604
+ ownDataProperty(value, String(index), `${label}[${index}]`),
2605
+ ));
2606
+ }
2607
+
2608
+ function requireNonEmptyArray(value: unknown, label: string): void {
2609
+ if (!Array.isArray(value) || value.length < 1) {
2610
+ throw new Error(`${label} must be a non-empty array`);
2611
+ }
2612
+ }
2613
+
2614
+ function requireBoundedNonEmptyArray(
2615
+ value: unknown,
2616
+ label: string,
2617
+ maximumItems: number,
2618
+ ): void {
2619
+ requireNonEmptyArray(value, label);
2620
+ if ((value as readonly unknown[]).length > maximumItems) {
2621
+ throw new Error(`${label} may contain at most ${maximumItems} items`);
2622
+ }
2623
+ }
2624
+
2625
+ function freezeStringList(
2626
+ values: readonly string[],
2627
+ label: string,
2628
+ maximumItems: number,
2629
+ allowEmpty = false,
2630
+ ): readonly string[] {
2631
+ if (
2632
+ !Array.isArray(values)
2633
+ || (!allowEmpty && values.length < 1)
2634
+ || values.length > maximumItems
2635
+ ) {
2636
+ throw new Error(
2637
+ `${label} must contain ${allowEmpty ? "at most" : "between 1 and"} ${maximumItems} strings`,
2638
+ );
2639
+ }
2640
+ const result: string[] = [];
2641
+ for (const value of values) {
2642
+ if (typeof value !== "string" || value.length < 1) {
2643
+ throw new Error(`${label} must contain non-empty strings`);
2644
+ }
2645
+ result.push(value);
2646
+ }
2647
+ if (new Set(result).size !== result.length) throw new Error(`${label} must not contain duplicates`);
2648
+ return Object.freeze(result);
2649
+ }
2650
+
2651
+ function freezeInputField(
2652
+ field: ScalarInputField | FileInputField,
2653
+ label: string,
2654
+ maximumArrayItems: 25 | 100,
2655
+ nested?: boolean,
2656
+ ): ScalarInputField | FileInputField;
2657
+ function freezeInputField(
2658
+ field: ArrayInputField,
2659
+ label: string,
2660
+ maximumArrayItems: 25 | 100,
2661
+ nested?: boolean,
2662
+ ): ArrayInputField;
2663
+ function freezeInputField(
2664
+ field: InputField,
2665
+ label: string,
2666
+ maximumArrayItems: 25 | 100,
2667
+ nested?: boolean,
2668
+ ): InputField;
2669
+ function freezeInputField(
2670
+ field: InputField,
2671
+ label: string,
2672
+ maximumArrayItems: 25 | 100,
2673
+ nested = false,
2674
+ ): InputField {
2675
+ if (
2676
+ typeof field !== "object"
2677
+ || field === null
2678
+ || typeof field.description !== "string"
2679
+ || field.description.length < 1
2680
+ || field.description.length > 500
2681
+ ) {
2682
+ throw new Error(`${label} must declare a bounded description`);
2683
+ }
2684
+ if (field.type === "file") {
2685
+ requireExactKeys(field, ["type", "description", "maxBytes", "mediaTypes"], label);
2686
+ if (
2687
+ !Number.isSafeInteger(field.maxBytes)
2688
+ || field.maxBytes < 1
2689
+ || field.maxBytes > 1024 * 1024 * 1024
2690
+ ) {
2691
+ throw new Error(`${label}.maxBytes must be a bounded positive integer`);
2692
+ }
2693
+ if (
2694
+ field.mediaTypes !== undefined
2695
+ && (
2696
+ field.mediaTypes.length < 1
2697
+ || field.mediaTypes.length > 32
2698
+ || field.mediaTypes.some((value) =>
2699
+ !/^[a-z0-9!#$&^_.+-]+\/(?:[a-z0-9!#$&^_.+-]+|\*)$/u.test(value))
2700
+ )
2701
+ ) {
2702
+ throw new Error(`${label}.mediaTypes must contain bounded media types`);
2703
+ }
2704
+ return Object.freeze({
2705
+ ...field,
2706
+ ...(field.mediaTypes === undefined
2707
+ ? {}
2708
+ : {
2709
+ mediaTypes: freezeStringList(
2710
+ field.mediaTypes,
2711
+ `${label}.mediaTypes`,
2712
+ 32,
2713
+ ),
2714
+ }),
2715
+ });
2716
+ }
2717
+ if (field.type === "array") {
2718
+ requireExactKeys(field, ["type", "description", "items", "minItems", "maxItems"], label);
2719
+ if (
2720
+ !Number.isSafeInteger(field.minItems)
2721
+ || !Number.isSafeInteger(field.maxItems)
2722
+ || nested
2723
+ || field.minItems < 0
2724
+ || field.maxItems < field.minItems
2725
+ || field.maxItems < 1
2726
+ || field.maxItems > maximumArrayItems
2727
+ ) {
2728
+ throw new Error(`${label} must declare valid bounded item counts`);
2729
+ }
2730
+ return Object.freeze({
2731
+ ...field,
2732
+ items: freezeInputField(
2733
+ field.items,
2734
+ `${label}.items`,
2735
+ maximumArrayItems,
2736
+ true,
2737
+ ),
2738
+ });
2739
+ }
2740
+ requireExactKeys(
2741
+ field,
2742
+ [
2743
+ "type",
2744
+ "description",
2745
+ "minLength",
2746
+ "maxLength",
2747
+ "minimum",
2748
+ "maximum",
2749
+ "enum",
2750
+ "format",
2751
+ "urlPathPrefixes",
2752
+ ],
2753
+ label,
2754
+ );
2755
+ if (
2756
+ field.type !== "string"
2757
+ && field.type !== "number"
2758
+ && field.type !== "boolean"
2759
+ ) {
2760
+ throw new Error(`${label}.type is unsupported`);
2761
+ }
2762
+ const lengthBounds = field.minLength !== undefined
2763
+ || field.maxLength !== undefined;
2764
+ if (
2765
+ lengthBounds
2766
+ && (
2767
+ field.type !== "string"
2768
+ || (
2769
+ field.minLength !== undefined
2770
+ && (
2771
+ !Number.isSafeInteger(field.minLength)
2772
+ || field.minLength < 0
2773
+ || field.minLength > 1_000_000
2774
+ )
2775
+ )
2776
+ || (
2777
+ field.maxLength !== undefined
2778
+ && (
2779
+ !Number.isSafeInteger(field.maxLength)
2780
+ || field.maxLength < 1
2781
+ || field.maxLength > 1_000_000
2782
+ )
2783
+ )
2784
+ || (
2785
+ field.minLength !== undefined
2786
+ && field.maxLength !== undefined
2787
+ && field.minLength > field.maxLength
2788
+ )
2789
+ )
2790
+ ) {
2791
+ throw new Error(`${label} has invalid string length bounds`);
2792
+ }
2793
+ const numericBounds = field.minimum !== undefined
2794
+ || field.maximum !== undefined;
2795
+ if (
2796
+ numericBounds
2797
+ && (
2798
+ field.type !== "number"
2799
+ || (field.minimum !== undefined && !Number.isFinite(field.minimum))
2800
+ || (field.maximum !== undefined && !Number.isFinite(field.maximum))
2801
+ || (
2802
+ field.minimum !== undefined
2803
+ && field.maximum !== undefined
2804
+ && field.minimum > field.maximum
2805
+ )
2806
+ )
2807
+ ) {
2808
+ throw new Error(`${label} has invalid numeric bounds`);
2809
+ }
2810
+ if (
2811
+ field.format !== undefined
2812
+ && (
2813
+ field.type !== "string"
2814
+ || (field.format !== "url" && field.format !== "path-segment")
2815
+ )
2816
+ ) {
2817
+ throw new Error(`${label}.format is invalid`);
2818
+ }
2819
+ if (
2820
+ field.urlPathPrefixes !== undefined
2821
+ && (
2822
+ field.type !== "string"
2823
+ || field.format !== "url"
2824
+ || field.urlPathPrefixes.length < 1
2825
+ || field.urlPathPrefixes.length > 20
2826
+ || field.urlPathPrefixes.some((prefix) =>
2827
+ !prefix.startsWith("/")
2828
+ || prefix.startsWith("//")
2829
+ || prefix.length > 2_048
2830
+ || prefix.includes(String.fromCharCode(0)))
2831
+ || field.urlPathPrefixes.some((prefix) =>
2832
+ prefix.includes("\\")
2833
+ || prefix.includes("?")
2834
+ || prefix.includes("#")
2835
+ || /%(?:25|2e|2f|5c)/iu.test(prefix)
2836
+ || prefix.split("/").some((segment) =>
2837
+ segment === "." || segment === ".."))
2838
+ )
2839
+ ) {
2840
+ throw new Error(`${label}.urlPathPrefixes is invalid`);
2841
+ }
2842
+ if (field.enum !== undefined) {
2843
+ if (
2844
+ field.enum.length < 1
2845
+ || field.enum.length > 100
2846
+ || field.enum.some((value) =>
2847
+ typeof value !== field.type
2848
+ || (typeof value === "number" && !Number.isFinite(value)))
2849
+ || new Set(field.enum.map((value) => JSON.stringify(value))).size
2850
+ !== field.enum.length
2851
+ ) {
2852
+ throw new Error(`${label}.enum is invalid`);
2853
+ }
2854
+ }
2855
+ return Object.freeze({
2856
+ ...field,
2857
+ ...(field.enum === undefined ? {} : { enum: Object.freeze([...field.enum]) }),
2858
+ ...(field.urlPathPrefixes === undefined
2859
+ ? {}
2860
+ : {
2861
+ urlPathPrefixes: freezeStringList(
2862
+ field.urlPathPrefixes,
2863
+ `${label}.urlPathPrefixes`,
2864
+ 20,
2865
+ ),
2866
+ }),
2867
+ });
2868
+ }
2869
+
2870
+ function freezeInputSchema(
2871
+ schema: InputSchema,
2872
+ label: string,
2873
+ maximumArrayItems: 25 | 100,
2874
+ ): InputSchema {
2875
+ requireExactKeys(schema, ["properties", "required"], label);
2876
+ if (
2877
+ typeof schema.properties !== "object"
2878
+ || schema.properties === null
2879
+ || Array.isArray(schema.properties)
2880
+ ) {
2881
+ throw new Error(`${label}.properties must be an object`);
2882
+ }
2883
+ const propertyEntries: [string, InputField][] = [];
2884
+ for (const name in schema.properties) {
2885
+ if (!Object.hasOwn(schema.properties, name)) continue;
2886
+ if (propertyEntries.length === 100) {
2887
+ throw new Error(`${label}.properties may contain at most 100 fields`);
2888
+ }
2889
+ propertyEntries.push([name, schema.properties[name]!]);
2890
+ }
2891
+ propertyEntries.sort(([left], [right]) => left.localeCompare(right));
2892
+ const properties: Record<string, InputField> = {};
2893
+ for (const [name, field] of propertyEntries) {
2894
+ if (!/^[a-z][a-z0-9_]{0,63}$/u.test(name)) {
2895
+ throw new Error(`${label}.properties contains invalid field ${name}`);
2896
+ }
2897
+ properties[name] = freezeInputField(
2898
+ field,
2899
+ `${label}.properties.${name}`,
2900
+ maximumArrayItems,
2901
+ );
2902
+ }
2903
+ const required = freezeStringList(
2904
+ schema.required,
2905
+ `${label}.required`,
2906
+ 100,
2907
+ true,
2908
+ );
2909
+ for (const name of required) {
2910
+ if (properties[name] === undefined) throw new Error(`${label}.required references ${name}`);
2911
+ }
2912
+ return Object.freeze({
2913
+ properties: Object.freeze(properties),
2914
+ required,
2915
+ });
2916
+ }
2917
+
2918
+ function containsFileInput(field: InputField): boolean {
2919
+ return field.type === "file"
2920
+ || (field.type === "array" && field.items.type === "file");
2921
+ }
2922
+
2923
+ function hasControlCharacters(value: string): boolean {
2924
+ for (let index = 0; index < value.length; index += 1) {
2925
+ const code = value.charCodeAt(index);
2926
+ if (code <= 0x1f || code === 0x7f) return true;
2927
+ }
2928
+ return false;
2929
+ }
2930
+
2931
+ function freezeOperation(
2932
+ operation: ProviderPluginOperationDefinitionV1,
2933
+ transport: ProviderPluginTransport,
2934
+ sourceKind: ProviderPluginV1["sourceKind"],
2935
+ ): ProviderPluginOperationV1 {
2936
+ const official = transport === "provider-api";
2937
+ requireExactKeys(
2938
+ operation,
2939
+ [
2940
+ "name",
2941
+ "contractVersion",
2942
+ "historicalContractVersions",
2943
+ "risk",
2944
+ "input",
2945
+ "sideEffect",
2946
+ "idempotency",
2947
+ "dedupeWindowMs",
2948
+ "state",
2949
+ "dispatch",
2950
+ "implementation",
2951
+ "planDispatches",
2952
+ "validateInput",
2953
+ "validateSubjectInput",
2954
+ "reconciliation",
2955
+ "omni",
2956
+ "access",
2957
+ ...(official ? ["requiredScopeSets", "coverage"] : []),
2958
+ ],
2959
+ "provider plugin operation",
2960
+ );
2961
+ if (
2962
+ !isProviderPluginOperationName(operation.name)
2963
+ ) {
2964
+ throw new Error(`provider plugin operation ${String(operation.name)} must be a bounded dotted semantic name`);
2965
+ }
2966
+ if (
2967
+ !Number.isSafeInteger(operation.contractVersion)
2968
+ || operation.contractVersion < 1
2969
+ || operation.contractVersion > 1_000_000
2970
+ ) {
2971
+ throw new Error(`provider plugin operation ${operation.name} has an invalid contract version`);
2972
+ }
2973
+ if (
2974
+ operation.historicalContractVersions !== undefined
2975
+ && (
2976
+ !Array.isArray(operation.historicalContractVersions)
2977
+ || operation.historicalContractVersions.length
2978
+ >= MAX_PROVIDER_PLUGIN_CONTRACT_VERSIONS_PER_OPERATION
2979
+ )
2980
+ ) {
2981
+ throw new Error(
2982
+ `provider plugin operation ${operation.name} may declare at most ${MAX_PROVIDER_PLUGIN_CONTRACT_VERSIONS_PER_OPERATION - 1} historical contract versions`,
2983
+ );
2984
+ }
2985
+ const rawHistoricalContractVersions: readonly unknown[] =
2986
+ operation.historicalContractVersions === undefined
2987
+ ? []
2988
+ : operation.historicalContractVersions;
2989
+ const copiedHistoricalContractVersions: number[] = [];
2990
+ for (const version of rawHistoricalContractVersions) {
2991
+ if (
2992
+ typeof version !== "number"
2993
+ || !Number.isSafeInteger(version)
2994
+ || version < 1
2995
+ || version > 1_000_000
2996
+ || version >= operation.contractVersion
2997
+ ) {
2998
+ throw new Error(`provider plugin operation ${operation.name} has an invalid historical contract version`);
2999
+ }
3000
+ copiedHistoricalContractVersions.push(version);
3001
+ }
3002
+ const historicalContractVersions = Object.freeze(
3003
+ copiedHistoricalContractVersions,
3004
+ );
3005
+ if (new Set(historicalContractVersions).size !== historicalContractVersions.length) {
3006
+ throw new Error(`provider plugin operation ${operation.name} repeats a historical contract version`);
3007
+ }
3008
+ if (!["R1", "R2", "R3", "R4"].includes(operation.risk)) {
3009
+ throw new Error(`provider plugin operation ${operation.name} has an invalid risk`);
3010
+ }
3011
+ if (operation.state !== "observed" && operation.state !== "capture-required") {
3012
+ throw new Error(`provider plugin operation ${operation.name} has an invalid state`);
3013
+ }
3014
+ if (
3015
+ operation.access !== undefined
3016
+ && (
3017
+ operation.access !== "public"
3018
+ || official
3019
+ || sourceKind !== "built-in"
3020
+ || transport !== "web-session-api"
3021
+ || operation.risk !== "R1"
3022
+ || operation.state !== "observed"
3023
+ || operation.dispatch !== "none"
3024
+ )
3025
+ ) {
3026
+ throw new Error(
3027
+ `provider plugin operation ${operation.name} public access requires an observed dispatch-free built-in web-session R1 operation`,
3028
+ );
3029
+ }
3030
+ if (
3031
+ operation.dispatch !== "none"
3032
+ && operation.dispatch !== "single"
3033
+ && operation.dispatch !== "thread-items"
3034
+ && operation.dispatch !== "bounded-items"
3035
+ ) {
3036
+ throw new Error(`provider plugin operation ${operation.name} has an invalid dispatch policy`);
3037
+ }
3038
+ if (
3039
+ typeof operation.sideEffect !== "string"
3040
+ || operation.sideEffect.length < 1
3041
+ || operation.sideEffect.length > 500
3042
+ || hasControlCharacters(operation.sideEffect)
3043
+ || typeof operation.implementation !== "string"
3044
+ || operation.implementation.length < 1
3045
+ || operation.implementation.length > 500
3046
+ || hasControlCharacters(operation.implementation)
3047
+ ) {
3048
+ throw new Error(`provider plugin operation ${operation.name} has incomplete semantics`);
3049
+ }
3050
+ if (
3051
+ (operation.idempotency !== "none" && operation.idempotency !== "local-at-most-once")
3052
+ || !Number.isSafeInteger(operation.dedupeWindowMs)
3053
+ || operation.dedupeWindowMs < 0
3054
+ || operation.dedupeWindowMs > 30 * 24 * 60 * 60_000
3055
+ || typeof operation.planDispatches !== "function"
3056
+ || typeof operation.validateInput !== "function"
3057
+ ) {
3058
+ throw new Error(`provider plugin operation ${operation.name} has invalid host hooks`);
3059
+ }
3060
+ if (
3061
+ operation.risk === "R1"
3062
+ && (
3063
+ operation.sideEffect !== "none"
3064
+ || operation.dispatch !== "none"
3065
+ || operation.idempotency !== "none"
3066
+ || operation.dedupeWindowMs !== 0
3067
+ || operation.reconciliation !== undefined
3068
+ || Object.values(operation.input.properties).some(containsFileInput)
3069
+ )
3070
+ ) {
3071
+ throw new Error(
3072
+ `provider plugin operation ${operation.name} must keep R1 read semantics side-effect-free and dispatch-free`,
3073
+ );
3074
+ }
3075
+ if (
3076
+ (operation.risk === "R2" || operation.risk === "R3")
3077
+ && (
3078
+ operation.dispatch === "none"
3079
+ || operation.idempotency !== "local-at-most-once"
3080
+ || operation.dedupeWindowMs < 60_000
3081
+ )
3082
+ ) {
3083
+ throw new Error(
3084
+ `provider plugin operation ${operation.name} must bind every R2/R3 write to a confirmed dispatch and positive at-most-once window`,
3085
+ );
3086
+ }
3087
+ if (
3088
+ operation.risk === "R4"
3089
+ && operation.state !== "capture-required"
3090
+ ) {
3091
+ throw new Error(
3092
+ `provider plugin operation ${operation.name} must keep R4 authority capture-required`,
3093
+ );
3094
+ }
3095
+ if (
3096
+ operation.validateSubjectInput !== undefined
3097
+ && typeof operation.validateSubjectInput !== "function"
3098
+ ) {
3099
+ throw new Error(
3100
+ `provider plugin operation ${operation.name} has an invalid subject-input hook`,
3101
+ );
3102
+ }
3103
+ if (operation.reconciliation !== undefined) {
3104
+ if (
3105
+ typeof operation.reconciliation !== "object"
3106
+ || operation.reconciliation === null
3107
+ ) {
3108
+ throw new Error(
3109
+ `provider plugin operation ${operation.name} has an invalid reconciliation contract`,
3110
+ );
3111
+ }
3112
+ const reconciliationLabel =
3113
+ `provider plugin operation ${operation.name} reconciliation`;
3114
+ if (operation.reconciliation.kind === "boolean-desired-state") {
3115
+ requireExactKeys(
3116
+ operation.reconciliation,
3117
+ ["kind", "desiredState"],
3118
+ reconciliationLabel,
3119
+ );
3120
+ if (typeof operation.reconciliation.desiredState !== "function") {
3121
+ throw new Error(
3122
+ `provider plugin operation ${operation.name} has an invalid reconciliation contract`,
3123
+ );
3124
+ }
3125
+ } else if (
3126
+ operation.reconciliation.kind === "provider-accepted-target-presence"
3127
+ || operation.reconciliation.kind
3128
+ === "provider-bound-target-desired-state"
3129
+ ) {
3130
+ requireExactKeys(
3131
+ operation.reconciliation,
3132
+ operation.reconciliation.kind === "provider-accepted-target-presence"
3133
+ ? ["kind"]
3134
+ : ["kind", "desiredState"],
3135
+ reconciliationLabel,
3136
+ );
3137
+ if (
3138
+ operation.reconciliation.kind
3139
+ === "provider-bound-target-desired-state"
3140
+ && operation.reconciliation.desiredState !== false
3141
+ ) {
3142
+ throw new Error(
3143
+ `provider plugin operation ${operation.name} has an invalid reconciliation contract`,
3144
+ );
3145
+ }
3146
+ if (operation.dispatch !== "single") {
3147
+ const targetKind = operation.reconciliation.kind
3148
+ === "provider-accepted-target-presence"
3149
+ ? "provider-accepted"
3150
+ : "provider-bound";
3151
+ throw new Error(
3152
+ `provider plugin operation ${operation.name} ${targetKind} target reconciliation requires one exact dispatch`,
3153
+ );
3154
+ }
3155
+ } else {
3156
+ throw new Error(
3157
+ `provider plugin operation ${operation.name} has an invalid reconciliation contract`,
3158
+ );
3159
+ }
3160
+ }
3161
+ const isOmniInboxRead = operation.name === "messaging.list"
3162
+ || operation.name === "messaging.read";
3163
+ if (isOmniInboxRead && operation.omni === undefined) {
3164
+ throw new Error(
3165
+ `provider plugin operation ${operation.name} must declare supported or unsupported omni normalization`,
3166
+ );
3167
+ }
3168
+ if (!isOmniInboxRead && operation.omni !== undefined) {
3169
+ throw new Error(
3170
+ `provider plugin operation ${operation.name} cannot declare inbox normalization`,
3171
+ );
3172
+ }
3173
+ let omni: ProviderPluginOmniDefinitionV1 | undefined;
3174
+ if (operation.omni !== undefined) {
3175
+ if (
3176
+ typeof operation.omni !== "object"
3177
+ || operation.omni === null
3178
+ ) {
3179
+ throw new Error(
3180
+ `provider plugin operation ${operation.name} has an invalid omni normalization contract`,
3181
+ );
3182
+ }
3183
+ if (operation.omni.state === "supported") {
3184
+ requireExactKeys(
3185
+ operation.omni,
3186
+ ["state", "schemaVersion", "materializerId", "materializerVersion", "materialize"],
3187
+ `provider plugin operation ${operation.name} omni normalization`,
3188
+ );
3189
+ if (
3190
+ operation.state !== "observed"
3191
+ || operation.risk !== "R1"
3192
+ || operation.omni.schemaVersion !== 1
3193
+ || typeof operation.omni.materializerId !== "string"
3194
+ || !/^[a-z][a-z0-9-]{0,63}$/u.test(operation.omni.materializerId)
3195
+ || !Number.isSafeInteger(operation.omni.materializerVersion)
3196
+ || operation.omni.materializerVersion < 1
3197
+ || operation.omni.materializerVersion > 1_000_000
3198
+ || typeof operation.omni.materialize !== "function"
3199
+ ) {
3200
+ throw new Error(
3201
+ `provider plugin operation ${operation.name} has an invalid supported omni normalization contract`,
3202
+ );
3203
+ }
3204
+ omni = Object.freeze({
3205
+ state: "supported",
3206
+ schemaVersion: 1,
3207
+ materializerId: operation.omni.materializerId,
3208
+ materializerVersion: operation.omni.materializerVersion,
3209
+ materialize: operation.omni.materialize,
3210
+ });
3211
+ } else if (operation.omni.state === "unsupported") {
3212
+ requireExactKeys(
3213
+ operation.omni,
3214
+ ["state", "reason"],
3215
+ `provider plugin operation ${operation.name} omni normalization`,
3216
+ );
3217
+ if (
3218
+ typeof operation.omni.reason !== "string"
3219
+ || operation.omni.reason.length < 1
3220
+ || operation.omni.reason.length > 1_000
3221
+ || hasControlCharacters(operation.omni.reason)
3222
+ ) {
3223
+ throw new Error(
3224
+ `provider plugin operation ${operation.name} has an invalid unsupported omni normalization reason`,
3225
+ );
3226
+ }
3227
+ omni = Object.freeze({
3228
+ state: "unsupported",
3229
+ reason: operation.omni.reason,
3230
+ });
3231
+ } else {
3232
+ throw new Error(
3233
+ `provider plugin operation ${operation.name} has an invalid omni normalization state`,
3234
+ );
3235
+ }
3236
+ }
3237
+ const {
3238
+ historicalContractVersions: ignoredHistoricalContractVersions,
3239
+ ...operationWithoutHistory
3240
+ } = operation;
3241
+ void ignoredHistoricalContractVersions;
3242
+ const common = {
3243
+ ...operationWithoutHistory,
3244
+ ...(historicalContractVersions.length === 0 ? {} : { historicalContractVersions }),
3245
+ contractVersions: Object.freeze([
3246
+ ...historicalContractVersions,
3247
+ operation.contractVersion,
3248
+ ].sort((left, right) => left - right)),
3249
+ input: freezeInputSchema(
3250
+ operation.input,
3251
+ `provider plugin operation ${operation.name}.input`,
3252
+ official ? 100 : 25,
3253
+ ),
3254
+ planDispatches: conformingProviderPluginPlanDispatches(operation),
3255
+ ...(operation.reconciliation === undefined
3256
+ ? {}
3257
+ : operation.reconciliation.kind === "boolean-desired-state"
3258
+ ? {
3259
+ reconciliation: Object.freeze({
3260
+ kind: operation.reconciliation.kind,
3261
+ desiredState: operation.reconciliation.desiredState,
3262
+ }),
3263
+ }
3264
+ : operation.reconciliation.kind
3265
+ === "provider-bound-target-desired-state"
3266
+ ? {
3267
+ reconciliation: Object.freeze({
3268
+ kind: operation.reconciliation.kind,
3269
+ desiredState: operation.reconciliation.desiredState,
3270
+ }),
3271
+ }
3272
+ : {
3273
+ reconciliation: Object.freeze({
3274
+ kind: operation.reconciliation.kind,
3275
+ }),
3276
+ }),
3277
+ ...(omni === undefined ? {} : { omni }),
3278
+ };
3279
+ if (!official) return Object.freeze(common);
3280
+ const providerOperation = operation as ProviderApiPluginOperationDefinitionV1;
3281
+ requireBoundedNonEmptyArray(
3282
+ providerOperation.requiredScopeSets,
3283
+ `provider plugin operation ${operation.name} requiredScopeSets`,
3284
+ 32,
3285
+ );
3286
+ for (const scopeSet of providerOperation.requiredScopeSets) {
3287
+ requireBoundedNonEmptyArray(
3288
+ scopeSet,
3289
+ `provider plugin operation ${operation.name} scope set`,
3290
+ 32,
3291
+ );
3292
+ }
3293
+ const requiredScopeSets = providerOperation.requiredScopeSets.map(
3294
+ (scopeSet) =>
3295
+ freezeStringList(
3296
+ scopeSet,
3297
+ `provider plugin operation ${operation.name} scope set`,
3298
+ 32,
3299
+ ),
3300
+ );
3301
+ requireBoundedNonEmptyArray(
3302
+ providerOperation.coverage,
3303
+ `provider plugin operation ${operation.name} coverage`,
3304
+ 64,
3305
+ );
3306
+ return Object.freeze({
3307
+ ...common,
3308
+ requiredScopeSets: Object.freeze(requiredScopeSets),
3309
+ coverage: freezeStringList(
3310
+ providerOperation.coverage,
3311
+ `provider plugin operation ${operation.name} coverage`,
3312
+ 64,
3313
+ ),
3314
+ });
3315
+ }
3316
+
3317
+ function validateProviderRuntime(value: ProviderApiPluginRuntimeV1): ProviderApiPluginRuntimeV1 {
3318
+ requireExactKeys(value, ["execute", "executeMessagingPart"], "provider plugin runtime");
3319
+ if (typeof value.execute !== "function") throw new Error("provider plugin runtime must declare execute");
3320
+ if (
3321
+ value.executeMessagingPart !== undefined
3322
+ && typeof value.executeMessagingPart !== "function"
3323
+ ) throw new Error("provider plugin messaging action runtime hook is invalid");
3324
+ return Object.freeze({
3325
+ execute: value.execute,
3326
+ ...(value.executeMessagingPart === undefined
3327
+ ? {}
3328
+ : { executeMessagingPart: value.executeMessagingPart }),
3329
+ });
3330
+ }
3331
+
3332
+ function validateWebRuntime(value: WebSessionPluginRuntimeV1): WebSessionPluginRuntimeV1 {
3333
+ requireExactKeys(
3334
+ value,
3335
+ ["probe", "execute", "executeMessagingPart", "executePublic", "reconcile", "linkedDeviceLifecycle"],
3336
+ "web-session plugin runtime",
3337
+ );
3338
+ if (typeof value.probe !== "function" || typeof value.execute !== "function") {
3339
+ throw new Error("web-session plugin runtime must declare probe and execute");
3340
+ }
3341
+ if (
3342
+ value.executePublic !== undefined
3343
+ && typeof value.executePublic !== "function"
3344
+ ) {
3345
+ throw new Error("web-session plugin public runtime hook is invalid");
3346
+ }
3347
+ if (
3348
+ value.executeMessagingPart !== undefined
3349
+ && typeof value.executeMessagingPart !== "function"
3350
+ ) throw new Error("web-session plugin messaging action runtime hook is invalid");
3351
+ if (value.reconcile !== undefined && typeof value.reconcile !== "function") {
3352
+ throw new Error("web-session plugin runtime reconciliation hook is invalid");
3353
+ }
3354
+ let linkedDeviceLifecycle:
3355
+ | ProviderPluginLinkedDeviceLifecycleRuntimeV1
3356
+ | undefined;
3357
+ if (value.linkedDeviceLifecycle !== undefined) {
3358
+ requireExactKeys(
3359
+ value.linkedDeviceLifecycle,
3360
+ ["inspect", "pair", "syncOnce"],
3361
+ "linked-device plugin lifecycle runtime",
3362
+ );
3363
+ if (
3364
+ typeof value.linkedDeviceLifecycle.inspect !== "function"
3365
+ || typeof value.linkedDeviceLifecycle.pair !== "function"
3366
+ || typeof value.linkedDeviceLifecycle.syncOnce !== "function"
3367
+ ) {
3368
+ throw new Error("linked-device plugin lifecycle runtime is invalid");
3369
+ }
3370
+ linkedDeviceLifecycle = Object.freeze({
3371
+ inspect: value.linkedDeviceLifecycle.inspect,
3372
+ pair: value.linkedDeviceLifecycle.pair,
3373
+ syncOnce: value.linkedDeviceLifecycle.syncOnce,
3374
+ });
3375
+ }
3376
+ return Object.freeze({
3377
+ probe: value.probe,
3378
+ execute: value.execute,
3379
+ ...(value.executeMessagingPart === undefined
3380
+ ? {}
3381
+ : { executeMessagingPart: value.executeMessagingPart }),
3382
+ ...(value.executePublic === undefined
3383
+ ? {}
3384
+ : { executePublic: value.executePublic }),
3385
+ ...(value.reconcile === undefined ? {} : { reconcile: value.reconcile }),
3386
+ ...(linkedDeviceLifecycle === undefined ? {} : { linkedDeviceLifecycle }),
3387
+ });
3388
+ }
3389
+
3390
+ function validateLocalCliRuntime(
3391
+ value: LocalCliPluginRuntimeV1,
3392
+ ): LocalCliPluginRuntimeV1 {
3393
+ requireExactKeys(
3394
+ value,
3395
+ ["inspect", "probe", "execute", "executeMessagingPart", "reconcile"],
3396
+ "local CLI plugin runtime",
3397
+ );
3398
+ if (
3399
+ typeof value.inspect !== "function"
3400
+ || typeof value.probe !== "function"
3401
+ || typeof value.execute !== "function"
3402
+ ) {
3403
+ throw new Error("local CLI plugin runtime must declare inspect, probe, and execute");
3404
+ }
3405
+ if (value.reconcile !== undefined && typeof value.reconcile !== "function") {
3406
+ throw new Error("local CLI plugin runtime reconciliation hook is invalid");
3407
+ }
3408
+ if (
3409
+ value.executeMessagingPart !== undefined
3410
+ && typeof value.executeMessagingPart !== "function"
3411
+ ) throw new Error("local CLI plugin messaging action runtime hook is invalid");
3412
+ return Object.freeze({
3413
+ inspect: value.inspect,
3414
+ probe: value.probe,
3415
+ execute: value.execute,
3416
+ ...(value.executeMessagingPart === undefined
3417
+ ? {}
3418
+ : { executeMessagingPart: value.executeMessagingPart }),
3419
+ ...(value.reconcile === undefined ? {} : { reconcile: value.reconcile }),
3420
+ });
3421
+ }
3422
+
3423
+ function memoizedRuntime<T>(
3424
+ loader: () => Promise<T>,
3425
+ validate: (value: T) => T,
3426
+ ): () => Promise<T> {
3427
+ let pending: Promise<T> | undefined;
3428
+ const loadRuntime = () => {
3429
+ providerPluginStartedRuntimeLoaders.add(loadRuntime);
3430
+ if (pending === undefined) {
3431
+ pending = Promise.resolve().then(async () => {
3432
+ const identity = providerPluginRuntimeLoadIdentities.get(loadRuntime);
3433
+ await identity?.verify("before");
3434
+ const runtime = validate(await loader());
3435
+ await identity?.verify("after");
3436
+ return runtime;
3437
+ });
3438
+ }
3439
+ return pending;
3440
+ };
3441
+ return loadRuntime;
3442
+ }
3443
+
3444
+ export type ProviderPluginRuntimeLoadIdentityPhase = "before" | "after";
3445
+
3446
+ type ProviderPluginRuntimeLoadIdentity = {
3447
+ readonly token: string;
3448
+ readonly verify: (
3449
+ phase: ProviderPluginRuntimeLoadIdentityPhase,
3450
+ ) => void | Promise<void>;
3451
+ };
3452
+
3453
+ const providerPluginRuntimeLoadIdentities = new WeakMap<
3454
+ () => Promise<unknown>,
3455
+ ProviderPluginRuntimeLoadIdentity
3456
+ >();
3457
+ const providerPluginLazyRuntimeLoaders = new WeakSet<
3458
+ () => Promise<unknown>
3459
+ >();
3460
+ const providerPluginStartedRuntimeLoaders = new WeakSet<
3461
+ () => Promise<unknown>
3462
+ >();
3463
+
3464
+ /**
3465
+ * Registry-only binding between a lazy loader and the exact startup source
3466
+ * snapshot whose hash is published in plans and receipts.
3467
+ */
3468
+ export function bindProviderPluginRuntimeLoadIdentity(
3469
+ loadRuntime: () => Promise<unknown>,
3470
+ identity: ProviderPluginRuntimeLoadIdentity,
3471
+ ): void {
3472
+ const current = providerPluginRuntimeLoadIdentities.get(loadRuntime);
3473
+ if (current !== undefined) {
3474
+ if (current.token !== identity.token) {
3475
+ throw new Error(
3476
+ "provider plugin runtime loader is already bound to a different implementation identity",
3477
+ );
3478
+ }
3479
+ return;
3480
+ }
3481
+ if (providerPluginStartedRuntimeLoaders.has(loadRuntime)) {
3482
+ throw new Error(
3483
+ "provider plugin runtime loader was invoked before its implementation identity was bound",
3484
+ );
3485
+ }
3486
+ providerPluginRuntimeLoadIdentities.set(
3487
+ loadRuntime,
3488
+ Object.freeze(identity),
3489
+ );
3490
+ }
3491
+
3492
+ export function lazyProviderApiRuntime(
3493
+ loader: () => Promise<ProviderApiPluginRuntimeV1>,
3494
+ ): ProviderApiPluginRuntimeHooksV1 {
3495
+ const loadRuntime = memoizedRuntime(loader, validateProviderRuntime);
3496
+ providerPluginLazyRuntimeLoaders.add(loadRuntime);
3497
+ return Object.freeze({ loadRuntime });
3498
+ }
3499
+
3500
+ export function lazyWebSessionRuntime(
3501
+ loader: () => Promise<WebSessionPluginRuntimeV1>,
3502
+ ): WebSessionPluginRuntimeHooksV1 {
3503
+ const loadRuntime = memoizedRuntime(loader, validateWebRuntime);
3504
+ providerPluginLazyRuntimeLoaders.add(loadRuntime);
3505
+ return Object.freeze({ loadRuntime });
3506
+ }
3507
+
3508
+ export function lazyLocalCliRuntime(
3509
+ loader: () => Promise<LocalCliPluginRuntimeV1>,
3510
+ ): LocalCliPluginRuntimeHooksV1 {
3511
+ const loadRuntime = memoizedRuntime(loader, validateLocalCliRuntime);
3512
+ providerPluginLazyRuntimeLoaders.add(loadRuntime);
3513
+ return Object.freeze({ loadRuntime });
3514
+ }
3515
+
3516
+ function freezeProviderPluginMessaging(
3517
+ value: ProviderPluginMessagingDefinitionV1 | undefined,
3518
+ operations: readonly ProviderPluginOperationV1[],
3519
+ surfaceId: string,
3520
+ ): ProviderPluginMessagingDefinitionV1 | undefined {
3521
+ if (value === undefined) return undefined;
3522
+ requireExactKeys(value, [
3523
+ "schemaVersion",
3524
+ "contractId",
3525
+ "network",
3526
+ "contextLiveness",
3527
+ "listOperation",
3528
+ "contextOperation",
3529
+ "enumerateRoutes",
3530
+ "resolveRoute",
3531
+ "parseTarget",
3532
+ "contextInput",
3533
+ "action",
3534
+ ], `provider plugin surface ${surfaceId} messaging SPI`);
3535
+ if (
3536
+ value.schemaVersion !== 1
3537
+ || typeof value.contractId !== "string"
3538
+ || !/^[a-z][a-z0-9.-]{0,127}\.v1$/u.test(value.contractId)
3539
+ || typeof value.network !== "string"
3540
+ || !/^[a-z][a-z0-9-]{0,63}$/u.test(value.network)
3541
+ || value.contextLiveness !== "fresh-as-of-live-preflight"
3542
+ && value.contextLiveness !== "freshness-unproven"
3543
+ || value.listOperation !== "messaging.list"
3544
+ || value.contextOperation !== "messaging.read"
3545
+ || typeof value.enumerateRoutes !== "function"
3546
+ || typeof value.resolveRoute !== "object"
3547
+ || value.resolveRoute === null
3548
+ || typeof value.parseTarget !== "function"
3549
+ || typeof value.contextInput !== "function"
3550
+ ) {
3551
+ throw new Error(
3552
+ `provider plugin surface ${surfaceId} has an invalid messaging SPI`,
3553
+ );
3554
+ }
3555
+ requireExactKeys(
3556
+ value.resolveRoute,
3557
+ ["operation", "input", "candidates", "sourceConversationCoordinate"],
3558
+ `provider plugin surface ${surfaceId} messaging exact route resolution`,
3559
+ );
3560
+ if (
3561
+ typeof value.resolveRoute.operation !== "string"
3562
+ || !isProviderPluginOperationName(value.resolveRoute.operation)
3563
+ || typeof value.resolveRoute.input !== "function"
3564
+ || typeof value.resolveRoute.candidates !== "function"
3565
+ || typeof value.resolveRoute.sourceConversationCoordinate !== "function"
3566
+ ) throw new Error(
3567
+ `provider plugin surface ${surfaceId} has an invalid exact messaging route resolver`,
3568
+ );
3569
+ const exactResolutionOperation = operations.find((candidate) =>
3570
+ candidate.name === value.resolveRoute.operation
3571
+ && candidate.contractVersion === 1);
3572
+ if (
3573
+ exactResolutionOperation === undefined
3574
+ || exactResolutionOperation.state !== "observed"
3575
+ || exactResolutionOperation.risk !== "R1"
3576
+ ) {
3577
+ throw new Error(
3578
+ `provider plugin surface ${surfaceId} messaging SPI requires one observed R1 exact route resolver`,
3579
+ );
3580
+ }
3581
+ for (const operationName of [value.listOperation, value.contextOperation]) {
3582
+ const operation = operations.find((candidate) =>
3583
+ candidate.name === operationName && candidate.contractVersion === 1);
3584
+ if (
3585
+ operation === undefined
3586
+ || operation.state !== "observed"
3587
+ || operation.risk !== "R1"
3588
+ || operation.omni?.state !== "supported"
3589
+ ) {
3590
+ throw new Error(
3591
+ `provider plugin surface ${surfaceId} messaging SPI requires observed normalized ${operationName}@1`,
3592
+ );
3593
+ }
3594
+ }
3595
+ if (typeof value.action !== "object" || value.action === null) {
3596
+ throw new Error(
3597
+ `provider plugin surface ${surfaceId} messaging SPI has an invalid action declaration`,
3598
+ );
3599
+ }
3600
+ if (value.action.state === "unavailable") {
3601
+ requireExactKeys(
3602
+ value.action,
3603
+ ["state", "reason", "reply"],
3604
+ `provider plugin surface ${surfaceId} messaging action`,
3605
+ );
3606
+ if (
3607
+ value.action.reply !== "unsupported"
3608
+ || typeof value.action.reason !== "string"
3609
+ || value.action.reason.length < 1
3610
+ || value.action.reason.length > 1_000
3611
+ || hasControlCharacters(value.action.reason)
3612
+ ) {
3613
+ throw new Error(
3614
+ `provider plugin surface ${surfaceId} messaging action unavailability is invalid`,
3615
+ );
3616
+ }
3617
+ return Object.freeze({
3618
+ schemaVersion: 1,
3619
+ contractId: value.contractId,
3620
+ network: value.network,
3621
+ contextLiveness: value.contextLiveness,
3622
+ listOperation: value.listOperation,
3623
+ contextOperation: value.contextOperation,
3624
+ enumerateRoutes: value.enumerateRoutes,
3625
+ resolveRoute: Object.freeze({
3626
+ operation: value.resolveRoute.operation,
3627
+ input: value.resolveRoute.input,
3628
+ candidates: value.resolveRoute.candidates,
3629
+ sourceConversationCoordinate:
3630
+ value.resolveRoute.sourceConversationCoordinate,
3631
+ }),
3632
+ parseTarget: value.parseTarget,
3633
+ contextInput: value.contextInput,
3634
+ action: Object.freeze({
3635
+ state: "unavailable",
3636
+ reason: value.action.reason,
3637
+ reply: "unsupported",
3638
+ }),
3639
+ });
3640
+ }
3641
+ if (value.action.state !== "supported") {
3642
+ throw new Error(
3643
+ `provider plugin surface ${surfaceId} messaging action has an invalid state`,
3644
+ );
3645
+ }
3646
+ if (value.resolveRoute.operation !== "conversations.read") {
3647
+ throw new Error(
3648
+ `provider plugin surface ${surfaceId} actionable messaging requires exact conversations.read route resolution`,
3649
+ );
3650
+ }
3651
+ const action = value.action;
3652
+ requireExactKeys(
3653
+ action,
3654
+ [
3655
+ "state",
3656
+ "operation",
3657
+ "reply",
3658
+ "livePreflight",
3659
+ "compileTurnPart",
3660
+ "mapAcceptedResult",
3661
+ "proveExpectedOwnPrefix",
3662
+ "reconciliation",
3663
+ ],
3664
+ `provider plugin surface ${surfaceId} messaging action`,
3665
+ );
3666
+ const actionOperation = operations.find((candidate) =>
3667
+ candidate.name === action.operation && candidate.contractVersion === 1);
3668
+ if (
3669
+ actionOperation === undefined
3670
+ || actionOperation.state !== "observed"
3671
+ || actionOperation.risk !== "R3"
3672
+ || action.reply !== "supported" && action.reply !== "unsupported"
3673
+ || typeof action.livePreflight !== "object"
3674
+ || action.livePreflight === null
3675
+ || typeof action.compileTurnPart !== "function"
3676
+ || typeof action.mapAcceptedResult !== "function"
3677
+ || typeof action.proveExpectedOwnPrefix !== "function"
3678
+ || typeof action.reconciliation !== "function"
3679
+ ) {
3680
+ throw new Error(
3681
+ `provider plugin surface ${surfaceId} messaging action must bind one observed R3 operation`,
3682
+ );
3683
+ }
3684
+ requireExactKeys(
3685
+ action.livePreflight,
3686
+ ["operation", "input", "snapshot"],
3687
+ `provider plugin surface ${surfaceId} messaging action live preflight`,
3688
+ );
3689
+ const livePreflightOperation = operations.find((candidate) =>
3690
+ candidate.name === action.livePreflight.operation
3691
+ && candidate.contractVersion === 1);
3692
+ if (
3693
+ livePreflightOperation === undefined
3694
+ || livePreflightOperation.state !== "observed"
3695
+ || livePreflightOperation.risk !== "R1"
3696
+ || typeof action.livePreflight.input !== "function"
3697
+ || typeof action.livePreflight.snapshot !== "function"
3698
+ ) throw new Error(
3699
+ `provider plugin surface ${surfaceId} messaging action requires one observed R1 live preflight`,
3700
+ );
3701
+ return Object.freeze({
3702
+ schemaVersion: 1,
3703
+ contractId: value.contractId,
3704
+ network: value.network,
3705
+ contextLiveness: value.contextLiveness,
3706
+ listOperation: value.listOperation,
3707
+ contextOperation: value.contextOperation,
3708
+ enumerateRoutes: value.enumerateRoutes,
3709
+ resolveRoute: Object.freeze({
3710
+ operation: value.resolveRoute.operation,
3711
+ input: value.resolveRoute.input,
3712
+ candidates: value.resolveRoute.candidates,
3713
+ sourceConversationCoordinate:
3714
+ value.resolveRoute.sourceConversationCoordinate,
3715
+ }),
3716
+ parseTarget: value.parseTarget,
3717
+ contextInput: value.contextInput,
3718
+ action: Object.freeze({
3719
+ state: "supported",
3720
+ operation: action.operation,
3721
+ reply: action.reply,
3722
+ livePreflight: Object.freeze({
3723
+ operation: action.livePreflight.operation,
3724
+ input: action.livePreflight.input,
3725
+ snapshot: action.livePreflight.snapshot,
3726
+ }),
3727
+ compileTurnPart: action.compileTurnPart,
3728
+ mapAcceptedResult: action.mapAcceptedResult,
3729
+ proveExpectedOwnPrefix: action.proveExpectedOwnPrefix,
3730
+ reconciliation: action.reconciliation,
3731
+ }),
3732
+ });
3733
+ }
3734
+
3735
+ function freezeBinding(
3736
+ binding: ProviderPluginBindingDefinitionV1,
3737
+ sourceKind: ProviderPluginV1["sourceKind"],
3738
+ ): ProviderPluginBindingV1 {
3739
+ requireExactKeys(
3740
+ binding,
3741
+ [
3742
+ "transport",
3743
+ "surfaceId",
3744
+ "origin",
3745
+ ...(binding.transport === "provider-api" ? ["runtimeOrigins"] : []),
3746
+ ...(binding.transport === "local-cli" ? ["tool"] : []),
3747
+ "manifestOrigins",
3748
+ "protectedHostnameFamilies",
3749
+ "authKinds",
3750
+ "operations",
3751
+ "subject",
3752
+ "messaging",
3753
+ ...(binding.transport === "linked-device"
3754
+ ? ["linkedDeviceLifecycle"]
3755
+ : []),
3756
+ "runtime",
3757
+ ],
3758
+ "provider plugin binding",
3759
+ );
3760
+ if (!providerPluginTransports.includes(binding.transport)) {
3761
+ throw new Error("provider plugin binding has an unsupported transport");
3762
+ }
3763
+ if (!isProviderPluginSurfaceId(binding.surfaceId)) {
3764
+ throw new Error(`provider plugin surface ID ${binding.surfaceId} must be strict lowercase kebab-case with at most 63 characters`);
3765
+ }
3766
+ let origin: URL;
3767
+ try {
3768
+ origin = new URL(binding.origin);
3769
+ } catch {
3770
+ throw new Error(`provider plugin surface ${binding.surfaceId} has an invalid origin`);
3771
+ }
3772
+ if (
3773
+ origin.protocol !== "https:"
3774
+ || origin.username !== ""
3775
+ || origin.password !== ""
3776
+ || origin.pathname !== "/"
3777
+ || origin.search !== ""
3778
+ || origin.hash !== ""
3779
+ || binding.origin !== origin.origin
3780
+ ) {
3781
+ throw new Error(`provider plugin surface ${binding.surfaceId} must declare an exact credential-free HTTPS origin`);
3782
+ }
3783
+ let runtimeOrigins: `https://${string}`[] = [];
3784
+ if (binding.transport === "provider-api") {
3785
+ const declaredRuntimeOrigins = binding.runtimeOrigins ?? [binding.origin];
3786
+ requireBoundedNonEmptyArray(
3787
+ declaredRuntimeOrigins,
3788
+ `provider plugin surface ${binding.surfaceId} runtimeOrigins`,
3789
+ 20,
3790
+ );
3791
+ runtimeOrigins = [...declaredRuntimeOrigins];
3792
+ for (const runtimeOrigin of runtimeOrigins) {
3793
+ let parsed: URL;
3794
+ try {
3795
+ parsed = new URL(runtimeOrigin);
3796
+ } catch {
3797
+ throw new Error(
3798
+ `provider plugin surface ${binding.surfaceId} has an invalid runtime origin`,
3799
+ );
3800
+ }
3801
+ if (
3802
+ parsed.protocol !== "https:"
3803
+ || parsed.username !== ""
3804
+ || parsed.password !== ""
3805
+ || parsed.pathname !== "/"
3806
+ || parsed.search !== ""
3807
+ || parsed.hash !== ""
3808
+ || runtimeOrigin !== parsed.origin
3809
+ ) {
3810
+ throw new Error(
3811
+ `provider plugin surface ${binding.surfaceId} must declare exact credential-free HTTPS runtime origins`,
3812
+ );
3813
+ }
3814
+ }
3815
+ if (new Set(runtimeOrigins).size !== runtimeOrigins.length) {
3816
+ throw new Error(
3817
+ `provider plugin surface ${binding.surfaceId} repeats a runtime origin`,
3818
+ );
3819
+ }
3820
+ if (!runtimeOrigins.includes(binding.origin)) {
3821
+ throw new Error(
3822
+ `provider plugin surface ${binding.surfaceId} runtime origins must include its primary origin`,
3823
+ );
3824
+ }
3825
+ runtimeOrigins.sort();
3826
+ }
3827
+ const declaredManifestOrigins = binding.manifestOrigins === undefined
3828
+ ? [binding.origin]
3829
+ : binding.manifestOrigins;
3830
+ requireBoundedNonEmptyArray(
3831
+ declaredManifestOrigins,
3832
+ `provider plugin surface ${binding.surfaceId} manifestOrigins`,
3833
+ 20,
3834
+ );
3835
+ const manifestOrigins = [...declaredManifestOrigins];
3836
+ for (const manifestOrigin of manifestOrigins) {
3837
+ let parsed: URL;
3838
+ try {
3839
+ parsed = new URL(manifestOrigin);
3840
+ } catch {
3841
+ throw new Error(
3842
+ `provider plugin surface ${binding.surfaceId} has an invalid manifest origin`,
3843
+ );
3844
+ }
3845
+ if (
3846
+ parsed.protocol !== "https:"
3847
+ || parsed.username !== ""
3848
+ || parsed.password !== ""
3849
+ || parsed.pathname !== "/"
3850
+ || parsed.search !== ""
3851
+ || parsed.hash !== ""
3852
+ || manifestOrigin !== parsed.origin
3853
+ ) {
3854
+ throw new Error(
3855
+ `provider plugin surface ${binding.surfaceId} must declare exact credential-free HTTPS manifest origins`,
3856
+ );
3857
+ }
3858
+ }
3859
+ if (new Set(manifestOrigins).size !== manifestOrigins.length) {
3860
+ throw new Error(
3861
+ `provider plugin surface ${binding.surfaceId} repeats a manifest origin`,
3862
+ );
3863
+ }
3864
+ const endpointHostnames = [
3865
+ ...runtimeOrigins.map((runtimeOrigin) =>
3866
+ new URL(runtimeOrigin).hostname.toLowerCase()),
3867
+ ...(binding.transport === "provider-api"
3868
+ ? []
3869
+ : [origin.hostname.toLowerCase()]),
3870
+ ...manifestOrigins.map((manifestOrigin) =>
3871
+ new URL(manifestOrigin).hostname.toLowerCase()),
3872
+ ];
3873
+ const declaredProtectedHostnameFamilies =
3874
+ binding.protectedHostnameFamilies === undefined
3875
+ ? [...new Set(endpointHostnames)]
3876
+ : binding.protectedHostnameFamilies;
3877
+ requireBoundedNonEmptyArray(
3878
+ declaredProtectedHostnameFamilies,
3879
+ `provider plugin surface ${binding.surfaceId} protectedHostnameFamilies`,
3880
+ 20,
3881
+ );
3882
+ const protectedHostnameFamilies = [...declaredProtectedHostnameFamilies];
3883
+ for (const family of protectedHostnameFamilies) {
3884
+ if (
3885
+ typeof family !== "string"
3886
+ || family !== family.toLowerCase()
3887
+ || family.length > 253
3888
+ || !/^[a-z0-9](?:[a-z0-9.-]{0,251}[a-z0-9])?$/u.test(family)
3889
+ || family.includes("..")
3890
+ ) {
3891
+ throw new Error(
3892
+ `provider plugin surface ${binding.surfaceId} has an invalid protected hostname family`,
3893
+ );
3894
+ }
3895
+ }
3896
+ if (
3897
+ new Set(protectedHostnameFamilies).size
3898
+ !== protectedHostnameFamilies.length
3899
+ ) {
3900
+ throw new Error(
3901
+ `provider plugin surface ${binding.surfaceId} repeats a protected hostname family`,
3902
+ );
3903
+ }
3904
+ for (const hostname of endpointHostnames) {
3905
+ if (
3906
+ !protectedHostnameFamilies.some((family) =>
3907
+ hostname === family || hostname.endsWith(`.${family}`))
3908
+ ) {
3909
+ throw new Error(
3910
+ `provider plugin surface ${binding.surfaceId} protected hostname families do not cover ${hostname}`,
3911
+ );
3912
+ }
3913
+ }
3914
+ requireBoundedNonEmptyArray(
3915
+ binding.authKinds,
3916
+ `provider plugin surface ${binding.surfaceId} authKinds`,
3917
+ binding.transport === "web-session-api" || binding.transport === "local-cli"
3918
+ ? 5
3919
+ : 1,
3920
+ );
3921
+ const acceptedAuthKinds = [...binding.authKinds];
3922
+ for (const kind of acceptedAuthKinds) {
3923
+ if (!authKinds.has(kind)) {
3924
+ throw new Error(`provider plugin surface ${binding.surfaceId} accepts unsupported auth kind ${String(kind)}`);
3925
+ }
3926
+ }
3927
+ if (new Set(acceptedAuthKinds).size !== acceptedAuthKinds.length) {
3928
+ throw new Error(`provider plugin surface ${binding.surfaceId} repeats an auth kind`);
3929
+ }
3930
+ acceptedAuthKinds.sort();
3931
+ if (
3932
+ binding.transport === "provider-api"
3933
+ && (acceptedAuthKinds.length !== 1 || acceptedAuthKinds[0] !== "oauth-token-file")
3934
+ ) {
3935
+ throw new Error(`provider plugin surface ${binding.surfaceId} provider-api auth must be oauth-token-file`);
3936
+ }
3937
+ if (
3938
+ binding.transport === "linked-device"
3939
+ && (acceptedAuthKinds.length !== 1 || acceptedAuthKinds[0] !== "linked-device-store")
3940
+ ) {
3941
+ throw new Error(`provider plugin surface ${binding.surfaceId} linked-device auth must be linked-device-store`);
3942
+ }
3943
+ if (
3944
+ binding.transport === "web-session-api"
3945
+ && acceptedAuthKinds.some((kind) => kind === "oauth-token-file" || kind === "linked-device-store")
3946
+ ) {
3947
+ throw new Error(`provider plugin surface ${binding.surfaceId} web-session-api auth must use browser-session credentials`);
3948
+ }
3949
+ const localCliTool = binding.transport === "local-cli"
3950
+ ? parseLocalCliToolIdentityV1(binding.tool)
3951
+ : undefined;
3952
+ requireBoundedNonEmptyArray(
3953
+ binding.operations,
3954
+ `provider plugin surface ${binding.surfaceId} operations`,
3955
+ MAX_PROVIDER_PLUGIN_OPERATIONS_PER_BINDING,
3956
+ );
3957
+ const operations = [...binding.operations].map((operation) =>
3958
+ freezeOperation(operation, binding.transport, sourceKind));
3959
+ operations.sort((left, right) =>
3960
+ left.name.localeCompare(right.name) || left.contractVersion - right.contractVersion);
3961
+ const operationKeys = operations.map((operation) =>
3962
+ `${operation.name}@${operation.contractVersion}`);
3963
+ if (new Set(operationKeys).size !== operationKeys.length) {
3964
+ throw new Error(`provider plugin surface ${binding.surfaceId} repeats an exact operation contract`);
3965
+ }
3966
+ const messaging = freezeProviderPluginMessaging(
3967
+ binding.messaging,
3968
+ operations,
3969
+ binding.surfaceId,
3970
+ );
3971
+ if (
3972
+ messaging?.action.state === "supported"
3973
+ && binding.transport === "provider-api"
3974
+ ) {
3975
+ throw new Error(
3976
+ `provider plugin surface ${binding.surfaceId} messaging actions require a cleanup-qualified session or local CLI transport`,
3977
+ );
3978
+ }
3979
+ if (
3980
+ typeof binding.subject !== "object"
3981
+ || binding.subject === null
3982
+ || typeof binding.subject.format !== "string"
3983
+ || binding.subject.format.length < 1
3984
+ || typeof binding.subject.matches !== "function"
3985
+ ) {
3986
+ throw new Error(`provider plugin surface ${binding.surfaceId} must declare a subject matcher`);
3987
+ }
3988
+ requireExactKeys(binding.subject, ["format", "matches"], "provider plugin subject");
3989
+ if (
3990
+ typeof binding.runtime !== "object"
3991
+ || binding.runtime === null
3992
+ || typeof binding.runtime.loadRuntime !== "function"
3993
+ ) {
3994
+ throw new Error(`provider plugin surface ${binding.surfaceId} must declare a lazy runtime loader`);
3995
+ }
3996
+ const common = {
3997
+ surfaceId: binding.surfaceId,
3998
+ origin: binding.origin,
3999
+ manifestOrigins: Object.freeze(manifestOrigins),
4000
+ protectedHostnameFamilies: Object.freeze(
4001
+ protectedHostnameFamilies.sort(),
4002
+ ),
4003
+ authKinds: Object.freeze(acceptedAuthKinds),
4004
+ ...(messaging === undefined ? {} : { messaging }),
4005
+ };
4006
+ if (binding.transport === "provider-api") {
4007
+ requireExactKeys(binding.runtime, ["loadRuntime"], "provider plugin runtime hooks");
4008
+ const loadRuntime = binding.runtime.loadRuntime;
4009
+ const result: ProviderApiPluginBindingV1 = Object.freeze({
4010
+ ...common,
4011
+ transport: "provider-api",
4012
+ runtimeOrigins: Object.freeze(runtimeOrigins),
4013
+ operations: Object.freeze(operations) as readonly ProviderApiPluginOperationV1[],
4014
+ subject: Object.freeze({ ...binding.subject }),
4015
+ loadRuntime,
4016
+ execute: async (context: ProviderActionContext) =>
4017
+ (await loadRuntime()).execute(context),
4018
+ ...(messaging?.action.state === "supported"
4019
+ ? {
4020
+ executeMessagingPart: async (
4021
+ operation: string,
4022
+ input: OperationInput,
4023
+ auth: GhostgetAuth,
4024
+ attempt: ProviderPluginMessagingActionAttemptV1,
4025
+ ) => {
4026
+ const hook = (await loadRuntime()).executeMessagingPart;
4027
+ if (hook === undefined) {
4028
+ throw new Error(
4029
+ `provider plugin surface ${binding.surfaceId} declared messaging actions without a runtime hook`,
4030
+ );
4031
+ }
4032
+ return hook(operation, input, auth, attempt);
4033
+ },
4034
+ }
4035
+ : {}),
4036
+ });
4037
+ return result;
4038
+ }
4039
+ if (binding.transport === "local-cli") {
4040
+ requireExactKeys(binding.runtime, ["loadRuntime"], "local CLI plugin runtime hooks");
4041
+ const loadRuntime = binding.runtime.loadRuntime;
4042
+ const reconciles = operations.some(
4043
+ (operation) => operation.reconciliation !== undefined,
4044
+ );
4045
+ const reconcile: NonNullable<LocalCliPluginRuntimeV1["reconcile"]> = async (
4046
+ operationName,
4047
+ input,
4048
+ auth,
4049
+ context,
4050
+ options,
4051
+ ) => {
4052
+ const selectedOperation = operations.find((operation) =>
4053
+ operation.name === operationName
4054
+ && operation.reconciliation !== undefined);
4055
+ if (selectedOperation === undefined) {
4056
+ throw new Error(
4057
+ `provider plugin surface ${binding.surfaceId} has no reconciliation contract for ${operationName}`,
4058
+ );
4059
+ }
4060
+ const reconciliationKind = selectedOperation.reconciliation?.kind;
4061
+ const reconciliationContext =
4062
+ reconciliationKind === "provider-accepted-target-presence"
4063
+ || reconciliationKind === "provider-bound-target-desired-state"
4064
+ ? (() => {
4065
+ const parsed = parseProviderPluginReconciliationContextV1(context);
4066
+ if (parsed.kind !== reconciliationKind) {
4067
+ throw new Error(
4068
+ `provider plugin surface ${binding.surfaceId} reconciliation target context kind changed`,
4069
+ );
4070
+ }
4071
+ return parsed;
4072
+ })()
4073
+ : (() => {
4074
+ if (context !== undefined) {
4075
+ throw new Error(
4076
+ `provider plugin surface ${binding.surfaceId} boolean reconciliation does not accept target context`,
4077
+ );
4078
+ }
4079
+ return undefined;
4080
+ })();
4081
+ const hook = (await loadRuntime()).reconcile;
4082
+ if (hook === undefined) {
4083
+ throw new Error(
4084
+ `provider plugin surface ${binding.surfaceId} declared reconciliation without a runtime hook`,
4085
+ );
4086
+ }
4087
+ const value = await hook(
4088
+ operationName,
4089
+ input,
4090
+ auth,
4091
+ reconciliationContext,
4092
+ options,
4093
+ );
4094
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
4095
+ throw new Error("provider plugin reconciliation returned an invalid readback");
4096
+ }
4097
+ requireExactKeys(
4098
+ value,
4099
+ ["actualState", "reason"],
4100
+ "provider plugin reconciliation readback",
4101
+ );
4102
+ if (
4103
+ typeof value.actualState !== "boolean"
4104
+ || typeof value.reason !== "string"
4105
+ || value.reason.length < 1
4106
+ || value.reason.length > 200
4107
+ ) {
4108
+ throw new Error("provider plugin reconciliation returned an invalid readback");
4109
+ }
4110
+ return Object.freeze({
4111
+ actualState: value.actualState,
4112
+ reason: value.reason,
4113
+ });
4114
+ };
4115
+ const result: LocalCliPluginBindingV1 = Object.freeze({
4116
+ ...common,
4117
+ transport: "local-cli",
4118
+ tool: localCliTool!,
4119
+ operations: Object.freeze(operations) as readonly LocalCliPluginOperationV1[],
4120
+ loadRuntime,
4121
+ subject: Object.freeze({
4122
+ ...binding.subject,
4123
+ probe: async (
4124
+ auth: GhostgetAuth,
4125
+ options?: ProviderPluginSubjectProbeOptionsV1,
4126
+ ) => {
4127
+ const subject = await (await loadRuntime()).probe(auth, options);
4128
+ if (
4129
+ typeof subject !== "string"
4130
+ || !binding.subject.matches(subject)
4131
+ ) {
4132
+ throw new Error(
4133
+ `provider plugin surface ${binding.surfaceId} returned a subject outside ${binding.subject.format}`,
4134
+ );
4135
+ }
4136
+ return subject;
4137
+ },
4138
+ }),
4139
+ inspect: async (environment, options) => {
4140
+ let artifact: LocalCliToolArtifactIdentityV1;
4141
+ try {
4142
+ artifact = localCliToolArtifactForCurrentRuntime(localCliTool!);
4143
+ } catch {
4144
+ return Object.freeze({
4145
+ ready: false,
4146
+ platform: process.platform,
4147
+ arch: process.arch,
4148
+ version: null,
4149
+ executableSha256: null,
4150
+ reason: `unsupported-runtime:${process.platform}/${process.arch}`,
4151
+ });
4152
+ }
4153
+ const cleanupFilesystem = inspectLocalCliCleanupFilesystemReadiness();
4154
+ if (!cleanupFilesystem.ready) {
4155
+ return Object.freeze({
4156
+ ready: false,
4157
+ platform: process.platform,
4158
+ arch: process.arch,
4159
+ version: null,
4160
+ executableSha256: null,
4161
+ reason: cleanupFilesystem.reason,
4162
+ });
4163
+ }
4164
+ const [ready, platform, arch, version, executableSha256, reason] =
4165
+ snapshotExactEnumerableDataProperties(
4166
+ await (await loadRuntime()).inspect(environment, options),
4167
+ [
4168
+ "ready",
4169
+ "platform",
4170
+ "arch",
4171
+ "version",
4172
+ "executableSha256",
4173
+ "reason",
4174
+ ],
4175
+ "local CLI runtime inspection status",
4176
+ );
4177
+ if (
4178
+ typeof ready !== "boolean"
4179
+ || typeof platform !== "string"
4180
+ || platform !== process.platform
4181
+ || typeof arch !== "string"
4182
+ || arch !== process.arch
4183
+ || (version !== null && (
4184
+ typeof version !== "string"
4185
+ || version.length < 1
4186
+ || version.length > 128
4187
+ || /[\u0000-\u001f\u007f-\u009f]/u.test(version)
4188
+ || hasUnpairedSurrogate(version)
4189
+ ))
4190
+ || (executableSha256 !== null && (
4191
+ typeof executableSha256 !== "string"
4192
+ || !/^[a-f0-9]{64}$/u.test(executableSha256)
4193
+ ))
4194
+ || (reason !== null && (
4195
+ typeof reason !== "string"
4196
+ || reason.length < 1
4197
+ || reason.length > 500
4198
+ || /[\u0000-\u001f\u007f-\u009f]/u.test(reason)
4199
+ || hasUnpairedSurrogate(reason)
4200
+ ))
4201
+ || (ready && (
4202
+ version !== localCliTool!.version
4203
+ || executableSha256 !== artifact.executableSha256
4204
+ || reason !== null
4205
+ ))
4206
+ || (!ready && reason === null)
4207
+ ) {
4208
+ throw new Error("local CLI runtime inspection returned an invalid status");
4209
+ }
4210
+ return Object.freeze({
4211
+ ready,
4212
+ platform,
4213
+ arch,
4214
+ version,
4215
+ executableSha256,
4216
+ reason,
4217
+ });
4218
+ },
4219
+ execute: async (manifest, recipe, input, auth, options) =>
4220
+ (await loadRuntime()).execute(manifest, recipe, input, auth, options),
4221
+ ...(messaging?.action.state === "supported"
4222
+ ? {
4223
+ executeMessagingPart: async (
4224
+ operation: string,
4225
+ input: OperationInput,
4226
+ auth: GhostgetAuth,
4227
+ attempt: ProviderPluginMessagingActionAttemptV1,
4228
+ ) => {
4229
+ const hook = (await loadRuntime()).executeMessagingPart;
4230
+ if (hook === undefined) {
4231
+ throw new Error(
4232
+ `provider plugin surface ${binding.surfaceId} declared messaging actions without a runtime hook`,
4233
+ );
4234
+ }
4235
+ return hook(operation, input, auth, attempt);
4236
+ },
4237
+ }
4238
+ : {}),
4239
+ ...(reconciles ? { reconcile } : {}),
4240
+ });
4241
+ return result;
4242
+ }
4243
+ requireExactKeys(binding.runtime, ["loadRuntime"], "web-session plugin runtime hooks");
4244
+ const loadRuntime = binding.runtime.loadRuntime;
4245
+ const execute: WebSessionOperationExecutor = async (
4246
+ manifest,
4247
+ recipe,
4248
+ input,
4249
+ auth,
4250
+ options,
4251
+ ) => (await loadRuntime()).execute(manifest, recipe, input, auth, options);
4252
+ const hasPublicOperations = operations.some(
4253
+ (operation) => operation.access === "public",
4254
+ );
4255
+ const executePublic: PublicWebSessionOperationExecutor = async (
4256
+ manifest,
4257
+ recipe,
4258
+ input,
4259
+ options,
4260
+ ) => {
4261
+ const runtime = await loadRuntime();
4262
+ if (runtime.executePublic === undefined) {
4263
+ throw new Error(
4264
+ `provider plugin surface ${binding.surfaceId} is missing its reviewed public runtime hook`,
4265
+ );
4266
+ }
4267
+ return runtime.executePublic(manifest, recipe, input, options);
4268
+ };
4269
+ const reconciles = operations.some(
4270
+ (operation) => operation.reconciliation !== undefined,
4271
+ );
4272
+ const reconcile: NonNullable<WebSessionPluginRuntimeV1["reconcile"]> = async (
4273
+ operationName,
4274
+ input,
4275
+ auth,
4276
+ context,
4277
+ ) => {
4278
+ const selectedOperation = operations.find((operation) =>
4279
+ operation.name === operationName
4280
+ && operation.reconciliation !== undefined);
4281
+ if (selectedOperation === undefined) {
4282
+ throw new Error(
4283
+ `provider plugin surface ${binding.surfaceId} has no reconciliation contract for ${operationName}`,
4284
+ );
4285
+ }
4286
+ const reconciliationKind = selectedOperation.reconciliation?.kind;
4287
+ const reconciliationContext =
4288
+ reconciliationKind === "provider-accepted-target-presence"
4289
+ || reconciliationKind === "provider-bound-target-desired-state"
4290
+ ? (() => {
4291
+ const parsed = parseProviderPluginReconciliationContextV1(context);
4292
+ if (parsed.kind !== reconciliationKind) {
4293
+ throw new Error(
4294
+ `provider plugin surface ${binding.surfaceId} reconciliation target context kind changed`,
4295
+ );
4296
+ }
4297
+ return parsed;
4298
+ })()
4299
+ : (() => {
4300
+ if (context !== undefined) {
4301
+ throw new Error(
4302
+ `provider plugin surface ${binding.surfaceId} boolean reconciliation does not accept target context`,
4303
+ );
4304
+ }
4305
+ return undefined;
4306
+ })();
4307
+ const hook = (await loadRuntime()).reconcile;
4308
+ if (hook === undefined) {
4309
+ throw new Error(
4310
+ `provider plugin surface ${binding.surfaceId} declared reconciliation without a runtime hook`,
4311
+ );
4312
+ }
4313
+ const value = await hook(
4314
+ operationName,
4315
+ input,
4316
+ auth,
4317
+ reconciliationContext,
4318
+ );
4319
+ if (
4320
+ typeof value !== "object"
4321
+ || value === null
4322
+ || Array.isArray(value)
4323
+ ) {
4324
+ throw new Error("provider plugin reconciliation returned an invalid readback");
4325
+ }
4326
+ requireExactKeys(
4327
+ value,
4328
+ ["actualState", "reason"],
4329
+ "provider plugin reconciliation readback",
4330
+ );
4331
+ if (
4332
+ typeof value.actualState !== "boolean"
4333
+ || typeof value.reason !== "string"
4334
+ || value.reason.length < 1
4335
+ || value.reason.length > 200
4336
+ ) {
4337
+ throw new Error("provider plugin reconciliation returned an invalid readback");
4338
+ }
4339
+ return Object.freeze({
4340
+ actualState: value.actualState,
4341
+ reason: value.reason,
4342
+ });
4343
+ };
4344
+ let linkedDeviceLifecycle:
4345
+ | ProviderPluginLinkedDeviceLifecycleRuntimeV1
4346
+ | undefined;
4347
+ if (binding.transport === "linked-device") {
4348
+ const declaration = binding.linkedDeviceLifecycle;
4349
+ if (declaration !== undefined) {
4350
+ requireExactKeys(
4351
+ declaration,
4352
+ ["inspect", "pair", "syncOnce"],
4353
+ "linked-device plugin lifecycle",
4354
+ );
4355
+ if (
4356
+ declaration.inspect !== true
4357
+ || declaration.pair !== true
4358
+ || declaration.syncOnce !== true
4359
+ ) {
4360
+ throw new Error(
4361
+ `provider plugin surface ${binding.surfaceId} has an invalid linked-device lifecycle declaration`,
4362
+ );
4363
+ }
4364
+ const requireLifecycle = async ():
4365
+ Promise<ProviderPluginLinkedDeviceLifecycleRuntimeV1> => {
4366
+ const lifecycle = (await loadRuntime()).linkedDeviceLifecycle;
4367
+ if (lifecycle === undefined) {
4368
+ throw new Error(
4369
+ `provider plugin surface ${binding.surfaceId} declared linked-device lifecycle capabilities without runtime hooks`,
4370
+ );
4371
+ }
4372
+ return lifecycle;
4373
+ };
4374
+ linkedDeviceLifecycle = Object.freeze({
4375
+ inspect: async (environment) => {
4376
+ const value = await (await requireLifecycle()).inspect(environment);
4377
+ if (
4378
+ typeof value !== "object"
4379
+ || value === null
4380
+ || Array.isArray(value)
4381
+ ) {
4382
+ throw new Error("linked-device lifecycle inspection returned invalid status");
4383
+ }
4384
+ const keys = [
4385
+ "ready",
4386
+ "implementation",
4387
+ "version",
4388
+ "integrity",
4389
+ ...(value.setupCommand === undefined ? [] : ["setupCommand"]),
4390
+ ];
4391
+ requireExactKeys(
4392
+ value,
4393
+ keys,
4394
+ "linked-device lifecycle inspection status",
4395
+ );
4396
+ if (
4397
+ typeof value.ready !== "boolean"
4398
+ || typeof value.implementation !== "string"
4399
+ || value.implementation.length < 1
4400
+ || typeof value.version !== "string"
4401
+ || value.version.length < 1
4402
+ || typeof value.integrity !== "string"
4403
+ || value.integrity.length < 1
4404
+ || (
4405
+ value.setupCommand !== undefined
4406
+ && (
4407
+ typeof value.setupCommand !== "string"
4408
+ || value.setupCommand.length < 1
4409
+ )
4410
+ )
4411
+ ) {
4412
+ throw new Error("linked-device lifecycle inspection returned invalid status");
4413
+ }
4414
+ return Object.freeze({ ...value });
4415
+ },
4416
+ pair: async (auth, options) => {
4417
+ const subject = await (await requireLifecycle()).pair(auth, options);
4418
+ if (
4419
+ typeof subject !== "string"
4420
+ || !binding.subject.matches(subject)
4421
+ ) {
4422
+ throw new Error("linked-device lifecycle pairing returned an invalid subject");
4423
+ }
4424
+ return subject;
4425
+ },
4426
+ syncOnce: async (auth, options) => {
4427
+ const value = await (await requireLifecycle()).syncOnce(auth, options);
4428
+ if (
4429
+ typeof value !== "object"
4430
+ || value === null
4431
+ || Array.isArray(value)
4432
+ ) {
4433
+ throw new Error("linked-device lifecycle sync returned an invalid result");
4434
+ }
4435
+ requireExactKeys(
4436
+ value,
4437
+ ["itemsStored", "projection", "emitsProtocolAcknowledgements"],
4438
+ "linked-device lifecycle sync result",
4439
+ );
4440
+ if (
4441
+ !Number.isSafeInteger(value.itemsStored)
4442
+ || value.itemsStored < 0
4443
+ || typeof value.projection !== "string"
4444
+ || value.projection.length < 1
4445
+ || value.emitsProtocolAcknowledgements !== true
4446
+ ) {
4447
+ throw new Error("linked-device lifecycle sync returned an invalid result");
4448
+ }
4449
+ return Object.freeze({ ...value });
4450
+ },
4451
+ });
4452
+ }
4453
+ }
4454
+ const result: WebSessionApiPluginBindingV1 | LinkedDevicePluginBindingV1 =
4455
+ Object.freeze({
4456
+ ...common,
4457
+ transport: binding.transport,
4458
+ operations: Object.freeze(operations) as readonly WebSessionPluginOperationV1[],
4459
+ loadRuntime,
4460
+ subject: Object.freeze({
4461
+ ...binding.subject,
4462
+ probe: async (
4463
+ auth: GhostgetAuth,
4464
+ options?: ProviderPluginSubjectProbeOptionsV1,
4465
+ ) => {
4466
+ const subject = await (await loadRuntime()).probe(auth, options);
4467
+ if (
4468
+ typeof subject !== "string"
4469
+ || !binding.subject.matches(subject)
4470
+ ) {
4471
+ throw new Error(
4472
+ `provider plugin surface ${binding.surfaceId} returned a subject outside ${binding.subject.format}`,
4473
+ );
4474
+ }
4475
+ return subject;
4476
+ },
4477
+ }),
4478
+ execute,
4479
+ ...(messaging?.action.state === "supported"
4480
+ ? {
4481
+ executeMessagingPart: async (
4482
+ operation: string,
4483
+ input: OperationInput,
4484
+ auth: GhostgetAuth,
4485
+ attempt: ProviderPluginMessagingActionAttemptV1,
4486
+ ) => {
4487
+ const hook = (await loadRuntime()).executeMessagingPart;
4488
+ if (hook === undefined) {
4489
+ throw new Error(
4490
+ `provider plugin surface ${binding.surfaceId} declared messaging actions without a runtime hook`,
4491
+ );
4492
+ }
4493
+ return hook(operation, input, auth, attempt);
4494
+ },
4495
+ }
4496
+ : {}),
4497
+ ...(hasPublicOperations ? { executePublic } : {}),
4498
+ ...(reconciles ? { reconcile } : {}),
4499
+ ...(linkedDeviceLifecycle === undefined ? {} : { linkedDeviceLifecycle }),
4500
+ });
4501
+ return result;
4502
+ }
4503
+
4504
+ /**
4505
+ * Validate and deeply freeze one trusted source plugin definition.
4506
+ *
4507
+ * V1 intentionally accepts only statically imported metadata. Runtime hooks
4508
+ * must remain behind a lazy loader; executable plugin code is not sandboxed.
4509
+ */
4510
+ export function defineProviderPlugin(
4511
+ plugin: ProviderPluginDefinitionV1,
4512
+ ): ProviderPluginV1 {
4513
+ requireExactKeys(
4514
+ plugin,
4515
+ [
4516
+ "apiVersion",
4517
+ "id",
4518
+ "version",
4519
+ "displayName",
4520
+ "sourceKind",
4521
+ "implementationSources",
4522
+ "bindings",
4523
+ ],
4524
+ "provider plugin",
4525
+ );
4526
+ if (plugin.apiVersion !== PROVIDER_PLUGIN_API_VERSION) {
4527
+ throw new Error(`provider plugin ${String(plugin.id)} uses unsupported API version ${String(plugin.apiVersion)}`);
4528
+ }
4529
+ if (!isProviderPluginId(plugin.id)) {
4530
+ throw new Error("provider plugin ID must be strict lowercase kebab-case with at most 63 characters");
4531
+ }
4532
+ if (!pluginVersionPattern.test(plugin.version)) {
4533
+ throw new Error(`provider plugin ${plugin.id} must declare a semantic version`);
4534
+ }
4535
+ if (typeof plugin.displayName !== "string" || plugin.displayName.length < 1 || plugin.displayName.length > 100) {
4536
+ throw new Error(`provider plugin ${plugin.id} must declare a display name`);
4537
+ }
4538
+ if (plugin.sourceKind !== "built-in" && plugin.sourceKind !== "source") {
4539
+ throw new Error(`provider plugin ${plugin.id} has an unsupported source kind`);
4540
+ }
4541
+ requireBoundedNonEmptyArray(
4542
+ plugin.implementationSources,
4543
+ `provider plugin ${plugin.id} implementationSources`,
4544
+ MAX_PROVIDER_PLUGIN_IMPLEMENTATION_SOURCES,
4545
+ );
4546
+ const sources = [...plugin.implementationSources].map((source) => {
4547
+ requireExactKeys(source, ["label", "url"], `provider plugin ${plugin.id} implementation source`);
4548
+ if (!sourceLabelPattern.test(source.label) || source.label.includes("..") || source.label.startsWith("/")) {
4549
+ throw new Error(`provider plugin ${plugin.id} has an unsafe implementation source label`);
4550
+ }
4551
+ const localFileProtocol = ["file", ":"].join("");
4552
+ if (!(source.url instanceof URL) || source.url.protocol !== localFileProtocol) {
4553
+ throw new Error(`provider plugin ${plugin.id} implementation sources must be file URLs`);
4554
+ }
4555
+ let stats: ReturnType<typeof lstatSync>;
4556
+ let realPath: string;
4557
+ try {
4558
+ const path = fileURLToPath(source.url);
4559
+ stats = lstatSync(path);
4560
+ realPath = realpathSync(path);
4561
+ } catch {
4562
+ throw new Error(`provider plugin ${plugin.id} implementation source ${source.label} is unreadable`);
4563
+ }
4564
+ const relativePath = relative(providerPluginSourceRoot, realPath);
4565
+ if (
4566
+ stats.isSymbolicLink()
4567
+ || !stats.isFile()
4568
+ || relativePath === ""
4569
+ || relativePath.startsWith("..")
4570
+ || isAbsolute(relativePath)
4571
+ ) {
4572
+ throw new Error(
4573
+ `provider plugin ${plugin.id} implementation source ${source.label} must be a regular file under the Ghostget source root`,
4574
+ );
4575
+ }
4576
+ return Object.freeze({ label: source.label, path: realPath });
4577
+ });
4578
+ sources.sort((left, right) => left.label.localeCompare(right.label));
4579
+ if (new Set(sources.map((source) => source.label)).size !== sources.length) {
4580
+ throw new Error(`provider plugin ${plugin.id} repeats an implementation source label`);
4581
+ }
4582
+ if (!sources.some((source) => source.label === "plugin.ts")) {
4583
+ throw new Error(`provider plugin ${plugin.id} must bind its plugin.ts implementation source`);
4584
+ }
4585
+ requireBoundedNonEmptyArray(
4586
+ plugin.bindings,
4587
+ `provider plugin ${plugin.id} bindings`,
4588
+ MAX_PROVIDER_PLUGIN_BINDINGS,
4589
+ );
4590
+ let operationCount = 0;
4591
+ for (const binding of plugin.bindings) {
4592
+ requireBoundedNonEmptyArray(
4593
+ binding.operations,
4594
+ `provider plugin surface ${String(binding.surfaceId)} operations`,
4595
+ MAX_PROVIDER_PLUGIN_OPERATIONS_PER_BINDING,
4596
+ );
4597
+ operationCount += binding.operations.length;
4598
+ if (operationCount > MAX_PROVIDER_PLUGIN_OPERATIONS) {
4599
+ throw new Error(
4600
+ `provider plugin ${plugin.id} may declare at most ${MAX_PROVIDER_PLUGIN_OPERATIONS} operations`,
4601
+ );
4602
+ }
4603
+ }
4604
+ const bindings = [...plugin.bindings].map((binding) =>
4605
+ freezeBinding(binding, plugin.sourceKind));
4606
+ for (const binding of bindings) {
4607
+ if (!providerPluginLazyRuntimeLoaders.has(binding.loadRuntime)) {
4608
+ throw new Error(
4609
+ `provider plugin ${plugin.id} runtime must use the branded lazy runtime helper`,
4610
+ );
4611
+ }
4612
+ }
4613
+ bindings.sort((left, right) =>
4614
+ left.transport.localeCompare(right.transport) || left.surfaceId.localeCompare(right.surfaceId));
4615
+ const result = Object.freeze({
4616
+ ...plugin,
4617
+ implementationSources: Object.freeze(sources),
4618
+ bindings: Object.freeze(bindings),
4619
+ });
4620
+ const evaluationIdentity = captureProviderPluginEvaluationIdentity(
4621
+ plugin.id,
4622
+ sources,
4623
+ );
4624
+ providerPluginEvaluationSourceDigests.set(
4625
+ result,
4626
+ evaluationIdentity.sourceDigests,
4627
+ );
4628
+ providerPluginEvaluationInstalledPackageDigests.set(
4629
+ result,
4630
+ evaluationIdentity.installedPackageDigests,
4631
+ );
4632
+ validatedProviderPlugins.add(result);
4633
+ return result;
4634
+ }
4635
+
4636
+ function freezePortableManifestValue<T>(value: T): T {
4637
+ if (typeof value !== "object" || value === null || Object.isFrozen(value)) {
4638
+ return value;
4639
+ }
4640
+ if (Array.isArray(value)) {
4641
+ for (const entry of value) freezePortableManifestValue(entry);
4642
+ return Object.freeze(value);
4643
+ }
4644
+ for (const entry of Object.values(value as Record<string, unknown>)) {
4645
+ freezePortableManifestValue(entry);
4646
+ }
4647
+ return Object.freeze(value);
4648
+ }
4649
+
4650
+ /**
4651
+ * Project one already verified, content-addressed portable package into the
4652
+ * same logical registry shape as trusted source plugins.
4653
+ *
4654
+ * This does not trust package code in-process. Every runtime hook supplied by
4655
+ * the caller must be a kernel-owned wrapper around the portable child host.
4656
+ */
4657
+ export function definePortableProviderPluginProjection(
4658
+ plugin: PortableProviderPluginProjectionDefinitionV1,
4659
+ ): ProviderPluginV1 {
4660
+ const [
4661
+ apiVersion,
4662
+ id,
4663
+ version,
4664
+ displayName,
4665
+ sourceKind,
4666
+ packageValue,
4667
+ rawBindings,
4668
+ ] = snapshotExactEnumerableDataProperties(
4669
+ plugin,
4670
+ [
4671
+ "apiVersion",
4672
+ "id",
4673
+ "version",
4674
+ "displayName",
4675
+ "sourceKind",
4676
+ "package",
4677
+ "bindings",
4678
+ ],
4679
+ "portable provider plugin projection",
4680
+ );
4681
+ if (
4682
+ apiVersion !== PROVIDER_PLUGIN_API_VERSION
4683
+ || sourceKind !== "portable"
4684
+ || !isProviderPluginId(id)
4685
+ || typeof version !== "string"
4686
+ || !pluginVersionPattern.test(version)
4687
+ || typeof displayName !== "string"
4688
+ || displayName.length < 1
4689
+ || displayName.length > 160
4690
+ || !isVerifiedPortableProviderPluginPackage(packageValue)
4691
+ || packageValue.manifest.id !== id
4692
+ || packageValue.manifest.version !== version
4693
+ || packageValue.manifest.displayName !== displayName
4694
+ || packageValue.manifest.hostApiVersion !== 1
4695
+ ) {
4696
+ throw new Error("portable provider plugin projection identity is invalid");
4697
+ }
4698
+ const portablePackage = Object.freeze({
4699
+ hostApiVersion: packageValue.manifest.hostApiVersion,
4700
+ bundleSha256: packageValue.bundleSha256,
4701
+ manifestSha256: packageValue.manifestSha256,
4702
+ capabilities: packageValue.manifest.capabilities,
4703
+ });
4704
+ const bindings = snapshotBoundedDenseArray(
4705
+ rawBindings,
4706
+ `portable provider plugin ${id} bindings`,
4707
+ MAX_PROVIDER_PLUGIN_BINDINGS,
4708
+ );
4709
+ let portableOperationCount = 0;
4710
+ const adapterIds = new Set<string>();
4711
+ const projected = bindings.map((rawEntry, index) => {
4712
+ const [
4713
+ adapterIdValue,
4714
+ manifestValue,
4715
+ portableBindingValue,
4716
+ bindingValue,
4717
+ ] = snapshotExactEnumerableDataProperties(
4718
+ rawEntry,
4719
+ ["adapterId", "manifest", "portableBinding", "binding"],
4720
+ `portable provider plugin ${id} binding projection ${index}`,
4721
+ );
4722
+ if (
4723
+ typeof adapterIdValue !== "string"
4724
+ || !/^[a-z][a-z0-9-]{0,47}$/u.test(adapterIdValue)
4725
+ ) {
4726
+ throw new Error(
4727
+ `portable provider plugin ${id} binding projection ${index} adapterId must be lowercase kebab-case with at most 48 characters`,
4728
+ );
4729
+ }
4730
+ if (adapterIds.has(adapterIdValue)) {
4731
+ throw new Error(
4732
+ `portable provider plugin ${id} repeats adapter ID ${adapterIdValue}`,
4733
+ );
4734
+ }
4735
+ if (
4736
+ typeof manifestValue !== "object"
4737
+ || manifestValue === null
4738
+ || typeof portableBindingValue !== "object"
4739
+ || portableBindingValue === null
4740
+ || typeof bindingValue !== "object"
4741
+ || bindingValue === null
4742
+ ) {
4743
+ throw new Error(
4744
+ `portable provider plugin ${id} has an invalid binding projection at index ${index}`,
4745
+ );
4746
+ }
4747
+ const adapterId = adapterIdValue;
4748
+ const manifest = manifestValue as GhostgetManifest;
4749
+ const portableBinding =
4750
+ portableBindingValue as PortablePackageBindingV1;
4751
+ const bindingDefinition =
4752
+ bindingValue as ProviderPluginBindingDefinitionV1;
4753
+ if (bindingDefinition.transport === "local-cli") {
4754
+ throw new Error(
4755
+ `portable provider plugin ${id} cannot project a source-only local-cli binding`,
4756
+ );
4757
+ }
4758
+ adapterIds.add(adapterId);
4759
+ assertKernelPortableProviderPluginBindingProjection(
4760
+ bindingDefinition,
4761
+ packageValue,
4762
+ portableBinding,
4763
+ adapterId,
4764
+ manifest,
4765
+ );
4766
+ const binding = freezeBinding(bindingDefinition, "portable");
4767
+ if (binding.operations.length > MAX_PROVIDER_PLUGIN_OPERATIONS_PER_BINDING) {
4768
+ throw new Error(
4769
+ `provider plugin surface ${binding.surfaceId} operations may contain at most ${MAX_PROVIDER_PLUGIN_OPERATIONS_PER_BINDING} items`,
4770
+ );
4771
+ }
4772
+ portableOperationCount += binding.operations.length;
4773
+ if (portableOperationCount > MAX_PROVIDER_PLUGIN_OPERATIONS) {
4774
+ throw new Error(
4775
+ `portable provider plugin ${id} may declare at most ${MAX_PROVIDER_PLUGIN_OPERATIONS} operations`,
4776
+ );
4777
+ }
4778
+ const operationNames = Object.keys(manifest.operations).sort();
4779
+ const bindingOperationNames = binding.operations
4780
+ .map((operation) => operation.name)
4781
+ .sort();
4782
+ const expectedSchema = binding.transport === "provider-api" ? 3 : 4;
4783
+ if (
4784
+ portableBinding.adapterId !== adapterId
4785
+ || portableBinding.transport !== binding.transport
4786
+ || portableBinding.surfaceId !== binding.surfaceId
4787
+ || portableBinding.origin !== binding.origin
4788
+ || portableBinding.subject.format !== binding.subject.format
4789
+ || portableBinding.authKinds.length !== binding.authKinds.length
4790
+ || portableBinding.authKinds.some(
4791
+ (kind, index) => kind !== binding.authKinds[index],
4792
+ )
4793
+ || manifest.id !== adapterId
4794
+ || manifest.schemaVersion !== expectedSchema
4795
+ || manifest.surfaceId !== binding.surfaceId
4796
+ || manifest.origins.length !== binding.manifestOrigins.length
4797
+ || manifest.origins.some(
4798
+ (origin, index) => origin !== binding.manifestOrigins[index],
4799
+ )
4800
+ || operationNames.length !== bindingOperationNames.length
4801
+ || operationNames.some(
4802
+ (operation, index) => operation !== bindingOperationNames[index],
4803
+ )
4804
+ ) {
4805
+ throw new Error(
4806
+ `portable provider plugin ${id} adapter ${adapterId} does not match its binding`,
4807
+ );
4808
+ }
4809
+ const operationIdentities = new Map<
4810
+ string,
4811
+ PortableOperationIdentityV1
4812
+ >();
4813
+ for (const operation of binding.operations) {
4814
+ if (
4815
+ operation.contractVersions.length !== 1
4816
+ || operation.contractVersions[0] !== operation.contractVersion
4817
+ ) {
4818
+ throw new Error(
4819
+ `portable provider plugin ${id} operation ${operation.name} must own one current contract version`,
4820
+ );
4821
+ }
4822
+ const portableOperation = portableBinding.operations.find(
4823
+ (candidate) =>
4824
+ candidate.name === operation.name
4825
+ && candidate.contractVersion === operation.contractVersion,
4826
+ );
4827
+ if (
4828
+ portableOperation === undefined
4829
+ || portableOperation.risk !== operation.risk
4830
+ || portableOperation.state !== operation.state
4831
+ || portableOperation.dispatch !== operation.dispatch
4832
+ || portableOperation.sideEffect !== operation.sideEffect
4833
+ || portableOperation.idempotency !== operation.idempotency
4834
+ || portableOperation.dedupeWindowMs !== operation.dedupeWindowMs
4835
+ || portableOperation.implementation !== operation.implementation
4836
+ || JSON.stringify(portableOperation.input)
4837
+ !== JSON.stringify(operation.input)
4838
+ ) {
4839
+ throw new Error(
4840
+ `portable provider plugin ${id} operation ${operation.name} metadata does not match its runtime projection`,
4841
+ );
4842
+ }
4843
+ const identity = createPortableOperationIdentityV1({
4844
+ package: {
4845
+ id,
4846
+ version,
4847
+ ...portablePackage,
4848
+ },
4849
+ binding: portableBinding,
4850
+ operation: portableOperation,
4851
+ });
4852
+ operationIdentities.set(
4853
+ `${operation.name}@${operation.contractVersion}`,
4854
+ identity,
4855
+ );
4856
+ }
4857
+ if (
4858
+ operationIdentities.size !== portableBinding.operations.length
4859
+ ) {
4860
+ throw new Error(
4861
+ `portable provider plugin ${id} binding operation metadata is incomplete`,
4862
+ );
4863
+ }
4864
+ const projectedManifest = freezePortableManifestValue(
4865
+ structuredClone(manifest),
4866
+ );
4867
+ portableProviderPluginAdapters.set(binding, Object.freeze({
4868
+ adapterId,
4869
+ manifest: projectedManifest,
4870
+ }));
4871
+ portableProviderPluginOperationIdentities.set(
4872
+ binding,
4873
+ Object.freeze(operationIdentities),
4874
+ );
4875
+ const subjectProbe = portableBinding.subject.probe;
4876
+ if (subjectProbe !== null) {
4877
+ const identity = operationIdentities.get(
4878
+ `${subjectProbe.operation}@${subjectProbe.contractVersion}`,
4879
+ );
4880
+ if (identity === undefined) {
4881
+ throw new Error(
4882
+ `portable provider plugin ${id} subject probe identity is missing`,
4883
+ );
4884
+ }
4885
+ portableProviderPluginSubjectProbeIdentities.set(binding, identity);
4886
+ }
4887
+ return binding;
4888
+ });
4889
+ projected.sort((left, right) =>
4890
+ left.transport.localeCompare(right.transport)
4891
+ || left.surfaceId.localeCompare(right.surfaceId));
4892
+ const result: ProviderPluginV1 = Object.freeze({
4893
+ apiVersion,
4894
+ id,
4895
+ version,
4896
+ displayName,
4897
+ sourceKind: "portable",
4898
+ implementationSources: Object.freeze([]),
4899
+ bindings: Object.freeze(projected),
4900
+ });
4901
+ validatedProviderPlugins.add(result);
4902
+ portableProviderPluginArtifacts.set(
4903
+ result,
4904
+ packageValue.bundleSha256,
4905
+ );
4906
+ return result;
4907
+ }