@secondlayer/shared 2.0.0 → 2.1.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 (34) hide show
  1. package/dist/src/db/index.d.ts +33 -1
  2. package/dist/src/db/queries/accounts.d.ts +27 -0
  3. package/dist/src/db/queries/integrity.d.ts +27 -0
  4. package/dist/src/db/queries/projects.d.ts +27 -0
  5. package/dist/src/db/queries/{marketplace.d.ts → provisioning-audit.d.ts} +48 -54
  6. package/dist/src/db/queries/provisioning-audit.js +40 -0
  7. package/dist/src/db/queries/provisioning-audit.js.map +10 -0
  8. package/dist/src/db/queries/subgraph-gaps.d.ts +27 -0
  9. package/dist/src/db/queries/subgraphs.d.ts +27 -0
  10. package/dist/src/db/queries/tenants.d.ts +35 -1
  11. package/dist/src/db/queries/tenants.js +27 -1
  12. package/dist/src/db/queries/tenants.js.map +3 -3
  13. package/dist/src/db/queries/usage.d.ts +27 -0
  14. package/dist/src/db/queries/workflows.d.ts +27 -0
  15. package/dist/src/db/schema.d.ts +33 -1
  16. package/dist/src/index.d.ts +60 -77
  17. package/dist/src/index.js +59 -69
  18. package/dist/src/index.js.map +4 -4
  19. package/dist/src/mode.d.ts +4 -5
  20. package/dist/src/mode.js.map +2 -2
  21. package/dist/src/node/local-client.d.ts +27 -0
  22. package/dist/src/schemas/accounts.d.ts +14 -0
  23. package/dist/src/schemas/{marketplace.js → accounts.js} +4 -14
  24. package/dist/src/schemas/accounts.js.map +10 -0
  25. package/dist/src/schemas/index.d.ts +28 -77
  26. package/dist/src/schemas/index.js +59 -69
  27. package/dist/src/schemas/index.js.map +4 -4
  28. package/migrations/0043_tenant_usage_monthly.ts +36 -0
  29. package/migrations/0044_provisioning_audit_log.ts +40 -0
  30. package/package.json +8 -8
  31. package/dist/src/db/queries/marketplace.js +0 -139
  32. package/dist/src/db/queries/marketplace.js.map +0 -10
  33. package/dist/src/schemas/marketplace.d.ts +0 -63
  34. package/dist/src/schemas/marketplace.js.map +0 -10
@@ -374,6 +374,8 @@ interface Database {
374
374
  workflow_signer_secrets: WorkflowSignerSecretsTable;
375
375
  workflow_budgets: WorkflowBudgetsTable;
376
376
  tenants: TenantsTable;
377
+ tenant_usage_monthly: TenantUsageMonthlyTable;
378
+ provisioning_audit_log: ProvisioningAuditLogTable;
377
379
  }
378
380
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
379
381
  interface TenantsTable {
@@ -407,6 +409,36 @@ interface TenantsTable {
407
409
  type Tenant = Selectable<TenantsTable>;
408
410
  type InsertTenant = Insertable<TenantsTable>;
409
411
  type UpdateTenant = Updateable<TenantsTable>;
412
+ interface TenantUsageMonthlyTable {
413
+ id: Generated<string>;
414
+ tenant_id: string;
415
+ period_month: Date;
416
+ storage_peak_mb: Generated<number>;
417
+ storage_avg_mb: Generated<number>;
418
+ storage_last_mb: Generated<number>;
419
+ measurements: Generated<number>;
420
+ first_at: Generated<Date>;
421
+ last_at: Generated<Date>;
422
+ }
423
+ type TenantUsageMonthly = Selectable<TenantUsageMonthlyTable>;
424
+ type InsertTenantUsageMonthly = Insertable<TenantUsageMonthlyTable>;
425
+ type UpdateTenantUsageMonthly = Updateable<TenantUsageMonthlyTable>;
426
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
427
+ type ProvisioningAuditStatus = "ok" | "error";
428
+ interface ProvisioningAuditLogTable {
429
+ id: Generated<string>;
430
+ tenant_id: string | null;
431
+ tenant_slug: string | null;
432
+ account_id: string | null;
433
+ actor: string;
434
+ event: ProvisioningAuditEvent;
435
+ status: ProvisioningAuditStatus;
436
+ detail: unknown | null;
437
+ error: string | null;
438
+ created_at: Generated<Date>;
439
+ }
440
+ type ProvisioningAuditLog = Selectable<ProvisioningAuditLogTable>;
441
+ type InsertProvisioningAuditLog = Insertable<ProvisioningAuditLogTable>;
410
442
  interface WorkflowBudgetsTable {
411
443
  id: Generated<string>;
412
444
  workflow_definition_id: string;
@@ -525,4 +557,4 @@ declare function getDb(connectionString?: string): Kysely<Database>;
525
557
  declare function getRawClient(role?: "source" | "target"): ReturnType<typeof postgres>;
526
558
  /** Close all DB connection pools. Call in CLI commands to allow process exit. */
527
559
  declare function closeDb(): Promise<void>;
528
- export { sql, parseJsonb, jsonb, getTargetDb, getSourceDb, getRawClient, getDb, closeDb, WorkflowStepsTable, WorkflowStep, WorkflowSignerSecretsTable, WorkflowSignerSecret, WorkflowSchedulesTable, WorkflowSchedule, WorkflowRunsTable, WorkflowRun, WorkflowQueueTable, WorkflowQueueItem, WorkflowDefinitionsTable, WorkflowDefinition, WorkflowCursorsTable, WorkflowCursor, WorkflowBudgetsTable, WorkflowBudget, WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateWorkflowStep, UpdateWorkflowSignerSecret, UpdateWorkflowSchedule, UpdateWorkflowRun, UpdateWorkflowDefinition, UpdateWorkflowBudget, UpdateTransaction, UpdateTenant, UpdateSubgraph, UpdateProject, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, TransactionsTable, Transaction, TenantsTable, TenantStatus, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ProjectsTable, Project, MagicLinksTable, MagicLink, InsertWorkflowStep, InsertWorkflowSignerSecret, InsertWorkflowSchedule, InsertWorkflowRun, InsertWorkflowQueueItem, InsertWorkflowDefinition, InsertWorkflowBudget, InsertTransaction, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubgraphUsageDaily, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Database, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
560
+ export { sql, parseJsonb, jsonb, getTargetDb, getSourceDb, getRawClient, getDb, closeDb, WorkflowStepsTable, WorkflowStep, WorkflowSignerSecretsTable, WorkflowSignerSecret, WorkflowSchedulesTable, WorkflowSchedule, WorkflowRunsTable, WorkflowRun, WorkflowQueueTable, WorkflowQueueItem, WorkflowDefinitionsTable, WorkflowDefinition, WorkflowCursorsTable, WorkflowCursor, WorkflowBudgetsTable, WorkflowBudget, WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateWorkflowStep, UpdateWorkflowSignerSecret, UpdateWorkflowSchedule, UpdateWorkflowRun, UpdateWorkflowDefinition, UpdateWorkflowBudget, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenant, UpdateSubgraph, UpdateProject, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, TransactionsTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantStatus, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, MagicLinksTable, MagicLink, InsertWorkflowStep, InsertWorkflowSignerSecret, InsertWorkflowSchedule, InsertWorkflowRun, InsertWorkflowQueueItem, InsertWorkflowDefinition, InsertWorkflowBudget, InsertTransaction, InsertTenantUsageMonthly, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubgraphUsageDaily, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Database, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
@@ -361,6 +361,8 @@ interface Database {
361
361
  workflow_signer_secrets: WorkflowSignerSecretsTable;
362
362
  workflow_budgets: WorkflowBudgetsTable;
363
363
  tenants: TenantsTable;
364
+ tenant_usage_monthly: TenantUsageMonthlyTable;
365
+ provisioning_audit_log: ProvisioningAuditLogTable;
364
366
  }
365
367
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
366
368
  interface TenantsTable {
@@ -391,6 +393,31 @@ interface TenantsTable {
391
393
  created_at: Generated<Date>;
392
394
  updated_at: Generated<Date>;
393
395
  }
396
+ interface TenantUsageMonthlyTable {
397
+ id: Generated<string>;
398
+ tenant_id: string;
399
+ period_month: Date;
400
+ storage_peak_mb: Generated<number>;
401
+ storage_avg_mb: Generated<number>;
402
+ storage_last_mb: Generated<number>;
403
+ measurements: Generated<number>;
404
+ first_at: Generated<Date>;
405
+ last_at: Generated<Date>;
406
+ }
407
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
408
+ type ProvisioningAuditStatus = "ok" | "error";
409
+ interface ProvisioningAuditLogTable {
410
+ id: Generated<string>;
411
+ tenant_id: string | null;
412
+ tenant_slug: string | null;
413
+ account_id: string | null;
414
+ actor: string;
415
+ event: ProvisioningAuditEvent;
416
+ status: ProvisioningAuditStatus;
417
+ detail: unknown | null;
418
+ error: string | null;
419
+ created_at: Generated<Date>;
420
+ }
394
421
  interface WorkflowBudgetsTable {
395
422
  id: Generated<string>;
396
423
  workflow_definition_id: string;
@@ -360,6 +360,8 @@ interface Database {
360
360
  workflow_signer_secrets: WorkflowSignerSecretsTable;
361
361
  workflow_budgets: WorkflowBudgetsTable;
362
362
  tenants: TenantsTable;
363
+ tenant_usage_monthly: TenantUsageMonthlyTable;
364
+ provisioning_audit_log: ProvisioningAuditLogTable;
363
365
  }
364
366
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
365
367
  interface TenantsTable {
@@ -390,6 +392,31 @@ interface TenantsTable {
390
392
  created_at: Generated<Date>;
391
393
  updated_at: Generated<Date>;
392
394
  }
395
+ interface TenantUsageMonthlyTable {
396
+ id: Generated<string>;
397
+ tenant_id: string;
398
+ period_month: Date;
399
+ storage_peak_mb: Generated<number>;
400
+ storage_avg_mb: Generated<number>;
401
+ storage_last_mb: Generated<number>;
402
+ measurements: Generated<number>;
403
+ first_at: Generated<Date>;
404
+ last_at: Generated<Date>;
405
+ }
406
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
407
+ type ProvisioningAuditStatus = "ok" | "error";
408
+ interface ProvisioningAuditLogTable {
409
+ id: Generated<string>;
410
+ tenant_id: string | null;
411
+ tenant_slug: string | null;
412
+ account_id: string | null;
413
+ actor: string;
414
+ event: ProvisioningAuditEvent;
415
+ status: ProvisioningAuditStatus;
416
+ detail: unknown | null;
417
+ error: string | null;
418
+ created_at: Generated<Date>;
419
+ }
393
420
  interface WorkflowBudgetsTable {
394
421
  id: Generated<string>;
395
422
  workflow_definition_id: string;
@@ -360,6 +360,8 @@ interface Database {
360
360
  workflow_signer_secrets: WorkflowSignerSecretsTable;
361
361
  workflow_budgets: WorkflowBudgetsTable;
362
362
  tenants: TenantsTable;
363
+ tenant_usage_monthly: TenantUsageMonthlyTable;
364
+ provisioning_audit_log: ProvisioningAuditLogTable;
363
365
  }
364
366
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
365
367
  interface TenantsTable {
@@ -390,6 +392,31 @@ interface TenantsTable {
390
392
  created_at: Generated<Date>;
391
393
  updated_at: Generated<Date>;
392
394
  }
395
+ interface TenantUsageMonthlyTable {
396
+ id: Generated<string>;
397
+ tenant_id: string;
398
+ period_month: Date;
399
+ storage_peak_mb: Generated<number>;
400
+ storage_avg_mb: Generated<number>;
401
+ storage_last_mb: Generated<number>;
402
+ measurements: Generated<number>;
403
+ first_at: Generated<Date>;
404
+ last_at: Generated<Date>;
405
+ }
406
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
407
+ type ProvisioningAuditStatus = "ok" | "error";
408
+ interface ProvisioningAuditLogTable {
409
+ id: Generated<string>;
410
+ tenant_id: string | null;
411
+ tenant_slug: string | null;
412
+ account_id: string | null;
413
+ actor: string;
414
+ event: ProvisioningAuditEvent;
415
+ status: ProvisioningAuditStatus;
416
+ detail: unknown | null;
417
+ error: string | null;
418
+ created_at: Generated<Date>;
419
+ }
393
420
  interface WorkflowBudgetsTable {
394
421
  id: Generated<string>;
395
422
  workflow_definition_id: string;
@@ -360,6 +360,8 @@ interface Database {
360
360
  workflow_signer_secrets: WorkflowSignerSecretsTable;
361
361
  workflow_budgets: WorkflowBudgetsTable;
362
362
  tenants: TenantsTable;
363
+ tenant_usage_monthly: TenantUsageMonthlyTable;
364
+ provisioning_audit_log: ProvisioningAuditLogTable;
363
365
  }
364
366
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
365
367
  interface TenantsTable {
@@ -390,6 +392,32 @@ interface TenantsTable {
390
392
  created_at: Generated<Date>;
391
393
  updated_at: Generated<Date>;
392
394
  }
395
+ interface TenantUsageMonthlyTable {
396
+ id: Generated<string>;
397
+ tenant_id: string;
398
+ period_month: Date;
399
+ storage_peak_mb: Generated<number>;
400
+ storage_avg_mb: Generated<number>;
401
+ storage_last_mb: Generated<number>;
402
+ measurements: Generated<number>;
403
+ first_at: Generated<Date>;
404
+ last_at: Generated<Date>;
405
+ }
406
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
407
+ type ProvisioningAuditStatus = "ok" | "error";
408
+ interface ProvisioningAuditLogTable {
409
+ id: Generated<string>;
410
+ tenant_id: string | null;
411
+ tenant_slug: string | null;
412
+ account_id: string | null;
413
+ actor: string;
414
+ event: ProvisioningAuditEvent;
415
+ status: ProvisioningAuditStatus;
416
+ detail: unknown | null;
417
+ error: string | null;
418
+ created_at: Generated<Date>;
419
+ }
420
+ type ProvisioningAuditLog = Selectable<ProvisioningAuditLogTable>;
393
421
  interface WorkflowBudgetsTable {
394
422
  id: Generated<string>;
395
423
  workflow_definition_id: string;
@@ -414,59 +442,25 @@ interface WorkflowSignerSecretsTable {
414
442
  created_at: Generated<Date>;
415
443
  updated_at: Generated<Date>;
416
444
  }
417
- type Subgraph = Selectable<SubgraphsTable>;
418
- /**
419
- * List public subgraphs with creator info and usage stats.
420
- */
421
- declare function listPublicSubgraphs(db: Kysely<Database>, opts?: {
422
- limit?: number
423
- offset?: number
424
- tags?: string[]
425
- search?: string
426
- sort?: "recent" | "popular" | "name"
427
- }): Promise<{
428
- data: any[]
429
- meta: {
430
- total: number
431
- limit: number
432
- offset: number
433
- }
434
- }>;
435
- /**
436
- * Get a single public subgraph by name with creator info.
437
- */
438
- declare function getPublicSubgraph(db: Kysely<Database>, name: string): Promise<any | undefined>;
439
- /**
440
- * Get a creator profile by slug with their public subgraphs.
441
- */
442
- declare function getCreatorProfile(db: Kysely<Database>, slug: string): Promise<{
443
- account: any
444
- subgraphs: any[]
445
- } | null>;
446
- /**
447
- * Publish a subgraph (set is_public = true).
448
- */
449
- declare function publishSubgraph(db: Kysely<Database>, subgraphId: string, opts?: {
450
- tags?: string[]
451
- description?: string
452
- }): Promise<Subgraph>;
453
- /**
454
- * Unpublish a subgraph (set is_public = false).
455
- */
456
- declare function unpublishSubgraph(db: Kysely<Database>, subgraphId: string): Promise<Subgraph>;
457
- /**
458
- * Increment per-subgraph query count for today. Fire-and-forget safe.
459
- */
460
- declare function incrementSubgraphQueryCount(db: Kysely<Database>, subgraphId: string): Promise<void>;
461
- /**
462
- * Get daily usage history for a subgraph.
463
- */
464
- declare function getSubgraphUsageHistory(db: Kysely<Database>, subgraphId: string, days: number): Promise<Array<{
465
- date: string
466
- query_count: number
467
- }>>;
468
445
  /**
469
- * Get total query count for a subgraph over last N days.
446
+ * Provisioning audit trail every lifecycle event that mutates a tenant
447
+ * lands here. Write on both happy and sad paths so we can reconstruct
448
+ * what was attempted and what failed.
449
+ *
450
+ * `actor` is the logical source (e.g. `account:<uuid>`, `worker:tenant-health`,
451
+ * `admin:<uuid>`). Keep it grep-able — this is the only breadcrumb when
452
+ * something goes sideways.
470
453
  */
471
- declare function getSubgraphQueryTotal(db: Kysely<Database>, subgraphId: string, days: number): Promise<number>;
472
- export { unpublishSubgraph, publishSubgraph, listPublicSubgraphs, incrementSubgraphQueryCount, getSubgraphUsageHistory, getSubgraphQueryTotal, getPublicSubgraph, getCreatorProfile };
454
+ interface AuditInput {
455
+ tenantId?: string | null;
456
+ tenantSlug?: string | null;
457
+ accountId?: string | null;
458
+ actor: string;
459
+ event: ProvisioningAuditEvent;
460
+ status: ProvisioningAuditStatus;
461
+ detail?: unknown;
462
+ error?: string;
463
+ }
464
+ declare function recordProvisioningAudit(db: Kysely<Database>, input: AuditInput): Promise<void>;
465
+ declare function listAuditForTenant(db: Kysely<Database>, tenantId: string, limit?: number): Promise<ProvisioningAuditLog[]>;
466
+ export { recordProvisioningAudit, listAuditForTenant, AuditInput };
@@ -0,0 +1,40 @@
1
+ import { createRequire } from "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+
17
+ // src/db/queries/provisioning-audit.ts
18
+ async function recordProvisioningAudit(db, input) {
19
+ const row = {
20
+ tenant_id: input.tenantId ?? null,
21
+ tenant_slug: input.tenantSlug ?? null,
22
+ account_id: input.accountId ?? null,
23
+ actor: input.actor,
24
+ event: input.event,
25
+ status: input.status,
26
+ detail: input.detail ?? null,
27
+ error: input.error ?? null
28
+ };
29
+ await db.insertInto("provisioning_audit_log").values(row).execute();
30
+ }
31
+ async function listAuditForTenant(db, tenantId, limit = 50) {
32
+ return db.selectFrom("provisioning_audit_log").selectAll().where("tenant_id", "=", tenantId).orderBy("created_at", "desc").limit(limit).execute();
33
+ }
34
+ export {
35
+ recordProvisioningAudit,
36
+ listAuditForTenant
37
+ };
38
+
39
+ //# debugId=DF2744A82ED5118E64756E2164756E21
40
+ //# sourceMappingURL=provisioning-audit.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/db/queries/provisioning-audit.ts"],
4
+ "sourcesContent": [
5
+ "import type { Kysely } from \"kysely\";\nimport type {\n\tDatabase,\n\tInsertProvisioningAuditLog,\n\tProvisioningAuditEvent,\n\tProvisioningAuditLog,\n\tProvisioningAuditStatus,\n} from \"../types.ts\";\n\n/**\n * Provisioning audit trail — every lifecycle event that mutates a tenant\n * lands here. Write on both happy and sad paths so we can reconstruct\n * what was attempted and what failed.\n *\n * `actor` is the logical source (e.g. `account:<uuid>`, `worker:tenant-health`,\n * `admin:<uuid>`). Keep it grep-able — this is the only breadcrumb when\n * something goes sideways.\n */\n\nexport interface AuditInput {\n\ttenantId?: string | null;\n\ttenantSlug?: string | null;\n\taccountId?: string | null;\n\tactor: string;\n\tevent: ProvisioningAuditEvent;\n\tstatus: ProvisioningAuditStatus;\n\tdetail?: unknown;\n\terror?: string;\n}\n\nexport async function recordProvisioningAudit(\n\tdb: Kysely<Database>,\n\tinput: AuditInput,\n): Promise<void> {\n\tconst row: InsertProvisioningAuditLog = {\n\t\ttenant_id: input.tenantId ?? null,\n\t\ttenant_slug: input.tenantSlug ?? null,\n\t\taccount_id: input.accountId ?? null,\n\t\tactor: input.actor,\n\t\tevent: input.event,\n\t\tstatus: input.status,\n\t\tdetail: input.detail ?? null,\n\t\terror: input.error ?? null,\n\t};\n\tawait db.insertInto(\"provisioning_audit_log\").values(row).execute();\n}\n\nexport async function listAuditForTenant(\n\tdb: Kysely<Database>,\n\ttenantId: string,\n\tlimit = 50,\n): Promise<ProvisioningAuditLog[]> {\n\treturn db\n\t\t.selectFrom(\"provisioning_audit_log\")\n\t\t.selectAll()\n\t\t.where(\"tenant_id\", \"=\", tenantId)\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.limit(limit)\n\t\t.execute();\n}\n"
6
+ ],
7
+ "mappings": ";;;;;;;;;;;;;;;;;AA8BA,eAAsB,uBAAuB,CAC5C,IACA,OACgB;AAAA,EAChB,MAAM,MAAkC;AAAA,IACvC,WAAW,MAAM,YAAY;AAAA,IAC7B,aAAa,MAAM,cAAc;AAAA,IACjC,YAAY,MAAM,aAAa;AAAA,IAC/B,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM,UAAU;AAAA,IACxB,OAAO,MAAM,SAAS;AAAA,EACvB;AAAA,EACA,MAAM,GAAG,WAAW,wBAAwB,EAAE,OAAO,GAAG,EAAE,QAAQ;AAAA;AAGnE,eAAsB,kBAAkB,CACvC,IACA,UACA,QAAQ,IAC0B;AAAA,EAClC,OAAO,GACL,WAAW,wBAAwB,EACnC,UAAU,EACV,MAAM,aAAa,KAAK,QAAQ,EAChC,QAAQ,cAAc,MAAM,EAC5B,MAAM,KAAK,EACX,QAAQ;AAAA;",
8
+ "debugId": "DF2744A82ED5118E64756E2164756E21",
9
+ "names": []
10
+ }
@@ -360,6 +360,8 @@ interface Database {
360
360
  workflow_signer_secrets: WorkflowSignerSecretsTable;
361
361
  workflow_budgets: WorkflowBudgetsTable;
362
362
  tenants: TenantsTable;
363
+ tenant_usage_monthly: TenantUsageMonthlyTable;
364
+ provisioning_audit_log: ProvisioningAuditLogTable;
363
365
  }
364
366
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
365
367
  interface TenantsTable {
@@ -390,6 +392,31 @@ interface TenantsTable {
390
392
  created_at: Generated<Date>;
391
393
  updated_at: Generated<Date>;
392
394
  }
395
+ interface TenantUsageMonthlyTable {
396
+ id: Generated<string>;
397
+ tenant_id: string;
398
+ period_month: Date;
399
+ storage_peak_mb: Generated<number>;
400
+ storage_avg_mb: Generated<number>;
401
+ storage_last_mb: Generated<number>;
402
+ measurements: Generated<number>;
403
+ first_at: Generated<Date>;
404
+ last_at: Generated<Date>;
405
+ }
406
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
407
+ type ProvisioningAuditStatus = "ok" | "error";
408
+ interface ProvisioningAuditLogTable {
409
+ id: Generated<string>;
410
+ tenant_id: string | null;
411
+ tenant_slug: string | null;
412
+ account_id: string | null;
413
+ actor: string;
414
+ event: ProvisioningAuditEvent;
415
+ status: ProvisioningAuditStatus;
416
+ detail: unknown | null;
417
+ error: string | null;
418
+ created_at: Generated<Date>;
419
+ }
393
420
  interface WorkflowBudgetsTable {
394
421
  id: Generated<string>;
395
422
  workflow_definition_id: string;
@@ -360,6 +360,8 @@ interface Database {
360
360
  workflow_signer_secrets: WorkflowSignerSecretsTable;
361
361
  workflow_budgets: WorkflowBudgetsTable;
362
362
  tenants: TenantsTable;
363
+ tenant_usage_monthly: TenantUsageMonthlyTable;
364
+ provisioning_audit_log: ProvisioningAuditLogTable;
363
365
  }
364
366
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
365
367
  interface TenantsTable {
@@ -390,6 +392,31 @@ interface TenantsTable {
390
392
  created_at: Generated<Date>;
391
393
  updated_at: Generated<Date>;
392
394
  }
395
+ interface TenantUsageMonthlyTable {
396
+ id: Generated<string>;
397
+ tenant_id: string;
398
+ period_month: Date;
399
+ storage_peak_mb: Generated<number>;
400
+ storage_avg_mb: Generated<number>;
401
+ storage_last_mb: Generated<number>;
402
+ measurements: Generated<number>;
403
+ first_at: Generated<Date>;
404
+ last_at: Generated<Date>;
405
+ }
406
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
407
+ type ProvisioningAuditStatus = "ok" | "error";
408
+ interface ProvisioningAuditLogTable {
409
+ id: Generated<string>;
410
+ tenant_id: string | null;
411
+ tenant_slug: string | null;
412
+ account_id: string | null;
413
+ actor: string;
414
+ event: ProvisioningAuditEvent;
415
+ status: ProvisioningAuditStatus;
416
+ detail: unknown | null;
417
+ error: string | null;
418
+ created_at: Generated<Date>;
419
+ }
393
420
  interface WorkflowBudgetsTable {
394
421
  id: Generated<string>;
395
422
  workflow_definition_id: string;
@@ -360,6 +360,8 @@ interface Database {
360
360
  workflow_signer_secrets: WorkflowSignerSecretsTable;
361
361
  workflow_budgets: WorkflowBudgetsTable;
362
362
  tenants: TenantsTable;
363
+ tenant_usage_monthly: TenantUsageMonthlyTable;
364
+ provisioning_audit_log: ProvisioningAuditLogTable;
363
365
  }
364
366
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
365
367
  interface TenantsTable {
@@ -391,6 +393,31 @@ interface TenantsTable {
391
393
  updated_at: Generated<Date>;
392
394
  }
393
395
  type Tenant = Selectable<TenantsTable>;
396
+ interface TenantUsageMonthlyTable {
397
+ id: Generated<string>;
398
+ tenant_id: string;
399
+ period_month: Date;
400
+ storage_peak_mb: Generated<number>;
401
+ storage_avg_mb: Generated<number>;
402
+ storage_last_mb: Generated<number>;
403
+ measurements: Generated<number>;
404
+ first_at: Generated<Date>;
405
+ last_at: Generated<Date>;
406
+ }
407
+ type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
408
+ type ProvisioningAuditStatus = "ok" | "error";
409
+ interface ProvisioningAuditLogTable {
410
+ id: Generated<string>;
411
+ tenant_id: string | null;
412
+ tenant_slug: string | null;
413
+ account_id: string | null;
414
+ actor: string;
415
+ event: ProvisioningAuditEvent;
416
+ status: ProvisioningAuditStatus;
417
+ detail: unknown | null;
418
+ error: string | null;
419
+ created_at: Generated<Date>;
420
+ }
394
421
  interface WorkflowBudgetsTable {
395
422
  id: Generated<string>;
396
423
  workflow_definition_id: string;
@@ -450,6 +477,13 @@ declare function listExpiredTrials(db: Kysely<Database>, now?: Date): Promise<Te
450
477
  declare function listSuspendedOlderThan(db: Kysely<Database>, olderThan: Date): Promise<Tenant[]>;
451
478
  declare function setTenantStatus(db: Kysely<Database>, slug: string, status: TenantStatus): Promise<void>;
452
479
  declare function recordHealthCheck(db: Kysely<Database>, slug: string, storageUsedMb: number | null): Promise<void>;
480
+ /**
481
+ * Record a storage measurement into the current calendar month's bucket.
482
+ * Maintains peak, running average, and the most recent value in a single
483
+ * upsert. Billing will consume this later; for now the table just gives
484
+ * us evidence of usage over time.
485
+ */
486
+ declare function recordMonthlyUsage(db: Kysely<Database>, tenantId: string, storageMb: number): Promise<void>;
453
487
  declare function updateTenantPlan(db: Kysely<Database>, slug: string, plan: string, cpus: number, memoryMb: number, storageLimitMb: number): Promise<void>;
454
488
  type RotateType = "service" | "anon" | "both";
455
489
  /**
@@ -490,4 +524,4 @@ interface TenantCredentials {
490
524
  * CLI). Never log the returned object.
491
525
  */
492
526
  declare function getTenantCredentials(db: Kysely<Database>, slug: string): Promise<TenantCredentials | null>;
493
- export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan, listExpiredTrials, insertTenant, getTenantCredentials, getTenantBySlug, getTenantByAccount, deleteTenant, bumpTenantKeyGen, TenantCredentials, RotateType, NewTenantInput };
527
+ export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordMonthlyUsage, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan, listExpiredTrials, insertTenant, getTenantCredentials, getTenantBySlug, getTenantByAccount, deleteTenant, bumpTenantKeyGen, TenantCredentials, RotateType, NewTenantInput };
@@ -61,6 +61,7 @@ function generateSecretsKey() {
61
61
  }
62
62
 
63
63
  // src/db/queries/tenants.ts
64
+ import { sql } from "kysely";
64
65
  async function insertTenant(db, input) {
65
66
  const row = {
66
67
  account_id: input.accountId,
@@ -119,6 +120,30 @@ async function recordHealthCheck(db, slug, storageUsedMb) {
119
120
  updated_at: new Date
120
121
  }).where("slug", "=", slug).execute();
121
122
  }
123
+ async function recordMonthlyUsage(db, tenantId, storageMb) {
124
+ const now = new Date;
125
+ const periodMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
126
+ await sql`
127
+ INSERT INTO tenant_usage_monthly (
128
+ tenant_id, period_month,
129
+ storage_peak_mb, storage_avg_mb, storage_last_mb,
130
+ measurements, first_at, last_at
131
+ ) VALUES (
132
+ ${tenantId}, ${periodMonth},
133
+ ${storageMb}, ${storageMb}, ${storageMb},
134
+ 1, now(), now()
135
+ )
136
+ ON CONFLICT (tenant_id, period_month) DO UPDATE SET
137
+ storage_peak_mb = GREATEST(tenant_usage_monthly.storage_peak_mb, EXCLUDED.storage_last_mb),
138
+ storage_avg_mb = (
139
+ (tenant_usage_monthly.storage_avg_mb * tenant_usage_monthly.measurements + EXCLUDED.storage_last_mb)
140
+ / (tenant_usage_monthly.measurements + 1)
141
+ ),
142
+ storage_last_mb = EXCLUDED.storage_last_mb,
143
+ measurements = tenant_usage_monthly.measurements + 1,
144
+ last_at = now()
145
+ `.execute(db);
146
+ }
122
147
  async function updateTenantPlan(db, slug, plan, cpus, memoryMb, storageLimitMb) {
123
148
  await db.updateTable("tenants").set({
124
149
  plan,
@@ -178,6 +203,7 @@ export {
178
203
  updateTenantPlan,
179
204
  updateTenantKeys,
180
205
  setTenantStatus,
206
+ recordMonthlyUsage,
181
207
  recordHealthCheck,
182
208
  listTenantsByStatus,
183
209
  listSuspendedOlderThan,
@@ -190,5 +216,5 @@ export {
190
216
  bumpTenantKeyGen
191
217
  };
192
218
 
193
- //# debugId=1D0DA0A21FCE7D7664756E2164756E21
219
+ //# debugId=06506A808C9D324064756E2164756E21
194
220
  //# sourceMappingURL=tenants.js.map
@@ -3,9 +3,9 @@
3
3
  "sources": ["../src/crypto/secrets.ts", "../src/db/queries/tenants.ts"],
4
4
  "sourcesContent": [
5
5
  "import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\n\n/**\n * AES-256-GCM symmetric envelope for workflow signer secrets.\n *\n * Ciphertext layout: `iv (12 bytes) || authTag (16 bytes) || ciphertext`\n *\n * The key comes from `SECONDLAYER_SECRETS_KEY` — 32 bytes hex. Callers must\n * load + cache the key once per process. Rotation strategy: when a customer\n * wants to rotate keys, re-encrypt all rows with the new key and swap the\n * env var. Not zero-downtime, but acceptable at v2 scale.\n *\n * For real KMS (AWS KMS, HashiCorp Vault, GCP KMS), wrap the same byte\n * layout behind an `EncryptSecret` / `DecryptSecret` interface in the\n * runner and swap the implementation at startup.\n */\n\nconst KEY_ENV = \"SECONDLAYER_SECRETS_KEY\";\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nfunction loadKey(): Buffer {\n\tconst hex = process.env[KEY_ENV];\n\tif (!hex) {\n\t\tthrow new Error(\n\t\t\t`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`,\n\t\t);\n\t}\n\tconst key = Buffer.from(hex, \"hex\");\n\tif (key.length !== 32) {\n\t\tthrow new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);\n\t}\n\treturn key;\n}\n\nlet _cachedKey: Buffer | null = null;\nfunction getKey(): Buffer {\n\tif (!_cachedKey) _cachedKey = loadKey();\n\treturn _cachedKey;\n}\n\nexport function encryptSecret(plaintext: string): Buffer {\n\tconst key = getKey();\n\tconst iv = randomBytes(IV_LEN);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, \"utf8\"),\n\t\tcipher.final(),\n\t]);\n\tconst tag = cipher.getAuthTag();\n\treturn Buffer.concat([iv, tag, ciphertext]);\n}\n\nexport function decryptSecret(envelope: Buffer): string {\n\tconst key = getKey();\n\tconst iv = envelope.subarray(0, IV_LEN);\n\tconst tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);\n\tconst ciphertext = envelope.subarray(IV_LEN + TAG_LEN);\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, iv);\n\tdecipher.setAuthTag(tag);\n\treturn decipher.update(ciphertext).toString(\"utf8\") + decipher.final(\"utf8\");\n}\n\n/** Generate a fresh 32-byte hex key suitable for `SECONDLAYER_SECRETS_KEY`. */\nexport function generateSecretsKey(): string {\n\treturn randomBytes(32).toString(\"hex\");\n}\n",
6
- "import type { Kysely } from \"kysely\";\nimport { decryptSecret, encryptSecret } from \"../../crypto/secrets.ts\";\nimport type { Database, InsertTenant, Tenant, TenantStatus } from \"../types.ts\";\n\n/**\n * Tenant registry queries. Encrypted columns are stored as `bytea` and\n * transparently encrypted/decrypted via `encryptSecret`/`decryptSecret`.\n *\n * Never return decrypted values from listTenants — only `getTenantCredentials`\n * surfaces plaintext, and only when explicitly called by a caller that\n * needs to hand creds to a CLI or dashboard session.\n */\n\nexport interface NewTenantInput {\n\taccountId: string;\n\tslug: string;\n\tplan: string;\n\tcpus: number;\n\tmemoryMb: number;\n\tstorageLimitMb: number;\n\tpgContainerId: string;\n\tapiContainerId: string;\n\tprocessorContainerId: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n\ttrialEndsAt: Date;\n\tprojectId?: string;\n}\n\nexport async function insertTenant(\n\tdb: Kysely<Database>,\n\tinput: NewTenantInput,\n): Promise<Tenant> {\n\tconst row: InsertTenant = {\n\t\taccount_id: input.accountId,\n\t\tslug: input.slug,\n\t\tstatus: \"active\",\n\t\tplan: input.plan,\n\t\tcpus: input.cpus,\n\t\tmemory_mb: input.memoryMb,\n\t\tstorage_limit_mb: input.storageLimitMb,\n\t\tpg_container_id: input.pgContainerId,\n\t\tapi_container_id: input.apiContainerId,\n\t\tprocessor_container_id: input.processorContainerId,\n\t\ttarget_database_url_enc: encryptSecret(input.targetDatabaseUrl),\n\t\ttenant_jwt_secret_enc: encryptSecret(input.tenantJwtSecret),\n\t\tanon_key_enc: encryptSecret(input.anonKey),\n\t\tservice_key_enc: encryptSecret(input.serviceKey),\n\t\tapi_url_internal: input.apiUrlInternal,\n\t\tapi_url_public: input.apiUrlPublic,\n\t\ttrial_ends_at: input.trialEndsAt,\n\t\tproject_id: input.projectId ?? null,\n\t};\n\treturn db\n\t\t.insertInto(\"tenants\")\n\t\t.values(row)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getTenantByAccount(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"<>\", \"deleted\")\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function getTenantBySlug(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function listTenantsByStatus(\n\tdb: Kysely<Database>,\n\tstatus: TenantStatus,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", status)\n\t\t.execute();\n}\n\nexport async function listExpiredTrials(\n\tdb: Kysely<Database>,\n\tnow: Date = new Date(),\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"in\", [\"provisioning\", \"active\"])\n\t\t.where(\"trial_ends_at\", \"<\", now)\n\t\t.execute();\n}\n\nexport async function listSuspendedOlderThan(\n\tdb: Kysely<Database>,\n\tolderThan: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", \"suspended\")\n\t\t.where(\"suspended_at\", \"<\", olderThan)\n\t\t.execute();\n}\n\nexport async function setTenantStatus(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstatus: TenantStatus,\n): Promise<void> {\n\tconst patch: Record<string, unknown> = {\n\t\tstatus,\n\t\tupdated_at: new Date(),\n\t};\n\tif (status === \"suspended\") patch.suspended_at = new Date();\n\tif (status === \"active\") patch.suspended_at = null;\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\nexport async function recordHealthCheck(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstorageUsedMb: number | null,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tlast_health_check_at: new Date(),\n\t\t\tstorage_used_mb: storageUsedMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport async function updateTenantPlan(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tplan: string,\n\tcpus: number,\n\tmemoryMb: number,\n\tstorageLimitMb: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tplan,\n\t\t\tcpus,\n\t\t\tmemory_mb: memoryMb,\n\t\t\tstorage_limit_mb: storageLimitMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport type RotateType = \"service\" | \"anon\" | \"both\";\n\n/**\n * Bump the selected gen counter(s) by 1 and return the new values.\n * Used by the key-rotate endpoint to force the tenant API to reject\n * previously-issued tokens of the rotated role(s).\n */\nexport async function bumpTenantKeyGen(\n\tdb: Kysely<Database>,\n\tslug: string,\n\ttype: RotateType,\n): Promise<{ serviceGen: number; anonGen: number }> {\n\tconst bumpService = type === \"service\" || type === \"both\";\n\tconst bumpAnon = type === \"anon\" || type === \"both\";\n\tconst row = await db\n\t\t.updateTable(\"tenants\")\n\t\t.set((eb) => ({\n\t\t\tservice_gen: bumpService\n\t\t\t\t? eb(\"service_gen\", \"+\", 1)\n\t\t\t\t: eb.ref(\"service_gen\"),\n\t\t\tanon_gen: bumpAnon ? eb(\"anon_gen\", \"+\", 1) : eb.ref(\"anon_gen\"),\n\t\t\tupdated_at: new Date(),\n\t\t}))\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.returning([\"service_gen\", \"anon_gen\"])\n\t\t.executeTakeFirstOrThrow();\n\treturn { serviceGen: row.service_gen, anonGen: row.anon_gen };\n}\n\n/**\n * Replace the encrypted key columns after a successful rotate. Only the\n * rotated column(s) are written — the other stays untouched.\n */\nexport async function updateTenantKeys(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tkeys: { serviceKey?: string; anonKey?: string },\n): Promise<void> {\n\tconst patch: Record<string, unknown> = { updated_at: new Date() };\n\tif (keys.serviceKey) patch.service_key_enc = encryptSecret(keys.serviceKey);\n\tif (keys.anonKey) patch.anon_key_enc = encryptSecret(keys.anonKey);\n\tif (Object.keys(patch).length === 1) return; // only updated_at — nothing to write\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\n/**\n * Hard-delete a tenant row. Call only AFTER the provisioner has torn down\n * containers + volume; otherwise orphaned resources linger. Returns whether\n * a row was actually deleted.\n */\nexport async function deleteTenant(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<boolean> {\n\tconst res = await db\n\t\t.deleteFrom(\"tenants\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn (res.numDeletedRows ?? 0n) > 0n;\n}\n\nexport interface TenantCredentials {\n\tslug: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n}\n\n/**\n * Decrypts the four encrypted columns and returns them plaintext. Call\n * this only when surfacing credentials to an authorized caller (dashboard,\n * CLI). Never log the returned object.\n */\nexport async function getTenantCredentials(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<TenantCredentials | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select([\n\t\t\t\"slug\",\n\t\t\t\"target_database_url_enc\",\n\t\t\t\"tenant_jwt_secret_enc\",\n\t\t\t\"anon_key_enc\",\n\t\t\t\"service_key_enc\",\n\t\t\t\"api_url_internal\",\n\t\t\t\"api_url_public\",\n\t\t])\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\tif (!row) return null;\n\treturn {\n\t\tslug: row.slug,\n\t\ttargetDatabaseUrl: decryptSecret(row.target_database_url_enc),\n\t\ttenantJwtSecret: decryptSecret(row.tenant_jwt_secret_enc),\n\t\tanonKey: decryptSecret(row.anon_key_enc),\n\t\tserviceKey: decryptSecret(row.service_key_enc),\n\t\tapiUrlInternal: row.api_url_internal,\n\t\tapiUrlPublic: row.api_url_public,\n\t};\n}\n"
6
+ "import { type Kysely, sql } from \"kysely\";\nimport { decryptSecret, encryptSecret } from \"../../crypto/secrets.ts\";\nimport type { Database, InsertTenant, Tenant, TenantStatus } from \"../types.ts\";\n\n/**\n * Tenant registry queries. Encrypted columns are stored as `bytea` and\n * transparently encrypted/decrypted via `encryptSecret`/`decryptSecret`.\n *\n * Never return decrypted values from listTenants — only `getTenantCredentials`\n * surfaces plaintext, and only when explicitly called by a caller that\n * needs to hand creds to a CLI or dashboard session.\n */\n\nexport interface NewTenantInput {\n\taccountId: string;\n\tslug: string;\n\tplan: string;\n\tcpus: number;\n\tmemoryMb: number;\n\tstorageLimitMb: number;\n\tpgContainerId: string;\n\tapiContainerId: string;\n\tprocessorContainerId: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n\ttrialEndsAt: Date;\n\tprojectId?: string;\n}\n\nexport async function insertTenant(\n\tdb: Kysely<Database>,\n\tinput: NewTenantInput,\n): Promise<Tenant> {\n\tconst row: InsertTenant = {\n\t\taccount_id: input.accountId,\n\t\tslug: input.slug,\n\t\tstatus: \"active\",\n\t\tplan: input.plan,\n\t\tcpus: input.cpus,\n\t\tmemory_mb: input.memoryMb,\n\t\tstorage_limit_mb: input.storageLimitMb,\n\t\tpg_container_id: input.pgContainerId,\n\t\tapi_container_id: input.apiContainerId,\n\t\tprocessor_container_id: input.processorContainerId,\n\t\ttarget_database_url_enc: encryptSecret(input.targetDatabaseUrl),\n\t\ttenant_jwt_secret_enc: encryptSecret(input.tenantJwtSecret),\n\t\tanon_key_enc: encryptSecret(input.anonKey),\n\t\tservice_key_enc: encryptSecret(input.serviceKey),\n\t\tapi_url_internal: input.apiUrlInternal,\n\t\tapi_url_public: input.apiUrlPublic,\n\t\ttrial_ends_at: input.trialEndsAt,\n\t\tproject_id: input.projectId ?? null,\n\t};\n\treturn db\n\t\t.insertInto(\"tenants\")\n\t\t.values(row)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getTenantByAccount(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"<>\", \"deleted\")\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function getTenantBySlug(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function listTenantsByStatus(\n\tdb: Kysely<Database>,\n\tstatus: TenantStatus,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", status)\n\t\t.execute();\n}\n\nexport async function listExpiredTrials(\n\tdb: Kysely<Database>,\n\tnow: Date = new Date(),\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"in\", [\"provisioning\", \"active\"])\n\t\t.where(\"trial_ends_at\", \"<\", now)\n\t\t.execute();\n}\n\nexport async function listSuspendedOlderThan(\n\tdb: Kysely<Database>,\n\tolderThan: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", \"suspended\")\n\t\t.where(\"suspended_at\", \"<\", olderThan)\n\t\t.execute();\n}\n\nexport async function setTenantStatus(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstatus: TenantStatus,\n): Promise<void> {\n\tconst patch: Record<string, unknown> = {\n\t\tstatus,\n\t\tupdated_at: new Date(),\n\t};\n\tif (status === \"suspended\") patch.suspended_at = new Date();\n\tif (status === \"active\") patch.suspended_at = null;\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\nexport async function recordHealthCheck(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstorageUsedMb: number | null,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tlast_health_check_at: new Date(),\n\t\t\tstorage_used_mb: storageUsedMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\n/**\n * Record a storage measurement into the current calendar month's bucket.\n * Maintains peak, running average, and the most recent value in a single\n * upsert. Billing will consume this later; for now the table just gives\n * us evidence of usage over time.\n */\nexport async function recordMonthlyUsage(\n\tdb: Kysely<Database>,\n\ttenantId: string,\n\tstorageMb: number,\n): Promise<void> {\n\t// Bucket is the first day of the current month (UTC), so the unique\n\t// (tenant_id, period_month) constraint groups all samples cleanly.\n\tconst now = new Date();\n\tconst periodMonth = new Date(\n\t\tDate.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n\t);\n\n\t// Running mean: avg_new = (avg_old * n + x) / (n + 1). Doing it in SQL\n\t// keeps the write atomic — no read-modify-write race between ticks.\n\tawait sql`\n\t\tINSERT INTO tenant_usage_monthly (\n\t\t\ttenant_id, period_month,\n\t\t\tstorage_peak_mb, storage_avg_mb, storage_last_mb,\n\t\t\tmeasurements, first_at, last_at\n\t\t) VALUES (\n\t\t\t${tenantId}, ${periodMonth},\n\t\t\t${storageMb}, ${storageMb}, ${storageMb},\n\t\t\t1, now(), now()\n\t\t)\n\t\tON CONFLICT (tenant_id, period_month) DO UPDATE SET\n\t\t\tstorage_peak_mb = GREATEST(tenant_usage_monthly.storage_peak_mb, EXCLUDED.storage_last_mb),\n\t\t\tstorage_avg_mb = (\n\t\t\t\t(tenant_usage_monthly.storage_avg_mb * tenant_usage_monthly.measurements + EXCLUDED.storage_last_mb)\n\t\t\t\t/ (tenant_usage_monthly.measurements + 1)\n\t\t\t),\n\t\t\tstorage_last_mb = EXCLUDED.storage_last_mb,\n\t\t\tmeasurements = tenant_usage_monthly.measurements + 1,\n\t\t\tlast_at = now()\n\t`.execute(db);\n}\n\nexport async function updateTenantPlan(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tplan: string,\n\tcpus: number,\n\tmemoryMb: number,\n\tstorageLimitMb: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tplan,\n\t\t\tcpus,\n\t\t\tmemory_mb: memoryMb,\n\t\t\tstorage_limit_mb: storageLimitMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport type RotateType = \"service\" | \"anon\" | \"both\";\n\n/**\n * Bump the selected gen counter(s) by 1 and return the new values.\n * Used by the key-rotate endpoint to force the tenant API to reject\n * previously-issued tokens of the rotated role(s).\n */\nexport async function bumpTenantKeyGen(\n\tdb: Kysely<Database>,\n\tslug: string,\n\ttype: RotateType,\n): Promise<{ serviceGen: number; anonGen: number }> {\n\tconst bumpService = type === \"service\" || type === \"both\";\n\tconst bumpAnon = type === \"anon\" || type === \"both\";\n\tconst row = await db\n\t\t.updateTable(\"tenants\")\n\t\t.set((eb) => ({\n\t\t\tservice_gen: bumpService\n\t\t\t\t? eb(\"service_gen\", \"+\", 1)\n\t\t\t\t: eb.ref(\"service_gen\"),\n\t\t\tanon_gen: bumpAnon ? eb(\"anon_gen\", \"+\", 1) : eb.ref(\"anon_gen\"),\n\t\t\tupdated_at: new Date(),\n\t\t}))\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.returning([\"service_gen\", \"anon_gen\"])\n\t\t.executeTakeFirstOrThrow();\n\treturn { serviceGen: row.service_gen, anonGen: row.anon_gen };\n}\n\n/**\n * Replace the encrypted key columns after a successful rotate. Only the\n * rotated column(s) are written — the other stays untouched.\n */\nexport async function updateTenantKeys(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tkeys: { serviceKey?: string; anonKey?: string },\n): Promise<void> {\n\tconst patch: Record<string, unknown> = { updated_at: new Date() };\n\tif (keys.serviceKey) patch.service_key_enc = encryptSecret(keys.serviceKey);\n\tif (keys.anonKey) patch.anon_key_enc = encryptSecret(keys.anonKey);\n\tif (Object.keys(patch).length === 1) return; // only updated_at — nothing to write\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\n/**\n * Hard-delete a tenant row. Call only AFTER the provisioner has torn down\n * containers + volume; otherwise orphaned resources linger. Returns whether\n * a row was actually deleted.\n */\nexport async function deleteTenant(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<boolean> {\n\tconst res = await db\n\t\t.deleteFrom(\"tenants\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn (res.numDeletedRows ?? 0n) > 0n;\n}\n\nexport interface TenantCredentials {\n\tslug: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n}\n\n/**\n * Decrypts the four encrypted columns and returns them plaintext. Call\n * this only when surfacing credentials to an authorized caller (dashboard,\n * CLI). Never log the returned object.\n */\nexport async function getTenantCredentials(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<TenantCredentials | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select([\n\t\t\t\"slug\",\n\t\t\t\"target_database_url_enc\",\n\t\t\t\"tenant_jwt_secret_enc\",\n\t\t\t\"anon_key_enc\",\n\t\t\t\"service_key_enc\",\n\t\t\t\"api_url_internal\",\n\t\t\t\"api_url_public\",\n\t\t])\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\tif (!row) return null;\n\treturn {\n\t\tslug: row.slug,\n\t\ttargetDatabaseUrl: decryptSecret(row.target_database_url_enc),\n\t\ttenantJwtSecret: decryptSecret(row.tenant_jwt_secret_enc),\n\t\tanonKey: decryptSecret(row.anon_key_enc),\n\t\tserviceKey: decryptSecret(row.service_key_enc),\n\t\tapiUrlInternal: row.api_url_internal,\n\t\tapiUrlPublic: row.api_url_public,\n\t};\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "1D0DA0A21FCE7D7664756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "06506A808C9D324064756E2164756E21",
10
10
  "names": []
11
11
  }