@opengeni/db 0.23.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3UCHDMKG.js → chunk-7WTDI7Y3.js} +109 -2
- package/dist/chunk-7WTDI7Y3.js.map +1 -0
- package/dist/{chunk-L6ADMZHE.js → chunk-KW6U54V2.js} +3 -1
- package/dist/chunk-KW6U54V2.js.map +1 -0
- package/dist/connection-token-resolver.d.ts +24 -2
- package/dist/index.d.ts +68 -2
- package/dist/index.js +1168 -336
- package/dist/index.js.map +1 -1
- package/dist/preference-registry.d.ts +14 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +2 -2
- package/dist/schema.d.ts +262 -4
- package/dist/schema.js +3 -1
- package/dist/session-realtime-mirror.d.ts +22 -1
- package/dist/workspace-instruction-policies-schema.d.ts +68 -0
- package/dist/workspace-instruction-policies.d.ts +53 -7
- package/drizzle/0165_document_authority_foundation.sql +259 -0
- package/drizzle/0166_connection_disconnect_idempotency.sql +49 -0
- package/drizzle/0167_document_index_replay_authority.sql +61 -0
- package/drizzle/0168_workspace_instruction_policy_operation_receipts.sql +44 -0
- package/package.json +3 -3
- package/src/connection-token-resolver.ts +79 -16
- package/src/index.ts +419 -23
- package/src/preference-registry.ts +103 -0
- package/src/runtime-posture.ts +2 -0
- package/src/schema.ts +86 -0
- package/src/session-control.ts +48 -1
- package/src/session-queue-commands.ts +45 -11
- package/src/session-realtime-mirror.ts +117 -1
- package/src/session-realtime.ts +49 -1
- package/src/workspace-instruction-policies-schema.ts +30 -0
- package/src/workspace-instruction-policies.ts +438 -24
- package/dist/chunk-3UCHDMKG.js.map +0 -1
- package/dist/chunk-L6ADMZHE.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ConnectionKind,
|
|
15
15
|
ConnectionMetadata,
|
|
16
16
|
ConnectionStatus,
|
|
17
|
+
DocumentAuthorityKind,
|
|
17
18
|
McpServerConnectionRef,
|
|
18
19
|
FileAsset,
|
|
19
20
|
FileStatus,
|
|
@@ -111,6 +112,7 @@ import {
|
|
|
111
112
|
decodeNativeSnapshotRef,
|
|
112
113
|
parseWorkspaceArchiveDescriptor,
|
|
113
114
|
stableJson,
|
|
115
|
+
MODEL_ATTACHMENT_REFS_FIELD,
|
|
114
116
|
} from "@opengeni/contracts";
|
|
115
117
|
|
|
116
118
|
import {
|
|
@@ -232,6 +234,11 @@ import {
|
|
|
232
234
|
projectSessionRealtimeDelegationProgressInTransaction,
|
|
233
235
|
projectSessionRealtimeDelegationTerminalInTransaction,
|
|
234
236
|
} from "./session-realtime-ledger";
|
|
237
|
+
import {
|
|
238
|
+
mirrorSessionRealtimeContextInTransaction,
|
|
239
|
+
renderRealtimeHumanInputRequestContext,
|
|
240
|
+
renderRealtimeHumanInputResponseContext,
|
|
241
|
+
} from "./session-realtime-mirror";
|
|
235
242
|
import {
|
|
236
243
|
listSessionRealtimeContinuityEntriesInTransaction,
|
|
237
244
|
type SessionRealtimeContinuityEntry,
|
|
@@ -1197,6 +1204,55 @@ export async function withWorkspaceUsageLock<T>(
|
|
|
1197
1204
|
});
|
|
1198
1205
|
}
|
|
1199
1206
|
|
|
1207
|
+
export type DocumentIndexAuthority = {
|
|
1208
|
+
authorityKind: DocumentAuthorityKind;
|
|
1209
|
+
authorityWorkspaceId: string | null;
|
|
1210
|
+
authoritySubjectId: string | null;
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Read the immutable authority tuple used by document indexing compatibility.
|
|
1215
|
+
* The database capability returns no document content and independently
|
|
1216
|
+
* verifies that this transaction already carries the exact account/workspace
|
|
1217
|
+
* RLS context. It is intentionally subject-free so a historical three-field
|
|
1218
|
+
* Temporal payload can recover a personal document's frozen subject without
|
|
1219
|
+
* weakening ordinary document visibility.
|
|
1220
|
+
*/
|
|
1221
|
+
export async function resolveDocumentIndexAuthority(
|
|
1222
|
+
db: Database,
|
|
1223
|
+
input: { accountId: string; workspaceId: string; documentId: string },
|
|
1224
|
+
): Promise<DocumentIndexAuthority | null> {
|
|
1225
|
+
const rows = await rawRows<{
|
|
1226
|
+
authority_kind: string;
|
|
1227
|
+
authority_workspace_id: string | null;
|
|
1228
|
+
authority_subject_id: string | null;
|
|
1229
|
+
}>(
|
|
1230
|
+
db,
|
|
1231
|
+
sql`
|
|
1232
|
+
select authority_kind, authority_workspace_id, authority_subject_id
|
|
1233
|
+
from opengeni_private.resolve_document_index_authority(
|
|
1234
|
+
${input.accountId},
|
|
1235
|
+
${input.workspaceId},
|
|
1236
|
+
${input.documentId}
|
|
1237
|
+
)
|
|
1238
|
+
`,
|
|
1239
|
+
);
|
|
1240
|
+
const row = rows[0];
|
|
1241
|
+
if (!row) return null;
|
|
1242
|
+
if (
|
|
1243
|
+
row.authority_kind !== "organization" &&
|
|
1244
|
+
row.authority_kind !== "workspace" &&
|
|
1245
|
+
row.authority_kind !== "personal"
|
|
1246
|
+
) {
|
|
1247
|
+
throw new Error(`Stored document authority kind is invalid: ${row.authority_kind}`);
|
|
1248
|
+
}
|
|
1249
|
+
return {
|
|
1250
|
+
authorityKind: row.authority_kind,
|
|
1251
|
+
authorityWorkspaceId: row.authority_workspace_id,
|
|
1252
|
+
authoritySubjectId: row.authority_subject_id,
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1200
1256
|
export async function withAccountRls<T>(
|
|
1201
1257
|
db: Database,
|
|
1202
1258
|
accountId: string,
|
|
@@ -3498,6 +3554,47 @@ export type UpdateConnectionInput = {
|
|
|
3498
3554
|
updatedBySubjectId?: string | null;
|
|
3499
3555
|
};
|
|
3500
3556
|
|
|
3557
|
+
export type TransitionConnectionStateInput = {
|
|
3558
|
+
workspaceId: string;
|
|
3559
|
+
connectionId: string;
|
|
3560
|
+
visibleToSubjectId?: string | null;
|
|
3561
|
+
expectedVersion: number;
|
|
3562
|
+
status?: ConnectionStatus;
|
|
3563
|
+
metadata: Record<string, unknown>;
|
|
3564
|
+
lastError?: string | null;
|
|
3565
|
+
updatedBySubjectId?: string | null;
|
|
3566
|
+
};
|
|
3567
|
+
|
|
3568
|
+
export type DisconnectConnectionIdempotentlyInput = {
|
|
3569
|
+
accountId: string;
|
|
3570
|
+
workspaceId: string;
|
|
3571
|
+
subjectId: string;
|
|
3572
|
+
connectionId: string;
|
|
3573
|
+
expectedVersion: number;
|
|
3574
|
+
idempotencyKey: string;
|
|
3575
|
+
metadata: Record<string, unknown>;
|
|
3576
|
+
lastError?: string | null;
|
|
3577
|
+
updatedBySubjectId?: string | null;
|
|
3578
|
+
};
|
|
3579
|
+
|
|
3580
|
+
export class ConnectionDisconnectIdempotencyError extends Error {
|
|
3581
|
+
readonly code = "IDEMPOTENCY_KEY_REUSED";
|
|
3582
|
+
|
|
3583
|
+
constructor() {
|
|
3584
|
+
super("The disconnect idempotency key was already used with different input");
|
|
3585
|
+
this.name = "ConnectionDisconnectIdempotencyError";
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
export class ConnectionDisconnectGenerationError extends Error {
|
|
3590
|
+
readonly code = "CONNECTION_GENERATION_CHANGED";
|
|
3591
|
+
|
|
3592
|
+
constructor() {
|
|
3593
|
+
super("The disconnect operation belongs to an older connection generation");
|
|
3594
|
+
this.name = "ConnectionDisconnectGenerationError";
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3501
3598
|
/** Server-owned verification facts; public schemas expose them read-only and nullable. */
|
|
3502
3599
|
export type ConnectionMetadataWithVerification = ConnectionMetadata & {
|
|
3503
3600
|
verifiedInstallAt: string | null;
|
|
@@ -3880,6 +3977,39 @@ export async function requireFile(
|
|
|
3880
3977
|
return file;
|
|
3881
3978
|
}
|
|
3882
3979
|
|
|
3980
|
+
/** One RLS-scoped query for all attachment metadata needed by a model turn. */
|
|
3981
|
+
export async function getFiles(
|
|
3982
|
+
db: Database,
|
|
3983
|
+
workspaceId: string,
|
|
3984
|
+
fileIds: readonly string[],
|
|
3985
|
+
): Promise<FileAsset[]> {
|
|
3986
|
+
const ids = [...new Set(fileIds)];
|
|
3987
|
+
if (ids.length === 0) return [];
|
|
3988
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
3989
|
+
const rows = await scopedDb
|
|
3990
|
+
.select()
|
|
3991
|
+
.from(schema.files)
|
|
3992
|
+
.where(and(eq(schema.files.workspaceId, workspaceId), inArray(schema.files.id, ids)));
|
|
3993
|
+
return rows.map(mapFile);
|
|
3994
|
+
});
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
/** Build the canonical user row with stable attachment refs, never inline bytes. */
|
|
3998
|
+
export function durableUserHistoryItem(
|
|
3999
|
+
prompt: string,
|
|
4000
|
+
resources: readonly ResourceRef[],
|
|
4001
|
+
): Record<string, unknown> {
|
|
4002
|
+
const attachmentRefs = resources.filter(
|
|
4003
|
+
(resource): resource is Extract<ResourceRef, { kind: "file" }> => resource.kind === "file",
|
|
4004
|
+
);
|
|
4005
|
+
return sanitizeModelPayload({
|
|
4006
|
+
type: "message",
|
|
4007
|
+
role: "user",
|
|
4008
|
+
content: prompt,
|
|
4009
|
+
...(attachmentRefs.length > 0 ? { [MODEL_ATTACHMENT_REFS_FIELD]: attachmentRefs } : {}),
|
|
4010
|
+
});
|
|
4011
|
+
}
|
|
4012
|
+
|
|
3883
4013
|
export type RetainedFileArtifact = {
|
|
3884
4014
|
file: FileAsset;
|
|
3885
4015
|
uploadStatus: FileUploadStatus | null;
|
|
@@ -5327,6 +5457,191 @@ export async function updateConnection(
|
|
|
5327
5457
|
);
|
|
5328
5458
|
}
|
|
5329
5459
|
|
|
5460
|
+
async function transitionConnectionStateInScope(
|
|
5461
|
+
db: Database,
|
|
5462
|
+
input: TransitionConnectionStateInput,
|
|
5463
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
5464
|
+
const [row] = await db
|
|
5465
|
+
.update(schema.connections)
|
|
5466
|
+
.set({
|
|
5467
|
+
...(input.status !== undefined ? { status: input.status } : {}),
|
|
5468
|
+
metadata: input.metadata,
|
|
5469
|
+
...(input.lastError !== undefined ? { lastError: input.lastError } : {}),
|
|
5470
|
+
version: sql`${schema.connections.version} + 1`,
|
|
5471
|
+
...(input.updatedBySubjectId !== undefined
|
|
5472
|
+
? { updatedBySubjectId: input.updatedBySubjectId }
|
|
5473
|
+
: {}),
|
|
5474
|
+
updatedAt: new Date(),
|
|
5475
|
+
})
|
|
5476
|
+
.where(
|
|
5477
|
+
and(
|
|
5478
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
5479
|
+
eq(schema.connections.id, input.connectionId),
|
|
5480
|
+
connectionSubjectVisibility(input.visibleToSubjectId),
|
|
5481
|
+
eq(schema.connections.version, input.expectedVersion),
|
|
5482
|
+
),
|
|
5483
|
+
)
|
|
5484
|
+
.returning(connectionMetadataColumns);
|
|
5485
|
+
return row ? mapConnectionMetadata(row) : null;
|
|
5486
|
+
}
|
|
5487
|
+
|
|
5488
|
+
/**
|
|
5489
|
+
* Applies a metadata/status lifecycle transition behind the connection's
|
|
5490
|
+
* existing `(id, version)` fence. Unlike ordinary metadata edits, every
|
|
5491
|
+
* transition advances the version so concurrent refresh, disconnect,
|
|
5492
|
+
* reconnect, source-selection, and lifecycle operations cannot overwrite one
|
|
5493
|
+
* another from stale snapshots.
|
|
5494
|
+
*/
|
|
5495
|
+
export async function transitionConnectionState(
|
|
5496
|
+
db: Database,
|
|
5497
|
+
input: TransitionConnectionStateInput,
|
|
5498
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
5499
|
+
return await withConnectionSubjectRls(
|
|
5500
|
+
db,
|
|
5501
|
+
input.workspaceId,
|
|
5502
|
+
input.visibleToSubjectId,
|
|
5503
|
+
async (scopedDb) => await transitionConnectionStateInScope(scopedDb, input),
|
|
5504
|
+
);
|
|
5505
|
+
}
|
|
5506
|
+
|
|
5507
|
+
const connectionDisconnectOperationColumns = {
|
|
5508
|
+
accountId: schema.connectionDisconnectOperations.accountId,
|
|
5509
|
+
workspaceId: schema.connectionDisconnectOperations.workspaceId,
|
|
5510
|
+
connectionId: schema.connectionDisconnectOperations.connectionId,
|
|
5511
|
+
subjectId: schema.connectionDisconnectOperations.subjectId,
|
|
5512
|
+
idempotencyKey: schema.connectionDisconnectOperations.idempotencyKey,
|
|
5513
|
+
expectedVersion: schema.connectionDisconnectOperations.expectedVersion,
|
|
5514
|
+
resultVersion: schema.connectionDisconnectOperations.resultVersion,
|
|
5515
|
+
};
|
|
5516
|
+
|
|
5517
|
+
/**
|
|
5518
|
+
* Disconnects one exact subject-owned connection generation and records the
|
|
5519
|
+
* caller's durable operation identity in the same transaction. Exact retries
|
|
5520
|
+
* converge only while the produced generation remains current; a reconnect or
|
|
5521
|
+
* any other later transition fences the old operation permanently.
|
|
5522
|
+
*/
|
|
5523
|
+
export async function disconnectConnectionIdempotently(
|
|
5524
|
+
db: Database,
|
|
5525
|
+
input: DisconnectConnectionIdempotentlyInput,
|
|
5526
|
+
): Promise<ConnectionMetadataWithVerification | null> {
|
|
5527
|
+
return await withConnectionSubjectRls(
|
|
5528
|
+
db,
|
|
5529
|
+
input.workspaceId,
|
|
5530
|
+
input.subjectId,
|
|
5531
|
+
async (scopedDb) => {
|
|
5532
|
+
const [connectionRow] = await scopedDb
|
|
5533
|
+
.select(connectionMetadataColumns)
|
|
5534
|
+
.from(schema.connections)
|
|
5535
|
+
.where(
|
|
5536
|
+
and(
|
|
5537
|
+
eq(schema.connections.accountId, input.accountId),
|
|
5538
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
5539
|
+
eq(schema.connections.id, input.connectionId),
|
|
5540
|
+
eq(schema.connections.subjectId, input.subjectId),
|
|
5541
|
+
),
|
|
5542
|
+
)
|
|
5543
|
+
.for("update");
|
|
5544
|
+
if (!connectionRow) return null;
|
|
5545
|
+
const connection = mapConnectionMetadata(connectionRow);
|
|
5546
|
+
|
|
5547
|
+
const [replayed] = await scopedDb
|
|
5548
|
+
.select(connectionDisconnectOperationColumns)
|
|
5549
|
+
.from(schema.connectionDisconnectOperations)
|
|
5550
|
+
.where(
|
|
5551
|
+
and(
|
|
5552
|
+
eq(schema.connectionDisconnectOperations.workspaceId, input.workspaceId),
|
|
5553
|
+
eq(schema.connectionDisconnectOperations.subjectId, input.subjectId),
|
|
5554
|
+
eq(schema.connectionDisconnectOperations.idempotencyKey, input.idempotencyKey),
|
|
5555
|
+
),
|
|
5556
|
+
)
|
|
5557
|
+
.limit(1);
|
|
5558
|
+
if (replayed) {
|
|
5559
|
+
if (
|
|
5560
|
+
replayed.accountId !== input.accountId ||
|
|
5561
|
+
replayed.connectionId !== input.connectionId ||
|
|
5562
|
+
replayed.expectedVersion !== input.expectedVersion
|
|
5563
|
+
) {
|
|
5564
|
+
throw new ConnectionDisconnectIdempotencyError();
|
|
5565
|
+
}
|
|
5566
|
+
if (connection.version !== replayed.resultVersion) {
|
|
5567
|
+
throw new ConnectionDisconnectGenerationError();
|
|
5568
|
+
}
|
|
5569
|
+
return connection;
|
|
5570
|
+
}
|
|
5571
|
+
|
|
5572
|
+
if (connection.version !== input.expectedVersion) {
|
|
5573
|
+
throw new ConnectionDisconnectGenerationError();
|
|
5574
|
+
}
|
|
5575
|
+
|
|
5576
|
+
const [inserted] = await scopedDb
|
|
5577
|
+
.insert(schema.connectionDisconnectOperations)
|
|
5578
|
+
.values({
|
|
5579
|
+
accountId: input.accountId,
|
|
5580
|
+
workspaceId: input.workspaceId,
|
|
5581
|
+
connectionId: input.connectionId,
|
|
5582
|
+
subjectId: input.subjectId,
|
|
5583
|
+
idempotencyKey: input.idempotencyKey,
|
|
5584
|
+
expectedVersion: input.expectedVersion,
|
|
5585
|
+
resultVersion: input.expectedVersion + 1,
|
|
5586
|
+
})
|
|
5587
|
+
.onConflictDoNothing()
|
|
5588
|
+
.returning(connectionDisconnectOperationColumns);
|
|
5589
|
+
if (!inserted) {
|
|
5590
|
+
const [conflictingKey] = await scopedDb
|
|
5591
|
+
.select(connectionDisconnectOperationColumns)
|
|
5592
|
+
.from(schema.connectionDisconnectOperations)
|
|
5593
|
+
.where(
|
|
5594
|
+
and(
|
|
5595
|
+
eq(schema.connectionDisconnectOperations.workspaceId, input.workspaceId),
|
|
5596
|
+
eq(schema.connectionDisconnectOperations.subjectId, input.subjectId),
|
|
5597
|
+
eq(schema.connectionDisconnectOperations.idempotencyKey, input.idempotencyKey),
|
|
5598
|
+
),
|
|
5599
|
+
)
|
|
5600
|
+
.limit(1);
|
|
5601
|
+
if (
|
|
5602
|
+
!conflictingKey ||
|
|
5603
|
+
conflictingKey.accountId !== input.accountId ||
|
|
5604
|
+
conflictingKey.connectionId !== input.connectionId ||
|
|
5605
|
+
conflictingKey.expectedVersion !== input.expectedVersion
|
|
5606
|
+
) {
|
|
5607
|
+
throw new ConnectionDisconnectIdempotencyError();
|
|
5608
|
+
}
|
|
5609
|
+
if (connection.version !== conflictingKey.resultVersion) {
|
|
5610
|
+
throw new ConnectionDisconnectGenerationError();
|
|
5611
|
+
}
|
|
5612
|
+
return connection;
|
|
5613
|
+
}
|
|
5614
|
+
|
|
5615
|
+
const [updated] = await scopedDb
|
|
5616
|
+
.update(schema.connections)
|
|
5617
|
+
.set({
|
|
5618
|
+
status: "revoked",
|
|
5619
|
+
metadata: input.metadata,
|
|
5620
|
+
...(input.lastError !== undefined ? { lastError: input.lastError } : {}),
|
|
5621
|
+
version: sql`${schema.connections.version} + 1`,
|
|
5622
|
+
...(input.updatedBySubjectId !== undefined
|
|
5623
|
+
? { updatedBySubjectId: input.updatedBySubjectId }
|
|
5624
|
+
: {}),
|
|
5625
|
+
updatedAt: new Date(),
|
|
5626
|
+
})
|
|
5627
|
+
.where(
|
|
5628
|
+
and(
|
|
5629
|
+
eq(schema.connections.accountId, input.accountId),
|
|
5630
|
+
eq(schema.connections.workspaceId, input.workspaceId),
|
|
5631
|
+
eq(schema.connections.id, input.connectionId),
|
|
5632
|
+
eq(schema.connections.subjectId, input.subjectId),
|
|
5633
|
+
eq(schema.connections.version, input.expectedVersion),
|
|
5634
|
+
),
|
|
5635
|
+
)
|
|
5636
|
+
.returning(connectionMetadataColumns);
|
|
5637
|
+
if (!updated) {
|
|
5638
|
+
throw new ConnectionDisconnectGenerationError();
|
|
5639
|
+
}
|
|
5640
|
+
return mapConnectionMetadata(updated);
|
|
5641
|
+
},
|
|
5642
|
+
);
|
|
5643
|
+
}
|
|
5644
|
+
|
|
5330
5645
|
async function revokeConnectionInScope(
|
|
5331
5646
|
db: Database,
|
|
5332
5647
|
workspaceId: string,
|
|
@@ -5371,13 +5686,20 @@ export async function revokeConnection(
|
|
|
5371
5686
|
workspaceId: string,
|
|
5372
5687
|
connectionId: string,
|
|
5373
5688
|
updatedBySubjectId?: string | null,
|
|
5689
|
+
expectedVersion?: number,
|
|
5374
5690
|
): Promise<ConnectionMetadataWithVerification | null> {
|
|
5375
5691
|
return await withConnectionSubjectRls(
|
|
5376
5692
|
db,
|
|
5377
5693
|
workspaceId,
|
|
5378
5694
|
updatedBySubjectId,
|
|
5379
5695
|
async (scopedDb) =>
|
|
5380
|
-
await revokeConnectionInScope(
|
|
5696
|
+
await revokeConnectionInScope(
|
|
5697
|
+
scopedDb,
|
|
5698
|
+
workspaceId,
|
|
5699
|
+
connectionId,
|
|
5700
|
+
updatedBySubjectId,
|
|
5701
|
+
expectedVersion,
|
|
5702
|
+
),
|
|
5381
5703
|
);
|
|
5382
5704
|
}
|
|
5383
5705
|
|
|
@@ -20364,21 +20686,9 @@ function validateAnsweredHumanInput(
|
|
|
20364
20686
|
`Text question ${question.id} accepts one value`,
|
|
20365
20687
|
);
|
|
20366
20688
|
}
|
|
20367
|
-
|
|
20368
|
-
|
|
20369
|
-
|
|
20370
|
-
if (minLength != null && value.length < minLength) {
|
|
20371
|
-
throw new HumanInputResponseValidationError(
|
|
20372
|
-
"INVALID_RESPONSE",
|
|
20373
|
-
`Question ${question.id} is shorter than its minimum length`,
|
|
20374
|
-
);
|
|
20375
|
-
}
|
|
20376
|
-
if (maxLength != null && value.length > maxLength) {
|
|
20377
|
-
throw new HumanInputResponseValidationError(
|
|
20378
|
-
"INVALID_RESPONSE",
|
|
20379
|
-
`Question ${question.id} exceeds its maximum length`,
|
|
20380
|
-
);
|
|
20381
|
-
}
|
|
20689
|
+
// Text answers have no agent-chosen char bounds — only the platform
|
|
20690
|
+
// string cap on HumanInputAnswer.values. Ignore legacy minLength/maxLength
|
|
20691
|
+
// still present on older persisted question JSON.
|
|
20382
20692
|
continue;
|
|
20383
20693
|
}
|
|
20384
20694
|
if (other && !question.allowOther) {
|
|
@@ -20689,6 +20999,27 @@ export async function acceptSessionHumanInputResponse(
|
|
|
20689
20999
|
})
|
|
20690
21000
|
.returning();
|
|
20691
21001
|
if (!event) throw new Error("Failed to append human-input response");
|
|
21002
|
+
await mirrorSessionRealtimeContextInTransaction(tx as unknown as Database, {
|
|
21003
|
+
accountId: session.accountId,
|
|
21004
|
+
workspaceId: input.workspaceId,
|
|
21005
|
+
sessionId: input.sessionId,
|
|
21006
|
+
sourceKind: "human_input_response",
|
|
21007
|
+
sourceId: event.id,
|
|
21008
|
+
turnId: turn.id,
|
|
21009
|
+
channel:
|
|
21010
|
+
response.outcome === "answered" || response.outcome === "skipped" ? "speakable" : null,
|
|
21011
|
+
text: renderRealtimeHumanInputResponseContext({
|
|
21012
|
+
requestId: request.id,
|
|
21013
|
+
questions: request.questions,
|
|
21014
|
+
response,
|
|
21015
|
+
}),
|
|
21016
|
+
payload: {
|
|
21017
|
+
requestId: request.id,
|
|
21018
|
+
outcome: response.outcome,
|
|
21019
|
+
sourceEventId: event.id,
|
|
21020
|
+
},
|
|
21021
|
+
now,
|
|
21022
|
+
});
|
|
20692
21023
|
await tx
|
|
20693
21024
|
.update(schema.sessions)
|
|
20694
21025
|
.set({ lastSequence: session.lastSequence + 1, updatedAt: now })
|
|
@@ -38173,11 +38504,10 @@ export async function claimSessionWorkForAttempt(
|
|
|
38173
38504
|
sessionId,
|
|
38174
38505
|
turnId: row.id,
|
|
38175
38506
|
position: Number(historyPosition),
|
|
38176
|
-
item:
|
|
38177
|
-
|
|
38178
|
-
|
|
38179
|
-
|
|
38180
|
-
}),
|
|
38507
|
+
item: durableUserHistoryItem(
|
|
38508
|
+
row.prompt,
|
|
38509
|
+
Array.isArray(row.resources) ? (row.resources as ResourceRef[]) : [],
|
|
38510
|
+
),
|
|
38181
38511
|
producerCodexCredentialId: null,
|
|
38182
38512
|
});
|
|
38183
38513
|
const providerDelegatedTurn = isSessionRealtimeDelegationTurnMetadata(row.metadata);
|
|
@@ -39912,11 +40242,11 @@ export async function applySessionTurnSettlement(
|
|
|
39912
40242
|
};
|
|
39913
40243
|
}
|
|
39914
40244
|
|
|
40245
|
+
const humanInputRequests = input.runState?.humanInputRequests ?? [];
|
|
39915
40246
|
if (input.runState) {
|
|
39916
40247
|
if (input.turnStatus !== "requires_action" || input.sessionStatus !== "requires_action") {
|
|
39917
40248
|
throw new Error("A frozen run state requires a requires_action settlement");
|
|
39918
40249
|
}
|
|
39919
|
-
const humanInputRequests = input.runState.humanInputRequests ?? [];
|
|
39920
40250
|
for (const request of humanInputRequests) {
|
|
39921
40251
|
const parsedQuestions = request.questions.map((question) =>
|
|
39922
40252
|
HumanInputQuestionContract.parse(question),
|
|
@@ -40168,7 +40498,10 @@ export async function applySessionTurnSettlement(
|
|
|
40168
40498
|
eq(schema.sessionHumanInputRequests.status, "pending"),
|
|
40169
40499
|
),
|
|
40170
40500
|
)
|
|
40171
|
-
.returning({
|
|
40501
|
+
.returning({
|
|
40502
|
+
id: schema.sessionHumanInputRequests.id,
|
|
40503
|
+
questions: schema.sessionHumanInputRequests.questions,
|
|
40504
|
+
})
|
|
40172
40505
|
: [];
|
|
40173
40506
|
const terminalHumanInputEvents: AppendEventInput[] = terminalHumanInputRows.map(
|
|
40174
40507
|
(request) => ({
|
|
@@ -40251,6 +40584,69 @@ export async function applySessionTurnSettlement(
|
|
|
40251
40584
|
});
|
|
40252
40585
|
const inserted =
|
|
40253
40586
|
values.length > 0 ? await tx.insert(schema.sessionEvents).values(values).returning() : [];
|
|
40587
|
+
const requestedEvents = inserted.filter(
|
|
40588
|
+
(event) => event.type === "session.humanInput.requested",
|
|
40589
|
+
);
|
|
40590
|
+
if (requestedEvents.length > 0) {
|
|
40591
|
+
const requestedIds = new Set(
|
|
40592
|
+
requestedEvents.flatMap((event) => {
|
|
40593
|
+
const request = sessionEventPayloadRecord(event.payload).request;
|
|
40594
|
+
if (!request || typeof request !== "object" || Array.isArray(request)) return [];
|
|
40595
|
+
const id = (request as Record<string, unknown>).id;
|
|
40596
|
+
return typeof id === "string" ? [id] : [];
|
|
40597
|
+
}),
|
|
40598
|
+
);
|
|
40599
|
+
const requests = humanInputRequests.filter((request) => requestedIds.has(request.id));
|
|
40600
|
+
if (requests.length !== requestedIds.size) {
|
|
40601
|
+
throw new Error("Human-input realtime projection lost its durable request contract");
|
|
40602
|
+
}
|
|
40603
|
+
await mirrorSessionRealtimeContextInTransaction(tx as unknown as Database, {
|
|
40604
|
+
accountId: session.accountId,
|
|
40605
|
+
workspaceId,
|
|
40606
|
+
sessionId: input.sessionId,
|
|
40607
|
+
sourceKind: "human_input_request",
|
|
40608
|
+
sourceId: requestedEvents.map((event) => event.id).join(":"),
|
|
40609
|
+
turnId: input.turnId,
|
|
40610
|
+
channel: "speakable",
|
|
40611
|
+
text: renderRealtimeHumanInputRequestContext({ requests }),
|
|
40612
|
+
payload: {
|
|
40613
|
+
status: "waiting_for_user",
|
|
40614
|
+
requestIds: requests.map((request) => request.id),
|
|
40615
|
+
sourceEventIds: requestedEvents.map((event) => event.id),
|
|
40616
|
+
},
|
|
40617
|
+
now,
|
|
40618
|
+
});
|
|
40619
|
+
}
|
|
40620
|
+
const terminalHumanInputById = new Map(
|
|
40621
|
+
terminalHumanInputRows.map((request) => [request.id, request]),
|
|
40622
|
+
);
|
|
40623
|
+
for (const event of inserted) {
|
|
40624
|
+
if (event.type !== "user.humanInputResponse") continue;
|
|
40625
|
+
const payload = sessionEventPayloadRecord(event.payload);
|
|
40626
|
+
const requestId = typeof payload.requestId === "string" ? payload.requestId : null;
|
|
40627
|
+
const request = requestId ? terminalHumanInputById.get(requestId) : null;
|
|
40628
|
+
if (!request) continue;
|
|
40629
|
+
await mirrorSessionRealtimeContextInTransaction(tx as unknown as Database, {
|
|
40630
|
+
accountId: session.accountId,
|
|
40631
|
+
workspaceId,
|
|
40632
|
+
sessionId: input.sessionId,
|
|
40633
|
+
sourceKind: "human_input_response",
|
|
40634
|
+
sourceId: event.id,
|
|
40635
|
+
turnId: input.turnId,
|
|
40636
|
+
channel: null,
|
|
40637
|
+
text: renderRealtimeHumanInputResponseContext({
|
|
40638
|
+
requestId: request.id,
|
|
40639
|
+
questions: request.questions,
|
|
40640
|
+
response: { outcome: "cancelled" },
|
|
40641
|
+
}),
|
|
40642
|
+
payload: {
|
|
40643
|
+
requestId: request.id,
|
|
40644
|
+
outcome: "cancelled",
|
|
40645
|
+
sourceEventId: event.id,
|
|
40646
|
+
},
|
|
40647
|
+
now,
|
|
40648
|
+
});
|
|
40649
|
+
}
|
|
40254
40650
|
const terminal = isTerminalSessionTurnStatus(input.turnStatus);
|
|
40255
40651
|
if (isTerminalSessionTurnStatus(input.turnStatus)) {
|
|
40256
40652
|
const terminalType = terminalSessionTurnEventType(input.turnStatus);
|
|
@@ -558,6 +558,109 @@ export async function listPreferenceRegistry(
|
|
|
558
558
|
});
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
+
export type PreferenceRegistryGovernanceIdentity = Pick<
|
|
562
|
+
PreferenceRegistryDescriptor,
|
|
563
|
+
"id" | "revisionId" | "contentHash" | "activeVersion" | "scope"
|
|
564
|
+
>;
|
|
565
|
+
|
|
566
|
+
export type CurrentPreferenceRegistryGovernanceMetadata = {
|
|
567
|
+
descriptors: PreferenceRegistryGovernanceIdentity[];
|
|
568
|
+
truncated: boolean;
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Read the current active descriptor identities for one exact authenticated
|
|
573
|
+
* subject. Full preference values and even descriptor display text stay inside
|
|
574
|
+
* this DB boundary; Workspace State receives only stable revision metadata.
|
|
575
|
+
*/
|
|
576
|
+
export async function getCurrentPreferenceRegistryGovernanceMetadata(
|
|
577
|
+
db: Database,
|
|
578
|
+
input: { workspaceId: string; subjectId: string },
|
|
579
|
+
): Promise<CurrentPreferenceRegistryGovernanceMetadata> {
|
|
580
|
+
return await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (scopedDb) => {
|
|
581
|
+
const rows = await scopedDb
|
|
582
|
+
.select({
|
|
583
|
+
preference: schema.preferenceRegistryPreferences,
|
|
584
|
+
revision: schema.preferenceRegistryRevisions,
|
|
585
|
+
})
|
|
586
|
+
.from(schema.preferenceRegistryPreferences)
|
|
587
|
+
.innerJoin(
|
|
588
|
+
schema.preferenceRegistryRevisions,
|
|
589
|
+
and(
|
|
590
|
+
eq(
|
|
591
|
+
schema.preferenceRegistryRevisions.id,
|
|
592
|
+
schema.preferenceRegistryPreferences.activeRevisionId,
|
|
593
|
+
),
|
|
594
|
+
eq(
|
|
595
|
+
schema.preferenceRegistryRevisions.accountId,
|
|
596
|
+
schema.preferenceRegistryPreferences.accountId,
|
|
597
|
+
),
|
|
598
|
+
),
|
|
599
|
+
)
|
|
600
|
+
.where(
|
|
601
|
+
and(
|
|
602
|
+
eq(schema.preferenceRegistryPreferences.status, "active"),
|
|
603
|
+
or(
|
|
604
|
+
isNull(schema.preferenceRegistryRevisions.expiresAt),
|
|
605
|
+
gt(schema.preferenceRegistryRevisions.expiresAt, sql`transaction_timestamp()`),
|
|
606
|
+
),
|
|
607
|
+
),
|
|
608
|
+
)
|
|
609
|
+
.orderBy(
|
|
610
|
+
sql`case ${schema.preferenceRegistryPreferences.scope}
|
|
611
|
+
when 'organization' then 0
|
|
612
|
+
when 'workspace' then 1
|
|
613
|
+
when 'user' then 2
|
|
614
|
+
else 3
|
|
615
|
+
end`,
|
|
616
|
+
desc(schema.preferenceRegistryRevisions.precedenceRank),
|
|
617
|
+
asc(schema.preferenceRegistryPreferences.stableKey),
|
|
618
|
+
asc(schema.preferenceRegistryPreferences.id),
|
|
619
|
+
)
|
|
620
|
+
.limit(PREFERENCE_REGISTRY_DESCRIPTOR_MAX_COUNT + 1);
|
|
621
|
+
|
|
622
|
+
const bounded = boundPreferenceRegistryDescriptors(
|
|
623
|
+
rows.map(({ preference, revision }) =>
|
|
624
|
+
PreferenceRegistryDescriptor.parse({
|
|
625
|
+
id: preference.id,
|
|
626
|
+
stableKey: preference.stableKey,
|
|
627
|
+
title: revision.title,
|
|
628
|
+
description: revision.description,
|
|
629
|
+
scope: preference.scope,
|
|
630
|
+
activeVersion: preference.activationVersion,
|
|
631
|
+
revisionId: revision.id,
|
|
632
|
+
contentHash: revision.contentHash,
|
|
633
|
+
precedence: {
|
|
634
|
+
tier: preference.scope,
|
|
635
|
+
rank: revision.precedenceRank,
|
|
636
|
+
conflictStrategy: revision.conflictStrategy,
|
|
637
|
+
conflictsWith: revision.conflictsWith,
|
|
638
|
+
},
|
|
639
|
+
provenance: {
|
|
640
|
+
source: revision.provenanceSource,
|
|
641
|
+
sourceIdHash: revision.provenanceSourceId
|
|
642
|
+
? contentHash(revision.provenanceSourceId)
|
|
643
|
+
: null,
|
|
644
|
+
trust: revision.trust,
|
|
645
|
+
},
|
|
646
|
+
expiresAt: revision.expiresAt ? iso(revision.expiresAt) : null,
|
|
647
|
+
retrievalHandle: `preference://${preference.id}/revisions/${revision.id}?sha256=${revision.contentHash}`,
|
|
648
|
+
}),
|
|
649
|
+
),
|
|
650
|
+
);
|
|
651
|
+
return {
|
|
652
|
+
descriptors: bounded.descriptors.map((descriptor) => ({
|
|
653
|
+
id: descriptor.id,
|
|
654
|
+
revisionId: descriptor.revisionId,
|
|
655
|
+
contentHash: descriptor.contentHash,
|
|
656
|
+
activeVersion: descriptor.activeVersion,
|
|
657
|
+
scope: descriptor.scope,
|
|
658
|
+
})),
|
|
659
|
+
truncated: bounded.truncated,
|
|
660
|
+
};
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
561
664
|
export async function listPreferenceRegistryForAttempt(
|
|
562
665
|
db: Database,
|
|
563
666
|
input: PreferenceRegistryAttemptClaims & PreferenceRegistryListInput,
|
package/src/runtime-posture.ts
CHANGED
|
@@ -24,6 +24,7 @@ export const FORCE_RLS_TABLES = [
|
|
|
24
24
|
"codex_rotation_settings",
|
|
25
25
|
"codex_subscription_credentials",
|
|
26
26
|
"composer_drafts",
|
|
27
|
+
"connection_disconnect_operations",
|
|
27
28
|
"connections",
|
|
28
29
|
"connector_action_policies",
|
|
29
30
|
"connector_action_requests",
|
|
@@ -176,6 +177,7 @@ export const RUNTIME_FULL_DML_TABLES = [
|
|
|
176
177
|
"codex_rotation_settings",
|
|
177
178
|
"codex_subscription_credentials",
|
|
178
179
|
"composer_drafts",
|
|
180
|
+
"connection_disconnect_operations",
|
|
179
181
|
"connections",
|
|
180
182
|
"connector_action_policies",
|
|
181
183
|
"connector_action_requests",
|