@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
|
@@ -55,7 +55,7 @@ function localTenantDirIds(): string[] {
|
|
|
55
55
|
return [...ids];
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Catalog fixtures committed on disk, keyed by data scope (== out/catalog dir
|
|
59
59
|
// name). A dev tenant whose scope has fixtures is served as a real COMMERCE tenant
|
|
60
60
|
// (see devTenantSeed); one without them degrades to a marketing home. Keys only —
|
|
61
61
|
// the same fixtures the FixtureToTClient reads. Repo root is four up from config/.
|
|
@@ -73,7 +73,7 @@ function catalogScopes(): Set<string> {
|
|
|
73
73
|
return scopes;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
//
|
|
76
|
+
// A slug tenant (dir name != its domain) declares its resolvable appDomain in
|
|
77
77
|
// the checkout's `.tot/config.json` `scope`. Read eagerly (tiny JSON) so synthesis
|
|
78
78
|
// can resolve it at `/<domain>/`; trusted only when it looks like a hostname (has a
|
|
79
79
|
// dot), mirroring scripts/tot-dev.mjs resolveDomain. Only the DEV fallback consults
|
|
@@ -110,8 +110,8 @@ function slugScopes(): Map<string, string> {
|
|
|
110
110
|
function devFallbackResolver(): TenantResolver {
|
|
111
111
|
if (!import.meta.env.DEV) return staticResolver;
|
|
112
112
|
const staticIds = new Set(staticTenants.map((t) => t.tenant_id));
|
|
113
|
-
const withCatalog = catalogScopes();
|
|
114
|
-
const scopes = slugScopes();
|
|
113
|
+
const withCatalog = catalogScopes();
|
|
114
|
+
const scopes = slugScopes();
|
|
115
115
|
const dev = devTenantsToSeed(localTenantDirIds(), staticIds, getTenantTheme, {
|
|
116
116
|
scopeFor: (id) => scopes.get(id),
|
|
117
117
|
hasCatalog: (scope) => withCatalog.has(scope),
|
|
@@ -23,6 +23,9 @@ import { referenceTheme } from "@/themes/reference";
|
|
|
23
23
|
import { mergeTheme } from "@/themes/schema";
|
|
24
24
|
import { organizationLd, websiteLd } from "@/lib/jsonld";
|
|
25
25
|
import { nonceInlineTags, CHROME_BODY_MARKER } from "@/lib/rawMarketingHtml";
|
|
26
|
+
import { resolveHeaderVariant, resolveFooterVariant } from "@/lib/chrome/variant";
|
|
27
|
+
import type { HeaderVariant, FooterVariant } from "@/lib/chrome/model";
|
|
28
|
+
import { renderIntegrationPhases } from "@tot/public-runtime";
|
|
26
29
|
import { readViewerSession } from "@/lib/auth/route";
|
|
27
30
|
import AppWidgetFrame from "@/components/apps/AppWidgetFrame.astro";
|
|
28
31
|
import { getRegistryService } from "@/lib/apps/adminService.js";
|
|
@@ -36,15 +39,70 @@ interface Props {
|
|
|
36
39
|
ogImage?: string;
|
|
37
40
|
noindex?: boolean;
|
|
38
41
|
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
|
|
42
|
+
/** Alternate RSS/Atom feeds this page advertises (threaded to <Seo>). */
|
|
43
|
+
feeds?: { title: string; href: string }[];
|
|
39
44
|
/** Wider pages (PLP/home) drop the prose max-width. */
|
|
40
45
|
fullBleed?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Explicit per-page header/footer content-variant override (see
|
|
48
|
+
* lib/chrome/model.ts HeaderVariant/FooterVariant). NEVER inferred from the
|
|
49
|
+
* route/CSS/title — a page that wants e.g. a minimal checkout header sets
|
|
50
|
+
* this prop. Absent ⇒ the tenant's chrome-config default
|
|
51
|
+
* (chrome.header.variant / chrome.footer.variant). An unrecognized value
|
|
52
|
+
* (page-supplied or the config's own default) fails closed with an
|
|
53
|
+
* actionable error rather than silently falling back.
|
|
54
|
+
*/
|
|
55
|
+
headerVariant?: HeaderVariant;
|
|
56
|
+
footerVariant?: FooterVariant;
|
|
57
|
+
// --- Article SEO hardening: pass-through to <Seo> (all additive) ---
|
|
58
|
+
article?: import("@/lib/seo/meta.js").ArticleMeta;
|
|
59
|
+
imageAlt?: string;
|
|
60
|
+
imageWidth?: number;
|
|
61
|
+
imageHeight?: number;
|
|
62
|
+
twitterSite?: string;
|
|
63
|
+
twitterCreator?: string;
|
|
64
|
+
canonicalOverride?: string;
|
|
65
|
+
// --- hreflang / <html lang> (additive) ---
|
|
66
|
+
/** Per-variant `hreflang` alternates for a translated article; threaded to
|
|
67
|
+
* <Seo>. Absent/empty ⇒ no alternate links (singleton unchanged). */
|
|
68
|
+
alternates?: import("@/lib/seo/alternates.js").Alternate[];
|
|
69
|
+
/** Overrides `<html lang>` (e.g. a translated article's `inLanguage`). Absent ⇒
|
|
70
|
+
* the tenant-locale default, so non-translated pages are unchanged. */
|
|
71
|
+
lang?: string;
|
|
41
72
|
}
|
|
42
73
|
|
|
43
|
-
const {
|
|
44
|
-
|
|
74
|
+
const {
|
|
75
|
+
title,
|
|
76
|
+
description,
|
|
77
|
+
path,
|
|
78
|
+
ogType,
|
|
79
|
+
ogImage,
|
|
80
|
+
noindex,
|
|
81
|
+
jsonLd,
|
|
82
|
+
feeds,
|
|
83
|
+
article,
|
|
84
|
+
imageAlt,
|
|
85
|
+
imageWidth,
|
|
86
|
+
imageHeight,
|
|
87
|
+
twitterSite,
|
|
88
|
+
twitterCreator,
|
|
89
|
+
canonicalOverride,
|
|
90
|
+
alternates,
|
|
91
|
+
lang,
|
|
92
|
+
headerVariant,
|
|
93
|
+
footerVariant,
|
|
94
|
+
} = Astro.props;
|
|
45
95
|
const { tenant, themeStyle, canonicalBase, basePath, cspNonce, features, capabilities } =
|
|
46
96
|
Astro.locals;
|
|
47
97
|
|
|
98
|
+
// Fixed-phase integration engine (ext-kernel-04): ONE engine emits the
|
|
99
|
+
// renderer-owned phases (platform.environment, consent.defaults, head.early,
|
|
100
|
+
// body.start, document.deferred) into the head + body-start + body-deferred
|
|
101
|
+
// boundaries. `rawHtmlResponse` is the other mount of the SAME engine, so both
|
|
102
|
+
// documents carry byte-identical phase markup. The nonce is stamped per response
|
|
103
|
+
// from a placeholder by the middleware, never serialized (ADR 0018).
|
|
104
|
+
const integrationPhases = renderIntegrationPhases(Astro.locals.integrationPlan, cspNonce);
|
|
105
|
+
|
|
48
106
|
// Organization JSON-LD is emitted site-wide (brief §8); pages add their own
|
|
49
107
|
// graphs (Product, BreadcrumbList…) via the jsonLd prop.
|
|
50
108
|
const pageGraphs = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : [];
|
|
@@ -58,6 +116,12 @@ const theme = mergeTheme(referenceTheme, tenant.theme_tokens);
|
|
|
58
116
|
|
|
59
117
|
const chrome = await Astro.locals.content.getChrome();
|
|
60
118
|
|
|
119
|
+
// Explicit variant resolution (fail-closed): the page's override wins over
|
|
120
|
+
// the tenant's chrome-config default; an unrecognized value throws with an
|
|
121
|
+
// actionable message rather than silently falling back (lib/chrome/variant.ts).
|
|
122
|
+
const resolvedHeaderVariant = resolveHeaderVariant(headerVariant, chrome.header.variant);
|
|
123
|
+
const resolvedFooterVariant = resolveFooterVariant(footerVariant, chrome.footer.variant);
|
|
124
|
+
|
|
61
125
|
// SHARED CHROME: if the tenant ships content/<id>/chrome.html (the pixel-perfect
|
|
62
126
|
// header/footer the raw marketing pages use), render THAT chrome around the
|
|
63
127
|
// Layout content too — so commerce pages (collections/products/search/…) share
|
|
@@ -65,7 +129,7 @@ const chrome = await Astro.locals.content.getChrome();
|
|
|
65
129
|
// <!--PAGE_BODY-->: everything before = header, after = footer; pull its tenant
|
|
66
130
|
// stylesheet links into <head>; nonce its inline tags for CSP. Tenants without a
|
|
67
131
|
// chrome.html keep the token-driven Header/Footer (below).
|
|
68
|
-
const chromeHtml = await Astro.locals.content.getChromeHtml();
|
|
132
|
+
const chromeHtml = await Astro.locals.content.getChromeHtml(path);
|
|
69
133
|
let sharedChrome: { header: string; footer: string; css: string[] } | null = null;
|
|
70
134
|
if (chromeHtml && chromeHtml.includes(CHROME_BODY_MARKER)) {
|
|
71
135
|
const headEnd = chromeHtml.indexOf("</head>");
|
|
@@ -85,7 +149,7 @@ if (chromeHtml && chromeHtml.includes(CHROME_BODY_MARKER)) {
|
|
|
85
149
|
|
|
86
150
|
const compliance = tenant.compliance;
|
|
87
151
|
|
|
88
|
-
// Site-chrome data resolution (
|
|
152
|
+
// Site-chrome data resolution (chrome/* is the canonical, prop-driven
|
|
89
153
|
// component set; this Layout is the ONE place that resolves session/feature/
|
|
90
154
|
// theme state into their props. Skipped entirely when sharedChrome (a raw
|
|
91
155
|
// content/<id>/chrome.html override) is present — that path wins outright.
|
|
@@ -119,25 +183,34 @@ if (features.catalog) {
|
|
|
119
183
|
// white logo); a light header always shows the text wordmark (no logo image).
|
|
120
184
|
const headerDark = theme.brand.headerVariant === "dark";
|
|
121
185
|
const headerLogoSrc = headerDark ? theme.brand.logoLight : theme.brand.logoDark;
|
|
122
|
-
const
|
|
186
|
+
const themeHeaderLogo =
|
|
123
187
|
headerDark && headerLogoSrc && /^(https?:|\/)/.test(headerLogoSrc)
|
|
124
188
|
? { src: headerLogoSrc, alt: tenant.displayName }
|
|
125
189
|
: undefined;
|
|
126
190
|
|
|
191
|
+
// chrome.header.brand (lib/chrome/model.ts ChromeBrand) is the governed,
|
|
192
|
+
// schema-validated brand identity — SOURCE OF TRUTH per field when set,
|
|
193
|
+
// falling back to the theme-derived values above only where a field is
|
|
194
|
+
// absent, so a tenant that hasn't authored a brand config is unaffected.
|
|
195
|
+
const brand = chrome.header.brand;
|
|
196
|
+
const brandLogoValid = brand?.logo && /^(https?:|\/)/.test(brand.logo.src) ? brand.logo : undefined;
|
|
197
|
+
const headerLogo = brandLogoValid ?? themeHeaderLogo;
|
|
198
|
+
const headerWordmark = brand?.label ?? tenant.displayName;
|
|
199
|
+
const headerBrandHref = brand?.href;
|
|
200
|
+
|
|
127
201
|
// Footer bottom bar: a "Legal"-titled column's links win; otherwise every
|
|
128
202
|
// column's links flatten into the policy-link row (mirrors the retired
|
|
129
203
|
// nav/Footer.astro exactly).
|
|
130
204
|
const legalCol = chrome.footer.columns.find((c) => /legal/i.test(c.title));
|
|
131
205
|
const bottomLinks = legalCol ? legalCol.links : chrome.footer.columns.flatMap((c) => c.links);
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
}`;
|
|
206
|
+
const legalBase = chrome.footer.bottom?.copyright ?? `© ${new Date().getFullYear()} ${tenant.displayName}`;
|
|
207
|
+
const legalLine = `${legalBase}${chrome.footer.bottom?.note ? ` · ${chrome.footer.bottom.note}` : ""}`;
|
|
135
208
|
|
|
136
209
|
const analyticsToken = import.meta.env.PUBLIC_CF_ANALYTICS_TOKEN;
|
|
137
210
|
const analyticsEnabled =
|
|
138
211
|
analyticsToken && !/REPLACE|PLACEHOLDER/i.test(analyticsToken);
|
|
139
212
|
|
|
140
|
-
// Private-app widgets
|
|
213
|
+
// Private-app widgets — global.footer slot. Resolved once here so it
|
|
141
214
|
// renders for BOTH footer paths below (the tenant-owned chrome.html splice
|
|
142
215
|
// and the generic <Footer>) instead of being owned by either. No dark-mode
|
|
143
216
|
// preference infra exists yet — "light" is the only theme hint until it does.
|
|
@@ -156,10 +229,18 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
156
229
|
---
|
|
157
230
|
|
|
158
231
|
<!doctype html>
|
|
159
|
-
<html lang={tenant.locale.split("-")[0] ?? "en"} data-base-path={basePath || undefined}>
|
|
232
|
+
<html lang={lang ?? tenant.locale.split("-")[0] ?? "en"} data-base-path={basePath || undefined}>
|
|
160
233
|
<head>
|
|
161
234
|
<meta charset="utf-8" />
|
|
162
235
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
236
|
+
|
|
237
|
+
{/* Renderer-owned head phases (platform.environment → consent.defaults →
|
|
238
|
+
head.early), emitted before any tenant/app code — the Layout half of "one
|
|
239
|
+
engine, two mounts" (rawHtmlResponse is the other). Same engine output, so
|
|
240
|
+
both mounts are byte-identical. Carries only public values; the nonce is a
|
|
241
|
+
script attribute stamped per response, never serialized (ADR 0018). */}
|
|
242
|
+
<Fragment set:html={integrationPhases.head} />
|
|
243
|
+
|
|
163
244
|
<link rel="icon" type="image/svg+xml" href={theme.brand.favicon} />
|
|
164
245
|
|
|
165
246
|
{/* Per-tenant theme tokens injected at the edge (brief §7). The id is the
|
|
@@ -185,6 +266,15 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
185
266
|
ogImage={ogImage}
|
|
186
267
|
noindex={noindex}
|
|
187
268
|
jsonLd={allJsonLd}
|
|
269
|
+
feeds={feeds}
|
|
270
|
+
article={article}
|
|
271
|
+
imageAlt={imageAlt}
|
|
272
|
+
imageWidth={imageWidth}
|
|
273
|
+
imageHeight={imageHeight}
|
|
274
|
+
twitterSite={twitterSite}
|
|
275
|
+
twitterCreator={twitterCreator}
|
|
276
|
+
canonicalOverride={canonicalOverride}
|
|
277
|
+
alternates={alternates}
|
|
188
278
|
/>
|
|
189
279
|
|
|
190
280
|
{
|
|
@@ -198,6 +288,10 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
198
288
|
}
|
|
199
289
|
</head>
|
|
200
290
|
<body>
|
|
291
|
+
{/* body.start fallback boundary (ext-kernel-04) — the renderer-owned mount
|
|
292
|
+
point for body-start contributions, before any page content. */}
|
|
293
|
+
<Fragment set:html={integrationPhases.bodyStart} />
|
|
294
|
+
|
|
201
295
|
<a
|
|
202
296
|
href="#main"
|
|
203
297
|
class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:m-3 focus:rounded-md focus:bg-[var(--color-primary)] focus:px-4 focus:py-2 focus:text-[var(--color-primary-contrast)]"
|
|
@@ -214,16 +308,20 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
214
308
|
) : (
|
|
215
309
|
<>
|
|
216
310
|
<div class="sticky top-0 z-40">
|
|
217
|
-
{chrome.announcement &&
|
|
311
|
+
{chrome.header.announcement?.messages[0] && (
|
|
312
|
+
<AnnouncementBar text={chrome.header.announcement.messages[0]} variant="static" />
|
|
313
|
+
)}
|
|
218
314
|
<SiteHeader
|
|
219
315
|
logo={headerLogo}
|
|
220
|
-
wordmark={
|
|
221
|
-
|
|
316
|
+
wordmark={headerWordmark}
|
|
317
|
+
brandHref={headerBrandHref}
|
|
318
|
+
nav={chrome.header.nav}
|
|
222
319
|
variant={headerDark ? "dark" : "light"}
|
|
223
|
-
|
|
320
|
+
layout={resolvedHeaderVariant}
|
|
321
|
+
announcement={chrome.header.announcement?.messages[0]}
|
|
224
322
|
searchAction={features.productSearch ? "/search" : undefined}
|
|
225
323
|
savedItemsHref={features.savedItems ? "/saved" : undefined}
|
|
226
|
-
headerCtas={chrome.
|
|
324
|
+
headerCtas={chrome.header.ctas}
|
|
227
325
|
authLink={authLink}
|
|
228
326
|
sticky={false}
|
|
229
327
|
/>
|
|
@@ -244,8 +342,9 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
244
342
|
<SiteFooter
|
|
245
343
|
brand={{
|
|
246
344
|
wordmark: tenant.displayName,
|
|
247
|
-
contact: chrome.footer.
|
|
345
|
+
contact: chrome.footer.brand?.text ? [chrome.footer.brand.text] : undefined,
|
|
248
346
|
}}
|
|
347
|
+
variant={resolvedFooterVariant}
|
|
249
348
|
columns={chrome.footer.columns}
|
|
250
349
|
newsletter={features.newsletter ? { title: "Newsletter", placeholder: "you@example.com" } : undefined}
|
|
251
350
|
social={chrome.footer.social}
|
|
@@ -255,7 +354,7 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
255
354
|
</>
|
|
256
355
|
)}
|
|
257
356
|
|
|
258
|
-
{/* Private-app widgets (
|
|
357
|
+
{/* Private-app widgets (global.footer) — sandboxed per install; one
|
|
259
358
|
resolution above covers both footer paths. */}
|
|
260
359
|
{footerWidgets.map((w) => (
|
|
261
360
|
<div class="container-prose py-6">
|
|
@@ -357,6 +456,8 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
357
456
|
function fix(a) {
|
|
358
457
|
var href = a.getAttribute("href");
|
|
359
458
|
if (!href || href.charAt(0) !== "/" || href.charAt(1) === "/") return;
|
|
459
|
+
// Global asset namespaces are root-served under every tenant.
|
|
460
|
+
if (href.indexOf("/tenants/") === 0 || href.indexOf("/shared/") === 0) return;
|
|
360
461
|
if (href === base || href.indexOf(base + "/") === 0) return;
|
|
361
462
|
a.setAttribute("href", base + href);
|
|
362
463
|
}
|
|
@@ -381,7 +482,7 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
381
482
|
</script>
|
|
382
483
|
)}
|
|
383
484
|
|
|
384
|
-
{/* Dev-loop theme hot-patch
|
|
485
|
+
{/* Dev-loop theme hot-patch: tenant-hot-reload.mjs broadcasts a
|
|
385
486
|
`tot:theme-vars` HMR custom event on every theme.json save instead of
|
|
386
487
|
forcing a full reload; swap the CSS var block in place so edits repaint
|
|
387
488
|
instantly with no lost scroll position. import.meta.hot is undefined in
|
|
@@ -397,7 +498,7 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
397
498
|
</script>
|
|
398
499
|
)}
|
|
399
500
|
|
|
400
|
-
{/* Private-app widget postMessage bridge
|
|
501
|
+
{/* Private-app widget postMessage bridge — mounted unconditionally
|
|
401
502
|
(cheap no-op with zero tracked iframes); scans for
|
|
402
503
|
`[data-tot-widget-origin]` frames itself, so it doesn't need to know
|
|
403
504
|
which slots rendered above. */}
|
|
@@ -431,14 +532,38 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
431
532
|
}
|
|
432
533
|
</script>
|
|
433
534
|
|
|
434
|
-
{/* Field Web Vitals (
|
|
535
|
+
{/* Field Web Vitals (RUM) — measures LCP/INP/CLS for real visitors and
|
|
435
536
|
beacons per-route samples to the RUM sink. ~1KB, no framework; no-ops
|
|
436
537
|
where PerformanceObserver is absent. This is the per-route-template
|
|
437
|
-
signal
|
|
538
|
+
signal the `core-web-vitals` monitor consumes (the CF Web Analytics
|
|
438
539
|
beacon above only gives aggregate field CWV). */}
|
|
439
540
|
<script>
|
|
440
541
|
import { startWebVitals } from "@/lib/analytics/webVitals";
|
|
441
542
|
startWebVitals();
|
|
442
543
|
</script>
|
|
544
|
+
|
|
545
|
+
{/* Protected-surface tamper sentinel (ext-policy-09, D-R7) — a DETECTIVE
|
|
546
|
+
runtime control. Full-parent third-party code shares this document and its
|
|
547
|
+
DOM reach can't be enforceably narrowed, so this never prevents mutation: it
|
|
548
|
+
detects tampering of the platform-owned compliance/commerce surfaces
|
|
549
|
+
(policy-04 PROTECTED_SURFACES), restores safe state, evidences it, and
|
|
550
|
+
suspends the attributable integration. No-ops when no integration plan is
|
|
551
|
+
present. */}
|
|
552
|
+
<script>
|
|
553
|
+
import { bootTamperSentinel } from "@/lib/compliance/tamper-sentinel-boot";
|
|
554
|
+
bootTamperSentinel();
|
|
555
|
+
</script>
|
|
556
|
+
|
|
557
|
+
{/* document.deferred global-entry boundary (ext-kernel-04) — the last phase,
|
|
558
|
+
the renderer-owned mount point for the deferred integration entry. */}
|
|
559
|
+
<Fragment set:html={integrationPhases.bodyDeferred} />
|
|
560
|
+
|
|
561
|
+
{/* document.deferred lifecycle boot (ext-kernel-05) — initializes the plan's
|
|
562
|
+
integrations exactly once per document, wires the reserved completed-
|
|
563
|
+
navigation event, and disposes on teardown; a no-op for an empty plan. */}
|
|
564
|
+
<script>
|
|
565
|
+
import { bootIntegrationLifecycleFromDocument } from "@tot/public-runtime";
|
|
566
|
+
bootIntegrationLifecycleFromDocument();
|
|
567
|
+
</script>
|
|
443
568
|
</body>
|
|
444
569
|
</html>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Operational-activity ALERT conditions
|
|
3
|
-
* the
|
|
2
|
+
* Operational-activity ALERT conditions — the detection layer on top of
|
|
3
|
+
* the activity query surface.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* conditions in that stream into ALERTS. It
|
|
7
|
-
* this repo's
|
|
5
|
+
* The query surface makes operational activity queryable; this turns four specific,
|
|
6
|
+
* high-value conditions in that stream into ALERTS. It follows the same shape as
|
|
7
|
+
* this repo's other alert contract, `analytics/rumAlert.ts`: a set of
|
|
8
8
|
* PURE, transport-agnostic detectors that take a window of activity and return a
|
|
9
|
-
* structured `ActivityAlert[]`. WHERE the alerts go (a log line, a
|
|
9
|
+
* structured `ActivityAlert[]`. WHERE the alerts go (a log line, a
|
|
10
10
|
* PagerDuty/Slack sink, a polled endpoint) is the sink's job — there is NO outbound
|
|
11
|
-
* notification transport in this repo
|
|
12
|
-
* so
|
|
11
|
+
* notification transport in this repo (rumAlert is likewise detection-only),
|
|
12
|
+
* so this layer does not invent one. It also does NOT add a cron: the checker is on-demand
|
|
13
13
|
* (computed when `/admin/ops-timeline` loads, or when the read-only alerts endpoint
|
|
14
|
-
* is polled)
|
|
14
|
+
* is polled).
|
|
15
15
|
*
|
|
16
16
|
* THE FOUR CONDITIONS (and how each avoids false-positive noise):
|
|
17
17
|
*
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* still legitimately in flight (received < grace ago) never fires; a terminal
|
|
38
38
|
* after the received (any status) clears it.
|
|
39
39
|
*
|
|
40
|
-
* 4. deploy-skew — promotes
|
|
40
|
+
* 4. deploy-skew — promotes the visibility-only `deriveDeploySkew()` into an
|
|
41
41
|
* alert. `skewDetected` is true on EVERY normal rollout (two isolates coexist
|
|
42
42
|
* briefly), so alerting on it directly would page on every deploy. Instead this
|
|
43
43
|
* fires ONLY when the build serving this request differs from the newest
|
|
@@ -45,12 +45,10 @@
|
|
|
45
45
|
* period (default 15m) — i.e. THIS isolate failed to roll to a deploy that
|
|
46
46
|
* finished long ago (a persistent stale isolate), not a rollout in progress.
|
|
47
47
|
*
|
|
48
|
-
* DATA DEPENDENCY
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* logs-only; `deployVersion.ts` notes the swap to a timeline-fanning emitter is
|
|
53
|
-
* pending) — NOT D7's job. `preview.serve` stays `analytics`-sink by design (too
|
|
48
|
+
* DATA DEPENDENCY. The detectors read the D1 `activity_events` timeline through the
|
|
49
|
+
* `queryActivity` seam, so they alert on whatever is in that timeline. Getting
|
|
50
|
+
* server-emitted lifecycle events into the timeline is the fan-out wiring's job, not
|
|
51
|
+
* this layer's. `preview.serve` stays `analytics`-sink by design (too
|
|
54
52
|
* high-volume to timeline wholesale); detector 2 is source-agnostic (the rumAlert
|
|
55
53
|
* precedent) and fires against an Analytics-Engine-sourced or seeded feed. See
|
|
56
54
|
* `ALERT_CONDITIONS` for the per-condition source + wiring note.
|
|
@@ -63,10 +61,10 @@ import {
|
|
|
63
61
|
} from "./query.js";
|
|
64
62
|
import type { Queryable } from "../d1/catalog.js";
|
|
65
63
|
|
|
66
|
-
/** Severity, mirroring
|
|
64
|
+
/** Severity, mirroring `rumAlert.AlertSeverity` / `manifest.Severity`. */
|
|
67
65
|
export type AlertSeverity = "page" | "critical" | "warning" | "info";
|
|
68
66
|
|
|
69
|
-
/** The four
|
|
67
|
+
/** The four conditions. Stable ids — a downstream sink/dashboard keys on them. */
|
|
70
68
|
export type ActivityAlertType =
|
|
71
69
|
| "silent-terminal"
|
|
72
70
|
| "recurring-preview-miss"
|
|
@@ -276,7 +274,7 @@ export interface DeploySkewAlertOptions {
|
|
|
276
274
|
}
|
|
277
275
|
|
|
278
276
|
/**
|
|
279
|
-
* Promote
|
|
277
|
+
* Promote the visibility-only `DeploySkewSummary` into an alert, WITHOUT firing on
|
|
280
278
|
* every normal rollout. Fires only when the build serving this request differs from
|
|
281
279
|
* the newest observed build AND that newest build has been latest for longer than
|
|
282
280
|
* `graceMs` — i.e. THIS isolate never rolled to a deploy that finished long ago.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { D1Like } from "@/lib/d1/catalog";
|
|
2
|
+
|
|
3
|
+
const DIAGNOSTIC_STUCK_AFTER_MS = 5 * 60 * 1000;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Atomically reserve one tenant/change across authorization, Gitea, and finalization.
|
|
7
|
+
* `expires_at` is an operator-visible stuck-work deadline, never an automatic takeover:
|
|
8
|
+
* a live but slow submit cannot lose ownership underneath its forge mutation.
|
|
9
|
+
*/
|
|
10
|
+
export async function acquireCandidateSubmitLease(
|
|
11
|
+
db: D1Like,
|
|
12
|
+
tenantId: string,
|
|
13
|
+
changeId: string,
|
|
14
|
+
authorizationId: string,
|
|
15
|
+
now = new Date(),
|
|
16
|
+
): Promise<boolean> {
|
|
17
|
+
const acquiredAt = now.toISOString();
|
|
18
|
+
const expiresAt = new Date(now.getTime() + DIAGNOSTIC_STUCK_AFTER_MS).toISOString();
|
|
19
|
+
const row = await db.prepare(`
|
|
20
|
+
INSERT INTO candidate_submit_leases
|
|
21
|
+
(tenant_id, change_id, authorization_id, status, acquired_at, expires_at)
|
|
22
|
+
VALUES (?, ?, ?, 'active', ?, ?)
|
|
23
|
+
ON CONFLICT(tenant_id, change_id) DO UPDATE SET
|
|
24
|
+
authorization_id = excluded.authorization_id,
|
|
25
|
+
status = 'active',
|
|
26
|
+
acquired_at = excluded.acquired_at,
|
|
27
|
+
expires_at = excluded.expires_at
|
|
28
|
+
WHERE candidate_submit_leases.status <> 'active'
|
|
29
|
+
OR candidate_submit_leases.authorization_id = excluded.authorization_id
|
|
30
|
+
RETURNING authorization_id
|
|
31
|
+
`).bind(tenantId, changeId, authorizationId, acquiredAt, expiresAt).first() as
|
|
32
|
+
{ authorization_id?: string } | null;
|
|
33
|
+
return row?.authorization_id === authorizationId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type CandidateSubmitLeaseState = "active" | "completed" | "missing";
|
|
37
|
+
|
|
38
|
+
/** Atomically prove ownership for the callback work. Active leases are never stolen. */
|
|
39
|
+
export async function renewCandidateSubmitLease(
|
|
40
|
+
db: D1Like,
|
|
41
|
+
tenantId: string,
|
|
42
|
+
changeId: string,
|
|
43
|
+
authorizationId: string,
|
|
44
|
+
now = new Date(),
|
|
45
|
+
): Promise<CandidateSubmitLeaseState> {
|
|
46
|
+
const current = await db.prepare(`
|
|
47
|
+
SELECT authorization_id, status FROM candidate_submit_leases
|
|
48
|
+
WHERE tenant_id = ? AND change_id = ?
|
|
49
|
+
`).bind(tenantId, changeId).first() as
|
|
50
|
+
{ authorization_id?: string; status?: string } | null;
|
|
51
|
+
if (current?.authorization_id !== authorizationId) return "missing";
|
|
52
|
+
if (current.status === "completed") return "completed";
|
|
53
|
+
const expiresAt = new Date(now.getTime() + DIAGNOSTIC_STUCK_AFTER_MS).toISOString();
|
|
54
|
+
const row = await db.prepare(`
|
|
55
|
+
UPDATE candidate_submit_leases SET expires_at = ?
|
|
56
|
+
WHERE tenant_id = ? AND change_id = ? AND authorization_id = ? AND status = 'active'
|
|
57
|
+
RETURNING authorization_id
|
|
58
|
+
`).bind(expiresAt, tenantId, changeId, authorizationId).first() as
|
|
59
|
+
{ authorization_id?: string } | null;
|
|
60
|
+
return row?.authorization_id === authorizationId ? "active" : "missing";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Mark the attempt immutable/idempotent while allowing a later submit to acquire. */
|
|
64
|
+
export async function completeCandidateSubmitLease(
|
|
65
|
+
db: D1Like,
|
|
66
|
+
tenantId: string,
|
|
67
|
+
changeId: string,
|
|
68
|
+
authorizationId: string,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
await db.prepare(`
|
|
71
|
+
UPDATE candidate_submit_leases SET status = 'completed'
|
|
72
|
+
WHERE tenant_id = ? AND change_id = ? AND authorization_id = ? AND status = 'active'
|
|
73
|
+
`).bind(tenantId, changeId, authorizationId).run();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Release only this attempt; it can never unlock a newer submit. */
|
|
77
|
+
export async function releaseCandidateSubmitLease(
|
|
78
|
+
db: D1Like,
|
|
79
|
+
tenantId: string,
|
|
80
|
+
changeId: string,
|
|
81
|
+
authorizationId: string,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
await db.prepare(`
|
|
84
|
+
DELETE FROM candidate_submit_leases
|
|
85
|
+
WHERE tenant_id = ? AND change_id = ? AND authorization_id = ? AND status = 'active'
|
|
86
|
+
`).bind(tenantId, changeId, authorizationId).run();
|
|
87
|
+
}
|
|
@@ -2,25 +2,36 @@
|
|
|
2
2
|
* Owner-facing "who did this" attribution for a candidate change or aggregate —
|
|
3
3
|
* DELIBERATELY separate from the anonymized activity spine. `ActivityActor.id`
|
|
4
4
|
* is a salted, non-reversible hash by design (see `recordActivity.ts`'s header:
|
|
5
|
-
* a raw PII id in the core zone would defeat the
|
|
5
|
+
* a raw PII id in the core zone would defeat the redaction contract), so it can never
|
|
6
6
|
* answer "who submitted/accepted/rejected/shipped this" for an owner reviewing
|
|
7
7
|
* `/admin#publish`. This persists the real email (never joined into the general
|
|
8
8
|
* `queryActivity()` surface, never cross-tenant) alongside the existing hashed
|
|
9
9
|
* event, so the two concerns stay separate: analytics stays anonymized, and
|
|
10
10
|
* this stays a narrow, owner-only-readable accountability record.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* email
|
|
14
|
-
*
|
|
15
|
-
* it through is a real follow-up, not done here; email is what every call site
|
|
16
|
-
* already has.
|
|
12
|
+
* MCP-submitted candidates carry the tot20-issued stable subject plus a verified
|
|
13
|
+
* email/display label. Storefront-local UI actions still use their authenticated
|
|
14
|
+
* session email until the same subject is threaded through `SessionRecord`.
|
|
17
15
|
*/
|
|
18
16
|
import { displayNameFromEmail } from "./recordActivity";
|
|
19
17
|
|
|
20
|
-
export type ChangeActorAction = "submit" | "reject" | "accept" | "ship" | "build";
|
|
18
|
+
export type ChangeActorAction = "submit" | "reject" | "resolve" | "accept" | "ship" | "build";
|
|
19
|
+
|
|
20
|
+
export interface ChangeActorIdentity {
|
|
21
|
+
kind: "human" | "service";
|
|
22
|
+
/** Immutable identity-provider subject for a person, or app subject for a service. */
|
|
23
|
+
subject: string;
|
|
24
|
+
/** Present only for a verified human identity. */
|
|
25
|
+
email: string | null;
|
|
26
|
+
/** Owner-facing label. */
|
|
27
|
+
name: string;
|
|
28
|
+
}
|
|
21
29
|
|
|
22
30
|
export interface ChangeActorRecord {
|
|
23
|
-
|
|
31
|
+
/** Older records may predate structured identity and omit these two fields. */
|
|
32
|
+
kind?: ChangeActorIdentity["kind"];
|
|
33
|
+
subject?: string;
|
|
34
|
+
email: string | null;
|
|
24
35
|
/** Best-effort display name — no real name is captured anywhere today, so
|
|
25
36
|
* this is always {@link displayNameFromEmail}'s local-part guess. Swap the
|
|
26
37
|
* source here (not the callers) if a real name ever becomes available. */
|
|
@@ -37,6 +48,14 @@ function attributionKey(tenantId: string, subjectId: string, action: ChangeActor
|
|
|
37
48
|
return `cust:change-actor:${tenantId}:${subjectId}:${action}`;
|
|
38
49
|
}
|
|
39
50
|
|
|
51
|
+
function submitAuthorizationKey(
|
|
52
|
+
tenantId: string,
|
|
53
|
+
changeId: string,
|
|
54
|
+
authorizationId: string,
|
|
55
|
+
): string {
|
|
56
|
+
return `cust:change-actor-authorization:${tenantId}:${changeId}:${authorizationId}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
/**
|
|
41
60
|
* Persist who took `action` on `subjectId` (a `changeId` for reject/accept/build,
|
|
42
61
|
* or the aggregate id for ship). Best-effort — never blocks or fails the
|
|
@@ -51,6 +70,8 @@ export async function recordChangeActor(
|
|
|
51
70
|
): Promise<void> {
|
|
52
71
|
try {
|
|
53
72
|
const record: ChangeActorRecord = {
|
|
73
|
+
kind: "human",
|
|
74
|
+
subject: `email:${email.trim().toLowerCase()}`,
|
|
54
75
|
email,
|
|
55
76
|
name: displayNameFromEmail(email),
|
|
56
77
|
at: new Date().toISOString(),
|
|
@@ -61,6 +82,65 @@ export async function recordChangeActor(
|
|
|
61
82
|
}
|
|
62
83
|
}
|
|
63
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Persist an actor as a required part of a privileged mutation's authorization
|
|
87
|
+
* boundary. Unlike {@link recordChangeActor}, storage failures propagate so the
|
|
88
|
+
* caller can fail closed before performing the mutation.
|
|
89
|
+
*/
|
|
90
|
+
export async function recordRequiredChangeActor(
|
|
91
|
+
kv: ChangeActorKv,
|
|
92
|
+
tenantId: string,
|
|
93
|
+
subjectId: string,
|
|
94
|
+
action: ChangeActorAction,
|
|
95
|
+
actor: ChangeActorIdentity,
|
|
96
|
+
at: string,
|
|
97
|
+
): Promise<void> {
|
|
98
|
+
const record: ChangeActorRecord = { ...actor, at };
|
|
99
|
+
await kv.put(attributionKey(tenantId, subjectId, action), JSON.stringify(record));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Stage an immutable, per-attempt submit authorization before the forge write.
|
|
104
|
+
* This deliberately does not replace the canonical `submit` actor: a failed
|
|
105
|
+
* Gitea update must never make an older PR look like it was submitted by the
|
|
106
|
+
* actor whose later attempt failed.
|
|
107
|
+
*/
|
|
108
|
+
export async function recordCandidateSubmitAuthorization(
|
|
109
|
+
kv: ChangeActorKv,
|
|
110
|
+
tenantId: string,
|
|
111
|
+
changeId: string,
|
|
112
|
+
authorizationId: string,
|
|
113
|
+
actor: ChangeActorIdentity,
|
|
114
|
+
authorizedAt: string,
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const record: ChangeActorRecord = { ...actor, at: authorizedAt };
|
|
117
|
+
await kv.put(
|
|
118
|
+
submitAuthorizationKey(tenantId, changeId, authorizationId),
|
|
119
|
+
JSON.stringify(record),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Read one exact pre-forge authorization. Missing/corrupt data fails closed. */
|
|
124
|
+
export async function readCandidateSubmitAuthorization(
|
|
125
|
+
kv: ChangeActorKv,
|
|
126
|
+
tenantId: string,
|
|
127
|
+
changeId: string,
|
|
128
|
+
authorizationId: string,
|
|
129
|
+
): Promise<ChangeActorRecord | null> {
|
|
130
|
+
try {
|
|
131
|
+
const raw = await kv.get(submitAuthorizationKey(tenantId, changeId, authorizationId));
|
|
132
|
+
return raw ? JSON.parse(raw) as ChangeActorRecord : null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Browser responses never need the immutable identity-provider subject. */
|
|
139
|
+
export function ownerVisibleChangeActor(record: ChangeActorRecord): ChangeActorRecord {
|
|
140
|
+
const { subject: _subject, ...visible } = record;
|
|
141
|
+
return visible;
|
|
142
|
+
}
|
|
143
|
+
|
|
64
144
|
/** Read every recorded action's attribution for one subject. Best-effort per
|
|
65
145
|
* action: a missing/corrupt record is simply absent from the result, never
|
|
66
146
|
* a thrown error. */
|
|
@@ -69,7 +149,7 @@ export async function readChangeActors(
|
|
|
69
149
|
tenantId: string,
|
|
70
150
|
subjectId: string,
|
|
71
151
|
): Promise<Partial<Record<ChangeActorAction, ChangeActorRecord>>> {
|
|
72
|
-
const actions: ChangeActorAction[] = ["submit", "reject", "accept", "ship", "build"];
|
|
152
|
+
const actions: ChangeActorAction[] = ["submit", "reject", "resolve", "accept", "ship", "build"];
|
|
73
153
|
const out: Partial<Record<ChangeActorAction, ChangeActorRecord>> = {};
|
|
74
154
|
await Promise.all(
|
|
75
155
|
actions.map(async (action) => {
|