@opengeni/db 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/chunk-57MLICFR.js +121 -0
  2. package/dist/chunk-57MLICFR.js.map +1 -0
  3. package/dist/chunk-OGCE6O2X.js +52 -0
  4. package/dist/chunk-OGCE6O2X.js.map +1 -0
  5. package/dist/chunk-PSX56ZTL.js +1093 -0
  6. package/dist/chunk-PSX56ZTL.js.map +1 -0
  7. package/dist/chunk-PZ5AY32C.js +10 -0
  8. package/dist/chunk-PZ5AY32C.js.map +1 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.js +5165 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/migrate.d.ts +40 -0
  13. package/dist/migrate.js +10 -0
  14. package/dist/migrate.js.map +1 -0
  15. package/dist/provision-roles.d.ts +2063 -0
  16. package/dist/provision-roles.js +8 -0
  17. package/dist/provision-roles.js.map +1 -0
  18. package/dist/schema-CaeZQAJQ.d.ts +9705 -0
  19. package/dist/schema.d.ts +3 -0
  20. package/dist/schema.js +110 -0
  21. package/dist/schema.js.map +1 -0
  22. package/drizzle/0000_initial.sql +179 -0
  23. package/drizzle/0001_workspace_auth_billing.sql +590 -0
  24. package/drizzle/0002_packs_and_social.sql +99 -0
  25. package/drizzle/0003_capability_catalog.sql +73 -0
  26. package/drizzle/0004_workspace_environments.sql +65 -0
  27. package/drizzle/0005_session_goals.sql +45 -0
  28. package/drizzle/0006_workspace_packs.sql +31 -0
  29. package/drizzle/0007_session_history_items.sql +66 -0
  30. package/drizzle/0008_session_first_party_mcp_permissions.sql +5 -0
  31. package/drizzle/0009_goal_sessions_first_party_goals_manage.sql +34 -0
  32. package/drizzle/0010_session_parent_linkage.sql +30 -0
  33. package/drizzle/0011_context_compaction.sql +33 -0
  34. package/drizzle/0012_compaction_summary_fractional_position.sql +19 -0
  35. package/drizzle/0013_session_compact_requested.sql +16 -0
  36. package/drizzle/0014_repair_orphaned_function_call_results.sql +125 -0
  37. package/drizzle/0015_workspace_agent_instructions.sql +17 -0
  38. package/drizzle/0016_session_create_idempotency.sql +27 -0
  39. package/drizzle/0017_sandbox_leases.sql +313 -0
  40. package/drizzle/0018_sandbox_os.sql +89 -0
  41. package/drizzle/0019_session_stream_acknowledgments.sql +94 -0
  42. package/drizzle/0020_session_recordings.sql +88 -0
  43. package/drizzle/0021_sandbox_pty_sessions.sql +70 -0
  44. package/drizzle/0022_sandbox_lease_terminal_url.sql +32 -0
  45. package/drizzle/0023_session_title.sql +19 -0
  46. package/drizzle/0024_codex_subscription_credentials.sql +51 -0
  47. package/drizzle/0024_sandboxes_enrollments_metrics.sql +262 -0
  48. package/drizzle/0025_device_enrollment_requests.sql +142 -0
  49. package/drizzle/0026_device_enrollment_user_code_resolver.sql +47 -0
  50. package/drizzle/0027_session_working_dir.sql +24 -0
  51. package/drizzle/0028_codex_multi_account.sql +85 -0
  52. package/drizzle/0029_session_history_item_producer.sql +31 -0
  53. package/drizzle/0030_agent_run_state_frozen_codex.sql +35 -0
  54. package/drizzle/0031_codex_usage_cache.sql +21 -0
  55. package/drizzle/0032_codex_account_cooldown.sql +18 -0
  56. package/drizzle/0033_codex_connector_cache.sql +20 -0
  57. package/drizzle/0034_sandbox_lease_image.sql +21 -0
  58. package/drizzle/meta/_journal.json +167 -0
  59. package/package.json +66 -0
  60. package/src/codex-token-resolver.ts +247 -0
  61. package/src/environment-crypto.ts +51 -0
  62. package/src/event-payload-sanitizer.ts +89 -0
  63. package/src/index.ts +7776 -0
  64. package/src/migrate.ts +95 -0
  65. package/src/provision-roles.ts +198 -0
  66. package/src/schema.ts +1110 -0
package/src/migrate.ts ADDED
@@ -0,0 +1,95 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import postgres from "postgres";
5
+
6
+ const DEFAULT_DATABASE_URL = "postgres://opengeni:opengeni@127.0.0.1:5432/opengeni";
7
+
8
+ /** A bare Postgres identifier (schema/role name) safe to interpolate into DDL. */
9
+ function assertIdentifier(name: string, value: string): string {
10
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
11
+ throw new Error(`${name} is not a valid Postgres identifier: ${value}`);
12
+ }
13
+ return value;
14
+ }
15
+
16
+ /**
17
+ * Apply the OpenGeni SQL migration chain.
18
+ *
19
+ * STANDALONE (default, unchanged): `migrate()` / `migrate(databaseUrl)` runs the
20
+ * whole chain with NO search_path manipulation, so every unqualified
21
+ * table/index/policy lands in the server default schema (`public`). This is the
22
+ * byte-for-byte historical behavior — the migration test suite calls
23
+ * `migrate(DB_URL)` and is unaffected.
24
+ *
25
+ * EMBEDDED (Step I, §7.8 runtime/SDK half): pass a `schema` (or set
26
+ * `OPENGENI_DB_SCHEMA`). The migrate session then `CREATE SCHEMA IF NOT EXISTS`
27
+ * for both `<schema>` and `opengeni_private`, and sets
28
+ * `search_path = "<schema>", "opengeni_private", "public"`, so EVERY unqualified
29
+ * DDL statement lands in the dedicated schema with NO per-statement SQL rewrite
30
+ * (the SPIKE-1 F1 result). Two things make this work and stay idempotent:
31
+ * 1. The policy-existence guards in the migration SQL use `current_schema()`
32
+ * (not a hardcoded `'public'`) — so a re-run finds the policy it already
33
+ * created in `<schema>` and DROP/CREATEs idempotently instead of failing
34
+ * with "policy already exists". (This guard substitution is the migrate-
35
+ * time enabler for the runtime search_path approach; without it the SDK
36
+ * entry point silently fails on re-run — the Fork-6 hazard.)
37
+ * 2. `public` stays LAST on the path so `gen_random_uuid()` (pgcrypto) and the
38
+ * `vector` type — both installed into `public` by 0000 — still resolve. The
39
+ * `opengeni_private.*` helpers are always called with an absolute prefix.
40
+ *
41
+ * `OPENGENI_DB_SCHEMA` defaults UNSET → `public` → standalone, so the default
42
+ * binding never regresses.
43
+ */
44
+ export async function migrate(
45
+ databaseUrl = process.env.OPENGENI_MIGRATIONS_DATABASE_URL ?? process.env.OPENGENI_DATABASE_URL ?? DEFAULT_DATABASE_URL,
46
+ schema: string | undefined = process.env.OPENGENI_DB_SCHEMA?.trim() || undefined,
47
+ ): Promise<void> {
48
+ const migrationsDir = join(dirname(fileURLToPath(import.meta.url)), "../drizzle");
49
+ const files = (await readdir(migrationsDir)).filter((file) => file.endsWith(".sql")).sort();
50
+ const sql = postgres(databaseUrl, { max: 1 });
51
+ try {
52
+ // Serialize concurrent migrate() runs; the session-level lock is released
53
+ // when the connection closes.
54
+ await sql`SELECT pg_advisory_lock(727458)`;
55
+ if (schema) {
56
+ assertIdentifier("OPENGENI_DB_SCHEMA", schema);
57
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);
58
+ // opengeni_private is also created by 0001 with an absolute prefix, but the
59
+ // session search_path must already resolve it for the policy predicates
60
+ // and the SECURITY DEFINER functions that inherit the caller's path.
61
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS "opengeni_private"`);
62
+ await sql.unsafe(`SET search_path = "${schema}", "opengeni_private", "public"`);
63
+ }
64
+ await sql.unsafe(`CREATE TABLE IF NOT EXISTS "schema_migrations" ("name" text PRIMARY KEY, "applied_at" timestamptz NOT NULL DEFAULT now())`);
65
+ const appliedRows = await sql`SELECT "name" FROM "schema_migrations"`;
66
+ const applied = new Set(appliedRows.map((row) => row.name as string));
67
+ for (const file of files) {
68
+ if (applied.has(file)) {
69
+ continue;
70
+ }
71
+ const sqlText = await readFile(join(migrationsDir, file), "utf8");
72
+ await sql.unsafe(sqlText);
73
+ await sql`INSERT INTO "schema_migrations" ("name") VALUES (${file}) ON CONFLICT DO NOTHING`;
74
+ }
75
+ } finally {
76
+ await sql.end();
77
+ }
78
+ }
79
+
80
+ /**
81
+ * SDK entry point (Step I): run the migration chain over a host-supplied admin
82
+ * connection string against an explicit target schema. This is the embedded
83
+ * topology's named entry — a host calls `runMigrations(adminConnection,
84
+ * targetSchema)` from its own provisioning code instead of relying on env vars.
85
+ * `targetSchema` undefined → `public` → standalone behavior. Thin wrapper over
86
+ * `migrate` so there is one migration engine.
87
+ */
88
+ export async function runMigrations(adminConnection: string, targetSchema?: string): Promise<void> {
89
+ await migrate(adminConnection, targetSchema);
90
+ }
91
+
92
+ if (import.meta.main) {
93
+ await migrate();
94
+ console.log("Applied Drizzle SQL migrations.");
95
+ }
@@ -0,0 +1,198 @@
1
+ import postgres from "postgres";
2
+ import type { RlsStrategy } from "./index";
3
+
4
+ export type ProvisionResult = {
5
+ appRole: string | null;
6
+ temporalRole: string | null;
7
+ temporalDatabases: string[];
8
+ schema: string;
9
+ rlsStrategy: RlsStrategy;
10
+ };
11
+
12
+ export type ProvisionRolesOptions = {
13
+ /**
14
+ * The schema OpenGeni's tables live in. The app-role GRANTs target this
15
+ * schema + `opengeni_private`. Defaults to `public` (standalone).
16
+ */
17
+ targetSchema?: string;
18
+ /**
19
+ * RLS posture (Step I). `"force"` (default) provisions the non-owner
20
+ * `opengeni_app` login role and GRANTs it table DML in the target schema —
21
+ * the role OpenGeni connects as under FORCE-RLS. `"scoped"` SKIPS the app-role
22
+ * provisioning entirely: the embedded host runs OpenGeni's queries over a role
23
+ * IT owns/manages (typically the schema owner), so OpenGeni neither creates
24
+ * nor grants the `opengeni_app` role. Temporal-role provisioning is unaffected
25
+ * by strategy.
26
+ */
27
+ rlsStrategy?: RlsStrategy;
28
+ appRole?: string;
29
+ appPassword?: string;
30
+ temporalRole?: string;
31
+ temporalPassword?: string;
32
+ temporalDatabases?: string[];
33
+ };
34
+
35
+ /**
36
+ * SDK entry point (Step I): provision the OpenGeni database roles + grants over
37
+ * a host-supplied admin connection. This is the named, parameterized form of the
38
+ * historical env-driven `provision-roles` script (which still works as a CLI via
39
+ * the `import.meta.main` block at the bottom — it just reads env into these
40
+ * options).
41
+ *
42
+ * STANDALONE (default): `provisionRoles(adminConnection)` with no options →
43
+ * `targetSchema: "public"`, `rlsStrategy: "force"`, reads `opengeni_app` creds
44
+ * from env. Byte-for-byte the historical script behavior.
45
+ *
46
+ * EMBEDDED: `provisionRoles(adminConnection, { targetSchema, rlsStrategy })` lets
47
+ * a host provision the app role over a dedicated schema (force) OR skip the
48
+ * app role entirely and own the connection role itself (scoped).
49
+ */
50
+ export async function provisionRoles(
51
+ adminConnection: string,
52
+ options: ProvisionRolesOptions = {},
53
+ ): Promise<ProvisionResult> {
54
+ const schema = validateIdentifier("targetSchema", options.targetSchema ?? "public");
55
+ const rlsStrategy: RlsStrategy = options.rlsStrategy ?? "force";
56
+
57
+ const appRole = validateIdentifier("appRole", options.appRole ?? (process.env.OPENGENI_APP_DATABASE_USER?.trim() || "opengeni_app"));
58
+ const appPassword = options.appPassword ?? process.env.OPENGENI_APP_DATABASE_PASSWORD;
59
+ const temporalRole = validateIdentifier("temporalRole", options.temporalRole ?? (process.env.OPENGENI_TEMPORAL_DATABASE_USER?.trim() || "opengeni_temporal"));
60
+ const temporalPassword = options.temporalPassword ?? process.env.OPENGENI_TEMPORAL_DATABASE_PASSWORD;
61
+ const temporalDatabases = (options.temporalDatabases
62
+ ?? commaSeparated(process.env.OPENGENI_TEMPORAL_DATABASES ?? "temporal,temporal_visibility"))
63
+ .map((name) => validateIdentifier("temporalDatabases", name));
64
+
65
+ const sql = postgres(adminConnection, { max: 1 });
66
+ try {
67
+ // FORCE strategy provisions the non-owner app role OpenGeni connects as.
68
+ // SCOPED strategy: the host owns the connection role; OpenGeni provisions no
69
+ // app role (skipped here), only the optional Temporal role.
70
+ let provisionedAppRole: string | null = null;
71
+ if (rlsStrategy === "force") {
72
+ if (!appPassword) {
73
+ throw new Error("OPENGENI_APP_DATABASE_PASSWORD (or appPassword) is required for rlsStrategy 'force'");
74
+ }
75
+ await ensureLoginRole(sql, appRole, appPassword);
76
+ provisionedAppRole = appRole;
77
+ }
78
+
79
+ if (temporalPassword) {
80
+ await ensureLoginRole(sql, temporalRole, temporalPassword);
81
+ for (const database of temporalDatabases) {
82
+ await ensureDatabase(sql, database, temporalRole);
83
+ await grantTemporalRoleInDatabase(adminConnection, database, temporalRole);
84
+ }
85
+ }
86
+
87
+ if (rlsStrategy === "force") {
88
+ await grantAppRoleIfSchemaExists(sql, appRole, schema);
89
+ }
90
+
91
+ return {
92
+ appRole: provisionedAppRole,
93
+ temporalRole: temporalPassword ? temporalRole : null,
94
+ temporalDatabases: temporalPassword ? temporalDatabases : [],
95
+ schema,
96
+ rlsStrategy,
97
+ };
98
+ } finally {
99
+ await sql.end();
100
+ }
101
+ }
102
+
103
+ async function ensureLoginRole(sql: postgres.Sql, role: string, password: string): Promise<void> {
104
+ await sql.unsafe(`
105
+ DO $$
106
+ BEGIN
107
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = ${literal(role)}) THEN
108
+ EXECUTE format('CREATE ROLE %I LOGIN PASSWORD %L', ${literal(role)}, ${literal(password)});
109
+ ELSE
110
+ EXECUTE format('ALTER ROLE %I LOGIN PASSWORD %L', ${literal(role)}, ${literal(password)});
111
+ END IF;
112
+ END $$;
113
+ `);
114
+ }
115
+
116
+ async function ensureDatabase(sql: postgres.Sql, database: string, owner: string): Promise<void> {
117
+ const existing = await sql<{ exists: boolean }[]>`
118
+ select exists(select 1 from pg_database where datname = ${database}) as exists
119
+ `;
120
+ if (!existing[0]?.exists) {
121
+ await sql.unsafe(`CREATE DATABASE ${identifier(database)} OWNER ${identifier(owner)}`);
122
+ }
123
+ await sql.unsafe(`GRANT ALL PRIVILEGES ON DATABASE ${identifier(database)} TO ${identifier(owner)}`);
124
+ }
125
+
126
+ async function grantTemporalRoleInDatabase(adminConnection: string, database: string, role: string): Promise<void> {
127
+ const databaseUrl = databaseUrlFor(adminConnection, database);
128
+ const databaseSql = postgres(databaseUrl, { max: 1 });
129
+ try {
130
+ await databaseSql.unsafe(`GRANT USAGE, CREATE ON SCHEMA public TO ${identifier(role)}`);
131
+ } finally {
132
+ await databaseSql.end();
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Grant the app role table DML in the OpenGeni data schema + EXECUTE on the
138
+ * `opengeni_private` helper functions. Schema-parameterized (Step I): standalone
139
+ * passes `public`; embedded passes the dedicated schema. The grants are guarded
140
+ * on schema existence so provisioning before migrate is a safe no-op.
141
+ */
142
+ async function grantAppRoleIfSchemaExists(sql: postgres.Sql, role: string, schema: string): Promise<void> {
143
+ await sql.unsafe(`
144
+ DO $$
145
+ BEGIN
146
+ IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = ${literal(schema)}) THEN
147
+ EXECUTE format('GRANT USAGE ON SCHEMA %I TO %I', ${literal(schema)}, ${literal(role)});
148
+ EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO %I', ${literal(schema)}, ${literal(role)});
149
+ END IF;
150
+ IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'opengeni_private') THEN
151
+ EXECUTE format('GRANT USAGE ON SCHEMA opengeni_private TO %I', ${literal(role)});
152
+ EXECUTE format('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA opengeni_private TO %I', ${literal(role)});
153
+ END IF;
154
+ END $$;
155
+ `);
156
+ }
157
+
158
+ function commaSeparated(value: string): string[] {
159
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
160
+ }
161
+
162
+ function validateIdentifier(name: string, value: string): string {
163
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
164
+ throw new Error(`${name} contains an invalid Postgres identifier: ${value}`);
165
+ }
166
+ return value;
167
+ }
168
+
169
+ function identifier(value: string): string {
170
+ return `"${value.replace(/"/g, "\"\"")}"`;
171
+ }
172
+
173
+ function literal(value: string): string {
174
+ return `'${value.replace(/'/g, "''")}'`;
175
+ }
176
+
177
+ function databaseUrlFor(value: string, database: string): string {
178
+ const url = new URL(value);
179
+ url.pathname = `/${database}`;
180
+ return url.toString();
181
+ }
182
+
183
+ // CLI form (unchanged behavior): read env into the SDK options and run. This is
184
+ // what `bun src/provision-roles.ts` / the `provision-roles` package script
185
+ // invokes — standalone byte-for-byte the historical script (public schema,
186
+ // force strategy, env-driven creds).
187
+ if (import.meta.main) {
188
+ const adminUrl = process.env.OPENGENI_MIGRATIONS_DATABASE_URL
189
+ ?? process.env.OPENGENI_DATABASE_ADMIN_URL
190
+ ?? process.env.OPENGENI_DATABASE_URL;
191
+ if (!adminUrl) {
192
+ throw new Error("OPENGENI_MIGRATIONS_DATABASE_URL, OPENGENI_DATABASE_ADMIN_URL, or OPENGENI_DATABASE_URL is required");
193
+ }
194
+ const result = await provisionRoles(adminUrl, {
195
+ ...(process.env.OPENGENI_DB_SCHEMA?.trim() ? { targetSchema: process.env.OPENGENI_DB_SCHEMA.trim() } : {}),
196
+ });
197
+ console.log(JSON.stringify(result, null, 2));
198
+ }