@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
package/dist/client.js
ADDED
|
@@ -0,0 +1,1605 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
PROVIDER_PLUGIN_ID_MAX_LENGTH,
|
|
4
|
+
PROVIDER_PLUGIN_OPERATION_NAME_MAX_LENGTH,
|
|
5
|
+
isProviderPluginOperationName,
|
|
6
|
+
isProviderPluginSurfaceId
|
|
7
|
+
} from "./index-26yq8q16.js";
|
|
8
|
+
import {
|
|
9
|
+
canonicalJson,
|
|
10
|
+
sha256
|
|
11
|
+
} from "./index-gwk7rbyj.js";
|
|
12
|
+
|
|
13
|
+
// src/client.ts
|
|
14
|
+
import { spawn, spawnSync } from "child_process";
|
|
15
|
+
import { existsSync } from "fs";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { types as nodeTypes } from "util";
|
|
18
|
+
|
|
19
|
+
// src/provider-plugin-portable-identity.ts
|
|
20
|
+
var PORTABLE_OPERATION_IDENTITY_VERSION = 1;
|
|
21
|
+
var descriptorDomain = `io-portable-operation-descriptor-v${PORTABLE_OPERATION_IDENTITY_VERSION}\x00`;
|
|
22
|
+
var pluginIdPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
23
|
+
var pluginVersionPattern = /^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
|
|
24
|
+
var sha256Pattern = /^[a-f0-9]{64}$/u;
|
|
25
|
+
var adapterIdPattern = /^[a-z][a-z0-9-]{0,47}$/u;
|
|
26
|
+
var operationPattern = /^[a-z][a-z0-9-]{0,39}(?:\.[a-z][a-z0-9-]{0,39}){1,3}$/u;
|
|
27
|
+
function ownDataValue(value, key, label) {
|
|
28
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
29
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor)) {
|
|
30
|
+
throw new Error(`${label} contains unsupported accessor state`);
|
|
31
|
+
}
|
|
32
|
+
return descriptor.value;
|
|
33
|
+
}
|
|
34
|
+
function identityRecord(value) {
|
|
35
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
36
|
+
throw new Error("portable operation identity must be an object");
|
|
37
|
+
}
|
|
38
|
+
const prototype = Object.getPrototypeOf(value);
|
|
39
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
40
|
+
throw new Error("portable operation identity must be a plain object");
|
|
41
|
+
}
|
|
42
|
+
const keys = Reflect.ownKeys(value);
|
|
43
|
+
const expected = [
|
|
44
|
+
"pluginId",
|
|
45
|
+
"pluginVersion",
|
|
46
|
+
"hostApiVersion",
|
|
47
|
+
"bundleSha256",
|
|
48
|
+
"manifestSha256",
|
|
49
|
+
"adapterId",
|
|
50
|
+
"transport",
|
|
51
|
+
"surfaceId",
|
|
52
|
+
"operation",
|
|
53
|
+
"contractVersion",
|
|
54
|
+
"descriptorSha256"
|
|
55
|
+
].sort();
|
|
56
|
+
if (keys.some((key) => typeof key !== "string") || keys.length !== expected.length || keys.sort().some((key, index) => key !== expected[index])) {
|
|
57
|
+
throw new Error("portable operation identity has unsupported fields");
|
|
58
|
+
}
|
|
59
|
+
const result = {};
|
|
60
|
+
for (const key of expected) {
|
|
61
|
+
result[key] = ownDataValue(value, key, `portable operation identity.${key}`);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
function parsePortableOperationIdentityV1(value) {
|
|
66
|
+
const record = identityRecord(value);
|
|
67
|
+
if (typeof record.pluginId !== "string" || record.pluginId.length > 128 || !pluginIdPattern.test(record.pluginId) || typeof record.pluginVersion !== "string" || record.pluginVersion.length > 128 || !pluginVersionPattern.test(record.pluginVersion) || record.hostApiVersion !== 1 || typeof record.bundleSha256 !== "string" || !sha256Pattern.test(record.bundleSha256) || typeof record.manifestSha256 !== "string" || !sha256Pattern.test(record.manifestSha256) || typeof record.adapterId !== "string" || !adapterIdPattern.test(record.adapterId) || record.transport !== "provider-api" && record.transport !== "web-session-api" && record.transport !== "linked-device" || typeof record.surfaceId !== "string" || record.surfaceId.length > 128 || !pluginIdPattern.test(record.surfaceId) || typeof record.operation !== "string" || !operationPattern.test(record.operation) || typeof record.contractVersion !== "number" || !Number.isSafeInteger(record.contractVersion) || record.contractVersion < 1 || record.contractVersion > 1e6 || typeof record.descriptorSha256 !== "string" || !sha256Pattern.test(record.descriptorSha256)) {
|
|
68
|
+
throw new Error("portable operation identity is malformed");
|
|
69
|
+
}
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
pluginId: record.pluginId,
|
|
72
|
+
pluginVersion: record.pluginVersion,
|
|
73
|
+
hostApiVersion: 1,
|
|
74
|
+
bundleSha256: record.bundleSha256,
|
|
75
|
+
manifestSha256: record.manifestSha256,
|
|
76
|
+
adapterId: record.adapterId,
|
|
77
|
+
transport: record.transport,
|
|
78
|
+
surfaceId: record.surfaceId,
|
|
79
|
+
operation: record.operation,
|
|
80
|
+
contractVersion: record.contractVersion,
|
|
81
|
+
descriptorSha256: record.descriptorSha256
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/client.ts
|
|
86
|
+
var MAX_INPUT_BYTES = 1024 * 1024;
|
|
87
|
+
var MAX_OUTPUT_BYTES = 20 * 1024 * 1024;
|
|
88
|
+
var MAX_ERROR_BYTES = 8 * 1024;
|
|
89
|
+
var MAX_RUN_RECEIPT_ERROR_BYTES = 12 * 1024;
|
|
90
|
+
var MAX_FRESH_FOR_MS = 365 * 24 * 60 * 60 * 1000;
|
|
91
|
+
var MAX_INPUT_DEPTH = 64;
|
|
92
|
+
var MAX_INPUT_NODES = 1e5;
|
|
93
|
+
var abortSignalAbortedGetter = (() => {
|
|
94
|
+
const descriptor = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted");
|
|
95
|
+
const getter = descriptor === undefined ? undefined : Reflect.get(descriptor, "get");
|
|
96
|
+
return typeof getter !== "function" ? undefined : (value) => Reflect.apply(getter, value, []);
|
|
97
|
+
})();
|
|
98
|
+
var PUBLIC_WEB_SESSION_AUTHORITY_KIND = "public-web-session";
|
|
99
|
+
function publicWebSessionAuthority(request) {
|
|
100
|
+
const coordinate = Object.freeze({
|
|
101
|
+
adapter: request.adapterId,
|
|
102
|
+
operation: request.operationId
|
|
103
|
+
});
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
schemaVersion: 1,
|
|
106
|
+
id: `public-${sha256(canonicalJson(coordinate)).slice(0, 32)}`,
|
|
107
|
+
kind: PUBLIC_WEB_SESSION_AUTHORITY_KIND,
|
|
108
|
+
subject: `public:${request.adapterId}:${request.operationId}`
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function expectedRequestAuthId(request, authKind) {
|
|
112
|
+
if (authKind === PUBLIC_WEB_SESSION_AUTHORITY_KIND) {
|
|
113
|
+
if (request.authId !== undefined) {
|
|
114
|
+
throw new Error("Ghostget public invocation unexpectedly accepted an auth locator");
|
|
115
|
+
}
|
|
116
|
+
return publicWebSessionAuthority(request).id;
|
|
117
|
+
}
|
|
118
|
+
return request.authId ?? request.adapterId;
|
|
119
|
+
}
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
function isUnknownArray(value) {
|
|
124
|
+
return Array.isArray(value);
|
|
125
|
+
}
|
|
126
|
+
function record(value, label) {
|
|
127
|
+
if (!isRecord(value) || nodeTypes.isProxy(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
|
128
|
+
throw new Error(`${label} must be a plain data object`);
|
|
129
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
130
|
+
if (Reflect.ownKeys(descriptors).some((key) => typeof key !== "string")) {
|
|
131
|
+
throw new Error(`${label} must be a plain data object`);
|
|
132
|
+
}
|
|
133
|
+
const result = {};
|
|
134
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
135
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
136
|
+
throw new Error(`${label} must be a plain data object`);
|
|
137
|
+
}
|
|
138
|
+
Object.defineProperty(result, key, {
|
|
139
|
+
configurable: true,
|
|
140
|
+
enumerable: true,
|
|
141
|
+
value: descriptor.value,
|
|
142
|
+
writable: true
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
function assertExactKeys(value, required, optional, label) {
|
|
148
|
+
const allowed = new Set([...required, ...optional]);
|
|
149
|
+
const keys = Object.keys(value);
|
|
150
|
+
if (required.some((key) => !Object.hasOwn(value, key)) || keys.some((key) => !allowed.has(key)))
|
|
151
|
+
throw new Error(`${label} is malformed`);
|
|
152
|
+
}
|
|
153
|
+
var clientReadFailureRetryDisposition = Object.freeze({
|
|
154
|
+
"target-unavailable": "do-not-retry",
|
|
155
|
+
"auth-repair-required": "repair-auth",
|
|
156
|
+
"account-mismatch": "do-not-retry",
|
|
157
|
+
"provider-throttled": "retry-once-after-60s",
|
|
158
|
+
"provider-temporary": "retry-once-after-60s",
|
|
159
|
+
"operation-timeout": "retry-once-after-60s",
|
|
160
|
+
"contract-drift": "do-not-retry",
|
|
161
|
+
"cleanup-required": "do-not-retry"
|
|
162
|
+
});
|
|
163
|
+
function parseClientReadFailure(value) {
|
|
164
|
+
const failure = record(value, "Ghostget read failure");
|
|
165
|
+
assertExactKeys(failure, ["category", "retryDisposition"], [], "Ghostget read failure");
|
|
166
|
+
const category = failure.category;
|
|
167
|
+
if (typeof category !== "string" || !Object.hasOwn(clientReadFailureRetryDisposition, category))
|
|
168
|
+
throw new Error("Ghostget read failure category is malformed");
|
|
169
|
+
const retryDisposition = clientReadFailureRetryDisposition[category];
|
|
170
|
+
if (failure.retryDisposition !== retryDisposition) {
|
|
171
|
+
throw new Error("Ghostget read failure retry disposition is inconsistent");
|
|
172
|
+
}
|
|
173
|
+
return Object.freeze({ category, retryDisposition });
|
|
174
|
+
}
|
|
175
|
+
function snapshotJson(value, label) {
|
|
176
|
+
let nodes = 0;
|
|
177
|
+
const ancestors = new WeakSet;
|
|
178
|
+
const completed = new WeakMap;
|
|
179
|
+
const visit = (candidate, depth) => {
|
|
180
|
+
nodes += 1;
|
|
181
|
+
if (nodes > MAX_INPUT_NODES || depth > MAX_INPUT_DEPTH) {
|
|
182
|
+
throw new Error(`${label} exceeds its structural bound`);
|
|
183
|
+
}
|
|
184
|
+
if (candidate === null || typeof candidate === "boolean" || typeof candidate === "string")
|
|
185
|
+
return candidate;
|
|
186
|
+
if (typeof candidate === "number") {
|
|
187
|
+
if (!Number.isFinite(candidate)) {
|
|
188
|
+
throw new Error(`${label} must contain only JSON data`);
|
|
189
|
+
}
|
|
190
|
+
return candidate;
|
|
191
|
+
}
|
|
192
|
+
if (typeof candidate !== "object") {
|
|
193
|
+
throw new Error(`${label} must contain only JSON data`);
|
|
194
|
+
}
|
|
195
|
+
if (nodeTypes.isProxy(candidate)) {
|
|
196
|
+
throw new Error(`${label} must not contain proxies`);
|
|
197
|
+
}
|
|
198
|
+
const prior = completed.get(candidate);
|
|
199
|
+
if (prior !== undefined)
|
|
200
|
+
return prior;
|
|
201
|
+
if (ancestors.has(candidate))
|
|
202
|
+
throw new Error(`${label} must not be circular`);
|
|
203
|
+
ancestors.add(candidate);
|
|
204
|
+
try {
|
|
205
|
+
if (Array.isArray(candidate)) {
|
|
206
|
+
if (Object.getPrototypeOf(candidate) !== Array.prototype) {
|
|
207
|
+
throw new Error(`${label} arrays must use the standard prototype`);
|
|
208
|
+
}
|
|
209
|
+
const descriptors2 = Object.getOwnPropertyDescriptors(candidate);
|
|
210
|
+
const descriptorKeys2 = Reflect.ownKeys(descriptors2);
|
|
211
|
+
if (descriptorKeys2.some((key) => typeof key !== "string")) {
|
|
212
|
+
throw new Error(`${label} arrays have unsupported symbol fields`);
|
|
213
|
+
}
|
|
214
|
+
const lengthDescriptor = descriptors2.length;
|
|
215
|
+
if (lengthDescriptor === undefined || !("value" in lengthDescriptor) || typeof lengthDescriptor.value !== "number" || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0)
|
|
216
|
+
throw new Error(`${label} arrays are malformed`);
|
|
217
|
+
const length = lengthDescriptor.value;
|
|
218
|
+
const keys = descriptorKeys2.filter((key) => typeof key === "string" && key !== "length");
|
|
219
|
+
if (keys.length !== length || keys.some((key, index) => key !== String(index)))
|
|
220
|
+
throw new Error(`${label} arrays must be dense data arrays`);
|
|
221
|
+
const cloned2 = [];
|
|
222
|
+
for (let index = 0;index < length; index += 1) {
|
|
223
|
+
const descriptor = descriptors2[String(index)];
|
|
224
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor))
|
|
225
|
+
throw new Error(`${label} arrays must contain only data elements`);
|
|
226
|
+
cloned2.push(visit(descriptor.value, depth + 1));
|
|
227
|
+
}
|
|
228
|
+
const snapshot2 = Object.freeze(cloned2);
|
|
229
|
+
completed.set(candidate, snapshot2);
|
|
230
|
+
return snapshot2;
|
|
231
|
+
}
|
|
232
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
233
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
234
|
+
throw new Error(`${label} objects must use a plain prototype`);
|
|
235
|
+
}
|
|
236
|
+
const descriptors = Object.getOwnPropertyDescriptors(candidate);
|
|
237
|
+
const descriptorKeys = Reflect.ownKeys(descriptors);
|
|
238
|
+
if (descriptorKeys.some((key) => typeof key !== "string")) {
|
|
239
|
+
throw new Error(`${label} objects have unsupported symbol fields`);
|
|
240
|
+
}
|
|
241
|
+
const cloned = {};
|
|
242
|
+
for (const key of descriptorKeys.sort((left, right) => left.localeCompare(right))) {
|
|
243
|
+
const descriptor = descriptors[key];
|
|
244
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor))
|
|
245
|
+
throw new Error(`${label} objects must contain only data properties`);
|
|
246
|
+
Object.defineProperty(cloned, key, {
|
|
247
|
+
value: visit(descriptor.value, depth + 1),
|
|
248
|
+
enumerable: true,
|
|
249
|
+
configurable: false,
|
|
250
|
+
writable: false
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const snapshot = Object.freeze(cloned);
|
|
254
|
+
completed.set(candidate, snapshot);
|
|
255
|
+
return snapshot;
|
|
256
|
+
} finally {
|
|
257
|
+
ancestors.delete(candidate);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
return visit(value, 0);
|
|
261
|
+
}
|
|
262
|
+
function safeString(value, label, maximum = 512) {
|
|
263
|
+
if (typeof value !== "string" || value.length < 1 || value.length > maximum || value.trim() !== value || [...value].some((character) => {
|
|
264
|
+
const code = character.codePointAt(0) ?? -1;
|
|
265
|
+
return code <= 31 || code === 127;
|
|
266
|
+
}))
|
|
267
|
+
throw new Error(`${label} is malformed`);
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
function providerOperationName(value, label) {
|
|
271
|
+
const operation = safeString(value, label, PROVIDER_PLUGIN_OPERATION_NAME_MAX_LENGTH);
|
|
272
|
+
if (!isProviderPluginOperationName(operation)) {
|
|
273
|
+
throw new Error(`${label} is malformed`);
|
|
274
|
+
}
|
|
275
|
+
return operation;
|
|
276
|
+
}
|
|
277
|
+
function providerSurfaceId(value, label) {
|
|
278
|
+
const surface = safeString(value, label, PROVIDER_PLUGIN_ID_MAX_LENGTH);
|
|
279
|
+
if (!isProviderPluginSurfaceId(surface)) {
|
|
280
|
+
throw new Error(`${label} is malformed`);
|
|
281
|
+
}
|
|
282
|
+
return surface;
|
|
283
|
+
}
|
|
284
|
+
function boundedUtf8String(value, label, maximumBytes) {
|
|
285
|
+
if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > maximumBytes)
|
|
286
|
+
throw new Error(`${label} is malformed`);
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
function digest(value, label) {
|
|
290
|
+
const text = safeString(value, label, 64);
|
|
291
|
+
if (!/^[a-f0-9]{64}$/u.test(text))
|
|
292
|
+
throw new Error(`${label} is malformed`);
|
|
293
|
+
return text;
|
|
294
|
+
}
|
|
295
|
+
function safeInteger(value, label, minimum, maximum) {
|
|
296
|
+
if (!Number.isSafeInteger(value) || typeof value !== "number" || value < minimum || value > maximum) {
|
|
297
|
+
throw new Error(`${label} is malformed`);
|
|
298
|
+
}
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
function timestamp(value, label) {
|
|
302
|
+
const text = safeString(value, label, 64);
|
|
303
|
+
const date = new Date(text);
|
|
304
|
+
if (!Number.isFinite(date.getTime()) || date.toISOString() !== text) {
|
|
305
|
+
throw new Error(`${label} is malformed`);
|
|
306
|
+
}
|
|
307
|
+
return text;
|
|
308
|
+
}
|
|
309
|
+
function boundedMessage(value) {
|
|
310
|
+
const bytes = Buffer.from(value, "utf8");
|
|
311
|
+
if (bytes.byteLength <= MAX_ERROR_BYTES)
|
|
312
|
+
return value.trim();
|
|
313
|
+
return `${bytes.subarray(0, MAX_ERROR_BYTES).toString("utf8").trim()}\u2026`;
|
|
314
|
+
}
|
|
315
|
+
function cliSourcePath() {
|
|
316
|
+
const besideSource = fileURLToPath(new URL("./cli.ts", import.meta.url));
|
|
317
|
+
if (existsSync(besideSource))
|
|
318
|
+
return besideSource;
|
|
319
|
+
const packagedSource = fileURLToPath(new URL("../src/cli.ts", import.meta.url));
|
|
320
|
+
if (existsSync(packagedSource))
|
|
321
|
+
return packagedSource;
|
|
322
|
+
throw new Error("the installed Ghostget CLI source is unavailable");
|
|
323
|
+
}
|
|
324
|
+
function requireBunRuntime() {
|
|
325
|
+
if (typeof process.versions.bun !== "string") {
|
|
326
|
+
throw new Error("@hraness/ghostget/client requires Bun to run the installed Ghostget CLI");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function environmentName(value) {
|
|
330
|
+
if (value.length < 1 || value.includes("=") || value.includes("\x00")) {
|
|
331
|
+
throw new Error("Ghostget client environment name is malformed");
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
335
|
+
function defineEnvironmentValue(environment, key, value) {
|
|
336
|
+
Object.defineProperty(environment, key, {
|
|
337
|
+
value,
|
|
338
|
+
enumerable: true,
|
|
339
|
+
configurable: true,
|
|
340
|
+
writable: true
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function snapshotChildEnvironment(overrides) {
|
|
344
|
+
const environment = Object.create(null);
|
|
345
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
346
|
+
if (typeof value === "string")
|
|
347
|
+
defineEnvironmentValue(environment, key, value);
|
|
348
|
+
}
|
|
349
|
+
if (overrides === undefined)
|
|
350
|
+
return Object.freeze(environment);
|
|
351
|
+
if (!isRecord(overrides) || nodeTypes.isProxy(overrides) || Object.getPrototypeOf(overrides) !== Object.prototype && Object.getPrototypeOf(overrides) !== null) {
|
|
352
|
+
throw new Error("Ghostget client environment must use a plain, non-proxy object");
|
|
353
|
+
}
|
|
354
|
+
const descriptors = Object.getOwnPropertyDescriptors(overrides);
|
|
355
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
356
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
357
|
+
throw new Error("Ghostget client environment has unsupported symbol fields");
|
|
358
|
+
}
|
|
359
|
+
for (const key of keys.sort((left, right) => left.localeCompare(right))) {
|
|
360
|
+
const descriptor = descriptors[key];
|
|
361
|
+
if (descriptor === undefined || !("value" in descriptor)) {
|
|
362
|
+
throw new Error("Ghostget client environment must contain only data properties");
|
|
363
|
+
}
|
|
364
|
+
if (!descriptor.enumerable) {
|
|
365
|
+
throw new Error("Ghostget client environment properties must be enumerable");
|
|
366
|
+
}
|
|
367
|
+
const name = environmentName(key);
|
|
368
|
+
if (descriptor.value === undefined) {
|
|
369
|
+
delete environment[name];
|
|
370
|
+
} else if (typeof descriptor.value !== "string" || descriptor.value.includes("\x00")) {
|
|
371
|
+
throw new Error("Ghostget client environment value is malformed");
|
|
372
|
+
} else {
|
|
373
|
+
defineEnvironmentValue(environment, name, descriptor.value);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return Object.freeze(environment);
|
|
377
|
+
}
|
|
378
|
+
function isBrandedAbortSignal(value) {
|
|
379
|
+
if (nodeTypes.isProxy(value) || typeof value !== "object" || value === null || abortSignalAbortedGetter === undefined)
|
|
380
|
+
return false;
|
|
381
|
+
if (Object.getPrototypeOf(value) !== AbortSignal.prototype)
|
|
382
|
+
return false;
|
|
383
|
+
try {
|
|
384
|
+
return typeof abortSignalAbortedGetter(value) === "boolean";
|
|
385
|
+
} catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function snapshotClientOptions(optionsValue, mode) {
|
|
390
|
+
if (!isRecord(optionsValue) || nodeTypes.isProxy(optionsValue) || Object.getPrototypeOf(optionsValue) !== Object.prototype && Object.getPrototypeOf(optionsValue) !== null)
|
|
391
|
+
throw new Error("Ghostget client options must use a plain, non-proxy object");
|
|
392
|
+
const descriptors = Object.getOwnPropertyDescriptors(optionsValue);
|
|
393
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
394
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
395
|
+
throw new Error("Ghostget client options have unsupported symbol fields");
|
|
396
|
+
}
|
|
397
|
+
const allowed = new Set(mode === "read" ? ["environment", "freshForMs", "now"] : mode === "revalidation" ? ["environment", "freshForMs", "now", "headed", "signal"] : mode === "invoke-async" ? ["environment", "headed", "signal"] : ["environment", "headed"]);
|
|
398
|
+
for (const key of keys) {
|
|
399
|
+
const descriptor = descriptors[key];
|
|
400
|
+
if (descriptor === undefined || !allowed.has(key)) {
|
|
401
|
+
throw new Error("Ghostget client options contain an unsupported field");
|
|
402
|
+
}
|
|
403
|
+
if (!("value" in descriptor)) {
|
|
404
|
+
throw new Error("Ghostget client options must contain only data properties");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const option = (key) => descriptors[key]?.value;
|
|
408
|
+
const freshForMsValue = option("freshForMs");
|
|
409
|
+
const nowValue = option("now");
|
|
410
|
+
const headedValue = option("headed");
|
|
411
|
+
const signalValue = option("signal");
|
|
412
|
+
const freshForMs = freshForMsValue === undefined ? undefined : safeInteger(freshForMsValue, "Ghostget client freshness window", 0, MAX_FRESH_FOR_MS);
|
|
413
|
+
let now;
|
|
414
|
+
if (nowValue !== undefined) {
|
|
415
|
+
if (nodeTypes.isProxy(nowValue) || !(nowValue instanceof Date) || Object.getPrototypeOf(nowValue) !== Date.prototype)
|
|
416
|
+
throw new Error("Ghostget client observation time is invalid");
|
|
417
|
+
let time;
|
|
418
|
+
try {
|
|
419
|
+
time = Date.prototype.getTime.call(nowValue);
|
|
420
|
+
} catch {
|
|
421
|
+
throw new Error("Ghostget client observation time is invalid");
|
|
422
|
+
}
|
|
423
|
+
if (!Number.isFinite(time)) {
|
|
424
|
+
throw new Error("Ghostget client observation time is invalid");
|
|
425
|
+
}
|
|
426
|
+
now = new Date(time);
|
|
427
|
+
}
|
|
428
|
+
if (headedValue !== undefined && typeof headedValue !== "boolean") {
|
|
429
|
+
throw new Error("Ghostget client headed option is malformed");
|
|
430
|
+
}
|
|
431
|
+
if (signalValue !== undefined && !isBrandedAbortSignal(signalValue))
|
|
432
|
+
throw new Error("Ghostget client abort signal is malformed");
|
|
433
|
+
return Object.freeze({
|
|
434
|
+
cwd: process.cwd(),
|
|
435
|
+
environment: snapshotChildEnvironment(option("environment")),
|
|
436
|
+
...freshForMs === undefined ? {} : { freshForMs },
|
|
437
|
+
...now === undefined ? {} : { now },
|
|
438
|
+
...headedValue === undefined ? {} : { headed: headedValue },
|
|
439
|
+
...signalValue === undefined ? {} : { signal: signalValue }
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function prepareRequest(requestValue) {
|
|
443
|
+
const snapshot = snapshotJson(requestValue, "Ghostget client request");
|
|
444
|
+
const request = record(snapshot, "Ghostget client request");
|
|
445
|
+
assertExactKeys(request, ["adapterId", "operationId"], ["authId", "input"], "Ghostget client request");
|
|
446
|
+
const adapterId = safeString(request.adapterId, "Ghostget client adapter ID", 64);
|
|
447
|
+
const operationId = providerOperationName(request.operationId, "Ghostget client operation ID");
|
|
448
|
+
const authId = Object.hasOwn(request, "authId") ? safeString(request.authId, "Ghostget client auth ID", 64) : undefined;
|
|
449
|
+
const rawInput = Object.hasOwn(request, "input") ? request.input : {};
|
|
450
|
+
if (!isRecord(rawInput))
|
|
451
|
+
throw new Error("input must be a JSON object");
|
|
452
|
+
const input = canonicalJson(rawInput);
|
|
453
|
+
if (Buffer.byteLength(input, "utf8") > MAX_INPUT_BYTES) {
|
|
454
|
+
throw new Error("Ghostget client input exceeds its byte bound");
|
|
455
|
+
}
|
|
456
|
+
return Object.freeze({
|
|
457
|
+
adapterId,
|
|
458
|
+
operationId,
|
|
459
|
+
...authId === undefined ? {} : { authId },
|
|
460
|
+
input
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function preparedCommand(request, options) {
|
|
464
|
+
return Object.freeze({
|
|
465
|
+
arguments: Object.freeze([
|
|
466
|
+
cliSourcePath(),
|
|
467
|
+
"invoke",
|
|
468
|
+
request.adapterId,
|
|
469
|
+
request.operationId,
|
|
470
|
+
"--input",
|
|
471
|
+
"-",
|
|
472
|
+
...request.authId === undefined ? [] : ["--auth", request.authId],
|
|
473
|
+
...options.cacheOnly ? ["--cache-only"] : [],
|
|
474
|
+
...options.projectionIdentityOnly ? ["--projection-identity-only"] : [],
|
|
475
|
+
...options.preview === true ? ["--preview"] : [],
|
|
476
|
+
...options.headed ? ["--headed"] : [],
|
|
477
|
+
"--json"
|
|
478
|
+
]),
|
|
479
|
+
input: request.input
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
function preparedCapabilitiesCommand(request) {
|
|
483
|
+
return Object.freeze({
|
|
484
|
+
arguments: Object.freeze([
|
|
485
|
+
cliSourcePath(),
|
|
486
|
+
"capabilities",
|
|
487
|
+
request.adapterId,
|
|
488
|
+
"--json"
|
|
489
|
+
]),
|
|
490
|
+
input: ""
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
function parseOutput(text, label) {
|
|
494
|
+
if (Buffer.byteLength(text, "utf8") > MAX_OUTPUT_BYTES) {
|
|
495
|
+
throw new Error(`${label} exceeds its byte bound`);
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
return record(JSON.parse(text), label);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (error instanceof Error && error.message === `${label} must be an object`)
|
|
501
|
+
throw error;
|
|
502
|
+
throw new Error(`${label} is malformed`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function runCacheCommand(command, options) {
|
|
506
|
+
requireBunRuntime();
|
|
507
|
+
const result = spawnSync(process.execPath, command.arguments, {
|
|
508
|
+
cwd: options.cwd,
|
|
509
|
+
env: options.environment,
|
|
510
|
+
input: command.input,
|
|
511
|
+
encoding: "utf8",
|
|
512
|
+
maxBuffer: MAX_OUTPUT_BYTES
|
|
513
|
+
});
|
|
514
|
+
if (result.error !== undefined)
|
|
515
|
+
throw result.error;
|
|
516
|
+
const code = result.status ?? 3;
|
|
517
|
+
const stdout = String(result.stdout ?? "");
|
|
518
|
+
const stderr = String(result.stderr ?? "");
|
|
519
|
+
if (stdout.trim().length === 0) {
|
|
520
|
+
throw new Error(boundedMessage(stderr) || `Ghostget cache lookup exited ${code}`);
|
|
521
|
+
}
|
|
522
|
+
if (code !== 0 && code !== 3) {
|
|
523
|
+
throw new Error(boundedMessage(stderr) || `Ghostget cache lookup exited ${code}`);
|
|
524
|
+
}
|
|
525
|
+
return Object.freeze({
|
|
526
|
+
code,
|
|
527
|
+
output: parseOutput(stdout, "Ghostget cache response")
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
function runIdentityCommand(command, options, label, responseLabel = `${label} response`) {
|
|
531
|
+
requireBunRuntime();
|
|
532
|
+
const result = spawnSync(process.execPath, command.arguments, {
|
|
533
|
+
cwd: options.cwd,
|
|
534
|
+
env: options.environment,
|
|
535
|
+
input: command.input,
|
|
536
|
+
encoding: "utf8",
|
|
537
|
+
maxBuffer: MAX_OUTPUT_BYTES
|
|
538
|
+
});
|
|
539
|
+
if (result.error !== undefined)
|
|
540
|
+
throw result.error;
|
|
541
|
+
const code = result.status ?? 3;
|
|
542
|
+
const stdout = String(result.stdout ?? "");
|
|
543
|
+
const stderr = String(result.stderr ?? "");
|
|
544
|
+
if (stdout.trim().length === 0 || code !== 0) {
|
|
545
|
+
throw new Error(boundedMessage(stderr) || `${label} exited ${code}`);
|
|
546
|
+
}
|
|
547
|
+
return parseOutput(stdout, responseLabel);
|
|
548
|
+
}
|
|
549
|
+
function runProjectionIdentityCommand(command, options) {
|
|
550
|
+
return runIdentityCommand(command, options, "Ghostget projection identity preflight", "Ghostget projection identity response");
|
|
551
|
+
}
|
|
552
|
+
function observeProjectionIdentity(request, options) {
|
|
553
|
+
const value = runProjectionIdentityCommand(preparedCommand(request, {
|
|
554
|
+
cacheOnly: false,
|
|
555
|
+
projectionIdentityOnly: true,
|
|
556
|
+
headed: false
|
|
557
|
+
}), options);
|
|
558
|
+
if (value.ok !== true || value.source !== "projection-identity")
|
|
559
|
+
throw new Error("Ghostget projection identity response is malformed");
|
|
560
|
+
if (value.status === "ready") {
|
|
561
|
+
assertExactKeys(value, [
|
|
562
|
+
"ok",
|
|
563
|
+
"source",
|
|
564
|
+
"status",
|
|
565
|
+
"authIdentity",
|
|
566
|
+
"authHash",
|
|
567
|
+
"inputHash",
|
|
568
|
+
"projection"
|
|
569
|
+
], [], "Ghostget projection identity response");
|
|
570
|
+
const projection = record(value.projection, "Ghostget projection identity");
|
|
571
|
+
assertExactKeys(projection, ["key"], [], "Ghostget projection identity");
|
|
572
|
+
return Object.freeze({
|
|
573
|
+
status: "ready",
|
|
574
|
+
key: digest(projection.key, "Ghostget projection identity key"),
|
|
575
|
+
authIdentity: digest(value.authIdentity, "Ghostget projection auth identity"),
|
|
576
|
+
authHash: digest(value.authHash, "Ghostget projection auth hash"),
|
|
577
|
+
inputHash: digest(value.inputHash, "Ghostget projection validated input hash")
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (value.status === "unbound") {
|
|
581
|
+
assertExactKeys(value, [
|
|
582
|
+
"ok",
|
|
583
|
+
"source",
|
|
584
|
+
"status",
|
|
585
|
+
"authIdentity",
|
|
586
|
+
"authHash",
|
|
587
|
+
"inputHash"
|
|
588
|
+
], [], "Ghostget projection identity response");
|
|
589
|
+
return Object.freeze({
|
|
590
|
+
status: "unbound",
|
|
591
|
+
authIdentity: digest(value.authIdentity, "Ghostget projection auth identity"),
|
|
592
|
+
authHash: digest(value.authHash, "Ghostget projection auth hash"),
|
|
593
|
+
inputHash: digest(value.inputHash, "Ghostget projection validated input hash")
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
throw new Error("Ghostget projection identity response is malformed");
|
|
597
|
+
}
|
|
598
|
+
function projectionIdentitiesMatch(before, after) {
|
|
599
|
+
return before.status === "ready" ? after.status === "ready" && after.key === before.key && after.authIdentity === before.authIdentity && after.authHash === before.authHash && after.inputHash === before.inputHash : after.status === "unbound" && after.authIdentity === before.authIdentity && after.authHash === before.authHash && after.inputHash === before.inputHash;
|
|
600
|
+
}
|
|
601
|
+
function parseExecutionPreview(value, request) {
|
|
602
|
+
const portable = value.transport === "portable-provider-plugin";
|
|
603
|
+
assertExactKeys(value, [
|
|
604
|
+
"ok",
|
|
605
|
+
"status",
|
|
606
|
+
"requiresConfirmation",
|
|
607
|
+
"adapter",
|
|
608
|
+
"operation",
|
|
609
|
+
"risk",
|
|
610
|
+
"sideEffect",
|
|
611
|
+
"input",
|
|
612
|
+
"auth",
|
|
613
|
+
"identityBinding",
|
|
614
|
+
"transport",
|
|
615
|
+
...portable ? ["portablePluginContract"] : []
|
|
616
|
+
], [], "Ghostget execution identity preview");
|
|
617
|
+
if (value.ok !== true || value.status !== "preview" || value.requiresConfirmation !== false || value.risk !== "R1")
|
|
618
|
+
throw new Error("Ghostget execution identity preview is malformed");
|
|
619
|
+
const adapterValue = record(value.adapter, "Ghostget execution identity preview adapter");
|
|
620
|
+
const auth = record(value.auth, "Ghostget execution identity preview auth");
|
|
621
|
+
const binding = record(value.identityBinding, "Ghostget execution identity preview binding");
|
|
622
|
+
assertExactKeys(adapterValue, ["id", "version", "hash"], [], "Ghostget execution identity preview adapter");
|
|
623
|
+
assertExactKeys(auth, ["id", "kind", "realmFingerprint"], [], "Ghostget execution identity preview auth");
|
|
624
|
+
assertExactKeys(binding, ["status", "subject", "accountActor", "requestedActor"], [], "Ghostget execution identity preview binding");
|
|
625
|
+
const adapter = Object.freeze({
|
|
626
|
+
id: safeString(adapterValue.id, "Ghostget execution identity preview adapter ID", 64),
|
|
627
|
+
version: safeString(adapterValue.version, "Ghostget execution identity preview adapter version", 64),
|
|
628
|
+
hash: digest(adapterValue.hash, "Ghostget execution identity preview adapter hash")
|
|
629
|
+
});
|
|
630
|
+
const operation = providerOperationName(value.operation, "Ghostget execution identity preview operation");
|
|
631
|
+
if (adapter.id !== request.adapterId || operation !== request.operationId) {
|
|
632
|
+
throw new Error("Ghostget execution identity preview route is malformed");
|
|
633
|
+
}
|
|
634
|
+
const authKind = safeString(auth.kind, "Ghostget execution identity preview auth kind", 64);
|
|
635
|
+
if (safeString(auth.id, "Ghostget execution identity preview auth ID", 64) !== expectedRequestAuthId(request, authKind))
|
|
636
|
+
throw new Error("Ghostget execution identity preview auth is malformed");
|
|
637
|
+
const realmFingerprint = safeString(auth.realmFingerprint, "Ghostget execution identity preview auth fingerprint", 16);
|
|
638
|
+
if (!/^[a-f0-9]{16}$/u.test(realmFingerprint)) {
|
|
639
|
+
throw new Error("Ghostget execution identity preview auth fingerprint is malformed");
|
|
640
|
+
}
|
|
641
|
+
if (authKind === PUBLIC_WEB_SESSION_AUTHORITY_KIND) {
|
|
642
|
+
const authority = publicWebSessionAuthority(request);
|
|
643
|
+
if (value.transport !== "web-session-api" || realmFingerprint !== sha256(canonicalJson(authority)).slice(0, 16) || binding.status !== "public" || binding.subject !== authority.subject || binding.accountActor !== null || binding.requestedActor !== null) {
|
|
644
|
+
throw new Error("Ghostget execution identity preview public authority is malformed");
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
safeString(value.sideEffect, "Ghostget execution identity preview side effect", 64);
|
|
648
|
+
const transport = value.transport;
|
|
649
|
+
if (transport === "portable-provider-plugin") {
|
|
650
|
+
const portablePluginContract = parsePortableOperationIdentity(value.portablePluginContract);
|
|
651
|
+
if (portablePluginContract.adapterId !== adapter.id || portablePluginContract.operation !== operation)
|
|
652
|
+
throw new Error("Ghostget execution identity preview route is malformed");
|
|
653
|
+
return Object.freeze({
|
|
654
|
+
adapter,
|
|
655
|
+
operation,
|
|
656
|
+
transport,
|
|
657
|
+
portablePluginContract
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
if (transport !== "browser" && transport !== "provider-api" && transport !== "web-session-api" && transport !== "reviewed-template-api" && transport !== "local-cli")
|
|
661
|
+
throw new Error("Ghostget execution identity preview transport is malformed");
|
|
662
|
+
return Object.freeze({ adapter, operation, transport });
|
|
663
|
+
}
|
|
664
|
+
function parseCatalogExecutionIdentity(value, request, preview) {
|
|
665
|
+
assertExactKeys(value, ["ok", "adapters"], [], "Ghostget execution identity catalog");
|
|
666
|
+
if (value.ok !== true || !isUnknownArray(value.adapters)) {
|
|
667
|
+
throw new Error("Ghostget execution identity catalog is malformed");
|
|
668
|
+
}
|
|
669
|
+
if (value.adapters.length !== 1) {
|
|
670
|
+
throw new Error("Ghostget execution identity catalog is ambiguous");
|
|
671
|
+
}
|
|
672
|
+
const adapterValue = record(value.adapters[0], "Ghostget execution identity catalog adapter");
|
|
673
|
+
assertExactKeys(adapterValue, [
|
|
674
|
+
"id",
|
|
675
|
+
"version",
|
|
676
|
+
"displayName",
|
|
677
|
+
"surfaceId",
|
|
678
|
+
"origins",
|
|
679
|
+
"manifestHash",
|
|
680
|
+
"operations"
|
|
681
|
+
], [], "Ghostget execution identity catalog adapter");
|
|
682
|
+
const adapter = Object.freeze({
|
|
683
|
+
id: safeString(adapterValue.id, "Ghostget execution identity catalog adapter ID", 64),
|
|
684
|
+
version: safeString(adapterValue.version, "Ghostget execution identity catalog adapter version", 64),
|
|
685
|
+
hash: digest(adapterValue.manifestHash, "Ghostget execution identity catalog adapter hash")
|
|
686
|
+
});
|
|
687
|
+
if (adapter.id !== request.adapterId || adapter.id !== preview.adapter.id || adapter.version !== preview.adapter.version || adapter.hash !== preview.adapter.hash || !isUnknownArray(adapterValue.operations))
|
|
688
|
+
throw new Error("Ghostget execution identity preflights disagreed");
|
|
689
|
+
const operations = adapterValue.operations.filter((candidate) => isRecord(candidate) && candidate.id === request.operationId);
|
|
690
|
+
if (operations.length !== 1) {
|
|
691
|
+
throw new Error("Ghostget execution identity catalog operation is ambiguous");
|
|
692
|
+
}
|
|
693
|
+
const operation = operations[0];
|
|
694
|
+
const commonKeys = [
|
|
695
|
+
"id",
|
|
696
|
+
"description",
|
|
697
|
+
"risk",
|
|
698
|
+
"sideEffect",
|
|
699
|
+
"idempotency",
|
|
700
|
+
"dedupeWindowMs",
|
|
701
|
+
"transport",
|
|
702
|
+
"input"
|
|
703
|
+
];
|
|
704
|
+
if (operation.risk !== "R1" || operation.transport !== preview.transport)
|
|
705
|
+
throw new Error("Ghostget execution identity preflights disagreed");
|
|
706
|
+
const common = Object.freeze({
|
|
707
|
+
adapter,
|
|
708
|
+
operation: preview.operation
|
|
709
|
+
});
|
|
710
|
+
if (preview.transport === "provider-api") {
|
|
711
|
+
assertExactKeys(operation, [
|
|
712
|
+
...commonKeys,
|
|
713
|
+
"provider",
|
|
714
|
+
"providerAction",
|
|
715
|
+
"providerContractVersion",
|
|
716
|
+
"providerContractHash",
|
|
717
|
+
"requiredScopeSets",
|
|
718
|
+
"coverage",
|
|
719
|
+
"implementation"
|
|
720
|
+
], [], "Ghostget execution identity provider capability");
|
|
721
|
+
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "Ghostget execution identity catalog surface");
|
|
722
|
+
const provider = providerSurfaceId(operation.provider, "Ghostget execution identity provider surface");
|
|
723
|
+
const providerAction = providerOperationName(operation.providerAction, "Ghostget execution identity provider action");
|
|
724
|
+
safeInteger(operation.providerContractVersion, "Ghostget execution identity provider contract version", 1, 1e6);
|
|
725
|
+
if (provider !== catalogSurface || providerAction !== preview.operation)
|
|
726
|
+
throw new Error("Ghostget execution identity provider route is malformed");
|
|
727
|
+
return Object.freeze({
|
|
728
|
+
...common,
|
|
729
|
+
schemaVersion: 3,
|
|
730
|
+
transport: "provider-api",
|
|
731
|
+
providerContractHash: digest(operation.providerContractHash, "Ghostget execution identity provider contract hash")
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
if (preview.transport === "web-session-api") {
|
|
735
|
+
assertExactKeys(operation, [
|
|
736
|
+
...commonKeys,
|
|
737
|
+
"site",
|
|
738
|
+
"webSessionAction",
|
|
739
|
+
"webSessionContractVersion",
|
|
740
|
+
"webSessionContractHash",
|
|
741
|
+
"state",
|
|
742
|
+
"implementation"
|
|
743
|
+
], [], "Ghostget execution identity web-session capability");
|
|
744
|
+
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "Ghostget execution identity catalog surface");
|
|
745
|
+
const site = providerSurfaceId(operation.site, "Ghostget execution identity web-session surface");
|
|
746
|
+
const webSessionAction = providerOperationName(operation.webSessionAction, "Ghostget execution identity web-session action");
|
|
747
|
+
safeInteger(operation.webSessionContractVersion, "Ghostget execution identity web-session contract version", 1, 1e6);
|
|
748
|
+
if (site !== catalogSurface || webSessionAction !== preview.operation)
|
|
749
|
+
throw new Error("Ghostget execution identity web-session route is malformed");
|
|
750
|
+
return Object.freeze({
|
|
751
|
+
...common,
|
|
752
|
+
schemaVersion: 4,
|
|
753
|
+
transport: "web-session-api",
|
|
754
|
+
webSessionContractHash: digest(operation.webSessionContractHash, "Ghostget execution identity web-session contract hash")
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
if (preview.transport === "local-cli") {
|
|
758
|
+
assertExactKeys(operation, [
|
|
759
|
+
...commonKeys,
|
|
760
|
+
"surface",
|
|
761
|
+
"localCliAction",
|
|
762
|
+
"localCliContractVersion",
|
|
763
|
+
"localCliContractHash",
|
|
764
|
+
"localCliTool",
|
|
765
|
+
"state",
|
|
766
|
+
"implementation"
|
|
767
|
+
], [], "Ghostget execution identity local CLI capability");
|
|
768
|
+
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "Ghostget execution identity catalog surface");
|
|
769
|
+
const localCliContract = parseLocalCliContractIdentity({
|
|
770
|
+
surface: operation.surface,
|
|
771
|
+
action: operation.localCliAction,
|
|
772
|
+
version: operation.localCliContractVersion,
|
|
773
|
+
hash: operation.localCliContractHash,
|
|
774
|
+
tool: operation.localCliTool
|
|
775
|
+
});
|
|
776
|
+
if (localCliContract.surface !== catalogSurface || localCliContract.action !== preview.operation) {
|
|
777
|
+
throw new Error("Ghostget execution identity local CLI route is malformed");
|
|
778
|
+
}
|
|
779
|
+
return Object.freeze({
|
|
780
|
+
...common,
|
|
781
|
+
schemaVersion: 7,
|
|
782
|
+
transport: "local-cli",
|
|
783
|
+
localCliContract
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
assertExactKeys(operation, [
|
|
787
|
+
...commonKeys,
|
|
788
|
+
"state",
|
|
789
|
+
"reviewedTemplateContractVersion",
|
|
790
|
+
"reviewedTemplateContractHash"
|
|
791
|
+
], ["instructions", "reviewedAt", "evidenceSha256", "origin"], "Ghostget execution identity reviewed-template capability");
|
|
792
|
+
safeInteger(operation.reviewedTemplateContractVersion, "Ghostget execution identity reviewed-template contract version", 1, Number.MAX_SAFE_INTEGER);
|
|
793
|
+
return Object.freeze({
|
|
794
|
+
...common,
|
|
795
|
+
schemaVersion: 5,
|
|
796
|
+
transport: "reviewed-template-api",
|
|
797
|
+
reviewedTemplateContractHash: digest(operation.reviewedTemplateContractHash, "Ghostget execution identity reviewed-template contract hash")
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
function observeExecutionIdentity(request, options) {
|
|
801
|
+
const preview = parseExecutionPreview(runIdentityCommand(preparedCommand(request, {
|
|
802
|
+
cacheOnly: false,
|
|
803
|
+
projectionIdentityOnly: false,
|
|
804
|
+
preview: true,
|
|
805
|
+
headed: false
|
|
806
|
+
}), options, "Ghostget execution identity preview"), request);
|
|
807
|
+
if (preview.transport === "portable-provider-plugin") {
|
|
808
|
+
return Object.freeze({
|
|
809
|
+
adapter: preview.adapter,
|
|
810
|
+
operation: preview.operation,
|
|
811
|
+
schemaVersion: 6,
|
|
812
|
+
transport: preview.transport,
|
|
813
|
+
portablePluginContract: preview.portablePluginContract
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
if (preview.transport === "browser") {
|
|
817
|
+
return Object.freeze({
|
|
818
|
+
adapter: preview.adapter,
|
|
819
|
+
operation: preview.operation,
|
|
820
|
+
schemaVersion: 2,
|
|
821
|
+
transport: preview.transport
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
return parseCatalogExecutionIdentity(runIdentityCommand(preparedCapabilitiesCommand(request), options, "Ghostget execution identity catalog preflight"), request, preview);
|
|
825
|
+
}
|
|
826
|
+
function receiptExecutionIdentity(receipt) {
|
|
827
|
+
const common = Object.freeze({
|
|
828
|
+
adapter: receipt.adapter,
|
|
829
|
+
operation: receipt.operation
|
|
830
|
+
});
|
|
831
|
+
if (receipt.schemaVersion === 2) {
|
|
832
|
+
return Object.freeze({
|
|
833
|
+
...common,
|
|
834
|
+
schemaVersion: receipt.schemaVersion,
|
|
835
|
+
transport: receipt.transport
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
if (receipt.schemaVersion === 3) {
|
|
839
|
+
return Object.freeze({
|
|
840
|
+
...common,
|
|
841
|
+
schemaVersion: receipt.schemaVersion,
|
|
842
|
+
transport: receipt.transport,
|
|
843
|
+
providerContractHash: receipt.providerContractHash
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
if (receipt.schemaVersion === 4) {
|
|
847
|
+
return Object.freeze({
|
|
848
|
+
...common,
|
|
849
|
+
schemaVersion: receipt.schemaVersion,
|
|
850
|
+
transport: receipt.transport,
|
|
851
|
+
webSessionContractHash: receipt.webSessionContractHash
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
if (receipt.schemaVersion === 5) {
|
|
855
|
+
return Object.freeze({
|
|
856
|
+
...common,
|
|
857
|
+
schemaVersion: receipt.schemaVersion,
|
|
858
|
+
transport: receipt.transport,
|
|
859
|
+
reviewedTemplateContractHash: receipt.reviewedTemplateContractHash
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
if (receipt.schemaVersion === 6)
|
|
863
|
+
return Object.freeze({
|
|
864
|
+
...common,
|
|
865
|
+
schemaVersion: receipt.schemaVersion,
|
|
866
|
+
transport: receipt.transport,
|
|
867
|
+
portablePluginContract: receipt.portablePluginContract
|
|
868
|
+
});
|
|
869
|
+
return Object.freeze({
|
|
870
|
+
...common,
|
|
871
|
+
schemaVersion: receipt.schemaVersion,
|
|
872
|
+
transport: receipt.transport,
|
|
873
|
+
localCliContract: receipt.localCliContract
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
function executionIdentitiesMatch(left, right) {
|
|
877
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
878
|
+
}
|
|
879
|
+
function runLiveCommand(command, options) {
|
|
880
|
+
requireBunRuntime();
|
|
881
|
+
return new Promise((resolve, reject) => {
|
|
882
|
+
const child = spawn(process.execPath, command.arguments, {
|
|
883
|
+
cwd: options.cwd,
|
|
884
|
+
env: options.environment,
|
|
885
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
886
|
+
...options.signal === undefined ? {} : { signal: options.signal }
|
|
887
|
+
});
|
|
888
|
+
const stdout = [];
|
|
889
|
+
const stderr = [];
|
|
890
|
+
let stdoutBytes = 0;
|
|
891
|
+
let stderrBytes = 0;
|
|
892
|
+
let settled = false;
|
|
893
|
+
const fail = (error) => {
|
|
894
|
+
if (settled)
|
|
895
|
+
return;
|
|
896
|
+
settled = true;
|
|
897
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
898
|
+
};
|
|
899
|
+
child.stdout.on("data", (chunkValue) => {
|
|
900
|
+
const chunk = Buffer.isBuffer(chunkValue) ? chunkValue : Buffer.from(chunkValue);
|
|
901
|
+
stdoutBytes += chunk.byteLength;
|
|
902
|
+
if (stdoutBytes > MAX_OUTPUT_BYTES) {
|
|
903
|
+
child.kill();
|
|
904
|
+
fail(new Error("Ghostget live response exceeds its byte bound"));
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
stdout.push(chunk);
|
|
908
|
+
});
|
|
909
|
+
child.stderr.on("data", (chunkValue) => {
|
|
910
|
+
const chunk = Buffer.isBuffer(chunkValue) ? chunkValue : Buffer.from(chunkValue);
|
|
911
|
+
stderrBytes += chunk.byteLength;
|
|
912
|
+
if (stderrBytes <= MAX_ERROR_BYTES)
|
|
913
|
+
stderr.push(chunk);
|
|
914
|
+
});
|
|
915
|
+
child.on("error", fail);
|
|
916
|
+
child.on("close", (code, signal) => {
|
|
917
|
+
if (settled)
|
|
918
|
+
return;
|
|
919
|
+
const output = Buffer.concat(stdout).toString("utf8");
|
|
920
|
+
const error = boundedMessage(Buffer.concat(stderr).toString("utf8"));
|
|
921
|
+
if (output.trim().length === 0) {
|
|
922
|
+
fail(new Error(error || `Ghostget live invocation exited ${code ?? signal ?? "unknown"}`));
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (code !== 0 && code !== 3 && code !== 5) {
|
|
926
|
+
fail(new Error(error || `Ghostget live invocation exited ${code ?? signal ?? "unknown"}`));
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
let parsed;
|
|
930
|
+
try {
|
|
931
|
+
parsed = parseOutput(output, "Ghostget live response");
|
|
932
|
+
} catch (parseError) {
|
|
933
|
+
fail(parseError);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
settled = true;
|
|
937
|
+
resolve(parsed);
|
|
938
|
+
});
|
|
939
|
+
child.stdin.on("error", fail);
|
|
940
|
+
child.stdin.end(command.input);
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
function runLiveCommandSync(command, options) {
|
|
944
|
+
requireBunRuntime();
|
|
945
|
+
const result = spawnSync(process.execPath, command.arguments, {
|
|
946
|
+
cwd: options.cwd,
|
|
947
|
+
env: options.environment,
|
|
948
|
+
input: command.input,
|
|
949
|
+
encoding: "utf8",
|
|
950
|
+
maxBuffer: MAX_OUTPUT_BYTES
|
|
951
|
+
});
|
|
952
|
+
if (result.error !== undefined)
|
|
953
|
+
throw result.error;
|
|
954
|
+
const code = result.status ?? 3;
|
|
955
|
+
const stdout = String(result.stdout ?? "");
|
|
956
|
+
const stderr = String(result.stderr ?? "");
|
|
957
|
+
if (stdout.trim().length === 0) {
|
|
958
|
+
throw new Error(boundedMessage(stderr) || `Ghostget live invocation exited ${String(code)}`);
|
|
959
|
+
}
|
|
960
|
+
if (code !== 0 && code !== 3 && code !== 5) {
|
|
961
|
+
throw new Error(boundedMessage(stderr) || `Ghostget live invocation exited ${String(code)}`);
|
|
962
|
+
}
|
|
963
|
+
return parseOutput(stdout, "Ghostget live response");
|
|
964
|
+
}
|
|
965
|
+
function parseFreshness(value, options, ageMs) {
|
|
966
|
+
const freshnessValue = record(value, "Ghostget cache freshness");
|
|
967
|
+
assertExactKeys(freshnessValue, ["state", "freshForMs"], [], "Ghostget cache freshness");
|
|
968
|
+
const childState = freshnessValue.state;
|
|
969
|
+
if (childState !== "fresh" && childState !== "stale" && childState !== "unclassified") {
|
|
970
|
+
throw new Error("Ghostget cache freshness state is malformed");
|
|
971
|
+
}
|
|
972
|
+
if (options.freshForMs === undefined) {
|
|
973
|
+
if (freshnessValue.freshForMs !== null) {
|
|
974
|
+
throw new Error("Ghostget cache freshness window is malformed");
|
|
975
|
+
}
|
|
976
|
+
return Object.freeze({ state: childState, freshForMs: null });
|
|
977
|
+
}
|
|
978
|
+
const freshForMs = safeInteger(options.freshForMs, "Ghostget client freshness window", 0, MAX_FRESH_FOR_MS);
|
|
979
|
+
return Object.freeze({
|
|
980
|
+
state: ageMs <= freshForMs ? "fresh" : "stale",
|
|
981
|
+
freshForMs
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
function validateReadOptions(options) {
|
|
985
|
+
const now = options.now ?? new Date;
|
|
986
|
+
if (!(now instanceof Date) || !Number.isFinite(now.getTime())) {
|
|
987
|
+
throw new Error("Ghostget client observation time is invalid");
|
|
988
|
+
}
|
|
989
|
+
if (options.freshForMs !== undefined) {
|
|
990
|
+
safeInteger(options.freshForMs, "Ghostget client freshness window", 0, MAX_FRESH_FOR_MS);
|
|
991
|
+
}
|
|
992
|
+
return now;
|
|
993
|
+
}
|
|
994
|
+
function parseCacheResult(value, options, now) {
|
|
995
|
+
if (value.source !== "cache")
|
|
996
|
+
throw new Error("Ghostget cache response has the wrong source");
|
|
997
|
+
const projection = record(value.projection, "Ghostget cache projection");
|
|
998
|
+
if (value.status === "cache-miss") {
|
|
999
|
+
assertExactKeys(value, ["ok", "source", "status", "projection"], [], "Ghostget cache miss");
|
|
1000
|
+
assertExactKeys(projection, ["key"], [], "Ghostget cache miss projection");
|
|
1001
|
+
if (value.ok !== false)
|
|
1002
|
+
throw new Error("Ghostget cache miss has the wrong success state");
|
|
1003
|
+
const key2 = digest(projection.key, "Ghostget cache key");
|
|
1004
|
+
return Object.freeze({ status: "miss", key: key2 });
|
|
1005
|
+
}
|
|
1006
|
+
if (value.status !== "cached" || value.ok !== true || !("output" in value)) {
|
|
1007
|
+
throw new Error("Ghostget cache response has an unsupported outcome");
|
|
1008
|
+
}
|
|
1009
|
+
assertExactKeys(value, ["ok", "source", "status", "projection", "output"], [], "Ghostget cache response");
|
|
1010
|
+
assertExactKeys(projection, [
|
|
1011
|
+
"key",
|
|
1012
|
+
"dataRevision",
|
|
1013
|
+
"createdAt",
|
|
1014
|
+
"dataChangedAt",
|
|
1015
|
+
"validatedAt",
|
|
1016
|
+
"runId",
|
|
1017
|
+
"ageMs",
|
|
1018
|
+
"freshness"
|
|
1019
|
+
], [], "Ghostget cache projection");
|
|
1020
|
+
const key = digest(projection.key, "Ghostget cache key");
|
|
1021
|
+
const validatedAt = timestamp(projection.validatedAt, "Ghostget cache validation time");
|
|
1022
|
+
const childAgeMs = safeInteger(projection.ageMs, "Ghostget cache age", 0, Number.MAX_SAFE_INTEGER);
|
|
1023
|
+
const ageMs = options.now === undefined ? childAgeMs : Math.max(0, now.getTime() - new Date(validatedAt).getTime());
|
|
1024
|
+
return Object.freeze({
|
|
1025
|
+
status: "hit",
|
|
1026
|
+
source: "cache",
|
|
1027
|
+
key,
|
|
1028
|
+
output: value.output,
|
|
1029
|
+
dataRevision: digest(projection.dataRevision, "Ghostget cache data revision"),
|
|
1030
|
+
createdAt: timestamp(projection.createdAt, "Ghostget cache creation time"),
|
|
1031
|
+
dataChangedAt: timestamp(projection.dataChangedAt, "Ghostget cache data-change time"),
|
|
1032
|
+
validatedAt,
|
|
1033
|
+
runId: safeString(projection.runId, "Ghostget cache run ID", 64),
|
|
1034
|
+
ageMs,
|
|
1035
|
+
freshness: parseFreshness(projection.freshness, options, ageMs)
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
function parsePublication(value) {
|
|
1039
|
+
const publication = record(value, "Ghostget cache publication");
|
|
1040
|
+
assertExactKeys(publication, ["key", "dataRevision", "validatedAt", "dataChangedAt", "disposition"], ["currentDataRevision"], "Ghostget cache publication");
|
|
1041
|
+
const disposition = publication.disposition;
|
|
1042
|
+
if (disposition !== "created" && disposition !== "changed" && disposition !== "unchanged" && disposition !== "superseded")
|
|
1043
|
+
throw new Error("Ghostget cache publication disposition is malformed");
|
|
1044
|
+
if (disposition === "superseded" !== Object.hasOwn(publication, "currentDataRevision"))
|
|
1045
|
+
throw new Error("Ghostget cache publication current revision is malformed");
|
|
1046
|
+
return Object.freeze({
|
|
1047
|
+
key: digest(publication.key, "Ghostget cache publication key"),
|
|
1048
|
+
dataRevision: digest(publication.dataRevision, "Ghostget cache publication data revision"),
|
|
1049
|
+
validatedAt: timestamp(publication.validatedAt, "Ghostget cache publication validation time"),
|
|
1050
|
+
dataChangedAt: timestamp(publication.dataChangedAt, "Ghostget cache publication data-change time"),
|
|
1051
|
+
disposition,
|
|
1052
|
+
...publication.currentDataRevision === undefined ? {} : {
|
|
1053
|
+
currentDataRevision: digest(publication.currentDataRevision, "Ghostget current cache data revision")
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
function parseCacheOutcome(value) {
|
|
1058
|
+
const outcome = record(value, "Ghostget cache outcome");
|
|
1059
|
+
if (outcome.status === "stored") {
|
|
1060
|
+
assertExactKeys(outcome, ["status", "publication"], [], "Ghostget cache outcome");
|
|
1061
|
+
return Object.freeze({ status: "stored", publication: parsePublication(outcome.publication) });
|
|
1062
|
+
}
|
|
1063
|
+
if (outcome.status === "retained" && outcome.reason === "live-read-failed") {
|
|
1064
|
+
assertExactKeys(outcome, ["status", "reason"], [], "Ghostget cache outcome");
|
|
1065
|
+
return Object.freeze({ status: "retained", reason: "live-read-failed" });
|
|
1066
|
+
}
|
|
1067
|
+
if (outcome.status === "miss" && outcome.reason === "no-cached-snapshot") {
|
|
1068
|
+
assertExactKeys(outcome, ["status", "reason"], [], "Ghostget cache outcome");
|
|
1069
|
+
return Object.freeze({ status: "miss", reason: "no-cached-snapshot" });
|
|
1070
|
+
}
|
|
1071
|
+
if (outcome.status === "skipped" && outcome.reason === "auth-subject-unbound") {
|
|
1072
|
+
assertExactKeys(outcome, ["status", "reason"], [], "Ghostget cache outcome");
|
|
1073
|
+
return Object.freeze({ status: "skipped", reason: outcome.reason });
|
|
1074
|
+
}
|
|
1075
|
+
if (outcome.status === "error") {
|
|
1076
|
+
assertExactKeys(outcome, ["status", "message"], [], "Ghostget cache outcome");
|
|
1077
|
+
return Object.freeze({ status: "error", message: safeString(outcome.message, "Ghostget cache error", MAX_ERROR_BYTES) });
|
|
1078
|
+
}
|
|
1079
|
+
throw new Error("Ghostget cache outcome is malformed");
|
|
1080
|
+
}
|
|
1081
|
+
function parseReceiptStatus(value) {
|
|
1082
|
+
if (value !== "succeeded" && value !== "failed") {
|
|
1083
|
+
throw new Error("Ghostget live receipt status is malformed");
|
|
1084
|
+
}
|
|
1085
|
+
return value;
|
|
1086
|
+
}
|
|
1087
|
+
function parsePortableOperationIdentity(value) {
|
|
1088
|
+
return parsePortableOperationIdentityV1(value);
|
|
1089
|
+
}
|
|
1090
|
+
function parseLocalCliToolIdentity(value) {
|
|
1091
|
+
const strictRecord = (candidate, label) => {
|
|
1092
|
+
if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate) || nodeTypes.isProxy(candidate) || Object.getPrototypeOf(candidate) !== Object.prototype && Object.getPrototypeOf(candidate) !== null)
|
|
1093
|
+
throw new Error(`${label} is malformed`);
|
|
1094
|
+
const descriptors = Object.getOwnPropertyDescriptors(candidate);
|
|
1095
|
+
if (Reflect.ownKeys(descriptors).some((key) => typeof key !== "string")) {
|
|
1096
|
+
throw new Error(`${label} is malformed`);
|
|
1097
|
+
}
|
|
1098
|
+
const result = {};
|
|
1099
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
1100
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
1101
|
+
throw new Error(`${label} is malformed`);
|
|
1102
|
+
}
|
|
1103
|
+
result[key] = descriptor.value;
|
|
1104
|
+
}
|
|
1105
|
+
return result;
|
|
1106
|
+
};
|
|
1107
|
+
const strictArray = (candidate, label) => {
|
|
1108
|
+
if (!Array.isArray(candidate) || nodeTypes.isProxy(candidate) || Object.getPrototypeOf(candidate) !== Array.prototype || candidate.length > 16)
|
|
1109
|
+
throw new Error(`${label} is malformed`);
|
|
1110
|
+
const descriptors = Object.getOwnPropertyDescriptors(candidate);
|
|
1111
|
+
if (Reflect.ownKeys(descriptors).some((key) => typeof key !== "string") || Object.keys(descriptors).length !== candidate.length + 1)
|
|
1112
|
+
throw new Error(`${label} is malformed`);
|
|
1113
|
+
return Object.freeze(Array.from({ length: candidate.length }, (_unused, index) => {
|
|
1114
|
+
const descriptor = descriptors[String(index)];
|
|
1115
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor)) {
|
|
1116
|
+
throw new Error(`${label} is malformed`);
|
|
1117
|
+
}
|
|
1118
|
+
return descriptor.value;
|
|
1119
|
+
}));
|
|
1120
|
+
};
|
|
1121
|
+
const exactHttpsUrl = (candidate, label) => {
|
|
1122
|
+
const source = safeString(candidate, label, 2000);
|
|
1123
|
+
let parsed;
|
|
1124
|
+
try {
|
|
1125
|
+
parsed = new URL(source);
|
|
1126
|
+
} catch {
|
|
1127
|
+
throw new Error(`${label} is malformed`);
|
|
1128
|
+
}
|
|
1129
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.hash !== "" || parsed.search !== "" || parsed.href !== source)
|
|
1130
|
+
throw new Error(`${label} is malformed`);
|
|
1131
|
+
return source;
|
|
1132
|
+
};
|
|
1133
|
+
const wellFormedVisible = (candidate) => {
|
|
1134
|
+
for (let index = 0;index < candidate.length; index += 1) {
|
|
1135
|
+
const code = candidate.charCodeAt(index);
|
|
1136
|
+
if (code <= 31 || code >= 127 && code <= 159)
|
|
1137
|
+
return false;
|
|
1138
|
+
if (code >= 55296 && code <= 56319) {
|
|
1139
|
+
if (index + 1 >= candidate.length)
|
|
1140
|
+
return false;
|
|
1141
|
+
const next = candidate.charCodeAt(index + 1);
|
|
1142
|
+
if (next < 56320 || next > 57343)
|
|
1143
|
+
return false;
|
|
1144
|
+
index += 1;
|
|
1145
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return true;
|
|
1150
|
+
};
|
|
1151
|
+
const token = (candidate, label) => {
|
|
1152
|
+
const parsed = safeString(candidate, label, 64);
|
|
1153
|
+
if (!/^[a-z0-9](?:[a-z0-9._+-]{0,62}[a-z0-9])?$/u.test(parsed)) {
|
|
1154
|
+
throw new Error(`${label} is malformed`);
|
|
1155
|
+
}
|
|
1156
|
+
return parsed;
|
|
1157
|
+
};
|
|
1158
|
+
const tool = strictRecord(value, "Ghostget local CLI tool identity");
|
|
1159
|
+
assertExactKeys(tool, [
|
|
1160
|
+
"schemaVersion",
|
|
1161
|
+
"id",
|
|
1162
|
+
"implementation",
|
|
1163
|
+
"versionScheme",
|
|
1164
|
+
"version",
|
|
1165
|
+
"artifacts"
|
|
1166
|
+
], [
|
|
1167
|
+
"sourceUrl",
|
|
1168
|
+
"releaseCommit",
|
|
1169
|
+
"releaseManifestSha256",
|
|
1170
|
+
"releaseManifestUrl"
|
|
1171
|
+
], "Ghostget local CLI tool identity");
|
|
1172
|
+
const artifactValues = strictArray(tool.artifacts, "Ghostget local CLI tool artifact table");
|
|
1173
|
+
if (tool.schemaVersion !== 1) {
|
|
1174
|
+
throw new Error("Ghostget local CLI tool identity is malformed");
|
|
1175
|
+
}
|
|
1176
|
+
if (artifactValues.length < 1) {
|
|
1177
|
+
throw new Error("Ghostget local CLI tool artifact table is malformed");
|
|
1178
|
+
}
|
|
1179
|
+
const artifacts = artifactValues.map((artifactValue, index) => {
|
|
1180
|
+
const artifact = strictRecord(artifactValue, `Ghostget local CLI tool artifact ${index}`);
|
|
1181
|
+
assertExactKeys(artifact, ["platform", "arch", "executableSha256"], ["archiveSha256", "downloadUrl"], `Ghostget local CLI tool artifact ${index}`);
|
|
1182
|
+
const archiveSha256 = artifact.archiveSha256 === undefined ? undefined : digest(artifact.archiveSha256, `Ghostget local CLI tool artifact ${index} archive hash`);
|
|
1183
|
+
const downloadUrl = artifact.downloadUrl === undefined ? undefined : exactHttpsUrl(artifact.downloadUrl, `Ghostget local CLI tool artifact ${index} download URL`);
|
|
1184
|
+
if (archiveSha256 === undefined !== (downloadUrl === undefined)) {
|
|
1185
|
+
throw new Error("Ghostget local CLI tool artifact archive provenance is malformed");
|
|
1186
|
+
}
|
|
1187
|
+
return Object.freeze({
|
|
1188
|
+
platform: token(artifact.platform, `Ghostget local CLI tool artifact ${index} platform`),
|
|
1189
|
+
arch: token(artifact.arch, `Ghostget local CLI tool artifact ${index} architecture`),
|
|
1190
|
+
executableSha256: digest(artifact.executableSha256, `Ghostget local CLI tool artifact ${index} executable hash`),
|
|
1191
|
+
...archiveSha256 === undefined ? {} : { archiveSha256 },
|
|
1192
|
+
...downloadUrl === undefined ? {} : { downloadUrl }
|
|
1193
|
+
});
|
|
1194
|
+
});
|
|
1195
|
+
const coordinates = artifacts.map((artifact) => `${artifact.platform}\x00${artifact.arch}`);
|
|
1196
|
+
if (new Set(coordinates).size !== coordinates.length || coordinates.some((coordinate, index) => index > 0 && coordinates[index - 1] >= coordinate))
|
|
1197
|
+
throw new Error("Ghostget local CLI tool artifact table is not canonical");
|
|
1198
|
+
const sourceUrl = tool.sourceUrl === undefined ? undefined : exactHttpsUrl(tool.sourceUrl, "Ghostget local CLI tool source URL");
|
|
1199
|
+
const releaseManifestSha256 = tool.releaseManifestSha256 === undefined ? undefined : digest(tool.releaseManifestSha256, "Ghostget local CLI tool release manifest hash");
|
|
1200
|
+
const releaseManifestUrl = tool.releaseManifestUrl === undefined ? undefined : exactHttpsUrl(tool.releaseManifestUrl, "Ghostget local CLI tool release manifest URL");
|
|
1201
|
+
if (releaseManifestSha256 === undefined !== (releaseManifestUrl === undefined))
|
|
1202
|
+
throw new Error("Ghostget local CLI tool release manifest provenance is malformed");
|
|
1203
|
+
if (tool.versionScheme !== "semver" && tool.versionScheme !== "opaque") {
|
|
1204
|
+
throw new Error("Ghostget local CLI tool version scheme is malformed");
|
|
1205
|
+
}
|
|
1206
|
+
if (typeof tool.version !== "string" || tool.version.length < 1 || tool.version.length > 128)
|
|
1207
|
+
throw new Error("Ghostget local CLI tool version is malformed");
|
|
1208
|
+
const version = tool.version;
|
|
1209
|
+
if (!wellFormedVisible(version) || tool.versionScheme === "semver" && !/^(?: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.test(version)) {
|
|
1210
|
+
throw new Error("Ghostget local CLI tool version is malformed");
|
|
1211
|
+
}
|
|
1212
|
+
const hasReleaseCommit = tool.releaseCommit !== undefined;
|
|
1213
|
+
const releaseCommit = hasReleaseCommit ? safeString(tool.releaseCommit, "Ghostget local CLI tool release commit", 40) : undefined;
|
|
1214
|
+
if (releaseCommit !== undefined && !/^[a-f0-9]{40}$/u.test(releaseCommit))
|
|
1215
|
+
throw new Error("Ghostget local CLI tool release commit is malformed");
|
|
1216
|
+
const implementation = safeString(tool.implementation, "Ghostget local CLI tool implementation", 256);
|
|
1217
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9._/+:-]{0,254}[A-Za-z0-9])?$/u.test(implementation)) {
|
|
1218
|
+
throw new Error("Ghostget local CLI tool implementation is malformed");
|
|
1219
|
+
}
|
|
1220
|
+
return Object.freeze({
|
|
1221
|
+
schemaVersion: 1,
|
|
1222
|
+
id: token(tool.id, "Ghostget local CLI tool ID"),
|
|
1223
|
+
implementation,
|
|
1224
|
+
versionScheme: tool.versionScheme,
|
|
1225
|
+
version,
|
|
1226
|
+
...releaseCommit === undefined ? {} : { releaseCommit },
|
|
1227
|
+
...releaseManifestSha256 === undefined ? {} : { releaseManifestSha256, releaseManifestUrl },
|
|
1228
|
+
...sourceUrl === undefined ? {} : { sourceUrl },
|
|
1229
|
+
artifacts: Object.freeze(artifacts)
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
function parseLocalCliContractIdentity(value) {
|
|
1233
|
+
const identity = record(value, "Ghostget local CLI contract identity");
|
|
1234
|
+
assertExactKeys(identity, ["surface", "action", "version", "hash", "tool"], [], "Ghostget local CLI contract identity");
|
|
1235
|
+
const surface = providerSurfaceId(identity.surface, "Ghostget local CLI surface");
|
|
1236
|
+
const action = providerOperationName(identity.action, "Ghostget local CLI action");
|
|
1237
|
+
return Object.freeze({
|
|
1238
|
+
surface,
|
|
1239
|
+
action,
|
|
1240
|
+
version: safeInteger(identity.version, "Ghostget local CLI contract version", 1, 1e6),
|
|
1241
|
+
hash: digest(identity.hash, "Ghostget local CLI contract hash"),
|
|
1242
|
+
tool: parseLocalCliToolIdentity(identity.tool)
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
function parseLiveReceipt(value, request, expectedInputHash) {
|
|
1246
|
+
const receipt = record(value, "Ghostget live receipt");
|
|
1247
|
+
const commonKeys = [
|
|
1248
|
+
"schemaVersion",
|
|
1249
|
+
"runId",
|
|
1250
|
+
"planDigest",
|
|
1251
|
+
"adapter",
|
|
1252
|
+
"operation",
|
|
1253
|
+
"risk",
|
|
1254
|
+
"inputHash",
|
|
1255
|
+
"auth",
|
|
1256
|
+
"transport",
|
|
1257
|
+
"status",
|
|
1258
|
+
"dispatchStarted",
|
|
1259
|
+
"dispatch",
|
|
1260
|
+
"startedAt",
|
|
1261
|
+
"finishedAt",
|
|
1262
|
+
"finalOrigin",
|
|
1263
|
+
"error"
|
|
1264
|
+
];
|
|
1265
|
+
if (receipt.schemaVersion === 2 && receipt.transport === "browser") {
|
|
1266
|
+
assertExactKeys(receipt, commonKeys, [], "Ghostget live receipt");
|
|
1267
|
+
} else if (receipt.schemaVersion === 3 && receipt.transport === "provider-api") {
|
|
1268
|
+
assertExactKeys(receipt, [...commonKeys, "providerContractHash"], [], "Ghostget live receipt");
|
|
1269
|
+
} else if (receipt.schemaVersion === 4 && receipt.transport === "web-session-api") {
|
|
1270
|
+
assertExactKeys(receipt, [...commonKeys, "webSessionContractHash"], [], "Ghostget live receipt");
|
|
1271
|
+
} else if (receipt.schemaVersion === 5 && receipt.transport === "reviewed-template-api") {
|
|
1272
|
+
assertExactKeys(receipt, [...commonKeys, "reviewedTemplateContractHash"], [], "Ghostget live receipt");
|
|
1273
|
+
} else if (receipt.schemaVersion === 6 && receipt.transport === "portable-provider-plugin") {
|
|
1274
|
+
assertExactKeys(receipt, [...commonKeys, "portablePluginContract"], [], "Ghostget live receipt");
|
|
1275
|
+
} else if (receipt.schemaVersion === 7 && receipt.transport === "local-cli") {
|
|
1276
|
+
assertExactKeys(receipt, [...commonKeys, "localCliContract"], [], "Ghostget live receipt");
|
|
1277
|
+
} else {
|
|
1278
|
+
throw new Error("Ghostget live receipt schema and transport are malformed");
|
|
1279
|
+
}
|
|
1280
|
+
const adapter = record(receipt.adapter, "Ghostget live receipt adapter");
|
|
1281
|
+
const auth = record(receipt.auth, "Ghostget live receipt auth");
|
|
1282
|
+
const dispatch = record(receipt.dispatch, "Ghostget live receipt dispatch");
|
|
1283
|
+
assertExactKeys(adapter, ["id", "version", "hash"], [], "Ghostget live receipt adapter");
|
|
1284
|
+
assertExactKeys(auth, ["id", "hash", "kind"], [], "Ghostget live receipt auth");
|
|
1285
|
+
assertExactKeys(dispatch, ["planned", "started", "verified"], [], "Ghostget live receipt dispatch");
|
|
1286
|
+
if (receipt.risk !== "R1") {
|
|
1287
|
+
throw new Error("Ghostget live receipt risk is malformed");
|
|
1288
|
+
}
|
|
1289
|
+
const authKind = auth.kind;
|
|
1290
|
+
if (authKind !== "browser-profile" && authKind !== "cookie-source" && authKind !== "cookies-file" && authKind !== "linked-device-store" && authKind !== "oauth-token-file" && authKind !== PUBLIC_WEB_SESSION_AUTHORITY_KIND)
|
|
1291
|
+
throw new Error("Ghostget live receipt auth kind is malformed");
|
|
1292
|
+
if (receipt.dispatchStarted !== false) {
|
|
1293
|
+
throw new Error("Ghostget live receipt dispatch state is malformed");
|
|
1294
|
+
}
|
|
1295
|
+
const planned = safeInteger(dispatch.planned, "Ghostget live receipt planned dispatch count", 0, Number.MAX_SAFE_INTEGER);
|
|
1296
|
+
const started = safeInteger(dispatch.started, "Ghostget live receipt started dispatch count", 0, Number.MAX_SAFE_INTEGER);
|
|
1297
|
+
const verified = safeInteger(dispatch.verified, "Ghostget live receipt verified dispatch count", 0, Number.MAX_SAFE_INTEGER);
|
|
1298
|
+
if (planned !== 0 || started !== 0 || verified !== 0) {
|
|
1299
|
+
throw new Error("Ghostget live receipt dispatch counts are inconsistent");
|
|
1300
|
+
}
|
|
1301
|
+
const status = parseReceiptStatus(receipt.status);
|
|
1302
|
+
const finalOrigin = receipt.finalOrigin === null ? null : safeString(receipt.finalOrigin, "Ghostget live receipt final origin", 4096);
|
|
1303
|
+
const error = receipt.error === null ? null : boundedUtf8String(receipt.error, "Ghostget live receipt error", MAX_RUN_RECEIPT_ERROR_BYTES);
|
|
1304
|
+
if (receipt.planDigest !== null) {
|
|
1305
|
+
throw new Error("Ghostget live receipt plan digest is malformed");
|
|
1306
|
+
}
|
|
1307
|
+
const startedAt = timestamp(receipt.startedAt, "Ghostget live receipt start time");
|
|
1308
|
+
const finishedAt = timestamp(receipt.finishedAt, "Ghostget live receipt finish time");
|
|
1309
|
+
if (startedAt > finishedAt) {
|
|
1310
|
+
throw new Error("Ghostget live receipt finished before it started");
|
|
1311
|
+
}
|
|
1312
|
+
const common = Object.freeze({
|
|
1313
|
+
runId: safeString(receipt.runId, "Ghostget live receipt run ID", 64),
|
|
1314
|
+
planDigest: null,
|
|
1315
|
+
adapter: Object.freeze({
|
|
1316
|
+
id: safeString(adapter.id, "Ghostget live receipt adapter ID", 64),
|
|
1317
|
+
version: safeString(adapter.version, "Ghostget live receipt adapter version", 64),
|
|
1318
|
+
hash: digest(adapter.hash, "Ghostget live receipt adapter hash")
|
|
1319
|
+
}),
|
|
1320
|
+
operation: providerOperationName(receipt.operation, "Ghostget live receipt operation"),
|
|
1321
|
+
risk: "R1",
|
|
1322
|
+
inputHash: digest(receipt.inputHash, "Ghostget live receipt input hash"),
|
|
1323
|
+
auth: Object.freeze({
|
|
1324
|
+
id: safeString(auth.id, "Ghostget live receipt auth ID", 64),
|
|
1325
|
+
hash: digest(auth.hash, "Ghostget live receipt auth hash"),
|
|
1326
|
+
kind: authKind
|
|
1327
|
+
}),
|
|
1328
|
+
status,
|
|
1329
|
+
dispatchStarted: false,
|
|
1330
|
+
dispatch: Object.freeze({
|
|
1331
|
+
planned: 0,
|
|
1332
|
+
started: 0,
|
|
1333
|
+
verified: 0
|
|
1334
|
+
}),
|
|
1335
|
+
startedAt,
|
|
1336
|
+
finishedAt,
|
|
1337
|
+
finalOrigin,
|
|
1338
|
+
error
|
|
1339
|
+
});
|
|
1340
|
+
if (common.adapter.id !== request.adapterId || common.operation !== request.operationId)
|
|
1341
|
+
throw new Error("Ghostget live receipt route does not match its request");
|
|
1342
|
+
if (common.auth.id !== expectedRequestAuthId(request, common.auth.kind)) {
|
|
1343
|
+
throw new Error("Ghostget live receipt auth does not match its request");
|
|
1344
|
+
}
|
|
1345
|
+
if (common.auth.kind === PUBLIC_WEB_SESSION_AUTHORITY_KIND) {
|
|
1346
|
+
const authority = publicWebSessionAuthority(request);
|
|
1347
|
+
if (receipt.schemaVersion !== 4 || receipt.transport !== "web-session-api" || common.auth.hash !== sha256(canonicalJson(authority))) {
|
|
1348
|
+
throw new Error("Ghostget live receipt public authority is malformed");
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
if (common.inputHash !== expectedInputHash) {
|
|
1352
|
+
throw new Error("Ghostget live receipt input does not match its request");
|
|
1353
|
+
}
|
|
1354
|
+
if (receipt.schemaVersion === 2 && receipt.transport === "browser") {
|
|
1355
|
+
return Object.freeze({ ...common, schemaVersion: 2, transport: "browser" });
|
|
1356
|
+
}
|
|
1357
|
+
if (receipt.schemaVersion === 3 && receipt.transport === "provider-api") {
|
|
1358
|
+
return Object.freeze({
|
|
1359
|
+
...common,
|
|
1360
|
+
schemaVersion: 3,
|
|
1361
|
+
transport: "provider-api",
|
|
1362
|
+
providerContractHash: digest(receipt.providerContractHash, "Ghostget live receipt provider contract hash")
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
if (receipt.schemaVersion === 4 && receipt.transport === "web-session-api") {
|
|
1366
|
+
return Object.freeze({
|
|
1367
|
+
...common,
|
|
1368
|
+
schemaVersion: 4,
|
|
1369
|
+
transport: "web-session-api",
|
|
1370
|
+
webSessionContractHash: digest(receipt.webSessionContractHash, "Ghostget live receipt web-session contract hash")
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
if (receipt.schemaVersion === 5 && receipt.transport === "reviewed-template-api") {
|
|
1374
|
+
return Object.freeze({
|
|
1375
|
+
...common,
|
|
1376
|
+
schemaVersion: 5,
|
|
1377
|
+
transport: "reviewed-template-api",
|
|
1378
|
+
reviewedTemplateContractHash: digest(receipt.reviewedTemplateContractHash, "Ghostget live receipt reviewed-template contract hash")
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
if (receipt.schemaVersion === 6 && receipt.transport === "portable-provider-plugin") {
|
|
1382
|
+
const portablePluginContract = parsePortableOperationIdentity(receipt.portablePluginContract);
|
|
1383
|
+
if (portablePluginContract.adapterId !== common.adapter.id || portablePluginContract.operation !== common.operation) {
|
|
1384
|
+
throw new Error("Ghostget live portable contract route is malformed");
|
|
1385
|
+
}
|
|
1386
|
+
return Object.freeze({
|
|
1387
|
+
...common,
|
|
1388
|
+
schemaVersion: 6,
|
|
1389
|
+
transport: "portable-provider-plugin",
|
|
1390
|
+
portablePluginContract
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
if (receipt.schemaVersion === 7 && receipt.transport === "local-cli") {
|
|
1394
|
+
const localCliContract = parseLocalCliContractIdentity(receipt.localCliContract);
|
|
1395
|
+
if (localCliContract.action !== common.operation) {
|
|
1396
|
+
throw new Error("Ghostget live local CLI contract route is malformed");
|
|
1397
|
+
}
|
|
1398
|
+
return Object.freeze({
|
|
1399
|
+
...common,
|
|
1400
|
+
schemaVersion: 7,
|
|
1401
|
+
transport: "local-cli",
|
|
1402
|
+
localCliContract
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
throw new Error("Ghostget live receipt schema and transport are malformed");
|
|
1406
|
+
}
|
|
1407
|
+
function parseLiveResult(value, request, expectedInputHash) {
|
|
1408
|
+
assertExactKeys(value, ["ok", "source", "status", "runId", "replayed", "receipt", "output", "cache"], ["readFailure"], "Ghostget live response");
|
|
1409
|
+
if (value.source !== "live")
|
|
1410
|
+
throw new Error("Ghostget live response has the wrong source");
|
|
1411
|
+
const receipt = parseLiveReceipt(value.receipt, request, expectedInputHash);
|
|
1412
|
+
const status = safeString(value.status, "Ghostget live status", 32);
|
|
1413
|
+
const responseRunId = safeString(value.runId, "Ghostget live run ID", 64);
|
|
1414
|
+
const expectedOk = receipt.status === "succeeded";
|
|
1415
|
+
if (status !== receipt.status || receipt.runId !== responseRunId || value.ok !== expectedOk) {
|
|
1416
|
+
throw new Error("Ghostget live response is not bound to its receipt");
|
|
1417
|
+
}
|
|
1418
|
+
if (typeof value.replayed !== "boolean" || !("output" in value)) {
|
|
1419
|
+
throw new Error("Ghostget live response is incomplete");
|
|
1420
|
+
}
|
|
1421
|
+
const cache = parseCacheOutcome(value.cache);
|
|
1422
|
+
const cacheMatchesReceipt = receipt.status === "succeeded" ? cache.status === "stored" || cache.status === "error" || cache.status === "skipped" : cache.status === "retained" || cache.status === "miss" || cache.status === "error" || cache.status === "skipped";
|
|
1423
|
+
if (!cacheMatchesReceipt) {
|
|
1424
|
+
throw new Error("Ghostget live cache outcome is inconsistent with its receipt");
|
|
1425
|
+
}
|
|
1426
|
+
const live = receipt.status === "failed" ? (() => {
|
|
1427
|
+
if (value.output !== null) {
|
|
1428
|
+
throw new Error("Ghostget failed live response retained an output");
|
|
1429
|
+
}
|
|
1430
|
+
return Object.freeze({
|
|
1431
|
+
status: "failed",
|
|
1432
|
+
receipt,
|
|
1433
|
+
output: null,
|
|
1434
|
+
replayed: value.replayed,
|
|
1435
|
+
readFailure: parseClientReadFailure(value.readFailure)
|
|
1436
|
+
});
|
|
1437
|
+
})() : (() => {
|
|
1438
|
+
if (value.readFailure !== undefined) {
|
|
1439
|
+
throw new Error("Ghostget successful live response included a read failure");
|
|
1440
|
+
}
|
|
1441
|
+
return Object.freeze({
|
|
1442
|
+
status: "succeeded",
|
|
1443
|
+
receipt,
|
|
1444
|
+
output: value.output,
|
|
1445
|
+
replayed: value.replayed
|
|
1446
|
+
});
|
|
1447
|
+
})();
|
|
1448
|
+
return Object.freeze({
|
|
1449
|
+
live,
|
|
1450
|
+
cache
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
function assertLiveInvocationFences(request, options, executionIdentityBefore, identityBefore, parsed, activity) {
|
|
1454
|
+
const identityAfter = observeProjectionIdentity(request, options);
|
|
1455
|
+
if (!projectionIdentitiesMatch(identityBefore, identityAfter)) {
|
|
1456
|
+
throw new Error(`Ghostget projection identity changed while ${activity} was running; the live result was discarded`);
|
|
1457
|
+
}
|
|
1458
|
+
if (!executionIdentitiesMatch(executionIdentityBefore, receiptExecutionIdentity(parsed.live.receipt))) {
|
|
1459
|
+
throw new Error(`Ghostget execution identity changed while ${activity} was running; the live result was discarded`);
|
|
1460
|
+
}
|
|
1461
|
+
if (parsed.live.receipt.auth.hash !== identityBefore.authHash) {
|
|
1462
|
+
throw new Error(`Ghostget projection identity changed while ${activity} was running; the live result was discarded`);
|
|
1463
|
+
}
|
|
1464
|
+
if (identityBefore.status === "unbound") {
|
|
1465
|
+
if (parsed.cache.status !== "skipped") {
|
|
1466
|
+
throw new Error(`Ghostget projection identity changed while ${activity} was running; the live result was discarded`);
|
|
1467
|
+
}
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (parsed.cache.status === "skipped" || parsed.cache.status === "stored" && parsed.cache.publication.key !== identityBefore.key) {
|
|
1471
|
+
throw new Error(`Ghostget projection identity changed while ${activity} was running; the live result was discarded`);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
function readCachedCapability(request, options = {}) {
|
|
1475
|
+
const preparedOptions = snapshotClientOptions(options, "read");
|
|
1476
|
+
const now = validateReadOptions(preparedOptions);
|
|
1477
|
+
const prepared = prepareRequest(request);
|
|
1478
|
+
return readCachedPreparedCapability(prepared, preparedOptions, now);
|
|
1479
|
+
}
|
|
1480
|
+
function readCachedPreparedCapability(request, options, now) {
|
|
1481
|
+
const command = preparedCommand(request, {
|
|
1482
|
+
cacheOnly: true,
|
|
1483
|
+
projectionIdentityOnly: false,
|
|
1484
|
+
headed: false
|
|
1485
|
+
});
|
|
1486
|
+
return parseCacheResult(runCacheCommand(command, options).output, options, now);
|
|
1487
|
+
}
|
|
1488
|
+
function selectCurrentCapability(cachedBefore, cachedAfter, parsed) {
|
|
1489
|
+
const before = cachedBefore?.status === "hit" ? cachedBefore : null;
|
|
1490
|
+
const after = cachedAfter?.status === "hit" ? cachedAfter : null;
|
|
1491
|
+
if (parsed.live.status === "failed")
|
|
1492
|
+
return after;
|
|
1493
|
+
if (parsed.cache.status === "stored") {
|
|
1494
|
+
if (after !== null)
|
|
1495
|
+
return after;
|
|
1496
|
+
if (parsed.cache.publication.disposition === "superseded")
|
|
1497
|
+
return null;
|
|
1498
|
+
return Object.freeze({ source: "live", output: parsed.live.output });
|
|
1499
|
+
}
|
|
1500
|
+
if (parsed.cache.status === "error") {
|
|
1501
|
+
const cacheAdvanced = after !== null && (before === null || after.runId !== before.runId || after.dataRevision !== before.dataRevision || after.validatedAt !== before.validatedAt);
|
|
1502
|
+
if (cacheAdvanced)
|
|
1503
|
+
return after;
|
|
1504
|
+
}
|
|
1505
|
+
return Object.freeze({ source: "live", output: parsed.live.output });
|
|
1506
|
+
}
|
|
1507
|
+
async function runRevalidation(request, options, executionIdentityBefore, identityBefore, cachedBefore, observationTime) {
|
|
1508
|
+
const parsed = parseLiveResult(await runLiveCommand(preparedCommand(request, {
|
|
1509
|
+
cacheOnly: false,
|
|
1510
|
+
projectionIdentityOnly: false,
|
|
1511
|
+
headed: options.headed ?? false
|
|
1512
|
+
}), options), request, identityBefore.inputHash);
|
|
1513
|
+
assertLiveInvocationFences(request, options, executionIdentityBefore, identityBefore, parsed, "revalidation");
|
|
1514
|
+
if (identityBefore.status === "unbound") {
|
|
1515
|
+
return Object.freeze({
|
|
1516
|
+
cachedBefore: null,
|
|
1517
|
+
cachedAfter: null,
|
|
1518
|
+
current: selectCurrentCapability(null, null, parsed),
|
|
1519
|
+
...parsed
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
let cachedAfter = null;
|
|
1523
|
+
try {
|
|
1524
|
+
cachedAfter = readCachedPreparedCapability(request, options, observationTime);
|
|
1525
|
+
} catch {}
|
|
1526
|
+
if (cachedBefore !== null && cachedBefore.key !== identityBefore.key || cachedAfter !== null && cachedAfter.key !== identityBefore.key) {
|
|
1527
|
+
throw new Error("Ghostget projection identity changed while revalidation was running; the live result was discarded");
|
|
1528
|
+
}
|
|
1529
|
+
return Object.freeze({
|
|
1530
|
+
cachedBefore,
|
|
1531
|
+
cachedAfter,
|
|
1532
|
+
current: selectCurrentCapability(cachedBefore, cachedAfter, parsed),
|
|
1533
|
+
...parsed
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
function scheduleRevalidation(request, options, observationTime, cachedBeforeSource) {
|
|
1537
|
+
return Promise.resolve().then(() => {
|
|
1538
|
+
const executionIdentityBefore = observeExecutionIdentity(request, options);
|
|
1539
|
+
const identityBefore = observeProjectionIdentity(request, options);
|
|
1540
|
+
let cachedBefore = cachedBeforeSource.status === "provided" ? cachedBeforeSource.value : null;
|
|
1541
|
+
if (cachedBeforeSource.status === "lookup" && identityBefore.status === "ready") {
|
|
1542
|
+
try {
|
|
1543
|
+
cachedBefore = readCachedPreparedCapability(request, options, observationTime);
|
|
1544
|
+
} catch {}
|
|
1545
|
+
}
|
|
1546
|
+
if (cachedBefore !== null && (identityBefore.status !== "ready" || cachedBefore.key !== identityBefore.key)) {
|
|
1547
|
+
throw new Error("Ghostget projection identity changed before revalidation started");
|
|
1548
|
+
}
|
|
1549
|
+
return runRevalidation(request, options, executionIdentityBefore, identityBefore, cachedBefore, observationTime);
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
function invokeCapability(request, options = {}) {
|
|
1553
|
+
const preparedOptions = snapshotClientOptions(options, "invoke-async");
|
|
1554
|
+
const prepared = prepareRequest(request);
|
|
1555
|
+
return Promise.resolve().then(async () => {
|
|
1556
|
+
const executionIdentityBefore = observeExecutionIdentity(prepared, preparedOptions);
|
|
1557
|
+
const identityBefore = observeProjectionIdentity(prepared, preparedOptions);
|
|
1558
|
+
const parsed = parseLiveResult(await runLiveCommand(preparedCommand(prepared, {
|
|
1559
|
+
cacheOnly: false,
|
|
1560
|
+
projectionIdentityOnly: false,
|
|
1561
|
+
headed: preparedOptions.headed ?? false
|
|
1562
|
+
}), preparedOptions), prepared, identityBefore.inputHash);
|
|
1563
|
+
assertLiveInvocationFences(prepared, preparedOptions, executionIdentityBefore, identityBefore, parsed, "invocation");
|
|
1564
|
+
return parsed.live;
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
function invokeCapabilitySync(request, options = {}) {
|
|
1568
|
+
const preparedOptions = snapshotClientOptions(options, "invoke-sync");
|
|
1569
|
+
const prepared = prepareRequest(request);
|
|
1570
|
+
const executionIdentityBefore = observeExecutionIdentity(prepared, preparedOptions);
|
|
1571
|
+
const identityBefore = observeProjectionIdentity(prepared, preparedOptions);
|
|
1572
|
+
const parsed = parseLiveResult(runLiveCommandSync(preparedCommand(prepared, {
|
|
1573
|
+
cacheOnly: false,
|
|
1574
|
+
projectionIdentityOnly: false,
|
|
1575
|
+
headed: preparedOptions.headed ?? false
|
|
1576
|
+
}), preparedOptions), prepared, identityBefore.inputHash);
|
|
1577
|
+
assertLiveInvocationFences(prepared, preparedOptions, executionIdentityBefore, identityBefore, parsed, "invocation");
|
|
1578
|
+
return parsed.live;
|
|
1579
|
+
}
|
|
1580
|
+
function revalidateCapability(request, options = {}) {
|
|
1581
|
+
const preparedOptions = snapshotClientOptions(options, "revalidation");
|
|
1582
|
+
const now = validateReadOptions(preparedOptions);
|
|
1583
|
+
const prepared = prepareRequest(request);
|
|
1584
|
+
return scheduleRevalidation(prepared, preparedOptions, now, { status: "lookup" });
|
|
1585
|
+
}
|
|
1586
|
+
function staleWhileRevalidateCapability(request, options = {}) {
|
|
1587
|
+
const preparedOptions = snapshotClientOptions(options, "revalidation");
|
|
1588
|
+
const now = validateReadOptions(preparedOptions);
|
|
1589
|
+
const prepared = prepareRequest(request);
|
|
1590
|
+
let cached = null;
|
|
1591
|
+
try {
|
|
1592
|
+
cached = readCachedPreparedCapability(prepared, preparedOptions, now);
|
|
1593
|
+
} catch {}
|
|
1594
|
+
return Object.freeze({
|
|
1595
|
+
cached,
|
|
1596
|
+
revalidation: scheduleRevalidation(prepared, preparedOptions, now, { status: "provided", value: cached })
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
export {
|
|
1600
|
+
staleWhileRevalidateCapability,
|
|
1601
|
+
revalidateCapability,
|
|
1602
|
+
readCachedCapability,
|
|
1603
|
+
invokeCapabilitySync,
|
|
1604
|
+
invokeCapability
|
|
1605
|
+
};
|