@opengeni/db 4.2.2 → 4.3.0-canary.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/canonical-human-identities.js +3 -3
- package/dist/{chunk-6GAPAYZR.js → chunk-7JPLGBSA.js} +562 -86
- package/dist/chunk-7JPLGBSA.js.map +1 -0
- package/dist/{chunk-SNLIKELL.js → chunk-IHADTE7C.js} +2 -2
- package/dist/{chunk-7WX3R3YL.js → chunk-ILKJKDII.js} +7 -2
- package/dist/chunk-ILKJKDII.js.map +1 -0
- package/dist/{chunk-MGVOYVPS.js → chunk-NCSPNCQK.js} +3 -3
- package/dist/{chunk-B2KR3TEM.js → chunk-SGJDIF2D.js} +2 -2
- package/dist/{chunk-S4Y42VSX.js → chunk-Y5XFVJJM.js} +207 -3
- package/dist/chunk-Y5XFVJJM.js.map +1 -0
- package/dist/{chunk-FPTI4NP4.js → chunk-YGBOVXC7.js} +2 -2
- package/dist/{chunk-6Y7ZDVJ5.js → chunk-YGRT6VZO.js} +2 -2
- package/dist/{chunk-6IQ4X35Y.js → chunk-YVYBAWBU.js} +46 -1
- package/dist/chunk-YVYBAWBU.js.map +1 -0
- package/dist/connect-attempts.d.ts +60 -0
- package/dist/connection-setup-authority.d.ts +10 -0
- package/dist/connection-token-resolver.d.ts +11 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/external-identities.d.ts +16 -0
- package/dist/external-identity-links.d.ts +74 -0
- package/dist/external-link-work.d.ts +51 -0
- package/dist/host-mcp-binding-match.d.ts +5 -0
- package/dist/host-mcp-bindings.d.ts +73 -0
- package/dist/host-mcp-task-authority.d.ts +47 -0
- package/dist/host-selection-identity.d.ts +6 -0
- package/dist/index.d.ts +51 -35
- package/dist/index.js +15171 -13775
- package/dist/index.js.map +1 -1
- package/dist/live-session-attempt.d.ts +275 -0
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/memory-domain.d.ts +3 -3
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +2 -2
- package/dist/runtime-posture.d.ts +6 -6
- package/dist/scheduled-task-revision-authority.d.ts +11 -0
- package/dist/schema.d.ts +1279 -9
- package/dist/schema.js +11 -1
- package/dist/session-background-commands.js +4 -4
- package/dist/session-command-output.js +4 -4
- package/dist/session-event-slices.js +2 -2
- package/dist/session-queue-commands.d.ts +7 -0
- package/dist/session-tenancy.d.ts +2 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/workspace-authority.d.ts +4 -4
- package/dist/workspace-tool-defaults.js +4 -4
- package/drizzle/0437_organization_scoped_external_workspaces.sql +71 -0
- package/drizzle/0438_durable_connect_attempts.sql +95 -0
- package/drizzle/0439_external_identity_provisioning.sql +128 -0
- package/drizzle/0440_external_workspace_member_removal.sql +59 -0
- package/drizzle/0441_external_identity_membership_lifecycle.sql +163 -0
- package/drizzle/0442_external_owning_user_authority.sql +81 -0
- package/drizzle/0443_host_mcp_binding_registry.sql +93 -0
- package/drizzle/0444_host_mcp_delegations.sql +129 -0
- package/drizzle/0445_host_mcp_turn_authorities.sql +145 -0
- package/drizzle/0446_host_mcp_causal_continuation.sql +126 -0
- package/drizzle/0447_host_mcp_task_authorities.sql +264 -0
- package/drizzle/0448_host_mcp_child_authority.sql +115 -0
- package/drizzle/0449_external_identity_link_lifecycle.sql +107 -0
- package/drizzle/0450_external_identity_link_work.sql +179 -0
- package/drizzle/0451_external_link_preview_and_permission_ceiling.sql +82 -0
- package/drizzle/0452_external_link_scheduled_origin.sql +34 -0
- package/drizzle/0453_host_mcp_native_owner.sql +35 -0
- package/drizzle/0454_connect_origin_authority.sql +42 -0
- package/drizzle/0455_external_link_inventory_labels.sql +44 -0
- package/drizzle/0456_social_connection_versions.sql +14 -0
- package/drizzle/0457_canonical_session_scope_subject.sql +46 -0
- package/drizzle/0458_skill_review_wire_compatibility.sql +36 -0
- package/package.json +6 -6
- package/src/connect-attempts.ts +317 -0
- package/src/connection-authority.ts +9 -0
- package/src/connection-setup-authority.ts +35 -0
- package/src/connection-token-resolver.ts +177 -48
- package/src/external-identities.ts +70 -0
- package/src/external-identity-links.ts +364 -0
- package/src/external-link-work.ts +228 -0
- package/src/host-mcp-binding-match.ts +33 -0
- package/src/host-mcp-bindings.ts +741 -0
- package/src/host-mcp-task-authority.ts +383 -0
- package/src/host-selection-identity.ts +29 -0
- package/src/index.ts +398 -147
- package/src/live-session-attempt.ts +85 -0
- package/src/managed-human-provisioning.ts +6 -1
- package/src/memory-domain.ts +6 -11
- package/src/provision-roles.ts +3 -0
- package/src/runtime-posture.ts +47 -0
- package/src/scheduled-task-revision-authority.ts +30 -0
- package/src/schema.ts +228 -3
- package/src/session-control.ts +14 -0
- package/src/session-queue-commands.ts +11 -0
- package/src/session-tenancy.ts +7 -0
- package/src/workspace-authority.ts +5 -5
- package/dist/chunk-6GAPAYZR.js.map +0 -1
- package/dist/chunk-6IQ4X35Y.js.map +0 -1
- package/dist/chunk-7WX3R3YL.js.map +0 -1
- package/dist/chunk-S4Y42VSX.js.map +0 -1
- /package/dist/{chunk-SNLIKELL.js.map → chunk-IHADTE7C.js.map} +0 -0
- /package/dist/{chunk-MGVOYVPS.js.map → chunk-NCSPNCQK.js.map} +0 -0
- /package/dist/{chunk-B2KR3TEM.js.map → chunk-SGJDIF2D.js.map} +0 -0
- /package/dist/{chunk-FPTI4NP4.js.map → chunk-YGBOVXC7.js.map} +0 -0
- /package/dist/{chunk-6Y7ZDVJ5.js.map → chunk-YGRT6VZO.js.map} +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
2
|
+
import { withWorkspaceRls, type Database } from "./database";
|
|
3
|
+
import * as schema from "./schema";
|
|
4
|
+
import { getExternalLinkTurnAuthorization } from "./external-link-work";
|
|
5
|
+
import { initiatorFromStorage } from "./turn-initiator";
|
|
6
|
+
|
|
7
|
+
/** Shared physical-attempt fence. Does not mutate goal/execution snapshots. */
|
|
8
|
+
export async function getLiveSessionAttemptTurn(
|
|
9
|
+
db: Database,
|
|
10
|
+
workspaceId: string,
|
|
11
|
+
sessionId: string,
|
|
12
|
+
attemptId: string,
|
|
13
|
+
) {
|
|
14
|
+
return withWorkspaceRls(db, workspaceId, async (tx) => {
|
|
15
|
+
const [row] = await tx
|
|
16
|
+
.select({ turn: schema.sessionTurns })
|
|
17
|
+
.from(schema.sessionTurnAttempts)
|
|
18
|
+
.innerJoin(
|
|
19
|
+
schema.sessionTurns,
|
|
20
|
+
and(
|
|
21
|
+
eq(schema.sessionTurns.workspaceId, schema.sessionTurnAttempts.workspaceId),
|
|
22
|
+
eq(schema.sessionTurns.id, schema.sessionTurnAttempts.turnId),
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
.innerJoin(
|
|
26
|
+
schema.sessions,
|
|
27
|
+
and(
|
|
28
|
+
eq(schema.sessions.workspaceId, schema.sessionTurnAttempts.workspaceId),
|
|
29
|
+
eq(schema.sessions.id, schema.sessionTurnAttempts.sessionId),
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
.where(
|
|
33
|
+
and(
|
|
34
|
+
eq(schema.sessionTurnAttempts.workspaceId, workspaceId),
|
|
35
|
+
eq(schema.sessionTurnAttempts.sessionId, sessionId),
|
|
36
|
+
eq(schema.sessionTurnAttempts.id, attemptId),
|
|
37
|
+
inArray(schema.sessionTurnAttempts.state, ["claimed", "running"]),
|
|
38
|
+
eq(schema.sessionTurns.activeAttemptId, attemptId),
|
|
39
|
+
eq(schema.sessions.activeTurnId, schema.sessionTurns.id),
|
|
40
|
+
inArray(schema.sessionTurns.status, [
|
|
41
|
+
"running",
|
|
42
|
+
"requires_action",
|
|
43
|
+
"recovering",
|
|
44
|
+
"waiting_capacity",
|
|
45
|
+
]),
|
|
46
|
+
sql`not exists (
|
|
47
|
+
select 1 from ${schema.sessionAttemptInterruptions} interruption
|
|
48
|
+
where interruption.workspace_id = ${workspaceId}
|
|
49
|
+
and interruption.attempt_id = ${attemptId}
|
|
50
|
+
and interruption.state in ('pending', 'delivered', 'acknowledged')
|
|
51
|
+
)`,
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
.limit(1);
|
|
55
|
+
if (!row) return null;
|
|
56
|
+
const linked = await getExternalLinkTurnAuthorization(
|
|
57
|
+
tx,
|
|
58
|
+
{ accountId: row.turn.accountId, workspaceId },
|
|
59
|
+
row.turn.id,
|
|
60
|
+
);
|
|
61
|
+
if (linked && !linked.authorized) return null;
|
|
62
|
+
return row.turn;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Host credential reads need provenance and live authority, not a runnable
|
|
67
|
+
* execution projection or a write to the turn's frozen goal snapshot. */
|
|
68
|
+
export async function getHostMcpLiveAttempt(
|
|
69
|
+
db: Database,
|
|
70
|
+
workspaceId: string,
|
|
71
|
+
sessionId: string,
|
|
72
|
+
attemptId: string,
|
|
73
|
+
) {
|
|
74
|
+
const row = await getLiveSessionAttemptTurn(db, workspaceId, sessionId, attemptId);
|
|
75
|
+
if (!row) return null;
|
|
76
|
+
return {
|
|
77
|
+
...row,
|
|
78
|
+
initiator: initiatorFromStorage(
|
|
79
|
+
row.initiatorKind,
|
|
80
|
+
row.initiatorSubjectId,
|
|
81
|
+
row.initiatorContext ?? {},
|
|
82
|
+
),
|
|
83
|
+
initiatorContext: row.initiatorContext ?? {},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -37,6 +37,7 @@ export async function ensureManagedHumanPersonalWorkspace(
|
|
|
37
37
|
and(
|
|
38
38
|
eq(schema.workspaces.externalSource, personalWorkspaceExternalSource),
|
|
39
39
|
eq(schema.workspaces.externalId, personalWorkspaceExternalId),
|
|
40
|
+
eq(schema.workspaces.accountId, input.accountId),
|
|
40
41
|
),
|
|
41
42
|
)
|
|
42
43
|
.limit(1);
|
|
@@ -51,7 +52,11 @@ export async function ensureManagedHumanPersonalWorkspace(
|
|
|
51
52
|
externalId: personalWorkspaceExternalId,
|
|
52
53
|
})
|
|
53
54
|
.onConflictDoUpdate({
|
|
54
|
-
target: [
|
|
55
|
+
target: [
|
|
56
|
+
schema.workspaces.accountId,
|
|
57
|
+
schema.workspaces.externalSource,
|
|
58
|
+
schema.workspaces.externalId,
|
|
59
|
+
],
|
|
55
60
|
set: { updatedAt: new Date() },
|
|
56
61
|
})
|
|
57
62
|
.returning();
|
package/src/memory-domain.ts
CHANGED
|
@@ -282,7 +282,7 @@ export function isMemoryScopeApplicable(
|
|
|
282
282
|
// its narrowest configured layer. `off` registers no Memory tools at all.
|
|
283
283
|
// ---------------------------------------------------------------------------
|
|
284
284
|
|
|
285
|
-
export type MemoryAgentScopeMode = "workspace" | "user" | "
|
|
285
|
+
export type MemoryAgentScopeMode = "workspace" | "user" | "off";
|
|
286
286
|
|
|
287
287
|
/**
|
|
288
288
|
* The agent-side Memory selector resolved from one session row. The end-user
|
|
@@ -292,16 +292,16 @@ export type MemoryAgentScopeMode = "workspace" | "user" | "session" | "off";
|
|
|
292
292
|
*/
|
|
293
293
|
export type MemoryAgentScope = {
|
|
294
294
|
mode: MemoryAgentScopeMode;
|
|
295
|
-
|
|
295
|
+
userSubjectId: string | null;
|
|
296
296
|
rootSessionId: string | null;
|
|
297
297
|
};
|
|
298
298
|
|
|
299
299
|
export const END_USER_MEMORY_SUBJECT_PREFIX = "end_user:v1:";
|
|
300
300
|
|
|
301
301
|
/** The typed `user` selector for an opaque session end-user label. */
|
|
302
|
-
export function endUserMemorySubjectId(
|
|
302
|
+
export function endUserMemorySubjectId(scopeSubjectId: { source: string; id: string }): string {
|
|
303
303
|
const digest = createHash("sha256")
|
|
304
|
-
.update(JSON.stringify([
|
|
304
|
+
.update(JSON.stringify([scopeSubjectId.source, scopeSubjectId.id]), "utf8")
|
|
305
305
|
.digest("hex");
|
|
306
306
|
return `${END_USER_MEMORY_SUBJECT_PREFIX}${digest}`;
|
|
307
307
|
}
|
|
@@ -319,15 +319,10 @@ export function memoryWriteScopeForAgentScope(scope: MemoryAgentScope): MemorySc
|
|
|
319
319
|
case "workspace":
|
|
320
320
|
return { type: "workspace" };
|
|
321
321
|
case "user":
|
|
322
|
-
if (!scope.
|
|
322
|
+
if (!scope.userSubjectId) {
|
|
323
323
|
throw new Error("memory scope user requires an end-user subject id");
|
|
324
324
|
}
|
|
325
|
-
return { type: "user", subjectId: scope.
|
|
326
|
-
case "session":
|
|
327
|
-
if (!scope.rootSessionId) {
|
|
328
|
-
throw new Error("memory scope session requires a root session id");
|
|
329
|
-
}
|
|
330
|
-
return { type: "session", sessionId: scope.rootSessionId };
|
|
325
|
+
return { type: "user", subjectId: scope.userSubjectId };
|
|
331
326
|
}
|
|
332
327
|
}
|
|
333
328
|
|
package/src/provision-roles.ts
CHANGED
|
@@ -524,6 +524,9 @@ async function grantAppRoleIfSchemaExists(
|
|
|
524
524
|
const runtimeReadInsertUpdateTables = `ARRAY[${RUNTIME_READ_INSERT_UPDATE_TABLES.map(literal).join(", ")}]`;
|
|
525
525
|
const workClaimCapabilityRoutines = `ARRAY[${WORK_CLAIM_CAPABILITY_ROUTINES.map(literal).join(", ")}]`;
|
|
526
526
|
const organizationMembershipLifecycleRoutines = `ARRAY[${[
|
|
527
|
+
"ensure_external_identity(uuid,text,text)",
|
|
528
|
+
"get_external_identity_link_reference(uuid,uuid,text)",
|
|
529
|
+
"get_external_identity_link_inventory_references(uuid,uuid[])",
|
|
527
530
|
"list_self_organization_memberships(text)",
|
|
528
531
|
"list_self_organization_invitations(text)",
|
|
529
532
|
"list_self_organization_invitations(text,uuid,integer)",
|
package/src/runtime-posture.ts
CHANGED
|
@@ -86,6 +86,9 @@ const MANAGED_HUMAN_PERSONAL_WORKSPACE_ROUTINE =
|
|
|
86
86
|
const ADDITIONAL_ORGANIZATION_CREATION_ROUTINE =
|
|
87
87
|
"create_additional_managed_organization(text, text, text, text, uuid)";
|
|
88
88
|
const ORGANIZATION_MEMBERSHIP_LIFECYCLE_ROUTINES = [
|
|
89
|
+
"get_external_identity_link_reference(uuid, uuid, text)",
|
|
90
|
+
"get_external_identity_link_inventory_references(uuid, uuid[])",
|
|
91
|
+
"ensure_external_identity(uuid, text, text)",
|
|
89
92
|
"list_self_organization_memberships(text)",
|
|
90
93
|
"list_self_organization_invitations(text)",
|
|
91
94
|
"list_self_organization_invitations(text, uuid, integer)",
|
|
@@ -700,6 +703,7 @@ export const FORCE_RLS_TABLES = [
|
|
|
700
703
|
"composer_drafts",
|
|
701
704
|
"computer_session_associations",
|
|
702
705
|
"computer_sessions",
|
|
706
|
+
"connect_attempts",
|
|
703
707
|
"connection_disconnect_operations",
|
|
704
708
|
"connection_use_audit_facts",
|
|
705
709
|
"connection_use_once_consumption_receipts",
|
|
@@ -732,6 +736,10 @@ export const FORCE_RLS_TABLES = [
|
|
|
732
736
|
"editable_artifact_versions",
|
|
733
737
|
"editable_artifacts",
|
|
734
738
|
"enrollments",
|
|
739
|
+
"external_identities",
|
|
740
|
+
"external_identity_links",
|
|
741
|
+
"external_link_task_authorities",
|
|
742
|
+
"external_link_turn_authorities",
|
|
735
743
|
"feedback_submissions",
|
|
736
744
|
"file_uploads",
|
|
737
745
|
"files",
|
|
@@ -749,6 +757,10 @@ export const FORCE_RLS_TABLES = [
|
|
|
749
757
|
"host_export_cursor_state",
|
|
750
758
|
"host_export_dead_letters",
|
|
751
759
|
"host_export_outbox",
|
|
760
|
+
"host_mcp_bindings",
|
|
761
|
+
"host_mcp_delegations",
|
|
762
|
+
"host_mcp_task_authorities",
|
|
763
|
+
"host_mcp_turn_authorities",
|
|
752
764
|
"image_generation_operations",
|
|
753
765
|
"import_batches",
|
|
754
766
|
"integration_facet_binding_owners",
|
|
@@ -1068,6 +1080,7 @@ export const RUNTIME_FULL_DML_TABLES = [
|
|
|
1068
1080
|
"codex_subscription_credentials",
|
|
1069
1081
|
"composer_drafts",
|
|
1070
1082
|
"computer_session_associations",
|
|
1083
|
+
"connect_attempts",
|
|
1071
1084
|
"connection_disconnect_operations",
|
|
1072
1085
|
"connections",
|
|
1073
1086
|
"connector_action_policies",
|
|
@@ -1085,6 +1098,8 @@ export const RUNTIME_FULL_DML_TABLES = [
|
|
|
1085
1098
|
"generated_video_artifacts",
|
|
1086
1099
|
"github_installation_repositories",
|
|
1087
1100
|
"github_installations",
|
|
1101
|
+
"host_mcp_bindings",
|
|
1102
|
+
"host_mcp_delegations",
|
|
1088
1103
|
"image_generation_operations",
|
|
1089
1104
|
"import_batches",
|
|
1090
1105
|
"integration_facet_binding_owners",
|
|
@@ -1249,9 +1264,13 @@ export const RUNTIME_READ_INSERT_TABLES = [
|
|
|
1249
1264
|
"editable_artifact_transactions",
|
|
1250
1265
|
"editable_artifact_undo_claims",
|
|
1251
1266
|
"editable_artifact_versions",
|
|
1267
|
+
"external_link_task_authorities",
|
|
1268
|
+
"external_link_turn_authorities",
|
|
1252
1269
|
"feedback_submissions",
|
|
1253
1270
|
"google_drive_object_acl_evidence",
|
|
1254
1271
|
"google_drive_object_acl_principals",
|
|
1272
|
+
"host_mcp_task_authorities",
|
|
1273
|
+
"host_mcp_turn_authorities",
|
|
1255
1274
|
"knowledge_change_proposals",
|
|
1256
1275
|
"knowledge_claim_evidence",
|
|
1257
1276
|
"knowledge_claim_relations",
|
|
@@ -1304,6 +1323,7 @@ export const RUNTIME_READ_INSERT_UPDATE_TABLES = [
|
|
|
1304
1323
|
"computer_sessions",
|
|
1305
1324
|
"editable_artifact_session_links",
|
|
1306
1325
|
"editable_artifacts",
|
|
1326
|
+
"external_identity_links",
|
|
1307
1327
|
"integration_facet_definitions",
|
|
1308
1328
|
"integration_spec_revisions",
|
|
1309
1329
|
"integration_tools",
|
|
@@ -1344,6 +1364,7 @@ export const PROTECTED_NO_DIRECT_DML_TABLES = [
|
|
|
1344
1364
|
"document_default_collection_backfill_runs",
|
|
1345
1365
|
"editable_artifact_live_tickets",
|
|
1346
1366
|
"editable_artifact_scope_authorization_heads",
|
|
1367
|
+
"external_identities",
|
|
1347
1368
|
"governed_learning_activation_receipts",
|
|
1348
1369
|
"governed_learning_activation_undo_receipts",
|
|
1349
1370
|
"governed_learning_decision_receipts",
|
|
@@ -2233,6 +2254,32 @@ export function evaluateRuntimeDatabasePosture(
|
|
|
2233
2254
|
);
|
|
2234
2255
|
}
|
|
2235
2256
|
}
|
|
2257
|
+
} else if (
|
|
2258
|
+
[
|
|
2259
|
+
"ensure_external_identity(uuid, text, text)",
|
|
2260
|
+
"get_external_identity_link_reference(uuid, uuid, text)",
|
|
2261
|
+
"get_external_identity_link_inventory_references(uuid, uuid[])",
|
|
2262
|
+
].includes(routine.name)
|
|
2263
|
+
) {
|
|
2264
|
+
const names =
|
|
2265
|
+
routine.name !== "ensure_external_identity(uuid, text, text)"
|
|
2266
|
+
? ["external_identity_links", "external_identities", "organization_memberships"]
|
|
2267
|
+
: [
|
|
2268
|
+
"external_identities",
|
|
2269
|
+
"organization_memberships",
|
|
2270
|
+
"workspaces",
|
|
2271
|
+
"workspace_inference_controls",
|
|
2272
|
+
];
|
|
2273
|
+
const missing = names.filter((name) => !tableByName.has(name));
|
|
2274
|
+
if (missing.length > 0) {
|
|
2275
|
+
violations.push(
|
|
2276
|
+
`target-schema runtime capability ${routine.name} authority tables are missing: ${missing.join(", ")}`,
|
|
2277
|
+
);
|
|
2278
|
+
} else if (names.some((name) => tableByName.get(name)!.owner !== routine.owner)) {
|
|
2279
|
+
violations.push(
|
|
2280
|
+
`target-schema runtime capability ${routine.name} authority table owners do not match`,
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2236
2283
|
} else if (routine.name === MANAGED_HUMAN_PERSONAL_WORKSPACE_ROUTINE) {
|
|
2237
2284
|
const authorityTables = MANAGED_HUMAN_PERSONAL_WORKSPACE_AUTHORITY_TABLES.filter(
|
|
2238
2285
|
(tableName) => tableByName.has(tableName),
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { ScheduledTaskRunAcceptedExecution } from "@opengeni/contracts";
|
|
3
|
+
import { rawRows, withRlsContext, type Database } from "./database";
|
|
4
|
+
|
|
5
|
+
export async function getScheduledTaskRevisionAuthority(
|
|
6
|
+
db: Database,
|
|
7
|
+
input: { accountId: string; workspaceId: string; taskId: string; taskAuthorityRevision: number },
|
|
8
|
+
): Promise<{
|
|
9
|
+
subjectId: string;
|
|
10
|
+
organizationMembershipId: string;
|
|
11
|
+
membershipAuthorizationRevision: number;
|
|
12
|
+
} | null> {
|
|
13
|
+
return withRlsContext(
|
|
14
|
+
db,
|
|
15
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
16
|
+
async (tx) => {
|
|
17
|
+
const [row] = await rawRows<{ authority: unknown }>(
|
|
18
|
+
tx,
|
|
19
|
+
sql`
|
|
20
|
+
select scheduled_task_revision_authority_snapshot(
|
|
21
|
+
${input.accountId}::uuid, ${input.workspaceId}::uuid,
|
|
22
|
+
${input.taskId}::uuid, ${input.taskAuthorityRevision}::bigint
|
|
23
|
+
) as authority`,
|
|
24
|
+
);
|
|
25
|
+
return row?.authority
|
|
26
|
+
? ScheduledTaskRunAcceptedExecution.shape.causalHumanAuthority.parse(row.authority)
|
|
27
|
+
: null;
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -150,7 +150,11 @@ export const workspaces = pgTable(
|
|
|
150
150
|
accountSlug: uniqueIndex("workspaces_account_slug_idx")
|
|
151
151
|
.on(table.accountId, table.slug)
|
|
152
152
|
.where(sql`${table.slug} is not null`),
|
|
153
|
-
external: uniqueIndex("workspaces_external_idx").on(
|
|
153
|
+
external: uniqueIndex("workspaces_external_idx").on(
|
|
154
|
+
table.accountId,
|
|
155
|
+
table.externalSource,
|
|
156
|
+
table.externalId,
|
|
157
|
+
),
|
|
154
158
|
sandboxViewerForceDrain: index("workspaces_sandbox_viewer_force_drain_idx")
|
|
155
159
|
.on(table.id)
|
|
156
160
|
.where(sql`${table.sandboxViewerForceDrainReason} is not null`),
|
|
@@ -519,6 +523,46 @@ export const workspaceMemberships = pgTable(
|
|
|
519
523
|
// Organization membership is distinct from workspace access. `account_id` is
|
|
520
524
|
// the physical organization identifier; `personal_workspace_id` is lifecycle
|
|
521
525
|
// metadata only and never the ownership anchor for user resources.
|
|
526
|
+
export const externalIdentities = pgTable(
|
|
527
|
+
"external_identities",
|
|
528
|
+
{
|
|
529
|
+
id: uuid("id").primaryKey(),
|
|
530
|
+
accountId: uuid("account_id")
|
|
531
|
+
.notNull()
|
|
532
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
533
|
+
source: text("source").notNull(),
|
|
534
|
+
externalId: text("external_id").notNull(),
|
|
535
|
+
subjectId: text("subject_id").notNull(),
|
|
536
|
+
organizationMembershipId: uuid("organization_membership_id").notNull(),
|
|
537
|
+
personalWorkspaceId: uuid("personal_workspace_id").notNull(),
|
|
538
|
+
status: text("status").notNull().default("active"),
|
|
539
|
+
authorizationRevision: bigint("authorization_revision", { mode: "number" })
|
|
540
|
+
.notNull()
|
|
541
|
+
.default(1),
|
|
542
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
543
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
544
|
+
},
|
|
545
|
+
(table) => ({
|
|
546
|
+
mapping: uniqueIndex("external_identities_account_id_source_external_id_key").on(
|
|
547
|
+
table.accountId,
|
|
548
|
+
table.source,
|
|
549
|
+
table.externalId,
|
|
550
|
+
),
|
|
551
|
+
subject: uniqueIndex("external_identities_account_id_subject_id_key").on(
|
|
552
|
+
table.accountId,
|
|
553
|
+
table.subjectId,
|
|
554
|
+
),
|
|
555
|
+
membership: foreignKey({
|
|
556
|
+
columns: [table.organizationMembershipId, table.accountId],
|
|
557
|
+
foreignColumns: [organizationMemberships.id, organizationMemberships.accountId],
|
|
558
|
+
}),
|
|
559
|
+
personalWorkspace: foreignKey({
|
|
560
|
+
columns: [table.personalWorkspaceId, table.accountId],
|
|
561
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
562
|
+
}),
|
|
563
|
+
}),
|
|
564
|
+
);
|
|
565
|
+
|
|
522
566
|
export const organizationMemberships = pgTable(
|
|
523
567
|
"organization_memberships",
|
|
524
568
|
{
|
|
@@ -895,7 +939,8 @@ export const organizationMembershipLifecycleEvents = pgTable(
|
|
|
895
939
|
.notNull()
|
|
896
940
|
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
897
941
|
operationId: uuid("operation_id").notNull(),
|
|
898
|
-
actorMembershipId: uuid("actor_membership_id")
|
|
942
|
+
actorMembershipId: uuid("actor_membership_id"),
|
|
943
|
+
actorServiceSubject: text("actor_service_subject"),
|
|
899
944
|
targetMembershipId: uuid("target_membership_id"),
|
|
900
945
|
kind: text("kind").notNull(),
|
|
901
946
|
priorAuthorizationRevision: bigint("prior_authorization_revision", {
|
|
@@ -926,6 +971,10 @@ export const organizationMembershipLifecycleEvents = pgTable(
|
|
|
926
971
|
"organization_membership_lifecycle_events_kind_check",
|
|
927
972
|
sql`${table.kind} in ('invite', 'accept', 'revoke_invitation', 'change_role', 'suspend', 'reactivate', 'offboard', 'retention')`,
|
|
928
973
|
),
|
|
974
|
+
actorKindValid: check(
|
|
975
|
+
"organization_membership_lifecycle_events_actor_kind_check",
|
|
976
|
+
sql`(${table.actorMembershipId} is not null and ${table.actorServiceSubject} is null) or (${table.actorMembershipId} is null and ${table.actorServiceSubject} is not null and ${table.actorServiceSubject} ~ '^api_key:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')`,
|
|
977
|
+
),
|
|
929
978
|
}),
|
|
930
979
|
);
|
|
931
980
|
|
|
@@ -3543,6 +3592,49 @@ export const integrationOauthClients = pgTable(
|
|
|
3543
3592
|
|
|
3544
3593
|
// Consumed OAuth state nonces. Rows are inserted only on callback; the primary
|
|
3545
3594
|
// key makes a verified state single-use across API instances.
|
|
3595
|
+
export const hostMcpTurnAuthorities = pgTable(
|
|
3596
|
+
"host_mcp_turn_authorities",
|
|
3597
|
+
{
|
|
3598
|
+
turnId: uuid("turn_id")
|
|
3599
|
+
.notNull()
|
|
3600
|
+
.references(() => sessionTurns.id, { onDelete: "cascade" }),
|
|
3601
|
+
serverId: text("server_id").notNull(),
|
|
3602
|
+
accountId: uuid("account_id")
|
|
3603
|
+
.notNull()
|
|
3604
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
3605
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
3606
|
+
sessionId: uuid("session_id")
|
|
3607
|
+
.notNull()
|
|
3608
|
+
.references(() => sessions.id, { onDelete: "cascade" }),
|
|
3609
|
+
ownerSubjectId: text("owner_subject_id").notNull(),
|
|
3610
|
+
bindingId: uuid("binding_id")
|
|
3611
|
+
.notNull()
|
|
3612
|
+
.references(() => hostMcpBindings.id),
|
|
3613
|
+
delegationId: uuid("delegation_id")
|
|
3614
|
+
.notNull()
|
|
3615
|
+
.references(() => hostMcpDelegations.id),
|
|
3616
|
+
canonicalSnapshot: jsonb("canonical_snapshot")
|
|
3617
|
+
.$type<import("@opengeni/contracts/host-mcp-bindings").HostMcpAcceptedAuthority>()
|
|
3618
|
+
.notNull(),
|
|
3619
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3620
|
+
},
|
|
3621
|
+
(table) => ({
|
|
3622
|
+
pk: primaryKey({ columns: [table.turnId, table.serverId] }),
|
|
3623
|
+
workspaceAccount: foreignKey({
|
|
3624
|
+
columns: [table.workspaceId, table.accountId],
|
|
3625
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
3626
|
+
}).onDelete("cascade"),
|
|
3627
|
+
serverBounds: check(
|
|
3628
|
+
"host_mcp_turn_authorities_server_id_check",
|
|
3629
|
+
sql`octet_length(${table.serverId}) between 1 and 1024`,
|
|
3630
|
+
),
|
|
3631
|
+
snapshotBounds: check(
|
|
3632
|
+
"host_mcp_turn_authorities_canonical_snapshot_check",
|
|
3633
|
+
sql`jsonb_typeof(${table.canonicalSnapshot}) = 'object' and octet_length(${table.canonicalSnapshot}::text) <= 262144`,
|
|
3634
|
+
),
|
|
3635
|
+
}),
|
|
3636
|
+
);
|
|
3637
|
+
|
|
3546
3638
|
export const integrationOauthStateNonces = pgTable(
|
|
3547
3639
|
"integration_oauth_state_nonces",
|
|
3548
3640
|
{
|
|
@@ -3564,6 +3656,135 @@ export const integrationOauthStateNonces = pgTable(
|
|
|
3564
3656
|
}),
|
|
3565
3657
|
);
|
|
3566
3658
|
|
|
3659
|
+
export const hostMcpBindings = pgTable(
|
|
3660
|
+
"host_mcp_bindings",
|
|
3661
|
+
{
|
|
3662
|
+
id: uuid("id").primaryKey(),
|
|
3663
|
+
accountId: uuid("account_id")
|
|
3664
|
+
.notNull()
|
|
3665
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
3666
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
3667
|
+
ownerSubjectId: text("owner_subject_id").notNull(),
|
|
3668
|
+
authorizationRevision: bigint("authorization_revision", { mode: "number" }).notNull(),
|
|
3669
|
+
operationId: uuid("operation_id").notNull(),
|
|
3670
|
+
requestDigest: text("request_digest").notNull(),
|
|
3671
|
+
definition: jsonb("definition").notNull(),
|
|
3672
|
+
generation: bigint("generation", { mode: "number" }).notNull().default(1),
|
|
3673
|
+
status: text("status").notNull().default("active"),
|
|
3674
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3675
|
+
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
3676
|
+
},
|
|
3677
|
+
(table) => ({
|
|
3678
|
+
workspace: foreignKey({
|
|
3679
|
+
name: "host_mcp_bindings_workspace_account_fk",
|
|
3680
|
+
columns: [table.workspaceId, table.accountId],
|
|
3681
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
3682
|
+
}).onDelete("cascade"),
|
|
3683
|
+
owner: foreignKey({
|
|
3684
|
+
name: "host_mcp_bindings_member_owner_fk",
|
|
3685
|
+
columns: [table.accountId, table.ownerSubjectId],
|
|
3686
|
+
foreignColumns: [organizationMemberships.accountId, organizationMemberships.subjectId],
|
|
3687
|
+
}),
|
|
3688
|
+
operation: uniqueIndex("host_mcp_bindings_workspace_id_owner_subject_id_operation_id_key").on(
|
|
3689
|
+
table.workspaceId,
|
|
3690
|
+
table.ownerSubjectId,
|
|
3691
|
+
table.operationId,
|
|
3692
|
+
),
|
|
3693
|
+
inventory: index("host_mcp_bindings_owner_idx").on(
|
|
3694
|
+
table.workspaceId,
|
|
3695
|
+
table.ownerSubjectId,
|
|
3696
|
+
table.createdAt,
|
|
3697
|
+
table.id,
|
|
3698
|
+
),
|
|
3699
|
+
}),
|
|
3700
|
+
);
|
|
3701
|
+
|
|
3702
|
+
export const hostMcpDelegations = pgTable(
|
|
3703
|
+
"host_mcp_delegations",
|
|
3704
|
+
{
|
|
3705
|
+
id: uuid("id").primaryKey(),
|
|
3706
|
+
accountId: uuid("account_id")
|
|
3707
|
+
.notNull()
|
|
3708
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
3709
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
3710
|
+
ownerSubjectId: text("owner_subject_id").notNull(),
|
|
3711
|
+
ownerAuthorizationRevision: bigint("owner_authorization_revision", {
|
|
3712
|
+
mode: "number",
|
|
3713
|
+
}).notNull(),
|
|
3714
|
+
bindingId: uuid("binding_id")
|
|
3715
|
+
.notNull()
|
|
3716
|
+
.references(() => hostMcpBindings.id),
|
|
3717
|
+
bindingGeneration: bigint("binding_generation", { mode: "number" }).notNull(),
|
|
3718
|
+
operationId: uuid("operation_id").notNull(),
|
|
3719
|
+
requestDigest: text("request_digest").notNull(),
|
|
3720
|
+
grantDefinition: jsonb("grant_definition").notNull(),
|
|
3721
|
+
generation: bigint("generation", { mode: "number" }).notNull().default(1),
|
|
3722
|
+
status: text("status").notNull().default("active"),
|
|
3723
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3724
|
+
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
3725
|
+
},
|
|
3726
|
+
(table) => ({
|
|
3727
|
+
workspace: foreignKey({
|
|
3728
|
+
name: "host_mcp_delegations_workspace_account_fk",
|
|
3729
|
+
columns: [table.workspaceId, table.accountId],
|
|
3730
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
3731
|
+
}).onDelete("cascade"),
|
|
3732
|
+
owner: foreignKey({
|
|
3733
|
+
name: "host_mcp_delegations_member_owner_fk",
|
|
3734
|
+
columns: [table.accountId, table.ownerSubjectId],
|
|
3735
|
+
foreignColumns: [organizationMemberships.accountId, organizationMemberships.subjectId],
|
|
3736
|
+
}),
|
|
3737
|
+
operation: uniqueIndex(
|
|
3738
|
+
"host_mcp_delegations_workspace_id_owner_subject_id_operation_id_key",
|
|
3739
|
+
).on(table.workspaceId, table.ownerSubjectId, table.operationId),
|
|
3740
|
+
inventory: index("host_mcp_delegations_owner_idx").on(
|
|
3741
|
+
table.workspaceId,
|
|
3742
|
+
table.ownerSubjectId,
|
|
3743
|
+
table.bindingId,
|
|
3744
|
+
table.id,
|
|
3745
|
+
),
|
|
3746
|
+
}),
|
|
3747
|
+
);
|
|
3748
|
+
|
|
3749
|
+
export const connectAttempts = pgTable(
|
|
3750
|
+
"connect_attempts",
|
|
3751
|
+
{
|
|
3752
|
+
id: uuid("id").primaryKey(),
|
|
3753
|
+
accountId: uuid("account_id")
|
|
3754
|
+
.notNull()
|
|
3755
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
3756
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
3757
|
+
subjectId: text("subject_id").notNull(),
|
|
3758
|
+
idempotencyKeyHash: text("idempotency_key_hash").notNull(),
|
|
3759
|
+
requestDigest: text("request_digest").notNull(),
|
|
3760
|
+
returnUrl: text("return_url").notNull(),
|
|
3761
|
+
externalContinuation: jsonb("external_continuation"),
|
|
3762
|
+
projection: jsonb("projection").notNull(),
|
|
3763
|
+
operationId: text("operation_id"),
|
|
3764
|
+
operationDigest: text("operation_digest"),
|
|
3765
|
+
receipts: jsonb("receipts").notNull().default({}),
|
|
3766
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
3767
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3768
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3769
|
+
},
|
|
3770
|
+
(table) => ({
|
|
3771
|
+
workspaceAccount: foreignKey({
|
|
3772
|
+
name: "connect_attempts_workspace_account_fk",
|
|
3773
|
+
columns: [table.workspaceId, table.accountId],
|
|
3774
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
3775
|
+
}).onDelete("cascade"),
|
|
3776
|
+
idempotency: uniqueIndex("connect_attempts_actor_idempotency_idx").on(
|
|
3777
|
+
table.workspaceId,
|
|
3778
|
+
table.subjectId,
|
|
3779
|
+
table.idempotencyKeyHash,
|
|
3780
|
+
),
|
|
3781
|
+
pending: index("connect_attempts_actor_pending_idx")
|
|
3782
|
+
.on(table.workspaceId, table.subjectId, table.createdAt.desc(), table.id.desc())
|
|
3783
|
+
.where(sql`${table.projection}->>'state' not in ('complete','cancelled','expired')`),
|
|
3784
|
+
expiry: index("connect_attempts_expiry_idx").on(table.expiresAt, table.id),
|
|
3785
|
+
}),
|
|
3786
|
+
);
|
|
3787
|
+
|
|
3567
3788
|
export const mcpOauthClients = pgTable(
|
|
3568
3789
|
"mcp_oauth_clients",
|
|
3569
3790
|
{
|
|
@@ -4175,6 +4396,9 @@ export const sessions = pgTable(
|
|
|
4175
4396
|
// sessions carrying the same end-user label; 'session' limits it to the
|
|
4176
4397
|
// own root tree. Enforced only in the core session-authorization seam.
|
|
4177
4398
|
agentAccess: text("agent_access").notNull().default("workspace"),
|
|
4399
|
+
// Canonical authenticated user for the frozen cross-session agent scope.
|
|
4400
|
+
// Independent of per-turn initiating users and historical external labels.
|
|
4401
|
+
scopeSubjectId: text("scope_subject_id"),
|
|
4178
4402
|
// Opaque end-user label (both set or both null). This is a product label
|
|
4179
4403
|
// used for scoping and filtering, never a subject and never authority.
|
|
4180
4404
|
endUserSource: text("end_user_source"),
|
|
@@ -10814,7 +11038,7 @@ export const scheduledTasks = pgTable(
|
|
|
10814
11038
|
>(),
|
|
10815
11039
|
creatorSessionPolicy: jsonb("creator_session_policy").$type<{
|
|
10816
11040
|
agentAccess: string | null;
|
|
10817
|
-
|
|
11041
|
+
scopeSubjectId: string | null;
|
|
10818
11042
|
memoryScope: string | null;
|
|
10819
11043
|
}>(),
|
|
10820
11044
|
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
|
@@ -12934,6 +13158,7 @@ export const socialConnections = pgTable(
|
|
|
12934
13158
|
"social_connections",
|
|
12935
13159
|
{
|
|
12936
13160
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
13161
|
+
version: integer("version").notNull().default(1),
|
|
12937
13162
|
accountId: uuid("account_id")
|
|
12938
13163
|
.notNull()
|
|
12939
13164
|
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
package/src/session-control.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { and, eq, inArray, sql } from "drizzle-orm";
|
|
|
12
12
|
import type { Database, SessionActivityDatabase } from "./database";
|
|
13
13
|
import { withLosslessContentWriteVersion } from "./lossless-json";
|
|
14
14
|
import { nestedPostgresSqlState } from "./persistence-errors";
|
|
15
|
+
import { getExternalLinkTurnAuthorization } from "./external-link-work";
|
|
15
16
|
import * as schema from "./schema";
|
|
16
17
|
import {
|
|
17
18
|
boundedChildPausedReason,
|
|
@@ -333,6 +334,19 @@ export async function assertAgentCommandAuthorityInTransaction(
|
|
|
333
334
|
"The calling agent attempt is being interrupted",
|
|
334
335
|
);
|
|
335
336
|
}
|
|
337
|
+
const linked = await getExternalLinkTurnAuthorization(
|
|
338
|
+
db,
|
|
339
|
+
{
|
|
340
|
+
accountId: callerSession.accountId,
|
|
341
|
+
workspaceId: input.workspaceId,
|
|
342
|
+
},
|
|
343
|
+
input.actor.turnId,
|
|
344
|
+
);
|
|
345
|
+
if (linked && !linked.authorized)
|
|
346
|
+
throw new AgentCommandAuthorityError(
|
|
347
|
+
"CALLER_STALE",
|
|
348
|
+
"The calling agent's native identity link is no longer active",
|
|
349
|
+
);
|
|
336
350
|
if (input.action === "steer" && input.targetSessionId === input.actor.sessionId) {
|
|
337
351
|
throw new AgentCommandAuthorityError("SELF_STEER", "An agent cannot steer its own session");
|
|
338
352
|
}
|
|
@@ -1767,6 +1767,13 @@ export async function submitHumanPromptInTransaction(
|
|
|
1767
1767
|
/** Trusted database-only admission seam. It runs only after a completed
|
|
1768
1768
|
* operation replay has been ruled out; throwing rolls the prompt back. */
|
|
1769
1769
|
beforeFreshPromptCommit?: (tx: Database) => Promise<void>;
|
|
1770
|
+
/** Backend-only authority capture, atomic with the newly accepted turn. */
|
|
1771
|
+
captureTurnAuthority?: (tx: Database, turnId: string) => Promise<void>;
|
|
1772
|
+
selectedHostMcpDelegations?: Array<{
|
|
1773
|
+
serverId: string;
|
|
1774
|
+
delegationId: string;
|
|
1775
|
+
generation: number;
|
|
1776
|
+
}>;
|
|
1770
1777
|
/** Request-scoped callers bound the control prefix wait; lifecycle callers omit it. */
|
|
1771
1778
|
controlLockTimeoutMs?: number;
|
|
1772
1779
|
},
|
|
@@ -1823,6 +1830,9 @@ export async function submitHumanPromptInTransaction(
|
|
|
1823
1830
|
mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
|
|
1824
1831
|
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
1825
1832
|
personalResourceAttachment: input.personalResourceAttachment ?? null,
|
|
1833
|
+
...(input.selectedHostMcpDelegations?.length
|
|
1834
|
+
? { selectedHostMcpDelegations: input.selectedHostMcpDelegations }
|
|
1835
|
+
: {}),
|
|
1826
1836
|
...(input.actor.type === "service"
|
|
1827
1837
|
? {
|
|
1828
1838
|
serviceInitiator: {
|
|
@@ -2194,6 +2204,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
2194
2204
|
)
|
|
2195
2205
|
.returning();
|
|
2196
2206
|
if (!turn) throw new SessionControlInvariantError("Prompt turn was not inserted");
|
|
2207
|
+
await input.captureTurnAuthority?.(db, turn.id);
|
|
2197
2208
|
let committedTurn = turn;
|
|
2198
2209
|
const personalResourceExpectedAuthorityEpoch =
|
|
2199
2210
|
input.personalResourceAttachment?.expectedAuthorityEpoch;
|