@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
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ship operations workspace
|
|
2
|
+
* Ship operations workspace — the PURE composer behind the admin
|
|
3
3
|
* Publish tab's operations surface that sits ALONGSIDE the go-live core
|
|
4
4
|
* (AdminPublishTab.astro's change queue + accept/reject + dispatch status).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* into the same tab:
|
|
6
|
+
* This module composes the operations workspace ALONGSIDE the Publish-tab core
|
|
7
|
+
* (the change queue from GET /api/changes, Go-live/Reject, the live-version +
|
|
8
|
+
* dispatch-status panel, and the owner-only Domain section) — the three read/act
|
|
9
|
+
* surfaces woven into the same tab:
|
|
11
10
|
*
|
|
12
11
|
* 1. DIFF VIEW — for one pending candidate, the artifact-level diff of what
|
|
13
12
|
* changed (its rendered immutable version vs the current live version),
|
|
14
|
-
* tied to the exact tenant Gitea head SHA, with the
|
|
15
|
-
*
|
|
13
|
+
* tied to the exact tenant Gitea head SHA, with the immutable
|
|
14
|
+
* `/<tenant>/preview/rev/<sha>/` link and the evidence AUTHORITY
|
|
16
15
|
* (authoritative / stale / missing) — never a diff a reviewer would mistake
|
|
17
16
|
* for the head when a last-good version actually renders.
|
|
18
17
|
* 2. VERSIONS — the tenant's published-version / promotion history
|
|
19
|
-
* (`listPromotions`,
|
|
20
|
-
* Gitea SHA
|
|
18
|
+
* (`listPromotions`, promotion_status), each row carrying the tenant
|
|
19
|
+
* Gitea SHA, its promotion time, a
|
|
21
20
|
* `/rev/<sha>/` link, and — marking the current live one.
|
|
22
21
|
* 3. ROLLBACK — pick a prior version and roll back THROUGH the one orchestrator
|
|
23
|
-
* (`/api/changes/rollback` → `rollbackCandidateAtomic` /
|
|
22
|
+
* (`/api/changes/rollback` → `rollbackCandidateAtomic` /
|
|
24
23
|
* `staticRollbackOrchestrator`), rendering only the HONEST terminal state
|
|
25
24
|
* (`rolled_back` when VERIFIED; `publish_pending` in flight; `publish_failed`
|
|
26
25
|
* / `blocked` fail-closed) — never a false "rolled back".
|
|
27
26
|
*
|
|
28
27
|
* COMPOSES, never rebuilds:
|
|
29
|
-
* - the
|
|
30
|
-
* - the
|
|
28
|
+
* - the immutable link is `reviewDashboardRevisionHref` verbatim;
|
|
29
|
+
* - the authority is `candidateEvidenceAuthority` /
|
|
31
30
|
* `classifyCandidateForRender` verbatim;
|
|
32
31
|
* - the versions come from the SAME `KVVersionStore.listPromotions` the
|
|
33
32
|
* "one step back" rollback resolution reads;
|
|
@@ -38,22 +37,16 @@
|
|
|
38
37
|
* decision is unit-tested without a Worker/KV — mirroring the reviewDashboard.ts /
|
|
39
38
|
* candidatePreviewState.ts split.
|
|
40
39
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* (
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `/api/changes/rollback` accepts the `targetVersionId` alone, restores the pointer,
|
|
49
|
-
* and dispatches a CI job that resolves the digest from the archive by sourceSha
|
|
50
|
-
* (`rollback-s3.mjs --source-sha`) and re-publishes — reporting `publish_pending`
|
|
51
|
-
* until the re-publish is verified live (never a false "rolled back"). `rollbackMode`
|
|
52
|
-
* names the two paths: a `control-plane` tenant rolls back one-click via the pointer
|
|
53
|
-
* flip (instant); a `static-archive` tenant's rollback DISPATCHES via CI and
|
|
54
|
-
* completes asynchronously — the surface must say so, never imply an instant flip.
|
|
40
|
+
* `rollbackMode` names the path a rollback is honestly driven by: a `control-plane`
|
|
41
|
+
* (hosted) tenant and a `pointer` (static-publish) tenant BOTH roll back one-click
|
|
42
|
+
* via the instant KV `live` pointer flip — the Worker serves whatever bundle the
|
|
43
|
+
* pointer's digest names (from R2, for a static tenant), so restoring the pointer IS
|
|
44
|
+
* the rollback. The `publish_pending`/`publish_failed`/`blocked` honesty model from
|
|
45
|
+
* `interpretRollbackResponse` below applies identically in both modes — only the
|
|
46
|
+
* digest column (surfaced for a static tenant) differs.
|
|
55
47
|
*/
|
|
56
48
|
import { reviewDashboardRevisionHref } from "@/lib/preview/reviewDashboard";
|
|
49
|
+
import { assignVersionNumbers } from "@/lib/publish/versionNumber";
|
|
57
50
|
import {
|
|
58
51
|
candidateEvidenceAuthority,
|
|
59
52
|
classifyCandidateForRender,
|
|
@@ -64,11 +57,45 @@ import type { ReviewEnvironment, PromotionEvent } from "@tot/public-runtime";
|
|
|
64
57
|
|
|
65
58
|
// ── Versions / promotion history ───────────────────────────────────────────
|
|
66
59
|
|
|
67
|
-
/**
|
|
60
|
+
/** The plain-language "what shipped in this version" model (same shape the staged
|
|
61
|
+
* release summary uses — a name + a lead sentence + one highlight per included change). */
|
|
62
|
+
export interface VersionReleaseSummary {
|
|
63
|
+
/** The human-readable release name (see `deriveReleaseName`), or null. */
|
|
64
|
+
name: string | null;
|
|
65
|
+
lead: string;
|
|
66
|
+
highlights: { title: string; detail: string }[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** One change that shipped in a published version — its PR summary, for the
|
|
70
|
+
* version's openable "what shipped" list. Fields degrade to null/`PR #N` when
|
|
71
|
+
* candidate_summary didn't cover the PR (best-effort enrichment). */
|
|
72
|
+
export interface WorkspaceVersionChange {
|
|
73
|
+
changeId: string;
|
|
74
|
+
prNumber: number | null;
|
|
75
|
+
headSha: string;
|
|
76
|
+
title: string | null;
|
|
77
|
+
description: string | null;
|
|
78
|
+
fileCount: number | null;
|
|
79
|
+
linesAdded: number | null;
|
|
80
|
+
linesRemoved: number | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Per-version release enrichment, keyed by versionId (== the promoted aggregate sha). */
|
|
84
|
+
export interface VersionReleaseInfo {
|
|
85
|
+
releaseSummary: VersionReleaseSummary | null;
|
|
86
|
+
changes: WorkspaceVersionChange[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** One published version in the tenant's promotion history (promotion_status). */
|
|
68
90
|
export interface WorkspaceVersion {
|
|
69
|
-
/** The tenant Gitea SHA the version serves — the content identity
|
|
70
|
-
* (`gitea-only-tenant-content-authority`). */
|
|
91
|
+
/** The tenant Gitea SHA the version serves — the content identity. */
|
|
71
92
|
versionId: string;
|
|
93
|
+
/**
|
|
94
|
+
* The merchant-facing monotonic version number ("v42") — derived from first
|
|
95
|
+
* promotion order (see `versionNumber.ts`), shared with the "Live version"
|
|
96
|
+
* metric. The human label; the SHA is demoted to a technical disclosure.
|
|
97
|
+
*/
|
|
98
|
+
number: number | null;
|
|
72
99
|
/** How the version was published (a Gitea commit, or a content hash). */
|
|
73
100
|
source: "commit" | "hash" | null;
|
|
74
101
|
/** ISO time the version was PROMOTED onto `live` (the promotion event). */
|
|
@@ -84,30 +111,46 @@ export interface WorkspaceVersion {
|
|
|
84
111
|
* digest lives only in the S3 archive the Worker can't enumerate. Never fabricated.
|
|
85
112
|
*/
|
|
86
113
|
digest: string | null;
|
|
87
|
-
/** The
|
|
114
|
+
/** The immutable `/<tenant>/preview/rev/<sha>/` deep-link, or `null` without a SHA. */
|
|
88
115
|
revHref: string | null;
|
|
89
116
|
/** The version currently serving live (the one you never roll back TO). */
|
|
90
117
|
isCurrent: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* The plain-language "what shipped in this version" summary, composed from the
|
|
120
|
+
* release receipt's included PRs + their candidate_summary. `null` when no
|
|
121
|
+
* receipt links this version to a PR set, or none carried summary prose.
|
|
122
|
+
*/
|
|
123
|
+
releaseSummary: VersionReleaseSummary | null;
|
|
124
|
+
/**
|
|
125
|
+
* The PRs that shipped in this version (from its release receipt), each with its
|
|
126
|
+
* human summary — the version's openable "what shipped" list. Empty when no
|
|
127
|
+
* receipt links this version to a PR set.
|
|
128
|
+
*/
|
|
129
|
+
changes: WorkspaceVersionChange[];
|
|
130
|
+
/** How many changes shipped in this version, or `null` when unknown (no receipt). */
|
|
131
|
+
changeCount: number | null;
|
|
91
132
|
}
|
|
92
133
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
134
|
+
/**
|
|
135
|
+
* How a rollback is honestly driven for this tenant. `control-plane` (hosted) and
|
|
136
|
+
* `pointer` (static-publish) both roll back one-click via the instant KV `live`
|
|
137
|
+
* pointer flip; they render identical instant "Roll back" copy, differing only in
|
|
138
|
+
* whether the static digest column is shown.
|
|
139
|
+
*/
|
|
140
|
+
export type RollbackMode = "control-plane" | "pointer";
|
|
95
141
|
|
|
96
142
|
/** The versions-panel view model. */
|
|
97
143
|
export interface VersionsView {
|
|
98
144
|
/** The current live version + (static) the digest `live/` actually serves. */
|
|
99
|
-
current: { versionId: string | null; digest: string | null };
|
|
145
|
+
current: { versionId: string | null; number: number | null; digest: string | null };
|
|
100
146
|
/** Promotion history, newest first, current marked. */
|
|
101
147
|
versions: WorkspaceVersion[];
|
|
102
148
|
/** Whether the tenant publishes its public site to an external static target. */
|
|
103
149
|
staticPublish: boolean;
|
|
104
150
|
/**
|
|
105
|
-
* `control-plane`
|
|
106
|
-
* (instant pointer flip)
|
|
107
|
-
*
|
|
108
|
-
* `targetVersionId` alone and CI resolves the archive digest + re-publishes
|
|
109
|
-
* asynchronously (`publish_pending` until verified live) — the surface must say
|
|
110
|
-
* "completes via CI", never imply an instant flip.
|
|
151
|
+
* `control-plane` (hosted) and `pointer` (static-publish) both roll back
|
|
152
|
+
* one-click via `targetVersionId` (instant KV pointer flip) and render identical
|
|
153
|
+
* instant "Roll back" copy.
|
|
111
154
|
*/
|
|
112
155
|
rollbackMode: RollbackMode;
|
|
113
156
|
}
|
|
@@ -129,6 +172,12 @@ export interface BuildVersionsViewInput {
|
|
|
129
172
|
currentDigest: string | null;
|
|
130
173
|
/** Per-version manifest facts, keyed by versionId (absent ⇒ unknown facts). */
|
|
131
174
|
manifests: Map<string, VersionManifestFacts>;
|
|
175
|
+
/**
|
|
176
|
+
* Per-version release enrichment, keyed by versionId (== the promoted aggregate
|
|
177
|
+
* sha on the release receipt). Absent ⇒ a version with no linked PR set (an
|
|
178
|
+
* honest empty release summary + no changes list — never fabricated).
|
|
179
|
+
*/
|
|
180
|
+
releases?: Map<string, VersionReleaseInfo>;
|
|
132
181
|
staticPublish: boolean;
|
|
133
182
|
}
|
|
134
183
|
|
|
@@ -136,16 +185,31 @@ export interface BuildVersionsViewInput {
|
|
|
136
185
|
* Assemble the versions panel from the promotion history + per-version manifest
|
|
137
186
|
* facts + the current-live pointer. Pure. The current live row is marked; the
|
|
138
187
|
* digest is populated ONLY for the current live of a static tenant (never faked
|
|
139
|
-
* for priors). `revHref` is the
|
|
188
|
+
* for priors). `revHref` is the immutable link.
|
|
140
189
|
*/
|
|
141
190
|
export function buildVersionsView(input: BuildVersionsViewInput): VersionsView {
|
|
142
|
-
const {
|
|
191
|
+
const {
|
|
192
|
+
tenantId,
|
|
193
|
+
promotions,
|
|
194
|
+
currentVersionId,
|
|
195
|
+
currentDigest,
|
|
196
|
+
manifests,
|
|
197
|
+
releases,
|
|
198
|
+
staticPublish,
|
|
199
|
+
} = input;
|
|
200
|
+
|
|
201
|
+
// The merchant-facing "v42" counter, derived from first-promotion order — the
|
|
202
|
+
// SAME derivation `resolveLiveVersion` feeds the "Live version" metric, so the
|
|
203
|
+
// panel and the metric always agree on a version's number.
|
|
204
|
+
const numbers = assignVersionNumbers(promotions);
|
|
143
205
|
|
|
144
206
|
const versions: WorkspaceVersion[] = promotions.map((p) => {
|
|
145
207
|
const facts = manifests.get(p.versionId);
|
|
208
|
+
const release = releases?.get(p.versionId);
|
|
146
209
|
const isCurrent = currentVersionId != null && p.versionId === currentVersionId;
|
|
147
210
|
return {
|
|
148
211
|
versionId: p.versionId,
|
|
212
|
+
number: numbers.get(p.versionId) ?? null,
|
|
149
213
|
source: facts?.source ?? null,
|
|
150
214
|
promotedAt: p.at,
|
|
151
215
|
createdAt: facts?.createdAt ?? null,
|
|
@@ -154,14 +218,21 @@ export function buildVersionsView(input: BuildVersionsViewInput): VersionsView {
|
|
|
154
218
|
digest: isCurrent && staticPublish ? currentDigest : null,
|
|
155
219
|
revHref: reviewDashboardRevisionHref(tenantId, p.versionId),
|
|
156
220
|
isCurrent,
|
|
221
|
+
releaseSummary: release?.releaseSummary ?? null,
|
|
222
|
+
changes: release?.changes ?? [],
|
|
223
|
+
changeCount: release ? release.changes.length : null,
|
|
157
224
|
};
|
|
158
225
|
});
|
|
159
226
|
|
|
160
227
|
return {
|
|
161
|
-
current: {
|
|
228
|
+
current: {
|
|
229
|
+
versionId: currentVersionId,
|
|
230
|
+
number: currentVersionId != null ? numbers.get(currentVersionId) ?? null : null,
|
|
231
|
+
digest: staticPublish ? currentDigest : null,
|
|
232
|
+
},
|
|
162
233
|
versions,
|
|
163
234
|
staticPublish,
|
|
164
|
-
rollbackMode: staticPublish ? "
|
|
235
|
+
rollbackMode: staticPublish ? "pointer" : "control-plane",
|
|
165
236
|
};
|
|
166
237
|
}
|
|
167
238
|
|
|
@@ -182,13 +253,13 @@ export interface CandidateDiffView {
|
|
|
182
253
|
headSha: string | null;
|
|
183
254
|
/** The immutable version actually rendered + diffed — the reconcile ACTUAL. */
|
|
184
255
|
versionId: string | null;
|
|
185
|
-
/** The
|
|
256
|
+
/** The immutable link to the exact reviewed SHA, or `null` without a head SHA. */
|
|
186
257
|
revHref: string | null;
|
|
187
|
-
/**
|
|
258
|
+
/** Evidence authority: authoritative (tied to head) / stale (last-good) / missing. */
|
|
188
259
|
authority: CandidateEvidenceAuthority;
|
|
189
260
|
/** The artifact-level diff (candidate rendered version vs current live), sorted by path. */
|
|
190
261
|
files: DiffEntry[];
|
|
191
|
-
/** True when the candidate has a renderable version to diff (
|
|
262
|
+
/** True when the candidate has a renderable version to diff (the `ready` classification). */
|
|
192
263
|
renderable: boolean;
|
|
193
264
|
/** When NOT renderable, the honest reason (names why) — never a fake empty diff. */
|
|
194
265
|
blockedReason: string | null;
|
|
@@ -206,7 +277,7 @@ export interface BuildCandidateDiffInput {
|
|
|
206
277
|
/**
|
|
207
278
|
* Diff a candidate's rendered version against the current live version, at the
|
|
208
279
|
* artifact level (each path carries a content hash in its site-version manifest).
|
|
209
|
-
* Composes
|
|
280
|
+
* Composes `classifyCandidateForRender` (only a `ready` candidate has a
|
|
210
281
|
* version to diff — a blocked/queued one yields an HONEST `blockedReason`, never a
|
|
211
282
|
* fabricated empty diff) and `candidateEvidenceAuthority` (so a diff of a last-good
|
|
212
283
|
* version that LAGS the head is flagged `stale`, not passed off as the head).
|
|
@@ -289,10 +360,9 @@ export interface RollbackOutcome {
|
|
|
289
360
|
* for BOTH planes without the surface having to know which it hit:
|
|
290
361
|
* - a HOSTED (control-plane) rollback returns `{ rolledBack: true, toVersionId }`
|
|
291
362
|
* on success (the pointer flip IS the rollback) or `{ error, reason }` on refusal;
|
|
292
|
-
* - a STATIC rollback returns the orchestrator's honest `{ state, ... }
|
|
363
|
+
* - a STATIC rollback returns the orchestrator's honest `{ state, ... }`.
|
|
293
364
|
* The rule the surface depends on: `rolledBack` is true ONLY for a verified
|
|
294
|
-
* `rolled_back` — a `publish_pending` is NEVER rendered as rolled back
|
|
295
|
-
* (`storefront-atomic-accept-is-ship-orchestrator` / `static-publish-fails-closed`).
|
|
365
|
+
* `rolled_back` — a `publish_pending` is NEVER rendered as rolled back.
|
|
296
366
|
* Pure.
|
|
297
367
|
*/
|
|
298
368
|
export function interpretRollbackResponse(
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared "buffer-or-stream on `Accept`" plumbing for a long-running admin
|
|
3
|
+
* route. ONE emit-driven work function serves BOTH transports:
|
|
4
|
+
*
|
|
5
|
+
* - a non-streaming caller (the CLI, any `Accept: application/json` client) gets
|
|
6
|
+
* the buffered terminal JSON body + status the work resolved — `emit` is a no-op;
|
|
7
|
+
* - a streaming caller (`Accept: text/event-stream`, the admin Publish tab) gets
|
|
8
|
+
* each `emit(event, data)` as an SSE frame AS IT HAPPENS, then a terminal
|
|
9
|
+
* `result` event carrying the SAME payload the JSON body would have, then close.
|
|
10
|
+
*
|
|
11
|
+
* The work runs ONCE either way; the terminal payload + status are IDENTICAL. Only
|
|
12
|
+
* the delivery differs. This is the generalized form of the pattern
|
|
13
|
+
* `POST /api/preview/build` first shipped inline — extracted here so every
|
|
14
|
+
* long-running route (build / integrate / rebuild / ship) streams the same way
|
|
15
|
+
* without re-implementing the ReadableStream/encoder/close dance.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A buffered JSON Response — the non-streaming terminal body. Inlined (not imported
|
|
20
|
+
* from the change/grants controlplane) so this generic transport helper stays in the
|
|
21
|
+
* `lib/publish` render layer, respecting the renderer-no-controlplane boundary.
|
|
22
|
+
*/
|
|
23
|
+
function jsonResponse(body: unknown, status = 200): Response {
|
|
24
|
+
return new Response(JSON.stringify(body), {
|
|
25
|
+
status,
|
|
26
|
+
headers: { "content-type": "application/json", "cache-control": "no-store" },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Emit one SSE event. A no-op in buffered (JSON) mode. */
|
|
31
|
+
export type SseEmit = (event: string, data: Record<string, unknown>) => void;
|
|
32
|
+
|
|
33
|
+
/** The terminal outcome of the work: the HTTP status + the JSON/`result` payload. */
|
|
34
|
+
export interface StreamWorkResult {
|
|
35
|
+
status: number;
|
|
36
|
+
payload: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** True when the caller asked for an SSE stream (`Accept: text/event-stream`). */
|
|
40
|
+
export function wantsEventStream(request: Request): boolean {
|
|
41
|
+
return (request.headers.get("accept") || "").includes("text/event-stream");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run `work` and deliver its terminal payload as either a buffered JSON Response
|
|
46
|
+
* (default) or a streamed SSE Response (when the caller sent
|
|
47
|
+
* `Accept: text/event-stream`). In stream mode each `emit` becomes an
|
|
48
|
+
* `event: <name>\ndata: <json>\n\n` frame; the terminal payload is sent as a
|
|
49
|
+
* `result` event and the stream closes. `onError` maps an UNEXPECTED throw (the
|
|
50
|
+
* work is expected to be fail-safe and return its own error payload) to a terminal
|
|
51
|
+
* result — in JSON mode it becomes the response body+status, in SSE mode the
|
|
52
|
+
* `result` event.
|
|
53
|
+
*/
|
|
54
|
+
export async function respondBufferedOrStreamed(
|
|
55
|
+
request: Request,
|
|
56
|
+
work: (emit: SseEmit) => Promise<StreamWorkResult>,
|
|
57
|
+
opts?: { onError?: (error: unknown) => StreamWorkResult },
|
|
58
|
+
): Promise<Response> {
|
|
59
|
+
const onError =
|
|
60
|
+
opts?.onError ??
|
|
61
|
+
((error: unknown): StreamWorkResult => ({
|
|
62
|
+
status: 500,
|
|
63
|
+
payload: {
|
|
64
|
+
ok: false,
|
|
65
|
+
outcome: "failed",
|
|
66
|
+
errors: [`operation failed: ${error instanceof Error ? error.message : String(error)}`],
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
// Buffered JSON mode — the CLI / any non-streaming caller. `emit` is a no-op; the
|
|
71
|
+
// work's own status + payload are returned verbatim (same contract as before).
|
|
72
|
+
if (!wantsEventStream(request)) {
|
|
73
|
+
try {
|
|
74
|
+
const { status, payload } = await work(() => {});
|
|
75
|
+
return jsonResponse(payload, status);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const { status, payload } = onError(error);
|
|
78
|
+
return jsonResponse(payload, status);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// SSE mode — stream each phase as it happens, then a terminal `result` event
|
|
83
|
+
// carrying the SAME payload the JSON body would have, then close. Workers stream a
|
|
84
|
+
// ReadableStream body natively — no infra.
|
|
85
|
+
const encoder = new TextEncoder();
|
|
86
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
87
|
+
start(controller) {
|
|
88
|
+
const send: SseEmit = (event, data) => {
|
|
89
|
+
try {
|
|
90
|
+
controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
|
|
91
|
+
} catch {
|
|
92
|
+
/* controller already closed (client disconnected) — nothing to do */
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
void (async () => {
|
|
96
|
+
try {
|
|
97
|
+
const { payload } = await work(send);
|
|
98
|
+
send("result", payload);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
send("result", onError(error).payload);
|
|
101
|
+
} finally {
|
|
102
|
+
try {
|
|
103
|
+
controller.close();
|
|
104
|
+
} catch {
|
|
105
|
+
/* already closed */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
return new Response(stream, {
|
|
112
|
+
status: 200,
|
|
113
|
+
headers: {
|
|
114
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
115
|
+
"cache-control": "no-cache, no-transform",
|
|
116
|
+
"x-accel-buffering": "no",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated static-bundle STAGING upload — the Worker-side write surface that
|
|
3
|
+
* lets CI stage a materialized bundle's objects into the `STATIC_BUNDLES` R2
|
|
4
|
+
* binding WITHOUT a static R2 S3-compatible key pair (`R2_ACCESS_KEY_ID`/
|
|
5
|
+
* `R2_SECRET_ACCESS_KEY`). CI proves identity with the SAME OIDC→AWS→SSM→
|
|
6
|
+
* `private_key_jwt` chain it already uses to authenticate to MCP
|
|
7
|
+
* (`scripts/publish/ci-mcp-read-remote.mjs`): it mints a short-lived RS256
|
|
8
|
+
* `client_assertion` under its dedicated confidential client
|
|
9
|
+
* (`storefront-ci.tokenoftrust.com` / kid `storefront-ci-publish-1`, the key held
|
|
10
|
+
* only in SSM), audience-bound to THIS upload endpoint, and the Worker verifies
|
|
11
|
+
* the signature against that client's PUBLISHED public JWKS.
|
|
12
|
+
*
|
|
13
|
+
* This is a WRITE path to a production-serving bucket, so the auth boundary is
|
|
14
|
+
* deliberately as strong as the MCP leg:
|
|
15
|
+
* - RS256 signature over the CI client's JWKS (no shared static secret).
|
|
16
|
+
* - `iss == sub == <expected CI client id>` (RFC 7523 §3).
|
|
17
|
+
* - `aud == <this exact upload endpoint>` (RFC 8707 resource binding) — an
|
|
18
|
+
* assertion minted for MCP's token endpoint can never be replayed here, and
|
|
19
|
+
* vice versa.
|
|
20
|
+
* - `exp`/`iat` freshness (jose enforces `exp`; assertions live ~60s).
|
|
21
|
+
* - `jti` one-time-use replay rejection (KV-backed), mirroring MCP's replay
|
|
22
|
+
* store — so a captured assertion can't be replayed inside its TTL window.
|
|
23
|
+
* - Object key confined to `versions/<digest>/…`: a valid CI credential can
|
|
24
|
+
* only write immutable version archives, never the `live/` pointer or any
|
|
25
|
+
* other bucket key.
|
|
26
|
+
*
|
|
27
|
+
* The KEY SCHEME (`versions/<digest>/<rel>`) MUST byte-match the read side
|
|
28
|
+
* (`@tot/public-runtime` `staticBundleObjectKey`/`staticBundleManifestKey`) and
|
|
29
|
+
* the CI writer (`scripts/publish/lib/r2-publish.mjs` `r2ObjectKey`) — the same
|
|
30
|
+
* lockstep those two already keep. MANIFEST-LAST ordering (the manifest is the
|
|
31
|
+
* "fully staged" signal) is the CALLER's responsibility (CI writes it last, the
|
|
32
|
+
* same as `writeBundleToR2`); this surface writes exactly the one object it is
|
|
33
|
+
* handed per request.
|
|
34
|
+
*/
|
|
35
|
+
import { createLocalJWKSet, jwtVerify, type JSONWebKeySet, type JWTPayload } from "jose";
|
|
36
|
+
|
|
37
|
+
/** The only signature algorithm accepted (matches the CI minter). */
|
|
38
|
+
export const STAGE_BUNDLE_UPLOAD_ALG = "RS256";
|
|
39
|
+
|
|
40
|
+
/** The route this surface is mounted at — the canonical `aud`/resource identifier. */
|
|
41
|
+
export const STAGE_BUNDLE_UPLOAD_PATH = "/api/publish/stage-bundle";
|
|
42
|
+
|
|
43
|
+
/** Default CI confidential-client id (`iss == sub`), overridable per env. */
|
|
44
|
+
export const DEFAULT_STAGE_BUNDLE_UPLOAD_ISSUER = "storefront-ci.tokenoftrust.com";
|
|
45
|
+
|
|
46
|
+
/** How long a used `jti` is remembered — the assertion TTL (~60s) plus clock skew. */
|
|
47
|
+
export const STAGE_BUNDLE_JTI_TTL_SECONDS = 120;
|
|
48
|
+
|
|
49
|
+
/** Clock skew tolerated on `exp`/`iat`, in seconds. */
|
|
50
|
+
const CLOCK_TOLERANCE_SECONDS = 5;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The `versions/<digest>/<rel>` keys a CI credential is allowed to write. The
|
|
54
|
+
* digest is a lowercase hex tree digest; `rel` is a bundle-relative POSIX path
|
|
55
|
+
* with no traversal. Anything else (a bare key, `live/…`, an absolute path, a
|
|
56
|
+
* `..` segment) is refused with 400 — the write authority is bounded to
|
|
57
|
+
* immutable version archives regardless of a valid signature.
|
|
58
|
+
*/
|
|
59
|
+
const VERSION_KEY_RE = /^versions\/[a-f0-9]{8,}\/.+$/;
|
|
60
|
+
|
|
61
|
+
/** Minimal R2 surface this module needs — structural, so tests pass a fake. */
|
|
62
|
+
export interface StageBundleUploadBucket {
|
|
63
|
+
head(key: string): Promise<{ key: string } | null>;
|
|
64
|
+
put(
|
|
65
|
+
key: string,
|
|
66
|
+
value: ArrayBuffer | ArrayBufferView,
|
|
67
|
+
options?: { httpMetadata?: { contentType?: string } },
|
|
68
|
+
): Promise<unknown>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* One-time-use `jti` store. `checkAndRemember` returns `true` when the `jti` was
|
|
73
|
+
* unseen (and records it), `false` when it was already used (a replay). Backed by
|
|
74
|
+
* KV in the deployed Worker; omitted in dev/tests where no KV is bound (replay
|
|
75
|
+
* protection then relies on the ~60s `aud`-bound `exp` window alone).
|
|
76
|
+
*/
|
|
77
|
+
export interface JtiReplayStore {
|
|
78
|
+
checkAndRemember(jti: string, ttlSeconds: number): Promise<boolean>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface StageBundleUploadConfig {
|
|
82
|
+
/** The CI client's published PUBLIC JWKS (verifies the assertion signature). */
|
|
83
|
+
jwks: JSONWebKeySet;
|
|
84
|
+
/** Expected `iss == sub` (the CI confidential-client id). */
|
|
85
|
+
issuer: string;
|
|
86
|
+
/** Expected `aud` — this exact upload endpoint URL (RFC 8707 resource). */
|
|
87
|
+
audience: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A KV-namespace-shaped store (`get`/`put`) — the deployed jti replay backing. */
|
|
91
|
+
export interface JtiKvLike {
|
|
92
|
+
get(key: string): Promise<string | null>;
|
|
93
|
+
put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Build a KV-backed {@link JtiReplayStore}. Keyed under a dedicated prefix. */
|
|
97
|
+
export function kvJtiReplayStore(kv: JtiKvLike): JtiReplayStore {
|
|
98
|
+
return {
|
|
99
|
+
async checkAndRemember(jti, ttlSeconds) {
|
|
100
|
+
const key = `stage-bundle-jti:${jti}`;
|
|
101
|
+
if (await kv.get(key)) return false;
|
|
102
|
+
// KV's minimum expirationTtl is 60s; the TTL default already exceeds it.
|
|
103
|
+
await kv.put(key, "1", { expirationTtl: Math.max(60, ttlSeconds) });
|
|
104
|
+
return true;
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function json(status: number, body: Record<string, unknown>): Response {
|
|
110
|
+
return new Response(JSON.stringify(body), {
|
|
111
|
+
status,
|
|
112
|
+
headers: { "content-type": "application/json" },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Extract the bearer assertion from the Authorization header, or null. */
|
|
117
|
+
function bearer(request: Request): string | null {
|
|
118
|
+
const h = request.headers.get("authorization");
|
|
119
|
+
if (!h) return null;
|
|
120
|
+
const m = /^Bearer (.+)$/.exec(h.trim());
|
|
121
|
+
return m ? m[1]!.trim() : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Verify the CI `client_assertion`: RS256 signature over the configured JWKS,
|
|
126
|
+
* `iss`/`sub`/`aud` exact match, `exp` freshness, and one-time `jti`. Returns the
|
|
127
|
+
* verified payload, or a `Response` (401) to return verbatim on any failure — no
|
|
128
|
+
* failure detail beyond a stable machine reason is leaked.
|
|
129
|
+
*/
|
|
130
|
+
export async function verifyUploadAssertion(
|
|
131
|
+
token: string,
|
|
132
|
+
config: StageBundleUploadConfig,
|
|
133
|
+
jtiStore?: JtiReplayStore,
|
|
134
|
+
): Promise<{ payload: JWTPayload } | { reject: Response }> {
|
|
135
|
+
let payload: JWTPayload;
|
|
136
|
+
try {
|
|
137
|
+
const keys = createLocalJWKSet(config.jwks);
|
|
138
|
+
({ payload } = await jwtVerify(token, keys, {
|
|
139
|
+
algorithms: [STAGE_BUNDLE_UPLOAD_ALG],
|
|
140
|
+
issuer: config.issuer,
|
|
141
|
+
audience: config.audience,
|
|
142
|
+
clockTolerance: CLOCK_TOLERANCE_SECONDS,
|
|
143
|
+
}));
|
|
144
|
+
} catch {
|
|
145
|
+
// Signature, iss/aud mismatch, or expiry — one opaque 401 (never which).
|
|
146
|
+
return { reject: json(401, { error: "invalid_assertion" }) };
|
|
147
|
+
}
|
|
148
|
+
// RFC 7523 §3: sub must equal the client id (iss). jose verifies iss but not
|
|
149
|
+
// that sub matches it, so assert it explicitly.
|
|
150
|
+
if (payload.sub !== config.issuer) {
|
|
151
|
+
return { reject: json(401, { error: "invalid_assertion" }) };
|
|
152
|
+
}
|
|
153
|
+
const jti = typeof payload.jti === "string" ? payload.jti : "";
|
|
154
|
+
if (!jti) {
|
|
155
|
+
return { reject: json(401, { error: "invalid_assertion" }) };
|
|
156
|
+
}
|
|
157
|
+
if (jtiStore) {
|
|
158
|
+
const fresh = await jtiStore.checkAndRemember(jti, STAGE_BUNDLE_JTI_TTL_SECONDS);
|
|
159
|
+
if (!fresh) return { reject: json(401, { error: "assertion_replayed" }) };
|
|
160
|
+
}
|
|
161
|
+
return { payload };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** True iff `key` is a well-formed, traversal-free `versions/<digest>/<rel>` key. */
|
|
165
|
+
export function isSafeVersionKey(key: string): boolean {
|
|
166
|
+
if (!VERSION_KEY_RE.test(key)) return false;
|
|
167
|
+
return !key.split("/").some((seg) => seg === ".." || seg === "." || seg === "");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Handle one authenticated staging request against `bucket`:
|
|
172
|
+
* - `PUT ?key=versions/<digest>/<rel>` — write the request body as that object.
|
|
173
|
+
* - `HEAD ?key=versions/<digest>/<rel>` — 200 if present, 404 if not (the CI
|
|
174
|
+
* idempotency probe, mirroring `r2ObjectExists`).
|
|
175
|
+
* Both require a valid CI assertion; the key is confined to `versions/<digest>/`.
|
|
176
|
+
*/
|
|
177
|
+
export async function handleStageBundleUpload(opts: {
|
|
178
|
+
request: Request;
|
|
179
|
+
bucket: StageBundleUploadBucket;
|
|
180
|
+
config: StageBundleUploadConfig;
|
|
181
|
+
jtiStore?: JtiReplayStore;
|
|
182
|
+
}): Promise<Response> {
|
|
183
|
+
const { request, bucket, config, jtiStore } = opts;
|
|
184
|
+
const method = request.method.toUpperCase();
|
|
185
|
+
if (method !== "PUT" && method !== "HEAD") {
|
|
186
|
+
return json(405, { error: "method_not_allowed" });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const token = bearer(request);
|
|
190
|
+
if (!token) return json(401, { error: "missing_bearer" });
|
|
191
|
+
|
|
192
|
+
// HEAD is a read/probe and must NOT consume the one-time jti (a PUT retry after
|
|
193
|
+
// a probe would then falsely 401 as a replay). Only PUT records the jti.
|
|
194
|
+
const verified = await verifyUploadAssertion(
|
|
195
|
+
token,
|
|
196
|
+
config,
|
|
197
|
+
method === "PUT" ? jtiStore : undefined,
|
|
198
|
+
);
|
|
199
|
+
if ("reject" in verified) return verified.reject;
|
|
200
|
+
|
|
201
|
+
const key = new URL(request.url).searchParams.get("key") ?? "";
|
|
202
|
+
if (!isSafeVersionKey(key)) {
|
|
203
|
+
return json(400, { error: "invalid_key" });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (method === "HEAD") {
|
|
207
|
+
const obj = await bucket.head(key);
|
|
208
|
+
return new Response(null, { status: obj ? 200 : 404 });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const body = await request.arrayBuffer();
|
|
212
|
+
const contentType = request.headers.get("content-type") ?? undefined;
|
|
213
|
+
await bucket.put(key, body, contentType ? { httpMetadata: { contentType } } : undefined);
|
|
214
|
+
return json(200, { key, staged: true });
|
|
215
|
+
}
|