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