@agent-native/core 0.77.8 → 0.77.11

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 (216) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +35 -0
  3. package/corpus/core/package.json +3 -3
  4. package/corpus/core/src/a2a/task-store.ts +25 -3
  5. package/corpus/core/src/agent/harness/store.ts +89 -55
  6. package/corpus/core/src/agent/observational-memory/store.ts +40 -14
  7. package/corpus/core/src/agent/run-store.ts +116 -59
  8. package/corpus/core/src/application-state/store.ts +36 -2
  9. package/corpus/core/src/audit/store.ts +37 -6
  10. package/corpus/core/src/browser-sessions/store.ts +31 -12
  11. package/corpus/core/src/chat-threads/store.ts +67 -3
  12. package/corpus/core/src/checkpoints/store.ts +14 -3
  13. package/corpus/core/src/cli/create.ts +9 -0
  14. package/corpus/core/src/collab/awareness.ts +12 -9
  15. package/corpus/core/src/collab/client.ts +4 -2
  16. package/corpus/core/src/collab/storage.ts +20 -2
  17. package/corpus/core/src/db/ddl-guard.ts +112 -0
  18. package/corpus/core/src/extensions/store.ts +76 -3
  19. package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
  20. package/corpus/core/src/integrations/config-store.ts +19 -18
  21. package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
  22. package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
  23. package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
  24. package/corpus/core/src/integrations/remote-push-store.ts +75 -35
  25. package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
  26. package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
  27. package/corpus/core/src/mcp/connect-store.ts +53 -16
  28. package/corpus/core/src/mcp/oauth-store.ts +32 -7
  29. package/corpus/core/src/notifications/store.ts +20 -4
  30. package/corpus/core/src/oauth-tokens/store.ts +45 -2
  31. package/corpus/core/src/observability/store.ts +154 -47
  32. package/corpus/core/src/progress/store.ts +36 -21
  33. package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
  34. package/corpus/core/src/provider-api/custom-registry.ts +15 -4
  35. package/corpus/core/src/provider-api/quota-governor.ts +15 -3
  36. package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
  37. package/corpus/core/src/resources/store.ts +66 -31
  38. package/corpus/core/src/secrets/storage.ts +18 -22
  39. package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
  40. package/corpus/core/src/server/auth.ts +22 -4
  41. package/corpus/core/src/server/better-auth-instance.ts +49 -15
  42. package/corpus/core/src/server/embed-session.ts +19 -4
  43. package/corpus/core/src/server/identity-sso-store.ts +40 -11
  44. package/corpus/core/src/server/recap-image-store.ts +28 -9
  45. package/corpus/core/src/settings/store.ts +14 -19
  46. package/corpus/core/src/usage/store.ts +51 -16
  47. package/corpus/core/src/workspace-connections/store.ts +200 -22
  48. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
  49. package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
  50. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  51. package/corpus/templates/content/actions/list-trashed-content-databases.ts +36 -2
  52. package/corpus/templates/content/actions/move-document.ts +79 -33
  53. package/corpus/templates/content/actions/restore-content-database.ts +40 -3
  54. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +84 -2
  55. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +177 -49
  56. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +393 -219
  57. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +632 -467
  58. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +114 -14
  59. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +56 -15
  60. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +6 -5
  61. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +12 -4
  62. package/corpus/templates/content/app/global.css +9 -0
  63. package/corpus/templates/content/app/hooks/use-create-page.ts +36 -7
  64. package/corpus/templates/content/app/i18n-data.ts +140 -10
  65. package/corpus/templates/content/changelog/2026-06-25-comment-cards-now-track-their-highlighted-text-while-scrolli.md +6 -0
  66. package/corpus/templates/content/changelog/2026-06-25-comments-now-scroll-with-the-document-and-stay-below-the-pag.md +6 -0
  67. package/corpus/templates/content/changelog/2026-06-25-creating-a-database-from-the-slash-menu-no-longer-leaves-sta.md +6 -0
  68. package/corpus/templates/content/changelog/2026-06-25-sidebar-hover-controls-no-longer-leave-a-lingering-fade-when.md +6 -0
  69. package/corpus/templates/content/changelog/2026-06-25-sidebar-page-actions-are-now-easier-to-see-when-hovering-ina.md +6 -0
  70. package/corpus/templates/content/changelog/2026-06-25-text-selections-in-the-editor-no-longer-show-white-gaps-when.md +6 -0
  71. package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-has-a-one-click-page-link-copy-button.md +6 -0
  72. package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-shows-page-breadcrumbs-and-the-latest.md +6 -0
  73. package/corpus/templates/content/changelog/2026-06-25-the-page-command-now-leaves-a-notion-style-page-reference-an.md +6 -0
  74. package/corpus/templates/content/shared/nfm.ts +16 -0
  75. package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
  76. package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
  77. package/corpus/templates/plan/app/global.css +6 -11
  78. package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
  79. package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
  80. package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
  81. package/dist/a2a/task-store.d.ts.map +1 -1
  82. package/dist/a2a/task-store.js +19 -3
  83. package/dist/a2a/task-store.js.map +1 -1
  84. package/dist/agent/harness/store.d.ts.map +1 -1
  85. package/dist/agent/harness/store.js +63 -34
  86. package/dist/agent/harness/store.js.map +1 -1
  87. package/dist/agent/observational-memory/store.d.ts.map +1 -1
  88. package/dist/agent/observational-memory/store.js +30 -8
  89. package/dist/agent/observational-memory/store.js.map +1 -1
  90. package/dist/agent/run-store.d.ts.map +1 -1
  91. package/dist/agent/run-store.js +103 -46
  92. package/dist/agent/run-store.js.map +1 -1
  93. package/dist/application-state/store.d.ts.map +1 -1
  94. package/dist/application-state/store.js +28 -2
  95. package/dist/application-state/store.js.map +1 -1
  96. package/dist/audit/store.d.ts.map +1 -1
  97. package/dist/audit/store.js +18 -3
  98. package/dist/audit/store.js.map +1 -1
  99. package/dist/browser-sessions/store.d.ts.map +1 -1
  100. package/dist/browser-sessions/store.js +22 -7
  101. package/dist/browser-sessions/store.js.map +1 -1
  102. package/dist/chat-threads/store.d.ts.map +1 -1
  103. package/dist/chat-threads/store.js +51 -3
  104. package/dist/chat-threads/store.js.map +1 -1
  105. package/dist/checkpoints/store.d.ts.map +1 -1
  106. package/dist/checkpoints/store.js +12 -3
  107. package/dist/checkpoints/store.js.map +1 -1
  108. package/dist/cli/create.d.ts.map +1 -1
  109. package/dist/cli/create.js +9 -0
  110. package/dist/cli/create.js.map +1 -1
  111. package/dist/collab/awareness.d.ts +1 -1
  112. package/dist/collab/awareness.d.ts.map +1 -1
  113. package/dist/collab/awareness.js +12 -8
  114. package/dist/collab/awareness.js.map +1 -1
  115. package/dist/collab/client.d.ts.map +1 -1
  116. package/dist/collab/client.js +4 -3
  117. package/dist/collab/client.js.map +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/collab/storage.d.ts.map +1 -1
  120. package/dist/collab/storage.js +14 -2
  121. package/dist/collab/storage.js.map +1 -1
  122. package/dist/db/ddl-guard.d.ts +63 -0
  123. package/dist/db/ddl-guard.d.ts.map +1 -1
  124. package/dist/db/ddl-guard.js +85 -0
  125. package/dist/db/ddl-guard.js.map +1 -1
  126. package/dist/extensions/store.d.ts.map +1 -1
  127. package/dist/extensions/store.js +32 -3
  128. package/dist/extensions/store.js.map +1 -1
  129. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  130. package/dist/integrations/a2a-continuations-store.js +44 -24
  131. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  132. package/dist/integrations/config-store.d.ts.map +1 -1
  133. package/dist/integrations/config-store.js +17 -11
  134. package/dist/integrations/config-store.js.map +1 -1
  135. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  136. package/dist/integrations/pending-tasks-store.js +24 -16
  137. package/dist/integrations/pending-tasks-store.js.map +1 -1
  138. package/dist/integrations/remote-commands-store.d.ts.map +1 -1
  139. package/dist/integrations/remote-commands-store.js +29 -21
  140. package/dist/integrations/remote-commands-store.js.map +1 -1
  141. package/dist/integrations/remote-devices-store.d.ts.map +1 -1
  142. package/dist/integrations/remote-devices-store.js +33 -18
  143. package/dist/integrations/remote-devices-store.js.map +1 -1
  144. package/dist/integrations/remote-push-store.d.ts.map +1 -1
  145. package/dist/integrations/remote-push-store.js +53 -31
  146. package/dist/integrations/remote-push-store.js.map +1 -1
  147. package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
  148. package/dist/integrations/remote-run-events-store.js +26 -10
  149. package/dist/integrations/remote-run-events-store.js.map +1 -1
  150. package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
  151. package/dist/integrations/thread-mapping-store.js +10 -2
  152. package/dist/integrations/thread-mapping-store.js.map +1 -1
  153. package/dist/mcp/connect-store.d.ts.map +1 -1
  154. package/dist/mcp/connect-store.js +34 -16
  155. package/dist/mcp/connect-store.js.map +1 -1
  156. package/dist/mcp/oauth-store.d.ts.map +1 -1
  157. package/dist/mcp/oauth-store.js +22 -7
  158. package/dist/mcp/oauth-store.js.map +1 -1
  159. package/dist/notifications/store.d.ts.map +1 -1
  160. package/dist/notifications/store.js +15 -3
  161. package/dist/notifications/store.js.map +1 -1
  162. package/dist/oauth-tokens/store.d.ts.map +1 -1
  163. package/dist/oauth-tokens/store.js +33 -2
  164. package/dist/oauth-tokens/store.js.map +1 -1
  165. package/dist/observability/store.d.ts.map +1 -1
  166. package/dist/observability/store.js +72 -29
  167. package/dist/observability/store.js.map +1 -1
  168. package/dist/progress/store.d.ts.map +1 -1
  169. package/dist/progress/store.js +31 -16
  170. package/dist/progress/store.js.map +1 -1
  171. package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
  172. package/dist/provider-api/corpus-jobs-store.js +16 -5
  173. package/dist/provider-api/corpus-jobs-store.js.map +1 -1
  174. package/dist/provider-api/custom-registry.d.ts.map +1 -1
  175. package/dist/provider-api/custom-registry.js +14 -4
  176. package/dist/provider-api/custom-registry.js.map +1 -1
  177. package/dist/provider-api/quota-governor.d.ts.map +1 -1
  178. package/dist/provider-api/quota-governor.js +12 -3
  179. package/dist/provider-api/quota-governor.js.map +1 -1
  180. package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
  181. package/dist/provider-api/staged-datasets-store.js +16 -5
  182. package/dist/provider-api/staged-datasets-store.js.map +1 -1
  183. package/dist/resources/store.d.ts.map +1 -1
  184. package/dist/resources/store.js +56 -25
  185. package/dist/resources/store.js.map +1 -1
  186. package/dist/secrets/storage.d.ts.map +1 -1
  187. package/dist/secrets/storage.js +10 -14
  188. package/dist/secrets/storage.js.map +1 -1
  189. package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
  190. package/dist/server/agent-teams-run-queue.js +15 -4
  191. package/dist/server/agent-teams-run-queue.js.map +1 -1
  192. package/dist/server/auth.d.ts.map +1 -1
  193. package/dist/server/auth.js +16 -2
  194. package/dist/server/auth.js.map +1 -1
  195. package/dist/server/better-auth-instance.d.ts.map +1 -1
  196. package/dist/server/better-auth-instance.js +55 -21
  197. package/dist/server/better-auth-instance.js.map +1 -1
  198. package/dist/server/embed-session.d.ts.map +1 -1
  199. package/dist/server/embed-session.js +15 -4
  200. package/dist/server/embed-session.js.map +1 -1
  201. package/dist/server/identity-sso-store.d.ts.map +1 -1
  202. package/dist/server/identity-sso-store.js +30 -11
  203. package/dist/server/identity-sso-store.js.map +1 -1
  204. package/dist/server/recap-image-store.d.ts.map +1 -1
  205. package/dist/server/recap-image-store.js +21 -7
  206. package/dist/server/recap-image-store.js.map +1 -1
  207. package/dist/settings/store.d.ts.map +1 -1
  208. package/dist/settings/store.js +11 -13
  209. package/dist/settings/store.js.map +1 -1
  210. package/dist/usage/store.d.ts.map +1 -1
  211. package/dist/usage/store.js +36 -11
  212. package/dist/usage/store.js.map +1 -1
  213. package/dist/workspace-connections/store.d.ts.map +1 -1
  214. package/dist/workspace-connections/store.js +57 -7
  215. package/dist/workspace-connections/store.js.map +1 -1
  216. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/oauth-tokens/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDbExec, isPostgres, intType } from \"../db/client.js\";\nimport { widenIntColumnsToBigInt } from \"../db/widen-columns.js\";\nimport {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"../secrets/crypto.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Encrypt the token bundle (AES-256-GCM) before it goes to the `tokens`\n * column. OAuth access/refresh tokens are long-lived, high-value credentials;\n * encrypting at rest means a leaked DB backup / pg_dump / read replica no\n * longer exposes them in plaintext. {@link parseStoredTokens} decrypts\n * transparently on read.\n */\nfunction serializeTokens(tokens: Record<string, unknown>): string {\n return encryptSecretValue(JSON.stringify(tokens));\n}\n\n/**\n * Parse a stored `tokens` value. Encrypted rows are decrypted; rows written\n * before encryption — or mirrored from Better Auth's `account` table — are\n * plaintext JSON and read transparently (the `db-migrate-encrypt-oauth-tokens`\n * script re-encrypts them in place). A row that can't be decrypted (key\n * rotated / corrupt / tampered) is treated as empty rather than throwing into\n * every token lookup.\n */\nfunction parseStoredTokens(\n stored: string | null | undefined,\n): Record<string, unknown> {\n if (!stored) return {};\n if (isEncryptedSecretValue(stored)) {\n try {\n return JSON.parse(decryptSecretValue(stored));\n } catch {\n return {};\n }\n }\n try {\n return JSON.parse(stored);\n } catch {\n return {};\n }\n}\n\nfunction oauthTokensTable(): string {\n return isPostgres() ? \"public.oauth_tokens\" : \"oauth_tokens\";\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const table = oauthTokensTable();\n await client.execute(`\n CREATE TABLE IF NOT EXISTS ${table} (\n provider TEXT NOT NULL,\n account_id TEXT NOT NULL,\n owner TEXT,\n tokens TEXT NOT NULL,\n updated_at ${intType()} NOT NULL,\n PRIMARY KEY (provider, account_id)\n )\n `);\n // Migration: add owner column to existing tables\n try {\n await client.execute(`ALTER TABLE ${table} ADD COLUMN owner TEXT`);\n } catch {\n // Column already exists\n }\n // Migration: add display_name column\n try {\n await client.execute(\n `ALTER TABLE ${table} ADD COLUMN display_name TEXT`,\n );\n } catch {\n // Column already exists\n }\n // Backfill: set owner = account_id for existing rows without an owner\n await client.execute(\n `UPDATE ${table} SET owner = account_id WHERE owner IS NULL`,\n );\n // Older deployments have a 32-bit `updated_at`; on Postgres the\n // `Date.now()` written on every token save overflows int4. Widen in place\n // (no-op once done / on fresh DBs). Unqualified name — the helper scopes\n // to the `public` schema.\n await widenIntColumnsToBigInt(\"oauth_tokens\", [\"updated_at\"]);\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nexport async function getOAuthTokens(\n provider: string,\n accountId: string,\n): Promise<Record<string, unknown> | null> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT tokens FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n if (rows.length === 0) return null;\n return parseStoredTokens(rows[0].tokens as string);\n}\n\n/**\n * Thrown when an OAuth save would re-bind an `(provider, account_id)` row\n * to a different owner than already holds it. Callers should catch this and\n * surface a clean \"this account is already linked to another user\" message\n * to the requester rather than letting it propagate as a 500.\n *\n * Carries `statusCode = 409` so route handlers using h3's `createError` can\n * pass it straight through.\n */\nexport class OAuthAccountOwnedByOtherUserError extends Error {\n readonly statusCode = 409;\n readonly provider: string;\n readonly accountId: string;\n readonly existingOwner: string;\n readonly attemptedOwner: string;\n constructor(opts: {\n provider: string;\n accountId: string;\n existingOwner: string;\n attemptedOwner: string;\n }) {\n super(\n `OAuth account ${opts.provider}:${opts.accountId} is already linked to another user — refusing to overwrite the owner.`,\n );\n this.name = \"OAuthAccountOwnedByOtherUserError\";\n this.provider = opts.provider;\n this.accountId = opts.accountId;\n this.existingOwner = opts.existingOwner;\n this.attemptedOwner = opts.attemptedOwner;\n }\n}\n\n/**\n * Save OAuth tokens. The `owner` parameter specifies which user owns this\n * account — defaults to `accountId` (the account itself is the owner).\n * For multi-account support, pass the logged-in user's email as owner.\n *\n * If the account already exists and is owned by a different user, throws\n * `OAuthAccountOwnedByOtherUserError` (statusCode 409) to prevent silently\n * stealing another user's linked account.\n *\n * Read + write happen as a single linearised batch (Postgres) or paired\n * statements (SQLite). On both backends the per-row PK serialises concurrent\n * writes for the same `(provider, account_id)` so the owner check cannot be\n * raced by an attacker calling saveOAuthTokens twice in flight — the second\n * caller sees the first caller's owner row and raises 409.\n */\nexport async function saveOAuthTokens(\n provider: string,\n accountId: string,\n tokens: Record<string, unknown>,\n owner?: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n\n // Read the current row before deciding what to write. We use this to\n // (a) preserve owner / display_name when this is a token refresh (no\n // owner argument), and (b) reject the write when the caller is trying\n // to overwrite a row owned by someone else.\n let resolvedOwner = owner ?? accountId;\n let existingDisplayName: string | null = null;\n let existingOwner: string | null = null;\n let existingTokens: Record<string, unknown> | null = null;\n const { rows: existing } = await client.execute({\n sql: `SELECT owner, display_name, tokens FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n if (existing.length > 0) {\n existingOwner = (existing[0].owner as string) ?? null;\n existingDisplayName = (existing[0].display_name as string) ?? null;\n existingTokens = parseStoredTokens(existing[0].tokens as string);\n }\n\n if (!owner) {\n // Token-refresh path: keep the existing owner/displayName unchanged.\n if (existingOwner) resolvedOwner = existingOwner;\n } else if (existingOwner && owner && existingOwner !== owner) {\n // Refuse to silently re-bind an account from one user to another.\n // This is the case the docstring promised but the previous\n // implementation didn't enforce — `ON CONFLICT DO UPDATE SET\n // owner=EXCLUDED.owner` would have overwritten the prior owner.\n throw new OAuthAccountOwnedByOtherUserError({\n provider,\n accountId,\n existingOwner,\n attemptedOwner: owner,\n });\n }\n\n const cleanedIncomingTokens = Object.fromEntries(\n Object.entries(tokens).filter(([, value]) => value !== undefined),\n );\n const tokensToStore = {\n ...(existingTokens ?? {}),\n ...cleanedIncomingTokens,\n };\n\n await client.execute({\n sql: isPostgres()\n ? `INSERT INTO ${table} (provider, account_id, owner, display_name, tokens, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (provider, account_id) DO UPDATE SET owner=EXCLUDED.owner, display_name=COALESCE(EXCLUDED.display_name, ${table}.display_name), tokens=EXCLUDED.tokens, updated_at=EXCLUDED.updated_at`\n : `INSERT OR REPLACE INTO ${table} (provider, account_id, owner, display_name, tokens, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,\n args: [\n provider,\n accountId,\n resolvedOwner,\n existingDisplayName,\n serializeTokens(tokensToStore),\n Date.now(),\n ],\n });\n}\n\nexport async function deleteOAuthTokens(\n provider: string,\n accountId?: string,\n): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n if (accountId) {\n const result = await client.execute({\n sql: `DELETE FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n return result.rowsAffected;\n }\n const result = await client.execute({\n sql: `DELETE FROM ${table} WHERE provider = ?`,\n args: [provider],\n });\n return result.rowsAffected;\n}\n\nexport async function listOAuthAccounts(provider: string): Promise<\n Array<{\n accountId: string;\n owner: string | null;\n tokens: Record<string, unknown>;\n }>\n> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT account_id, owner, tokens FROM ${table} WHERE provider = ?`,\n args: [provider],\n });\n return rows.map((row) => ({\n accountId: row.account_id as string,\n owner: (row.owner as string) ?? null,\n tokens: parseStoredTokens(row.tokens as string),\n }));\n}\n\n/**\n * List all OAuth accounts owned by a specific user.\n * In multi-account mode, a user may have connected multiple Google accounts.\n */\nexport async function listOAuthAccountsByOwner(\n provider: string,\n owner: string,\n): Promise<\n Array<{\n accountId: string;\n displayName: string | null;\n tokens: Record<string, unknown>;\n }>\n> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT account_id, display_name, tokens FROM ${table} WHERE provider = ? AND owner = ?`,\n args: [provider, owner],\n });\n return rows.map((row) => ({\n accountId: row.account_id as string,\n displayName: (row.display_name as string) ?? null,\n tokens: parseStoredTokens(row.tokens as string),\n }));\n}\n\n/**\n * Set the display name for an OAuth account (e.g. Google profile name).\n */\nexport async function setOAuthDisplayName(\n provider: string,\n accountId: string,\n displayName: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n await client.execute({\n sql: `UPDATE ${table} SET display_name = ? WHERE provider = ? AND account_id = ?`,\n args: [displayName, provider, accountId],\n });\n}\n\n/**\n * Check whether a specific user has tokens for a provider.\n *\n * `owner` is REQUIRED. The previous unscoped form leaked information\n * across users — the onboarding banner would mark the OAuth secret as\n * \"set\" for user B as soon as ANY user in the deployment connected the\n * provider, and user B would never see the prompt to connect.\n */\nexport async function hasOAuthTokens(\n provider: string,\n owner: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM ${table} WHERE provider = ? AND owner = ? LIMIT 1`,\n args: [provider, owner],\n });\n return rows.length > 0;\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/oauth-tokens/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDbExec, isPostgres, intType } from \"../db/client.js\";\nimport { ensureColumnExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport { widenIntColumnsToBigInt } from \"../db/widen-columns.js\";\nimport {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"../secrets/crypto.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Encrypt the token bundle (AES-256-GCM) before it goes to the `tokens`\n * column. OAuth access/refresh tokens are long-lived, high-value credentials;\n * encrypting at rest means a leaked DB backup / pg_dump / read replica no\n * longer exposes them in plaintext. {@link parseStoredTokens} decrypts\n * transparently on read.\n */\nfunction serializeTokens(tokens: Record<string, unknown>): string {\n return encryptSecretValue(JSON.stringify(tokens));\n}\n\n/**\n * Parse a stored `tokens` value. Encrypted rows are decrypted; rows written\n * before encryption — or mirrored from Better Auth's `account` table — are\n * plaintext JSON and read transparently (the `db-migrate-encrypt-oauth-tokens`\n * script re-encrypts them in place). A row that can't be decrypted (key\n * rotated / corrupt / tampered) is treated as empty rather than throwing into\n * every token lookup.\n */\nfunction parseStoredTokens(\n stored: string | null | undefined,\n): Record<string, unknown> {\n if (!stored) return {};\n if (isEncryptedSecretValue(stored)) {\n try {\n return JSON.parse(decryptSecretValue(stored));\n } catch {\n return {};\n }\n }\n try {\n return JSON.parse(stored);\n } catch {\n return {};\n }\n}\n\nfunction oauthTokensTable(): string {\n return isPostgres() ? \"public.oauth_tokens\" : \"oauth_tokens\";\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const table = oauthTokensTable();\n const createSql = `\n CREATE TABLE IF NOT EXISTS ${table} (\n provider TEXT NOT NULL,\n account_id TEXT NOT NULL,\n owner TEXT,\n tokens TEXT NOT NULL,\n updated_at ${intType()} NOT NULL,\n PRIMARY KEY (provider, account_id)\n )\n `;\n\n if (isPostgres()) {\n // Hot path: the `oauth_tokens` table and its additive columns are\n // virtually always already present in production. Issuing `CREATE\n // TABLE`/`ALTER TABLE` still takes an ACCESS EXCLUSIVE lock that, in a\n // fresh background-worker process behind a concurrent connection on the\n // shared Neon DB, can block ~indefinitely. The ensure* wrappers probe\n // `information_schema` first (plain reads, no lock) and run DDL ONLY for\n // what is actually missing, bounded by a transaction-scoped\n // `lock_timeout`. If a swallowed lock-timeout leaves the schema still\n // missing they RE-PROBE and THROW rather than letting init memoize\n // success against absent schema. `oauthTokensTable()` is\n // `public.oauth_tokens` on Postgres; the wrappers take the unqualified\n // table name.\n await ensureTableExists(\"oauth_tokens\", createSql);\n // Migration: add owner column to existing tables — guarded so the hot\n // path (column already present) skips the ACCESS EXCLUSIVE ALTER.\n await ensureColumnExists(\n \"oauth_tokens\",\n \"owner\",\n `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS owner TEXT`,\n );\n // Migration: add display_name column\n await ensureColumnExists(\n \"oauth_tokens\",\n \"display_name\",\n `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS display_name TEXT`,\n );\n // Backfill: set owner = account_id for existing rows without an owner\n await client.execute(\n `UPDATE ${table} SET owner = account_id WHERE owner IS NULL`,\n );\n // Older deployments have a 32-bit `updated_at`; on Postgres the\n // `Date.now()` written on every token save overflows int4. Widen in place\n // (no-op once done / on fresh DBs). Unqualified name — the helper scopes\n // to the `public` schema.\n await widenIntColumnsToBigInt(\"oauth_tokens\", [\"updated_at\"]);\n return;\n }\n\n // SQLite (local dev): no lock problem — keep the original behaviour.\n await client.execute(createSql);\n // Migration: add owner column to existing tables\n try {\n await client.execute(`ALTER TABLE ${table} ADD COLUMN owner TEXT`);\n } catch {\n // Column already exists\n }\n // Migration: add display_name column\n try {\n await client.execute(\n `ALTER TABLE ${table} ADD COLUMN display_name TEXT`,\n );\n } catch {\n // Column already exists\n }\n // Backfill: set owner = account_id for existing rows without an owner\n await client.execute(\n `UPDATE ${table} SET owner = account_id WHERE owner IS NULL`,\n );\n // Older deployments have a 32-bit `updated_at`; on Postgres the\n // `Date.now()` written on every token save overflows int4. Widen in place\n // (no-op once done / on fresh DBs). Unqualified name — the helper scopes\n // to the `public` schema.\n await widenIntColumnsToBigInt(\"oauth_tokens\", [\"updated_at\"]);\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nexport async function getOAuthTokens(\n provider: string,\n accountId: string,\n): Promise<Record<string, unknown> | null> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT tokens FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n if (rows.length === 0) return null;\n return parseStoredTokens(rows[0].tokens as string);\n}\n\n/**\n * Thrown when an OAuth save would re-bind an `(provider, account_id)` row\n * to a different owner than already holds it. Callers should catch this and\n * surface a clean \"this account is already linked to another user\" message\n * to the requester rather than letting it propagate as a 500.\n *\n * Carries `statusCode = 409` so route handlers using h3's `createError` can\n * pass it straight through.\n */\nexport class OAuthAccountOwnedByOtherUserError extends Error {\n readonly statusCode = 409;\n readonly provider: string;\n readonly accountId: string;\n readonly existingOwner: string;\n readonly attemptedOwner: string;\n constructor(opts: {\n provider: string;\n accountId: string;\n existingOwner: string;\n attemptedOwner: string;\n }) {\n super(\n `OAuth account ${opts.provider}:${opts.accountId} is already linked to another user — refusing to overwrite the owner.`,\n );\n this.name = \"OAuthAccountOwnedByOtherUserError\";\n this.provider = opts.provider;\n this.accountId = opts.accountId;\n this.existingOwner = opts.existingOwner;\n this.attemptedOwner = opts.attemptedOwner;\n }\n}\n\n/**\n * Save OAuth tokens. The `owner` parameter specifies which user owns this\n * account — defaults to `accountId` (the account itself is the owner).\n * For multi-account support, pass the logged-in user's email as owner.\n *\n * If the account already exists and is owned by a different user, throws\n * `OAuthAccountOwnedByOtherUserError` (statusCode 409) to prevent silently\n * stealing another user's linked account.\n *\n * Read + write happen as a single linearised batch (Postgres) or paired\n * statements (SQLite). On both backends the per-row PK serialises concurrent\n * writes for the same `(provider, account_id)` so the owner check cannot be\n * raced by an attacker calling saveOAuthTokens twice in flight — the second\n * caller sees the first caller's owner row and raises 409.\n */\nexport async function saveOAuthTokens(\n provider: string,\n accountId: string,\n tokens: Record<string, unknown>,\n owner?: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n\n // Read the current row before deciding what to write. We use this to\n // (a) preserve owner / display_name when this is a token refresh (no\n // owner argument), and (b) reject the write when the caller is trying\n // to overwrite a row owned by someone else.\n let resolvedOwner = owner ?? accountId;\n let existingDisplayName: string | null = null;\n let existingOwner: string | null = null;\n let existingTokens: Record<string, unknown> | null = null;\n const { rows: existing } = await client.execute({\n sql: `SELECT owner, display_name, tokens FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n if (existing.length > 0) {\n existingOwner = (existing[0].owner as string) ?? null;\n existingDisplayName = (existing[0].display_name as string) ?? null;\n existingTokens = parseStoredTokens(existing[0].tokens as string);\n }\n\n if (!owner) {\n // Token-refresh path: keep the existing owner/displayName unchanged.\n if (existingOwner) resolvedOwner = existingOwner;\n } else if (existingOwner && owner && existingOwner !== owner) {\n // Refuse to silently re-bind an account from one user to another.\n // This is the case the docstring promised but the previous\n // implementation didn't enforce — `ON CONFLICT DO UPDATE SET\n // owner=EXCLUDED.owner` would have overwritten the prior owner.\n throw new OAuthAccountOwnedByOtherUserError({\n provider,\n accountId,\n existingOwner,\n attemptedOwner: owner,\n });\n }\n\n const cleanedIncomingTokens = Object.fromEntries(\n Object.entries(tokens).filter(([, value]) => value !== undefined),\n );\n const tokensToStore = {\n ...(existingTokens ?? {}),\n ...cleanedIncomingTokens,\n };\n\n await client.execute({\n sql: isPostgres()\n ? `INSERT INTO ${table} (provider, account_id, owner, display_name, tokens, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (provider, account_id) DO UPDATE SET owner=EXCLUDED.owner, display_name=COALESCE(EXCLUDED.display_name, ${table}.display_name), tokens=EXCLUDED.tokens, updated_at=EXCLUDED.updated_at`\n : `INSERT OR REPLACE INTO ${table} (provider, account_id, owner, display_name, tokens, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,\n args: [\n provider,\n accountId,\n resolvedOwner,\n existingDisplayName,\n serializeTokens(tokensToStore),\n Date.now(),\n ],\n });\n}\n\nexport async function deleteOAuthTokens(\n provider: string,\n accountId?: string,\n): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n if (accountId) {\n const result = await client.execute({\n sql: `DELETE FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n return result.rowsAffected;\n }\n const result = await client.execute({\n sql: `DELETE FROM ${table} WHERE provider = ?`,\n args: [provider],\n });\n return result.rowsAffected;\n}\n\nexport async function listOAuthAccounts(provider: string): Promise<\n Array<{\n accountId: string;\n owner: string | null;\n tokens: Record<string, unknown>;\n }>\n> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT account_id, owner, tokens FROM ${table} WHERE provider = ?`,\n args: [provider],\n });\n return rows.map((row) => ({\n accountId: row.account_id as string,\n owner: (row.owner as string) ?? null,\n tokens: parseStoredTokens(row.tokens as string),\n }));\n}\n\n/**\n * List all OAuth accounts owned by a specific user.\n * In multi-account mode, a user may have connected multiple Google accounts.\n */\nexport async function listOAuthAccountsByOwner(\n provider: string,\n owner: string,\n): Promise<\n Array<{\n accountId: string;\n displayName: string | null;\n tokens: Record<string, unknown>;\n }>\n> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT account_id, display_name, tokens FROM ${table} WHERE provider = ? AND owner = ?`,\n args: [provider, owner],\n });\n return rows.map((row) => ({\n accountId: row.account_id as string,\n displayName: (row.display_name as string) ?? null,\n tokens: parseStoredTokens(row.tokens as string),\n }));\n}\n\n/**\n * Set the display name for an OAuth account (e.g. Google profile name).\n */\nexport async function setOAuthDisplayName(\n provider: string,\n accountId: string,\n displayName: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n await client.execute({\n sql: `UPDATE ${table} SET display_name = ? WHERE provider = ? AND account_id = ?`,\n args: [displayName, provider, accountId],\n });\n}\n\n/**\n * Check whether a specific user has tokens for a provider.\n *\n * `owner` is REQUIRED. The previous unscoped form leaked information\n * across users — the onboarding banner would mark the OAuth secret as\n * \"set\" for user B as soon as ANY user in the deployment connected the\n * provider, and user B would never see the prompt to connect.\n */\nexport async function hasOAuthTokens(\n provider: string,\n owner: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM ${table} WHERE provider = ? AND owner = ? LIMIT 1`,\n args: [provider, owner],\n });\n return rows.length > 0;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/observability/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/observability/store.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@
7
7
  * rather than Drizzle ORM (which is for template-level schemas).
8
8
  */
9
9
  import { getDbExec, intType, isPostgres, retryOnDdlRace, } from "../db/client.js";
10
+ import { ensureTableExists, ensureColumnExists, ensureIndexExists, } from "../db/ddl-guard.js";
10
11
  import { isDuplicateColumnError } from "../db/migrations.js";
11
12
  function safeJsonParse(value, fallback) {
12
13
  if (!value)
@@ -47,7 +48,7 @@ export async function ensureObservabilityTables() {
47
48
  if (!_initPromise) {
48
49
  _initPromise = (async () => {
49
50
  const client = getDbExec();
50
- await retryOnDdlRace(() => client.execute(`
51
+ const traceSpansCreateSql = `
51
52
  CREATE TABLE IF NOT EXISTS agent_trace_spans (
52
53
  id TEXT PRIMARY KEY,
53
54
  run_id TEXT NOT NULL,
@@ -67,8 +68,8 @@ export async function ensureObservabilityTables() {
67
68
  metadata TEXT,
68
69
  created_at ${intType()} NOT NULL
69
70
  )
70
- `));
71
- await retryOnDdlRace(() => client.execute(`
71
+ `;
72
+ const traceSummariesCreateSql = `
72
73
  CREATE TABLE IF NOT EXISTS agent_trace_summaries (
73
74
  run_id TEXT PRIMARY KEY,
74
75
  thread_id TEXT,
@@ -85,8 +86,8 @@ export async function ensureObservabilityTables() {
85
86
  model TEXT NOT NULL DEFAULT '',
86
87
  created_at ${intType()} NOT NULL
87
88
  )
88
- `));
89
- await retryOnDdlRace(() => client.execute(`
89
+ `;
90
+ const feedbackCreateSql = `
90
91
  CREATE TABLE IF NOT EXISTS agent_feedback (
91
92
  id TEXT PRIMARY KEY,
92
93
  run_id TEXT,
@@ -97,8 +98,8 @@ export async function ensureObservabilityTables() {
97
98
  user_id TEXT,
98
99
  created_at ${intType()} NOT NULL
99
100
  )
100
- `));
101
- await retryOnDdlRace(() => client.execute(`
101
+ `;
102
+ const satisfactionScoresCreateSql = `
102
103
  CREATE TABLE IF NOT EXISTS agent_satisfaction_scores (
103
104
  id TEXT PRIMARY KEY,
104
105
  thread_id TEXT NOT NULL,
@@ -110,8 +111,8 @@ export async function ensureObservabilityTables() {
110
111
  length_trend_score REAL NOT NULL DEFAULT 0,
111
112
  computed_at ${intType()} NOT NULL
112
113
  )
113
- `));
114
- await retryOnDdlRace(() => client.execute(`
114
+ `;
115
+ const evalsCreateSql = `
115
116
  CREATE TABLE IF NOT EXISTS agent_evals (
116
117
  id TEXT PRIMARY KEY,
117
118
  run_id TEXT NOT NULL,
@@ -124,8 +125,8 @@ export async function ensureObservabilityTables() {
124
125
  metadata TEXT,
125
126
  created_at ${intType()} NOT NULL
126
127
  )
127
- `));
128
- await retryOnDdlRace(() => client.execute(`
128
+ `;
129
+ const evalDatasetsCreateSql = `
129
130
  CREATE TABLE IF NOT EXISTS agent_eval_datasets (
130
131
  id TEXT PRIMARY KEY,
131
132
  name TEXT NOT NULL,
@@ -134,8 +135,8 @@ export async function ensureObservabilityTables() {
134
135
  created_at ${intType()} NOT NULL,
135
136
  updated_at ${intType()} NOT NULL
136
137
  )
137
- `));
138
- await retryOnDdlRace(() => client.execute(`
138
+ `;
139
+ const experimentsCreateSql = `
139
140
  CREATE TABLE IF NOT EXISTS agent_experiments (
140
141
  id TEXT PRIMARY KEY,
141
142
  name TEXT NOT NULL,
@@ -148,19 +149,8 @@ export async function ensureObservabilityTables() {
148
149
  created_at ${intType()} NOT NULL,
149
150
  owner_email TEXT
150
151
  )
151
- `));
152
- // Additive migration for DBs created before the owner column shipped
153
- // (any pre-existing rows have NULL owner — see `updateExperiment` for
154
- // the migration semantics). Mutations on those rows fall back to the
155
- // standard authentication gate but cannot enforce per-owner scoping
156
- // until they're re-saved.
157
- try {
158
- await client.execute(`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`);
159
- }
160
- catch {
161
- // Column already exists — expected after first run.
162
- }
163
- await retryOnDdlRace(() => client.execute(`
152
+ `;
153
+ const experimentAssignmentsCreateSql = `
164
154
  CREATE TABLE IF NOT EXISTS agent_experiment_assignments (
165
155
  experiment_id TEXT NOT NULL,
166
156
  user_id TEXT NOT NULL,
@@ -168,8 +158,8 @@ export async function ensureObservabilityTables() {
168
158
  assigned_at ${intType()} NOT NULL,
169
159
  PRIMARY KEY (experiment_id, user_id)
170
160
  )
171
- `));
172
- await retryOnDdlRace(() => client.execute(`
161
+ `;
162
+ const experimentResultsCreateSql = `
173
163
  CREATE TABLE IF NOT EXISTS agent_experiment_results (
174
164
  id TEXT PRIMARY KEY,
175
165
  experiment_id TEXT NOT NULL,
@@ -181,7 +171,60 @@ export async function ensureObservabilityTables() {
181
171
  confidence_high REAL NOT NULL DEFAULT 0,
182
172
  computed_at ${intType()} NOT NULL
183
173
  )
184
- `));
174
+ `;
175
+ if (isPostgres()) {
176
+ // PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
177
+ await ensureTableExists("agent_trace_spans", traceSpansCreateSql);
178
+ await ensureTableExists("agent_trace_summaries", traceSummariesCreateSql);
179
+ await ensureTableExists("agent_feedback", feedbackCreateSql);
180
+ await ensureTableExists("agent_satisfaction_scores", satisfactionScoresCreateSql);
181
+ await ensureTableExists("agent_evals", evalsCreateSql);
182
+ await ensureTableExists("agent_eval_datasets", evalDatasetsCreateSql);
183
+ await ensureTableExists("agent_experiments", experimentsCreateSql);
184
+ await ensureTableExists("agent_experiment_assignments", experimentAssignmentsCreateSql);
185
+ await ensureTableExists("agent_experiment_results", experimentResultsCreateSql);
186
+ await ensureColumnExists("agent_experiments", "owner_email", `ALTER TABLE agent_experiments ADD COLUMN IF NOT EXISTS owner_email TEXT`);
187
+ for (const table of USER_SCOPED_TABLES) {
188
+ await ensureColumnExists(table, "user_id", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id TEXT`);
189
+ }
190
+ await ensureIndexExists("idx_trace_spans_run", `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`);
191
+ await ensureIndexExists("idx_trace_spans_thread", `CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`);
192
+ await ensureIndexExists("idx_trace_spans_created", `CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`);
193
+ await ensureIndexExists("idx_trace_summaries_created", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`);
194
+ await ensureIndexExists("idx_trace_summaries_user", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`);
195
+ await ensureIndexExists("idx_trace_spans_user", `CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`);
196
+ await ensureIndexExists("idx_feedback_thread", `CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`);
197
+ await ensureIndexExists("idx_feedback_created", `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`);
198
+ await ensureIndexExists("idx_feedback_user", `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`);
199
+ await ensureIndexExists("idx_satisfaction_thread", `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`);
200
+ await ensureIndexExists("idx_satisfaction_user", `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`);
201
+ await ensureIndexExists("idx_evals_run", `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`);
202
+ await ensureIndexExists("idx_evals_created", `CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`);
203
+ await ensureIndexExists("idx_evals_user", `CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`);
204
+ await ensureIndexExists("idx_experiment_results_exp", `CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`);
205
+ return;
206
+ }
207
+ // SQLite (local dev): no lock problem — keep the original behaviour.
208
+ await retryOnDdlRace(() => client.execute(traceSpansCreateSql));
209
+ await retryOnDdlRace(() => client.execute(traceSummariesCreateSql));
210
+ await retryOnDdlRace(() => client.execute(feedbackCreateSql));
211
+ await retryOnDdlRace(() => client.execute(satisfactionScoresCreateSql));
212
+ await retryOnDdlRace(() => client.execute(evalsCreateSql));
213
+ await retryOnDdlRace(() => client.execute(evalDatasetsCreateSql));
214
+ await retryOnDdlRace(() => client.execute(experimentsCreateSql));
215
+ // Additive migration for DBs created before the owner column shipped
216
+ // (any pre-existing rows have NULL owner — see `updateExperiment` for
217
+ // the migration semantics). Mutations on those rows fall back to the
218
+ // standard authentication gate but cannot enforce per-owner scoping
219
+ // until they're re-saved.
220
+ try {
221
+ await client.execute(`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`);
222
+ }
223
+ catch {
224
+ // Column already exists — expected after first run.
225
+ }
226
+ await retryOnDdlRace(() => client.execute(experimentAssignmentsCreateSql));
227
+ await retryOnDdlRace(() => client.execute(experimentResultsCreateSql));
185
228
  // Idempotent column upgrades for DBs created before per-user
186
229
  // isolation. SQLite has no `ADD COLUMN IF NOT EXISTS`; Postgres
187
230
  // surfaces "column ... already exists". `isDuplicateColumnError`