@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.
- package/dist/automations.d.ts +0 -2
- package/dist/canonical-human-identities.d.ts +4 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-components.d.ts +5 -13
- package/dist/capability-integrations.d.ts +23 -8
- package/dist/capability-reconciliation.d.ts +5 -0
- package/dist/capability-reconciliation.js +21 -0
- package/dist/capability-reconciliation.js.map +1 -0
- package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
- package/dist/chunk-4UJCXBR2.js.map +1 -0
- package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
- package/dist/chunk-AJA2ZIUW.js +219 -0
- package/dist/chunk-AJA2ZIUW.js.map +1 -0
- package/dist/chunk-CDAT6EKW.js +247 -0
- package/dist/chunk-CDAT6EKW.js.map +1 -0
- package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
- package/dist/chunk-CJI6G254.js.map +1 -0
- package/dist/chunk-G32NVMRJ.js +276 -0
- package/dist/chunk-G32NVMRJ.js.map +1 -0
- package/dist/chunk-GABXCG3N.js +5811 -0
- package/dist/chunk-GABXCG3N.js.map +1 -0
- package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
- package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
- package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
- package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
- package/dist/chunk-MN5PZMEK.js.map +1 -0
- package/dist/chunk-PJI4MUZ7.js +131 -0
- package/dist/chunk-PJI4MUZ7.js.map +1 -0
- package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
- package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
- package/dist/chunk-RL2MUXM3.js.map +1 -0
- package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
- package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
- package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
- package/dist/chunk-XIXVVBW2.js.map +1 -0
- package/dist/chunk-ZPJ6V2VG.js +156 -0
- package/dist/chunk-ZPJ6V2VG.js.map +1 -0
- package/dist/codex-capacity-recovery.d.ts +12 -0
- package/dist/codex-failure-eligibility.d.ts +9 -0
- package/dist/company-profile-schema.d.ts +4 -2
- package/dist/connect-attempts.d.ts +15 -0
- package/dist/connect-attempts.js +25 -0
- package/dist/connect-attempts.js.map +1 -0
- package/dist/connection-authority.d.ts +7 -63
- package/dist/connection-refresh-lock.d.ts +4 -0
- package/dist/connection-token-resolver.d.ts +8 -43
- package/dist/database-timing.d.ts +10 -0
- package/dist/database.d.ts +4 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.d.ts +9 -0
- package/dist/editable-artifacts.js +7 -5
- package/dist/index.d.ts +447 -78
- package/dist/index.js +12705 -12872
- package/dist/index.js.map +1 -1
- package/dist/insights-usage-bundle.d.ts +3 -0
- package/dist/integration-bindings.d.ts +1 -1
- package/dist/integration-facets.d.ts +18 -1
- package/dist/knowledge-entries-schema.d.ts +2 -0
- package/dist/knowledge-entries.d.ts +1 -0
- package/dist/knowledge-indexing.d.ts +1 -0
- package/dist/live-session-attempt.d.ts +35 -137
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/mcp-account-bindings.d.ts +4 -0
- package/dist/mcp-oauth.d.ts +11 -0
- package/dist/mcp-operations.js +2 -2
- package/dist/migrate.js +1 -1
- package/dist/organization-integration-policy.d.ts +51 -0
- package/dist/organization-integration-policy.js +18 -0
- package/dist/organization-integration-policy.js.map +1 -0
- package/dist/organization-usage.d.ts +20 -0
- package/dist/plugin-packages.d.ts +9 -12
- package/dist/pr-review.d.ts +0 -11
- package/dist/preference-registry-schema.d.ts +17 -0
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.d.ts +124 -6
- package/dist/retained-provider-commands.js +29 -97
- package/dist/retained-provider-commands.js.map +1 -1
- package/dist/retired-session-create-metadata.d.ts +4 -0
- package/dist/runtime-posture.d.ts +7 -5
- package/dist/schema.d.ts +876 -656
- package/dist/schema.js +5 -7
- package/dist/session-admission-block.d.ts +32 -0
- package/dist/session-attempt-writers.d.ts +4 -0
- package/dist/session-background-commands.js +5 -4
- package/dist/session-command-output.js +8 -126
- package/dist/session-command-output.js.map +1 -1
- package/dist/session-control.d.ts +5 -1
- package/dist/session-event-slices.d.ts +10 -0
- package/dist/session-event-slices.js +8 -222
- package/dist/session-event-slices.js.map +1 -1
- package/dist/session-file-attachments.d.ts +28 -0
- package/dist/session-goal-reports.d.ts +43 -0
- package/dist/session-mcp-approval.d.ts +4 -0
- package/dist/session-mcp-credential-rotation.js +5 -4
- package/dist/session-mcp-credential-rotation.js.map +1 -1
- package/dist/session-message-search.d.ts +22 -0
- package/dist/session-queue-commands.d.ts +2 -6
- package/dist/session-retry.d.ts +36 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/skill-source-release-impact.d.ts +18 -0
- package/dist/skill-source-release.d.ts +9 -0
- package/dist/skills.d.ts +7 -1
- package/dist/user-resource-authority.d.ts +0 -8
- package/dist/video-generation.js +3 -3
- package/dist/workspace-tool-defaults.js +5 -4
- package/dist/workspace-tool-defaults.js.map +1 -1
- package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
- package/drizzle/0469_knowledge_source_discovery.sql +85 -0
- package/drizzle/0470_organization_integration_policy.sql +151 -0
- package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
- package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
- package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
- package/drizzle/0474_goal_report_requirements.sql +156 -0
- package/drizzle/0475_session_event_history_statistics.sql +15 -0
- package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
- package/drizzle/0477_managed_sign_in_methods.sql +303 -0
- package/drizzle/0478_sender_owned_connections.sql +2772 -0
- package/drizzle/0479_connection_create_idempotency.sql +16 -0
- package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
- package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
- package/drizzle/0482_remove_packs.sql +417 -0
- package/drizzle/0483_preclaim_admission_block.sql +85 -0
- package/drizzle/0484_insights_usage_projection.sql +211 -0
- package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
- package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
- package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
- package/drizzle/0488_permanent_skill_removal.sql +207 -0
- package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
- package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
- package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
- package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
- package/drizzle/0493_stopping_command_error_containment.sql +58 -0
- package/drizzle/0494_mcp_account_bindings.sql +334 -0
- package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
- package/drizzle/0496_supervised_command_settlement.sql +386 -0
- package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
- package/drizzle/0498_enrollment_membership_fence.sql +87 -0
- package/drizzle/0499_session_attachment_access.sql +224 -0
- package/drizzle/0500_session_execution_epoch.sql +37 -0
- package/drizzle/0501_session_sharing_execution.sql +591 -0
- package/drizzle/0502_active_message_boundary_forks.sql +30 -0
- package/package.json +17 -5
- package/src/automations.ts +17 -73
- package/src/canonical-human-identities.ts +17 -5
- package/src/capability-components.ts +7 -24
- package/src/capability-integrations.ts +301 -21
- package/src/capability-reconciliation.ts +19 -0
- package/src/codex-capacity-recovery.ts +47 -0
- package/src/codex-failure-eligibility.ts +93 -0
- package/src/connect-attempts.ts +49 -18
- package/src/connection-authority.ts +12 -158
- package/src/connection-refresh-lock.ts +22 -0
- package/src/connection-token-resolver.ts +58 -530
- package/src/database-timing.ts +43 -0
- package/src/database.ts +61 -26
- package/src/editable-artifacts.ts +1 -1
- package/src/index.ts +3578 -1273
- package/src/insights-model-bundle.ts +19 -7
- package/src/insights-usage-bundle.ts +16 -19
- package/src/integration-bindings.ts +2 -7
- package/src/integration-facets.ts +73 -6
- package/src/knowledge-entries.ts +8 -1
- package/src/live-session-attempt.ts +0 -22
- package/src/mcp-account-bindings.ts +12 -0
- package/src/mcp-oauth.ts +26 -0
- package/src/organization-integration-policy.ts +235 -0
- package/src/organization-usage.ts +83 -0
- package/src/plugin-packages.ts +226 -42
- package/src/pr-review.ts +7 -69
- package/src/preference-registry-schema.ts +1 -0
- package/src/provision-roles.ts +50 -37
- package/src/retained-provider-commands.ts +286 -9
- package/src/retired-session-create-metadata.ts +16 -0
- package/src/runtime-posture.ts +217 -13
- package/src/schema.ts +104 -120
- package/src/session-admission-block.ts +29 -0
- package/src/session-attempt-writers.ts +48 -0
- package/src/session-control.ts +21 -5
- package/src/session-event-slices.ts +76 -26
- package/src/session-file-attachments.ts +66 -0
- package/src/session-goal-reports.ts +255 -0
- package/src/session-mcp-approval.ts +30 -0
- package/src/session-message-search.ts +348 -0
- package/src/session-queue-commands.ts +60 -36
- package/src/session-retry.ts +319 -0
- package/src/skill-config-migration.ts +11 -2
- package/src/skill-source-release-impact.ts +33 -0
- package/src/skill-source-release.ts +67 -26
- package/src/skills.ts +44 -6
- package/src/user-resource-authority.ts +0 -56
- package/dist/chunk-6T2GGYWE.js.map +0 -1
- package/dist/chunk-7HMK3B45.js.map +0 -1
- package/dist/chunk-DPONPYJD.js.map +0 -1
- package/dist/chunk-F73UKJY6.js.map +0 -1
- package/dist/chunk-T6YLHJLS.js.map +0 -1
- package/dist/host-mcp-binding-match.d.ts +0 -5
- package/dist/host-mcp-bindings.d.ts +0 -79
- package/dist/host-mcp-resolvers.d.ts +0 -47
- package/dist/host-mcp-task-authority.d.ts +0 -43
- package/dist/host-selection-identity.d.ts +0 -6
- package/dist/pack-components.d.ts +0 -83
- package/dist/pack-installations.d.ts +0 -86
- package/src/host-mcp-binding-match.ts +0 -34
- package/src/host-mcp-bindings.ts +0 -777
- package/src/host-mcp-resolvers.ts +0 -266
- package/src/host-mcp-task-authority.ts +0 -384
- package/src/host-selection-identity.ts +0 -29
- package/src/pack-components.ts +0 -980
- package/src/pack-installations.ts +0 -950
- /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
- /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
- /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 =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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((
|
|
221
|
-
else substring(raw from ${Math.min(start + 1, 2_147_483_647)}::integer for ${
|
|
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 +
|
|
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
|
+
}
|