@hraness/ghostget 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +458 -0
- package/LICENSE +21 -0
- package/README.md +1359 -0
- package/bunfig.toml +2 -0
- package/dist/apple-photos-client.js +704 -0
- package/dist/beeper-client.js +4602 -0
- package/dist/client.js +1605 -0
- package/dist/index-26yq8q16.js +31 -0
- package/dist/index-9t13b4k7.js +508 -0
- package/dist/index-axwm9b3v.js +5 -0
- package/dist/index-gwk7rbyj.js +30 -0
- package/dist/index.js +701 -0
- package/dist/messaging.js +1130 -0
- package/dist/omni-client.js +1406 -0
- package/dist/whatsapp-client.js +355 -0
- package/docs/imessage-direct-provider.md +106 -0
- package/docs/rental-listings.md +94 -0
- package/package.json +600 -0
- package/skills/ghostget/SKILL.md +251 -0
- package/skills/ghostget/agents/openai.yaml +4 -0
- package/skills/ghostget/references/adapter-contract.md +181 -0
- package/skills/ghostget/references/apple-photos.md +115 -0
- package/skills/ghostget/references/article-drafts.md +235 -0
- package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
- package/skills/ghostget/references/cross-posting-video.md +139 -0
- package/skills/ghostget/references/cross-posting.md +82 -0
- package/skills/ghostget/references/derivation.md +390 -0
- package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
- package/skills/ghostget/references/install.md +66 -0
- package/skills/ghostget/references/linkedin-adapter.md +442 -0
- package/skills/ghostget/references/messaging.md +168 -0
- package/skills/ghostget/references/meta-comet-contract.md +153 -0
- package/skills/ghostget/references/platform-patterns.md +122 -0
- package/skills/ghostget/references/provider-plugins.md +496 -0
- package/skills/ghostget/references/reddit-flair.md +78 -0
- package/skills/ghostget/references/safety-and-state.md +214 -0
- package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
- package/skills/ghostget/references/social-platform-routing.md +66 -0
- package/skills/ghostget/references/social-profile-stats.md +195 -0
- package/skills/ghostget/references/social-video-platform-routing.md +89 -0
- package/skills/ghostget/references/whatsapp-adapter.md +101 -0
- package/skills/ghostget/references/x-adapter.md +332 -0
- package/skills/ghostget/references/x-ai-disclosure.md +71 -0
- package/src/apple-photos-cli.ts +639 -0
- package/src/apple-photos-client-types.ts +168 -0
- package/src/apple-photos-client.ts +201 -0
- package/src/apple-photos-contact-evidence.ts +838 -0
- package/src/apple-photos-local-source.ts +1131 -0
- package/src/args.ts +2053 -0
- package/src/article-draft-document.ts +417 -0
- package/src/article-draft-embeds.ts +123 -0
- package/src/article-draft-images.ts +214 -0
- package/src/assets/adapter-template/wrench-adapter.json +40 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
- package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
- package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
- package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
- package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
- package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
- package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
- package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
- package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
- package/src/assets/adapters/x/wrench-adapter.json +749 -0
- package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
- package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
- package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
- package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
- package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
- package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
- package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
- package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
- package/src/assets.d.ts +4 -0
- package/src/auth.ts +1157 -0
- package/src/beeper-client-types.ts +169 -0
- package/src/beeper-client.ts +250 -0
- package/src/beeper-contact-interactions-cli.ts +180 -0
- package/src/beeper-contact-interactions.ts +1612 -0
- package/src/beeper-message-bundle-v1.ts +257 -0
- package/src/beeper-message-like-me-cli.ts +84 -0
- package/src/beeper-message-like-me-export.ts +2274 -0
- package/src/beeper-message-like-me-recovery.ts +931 -0
- package/src/beeper-message-like-me-source.ts +4034 -0
- package/src/browser-admission.ts +928 -0
- package/src/browser-snapshots.ts +260 -0
- package/src/browser.ts +3873 -0
- package/src/canonical-json.ts +60 -0
- package/src/catalog-cli.ts +638 -0
- package/src/cli.ts +208 -0
- package/src/client-types.ts +301 -0
- package/src/client.ts +2726 -0
- package/src/confirmed-write-failure.ts +44 -0
- package/src/confirmed-write-model.ts +72 -0
- package/src/confirmed-write-platform.ts +1055 -0
- package/src/confirmed-write-program.ts +224 -0
- package/src/confirmed-write-runtime.ts +16 -0
- package/src/cursor-token.ts +228 -0
- package/src/derivation-dnr-readiness.ts +367 -0
- package/src/derivation-file-chooser.ts +384 -0
- package/src/derivation-network-boundary.ts +835 -0
- package/src/derivation-network-guard.ts +782 -0
- package/src/derivation-network-proxy-helper.ts +440 -0
- package/src/derivation-network-proxy.ts +385 -0
- package/src/derive-command-helper.ts +693 -0
- package/src/derive-fixtures.ts +343 -0
- package/src/derive-review.ts +980 -0
- package/src/derive.ts +4754 -0
- package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
- package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
- package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
- package/src/ghostget.ts +3672 -0
- package/src/gmail-capture.ts +1244 -0
- package/src/har-internal.ts +1242 -0
- package/src/har.ts +743 -0
- package/src/index.ts +218 -0
- package/src/invocation-read-platform.ts +55 -0
- package/src/invocation-read-program.ts +93 -0
- package/src/invocation-read-runtime.ts +11 -0
- package/src/linked-device-lifecycle-admission.ts +450 -0
- package/src/linked-device-lifecycle-journal.ts +1560 -0
- package/src/linked-device-lifecycle-runtime.ts +1602 -0
- package/src/local-cli-admission.ts +135 -0
- package/src/local-cli-contracts.ts +271 -0
- package/src/local-cli-execution.ts +98 -0
- package/src/local-cli-surface-contract.ts +1510 -0
- package/src/local-cli-tool-identity.ts +336 -0
- package/src/media/archive.ts +2325 -0
- package/src/media/args.ts +303 -0
- package/src/media/cli.ts +287 -0
- package/src/media/doctor.ts +475 -0
- package/src/media/ffmpeg.ts +1330 -0
- package/src/media/http-capture.ts +711 -0
- package/src/media/http-probe.ts +593 -0
- package/src/media/http.ts +387 -0
- package/src/media/index.ts +10 -0
- package/src/media/local-transcription.ts +613 -0
- package/src/media/lock.ts +321 -0
- package/src/media/manifest.ts +1712 -0
- package/src/media/metadata.ts +906 -0
- package/src/media/process.ts +723 -0
- package/src/media/revision.ts +166 -0
- package/src/media/runtime-closure.ts +1063 -0
- package/src/media/source-router.ts +187 -0
- package/src/media/transcriber-config.ts +1122 -0
- package/src/media/transcript-persistence-model.ts +15 -0
- package/src/media/transcript-persistence-platform.ts +89 -0
- package/src/media/transcript-persistence-program.ts +39 -0
- package/src/media/transcript-persistence-runtime.ts +24 -0
- package/src/media/transcript.ts +616 -0
- package/src/media/utf8-order.ts +30 -0
- package/src/media/whisper-cpp.ts +464 -0
- package/src/media/whisper-language.ts +58 -0
- package/src/media/yt-dlp.ts +439 -0
- package/src/message-like-me-agentic-messaging.ts +882 -0
- package/src/messaging-action-store.ts +793 -0
- package/src/messaging-runtime.ts +2136 -0
- package/src/messaging-store.ts +539 -0
- package/src/messaging-types.ts +1663 -0
- package/src/messaging.ts +334 -0
- package/src/model.ts +2671 -0
- package/src/oauth-google.ts +888 -0
- package/src/omni-client-types.ts +306 -0
- package/src/omni-client.ts +2225 -0
- package/src/omni-limits.ts +34 -0
- package/src/omni-model.ts +1614 -0
- package/src/omni-request.ts +379 -0
- package/src/omni-runtime.ts +1632 -0
- package/src/operation-deadline.ts +189 -0
- package/src/path-helper.ts +1656 -0
- package/src/pinned-https.ts +194 -0
- package/src/plan-assets.ts +832 -0
- package/src/platform-catalog.ts +1832 -0
- package/src/plugins/beeper-linked-device/plugin.ts +310 -0
- package/src/plugins/bluesky-web/plugin.ts +178 -0
- package/src/plugins/clasificados-web/plugin.ts +66 -0
- package/src/plugins/github-web/plugin.ts +80 -0
- package/src/plugins/gmail-official/plugin.ts +110 -0
- package/src/plugins/hacker-news-web/plugin.ts +52 -0
- package/src/plugins/imessage-direct/plugin.ts +195 -0
- package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
- package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
- package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
- package/src/plugins/linkedin-official/plugin.ts +84 -0
- package/src/plugins/linkedin-web/plugin.ts +412 -0
- package/src/plugins/meta-web/plugin.ts +317 -0
- package/src/plugins/reddit-web/flair.ts +492 -0
- package/src/plugins/reddit-web/plugin.ts +477 -0
- package/src/plugins/substack-web/plugin.ts +124 -0
- package/src/plugins/tiktok-web/plugin.ts +67 -0
- package/src/plugins/twitch-web/plugin.ts +54 -0
- package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
- package/src/plugins/x-official/plugin.ts +64 -0
- package/src/plugins/x-web/plugin.ts +488 -0
- package/src/plugins/youtube-web/plugin.ts +101 -0
- package/src/portable-run-recovery.ts +466 -0
- package/src/process-identity.ts +501 -0
- package/src/profile-clone-helper.ts +104 -0
- package/src/provider-catalog-views.ts +51 -0
- package/src/provider-context.ts +69 -0
- package/src/provider-contract-definitions-x.ts +45 -0
- package/src/provider-contract-definitions.ts +948 -0
- package/src/provider-contract-input-gmail.ts +176 -0
- package/src/provider-contract-input-linkedin.ts +77 -0
- package/src/provider-contract-input-x.ts +304 -0
- package/src/provider-contract-planning.ts +23 -0
- package/src/provider-contract-semantic-identity.ts +54 -0
- package/src/provider-contracts.ts +251 -0
- package/src/provider-http.ts +723 -0
- package/src/provider-plugin-auth.ts +36 -0
- package/src/provider-plugin-builtins.ts +229 -0
- package/src/provider-plugin-cleanup-barrier.ts +229 -0
- package/src/provider-plugin-cleanup-execution.ts +79 -0
- package/src/provider-plugin-cleanup-resource.ts +470 -0
- package/src/provider-plugin-contract-identity.ts +707 -0
- package/src/provider-plugin-host.ts +1972 -0
- package/src/provider-plugin-identifiers.ts +60 -0
- package/src/provider-plugin-invocation-lease.ts +889 -0
- package/src/provider-plugin-lifecycle-kernel.ts +566 -0
- package/src/provider-plugin-lifecycle.ts +1455 -0
- package/src/provider-plugin-module-analysis.ts +336 -0
- package/src/provider-plugin-package.ts +2530 -0
- package/src/provider-plugin-portable-authority.ts +106 -0
- package/src/provider-plugin-portable-catalog.ts +94 -0
- package/src/provider-plugin-portable-identity.ts +315 -0
- package/src/provider-plugin-portable-runtime.ts +2057 -0
- package/src/provider-plugin-protocol.ts +1625 -0
- package/src/provider-plugin-registry.ts +3342 -0
- package/src/provider-plugin-store.ts +2155 -0
- package/src/provider-plugin-test-fixture.ts +5 -0
- package/src/provider-plugin.ts +4907 -0
- package/src/provider-plugins.generated.ts +43 -0
- package/src/provider-plugins.ts +8 -0
- package/src/provider-subject.ts +41 -0
- package/src/provider.ts +315 -0
- package/src/providers/beeper-local-runtime.ts +5426 -0
- package/src/providers/beeper-local.ts +2739 -0
- package/src/providers/beeper-messaging.ts +462 -0
- package/src/providers/beeper-omni.ts +1159 -0
- package/src/providers/bluesky-web-runtime.ts +2895 -0
- package/src/providers/bluesky-web.ts +1614 -0
- package/src/providers/clasificados-web-runtime.ts +255 -0
- package/src/providers/clasificados-web.ts +503 -0
- package/src/providers/contact-projection.ts +301 -0
- package/src/providers/github-read-model.ts +94 -0
- package/src/providers/github-read-platform.ts +165 -0
- package/src/providers/github-read-program.ts +97 -0
- package/src/providers/github-web-runtime.ts +320 -0
- package/src/providers/github-web.ts +403 -0
- package/src/providers/gmail-api.ts +2834 -0
- package/src/providers/gmail-omni.ts +830 -0
- package/src/providers/gmail.ts +1130 -0
- package/src/providers/hacker-news-web-runtime.ts +220 -0
- package/src/providers/hacker-news-web.ts +809 -0
- package/src/providers/imessage-direct-install.ts +354 -0
- package/src/providers/imessage-direct-messaging.ts +532 -0
- package/src/providers/imessage-direct-omni.ts +400 -0
- package/src/providers/imessage-direct-runtime.ts +1498 -0
- package/src/providers/imessage-direct.ts +351 -0
- package/src/providers/instagram-video-foundations.ts +913 -0
- package/src/providers/instagram-web-profile-browser.ts +600 -0
- package/src/providers/iso-bmff.ts +338 -0
- package/src/providers/linkedin-company-platform.ts +42 -0
- package/src/providers/linkedin-company-program.ts +88 -0
- package/src/providers/linkedin-contact-failure.ts +84 -0
- package/src/providers/linkedin-contact-platform.ts +45 -0
- package/src/providers/linkedin-contact-program.ts +91 -0
- package/src/providers/linkedin-profile-activity-failure.ts +76 -0
- package/src/providers/linkedin-profile-activity-platform.ts +41 -0
- package/src/providers/linkedin-profile-activity-program.ts +67 -0
- package/src/providers/linkedin-read-failure.ts +52 -0
- package/src/providers/linkedin-self-platform.ts +53 -0
- package/src/providers/linkedin-self-program.ts +122 -0
- package/src/providers/linkedin-web-article-browser.ts +794 -0
- package/src/providers/linkedin-web-bootstrap.ts +146 -0
- package/src/providers/linkedin-web-contact.ts +830 -0
- package/src/providers/linkedin-web-feed-browser.ts +832 -0
- package/src/providers/linkedin-web-feed.ts +864 -0
- package/src/providers/linkedin-web-post-browser.ts +653 -0
- package/src/providers/linkedin-web-profile-browser.ts +683 -0
- package/src/providers/linkedin-web-runtime.ts +2388 -0
- package/src/providers/linkedin-web.ts +3305 -0
- package/src/providers/linkedin.ts +1885 -0
- package/src/providers/meta-bootstrap.ts +1043 -0
- package/src/providers/meta-facebook-group.ts +690 -0
- package/src/providers/meta-marketplace-relay.ts +747 -0
- package/src/providers/meta-omni.ts +373 -0
- package/src/providers/meta-relay-bundle.ts +1049 -0
- package/src/providers/meta-web-descriptors.ts +1740 -0
- package/src/providers/meta-web-runtime.ts +3388 -0
- package/src/providers/meta-web.ts +2931 -0
- package/src/providers/read-failure.ts +139 -0
- package/src/providers/reddit-omni.ts +462 -0
- package/src/providers/reddit-web-runtime.ts +1929 -0
- package/src/providers/reddit-web.ts +1611 -0
- package/src/providers/rental-listings.ts +460 -0
- package/src/providers/substack-omni.ts +406 -0
- package/src/providers/substack-video-mp4.ts +33 -0
- package/src/providers/substack-web-runtime.ts +2812 -0
- package/src/providers/substack-web.ts +1502 -0
- package/src/providers/tiktok-video-mp4.ts +33 -0
- package/src/providers/tiktok-web-runtime.ts +770 -0
- package/src/providers/tiktok-web.ts +2786 -0
- package/src/providers/twitch-web-runtime.ts +226 -0
- package/src/providers/twitch-web.ts +274 -0
- package/src/providers/whatsapp-account-identity.ts +55 -0
- package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
- package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
- package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
- package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
- package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
- package/src/providers/whatsapp-messaging.ts +132 -0
- package/src/providers/whatsapp-omni.ts +586 -0
- package/src/providers/whatsapp-web-runtime.ts +3108 -0
- package/src/providers/whatsapp-web.ts +1037 -0
- package/src/providers/x-image-provenance.ts +523 -0
- package/src/providers/x-made-with-ai.ts +106 -0
- package/src/providers/x-transaction-id.ts +413 -0
- package/src/providers/x-web-runtime.ts +3485 -0
- package/src/providers/x-web.ts +2332 -0
- package/src/providers/x.ts +1889 -0
- package/src/providers/youtube-web-runtime.ts +1400 -0
- package/src/providers/youtube-web.ts +1221 -0
- package/src/read-admission-runtime.ts +91 -0
- package/src/read-client.ts +521 -0
- package/src/read-effect-platform.ts +7 -0
- package/src/read-effect-runtime.ts +14 -0
- package/src/read-effect.ts +52 -0
- package/src/read-projection-admission.ts +866 -0
- package/src/read-projections.ts +3151 -0
- package/src/recovery.ts +1492 -0
- package/src/reviewed-template.ts +146 -0
- package/src/run-journal.ts +1272 -0
- package/src/runtime.ts +5116 -0
- package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
- package/src/scripts/install-whatsapp-protocol.sh +232 -0
- package/src/scripts/recover-install-stages.ts +1491 -0
- package/src/scripts/resolve-state-home.ts +30 -0
- package/src/scripts/scaffold-web-provider.ts +973 -0
- package/src/scripts/sync-bundled-adapters.ts +695 -0
- package/src/session-secrets.ts +1183 -0
- package/src/state-helper.bunfig.toml +2 -0
- package/src/state-helper.ts +2914 -0
- package/src/storage.ts +3842 -0
- package/src/transport-policy.ts +2 -0
- package/src/usage.ts +182 -0
- package/src/version.ts +2 -0
- package/src/web-session-authentication-policy.ts +136 -0
- package/src/web-session-cleanup-admission.ts +2344 -0
- package/src/web-session-cleanup-join.ts +99 -0
- package/src/web-session-client.ts +996 -0
- package/src/web-session-contract-definitions.ts +820 -0
- package/src/web-session-contract-planning.ts +42 -0
- package/src/web-session-contracts.ts +228 -0
- package/src/web-session-cookies.ts +500 -0
- package/src/web-session-execution.ts +385 -0
- package/src/web-session-http.ts +373 -0
- package/src/web-session-read-errors.ts +19 -0
- package/src/web-session-read-runtime.ts +124 -0
- package/src/web-session-recovery.ts +818 -0
- package/src/web-session-sites.ts +58 -0
- package/src/web-session-template.ts +994 -0
- package/src/web-session.ts +93 -0
- package/src/whatsapp-client-binding.ts +21 -0
- package/src/whatsapp-client-types.ts +75 -0
- package/src/whatsapp-client.ts +395 -0
- package/src/whatsapp-export-coordinate.ts +17 -0
- package/src/whatsapp-message-bundle-v2.ts +195 -0
- package/src/whatsapp-message-like-me-cli.ts +195 -0
- package/src/whatsapp-message-like-me-export.ts +1072 -0
- package/src/whatsapp-message-like-me-source.ts +1012 -0
- package/tsconfig.json +67 -0
|
@@ -0,0 +1,1510 @@
|
|
|
1
|
+
import { types as nodeTypes } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { sha256 } from "./canonical-json";
|
|
4
|
+
import { isProviderPluginOperationName } from "./provider-plugin-identifiers";
|
|
5
|
+
|
|
6
|
+
export const LOCAL_CLI_SURFACE_DISPOSITIONS = Object.freeze([
|
|
7
|
+
"supported",
|
|
8
|
+
"fixed",
|
|
9
|
+
"absorbed",
|
|
10
|
+
"replaced",
|
|
11
|
+
"internal",
|
|
12
|
+
"R4",
|
|
13
|
+
"unsupported",
|
|
14
|
+
] as const);
|
|
15
|
+
|
|
16
|
+
export type LocalCliSurfaceDispositionV1 =
|
|
17
|
+
(typeof LOCAL_CLI_SURFACE_DISPOSITIONS)[number];
|
|
18
|
+
|
|
19
|
+
export type LocalCliSurfaceDecisionV1 = Readonly<{
|
|
20
|
+
disposition: LocalCliSurfaceDispositionV1;
|
|
21
|
+
rationale: string;
|
|
22
|
+
operation: string | null;
|
|
23
|
+
replacement: string | null;
|
|
24
|
+
fixedValue: string | number | boolean | null;
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
export type LocalCliSurfacePathSemanticInputsV1 = Readonly<
|
|
28
|
+
Record<string, string | number | boolean | null>
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
export type LocalCliSurfaceDefaultV1 =
|
|
32
|
+
| Readonly<{ kind: "none" }>
|
|
33
|
+
| Readonly<{
|
|
34
|
+
kind: "literal";
|
|
35
|
+
value: string | number | boolean;
|
|
36
|
+
authority: "tagged-source" | "jit-plugin-source" | "sdk-openapi";
|
|
37
|
+
}>
|
|
38
|
+
| Readonly<{ kind: "derived"; description: string }>
|
|
39
|
+
| Readonly<{ kind: "environment"; name: string }>;
|
|
40
|
+
|
|
41
|
+
export type LocalCliSurfacePredicateV1 =
|
|
42
|
+
| Readonly<{ op: "true" }>
|
|
43
|
+
| Readonly<{ op: "present"; field: string }>
|
|
44
|
+
| Readonly<{ op: "eq"; field: string; value: string | number | boolean | null }>
|
|
45
|
+
| Readonly<{ op: "not"; predicate: LocalCliSurfacePredicateV1 }>
|
|
46
|
+
| Readonly<{
|
|
47
|
+
op: "and" | "or";
|
|
48
|
+
predicates: readonly LocalCliSurfacePredicateV1[];
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
export type LocalCliSurfaceInputRuleV1 = Readonly<{
|
|
52
|
+
namespace: "semantic-operation" | "upstream-command";
|
|
53
|
+
when: LocalCliSurfacePredicateV1;
|
|
54
|
+
require: readonly string[];
|
|
55
|
+
requireAny: readonly string[];
|
|
56
|
+
exactlyOne: readonly string[];
|
|
57
|
+
forbid: readonly string[];
|
|
58
|
+
rationale: string;
|
|
59
|
+
}>;
|
|
60
|
+
|
|
61
|
+
export type LocalCliSurfaceArgumentV1 = Readonly<{
|
|
62
|
+
name: string;
|
|
63
|
+
position: number;
|
|
64
|
+
required: boolean;
|
|
65
|
+
multiple: boolean;
|
|
66
|
+
valueType: "string" | "number" | "boolean";
|
|
67
|
+
enum: readonly string[];
|
|
68
|
+
default: LocalCliSurfaceDefaultV1;
|
|
69
|
+
decision: LocalCliSurfaceDecisionV1;
|
|
70
|
+
}>;
|
|
71
|
+
|
|
72
|
+
export type LocalCliSurfaceFlagV1 = Readonly<{
|
|
73
|
+
name: string;
|
|
74
|
+
aliases: readonly string[];
|
|
75
|
+
source: "command" | "global";
|
|
76
|
+
valueType: "string" | "number" | "boolean";
|
|
77
|
+
allowNo: boolean;
|
|
78
|
+
required: boolean;
|
|
79
|
+
multiple: boolean;
|
|
80
|
+
enum: readonly string[];
|
|
81
|
+
default: LocalCliSurfaceDefaultV1;
|
|
82
|
+
decision: LocalCliSurfaceDecisionV1;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
85
|
+
export type LocalCliSurfaceOutputV1 = Readonly<{
|
|
86
|
+
shape: string;
|
|
87
|
+
completeness:
|
|
88
|
+
| "complete"
|
|
89
|
+
| "bounded"
|
|
90
|
+
| "candidate-window"
|
|
91
|
+
| "input-dependent"
|
|
92
|
+
| "internal"
|
|
93
|
+
| "unavailable";
|
|
94
|
+
maxBytes: number | null;
|
|
95
|
+
privateArtifact: boolean;
|
|
96
|
+
truncation: string | null;
|
|
97
|
+
}>;
|
|
98
|
+
|
|
99
|
+
export type LocalCliSurfaceReconciliationV1 = Readonly<{
|
|
100
|
+
availability: "none" | "always" | "input-dependent";
|
|
101
|
+
namespace: "semantic-operation" | null;
|
|
102
|
+
predicate: LocalCliSurfacePredicateV1 | null;
|
|
103
|
+
rationale: string;
|
|
104
|
+
}>;
|
|
105
|
+
|
|
106
|
+
export type LocalCliSurfaceCommandDefinitionV1 = Readonly<{
|
|
107
|
+
path: readonly string[];
|
|
108
|
+
provenance: LocalCliSurfaceProvenanceKindV1;
|
|
109
|
+
profileAuthority: "tagged-source" | "jit-plugin-source";
|
|
110
|
+
package: string | null;
|
|
111
|
+
version: string | null;
|
|
112
|
+
versionKind: "exact" | "range" | null;
|
|
113
|
+
registered: boolean;
|
|
114
|
+
publicManual: boolean;
|
|
115
|
+
generatedCanonical: boolean;
|
|
116
|
+
upstreamReportedMutates: boolean | null;
|
|
117
|
+
reviewedEffect: "read" | "write" | "input-dependent";
|
|
118
|
+
arguments: readonly LocalCliSurfaceArgumentV1[];
|
|
119
|
+
flags: readonly LocalCliSurfaceFlagV1[];
|
|
120
|
+
decision: LocalCliSurfaceDecisionV1;
|
|
121
|
+
/** Semantic inputs fixed by the exact command path before caller input is applied. */
|
|
122
|
+
pathSemanticInputs: LocalCliSurfacePathSemanticInputsV1;
|
|
123
|
+
output: LocalCliSurfaceOutputV1;
|
|
124
|
+
conditionalInputs: readonly LocalCliSurfaceInputRuleV1[];
|
|
125
|
+
reconciliation: LocalCliSurfaceReconciliationV1;
|
|
126
|
+
}>;
|
|
127
|
+
|
|
128
|
+
export type LocalCliSurfaceCommandV1 = LocalCliSurfaceCommandDefinitionV1 &
|
|
129
|
+
Readonly<{ semanticProfileSha256: string }>;
|
|
130
|
+
|
|
131
|
+
export const LOCAL_CLI_SURFACE_PROVENANCE_KINDS = Object.freeze([
|
|
132
|
+
"built-in-canonical",
|
|
133
|
+
"built-in-hidden",
|
|
134
|
+
"built-in-alias",
|
|
135
|
+
"source-only-private",
|
|
136
|
+
"jit-plugin",
|
|
137
|
+
"dynamic-plugin",
|
|
138
|
+
"documented-only",
|
|
139
|
+
] as const);
|
|
140
|
+
|
|
141
|
+
export type LocalCliSurfaceProvenanceKindV1 =
|
|
142
|
+
(typeof LOCAL_CLI_SURFACE_PROVENANCE_KINDS)[number];
|
|
143
|
+
|
|
144
|
+
export type LocalCliSurfaceAdditionalEntryV1 = Readonly<{
|
|
145
|
+
path: readonly string[];
|
|
146
|
+
provenance: LocalCliSurfaceProvenanceKindV1;
|
|
147
|
+
profileAuthority: "tagged-source" | "framework-runtime" | "documentation";
|
|
148
|
+
canonicalTarget: readonly string[] | null;
|
|
149
|
+
package: string | null;
|
|
150
|
+
version: string | null;
|
|
151
|
+
versionKind: "exact" | "range" | null;
|
|
152
|
+
registered: boolean;
|
|
153
|
+
publicManual: boolean;
|
|
154
|
+
rationale: string;
|
|
155
|
+
decision: LocalCliSurfaceDecisionV1;
|
|
156
|
+
}>;
|
|
157
|
+
|
|
158
|
+
export type LocalCliSurfaceArtifactV1 = Readonly<{
|
|
159
|
+
platform: string;
|
|
160
|
+
arch: string;
|
|
161
|
+
archiveSha256: string;
|
|
162
|
+
executableSha256: string;
|
|
163
|
+
}>;
|
|
164
|
+
|
|
165
|
+
export type LocalCliSurfaceContractDefinitionV1 = Readonly<{
|
|
166
|
+
schemaVersion: 1;
|
|
167
|
+
format: "wrench.local-cli-surface";
|
|
168
|
+
surface: string;
|
|
169
|
+
executable: Readonly<{
|
|
170
|
+
id: string;
|
|
171
|
+
implementation: string;
|
|
172
|
+
releaseVersion: string;
|
|
173
|
+
releaseDate: string;
|
|
174
|
+
releaseTag: string;
|
|
175
|
+
releaseCommit: string;
|
|
176
|
+
releaseManifestSha256: string;
|
|
177
|
+
runtimeReportedName: string;
|
|
178
|
+
runtimeReportedVersion: string;
|
|
179
|
+
artifacts: readonly LocalCliSurfaceArtifactV1[];
|
|
180
|
+
}>;
|
|
181
|
+
source: Readonly<{
|
|
182
|
+
package: string;
|
|
183
|
+
packagePath: string;
|
|
184
|
+
packageDeclaredVersion: string;
|
|
185
|
+
versionDiscrepancy: string | null;
|
|
186
|
+
generatedManualSha256: string;
|
|
187
|
+
generatedManualIncludesFlagsAndDefaults: boolean;
|
|
188
|
+
generatedManualEntries: number;
|
|
189
|
+
generatedCanonicalEntries: number;
|
|
190
|
+
registeredKeys: number;
|
|
191
|
+
}>;
|
|
192
|
+
sdk: Readonly<{
|
|
193
|
+
package: string;
|
|
194
|
+
version: string;
|
|
195
|
+
commit: string;
|
|
196
|
+
}>;
|
|
197
|
+
runtime: Readonly<{
|
|
198
|
+
providerPluginId: string;
|
|
199
|
+
providerPluginVersion: string;
|
|
200
|
+
adapterId: string;
|
|
201
|
+
adapterVersion: string;
|
|
202
|
+
operationContractVersions: Readonly<Record<string, number>>;
|
|
203
|
+
operationInputTypes: Readonly<
|
|
204
|
+
Record<string, Readonly<Record<string, "string" | "number" | "boolean" | "array" | "file">>>
|
|
205
|
+
>;
|
|
206
|
+
target: string;
|
|
207
|
+
realm: string;
|
|
208
|
+
compatibility: string;
|
|
209
|
+
}>;
|
|
210
|
+
globalFlags: readonly LocalCliSurfaceFlagV1[];
|
|
211
|
+
commands: readonly LocalCliSurfaceCommandDefinitionV1[];
|
|
212
|
+
additionalEntries: readonly LocalCliSurfaceAdditionalEntryV1[];
|
|
213
|
+
}>;
|
|
214
|
+
|
|
215
|
+
export type LocalCliSurfaceContractV1 = Omit<
|
|
216
|
+
LocalCliSurfaceContractDefinitionV1,
|
|
217
|
+
"commands"
|
|
218
|
+
> & Readonly<{
|
|
219
|
+
commands: readonly LocalCliSurfaceCommandV1[];
|
|
220
|
+
digests: Readonly<{
|
|
221
|
+
upstreamSurfaceSha256: string;
|
|
222
|
+
classificationSha256: string;
|
|
223
|
+
semanticProfilesSha256: string;
|
|
224
|
+
wholeSurfaceSha256: string;
|
|
225
|
+
}>;
|
|
226
|
+
}>;
|
|
227
|
+
|
|
228
|
+
type JsonRecord = Readonly<Record<string, unknown>>;
|
|
229
|
+
|
|
230
|
+
function hasWellFormedUnicode(value: string): boolean {
|
|
231
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
232
|
+
const code = value.charCodeAt(index);
|
|
233
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
234
|
+
const next = value.charCodeAt(index + 1);
|
|
235
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) return false;
|
|
236
|
+
index += 1;
|
|
237
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) return false;
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function record(value: unknown, label: string): JsonRecord {
|
|
243
|
+
if (
|
|
244
|
+
nodeTypes.isProxy(value)
|
|
245
|
+
|| typeof value !== "object"
|
|
246
|
+
|| value === null
|
|
247
|
+
|| Array.isArray(value)
|
|
248
|
+
|| (
|
|
249
|
+
Object.getPrototypeOf(value) !== Object.prototype
|
|
250
|
+
&& Object.getPrototypeOf(value) !== null
|
|
251
|
+
)
|
|
252
|
+
) throw new Error(`${label} must be a plain data object`);
|
|
253
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
254
|
+
const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
255
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
256
|
+
if (typeof key !== "string") throw new Error(`${label} must not contain symbols`);
|
|
257
|
+
const descriptor = descriptors[key];
|
|
258
|
+
if (
|
|
259
|
+
descriptor === undefined
|
|
260
|
+
|| !("value" in descriptor)
|
|
261
|
+
|| !descriptor.enumerable
|
|
262
|
+
|| !hasWellFormedUnicode(key)
|
|
263
|
+
) throw new Error(`${label} must contain only enumerable Unicode data fields`);
|
|
264
|
+
result[key] = descriptor.value;
|
|
265
|
+
}
|
|
266
|
+
return Object.freeze(result);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function exactKeys(
|
|
270
|
+
value: JsonRecord,
|
|
271
|
+
required: readonly string[],
|
|
272
|
+
optional: readonly string[],
|
|
273
|
+
label: string,
|
|
274
|
+
): void {
|
|
275
|
+
const allowed = new Set([...required, ...optional]);
|
|
276
|
+
for (const key of Object.keys(value)) {
|
|
277
|
+
if (!allowed.has(key)) throw new Error(`${label} contains unsupported field ${key}`);
|
|
278
|
+
}
|
|
279
|
+
for (const key of required) {
|
|
280
|
+
if (!Object.hasOwn(value, key)) throw new Error(`${label}.${key} is required`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function array(value: unknown, label: string, maximum: number): readonly unknown[] {
|
|
285
|
+
if (
|
|
286
|
+
nodeTypes.isProxy(value)
|
|
287
|
+
|| !Array.isArray(value)
|
|
288
|
+
|| Object.getPrototypeOf(value) !== Array.prototype
|
|
289
|
+
|| value.length > maximum
|
|
290
|
+
|| Reflect.ownKeys(value).length !== value.length + 1
|
|
291
|
+
) throw new Error(`${label} must be a dense array of at most ${maximum} items`);
|
|
292
|
+
const result: unknown[] = [];
|
|
293
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
294
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
295
|
+
if (descriptor === undefined || !("value" in descriptor) || !descriptor.enumerable) {
|
|
296
|
+
throw new Error(`${label} must contain only enumerable data items`);
|
|
297
|
+
}
|
|
298
|
+
result.push(descriptor.value);
|
|
299
|
+
}
|
|
300
|
+
return Object.freeze(result);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function string(value: unknown, label: string, maximum = 1_024): string {
|
|
304
|
+
if (
|
|
305
|
+
typeof value !== "string"
|
|
306
|
+
|| value.length < 1
|
|
307
|
+
|| value.length > maximum
|
|
308
|
+
|| !hasWellFormedUnicode(value)
|
|
309
|
+
|| /[\u0000-\u001f\u007f-\u009f]/u.test(value)
|
|
310
|
+
) throw new Error(`${label} must be bounded Unicode text`);
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function nullableString(value: unknown, label: string): string | null {
|
|
315
|
+
return value === null ? null : string(value, label);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function parseVersionKind(
|
|
319
|
+
value: unknown,
|
|
320
|
+
versionValue: unknown,
|
|
321
|
+
label: string,
|
|
322
|
+
): "exact" | "range" | null {
|
|
323
|
+
if (versionValue === null) {
|
|
324
|
+
if (value !== null) throw new Error(`${label} requires a package version`);
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return exactEnum(value, label, ["exact", "range"] as const);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function boolean(value: unknown, label: string): boolean {
|
|
331
|
+
if (typeof value !== "boolean") throw new Error(`${label} must be boolean`);
|
|
332
|
+
return value;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function integer(value: unknown, label: string, minimum: number, maximum: number): number {
|
|
336
|
+
if (!Number.isSafeInteger(value) || (value as number) < minimum || (value as number) > maximum) {
|
|
337
|
+
throw new Error(`${label} must be a bounded integer`);
|
|
338
|
+
}
|
|
339
|
+
return value as number;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function exactEnum<T extends string>(
|
|
343
|
+
value: unknown,
|
|
344
|
+
label: string,
|
|
345
|
+
values: readonly T[],
|
|
346
|
+
): T {
|
|
347
|
+
if (typeof value !== "string" || !values.includes(value as T)) {
|
|
348
|
+
throw new Error(`${label} is unsupported`);
|
|
349
|
+
}
|
|
350
|
+
return value as T;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function sha(value: unknown, label: string): string {
|
|
354
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/u.test(value)) {
|
|
355
|
+
throw new Error(`${label} must be one SHA-256 digest`);
|
|
356
|
+
}
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function commitSha(value: unknown, label: string): string {
|
|
361
|
+
if (typeof value !== "string" || !/^[a-f0-9]{40}$/u.test(value)) {
|
|
362
|
+
throw new Error(`${label} must be one full Git commit ID`);
|
|
363
|
+
}
|
|
364
|
+
return value;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function canonicalDate(value: unknown, label: string): string {
|
|
368
|
+
if (typeof value !== "string" || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/u.test(value)) {
|
|
369
|
+
throw new Error(`${label} must be a canonical YYYY-MM-DD date`);
|
|
370
|
+
}
|
|
371
|
+
const milliseconds = Date.parse(`${value}T00:00:00.000Z`);
|
|
372
|
+
if (
|
|
373
|
+
!Number.isFinite(milliseconds)
|
|
374
|
+
|| new Date(milliseconds).toISOString().slice(0, 10) !== value
|
|
375
|
+
) throw new Error(`${label} must be a canonical YYYY-MM-DD date`);
|
|
376
|
+
return value;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function scalar(value: unknown, label: string): string | number | boolean | null {
|
|
380
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
381
|
+
if (typeof value === "string") string(value, label, 4_096);
|
|
382
|
+
return value;
|
|
383
|
+
}
|
|
384
|
+
if (typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0)) return value;
|
|
385
|
+
throw new Error(`${label} must be a finite JSON scalar`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function stringList(value: unknown, label: string, maximum: number): readonly string[] {
|
|
389
|
+
const result = array(value, label, maximum).map((item, index) =>
|
|
390
|
+
string(item, `${label}[${index}]`, 512));
|
|
391
|
+
if (new Set(result).size !== result.length) throw new Error(`${label} repeats a value`);
|
|
392
|
+
return Object.freeze(result);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function commandPath(value: unknown, label: string): readonly string[] {
|
|
396
|
+
const path = stringList(value, label, 16);
|
|
397
|
+
if (path.length < 1 || path.some((segment) => /\s/u.test(segment))) {
|
|
398
|
+
throw new Error(`${label} must contain nonempty whitespace-free command segments`);
|
|
399
|
+
}
|
|
400
|
+
return path;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function codePointCompare(left: string, right: string): number {
|
|
404
|
+
const leftCodePoints = [...left];
|
|
405
|
+
const rightCodePoints = [...right];
|
|
406
|
+
const length = Math.min(leftCodePoints.length, rightCodePoints.length);
|
|
407
|
+
for (let index = 0; index < length; index += 1) {
|
|
408
|
+
const leftCodePoint = leftCodePoints[index]!.codePointAt(0)!;
|
|
409
|
+
const rightCodePoint = rightCodePoints[index]!.codePointAt(0)!;
|
|
410
|
+
if (leftCodePoint !== rightCodePoint) return leftCodePoint < rightCodePoint ? -1 : 1;
|
|
411
|
+
}
|
|
412
|
+
return leftCodePoints.length < rightCodePoints.length
|
|
413
|
+
? -1
|
|
414
|
+
: leftCodePoints.length > rightCodePoints.length ? 1 : 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Surface-scoped canonical JSON; intentionally independent of host locale. */
|
|
418
|
+
function surfaceCanonicalJson(value: unknown): string {
|
|
419
|
+
if (value === null) return "null";
|
|
420
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
421
|
+
return JSON.stringify(value);
|
|
422
|
+
}
|
|
423
|
+
if (typeof value === "number") {
|
|
424
|
+
if (!Number.isFinite(value) || Object.is(value, -0)) {
|
|
425
|
+
throw new Error("local CLI surface canonical JSON contains an invalid number");
|
|
426
|
+
}
|
|
427
|
+
return JSON.stringify(value);
|
|
428
|
+
}
|
|
429
|
+
if (Array.isArray(value)) {
|
|
430
|
+
return `[${value.map((item) => surfaceCanonicalJson(item)).join(",")}]`;
|
|
431
|
+
}
|
|
432
|
+
if (typeof value !== "object" || value === null) {
|
|
433
|
+
throw new Error("local CLI surface canonical JSON contains a non-JSON value");
|
|
434
|
+
}
|
|
435
|
+
const entries = Object.entries(value).sort(([left], [right]) => codePointCompare(left, right));
|
|
436
|
+
return `{${entries.map(([key, item]) =>
|
|
437
|
+
`${JSON.stringify(key)}:${surfaceCanonicalJson(item)}`).join(",")}}`;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function parseDefault(value: unknown, label: string): LocalCliSurfaceDefaultV1 {
|
|
441
|
+
const source = record(value, label);
|
|
442
|
+
const kind = exactEnum(source.kind, `${label}.kind`, [
|
|
443
|
+
"none", "literal", "derived", "environment",
|
|
444
|
+
] as const);
|
|
445
|
+
if (kind === "none") {
|
|
446
|
+
exactKeys(source, ["kind"], [], label);
|
|
447
|
+
return Object.freeze({ kind });
|
|
448
|
+
}
|
|
449
|
+
if (kind === "literal") {
|
|
450
|
+
exactKeys(source, ["kind", "value", "authority"], [], label);
|
|
451
|
+
const value = scalar(source.value, `${label}.value`);
|
|
452
|
+
if (value === null) throw new Error(`${label}.literal null is ambiguous with kind none`);
|
|
453
|
+
return Object.freeze({
|
|
454
|
+
kind,
|
|
455
|
+
value,
|
|
456
|
+
authority: exactEnum(source.authority, `${label}.authority`, [
|
|
457
|
+
"tagged-source", "jit-plugin-source", "sdk-openapi",
|
|
458
|
+
] as const),
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
if (kind === "derived") {
|
|
462
|
+
exactKeys(source, ["kind", "description"], [], label);
|
|
463
|
+
return Object.freeze({ kind, description: string(source.description, `${label}.description`, 500) });
|
|
464
|
+
}
|
|
465
|
+
exactKeys(source, ["kind", "name"], [], label);
|
|
466
|
+
const name = string(source.name, `${label}.name`, 128);
|
|
467
|
+
if (!/^[A-Z][A-Z0-9_]*$/u.test(name)) throw new Error(`${label}.name must be an environment variable`);
|
|
468
|
+
return Object.freeze({ kind, name });
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function parseDecision(value: unknown, label: string): LocalCliSurfaceDecisionV1 {
|
|
472
|
+
const source = record(value, label);
|
|
473
|
+
exactKeys(source, [
|
|
474
|
+
"disposition", "rationale", "operation", "replacement", "fixedValue",
|
|
475
|
+
], [], label);
|
|
476
|
+
const disposition = exactEnum(
|
|
477
|
+
source.disposition,
|
|
478
|
+
`${label}.disposition`,
|
|
479
|
+
LOCAL_CLI_SURFACE_DISPOSITIONS,
|
|
480
|
+
);
|
|
481
|
+
const operation = nullableString(source.operation, `${label}.operation`);
|
|
482
|
+
const replacement = nullableString(source.replacement, `${label}.replacement`);
|
|
483
|
+
const fixedValue = scalar(source.fixedValue, `${label}.fixedValue`);
|
|
484
|
+
if (
|
|
485
|
+
operation !== null
|
|
486
|
+
&& !isProviderPluginOperationName(operation)
|
|
487
|
+
) throw new Error(`${label}.operation must be a semantic operation name`);
|
|
488
|
+
if (disposition === "supported" && operation === null) {
|
|
489
|
+
throw new Error(`${label} supported disposition requires an operation`);
|
|
490
|
+
}
|
|
491
|
+
if (disposition === "fixed" && fixedValue === null) {
|
|
492
|
+
throw new Error(`${label}.fixedValue is required for fixed disposition`);
|
|
493
|
+
}
|
|
494
|
+
if (disposition !== "fixed" && fixedValue !== null) {
|
|
495
|
+
throw new Error(`${label}.fixedValue requires fixed disposition`);
|
|
496
|
+
}
|
|
497
|
+
return Object.freeze({
|
|
498
|
+
disposition,
|
|
499
|
+
rationale: string(source.rationale, `${label}.rationale`, 1_000),
|
|
500
|
+
operation,
|
|
501
|
+
replacement,
|
|
502
|
+
fixedValue,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function parsePathSemanticInputs(
|
|
507
|
+
value: unknown,
|
|
508
|
+
label: string,
|
|
509
|
+
): LocalCliSurfacePathSemanticInputsV1 {
|
|
510
|
+
const source = record(value, label);
|
|
511
|
+
const entries = Object.entries(source);
|
|
512
|
+
if (entries.length > 128) {
|
|
513
|
+
throw new Error(`${label} exceeds its semantic input bound`);
|
|
514
|
+
}
|
|
515
|
+
const parsed: Record<string, string | number | boolean | null> =
|
|
516
|
+
Object.create(null) as Record<string, string | number | boolean | null>;
|
|
517
|
+
for (const [field, fieldValue] of entries) {
|
|
518
|
+
if (!/^[a-z][a-z0-9_]{0,127}$/u.test(field)) {
|
|
519
|
+
throw new Error(`${label} contains an invalid semantic input field ${field}`);
|
|
520
|
+
}
|
|
521
|
+
parsed[field] = scalar(fieldValue, `${label}.${field}`);
|
|
522
|
+
}
|
|
523
|
+
return Object.freeze(parsed);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
type PredicateTraversal = {
|
|
527
|
+
readonly seen: WeakSet<object>;
|
|
528
|
+
nodes: number;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
function parsePredicate(
|
|
532
|
+
value: unknown,
|
|
533
|
+
label: string,
|
|
534
|
+
traversal: PredicateTraversal,
|
|
535
|
+
depth = 0,
|
|
536
|
+
): LocalCliSurfacePredicateV1 {
|
|
537
|
+
if (depth > 8) throw new Error(`${label} exceeds the predicate depth bound`);
|
|
538
|
+
if (typeof value !== "object" || value === null) {
|
|
539
|
+
throw new Error(`${label} must be a predicate data object`);
|
|
540
|
+
}
|
|
541
|
+
if (traversal.seen.has(value)) {
|
|
542
|
+
throw new Error(`${label} repeats a predicate object identity`);
|
|
543
|
+
}
|
|
544
|
+
traversal.seen.add(value);
|
|
545
|
+
traversal.nodes += 1;
|
|
546
|
+
if (traversal.nodes > 10_000) {
|
|
547
|
+
throw new Error("local CLI surface predicates exceed the whole-contract node bound");
|
|
548
|
+
}
|
|
549
|
+
const source = record(value, label);
|
|
550
|
+
const op = exactEnum(source.op, `${label}.op`, [
|
|
551
|
+
"true", "present", "eq", "not", "and", "or",
|
|
552
|
+
] as const);
|
|
553
|
+
if (op === "true") {
|
|
554
|
+
exactKeys(source, ["op"], [], label);
|
|
555
|
+
return Object.freeze({ op });
|
|
556
|
+
}
|
|
557
|
+
if (op === "present") {
|
|
558
|
+
exactKeys(source, ["op", "field"], [], label);
|
|
559
|
+
return Object.freeze({ op, field: string(source.field, `${label}.field`, 128) });
|
|
560
|
+
}
|
|
561
|
+
if (op === "eq") {
|
|
562
|
+
exactKeys(source, ["op", "field", "value"], [], label);
|
|
563
|
+
return Object.freeze({
|
|
564
|
+
op,
|
|
565
|
+
field: string(source.field, `${label}.field`, 128),
|
|
566
|
+
value: scalar(source.value, `${label}.value`),
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
if (op === "not") {
|
|
570
|
+
exactKeys(source, ["op", "predicate"], [], label);
|
|
571
|
+
return Object.freeze({
|
|
572
|
+
op,
|
|
573
|
+
predicate: parsePredicate(source.predicate, `${label}.predicate`, traversal, depth + 1),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
exactKeys(source, ["op", "predicates"], [], label);
|
|
577
|
+
const predicates = array(source.predicates, `${label}.predicates`, 32)
|
|
578
|
+
.map((item, index) => parsePredicate(
|
|
579
|
+
item,
|
|
580
|
+
`${label}.predicates[${index}]`,
|
|
581
|
+
traversal,
|
|
582
|
+
depth + 1,
|
|
583
|
+
));
|
|
584
|
+
if (predicates.length < 1) throw new Error(`${label}.predicates must not be empty`);
|
|
585
|
+
return Object.freeze({ op, predicates: Object.freeze(predicates) });
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function parseRule(
|
|
589
|
+
value: unknown,
|
|
590
|
+
label: string,
|
|
591
|
+
predicateTraversal: PredicateTraversal,
|
|
592
|
+
): LocalCliSurfaceInputRuleV1 {
|
|
593
|
+
const source = record(value, label);
|
|
594
|
+
exactKeys(source, [
|
|
595
|
+
"namespace", "when", "require", "requireAny", "exactlyOne", "forbid", "rationale",
|
|
596
|
+
], [], label);
|
|
597
|
+
const requiredFields = stringList(source.require, `${label}.require`, 64);
|
|
598
|
+
const requireAny = stringList(source.requireAny, `${label}.requireAny`, 64);
|
|
599
|
+
const exactlyOne = stringList(source.exactlyOne, `${label}.exactlyOne`, 64);
|
|
600
|
+
const forbid = stringList(source.forbid, `${label}.forbid`, 64);
|
|
601
|
+
if ([...requiredFields, ...requireAny, ...exactlyOne].some((field) => forbid.includes(field))) {
|
|
602
|
+
throw new Error(`${label} cannot require and forbid the same field`);
|
|
603
|
+
}
|
|
604
|
+
return Object.freeze({
|
|
605
|
+
namespace: exactEnum(source.namespace, `${label}.namespace`, [
|
|
606
|
+
"semantic-operation", "upstream-command",
|
|
607
|
+
] as const),
|
|
608
|
+
when: parsePredicate(source.when, `${label}.when`, predicateTraversal),
|
|
609
|
+
require: requiredFields,
|
|
610
|
+
requireAny,
|
|
611
|
+
exactlyOne,
|
|
612
|
+
forbid,
|
|
613
|
+
rationale: string(source.rationale, `${label}.rationale`, 1_000),
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function parseArgument(value: unknown, label: string): LocalCliSurfaceArgumentV1 {
|
|
618
|
+
const source = record(value, label);
|
|
619
|
+
exactKeys(source, [
|
|
620
|
+
"name", "position", "required", "multiple", "valueType", "enum", "default", "decision",
|
|
621
|
+
], [], label);
|
|
622
|
+
const valueType = exactEnum(source.valueType, `${label}.valueType`, ["string", "number", "boolean"] as const);
|
|
623
|
+
const enumValues = stringList(source.enum, `${label}.enum`, 128);
|
|
624
|
+
const defaultValue = parseDefault(source.default, `${label}.default`);
|
|
625
|
+
const decision = parseDecision(source.decision, `${label}.decision`);
|
|
626
|
+
if (enumValues.length > 0 && valueType !== "string") {
|
|
627
|
+
throw new Error(`${label}.enum requires a string argument`);
|
|
628
|
+
}
|
|
629
|
+
if (
|
|
630
|
+
defaultValue.kind === "literal"
|
|
631
|
+
&& enumValues.length > 0
|
|
632
|
+
&& !enumValues.includes(defaultValue.value as string)
|
|
633
|
+
) throw new Error(`${label}.default must belong to the declared enum`);
|
|
634
|
+
if (
|
|
635
|
+
defaultValue.kind === "literal"
|
|
636
|
+
&& defaultValue.value !== null
|
|
637
|
+
&& typeof defaultValue.value !== valueType
|
|
638
|
+
) throw new Error(`${label}.default must match the argument value type`);
|
|
639
|
+
if (
|
|
640
|
+
decision.disposition === "fixed"
|
|
641
|
+
&& typeof decision.fixedValue !== valueType
|
|
642
|
+
) throw new Error(`${label}.fixedValue must match the argument value type`);
|
|
643
|
+
if (
|
|
644
|
+
decision.disposition === "fixed"
|
|
645
|
+
&& enumValues.length > 0
|
|
646
|
+
&& !enumValues.includes(decision.fixedValue as string)
|
|
647
|
+
) throw new Error(`${label}.fixedValue must belong to the declared enum`);
|
|
648
|
+
return Object.freeze({
|
|
649
|
+
name: string(source.name, `${label}.name`, 128),
|
|
650
|
+
position: integer(source.position, `${label}.position`, 0, 255),
|
|
651
|
+
required: boolean(source.required, `${label}.required`),
|
|
652
|
+
multiple: boolean(source.multiple, `${label}.multiple`),
|
|
653
|
+
valueType,
|
|
654
|
+
enum: enumValues,
|
|
655
|
+
default: defaultValue,
|
|
656
|
+
decision,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function parseFlag(value: unknown, label: string): LocalCliSurfaceFlagV1 {
|
|
661
|
+
const source = record(value, label);
|
|
662
|
+
exactKeys(source, [
|
|
663
|
+
"name", "aliases", "source", "valueType", "allowNo", "required", "multiple", "enum", "default", "decision",
|
|
664
|
+
], [], label);
|
|
665
|
+
const name = string(source.name, `${label}.name`, 128);
|
|
666
|
+
if (!/^--[a-z0-9][a-z0-9-]*$/u.test(name)) throw new Error(`${label}.name must be a long flag`);
|
|
667
|
+
const aliases = stringList(source.aliases, `${label}.aliases`, 8);
|
|
668
|
+
if (aliases.some((alias) => !/^-[a-zA-Z]$/u.test(alias))) {
|
|
669
|
+
throw new Error(`${label}.aliases must be one-character flags`);
|
|
670
|
+
}
|
|
671
|
+
const valueType = exactEnum(source.valueType, `${label}.valueType`, ["string", "number", "boolean"] as const);
|
|
672
|
+
const allowNo = boolean(source.allowNo, `${label}.allowNo`);
|
|
673
|
+
const defaultValue = parseDefault(source.default, `${label}.default`);
|
|
674
|
+
const enumValues = stringList(source.enum, `${label}.enum`, 128);
|
|
675
|
+
const decision = parseDecision(source.decision, `${label}.decision`);
|
|
676
|
+
if (allowNo && valueType !== "boolean") {
|
|
677
|
+
throw new Error(`${label}.allowNo requires a boolean flag`);
|
|
678
|
+
}
|
|
679
|
+
if (enumValues.length > 0 && valueType !== "string") {
|
|
680
|
+
throw new Error(`${label}.enum requires a string flag`);
|
|
681
|
+
}
|
|
682
|
+
if (
|
|
683
|
+
defaultValue.kind === "literal"
|
|
684
|
+
&& enumValues.length > 0
|
|
685
|
+
&& !enumValues.includes(defaultValue.value as string)
|
|
686
|
+
) throw new Error(`${label}.default must belong to the declared enum`);
|
|
687
|
+
if (
|
|
688
|
+
defaultValue.kind === "literal"
|
|
689
|
+
&& defaultValue.value !== null
|
|
690
|
+
&& typeof defaultValue.value !== valueType
|
|
691
|
+
) throw new Error(`${label}.default must match the flag value type`);
|
|
692
|
+
if (
|
|
693
|
+
decision.disposition === "fixed"
|
|
694
|
+
&& typeof decision.fixedValue !== valueType
|
|
695
|
+
) throw new Error(`${label}.fixedValue must match the flag value type`);
|
|
696
|
+
if (
|
|
697
|
+
decision.disposition === "fixed"
|
|
698
|
+
&& enumValues.length > 0
|
|
699
|
+
&& !enumValues.includes(decision.fixedValue as string)
|
|
700
|
+
) throw new Error(`${label}.fixedValue must belong to the declared enum`);
|
|
701
|
+
return Object.freeze({
|
|
702
|
+
name,
|
|
703
|
+
aliases,
|
|
704
|
+
source: exactEnum(source.source, `${label}.source`, ["command", "global"] as const),
|
|
705
|
+
valueType,
|
|
706
|
+
allowNo,
|
|
707
|
+
required: boolean(source.required, `${label}.required`),
|
|
708
|
+
multiple: boolean(source.multiple, `${label}.multiple`),
|
|
709
|
+
enum: enumValues,
|
|
710
|
+
default: defaultValue,
|
|
711
|
+
decision,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function parseOutput(value: unknown, label: string): LocalCliSurfaceOutputV1 {
|
|
716
|
+
const source = record(value, label);
|
|
717
|
+
exactKeys(source, [
|
|
718
|
+
"shape", "completeness", "maxBytes", "privateArtifact", "truncation",
|
|
719
|
+
], [], label);
|
|
720
|
+
const maxBytes = source.maxBytes === null
|
|
721
|
+
? null
|
|
722
|
+
: integer(source.maxBytes, `${label}.maxBytes`, 1, 4 * 1024 * 1024 * 1024);
|
|
723
|
+
return Object.freeze({
|
|
724
|
+
shape: string(source.shape, `${label}.shape`, 1_000),
|
|
725
|
+
completeness: exactEnum(source.completeness, `${label}.completeness`, [
|
|
726
|
+
"complete", "bounded", "candidate-window", "input-dependent", "internal", "unavailable",
|
|
727
|
+
] as const),
|
|
728
|
+
maxBytes,
|
|
729
|
+
privateArtifact: boolean(source.privateArtifact, `${label}.privateArtifact`),
|
|
730
|
+
truncation: nullableString(source.truncation, `${label}.truncation`),
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function parseReconciliation(
|
|
735
|
+
value: unknown,
|
|
736
|
+
label: string,
|
|
737
|
+
predicateTraversal: PredicateTraversal,
|
|
738
|
+
): LocalCliSurfaceReconciliationV1 {
|
|
739
|
+
const source = record(value, label);
|
|
740
|
+
exactKeys(source, ["availability", "namespace", "predicate", "rationale"], [], label);
|
|
741
|
+
const availability = exactEnum(source.availability, `${label}.availability`, [
|
|
742
|
+
"none", "always", "input-dependent",
|
|
743
|
+
] as const);
|
|
744
|
+
const predicate = source.predicate === null
|
|
745
|
+
? null
|
|
746
|
+
: parsePredicate(source.predicate, `${label}.predicate`, predicateTraversal);
|
|
747
|
+
if ((availability === "input-dependent") !== (predicate !== null)) {
|
|
748
|
+
throw new Error(`${label}.predicate must exactly match input-dependent availability`);
|
|
749
|
+
}
|
|
750
|
+
const namespace = source.namespace === null
|
|
751
|
+
? null
|
|
752
|
+
: exactEnum(source.namespace, `${label}.namespace`, ["semantic-operation"] as const);
|
|
753
|
+
if ((availability === "input-dependent") !== (namespace !== null)) {
|
|
754
|
+
throw new Error(`${label}.namespace must exactly match input-dependent availability`);
|
|
755
|
+
}
|
|
756
|
+
return Object.freeze({
|
|
757
|
+
availability,
|
|
758
|
+
namespace,
|
|
759
|
+
predicate,
|
|
760
|
+
rationale: string(source.rationale, `${label}.rationale`, 1_000),
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function parseCommand(
|
|
765
|
+
value: unknown,
|
|
766
|
+
label: string,
|
|
767
|
+
predicateTraversal: PredicateTraversal,
|
|
768
|
+
): LocalCliSurfaceCommandDefinitionV1 {
|
|
769
|
+
const source = record(value, label);
|
|
770
|
+
exactKeys(source, [
|
|
771
|
+
"path", "provenance", "profileAuthority", "package", "version", "versionKind",
|
|
772
|
+
"registered", "publicManual", "generatedCanonical",
|
|
773
|
+
"upstreamReportedMutates", "reviewedEffect", "arguments", "flags",
|
|
774
|
+
"decision", "pathSemanticInputs", "output", "conditionalInputs", "reconciliation",
|
|
775
|
+
], ["semanticProfileSha256"], label);
|
|
776
|
+
const path = commandPath(source.path, `${label}.path`);
|
|
777
|
+
const args = array(source.arguments, `${label}.arguments`, 32)
|
|
778
|
+
.map((item, index) => parseArgument(item, `${label}.arguments[${index}]`));
|
|
779
|
+
const normalizedArgumentNames = args.map((argument) => argument.name.replaceAll("-", "_"));
|
|
780
|
+
if (new Set(normalizedArgumentNames).size !== normalizedArgumentNames.length) {
|
|
781
|
+
throw new Error(`${label}.arguments repeat a normalized name`);
|
|
782
|
+
}
|
|
783
|
+
const positions = args.map((argument) => argument.position);
|
|
784
|
+
if (positions.some((position, index) => position !== index)) {
|
|
785
|
+
throw new Error(`${label}.arguments positions must be contiguous from zero`);
|
|
786
|
+
}
|
|
787
|
+
const flags = array(source.flags, `${label}.flags`, 128)
|
|
788
|
+
.map((item, index) => parseFlag(item, `${label}.flags[${index}]`));
|
|
789
|
+
if (new Set(flags.map((flag) => flag.name)).size !== flags.length) {
|
|
790
|
+
throw new Error(`${label}.flags repeat a long name`);
|
|
791
|
+
}
|
|
792
|
+
if (flags.some((flag) => flag.source !== "command")) {
|
|
793
|
+
throw new Error(`${label}.flags must be command flags`);
|
|
794
|
+
}
|
|
795
|
+
const normalizedFlagNames = flags.map((flag) => flag.name.slice(2).replaceAll("-", "_"));
|
|
796
|
+
if (normalizedFlagNames.some((name) => normalizedArgumentNames.includes(name))) {
|
|
797
|
+
throw new Error(`${label} repeats a normalized argument/flag field`);
|
|
798
|
+
}
|
|
799
|
+
const conditionalInputs = array(source.conditionalInputs, `${label}.conditionalInputs`, 64)
|
|
800
|
+
.map((item, index) => parseRule(item, `${label}.conditionalInputs[${index}]`, predicateTraversal));
|
|
801
|
+
const upstreamReportedMutates = source.upstreamReportedMutates === null
|
|
802
|
+
? null
|
|
803
|
+
: boolean(source.upstreamReportedMutates, `${label}.upstreamReportedMutates`);
|
|
804
|
+
return Object.freeze({
|
|
805
|
+
path,
|
|
806
|
+
provenance: exactEnum(source.provenance, `${label}.provenance`, LOCAL_CLI_SURFACE_PROVENANCE_KINDS),
|
|
807
|
+
profileAuthority: exactEnum(source.profileAuthority, `${label}.profileAuthority`, [
|
|
808
|
+
"tagged-source", "jit-plugin-source",
|
|
809
|
+
] as const),
|
|
810
|
+
package: nullableString(source.package, `${label}.package`),
|
|
811
|
+
version: nullableString(source.version, `${label}.version`),
|
|
812
|
+
versionKind: parseVersionKind(source.versionKind, source.version, `${label}.versionKind`),
|
|
813
|
+
registered: boolean(source.registered, `${label}.registered`),
|
|
814
|
+
publicManual: boolean(source.publicManual, `${label}.publicManual`),
|
|
815
|
+
generatedCanonical: boolean(source.generatedCanonical, `${label}.generatedCanonical`),
|
|
816
|
+
upstreamReportedMutates,
|
|
817
|
+
reviewedEffect: exactEnum(source.reviewedEffect, `${label}.reviewedEffect`, [
|
|
818
|
+
"read", "write", "input-dependent",
|
|
819
|
+
] as const),
|
|
820
|
+
arguments: Object.freeze(args),
|
|
821
|
+
flags: Object.freeze(flags),
|
|
822
|
+
decision: parseDecision(source.decision, `${label}.decision`),
|
|
823
|
+
pathSemanticInputs: parsePathSemanticInputs(
|
|
824
|
+
source.pathSemanticInputs,
|
|
825
|
+
`${label}.pathSemanticInputs`,
|
|
826
|
+
),
|
|
827
|
+
output: parseOutput(source.output, `${label}.output`),
|
|
828
|
+
conditionalInputs: Object.freeze(conditionalInputs),
|
|
829
|
+
reconciliation: parseReconciliation(source.reconciliation, `${label}.reconciliation`, predicateTraversal),
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function parseAdditionalEntry(
|
|
834
|
+
value: unknown,
|
|
835
|
+
label: string,
|
|
836
|
+
): LocalCliSurfaceAdditionalEntryV1 {
|
|
837
|
+
const source = record(value, label);
|
|
838
|
+
exactKeys(source, [
|
|
839
|
+
"path", "provenance", "profileAuthority", "canonicalTarget", "package", "version", "versionKind", "registered",
|
|
840
|
+
"publicManual", "rationale", "decision",
|
|
841
|
+
], [], label);
|
|
842
|
+
const path = commandPath(source.path, `${label}.path`);
|
|
843
|
+
const canonicalTarget = source.canonicalTarget === null
|
|
844
|
+
? null
|
|
845
|
+
: commandPath(source.canonicalTarget, `${label}.canonicalTarget`);
|
|
846
|
+
return Object.freeze({
|
|
847
|
+
path,
|
|
848
|
+
provenance: exactEnum(source.provenance, `${label}.provenance`, LOCAL_CLI_SURFACE_PROVENANCE_KINDS),
|
|
849
|
+
profileAuthority: exactEnum(source.profileAuthority, `${label}.profileAuthority`, [
|
|
850
|
+
"tagged-source", "framework-runtime", "documentation",
|
|
851
|
+
] as const),
|
|
852
|
+
canonicalTarget,
|
|
853
|
+
package: nullableString(source.package, `${label}.package`),
|
|
854
|
+
version: nullableString(source.version, `${label}.version`),
|
|
855
|
+
versionKind: parseVersionKind(source.versionKind, source.version, `${label}.versionKind`),
|
|
856
|
+
registered: boolean(source.registered, `${label}.registered`),
|
|
857
|
+
publicManual: boolean(source.publicManual, `${label}.publicManual`),
|
|
858
|
+
rationale: string(source.rationale, `${label}.rationale`, 1_000),
|
|
859
|
+
decision: parseDecision(source.decision, `${label}.decision`),
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function flagSpellings(flag: LocalCliSurfaceFlagV1): readonly string[] {
|
|
864
|
+
return Object.freeze([
|
|
865
|
+
flag.name,
|
|
866
|
+
...(flag.allowNo ? [`--no-${flag.name.slice(2)}`] : []),
|
|
867
|
+
...flag.aliases,
|
|
868
|
+
]);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function predicateFieldNames(predicate: LocalCliSurfacePredicateV1): readonly string[] {
|
|
872
|
+
if (predicate.op === "true") return Object.freeze([]);
|
|
873
|
+
if (predicate.op === "present" || predicate.op === "eq") {
|
|
874
|
+
return Object.freeze([predicate.field]);
|
|
875
|
+
}
|
|
876
|
+
if (predicate.op === "not") return predicateFieldNames(predicate.predicate);
|
|
877
|
+
return Object.freeze(predicate.predicates.flatMap(predicateFieldNames));
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
type SurfaceFieldType = "string" | "number" | "boolean" | "array" | "file";
|
|
881
|
+
|
|
882
|
+
function validatePredicateTypes(
|
|
883
|
+
predicate: LocalCliSurfacePredicateV1,
|
|
884
|
+
fieldTypes: Readonly<Record<string, SurfaceFieldType>>,
|
|
885
|
+
label: string,
|
|
886
|
+
): void {
|
|
887
|
+
if (predicate.op === "true" || predicate.op === "present") return;
|
|
888
|
+
if (predicate.op === "eq") {
|
|
889
|
+
const expected = fieldTypes[predicate.field];
|
|
890
|
+
if (
|
|
891
|
+
expected === undefined
|
|
892
|
+
|| (
|
|
893
|
+
predicate.value !== null
|
|
894
|
+
&& expected !== "file"
|
|
895
|
+
&& expected !== "array"
|
|
896
|
+
&& typeof predicate.value !== expected
|
|
897
|
+
)
|
|
898
|
+
|| ((expected === "file" || expected === "array") && predicate.value !== null)
|
|
899
|
+
) throw new Error(`${label} equality value does not match field ${predicate.field}`);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (predicate.op === "not") {
|
|
903
|
+
validatePredicateTypes(predicate.predicate, fieldTypes, `${label}.predicate`);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
predicate.predicates.forEach((item, index) =>
|
|
907
|
+
validatePredicateTypes(item, fieldTypes, `${label}.predicates[${index}]`));
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function ruleFieldNames(rule: LocalCliSurfaceInputRuleV1): readonly string[] {
|
|
911
|
+
return Object.freeze([
|
|
912
|
+
...predicateFieldNames(rule.when),
|
|
913
|
+
...rule.require,
|
|
914
|
+
...rule.requireAny,
|
|
915
|
+
...rule.exactlyOne,
|
|
916
|
+
...rule.forbid,
|
|
917
|
+
]);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function parseArtifact(value: unknown, label: string): LocalCliSurfaceArtifactV1 {
|
|
921
|
+
const source = record(value, label);
|
|
922
|
+
exactKeys(source, ["platform", "arch", "archiveSha256", "executableSha256"], [], label);
|
|
923
|
+
return Object.freeze({
|
|
924
|
+
platform: string(source.platform, `${label}.platform`, 32),
|
|
925
|
+
arch: string(source.arch, `${label}.arch`, 32),
|
|
926
|
+
archiveSha256: sha(source.archiveSha256, `${label}.archiveSha256`),
|
|
927
|
+
executableSha256: sha(source.executableSha256, `${label}.executableSha256`),
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function parseDefinition(value: unknown): LocalCliSurfaceContractDefinitionV1 {
|
|
932
|
+
const source = record(value, "local CLI surface contract");
|
|
933
|
+
exactKeys(source, [
|
|
934
|
+
"schemaVersion", "format", "surface", "executable", "source", "sdk", "runtime",
|
|
935
|
+
"globalFlags", "commands", "additionalEntries",
|
|
936
|
+
], ["digests"], "local CLI surface contract");
|
|
937
|
+
if (source.schemaVersion !== 1 || source.format !== "wrench.local-cli-surface") {
|
|
938
|
+
throw new Error("local CLI surface contract version is unsupported");
|
|
939
|
+
}
|
|
940
|
+
const executable = record(source.executable, "local CLI surface executable");
|
|
941
|
+
exactKeys(executable, [
|
|
942
|
+
"id", "implementation", "releaseVersion", "releaseDate", "releaseTag", "releaseCommit",
|
|
943
|
+
"releaseManifestSha256", "runtimeReportedName", "runtimeReportedVersion", "artifacts",
|
|
944
|
+
], [], "local CLI surface executable");
|
|
945
|
+
const sourceIdentity = record(source.source, "local CLI surface source");
|
|
946
|
+
exactKeys(sourceIdentity, [
|
|
947
|
+
"package", "packagePath", "packageDeclaredVersion", "versionDiscrepancy",
|
|
948
|
+
"generatedManualSha256", "generatedManualIncludesFlagsAndDefaults",
|
|
949
|
+
"generatedManualEntries", "generatedCanonicalEntries", "registeredKeys",
|
|
950
|
+
], [], "local CLI surface source");
|
|
951
|
+
const sdk = record(source.sdk, "local CLI surface SDK");
|
|
952
|
+
exactKeys(sdk, ["package", "version", "commit"], [], "local CLI surface SDK");
|
|
953
|
+
const runtime = record(source.runtime, "local CLI surface runtime");
|
|
954
|
+
exactKeys(runtime, [
|
|
955
|
+
"providerPluginId", "providerPluginVersion", "adapterId", "adapterVersion",
|
|
956
|
+
"operationContractVersions", "operationInputTypes", "target", "realm", "compatibility",
|
|
957
|
+
], [], "local CLI surface runtime");
|
|
958
|
+
const rawOperationContractVersions = record(
|
|
959
|
+
runtime.operationContractVersions,
|
|
960
|
+
"local CLI surface runtime.operationContractVersions",
|
|
961
|
+
);
|
|
962
|
+
const operationContractVersionKeys = Object.keys(rawOperationContractVersions)
|
|
963
|
+
.sort(codePointCompare);
|
|
964
|
+
if (
|
|
965
|
+
operationContractVersionKeys.length < 1
|
|
966
|
+
|| operationContractVersionKeys.length > 1_000
|
|
967
|
+
) {
|
|
968
|
+
throw new Error("local CLI surface runtime.operationContractVersions must contain 1 to 1000 operations");
|
|
969
|
+
}
|
|
970
|
+
const operationContractVersions = Object.freeze(Object.fromEntries(
|
|
971
|
+
operationContractVersionKeys.map((operation) => {
|
|
972
|
+
if (!isProviderPluginOperationName(operation)) {
|
|
973
|
+
throw new Error(`local CLI surface runtime operation ${operation} is not a bounded semantic name`);
|
|
974
|
+
}
|
|
975
|
+
return [
|
|
976
|
+
operation,
|
|
977
|
+
integer(
|
|
978
|
+
rawOperationContractVersions[operation],
|
|
979
|
+
`local CLI surface runtime.operationContractVersions.${operation}`,
|
|
980
|
+
1,
|
|
981
|
+
1_000_000,
|
|
982
|
+
),
|
|
983
|
+
] as const;
|
|
984
|
+
}),
|
|
985
|
+
));
|
|
986
|
+
const rawOperationInputTypes = record(
|
|
987
|
+
runtime.operationInputTypes,
|
|
988
|
+
"local CLI surface runtime.operationInputTypes",
|
|
989
|
+
);
|
|
990
|
+
const operationInputTypeKeys = Object.keys(rawOperationInputTypes).sort(codePointCompare);
|
|
991
|
+
if (
|
|
992
|
+
operationInputTypeKeys.length !== operationContractVersionKeys.length
|
|
993
|
+
|| operationInputTypeKeys.some((operation, index) =>
|
|
994
|
+
operation !== operationContractVersionKeys[index])
|
|
995
|
+
) throw new Error("local CLI surface runtime input fields must exactly cover operation versions");
|
|
996
|
+
const operationInputTypes = Object.freeze(Object.fromEntries(
|
|
997
|
+
operationInputTypeKeys.map((operation) => {
|
|
998
|
+
const rawFields = record(
|
|
999
|
+
rawOperationInputTypes[operation],
|
|
1000
|
+
`local CLI surface runtime.operationInputTypes.${operation}`,
|
|
1001
|
+
);
|
|
1002
|
+
const fields = Object.keys(rawFields).sort(codePointCompare);
|
|
1003
|
+
if (
|
|
1004
|
+
fields.length > 256
|
|
1005
|
+
|| fields.some((field) => !/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/u.test(field))
|
|
1006
|
+
) throw new Error("local CLI surface runtime contains invalid semantic input fields");
|
|
1007
|
+
return [operation, Object.freeze(Object.fromEntries(fields.map((field) => [
|
|
1008
|
+
field,
|
|
1009
|
+
exactEnum(
|
|
1010
|
+
rawFields[field],
|
|
1011
|
+
`local CLI surface runtime.operationInputTypes.${operation}.${field}`,
|
|
1012
|
+
["string", "number", "boolean", "array", "file"] as const,
|
|
1013
|
+
),
|
|
1014
|
+
])))] as const;
|
|
1015
|
+
}),
|
|
1016
|
+
));
|
|
1017
|
+
const predicateTraversal: PredicateTraversal = {
|
|
1018
|
+
seen: new WeakSet<object>(),
|
|
1019
|
+
nodes: 0,
|
|
1020
|
+
};
|
|
1021
|
+
const commands = array(source.commands, "local CLI surface commands", 1_000)
|
|
1022
|
+
.map((item, index) => parseCommand(
|
|
1023
|
+
item,
|
|
1024
|
+
`local CLI surface commands[${index}]`,
|
|
1025
|
+
predicateTraversal,
|
|
1026
|
+
));
|
|
1027
|
+
const commandPaths = commands.map((command) => command.path.join(" "));
|
|
1028
|
+
if (new Set(commandPaths).size !== commandPaths.length) {
|
|
1029
|
+
throw new Error("local CLI surface commands repeat a normalized path");
|
|
1030
|
+
}
|
|
1031
|
+
const globalFlags = array(source.globalFlags, "local CLI surface globalFlags", 128)
|
|
1032
|
+
.map((item, index) => parseFlag(item, `local CLI surface globalFlags[${index}]`));
|
|
1033
|
+
if (
|
|
1034
|
+
globalFlags.some((flag) => flag.source !== "global")
|
|
1035
|
+
|| new Set(globalFlags.map((flag) => flag.name)).size !== globalFlags.length
|
|
1036
|
+
) throw new Error("local CLI surface globalFlags must be unique global flags");
|
|
1037
|
+
if (globalFlags.some((flag) =>
|
|
1038
|
+
flag.default.kind === "literal" && flag.default.authority === "jit-plugin-source")) {
|
|
1039
|
+
throw new Error("local CLI surface global flags cannot use JIT plugin default authority");
|
|
1040
|
+
}
|
|
1041
|
+
const globalSpellings = new Set<string>();
|
|
1042
|
+
for (const flag of globalFlags) {
|
|
1043
|
+
for (const spelling of flagSpellings(flag)) {
|
|
1044
|
+
if (globalSpellings.has(spelling)) {
|
|
1045
|
+
throw new Error("local CLI surface globalFlags repeat a flag spelling");
|
|
1046
|
+
}
|
|
1047
|
+
globalSpellings.add(spelling);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
for (const command of commands) {
|
|
1051
|
+
const commandSpellings = new Set<string>();
|
|
1052
|
+
for (const flag of command.flags) {
|
|
1053
|
+
for (const spelling of flagSpellings(flag)) {
|
|
1054
|
+
if (commandSpellings.has(spelling)) {
|
|
1055
|
+
throw new Error(`local CLI surface command ${command.path.join(" ")} repeats a flag spelling`);
|
|
1056
|
+
}
|
|
1057
|
+
if (spelling.startsWith("-") && !spelling.startsWith("--") && globalSpellings.has(spelling)) {
|
|
1058
|
+
throw new Error(`local CLI surface command ${command.path.join(" ")} shadows a global short alias`);
|
|
1059
|
+
}
|
|
1060
|
+
commandSpellings.add(spelling);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
const additionalEntries = array(source.additionalEntries, "local CLI surface additionalEntries", 1_000)
|
|
1065
|
+
.map((item, index) => parseAdditionalEntry(item, `local CLI surface additionalEntries[${index}]`));
|
|
1066
|
+
const allPaths = [...commandPaths, ...additionalEntries.map((entry) => entry.path.join(" "))];
|
|
1067
|
+
if (new Set(allPaths).size !== allPaths.length) {
|
|
1068
|
+
throw new Error("local CLI surface entries repeat a normalized path");
|
|
1069
|
+
}
|
|
1070
|
+
const allPathSet = new Set(allPaths);
|
|
1071
|
+
for (const entry of additionalEntries) {
|
|
1072
|
+
if (
|
|
1073
|
+
entry.canonicalTarget !== null
|
|
1074
|
+
&& !allPathSet.has(entry.canonicalTarget.join(" "))
|
|
1075
|
+
) throw new Error("local CLI surface entry has a dangling canonical target");
|
|
1076
|
+
}
|
|
1077
|
+
const additionalByPath = new Map(additionalEntries.map((entry) => [
|
|
1078
|
+
entry.path.join(" "),
|
|
1079
|
+
entry,
|
|
1080
|
+
]));
|
|
1081
|
+
const aliasVisitState = new Map<string, "visiting" | "visited">();
|
|
1082
|
+
for (const entry of additionalEntries) {
|
|
1083
|
+
const chain: string[] = [];
|
|
1084
|
+
let path = entry.path.join(" ");
|
|
1085
|
+
while (true) {
|
|
1086
|
+
const state = aliasVisitState.get(path);
|
|
1087
|
+
if (state === "visiting") {
|
|
1088
|
+
throw new Error("local CLI surface canonical target graph contains an alias cycle");
|
|
1089
|
+
}
|
|
1090
|
+
if (state === "visited") break;
|
|
1091
|
+
aliasVisitState.set(path, "visiting");
|
|
1092
|
+
chain.push(path);
|
|
1093
|
+
const target = additionalByPath.get(path)?.canonicalTarget?.join(" ") ?? null;
|
|
1094
|
+
if (target === null || !additionalByPath.has(target)) break;
|
|
1095
|
+
path = target;
|
|
1096
|
+
}
|
|
1097
|
+
for (const visited of chain) aliasVisitState.set(visited, "visited");
|
|
1098
|
+
}
|
|
1099
|
+
for (const command of commands) {
|
|
1100
|
+
if (
|
|
1101
|
+
command.profileAuthority === "tagged-source"
|
|
1102
|
+
&& (
|
|
1103
|
+
!["built-in-canonical", "source-only-private"].includes(command.provenance)
|
|
1104
|
+
|| command.package !== sourceIdentity.package
|
|
1105
|
+
|| command.version !== sourceIdentity.packageDeclaredVersion
|
|
1106
|
+
|| command.versionKind !== "exact"
|
|
1107
|
+
)
|
|
1108
|
+
) throw new Error("local CLI surface tagged command profile has inconsistent source authority");
|
|
1109
|
+
if (
|
|
1110
|
+
command.profileAuthority === "jit-plugin-source"
|
|
1111
|
+
&& (
|
|
1112
|
+
command.provenance !== "jit-plugin"
|
|
1113
|
+
|| command.package === null
|
|
1114
|
+
|| command.version === null
|
|
1115
|
+
|| command.versionKind !== "range"
|
|
1116
|
+
)
|
|
1117
|
+
) throw new Error("local CLI surface JIT command profile has inconsistent package authority");
|
|
1118
|
+
for (const item of [...command.arguments, ...command.flags]) {
|
|
1119
|
+
if (item.default.kind !== "literal") continue;
|
|
1120
|
+
if (
|
|
1121
|
+
command.profileAuthority === "jit-plugin-source"
|
|
1122
|
+
&& item.default.authority !== "jit-plugin-source"
|
|
1123
|
+
) throw new Error("local CLI surface JIT command literal default has inconsistent authority");
|
|
1124
|
+
if (
|
|
1125
|
+
command.profileAuthority === "tagged-source"
|
|
1126
|
+
&& item.default.authority === "jit-plugin-source"
|
|
1127
|
+
) throw new Error("local CLI surface tagged command literal default has inconsistent authority");
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
for (const entry of additionalEntries) {
|
|
1131
|
+
if (
|
|
1132
|
+
entry.profileAuthority === "tagged-source"
|
|
1133
|
+
&& (
|
|
1134
|
+
entry.package !== sourceIdentity.package
|
|
1135
|
+
|| entry.version !== sourceIdentity.packageDeclaredVersion
|
|
1136
|
+
|| entry.versionKind !== "exact"
|
|
1137
|
+
)
|
|
1138
|
+
) throw new Error("local CLI surface tagged additional entry has inconsistent source authority");
|
|
1139
|
+
if (
|
|
1140
|
+
entry.profileAuthority === "documentation"
|
|
1141
|
+
&& (entry.provenance !== "documented-only" || entry.package !== null || entry.version !== null)
|
|
1142
|
+
) throw new Error("local CLI surface documented entry has inconsistent authority");
|
|
1143
|
+
}
|
|
1144
|
+
const artifacts = array(executable.artifacts, "local CLI surface executable.artifacts", 32)
|
|
1145
|
+
.map((item, index) => parseArtifact(item, `local CLI surface executable.artifacts[${index}]`));
|
|
1146
|
+
const artifactCoordinates = artifacts.map((artifact) => `${artifact.platform}/${artifact.arch}`);
|
|
1147
|
+
if (artifacts.length < 1 || new Set(artifactCoordinates).size !== artifacts.length) {
|
|
1148
|
+
throw new Error("local CLI surface executable artifacts must be unique and nonempty");
|
|
1149
|
+
}
|
|
1150
|
+
const surface = string(source.surface, "local CLI surface contract.surface", 63);
|
|
1151
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/u.test(surface)) {
|
|
1152
|
+
throw new Error("local CLI surface contract.surface must be a normalized surface ID");
|
|
1153
|
+
}
|
|
1154
|
+
const generatedManualEntries = integer(
|
|
1155
|
+
sourceIdentity.generatedManualEntries,
|
|
1156
|
+
"local CLI surface source.generatedManualEntries",
|
|
1157
|
+
1,
|
|
1158
|
+
10_000,
|
|
1159
|
+
);
|
|
1160
|
+
const generatedCanonicalEntries = integer(
|
|
1161
|
+
sourceIdentity.generatedCanonicalEntries,
|
|
1162
|
+
"local CLI surface source.generatedCanonicalEntries",
|
|
1163
|
+
1,
|
|
1164
|
+
10_000,
|
|
1165
|
+
);
|
|
1166
|
+
const registeredKeys = integer(
|
|
1167
|
+
sourceIdentity.registeredKeys,
|
|
1168
|
+
"local CLI surface source.registeredKeys",
|
|
1169
|
+
1,
|
|
1170
|
+
100_000,
|
|
1171
|
+
);
|
|
1172
|
+
if (
|
|
1173
|
+
commands.filter((command) => command.publicManual).length !== generatedManualEntries
|
|
1174
|
+
|| commands.filter((command) => command.generatedCanonical).length
|
|
1175
|
+
+ additionalEntries.filter((entry) =>
|
|
1176
|
+
entry.provenance === "built-in-hidden"
|
|
1177
|
+
&& entry.profileAuthority === "tagged-source").length
|
|
1178
|
+
!== generatedCanonicalEntries
|
|
1179
|
+
|| [
|
|
1180
|
+
...commands,
|
|
1181
|
+
...additionalEntries,
|
|
1182
|
+
].filter((entry) => entry.registered && entry.package === sourceIdentity.package).length
|
|
1183
|
+
!== registeredKeys
|
|
1184
|
+
) throw new Error("local CLI surface source counts do not match normalized entries");
|
|
1185
|
+
const installedOperations = new Set(operationContractVersionKeys);
|
|
1186
|
+
for (const decision of [
|
|
1187
|
+
...globalFlags.map((flag) => flag.decision),
|
|
1188
|
+
...commands.flatMap((command) => [
|
|
1189
|
+
command.decision,
|
|
1190
|
+
...command.arguments.map((argument) => argument.decision),
|
|
1191
|
+
...command.flags.map((flag) => flag.decision),
|
|
1192
|
+
]),
|
|
1193
|
+
...additionalEntries.map((entry) => entry.decision),
|
|
1194
|
+
]) {
|
|
1195
|
+
if (decision.operation !== null && !installedOperations.has(decision.operation)) {
|
|
1196
|
+
throw new Error(`local CLI surface decision references uninstalled operation ${decision.operation}`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
for (const command of commands) {
|
|
1200
|
+
if (
|
|
1201
|
+
(command.decision.disposition === "supported")
|
|
1202
|
+
!== (command.decision.operation !== null)
|
|
1203
|
+
) {
|
|
1204
|
+
throw new Error(
|
|
1205
|
+
`local CLI surface command ${command.path.join(" ")} must bind an operation exactly when supported`,
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
for (const item of [...command.arguments, ...command.flags]) {
|
|
1209
|
+
if (
|
|
1210
|
+
item.decision.operation !== null
|
|
1211
|
+
&& item.decision.operation !== command.decision.operation
|
|
1212
|
+
) {
|
|
1213
|
+
throw new Error(
|
|
1214
|
+
`local CLI surface command ${command.path.join(" ")} item operation differs from its command`,
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
const upstreamFieldTypes = Object.freeze(Object.fromEntries([
|
|
1219
|
+
...command.arguments.map((argument) => [
|
|
1220
|
+
argument.name.replaceAll("-", "_"),
|
|
1221
|
+
argument.valueType,
|
|
1222
|
+
] as const),
|
|
1223
|
+
...command.flags.map((flag) => [
|
|
1224
|
+
flag.name.slice(2).replaceAll("-", "_"),
|
|
1225
|
+
flag.valueType,
|
|
1226
|
+
] as const),
|
|
1227
|
+
]));
|
|
1228
|
+
const semanticFieldTypes = command.decision.operation === null
|
|
1229
|
+
? null
|
|
1230
|
+
: operationInputTypes[command.decision.operation] ?? null;
|
|
1231
|
+
for (const [field, value] of Object.entries(command.pathSemanticInputs)) {
|
|
1232
|
+
if (semanticFieldTypes === null || !Object.hasOwn(semanticFieldTypes, field)) {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
`local CLI surface command ${command.path.join(" ")} path semantic input references unknown field ${field}`,
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const expected = semanticFieldTypes[field];
|
|
1238
|
+
if (
|
|
1239
|
+
value !== null
|
|
1240
|
+
&& (
|
|
1241
|
+
expected === "file"
|
|
1242
|
+
|| expected === "array"
|
|
1243
|
+
|| typeof value !== expected
|
|
1244
|
+
)
|
|
1245
|
+
) {
|
|
1246
|
+
throw new Error(
|
|
1247
|
+
`local CLI surface command ${command.path.join(" ")} path semantic input ${field} has the wrong type`,
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
for (const [index, rule] of command.conditionalInputs.entries()) {
|
|
1252
|
+
const allowed = rule.namespace === "semantic-operation"
|
|
1253
|
+
? semanticFieldTypes
|
|
1254
|
+
: upstreamFieldTypes;
|
|
1255
|
+
if (allowed === null) {
|
|
1256
|
+
throw new Error(`local CLI surface command ${command.path.join(" ")} has no semantic input namespace`);
|
|
1257
|
+
}
|
|
1258
|
+
for (const field of ruleFieldNames(rule)) {
|
|
1259
|
+
if (!Object.hasOwn(allowed, field)) {
|
|
1260
|
+
throw new Error(`local CLI surface command ${command.path.join(" ")} rule references unknown ${rule.namespace} field ${field}`);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
validatePredicateTypes(
|
|
1264
|
+
rule.when,
|
|
1265
|
+
allowed,
|
|
1266
|
+
`local CLI surface command ${command.path.join(" ")} conditionalInputs[${index}].when`,
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
if (command.reconciliation.predicate !== null) {
|
|
1270
|
+
if (semanticFieldTypes === null) {
|
|
1271
|
+
throw new Error(`local CLI surface command ${command.path.join(" ")} has no reconciliation input namespace`);
|
|
1272
|
+
}
|
|
1273
|
+
for (const field of predicateFieldNames(command.reconciliation.predicate)) {
|
|
1274
|
+
if (!Object.hasOwn(semanticFieldTypes, field)) {
|
|
1275
|
+
throw new Error(`local CLI surface command ${command.path.join(" ")} reconciliation references unknown semantic field ${field}`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
validatePredicateTypes(
|
|
1279
|
+
command.reconciliation.predicate,
|
|
1280
|
+
semanticFieldTypes,
|
|
1281
|
+
`local CLI surface command ${command.path.join(" ")} reconciliation.predicate`,
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
const releaseVersion = string(
|
|
1286
|
+
executable.releaseVersion,
|
|
1287
|
+
"local CLI surface executable.releaseVersion",
|
|
1288
|
+
128,
|
|
1289
|
+
);
|
|
1290
|
+
const runtimeReportedVersion = string(
|
|
1291
|
+
executable.runtimeReportedVersion,
|
|
1292
|
+
"local CLI surface executable.runtimeReportedVersion",
|
|
1293
|
+
128,
|
|
1294
|
+
);
|
|
1295
|
+
if (releaseVersion !== runtimeReportedVersion) {
|
|
1296
|
+
throw new Error("local CLI surface pinned release and runtime-reported versions must match");
|
|
1297
|
+
}
|
|
1298
|
+
const packageDeclaredVersion = string(
|
|
1299
|
+
sourceIdentity.packageDeclaredVersion,
|
|
1300
|
+
"local CLI surface source.packageDeclaredVersion",
|
|
1301
|
+
128,
|
|
1302
|
+
);
|
|
1303
|
+
const versionDiscrepancy = nullableString(
|
|
1304
|
+
sourceIdentity.versionDiscrepancy,
|
|
1305
|
+
"local CLI surface source.versionDiscrepancy",
|
|
1306
|
+
);
|
|
1307
|
+
if ((packageDeclaredVersion !== releaseVersion) !== (versionDiscrepancy !== null)) {
|
|
1308
|
+
throw new Error("local CLI surface source version discrepancy must exactly match version divergence");
|
|
1309
|
+
}
|
|
1310
|
+
return Object.freeze({
|
|
1311
|
+
schemaVersion: 1,
|
|
1312
|
+
format: "wrench.local-cli-surface",
|
|
1313
|
+
surface,
|
|
1314
|
+
executable: Object.freeze({
|
|
1315
|
+
id: string(executable.id, "local CLI surface executable.id", 128),
|
|
1316
|
+
implementation: string(executable.implementation, "local CLI surface executable.implementation", 512),
|
|
1317
|
+
releaseVersion,
|
|
1318
|
+
releaseDate: canonicalDate(executable.releaseDate, "local CLI surface executable.releaseDate"),
|
|
1319
|
+
releaseTag: string(executable.releaseTag, "local CLI surface executable.releaseTag", 128),
|
|
1320
|
+
releaseCommit: commitSha(executable.releaseCommit, "local CLI surface executable.releaseCommit"),
|
|
1321
|
+
releaseManifestSha256: sha(executable.releaseManifestSha256, "local CLI surface executable.releaseManifestSha256"),
|
|
1322
|
+
runtimeReportedName: string(executable.runtimeReportedName, "local CLI surface executable.runtimeReportedName", 128),
|
|
1323
|
+
runtimeReportedVersion,
|
|
1324
|
+
artifacts: Object.freeze(artifacts),
|
|
1325
|
+
}),
|
|
1326
|
+
source: Object.freeze({
|
|
1327
|
+
package: string(sourceIdentity.package, "local CLI surface source.package", 128),
|
|
1328
|
+
packagePath: string(sourceIdentity.packagePath, "local CLI surface source.packagePath", 512),
|
|
1329
|
+
packageDeclaredVersion,
|
|
1330
|
+
versionDiscrepancy,
|
|
1331
|
+
generatedManualSha256: sha(sourceIdentity.generatedManualSha256, "local CLI surface source.generatedManualSha256"),
|
|
1332
|
+
generatedManualIncludesFlagsAndDefaults: boolean(
|
|
1333
|
+
sourceIdentity.generatedManualIncludesFlagsAndDefaults,
|
|
1334
|
+
"local CLI surface source.generatedManualIncludesFlagsAndDefaults",
|
|
1335
|
+
),
|
|
1336
|
+
generatedManualEntries,
|
|
1337
|
+
generatedCanonicalEntries,
|
|
1338
|
+
registeredKeys,
|
|
1339
|
+
}),
|
|
1340
|
+
sdk: Object.freeze({
|
|
1341
|
+
package: string(sdk.package, "local CLI surface SDK.package", 128),
|
|
1342
|
+
version: string(sdk.version, "local CLI surface SDK.version", 128),
|
|
1343
|
+
commit: commitSha(sdk.commit, "local CLI surface SDK.commit"),
|
|
1344
|
+
}),
|
|
1345
|
+
runtime: Object.freeze({
|
|
1346
|
+
providerPluginId: string(runtime.providerPluginId, "local CLI surface runtime.providerPluginId", 128),
|
|
1347
|
+
providerPluginVersion: string(runtime.providerPluginVersion, "local CLI surface runtime.providerPluginVersion", 128),
|
|
1348
|
+
adapterId: string(runtime.adapterId, "local CLI surface runtime.adapterId", 128),
|
|
1349
|
+
adapterVersion: string(runtime.adapterVersion, "local CLI surface runtime.adapterVersion", 128),
|
|
1350
|
+
operationContractVersions,
|
|
1351
|
+
operationInputTypes,
|
|
1352
|
+
target: string(runtime.target, "local CLI surface runtime.target", 128),
|
|
1353
|
+
realm: string(runtime.realm, "local CLI surface runtime.realm", 1_000),
|
|
1354
|
+
compatibility: string(runtime.compatibility, "local CLI surface runtime.compatibility", 1_000),
|
|
1355
|
+
}),
|
|
1356
|
+
globalFlags: Object.freeze(globalFlags),
|
|
1357
|
+
commands: Object.freeze(commands),
|
|
1358
|
+
additionalEntries: Object.freeze(additionalEntries),
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function upstreamProjection(definition: LocalCliSurfaceContractDefinitionV1): unknown {
|
|
1363
|
+
return {
|
|
1364
|
+
executable: definition.executable,
|
|
1365
|
+
source: {
|
|
1366
|
+
package: definition.source.package,
|
|
1367
|
+
packagePath: definition.source.packagePath,
|
|
1368
|
+
packageDeclaredVersion: definition.source.packageDeclaredVersion,
|
|
1369
|
+
generatedManualSha256: definition.source.generatedManualSha256,
|
|
1370
|
+
generatedManualIncludesFlagsAndDefaults:
|
|
1371
|
+
definition.source.generatedManualIncludesFlagsAndDefaults,
|
|
1372
|
+
generatedManualEntries: definition.source.generatedManualEntries,
|
|
1373
|
+
generatedCanonicalEntries: definition.source.generatedCanonicalEntries,
|
|
1374
|
+
registeredKeys: definition.source.registeredKeys,
|
|
1375
|
+
},
|
|
1376
|
+
sdk: definition.sdk,
|
|
1377
|
+
globalFlags: definition.globalFlags.map(({ decision: _decision, ...flag }) => flag),
|
|
1378
|
+
commands: definition.commands.map((command) => ({
|
|
1379
|
+
path: command.path,
|
|
1380
|
+
provenance: command.provenance,
|
|
1381
|
+
profileAuthority: command.profileAuthority,
|
|
1382
|
+
package: command.package,
|
|
1383
|
+
version: command.version,
|
|
1384
|
+
versionKind: command.versionKind,
|
|
1385
|
+
registered: command.registered,
|
|
1386
|
+
publicManual: command.publicManual,
|
|
1387
|
+
generatedCanonical: command.generatedCanonical,
|
|
1388
|
+
upstreamReportedMutates: command.upstreamReportedMutates,
|
|
1389
|
+
arguments: command.arguments.map(({ decision: _decision, ...argument }) => argument),
|
|
1390
|
+
flags: command.flags.map(({ decision: _decision, ...flag }) => flag),
|
|
1391
|
+
})),
|
|
1392
|
+
additionalEntries: definition.additionalEntries.map((entry) => ({
|
|
1393
|
+
path: entry.path,
|
|
1394
|
+
provenance: entry.provenance,
|
|
1395
|
+
profileAuthority: entry.profileAuthority,
|
|
1396
|
+
canonicalTarget: entry.canonicalTarget,
|
|
1397
|
+
package: entry.package,
|
|
1398
|
+
version: entry.version,
|
|
1399
|
+
versionKind: entry.versionKind,
|
|
1400
|
+
registered: entry.registered,
|
|
1401
|
+
publicManual: entry.publicManual,
|
|
1402
|
+
})),
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function classificationProjection(definition: LocalCliSurfaceContractDefinitionV1): unknown {
|
|
1407
|
+
return {
|
|
1408
|
+
runtime: {
|
|
1409
|
+
operationContractVersions: definition.runtime.operationContractVersions,
|
|
1410
|
+
operationInputTypes: definition.runtime.operationInputTypes,
|
|
1411
|
+
},
|
|
1412
|
+
globalFlags: definition.globalFlags.map((flag) => ({
|
|
1413
|
+
name: flag.name,
|
|
1414
|
+
source: flag.source,
|
|
1415
|
+
decision: flag.decision,
|
|
1416
|
+
})),
|
|
1417
|
+
commands: definition.commands.map((command) => ({
|
|
1418
|
+
path: command.path,
|
|
1419
|
+
provenance: command.provenance,
|
|
1420
|
+
reviewedEffect: command.reviewedEffect,
|
|
1421
|
+
decision: command.decision,
|
|
1422
|
+
pathSemanticInputs: command.pathSemanticInputs,
|
|
1423
|
+
arguments: command.arguments.map((argument) => ({ name: argument.name, decision: argument.decision })),
|
|
1424
|
+
flags: command.flags.map((flag) => ({ name: flag.name, source: flag.source, decision: flag.decision })),
|
|
1425
|
+
})),
|
|
1426
|
+
additionalEntries: definition.additionalEntries.map((entry) => ({
|
|
1427
|
+
path: entry.path,
|
|
1428
|
+
provenance: entry.provenance,
|
|
1429
|
+
decision: entry.decision,
|
|
1430
|
+
})),
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function semanticProfile(command: LocalCliSurfaceCommandDefinitionV1): unknown {
|
|
1435
|
+
return {
|
|
1436
|
+
path: command.path,
|
|
1437
|
+
provenance: command.provenance,
|
|
1438
|
+
profileAuthority: command.profileAuthority,
|
|
1439
|
+
package: command.package,
|
|
1440
|
+
version: command.version,
|
|
1441
|
+
versionKind: command.versionKind,
|
|
1442
|
+
registered: command.registered,
|
|
1443
|
+
upstreamReportedMutates: command.upstreamReportedMutates,
|
|
1444
|
+
reviewedEffect: command.reviewedEffect,
|
|
1445
|
+
arguments: command.arguments,
|
|
1446
|
+
flags: command.flags,
|
|
1447
|
+
decision: command.decision,
|
|
1448
|
+
pathSemanticInputs: command.pathSemanticInputs,
|
|
1449
|
+
output: command.output,
|
|
1450
|
+
conditionalInputs: command.conditionalInputs,
|
|
1451
|
+
reconciliation: command.reconciliation,
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
function buildContract(
|
|
1456
|
+
definitionValue: unknown,
|
|
1457
|
+
): LocalCliSurfaceContractV1 {
|
|
1458
|
+
const definition = parseDefinition(definitionValue);
|
|
1459
|
+
const commands = definition.commands.map((command) => Object.freeze({
|
|
1460
|
+
...command,
|
|
1461
|
+
semanticProfileSha256: sha256(surfaceCanonicalJson(semanticProfile(command))),
|
|
1462
|
+
}));
|
|
1463
|
+
const semanticProfileMap = commands.map((command) => ({
|
|
1464
|
+
path: command.path,
|
|
1465
|
+
semanticProfileSha256: command.semanticProfileSha256,
|
|
1466
|
+
}));
|
|
1467
|
+
return Object.freeze({
|
|
1468
|
+
...definition,
|
|
1469
|
+
commands: Object.freeze(commands),
|
|
1470
|
+
digests: Object.freeze({
|
|
1471
|
+
upstreamSurfaceSha256: sha256(surfaceCanonicalJson(upstreamProjection(definition))),
|
|
1472
|
+
classificationSha256: sha256(surfaceCanonicalJson(classificationProjection(definition))),
|
|
1473
|
+
semanticProfilesSha256: sha256(surfaceCanonicalJson(semanticProfileMap)),
|
|
1474
|
+
wholeSurfaceSha256: sha256(surfaceCanonicalJson(definition)),
|
|
1475
|
+
}),
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
export function defineLocalCliSurfaceContractV1(
|
|
1480
|
+
definition: LocalCliSurfaceContractDefinitionV1,
|
|
1481
|
+
): LocalCliSurfaceContractV1 {
|
|
1482
|
+
return buildContract(definition);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
export function parseLocalCliSurfaceContractV1(
|
|
1486
|
+
value: unknown,
|
|
1487
|
+
): LocalCliSurfaceContractV1 {
|
|
1488
|
+
const source = record(value, "local CLI surface contract");
|
|
1489
|
+
const suppliedDigests = record(source.digests, "local CLI surface contract.digests");
|
|
1490
|
+
exactKeys(suppliedDigests, [
|
|
1491
|
+
"upstreamSurfaceSha256", "classificationSha256", "semanticProfilesSha256", "wholeSurfaceSha256",
|
|
1492
|
+
], [], "local CLI surface contract.digests");
|
|
1493
|
+
const parsed = buildContract(value);
|
|
1494
|
+
for (const key of [
|
|
1495
|
+
"upstreamSurfaceSha256", "classificationSha256", "semanticProfilesSha256", "wholeSurfaceSha256",
|
|
1496
|
+
] as const) {
|
|
1497
|
+
if (sha(suppliedDigests[key], `local CLI surface contract.digests.${key}`) !== parsed.digests[key]) {
|
|
1498
|
+
throw new Error(`local CLI surface contract digest ${key} did not match its canonical content`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const suppliedCommands = array(source.commands, "local CLI surface contract.commands", 1_000);
|
|
1502
|
+
for (const [index, command] of suppliedCommands.entries()) {
|
|
1503
|
+
const commandSource = record(command, `local CLI surface contract.commands[${index}]`);
|
|
1504
|
+
if (
|
|
1505
|
+
sha(commandSource.semanticProfileSha256, `local CLI surface contract.commands[${index}].semanticProfileSha256`)
|
|
1506
|
+
!== parsed.commands[index]?.semanticProfileSha256
|
|
1507
|
+
) throw new Error("local CLI surface command semantic profile digest did not match");
|
|
1508
|
+
}
|
|
1509
|
+
return parsed;
|
|
1510
|
+
}
|