@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.0
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/apps/storefront/astro.config.mjs +3 -3
- package/apps/storefront/env.d.ts +54 -5
- package/apps/storefront/integrations/materialize-guard.mjs +12 -14
- package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
- package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
- package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
- package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
- package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
- package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
- package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
- package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
- package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
- package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
- package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
- package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
- package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
- package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
- package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
- package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
- package/apps/storefront/public/shared/commerce-chrome.css +20 -2
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
- package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
- package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
- package/apps/storefront/src/components/Img.astro +1 -1
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
- package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
- package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
- package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
- package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
- package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
- package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
- package/apps/storefront/src/components/membership/TierCards.astro +2 -2
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
- package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
- package/apps/storefront/src/config/devTenantSeed.ts +9 -9
- package/apps/storefront/src/config/resolver.ts +4 -4
- package/apps/storefront/src/layouts/Layout.astro +100 -20
- package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
- package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
- package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
- package/apps/storefront/src/lib/admin/envelope.ts +1 -2
- package/apps/storefront/src/lib/apps/adminService.ts +10 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
- package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
- package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
- package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
- package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
- package/apps/storefront/src/lib/apps/registryService.ts +264 -10
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
- package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
- package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
- package/apps/storefront/src/lib/blog/collection.ts +97 -10
- package/apps/storefront/src/lib/blog/markdown.ts +1 -1
- package/apps/storefront/src/lib/blog/provider.ts +57 -3
- package/apps/storefront/src/lib/blog/types.ts +9 -9
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
- package/apps/storefront/src/lib/chrome/model.ts +48 -253
- package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
- package/apps/storefront/src/lib/chrome/variant.ts +39 -0
- package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
- package/apps/storefront/src/lib/content/index.ts +5 -5
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
- package/apps/storefront/src/lib/content-edit/route.ts +1 -1
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
- package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
- package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
- package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
- package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
- package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
- package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
- package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
- package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
- package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
- package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
- package/apps/storefront/src/lib/edgeCache.ts +18 -6
- package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
- package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
- package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
- package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
- package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
- package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
- package/apps/storefront/src/lib/membership/model.ts +1 -2
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
- package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
- package/apps/storefront/src/lib/newsletter/send.ts +3 -3
- package/apps/storefront/src/lib/previewReload.ts +57 -1
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
- package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
- package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
- package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
- package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
- package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
- package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
- package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
- package/apps/storefront/src/lib/rawChrome.ts +125 -47
- package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
- package/apps/storefront/src/lib/seo/alternates.ts +1 -1
- package/apps/storefront/src/lib/social/notify.ts +4 -4
- package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
- package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
- package/apps/storefront/src/lib/subscription/model.ts +4 -4
- package/apps/storefront/src/lib/the-build/provider.ts +9 -4
- package/apps/storefront/src/middleware/index.ts +273 -51
- package/apps/storefront/src/pages/[...slug].astro +9 -4
- package/apps/storefront/src/pages/admin/review-release.astro +265 -0
- package/apps/storefront/src/pages/admin/select.astro +96 -0
- package/apps/storefront/src/pages/admin.astro +16 -2
- package/apps/storefront/src/pages/api/activity.ts +8 -8
- package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
- package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
- package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
- package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
- package/apps/storefront/src/pages/blog/[slug].astro +5 -5
- package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
- package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
- package/apps/storefront/src/pages/blog/index.astro +2 -2
- package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
- package/apps/storefront/src/pages/cockpit.astro +61 -1362
- package/apps/storefront/src/pages/index.astro +10 -5
- package/apps/storefront/src/pages/llms.txt.ts +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +7 -6
- package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
- package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
- package/apps/storefront/src/pages/the-build/index.astro +2 -2
- package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
- package/apps/storefront/src/styles/admin.css +27 -1
- package/apps/storefront/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/package.json +2 -1
- package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
- package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
- package/packages/public-runtime/src/activity/catalog.ts +41 -37
- package/packages/public-runtime/src/activity/event.ts +6 -6
- package/packages/public-runtime/src/activity/index.ts +2 -2
- package/packages/public-runtime/src/activity/redaction.ts +4 -4
- package/packages/public-runtime/src/candidate-index.ts +42 -8
- package/packages/public-runtime/src/checkout-orders.ts +2 -2
- package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
- package/packages/public-runtime/src/checkout-style.ts +7 -7
- package/packages/public-runtime/src/checkout.ts +3 -3
- package/packages/public-runtime/src/chrome.ts +777 -0
- package/packages/public-runtime/src/compliance-obligations.ts +310 -0
- package/packages/public-runtime/src/csp.ts +99 -0
- package/packages/public-runtime/src/customization-runtime.ts +32 -1
- package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
- package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
- package/packages/public-runtime/src/embed-catalog.mjs +40 -0
- package/packages/public-runtime/src/evidence.ts +288 -0
- package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
- package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
- package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
- package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
- package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
- package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
- package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
- package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
- package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
- package/packages/public-runtime/src/index.ts +16 -1
- package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
- package/packages/public-runtime/src/integration/assurance.ts +228 -0
- package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
- package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
- package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
- package/packages/public-runtime/src/integration/consent-config.ts +260 -0
- package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
- package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
- package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
- package/packages/public-runtime/src/integration/csp.ts +105 -0
- package/packages/public-runtime/src/integration/digest.ts +43 -0
- package/packages/public-runtime/src/integration/environment.ts +228 -0
- package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
- package/packages/public-runtime/src/integration/events.ts +701 -0
- package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
- package/packages/public-runtime/src/integration/index.ts +90 -0
- package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
- package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
- package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
- package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
- package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
- package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
- package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
- package/packages/public-runtime/src/integration/manifest.ts +470 -0
- package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
- package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
- package/packages/public-runtime/src/integration/observability.ts +451 -0
- package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
- package/packages/public-runtime/src/integration/phases.ts +182 -0
- package/packages/public-runtime/src/integration/plan.ts +112 -0
- package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
- package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
- package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
- package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
- package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
- package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
- package/packages/public-runtime/src/integration/render-phases.ts +267 -0
- package/packages/public-runtime/src/integration/resolve.ts +465 -0
- package/packages/public-runtime/src/integration/result.ts +239 -0
- package/packages/public-runtime/src/integration/secrets.ts +92 -0
- package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
- package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
- package/packages/public-runtime/src/integration/version.ts +92 -0
- package/packages/public-runtime/src/integration-capabilities.ts +520 -0
- package/packages/public-runtime/src/membership.ts +2 -2
- package/packages/public-runtime/src/raw-html-policy.ts +2 -4
- package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
- package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
- package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
- package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
- package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
- package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
- package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
- package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
- package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
- package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
- package/packages/public-runtime/src/static-bundle.ts +5 -5
- package/packages/public-runtime/src/tenant-assets.ts +143 -4
- package/packages/public-runtime/src/tenant.ts +45 -9
- package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
- package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
- package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
- package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
- package/scripts/dev/file-browser.mjs +2 -2
- package/scripts/dev/file-writer.mjs +2 -2
- package/scripts/dev/integration-guardrails.mjs +498 -0
- package/scripts/dev/safe-path.mjs +2 -2
- package/scripts/dev/tenant-source-root.mjs +1 -1
- package/scripts/tot-dev.mjs +2 -2
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public control-plane contract for managed analytics integrations.
|
|
3
|
+
*
|
|
4
|
+
* Tenant authority comes from the authenticated server context. Consequently no
|
|
5
|
+
* request in this module accepts a tenant id. The contract is declarative: it can
|
|
6
|
+
* select an approved adapter and provide primitive configuration values, but it
|
|
7
|
+
* cannot carry executable markup/code, selectors, storage instructions, secrets,
|
|
8
|
+
* wildcard origins, legacy Universal Analytics ids, or GTM container ids.
|
|
9
|
+
*/
|
|
10
|
+
import type { ContractResult } from "./result.js";
|
|
11
|
+
import { Violations, isPlainObject } from "./result.js";
|
|
12
|
+
import { isForbiddenKey } from "./secrets.js";
|
|
13
|
+
import {
|
|
14
|
+
MANAGED_ANALYTICS_PII_VALUE_PATTERN,
|
|
15
|
+
MANAGED_ANALYTICS_SECRET_VALUE_PATTERN,
|
|
16
|
+
} from "./managed-analytics-api-v1-safety.mjs";
|
|
17
|
+
|
|
18
|
+
export const MANAGED_ANALYTICS_API_VERSION = "v1" as const;
|
|
19
|
+
export const MANAGED_ANALYTICS_SCHEMA_VERSION = 1 as const;
|
|
20
|
+
|
|
21
|
+
export const INTEGRATION_INSTALL_STATUSES = ["disabled", "enabled"] as const;
|
|
22
|
+
export type IntegrationInstallStatus = (typeof INTEGRATION_INSTALL_STATUSES)[number];
|
|
23
|
+
|
|
24
|
+
export const INTEGRATION_REVISION_STATUSES = ["draft", "active", "superseded"] as const;
|
|
25
|
+
export type IntegrationRevisionStatus = (typeof INTEGRATION_REVISION_STATUSES)[number];
|
|
26
|
+
|
|
27
|
+
export const ADAPTER_AVAILABILITY = ["available", "deprecated"] as const;
|
|
28
|
+
export type AdapterAvailability = (typeof ADAPTER_AVAILABILITY)[number];
|
|
29
|
+
|
|
30
|
+
export const ADAPTER_CONFIG_VALUE_KINDS = ["string", "boolean", "integer"] as const;
|
|
31
|
+
export type AdapterConfigValueKind = (typeof ADAPTER_CONFIG_VALUE_KINDS)[number];
|
|
32
|
+
export type AdapterConfigValue = string | boolean | number;
|
|
33
|
+
/** Public identifiers are opaque handles: never emails, names, or user-provided labels. */
|
|
34
|
+
export type OpaquePublicId = string;
|
|
35
|
+
/** Stable, redacted machine code; migration findings never contain source text or PII. */
|
|
36
|
+
export type MigrationFindingCode = string;
|
|
37
|
+
/** Operator-safe text that must not interpolate request values, secrets, or PII. */
|
|
38
|
+
export type RedactedPublicMessage = string;
|
|
39
|
+
|
|
40
|
+
export interface AdapterConfigField {
|
|
41
|
+
key: string;
|
|
42
|
+
label: string;
|
|
43
|
+
description: string;
|
|
44
|
+
kind: AdapterConfigValueKind;
|
|
45
|
+
required: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AdapterDeprecation {
|
|
49
|
+
deprecatedAt: string;
|
|
50
|
+
sunsetAt: string;
|
|
51
|
+
replacementAdapterId?: OpaquePublicId;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A reviewed catalog entry. It describes configuration; it is not executable. */
|
|
55
|
+
export interface AdapterDefinition {
|
|
56
|
+
schemaVersion: typeof MANAGED_ANALYTICS_SCHEMA_VERSION;
|
|
57
|
+
adapterId: OpaquePublicId;
|
|
58
|
+
adapterVersion: string;
|
|
59
|
+
displayName: string;
|
|
60
|
+
description: string;
|
|
61
|
+
availability: AdapterAvailability;
|
|
62
|
+
configuration: AdapterConfigField[];
|
|
63
|
+
eventTypes: string[];
|
|
64
|
+
consentPurposes: string[];
|
|
65
|
+
/** Exact HTTPS collection origins. Paths, queries, credentials, and wildcards fail validation. */
|
|
66
|
+
collectionOrigins: string[];
|
|
67
|
+
deprecation?: AdapterDeprecation;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface IntegrationInstall {
|
|
71
|
+
schemaVersion: typeof MANAGED_ANALYTICS_SCHEMA_VERSION;
|
|
72
|
+
installId: OpaquePublicId;
|
|
73
|
+
adapterId: OpaquePublicId;
|
|
74
|
+
status: IntegrationInstallStatus;
|
|
75
|
+
activeRevisionId: OpaquePublicId | null;
|
|
76
|
+
/** Monotonic compare-and-swap token for mutations against this install. */
|
|
77
|
+
revision: number;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
updatedAt: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface IntegrationRevisionV1 {
|
|
83
|
+
schemaVersion: typeof MANAGED_ANALYTICS_SCHEMA_VERSION;
|
|
84
|
+
revisionId: OpaquePublicId;
|
|
85
|
+
installId: OpaquePublicId;
|
|
86
|
+
adapterId: OpaquePublicId;
|
|
87
|
+
adapterVersion: string;
|
|
88
|
+
sequence: number;
|
|
89
|
+
status: IntegrationRevisionStatus;
|
|
90
|
+
configuration: Record<string, AdapterConfigValue>;
|
|
91
|
+
createdAt: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* An approved migration suggestion. A migration draft is inert by contract: it
|
|
96
|
+
* cannot enable an install or publish a revision. A separate install mutation is
|
|
97
|
+
* always required after review.
|
|
98
|
+
*/
|
|
99
|
+
export interface MigrationDraftV1 {
|
|
100
|
+
schemaVersion: typeof MANAGED_ANALYTICS_SCHEMA_VERSION;
|
|
101
|
+
draftId: OpaquePublicId;
|
|
102
|
+
/** Monotonic compare-and-swap token for update and delete mutations. */
|
|
103
|
+
revision: number;
|
|
104
|
+
source: "approved-migration";
|
|
105
|
+
status: "draft";
|
|
106
|
+
enabled: false;
|
|
107
|
+
publishes: false;
|
|
108
|
+
proposedAdapterId: OpaquePublicId;
|
|
109
|
+
proposedConfiguration: Record<string, AdapterConfigValue>;
|
|
110
|
+
findings: MigrationFindingCode[];
|
|
111
|
+
createdAt: string;
|
|
112
|
+
updatedAt: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface PageRequest {
|
|
116
|
+
cursor?: OpaquePublicId;
|
|
117
|
+
limit?: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface Page<T> {
|
|
121
|
+
items: T[];
|
|
122
|
+
nextCursor?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Common envelope for reads. `tenantId` is deliberately absent. */
|
|
126
|
+
export interface ReadRequest<TQuery extends object = Record<string, never>> {
|
|
127
|
+
apiVersion: typeof MANAGED_ANALYTICS_API_VERSION;
|
|
128
|
+
query: TQuery;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Common envelope for writes. Every write is idempotency-keyed and CAS-guarded.
|
|
133
|
+
* Reusing a key with the same canonical request replays the original response;
|
|
134
|
+
* reusing it with different content fails with `idempotency_conflict`. A stale
|
|
135
|
+
* `expectedRevision` fails with `stale_revision` and never partially applies.
|
|
136
|
+
*/
|
|
137
|
+
export interface MutationRequest<TBody extends object> {
|
|
138
|
+
apiVersion: typeof MANAGED_ANALYTICS_API_VERSION;
|
|
139
|
+
idempotencyKey: OpaquePublicId;
|
|
140
|
+
expectedRevision: number;
|
|
141
|
+
body: TBody;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ApiSuccess<T> {
|
|
145
|
+
apiVersion: typeof MANAGED_ANALYTICS_API_VERSION;
|
|
146
|
+
requestId: OpaquePublicId;
|
|
147
|
+
data: T;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface MutationSuccess<T> extends ApiSuccess<T> {
|
|
151
|
+
idempotencyReplayed: boolean;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const MANAGED_ANALYTICS_ERROR_CODES = [
|
|
155
|
+
"invalid_request",
|
|
156
|
+
"unauthorized",
|
|
157
|
+
"forbidden",
|
|
158
|
+
"not_found",
|
|
159
|
+
"validation_failed",
|
|
160
|
+
"unsupported_version",
|
|
161
|
+
"stale_revision",
|
|
162
|
+
"idempotency_conflict",
|
|
163
|
+
"adapter_deprecated",
|
|
164
|
+
"conflict",
|
|
165
|
+
"internal_error",
|
|
166
|
+
] as const;
|
|
167
|
+
export type ManagedAnalyticsErrorCode = (typeof MANAGED_ANALYTICS_ERROR_CODES)[number];
|
|
168
|
+
|
|
169
|
+
export interface ApiErrorDetail {
|
|
170
|
+
/** Redacted schema path only; never a raw input value. */
|
|
171
|
+
path: string;
|
|
172
|
+
code: string;
|
|
173
|
+
message: RedactedPublicMessage;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface ApiError {
|
|
177
|
+
code: ManagedAnalyticsErrorCode;
|
|
178
|
+
message: RedactedPublicMessage;
|
|
179
|
+
details?: ApiErrorDetail[];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface ApiErrorEnvelope {
|
|
183
|
+
apiVersion: typeof MANAGED_ANALYTICS_API_VERSION;
|
|
184
|
+
requestId: OpaquePublicId;
|
|
185
|
+
error: ApiError;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type ApiResult<T> = ApiSuccess<T> | ApiErrorEnvelope;
|
|
189
|
+
export type MutationResult<T> = MutationSuccess<T> | ApiErrorEnvelope;
|
|
190
|
+
|
|
191
|
+
export type ListAdapterDefinitionsRequest = ReadRequest<PageRequest>;
|
|
192
|
+
export type ListAdapterDefinitionsResponse = ApiResult<Page<AdapterDefinition>>;
|
|
193
|
+
export type GetAdapterDefinitionRequest = ReadRequest<{ adapterId: OpaquePublicId }>;
|
|
194
|
+
export type GetAdapterDefinitionResponse = ApiResult<AdapterDefinition>;
|
|
195
|
+
export type ListIntegrationInstallsRequest = ReadRequest<PageRequest>;
|
|
196
|
+
export type ListIntegrationInstallsResponse = ApiResult<Page<IntegrationInstall>>;
|
|
197
|
+
export type GetIntegrationInstallRequest = ReadRequest<{ installId: OpaquePublicId }>;
|
|
198
|
+
export type GetIntegrationInstallResponse = ApiResult<IntegrationInstall>;
|
|
199
|
+
export type ListIntegrationRevisionsRequest = ReadRequest<{ installId: OpaquePublicId } & PageRequest>;
|
|
200
|
+
export type ListIntegrationRevisionsResponse = ApiResult<Page<IntegrationRevisionV1>>;
|
|
201
|
+
export type GetIntegrationRevisionRequest = ReadRequest<{ installId: OpaquePublicId; revisionId: OpaquePublicId }>;
|
|
202
|
+
export type GetIntegrationRevisionResponse = ApiResult<IntegrationRevisionV1>;
|
|
203
|
+
export type GetMigrationDraftRequest = ReadRequest<{ draftId: OpaquePublicId }>;
|
|
204
|
+
export type GetMigrationDraftResponse = ApiResult<MigrationDraftV1>;
|
|
205
|
+
|
|
206
|
+
export interface CreateMigrationDraftBody {
|
|
207
|
+
proposedAdapterId: OpaquePublicId;
|
|
208
|
+
proposedConfiguration: Record<string, AdapterConfigValue>;
|
|
209
|
+
/** Stable redacted codes only; raw migration-source text is never public. */
|
|
210
|
+
findings: MigrationFindingCode[];
|
|
211
|
+
}
|
|
212
|
+
export type CreateMigrationDraftRequest = MutationRequest<CreateMigrationDraftBody>;
|
|
213
|
+
export type CreateMigrationDraftResponse = MutationResult<MigrationDraftV1>;
|
|
214
|
+
|
|
215
|
+
export interface UpdateMigrationDraftBody {
|
|
216
|
+
draftId: OpaquePublicId;
|
|
217
|
+
proposedAdapterId: OpaquePublicId;
|
|
218
|
+
proposedConfiguration: Record<string, AdapterConfigValue>;
|
|
219
|
+
/** Stable redacted codes only; raw migration-source text is never public. */
|
|
220
|
+
findings: MigrationFindingCode[];
|
|
221
|
+
}
|
|
222
|
+
export type UpdateMigrationDraftRequest = MutationRequest<UpdateMigrationDraftBody>;
|
|
223
|
+
export type UpdateMigrationDraftResponse = MutationResult<MigrationDraftV1>;
|
|
224
|
+
|
|
225
|
+
export interface DeleteMigrationDraftBody { draftId: OpaquePublicId }
|
|
226
|
+
export type DeleteMigrationDraftRequest = MutationRequest<DeleteMigrationDraftBody>;
|
|
227
|
+
export type DeleteMigrationDraftResponse = MutationResult<{ deleted: true }>;
|
|
228
|
+
|
|
229
|
+
export interface CreateIntegrationInstallBody {
|
|
230
|
+
adapterId: OpaquePublicId;
|
|
231
|
+
adapterVersion: string;
|
|
232
|
+
configuration: Record<string, AdapterConfigValue>;
|
|
233
|
+
/** Creation is reviewed-first; callers cannot create a live install in one step. */
|
|
234
|
+
status: "disabled";
|
|
235
|
+
}
|
|
236
|
+
export type CreateIntegrationInstallRequest = MutationRequest<CreateIntegrationInstallBody>;
|
|
237
|
+
export type CreateIntegrationInstallResponse = MutationResult<{
|
|
238
|
+
install: IntegrationInstall;
|
|
239
|
+
revision: IntegrationRevisionV1;
|
|
240
|
+
}>;
|
|
241
|
+
|
|
242
|
+
export interface ReviseIntegrationInstallBody {
|
|
243
|
+
installId: OpaquePublicId;
|
|
244
|
+
adapterVersion: string;
|
|
245
|
+
configuration: Record<string, AdapterConfigValue>;
|
|
246
|
+
}
|
|
247
|
+
export type ReviseIntegrationInstallRequest = MutationRequest<ReviseIntegrationInstallBody>;
|
|
248
|
+
export type ReviseIntegrationInstallResponse = MutationResult<{
|
|
249
|
+
install: IntegrationInstall;
|
|
250
|
+
revision: IntegrationRevisionV1;
|
|
251
|
+
}>;
|
|
252
|
+
|
|
253
|
+
export interface SetIntegrationInstallStatusBody {
|
|
254
|
+
installId: OpaquePublicId;
|
|
255
|
+
status: IntegrationInstallStatus;
|
|
256
|
+
}
|
|
257
|
+
export type SetIntegrationInstallStatusRequest = MutationRequest<SetIntegrationInstallStatusBody>;
|
|
258
|
+
export type SetIntegrationInstallStatusResponse = MutationResult<IntegrationInstall>;
|
|
259
|
+
|
|
260
|
+
export interface DeleteIntegrationInstallBody { installId: OpaquePublicId }
|
|
261
|
+
export type DeleteIntegrationInstallRequest = MutationRequest<DeleteIntegrationInstallBody>;
|
|
262
|
+
export type DeleteIntegrationInstallResponse = MutationResult<{ deleted: true }>;
|
|
263
|
+
|
|
264
|
+
/** The only backend seam WS2 needs; tests can mock this interface without HTTP. */
|
|
265
|
+
export interface ManagedAnalyticsApiV1Client {
|
|
266
|
+
listAdapterDefinitions(request: ListAdapterDefinitionsRequest): Promise<ListAdapterDefinitionsResponse>;
|
|
267
|
+
getAdapterDefinition(request: GetAdapterDefinitionRequest): Promise<GetAdapterDefinitionResponse>;
|
|
268
|
+
listIntegrationInstalls(request: ListIntegrationInstallsRequest): Promise<ListIntegrationInstallsResponse>;
|
|
269
|
+
getIntegrationInstall(request: GetIntegrationInstallRequest): Promise<GetIntegrationInstallResponse>;
|
|
270
|
+
listIntegrationRevisions(request: ListIntegrationRevisionsRequest): Promise<ListIntegrationRevisionsResponse>;
|
|
271
|
+
getIntegrationRevision(request: GetIntegrationRevisionRequest): Promise<GetIntegrationRevisionResponse>;
|
|
272
|
+
getMigrationDraft(request: GetMigrationDraftRequest): Promise<GetMigrationDraftResponse>;
|
|
273
|
+
createMigrationDraft(request: CreateMigrationDraftRequest): Promise<CreateMigrationDraftResponse>;
|
|
274
|
+
updateMigrationDraft(request: UpdateMigrationDraftRequest): Promise<UpdateMigrationDraftResponse>;
|
|
275
|
+
deleteMigrationDraft(request: DeleteMigrationDraftRequest): Promise<DeleteMigrationDraftResponse>;
|
|
276
|
+
createIntegrationInstall(request: CreateIntegrationInstallRequest): Promise<CreateIntegrationInstallResponse>;
|
|
277
|
+
reviseIntegrationInstall(request: ReviseIntegrationInstallRequest): Promise<ReviseIntegrationInstallResponse>;
|
|
278
|
+
setIntegrationInstallStatus(request: SetIntegrationInstallStatusRequest): Promise<SetIntegrationInstallStatusResponse>;
|
|
279
|
+
deleteIntegrationInstall(request: DeleteIntegrationInstallRequest): Promise<DeleteIntegrationInstallResponse>;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export const MANAGED_ANALYTICS_API_V1_VERSION_POLICY = Object.freeze({
|
|
283
|
+
path: "/api/integrations/v1",
|
|
284
|
+
breakingChanges: "new-major-path",
|
|
285
|
+
additiveChanges: "new-operations-only; resource-shape-changes-bump-schemaVersion",
|
|
286
|
+
unknownRequestFields: "reject",
|
|
287
|
+
deprecatedAdapter: Object.freeze({
|
|
288
|
+
newInstalls: "reject-after-deprecatedAt",
|
|
289
|
+
existingInstalls: "readable-until-sunsetAt",
|
|
290
|
+
sunsetMinimumDays: 90,
|
|
291
|
+
}),
|
|
292
|
+
} as const);
|
|
293
|
+
|
|
294
|
+
const FORBIDDEN_KEY_WORDS = [
|
|
295
|
+
"tenant", "html", "javascript", "js", "script", "template", "eval", "selector",
|
|
296
|
+
"storage", "cookie", "secret", "token", "credential", "password", "gtm",
|
|
297
|
+
] as const;
|
|
298
|
+
const FORBIDDEN_KEY_FORMS = [
|
|
299
|
+
"tenantid", "privatekey", "apikey", "liveauthority", "publishauthority", "uaid",
|
|
300
|
+
"localstorage", "sessionstorage",
|
|
301
|
+
] as const;
|
|
302
|
+
const PUBLIC_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
303
|
+
const FINDING_CODE = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;
|
|
304
|
+
const REDACTED_PATH = /^(?:\$|[A-Za-z][A-Za-z0-9]*)(?:\.[A-Za-z][A-Za-z0-9]*|\[[0-9]+\])*$/;
|
|
305
|
+
const GTM_OR_UA_ID = /(?:GTM-[A-Z0-9]+|UA-\d+-\d+)/i;
|
|
306
|
+
const PII_VALUE = new RegExp(MANAGED_ANALYTICS_PII_VALUE_PATTERN);
|
|
307
|
+
const SECRET_VALUE = new RegExp(MANAGED_ANALYTICS_SECRET_VALUE_PATTERN);
|
|
308
|
+
const PROTOTYPE_PATH_KEYS = new Set(["constructor", "prototype", "__proto__"]);
|
|
309
|
+
const SECRET_PATH_KEY_HINT = /^(?:AKIA|AIza|SG|eyJ|(?:sk|rk)(?:Live|Test|Prod)|gh[pousr]|githubPat|npm|glpat|xox|secret|password|credential|apiKey|clientSecret|privateKey)/i;
|
|
310
|
+
const EXECUTABLE_OR_INSTRUCTION_VALUE =
|
|
311
|
+
/(?:<\/?[a-z][^>]*>|javascript\s*:|\beval\s*\(|\b(?:alert|fetch)\s*\(|\{\{|\}\}|\$\{|#[a-z_][\w-]*|(?:^|[\s,>+~])\.[a-z_-][\w-]*(?:$|[\s,>+~.#[:])|\[[a-z_:][-a-z0-9_:.]*(?:[~|^$*]?=[^\]]+)?\]|\b(?:local|session)Storage\b|\bindexedDB\b|\bquerySelector\b|\b(?:window|document|globalThis|console)\s*(?:\.|\[)|\b[a-z_$][\w$]*\s*\.\s*[a-z_$][\w$]*\s*\(|\b[a-z_$][\w$]*\s*\([^)]*\)\s*;|\breturn\b[^;{}]*;|\bfunction\b|=>|\b(?:var|let|const)\s+[a-z_$])/i;
|
|
312
|
+
|
|
313
|
+
function redactedPathKey(key: string): string | undefined {
|
|
314
|
+
if (!/^[A-Za-z][A-Za-z0-9]*$/.test(key) || PROTOTYPE_PATH_KEYS.has(key) ||
|
|
315
|
+
Object.prototype.hasOwnProperty.call(Object.prototype, key)) return undefined;
|
|
316
|
+
return SECRET_PATH_KEY_HINT.test(key) ? undefined : key;
|
|
317
|
+
}
|
|
318
|
+
function pathAt(path: string, key: string): string {
|
|
319
|
+
const safe = redactedPathKey(key);
|
|
320
|
+
if (!safe) return path || "$";
|
|
321
|
+
return path && path !== "$" ? `${path}.${safe}` : path === "$" ? `$.${safe}` : safe;
|
|
322
|
+
}
|
|
323
|
+
function indexAt(path: string, index: string | number): string { return `${path || "$"}[${index}]`; }
|
|
324
|
+
function keyWords(key: string): string[] {
|
|
325
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9]+/g, " ").toLowerCase().split(/\s+/).filter(Boolean);
|
|
326
|
+
}
|
|
327
|
+
function isForbiddenKeyName(key: string): boolean {
|
|
328
|
+
const tokens = keyWords(key);
|
|
329
|
+
const collapsed = tokens.join("");
|
|
330
|
+
return isForbiddenKey(key) || tokens.some((token) => FORBIDDEN_KEY_WORDS.includes(token as (typeof FORBIDDEN_KEY_WORDS)[number])) || FORBIDDEN_KEY_FORMS.includes(collapsed as (typeof FORBIDDEN_KEY_FORMS)[number]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
class HostileManagedAnalyticsInput extends Error {
|
|
334
|
+
constructor(readonly redactedPath: string) { super("managed_analytics_input_unreadable"); }
|
|
335
|
+
}
|
|
336
|
+
const MANAGED_ANALYTICS_SNAPSHOTS = new WeakSet<object>();
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Detach public input without invoking getters or ordinary property reads. A
|
|
340
|
+
* proxy can still trap the reflection operations themselves, so each object is
|
|
341
|
+
* asked for its key set once and each descriptor once; any trap failure is
|
|
342
|
+
* collapsed to one stable, redacted boundary violation.
|
|
343
|
+
*/
|
|
344
|
+
export function snapshotManagedAnalyticsDeclarativeInput<T>(input: unknown): ContractResult<T> {
|
|
345
|
+
const copies = new WeakMap<object, object>();
|
|
346
|
+
const active = new WeakSet<object>();
|
|
347
|
+
const fail = (path: string): never => { throw new HostileManagedAnalyticsInput(path && REDACTED_PATH.test(path) ? path : "$"); };
|
|
348
|
+
const copy = (value: unknown, path: string): unknown => {
|
|
349
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
350
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : fail(path);
|
|
351
|
+
if (typeof value !== "object") return fail(path);
|
|
352
|
+
if (MANAGED_ANALYTICS_SNAPSHOTS.has(value)) return value;
|
|
353
|
+
if (active.has(value)) return fail(path);
|
|
354
|
+
const prior = copies.get(value);
|
|
355
|
+
if (prior) return prior;
|
|
356
|
+
|
|
357
|
+
let array: boolean;
|
|
358
|
+
let keys: readonly PropertyKey[];
|
|
359
|
+
let prototype: object | null;
|
|
360
|
+
try {
|
|
361
|
+
array = Array.isArray(value);
|
|
362
|
+
keys = Reflect.ownKeys(value);
|
|
363
|
+
prototype = Reflect.getPrototypeOf(value);
|
|
364
|
+
} catch {
|
|
365
|
+
return fail(path);
|
|
366
|
+
}
|
|
367
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) return fail(path);
|
|
368
|
+
const output: Record<string, unknown> | unknown[] = array ? [] : Object.create(null) as Record<string, unknown>;
|
|
369
|
+
let arrayLength: number | undefined;
|
|
370
|
+
let arrayIndexes = 0;
|
|
371
|
+
let maximumArrayIndex = -1;
|
|
372
|
+
copies.set(value, output);
|
|
373
|
+
active.add(value);
|
|
374
|
+
try {
|
|
375
|
+
for (const key of keys) {
|
|
376
|
+
let descriptor: PropertyDescriptor | undefined;
|
|
377
|
+
try { descriptor = Reflect.getOwnPropertyDescriptor(value, key); }
|
|
378
|
+
catch { return fail(path); }
|
|
379
|
+
if (!descriptor) return fail(path);
|
|
380
|
+
if (array && key === "length") {
|
|
381
|
+
if (!("value" in descriptor) || typeof descriptor.value !== "number" ||
|
|
382
|
+
!Number.isInteger(descriptor.value) || descriptor.value < 0 || descriptor.value > 0xffff_ffff) return fail(path);
|
|
383
|
+
arrayLength = descriptor.value;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (!descriptor.enumerable) continue;
|
|
387
|
+
if (typeof key !== "string" || !("value" in descriptor)) return fail(typeof key === "string" ? pathAt(path, key) : path);
|
|
388
|
+
if (array) {
|
|
389
|
+
const index = Number(key);
|
|
390
|
+
if (!/^(?:0|[1-9]\d*)$/.test(key) || !Number.isSafeInteger(index) || index >= 0xffff_ffff) return fail(pathAt(path, key));
|
|
391
|
+
arrayIndexes += 1;
|
|
392
|
+
maximumArrayIndex = Math.max(maximumArrayIndex, index);
|
|
393
|
+
}
|
|
394
|
+
const child = copy(descriptor.value, array ? indexAt(path, key) : pathAt(path, key));
|
|
395
|
+
if (array) (output as unknown[])[Number(key)] = child;
|
|
396
|
+
else Object.defineProperty(output, key, { value: child, enumerable: true, writable: false, configurable: false });
|
|
397
|
+
}
|
|
398
|
+
if (array && (arrayLength === undefined || arrayIndexes !== arrayLength || maximumArrayIndex >= arrayLength)) return fail(path);
|
|
399
|
+
const frozen = Object.freeze(output);
|
|
400
|
+
MANAGED_ANALYTICS_SNAPSHOTS.add(frozen);
|
|
401
|
+
return frozen;
|
|
402
|
+
} finally {
|
|
403
|
+
active.delete(value);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
try { return { ok: true, value: copy(input, "") as T }; }
|
|
408
|
+
catch (cause) {
|
|
409
|
+
const path = cause instanceof HostileManagedAnalyticsInput ? cause.redactedPath : "$";
|
|
410
|
+
return { ok: false, violations: [{ path, code: "hostile_object", message: "Input could not be safely read as declarative data." }] };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function rejectKeys(obj: Record<string, unknown>, allowed: readonly string[], v: Violations, path: string): void {
|
|
415
|
+
for (const key of Object.keys(obj)) {
|
|
416
|
+
if (!allowed.includes(key)) v.add(pathAt(path, key), "unknown_field", "Unknown field; this contract is closed.");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function object(value: unknown, v: Violations, path: string): Record<string, unknown> | undefined {
|
|
420
|
+
if (!isPlainObject(value)) { v.add(path || "$", "type", "Expected an object."); return undefined; }
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
function string(obj: Record<string, unknown>, key: string, v: Violations, path: string): string | undefined {
|
|
424
|
+
const value = obj[key];
|
|
425
|
+
if (typeof value !== "string" || value.length === 0) { v.add(pathAt(path, key), "type", "Expected a non-empty string."); return undefined; }
|
|
426
|
+
return value;
|
|
427
|
+
}
|
|
428
|
+
function integer(obj: Record<string, unknown>, key: string, v: Violations, path: string, minimum = 0): number | undefined {
|
|
429
|
+
const value = obj[key];
|
|
430
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < minimum) { v.add(pathAt(path, key), "type", `Expected an integer >= ${minimum}.`); return undefined; }
|
|
431
|
+
return value;
|
|
432
|
+
}
|
|
433
|
+
function timestamp(obj: Record<string, unknown>, key: string, v: Violations, path: string): string | undefined {
|
|
434
|
+
const value = string(obj, key, v, path);
|
|
435
|
+
if (value !== undefined && (!/^\d{4}-\d{2}-\d{2}T/.test(value) || Number.isNaN(Date.parse(value)))) v.add(pathAt(path, key), "format", "Expected an ISO-8601 timestamp.");
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
function literal(obj: Record<string, unknown>, key: string, expected: unknown, v: Violations, path: string): void {
|
|
439
|
+
if (obj[key] !== expected) v.add(pathAt(path, key), "unsupported_value", `Expected ${JSON.stringify(expected)}.`);
|
|
440
|
+
}
|
|
441
|
+
function stringArray(obj: Record<string, unknown>, key: string, v: Violations, path: string): string[] | undefined {
|
|
442
|
+
const value = obj[key];
|
|
443
|
+
if (!Array.isArray(value)) { v.add(pathAt(path, key), "type", "Expected an array."); return undefined; }
|
|
444
|
+
const out: string[] = [];
|
|
445
|
+
value.forEach((item, i) => typeof item === "string" && item.length > 0 ? out.push(item) : v.add(`${pathAt(path, key)}[${i}]`, "type", "Expected a non-empty string."));
|
|
446
|
+
return out;
|
|
447
|
+
}
|
|
448
|
+
function enumeration(obj: Record<string, unknown>, key: string, values: readonly string[], v: Violations, path: string): string | undefined {
|
|
449
|
+
const value = string(obj, key, v, path);
|
|
450
|
+
if (value !== undefined && !values.includes(value)) { v.add(pathAt(path, key), "unknown_enum", `Expected one of: ${values.join(", ")}.`); return undefined; }
|
|
451
|
+
return value;
|
|
452
|
+
}
|
|
453
|
+
function opaqueId(obj: Record<string, unknown>, key: string, v: Violations, path: string): string | undefined {
|
|
454
|
+
const value = string(obj, key, v, path);
|
|
455
|
+
if (value !== undefined && !PUBLIC_ID.test(value)) { v.add(pathAt(path, key), "opaque_id", "Expected an opaque public identifier."); return undefined; }
|
|
456
|
+
return value;
|
|
457
|
+
}
|
|
458
|
+
function findingCodes(obj: Record<string, unknown>, key: string, v: Violations, path: string): string[] | undefined {
|
|
459
|
+
const values = stringArray(obj, key, v, path);
|
|
460
|
+
values?.forEach((value, i) => { if (!FINDING_CODE.test(value)) v.add(`${pathAt(path, key)}[${i}]`, "redacted_code", "Expected a stable redacted finding code."); });
|
|
461
|
+
return values;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Defense-in-depth for every declarative request/resource payload. */
|
|
465
|
+
export function validateManagedAnalyticsSafePayload(input: unknown): ContractResult<unknown> {
|
|
466
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<unknown>(input);
|
|
467
|
+
if (!prepared.ok) return prepared;
|
|
468
|
+
const v = new Violations();
|
|
469
|
+
const seen = new Set<unknown>();
|
|
470
|
+
const walk = (value: unknown, path: string): void => {
|
|
471
|
+
if (typeof value === "string" && GTM_OR_UA_ID.test(value.trim())) v.add(path || "$", "legacy_execution_id", "GTM container ids and Universal Analytics ids are not accepted by managed analytics.");
|
|
472
|
+
if (typeof value === "string" && SECRET_VALUE.test(value)) v.add(path || "$", "secret_value", "Secret-shaped values are not accepted by managed analytics.");
|
|
473
|
+
if (typeof value === "string" && PII_VALUE.test(value)) v.add(path || "$", "pii_value", "PII-shaped values are not accepted by managed analytics.");
|
|
474
|
+
if (typeof value === "string" && EXECUTABLE_OR_INSTRUCTION_VALUE.test(value)) v.add(path || "$", "executable_value", "Executable markup, code, templates, selectors, and browser storage instructions are not accepted by managed analytics.");
|
|
475
|
+
if (Array.isArray(value)) { value.forEach((item, i) => walk(item, indexAt(path, i))); return; }
|
|
476
|
+
if (!isPlainObject(value) || seen.has(value)) return;
|
|
477
|
+
seen.add(value);
|
|
478
|
+
for (const [key, child] of Object.entries(value)) {
|
|
479
|
+
if (isForbiddenKeyName(key)) v.add(pathAt(path, key), "forbidden_field", "A field names data outside the declarative managed-analytics contract.");
|
|
480
|
+
walk(child, pathAt(path, key));
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
walk(prepared.value, "");
|
|
484
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function validateConfig(input: unknown, v: Violations, path: string): void {
|
|
488
|
+
const obj = object(input, v, path);
|
|
489
|
+
if (!obj) return;
|
|
490
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
491
|
+
if (!/^[a-z][a-zA-Z0-9]{0,63}$/.test(key)) v.add(pathAt(path, key), "format", "Configuration keys must be lower camel-case identifiers.");
|
|
492
|
+
if (!(typeof value === "string" || typeof value === "boolean" || (typeof value === "number" && Number.isInteger(value)))) v.add(pathAt(path, key), "type", "Configuration values must be strings, booleans, or integers.");
|
|
493
|
+
if (typeof value === "string" && EXECUTABLE_OR_INSTRUCTION_VALUE.test(value)) v.add(pathAt(path, key), "executable_value", "Configuration cannot contain markup, JavaScript, templates, selectors, or storage instructions.");
|
|
494
|
+
}
|
|
495
|
+
const safe = validateManagedAnalyticsSafePayload(obj);
|
|
496
|
+
if (!safe.ok) for (const item of safe.violations) v.items.push({ ...item, path: pathAt(path, item.path) });
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function validateExactOrigin(value: string, v: Violations, path: string): void {
|
|
500
|
+
if (value.includes("*")) { v.add(path, "wildcard_origin", "Wildcard origins are forbidden."); return; }
|
|
501
|
+
try {
|
|
502
|
+
const url = new URL(value);
|
|
503
|
+
if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash || url.origin !== value.replace(/\/$/, "")) v.add(path, "origin", "Expected an exact HTTPS origin with no path, query, fragment, or credentials.");
|
|
504
|
+
} catch { v.add(path, "origin", "Expected an exact HTTPS origin."); }
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export function validateAdapterDefinition(input: unknown): ContractResult<AdapterDefinition> {
|
|
508
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<AdapterDefinition>(input); if (!prepared.ok) return prepared;
|
|
509
|
+
const v = new Violations(); const obj = object(prepared.value, v, "");
|
|
510
|
+
if (!obj) return { ok: false, violations: v.items };
|
|
511
|
+
rejectKeys(obj, ["schemaVersion", "adapterId", "adapterVersion", "displayName", "description", "availability", "configuration", "eventTypes", "consentPurposes", "collectionOrigins", "deprecation"], v, "");
|
|
512
|
+
literal(obj, "schemaVersion", 1, v, ""); opaqueId(obj, "adapterId", v, ""); opaqueId(obj, "adapterVersion", v, ""); string(obj, "displayName", v, ""); string(obj, "description", v, "");
|
|
513
|
+
const availability = enumeration(obj, "availability", ADAPTER_AVAILABILITY, v, "");
|
|
514
|
+
const fields = obj.configuration;
|
|
515
|
+
if (!Array.isArray(fields)) v.add("configuration", "type", "Expected an array.");
|
|
516
|
+
else fields.forEach((field, i) => {
|
|
517
|
+
const path = `configuration[${i}]`; const f = object(field, v, path); if (!f) return;
|
|
518
|
+
rejectKeys(f, ["key", "label", "description", "kind", "required"], v, path);
|
|
519
|
+
const key = string(f, "key", v, path); if (key && (!/^[a-z][a-zA-Z0-9]{0,63}$/.test(key) || isForbiddenKeyName(key))) v.add(`${path}.key`, "forbidden_field", "Configuration field names must be safe lower camel-case identifiers.");
|
|
520
|
+
string(f, "label", v, path); string(f, "description", v, path); enumeration(f, "kind", ADAPTER_CONFIG_VALUE_KINDS, v, path);
|
|
521
|
+
if (typeof f.required !== "boolean") v.add(`${path}.required`, "type", "Expected a boolean.");
|
|
522
|
+
});
|
|
523
|
+
if (Array.isArray(fields)) {
|
|
524
|
+
const keys = fields.flatMap((field) => isPlainObject(field) && typeof field.key === "string" ? [field.key] : []);
|
|
525
|
+
if (new Set(keys).size !== keys.length) v.add("configuration", "duplicate", "Configuration field keys must be unique.");
|
|
526
|
+
}
|
|
527
|
+
const events = stringArray(obj, "eventTypes", v, ""); if (events && new Set(events).size !== events.length) v.add("eventTypes", "duplicate", "Event types must be unique.");
|
|
528
|
+
const purposes = stringArray(obj, "consentPurposes", v, ""); if (purposes && new Set(purposes).size !== purposes.length) v.add("consentPurposes", "duplicate", "Consent purposes must be unique.");
|
|
529
|
+
const origins = stringArray(obj, "collectionOrigins", v, ""); origins?.forEach((origin, i) => validateExactOrigin(origin, v, `collectionOrigins[${i}]`));
|
|
530
|
+
if (origins && new Set(origins).size !== origins.length) v.add("collectionOrigins", "duplicate", "Collection origins must be unique.");
|
|
531
|
+
if (availability === "available" && obj.deprecation !== undefined) v.add("deprecation", "state", "Available adapters cannot carry deprecation metadata.");
|
|
532
|
+
if (availability === "deprecated" || obj.deprecation !== undefined) {
|
|
533
|
+
const d = object(obj.deprecation, v, "deprecation");
|
|
534
|
+
if (d) { rejectKeys(d, ["deprecatedAt", "sunsetAt", "replacementAdapterId"], v, "deprecation"); const deprecatedAt = timestamp(d, "deprecatedAt", v, "deprecation"); const sunsetAt = timestamp(d, "sunsetAt", v, "deprecation"); if (d.replacementAdapterId !== undefined) string(d, "replacementAdapterId", v, "deprecation"); if (deprecatedAt && sunsetAt && Date.parse(sunsetAt) - Date.parse(deprecatedAt) < 90 * 86_400_000) v.add("deprecation.sunsetAt", "deprecation_window", "Adapter sunset must be at least 90 days after deprecation."); }
|
|
535
|
+
}
|
|
536
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
537
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export function validateIntegrationInstall(input: unknown): ContractResult<IntegrationInstall> {
|
|
541
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<IntegrationInstall>(input); if (!prepared.ok) return prepared;
|
|
542
|
+
const v = new Violations(); const obj = object(prepared.value, v, ""); if (!obj) return { ok: false, violations: v.items };
|
|
543
|
+
rejectKeys(obj, ["schemaVersion", "installId", "adapterId", "status", "activeRevisionId", "revision", "createdAt", "updatedAt"], v, "");
|
|
544
|
+
literal(obj, "schemaVersion", 1, v, ""); opaqueId(obj, "installId", v, ""); opaqueId(obj, "adapterId", v, ""); enumeration(obj, "status", INTEGRATION_INSTALL_STATUSES, v, "");
|
|
545
|
+
if (!(obj.activeRevisionId === null || (typeof obj.activeRevisionId === "string" && PUBLIC_ID.test(obj.activeRevisionId)))) v.add("activeRevisionId", "opaque_id", "Expected an opaque public identifier or null.");
|
|
546
|
+
integer(obj, "revision", v, "", 0); timestamp(obj, "createdAt", v, ""); timestamp(obj, "updatedAt", v, "");
|
|
547
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
548
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export function validateIntegrationRevisionV1(input: unknown): ContractResult<IntegrationRevisionV1> {
|
|
552
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<IntegrationRevisionV1>(input); if (!prepared.ok) return prepared;
|
|
553
|
+
const v = new Violations(); const obj = object(prepared.value, v, ""); if (!obj) return { ok: false, violations: v.items };
|
|
554
|
+
rejectKeys(obj, ["schemaVersion", "revisionId", "installId", "adapterId", "adapterVersion", "sequence", "status", "configuration", "createdAt"], v, "");
|
|
555
|
+
literal(obj, "schemaVersion", 1, v, ""); opaqueId(obj, "revisionId", v, ""); opaqueId(obj, "installId", v, ""); opaqueId(obj, "adapterId", v, ""); opaqueId(obj, "adapterVersion", v, ""); integer(obj, "sequence", v, "", 1); enumeration(obj, "status", INTEGRATION_REVISION_STATUSES, v, ""); validateConfig(obj.configuration, v, "configuration"); timestamp(obj, "createdAt", v, "");
|
|
556
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
557
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function validateMigrationDraftV1(input: unknown): ContractResult<MigrationDraftV1> {
|
|
561
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<MigrationDraftV1>(input); if (!prepared.ok) return prepared;
|
|
562
|
+
const v = new Violations(); const obj = object(prepared.value, v, ""); if (!obj) return { ok: false, violations: v.items };
|
|
563
|
+
rejectKeys(obj, ["schemaVersion", "draftId", "revision", "source", "status", "enabled", "publishes", "proposedAdapterId", "proposedConfiguration", "findings", "createdAt", "updatedAt"], v, "");
|
|
564
|
+
literal(obj, "schemaVersion", 1, v, ""); opaqueId(obj, "draftId", v, ""); integer(obj, "revision", v, "", 0); literal(obj, "source", "approved-migration", v, ""); literal(obj, "status", "draft", v, ""); literal(obj, "enabled", false, v, ""); literal(obj, "publishes", false, v, ""); opaqueId(obj, "proposedAdapterId", v, ""); validateConfig(obj.proposedConfiguration, v, "proposedConfiguration"); findingCodes(obj, "findings", v, ""); timestamp(obj, "createdAt", v, ""); timestamp(obj, "updatedAt", v, "");
|
|
565
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
566
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export function validateReadRequest(input: unknown): ContractResult<ReadRequest<Record<string, unknown>>> {
|
|
570
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<ReadRequest<Record<string, unknown>>>(input); if (!prepared.ok) return prepared;
|
|
571
|
+
const v = new Violations(); const obj = object(prepared.value, v, ""); if (!obj) return { ok: false, violations: v.items };
|
|
572
|
+
rejectKeys(obj, ["apiVersion", "query"], v, ""); literal(obj, "apiVersion", "v1", v, ""); object(obj.query, v, "query");
|
|
573
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
574
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export function validateMutationRequest(input: unknown): ContractResult<MutationRequest<Record<string, unknown>>> {
|
|
578
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<MutationRequest<Record<string, unknown>>>(input); if (!prepared.ok) return prepared;
|
|
579
|
+
const v = new Violations(); const obj = object(prepared.value, v, ""); if (!obj) return { ok: false, violations: v.items };
|
|
580
|
+
rejectKeys(obj, ["apiVersion", "idempotencyKey", "expectedRevision", "body"], v, ""); literal(obj, "apiVersion", "v1", v, "");
|
|
581
|
+
const key = string(obj, "idempotencyKey", v, ""); if (key && (key.length < 8 || key.length > 128 || !PUBLIC_ID.test(key))) v.add("idempotencyKey", "opaque_id", "Idempotency keys must be opaque and contain 8–128 characters.");
|
|
582
|
+
integer(obj, "expectedRevision", v, "", 0); object(obj.body, v, "body");
|
|
583
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
584
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
type ObjectShapeValidator = (obj: Record<string, unknown>, v: Violations, path: string) => void;
|
|
588
|
+
|
|
589
|
+
function validateReadOperation<T>(input: unknown, validateQuery: ObjectShapeValidator): ContractResult<T> {
|
|
590
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<T>(input); if (!prepared.ok) return prepared;
|
|
591
|
+
const v = new Violations(); const obj = object(prepared.value, v, "");
|
|
592
|
+
if (!obj) return { ok: false, violations: v.items };
|
|
593
|
+
rejectKeys(obj, ["apiVersion", "query"], v, ""); literal(obj, "apiVersion", "v1", v, "");
|
|
594
|
+
const query = object(obj.query, v, "query"); if (query) validateQuery(query, v, "query");
|
|
595
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
596
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function validateMutationOperation<T>(input: unknown, validateBody: ObjectShapeValidator): ContractResult<T> {
|
|
600
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<T>(input); if (!prepared.ok) return prepared;
|
|
601
|
+
const v = new Violations(); const obj = object(prepared.value, v, "");
|
|
602
|
+
if (!obj) return { ok: false, violations: v.items };
|
|
603
|
+
rejectKeys(obj, ["apiVersion", "idempotencyKey", "expectedRevision", "body"], v, ""); literal(obj, "apiVersion", "v1", v, "");
|
|
604
|
+
const key = string(obj, "idempotencyKey", v, ""); if (key && (key.length < 8 || key.length > 128 || !PUBLIC_ID.test(key))) v.add("idempotencyKey", "opaque_id", "Idempotency keys must be opaque and contain 8–128 characters.");
|
|
605
|
+
integer(obj, "expectedRevision", v, "", 0);
|
|
606
|
+
const body = object(obj.body, v, "body"); if (body) validateBody(body, v, "body");
|
|
607
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
608
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function validatePageShape(obj: Record<string, unknown>, v: Violations, path: string, required: readonly string[] = []): void {
|
|
612
|
+
rejectKeys(obj, [...required, "cursor", "limit"], v, path);
|
|
613
|
+
for (const key of required) opaqueId(obj, key, v, path);
|
|
614
|
+
if ("cursor" in obj) opaqueId(obj, "cursor", v, path);
|
|
615
|
+
if ("limit" in obj) integer(obj, "limit", v, path, 1);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const validateAdapterIdQuery: ObjectShapeValidator = (obj, v, path) => { rejectKeys(obj, ["adapterId"], v, path); opaqueId(obj, "adapterId", v, path); };
|
|
619
|
+
const validateInstallIdQuery: ObjectShapeValidator = (obj, v, path) => { rejectKeys(obj, ["installId"], v, path); opaqueId(obj, "installId", v, path); };
|
|
620
|
+
const validateRevisionQuery: ObjectShapeValidator = (obj, v, path) => { rejectKeys(obj, ["installId", "revisionId"], v, path); opaqueId(obj, "installId", v, path); opaqueId(obj, "revisionId", v, path); };
|
|
621
|
+
const validateDraftIdQuery: ObjectShapeValidator = (obj, v, path) => { rejectKeys(obj, ["draftId"], v, path); opaqueId(obj, "draftId", v, path); };
|
|
622
|
+
|
|
623
|
+
export function validateListAdapterDefinitionsRequest(input: unknown): ContractResult<ListAdapterDefinitionsRequest> { return validateReadOperation(input, validatePageShape); }
|
|
624
|
+
export function validateGetAdapterDefinitionRequest(input: unknown): ContractResult<GetAdapterDefinitionRequest> { return validateReadOperation(input, validateAdapterIdQuery); }
|
|
625
|
+
export function validateListIntegrationInstallsRequest(input: unknown): ContractResult<ListIntegrationInstallsRequest> { return validateReadOperation(input, validatePageShape); }
|
|
626
|
+
export function validateGetIntegrationInstallRequest(input: unknown): ContractResult<GetIntegrationInstallRequest> { return validateReadOperation(input, validateInstallIdQuery); }
|
|
627
|
+
export function validateListIntegrationRevisionsRequest(input: unknown): ContractResult<ListIntegrationRevisionsRequest> { return validateReadOperation(input, (obj, v, path) => validatePageShape(obj, v, path, ["installId"])); }
|
|
628
|
+
export function validateGetIntegrationRevisionRequest(input: unknown): ContractResult<GetIntegrationRevisionRequest> { return validateReadOperation(input, validateRevisionQuery); }
|
|
629
|
+
export function validateGetMigrationDraftRequest(input: unknown): ContractResult<GetMigrationDraftRequest> { return validateReadOperation(input, validateDraftIdQuery); }
|
|
630
|
+
|
|
631
|
+
const validateCreateMigrationDraftBody: ObjectShapeValidator = (obj, v, path) => {
|
|
632
|
+
rejectKeys(obj, ["proposedAdapterId", "proposedConfiguration", "findings"], v, path);
|
|
633
|
+
opaqueId(obj, "proposedAdapterId", v, path); validateConfig(obj.proposedConfiguration, v, `${path}.proposedConfiguration`); findingCodes(obj, "findings", v, path);
|
|
634
|
+
};
|
|
635
|
+
const validateUpdateMigrationDraftBody: ObjectShapeValidator = (obj, v, path) => {
|
|
636
|
+
rejectKeys(obj, ["draftId", "proposedAdapterId", "proposedConfiguration", "findings"], v, path);
|
|
637
|
+
opaqueId(obj, "draftId", v, path); opaqueId(obj, "proposedAdapterId", v, path); validateConfig(obj.proposedConfiguration, v, `${path}.proposedConfiguration`); findingCodes(obj, "findings", v, path);
|
|
638
|
+
};
|
|
639
|
+
const validateDeleteMigrationDraftBody: ObjectShapeValidator = (obj, v, path) => { rejectKeys(obj, ["draftId"], v, path); opaqueId(obj, "draftId", v, path); };
|
|
640
|
+
const validateCreateIntegrationInstallBody: ObjectShapeValidator = (obj, v, path) => {
|
|
641
|
+
rejectKeys(obj, ["adapterId", "adapterVersion", "configuration", "status"], v, path);
|
|
642
|
+
opaqueId(obj, "adapterId", v, path); opaqueId(obj, "adapterVersion", v, path); validateConfig(obj.configuration, v, `${path}.configuration`); literal(obj, "status", "disabled", v, path);
|
|
643
|
+
};
|
|
644
|
+
const validateReviseIntegrationInstallBody: ObjectShapeValidator = (obj, v, path) => {
|
|
645
|
+
rejectKeys(obj, ["installId", "adapterVersion", "configuration"], v, path);
|
|
646
|
+
opaqueId(obj, "installId", v, path); opaqueId(obj, "adapterVersion", v, path); validateConfig(obj.configuration, v, `${path}.configuration`);
|
|
647
|
+
};
|
|
648
|
+
const validateSetIntegrationInstallStatusBody: ObjectShapeValidator = (obj, v, path) => {
|
|
649
|
+
rejectKeys(obj, ["installId", "status"], v, path); opaqueId(obj, "installId", v, path); enumeration(obj, "status", INTEGRATION_INSTALL_STATUSES, v, path);
|
|
650
|
+
};
|
|
651
|
+
const validateDeleteIntegrationInstallBody: ObjectShapeValidator = (obj, v, path) => { rejectKeys(obj, ["installId"], v, path); opaqueId(obj, "installId", v, path); };
|
|
652
|
+
|
|
653
|
+
export function validateCreateMigrationDraftRequest(input: unknown): ContractResult<CreateMigrationDraftRequest> { return validateMutationOperation(input, validateCreateMigrationDraftBody); }
|
|
654
|
+
export function validateUpdateMigrationDraftRequest(input: unknown): ContractResult<UpdateMigrationDraftRequest> { return validateMutationOperation(input, validateUpdateMigrationDraftBody); }
|
|
655
|
+
export function validateDeleteMigrationDraftRequest(input: unknown): ContractResult<DeleteMigrationDraftRequest> { return validateMutationOperation(input, validateDeleteMigrationDraftBody); }
|
|
656
|
+
export function validateCreateIntegrationInstallRequest(input: unknown): ContractResult<CreateIntegrationInstallRequest> { return validateMutationOperation(input, validateCreateIntegrationInstallBody); }
|
|
657
|
+
export function validateReviseIntegrationInstallRequest(input: unknown): ContractResult<ReviseIntegrationInstallRequest> { return validateMutationOperation(input, validateReviseIntegrationInstallBody); }
|
|
658
|
+
export function validateSetIntegrationInstallStatusRequest(input: unknown): ContractResult<SetIntegrationInstallStatusRequest> { return validateMutationOperation(input, validateSetIntegrationInstallStatusBody); }
|
|
659
|
+
export function validateDeleteIntegrationInstallRequest(input: unknown): ContractResult<DeleteIntegrationInstallRequest> { return validateMutationOperation(input, validateDeleteIntegrationInstallBody); }
|
|
660
|
+
|
|
661
|
+
/** Validate the closed, public-safe error shape before it crosses the API boundary. */
|
|
662
|
+
export function validateApiErrorEnvelope(input: unknown): ContractResult<ApiErrorEnvelope> {
|
|
663
|
+
const prepared = snapshotManagedAnalyticsDeclarativeInput<ApiErrorEnvelope>(input); if (!prepared.ok) return prepared;
|
|
664
|
+
const v = new Violations(); const obj = object(prepared.value, v, "");
|
|
665
|
+
if (!obj) return { ok: false, violations: v.items };
|
|
666
|
+
rejectKeys(obj, ["apiVersion", "requestId", "error"], v, ""); literal(obj, "apiVersion", "v1", v, ""); opaqueId(obj, "requestId", v, "");
|
|
667
|
+
const error = object(obj.error, v, "error");
|
|
668
|
+
if (error) {
|
|
669
|
+
rejectKeys(error, ["code", "message", "details"], v, "error");
|
|
670
|
+
enumeration(error, "code", MANAGED_ANALYTICS_ERROR_CODES, v, "error"); string(error, "message", v, "error");
|
|
671
|
+
if (error.details !== undefined) {
|
|
672
|
+
if (!Array.isArray(error.details)) v.add("error.details", "type", "Expected an array.");
|
|
673
|
+
else error.details.forEach((detail, i) => {
|
|
674
|
+
const path = `error.details[${i}]`; const item = object(detail, v, path); if (!item) return;
|
|
675
|
+
rejectKeys(item, ["path", "code", "message"], v, path);
|
|
676
|
+
const detailPath = string(item, "path", v, path); if (detailPath && !REDACTED_PATH.test(detailPath)) v.add(`${path}.path`, "redacted_path", "Expected a redacted schema path.");
|
|
677
|
+
const code = string(item, "code", v, path); if (code && !FINDING_CODE.test(code)) v.add(`${path}.code`, "redacted_code", "Expected a stable redacted error code.");
|
|
678
|
+
string(item, "message", v, path);
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
const safe = validateManagedAnalyticsSafePayload(obj); if (!safe.ok) v.items.push(...safe.violations);
|
|
683
|
+
return v.ok ? prepared : { ok: false, violations: v.items };
|
|
684
|
+
}
|