@opengeni/db 0.28.9 → 0.36.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 (225) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +299 -0
  4. package/dist/browser-downloads.d.ts +51 -0
  5. package/dist/browser-identities.d.ts +137 -0
  6. package/dist/browser-sessions.d.ts +354 -0
  7. package/dist/browser-state-artifacts.d.ts +91 -0
  8. package/dist/canonical-human-identities.d.ts +48 -0
  9. package/dist/canonical-human-identities.js +28 -0
  10. package/dist/canonical-human-identities.js.map +1 -0
  11. package/dist/capability-components.d.ts +37 -0
  12. package/dist/capability-integrations.d.ts +194 -0
  13. package/dist/chunk-5KTIEDX7.js +13200 -0
  14. package/dist/chunk-5KTIEDX7.js.map +1 -0
  15. package/dist/chunk-CQZ4QSP5.js +995 -0
  16. package/dist/chunk-CQZ4QSP5.js.map +1 -0
  17. package/dist/chunk-F32YT7MP.js +117 -0
  18. package/dist/chunk-F32YT7MP.js.map +1 -0
  19. package/dist/chunk-JC5QPLUM.js +666 -0
  20. package/dist/chunk-JC5QPLUM.js.map +1 -0
  21. package/dist/chunk-P3RE4D2B.js +3535 -0
  22. package/dist/chunk-P3RE4D2B.js.map +1 -0
  23. package/dist/chunk-TAUTWOS5.js +259 -0
  24. package/dist/chunk-TAUTWOS5.js.map +1 -0
  25. package/dist/chunk-VKLB2ZGI.js +2389 -0
  26. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  27. package/dist/codemode-operations.d.ts +117 -0
  28. package/dist/codex-token-resolver.d.ts +2 -2
  29. package/dist/company-profile-schema.d.ts +798 -0
  30. package/dist/company-profile.d.ts +268 -0
  31. package/dist/computer-sessions.d.ts +109 -0
  32. package/dist/connection-token-resolver.d.ts +9 -4
  33. package/dist/database.d.ts +57 -3
  34. package/dist/editable-artifact-durable-export.d.ts +153 -0
  35. package/dist/editable-artifact-durable-export.js +788 -0
  36. package/dist/editable-artifact-durable-export.js.map +1 -0
  37. package/dist/editable-artifact-materialization.d.ts +123 -0
  38. package/dist/editable-artifacts-schema.d.ts +4152 -0
  39. package/dist/editable-artifacts.d.ts +610 -0
  40. package/dist/editable-artifacts.js +26 -0
  41. package/dist/editable-artifacts.js.map +1 -0
  42. package/dist/generated-images.d.ts +139 -0
  43. package/dist/index.d.ts +1122 -126
  44. package/dist/index.js +42329 -18407
  45. package/dist/index.js.map +1 -1
  46. package/dist/insights.d.ts +73 -10
  47. package/dist/integration-bindings.d.ts +70 -0
  48. package/dist/integration-facets.d.ts +99 -0
  49. package/dist/interaction-revisions.d.ts +14 -0
  50. package/dist/interaction-schema.d.ts +6036 -0
  51. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  52. package/dist/knowledge-source-sync.d.ts +274 -0
  53. package/dist/memory-governance.d.ts +2 -2
  54. package/dist/memory-slack-delivery.d.ts +101 -0
  55. package/dist/new-session-drafts.d.ts +2 -2
  56. package/dist/pack-components.d.ts +76 -0
  57. package/dist/pack-installations.d.ts +86 -0
  58. package/dist/persistence-errors.d.ts +3 -3
  59. package/dist/plugin-packages.d.ts +126 -0
  60. package/dist/preference-registry.d.ts +1 -1
  61. package/dist/provision-roles.d.ts +9 -1
  62. package/dist/provision-roles.js +1 -1
  63. package/dist/runtime-posture.d.ts +19 -6
  64. package/dist/schema.d.ts +22654 -9359
  65. package/dist/schema.js +209 -3
  66. package/dist/scoped-knowledge.d.ts +97 -1
  67. package/dist/session-control.d.ts +33 -7
  68. package/dist/session-queue-commands.d.ts +19 -11
  69. package/dist/session-realtime-context.d.ts +2 -2
  70. package/dist/session-realtime-ledger.d.ts +4 -4
  71. package/dist/session-realtime-mirror.d.ts +2 -2
  72. package/dist/session-realtime-state.d.ts +1 -1
  73. package/dist/session-realtime-terminal.d.ts +1 -1
  74. package/dist/session-realtime.d.ts +7 -7
  75. package/dist/session-tenancy.d.ts +44 -0
  76. package/dist/session-tenancy.js +16 -0
  77. package/dist/session-tenancy.js.map +1 -0
  78. package/dist/session-tool-call-settlement.d.ts +1 -1
  79. package/dist/slack-task-policy-schema.d.ts +847 -0
  80. package/dist/slack-task-policy.d.ts +65 -0
  81. package/dist/slack-user-link-access.d.ts +54 -0
  82. package/dist/task-notes-schema.d.ts +924 -0
  83. package/dist/task-notes.d.ts +85 -0
  84. package/dist/transcription-recordings.d.ts +2 -2
  85. package/dist/turn-initiator.d.ts +2 -2
  86. package/dist/video-generation.d.ts +225 -0
  87. package/dist/video-generation.js +68 -0
  88. package/dist/video-generation.js.map +1 -0
  89. package/dist/workspace-artifacts.d.ts +1 -1
  90. package/dist/workspace-instruction-policies.d.ts +1 -1
  91. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  92. package/dist/workspace-learning-policy.d.ts +161 -0
  93. package/dist/xai-subscription.d.ts +326 -0
  94. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  95. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  96. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  97. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  98. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  99. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  100. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  101. package/drizzle/0190_timeline_annotations.sql +31 -0
  102. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  103. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  104. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  105. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  106. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  107. package/drizzle/0196_rig_provider_images.sql +152 -0
  108. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  109. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  110. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  111. package/drizzle/0201_company_profile_authority.sql +849 -0
  112. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  113. package/drizzle/0203_durable_video_generation.sql +353 -0
  114. package/drizzle/0204_video_generation_funding.sql +44 -0
  115. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  116. package/drizzle/0206_browser_sessions.sql +646 -0
  117. package/drizzle/0207_browser_identities.sql +379 -0
  118. package/drizzle/0208_attached_browser_devices.sql +165 -0
  119. package/drizzle/0209_computer_sessions.sql +575 -0
  120. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  121. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  122. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  123. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  124. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  125. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  126. package/drizzle/0214_browser_download_saves.sql +34 -0
  127. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  128. package/drizzle/0215_browser_controller_host.sql +385 -0
  129. package/drizzle/0215_capabilities_platform.sql +980 -0
  130. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  131. package/drizzle/0216_pack_component_ownership.sql +235 -0
  132. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  133. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  134. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  135. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  136. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  137. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  138. package/drizzle/0220_session_channels.sql +54 -0
  139. package/drizzle/0221_sessions_channel_index.sql +5 -0
  140. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  141. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  142. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  143. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  144. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  145. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  146. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  147. package/drizzle/0227_slack_native_actions.sql +145 -0
  148. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  149. package/drizzle/0228_slack_task_policy.sql +400 -0
  150. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  151. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  152. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  153. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  154. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  155. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  156. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  157. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  158. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  159. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  160. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  161. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  162. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  163. package/drizzle/0239_task_tree_notes.sql +990 -0
  164. package/package.json +26 -5
  165. package/src/attached-browser-devices.ts +371 -0
  166. package/src/attempt-tool-catalogs.ts +131 -0
  167. package/src/browser-auth.ts +4373 -0
  168. package/src/browser-downloads.ts +517 -0
  169. package/src/browser-identities.ts +1385 -0
  170. package/src/browser-sessions.ts +2294 -0
  171. package/src/browser-state-artifacts.ts +449 -0
  172. package/src/canonical-human-identities.ts +317 -0
  173. package/src/capability-components.ts +166 -0
  174. package/src/capability-integrations.ts +1571 -0
  175. package/src/codemode-operations.ts +674 -0
  176. package/src/company-profile-schema.ts +143 -0
  177. package/src/company-profile.ts +968 -0
  178. package/src/computer-sessions.ts +1130 -0
  179. package/src/connection-token-resolver.ts +270 -25
  180. package/src/database.ts +468 -24
  181. package/src/editable-artifact-durable-export.ts +1201 -0
  182. package/src/editable-artifact-materialization.ts +893 -0
  183. package/src/editable-artifacts-schema.ts +879 -0
  184. package/src/editable-artifacts.ts +5404 -0
  185. package/src/generated-images.ts +740 -0
  186. package/src/index.ts +9963 -1465
  187. package/src/insights.ts +251 -23
  188. package/src/integration-bindings.ts +455 -0
  189. package/src/integration-facets.ts +862 -0
  190. package/src/interaction-revisions.ts +70 -0
  191. package/src/interaction-schema.ts +1606 -0
  192. package/src/knowledge-source-sync-schema.ts +192 -0
  193. package/src/knowledge-source-sync.ts +1922 -0
  194. package/src/memory-slack-delivery.ts +801 -0
  195. package/src/pack-components.ts +954 -0
  196. package/src/pack-installations.ts +886 -0
  197. package/src/persistence-errors.ts +4 -7
  198. package/src/plugin-packages.ts +1186 -0
  199. package/src/provision-roles.ts +534 -1
  200. package/src/runtime-posture-cli.ts +9 -0
  201. package/src/runtime-posture.ts +721 -5
  202. package/src/schema.ts +6270 -3159
  203. package/src/scoped-knowledge-schema.ts +1 -1
  204. package/src/scoped-knowledge.ts +655 -3
  205. package/src/session-control.ts +130 -42
  206. package/src/session-queue-commands.ts +265 -15
  207. package/src/session-realtime-context.ts +2 -2
  208. package/src/session-realtime-ledger.ts +3 -3
  209. package/src/session-realtime.ts +7 -7
  210. package/src/session-tenancy.ts +188 -0
  211. package/src/session-tool-call-settlement.ts +18 -3
  212. package/src/slack-task-policy-schema.ts +130 -0
  213. package/src/slack-task-policy.ts +341 -0
  214. package/src/slack-user-link-access.ts +702 -0
  215. package/src/task-notes-schema.ts +135 -0
  216. package/src/task-notes.ts +178 -0
  217. package/src/video-generation.ts +1530 -0
  218. package/src/workspace-artifacts.ts +1 -1
  219. package/src/workspace-learning-policy-schema.ts +139 -0
  220. package/src/workspace-learning-policy.ts +595 -0
  221. package/src/xai-subscription.ts +1887 -0
  222. package/dist/chunk-P6CUHXQZ.js +0 -7248
  223. package/dist/chunk-P6CUHXQZ.js.map +0 -1
  224. package/dist/chunk-VHBFHMPC.js +0 -1219
  225. package/dist/chunk-VHBFHMPC.js.map +0 -1
@@ -7,7 +7,7 @@ import {
7
7
  type TurnInitiatorContext,
8
8
  } from "@opengeni/contracts";
9
9
  import { and, eq, inArray, sql } from "drizzle-orm";
10
- import type { Database } from "./database";
10
+ import type { Database, SessionActivityDatabase } from "./database";
11
11
  import { withLosslessContentWriteVersion } from "./lossless-json";
12
12
  import * as schema from "./schema";
13
13
  import { closePendingSessionToolCallsInTransaction } from "./session-tool-call-settlement";
@@ -200,7 +200,7 @@ export class SessionCommandIdempotencyError extends Error {
200
200
 
201
201
  export class AgentCommandAuthorityError extends Error {
202
202
  constructor(
203
- readonly code: "CALLER_STALE" | "CALLER_INTERRUPTED" | "SELF_OR_ANCESTOR_PAUSE" | "SELF_STEER",
203
+ readonly code: "CALLER_STALE" | "CALLER_INTERRUPTED" | "TARGET_NOT_VERTICAL" | "SELF_STEER",
204
204
  message: string,
205
205
  ) {
206
206
  super(message);
@@ -232,10 +232,12 @@ export async function assertAgentCommandAuthorityInTransaction(
232
232
  attemptIds: [input.actor.attemptId],
233
233
  });
234
234
  const lockedSessions = authorityLocks.sessions;
235
- if (!lockedSessions.some((row) => row.id === input.actor.sessionId)) {
235
+ const callerSession = lockedSessions.find((row) => row.id === input.actor.sessionId);
236
+ if (!callerSession) {
236
237
  throw new AgentCommandAuthorityError("CALLER_STALE", "The calling session no longer exists");
237
238
  }
238
- if (!lockedSessions.some((row) => row.id === input.targetSessionId)) {
239
+ const targetSession = lockedSessions.find((row) => row.id === input.targetSessionId);
240
+ if (!targetSession) {
239
241
  throw new SessionControlInvariantError(`Target session not found: ${input.targetSessionId}`);
240
242
  }
241
243
  const rows = await db.execute<{
@@ -298,37 +300,21 @@ export async function assertAgentCommandAuthorityInTransaction(
298
300
  if (input.action === "steer" && input.targetSessionId === input.actor.sessionId) {
299
301
  throw new AgentCommandAuthorityError("SELF_STEER", "An agent cannot steer its own session");
300
302
  }
301
- if (input.action !== "pause") return;
302
- const ancestry = await db.execute<{
303
- containsCaller: boolean;
304
- invalid: boolean;
305
- }>(sql`
306
- with recursive caller_ancestry(id, parent_id, depth, path, cycle) as (
307
- select session.id, session.parent_session_id, 0, array[session.id], false
308
- from ${schema.sessions} session
309
- where session.workspace_id = ${input.workspaceId}
310
- and session.id = ${input.actor.sessionId}
311
- union all
312
- select parent.id, parent.parent_session_id, child.depth + 1,
313
- child.path || parent.id, parent.id = any(child.path)
314
- from caller_ancestry child
315
- join ${schema.sessions} parent
316
- on parent.workspace_id = ${input.workspaceId} and parent.id = child.parent_id
317
- where child.depth < ${SESSION_ANCESTRY_LIMIT} and not child.cycle
318
- )
319
- select
320
- coalesce(bool_or(id = ${input.targetSessionId}), false) as "containsCaller",
321
- coalesce(bool_or(cycle), false) or coalesce(max(depth), 0) >= ${SESSION_ANCESTRY_LIMIT}
322
- as invalid
323
- from caller_ancestry
324
- `);
325
- if (ancestry[0]?.invalid) {
326
- throw new SessionControlInvariantError("Caller ancestry is invalid");
327
- }
328
- if (ancestry[0]?.containsCaller) {
303
+ const targetsSelf = targetSession.id === callerSession.id;
304
+ const targetsDirectParent = callerSession.parentSessionId === targetSession.id;
305
+ const targetsDirectChild = targetSession.parentSessionId === callerSession.id;
306
+ const verticalTargetAllowed =
307
+ input.action === "goal"
308
+ ? targetsSelf
309
+ : input.action === "message"
310
+ ? targetsSelf || targetsDirectParent || targetsDirectChild
311
+ : targetsDirectChild;
312
+ if (!verticalTargetAllowed) {
329
313
  throw new AgentCommandAuthorityError(
330
- "SELF_OR_ANCESTOR_PAUSE",
331
- "An agent cannot pause its own session or an ancestor workstream",
314
+ "TARGET_NOT_VERTICAL",
315
+ input.action === "message"
316
+ ? "An agent may message only its own session, direct parent, or direct child"
317
+ : "An agent may control only a direct child session",
332
318
  );
333
319
  }
334
320
  }
@@ -430,7 +416,7 @@ export type SessionEventWriteLocks = {
430
416
  *
431
417
  * workspace_inference_controls (when control-aware)
432
418
  * -> actual workspaces row FOR KEY SHARE
433
- * -> session rows FOR UPDATE, UUID ordered
419
+ * -> session rows FOR NO KEY UPDATE, UUID ordered
434
420
  * -> exact turn rows FOR UPDATE, UUID ordered
435
421
  * -> exact attempt rows FOR UPDATE, UUID ordered
436
422
  *
@@ -439,6 +425,11 @@ export type SessionEventWriteLocks = {
439
425
  * `FOR UPDATE` lock serialized unrelated sessions and inverted the activity
440
426
  * path's session -> implicit workspace-FK edge.
441
427
  *
428
+ * `FOR NO KEY UPDATE` is equally deliberate for sessions. Session primary and
429
+ * composite identity keys are immutable, so writers need to exclude only concurrent
430
+ * non-key mutation. Keeping FK `FOR KEY SHARE` checks compatible prevents the
431
+ * activity finalizer's workspace counter from participating in a lock cycle.
432
+ *
442
433
  * Complex lifecycle transactions may acquire allocator/control locks before
443
434
  * this helper and may discover exact turn IDs only after locking the session.
444
435
  * They use the explicit `already_locked` stages, while retaining the same
@@ -478,7 +469,7 @@ export async function lockSessionEventWriteRows(
478
469
  ),
479
470
  )
480
471
  .orderBy(schema.sessions.id)
481
- .for("update")
472
+ .for("no key update")
482
473
  : [];
483
474
 
484
475
  const turnIds = [...new Set(input.turnIds ?? [])].sort();
@@ -529,14 +520,24 @@ export async function registerSessionTurnAttemptClaim(
529
520
  temporalWorkflowRunId: string;
530
521
  temporalActivityId: string;
531
522
  verifiedControlRevision: number;
523
+ authorityEpoch: number;
524
+ authorityVisibility: "user_private" | "workspace_shared";
525
+ authorityOwnerOrganizationMembershipId: string | null;
532
526
  mcpApprovalPolicies: Record<string, SessionMcpApprovalPolicy>;
533
527
  connectorActionPolicies: schema.ConnectorActionPolicySnapshotEntry[];
534
528
  },
535
529
  ): Promise<typeof schema.sessionTurnAttempts.$inferSelect> {
530
+ const authoritySnapshot = assertSessionAuthoritySnapshot({
531
+ attemptId: input.id,
532
+ authorityEpoch: input.authorityEpoch,
533
+ authorityVisibility: input.authorityVisibility,
534
+ authorityOwnerOrganizationMembershipId: input.authorityOwnerOrganizationMembershipId,
535
+ });
536
536
  const [inserted] = await db
537
537
  .insert(schema.sessionTurnAttempts)
538
538
  .values({
539
539
  ...input,
540
+ ...authoritySnapshot,
540
541
  state: "claimed",
541
542
  })
542
543
  // Only an idempotent replay of this exact preallocated attempt ID may
@@ -557,12 +558,22 @@ export async function registerSessionTurnAttemptClaim(
557
558
  )
558
559
  .for("update")
559
560
  .limit(1);
561
+ const existingAuthoritySnapshot = existing
562
+ ? assertSessionAuthoritySnapshot({
563
+ attemptId: existing.id,
564
+ authorityEpoch: existing.authorityEpoch,
565
+ authorityVisibility: existing.authorityVisibility,
566
+ authorityOwnerOrganizationMembershipId: existing.authorityOwnerOrganizationMembershipId,
567
+ })
568
+ : null;
560
569
  if (
561
570
  !existing ||
562
571
  existing.accountId !== input.accountId ||
563
572
  existing.sessionId !== input.sessionId ||
564
573
  existing.turnId !== input.turnId ||
565
574
  existing.executionGeneration !== input.executionGeneration ||
575
+ !existingAuthoritySnapshot ||
576
+ !sessionAuthoritySnapshotsEqual(existingAuthoritySnapshot, authoritySnapshot) ||
566
577
  existing.temporalWorkflowId !== input.temporalWorkflowId ||
567
578
  existing.temporalWorkflowRunId !== input.temporalWorkflowRunId ||
568
579
  existing.temporalActivityId !== input.temporalActivityId ||
@@ -577,6 +588,80 @@ export async function registerSessionTurnAttemptClaim(
577
588
  return existing;
578
589
  }
579
590
 
591
+ export type SessionAuthoritySnapshot = {
592
+ authorityEpoch: number;
593
+ authorityVisibility: "user_private" | "workspace_shared";
594
+ authorityOwnerOrganizationMembershipId: string | null;
595
+ };
596
+
597
+ /**
598
+ * Validate the complete accepted-attempt authority tuple. Missing or partial
599
+ * values are not legacy defaults: current claim writers must provide the
600
+ * exact locked session authority explicitly.
601
+ */
602
+ export function assertSessionAuthoritySnapshot(input: {
603
+ attemptId?: string;
604
+ authorityEpoch: unknown;
605
+ authorityVisibility: unknown;
606
+ authorityOwnerOrganizationMembershipId: unknown;
607
+ }): SessionAuthoritySnapshot {
608
+ const valid =
609
+ Number.isSafeInteger(input.authorityEpoch) &&
610
+ (input.authorityEpoch as number) > 0 &&
611
+ (input.authorityVisibility === "user_private" ||
612
+ input.authorityVisibility === "workspace_shared") &&
613
+ ((input.authorityVisibility === "user_private" &&
614
+ typeof input.authorityOwnerOrganizationMembershipId === "string") ||
615
+ (input.authorityVisibility === "workspace_shared" &&
616
+ (input.authorityOwnerOrganizationMembershipId === null ||
617
+ typeof input.authorityOwnerOrganizationMembershipId === "string")));
618
+ if (!valid) {
619
+ throw new SessionControlInvariantError(
620
+ `Attempt ${input.attemptId ?? "unknown"} has an invalid session authority snapshot`,
621
+ );
622
+ }
623
+ return {
624
+ authorityEpoch: input.authorityEpoch as number,
625
+ authorityVisibility:
626
+ input.authorityVisibility as SessionAuthoritySnapshot["authorityVisibility"],
627
+ authorityOwnerOrganizationMembershipId: input.authorityOwnerOrganizationMembershipId as
628
+ | string
629
+ | null,
630
+ };
631
+ }
632
+
633
+ export function sessionAuthoritySnapshotMatchesSession(
634
+ snapshot: SessionAuthoritySnapshot,
635
+ session: Pick<
636
+ typeof schema.sessions.$inferSelect,
637
+ "authorityEpoch" | "visibility" | "ownerOrganizationMembershipId"
638
+ >,
639
+ ): boolean {
640
+ return (
641
+ snapshot.authorityEpoch === session.authorityEpoch &&
642
+ snapshot.authorityVisibility === session.visibility &&
643
+ snapshot.authorityOwnerOrganizationMembershipId ===
644
+ (session.ownerOrganizationMembershipId ?? null)
645
+ );
646
+ }
647
+
648
+ export function sessionAuthoritySnapshotsEqual(
649
+ left: Pick<
650
+ SessionAuthoritySnapshot,
651
+ "authorityEpoch" | "authorityVisibility" | "authorityOwnerOrganizationMembershipId"
652
+ >,
653
+ right: Pick<
654
+ SessionAuthoritySnapshot,
655
+ "authorityEpoch" | "authorityVisibility" | "authorityOwnerOrganizationMembershipId"
656
+ >,
657
+ ): boolean {
658
+ return (
659
+ left.authorityEpoch === right.authorityEpoch &&
660
+ left.authorityVisibility === right.authorityVisibility &&
661
+ left.authorityOwnerOrganizationMembershipId === right.authorityOwnerOrganizationMembershipId
662
+ );
663
+ }
664
+
580
665
  /**
581
666
  * Close the exact first-class owner while the caller still holds the owning
582
667
  * session/turn locks. Every path that clears `session_turns.active_attempt_id`
@@ -1163,7 +1248,10 @@ export async function evaluateSessionDiscoveryControls(
1163
1248
  );
1164
1249
  }
1165
1250
 
1166
- const workspace = await lockWorkspaceInferenceControl(db, workspaceId, "share");
1251
+ // Discovery owns one read-only repeatable-read snapshot, so this projection
1252
+ // must not join the writer lock graph merely to keep workspace/session facts
1253
+ // coherent. The snapshot is the consistency boundary.
1254
+ const workspace = await lockWorkspaceInferenceControl(db, workspaceId, "none");
1167
1255
  const workspacePauseRevision = asSafeRevision(
1168
1256
  workspace.workspacePauseRevision,
1169
1257
  "workspace pause revision",
@@ -1412,12 +1500,12 @@ export async function reserveSessionCommandReceipt(
1412
1500
  if (
1413
1501
  identityScope === "goal_operation" &&
1414
1502
  (input.actor.type !== "agent_attempt" ||
1415
- input.action !== "goal.update" ||
1503
+ !["goal.update", "goal.progress"].includes(input.action) ||
1416
1504
  input.targetSessionId === null ||
1417
1505
  input.targetTurnId !== null)
1418
1506
  ) {
1419
1507
  throw new SessionControlInvariantError(
1420
- "Target-scoped receipt identity is reserved for agent goal.update commands",
1508
+ "Target-scoped receipt identity is reserved for agent goal commands",
1421
1509
  );
1422
1510
  }
1423
1511
  const actorSubjectId = input.actor.type === "agent_attempt" ? null : input.actor.subjectId;
@@ -2050,7 +2138,7 @@ async function assertSessionBranchIsNotCancelled(
2050
2138
  }
2051
2139
 
2052
2140
  async function cancelSessionSubtreeInTransaction(
2053
- db: Database,
2141
+ db: SessionActivityDatabase,
2054
2142
  input: {
2055
2143
  accountId: string;
2056
2144
  workspaceId: string;
@@ -2375,7 +2463,7 @@ async function cancelSessionSubtreeInTransaction(
2375
2463
  }
2376
2464
 
2377
2465
  export async function mutateSessionControlInTransaction(
2378
- db: Database,
2466
+ db: SessionActivityDatabase,
2379
2467
  input: {
2380
2468
  accountId: string;
2381
2469
  workspaceId: string;
@@ -2694,7 +2782,7 @@ export async function mutateSessionControlInTransaction(
2694
2782
  }
2695
2783
 
2696
2784
  export async function autoResumeSessionBranchInTransaction(
2697
- db: Database,
2785
+ db: SessionActivityDatabase,
2698
2786
  input: {
2699
2787
  workspaceId: string;
2700
2788
  sessionId: string;