@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,701 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical semantic commerce event contract — the shared spine for dual
|
|
3
|
+
* client/server measurement (ADR 0014 M3 `integration-events-dual-delivery`,
|
|
4
|
+
* M2 `integration-commerce-data-minimized`, M1 `integration-checkout-provider-specific`).
|
|
5
|
+
*
|
|
6
|
+
* One contract feeds two delivery channels. Every event is a CloudEvents 1.0
|
|
7
|
+
* envelope with a small server-owned storefront extension:
|
|
8
|
+
*
|
|
9
|
+
* - The envelope is SEMANTIC and VERSIONED (`storefront.<domain>.<action>.vN`),
|
|
10
|
+
* never DOM-selector based. Unknown or unreleased types fail closed.
|
|
11
|
+
* - `id` is the OPAQUE LOGICAL EVENT IDENTITY: the same logical action delivered
|
|
12
|
+
* to both a browser destination and a server destination carries one `id`, so
|
|
13
|
+
* downstream vendors deduplicate on it (`eventDedupeKey`). Replays and late
|
|
14
|
+
* duplicates collapse to that key.
|
|
15
|
+
* - `correlation.id` is the attribution THREAD that spans a browser
|
|
16
|
+
* `checkout.handoff` and the later server-authoritative `order.*` — distinct
|
|
17
|
+
* logical events, one bounded-lifetime correlation.
|
|
18
|
+
* - `storefront.tenant`, `storefront.environment`, and `storefront.siteSurface`
|
|
19
|
+
* are SERVER-OWNED: stamped from trusted execution context, never read from a
|
|
20
|
+
* caller-supplied payload. `validateCommerceEventInContext` rejects any event
|
|
21
|
+
* whose claimed tenant/environment/surface disagrees with the trusted context,
|
|
22
|
+
* so tenant/env cannot be caller-forged.
|
|
23
|
+
* - `storefront.emitter` names the delivery channel of THIS instance. A
|
|
24
|
+
* verified-provider (server-authoritative) event type — `order.created`,
|
|
25
|
+
* `order.fulfilled` — cannot be emitted by the browser: a browser-emitted
|
|
26
|
+
* conversion is rejected (`forged_authority`), so generic browser code cannot
|
|
27
|
+
* invent a purchase.
|
|
28
|
+
* - Payloads are anonymous and field-allowlisted per event type: no customer
|
|
29
|
+
* identity, address, payment data, identity result, preview token, signed-cart
|
|
30
|
+
* material, or nonce. Data keys outside a type's catalog are rejected.
|
|
31
|
+
*
|
|
32
|
+
* The per-field owner/purpose/sensitivity catalog and retention classes live in
|
|
33
|
+
* `./events-catalog.ts`; the human-readable contract is
|
|
34
|
+
* `docs/architecture/commerce-event-contract.md`. The reserved completed-
|
|
35
|
+
* navigation event (charter invariant 9) shares the same envelope roots and ships
|
|
36
|
+
* before any client router.
|
|
37
|
+
*/
|
|
38
|
+
import type { ContractResult } from "./result.js";
|
|
39
|
+
import {
|
|
40
|
+
Violations,
|
|
41
|
+
reqObject,
|
|
42
|
+
childObject,
|
|
43
|
+
reqEnum,
|
|
44
|
+
reqLiteral,
|
|
45
|
+
reqString,
|
|
46
|
+
reqInt,
|
|
47
|
+
reqStringArray,
|
|
48
|
+
reqIsoTimestamp,
|
|
49
|
+
rejectUnknownKeys,
|
|
50
|
+
joinPath,
|
|
51
|
+
} from "./result.js";
|
|
52
|
+
import {
|
|
53
|
+
COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION,
|
|
54
|
+
CONSENT_SNAPSHOT_SCHEMA_VERSION,
|
|
55
|
+
} from "./version.js";
|
|
56
|
+
import { INTEGRATION_ENVIRONMENTS, INTEGRATION_SITE_SURFACES } from "./environment.js";
|
|
57
|
+
import type { IntegrationEnvironment, IntegrationSiteSurface } from "./environment.js";
|
|
58
|
+
import { findForbiddenFields } from "./secrets.js";
|
|
59
|
+
import { resemblesManagedAnalyticsPii } from "./managed-analytics-value-safety.js";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The reserved commerce event taxonomy. Every accepted type is here; an unknown
|
|
63
|
+
* type fails closed (`unknown_enum`). New behavior is a NEW `.vN` type or a new
|
|
64
|
+
* name — a type is never silently reinterpreted.
|
|
65
|
+
*/
|
|
66
|
+
export const COMMERCE_EVENT_TYPES = [
|
|
67
|
+
"storefront.page.viewed.v1",
|
|
68
|
+
"storefront.page.viewed.v2",
|
|
69
|
+
"storefront.action.activated.v1",
|
|
70
|
+
"storefront.product.viewed.v1",
|
|
71
|
+
"storefront.cart.item_added.v1",
|
|
72
|
+
"storefront.cart.item_removed.v1",
|
|
73
|
+
"storefront.cart.updated.v1",
|
|
74
|
+
"storefront.checkout.handoff.v1",
|
|
75
|
+
"storefront.purchase.completed.v1",
|
|
76
|
+
"storefront.consent.changed.v1",
|
|
77
|
+
"storefront.order.created.v1",
|
|
78
|
+
"storefront.order.fulfilled.v1",
|
|
79
|
+
] as const;
|
|
80
|
+
export type CommerceEventType = (typeof COMMERCE_EVENT_TYPES)[number];
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The browser-deliverable, consent-gated post-purchase signal a sandboxed
|
|
84
|
+
* third-party subscriber reacts to (post-purchase NPS / review / loyalty /
|
|
85
|
+
* referral widgets). It is NOT the authoritative verified order —
|
|
86
|
+
* `storefront.order.created.v1` / `.fulfilled.v1` stay `verified-provider` and are
|
|
87
|
+
* broker-refused for the browser (`forged_authority`), and the financial record
|
|
88
|
+
* lives only on the server. This is an ANONYMOUS, minimized (M2), analytics-gated
|
|
89
|
+
* signal that *a purchase completed*, carrying no order id, customer identity,
|
|
90
|
+
* payment data, or signed-cart material — safe to fan out to a browser subscriber
|
|
91
|
+
* under analytics consent.
|
|
92
|
+
*/
|
|
93
|
+
export const PURCHASE_COMPLETED_EVENT_TYPE = "storefront.purchase.completed.v1" as const;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Forward-declared types whose NAMES are reserved but whose semantics are not yet
|
|
97
|
+
* stable. They require provider truth (M1) before they can be emitted, so an
|
|
98
|
+
* instance of one is rejected `reserved_unavailable` rather than silently
|
|
99
|
+
* accepted or confused with an unknown name.
|
|
100
|
+
*/
|
|
101
|
+
export const FORWARD_DECLARED_COMMERCE_EVENT_TYPES = [
|
|
102
|
+
"storefront.order.cancelled.v1",
|
|
103
|
+
"storefront.order.refunded.v1",
|
|
104
|
+
"storefront.checkout.completed.v1",
|
|
105
|
+
] as const;
|
|
106
|
+
export type ForwardDeclaredCommerceEventType =
|
|
107
|
+
(typeof FORWARD_DECLARED_COMMERCE_EVENT_TYPES)[number];
|
|
108
|
+
|
|
109
|
+
/** The reserved completed-navigation event that must ship before any client router. */
|
|
110
|
+
export const NAVIGATION_COMPLETED_EVENT_TYPE = "storefront.navigation.completed.v1" as const;
|
|
111
|
+
|
|
112
|
+
export const NAVIGATION_KINDS = ["document", "hash", "back-forward", "redirect"] as const;
|
|
113
|
+
export type NavigationKind = (typeof NAVIGATION_KINDS)[number];
|
|
114
|
+
|
|
115
|
+
/** Stable semantic marketing-action namespace; action labels and DOM bindings never qualify. */
|
|
116
|
+
export const MARKETING_ACTION_NAME_PATTERN = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*_mktg$/;
|
|
117
|
+
|
|
118
|
+
/** The program-approved v1 marketing action identifiers. */
|
|
119
|
+
export const APPROVED_MARKETING_ACTION_NAMES = [
|
|
120
|
+
"user_clicked_contact_sales_mktg",
|
|
121
|
+
"user_clicked_create_account_mktg",
|
|
122
|
+
"user_clicked_login_mktg",
|
|
123
|
+
"user_clicked_contact_support_consumer_mktg",
|
|
124
|
+
"user_clicked_contact_support_partner_mktg",
|
|
125
|
+
] as const;
|
|
126
|
+
export type ApprovedMarketingActionName = (typeof APPROVED_MARKETING_ACTION_NAMES)[number];
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Which channel is AUTHORITATIVE for a given event type. `browser` events
|
|
130
|
+
* originate in the document (a server destination is a mirror). `verified-provider`
|
|
131
|
+
* events are authored only from a verified provider webhook and can never be
|
|
132
|
+
* emitted by the browser.
|
|
133
|
+
*/
|
|
134
|
+
export type EventSourceOfTruth = "browser" | "verified-provider";
|
|
135
|
+
export const EVENT_SOURCE_OF_TRUTH: Record<CommerceEventType, EventSourceOfTruth> = {
|
|
136
|
+
"storefront.page.viewed.v1": "browser",
|
|
137
|
+
"storefront.page.viewed.v2": "browser",
|
|
138
|
+
"storefront.action.activated.v1": "browser",
|
|
139
|
+
"storefront.product.viewed.v1": "browser",
|
|
140
|
+
"storefront.cart.item_added.v1": "browser",
|
|
141
|
+
"storefront.cart.item_removed.v1": "browser",
|
|
142
|
+
"storefront.cart.updated.v1": "browser",
|
|
143
|
+
"storefront.checkout.handoff.v1": "browser",
|
|
144
|
+
"storefront.purchase.completed.v1": "browser",
|
|
145
|
+
"storefront.consent.changed.v1": "browser",
|
|
146
|
+
"storefront.order.created.v1": "verified-provider",
|
|
147
|
+
"storefront.order.fulfilled.v1": "verified-provider",
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Whether a released type can be delivered to a browser subscriber — its
|
|
152
|
+
* source of truth is the browser. A `verified-provider` type (a conversion) is
|
|
153
|
+
* never browser-deliverable: the broker refuses to emit it (`forged_authority`)
|
|
154
|
+
* and no browser path carries it to a sandbox subscriber. This is the check a
|
|
155
|
+
* subscription-wiring layer runs over a manifest's declared `permissions.events`
|
|
156
|
+
* before it wires a delivery, so an impossible (verified-provider), unknown, or
|
|
157
|
+
* forward-declared event is rejected rather than silently never firing.
|
|
158
|
+
*/
|
|
159
|
+
export function isBrowserDeliverableEventType(type: string): type is CommerceEventType {
|
|
160
|
+
return (
|
|
161
|
+
(COMMERCE_EVENT_TYPES as readonly string[]).includes(type) &&
|
|
162
|
+
EVENT_SOURCE_OF_TRUTH[type as CommerceEventType] === "browser"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The delivery channel that emitted a specific envelope instance. */
|
|
167
|
+
export const EVENT_EMITTERS = ["browser", "server"] as const;
|
|
168
|
+
export type EventEmitter = (typeof EVENT_EMITTERS)[number];
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The server-owned identity block. `tenant`, `environment`, and `siteSurface` are
|
|
172
|
+
* stamped from trusted execution context (the renderer for a browser emitter, the
|
|
173
|
+
* verified webhook receiver for a server emitter) — never from caller input.
|
|
174
|
+
* `sequence` is an optional per-emitter monotonic counter for out-of-order
|
|
175
|
+
* detection independent of wall-clock `time`.
|
|
176
|
+
*/
|
|
177
|
+
export interface CommerceEventContext {
|
|
178
|
+
schemaVersion: typeof COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION;
|
|
179
|
+
tenant: string;
|
|
180
|
+
environment: IntegrationEnvironment;
|
|
181
|
+
siteSurface: IntegrationSiteSurface;
|
|
182
|
+
emitter: EventEmitter;
|
|
183
|
+
sequence?: number;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The base storefront context the navigation lifecycle event carries. */
|
|
187
|
+
export interface StorefrontEventContext {
|
|
188
|
+
schemaVersion: typeof COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION;
|
|
189
|
+
environment: IntegrationEnvironment;
|
|
190
|
+
siteSurface: IntegrationSiteSurface;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The consent snapshot a browser event was filtered against (a reference, not raw consent state). */
|
|
194
|
+
export interface EventConsentSnapshot {
|
|
195
|
+
schemaVersion: typeof CONSENT_SNAPSHOT_SCHEMA_VERSION;
|
|
196
|
+
snapshotId: string;
|
|
197
|
+
eligiblePurposes: string[];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The bounded-lifetime attribution thread that spans handoff and verified order events. */
|
|
201
|
+
export interface EventCorrelation {
|
|
202
|
+
id: string;
|
|
203
|
+
expiresAt: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface CommerceEvent {
|
|
207
|
+
specversion: "1.0";
|
|
208
|
+
id: string;
|
|
209
|
+
source: string;
|
|
210
|
+
type: CommerceEventType;
|
|
211
|
+
subject: string;
|
|
212
|
+
time: string;
|
|
213
|
+
datacontenttype: "application/json";
|
|
214
|
+
dataschema?: string;
|
|
215
|
+
storefront: CommerceEventContext;
|
|
216
|
+
correlation: EventCorrelation;
|
|
217
|
+
consent: EventConsentSnapshot;
|
|
218
|
+
data: Record<string, unknown>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface NavigationCompletedEvent {
|
|
222
|
+
specversion: "1.0";
|
|
223
|
+
id: string;
|
|
224
|
+
source: string;
|
|
225
|
+
type: typeof NAVIGATION_COMPLETED_EVENT_TYPE;
|
|
226
|
+
time: string;
|
|
227
|
+
storefront: StorefrontEventContext;
|
|
228
|
+
data: { from: string; to: string; kind: NavigationKind };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The trusted execution context a submitted event's server-owned fields must match. */
|
|
232
|
+
export interface TrustedEventContext {
|
|
233
|
+
tenant: string;
|
|
234
|
+
environment: IntegrationEnvironment;
|
|
235
|
+
siteSurface: IntegrationSiteSurface;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The one canonical dedupe/idempotency key for a logical event: its `id`. Both the
|
|
240
|
+
* browser and the server delivery of a single logical action carry the same `id`,
|
|
241
|
+
* so a consumer that keys on this value processes each logical event exactly once
|
|
242
|
+
* regardless of channel, retry, or replay.
|
|
243
|
+
*/
|
|
244
|
+
export function eventDedupeKey(event: Pick<CommerceEvent, "id">): string {
|
|
245
|
+
return event.id;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Allowed data keys per event type. Data is CLOSED: an unlisted key is rejected. */
|
|
249
|
+
export const COMMERCE_EVENT_DATA_FIELDS: Record<CommerceEventType, readonly string[]> = {
|
|
250
|
+
"storefront.page.viewed.v1": ["path", "title"],
|
|
251
|
+
"storefront.page.viewed.v2": ["path", "attribution"],
|
|
252
|
+
"storefront.action.activated.v1": ["name"],
|
|
253
|
+
"storefront.product.viewed.v1": ["productId", "variantId", "listId", "position", "currency", "unitPrice"],
|
|
254
|
+
"storefront.cart.item_added.v1": ["productId", "variantId", "quantity", "unitPrice", "currency"],
|
|
255
|
+
"storefront.cart.item_removed.v1": ["productId", "variantId", "quantity", "unitPrice", "currency"],
|
|
256
|
+
"storefront.cart.updated.v1": ["currency", "value", "itemCount", "items"],
|
|
257
|
+
"storefront.checkout.handoff.v1": ["currency", "value", "itemCount", "items"],
|
|
258
|
+
"storefront.purchase.completed.v1": ["currency", "value", "itemCount", "items"],
|
|
259
|
+
"storefront.consent.changed.v1": ["eligiblePurposes", "previousPurposes"],
|
|
260
|
+
"storefront.order.created.v1": ["orderId", "currency", "value", "itemCount", "items"],
|
|
261
|
+
"storefront.order.fulfilled.v1": ["orderId", "currency", "value", "itemCount", "items"],
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/** Allowed keys on a line item within a cart/handoff/order `items[]` array. */
|
|
265
|
+
export const COMMERCE_EVENT_LINE_ITEM_FIELDS = [
|
|
266
|
+
"productId",
|
|
267
|
+
"variantId",
|
|
268
|
+
"quantity",
|
|
269
|
+
"unitPrice",
|
|
270
|
+
] as const;
|
|
271
|
+
|
|
272
|
+
/** Key tokens that would carry customer identity or payment data (ADR 0014 M2). */
|
|
273
|
+
const PII_TOKENS = new Set([
|
|
274
|
+
"email",
|
|
275
|
+
"phone",
|
|
276
|
+
"name",
|
|
277
|
+
"firstname",
|
|
278
|
+
"lastname",
|
|
279
|
+
"address",
|
|
280
|
+
"dob",
|
|
281
|
+
"birthdate",
|
|
282
|
+
"ssn",
|
|
283
|
+
"card",
|
|
284
|
+
"cardnumber",
|
|
285
|
+
"pan",
|
|
286
|
+
"cvv",
|
|
287
|
+
"cvc",
|
|
288
|
+
"payment",
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
/** A non-negative decimal money string, e.g. `"42.50"` — never a float, never a locale form. */
|
|
292
|
+
const DECIMAL_STRING = /^\d+(\.\d+)?$/;
|
|
293
|
+
|
|
294
|
+
function findPiiFields(value: unknown, path: string, out: { path: string; code: string; message: string }[]): void {
|
|
295
|
+
if (Array.isArray(value)) {
|
|
296
|
+
value.forEach((item, i) => findPiiFields(item, `${path}[${i}]`, out));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (typeof value !== "object" || value === null) return;
|
|
300
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
301
|
+
const collapsed = key.replace(/[_\-.]/g, "").toLowerCase();
|
|
302
|
+
// action.activated's data.name is a constrained *_mktg registry identifier,
|
|
303
|
+
// not a person's name. Its type-specific validator enforces that closed syntax.
|
|
304
|
+
const semanticActionName = path === "data" && key === "name";
|
|
305
|
+
if (PII_TOKENS.has(collapsed) && !semanticActionName) {
|
|
306
|
+
out.push({
|
|
307
|
+
path: joinPath(path, key),
|
|
308
|
+
code: "pii_field",
|
|
309
|
+
message: `Field "${key}" looks like customer PII/payment data; browser events must be anonymous and minimal.`,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
findPiiFields(child, joinPath(path, key), out);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function reqDecimalString(
|
|
317
|
+
obj: Record<string, unknown>,
|
|
318
|
+
key: string,
|
|
319
|
+
v: Violations,
|
|
320
|
+
path: string,
|
|
321
|
+
): void {
|
|
322
|
+
const value = reqString(obj, key, v, path);
|
|
323
|
+
if (value !== undefined && !DECIMAL_STRING.test(value)) {
|
|
324
|
+
v.add(joinPath(path, key), "format", `"${key}" must be a non-negative decimal string, e.g. "42.50".`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function validateLineItems(parent: Record<string, unknown>, v: Violations, path: string): void {
|
|
329
|
+
const items = parent["items"];
|
|
330
|
+
if (!Array.isArray(items)) {
|
|
331
|
+
v.add(joinPath(path, "items"), "type", "Expected an array at \"items\".");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
items.forEach((raw, i) => {
|
|
335
|
+
const itemPath = `${joinPath(path, "items")}[${i}]`;
|
|
336
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
337
|
+
v.add(itemPath, "type", "Expected an object.");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const item = raw as Record<string, unknown>;
|
|
341
|
+
rejectUnknownKeys(item, COMMERCE_EVENT_LINE_ITEM_FIELDS, v, itemPath);
|
|
342
|
+
reqString(item, "productId", v, itemPath);
|
|
343
|
+
if ("variantId" in item) reqString(item, "variantId", v, itemPath);
|
|
344
|
+
reqInt(item, "quantity", v, itemPath);
|
|
345
|
+
reqDecimalString(item, "unitPrice", v, itemPath);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const UTM_FIELDS = ["source", "medium", "campaign", "term", "content"] as const;
|
|
350
|
+
const SAFE_UTM_VALUE = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$/;
|
|
351
|
+
const PROTECTED_ATTRIBUTION_VALUE =
|
|
352
|
+
/(?:@|\d{7,}|(?:^|[._~-])(?:bearer|password|secret|token|authorization|preview)(?:$|[._~-]))/i;
|
|
353
|
+
|
|
354
|
+
function validatePageAttribution(parent: Record<string, unknown>, v: Violations): void {
|
|
355
|
+
const attribution = childObject(parent, "attribution", v, "data");
|
|
356
|
+
if (!attribution) return;
|
|
357
|
+
rejectUnknownKeys(attribution, ["referrer", "utm"], v, "data.attribution");
|
|
358
|
+
if (!("referrer" in attribution) && !("utm" in attribution)) {
|
|
359
|
+
v.add("data.attribution", "empty", "Attribution must contain a minimized referrer or UTM object.");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if ("referrer" in attribution) {
|
|
363
|
+
const referrer = childObject(attribution, "referrer", v, "data.attribution");
|
|
364
|
+
if (referrer) {
|
|
365
|
+
rejectUnknownKeys(referrer, ["origin"], v, "data.attribution.referrer");
|
|
366
|
+
const origin = reqString(referrer, "origin", v, "data.attribution.referrer");
|
|
367
|
+
if (origin !== undefined) {
|
|
368
|
+
try {
|
|
369
|
+
const url = new URL(origin);
|
|
370
|
+
if (
|
|
371
|
+
!["http:", "https:"].includes(url.protocol) ||
|
|
372
|
+
url.origin !== origin ||
|
|
373
|
+
url.username !== "" ||
|
|
374
|
+
url.password !== ""
|
|
375
|
+
) {
|
|
376
|
+
v.add(
|
|
377
|
+
"data.attribution.referrer.origin",
|
|
378
|
+
"format",
|
|
379
|
+
"Referrer must be an http(s) origin only; paths, query strings, fragments, and credentials are forbidden.",
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
v.add("data.attribution.referrer.origin", "format", "Referrer must be a valid http(s) origin.");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if ("utm" in attribution) {
|
|
390
|
+
const utm = childObject(attribution, "utm", v, "data.attribution");
|
|
391
|
+
if (utm) {
|
|
392
|
+
rejectUnknownKeys(utm, UTM_FIELDS, v, "data.attribution.utm");
|
|
393
|
+
if (Object.keys(utm).length === 0) {
|
|
394
|
+
v.add("data.attribution.utm", "empty", "UTM must contain at least one allowlisted field.");
|
|
395
|
+
}
|
|
396
|
+
for (const key of UTM_FIELDS) {
|
|
397
|
+
if (!(key in utm)) continue;
|
|
398
|
+
const value = reqString(utm, key, v, "data.attribution.utm");
|
|
399
|
+
if (value !== undefined && !SAFE_UTM_VALUE.test(value)) {
|
|
400
|
+
v.add(
|
|
401
|
+
`data.attribution.utm.${key}`,
|
|
402
|
+
"format",
|
|
403
|
+
`UTM ${key} must be a 1-128 character opaque campaign token.`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (value !== undefined && PROTECTED_ATTRIBUTION_VALUE.test(value)) {
|
|
407
|
+
v.add(
|
|
408
|
+
`data.attribution.utm.${key}`,
|
|
409
|
+
"protected_value",
|
|
410
|
+
`UTM ${key} resembles PII or protected material and must not be emitted.`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Validate a type's `data` against its closed field catalog. */
|
|
419
|
+
function validateEventData(
|
|
420
|
+
type: CommerceEventType,
|
|
421
|
+
data: Record<string, unknown>,
|
|
422
|
+
v: Violations,
|
|
423
|
+
): void {
|
|
424
|
+
rejectUnknownKeys(data, COMMERCE_EVENT_DATA_FIELDS[type], v, "data");
|
|
425
|
+
switch (type) {
|
|
426
|
+
case "storefront.page.viewed.v1":
|
|
427
|
+
reqString(data, "path", v, "data");
|
|
428
|
+
if ("title" in data) reqString(data, "title", v, "data");
|
|
429
|
+
break;
|
|
430
|
+
case "storefront.page.viewed.v2":
|
|
431
|
+
{
|
|
432
|
+
const path = reqString(data, "path", v, "data");
|
|
433
|
+
if (
|
|
434
|
+
path !== undefined &&
|
|
435
|
+
(!path.startsWith("/") ||
|
|
436
|
+
path.startsWith("//") ||
|
|
437
|
+
path.includes("\\") ||
|
|
438
|
+
path.includes("?") ||
|
|
439
|
+
path.includes("#"))
|
|
440
|
+
) {
|
|
441
|
+
v.add(
|
|
442
|
+
"data.path",
|
|
443
|
+
"format",
|
|
444
|
+
"Page path must be a root-relative local path and exclude network-path forms, backslashes, query strings, and fragments.",
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
if (resemblesManagedAnalyticsPii(path)) {
|
|
448
|
+
v.add(
|
|
449
|
+
"data.path",
|
|
450
|
+
"pii_value",
|
|
451
|
+
"Page paths cannot contain values resembling customer PII.",
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if ("attribution" in data) validatePageAttribution(data, v);
|
|
456
|
+
break;
|
|
457
|
+
case "storefront.action.activated.v1": {
|
|
458
|
+
const name = reqString(data, "name", v, "data");
|
|
459
|
+
if (name !== undefined && !MARKETING_ACTION_NAME_PATTERN.test(name)) {
|
|
460
|
+
v.add("data.name", "format", "Action name must be lowercase snake_case and end in _mktg.");
|
|
461
|
+
} else if (
|
|
462
|
+
name !== undefined &&
|
|
463
|
+
!(APPROVED_MARKETING_ACTION_NAMES as readonly string[]).includes(name)
|
|
464
|
+
) {
|
|
465
|
+
v.add("data.name", "unknown_enum", "Action name is not in the approved v1 marketing action allowlist.");
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
case "storefront.product.viewed.v1":
|
|
470
|
+
reqString(data, "productId", v, "data");
|
|
471
|
+
if ("variantId" in data) reqString(data, "variantId", v, "data");
|
|
472
|
+
if ("listId" in data) reqString(data, "listId", v, "data");
|
|
473
|
+
if ("position" in data) reqInt(data, "position", v, "data");
|
|
474
|
+
if ("currency" in data) reqString(data, "currency", v, "data");
|
|
475
|
+
if ("unitPrice" in data) reqDecimalString(data, "unitPrice", v, "data");
|
|
476
|
+
break;
|
|
477
|
+
case "storefront.cart.item_added.v1":
|
|
478
|
+
case "storefront.cart.item_removed.v1":
|
|
479
|
+
reqString(data, "productId", v, "data");
|
|
480
|
+
if ("variantId" in data) reqString(data, "variantId", v, "data");
|
|
481
|
+
reqInt(data, "quantity", v, "data");
|
|
482
|
+
if ("unitPrice" in data) reqDecimalString(data, "unitPrice", v, "data");
|
|
483
|
+
if ("currency" in data) reqString(data, "currency", v, "data");
|
|
484
|
+
break;
|
|
485
|
+
case "storefront.cart.updated.v1":
|
|
486
|
+
case "storefront.checkout.handoff.v1":
|
|
487
|
+
case "storefront.purchase.completed.v1":
|
|
488
|
+
reqString(data, "currency", v, "data");
|
|
489
|
+
reqDecimalString(data, "value", v, "data");
|
|
490
|
+
reqInt(data, "itemCount", v, "data");
|
|
491
|
+
validateLineItems(data, v, "data");
|
|
492
|
+
break;
|
|
493
|
+
case "storefront.consent.changed.v1":
|
|
494
|
+
reqStringArray(data, "eligiblePurposes", v, "data");
|
|
495
|
+
if ("previousPurposes" in data) reqStringArray(data, "previousPurposes", v, "data");
|
|
496
|
+
break;
|
|
497
|
+
case "storefront.order.created.v1":
|
|
498
|
+
case "storefront.order.fulfilled.v1":
|
|
499
|
+
reqString(data, "orderId", v, "data");
|
|
500
|
+
reqString(data, "currency", v, "data");
|
|
501
|
+
reqDecimalString(data, "value", v, "data");
|
|
502
|
+
reqInt(data, "itemCount", v, "data");
|
|
503
|
+
validateLineItems(data, v, "data");
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function validateCommerceContext(
|
|
509
|
+
parent: Record<string, unknown>,
|
|
510
|
+
type: CommerceEventType | undefined,
|
|
511
|
+
v: Violations,
|
|
512
|
+
): void {
|
|
513
|
+
const ctx = childObject(parent, "storefront", v, "");
|
|
514
|
+
if (!ctx) return;
|
|
515
|
+
rejectUnknownKeys(
|
|
516
|
+
ctx,
|
|
517
|
+
["schemaVersion", "tenant", "environment", "siteSurface", "emitter", "sequence"],
|
|
518
|
+
v,
|
|
519
|
+
"storefront",
|
|
520
|
+
);
|
|
521
|
+
reqLiteral(ctx, "schemaVersion", COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION, v, "storefront");
|
|
522
|
+
reqString(ctx, "tenant", v, "storefront");
|
|
523
|
+
reqEnum(ctx, "environment", INTEGRATION_ENVIRONMENTS, v, "storefront");
|
|
524
|
+
reqEnum(ctx, "siteSurface", INTEGRATION_SITE_SURFACES, v, "storefront");
|
|
525
|
+
const emitter = reqEnum(ctx, "emitter", EVENT_EMITTERS, v, "storefront");
|
|
526
|
+
if ("sequence" in ctx) reqInt(ctx, "sequence", v, "storefront");
|
|
527
|
+
|
|
528
|
+
// No-forge of authority: a verified-provider event type can only be emitted by
|
|
529
|
+
// the server. A browser-emitted conversion is rejected, not trusted.
|
|
530
|
+
if (type && emitter === "browser" && EVENT_SOURCE_OF_TRUTH[type] === "verified-provider") {
|
|
531
|
+
v.add(
|
|
532
|
+
"storefront.emitter",
|
|
533
|
+
"forged_authority",
|
|
534
|
+
`"${type}" is server-authoritative (verified provider); a browser emitter cannot author it.`,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function validateBaseStorefrontContext(parent: Record<string, unknown>, v: Violations): void {
|
|
540
|
+
const ctx = childObject(parent, "storefront", v, "");
|
|
541
|
+
if (!ctx) return;
|
|
542
|
+
rejectUnknownKeys(ctx, ["schemaVersion", "environment", "siteSurface"], v, "storefront");
|
|
543
|
+
reqLiteral(ctx, "schemaVersion", COMMERCE_EVENT_STOREFRONT_SCHEMA_VERSION, v, "storefront");
|
|
544
|
+
reqEnum(ctx, "environment", INTEGRATION_ENVIRONMENTS, v, "storefront");
|
|
545
|
+
reqEnum(ctx, "siteSurface", INTEGRATION_SITE_SURFACES, v, "storefront");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function validateCommerceEvent(input: unknown): ContractResult<CommerceEvent> {
|
|
549
|
+
const v = new Violations();
|
|
550
|
+
const obj = reqObject(input, v, "");
|
|
551
|
+
if (!obj) return { ok: false, violations: v.items };
|
|
552
|
+
|
|
553
|
+
rejectUnknownKeys(
|
|
554
|
+
obj,
|
|
555
|
+
[
|
|
556
|
+
"specversion",
|
|
557
|
+
"id",
|
|
558
|
+
"source",
|
|
559
|
+
"type",
|
|
560
|
+
"subject",
|
|
561
|
+
"time",
|
|
562
|
+
"datacontenttype",
|
|
563
|
+
"dataschema",
|
|
564
|
+
"storefront",
|
|
565
|
+
"correlation",
|
|
566
|
+
"consent",
|
|
567
|
+
"data",
|
|
568
|
+
],
|
|
569
|
+
v,
|
|
570
|
+
"",
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
reqLiteral(obj, "specversion", "1.0", v, "");
|
|
574
|
+
const id = reqString(obj, "id", v, "");
|
|
575
|
+
if (resemblesManagedAnalyticsPii(id)) {
|
|
576
|
+
v.add("id", "pii_value", "Event ids must be opaque and cannot resemble customer PII.");
|
|
577
|
+
}
|
|
578
|
+
reqString(obj, "source", v, "");
|
|
579
|
+
|
|
580
|
+
// Type resolution fails closed: a reserved-but-unreleased forward declaration is
|
|
581
|
+
// rejected distinctly from an entirely unknown name, and only a released type
|
|
582
|
+
// resolves to a concrete `data` schema.
|
|
583
|
+
const rawType = obj["type"];
|
|
584
|
+
let type: CommerceEventType | undefined;
|
|
585
|
+
if (
|
|
586
|
+
typeof rawType === "string" &&
|
|
587
|
+
(FORWARD_DECLARED_COMMERCE_EVENT_TYPES as readonly string[]).includes(rawType)
|
|
588
|
+
) {
|
|
589
|
+
v.add(
|
|
590
|
+
"type",
|
|
591
|
+
"reserved_unavailable",
|
|
592
|
+
`"${rawType}" is reserved but not yet available; it requires verified provider truth and stable semantics.`,
|
|
593
|
+
);
|
|
594
|
+
} else {
|
|
595
|
+
type = reqEnum(obj, "type", COMMERCE_EVENT_TYPES, v, "");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
reqString(obj, "subject", v, "");
|
|
599
|
+
reqIsoTimestamp(obj, "time", v, "");
|
|
600
|
+
reqLiteral(obj, "datacontenttype", "application/json", v, "");
|
|
601
|
+
if ("dataschema" in obj) reqString(obj, "dataschema", v, "");
|
|
602
|
+
|
|
603
|
+
validateCommerceContext(obj, type, v);
|
|
604
|
+
|
|
605
|
+
const correlation = childObject(obj, "correlation", v, "");
|
|
606
|
+
if (correlation) {
|
|
607
|
+
rejectUnknownKeys(correlation, ["id", "expiresAt"], v, "correlation");
|
|
608
|
+
const correlationId = reqString(correlation, "id", v, "correlation");
|
|
609
|
+
if (resemblesManagedAnalyticsPii(correlationId)) {
|
|
610
|
+
v.add(
|
|
611
|
+
"correlation.id",
|
|
612
|
+
"pii_value",
|
|
613
|
+
"Correlation ids must be opaque and cannot resemble customer PII.",
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
reqIsoTimestamp(correlation, "expiresAt", v, "correlation");
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const consent = childObject(obj, "consent", v, "");
|
|
620
|
+
if (consent) {
|
|
621
|
+
rejectUnknownKeys(consent, ["schemaVersion", "snapshotId", "eligiblePurposes"], v, "consent");
|
|
622
|
+
reqLiteral(consent, "schemaVersion", CONSENT_SNAPSHOT_SCHEMA_VERSION, v, "consent");
|
|
623
|
+
reqString(consent, "snapshotId", v, "consent");
|
|
624
|
+
reqStringArray(consent, "eligiblePurposes", v, "consent");
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const data = childObject(obj, "data", v, "");
|
|
628
|
+
if (data) {
|
|
629
|
+
if (type) validateEventData(type, data, v);
|
|
630
|
+
findPiiFields(data, "data", v.items);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
for (const violation of findForbiddenFields(obj)) v.items.push(violation);
|
|
634
|
+
|
|
635
|
+
if (!v.ok) return { ok: false, violations: v.items };
|
|
636
|
+
return { ok: true, value: input as CommerceEvent };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Validate a submitted event AND prove its server-owned fields were not forged.
|
|
641
|
+
* `trusted` is the tenant/environment/surface the server already knows for the
|
|
642
|
+
* execution context that produced the event; any disagreement is a
|
|
643
|
+
* `context_mismatch` violation, so a caller cannot assert a different tenant or a
|
|
644
|
+
* more-privileged environment than the one it is actually running in.
|
|
645
|
+
*/
|
|
646
|
+
export function validateCommerceEventInContext(
|
|
647
|
+
input: unknown,
|
|
648
|
+
trusted: TrustedEventContext,
|
|
649
|
+
): ContractResult<CommerceEvent> {
|
|
650
|
+
const base = validateCommerceEvent(input);
|
|
651
|
+
if (!base.ok) return base;
|
|
652
|
+
const v = new Violations();
|
|
653
|
+
const ctx = base.value.storefront;
|
|
654
|
+
if (ctx.tenant !== trusted.tenant) {
|
|
655
|
+
v.add("storefront.tenant", "context_mismatch", "tenant is server-owned and must match the trusted execution context.");
|
|
656
|
+
}
|
|
657
|
+
if (ctx.environment !== trusted.environment) {
|
|
658
|
+
v.add("storefront.environment", "context_mismatch", "environment is server-owned and must match the trusted execution context.");
|
|
659
|
+
}
|
|
660
|
+
if (ctx.siteSurface !== trusted.siteSurface) {
|
|
661
|
+
v.add("storefront.siteSurface", "context_mismatch", "siteSurface is server-owned and must match the trusted execution context.");
|
|
662
|
+
}
|
|
663
|
+
if (!v.ok) return { ok: false, violations: v.items };
|
|
664
|
+
return base;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export function validateNavigationCompletedEvent(
|
|
668
|
+
input: unknown,
|
|
669
|
+
): ContractResult<NavigationCompletedEvent> {
|
|
670
|
+
const v = new Violations();
|
|
671
|
+
const obj = reqObject(input, v, "");
|
|
672
|
+
if (!obj) return { ok: false, violations: v.items };
|
|
673
|
+
|
|
674
|
+
rejectUnknownKeys(
|
|
675
|
+
obj,
|
|
676
|
+
["specversion", "id", "source", "type", "time", "storefront", "data"],
|
|
677
|
+
v,
|
|
678
|
+
"",
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
reqLiteral(obj, "specversion", "1.0", v, "");
|
|
682
|
+
reqString(obj, "id", v, "");
|
|
683
|
+
reqString(obj, "source", v, "");
|
|
684
|
+
reqLiteral(obj, "type", NAVIGATION_COMPLETED_EVENT_TYPE, v, "");
|
|
685
|
+
reqIsoTimestamp(obj, "time", v, "");
|
|
686
|
+
validateBaseStorefrontContext(obj, v);
|
|
687
|
+
|
|
688
|
+
const data = childObject(obj, "data", v, "");
|
|
689
|
+
if (data) {
|
|
690
|
+
rejectUnknownKeys(data, ["from", "to", "kind"], v, "data");
|
|
691
|
+
reqString(data, "from", v, "data");
|
|
692
|
+
reqString(data, "to", v, "data");
|
|
693
|
+
reqEnum(data, "kind", NAVIGATION_KINDS, v, "data");
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Payloads exclude candidate wrappers/tokens.
|
|
697
|
+
for (const violation of findForbiddenFields(obj)) v.items.push(violation);
|
|
698
|
+
|
|
699
|
+
if (!v.ok) return { ok: false, violations: v.items };
|
|
700
|
+
return { ok: true, value: input as NavigationCompletedEvent };
|
|
701
|
+
}
|