@opengeni/db 5.0.1 → 6.0.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 (212) hide show
  1. package/dist/automations.d.ts +0 -2
  2. package/dist/canonical-human-identities.d.ts +4 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-components.d.ts +5 -13
  5. package/dist/capability-integrations.d.ts +23 -8
  6. package/dist/capability-reconciliation.d.ts +5 -0
  7. package/dist/capability-reconciliation.js +21 -0
  8. package/dist/capability-reconciliation.js.map +1 -0
  9. package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
  10. package/dist/chunk-4UJCXBR2.js.map +1 -0
  11. package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
  12. package/dist/chunk-AJA2ZIUW.js +219 -0
  13. package/dist/chunk-AJA2ZIUW.js.map +1 -0
  14. package/dist/chunk-CDAT6EKW.js +247 -0
  15. package/dist/chunk-CDAT6EKW.js.map +1 -0
  16. package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
  17. package/dist/chunk-CJI6G254.js.map +1 -0
  18. package/dist/chunk-G32NVMRJ.js +276 -0
  19. package/dist/chunk-G32NVMRJ.js.map +1 -0
  20. package/dist/chunk-GABXCG3N.js +5811 -0
  21. package/dist/chunk-GABXCG3N.js.map +1 -0
  22. package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
  23. package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
  24. package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
  25. package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
  26. package/dist/chunk-MN5PZMEK.js.map +1 -0
  27. package/dist/chunk-PJI4MUZ7.js +131 -0
  28. package/dist/chunk-PJI4MUZ7.js.map +1 -0
  29. package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
  30. package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
  31. package/dist/chunk-RL2MUXM3.js.map +1 -0
  32. package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
  33. package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
  34. package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
  35. package/dist/chunk-XIXVVBW2.js.map +1 -0
  36. package/dist/chunk-ZPJ6V2VG.js +156 -0
  37. package/dist/chunk-ZPJ6V2VG.js.map +1 -0
  38. package/dist/codex-capacity-recovery.d.ts +12 -0
  39. package/dist/codex-failure-eligibility.d.ts +9 -0
  40. package/dist/company-profile-schema.d.ts +4 -2
  41. package/dist/connect-attempts.d.ts +15 -0
  42. package/dist/connect-attempts.js +25 -0
  43. package/dist/connect-attempts.js.map +1 -0
  44. package/dist/connection-authority.d.ts +7 -63
  45. package/dist/connection-refresh-lock.d.ts +4 -0
  46. package/dist/connection-token-resolver.d.ts +8 -43
  47. package/dist/database-timing.d.ts +10 -0
  48. package/dist/database.d.ts +4 -0
  49. package/dist/editable-artifact-durable-export.js +2 -2
  50. package/dist/editable-artifacts.d.ts +9 -0
  51. package/dist/editable-artifacts.js +7 -5
  52. package/dist/index.d.ts +447 -78
  53. package/dist/index.js +12705 -12872
  54. package/dist/index.js.map +1 -1
  55. package/dist/insights-usage-bundle.d.ts +3 -0
  56. package/dist/integration-bindings.d.ts +1 -1
  57. package/dist/integration-facets.d.ts +18 -1
  58. package/dist/knowledge-entries-schema.d.ts +2 -0
  59. package/dist/knowledge-entries.d.ts +1 -0
  60. package/dist/knowledge-indexing.d.ts +1 -0
  61. package/dist/live-session-attempt.d.ts +35 -137
  62. package/dist/managed-auth-session-sets.js +3 -3
  63. package/dist/mcp-account-bindings.d.ts +4 -0
  64. package/dist/mcp-oauth.d.ts +11 -0
  65. package/dist/mcp-operations.js +2 -2
  66. package/dist/migrate.js +1 -1
  67. package/dist/organization-integration-policy.d.ts +51 -0
  68. package/dist/organization-integration-policy.js +18 -0
  69. package/dist/organization-integration-policy.js.map +1 -0
  70. package/dist/organization-usage.d.ts +20 -0
  71. package/dist/plugin-packages.d.ts +9 -12
  72. package/dist/pr-review.d.ts +0 -11
  73. package/dist/preference-registry-schema.d.ts +17 -0
  74. package/dist/provision-roles.js +1 -1
  75. package/dist/retained-provider-commands.d.ts +124 -6
  76. package/dist/retained-provider-commands.js +29 -97
  77. package/dist/retained-provider-commands.js.map +1 -1
  78. package/dist/retired-session-create-metadata.d.ts +4 -0
  79. package/dist/runtime-posture.d.ts +7 -5
  80. package/dist/schema.d.ts +876 -656
  81. package/dist/schema.js +5 -7
  82. package/dist/session-admission-block.d.ts +32 -0
  83. package/dist/session-attempt-writers.d.ts +4 -0
  84. package/dist/session-background-commands.js +5 -4
  85. package/dist/session-command-output.js +8 -126
  86. package/dist/session-command-output.js.map +1 -1
  87. package/dist/session-control.d.ts +5 -1
  88. package/dist/session-event-slices.d.ts +10 -0
  89. package/dist/session-event-slices.js +8 -222
  90. package/dist/session-event-slices.js.map +1 -1
  91. package/dist/session-file-attachments.d.ts +28 -0
  92. package/dist/session-goal-reports.d.ts +43 -0
  93. package/dist/session-mcp-approval.d.ts +4 -0
  94. package/dist/session-mcp-credential-rotation.js +5 -4
  95. package/dist/session-mcp-credential-rotation.js.map +1 -1
  96. package/dist/session-message-search.d.ts +22 -0
  97. package/dist/session-queue-commands.d.ts +2 -6
  98. package/dist/session-retry.d.ts +36 -0
  99. package/dist/session-tenancy.js +3 -3
  100. package/dist/skill-source-release-impact.d.ts +18 -0
  101. package/dist/skill-source-release.d.ts +9 -0
  102. package/dist/skills.d.ts +7 -1
  103. package/dist/user-resource-authority.d.ts +0 -8
  104. package/dist/video-generation.js +3 -3
  105. package/dist/workspace-tool-defaults.js +5 -4
  106. package/dist/workspace-tool-defaults.js.map +1 -1
  107. package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
  108. package/drizzle/0469_knowledge_source_discovery.sql +85 -0
  109. package/drizzle/0470_organization_integration_policy.sql +151 -0
  110. package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
  111. package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
  112. package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
  113. package/drizzle/0474_goal_report_requirements.sql +156 -0
  114. package/drizzle/0475_session_event_history_statistics.sql +15 -0
  115. package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
  116. package/drizzle/0477_managed_sign_in_methods.sql +303 -0
  117. package/drizzle/0478_sender_owned_connections.sql +2772 -0
  118. package/drizzle/0479_connection_create_idempotency.sql +16 -0
  119. package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
  120. package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
  121. package/drizzle/0482_remove_packs.sql +417 -0
  122. package/drizzle/0483_preclaim_admission_block.sql +85 -0
  123. package/drizzle/0484_insights_usage_projection.sql +211 -0
  124. package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
  125. package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
  126. package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
  127. package/drizzle/0488_permanent_skill_removal.sql +207 -0
  128. package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
  129. package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
  130. package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
  131. package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
  132. package/drizzle/0493_stopping_command_error_containment.sql +58 -0
  133. package/drizzle/0494_mcp_account_bindings.sql +334 -0
  134. package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
  135. package/drizzle/0496_supervised_command_settlement.sql +386 -0
  136. package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
  137. package/drizzle/0498_enrollment_membership_fence.sql +87 -0
  138. package/drizzle/0499_session_attachment_access.sql +224 -0
  139. package/drizzle/0500_session_execution_epoch.sql +37 -0
  140. package/drizzle/0501_session_sharing_execution.sql +591 -0
  141. package/drizzle/0502_active_message_boundary_forks.sql +30 -0
  142. package/package.json +17 -5
  143. package/src/automations.ts +17 -73
  144. package/src/canonical-human-identities.ts +17 -5
  145. package/src/capability-components.ts +7 -24
  146. package/src/capability-integrations.ts +301 -21
  147. package/src/capability-reconciliation.ts +19 -0
  148. package/src/codex-capacity-recovery.ts +47 -0
  149. package/src/codex-failure-eligibility.ts +93 -0
  150. package/src/connect-attempts.ts +49 -18
  151. package/src/connection-authority.ts +12 -158
  152. package/src/connection-refresh-lock.ts +22 -0
  153. package/src/connection-token-resolver.ts +58 -530
  154. package/src/database-timing.ts +43 -0
  155. package/src/database.ts +61 -26
  156. package/src/editable-artifacts.ts +1 -1
  157. package/src/index.ts +3578 -1273
  158. package/src/insights-model-bundle.ts +19 -7
  159. package/src/insights-usage-bundle.ts +16 -19
  160. package/src/integration-bindings.ts +2 -7
  161. package/src/integration-facets.ts +73 -6
  162. package/src/knowledge-entries.ts +8 -1
  163. package/src/live-session-attempt.ts +0 -22
  164. package/src/mcp-account-bindings.ts +12 -0
  165. package/src/mcp-oauth.ts +26 -0
  166. package/src/organization-integration-policy.ts +235 -0
  167. package/src/organization-usage.ts +83 -0
  168. package/src/plugin-packages.ts +226 -42
  169. package/src/pr-review.ts +7 -69
  170. package/src/preference-registry-schema.ts +1 -0
  171. package/src/provision-roles.ts +50 -37
  172. package/src/retained-provider-commands.ts +286 -9
  173. package/src/retired-session-create-metadata.ts +16 -0
  174. package/src/runtime-posture.ts +217 -13
  175. package/src/schema.ts +104 -120
  176. package/src/session-admission-block.ts +29 -0
  177. package/src/session-attempt-writers.ts +48 -0
  178. package/src/session-control.ts +21 -5
  179. package/src/session-event-slices.ts +76 -26
  180. package/src/session-file-attachments.ts +66 -0
  181. package/src/session-goal-reports.ts +255 -0
  182. package/src/session-mcp-approval.ts +30 -0
  183. package/src/session-message-search.ts +348 -0
  184. package/src/session-queue-commands.ts +60 -36
  185. package/src/session-retry.ts +319 -0
  186. package/src/skill-config-migration.ts +11 -2
  187. package/src/skill-source-release-impact.ts +33 -0
  188. package/src/skill-source-release.ts +67 -26
  189. package/src/skills.ts +44 -6
  190. package/src/user-resource-authority.ts +0 -56
  191. package/dist/chunk-6T2GGYWE.js.map +0 -1
  192. package/dist/chunk-7HMK3B45.js.map +0 -1
  193. package/dist/chunk-DPONPYJD.js.map +0 -1
  194. package/dist/chunk-F73UKJY6.js.map +0 -1
  195. package/dist/chunk-T6YLHJLS.js.map +0 -1
  196. package/dist/host-mcp-binding-match.d.ts +0 -5
  197. package/dist/host-mcp-bindings.d.ts +0 -79
  198. package/dist/host-mcp-resolvers.d.ts +0 -47
  199. package/dist/host-mcp-task-authority.d.ts +0 -43
  200. package/dist/host-selection-identity.d.ts +0 -6
  201. package/dist/pack-components.d.ts +0 -83
  202. package/dist/pack-installations.d.ts +0 -86
  203. package/src/host-mcp-binding-match.ts +0 -34
  204. package/src/host-mcp-bindings.ts +0 -777
  205. package/src/host-mcp-resolvers.ts +0 -266
  206. package/src/host-mcp-task-authority.ts +0 -384
  207. package/src/host-selection-identity.ts +0 -29
  208. package/src/pack-components.ts +0 -980
  209. package/src/pack-installations.ts +0 -950
  210. /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
  211. /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
  212. /package/dist/{chunk-T6BWIGWJ.js.map → chunk-RIYHP2WU.js.map} +0 -0
@@ -27,6 +27,40 @@ export type SessionEventSlicePage = SessionEventPage & {
27
27
  const WINDOW = 8192;
28
28
  const STRUCTURED_BYTES = 1024 * 1024;
29
29
 
30
+ type SliceIdentity = Pick<
31
+ SessionEvent,
32
+ "id" | "sequence" | "type" | "turnId" | "turnAssociation" | "duplicateOfEventId"
33
+ >;
34
+
35
+ /** Internal search projection, NOT authorization. The scanner has selected this
36
+ * exact identity under its subject RLS/list filters and still holds that same
37
+ * transaction and tenancy/membership fences. Reuse them rather than creating
38
+ * two nested scopes and rediscovering the identity for every scalar window.
39
+ * The scalar query still checks the exact workspace/session/event and RLS.
40
+ * Keep the codec/window implementation shared with the conversation reader.
41
+ */
42
+ export async function readSessionMessageSliceInScope(
43
+ db: Database,
44
+ workspaceId: string,
45
+ sessionId: string,
46
+ identity: SliceIdentity,
47
+ offset: number,
48
+ windows = 1,
49
+ ): Promise<SessionEventSlice | undefined> {
50
+ if (!Number.isInteger(windows) || windows < 1 || windows > 4)
51
+ throw new Error("Invalid search scalar window budget");
52
+ const page = await listSessionEventSlicesInternal(
53
+ db,
54
+ workspaceId,
55
+ sessionId,
56
+ { sourceSequence: identity.sequence, sourceOffset: offset, view: "conversation" },
57
+ undefined,
58
+ identity,
59
+ WINDOW * windows,
60
+ );
61
+ return page.slices?.[identity.sequence];
62
+ }
63
+
30
64
  /** Bounded transfer over retained scalar truth. JSONB extraction/detoasting still
31
65
  * costs PostgreSQL work proportional to the individual source value, not history.
32
66
  * This is a read projection, not authorization: the MCP caller must authorize the
@@ -38,6 +72,18 @@ export async function listSessionEventSlices(
38
72
  sessionId: string,
39
73
  options: SessionEventSliceOptions,
40
74
  legacyRead?: (options: ListSessionEventPageOptions) => Promise<SessionEventPage>,
75
+ ): Promise<SessionEventSlicePage> {
76
+ return listSessionEventSlicesInternal(db, workspaceId, sessionId, options, legacyRead);
77
+ }
78
+
79
+ async function listSessionEventSlicesInternal(
80
+ db: Database,
81
+ workspaceId: string,
82
+ sessionId: string,
83
+ options: SessionEventSliceOptions,
84
+ legacyRead?: (options: ListSessionEventPageOptions) => Promise<SessionEventPage>,
85
+ selectedIdentity?: SliceIdentity,
86
+ scalarWindow = WINDOW,
41
87
  ): Promise<SessionEventSlicePage> {
42
88
  // Finish an already-issued UTF-16 cursor using its original bounded reader.
43
89
  // New messages use source slices; no cursor offset is silently reinterpreted.
@@ -79,27 +125,29 @@ export async function listSessionEventSlices(
79
125
  predicates.push(notInArray(schema.sessionEvents.type, filters.excludeTypes));
80
126
  // Unlike audit `none` mode, this identity query does not compute a payload
81
127
  // projection merely to report its size. Only the selected scalar is extracted.
82
- const identities = await withWorkspaceRls(db, workspaceId, (tx) =>
83
- tx
84
- .select({
85
- id: schema.sessionEvents.id,
86
- sequence: schema.sessionEvents.sequence,
87
- type: sql<string>`case when octet_length(${schema.sessionEvents.type}) <= 256 then ${schema.sessionEvents.type} else 'session.event.envelope_omitted' end`,
88
- turnId: schema.sessionEvents.turnId,
89
- turnAssociation: sql<
90
- string | null
91
- >`case when ${schema.sessionEvents.turnAssociation} is null then null when ${schema.sessionEvents.turnAssociation} = 'current' then 'current' else 'late_rejected' end`,
92
- duplicateOfEventId: schema.sessionEvents.duplicateOfEventId,
93
- })
94
- .from(schema.sessionEvents)
95
- .where(and(...predicates))
96
- .orderBy(
97
- direction === "before"
98
- ? desc(schema.sessionEvents.sequence)
99
- : asc(schema.sessionEvents.sequence),
100
- )
101
- .limit(limit + 1),
102
- );
128
+ const identities = selectedIdentity
129
+ ? [selectedIdentity]
130
+ : await withWorkspaceRls(db, workspaceId, (tx) =>
131
+ tx
132
+ .select({
133
+ id: schema.sessionEvents.id,
134
+ sequence: schema.sessionEvents.sequence,
135
+ type: sql<string>`case when octet_length(${schema.sessionEvents.type}) <= 256 then ${schema.sessionEvents.type} else 'session.event.envelope_omitted' end`,
136
+ turnId: schema.sessionEvents.turnId,
137
+ turnAssociation: sql<
138
+ string | null
139
+ >`case when ${schema.sessionEvents.turnAssociation} is null then null when ${schema.sessionEvents.turnAssociation} = 'current' then 'current' else 'late_rejected' end`,
140
+ duplicateOfEventId: schema.sessionEvents.duplicateOfEventId,
141
+ })
142
+ .from(schema.sessionEvents)
143
+ .where(and(...predicates))
144
+ .orderBy(
145
+ direction === "before"
146
+ ? desc(schema.sessionEvents.sequence)
147
+ : asc(schema.sessionEvents.sequence),
148
+ )
149
+ .limit(limit + 1),
150
+ );
103
151
  const ordered = identities.slice(0, limit).map(
104
152
  (identity) =>
105
153
  ({
@@ -124,7 +172,7 @@ export async function listSessionEventSlices(
124
172
  const slices: Record<number, SessionEventSlice> = {};
125
173
  const events: SessionEvent[] = [];
126
174
  let consumed = 0;
127
- await withWorkspaceRls(db, workspaceId, async (tx) => {
175
+ const readScalars = async (tx: Database) => {
128
176
  for (const event of ordered) {
129
177
  consumed++;
130
178
  if (
@@ -217,8 +265,8 @@ export async function listSessionEventSlices(
217
265
  case when tagged then (length(encoded) / 4 * 3 - case when right(encoded, 2) = '==' then 2 when right(encoded, 1) = '=' then 1 else 0 end) / 2
218
266
  else length(raw) end else 0 end as total,
219
267
  case when kind = 'string' then
220
- case when tagged then substring(encoded from ${Math.min(group * 8 + 1, 2_147_483_647)}::integer for ${Math.ceil((WINDOW + 6) / 3) * 8}::integer)
221
- else substring(raw from ${Math.min(start + 1, 2_147_483_647)}::integer for ${WINDOW}::integer) end else null end as window,
268
+ case when tagged then substring(encoded from ${Math.min(group * 8 + 1, 2_147_483_647)}::integer for ${Math.ceil((scalarWindow + 6) / 3) * 8}::integer)
269
+ else substring(raw from ${Math.min(start + 1, 2_147_483_647)}::integer for ${scalarWindow}::integer) end else null end as window,
222
270
  case when kind <> 'string' and structured_bytes <= ${STRUCTURED_BYTES} then value else null end as structured,
223
271
  coalesce(kind <> 'string' and structured_bytes > ${STRUCTURED_BYTES}, false) as omitted
224
272
  from validated`,
@@ -254,7 +302,7 @@ export async function listSessionEventSlices(
254
302
  /[\uDC00-\uDFFF]/.test(text[local] ?? "")
255
303
  )
256
304
  throw new Error("Invalid message continuation offset: splits a surrogate pair");
257
- text = text.slice(local, local + WINDOW);
305
+ text = text.slice(local, local + scalarWindow);
258
306
  if (start + text.length < row.total && /[\uD800-\uDBFF]/.test(text.at(-1)!))
259
307
  text = text.slice(0, -1);
260
308
  }
@@ -291,7 +339,9 @@ export async function listSessionEventSlices(
291
339
  )
292
340
  break;
293
341
  }
294
- });
342
+ };
343
+ if (selectedIdentity) await readScalars(db);
344
+ else await withWorkspaceRls(db, workspaceId, readScalars);
295
345
  // Preserve scan advancement even when every identity was excluded above.
296
346
  return {
297
347
  ...page,
@@ -0,0 +1,66 @@
1
+ import type { FileAsset, ResourceRef, SessionAuthorizationActor } from "@opengeni/contracts";
2
+ import { sql } from "drizzle-orm";
3
+ import { rawRows, withRlsContext, type Database } from "./database";
4
+
5
+ /** Only call during verified human initial/Send/Steer acceptance, in its transaction. */
6
+ export async function acceptSessionFileAttachments(
7
+ db: Database,
8
+ input: {
9
+ accountId: string;
10
+ workspaceId: string;
11
+ sessionId: string;
12
+ turnId: string;
13
+ subjectId: string;
14
+ resources: ResourceRef[];
15
+ },
16
+ ): Promise<void> {
17
+ const ids = [...new Set(input.resources.flatMap((r) => (r.kind === "file" ? [r.fileId] : [])))];
18
+ if (!ids.length) return;
19
+ await withRlsContext(db, input, async (tx) => {
20
+ await tx.execute(sql`select opengeni_private.accept_session_file_attachments(
21
+ ${input.accountId}::uuid,${input.workspaceId}::uuid,${input.sessionId}::uuid,${input.turnId}::uuid,
22
+ ${input.subjectId},ARRAY(select jsonb_array_elements_text(${JSON.stringify(ids)}::jsonb))::uuid[])`);
23
+ });
24
+ }
25
+
26
+ export type SessionAttachmentReadAccess = {
27
+ sessionId: string;
28
+ authorityEpoch: number;
29
+ actor: Pick<SessionAuthorizationActor, "kind" | "subjectId"> &
30
+ Partial<{
31
+ callerSessionId: string;
32
+ turnId: string;
33
+ attemptId: string;
34
+ executionGeneration: number;
35
+ }>;
36
+ };
37
+ /** Core must prove session authorization; workers supply their live own-session attempt. */
38
+ export async function readSessionFileAttachments(
39
+ db: Database,
40
+ input: {
41
+ accountId: string;
42
+ workspaceId: string;
43
+ fileIds: readonly string[];
44
+ access: SessionAttachmentReadAccess;
45
+ },
46
+ ): Promise<FileAsset[]> {
47
+ if (!input.fileIds.length) return [];
48
+ return withRlsContext(db, input, async (tx) => {
49
+ const rows = await rawRows<FileAsset>(
50
+ tx,
51
+ sql`
52
+ select id,case when private_owner_subject_ids is null then 'workspace' else 'personal' end as scope,workspace_id as "workspaceId",status,filename,
53
+ safe_filename as "safeFilename",content_type as "contentType",size_bytes::double precision as "sizeBytes",
54
+ sha256,bucket,object_key as "objectKey",created_at::text as "createdAt",updated_at::text as "updatedAt"
55
+ from opengeni_private.read_session_file_attachments(
56
+ ${input.accountId}::uuid,${input.workspaceId}::uuid,${input.access.sessionId}::uuid,
57
+ ${input.access.authorityEpoch},ARRAY(select jsonb_array_elements_text(${JSON.stringify([...new Set(input.fileIds)])}::jsonb))::uuid[],
58
+ ${JSON.stringify(input.access.actor)}::jsonb)`,
59
+ );
60
+ return rows.map((row) => ({
61
+ ...row,
62
+ createdAt: new Date(row.createdAt).toISOString(),
63
+ updatedAt: new Date(row.updatedAt).toISOString(),
64
+ }));
65
+ });
66
+ }
@@ -0,0 +1,255 @@
1
+ import {
2
+ SessionGoalReportDeliveries,
3
+ SessionGoalReportRequirements,
4
+ type SessionGoalReportRequirement,
5
+ } from "@opengeni/contracts";
6
+ import { and, eq, sql } from "drizzle-orm";
7
+ import {
8
+ rawRows,
9
+ withSessionActivityRlsContext,
10
+ withSessionActivitySavepoint,
11
+ type Database,
12
+ } from "./database";
13
+ import {
14
+ transactionallyAuthorizeEditableArtifactActor,
15
+ type PersistedEditableArtifactActor,
16
+ } from "./editable-artifacts";
17
+ import {
18
+ assertAgentCommandAuthorityInTransaction,
19
+ canonicalSessionCommandHash,
20
+ lockSessionEventWriteRows,
21
+ reserveSessionCommandReceipt,
22
+ } from "./session-control";
23
+ import * as schema from "./schema";
24
+
25
+ /** Versioned, server-owned metadata. Malformed persisted data fails closed. */
26
+ export const GOAL_REPORT_REQUIREMENTS_KEY = "reportRequirementsV1";
27
+
28
+ export function goalReportRequirements(
29
+ metadata: Record<string, unknown>,
30
+ ): SessionGoalReportRequirement[] {
31
+ return SessionGoalReportRequirements.parse(
32
+ Object.hasOwn(metadata, GOAL_REPORT_REQUIREMENTS_KEY)
33
+ ? metadata[GOAL_REPORT_REQUIREMENTS_KEY]
34
+ : [],
35
+ );
36
+ }
37
+
38
+ export function appendGoalReportRequirements(
39
+ metadata: Record<string, unknown>,
40
+ declarations: readonly SessionGoalReportRequirement[] = [],
41
+ ): Record<string, unknown> {
42
+ const existing = goalReportRequirements(metadata);
43
+ const incoming = SessionGoalReportRequirements.parse(declarations);
44
+ for (const requirement of incoming) {
45
+ const previous = existing.find((item) => item.id === requirement.id);
46
+ if (previous && previous.title !== requirement.title) {
47
+ throw new Error(`Report requirement ${requirement.id} is immutable`);
48
+ }
49
+ if (!previous) existing.push(requirement);
50
+ }
51
+ return {
52
+ ...metadata,
53
+ [GOAL_REPORT_REQUIREMENTS_KEY]: SessionGoalReportRequirements.parse(existing),
54
+ };
55
+ }
56
+
57
+ export type ReportArtifactActor = Extract<PersistedEditableArtifactActor, { kind: "agent" }>;
58
+
59
+ /** Called under the canonical session lock. NOWAIT avoids the existing artifact
60
+ * writer's artifact -> actor-session authorization lock inversion. The caller
61
+ * rolls back and retries/re-inspects after a concurrent edit; never waits here. */
62
+ async function lockReadableDocument(
63
+ tx: Database,
64
+ scope: { accountId: string; workspaceId: string },
65
+ actor: ReportArtifactActor,
66
+ artifactId: string,
67
+ ) {
68
+ const [artifact] = await rawRows<{
69
+ head_sequence: string | number;
70
+ state_hash: string;
71
+ modality: string;
72
+ lifecycle_state: string;
73
+ authorization_revision: string | number;
74
+ }>(
75
+ tx,
76
+ sql`
77
+ select head_sequence, state_hash, modality, lifecycle_state, authorization_revision from editable_artifacts
78
+ where account_id = ${scope.accountId}::uuid and workspace_id = ${scope.workspaceId}::uuid
79
+ and id = ${artifactId} for share nowait
80
+ `,
81
+ );
82
+ if (!artifact || artifact.modality !== "document" || artifact.lifecycle_state !== "active") {
83
+ throw new Error("Report delivery requires an accessible active native document");
84
+ }
85
+ const authorization = await transactionallyAuthorizeEditableArtifactActor(tx, {
86
+ scope,
87
+ actor,
88
+ artifactId,
89
+ permission: "read",
90
+ });
91
+ if (!authorization.allowed) throw new Error("Report document access denied");
92
+ return artifact;
93
+ }
94
+
95
+ /** Trusted application-only recorder, called after native query/decode. There is
96
+ * no public route/tool accepting this proof body. The runtime SQL role remains
97
+ * trusted: it can insert receipt-shaped rows, so stored proof is not a native
98
+ * execution attestation against arbitrary application-role SQL. UPDATE and
99
+ * direct DELETE are fenced; legitimate session-retention cascades remove proof.
100
+ */
101
+ export async function recordNativeDocumentInspection(
102
+ db: Database,
103
+ input: {
104
+ scope: { accountId: string; workspaceId: string };
105
+ actor: ReportArtifactActor;
106
+ sessionId: string;
107
+ artifact: { id: string; modality: string; headSequence: number; stateHash: string };
108
+ queryHash: string;
109
+ queryKind: string;
110
+ },
111
+ ): Promise<string> {
112
+ if (input.actor.sessionId !== input.sessionId)
113
+ throw new Error("Inspection session authority mismatch");
114
+ return withSessionActivityRlsContext(db, input.scope, (scopedDb) =>
115
+ withSessionActivitySavepoint(scopedDb, async (tx) => {
116
+ const locks = await lockSessionEventWriteRows(tx, {
117
+ workspaceId: input.scope.workspaceId,
118
+ controlLock: "share",
119
+ sessionIds: [input.sessionId],
120
+ });
121
+ if (!locks.control || !locks.workspace || !locks.sessions[0])
122
+ throw new Error("Inspection session not found");
123
+ const actor = {
124
+ type: "agent_attempt" as const,
125
+ sessionId: input.actor.sessionId,
126
+ turnId: input.actor.turnId,
127
+ attemptId: input.actor.attemptId,
128
+ executionGeneration: input.actor.generation,
129
+ };
130
+ await assertAgentCommandAuthorityInTransaction(tx, {
131
+ workspaceId: input.scope.workspaceId,
132
+ actor,
133
+ targetSessionId: input.sessionId,
134
+ action: "goal",
135
+ });
136
+ const artifact = await lockReadableDocument(tx, input.scope, input.actor, input.artifact.id);
137
+ if (
138
+ Number(artifact.head_sequence) !== input.artifact.headSequence ||
139
+ artifact.state_hash !== input.artifact.stateHash
140
+ ) {
141
+ throw new Error("Document changed during inspection; inspect its current head again");
142
+ }
143
+ const result = {
144
+ version: "native-document-inspection.v1",
145
+ artifactId: input.artifact.id,
146
+ headSequence: input.artifact.headSequence,
147
+ stateHash: input.artifact.stateHash,
148
+ subjectId: input.actor.subjectId,
149
+ queryHash: input.queryHash,
150
+ queryKind: input.queryKind,
151
+ authorizationRevision: Number(artifact.authorization_revision),
152
+ };
153
+ const reserved = await reserveSessionCommandReceipt(tx, {
154
+ ...input.scope,
155
+ actor,
156
+ action: "artifact.document.inspect",
157
+ targetSessionId: input.sessionId,
158
+ targetTurnId: input.actor.turnId,
159
+ operationKey: `document-inspection:${canonicalSessionCommandHash(result)}`,
160
+ canonicalRequestHash: canonicalSessionCommandHash(result),
161
+ initialResult: result,
162
+ });
163
+ return reserved.receipt.id;
164
+ }),
165
+ );
166
+ }
167
+
168
+ /** No provider calls here: receipt, access, locked current head and completion
169
+ * share one transaction. Receipts from previous attempts may prove inspection,
170
+ * but the completing attempt must still have live read authority. */
171
+ export async function verifyGoalReportDeliveries(
172
+ tx: Database,
173
+ input: {
174
+ accountId: string;
175
+ workspaceId: string;
176
+ sessionId: string;
177
+ metadata: Record<string, unknown>;
178
+ deliveries: unknown;
179
+ actor?: ReportArtifactActor;
180
+ },
181
+ ): Promise<void> {
182
+ const requirements = goalReportRequirements(input.metadata);
183
+ const deliveries = SessionGoalReportDeliveries.parse(input.deliveries ?? []);
184
+ if (
185
+ deliveries.length !== requirements.length ||
186
+ requirements.some((item) => !deliveries.some((delivery) => delivery.requirementId === item.id))
187
+ ) {
188
+ throw new Error(
189
+ "Every persisted report requirement needs exactly one verified document delivery",
190
+ );
191
+ }
192
+ if (!requirements.length) return;
193
+ if (!input.actor || input.actor.sessionId !== input.sessionId)
194
+ throw new Error("Report completion requires exact agent artifact authority");
195
+ const [turn] = await tx
196
+ .select({
197
+ initiatingHumanSubjectId: schema.sessionTurns.initiatingHumanSubjectId,
198
+ initiatorKind: schema.sessionTurns.initiatorKind,
199
+ initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
200
+ })
201
+ .from(schema.sessionTurns)
202
+ .where(
203
+ and(
204
+ eq(schema.sessionTurns.workspaceId, input.workspaceId),
205
+ eq(schema.sessionTurns.sessionId, input.sessionId),
206
+ eq(schema.sessionTurns.id, input.actor.turnId),
207
+ ),
208
+ )
209
+ .limit(1);
210
+ const recipient =
211
+ turn?.initiatingHumanSubjectId ??
212
+ (turn?.initiatorKind === "subject" ? turn.initiatorSubjectId : null);
213
+ if (!recipient)
214
+ throw new Error("Report completion requires a verified initiating human recipient");
215
+ for (const delivery of [...deliveries].sort((a, b) => a.artifactId.localeCompare(b.artifactId))) {
216
+ const artifact = await lockReadableDocument(tx, input, input.actor, delivery.artifactId);
217
+ const recipientAccess = await transactionallyAuthorizeEditableArtifactActor(tx, {
218
+ scope: input,
219
+ artifactId: delivery.artifactId,
220
+ actor: { kind: "human", subjectId: recipient, replicaId: input.actor.replicaId },
221
+ permission: "read",
222
+ });
223
+ if (!recipientAccess.allowed)
224
+ throw new Error("Report recipient cannot access the native document");
225
+ const [receipt] = await tx
226
+ .select()
227
+ .from(schema.sessionCommandReceipts)
228
+ .where(
229
+ and(
230
+ eq(schema.sessionCommandReceipts.id, delivery.inspectionReceiptId),
231
+ eq(schema.sessionCommandReceipts.accountId, input.accountId),
232
+ eq(schema.sessionCommandReceipts.workspaceId, input.workspaceId),
233
+ eq(schema.sessionCommandReceipts.targetSessionId, input.sessionId),
234
+ eq(schema.sessionCommandReceipts.action, "artifact.document.inspect"),
235
+ eq(schema.sessionCommandReceipts.actorType, "agent_attempt"),
236
+ ),
237
+ )
238
+ .limit(1);
239
+ const proof = receipt?.result;
240
+ if (
241
+ !proof ||
242
+ proof.version !== "native-document-inspection.v1" ||
243
+ proof.queryKind !== "body" ||
244
+ proof.artifactId !== delivery.artifactId ||
245
+ proof.subjectId !== input.actor.subjectId ||
246
+ proof.headSequence !== Number(artifact.head_sequence) ||
247
+ proof.stateHash !== artifact.state_hash ||
248
+ proof.authorizationRevision !== Number(artifact.authorization_revision)
249
+ ) {
250
+ throw new Error(
251
+ "Missing, untrusted, inaccessible or stale native document inspection receipt",
252
+ );
253
+ }
254
+ }
255
+ }
@@ -0,0 +1,30 @@
1
+ import { and, eq, inArray } from "drizzle-orm";
2
+ import type { SessionMcpApprovalPolicy } from "@opengeni/contracts";
3
+ import { withWorkspaceRls, type Database } from "./database";
4
+ import * as schema from "./schema";
5
+
6
+ /** Read only the immutable active-attempt policy, never a mutable session fallback. */
7
+ export async function getSessionAttemptMcpApprovalPolicies(
8
+ db: Database,
9
+ workspaceId: string,
10
+ sessionId: string,
11
+ attemptId: string,
12
+ ): Promise<Record<string, SessionMcpApprovalPolicy>> {
13
+ return await withWorkspaceRls(db, workspaceId, async (tx) => {
14
+ const [attempt] = await tx
15
+ .select({ policies: schema.sessionTurnAttempts.mcpApprovalPolicies })
16
+ .from(schema.sessionTurnAttempts)
17
+ .where(
18
+ and(
19
+ eq(schema.sessionTurnAttempts.workspaceId, workspaceId),
20
+ eq(schema.sessionTurnAttempts.sessionId, sessionId),
21
+ eq(schema.sessionTurnAttempts.id, attemptId),
22
+ inArray(schema.sessionTurnAttempts.state, ["claimed", "running"]),
23
+ ),
24
+ )
25
+ .limit(1);
26
+ if (!attempt)
27
+ throw new Error(`session MCP policy snapshot is unavailable for attempt ${attemptId}`);
28
+ return attempt.policies;
29
+ });
30
+ }