@opengeni/db 0.12.1 → 0.13.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 (37) hide show
  1. package/dist/chunk-ELMUCYZF.js +906 -0
  2. package/dist/chunk-ELMUCYZF.js.map +1 -0
  3. package/dist/chunk-N4WTE6SB.js +4033 -0
  4. package/dist/chunk-N4WTE6SB.js.map +1 -0
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +3594 -2059
  7. package/dist/index.js.map +1 -1
  8. package/dist/provision-roles.d.ts +472 -43
  9. package/dist/provision-roles.js +1 -1
  10. package/dist/{schema-BejThLcd.d.ts → schema-Bh1Hr7xY.d.ts} +2507 -970
  11. package/dist/schema.d.ts +1 -1
  12. package/dist/schema.js +9 -1
  13. package/drizzle/0122_codex_capacity_same_turn.sql +59 -0
  14. package/drizzle/0123_session_tool_policy_version.sql +14 -0
  15. package/drizzle/0124_session_event_duplicate_lookup.sql +4 -0
  16. package/drizzle/0125_document_drops_visibility.sql +29 -0
  17. package/drizzle/0126_document_access_constraints.sql +138 -0
  18. package/drizzle/0127_document_default_base_index.sql +5 -0
  19. package/drizzle/0128_github_installation_authority.sql +69 -0
  20. package/drizzle/0129_retained_process_reconciliation.sql +356 -0
  21. package/drizzle/0130_workspace_instruction_policies.sql +330 -0
  22. package/drizzle/0131_slack_bot_install_and_post_idempotency.sql +133 -0
  23. package/package.json +4 -3
  24. package/src/event-payload-sanitizer.ts +167 -55
  25. package/src/index.ts +1850 -264
  26. package/src/new-session-drafts.ts +127 -6
  27. package/src/provision-roles.ts +184 -2
  28. package/src/runtime-posture-cli.ts +57 -0
  29. package/src/runtime-posture.ts +770 -0
  30. package/src/schema.ts +243 -3
  31. package/src/session-control.ts +1 -0
  32. package/src/workspace-instruction-policies-schema.ts +140 -0
  33. package/src/workspace-instruction-policies.ts +624 -0
  34. package/dist/chunk-BMFDXFPA.js +0 -155
  35. package/dist/chunk-BMFDXFPA.js.map +0 -1
  36. package/dist/chunk-VUKRIBO5.js +0 -3679
  37. package/dist/chunk-VUKRIBO5.js.map +0 -1
package/src/schema.ts CHANGED
@@ -485,6 +485,12 @@ export const connections = pgTable(
485
485
  lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
486
486
  lastError: text("last_error"),
487
487
  version: integer("version").notNull().default(1),
488
+ // Server-owned proof that the dedicated install route verified the exact
489
+ // credential at this connection version. Generic/legacy writers cannot set
490
+ // these columns, and migration 0131 clears them when protected fields change
491
+ // without a fresh verification in the same statement.
492
+ verifiedInstallAt: timestamp("verified_install_at", { withTimezone: true }),
493
+ verifiedInstallVersion: integer("verified_install_version"),
488
494
  metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
489
495
  createdBySubjectId: text("created_by_subject_id"),
490
496
  updatedBySubjectId: text("updated_by_subject_id"),
@@ -510,6 +516,85 @@ export const connections = pgTable(
510
516
  }),
511
517
  );
512
518
 
519
+ // Durable provider-operation identity for OpenGeni Slack bot posts. The
520
+ // caller-supplied operation UUID is also Slack's client_msg_id; a bounded claim
521
+ // serializes live attempts while an expired/released claim can safely retry the
522
+ // same provider identity after response loss or process death.
523
+ export const slackBotPostOperations = pgTable(
524
+ "slack_bot_post_operations",
525
+ {
526
+ id: uuid("id").primaryKey().defaultRandom(),
527
+ accountId: uuid("account_id")
528
+ .notNull()
529
+ .references(() => managedAccounts.id, { onDelete: "cascade" }),
530
+ workspaceId: uuid("workspace_id")
531
+ .notNull()
532
+ .references(() => workspaces.id, { onDelete: "cascade" }),
533
+ connectionId: uuid("connection_id")
534
+ .notNull()
535
+ .references(() => connections.id, { onDelete: "cascade" }),
536
+ operationId: uuid("operation_id").notNull(),
537
+ clientMessageId: uuid("client_message_id").notNull(),
538
+ targetKind: text("target_kind").$type<"channel" | "user">().notNull(),
539
+ targetId: text("target_id").notNull(),
540
+ requestDigest: text("request_digest").notNull(),
541
+ status: text("status").$type<"provider_started" | "completed">().notNull(),
542
+ claimHolderId: uuid("claim_holder_id"),
543
+ claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
544
+ attemptCount: integer("attempt_count").notNull().default(0),
545
+ lastFailureCode: text("last_failure_code"),
546
+ slackChannelId: text("slack_channel_id"),
547
+ slackMessageTimestamp: text("slack_message_timestamp"),
548
+ completedAt: timestamp("completed_at", { withTimezone: true }),
549
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
550
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
551
+ },
552
+ (table) => ({
553
+ workspaceOperation: uniqueIndex("slack_bot_post_operations_workspace_operation_uq").on(
554
+ table.workspaceId,
555
+ table.connectionId,
556
+ table.operationId,
557
+ ),
558
+ workspaceStatus: index("slack_bot_post_operations_workspace_status_idx").on(
559
+ table.workspaceId,
560
+ table.status,
561
+ table.updatedAt,
562
+ ),
563
+ targetKindValid: check(
564
+ "slack_bot_post_operations_target_kind_check",
565
+ sql`${table.targetKind} in ('channel', 'user')`,
566
+ ),
567
+ statusValid: check(
568
+ "slack_bot_post_operations_status_check",
569
+ sql`${table.status} in ('provider_started', 'completed')`,
570
+ ),
571
+ identityValid: check(
572
+ "slack_bot_post_operations_identity_check",
573
+ sql`${table.clientMessageId} = ${table.operationId}
574
+ and length(${table.targetId}) between 1 and 64
575
+ and ${table.requestDigest} ~ '^[0-9a-f]{64}$'
576
+ and ${table.attemptCount} > 0
577
+ and ((${table.claimHolderId} is null) = (${table.claimExpiresAt} is null))`,
578
+ ),
579
+ completionValid: check(
580
+ "slack_bot_post_operations_completion_check",
581
+ sql`(
582
+ ${table.status} = 'provider_started'
583
+ and ${table.slackChannelId} is null
584
+ and ${table.slackMessageTimestamp} is null
585
+ and ${table.completedAt} is null
586
+ ) or (
587
+ ${table.status} = 'completed'
588
+ and ${table.claimHolderId} is null
589
+ and ${table.claimExpiresAt} is null
590
+ and ${table.slackChannelId} is not null
591
+ and ${table.slackMessageTimestamp} is not null
592
+ and ${table.completedAt} is not null
593
+ )`,
594
+ ),
595
+ }),
596
+ );
597
+
513
598
  // OAuth client registrations minted through MCP DCR, keyed by authorization
514
599
  // server issuer. This is deployment-wide client identity, not a workspace
515
600
  // credential; per-user/provider tokens still live only in connections.
@@ -756,6 +841,8 @@ export const sessions = pgTable(
756
841
  // mapSession exposes those rows as `legacy` instead of guessing omitted vs
757
842
  // explicit [].
758
843
  toolPolicy: jsonb("tool_policy").$type<SessionToolPolicy>(),
844
+ // Optimistic-concurrency fence for durable session tool-policy writes.
845
+ toolPolicyVersion: integer("tool_policy_version").notNull().default(1),
759
846
  // The manager session that spawned this one via session_create. Set only
760
847
  // when the creating grant carried a worker-signed sessionId claim (a session
761
848
  // spawning a worker); null for direct API creates and scheduled-task runs.
@@ -1152,6 +1239,9 @@ export const documentBases = pgTable(
1152
1239
  table.workspaceId,
1153
1240
  table.createdAt,
1154
1241
  ),
1242
+ defaultName: uniqueIndex("document_bases_workspace_default_name_uq")
1243
+ .on(table.workspaceId)
1244
+ .where(sql`lower(btrim(${table.name})) = 'default'`),
1155
1245
  }),
1156
1246
  );
1157
1247
 
@@ -1185,6 +1275,17 @@ export const documents = pgTable(
1185
1275
  sourceUpdatedAt: timestamp("source_updated_at", { withTimezone: true }),
1186
1276
  sourceVersion: text("source_version"),
1187
1277
  aclTags: jsonb("acl_tags").$type<string[]>().notNull().default([]),
1278
+ // Per-document access controls. visibility 'private' restricts human reads to
1279
+ // created_by (a grant subject id, not a uuid); agent_access=false hides the
1280
+ // document from agent retrieval surfaces (docs MCP) while humans keep REST.
1281
+ visibility: text("visibility").notNull().default("workspace"),
1282
+ createdBy: text("created_by"),
1283
+ agentAccess: boolean("agent_access").notNull().default(true),
1284
+ // Auto-curation output (knowledge drops).
1285
+ summary: text("summary"),
1286
+ topics: jsonb("topics").$type<string[]>().notNull().default([]),
1287
+ curationStatus: text("curation_status").notNull().default("none"),
1288
+ curation: jsonb("curation").$type<Record<string, unknown>>(),
1188
1289
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
1189
1290
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
1190
1291
  },
@@ -1207,6 +1308,27 @@ export const documents = pgTable(
1207
1308
  table.workspaceId,
1208
1309
  table.sourceExternalId,
1209
1310
  ),
1311
+ curationStatus: index("documents_workspace_curation_status_idx").on(
1312
+ table.workspaceId,
1313
+ table.curationStatus,
1314
+ ),
1315
+ visibilityState: check(
1316
+ "documents_visibility_chk",
1317
+ sql`${table.visibility} in ('workspace', 'private')`,
1318
+ ),
1319
+ curationState: check(
1320
+ "documents_curation_status_chk",
1321
+ sql`${table.curationStatus} in ('none', 'pending', 'suggested', 'auto_filed', 'failed')`,
1322
+ ),
1323
+ privateCreator: check(
1324
+ "documents_private_creator_chk",
1325
+ sql`${table.visibility} <> 'private' or nullif(btrim(${table.createdBy}), '') is not null`,
1326
+ ),
1327
+ topicsArray: check("documents_topics_array_chk", sql`jsonb_typeof(${table.topics}) = 'array'`),
1328
+ curationObject: check(
1329
+ "documents_curation_object_chk",
1330
+ sql`${table.curation} is null or jsonb_typeof(${table.curation}) = 'object'`,
1331
+ ),
1210
1332
  }),
1211
1333
  );
1212
1334
 
@@ -1485,7 +1607,8 @@ export const sessionTurnAttempts = pgTable(
1485
1607
  "session_turn_attempts_outcome_check",
1486
1608
  sql`${table.outcome} is null or ${table.outcome} in (
1487
1609
  'completed', 'failed', 'cancelled', 'superseded', 'requires_action',
1488
- 'interrupted_recoverable', 'lease_lost_recoverable', 'pre_cutover_closed'
1610
+ 'waiting_capacity', 'interrupted_recoverable', 'lease_lost_recoverable',
1611
+ 'pre_cutover_closed'
1489
1612
  )`,
1490
1613
  ),
1491
1614
  closedConsistent: check(
@@ -2014,12 +2137,13 @@ export const codexCapacityWaiters = pgTable(
2014
2137
  .notNull()
2015
2138
  .references(() => workspaces.id, { onDelete: "cascade" }),
2016
2139
  sessionId: uuid("session_id").notNull(),
2017
- goalId: uuid("goal_id").notNull(),
2140
+ goalId: uuid("goal_id"),
2018
2141
  blockedTurnId: uuid("blocked_turn_id").notNull(),
2142
+ blockedTurnGeneration: integer("blocked_turn_generation").notNull(),
2019
2143
  workflowId: text("workflow_id").notNull(),
2020
2144
  generation: integer("generation").notNull().default(1),
2021
2145
  status: text("status").notNull().default("waiting"), // waiting | resumed | superseded
2022
- goalVersion: integer("goal_version").notNull(),
2146
+ goalVersion: integer("goal_version"),
2023
2147
  policyHash: text("policy_hash"),
2024
2148
  earliestResetAt: timestamp("earliest_reset_at", { withTimezone: true }),
2025
2149
  nextCheckAt: timestamp("next_check_at", { withTimezone: true }).notNull(),
@@ -2118,6 +2242,7 @@ export const sessionEvents = pgTable(
2118
2242
  .where(
2119
2243
  sql`${table.type} not in ('agent.message.delta', 'agent.reasoning.delta', 'sandbox.command.output.delta', 'terminal.pty.output.delta')`,
2120
2244
  ),
2245
+ duplicateOfEvent: index("session_events_duplicate_of_event_idx").on(table.duplicateOfEventId),
2121
2246
  payloadBytes: check(
2122
2247
  "session_events_payload_bytes_check",
2123
2248
  sql`octet_length(${table.payload}::text) <= 65536`,
@@ -2499,6 +2624,9 @@ export const sandboxLeases = pgTable(
2499
2624
  reaperIdx: index("sandbox_leases_reaper_idx")
2500
2625
  .on(table.expiresAt)
2501
2626
  .where(sql`${table.liveness} in ('warming','warm','draining')`),
2627
+ expiredDrainingInventory: index("sandbox_leases_expired_draining_inventory_idx")
2628
+ .on(table.expiresAt, table.backend)
2629
+ .where(sql`${table.liveness} = 'draining'`),
2502
2630
  workspaceGenerationValid: check(
2503
2631
  "sandbox_leases_workspace_generation_check",
2504
2632
  sql`${table.workspaceGeneration} >= 0`,
@@ -2736,6 +2864,19 @@ export const sandboxRetainedProcesses = pgTable(
2736
2864
  settlementReason: text("settlement_reason"),
2737
2865
  startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
2738
2866
  settledAt: timestamp("settled_at", { withTimezone: true }),
2867
+ // Coordination state for bounded terminal-owner reconciliation. While a
2868
+ // claim is live, reconcileAfter is its expiry; otherwise it is the next
2869
+ // retry time. Becoming due only licenses an exact provider probe and is
2870
+ // never exit/loss proof.
2871
+ reconcileAfter: timestamp("reconcile_after", { withTimezone: true }).notNull().defaultNow(),
2872
+ reconcileClaimId: uuid("reconcile_claim_id"),
2873
+ reconcileClaimedAt: timestamp("reconcile_claimed_at", { withTimezone: true }),
2874
+ reconcileAttempts: integer("reconcile_attempts").notNull().default(0),
2875
+ lastReconcileOutcome: text("last_reconcile_outcome"),
2876
+ reconcileProofOutcome: text("reconcile_proof_outcome", { enum: ["exited", "lost"] }),
2877
+ reconcileProofExitCode: integer("reconcile_proof_exit_code"),
2878
+ reconcileProofReason: text("reconcile_proof_reason"),
2879
+ reconcileProofObservedAt: timestamp("reconcile_proof_observed_at", { withTimezone: true }),
2739
2880
  },
2740
2881
  (table) => ({
2741
2882
  workspaceAccount: foreignKey({
@@ -2788,6 +2929,12 @@ export const sandboxRetainedProcesses = pgTable(
2788
2929
  active: index("sandbox_retained_processes_active_idx")
2789
2930
  .on(table.workspaceId, table.sessionId, table.startedAt)
2790
2931
  .where(sql`${table.state} = 'active'`),
2932
+ reconcileDue: index("sandbox_retained_processes_reconcile_due_idx")
2933
+ .on(table.reconcileAfter, table.startedAt, table.id)
2934
+ .where(sql`${table.state} = 'active'`),
2935
+ activeInventory: index("sandbox_retained_processes_active_inventory_idx")
2936
+ .on(table.ownerActorKind, table.workspaceId, table.ownerTurnId, table.ownerAttemptId)
2937
+ .where(sql`${table.state} = 'active'`),
2791
2938
  identityValid: check(
2792
2939
  "sandbox_retained_processes_identity_check",
2793
2940
  sql`${table.leaseEpoch} >= 0
@@ -2824,6 +2971,41 @@ export const sandboxRetainedProcesses = pgTable(
2824
2971
  sql`${table.settlementReason} is null
2825
2972
  or octet_length(${table.settlementReason}) between 1 and 512`,
2826
2973
  ),
2974
+ reconcileClaimValid: check(
2975
+ "sandbox_retained_processes_reconcile_claim_check",
2976
+ sql`(${table.reconcileClaimId} is null and ${table.reconcileClaimedAt} is null)
2977
+ or (${table.reconcileClaimId} is not null and ${table.reconcileClaimedAt} is not null)`,
2978
+ ),
2979
+ reconcileAttemptsValid: check(
2980
+ "sandbox_retained_processes_reconcile_attempts_check",
2981
+ sql`${table.reconcileAttempts} >= 0`,
2982
+ ),
2983
+ reconcileOutcomeValid: check(
2984
+ "sandbox_retained_processes_reconcile_outcome_check",
2985
+ sql`${table.lastReconcileOutcome} is null
2986
+ or octet_length(${table.lastReconcileOutcome}) between 1 and 64`,
2987
+ ),
2988
+ reconcileProofValid: check(
2989
+ "sandbox_retained_processes_reconcile_proof_check",
2990
+ sql`(
2991
+ ${table.reconcileProofOutcome} is null
2992
+ and ${table.reconcileProofExitCode} is null
2993
+ and ${table.reconcileProofReason} is null
2994
+ and ${table.reconcileProofObservedAt} is null
2995
+ ) or (
2996
+ ${table.reconcileProofOutcome} = 'exited'
2997
+ and ${table.reconcileProofExitCode} is not null
2998
+ and ${table.reconcileProofReason} = 'provider_exit_banner'
2999
+ and ${table.reconcileProofObservedAt} is not null
3000
+ ) or (
3001
+ ${table.reconcileProofOutcome} = 'lost'
3002
+ and ${table.reconcileProofExitCode} is null
3003
+ and ${table.reconcileProofReason} in (
3004
+ 'provider_session_lost_banner', 'provider_instance_not_found'
3005
+ )
3006
+ and ${table.reconcileProofObservedAt} is not null
3007
+ )`,
3008
+ ),
2827
3009
  }),
2828
3010
  );
2829
3011
 
@@ -3410,10 +3592,17 @@ export const githubInstallations = pgTable(
3410
3592
  .notNull()
3411
3593
  .references(() => workspaces.id, { onDelete: "cascade" }),
3412
3594
  installationId: integer("installation_id").notNull(),
3595
+ githubAccountId: bigint("github_account_id", { mode: "number" }),
3413
3596
  accountLogin: text("account_login"),
3414
3597
  accountType: text("account_type"),
3415
3598
  repositoryScope: text("repository_scope").notNull().default("all"),
3416
3599
  linkedBySubjectId: text("linked_by_subject_id"),
3600
+ githubActorId: bigint("github_actor_id", { mode: "number" }),
3601
+ githubActorLogin: text("github_actor_login"),
3602
+ authorityKind: text("authority_kind"),
3603
+ authorityCheckedAt: timestamp("authority_checked_at", { withTimezone: true }),
3604
+ authorityExpiresAt: timestamp("authority_expires_at", { withTimezone: true }),
3605
+ authorityNonce: text("authority_nonce"),
3417
3606
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
3418
3607
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
3419
3608
  },
@@ -3428,6 +3617,55 @@ export const githubInstallations = pgTable(
3428
3617
  "github_installations_repository_scope_check",
3429
3618
  sql`${table.repositoryScope} in ('all', 'selected')`,
3430
3619
  ),
3620
+ authorityKindCheck: check(
3621
+ "github_installations_authority_kind_check",
3622
+ sql`
3623
+ (
3624
+ ${table.githubAccountId} is null
3625
+ and ${table.githubActorId} is null
3626
+ and ${table.githubActorLogin} is null
3627
+ and ${table.authorityKind} is null
3628
+ and ${table.authorityCheckedAt} is null
3629
+ and ${table.authorityExpiresAt} is null
3630
+ and ${table.authorityNonce} is null
3631
+ )
3632
+ or (
3633
+ ${table.githubAccountId} is not null
3634
+ and ${table.githubAccountId} > 0
3635
+ and ${table.githubActorId} is not null
3636
+ and ${table.githubActorId} > 0
3637
+ and ${table.githubActorLogin} is not null
3638
+ and length(${table.githubActorLogin}) > 0
3639
+ and ${table.accountLogin} is not null
3640
+ and length(${table.accountLogin}) > 0
3641
+ and ${table.accountType} is not null
3642
+ and ${table.linkedBySubjectId} is not null
3643
+ and length(${table.linkedBySubjectId}) > 0
3644
+ and ${table.authorityKind} is not null
3645
+ and ${table.authorityCheckedAt} is not null
3646
+ and ${table.authorityExpiresAt} is not null
3647
+ and ${table.authorityCheckedAt} < ${table.authorityExpiresAt}
3648
+ and ${table.authorityExpiresAt} <= ${table.authorityCheckedAt} + interval '10 minutes'
3649
+ and ${table.authorityNonce} is not null
3650
+ and length(${table.authorityNonce}) > 0
3651
+ and ${table.repositoryScope} = 'selected'
3652
+ and (
3653
+ (
3654
+ ${table.authorityKind} = 'personal_owner'
3655
+ and ${table.accountType} = 'User'
3656
+ and ${table.githubActorId} = ${table.githubAccountId}
3657
+ )
3658
+ or (
3659
+ ${table.authorityKind} = 'organization_owner'
3660
+ and ${table.accountType} = 'Organization'
3661
+ )
3662
+ )
3663
+ )
3664
+ `,
3665
+ ),
3666
+ authorityNonce: uniqueIndex("github_installations_authority_nonce_uq")
3667
+ .on(table.authorityNonce)
3668
+ .where(sql`${table.authorityNonce} is not null`),
3431
3669
  }),
3432
3670
  );
3433
3671
 
@@ -4180,3 +4418,5 @@ export const rigChanges = pgTable(
4180
4418
  workspaceStatus: index("rig_changes_workspace_status_idx").on(table.workspaceId, table.status),
4181
4419
  }),
4182
4420
  );
4421
+
4422
+ export * from "./workspace-instruction-policies-schema";
@@ -34,6 +34,7 @@ export type SessionTurnAttemptOutcome =
34
34
  | "cancelled"
35
35
  | "superseded"
36
36
  | "requires_action"
37
+ | "waiting_capacity"
37
38
  | "interrupted_recoverable"
38
39
  | "lease_lost_recoverable"
39
40
  | "pre_cutover_closed";
@@ -0,0 +1,140 @@
1
+ import { sql } from "drizzle-orm";
2
+ import {
3
+ bigint,
4
+ check,
5
+ index,
6
+ pgTable,
7
+ text,
8
+ timestamp,
9
+ uniqueIndex,
10
+ uuid,
11
+ } from "drizzle-orm/pg-core";
12
+
13
+ // Foreign keys and the cross-row revision/head integrity triggers live in the
14
+ // SQL migration. Keeping this additive schema leaf independent avoids a cycle
15
+ // back into schema.ts while retaining one canonical Drizzle namespace export.
16
+
17
+ export const workspaceInstructionPolicyRevisions = pgTable(
18
+ "workspace_instruction_policy_revisions",
19
+ {
20
+ id: uuid("id").primaryKey().defaultRandom(),
21
+ accountId: uuid("account_id").notNull(),
22
+ workspaceId: uuid("workspace_id").notNull(),
23
+ revision: bigint("revision", { mode: "number" })
24
+ .notNull()
25
+ .default(sql`nextval('workspace_instruction_policy_revision_seq')`),
26
+ kind: text("kind").notNull(),
27
+ scope: text("scope").notNull(),
28
+ roleKey: text("role_key"),
29
+ content: text("content").notNull(),
30
+ contentHash: text("content_hash").notNull(),
31
+ provenanceSource: text("provenance_source").notNull(),
32
+ provenanceSourceId: text("provenance_source_id"),
33
+ supersedesRevisionId: uuid("supersedes_revision_id"),
34
+ createdBySubjectId: text("created_by_subject_id").notNull(),
35
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
36
+ },
37
+ (table) => ({
38
+ workspaceRevision: uniqueIndex(
39
+ "workspace_instruction_policy_revisions_workspace_revision_uq",
40
+ ).on(table.workspaceId, table.revision),
41
+ workspaceHistory: index("workspace_instruction_policy_revisions_workspace_history_idx").on(
42
+ table.workspaceId,
43
+ table.kind,
44
+ table.scope,
45
+ table.roleKey,
46
+ table.revision,
47
+ ),
48
+ target: check(
49
+ "workspace_instruction_policy_revisions_target_chk",
50
+ sql`(
51
+ (${table.kind} = 'charter' and ${table.scope} = 'global' and ${table.roleKey} is null)
52
+ or (${table.kind} = 'policy' and ${table.scope} = 'global' and ${table.roleKey} is null)
53
+ or (${table.kind} = 'policy' and ${table.scope} = 'role' and ${table.roleKey} is not null)
54
+ )`,
55
+ ),
56
+ }),
57
+ );
58
+
59
+ export const workspaceInstructionPolicyHeads = pgTable(
60
+ "workspace_instruction_policy_heads",
61
+ {
62
+ id: uuid("id").primaryKey().defaultRandom(),
63
+ accountId: uuid("account_id").notNull(),
64
+ workspaceId: uuid("workspace_id").notNull(),
65
+ kind: text("kind").notNull(),
66
+ scope: text("scope").notNull(),
67
+ roleKey: text("role_key"),
68
+ revisionId: uuid("revision_id").notNull(),
69
+ revision: bigint("revision", { mode: "number" }).notNull(),
70
+ contentHash: text("content_hash").notNull(),
71
+ activationVersion: bigint("activation_version", { mode: "number" }).notNull(),
72
+ activatedAt: timestamp("activated_at", { withTimezone: true }).notNull().defaultNow(),
73
+ },
74
+ (table) => ({
75
+ charter: uniqueIndex("workspace_instruction_policy_heads_charter_uq")
76
+ .on(table.workspaceId)
77
+ .where(sql`${table.kind} = 'charter'`),
78
+ globalPolicy: uniqueIndex("workspace_instruction_policy_heads_global_policy_uq")
79
+ .on(table.workspaceId)
80
+ .where(sql`${table.kind} = 'policy' and ${table.scope} = 'global'`),
81
+ rolePolicy: uniqueIndex("workspace_instruction_policy_heads_role_policy_uq")
82
+ .on(table.workspaceId, table.roleKey)
83
+ .where(sql`${table.kind} = 'policy' and ${table.scope} = 'role'`),
84
+ target: check(
85
+ "workspace_instruction_policy_heads_target_chk",
86
+ sql`(
87
+ (${table.kind} = 'charter' and ${table.scope} = 'global' and ${table.roleKey} is null)
88
+ or (${table.kind} = 'policy' and ${table.scope} = 'global' and ${table.roleKey} is null)
89
+ or (${table.kind} = 'policy' and ${table.scope} = 'role' and ${table.roleKey} is not null)
90
+ )`,
91
+ ),
92
+ }),
93
+ );
94
+
95
+ export const workspaceInstructionPolicyActivationEvents = pgTable(
96
+ "workspace_instruction_policy_activation_events",
97
+ {
98
+ id: uuid("id").primaryKey().defaultRandom(),
99
+ accountId: uuid("account_id").notNull(),
100
+ workspaceId: uuid("workspace_id").notNull(),
101
+ kind: text("kind").notNull(),
102
+ scope: text("scope").notNull(),
103
+ roleKey: text("role_key"),
104
+ type: text("type").notNull(),
105
+ activationVersion: bigint("activation_version", { mode: "number" }).notNull(),
106
+ oldRevisionId: uuid("old_revision_id"),
107
+ oldRevision: bigint("old_revision", { mode: "number" }),
108
+ oldContentHash: text("old_content_hash"),
109
+ newRevisionId: uuid("new_revision_id").notNull(),
110
+ newRevision: bigint("new_revision", { mode: "number" }).notNull(),
111
+ newContentHash: text("new_content_hash").notNull(),
112
+ actorSubjectId: text("actor_subject_id").notNull(),
113
+ reason: text("reason").notNull(),
114
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
115
+ },
116
+ (table) => ({
117
+ workspaceActivationVersion: uniqueIndex(
118
+ "workspace_instruction_policy_events_target_version_uq",
119
+ ).on(
120
+ table.workspaceId,
121
+ table.kind,
122
+ table.scope,
123
+ sql`coalesce(${table.roleKey}, '')`,
124
+ table.activationVersion,
125
+ ),
126
+ workspaceTimeline: index("workspace_instruction_policy_events_workspace_time_idx").on(
127
+ table.workspaceId,
128
+ table.createdAt,
129
+ table.id,
130
+ ),
131
+ target: check(
132
+ "workspace_instruction_policy_activation_events_target_chk",
133
+ sql`(
134
+ (${table.kind} = 'charter' and ${table.scope} = 'global' and ${table.roleKey} is null)
135
+ or (${table.kind} = 'policy' and ${table.scope} = 'global' and ${table.roleKey} is null)
136
+ or (${table.kind} = 'policy' and ${table.scope} = 'role' and ${table.roleKey} is not null)
137
+ )`,
138
+ ),
139
+ }),
140
+ );