@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.1
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/dev-plugins/tenant-hot-reload.mjs +8 -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 +18 -10
- 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 +114 -17
- package/apps/storefront/src/lib/blog/markdown.ts +1 -1
- package/apps/storefront/src/lib/blog/provider.ts +76 -9
- 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 +203 -69
- 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 +42 -31
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +8 -3
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +6 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +6 -2
- package/apps/storefront/src/pages/style-guide/index.astro +19 -5
- 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/src/themes/tenants/index.ts +19 -7
- 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 +136 -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/build/copy-tenant-assets.mjs +12 -5
- package/scripts/dev/ai-edit.mjs +2 -1
- 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 +3 -2
- package/scripts/tot-dev.mjs +5 -4
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS5's intentionally small, transport-free authenticated webhook boundary.
|
|
3
|
+
*
|
|
4
|
+
* This module parses and verifies already-received values only. It neither
|
|
5
|
+
* accepts HTTP requests nor stores replay state, retries delivery, creates
|
|
6
|
+
* credentials, or knows where a destination lives. A host must supply an
|
|
7
|
+
* explicit signature verifier and an already-computed replay assessment for
|
|
8
|
+
* every invocation; an unavailable assessment fails closed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const AUTHENTICATED_WEBHOOK_SCHEMA_VERSION = 1 as const;
|
|
12
|
+
|
|
13
|
+
export const WEBHOOK_DESTINATION_EVENT_TYPES = [
|
|
14
|
+
"storefront.destination.catalog.snapshot.v1",
|
|
15
|
+
"storefront.destination.availability.changed.v1",
|
|
16
|
+
] as const;
|
|
17
|
+
export type WebhookDestinationEventType = (typeof WEBHOOK_DESTINATION_EVENT_TYPES)[number];
|
|
18
|
+
|
|
19
|
+
export const WEBHOOK_SIGNATURE_SCHEMES = ["ed25519-detached-v1"] as const;
|
|
20
|
+
export type WebhookSignatureScheme = (typeof WEBHOOK_SIGNATURE_SCHEMES)[number];
|
|
21
|
+
|
|
22
|
+
export type DestinationAvailability = "available" | "unavailable";
|
|
23
|
+
|
|
24
|
+
export interface CatalogSnapshotWebhookPayload {
|
|
25
|
+
snapshotId: string;
|
|
26
|
+
itemCount: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AvailabilityChangedWebhookPayload {
|
|
30
|
+
state: DestinationAvailability;
|
|
31
|
+
revision: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type WebhookDestinationPayload =
|
|
35
|
+
| CatalogSnapshotWebhookPayload
|
|
36
|
+
| AvailabilityChangedWebhookPayload;
|
|
37
|
+
|
|
38
|
+
export interface AuthenticatedWebhookEvent {
|
|
39
|
+
id: string;
|
|
40
|
+
type: WebhookDestinationEventType;
|
|
41
|
+
occurredAtMs: number;
|
|
42
|
+
payload: WebhookDestinationPayload;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WebhookAuthentication {
|
|
46
|
+
scheme: WebhookSignatureScheme;
|
|
47
|
+
/** Opaque detached signature. It is never returned in a failure. */
|
|
48
|
+
signature: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AuthenticatedWebhookEnvelope {
|
|
52
|
+
schemaVersion: typeof AUTHENTICATED_WEBHOOK_SCHEMA_VERSION;
|
|
53
|
+
event: AuthenticatedWebhookEvent;
|
|
54
|
+
authentication: WebhookAuthentication;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface WebhookContractViolation {
|
|
58
|
+
path: string;
|
|
59
|
+
code: "type" | "format" | "unknown_field" | "unknown_enum" | "unsupported_version";
|
|
60
|
+
/** Describes the contract expectation, never the rejected value. */
|
|
61
|
+
message: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type WebhookContractResult<T> =
|
|
65
|
+
| { ok: true; value: T }
|
|
66
|
+
| { ok: false; violations: readonly WebhookContractViolation[] };
|
|
67
|
+
|
|
68
|
+
export type WebhookReplayAssessment = "fresh" | "replayed" | "unavailable";
|
|
69
|
+
|
|
70
|
+
export interface WebhookTimestampBounds {
|
|
71
|
+
maximumAgeMs: number;
|
|
72
|
+
maximumFutureSkewMs: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const DEFAULT_WEBHOOK_TIMESTAMP_BOUNDS: Readonly<WebhookTimestampBounds> = Object.freeze({
|
|
76
|
+
maximumAgeMs: 300_000,
|
|
77
|
+
maximumFutureSkewMs: 30_000,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const MAXIMUM_WEBHOOK_AGE_MS = 900_000;
|
|
81
|
+
export const MAXIMUM_WEBHOOK_FUTURE_SKEW_MS = 60_000;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The caller owns key management and cryptography. This interface deliberately
|
|
85
|
+
* receives only the closed signing input and has no endpoint, key, credential,
|
|
86
|
+
* persistence, or network capability.
|
|
87
|
+
*/
|
|
88
|
+
export interface WebhookSignatureVerifier {
|
|
89
|
+
verify(input: Readonly<{
|
|
90
|
+
scheme: WebhookSignatureScheme;
|
|
91
|
+
signature: string;
|
|
92
|
+
signingInput: string;
|
|
93
|
+
}>): { verified: boolean };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface AuthenticatedWebhookVerificationContext {
|
|
97
|
+
nowMs: number;
|
|
98
|
+
timestampBounds?: WebhookTimestampBounds;
|
|
99
|
+
/** A host-computed result; this contract never reads or writes replay state. */
|
|
100
|
+
replay: WebhookReplayAssessment;
|
|
101
|
+
verifier: WebhookSignatureVerifier;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type AuthenticatedWebhookVerificationFailureCode =
|
|
105
|
+
| "invalid_envelope"
|
|
106
|
+
| "invalid_verification_context"
|
|
107
|
+
| "signature_invalid"
|
|
108
|
+
| "timestamp_out_of_bounds"
|
|
109
|
+
| "replayed";
|
|
110
|
+
|
|
111
|
+
export type AuthenticatedWebhookVerificationResult =
|
|
112
|
+
| { ok: true; value: AuthenticatedWebhookEnvelope }
|
|
113
|
+
| {
|
|
114
|
+
ok: false;
|
|
115
|
+
failure: Readonly<{
|
|
116
|
+
code: AuthenticatedWebhookVerificationFailureCode;
|
|
117
|
+
message: string;
|
|
118
|
+
}>;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const OPAQUE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
122
|
+
const OPAQUE_SIGNATURE = /^[A-Za-z0-9+/=_-]{16,1024}$/;
|
|
123
|
+
const REVISION = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
124
|
+
|
|
125
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
126
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
127
|
+
try {
|
|
128
|
+
const prototype = Object.getPrototypeOf(value);
|
|
129
|
+
return prototype === Object.prototype || prototype === null;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ownValue(value: Record<string, unknown>, key: string): unknown {
|
|
136
|
+
return Object.prototype.propertyIsEnumerable.call(value, key) ? value[key] : undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isSafeNonNegativeInteger(value: unknown): value is number {
|
|
140
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function violations(): WebhookContractViolation[] {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function rejectUnknownKeys(
|
|
148
|
+
value: Record<string, unknown>,
|
|
149
|
+
allowed: readonly string[],
|
|
150
|
+
path: string,
|
|
151
|
+
result: WebhookContractViolation[],
|
|
152
|
+
): void {
|
|
153
|
+
for (const key of Object.keys(value)) {
|
|
154
|
+
if (!allowed.includes(key)) {
|
|
155
|
+
result.push({
|
|
156
|
+
path: path ? `${path}.${key}` : key,
|
|
157
|
+
code: "unknown_field",
|
|
158
|
+
message: "Unknown field; this webhook contract is closed.",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function requireObject(
|
|
165
|
+
value: unknown,
|
|
166
|
+
path: string,
|
|
167
|
+
result: WebhookContractViolation[],
|
|
168
|
+
): Record<string, unknown> | undefined {
|
|
169
|
+
if (!isPlainObject(value)) {
|
|
170
|
+
result.push({ path, code: "type", message: "Expected an object." });
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function requireOpaqueString(
|
|
177
|
+
value: unknown,
|
|
178
|
+
path: string,
|
|
179
|
+
expression: RegExp,
|
|
180
|
+
result: WebhookContractViolation[],
|
|
181
|
+
): void {
|
|
182
|
+
if (typeof value !== "string") {
|
|
183
|
+
result.push({ path, code: "type", message: "Expected a string." });
|
|
184
|
+
} else if (!expression.test(value)) {
|
|
185
|
+
result.push({ path, code: "format", message: "Expected a bounded opaque identifier." });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isEventType(value: unknown): value is WebhookDestinationEventType {
|
|
190
|
+
return typeof value === "string" && (WEBHOOK_DESTINATION_EVENT_TYPES as readonly string[]).includes(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function isSignatureScheme(value: unknown): value is WebhookSignatureScheme {
|
|
194
|
+
return typeof value === "string" && (WEBHOOK_SIGNATURE_SCHEMES as readonly string[]).includes(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function validatePayload(
|
|
198
|
+
type: unknown,
|
|
199
|
+
payload: unknown,
|
|
200
|
+
result: WebhookContractViolation[],
|
|
201
|
+
): void {
|
|
202
|
+
const value = requireObject(payload, "event.payload", result);
|
|
203
|
+
if (!value || !isEventType(type)) return;
|
|
204
|
+
|
|
205
|
+
if (type === "storefront.destination.catalog.snapshot.v1") {
|
|
206
|
+
rejectUnknownKeys(value, ["snapshotId", "itemCount"], "event.payload", result);
|
|
207
|
+
requireOpaqueString(ownValue(value, "snapshotId"), "event.payload.snapshotId", OPAQUE_ID, result);
|
|
208
|
+
if (!isSafeNonNegativeInteger(ownValue(value, "itemCount"))) {
|
|
209
|
+
result.push({
|
|
210
|
+
path: "event.payload.itemCount",
|
|
211
|
+
code: "type",
|
|
212
|
+
message: "Expected a non-negative safe integer.",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
rejectUnknownKeys(value, ["state", "revision"], "event.payload", result);
|
|
219
|
+
const state = ownValue(value, "state");
|
|
220
|
+
if (state !== "available" && state !== "unavailable") {
|
|
221
|
+
result.push({
|
|
222
|
+
path: "event.payload.state",
|
|
223
|
+
code: "unknown_enum",
|
|
224
|
+
message: "Expected a declared availability state.",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
requireOpaqueString(ownValue(value, "revision"), "event.payload.revision", REVISION, result);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Validates the closed, data-only wire shape without performing verification. */
|
|
231
|
+
export function validateAuthenticatedWebhookEnvelope(
|
|
232
|
+
input: unknown,
|
|
233
|
+
): WebhookContractResult<AuthenticatedWebhookEnvelope> {
|
|
234
|
+
const result = violations();
|
|
235
|
+
const root = requireObject(input, "(root)", result);
|
|
236
|
+
if (!root) return { ok: false, violations: result };
|
|
237
|
+
|
|
238
|
+
rejectUnknownKeys(root, ["schemaVersion", "event", "authentication"], "", result);
|
|
239
|
+
const schemaVersion = ownValue(root, "schemaVersion");
|
|
240
|
+
if (schemaVersion !== AUTHENTICATED_WEBHOOK_SCHEMA_VERSION) {
|
|
241
|
+
result.push({
|
|
242
|
+
path: "schemaVersion",
|
|
243
|
+
code: "unsupported_version",
|
|
244
|
+
message: "Expected the supported webhook schema version.",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const event = requireObject(ownValue(root, "event"), "event", result);
|
|
249
|
+
if (event) {
|
|
250
|
+
rejectUnknownKeys(event, ["id", "type", "occurredAtMs", "payload"], "event", result);
|
|
251
|
+
const eventType = ownValue(event, "type");
|
|
252
|
+
requireOpaqueString(ownValue(event, "id"), "event.id", OPAQUE_ID, result);
|
|
253
|
+
if (!isEventType(eventType)) {
|
|
254
|
+
result.push({
|
|
255
|
+
path: "event.type",
|
|
256
|
+
code: "unknown_enum",
|
|
257
|
+
message: "Expected a declared webhook destination event type.",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (!isSafeNonNegativeInteger(ownValue(event, "occurredAtMs"))) {
|
|
261
|
+
result.push({
|
|
262
|
+
path: "event.occurredAtMs",
|
|
263
|
+
code: "type",
|
|
264
|
+
message: "Expected a non-negative safe integer timestamp.",
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
validatePayload(eventType, ownValue(event, "payload"), result);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const authentication = requireObject(ownValue(root, "authentication"), "authentication", result);
|
|
271
|
+
if (authentication) {
|
|
272
|
+
rejectUnknownKeys(authentication, ["scheme", "signature"], "authentication", result);
|
|
273
|
+
if (!isSignatureScheme(ownValue(authentication, "scheme"))) {
|
|
274
|
+
result.push({
|
|
275
|
+
path: "authentication.scheme",
|
|
276
|
+
code: "unknown_enum",
|
|
277
|
+
message: "Expected a declared detached signature scheme.",
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
requireOpaqueString(ownValue(authentication, "signature"), "authentication.signature", OPAQUE_SIGNATURE, result);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (result.length > 0) return { ok: false, violations: result };
|
|
284
|
+
return { ok: true, value: input as AuthenticatedWebhookEnvelope };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Returns the exact stable signing input after validation. The authentication
|
|
289
|
+
* block is deliberately excluded, preventing the signature from signing itself.
|
|
290
|
+
*/
|
|
291
|
+
export function canonicalWebhookSigningInput(envelope: AuthenticatedWebhookEnvelope): string {
|
|
292
|
+
return JSON.stringify({ schemaVersion: envelope.schemaVersion, event: envelope.event });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function failure(code: AuthenticatedWebhookVerificationFailureCode, message: string): AuthenticatedWebhookVerificationResult {
|
|
296
|
+
return { ok: false, failure: Object.freeze({ code, message }) };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function hasValidContext(context: unknown): context is AuthenticatedWebhookVerificationContext {
|
|
300
|
+
if (!isPlainObject(context)) return false;
|
|
301
|
+
const candidate = context as Partial<AuthenticatedWebhookVerificationContext>;
|
|
302
|
+
const bounds = candidate.timestampBounds ?? DEFAULT_WEBHOOK_TIMESTAMP_BOUNDS;
|
|
303
|
+
return (
|
|
304
|
+
isSafeNonNegativeInteger(candidate.nowMs) &&
|
|
305
|
+
isSafeNonNegativeInteger(bounds.maximumAgeMs) &&
|
|
306
|
+
isSafeNonNegativeInteger(bounds.maximumFutureSkewMs) &&
|
|
307
|
+
bounds.maximumAgeMs <= MAXIMUM_WEBHOOK_AGE_MS &&
|
|
308
|
+
bounds.maximumFutureSkewMs <= MAXIMUM_WEBHOOK_FUTURE_SKEW_MS &&
|
|
309
|
+
(candidate.replay === "fresh" || candidate.replay === "replayed" || candidate.replay === "unavailable") &&
|
|
310
|
+
typeof candidate.verifier?.verify === "function"
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Verifies an already-parsed envelope deterministically at caller-provided time.
|
|
316
|
+
* Every unsuccessful result is intentionally redacted: it contains a stable
|
|
317
|
+
* category but never returns the envelope, signature, verifier details, or time.
|
|
318
|
+
*/
|
|
319
|
+
export function verifyAuthenticatedWebhook(
|
|
320
|
+
input: unknown,
|
|
321
|
+
context: AuthenticatedWebhookVerificationContext,
|
|
322
|
+
): AuthenticatedWebhookVerificationResult {
|
|
323
|
+
const parsed = validateAuthenticatedWebhookEnvelope(input);
|
|
324
|
+
if (!parsed.ok) return failure("invalid_envelope", "Webhook envelope was rejected.");
|
|
325
|
+
if (!hasValidContext(context)) {
|
|
326
|
+
return failure("invalid_verification_context", "Webhook verification context was rejected.");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let verification: { verified: boolean } | undefined;
|
|
330
|
+
try {
|
|
331
|
+
verification = context.verifier.verify({
|
|
332
|
+
scheme: parsed.value.authentication.scheme,
|
|
333
|
+
signature: parsed.value.authentication.signature,
|
|
334
|
+
signingInput: canonicalWebhookSigningInput(parsed.value),
|
|
335
|
+
});
|
|
336
|
+
} catch {
|
|
337
|
+
return failure("signature_invalid", "Webhook authentication failed.");
|
|
338
|
+
}
|
|
339
|
+
if (!verification || verification.verified !== true) {
|
|
340
|
+
return failure("signature_invalid", "Webhook authentication failed.");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const bounds = context.timestampBounds ?? DEFAULT_WEBHOOK_TIMESTAMP_BOUNDS;
|
|
344
|
+
const age = context.nowMs - parsed.value.event.occurredAtMs;
|
|
345
|
+
if (age > bounds.maximumAgeMs || age < -bounds.maximumFutureSkewMs) {
|
|
346
|
+
return failure("timestamp_out_of_bounds", "Webhook timestamp is outside the accepted window.");
|
|
347
|
+
}
|
|
348
|
+
if (context.replay !== "fresh") {
|
|
349
|
+
return failure("replayed", "Webhook replay protection rejected the event.");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { ok: true, value: parsed.value };
|
|
353
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Types for the dependency-free embed catalog data module (embed-catalog.mjs). */
|
|
2
|
+
export interface EmbedProviderData {
|
|
3
|
+
label: string;
|
|
4
|
+
markers: readonly string[];
|
|
5
|
+
scriptSrc?: readonly string[];
|
|
6
|
+
connectSrc?: readonly string[];
|
|
7
|
+
frameSrc?: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export const EMBED_PROVIDERS: Readonly<Record<string, EmbedProviderData>>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the third-party embed catalog (CSP allowlist).
|
|
3
|
+
*
|
|
4
|
+
* Plain JS ON PURPOSE: this data is consumed both by TypeScript (csp.ts, the
|
|
5
|
+
* worker runtime) AND by plain-`node` tooling that can't import TS — the
|
|
6
|
+
* dev-loop lint (scripts/tenant/lint-embeds.mjs) runs under `node --test` /
|
|
7
|
+
* `node`. Keeping the catalog here, dependency-free, lets those share ONE copy
|
|
8
|
+
* instead of hand-mirroring it. (The published `tot` CLI validator keeps its own
|
|
9
|
+
* inline copy — it can't reach this package at runtime — but a parity test
|
|
10
|
+
* pins it to this module so it can't drift.)
|
|
11
|
+
*
|
|
12
|
+
* Each entry: `markers` are substrings that appear in rendered HTML when the
|
|
13
|
+
* embed is present on a page (so the CSP origins are added only to pages that
|
|
14
|
+
* actually use it); `frameSrc`/`connectSrc`/`scriptSrc` are the EXACT hosts the
|
|
15
|
+
* embed needs in the parent document — never wildcards. The embed itself renders
|
|
16
|
+
* in a cross-origin iframe governed by the provider's own CSP.
|
|
17
|
+
*
|
|
18
|
+
* @typedef {Object} EmbedProviderData
|
|
19
|
+
* @property {string} label
|
|
20
|
+
* @property {readonly string[]} markers
|
|
21
|
+
* @property {readonly string[]} [scriptSrc]
|
|
22
|
+
* @property {readonly string[]} [connectSrc]
|
|
23
|
+
* @property {readonly string[]} [frameSrc]
|
|
24
|
+
*
|
|
25
|
+
* @type {Readonly<Record<string, EmbedProviderData>>}
|
|
26
|
+
*/
|
|
27
|
+
export const EMBED_PROVIDERS = {
|
|
28
|
+
pipedrive: {
|
|
29
|
+
label: "Pipedrive Web Forms",
|
|
30
|
+
// The embed div carries data-pd-webforms=… and the loader is
|
|
31
|
+
// webforms.pipedrive.com/f/loader; either marker means the form is present.
|
|
32
|
+
markers: ["webforms.pipedrive.com", "pipedriveWebForms"],
|
|
33
|
+
// The nonce-allowed loader fetches its form definition from this origin and
|
|
34
|
+
// mounts the form in a same-origin iframe from it. script-src as a
|
|
35
|
+
// belt-and-suspenders in case a future loader drops the parent-tag nonce.
|
|
36
|
+
scriptSrc: ["https://webforms.pipedrive.com"],
|
|
37
|
+
connectSrc: ["https://webforms.pipedrive.com"],
|
|
38
|
+
frameSrc: ["https://webforms.pipedrive.com"],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical compliance + SEO **evidence contract** — the single source of truth shared by:
|
|
3
|
+
* - the storefront's `/__evidence` route (renders it),
|
|
4
|
+
* - the CI cutover crawl (writes it),
|
|
5
|
+
* - the ToT MCP `evidence` / `validate` / `promote` tools (consume it),
|
|
6
|
+
* - the go-live gate (enforces it).
|
|
7
|
+
*
|
|
8
|
+
* One contract means "what the preview shows" can never drift from "what the gate blocks on."
|
|
9
|
+
* See `epics/dev-loop.md` (D4 + the MCP-foundations block).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// NOTE (2026-08): a v2 `assets_materialized` check was briefly added here and REVERTED —
|
|
13
|
+
// the deployed tot-mcp evidence validator pins `contract_version:1` + a 7-id enum and
|
|
14
|
+
// REJECTED every v2 report (".contract_version should be equal to constant; .checks[7].id
|
|
15
|
+
// should be equal to one of the allowed values"), silently dropping the evidence push so
|
|
16
|
+
// `preview_status` hung `pending` on every reconcile. The reconcile STILL fetch-verifies
|
|
17
|
+
// the served asset subtree (verify-assets.ts) and surfaces the result in the reconcile
|
|
18
|
+
// trace ("assets-verified" marker) — that regression guard is intact. Re-add the
|
|
19
|
+
// `assets_materialized` check id here + bump to 2 ONLY in lockstep with tot-mcp accepting
|
|
20
|
+
// contract_version 2 (and mirroring the evidence-url CRC). Until then this stays v1.
|
|
21
|
+
export const EVIDENCE_CONTRACT_VERSION = 1;
|
|
22
|
+
|
|
23
|
+
/** The per-check verdicts. `skip` = not applicable / not yet measured. */
|
|
24
|
+
export const EVIDENCE_STATUSES = ["pass", "warn", "fail", "skip"] as const;
|
|
25
|
+
export type EvidenceStatus = (typeof EVIDENCE_STATUSES)[number];
|
|
26
|
+
|
|
27
|
+
/** Who produced a report. */
|
|
28
|
+
export const EVIDENCE_SOURCES = ["route", "ci", "mcp"] as const;
|
|
29
|
+
export type EvidenceSource = (typeof EVIDENCE_SOURCES)[number];
|
|
30
|
+
|
|
31
|
+
/** The canonical instrument-panel rows, in display order. */
|
|
32
|
+
export const EVIDENCE_CHECK_IDS = [
|
|
33
|
+
"age_gate",
|
|
34
|
+
"excise",
|
|
35
|
+
"jurisdiction",
|
|
36
|
+
"llms_txt",
|
|
37
|
+
"json_ld",
|
|
38
|
+
"sitemap",
|
|
39
|
+
"cutover_crawl",
|
|
40
|
+
// (v2 `assets_materialized` intentionally omitted — see EVIDENCE_CONTRACT_VERSION note.)
|
|
41
|
+
] as const;
|
|
42
|
+
export type EvidenceCheckId = (typeof EVIDENCE_CHECK_IDS)[number];
|
|
43
|
+
|
|
44
|
+
/** Compliance-critical checks — any non-`pass` here blocks promotion. */
|
|
45
|
+
export const REQUIRED_FOR_PROMOTE: readonly EvidenceCheckId[] = [
|
|
46
|
+
"age_gate",
|
|
47
|
+
"excise",
|
|
48
|
+
"jurisdiction",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export interface EvidenceCheck {
|
|
52
|
+
id: EvidenceCheckId;
|
|
53
|
+
/** Human label for the panel row, written from the operator's side. */
|
|
54
|
+
label: string;
|
|
55
|
+
status: EvidenceStatus;
|
|
56
|
+
/** Short, plain explanation of the verdict. */
|
|
57
|
+
detail?: string;
|
|
58
|
+
/** Pointer to the proof (a URL, a crawl-report path, the `llms.txt` route). */
|
|
59
|
+
evidence?: string;
|
|
60
|
+
/**
|
|
61
|
+
* A concrete fix suggestion for a non-pass check — the moat over a bare
|
|
62
|
+
* pass/fail. Additive (contract_version unchanged — a report without it still
|
|
63
|
+
* validates). Only set on `fail`/`warn`; a `pass`/`skip` has nothing to fix.
|
|
64
|
+
*/
|
|
65
|
+
hint?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface EvidenceReport {
|
|
69
|
+
contract_version: number;
|
|
70
|
+
tenant_id: string;
|
|
71
|
+
/** ISO 8601. Pass it in — keeps the report deterministic/testable. */
|
|
72
|
+
generated_at: string;
|
|
73
|
+
/** Who produced it. */
|
|
74
|
+
source: EvidenceSource;
|
|
75
|
+
checks: EvidenceCheck[];
|
|
76
|
+
/** Derived: every `REQUIRED_FOR_PROMOTE` check is `pass`. */
|
|
77
|
+
promotable: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** True iff every compliance-critical check passes. The single promote rule. */
|
|
81
|
+
export function computePromotable(checks: readonly EvidenceCheck[]): boolean {
|
|
82
|
+
return REQUIRED_FOR_PROMOTE.every(
|
|
83
|
+
(id) => checks.find((c) => c.id === id)?.status === "pass",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Freshness bound for promotion evidence: evidence older than this (from its
|
|
89
|
+
* `generated_at` to the promote instant) is STALE and must be recomputed before it
|
|
90
|
+
* can gate a live promotion. A reconcile regenerates evidence on every head move, so
|
|
91
|
+
* a promote riding evidence older than this window is promoting a snapshot no one
|
|
92
|
+
* re-measured recently — refuse and re-reconcile.
|
|
93
|
+
*/
|
|
94
|
+
export const PROMOTION_EVIDENCE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
95
|
+
|
|
96
|
+
const KNOWN_CHECK_IDS: ReadonlySet<string> = new Set(EVIDENCE_CHECK_IDS);
|
|
97
|
+
|
|
98
|
+
/** A minimal `{ id, status }` — the only fields the promotion verifier reads. */
|
|
99
|
+
export interface PromotionEvidenceCheck {
|
|
100
|
+
id: string;
|
|
101
|
+
status: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface VerifyPromotionEvidenceOptions {
|
|
105
|
+
/**
|
|
106
|
+
* The authoritative expected required-check ids — SERVER-DERIVED from the
|
|
107
|
+
* tenant's obligations, never taken from the (spoofable) report's own `required`
|
|
108
|
+
* flags or a client-supplied list. Defaults to {@link REQUIRED_FOR_PROMOTE}.
|
|
109
|
+
*/
|
|
110
|
+
requiredIds?: readonly string[];
|
|
111
|
+
/** The evidence's `generated_at` (ISO). Required to evaluate freshness. */
|
|
112
|
+
generatedAt?: string;
|
|
113
|
+
/** The promote instant (ms epoch) freshness is measured against. */
|
|
114
|
+
now?: number;
|
|
115
|
+
/** Max evidence age before STALE (ms). Omit ⇒ no wall-clock freshness check. */
|
|
116
|
+
maxAgeMs?: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface PromotionEvidenceVerdict {
|
|
120
|
+
/** True only for a COMPLETE, matching, fresh, all-passing required set. */
|
|
121
|
+
ok: boolean;
|
|
122
|
+
/** Every reason the evidence may not gate a promotion (empty ⇔ ok). */
|
|
123
|
+
blockers: string[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The single fail-CLOSED authority for "may this evidence gate a live promotion?".
|
|
128
|
+
*
|
|
129
|
+
* The expected required-check set is SERVER-DERIVED ({@link REQUIRED_FOR_PROMOTE} by
|
|
130
|
+
* default). Unlike `computePromotable` (a bare boolean), this returns the reasons and
|
|
131
|
+
* additionally hardens the set itself so an empty/partial/spoofed report can't slip
|
|
132
|
+
* through a downstream check that only blocks present-but-failing rows. Rejects on any
|
|
133
|
+
* of: an absent report (no array), an omitted required id, a required id that is
|
|
134
|
+
* present but not `pass`, an unrecognized id (contract drift / a spoofed row), a
|
|
135
|
+
* duplicated id (an ambiguous set that could mask a real verdict), or stale evidence.
|
|
136
|
+
*/
|
|
137
|
+
export function verifyPromotionEvidence(
|
|
138
|
+
checks: readonly PromotionEvidenceCheck[] | null | undefined,
|
|
139
|
+
opts: VerifyPromotionEvidenceOptions = {},
|
|
140
|
+
): PromotionEvidenceVerdict {
|
|
141
|
+
const requiredIds = opts.requiredIds ?? REQUIRED_FOR_PROMOTE;
|
|
142
|
+
|
|
143
|
+
// Absent report — fail closed. No evidence is NOT "nothing to check": a promotion
|
|
144
|
+
// with no compliance report is exactly what this gate exists to refuse.
|
|
145
|
+
if (!Array.isArray(checks)) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
blockers: ["Compliance evidence is absent — no report to promote against."],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const blockers: string[] = [];
|
|
153
|
+
|
|
154
|
+
// "matching" half: an unrecognized id (outside the canonical contract) or a
|
|
155
|
+
// duplicate makes the set ambiguous — refuse rather than let a stray row stand in
|
|
156
|
+
// for, or mask, a real required verdict.
|
|
157
|
+
const seen = new Set<string>();
|
|
158
|
+
for (const c of checks) {
|
|
159
|
+
if (!KNOWN_CHECK_IDS.has(c.id)) {
|
|
160
|
+
blockers.push(`Unrecognized compliance check "${c.id}" is not part of the evidence contract.`);
|
|
161
|
+
}
|
|
162
|
+
if (seen.has(c.id)) {
|
|
163
|
+
blockers.push(`Duplicate compliance check "${c.id}" in the evidence.`);
|
|
164
|
+
}
|
|
165
|
+
seen.add(c.id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// "complete" half: every authoritative required id must be present AND pass.
|
|
169
|
+
for (const id of requiredIds) {
|
|
170
|
+
const row = checks.find((c) => c.id === id);
|
|
171
|
+
if (!row) {
|
|
172
|
+
blockers.push(`Required compliance check "${id}" is missing from the evidence.`);
|
|
173
|
+
} else if (row.status !== "pass") {
|
|
174
|
+
blockers.push(`Required compliance check "${id}" did not pass (status: ${row.status}).`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// "fresh" half: stale evidence must be recomputed before it may gate a promotion.
|
|
179
|
+
if (opts.maxAgeMs != null && opts.now != null) {
|
|
180
|
+
if (!opts.generatedAt) {
|
|
181
|
+
blockers.push("Compliance evidence has no generatedAt — freshness cannot be confirmed.");
|
|
182
|
+
} else {
|
|
183
|
+
const generated = Date.parse(opts.generatedAt);
|
|
184
|
+
if (Number.isNaN(generated)) {
|
|
185
|
+
blockers.push(`Compliance evidence generatedAt "${opts.generatedAt}" is not a valid timestamp.`);
|
|
186
|
+
} else if (generated - opts.now > 60_000) {
|
|
187
|
+
// Tolerate ~60s of clock skew; a report meaningfully in the future is bogus.
|
|
188
|
+
blockers.push("Compliance evidence is timestamped in the future.");
|
|
189
|
+
} else if (opts.now - generated > opts.maxAgeMs) {
|
|
190
|
+
blockers.push(`Compliance evidence is stale (older than ${opts.maxAgeMs}ms).`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { ok: blockers.length === 0, blockers };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Construct a report with `promotable` derived from the checks — the only constructor. */
|
|
199
|
+
export function buildEvidenceReport(
|
|
200
|
+
input: Omit<EvidenceReport, "promotable" | "contract_version">,
|
|
201
|
+
): EvidenceReport {
|
|
202
|
+
return {
|
|
203
|
+
contract_version: EVIDENCE_CONTRACT_VERSION,
|
|
204
|
+
...input,
|
|
205
|
+
promotable: computePromotable(input.checks),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export type EvidenceDirection = "improved" | "regressed" | "unchanged";
|
|
210
|
+
|
|
211
|
+
export interface EvidenceDelta {
|
|
212
|
+
id: EvidenceCheckId;
|
|
213
|
+
from: EvidenceStatus;
|
|
214
|
+
to: EvidenceStatus;
|
|
215
|
+
direction: EvidenceDirection;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const STATUS_RANK: Record<EvidenceStatus, number> = { fail: 0, warn: 1, skip: 2, pass: 3 };
|
|
219
|
+
|
|
220
|
+
/** Preview-vs-live diff: what got better/worse since `prev`. Bolder than a visual-only preview. */
|
|
221
|
+
export function diffEvidence(prev: EvidenceReport, next: EvidenceReport): EvidenceDelta[] {
|
|
222
|
+
return next.checks.map((c) => {
|
|
223
|
+
const from = prev.checks.find((x) => x.id === c.id)?.status ?? "skip";
|
|
224
|
+
const direction: EvidenceDirection =
|
|
225
|
+
STATUS_RANK[c.status] > STATUS_RANK[from]
|
|
226
|
+
? "improved"
|
|
227
|
+
: STATUS_RANK[c.status] < STATUS_RANK[from]
|
|
228
|
+
? "regressed"
|
|
229
|
+
: "unchanged";
|
|
230
|
+
return { id: c.id, from, to: c.status, direction };
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Runtime JSON Schema (draft-07) for an `EvidenceReport` — the artifact the ToT MCP
|
|
236
|
+
* validates a *relayed* report's **shape** against. The TS `interface`s above evaporate at
|
|
237
|
+
* runtime, so this is the wire contract. Enums are spread from the same constants the types
|
|
238
|
+
* derive from (`EVIDENCE_*`), so the schema can't drift from the types.
|
|
239
|
+
*
|
|
240
|
+
* The MCP only re-checks shape; it never recomputes the promote rule (`computePromotable`
|
|
241
|
+
* stays the producer's sole authority). Kept dependency-free on purpose — `@tot/private-controlplane` carries
|
|
242
|
+
* no runtime deps; consumers (MCP) run it through their own validator (ajv).
|
|
243
|
+
*/
|
|
244
|
+
export const EVIDENCE_REPORT_JSON_SCHEMA = {
|
|
245
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
246
|
+
$id: "https://tokenoftrust.com/schemas/evidence-report.json",
|
|
247
|
+
title: "EvidenceReport",
|
|
248
|
+
type: "object",
|
|
249
|
+
additionalProperties: false,
|
|
250
|
+
required: ["contract_version", "tenant_id", "generated_at", "source", "checks", "promotable"],
|
|
251
|
+
properties: {
|
|
252
|
+
contract_version: { type: "integer", const: EVIDENCE_CONTRACT_VERSION },
|
|
253
|
+
tenant_id: { type: "string", minLength: 1 },
|
|
254
|
+
generated_at: { type: "string", minLength: 1 },
|
|
255
|
+
source: { type: "string", enum: [...EVIDENCE_SOURCES] },
|
|
256
|
+
promotable: { type: "boolean" },
|
|
257
|
+
checks: {
|
|
258
|
+
type: "array",
|
|
259
|
+
items: {
|
|
260
|
+
type: "object",
|
|
261
|
+
additionalProperties: false,
|
|
262
|
+
required: ["id", "label", "status"],
|
|
263
|
+
properties: {
|
|
264
|
+
id: { type: "string", enum: [...EVIDENCE_CHECK_IDS] },
|
|
265
|
+
label: { type: "string" },
|
|
266
|
+
status: { type: "string", enum: [...EVIDENCE_STATUSES] },
|
|
267
|
+
detail: { type: "string" },
|
|
268
|
+
evidence: { type: "string" },
|
|
269
|
+
// Additive (contract_version unchanged) — see EvidenceCheck.hint.
|
|
270
|
+
hint: { type: "string" },
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
} as const;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The full `evidence://contract` payload the storefront publishes into the MCP (see
|
|
279
|
+
* `docs/handoffs/mcp-foundations-runtime-schema.md`). Bundles the version + check ids +
|
|
280
|
+
* promote-gate + JSON Schema so the MCP can serve one versioned record. Bump
|
|
281
|
+
* `EVIDENCE_CONTRACT_VERSION` on any breaking shape change so consumers can detect skew.
|
|
282
|
+
*/
|
|
283
|
+
export const EVIDENCE_CONTRACT = {
|
|
284
|
+
contract_version: EVIDENCE_CONTRACT_VERSION,
|
|
285
|
+
check_ids: EVIDENCE_CHECK_IDS,
|
|
286
|
+
required_for_promote: REQUIRED_FOR_PROMOTE,
|
|
287
|
+
json_schema: EVIDENCE_REPORT_JSON_SCHEMA,
|
|
288
|
+
};
|