@tokenoftrust/storefront-runner 1.4.1 → 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 +17 -1
- package/apps/storefront/env.d.ts +54 -5
- package/apps/storefront/integrations/materialize-guard.mjs +54 -0
- package/apps/storefront/migrations-apps/0002_good_switch.sql +26 -0
- package/apps/storefront/migrations-apps/0003_mute_marauders.sql +12 -0
- 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/0002_snapshot.json +1160 -0
- package/apps/storefront/migrations-apps/meta/0003_snapshot.json +1235 -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 +56 -0
- package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
- package/apps/storefront/public/js/dashboard-team.js +38 -2
- 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/Seo.astro +65 -1
- package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +878 -0
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +1435 -0
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +3 -7
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1980 -2564
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +1 -3
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
- package/apps/storefront/src/components/blog/BlogPaywall.astro +87 -0
- package/apps/storefront/src/components/blog/PostAccessBadge.astro +43 -0
- package/apps/storefront/src/components/blog/PostGrid.astro +57 -0
- package/apps/storefront/src/components/blog/ReactionBar.astro +214 -0
- 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 +146 -21
- package/apps/storefront/src/lib/activity/alerts.ts +17 -19
- package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +89 -9
- package/apps/storefront/src/lib/activity/deployVersion.ts +20 -27
- package/apps/storefront/src/lib/activity/ingest.ts +8 -8
- package/apps/storefront/src/lib/activity/ingestAuth.ts +1 -1
- package/apps/storefront/src/lib/activity/killSwitch.ts +6 -6
- package/apps/storefront/src/lib/activity/query.ts +9 -9
- package/apps/storefront/src/lib/activity/recordActivity.ts +11 -15
- package/apps/storefront/src/lib/activity/store.ts +4 -6
- package/apps/storefront/src/lib/activity/uiActor.ts +12 -12
- package/apps/storefront/src/lib/activity/workerCommit.ts +6 -6
- package/apps/storefront/src/lib/admin/adminShell.ts +292 -0
- package/apps/storefront/src/lib/admin/ai/approvalMode.ts +2 -3
- package/apps/storefront/src/lib/admin/ai/audit.ts +3 -5
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +7 -7
- package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +3 -3
- package/apps/storefront/src/lib/admin/envelope.ts +1 -2
- package/apps/storefront/src/lib/admin/orderClient.ts +3 -5
- package/apps/storefront/src/lib/admin/services/orders.ts +17 -19
- package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +4 -6
- package/apps/storefront/src/lib/admin/viewmodels/settings.ts +1 -1
- package/apps/storefront/src/lib/apps/adminService.ts +14 -4
- package/apps/storefront/src/lib/apps/adminSession.ts +54 -45
- package/apps/storefront/src/lib/apps/apiAuth.ts +2 -2
- package/apps/storefront/src/lib/apps/apiRoute.ts +2 -2
- package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
- package/apps/storefront/src/lib/apps/catalogMapper.ts +1 -1
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
- package/apps/storefront/src/lib/apps/credentials.ts +2 -2
- package/apps/storefront/src/lib/apps/gatewayKeys.ts +4 -4
- 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/healthAggregate.ts +5 -5
- 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/attributionService.ts +2 -2
- package/apps/storefront/src/lib/apps/orders/customerHash.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +12 -12
- package/apps/storefront/src/lib/apps/orders/idempotency.ts +7 -7
- package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +9 -1
- package/apps/storefront/src/lib/apps/orders/orderMapper.ts +2 -2
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +7 -7
- package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
- package/apps/storefront/src/lib/apps/registryService.ts +270 -16
- package/apps/storefront/src/lib/apps/scopes.ts +2 -2
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +4 -4
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +6 -6
- 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/widgets/eligibility.ts +2 -2
- package/apps/storefront/src/lib/apps/widgets/frameProps.ts +3 -3
- package/apps/storefront/src/lib/apps/widgets/launchToken.ts +4 -4
- package/apps/storefront/src/lib/apps/widgets/placements.ts +2 -2
- package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +1 -1
- 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 +11 -11
- package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +6 -14
- package/apps/storefront/src/lib/auth/loginGate.ts +75 -13
- package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +2 -4
- package/apps/storefront/src/lib/auth/operatorSession.ts +113 -0
- package/apps/storefront/src/lib/auth/session.ts +7 -8
- package/apps/storefront/src/lib/auth/totAccessClient.ts +8 -18
- package/apps/storefront/src/lib/basePath.ts +9 -2
- package/apps/storefront/src/lib/blog/access.ts +29 -0
- package/apps/storefront/src/lib/blog/collection.ts +400 -0
- package/apps/storefront/src/lib/blog/markdown.ts +174 -0
- package/apps/storefront/src/lib/blog/provider.ts +62 -3
- package/apps/storefront/src/lib/blog/reactions.ts +87 -0
- package/apps/storefront/src/lib/blog/rss.ts +92 -0
- package/apps/storefront/src/lib/blog/teaser.ts +83 -0
- package/apps/storefront/src/lib/blog/types.ts +34 -0
- package/apps/storefront/src/lib/blog/viewCounts.ts +243 -0
- 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 +14 -2
- package/apps/storefront/src/lib/d1/schema-apps.ts +372 -17
- package/apps/storefront/src/lib/dev/activityIngestToken.ts +2 -4
- package/apps/storefront/src/lib/dev/activityStore.ts +3 -5
- package/apps/storefront/src/lib/dev/apiBase.ts +1 -4
- 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/cockpitStore.ts +1 -9
- package/apps/storefront/src/lib/dev/devDraft.ts +1 -1
- package/apps/storefront/src/lib/dev/previewStatus.ts +9 -21
- package/apps/storefront/src/lib/dev/rendezvousBroker.ts +9 -24
- package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
- package/apps/storefront/src/lib/edgeCache.ts +34 -5
- package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
- package/apps/storefront/src/lib/http/fetchTransport.ts +63 -0
- package/apps/storefront/src/lib/i18n.ts +25 -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/jsonld.ts +118 -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/monitoring/manifest.ts +11 -11
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +30 -0
- package/apps/storefront/src/lib/newsletter/consent.ts +23 -0
- package/apps/storefront/src/lib/newsletter/digest.ts +117 -0
- package/apps/storefront/src/lib/newsletter/resendClient.ts +79 -0
- package/apps/storefront/src/lib/newsletter/send.ts +265 -0
- package/apps/storefront/src/lib/newsletter/subscribers.ts +156 -0
- package/apps/storefront/src/lib/previewReload.ts +57 -1
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +4625 -0
- package/apps/storefront/src/lib/publish/apex-readiness.ts +41 -18
- 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/dispatchHealth.ts +5 -7
- package/apps/storefront/src/lib/publish/domainState.ts +22 -13
- 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/panelPolling.ts +78 -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 +121 -51
- package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
- package/apps/storefront/src/lib/publish/stageBundleUpload.ts +215 -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/rate/kvWindow.ts +53 -0
- package/apps/storefront/src/lib/rawChrome.ts +125 -47
- package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
- package/apps/storefront/src/lib/search/index.ts +2 -2
- package/apps/storefront/src/lib/seo/alternates.ts +42 -0
- package/apps/storefront/src/lib/seo/meta.ts +65 -0
- package/apps/storefront/src/lib/social/golive.ts +212 -0
- package/apps/storefront/src/lib/social/notify.ts +32 -0
- package/apps/storefront/src/lib/social/tokenStore.ts +171 -0
- 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 +32 -0
- package/apps/storefront/src/lib/the-build/readingTime.ts +35 -0
- package/apps/storefront/src/lib/the-build/toc.ts +95 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +1 -1
- package/apps/storefront/src/lib/tot/d1Client.ts +2 -2
- package/apps/storefront/src/lib/tot/totClientInterface.ts +3 -4
- package/apps/storefront/src/lib/webhooks/deliveryStore.ts +16 -17
- package/apps/storefront/src/middleware/index.ts +385 -52
- 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 +25 -1313
- 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/blog/[collection]/[slug]/react.ts +104 -0
- 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/newsletter/[collection]/subscribe.ts +146 -0
- package/apps/storefront/src/pages/api/newsletter/confirm.ts +75 -0
- package/apps/storefront/src/pages/api/newsletter/unsubscribe.ts +90 -0
- package/apps/storefront/src/pages/api/publish/stage-bundle.ts +70 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +20 -2
- package/apps/storefront/src/pages/blog/[slug].astro +99 -19
- package/apps/storefront/src/pages/blog/author/[author].astro +140 -0
- package/apps/storefront/src/pages/blog/category/[category].astro +67 -0
- package/apps/storefront/src/pages/blog/index.astro +4 -2
- package/apps/storefront/src/pages/blog/tag/[tag].astro +65 -0
- package/apps/storefront/src/pages/cockpit.astro +77 -1353
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +15 -11
- 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/search.astro +1 -1
- package/apps/storefront/src/pages/sitemap.xml.ts +21 -1
- 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 +129 -0
- package/apps/storefront/src/pages/the-build/index.astro +146 -0
- package/apps/storefront/src/pages/the-build/rss.xml.ts +33 -0
- package/apps/storefront/src/styles/admin.css +1106 -0
- package/apps/storefront/tsconfig.json +6 -1
- package/package.json +1 -1
- package/packages/public-runtime/package.json +3 -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 +89 -33
- 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/catalog-d1.ts +1 -1
- 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-preview.ts +25 -2
- package/packages/public-runtime/src/customization-runtime.ts +156 -3
- package/packages/public-runtime/src/customization-versioning.ts +10 -0
- 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 +17 -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 +357 -0
- package/packages/public-runtime/src/tenant-assets.ts +143 -4
- package/packages/public-runtime/src/tenant.ts +81 -17
- 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 +14 -4
- package/scripts/dev/ai-edit.mjs +1 -1
- package/scripts/dev/checkout-watch.mjs +2 -2
- package/scripts/dev/file-browser.mjs +4 -6
- package/scripts/dev/file-writer.mjs +3 -3
- package/scripts/dev/git-status.mjs +2 -2
- package/scripts/dev/integration-guardrails.mjs +498 -0
- package/scripts/dev/locate-handler.mjs +3 -3
- package/scripts/dev/port-check.mjs +3 -3
- package/scripts/dev/publish.mjs +5 -9
- package/scripts/dev/safe-path.mjs +2 -2
- package/scripts/dev/shot.mjs +1 -1
- package/scripts/dev/tenant-source-root.mjs +1 -1
- package/scripts/dev/unified-diff.mjs +8 -3
- package/scripts/tot-dev.mjs +8 -11
- package/apps/storefront/src/lib/dev/vcBinding.ts +0 -80
|
@@ -11,7 +11,7 @@ import { getTenantResolver } from "@/config/resolver";
|
|
|
11
11
|
import { referenceTheme } from "@/themes/reference";
|
|
12
12
|
import { mergeTheme, themeToCssVars } from "@/themes/schema";
|
|
13
13
|
import { createToTClient } from "@/lib/tot/ToTClient";
|
|
14
|
-
import { readEnv, readKv, readD1 } from "@/lib/env";
|
|
14
|
+
import { readEnv, readKv, readD1, readR2 } from "@/lib/env";
|
|
15
15
|
import { resolveTenantRouting } from "@/lib/tenantRouting";
|
|
16
16
|
import { createContentProvider, DevDraftContentProvider } from "@/lib/storyblok/provider";
|
|
17
17
|
import { DEV_DRAFT_COOKIE, devDraftKey, verifyDevDraftToken } from "@/lib/dev/devDraft";
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
readCacheVersion,
|
|
22
22
|
getCachedPage,
|
|
23
23
|
putCachedPage,
|
|
24
|
+
staticBundleCacheKey,
|
|
24
25
|
} from "@/lib/edgeCache";
|
|
25
26
|
import { STATIC_SCRIPT_HASHES, STATIC_STYLE_HASHES } from "@/lib/cspStaticHashes";
|
|
26
27
|
import { applySecurityHeaders } from "@/lib/securityHeaders";
|
|
@@ -31,6 +32,10 @@ import {
|
|
|
31
32
|
STOREFRONT_CSP_ALLOWLIST,
|
|
32
33
|
checkoutCspAdditions,
|
|
33
34
|
appWidgetCspAdditions,
|
|
35
|
+
embedCspAdditions,
|
|
36
|
+
foldIntegrationCsp,
|
|
37
|
+
embedsArtifactPath,
|
|
38
|
+
resolveEffectiveEmbeds,
|
|
34
39
|
CSP_REPORT_GROUP,
|
|
35
40
|
CSP_REPORT_PATH,
|
|
36
41
|
readPublishedArtifact,
|
|
@@ -38,23 +43,40 @@ import {
|
|
|
38
43
|
resolveTenantFeatures,
|
|
39
44
|
resolveCapabilities,
|
|
40
45
|
capabilitiesArtifactPath,
|
|
46
|
+
resolveChannelDigest,
|
|
47
|
+
resolveServableChannelVersion,
|
|
48
|
+
channelFromEnv,
|
|
49
|
+
serveStaticBundle,
|
|
50
|
+
resolveEnvironmentOnlyPlan,
|
|
51
|
+
resolveInterimBridgePlan,
|
|
52
|
+
compileIntegrationCspFromPlan,
|
|
53
|
+
collectDeclaredCspProfiles,
|
|
54
|
+
INTEGRATION_NONCE_PLACEHOLDER,
|
|
41
55
|
} from "@tot/public-runtime";
|
|
42
|
-
import type { CapabilityConfig } from "@tot/public-runtime";
|
|
56
|
+
import type { CapabilityConfig, StaticBundleR2BucketLike, BrowserEnvironment } from "@tot/public-runtime";
|
|
57
|
+
import { loadTenantIntegrationDeclarations } from "@/lib/integration/tenantDeclarations";
|
|
58
|
+
import { loadActiveManagedInstallDeclarations } from "@/lib/integration/managedInstallDeclarations";
|
|
59
|
+
import { fromD1 } from "@/lib/d1/catalog";
|
|
43
60
|
import { resolveTenantCommerce } from "@/lib/checkoutCommerce";
|
|
44
61
|
import { evaluateDemoGate, hostInGateList } from "@/lib/demoGate";
|
|
45
62
|
import {
|
|
46
63
|
evaluateLoginGate,
|
|
47
64
|
parseGateMode,
|
|
48
65
|
isAuthPath,
|
|
66
|
+
isAdminPath,
|
|
49
67
|
sessionAdmitsHost,
|
|
50
68
|
sessionHostCapability,
|
|
51
69
|
PUBLIC_HOME_TENANT_ID,
|
|
52
70
|
} from "@/lib/auth/loginGate";
|
|
71
|
+
import { resolveAdminLanding, isAdminSelectPath } from "@/lib/auth/adminLanding";
|
|
53
72
|
import { gatedHoldingResponse } from "@/lib/auth/gatePage";
|
|
54
73
|
import { readSession, KvSessionStore, SESSION_COOKIE } from "@/lib/auth/session";
|
|
55
74
|
import { isMaintenanceOn, maintenanceResponse } from "@/lib/maintenance";
|
|
56
75
|
import { HEALTH_PATH, healthResponse } from "@/lib/health";
|
|
57
76
|
import { observeDeployVersion } from "@/lib/activity/deployVersion";
|
|
77
|
+
import { requestBrowserEnvironment, deriveSiteSurface, cspHeaderName } from "@/lib/integration/browserEnvironment";
|
|
78
|
+
import { freshNonce, stampResponseNonce } from "@/lib/integration/nonceStamp";
|
|
79
|
+
import { recordActivity } from "@/lib/activity/recordActivity";
|
|
58
80
|
import { primeActivityKillSwitch } from "@/lib/activity/killSwitch";
|
|
59
81
|
import type { DeepPartial, ThemeTokens } from "@tot/public-runtime";
|
|
60
82
|
|
|
@@ -64,19 +86,19 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
64
86
|
const host =
|
|
65
87
|
request.headers.get("host") ?? url.host ?? "localhost";
|
|
66
88
|
|
|
67
|
-
// ---- Activity telemetry kill switch
|
|
89
|
+
// ---- Activity telemetry kill switch -----------------------------
|
|
68
90
|
// Prime the ONE `ACTIVITY_TELEMETRY_DISABLED` flag from the (async) Worker env
|
|
69
91
|
// once per isolate, BEFORE the first emit, so every telemetry chokepoint in this
|
|
70
92
|
// isolate (deploy-provenance, recordActivity, ingest) observes it. Fail-open,
|
|
71
93
|
// never throws. Off by default (unset ⇒ telemetry on).
|
|
72
94
|
await primeActivityKillSwitch();
|
|
73
95
|
|
|
74
|
-
// ---- Deploy provenance
|
|
96
|
+
// ---- Deploy provenance — worker_commit / deploy.version_observed -
|
|
75
97
|
// Once per worker isolate (cold start), emit `deploy.version_observed` carrying
|
|
76
|
-
// this build's commit SHA
|
|
77
|
-
// tip). Idempotent per isolate, best-effort, never throws — so it's safe
|
|
78
|
-
// the very front door, ahead of /health, and fires regardless of
|
|
79
|
-
// A no-op when the
|
|
98
|
+
// this build's commit SHA for downstream deploy-skew derivation (running build vs
|
|
99
|
+
// umbrella tip). Idempotent per isolate, best-effort, never throws — so it's safe
|
|
100
|
+
// here at the very front door, ahead of /health, and fires regardless of
|
|
101
|
+
// Host/tenant/gate. A no-op when the kill switch above is set.
|
|
80
102
|
observeDeployVersion();
|
|
81
103
|
|
|
82
104
|
// ---- /health — liveness + deploy-identity probe ---------------------------
|
|
@@ -164,16 +186,67 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
164
186
|
}
|
|
165
187
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
166
188
|
const admitted = sessionAdmitsHost(viewerRecord, tenant.appDomain, nowSeconds);
|
|
189
|
+
// QA content-bypass: only when the server has configured DEMO_GATE_BYPASS_TOKEN
|
|
190
|
+
// (never in a prod slot) AND the request presents a matching X-Demo-Gate-Bypass
|
|
191
|
+
// header. Unset token ⇒ false ⇒ fully inert; a client cannot self-grant. The
|
|
192
|
+
// public-content-only restriction (admin/cockpit/api excluded) is enforced
|
|
193
|
+
// inside evaluateLoginGate via isBypassPrivilegedPath.
|
|
194
|
+
const bypassToken = await readEnv("DEMO_GATE_BYPASS_TOKEN");
|
|
195
|
+
const qaBypass =
|
|
196
|
+
!!bypassToken && request.headers.get("x-demo-gate-bypass") === bypassToken;
|
|
197
|
+
|
|
198
|
+
// Canonical admin tenant landing: an AUTHENTICATED viewer who is not a
|
|
199
|
+
// member/staff of THIS host's tenant must be routed to their OWN tenant
|
|
200
|
+
// (auto when sole) or the tenant picker (when several / none) — never bounced
|
|
201
|
+
// into a sign-in loop and never silently shown the wrong tenant. Only for
|
|
202
|
+
// admin paths, only for an authed non-member, and never on the /admin/select
|
|
203
|
+
// picker itself (whose whole purpose is to be reachable by such a viewer).
|
|
204
|
+
if (
|
|
205
|
+
isAdminPath(url.pathname) &&
|
|
206
|
+
!isAdminSelectPath(url.pathname) &&
|
|
207
|
+
!admitted &&
|
|
208
|
+
viewerRecord &&
|
|
209
|
+
!qaBypass
|
|
210
|
+
) {
|
|
211
|
+
const landing = resolveAdminLanding({
|
|
212
|
+
record: viewerRecord,
|
|
213
|
+
routedTenant: tenant.appDomain,
|
|
214
|
+
basePath,
|
|
215
|
+
intended: url.pathname + url.search,
|
|
216
|
+
nowSeconds,
|
|
217
|
+
});
|
|
218
|
+
if (landing.action === "auto" || landing.action === "select") {
|
|
219
|
+
return context.redirect(landing.location, 303);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// The /admin/select picker requires only a VALID SESSION (any authed viewer),
|
|
224
|
+
// not membership of the routed tenant — it lists the viewer's OWN tenants. So
|
|
225
|
+
// it passes the gate on the strength of the session alone.
|
|
226
|
+
const hasValidSession =
|
|
227
|
+
admitted || (isAdminSelectPath(url.pathname) && !!viewerRecord);
|
|
167
228
|
const decision = evaluateLoginGate({
|
|
168
229
|
host,
|
|
169
230
|
pathname: url.pathname,
|
|
170
231
|
search: url.search,
|
|
171
232
|
mode: gateMode,
|
|
172
233
|
gatedHosts: demoGateHosts,
|
|
173
|
-
hasValidSession
|
|
234
|
+
hasValidSession,
|
|
174
235
|
tenantExempt,
|
|
236
|
+
qaBypass,
|
|
175
237
|
});
|
|
176
238
|
if (decision.action === "redirect") {
|
|
239
|
+
// ADMIN (and any privileged surface) hard-redirects to sign-in rather than
|
|
240
|
+
// rendering the merchant-facing "Coming soon" holding page: an unauthenticated
|
|
241
|
+
// admin visitor must land on the sign-in prompt, never an ambiguous 200 that
|
|
242
|
+
// reads as "this store isn't ready." The holding page's whole audience is a
|
|
243
|
+
// public browser of a gated STOREFRONT; it is the wrong signal for /admin. The
|
|
244
|
+
// gate already decided to withhold — this only changes HOW (303 vs holding
|
|
245
|
+
// page); it never weakens the gate. `decision.location` already carries
|
|
246
|
+
// `?next=` so the admin returns to exactly where they were headed.
|
|
247
|
+
if (isAdminPath(url.pathname)) {
|
|
248
|
+
return context.redirect(decision.location, 303);
|
|
249
|
+
}
|
|
177
250
|
// Withhold content but offer sign-in (carrying the intended destination).
|
|
178
251
|
return gatedHoldingResponse(decision.location);
|
|
179
252
|
}
|
|
@@ -183,7 +256,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
183
256
|
// in: a member sees their tenants[] role; the owner of their own tenant sees
|
|
184
257
|
// the resource-scoped session's capability; a ToT-staff viewer admitted via
|
|
185
258
|
// an explicit vendor selection sees the staffRoles-scoped capability. The
|
|
186
|
-
// ship gates (
|
|
259
|
+
// ship gates (decideIsOwner/resolveShipPrincipal) read this capability,
|
|
187
260
|
// so binding it here keeps owner-resolution server-side + tenant-correct.
|
|
188
261
|
const hostCapability = sessionHostCapability(viewerRecord, tenant.appDomain, nowSeconds);
|
|
189
262
|
locals.viewer = {
|
|
@@ -199,13 +272,156 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
199
272
|
const isPreviewHost = host.toLowerCase().startsWith("preview.");
|
|
200
273
|
const queryToken = url.searchParams.get("__preview");
|
|
201
274
|
const cookieToken = context.cookies.get("tot_preview")?.value ?? null;
|
|
202
|
-
// Per-developer AI-edit draft
|
|
275
|
+
// Per-developer AI-edit draft: the signed
|
|
203
276
|
// tot_dev_draft cookie's mere PRESENCE opts the request out of the shared page
|
|
204
277
|
// cache below (a draft view must never be cached publicly); it's verified —
|
|
205
278
|
// signature, expiry, tenant binding — before the overlay is applied further down.
|
|
206
279
|
const draftCookie = context.cookies.get(DEV_DRAFT_COOKIE)?.value ?? null;
|
|
207
280
|
|
|
208
|
-
//
|
|
281
|
+
// Active managed installs share the same declaration/resolver/CSP/broker/cache
|
|
282
|
+
// plane as tenant-config integrations. Read only canonical active rows and their
|
|
283
|
+
// current immutable snapshot; candidates, suspended rows, and drafts produce no
|
|
284
|
+
// declaration. A missing/unmigrated binding fails closed to no managed effects.
|
|
285
|
+
const appsD1 = await readD1("STOREFRONT_APPS_DB");
|
|
286
|
+
let managedIntegrationDeclarations = [] as Awaited<ReturnType<typeof loadActiveManagedInstallDeclarations>>;
|
|
287
|
+
if (appsD1) {
|
|
288
|
+
try {
|
|
289
|
+
managedIntegrationDeclarations = await loadActiveManagedInstallDeclarations(fromD1(appsD1), tenant.tenant_id);
|
|
290
|
+
} catch {
|
|
291
|
+
managedIntegrationDeclarations = [];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ---- Digest-addressed static-bundle serving (ADR 0012) --------------------
|
|
296
|
+
// For a static-publish tenant's PRODUCTION reads only (never preview/test — the
|
|
297
|
+
// "Worker serves interactive preview, R2 serves prod" split), read the live
|
|
298
|
+
// bundle straight out of R2 instead of falling through to SSR. Deliberately
|
|
299
|
+
// BEFORE the edge page cache / catalog / theme / customization machinery below:
|
|
300
|
+
// none of that applies to bundle bytes served verbatim, and this path must
|
|
301
|
+
// never SSR-fallthrough once it's eligible (a missing digest/object fails
|
|
302
|
+
// closed, see `serveStaticBundle`), so it has to fully own the response, not
|
|
303
|
+
// degrade into the normal render.
|
|
304
|
+
// Host-resolved reads only (`basePath === ""`): bundle bytes are served
|
|
305
|
+
// VERBATIM with root-absolute links, so a path-prefixed view
|
|
306
|
+
// (/<domain>/… on the shared .store host) would escape its prefix on every
|
|
307
|
+
// href — those views keep SSR, which rewrites hrefs through basePath.
|
|
308
|
+
const staticBundleEligible =
|
|
309
|
+
tenant.staticPublish != null &&
|
|
310
|
+
tenant.staticPublish.enabled !== false &&
|
|
311
|
+
basePath === "" &&
|
|
312
|
+
request.method.toUpperCase() === "GET" &&
|
|
313
|
+
!isPreviewHost &&
|
|
314
|
+
!queryToken &&
|
|
315
|
+
!cookieToken &&
|
|
316
|
+
!draftCookie &&
|
|
317
|
+
managedIntegrationDeclarations.length === 0 &&
|
|
318
|
+
!url.pathname.startsWith("/api/");
|
|
319
|
+
if (staticBundleEligible) {
|
|
320
|
+
const bundleBucket = await readR2("STATIC_BUNDLES");
|
|
321
|
+
if (bundleBucket) {
|
|
322
|
+
const kvGet = kv ? (key: string) => kv.get(key) : async () => null;
|
|
323
|
+
// The R2 bundle key is the `treeDigest`, NOT the `live` channel versionId
|
|
324
|
+
// (the Gitea sha). Read the reconciled digest companion (`channelDigests.live`)
|
|
325
|
+
// — `gatedPromote`/`rollbackCandidateAtomic` write it in the same CAS flip as
|
|
326
|
+
// the versionId. No digest = this tenant has never staged a bundle — SSR
|
|
327
|
+
// serves it (a static-publish tenant must be able to bootstrap, and its
|
|
328
|
+
// app surfaces must never be downed by an unstaged static plane).
|
|
329
|
+
const digest = await resolveChannelDigest(kvGet, tenant.tenant_id, "live");
|
|
330
|
+
if (digest) {
|
|
331
|
+
const bundleCacheKey = cache
|
|
332
|
+
? staticBundleCacheKey(tenant.tenant_id, digest, url.pathname, url.search)
|
|
333
|
+
: null;
|
|
334
|
+
if (bundleCacheKey && cache) {
|
|
335
|
+
const hit = await getCachedPage(cache, bundleCacheKey);
|
|
336
|
+
if (hit) return hit;
|
|
337
|
+
}
|
|
338
|
+
const bundleResponse = await serveStaticBundle(
|
|
339
|
+
{
|
|
340
|
+
bucket: bundleBucket as unknown as StaticBundleR2BucketLike,
|
|
341
|
+
digest,
|
|
342
|
+
onFailClosed: (detail) => {
|
|
343
|
+
console.error(
|
|
344
|
+
`[static-bundle] fail-closed for ${tenant.tenant_id} ${url.pathname}: ${detail}`,
|
|
345
|
+
);
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
url.pathname,
|
|
349
|
+
);
|
|
350
|
+
// `null` = the manifest proves this path is outside the bundle's static
|
|
351
|
+
// surface (an app surface like /admin) — SSR owns it below.
|
|
352
|
+
if (bundleResponse) {
|
|
353
|
+
if (bundleCacheKey && cache) {
|
|
354
|
+
await putCachedPage(cache, bundleCacheKey, bundleResponse);
|
|
355
|
+
}
|
|
356
|
+
return bundleResponse;
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
console.error(
|
|
360
|
+
`[static-bundle] no live digest for ${tenant.tenant_id} — SSR until a bundle is staged`,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// No STATIC_BUNDLES binding (plain `astro dev`, or unconfigured) —
|
|
365
|
+
// degrading to SSR here rather than a hard failure keeps `astro dev`/tests
|
|
366
|
+
// usable without provisioning R2.
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ---- Tenant-local integration materialization (ext-kernel-09) --------------
|
|
370
|
+
// Resolve a tenant's OWN declared, exact-version integrations into the effective
|
|
371
|
+
// plan the render engine serves, THROUGH the interim marketing bridge (which
|
|
372
|
+
// refuses a regulated tenant and fail-closes an invalid declaration set). Done
|
|
373
|
+
// HERE, before the edge-cache read, so a declared tenant's `planRevision` folds
|
|
374
|
+
// into the page-cache key below (a rollback/uninstall resolves a new revision →
|
|
375
|
+
// the old warm entry is unreachable → a clean re-render without the removed
|
|
376
|
+
// origins, never a TTL wait) AND its reviewed CSP origins reach the strict header.
|
|
377
|
+
//
|
|
378
|
+
// An UNDECLARED tenant does NONE of this: `loadTenantIntegrationDeclarations`
|
|
379
|
+
// returns the empty set, so `integrationCsp` stays undefined (the page-cache key
|
|
380
|
+
// is byte-identical to the non-integration form) and the environment-only plan is
|
|
381
|
+
// resolved below exactly as before — zero cost.
|
|
382
|
+
let resolvedBrowserEnvironment: BrowserEnvironment | null = null;
|
|
383
|
+
const integrationDeclarations = [
|
|
384
|
+
...loadTenantIntegrationDeclarations(tenant),
|
|
385
|
+
...managedIntegrationDeclarations,
|
|
386
|
+
];
|
|
387
|
+
if (integrationDeclarations.length > 0) {
|
|
388
|
+
const declPreviewSecret = (await readEnv("CUSTOMIZATION_PREVIEW_SECRET")) ?? null;
|
|
389
|
+
const declEnv = await resolveCustomizationEnv({
|
|
390
|
+
isPreviewHost,
|
|
391
|
+
tenantId: tenant.tenant_id,
|
|
392
|
+
token: queryToken ?? cookieToken,
|
|
393
|
+
secret: declPreviewSecret,
|
|
394
|
+
nowSeconds: Math.floor(Date.now() / 1000),
|
|
395
|
+
pathname: url.pathname,
|
|
396
|
+
});
|
|
397
|
+
const declFeatures = resolveTenantFeatures(tenant);
|
|
398
|
+
resolvedBrowserEnvironment = requestBrowserEnvironment({
|
|
399
|
+
host,
|
|
400
|
+
isPreviewHost,
|
|
401
|
+
customizationEnv: declEnv.env,
|
|
402
|
+
canonicalHost: tenant.canonicalDomain || host,
|
|
403
|
+
siteSurface: deriveSiteSurface({ catalog: declFeatures.catalog, pathname: renderPath }),
|
|
404
|
+
});
|
|
405
|
+
const bridge = resolveInterimBridgePlan({
|
|
406
|
+
tenantPublicId: tenant.tenant_id,
|
|
407
|
+
tenant,
|
|
408
|
+
environment: resolvedBrowserEnvironment,
|
|
409
|
+
declarations: integrationDeclarations,
|
|
410
|
+
});
|
|
411
|
+
if (bridge.ok) {
|
|
412
|
+
// A resolved plan serves; its compiled CSP additions + `planRevision` reach the
|
|
413
|
+
// cache key and the header. A refusal (regulated) or an invalid set leaves the
|
|
414
|
+
// plan unset here → the environment-only fallback below fills it (nothing loads).
|
|
415
|
+
locals.integrationPlan = bridge.plan;
|
|
416
|
+
const compiled = compileIntegrationCspFromPlan(
|
|
417
|
+
bridge.plan,
|
|
418
|
+
collectDeclaredCspProfiles(integrationDeclarations),
|
|
419
|
+
);
|
|
420
|
+
if (compiled.ok) locals.integrationCsp = compiled.value;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ---- Edge page cache: short-circuit production browse reads on a hit,
|
|
209
425
|
// BEFORE building the client / theme / rendering. Misses store the final
|
|
210
426
|
// response at the end of this middleware.
|
|
211
427
|
//
|
|
@@ -224,12 +440,27 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
224
440
|
let pageKey: Request | null = null;
|
|
225
441
|
if (pageCacheEligible && cache && kv) {
|
|
226
442
|
const version = await readCacheVersion(kv, tenant.tenant_id);
|
|
227
|
-
|
|
443
|
+
// Fold the resolved integration plan revision into the key so a warm entry is
|
|
444
|
+
// version-scoped: a rollback/uninstall resolves a new revision → old entry
|
|
445
|
+
// unreachable → clean re-render without the removed origins (never a TTL wait).
|
|
446
|
+
// Undefined until the integration plane is resolved for this tenant — the key
|
|
447
|
+
// is then byte-identical to the non-integration form. The store below reuses
|
|
448
|
+
// this same `pageKey`, so read and write stay consistent.
|
|
449
|
+
pageKey = pageCacheKey(
|
|
450
|
+
tenant.tenant_id,
|
|
451
|
+
url.pathname,
|
|
452
|
+
url.search,
|
|
453
|
+
version,
|
|
454
|
+
locals.integrationCsp?.planRevision,
|
|
455
|
+
);
|
|
228
456
|
const hit = await getCachedPage(cache, pageKey);
|
|
229
|
-
|
|
457
|
+
// The cached artifact is nonce-free (carries the placeholder). Stamp a fresh
|
|
458
|
+
// per-response nonce into its body + CSP header so a warm hit is served with a
|
|
459
|
+
// brand-new nonce, never the one an earlier response rendered with.
|
|
460
|
+
if (hit) return await stampResponseNonce(hit, freshNonce());
|
|
230
461
|
}
|
|
231
462
|
|
|
232
|
-
// Catalog source selection (
|
|
463
|
+
// Catalog source selection (docs/architecture/product-catalog.md).
|
|
233
464
|
// A store's product source graduates over its life: JSON fixtures (dev default) ->
|
|
234
465
|
// D1 edge replica (staging) -> hosted ToT catalog (go-live). Here we pick the read
|
|
235
466
|
// backend: USE_D1_CATALOG=1 (+ a CATALOG_DB binding) reads the D1 edge replica;
|
|
@@ -249,7 +480,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
249
480
|
? `${proto}://${host}${basePath}`
|
|
250
481
|
: `https://${tenant.canonicalDomain}`;
|
|
251
482
|
|
|
252
|
-
// Live vs test customization
|
|
483
|
+
// Live vs test customization. Production hosts always serve "live" and
|
|
253
484
|
// ignore any token; a "preview." host is auth-gated by a signed, tenant-bound,
|
|
254
485
|
// short-TTL preview token (?__preview= on first hit, then the tot_preview
|
|
255
486
|
// cookie). Test is never indexable (X-Robots-Tag below).
|
|
@@ -262,6 +493,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
262
493
|
token: previewToken,
|
|
263
494
|
secret: previewSecret,
|
|
264
495
|
nowSeconds,
|
|
496
|
+
pathname: url.pathname,
|
|
265
497
|
});
|
|
266
498
|
if (customization.reject) {
|
|
267
499
|
return new Response(customization.reject.message, {
|
|
@@ -290,6 +522,28 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
290
522
|
// pointer fall back to the legacy per-path selection — no behavior change.
|
|
291
523
|
const kvGet = kv ? (key: string) => kv.get(key) : async () => null;
|
|
292
524
|
|
|
525
|
+
// Self-heal a DANGLING channel pointer: the pointer for this env's channel may
|
|
526
|
+
// name a version that was never built (or whose manifest was evicted).
|
|
527
|
+
// `readVersionedArtifact` already serves last-built content instead of emptying
|
|
528
|
+
// the surface; here we do the once-per-request detection to ENQUEUE a rebuild of
|
|
529
|
+
// the dangling version. Fail-open: a KV hiccup never blocks the render (the reads
|
|
530
|
+
// below still run).
|
|
531
|
+
if (kv) {
|
|
532
|
+
const channel = channelFromEnv(customization.env);
|
|
533
|
+
await resolveServableChannelVersion(kvGet, tenant.tenant_id, channel, ({ versionId }) => {
|
|
534
|
+
recordActivity({
|
|
535
|
+
action: "preview.build.requested",
|
|
536
|
+
actor: { kind: "system", id: "storefront" },
|
|
537
|
+
source: "server",
|
|
538
|
+
outcome: { status: "succeeded" },
|
|
539
|
+
scope: { tenantId: tenant.tenant_id },
|
|
540
|
+
payload: { args: { changeId: versionId } },
|
|
541
|
+
});
|
|
542
|
+
}).catch(() => {
|
|
543
|
+
/* fail-open: self-heal detection never breaks the request */
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
293
547
|
// Verify the developer-draft cookie (signature + expiry + tenant binding) — the
|
|
294
548
|
// overlay below only engages for the developer who minted the draft, on THIS
|
|
295
549
|
// tenant. Same secret + trust model as the preview token; invalid/expired/foreign
|
|
@@ -348,9 +602,34 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
348
602
|
}
|
|
349
603
|
const capabilities = resolveCapabilities(tenant, publishedCaps);
|
|
350
604
|
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
|
|
605
|
+
// Embed opt-in (self-serve, config-as-code on the same spine as capabilities):
|
|
606
|
+
// the build-time TenantConfig.embeds default is overlaid with the tenant's
|
|
607
|
+
// published embeds.json for THIS env, then filtered to the vetted catalog. A
|
|
608
|
+
// tenant enables a catalogued embed by committing embeds.json — no platform
|
|
609
|
+
// change — but can never introduce a non-catalog origin.
|
|
610
|
+
let publishedEmbeds: string[] | null = null;
|
|
611
|
+
const publishedEmbedsRaw = await readPublishedArtifact(
|
|
612
|
+
kvGet,
|
|
613
|
+
tenant.tenant_id,
|
|
614
|
+
customization.env,
|
|
615
|
+
embedsArtifactPath(tenant.tenant_id),
|
|
616
|
+
);
|
|
617
|
+
if (publishedEmbedsRaw) {
|
|
618
|
+
try {
|
|
619
|
+
const parsed = JSON.parse(publishedEmbedsRaw);
|
|
620
|
+
if (Array.isArray(parsed)) publishedEmbeds = parsed.filter((s) => typeof s === "string");
|
|
621
|
+
} catch {
|
|
622
|
+
/* malformed published artifact — keep the build-time default */
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const effectiveEmbeds = resolveEffectiveEmbeds(tenant.embeds, publishedEmbeds);
|
|
626
|
+
|
|
627
|
+
// Nonce placeholder: the platform's inline tags AND the CSP header are rendered
|
|
628
|
+
// with this sentinel, never a live nonce (ADR 0018). A fresh per-response nonce
|
|
629
|
+
// is stamped in at serve time (stampResponseNonce below), so the cached artifact
|
|
630
|
+
// is nonce-free and every response — cold render or warm cache hit — gets a fresh
|
|
631
|
+
// matching nonce/CSP pair.
|
|
632
|
+
const cspNonce = INTEGRATION_NONCE_PLACEHOLDER;
|
|
354
633
|
|
|
355
634
|
locals.tenant = tenant;
|
|
356
635
|
// Effective capability flags for this tenant (commerce defaults unless the
|
|
@@ -393,6 +672,31 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
393
672
|
// tenant-agnostic; the Layout uses this to keep root-absolute links inside
|
|
394
673
|
// the tenant prefix on the client.
|
|
395
674
|
locals.basePath = basePath;
|
|
675
|
+
// Renderer-owned four-stage browser environment (ADR 0014 E1 / 0017), computed
|
|
676
|
+
// ONCE here from authoritative request state and serialized before app code by
|
|
677
|
+
// both document mounts (Layout.astro + rawHtmlResponse). The candidate render
|
|
678
|
+
// paths build their own `candidate`-stage object instead of this one.
|
|
679
|
+
// Reuse the environment resolved for a declared tenant's integration plane above
|
|
680
|
+
// (identical inputs → identical object) so the plan's environment and this object
|
|
681
|
+
// never diverge; an undeclared tenant computes it here for the first time.
|
|
682
|
+
locals.browserEnvironment =
|
|
683
|
+
resolvedBrowserEnvironment ??
|
|
684
|
+
requestBrowserEnvironment({
|
|
685
|
+
host,
|
|
686
|
+
isPreviewHost,
|
|
687
|
+
customizationEnv: customization.env,
|
|
688
|
+
canonicalHost: tenant.canonicalDomain || host,
|
|
689
|
+
siteSurface: deriveSiteSurface({ catalog: locals.features.catalog, pathname: renderPath }),
|
|
690
|
+
});
|
|
691
|
+
// Resolved effective plan (ext-kernel-03) for this request — the fixed-phase
|
|
692
|
+
// render engine (ext-kernel-04) consumes it in BOTH document mounts (Layout.astro
|
|
693
|
+
// + rawHtmlResponse). A declared tenant's plan is already resolved above (through
|
|
694
|
+
// the interim bridge); an undeclared tenant — or one the bridge refused / failed
|
|
695
|
+
// closed — resolves the environment-only plan here, so a page with no serving
|
|
696
|
+
// integration emits no vendor runtime/markup.
|
|
697
|
+
locals.integrationPlan =
|
|
698
|
+
locals.integrationPlan ??
|
|
699
|
+
resolveEnvironmentOnlyPlan(locals.browserEnvironment, tenant.tenant_id);
|
|
396
700
|
|
|
397
701
|
// /dev was renamed to /cockpit (2026-07-20). 302-redirect any old /dev link —
|
|
398
702
|
// host-routed (/dev) or path-routed (/<vendor>/dev) — to the new path, preserving
|
|
@@ -421,7 +725,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
421
725
|
nonce: cspNonce,
|
|
422
726
|
});
|
|
423
727
|
|
|
424
|
-
// Baseline security headers
|
|
728
|
+
// Baseline security headers on every response — set before the page-cache
|
|
425
729
|
// store below so cached copies carry them too.
|
|
426
730
|
applySecurityHeaders(response.headers);
|
|
427
731
|
|
|
@@ -445,9 +749,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
445
749
|
// site unstyled. The BUILD is CSP-clean (styles are externalized via
|
|
446
750
|
// `inlineStylesheets:"never"`), so we enforce there and only REPORT in dev —
|
|
447
751
|
// violations are still surfaced, but styling/HMR work locally.
|
|
448
|
-
const cspHeader = import.meta.env.DEV
|
|
449
|
-
? "Content-Security-Policy-Report-Only"
|
|
450
|
-
: "Content-Security-Policy";
|
|
752
|
+
const cspHeader = cspHeaderName(import.meta.env.DEV);
|
|
451
753
|
response.headers.set(
|
|
452
754
|
"Reporting-Endpoints",
|
|
453
755
|
`${CSP_REPORT_GROUP}="${CSP_REPORT_PATH}"`,
|
|
@@ -456,40 +758,66 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
456
758
|
// strict policy only when this tenant has a store — so add-to-cart, the
|
|
457
759
|
// sidecart iframe/XHR, and the cross-origin form POST all pass.
|
|
458
760
|
const checkoutCsp = commerce ? checkoutCspAdditions(commerce.origin) : null;
|
|
459
|
-
// Private-app widget origins
|
|
761
|
+
// Private-app widget origins — read back AFTER next() so this reflects
|
|
460
762
|
// exactly the placements that rendered on THIS page, never a wildcard.
|
|
461
763
|
const widgetCsp = appWidgetCspAdditions(locals.activeWidgetPlacements ?? []);
|
|
764
|
+
// Third-party marketing embeds (e.g. a Pipedrive form) the tenant opted into
|
|
765
|
+
// via .tot/config.json `embeds:`. Only scan the HTML body when the tenant
|
|
766
|
+
// declared at least one embed — zero cost for everyone else — and add each
|
|
767
|
+
// provider's origins ONLY if its marker is actually on THIS page.
|
|
768
|
+
// Prefer an opt-in the render handler resolved for a tenant the URL router
|
|
769
|
+
// couldn't (the candidate renderer sets locals.embedOptIn — see candidateRender);
|
|
770
|
+
// otherwise use the router-resolved tenant's effective embeds.
|
|
771
|
+
const embedsForScan = locals.embedOptIn ?? effectiveEmbeds;
|
|
772
|
+
let embedCsp = { scriptSrc: [] as string[], connectSrc: [] as string[], frameSrc: [] as string[] };
|
|
773
|
+
if (
|
|
774
|
+
embedsForScan.length > 0 &&
|
|
775
|
+
(response.headers.get("content-type") ?? "").includes("text/html")
|
|
776
|
+
) {
|
|
777
|
+
embedCsp = embedCspAdditions(embedsForScan, await response.clone().text());
|
|
778
|
+
}
|
|
779
|
+
const cspOptions = {
|
|
780
|
+
nonce: cspNonce,
|
|
781
|
+
...STOREFRONT_CSP_ALLOWLIST,
|
|
782
|
+
// Pin Astro's static inline hydration scripts + island style by hash
|
|
783
|
+
// (they're identical every request); my dynamic inline tags use the nonce.
|
|
784
|
+
scriptSrc: [
|
|
785
|
+
...STOREFRONT_CSP_ALLOWLIST.scriptSrc,
|
|
786
|
+
...STATIC_SCRIPT_HASHES,
|
|
787
|
+
...(checkoutCsp?.scriptSrc ?? []),
|
|
788
|
+
...embedCsp.scriptSrc,
|
|
789
|
+
],
|
|
790
|
+
styleSrc: [
|
|
791
|
+
...STOREFRONT_CSP_ALLOWLIST.styleSrc,
|
|
792
|
+
...STATIC_STYLE_HASHES,
|
|
793
|
+
...(checkoutCsp?.styleSrc ?? []),
|
|
794
|
+
],
|
|
795
|
+
fontSrc: [...STOREFRONT_CSP_ALLOWLIST.fontSrc, ...(checkoutCsp?.fontSrc ?? [])],
|
|
796
|
+
connectSrc: [
|
|
797
|
+
...STOREFRONT_CSP_ALLOWLIST.connectSrc,
|
|
798
|
+
...(checkoutCsp?.connectSrc ?? []),
|
|
799
|
+
...embedCsp.connectSrc,
|
|
800
|
+
],
|
|
801
|
+
imgSrc: [...STOREFRONT_CSP_ALLOWLIST.imgSrc, ...(checkoutCsp?.imgSrc ?? [])],
|
|
802
|
+
frameSrc: [
|
|
803
|
+
...(checkoutCsp?.frameSrc ?? []),
|
|
804
|
+
...widgetCsp.frameSrc,
|
|
805
|
+
...embedCsp.frameSrc,
|
|
806
|
+
],
|
|
807
|
+
formAction: checkoutCsp?.formAction ?? [],
|
|
808
|
+
reportGroup: CSP_REPORT_GROUP,
|
|
809
|
+
reportUri: CSP_REPORT_PATH,
|
|
810
|
+
};
|
|
811
|
+
// Version-scoped integration plane: APPEND the resolved plan's compiled origins
|
|
812
|
+
// (curated adapter profiles + advanced exact origins) to the six contributable
|
|
813
|
+
// directives only — never the base. Absent (no integration) this is a no-op, so
|
|
814
|
+
// the header is byte-identical to a non-integration tenant's.
|
|
462
815
|
response.headers.set(
|
|
463
816
|
cspHeader,
|
|
464
|
-
buildCsp({
|
|
465
|
-
nonce: cspNonce,
|
|
466
|
-
...STOREFRONT_CSP_ALLOWLIST,
|
|
467
|
-
// Pin Astro's static inline hydration scripts + island style by hash
|
|
468
|
-
// (they're identical every request); my dynamic inline tags use the nonce.
|
|
469
|
-
scriptSrc: [
|
|
470
|
-
...STOREFRONT_CSP_ALLOWLIST.scriptSrc,
|
|
471
|
-
...STATIC_SCRIPT_HASHES,
|
|
472
|
-
...(checkoutCsp?.scriptSrc ?? []),
|
|
473
|
-
],
|
|
474
|
-
styleSrc: [
|
|
475
|
-
...STOREFRONT_CSP_ALLOWLIST.styleSrc,
|
|
476
|
-
...STATIC_STYLE_HASHES,
|
|
477
|
-
...(checkoutCsp?.styleSrc ?? []),
|
|
478
|
-
],
|
|
479
|
-
fontSrc: [...STOREFRONT_CSP_ALLOWLIST.fontSrc, ...(checkoutCsp?.fontSrc ?? [])],
|
|
480
|
-
connectSrc: [
|
|
481
|
-
...STOREFRONT_CSP_ALLOWLIST.connectSrc,
|
|
482
|
-
...(checkoutCsp?.connectSrc ?? []),
|
|
483
|
-
],
|
|
484
|
-
imgSrc: [...STOREFRONT_CSP_ALLOWLIST.imgSrc, ...(checkoutCsp?.imgSrc ?? [])],
|
|
485
|
-
frameSrc: [...(checkoutCsp?.frameSrc ?? []), ...widgetCsp.frameSrc],
|
|
486
|
-
formAction: checkoutCsp?.formAction ?? [],
|
|
487
|
-
reportGroup: CSP_REPORT_GROUP,
|
|
488
|
-
reportUri: CSP_REPORT_PATH,
|
|
489
|
-
}),
|
|
817
|
+
buildCsp(foldIntegrationCsp(cspOptions, locals.integrationCsp?.additions ?? {})),
|
|
490
818
|
);
|
|
491
819
|
|
|
492
|
-
//
|
|
820
|
+
// Store the fully-rendered, CSP-stamped response for the next reader. Only
|
|
493
821
|
// production (no preview token reached here) cacheable GETs have a pageKey;
|
|
494
822
|
// putCachedPage additionally skips non-200, non-HTML, and cookie-bearing
|
|
495
823
|
// responses. Also expose Cache-Control to any fronting CDN.
|
|
@@ -498,7 +826,12 @@ export const onRequest = defineMiddleware(async (context, next) => {
|
|
|
498
826
|
"Cache-Control",
|
|
499
827
|
"public, s-maxage=300, stale-while-revalidate=86400",
|
|
500
828
|
);
|
|
829
|
+
// Store the nonce-free artifact (the placeholder is still in the body + CSP
|
|
830
|
+
// header) BEFORE stamping — so the cache never holds a live/reusable nonce.
|
|
501
831
|
await putCachedPage(cache, pageKey, response);
|
|
502
832
|
}
|
|
503
|
-
|
|
833
|
+
// Stamp a fresh per-response nonce into this cold response's body + CSP header
|
|
834
|
+
// (the warm-hit path above stamps its own). Both paths therefore serve a fresh,
|
|
835
|
+
// matching nonce/CSP pair, and no nonce is reused across responses (ADR 0018).
|
|
836
|
+
return await stampResponseNonce(response, freshNonce());
|
|
504
837
|
});
|
|
@@ -43,8 +43,13 @@ const rawHtml = await content.getPageHtml(slug);
|
|
|
43
43
|
if (rawHtml && rawHtmlPolicy(tenant).allowed) {
|
|
44
44
|
// Shared chrome: a body-only fragment gets wrapped in the tenant's chrome.html;
|
|
45
45
|
// a full document serves verbatim (backward-compatible). See rawMarketingHtml.ts.
|
|
46
|
-
const chromeWrapper = await content.getChromeHtml();
|
|
47
|
-
return rawHtmlResponse(rawHtml, {
|
|
46
|
+
const chromeWrapper = await content.getChromeHtml(`/${slug}`);
|
|
47
|
+
return rawHtmlResponse(rawHtml, {
|
|
48
|
+
nonce: Astro.locals.cspNonce,
|
|
49
|
+
basePath,
|
|
50
|
+
chromeWrapper,
|
|
51
|
+
plan: Astro.locals.integrationPlan,
|
|
52
|
+
});
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
const page = await content.getPage(slug);
|
|
@@ -59,7 +64,7 @@ const description =
|
|
|
59
64
|
page.seoDescription ||
|
|
60
65
|
(page.intro ? truncate(page.intro, 160) : undefined);
|
|
61
66
|
|
|
62
|
-
// Structured pages
|
|
67
|
+
// Structured pages drive the body from `sections`; legacy pages keep the
|
|
63
68
|
// sanitized `body_html`. Prefer structured whenever a page ships sections.
|
|
64
69
|
const structured = (page.sections?.length ?? 0) > 0;
|
|
65
70
|
|
|
@@ -109,7 +114,7 @@ const updatedLabel = page.updated
|
|
|
109
114
|
{structured && <ProseToc sections={page.sections!} />}
|
|
110
115
|
</header>
|
|
111
116
|
|
|
112
|
-
{/* Body — constrained to a readable measure. Structured pages
|
|
117
|
+
{/* Body — constrained to a readable measure. Structured pages render
|
|
113
118
|
framework widgets from `sections`; legacy pages keep the sanitized HTML. */}
|
|
114
119
|
<div class="container-prose pb-[clamp(3rem,7vw,6rem)] pt-8">
|
|
115
120
|
{structured ? (
|