@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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storefront-side resolution of the renderer-owned four-stage browser
|
|
3
|
+
* environment (`@tot/public-runtime` `resolveBrowserEnvironment`). This is the ONE
|
|
4
|
+
* place request state is turned into the authoritative environment object; both
|
|
5
|
+
* document mounts (Layout.astro and `rawHtmlResponse`) serialize the result, so
|
|
6
|
+
* the object is identical in both by construction.
|
|
7
|
+
*
|
|
8
|
+
* The stage is derived from server facts only — `import.meta.env.DEV`, the request
|
|
9
|
+
* host, the resolved customization environment, and whether this is a per-change
|
|
10
|
+
* candidate render — never a client-supplied value. A candidate render is always
|
|
11
|
+
* `candidate` and never `live`; anything unresolved stays non-live.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
resolveBrowserEnvironment,
|
|
15
|
+
resolveEnvironmentOnlyPlan,
|
|
16
|
+
type BrowserEnvironment,
|
|
17
|
+
type EffectivePlan,
|
|
18
|
+
type IntegrationSiteSurface,
|
|
19
|
+
} from "@tot/public-runtime";
|
|
20
|
+
import { isAdminPath } from "@/lib/auth/loginGate";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The non-secret render revision: the commit SHA this build was cut from (baked at
|
|
24
|
+
* build time, the same value GET /health reports), or `"local"` when there is no
|
|
25
|
+
* git context (dev, vitest). Never a token or signed URL.
|
|
26
|
+
*/
|
|
27
|
+
function platformRevision(): string {
|
|
28
|
+
return typeof __GIT_SHA__ !== "undefined" && __GIT_SHA__ ? __GIT_SHA__ : "local";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A request host is local when it is served on loopback (`astro dev` or a local built Worker). */
|
|
32
|
+
function isLocalHost(host: string): boolean {
|
|
33
|
+
return host.startsWith("localhost") || host.startsWith("127.0.0.1");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Map the resolved tenant + request path to a document surface. Admin/cockpit
|
|
38
|
+
* paths are `admin`; cart/checkout paths are `checkout`; a catalog tenant is
|
|
39
|
+
* `storefront`; a marketing tenant (no catalog) is `marketing`.
|
|
40
|
+
*/
|
|
41
|
+
export function deriveSiteSurface(input: {
|
|
42
|
+
catalog: boolean;
|
|
43
|
+
pathname: string;
|
|
44
|
+
}): IntegrationSiteSurface {
|
|
45
|
+
// Admin + the cockpit control-plane panel (host-routed `/cockpit` or tenant-first
|
|
46
|
+
// `/<tenant>/cockpit`) are the `admin` surface.
|
|
47
|
+
if (isAdminPath(input.pathname) || /^\/(?:[^/]+\/)?cockpit(?:\/|$)/.test(input.pathname)) {
|
|
48
|
+
return "admin";
|
|
49
|
+
}
|
|
50
|
+
if (/^\/(checkout|cart)(\/|$)/.test(input.pathname)) return "checkout";
|
|
51
|
+
return input.catalog ? "storefront" : "marketing";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The normal (non-candidate) per-request environment object, computed once by the
|
|
56
|
+
* middleware. `astro dev` and a Worker served on localhost both resolve to `local`
|
|
57
|
+
* (their CSP-enforcement mode differs — report-only vs enforced — but that is a
|
|
58
|
+
* response-header concern, not part of this object).
|
|
59
|
+
*/
|
|
60
|
+
export function requestBrowserEnvironment(input: {
|
|
61
|
+
host: string;
|
|
62
|
+
isPreviewHost: boolean;
|
|
63
|
+
customizationEnv: "live" | "test";
|
|
64
|
+
canonicalHost: string;
|
|
65
|
+
siteSurface: IntegrationSiteSurface;
|
|
66
|
+
}): BrowserEnvironment {
|
|
67
|
+
return resolveBrowserEnvironment({
|
|
68
|
+
dev: import.meta.env.DEV,
|
|
69
|
+
localHost: isLocalHost(input.host),
|
|
70
|
+
candidate: false,
|
|
71
|
+
previewHost: input.isPreviewHost,
|
|
72
|
+
customizationEnv: input.customizationEnv,
|
|
73
|
+
siteSurface: input.siteSurface,
|
|
74
|
+
canonicalHost: input.canonicalHost,
|
|
75
|
+
revision: platformRevision(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* CSP enforcement mode for this runtime — the axis that stays DISTINCT from the
|
|
81
|
+
* `local` stage. `astro dev` (Node) serves un-nonced Vite `<style>`/`<script>`, so a
|
|
82
|
+
* strict nonce policy would blank the site: it is REPORT-ONLY there (violations
|
|
83
|
+
* still surface). The built Worker is CSP-clean, so the strict policy is ENFORCED —
|
|
84
|
+
* including the built Worker run locally, which is how enforced CSP is proven.
|
|
85
|
+
* Report-only-dev and enforced-built-Worker are both correct; only the mode differs,
|
|
86
|
+
* never the four-stage environment object.
|
|
87
|
+
*/
|
|
88
|
+
export type CspEnforcementMode = "report-only" | "enforced";
|
|
89
|
+
|
|
90
|
+
export function cspEnforcementMode(dev: boolean): CspEnforcementMode {
|
|
91
|
+
return dev ? "report-only" : "enforced";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The response header name that carries the mode: report-only in dev, enforced otherwise. */
|
|
95
|
+
export function cspHeaderName(
|
|
96
|
+
dev: boolean,
|
|
97
|
+
): "Content-Security-Policy-Report-Only" | "Content-Security-Policy" {
|
|
98
|
+
return dev ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The environment object for a per-change candidate render (the cockpit candidate
|
|
103
|
+
* renderer, the revision renderer, and the not-yet-built PR page). The stage is
|
|
104
|
+
* always `candidate` — never `live`, even when the underlying tenant is live — and
|
|
105
|
+
* `revision` is the candidate's own version identity (a versionId / head sha), a
|
|
106
|
+
* public, non-secret value.
|
|
107
|
+
*/
|
|
108
|
+
export function candidateBrowserEnvironment(input: {
|
|
109
|
+
canonicalHost: string;
|
|
110
|
+
revision: string;
|
|
111
|
+
siteSurface?: IntegrationSiteSurface;
|
|
112
|
+
}): BrowserEnvironment {
|
|
113
|
+
return resolveBrowserEnvironment({
|
|
114
|
+
dev: import.meta.env.DEV,
|
|
115
|
+
localHost: false,
|
|
116
|
+
candidate: true,
|
|
117
|
+
previewHost: true,
|
|
118
|
+
customizationEnv: "test",
|
|
119
|
+
siteSurface: input.siteSurface ?? "marketing",
|
|
120
|
+
canonicalHost: input.canonicalHost,
|
|
121
|
+
revision: input.revision,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The resolved effective plan for a candidate render — the `candidate`-stage
|
|
127
|
+
* environment wrapped in an environment-only plan so `rawHtmlResponse` (the raw
|
|
128
|
+
* mount of the fixed-phase engine) renders the same renderer-owned phases a live
|
|
129
|
+
* page gets. No integration declarations are materialized on the candidate hot
|
|
130
|
+
* path, so this is environment-only until that wiring lands.
|
|
131
|
+
*/
|
|
132
|
+
export function candidateIntegrationPlan(
|
|
133
|
+
input: { canonicalHost: string; revision: string; siteSurface?: IntegrationSiteSurface },
|
|
134
|
+
tenantPublicId: string,
|
|
135
|
+
): EffectivePlan {
|
|
136
|
+
return resolveEnvironmentOnlyPlan(candidateBrowserEnvironment(input), tenantPublicId);
|
|
137
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Materialize active managed-analytics installs into the render plane's existing
|
|
3
|
+
* declaration seam. `app_installs` owns status/manifest and MAX(seq) owns the
|
|
4
|
+
* active public config; candidates and drafts are never read here.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
ENVIRONMENT_SCHEMA_VERSION,
|
|
8
|
+
buildPlatformAdapterDeclaration,
|
|
9
|
+
validateIntegrationManifest,
|
|
10
|
+
type IntegrationDeclarationInput,
|
|
11
|
+
} from "@tot/public-runtime";
|
|
12
|
+
import { AppVersionStore } from "../apps/appVersionStore.js";
|
|
13
|
+
import type { Queryable } from "../d1/catalog.js";
|
|
14
|
+
|
|
15
|
+
const MANAGED_KIND = "managed-analytics-v1";
|
|
16
|
+
|
|
17
|
+
interface ActiveManagedRow {
|
|
18
|
+
install_id: string;
|
|
19
|
+
app_id: string;
|
|
20
|
+
app_version: string;
|
|
21
|
+
manifest: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Return only declarations whose canonical row, current immutable activation,
|
|
26
|
+
* and reviewed platform-adapter rebuild agree. Malformed/stale rows fail closed
|
|
27
|
+
* individually; an unavailable database is handled by the caller as no effects.
|
|
28
|
+
*/
|
|
29
|
+
export async function loadActiveManagedInstallDeclarations(
|
|
30
|
+
db: Queryable,
|
|
31
|
+
tenantPublicId: string,
|
|
32
|
+
): Promise<IntegrationDeclarationInput[]> {
|
|
33
|
+
const rows = await db.all<ActiveManagedRow>(
|
|
34
|
+
`SELECT install_id, app_id, app_version, manifest
|
|
35
|
+
FROM app_installs
|
|
36
|
+
WHERE tenant_id = ? AND managed_kind = ? AND status = 'active'
|
|
37
|
+
ORDER BY install_id`,
|
|
38
|
+
tenantPublicId,
|
|
39
|
+
MANAGED_KIND,
|
|
40
|
+
);
|
|
41
|
+
const versions = new AppVersionStore(db);
|
|
42
|
+
const declarations: IntegrationDeclarationInput[] = [];
|
|
43
|
+
for (const row of rows) {
|
|
44
|
+
try {
|
|
45
|
+
const manifest = validateIntegrationManifest(JSON.parse(row.manifest) as unknown);
|
|
46
|
+
const current = await versions.currentVersion(row.install_id);
|
|
47
|
+
if (!manifest.ok || !current || current.tenantId !== tenantPublicId) continue;
|
|
48
|
+
if (current.appVersion !== row.app_version || current.facets.appVersion !== row.app_version) continue;
|
|
49
|
+
if (current.facets.digest !== manifest.value.app.digest || manifest.value.app.id !== row.app_id) continue;
|
|
50
|
+
const destinationId = current.facets.publicConfig["destinationId"];
|
|
51
|
+
if (typeof destinationId !== "string") continue;
|
|
52
|
+
const built = buildPlatformAdapterDeclaration({
|
|
53
|
+
adapterId: row.app_id,
|
|
54
|
+
ids: { live: destinationId },
|
|
55
|
+
tenantPublicId,
|
|
56
|
+
environment: {
|
|
57
|
+
schemaVersion: ENVIRONMENT_SCHEMA_VERSION,
|
|
58
|
+
siteSurface: "storefront",
|
|
59
|
+
environment: "live",
|
|
60
|
+
canonicalHost: tenantPublicId,
|
|
61
|
+
revision: `managed-live:${current.seq}`,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
if (!built.ok || built.declaration.manifest.app.digest !== manifest.value.app.digest) continue;
|
|
65
|
+
declarations.push({ ...built.declaration, ref: `managed-install:${row.install_id}` });
|
|
66
|
+
} catch {
|
|
67
|
+
// Persisted JSON/version corruption must disable this install, never widen
|
|
68
|
+
// CSP or partially materialize a subscription.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return declarations;
|
|
72
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-response CSP-nonce stamping (ADR 0018 nonce non-serialization; ext-kernel-04).
|
|
3
|
+
*
|
|
4
|
+
* The renderer emits a nonce-free template: every platform inline tag and the CSP
|
|
5
|
+
* header carry {@link INTEGRATION_NONCE_PLACEHOLDER}, never a live nonce. This is
|
|
6
|
+
* what the edge cache stores — a nonce-free artifact. At serve time (a cold render
|
|
7
|
+
* OR a warm cache hit) this stamps ONE fresh per-response nonce into both the HTML
|
|
8
|
+
* body and the CSP header, so every response gets a fresh, matching nonce/CSP pair
|
|
9
|
+
* and no nonce is ever reused across responses.
|
|
10
|
+
*/
|
|
11
|
+
import { INTEGRATION_NONCE_PLACEHOLDER, stampNonce } from "@tot/public-runtime";
|
|
12
|
+
|
|
13
|
+
const CSP_HEADERS = [
|
|
14
|
+
"content-security-policy",
|
|
15
|
+
"content-security-policy-report-only",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
/** A fresh, non-guessable per-response nonce. */
|
|
19
|
+
export function freshNonce(): string {
|
|
20
|
+
return crypto.randomUUID();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Replace the nonce placeholder with a fresh `nonce` in a response's CSP header(s)
|
|
25
|
+
* and — for an HTML response — its body. Non-HTML responses keep their (streamed)
|
|
26
|
+
* body untouched; their CSP header is still stamped. A response with no placeholder
|
|
27
|
+
* (a static-bundle body, an API JSON) is returned effectively unchanged.
|
|
28
|
+
*/
|
|
29
|
+
export async function stampResponseNonce(
|
|
30
|
+
response: Response,
|
|
31
|
+
nonce: string,
|
|
32
|
+
): Promise<Response> {
|
|
33
|
+
for (const header of CSP_HEADERS) {
|
|
34
|
+
const value = response.headers.get(header);
|
|
35
|
+
if (value && value.includes(INTEGRATION_NONCE_PLACEHOLDER)) {
|
|
36
|
+
response.headers.set(header, stampNonce(value, nonce));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
41
|
+
if (!contentType.includes("text/html")) return response;
|
|
42
|
+
|
|
43
|
+
const body = await response.text();
|
|
44
|
+
return new Response(stampNonce(body, nonce), {
|
|
45
|
+
status: response.status,
|
|
46
|
+
statusText: response.statusText,
|
|
47
|
+
headers: response.headers,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant-local integration declaration materialization (ext-kernel-09).
|
|
3
|
+
*
|
|
4
|
+
* This is the ONE production seam that turns a tenant's OWN declared, exact-version
|
|
5
|
+
* integrations into the already-materialized {@link IntegrationDeclarationInput} set
|
|
6
|
+
* the render hot path consumes. The interim marketing bridge
|
|
7
|
+
* ({@link resolveInterimBridgePlan}) resolves that set into the effective plan the
|
|
8
|
+
* fixed-phase engine serves — refusing a regulated tenant, resolving an eligible
|
|
9
|
+
* unregulated one — so a declared tenant boots its integration on-by-default
|
|
10
|
+
* (consent-gated) with no page edit and no vendor branch.
|
|
11
|
+
*
|
|
12
|
+
* A tenant with no declaration returns the empty set, so the bridge resolves an
|
|
13
|
+
* environment-only plan and the request is byte-identical to non-integration
|
|
14
|
+
* behavior (zero cost). The declarations are already materialized in tenant config
|
|
15
|
+
* (the manifest, the resolved public config, the set of asset paths present for the
|
|
16
|
+
* version); this loader only reads them, fail-closed — resolution is the bridge's
|
|
17
|
+
* job, materialization is config's, and the two never blur.
|
|
18
|
+
*/
|
|
19
|
+
import type { IntegrationDeclarationInput, IntegrationManifest, TenantConfig } from "@tot/public-runtime";
|
|
20
|
+
|
|
21
|
+
const NO_DECLARATIONS: readonly IntegrationDeclarationInput[] = Object.freeze([]);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read a tenant's already-materialized tenant-local declarations. Returns the empty
|
|
25
|
+
* set (never throws) when the tenant declares none or the field is malformed — an
|
|
26
|
+
* undeclared tenant must resolve to the environment-only plan, never an error.
|
|
27
|
+
*/
|
|
28
|
+
export function loadTenantIntegrationDeclarations(
|
|
29
|
+
tenant: Pick<TenantConfig, "integrations">,
|
|
30
|
+
): readonly IntegrationDeclarationInput[] {
|
|
31
|
+
const declared = tenant.integrations;
|
|
32
|
+
if (!Array.isArray(declared) || declared.length === 0) return NO_DECLARATIONS;
|
|
33
|
+
return declared;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Demo tenant-local declaration — a same-origin sandbox analytics adapter.
|
|
38
|
+
//
|
|
39
|
+
// Seeded on the unregulated marketing demo/home tenants so a preview deploy
|
|
40
|
+
// DEMONSTRABLY materializes a real integration on-by-default: its exact-version
|
|
41
|
+
// entry module (committed under `public/apps/…`, so always materialized and served
|
|
42
|
+
// same-origin) flows the declaration → interim bridge → effective plan → boot
|
|
43
|
+
// descriptor → lifecycle import. Loading is consent-gated (purpose `analytics`,
|
|
44
|
+
// held back under the default-deny snapshot until granted), and the declared
|
|
45
|
+
// beacon origin reaches the strict CSP via `cspNeeds`. A regulated tenant carrying
|
|
46
|
+
// the same declaration is still REFUSED by the bridge.
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/** The demo adapter's app id — a same-origin, sandbox-tier marketing analytics adapter. */
|
|
50
|
+
export const DEMO_ANALYTICS_APP_ID = "tot.demo.analytics";
|
|
51
|
+
/** The demo adapter's exact pinned version. */
|
|
52
|
+
export const DEMO_ANALYTICS_VERSION = "0.1.0";
|
|
53
|
+
/**
|
|
54
|
+
* The same-origin, exact-version entry module the lifecycle runtime dynamically
|
|
55
|
+
* imports. Committed under `apps/storefront/public/apps/…` so it is served verbatim
|
|
56
|
+
* at this root-absolute URL and is ALWAYS materialized (no R2 pointer needed).
|
|
57
|
+
*/
|
|
58
|
+
export const DEMO_ANALYTICS_ENTRY_URL = `/apps/${DEMO_ANALYTICS_APP_ID}/${DEMO_ANALYTICS_VERSION}/entry.js`;
|
|
59
|
+
/** The beacon endpoint the demo declaration reserves in CSP (`connect-src`). */
|
|
60
|
+
export const DEMO_ANALYTICS_BEACON_ORIGIN = "https://demo-analytics.tokenoftrust.com";
|
|
61
|
+
|
|
62
|
+
/** The demo adapter's shared browser manifest (ext-kernel-01 v2). */
|
|
63
|
+
const DEMO_ANALYTICS_MANIFEST: IntegrationManifest = {
|
|
64
|
+
schemaVersion: 2,
|
|
65
|
+
app: {
|
|
66
|
+
id: DEMO_ANALYTICS_APP_ID,
|
|
67
|
+
name: "ToT Demo Analytics",
|
|
68
|
+
version: DEMO_ANALYTICS_VERSION,
|
|
69
|
+
digest: `sha256:${DEMO_ANALYTICS_APP_ID}-${DEMO_ANALYTICS_VERSION}`,
|
|
70
|
+
publisher: "Token of Trust",
|
|
71
|
+
},
|
|
72
|
+
compatibility: { storefrontBrowserContract: ">=2.0.0 <3.0.0", configurationSchemaVersion: 1 },
|
|
73
|
+
browser: {
|
|
74
|
+
executionTier: "sandbox",
|
|
75
|
+
entry: { asset: DEMO_ANALYTICS_ENTRY_URL, format: "module", phase: "document.deferred" },
|
|
76
|
+
contributions: [],
|
|
77
|
+
dependencies: [],
|
|
78
|
+
publicConfigSchema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
required: ["siteId"],
|
|
82
|
+
properties: { siteId: { type: "string" } },
|
|
83
|
+
},
|
|
84
|
+
consent: {
|
|
85
|
+
purposes: ["analytics"],
|
|
86
|
+
loadWhen: "all-declared-purposes-eligible",
|
|
87
|
+
unknownState: "block",
|
|
88
|
+
},
|
|
89
|
+
// Same-origin entry loads under the strict base `'self'`; the declared beacon
|
|
90
|
+
// origin is the advanced exact source the compiler folds into `connect-src`.
|
|
91
|
+
csp: { profiles: [], sources: { "connect-src": [DEMO_ANALYTICS_BEACON_ORIGIN] } },
|
|
92
|
+
permissions: { events: ["storefront.page.viewed.v1"], data: [], actions: [], domIntent: "none" },
|
|
93
|
+
performanceBudget: {
|
|
94
|
+
maxTransferredBytes: 20000,
|
|
95
|
+
maxAdditionalRequests: 1,
|
|
96
|
+
maxMainThreadMilliseconds: 20,
|
|
97
|
+
mustBeAsyncOrDeferred: true,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
server: { topics: [], secretRequirements: [] },
|
|
101
|
+
assurance: { requestedClassification: "baseline", regulatedTenantDefault: "disabled-until-certified" },
|
|
102
|
+
configurationMigrations: [],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The demo tenant-local declaration seeded on the unregulated marketing demo tenants.
|
|
107
|
+
* `materializedAssets` names the committed same-origin entry module, so the resolver's
|
|
108
|
+
* fail-closed materialized-asset check passes without any R2/publish step.
|
|
109
|
+
*/
|
|
110
|
+
export const DEMO_ANALYTICS_DECLARATION: IntegrationDeclarationInput = {
|
|
111
|
+
origin: "tenant-local",
|
|
112
|
+
ref: "config/tenants:demo-analytics",
|
|
113
|
+
manifest: DEMO_ANALYTICS_MANIFEST,
|
|
114
|
+
publicConfig: { siteId: "tot-demo" },
|
|
115
|
+
materializedAssets: [DEMO_ANALYTICS_ENTRY_URL],
|
|
116
|
+
};
|
|
@@ -81,6 +81,124 @@ export function breadcrumbLd(
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/** Words in rendered HTML — tags/entities stripped. Feeds `BlogPosting.wordCount`. */
|
|
85
|
+
export function wordCount(html: string): number {
|
|
86
|
+
const text = html
|
|
87
|
+
.replace(/<[^>]+>/g, " ")
|
|
88
|
+
.replace(/&[a-z#0-9]+;/gi, " ");
|
|
89
|
+
return text.match(/\S+/g)?.length ?? 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The subset of a blog post `blogPostingLd` reads — kept structural so the
|
|
93
|
+
* builder doesn't couple `jsonld.ts` to the blog module. */
|
|
94
|
+
export interface BlogPostingInput {
|
|
95
|
+
title: string;
|
|
96
|
+
description: string;
|
|
97
|
+
publishedAt: string;
|
|
98
|
+
modifiedAt: string;
|
|
99
|
+
author: string;
|
|
100
|
+
categories: string[];
|
|
101
|
+
tags: string[];
|
|
102
|
+
contentHtml: string;
|
|
103
|
+
inLanguage?: string;
|
|
104
|
+
featuredImage?: { src: string; alt?: string; width?: number; height?: number };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Enriched `BlogPosting` graph (B1.6): adds `wordCount`, `articleSection`,
|
|
109
|
+
* `keywords`, `inLanguage` (default "en"), an `author.url`, and an `ImageObject`
|
|
110
|
+
* `image` with dimensions — on top of the baseline
|
|
111
|
+
* headline/description/dates/publisher.
|
|
112
|
+
*/
|
|
113
|
+
export function blogPostingLd(
|
|
114
|
+
post: BlogPostingInput,
|
|
115
|
+
opts: { url: string; canonicalBase: string; publisherName: string; authorUrl?: string },
|
|
116
|
+
) {
|
|
117
|
+
const { url, canonicalBase, publisherName, authorUrl } = opts;
|
|
118
|
+
const image = post.featuredImage
|
|
119
|
+
? {
|
|
120
|
+
"@type": "ImageObject",
|
|
121
|
+
url: new URL(post.featuredImage.src, canonicalBase + "/").toString(),
|
|
122
|
+
...(post.featuredImage.alt ? { caption: post.featuredImage.alt } : {}),
|
|
123
|
+
...(post.featuredImage.width ? { width: post.featuredImage.width } : {}),
|
|
124
|
+
...(post.featuredImage.height ? { height: post.featuredImage.height } : {}),
|
|
125
|
+
}
|
|
126
|
+
: undefined;
|
|
127
|
+
return {
|
|
128
|
+
"@context": "https://schema.org",
|
|
129
|
+
"@type": "BlogPosting",
|
|
130
|
+
headline: post.title,
|
|
131
|
+
description: post.description,
|
|
132
|
+
url,
|
|
133
|
+
mainEntityOfPage: url,
|
|
134
|
+
datePublished: post.publishedAt,
|
|
135
|
+
dateModified: post.modifiedAt,
|
|
136
|
+
author: {
|
|
137
|
+
"@type": "Person",
|
|
138
|
+
name: post.author,
|
|
139
|
+
...(authorUrl ? { url: authorUrl } : {}),
|
|
140
|
+
},
|
|
141
|
+
publisher: { "@type": "Organization", name: publisherName, url: canonicalBase },
|
|
142
|
+
wordCount: wordCount(post.contentHtml),
|
|
143
|
+
...(post.categories[0] ? { articleSection: post.categories[0] } : {}),
|
|
144
|
+
...(post.tags.length ? { keywords: post.tags.join(", ") } : {}),
|
|
145
|
+
inLanguage: post.inLanguage ?? "en",
|
|
146
|
+
...(image ? { image } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* `CollectionPage` + embedded `ItemList` for a category/tag/author archive
|
|
152
|
+
* (B1.2/B1.3 opt in — this composes with them without requiring their routes
|
|
153
|
+
* exist yet). `items` are already absolute URLs.
|
|
154
|
+
*/
|
|
155
|
+
export function collectionPageLd(opts: {
|
|
156
|
+
name: string;
|
|
157
|
+
url: string;
|
|
158
|
+
description?: string;
|
|
159
|
+
items: { title: string; url: string }[];
|
|
160
|
+
}) {
|
|
161
|
+
return {
|
|
162
|
+
"@context": "https://schema.org",
|
|
163
|
+
"@type": "CollectionPage",
|
|
164
|
+
name: opts.name,
|
|
165
|
+
url: opts.url,
|
|
166
|
+
...(opts.description ? { description: opts.description } : {}),
|
|
167
|
+
mainEntity: {
|
|
168
|
+
"@type": "ItemList",
|
|
169
|
+
numberOfItems: opts.items.length,
|
|
170
|
+
itemListElement: opts.items.map((it, i) => ({
|
|
171
|
+
"@type": "ListItem",
|
|
172
|
+
position: i + 1,
|
|
173
|
+
name: it.title,
|
|
174
|
+
url: it.url,
|
|
175
|
+
})),
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* `Person` graph for an author page (B1.3) — the E-E-A-T signal Google weights
|
|
182
|
+
* for author authority. `sameAs` carries the author's social-profile URLs.
|
|
183
|
+
*/
|
|
184
|
+
export function personLd(opts: {
|
|
185
|
+
name: string;
|
|
186
|
+
url?: string;
|
|
187
|
+
description?: string;
|
|
188
|
+
sameAs?: string[];
|
|
189
|
+
image?: string;
|
|
190
|
+
}) {
|
|
191
|
+
return {
|
|
192
|
+
"@context": "https://schema.org",
|
|
193
|
+
"@type": "Person",
|
|
194
|
+
name: opts.name,
|
|
195
|
+
...(opts.url ? { url: opts.url } : {}),
|
|
196
|
+
...(opts.description ? { description: opts.description } : {}),
|
|
197
|
+
...(opts.sameAs && opts.sameAs.length ? { sameAs: opts.sameAs } : {}),
|
|
198
|
+
...(opts.image ? { image: opts.image } : {}),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
84
202
|
export function productLd(
|
|
85
203
|
p: CatalogProduct,
|
|
86
204
|
tenant: TenantConfig,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shopper membership eligibility — the request-scoped seam that resolves whether
|
|
3
|
-
* the current shopper is a member, and which tier
|
|
3
|
+
* the current shopper is a member, and which tier.
|
|
4
4
|
*
|
|
5
5
|
* Phase 1 storefronts have NO shopper-login/account source (member accounts are
|
|
6
6
|
* a Phase-2 managed-checkout capability), so this resolves anonymous
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* a tier explicitly asserts `real: true`, the widget marks its figures as
|
|
12
12
|
* illustrative ("e.g." affordance + a group-level "Illustrative figures" label)
|
|
13
13
|
* so example numbers are never presented as a live offer. This mirrors the
|
|
14
|
-
* platform-owned / never-faked discipline of the compliance widgets
|
|
15
|
-
* `compliance-widgets-platform-owned`).
|
|
14
|
+
* platform-owned / never-faked discipline of the compliance widgets.
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Monitoring contract (
|
|
2
|
+
* Monitoring contract (monitors, alerts, operator runbooks).
|
|
3
3
|
*
|
|
4
4
|
* This is the CHECKED-IN source of truth for "what do we watch, at what
|
|
5
5
|
* threshold, at what severity, and which runbook fires when it trips." It is
|
|
6
|
-
* deliberately data, not wiring:
|
|
6
|
+
* deliberately data, not wiring: this file ships the *contract* so operators can
|
|
7
7
|
* see coverage and reviewers can diff it; the actual alert providers (Cloudflare
|
|
8
8
|
* Health Checks / Workers metrics / Logpush alerts / a RUM sink) are provisioned
|
|
9
|
-
* out-of-band and each monitor records whether it is `in-code`
|
|
9
|
+
* out-of-band and each monitor records whether it is `in-code` or still
|
|
10
10
|
* `needs-external` / `needs-secret`.
|
|
11
11
|
*
|
|
12
12
|
* Signal sources are named against real code where it exists in this repo
|
|
13
13
|
* (`/health` → lib/health.ts, CSP → pages/api/csp-report.ts + CSP_REPORT_PATH,
|
|
14
14
|
* commerce events → lib/analytics). Signals owned by sibling units are referenced
|
|
15
|
-
* by CONTRACT NAME, not imported — Core Web Vitals come from
|
|
16
|
-
* and age-verification / restricted-state denials come from
|
|
17
|
-
* evidence signals. This file does not edit or depend on those units;
|
|
18
|
-
*
|
|
15
|
+
* by CONTRACT NAME, not imported — Core Web Vitals come from the RUM beacon,
|
|
16
|
+
* and age-verification / restricted-state denials come from the compliance
|
|
17
|
+
* evidence signals. This file does not edit or depend on those units; point the
|
|
18
|
+
* corresponding monitor's `observe` at the real sink where one exists.
|
|
19
19
|
*
|
|
20
20
|
* The companion operator doc renders this for humans and lists the external
|
|
21
21
|
* wiring/secrets checklist: docs/epics/sample-store-migration/monitoring-and-runbooks.md.
|
|
@@ -89,9 +89,9 @@ export interface Monitor {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* The ten launch-critical monitors
|
|
93
|
-
* Thresholds are
|
|
94
|
-
*
|
|
92
|
+
* The ten launch-critical monitors. Order is presentation order.
|
|
93
|
+
* Thresholds are defaults; tune against the 24h post-cutover soak
|
|
94
|
+
* and record any change here — this file is the audit trail.
|
|
95
95
|
*/
|
|
96
96
|
export const MONITORS: readonly Monitor[] = [
|
|
97
97
|
{
|
|
@@ -264,7 +264,7 @@ export const MONITORS: readonly Monitor[] = [
|
|
|
264
264
|
] as const;
|
|
265
265
|
|
|
266
266
|
/**
|
|
267
|
-
* The monitors
|
|
267
|
+
* The monitors this contract requires by name. The test asserts MONITORS covers exactly
|
|
268
268
|
* these — so dropping or misnaming one fails CI, and adding a new monitor is a
|
|
269
269
|
* deliberate edit here.
|
|
270
270
|
*/
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Double-opt-in confirmation mail — STUB.
|
|
3
|
+
*
|
|
4
|
+
* The storefront Worker sends no email itself: every existing "email" flow is
|
|
5
|
+
* delegated over HTTP to tot20, and there is no bulk/marketing sender in-repo
|
|
6
|
+
* (docs/plans/2026-08-22-newsletter-send-spike.md §1). Wiring the real send is
|
|
7
|
+
* the ESP-pipeline's job — it replaces the body of this function with a
|
|
8
|
+
* transactional send (confirm link in the body). Until then we only LOG intent,
|
|
9
|
+
* and never let a send failure fail the subscribe (the pending row + returned
|
|
10
|
+
* token are the durable state; the mail is a follow-up).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface ConfirmationEmailRequest {
|
|
14
|
+
email: string;
|
|
15
|
+
collection: string;
|
|
16
|
+
/** Absolute confirm URL the recipient clicks: `…/api/newsletter/confirm?token=…`. */
|
|
17
|
+
confirmUrl: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* N4 REPLACES THIS with a real ESP send. Today: log the intent and return.
|
|
22
|
+
* Best-effort by contract — callers do not await deliverability, and this never
|
|
23
|
+
* throws.
|
|
24
|
+
*/
|
|
25
|
+
export async function sendConfirmationEmail(req: ConfirmationEmailRequest): Promise<void> {
|
|
26
|
+
console.log(
|
|
27
|
+
`[newsletter] confirmation email STUB — would send to ${req.email} ` +
|
|
28
|
+
`(collection: ${req.collection}) confirm: ${req.confirmUrl}`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent-record primitives for the newsletter double-opt-in flow.
|
|
3
|
+
*
|
|
4
|
+
* `randomToken` mints the opaque per-subscriber secret that both the confirm and
|
|
5
|
+
* unsubscribe links carry — Web Crypto only (no `node:*`) so it runs
|
|
6
|
+
* identically in the Worker and in tests. `hashConsentIp` is the GDPR-safe IP
|
|
7
|
+
* record: we store `sha256(ip + ":" + salt)`, NEVER the raw IP (repo convention:
|
|
8
|
+
* opaque hashes only, cf. `apps/orders/customerHash.ts`).
|
|
9
|
+
*/
|
|
10
|
+
import { sha256Hex } from "@tot/public-runtime";
|
|
11
|
+
|
|
12
|
+
/** 32 bytes of CSPRNG entropy, hex-encoded — an opaque, unguessable link token. */
|
|
13
|
+
export function randomToken(): string {
|
|
14
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
15
|
+
let hex = "";
|
|
16
|
+
for (const b of bytes) hex += b.toString(16).padStart(2, "0");
|
|
17
|
+
return hex;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Salted one-way hash of the client IP — the consent record's IP evidence. */
|
|
21
|
+
export function hashConsentIp(ip: string, salt: string): Promise<string> {
|
|
22
|
+
return sha256Hex(`${ip}:${salt}`);
|
|
23
|
+
}
|