@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,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension-policy compliance obligations — the complete, extensible floor model.
|
|
3
|
+
*
|
|
4
|
+
* An OBLIGATION is a mandated compliance duty: an age/identity gate, excise-tax
|
|
5
|
+
* collection, an FDA nicotine warning, a PACT Act shipment notice, an adult
|
|
6
|
+
* signature, a California Prop 65 warning, a shipping/jurisdiction restriction, and
|
|
7
|
+
* any future duty a later card registers. The set a tenant owes is SERVER-DERIVED
|
|
8
|
+
* from its authoritative `TenantConfig.compliance` — an app manifest or a raw-HTML
|
|
9
|
+
* page can never be an input to resolution, so nothing external can drop or weaken
|
|
10
|
+
* a configured duty (ADR 0014 G1 `integration-regulated-floor-nonwaivable`, D-R7
|
|
11
|
+
* tamper-recovery). Raw HTML and managed apps resolve the SAME obligation set
|
|
12
|
+
* (ADR 0015 `integration-schema-shared`).
|
|
13
|
+
*
|
|
14
|
+
* The compliance-authority axis is kernel-01's `ASSURANCE_SURFACES`
|
|
15
|
+
* (`unregulated-marketing` | `commerce` | `regulated`) — the single vocabulary;
|
|
16
|
+
* this module never invents a second. (The finer `SURFACES` in
|
|
17
|
+
* `integration-capabilities.ts` is a separate axis; ext-policy-11 reconciles the
|
|
18
|
+
* two.)
|
|
19
|
+
*
|
|
20
|
+
* The registry is OPEN — a future duty registers a spec without editing resolution
|
|
21
|
+
* or gating logic. An obligation id OUTSIDE the registry is UNKNOWN and FAILS
|
|
22
|
+
* CLOSED (`verifyObligationCoverage`), never assumed satisfied.
|
|
23
|
+
*
|
|
24
|
+
* Obligation ids `age_gate`, `excise`, and `jurisdiction` are IDENTICAL to the
|
|
25
|
+
* promotion-evidence check ids (ext-prereq-03 `REQUIRED_FOR_PROMOTE`), so
|
|
26
|
+
* ext-policy-06 maps an obligation straight onto its evidence row with no
|
|
27
|
+
* translation table.
|
|
28
|
+
*/
|
|
29
|
+
import type { TenantConfig } from "./tenant.js";
|
|
30
|
+
import { ASSURANCE_SURFACES, type AssuranceSurface } from "./integration/assurance.js";
|
|
31
|
+
|
|
32
|
+
/** Contract token — bump the minor for additive vocabulary, the major for a break (ADR 0006 semver). */
|
|
33
|
+
export const COMPLIANCE_OBLIGATION_MODEL = "compliance-obligation-model@1" as const;
|
|
34
|
+
export type ComplianceObligationModel = typeof COMPLIANCE_OBLIGATION_MODEL;
|
|
35
|
+
|
|
36
|
+
/** The only input to obligation resolution: the tenant's authoritative config. */
|
|
37
|
+
export type ObligationTenant = Pick<TenantConfig, "compliance" | "siteType">;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Platform-owned protected UI/preflight anchors. A third-party integration can
|
|
41
|
+
* never remove, hide, overlay, or mutate these (D-R7). This is the authoritative
|
|
42
|
+
* list the capability-specific conformance suite and ext-policy-09 sentinels key
|
|
43
|
+
* on; here it is the declarative floor, not the detective control.
|
|
44
|
+
*/
|
|
45
|
+
export const PROTECTED_SURFACES = [
|
|
46
|
+
"age-gate",
|
|
47
|
+
"compliance-warning",
|
|
48
|
+
"shipping-jurisdiction-notice",
|
|
49
|
+
"cart-preflight",
|
|
50
|
+
"checkout-controls",
|
|
51
|
+
] as const;
|
|
52
|
+
export type ProtectedSurface = (typeof PROTECTED_SURFACES)[number];
|
|
53
|
+
|
|
54
|
+
/** A registered compliance duty. */
|
|
55
|
+
export interface ObligationSpec {
|
|
56
|
+
/** Stable, wire-serialized obligation id. */
|
|
57
|
+
id: string;
|
|
58
|
+
/** Operator-facing label. */
|
|
59
|
+
label: string;
|
|
60
|
+
/** The compliance-authority surface this duty belongs to (kernel-01 axis). */
|
|
61
|
+
surface: AssuranceSurface;
|
|
62
|
+
/**
|
|
63
|
+
* Platform-owned protected anchors this obligation renders. Non-empty ⇒ the
|
|
64
|
+
* obligation is tamper-protected: an integration can never disable it (D-R7).
|
|
65
|
+
*/
|
|
66
|
+
protectedSurfaces: readonly ProtectedSurface[];
|
|
67
|
+
/**
|
|
68
|
+
* Pure predicate over authoritative tenant config: does this tenant configure
|
|
69
|
+
* this obligation? The ONLY resolution input — never an app/raw-HTML declaration.
|
|
70
|
+
*/
|
|
71
|
+
applies: (tenant: ObligationTenant) => boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A resolved (applicable) obligation for a specific tenant. */
|
|
75
|
+
export interface ResolvedObligation {
|
|
76
|
+
id: string;
|
|
77
|
+
label: string;
|
|
78
|
+
surface: AssuranceSurface;
|
|
79
|
+
protectedSurfaces: readonly ProtectedSurface[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const REGISTRY = new Map<string, ObligationSpec>();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Register a compliance obligation. Extensible seam — a future duty registers a
|
|
86
|
+
* spec here (or from a downstream card) and flows through resolution, surface
|
|
87
|
+
* derivation, coverage verification, and the floor guard with no core edit. A
|
|
88
|
+
* duplicate id is a programming error and throws.
|
|
89
|
+
*/
|
|
90
|
+
export function registerObligation(spec: ObligationSpec): void {
|
|
91
|
+
if (REGISTRY.has(spec.id)) {
|
|
92
|
+
throw new Error(`Duplicate compliance obligation id: ${spec.id}`);
|
|
93
|
+
}
|
|
94
|
+
REGISTRY.set(spec.id, spec);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Every registered obligation spec, in registration order. */
|
|
98
|
+
export function obligationSpecs(): ObligationSpec[] {
|
|
99
|
+
return [...REGISTRY.values()];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Is this id a registered obligation? Anything else is UNKNOWN → fails closed. */
|
|
103
|
+
export function isKnownObligation(id: string): boolean {
|
|
104
|
+
return REGISTRY.has(id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Built-in floor obligations ──────────────────────────────────────────────
|
|
108
|
+
// Each derives PURELY from `TenantConfig.compliance`. `age_gate`, `excise`, and
|
|
109
|
+
// `jurisdiction` share the promotion-evidence check ids (ext-prereq-03).
|
|
110
|
+
|
|
111
|
+
const BUILTIN_OBLIGATIONS: readonly ObligationSpec[] = [
|
|
112
|
+
{
|
|
113
|
+
id: "age_gate",
|
|
114
|
+
label: "Age/identity verification gate",
|
|
115
|
+
surface: "regulated",
|
|
116
|
+
protectedSurfaces: ["age-gate", "cart-preflight"],
|
|
117
|
+
applies: (t) => !!t.compliance?.minAge,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "excise",
|
|
121
|
+
label: "Excise tax collection",
|
|
122
|
+
surface: "regulated",
|
|
123
|
+
protectedSurfaces: ["checkout-controls"],
|
|
124
|
+
applies: (t) => !!t.compliance?.nicotineWarning || !!t.compliance?.exciseTax,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "nicotine_warning",
|
|
128
|
+
label: "FDA nicotine warning",
|
|
129
|
+
surface: "regulated",
|
|
130
|
+
protectedSurfaces: ["compliance-warning"],
|
|
131
|
+
applies: (t) => !!t.compliance?.nicotineWarning,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "pact_act",
|
|
135
|
+
label: "PACT Act shipment notice",
|
|
136
|
+
surface: "regulated",
|
|
137
|
+
protectedSurfaces: ["compliance-warning", "shipping-jurisdiction-notice"],
|
|
138
|
+
applies: (t) => !!t.compliance?.pactAct,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: "adult_signature",
|
|
142
|
+
label: "Adult signature on delivery",
|
|
143
|
+
surface: "regulated",
|
|
144
|
+
protectedSurfaces: ["shipping-jurisdiction-notice", "cart-preflight"],
|
|
145
|
+
applies: (t) => !!t.compliance?.adultSignature,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "prop65",
|
|
149
|
+
label: "California Prop 65 warning",
|
|
150
|
+
surface: "commerce",
|
|
151
|
+
protectedSurfaces: ["compliance-warning"],
|
|
152
|
+
applies: (t) => !!t.compliance?.prop65,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "jurisdiction",
|
|
156
|
+
label: "Restricted shipping / jurisdiction eligibility",
|
|
157
|
+
surface: "commerce",
|
|
158
|
+
protectedSurfaces: ["shipping-jurisdiction-notice", "cart-preflight"],
|
|
159
|
+
applies: (t) =>
|
|
160
|
+
!!t.compliance?.stateEligibility || !!t.compliance?.shippingRestriction,
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
for (const spec of BUILTIN_OBLIGATIONS) registerObligation(spec);
|
|
165
|
+
|
|
166
|
+
// ── Resolution (server-derived, floor-only) ─────────────────────────────────
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The tenant's REQUIRED obligation set, derived purely from authoritative config.
|
|
170
|
+
* An app/raw-HTML declaration is never an argument, so no integration can drop a
|
|
171
|
+
* duty (ADR G1 / D-R7). Order is stable (registration order) so the serialized
|
|
172
|
+
* `configuredObligations` and the assurance record stay deterministic.
|
|
173
|
+
*/
|
|
174
|
+
export function resolveObligations(tenant: ObligationTenant): ResolvedObligation[] {
|
|
175
|
+
const out: ResolvedObligation[] = [];
|
|
176
|
+
for (const spec of REGISTRY.values()) {
|
|
177
|
+
if (spec.applies(tenant)) {
|
|
178
|
+
out.push({
|
|
179
|
+
id: spec.id,
|
|
180
|
+
label: spec.label,
|
|
181
|
+
surface: spec.surface,
|
|
182
|
+
protectedSurfaces: spec.protectedSurfaces,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The resolved obligation ids — the shape gates and the assurance record carry. */
|
|
190
|
+
export function resolveObligationIds(tenant: ObligationTenant): string[] {
|
|
191
|
+
return resolveObligations(tenant).map((o) => o.id);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const SURFACE_RANK: Record<AssuranceSurface, number> = Object.fromEntries(
|
|
195
|
+
ASSURANCE_SURFACES.map((s, i) => [s, i]),
|
|
196
|
+
) as Record<AssuranceSurface, number>;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The tenant's compliance-authority surface (kernel-01 axis): the MOST restrictive
|
|
200
|
+
* surface any applied obligation implies, else the siteType floor (a marketing site
|
|
201
|
+
* with no duty is `unregulated-marketing`; a commerce site is `commerce`).
|
|
202
|
+
*/
|
|
203
|
+
export function resolveObligationSurface(tenant: ObligationTenant): AssuranceSurface {
|
|
204
|
+
let surface: AssuranceSurface =
|
|
205
|
+
tenant.siteType === "marketing" ? "unregulated-marketing" : "commerce";
|
|
206
|
+
for (const o of resolveObligations(tenant)) {
|
|
207
|
+
if (SURFACE_RANK[o.surface] > SURFACE_RANK[surface]) surface = o.surface;
|
|
208
|
+
}
|
|
209
|
+
return surface;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** True iff the tenant resolves to the `regulated` authority surface. */
|
|
213
|
+
export function isRegulated(tenant: ObligationTenant): boolean {
|
|
214
|
+
return resolveObligationSurface(tenant) === "regulated";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The union of platform-owned protected anchors the tenant's applied obligations
|
|
219
|
+
* render, in canonical order. Drives the conformance suite and the ext-policy-09
|
|
220
|
+
* sentinels — the set an integration can never disable (D-R7).
|
|
221
|
+
*/
|
|
222
|
+
export function protectedSurfacesFor(tenant: ObligationTenant): ProtectedSurface[] {
|
|
223
|
+
const seen = new Set<ProtectedSurface>();
|
|
224
|
+
for (const o of resolveObligations(tenant)) {
|
|
225
|
+
for (const s of o.protectedSurfaces) seen.add(s);
|
|
226
|
+
}
|
|
227
|
+
return PROTECTED_SURFACES.filter((s) => seen.has(s));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Fail-closed coverage gate (mirrors ext-prereq-03 verifyPromotionEvidence) ──
|
|
231
|
+
|
|
232
|
+
export interface ObligationCoverageVerdict {
|
|
233
|
+
/** True only when the declaration COMPLETELY, non-ambiguously covers the floor. */
|
|
234
|
+
ok: boolean;
|
|
235
|
+
/** Every reason coverage is insufficient (empty ⇔ ok). */
|
|
236
|
+
blockers: string[];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The single fail-CLOSED authority for "does this declaration satisfy the tenant's
|
|
241
|
+
* compliance floor?". The required set is SERVER-DERIVED from tenant config, never
|
|
242
|
+
* taken from the (spoofable) declaration. Rejects on any of: an absent declaration
|
|
243
|
+
* (no array), an omitted required obligation, an UNKNOWN obligation id (registry
|
|
244
|
+
* drift / a spoofed row), or a duplicated id (an ambiguous set that could mask a
|
|
245
|
+
* missing duty). Shared by the raw-HTML path and the managed-app path (ADR 0015).
|
|
246
|
+
*/
|
|
247
|
+
export function verifyObligationCoverage(
|
|
248
|
+
tenant: ObligationTenant,
|
|
249
|
+
declaredIds: readonly string[] | null | undefined,
|
|
250
|
+
): ObligationCoverageVerdict {
|
|
251
|
+
// Absent declaration — fail closed. Nothing declared against a regulated floor is
|
|
252
|
+
// exactly what this gate exists to refuse.
|
|
253
|
+
if (!Array.isArray(declaredIds)) {
|
|
254
|
+
return {
|
|
255
|
+
ok: false,
|
|
256
|
+
blockers: [
|
|
257
|
+
"Obligation coverage is absent — nothing declared against the compliance floor.",
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const blockers: string[] = [];
|
|
263
|
+
const seen = new Set<string>();
|
|
264
|
+
for (const id of declaredIds) {
|
|
265
|
+
if (!isKnownObligation(id)) {
|
|
266
|
+
blockers.push(`Unknown compliance obligation declared: ${id}`);
|
|
267
|
+
}
|
|
268
|
+
if (seen.has(id)) {
|
|
269
|
+
blockers.push(`Duplicated compliance obligation declared: ${id}`);
|
|
270
|
+
}
|
|
271
|
+
seen.add(id);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const id of resolveObligationIds(tenant)) {
|
|
275
|
+
if (!seen.has(id)) {
|
|
276
|
+
blockers.push(`Required compliance obligation omitted: ${id}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return { ok: blockers.length === 0, blockers };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface ObligationFloorViolation {
|
|
284
|
+
obligation: string;
|
|
285
|
+
reason: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Surface any attempt by an integration to disable a configured obligation. The
|
|
290
|
+
* resolved floor is unchanged regardless — `resolveObligations` never reads this
|
|
291
|
+
* input — so this only records the attempt for denial/audit (D-R7). Any id in
|
|
292
|
+
* `requestedDisable` that names a resolved required obligation is a violation.
|
|
293
|
+
*/
|
|
294
|
+
export function obligationFloorViolations(
|
|
295
|
+
tenant: ObligationTenant,
|
|
296
|
+
requestedDisable?: readonly string[] | null,
|
|
297
|
+
): ObligationFloorViolation[] {
|
|
298
|
+
if (!requestedDisable) return [];
|
|
299
|
+
const required = new Set(resolveObligationIds(tenant));
|
|
300
|
+
const out: ObligationFloorViolation[] = [];
|
|
301
|
+
for (const id of requestedDisable) {
|
|
302
|
+
if (required.has(id)) {
|
|
303
|
+
out.push({
|
|
304
|
+
obligation: id,
|
|
305
|
+
reason: `${id} is a configured compliance obligation and cannot be disabled by an integration.`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* The middleware decides the header name — `Content-Security-Policy` (enforce)
|
|
11
11
|
* vs `Content-Security-Policy-Report-Only` (rollout). This only builds the value.
|
|
12
12
|
*/
|
|
13
|
+
// The embed catalog DATA lives in a dependency-free .mjs so plain-node tooling
|
|
14
|
+
// (the dev-loop lint) can share this exact copy — see embed-catalog.mjs. csp.ts
|
|
15
|
+
// owns the TYPE (EmbedProvider) and re-exports the data under it below.
|
|
16
|
+
import { EMBED_PROVIDERS as EMBED_PROVIDERS_DATA } from "./embed-catalog.mjs";
|
|
17
|
+
|
|
13
18
|
export interface CspOptions {
|
|
14
19
|
/** Per-request nonce; the same value goes on the platform's inline tags. */
|
|
15
20
|
nonce: string;
|
|
@@ -85,6 +90,100 @@ export const STOREFRONT_CSP_ALLOWLIST = {
|
|
|
85
90
|
connectSrc: ["https://cloudflareinsights.com"],
|
|
86
91
|
} as const;
|
|
87
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Curated catalog of known third-party embeds a tenant may opt into. Keyed by a
|
|
95
|
+
* stable provider slug the tenant lists in `.tot/config.json` `embeds:`; the
|
|
96
|
+
* platform (never the tenant) owns the exact origins each provider needs, so a
|
|
97
|
+
* tenant can enable "pipedrive" without ever hand-writing a CSP origin — and a
|
|
98
|
+
* novel provider requires a reviewed catalog entry here, not a config edit.
|
|
99
|
+
*
|
|
100
|
+
* `markers` are substrings that appear in rendered HTML when the embed is
|
|
101
|
+
* actually present on a page (so `embedCspAdditions` can add origins ONLY to the
|
|
102
|
+
* pages that use them — the same derive-from-actual-usage posture as
|
|
103
|
+
* {@link appWidgetCspAdditions}). Origins are EXACT hosts, never wildcards. An
|
|
104
|
+
* embed renders in its own cross-origin iframe governed by the provider's CSP,
|
|
105
|
+
* so `frameSrc` mounts it and `connectSrc`/`scriptSrc` cover only what the
|
|
106
|
+
* loader needs in the PARENT document.
|
|
107
|
+
*/
|
|
108
|
+
export interface EmbedProvider {
|
|
109
|
+
/** Human label for lint/report messages. */
|
|
110
|
+
label: string;
|
|
111
|
+
/** Substrings whose presence in rendered HTML means the embed is on the page. */
|
|
112
|
+
markers: readonly string[];
|
|
113
|
+
scriptSrc?: readonly string[];
|
|
114
|
+
connectSrc?: readonly string[];
|
|
115
|
+
frameSrc?: readonly string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const EMBED_PROVIDERS: Readonly<Record<string, EmbedProvider>> =
|
|
119
|
+
EMBED_PROVIDERS_DATA;
|
|
120
|
+
|
|
121
|
+
export type EmbedProviderKey = keyof typeof EMBED_PROVIDERS;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Canonical published-artifact path for a tenant's embed opt-in — the SAME
|
|
125
|
+
* colocated `tenants/<id>/…` config-as-code spine as capabilities/theme. A
|
|
126
|
+
* tenant self-serves by committing `embeds.json` (a JSON array of catalog
|
|
127
|
+
* slugs, e.g. `["pipedrive"]`) to their repo; the reconcile pipeline keys the
|
|
128
|
+
* KV artifact by this path and the storefront reads it back per environment
|
|
129
|
+
* (localhost=working tree, preview=PR head, live=promoted). No platform code
|
|
130
|
+
* change needed to enable a catalogued embed.
|
|
131
|
+
*/
|
|
132
|
+
export function embedsArtifactPath(tenantId: string): string {
|
|
133
|
+
return `tenants/${tenantId}/embeds.json`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Effective embed opt-in for a tenant: the build-time `TenantConfig.embeds`
|
|
138
|
+
* default overlaid with the tenant's published `embeds.json` (self-serve,
|
|
139
|
+
* per-environment) when present — deduped and FILTERED to slugs that exist in
|
|
140
|
+
* EMBED_PROVIDERS. The filter is the security floor: a tenant can only enable a
|
|
141
|
+
* platform-vetted provider, never introduce an arbitrary origin; an unknown
|
|
142
|
+
* slug is dropped here (and flagged by the embeds.json validator + lint).
|
|
143
|
+
*/
|
|
144
|
+
export function resolveEffectiveEmbeds(
|
|
145
|
+
buildDefault: readonly string[] | undefined,
|
|
146
|
+
published: readonly string[] | null,
|
|
147
|
+
): string[] {
|
|
148
|
+
const merged = new Set<string>([...(buildDefault ?? []), ...(published ?? [])]);
|
|
149
|
+
return [...merged].filter((slug) => slug in EMBED_PROVIDERS);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface EmbedCspAdditions {
|
|
153
|
+
scriptSrc: string[];
|
|
154
|
+
connectSrc: string[];
|
|
155
|
+
frameSrc: string[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* CSP additions for the third-party embeds ACTUALLY rendered on this page.
|
|
160
|
+
*
|
|
161
|
+
* An origin is added only when BOTH hold: (1) the tenant opted the provider in
|
|
162
|
+
* via `.tot/config.json` `embeds:` (audit trail + platform review of novel
|
|
163
|
+
* providers), and (2) the provider's marker appears in the rendered HTML (so a
|
|
164
|
+
* page that doesn't embed the form never carries the origin). This keeps the
|
|
165
|
+
* strict policy tenant- AND page-scoped — never a blanket global relaxation.
|
|
166
|
+
*
|
|
167
|
+
* Unknown keys in `enabled` are ignored here (the lint surfaces them); a marker
|
|
168
|
+
* present WITHOUT the matching opt-in is likewise not allowed here — that's the
|
|
169
|
+
* blocked-embed case the lint/report loop is meant to catch before it ships.
|
|
170
|
+
*/
|
|
171
|
+
export function embedCspAdditions(
|
|
172
|
+
enabled: readonly string[],
|
|
173
|
+
renderedHtml: string,
|
|
174
|
+
): EmbedCspAdditions {
|
|
175
|
+
const out: EmbedCspAdditions = { scriptSrc: [], connectSrc: [], frameSrc: [] };
|
|
176
|
+
for (const key of new Set(enabled)) {
|
|
177
|
+
const provider = EMBED_PROVIDERS[key];
|
|
178
|
+
if (!provider) continue;
|
|
179
|
+
if (!provider.markers.some((m) => renderedHtml.includes(m))) continue;
|
|
180
|
+
if (provider.scriptSrc) out.scriptSrc.push(...provider.scriptSrc);
|
|
181
|
+
if (provider.connectSrc) out.connectSrc.push(...provider.connectSrc);
|
|
182
|
+
if (provider.frameSrc) out.frameSrc.push(...provider.frameSrc);
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
88
187
|
/**
|
|
89
188
|
* CSP additions for a managed-checkout tenant. The loader comes from the checkout
|
|
90
189
|
* CDN; cart requests, frames, and add-to-cart form POSTs hit the store's own
|
|
@@ -142,6 +142,8 @@ export interface EnvResolutionInput {
|
|
|
142
142
|
/** Platform preview-signing secret (env), if configured. */
|
|
143
143
|
secret?: string | null;
|
|
144
144
|
nowSeconds: number;
|
|
145
|
+
/** Request pathname — used only to exempt the token dispenser itself (see {@link isPreviewDispatcherPath}). */
|
|
146
|
+
pathname?: string;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
export interface EnvResolution {
|
|
@@ -152,16 +154,37 @@ export interface EnvResolution {
|
|
|
152
154
|
reject?: { status: number; message: string };
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
/**
|
|
158
|
+
* The bare tenant-preview DISPATCHER path (`/<tenant>/preview` or
|
|
159
|
+
* `/<tenant>/preview/`, e.g. `pages/preview/index.astro`, tenant-first) — the page
|
|
160
|
+
* whose ENTIRE job is to mint a fresh `__preview` token and hand it to the
|
|
161
|
+
* visitor. On a preview-prefixed host this path would otherwise hit the SAME
|
|
162
|
+
* "no token -> 401" gate `resolveCustomizationEnv` enforces for every other
|
|
163
|
+
* path, deadlocking a signed-in-but-token-less visitor: they can never reach
|
|
164
|
+
* the one page that gives them a token. Distinct from `isCandidatePreviewPath`
|
|
165
|
+
* (loginGate.ts) — that exempts several preview routes from the LOGIN wall; this
|
|
166
|
+
* exempts exactly ONE route from the TOKEN wall. `<tenant>` is required to look
|
|
167
|
+
* like a real tenant id (contains a `.`) so this never accidentally matches a
|
|
168
|
+
* sibling route. Matched on the pre-strip request path.
|
|
169
|
+
*/
|
|
170
|
+
export function isPreviewDispatcherPath(pathname: string): boolean {
|
|
171
|
+
const m = pathname.match(/^\/([^/]+)\/preview\/?$/);
|
|
172
|
+
return m != null && m[1]!.includes(".");
|
|
173
|
+
}
|
|
174
|
+
|
|
155
175
|
/**
|
|
156
176
|
* Decide which customization environment to serve. Production hosts always get
|
|
157
177
|
* `live` and never consult a token. Preview hosts are auth-gated: a valid,
|
|
158
178
|
* unexpired, tenant-matching token yields `test` (noindex); anything else is
|
|
159
|
-
* rejected.
|
|
179
|
+
* rejected. The one exception is the token-dispenser path itself (see
|
|
180
|
+
* {@link isPreviewDispatcherPath}) — it renders the dispatcher's own minimal
|
|
181
|
+
* page, never tenant content, so it's let through as `live`/no-token rather
|
|
182
|
+
* than 401ing on the very request that would otherwise fetch it a token.
|
|
160
183
|
*/
|
|
161
184
|
export async function resolveCustomizationEnv(
|
|
162
185
|
input: EnvResolutionInput,
|
|
163
186
|
): Promise<EnvResolution> {
|
|
164
|
-
if (!input.isPreviewHost) {
|
|
187
|
+
if (!input.isPreviewHost || isPreviewDispatcherPath(input.pathname ?? "")) {
|
|
165
188
|
return { env: "live", noindex: false };
|
|
166
189
|
}
|
|
167
190
|
if (!input.secret) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ChannelName,
|
|
16
16
|
type PointerRecord,
|
|
17
17
|
type SiteVersionManifest,
|
|
18
|
+
type SiteVersionSummary,
|
|
18
19
|
} from "./customization-versioning.js";
|
|
19
20
|
import type { CustomizationEnv } from "./customization-preview.js";
|
|
20
21
|
|
|
@@ -93,6 +94,27 @@ export async function resolveChannelVersion(
|
|
|
93
94
|
return pointer?.channels?.[channel] ?? null;
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
/**
|
|
98
|
+
* The static-bundle `treeDigest` a channel currently serves (ADR 0012), or null
|
|
99
|
+
* when the tenant has no digest companion for that channel (a hosted tenant — it
|
|
100
|
+
* sets only `channels`, never `channelDigests`).
|
|
101
|
+
*
|
|
102
|
+
* This is the RECONCILED read the digest-addressed serving path
|
|
103
|
+
* (`static-bundle.ts`), the pointer verify (`createPointerVerifyTarget`), and
|
|
104
|
+
* the pointer-served "current" read (`resolveStaticLiveDigestFromPointer`) use
|
|
105
|
+
* INSTEAD of `resolveChannelVersion` for a static tenant: the versionId
|
|
106
|
+
* (`channels[ch]`, the Gitea sha) is NOT the R2 key — the treeDigest is, and it
|
|
107
|
+
* lives here. Reads the pointer fresh; never throws.
|
|
108
|
+
*/
|
|
109
|
+
export async function resolveChannelDigest(
|
|
110
|
+
get: KvGet,
|
|
111
|
+
tenantId: string,
|
|
112
|
+
channel: ChannelName,
|
|
113
|
+
): Promise<string | null> {
|
|
114
|
+
const pointer = await readPointer(get, tenantId);
|
|
115
|
+
return pointer?.channelDigests?.[channel] ?? null;
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
/** Read one immutable site-version manifest, or null. Never throws. */
|
|
97
119
|
export async function readSiteVersion(
|
|
98
120
|
get: KvGet,
|
|
@@ -108,12 +130,112 @@ export async function readSiteVersion(
|
|
|
108
130
|
}
|
|
109
131
|
}
|
|
110
132
|
|
|
133
|
+
/** A manifest is BUILT (servable) when it exists AND materialized ≥1 artifact. */
|
|
134
|
+
function isBuiltManifest(m: SiteVersionManifest | null): m is SiteVersionManifest {
|
|
135
|
+
return !!m && Object.keys(m.artifacts ?? {}).length > 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Read a tenant's version-history index (list of summaries), NEWEST first — the
|
|
140
|
+
* runtime read-side view of the same `siteVersionIndexKey` the KV store appends
|
|
141
|
+
* to (stored oldest-first). Never throws — malformed/absent → `[]`.
|
|
142
|
+
*/
|
|
143
|
+
export async function readSiteVersionIndex(
|
|
144
|
+
get: KvGet,
|
|
145
|
+
tenantId: string,
|
|
146
|
+
): Promise<SiteVersionSummary[]> {
|
|
147
|
+
const raw = await get(siteVersionIndexKey(tenantId));
|
|
148
|
+
if (!raw) return [];
|
|
149
|
+
try {
|
|
150
|
+
return (JSON.parse(raw) as SiteVersionSummary[]).slice().reverse();
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The outcome of self-healing a channel pointer to a SERVABLE version. */
|
|
157
|
+
export interface ServableChannelVersion {
|
|
158
|
+
/**
|
|
159
|
+
* The versionId to actually SERVE from: the pointed version when it is built,
|
|
160
|
+
* else the newest BUILT version in history (self-heal), else `null` (nothing
|
|
161
|
+
* built at all — the caller falls back to build-time, still never a bare 404).
|
|
162
|
+
*/
|
|
163
|
+
versionId: string | null;
|
|
164
|
+
/** The versionId the channel pointer NAMES (may dangle at an unbuilt version). */
|
|
165
|
+
pointedVersionId: string | null;
|
|
166
|
+
/**
|
|
167
|
+
* True when the pointer named a version with no built manifest — so the read
|
|
168
|
+
* was self-healed onto an older built version (or found none). Signals the
|
|
169
|
+
* caller to enqueue a rebuild of the pointed version.
|
|
170
|
+
*/
|
|
171
|
+
dangling: boolean;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Optional seam so a self-healing read can enqueue a rebuild of the dangling
|
|
175
|
+
* version. Best-effort/fail-open: never awaited into the serve path, never
|
|
176
|
+
* throws through it. Absent ⇒ pure resolution (no side effect). */
|
|
177
|
+
export type EnqueueRebuild = (info: {
|
|
178
|
+
tenantId: string;
|
|
179
|
+
channel: ChannelName;
|
|
180
|
+
versionId: string;
|
|
181
|
+
}) => void;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Resolve the SERVABLE version for a channel, self-healing a dangling pointer.
|
|
185
|
+
*
|
|
186
|
+
* A channel pointer can name a version that was never built (or whose manifest
|
|
187
|
+
* was evicted). Serving that naively empties/404s the whole surface. Instead: if
|
|
188
|
+
* the pointed version has no built manifest, fall back to the newest version in
|
|
189
|
+
* history that DOES, and flag `dangling` so the caller can enqueue a rebuild.
|
|
190
|
+
* Only kicks in when the pointed
|
|
191
|
+
* manifest is entirely absent/unbuilt — a present manifest that merely omits one
|
|
192
|
+
* path stays authoritative (unchanged behavior). Reads fresh; never throws.
|
|
193
|
+
*/
|
|
194
|
+
export async function resolveServableChannelVersion(
|
|
195
|
+
get: KvGet,
|
|
196
|
+
tenantId: string,
|
|
197
|
+
channel: ChannelName,
|
|
198
|
+
enqueueRebuild?: EnqueueRebuild,
|
|
199
|
+
): Promise<ServableChannelVersion> {
|
|
200
|
+
const pointedVersionId = await resolveChannelVersion(get, tenantId, channel);
|
|
201
|
+
if (!pointedVersionId) {
|
|
202
|
+
return { versionId: null, pointedVersionId: null, dangling: false };
|
|
203
|
+
}
|
|
204
|
+
const pointed = await readSiteVersion(get, tenantId, pointedVersionId);
|
|
205
|
+
if (isBuiltManifest(pointed)) {
|
|
206
|
+
return { versionId: pointedVersionId, pointedVersionId, dangling: false };
|
|
207
|
+
}
|
|
208
|
+
// Dangling: the pointer names an unbuilt/absent version. Self-heal onto the
|
|
209
|
+
// newest BUILT version in history so the surface keeps rendering last-good.
|
|
210
|
+
let healed: string | null = null;
|
|
211
|
+
for (const summary of await readSiteVersionIndex(get, tenantId)) {
|
|
212
|
+
if (summary.versionId === pointedVersionId) continue;
|
|
213
|
+
if (isBuiltManifest(await readSiteVersion(get, tenantId, summary.versionId))) {
|
|
214
|
+
healed = summary.versionId;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (enqueueRebuild) {
|
|
219
|
+
try {
|
|
220
|
+
enqueueRebuild({ tenantId, channel, versionId: pointedVersionId });
|
|
221
|
+
} catch {
|
|
222
|
+
/* fail-open: enqueue is best-effort, never breaks the read */
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { versionId: healed, pointedVersionId, dangling: true };
|
|
226
|
+
}
|
|
227
|
+
|
|
111
228
|
/**
|
|
112
229
|
* Resolve a single artifact's content via the VERSIONED path:
|
|
113
230
|
* channel → versionId → site-version manifest → content hash → bytes. Returns
|
|
114
231
|
* null (so the caller falls back to build-time) when the tenant has no channel
|
|
115
|
-
* pointer, the
|
|
116
|
-
*
|
|
232
|
+
* pointer, the path isn't in the snapshot, or the content is gone. Never throws.
|
|
233
|
+
*
|
|
234
|
+
* Self-healing: when the channel pointer DANGLES (names an unbuilt/absent
|
|
235
|
+
* version), the artifact is read from the newest built version instead of
|
|
236
|
+
* returning null for every path — so a dangling `preview`/`live` pointer serves
|
|
237
|
+
* the last built site rather than 404ing the whole surface. See
|
|
238
|
+
* {@link resolveServableChannelVersion}.
|
|
117
239
|
*/
|
|
118
240
|
export async function readVersionedArtifact(
|
|
119
241
|
get: KvGet,
|
|
@@ -121,7 +243,7 @@ export async function readVersionedArtifact(
|
|
|
121
243
|
channel: ChannelName,
|
|
122
244
|
path: string,
|
|
123
245
|
): Promise<string | null> {
|
|
124
|
-
const versionId = await
|
|
246
|
+
const { versionId } = await resolveServableChannelVersion(get, tenantId, channel);
|
|
125
247
|
if (!versionId) return null;
|
|
126
248
|
const manifest = await readSiteVersion(get, tenantId, versionId);
|
|
127
249
|
const contentHash = manifest?.artifacts?.[path];
|
|
@@ -163,3 +285,34 @@ export async function readPublishedArtifact(
|
|
|
163
285
|
if (!versionId) return null;
|
|
164
286
|
return get(customizationArtifactKey(tenantId, versionId));
|
|
165
287
|
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* List artifact paths in the immutable site version selected for `(tenant, env)`.
|
|
291
|
+
*
|
|
292
|
+
* `null` means the tenant/channel has not adopted versioned publishing, so a
|
|
293
|
+
* caller may use its local/legacy fallback. Once a channel pointer exists the
|
|
294
|
+
* manifest is authoritative and this returns an array (possibly empty). This is
|
|
295
|
+
* the enumeration companion to `readPublishedArtifact`: indexes, sitemaps, and
|
|
296
|
+
* collection routes can discover one tenant's content at request time without
|
|
297
|
+
* bundling every configured tenant into the framework Worker.
|
|
298
|
+
*/
|
|
299
|
+
export async function listPublishedArtifactPaths(
|
|
300
|
+
get: KvGet,
|
|
301
|
+
tenantId: string,
|
|
302
|
+
env: CustomizationEnv,
|
|
303
|
+
prefix = "",
|
|
304
|
+
): Promise<string[] | null> {
|
|
305
|
+
const pointer = await readPointer(get, tenantId);
|
|
306
|
+
if (!pointer) return null;
|
|
307
|
+
|
|
308
|
+
const channel = channelFromEnv(env);
|
|
309
|
+
if (!pointer.channels?.[channel]) return null;
|
|
310
|
+
|
|
311
|
+
const { versionId } = await resolveServableChannelVersion(get, tenantId, channel);
|
|
312
|
+
if (!versionId) return [];
|
|
313
|
+
const manifest = await readSiteVersion(get, tenantId, versionId);
|
|
314
|
+
if (!manifest) return [];
|
|
315
|
+
return Object.keys(manifest.artifacts ?? {})
|
|
316
|
+
.filter((path) => path.startsWith(prefix))
|
|
317
|
+
.sort((a, b) => a.localeCompare(b));
|
|
318
|
+
}
|
|
@@ -37,6 +37,15 @@ export type ChannelPointers = Partial<Record<ChannelName, string>>;
|
|
|
37
37
|
* - `channels`: the versioned-publish channel pointers (`live`/`preview` →
|
|
38
38
|
* a single site `versionId`). Additive + optional; when present it is
|
|
39
39
|
* authoritative for that channel and shadows the legacy selection.
|
|
40
|
+
* - `channelDigests`: (ADR 0012) the RECONCILED static-serving companion to
|
|
41
|
+
* `channels`. For a static-publish tenant, `channels[ch]` still holds the site
|
|
42
|
+
* `versionId` (the tenant Gitea commit sha — the hosted contract), while
|
|
43
|
+
* `channelDigests[ch]` holds that same publish's bundle `treeDigest` (the
|
|
44
|
+
* content-addressed R2 key prefix `versions/<digest>/` `static-bundle.ts`
|
|
45
|
+
* reads). The two are DISTINCT namespaces — the Gitea sha is NOT the treeDigest
|
|
46
|
+
* — and are written in the SAME CAS pointer write (`setChannel`), so they can
|
|
47
|
+
* never drift. Absent for a hosted tenant (which only ever sets/reads
|
|
48
|
+
* `channels`).
|
|
40
49
|
* - `rev`: optimistic-concurrency token, incremented on every successful write.
|
|
41
50
|
*/
|
|
42
51
|
export interface PointerRecord {
|
|
@@ -45,6 +54,7 @@ export interface PointerRecord {
|
|
|
45
54
|
live: Selection;
|
|
46
55
|
test: Selection;
|
|
47
56
|
channels?: ChannelPointers;
|
|
57
|
+
channelDigests?: ChannelPointers;
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
export function emptyPointer(tenantId: string): PointerRecord {
|