@opengeni/db 4.3.2 → 4.3.3-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-UVBPOGA7.js → chunk-232VC2LW.js} +2 -2
  3. package/dist/{chunk-DVSLOUEI.js → chunk-2ZMTQVTR.js} +2 -2
  4. package/dist/{chunk-AI4NC6XW.js → chunk-6TKL3GQO.js} +47 -1
  5. package/dist/chunk-6TKL3GQO.js.map +1 -0
  6. package/dist/{chunk-F4BIHJ7J.js → chunk-EYF6MH6C.js} +4 -4
  7. package/dist/{chunk-I4ITNMX2.js → chunk-GVZJ7XSR.js} +16 -4
  8. package/dist/{chunk-I4ITNMX2.js.map → chunk-GVZJ7XSR.js.map} +1 -1
  9. package/dist/{chunk-XBXANJQS.js → chunk-M7PSMVFF.js} +2 -2
  10. package/dist/{chunk-PFBUHOI5.js → chunk-QLA3UQNX.js} +3 -3
  11. package/dist/{chunk-YVYBAWBU.js → chunk-THJGMEK5.js} +53 -1
  12. package/dist/chunk-THJGMEK5.js.map +1 -0
  13. package/dist/{chunk-UUPY6IGP.js → chunk-UK57QA2H.js} +2 -2
  14. package/dist/codex-selection-diagnostics.d.ts +12 -0
  15. package/dist/connection-token-resolver.d.ts +2 -0
  16. package/dist/database.d.ts +7 -2
  17. package/dist/editable-artifact-durable-export.js +2 -2
  18. package/dist/editable-artifacts.js +3 -3
  19. package/dist/host-mcp-bindings.d.ts +3 -1
  20. package/dist/index.d.ts +32 -1
  21. package/dist/index.js +345 -75
  22. package/dist/index.js.map +1 -1
  23. package/dist/managed-auth-session-sets.js +3 -3
  24. package/dist/mcp-operations.d.ts +95 -0
  25. package/dist/mcp-operations.js +95 -0
  26. package/dist/mcp-operations.js.map +1 -0
  27. package/dist/plugin-packages.d.ts +1 -0
  28. package/dist/provision-roles.js +1 -1
  29. package/dist/retained-provider-commands.js +2 -2
  30. package/dist/runtime-posture.d.ts +3 -3
  31. package/dist/sandbox-transition-wait.d.ts +10 -0
  32. package/dist/schema.d.ts +517 -0
  33. package/dist/schema.js +3 -1
  34. package/dist/session-background-commands.js +4 -4
  35. package/dist/session-command-output.js +4 -4
  36. package/dist/session-event-slices.js +2 -2
  37. package/dist/session-mcp-progress.d.ts +8 -1
  38. package/dist/session-tenancy.js +3 -3
  39. package/dist/video-generation.js +3 -3
  40. package/dist/workspace-tool-defaults.js +4 -4
  41. package/drizzle/0459_mcp_operations.sql +396 -0
  42. package/drizzle/0460_host_export_message_attribution.sql +177 -0
  43. package/package.json +10 -6
  44. package/src/codex-selection-diagnostics.ts +25 -0
  45. package/src/computer-sessions.ts +5 -1
  46. package/src/connection-token-resolver.ts +2 -0
  47. package/src/database.ts +23 -2
  48. package/src/host-mcp-bindings.ts +14 -1
  49. package/src/index.ts +370 -60
  50. package/src/mcp-operations.ts +189 -0
  51. package/src/plugin-packages.ts +3 -0
  52. package/src/provision-roles.ts +6 -0
  53. package/src/runtime-posture.ts +50 -0
  54. package/src/sandbox-transition-wait.ts +45 -0
  55. package/src/schema.ts +46 -0
  56. package/src/session-mcp-progress.ts +26 -5
  57. package/dist/chunk-AI4NC6XW.js.map +0 -1
  58. package/dist/chunk-YVYBAWBU.js.map +0 -1
  59. /package/dist/{chunk-UVBPOGA7.js.map → chunk-232VC2LW.js.map} +0 -0
  60. /package/dist/{chunk-DVSLOUEI.js.map → chunk-2ZMTQVTR.js.map} +0 -0
  61. /package/dist/{chunk-F4BIHJ7J.js.map → chunk-EYF6MH6C.js.map} +0 -0
  62. /package/dist/{chunk-XBXANJQS.js.map → chunk-M7PSMVFF.js.map} +0 -0
  63. /package/dist/{chunk-PFBUHOI5.js.map → chunk-QLA3UQNX.js.map} +0 -0
  64. /package/dist/{chunk-UUPY6IGP.js.map → chunk-UK57QA2H.js.map} +0 -0
@@ -0,0 +1,189 @@
1
+ import { AttemptToolResult } from "@opengeni/contracts";
2
+ import { sql } from "drizzle-orm";
3
+ import { type Database, withRlsContext } from "./database";
4
+ import {
5
+ fromPostgresLosslessJson,
6
+ toPostgresLosslessJson,
7
+ LOSSLESS_CONTENT_CODEC_VERSION,
8
+ } from "./lossless-json";
9
+
10
+ /** Worker-authenticated invocation identity, never model/HTTP/request JSON.
11
+ * Possessing a DB handle or matching GUCs does NOT authenticate a caller.
12
+ * Bind this tuple only after verifying the worker invocation credential. SQL
13
+ * then derives the principal and rechecks canonical live disclosure authority. */
14
+ export type McpOperationAttempt = {
15
+ accountId: string;
16
+ workspaceId: string;
17
+ sessionId: string;
18
+ turnId: string;
19
+ attemptId: string;
20
+ executionGeneration: number;
21
+ };
22
+
23
+ /** Structurally aligned with runtime CapturedMcpOperation, without a runtime dependency. */
24
+ export type CaptureMcpOperationInput = {
25
+ operationId: string;
26
+ sourceCallId?: string;
27
+ serverId: string;
28
+ originalTool: string;
29
+ observerTool: string;
30
+ destinationDigest: string;
31
+ argumentDigest: string;
32
+ authorityDigest: string;
33
+ };
34
+ export type McpOperationSelector =
35
+ | { operationId: string }
36
+ | { sourceTurnId: string; sourceCallId: string };
37
+ export type McpOperationRecord = Omit<CaptureMcpOperationInput, "sourceCallId"> & {
38
+ sourceCallId: string | null;
39
+ accountId: string;
40
+ workspaceId: string;
41
+ sessionId: string;
42
+ sourceTurnId: string;
43
+ sourceAttemptId: string;
44
+ sourceExecutionGeneration: number;
45
+ principalKind: "subject" | "service";
46
+ principalId: string;
47
+ /** captured means possibly dispatched, NEVER permission to replay a mutation. */
48
+ originalOutcome: "captured" | "completed" | "outcome_unknown";
49
+ originalResult: AttemptToolResult | null;
50
+ observationResult: AttemptToolResult | null;
51
+ receiptRevision: string | null;
52
+ receiptDigest: string | null;
53
+ };
54
+ export type McpOperationLookup =
55
+ | { status: "found"; operation: McpOperationRecord }
56
+ | { status: "not_found" | "ambiguous" };
57
+
58
+ async function command<T>(
59
+ db: Database,
60
+ attempt: McpOperationAttempt,
61
+ action: string,
62
+ payload: object,
63
+ ): Promise<T> {
64
+ return withRlsContext(
65
+ db,
66
+ { accountId: attempt.accountId, workspaceId: attempt.workspaceId },
67
+ async (tx) => {
68
+ const result =
69
+ await tx.execute(sql`select mcp_operation_command(${JSON.stringify(attempt)}::jsonb,
70
+ ${action}::text, ${JSON.stringify(payload)}::jsonb) as value`);
71
+ const rows = Array.isArray(result) ? result : result.rows;
72
+ if (!rows?.[0]) throw new Error("MCP operation command returned no receipt");
73
+ return rows[0].value as T;
74
+ },
75
+ undefined,
76
+ "none", // SQL owns membership -> tenancy -> control -> session lock order.
77
+ );
78
+ }
79
+
80
+ export function captureMcpOperation(
81
+ db: Database,
82
+ attempt: McpOperationAttempt,
83
+ operation: CaptureMcpOperationInput,
84
+ ) {
85
+ return command<"created" | "existing">(db, attempt, "capture", operation);
86
+ }
87
+ export async function settleOriginalMcpOperation(
88
+ db: Database,
89
+ attempt: McpOperationAttempt,
90
+ input:
91
+ | { operationId: string; outcome: "completed"; result: AttemptToolResult }
92
+ | { operationId: string; outcome: "outcome_unknown" },
93
+ ): Promise<void> {
94
+ const payload =
95
+ input.outcome === "completed"
96
+ ? {
97
+ ...input,
98
+ result: toPostgresLosslessJson(AttemptToolResult.parse(input.result)),
99
+ resultCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
100
+ }
101
+ : input;
102
+ const result = await command<{ status: string }>(db, attempt, "settle_original", payload);
103
+ if (result.status !== "settled") throw new Error("Original MCP operation not found");
104
+ }
105
+ export async function readMcpOperation(
106
+ db: Database,
107
+ attempt: McpOperationAttempt,
108
+ selector: McpOperationSelector,
109
+ ): Promise<McpOperationLookup> {
110
+ const result = await command<McpOperationLookup>(db, attempt, "read", selector);
111
+ if (result.status !== "found") return result;
112
+ const { originalResultCodecVersion, observationResultCodecVersion, ...operation } =
113
+ result.operation as McpOperationRecord & {
114
+ originalResultCodecVersion: number | null;
115
+ observationResultCodecVersion: number | null;
116
+ };
117
+ return {
118
+ status: "found",
119
+ operation: {
120
+ ...operation,
121
+ originalResult: fromPostgresLosslessJson(
122
+ operation.originalResult,
123
+ originalResultCodecVersion,
124
+ ),
125
+ observationResult: fromPostgresLosslessJson(
126
+ operation.observationResult,
127
+ observationResultCodecVersion,
128
+ ),
129
+ },
130
+ };
131
+ }
132
+ /** Claims authorize only the read observer; expiry never authorizes a mutation. */
133
+ export function claimMcpOperationObservation(
134
+ db: Database,
135
+ attempt: McpOperationAttempt,
136
+ operationId: string,
137
+ ) {
138
+ return command<
139
+ { status: "claimed"; claimId: string } | { status: "busy" | "terminal" | "not_found" }
140
+ >(db, attempt, "claim_read", { operationId });
141
+ }
142
+ export function settleMcpOperationObservation(
143
+ db: Database,
144
+ attempt: McpOperationAttempt,
145
+ input: {
146
+ operationId: string;
147
+ claimId: string;
148
+ receiptRevision: string;
149
+ result: AttemptToolResult;
150
+ },
151
+ ) {
152
+ // This atomically stores one receipt, not an autonomous session notification.
153
+ // Explicit operation_read is a new ordinary tool call: its existing result
154
+ // lifecycle delivers the evidence. Repeated explicit reads legitimately have
155
+ // distinct call outputs while the original timeout and receipt stay immutable.
156
+ return command<
157
+ | { status: "settled" | "existing" | "conflict"; receiptDigest: string }
158
+ | { status: "stale_claim" | "not_found" | "original_completed" }
159
+ >(db, attempt, "settle_observation", {
160
+ ...input,
161
+ result: toPostgresLosslessJson(AttemptToolResult.parse(input.result)),
162
+ resultCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
163
+ });
164
+ }
165
+
166
+ /** Unknown/pending/error cleanup only; never releases or replaces a receipt. */
167
+ export function releaseMcpOperationObservation(
168
+ db: Database,
169
+ attempt: McpOperationAttempt,
170
+ input: { operationId: string; claimId: string },
171
+ ) {
172
+ return command<{ status: "released" | "stale_claim" | "terminal" | "not_found" }>(
173
+ db,
174
+ attempt,
175
+ "release_read",
176
+ input,
177
+ );
178
+ }
179
+
180
+ /** Bind only inside the trusted worker, after exact invocation authentication.
181
+ * Provider authorization and immutable binding fences remain the broker's job. */
182
+ export function createMcpOperationPersistence(db: Database, attempt: McpOperationAttempt) {
183
+ const frozen = { ...attempt };
184
+ return {
185
+ capture: (operation: CaptureMcpOperationInput) => captureMcpOperation(db, frozen, operation),
186
+ settleOriginal: (input: Parameters<typeof settleOriginalMcpOperation>[2]) =>
187
+ settleOriginalMcpOperation(db, frozen, input),
188
+ };
189
+ }
@@ -48,6 +48,7 @@ export type InstalledPluginPackageSummary = {
48
48
  category: string;
49
49
  tags: string[];
50
50
  sourceUrl: string | null;
51
+ logoUrl: string | null;
51
52
  manifestDigest: string;
52
53
  installationVersion: number;
53
54
  componentCount: number;
@@ -158,6 +159,7 @@ export async function listInstalledPluginPackages(
158
159
  return rows.map((row) => {
159
160
  const manifest = objectValue(row.manifest);
160
161
  const sourceUrl = stringValue(manifest.sourceUrl);
162
+ const logoUrl = stringValue(objectValue(manifest.discovery).logoUrl);
161
163
  const bom = pluginBom(manifest);
162
164
  if (row.status !== "active" && row.status !== "needs_attention") {
163
165
  throw new Error(`Unknown installed Plugin status: ${row.status}`);
@@ -173,6 +175,7 @@ export async function listInstalledPluginPackages(
173
175
  ? stringArray(manifest.tags)
174
176
  : stringArray(row.tags),
175
177
  sourceUrl: sourceUrl && safeHttpUrl(sourceUrl) ? sourceUrl : null,
178
+ logoUrl: logoUrl && safeHttpUrl(logoUrl) ? logoUrl : null,
176
179
  manifestDigest: row.manifestDigest,
177
180
  installationVersion: row.installationVersion,
178
181
  componentCount: bom.length,
@@ -618,6 +618,10 @@ DECLARE
618
618
  BEGIN
619
619
  EXECUTE format('REVOKE CREATE ON DATABASE %I FROM %I', current_database(), ${literal(role)});
620
620
  IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = ${literal(schema)}) THEN
621
+ IF to_regprocedure(format('%I.mcp_operation_command(jsonb,text,jsonb)', ${literal(schema)})) IS NOT NULL THEN
622
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.mcp_operation_command(jsonb,text,jsonb) FROM PUBLIC', ${literal(schema)});
623
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.mcp_operation_command(jsonb,text,jsonb) TO %I', ${literal(schema)}, ${literal(role)});
624
+ END IF;
621
625
  EXECUTE format('GRANT USAGE ON SCHEMA %I TO %I', ${literal(schema)}, ${literal(role)});
622
626
  EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM %I', ${literal(schema)}, ${literal(role)});
623
627
  EXECUTE format('REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA %I FROM %I', ${literal(schema)}, ${literal(role)});
@@ -2120,6 +2124,8 @@ BEGIN
2120
2124
  EXECUTE format('REVOKE CREATE ON SCHEMA opengeni_private FROM %I', ${literal(role)});
2121
2125
  EXECUTE format('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA opengeni_private TO %I', ${literal(role)});
2122
2126
  FOREACH routine_signature IN ARRAY ARRAY[
2127
+ 'guard_mcp_operation_immutable()',
2128
+ 'mcp_operation_command_scoped(jsonb,text,jsonb)',
2123
2129
  'guard_workspace_owned_skill_head_delete()',
2124
2130
  'guard_workspace_owned_skill_history_delete()'
2125
2131
  ] LOOP
@@ -51,7 +51,24 @@ const AUTOMATIC_SESSION_TITLE_FANOUT_OUTBOX_TABLE = "automatic_session_title_fan
51
51
  const AUTOMATIC_SESSION_TITLE_QUARANTINE_FENCE_ROUTINE =
52
52
  "acquire_automatic_session_title_quarantine_fences_v1(integer)";
53
53
 
54
+ const MCP_OPERATION_CAPABILITY_ROUTINE = "mcp_operation_command(jsonb, text, jsonb)";
55
+ const MCP_OPERATION_AUTHORITY_TABLES = [
56
+ "mcp_operations",
57
+ "sessions",
58
+ "session_turns",
59
+ "session_turn_attempts",
60
+ "workspaces",
61
+ "workspace_inference_controls",
62
+ "organization_memberships",
63
+ "workspace_memberships",
64
+ "external_identity_links",
65
+ "external_link_turn_authorities",
66
+ "host_mcp_turn_authorities",
67
+ "scheduled_task_runs",
68
+ ] as const;
54
69
  const OWNER_INTERNAL_PRIVATE_ROUTINES = new Set<string>([
70
+ "guard_mcp_operation_immutable()",
71
+ "mcp_operation_command_scoped(jsonb, text, jsonb)",
55
72
  "guard_workspace_owned_skill_head_delete()",
56
73
  "guard_workspace_owned_skill_history_delete()",
57
74
  ...ARTIFACT_OUTBOX_CAPABILITY_ROUTINES,
@@ -557,6 +574,7 @@ const XAI_AUTHORITY_TABLES = [
557
574
  ] as const;
558
575
 
559
576
  export const RUNTIME_TARGET_SCHEMA_CAPABILITY_ROUTINES = [
577
+ MCP_OPERATION_CAPABILITY_ROUTINE,
560
578
  "skill_apply_lifecycle(uuid, uuid, jsonb, jsonb)",
561
579
  COMPANY_BRAIN_CONTEXT_INSPECTION_ROUTINE,
562
580
  COMPANY_BRAIN_CONTEXT_SELECTION_ROUTINE,
@@ -807,6 +825,7 @@ export const FORCE_RLS_TABLES = [
807
825
  "managed_auth_login_transactions",
808
826
  "managed_auth_session_set_operations",
809
827
  "managed_auth_session_sets",
828
+ "mcp_operations",
810
829
  "memory_slack_publication_configurations",
811
830
  "memory_slack_publication_receipts",
812
831
  "memory_slack_publications",
@@ -1381,6 +1400,7 @@ export const PROTECTED_NO_DIRECT_DML_TABLES = [
1381
1400
  "managed_auth_login_transactions",
1382
1401
  "managed_auth_session_set_operations",
1383
1402
  "managed_auth_session_sets",
1403
+ "mcp_operations",
1384
1404
  "organization_company_profile_agent_policies",
1385
1405
  "organization_company_profile_agent_policy_events",
1386
1406
  "organization_invitation_binding_events",
@@ -2231,6 +2251,19 @@ export function evaluateRuntimeDatabasePosture(
2231
2251
  );
2232
2252
  }
2233
2253
  }
2254
+ } else if (routine.name === MCP_OPERATION_CAPABILITY_ROUTINE) {
2255
+ const missing = MCP_OPERATION_AUTHORITY_TABLES.filter((name) => !tableByName.has(name));
2256
+ if (missing.length > 0) {
2257
+ violations.push(`MCP operation authority tables are missing: ${missing.join(", ")}`);
2258
+ } else if (
2259
+ MCP_OPERATION_AUTHORITY_TABLES.some(
2260
+ (name) => tableByName.get(name)!.owner !== routine.owner,
2261
+ )
2262
+ ) {
2263
+ violations.push(
2264
+ `MCP operation capability ${routine.name} authority table owners do not match`,
2265
+ );
2266
+ }
2234
2267
  } else if (routine.name === KNOWLEDGE_SOURCE_SYNC_LOCK_AUTHORITY_ROUTINE) {
2235
2268
  const missingAuthorityTables = KNOWLEDGE_SOURCE_SYNC_LOCK_AUTHORITY_TABLES.filter(
2236
2269
  (tableName) => !tableByName.has(tableName),
@@ -3385,6 +3418,23 @@ export function evaluateRuntimeDatabasePosture(
3385
3418
  violations.push(`runtime role owns private routine ${routine.name}`);
3386
3419
  }
3387
3420
  const ownerInternalRoutine = OWNER_INTERNAL_PRIVATE_ROUTINES.has(routine.name);
3421
+ if (
3422
+ [
3423
+ "guard_mcp_operation_immutable()",
3424
+ "mcp_operation_command_scoped(jsonb, text, jsonb)",
3425
+ ].includes(routine.name)
3426
+ ) {
3427
+ if (routine.execute || routine.publicExecute) {
3428
+ violations.push(
3429
+ `runtime or PUBLIC has forbidden EXECUTE on MCP operation internal routine ${routine.name}`,
3430
+ );
3431
+ }
3432
+ if (routine.owner !== tableByName.get("mcp_operations")?.owner) {
3433
+ violations.push(
3434
+ `MCP operation internal routine ${routine.name} owner does not match ledger owner`,
3435
+ );
3436
+ }
3437
+ }
3388
3438
  if (
3389
3439
  [
3390
3440
  "guard_workspace_owned_skill_head_delete()",
@@ -0,0 +1,45 @@
1
+ import {
2
+ SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS,
3
+ SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS,
4
+ } from "@opengeni/config";
5
+
6
+ /** Observational only: expiring a caller's wait never revokes a capture fence. */
7
+ export class SandboxTransitionWaitBudget {
8
+ private observedCapture = false;
9
+ private currentDeadline: number;
10
+ private readonly hardDeadline: number;
11
+
12
+ constructor(
13
+ private readonly waitMs: number,
14
+ startedAt: number,
15
+ ) {
16
+ this.hardDeadline = startedAt + SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS;
17
+ this.currentDeadline = Math.min(this.hardDeadline, startedAt + waitMs);
18
+ }
19
+
20
+ get deadline(): number {
21
+ return this.currentDeadline;
22
+ }
23
+
24
+ observeCapture(remainingMs: number | null | undefined, now: number): void {
25
+ if (
26
+ this.waitMs === 0 ||
27
+ this.observedCapture ||
28
+ remainingMs === null ||
29
+ remainingMs === undefined ||
30
+ !Number.isSafeInteger(remainingMs) ||
31
+ remainingMs < 0 ||
32
+ remainingMs > SANDBOX_LIFECYCLE_TRANSITION_MAX_WAIT_MS
33
+ )
34
+ return;
35
+
36
+ // Honor the first observed child's frozen timeout across rolling settings
37
+ // changes. Neither an expired claim (remaining=0) nor a replacement capture
38
+ // can replenish the grace/budget on every poll and starve this caller.
39
+ this.observedCapture = true;
40
+ this.currentDeadline = Math.min(
41
+ this.hardDeadline,
42
+ Math.max(this.currentDeadline, now + remainingMs + SANDBOX_LIFECYCLE_RETRY_HANDOFF_GRACE_MS),
43
+ );
44
+ }
45
+ }
package/src/schema.ts CHANGED
@@ -6928,6 +6928,52 @@ export const sessionRealtimeContextProjections = pgTable(
6928
6928
  // First-class ownership for one accepted execution attempt. A workflow may
6929
6929
  // preallocate id, but this row is inserted only by the activity transaction
6930
6930
  // that actually claims the logical turn and registers its exact dispatch.
6931
+ // Protected EXECUTE-only ledger. SQL owns live-attempt/principal derivation,
6932
+ // immutable settlement, bounds, RLS and source ownership constraints.
6933
+ export const mcpOperations = pgTable(
6934
+ "mcp_operations",
6935
+ {
6936
+ operationId: uuid("operation_id").primaryKey(),
6937
+ accountId: uuid("account_id").notNull(),
6938
+ workspaceId: uuid("workspace_id").notNull(),
6939
+ sessionId: uuid("session_id").notNull(),
6940
+ sourceTurnId: uuid("source_turn_id").notNull(),
6941
+ sourceAttemptId: uuid("source_attempt_id").notNull(),
6942
+ sourceExecutionGeneration: integer("source_execution_generation").notNull(),
6943
+ sourceCallId: text("source_call_id"),
6944
+ principalKind: text("principal_kind").notNull(),
6945
+ principalId: text("principal_id").notNull(),
6946
+ principalMembershipId: uuid("principal_membership_id"),
6947
+ principalMembershipRevision: bigint("principal_membership_revision", { mode: "number" }),
6948
+ serverId: text("server_id").notNull(),
6949
+ originalTool: text("original_tool").notNull(),
6950
+ observerTool: text("observer_tool").notNull(),
6951
+ argumentDigest: text("argument_digest").notNull(),
6952
+ destinationDigest: text("destination_digest").notNull(),
6953
+ authorityDigest: text("authority_digest").notNull(),
6954
+ originalOutcome: text("original_outcome").notNull().default("captured"),
6955
+ originalResult: jsonb("original_result"),
6956
+ originalResultCodecVersion: integer("original_result_codec_version"),
6957
+ observationResult: jsonb("observation_result"),
6958
+ observationResultCodecVersion: integer("observation_result_codec_version"),
6959
+ receiptRevision: text("receipt_revision"),
6960
+ receiptDigest: text("receipt_digest"),
6961
+ observationClaimId: uuid("observation_claim_id"),
6962
+ observationClaimAttemptId: uuid("observation_claim_attempt_id"),
6963
+ observationClaimExpiresAt: timestamp("observation_claim_expires_at", { withTimezone: true }),
6964
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6965
+ observedAt: timestamp("observed_at", { withTimezone: true }),
6966
+ },
6967
+ (table) => ({
6968
+ source: index("mcp_operations_source_idx").on(
6969
+ table.workspaceId,
6970
+ table.sessionId,
6971
+ table.sourceTurnId,
6972
+ table.sourceCallId,
6973
+ ),
6974
+ }),
6975
+ );
6976
+
6931
6977
  export const sessionTurnAttempts = pgTable(
6932
6978
  "session_turn_attempts",
6933
6979
  {
@@ -11,8 +11,12 @@ export const SESSION_MCP_PROGRESS_CHARS = 600;
11
11
  // so a prefix cannot split the last delivered surrogate pair. Eight base64
12
12
  // characters encode three complete UTF-16 units, so a cut encoded prefix is
13
13
  // independently decodable by the canonical codec (no partial bytes/units).
14
- export const SESSION_MCP_PROGRESS_STORAGE_CHARS =
15
- LOSSLESS_JSON_STRING_PREFIX.length + Math.ceil(((SESSION_MCP_PROGRESS_CHARS + 1) * 2) / 3) * 8;
14
+ export function sessionTextStoragePrefixChars(maxChars: number): number {
15
+ return LOSSLESS_JSON_STRING_PREFIX.length + Math.ceil(((maxChars + 1) * 2) / 3) * 8;
16
+ }
17
+ export const SESSION_MCP_PROGRESS_STORAGE_CHARS = sessionTextStoragePrefixChars(
18
+ SESSION_MCP_PROGRESS_CHARS,
19
+ );
16
20
 
17
21
  // Match the canonical decoder's nonempty, round-tripping base64 of an even
18
22
  // byte count. Each 8-character block is 6 bytes. The final block contributes
@@ -38,12 +42,13 @@ export function sessionMcpProgressScalarIsEncodedSql(
38
42
  )`;
39
43
  }
40
44
 
41
- /** Project only the selected scalar, never the rest of a goal.progress payload. */
42
- export function projectSessionMcpProgressText(
45
+ /** Project only the selected scalar, never the rest of its event payload. */
46
+ export function projectSessionTextPrefix(
43
47
  storedPrefix: string | null,
44
48
  storedChars: number | null,
45
49
  codecVersion: number | null,
46
50
  scalarIsEncoded: boolean,
51
+ maxChars: number,
47
52
  ): { text: string | null; originalChars: number | null; textTruncated?: true } {
48
53
  if (storedPrefix === null) return { text: null, originalChars: null };
49
54
  const decoded = scalarIsEncoded
@@ -60,8 +65,24 @@ export function projectSessionMcpProgressText(
60
65
  ? storedChars
61
66
  : chars.length;
62
67
  return {
63
- text: chars.slice(0, SESSION_MCP_PROGRESS_CHARS).join(""),
68
+ text: chars.slice(0, maxChars).join(""),
64
69
  originalChars,
65
70
  ...(encodedPrefixWasCut ? { textTruncated: true } : {}),
66
71
  };
67
72
  }
73
+
74
+ /** Existing MCP progress budget, using the shared logical-text prefix projection. */
75
+ export function projectSessionMcpProgressText(
76
+ storedPrefix: string | null,
77
+ storedChars: number | null,
78
+ codecVersion: number | null,
79
+ scalarIsEncoded: boolean,
80
+ ): { text: string | null; originalChars: number | null; textTruncated?: true } {
81
+ return projectSessionTextPrefix(
82
+ storedPrefix,
83
+ storedChars,
84
+ codecVersion,
85
+ scalarIsEncoded,
86
+ SESSION_MCP_PROGRESS_CHARS,
87
+ );
88
+ }