@opengeni/db 0.28.1 → 0.31.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.
- package/dist/attached-browser-devices.d.ts +42 -0
- package/dist/attempt-tool-catalogs.d.ts +16 -0
- package/dist/browser-auth.d.ts +91 -0
- package/dist/browser-identities.d.ts +128 -0
- package/dist/browser-sessions.d.ts +272 -0
- package/dist/browser-state-artifacts.d.ts +17 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +192 -0
- package/dist/chunk-F5FT7BEZ.js +3535 -0
- package/dist/chunk-F5FT7BEZ.js.map +1 -0
- package/dist/chunk-ISI2TIUG.js +639 -0
- package/dist/chunk-ISI2TIUG.js.map +1 -0
- package/dist/chunk-MWDVXQOL.js +11639 -0
- package/dist/chunk-MWDVXQOL.js.map +1 -0
- package/dist/chunk-NBE6CHRI.js +955 -0
- package/dist/chunk-NBE6CHRI.js.map +1 -0
- package/dist/{chunk-DBS5HPEW.js → chunk-YHZXQ7HP.js} +760 -11
- package/dist/chunk-YHZXQ7HP.js.map +1 -0
- package/dist/codemode-operations.d.ts +117 -0
- package/dist/codex-token-resolver.d.ts +2 -2
- package/dist/company-profile-schema.d.ts +798 -0
- package/dist/company-profile.d.ts +268 -0
- package/dist/computer-sessions.d.ts +109 -0
- package/dist/connection-token-resolver.d.ts +9 -4
- package/dist/database.d.ts +52 -3
- package/dist/editable-artifact-durable-export.d.ts +153 -0
- package/dist/editable-artifact-durable-export.js +788 -0
- package/dist/editable-artifact-durable-export.js.map +1 -0
- package/dist/editable-artifact-materialization.d.ts +123 -0
- package/dist/editable-artifacts-schema.d.ts +4152 -0
- package/dist/editable-artifacts.d.ts +610 -0
- package/dist/editable-artifacts.js +26 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/environment-crypto.d.ts +8 -4
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +958 -126
- package/dist/index.js +31204 -12172
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +51 -4
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-features.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +3 -0
- package/dist/interaction-schema.d.ts +5372 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/lossless-columns.d.ts +58 -0
- package/dist/lossless-json.d.ts +39 -0
- package/dist/memory-domain.d.ts +3 -6
- package/dist/memory-governance.d.ts +2 -2
- package/dist/memory-slack-delivery.d.ts +101 -0
- package/dist/new-session-drafts.d.ts +2 -2
- package/dist/pack-components.d.ts +76 -0
- package/dist/pack-installations.d.ts +86 -0
- package/dist/persistence-errors.d.ts +7 -14
- package/dist/plugin-packages.d.ts +126 -0
- package/dist/preference-registry.d.ts +1 -1
- package/dist/provision-roles.d.ts +9 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +18 -6
- package/dist/schema.d.ts +19122 -8731
- package/dist/schema.js +173 -1
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +4 -4
- package/dist/session-queue-commands.d.ts +19 -11
- package/dist/session-realtime-context.d.ts +2 -2
- package/dist/session-realtime-ledger.d.ts +4 -4
- package/dist/session-realtime-mirror.d.ts +2 -2
- package/dist/session-realtime-state.d.ts +1 -1
- package/dist/session-realtime-terminal.d.ts +8 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/transcription-recordings-schema.d.ts +44 -6
- package/dist/transcription-recordings.d.ts +3 -3
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +216 -0
- package/dist/video-generation.js +66 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +1 -1
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-learning-policy-schema.d.ts +932 -0
- package/dist/workspace-learning-policy.d.ts +161 -0
- package/drizzle/0065_enrollment_credential_generation.sql +10 -0
- package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
- package/drizzle/0176_lossless_canonical_json.sql +975 -0
- package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
- package/drizzle/0178_permissioned_secret_reads.sql +14 -0
- package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
- package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
- package/drizzle/0181_connected_machine_removal.sql +52 -0
- package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
- package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
- package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
- package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
- package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
- package/drizzle/0187_generated_image_artifacts.sql +173 -0
- package/drizzle/0188_image_generation_retention_failure.sql +25 -0
- package/drizzle/0189_retained_session_image_formats.sql +13 -0
- package/drizzle/0190_timeline_annotations.sql +31 -0
- package/drizzle/0191_editable_artifact_engine.sql +3853 -0
- package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
- package/drizzle/0193_editable_artifact_authorization.sql +190 -0
- package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
- package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
- package/drizzle/0196_rig_provider_images.sql +152 -0
- package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
- package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
- package/drizzle/0199_workspace_learning_policy.sql +980 -0
- package/drizzle/0201_company_profile_authority.sql +849 -0
- package/drizzle/0202_document_index_checkpoints.sql +85 -0
- package/drizzle/0203_durable_video_generation.sql +353 -0
- package/drizzle/0204_video_generation_funding.sql +44 -0
- package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
- package/drizzle/0206_browser_sessions.sql +646 -0
- package/drizzle/0207_browser_identities.sql +379 -0
- package/drizzle/0208_attached_browser_devices.sql +165 -0
- package/drizzle/0209_computer_sessions.sql +575 -0
- package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
- package/drizzle/0211_editable_artifact_session_links.sql +58 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/package.json +18 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +1896 -0
- package/src/browser-identities.ts +1168 -0
- package/src/browser-sessions.ts +2005 -0
- package/src/browser-state-artifacts.ts +59 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1663 -0
- package/src/codemode-operations.ts +674 -0
- package/src/company-profile-schema.ts +143 -0
- package/src/company-profile.ts +968 -0
- package/src/computer-sessions.ts +1130 -0
- package/src/connection-token-resolver.ts +270 -25
- package/src/database.ts +434 -24
- package/src/editable-artifact-durable-export.ts +1201 -0
- package/src/editable-artifact-materialization.ts +893 -0
- package/src/editable-artifacts-schema.ts +879 -0
- package/src/editable-artifacts.ts +5404 -0
- package/src/environment-crypto.ts +22 -9
- package/src/generated-images.ts +740 -0
- package/src/index.ts +10117 -2730
- package/src/insights.ts +160 -13
- package/src/integration-bindings.ts +455 -0
- package/src/integration-features.ts +859 -0
- package/src/interaction-revisions.ts +40 -0
- package/src/interaction-schema.ts +1303 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/lossless-columns.ts +35 -0
- package/src/lossless-json.ts +322 -0
- package/src/memory-domain.ts +5 -44
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +929 -0
- package/src/persistence-errors.ts +26 -34
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +302 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +514 -5
- package/src/schema.ts +5177 -2957
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +133 -81
- package/src/session-queue-commands.ts +511 -247
- package/src/session-realtime-context.ts +20 -7
- package/src/session-realtime-ledger.ts +32 -10
- package/src/session-realtime-mirror.ts +4 -2
- package/src/session-realtime-terminal.ts +89 -21
- package/src/session-realtime.ts +10 -9
- package/src/session-tool-call-settlement.ts +38 -15
- package/src/slack-user-link-access.ts +702 -0
- package/src/transcription-recordings-schema.ts +5 -2
- package/src/transcription-recordings.ts +70 -40
- package/src/video-generation.ts +1452 -0
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/dist/chunk-DBS5HPEW.js.map +0 -1
- package/dist/chunk-HT5T62CC.js +0 -6803
- package/dist/chunk-HT5T62CC.js.map +0 -1
- package/dist/event-payload-sanitizer.d.ts +0 -32
- package/src/event-payload-sanitizer.ts +0 -377
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOSSLESS_CONTENT_WRITER_APPLICATION_NAME,
|
|
3
|
+
schema_exports,
|
|
4
|
+
sessions,
|
|
5
|
+
workspaceSessionActivityRevisions,
|
|
6
|
+
workspaces
|
|
7
|
+
} from "./chunk-MWDVXQOL.js";
|
|
8
|
+
|
|
9
|
+
// src/persistence-errors.ts
|
|
10
|
+
var SQLSTATE_KEYS = ["sqlState", "sqlstate", "code"];
|
|
11
|
+
var NESTED_ERROR_KEYS = ["cause", "original", "driverError", "error", "errors"];
|
|
12
|
+
var DATABASE_ERROR_NAMES = /* @__PURE__ */ new Set(["DatabaseError", "DrizzleQueryError", "PostgresError"]);
|
|
13
|
+
var DATABASE_DIAGNOSTIC_KEYS = [
|
|
14
|
+
"severity",
|
|
15
|
+
"schema_name",
|
|
16
|
+
"table_name",
|
|
17
|
+
"column_name",
|
|
18
|
+
"data_type_name",
|
|
19
|
+
"constraint_name",
|
|
20
|
+
"routine"
|
|
21
|
+
];
|
|
22
|
+
var SAFE_FACT_KEYS = [
|
|
23
|
+
["severity", "severity"],
|
|
24
|
+
["schema_name", "schema"],
|
|
25
|
+
["schema", "schema"],
|
|
26
|
+
["table_name", "table"],
|
|
27
|
+
["table", "table"],
|
|
28
|
+
["column_name", "column"],
|
|
29
|
+
["column", "column"],
|
|
30
|
+
["data_type_name", "dataType"],
|
|
31
|
+
["dataType", "dataType"],
|
|
32
|
+
["constraint_name", "constraint"],
|
|
33
|
+
["constraint", "constraint"],
|
|
34
|
+
["routine", "routine"]
|
|
35
|
+
];
|
|
36
|
+
function isRecord(value) {
|
|
37
|
+
return Boolean(value && typeof value === "object");
|
|
38
|
+
}
|
|
39
|
+
function safeFact(value) {
|
|
40
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function nestedPostgresSqlState(error) {
|
|
44
|
+
const queue = [error];
|
|
45
|
+
const seen = /* @__PURE__ */ new Set();
|
|
46
|
+
let fallback = null;
|
|
47
|
+
while (queue.length > 0 && seen.size < 64) {
|
|
48
|
+
const current = queue.shift();
|
|
49
|
+
if (!isRecord(current) || seen.has(current)) continue;
|
|
50
|
+
seen.add(current);
|
|
51
|
+
for (const key of SQLSTATE_KEYS) {
|
|
52
|
+
const value = current[key];
|
|
53
|
+
if (typeof value !== "string" || !/^[0-9A-Z]{5}$/i.test(value)) continue;
|
|
54
|
+
const normalized = value.toUpperCase();
|
|
55
|
+
if (normalized === "40P01" || normalized === "40001") return normalized;
|
|
56
|
+
fallback ??= normalized;
|
|
57
|
+
}
|
|
58
|
+
for (const key of NESTED_ERROR_KEYS) {
|
|
59
|
+
const nested = current[key];
|
|
60
|
+
if (Array.isArray(nested)) queue.push(...nested);
|
|
61
|
+
else if (nested !== void 0) queue.push(nested);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return fallback;
|
|
65
|
+
}
|
|
66
|
+
function databaseFailureCode(sqlState) {
|
|
67
|
+
if (sqlState === "40P01") return "db_deadlock";
|
|
68
|
+
if (sqlState === "40001") return "db_serialization_failure";
|
|
69
|
+
return "db_failure";
|
|
70
|
+
}
|
|
71
|
+
function isRetryablePersistenceSqlState(sqlState) {
|
|
72
|
+
return sqlState === "40P01" || sqlState === "40001";
|
|
73
|
+
}
|
|
74
|
+
function isDatabasePersistenceFailure(error) {
|
|
75
|
+
if (nestedPostgresSqlState(error) !== null) return true;
|
|
76
|
+
const queue = [error];
|
|
77
|
+
const seen = /* @__PURE__ */ new Set();
|
|
78
|
+
while (queue.length > 0 && seen.size < 64) {
|
|
79
|
+
const current = queue.shift();
|
|
80
|
+
if (!isRecord(current) || seen.has(current)) continue;
|
|
81
|
+
seen.add(current);
|
|
82
|
+
if (typeof current.name === "string" && DATABASE_ERROR_NAMES.has(current.name)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (typeof current.query === "string" && (Object.hasOwn(current, "params") || Object.hasOwn(current, "parameters"))) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (DATABASE_DIAGNOSTIC_KEYS.some((key) => Object.hasOwn(current, key))) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
for (const key of NESTED_ERROR_KEYS) {
|
|
92
|
+
const nested = current[key];
|
|
93
|
+
if (Array.isArray(nested)) queue.push(...nested);
|
|
94
|
+
else if (nested !== void 0) queue.push(nested);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
function safeDatabaseErrorFacts(error) {
|
|
100
|
+
const queue = [error];
|
|
101
|
+
const seen = /* @__PURE__ */ new Set();
|
|
102
|
+
const facts = {};
|
|
103
|
+
while (queue.length > 0 && seen.size < 64) {
|
|
104
|
+
const current = queue.shift();
|
|
105
|
+
if (!isRecord(current) || seen.has(current)) continue;
|
|
106
|
+
seen.add(current);
|
|
107
|
+
for (const [source, destination] of SAFE_FACT_KEYS) {
|
|
108
|
+
if (facts[destination] !== void 0) continue;
|
|
109
|
+
const value = safeFact(current[source]);
|
|
110
|
+
if (value !== void 0) facts[destination] = value;
|
|
111
|
+
}
|
|
112
|
+
for (const key of NESTED_ERROR_KEYS) {
|
|
113
|
+
const nested = current[key];
|
|
114
|
+
if (Array.isArray(nested)) queue.push(...nested);
|
|
115
|
+
else if (nested !== void 0) queue.push(nested);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return facts;
|
|
119
|
+
}
|
|
120
|
+
var SessionEventPersistenceError = class extends Error {
|
|
121
|
+
constructor(details, cause) {
|
|
122
|
+
const label = details.code === "db_deadlock" ? "Database deadlock" : details.code === "db_serialization_failure" ? "Database serialization failure" : "Database failure";
|
|
123
|
+
const operation = `${label} while persisting ${details.eventTypes.join(", ") || "session events"}`;
|
|
124
|
+
super(operation, cause === void 0 ? void 0 : { cause });
|
|
125
|
+
this.details = details;
|
|
126
|
+
}
|
|
127
|
+
name = "SessionEventPersistenceError";
|
|
128
|
+
get code() {
|
|
129
|
+
return this.details.code;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
function isSessionEventPersistenceError(error) {
|
|
133
|
+
return error instanceof SessionEventPersistenceError;
|
|
134
|
+
}
|
|
135
|
+
async function runIdempotentPersistenceTransaction(options, transaction) {
|
|
136
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
137
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
|
|
138
|
+
throw new Error("Persistence maxAttempts must be a positive integer");
|
|
139
|
+
}
|
|
140
|
+
const correlationId = options.correlationId ?? crypto.randomUUID();
|
|
141
|
+
const eventTypes = [...new Set(options.eventTypes ?? [])].sort();
|
|
142
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
143
|
+
try {
|
|
144
|
+
return await transaction(attempt);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const sqlState = nestedPostgresSqlState(error);
|
|
147
|
+
const retryable = isRetryablePersistenceSqlState(sqlState);
|
|
148
|
+
if (retryable && attempt < maxAttempts) {
|
|
149
|
+
await options.onRetry?.({
|
|
150
|
+
attempt,
|
|
151
|
+
sqlState
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (!isDatabasePersistenceFailure(error)) throw error;
|
|
156
|
+
throw new SessionEventPersistenceError(
|
|
157
|
+
{
|
|
158
|
+
code: databaseFailureCode(sqlState),
|
|
159
|
+
sqlState,
|
|
160
|
+
stage: options.stage,
|
|
161
|
+
eventTypes,
|
|
162
|
+
correlationId,
|
|
163
|
+
attempts: attempt,
|
|
164
|
+
retryOutcome: retryable ? "exhausted" : "not_retryable",
|
|
165
|
+
database: safeDatabaseErrorFacts(error)
|
|
166
|
+
},
|
|
167
|
+
error
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
throw new Error("Unreachable persistence retry state");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/database.ts
|
|
175
|
+
import { eq, sql } from "drizzle-orm";
|
|
176
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
177
|
+
import postgres from "postgres";
|
|
178
|
+
var dbBindings = /* @__PURE__ */ new WeakMap();
|
|
179
|
+
function rlsStrategyFor(db) {
|
|
180
|
+
return dbBindings.get(db)?.rlsStrategy ?? "force";
|
|
181
|
+
}
|
|
182
|
+
async function rawRows(executor, query) {
|
|
183
|
+
const result = await executor.execute(query);
|
|
184
|
+
if (Array.isArray(result)) {
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
const rows = result.rows;
|
|
188
|
+
if (Array.isArray(rows)) {
|
|
189
|
+
return rows;
|
|
190
|
+
}
|
|
191
|
+
throw new Error("Unsupported database execute() result shape");
|
|
192
|
+
}
|
|
193
|
+
var SandboxProviderReadLockUnavailableError = class extends Error {
|
|
194
|
+
constructor() {
|
|
195
|
+
super("Sandbox provider reads are temporarily busy. Retry the request.");
|
|
196
|
+
this.name = "SandboxProviderReadLockUnavailableError";
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
var SANDBOX_PROVIDER_READ_LOCK_WAIT_MS = 15e3;
|
|
200
|
+
var SANDBOX_PROVIDER_READ_LOCK_POLL_MAX_MS = 200;
|
|
201
|
+
function sandboxProviderReadLockKey(identity) {
|
|
202
|
+
return [
|
|
203
|
+
"sandbox-provider-read",
|
|
204
|
+
identity.workspaceId,
|
|
205
|
+
identity.sandboxGroupId,
|
|
206
|
+
identity.leaseEpoch,
|
|
207
|
+
identity.instanceId
|
|
208
|
+
].join(":");
|
|
209
|
+
}
|
|
210
|
+
async function waitForSandboxProviderReadLockPoll(delayMs, signal) {
|
|
211
|
+
if (signal?.aborted) {
|
|
212
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
213
|
+
}
|
|
214
|
+
await new Promise((resolve, reject) => {
|
|
215
|
+
let settled = false;
|
|
216
|
+
let timer;
|
|
217
|
+
const cleanup = () => signal?.removeEventListener("abort", abort);
|
|
218
|
+
const finish = () => {
|
|
219
|
+
if (settled) return;
|
|
220
|
+
settled = true;
|
|
221
|
+
cleanup();
|
|
222
|
+
resolve();
|
|
223
|
+
};
|
|
224
|
+
const abort = () => {
|
|
225
|
+
if (settled) return;
|
|
226
|
+
settled = true;
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
cleanup();
|
|
229
|
+
reject(signal?.reason ?? new DOMException("The operation was aborted", "AbortError"));
|
|
230
|
+
};
|
|
231
|
+
timer = setTimeout(finish, delayMs);
|
|
232
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
233
|
+
if (signal?.aborted) abort();
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async function withSandboxProviderReadLock(db, identity, signal, fn) {
|
|
237
|
+
const key = sandboxProviderReadLockKey(identity);
|
|
238
|
+
const deadline = Date.now() + SANDBOX_PROVIDER_READ_LOCK_WAIT_MS;
|
|
239
|
+
return await db.transaction(async (lockedDb) => {
|
|
240
|
+
let pollMs = 20;
|
|
241
|
+
while (true) {
|
|
242
|
+
if (signal?.aborted) {
|
|
243
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
244
|
+
}
|
|
245
|
+
const [row] = await rawRows(
|
|
246
|
+
lockedDb,
|
|
247
|
+
sql`select pg_try_advisory_xact_lock(hashtextextended(${key}, 0)) as acquired`
|
|
248
|
+
);
|
|
249
|
+
if (row?.acquired === true) return await fn();
|
|
250
|
+
const remainingMs = deadline - Date.now();
|
|
251
|
+
if (remainingMs <= 0) throw new SandboxProviderReadLockUnavailableError();
|
|
252
|
+
await waitForSandboxProviderReadLockPoll(Math.min(pollMs, remainingMs), signal);
|
|
253
|
+
pollMs = Math.min(pollMs * 2, SANDBOX_PROVIDER_READ_LOCK_POLL_MAX_MS);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function createDb(databaseUrl, options = {}) {
|
|
258
|
+
const client = postgres(databaseUrl, {
|
|
259
|
+
max: options.max ?? 10,
|
|
260
|
+
prepare: false,
|
|
261
|
+
idle_timeout: 30,
|
|
262
|
+
max_lifetime: 1800,
|
|
263
|
+
// `connection` carries per-session Postgres STARTUP parameters. `application_name`
|
|
264
|
+
// (always) aids server-side diagnostics; `search_path` (embedded only) is the
|
|
265
|
+
// supported, query-param-free way to scope a connection to a dedicated schema —
|
|
266
|
+
// postgres-js IGNORES a URL `?search_path=`. Unset searchPath → omit it so the
|
|
267
|
+
// server default (`public`) is unchanged for standalone.
|
|
268
|
+
connection: {
|
|
269
|
+
application_name: LOSSLESS_CONTENT_WRITER_APPLICATION_NAME,
|
|
270
|
+
...options.searchPath ? { search_path: options.searchPath } : {},
|
|
271
|
+
...options.isolationLevel ? { default_transaction_isolation: options.isolationLevel } : {}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
const db = drizzle(client, { schema: schema_exports });
|
|
275
|
+
dbBindings.set(db, {
|
|
276
|
+
rlsStrategy: options.rlsStrategy ?? "force",
|
|
277
|
+
...options.userLookup ? { userLookup: options.userLookup } : {}
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
db,
|
|
281
|
+
close: async () => {
|
|
282
|
+
await client.end();
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function registerDbBinding(db, binding) {
|
|
287
|
+
dbBindings.set(db, {
|
|
288
|
+
rlsStrategy: binding.rlsStrategy ?? "force",
|
|
289
|
+
...binding.userLookup ? { userLookup: binding.userLookup } : {}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
async function setRlsContext(db, context) {
|
|
293
|
+
if (typeof context.accountId !== "string" || context.accountId.trim() === "") {
|
|
294
|
+
throw new Error("setRlsContext: a non-empty accountId is required to establish an RLS context");
|
|
295
|
+
}
|
|
296
|
+
await db.execute(sql`select set_config('opengeni.account_id', ${context.accountId}, true)`);
|
|
297
|
+
await db.execute(
|
|
298
|
+
sql`select set_config('opengeni.workspace_id', ${context.workspaceId ?? ""}, true)`
|
|
299
|
+
);
|
|
300
|
+
await db.execute(sql`select set_config('opengeni.lossless_content_writer', '1', true)`);
|
|
301
|
+
await db.execute(sql`select set_config('opengeni.sandbox_recovery_protocol_v2', '1', true)`);
|
|
302
|
+
}
|
|
303
|
+
async function readRlsContextSettings(db) {
|
|
304
|
+
const [settings] = await rawRows(
|
|
305
|
+
db,
|
|
306
|
+
sql`select
|
|
307
|
+
current_setting('opengeni.account_id', true) as account_id,
|
|
308
|
+
current_setting('opengeni.workspace_id', true) as workspace_id`
|
|
309
|
+
);
|
|
310
|
+
return {
|
|
311
|
+
accountId: settings?.account_id ?? "",
|
|
312
|
+
workspaceId: settings?.workspace_id ?? ""
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async function assertRlsContextApplied(db, context) {
|
|
316
|
+
const applied = await readRlsContextSettings(db);
|
|
317
|
+
const expectedWorkspaceId = context.workspaceId ?? "";
|
|
318
|
+
if (applied.accountId !== context.accountId) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`RLS context not applied on the active backend: expected account ${context.accountId}, got "${applied.accountId}"`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (applied.workspaceId !== expectedWorkspaceId) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`RLS context not applied on the active backend: expected workspace "${expectedWorkspaceId}", got "${applied.workspaceId}"`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function restoreRlsContextSettings(db, settings) {
|
|
330
|
+
const [restored] = await rawRows(
|
|
331
|
+
db,
|
|
332
|
+
sql`select
|
|
333
|
+
set_config('opengeni.account_id', ${settings.accountId}, true) as account_id,
|
|
334
|
+
set_config('opengeni.workspace_id', ${settings.workspaceId}, true) as workspace_id`
|
|
335
|
+
);
|
|
336
|
+
if (restored?.account_id !== settings.accountId || restored.workspace_id !== settings.workspaceId) {
|
|
337
|
+
throw new Error("RLS context could not be restored after a nested scope");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function withRlsContext(db, context, fn, transactionConfig) {
|
|
341
|
+
const restoreParentScope = isTransactionHandle(db);
|
|
342
|
+
return await db.transaction(async (tx) => {
|
|
343
|
+
const scoped = tx;
|
|
344
|
+
const parentScope = restoreParentScope ? await readRlsContextSettings(scoped) : null;
|
|
345
|
+
await setRlsContext(scoped, context);
|
|
346
|
+
await assertRlsContextApplied(scoped, context);
|
|
347
|
+
const value = await fn(scoped);
|
|
348
|
+
if (parentScope) await restoreRlsContextSettings(scoped, parentScope);
|
|
349
|
+
return value;
|
|
350
|
+
}, transactionConfig);
|
|
351
|
+
}
|
|
352
|
+
async function withDatabaseStatementTimeout(db, timeoutMs, fn) {
|
|
353
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
354
|
+
throw new Error("withDatabaseStatementTimeout requires a positive timeout");
|
|
355
|
+
}
|
|
356
|
+
const boundedTimeoutMs = Math.max(1, Math.floor(timeoutMs));
|
|
357
|
+
return await db.transaction(async (tx) => {
|
|
358
|
+
const scoped = tx;
|
|
359
|
+
await scoped.execute(
|
|
360
|
+
sql`select set_config('statement_timeout', ${`${boundedTimeoutMs}ms`}, true)`
|
|
361
|
+
);
|
|
362
|
+
return await fn(scoped);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
async function rlsContextForWorkspace(db, workspaceId) {
|
|
366
|
+
const [row] = await db.select({ accountId: workspaces.accountId }).from(workspaces).where(eq(workspaces.id, workspaceId)).limit(1);
|
|
367
|
+
if (!row) {
|
|
368
|
+
throw new Error(`Workspace not found: ${workspaceId}`);
|
|
369
|
+
}
|
|
370
|
+
return { accountId: row.accountId, workspaceId };
|
|
371
|
+
}
|
|
372
|
+
async function withWorkspaceRls(db, workspaceId, fn, transactionConfig) {
|
|
373
|
+
return await withRlsContext(
|
|
374
|
+
db,
|
|
375
|
+
await rlsContextForWorkspace(db, workspaceId),
|
|
376
|
+
fn,
|
|
377
|
+
transactionConfig
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
function isTransactionHandle(db) {
|
|
381
|
+
return typeof db.rollback === "function";
|
|
382
|
+
}
|
|
383
|
+
async function assertSessionActivityGateEntry(db) {
|
|
384
|
+
if (!isTransactionHandle(db)) return;
|
|
385
|
+
const [gate] = await rawRows(
|
|
386
|
+
db,
|
|
387
|
+
sql`
|
|
388
|
+
select nullif(
|
|
389
|
+
current_setting('opengeni.session_activity_gate_state', true),
|
|
390
|
+
''
|
|
391
|
+
) as state
|
|
392
|
+
`
|
|
393
|
+
);
|
|
394
|
+
if (gate?.state === null || gate === void 0) {
|
|
395
|
+
throw new Error("Session activity commit gate must start at the outer transaction boundary");
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async function beginSessionActivityGate(db, workspaceId) {
|
|
399
|
+
const [gate] = await rawRows(
|
|
400
|
+
db,
|
|
401
|
+
sql`
|
|
402
|
+
with current_gate as materialized (
|
|
403
|
+
select
|
|
404
|
+
nullif(current_setting('opengeni.session_activity_gate_state', true), '') as prior_state,
|
|
405
|
+
nullif(current_setting('opengeni.session_activity_gate_workspace_id', true), '') as prior_workspace_id
|
|
406
|
+
)
|
|
407
|
+
select
|
|
408
|
+
prior_state,
|
|
409
|
+
prior_workspace_id,
|
|
410
|
+
case
|
|
411
|
+
when prior_state is null
|
|
412
|
+
then set_config('opengeni.session_activity_gate_state', 'open', true)
|
|
413
|
+
else prior_state
|
|
414
|
+
end as effective_state,
|
|
415
|
+
case
|
|
416
|
+
when prior_state is null
|
|
417
|
+
then set_config('opengeni.session_activity_gate_workspace_id', ${workspaceId}, true)
|
|
418
|
+
else prior_workspace_id
|
|
419
|
+
end as effective_workspace_id
|
|
420
|
+
from current_gate
|
|
421
|
+
`
|
|
422
|
+
);
|
|
423
|
+
if (!gate) {
|
|
424
|
+
throw new Error("Session activity commit gate could not be opened");
|
|
425
|
+
}
|
|
426
|
+
if (gate.prior_state === null) {
|
|
427
|
+
return { db, owner: true };
|
|
428
|
+
}
|
|
429
|
+
if (gate.prior_state === "open" && gate.prior_workspace_id === workspaceId) {
|
|
430
|
+
return { db, owner: false };
|
|
431
|
+
}
|
|
432
|
+
throw new Error("Session activity commit gate is already owned by another transaction scope");
|
|
433
|
+
}
|
|
434
|
+
async function finalizeSessionActivityGate(db, workspaceId) {
|
|
435
|
+
await db.execute(
|
|
436
|
+
sql`select set_config('opengeni.session_activity_gate_state', 'preparing', true)`
|
|
437
|
+
);
|
|
438
|
+
await db.execute(sql`set constraints all immediate`);
|
|
439
|
+
await db.execute(
|
|
440
|
+
sql`set constraints sessions_activity_insert_commit_guard, sessions_activity_update_commit_guard deferred`
|
|
441
|
+
);
|
|
442
|
+
const [result] = await rawRows(
|
|
443
|
+
db,
|
|
444
|
+
sql`
|
|
445
|
+
with gate as materialized (
|
|
446
|
+
select set_config('opengeni.session_activity_gate_state', 'finalizing', true) as state
|
|
447
|
+
),
|
|
448
|
+
pending as materialized (
|
|
449
|
+
select ${sessions.id}
|
|
450
|
+
from ${sessions}
|
|
451
|
+
cross join gate
|
|
452
|
+
where ${sessions.workspaceId} = ${workspaceId}
|
|
453
|
+
and ${sessions.activityRevisionPendingXid}
|
|
454
|
+
= pg_current_xact_id()::text::bigint
|
|
455
|
+
),
|
|
456
|
+
advanced as (
|
|
457
|
+
update ${workspaceSessionActivityRevisions} as counter
|
|
458
|
+
set revision = counter.revision + 1
|
|
459
|
+
where counter.workspace_id = ${workspaceId}
|
|
460
|
+
and exists (select 1 from pending)
|
|
461
|
+
returning counter.revision
|
|
462
|
+
),
|
|
463
|
+
stamped as (
|
|
464
|
+
update ${sessions} as session
|
|
465
|
+
set
|
|
466
|
+
activity_revision = advanced.revision,
|
|
467
|
+
activity_revision_pending_xid = null
|
|
468
|
+
from advanced
|
|
469
|
+
where session.workspace_id = ${workspaceId}
|
|
470
|
+
and session.activity_revision_pending_xid
|
|
471
|
+
= pg_current_xact_id()::text::bigint
|
|
472
|
+
returning session.id
|
|
473
|
+
),
|
|
474
|
+
stats as materialized (
|
|
475
|
+
select
|
|
476
|
+
exists (select 1 from pending) as had_pending,
|
|
477
|
+
(select count(*)::integer from pending) as pending_count,
|
|
478
|
+
(select revision::text from advanced) as revision,
|
|
479
|
+
(select count(*)::integer from stamped) as stamped_count
|
|
480
|
+
),
|
|
481
|
+
finalized as materialized (
|
|
482
|
+
select
|
|
483
|
+
had_pending,
|
|
484
|
+
pending_count,
|
|
485
|
+
revision,
|
|
486
|
+
stamped_count,
|
|
487
|
+
set_config('opengeni.session_activity_gate_state', 'finalized', true) as state
|
|
488
|
+
from stats
|
|
489
|
+
)
|
|
490
|
+
select had_pending, pending_count, revision, stamped_count
|
|
491
|
+
from finalized
|
|
492
|
+
`
|
|
493
|
+
);
|
|
494
|
+
if (!result) {
|
|
495
|
+
throw new Error("Session activity commit gate did not return a finalization result");
|
|
496
|
+
}
|
|
497
|
+
if (result.had_pending && result.revision === null) {
|
|
498
|
+
throw new Error("Workspace session activity counter is unavailable");
|
|
499
|
+
}
|
|
500
|
+
if (result.stamped_count !== result.pending_count) {
|
|
501
|
+
throw new Error("Session activity commit gate did not stamp every pending row");
|
|
502
|
+
}
|
|
503
|
+
await db.execute(
|
|
504
|
+
sql`set constraints sessions_activity_insert_commit_guard, sessions_activity_update_commit_guard immediate`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
async function withSessionActivityRlsContext(db, context, fn, transactionConfig) {
|
|
508
|
+
await assertSessionActivityGateEntry(db);
|
|
509
|
+
return await withRlsContext(
|
|
510
|
+
db,
|
|
511
|
+
context,
|
|
512
|
+
async (scopedDb) => {
|
|
513
|
+
const gate = await beginSessionActivityGate(scopedDb, context.workspaceId);
|
|
514
|
+
const value = await fn(gate.db);
|
|
515
|
+
await assertRlsContextApplied(gate.db, context);
|
|
516
|
+
if (gate.owner) {
|
|
517
|
+
await finalizeSessionActivityGate(gate.db, context.workspaceId);
|
|
518
|
+
}
|
|
519
|
+
return value;
|
|
520
|
+
},
|
|
521
|
+
transactionConfig
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
async function withWorkspaceSessionActivityRls(db, workspaceId, fn, transactionConfig) {
|
|
525
|
+
return await withSessionActivityRlsContext(
|
|
526
|
+
db,
|
|
527
|
+
{ ...await rlsContextForWorkspace(db, workspaceId), workspaceId },
|
|
528
|
+
fn,
|
|
529
|
+
transactionConfig
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
async function withSessionActivitySavepoint(db, fn) {
|
|
533
|
+
return await db.transaction(async (tx) => await fn(tx));
|
|
534
|
+
}
|
|
535
|
+
async function retrySessionActivityRls(db, workspaceId, options, fn) {
|
|
536
|
+
return await runIdempotentPersistenceTransaction(options, async () => {
|
|
537
|
+
return await withWorkspaceSessionActivityRls(db, workspaceId, fn);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
async function retryWorkspacePersistence(db, workspaceId, options, fn) {
|
|
541
|
+
return await runIdempotentPersistenceTransaction(options, async () => {
|
|
542
|
+
return await withWorkspaceRls(db, workspaceId, fn);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
async function retryRlsPersistence(db, context, options, fn) {
|
|
546
|
+
return await runIdempotentPersistenceTransaction(options, async () => {
|
|
547
|
+
return await withRlsContext(db, context, fn);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
async function withWorkspaceSubjectRls(db, workspaceId, subjectId, fn, transactionConfig) {
|
|
551
|
+
if (!subjectId.trim()) {
|
|
552
|
+
throw new Error("withWorkspaceSubjectRls: a non-empty subjectId is required");
|
|
553
|
+
}
|
|
554
|
+
const context = await rlsContextForWorkspace(db, workspaceId);
|
|
555
|
+
return await withRlsContext(
|
|
556
|
+
db,
|
|
557
|
+
context,
|
|
558
|
+
async (scopedDb) => {
|
|
559
|
+
await setSubjectRlsContext(scopedDb, subjectId);
|
|
560
|
+
return await fn(scopedDb);
|
|
561
|
+
},
|
|
562
|
+
transactionConfig
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
async function withWorkspaceSubjectSessionActivityRls(db, workspaceId, subjectId, fn, transactionConfig) {
|
|
566
|
+
if (!subjectId.trim()) {
|
|
567
|
+
throw new Error("withWorkspaceSubjectSessionActivityRls: a non-empty subjectId is required");
|
|
568
|
+
}
|
|
569
|
+
const context = await rlsContextForWorkspace(db, workspaceId);
|
|
570
|
+
return await withSessionActivityRlsContext(
|
|
571
|
+
db,
|
|
572
|
+
{ ...context, workspaceId },
|
|
573
|
+
async (scopedDb) => {
|
|
574
|
+
await setSubjectRlsContext(scopedDb, subjectId);
|
|
575
|
+
return await fn(scopedDb);
|
|
576
|
+
},
|
|
577
|
+
transactionConfig
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
async function setSubjectRlsContext(db, subjectId) {
|
|
581
|
+
if (!subjectId.trim()) {
|
|
582
|
+
throw new Error("setSubjectRlsContext: a non-empty subjectId is required");
|
|
583
|
+
}
|
|
584
|
+
await db.execute(sql`select set_config('opengeni.subject_id', ${subjectId}, true)`);
|
|
585
|
+
const applied = await db.execute(
|
|
586
|
+
sql`select current_setting('opengeni.subject_id', true) as subject_id`
|
|
587
|
+
);
|
|
588
|
+
if ((applied[0]?.subject_id ?? "") !== subjectId) {
|
|
589
|
+
throw new Error("Authenticated subject RLS context was not applied on the active backend");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function withWorkspaceUsageLock(db, workspaceId, fn) {
|
|
593
|
+
const context = await rlsContextForWorkspace(db, workspaceId);
|
|
594
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
595
|
+
await scopedDb.execute(sql`select pg_advisory_xact_lock(hashtext(${`usage:${workspaceId}`}))`);
|
|
596
|
+
return await fn(scopedDb);
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
async function withAccountRls(db, accountId, fn) {
|
|
600
|
+
return await withRlsContext(db, { accountId, workspaceId: null }, fn);
|
|
601
|
+
}
|
|
602
|
+
function dbBindingFor(db) {
|
|
603
|
+
return dbBindings.get(db);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export {
|
|
607
|
+
nestedPostgresSqlState,
|
|
608
|
+
databaseFailureCode,
|
|
609
|
+
isRetryablePersistenceSqlState,
|
|
610
|
+
isDatabasePersistenceFailure,
|
|
611
|
+
safeDatabaseErrorFacts,
|
|
612
|
+
SessionEventPersistenceError,
|
|
613
|
+
isSessionEventPersistenceError,
|
|
614
|
+
runIdempotentPersistenceTransaction,
|
|
615
|
+
rlsStrategyFor,
|
|
616
|
+
rawRows,
|
|
617
|
+
SandboxProviderReadLockUnavailableError,
|
|
618
|
+
withSandboxProviderReadLock,
|
|
619
|
+
createDb,
|
|
620
|
+
registerDbBinding,
|
|
621
|
+
setRlsContext,
|
|
622
|
+
withRlsContext,
|
|
623
|
+
withDatabaseStatementTimeout,
|
|
624
|
+
rlsContextForWorkspace,
|
|
625
|
+
withWorkspaceRls,
|
|
626
|
+
withSessionActivityRlsContext,
|
|
627
|
+
withWorkspaceSessionActivityRls,
|
|
628
|
+
withSessionActivitySavepoint,
|
|
629
|
+
retrySessionActivityRls,
|
|
630
|
+
retryWorkspacePersistence,
|
|
631
|
+
retryRlsPersistence,
|
|
632
|
+
withWorkspaceSubjectRls,
|
|
633
|
+
withWorkspaceSubjectSessionActivityRls,
|
|
634
|
+
setSubjectRlsContext,
|
|
635
|
+
withWorkspaceUsageLock,
|
|
636
|
+
withAccountRls,
|
|
637
|
+
dbBindingFor
|
|
638
|
+
};
|
|
639
|
+
//# sourceMappingURL=chunk-ISI2TIUG.js.map
|