@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.
Files changed (102) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-6GAPAYZR.js → chunk-7JPLGBSA.js} +562 -86
  3. package/dist/chunk-7JPLGBSA.js.map +1 -0
  4. package/dist/{chunk-SNLIKELL.js → chunk-IHADTE7C.js} +2 -2
  5. package/dist/{chunk-7WX3R3YL.js → chunk-ILKJKDII.js} +7 -2
  6. package/dist/chunk-ILKJKDII.js.map +1 -0
  7. package/dist/{chunk-MGVOYVPS.js → chunk-NCSPNCQK.js} +3 -3
  8. package/dist/{chunk-B2KR3TEM.js → chunk-SGJDIF2D.js} +2 -2
  9. package/dist/{chunk-S4Y42VSX.js → chunk-Y5XFVJJM.js} +207 -3
  10. package/dist/chunk-Y5XFVJJM.js.map +1 -0
  11. package/dist/{chunk-FPTI4NP4.js → chunk-YGBOVXC7.js} +2 -2
  12. package/dist/{chunk-6Y7ZDVJ5.js → chunk-YGRT6VZO.js} +2 -2
  13. package/dist/{chunk-6IQ4X35Y.js → chunk-YVYBAWBU.js} +46 -1
  14. package/dist/chunk-YVYBAWBU.js.map +1 -0
  15. package/dist/connect-attempts.d.ts +60 -0
  16. package/dist/connection-setup-authority.d.ts +10 -0
  17. package/dist/connection-token-resolver.d.ts +11 -2
  18. package/dist/editable-artifact-durable-export.js +2 -2
  19. package/dist/editable-artifacts.js +3 -3
  20. package/dist/external-identities.d.ts +16 -0
  21. package/dist/external-identity-links.d.ts +74 -0
  22. package/dist/external-link-work.d.ts +51 -0
  23. package/dist/host-mcp-binding-match.d.ts +5 -0
  24. package/dist/host-mcp-bindings.d.ts +73 -0
  25. package/dist/host-mcp-task-authority.d.ts +47 -0
  26. package/dist/host-selection-identity.d.ts +6 -0
  27. package/dist/index.d.ts +51 -35
  28. package/dist/index.js +15171 -13775
  29. package/dist/index.js.map +1 -1
  30. package/dist/live-session-attempt.d.ts +275 -0
  31. package/dist/managed-auth-session-sets.js +3 -3
  32. package/dist/memory-domain.d.ts +3 -3
  33. package/dist/provision-roles.js +1 -1
  34. package/dist/retained-provider-commands.js +2 -2
  35. package/dist/runtime-posture.d.ts +6 -6
  36. package/dist/scheduled-task-revision-authority.d.ts +11 -0
  37. package/dist/schema.d.ts +1279 -9
  38. package/dist/schema.js +11 -1
  39. package/dist/session-background-commands.js +4 -4
  40. package/dist/session-command-output.js +4 -4
  41. package/dist/session-event-slices.js +2 -2
  42. package/dist/session-queue-commands.d.ts +7 -0
  43. package/dist/session-tenancy.d.ts +2 -0
  44. package/dist/session-tenancy.js +3 -3
  45. package/dist/video-generation.js +3 -3
  46. package/dist/workspace-authority.d.ts +4 -4
  47. package/dist/workspace-tool-defaults.js +4 -4
  48. package/drizzle/0437_organization_scoped_external_workspaces.sql +71 -0
  49. package/drizzle/0438_durable_connect_attempts.sql +95 -0
  50. package/drizzle/0439_external_identity_provisioning.sql +128 -0
  51. package/drizzle/0440_external_workspace_member_removal.sql +59 -0
  52. package/drizzle/0441_external_identity_membership_lifecycle.sql +163 -0
  53. package/drizzle/0442_external_owning_user_authority.sql +81 -0
  54. package/drizzle/0443_host_mcp_binding_registry.sql +93 -0
  55. package/drizzle/0444_host_mcp_delegations.sql +129 -0
  56. package/drizzle/0445_host_mcp_turn_authorities.sql +145 -0
  57. package/drizzle/0446_host_mcp_causal_continuation.sql +126 -0
  58. package/drizzle/0447_host_mcp_task_authorities.sql +264 -0
  59. package/drizzle/0448_host_mcp_child_authority.sql +115 -0
  60. package/drizzle/0449_external_identity_link_lifecycle.sql +107 -0
  61. package/drizzle/0450_external_identity_link_work.sql +179 -0
  62. package/drizzle/0451_external_link_preview_and_permission_ceiling.sql +82 -0
  63. package/drizzle/0452_external_link_scheduled_origin.sql +34 -0
  64. package/drizzle/0453_host_mcp_native_owner.sql +35 -0
  65. package/drizzle/0454_connect_origin_authority.sql +42 -0
  66. package/drizzle/0455_external_link_inventory_labels.sql +44 -0
  67. package/drizzle/0456_social_connection_versions.sql +14 -0
  68. package/drizzle/0457_canonical_session_scope_subject.sql +46 -0
  69. package/drizzle/0458_skill_review_wire_compatibility.sql +36 -0
  70. package/package.json +6 -6
  71. package/src/connect-attempts.ts +317 -0
  72. package/src/connection-authority.ts +9 -0
  73. package/src/connection-setup-authority.ts +35 -0
  74. package/src/connection-token-resolver.ts +177 -48
  75. package/src/external-identities.ts +70 -0
  76. package/src/external-identity-links.ts +364 -0
  77. package/src/external-link-work.ts +228 -0
  78. package/src/host-mcp-binding-match.ts +33 -0
  79. package/src/host-mcp-bindings.ts +741 -0
  80. package/src/host-mcp-task-authority.ts +383 -0
  81. package/src/host-selection-identity.ts +29 -0
  82. package/src/index.ts +398 -147
  83. package/src/live-session-attempt.ts +85 -0
  84. package/src/managed-human-provisioning.ts +6 -1
  85. package/src/memory-domain.ts +6 -11
  86. package/src/provision-roles.ts +3 -0
  87. package/src/runtime-posture.ts +47 -0
  88. package/src/scheduled-task-revision-authority.ts +30 -0
  89. package/src/schema.ts +228 -3
  90. package/src/session-control.ts +14 -0
  91. package/src/session-queue-commands.ts +11 -0
  92. package/src/session-tenancy.ts +7 -0
  93. package/src/workspace-authority.ts +5 -5
  94. package/dist/chunk-6GAPAYZR.js.map +0 -1
  95. package/dist/chunk-6IQ4X35Y.js.map +0 -1
  96. package/dist/chunk-7WX3R3YL.js.map +0 -1
  97. package/dist/chunk-S4Y42VSX.js.map +0 -1
  98. /package/dist/{chunk-SNLIKELL.js.map → chunk-IHADTE7C.js.map} +0 -0
  99. /package/dist/{chunk-MGVOYVPS.js.map → chunk-NCSPNCQK.js.map} +0 -0
  100. /package/dist/{chunk-B2KR3TEM.js.map → chunk-SGJDIF2D.js.map} +0 -0
  101. /package/dist/{chunk-FPTI4NP4.js.map → chunk-YGBOVXC7.js.map} +0 -0
  102. /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: [schema.workspaces.externalSource, schema.workspaces.externalId],
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();
@@ -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" | "session" | "off";
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
- endUserSubjectId: string | null;
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(endUser: { source: string; id: string }): string {
302
+ export function endUserMemorySubjectId(scopeSubjectId: { source: string; id: string }): string {
303
303
  const digest = createHash("sha256")
304
- .update(JSON.stringify([endUser.source, endUser.id]), "utf8")
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.endUserSubjectId) {
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.endUserSubjectId };
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
 
@@ -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)",
@@ -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(table.externalSource, table.externalId),
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").notNull(),
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
- endUser: { source: string; id: string } | null;
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" }),
@@ -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;