@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
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { CHROME_BODY_MARKER, CHROME_HEAD_MARKER } from "./rawMarketingHtml.js";
|
|
28
28
|
import { isCurrentNavHref } from "./chrome/navCurrent.js";
|
|
29
|
+
import { ctaIdentityAttr } from "./chrome/ctaIdentity.js";
|
|
30
|
+
import { CHROME_PLACEMENT } from "./chrome/trackingPlacements.js";
|
|
31
|
+
import { trackingAttributesHtml } from "@tot/public-runtime";
|
|
29
32
|
import type {
|
|
30
33
|
ChromeAgeGate,
|
|
31
34
|
ChromeAnnouncement,
|
|
@@ -165,12 +168,27 @@ function renderAnnouncement(a: ChromeAnnouncement): string {
|
|
|
165
168
|
|
|
166
169
|
function renderUtilityNav(u: ChromeUtilityNav): string {
|
|
167
170
|
const links = (u.links ?? [])
|
|
168
|
-
.map((l) =>
|
|
171
|
+
.map((l, i) => {
|
|
172
|
+
const stamp = trackingAttributesHtml({
|
|
173
|
+
el: l.id,
|
|
174
|
+
action: l.action,
|
|
175
|
+
tracking: l.tracking,
|
|
176
|
+
placement: CHROME_PLACEMENT.utilityNav,
|
|
177
|
+
scope: "utility-nav",
|
|
178
|
+
index: i,
|
|
179
|
+
});
|
|
180
|
+
return `<li><a href="${esc(l.href)}"${relFor(l)}${stamp}>${esc(l.label)}</a></li>`;
|
|
181
|
+
})
|
|
169
182
|
.join("");
|
|
170
183
|
const locale = (u.locale ?? []).map((s) => `<span class="sel">${esc(s)}</span>`).join("");
|
|
171
184
|
const social = u.social?.length ? `<span class="social">${renderSocial(u.social)}</span>` : "";
|
|
172
185
|
const cue = u.memberCue
|
|
173
|
-
? `<a class="member-cue" href="${esc(u.memberCue.href)}"
|
|
186
|
+
? `<a class="member-cue" href="${esc(u.memberCue.href)}"${trackingAttributesHtml({
|
|
187
|
+
el: u.memberCue.id,
|
|
188
|
+
action: u.memberCue.action,
|
|
189
|
+
tracking: u.memberCue.tracking,
|
|
190
|
+
placement: CHROME_PLACEMENT.utilityNav,
|
|
191
|
+
})}>${esc(u.memberCue.label)}</a>`
|
|
174
192
|
: "";
|
|
175
193
|
const right = `${locale}${social}${cue}`;
|
|
176
194
|
return (
|
|
@@ -194,7 +212,7 @@ function renderUtilityNav(u: ChromeUtilityNav): string {
|
|
|
194
212
|
* unprefixed and exact-match-only (see navCurrent.ts). Omitted ⇒ no item is
|
|
195
213
|
* ever marked current (callers that don't know the request path, e.g. tests).
|
|
196
214
|
*/
|
|
197
|
-
function renderNavItem(item: ChromeNavItem, currentPath?: string): string {
|
|
215
|
+
function renderNavItem(item: ChromeNavItem, index: number, currentPath?: string): string {
|
|
198
216
|
const columns = item.columns?.length
|
|
199
217
|
? item.columns
|
|
200
218
|
: item.children?.length
|
|
@@ -207,9 +225,19 @@ function renderNavItem(item: ChromeNavItem, currentPath?: string): string {
|
|
|
207
225
|
const haspopup = hasPanel ? ' aria-haspopup="true"' : "";
|
|
208
226
|
const itemCurrent = currentPath !== undefined && isCurrentNavHref(currentPath, item.href);
|
|
209
227
|
const ariaCurrent = itemCurrent ? ' aria-current="page"' : "";
|
|
210
|
-
const
|
|
228
|
+
const itemStamp = trackingAttributesHtml({
|
|
229
|
+
el: item.id,
|
|
230
|
+
action: item.action,
|
|
231
|
+
tracking: item.tracking,
|
|
232
|
+
placement: CHROME_PLACEMENT.primaryNav,
|
|
233
|
+
scope: "nav-primary",
|
|
234
|
+
index,
|
|
235
|
+
});
|
|
236
|
+
const link = `<a${linkClass} href="${esc(item.href)}"${haspopup}${ariaCurrent}${itemStamp}>${esc(item.label)}${badge}${caret}</a>`;
|
|
211
237
|
if (!hasPanel) return `<li class="nav-item">${link}</li>`;
|
|
212
238
|
|
|
239
|
+
const megaScope = `nav-mega:${item.id}`;
|
|
240
|
+
let childIndex = 0;
|
|
213
241
|
const wide = columns.length > 1 ? " mega--wide" : "";
|
|
214
242
|
const cols = columns
|
|
215
243
|
.map((c) => {
|
|
@@ -219,7 +247,15 @@ function renderNavItem(item: ChromeNavItem, currentPath?: string): string {
|
|
|
219
247
|
const childCurrent =
|
|
220
248
|
currentPath !== undefined && isCurrentNavHref(currentPath, l.href, l.external);
|
|
221
249
|
const childAriaCurrent = childCurrent ? ' aria-current="page"' : "";
|
|
222
|
-
|
|
250
|
+
const childStamp = trackingAttributesHtml({
|
|
251
|
+
el: l.id,
|
|
252
|
+
action: l.action,
|
|
253
|
+
tracking: l.tracking,
|
|
254
|
+
placement: CHROME_PLACEMENT.megaMenu,
|
|
255
|
+
scope: megaScope,
|
|
256
|
+
index: childIndex++,
|
|
257
|
+
});
|
|
258
|
+
return `<a href="${esc(l.href)}"${relFor(l)}${childAriaCurrent}${childStamp}>${esc(l.label)}</a>`;
|
|
223
259
|
})
|
|
224
260
|
.join("");
|
|
225
261
|
return `<div class="mega__col">${heading}${links}</div>`;
|
|
@@ -257,22 +293,35 @@ function renderHeader(header: ChromeHeader, variant: HeaderVariant, currentPath?
|
|
|
257
293
|
const minimal = variant === "minimal";
|
|
258
294
|
// Header CTAs, on the same `!minimal` rule the Astro SiteHeader applies, so the
|
|
259
295
|
// two surfaces render the same governed config identically. Each carries its
|
|
260
|
-
//
|
|
261
|
-
// served DOM rather than implied by label
|
|
296
|
+
// identity attribute (ctaIdentityAttr, shared with SiteHeader.astro), so the
|
|
297
|
+
// governed identity is present in the served DOM rather than implied by label
|
|
298
|
+
// or href.
|
|
262
299
|
const ctas =
|
|
263
300
|
minimal || !header.ctas?.length
|
|
264
301
|
? ""
|
|
265
302
|
: header.ctas
|
|
266
|
-
.map(
|
|
267
|
-
(c)
|
|
303
|
+
.map((c, i) => {
|
|
304
|
+
const idAttr = ctaIdentityAttr(c);
|
|
305
|
+
const stamp = trackingAttributesHtml({
|
|
306
|
+
el: c.id,
|
|
307
|
+
action: c.action,
|
|
308
|
+
tracking: c.tracking,
|
|
309
|
+
placement: CHROME_PLACEMENT.header,
|
|
310
|
+
scope: "header-ctas",
|
|
311
|
+
index: i,
|
|
312
|
+
});
|
|
313
|
+
return (
|
|
268
314
|
`<a class="header-cta header-cta--${esc(c.variant ?? "primary")}" ` +
|
|
269
|
-
|
|
270
|
-
|
|
315
|
+
`${idAttr.name}="${esc(idAttr.value)}" href="${esc(c.href)}"${stamp}>${esc(c.label)}</a>`
|
|
316
|
+
);
|
|
317
|
+
})
|
|
271
318
|
.join("");
|
|
272
319
|
const hamburger = minimal
|
|
273
320
|
? ""
|
|
274
321
|
: `<button class="hamburger" id="hamburger" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="primaryNav"><span></span><span></span><span></span></button>`;
|
|
275
|
-
const navItems = header.nav
|
|
322
|
+
const navItems = header.nav
|
|
323
|
+
.map((item, i) => renderNavItem(item, i, currentPath))
|
|
324
|
+
.join("\n ");
|
|
276
325
|
const navBar = minimal
|
|
277
326
|
? ""
|
|
278
327
|
: ` <nav class="primary-nav-bar" aria-label="Primary">\n` +
|
|
@@ -343,9 +392,19 @@ function renderFooter(footer: ChromeFooter): string {
|
|
|
343
392
|
})()
|
|
344
393
|
: "";
|
|
345
394
|
const columns = footer.columns
|
|
346
|
-
.map((c) => {
|
|
395
|
+
.map((c, colIndex) => {
|
|
347
396
|
const links = c.links
|
|
348
|
-
.map((l) =>
|
|
397
|
+
.map((l, i) => {
|
|
398
|
+
const stamp = trackingAttributesHtml({
|
|
399
|
+
el: l.id,
|
|
400
|
+
action: l.action,
|
|
401
|
+
tracking: l.tracking,
|
|
402
|
+
placement: CHROME_PLACEMENT.footer,
|
|
403
|
+
scope: `footer-col:${colIndex}`,
|
|
404
|
+
index: i,
|
|
405
|
+
});
|
|
406
|
+
return `<li><a href="${esc(l.href)}"${relFor(l)}${stamp}>${esc(l.label)}</a></li>`;
|
|
407
|
+
})
|
|
349
408
|
.join("");
|
|
350
409
|
return ` <div class="footer-col">\n <h4>${esc(c.title)}</h4>\n <ul>${links}</ul>\n </div>`;
|
|
351
410
|
})
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import {
|
|
19
19
|
renderIntegrationPhases,
|
|
20
|
+
INTERACTION_TRACKING_BOOT_JS,
|
|
20
21
|
type EffectivePlan,
|
|
21
22
|
} from "@tot/public-runtime";
|
|
23
|
+
import { reportTrackingChainHealth } from "./tracking/interactionHealth.js";
|
|
22
24
|
|
|
23
25
|
export { rawHtmlPolicy } from "@tot/public-runtime";
|
|
24
26
|
|
|
@@ -113,6 +115,23 @@ function injectBodyEnd(html: string, snippet: string): string {
|
|
|
113
115
|
return html + snippet;
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The declarative-interaction-tracking listener as a nonce-carrying inline
|
|
120
|
+
* `<script>` — the raw-mount half of the install (`Layout.astro` is the other,
|
|
121
|
+
* ADR 0015 one-engine-two-mounts). Both mounts embed the SAME serialized
|
|
122
|
+
* listener from `@tot/public-runtime`, so no markup path can drift into a
|
|
123
|
+
* second implementation, and the listener's install-once marker makes a second
|
|
124
|
+
* copy in the same document (a tenant `chrome.html` wrapper that also carries
|
|
125
|
+
* it) a no-op.
|
|
126
|
+
*
|
|
127
|
+
* Carries the nonce itself because it is spliced AFTER `nonceInlineTags` has
|
|
128
|
+
* run — same discipline as the render engine's phase markup.
|
|
129
|
+
*/
|
|
130
|
+
export function interactionTrackingScript(nonce: string): string {
|
|
131
|
+
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
132
|
+
return `<script${nonceAttr}>${INTERACTION_TRACKING_BOOT_JS}</script>`;
|
|
133
|
+
}
|
|
134
|
+
|
|
116
135
|
/** Stamp `nonce` onto every inline `<style>`/`<script>` so a strict CSP admits them. */
|
|
117
136
|
export function nonceInlineTags(html: string, nonce: string): string {
|
|
118
137
|
return html
|
|
@@ -167,6 +186,12 @@ export function rawHtmlResponse(
|
|
|
167
186
|
isFullDocument(html) || !opts.chromeWrapper
|
|
168
187
|
? html
|
|
169
188
|
: wrapFragmentInChrome(html, opts.chromeWrapper);
|
|
189
|
+
// HEALTH: a raw surface bypasses governed chrome, so `validateChromeConfig`
|
|
190
|
+
// never saw it and `trackingAttributes()` never stamped it. If NOT ONE of its
|
|
191
|
+
// actionable elements carries an identity, no click on this page can ever be
|
|
192
|
+
// collected — and every other layer is silent about that by design (absence
|
|
193
|
+
// is also how a legitimate opt-out looks). One structured line, never a throw.
|
|
194
|
+
reportTrackingChainHealth(composed, { surface: `raw:${opts.basePath || "/"}` });
|
|
170
195
|
// DEV ONLY: these pages are served verbatim, bypassing Astro's render pipeline,
|
|
171
196
|
// so the Vite HMR client is never injected — a save would reach the server (see
|
|
172
197
|
// the tenantHotReload plugin in astro.config.mjs) but the browser would have no
|
|
@@ -201,6 +226,11 @@ export function rawHtmlResponse(
|
|
|
201
226
|
composed = injectHeadStart(composed, phases.head);
|
|
202
227
|
composed = injectBodyStart(composed, phases.bodyStart);
|
|
203
228
|
composed = injectBodyEnd(composed, phases.bodyDeferred);
|
|
229
|
+
// Declarative interaction tracking, last in the body so the whole document —
|
|
230
|
+
// a verbatim tenant document, a fragment spliced into a hand-authored
|
|
231
|
+
// `chrome.html`, or a fragment spliced into the generated `renderRawChrome`
|
|
232
|
+
// wrapper — is already present when the delegated listener binds.
|
|
233
|
+
composed = injectBodyEnd(composed, interactionTrackingScript(opts.nonce));
|
|
204
234
|
const doc = prefixTenantLinks(composed, opts.basePath);
|
|
205
235
|
return new Response(doc, {
|
|
206
236
|
headers: { "content-type": "text/html; charset=utf-8" },
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ship/owner capability primitives — genuinely shared between the control-plane
|
|
3
|
+
* ship surface (`@/lib/change`, `@/lib/grants`, `@/lib/forge` — all moat, dropped
|
|
4
|
+
* from the public storefront-runner) and renderer-adjacent code that ships in the
|
|
5
|
+
* runner (`@/lib/auth/adminEntry`, the order-admin API guard). Pure types + pure
|
|
6
|
+
* functions only — no I/O, no MCP/forge calls, no secrets — which is exactly why
|
|
7
|
+
* they can live outside the moat cluster instead of forcing a KEPT file to import
|
|
8
|
+
* a DROPped one.
|
|
9
|
+
*
|
|
10
|
+
* Canonical home for these five exports; `@/lib/forge/types`, `@/lib/grants/session`,
|
|
11
|
+
* `@/lib/change/route/route`, and `@/lib/change/route/ship-capabilities` re-export
|
|
12
|
+
* from here (not the other way around) so the moat cluster still has one place to
|
|
13
|
+
* read them, while nothing outside the moat needs to import INTO it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Access level the acting operator holds. */
|
|
17
|
+
export type Capability = "preview-access" | "ship-on-behalf";
|
|
18
|
+
|
|
19
|
+
/** Capabilities that carry tenant-owner standing (vs. a granted developer). */
|
|
20
|
+
export const OWNER_CAPABILITIES: ReadonlySet<string> = new Set(["owner", "admin"]);
|
|
21
|
+
|
|
22
|
+
/** Pure inputs to the owner-principal decision (session only, no I/O). */
|
|
23
|
+
export interface OwnerPrincipalInput {
|
|
24
|
+
/** Whether the request is authenticated for the resolved tenant. */
|
|
25
|
+
authorized: boolean;
|
|
26
|
+
/** The signed-in viewer's capability over the resolved tenant, if any. */
|
|
27
|
+
viewerCapability: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Decide whether the acting principal IS the tenant owner. Owner iff the
|
|
32
|
+
* request is authorized AND a signed-in viewer holds an owner/admin capability
|
|
33
|
+
* over the resolved tenant.
|
|
34
|
+
*/
|
|
35
|
+
export function decideIsOwner(input: OwnerPrincipalInput): boolean {
|
|
36
|
+
if (!input.authorized) return false;
|
|
37
|
+
return !!input.viewerCapability && OWNER_CAPABILITIES.has(input.viewerCapability);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The acting principal's ship-relevant relationship to the tenant, resolved
|
|
42
|
+
* SERVER-SIDE (never from request input).
|
|
43
|
+
*/
|
|
44
|
+
export interface ShipPrincipal {
|
|
45
|
+
/** True when the acting principal IS the tenant owner (see {@link decideIsOwner}). */
|
|
46
|
+
isOwner: boolean;
|
|
47
|
+
/** The developer grant capability the principal holds. Fail-closed default is `preview-access`. */
|
|
48
|
+
capability: Capability;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The two distinct ship-gate capabilities, derived per-principal. */
|
|
52
|
+
export interface ShipCapabilities {
|
|
53
|
+
/** May invoke ship-on-behalf (accept / reject / rollback). Owner is a superset. */
|
|
54
|
+
shipOnBehalf: boolean;
|
|
55
|
+
/** May invoke the apex (DNS) cutover. OWNER-ONLY. */
|
|
56
|
+
apexCutover: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the two capabilities from the principal. Owner ⊇ ship-on-behalf for the
|
|
61
|
+
* ship axis; apex-cutover is owner-only.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveShipCapabilities(principal: ShipPrincipal): ShipCapabilities {
|
|
64
|
+
return {
|
|
65
|
+
shipOnBehalf: principal.isOwner || principal.capability === "ship-on-behalf",
|
|
66
|
+
apexCutover: principal.isOwner,
|
|
67
|
+
};
|
|
68
|
+
}
|