@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,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digest-addressed static-bundle serving (ADR 0012 —
|
|
3
|
+
* `docs/architecture/decisions/0012-instant-static-rollback-pointer-substrate.md`),
|
|
4
|
+
* dedup-aware (per-file content-addressed asset dedup,
|
|
5
|
+
* `docs/epics/proposed/storefront-instant-static-rollback.md` Addendum §A).
|
|
6
|
+
*
|
|
7
|
+
* Reads an already-materialized, already-staged static bundle
|
|
8
|
+
* (`scripts/publish/materialize-tenant.mjs` output, staged by
|
|
9
|
+
* `scripts/publish/stage-bundle.mjs` to `versions/<treeDigest>/…` PLUS the
|
|
10
|
+
* shared `assets/<sha256>` pool) straight out of R2 and serves the bytes
|
|
11
|
+
* VERBATIM — no re-render, no per-request CSP-nonce middleware (the bundle
|
|
12
|
+
* carries one pinned nonce for its whole tree; see the ADR's "Consequences"
|
|
13
|
+
* section). This is the read half: the write path (what PUTs a bundle into this
|
|
14
|
+
* R2 bucket) is `scripts/publish/lib/r2-runner.mjs`, which stages every
|
|
15
|
+
* `versions/<digest>/` bundle at candidate-prebuild time. Every static-publish
|
|
16
|
+
* tenant's production reads are served through here.
|
|
17
|
+
*
|
|
18
|
+
* DIGEST CONTRACT (RECONCILED, T14 — ADR 0012): the caller resolves "what to
|
|
19
|
+
* serve" via `resolveChannelDigest(get, tenantId, "live")` — the `channelDigests`
|
|
20
|
+
* companion to the `live` channel pointer, NOT `resolveChannelVersion` (which
|
|
21
|
+
* returns the versionId / Gitea sha, a DIFFERENT namespace than the treeDigest).
|
|
22
|
+
* `gatedPromote` (go-live) and `rollbackCandidateAtomic` (rollback) write the
|
|
23
|
+
* staged bundle's real `treeDigest` to `channelDigests.live` in the SAME CAS flip
|
|
24
|
+
* that moves the versionId, so this module gets the exact R2 key prefix
|
|
25
|
+
* `versions/<treeDigest>/`. A static tenant whose `live` has a versionId but no
|
|
26
|
+
* digest companion yet resolves to `null` here and fails closed (see below)
|
|
27
|
+
* rather than serving the wrong bytes under the versionId.
|
|
28
|
+
*
|
|
29
|
+
* T6 KEY RESOLUTION (`kind: "route"` vs `kind: "asset"`, `scripts/publish/lib/
|
|
30
|
+
* asset-pool.mjs`'s classification, carried verbatim in `manifest.files`): the
|
|
31
|
+
* manifest is now read on EVERY request (not just HTML, as before T6) because
|
|
32
|
+
* it's the ONLY source of truth for which R2 key a bundle-relative path
|
|
33
|
+
* resolves to — `kind: "route"` files still live at `versions/<digest>/<rel>`
|
|
34
|
+
* (rendered fresh per version), `kind: "asset"` files live at the SHARED,
|
|
35
|
+
* content-addressed `assets/<sha256><ext>` pool (`staticBundleAssetPoolKey`,
|
|
36
|
+
* kept in lockstep with `scripts/publish/lib/asset-pool.mjs`'s `assetPoolKey`
|
|
37
|
+
* — that module can't be imported here, this is the Worker bundle, that one
|
|
38
|
+
* runs under plain node). A `rel` absent from `manifest.files` (a genuinely
|
|
39
|
+
* unexpected path, or a pre-T6 manifest with no `files` classification) falls
|
|
40
|
+
* back to the OLD `versions/<digest>/<rel>` lookup — fail-closed on a miss
|
|
41
|
+
* either way, never a guess.
|
|
42
|
+
*/
|
|
43
|
+
import { assetContentType } from "./tenant-assets.js";
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// R2 seam — same structural subset tenant-assets.ts uses (no workers-types dep)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export interface StaticBundleR2ObjectLike {
|
|
50
|
+
body: ReadableStream<Uint8Array> | null;
|
|
51
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
52
|
+
size?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface StaticBundleR2BucketLike {
|
|
56
|
+
get(key: string): Promise<StaticBundleR2ObjectLike | null>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Path mapping — request pathname -> bundle-relative object path
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reject empty, absolute-outside-bundle, or traversal-bearing relative paths
|
|
65
|
+
* (mirrors `tenant-assets.ts`'s `isSafeRel`, duplicated here to keep this
|
|
66
|
+
* module import-independent of the assets pointer model).
|
|
67
|
+
*/
|
|
68
|
+
function isSafeRel(rel: string): boolean {
|
|
69
|
+
if (!rel) return false;
|
|
70
|
+
return !rel.split("/").some((seg) => seg === ".." || seg === ".");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Map a request pathname to the bundle-relative file the materializer wrote
|
|
75
|
+
* for it — the SAME transform `scripts/publish/lib/materialize.mjs`'s
|
|
76
|
+
* `bundlePathForRoute` (routes) and `assetFilePath` (assets) apply, ported
|
|
77
|
+
* here as one pure function since `scripts/` is not importable from the
|
|
78
|
+
* Worker bundle:
|
|
79
|
+
* "/" -> "index.html"
|
|
80
|
+
* "/about" -> "about/index.html" (directory-index for pretty URLs)
|
|
81
|
+
* "/a/b/" -> "a/b/index.html"
|
|
82
|
+
* "/sitemap.xml" -> "sitemap.xml" (a real file extension: as-is)
|
|
83
|
+
* "/_astro/x.js" -> "_astro/x.js" (asset: as-is)
|
|
84
|
+
* Returns null for an unsafe path (traversal, decode failure).
|
|
85
|
+
*/
|
|
86
|
+
export function staticBundleRelPath(pathname: string): string | null {
|
|
87
|
+
let decoded: string;
|
|
88
|
+
try {
|
|
89
|
+
decoded = decodeURIComponent(pathname);
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const clean = decoded.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
94
|
+
if (clean === "") return "index.html";
|
|
95
|
+
if (!isSafeRel(clean)) return null;
|
|
96
|
+
const last = clean.split("/").pop() || "";
|
|
97
|
+
if (/\.[a-z0-9]+$/i.test(last)) return clean;
|
|
98
|
+
return `${clean}/index.html`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Full R2 object key for a `route`-kind bundle-relative path at a given digest. */
|
|
102
|
+
export function staticBundleObjectKey(digest: string, rel: string): string {
|
|
103
|
+
return `versions/${digest}/${rel}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** R2 key for a digest's bundle manifest (materialize-tenant.mjs's `manifest.json`). */
|
|
107
|
+
export function staticBundleManifestKey(digest: string): string {
|
|
108
|
+
return `versions/${digest}/manifest.json`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* T6: the shared content-addressed pool key for one asset's hash — MUST stay
|
|
113
|
+
* in lockstep with `scripts/publish/lib/asset-pool.mjs`'s `assetPoolKey`
|
|
114
|
+
* (same extension-preserving `assets/<sha256hex><ext>` shape; that module
|
|
115
|
+
* can't be imported here since this is the Worker bundle and that one runs
|
|
116
|
+
* under plain node — both sides' tests assert the exact key shape).
|
|
117
|
+
*/
|
|
118
|
+
export function staticBundleAssetPoolKey(hash: string, rel: string): string {
|
|
119
|
+
const dot = rel.lastIndexOf(".");
|
|
120
|
+
const slash = rel.lastIndexOf("/");
|
|
121
|
+
const ext = dot > slash ? rel.slice(dot) : "";
|
|
122
|
+
return `assets/${hash}${ext}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the R2 key to fetch for a `rel` bundle path, using the manifest's
|
|
127
|
+
* T6 file classification: `kind: "asset"` -> the shared pool key (by content
|
|
128
|
+
* hash, NOT the version); anything else (`kind: "route"`, or `rel` absent
|
|
129
|
+
* from `files` entirely — a pre-T6 manifest, or a genuinely unlisted path) ->
|
|
130
|
+
* the per-version `versions/<digest>/<rel>` key, same as before T6.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveStaticBundleObjectKey(
|
|
133
|
+
digest: string,
|
|
134
|
+
rel: string,
|
|
135
|
+
files: StaticBundleManifest["files"] | undefined,
|
|
136
|
+
): string {
|
|
137
|
+
const entry = files?.[rel];
|
|
138
|
+
if (entry?.kind === "asset" && entry.hash) {
|
|
139
|
+
return staticBundleAssetPoolKey(entry.hash, rel);
|
|
140
|
+
}
|
|
141
|
+
return staticBundleObjectKey(digest, rel);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Manifest (CSP pinned-nonce header) — read once per hit for an HTML response
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/** One `manifest.files` entry (T6, `scripts/publish/lib/asset-pool.mjs`'s `classifyBundleFiles` shape). */
|
|
149
|
+
export interface StaticBundleFileEntry {
|
|
150
|
+
hash: string;
|
|
151
|
+
kind: "route" | "asset";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The slice of `materialize-tenant.mjs`'s manifest this read path needs. */
|
|
155
|
+
export interface StaticBundleManifest {
|
|
156
|
+
treeDigest: string;
|
|
157
|
+
pinnedNonce: string;
|
|
158
|
+
csp: { headerName: string; value: string } | null;
|
|
159
|
+
/** T6 classification: relPath -> {hash, kind}. Absent on a pre-T6 manifest — every lookup then falls back to the old versions/<digest>/<rel> key (see `resolveStaticBundleObjectKey`). */
|
|
160
|
+
files?: Record<string, StaticBundleFileEntry>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function parseManifest(raw: string): StaticBundleManifest | null {
|
|
164
|
+
try {
|
|
165
|
+
const m = JSON.parse(raw) as Partial<StaticBundleManifest>;
|
|
166
|
+
if (typeof m.treeDigest !== "string" || typeof m.pinnedNonce !== "string") return null;
|
|
167
|
+
const csp =
|
|
168
|
+
m.csp &&
|
|
169
|
+
typeof (m.csp as { headerName?: unknown }).headerName === "string" &&
|
|
170
|
+
typeof (m.csp as { value?: unknown }).value === "string"
|
|
171
|
+
? (m.csp as { headerName: string; value: string })
|
|
172
|
+
: null;
|
|
173
|
+
let files: StaticBundleManifest["files"];
|
|
174
|
+
if (m.files && typeof m.files === "object") {
|
|
175
|
+
files = {};
|
|
176
|
+
for (const [rel, entry] of Object.entries(m.files as Record<string, unknown>)) {
|
|
177
|
+
const e = entry as Partial<StaticBundleFileEntry> | null;
|
|
178
|
+
if (
|
|
179
|
+
e &&
|
|
180
|
+
typeof e.hash === "string" &&
|
|
181
|
+
(e.kind === "route" || e.kind === "asset")
|
|
182
|
+
) {
|
|
183
|
+
files[rel] = { hash: e.hash, kind: e.kind };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { treeDigest: m.treeDigest, pinnedNonce: m.pinnedNonce, csp, files };
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Read+parse a digest's `manifest.json` from R2. Null on any miss/malformed data. */
|
|
194
|
+
export async function readStaticBundleManifest(
|
|
195
|
+
bucket: StaticBundleR2BucketLike,
|
|
196
|
+
digest: string,
|
|
197
|
+
): Promise<StaticBundleManifest | null> {
|
|
198
|
+
const object = await bucket.get(staticBundleManifestKey(digest));
|
|
199
|
+
if (!object) return null;
|
|
200
|
+
const text = new TextDecoder().decode(await object.arrayBuffer());
|
|
201
|
+
return parseManifest(text);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Serve — resolve digest, stream the object from R2, fail closed on a miss
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
export interface ServeStaticBundleDeps {
|
|
209
|
+
bucket: StaticBundleR2BucketLike;
|
|
210
|
+
/**
|
|
211
|
+
* The digest to serve, already resolved by the caller (typically
|
|
212
|
+
* `resolveChannelVersion(get, tenantId, "live")` — see the module
|
|
213
|
+
* docstring's DIGEST CONTRACT). `null` means "pointer-serving is on for
|
|
214
|
+
* this tenant but nothing is live yet" — fails closed, same as a missing
|
|
215
|
+
* object, per the ADR: this path is only ever reached for a confirmed
|
|
216
|
+
* pointer-serving tenant, so an unresolved digest is itself a fault, not a
|
|
217
|
+
* "fall through to SSR" signal.
|
|
218
|
+
*/
|
|
219
|
+
digest: string | null;
|
|
220
|
+
/** Best-effort diagnostic sink (never throws, never blocks the response). */
|
|
221
|
+
onFailClosed?: (detail: string) => void;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Structured detail for the one fail-closed shape this module can produce. */
|
|
225
|
+
export class StaticBundleUnavailableError extends Error {
|
|
226
|
+
readonly code = "static-bundle-unavailable" as const;
|
|
227
|
+
constructor(detail: string) {
|
|
228
|
+
super(detail);
|
|
229
|
+
this.name = "StaticBundleUnavailableError";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function failClosed(deps: ServeStaticBundleDeps, detail: string): Response {
|
|
234
|
+
try {
|
|
235
|
+
deps.onFailClosed?.(detail);
|
|
236
|
+
} catch {
|
|
237
|
+
/* diagnostic sink must never break the response path */
|
|
238
|
+
}
|
|
239
|
+
// 503, not a 200 masquerading as content and not a 404 (which would read as
|
|
240
|
+
// "this route doesn't exist" — it does, the bundle is just unreachable) and
|
|
241
|
+
// never an SSR fallthrough: this route is ONLY reached for a tenant
|
|
242
|
+
// confirmed to be on the pointer-serving model, so an unreadable bundle is
|
|
243
|
+
// an infra fault to surface (verify/monitoring), not a content gap.
|
|
244
|
+
return new Response("Static bundle unavailable", {
|
|
245
|
+
status: 503,
|
|
246
|
+
headers: {
|
|
247
|
+
"cache-control": "no-store",
|
|
248
|
+
"x-tot-static-bundle": "fail-closed",
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Cache-Control for a `route`-kind file (HTML/xml/txt — rendered fresh per version, stable non-digest url). */
|
|
254
|
+
const ROUTE_CACHE_CONTROL = "public, max-age=0, s-maxage=300, stale-while-revalidate=86400";
|
|
255
|
+
/** Cache-Control for an `asset`-kind file — the pool key IS the content hash, so caching forever is always correct (epic §A). */
|
|
256
|
+
const ASSET_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Serve `pathname` from the digest-addressed static bundle in R2.
|
|
260
|
+
*
|
|
261
|
+
* Returns `null` — the ONE "fall through to SSR" signal — when the manifest's
|
|
262
|
+
* `files` map proves `pathname` is not part of this bundle's static surface:
|
|
263
|
+
* app surfaces (/admin, /cockpit, checkout, …) share the tenant's host but are
|
|
264
|
+
* never materialized into a bundle, so the caller must render them. Only a
|
|
265
|
+
* manifest WITH `files` can prove non-membership; a pre-T6 manifest keeps the
|
|
266
|
+
* fetch-then-fail-closed path.
|
|
267
|
+
*
|
|
268
|
+
* Every OTHER outcome (hit, unsafe path, unresolved digest, missing manifest,
|
|
269
|
+
* missing object for a LISTED file) terminates the request here rather than
|
|
270
|
+
* falling through — a staged digest's listed route must never silently degrade
|
|
271
|
+
* into SSR (wrong/divergent bytes), per the ADR's fail-closed guidance.
|
|
272
|
+
*
|
|
273
|
+
* T6: the manifest is now read FIRST, on every request (not just HTML, as
|
|
274
|
+
* before T6) — it's the only source of truth for whether `rel` resolves to
|
|
275
|
+
* the per-version `versions/<digest>/<rel>` key or the shared content-addressed
|
|
276
|
+
* `assets/<sha256>` pool key (`resolveStaticBundleObjectKey`). A missing
|
|
277
|
+
* manifest fails closed the same way a missing object always has: the
|
|
278
|
+
* manifest is deliberately the LAST thing `stage-bundle.mjs` writes for a
|
|
279
|
+
* digest (see `s3-publish.mjs`'s `buildManifestPromoteStep`), so its absence
|
|
280
|
+
* means this digest was never fully staged — serving from it would be wrong
|
|
281
|
+
* before T6 too, this just makes the fail-closed check unconditional.
|
|
282
|
+
*
|
|
283
|
+
* On a hit, serves the R2 bytes VERBATIM with a Cache-Control keyed by the
|
|
284
|
+
* manifest's file `kind` (long-lived immutable for the shared `asset` pool —
|
|
285
|
+
* safe because the key itself is the content hash; short/revalidating for a
|
|
286
|
+
* `route` doc, rendered fresh per version at a STABLE, non-digest url) and,
|
|
287
|
+
* for an HTML response, the bundle's PINNED CSP header read back from the
|
|
288
|
+
* same manifest read — never the Worker's per-request nonce middleware, per
|
|
289
|
+
* the ADR's CSP-consequences note.
|
|
290
|
+
*/
|
|
291
|
+
export async function serveStaticBundle(
|
|
292
|
+
deps: ServeStaticBundleDeps,
|
|
293
|
+
pathname: string,
|
|
294
|
+
): Promise<Response | null> {
|
|
295
|
+
const { bucket, digest } = deps;
|
|
296
|
+
if (!digest) {
|
|
297
|
+
return failClosed(deps, "pointer-serving tenant has no resolved live digest");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const rel = staticBundleRelPath(pathname);
|
|
301
|
+
if (!rel) {
|
|
302
|
+
return failClosed(deps, `unsafe/unparseable path: ${JSON.stringify(pathname)}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const manifest = await readStaticBundleManifest(bucket, digest);
|
|
306
|
+
if (!manifest) {
|
|
307
|
+
return failClosed(deps, `missing/unreadable manifest for digest ${digest}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const entry = manifest.files?.[rel];
|
|
311
|
+
if (manifest.files && !entry) {
|
|
312
|
+
// Provably not in this bundle's static surface — an app surface (/admin,
|
|
313
|
+
// /cockpit, …) or a route this version never materialized. SSR owns it.
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const objectKey = resolveStaticBundleObjectKey(digest, rel, manifest.files);
|
|
317
|
+
const object = await bucket.get(objectKey);
|
|
318
|
+
if (!object) {
|
|
319
|
+
return failClosed(deps, `missing R2 object ${objectKey}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const isHtml = rel.endsWith(".html");
|
|
323
|
+
// Cache tier: use the manifest's `kind` classification when present.
|
|
324
|
+
// A manifest with no `files` at all falls back to the ORIGINAL
|
|
325
|
+
// isHtml-based heuristic — every non-html path used to be treated as a
|
|
326
|
+
// long-cacheable asset, which this preserves exactly for an old bundle
|
|
327
|
+
// this Worker version might still encounter mid-rollout.
|
|
328
|
+
const cacheControl = entry
|
|
329
|
+
? entry.kind === "asset"
|
|
330
|
+
? ASSET_CACHE_CONTROL
|
|
331
|
+
: ROUTE_CACHE_CONTROL
|
|
332
|
+
: isHtml
|
|
333
|
+
? ROUTE_CACHE_CONTROL
|
|
334
|
+
: ASSET_CACHE_CONTROL;
|
|
335
|
+
const headers: Record<string, string> = {
|
|
336
|
+
"content-type": assetContentType(rel),
|
|
337
|
+
// Cache-key-by-digest lives in the caller's edge-cache layer for `route`
|
|
338
|
+
// keys (versions/<digest>/<rel> is digest-scoped — a pointer flip is a
|
|
339
|
+
// clean cache miss on the NEW digest's keys, never a stale serve of the
|
|
340
|
+
// old one); `asset` keys are ADDITIONALLY digest-independent (content-
|
|
341
|
+
// addressed by hash), so they're safe to cache forever downstream too.
|
|
342
|
+
// This header governs downstream/browser caching of the STABLE URL:
|
|
343
|
+
"cache-control": cacheControl,
|
|
344
|
+
"x-tot-static-bundle": "hit",
|
|
345
|
+
"x-tot-static-bundle-digest": digest,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
if (isHtml && manifest.csp) {
|
|
349
|
+
headers[manifest.csp.headerName] = manifest.csp.value;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (typeof object.size === "number") {
|
|
353
|
+
headers["content-length"] = String(object.size);
|
|
354
|
+
}
|
|
355
|
+
const body: BodyInit = object.body ?? (await object.arrayBuffer());
|
|
356
|
+
return new Response(body, { status: 200, headers });
|
|
357
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Versioned tenant static-asset store
|
|
2
|
+
* Versioned tenant static-asset store.
|
|
3
3
|
*
|
|
4
4
|
* Tenant static assets (`tenants/<id>/public/**` — logo, CSS, ~36 binaries) are
|
|
5
5
|
* served from **R2, version-scoped**, replacing the build-time
|
|
@@ -275,6 +275,46 @@ export interface IngestResult {
|
|
|
275
275
|
count: number;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
const R2_PUT_MAX_ATTEMPTS = 3;
|
|
279
|
+
const R2_PUT_RETRY_DELAYS_MS = [100, 400] as const;
|
|
280
|
+
|
|
281
|
+
function errorField(error: unknown, field: string): unknown {
|
|
282
|
+
if (typeof error !== "object" || error === null || !(field in error)) return undefined;
|
|
283
|
+
return (error as Record<string, unknown>)[field];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function isTransientR2PutError(error: unknown): boolean {
|
|
287
|
+
const code = String(errorField(error, "code") ?? "");
|
|
288
|
+
const status = Number(errorField(error, "status") ?? errorField(error, "statusCode"));
|
|
289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
290
|
+
return (
|
|
291
|
+
code === "10001" ||
|
|
292
|
+
code === "InternalError" ||
|
|
293
|
+
(Number.isFinite(status) && status >= 500 && status <= 599) ||
|
|
294
|
+
/\b10001\b|internal error|temporarily unavailable|service unavailable|timed? out/i.test(message)
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function waitForR2Retry(ms: number): Promise<void> {
|
|
299
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function putAssetWithRetry(
|
|
303
|
+
bucket: R2BucketLike,
|
|
304
|
+
key: string,
|
|
305
|
+
bytes: Uint8Array,
|
|
306
|
+
): Promise<void> {
|
|
307
|
+
for (let attempt = 1; attempt <= R2_PUT_MAX_ATTEMPTS; attempt++) {
|
|
308
|
+
try {
|
|
309
|
+
await bucket.put(key, bytes);
|
|
310
|
+
return;
|
|
311
|
+
} catch (error) {
|
|
312
|
+
if (attempt === R2_PUT_MAX_ATTEMPTS || !isTransientR2PutError(error)) throw error;
|
|
313
|
+
await waitForR2Retry(R2_PUT_RETRY_DELAYS_MS[attempt - 1]!);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
278
318
|
/**
|
|
279
319
|
* Write a version's public assets to R2 under `tenants/<id>/<versionId>/<rel>`.
|
|
280
320
|
* Content-addressed by the (caller-supplied or computed) snapshot id → immutable
|
|
@@ -294,7 +334,9 @@ export async function ingestAssetVersion(
|
|
|
294
334
|
const keys: string[] = [];
|
|
295
335
|
for (const a of assets) {
|
|
296
336
|
const key = tenantAssetKey(tenantId, vid, a.rel);
|
|
297
|
-
|
|
337
|
+
// The key and bytes are immutable for this version, so retrying an ambiguous
|
|
338
|
+
// transient R2 failure is idempotent even if the first write landed server-side.
|
|
339
|
+
await putAssetWithRetry(bucket, key, a.bytes);
|
|
298
340
|
keys.push(key);
|
|
299
341
|
}
|
|
300
342
|
return { versionId: vid, keys, count: keys.length };
|
|
@@ -411,6 +453,16 @@ export interface ServeTenantAssetDeps {
|
|
|
411
453
|
kvGet: (key: string) => Promise<string | null>;
|
|
412
454
|
/** Channel to serve; default "live". Pass `locals.customizationEnv`. */
|
|
413
455
|
channel?: CustomizationEnv;
|
|
456
|
+
/**
|
|
457
|
+
* Explicit snapshot versionId that OVERRIDES the channel→version pointer. Set for
|
|
458
|
+
* a CANDIDATE preview session: a candidate's assets are ingested to R2 keyed by
|
|
459
|
+
* the candidate versionId but NEVER pointed to by the shared test/live channel
|
|
460
|
+
* (the no-channel-flip invariant), so without this the candidate's NEW assets are
|
|
461
|
+
* materialized-but-unreachable. The caller must have authorized the viewer for
|
|
462
|
+
* THIS tenant (the versionId is a content hash, tenant-scoped by the R2 key) —
|
|
463
|
+
* see the `/tenants/<id>` route's preview-token check.
|
|
464
|
+
*/
|
|
465
|
+
versionId?: string;
|
|
414
466
|
/** Request `If-None-Match` header, for conditional GET (304). */
|
|
415
467
|
ifNoneMatch?: string | null;
|
|
416
468
|
}
|
|
@@ -434,8 +486,13 @@ export async function serveTenantAsset(
|
|
|
434
486
|
if (!parsed) return null;
|
|
435
487
|
const { tenantId, rel } = parsed;
|
|
436
488
|
|
|
437
|
-
|
|
438
|
-
|
|
489
|
+
// A candidate-preview override pins the snapshot directly; otherwise resolve the
|
|
490
|
+
// channel→version pointer (live/test) as usual.
|
|
491
|
+
let versionId = deps.versionId ?? null;
|
|
492
|
+
if (!versionId) {
|
|
493
|
+
const pointer = await readAssetsPointer({ get: deps.kvGet }, tenantId);
|
|
494
|
+
versionId = resolveAssetVersion(pointer, deps.channel ?? "live");
|
|
495
|
+
}
|
|
439
496
|
if (!versionId) return null; // non-versioned tenant → fall through
|
|
440
497
|
|
|
441
498
|
const etag = `"${versionId}"`;
|
|
@@ -477,3 +534,85 @@ function matchesEtag(ifNoneMatch: string, etag: string): boolean {
|
|
|
477
534
|
.split(",")
|
|
478
535
|
.some((t) => t.trim() === "*" || norm(t) === target);
|
|
479
536
|
}
|
|
537
|
+
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// Verify — fetch-confirm a version's app-asset subtree actually materialized
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
|
|
542
|
+
/** Outcome of {@link verifyMaterializedAssets}. */
|
|
543
|
+
export interface AssetVerificationResult {
|
|
544
|
+
/** App-asset objects expected to serve at this version. */
|
|
545
|
+
expected: number;
|
|
546
|
+
/** How many were confirmed present + non-empty in R2. */
|
|
547
|
+
verified: number;
|
|
548
|
+
/** The served relative paths (or artifact paths) that did NOT materialize. */
|
|
549
|
+
missing: string[];
|
|
550
|
+
/** Set when the verification itself could not run (fail-closed, not a pass). */
|
|
551
|
+
error?: string;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** How many R2 existence probes run at once — matches the runtime's ~6/host reality. */
|
|
555
|
+
const VERIFY_PROBE_POOL = 6;
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Confirm every app-asset artifact path is present + non-empty in R2 at
|
|
559
|
+
* `versionId` — the EXACT object `serveTenantAsset` streams for `/tenants/<id>/<rel>`.
|
|
560
|
+
* Positive, fail-closed evidence for the version-scoped asset lane: an asset whose
|
|
561
|
+
* path can't normalize to a served rel, or is absent/empty in R2, is reported
|
|
562
|
+
* `missing`; a systemic fault (the bucket throwing on every probe) reports the whole
|
|
563
|
+
* subtree missing with an `error` rather than a vacuous pass. Checking the object at
|
|
564
|
+
* the published `versionId` (not an HTTP self-fetch) is channel-independent: the
|
|
565
|
+
* served route may resolve a different channel→version pointer per request, but a
|
|
566
|
+
* version's bytes are fixed. `bucket` needs only `get`; the body is never read.
|
|
567
|
+
*/
|
|
568
|
+
export async function verifyMaterializedAssets(
|
|
569
|
+
bucket: Pick<R2BucketLike, "get">,
|
|
570
|
+
tenantId: string,
|
|
571
|
+
versionId: string,
|
|
572
|
+
appAssetArtifactPaths: readonly string[],
|
|
573
|
+
): Promise<AssetVerificationResult> {
|
|
574
|
+
const expected = appAssetArtifactPaths.length;
|
|
575
|
+
if (expected === 0) return { expected: 0, verified: 0, missing: [] };
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
const missing: string[] = [];
|
|
579
|
+
let verified = 0;
|
|
580
|
+
let next = 0;
|
|
581
|
+
const worker = async (): Promise<void> => {
|
|
582
|
+
while (next < appAssetArtifactPaths.length) {
|
|
583
|
+
const artifactPath = appAssetArtifactPaths[next++]!;
|
|
584
|
+
const rel = toPublicRel(tenantId, artifactPath);
|
|
585
|
+
// An app asset whose path doesn't normalize to a served rel can't serve —
|
|
586
|
+
// count it missing (fail-closed), labelled by its artifact path.
|
|
587
|
+
if (!rel) {
|
|
588
|
+
missing.push(artifactPath);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
let present = false;
|
|
592
|
+
try {
|
|
593
|
+
const obj = await bucket.get(tenantAssetKey(tenantId, versionId, rel));
|
|
594
|
+
// Present when the object exists and (when the store reports size) is non-empty.
|
|
595
|
+
present = obj != null && (obj.size == null || obj.size > 0);
|
|
596
|
+
} catch {
|
|
597
|
+
present = false;
|
|
598
|
+
}
|
|
599
|
+
if (present) verified++;
|
|
600
|
+
else missing.push(rel);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const width = Math.max(1, Math.min(VERIFY_PROBE_POOL, appAssetArtifactPaths.length));
|
|
604
|
+
await Promise.all(Array.from({ length: width }, worker));
|
|
605
|
+
// Stable order aids diffing/readability — pool completion order is nondeterministic.
|
|
606
|
+
missing.sort();
|
|
607
|
+
return { expected, verified, missing };
|
|
608
|
+
} catch (err) {
|
|
609
|
+
// Systemic failure (e.g. the bucket seam throwing on every call): fail closed —
|
|
610
|
+
// report the whole subtree as unverified rather than claiming a pass.
|
|
611
|
+
return {
|
|
612
|
+
expected,
|
|
613
|
+
verified: 0,
|
|
614
|
+
missing: [...appAssetArtifactPaths],
|
|
615
|
+
error: err instanceof Error ? err.message : String(err),
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
}
|