@opengeni/db 0.27.11 → 0.28.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/{chunk-RPHPNVWW.js → chunk-DBS5HPEW.js} +9 -1
- package/dist/chunk-DBS5HPEW.js.map +1 -0
- package/dist/{chunk-JYQPJ6Y5.js → chunk-HT5T62CC.js} +2499 -2249
- package/dist/chunk-HT5T62CC.js.map +1 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +3896 -2887
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +2 -2
- package/dist/schema.d.ts +1 -0
- package/dist/schema.js +17 -1
- package/dist/transcription-recordings-schema.d.ts +1226 -0
- package/dist/transcription-recordings.d.ts +228 -0
- package/drizzle/0170_resumable_transcription_recordings.sql +440 -0
- package/drizzle/0175_resumable_transcription_provider_deadline.sql +24 -0
- package/package.json +3 -3
- package/src/index.ts +117 -79
- package/src/runtime-posture.ts +8 -0
- package/src/schema.ts +4 -4
- package/src/transcription-recordings-schema.ts +250 -0
- package/src/transcription-recordings.ts +1508 -0
- package/dist/chunk-JYQPJ6Y5.js.map +0 -1
- package/dist/chunk-RPHPNVWW.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.1",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@opengeni/codex": "^0.2.11",
|
|
54
|
-
"@opengeni/config": "^0.
|
|
55
|
-
"@opengeni/contracts": "^0.
|
|
54
|
+
"@opengeni/config": "^0.11.0",
|
|
55
|
+
"@opengeni/contracts": "^0.39.0",
|
|
56
56
|
"@opengeni/network": "^0.2.0",
|
|
57
57
|
"drizzle-orm": "^0.45.2",
|
|
58
58
|
"postgres": "^3.4.7"
|
package/src/index.ts
CHANGED
|
@@ -21967,7 +21967,6 @@ export async function applyContextCompaction(
|
|
|
21967
21967
|
expectedAttemptId: string;
|
|
21968
21968
|
replacementItems: Array<Record<string, unknown>>;
|
|
21969
21969
|
summaryItem: Record<string, unknown>;
|
|
21970
|
-
replacementInputTokens: number;
|
|
21971
21970
|
clearRequestedCompaction?: boolean;
|
|
21972
21971
|
eventPayload?: Record<string, unknown>;
|
|
21973
21972
|
},
|
|
@@ -22056,7 +22055,10 @@ export async function applyContextCompaction(
|
|
|
22056
22055
|
await tx
|
|
22057
22056
|
.update(schema.sessions)
|
|
22058
22057
|
.set({
|
|
22059
|
-
|
|
22058
|
+
// The active model input changed without an ordinary provider call.
|
|
22059
|
+
// Keep this field provider-only and let the first successful
|
|
22060
|
+
// post-compaction response install the next authoritative count.
|
|
22061
|
+
lastInputTokens: null,
|
|
22060
22062
|
...(input.clearRequestedCompaction ? { compactRequested: false } : {}),
|
|
22061
22063
|
...(insertedEvents.length > 0
|
|
22062
22064
|
? {
|
|
@@ -22279,8 +22281,9 @@ export async function nextSessionHistoryPosition(
|
|
|
22279
22281
|
}
|
|
22280
22282
|
|
|
22281
22283
|
/**
|
|
22282
|
-
*
|
|
22283
|
-
*
|
|
22284
|
+
* Replace the input-token count for the most recent authoritative terminal
|
|
22285
|
+
* response. Null means that response supplied no usable final-call count, so
|
|
22286
|
+
* the next turn must not reuse an older response's value for compaction.
|
|
22284
22287
|
*/
|
|
22285
22288
|
export async function setSessionLastInputTokensForTurnAttempt(
|
|
22286
22289
|
db: Database,
|
|
@@ -22290,7 +22293,7 @@ export async function setSessionLastInputTokensForTurnAttempt(
|
|
|
22290
22293
|
turnId: string;
|
|
22291
22294
|
expectedExecutionGeneration: number;
|
|
22292
22295
|
expectedAttemptId: string;
|
|
22293
|
-
lastInputTokens: number;
|
|
22296
|
+
lastInputTokens: number | null;
|
|
22294
22297
|
},
|
|
22295
22298
|
): Promise<boolean> {
|
|
22296
22299
|
return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
|
|
@@ -22348,8 +22351,8 @@ export class SessionContextBusyError extends Error {
|
|
|
22348
22351
|
* reserved for an approval that paused mid-turn, and the API forbids a
|
|
22349
22352
|
* clear while such an approval or active turn exists.
|
|
22350
22353
|
*
|
|
22351
|
-
* Also
|
|
22352
|
-
*
|
|
22354
|
+
* Also clears last_input_tokens because no provider has observed the new
|
|
22355
|
+
* context yet.
|
|
22353
22356
|
*
|
|
22354
22357
|
* Idempotent: a re-run supersedes the (now sole, already-marker) active row,
|
|
22355
22358
|
* inserts another marker at the next position. The post-condition (one active
|
|
@@ -22441,7 +22444,7 @@ export async function clearSessionContext(
|
|
|
22441
22444
|
|
|
22442
22445
|
await tx
|
|
22443
22446
|
.update(schema.sessions)
|
|
22444
|
-
.set({ lastInputTokens:
|
|
22447
|
+
.set({ lastInputTokens: null, updatedAt: new Date() })
|
|
22445
22448
|
.where(
|
|
22446
22449
|
and(
|
|
22447
22450
|
eq(schema.sessions.workspaceId, input.workspaceId),
|
|
@@ -29167,67 +29170,87 @@ async function verifyWorkspaceMutationSettlementForAuthority(
|
|
|
29167
29170
|
},
|
|
29168
29171
|
): Promise<void> {
|
|
29169
29172
|
const operation = normalizeWorkspaceMutationOperation(input.operation);
|
|
29170
|
-
const
|
|
29171
|
-
|
|
29173
|
+
const settleOnce = async (): Promise<SandboxWorkspaceMutationSettlementResult> =>
|
|
29174
|
+
await withRlsContext(
|
|
29175
|
+
db,
|
|
29176
|
+
{
|
|
29177
|
+
accountId: authorityInput.accountId,
|
|
29178
|
+
workspaceId: authorityInput.workspaceId,
|
|
29179
|
+
},
|
|
29180
|
+
async (scopedDb) =>
|
|
29181
|
+
await scopedDb.transaction(async (txRaw) => {
|
|
29182
|
+
const tx = txRaw as unknown as Database;
|
|
29183
|
+
// Admission and settlement must take the same canonical ownership
|
|
29184
|
+
// prefix before either touches the lease. The former order
|
|
29185
|
+
// (admission row -> authority -> lease) deadlocked a completed
|
|
29186
|
+
// parallel exec settlement against retained-process promotion
|
|
29187
|
+
// (admission row -> lease -> authority). PostgreSQL then rolled
|
|
29188
|
+
// back the losing admission settlement and permanently blocked
|
|
29189
|
+
// checkpoint capture.
|
|
29190
|
+
//
|
|
29191
|
+
// Preserve physical-settlement semantics when mutable authority
|
|
29192
|
+
// is stale: retain the typed fence, settle the exact immutable
|
|
29193
|
+
// admission below, commit, and only then reject its output.
|
|
29194
|
+
let authority: LockedWorkspaceMutationAuthority | null = null;
|
|
29195
|
+
let authorityFailure: SandboxWorkspaceMutationSettlementResult | null = null;
|
|
29196
|
+
try {
|
|
29197
|
+
authority = await lockWorkspaceMutationAuthorityTx(tx, authorityInput);
|
|
29198
|
+
} catch (error) {
|
|
29199
|
+
const failure = workspaceMutationAuthorityFailure(error);
|
|
29200
|
+
if (!failure) throw error;
|
|
29201
|
+
authorityFailure = failure;
|
|
29202
|
+
}
|
|
29203
|
+
|
|
29204
|
+
const actorKind = authorityInput.kind;
|
|
29205
|
+
const actorId =
|
|
29206
|
+
authorityInput.kind === "turn"
|
|
29207
|
+
? authorityInput.attemptId
|
|
29208
|
+
: authorityInput.kind === "direct"
|
|
29209
|
+
? authorityInput.requestId
|
|
29210
|
+
: authorityInput.processId;
|
|
29211
|
+
const admission = await selectExactAdmissionForUpdate(tx, {
|
|
29212
|
+
accountId: authorityInput.accountId,
|
|
29213
|
+
workspaceId: authorityInput.workspaceId,
|
|
29214
|
+
admissionId: input.admission.id,
|
|
29215
|
+
actorKind,
|
|
29216
|
+
actorId,
|
|
29217
|
+
sessionId: authorityInput.sessionId,
|
|
29218
|
+
admittedWorkspaceGeneration: input.admission.workspaceGeneration,
|
|
29219
|
+
operation,
|
|
29220
|
+
});
|
|
29221
|
+
if (
|
|
29222
|
+
!admission ||
|
|
29223
|
+
!admissionMatchesSnapshot(admission, input.admission) ||
|
|
29224
|
+
!admissionSnapshotMatchesAuthorityInput(input.admission, authorityInput) ||
|
|
29225
|
+
admission.provider_outcome === "retained" ||
|
|
29226
|
+
(admission.provider_outcome && admission.provider_outcome !== input.outcome)
|
|
29227
|
+
) {
|
|
29228
|
+
return {
|
|
29229
|
+
failure: "admission_fenced" as const,
|
|
29230
|
+
detail: "Workspace mutation settlement did not match its exact durable admission",
|
|
29231
|
+
};
|
|
29232
|
+
}
|
|
29233
|
+
if (!admission.settled_at) {
|
|
29234
|
+
await tx.execute(sql`
|
|
29235
|
+
update sandbox_workspace_mutation_admissions set
|
|
29236
|
+
provider_outcome = ${input.outcome}, settled_at = now()
|
|
29237
|
+
where id = ${input.admission.id} and settled_at is null
|
|
29238
|
+
`);
|
|
29239
|
+
}
|
|
29240
|
+
if (input.outcome === "rejected") return { failure: null };
|
|
29241
|
+
if (authorityFailure) return authorityFailure;
|
|
29242
|
+
if (!authority) {
|
|
29243
|
+
throw new Error("Workspace mutation settlement lost its locked authority");
|
|
29244
|
+
}
|
|
29245
|
+
return await verifyResolvedAdmissionAuthority(tx, authority, admission);
|
|
29246
|
+
}),
|
|
29247
|
+
);
|
|
29248
|
+
const settlement = await runIdempotentPersistenceTransaction(
|
|
29172
29249
|
{
|
|
29173
|
-
|
|
29174
|
-
|
|
29250
|
+
stage: "sandbox_workspace_mutation_settlement",
|
|
29251
|
+
maxAttempts: 5,
|
|
29175
29252
|
},
|
|
29176
|
-
|
|
29177
|
-
await scopedDb.transaction(async (txRaw) => {
|
|
29178
|
-
const tx = txRaw as unknown as Database;
|
|
29179
|
-
const actorKind = authorityInput.kind;
|
|
29180
|
-
const actorId =
|
|
29181
|
-
authorityInput.kind === "turn"
|
|
29182
|
-
? authorityInput.attemptId
|
|
29183
|
-
: authorityInput.kind === "direct"
|
|
29184
|
-
? authorityInput.requestId
|
|
29185
|
-
: authorityInput.processId;
|
|
29186
|
-
const admission = await selectExactAdmissionForUpdate(tx, {
|
|
29187
|
-
accountId: authorityInput.accountId,
|
|
29188
|
-
workspaceId: authorityInput.workspaceId,
|
|
29189
|
-
admissionId: input.admission.id,
|
|
29190
|
-
actorKind,
|
|
29191
|
-
actorId,
|
|
29192
|
-
sessionId: authorityInput.sessionId,
|
|
29193
|
-
admittedWorkspaceGeneration: input.admission.workspaceGeneration,
|
|
29194
|
-
operation,
|
|
29195
|
-
});
|
|
29196
|
-
if (
|
|
29197
|
-
!admission ||
|
|
29198
|
-
!admissionMatchesSnapshot(admission, input.admission) ||
|
|
29199
|
-
!admissionSnapshotMatchesAuthorityInput(input.admission, authorityInput) ||
|
|
29200
|
-
admission.provider_outcome === "retained" ||
|
|
29201
|
-
(admission.provider_outcome && admission.provider_outcome !== input.outcome)
|
|
29202
|
-
) {
|
|
29203
|
-
return {
|
|
29204
|
-
failure: "admission_fenced" as const,
|
|
29205
|
-
detail: "Workspace mutation settlement did not match its exact durable admission",
|
|
29206
|
-
};
|
|
29207
|
-
}
|
|
29208
|
-
if (!admission.settled_at) {
|
|
29209
|
-
await tx.execute(sql`
|
|
29210
|
-
update sandbox_workspace_mutation_admissions set
|
|
29211
|
-
provider_outcome = ${input.outcome}, settled_at = now()
|
|
29212
|
-
where id = ${input.admission.id} and settled_at is null
|
|
29213
|
-
`);
|
|
29214
|
-
}
|
|
29215
|
-
if (input.outcome === "rejected") return { failure: null };
|
|
29216
|
-
// The provider has already returned. Lock and settle its immutable
|
|
29217
|
-
// admission before consulting mutable turn/route/process authority, so
|
|
29218
|
-
// a stale-authority rejection cannot roll the physical settlement back
|
|
29219
|
-
// and strand archive capture. Only authority-fence errors are converted
|
|
29220
|
-
// to a post-commit rejection; database failures still abort normally.
|
|
29221
|
-
let authority: LockedWorkspaceMutationAuthority;
|
|
29222
|
-
try {
|
|
29223
|
-
authority = await lockWorkspaceMutationAuthorityTx(tx, authorityInput);
|
|
29224
|
-
} catch (error) {
|
|
29225
|
-
const failure = workspaceMutationAuthorityFailure(error);
|
|
29226
|
-
if (failure) return failure;
|
|
29227
|
-
throw error;
|
|
29228
|
-
}
|
|
29229
|
-
return await verifyResolvedAdmissionAuthority(tx, authority, admission);
|
|
29230
|
-
}),
|
|
29253
|
+
settleOnce,
|
|
29231
29254
|
);
|
|
29232
29255
|
if (settlement.failure !== null) {
|
|
29233
29256
|
throw new SandboxWorkspaceMutationFencedError(settlement.failure, settlement.detail);
|
|
@@ -29426,6 +29449,19 @@ export async function retainWorkspaceMutationProcess(
|
|
|
29426
29449
|
async (scopedDb) =>
|
|
29427
29450
|
await scopedDb.transaction(async (txRaw) => {
|
|
29428
29451
|
const tx = txRaw as unknown as Database;
|
|
29452
|
+
// Use the same ownership prefix as mutation admission and settlement.
|
|
29453
|
+
// A stale authority still cannot strand a provider process: remember
|
|
29454
|
+
// the fence, durably promote the exact process, then reject its output
|
|
29455
|
+
// after this transaction commits.
|
|
29456
|
+
let authority: LockedWorkspaceMutationAuthority | null = null;
|
|
29457
|
+
let authorityFailure: SandboxWorkspaceMutationSettlementResult | null = null;
|
|
29458
|
+
try {
|
|
29459
|
+
authority = await lockWorkspaceMutationAuthorityTx(tx, authorityInput);
|
|
29460
|
+
} catch (error) {
|
|
29461
|
+
const failure = workspaceMutationAuthorityFailure(error);
|
|
29462
|
+
if (!failure) throw error;
|
|
29463
|
+
authorityFailure = failure;
|
|
29464
|
+
}
|
|
29429
29465
|
const actorKind = authorityInput.kind;
|
|
29430
29466
|
const actorId =
|
|
29431
29467
|
authorityInput.kind === "turn" ? authorityInput.attemptId : authorityInput.requestId;
|
|
@@ -29586,16 +29622,13 @@ export async function retainWorkspaceMutationProcess(
|
|
|
29586
29622
|
}
|
|
29587
29623
|
|
|
29588
29624
|
// A yielded provider process is already a physical outcome. Persist its
|
|
29589
|
-
// exact route and non-TTL holder
|
|
29590
|
-
// a route/turn
|
|
29591
|
-
|
|
29592
|
-
|
|
29593
|
-
|
|
29594
|
-
|
|
29595
|
-
|
|
29596
|
-
const failure = workspaceMutationAuthorityFailure(error);
|
|
29597
|
-
if (failure) return { process: mapRetainedProcess(process!), failure };
|
|
29598
|
-
throw error;
|
|
29625
|
+
// exact route and non-TTL holder even when the earlier authority check
|
|
29626
|
+
// found a stale route/turn, then report staleness after commit.
|
|
29627
|
+
if (authorityFailure) {
|
|
29628
|
+
return { process: mapRetainedProcess(process!), failure: authorityFailure };
|
|
29629
|
+
}
|
|
29630
|
+
if (!authority) {
|
|
29631
|
+
throw new Error("Retained process promotion lost its locked authority");
|
|
29599
29632
|
}
|
|
29600
29633
|
const identity = await verifyResolvedAdmissionAuthority(tx, authority, admission);
|
|
29601
29634
|
return { process: mapRetainedProcess(process!), failure: identity };
|
|
@@ -39494,8 +39527,10 @@ export async function peekSessionWork(
|
|
|
39494
39527
|
attemptId: interruption.attemptId,
|
|
39495
39528
|
};
|
|
39496
39529
|
}
|
|
39497
|
-
|
|
39498
|
-
|
|
39530
|
+
// Physical quiescence finishes an already-accepted interruption; it is not
|
|
39531
|
+
// new session work. Reconcile the missing receipt even while control stays
|
|
39532
|
+
// paused, otherwise the pause itself can strand this session and every
|
|
39533
|
+
// ancestor behind a permanent `settlement: stopping` projection.
|
|
39499
39534
|
const awaitingQuiescence = await nextSessionAttemptAwaitingQuiescence(
|
|
39500
39535
|
scopedDb,
|
|
39501
39536
|
workspaceId,
|
|
@@ -39508,6 +39543,8 @@ export async function peekSessionWork(
|
|
|
39508
39543
|
};
|
|
39509
39544
|
}
|
|
39510
39545
|
|
|
39546
|
+
if (effectiveControl.state !== "active") return { kind: "idle" };
|
|
39547
|
+
|
|
39511
39548
|
const [capacityWait] = await scopedDb
|
|
39512
39549
|
.select()
|
|
39513
39550
|
.from(schema.codexCapacityWaiters)
|
|
@@ -45137,3 +45174,4 @@ export {
|
|
|
45137
45174
|
} from "./connection-token-resolver";
|
|
45138
45175
|
|
|
45139
45176
|
export * from "./workspace-artifacts";
|
|
45177
|
+
export * from "./transcription-recordings";
|
package/src/runtime-posture.ts
CHANGED
|
@@ -118,6 +118,10 @@ export const FORCE_RLS_TABLES = [
|
|
|
118
118
|
"slack_interactions",
|
|
119
119
|
"social_connections",
|
|
120
120
|
"social_posts",
|
|
121
|
+
"transcription_recording_chunks",
|
|
122
|
+
"transcription_recording_objects",
|
|
123
|
+
"transcription_recording_segments",
|
|
124
|
+
"transcription_recordings",
|
|
121
125
|
"usage_events",
|
|
122
126
|
"workspace_artifact_events",
|
|
123
127
|
"workspace_artifact_versions",
|
|
@@ -248,6 +252,10 @@ export const RUNTIME_FULL_DML_TABLES = [
|
|
|
248
252
|
"social_connections",
|
|
249
253
|
"social_posts",
|
|
250
254
|
"stripe_webhook_events",
|
|
255
|
+
"transcription_recording_chunks",
|
|
256
|
+
"transcription_recording_objects",
|
|
257
|
+
"transcription_recording_segments",
|
|
258
|
+
"transcription_recordings",
|
|
251
259
|
"usage_events",
|
|
252
260
|
"workspace_artifacts",
|
|
253
261
|
"workspace_captures",
|
package/src/schema.ts
CHANGED
|
@@ -1499,10 +1499,9 @@ export const sessions = pgTable(
|
|
|
1499
1499
|
nestedAgentDepthPolicySessionId: uuid("nested_agent_depth_policy_session_id"),
|
|
1500
1500
|
temporalWorkflowId: text("temporal_workflow_id"),
|
|
1501
1501
|
activeTurnId: uuid("active_turn_id"),
|
|
1502
|
-
// Actual input tokens reported for the
|
|
1503
|
-
//
|
|
1504
|
-
//
|
|
1505
|
-
// usage has completed.
|
|
1502
|
+
// Actual input tokens reported for the latest authoritative ordinary model
|
|
1503
|
+
// call. Compaction/context clearing invalidates it to null; local history
|
|
1504
|
+
// estimates must never be stored here.
|
|
1506
1505
|
lastInputTokens: integer("last_input_tokens"),
|
|
1507
1506
|
// Operator /compact request flag. The API sets
|
|
1508
1507
|
// it true; the worker honors it BEFORE the next turn's model call by forcing
|
|
@@ -6037,3 +6036,4 @@ export * from "./workspace-instruction-policies-schema";
|
|
|
6037
6036
|
export * from "./preference-registry-schema";
|
|
6038
6037
|
export * from "./memory-governance-schema";
|
|
6039
6038
|
export * from "./scoped-knowledge-schema";
|
|
6039
|
+
export * from "./transcription-recordings-schema";
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import {
|
|
3
|
+
boolean,
|
|
4
|
+
check,
|
|
5
|
+
index,
|
|
6
|
+
integer,
|
|
7
|
+
jsonb,
|
|
8
|
+
pgTable,
|
|
9
|
+
primaryKey,
|
|
10
|
+
text,
|
|
11
|
+
timestamp,
|
|
12
|
+
uniqueIndex,
|
|
13
|
+
uuid,
|
|
14
|
+
} from "drizzle-orm/pg-core";
|
|
15
|
+
|
|
16
|
+
export const transcriptionRecordingStateValues = [
|
|
17
|
+
"uploading",
|
|
18
|
+
"segmenting",
|
|
19
|
+
"ready",
|
|
20
|
+
"transcribing",
|
|
21
|
+
"complete",
|
|
22
|
+
"failed",
|
|
23
|
+
"discarded",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export const transcriptionRecordingChunkStateValues = ["uploading", "complete"] as const;
|
|
27
|
+
export const transcriptionRecordingObjectKindValues = ["chunk", "segment"] as const;
|
|
28
|
+
|
|
29
|
+
export const transcriptionRecordingSegmentStateValues = [
|
|
30
|
+
"preparing",
|
|
31
|
+
"pending",
|
|
32
|
+
"transcribing",
|
|
33
|
+
"complete",
|
|
34
|
+
"failed",
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export const transcriptionRecordings = pgTable(
|
|
38
|
+
"transcription_recordings",
|
|
39
|
+
{
|
|
40
|
+
id: uuid("id").primaryKey(),
|
|
41
|
+
accountId: uuid("account_id").notNull(),
|
|
42
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
43
|
+
subjectId: text("subject_id").notNull(),
|
|
44
|
+
mimeType: text("mime_type").notNull(),
|
|
45
|
+
state: text("state", { enum: transcriptionRecordingStateValues })
|
|
46
|
+
.notNull()
|
|
47
|
+
.default("uploading"),
|
|
48
|
+
nextChunkNumber: integer("next_chunk_number").notNull().default(0),
|
|
49
|
+
chunkCount: integer("chunk_count").notNull().default(0),
|
|
50
|
+
totalBytes: integer("total_bytes").notNull().default(0),
|
|
51
|
+
totalDurationMilliseconds: integer("total_duration_milliseconds").notNull().default(0),
|
|
52
|
+
segmentCount: integer("segment_count").notNull().default(0),
|
|
53
|
+
completedSegmentCount: integer("completed_segment_count").notNull().default(0),
|
|
54
|
+
transcriptText: text("transcript_text"),
|
|
55
|
+
languages: jsonb("languages").$type<string[]>().notNull().default([]),
|
|
56
|
+
errorCode: text("error_code"),
|
|
57
|
+
retryable: boolean("retryable").notNull().default(false),
|
|
58
|
+
providerId: text("provider_id"),
|
|
59
|
+
processingGeneration: integer("processing_generation").notNull().default(0),
|
|
60
|
+
processingOwner: uuid("processing_owner"),
|
|
61
|
+
processingStartedAt: timestamp("processing_started_at", { withTimezone: true }),
|
|
62
|
+
objectsCleanedAt: timestamp("objects_cleaned_at", { withTimezone: true }),
|
|
63
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
64
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
65
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
66
|
+
},
|
|
67
|
+
(table) => ({
|
|
68
|
+
exactAuthority: uniqueIndex("transcription_recordings_exact_authority_uq").on(
|
|
69
|
+
table.accountId,
|
|
70
|
+
table.workspaceId,
|
|
71
|
+
table.subjectId,
|
|
72
|
+
table.id,
|
|
73
|
+
),
|
|
74
|
+
subjectCreated: index("transcription_recordings_subject_created_idx").on(
|
|
75
|
+
table.workspaceId,
|
|
76
|
+
table.subjectId,
|
|
77
|
+
table.createdAt,
|
|
78
|
+
),
|
|
79
|
+
expiry: index("transcription_recordings_expiry_idx").on(table.expiresAt, table.id),
|
|
80
|
+
valuesValid: check(
|
|
81
|
+
"transcription_recordings_values_check",
|
|
82
|
+
sql`${table.nextChunkNumber} >= 0
|
|
83
|
+
and ${table.chunkCount} >= 0
|
|
84
|
+
and ${table.totalBytes} >= 0
|
|
85
|
+
and ${table.totalDurationMilliseconds} >= 0
|
|
86
|
+
and ${table.segmentCount} >= 0
|
|
87
|
+
and ${table.completedSegmentCount} >= 0
|
|
88
|
+
and ${table.completedSegmentCount} <= ${table.segmentCount}
|
|
89
|
+
and octet_length(${table.subjectId}) between 1 and 1024
|
|
90
|
+
and octet_length(${table.mimeType}) between 1 and 128
|
|
91
|
+
and (
|
|
92
|
+
${table.providerId} is null
|
|
93
|
+
or octet_length(${table.providerId}) between 1 and 128
|
|
94
|
+
)`,
|
|
95
|
+
),
|
|
96
|
+
processingValid: check(
|
|
97
|
+
"transcription_recordings_processing_check",
|
|
98
|
+
sql`(
|
|
99
|
+
${table.processingOwner} is null
|
|
100
|
+
and ${table.processingStartedAt} is null
|
|
101
|
+
) or (
|
|
102
|
+
${table.processingOwner} is not null
|
|
103
|
+
and ${table.processingStartedAt} is not null
|
|
104
|
+
)`,
|
|
105
|
+
),
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
export const transcriptionRecordingObjects = pgTable(
|
|
110
|
+
"transcription_recording_objects",
|
|
111
|
+
{
|
|
112
|
+
accountId: uuid("account_id").notNull(),
|
|
113
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
114
|
+
subjectId: text("subject_id").notNull(),
|
|
115
|
+
recordingId: uuid("recording_id").notNull(),
|
|
116
|
+
objectKey: text("object_key").primaryKey(),
|
|
117
|
+
kind: text("kind", { enum: transcriptionRecordingObjectKindValues }).notNull(),
|
|
118
|
+
cleanupAfter: timestamp("cleanup_after", { withTimezone: true }).notNull(),
|
|
119
|
+
cleanupClaimId: uuid("cleanup_claim_id"),
|
|
120
|
+
cleanupClaimedAt: timestamp("cleanup_claimed_at", { withTimezone: true }),
|
|
121
|
+
cleanedAt: timestamp("cleaned_at", { withTimezone: true }),
|
|
122
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
123
|
+
},
|
|
124
|
+
(table) => ({
|
|
125
|
+
dueCleanup: index("transcription_recording_objects_due_cleanup_idx")
|
|
126
|
+
.on(table.cleanupAfter, table.objectKey)
|
|
127
|
+
.where(sql`${table.cleanedAt} is null`),
|
|
128
|
+
claimRecovery: index("transcription_recording_objects_claim_recovery_idx")
|
|
129
|
+
.on(table.cleanupClaimedAt, table.objectKey)
|
|
130
|
+
.where(sql`${table.cleanedAt} is null and ${table.cleanupClaimId} is not null`),
|
|
131
|
+
valuesValid: check(
|
|
132
|
+
"transcription_recording_objects_values_check",
|
|
133
|
+
sql`octet_length(${table.objectKey}) between 1 and 1024
|
|
134
|
+
and (
|
|
135
|
+
(${table.cleanupClaimId} is null and ${table.cleanupClaimedAt} is null)
|
|
136
|
+
or (${table.cleanupClaimId} is not null and ${table.cleanupClaimedAt} is not null)
|
|
137
|
+
)
|
|
138
|
+
and (
|
|
139
|
+
${table.cleanedAt} is null
|
|
140
|
+
or (${table.cleanupClaimId} is null and ${table.cleanupClaimedAt} is null)
|
|
141
|
+
)`,
|
|
142
|
+
),
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
export const transcriptionRecordingChunks = pgTable(
|
|
147
|
+
"transcription_recording_chunks",
|
|
148
|
+
{
|
|
149
|
+
accountId: uuid("account_id").notNull(),
|
|
150
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
151
|
+
subjectId: text("subject_id").notNull(),
|
|
152
|
+
recordingId: uuid("recording_id").notNull(),
|
|
153
|
+
chunkNumber: integer("chunk_number").notNull(),
|
|
154
|
+
state: text("state", { enum: transcriptionRecordingChunkStateValues })
|
|
155
|
+
.notNull()
|
|
156
|
+
.default("uploading"),
|
|
157
|
+
byteLength: integer("byte_length").notNull(),
|
|
158
|
+
sha256: text("sha256").notNull(),
|
|
159
|
+
startMilliseconds: integer("start_milliseconds").notNull(),
|
|
160
|
+
durationMilliseconds: integer("duration_milliseconds").notNull(),
|
|
161
|
+
objectKey: text("object_key").notNull(),
|
|
162
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
163
|
+
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
164
|
+
},
|
|
165
|
+
(table) => ({
|
|
166
|
+
pk: primaryKey({
|
|
167
|
+
name: "transcription_recording_chunks_pk",
|
|
168
|
+
columns: [table.recordingId, table.chunkNumber],
|
|
169
|
+
}),
|
|
170
|
+
recordingOrder: index("transcription_recording_chunks_order_idx").on(
|
|
171
|
+
table.workspaceId,
|
|
172
|
+
table.recordingId,
|
|
173
|
+
table.chunkNumber,
|
|
174
|
+
),
|
|
175
|
+
objectKey: uniqueIndex("transcription_recording_chunks_object_key_uq").on(table.objectKey),
|
|
176
|
+
valuesValid: check(
|
|
177
|
+
"transcription_recording_chunks_values_check",
|
|
178
|
+
sql`${table.chunkNumber} >= 0
|
|
179
|
+
and ${table.byteLength} > 0
|
|
180
|
+
and ${table.startMilliseconds} >= 0
|
|
181
|
+
and ${table.durationMilliseconds} >= 0
|
|
182
|
+
and ${table.sha256} ~ '^[0-9a-f]{64}$'
|
|
183
|
+
and octet_length(${table.objectKey}) between 1 and 1024
|
|
184
|
+
and (
|
|
185
|
+
(${table.state} = 'uploading' and ${table.completedAt} is null)
|
|
186
|
+
or (${table.state} = 'complete' and ${table.completedAt} is not null)
|
|
187
|
+
)`,
|
|
188
|
+
),
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
export const transcriptionRecordingSegments = pgTable(
|
|
193
|
+
"transcription_recording_segments",
|
|
194
|
+
{
|
|
195
|
+
accountId: uuid("account_id").notNull(),
|
|
196
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
197
|
+
subjectId: text("subject_id").notNull(),
|
|
198
|
+
recordingId: uuid("recording_id").notNull(),
|
|
199
|
+
segmentNumber: integer("segment_number").notNull(),
|
|
200
|
+
generation: integer("generation").notNull(),
|
|
201
|
+
state: text("state", { enum: transcriptionRecordingSegmentStateValues })
|
|
202
|
+
.notNull()
|
|
203
|
+
.default("preparing"),
|
|
204
|
+
byteLength: integer("byte_length").notNull(),
|
|
205
|
+
sha256: text("sha256").notNull(),
|
|
206
|
+
startMilliseconds: integer("start_milliseconds").notNull(),
|
|
207
|
+
durationMilliseconds: integer("duration_milliseconds").notNull(),
|
|
208
|
+
objectKey: text("object_key").notNull(),
|
|
209
|
+
attemptId: uuid("attempt_id"),
|
|
210
|
+
attemptStartedAt: timestamp("attempt_started_at", { withTimezone: true }),
|
|
211
|
+
attemptDeadlineAt: timestamp("attempt_deadline_at", { withTimezone: true }),
|
|
212
|
+
transcriptText: text("transcript_text"),
|
|
213
|
+
languages: jsonb("languages").$type<string[]>().notNull().default([]),
|
|
214
|
+
providerId: text("provider_id"),
|
|
215
|
+
errorCode: text("error_code"),
|
|
216
|
+
retryable: boolean("retryable").notNull().default(false),
|
|
217
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
218
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
219
|
+
},
|
|
220
|
+
(table) => ({
|
|
221
|
+
pk: primaryKey({
|
|
222
|
+
name: "transcription_recording_segments_pk",
|
|
223
|
+
columns: [table.recordingId, table.segmentNumber],
|
|
224
|
+
}),
|
|
225
|
+
recordingOrder: index("transcription_recording_segments_order_idx").on(
|
|
226
|
+
table.workspaceId,
|
|
227
|
+
table.recordingId,
|
|
228
|
+
table.segmentNumber,
|
|
229
|
+
),
|
|
230
|
+
objectKey: uniqueIndex("transcription_recording_segments_object_key_uq").on(table.objectKey),
|
|
231
|
+
valuesValid: check(
|
|
232
|
+
"transcription_recording_segments_values_check",
|
|
233
|
+
sql`${table.segmentNumber} >= 0
|
|
234
|
+
and ${table.generation} > 0
|
|
235
|
+
and ${table.byteLength} > 0
|
|
236
|
+
and ${table.startMilliseconds} >= 0
|
|
237
|
+
and ${table.durationMilliseconds} > 0
|
|
238
|
+
and ${table.sha256} ~ '^[0-9a-f]{64}$'
|
|
239
|
+
and octet_length(${table.objectKey}) between 1 and 1024
|
|
240
|
+
and (
|
|
241
|
+
(${table.attemptId} is null
|
|
242
|
+
and ${table.attemptStartedAt} is null
|
|
243
|
+
and ${table.attemptDeadlineAt} is null)
|
|
244
|
+
or (${table.attemptId} is not null
|
|
245
|
+
and ${table.attemptStartedAt} is not null
|
|
246
|
+
and ${table.attemptDeadlineAt} is not null)
|
|
247
|
+
)`,
|
|
248
|
+
),
|
|
249
|
+
}),
|
|
250
|
+
);
|