@tokenoftrust/storefront-runner 1.4.1 → 1.4.2-rc.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.
Files changed (166) hide show
  1. package/apps/storefront/astro.config.mjs +17 -1
  2. package/apps/storefront/integrations/materialize-guard.mjs +56 -0
  3. package/apps/storefront/migrations-apps/0002_good_switch.sql +26 -0
  4. package/apps/storefront/migrations-apps/0003_mute_marauders.sql +12 -0
  5. package/apps/storefront/migrations-apps/meta/0002_snapshot.json +1160 -0
  6. package/apps/storefront/migrations-apps/meta/0003_snapshot.json +1235 -0
  7. package/apps/storefront/migrations-apps/meta/_journal.json +14 -0
  8. package/apps/storefront/public/js/dashboard-team.js +38 -2
  9. package/apps/storefront/src/components/Seo.astro +65 -1
  10. package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +878 -0
  11. package/apps/storefront/src/components/admin/AdminBlogTab.astro +1432 -0
  12. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +3 -7
  13. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1105 -2334
  14. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +1 -3
  15. package/apps/storefront/src/components/blog/BlogPaywall.astro +87 -0
  16. package/apps/storefront/src/components/blog/PostAccessBadge.astro +43 -0
  17. package/apps/storefront/src/components/blog/PostGrid.astro +57 -0
  18. package/apps/storefront/src/components/blog/ReactionBar.astro +214 -0
  19. package/apps/storefront/src/layouts/Layout.astro +48 -3
  20. package/apps/storefront/src/lib/activity/alerts.ts +17 -19
  21. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +3 -3
  22. package/apps/storefront/src/lib/activity/deployVersion.ts +19 -26
  23. package/apps/storefront/src/lib/activity/ingest.ts +8 -8
  24. package/apps/storefront/src/lib/activity/ingestAuth.ts +1 -1
  25. package/apps/storefront/src/lib/activity/killSwitch.ts +6 -6
  26. package/apps/storefront/src/lib/activity/query.ts +9 -9
  27. package/apps/storefront/src/lib/activity/recordActivity.ts +11 -15
  28. package/apps/storefront/src/lib/activity/store.ts +4 -6
  29. package/apps/storefront/src/lib/activity/uiActor.ts +12 -12
  30. package/apps/storefront/src/lib/activity/workerCommit.ts +6 -6
  31. package/apps/storefront/src/lib/admin/adminShell.ts +290 -0
  32. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +2 -3
  33. package/apps/storefront/src/lib/admin/ai/audit.ts +3 -5
  34. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +7 -7
  35. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +3 -3
  36. package/apps/storefront/src/lib/admin/orderClient.ts +3 -5
  37. package/apps/storefront/src/lib/admin/services/orders.ts +17 -19
  38. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +4 -6
  39. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +1 -1
  40. package/apps/storefront/src/lib/apps/adminService.ts +4 -4
  41. package/apps/storefront/src/lib/apps/adminSession.ts +53 -44
  42. package/apps/storefront/src/lib/apps/apiAuth.ts +2 -2
  43. package/apps/storefront/src/lib/apps/apiRoute.ts +2 -2
  44. package/apps/storefront/src/lib/apps/catalogMapper.ts +1 -1
  45. package/apps/storefront/src/lib/apps/credentials.ts +2 -2
  46. package/apps/storefront/src/lib/apps/gatewayKeys.ts +4 -4
  47. package/apps/storefront/src/lib/apps/healthAggregate.ts +5 -5
  48. package/apps/storefront/src/lib/apps/orders/attributionService.ts +2 -2
  49. package/apps/storefront/src/lib/apps/orders/customerHash.ts +1 -1
  50. package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +12 -12
  51. package/apps/storefront/src/lib/apps/orders/idempotency.ts +7 -7
  52. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +1 -1
  53. package/apps/storefront/src/lib/apps/orders/orderMapper.ts +2 -2
  54. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +6 -6
  55. package/apps/storefront/src/lib/apps/registryService.ts +6 -6
  56. package/apps/storefront/src/lib/apps/scopes.ts +2 -2
  57. package/apps/storefront/src/lib/apps/tokenIssuer.ts +4 -4
  58. package/apps/storefront/src/lib/apps/tokenVerifier.ts +6 -6
  59. package/apps/storefront/src/lib/apps/widgets/eligibility.ts +2 -2
  60. package/apps/storefront/src/lib/apps/widgets/frameProps.ts +3 -3
  61. package/apps/storefront/src/lib/apps/widgets/launchToken.ts +4 -4
  62. package/apps/storefront/src/lib/apps/widgets/placements.ts +2 -2
  63. package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +1 -1
  64. package/apps/storefront/src/lib/auth/adminEntry.ts +9 -9
  65. package/apps/storefront/src/lib/auth/identityToken.ts +6 -14
  66. package/apps/storefront/src/lib/auth/loginGate.ts +75 -13
  67. package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +2 -4
  68. package/apps/storefront/src/lib/auth/operatorSession.ts +113 -0
  69. package/apps/storefront/src/lib/auth/session.ts +7 -8
  70. package/apps/storefront/src/lib/auth/totAccessClient.ts +8 -18
  71. package/apps/storefront/src/lib/basePath.ts +9 -2
  72. package/apps/storefront/src/lib/blog/access.ts +29 -0
  73. package/apps/storefront/src/lib/blog/collection.ts +313 -0
  74. package/apps/storefront/src/lib/blog/markdown.ts +174 -0
  75. package/apps/storefront/src/lib/blog/provider.ts +5 -0
  76. package/apps/storefront/src/lib/blog/reactions.ts +87 -0
  77. package/apps/storefront/src/lib/blog/rss.ts +92 -0
  78. package/apps/storefront/src/lib/blog/teaser.ts +83 -0
  79. package/apps/storefront/src/lib/blog/types.ts +34 -0
  80. package/apps/storefront/src/lib/blog/viewCounts.ts +243 -0
  81. package/apps/storefront/src/lib/d1/catalog.ts +2 -2
  82. package/apps/storefront/src/lib/d1/schema-apps.ts +96 -17
  83. package/apps/storefront/src/lib/dev/activityIngestToken.ts +2 -4
  84. package/apps/storefront/src/lib/dev/activityStore.ts +3 -5
  85. package/apps/storefront/src/lib/dev/apiBase.ts +1 -4
  86. package/apps/storefront/src/lib/dev/cockpitStore.ts +1 -9
  87. package/apps/storefront/src/lib/dev/devDraft.ts +1 -1
  88. package/apps/storefront/src/lib/dev/previewStatus.ts +9 -21
  89. package/apps/storefront/src/lib/dev/rendezvousBroker.ts +9 -24
  90. package/apps/storefront/src/lib/edgeCache.ts +17 -0
  91. package/apps/storefront/src/lib/http/fetchTransport.ts +63 -0
  92. package/apps/storefront/src/lib/i18n.ts +25 -0
  93. package/apps/storefront/src/lib/jsonld.ts +118 -0
  94. package/apps/storefront/src/lib/monitoring/manifest.ts +11 -11
  95. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +30 -0
  96. package/apps/storefront/src/lib/newsletter/consent.ts +23 -0
  97. package/apps/storefront/src/lib/newsletter/digest.ts +117 -0
  98. package/apps/storefront/src/lib/newsletter/resendClient.ts +79 -0
  99. package/apps/storefront/src/lib/newsletter/send.ts +265 -0
  100. package/apps/storefront/src/lib/newsletter/subscribers.ts +156 -0
  101. package/apps/storefront/src/lib/publish/adminPublishTab.ts +3519 -0
  102. package/apps/storefront/src/lib/publish/apex-readiness.ts +41 -18
  103. package/apps/storefront/src/lib/publish/dispatchHealth.ts +5 -7
  104. package/apps/storefront/src/lib/publish/domainState.ts +22 -13
  105. package/apps/storefront/src/lib/publish/panelPolling.ts +78 -0
  106. package/apps/storefront/src/lib/publish/shipWorkspace.ts +100 -49
  107. package/apps/storefront/src/lib/publish/stageBundleUpload.ts +215 -0
  108. package/apps/storefront/src/lib/rate/kvWindow.ts +53 -0
  109. package/apps/storefront/src/lib/search/index.ts +2 -2
  110. package/apps/storefront/src/lib/seo/alternates.ts +42 -0
  111. package/apps/storefront/src/lib/seo/meta.ts +65 -0
  112. package/apps/storefront/src/lib/social/golive.ts +212 -0
  113. package/apps/storefront/src/lib/social/notify.ts +32 -0
  114. package/apps/storefront/src/lib/social/tokenStore.ts +171 -0
  115. package/apps/storefront/src/lib/the-build/provider.ts +27 -0
  116. package/apps/storefront/src/lib/the-build/readingTime.ts +35 -0
  117. package/apps/storefront/src/lib/the-build/toc.ts +95 -0
  118. package/apps/storefront/src/lib/tot/ToTClient.ts +1 -1
  119. package/apps/storefront/src/lib/tot/d1Client.ts +2 -2
  120. package/apps/storefront/src/lib/tot/totClientInterface.ts +3 -4
  121. package/apps/storefront/src/lib/webhooks/deliveryStore.ts +16 -17
  122. package/apps/storefront/src/middleware/index.ts +113 -2
  123. package/apps/storefront/src/pages/admin.astro +10 -1312
  124. package/apps/storefront/src/pages/api/blog/[collection]/[slug]/react.ts +104 -0
  125. package/apps/storefront/src/pages/api/newsletter/[collection]/subscribe.ts +146 -0
  126. package/apps/storefront/src/pages/api/newsletter/confirm.ts +75 -0
  127. package/apps/storefront/src/pages/api/newsletter/unsubscribe.ts +90 -0
  128. package/apps/storefront/src/pages/api/publish/stage-bundle.ts +70 -0
  129. package/apps/storefront/src/pages/api/rum/vitals.ts +21 -2
  130. package/apps/storefront/src/pages/blog/[slug].astro +96 -16
  131. package/apps/storefront/src/pages/blog/author/[author].astro +140 -0
  132. package/apps/storefront/src/pages/blog/category/[category].astro +67 -0
  133. package/apps/storefront/src/pages/blog/index.astro +2 -0
  134. package/apps/storefront/src/pages/blog/tag/[tag].astro +65 -0
  135. package/apps/storefront/src/pages/cockpit.astro +37 -12
  136. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +15 -11
  137. package/apps/storefront/src/pages/search.astro +1 -1
  138. package/apps/storefront/src/pages/sitemap.xml.ts +20 -0
  139. package/apps/storefront/src/pages/the-build/[slug].astro +129 -0
  140. package/apps/storefront/src/pages/the-build/index.astro +146 -0
  141. package/apps/storefront/src/pages/the-build/rss.xml.ts +33 -0
  142. package/apps/storefront/src/styles/admin.css +1080 -0
  143. package/apps/storefront/tsconfig.json +5 -0
  144. package/package.json +1 -1
  145. package/packages/public-runtime/package.json +1 -0
  146. package/packages/public-runtime/src/activity/catalog.ts +52 -0
  147. package/packages/public-runtime/src/catalog-d1.ts +1 -1
  148. package/packages/public-runtime/src/customization-preview.ts +25 -2
  149. package/packages/public-runtime/src/customization-runtime.ts +125 -3
  150. package/packages/public-runtime/src/customization-versioning.ts +10 -0
  151. package/packages/public-runtime/src/index.ts +1 -0
  152. package/packages/public-runtime/src/static-bundle.ts +357 -0
  153. package/packages/public-runtime/src/tenant.ts +36 -8
  154. package/scripts/build/copy-tenant-assets.mjs +14 -4
  155. package/scripts/dev/ai-edit.mjs +1 -1
  156. package/scripts/dev/checkout-watch.mjs +2 -2
  157. package/scripts/dev/file-browser.mjs +2 -4
  158. package/scripts/dev/file-writer.mjs +1 -1
  159. package/scripts/dev/git-status.mjs +2 -2
  160. package/scripts/dev/locate-handler.mjs +3 -3
  161. package/scripts/dev/port-check.mjs +3 -3
  162. package/scripts/dev/publish.mjs +5 -9
  163. package/scripts/dev/shot.mjs +1 -1
  164. package/scripts/dev/unified-diff.mjs +8 -3
  165. package/scripts/tot-dev.mjs +6 -9
  166. package/apps/storefront/src/lib/dev/vcBinding.ts +0 -80
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Operator-session resolution for the Private Apps admin API
3
- * (`/api/apps/admin/*`, PrivateApps epic, D2 Chunk C). Adapted from
3
+ * (`/api/apps/admin/*`). Mirrors
4
4
  * `../grants/session.ts`'s owner-session resolution — same two paths, same
5
5
  * fail-closed shape — but resolves `{ tenantId, actor }` instead of `{ owner }`:
6
6
  *
@@ -38,7 +38,7 @@
38
38
  * Routes depend only on the `{ ok, tenantId, actor }` result. `authorizeAdmin`
39
39
  * is the pure core of path 2 (unit-testable, no I/O).
40
40
  *
41
- * D8 ADDITION — console admin session. The two paths above are host-bound:
41
+ * Console admin session. The two paths above are host-bound:
42
42
  * they resolve exactly one tenant (`context.locals.tenant`). The
43
43
  * `/api/apps/admin/*` surface is also reachable from the multi-tenant vendor
44
44
  * console, where a signed-in viewer may hold memberships on several vendors —
@@ -61,6 +61,11 @@ import { sha256Hex } from "@tot/public-runtime";
61
61
  import { readEnv } from "@/lib/env";
62
62
  import { readViewerSession } from "@/lib/auth/route.js";
63
63
  import { viewerCanAccess, membershipFor } from "@/lib/dashboard/tenantSelection.js";
64
+ import {
65
+ authorizeBearerOperator,
66
+ resolveHostBoundSession,
67
+ type SessionFailure,
68
+ } from "@/lib/auth/operatorSession.js";
64
69
 
65
70
  /** Capabilities that make a signed-in viewer an app-registry operator for their tenant. */
66
71
  const ADMIN_CAPABILITIES: ReadonlySet<string> = new Set(["owner", "admin"]);
@@ -88,33 +93,30 @@ export interface AdminAuthInput {
88
93
 
89
94
  export type AdminAuthResult =
90
95
  | { ok: true; tenantId: string; actor: string; role?: string }
91
- | { ok: false; status: number; message: string };
96
+ | SessionFailure;
97
+
98
+ /** The one authorized-success shape both host-bound and Bearer admin paths return. */
99
+ type AdminAuthOk = Extract<AdminAuthResult, { ok: true }>;
92
100
 
93
101
  /**
94
102
  * Decide the authenticated tenant from env + request, with NO I/O —
95
103
  * unit-testable. Fail-closed: unconfigured gate → 503, bad/absent Bearer →
96
- * 401, no resolvable tenant → 400.
104
+ * 401, no resolvable tenant → 400. A thin adapter over the shared
105
+ * Bearer-operator core that maps its resolved `principal` to this surface's
106
+ * `tenantId` and stamps the fixed `BEARER_ACTOR` (no per-caller identity here).
97
107
  */
98
108
  export function authorizeAdmin(input: AdminAuthInput): AdminAuthResult {
99
- if (!input.gateSecret) {
100
- return {
101
- ok: false,
102
- status: 503,
103
- message: "apps admin operator secret not configured",
104
- };
105
- }
106
- if (input.authHeader !== `Bearer ${input.gateSecret}`) {
107
- return { ok: false, status: 401, message: "Unauthorized" };
108
- }
109
- const tenantId = (input.configuredTenant ?? input.requestedTenant ?? "").trim();
110
- if (!tenantId) {
111
- return {
112
- ok: false,
113
- status: 400,
114
- message: "tenant unresolved (set APPS_ADMIN_TENANT or X-Tot-Tenant)",
115
- };
116
- }
117
- return { ok: true, tenantId, actor: BEARER_ACTOR };
109
+ const res = authorizeBearerOperator({
110
+ authHeader: input.authHeader,
111
+ gateSecret: input.gateSecret,
112
+ configuredPrincipal: input.configuredTenant,
113
+ requestedPrincipal: input.requestedTenant,
114
+ unconfiguredMessage: "apps admin operator secret not configured",
115
+ unresolvedMessage: "tenant unresolved (set APPS_ADMIN_TENANT or X-Tot-Tenant)",
116
+ });
117
+ return res.ok
118
+ ? { ok: true, tenantId: res.principal, actor: BEARER_ACTOR }
119
+ : res;
118
120
  }
119
121
 
120
122
  /** One-way, stable pseudonym for an audit `actor` — never the email itself. */
@@ -128,30 +130,37 @@ async function actorFromEmail(email: string): Promise<string> {
128
130
  * from the body.
129
131
  */
130
132
  export async function resolveAdminSession(context: APIContext): Promise<AdminAuthResult> {
131
- // Path 1: a signed-in tenant-operator session. The tenant IS the resolved
132
- // host's appDomain — never from the request body.
133
- const viewer = context.locals?.viewer;
134
- const tenantId = context.locals?.tenant?.appDomain;
135
- if (viewer && tenantId) {
136
- if (!ADMIN_CAPABILITIES.has(viewer.capability)) {
137
- return {
133
+ return resolveHostBoundSession<AdminAuthOk>(context, {
134
+ capabilities: ADMIN_CAPABILITIES,
135
+ // A resolved-but-insufficient viewer fails closed with 403 and does NOT
136
+ // fall through to the Bearer gate — an authenticated-but-under-privileged
137
+ // caller must not be silently admitted by a secret it also presents.
138
+ onInsufficientCapability: {
139
+ mode: "failClosed",
140
+ failure: {
138
141
  ok: false,
139
142
  status: 403,
140
143
  message: "not authorized to manage apps for this tenant",
141
- };
142
- }
143
- return { ok: true, tenantId, actor: await actorFromEmail(viewer.email) };
144
- }
145
-
146
- // Path 2 (fallback): the headless-console Bearer operator secret. Dedicated
147
- // secret only — no fallback to another deployment's secret.
148
- const gateSecret = await readEnv("APPS_ADMIN_SECRET");
149
- const configuredTenant = await readEnv("APPS_ADMIN_TENANT");
150
- return authorizeAdmin({
151
- authHeader: context.request.headers.get("authorization"),
152
- gateSecret,
153
- configuredTenant,
154
- requestedTenant: context.request.headers.get("x-tot-tenant"),
144
+ },
145
+ },
146
+ // Path 1: the tenant IS the resolved host's appDomain — never the body.
147
+ resolveViewer: async (viewer, tenantId) => ({
148
+ ok: true,
149
+ tenantId,
150
+ actor: await actorFromEmail(viewer.email),
151
+ }),
152
+ // Path 2 (fallback): the headless-console Bearer operator secret. Dedicated
153
+ // secret only — no fallback to another deployment's secret.
154
+ resolveBearer: async (ctx) => {
155
+ const gateSecret = await readEnv("APPS_ADMIN_SECRET");
156
+ const configuredTenant = await readEnv("APPS_ADMIN_TENANT");
157
+ return authorizeAdmin({
158
+ authHeader: ctx.request.headers.get("authorization"),
159
+ gateSecret,
160
+ configuredTenant,
161
+ requestedTenant: ctx.request.headers.get("x-tot-tenant"),
162
+ });
163
+ },
155
164
  });
156
165
  }
157
166
 
@@ -1,7 +1,7 @@
1
1
  /**
2
- * The D3 scoped-API authorization boundary (PrivateApps epic) — every
2
+ * The scoped-API authorization boundary — every
3
3
  * `/api/apps/v1/*` route calls `authorizeAppRequest` FIRST. Verifies the
4
- * app-gateway bearer JWT (D2's `AppTokenVerifier`), resolves the calling
4
+ * app-gateway bearer JWT (`AppTokenVerifier`), resolves the calling
5
5
  * install from the registry, and enforces the single scope the route
6
6
  * requires per `docs/private-apps/contract/openapi.yaml`'s `x-requiredScope`.
7
7
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Response helpers shared by every `/api/apps/v1/*` route (PrivateApps epic,
3
- * D3) — a no-store JSON responder (mirrors `../grants/route.ts` /
2
+ * Response helpers shared by every `/api/apps/v1/*` route a no-store JSON
3
+ * responder (mirrors `../grants/route.ts` /
4
4
  * `./adminService.ts`'s pattern) plus the `tot-request-id` / `traceparent`
5
5
  * response headers `docs/private-apps/contract/openapi.yaml` documents on
6
6
  * every operation.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Maps the internal `CatalogProduct` read model to the D3 contract's
2
+ * Maps the internal `CatalogProduct` read model to the scoped-API contract's
3
3
  * `Product` shape (`docs/private-apps/contract/openapi.yaml`, schema
4
4
  * `Product` — `additionalProperties: false`). Drops every internal field
5
5
  * the contract doesn't declare (variants, images, metafields, source, …) so
@@ -1,6 +1,6 @@
1
1
  /**
2
- * D1-backed `CredentialPort` implementation (PrivateApps epic, D2 Chunk B) —
3
- * issues/rotates/clears the OAuth2 client-credentials for an app install, and
2
+ * D1-backed `CredentialPort` implementation issues/rotates/clears the
3
+ * OAuth2 client-credentials for an app install, and
4
4
  * verifies a presented `(clientId, secret)` pair for the token endpoint
5
5
  * (`tokenIssuer.ts`).
6
6
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
- * RS256 signing-key loader for the Storefront App Gateway (PrivateApps epic,
3
- * D2 Chunk B) — the key that mints and verifies the OAuth2 client-credentials
4
- * JWTs a private app presents to the D3 scoped APIs.
2
+ * RS256 signing-key loader for the Storefront App Gateway the key that mints
3
+ * and verifies the OAuth2 client-credentials
4
+ * JWTs a private app presents to the scoped APIs.
5
5
  *
6
6
  * Mirrors `../auth/brokerAssertion.ts`'s `loadSigningKey` caching + `readEnv`
7
7
  * usage EXACTLY, but over a SEPARATE key (`JWT_APP_GATEWAY_PRIVATE_KEY_B64`):
@@ -34,7 +34,7 @@ export const DEFAULT_APP_GATEWAY_ISSUER = "storefront-app-gateway";
34
34
  export const APP_TOKEN_TTL_SECONDS = 300;
35
35
 
36
36
  /**
37
- * Widget launch-token lifetime (PrivateApps epic, D5 Chunk B) — shorter than
37
+ * Widget launch-token lifetime — shorter than
38
38
  * {@link APP_TOKEN_TTL_SECONDS}: this token only needs to survive the SSR
39
39
  * render → iframe-load round trip, never a standing API session.
40
40
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Admin Health view aggregation — PrivateApps epic, D8 (Chunk A + D). Folds
2
+ * Admin Health view aggregation. Folds
3
3
  * one `AppInstall` (`registryService.ts`) and its `InstallHealth` rollup
4
- * (`../webhooks/deliveryStore.ts`'s `getInstallHealth`, now including
4
+ * (`../webhooks/deliveryStore.ts`'s `getInstallHealth`, including
5
5
  * `p95DeliveryMs`) into the single `AppInstallHealthView` shape the admin
6
6
  * console renders. Deliberately transport-free — no D1/fetch here, so it's
7
7
  * unit-testable with fakes; the caller (the admin health route) owns
@@ -11,9 +11,9 @@
11
11
  * a raw URL here, only as `hasWebhookEndpoint` presence — the console shows
12
12
  * "configured or not", not the app's receiver address.
13
13
  *
14
- * `runtimeMode` is hardcoded to `"external"` for V1: there is no per-install
15
- * runtime-mode column yet (every install today IS an externally-hosted app;
16
- * an in-platform runtime mode is a D9+ concern).
14
+ * `runtimeMode` is hardcoded to `"external"`: there is no per-install
15
+ * runtime-mode column (every install IS an externally-hosted app;
16
+ * an in-platform runtime mode is out of scope).
17
17
  */
18
18
  import { validateWebhookEndpoint } from "../webhooks/endpointGuard.js";
19
19
  import type { InstallHealth } from "../webhooks/deliveryStore.js";
@@ -1,6 +1,6 @@
1
1
  /**
2
- * App-owned attribution/commission write model PrivateApps epic, D6
3
- * (Chunk C). Owns the `app_attributions` rows `schema.ts` declares: the
2
+ * App-owned attribution/commission write model. Owns the
3
+ * `app_attributions` rows `schema.ts` declares: the
4
4
  * ledger `POST /attribution` (`docs/private-apps/contract/openapi.yaml`'s
5
5
  * `writeAttribution`) writes into. Writes only into the app's own
6
6
  * attribution namespace — this service never mutates `orders`, it only
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Opaque customer hash — PrivateApps epic, D6 (Chunk A). The `customerHash`
2
+ * Opaque customer hash — the `customerHash`
3
3
  * every order/attribution surface exposes instead of a raw customer id (see
4
4
  * `docs/private-apps/api-event-reference.md`'s PII-minimization matrix and
5
5
  * `contract/openapi.yaml`'s `Order.customerHash`).
@@ -1,18 +1,18 @@
1
1
  /**
2
- * FoxyOrderClient — the boundary to tot-foxycart's order-read surface
3
- * (PrivateApps epic, D6 Chunk A/B). `ordersStore.ts` and the read
4
- * routes / event receiver (later chunks) never call `fetch` directly; they go
5
- * through this seam so a fake can stand in for tot-foxycart in tests.
2
+ * FoxyOrderClient — the boundary to tot-foxycart's order-read surface.
3
+ * `ordersStore.ts` and the read routes / event receiver never call `fetch`
4
+ * directly; they go through this seam so a fake can stand in for tot-foxycart
5
+ * in tests.
6
6
  *
7
- * RECONCILED (Chunk B) against the real service —
8
- * `tot-foxycart/routes/foxyOrderApi.js` + `modules/integrations/foxyOrderMapper.node.js`:
7
+ * The service is `tot-foxycart/routes/foxyOrderApi.js` +
8
+ * `modules/integrations/foxyOrderMapper.node.js`:
9
9
  * a `GET {baseUrl}/commerce/orders?appDomain=&limit=&offset=&since=&until=&status=`
10
10
  * list endpoint returning `{ orders, limit, offset, total, hasMore }` (limit/offset,
11
- * NOT page/pageSize — chunk A guessed page/pageSize), and a
11
+ * NOT page/pageSize), and a
12
12
  * `GET {baseUrl}/commerce/orders/{externalOrderId}?appDomain=` detail endpoint
13
- * returning `{ order }` (an envelope, not the bare order — chunk A guessed bare),
13
+ * returning `{ order }` (an envelope, not the bare order),
14
14
  * both Bearer-authenticated with the tenant's Foxy partner API key. `FoxyOrder`
15
- * below now mirrors `buildOrderPayload()`'s actual normalized DTO — NOT the
15
+ * below mirrors `buildOrderPayload()`'s actual normalized DTO — NOT the
16
16
  * contract's minimized `Order` shape (`contract/openapi.yaml`); mapping one to
17
17
  * the other is `orderMapper.ts`'s job.
18
18
  *
@@ -20,8 +20,8 @@
20
20
  * factory shape and `../../webhooks/deliveryEngine.ts`'s bounded-timeout
21
21
  * `fetch` (AbortController, injectable `fetchImpl`) — but adds a settable
22
22
  * test seam (`setFoxyOrderClientForTesting`) since this client, unlike
23
- * `messagesClient.ts`, is a shared singleton multiple later chunks (read
24
- * routes, event receiver) will each independently resolve.
23
+ * `messagesClient.ts`, is a shared singleton that multiple callers (read
24
+ * routes, event receiver) each independently resolve.
25
25
  */
26
26
  import { readEnv } from "@/lib/env";
27
27
 
@@ -174,7 +174,7 @@ export class HttpFoxyOrderClient implements FoxyOrderClient {
174
174
  }
175
175
 
176
176
  // --- Test seam ---------------------------------------------------------------
177
- // Later chunks (read routes, event receiver) each resolve their own client
177
+ // Callers (read routes, event receiver) each resolve their own client
178
178
  // instance via `getFoxyOrderClient()`; a module-level override lets a single
179
179
  // `setFoxyOrderClientForTesting` call stand in for tot-foxycart across all of
180
180
  // them in one test, without threading a fake through every call site.
@@ -1,17 +1,17 @@
1
1
  /**
2
- * Idempotency-Key ledger — PrivateApps epic, D6 (Chunk C). Backs the
3
- * `idempotency_keys` table `schema.ts` declares: the FIRST real (not
2
+ * Idempotency-Key ledger. Backs the
3
+ * `idempotency_keys` table `schema.ts` declares: the one real (not
4
4
  * presence-only) idempotency ledger in this codebase, and the one
5
5
  * `POST /attribution` (`docs/private-apps/contract/openapi.yaml`'s
6
6
  * `writeAttribution`) requires — a reused key with a body hash that doesn't
7
7
  * match the one first stored is a 409, not a silent overwrite or a second
8
- * row. Contrast D4's replay endpoint (`webhooks/deliveries/{id}/replay.ts`),
8
+ * row. Contrast the webhook replay endpoint (`webhooks/deliveries/{id}/replay.ts`),
9
9
  * which only checks the header's presence because it has no request body to
10
- * hash and no table backing it yet.
10
+ * hash and no table backing it.
11
11
  *
12
- * Pure over the `Queryable` seam, same shape every other D6 store uses.
13
- * Route-agnostic by design: `attribution.ts` is the first caller, but any
14
- * future idempotent write route can reuse this unchanged.
12
+ * Pure over the `Queryable` seam, same shape every other orders store uses.
13
+ * Route-agnostic by design: `attribution.ts` is one caller, but any
14
+ * idempotent write route can reuse this.
15
15
  */
16
16
  import { sha256Hex } from "@tot/public-runtime";
17
17
  import type { Queryable } from "../../d1/catalog.js";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Inbound order-forward receiver — PrivateApps epic, D6 (Chunk D). The
2
+ * Inbound order-forward receiver. The
3
3
  * transport-free core `pages/api/apps/internal/order-forward.ts` calls once
4
4
  * it has verified the HMAC signature: given one reconciled order from
5
5
  * tot-foxycart, upsert it into `ordersStore.ts` and decide whether that write
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Pure mapper: `FoxyOrderClient`'s `FoxyOrder` (tot-foxycart's normalized
3
- * transaction DTO) -> the contract's minimized `Order` shape (PrivateApps
4
- * epic, D6 Chunk B; `docs/private-apps/contract/openapi.yaml`'s `Order`).
3
+ * transaction DTO) -> the contract's minimized `Order` shape
4
+ * (`docs/private-apps/contract/openapi.yaml`'s `Order`).
5
5
  *
6
6
  * Drops everything the contract's `additionalProperties: false` Order
7
7
  * doesn't declare — no raw checkout payload, no signed-cart material, no tax
@@ -1,9 +1,9 @@
1
1
  /**
2
- * `orders` read-model store — PrivateApps epic, D6 (Chunk A). Owns the
3
- * `orders` rows `schema.ts` declares: the diffing surface a later chunk's
4
- * event emission reads (`upsertOrder`'s returned `previousStatus` is how it
2
+ * `orders` read-model store. Owns the
3
+ * `orders` rows `schema.ts` declares: the diffing surface the event emission
4
+ * reads (`upsertOrder`'s returned `previousStatus` is how it
5
5
  * detects a created→fulfilled transition without a second query) and the
6
- * lookup surface the attribution route (also later) uses for its cross-tenant
6
+ * lookup surface the attribution route uses for its cross-tenant
7
7
  * ownership check.
8
8
  *
9
9
  * Pure over the `Queryable` seam (see `../../d1/catalog.ts`) — same shape
@@ -54,8 +54,8 @@ export interface UpsertOrderInput {
54
54
 
55
55
  /**
56
56
  * `upsertOrder`'s return: the row's status BEFORE this write (`null` for a
57
- * brand-new order) alongside the row as it now stands — the diffing signal a
58
- * later chunk's event emission needs to tell "just created" apart from
57
+ * brand-new order) alongside the row as it now stands — the diffing signal the
58
+ * event emission needs to tell "just created" apart from
59
59
  * "created → fulfilled" apart from "no-op re-delivery of the same status",
60
60
  * without a second round trip.
61
61
  */
@@ -1,8 +1,8 @@
1
1
  /**
2
- * App registry service — PrivateApps epic, D2 (Chunk A). Owns the
2
+ * App registry service. Owns the
3
3
  * `app_installs` / `app_audit_log` rows described in `schema.ts` and
4
4
  * orchestrates credential lifecycle through the {@link CredentialPort} seam
5
- * (Chunk B implements it; Chunk A only depends on the interface, mirroring
5
+ * (the credential store implements it; this service depends only on the interface, mirroring
6
6
  * how `D1VersionStore` implements `@tot/private-controlplane`'s `VersionStore`
7
7
  * contract without either side importing the other's internals).
8
8
  *
@@ -131,7 +131,7 @@ export interface IssuedCredential {
131
131
 
132
132
  /**
133
133
  * `IssuedCredential` plus the one-time plaintext secret. `issue`/`rotate` mint
134
- * the secret, hash it for storage (Chunk B: `sha256Hex`), and hand the
134
+ * the secret, hash it for storage (`sha256Hex`), and hand the
135
135
  * plaintext back exactly once here — there is no "show secret again" path by
136
136
  * design, so a caller that discards this return has lost it for good (see
137
137
  * `AppRegistryService.install`'s `InstallResult`, which is why this seam
@@ -140,10 +140,10 @@ export interface IssuedCredential {
140
140
  export type IssuedCredentialSecret = IssuedCredential & { secret: string };
141
141
 
142
142
  /**
143
- * Minimal seam for the credential lifecycle a later chunk (D2 Chunk B)
143
+ * Minimal seam for the credential lifecycle the credential store
144
144
  * implements against `app_credentials`. `AppRegistryService` orchestrates
145
145
  * through this interface only — it never touches credential storage directly,
146
- * so Chunk A can ship and be tested (with a fake) without Chunk B existing.
146
+ * so it can be tested with a fake.
147
147
  */
148
148
  export interface CredentialPort {
149
149
  /** Issue a fresh client-credential set for a newly installed app. */
@@ -155,7 +155,7 @@ export interface CredentialPort {
155
155
  }
156
156
 
157
157
  /**
158
- * The 3 platform-owned widget slots (D5 Chunk A). Mirrors the `widgets[].placement`
158
+ * The 3 platform-owned widget slots. Mirrors the `widgets[].placement`
159
159
  * enum in `docs/private-apps/contract/tot-app.schema.json` — widgets render in a
160
160
  * platform-managed sandboxed iframe at one of these fixed slots, never as
161
161
  * merchant-pasted script at an app-chosen location.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Scope validation — the compliance-floor gate for Storefront Private Apps
3
- * (PrivateApps epic, D2). See `docs/private-apps/feature-brief.md` §"The
2
+ * Scope validation — the compliance-floor gate for Storefront Private Apps.
3
+ * See `docs/private-apps/feature-brief.md` §"The
4
4
  * compliance floor" and the machine-readable contract:
5
5
  * - `docs/private-apps/contract/scopes.json` — the 8-scope V1 catalog.
6
6
  * - `docs/private-apps/contract/forbidden-scopes.json` — the 8 ids that map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * OAuth2 client-credentials token issuer — the Storefront App Gateway's
3
- * `POST /oauth/token` grant (PrivateApps epic, D2 Chunk B). Mints the
4
- * short-lived RS256 JWT a private app presents as a bearer token to the D3
3
+ * `POST /oauth/token` grant. Mints the
4
+ * short-lived RS256 JWT a private app presents as a bearer token to the
5
5
  * scoped APIs.
6
6
  *
7
7
  * `tenant` and `env` are supplied by the CALLER (the route, from
@@ -97,8 +97,8 @@ export class AppTokenIssuer {
97
97
  tenant: install.tenantId,
98
98
  env: install.env,
99
99
  scopes: install.scopes,
100
- // Discriminates this bearer token from a widget launch token (D5 Chunk
101
- // B) so the two can never be replayed for each other even though both
100
+ // Discriminates this bearer token from a widget launch token so the two
101
+ // can never be replayed for each other even though both
102
102
  // are RS256 JWTs signed by the same gateway key — see tokenVerifier.ts.
103
103
  token_use: "access",
104
104
  })
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Offline verification of the Storefront App Gateway's own OAuth2
3
- * client-credentials JWT (PrivateApps epic, D2 Chunk B) — this is what D3
4
- * imports at the scoped-API boundary to authorize an app's request.
3
+ * client-credentials JWT — this is what the scoped-API boundary
4
+ * imports to authorize an app's request.
5
5
  *
6
6
  * Modeled directly on `../auth/identityToken.ts`'s `IdentityAssertionVerifier`
7
7
  * (same `jwtVerify` + JWKS-by-`kid` shape, `aud`/`iss` pinning, alg allowlist),
@@ -25,7 +25,7 @@ import {
25
25
  } from "./gatewayKeys.js";
26
26
  import { readEnv } from "@/lib/env";
27
27
 
28
- /** The verified app-token claims (D3's authorization input). */
28
+ /** The verified app-token claims (the scoped API's authorization input). */
29
29
  export interface AppTokenClaims {
30
30
  iss: string;
31
31
  sub: string; // install id
@@ -40,8 +40,8 @@ export interface AppTokenClaims {
40
40
 
41
41
  /** Normalize a jose JWT payload into typed {@link AppTokenClaims}, throwing on any missing claim. */
42
42
  function toAppTokenClaims(payload: Record<string, unknown>): AppTokenClaims {
43
- // The security-critical discriminator: a widget launch token (D5 Chunk B,
44
- // `token_use: "widget_launch"`) is signed by this SAME gateway key, so
43
+ // The security-critical discriminator: a widget launch token
44
+ // (`token_use: "widget_launch"`) is signed by this SAME gateway key, so
45
45
  // signature/aud/iss/exp checks alone can't tell it apart from a bearer
46
46
  // token. Requiring `token_use === "access"` here is what stops a launch
47
47
  // token — minted for a sandboxed iframe, short-TTL, no scopes checked —
@@ -128,7 +128,7 @@ export class AppTokenVerifier {
128
128
  /**
129
129
  * Get (or build) the verifier for one tenant, from the gateway's own
130
130
  * locally-derived JWKS. Returns `null` when the signing key isn't configured
131
- * — callers (D3 routes) fail closed with 503, mirroring `getIdentityVerifier`.
131
+ * — callers (the scoped-API routes) fail closed with 503, mirroring `getIdentityVerifier`.
132
132
  * Not cached across calls (unlike `identityToken.ts`'s remote-fetch verifier):
133
133
  * deriving the local JWKS is cheap (`gatewayKeys.ts` caches the export itself).
134
134
  */
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Widget eligibility — PrivateApps epic, D5 (Chunk C). Mirrors
2
+ * Widget eligibility. Mirrors
3
3
  * `webhooks/emit.ts`'s `isEligibleTarget` discipline: an install is eligible
4
4
  * to render its widget at `slot` only while it is LIVE — `active`, scoped for
5
5
  * `widgets:launch`, and carrying a validated placement for that slot. This
6
6
  * live check is what makes suspending an install actually stop it rendering
7
- * on the very next page load — the D8 "suspension kills widgets"
7
+ * on the very next page load — the "suspension kills widgets"
8
8
  * enforcement — with no separate revoke step; it's just this gate re-run on
9
9
  * every request.
10
10
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Pure prop-building for `<AppWidgetFrame>` PrivateApps epic, D5 (Chunk D).
3
- * Split out of the `.astro` component so the query-param handoff and the
4
- * sandbox attrs are unit-testable: `vitest.config.ts` deliberately does NOT
2
+ * Pure prop-building for `<AppWidgetFrame>`. The query-param handoff and the
3
+ * sandbox attrs live here (not in the `.astro` component) so they are
4
+ * unit-testable: `vitest.config.ts` deliberately does NOT
5
5
  * render `.astro` components (Astro Container API breaks under vitest@2 +
6
6
  * astro@7 — see that file's docstring), so render *logic* lives here and
7
7
  * rendered output is verified on a live preview instead.
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Widget launch-token minting — PrivateApps epic, D5 Chunk B. Mints the
2
+ * Widget launch-token minting. Mints the
3
3
  * short-lived RS256 JWT a platform-managed sandboxed widget iframe carries at
4
- * SSR render time: Chunk D calls `mintWidgetLaunchToken` while rendering the
4
+ * SSR render time: the page-render path calls `mintWidgetLaunchToken` while rendering the
5
5
  * page for each declared placement, and the resulting token is what the
6
6
  * widget's own endpoint receives as proof it was launched by THIS platform,
7
7
  * for THIS install, at THIS placement — never a merchant-forged or replayed
@@ -31,10 +31,10 @@ import {
31
31
  export interface MintWidgetLaunchTokenInput {
32
32
  /**
33
33
  * Only `installId`/`tenantId`/`env` are read. Narrowed from `AppInstall` to
34
- * this `Pick` so Chunk D's render path — which has `placements.ts`'s
34
+ * this `Pick` so the render path — which has `placements.ts`'s
35
35
  * `ResolvedPlacement` (installId/appOrigin/endpoint), not the full
36
36
  * `AppInstall` — can build the minimal object itself instead of re-fetching
37
- * the install; a full `AppInstall` still satisfies this shape unchanged.
37
+ * the install; a full `AppInstall` still satisfies this shape.
38
38
  */
39
39
  install: Pick<AppInstall, "installId" | "tenantId" | "env">;
40
40
  slot: WidgetPlacement;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Widget placement resolution — PrivateApps epic, D5 (Chunk C). Turns "which
3
- * apps have a live widget at this slot" into the concrete list Chunk D needs
2
+ * Widget placement resolution. Turns "which
3
+ * apps have a live widget at this slot" into the concrete list the render path needs
4
4
  * to render sandboxed iframes and mint launch tokens: reads
5
5
  * `registry.listByTenant`, filters to the requested `env` and
6
6
  * {@link isWidgetEligible}, then reads each eligible install's `endpoint`
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Widget slot render composition — PrivateApps epic, D5 (Chunk D). The one
2
+ * Widget slot render composition. The one
3
3
  * call a page/layout makes to turn "render slot X for this request" into the
4
4
  * concrete, launch-token-bearing list `AppWidgetFrame` needs: resolves live
5
5
  * placements (`placements.ts`), mints each one a launch token
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Admin session-entry resolution (unit dg6) — the SERVER-SIDE decision that puts
2
+ * Admin session-entry resolution — the SERVER-SIDE decision that puts
3
3
  * an authenticated owner/team member into the CORRECT tenant admin context, and
4
4
  * keeps a non-member / unauthenticated visitor on the legitimate sign-in gate.
5
5
  *
6
- * NARROW to auth/session ENTRY: it CONSUMES the go-live admin tab UX
7
- * (`pages/admin.astro` + `components/admin/AdminPublishTab.astro`) and u10's
6
+ * NARROW to auth/session ENTRY: it CONSUMES the admin tab UX
7
+ * (`pages/admin.astro` + `components/admin/AdminPublishTab.astro`) and the
8
8
  * ship-capability model (`change/shipCapabilities` + `change/route` decideIsOwner)
9
9
  * rather than reimplementing either. Everything it resolves is bound to the
10
10
  * RESOLVED tenant, never taken from client input, and never requires a
@@ -14,15 +14,15 @@
14
14
  * - `hostCapability` — the viewer's owner/admin/member standing FOR THIS host,
15
15
  * from {@link sessionHostCapability} (tenants[] membership → resource-scoped
16
16
  * session → live staff selection). null ⇒ not a member of this tenant.
17
- * - `isOwnerViewer` / `shipCapabilities` — u10's {@link decideIsOwner} +
17
+ * - `isOwnerViewer` / `shipCapabilities` — {@link decideIsOwner} +
18
18
  * {@link resolveShipCapabilities} over that host-bound capability. This is the
19
- * OWNER VIEWER identity (decision `owner-identity-narrow-viewer-not-bearer-console`):
19
+ * OWNER VIEWER identity:
20
20
  * a signed-in owner/admin viewer over the resolved tenant, NOT the bearer
21
21
  * operator console. The ship-axis `capability` is the fail-closed
22
22
  * `preview-access` — a viewer session carries no operator ship grant, so the
23
23
  * owner is the ONLY source of `shipOnBehalf`/`apexCutover` here.
24
24
  *
25
- * The distinguishing logic the go-live "definition of done" demands: `admitted`
25
+ * The distinguishing logic: `admitted`
26
26
  * separates an authenticated OWNER/member (a host-bound capability, or ToT-staff
27
27
  * scope) — who must NEVER see the "Coming soon" holding page at their own admin —
28
28
  * from an authenticated NON-member and an anonymous visitor, who legitimately do
@@ -56,9 +56,9 @@ export interface AdminEntryDecision {
56
56
  admitted: boolean;
57
57
  /** The owner/admin/member standing bound to this host, or null. */
58
58
  hostCapability: string | null;
59
- /** u10: the acting viewer IS the tenant owner/admin (superset of ship-on-behalf). */
59
+ /** The acting viewer IS the tenant owner/admin (superset of ship-on-behalf). */
60
60
  isOwnerViewer: boolean;
61
- /** u10: the two derived ship-gate capabilities for this viewer entry. */
61
+ /** The two derived ship-gate capabilities for this viewer entry. */
62
62
  shipCapabilities: ShipCapabilities;
63
63
  /** Which of the four entry cases this is. */
64
64
  principal: AdminEntryPrincipal;
@@ -90,7 +90,7 @@ export function resolveAdminEntry(input: {
90
90
  const staffScope = Array.isArray(record?.staff) && record!.staff.length > 0;
91
91
  const admitted = hostCapability !== null || staffScope;
92
92
 
93
- // u10 owner resolution — the OWNER VIEWER, not the bearer console: authorized
93
+ // Owner resolution — the OWNER VIEWER, not the bearer console: authorized
94
94
  // (a member/staff viewer of THIS tenant) AND holding an owner/admin capability.
95
95
  const isOwnerViewer = decideIsOwner({
96
96
  authorized: admitted,