@opengeni/db 4.4.0-canary.0 → 4.4.0-canary.2
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/canonical-human-identities.js +4 -3
- package/dist/{chunk-2ZMTQVTR.js → chunk-2KOLL33E.js} +2 -2
- package/dist/chunk-45CP2YKY.js +1062 -0
- package/dist/chunk-45CP2YKY.js.map +1 -0
- package/dist/{chunk-6TKL3GQO.js → chunk-5UUWZDAV.js} +6531 -6480
- package/dist/chunk-5UUWZDAV.js.map +1 -0
- package/dist/{chunk-QLA3UQNX.js → chunk-6IJD3XXF.js} +17 -3
- package/dist/chunk-6IJD3XXF.js.map +1 -0
- package/dist/{chunk-232VC2LW.js → chunk-6V3HDEMR.js} +2 -2
- package/dist/{chunk-M7PSMVFF.js → chunk-CHBLBEU6.js} +2 -2
- package/dist/chunk-M4PKBJRS.js +20318 -0
- package/dist/chunk-M4PKBJRS.js.map +1 -0
- package/dist/chunk-S2TZ3F4L.js +237 -0
- package/dist/chunk-S2TZ3F4L.js.map +1 -0
- package/dist/{chunk-UK57QA2H.js → chunk-UOXWVP3T.js} +2 -2
- package/dist/{chunk-GVZJ7XSR.js → chunk-WFU6PS54.js} +11 -4
- package/dist/chunk-WFU6PS54.js.map +1 -0
- package/dist/{chunk-THJGMEK5.js → chunk-XJP7KJ6U.js} +121 -66
- package/dist/chunk-XJP7KJ6U.js.map +1 -0
- package/dist/connection-token-resolver.d.ts +2 -0
- package/dist/database.d.ts +2 -0
- package/dist/editable-artifact-durable-export.js +3 -2
- package/dist/editable-artifact-durable-export.js.map +1 -1
- package/dist/editable-artifacts.js +4 -3
- package/dist/external-identities.d.ts +1 -0
- package/dist/external-membership-operations.d.ts +44 -0
- package/dist/governed-learning-evaluator.d.ts +1 -14
- package/dist/host-mcp-bindings.d.ts +4 -0
- package/dist/host-mcp-resolvers.d.ts +47 -0
- package/dist/host-mcp-task-authority.d.ts +2 -6
- package/dist/index.d.ts +29 -31
- package/dist/index.js +8616 -8321
- package/dist/index.js.map +1 -1
- package/dist/knowledge-document-preparation.d.ts +56 -0
- package/dist/knowledge-entries-schema.d.ts +2479 -0
- package/dist/knowledge-entries.d.ts +421 -0
- package/dist/knowledge-indexing.d.ts +99 -0
- package/dist/knowledge-migration.d.ts +6 -0
- package/dist/managed-auth-session-sets.js +4 -3
- package/dist/mcp-operations.js +3 -2
- package/dist/mcp-operations.js.map +1 -1
- package/dist/migrate.js +2 -1
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +3 -2
- package/dist/retained-provider-commands.js.map +1 -1
- package/dist/runtime-posture.d.ts +7 -7
- package/dist/schema.d.ts +115 -12
- package/dist/schema.js +26 -1
- package/dist/session-background-commands.js +5 -4
- package/dist/session-command-output.js +8 -6
- package/dist/session-command-output.js.map +1 -1
- package/dist/session-event-slices.js +6 -4
- package/dist/session-event-slices.js.map +1 -1
- package/dist/session-mcp-credential-rotation.js +5 -4
- package/dist/session-mcp-credential-rotation.js.map +1 -1
- package/dist/session-tenancy.js +4 -3
- package/dist/video-generation.js +4 -3
- package/dist/workspace-artifacts.d.ts +1 -0
- package/dist/workspace-instruction-policies-schema.d.ts +21 -2
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-membership-access.d.ts +11 -0
- package/dist/workspace-tool-defaults.js +5 -4
- package/dist/workspace-tool-defaults.js.map +1 -1
- package/drizzle/0461_unified_knowledge.sql +2859 -0
- package/drizzle/0462_agent_instruction_non_destructive_edits.sql +212 -0
- package/drizzle/0463_host_mcp_resolver_registration.sql +111 -0
- package/drizzle/0464_external_workspace_membership_operations.sql +166 -0
- package/package.json +6 -6
- package/src/connection-token-resolver.ts +75 -6
- package/src/database.ts +12 -1
- package/src/external-identities.ts +1 -0
- package/src/external-membership-operations.ts +134 -0
- package/src/governed-learning-evaluator.ts +0 -69
- package/src/host-mcp-binding-match.ts +1 -0
- package/src/host-mcp-bindings.ts +24 -1
- package/src/host-mcp-resolvers.ts +266 -0
- package/src/host-mcp-task-authority.ts +11 -10
- package/src/index.ts +307 -128
- package/src/knowledge-document-preparation.ts +90 -0
- package/src/knowledge-entries-schema.ts +264 -0
- package/src/knowledge-entries.ts +860 -0
- package/src/knowledge-indexing.ts +126 -0
- package/src/knowledge-migration.ts +761 -0
- package/src/knowledge-source-sync.ts +46 -7
- package/src/migrate.ts +19 -0
- package/src/provision-roles.ts +23 -57
- package/src/runtime-posture.ts +103 -7
- package/src/schema.ts +15 -3
- package/src/skills.ts +4 -0
- package/src/video-generation.ts +14 -0
- package/src/workspace-artifacts.ts +2 -0
- package/src/workspace-instruction-policies-schema.ts +1 -0
- package/src/workspace-membership-access.ts +61 -0
- package/dist/chunk-6TKL3GQO.js.map +0 -1
- package/dist/chunk-EYF6MH6C.js +0 -4772
- package/dist/chunk-EYF6MH6C.js.map +0 -1
- package/dist/chunk-GSBONN55.js +0 -533
- package/dist/chunk-GSBONN55.js.map +0 -1
- package/dist/chunk-GVZJ7XSR.js.map +0 -1
- package/dist/chunk-QLA3UQNX.js.map +0 -1
- package/dist/chunk-THJGMEK5.js.map +0 -1
- /package/dist/{chunk-2ZMTQVTR.js.map → chunk-2KOLL33E.js.map} +0 -0
- /package/dist/{chunk-232VC2LW.js.map → chunk-6V3HDEMR.js.map} +0 -0
- /package/dist/{chunk-M7PSMVFF.js.map → chunk-CHBLBEU6.js.map} +0 -0
- /package/dist/{chunk-UK57QA2H.js.map → chunk-UOXWVP3T.js.map} +0 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
rawRows,
|
|
3
3
|
withWorkspaceRls
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WFU6PS54.js";
|
|
5
5
|
import {
|
|
6
|
-
LOSSLESS_JSON_STRING_PREFIX,
|
|
7
|
-
fromPostgresLosslessJson,
|
|
8
6
|
sessionEvents,
|
|
9
7
|
sessionTurns
|
|
10
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5UUWZDAV.js";
|
|
9
|
+
import {
|
|
10
|
+
LOSSLESS_JSON_STRING_PREFIX,
|
|
11
|
+
fromPostgresLosslessJson
|
|
12
|
+
} from "./chunk-S2TZ3F4L.js";
|
|
11
13
|
import "./chunk-PZ5AY32C.js";
|
|
12
14
|
|
|
13
15
|
// src/session-event-slices.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session-event-slices.ts"],"sourcesContent":["import { and, asc, desc, eq, gt, inArray, lt, notInArray, sql, type SQL } from \"drizzle-orm\";\nimport { resolveSessionEventTypeFilters, type SessionEvent } from \"@opengeni/contracts\";\nimport { rawRows, withWorkspaceRls, type Database } from \"./database\";\ntype ListSessionEventPageOptions = import(\"./index\").ListSessionEventPageOptions;\ntype SessionEventPage = import(\"./index\").SessionEventPage;\nimport { fromPostgresLosslessJson, LOSSLESS_JSON_STRING_PREFIX } from \"./lossless-json\";\nimport * as schema from \"./schema\";\n\nexport type SessionEventSliceOptions = ListSessionEventPageOptions & {\n sourceSequence?: number;\n sourceOffset?: number;\n view?: \"conversation\" | \"results\" | \"tools\";\n includeArguments?: boolean;\n includeOutput?: boolean;\n legacyUtf16?: boolean;\n};\nexport type SessionEventSlice = {\n offset: number;\n total: number;\n unit: \"codepoint\" | \"utf16\";\n text: string;\n omitted: boolean;\n};\nexport type SessionEventSlicePage = SessionEventPage & {\n slices?: Record<number, SessionEventSlice>;\n};\nconst WINDOW = 8192;\nconst STRUCTURED_BYTES = 1024 * 1024;\n\n/** Bounded transfer over retained scalar truth. JSONB extraction/detoasting still\n * costs PostgreSQL work proportional to the individual source value, not history.\n * This is a read projection, not authorization: the MCP caller must authorize the\n * target on every request. Both metadata and scalar reads retain workspace RLS.\n */\nexport async function listSessionEventSlices(\n db: Database,\n workspaceId: string,\n sessionId: string,\n options: SessionEventSliceOptions,\n legacyRead?: (options: ListSessionEventPageOptions) => Promise<SessionEventPage>,\n): Promise<SessionEventSlicePage> {\n // Finish an already-issued UTF-16 cursor using its original bounded reader.\n // New messages use source slices; no cursor offset is silently reinterpreted.\n if (options.legacyUtf16) {\n if (!legacyRead) throw new Error(\"Legacy continuation requires its original bounded reader\");\n return legacyRead({\n ...options,\n limit: 1,\n maxBytes: 1024 * 1024,\n });\n }\n const offset = options.sourceOffset ?? 0;\n if (!Number.isSafeInteger(offset) || offset < 0 || offset > 2_147_483_647)\n throw new Error(\"Invalid message continuation offset\");\n const direction = options.direction ?? (options.before === undefined ? \"after\" : \"before\");\n const limit = options.sourceSequence ? 1 : Math.max(1, Math.min(options.limit ?? 8, 8));\n const filters = resolveSessionEventTypeFilters(options);\n const predicates: SQL[] = [\n eq(schema.sessionEvents.workspaceId, workspaceId),\n eq(schema.sessionEvents.sessionId, sessionId),\n gt(schema.sessionEvents.sequence, Math.min(options.after ?? 0, 2_147_483_647)),\n // Same indexed exclusion as the audit/discovery reader. Do not substitute\n // turn status: terminal-before-claim prompts must remain excluded too.\n sql`(${schema.sessionEvents.type} <> 'user.message' or not exists (\n select 1 from ${schema.sessionTurns} unclaimed_prompt_turn\n where unclaimed_prompt_turn.workspace_id = ${workspaceId}\n and unclaimed_prompt_turn.workspace_id = ${schema.sessionEvents.workspaceId}\n and unclaimed_prompt_turn.session_id = ${schema.sessionEvents.sessionId}\n and unclaimed_prompt_turn.trigger_event_id = ${schema.sessionEvents.id}\n and unclaimed_prompt_turn.source in ('user', 'api')\n and unclaimed_prompt_turn.started_at is null\n ))`,\n ];\n if (options.before !== undefined && options.before <= 2_147_483_647)\n predicates.push(lt(schema.sessionEvents.sequence, options.before));\n if (filters.includeTypes.length)\n predicates.push(inArray(schema.sessionEvents.type, filters.includeTypes));\n if (filters.excludeTypes.length)\n predicates.push(notInArray(schema.sessionEvents.type, filters.excludeTypes));\n // Unlike audit `none` mode, this identity query does not compute a payload\n // projection merely to report its size. Only the selected scalar is extracted.\n const identities = await withWorkspaceRls(db, workspaceId, (tx) =>\n tx\n .select({\n id: schema.sessionEvents.id,\n sequence: schema.sessionEvents.sequence,\n type: sql<string>`case when octet_length(${schema.sessionEvents.type}) <= 256 then ${schema.sessionEvents.type} else 'session.event.envelope_omitted' end`,\n turnId: schema.sessionEvents.turnId,\n turnAssociation: sql<\n string | null\n >`case when ${schema.sessionEvents.turnAssociation} is null then null when ${schema.sessionEvents.turnAssociation} = 'current' then 'current' else 'late_rejected' end`,\n duplicateOfEventId: schema.sessionEvents.duplicateOfEventId,\n })\n .from(schema.sessionEvents)\n .where(and(...predicates))\n .orderBy(\n direction === \"before\"\n ? desc(schema.sessionEvents.sequence)\n : asc(schema.sessionEvents.sequence),\n )\n .limit(limit + 1),\n );\n const ordered = identities.slice(0, limit).map(\n (identity) =>\n ({\n ...identity,\n workspaceId,\n sessionId,\n payload: {},\n occurredAt: \"\",\n }) as SessionEvent,\n );\n const page: SessionEventPage = {\n events: [],\n hasMore: identities.length > limit,\n bytes: 2,\n fullPayloadsExact: true,\n direction,\n coveredSequence: null,\n nextAfter: null,\n nextBefore: null,\n truncatedBy: identities.length > limit ? \"count\" : null,\n };\n const slices: Record<number, SessionEventSlice> = {};\n const events: SessionEvent[] = [];\n let consumed = 0;\n await withWorkspaceRls(db, workspaceId, async (tx) => {\n for (const event of ordered) {\n consumed++;\n if (\n event.duplicateOfEventId ||\n (event.turnAssociation && event.turnAssociation !== \"current\")\n )\n continue;\n const p = schema.sessionEvents.payload;\n const output = event.type === \"agent.toolCall.output\";\n let value: SQL = sql`${p}->'text'`;\n if (options.view === \"tools\")\n value = output\n ? sql`${p}->'output'`\n : sql`coalesce(nullif(${p}->'arguments', 'null'::jsonb), ${p}->'args')`;\n else if (options.view === \"results\")\n value =\n event.type === \"turn.completed\"\n ? sql`coalesce(nullif(${p}->'output', 'null'::jsonb), ${p}->'result')`\n : sql`${p}`;\n const include =\n options.view !== \"tools\" || (output ? options.includeOutput : options.includeArguments);\n if (!include) value = sql`null::jsonb`;\n const start = options.sourceSequence === event.sequence ? offset : 0;\n const prefix = LOSSLESS_JSON_STRING_PREFIX;\n const raw = sql`raw`;\n const encoded = sql`encoded`;\n // Match exactly the JS codec's canonical-base64/even-byte acceptance.\n // A malformed active marker remains literal, just like the shared codec.\n const tagged = sql`case when version = 1\n and left(${raw}, ${prefix.length}) = ${prefix}\n and length(${encoded}) > 0\n and ${encoded} ~ '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'\n then (length(${encoded}) / 4 * 3 - case when right(${encoded}, 2) = '==' then 2 when right(${encoded}, 1) = '=' then 1 else 0 end) % 2 = 0\n and encode(decode(right(${encoded}, 4), 'base64'), 'base64') = right(${encoded}, 4)\n else false end`;\n // Aligned base64 windows represent groups of three UTF-16 units. Include\n // one preceding and one following unit to validate/avoid pair splits.\n const group = Math.floor(Math.max(0, start - 1) / 3);\n const safeMetadata = (key: string) =>\n sql`case when octet_length((${p}->${key})::text) <= 4096 then ${p}->${key} else null end`;\n const callId = sql`coalesce(nullif(${p}->'callId', 'null'::jsonb), nullif(${p}->'call_id', 'null'::jsonb), ${p}->'id')`;\n const metadataSql =\n options.view === \"tools\"\n ? sql`jsonb_strip_nulls(jsonb_build_object(\n 'callId', case when octet_length((${callId})::text) <= 4096 then ${callId} else null end,\n 'identityOmitted', case when octet_length((${callId})::text) > 4096 or octet_length((${p}->'name')::text) > 4096 then true else null end,\n 'name', ${safeMetadata(\"name\")}, 'isError', case when ${p}->'isError' = 'true'::jsonb or (${output} and ${p}->'output'->'isError' = 'true'::jsonb) then true else null end,\n 'maintenance', case when ${p} ? 'maintenance' then true else null end,\n 'segmentLimit', case when ${p} ? 'segmentLimit' then true else null end))`\n : options.view === \"results\" && event.type === \"turn.completed\"\n ? sql`jsonb_strip_nulls(jsonb_build_object(\n 'maintenance', case when ${p} ? 'maintenance' then true else null end,\n 'segmentLimit', case when ${p} ? 'segmentLimit' then true else null end))`\n : sql`'{}'::jsonb`;\n const [row] = await rawRows<{\n metadata: unknown;\n version: number | null;\n kind: string | null;\n tagged: boolean;\n total: number;\n window: string | null;\n structured: unknown;\n omitted: boolean;\n }>(\n tx,\n sql`with source as (\n select ${value} as value, ${metadataSql} as metadata,\n ${schema.sessionEvents.payloadCodecVersion} as version\n from ${schema.sessionEvents}\n where ${schema.sessionEvents.workspaceId} = ${workspaceId}\n and ${schema.sessionEvents.sessionId} = ${sessionId}\n and ${schema.sessionEvents.id} = ${event.id}\n and (${schema.sessionEvents.turnAssociation} is null or ${schema.sessionEvents.turnAssociation} = 'current')\n and ${schema.sessionEvents.duplicateOfEventId} is null\n limit 1\n ), scalar as (\n select *, jsonb_typeof(value) as kind,\n case when jsonb_typeof(value) = 'string' then value #>> '{}' else null end as raw,\n case when jsonb_typeof(value) <> 'string' then octet_length(value::text) else 0 end as structured_bytes\n from source offset 0\n ), encoding as (\n select *, case when version = 1 and left(raw, ${prefix.length}) = ${prefix}\n then substring(raw from ${prefix.length + 1}::integer) else null end as encoded\n from scalar offset 0\n ), validated as (\n select *, ${tagged} as tagged from encoding offset 0\n )\n select metadata, version, kind, tagged,\n case when kind = 'string' then\n 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\n else length(raw) end else 0 end as total,\n case when kind = 'string' then\n 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)\n else substring(raw from ${Math.min(start + 1, 2_147_483_647)}::integer for ${WINDOW}::integer) end else null end as window,\n case when kind <> 'string' and structured_bytes <= ${STRUCTURED_BYTES} then value else null end as structured,\n coalesce(kind <> 'string' and structured_bytes > ${STRUCTURED_BYTES}, false) as omitted\n from validated`,\n );\n if (!row) continue;\n const metadata = fromPostgresLosslessJson(row.metadata, row.version) as Record<\n string,\n unknown\n >;\n // Codec decoding can expand JSON escaping (notably NUL/lone surrogates).\n // Keep identities bounded too, without falling back to a different alias.\n for (const [key, budget] of [\n [\"callId\", 4096],\n [\"name\", 1024],\n ] as const) {\n if (\n metadata[key] !== undefined &&\n Buffer.byteLength(JSON.stringify(metadata[key]), \"utf8\") > budget\n ) {\n delete metadata[key];\n metadata.identityOmitted = true;\n }\n }\n let text = row.window ?? \"\";\n if (row.tagged) {\n // Window lengths are multiples of eight, so each partial block is also\n // a complete canonical encoding accepted by the existing lossless codec.\n text = fromPostgresLosslessJson(prefix + text, 1);\n const local = start - group * 3;\n if (\n local > 0 &&\n /[\\uD800-\\uDBFF]/.test(text[local - 1]!) &&\n /[\\uDC00-\\uDFFF]/.test(text[local] ?? \"\")\n )\n throw new Error(\"Invalid message continuation offset: splits a surrogate pair\");\n text = text.slice(local, local + WINDOW);\n if (start + text.length < row.total && /[\\uD800-\\uDBFF]/.test(text.at(-1)!))\n text = text.slice(0, -1);\n }\n if (start > row.total && row.kind === \"string\")\n throw new Error(\"Invalid message continuation offset\");\n const logical = row.omitted\n ? {}\n : row.kind === \"string\"\n ? text\n : fromPostgresLosslessJson(row.structured, row.version);\n let payload: Record<string, unknown> = { ...metadata };\n if (options.view === \"tools\") {\n if (row.kind !== null) payload[output ? \"output\" : \"arguments\"] = logical;\n } else if (options.view === \"results\") {\n if (event.type === \"turn.completed\") payload.output = logical;\n else payload = (logical as Record<string, unknown>) ?? {};\n } else payload.text = logical;\n if (row.omitted) payload = { ...payload, sourceOmitted: true };\n if (row.kind === \"string\" || row.omitted)\n slices[event.sequence] = {\n offset: start,\n total: row.total,\n unit: row.tagged ? \"utf16\" : \"codepoint\",\n text,\n omitted: row.omitted,\n };\n events.push({ ...event, payload });\n // One large structured value preserves the previous bounded JSON path,\n // without accumulating several megabyte-sized values in one source page.\n if (row.kind !== \"string\" && Buffer.byteLength(JSON.stringify(payload)) > WINDOW) break;\n if (\n row.kind === \"string\" &&\n start + (row.tagged ? text.length : Array.from(text).length) < row.total\n )\n break;\n }\n });\n // Preserve scan advancement even when every identity was excluded above.\n return {\n ...page,\n events: events.sort((a, b) => a.sequence - b.sequence),\n hasMore: page.hasMore || consumed < ordered.length,\n coveredSequence: consumed\n ? {\n first: Math.min(ordered[0]!.sequence, ordered[consumed - 1]!.sequence),\n last: Math.max(ordered[0]!.sequence, ordered[consumed - 1]!.sequence),\n }\n : null,\n bytes: Buffer.byteLength(JSON.stringify(events), \"utf8\"),\n fullPayloadsExact: !Object.values(slices).some((slice) => slice.omitted),\n slices,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/session-event-slices.ts"],"sourcesContent":["import { and, asc, desc, eq, gt, inArray, lt, notInArray, sql, type SQL } from \"drizzle-orm\";\nimport { resolveSessionEventTypeFilters, type SessionEvent } from \"@opengeni/contracts\";\nimport { rawRows, withWorkspaceRls, type Database } from \"./database\";\ntype ListSessionEventPageOptions = import(\"./index\").ListSessionEventPageOptions;\ntype SessionEventPage = import(\"./index\").SessionEventPage;\nimport { fromPostgresLosslessJson, LOSSLESS_JSON_STRING_PREFIX } from \"./lossless-json\";\nimport * as schema from \"./schema\";\n\nexport type SessionEventSliceOptions = ListSessionEventPageOptions & {\n sourceSequence?: number;\n sourceOffset?: number;\n view?: \"conversation\" | \"results\" | \"tools\";\n includeArguments?: boolean;\n includeOutput?: boolean;\n legacyUtf16?: boolean;\n};\nexport type SessionEventSlice = {\n offset: number;\n total: number;\n unit: \"codepoint\" | \"utf16\";\n text: string;\n omitted: boolean;\n};\nexport type SessionEventSlicePage = SessionEventPage & {\n slices?: Record<number, SessionEventSlice>;\n};\nconst WINDOW = 8192;\nconst STRUCTURED_BYTES = 1024 * 1024;\n\n/** Bounded transfer over retained scalar truth. JSONB extraction/detoasting still\n * costs PostgreSQL work proportional to the individual source value, not history.\n * This is a read projection, not authorization: the MCP caller must authorize the\n * target on every request. Both metadata and scalar reads retain workspace RLS.\n */\nexport async function listSessionEventSlices(\n db: Database,\n workspaceId: string,\n sessionId: string,\n options: SessionEventSliceOptions,\n legacyRead?: (options: ListSessionEventPageOptions) => Promise<SessionEventPage>,\n): Promise<SessionEventSlicePage> {\n // Finish an already-issued UTF-16 cursor using its original bounded reader.\n // New messages use source slices; no cursor offset is silently reinterpreted.\n if (options.legacyUtf16) {\n if (!legacyRead) throw new Error(\"Legacy continuation requires its original bounded reader\");\n return legacyRead({\n ...options,\n limit: 1,\n maxBytes: 1024 * 1024,\n });\n }\n const offset = options.sourceOffset ?? 0;\n if (!Number.isSafeInteger(offset) || offset < 0 || offset > 2_147_483_647)\n throw new Error(\"Invalid message continuation offset\");\n const direction = options.direction ?? (options.before === undefined ? \"after\" : \"before\");\n const limit = options.sourceSequence ? 1 : Math.max(1, Math.min(options.limit ?? 8, 8));\n const filters = resolveSessionEventTypeFilters(options);\n const predicates: SQL[] = [\n eq(schema.sessionEvents.workspaceId, workspaceId),\n eq(schema.sessionEvents.sessionId, sessionId),\n gt(schema.sessionEvents.sequence, Math.min(options.after ?? 0, 2_147_483_647)),\n // Same indexed exclusion as the audit/discovery reader. Do not substitute\n // turn status: terminal-before-claim prompts must remain excluded too.\n sql`(${schema.sessionEvents.type} <> 'user.message' or not exists (\n select 1 from ${schema.sessionTurns} unclaimed_prompt_turn\n where unclaimed_prompt_turn.workspace_id = ${workspaceId}\n and unclaimed_prompt_turn.workspace_id = ${schema.sessionEvents.workspaceId}\n and unclaimed_prompt_turn.session_id = ${schema.sessionEvents.sessionId}\n and unclaimed_prompt_turn.trigger_event_id = ${schema.sessionEvents.id}\n and unclaimed_prompt_turn.source in ('user', 'api')\n and unclaimed_prompt_turn.started_at is null\n ))`,\n ];\n if (options.before !== undefined && options.before <= 2_147_483_647)\n predicates.push(lt(schema.sessionEvents.sequence, options.before));\n if (filters.includeTypes.length)\n predicates.push(inArray(schema.sessionEvents.type, filters.includeTypes));\n if (filters.excludeTypes.length)\n predicates.push(notInArray(schema.sessionEvents.type, filters.excludeTypes));\n // Unlike audit `none` mode, this identity query does not compute a payload\n // projection merely to report its size. Only the selected scalar is extracted.\n const identities = await withWorkspaceRls(db, workspaceId, (tx) =>\n tx\n .select({\n id: schema.sessionEvents.id,\n sequence: schema.sessionEvents.sequence,\n type: sql<string>`case when octet_length(${schema.sessionEvents.type}) <= 256 then ${schema.sessionEvents.type} else 'session.event.envelope_omitted' end`,\n turnId: schema.sessionEvents.turnId,\n turnAssociation: sql<\n string | null\n >`case when ${schema.sessionEvents.turnAssociation} is null then null when ${schema.sessionEvents.turnAssociation} = 'current' then 'current' else 'late_rejected' end`,\n duplicateOfEventId: schema.sessionEvents.duplicateOfEventId,\n })\n .from(schema.sessionEvents)\n .where(and(...predicates))\n .orderBy(\n direction === \"before\"\n ? desc(schema.sessionEvents.sequence)\n : asc(schema.sessionEvents.sequence),\n )\n .limit(limit + 1),\n );\n const ordered = identities.slice(0, limit).map(\n (identity) =>\n ({\n ...identity,\n workspaceId,\n sessionId,\n payload: {},\n occurredAt: \"\",\n }) as SessionEvent,\n );\n const page: SessionEventPage = {\n events: [],\n hasMore: identities.length > limit,\n bytes: 2,\n fullPayloadsExact: true,\n direction,\n coveredSequence: null,\n nextAfter: null,\n nextBefore: null,\n truncatedBy: identities.length > limit ? \"count\" : null,\n };\n const slices: Record<number, SessionEventSlice> = {};\n const events: SessionEvent[] = [];\n let consumed = 0;\n await withWorkspaceRls(db, workspaceId, async (tx) => {\n for (const event of ordered) {\n consumed++;\n if (\n event.duplicateOfEventId ||\n (event.turnAssociation && event.turnAssociation !== \"current\")\n )\n continue;\n const p = schema.sessionEvents.payload;\n const output = event.type === \"agent.toolCall.output\";\n let value: SQL = sql`${p}->'text'`;\n if (options.view === \"tools\")\n value = output\n ? sql`${p}->'output'`\n : sql`coalesce(nullif(${p}->'arguments', 'null'::jsonb), ${p}->'args')`;\n else if (options.view === \"results\")\n value =\n event.type === \"turn.completed\"\n ? sql`coalesce(nullif(${p}->'output', 'null'::jsonb), ${p}->'result')`\n : sql`${p}`;\n const include =\n options.view !== \"tools\" || (output ? options.includeOutput : options.includeArguments);\n if (!include) value = sql`null::jsonb`;\n const start = options.sourceSequence === event.sequence ? offset : 0;\n const prefix = LOSSLESS_JSON_STRING_PREFIX;\n const raw = sql`raw`;\n const encoded = sql`encoded`;\n // Match exactly the JS codec's canonical-base64/even-byte acceptance.\n // A malformed active marker remains literal, just like the shared codec.\n const tagged = sql`case when version = 1\n and left(${raw}, ${prefix.length}) = ${prefix}\n and length(${encoded}) > 0\n and ${encoded} ~ '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'\n then (length(${encoded}) / 4 * 3 - case when right(${encoded}, 2) = '==' then 2 when right(${encoded}, 1) = '=' then 1 else 0 end) % 2 = 0\n and encode(decode(right(${encoded}, 4), 'base64'), 'base64') = right(${encoded}, 4)\n else false end`;\n // Aligned base64 windows represent groups of three UTF-16 units. Include\n // one preceding and one following unit to validate/avoid pair splits.\n const group = Math.floor(Math.max(0, start - 1) / 3);\n const safeMetadata = (key: string) =>\n sql`case when octet_length((${p}->${key})::text) <= 4096 then ${p}->${key} else null end`;\n const callId = sql`coalesce(nullif(${p}->'callId', 'null'::jsonb), nullif(${p}->'call_id', 'null'::jsonb), ${p}->'id')`;\n const metadataSql =\n options.view === \"tools\"\n ? sql`jsonb_strip_nulls(jsonb_build_object(\n 'callId', case when octet_length((${callId})::text) <= 4096 then ${callId} else null end,\n 'identityOmitted', case when octet_length((${callId})::text) > 4096 or octet_length((${p}->'name')::text) > 4096 then true else null end,\n 'name', ${safeMetadata(\"name\")}, 'isError', case when ${p}->'isError' = 'true'::jsonb or (${output} and ${p}->'output'->'isError' = 'true'::jsonb) then true else null end,\n 'maintenance', case when ${p} ? 'maintenance' then true else null end,\n 'segmentLimit', case when ${p} ? 'segmentLimit' then true else null end))`\n : options.view === \"results\" && event.type === \"turn.completed\"\n ? sql`jsonb_strip_nulls(jsonb_build_object(\n 'maintenance', case when ${p} ? 'maintenance' then true else null end,\n 'segmentLimit', case when ${p} ? 'segmentLimit' then true else null end))`\n : sql`'{}'::jsonb`;\n const [row] = await rawRows<{\n metadata: unknown;\n version: number | null;\n kind: string | null;\n tagged: boolean;\n total: number;\n window: string | null;\n structured: unknown;\n omitted: boolean;\n }>(\n tx,\n sql`with source as (\n select ${value} as value, ${metadataSql} as metadata,\n ${schema.sessionEvents.payloadCodecVersion} as version\n from ${schema.sessionEvents}\n where ${schema.sessionEvents.workspaceId} = ${workspaceId}\n and ${schema.sessionEvents.sessionId} = ${sessionId}\n and ${schema.sessionEvents.id} = ${event.id}\n and (${schema.sessionEvents.turnAssociation} is null or ${schema.sessionEvents.turnAssociation} = 'current')\n and ${schema.sessionEvents.duplicateOfEventId} is null\n limit 1\n ), scalar as (\n select *, jsonb_typeof(value) as kind,\n case when jsonb_typeof(value) = 'string' then value #>> '{}' else null end as raw,\n case when jsonb_typeof(value) <> 'string' then octet_length(value::text) else 0 end as structured_bytes\n from source offset 0\n ), encoding as (\n select *, case when version = 1 and left(raw, ${prefix.length}) = ${prefix}\n then substring(raw from ${prefix.length + 1}::integer) else null end as encoded\n from scalar offset 0\n ), validated as (\n select *, ${tagged} as tagged from encoding offset 0\n )\n select metadata, version, kind, tagged,\n case when kind = 'string' then\n 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\n else length(raw) end else 0 end as total,\n case when kind = 'string' then\n 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)\n else substring(raw from ${Math.min(start + 1, 2_147_483_647)}::integer for ${WINDOW}::integer) end else null end as window,\n case when kind <> 'string' and structured_bytes <= ${STRUCTURED_BYTES} then value else null end as structured,\n coalesce(kind <> 'string' and structured_bytes > ${STRUCTURED_BYTES}, false) as omitted\n from validated`,\n );\n if (!row) continue;\n const metadata = fromPostgresLosslessJson(row.metadata, row.version) as Record<\n string,\n unknown\n >;\n // Codec decoding can expand JSON escaping (notably NUL/lone surrogates).\n // Keep identities bounded too, without falling back to a different alias.\n for (const [key, budget] of [\n [\"callId\", 4096],\n [\"name\", 1024],\n ] as const) {\n if (\n metadata[key] !== undefined &&\n Buffer.byteLength(JSON.stringify(metadata[key]), \"utf8\") > budget\n ) {\n delete metadata[key];\n metadata.identityOmitted = true;\n }\n }\n let text = row.window ?? \"\";\n if (row.tagged) {\n // Window lengths are multiples of eight, so each partial block is also\n // a complete canonical encoding accepted by the existing lossless codec.\n text = fromPostgresLosslessJson(prefix + text, 1);\n const local = start - group * 3;\n if (\n local > 0 &&\n /[\\uD800-\\uDBFF]/.test(text[local - 1]!) &&\n /[\\uDC00-\\uDFFF]/.test(text[local] ?? \"\")\n )\n throw new Error(\"Invalid message continuation offset: splits a surrogate pair\");\n text = text.slice(local, local + WINDOW);\n if (start + text.length < row.total && /[\\uD800-\\uDBFF]/.test(text.at(-1)!))\n text = text.slice(0, -1);\n }\n if (start > row.total && row.kind === \"string\")\n throw new Error(\"Invalid message continuation offset\");\n const logical = row.omitted\n ? {}\n : row.kind === \"string\"\n ? text\n : fromPostgresLosslessJson(row.structured, row.version);\n let payload: Record<string, unknown> = { ...metadata };\n if (options.view === \"tools\") {\n if (row.kind !== null) payload[output ? \"output\" : \"arguments\"] = logical;\n } else if (options.view === \"results\") {\n if (event.type === \"turn.completed\") payload.output = logical;\n else payload = (logical as Record<string, unknown>) ?? {};\n } else payload.text = logical;\n if (row.omitted) payload = { ...payload, sourceOmitted: true };\n if (row.kind === \"string\" || row.omitted)\n slices[event.sequence] = {\n offset: start,\n total: row.total,\n unit: row.tagged ? \"utf16\" : \"codepoint\",\n text,\n omitted: row.omitted,\n };\n events.push({ ...event, payload });\n // One large structured value preserves the previous bounded JSON path,\n // without accumulating several megabyte-sized values in one source page.\n if (row.kind !== \"string\" && Buffer.byteLength(JSON.stringify(payload)) > WINDOW) break;\n if (\n row.kind === \"string\" &&\n start + (row.tagged ? text.length : Array.from(text).length) < row.total\n )\n break;\n }\n });\n // Preserve scan advancement even when every identity was excluded above.\n return {\n ...page,\n events: events.sort((a, b) => a.sequence - b.sequence),\n hasMore: page.hasMore || consumed < ordered.length,\n coveredSequence: consumed\n ? {\n first: Math.min(ordered[0]!.sequence, ordered[consumed - 1]!.sequence),\n last: Math.max(ordered[0]!.sequence, ordered[consumed - 1]!.sequence),\n }\n : null,\n bytes: Buffer.byteLength(JSON.stringify(events), \"utf8\"),\n fullPayloadsExact: !Object.values(slices).some((slice) => slice.omitted),\n slices,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
lockSessionEventWriteRows
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-M4PKBJRS.js";
|
|
4
|
+
import "./chunk-6IJD3XXF.js";
|
|
5
5
|
import {
|
|
6
6
|
rawRows,
|
|
7
7
|
setSubjectRlsContext,
|
|
8
8
|
withRlsContext
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WFU6PS54.js";
|
|
10
10
|
import {
|
|
11
11
|
apiKeys,
|
|
12
12
|
sessionCommandReceipts,
|
|
13
13
|
sessionMcpServers,
|
|
14
14
|
sessions
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-5UUWZDAV.js";
|
|
16
|
+
import "./chunk-S2TZ3F4L.js";
|
|
16
17
|
import "./chunk-PZ5AY32C.js";
|
|
17
18
|
|
|
18
19
|
// src/session-mcp-credential-rotation.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session-mcp-credential-rotation.ts"],"sourcesContent":["import { and, eq, inArray, sql } from \"drizzle-orm\";\nimport { RotateSessionMcpCredentialsReceipt, SessionTurnStatus } from \"@opengeni/contracts\";\nimport { rawRows, setSubjectRlsContext, withRlsContext, type Database } from \"./database\";\nimport { lockSessionEventWriteRows } from \"./session-control\";\nimport * as schema from \"./schema\";\n\nconst action = \"session.mcp.credentials.rotate\";\nconst liveTurnStatuses = SessionTurnStatus.exclude([\n \"completed\",\n \"failed\",\n \"cancelled\",\n \"superseded\",\n \"withdrawn_for_edit\",\n]).options;\n\nexport class SessionMcpCredentialRotationError extends Error {\n constructor(\n readonly code:\n | \"invalid_request\"\n | \"not_found\"\n | \"not_quiescent\"\n | \"version_conflict\"\n | \"destination_conflict\"\n | \"brokered_server\"\n | \"operation_reuse\"\n | \"receipt_key_unavailable\"\n | \"authority_revoked\",\n ) {\n super(code);\n this.name = \"SessionMcpCredentialRotationError\";\n }\n}\n\nexport type AtomicSessionMcpCredentialRotationInput = {\n accountId: string;\n workspaceId: string;\n sessionId: string;\n subjectId: string;\n actorType: \"human\" | \"service\";\n operationKey: string;\n requestDigest: string;\n digestKeyTag: string;\n updates: Array<{\n id: string;\n expectedCredentialVersion: number;\n expectedServerUrl: string;\n headersEncrypted: Record<string, string>;\n }>;\n /** Trusted request authorizer, mandatory even for a committed receipt replay.\n * Revalidate the original authenticated subject and permissions on this tx. */\n authorize: (tx: Database) => Promise<void>;\n};\n\n/** Existing command receipts own durable idempotency; this is not a new queue\n * command. No session/event/history/attempt/control/wake rows are mutated. */\nexport async function rotateSessionMcpCredentialsAtomically(\n db: Database,\n input: AtomicSessionMcpCredentialRotationInput,\n): Promise<RotateSessionMcpCredentialsReceipt> {\n if (\n !input.updates.length ||\n input.updates.length > 64 ||\n new Set(input.updates.map((update) => update.id)).size !== input.updates.length ||\n !/^[a-f0-9]{64}$/.test(input.requestDigest) ||\n !/^[a-f0-9]{64}$/.test(input.digestKeyTag)\n ) {\n throw new SessionMcpCredentialRotationError(\"invalid_request\");\n }\n return withRlsContext(\n db,\n input,\n async (tx) => {\n // Membership before tenancy/control/session is the canonical claim/removal\n // order. External continuation authorization reacquires this exclusive\n // membership fence, so taking shared first would introduce an upgrade.\n // Do not upgrade a previously acquired shared tenancy lock.\n await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(\n ${`organization-membership:${input.accountId}`}, 0))`);\n await tx.execute(sql`select pg_advisory_xact_lock_shared(hashtextextended(\n ${`session-tenancy:${input.workspaceId}`}, 0))`);\n await setSubjectRlsContext(tx, input.subjectId);\n const [target] = await tx\n .select({ id: schema.sessions.id })\n .from(schema.sessions)\n .where(\n and(\n eq(schema.sessions.accountId, input.accountId),\n eq(schema.sessions.workspaceId, input.workspaceId),\n eq(schema.sessions.id, input.sessionId),\n ),\n );\n if (!target) throw new SessionMcpCredentialRotationError(\"not_found\");\n const locks = await lockSessionEventWriteRows(tx, {\n workspaceId: input.workspaceId,\n controlLock: \"share\",\n sessionIds: [input.sessionId],\n });\n const session = locks.sessions[0];\n if (!session || session.accountId !== input.accountId) {\n throw new SessionMcpCredentialRotationError(\"not_found\");\n }\n // Hold a current API key through commit. The ordinary access resolver's\n // last-used write alone does not recheck a revocation racing its first read.\n // Take UPDATE directly because fresh resolution writes lastUsedAt.\n if (input.subjectId.startsWith(\"api_key:\")) {\n const keyId = input.subjectId.slice(\"api_key:\".length);\n const [key] = await tx\n .select({ id: schema.apiKeys.id, permissions: schema.apiKeys.permissions })\n .from(schema.apiKeys)\n .where(\n and(\n eq(schema.apiKeys.id, keyId),\n eq(schema.apiKeys.accountId, input.accountId),\n sql`(${schema.apiKeys.workspaceId} is null or ${schema.apiKeys.workspaceId} = ${input.workspaceId}::uuid)`,\n sql`${schema.apiKeys.revokedAt} is null`,\n sql`(${schema.apiKeys.expiresAt} is null or ${schema.apiKeys.expiresAt} > clock_timestamp())`,\n ),\n )\n .for(\"update\");\n if (\n !key ||\n (!key.permissions.includes(\"workspace:admin\") &&\n (!key.permissions.includes(\"sessions:control\") ||\n !key.permissions.includes(\"mcp_servers:attach\")))\n )\n throw new SessionMcpCredentialRotationError(\"authority_revoked\");\n }\n await input.authorize(tx);\n const [prior] = await tx\n .select()\n .from(schema.sessionCommandReceipts)\n .where(\n and(\n eq(schema.sessionCommandReceipts.accountId, input.accountId),\n eq(schema.sessionCommandReceipts.workspaceId, input.workspaceId),\n eq(schema.sessionCommandReceipts.targetSessionId, input.sessionId),\n eq(schema.sessionCommandReceipts.actorType, input.actorType),\n eq(schema.sessionCommandReceipts.actorSubjectId, input.subjectId),\n eq(schema.sessionCommandReceipts.action, action),\n eq(schema.sessionCommandReceipts.operationKey, input.operationKey),\n ),\n );\n if (prior) {\n if (prior.result.digestKeyTag !== input.digestKeyTag)\n throw new SessionMcpCredentialRotationError(\"receipt_key_unavailable\");\n if (prior.canonicalRequestHash !== input.requestDigest)\n throw new SessionMcpCredentialRotationError(\"operation_reuse\");\n return RotateSessionMcpCredentialsReceipt.parse(prior.result.receipt);\n }\n // Session lock serializes admission, claim, realtime start, interruption\n // settlement, and attempt-owned provider admission. Dormant goals/schedules\n // and unrelated viewers are deliberately not credential consumers.\n // Retained run snapshots and tool receipts need a live turn/attempt to\n // consume credentials; their historical presence alone is not activity.\n const [busy] = await rawRows<{ present: boolean }>(\n tx,\n sql`select (\n exists(select 1 from session_turns where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and status in (${sql.join(\n liveTurnStatuses.map((status) => sql`${status}`),\n sql`, `,\n )}))\n or exists(select 1 from session_turn_attempts a where a.workspace_id = ${input.workspaceId}::uuid\n and a.session_id = ${input.sessionId}::uuid and (a.state <> 'closed' or\n (a.quiesced_at is null and exists(select 1 from session_attempt_interruptions i\n where i.workspace_id = a.workspace_id and i.attempt_id = a.id))))\n or exists(select 1 from session_system_updates where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and state = 'pending')\n or exists(select 1 from sandbox_workspace_mutation_admissions where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and attempt_id is not null and settled_at is null)\n or exists(select 1 from session_realtime_modes where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and state = 'active')\n or exists(select 1 from session_realtime_connections where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and state not in ('failed', 'closed'))\n ) as present`,\n );\n if (!busy || busy.present) throw new SessionMcpCredentialRotationError(\"not_quiescent\");\n const rows = await tx\n .select()\n .from(schema.sessionMcpServers)\n .where(\n and(\n eq(schema.sessionMcpServers.workspaceId, input.workspaceId),\n eq(schema.sessionMcpServers.sessionId, input.sessionId),\n inArray(\n schema.sessionMcpServers.serverId,\n input.updates.map((update) => update.id),\n ),\n ),\n )\n .orderBy(schema.sessionMcpServers.serverId)\n .for(\"update\");\n for (const update of input.updates) {\n const row = rows.find((server) => server.serverId === update.id);\n if (!row || row.accountId !== input.accountId)\n throw new SessionMcpCredentialRotationError(\"not_found\");\n if (row.connectionRef) throw new SessionMcpCredentialRotationError(\"brokered_server\");\n if (row.url !== update.expectedServerUrl)\n throw new SessionMcpCredentialRotationError(\"destination_conflict\");\n if (\n !Number.isInteger(update.expectedCredentialVersion) ||\n update.expectedCredentialVersion < 1 ||\n update.expectedCredentialVersion >= 2_147_483_647 ||\n row.credentialVersion !== update.expectedCredentialVersion\n )\n throw new SessionMcpCredentialRotationError(\"version_conflict\");\n }\n const receipt: RotateSessionMcpCredentialsReceipt = {\n operationKey: input.operationKey,\n sessionId: input.sessionId,\n appliedAt: new Date().toISOString(),\n servers: input.updates.map((update) => ({\n id: update.id,\n credentialVersion: update.expectedCredentialVersion + 1,\n })),\n };\n for (const update of input.updates) {\n await tx\n .update(schema.sessionMcpServers)\n .set({\n headersEncrypted: update.headersEncrypted,\n credentialVersion: update.expectedCredentialVersion + 1,\n updatedAt: new Date(receipt.appliedAt),\n })\n .where(\n and(\n eq(schema.sessionMcpServers.workspaceId, input.workspaceId),\n eq(schema.sessionMcpServers.sessionId, input.sessionId),\n eq(schema.sessionMcpServers.serverId, update.id),\n ),\n );\n }\n await tx.insert(schema.sessionCommandReceipts).values({\n accountId: input.accountId,\n workspaceId: input.workspaceId,\n actorType: input.actorType,\n actorSubjectId: input.subjectId,\n action,\n targetSessionId: input.sessionId,\n operationKey: input.operationKey,\n canonicalRequestHash: input.requestDigest,\n result: { digestKeyTag: input.digestKeyTag, receipt },\n });\n return receipt;\n },\n undefined,\n \"none\",\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/session-mcp-credential-rotation.ts"],"sourcesContent":["import { and, eq, inArray, sql } from \"drizzle-orm\";\nimport { RotateSessionMcpCredentialsReceipt, SessionTurnStatus } from \"@opengeni/contracts\";\nimport { rawRows, setSubjectRlsContext, withRlsContext, type Database } from \"./database\";\nimport { lockSessionEventWriteRows } from \"./session-control\";\nimport * as schema from \"./schema\";\n\nconst action = \"session.mcp.credentials.rotate\";\nconst liveTurnStatuses = SessionTurnStatus.exclude([\n \"completed\",\n \"failed\",\n \"cancelled\",\n \"superseded\",\n \"withdrawn_for_edit\",\n]).options;\n\nexport class SessionMcpCredentialRotationError extends Error {\n constructor(\n readonly code:\n | \"invalid_request\"\n | \"not_found\"\n | \"not_quiescent\"\n | \"version_conflict\"\n | \"destination_conflict\"\n | \"brokered_server\"\n | \"operation_reuse\"\n | \"receipt_key_unavailable\"\n | \"authority_revoked\",\n ) {\n super(code);\n this.name = \"SessionMcpCredentialRotationError\";\n }\n}\n\nexport type AtomicSessionMcpCredentialRotationInput = {\n accountId: string;\n workspaceId: string;\n sessionId: string;\n subjectId: string;\n actorType: \"human\" | \"service\";\n operationKey: string;\n requestDigest: string;\n digestKeyTag: string;\n updates: Array<{\n id: string;\n expectedCredentialVersion: number;\n expectedServerUrl: string;\n headersEncrypted: Record<string, string>;\n }>;\n /** Trusted request authorizer, mandatory even for a committed receipt replay.\n * Revalidate the original authenticated subject and permissions on this tx. */\n authorize: (tx: Database) => Promise<void>;\n};\n\n/** Existing command receipts own durable idempotency; this is not a new queue\n * command. No session/event/history/attempt/control/wake rows are mutated. */\nexport async function rotateSessionMcpCredentialsAtomically(\n db: Database,\n input: AtomicSessionMcpCredentialRotationInput,\n): Promise<RotateSessionMcpCredentialsReceipt> {\n if (\n !input.updates.length ||\n input.updates.length > 64 ||\n new Set(input.updates.map((update) => update.id)).size !== input.updates.length ||\n !/^[a-f0-9]{64}$/.test(input.requestDigest) ||\n !/^[a-f0-9]{64}$/.test(input.digestKeyTag)\n ) {\n throw new SessionMcpCredentialRotationError(\"invalid_request\");\n }\n return withRlsContext(\n db,\n input,\n async (tx) => {\n // Membership before tenancy/control/session is the canonical claim/removal\n // order. External continuation authorization reacquires this exclusive\n // membership fence, so taking shared first would introduce an upgrade.\n // Do not upgrade a previously acquired shared tenancy lock.\n await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(\n ${`organization-membership:${input.accountId}`}, 0))`);\n await tx.execute(sql`select pg_advisory_xact_lock_shared(hashtextextended(\n ${`session-tenancy:${input.workspaceId}`}, 0))`);\n await setSubjectRlsContext(tx, input.subjectId);\n const [target] = await tx\n .select({ id: schema.sessions.id })\n .from(schema.sessions)\n .where(\n and(\n eq(schema.sessions.accountId, input.accountId),\n eq(schema.sessions.workspaceId, input.workspaceId),\n eq(schema.sessions.id, input.sessionId),\n ),\n );\n if (!target) throw new SessionMcpCredentialRotationError(\"not_found\");\n const locks = await lockSessionEventWriteRows(tx, {\n workspaceId: input.workspaceId,\n controlLock: \"share\",\n sessionIds: [input.sessionId],\n });\n const session = locks.sessions[0];\n if (!session || session.accountId !== input.accountId) {\n throw new SessionMcpCredentialRotationError(\"not_found\");\n }\n // Hold a current API key through commit. The ordinary access resolver's\n // last-used write alone does not recheck a revocation racing its first read.\n // Take UPDATE directly because fresh resolution writes lastUsedAt.\n if (input.subjectId.startsWith(\"api_key:\")) {\n const keyId = input.subjectId.slice(\"api_key:\".length);\n const [key] = await tx\n .select({ id: schema.apiKeys.id, permissions: schema.apiKeys.permissions })\n .from(schema.apiKeys)\n .where(\n and(\n eq(schema.apiKeys.id, keyId),\n eq(schema.apiKeys.accountId, input.accountId),\n sql`(${schema.apiKeys.workspaceId} is null or ${schema.apiKeys.workspaceId} = ${input.workspaceId}::uuid)`,\n sql`${schema.apiKeys.revokedAt} is null`,\n sql`(${schema.apiKeys.expiresAt} is null or ${schema.apiKeys.expiresAt} > clock_timestamp())`,\n ),\n )\n .for(\"update\");\n if (\n !key ||\n (!key.permissions.includes(\"workspace:admin\") &&\n (!key.permissions.includes(\"sessions:control\") ||\n !key.permissions.includes(\"mcp_servers:attach\")))\n )\n throw new SessionMcpCredentialRotationError(\"authority_revoked\");\n }\n await input.authorize(tx);\n const [prior] = await tx\n .select()\n .from(schema.sessionCommandReceipts)\n .where(\n and(\n eq(schema.sessionCommandReceipts.accountId, input.accountId),\n eq(schema.sessionCommandReceipts.workspaceId, input.workspaceId),\n eq(schema.sessionCommandReceipts.targetSessionId, input.sessionId),\n eq(schema.sessionCommandReceipts.actorType, input.actorType),\n eq(schema.sessionCommandReceipts.actorSubjectId, input.subjectId),\n eq(schema.sessionCommandReceipts.action, action),\n eq(schema.sessionCommandReceipts.operationKey, input.operationKey),\n ),\n );\n if (prior) {\n if (prior.result.digestKeyTag !== input.digestKeyTag)\n throw new SessionMcpCredentialRotationError(\"receipt_key_unavailable\");\n if (prior.canonicalRequestHash !== input.requestDigest)\n throw new SessionMcpCredentialRotationError(\"operation_reuse\");\n return RotateSessionMcpCredentialsReceipt.parse(prior.result.receipt);\n }\n // Session lock serializes admission, claim, realtime start, interruption\n // settlement, and attempt-owned provider admission. Dormant goals/schedules\n // and unrelated viewers are deliberately not credential consumers.\n // Retained run snapshots and tool receipts need a live turn/attempt to\n // consume credentials; their historical presence alone is not activity.\n const [busy] = await rawRows<{ present: boolean }>(\n tx,\n sql`select (\n exists(select 1 from session_turns where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and status in (${sql.join(\n liveTurnStatuses.map((status) => sql`${status}`),\n sql`, `,\n )}))\n or exists(select 1 from session_turn_attempts a where a.workspace_id = ${input.workspaceId}::uuid\n and a.session_id = ${input.sessionId}::uuid and (a.state <> 'closed' or\n (a.quiesced_at is null and exists(select 1 from session_attempt_interruptions i\n where i.workspace_id = a.workspace_id and i.attempt_id = a.id))))\n or exists(select 1 from session_system_updates where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and state = 'pending')\n or exists(select 1 from sandbox_workspace_mutation_admissions where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and attempt_id is not null and settled_at is null)\n or exists(select 1 from session_realtime_modes where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and state = 'active')\n or exists(select 1 from session_realtime_connections where workspace_id = ${input.workspaceId}::uuid\n and session_id = ${input.sessionId}::uuid and state not in ('failed', 'closed'))\n ) as present`,\n );\n if (!busy || busy.present) throw new SessionMcpCredentialRotationError(\"not_quiescent\");\n const rows = await tx\n .select()\n .from(schema.sessionMcpServers)\n .where(\n and(\n eq(schema.sessionMcpServers.workspaceId, input.workspaceId),\n eq(schema.sessionMcpServers.sessionId, input.sessionId),\n inArray(\n schema.sessionMcpServers.serverId,\n input.updates.map((update) => update.id),\n ),\n ),\n )\n .orderBy(schema.sessionMcpServers.serverId)\n .for(\"update\");\n for (const update of input.updates) {\n const row = rows.find((server) => server.serverId === update.id);\n if (!row || row.accountId !== input.accountId)\n throw new SessionMcpCredentialRotationError(\"not_found\");\n if (row.connectionRef) throw new SessionMcpCredentialRotationError(\"brokered_server\");\n if (row.url !== update.expectedServerUrl)\n throw new SessionMcpCredentialRotationError(\"destination_conflict\");\n if (\n !Number.isInteger(update.expectedCredentialVersion) ||\n update.expectedCredentialVersion < 1 ||\n update.expectedCredentialVersion >= 2_147_483_647 ||\n row.credentialVersion !== update.expectedCredentialVersion\n )\n throw new SessionMcpCredentialRotationError(\"version_conflict\");\n }\n const receipt: RotateSessionMcpCredentialsReceipt = {\n operationKey: input.operationKey,\n sessionId: input.sessionId,\n appliedAt: new Date().toISOString(),\n servers: input.updates.map((update) => ({\n id: update.id,\n credentialVersion: update.expectedCredentialVersion + 1,\n })),\n };\n for (const update of input.updates) {\n await tx\n .update(schema.sessionMcpServers)\n .set({\n headersEncrypted: update.headersEncrypted,\n credentialVersion: update.expectedCredentialVersion + 1,\n updatedAt: new Date(receipt.appliedAt),\n })\n .where(\n and(\n eq(schema.sessionMcpServers.workspaceId, input.workspaceId),\n eq(schema.sessionMcpServers.sessionId, input.sessionId),\n eq(schema.sessionMcpServers.serverId, update.id),\n ),\n );\n }\n await tx.insert(schema.sessionCommandReceipts).values({\n accountId: input.accountId,\n workspaceId: input.workspaceId,\n actorType: input.actorType,\n actorSubjectId: input.subjectId,\n action,\n targetSessionId: input.sessionId,\n operationKey: input.operationKey,\n canonicalRequestHash: input.requestDigest,\n result: { digestKeyTag: input.digestKeyTag, receipt },\n });\n return receipt;\n },\n undefined,\n \"none\",\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,SAAS,KAAK,IAAI,SAAS,WAAW;AACtC,SAAS,oCAAoC,yBAAyB;AAKtE,IAAM,SAAS;AACf,IAAM,mBAAmB,kBAAkB,QAAQ;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC,EAAE;AAEI,IAAM,oCAAN,cAAgD,MAAM;AAAA,EAC3D,YACW,MAUT;AACA,UAAM,IAAI;AAXD;AAYT,SAAK,OAAO;AAAA,EACd;AACF;AAwBA,eAAsB,sCACpB,IACA,OAC6C;AAC7C,MACE,CAAC,MAAM,QAAQ,UACf,MAAM,QAAQ,SAAS,MACvB,IAAI,IAAI,MAAM,QAAQ,IAAI,CAAC,WAAW,OAAO,EAAE,CAAC,EAAE,SAAS,MAAM,QAAQ,UACzE,CAAC,iBAAiB,KAAK,MAAM,aAAa,KAC1C,CAAC,iBAAiB,KAAK,MAAM,YAAY,GACzC;AACA,UAAM,IAAI,kCAAkC,iBAAiB;AAAA,EAC/D;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,OAAO;AAKZ,YAAM,GAAG,QAAQ;AAAA,QACf,2BAA2B,MAAM,SAAS,EAAE,OAAO;AACrD,YAAM,GAAG,QAAQ;AAAA,QACf,mBAAmB,MAAM,WAAW,EAAE,OAAO;AAC/C,YAAM,qBAAqB,IAAI,MAAM,SAAS;AAC9C,YAAM,CAAC,MAAM,IAAI,MAAM,GACpB,OAAO,EAAE,IAAW,SAAS,GAAG,CAAC,EACjC,KAAY,QAAQ,EACpB;AAAA,QACC;AAAA,UACE,GAAU,SAAS,WAAW,MAAM,SAAS;AAAA,UAC7C,GAAU,SAAS,aAAa,MAAM,WAAW;AAAA,UACjD,GAAU,SAAS,IAAI,MAAM,SAAS;AAAA,QACxC;AAAA,MACF;AACF,UAAI,CAAC,OAAQ,OAAM,IAAI,kCAAkC,WAAW;AACpE,YAAM,QAAQ,MAAM,0BAA0B,IAAI;AAAA,QAChD,aAAa,MAAM;AAAA,QACnB,aAAa;AAAA,QACb,YAAY,CAAC,MAAM,SAAS;AAAA,MAC9B,CAAC;AACD,YAAM,UAAU,MAAM,SAAS,CAAC;AAChC,UAAI,CAAC,WAAW,QAAQ,cAAc,MAAM,WAAW;AACrD,cAAM,IAAI,kCAAkC,WAAW;AAAA,MACzD;AAIA,UAAI,MAAM,UAAU,WAAW,UAAU,GAAG;AAC1C,cAAM,QAAQ,MAAM,UAAU,MAAM,WAAW,MAAM;AACrD,cAAM,CAAC,GAAG,IAAI,MAAM,GACjB,OAAO,EAAE,IAAW,QAAQ,IAAI,aAAoB,QAAQ,YAAY,CAAC,EACzE,KAAY,OAAO,EACnB;AAAA,UACC;AAAA,YACE,GAAU,QAAQ,IAAI,KAAK;AAAA,YAC3B,GAAU,QAAQ,WAAW,MAAM,SAAS;AAAA,YAC5C,OAAc,QAAQ,WAAW,eAAsB,QAAQ,WAAW,MAAM,MAAM,WAAW;AAAA,YACjG,MAAa,QAAQ,SAAS;AAAA,YAC9B,OAAc,QAAQ,SAAS,eAAsB,QAAQ,SAAS;AAAA,UACxE;AAAA,QACF,EACC,IAAI,QAAQ;AACf,YACE,CAAC,OACA,CAAC,IAAI,YAAY,SAAS,iBAAiB,MACzC,CAAC,IAAI,YAAY,SAAS,kBAAkB,KAC3C,CAAC,IAAI,YAAY,SAAS,oBAAoB;AAElD,gBAAM,IAAI,kCAAkC,mBAAmB;AAAA,MACnE;AACA,YAAM,MAAM,UAAU,EAAE;AACxB,YAAM,CAAC,KAAK,IAAI,MAAM,GACnB,OAAO,EACP,KAAY,sBAAsB,EAClC;AAAA,QACC;AAAA,UACE,GAAU,uBAAuB,WAAW,MAAM,SAAS;AAAA,UAC3D,GAAU,uBAAuB,aAAa,MAAM,WAAW;AAAA,UAC/D,GAAU,uBAAuB,iBAAiB,MAAM,SAAS;AAAA,UACjE,GAAU,uBAAuB,WAAW,MAAM,SAAS;AAAA,UAC3D,GAAU,uBAAuB,gBAAgB,MAAM,SAAS;AAAA,UAChE,GAAU,uBAAuB,QAAQ,MAAM;AAAA,UAC/C,GAAU,uBAAuB,cAAc,MAAM,YAAY;AAAA,QACnE;AAAA,MACF;AACF,UAAI,OAAO;AACT,YAAI,MAAM,OAAO,iBAAiB,MAAM;AACtC,gBAAM,IAAI,kCAAkC,yBAAyB;AACvE,YAAI,MAAM,yBAAyB,MAAM;AACvC,gBAAM,IAAI,kCAAkC,iBAAiB;AAC/D,eAAO,mCAAmC,MAAM,MAAM,OAAO,OAAO;AAAA,MACtE;AAMA,YAAM,CAAC,IAAI,IAAI,MAAM;AAAA,QACnB;AAAA,QACA;AAAA,gEACwD,MAAM,WAAW;AAAA,2BACtD,MAAM,SAAS,yBAAyB,IAAI;AAAA,UAC7D,iBAAiB,IAAI,CAAC,WAAW,MAAM,MAAM,EAAE;AAAA,UAC/C;AAAA,QACF,CAAC;AAAA,+EACsE,MAAM,WAAW;AAAA,6BACnE,MAAM,SAAS;AAAA;AAAA;AAAA,4EAGgC,MAAM,WAAW;AAAA,2BAClE,MAAM,SAAS;AAAA,2FACiD,MAAM,WAAW;AAAA,2BACjF,MAAM,SAAS;AAAA,4EACkC,MAAM,WAAW;AAAA,2BAClE,MAAM,SAAS;AAAA,kFACwC,MAAM,WAAW;AAAA,2BACxE,MAAM,SAAS;AAAA;AAAA,MAEpC;AACA,UAAI,CAAC,QAAQ,KAAK,QAAS,OAAM,IAAI,kCAAkC,eAAe;AACtF,YAAM,OAAO,MAAM,GAChB,OAAO,EACP,KAAY,iBAAiB,EAC7B;AAAA,QACC;AAAA,UACE,GAAU,kBAAkB,aAAa,MAAM,WAAW;AAAA,UAC1D,GAAU,kBAAkB,WAAW,MAAM,SAAS;AAAA,UACtD;AAAA,YACS,kBAAkB;AAAA,YACzB,MAAM,QAAQ,IAAI,CAAC,WAAW,OAAO,EAAE;AAAA,UACzC;AAAA,QACF;AAAA,MACF,EACC,QAAe,kBAAkB,QAAQ,EACzC,IAAI,QAAQ;AACf,iBAAW,UAAU,MAAM,SAAS;AAClC,cAAM,MAAM,KAAK,KAAK,CAAC,WAAW,OAAO,aAAa,OAAO,EAAE;AAC/D,YAAI,CAAC,OAAO,IAAI,cAAc,MAAM;AAClC,gBAAM,IAAI,kCAAkC,WAAW;AACzD,YAAI,IAAI,cAAe,OAAM,IAAI,kCAAkC,iBAAiB;AACpF,YAAI,IAAI,QAAQ,OAAO;AACrB,gBAAM,IAAI,kCAAkC,sBAAsB;AACpE,YACE,CAAC,OAAO,UAAU,OAAO,yBAAyB,KAClD,OAAO,4BAA4B,KACnC,OAAO,6BAA6B,cACpC,IAAI,sBAAsB,OAAO;AAEjC,gBAAM,IAAI,kCAAkC,kBAAkB;AAAA,MAClE;AACA,YAAM,UAA8C;AAAA,QAClD,cAAc,MAAM;AAAA,QACpB,WAAW,MAAM;AAAA,QACjB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,SAAS,MAAM,QAAQ,IAAI,CAAC,YAAY;AAAA,UACtC,IAAI,OAAO;AAAA,UACX,mBAAmB,OAAO,4BAA4B;AAAA,QACxD,EAAE;AAAA,MACJ;AACA,iBAAW,UAAU,MAAM,SAAS;AAClC,cAAM,GACH,OAAc,iBAAiB,EAC/B,IAAI;AAAA,UACH,kBAAkB,OAAO;AAAA,UACzB,mBAAmB,OAAO,4BAA4B;AAAA,UACtD,WAAW,IAAI,KAAK,QAAQ,SAAS;AAAA,QACvC,CAAC,EACA;AAAA,UACC;AAAA,YACE,GAAU,kBAAkB,aAAa,MAAM,WAAW;AAAA,YAC1D,GAAU,kBAAkB,WAAW,MAAM,SAAS;AAAA,YACtD,GAAU,kBAAkB,UAAU,OAAO,EAAE;AAAA,UACjD;AAAA,QACF;AAAA,MACJ;AACA,YAAM,GAAG,OAAc,sBAAsB,EAAE,OAAO;AAAA,QACpD,WAAW,MAAM;AAAA,QACjB,aAAa,MAAM;AAAA,QACnB,WAAW,MAAM;AAAA,QACjB,gBAAgB,MAAM;AAAA,QACtB;AAAA,QACA,iBAAiB,MAAM;AAAA,QACvB,cAAc,MAAM;AAAA,QACpB,sBAAsB,MAAM;AAAA,QAC5B,QAAQ,EAAE,cAAc,MAAM,cAAc,QAAQ;AAAA,MACtD,CAAC;AACD,aAAO;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
package/dist/session-tenancy.js
CHANGED
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
replayAppliedSessionFork,
|
|
15
15
|
sessionTenancyProductActivated,
|
|
16
16
|
transitionSessionVisibility
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-CHBLBEU6.js";
|
|
18
|
+
import "./chunk-WFU6PS54.js";
|
|
19
|
+
import "./chunk-5UUWZDAV.js";
|
|
20
|
+
import "./chunk-S2TZ3F4L.js";
|
|
20
21
|
import "./chunk-PZ5AY32C.js";
|
|
21
22
|
export {
|
|
22
23
|
SessionTenancyAccessError,
|
package/dist/video-generation.js
CHANGED
|
@@ -29,9 +29,10 @@ import {
|
|
|
29
29
|
settleVideoGenerationFailure,
|
|
30
30
|
settleVideoGenerationReady,
|
|
31
31
|
updateWorkspaceVideoGenerationPolicy
|
|
32
|
-
} from "./chunk-
|
|
33
|
-
import "./chunk-
|
|
34
|
-
import "./chunk-
|
|
32
|
+
} from "./chunk-6IJD3XXF.js";
|
|
33
|
+
import "./chunk-WFU6PS54.js";
|
|
34
|
+
import "./chunk-5UUWZDAV.js";
|
|
35
|
+
import "./chunk-S2TZ3F4L.js";
|
|
35
36
|
import "./chunk-PZ5AY32C.js";
|
|
36
37
|
export {
|
|
37
38
|
ACTIVE_VIDEO_GENERATION_STATUSES,
|
|
@@ -59,6 +59,7 @@ export declare function listWorkspaceArtifacts(db: Database, workspaceId: string
|
|
|
59
59
|
export declare function getWorkspaceArtifact(db: Database, workspaceId: string, artifactId: string): Promise<WorkspaceArtifactDetailResponse>;
|
|
60
60
|
export declare function getWorkspaceArtifactContentRef(db: Database, workspaceId: string, artifactId: string, versionId?: string): Promise<{
|
|
61
61
|
artifactId: string;
|
|
62
|
+
status: WorkspaceArtifact["status"];
|
|
62
63
|
version: WorkspaceArtifactVersion;
|
|
63
64
|
contentKey: string;
|
|
64
65
|
sourceKey: string | null;
|
|
@@ -223,6 +223,25 @@ export declare const workspaceInstructionPolicyRevisions: import("drizzle-orm/pg
|
|
|
223
223
|
identity: undefined;
|
|
224
224
|
generated: undefined;
|
|
225
225
|
}, {}, {}>;
|
|
226
|
+
agentLearningContext: import("drizzle-orm/pg-core").PgColumn<{
|
|
227
|
+
name: "agent_learning_context";
|
|
228
|
+
tableName: "workspace_instruction_policy_revisions";
|
|
229
|
+
dataType: "json";
|
|
230
|
+
columnType: "PgJsonb";
|
|
231
|
+
data: Record<string, unknown>;
|
|
232
|
+
driverParam: unknown;
|
|
233
|
+
notNull: false;
|
|
234
|
+
hasDefault: false;
|
|
235
|
+
isPrimaryKey: false;
|
|
236
|
+
isAutoincrement: false;
|
|
237
|
+
hasRuntimeDefault: false;
|
|
238
|
+
enumValues: undefined;
|
|
239
|
+
baseColumn: never;
|
|
240
|
+
identity: undefined;
|
|
241
|
+
generated: undefined;
|
|
242
|
+
}, {}, {
|
|
243
|
+
$type: Record<string, unknown>;
|
|
244
|
+
}>;
|
|
226
245
|
supersedesRevisionId: import("drizzle-orm/pg-core").PgColumn<{
|
|
227
246
|
name: "supersedes_revision_id";
|
|
228
247
|
tableName: "workspace_instruction_policy_revisions";
|
|
@@ -1633,7 +1652,7 @@ export declare const workspaceInstructionPolicySnapshots: import("drizzle-orm/pg
|
|
|
1633
1652
|
activationVersion: number;
|
|
1634
1653
|
activatedAt: string;
|
|
1635
1654
|
provenance: {
|
|
1636
|
-
source: "human" | "knowledge_proposal" | "legacy_import" | "onboarding";
|
|
1655
|
+
source: "agent_learning" | "human" | "knowledge_proposal" | "legacy_import" | "onboarding";
|
|
1637
1656
|
sourceIdHash: string | null;
|
|
1638
1657
|
};
|
|
1639
1658
|
}[];
|
|
@@ -1658,7 +1677,7 @@ export declare const workspaceInstructionPolicySnapshots: import("drizzle-orm/pg
|
|
|
1658
1677
|
activationVersion: number;
|
|
1659
1678
|
activatedAt: string;
|
|
1660
1679
|
provenance: {
|
|
1661
|
-
source: "human" | "knowledge_proposal" | "legacy_import" | "onboarding";
|
|
1680
|
+
source: "agent_learning" | "human" | "knowledge_proposal" | "legacy_import" | "onboarding";
|
|
1662
1681
|
sourceIdHash: string | null;
|
|
1663
1682
|
};
|
|
1664
1683
|
}[];
|
|
@@ -226,7 +226,7 @@ export declare function getOrCreateWorkspaceInstructionPolicySnapshot(db: Databa
|
|
|
226
226
|
activationVersion: number;
|
|
227
227
|
activatedAt: string;
|
|
228
228
|
provenance: {
|
|
229
|
-
source: "human" | "knowledge_proposal" | "legacy_import" | "onboarding";
|
|
229
|
+
source: "agent_learning" | "human" | "knowledge_proposal" | "legacy_import" | "onboarding";
|
|
230
230
|
sourceIdHash: string | null;
|
|
231
231
|
};
|
|
232
232
|
content: string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Permission, WorkspaceMember } from "@opengeni/contracts";
|
|
2
|
+
import { type Database } from "./database.js";
|
|
3
|
+
export declare function grantWorkspaceAccess(db: Database, input: {
|
|
4
|
+
accountId: string;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
subjectId: string;
|
|
7
|
+
subjectLabel?: string;
|
|
8
|
+
role?: string;
|
|
9
|
+
permissions: Permission[];
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
export declare function listWorkspaceMembers(db: Database, workspaceId: string): Promise<WorkspaceMember[]>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
lockWorkspaceInferenceControl
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-M4PKBJRS.js";
|
|
4
|
+
import "./chunk-6IJD3XXF.js";
|
|
5
5
|
import {
|
|
6
6
|
withWorkspaceRls
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-WFU6PS54.js";
|
|
8
|
+
import "./chunk-5UUWZDAV.js";
|
|
9
|
+
import "./chunk-S2TZ3F4L.js";
|
|
9
10
|
import "./chunk-PZ5AY32C.js";
|
|
10
11
|
|
|
11
12
|
// src/workspace-tool-defaults.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/workspace-tool-defaults.ts"],"sourcesContent":["import { WorkspaceSessionToolDefaultsPatch, type Workspace } from \"@opengeni/contracts\";\nimport { sql } from \"drizzle-orm\";\nimport { type Database, withWorkspaceRls } from \"./database\";\nimport { lockWorkspaceInferenceControl } from \"./session-control\";\n\ntype SettingsStore = {\n requireWorkspace(db: Database, workspaceId: string): Promise<Workspace>;\n updateWorkspaceSettings(\n db: Database,\n workspaceId: string,\n patch: Record<string, unknown>,\n options: { controlLockTimeoutMs?: number },\n ): Promise<Workspace>;\n};\n\n/** Independent nested patches; omission preserves and null restores inheritance. */\nexport async function updateWorkspaceSettingsWithToolDefaults(\n db: Database,\n workspaceId: string,\n patch: Record<string, unknown>,\n store: SettingsStore,\n options: { controlLockTimeoutMs?: number } = {},\n): Promise<Workspace> {\n if (patch.sessionToolDefaults === undefined)\n return store.updateWorkspaceSettings(db, workspaceId, patch, options);\n const tools = WorkspaceSessionToolDefaultsPatch.parse(patch.sessionToolDefaults);\n const { sessionToolDefaults: _tools, ...other } = patch;\n return withWorkspaceRls(db, workspaceId, async (scoped) =>\n scoped.transaction(async (tx) => {\n const transaction = tx as unknown as Database;\n await lockWorkspaceInferenceControl(transaction, workspaceId, \"update\", {\n ...(options.controlLockTimeoutMs !== undefined\n ? { lockTimeoutMs: options.controlLockTimeoutMs }\n : {}),\n });\n if (Object.keys(other).length)\n await store.updateWorkspaceSettings(transaction, workspaceId, other, options);\n await transaction.execute(sql`\n update workspaces set settings = jsonb_set(settings, '{sessionToolDefaults}',\n jsonb_strip_nulls(coalesce(settings->'sessionToolDefaults', '{}'::jsonb) || ${JSON.stringify(tools)}::jsonb)),\n updated_at = now()\n where id = ${workspaceId}::uuid\n `);\n return store.requireWorkspace(transaction, workspaceId);\n }),\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/workspace-tool-defaults.ts"],"sourcesContent":["import { WorkspaceSessionToolDefaultsPatch, type Workspace } from \"@opengeni/contracts\";\nimport { sql } from \"drizzle-orm\";\nimport { type Database, withWorkspaceRls } from \"./database\";\nimport { lockWorkspaceInferenceControl } from \"./session-control\";\n\ntype SettingsStore = {\n requireWorkspace(db: Database, workspaceId: string): Promise<Workspace>;\n updateWorkspaceSettings(\n db: Database,\n workspaceId: string,\n patch: Record<string, unknown>,\n options: { controlLockTimeoutMs?: number },\n ): Promise<Workspace>;\n};\n\n/** Independent nested patches; omission preserves and null restores inheritance. */\nexport async function updateWorkspaceSettingsWithToolDefaults(\n db: Database,\n workspaceId: string,\n patch: Record<string, unknown>,\n store: SettingsStore,\n options: { controlLockTimeoutMs?: number } = {},\n): Promise<Workspace> {\n if (patch.sessionToolDefaults === undefined)\n return store.updateWorkspaceSettings(db, workspaceId, patch, options);\n const tools = WorkspaceSessionToolDefaultsPatch.parse(patch.sessionToolDefaults);\n const { sessionToolDefaults: _tools, ...other } = patch;\n return withWorkspaceRls(db, workspaceId, async (scoped) =>\n scoped.transaction(async (tx) => {\n const transaction = tx as unknown as Database;\n await lockWorkspaceInferenceControl(transaction, workspaceId, \"update\", {\n ...(options.controlLockTimeoutMs !== undefined\n ? { lockTimeoutMs: options.controlLockTimeoutMs }\n : {}),\n });\n if (Object.keys(other).length)\n await store.updateWorkspaceSettings(transaction, workspaceId, other, options);\n await transaction.execute(sql`\n update workspaces set settings = jsonb_set(settings, '{sessionToolDefaults}',\n jsonb_strip_nulls(coalesce(settings->'sessionToolDefaults', '{}'::jsonb) || ${JSON.stringify(tools)}::jsonb)),\n updated_at = now()\n where id = ${workspaceId}::uuid\n `);\n return store.requireWorkspace(transaction, workspaceId);\n }),\n );\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,yCAAyD;AAClE,SAAS,WAAW;AAepB,eAAsB,wCACpB,IACA,aACA,OACA,OACA,UAA6C,CAAC,GAC1B;AACpB,MAAI,MAAM,wBAAwB;AAChC,WAAO,MAAM,wBAAwB,IAAI,aAAa,OAAO,OAAO;AACtE,QAAM,QAAQ,kCAAkC,MAAM,MAAM,mBAAmB;AAC/E,QAAM,EAAE,qBAAqB,QAAQ,GAAG,MAAM,IAAI;AAClD,SAAO;AAAA,IAAiB;AAAA,IAAI;AAAA,IAAa,OAAO,WAC9C,OAAO,YAAY,OAAO,OAAO;AAC/B,YAAM,cAAc;AACpB,YAAM,8BAA8B,aAAa,aAAa,UAAU;AAAA,QACtE,GAAI,QAAQ,yBAAyB,SACjC,EAAE,eAAe,QAAQ,qBAAqB,IAC9C,CAAC;AAAA,MACP,CAAC;AACD,UAAI,OAAO,KAAK,KAAK,EAAE;AACrB,cAAM,MAAM,wBAAwB,aAAa,aAAa,OAAO,OAAO;AAC9E,YAAM,YAAY,QAAQ;AAAA;AAAA,sFAEsD,KAAK,UAAU,KAAK,CAAC;AAAA;AAAA,mBAExF,WAAW;AAAA,KACzB;AACC,aAAO,MAAM,iBAAiB,aAAa,WAAW;AAAA,IACxD,CAAC;AAAA,EACH;AACF;","names":[]}
|