@opengeni/db 0.31.1 → 0.36.1
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.
- package/dist/browser-auth.d.ts +209 -1
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +11 -2
- package/dist/browser-sessions.d.ts +83 -1
- package/dist/browser-state-artifacts.d.ts +75 -1
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-integrations.d.ts +8 -6
- package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/chunk-FPVAD4CO.js +117 -0
- package/dist/chunk-FPVAD4CO.js.map +1 -0
- package/dist/chunk-GYO7ED2Q.js +259 -0
- package/dist/chunk-GYO7ED2Q.js.map +1 -0
- package/dist/{chunk-F5FT7BEZ.js → chunk-HSOWSYOX.js} +2 -2
- package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
- package/dist/chunk-VKLB2ZGI.js.map +1 -0
- package/dist/{chunk-NBE6CHRI.js → chunk-VTPRIXKG.js} +49 -9
- package/dist/chunk-VTPRIXKG.js.map +1 -0
- package/dist/{chunk-ISI2TIUG.js → chunk-Z7VYMNPZ.js} +76 -4
- package/dist/chunk-Z7VYMNPZ.js.map +1 -0
- package/dist/database.d.ts +5 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +467 -29
- package/dist/index.js +23247 -16232
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +23 -7
- package/dist/integration-bindings.d.ts +24 -24
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +11 -0
- package/dist/interaction-schema.d.ts +698 -34
- package/dist/persistence-errors.d.ts +8 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +8 -7
- package/dist/schema.d.ts +9464 -5412
- package/dist/schema.js +51 -9
- package/dist/session-control.d.ts +29 -3
- package/dist/session-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/video-generation.d.ts +9 -0
- package/dist/video-generation.js +5 -3
- package/dist/xai-subscription.d.ts +326 -0
- package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_recover_unclaimed_session_turns.sql +307 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +13 -5
- package/src/browser-auth.ts +2600 -123
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +220 -3
- package/src/browser-sessions.ts +334 -45
- package/src/browser-state-artifacts.ts +402 -12
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-integrations.ts +121 -213
- package/src/database.ts +41 -1
- package/src/index.ts +4625 -411
- package/src/insights.ts +109 -28
- package/src/integration-bindings.ts +101 -101
- package/src/{integration-features.ts → integration-facets.ts} +187 -184
- package/src/interaction-revisions.ts +33 -3
- package/src/interaction-schema.ts +311 -8
- package/src/pack-components.ts +43 -43
- package/src/pack-installations.ts +0 -43
- package/src/persistence-errors.ts +60 -1
- package/src/plugin-packages.ts +9 -9
- package/src/provision-roles.ts +232 -0
- package/src/runtime-posture.ts +227 -14
- package/src/schema.ts +1188 -75
- package/src/session-control.ts +122 -37
- package/src/session-queue-commands.ts +78 -1
- package/src/session-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +9 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +90 -12
- package/src/workspace-artifacts.ts +1 -1
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-ISI2TIUG.js.map +0 -1
- package/dist/chunk-MWDVXQOL.js.map +0 -1
- package/dist/chunk-NBE6CHRI.js.map +0 -1
- package/dist/chunk-YHZXQ7HP.js.map +0 -1
- package/dist/integration-features.d.ts +0 -99
- /package/dist/{chunk-F5FT7BEZ.js.map → chunk-HSOWSYOX.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
|
+
);
|