@tokenoftrust/storefront-runner 1.4.0 → 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 (193) 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/0001_woozy_lyja.sql +22 -0
  4. package/apps/storefront/migrations-apps/0002_good_switch.sql +26 -0
  5. package/apps/storefront/migrations-apps/0003_mute_marauders.sql +12 -0
  6. package/apps/storefront/migrations-apps/meta/0001_snapshot.json +990 -0
  7. package/apps/storefront/migrations-apps/meta/0002_snapshot.json +1160 -0
  8. package/apps/storefront/migrations-apps/meta/0003_snapshot.json +1235 -0
  9. package/apps/storefront/migrations-apps/meta/_journal.json +21 -0
  10. package/apps/storefront/package.json +0 -1
  11. package/apps/storefront/public/js/dashboard-team.js +38 -2
  12. package/apps/storefront/src/components/Seo.astro +65 -1
  13. package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +878 -0
  14. package/apps/storefront/src/components/admin/AdminBlogTab.astro +1432 -0
  15. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +3 -7
  16. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1756 -1382
  17. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +1 -3
  18. package/apps/storefront/src/components/blog/BlogPaywall.astro +87 -0
  19. package/apps/storefront/src/components/blog/PostAccessBadge.astro +43 -0
  20. package/apps/storefront/src/components/blog/PostGrid.astro +57 -0
  21. package/apps/storefront/src/components/blog/ReactionBar.astro +214 -0
  22. package/apps/storefront/src/layouts/Layout.astro +48 -3
  23. package/apps/storefront/src/lib/activity/alerts.ts +426 -0
  24. package/apps/storefront/src/lib/activity/changeActorAttribution.ts +85 -0
  25. package/apps/storefront/src/lib/activity/deployVersion.ts +120 -0
  26. package/apps/storefront/src/lib/activity/ingest.ts +188 -0
  27. package/apps/storefront/src/lib/activity/ingestAuth.ts +105 -0
  28. package/apps/storefront/src/lib/activity/killSwitch.ts +80 -0
  29. package/apps/storefront/src/lib/activity/query.ts +403 -0
  30. package/apps/storefront/src/lib/activity/recordActivity.ts +101 -0
  31. package/apps/storefront/src/lib/activity/store.ts +120 -0
  32. package/apps/storefront/src/lib/activity/uiActor.ts +150 -0
  33. package/apps/storefront/src/lib/activity/workerCommit.ts +70 -0
  34. package/apps/storefront/src/lib/admin/adminShell.ts +290 -0
  35. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +2 -3
  36. package/apps/storefront/src/lib/admin/ai/audit.ts +3 -5
  37. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +7 -7
  38. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +3 -3
  39. package/apps/storefront/src/lib/admin/orderClient.ts +3 -5
  40. package/apps/storefront/src/lib/admin/services/orders.ts +17 -19
  41. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +4 -6
  42. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +1 -1
  43. package/apps/storefront/src/lib/apps/adminService.ts +4 -4
  44. package/apps/storefront/src/lib/apps/adminSession.ts +53 -44
  45. package/apps/storefront/src/lib/apps/apiAuth.ts +2 -2
  46. package/apps/storefront/src/lib/apps/apiRoute.ts +2 -2
  47. package/apps/storefront/src/lib/apps/catalogMapper.ts +1 -1
  48. package/apps/storefront/src/lib/apps/credentials.ts +2 -2
  49. package/apps/storefront/src/lib/apps/gatewayKeys.ts +4 -4
  50. package/apps/storefront/src/lib/apps/healthAggregate.ts +5 -5
  51. package/apps/storefront/src/lib/apps/orders/attributionService.ts +2 -2
  52. package/apps/storefront/src/lib/apps/orders/customerHash.ts +1 -1
  53. package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +12 -12
  54. package/apps/storefront/src/lib/apps/orders/idempotency.ts +7 -7
  55. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +1 -1
  56. package/apps/storefront/src/lib/apps/orders/orderMapper.ts +2 -2
  57. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +6 -6
  58. package/apps/storefront/src/lib/apps/registryService.ts +6 -6
  59. package/apps/storefront/src/lib/apps/scopes.ts +2 -2
  60. package/apps/storefront/src/lib/apps/tokenIssuer.ts +4 -4
  61. package/apps/storefront/src/lib/apps/tokenVerifier.ts +6 -6
  62. package/apps/storefront/src/lib/apps/widgets/eligibility.ts +2 -2
  63. package/apps/storefront/src/lib/apps/widgets/frameProps.ts +3 -3
  64. package/apps/storefront/src/lib/apps/widgets/launchToken.ts +4 -4
  65. package/apps/storefront/src/lib/apps/widgets/placements.ts +2 -2
  66. package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +1 -1
  67. package/apps/storefront/src/lib/auth/adminEntry.ts +9 -9
  68. package/apps/storefront/src/lib/auth/identityToken.ts +6 -14
  69. package/apps/storefront/src/lib/auth/loginGate.ts +75 -13
  70. package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +6 -4
  71. package/apps/storefront/src/lib/auth/operatorSession.ts +113 -0
  72. package/apps/storefront/src/lib/auth/route.ts +44 -4
  73. package/apps/storefront/src/lib/auth/session.ts +7 -8
  74. package/apps/storefront/src/lib/auth/totAccessClient.ts +8 -18
  75. package/apps/storefront/src/lib/basePath.ts +9 -2
  76. package/apps/storefront/src/lib/blog/access.ts +29 -0
  77. package/apps/storefront/src/lib/blog/collection.ts +313 -0
  78. package/apps/storefront/src/lib/blog/markdown.ts +174 -0
  79. package/apps/storefront/src/lib/blog/provider.ts +5 -0
  80. package/apps/storefront/src/lib/blog/reactions.ts +87 -0
  81. package/apps/storefront/src/lib/blog/rss.ts +92 -0
  82. package/apps/storefront/src/lib/blog/teaser.ts +83 -0
  83. package/apps/storefront/src/lib/blog/types.ts +34 -0
  84. package/apps/storefront/src/lib/blog/viewCounts.ts +243 -0
  85. package/apps/storefront/src/lib/d1/catalog.ts +2 -2
  86. package/apps/storefront/src/lib/d1/schema-apps.ts +147 -14
  87. package/apps/storefront/src/lib/dev/activityIngestToken.ts +2 -4
  88. package/apps/storefront/src/lib/dev/activityStore.ts +3 -5
  89. package/apps/storefront/src/lib/dev/apiBase.ts +1 -4
  90. package/apps/storefront/src/lib/dev/cockpitStore.ts +1 -9
  91. package/apps/storefront/src/lib/dev/devDraft.ts +1 -1
  92. package/apps/storefront/src/lib/dev/previewStatus.ts +9 -21
  93. package/apps/storefront/src/lib/dev/rendezvousBroker.ts +9 -24
  94. package/apps/storefront/src/lib/edgeCache.ts +17 -0
  95. package/apps/storefront/src/lib/env.ts +51 -0
  96. package/apps/storefront/src/lib/http/fetchTransport.ts +63 -0
  97. package/apps/storefront/src/lib/i18n.ts +25 -0
  98. package/apps/storefront/src/lib/jsonld.ts +118 -0
  99. package/apps/storefront/src/lib/monitoring/manifest.ts +11 -11
  100. package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +30 -0
  101. package/apps/storefront/src/lib/newsletter/consent.ts +23 -0
  102. package/apps/storefront/src/lib/newsletter/digest.ts +117 -0
  103. package/apps/storefront/src/lib/newsletter/resendClient.ts +79 -0
  104. package/apps/storefront/src/lib/newsletter/send.ts +265 -0
  105. package/apps/storefront/src/lib/newsletter/subscribers.ts +156 -0
  106. package/apps/storefront/src/lib/publish/adminPublishTab.ts +3519 -0
  107. package/apps/storefront/src/lib/publish/apex-readiness.ts +41 -18
  108. package/apps/storefront/src/lib/publish/dispatchHealth.ts +5 -7
  109. package/apps/storefront/src/lib/publish/domainState.ts +22 -13
  110. package/apps/storefront/src/lib/publish/panelPolling.ts +78 -0
  111. package/apps/storefront/src/lib/publish/shipWorkspace.ts +100 -42
  112. package/apps/storefront/src/lib/publish/stageBundleUpload.ts +215 -0
  113. package/apps/storefront/src/lib/rate/kvWindow.ts +53 -0
  114. package/apps/storefront/src/lib/search/index.ts +2 -2
  115. package/apps/storefront/src/lib/seo/alternates.ts +42 -0
  116. package/apps/storefront/src/lib/seo/meta.ts +65 -0
  117. package/apps/storefront/src/lib/social/golive.ts +212 -0
  118. package/apps/storefront/src/lib/social/notify.ts +32 -0
  119. package/apps/storefront/src/lib/social/tokenStore.ts +171 -0
  120. package/apps/storefront/src/lib/the-build/provider.ts +27 -0
  121. package/apps/storefront/src/lib/the-build/readingTime.ts +35 -0
  122. package/apps/storefront/src/lib/the-build/toc.ts +95 -0
  123. package/apps/storefront/src/lib/tot/ToTClient.ts +1 -1
  124. package/apps/storefront/src/lib/tot/d1Client.ts +2 -2
  125. package/apps/storefront/src/lib/tot/totClientInterface.ts +3 -4
  126. package/apps/storefront/src/lib/webhooks/deliveryStore.ts +16 -17
  127. package/apps/storefront/src/middleware/index.ts +130 -2
  128. package/apps/storefront/src/pages/admin/ops-timeline.astro +420 -0
  129. package/apps/storefront/src/pages/admin.astro +52 -1206
  130. package/apps/storefront/src/pages/api/activity.ts +137 -0
  131. package/apps/storefront/src/pages/api/admin/activity-alerts.ts +73 -0
  132. package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +27 -1
  133. package/apps/storefront/src/pages/api/apps/admin/install.ts +26 -1
  134. package/apps/storefront/src/pages/api/apps/admin/resume.ts +24 -1
  135. package/apps/storefront/src/pages/api/apps/admin/suspend.ts +24 -1
  136. package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +25 -1
  137. package/apps/storefront/src/pages/api/apps/admin/update.ts +24 -1
  138. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +11 -0
  139. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +11 -0
  140. package/apps/storefront/src/pages/api/apps/v1/attribution.ts +20 -0
  141. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +12 -0
  142. package/apps/storefront/src/pages/api/blog/[collection]/[slug]/react.ts +104 -0
  143. package/apps/storefront/src/pages/api/cache-purge.ts +11 -0
  144. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +30 -0
  145. package/apps/storefront/src/pages/api/newsletter/[collection]/subscribe.ts +146 -0
  146. package/apps/storefront/src/pages/api/newsletter/confirm.ts +75 -0
  147. package/apps/storefront/src/pages/api/newsletter/unsubscribe.ts +90 -0
  148. package/apps/storefront/src/pages/api/publish/stage-bundle.ts +70 -0
  149. package/apps/storefront/src/pages/api/rum/vitals.ts +21 -2
  150. package/apps/storefront/src/pages/auth/login.astro +5 -4
  151. package/apps/storefront/src/pages/blog/[slug].astro +96 -16
  152. package/apps/storefront/src/pages/blog/author/[author].astro +140 -0
  153. package/apps/storefront/src/pages/blog/category/[category].astro +67 -0
  154. package/apps/storefront/src/pages/blog/index.astro +2 -0
  155. package/apps/storefront/src/pages/blog/tag/[tag].astro +65 -0
  156. package/apps/storefront/src/pages/cockpit.astro +74 -12
  157. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +15 -11
  158. package/apps/storefront/src/pages/search.astro +1 -1
  159. package/apps/storefront/src/pages/sitemap.xml.ts +20 -0
  160. package/apps/storefront/src/pages/the-build/[slug].astro +129 -0
  161. package/apps/storefront/src/pages/the-build/index.astro +146 -0
  162. package/apps/storefront/src/pages/the-build/rss.xml.ts +33 -0
  163. package/apps/storefront/src/styles/admin.css +1080 -0
  164. package/apps/storefront/tsconfig.json +5 -0
  165. package/package.json +1 -1
  166. package/packages/public-runtime/package.json +1 -0
  167. package/packages/public-runtime/src/activity/README.md +146 -0
  168. package/packages/public-runtime/src/activity/catalog.ts +553 -0
  169. package/packages/public-runtime/src/activity/event.ts +168 -0
  170. package/packages/public-runtime/src/activity/index.ts +16 -0
  171. package/packages/public-runtime/src/activity/redaction.ts +263 -0
  172. package/packages/public-runtime/src/candidate-index.ts +13 -0
  173. package/packages/public-runtime/src/catalog-d1.ts +1 -1
  174. package/packages/public-runtime/src/customization-preview.ts +25 -2
  175. package/packages/public-runtime/src/customization-runtime.ts +125 -3
  176. package/packages/public-runtime/src/customization-versioning.ts +10 -0
  177. package/packages/public-runtime/src/index.ts +7 -0
  178. package/packages/public-runtime/src/static-bundle.ts +357 -0
  179. package/packages/public-runtime/src/tenant.ts +36 -8
  180. package/scripts/build/copy-tenant-assets.mjs +14 -4
  181. package/scripts/dev/ai-edit.mjs +1 -1
  182. package/scripts/dev/checkout-watch.mjs +2 -2
  183. package/scripts/dev/file-browser.mjs +2 -4
  184. package/scripts/dev/file-writer.mjs +1 -1
  185. package/scripts/dev/git-status.mjs +2 -2
  186. package/scripts/dev/locate-handler.mjs +3 -3
  187. package/scripts/dev/port-check.mjs +3 -3
  188. package/scripts/dev/publish.mjs +5 -9
  189. package/scripts/dev/shot.mjs +1 -1
  190. package/scripts/dev/unified-diff.mjs +8 -3
  191. package/scripts/tot-dev.mjs +6 -9
  192. package/apps/storefront/src/lib/webhooks/signing.ts +0 -29
  193. package/apps/storefront/src/lib/webhooks/webhookSigningKey.ts +0 -146
@@ -5,13 +5,11 @@
5
5
  *
6
6
  * Follows the scripts/dev delegation pattern (see ai-edit.mjs): all logic lives
7
7
  * here, dependency-free and testable under `node --test`; a thin dev-only Vite
8
- * plugin (dev-plugins/dev-file-browser.mjs — added when C0/C1 integrate) maps
8
+ * plugin (dev-plugins/dev-file-browser.mjs) maps
9
9
  * req/res onto `handleTree` / `handleFile`. Every path is routed through
10
10
  * safe-path.mjs; the source root comes from tenant-source-root.mjs (never SSR).
11
11
  *
12
- * Git-status badges (C3 acceptance) layer on when C4's git-status.mjs lands —
13
- * the tree here carries structure only, so this module owns no git surface and
14
- * doesn't collide with C4.
12
+ * The tree here carries structure only, so this module owns no git surface.
15
13
  */
16
14
  import { readFileSync, readdirSync, statSync } from "node:fs";
17
15
  import { join, extname, sep } from "node:path";
@@ -123,7 +123,7 @@ export function handleWrite(env = process.env, body = {}) {
123
123
  return { status: 400, json: { ok: false, status: "no_path", message: "path is required" } };
124
124
  }
125
125
 
126
- const result = writeSourceFile(root.sourceRoot, relPath, body.content, {
126
+ const result = writeSourceFile(root.sourceRoot, relPath, /** @type {string} */ (body.content), {
127
127
  baseVersion: body ? body.baseVersion : undefined,
128
128
  });
129
129
  const status = result.ok
@@ -118,10 +118,10 @@ export function computeDiff(gitCwd, sourceRoot, relPath) {
118
118
  } catch {
119
119
  workingExists = false;
120
120
  }
121
- if (workingExists && st.isDirectory()) {
121
+ if (st && st.isDirectory()) {
122
122
  return { ok: false, status: "is_dir", message: `${relPath} is a directory` };
123
123
  }
124
- if (workingExists) {
124
+ if (workingExists && st) {
125
125
  if (BINARY_EXT.has(extname(posix).toLowerCase())) {
126
126
  return { ok: true, path: posix, status: fileStatus(gitCwd, relPath), shown: false, reason: "binary" };
127
127
  }
@@ -3,12 +3,12 @@
3
3
  * POST /__tot/locate {domPath, path} → {ok, path, line, col} | {status}
4
4
  * POST /__tot/open-in-editor {path, line, col} → {ok} | {status}
5
5
  *
6
- * Keeps locate.mjs a PURE parse5 walk (already integrated + tested 5/5); this module
6
+ * Keeps locate.mjs a PURE parse5 walk; this module
7
7
  * adds the env/path-resolution/refusal concerns and is itself unit-testable under
8
8
  * `node --test` with no running dev server (the scripts/dev delegation pattern — see
9
9
  * file-browser.mjs). The thin dev-plugins/dev-locate.mjs wrapper maps req/res onto it.
10
10
  *
11
- * HONORS decision `click-to-source-anchors-at-content-region`: the served page wraps
11
+ * The served page wraps
12
12
  * the raw content region (<main>, the whole of content/home.html) in the storefront's
13
13
  * own chrome (skip-link, header, nav, footer) that is NOT in the tenant source. So we
14
14
  * resolve the clicked URL to its raw content source and walk THAT from <body>:
@@ -90,7 +90,7 @@ export function handleLocate(env = process.env, body = {}) {
90
90
 
91
91
  const html = readFileSync(abs, "utf8");
92
92
  const found = locate(html, body.domPath);
93
- if (typeof found.line === "number") {
93
+ if ("line" in found && typeof found.line === "number") {
94
94
  return { status: 200, json: { ok: true, path: rel, line: found.line, col: found.col } };
95
95
  }
96
96
  // The walk missed: the element is outside the raw content region — i.e. the storefront
@@ -8,9 +8,9 @@ import net from "node:net";
8
8
  * predicts astro's own bind on either IP family.
9
9
  *
10
10
  * WHY THIS EXISTS: Vite's `strictPort` defaults OFF, so when the requested port is
11
- * taken astro SILENTLY moves to the next free port. On 2026-07-14 that stranded the
12
- * CLI's liveness poll on the ORIGINAL port (answered by a stale foreign server) →
13
- * a false "✨ You're live" pointing at the wrong server. Pre-flighting the port and
11
+ * taken astro SILENTLY moves to the next free port stranding the CLI's liveness
12
+ * poll on the ORIGINAL port (answered by a stale foreign server) → a false
13
+ * "✨ You're live" pointing at the wrong server. Pre-flighting the port and
14
14
  * failing clearly removes that whole class of silent failure.
15
15
  *
16
16
  * Dependency-free (node:net only) so it copies into the standalone runner and is
@@ -20,15 +20,11 @@
20
20
  * Auth: the developer's own cached OAuth session (`tot login`,
21
21
  * `~/.tot/credentials.json`), via `packages/cli/src/auth.mjs`'s
22
22
  * `resolveDeveloperSession` — reads, silently refreshes, and attaches the
23
- * Bearer exactly as `tot submit` does. NOT the storefront-operator
24
- * TOT_API_KEY/TOT_SECRET_KEY path this module used before 2026-07-26:
25
- * tot-mcp went OAuth-first on 2026-07-23 ("Require OAuth before remote MCP
26
- * initialize"), which retired the old no-browser operator/credential_validate
27
- * trust path entirely every caller, including this one, now needs a real
28
- * OAuth bearer just to complete `initialize`. There is no service-account /
29
- * client-credentials grant on this MCP (confirmed against its OAuth
30
- * authorization-server metadata), so a human `tot login` (one-time; refresh
31
- * is silent afterward, ~30-day refresh tokens) is the only way in.
23
+ * Bearer exactly as `tot submit` does. Every caller needs a real OAuth bearer
24
+ * just to complete `initialize`; there is no service-account / client-credentials
25
+ * grant on this MCP (confirmed against its OAuth authorization-server metadata),
26
+ * so a human `tot login` (one-time; refresh is silent afterward, ~30-day refresh
27
+ * tokens) is the only way in.
32
28
  */
33
29
  import { createHash } from "node:crypto";
34
30
  import { readFile } from "./file-browser.mjs";
@@ -20,7 +20,7 @@ const CHROME_PATHS = [
20
20
  "/usr/bin/chromium-browser",
21
21
  "/usr/bin/chromium",
22
22
  ].filter(Boolean);
23
- const executablePath = CHROME_PATHS.find((p) => existsSync(p));
23
+ const executablePath = CHROME_PATHS.find((p) => !!p && existsSync(p));
24
24
 
25
25
  const args = process.argv.slice(2);
26
26
  const url = args[0];
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Minimal unified-diff applier for the inline-AI edit loop (WS5 G1).
3
3
  *
4
- * The MCP `content_edit` tool returns a per-file unified diff (`--- a/<path>` /
5
- * `+++ b/<path>` header, `@@ -l,c +l,c @@` hunks) rather than a full-file
4
+ * The MCP `content_edit` tool returns a per-file unified diff (a `---`/`+++`
5
+ * file header, then hunk headers of the form "-l,c +l,c") rather than a full-file
6
6
  * rewrite — this is what turns that diff back into the file's new content, so
7
7
  * `tot dev` can apply it, hot-reload, and undo it cleanly. Dependency-free
8
8
  * (matches the rest of the tenant tooling — see packages/cli/src/mcp.mjs).
@@ -12,10 +12,15 @@
12
12
  * that's exactly the shape content_edit's system prompt asks the model for.
13
13
  */
14
14
 
15
- /** One `@@ -l,c +l,c @@` hunk: the old/new starting line + its body lines. */
15
+ /**
16
+ * One hunk (its "-l,c +l,c" header): the old/new starting line + its body lines.
17
+ * @typedef {{ oldStart: number, body: string[] }} Hunk
18
+ */
16
19
  function parseHunks(diff) {
17
20
  const lines = diff.split(/\r?\n/);
21
+ /** @type {Hunk[]} */
18
22
  const hunks = [];
23
+ /** @type {Hunk | null} */
19
24
  let current = null;
20
25
  const hunkHeader = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
21
26
 
@@ -31,7 +31,7 @@
31
31
  * Vite trusts) and run a recursive `fs.watch` on the checkout that re-syncs each
32
32
  * changed file back into the in-tree copy on save — which is what fires Vite HMR.
33
33
  *
34
- * Two things learned the hard way (see the two-liners below):
34
+ * Two things to know (see the two-liners below):
35
35
  * - Tenant is selected by the URL path-prefix (`/<appDomain>/`), NOT by
36
36
  * DEFAULT_TENANT_ID — the @astrojs/cloudflare dev runtime (workerd) reads env
37
37
  * from .dev.vars/bindings, so a spawned process.env var never reaches it, and
@@ -76,9 +76,8 @@ const flag = (name, def) => {
76
76
 
77
77
  // `--version` / `-v` / `version` → print the runner's PUBLISHED version and exit.
78
78
  // Reads the emitted root package.json (build-runner.mjs stamps RUNNER_VERSION in
79
- // PUBLISH mode). This is the version the 2026-07-14 cache-staleness bug hid — the
80
- // runner kept being served from a stale public-0.1.0 dir with no way to see it —
81
- // so making it directly checkable (npm bin `storefront-runner`) is part of the fix.
79
+ // PUBLISH mode), so the runner's version is directly checkable (npm bin
80
+ // `storefront-runner`).
82
81
  if (argv[0] === "--version" || argv[0] === "-v" || argv[0] === "version") {
83
82
  let v = "0.0.0";
84
83
  try {
@@ -165,7 +164,7 @@ if (!domain) {
165
164
  // Records let us undo everything on exit (leave the tree exactly as found).
166
165
  /** @type {{ target: string, backup: string|null }[]} */
167
166
  const grafts = [];
168
- /** @type {import("node:fs").FSWatcher[]} */
167
+ /** @type {Array<{ close: () => void }>} */
169
168
  const watchers = [];
170
169
  let cleanedUp = false;
171
170
 
@@ -295,8 +294,7 @@ if (workspacePath) {
295
294
 
296
295
  // Resolve the port BEFORE printing a URL or booting astro. Vite's `strictPort`
297
296
  // defaults off, so a taken port makes astro SILENTLY drift to the next free one —
298
- // which then mismatches the URL we (and the CLI's liveness poll) advertise, and a
299
- // stale server on the original port produced a false "You're live" (2026-07-14).
297
+ // which then mismatches the URL we (and the CLI's liveness poll) advertise.
300
298
  // Instead we pick the first free port ourselves and PRINT it, so a busy default
301
299
  // port "just works" (better UX than failing). See port-check.mjs.
302
300
  const requestedPort = Number(flag("port", "4321"));
@@ -362,8 +360,7 @@ const spawnEnv = workspacePath
362
360
  // require pnpm at RUNTIME), so resolve astro's real entry through Node's own
363
361
  // resolver from the app package and exec it with our Node. No package-manager
364
362
  // binary, no .bin shims, and no coupling to the package's name (the build
365
- // renames @tot/storefront → @tot/storefront-runner in the shipped tree, which
366
- // the old `pnpm --filter <name> exec` had to track).
363
+ // renames @tot/storefront → @tot/storefront-runner in the shipped tree).
367
364
  const appDir = resolve(repoRoot, "apps/storefront");
368
365
  const astroEntry = resolve(
369
366
  dirname(createRequire(join(appDir, "package.json")).resolve("astro/package.json")),
@@ -1,29 +0,0 @@
1
- /**
2
- * HTTP Message Signatures (RFC 9421) + Content-Digest (RFC 9530) signer for
3
- * outbound webhook delivery (PrivateApps epic, D4 Chunk B).
4
- *
5
- * The implementation MOVED to `@tokenoftrust/private-apps-devkit`
6
- * (PrivateApps epic D6 Chunk B) so the `tot app` CLI harness (D6 Chunk D) can
7
- * verify webhook signatures without importing Astro-internal code — without
8
- * forking security-critical crypto into a second, drifting copy. This file
9
- * is now a thin re-export; the devkit package is the ONE source of truth.
10
- * See `packages/private-apps-devkit/src/signing.ts` for the full scope
11
- * notes (hand-rolled/narrowly-scoped, not a general httpsig implementation)
12
- * and `packages/private-apps-devkit/tests/signing.test.ts` for the RFC-vector
13
- * proofs. `signing.test.ts` alongside this file still imports from here —
14
- * unchanged — so it now exercises those same functions through this
15
- * re-export.
16
- */
17
- export {
18
- SIGNATURE_LABEL,
19
- WEBHOOK_SIGNATURE_COVERED_COMPONENTS,
20
- computeContentDigestSha256,
21
- buildSignatureParamsValue,
22
- buildSignatureBase,
23
- signWebhookRequest,
24
- verifyWebhookSignature,
25
- type SignWebhookRequestInput,
26
- type SignWebhookRequestResult,
27
- type WebhookVerifyResult,
28
- type VerifyWebhookSignatureInput,
29
- } from "@tokenoftrust/private-apps-devkit";
@@ -1,146 +0,0 @@
1
- /**
2
- * RS256 signing-key loader for outbound webhook delivery (PrivateApps epic,
3
- * D4 Chunk B) — the key `signing.ts` uses to attach HTTP Message Signatures
4
- * (RFC 9421) to every CloudEvents webhook POST.
5
- *
6
- * Mirrors `../apps/gatewayKeys.ts`'s `loadGatewaySigningConfig`/
7
- * `loadGatewayJwks` structure EXACTLY (import cache keyed on the base64
8
- * source, JWKS derivation that strips every private RSA field), but over a
9
- * SEPARATE key (`WEBHOOK_SIGNING_PRIVATE_KEY_B64`) with a separate kid
10
- * (`webhook-signing-1`) — per `d4-design-resolutions`, this key must never be
11
- * the same as the D2 gateway key. The gateway key attests requests an app
12
- * makes INTO the storefront (app → gateway trust); the webhook signing key
13
- * attests events the storefront pushes OUT to an app (gateway → app trust,
14
- * the opposite direction). Sharing a key would let a receiver's verification
15
- * key double as the ability to (offline) verify gateway tokens, and rotating
16
- * one would force rotating the other.
17
- *
18
- * The private key never leaves this module. `loadWebhookJwks` derives the
19
- * PUBLIC-only JWKS from it, published (Chunk B) via a new field on
20
- * `/.well-known/tot-app-authorization-metadata`.
21
- */
22
- import { importPKCS8, exportJWK, type CryptoKey, type JWK, type JSONWebKeySet } from "jose";
23
- import { readEnv } from "@/lib/env";
24
- // Re-exported (not re-defined) — `@tokenoftrust/private-apps-devkit`'s
25
- // signing.ts is the ONE source of truth for this constant since D6 Chunk B
26
- // (it's the `alg` its sign/verify functions produce/accept); this module
27
- // just keeps the historical import path (`./webhookSigningKey.js`) working.
28
- export { WEBHOOK_SIGNING_HTTPSIG_ALG } from "@tokenoftrust/private-apps-devkit";
29
-
30
- /** The only algorithm the webhook signer signs with (verifiers reject anything else). */
31
- export const WEBHOOK_SIGNING_ALG = "RS256";
32
-
33
- /** Default key id (overridable via `WEBHOOK_SIGNING_KID`). Names the published key. */
34
- export const DEFAULT_WEBHOOK_SIGNING_KID = "webhook-signing-1";
35
-
36
- /** Env keys the loader reads (server-only). */
37
- export const WEBHOOK_SIGNING_PRIVATE_KEY_ENV = "WEBHOOK_SIGNING_PRIVATE_KEY_B64";
38
- export const WEBHOOK_SIGNING_KID_ENV = "WEBHOOK_SIGNING_KID";
39
-
40
- /**
41
- * Thrown when the webhook signer can't load its signing key — the private
42
- * key env is absent/malformed. Callers FAIL CLOSED: with no key, no delivery
43
- * can be signed, and the metadata route omits the webhook JWKS field rather
44
- * than publish a key that will never actually sign anything.
45
- */
46
- export class WebhookSigningKeyError extends Error {}
47
-
48
- /** Optional test/DI overrides (avoids touching process env in unit tests). */
49
- export interface WebhookSigningKeyOverrides {
50
- /** base64 of the PKCS8 PEM private key (defaults to the env value). */
51
- privateKeyB64?: string;
52
- kid?: string;
53
- }
54
-
55
- export interface WebhookSigningConfig {
56
- key: CryptoKey;
57
- kid: string;
58
- }
59
-
60
- // Same cache shape as gatewayKeys.ts's signingKeyCache/publicJwkCache, keyed
61
- // on the base64 string so a rotated key transparently re-imports/re-derives.
62
- const signingKeyCache = new Map<string, CryptoKey>();
63
- const publicJwkCache = new Map<string, JWK>();
64
-
65
- async function importPrivateKey(privateKeyB64: string): Promise<CryptoKey> {
66
- const cached = signingKeyCache.get(privateKeyB64);
67
- if (cached) return cached;
68
- let pem: string;
69
- try {
70
- pem = Buffer.from(privateKeyB64, "base64").toString("utf8");
71
- } catch (cause) {
72
- throw new WebhookSigningKeyError(
73
- `webhook signing: ${WEBHOOK_SIGNING_PRIVATE_KEY_ENV} is not valid base64 ` +
74
- (cause instanceof Error ? cause.message : String(cause)),
75
- );
76
- }
77
- if (!/-----BEGIN (RSA )?PRIVATE KEY-----/.test(pem)) {
78
- throw new WebhookSigningKeyError(
79
- `webhook signing: decoded ${WEBHOOK_SIGNING_PRIVATE_KEY_ENV} is not a PEM private key`,
80
- );
81
- }
82
- let key: CryptoKey;
83
- try {
84
- // extractable: true — required so `loadWebhookJwks` below can derive the
85
- // public JWK from this same key, and so `signing.ts` can sign raw bytes
86
- // with `crypto.subtle.sign` directly (no JWS wrapping needed for RFC
87
- // 9421 — the signature is over the derived signature base, not a JWT).
88
- key = (await importPKCS8(pem, WEBHOOK_SIGNING_ALG, { extractable: true })) as CryptoKey;
89
- } catch (cause) {
90
- throw new WebhookSigningKeyError(
91
- `webhook signing: failed to import the RS256 private key ` +
92
- (cause instanceof Error ? cause.message : String(cause)),
93
- );
94
- }
95
- signingKeyCache.set(privateKeyB64, key);
96
- return key;
97
- }
98
-
99
- /**
100
- * Resolve the active signing config (key + kid) from env, or `overrides` in
101
- * tests. FAILS CLOSED: throws {@link WebhookSigningKeyError} when the private
102
- * key env is absent/malformed.
103
- */
104
- export async function loadWebhookSigningConfig(
105
- overrides: WebhookSigningKeyOverrides = {},
106
- ): Promise<WebhookSigningConfig> {
107
- const privateKeyB64 =
108
- overrides.privateKeyB64 ?? (await readEnv(WEBHOOK_SIGNING_PRIVATE_KEY_ENV));
109
- if (!privateKeyB64) {
110
- throw new WebhookSigningKeyError(
111
- `webhook signing: ${WEBHOOK_SIGNING_PRIVATE_KEY_ENV} is not configured (fail closed)`,
112
- );
113
- }
114
- const kid = overrides.kid ?? (await readEnv(WEBHOOK_SIGNING_KID_ENV)) ?? DEFAULT_WEBHOOK_SIGNING_KID;
115
- const key = await importPrivateKey(privateKeyB64);
116
- return { key, kid };
117
- }
118
-
119
- /**
120
- * Derive the PUBLIC JWKS for the active webhook signing key — sanitized
121
- * (every private RSA field stripped) and cached by the source private-key
122
- * base64. V1 always returns exactly one key; the envelope supports more
123
- * without a shape change later (rotation-with-overlap).
124
- */
125
- export async function loadWebhookJwks(
126
- overrides: WebhookSigningKeyOverrides = {},
127
- ): Promise<JSONWebKeySet> {
128
- const privateKeyB64 =
129
- overrides.privateKeyB64 ?? (await readEnv(WEBHOOK_SIGNING_PRIVATE_KEY_ENV));
130
- if (!privateKeyB64) {
131
- throw new WebhookSigningKeyError(
132
- `webhook signing: ${WEBHOOK_SIGNING_PRIVATE_KEY_ENV} is not configured (fail closed)`,
133
- );
134
- }
135
- const { key, kid } = await loadWebhookSigningConfig(overrides);
136
- let jwk = publicJwkCache.get(privateKeyB64);
137
- if (!jwk) {
138
- const full = (await exportJWK(key)) as Record<string, unknown>;
139
- // Strip every private RSA field (RFC 7518 §6.3.2) — only the public parts
140
- // (kty, n, e) are ever published. This line is the one place that matters.
141
- const { d: _d, p: _p, q: _q, dp: _dp, dq: _dq, qi: _qi, oth: _oth, ...publicOnly } = full;
142
- jwk = { ...publicOnly, kid, alg: WEBHOOK_SIGNING_ALG, use: "sig" };
143
- publicJwkCache.set(privateKeyB64, jwk);
144
- }
145
- return { keys: [jwk] };
146
- }