@opengeni/db 4.3.3-canary.0 → 4.3.3-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.
@@ -26,6 +26,7 @@ export type InstalledPluginPackageSummary = {
26
26
  category: string;
27
27
  tags: string[];
28
28
  sourceUrl: string | null;
29
+ logoUrl: string | null;
29
30
  manifestDigest: string;
30
31
  installationVersion: number;
31
32
  componentCount: number;
@@ -0,0 +1,10 @@
1
+ /** Observational only: expiring a caller's wait never revokes a capture fence. */
2
+ export declare class SandboxTransitionWaitBudget {
3
+ private readonly waitMs;
4
+ private observedCapture;
5
+ private currentDeadline;
6
+ private readonly hardDeadline;
7
+ constructor(waitMs: number, startedAt: number);
8
+ get deadline(): number;
9
+ observeCapture(remainingMs: number | null | undefined, now: number): void;
10
+ }
@@ -1,10 +1,17 @@
1
1
  import { type SQL, type SQLWrapper } from "drizzle-orm";
2
2
  export declare const SESSION_MCP_PROGRESS_CHARS = 600;
3
+ export declare function sessionTextStoragePrefixChars(maxChars: number): number;
3
4
  export declare const SESSION_MCP_PROGRESS_STORAGE_CHARS: number;
4
5
  export declare const SESSION_MCP_PROGRESS_UTF16_BASE64_PATTERN = "([A-Za-z0-9+/]{8})*([A-Za-z0-9+/]{8}|[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=|[A-Za-z0-9+/]{5}[AQgw]==)";
5
6
  /** Validate the full scalar in storage; transfer only this boolean, not its suffix. */
6
7
  export declare function sessionMcpProgressScalarIsEncodedSql(value: SQL, codecVersion: SQLWrapper): SQL<boolean>;
7
- /** Project only the selected scalar, never the rest of a goal.progress payload. */
8
+ /** Project only the selected scalar, never the rest of its event payload. */
9
+ export declare function projectSessionTextPrefix(storedPrefix: string | null, storedChars: number | null, codecVersion: number | null, scalarIsEncoded: boolean, maxChars: number): {
10
+ text: string | null;
11
+ originalChars: number | null;
12
+ textTruncated?: true;
13
+ };
14
+ /** Existing MCP progress budget, using the shared logical-text prefix projection. */
8
15
  export declare function projectSessionMcpProgressText(storedPrefix: string | null, storedChars: number | null, codecVersion: number | null, scalarIsEncoded: boolean): {
9
16
  text: string | null;
10
17
  originalChars: number | null;
@@ -0,0 +1,177 @@
1
+ -- deployment-mode: rolling
2
+ -- Attribute accepted user messages through their exact triggering turn. The
3
+ -- source event deliberately has no turn_id; the deferred INSERT trigger runs
4
+ -- after the accepting transaction has installed its turn. Never infer a sender
5
+ -- from the session creator or from arbitrary payload fields. Existing immutable
6
+ -- export rows and consumer checkpoints are unchanged.
7
+
8
+ SET lock_timeout = '5s';
9
+ SET statement_timeout = '10min';
10
+
11
+ DO $migration$
12
+ DECLARE target_schema text := current_schema();
13
+ BEGIN
14
+ EXECUTE format($create$
15
+ CREATE OR REPLACE FUNCTION opengeni_private.enqueue_host_session_event_export()
16
+ RETURNS trigger
17
+ LANGUAGE plpgsql
18
+ SECURITY DEFINER
19
+ SET search_path = pg_catalog
20
+ AS $function$
21
+ DECLARE
22
+ v_enabled boolean;
23
+ v_initiator jsonb;
24
+ v_context jsonb := '{}'::jsonb;
25
+ v_origin text;
26
+ v_attribution_turn_id uuid;
27
+ v_source_payload_bytes integer;
28
+ v_export_payload jsonb;
29
+ v_export_payload_codec_version smallint;
30
+ v_payload_bytes integer;
31
+ BEGIN
32
+ IF NEW.type IN (
33
+ 'agent.message.delta', 'agent.reasoning.delta',
34
+ 'sandbox.command.output.delta', 'terminal.pty.output.delta'
35
+ ) THEN
36
+ RETURN NEW;
37
+ END IF;
38
+
39
+ SELECT c.session_events_enabled INTO v_enabled
40
+ FROM %1$I.host_export_config c WHERE c.id = 1
41
+ FOR SHARE;
42
+ IF coalesce(v_enabled, false) = false THEN
43
+ RETURN NEW;
44
+ END IF;
45
+
46
+ v_attribution_turn_id := NEW.turn_id;
47
+ IF v_attribution_turn_id IS NULL AND NEW.type = 'user.message' THEN
48
+ SELECT min(t.id::text)::uuid INTO v_attribution_turn_id
49
+ FROM %1$I.session_turns t
50
+ WHERE t.account_id = NEW.account_id
51
+ AND t.workspace_id = NEW.workspace_id
52
+ AND t.session_id = NEW.session_id
53
+ AND t.trigger_event_id = NEW.id
54
+ HAVING count(*) = 1;
55
+ END IF;
56
+
57
+ IF v_attribution_turn_id IS NOT NULL THEN
58
+ SELECT
59
+ CASE WHEN octet_length(t.initiator_subject_id) <= 1024 THEN
60
+ jsonb_strip_nulls(jsonb_build_object(
61
+ 'kind', t.initiator_kind,
62
+ 'subjectId', t.initiator_subject_id,
63
+ 'label', CASE
64
+ WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
65
+ THEN left(t.initiator_context ->> 'label', 256)
66
+ ELSE NULL
67
+ END
68
+ ))
69
+ ELSE NULL END,
70
+ jsonb_strip_nulls(jsonb_build_object(
71
+ 'label', CASE
72
+ WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
73
+ THEN left(t.initiator_context ->> 'label', 256)
74
+ ELSE NULL
75
+ END,
76
+ 'backfill', CASE
77
+ WHEN jsonb_typeof(t.initiator_context -> 'backfill') = 'boolean'
78
+ THEN t.initiator_context -> 'backfill'
79
+ ELSE NULL
80
+ END,
81
+ 'attributionOmitted', CASE
82
+ WHEN octet_length(t.initiator_subject_id) > 1024 THEN 'subject_id_too_large'
83
+ ELSE NULL
84
+ END
85
+ )),
86
+ t.source
87
+ INTO v_initiator, v_context, v_origin
88
+ FROM %1$I.session_turns t
89
+ WHERE t.account_id = NEW.account_id
90
+ AND t.workspace_id = NEW.workspace_id
91
+ AND t.session_id = NEW.session_id
92
+ AND t.id = v_attribution_turn_id;
93
+ ELSIF NEW.type = 'session.created' THEN
94
+ SELECT
95
+ CASE WHEN octet_length(s.created_by_subject_id) <= 1024 THEN
96
+ jsonb_strip_nulls(jsonb_build_object(
97
+ 'kind', s.created_by_kind,
98
+ 'subjectId', s.created_by_subject_id,
99
+ 'label', CASE
100
+ WHEN jsonb_typeof(s.created_by_context -> 'label') = 'string'
101
+ THEN left(s.created_by_context ->> 'label', 256)
102
+ ELSE NULL
103
+ END
104
+ ))
105
+ ELSE NULL END,
106
+ jsonb_strip_nulls(jsonb_build_object(
107
+ 'label', CASE
108
+ WHEN jsonb_typeof(s.created_by_context -> 'label') = 'string'
109
+ THEN left(s.created_by_context ->> 'label', 256)
110
+ ELSE NULL
111
+ END,
112
+ 'backfill', CASE
113
+ WHEN jsonb_typeof(s.created_by_context -> 'backfill') = 'boolean'
114
+ THEN s.created_by_context -> 'backfill'
115
+ ELSE NULL
116
+ END,
117
+ 'attributionOmitted', CASE
118
+ WHEN octet_length(s.created_by_subject_id) > 1024 THEN 'subject_id_too_large'
119
+ ELSE NULL
120
+ END
121
+ )),
122
+ NULL
123
+ INTO v_initiator, v_context, v_origin
124
+ FROM %1$I.sessions s
125
+ WHERE s.workspace_id = NEW.workspace_id AND s.id = NEW.session_id;
126
+ END IF;
127
+
128
+ v_source_payload_bytes := octet_length(NEW.payload::text);
129
+ v_export_payload := NEW.payload;
130
+ v_export_payload_codec_version := NEW.payload_codec_version;
131
+ IF v_source_payload_bytes > 65536 THEN
132
+ v_export_payload := jsonb_build_object(
133
+ '_hostExport', jsonb_build_object(
134
+ 'payloadMode', 'summary',
135
+ 'payloadTruncated', true,
136
+ 'originalBytes', v_source_payload_bytes,
137
+ 'sourceEventId', NEW.id,
138
+ 'fullPayload', 'retained in canonical session event'
139
+ ),
140
+ 'preview', '[host-export payload omitted at bounded projection boundary]'
141
+ );
142
+ -- The projection is literal PostgreSQL JSON, not an application-codec
143
+ -- envelope. Preserve codec truth only when the original payload is kept.
144
+ v_export_payload_codec_version := NULL;
145
+ END IF;
146
+
147
+ v_payload_bytes := octet_length(v_export_payload::text)
148
+ + octet_length(NEW.type)
149
+ + coalesce(octet_length(NEW.client_event_id), 0)
150
+ + coalesce(octet_length(NEW.turn_association), 0)
151
+ + coalesce(octet_length(NEW.duplicate_reason), 0)
152
+ + octet_length(coalesce(v_initiator, 'null'::jsonb)::text)
153
+ + octet_length(v_context::text)
154
+ + 768;
155
+ INSERT INTO %1$I.host_export_outbox (
156
+ export_kind, source_id, account_id, workspace_id, session_id,
157
+ turn_id, turn_generation, turn_attempt_id, session_sequence,
158
+ client_event_id, turn_association, duplicate_of_event_id, duplicate_reason,
159
+ event_type, idempotency_key, initiator, initiator_context, origin,
160
+ payload, payload_codec_version, envelope_bytes, occurred_at,
161
+ source_recorded_at, enqueued_at
162
+ ) VALUES (
163
+ 'session_event', NEW.id, NEW.account_id, NEW.workspace_id, NEW.session_id,
164
+ NEW.turn_id, NEW.turn_generation, NEW.turn_attempt_id, NEW.sequence,
165
+ NEW.client_event_id, NEW.turn_association, NEW.duplicate_of_event_id,
166
+ NEW.duplicate_reason,
167
+ NEW.type, 'session_event:' || NEW.id::text, v_initiator,
168
+ coalesce(v_context, '{}'::jsonb), v_origin, v_export_payload,
169
+ v_export_payload_codec_version, greatest(1, v_payload_bytes), NEW.occurred_at,
170
+ NEW.created_at, clock_timestamp()
171
+ )
172
+ ON CONFLICT (export_kind, source_id) DO NOTHING;
173
+ RETURN NEW;
174
+ END $function$;
175
+ $create$, target_schema);
176
+ END
177
+ $migration$;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/db",
3
- "version": "4.3.3-canary.0",
3
+ "version": "4.3.3-canary.2",
4
4
  "description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -98,11 +98,11 @@
98
98
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
99
99
  },
100
100
  "dependencies": {
101
- "@opengeni/codemode": "^0.5.6-canary.0",
102
- "@opengeni/codex": "^0.2.22-canary.8",
103
- "@opengeni/config": "^1.1.2-canary.0",
104
- "@opengeni/contracts": "^3.0.2-canary.0",
105
- "@opengeni/network": "^0.3.1-canary.8",
101
+ "@opengeni/codemode": "^0.5.6-canary.2",
102
+ "@opengeni/codex": "^0.2.22-canary.10",
103
+ "@opengeni/config": "^1.1.2-canary.2",
104
+ "@opengeni/contracts": "^3.0.2-canary.2",
105
+ "@opengeni/network": "^0.3.1-canary.10",
106
106
  "drizzle-orm": "^0.45.2",
107
107
  "postgres": "^3.4.7"
108
108
  },
@@ -0,0 +1,25 @@
1
+ /** Observations about this session, never allocator inputs or failover policy. */
2
+ export function codexSelectionDiagnostics(input: {
3
+ previousCredentialId: string | null;
4
+ credentialId: string;
5
+ reusedLease: boolean;
6
+ pinnedCredentialId: string | null;
7
+ pinSource?: "manual" | "policy" | null;
8
+ }) {
9
+ const transition =
10
+ input.previousCredentialId === input.credentialId
11
+ ? ("unchanged" as const)
12
+ : input.previousCredentialId === null
13
+ ? ("assigned" as const)
14
+ : ("switched" as const);
15
+ const source =
16
+ input.pinnedCredentialId === input.credentialId && input.pinSource !== "policy"
17
+ ? ("manual_pin" as const)
18
+ : ("allocator" as const);
19
+ const reason = input.reusedLease
20
+ ? ("lease_reused" as const)
21
+ : transition === "unchanged"
22
+ ? ("affinity_reused" as const)
23
+ : transition;
24
+ return { transition, source, reason };
25
+ }
@@ -504,7 +504,11 @@ export async function prepareComputerSessionCreate(
504
504
  state: "prepared",
505
505
  actorSubjectId: input.actorSubjectId,
506
506
  })
507
- .onConflictDoNothing({ target: schema.interactionOperations.operationId })
507
+ // The operation also has a workspace/operation unique index. Concurrent
508
+ // speculative inserts can collide there even when the primary-key
509
+ // arbiter would deduplicate them. Arbitrate every constraint, then use
510
+ // the scoped lookup and immutable request checks below to authorize replay.
511
+ .onConflictDoNothing()
508
512
  .returning();
509
513
  if (!insertedOperation) {
510
514
  const existing = await loadOperation(tx, input.workspaceId, input.operationId);