@pyric/cli 0.1.0-alpha.26 → 0.1.0-alpha.28
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/README.md +1 -1
- package/dist/assurance/.generated/conformance-verdicts.d.ts +5 -0
- package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
- package/dist/assurance/.generated/conformance-verdicts.js +5 -0
- package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
- package/dist/assurance/capabilities.d.ts.map +1 -1
- package/dist/assurance/capabilities.js +24 -5
- package/dist/assurance/capabilities.js.map +1 -1
- package/dist/assurance/runner.d.ts.map +1 -1
- package/dist/assurance/runner.js +37 -50
- package/dist/assurance/runner.js.map +1 -1
- package/dist/assurance/types.d.ts +5 -0
- package/dist/assurance/types.d.ts.map +1 -1
- package/dist/assurance/types.js.map +1 -1
- package/dist/assurance/validation.d.ts +2 -16
- package/dist/assurance/validation.d.ts.map +1 -1
- package/dist/assurance/validation.js +121 -1
- package/dist/assurance/validation.js.map +1 -1
- package/dist/bridge/surface/arguments/assurance.d.ts +31 -6
- package/dist/bridge/surface/arguments/assurance.d.ts.map +1 -1
- package/dist/bridge/surface/arguments/assurance.js +1 -0
- package/dist/bridge/surface/arguments/assurance.js.map +1 -1
- package/dist/bridge/surface/arguments/messaging.d.ts +2 -2
- package/dist/bridge/surface/methods/assurance/define.d.ts +2 -2
- package/dist/bridge/surface/methods/assurance/map.d.ts +29 -6
- package/dist/bridge/surface/methods/assurance/map.d.ts.map +1 -1
- package/dist/bridge/surface/methods/assurance/propose.d.ts +7 -0
- package/dist/bridge/surface/methods/assurance/propose.d.ts.map +1 -1
- package/dist/bridge/surface/methods/messaging/send.d.ts +4 -4
- package/dist/bridge/surface/render/discriminator-schemas.d.ts +6 -6
- package/dist/cli/database-rules.js +1 -1
- package/dist/cli/database-rules.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +28 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/pyric-config.d.ts +8 -0
- package/dist/cli/pyric-config.d.ts.map +1 -1
- package/dist/cli/pyric-config.js.map +1 -1
- package/dist/cli/run.d.ts +42 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +115 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +5 -3
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/verify.d.ts +6 -0
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +240 -15
- package/dist/cli/verify.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +1 -1
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
- package/dist/conformance/.generated/conformance-docs.js +1 -1
- package/dist/conformance/.generated/conformance-docs.js.map +1 -1
- package/dist/remote/local-storage-url.d.ts +2 -15
- package/dist/remote/local-storage-url.d.ts.map +1 -1
- package/dist/remote/local-storage-url.js +1 -60
- package/dist/remote/local-storage-url.js.map +1 -1
- package/dist/serve/assets/avatar-route.d.ts.map +1 -1
- package/dist/serve/assets/avatar-route.js +6 -6
- package/dist/serve/assets/avatar-route.js.map +1 -1
- package/dist/serve/assets/resolver.d.ts +3 -0
- package/dist/serve/assets/resolver.d.ts.map +1 -1
- package/dist/serve/assets/resolver.js +1 -1
- package/dist/serve/assets/resolver.js.map +1 -1
- package/dist/serve/entries/app-client.d.ts.map +1 -1
- package/dist/serve/entries/app-client.js +6 -0
- package/dist/serve/entries/app-client.js.map +1 -1
- package/dist/serve/repro/denials.d.ts.map +1 -1
- package/dist/serve/repro/denials.js +2 -1
- package/dist/serve/repro/denials.js.map +1 -1
- package/dist/serve/site-ui/_astro/{app.DFdWhpVY.js → app.BCNb6gMq.js} +1 -1
- package/dist/serve/site-ui/_astro/app.DhoRPDdN.js +147 -0
- package/dist/serve/site-ui/_astro/{seed.vvXV1ioI.js → seed.BO5CXcqE.js} +1 -1
- package/dist/serve/site-ui/auth/index.html +1 -1
- package/dist/serve/site-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/site-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/api-reference/index.html +2 -1
- package/dist/serve/site-ui/docs/api-reference.md +1 -0
- package/dist/serve/site-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/auth-compat/index.html +2 -2
- package/dist/serve/site-ui/docs/auth-compat.md +1 -1
- package/dist/serve/site-ui/docs/auth-flutter-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/auth-kotlin-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/auth-swift-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/build/ai-logic/index.html +1 -1
- package/dist/serve/site-ui/docs/build/authentication/index.html +1 -1
- package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +1 -1
- package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/site-ui/docs/build/cloud-storage/index.html +13 -2
- package/dist/serve/site-ui/docs/build/cloud-storage.md +23 -0
- package/dist/serve/site-ui/docs/build/default-avatars/index.html +1 -1
- package/dist/serve/site-ui/docs/build/inspect-activity/index.html +1 -1
- package/dist/serve/site-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/site-ui/docs/conformance-scores/index.html +1 -1
- package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/database-compat/index.html +5 -1
- package/dist/serve/site-ui/docs/database-compat.md +4 -0
- package/dist/serve/site-ui/docs/examples/chess/index.html +1 -1
- package/dist/serve/site-ui/docs/examples/index.html +1 -1
- package/dist/serve/site-ui/docs/firestore-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/firestore-flutter-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/firestore-kotlin-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/firestore-swift-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/backend-bundlers/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/flutter/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/nextjs/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/node-host/index.html +1 -1
- package/dist/serve/site-ui/docs/get-started/start-building/index.html +2 -1
- package/dist/serve/site-ui/docs/get-started/start-building.md +2 -0
- package/dist/serve/site-ui/docs/get-started/vite/index.html +4 -2
- package/dist/serve/site-ui/docs/get-started/vite.md +4 -0
- package/dist/serve/site-ui/docs/index.json +50 -0
- package/dist/serve/site-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +1 -1
- package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/site-ui/docs/observe/switch-and-impersonate-identities/index.html +1 -1
- package/dist/serve/site-ui/docs/overview/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-app-check-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-data-connect-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-eventarc-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-extensions-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-functions-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-installations-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-instance-id-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-machine-learning-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-phone-number-verification-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-project-management-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-remote-config-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-security-rules-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-analytics-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-app-check-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +2 -2
- package/dist/serve/site-ui/docs/pyric-auth-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +24 -5
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api.md +11 -4
- package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-flow-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-client-app-bridge-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-client-app-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-client-auth-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-client-database-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-client-firestore-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-client-storage-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-repro-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +16 -3
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api.md +11 -10
- package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +17 -3
- package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api.md +7 -5
- package/dist/serve/site-ui/docs/pyric-cli-vite-ssr-loader-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-functions-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-performance-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-remote-config-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-sandbox-branches-store-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-sandbox-checkpoints-directory-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-sandbox-checkpoints-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-storage-sw-reference-api/index.html +102 -0
- package/dist/serve/site-ui/docs/pyric-storage-sw-reference-api.md +76 -0
- package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-listener-owner-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/site-ui/docs/reference/cli/index.html +12 -2
- package/dist/serve/site-ui/docs/reference/cli.md +24 -0
- package/dist/serve/site-ui/docs/rtdb-flutter-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/rtdb-kotlin-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/rtdb-swift-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/rules-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/rtdb-rules-standard-library/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +2 -2
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite.md +1 -1
- package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +5 -2
- package/dist/serve/site-ui/docs/ship/ship-to-production.md +7 -1
- package/dist/serve/site-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/site-ui/docs/storage-compat/index.html +1 -1
- package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +1 -1
- package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/site-ui/docs/tutorial/index.html +1 -1
- package/dist/serve/site-ui/examples/chess/index.html +1 -1
- package/dist/serve/site-ui/firestore/index.html +1 -1
- package/dist/serve/site-ui/llms.txt +1 -0
- package/dist/serve/site-ui/rtdb/index.html +1 -1
- package/dist/serve/site-ui/settings/index.html +1 -1
- package/dist/serve/site-ui/storage/index.html +1 -1
- package/dist/serve/site-ui/studio/index.html +1 -1
- package/dist/serve/site-ui/traffic/index.html +1 -1
- package/dist/serve/verify-hint.d.ts +2 -0
- package/dist/serve/verify-hint.d.ts.map +1 -0
- package/dist/serve/verify-hint.js +10 -0
- package/dist/serve/verify-hint.js.map +1 -0
- package/dist/serve/vite-generation-rules-watch.d.ts.map +1 -1
- package/dist/serve/vite-generation-rules-watch.js +5 -3
- package/dist/serve/vite-generation-rules-watch.js.map +1 -1
- package/dist/serve/vite-plugin.d.ts +6 -0
- package/dist/serve/vite-plugin.d.ts.map +1 -1
- package/dist/serve/vite-plugin.js +11 -1
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-service-worker.d.ts +12 -0
- package/dist/serve/vite-service-worker.d.ts.map +1 -0
- package/dist/serve/vite-service-worker.js +33 -0
- package/dist/serve/vite-service-worker.js.map +1 -0
- package/dist/serve/worker/client/auth.d.ts.map +1 -1
- package/dist/serve/worker/client/auth.js +4 -3
- package/dist/serve/worker/client/auth.js.map +1 -1
- package/dist/serve/worker/host/auth-action-codes.js +2 -2
- package/dist/serve/worker/host/auth-action-codes.js.map +1 -1
- package/dist/serve/worker/host/auth-custom-token.d.ts.map +1 -1
- package/dist/serve/worker/host/auth-custom-token.js +4 -2
- package/dist/serve/worker/host/auth-custom-token.js.map +1 -1
- package/dist/serve/worker/host/dispatch.d.ts.map +1 -1
- package/dist/serve/worker/host/dispatch.js +3 -1
- package/dist/serve/worker/host/dispatch.js.map +1 -1
- package/dist/serve/worker/host-auth.d.ts +6 -0
- package/dist/serve/worker/host-auth.d.ts.map +1 -1
- package/dist/serve/worker/host-auth.js +16 -5
- package/dist/serve/worker/host-auth.js.map +1 -1
- package/dist/serve/worker/inbound-validation.d.ts.map +1 -1
- package/dist/serve/worker/inbound-validation.js +1 -0
- package/dist/serve/worker/inbound-validation.js.map +1 -1
- package/dist/serve/worker/protocol/storage.d.ts +1 -8
- package/dist/serve/worker/protocol/storage.d.ts.map +1 -1
- package/dist/serve/worker/protocol/storage.js +2 -14
- package/dist/serve/worker/protocol/storage.js.map +1 -1
- package/dist/serve/worker/protocol.d.ts +2 -0
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/verify/index.d.ts +16 -2
- package/dist/verify/index.d.ts.map +1 -1
- package/dist/verify/index.js +100 -18
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/staging/engine.d.ts +102 -0
- package/dist/verify/staging/engine.d.ts.map +1 -0
- package/dist/verify/staging/engine.js +371 -0
- package/dist/verify/staging/engine.js.map +1 -0
- package/dist/verify/staging/rest.d.ts +68 -0
- package/dist/verify/staging/rest.d.ts.map +1 -0
- package/dist/verify/staging/rest.js +93 -0
- package/dist/verify/staging/rest.js.map +1 -0
- package/dist/verify/staging/target.d.ts +39 -0
- package/dist/verify/staging/target.d.ts.map +1 -0
- package/dist/verify/staging/target.js +117 -0
- package/dist/verify/staging/target.js.map +1 -0
- package/package.json +12 -8
- package/src/assurance/.generated/conformance-verdicts.ts +5 -0
- package/src/assurance/capabilities.ts +22 -4
- package/src/assurance/runner.ts +40 -58
- package/src/assurance/types.ts +3 -0
- package/src/assurance/validation.ts +137 -0
- package/src/bridge/surface/arguments/assurance.ts +1 -0
- package/src/cli/database-rules.ts +1 -1
- package/src/cli/index.ts +28 -3
- package/src/cli/pyric-config.ts +5 -0
- package/src/cli/run.ts +139 -0
- package/src/cli/serve.ts +6 -3
- package/src/cli/verify.ts +244 -18
- package/src/conformance/.generated/can-i-use.ts +1 -1
- package/src/conformance/.generated/conformance-docs.ts +1 -1
- package/src/remote/local-storage-url.ts +1 -58
- package/src/serve/assets/avatar-route.ts +6 -7
- package/src/serve/assets/resolver.ts +4 -1
- package/src/serve/entries/app-client.ts +7 -0
- package/src/serve/repro/denials.ts +2 -1
- package/src/serve/verify-hint.ts +8 -0
- package/src/serve/vite-generation-rules-watch.ts +6 -3
- package/src/serve/vite-plugin.ts +15 -1
- package/src/serve/vite-service-worker.ts +44 -0
- package/src/serve/worker/client/auth.ts +4 -3
- package/src/serve/worker/host/auth-action-codes.ts +2 -2
- package/src/serve/worker/host/auth-custom-token.ts +4 -2
- package/src/serve/worker/host/dispatch.ts +2 -0
- package/src/serve/worker/host-auth.ts +18 -5
- package/src/serve/worker/inbound-validation.ts +1 -0
- package/src/serve/worker/protocol/storage.ts +2 -14
- package/src/serve/worker/protocol.ts +2 -0
- package/src/verify/index.ts +119 -19
- package/src/verify/staging/engine.ts +455 -0
- package/src/verify/staging/rest.ts +129 -0
- package/src/verify/staging/target.ts +137 -0
- package/dist/serve/site-ui/_astro/app.BpEphDR7.js +0 -147
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The staging target `pyric verify --engine staging` replays against, from
|
|
3
|
+
* the project's `pyric.json`:
|
|
4
|
+
*
|
|
5
|
+
* { "verify": { "staging": { "project": "my-staging", "rtdb": "https://my-verify.firebaseio.com" } } }
|
|
6
|
+
*
|
|
7
|
+
* `rtdb` is a database URL or an instance name (`my-verify` resolves to
|
|
8
|
+
* `https://my-verify.firebaseio.com`; an instance outside us-central1 needs
|
|
9
|
+
* its full URL). The user brings the instance; pyric provisions nothing.
|
|
10
|
+
*
|
|
11
|
+
* An instance the app's own `firebase.json` deploys rules to, or the app
|
|
12
|
+
* project's default instance, is refused: those hold the app's data.
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { resolveDatabaseInstance } from 'pyric/sandbox/internal';
|
|
16
|
+
import { firebaseToolsConfigstorePath } from '../../cli/firebase-project.js';
|
|
17
|
+
import { resolveScope, SERVICE_ACCOUNT_BASE64_ENV_KEY, SERVICE_ACCOUNT_FILE_ENV_KEY } from '../../credentials/node/scope.js';
|
|
18
|
+
export class StagingConfigError extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'StagingConfigError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const CONFIG_EXAMPLE = '{ "verify": { "staging": { "project": "<staging project id>", "rtdb": "https://<dedicated instance>.firebaseio.com" } } }';
|
|
25
|
+
/** The staging target in a parsed `pyric.json`, or a config error naming what is missing. */
|
|
26
|
+
export function stagingTargetFrom(config) {
|
|
27
|
+
const staging = config?.verify?.staging;
|
|
28
|
+
if (staging === undefined) {
|
|
29
|
+
throw new StagingConfigError(`no staging target. Add one to pyric.json: ${CONFIG_EXAMPLE}`);
|
|
30
|
+
}
|
|
31
|
+
const { project, rtdb } = (staging ?? {});
|
|
32
|
+
if (typeof project !== 'string' || project.trim() === '') {
|
|
33
|
+
throw new StagingConfigError(`pyric.json verify.staging.project must name the staging project: ${CONFIG_EXAMPLE}`);
|
|
34
|
+
}
|
|
35
|
+
if (typeof rtdb !== 'string' || rtdb.trim() === '') {
|
|
36
|
+
throw new StagingConfigError(`pyric.json verify.staging.rtdb must name the dedicated instance: ${CONFIG_EXAMPLE}`);
|
|
37
|
+
}
|
|
38
|
+
const trimmed = rtdb.trim();
|
|
39
|
+
const databaseUrl = /^[a-z0-9-]+$/i.test(trimmed) ? `https://${trimmed}.firebaseio.com` : trimmed.replace(/\/+$/, '');
|
|
40
|
+
let instance;
|
|
41
|
+
try {
|
|
42
|
+
instance = resolveDatabaseInstance(databaseUrl)?.name;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
instance = undefined;
|
|
46
|
+
}
|
|
47
|
+
if (instance === undefined) {
|
|
48
|
+
throw new StagingConfigError(`pyric.json verify.staging.rtdb "${rtdb}" is not a Realtime Database URL or instance name.`);
|
|
49
|
+
}
|
|
50
|
+
return { projectId: project.trim(), databaseUrl, instance };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Refuse a staging instance the app uses: one `firebase.json` deploys rules
|
|
54
|
+
* to (by instance or by a `.firebaserc` target), or the default instance of
|
|
55
|
+
* the app's project.
|
|
56
|
+
*/
|
|
57
|
+
export function assertNotAppInstance(target, app) {
|
|
58
|
+
const declared = new Set(app.targetInstances ?? []);
|
|
59
|
+
const database = app.firebaseJson?.database;
|
|
60
|
+
const entries = Array.isArray(database) ? database : database ? [database] : [];
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
const instance = entry.instance;
|
|
63
|
+
if (typeof instance === 'string')
|
|
64
|
+
declared.add(instance);
|
|
65
|
+
}
|
|
66
|
+
if (app.appProjectId)
|
|
67
|
+
declared.add(`${app.appProjectId}-default-rtdb`);
|
|
68
|
+
if (declared.has(target.instance)) {
|
|
69
|
+
throw new StagingConfigError(`refusing staging instance ${target.instance}: the app uses it (firebase.json deploys rules to it, or it is the default instance of project ${app.appProjectId}). The staging engine replaces an instance's rules and data, so it needs one dedicated to it.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* An OAuth token for the staging project: a service account or Application
|
|
74
|
+
* Default Credentials (`resolveScope`), else the Firebase CLI's login
|
|
75
|
+
* (`firebase login`) while its access token is fresh.
|
|
76
|
+
*/
|
|
77
|
+
export async function stagingToken(projectId, deps = {}) {
|
|
78
|
+
const env = deps.env ?? process.env;
|
|
79
|
+
try {
|
|
80
|
+
const { scope, source } = await (deps.resolve ?? resolveScope)({ projectId, ...(deps.env ? { env: deps.env } : {}) });
|
|
81
|
+
return { token: () => scope.resolveToken(), source };
|
|
82
|
+
}
|
|
83
|
+
catch (resolveError) {
|
|
84
|
+
// A configured credential that fails is an error to fix, not a reason to
|
|
85
|
+
// run as the developer's own CLI login instead.
|
|
86
|
+
if (configuredCredential(env))
|
|
87
|
+
throw resolveError;
|
|
88
|
+
const cli = firebaseCliToken(env, (deps.now ?? Date.now)());
|
|
89
|
+
if (cli !== undefined)
|
|
90
|
+
return { token: async () => cli, source: 'firebase login' };
|
|
91
|
+
throw new StagingConfigError(`no credentials for staging project ${projectId}. ${resolveError instanceof Error ? resolveError.message : String(resolveError)} ` +
|
|
92
|
+
'Or sign in with `firebase login`: its token is used while it has at least 15 minutes left (run any firebase command to refresh it).');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Whether the environment names a service account (`resolveScope`'s first two sources). */
|
|
96
|
+
function configuredCredential(env) {
|
|
97
|
+
return [SERVICE_ACCOUNT_BASE64_ENV_KEY, SERVICE_ACCOUNT_FILE_ENV_KEY].some((key) => (env[key] ?? '').trim() !== '');
|
|
98
|
+
}
|
|
99
|
+
/** How long a CLI token must stay valid for a run to start with it; it cannot be refreshed mid-run. */
|
|
100
|
+
export const CLI_TOKEN_MARGIN_MS = 15 * 60_000;
|
|
101
|
+
/** The Firebase CLI's access token, when its configstore holds one with a known expiry at least the margin away. */
|
|
102
|
+
function firebaseCliToken(env, now) {
|
|
103
|
+
try {
|
|
104
|
+
const store = JSON.parse(readFileSync(firebaseToolsConfigstorePath(env), 'utf8'));
|
|
105
|
+
const token = store.tokens?.access_token;
|
|
106
|
+
const expiresAt = store.tokens?.expires_at;
|
|
107
|
+
if (typeof token !== 'string' || token === '')
|
|
108
|
+
return undefined;
|
|
109
|
+
if (typeof expiresAt !== 'number' || expiresAt - now < CLI_TOKEN_MARGIN_MS)
|
|
110
|
+
return undefined;
|
|
111
|
+
return token;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target.js","sourceRoot":"","sources":["../../../src/verify/staging/target.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyric/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.28",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://pyric.dev",
|
|
6
6
|
"repository": {
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
},
|
|
78
78
|
"./vite": {
|
|
79
79
|
"types": "./dist/vite.d.ts",
|
|
80
|
-
"import": "./dist/vite.js"
|
|
80
|
+
"import": "./dist/vite.js",
|
|
81
|
+
"default": "./dist/vite.js"
|
|
81
82
|
},
|
|
82
83
|
"./vite/ssr-loader": {
|
|
83
84
|
"types": "./dist/serve/vite-ssr-loader.d.ts",
|
|
@@ -88,7 +89,8 @@
|
|
|
88
89
|
},
|
|
89
90
|
"./next": {
|
|
90
91
|
"types": "./dist/next/index.d.ts",
|
|
91
|
-
"import": "./dist/next/index.js"
|
|
92
|
+
"import": "./dist/next/index.js",
|
|
93
|
+
"default": "./dist/next/index.js"
|
|
92
94
|
},
|
|
93
95
|
"./bundler": {
|
|
94
96
|
"types": "./dist/bundler/index.d.ts",
|
|
@@ -109,7 +111,8 @@
|
|
|
109
111
|
"types": "./dist/register/index.d.ts",
|
|
110
112
|
"node": {
|
|
111
113
|
"types": "./dist/register/index.d.ts",
|
|
112
|
-
"import": "./dist/register/index.js"
|
|
114
|
+
"import": "./dist/register/index.js",
|
|
115
|
+
"default": "./dist/register/index.js"
|
|
113
116
|
}
|
|
114
117
|
},
|
|
115
118
|
"./register/app-bridge": {
|
|
@@ -209,7 +212,8 @@
|
|
|
209
212
|
},
|
|
210
213
|
"./next/internal/react-hook": {
|
|
211
214
|
"import": "./dist/next/react-hook-entry.js"
|
|
212
|
-
}
|
|
215
|
+
},
|
|
216
|
+
"./package.json": "./package.json"
|
|
213
217
|
},
|
|
214
218
|
"bin": {
|
|
215
219
|
"pyric": "./dist/cli/index.js"
|
|
@@ -245,12 +249,12 @@
|
|
|
245
249
|
},
|
|
246
250
|
"dependencies": {
|
|
247
251
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
248
|
-
"create-pyric": "^0.1.0-alpha.
|
|
252
|
+
"create-pyric": "^0.1.0-alpha.28",
|
|
249
253
|
"esbuild": "^0.28.0",
|
|
250
254
|
"fake-indexeddb": "^6.0.0",
|
|
251
255
|
"jsonc-parser": "^3.3.1",
|
|
252
|
-
"pyric": "^0.1.0-alpha.
|
|
253
|
-
"pyric-admin": "^0.1.0-alpha.
|
|
256
|
+
"pyric": "^0.1.0-alpha.28",
|
|
257
|
+
"pyric-admin": "^0.1.0-alpha.28",
|
|
254
258
|
"ws": "^8.18.0",
|
|
255
259
|
"zod": "^3.23.0"
|
|
256
260
|
},
|
|
@@ -1372,9 +1372,14 @@ export const CONFORMANCE_VERDICTS = {
|
|
|
1372
1372
|
"rtdb-modular#MI4": "supported",
|
|
1373
1373
|
"rtdb-modular#MI5": "supported",
|
|
1374
1374
|
"rtdb-modular#MI6": "supported",
|
|
1375
|
+
"rtdb-modular#MI7": "supported",
|
|
1375
1376
|
"rtdb-modular#RS1": "qualified",
|
|
1376
1377
|
"rtdb-modular#RS2": "qualified",
|
|
1377
1378
|
"rtdb-modular#RS3": "qualified",
|
|
1379
|
+
"rtdb-modular#VR1": "supported",
|
|
1380
|
+
"rtdb-modular#VR2": "supported",
|
|
1381
|
+
"rtdb-modular#VR3": "supported",
|
|
1382
|
+
"rtdb-modular#VR4": "supported",
|
|
1378
1383
|
"rtdb-rules#1": "supported",
|
|
1379
1384
|
"rtdb-rules#10": "supported",
|
|
1380
1385
|
"rtdb-rules#11": "supported",
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type PathSegment,
|
|
22
22
|
} from "pyric/rules/internal";
|
|
23
23
|
import { rtdbRulesSourceRejection } from "pyric/rules/internal/rtdb";
|
|
24
|
+
import { resolveDatabaseInstance } from "pyric/sandbox/internal";
|
|
24
25
|
import { compileStorageRules } from "pyric/storage/internal";
|
|
25
26
|
import {
|
|
26
27
|
CONFORMANCE_VERDICTS,
|
|
@@ -101,9 +102,17 @@ export function rulesLoadRefusal(
|
|
|
101
102
|
service: AssuranceProbe["control"]["service"],
|
|
102
103
|
): string | undefined {
|
|
103
104
|
if (service === "rtdb") {
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if (target.rules.rtdb) {
|
|
106
|
+
const rejection = rtdbRulesSourceRejection(target.rules.rtdb);
|
|
107
|
+
if (rejection !== null) return rejection.message;
|
|
108
|
+
}
|
|
109
|
+
if (target.rules.rtdbInstances) {
|
|
110
|
+
for (const [instanceName, instanceRules] of Object.entries(target.rules.rtdbInstances)) {
|
|
111
|
+
const rejection = rtdbRulesSourceRejection(instanceRules as any);
|
|
112
|
+
if (rejection !== null) return `instance '${instanceName}': ${rejection.message}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
107
116
|
}
|
|
108
117
|
const source =
|
|
109
118
|
service === "firestore"
|
|
@@ -389,7 +398,16 @@ function rtdbRequirements(
|
|
|
389
398
|
target: LocalFirebaseTarget,
|
|
390
399
|
probe: AssuranceProbe,
|
|
391
400
|
): CapabilityRequirement[] {
|
|
392
|
-
|
|
401
|
+
let rules = target.rules.rtdb;
|
|
402
|
+
if (probe.control.service === "rtdb" && probe.control.instance && probe.control.instance !== "default") {
|
|
403
|
+
const resolvedName = resolveDatabaseInstance(probe.control.instance)?.name;
|
|
404
|
+
if (resolvedName && target.rules.rtdbInstances) {
|
|
405
|
+
const declaredEntry = Object.entries(target.rules.rtdbInstances).find(([k]) => resolveDatabaseInstance(k)?.name === resolvedName);
|
|
406
|
+
if (declaredEntry) {
|
|
407
|
+
rules = declaredEntry[1] as any;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
393
411
|
const operations = [probe.control, probe.mutation.operation];
|
|
394
412
|
const ruleSources = rules
|
|
395
413
|
? collectRtdbRuleSources(rules.rules).filter((rule) =>
|
package/src/assurance/runner.ts
CHANGED
|
@@ -60,15 +60,17 @@ import {
|
|
|
60
60
|
initializeSandbox,
|
|
61
61
|
type AuthState,
|
|
62
62
|
type LocalSandbox,
|
|
63
|
+
type SandboxContext,
|
|
63
64
|
type SandboxEvent,
|
|
64
65
|
} from "pyric/sandbox";
|
|
65
|
-
import { getInternalEnv } from "pyric/sandbox/internal";
|
|
66
|
+
import { getInternalEnv, resolveDatabaseInstance } from "pyric/sandbox/internal";
|
|
66
67
|
import { qualifyProbe, rulesLoadRefusal } from "./capabilities.js";
|
|
67
68
|
import {
|
|
68
69
|
assertActor,
|
|
69
70
|
assertInvariant,
|
|
70
71
|
assertProbe,
|
|
71
72
|
assertTarget,
|
|
73
|
+
assertCampaign,
|
|
72
74
|
} from "./validation.js";
|
|
73
75
|
import {
|
|
74
76
|
ASSURANCE_CAMPAIGN_SCHEMA,
|
|
@@ -96,9 +98,12 @@ interface ActorRuntime {
|
|
|
96
98
|
auth: AuthState | null;
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
|
|
102
|
+
|
|
99
103
|
interface ServiceRuntime {
|
|
100
104
|
sandbox: LocalSandbox;
|
|
101
105
|
actor: ActorRuntime;
|
|
106
|
+
context: SandboxContext;
|
|
102
107
|
firestore?: Firestore;
|
|
103
108
|
rtdb?: Database;
|
|
104
109
|
rtdbAdmin?: Database;
|
|
@@ -114,56 +119,6 @@ interface ExecutedOperation {
|
|
|
114
119
|
|
|
115
120
|
let storageRunSequence = 0;
|
|
116
121
|
|
|
117
|
-
function assertCampaign(spec: AuthorizationCampaignSpec): void {
|
|
118
|
-
if (spec.schema !== ASSURANCE_CAMPAIGN_SCHEMA) {
|
|
119
|
-
throw new AssuranceInputError(
|
|
120
|
-
`campaign schema must be '${ASSURANCE_CAMPAIGN_SCHEMA}'.`,
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
if (!spec.id.trim())
|
|
124
|
-
throw new AssuranceInputError("campaign id is required.");
|
|
125
|
-
assertTarget(spec.target);
|
|
126
|
-
if (!Array.isArray(spec.actors)) {
|
|
127
|
-
throw new AssuranceInputError("campaign actors must be an array.");
|
|
128
|
-
}
|
|
129
|
-
if (!Array.isArray(spec.invariants)) {
|
|
130
|
-
throw new AssuranceInputError("campaign invariants must be an array.");
|
|
131
|
-
}
|
|
132
|
-
if (!Array.isArray(spec.probes)) {
|
|
133
|
-
throw new AssuranceInputError("campaign probes must be an array.");
|
|
134
|
-
}
|
|
135
|
-
for (const actor of spec.actors) assertActor(actor);
|
|
136
|
-
for (const invariant of spec.invariants) assertInvariant(invariant);
|
|
137
|
-
for (const probe of spec.probes) assertProbe(probe);
|
|
138
|
-
uniqueIds(spec.actors, "actor");
|
|
139
|
-
uniqueIds(spec.invariants, "invariant");
|
|
140
|
-
uniqueIds(spec.probes, "probe");
|
|
141
|
-
|
|
142
|
-
for (const probe of spec.probes) {
|
|
143
|
-
const actor = spec.actors.find((item) => item.id === probe.actorId);
|
|
144
|
-
const invariant = spec.invariants.find(
|
|
145
|
-
(item) => item.id === probe.invariantId,
|
|
146
|
-
);
|
|
147
|
-
if (!actor)
|
|
148
|
-
throw new AssuranceInputError(
|
|
149
|
-
`probe '${probe.id}' references unknown actor '${probe.actorId}'.`,
|
|
150
|
-
);
|
|
151
|
-
if (!invariant) {
|
|
152
|
-
throw new AssuranceInputError(
|
|
153
|
-
`probe '${probe.id}' references unknown invariant '${probe.invariantId}'.`,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
if (
|
|
157
|
-
invariant.service !== "cross-service" &&
|
|
158
|
-
invariant.service !== probe.control.service
|
|
159
|
-
) {
|
|
160
|
-
throw new AssuranceInputError(
|
|
161
|
-
`probe '${probe.id}' service does not match invariant '${invariant.id}'.`,
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
122
|
function uniqueIds(items: ReadonlyArray<{ id: string }>, label: string): void {
|
|
168
123
|
const seen = new Set<string>();
|
|
169
124
|
for (const item of items) {
|
|
@@ -337,7 +292,7 @@ async function createRuntime(
|
|
|
337
292
|
const sandbox = initializeSandbox();
|
|
338
293
|
seedAuth(sandbox, target);
|
|
339
294
|
const context = sandbox.withAuth(actorRuntime.auth);
|
|
340
|
-
const runtime: ServiceRuntime = { sandbox, actor: actorRuntime };
|
|
295
|
+
const runtime: ServiceRuntime = { sandbox, actor: actorRuntime, context };
|
|
341
296
|
|
|
342
297
|
if (service === "firestore") {
|
|
343
298
|
const firestore = getFirestore(context);
|
|
@@ -347,12 +302,34 @@ async function createRuntime(
|
|
|
347
302
|
} else if (service === "rtdb") {
|
|
348
303
|
const rtdb = getDatabase(context);
|
|
349
304
|
const admin = getAdminDatabase(sandbox);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
305
|
+
if (target.rules.rtdb) {
|
|
306
|
+
const refusal = rtdbRulesSourceRejection(target.rules.rtdb as any);
|
|
307
|
+
if (refusal !== null) throw new Error(`The assurance target's database rules would not deploy. ${refusal.message}`);
|
|
308
|
+
rtdbSandbox.setRules(rtdb, target.rules.rtdb as any);
|
|
309
|
+
rtdbSandbox.setData(admin, { "/": target.state.rtdb ?? {} });
|
|
310
|
+
}
|
|
354
311
|
runtime.rtdb = rtdb;
|
|
355
312
|
runtime.rtdbAdmin = admin;
|
|
313
|
+
|
|
314
|
+
if (target.rules.rtdbInstances) {
|
|
315
|
+
const stateByResolved = new Map<string, unknown>();
|
|
316
|
+
if (target.state.rtdbInstances) {
|
|
317
|
+
for (const [k, v] of Object.entries(target.state.rtdbInstances)) {
|
|
318
|
+
stateByResolved.set(resolveDatabaseInstance(k)!.name, v);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const [instanceName, instanceValue] of Object.entries(target.rules.rtdbInstances)) {
|
|
322
|
+
const instanceRefusal = rtdbRulesSourceRejection(instanceValue);
|
|
323
|
+
if (instanceRefusal !== null) throw new Error(`The assurance target's database rules for instance '${instanceName}' would not deploy. ${instanceRefusal.message}`);
|
|
324
|
+
|
|
325
|
+
const resolved = resolveDatabaseInstance(instanceName)!;
|
|
326
|
+
const instDb = getDatabase(context, resolved.url);
|
|
327
|
+
const instAdmin = getAdminDatabase(sandbox, resolved.url);
|
|
328
|
+
rtdbSandbox.setRules(instDb, instanceValue as { rules: Record<string, unknown> });
|
|
329
|
+
const instData = stateByResolved.get(resolved.name);
|
|
330
|
+
rtdbSandbox.setData(instAdmin, { "/": instData ?? {} });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
356
333
|
} else {
|
|
357
334
|
if (target.state.firestore) {
|
|
358
335
|
seedDocuments(
|
|
@@ -678,7 +655,12 @@ async function readState(
|
|
|
678
655
|
return state[normalizedPath] ?? null;
|
|
679
656
|
}
|
|
680
657
|
if (operation.service === "rtdb") {
|
|
681
|
-
|
|
658
|
+
let admin = runtime.rtdbAdmin!;
|
|
659
|
+
if (operation.instance && operation.instance !== "default") {
|
|
660
|
+
const url = resolveDatabaseInstance(operation.instance)!.url;
|
|
661
|
+
admin = getAdminDatabase(runtime.sandbox, url);
|
|
662
|
+
}
|
|
663
|
+
return (await getRtdb(rtdbRef(admin, operation.path))).val();
|
|
682
664
|
}
|
|
683
665
|
return readStorageState(operation, runtime.storageAdmin!);
|
|
684
666
|
}
|
|
@@ -740,7 +722,7 @@ async function executeOperation(
|
|
|
740
722
|
operation.service === "firestore"
|
|
741
723
|
? await executeFirestore(operation, runtime.firestore!, runtime)
|
|
742
724
|
: operation.service === "rtdb"
|
|
743
|
-
? await executeRtdb(operation, runtime.rtdb!)
|
|
725
|
+
? await executeRtdb(operation, operation.instance && operation.instance !== "default" ? getDatabase(runtime.context, resolveDatabaseInstance(operation.instance)!.url) : runtime.rtdb!)
|
|
744
726
|
: await executeStorage(operation, runtime.storage!);
|
|
745
727
|
} catch (error) {
|
|
746
728
|
const result = errorDecision(error);
|
package/src/assurance/types.ts
CHANGED
|
@@ -64,11 +64,13 @@ export interface LocalFirebaseTarget {
|
|
|
64
64
|
rules: {
|
|
65
65
|
firestore?: string;
|
|
66
66
|
rtdb?: { rules: Record<string, unknown> };
|
|
67
|
+
rtdbInstances?: Record<string, { rules: Record<string, unknown> }>;
|
|
67
68
|
storage?: string;
|
|
68
69
|
};
|
|
69
70
|
state: {
|
|
70
71
|
firestore?: Record<string, Record<string, unknown>>;
|
|
71
72
|
rtdb?: unknown;
|
|
73
|
+
rtdbInstances?: Record<string, unknown>;
|
|
72
74
|
storage?: StorageObjectFixture[];
|
|
73
75
|
auth?: { users: AuthFixtureUser[] };
|
|
74
76
|
};
|
|
@@ -125,6 +127,7 @@ export interface FirestoreOperation {
|
|
|
125
127
|
|
|
126
128
|
export interface RtdbOperation {
|
|
127
129
|
service: "rtdb";
|
|
130
|
+
instance?: string;
|
|
128
131
|
method: "get" | "set" | "update" | "remove";
|
|
129
132
|
path: string;
|
|
130
133
|
data?: unknown;
|
|
@@ -13,13 +13,17 @@
|
|
|
13
13
|
* `observations[0]`, `probes[2]`. A caller that validates one record on its
|
|
14
14
|
* own passes nothing and gets the record's kind instead.
|
|
15
15
|
*/
|
|
16
|
+
import { resolveDatabaseInstance } from "pyric/sandbox/internal";
|
|
17
|
+
import { rtdbRulesSourceRejection } from "pyric/rules/internal/rtdb";
|
|
16
18
|
import {
|
|
19
|
+
ASSURANCE_CAMPAIGN_SCHEMA,
|
|
17
20
|
ASSURANCE_TARGET_SCHEMA,
|
|
18
21
|
AssuranceInputError,
|
|
19
22
|
type AssuranceActor,
|
|
20
23
|
type AssuranceObservation,
|
|
21
24
|
type AssuranceProbe,
|
|
22
25
|
type FirebaseOperation,
|
|
26
|
+
type AuthorizationCampaignSpec,
|
|
23
27
|
type LocalFirebaseTarget,
|
|
24
28
|
type MutationDimension,
|
|
25
29
|
type SecurityInvariant,
|
|
@@ -225,13 +229,56 @@ export function assertTarget(
|
|
|
225
229
|
if (target.rules.rtdb !== undefined) {
|
|
226
230
|
requiredRecord(target.rules.rtdb, `${where}.rules.rtdb`);
|
|
227
231
|
requiredRecord(target.rules.rtdb.rules, `${where}.rules.rtdb.rules`);
|
|
232
|
+
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const declaredRuleInstances = new Map<string, string>();
|
|
236
|
+
if (target.rules.rtdbInstances !== undefined) {
|
|
237
|
+
requiredRecord(target.rules.rtdbInstances, `${where}.rules.rtdbInstances`);
|
|
238
|
+
for (const [instance, rules] of Object.entries(target.rules.rtdbInstances)) {
|
|
239
|
+
const at = `${where}.rules.rtdbInstances[${JSON.stringify(instance)}]`;
|
|
240
|
+
requiredRecord(rules, at);
|
|
241
|
+
requiredRecord((rules as any).rules, `${at}.rules`);
|
|
242
|
+
try {
|
|
243
|
+
const resolvedName = resolveDatabaseInstance(instance)!.name;
|
|
244
|
+
if (declaredRuleInstances.has(resolvedName)) {
|
|
245
|
+
throw new AssuranceInputError(`${at} declares instance '${resolvedName}' which is already declared by key '${declaredRuleInstances.get(resolvedName)}'`);
|
|
246
|
+
}
|
|
247
|
+
declaredRuleInstances.set(resolvedName, instance);
|
|
248
|
+
} catch (e) {
|
|
249
|
+
if (e instanceof AssuranceInputError) throw e;
|
|
250
|
+
throw new AssuranceInputError(`${at} names a malformed RTDB instance: ${e instanceof Error ? e.message : String(e)}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
228
253
|
}
|
|
254
|
+
|
|
229
255
|
if (target.state.firestore !== undefined) {
|
|
230
256
|
requiredRecord(target.state.firestore, `${where}.state.firestore`);
|
|
231
257
|
for (const [docPath, docData] of Object.entries(target.state.firestore)) {
|
|
232
258
|
requiredRecord(docData, `${where}.state.firestore[${docPath}]`);
|
|
233
259
|
}
|
|
234
260
|
}
|
|
261
|
+
if (target.state.rtdbInstances !== undefined) {
|
|
262
|
+
requiredRecord(target.state.rtdbInstances, `${where}.state.rtdbInstances`);
|
|
263
|
+
const declaredStateInstances = new Map<string, string>();
|
|
264
|
+
for (const [instance] of Object.entries(target.state.rtdbInstances)) {
|
|
265
|
+
const at = `${where}.state.rtdbInstances[${JSON.stringify(instance)}]`;
|
|
266
|
+
try {
|
|
267
|
+
const resolvedName = resolveDatabaseInstance(instance)!.name;
|
|
268
|
+
if (!declaredRuleInstances.has(resolvedName)) {
|
|
269
|
+
throw new AssuranceInputError(`${at} provides state for instance '${resolvedName}' which has no declared rules`);
|
|
270
|
+
}
|
|
271
|
+
if (declaredStateInstances.has(resolvedName)) {
|
|
272
|
+
throw new AssuranceInputError(`${at} provides state for instance '${resolvedName}' which is already provided by key '${declaredStateInstances.get(resolvedName)}'`);
|
|
273
|
+
}
|
|
274
|
+
declaredStateInstances.set(resolvedName, instance);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
if (e instanceof AssuranceInputError) throw e;
|
|
277
|
+
throw new AssuranceInputError(`${at} names a malformed RTDB instance: ${e instanceof Error ? e.message : String(e)}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
235
282
|
if (target.state.storage !== undefined) {
|
|
236
283
|
if (!Array.isArray(target.state.storage)) {
|
|
237
284
|
throw new AssuranceInputError(
|
|
@@ -367,6 +414,9 @@ function assertOperation(
|
|
|
367
414
|
): asserts operation is FirebaseOperation {
|
|
368
415
|
requiredRecord(operation, where);
|
|
369
416
|
enumValue(operation.service, OPERATION_SERVICES, where, "service");
|
|
417
|
+
if (operation.service === "rtdb" && (operation as any).instance !== undefined) {
|
|
418
|
+
requiredString((operation as any).instance, where, "instance");
|
|
419
|
+
}
|
|
370
420
|
requiredString(operation.path, where, "path");
|
|
371
421
|
enumValue(
|
|
372
422
|
operation.method,
|
|
@@ -507,3 +557,90 @@ export function changedDimensions(
|
|
|
507
557
|
}
|
|
508
558
|
return changed;
|
|
509
559
|
}
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
export function assertCampaign(spec: AuthorizationCampaignSpec): void {
|
|
563
|
+
if (spec.schema !== ASSURANCE_CAMPAIGN_SCHEMA) {
|
|
564
|
+
throw new AssuranceInputError(
|
|
565
|
+
`campaign schema must be '${ASSURANCE_CAMPAIGN_SCHEMA}'.`,
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
if (!spec.id.trim())
|
|
569
|
+
throw new AssuranceInputError("campaign id is required.");
|
|
570
|
+
assertTarget(spec.target);
|
|
571
|
+
if (!Array.isArray(spec.actors)) {
|
|
572
|
+
throw new AssuranceInputError("campaign actors must be an array.");
|
|
573
|
+
}
|
|
574
|
+
if (!Array.isArray(spec.invariants)) {
|
|
575
|
+
throw new AssuranceInputError("campaign invariants must be an array.");
|
|
576
|
+
}
|
|
577
|
+
if (!Array.isArray(spec.probes)) {
|
|
578
|
+
throw new AssuranceInputError("campaign probes must be an array.");
|
|
579
|
+
}
|
|
580
|
+
for (const actor of spec.actors) assertActor(actor);
|
|
581
|
+
for (const invariant of spec.invariants) assertInvariant(invariant);
|
|
582
|
+
for (const probe of spec.probes) assertProbe(probe);
|
|
583
|
+
uniqueIds(spec.actors, "actor");
|
|
584
|
+
uniqueIds(spec.invariants, "invariant");
|
|
585
|
+
uniqueIds(spec.probes, "probe");
|
|
586
|
+
|
|
587
|
+
const declaredRtdbInstances = spec.target.rules.rtdbInstances ? Object.keys(spec.target.rules.rtdbInstances).map(k => resolveDatabaseInstance(k)!.name) : [];
|
|
588
|
+
for (const probe of spec.probes) {
|
|
589
|
+
for (const op of [probe.control, probe.mutation.operation]) {
|
|
590
|
+
// test what happens to unknown fields (unrecognized keys)
|
|
591
|
+
const allowedKeys = new Set(["service", "method", "path", "data", "query", "dataBase64", "contentType", "customMetadata", "instance"]);
|
|
592
|
+
for (const key of Object.keys(op)) {
|
|
593
|
+
if (!allowedKeys.has(key)) {
|
|
594
|
+
throw new AssuranceInputError(`operation names unknown field '${key}'`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (op.service === "rtdb" && op.instance !== undefined && op.instance !== "default") {
|
|
599
|
+
let resolvedName;
|
|
600
|
+
try {
|
|
601
|
+
resolvedName = resolveDatabaseInstance(op.instance)!.name;
|
|
602
|
+
} catch (e) {
|
|
603
|
+
throw new AssuranceInputError(`invalid RTDB instance '${op.instance}': ${e instanceof Error ? e.message : String(e)}`);
|
|
604
|
+
}
|
|
605
|
+
if (!declaredRtdbInstances.includes(resolvedName)) {
|
|
606
|
+
throw new AssuranceInputError(`probe '${probe.id}' names undeclared RTDB instance '${op.instance}'. Declared instances: ${declaredRtdbInstances.length > 0 ? declaredRtdbInstances.join(", ") : "none"}.`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
for (const probe of spec.probes) {
|
|
613
|
+
const actor = spec.actors.find((item) => item.id === probe.actorId);
|
|
614
|
+
const invariant = spec.invariants.find(
|
|
615
|
+
(item) => item.id === probe.invariantId,
|
|
616
|
+
);
|
|
617
|
+
if (!actor)
|
|
618
|
+
throw new AssuranceInputError(
|
|
619
|
+
`probe '${probe.id}' references unknown actor '${probe.actorId}'.`,
|
|
620
|
+
);
|
|
621
|
+
if (!invariant) {
|
|
622
|
+
throw new AssuranceInputError(
|
|
623
|
+
`probe '${probe.id}' references unknown invariant '${probe.invariantId}'.`,
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
if (
|
|
627
|
+
invariant.service !== "cross-service" &&
|
|
628
|
+
invariant.service !== probe.control.service
|
|
629
|
+
) {
|
|
630
|
+
throw new AssuranceInputError(
|
|
631
|
+
`probe '${probe.id}' service does not match invariant '${invariant.id}'.`,
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function uniqueIds(items: ReadonlyArray<{ id: string }>, label: string): void {
|
|
638
|
+
const seen = new Set<string>();
|
|
639
|
+
for (const item of items) {
|
|
640
|
+
if (!item.id.trim())
|
|
641
|
+
throw new AssuranceInputError(`${label} id is required.`);
|
|
642
|
+
if (seen.has(item.id))
|
|
643
|
+
throw new AssuranceInputError(`duplicate ${label} id '${item.id}'.`);
|
|
644
|
+
seen.add(item.id);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
@@ -82,6 +82,7 @@ export const OPERATION_METHOD_VALUES: readonly string[] = [
|
|
|
82
82
|
export const firebaseOperation = z
|
|
83
83
|
.object({
|
|
84
84
|
service: z.enum(OPERATION_SERVICES).describe('The service the operation runs against.'),
|
|
85
|
+
instance: z.string().optional().describe('The named RTDB instance, if the service is rtdb.'),
|
|
85
86
|
method: z
|
|
86
87
|
.enum(OPERATION_METHOD_VALUES as [string, ...string[]])
|
|
87
88
|
.describe('The request method, from the set the named service evaluates.'),
|
|
@@ -236,6 +236,6 @@ export async function runDatabaseRulesGenerate(
|
|
|
236
236
|
await mkdirFn(dirname(resolvedOut), { recursive: true });
|
|
237
237
|
await writeFileFn(resolvedOut, `${JSON.stringify(rulesJson, null, 2)}\n`, 'utf-8');
|
|
238
238
|
|
|
239
|
-
out.write(`pyric database rules generate: wrote ${resolvedOut}\n`);
|
|
239
|
+
out.write(`pyric database rules generate: wrote ${resolvedOut}\n Test before deploy: pyric verify --service rtdb --rules rtdb=${outPath}\n`);
|
|
240
240
|
return 0;
|
|
241
241
|
}
|