@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
package/src/index.ts CHANGED
@@ -39,6 +39,33 @@ import {
39
39
  import { releaseOrphanedSkillHeads, type SkillSourceReleaseReceipt } from "./skill-source-release";
40
40
  import type { SkillActor, SkillWriteReceipt } from "@opengeni/contracts";
41
41
  import { isDeepStrictEqual } from "node:util";
42
+ import {
43
+ normalizeHostCreateSelection,
44
+ metadataWithHostCreateSelection,
45
+ hostCreateSelectionFromMetadata,
46
+ type HostMcpCreateSelection,
47
+ } from "./host-selection-identity";
48
+ export * from "./connect-attempts";
49
+ export * from "./external-identities";
50
+ export * from "./external-identity-links";
51
+ export * from "./connection-setup-authority";
52
+ export * from "./external-link-work";
53
+ import {
54
+ inheritExternalLinkTurnAuthority,
55
+ captureScheduledExternalLinkTurnAuthority,
56
+ cloneExternalLinkTaskAuthority,
57
+ getExternalLinkTurnAuthorization,
58
+ } from "./external-link-work";
59
+ export * from "./host-mcp-bindings";
60
+ import {
61
+ inheritCausalHostMcpTurnAuthorities,
62
+ inheritChildHostMcpTurnAuthorities,
63
+ } from "./host-mcp-bindings";
64
+ export * from "./host-mcp-task-authority";
65
+ import {
66
+ cloneHostMcpTaskAuthorities,
67
+ captureScheduledHostMcpTurnAuthorities,
68
+ } from "./host-mcp-task-authority";
42
69
  import {
43
70
  SESSION_GOAL_PROGRESS_MAX_BYTES,
44
71
  SESSION_GOAL_RATIONALE_MAX_BYTES,
@@ -150,7 +177,7 @@ import type {
150
177
  SessionAgentAccess,
151
178
  SessionAgentAccessViewer,
152
179
  SessionAuthorizationListScope,
153
- SessionEndUser,
180
+ SessionScopeSubjectId,
154
181
  SessionMemoryScope,
155
182
  SessionListResponse,
156
183
  SessionTenancyPublicProjection,
@@ -296,6 +323,7 @@ import {
296
323
  HostUsageExportBatch as HostUsageExportBatchContract,
297
324
  OPENGENI_HOST_EXPORT_SCHEMA_REVISION,
298
325
  HumanInputQuestion as HumanInputQuestionContract,
326
+ canonicalSkillReviewQuestion,
299
327
  SubmitHumanInputResponseRequest,
300
328
  TurnExecutionPolicyV1,
301
329
  CodexCredentialPolicySnapshotV1,
@@ -349,6 +377,7 @@ import {
349
377
  type SQLWrapper,
350
378
  } from "drizzle-orm";
351
379
  import type { PgTransactionConfig } from "drizzle-orm/pg-core";
380
+ import { getLiveSessionAttemptTurn } from "./live-session-attempt";
352
381
  import {
353
382
  creatorColumns,
354
383
  frozenInitiatorForCommandActor,
@@ -488,7 +517,6 @@ import {
488
517
  renderWorkspaceMemoryBlock,
489
518
  memoryTextForStorage,
490
519
  WORKSPACE_MEMORY_BLOCK_EMPTY,
491
- endUserMemorySubjectId,
492
520
  memoryReadScopesForAgentScope,
493
521
  memoryWriteScopeForAgentScope,
494
522
  normalizeMemoryScope,
@@ -644,6 +672,7 @@ export {
644
672
  type UserProfileLookup,
645
673
  } from "./database";
646
674
  export { withSessionRlsActorContext } from "./database";
675
+ export { normalizedHostCredentialHeaders } from "./connection-token-resolver";
647
676
  import {
648
677
  buildCodexTokenResolver as buildCodexTokenResolverCore,
649
678
  fetchCodexRateLimitResetCreditsForAccount as fetchCodexRateLimitResetCreditsForAccountCore,
@@ -1495,6 +1524,7 @@ export async function bootstrapWorkspace(
1495
1524
  and(
1496
1525
  eq(schema.workspaces.externalSource, input.workspaceExternalSource),
1497
1526
  eq(schema.workspaces.externalId, input.workspaceExternalId),
1527
+ eq(schema.workspaces.accountId, account.id),
1498
1528
  ),
1499
1529
  )
1500
1530
  .limit(1);
@@ -1508,7 +1538,11 @@ export async function bootstrapWorkspace(
1508
1538
  externalId: input.workspaceExternalId,
1509
1539
  })
1510
1540
  .onConflictDoUpdate({
1511
- target: [schema.workspaces.externalSource, schema.workspaces.externalId],
1541
+ target: [
1542
+ schema.workspaces.accountId,
1543
+ schema.workspaces.externalSource,
1544
+ schema.workspaces.externalId,
1545
+ ],
1512
1546
  set: {
1513
1547
  name: input.workspaceName,
1514
1548
  updatedAt: new Date(),
@@ -1976,6 +2010,7 @@ export async function ensureManagedAccessForUserWithOrganizationMemberships(
1976
2010
  and(
1977
2011
  eq(schema.workspaces.externalSource, "better-auth:user"),
1978
2012
  eq(schema.workspaces.externalId, `${input.userId}:default`),
2013
+ eq(schema.workspaces.accountId, account.id),
1979
2014
  ),
1980
2015
  )
1981
2016
  .limit(1);
@@ -1990,7 +2025,11 @@ export async function ensureManagedAccessForUserWithOrganizationMemberships(
1990
2025
  externalId: `${input.userId}:default`,
1991
2026
  })
1992
2027
  .onConflictDoUpdate({
1993
- target: [schema.workspaces.externalSource, schema.workspaces.externalId],
2028
+ target: [
2029
+ schema.workspaces.accountId,
2030
+ schema.workspaces.externalSource,
2031
+ schema.workspaces.externalId,
2032
+ ],
1994
2033
  // Preserve a later administrator rename on legacy fallback workspaces.
1995
2034
  set: { updatedAt: new Date() },
1996
2035
  })
@@ -2403,6 +2442,7 @@ export class WorkspaceExternalIdentityConflictError extends Error {
2403
2442
  export async function findWorkspaceByExternalIdentity(
2404
2443
  db: Database,
2405
2444
  input: {
2445
+ accountId: string;
2406
2446
  externalSource: string;
2407
2447
  externalId: string;
2408
2448
  },
@@ -2414,6 +2454,7 @@ export async function findWorkspaceByExternalIdentity(
2414
2454
  and(
2415
2455
  eq(schema.workspaces.externalSource, input.externalSource),
2416
2456
  eq(schema.workspaces.externalId, input.externalId),
2457
+ eq(schema.workspaces.accountId, input.accountId),
2417
2458
  ),
2418
2459
  )
2419
2460
  .limit(1);
@@ -2461,7 +2502,7 @@ async function assertWorkspaceCapacity(
2461
2502
 
2462
2503
  /**
2463
2504
  * Create an organization workspace exactly once for a stable external tenant
2464
- * identity. The global unique index is the concurrency arbiter. A replay never
2505
+ * identity. The organization-scoped unique index is the concurrency arbiter. A replay never
2465
2506
  * mutates presentation fields supplied by the original create.
2466
2507
  */
2467
2508
  export async function ensureWorkspaceByExternalIdentity(
@@ -2488,6 +2529,7 @@ export async function ensureWorkspaceByExternalIdentity(
2488
2529
  and(
2489
2530
  eq(schema.workspaces.externalSource, input.externalSource),
2490
2531
  eq(schema.workspaces.externalId, input.externalId),
2532
+ eq(schema.workspaces.accountId, input.accountId),
2491
2533
  ),
2492
2534
  )
2493
2535
  .limit(1);
@@ -2522,7 +2564,11 @@ export async function ensureWorkspaceByExternalIdentity(
2522
2564
  agentInstructions: input.agentInstructions ?? null,
2523
2565
  })
2524
2566
  .onConflictDoNothing({
2525
- target: [schema.workspaces.externalSource, schema.workspaces.externalId],
2567
+ target: [
2568
+ schema.workspaces.accountId,
2569
+ schema.workspaces.externalSource,
2570
+ schema.workspaces.externalId,
2571
+ ],
2526
2572
  })
2527
2573
  .returning();
2528
2574
 
@@ -2558,6 +2604,7 @@ export async function ensureWorkspaceByExternalIdentity(
2558
2604
  and(
2559
2605
  eq(schema.workspaces.externalSource, input.externalSource),
2560
2606
  eq(schema.workspaces.externalId, input.externalId),
2607
+ eq(schema.workspaces.accountId, input.accountId),
2561
2608
  ),
2562
2609
  )
2563
2610
  .limit(1);
@@ -5415,7 +5462,7 @@ export type AppendEventInput = {
5415
5462
  */
5416
5463
  export type ScheduledTaskCreatorSessionPolicy = {
5417
5464
  agentAccess: string | null;
5418
- endUser: { source: string; id: string } | null;
5465
+ scopeSubjectId: string | null;
5419
5466
  memoryScope: string | null;
5420
5467
  };
5421
5468
 
@@ -5458,6 +5505,8 @@ export type CreateScheduledTaskInput = {
5458
5505
  metadata: Record<string, unknown>;
5459
5506
  /** Trusted database-only admission seam. Throwing rolls the task creation back. */
5460
5507
  beforeCreateCommit?: (tx: Database) => Promise<void>;
5508
+ captureHostAuthority?: (tx: Database, task: ScheduledTask) => Promise<void>;
5509
+ captureLinkAuthority?: (tx: Database, task: ScheduledTask) => Promise<void>;
5461
5510
  };
5462
5511
 
5463
5512
  export type UpdateScheduledTaskInput = Partial<{
@@ -5482,6 +5531,8 @@ export type UpdateScheduledTaskInput = Partial<{
5482
5531
  authorityUpdatedByActor: AgentSessionCreationActor | null;
5483
5532
  /** Trusted database-only admission seam. Throwing rolls the task update back. */
5484
5533
  beforeUpdateCommit: (tx: Database) => Promise<void>;
5534
+ captureHostAuthority: (tx: Database, task: ScheduledTask) => Promise<void>;
5535
+ captureLinkAuthority: (tx: Database, task: ScheduledTask) => Promise<void>;
5485
5536
  }>;
5486
5537
 
5487
5538
  export type CreatePackInstallationInput = {
@@ -5566,6 +5617,7 @@ export type CreateSocialConnectionInput = {
5566
5617
  };
5567
5618
 
5568
5619
  export type UpsertSocialOAuthConnectionInput = {
5620
+ expectedConnection?: { id: string; version: number };
5569
5621
  accountId: string;
5570
5622
  workspaceId: string;
5571
5623
  subjectId?: string | null;
@@ -5682,6 +5734,8 @@ export type UpdateConnectionInput = {
5682
5734
  };
5683
5735
 
5684
5736
  export type PersistProviderOAuthConnectionInput = CreateConnectionInput & {
5737
+ /** Server continuation reauthorization; runs inside the credential transaction. */
5738
+ authorize?: (tx: Database) => Promise<void>;
5685
5739
  visibleToSubjectId: string;
5686
5740
  credentialRole: string;
5687
5741
  providerFamily: string;
@@ -10291,6 +10345,11 @@ export async function persistProviderOAuthConnection(
10291
10345
  return await scopedDb.transaction(async (txRaw) => {
10292
10346
  const tx = txRaw as unknown as Database;
10293
10347
  const ownerKey = input.subjectId ?? "workspace";
10348
+ if (input.authorize) {
10349
+ await input.authorize(tx);
10350
+ await setRlsContext(tx, { accountId: input.accountId, workspaceId: input.workspaceId });
10351
+ await setSubjectRlsContext(tx, input.subjectId ?? input.visibleToSubjectId ?? "");
10352
+ }
10294
10353
  if (input.requireLiveUserAuthority) {
10295
10354
  if (!input.subjectId) {
10296
10355
  throw new Error("Live user authority requires a subject-owned connection");
@@ -16274,6 +16333,40 @@ export async function upsertSocialOAuthConnection(
16274
16333
  { accountId: input.accountId, workspaceId: input.workspaceId },
16275
16334
  async (scopedDb) => {
16276
16335
  if (input.subjectId) await setSubjectRlsContext(scopedDb, input.subjectId);
16336
+ if (input.expectedConnection) {
16337
+ const [updated] = await scopedDb
16338
+ .update(schema.socialConnections)
16339
+ .set({
16340
+ accountHandle: input.accountHandle,
16341
+ accountName: input.accountName ?? null,
16342
+ status: "connected",
16343
+ scopes: input.scopes,
16344
+ credentialEncrypted: input.credentialEncrypted,
16345
+ tokenMetadata: input.tokenMetadata ?? {},
16346
+ updatedAt: new Date(),
16347
+ })
16348
+ .where(
16349
+ and(
16350
+ eq(schema.socialConnections.accountId, input.accountId),
16351
+ eq(schema.socialConnections.workspaceId, input.workspaceId),
16352
+ eq(schema.socialConnections.id, input.expectedConnection.id),
16353
+ eq(schema.socialConnections.version, input.expectedConnection.version),
16354
+ eq(schema.socialConnections.provider, input.provider),
16355
+ input.subjectId
16356
+ ? eq(schema.socialConnections.subjectId, input.subjectId)
16357
+ : isNull(schema.socialConnections.subjectId),
16358
+ input.externalAccountId
16359
+ ? eq(schema.socialConnections.externalAccountId, input.externalAccountId)
16360
+ : sql`false`,
16361
+ ),
16362
+ )
16363
+ .returning();
16364
+ if (!updated)
16365
+ throw new Error(
16366
+ "Social connection changed; authorize the same account again from current state",
16367
+ );
16368
+ return mapSocialConnection(updated);
16369
+ }
16277
16370
  const [row] = await scopedDb
16278
16371
  .insert(schema.socialConnections)
16279
16372
  .values({
@@ -16651,6 +16744,8 @@ export async function createScheduledTask(
16651
16744
  ${row.id}::uuid,
16652
16745
  ${row.authorityRevision}::bigint
16653
16746
  )`);
16747
+ await input.captureHostAuthority?.(scopedDb, mapScheduledTask(row));
16748
+ await input.captureLinkAuthority?.(scopedDb, mapScheduledTask(row));
16654
16749
  return mapScheduledTask(row);
16655
16750
  },
16656
16751
  );
@@ -16663,6 +16758,17 @@ export async function updateScheduledTask(
16663
16758
  input: UpdateScheduledTaskInput,
16664
16759
  ): Promise<ScheduledTask> {
16665
16760
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
16761
+ const [previousHostRevision] = await scopedDb
16762
+ .select({ authorityRevision: schema.scheduledTasks.authorityRevision })
16763
+ .from(schema.scheduledTasks)
16764
+ .where(
16765
+ and(
16766
+ eq(schema.scheduledTasks.workspaceId, workspaceId),
16767
+ eq(schema.scheduledTasks.id, taskId),
16768
+ ),
16769
+ )
16770
+ .for("update")
16771
+ .limit(1);
16666
16772
  if (
16667
16773
  input.refreshPersonalResourceAuthority &&
16668
16774
  input.clonePersonalResourceAuthorityFromRevision !== undefined
@@ -16772,7 +16878,26 @@ export async function updateScheduledTask(
16772
16878
  ${row.authorityRevision}::bigint
16773
16879
  )`);
16774
16880
  }
16775
- return mapScheduledTask(row);
16881
+ const mapped = mapScheduledTask(row);
16882
+ if (input.captureLinkAuthority) await input.captureLinkAuthority(scopedDb, mapped);
16883
+ else if (previousHostRevision)
16884
+ await cloneExternalLinkTaskAuthority(
16885
+ scopedDb,
16886
+ mapped,
16887
+ input.clonePersonalResourceAuthorityFromRevision ??
16888
+ input.cloneConnectionAuthorityFromRevision ??
16889
+ previousHostRevision.authorityRevision,
16890
+ );
16891
+ if (input.captureHostAuthority) await input.captureHostAuthority(scopedDb, mapped);
16892
+ else if (previousHostRevision)
16893
+ await cloneHostMcpTaskAuthorities(
16894
+ scopedDb,
16895
+ mapped,
16896
+ input.clonePersonalResourceAuthorityFromRevision ??
16897
+ input.cloneConnectionAuthorityFromRevision ??
16898
+ previousHostRevision.authorityRevision,
16899
+ );
16900
+ return mapped;
16776
16901
  });
16777
16902
  }
16778
16903
 
@@ -16973,8 +17098,11 @@ export async function getScheduledTaskCreatorPolicy(
16973
17098
  sessionPolicy: row.sessionPolicy
16974
17099
  ? {
16975
17100
  agentAccess: row.sessionPolicy.agentAccess ?? null,
16976
- endUser: row.sessionPolicy.endUser ?? null,
16977
- memoryScope: row.sessionPolicy.memoryScope ?? null,
17101
+ scopeSubjectId: row.sessionPolicy.scopeSubjectId ?? null,
17102
+ memoryScope:
17103
+ row.sessionPolicy.memoryScope === "session"
17104
+ ? "off"
17105
+ : (row.sessionPolicy.memoryScope ?? null),
16978
17106
  }
16979
17107
  : null,
16980
17108
  };
@@ -17722,6 +17850,19 @@ export async function materializeScheduledTaskReusableSessionFromRun(
17722
17850
  if (!row) {
17723
17851
  throw new Error("scheduled reusable-session materialization returned no revision");
17724
17852
  }
17853
+ const materializedTask = await getScheduledTask(scopedDb, input.workspaceId, input.taskId);
17854
+ if (!materializedTask || materializedTask.authorityRevision !== Number(row.authorityRevision))
17855
+ throw new Error("scheduled host materialization revision changed");
17856
+ await cloneHostMcpTaskAuthorities(
17857
+ scopedDb,
17858
+ materializedTask,
17859
+ input.sourceTaskAuthorityRevision,
17860
+ );
17861
+ await cloneExternalLinkTaskAuthority(
17862
+ scopedDb,
17863
+ materializedTask,
17864
+ input.sourceTaskAuthorityRevision,
17865
+ );
17725
17866
  return Number(row.authorityRevision);
17726
17867
  },
17727
17868
  );
@@ -17861,38 +18002,7 @@ export async function getScheduledTaskRevisionAuthoritySubject(
17861
18002
  );
17862
18003
  }
17863
18004
 
17864
- export async function getScheduledTaskRevisionAuthority(
17865
- db: Database,
17866
- input: {
17867
- accountId: string;
17868
- workspaceId: string;
17869
- taskId: string;
17870
- taskAuthorityRevision: number;
17871
- },
17872
- ): Promise<{
17873
- subjectId: string;
17874
- organizationMembershipId: string;
17875
- membershipAuthorizationRevision: number;
17876
- } | null> {
17877
- return await withRlsContext(
17878
- db,
17879
- { accountId: input.accountId, workspaceId: input.workspaceId },
17880
- async (scopedDb) => {
17881
- const [row] = await rawRows<{ authority: unknown }>(
17882
- scopedDb,
17883
- sql`select scheduled_task_revision_authority_snapshot(
17884
- ${input.accountId}::uuid,
17885
- ${input.workspaceId}::uuid,
17886
- ${input.taskId}::uuid,
17887
- ${input.taskAuthorityRevision}::bigint
17888
- ) as authority`,
17889
- );
17890
- const authority = row?.authority;
17891
- if (!authority) return null;
17892
- return ScheduledTaskRunAcceptedExecution.shape.causalHumanAuthority.parse(authority);
17893
- },
17894
- );
17895
- }
18005
+ export { getScheduledTaskRevisionAuthority } from "./scheduled-task-revision-authority";
17896
18006
 
17897
18007
  /** Failure settlement must not rewrite a source already committed as dispatched. */
17898
18008
  export async function markScheduledTaskRunFailedIfQueued(
@@ -22606,6 +22716,17 @@ async function lockOrganizationCodexSubscriptionSources(
22606
22716
  order by workspace_id
22607
22717
  `);
22608
22718
  const workspaceIds = rows.map((row: { workspace_id: string }) => row.workspace_id);
22719
+ for (const workspaceId of workspaceIds) {
22720
+ // Credential deletion clears session pins/last-used references through FK
22721
+ // SET NULL, including references in Personal or no-longer-inheriting
22722
+ // workspaces. Fence the complete authorized inventory before any source,
22723
+ // pool, or credential lock; the FK's session writes cannot acquire this
22724
+ // prefix after taking their row locks. Keep the UUID order across both
22725
+ // passes, matching ordinary workspace writers' tenancy -> source order.
22726
+ await scopedDb.execute(
22727
+ sql`select pg_advisory_xact_lock_shared(hashtextextended(${`session-tenancy:${workspaceId}`}, 0))`,
22728
+ );
22729
+ }
22609
22730
  for (const workspaceId of workspaceIds) {
22610
22731
  // Organization credential mutations can change automatic routing in every
22611
22732
  // inheriting workspace. Acquire all workspace source locks before the
@@ -31444,11 +31565,16 @@ async function setScheduledTaskAuthorityRlsContext(
31444
31565
  ): Promise<void> {
31445
31566
  const subjectId = frozen.initiator.subjectId.trim();
31446
31567
  if (!subjectId) throw new Error("scheduled task authority writer has no subject");
31568
+ // frozenSessionCreatorForInsert verified the agent attempt before reading
31569
+ // this separate causal-human field. Keep service audit attribution intact;
31570
+ // never substitute the session creator or parse a caller's provenance JSON.
31571
+ const causalHumanSubjectId =
31572
+ frozen.initiatingHumanSubjectId ?? (frozen.initiator.kind === "subject" ? subjectId : "");
31447
31573
  await tx.execute(sql`select
31448
31574
  set_config('opengeni.subject_id', ${subjectId}, true),
31449
31575
  set_config(
31450
31576
  'opengeni.initiating_human_subject_id',
31451
- ${frozen.initiator.kind === "subject" ? subjectId : ""},
31577
+ ${causalHumanSubjectId},
31452
31578
  true
31453
31579
  )`);
31454
31580
  }
@@ -31485,13 +31611,15 @@ export type SessionCreateInput = {
31485
31611
  /** Agent-to-agent reach (migration 0427); omitted means the workspace default. */
31486
31612
  agentAccess?: SessionAgentAccess;
31487
31613
  /** Opaque end-user label; omitted or null means none. */
31488
- endUser?: SessionEndUser | null;
31614
+ scopeSubjectId?: SessionScopeSubjectId | null;
31489
31615
  /** Typed Memory selector (migration 0427); omitted means the workspace layer. */
31490
31616
  memoryScope?: SessionMemoryScope;
31491
31617
  parentSessionId?: string | null;
31492
31618
  createIdempotencyKey?: string | null;
31493
31619
  /** Exact explicit installed-Skill selection used for keyed-create replay. */
31494
31620
  selectedInstalledSkillIds?: string[];
31621
+ /** Backend-only replay identity; does not admit host credential authority. */
31622
+ selectedHostMcpDelegations?: HostMcpCreateSelection[];
31495
31623
  sandboxGroupId?: string | null;
31496
31624
  sandboxOs?: SandboxOs;
31497
31625
  /** Exact accepted generated-session compaction policy; internal lifecycle callers only. */
@@ -31785,10 +31913,11 @@ type SessionCreateReplayIdentity = {
31785
31913
  visibility?: "user_private" | "workspace_shared";
31786
31914
  variableSetIds: string[];
31787
31915
  selectedInstalledSkillIds: string[];
31916
+ selectedHostMcpDelegations?: HostMcpCreateSelection[];
31788
31917
  initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
31789
31918
  /** Access/memory scope of the retrying request; omitted means the defaults. */
31790
31919
  agentAccess?: SessionAgentAccess;
31791
- endUser?: SessionEndUser | null;
31920
+ scopeSubjectId?: SessionScopeSubjectId | null;
31792
31921
  memoryScope?: SessionMemoryScope;
31793
31922
  };
31794
31923
 
@@ -31823,6 +31952,12 @@ function assertSessionCreateReplayIdentity(
31823
31952
  existing: typeof schema.sessions.$inferSelect,
31824
31953
  input: SessionCreateReplayIdentity,
31825
31954
  ): void {
31955
+ if (
31956
+ stableJson(hostCreateSelectionFromMetadata(existing.metadata)) !==
31957
+ stableJson(normalizeHostCreateSelection(input.selectedHostMcpDelegations))
31958
+ ) {
31959
+ throw new SessionCreateIdempotencyConflictError();
31960
+ }
31826
31961
  if (
31827
31962
  stableJson(bundledSkillSelectionFromMetadata(existing.metadata) ?? null) !==
31828
31963
  stableJson(input.bundledSkillIds ? [...input.bundledSkillIds].sort() : null)
@@ -31856,8 +31991,7 @@ function assertSessionCreateReplayIdentity(
31856
31991
  if (
31857
31992
  existing.agentAccess !== (input.agentAccess ?? "workspace") ||
31858
31993
  existing.memoryScope !== (input.memoryScope ?? "workspace") ||
31859
- (existing.endUserSource ?? null) !== (input.endUser?.source ?? null) ||
31860
- (existing.endUserId ?? null) !== (input.endUser?.id ?? null)
31994
+ (input.scopeSubjectId !== undefined && sessionEndUserFromRow(existing) !== input.scopeSubjectId)
31861
31995
  ) {
31862
31996
  throw new SessionCreateIdempotencyConflictError();
31863
31997
  }
@@ -31954,7 +32088,10 @@ async function createSessionInTransaction(
31954
32088
  const variableSetId = variableSetIds.at(-1) ?? null;
31955
32089
  const selectedInstalledSkillIds = input.selectedInstalledSkillIds ?? [];
31956
32090
  const sessionMetadata = metadataWithSelectedInstalledSkillCreateIdentity(
31957
- withBundledSkillSelectionMetadata(input.metadata, input.bundledSkillIds),
32091
+ metadataWithHostCreateSelection(
32092
+ withBundledSkillSelectionMetadata(input.metadata, input.bundledSkillIds),
32093
+ input.selectedHostMcpDelegations,
32094
+ ),
31958
32095
  selectedInstalledSkillIds,
31959
32096
  );
31960
32097
  const createIdempotencyKey = input.createIdempotencyKey ?? null;
@@ -32009,10 +32146,11 @@ async function createSessionInTransaction(
32009
32146
  visibility: createRequestedVisibility,
32010
32147
  variableSetIds,
32011
32148
  selectedInstalledSkillIds: input.selectedInstalledSkillIds ?? [],
32149
+ selectedHostMcpDelegations: input.selectedHostMcpDelegations ?? [],
32012
32150
  initialPersonalResourceAttachmentIntent:
32013
32151
  input.initialPersonalResourceAttachmentIntent ?? null,
32014
32152
  ...(input.agentAccess ? { agentAccess: input.agentAccess } : {}),
32015
- ...(input.endUser !== undefined ? { endUser: input.endUser } : {}),
32153
+ ...(input.scopeSubjectId !== undefined ? { scopeSubjectId: input.scopeSubjectId } : {}),
32016
32154
  ...(input.memoryScope ? { memoryScope: input.memoryScope } : {}),
32017
32155
  });
32018
32156
  const grouped = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
@@ -32169,8 +32307,17 @@ async function createSessionInTransaction(
32169
32307
  instructions: input.instructions ?? null,
32170
32308
  policyRole: input.policyRole ?? null,
32171
32309
  agentAccess: input.agentAccess ?? "workspace",
32172
- endUserSource: input.endUser?.source ?? null,
32173
- endUserId: input.endUser?.id ?? null,
32310
+ scopeSubjectId:
32311
+ input.scopeSubjectId !== undefined
32312
+ ? input.scopeSubjectId
32313
+ : frozenCreator.initiator.kind === "subject" &&
32314
+ /^(?:user:|external_user:)/u.test(frozenCreator.initiator.subjectId)
32315
+ ? frozenCreator.initiator.subjectId
32316
+ : null,
32317
+ // Retained columns are historical only. Canonical scope is derived
32318
+ // from the creator resolved under the admission locks above.
32319
+ endUserSource: null,
32320
+ endUserId: null,
32174
32321
  memoryScope: input.memoryScope ?? "workspace",
32175
32322
  parentSessionId: input.parentSessionId ?? null,
32176
32323
  parentTurnId,
@@ -32223,10 +32370,11 @@ async function createSessionInTransaction(
32223
32370
  visibility: createRequestedVisibility,
32224
32371
  variableSetIds,
32225
32372
  selectedInstalledSkillIds: input.selectedInstalledSkillIds ?? [],
32373
+ selectedHostMcpDelegations: input.selectedHostMcpDelegations ?? [],
32226
32374
  initialPersonalResourceAttachmentIntent:
32227
32375
  input.initialPersonalResourceAttachmentIntent ?? null,
32228
32376
  ...(input.agentAccess ? { agentAccess: input.agentAccess } : {}),
32229
- ...(input.endUser !== undefined ? { endUser: input.endUser } : {}),
32377
+ ...(input.scopeSubjectId !== undefined ? { scopeSubjectId: input.scopeSubjectId } : {}),
32230
32378
  ...(input.memoryScope ? { memoryScope: input.memoryScope } : {}),
32231
32379
  });
32232
32380
  const grouped = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
@@ -32418,6 +32566,7 @@ export async function getInitializedSessionCreateReplay(
32418
32566
  visibility?: "user_private" | "workspace_shared";
32419
32567
  variableSetIds: string[];
32420
32568
  selectedInstalledSkillIds: string[];
32569
+ selectedHostMcpDelegations?: HostMcpCreateSelection[];
32421
32570
  initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
32422
32571
  deferInitialTurn?: boolean;
32423
32572
  },
@@ -32876,7 +33025,7 @@ export type SessionAccessProjection = {
32876
33025
  sessionId: string;
32877
33026
  rootSessionId: string;
32878
33027
  agentAccess: SessionAgentAccess;
32879
- endUser: SessionEndUser | null;
33028
+ scopeSubjectId: SessionScopeSubjectId | null;
32880
33029
  memoryScope: SessionMemoryScope;
32881
33030
  };
32882
33031
 
@@ -32887,7 +33036,7 @@ export type SessionAuthorityProjection = SessionAccessProjection & {
32887
33036
 
32888
33037
  type SessionAccessRow = Pick<
32889
33038
  typeof schema.sessions.$inferSelect,
32890
- "agentAccess" | "endUserSource" | "endUserId" | "memoryScope"
33039
+ "agentAccess" | "scopeSubjectId" | "memoryScope"
32891
33040
  >;
32892
33041
 
32893
33042
  function sessionAgentAccessFromRow(row: Pick<SessionAccessRow, "agentAccess">): SessionAgentAccess {
@@ -32897,20 +33046,18 @@ function sessionAgentAccessFromRow(row: Pick<SessionAccessRow, "agentAccess">):
32897
33046
  }
32898
33047
 
32899
33048
  function sessionEndUserFromRow(
32900
- row: Pick<SessionAccessRow, "endUserSource" | "endUserId">,
32901
- ): SessionEndUser | null {
32902
- return row.endUserSource !== null &&
32903
- row.endUserSource !== undefined &&
32904
- row.endUserId !== null &&
32905
- row.endUserId !== undefined
32906
- ? { source: row.endUserSource, id: row.endUserId }
33049
+ row: Pick<SessionAccessRow, "scopeSubjectId">,
33050
+ ): SessionScopeSubjectId | null {
33051
+ return typeof row.scopeSubjectId === "string" &&
33052
+ /^(?:user:|external_user:)/u.test(row.scopeSubjectId)
33053
+ ? row.scopeSubjectId
32907
33054
  : null;
32908
33055
  }
32909
33056
 
32910
33057
  function sessionMemoryScopeFromRow(row: Pick<SessionAccessRow, "memoryScope">): SessionMemoryScope {
32911
- return row.memoryScope === "user" || row.memoryScope === "session" || row.memoryScope === "off"
32912
- ? row.memoryScope
32913
- : "workspace";
33058
+ // Legacy task-local Memory is no longer active. Never widen it to workspace.
33059
+ if (row.memoryScope === "session") return "off";
33060
+ return row.memoryScope === "user" || row.memoryScope === "off" ? row.memoryScope : "workspace";
32914
33061
  }
32915
33062
 
32916
33063
  export async function getSessionAuthorityProjection(
@@ -32926,8 +33073,7 @@ export async function getSessionAuthorityProjection(
32926
33073
  visibility: schema.sessions.visibility,
32927
33074
  ownerSubjectId: schema.sessions.ownerSubjectId,
32928
33075
  agentAccess: schema.sessions.agentAccess,
32929
- endUserSource: schema.sessions.endUserSource,
32930
- endUserId: schema.sessions.endUserId,
33076
+ scopeSubjectId: schema.sessions.scopeSubjectId,
32931
33077
  memoryScope: schema.sessions.memoryScope,
32932
33078
  })
32933
33079
  .from(schema.sessions)
@@ -32940,7 +33086,7 @@ export async function getSessionAuthorityProjection(
32940
33086
  visibility: row.visibility as SessionAuthorityProjection["visibility"],
32941
33087
  ownerSubjectId: row.ownerSubjectId ?? null,
32942
33088
  agentAccess: sessionAgentAccessFromRow(row),
32943
- endUser: sessionEndUserFromRow(row),
33089
+ scopeSubjectId: sessionEndUserFromRow(row),
32944
33090
  memoryScope: sessionMemoryScopeFromRow(row),
32945
33091
  };
32946
33092
  });
@@ -32958,33 +33104,52 @@ export async function getSessionAccessProjection(
32958
33104
  sessionId: authority.sessionId,
32959
33105
  rootSessionId: authority.rootSessionId,
32960
33106
  agentAccess: authority.agentAccess,
32961
- endUser: authority.endUser,
33107
+ scopeSubjectId: authority.scopeSubjectId,
32962
33108
  memoryScope: authority.memoryScope,
32963
33109
  };
32964
33110
  }
32965
33111
 
32966
33112
  /**
32967
33113
  * The typed Memory selector an agent on this session reads and writes. The
32968
- * end-user subject is the opaque `end_user:v1:<tuple hash>` label and the
32969
- * session selector is the lineage root, so one tree shares one private layer.
33114
+ * user subject comes from the verified active execution, not a conversation
33115
+ * label or its original creator. Task notes own temporary tree-local data.
32970
33116
  * Null when the session does not exist in the workspace.
32971
33117
  */
32972
33118
  export async function resolveSessionMemoryAgentScope(
32973
33119
  db: Database,
32974
33120
  workspaceId: string,
32975
33121
  sessionId: string,
33122
+ caller?: { turnId?: unknown; attemptId?: unknown; executionGeneration?: unknown },
32976
33123
  ): Promise<MemoryAgentScope | null> {
32977
33124
  const access = await getSessionAccessProjection(db, workspaceId, sessionId);
32978
33125
  if (!access) return null;
33126
+ if (access.memoryScope === "user") {
33127
+ const turn = await getActiveSessionTurnForExecution(db, workspaceId, sessionId);
33128
+ const subjectId =
33129
+ turn?.initiatingHumanSubjectId ??
33130
+ (turn?.initiator.kind === "subject" ? turn.initiator.subjectId : null);
33131
+ if (
33132
+ !turn ||
33133
+ !caller ||
33134
+ turn.id !== caller.turnId ||
33135
+ turn.activeAttemptId !== caller.attemptId ||
33136
+ turn.executionGeneration !== caller.executionGeneration ||
33137
+ !subjectId ||
33138
+ !/^(?:user:|external_user:)/u.test(subjectId)
33139
+ ) {
33140
+ return { mode: "off", userSubjectId: null, rootSessionId: access.rootSessionId };
33141
+ }
33142
+ return memoryAgentScopeForSessionAccess({ ...access, scopeSubjectId: subjectId });
33143
+ }
32979
33144
  return memoryAgentScopeForSessionAccess(access);
32980
33145
  }
32981
33146
 
32982
33147
  export function memoryAgentScopeForSessionAccess(
32983
- access: Pick<SessionAccessProjection, "rootSessionId" | "endUser" | "memoryScope">,
33148
+ access: Pick<SessionAccessProjection, "rootSessionId" | "scopeSubjectId" | "memoryScope">,
32984
33149
  ): MemoryAgentScope {
32985
33150
  return {
32986
33151
  mode: access.memoryScope,
32987
- endUserSubjectId: access.endUser ? endUserMemorySubjectId(access.endUser) : null,
33152
+ userSubjectId: access.scopeSubjectId,
32988
33153
  rootSessionId: access.rootSessionId,
32989
33154
  };
32990
33155
  }
@@ -33392,7 +33557,7 @@ export type SessionListFilterOptions = {
33392
33557
  /** Exclusive creation upper bound. */
33393
33558
  createdBefore?: Date;
33394
33559
  /** Exact opaque end-user label pair (both parts). */
33395
- endUser?: SessionEndUser;
33560
+ scopeSubjectId?: SessionScopeSubjectId;
33396
33561
  };
33397
33562
 
33398
33563
  export type ListSessionsForSubjectOptions = ListSessionsOptions &
@@ -33423,13 +33588,13 @@ export type ListSessionsForSubjectOptions = ListSessionsOptions &
33423
33588
  *
33424
33589
  * **This flag is the authorization.** The resolver it gates is not: it answers
33425
33590
  * "does subject X hold authority here" and establishes nothing about who the
33426
- * caller is. The exception is for "the canonical managed-cookie (Better Auth)
33427
- * session" only "Bearer/delegated principals, API keys, and account or
33428
- * organization administrators receive no personal-workspace access through that
33429
- * exception" — and a host-signed delegated bearer chooses its own `subjectId`,
33591
+ * caller is. The exception requires verified native-cookie or dedicated
33592
+ * external owning-user provenance. Unscoped service keys and account or
33593
+ * organization administrators receive no Personal access through it.
33594
+ * A host-signed delegated bearer chooses its own `subjectId`,
33430
33595
  * `principalKind`, `metadata.delegated`, and `serviceInitiator` claims, so no
33431
33596
  * inspection of the grant can carry that distinction. Set this ONLY from
33432
- * `AccessGrantAuthorization.canonicalManagedHumanSession` (`@opengeni/core`),
33597
+ * `hasVerifiedOwningUserAuthorization` (`@opengeni/core`),
33433
33598
  * which reflects HOW the request authenticated. Omitted/false keeps the
33434
33599
  * historical bare-membership fence exactly.
33435
33600
  */
@@ -34189,7 +34354,7 @@ function sessionFilters(
34189
34354
  | "updatedBefore"
34190
34355
  | "createdFrom"
34191
34356
  | "createdBefore"
34192
- | "endUser"
34357
+ | "scopeSubjectId"
34193
34358
  >,
34194
34359
  ): SQL[] {
34195
34360
  const filters: SQL[] = [
@@ -34258,43 +34423,30 @@ function sessionFilters(
34258
34423
  if (options.updatedBefore) filters.push(lt(schema.sessions.updatedAt, options.updatedBefore));
34259
34424
  if (options.createdFrom) filters.push(gte(schema.sessions.createdAt, options.createdFrom));
34260
34425
  if (options.createdBefore) filters.push(lt(schema.sessions.createdAt, options.createdBefore));
34261
- if (options.endUser) {
34262
- filters.push(
34263
- eq(schema.sessions.endUserSource, options.endUser.source),
34264
- eq(schema.sessions.endUserId, options.endUser.id),
34265
- );
34426
+ if (options.scopeSubjectId) {
34427
+ filters.push(eq(schema.sessions.scopeSubjectId, options.scopeSubjectId));
34266
34428
  }
34267
34429
  return filters;
34268
34430
  }
34269
34431
 
34270
34432
  /**
34271
34433
  * The agent-access predicate for one calling attempt (migration 0427). It
34272
- * mirrors the pairwise rule in the core seam exactly: a caller always keeps
34273
- * its own root tree; a `session` caller sees nothing else; a `user` caller
34274
- * additionally sees non-`session` sessions carrying its own end-user label; a
34275
- * `workspace` caller additionally sees every `workspace` session and the
34276
- * `user` sessions carrying its own label. A caller without a label can never
34277
- * match a labelled `user` session.
34434
+ * mirrors outgoing reach in the core seam: own tree, same canonical user, or
34435
+ * workspace. Target task scope never hides the conversation. This predicate
34436
+ * remains intersected with host scope and ordinary private-session visibility.
34278
34437
  */
34279
34438
  export function sessionAgentAccessViewerFilter(viewer: SessionAgentAccessViewer): SQL {
34280
34439
  const ownTree = eq(schema.sessions.rootSessionId, viewer.callerRootSessionId);
34281
- const sameEndUser = viewer.endUser
34282
- ? and(
34283
- eq(schema.sessions.endUserSource, viewer.endUser.source),
34284
- eq(schema.sessions.endUserId, viewer.endUser.id),
34285
- )!
34440
+ const sameUser = viewer.scopeSubjectId
34441
+ ? and(eq(schema.sessions.scopeSubjectId, viewer.scopeSubjectId))!
34286
34442
  : sql`false`;
34287
34443
  switch (viewer.agentAccess) {
34288
34444
  case "session":
34289
34445
  return ownTree;
34290
34446
  case "user":
34291
- return or(ownTree, and(ne(schema.sessions.agentAccess, "session"), sameEndUser)!)!;
34447
+ return or(ownTree, sameUser)!;
34292
34448
  case "workspace":
34293
- return or(
34294
- ownTree,
34295
- eq(schema.sessions.agentAccess, "workspace"),
34296
- and(eq(schema.sessions.agentAccess, "user"), sameEndUser)!,
34297
- )!;
34449
+ return sql`true`;
34298
34450
  }
34299
34451
  }
34300
34452
 
@@ -34431,7 +34583,7 @@ function sessionListFilterIdentity(options: SessionListFilterOptions): string {
34431
34583
  !options.updatedBefore &&
34432
34584
  !options.createdFrom &&
34433
34585
  !options.createdBefore &&
34434
- !options.endUser
34586
+ !options.scopeSubjectId
34435
34587
  ) {
34436
34588
  return "all";
34437
34589
  }
@@ -34443,7 +34595,7 @@ function sessionListFilterIdentity(options: SessionListFilterOptions): string {
34443
34595
  options.updatedBefore ? ["updatedBefore", options.updatedBefore.toISOString()] : null,
34444
34596
  options.createdFrom ? ["createdFrom", options.createdFrom.toISOString()] : null,
34445
34597
  options.createdBefore ? ["createdBefore", options.createdBefore.toISOString()] : null,
34446
- options.endUser ? ["endUser", options.endUser.source, options.endUser.id] : null,
34598
+ options.scopeSubjectId ? ["scopeSubjectId", options.scopeSubjectId] : null,
34447
34599
  ]);
34448
34600
  }
34449
34601
 
@@ -49787,6 +49939,15 @@ async function advanceWorkspaceGenerationForAuthorityOnce(
49787
49939
  await scopedDb.transaction(async (txRaw) => {
49788
49940
  const tx = txRaw as unknown as Database;
49789
49941
  const locked = await lockWorkspaceMutationAuthorityTx(tx, authority);
49942
+ // Keep the admission UPDATE and its rejection diagnostic on one lease
49943
+ // state. A concurrent capture release between these READ COMMITTED
49944
+ // statements otherwise turns a recoverable capture wait into a false
49945
+ // lease_fenced error. Take this after the canonical actor/attempt prefix.
49946
+ await tx.execute(sql`select id from sandbox_leases
49947
+ where account_id = ${locked.accountId}
49948
+ and workspace_id = ${locked.workspaceId}
49949
+ and sandbox_group_id = ${locked.sandboxGroupId}
49950
+ for update`);
49790
49951
  const rows = await tx.execute<{
49791
49952
  id: string;
49792
49953
  lease_id: string;
@@ -54451,6 +54612,21 @@ async function commitWorkspaceCaptureRevision(
54451
54612
  return null;
54452
54613
  }
54453
54614
 
54615
+ // Capture yields when new work is accepted. Its last database call can
54616
+ // still be in flight when that cancellation arrives, so serialize this
54617
+ // fence with enqueue/claim on the session row and refuse older reviews.
54618
+ // Position is priority order, not chronology.
54619
+ const successor = await tx.execute<{ id: string }>(sql`
54620
+ select id from session_turns
54621
+ where workspace_id = ${input.workspaceId}
54622
+ and session_id = ${input.sessionId}
54623
+ and id <> ${input.turnId}
54624
+ and (created_at >= (select created_at from session_turns where id = ${input.turnId})
54625
+ or status in ('queued', 'running'))
54626
+ limit 1
54627
+ `);
54628
+ if (successor.length > 0) return null;
54629
+
54454
54630
  const capturedAt = input.capturedAt ?? new Date();
54455
54631
  const rows = await tx.execute<{ revision: number | string }>(sql`
54456
54632
  insert into workspace_captures
@@ -62258,6 +62434,10 @@ export type InitializeSessionStartInput = {
62258
62434
  /** Trusted create-session policy. Omitted only by legacy low-level callers. */
62259
62435
  turnExecutionPolicy?: TurnExecutionPolicyV1;
62260
62436
  createdEventPayload: Record<string, unknown>;
62437
+ /** Trusted backend-only capture for a newly inserted initial turn. Runs under
62438
+ * the canonical activity transaction; failure rolls back events and turn.
62439
+ * Never invoked on replay or deferred starts; not caller JSON authority. */
62440
+ captureInitialTurnAuthority?: (tx: Database, turnId: string) => Promise<void>;
62261
62441
  /** Sensitive-safe semantic title supplied by the trusted agent-child create
62262
62442
  * path. It is committed with the initial timeline, never as a bare row edit. */
62263
62443
  initialAutomaticTitle?: string | null;
@@ -62314,6 +62494,8 @@ export async function initializeSessionStartAtomically(
62314
62494
  db: Database,
62315
62495
  input: InitializeSessionStartInput,
62316
62496
  ): Promise<InitializeSessionStartResult> {
62497
+ if (input.deferInitialTurn && input.captureInitialTurnAuthority)
62498
+ throw new Error("Deferred session starts cannot capture initial turn authority");
62317
62499
  return await withSessionActivityRlsContext(
62318
62500
  db,
62319
62501
  { accountId: input.accountId, workspaceId: input.workspaceId },
@@ -62844,6 +63026,30 @@ export async function initializeSessionStartAtomically(
62844
63026
  });
62845
63027
 
62846
63028
  const initialPersonalResourceIntent = session.initialPersonalResourceAttachmentIntent;
63029
+ if (insertedTurn && input.captureInitialTurnAuthority)
63030
+ await input.captureInitialTurnAuthority(tx as unknown as Database, turn.id);
63031
+ if (insertedTurn && session.parentSessionId && session.parentTurnId)
63032
+ await inheritExternalLinkTurnAuthority(tx as unknown as Database, {
63033
+ accountId: session.accountId,
63034
+ workspaceId: input.workspaceId,
63035
+ sessionId: session.id,
63036
+ turnId: turn.id,
63037
+ sourceTurnId: session.parentTurnId,
63038
+ kind: "child",
63039
+ });
63040
+ if (
63041
+ insertedTurn &&
63042
+ session.parentSessionId &&
63043
+ session.parentTurnId &&
63044
+ turn.initiatingHumanSubjectId
63045
+ )
63046
+ await inheritChildHostMcpTurnAuthorities(tx as unknown as Database, {
63047
+ accountId: session.accountId,
63048
+ workspaceId: input.workspaceId,
63049
+ sessionId: session.id,
63050
+ turnId: turn.id,
63051
+ subjectId: turn.initiatingHumanSubjectId,
63052
+ });
62847
63053
  if (initialPersonalResourceIntent) {
62848
63054
  const attachmentInitiatingHumanSubjectId =
62849
63055
  turn.initiatingHumanSubjectId ??
@@ -65811,6 +66017,39 @@ export async function claimSessionWorkForAttempt(
65811
66017
  frozenGoalSnapshot,
65812
66018
  goalContinuationHistoryItem ?? scheduledOccurrenceHistoryItem,
65813
66019
  );
66020
+ if (scheduledTaskRunId) {
66021
+ await captureScheduledExternalLinkTurnAuthority(tx as unknown as Database, {
66022
+ accountId: session.accountId,
66023
+ workspaceId,
66024
+ sessionId,
66025
+ turnId: internalTurn.id,
66026
+ runId: scheduledTaskRunId,
66027
+ });
66028
+ await captureScheduledHostMcpTurnAuthorities(tx as unknown as Database, {
66029
+ accountId: session.accountId,
66030
+ workspaceId,
66031
+ sessionId,
66032
+ turnId: internalTurn.id,
66033
+ runId: scheduledTaskRunId,
66034
+ });
66035
+ } else if (causalHumanTurnId && initiatingHumanSubjectId) {
66036
+ await inheritExternalLinkTurnAuthority(tx as unknown as Database, {
66037
+ accountId: session.accountId,
66038
+ workspaceId,
66039
+ sessionId,
66040
+ turnId: internalTurn.id,
66041
+ sourceTurnId: causalHumanTurnId,
66042
+ kind: "causal",
66043
+ });
66044
+ await inheritCausalHostMcpTurnAuthorities(tx as unknown as Database, {
66045
+ accountId: session.accountId,
66046
+ workspaceId,
66047
+ sessionId,
66048
+ subjectId: initiatingHumanSubjectId,
66049
+ sourceTurnId: causalHumanTurnId,
66050
+ targetTurnId: internalTurn.id,
66051
+ });
66052
+ }
65814
66053
  // The batch is now durable model memory. Every child it reports on has
65815
66054
  // been carried to this turn's initiating human, so their read fence on
65816
66055
  // those children advances with the same commit.
@@ -70157,6 +70396,13 @@ export async function applySessionTurnSettlement(
70157
70396
  ...request,
70158
70397
  questions: request.questions.map((question) => {
70159
70398
  const parsed = HumanInputQuestionContract.parse(question);
70399
+ if (parsed.skillReview) {
70400
+ const canonical = canonicalSkillReviewQuestion(question);
70401
+ if (!canonical || request.questions.length !== 1 || request.allowSkip) {
70402
+ throw new Error("Skill review requires the exact dedicated confirmation contract");
70403
+ }
70404
+ return canonical;
70405
+ }
70160
70406
  return parsed.kind === "text" || parsed.allowOther
70161
70407
  ? parsed
70162
70408
  : { ...parsed, allowOther: true };
@@ -70226,6 +70472,40 @@ export async function applySessionTurnSettlement(
70226
70472
  );
70227
70473
  if (humanInputRequests.length > 0) {
70228
70474
  for (const request of humanInputRequests) {
70475
+ if (request.questions.length === 1 && request.questions[0]!.skillReview) {
70476
+ const [existing] = await tx
70477
+ .select()
70478
+ .from(schema.sessionHumanInputRequests)
70479
+ .where(
70480
+ and(
70481
+ eq(schema.sessionHumanInputRequests.accountId, session.accountId),
70482
+ eq(schema.sessionHumanInputRequests.workspaceId, workspaceId),
70483
+ eq(schema.sessionHumanInputRequests.sessionId, input.sessionId),
70484
+ eq(schema.sessionHumanInputRequests.turnId, input.turnId),
70485
+ eq(schema.sessionHumanInputRequests.toolCallId, request.toolCallId),
70486
+ ),
70487
+ )
70488
+ .for("update");
70489
+ if (existing) {
70490
+ const canonical =
70491
+ existing.questions.length === 1
70492
+ ? canonicalSkillReviewQuestion(existing.questions[0]!)
70493
+ : null;
70494
+ if (
70495
+ existing.id !== request.id ||
70496
+ !canonical ||
70497
+ stableJson(canonical) !== stableJson(request.questions[0])
70498
+ ) {
70499
+ throw new Error(
70500
+ `Human-input request ${request.id} changed contract before re-freeze`,
70501
+ );
70502
+ }
70503
+ // Preserve immutable displayed/audited bytes. The unchanged
70504
+ // UPSERT predicate below still fences pending status, skip and
70505
+ // exact deadline; only generation ownership can advance.
70506
+ request.questions = existing.questions;
70507
+ }
70508
+ }
70229
70509
  const [persistedRequest] = await tx
70230
70510
  .insert(schema.sessionHumanInputRequests)
70231
70511
  .values({
@@ -72293,6 +72573,15 @@ export async function getActiveSessionTurnForExecution(
72293
72573
  )
72294
72574
  .limit(1);
72295
72575
  if (!row) return null;
72576
+ const linked = await getExternalLinkTurnAuthorization(
72577
+ scopedDb,
72578
+ {
72579
+ accountId: row.turn.accountId,
72580
+ workspaceId,
72581
+ },
72582
+ row.turn.id,
72583
+ );
72584
+ if (linked && !linked.authorized) return null;
72296
72585
  await goalSnapshotForAcceptedTurnInTransaction(scopedDb, row.turn);
72297
72586
  return mapSessionTurnForExecution(row.turn);
72298
72587
  });
@@ -72305,50 +72594,10 @@ export async function getSessionTurnForAttempt(
72305
72594
  attemptId: string,
72306
72595
  ): Promise<SessionTurnForExecution | null> {
72307
72596
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
72308
- const [row] = await scopedDb
72309
- .select({ turn: schema.sessionTurns })
72310
- .from(schema.sessionTurnAttempts)
72311
- .innerJoin(
72312
- schema.sessionTurns,
72313
- and(
72314
- eq(schema.sessionTurns.workspaceId, schema.sessionTurnAttempts.workspaceId),
72315
- eq(schema.sessionTurns.id, schema.sessionTurnAttempts.turnId),
72316
- ),
72317
- )
72318
- .innerJoin(
72319
- schema.sessions,
72320
- and(
72321
- eq(schema.sessions.workspaceId, schema.sessionTurnAttempts.workspaceId),
72322
- eq(schema.sessions.id, schema.sessionTurnAttempts.sessionId),
72323
- ),
72324
- )
72325
- .where(
72326
- and(
72327
- eq(schema.sessionTurnAttempts.workspaceId, workspaceId),
72328
- eq(schema.sessionTurnAttempts.sessionId, sessionId),
72329
- eq(schema.sessionTurnAttempts.id, attemptId),
72330
- inArray(schema.sessionTurnAttempts.state, ["claimed", "running"]),
72331
- eq(schema.sessionTurns.activeAttemptId, attemptId),
72332
- eq(schema.sessions.activeTurnId, schema.sessionTurns.id),
72333
- inArray(schema.sessionTurns.status, [
72334
- "running",
72335
- "requires_action",
72336
- "recovering",
72337
- "waiting_capacity",
72338
- ]),
72339
- sql`not exists (
72340
- select 1
72341
- from ${schema.sessionAttemptInterruptions} interruption
72342
- where interruption.workspace_id = ${workspaceId}
72343
- and interruption.attempt_id = ${attemptId}
72344
- and interruption.state in ('pending', 'delivered', 'acknowledged')
72345
- )`,
72346
- ),
72347
- )
72348
- .limit(1);
72597
+ const row = await getLiveSessionAttemptTurn(scopedDb, workspaceId, sessionId, attemptId);
72349
72598
  if (!row) return null;
72350
- await goalSnapshotForAcceptedTurnInTransaction(scopedDb, row.turn);
72351
- return mapSessionTurnForExecution(row.turn);
72599
+ await goalSnapshotForAcceptedTurnInTransaction(scopedDb, row);
72600
+ return mapSessionTurnForExecution(row);
72352
72601
  });
72353
72602
  }
72354
72603
 
@@ -76755,7 +77004,7 @@ function mapSession(
76755
77004
  instructions: row.instructions ?? null,
76756
77005
  policyRole: row.policyRole ?? null,
76757
77006
  agentAccess: sessionAgentAccessFromRow(row),
76758
- endUser: sessionEndUserFromRow(row),
77007
+ scopeSubjectId: sessionEndUserFromRow(row),
76759
77008
  memoryScope: sessionMemoryScopeFromRow(row),
76760
77009
  resources: row.resources as ResourceRef[],
76761
77010
  skills: StoredSessionSkills.parse(row.skills ?? []),
@@ -77543,6 +77792,7 @@ function mapSocialConnection(
77543
77792
  ): SocialConnection {
77544
77793
  return {
77545
77794
  id: row.id,
77795
+ version: row.version,
77546
77796
  accountId: row.accountId,
77547
77797
  workspaceId: row.workspaceId,
77548
77798
  provider: row.provider as SocialProvider,
@@ -77919,6 +78169,7 @@ export {
77919
78169
 
77920
78170
  export {
77921
78171
  buildHostConnectionTokenResolver,
78172
+ buildHostGatewayConnectionTokenResolver,
77922
78173
  ConnectionRefreshHttpError,
77923
78174
  HostMcpCredentialBindingError,
77924
78175
  HostMcpCredentialScopeError,