@tokenoftrust/storefront-runner 2.0.3 → 2.0.5
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/content.d.ts +159 -0
- package/apps/storefront/.astro/integrations/_astrojs_cloudflare/cloudflare.d.ts +1 -0
- package/apps/storefront/.astro/types.d.ts +3 -0
- package/apps/storefront/migrations-apps/0011_dt_tracking_tenant_databases.sql +23 -0
- package/apps/storefront/node_modules/.bin/acorn +43 -0
- package/apps/storefront/node_modules/.bin/astro +43 -0
- package/apps/storefront/node_modules/.bin/astro-check +43 -0
- package/apps/storefront/node_modules/.bin/browserslist +43 -0
- package/apps/storefront/node_modules/.bin/cf-wrangler +43 -0
- package/apps/storefront/node_modules/.bin/depcruise +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-baseline +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-fmt +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-wrap-stream-in-html +43 -0
- package/apps/storefront/node_modules/.bin/dependency-cruise +43 -0
- package/apps/storefront/node_modules/.bin/dependency-cruiser +43 -0
- package/apps/storefront/node_modules/.bin/drizzle-kit +43 -0
- package/apps/storefront/node_modules/.bin/esbuild +43 -0
- package/apps/storefront/node_modules/.bin/jiti +43 -0
- package/apps/storefront/node_modules/.bin/playwright +43 -0
- package/apps/storefront/node_modules/.bin/playwright-core +43 -0
- package/apps/storefront/node_modules/.bin/prettier +43 -0
- package/apps/storefront/node_modules/.bin/rollup +43 -0
- package/apps/storefront/node_modules/.bin/tsc +43 -0
- package/apps/storefront/node_modules/.bin/tsserver +43 -0
- package/apps/storefront/node_modules/.bin/tsx +43 -0
- package/apps/storefront/node_modules/.bin/vite +43 -0
- package/apps/storefront/node_modules/.bin/vitest +43 -0
- package/apps/storefront/node_modules/.bin/workerd +43 -0
- package/apps/storefront/node_modules/.bin/wrangler +43 -0
- package/apps/storefront/node_modules/.bin/wrangler2 +43 -0
- package/apps/storefront/node_modules/.bin/yaml +43 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js +58 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js +741 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js +2549 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/_metadata.json +150 -0
- package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js +434 -0
- package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js +2705 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js +1535 -0
- package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js +48 -0
- package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js +42 -0
- package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/package.json +3 -0
- package/apps/storefront/node_modules/.vite/deps/preact.js +310 -0
- package/apps/storefront/node_modules/.vite/deps/preact.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat.js +420 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat_jsx-runtime.js +4 -0
- package/apps/storefront/node_modules/.vite/deps/preact_debug.js +236 -0
- package/apps/storefront/node_modules/.vite/deps/preact_debug.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_devtools.js +12 -0
- package/apps/storefront/node_modules/.vite/deps/preact_devtools.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_hooks.js +192 -0
- package/apps/storefront/node_modules/.vite/deps/preact_hooks.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-dev-runtime.js +3 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js +92 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js +59 -0
- package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/rolldown-runtime-BPOCksWG.js +24 -0
- package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js +189 -0
- package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js +13 -0
- package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js +458 -0
- package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js +1016 -0
- package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js +32 -0
- package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js +113 -0
- package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps_ssr/_metadata.json +8 -0
- package/apps/storefront/node_modules/.vite/deps_ssr/package.json +3 -0
- package/apps/storefront/src/components/chrome/MegaMenu.astro +11 -1
- package/apps/storefront/src/components/chrome/NavDropdown.astro +49 -7
- package/apps/storefront/src/components/chrome/SiteFooter.astro +29 -4
- package/apps/storefront/src/components/chrome/SiteHeader.astro +39 -6
- package/apps/storefront/src/components/chrome/UtilityNav.astro +13 -1
- package/apps/storefront/src/components/islands/MobileNav.tsx +42 -2
- package/apps/storefront/src/layouts/Layout.astro +65 -7
- package/apps/storefront/src/lib/apps/registryAccess.ts +45 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +4 -4
- package/apps/storefront/src/lib/chrome/ctaIdentity.ts +21 -0
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +22 -22
- package/apps/storefront/src/lib/chrome/trackingPlacements.ts +36 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +57 -0
- package/apps/storefront/src/lib/rawChrome.ts +73 -14
- package/apps/storefront/src/lib/rawMarketingHtml.ts +30 -0
- package/apps/storefront/src/lib/shared/capability.ts +68 -0
- package/apps/storefront/src/lib/shared/developerInvites.ts +1250 -0
- package/apps/storefront/src/lib/shared/invitePreflight.ts +123 -0
- package/apps/storefront/src/lib/storyblok/provider.ts +21 -5
- package/apps/storefront/src/lib/tracking/collectorAllowlist.ts +40 -0
- package/apps/storefront/src/lib/tracking/collectorIngest.ts +188 -0
- package/apps/storefront/src/lib/tracking/d1Control.ts +180 -0
- package/apps/storefront/src/lib/tracking/interactionCollector.ts +202 -0
- package/apps/storefront/src/lib/tracking/interactionHealth.ts +252 -0
- package/apps/storefront/src/lib/tracking/interactionSchema.ts +107 -0
- package/apps/storefront/src/lib/tracking/interactionStore.ts +0 -0
- package/apps/storefront/src/lib/tracking/pruneRollup.ts +260 -0
- package/apps/storefront/src/lib/tracking/publishSafetyNet.ts +162 -0
- package/apps/storefront/src/lib/tracking/tenantD1Registry.ts +214 -0
- package/apps/storefront/src/lib/tracking/tenantReadAccess.ts +138 -0
- package/apps/storefront/src/lib/tracking-writeback/rewriteTrackingIds.ts +175 -0
- package/apps/storefront/src/lib/tracking-writeback/writebackCandidate.ts +110 -0
- package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +38 -4
- package/apps/storefront/src/lib/webhooks/emit.ts +1 -1
- package/apps/storefront/src/lib/webhooks/getDispatcher.ts +1 -1
- package/apps/storefront/src/pages/api/admin/interaction-insights.ts +101 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +1 -1
- package/apps/storefront/src/pages/api/tracking/interaction.ts +123 -0
- package/apps/storefront/src/pages/index.astro +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +1 -1
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +33 -14
- package/apps/storefront/src/workers/pruneRollupWorker.ts +120 -0
- package/package.json +1 -1
- package/packages/public-runtime/node_modules/.bin/tsc +43 -0
- package/packages/public-runtime/node_modules/.bin/tsserver +43 -0
- package/packages/public-runtime/node_modules/.bin/vitest +43 -0
- package/packages/public-runtime/src/chrome.ts +18 -7
- package/packages/public-runtime/src/index.ts +10 -0
- package/packages/public-runtime/src/integration/index.ts +1 -0
- package/packages/public-runtime/src/integration/interaction-tracking-gate.ts +142 -0
- package/packages/public-runtime/src/interaction-tracking.ts +364 -0
- package/packages/public-runtime/src/tracking-id-derivation.ts +1008 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/tenant/validate.mjs +306 -1
- package/scripts/tot-dev.mjs +59 -3
- package/tenants/home/public/js/storefront.js +6 -87
- package/apps/storefront/src/lib/apps/adminService.ts +0 -116
- package/apps/storefront/src/lib/apps/adminSession.ts +0 -214
- package/apps/storefront/src/lib/apps/apiAuth.ts +0 -91
- package/apps/storefront/src/lib/apps/apiRoute.ts +0 -35
- package/apps/storefront/src/lib/apps/catalogMapper.ts +0 -39
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +0 -228
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +0 -447
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +0 -367
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +0 -315
- package/apps/storefront/src/lib/apps/healthAggregate.ts +0 -66
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +0 -614
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +0 -125
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +0 -563
- package/apps/storefront/src/lib/apps/integrationsTab.ts +0 -547
- package/apps/storefront/src/lib/apps/integrationsView.ts +0 -106
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +0 -599
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +0 -170
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +0 -121
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +0 -148
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +0 -16
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +0 -105
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +0 -177
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +0 -251
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +0 -241
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +0 -211
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +0 -409
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +0 -368
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +0 -188
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +0 -291
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +0 -95
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +0 -91
- package/apps/storefront/src/lib/content-edit/client.ts +0 -286
- package/apps/storefront/src/lib/content-edit/index.ts +0 -14
- package/apps/storefront/src/lib/content-edit/route.ts +0 -25
- package/apps/storefront/src/lib/content-edit/types.ts +0 -19
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +0 -164
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { ChromeLink, SocialLink } from "@/lib/chrome/model";
|
|
9
9
|
import { withBase } from "@/lib/basePath";
|
|
10
|
+
import { CHROME_PLACEMENT } from "@/lib/chrome/trackingPlacements";
|
|
11
|
+
import { trackingAttributes } from "@tot/public-runtime";
|
|
10
12
|
|
|
11
13
|
interface Props {
|
|
12
14
|
links?: ChromeLink[];
|
|
@@ -19,6 +21,15 @@ interface Props {
|
|
|
19
21
|
const { links = [], locale, currency, social = [], class: className } = Astro.props;
|
|
20
22
|
const { basePath } = Astro.locals;
|
|
21
23
|
const hrefOf = (l: ChromeLink) => (l.external ? l.href : withBase(basePath, l.href));
|
|
24
|
+
const linkStamp = (l: ChromeLink, index: number) =>
|
|
25
|
+
trackingAttributes({
|
|
26
|
+
el: l.id,
|
|
27
|
+
action: l.action,
|
|
28
|
+
tracking: l.tracking,
|
|
29
|
+
placement: CHROME_PLACEMENT.utilityNav,
|
|
30
|
+
scope: "utility-nav",
|
|
31
|
+
index,
|
|
32
|
+
});
|
|
22
33
|
---
|
|
23
34
|
|
|
24
35
|
<div
|
|
@@ -30,12 +41,13 @@ const hrefOf = (l: ChromeLink) => (l.external ? l.href : withBase(basePath, l.hr
|
|
|
30
41
|
<div class="container-prose flex h-9 items-center justify-between gap-4 text-[0.72rem]">
|
|
31
42
|
<ul class="flex items-center gap-5">
|
|
32
43
|
{
|
|
33
|
-
links.map((l) => (
|
|
44
|
+
links.map((l, i) => (
|
|
34
45
|
<li>
|
|
35
46
|
<a
|
|
36
47
|
href={hrefOf(l)}
|
|
37
48
|
class="transition-colors hover:text-primary"
|
|
38
49
|
{...(l.external ? { rel: "noopener noreferrer" } : {})}
|
|
50
|
+
{...linkStamp(l, i)}
|
|
39
51
|
>
|
|
40
52
|
{l.label}
|
|
41
53
|
</a>
|
|
@@ -12,14 +12,30 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
|
14
14
|
import { isCurrentNavHref } from "@/lib/chrome/navCurrent";
|
|
15
|
+
import { CHROME_PLACEMENT } from "@/lib/chrome/trackingPlacements";
|
|
16
|
+
import { trackingAttributes } from "@tot/public-runtime";
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* `action`/`tracking` are declared loosely here (not the strict
|
|
20
|
+
* `TrackingDeclaration` union `SiteHeader.astro` maps from) — a fresh object
|
|
21
|
+
* literal spreading a discriminated union's fields widens to two independent
|
|
22
|
+
* optional properties, so TS can no longer see "exactly one of the two" at
|
|
23
|
+
* the type level. `trackingAttributes()` is still the runtime oracle: it
|
|
24
|
+
* throws on an invalid combination regardless of what the type allows.
|
|
25
|
+
*/
|
|
16
26
|
interface NavChild {
|
|
27
|
+
id: string;
|
|
17
28
|
label: string;
|
|
18
29
|
href: string;
|
|
30
|
+
action?: string;
|
|
31
|
+
tracking?: "none";
|
|
19
32
|
}
|
|
20
33
|
interface NavItem {
|
|
34
|
+
id: string;
|
|
21
35
|
label: string;
|
|
22
36
|
href: string;
|
|
37
|
+
action?: string;
|
|
38
|
+
tracking?: "none";
|
|
23
39
|
children?: NavChild[];
|
|
24
40
|
}
|
|
25
41
|
|
|
@@ -212,7 +228,7 @@ export default function MobileNav({ nav, announcement, currentPath }: Props) {
|
|
|
212
228
|
|
|
213
229
|
<nav aria-label="Mobile" class="flex-1 px-2 py-4">
|
|
214
230
|
<ul class="space-y-1">
|
|
215
|
-
{nav.map((item) => (
|
|
231
|
+
{nav.map((item, i) => (
|
|
216
232
|
<li key={item.href}>
|
|
217
233
|
{item.children && item.children.length > 0 ? (
|
|
218
234
|
<div class="px-3 py-2">
|
|
@@ -222,11 +238,19 @@ export default function MobileNav({ nav, announcement, currentPath }: Props) {
|
|
|
222
238
|
currentPath && isCurrentNavHref(currentPath, item.href) ? "page" : undefined
|
|
223
239
|
}
|
|
224
240
|
class="block font-display text-base font-medium text-text hover:text-primary"
|
|
241
|
+
{...trackingAttributes({
|
|
242
|
+
el: item.id,
|
|
243
|
+
action: item.action,
|
|
244
|
+
tracking: item.tracking,
|
|
245
|
+
placement: CHROME_PLACEMENT.mobileMenu,
|
|
246
|
+
scope: "nav-mobile",
|
|
247
|
+
index: i,
|
|
248
|
+
})}
|
|
225
249
|
>
|
|
226
250
|
{item.label}
|
|
227
251
|
</a>
|
|
228
252
|
<ul class="mt-1 space-y-0.5 border-l border-border pl-3">
|
|
229
|
-
{item.children.map((child) => (
|
|
253
|
+
{item.children.map((child, ci) => (
|
|
230
254
|
<li key={child.href}>
|
|
231
255
|
<a
|
|
232
256
|
href={child.href}
|
|
@@ -234,6 +258,14 @@ export default function MobileNav({ nav, announcement, currentPath }: Props) {
|
|
|
234
258
|
currentPath && isCurrentNavHref(currentPath, child.href) ? "page" : undefined
|
|
235
259
|
}
|
|
236
260
|
class="block rounded px-2 py-1.5 font-body text-sm text-muted hover:bg-border/40 hover:text-text"
|
|
261
|
+
{...trackingAttributes({
|
|
262
|
+
el: child.id,
|
|
263
|
+
action: child.action,
|
|
264
|
+
tracking: child.tracking,
|
|
265
|
+
placement: CHROME_PLACEMENT.mobileMenu,
|
|
266
|
+
scope: `nav-mobile-mega:${item.id}`,
|
|
267
|
+
index: ci,
|
|
268
|
+
})}
|
|
237
269
|
>
|
|
238
270
|
{child.label}
|
|
239
271
|
</a>
|
|
@@ -248,6 +280,14 @@ export default function MobileNav({ nav, announcement, currentPath }: Props) {
|
|
|
248
280
|
currentPath && isCurrentNavHref(currentPath, item.href) ? "page" : undefined
|
|
249
281
|
}
|
|
250
282
|
class="block rounded px-3 py-2.5 font-display text-base font-medium text-text hover:bg-border/40 hover:text-primary"
|
|
283
|
+
{...trackingAttributes({
|
|
284
|
+
el: item.id,
|
|
285
|
+
action: item.action,
|
|
286
|
+
tracking: item.tracking,
|
|
287
|
+
placement: CHROME_PLACEMENT.mobileMenu,
|
|
288
|
+
scope: "nav-mobile",
|
|
289
|
+
index: i,
|
|
290
|
+
})}
|
|
251
291
|
>
|
|
252
292
|
{item.label}
|
|
253
293
|
</a>
|
|
@@ -23,12 +23,13 @@ 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 { reportTrackingChainHealth } from "@/lib/tracking/interactionHealth";
|
|
26
27
|
import { resolveHeaderVariant, resolveFooterVariant } from "@/lib/chrome/variant";
|
|
27
28
|
import type { HeaderVariant, FooterVariant } from "@/lib/chrome/model";
|
|
28
|
-
import { renderIntegrationPhases } from "@tot/public-runtime";
|
|
29
|
+
import { renderIntegrationPhases, INTERACTION_TRACKING_BOOT_JS } from "@tot/public-runtime";
|
|
29
30
|
import { readViewerSession } from "@/lib/auth/route";
|
|
30
31
|
import AppWidgetFrame from "@/components/apps/AppWidgetFrame.astro";
|
|
31
|
-
import { getRegistryService } from "@/lib/apps/
|
|
32
|
+
import { getRegistryService } from "@/lib/apps/registryAccess.js";
|
|
32
33
|
import { renderWidgetSlot } from "@/lib/apps/widgets/renderSlot.js";
|
|
33
34
|
|
|
34
35
|
interface Props {
|
|
@@ -130,6 +131,15 @@ const resolvedFooterVariant = resolveFooterVariant(footerVariant, chrome.footer.
|
|
|
130
131
|
// stylesheet links into <head>; nonce its inline tags for CSP. Tenants without a
|
|
131
132
|
// chrome.html keep the token-driven Header/Footer (below).
|
|
132
133
|
const chromeHtml = await Astro.locals.content.getChromeHtml(path);
|
|
134
|
+
if (chromeHtml) {
|
|
135
|
+
// HEALTH: this is the sharedChrome BYPASS path — a hand-authored chrome.html
|
|
136
|
+
// wins outright over the governed chrome components below, so nothing
|
|
137
|
+
// validated its tracking declarations and nothing stamped them. A wholly
|
|
138
|
+
// unstamped wrapper means no click on this page's chrome can ever be
|
|
139
|
+
// collected, and every other layer stays silent about it (absence is also how
|
|
140
|
+
// a legitimate opt-out looks). One structured line per surface, never a throw.
|
|
141
|
+
reportTrackingChainHealth(chromeHtml, { surface: `sharedChrome:${tenant.tenant_id}` });
|
|
142
|
+
}
|
|
133
143
|
let sharedChrome: { header: string; footer: string; css: string[] } | null = null;
|
|
134
144
|
if (chromeHtml && chromeHtml.includes(CHROME_BODY_MARKER)) {
|
|
135
145
|
const headEnd = chromeHtml.indexOf("</head>");
|
|
@@ -277,13 +287,44 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
277
287
|
alternates={alternates}
|
|
278
288
|
/>
|
|
279
289
|
|
|
290
|
+
{
|
|
291
|
+
/* Cloudflare Web Analytics is a non-essential measurement destination —
|
|
292
|
+
gate it on the SAME neutral consent broker every vendor loader reads
|
|
293
|
+
(ext-kernel-04 `consent.defaults` phase, published above as
|
|
294
|
+
`window.__TOT_INTEGRATION_CONSENT__`). The token rides a <meta> tag
|
|
295
|
+
(any script attribute, incl. define:vars, forces is:inline and drops
|
|
296
|
+
module processing — this script needs a real import) rather than a
|
|
297
|
+
page global; the script fail-closes like every other consumer of that
|
|
298
|
+
global: an absent/tampered snapshot denies, so an unconfigured CMP
|
|
299
|
+
means this never loads rather than loading ungated. */
|
|
300
|
+
analyticsEnabled && <meta name="cf-beacon-token" content={analyticsToken} />
|
|
301
|
+
}
|
|
280
302
|
{
|
|
281
303
|
analyticsEnabled && (
|
|
282
|
-
<script
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
304
|
+
<script>
|
|
305
|
+
import {
|
|
306
|
+
parseConsentSnapshot,
|
|
307
|
+
isPurposeEligible,
|
|
308
|
+
defaultConsentSnapshot,
|
|
309
|
+
INTEGRATION_CONSENT_GLOBAL,
|
|
310
|
+
} from "@tot/public-runtime";
|
|
311
|
+
|
|
312
|
+
const snapshot =
|
|
313
|
+
parseConsentSnapshot((window as unknown as Record<string, unknown>)[INTEGRATION_CONSENT_GLOBAL]) ??
|
|
314
|
+
defaultConsentSnapshot();
|
|
315
|
+
|
|
316
|
+
const token = document
|
|
317
|
+
.querySelector('meta[name="cf-beacon-token"]')
|
|
318
|
+
?.getAttribute("content");
|
|
319
|
+
|
|
320
|
+
if (token && isPurposeEligible(snapshot, "analytics")) {
|
|
321
|
+
const beacon = document.createElement("script");
|
|
322
|
+
beacon.defer = true;
|
|
323
|
+
beacon.src = "https://static.cloudflareinsights.com/beacon.min.js";
|
|
324
|
+
beacon.setAttribute("data-cf-beacon", JSON.stringify({ token }));
|
|
325
|
+
document.head.appendChild(beacon);
|
|
326
|
+
}
|
|
327
|
+
</script>
|
|
287
328
|
)
|
|
288
329
|
}
|
|
289
330
|
</head>
|
|
@@ -554,6 +595,23 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
554
595
|
bootTamperSentinel();
|
|
555
596
|
</script>
|
|
556
597
|
|
|
598
|
+
{/* Declarative interaction tracking (dt-contract-v1) — ONE delegated click
|
|
599
|
+
listener, installed once per document, that resolves the nearest
|
|
600
|
+
ancestor carrying `data-tot-el` and reports its declared
|
|
601
|
+
identity/action/placement (plus the data-tot-scope/data-tot-i instance
|
|
602
|
+
pair). It reads declared attributes ONLY: no textContent/innerText/alt/
|
|
603
|
+
value/placeholder, no form-field reads. Delivery, consent gating and
|
|
604
|
+
environment gating are NOT here — each interaction is dispatched as a
|
|
605
|
+
`tot:interaction` CustomEvent for the collector to consume.
|
|
606
|
+
|
|
607
|
+
This is the Astro-mount half of the install; `rawHtmlResponse()` is the
|
|
608
|
+
other (ADR 0015 one-engine-two-mounts). Both embed the SAME serialized
|
|
609
|
+
listener, and the install-once marker makes a double install a no-op —
|
|
610
|
+
which is what makes the raw chrome wrapper spliced into this document
|
|
611
|
+
(above) safe. Emitted as `is:inline` + the page nonce so it needs no
|
|
612
|
+
entry in lib/cspStaticHashes.ts. */}
|
|
613
|
+
<script is:inline nonce={cspNonce} set:html={INTERACTION_TRACKING_BOOT_JS}></script>
|
|
614
|
+
|
|
557
615
|
{/* document.deferred global-entry boundary (ext-kernel-04) — the last phase,
|
|
558
616
|
the renderer-owned mount point for the deferred integration entry. */}
|
|
559
617
|
<Fragment set:html={integrationPhases.bodyDeferred} />
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer-shared registry access — split out of `adminService.ts` (2026-09-02
|
|
3
|
+
* audit) because `getRegistryService`/`json` are needed by the actual page
|
|
4
|
+
* render path (Layout.astro, index.astro, products/[handle].astro resolve
|
|
5
|
+
* installed app widgets on every request) AND by the webhook dispatcher
|
|
6
|
+
* (`getDispatcher.ts`/`emit.ts`), while the REST of `adminService.ts`
|
|
7
|
+
* (`getManagedAnalyticsService`, `registryErrorResponse`, `parseInstallId`,
|
|
8
|
+
* `requireOwnedInstall`, `requireManageRole`) is reached only from the
|
|
9
|
+
* operator-only `/api/apps/admin/*` cluster. ESM loads a whole file's imports
|
|
10
|
+
* regardless of which export is used, so keeping both halves in one file meant
|
|
11
|
+
* every page render transitively pulled in the admin-only chain too (and made
|
|
12
|
+
* `adminService.ts` impossible to drop from the public storefront-runner
|
|
13
|
+
* without also breaking the renderer). This file is the renderer-safe half;
|
|
14
|
+
* `adminService.ts` re-exports `json` from here for its own admin-only helpers.
|
|
15
|
+
*/
|
|
16
|
+
import { AppRegistryService } from "./registryService.js";
|
|
17
|
+
import { D1CredentialStore } from "./credentials.js";
|
|
18
|
+
import { DEFAULT_APP_GATEWAY_KID, APP_GATEWAY_KID_ENV } from "./gatewayKeys.js";
|
|
19
|
+
import { fromD1 } from "../d1/catalog.js";
|
|
20
|
+
import { readD1, readEnv } from "../env.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* No-store JSON response — install/rotate bodies carry a one-time client
|
|
24
|
+
* secret and must never be edge- or browser-cached.
|
|
25
|
+
*/
|
|
26
|
+
export function json(body: unknown, status = 200): Response {
|
|
27
|
+
return new Response(JSON.stringify(body), {
|
|
28
|
+
status,
|
|
29
|
+
headers: { "content-type": "application/json", "cache-control": "no-store" },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build an `AppRegistryService` over the real `STOREFRONT_APPS_DB` D1
|
|
35
|
+
* binding, or return a 503 Response when that binding isn't configured in
|
|
36
|
+
* this runtime. Never leaks binding/secret detail in the response body.
|
|
37
|
+
*/
|
|
38
|
+
export async function getRegistryService(): Promise<AppRegistryService | Response> {
|
|
39
|
+
const d1 = await readD1("STOREFRONT_APPS_DB");
|
|
40
|
+
if (!d1) return json({ error: "app registry not configured" }, 503);
|
|
41
|
+
const db = fromD1(d1);
|
|
42
|
+
const activeKid = (await readEnv(APP_GATEWAY_KID_ENV)) ?? DEFAULT_APP_GATEWAY_KID;
|
|
43
|
+
const credentials = new D1CredentialStore(db, activeKid);
|
|
44
|
+
return new AppRegistryService(db, credentials);
|
|
45
|
+
}
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import type { SessionRecord } from "./session.js";
|
|
32
32
|
import { sessionHostCapability } from "./loginGate.js";
|
|
33
|
-
import { decideIsOwner } from "@/lib/change/route/route";
|
|
34
33
|
import {
|
|
34
|
+
decideIsOwner,
|
|
35
35
|
resolveShipCapabilities,
|
|
36
|
+
OWNER_CAPABILITIES,
|
|
37
|
+
type Capability,
|
|
36
38
|
type ShipCapabilities,
|
|
37
|
-
} from "@/lib/
|
|
38
|
-
import type { Capability } from "@/lib/change/types";
|
|
39
|
-
import { OWNER_CAPABILITIES } from "@/lib/grants/session";
|
|
39
|
+
} from "@/lib/shared/capability";
|
|
40
40
|
|
|
41
41
|
/** Which entry case a viewer is in, for chrome + the DoD test matrix. */
|
|
42
42
|
export type AdminEntryPrincipal =
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governed-CTA identity attribute — the ONE mapping from a `ChromeCta` config
|
|
3
|
+
* to the markup identity every renderer stamps onto its rendered anchor.
|
|
4
|
+
*
|
|
5
|
+
* Two render paths draw on the same governed `ChromeCta` config: `rawChrome.ts`
|
|
6
|
+
* (the RAW-page chrome wrapper) and `SiteHeader.astro` (the app-Layout header).
|
|
7
|
+
* Both must emit the identical identity attribute for the identical CTA, so a
|
|
8
|
+
* downstream consumer (tracking, an operator's own selectors) never has to care
|
|
9
|
+
* which surface rendered the page. Keeping the derivation in ONE function,
|
|
10
|
+
* imported by both, makes that a structural guarantee rather than a convention
|
|
11
|
+
* two files have to remember to keep in sync.
|
|
12
|
+
*/
|
|
13
|
+
import type { ChromeCta } from "./model.js";
|
|
14
|
+
|
|
15
|
+
/** The identity attribute name governed CTAs are stamped with. */
|
|
16
|
+
export const CTA_IDENTITY_ATTR = "data-cta-id";
|
|
17
|
+
|
|
18
|
+
/** The (name, value) pair a governed CTA's config resolves to for markup identity. */
|
|
19
|
+
export function ctaIdentityAttr(cta: ChromeCta): { name: string; value: string } {
|
|
20
|
+
return { name: CTA_IDENTITY_ATTR, value: cta.id };
|
|
21
|
+
}
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"nav": [
|
|
21
|
-
{ "id": "nav-platform", "label": "Platform", "href": "/#platform" },
|
|
22
|
-
{ "id": "nav-industries", "label": "Industries", "href": "/#industries" },
|
|
23
|
-
{ "id": "nav-resources", "label": "Resources", "href": "https://help.tokenoftrust.com", "external": true },
|
|
24
|
-
{ "id": "nav-pricing", "label": "Pricing", "href": "https://tokenoftrust.com/pricing/", "external": true },
|
|
25
|
-
{ "id": "nav-login", "label": "Login", "href": "https://app.tokenoftrust.com", "external": true }
|
|
21
|
+
{ "id": "nav-platform", "action": "nav-platform", "label": "Platform", "href": "/#platform" },
|
|
22
|
+
{ "id": "nav-industries", "action": "nav-industries", "label": "Industries", "href": "/#industries" },
|
|
23
|
+
{ "id": "nav-resources", "action": "nav-resources", "label": "Resources", "href": "https://help.tokenoftrust.com", "external": true },
|
|
24
|
+
{ "id": "nav-pricing", "action": "nav-pricing", "label": "Pricing", "href": "https://tokenoftrust.com/pricing/", "external": true },
|
|
25
|
+
{ "id": "nav-login", "action": "nav-login", "label": "Login", "href": "https://app.tokenoftrust.com", "external": true }
|
|
26
26
|
],
|
|
27
27
|
"ctas": [
|
|
28
|
-
{ "id": "cta-book-demo", "label": "Book Demo", "href": "https://tokenoftrust.com/contact/", "variant": "secondary" },
|
|
29
|
-
{ "id": "cta-start-free", "label": "Start For Free", "href": "https://app.tokenoftrust.com", "variant": "primary" }
|
|
28
|
+
{ "id": "cta-book-demo", "action": "cta-book-demo", "label": "Book Demo", "href": "https://tokenoftrust.com/contact/", "variant": "secondary" },
|
|
29
|
+
{ "id": "cta-start-free", "action": "cta-start-for-free", "label": "Start For Free", "href": "https://app.tokenoftrust.com", "variant": "primary" }
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
32
|
"footer": {
|
|
@@ -38,36 +38,36 @@
|
|
|
38
38
|
{
|
|
39
39
|
"title": "Platform",
|
|
40
40
|
"links": [
|
|
41
|
-
{ "id": "ftr-platform-idv", "label": "Identity Verification", "href": "/#platform" },
|
|
42
|
-
{ "id": "ftr-platform-age", "label": "Age Assurance", "href": "/#platform" },
|
|
43
|
-
{ "id": "ftr-platform-fraud", "label": "Fraud Prevention", "href": "/#platform" },
|
|
44
|
-
{ "id": "ftr-platform-evidence", "label": "Compliance Evidence", "href": "/#platform" },
|
|
45
|
-
{ "id": "ftr-platform-aml", "label": "AML · PACT Act · Tax", "href": "/#platform" }
|
|
41
|
+
{ "id": "ftr-platform-idv", "action": "nav-identity-verification", "label": "Identity Verification", "href": "/#platform" },
|
|
42
|
+
{ "id": "ftr-platform-age", "action": "nav-age-assurance", "label": "Age Assurance", "href": "/#platform" },
|
|
43
|
+
{ "id": "ftr-platform-fraud", "action": "nav-fraud-prevention", "label": "Fraud Prevention", "href": "/#platform" },
|
|
44
|
+
{ "id": "ftr-platform-evidence", "action": "nav-compliance-evidence", "label": "Compliance Evidence", "href": "/#platform" },
|
|
45
|
+
{ "id": "ftr-platform-aml", "action": "nav-aml-pact-act-tax", "label": "AML · PACT Act · Tax", "href": "/#platform" }
|
|
46
46
|
]
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
"title": "Industries",
|
|
50
50
|
"links": [
|
|
51
|
-
{ "id": "ftr-industries-alcohol", "label": "Alcohol", "href": "/#industries" },
|
|
52
|
-
{ "id": "ftr-industries-vape", "label": "Tobacco & Vape", "href": "/#industries" },
|
|
53
|
-
{ "id": "ftr-industries-age-restricted", "label": "Age-Restricted Goods", "href": "/#industries" },
|
|
54
|
-
{ "id": "ftr-industries-marketplaces", "label": "Marketplaces", "href": "/#industries" }
|
|
51
|
+
{ "id": "ftr-industries-alcohol", "action": "nav-alcohol", "label": "Alcohol", "href": "/#industries" },
|
|
52
|
+
{ "id": "ftr-industries-vape", "action": "nav-tobacco-and-vape", "label": "Tobacco & Vape", "href": "/#industries" },
|
|
53
|
+
{ "id": "ftr-industries-age-restricted", "action": "nav-age-restricted-goods", "label": "Age-Restricted Goods", "href": "/#industries" },
|
|
54
|
+
{ "id": "ftr-industries-marketplaces", "action": "nav-marketplaces", "label": "Marketplaces", "href": "/#industries" }
|
|
55
55
|
]
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"title": "Company",
|
|
59
59
|
"links": [
|
|
60
|
-
{ "id": "ftr-company-pricing", "label": "Pricing", "href": "https://tokenoftrust.com/pricing/", "external": true },
|
|
61
|
-
{ "id": "ftr-company-contact", "label": "Contact Sales", "href": "https://tokenoftrust.com/contact/", "external": true },
|
|
62
|
-
{ "id": "ftr-company-resources", "label": "Resources", "href": "https://help.tokenoftrust.com", "external": true },
|
|
63
|
-
{ "id": "ftr-company-login", "label": "Login", "href": "https://app.tokenoftrust.com", "external": true }
|
|
60
|
+
{ "id": "ftr-company-pricing", "action": "nav-pricing", "label": "Pricing", "href": "https://tokenoftrust.com/pricing/", "external": true },
|
|
61
|
+
{ "id": "ftr-company-contact", "action": "nav-contact-sales", "label": "Contact Sales", "href": "https://tokenoftrust.com/contact/", "external": true },
|
|
62
|
+
{ "id": "ftr-company-resources", "action": "nav-resources", "label": "Resources", "href": "https://help.tokenoftrust.com", "external": true },
|
|
63
|
+
{ "id": "ftr-company-login", "action": "nav-login", "label": "Login", "href": "https://app.tokenoftrust.com", "external": true }
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"title": "Legal",
|
|
68
68
|
"links": [
|
|
69
|
-
{ "id": "ftr-legal-privacy", "label": "Privacy Policy", "href": "https://tokenoftrust.com/privacy/", "external": true },
|
|
70
|
-
{ "id": "ftr-legal-terms", "label": "Terms", "href": "https://tokenoftrust.com/terms/", "external": true }
|
|
69
|
+
{ "id": "ftr-legal-privacy", "action": "nav-privacy-policy", "label": "Privacy Policy", "href": "https://tokenoftrust.com/privacy/", "external": true },
|
|
70
|
+
{ "id": "ftr-legal-terms", "action": "nav-terms", "label": "Terms", "href": "https://tokenoftrust.com/terms/", "external": true }
|
|
71
71
|
]
|
|
72
72
|
}
|
|
73
73
|
],
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical `data-tot-placement` values for governed chrome + navigation (V1
|
|
3
|
+
* pilot scope: chrome CTAs, primary nav, mega-menu children, utility nav,
|
|
4
|
+
* footer, mobile nav — see docs/declarative-interaction-tracking-contract.md
|
|
5
|
+
* and decision v1-scope-chrome-nav-pilot). ONE source of truth shared by both
|
|
6
|
+
* the component renderer (SiteHeader.astro / MegaMenu.astro / NavDropdown.astro
|
|
7
|
+
* / MobileNav.tsx / UtilityNav.astro / SiteFooter.astro) and the raw-string
|
|
8
|
+
* renderer (rawChrome.ts), so the same section can never emit a different
|
|
9
|
+
* placement literal on the two surfaces.
|
|
10
|
+
*
|
|
11
|
+
* Placement is always passed explicitly by whichever renderer knows where it
|
|
12
|
+
* is rendering (decision identity-action-placement-split-v2) — never inferred
|
|
13
|
+
* from DOM position, component nesting, or render mode.
|
|
14
|
+
*/
|
|
15
|
+
export const CHROME_PLACEMENT = {
|
|
16
|
+
/** Header-actions cluster CTAs (rawChrome's `.header-cta`, SiteHeader's `headerCtas`). */
|
|
17
|
+
header: "header",
|
|
18
|
+
/** Primary-nav top-level items — desktop bar (raw `.primary-nav`, MegaMenu/NavDropdown mode="bar"). */
|
|
19
|
+
primaryNav: "primary-nav",
|
|
20
|
+
/** Mega-menu / single-column dropdown children, on either surface. */
|
|
21
|
+
megaMenu: "mega-menu",
|
|
22
|
+
/** The thin utility bar above the header. */
|
|
23
|
+
utilityNav: "utility-nav",
|
|
24
|
+
/** Footer link columns. */
|
|
25
|
+
footer: "footer",
|
|
26
|
+
/**
|
|
27
|
+
* Footer bottom legal/policy link row — component surface only. The raw
|
|
28
|
+
* renderer's `ChromeFooter.bottom` carries text only (no links); its footer
|
|
29
|
+
* columns cover the same links under `footer` above.
|
|
30
|
+
*/
|
|
31
|
+
footerBottom: "footer-bottom",
|
|
32
|
+
/** The mobile nav drawer (MobileNav island) and its no-JS accordion fallback. */
|
|
33
|
+
mobileMenu: "mobile-menu",
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export type ChromePlacement = (typeof CHROME_PLACEMENT)[keyof typeof CHROME_PLACEMENT];
|
|
@@ -149,6 +149,63 @@ export const activityEvents = sqliteTable(
|
|
|
149
149
|
}),
|
|
150
150
|
);
|
|
151
151
|
|
|
152
|
+
// --- Per-tenant raw-interaction store directory ------------------------------
|
|
153
|
+
// The DIRECTORY of per-tenant D1 databases that hold raw declarative-tracking
|
|
154
|
+
// interaction rows — one database per tenant (`lib/tracking/interactionSchema.ts`
|
|
155
|
+
// is that database's schema). This table holds NO interaction data: only the
|
|
156
|
+
// `tenant_id → (database_name, database_uuid)` mapping the Worker needs to
|
|
157
|
+
// address a tenant's own database over the D1 REST API, since Worker bindings
|
|
158
|
+
// are static and cannot be created per tenant at runtime.
|
|
159
|
+
//
|
|
160
|
+
// It lives in the SHARED STOREFRONT_APPS_DB on purpose: a directory of tenants
|
|
161
|
+
// is inherently cross-tenant, and keeping it here means the isolation boundary
|
|
162
|
+
// stays exactly one hop — a lookup returns ONE tenant's database handle, and
|
|
163
|
+
// that database contains nothing else. `tenant_id` is the primary key, so one
|
|
164
|
+
// tenant can never map to two databases (and two tenants can never share one).
|
|
165
|
+
//
|
|
166
|
+
// This is the seam the follow-on units read:
|
|
167
|
+
// • `dt-tenant-isolation` — resolves the caller's authorized tenant to
|
|
168
|
+
// exactly one `database_uuid`; a merchant read
|
|
169
|
+
// that is not in their own row cannot be built.
|
|
170
|
+
// • `dt-prune-and-rollup` — ENUMERATES this table to sweep every tenant's
|
|
171
|
+
// 90-day window (`last_pruned_at` / `pruned_rows`
|
|
172
|
+
// are its bookkeeping), and uses `size_bytes` /
|
|
173
|
+
// `size_checked_at` to surface a tenant nearing
|
|
174
|
+
// D1's 10GB per-database ceiling.
|
|
175
|
+
export const trackingTenantDatabases = sqliteTable(
|
|
176
|
+
"tracking_tenant_databases",
|
|
177
|
+
{
|
|
178
|
+
tenantId: text("tenant_id").primaryKey(),
|
|
179
|
+
// The Cloudflare D1 database name, derived deterministically from the tenant
|
|
180
|
+
// id (`lib/tracking/tenantD1Registry.ts`) — recorded so an operator can find
|
|
181
|
+
// it in the dashboard without re-deriving.
|
|
182
|
+
databaseName: text("database_name").notNull(),
|
|
183
|
+
// The D1 UUID — what the REST data plane actually addresses.
|
|
184
|
+
databaseUuid: text("database_uuid").notNull(),
|
|
185
|
+
// Applied `INTERACTION_SCHEMA_VERSION`; a bump re-applies the DDL per tenant.
|
|
186
|
+
schemaVersion: integer("schema_version").notNull(),
|
|
187
|
+
createdAt: text("created_at").notNull(),
|
|
188
|
+
updatedAt: text("updated_at").notNull(),
|
|
189
|
+
// Bookkeeping for the scheduled prune (written by `dt-prune-and-rollup`).
|
|
190
|
+
lastPrunedAt: text("last_pruned_at"),
|
|
191
|
+
prunedRows: integer("pruned_rows").notNull().default(0),
|
|
192
|
+
// Last observed database size + when, for the 10GB-ceiling warning.
|
|
193
|
+
sizeBytes: integer("size_bytes"),
|
|
194
|
+
sizeCheckedAt: text("size_checked_at"),
|
|
195
|
+
},
|
|
196
|
+
(t) => ({
|
|
197
|
+
// Two tenants sharing one database would defeat the whole structural-
|
|
198
|
+
// isolation claim, so make it impossible at the schema level.
|
|
199
|
+
uqTrackingTenantDatabasesUuid: uniqueIndex("uq_tracking_tenant_databases_uuid").on(
|
|
200
|
+
t.databaseUuid,
|
|
201
|
+
),
|
|
202
|
+
// The prune sweep's work queue: oldest-pruned first.
|
|
203
|
+
idxTrackingTenantDatabasesPruned: index("idx_tracking_tenant_databases_pruned").on(
|
|
204
|
+
t.lastPrunedAt,
|
|
205
|
+
),
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
|
|
152
209
|
// --- Webhook event spine ----------------------------------------------------
|
|
153
210
|
// Immutable event log. `id` is the semantic event id we mint — distinct from
|
|
154
211
|
// any per-delivery wire envelope id (see `webhookDeliveries.deliveryId` below).
|