@opengeni/db 0.31.1 → 0.36.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 (122) hide show
  1. package/dist/browser-auth.d.ts +209 -1
  2. package/dist/browser-downloads.d.ts +51 -0
  3. package/dist/browser-identities.d.ts +11 -2
  4. package/dist/browser-sessions.d.ts +83 -1
  5. package/dist/browser-state-artifacts.d.ts +75 -1
  6. package/dist/canonical-human-identities.d.ts +48 -0
  7. package/dist/canonical-human-identities.js +28 -0
  8. package/dist/canonical-human-identities.js.map +1 -0
  9. package/dist/capability-integrations.d.ts +8 -6
  10. package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
  11. package/dist/chunk-5KTIEDX7.js.map +1 -0
  12. package/dist/{chunk-NBE6CHRI.js → chunk-CQZ4QSP5.js} +49 -9
  13. package/dist/chunk-CQZ4QSP5.js.map +1 -0
  14. package/dist/chunk-F32YT7MP.js +117 -0
  15. package/dist/chunk-F32YT7MP.js.map +1 -0
  16. package/dist/{chunk-ISI2TIUG.js → chunk-JC5QPLUM.js} +30 -3
  17. package/dist/chunk-JC5QPLUM.js.map +1 -0
  18. package/dist/{chunk-F5FT7BEZ.js → chunk-P3RE4D2B.js} +2 -2
  19. package/dist/chunk-TAUTWOS5.js +259 -0
  20. package/dist/chunk-TAUTWOS5.js.map +1 -0
  21. package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
  22. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  23. package/dist/database.d.ts +5 -0
  24. package/dist/editable-artifact-durable-export.js +2 -2
  25. package/dist/editable-artifacts.js +3 -3
  26. package/dist/index.d.ts +438 -29
  27. package/dist/index.js +35640 -28969
  28. package/dist/index.js.map +1 -1
  29. package/dist/insights.d.ts +23 -7
  30. package/dist/integration-bindings.d.ts +24 -24
  31. package/dist/integration-facets.d.ts +99 -0
  32. package/dist/interaction-revisions.d.ts +11 -0
  33. package/dist/interaction-schema.d.ts +698 -34
  34. package/dist/provision-roles.js +1 -1
  35. package/dist/runtime-posture.d.ts +8 -7
  36. package/dist/schema.d.ts +9464 -5412
  37. package/dist/schema.js +51 -9
  38. package/dist/session-control.d.ts +29 -3
  39. package/dist/session-tenancy.d.ts +44 -0
  40. package/dist/session-tenancy.js +16 -0
  41. package/dist/session-tenancy.js.map +1 -0
  42. package/dist/slack-task-policy-schema.d.ts +847 -0
  43. package/dist/slack-task-policy.d.ts +65 -0
  44. package/dist/task-notes-schema.d.ts +924 -0
  45. package/dist/task-notes.d.ts +85 -0
  46. package/dist/video-generation.d.ts +9 -0
  47. package/dist/video-generation.js +5 -3
  48. package/dist/xai-subscription.d.ts +326 -0
  49. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  50. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  51. package/drizzle/0214_browser_download_saves.sql +34 -0
  52. package/drizzle/0215_browser_controller_host.sql +385 -0
  53. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  54. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  55. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  56. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  57. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  58. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  59. package/drizzle/0220_session_channels.sql +54 -0
  60. package/drizzle/0221_sessions_channel_index.sql +5 -0
  61. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  62. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  63. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  64. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  65. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  66. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  67. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  68. package/drizzle/0227_slack_native_actions.sql +145 -0
  69. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  70. package/drizzle/0228_slack_task_policy.sql +400 -0
  71. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  72. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  73. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  74. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  75. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  76. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  77. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  78. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  79. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  80. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  81. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  82. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  83. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  84. package/drizzle/0239_task_tree_notes.sql +990 -0
  85. package/package.json +13 -5
  86. package/src/browser-auth.ts +2600 -123
  87. package/src/browser-downloads.ts +517 -0
  88. package/src/browser-identities.ts +220 -3
  89. package/src/browser-sessions.ts +334 -45
  90. package/src/browser-state-artifacts.ts +402 -12
  91. package/src/canonical-human-identities.ts +317 -0
  92. package/src/capability-integrations.ts +121 -213
  93. package/src/database.ts +41 -1
  94. package/src/index.ts +4157 -394
  95. package/src/insights.ts +109 -28
  96. package/src/integration-bindings.ts +101 -101
  97. package/src/{integration-features.ts → integration-facets.ts} +187 -184
  98. package/src/interaction-revisions.ts +33 -3
  99. package/src/interaction-schema.ts +311 -8
  100. package/src/pack-components.ts +43 -43
  101. package/src/pack-installations.ts +0 -43
  102. package/src/plugin-packages.ts +9 -9
  103. package/src/provision-roles.ts +232 -0
  104. package/src/runtime-posture.ts +227 -14
  105. package/src/schema.ts +1188 -75
  106. package/src/session-control.ts +122 -37
  107. package/src/session-queue-commands.ts +78 -1
  108. package/src/session-tenancy.ts +188 -0
  109. package/src/session-tool-call-settlement.ts +9 -3
  110. package/src/slack-task-policy-schema.ts +130 -0
  111. package/src/slack-task-policy.ts +341 -0
  112. package/src/task-notes-schema.ts +135 -0
  113. package/src/task-notes.ts +178 -0
  114. package/src/video-generation.ts +90 -12
  115. package/src/workspace-artifacts.ts +1 -1
  116. package/src/xai-subscription.ts +1887 -0
  117. package/dist/chunk-ISI2TIUG.js.map +0 -1
  118. package/dist/chunk-MWDVXQOL.js.map +0 -1
  119. package/dist/chunk-NBE6CHRI.js.map +0 -1
  120. package/dist/chunk-YHZXQ7HP.js.map +0 -1
  121. package/dist/integration-features.d.ts +0 -99
  122. /package/dist/{chunk-F5FT7BEZ.js.map → chunk-P3RE4D2B.js.map} +0 -0
@@ -0,0 +1,130 @@
1
+ import type { SlackTaskPolicyContent } from "@opengeni/contracts";
2
+ import { sql } from "drizzle-orm";
3
+ import {
4
+ bigint,
5
+ check,
6
+ index,
7
+ jsonb,
8
+ pgTable,
9
+ text,
10
+ timestamp,
11
+ uniqueIndex,
12
+ uuid,
13
+ } from "drizzle-orm/pg-core";
14
+
15
+ export const slackTaskPolicyRevisions = pgTable(
16
+ "slack_task_policy_revisions",
17
+ {
18
+ id: uuid("id").primaryKey().defaultRandom(),
19
+ operationId: uuid("operation_id").notNull(),
20
+ requestFingerprint: text("request_fingerprint").notNull(),
21
+ accountId: uuid("account_id").notNull(),
22
+ workspaceId: uuid("workspace_id").notNull(),
23
+ revision: bigint("revision", { mode: "number" })
24
+ .notNull()
25
+ .default(sql`nextval('slack_task_policy_revision_seq')`),
26
+ policy: jsonb("policy").$type<SlackTaskPolicyContent>().notNull(),
27
+ policyHash: text("policy_hash").notNull(),
28
+ supersedesRevisionId: uuid("supersedes_revision_id"),
29
+ createdBySubjectId: text("created_by_subject_id").notNull(),
30
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
31
+ },
32
+ (table) => ({
33
+ workspaceOperation: uniqueIndex("slack_task_policy_revisions_workspace_operation_uq").on(
34
+ table.workspaceId,
35
+ table.operationId,
36
+ ),
37
+ workspaceRevision: uniqueIndex("slack_task_policy_revisions_workspace_revision_uq").on(
38
+ table.workspaceId,
39
+ table.revision,
40
+ ),
41
+ workspaceHistory: index("slack_task_policy_revisions_workspace_history_idx").on(
42
+ table.workspaceId,
43
+ table.revision,
44
+ ),
45
+ receipt: check(
46
+ "slack_task_policy_revisions_receipt_chk",
47
+ sql`${table.requestFingerprint} ~ '^[0-9a-f]{64}$' and ${table.policyHash} ~ '^[0-9a-f]{64}$'`,
48
+ ),
49
+ }),
50
+ );
51
+
52
+ export const slackTaskPolicyHeads = pgTable(
53
+ "slack_task_policy_heads",
54
+ {
55
+ accountId: uuid("account_id").notNull(),
56
+ workspaceId: uuid("workspace_id").primaryKey(),
57
+ revisionId: uuid("revision_id").notNull(),
58
+ revision: bigint("revision", { mode: "number" }).notNull(),
59
+ policyHash: text("policy_hash").notNull(),
60
+ activationVersion: bigint("activation_version", { mode: "number" }).notNull(),
61
+ activatedAt: timestamp("activated_at", { withTimezone: true }).notNull().defaultNow(),
62
+ },
63
+ (table) => ({
64
+ account: index("slack_task_policy_heads_account_idx").on(table.accountId),
65
+ }),
66
+ );
67
+
68
+ export const slackTaskPolicyActivationEvents = pgTable(
69
+ "slack_task_policy_activation_events",
70
+ {
71
+ id: uuid("id").primaryKey().defaultRandom(),
72
+ operationId: uuid("operation_id").notNull(),
73
+ requestFingerprint: text("request_fingerprint").notNull(),
74
+ accountId: uuid("account_id").notNull(),
75
+ workspaceId: uuid("workspace_id").notNull(),
76
+ activationVersion: bigint("activation_version", { mode: "number" }).notNull(),
77
+ oldRevisionId: uuid("old_revision_id"),
78
+ oldRevision: bigint("old_revision", { mode: "number" }),
79
+ oldPolicyHash: text("old_policy_hash"),
80
+ newRevisionId: uuid("new_revision_id").notNull(),
81
+ newRevision: bigint("new_revision", { mode: "number" }).notNull(),
82
+ newPolicyHash: text("new_policy_hash").notNull(),
83
+ actorSubjectId: text("actor_subject_id").notNull(),
84
+ reason: text("reason").notNull(),
85
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
86
+ },
87
+ (table) => ({
88
+ workspaceOperation: uniqueIndex("slack_task_policy_events_workspace_operation_uq").on(
89
+ table.workspaceId,
90
+ table.operationId,
91
+ ),
92
+ workspaceVersion: uniqueIndex("slack_task_policy_events_workspace_version_uq").on(
93
+ table.workspaceId,
94
+ table.activationVersion,
95
+ ),
96
+ workspaceTimeline: index("slack_task_policy_events_workspace_time_idx").on(
97
+ table.workspaceId,
98
+ table.createdAt,
99
+ table.id,
100
+ ),
101
+ }),
102
+ );
103
+
104
+ export const slackSharedTaskOrigins = pgTable(
105
+ "slack_shared_task_origins",
106
+ {
107
+ interactionId: uuid("interaction_id").primaryKey(),
108
+ accountId: uuid("account_id").notNull(),
109
+ workspaceId: uuid("workspace_id").notNull(),
110
+ connectionId: uuid("connection_id").notNull(),
111
+ sessionId: uuid("session_id").notNull(),
112
+ slackTeamId: text("slack_team_id").notNull(),
113
+ sourceChannelId: text("source_channel_id").notNull(),
114
+ sourceThreadTs: text("source_thread_ts").notNull(),
115
+ initiatingSlackUserId: text("initiating_slack_user_id").notNull(),
116
+ policyRevisionId: uuid("policy_revision_id").notNull(),
117
+ policyHash: text("policy_hash").notNull(),
118
+ policyActivationVersion: bigint("policy_activation_version", { mode: "number" }).notNull(),
119
+ publicationMode: text("publication_mode")
120
+ .$type<"never" | "approval_required" | "allow">()
121
+ .notNull(),
122
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
123
+ },
124
+ (table) => ({
125
+ workspaceSession: uniqueIndex("slack_shared_task_origins_workspace_session_uq").on(
126
+ table.workspaceId,
127
+ table.sessionId,
128
+ ),
129
+ }),
130
+ );
@@ -0,0 +1,341 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ SlackTaskPolicyContent,
4
+ canonicalizeSlackTaskPolicy,
5
+ type SlackTaskPolicyActivationEvent,
6
+ type SlackTaskPolicyHead,
7
+ type SlackTaskPolicyListResponse,
8
+ type SlackTaskPolicyMutationResponse,
9
+ type SlackTaskPolicyRevision,
10
+ } from "@opengeni/contracts";
11
+ import { and, desc, eq, sql } from "drizzle-orm";
12
+ import type { Database } from "./database";
13
+ import { rawRows, withRlsContext } from "./database";
14
+ import { nestedPostgresSqlState } from "./persistence-errors";
15
+ import * as schema from "./schema";
16
+
17
+ type RevisionRow = typeof schema.slackTaskPolicyRevisions.$inferSelect;
18
+ type HeadRow = typeof schema.slackTaskPolicyHeads.$inferSelect;
19
+ type EventRow = typeof schema.slackTaskPolicyActivationEvents.$inferSelect;
20
+ type OriginRow = typeof schema.slackSharedTaskOrigins.$inferSelect;
21
+
22
+ export type SlackSharedTaskOrigin = {
23
+ interactionId: string;
24
+ accountId: string;
25
+ workspaceId: string;
26
+ connectionId: string;
27
+ sessionId: string;
28
+ slackTeamId: string;
29
+ sourceChannelId: string;
30
+ sourceThreadTs: string;
31
+ initiatingSlackUserId: string;
32
+ policyRevisionId: string;
33
+ policyHash: string;
34
+ policyActivationVersion: number;
35
+ publicationMode: "never" | "approval_required" | "allow";
36
+ createdAt: Date;
37
+ };
38
+
39
+ export class SlackTaskPolicyConflictError extends Error {
40
+ readonly name = "SlackTaskPolicyConflictError";
41
+ readonly code = "SLACK_TASK_POLICY_CONFLICT";
42
+ constructor(readonly currentHead: SlackTaskPolicyHead | null) {
43
+ super("The active Slack task policy changed in another request");
44
+ }
45
+ }
46
+
47
+ export class SlackTaskPolicyOperationReuseError extends Error {
48
+ readonly name = "SlackTaskPolicyOperationReuseError";
49
+ readonly code = "SLACK_TASK_POLICY_OPERATION_REUSED";
50
+ constructor() {
51
+ super("The Slack task-policy operation id was already used for another request");
52
+ }
53
+ }
54
+
55
+ export class SlackTaskPolicyInvalidOperationError extends Error {
56
+ readonly name = "SlackTaskPolicyInvalidOperationError";
57
+ }
58
+
59
+ export class SlackTaskPolicyAuthorityError extends Error {
60
+ readonly name = "SlackTaskPolicyAuthorityError";
61
+
62
+ constructor(message = "Slack task-policy administration requires exact workspace authority") {
63
+ super(message);
64
+ }
65
+ }
66
+
67
+ function iso(value: Date | string): string {
68
+ return (value instanceof Date ? value : new Date(value)).toISOString();
69
+ }
70
+
71
+ function revisionFromRow(row: RevisionRow): SlackTaskPolicyRevision {
72
+ return {
73
+ id: row.id,
74
+ operationId: row.operationId,
75
+ accountId: row.accountId,
76
+ workspaceId: row.workspaceId,
77
+ revision: row.revision,
78
+ policy: canonicalizeSlackTaskPolicy(SlackTaskPolicyContent.parse(row.policy)),
79
+ policyHash: row.policyHash,
80
+ supersedesRevisionId: row.supersedesRevisionId,
81
+ createdBySubjectId: row.createdBySubjectId,
82
+ createdAt: iso(row.createdAt),
83
+ };
84
+ }
85
+
86
+ function headFromRow(row: HeadRow): SlackTaskPolicyHead {
87
+ return {
88
+ accountId: row.accountId,
89
+ workspaceId: row.workspaceId,
90
+ revisionId: row.revisionId,
91
+ revision: row.revision,
92
+ policyHash: row.policyHash,
93
+ activationVersion: row.activationVersion,
94
+ activatedAt: iso(row.activatedAt),
95
+ };
96
+ }
97
+
98
+ function eventFromRow(row: EventRow): SlackTaskPolicyActivationEvent {
99
+ return {
100
+ id: row.id,
101
+ operationId: row.operationId,
102
+ accountId: row.accountId,
103
+ workspaceId: row.workspaceId,
104
+ activationVersion: row.activationVersion,
105
+ oldRevision:
106
+ row.oldRevisionId === null
107
+ ? null
108
+ : { id: row.oldRevisionId, revision: row.oldRevision!, policyHash: row.oldPolicyHash! },
109
+ newRevision: {
110
+ id: row.newRevisionId,
111
+ revision: row.newRevision,
112
+ policyHash: row.newPolicyHash,
113
+ },
114
+ actorSubjectId: row.actorSubjectId,
115
+ reason: row.reason,
116
+ createdAt: iso(row.createdAt),
117
+ };
118
+ }
119
+
120
+ async function currentSlackTaskPolicyHead(
121
+ db: Database,
122
+ accountId: string,
123
+ workspaceId: string,
124
+ ): Promise<SlackTaskPolicyHead | null> {
125
+ return (await getActiveSlackTaskPolicy(db, { accountId, workspaceId }))?.head ?? null;
126
+ }
127
+
128
+ export async function getActiveSlackTaskPolicy(
129
+ db: Database,
130
+ input: { accountId: string; workspaceId: string },
131
+ ): Promise<{ head: SlackTaskPolicyHead; revision: SlackTaskPolicyRevision } | null> {
132
+ return await withRlsContext(db, input, async (scopedDb) => {
133
+ const [row] = await scopedDb
134
+ .select({
135
+ head: schema.slackTaskPolicyHeads,
136
+ revision: schema.slackTaskPolicyRevisions,
137
+ })
138
+ .from(schema.slackTaskPolicyHeads)
139
+ .innerJoin(
140
+ schema.slackTaskPolicyRevisions,
141
+ and(
142
+ eq(schema.slackTaskPolicyRevisions.id, schema.slackTaskPolicyHeads.revisionId),
143
+ eq(schema.slackTaskPolicyRevisions.workspaceId, schema.slackTaskPolicyHeads.workspaceId),
144
+ ),
145
+ )
146
+ .where(
147
+ and(
148
+ eq(schema.slackTaskPolicyHeads.accountId, input.accountId),
149
+ eq(schema.slackTaskPolicyHeads.workspaceId, input.workspaceId),
150
+ ),
151
+ )
152
+ .limit(1);
153
+ return row ? { head: headFromRow(row.head), revision: revisionFromRow(row.revision) } : null;
154
+ });
155
+ }
156
+
157
+ export async function listSlackTaskPolicy(
158
+ db: Database,
159
+ input: { accountId: string; workspaceId: string; limit?: number },
160
+ ): Promise<SlackTaskPolicyListResponse> {
161
+ return await withRlsContext(db, input, async (scopedDb) => {
162
+ const limit = Math.max(1, Math.min(100, input.limit ?? 50));
163
+ const [active, revisions, events] = await Promise.all([
164
+ getActiveSlackTaskPolicy(scopedDb, input),
165
+ scopedDb
166
+ .select()
167
+ .from(schema.slackTaskPolicyRevisions)
168
+ .where(eq(schema.slackTaskPolicyRevisions.workspaceId, input.workspaceId))
169
+ .orderBy(desc(schema.slackTaskPolicyRevisions.revision))
170
+ .limit(limit),
171
+ scopedDb
172
+ .select()
173
+ .from(schema.slackTaskPolicyActivationEvents)
174
+ .where(eq(schema.slackTaskPolicyActivationEvents.workspaceId, input.workspaceId))
175
+ .orderBy(desc(schema.slackTaskPolicyActivationEvents.activationVersion))
176
+ .limit(limit),
177
+ ]);
178
+ return {
179
+ current: active?.head ?? null,
180
+ activeRevision: active?.revision ?? null,
181
+ revisions: revisions.map(revisionFromRow),
182
+ activationEvents: events.map(eventFromRow),
183
+ };
184
+ });
185
+ }
186
+
187
+ export async function updateSlackTaskPolicy(
188
+ db: Database,
189
+ input: {
190
+ operationId?: string;
191
+ accountId: string;
192
+ workspaceId: string;
193
+ policy: SlackTaskPolicyContent;
194
+ expectedCurrentRevisionId: string | null;
195
+ expectedActivationVersion: number;
196
+ actorSubjectId: string;
197
+ principalKind: "human_session" | undefined;
198
+ reason: string;
199
+ },
200
+ ): Promise<SlackTaskPolicyMutationResponse> {
201
+ if (input.principalKind !== "human_session" || input.actorSubjectId.trim().length === 0) {
202
+ throw new SlackTaskPolicyAuthorityError(
203
+ "Slack task-policy changes require an exact authenticated human actor",
204
+ );
205
+ }
206
+ const operationId = input.operationId ?? randomUUID();
207
+ const policy = canonicalizeSlackTaskPolicy(input.policy);
208
+ const requestFingerprint = createHash("sha256")
209
+ .update(
210
+ JSON.stringify([
211
+ "slack_task_policy_operation",
212
+ 1,
213
+ input.accountId,
214
+ input.workspaceId,
215
+ policy,
216
+ input.expectedCurrentRevisionId,
217
+ input.expectedActivationVersion,
218
+ input.actorSubjectId,
219
+ input.reason,
220
+ ]),
221
+ "utf8",
222
+ )
223
+ .digest("hex");
224
+
225
+ try {
226
+ return await withRlsContext(db, input, async (scopedDb) => {
227
+ await scopedDb.execute(
228
+ sql`select set_config('opengeni.subject_id', ${input.actorSubjectId}, true)`,
229
+ );
230
+ await scopedDb.execute(
231
+ sql`select set_config('opengeni.principal_kind', 'human_session', true)`,
232
+ );
233
+ const [receipt] = await rawRows<{ revisionId: string; eventId: string }>(
234
+ scopedDb,
235
+ sql`select revision_id as "revisionId", event_id as "eventId"
236
+ from slack_task_policy_update(
237
+ ${operationId}::uuid,
238
+ ${requestFingerprint},
239
+ ${input.accountId}::uuid,
240
+ ${input.workspaceId}::uuid,
241
+ ${JSON.stringify(policy)}::jsonb,
242
+ ${input.expectedCurrentRevisionId}::uuid,
243
+ ${input.expectedActivationVersion}::bigint,
244
+ ${input.actorSubjectId},
245
+ ${input.reason}
246
+ )`,
247
+ );
248
+ if (!receipt) throw new Error("Slack task-policy update returned no lifecycle receipt");
249
+ const [[revision], [head], [event]] = await Promise.all([
250
+ scopedDb
251
+ .select()
252
+ .from(schema.slackTaskPolicyRevisions)
253
+ .where(eq(schema.slackTaskPolicyRevisions.id, receipt.revisionId))
254
+ .limit(1),
255
+ scopedDb
256
+ .select()
257
+ .from(schema.slackTaskPolicyHeads)
258
+ .where(eq(schema.slackTaskPolicyHeads.workspaceId, input.workspaceId))
259
+ .limit(1),
260
+ scopedDb
261
+ .select()
262
+ .from(schema.slackTaskPolicyActivationEvents)
263
+ .where(eq(schema.slackTaskPolicyActivationEvents.id, receipt.eventId))
264
+ .limit(1),
265
+ ]);
266
+ if (!revision || !head || !event) {
267
+ throw new Error("Slack task-policy lifecycle receipt was incomplete");
268
+ }
269
+ return {
270
+ revision: revisionFromRow(revision),
271
+ head: headFromRow(head),
272
+ event: eventFromRow(event),
273
+ };
274
+ });
275
+ } catch (error) {
276
+ const state = nestedPostgresSqlState(error);
277
+ if (state === "P1471") throw new SlackTaskPolicyOperationReuseError();
278
+ if (state === "40001") {
279
+ throw new SlackTaskPolicyConflictError(
280
+ await currentSlackTaskPolicyHead(db, input.accountId, input.workspaceId),
281
+ );
282
+ }
283
+ if (state === "42501") {
284
+ throw new SlackTaskPolicyAuthorityError(error instanceof Error ? error.message : undefined);
285
+ }
286
+ if (state === "22023") throw new SlackTaskPolicyInvalidOperationError();
287
+ throw error;
288
+ }
289
+ }
290
+
291
+ function originFromRow(row: OriginRow): SlackSharedTaskOrigin {
292
+ return { ...row };
293
+ }
294
+
295
+ export async function saveSlackSharedTaskOrigin(
296
+ db: Database,
297
+ input: Omit<SlackSharedTaskOrigin, "createdAt">,
298
+ ): Promise<SlackSharedTaskOrigin> {
299
+ return await withRlsContext(db, input, async (scopedDb) => {
300
+ const [created] = await scopedDb
301
+ .insert(schema.slackSharedTaskOrigins)
302
+ .values(input)
303
+ .onConflictDoNothing({ target: schema.slackSharedTaskOrigins.interactionId })
304
+ .returning();
305
+ if (created) return originFromRow(created);
306
+ const [existing] = await scopedDb
307
+ .select()
308
+ .from(schema.slackSharedTaskOrigins)
309
+ .where(eq(schema.slackSharedTaskOrigins.interactionId, input.interactionId))
310
+ .limit(1);
311
+ if (!existing) throw new Error("Slack shared-task origin conflict could not be resolved");
312
+ for (const [key, value] of Object.entries(input)) {
313
+ if (existing[key as keyof OriginRow] !== value) {
314
+ throw new SlackTaskPolicyInvalidOperationError(
315
+ "Slack shared-task origin identity conflicted with immutable evidence",
316
+ );
317
+ }
318
+ }
319
+ return originFromRow(existing);
320
+ });
321
+ }
322
+
323
+ export async function getSlackSharedTaskOrigin(
324
+ db: Database,
325
+ input: { accountId: string; workspaceId: string; interactionId: string },
326
+ ): Promise<SlackSharedTaskOrigin | null> {
327
+ return await withRlsContext(db, input, async (scopedDb) => {
328
+ const [row] = await scopedDb
329
+ .select()
330
+ .from(schema.slackSharedTaskOrigins)
331
+ .where(
332
+ and(
333
+ eq(schema.slackSharedTaskOrigins.accountId, input.accountId),
334
+ eq(schema.slackSharedTaskOrigins.workspaceId, input.workspaceId),
335
+ eq(schema.slackSharedTaskOrigins.interactionId, input.interactionId),
336
+ ),
337
+ )
338
+ .limit(1);
339
+ return row ? originFromRow(row) : null;
340
+ });
341
+ }
@@ -0,0 +1,135 @@
1
+ import {
2
+ customType,
3
+ index,
4
+ integer,
5
+ pgTable,
6
+ text,
7
+ timestamp,
8
+ uniqueIndex,
9
+ uuid,
10
+ } from "drizzle-orm/pg-core";
11
+ import { sql } from "drizzle-orm";
12
+
13
+ const xid8 = customType<{ data: string; driverData: string }>({
14
+ dataType: () => "xid8",
15
+ });
16
+
17
+ // Cross-row validation, lifecycle-only mutation, exact-attempt authority,
18
+ // restrictive session-visibility RLS, and grants are owned by migration 0239.
19
+ export const taskNotes = pgTable(
20
+ "task_notes",
21
+ {
22
+ id: uuid("id").primaryKey().defaultRandom(),
23
+ accountId: uuid("account_id").notNull(),
24
+ workspaceId: uuid("workspace_id").notNull(),
25
+ rootSessionId: uuid("root_session_id").notNull(),
26
+ kind: text("kind").notNull(),
27
+ text: text("text").notNull(),
28
+ textHash: text("text_hash").notNull(),
29
+ status: text("status").notNull().default("active"),
30
+ version: integer("version").notNull().default(1),
31
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
32
+ createOperationId: uuid("create_operation_id").notNull(),
33
+ createInputHash: text("create_input_hash").notNull(),
34
+ createdByActorKind: text("created_by_actor_kind").notNull(),
35
+ createdByActorSubjectId: text("created_by_actor_subject_id").notNull(),
36
+ createdByInitiatingHumanSubjectId: text("created_by_initiating_human_subject_id"),
37
+ createdBySessionId: uuid("created_by_session_id").notNull(),
38
+ createdByTurnId: uuid("created_by_turn_id").notNull(),
39
+ createdByAttemptId: uuid("created_by_attempt_id").notNull(),
40
+ createdByExecutionGeneration: integer("created_by_execution_generation").notNull(),
41
+ archiveOperationId: uuid("archive_operation_id"),
42
+ archiveInputHash: text("archive_input_hash"),
43
+ archivedByActorKind: text("archived_by_actor_kind"),
44
+ archivedByActorSubjectId: text("archived_by_actor_subject_id"),
45
+ archivedByInitiatingHumanSubjectId: text("archived_by_initiating_human_subject_id"),
46
+ archivedBySessionId: uuid("archived_by_session_id"),
47
+ archivedByTurnId: uuid("archived_by_turn_id"),
48
+ archivedByAttemptId: uuid("archived_by_attempt_id"),
49
+ archivedByExecutionGeneration: integer("archived_by_execution_generation"),
50
+ archivedAt: timestamp("archived_at", { withTimezone: true }),
51
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
52
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
53
+ },
54
+ (table) => ({
55
+ workspaceIdentity: uniqueIndex("task_notes_workspace_id_uq").on(table.workspaceId, table.id),
56
+ workspaceRootIdentity: uniqueIndex("task_notes_workspace_root_id_uq").on(
57
+ table.workspaceId,
58
+ table.rootSessionId,
59
+ table.id,
60
+ ),
61
+ createOperation: uniqueIndex("task_notes_workspace_create_operation_uq").on(
62
+ table.workspaceId,
63
+ table.createOperationId,
64
+ ),
65
+ archiveOperation: uniqueIndex("task_notes_workspace_archive_operation_uq")
66
+ .on(table.workspaceId, table.archiveOperationId)
67
+ .where(sql`${table.archiveOperationId} is not null`),
68
+ rootActive: index("task_notes_root_active_idx").on(
69
+ table.workspaceId,
70
+ table.rootSessionId,
71
+ table.status,
72
+ table.expiresAt,
73
+ table.updatedAt,
74
+ table.id,
75
+ ),
76
+ }),
77
+ );
78
+
79
+ export const taskNoteEvents = pgTable(
80
+ "task_note_events",
81
+ {
82
+ id: uuid("id").primaryKey().defaultRandom(),
83
+ accountId: uuid("account_id").notNull(),
84
+ workspaceId: uuid("workspace_id").notNull(),
85
+ rootSessionId: uuid("root_session_id").notNull(),
86
+ noteId: uuid("note_id").notNull(),
87
+ eventType: text("event_type").notNull(),
88
+ noteVersion: integer("note_version").notNull(),
89
+ operationId: uuid("operation_id").notNull(),
90
+ inputHash: text("input_hash").notNull(),
91
+ textHash: text("text_hash").notNull(),
92
+ reason: text("reason"),
93
+ actorKind: text("actor_kind").notNull(),
94
+ actorSubjectId: text("actor_subject_id").notNull(),
95
+ initiatingHumanSubjectId: text("initiating_human_subject_id"),
96
+ actorSessionId: uuid("actor_session_id").notNull(),
97
+ actorTurnId: uuid("actor_turn_id").notNull(),
98
+ actorAttemptId: uuid("actor_attempt_id").notNull(),
99
+ actorExecutionGeneration: integer("actor_execution_generation").notNull(),
100
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
101
+ },
102
+ (table) => ({
103
+ operation: uniqueIndex("task_note_events_workspace_operation_uq").on(
104
+ table.workspaceId,
105
+ table.operationId,
106
+ ),
107
+ noteVersion: uniqueIndex("task_note_events_note_version_uq").on(
108
+ table.workspaceId,
109
+ table.noteId,
110
+ table.noteVersion,
111
+ ),
112
+ rootTimeline: index("task_note_events_root_timeline_idx").on(
113
+ table.workspaceId,
114
+ table.rootSessionId,
115
+ table.createdAt,
116
+ table.id,
117
+ ),
118
+ }),
119
+ );
120
+
121
+ export const taskNoteWriteCapabilities = pgTable(
122
+ "task_note_write_capabilities",
123
+ {
124
+ backendPid: integer("backend_pid").notNull(),
125
+ transactionId: xid8("transaction_id").notNull(),
126
+ capabilityId: uuid("capability_id").notNull(),
127
+ },
128
+ (table) => ({
129
+ identity: uniqueIndex("task_note_write_capabilities_identity_uq").on(
130
+ table.backendPid,
131
+ table.transactionId,
132
+ table.capabilityId,
133
+ ),
134
+ }),
135
+ );