@secondlayer/shared 2.0.0 → 3.0.0-alpha.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 (78) hide show
  1. package/README.md +2 -2
  2. package/dist/src/db/index.d.ts +64 -130
  3. package/dist/src/db/index.js.map +2 -2
  4. package/dist/src/db/jsonb.d.ts +5 -1
  5. package/dist/src/db/jsonb.js.map +2 -2
  6. package/dist/src/db/queries/account-spend-caps.d.ts +379 -0
  7. package/dist/src/db/queries/account-spend-caps.js +60 -0
  8. package/dist/src/db/queries/account-spend-caps.js.map +10 -0
  9. package/dist/src/db/queries/account-usage.d.ts +403 -0
  10. package/dist/src/db/queries/account-usage.js +222 -0
  11. package/dist/src/db/queries/account-usage.js.map +11 -0
  12. package/dist/src/db/queries/accounts.d.ts +61 -108
  13. package/dist/src/db/queries/accounts.js +15 -1
  14. package/dist/src/db/queries/accounts.js.map +3 -3
  15. package/dist/src/db/queries/integrity.d.ts +47 -107
  16. package/dist/src/db/queries/projects.d.ts +47 -107
  17. package/dist/src/db/queries/{workflows.d.ts → provisioning-audit.d.ts} +70 -142
  18. package/dist/src/db/queries/provisioning-audit.js +40 -0
  19. package/dist/src/db/queries/provisioning-audit.js.map +10 -0
  20. package/dist/src/db/queries/subgraph-gaps.d.ts +47 -107
  21. package/dist/src/db/queries/subgraphs.d.ts +47 -108
  22. package/dist/src/db/queries/subgraphs.js +2 -3
  23. package/dist/src/db/queries/subgraphs.js.map +4 -4
  24. package/dist/src/db/queries/{marketplace.d.ts → tenant-compute-addons.d.ts} +66 -159
  25. package/dist/src/db/queries/tenant-compute-addons.js +47 -0
  26. package/dist/src/db/queries/tenant-compute-addons.js.map +10 -0
  27. package/dist/src/db/queries/tenants.d.ts +67 -110
  28. package/dist/src/db/queries/tenants.js +35 -6
  29. package/dist/src/db/queries/tenants.js.map +3 -3
  30. package/dist/src/db/queries/usage.d.ts +48 -132
  31. package/dist/src/db/queries/usage.js +5 -64
  32. package/dist/src/db/queries/usage.js.map +4 -5
  33. package/dist/src/db/schema.d.ts +59 -129
  34. package/dist/src/errors.d.ts +8 -7
  35. package/dist/src/errors.js +11 -12
  36. package/dist/src/errors.js.map +3 -3
  37. package/dist/src/index.d.ts +98 -212
  38. package/dist/src/index.js +69 -80
  39. package/dist/src/index.js.map +6 -6
  40. package/dist/src/mode.d.ts +4 -5
  41. package/dist/src/mode.js.map +2 -2
  42. package/dist/src/node/local-client.d.ts +47 -107
  43. package/dist/src/pricing.d.ts +20 -1
  44. package/dist/src/pricing.js +58 -1
  45. package/dist/src/pricing.js.map +3 -3
  46. package/dist/src/schemas/accounts.d.ts +14 -0
  47. package/dist/src/schemas/{marketplace.js → accounts.js} +4 -14
  48. package/dist/src/schemas/accounts.js.map +10 -0
  49. package/dist/src/schemas/index.d.ts +28 -77
  50. package/dist/src/schemas/index.js +59 -69
  51. package/dist/src/schemas/index.js.map +4 -4
  52. package/migrations/0043_tenant_usage_monthly.ts +36 -0
  53. package/migrations/0044_provisioning_audit_log.ts +40 -0
  54. package/migrations/0045_drop_marketplace_columns.ts +47 -0
  55. package/migrations/0046_tenant_activity_signal.ts +47 -0
  56. package/migrations/0047_usage_daily_tenant_id.ts +73 -0
  57. package/migrations/0048_tenant_compute_addons.ts +49 -0
  58. package/migrations/0049_accounts_stripe_customer_id.ts +30 -0
  59. package/migrations/0050_account_spend_caps.ts +45 -0
  60. package/migrations/0051_workflow_ai_usage_daily.ts +40 -0
  61. package/migrations/0052_sentries.ts +61 -0
  62. package/migrations/0053_workflow_runtime.ts +88 -0
  63. package/migrations/0054_accounts_plan_hobby.ts +32 -0
  64. package/migrations/0055_ai_usage_account_scope.ts +108 -0
  65. package/migrations/0056_drop_workflow_sentry_residuals.ts +23 -0
  66. package/package.json +33 -21
  67. package/dist/src/db/queries/marketplace.js +0 -139
  68. package/dist/src/db/queries/marketplace.js.map +0 -10
  69. package/dist/src/db/queries/workflows.js +0 -260
  70. package/dist/src/db/queries/workflows.js.map +0 -12
  71. package/dist/src/lib/plans.d.ts +0 -9
  72. package/dist/src/lib/plans.js +0 -37
  73. package/dist/src/lib/plans.js.map +0 -10
  74. package/dist/src/schemas/marketplace.d.ts +0 -63
  75. package/dist/src/schemas/marketplace.js.map +0 -10
  76. package/dist/src/schemas/workflows.d.ts +0 -70
  77. package/dist/src/schemas/workflows.js +0 -43
  78. package/dist/src/schemas/workflows.js.map +0 -10
@@ -61,10 +61,6 @@ interface SubgraphsTable {
61
61
  handler_code: string | null;
62
62
  source_code: string | null;
63
63
  project_id: string | null;
64
- is_public: Generated<boolean>;
65
- tags: Generated<string[]>;
66
- description: string | null;
67
- forked_from_id: string | null;
68
64
  created_at: Generated<Date>;
69
65
  updated_at: Generated<Date>;
70
66
  }
@@ -99,6 +95,7 @@ interface AccountsTable {
99
95
  bio: string | null;
100
96
  avatar_url: string | null;
101
97
  slug: string | null;
98
+ stripe_customer_id: string | null;
102
99
  created_at: Generated<Date>;
103
100
  }
104
101
  interface SessionsTable {
@@ -124,6 +121,7 @@ interface MagicLinksTable {
124
121
  }
125
122
  interface UsageDailyTable {
126
123
  account_id: string;
124
+ tenant_id: string | null;
127
125
  date: string;
128
126
  api_requests: Generated<number>;
129
127
  deliveries: Generated<number>;
@@ -250,83 +248,6 @@ interface ChatMessagesTable {
250
248
  metadata: unknown | null;
251
249
  created_at: Generated<Date>;
252
250
  }
253
- interface WorkflowDefinitionsTable {
254
- id: Generated<string>;
255
- name: string;
256
- version: Generated<string>;
257
- status: Generated<string>;
258
- trigger_type: string;
259
- trigger_config: unknown;
260
- handler_path: string;
261
- source_code: string | null;
262
- retries_config: unknown | null;
263
- timeout_ms: number | null;
264
- api_key_id: string;
265
- project_id: string | null;
266
- created_at: Generated<Date>;
267
- updated_at: Generated<Date>;
268
- }
269
- interface WorkflowRunsTable {
270
- id: Generated<string>;
271
- definition_id: string;
272
- status: Generated<string>;
273
- trigger_type: string;
274
- trigger_data: unknown | null;
275
- dedup_key: string | null;
276
- error: string | null;
277
- started_at: Date | null;
278
- completed_at: Date | null;
279
- duration_ms: number | null;
280
- total_ai_tokens: Generated<number>;
281
- created_at: Generated<Date>;
282
- }
283
- interface WorkflowStepsTable {
284
- id: Generated<string>;
285
- run_id: string;
286
- step_index: number;
287
- step_id: string;
288
- step_type: string;
289
- status: Generated<string>;
290
- input: unknown | null;
291
- output: unknown | null;
292
- error: string | null;
293
- retry_count: Generated<number>;
294
- ai_tokens_used: Generated<number>;
295
- started_at: Date | null;
296
- completed_at: Date | null;
297
- duration_ms: number | null;
298
- memo_key: string | null;
299
- parent_step_id: string | null;
300
- created_at: Generated<Date>;
301
- }
302
- interface WorkflowQueueTable {
303
- id: Generated<string>;
304
- run_id: string;
305
- status: Generated<string>;
306
- attempts: Generated<number>;
307
- max_attempts: Generated<number>;
308
- scheduled_for: Generated<Date>;
309
- locked_at: Date | null;
310
- locked_by: string | null;
311
- error: string | null;
312
- created_at: Generated<Date>;
313
- completed_at: Date | null;
314
- }
315
- interface WorkflowSchedulesTable {
316
- id: Generated<string>;
317
- definition_id: string;
318
- cron_expr: string;
319
- timezone: Generated<string>;
320
- next_run_at: Date;
321
- last_run_at: Date | null;
322
- enabled: Generated<boolean>;
323
- created_at: Generated<Date>;
324
- }
325
- interface WorkflowCursorsTable {
326
- name: string;
327
- block_height: Generated<number>;
328
- updated_at: Generated<Date>;
329
- }
330
251
  interface Database {
331
252
  blocks: BlocksTable;
332
253
  transactions: TransactionsTable;
@@ -352,15 +273,11 @@ interface Database {
352
273
  team_invitations: TeamInvitationsTable;
353
274
  chat_sessions: ChatSessionsTable;
354
275
  chat_messages: ChatMessagesTable;
355
- workflow_definitions: WorkflowDefinitionsTable;
356
- workflow_runs: WorkflowRunsTable;
357
- workflow_steps: WorkflowStepsTable;
358
- workflow_queue: WorkflowQueueTable;
359
- workflow_schedules: WorkflowSchedulesTable;
360
- workflow_cursors: WorkflowCursorsTable;
361
- workflow_signer_secrets: WorkflowSignerSecretsTable;
362
- workflow_budgets: WorkflowBudgetsTable;
363
276
  tenants: TenantsTable;
277
+ tenant_usage_monthly: TenantUsageMonthlyTable;
278
+ tenant_compute_addons: TenantComputeAddonsTable;
279
+ account_spend_caps: AccountSpendCapsTable;
280
+ provisioning_audit_log: ProvisioningAuditLogTable;
364
281
  }
365
282
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
366
283
  interface TenantsTable {
@@ -382,38 +299,61 @@ interface TenantsTable {
382
299
  service_key_enc: Buffer;
383
300
  api_url_internal: string;
384
301
  api_url_public: string;
385
- trial_ends_at: Date;
386
302
  suspended_at: Date | null;
387
303
  last_health_check_at: Date | null;
304
+ last_active_at: Generated<Date>;
388
305
  service_gen: Generated<number>;
389
306
  anon_gen: Generated<number>;
390
307
  project_id: string | null;
391
308
  created_at: Generated<Date>;
392
309
  updated_at: Generated<Date>;
393
310
  }
394
- interface WorkflowBudgetsTable {
311
+ interface TenantUsageMonthlyTable {
312
+ id: Generated<string>;
313
+ tenant_id: string;
314
+ period_month: Date;
315
+ storage_peak_mb: Generated<number>;
316
+ storage_avg_mb: Generated<number>;
317
+ storage_last_mb: Generated<number>;
318
+ measurements: Generated<number>;
319
+ first_at: Generated<Date>;
320
+ last_at: Generated<Date>;
321
+ }
322
+ interface TenantComputeAddonsTable {
395
323
  id: Generated<string>;
396
- workflow_definition_id: string;
397
- /** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
398
- period: string;
399
- ai_usd_used: Generated<string>;
400
- ai_tokens_used: Generated<string>;
401
- chain_microstx_used: Generated<string>;
402
- chain_tx_count: Generated<number>;
403
- run_count: Generated<number>;
404
- step_count: Generated<number>;
405
- reset_at: Date;
324
+ tenant_id: string;
325
+ memory_mb_delta: Generated<number>;
326
+ cpu_delta: Generated<number | string>;
327
+ storage_mb_delta: Generated<number>;
328
+ effective_from: Generated<Date>;
329
+ effective_until: Date | null;
330
+ stripe_subscription_item_id: string | null;
406
331
  created_at: Generated<Date>;
332
+ }
333
+ interface AccountSpendCapsTable {
334
+ account_id: string;
335
+ monthly_cap_cents: number | null;
336
+ compute_cap_cents: number | null;
337
+ storage_cap_cents: number | null;
338
+ ai_cap_cents: number | null;
339
+ alert_threshold_pct: Generated<number>;
340
+ alert_sent_at: Date | null;
341
+ frozen_at: Date | null;
407
342
  updated_at: Generated<Date>;
408
343
  }
409
- interface WorkflowSignerSecretsTable {
344
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
345
+ type ProvisioningAuditStatus = "ok" | "error";
346
+ interface ProvisioningAuditLogTable {
410
347
  id: Generated<string>;
411
- account_id: string;
412
- name: string;
413
- /** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
414
- encrypted_value: Buffer;
348
+ tenant_id: string | null;
349
+ tenant_slug: string | null;
350
+ account_id: string | null;
351
+ actor: string;
352
+ event: ProvisioningAuditEvent;
353
+ status: ProvisioningAuditStatus;
354
+ detail: unknown | null;
355
+ error: string | null;
415
356
  created_at: Generated<Date>;
416
- updated_at: Generated<Date>;
417
357
  }
418
358
  type Account = Selectable<AccountsTable>;
419
359
  declare function upsertAccount(db: Kysely<Database>, email: string): Promise<Account>;
@@ -423,6 +363,19 @@ declare function updateAccountProfile(db: Kysely<Database>, id: string, data: {
423
363
  bio?: string
424
364
  slug?: string
425
365
  }): Promise<Account>;
366
+ /** Persist the Stripe customer id on first upgrade (lazy customer model). */
367
+ declare function setStripeCustomerId(db: Kysely<Database>, accountId: string, stripeCustomerId: string): Promise<void>;
368
+ /**
369
+ * Set the plan tier on an account. Called by the Stripe webhook on
370
+ * subscription lifecycle events + by the billing page's fast-resolve
371
+ * after a successful Checkout redirect. Returns true if a row was
372
+ * updated (account exists).
373
+ */
374
+ declare function setAccountPlan(db: Kysely<Database>, accountId: string, plan: string): Promise<boolean>;
375
+ /** Resolve an account by its Stripe customer id. Null if no match. */
376
+ declare function getAccountByStripeCustomerId(db: Kysely<Database>, stripeCustomerId: string): Promise<{
377
+ id: string
378
+ } | null>;
426
379
  declare function isSlugTaken(db: Kysely<Database>, slug: string, excludeAccountId: string): Promise<boolean>;
427
380
  declare function isEmailAllowed(db: Kysely<Database>, email: string): Promise<boolean>;
428
381
  declare function createMagicLink(db: Kysely<Database>, email: string, token: string, code: string, expiresInMs?: number): Promise<void>;
@@ -445,4 +398,4 @@ declare function approveWaitlistEntry(db: Kysely<Database>, email: string): Prom
445
398
  code: string
446
399
  status: "approved" | "already_approved" | "not_found"
447
400
  }>;
448
- export { verifyMagicLinkByCode, verifyMagicLink, upsertAccount, updateAccountProfile, listWaitlist, isSlugTaken, isEmailAllowed, getWaitlistById, getAccountById, createMagicLink, approveWaitlistEntry, WaitlistEntry };
401
+ export { verifyMagicLinkByCode, verifyMagicLink, upsertAccount, updateAccountProfile, setStripeCustomerId, setAccountPlan, listWaitlist, isSlugTaken, isEmailAllowed, getWaitlistById, getAccountByStripeCustomerId, getAccountById, createMagicLink, approveWaitlistEntry, WaitlistEntry };
@@ -32,6 +32,17 @@ async function updateAccountProfile(db, id, data) {
32
32
  set.slug = data.slug;
33
33
  return db.updateTable("accounts").set(set).where("id", "=", id).returningAll().executeTakeFirstOrThrow();
34
34
  }
35
+ async function setStripeCustomerId(db, accountId, stripeCustomerId) {
36
+ await db.updateTable("accounts").set({ stripe_customer_id: stripeCustomerId }).where("id", "=", accountId).execute();
37
+ }
38
+ async function setAccountPlan(db, accountId, plan) {
39
+ const result = await db.updateTable("accounts").set({ plan }).where("id", "=", accountId).executeTakeFirst();
40
+ return (result.numUpdatedRows ?? 0n) > 0n;
41
+ }
42
+ async function getAccountByStripeCustomerId(db, stripeCustomerId) {
43
+ const row = await db.selectFrom("accounts").select("id").where("stripe_customer_id", "=", stripeCustomerId).executeTakeFirst();
44
+ return row ?? null;
45
+ }
35
46
  async function isSlugTaken(db, slug, excludeAccountId) {
36
47
  const row = await db.selectFrom("accounts").select("id").where("slug", "=", slug).where("id", "!=", excludeAccountId).executeTakeFirst();
37
48
  return !!row;
@@ -94,14 +105,17 @@ export {
94
105
  verifyMagicLink,
95
106
  upsertAccount,
96
107
  updateAccountProfile,
108
+ setStripeCustomerId,
109
+ setAccountPlan,
97
110
  listWaitlist,
98
111
  isSlugTaken,
99
112
  isEmailAllowed,
100
113
  getWaitlistById,
114
+ getAccountByStripeCustomerId,
101
115
  getAccountById,
102
116
  createMagicLink,
103
117
  approveWaitlistEntry
104
118
  };
105
119
 
106
- //# debugId=3FB34D04C85084DA64756E2164756E21
120
+ //# debugId=E1E89B16EA4EC82664756E2164756E21
107
121
  //# sourceMappingURL=accounts.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/queries/accounts.ts"],
4
4
  "sourcesContent": [
5
- "import { type Kysely, sql } from \"kysely\";\nimport type { Selectable } from \"kysely\";\nimport type { Account, Database, WaitlistTable } from \"../types.ts\";\n\nexport async function upsertAccount(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<Account> {\n\treturn await db\n\t\t.insertInto(\"accounts\")\n\t\t.values({ email })\n\t\t.onConflict(\n\t\t\t(oc) => oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<Account | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"accounts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function updateAccountProfile(\n\tdb: Kysely<Database>,\n\tid: string,\n\tdata: {\n\t\tdisplay_name?: string;\n\t\tbio?: string;\n\t\tslug?: string;\n\t},\n): Promise<Account> {\n\tconst set: Record<string, unknown> = {};\n\tif (data.display_name !== undefined) set.display_name = data.display_name;\n\tif (data.bio !== undefined) set.bio = data.bio;\n\tif (data.slug !== undefined) set.slug = data.slug;\n\n\treturn db\n\t\t.updateTable(\"accounts\")\n\t\t.set(set)\n\t\t.where(\"id\", \"=\", id)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function isSlugTaken(\n\tdb: Kysely<Database>,\n\tslug: string,\n\texcludeAccountId: string,\n): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"accounts\")\n\t\t.select(\"id\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.where(\"id\", \"!=\", excludeAccountId)\n\t\t.executeTakeFirst();\n\treturn !!row;\n}\n\nexport async function isEmailAllowed(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<boolean> {\n\tconst result = await sql<{ found: number }>`\n SELECT 1 AS found FROM accounts WHERE email = ${email}\n UNION ALL\n SELECT 1 AS found FROM waitlist WHERE email = ${email} AND status = 'approved'\n LIMIT 1\n `.execute(db);\n\n\treturn result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n\tdb: Kysely<Database>,\n\temail: string,\n\ttoken: string,\n\tcode: string,\n\texpiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"magic_links\")\n\t\t.values({\n\t\t\temail,\n\t\t\ttoken,\n\t\t\tcode,\n\t\t\texpires_at: new Date(Date.now() + expiresInMs),\n\t\t})\n\t\t.execute();\n}\n\n/**\n * Verify a magic link token. Returns the email if valid, null otherwise.\n * Marks the token as used atomically. Rejects after 3 failed attempts.\n */\nexport async function verifyMagicLink(\n\tdb: Kysely<Database>,\n\ttoken: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts if token exists but didn't verify\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n/**\n * Verify by 6-digit code + email. Same atomic pattern as verifyMagicLink.\n * Rejects after 3 failed attempts. Increments failed_attempts on all\n * active codes for this email on failure (prevents parallel brute-force).\n */\nexport async function verifyMagicLinkByCode(\n\tdb: Kysely<Database>,\n\temail: string,\n\tcode: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"code\", \"=\", code)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts on all active codes for this email\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n// ── Waitlist ──\n\nexport type WaitlistEntry = Selectable<WaitlistTable>;\n\nexport async function listWaitlist(\n\tdb: Kysely<Database>,\n\tstatus?: string,\n): Promise<WaitlistEntry[]> {\n\tlet query = db\n\t\t.selectFrom(\"waitlist\")\n\t\t.selectAll()\n\t\t.orderBy(\"created_at\", \"desc\");\n\tif (status) {\n\t\tquery = query.where(\"status\", \"=\", status);\n\t}\n\treturn query.execute();\n}\n\nexport async function getWaitlistById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<WaitlistEntry | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"waitlist\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function approveWaitlistEntry(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<{\n\ttoken: string;\n\tcode: string;\n\tstatus: \"approved\" | \"already_approved\" | \"not_found\";\n}> {\n\tconst row = await db\n\t\t.selectFrom(\"waitlist\")\n\t\t.select(\"status\")\n\t\t.where(\"email\", \"=\", email)\n\t\t.executeTakeFirst();\n\n\tif (!row) return { token: \"\", code: \"\", status: \"not_found\" };\n\tif (row.status !== \"pending\")\n\t\treturn { token: \"\", code: \"\", status: \"already_approved\" };\n\n\tawait db\n\t\t.updateTable(\"waitlist\")\n\t\t.set({ status: \"approved\" })\n\t\t.where(\"email\", \"=\", email)\n\t\t.execute();\n\n\tconst token = Math.floor(100000 + Math.random() * 900000).toString();\n\tconst code = String(Math.floor(Math.random() * 1_000_000)).padStart(6, \"0\");\n\tawait createMagicLink(db, email, token, code, 7 * 24 * 60 * 60 * 1000);\n\n\treturn { token, code, status: \"approved\" };\n}\n"
5
+ "import { type Kysely, sql } from \"kysely\";\nimport type { Selectable } from \"kysely\";\nimport type { Account, Database, WaitlistTable } from \"../types.ts\";\n\nexport async function upsertAccount(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<Account> {\n\treturn await db\n\t\t.insertInto(\"accounts\")\n\t\t.values({ email })\n\t\t.onConflict(\n\t\t\t(oc) => oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<Account | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"accounts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function updateAccountProfile(\n\tdb: Kysely<Database>,\n\tid: string,\n\tdata: {\n\t\tdisplay_name?: string;\n\t\tbio?: string;\n\t\tslug?: string;\n\t},\n): Promise<Account> {\n\tconst set: Record<string, unknown> = {};\n\tif (data.display_name !== undefined) set.display_name = data.display_name;\n\tif (data.bio !== undefined) set.bio = data.bio;\n\tif (data.slug !== undefined) set.slug = data.slug;\n\n\treturn db\n\t\t.updateTable(\"accounts\")\n\t\t.set(set)\n\t\t.where(\"id\", \"=\", id)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\n/** Persist the Stripe customer id on first upgrade (lazy customer model). */\nexport async function setStripeCustomerId(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tstripeCustomerId: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"accounts\")\n\t\t.set({ stripe_customer_id: stripeCustomerId })\n\t\t.where(\"id\", \"=\", accountId)\n\t\t.execute();\n}\n\n/**\n * Set the plan tier on an account. Called by the Stripe webhook on\n * subscription lifecycle events + by the billing page's fast-resolve\n * after a successful Checkout redirect. Returns true if a row was\n * updated (account exists).\n */\nexport async function setAccountPlan(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"accounts\")\n\t\t.set({ plan })\n\t\t.where(\"id\", \"=\", accountId)\n\t\t.executeTakeFirst();\n\treturn (result.numUpdatedRows ?? 0n) > 0n;\n}\n\n/** Resolve an account by its Stripe customer id. Null if no match. */\nexport async function getAccountByStripeCustomerId(\n\tdb: Kysely<Database>,\n\tstripeCustomerId: string,\n): Promise<{ id: string } | null> {\n\tconst row = await db\n\t\t.selectFrom(\"accounts\")\n\t\t.select(\"id\")\n\t\t.where(\"stripe_customer_id\", \"=\", stripeCustomerId)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function isSlugTaken(\n\tdb: Kysely<Database>,\n\tslug: string,\n\texcludeAccountId: string,\n): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"accounts\")\n\t\t.select(\"id\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.where(\"id\", \"!=\", excludeAccountId)\n\t\t.executeTakeFirst();\n\treturn !!row;\n}\n\nexport async function isEmailAllowed(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<boolean> {\n\tconst result = await sql<{ found: number }>`\n SELECT 1 AS found FROM accounts WHERE email = ${email}\n UNION ALL\n SELECT 1 AS found FROM waitlist WHERE email = ${email} AND status = 'approved'\n LIMIT 1\n `.execute(db);\n\n\treturn result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n\tdb: Kysely<Database>,\n\temail: string,\n\ttoken: string,\n\tcode: string,\n\texpiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"magic_links\")\n\t\t.values({\n\t\t\temail,\n\t\t\ttoken,\n\t\t\tcode,\n\t\t\texpires_at: new Date(Date.now() + expiresInMs),\n\t\t})\n\t\t.execute();\n}\n\n/**\n * Verify a magic link token. Returns the email if valid, null otherwise.\n * Marks the token as used atomically. Rejects after 3 failed attempts.\n */\nexport async function verifyMagicLink(\n\tdb: Kysely<Database>,\n\ttoken: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts if token exists but didn't verify\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n/**\n * Verify by 6-digit code + email. Same atomic pattern as verifyMagicLink.\n * Rejects after 3 failed attempts. Increments failed_attempts on all\n * active codes for this email on failure (prevents parallel brute-force).\n */\nexport async function verifyMagicLinkByCode(\n\tdb: Kysely<Database>,\n\temail: string,\n\tcode: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"code\", \"=\", code)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts on all active codes for this email\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n// ── Waitlist ──\n\nexport type WaitlistEntry = Selectable<WaitlistTable>;\n\nexport async function listWaitlist(\n\tdb: Kysely<Database>,\n\tstatus?: string,\n): Promise<WaitlistEntry[]> {\n\tlet query = db\n\t\t.selectFrom(\"waitlist\")\n\t\t.selectAll()\n\t\t.orderBy(\"created_at\", \"desc\");\n\tif (status) {\n\t\tquery = query.where(\"status\", \"=\", status);\n\t}\n\treturn query.execute();\n}\n\nexport async function getWaitlistById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<WaitlistEntry | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"waitlist\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function approveWaitlistEntry(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<{\n\ttoken: string;\n\tcode: string;\n\tstatus: \"approved\" | \"already_approved\" | \"not_found\";\n}> {\n\tconst row = await db\n\t\t.selectFrom(\"waitlist\")\n\t\t.select(\"status\")\n\t\t.where(\"email\", \"=\", email)\n\t\t.executeTakeFirst();\n\n\tif (!row) return { token: \"\", code: \"\", status: \"not_found\" };\n\tif (row.status !== \"pending\")\n\t\treturn { token: \"\", code: \"\", status: \"already_approved\" };\n\n\tawait db\n\t\t.updateTable(\"waitlist\")\n\t\t.set({ status: \"approved\" })\n\t\t.where(\"email\", \"=\", email)\n\t\t.execute();\n\n\tconst token = Math.floor(100000 + Math.random() * 900000).toString();\n\tconst code = String(Math.floor(Math.random() * 1_000_000)).padStart(6, \"0\");\n\tawait createMagicLink(db, email, token, code, 7 * 24 * 60 * 60 * 1000);\n\n\treturn { token, code, status: \"approved\" };\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "3FB34D04C85084DA64756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "E1E89B16EA4EC82664756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -60,10 +60,6 @@ interface SubgraphsTable {
60
60
  handler_code: string | null;
61
61
  source_code: string | null;
62
62
  project_id: string | null;
63
- is_public: Generated<boolean>;
64
- tags: Generated<string[]>;
65
- description: string | null;
66
- forked_from_id: string | null;
67
63
  created_at: Generated<Date>;
68
64
  updated_at: Generated<Date>;
69
65
  }
@@ -98,6 +94,7 @@ interface AccountsTable {
98
94
  bio: string | null;
99
95
  avatar_url: string | null;
100
96
  slug: string | null;
97
+ stripe_customer_id: string | null;
101
98
  created_at: Generated<Date>;
102
99
  }
103
100
  interface SessionsTable {
@@ -123,6 +120,7 @@ interface MagicLinksTable {
123
120
  }
124
121
  interface UsageDailyTable {
125
122
  account_id: string;
123
+ tenant_id: string | null;
126
124
  date: string;
127
125
  api_requests: Generated<number>;
128
126
  deliveries: Generated<number>;
@@ -249,83 +247,6 @@ interface ChatMessagesTable {
249
247
  metadata: unknown | null;
250
248
  created_at: Generated<Date>;
251
249
  }
252
- interface WorkflowDefinitionsTable {
253
- id: Generated<string>;
254
- name: string;
255
- version: Generated<string>;
256
- status: Generated<string>;
257
- trigger_type: string;
258
- trigger_config: unknown;
259
- handler_path: string;
260
- source_code: string | null;
261
- retries_config: unknown | null;
262
- timeout_ms: number | null;
263
- api_key_id: string;
264
- project_id: string | null;
265
- created_at: Generated<Date>;
266
- updated_at: Generated<Date>;
267
- }
268
- interface WorkflowRunsTable {
269
- id: Generated<string>;
270
- definition_id: string;
271
- status: Generated<string>;
272
- trigger_type: string;
273
- trigger_data: unknown | null;
274
- dedup_key: string | null;
275
- error: string | null;
276
- started_at: Date | null;
277
- completed_at: Date | null;
278
- duration_ms: number | null;
279
- total_ai_tokens: Generated<number>;
280
- created_at: Generated<Date>;
281
- }
282
- interface WorkflowStepsTable {
283
- id: Generated<string>;
284
- run_id: string;
285
- step_index: number;
286
- step_id: string;
287
- step_type: string;
288
- status: Generated<string>;
289
- input: unknown | null;
290
- output: unknown | null;
291
- error: string | null;
292
- retry_count: Generated<number>;
293
- ai_tokens_used: Generated<number>;
294
- started_at: Date | null;
295
- completed_at: Date | null;
296
- duration_ms: number | null;
297
- memo_key: string | null;
298
- parent_step_id: string | null;
299
- created_at: Generated<Date>;
300
- }
301
- interface WorkflowQueueTable {
302
- id: Generated<string>;
303
- run_id: string;
304
- status: Generated<string>;
305
- attempts: Generated<number>;
306
- max_attempts: Generated<number>;
307
- scheduled_for: Generated<Date>;
308
- locked_at: Date | null;
309
- locked_by: string | null;
310
- error: string | null;
311
- created_at: Generated<Date>;
312
- completed_at: Date | null;
313
- }
314
- interface WorkflowSchedulesTable {
315
- id: Generated<string>;
316
- definition_id: string;
317
- cron_expr: string;
318
- timezone: Generated<string>;
319
- next_run_at: Date;
320
- last_run_at: Date | null;
321
- enabled: Generated<boolean>;
322
- created_at: Generated<Date>;
323
- }
324
- interface WorkflowCursorsTable {
325
- name: string;
326
- block_height: Generated<number>;
327
- updated_at: Generated<Date>;
328
- }
329
250
  interface Database {
330
251
  blocks: BlocksTable;
331
252
  transactions: TransactionsTable;
@@ -351,15 +272,11 @@ interface Database {
351
272
  team_invitations: TeamInvitationsTable;
352
273
  chat_sessions: ChatSessionsTable;
353
274
  chat_messages: ChatMessagesTable;
354
- workflow_definitions: WorkflowDefinitionsTable;
355
- workflow_runs: WorkflowRunsTable;
356
- workflow_steps: WorkflowStepsTable;
357
- workflow_queue: WorkflowQueueTable;
358
- workflow_schedules: WorkflowSchedulesTable;
359
- workflow_cursors: WorkflowCursorsTable;
360
- workflow_signer_secrets: WorkflowSignerSecretsTable;
361
- workflow_budgets: WorkflowBudgetsTable;
362
275
  tenants: TenantsTable;
276
+ tenant_usage_monthly: TenantUsageMonthlyTable;
277
+ tenant_compute_addons: TenantComputeAddonsTable;
278
+ account_spend_caps: AccountSpendCapsTable;
279
+ provisioning_audit_log: ProvisioningAuditLogTable;
363
280
  }
364
281
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
365
282
  interface TenantsTable {
@@ -381,38 +298,61 @@ interface TenantsTable {
381
298
  service_key_enc: Buffer;
382
299
  api_url_internal: string;
383
300
  api_url_public: string;
384
- trial_ends_at: Date;
385
301
  suspended_at: Date | null;
386
302
  last_health_check_at: Date | null;
303
+ last_active_at: Generated<Date>;
387
304
  service_gen: Generated<number>;
388
305
  anon_gen: Generated<number>;
389
306
  project_id: string | null;
390
307
  created_at: Generated<Date>;
391
308
  updated_at: Generated<Date>;
392
309
  }
393
- interface WorkflowBudgetsTable {
310
+ interface TenantUsageMonthlyTable {
394
311
  id: Generated<string>;
395
- workflow_definition_id: string;
396
- /** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
397
- period: string;
398
- ai_usd_used: Generated<string>;
399
- ai_tokens_used: Generated<string>;
400
- chain_microstx_used: Generated<string>;
401
- chain_tx_count: Generated<number>;
402
- run_count: Generated<number>;
403
- step_count: Generated<number>;
404
- reset_at: Date;
312
+ tenant_id: string;
313
+ period_month: Date;
314
+ storage_peak_mb: Generated<number>;
315
+ storage_avg_mb: Generated<number>;
316
+ storage_last_mb: Generated<number>;
317
+ measurements: Generated<number>;
318
+ first_at: Generated<Date>;
319
+ last_at: Generated<Date>;
320
+ }
321
+ interface TenantComputeAddonsTable {
322
+ id: Generated<string>;
323
+ tenant_id: string;
324
+ memory_mb_delta: Generated<number>;
325
+ cpu_delta: Generated<number | string>;
326
+ storage_mb_delta: Generated<number>;
327
+ effective_from: Generated<Date>;
328
+ effective_until: Date | null;
329
+ stripe_subscription_item_id: string | null;
405
330
  created_at: Generated<Date>;
331
+ }
332
+ interface AccountSpendCapsTable {
333
+ account_id: string;
334
+ monthly_cap_cents: number | null;
335
+ compute_cap_cents: number | null;
336
+ storage_cap_cents: number | null;
337
+ ai_cap_cents: number | null;
338
+ alert_threshold_pct: Generated<number>;
339
+ alert_sent_at: Date | null;
340
+ frozen_at: Date | null;
406
341
  updated_at: Generated<Date>;
407
342
  }
408
- interface WorkflowSignerSecretsTable {
343
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
344
+ type ProvisioningAuditStatus = "ok" | "error";
345
+ interface ProvisioningAuditLogTable {
409
346
  id: Generated<string>;
410
- account_id: string;
411
- name: string;
412
- /** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
413
- encrypted_value: Buffer;
347
+ tenant_id: string | null;
348
+ tenant_slug: string | null;
349
+ account_id: string | null;
350
+ actor: string;
351
+ event: ProvisioningAuditEvent;
352
+ status: ProvisioningAuditStatus;
353
+ detail: unknown | null;
354
+ error: string | null;
414
355
  created_at: Generated<Date>;
415
- updated_at: Generated<Date>;
416
356
  }
417
357
  interface Gap {
418
358
  gapStart: number;