@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,1887 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1,
|
|
3
|
+
XaiProviderAccountAuthoritySnapshotV1,
|
|
4
|
+
type XaiProviderAccountAuthoritySnapshotV1 as XaiAuthoritySnapshot,
|
|
5
|
+
} from "@opengeni/contracts";
|
|
6
|
+
import { and, asc, eq, gt, isNull, lte, sql } from "drizzle-orm";
|
|
7
|
+
import type { Database } from "./database";
|
|
8
|
+
import { rawRows, withWorkspaceSubjectRls } from "./database";
|
|
9
|
+
import { decryptEnvironmentValue, encryptEnvironmentValue } from "./environment-crypto";
|
|
10
|
+
import * as schema from "./schema";
|
|
11
|
+
|
|
12
|
+
export type XaiAccountAuthorityScope = "workspace" | "user";
|
|
13
|
+
export type XaiCredentialStatus = "active" | "needs_relogin" | "error" | "disabled";
|
|
14
|
+
|
|
15
|
+
export type XaiCredentialSecretV1 = {
|
|
16
|
+
version: 1;
|
|
17
|
+
accessToken?: string;
|
|
18
|
+
refreshToken?: string;
|
|
19
|
+
sessionToken?: string;
|
|
20
|
+
cookie?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type XaiSubscriptionAccountMetadata = {
|
|
24
|
+
id: string;
|
|
25
|
+
scope: XaiAccountAuthorityScope;
|
|
26
|
+
providerAccountId: string | null;
|
|
27
|
+
label: string | null;
|
|
28
|
+
accountEmail: string | null;
|
|
29
|
+
planType: string | null;
|
|
30
|
+
status: XaiCredentialStatus;
|
|
31
|
+
allocatorEnabled: boolean;
|
|
32
|
+
version: number;
|
|
33
|
+
allocatorVersion: number;
|
|
34
|
+
allocatorUpdatedAt: Date | null;
|
|
35
|
+
expiresAt: Date | null;
|
|
36
|
+
lastRefreshAt: Date | null;
|
|
37
|
+
lastError: string | null;
|
|
38
|
+
quotaUsedPercent: number | null;
|
|
39
|
+
quotaResetAt: Date | null;
|
|
40
|
+
quotaCheckedAt: Date | null;
|
|
41
|
+
exhaustedUntil: Date | null;
|
|
42
|
+
selectionCount: number;
|
|
43
|
+
lastSelectedAt: Date | null;
|
|
44
|
+
connectedBySubjectId: string | null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type XaiCredentialForRun = XaiSubscriptionAccountMetadata & {
|
|
48
|
+
secret: XaiCredentialSecretV1;
|
|
49
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type XaiCredentialLeaseResult = {
|
|
53
|
+
credentialId: string | null;
|
|
54
|
+
rotationEnabled: boolean;
|
|
55
|
+
reused: boolean;
|
|
56
|
+
holderId: string | null;
|
|
57
|
+
generation: number | null;
|
|
58
|
+
leasedUntil: Date | null;
|
|
59
|
+
accounts: XaiSubscriptionAccountMetadata[];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const XAI_CREDENTIAL_LEASE_TTL_MS = 5 * 60_000;
|
|
63
|
+
|
|
64
|
+
/** Stable session sharding, matching Codex's cache-affinity contract. */
|
|
65
|
+
export function xaiCredentialShardIndex(sessionId: string, candidateCount: number): number {
|
|
66
|
+
if (!Number.isSafeInteger(candidateCount) || candidateCount <= 0) {
|
|
67
|
+
throw new Error("xAI shard candidate count must be positive");
|
|
68
|
+
}
|
|
69
|
+
let hash = 0x811c9dc5;
|
|
70
|
+
for (let index = 0; index < sessionId.length; index += 1) {
|
|
71
|
+
hash ^= sessionId.charCodeAt(index);
|
|
72
|
+
hash = Math.imul(hash, 0x01000193);
|
|
73
|
+
}
|
|
74
|
+
return (hash >>> 0) % candidateCount;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type XaiCredentialMetadataRow = Pick<
|
|
78
|
+
typeof schema.xaiSubscriptionCredentials.$inferSelect,
|
|
79
|
+
| "id"
|
|
80
|
+
| "authorityScope"
|
|
81
|
+
| "providerAccountId"
|
|
82
|
+
| "label"
|
|
83
|
+
| "accountEmail"
|
|
84
|
+
| "planType"
|
|
85
|
+
| "status"
|
|
86
|
+
| "allocatorEnabled"
|
|
87
|
+
| "version"
|
|
88
|
+
| "allocatorVersion"
|
|
89
|
+
| "allocatorUpdatedAt"
|
|
90
|
+
| "expiresAt"
|
|
91
|
+
| "lastRefreshAt"
|
|
92
|
+
| "lastError"
|
|
93
|
+
| "quotaUsedPercent"
|
|
94
|
+
| "quotaResetAt"
|
|
95
|
+
| "quotaCheckedAt"
|
|
96
|
+
| "exhaustedUntil"
|
|
97
|
+
| "selectionCount"
|
|
98
|
+
| "lastSelectedAt"
|
|
99
|
+
| "connectedBySubjectId"
|
|
100
|
+
>;
|
|
101
|
+
|
|
102
|
+
const xaiCredentialMetadataColumns = {
|
|
103
|
+
id: schema.xaiSubscriptionCredentials.id,
|
|
104
|
+
authorityScope: schema.xaiSubscriptionCredentials.authorityScope,
|
|
105
|
+
providerAccountId: schema.xaiSubscriptionCredentials.providerAccountId,
|
|
106
|
+
label: schema.xaiSubscriptionCredentials.label,
|
|
107
|
+
accountEmail: schema.xaiSubscriptionCredentials.accountEmail,
|
|
108
|
+
planType: schema.xaiSubscriptionCredentials.planType,
|
|
109
|
+
status: schema.xaiSubscriptionCredentials.status,
|
|
110
|
+
allocatorEnabled: schema.xaiSubscriptionCredentials.allocatorEnabled,
|
|
111
|
+
version: schema.xaiSubscriptionCredentials.version,
|
|
112
|
+
allocatorVersion: schema.xaiSubscriptionCredentials.allocatorVersion,
|
|
113
|
+
allocatorUpdatedAt: schema.xaiSubscriptionCredentials.allocatorUpdatedAt,
|
|
114
|
+
expiresAt: schema.xaiSubscriptionCredentials.expiresAt,
|
|
115
|
+
lastRefreshAt: schema.xaiSubscriptionCredentials.lastRefreshAt,
|
|
116
|
+
lastError: schema.xaiSubscriptionCredentials.lastError,
|
|
117
|
+
quotaUsedPercent: schema.xaiSubscriptionCredentials.quotaUsedPercent,
|
|
118
|
+
quotaResetAt: schema.xaiSubscriptionCredentials.quotaResetAt,
|
|
119
|
+
quotaCheckedAt: schema.xaiSubscriptionCredentials.quotaCheckedAt,
|
|
120
|
+
exhaustedUntil: schema.xaiSubscriptionCredentials.exhaustedUntil,
|
|
121
|
+
selectionCount: schema.xaiSubscriptionCredentials.selectionCount,
|
|
122
|
+
lastSelectedAt: schema.xaiSubscriptionCredentials.lastSelectedAt,
|
|
123
|
+
connectedBySubjectId: schema.xaiSubscriptionCredentials.connectedBySubjectId,
|
|
124
|
+
} as const;
|
|
125
|
+
|
|
126
|
+
const xaiCredentialAllocationColumns = {
|
|
127
|
+
...xaiCredentialMetadataColumns,
|
|
128
|
+
accountId: schema.xaiSubscriptionCredentials.accountId,
|
|
129
|
+
workspaceId: schema.xaiSubscriptionCredentials.workspaceId,
|
|
130
|
+
ownerOrganizationMembershipId: schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId,
|
|
131
|
+
createdAt: schema.xaiSubscriptionCredentials.createdAt,
|
|
132
|
+
} as const;
|
|
133
|
+
|
|
134
|
+
function assertSecret(secret: XaiCredentialSecretV1): void {
|
|
135
|
+
if (secret.version !== 1) throw new Error("Unsupported xAI credential secret version");
|
|
136
|
+
const values = [secret.accessToken, secret.refreshToken, secret.sessionToken, secret.cookie];
|
|
137
|
+
if (!values.some((value) => typeof value === "string" && value.length > 0)) {
|
|
138
|
+
throw new Error("An xAI credential must contain at least one secret value");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseSecret(value: string): XaiCredentialSecretV1 {
|
|
143
|
+
const parsed = JSON.parse(value) as Partial<XaiCredentialSecretV1>;
|
|
144
|
+
const secret: XaiCredentialSecretV1 = {
|
|
145
|
+
version: 1,
|
|
146
|
+
...(typeof parsed.accessToken === "string" ? { accessToken: parsed.accessToken } : {}),
|
|
147
|
+
...(typeof parsed.refreshToken === "string" ? { refreshToken: parsed.refreshToken } : {}),
|
|
148
|
+
...(typeof parsed.sessionToken === "string" ? { sessionToken: parsed.sessionToken } : {}),
|
|
149
|
+
...(typeof parsed.cookie === "string" ? { cookie: parsed.cookie } : {}),
|
|
150
|
+
};
|
|
151
|
+
assertSecret(secret);
|
|
152
|
+
return secret;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function metadataFromRow(row: XaiCredentialMetadataRow): XaiSubscriptionAccountMetadata {
|
|
156
|
+
return {
|
|
157
|
+
id: row.id,
|
|
158
|
+
scope: row.authorityScope as XaiAccountAuthorityScope,
|
|
159
|
+
providerAccountId: row.providerAccountId,
|
|
160
|
+
label: row.label,
|
|
161
|
+
accountEmail: row.accountEmail,
|
|
162
|
+
planType: row.planType,
|
|
163
|
+
status: row.status as XaiCredentialStatus,
|
|
164
|
+
allocatorEnabled: row.allocatorEnabled,
|
|
165
|
+
version: row.version,
|
|
166
|
+
allocatorVersion: row.allocatorVersion,
|
|
167
|
+
allocatorUpdatedAt: row.allocatorUpdatedAt,
|
|
168
|
+
expiresAt: row.expiresAt,
|
|
169
|
+
lastRefreshAt: row.lastRefreshAt,
|
|
170
|
+
lastError: row.lastError,
|
|
171
|
+
quotaUsedPercent: row.quotaUsedPercent,
|
|
172
|
+
quotaResetAt: row.quotaResetAt,
|
|
173
|
+
quotaCheckedAt: row.quotaCheckedAt,
|
|
174
|
+
exhaustedUntil: row.exhaustedUntil,
|
|
175
|
+
selectionCount: row.selectionCount,
|
|
176
|
+
lastSelectedAt: row.lastSelectedAt,
|
|
177
|
+
connectedBySubjectId: row.connectedBySubjectId,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function resolveXaiPoolOwnerMembershipId(
|
|
182
|
+
db: Database,
|
|
183
|
+
input: {
|
|
184
|
+
workspaceId: string;
|
|
185
|
+
subjectId: string;
|
|
186
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
187
|
+
},
|
|
188
|
+
): Promise<string | null> {
|
|
189
|
+
if (input.authoritySnapshot.scope === "workspace") return null;
|
|
190
|
+
const rows = await rawRows<{ membership_id: string }>(
|
|
191
|
+
db,
|
|
192
|
+
sql`select organization_membership_id as membership_id
|
|
193
|
+
from resolve_xai_authority_pool(
|
|
194
|
+
current_setting('opengeni.account_id')::uuid,
|
|
195
|
+
${input.workspaceId}::uuid,
|
|
196
|
+
${input.subjectId},
|
|
197
|
+
${JSON.stringify(input.authoritySnapshot)}::jsonb
|
|
198
|
+
)`,
|
|
199
|
+
);
|
|
200
|
+
const ownerMembershipId = rows[0]?.membership_id ?? null;
|
|
201
|
+
if (!ownerMembershipId) {
|
|
202
|
+
throw new Error("xAI user authority pool is no longer active");
|
|
203
|
+
}
|
|
204
|
+
return ownerMembershipId;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function assertXaiTurnAuthoritySnapshot(
|
|
208
|
+
db: Database,
|
|
209
|
+
input: {
|
|
210
|
+
accountId: string;
|
|
211
|
+
workspaceId: string;
|
|
212
|
+
turnId: string;
|
|
213
|
+
sessionId?: string;
|
|
214
|
+
executionGeneration?: number;
|
|
215
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
216
|
+
},
|
|
217
|
+
): Promise<void> {
|
|
218
|
+
const rows = await rawRows<{ id: string }>(
|
|
219
|
+
db,
|
|
220
|
+
sql`select id
|
|
221
|
+
from session_turns
|
|
222
|
+
where account_id = ${input.accountId}::uuid
|
|
223
|
+
and workspace_id = ${input.workspaceId}::uuid
|
|
224
|
+
and id = ${input.turnId}::uuid
|
|
225
|
+
${input.sessionId ? sql`and session_id = ${input.sessionId}::uuid` : sql``}
|
|
226
|
+
${input.executionGeneration !== undefined ? sql`and execution_generation = ${input.executionGeneration}` : sql``}
|
|
227
|
+
and xai_provider_account_authority_snapshot =
|
|
228
|
+
${JSON.stringify(input.authoritySnapshot)}::jsonb
|
|
229
|
+
for share`,
|
|
230
|
+
);
|
|
231
|
+
if (!rows[0]) {
|
|
232
|
+
throw new Error("xAI logical turn authority snapshot is unavailable");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function assertXaiCredentialInPool(
|
|
237
|
+
db: Database,
|
|
238
|
+
input: {
|
|
239
|
+
accountId: string;
|
|
240
|
+
workspaceId: string;
|
|
241
|
+
credentialId: string;
|
|
242
|
+
authorityScope: XaiAccountAuthorityScope;
|
|
243
|
+
ownerMembershipId: string | null;
|
|
244
|
+
},
|
|
245
|
+
): Promise<void> {
|
|
246
|
+
const [row] = await db
|
|
247
|
+
.select({ id: schema.xaiSubscriptionCredentials.id })
|
|
248
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
249
|
+
.where(
|
|
250
|
+
and(
|
|
251
|
+
eq(schema.xaiSubscriptionCredentials.accountId, input.accountId),
|
|
252
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
253
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
254
|
+
eq(schema.xaiSubscriptionCredentials.authorityScope, input.authorityScope),
|
|
255
|
+
input.ownerMembershipId === null
|
|
256
|
+
? isNull(schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId)
|
|
257
|
+
: eq(
|
|
258
|
+
schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId,
|
|
259
|
+
input.ownerMembershipId,
|
|
260
|
+
),
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
.limit(1);
|
|
264
|
+
if (!row) throw new Error("xAI credential is outside the authorized account pool");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function createXaiSubscriptionCredential(
|
|
268
|
+
db: Database,
|
|
269
|
+
input: {
|
|
270
|
+
accountId: string;
|
|
271
|
+
workspaceId: string;
|
|
272
|
+
subjectId: string;
|
|
273
|
+
scope?: XaiAccountAuthorityScope;
|
|
274
|
+
encryptionKey: Uint8Array;
|
|
275
|
+
secret: XaiCredentialSecretV1;
|
|
276
|
+
providerAccountId?: string | null;
|
|
277
|
+
label?: string | null;
|
|
278
|
+
accountEmail?: string | null;
|
|
279
|
+
planType?: string | null;
|
|
280
|
+
expiresAt?: Date | null;
|
|
281
|
+
},
|
|
282
|
+
): Promise<{ account: XaiSubscriptionAccountMetadata; authoritySnapshot: XaiAuthoritySnapshot }> {
|
|
283
|
+
assertSecret(input.secret);
|
|
284
|
+
const scope = input.scope ?? "workspace";
|
|
285
|
+
const encrypted = encryptEnvironmentValue(input.encryptionKey, JSON.stringify(input.secret));
|
|
286
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
287
|
+
const rows = await rawRows<{
|
|
288
|
+
credential_id: string;
|
|
289
|
+
authority_generation: number | string | null;
|
|
290
|
+
}>(
|
|
291
|
+
scopedDb,
|
|
292
|
+
sql`select * from create_xai_subscription_credential(
|
|
293
|
+
${input.accountId}::uuid,
|
|
294
|
+
${input.workspaceId}::uuid,
|
|
295
|
+
${input.subjectId},
|
|
296
|
+
${scope},
|
|
297
|
+
${encrypted},
|
|
298
|
+
${input.providerAccountId ?? null},
|
|
299
|
+
${input.label ?? null},
|
|
300
|
+
${input.accountEmail ?? null},
|
|
301
|
+
${input.planType ?? null},
|
|
302
|
+
${input.expiresAt?.toISOString() ?? null}::timestamptz
|
|
303
|
+
)`,
|
|
304
|
+
);
|
|
305
|
+
const created = rows[0];
|
|
306
|
+
if (!created) throw new Error("xAI credential lifecycle returned no row");
|
|
307
|
+
const [row] = await scopedDb
|
|
308
|
+
.select(xaiCredentialMetadataColumns)
|
|
309
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
310
|
+
.where(eq(schema.xaiSubscriptionCredentials.id, created.credential_id))
|
|
311
|
+
.limit(1);
|
|
312
|
+
if (!row) throw new Error("xAI credential lifecycle result is not visible");
|
|
313
|
+
const authoritySnapshot =
|
|
314
|
+
scope === "workspace"
|
|
315
|
+
? WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1
|
|
316
|
+
: XaiProviderAccountAuthoritySnapshotV1.parse({
|
|
317
|
+
version: 1,
|
|
318
|
+
scope: "user",
|
|
319
|
+
authorityGeneration: Number(created.authority_generation),
|
|
320
|
+
});
|
|
321
|
+
return { account: metadataFromRow(row), authoritySnapshot };
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export async function upsertXaiSubscriptionCredential(
|
|
326
|
+
db: Database,
|
|
327
|
+
input: {
|
|
328
|
+
accountId: string;
|
|
329
|
+
workspaceId: string;
|
|
330
|
+
subjectId: string;
|
|
331
|
+
credentialId?: string | null;
|
|
332
|
+
authoritySnapshot?: XaiAuthoritySnapshot;
|
|
333
|
+
scope?: XaiAccountAuthorityScope;
|
|
334
|
+
encryptionKey: Uint8Array;
|
|
335
|
+
secret: XaiCredentialSecretV1;
|
|
336
|
+
providerAccountId?: string | null;
|
|
337
|
+
label?: string | null;
|
|
338
|
+
accountEmail?: string | null;
|
|
339
|
+
planType?: string | null;
|
|
340
|
+
expiresAt?: Date | null;
|
|
341
|
+
},
|
|
342
|
+
): Promise<{ account: XaiSubscriptionAccountMetadata; authoritySnapshot: XaiAuthoritySnapshot }> {
|
|
343
|
+
if (!input.credentialId) {
|
|
344
|
+
if (input.providerAccountId) {
|
|
345
|
+
const existing = await findXaiCredentialByProviderIdentity(db, {
|
|
346
|
+
workspaceId: input.workspaceId,
|
|
347
|
+
subjectId: input.subjectId,
|
|
348
|
+
scope: input.scope ?? "workspace",
|
|
349
|
+
providerAccountId: input.providerAccountId,
|
|
350
|
+
});
|
|
351
|
+
if (existing) {
|
|
352
|
+
return await upsertXaiSubscriptionCredential(db, {
|
|
353
|
+
...input,
|
|
354
|
+
credentialId: existing.credentialId,
|
|
355
|
+
authoritySnapshot: existing.authoritySnapshot,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return await createXaiSubscriptionCredential(db, input);
|
|
360
|
+
}
|
|
361
|
+
if (!input.authoritySnapshot) {
|
|
362
|
+
throw new Error("Existing xAI credentials require their frozen authority snapshot");
|
|
363
|
+
}
|
|
364
|
+
const credentialId = input.credentialId;
|
|
365
|
+
assertSecret(input.secret);
|
|
366
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
367
|
+
const encrypted = encryptEnvironmentValue(input.encryptionKey, JSON.stringify(input.secret));
|
|
368
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
369
|
+
const authorized = await rawRows<{ id: string }>(
|
|
370
|
+
scopedDb,
|
|
371
|
+
sql`select id from revalidate_xai_subscription_authority(
|
|
372
|
+
${input.workspaceId}::uuid, ${input.subjectId}, ${credentialId}::uuid,
|
|
373
|
+
${JSON.stringify(snapshot)}::jsonb
|
|
374
|
+
)`,
|
|
375
|
+
);
|
|
376
|
+
if (!authorized[0]) throw new Error("xAI provider-account authority is no longer active");
|
|
377
|
+
const [row] = await scopedDb
|
|
378
|
+
.update(schema.xaiSubscriptionCredentials)
|
|
379
|
+
.set({
|
|
380
|
+
credentialEncrypted: encrypted,
|
|
381
|
+
providerAccountId: input.providerAccountId ?? null,
|
|
382
|
+
label: input.label ?? null,
|
|
383
|
+
accountEmail: input.accountEmail ?? null,
|
|
384
|
+
planType: input.planType ?? null,
|
|
385
|
+
expiresAt: input.expiresAt ?? null,
|
|
386
|
+
lastRefreshAt: new Date(),
|
|
387
|
+
status: "active",
|
|
388
|
+
lastError: null,
|
|
389
|
+
version: sql`${schema.xaiSubscriptionCredentials.version} + 1`,
|
|
390
|
+
updatedAt: new Date(),
|
|
391
|
+
})
|
|
392
|
+
.where(eq(schema.xaiSubscriptionCredentials.id, credentialId))
|
|
393
|
+
.returning(xaiCredentialMetadataColumns);
|
|
394
|
+
if (!row) throw new Error("xAI credential update lost its authority fence");
|
|
395
|
+
return { account: metadataFromRow(row), authoritySnapshot: snapshot };
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function findXaiCredentialByProviderIdentity(
|
|
400
|
+
db: Database,
|
|
401
|
+
input: {
|
|
402
|
+
workspaceId: string;
|
|
403
|
+
subjectId: string;
|
|
404
|
+
scope: XaiAccountAuthorityScope;
|
|
405
|
+
providerAccountId: string;
|
|
406
|
+
},
|
|
407
|
+
): Promise<{ credentialId: string; authoritySnapshot: XaiAuthoritySnapshot } | null> {
|
|
408
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
409
|
+
const [row] = await scopedDb
|
|
410
|
+
.select({
|
|
411
|
+
id: schema.xaiSubscriptionCredentials.id,
|
|
412
|
+
authorityScope: schema.xaiSubscriptionCredentials.authorityScope,
|
|
413
|
+
authorityGeneration:
|
|
414
|
+
schema.xaiSubscriptionCredentials.organizationUserResourceAuthorityGeneration,
|
|
415
|
+
})
|
|
416
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
417
|
+
.where(
|
|
418
|
+
and(
|
|
419
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
420
|
+
eq(schema.xaiSubscriptionCredentials.authorityScope, input.scope),
|
|
421
|
+
eq(schema.xaiSubscriptionCredentials.providerAccountId, input.providerAccountId),
|
|
422
|
+
),
|
|
423
|
+
)
|
|
424
|
+
.limit(1);
|
|
425
|
+
if (!row) return null;
|
|
426
|
+
return {
|
|
427
|
+
credentialId: row.id,
|
|
428
|
+
authoritySnapshot:
|
|
429
|
+
row.authorityScope === "workspace"
|
|
430
|
+
? WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1
|
|
431
|
+
: XaiProviderAccountAuthoritySnapshotV1.parse({
|
|
432
|
+
version: 1,
|
|
433
|
+
scope: "user",
|
|
434
|
+
authorityGeneration: row.authorityGeneration,
|
|
435
|
+
}),
|
|
436
|
+
};
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export async function listXaiSubscriptionAccountsMetadata(
|
|
441
|
+
db: Database,
|
|
442
|
+
input: { workspaceId: string; subjectId: string },
|
|
443
|
+
): Promise<XaiSubscriptionAccountMetadata[]> {
|
|
444
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
445
|
+
const rows = await scopedDb
|
|
446
|
+
.select(xaiCredentialMetadataColumns)
|
|
447
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
448
|
+
.where(eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId))
|
|
449
|
+
.orderBy(
|
|
450
|
+
asc(schema.xaiSubscriptionCredentials.createdAt),
|
|
451
|
+
asc(schema.xaiSubscriptionCredentials.id),
|
|
452
|
+
);
|
|
453
|
+
return rows.map(metadataFromRow);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Metadata-only readiness check for the SuperGrok model catalog.
|
|
459
|
+
*
|
|
460
|
+
* Like Codex, allocator eligibility is runtime scheduling state, not connection
|
|
461
|
+
* readiness. With rotation enabled any healthy account makes the rail ready;
|
|
462
|
+
* with rotation disabled the explicit active account is authoritative.
|
|
463
|
+
*/
|
|
464
|
+
export async function workspaceXaiSubscriptionActive(
|
|
465
|
+
db: Database,
|
|
466
|
+
settings: { readonly supergrokSubscriptionEnabled: boolean },
|
|
467
|
+
workspaceId: string,
|
|
468
|
+
subjectId: string,
|
|
469
|
+
): Promise<boolean> {
|
|
470
|
+
if (!settings.supergrokSubscriptionEnabled) return false;
|
|
471
|
+
const authoritySnapshot = await resolveXaiProviderAccountAuthoritySnapshotForAcceptance(db, {
|
|
472
|
+
workspaceId,
|
|
473
|
+
subjectId,
|
|
474
|
+
});
|
|
475
|
+
const [accounts, rotation] = await Promise.all([
|
|
476
|
+
listXaiSubscriptionAccountsMetadata(db, { workspaceId, subjectId }),
|
|
477
|
+
getXaiRotationSettings(db, { workspaceId, subjectId, authoritySnapshot }),
|
|
478
|
+
]);
|
|
479
|
+
const activeCredentialId = rotation?.activeCredentialId ?? null;
|
|
480
|
+
const now = new Date();
|
|
481
|
+
const eligible = (account: XaiSubscriptionAccountMetadata) =>
|
|
482
|
+
account.status === "active" &&
|
|
483
|
+
account.allocatorEnabled &&
|
|
484
|
+
(!account.exhaustedUntil || account.exhaustedUntil <= now);
|
|
485
|
+
if (rotation?.rotationEnabled !== false) {
|
|
486
|
+
return accounts.some(eligible);
|
|
487
|
+
}
|
|
488
|
+
return accounts.some((account) => account.id === activeCredentialId && eligible(account));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export async function getXaiSubscriptionAccountMetadata(
|
|
492
|
+
db: Database,
|
|
493
|
+
input: { workspaceId: string; subjectId: string; credentialId: string },
|
|
494
|
+
): Promise<XaiSubscriptionAccountMetadata | null> {
|
|
495
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
496
|
+
const [row] = await scopedDb
|
|
497
|
+
.select(xaiCredentialMetadataColumns)
|
|
498
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
499
|
+
.where(
|
|
500
|
+
and(
|
|
501
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
502
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
503
|
+
),
|
|
504
|
+
)
|
|
505
|
+
.limit(1);
|
|
506
|
+
return row ? metadataFromRow(row) : null;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export async function getXaiSubscriptionAccountAuthoritySnapshot(
|
|
511
|
+
db: Database,
|
|
512
|
+
input: { workspaceId: string; subjectId: string; credentialId: string },
|
|
513
|
+
): Promise<XaiAuthoritySnapshot | null> {
|
|
514
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
515
|
+
const [row] = await scopedDb
|
|
516
|
+
.select({
|
|
517
|
+
authorityScope: schema.xaiSubscriptionCredentials.authorityScope,
|
|
518
|
+
authorityGeneration:
|
|
519
|
+
schema.xaiSubscriptionCredentials.organizationUserResourceAuthorityGeneration,
|
|
520
|
+
})
|
|
521
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
522
|
+
.where(
|
|
523
|
+
and(
|
|
524
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
525
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
526
|
+
),
|
|
527
|
+
)
|
|
528
|
+
.limit(1);
|
|
529
|
+
if (!row) return null;
|
|
530
|
+
return row.authorityScope === "workspace"
|
|
531
|
+
? WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1
|
|
532
|
+
: XaiProviderAccountAuthoritySnapshotV1.parse({
|
|
533
|
+
version: 1,
|
|
534
|
+
scope: "user",
|
|
535
|
+
authorityGeneration: row.authorityGeneration,
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Resolve the provider-account authority frozen on a newly accepted human turn.
|
|
542
|
+
* Workspace authority is the default. A caller's private pool becomes effective
|
|
543
|
+
* only after that exact pool has an active credential pointer, which is set by
|
|
544
|
+
* the caller's explicit private connection/activation action.
|
|
545
|
+
*/
|
|
546
|
+
export async function resolveXaiProviderAccountAuthoritySnapshotForAcceptance(
|
|
547
|
+
db: Database,
|
|
548
|
+
input: { workspaceId: string; subjectId: string },
|
|
549
|
+
): Promise<XaiAuthoritySnapshot> {
|
|
550
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
551
|
+
return await resolveXaiProviderAccountAuthoritySnapshotForAcceptanceInTransaction(scopedDb, {
|
|
552
|
+
workspaceId: input.workspaceId,
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** Transaction-local acceptance resolver. The caller must already have set the
|
|
558
|
+
* exact authenticated subject GUC on this same transaction. */
|
|
559
|
+
export async function resolveXaiProviderAccountAuthoritySnapshotForAcceptanceInTransaction(
|
|
560
|
+
db: Database,
|
|
561
|
+
input: { workspaceId: string },
|
|
562
|
+
): Promise<XaiAuthoritySnapshot> {
|
|
563
|
+
const [row] = await db
|
|
564
|
+
.select({
|
|
565
|
+
authorityGeneration:
|
|
566
|
+
schema.xaiSubscriptionCredentials.organizationUserResourceAuthorityGeneration,
|
|
567
|
+
})
|
|
568
|
+
.from(schema.xaiRotationSettings)
|
|
569
|
+
.innerJoin(
|
|
570
|
+
schema.xaiSubscriptionCredentials,
|
|
571
|
+
and(
|
|
572
|
+
eq(schema.xaiSubscriptionCredentials.id, schema.xaiRotationSettings.activeCredentialId),
|
|
573
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, schema.xaiRotationSettings.workspaceId),
|
|
574
|
+
eq(schema.xaiSubscriptionCredentials.authorityScope, "user"),
|
|
575
|
+
eq(schema.xaiSubscriptionCredentials.status, "active"),
|
|
576
|
+
),
|
|
577
|
+
)
|
|
578
|
+
.where(
|
|
579
|
+
and(
|
|
580
|
+
eq(schema.xaiRotationSettings.workspaceId, input.workspaceId),
|
|
581
|
+
eq(schema.xaiRotationSettings.authorityScope, "user"),
|
|
582
|
+
),
|
|
583
|
+
)
|
|
584
|
+
.limit(1);
|
|
585
|
+
if (!row) return WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1;
|
|
586
|
+
return XaiProviderAccountAuthoritySnapshotV1.parse({
|
|
587
|
+
version: 1,
|
|
588
|
+
scope: "user",
|
|
589
|
+
authorityGeneration: row.authorityGeneration,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export async function updateXaiSubscriptionAccountSettings(
|
|
594
|
+
db: Database,
|
|
595
|
+
input: {
|
|
596
|
+
workspaceId: string;
|
|
597
|
+
subjectId: string;
|
|
598
|
+
credentialId: string;
|
|
599
|
+
expectedVersion: number;
|
|
600
|
+
label?: string | null;
|
|
601
|
+
allocatorEnabled?: boolean;
|
|
602
|
+
},
|
|
603
|
+
): Promise<XaiSubscriptionAccountMetadata> {
|
|
604
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
605
|
+
const [row] = await scopedDb
|
|
606
|
+
.update(schema.xaiSubscriptionCredentials)
|
|
607
|
+
.set({
|
|
608
|
+
...(input.label !== undefined ? { label: input.label } : {}),
|
|
609
|
+
...(input.allocatorEnabled !== undefined
|
|
610
|
+
? {
|
|
611
|
+
allocatorEnabled: input.allocatorEnabled,
|
|
612
|
+
allocatorVersion: sql`${schema.xaiSubscriptionCredentials.allocatorVersion} + 1`,
|
|
613
|
+
}
|
|
614
|
+
: {}),
|
|
615
|
+
version: sql`${schema.xaiSubscriptionCredentials.version} + 1`,
|
|
616
|
+
updatedAt: new Date(),
|
|
617
|
+
})
|
|
618
|
+
.where(
|
|
619
|
+
and(
|
|
620
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
621
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
622
|
+
eq(schema.xaiSubscriptionCredentials.version, input.expectedVersion),
|
|
623
|
+
),
|
|
624
|
+
)
|
|
625
|
+
.returning(xaiCredentialMetadataColumns);
|
|
626
|
+
if (!row) throw new Error("xAI subscription account settings changed");
|
|
627
|
+
return metadataFromRow(row);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export type XaiAllocatorUpdateResult =
|
|
632
|
+
| {
|
|
633
|
+
kind: "updated" | "unchanged" | "conflict";
|
|
634
|
+
allocatorEnabled: boolean;
|
|
635
|
+
allocatorVersion: number;
|
|
636
|
+
allocatorUpdatedAt: Date | null;
|
|
637
|
+
}
|
|
638
|
+
| { kind: "not_found" };
|
|
639
|
+
|
|
640
|
+
export async function updateXaiAllocatorEligibility(
|
|
641
|
+
db: Database,
|
|
642
|
+
input: {
|
|
643
|
+
workspaceId: string;
|
|
644
|
+
subjectId: string;
|
|
645
|
+
credentialId: string;
|
|
646
|
+
enabled: boolean;
|
|
647
|
+
expectedVersion: number;
|
|
648
|
+
},
|
|
649
|
+
): Promise<XaiAllocatorUpdateResult> {
|
|
650
|
+
return await withWorkspaceSubjectRls(
|
|
651
|
+
db,
|
|
652
|
+
input.workspaceId,
|
|
653
|
+
input.subjectId,
|
|
654
|
+
async (scopedDb) =>
|
|
655
|
+
await scopedDb.transaction(async (tx) => {
|
|
656
|
+
const [row] = await tx
|
|
657
|
+
.select({
|
|
658
|
+
allocatorEnabled: schema.xaiSubscriptionCredentials.allocatorEnabled,
|
|
659
|
+
allocatorVersion: schema.xaiSubscriptionCredentials.allocatorVersion,
|
|
660
|
+
allocatorUpdatedAt: schema.xaiSubscriptionCredentials.allocatorUpdatedAt,
|
|
661
|
+
})
|
|
662
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
663
|
+
.where(
|
|
664
|
+
and(
|
|
665
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
666
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
667
|
+
),
|
|
668
|
+
)
|
|
669
|
+
.for("update")
|
|
670
|
+
.limit(1);
|
|
671
|
+
if (!row) return { kind: "not_found" } as const;
|
|
672
|
+
const current = {
|
|
673
|
+
allocatorEnabled: row.allocatorEnabled,
|
|
674
|
+
allocatorVersion: row.allocatorVersion,
|
|
675
|
+
allocatorUpdatedAt: row.allocatorUpdatedAt,
|
|
676
|
+
};
|
|
677
|
+
if (row.allocatorEnabled === input.enabled) {
|
|
678
|
+
return { kind: "unchanged", ...current } as const;
|
|
679
|
+
}
|
|
680
|
+
if (row.allocatorVersion !== input.expectedVersion) {
|
|
681
|
+
return { kind: "conflict", ...current } as const;
|
|
682
|
+
}
|
|
683
|
+
const changedAt = new Date();
|
|
684
|
+
const [updated] = await tx
|
|
685
|
+
.update(schema.xaiSubscriptionCredentials)
|
|
686
|
+
.set({
|
|
687
|
+
allocatorEnabled: input.enabled,
|
|
688
|
+
allocatorVersion: sql`${schema.xaiSubscriptionCredentials.allocatorVersion} + 1`,
|
|
689
|
+
allocatorUpdatedAt: changedAt,
|
|
690
|
+
})
|
|
691
|
+
.where(
|
|
692
|
+
and(
|
|
693
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
694
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
695
|
+
eq(schema.xaiSubscriptionCredentials.allocatorVersion, input.expectedVersion),
|
|
696
|
+
),
|
|
697
|
+
)
|
|
698
|
+
.returning({
|
|
699
|
+
allocatorEnabled: schema.xaiSubscriptionCredentials.allocatorEnabled,
|
|
700
|
+
allocatorVersion: schema.xaiSubscriptionCredentials.allocatorVersion,
|
|
701
|
+
allocatorUpdatedAt: schema.xaiSubscriptionCredentials.allocatorUpdatedAt,
|
|
702
|
+
});
|
|
703
|
+
if (!updated) throw new Error("xAI allocator row changed while locked");
|
|
704
|
+
return { kind: "updated", ...updated } as const;
|
|
705
|
+
}),
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export async function renameXaiSubscriptionAccount(
|
|
710
|
+
db: Database,
|
|
711
|
+
input: {
|
|
712
|
+
workspaceId: string;
|
|
713
|
+
subjectId: string;
|
|
714
|
+
credentialId: string;
|
|
715
|
+
label: string | null;
|
|
716
|
+
},
|
|
717
|
+
): Promise<XaiSubscriptionAccountMetadata | null> {
|
|
718
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
719
|
+
const [row] = await scopedDb
|
|
720
|
+
.update(schema.xaiSubscriptionCredentials)
|
|
721
|
+
.set({
|
|
722
|
+
label: input.label,
|
|
723
|
+
version: sql`${schema.xaiSubscriptionCredentials.version} + 1`,
|
|
724
|
+
updatedAt: new Date(),
|
|
725
|
+
})
|
|
726
|
+
.where(
|
|
727
|
+
and(
|
|
728
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
729
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
730
|
+
),
|
|
731
|
+
)
|
|
732
|
+
.returning(xaiCredentialMetadataColumns);
|
|
733
|
+
return row ? metadataFromRow(row) : null;
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export async function disconnectXaiSubscriptionCredential(
|
|
738
|
+
db: Database,
|
|
739
|
+
input: {
|
|
740
|
+
accountId: string;
|
|
741
|
+
workspaceId: string;
|
|
742
|
+
subjectId: string;
|
|
743
|
+
credentialId: string;
|
|
744
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
745
|
+
},
|
|
746
|
+
): Promise<boolean> {
|
|
747
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
748
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
749
|
+
const rows = await rawRows<{ disconnected: boolean }>(
|
|
750
|
+
scopedDb,
|
|
751
|
+
sql`select disconnect_xai_subscription_credential(
|
|
752
|
+
${input.accountId}::uuid, ${input.workspaceId}::uuid,
|
|
753
|
+
${input.subjectId}, ${input.credentialId}::uuid,
|
|
754
|
+
${JSON.stringify(snapshot)}::jsonb
|
|
755
|
+
) as disconnected`,
|
|
756
|
+
);
|
|
757
|
+
return rows[0]?.disconnected === true;
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export async function materializeXaiCredentialForRun(
|
|
762
|
+
db: Database,
|
|
763
|
+
input: {
|
|
764
|
+
workspaceId: string;
|
|
765
|
+
subjectId: string;
|
|
766
|
+
credentialId: string;
|
|
767
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
768
|
+
encryptionKey: Uint8Array;
|
|
769
|
+
},
|
|
770
|
+
): Promise<XaiCredentialForRun> {
|
|
771
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
772
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
773
|
+
const rows = await rawRows<{ id: string }>(
|
|
774
|
+
scopedDb,
|
|
775
|
+
sql`select id from revalidate_xai_subscription_authority(
|
|
776
|
+
${input.workspaceId}::uuid,
|
|
777
|
+
${input.subjectId},
|
|
778
|
+
${input.credentialId}::uuid,
|
|
779
|
+
${JSON.stringify(snapshot)}::jsonb
|
|
780
|
+
)`,
|
|
781
|
+
);
|
|
782
|
+
if (!rows[0]) throw new Error("xAI provider-account authority is no longer active");
|
|
783
|
+
const [row] = await scopedDb
|
|
784
|
+
.select({
|
|
785
|
+
...xaiCredentialMetadataColumns,
|
|
786
|
+
credentialEncrypted: schema.xaiSubscriptionCredentials.credentialEncrypted,
|
|
787
|
+
})
|
|
788
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
789
|
+
.where(
|
|
790
|
+
and(
|
|
791
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
792
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
793
|
+
eq(schema.xaiSubscriptionCredentials.status, "active"),
|
|
794
|
+
),
|
|
795
|
+
)
|
|
796
|
+
.limit(1);
|
|
797
|
+
if (!row) throw new Error("xAI credential is unavailable");
|
|
798
|
+
return {
|
|
799
|
+
...metadataFromRow(row),
|
|
800
|
+
secret: parseSecret(decryptEnvironmentValue(input.encryptionKey, row.credentialEncrypted)),
|
|
801
|
+
authoritySnapshot: snapshot,
|
|
802
|
+
};
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export type XaiSerializedCredentialRefreshResult = {
|
|
807
|
+
credential: XaiCredentialForRun;
|
|
808
|
+
refreshed: boolean;
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Refresh one connected xAI account under a database row lock.
|
|
813
|
+
*
|
|
814
|
+
* OAuth refresh tokens may rotate. Multiple sessions are allowed to share the
|
|
815
|
+
* same account, so an unlocked read-refresh-write sequence can make one
|
|
816
|
+
* successful refresh invalidate every other in-flight refresh. This operation
|
|
817
|
+
* re-reads the secret after acquiring the lock and skips the provider call when
|
|
818
|
+
* another request already installed a newer token pair.
|
|
819
|
+
*/
|
|
820
|
+
export async function refreshXaiSubscriptionCredentialSerialized(
|
|
821
|
+
db: Database,
|
|
822
|
+
input: {
|
|
823
|
+
accountId: string;
|
|
824
|
+
workspaceId: string;
|
|
825
|
+
subjectId: string;
|
|
826
|
+
credentialId: string;
|
|
827
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
828
|
+
encryptionKey: Uint8Array;
|
|
829
|
+
observedAccessToken: string | undefined;
|
|
830
|
+
observedRefreshToken: string | undefined;
|
|
831
|
+
refresh: (current: XaiCredentialForRun) => Promise<{
|
|
832
|
+
secret: XaiCredentialSecretV1;
|
|
833
|
+
expiresAt: Date | null;
|
|
834
|
+
}>;
|
|
835
|
+
},
|
|
836
|
+
): Promise<XaiSerializedCredentialRefreshResult> {
|
|
837
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
838
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
839
|
+
const authorized = await rawRows<{ id: string }>(
|
|
840
|
+
scopedDb,
|
|
841
|
+
sql`select id from revalidate_xai_subscription_authority(
|
|
842
|
+
${input.workspaceId}::uuid,
|
|
843
|
+
${input.subjectId},
|
|
844
|
+
${input.credentialId}::uuid,
|
|
845
|
+
${JSON.stringify(snapshot)}::jsonb
|
|
846
|
+
)`,
|
|
847
|
+
);
|
|
848
|
+
if (!authorized[0]) throw new Error("xAI provider-account authority is no longer active");
|
|
849
|
+
|
|
850
|
+
const [row] = await scopedDb
|
|
851
|
+
.select({
|
|
852
|
+
...xaiCredentialMetadataColumns,
|
|
853
|
+
credentialEncrypted: schema.xaiSubscriptionCredentials.credentialEncrypted,
|
|
854
|
+
})
|
|
855
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
856
|
+
.where(
|
|
857
|
+
and(
|
|
858
|
+
eq(schema.xaiSubscriptionCredentials.accountId, input.accountId),
|
|
859
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
860
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
861
|
+
eq(schema.xaiSubscriptionCredentials.status, "active"),
|
|
862
|
+
),
|
|
863
|
+
)
|
|
864
|
+
.for("update")
|
|
865
|
+
.limit(1);
|
|
866
|
+
if (!row) throw new Error("xAI credential is unavailable");
|
|
867
|
+
|
|
868
|
+
const currentSecret = parseSecret(
|
|
869
|
+
decryptEnvironmentValue(input.encryptionKey, row.credentialEncrypted),
|
|
870
|
+
);
|
|
871
|
+
const current: XaiCredentialForRun = {
|
|
872
|
+
...metadataFromRow(row),
|
|
873
|
+
secret: currentSecret,
|
|
874
|
+
authoritySnapshot: snapshot,
|
|
875
|
+
};
|
|
876
|
+
if (
|
|
877
|
+
currentSecret.accessToken !== input.observedAccessToken ||
|
|
878
|
+
currentSecret.refreshToken !== input.observedRefreshToken
|
|
879
|
+
) {
|
|
880
|
+
return { credential: current, refreshed: false };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const next = await input.refresh(current);
|
|
884
|
+
assertSecret(next.secret);
|
|
885
|
+
const credentialEncrypted = encryptEnvironmentValue(
|
|
886
|
+
input.encryptionKey,
|
|
887
|
+
JSON.stringify(next.secret),
|
|
888
|
+
);
|
|
889
|
+
const [updated] = await scopedDb
|
|
890
|
+
.update(schema.xaiSubscriptionCredentials)
|
|
891
|
+
.set({
|
|
892
|
+
credentialEncrypted,
|
|
893
|
+
expiresAt: next.expiresAt,
|
|
894
|
+
lastRefreshAt: new Date(),
|
|
895
|
+
status: "active",
|
|
896
|
+
lastError: null,
|
|
897
|
+
version: sql`${schema.xaiSubscriptionCredentials.version} + 1`,
|
|
898
|
+
updatedAt: new Date(),
|
|
899
|
+
})
|
|
900
|
+
.where(
|
|
901
|
+
and(
|
|
902
|
+
eq(schema.xaiSubscriptionCredentials.accountId, input.accountId),
|
|
903
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
904
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
905
|
+
),
|
|
906
|
+
)
|
|
907
|
+
.returning(xaiCredentialMetadataColumns);
|
|
908
|
+
if (!updated) throw new Error("xAI credential refresh lost its authority fence");
|
|
909
|
+
return {
|
|
910
|
+
credential: {
|
|
911
|
+
...metadataFromRow(updated),
|
|
912
|
+
secret: next.secret,
|
|
913
|
+
authoritySnapshot: snapshot,
|
|
914
|
+
},
|
|
915
|
+
refreshed: true,
|
|
916
|
+
};
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
export async function acquireXaiCredentialLease(
|
|
921
|
+
db: Database,
|
|
922
|
+
input: {
|
|
923
|
+
accountId: string;
|
|
924
|
+
workspaceId: string;
|
|
925
|
+
subjectId: string;
|
|
926
|
+
sessionId: string;
|
|
927
|
+
turnId: string;
|
|
928
|
+
holderId: string;
|
|
929
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
930
|
+
pinnedCredentialId?: string | null;
|
|
931
|
+
pinSource?: "manual" | "policy" | null;
|
|
932
|
+
now?: Date;
|
|
933
|
+
leaseTtlMs?: number;
|
|
934
|
+
},
|
|
935
|
+
): Promise<XaiCredentialLeaseResult> {
|
|
936
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
937
|
+
const now = input.now ?? new Date();
|
|
938
|
+
const leaseTtlMs = input.leaseTtlMs ?? XAI_CREDENTIAL_LEASE_TTL_MS;
|
|
939
|
+
if (!Number.isFinite(leaseTtlMs) || leaseTtlMs <= 0) {
|
|
940
|
+
throw new Error("xAI credential lease TTL must be positive");
|
|
941
|
+
}
|
|
942
|
+
if (!input.holderId.trim()) {
|
|
943
|
+
throw new Error("xAI credential lease holder id is required");
|
|
944
|
+
}
|
|
945
|
+
const leasedUntil = new Date(now.getTime() + leaseTtlMs);
|
|
946
|
+
return await withWorkspaceSubjectRls(
|
|
947
|
+
db,
|
|
948
|
+
input.workspaceId,
|
|
949
|
+
input.subjectId,
|
|
950
|
+
async (scopedDb) =>
|
|
951
|
+
await scopedDb.transaction(async (tx) => {
|
|
952
|
+
await assertXaiTurnAuthoritySnapshot(tx, {
|
|
953
|
+
accountId: input.accountId,
|
|
954
|
+
workspaceId: input.workspaceId,
|
|
955
|
+
turnId: input.turnId,
|
|
956
|
+
authoritySnapshot: snapshot,
|
|
957
|
+
});
|
|
958
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(tx, {
|
|
959
|
+
workspaceId: input.workspaceId,
|
|
960
|
+
subjectId: input.subjectId,
|
|
961
|
+
authoritySnapshot: snapshot,
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
await tx
|
|
965
|
+
.insert(schema.xaiRotationSettings)
|
|
966
|
+
.values({
|
|
967
|
+
accountId: input.accountId,
|
|
968
|
+
workspaceId: input.workspaceId,
|
|
969
|
+
authorityScope: snapshot.scope,
|
|
970
|
+
ownerOrganizationMembershipId: ownerMembershipId,
|
|
971
|
+
})
|
|
972
|
+
.onConflictDoNothing();
|
|
973
|
+
const [settings] = await tx
|
|
974
|
+
.select()
|
|
975
|
+
.from(schema.xaiRotationSettings)
|
|
976
|
+
.where(
|
|
977
|
+
and(
|
|
978
|
+
eq(schema.xaiRotationSettings.workspaceId, input.workspaceId),
|
|
979
|
+
eq(schema.xaiRotationSettings.authorityScope, snapshot.scope),
|
|
980
|
+
ownerMembershipId === null
|
|
981
|
+
? isNull(schema.xaiRotationSettings.ownerOrganizationMembershipId)
|
|
982
|
+
: eq(schema.xaiRotationSettings.ownerOrganizationMembershipId, ownerMembershipId),
|
|
983
|
+
),
|
|
984
|
+
)
|
|
985
|
+
.for("update")
|
|
986
|
+
.limit(1);
|
|
987
|
+
if (!settings) throw new Error("xAI rotation settings are unavailable");
|
|
988
|
+
|
|
989
|
+
await tx
|
|
990
|
+
.delete(schema.xaiCredentialLeases)
|
|
991
|
+
.where(
|
|
992
|
+
and(
|
|
993
|
+
eq(schema.xaiCredentialLeases.workspaceId, input.workspaceId),
|
|
994
|
+
lte(schema.xaiCredentialLeases.leasedUntil, now),
|
|
995
|
+
),
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
const [existing] = await tx
|
|
999
|
+
.select()
|
|
1000
|
+
.from(schema.xaiCredentialLeases)
|
|
1001
|
+
.where(
|
|
1002
|
+
and(
|
|
1003
|
+
eq(schema.xaiCredentialLeases.workspaceId, input.workspaceId),
|
|
1004
|
+
eq(schema.xaiCredentialLeases.turnId, input.turnId),
|
|
1005
|
+
gt(schema.xaiCredentialLeases.leasedUntil, now),
|
|
1006
|
+
),
|
|
1007
|
+
)
|
|
1008
|
+
.for("update")
|
|
1009
|
+
.limit(1);
|
|
1010
|
+
if (existing) {
|
|
1011
|
+
const [updated] = await tx
|
|
1012
|
+
.update(schema.xaiCredentialLeases)
|
|
1013
|
+
.set({
|
|
1014
|
+
holderId: input.holderId,
|
|
1015
|
+
generation:
|
|
1016
|
+
existing.holderId === input.holderId
|
|
1017
|
+
? existing.generation
|
|
1018
|
+
: existing.generation + 1,
|
|
1019
|
+
leasedUntil,
|
|
1020
|
+
updatedAt: now,
|
|
1021
|
+
})
|
|
1022
|
+
.where(eq(schema.xaiCredentialLeases.id, existing.id))
|
|
1023
|
+
.returning();
|
|
1024
|
+
const accounts = await tx
|
|
1025
|
+
.select(xaiCredentialMetadataColumns)
|
|
1026
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
1027
|
+
.where(eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId));
|
|
1028
|
+
return {
|
|
1029
|
+
credentialId: updated!.credentialId,
|
|
1030
|
+
rotationEnabled: settings.rotationEnabled,
|
|
1031
|
+
reused: true,
|
|
1032
|
+
holderId: updated!.holderId,
|
|
1033
|
+
generation: updated!.generation,
|
|
1034
|
+
leasedUntil: updated!.leasedUntil,
|
|
1035
|
+
accounts: accounts.map(metadataFromRow),
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const candidates = await tx
|
|
1040
|
+
.select(xaiCredentialAllocationColumns)
|
|
1041
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
1042
|
+
.where(
|
|
1043
|
+
and(
|
|
1044
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
1045
|
+
eq(schema.xaiSubscriptionCredentials.accountId, input.accountId),
|
|
1046
|
+
eq(schema.xaiSubscriptionCredentials.authorityScope, snapshot.scope),
|
|
1047
|
+
ownerMembershipId === null
|
|
1048
|
+
? isNull(schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId)
|
|
1049
|
+
: eq(
|
|
1050
|
+
schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId,
|
|
1051
|
+
ownerMembershipId,
|
|
1052
|
+
),
|
|
1053
|
+
),
|
|
1054
|
+
)
|
|
1055
|
+
.orderBy(
|
|
1056
|
+
asc(schema.xaiSubscriptionCredentials.createdAt),
|
|
1057
|
+
asc(schema.xaiSubscriptionCredentials.id),
|
|
1058
|
+
);
|
|
1059
|
+
const eligible = candidates.filter(
|
|
1060
|
+
(candidate) =>
|
|
1061
|
+
candidate.status === "active" &&
|
|
1062
|
+
candidate.allocatorEnabled &&
|
|
1063
|
+
(!candidate.exhaustedUntil || candidate.exhaustedUntil <= now),
|
|
1064
|
+
);
|
|
1065
|
+
const pinned = input.pinnedCredentialId
|
|
1066
|
+
? eligible.find((candidate) => candidate.id === input.pinnedCredentialId)
|
|
1067
|
+
: undefined;
|
|
1068
|
+
const pinIsManual = input.pinnedCredentialId && input.pinSource !== "policy";
|
|
1069
|
+
const selected = pinIsManual
|
|
1070
|
+
? pinned
|
|
1071
|
+
: settings.rotationEnabled
|
|
1072
|
+
? (pinned ??
|
|
1073
|
+
(eligible.length > 0
|
|
1074
|
+
? eligible[xaiCredentialShardIndex(input.sessionId, eligible.length)]
|
|
1075
|
+
: undefined))
|
|
1076
|
+
: eligible.find((candidate) => candidate.id === settings.activeCredentialId);
|
|
1077
|
+
if (!selected) {
|
|
1078
|
+
return {
|
|
1079
|
+
credentialId: null,
|
|
1080
|
+
rotationEnabled: settings.rotationEnabled,
|
|
1081
|
+
reused: false,
|
|
1082
|
+
holderId: null,
|
|
1083
|
+
generation: null,
|
|
1084
|
+
leasedUntil: null,
|
|
1085
|
+
accounts: candidates.map(metadataFromRow),
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
const [lease] = await tx
|
|
1089
|
+
.insert(schema.xaiCredentialLeases)
|
|
1090
|
+
.values({
|
|
1091
|
+
accountId: input.accountId,
|
|
1092
|
+
workspaceId: input.workspaceId,
|
|
1093
|
+
authorityScope: snapshot.scope,
|
|
1094
|
+
ownerOrganizationMembershipId: ownerMembershipId,
|
|
1095
|
+
credentialId: selected.id,
|
|
1096
|
+
turnId: input.turnId,
|
|
1097
|
+
holderId: input.holderId,
|
|
1098
|
+
leasedUntil,
|
|
1099
|
+
})
|
|
1100
|
+
.returning();
|
|
1101
|
+
await tx
|
|
1102
|
+
.update(schema.xaiSubscriptionCredentials)
|
|
1103
|
+
.set({
|
|
1104
|
+
selectionCount: sql`${schema.xaiSubscriptionCredentials.selectionCount} + 1`,
|
|
1105
|
+
lastSelectedAt: now,
|
|
1106
|
+
updatedAt: now,
|
|
1107
|
+
})
|
|
1108
|
+
.where(eq(schema.xaiSubscriptionCredentials.id, selected.id));
|
|
1109
|
+
// A session policy/manual home must never move the workspace-global
|
|
1110
|
+
// active pointer. A missing pointer is bootstrapped once for rotation-off
|
|
1111
|
+
// fallback and UI state, but healthy sharded turns never churn it.
|
|
1112
|
+
if (settings.activeCredentialId === null) {
|
|
1113
|
+
await tx
|
|
1114
|
+
.update(schema.xaiRotationSettings)
|
|
1115
|
+
.set({
|
|
1116
|
+
activeCredentialId: selected.id,
|
|
1117
|
+
version: sql`${schema.xaiRotationSettings.version} + 1`,
|
|
1118
|
+
updatedAt: now,
|
|
1119
|
+
})
|
|
1120
|
+
.where(eq(schema.xaiRotationSettings.id, settings.id));
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
credentialId: selected.id,
|
|
1124
|
+
rotationEnabled: settings.rotationEnabled,
|
|
1125
|
+
reused: false,
|
|
1126
|
+
holderId: lease!.holderId,
|
|
1127
|
+
generation: lease!.generation,
|
|
1128
|
+
leasedUntil: lease!.leasedUntil,
|
|
1129
|
+
accounts: candidates.map(metadataFromRow),
|
|
1130
|
+
};
|
|
1131
|
+
}),
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Select an authorized connected account for a non-turn operation (voice,
|
|
1137
|
+
* transcription, media). Rotation uses the same stable session/request shard
|
|
1138
|
+
* as turns, but does not create a capacity lease: one account may serve many
|
|
1139
|
+
* concurrent upstream sessions just like Codex subscriptions.
|
|
1140
|
+
*/
|
|
1141
|
+
export async function selectXaiCredentialForUse(
|
|
1142
|
+
db: Database,
|
|
1143
|
+
input: {
|
|
1144
|
+
accountId: string;
|
|
1145
|
+
workspaceId: string;
|
|
1146
|
+
subjectId: string;
|
|
1147
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1148
|
+
shardKey: string;
|
|
1149
|
+
pinnedCredentialId?: string | null;
|
|
1150
|
+
pinSource?: "manual" | "policy" | null;
|
|
1151
|
+
now?: Date;
|
|
1152
|
+
},
|
|
1153
|
+
): Promise<{
|
|
1154
|
+
credentialId: string | null;
|
|
1155
|
+
rotationEnabled: boolean;
|
|
1156
|
+
accounts: XaiSubscriptionAccountMetadata[];
|
|
1157
|
+
}> {
|
|
1158
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1159
|
+
const now = input.now ?? new Date();
|
|
1160
|
+
return await withWorkspaceSubjectRls(
|
|
1161
|
+
db,
|
|
1162
|
+
input.workspaceId,
|
|
1163
|
+
input.subjectId,
|
|
1164
|
+
async (scopedDb) =>
|
|
1165
|
+
await scopedDb.transaction(async (tx) => {
|
|
1166
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(tx, {
|
|
1167
|
+
workspaceId: input.workspaceId,
|
|
1168
|
+
subjectId: input.subjectId,
|
|
1169
|
+
authoritySnapshot: snapshot,
|
|
1170
|
+
});
|
|
1171
|
+
await tx
|
|
1172
|
+
.insert(schema.xaiRotationSettings)
|
|
1173
|
+
.values({
|
|
1174
|
+
accountId: input.accountId,
|
|
1175
|
+
workspaceId: input.workspaceId,
|
|
1176
|
+
authorityScope: snapshot.scope,
|
|
1177
|
+
ownerOrganizationMembershipId: ownerMembershipId,
|
|
1178
|
+
})
|
|
1179
|
+
.onConflictDoNothing();
|
|
1180
|
+
const [settings] = await tx
|
|
1181
|
+
.select()
|
|
1182
|
+
.from(schema.xaiRotationSettings)
|
|
1183
|
+
.where(
|
|
1184
|
+
and(
|
|
1185
|
+
eq(schema.xaiRotationSettings.workspaceId, input.workspaceId),
|
|
1186
|
+
eq(schema.xaiRotationSettings.authorityScope, snapshot.scope),
|
|
1187
|
+
ownerMembershipId === null
|
|
1188
|
+
? isNull(schema.xaiRotationSettings.ownerOrganizationMembershipId)
|
|
1189
|
+
: eq(schema.xaiRotationSettings.ownerOrganizationMembershipId, ownerMembershipId),
|
|
1190
|
+
),
|
|
1191
|
+
)
|
|
1192
|
+
.limit(1);
|
|
1193
|
+
if (!settings) throw new Error("xAI rotation settings are unavailable");
|
|
1194
|
+
const candidates = await tx
|
|
1195
|
+
.select(xaiCredentialAllocationColumns)
|
|
1196
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
1197
|
+
.where(
|
|
1198
|
+
and(
|
|
1199
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
1200
|
+
eq(schema.xaiSubscriptionCredentials.accountId, input.accountId),
|
|
1201
|
+
eq(schema.xaiSubscriptionCredentials.authorityScope, snapshot.scope),
|
|
1202
|
+
ownerMembershipId === null
|
|
1203
|
+
? isNull(schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId)
|
|
1204
|
+
: eq(
|
|
1205
|
+
schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId,
|
|
1206
|
+
ownerMembershipId,
|
|
1207
|
+
),
|
|
1208
|
+
),
|
|
1209
|
+
)
|
|
1210
|
+
.orderBy(
|
|
1211
|
+
asc(schema.xaiSubscriptionCredentials.createdAt),
|
|
1212
|
+
asc(schema.xaiSubscriptionCredentials.id),
|
|
1213
|
+
);
|
|
1214
|
+
const eligible = candidates.filter(
|
|
1215
|
+
(candidate) =>
|
|
1216
|
+
candidate.status === "active" &&
|
|
1217
|
+
candidate.allocatorEnabled &&
|
|
1218
|
+
(!candidate.exhaustedUntil || candidate.exhaustedUntil <= now),
|
|
1219
|
+
);
|
|
1220
|
+
const pinned = input.pinnedCredentialId
|
|
1221
|
+
? eligible.find((candidate) => candidate.id === input.pinnedCredentialId)
|
|
1222
|
+
: undefined;
|
|
1223
|
+
const selected =
|
|
1224
|
+
input.pinnedCredentialId && input.pinSource !== "policy"
|
|
1225
|
+
? pinned
|
|
1226
|
+
: settings.rotationEnabled
|
|
1227
|
+
? (pinned ??
|
|
1228
|
+
(eligible.length > 0
|
|
1229
|
+
? eligible[xaiCredentialShardIndex(input.shardKey, eligible.length)]
|
|
1230
|
+
: undefined))
|
|
1231
|
+
: eligible.find((candidate) => candidate.id === settings.activeCredentialId);
|
|
1232
|
+
return {
|
|
1233
|
+
credentialId: selected?.id ?? null,
|
|
1234
|
+
rotationEnabled: settings.rotationEnabled,
|
|
1235
|
+
accounts: candidates.map(metadataFromRow),
|
|
1236
|
+
};
|
|
1237
|
+
}),
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
export async function releaseXaiCredentialLease(
|
|
1242
|
+
db: Database,
|
|
1243
|
+
input: {
|
|
1244
|
+
workspaceId: string;
|
|
1245
|
+
subjectId: string;
|
|
1246
|
+
turnId: string;
|
|
1247
|
+
holderId: string;
|
|
1248
|
+
generation: number;
|
|
1249
|
+
},
|
|
1250
|
+
): Promise<boolean> {
|
|
1251
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1252
|
+
const deleted = await scopedDb
|
|
1253
|
+
.delete(schema.xaiCredentialLeases)
|
|
1254
|
+
.where(
|
|
1255
|
+
and(
|
|
1256
|
+
eq(schema.xaiCredentialLeases.workspaceId, input.workspaceId),
|
|
1257
|
+
eq(schema.xaiCredentialLeases.turnId, input.turnId),
|
|
1258
|
+
eq(schema.xaiCredentialLeases.holderId, input.holderId),
|
|
1259
|
+
eq(schema.xaiCredentialLeases.generation, input.generation),
|
|
1260
|
+
),
|
|
1261
|
+
)
|
|
1262
|
+
.returning({ id: schema.xaiCredentialLeases.id });
|
|
1263
|
+
return deleted.length === 1;
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
export async function heartbeatXaiCredentialLeaseUntil(
|
|
1268
|
+
db: Database,
|
|
1269
|
+
input: {
|
|
1270
|
+
workspaceId: string;
|
|
1271
|
+
subjectId: string;
|
|
1272
|
+
turnId: string;
|
|
1273
|
+
holderId: string;
|
|
1274
|
+
generation: number;
|
|
1275
|
+
leaseTtlMs?: number;
|
|
1276
|
+
now?: Date;
|
|
1277
|
+
},
|
|
1278
|
+
): Promise<Date | null> {
|
|
1279
|
+
const now = input.now ?? new Date();
|
|
1280
|
+
const leaseTtlMs = input.leaseTtlMs ?? XAI_CREDENTIAL_LEASE_TTL_MS;
|
|
1281
|
+
if (!Number.isFinite(leaseTtlMs) || leaseTtlMs <= 0) {
|
|
1282
|
+
throw new Error("xAI credential lease TTL must be positive");
|
|
1283
|
+
}
|
|
1284
|
+
const leasedUntil = new Date(now.getTime() + leaseTtlMs);
|
|
1285
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1286
|
+
const [row] = await scopedDb
|
|
1287
|
+
.update(schema.xaiCredentialLeases)
|
|
1288
|
+
.set({ leasedUntil, updatedAt: now })
|
|
1289
|
+
.where(
|
|
1290
|
+
and(
|
|
1291
|
+
eq(schema.xaiCredentialLeases.workspaceId, input.workspaceId),
|
|
1292
|
+
eq(schema.xaiCredentialLeases.turnId, input.turnId),
|
|
1293
|
+
eq(schema.xaiCredentialLeases.holderId, input.holderId),
|
|
1294
|
+
eq(schema.xaiCredentialLeases.generation, input.generation),
|
|
1295
|
+
gt(schema.xaiCredentialLeases.leasedUntil, now),
|
|
1296
|
+
),
|
|
1297
|
+
)
|
|
1298
|
+
.returning({ leasedUntil: schema.xaiCredentialLeases.leasedUntil });
|
|
1299
|
+
return row?.leasedUntil ?? null;
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
export async function getXaiRotationSettings(
|
|
1304
|
+
db: Database,
|
|
1305
|
+
input: {
|
|
1306
|
+
workspaceId: string;
|
|
1307
|
+
subjectId: string;
|
|
1308
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1309
|
+
},
|
|
1310
|
+
): Promise<typeof schema.xaiRotationSettings.$inferSelect | null> {
|
|
1311
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1312
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1313
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(scopedDb, {
|
|
1314
|
+
workspaceId: input.workspaceId,
|
|
1315
|
+
subjectId: input.subjectId,
|
|
1316
|
+
authoritySnapshot: snapshot,
|
|
1317
|
+
});
|
|
1318
|
+
const rows = await scopedDb
|
|
1319
|
+
.select()
|
|
1320
|
+
.from(schema.xaiRotationSettings)
|
|
1321
|
+
.where(
|
|
1322
|
+
and(
|
|
1323
|
+
eq(schema.xaiRotationSettings.workspaceId, input.workspaceId),
|
|
1324
|
+
eq(schema.xaiRotationSettings.authorityScope, snapshot.scope),
|
|
1325
|
+
ownerMembershipId === null
|
|
1326
|
+
? isNull(schema.xaiRotationSettings.ownerOrganizationMembershipId)
|
|
1327
|
+
: eq(schema.xaiRotationSettings.ownerOrganizationMembershipId, ownerMembershipId),
|
|
1328
|
+
),
|
|
1329
|
+
);
|
|
1330
|
+
return rows[0] ?? null;
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
export async function ensureXaiRotationSettings(
|
|
1335
|
+
db: Database,
|
|
1336
|
+
input: {
|
|
1337
|
+
accountId: string;
|
|
1338
|
+
workspaceId: string;
|
|
1339
|
+
subjectId: string;
|
|
1340
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1341
|
+
},
|
|
1342
|
+
): Promise<typeof schema.xaiRotationSettings.$inferSelect> {
|
|
1343
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1344
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1345
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(scopedDb, {
|
|
1346
|
+
workspaceId: input.workspaceId,
|
|
1347
|
+
subjectId: input.subjectId,
|
|
1348
|
+
authoritySnapshot: snapshot,
|
|
1349
|
+
});
|
|
1350
|
+
const [row] = await scopedDb
|
|
1351
|
+
.insert(schema.xaiRotationSettings)
|
|
1352
|
+
.values({
|
|
1353
|
+
accountId: input.accountId,
|
|
1354
|
+
workspaceId: input.workspaceId,
|
|
1355
|
+
authorityScope: snapshot.scope,
|
|
1356
|
+
ownerOrganizationMembershipId: ownerMembershipId,
|
|
1357
|
+
})
|
|
1358
|
+
.onConflictDoNothing()
|
|
1359
|
+
.returning();
|
|
1360
|
+
if (row) return row;
|
|
1361
|
+
const [current] = await scopedDb
|
|
1362
|
+
.select()
|
|
1363
|
+
.from(schema.xaiRotationSettings)
|
|
1364
|
+
.where(
|
|
1365
|
+
and(
|
|
1366
|
+
eq(schema.xaiRotationSettings.workspaceId, input.workspaceId),
|
|
1367
|
+
eq(schema.xaiRotationSettings.authorityScope, snapshot.scope),
|
|
1368
|
+
ownerMembershipId === null
|
|
1369
|
+
? isNull(schema.xaiRotationSettings.ownerOrganizationMembershipId)
|
|
1370
|
+
: eq(schema.xaiRotationSettings.ownerOrganizationMembershipId, ownerMembershipId),
|
|
1371
|
+
),
|
|
1372
|
+
)
|
|
1373
|
+
.limit(1);
|
|
1374
|
+
if (!current) throw new Error("xAI rotation settings are unavailable");
|
|
1375
|
+
return current;
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
export async function setActiveXaiCredential(
|
|
1380
|
+
db: Database,
|
|
1381
|
+
input: {
|
|
1382
|
+
accountId: string;
|
|
1383
|
+
workspaceId: string;
|
|
1384
|
+
subjectId: string;
|
|
1385
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1386
|
+
credentialId: string;
|
|
1387
|
+
},
|
|
1388
|
+
): Promise<boolean> {
|
|
1389
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1390
|
+
return await withWorkspaceSubjectRls(
|
|
1391
|
+
db,
|
|
1392
|
+
input.workspaceId,
|
|
1393
|
+
input.subjectId,
|
|
1394
|
+
async (scopedDb) =>
|
|
1395
|
+
await scopedDb.transaction(async (tx) => {
|
|
1396
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(tx, {
|
|
1397
|
+
workspaceId: input.workspaceId,
|
|
1398
|
+
subjectId: input.subjectId,
|
|
1399
|
+
authoritySnapshot: snapshot,
|
|
1400
|
+
});
|
|
1401
|
+
await assertXaiCredentialInPool(tx, {
|
|
1402
|
+
accountId: input.accountId,
|
|
1403
|
+
workspaceId: input.workspaceId,
|
|
1404
|
+
credentialId: input.credentialId,
|
|
1405
|
+
authorityScope: snapshot.scope,
|
|
1406
|
+
ownerMembershipId,
|
|
1407
|
+
});
|
|
1408
|
+
const [credential] = await tx
|
|
1409
|
+
.select({ status: schema.xaiSubscriptionCredentials.status })
|
|
1410
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
1411
|
+
.where(eq(schema.xaiSubscriptionCredentials.id, input.credentialId))
|
|
1412
|
+
.limit(1);
|
|
1413
|
+
if (!credential || credential.status !== "active") return false;
|
|
1414
|
+
await tx
|
|
1415
|
+
.insert(schema.xaiRotationSettings)
|
|
1416
|
+
.values({
|
|
1417
|
+
accountId: input.accountId,
|
|
1418
|
+
workspaceId: input.workspaceId,
|
|
1419
|
+
authorityScope: snapshot.scope,
|
|
1420
|
+
ownerOrganizationMembershipId: ownerMembershipId,
|
|
1421
|
+
activeCredentialId: input.credentialId,
|
|
1422
|
+
})
|
|
1423
|
+
.onConflictDoUpdate({
|
|
1424
|
+
target: [
|
|
1425
|
+
schema.xaiRotationSettings.workspaceId,
|
|
1426
|
+
schema.xaiRotationSettings.authorityScope,
|
|
1427
|
+
schema.xaiRotationSettings.ownerOrganizationMembershipId,
|
|
1428
|
+
],
|
|
1429
|
+
set: {
|
|
1430
|
+
activeCredentialId: input.credentialId,
|
|
1431
|
+
version: sql`${schema.xaiRotationSettings.version} + 1`,
|
|
1432
|
+
updatedAt: new Date(),
|
|
1433
|
+
},
|
|
1434
|
+
});
|
|
1435
|
+
if (snapshot.scope === "workspace") {
|
|
1436
|
+
// Workspace is the deliberate default. Selecting a workspace account
|
|
1437
|
+
// also opts the current subject out of their private pool; FORCE RLS
|
|
1438
|
+
// limits this update to that subject's visible user-scoped row.
|
|
1439
|
+
await tx
|
|
1440
|
+
.update(schema.xaiRotationSettings)
|
|
1441
|
+
.set({
|
|
1442
|
+
activeCredentialId: null,
|
|
1443
|
+
version: sql`${schema.xaiRotationSettings.version} + 1`,
|
|
1444
|
+
updatedAt: new Date(),
|
|
1445
|
+
})
|
|
1446
|
+
.where(
|
|
1447
|
+
and(
|
|
1448
|
+
eq(schema.xaiRotationSettings.workspaceId, input.workspaceId),
|
|
1449
|
+
eq(schema.xaiRotationSettings.authorityScope, "user"),
|
|
1450
|
+
),
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
return true;
|
|
1454
|
+
}),
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
export async function setInitialActiveXaiCredential(
|
|
1459
|
+
db: Database,
|
|
1460
|
+
input: {
|
|
1461
|
+
accountId: string;
|
|
1462
|
+
workspaceId: string;
|
|
1463
|
+
subjectId: string;
|
|
1464
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1465
|
+
credentialId: string;
|
|
1466
|
+
},
|
|
1467
|
+
): Promise<boolean> {
|
|
1468
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1469
|
+
return await withWorkspaceSubjectRls(
|
|
1470
|
+
db,
|
|
1471
|
+
input.workspaceId,
|
|
1472
|
+
input.subjectId,
|
|
1473
|
+
async (scopedDb) =>
|
|
1474
|
+
await scopedDb.transaction(async (tx) => {
|
|
1475
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(tx, {
|
|
1476
|
+
workspaceId: input.workspaceId,
|
|
1477
|
+
subjectId: input.subjectId,
|
|
1478
|
+
authoritySnapshot: snapshot,
|
|
1479
|
+
});
|
|
1480
|
+
await assertXaiCredentialInPool(tx, {
|
|
1481
|
+
accountId: input.accountId,
|
|
1482
|
+
workspaceId: input.workspaceId,
|
|
1483
|
+
credentialId: input.credentialId,
|
|
1484
|
+
authorityScope: snapshot.scope,
|
|
1485
|
+
ownerMembershipId,
|
|
1486
|
+
});
|
|
1487
|
+
await tx
|
|
1488
|
+
.insert(schema.xaiRotationSettings)
|
|
1489
|
+
.values({
|
|
1490
|
+
accountId: input.accountId,
|
|
1491
|
+
workspaceId: input.workspaceId,
|
|
1492
|
+
authorityScope: snapshot.scope,
|
|
1493
|
+
ownerOrganizationMembershipId: ownerMembershipId,
|
|
1494
|
+
})
|
|
1495
|
+
.onConflictDoNothing();
|
|
1496
|
+
const [updated] = await tx
|
|
1497
|
+
.update(schema.xaiRotationSettings)
|
|
1498
|
+
.set({
|
|
1499
|
+
activeCredentialId: input.credentialId,
|
|
1500
|
+
version: sql`${schema.xaiRotationSettings.version} + 1`,
|
|
1501
|
+
updatedAt: new Date(),
|
|
1502
|
+
})
|
|
1503
|
+
.where(
|
|
1504
|
+
and(
|
|
1505
|
+
eq(schema.xaiRotationSettings.workspaceId, input.workspaceId),
|
|
1506
|
+
eq(schema.xaiRotationSettings.authorityScope, snapshot.scope),
|
|
1507
|
+
ownerMembershipId === null
|
|
1508
|
+
? isNull(schema.xaiRotationSettings.ownerOrganizationMembershipId)
|
|
1509
|
+
: eq(schema.xaiRotationSettings.ownerOrganizationMembershipId, ownerMembershipId),
|
|
1510
|
+
isNull(schema.xaiRotationSettings.activeCredentialId),
|
|
1511
|
+
),
|
|
1512
|
+
)
|
|
1513
|
+
.returning({ id: schema.xaiRotationSettings.id });
|
|
1514
|
+
return updated !== undefined;
|
|
1515
|
+
}),
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
export async function disconnectXaiSubscriptionCredentialAndRepick(
|
|
1520
|
+
db: Database,
|
|
1521
|
+
input: {
|
|
1522
|
+
accountId: string;
|
|
1523
|
+
workspaceId: string;
|
|
1524
|
+
subjectId: string;
|
|
1525
|
+
credentialId: string;
|
|
1526
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1527
|
+
},
|
|
1528
|
+
): Promise<{ disconnected: boolean; newActiveCredentialId: string | null }> {
|
|
1529
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1530
|
+
return await withWorkspaceSubjectRls(
|
|
1531
|
+
db,
|
|
1532
|
+
input.workspaceId,
|
|
1533
|
+
input.subjectId,
|
|
1534
|
+
async (scopedDb) =>
|
|
1535
|
+
await scopedDb.transaction(async (tx) => {
|
|
1536
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(tx, {
|
|
1537
|
+
workspaceId: input.workspaceId,
|
|
1538
|
+
subjectId: input.subjectId,
|
|
1539
|
+
authoritySnapshot: snapshot,
|
|
1540
|
+
});
|
|
1541
|
+
const disconnected = await disconnectXaiSubscriptionCredential(tx, input);
|
|
1542
|
+
if (!disconnected) return { disconnected: false, newActiveCredentialId: null };
|
|
1543
|
+
const [settings] = await tx
|
|
1544
|
+
.select()
|
|
1545
|
+
.from(schema.xaiRotationSettings)
|
|
1546
|
+
.where(
|
|
1547
|
+
and(
|
|
1548
|
+
eq(schema.xaiRotationSettings.workspaceId, input.workspaceId),
|
|
1549
|
+
eq(schema.xaiRotationSettings.authorityScope, snapshot.scope),
|
|
1550
|
+
ownerMembershipId === null
|
|
1551
|
+
? isNull(schema.xaiRotationSettings.ownerOrganizationMembershipId)
|
|
1552
|
+
: eq(schema.xaiRotationSettings.ownerOrganizationMembershipId, ownerMembershipId),
|
|
1553
|
+
),
|
|
1554
|
+
)
|
|
1555
|
+
.for("update")
|
|
1556
|
+
.limit(1);
|
|
1557
|
+
if (!settings) return { disconnected: true, newActiveCredentialId: null };
|
|
1558
|
+
if (settings.activeCredentialId !== null) {
|
|
1559
|
+
return { disconnected: true, newActiveCredentialId: settings.activeCredentialId };
|
|
1560
|
+
}
|
|
1561
|
+
const [replacement] = await tx
|
|
1562
|
+
.select({ id: schema.xaiSubscriptionCredentials.id })
|
|
1563
|
+
.from(schema.xaiSubscriptionCredentials)
|
|
1564
|
+
.where(
|
|
1565
|
+
and(
|
|
1566
|
+
eq(schema.xaiSubscriptionCredentials.accountId, input.accountId),
|
|
1567
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
1568
|
+
eq(schema.xaiSubscriptionCredentials.authorityScope, snapshot.scope),
|
|
1569
|
+
ownerMembershipId === null
|
|
1570
|
+
? isNull(schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId)
|
|
1571
|
+
: eq(
|
|
1572
|
+
schema.xaiSubscriptionCredentials.ownerOrganizationMembershipId,
|
|
1573
|
+
ownerMembershipId,
|
|
1574
|
+
),
|
|
1575
|
+
eq(schema.xaiSubscriptionCredentials.status, "active"),
|
|
1576
|
+
),
|
|
1577
|
+
)
|
|
1578
|
+
.orderBy(
|
|
1579
|
+
asc(schema.xaiSubscriptionCredentials.createdAt),
|
|
1580
|
+
asc(schema.xaiSubscriptionCredentials.id),
|
|
1581
|
+
)
|
|
1582
|
+
.limit(1);
|
|
1583
|
+
await tx
|
|
1584
|
+
.update(schema.xaiRotationSettings)
|
|
1585
|
+
.set({
|
|
1586
|
+
activeCredentialId: replacement?.id ?? null,
|
|
1587
|
+
version: sql`${schema.xaiRotationSettings.version} + 1`,
|
|
1588
|
+
updatedAt: new Date(),
|
|
1589
|
+
})
|
|
1590
|
+
.where(eq(schema.xaiRotationSettings.id, settings.id));
|
|
1591
|
+
return { disconnected: true, newActiveCredentialId: replacement?.id ?? null };
|
|
1592
|
+
}),
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
export async function updateXaiRotationSettings(
|
|
1597
|
+
db: Database,
|
|
1598
|
+
input: {
|
|
1599
|
+
workspaceId: string;
|
|
1600
|
+
subjectId: string;
|
|
1601
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1602
|
+
expectedVersion: number;
|
|
1603
|
+
rotationEnabled: boolean;
|
|
1604
|
+
},
|
|
1605
|
+
): Promise<typeof schema.xaiRotationSettings.$inferSelect> {
|
|
1606
|
+
const current = await getXaiRotationSettings(db, input);
|
|
1607
|
+
if (!current || current.version !== input.expectedVersion) {
|
|
1608
|
+
throw new Error("xAI rotation settings changed");
|
|
1609
|
+
}
|
|
1610
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1611
|
+
const [row] = await scopedDb
|
|
1612
|
+
.update(schema.xaiRotationSettings)
|
|
1613
|
+
.set({
|
|
1614
|
+
rotationEnabled: input.rotationEnabled,
|
|
1615
|
+
version: sql`${schema.xaiRotationSettings.version} + 1`,
|
|
1616
|
+
updatedAt: new Date(),
|
|
1617
|
+
})
|
|
1618
|
+
.where(
|
|
1619
|
+
and(
|
|
1620
|
+
eq(schema.xaiRotationSettings.id, current.id),
|
|
1621
|
+
eq(schema.xaiRotationSettings.version, input.expectedVersion),
|
|
1622
|
+
),
|
|
1623
|
+
)
|
|
1624
|
+
.returning();
|
|
1625
|
+
if (!row) throw new Error("xAI rotation settings changed");
|
|
1626
|
+
return row;
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
export async function setXaiSessionAccountPin(
|
|
1631
|
+
db: Database,
|
|
1632
|
+
input: {
|
|
1633
|
+
accountId: string;
|
|
1634
|
+
workspaceId: string;
|
|
1635
|
+
subjectId: string;
|
|
1636
|
+
sessionId: string;
|
|
1637
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1638
|
+
credentialId: string | null;
|
|
1639
|
+
pinSource: "manual" | "policy" | null;
|
|
1640
|
+
/** undefined = unconditional human write; null = row must not exist. */
|
|
1641
|
+
expectedVersion?: number | null;
|
|
1642
|
+
},
|
|
1643
|
+
): Promise<typeof schema.xaiSessionAccountPins.$inferSelect> {
|
|
1644
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1645
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1646
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(scopedDb, {
|
|
1647
|
+
workspaceId: input.workspaceId,
|
|
1648
|
+
subjectId: input.subjectId,
|
|
1649
|
+
authoritySnapshot: snapshot,
|
|
1650
|
+
});
|
|
1651
|
+
if (input.credentialId) {
|
|
1652
|
+
await assertXaiCredentialInPool(scopedDb, {
|
|
1653
|
+
accountId: input.accountId,
|
|
1654
|
+
workspaceId: input.workspaceId,
|
|
1655
|
+
credentialId: input.credentialId,
|
|
1656
|
+
authorityScope: snapshot.scope,
|
|
1657
|
+
ownerMembershipId,
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
const [row] = await scopedDb
|
|
1661
|
+
.insert(schema.xaiSessionAccountPins)
|
|
1662
|
+
.values({
|
|
1663
|
+
accountId: input.accountId,
|
|
1664
|
+
workspaceId: input.workspaceId,
|
|
1665
|
+
sessionId: input.sessionId,
|
|
1666
|
+
authorityScope: snapshot.scope,
|
|
1667
|
+
ownerOrganizationMembershipId: ownerMembershipId,
|
|
1668
|
+
pinnedCredentialId: input.credentialId,
|
|
1669
|
+
pinSource: input.credentialId ? input.pinSource : null,
|
|
1670
|
+
})
|
|
1671
|
+
.onConflictDoUpdate({
|
|
1672
|
+
target: [
|
|
1673
|
+
schema.xaiSessionAccountPins.workspaceId,
|
|
1674
|
+
schema.xaiSessionAccountPins.sessionId,
|
|
1675
|
+
schema.xaiSessionAccountPins.authorityScope,
|
|
1676
|
+
schema.xaiSessionAccountPins.ownerOrganizationMembershipId,
|
|
1677
|
+
],
|
|
1678
|
+
set: {
|
|
1679
|
+
pinnedCredentialId: input.credentialId,
|
|
1680
|
+
pinSource: input.credentialId ? input.pinSource : null,
|
|
1681
|
+
version: sql`${schema.xaiSessionAccountPins.version} + 1`,
|
|
1682
|
+
updatedAt: new Date(),
|
|
1683
|
+
},
|
|
1684
|
+
...(input.expectedVersion !== undefined
|
|
1685
|
+
? {
|
|
1686
|
+
setWhere:
|
|
1687
|
+
input.expectedVersion === null
|
|
1688
|
+
? sql`false`
|
|
1689
|
+
: eq(schema.xaiSessionAccountPins.version, input.expectedVersion),
|
|
1690
|
+
}
|
|
1691
|
+
: {}),
|
|
1692
|
+
})
|
|
1693
|
+
.returning();
|
|
1694
|
+
if (!row) throw new Error("xAI session pin changed");
|
|
1695
|
+
return row;
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
export async function getXaiSessionAccountPin(
|
|
1700
|
+
db: Database,
|
|
1701
|
+
input: {
|
|
1702
|
+
workspaceId: string;
|
|
1703
|
+
subjectId: string;
|
|
1704
|
+
sessionId: string;
|
|
1705
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1706
|
+
},
|
|
1707
|
+
): Promise<typeof schema.xaiSessionAccountPins.$inferSelect | null> {
|
|
1708
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1709
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1710
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(scopedDb, {
|
|
1711
|
+
workspaceId: input.workspaceId,
|
|
1712
|
+
subjectId: input.subjectId,
|
|
1713
|
+
authoritySnapshot: snapshot,
|
|
1714
|
+
});
|
|
1715
|
+
const [row] = await scopedDb
|
|
1716
|
+
.select()
|
|
1717
|
+
.from(schema.xaiSessionAccountPins)
|
|
1718
|
+
.where(
|
|
1719
|
+
and(
|
|
1720
|
+
eq(schema.xaiSessionAccountPins.workspaceId, input.workspaceId),
|
|
1721
|
+
eq(schema.xaiSessionAccountPins.sessionId, input.sessionId),
|
|
1722
|
+
eq(schema.xaiSessionAccountPins.authorityScope, snapshot.scope),
|
|
1723
|
+
ownerMembershipId === null
|
|
1724
|
+
? isNull(schema.xaiSessionAccountPins.ownerOrganizationMembershipId)
|
|
1725
|
+
: eq(schema.xaiSessionAccountPins.ownerOrganizationMembershipId, ownerMembershipId),
|
|
1726
|
+
),
|
|
1727
|
+
)
|
|
1728
|
+
.limit(1);
|
|
1729
|
+
return row ?? null;
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
export async function recordXaiSessionLastAccount(
|
|
1734
|
+
db: Database,
|
|
1735
|
+
input: {
|
|
1736
|
+
accountId: string;
|
|
1737
|
+
workspaceId: string;
|
|
1738
|
+
subjectId: string;
|
|
1739
|
+
sessionId: string;
|
|
1740
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1741
|
+
credentialId: string;
|
|
1742
|
+
},
|
|
1743
|
+
): Promise<typeof schema.xaiSessionAccountPins.$inferSelect> {
|
|
1744
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1745
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1746
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(scopedDb, {
|
|
1747
|
+
workspaceId: input.workspaceId,
|
|
1748
|
+
subjectId: input.subjectId,
|
|
1749
|
+
authoritySnapshot: snapshot,
|
|
1750
|
+
});
|
|
1751
|
+
await assertXaiCredentialInPool(scopedDb, {
|
|
1752
|
+
accountId: input.accountId,
|
|
1753
|
+
workspaceId: input.workspaceId,
|
|
1754
|
+
credentialId: input.credentialId,
|
|
1755
|
+
authorityScope: snapshot.scope,
|
|
1756
|
+
ownerMembershipId,
|
|
1757
|
+
});
|
|
1758
|
+
const [row] = await scopedDb
|
|
1759
|
+
.insert(schema.xaiSessionAccountPins)
|
|
1760
|
+
.values({
|
|
1761
|
+
accountId: input.accountId,
|
|
1762
|
+
workspaceId: input.workspaceId,
|
|
1763
|
+
sessionId: input.sessionId,
|
|
1764
|
+
authorityScope: snapshot.scope,
|
|
1765
|
+
ownerOrganizationMembershipId: ownerMembershipId,
|
|
1766
|
+
lastCredentialId: input.credentialId,
|
|
1767
|
+
})
|
|
1768
|
+
.onConflictDoUpdate({
|
|
1769
|
+
target: [
|
|
1770
|
+
schema.xaiSessionAccountPins.workspaceId,
|
|
1771
|
+
schema.xaiSessionAccountPins.sessionId,
|
|
1772
|
+
schema.xaiSessionAccountPins.authorityScope,
|
|
1773
|
+
schema.xaiSessionAccountPins.ownerOrganizationMembershipId,
|
|
1774
|
+
],
|
|
1775
|
+
set: {
|
|
1776
|
+
lastCredentialId: input.credentialId,
|
|
1777
|
+
version: sql`${schema.xaiSessionAccountPins.version} + 1`,
|
|
1778
|
+
updatedAt: new Date(),
|
|
1779
|
+
},
|
|
1780
|
+
})
|
|
1781
|
+
.returning();
|
|
1782
|
+
return row!;
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
export async function updateXaiQuotaMetadata(
|
|
1787
|
+
db: Database,
|
|
1788
|
+
input: {
|
|
1789
|
+
workspaceId: string;
|
|
1790
|
+
subjectId: string;
|
|
1791
|
+
credentialId: string;
|
|
1792
|
+
quotaUsedPercent: number | null;
|
|
1793
|
+
quotaResetAt: Date | null;
|
|
1794
|
+
quotaCheckedAt: Date;
|
|
1795
|
+
exhaustedUntil: Date | null;
|
|
1796
|
+
},
|
|
1797
|
+
): Promise<boolean> {
|
|
1798
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1799
|
+
const updated = await scopedDb
|
|
1800
|
+
.update(schema.xaiSubscriptionCredentials)
|
|
1801
|
+
.set({
|
|
1802
|
+
quotaUsedPercent: input.quotaUsedPercent,
|
|
1803
|
+
quotaResetAt: input.quotaResetAt,
|
|
1804
|
+
quotaCheckedAt: input.quotaCheckedAt,
|
|
1805
|
+
exhaustedUntil: input.exhaustedUntil,
|
|
1806
|
+
updatedAt: input.quotaCheckedAt,
|
|
1807
|
+
})
|
|
1808
|
+
.where(
|
|
1809
|
+
and(
|
|
1810
|
+
eq(schema.xaiSubscriptionCredentials.workspaceId, input.workspaceId),
|
|
1811
|
+
eq(schema.xaiSubscriptionCredentials.id, input.credentialId),
|
|
1812
|
+
),
|
|
1813
|
+
)
|
|
1814
|
+
.returning({ id: schema.xaiSubscriptionCredentials.id });
|
|
1815
|
+
return updated.length === 1;
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
export async function wakeXaiCapacityWaiters(
|
|
1820
|
+
db: Database,
|
|
1821
|
+
input: {
|
|
1822
|
+
workspaceId: string;
|
|
1823
|
+
subjectId: string;
|
|
1824
|
+
authoritySnapshot: XaiAuthoritySnapshot;
|
|
1825
|
+
reason: string;
|
|
1826
|
+
now?: Date;
|
|
1827
|
+
},
|
|
1828
|
+
): Promise<number> {
|
|
1829
|
+
const snapshot = XaiProviderAccountAuthoritySnapshotV1.parse(input.authoritySnapshot);
|
|
1830
|
+
const now = input.now ?? new Date();
|
|
1831
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
1832
|
+
const ownerMembershipId = await resolveXaiPoolOwnerMembershipId(scopedDb, {
|
|
1833
|
+
workspaceId: input.workspaceId,
|
|
1834
|
+
subjectId: input.subjectId,
|
|
1835
|
+
authoritySnapshot: snapshot,
|
|
1836
|
+
});
|
|
1837
|
+
const rows = await scopedDb
|
|
1838
|
+
.update(schema.xaiCapacityWaiters)
|
|
1839
|
+
.set({
|
|
1840
|
+
wakeRevision: sql`${schema.xaiCapacityWaiters.wakeRevision} + 1`,
|
|
1841
|
+
lastWakeReason: input.reason,
|
|
1842
|
+
nextCheckAt: now,
|
|
1843
|
+
updatedAt: now,
|
|
1844
|
+
})
|
|
1845
|
+
.where(
|
|
1846
|
+
and(
|
|
1847
|
+
eq(schema.xaiCapacityWaiters.workspaceId, input.workspaceId),
|
|
1848
|
+
eq(schema.xaiCapacityWaiters.status, "waiting"),
|
|
1849
|
+
eq(schema.xaiCapacityWaiters.authorityScope, snapshot.scope),
|
|
1850
|
+
ownerMembershipId === null
|
|
1851
|
+
? isNull(schema.xaiCapacityWaiters.ownerOrganizationMembershipId)
|
|
1852
|
+
: eq(schema.xaiCapacityWaiters.ownerOrganizationMembershipId, ownerMembershipId),
|
|
1853
|
+
),
|
|
1854
|
+
)
|
|
1855
|
+
.returning({
|
|
1856
|
+
id: schema.xaiCapacityWaiters.id,
|
|
1857
|
+
accountId: schema.xaiCapacityWaiters.accountId,
|
|
1858
|
+
sessionId: schema.xaiCapacityWaiters.sessionId,
|
|
1859
|
+
workflowId: schema.xaiCapacityWaiters.workflowId,
|
|
1860
|
+
});
|
|
1861
|
+
for (const row of rows) {
|
|
1862
|
+
await scopedDb
|
|
1863
|
+
.insert(schema.sessionWorkflowWakeOutbox)
|
|
1864
|
+
.values({
|
|
1865
|
+
accountId: row.accountId,
|
|
1866
|
+
workspaceId: input.workspaceId,
|
|
1867
|
+
sessionId: row.sessionId,
|
|
1868
|
+
temporalWorkflowId: row.workflowId,
|
|
1869
|
+
reason: "xai_capacity",
|
|
1870
|
+
nextAttemptAt: now,
|
|
1871
|
+
})
|
|
1872
|
+
.onConflictDoUpdate({
|
|
1873
|
+
target: schema.sessionWorkflowWakeOutbox.sessionId,
|
|
1874
|
+
set: {
|
|
1875
|
+
temporalWorkflowId: row.workflowId,
|
|
1876
|
+
wakeRevision: sql`${schema.sessionWorkflowWakeOutbox.wakeRevision} + 1`,
|
|
1877
|
+
reason: "xai_capacity",
|
|
1878
|
+
attempts: 0,
|
|
1879
|
+
nextAttemptAt: sql`least(${schema.sessionWorkflowWakeOutbox.nextAttemptAt}, ${now.toISOString()}::timestamptz)`,
|
|
1880
|
+
lastError: null,
|
|
1881
|
+
updatedAt: now,
|
|
1882
|
+
},
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
return rows.length;
|
|
1886
|
+
});
|
|
1887
|
+
}
|