@opengeni/db 0.6.1 → 0.7.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 (53) hide show
  1. package/dist/{chunk-OGCE6O2X.js → chunk-7LDU7F5P.js} +31 -3
  2. package/dist/chunk-7LDU7F5P.js.map +1 -0
  3. package/dist/chunk-B22X3IEZ.js +2634 -0
  4. package/dist/chunk-B22X3IEZ.js.map +1 -0
  5. package/dist/{chunk-57MLICFR.js → chunk-YFQ7SGE4.js} +18 -6
  6. package/dist/chunk-YFQ7SGE4.js.map +1 -0
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.js +16571 -5018
  9. package/dist/index.js.map +1 -1
  10. package/dist/migrate.js +1 -1
  11. package/dist/provision-roles.d.ts +2121 -248
  12. package/dist/provision-roles.js +1 -1
  13. package/dist/{schema-BUbuMteO.d.ts → schema-BN5mB9xZ.d.ts} +8716 -3432
  14. package/dist/schema.d.ts +1 -1
  15. package/dist/schema.js +43 -5
  16. package/drizzle/0044_reap_dead_turn_holders.sql +104 -0
  17. package/drizzle/0045_workspace_captures.sql +83 -0
  18. package/drizzle/0045_workspace_memory_v1.sql +71 -0
  19. package/drizzle/0046_variable_sets_rename.sql +56 -0
  20. package/drizzle/0047_rigs.sql +151 -0
  21. package/drizzle/0048_rig_runtime.sql +9 -0
  22. package/drizzle/0049_enrollment_went_offline.sql +28 -0
  23. package/drizzle/0050_enrollment_op_stream.sql +1 -0
  24. package/drizzle/0051_codex_pin_source.sql +48 -0
  25. package/drizzle/0052_file_upload_cleanup.sql +91 -0
  26. package/drizzle/0053_codex_credential_leases.sql +230 -0
  27. package/drizzle/0054_session_pins.sql +85 -0
  28. package/drizzle/0055_session_list_snapshots.sql +73 -0
  29. package/drizzle/0056_workspace_model_policies.sql +48 -0
  30. package/drizzle/0057_durable_queue_control.sql +536 -0
  31. package/drizzle/0058_turn_admission_usage_enrollment.sql +158 -0
  32. package/drizzle/0059_workspace_pause_control_kind.sql +12 -0
  33. package/drizzle/0060_session_system_update_deferral.sql +10 -0
  34. package/drizzle/0061_session_workflow_wake_outbox.sql +157 -0
  35. package/drizzle/0062_session_list_snapshot_reaper.sql +48 -0
  36. package/drizzle/0063_session_control_mega_foundation.sql +1324 -0
  37. package/package.json +13 -13
  38. package/src/codex-token-resolver.ts +58 -23
  39. package/src/connection-token-resolver.ts +146 -57
  40. package/src/environment-crypto.ts +5 -1
  41. package/src/event-payload-sanitizer.ts +29 -1
  42. package/src/index.ts +20990 -6465
  43. package/src/memory-domain.ts +218 -0
  44. package/src/migrate.ts +58 -3
  45. package/src/provision-roles.ts +46 -17
  46. package/src/schema.ts +2888 -1121
  47. package/src/session-control.ts +1759 -0
  48. package/src/session-queue-commands.ts +1753 -0
  49. package/src/session-tool-call-settlement.ts +269 -0
  50. package/dist/chunk-57MLICFR.js.map +0 -1
  51. package/dist/chunk-OGCE6O2X.js.map +0 -1
  52. package/dist/chunk-ZIUCA2IO.js +0 -1268
  53. package/dist/chunk-ZIUCA2IO.js.map +0 -1
package/dist/schema.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import 'drizzle-orm';
2
2
  import 'drizzle-orm/pg-core';
3
- export { i as agentRunStates, j as apiKeys, k as auditEvents, l as billingCustomers, m as capabilityCatalogItems, n as capabilityInstallations, o as codexRotationSettings, p as codexSubscriptionCredentials, q as connections, r as creditLedgerEntries, t as deviceEnrollmentRequests, d as deviceEnrollmentStatusValues, u as documentBases, v as documentChunks, w as documents, a as enrollmentExposureValues, e as enrollmentOsValues, b as enrollmentStatusValues, x as enrollments, y as fileUploads, z as files, A as githubInstallations, B as importBatches, C as integrationOauthClients, D as integrationOauthStateNonces, E as knowledgeMemories, F as machineMetricsLatest, G as machineMetricsSeries, H as managedAccounts, I as packInstallations, c as sandboxKindValues, J as sandboxLeaseHolders, K as sandboxLeaseLivenessValues, L as sandboxLeases, M as sandboxPtySessions, N as sandboxSessionEnvelopes, O as sandboxes, P as scheduledTaskRuns, Q as scheduledTasks, R as sessionEvents, S as sessionGoals, T as sessionHistoryItems, U as sessionMcpServers, f as sessionRecordingCodecValues, g as sessionRecordingModeValues, h as sessionRecordingStateValues, V as sessionRecordings, W as sessionTurns, X as sessions, Y as socialConnections, Z as socialPosts, _ as stripeWebhookEvents, $ as usageEvents, a0 as workspaceEnvironmentVariables, a1 as workspaceEnvironments, a2 as workspaceMemberships, a3 as workspacePacks, a4 as workspaces } from './schema-BUbuMteO.js';
3
+ export { m as agentRunStates, n as apiKeys, o as auditEvents, p as billingCustomers, q as capabilityCatalogItems, r as capabilityInstallations, t as codexCapacityWaiters, u as codexCredentialLeases, v as codexRotationSettings, w as codexSubscriptionCredentials, c as composerDrafts, x as connections, y as creditLedgerEntries, z as deviceEnrollmentRequests, g as deviceEnrollmentStatusValues, A as documentBases, B as documentChunks, C as documents, f as enrollmentExposureValues, e as enrollmentOsValues, h as enrollmentStatusValues, D as enrollments, E as fileUploads, F as files, G as githubInstallations, H as importBatches, I as integrationOauthClients, J as integrationOauthStateNonces, K as knowledgeMemories, L as machineMetricsLatest, M as machineMetricsSeries, N as managedAccounts, O as packInstallations, P as rigChanges, Q as rigVersions, R as rigs, i as sandboxKindValues, S as sandboxLeaseHolders, T as sandboxLeaseLivenessValues, U as sandboxLeases, V as sandboxPtySessions, W as sandboxSessionEnvelopes, X as sandboxes, Y as scheduledTaskRuns, Z as scheduledTasks, _ as sessionAttemptInterruptions, s as sessionCommandReceipts, $ as sessionEvents, a0 as sessionGoals, a1 as sessionHistoryItems, a2 as sessionListSnapshots, a3 as sessionMcpServers, a4 as sessionPendingToolCalls, a5 as sessionPins, j as sessionRecordingCodecValues, k as sessionRecordingModeValues, l as sessionRecordingStateValues, a6 as sessionRecordings, a7 as sessionSystemUpdateOutbox, a8 as sessionSystemUpdates, a as sessionTurnAttempts, b as sessionTurns, a9 as sessionWorkflowWakeOutbox, aa as sessions, ab as socialConnections, ac as socialPosts, ad as stripeWebhookEvents, ae as usageEvents, af as workspaceCaptures, ag as workspaceControlEvents, ah as workspaceInferenceControls, ai as workspaceMemberships, aj as workspaceModelPolicies, ak as workspacePacks, al as workspaceVariableSetVariables, am as workspaceVariableSets, an as workspaces } from './schema-BN5mB9xZ.js';
package/dist/schema.js CHANGED
@@ -5,8 +5,11 @@ import {
5
5
  billingCustomers,
6
6
  capabilityCatalogItems,
7
7
  capabilityInstallations,
8
+ codexCapacityWaiters,
9
+ codexCredentialLeases,
8
10
  codexRotationSettings,
9
11
  codexSubscriptionCredentials,
12
+ composerDrafts,
10
13
  connections,
11
14
  creditLedgerEntries,
12
15
  deviceEnrollmentRequests,
@@ -29,6 +32,9 @@ import {
29
32
  machineMetricsSeries,
30
33
  managedAccounts,
31
34
  packInstallations,
35
+ rigChanges,
36
+ rigVersions,
37
+ rigs,
32
38
  sandboxKindValues,
33
39
  sandboxLeaseHolders,
34
40
  sandboxLeaseLivenessValues,
@@ -38,26 +44,39 @@ import {
38
44
  sandboxes,
39
45
  scheduledTaskRuns,
40
46
  scheduledTasks,
47
+ sessionAttemptInterruptions,
48
+ sessionCommandReceipts,
41
49
  sessionEvents,
42
50
  sessionGoals,
43
51
  sessionHistoryItems,
52
+ sessionListSnapshots,
44
53
  sessionMcpServers,
54
+ sessionPendingToolCalls,
55
+ sessionPins,
45
56
  sessionRecordingCodecValues,
46
57
  sessionRecordingModeValues,
47
58
  sessionRecordingStateValues,
48
59
  sessionRecordings,
60
+ sessionSystemUpdateOutbox,
61
+ sessionSystemUpdates,
62
+ sessionTurnAttempts,
49
63
  sessionTurns,
64
+ sessionWorkflowWakeOutbox,
50
65
  sessions,
51
66
  socialConnections,
52
67
  socialPosts,
53
68
  stripeWebhookEvents,
54
69
  usageEvents,
55
- workspaceEnvironmentVariables,
56
- workspaceEnvironments,
70
+ workspaceCaptures,
71
+ workspaceControlEvents,
72
+ workspaceInferenceControls,
57
73
  workspaceMemberships,
74
+ workspaceModelPolicies,
58
75
  workspacePacks,
76
+ workspaceVariableSetVariables,
77
+ workspaceVariableSets,
59
78
  workspaces
60
- } from "./chunk-ZIUCA2IO.js";
79
+ } from "./chunk-B22X3IEZ.js";
61
80
  import "./chunk-PZ5AY32C.js";
62
81
  export {
63
82
  agentRunStates,
@@ -66,8 +85,11 @@ export {
66
85
  billingCustomers,
67
86
  capabilityCatalogItems,
68
87
  capabilityInstallations,
88
+ codexCapacityWaiters,
89
+ codexCredentialLeases,
69
90
  codexRotationSettings,
70
91
  codexSubscriptionCredentials,
92
+ composerDrafts,
71
93
  connections,
72
94
  creditLedgerEntries,
73
95
  deviceEnrollmentRequests,
@@ -90,6 +112,9 @@ export {
90
112
  machineMetricsSeries,
91
113
  managedAccounts,
92
114
  packInstallations,
115
+ rigChanges,
116
+ rigVersions,
117
+ rigs,
93
118
  sandboxKindValues,
94
119
  sandboxLeaseHolders,
95
120
  sandboxLeaseLivenessValues,
@@ -99,24 +124,37 @@ export {
99
124
  sandboxes,
100
125
  scheduledTaskRuns,
101
126
  scheduledTasks,
127
+ sessionAttemptInterruptions,
128
+ sessionCommandReceipts,
102
129
  sessionEvents,
103
130
  sessionGoals,
104
131
  sessionHistoryItems,
132
+ sessionListSnapshots,
105
133
  sessionMcpServers,
134
+ sessionPendingToolCalls,
135
+ sessionPins,
106
136
  sessionRecordingCodecValues,
107
137
  sessionRecordingModeValues,
108
138
  sessionRecordingStateValues,
109
139
  sessionRecordings,
140
+ sessionSystemUpdateOutbox,
141
+ sessionSystemUpdates,
142
+ sessionTurnAttempts,
110
143
  sessionTurns,
144
+ sessionWorkflowWakeOutbox,
111
145
  sessions,
112
146
  socialConnections,
113
147
  socialPosts,
114
148
  stripeWebhookEvents,
115
149
  usageEvents,
116
- workspaceEnvironmentVariables,
117
- workspaceEnvironments,
150
+ workspaceCaptures,
151
+ workspaceControlEvents,
152
+ workspaceInferenceControls,
118
153
  workspaceMemberships,
154
+ workspaceModelPolicies,
119
155
  workspacePacks,
156
+ workspaceVariableSetVariables,
157
+ workspaceVariableSets,
120
158
  workspaces
121
159
  };
122
160
  //# sourceMappingURL=schema.js.map
@@ -0,0 +1,104 @@
1
+ -- 0044_reap_dead_turn_holders.sql
2
+ -- Dead-worker turn holders were TTL-exempt forever: a live turn refreshes its
3
+ -- holder every 10s (even a legit multi-day turn), but a worker killed without
4
+ -- cleanup (SIGKILL/OOM/deploy churn) leaves a holder whose heartbeat is frozen.
5
+ -- Left in place it pins refcount >= 1 FOREVER, so the lease never drains, the
6
+ -- reaper never persists /workspace, and the box rides the provider hard-timeout
7
+ -- to an UNPERSISTED death (2026-07-06 staging deploy churn left turn holders
8
+ -- frozen for hours). Reap turn holders whose heartbeat is older than the
9
+ -- caller-supplied horizon. A live holder is touched every 10s from the moment
10
+ -- it is registered (the worker's holder-liveness loop covers the warmup;
11
+ -- the turn heartbeat covers the run), so the horizon the worker passes
12
+ -- (warming-budget + lease-TTL) is generous defense-in-depth, never a bound a
13
+ -- live path can reach; a redispatched turn re-acquires under a NEW holder id,
14
+ -- so a live execution is never touched.
15
+
16
+ CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
17
+ p_viewer_holder_ttl_ms bigint,
18
+ p_turn_holder_ttl_ms bigint,
19
+ p_idle_grace_ms bigint
20
+ )
21
+ RETURNS TABLE (workspace_id uuid, sandbox_group_id uuid, instance_id text, lease_epoch integer)
22
+ LANGUAGE plpgsql
23
+ SECURITY DEFINER
24
+ AS $$
25
+ BEGIN
26
+ DELETE FROM sandbox_lease_holders h
27
+ WHERE h.kind = 'viewer'
28
+ AND h.last_heartbeat_at < now() - make_interval(secs => p_viewer_holder_ttl_ms / 1000.0);
29
+
30
+ -- Dead-worker turn holders (see header). p_turn_holder_ttl_ms <= 0 preserves
31
+ -- the legacy never-reap behaviour.
32
+ IF p_turn_holder_ttl_ms > 0 THEN
33
+ DELETE FROM sandbox_lease_holders h
34
+ WHERE h.kind = 'turn'
35
+ AND h.last_heartbeat_at < now() - make_interval(secs => p_turn_holder_ttl_ms / 1000.0);
36
+ END IF;
37
+
38
+ UPDATE sandbox_leases L SET
39
+ refcount = c.total,
40
+ turn_holders = c.turns,
41
+ viewer_holders = c.viewers,
42
+ liveness = CASE WHEN L.liveness = 'warm' AND c.total = 0 AND c.turns = 0
43
+ THEN 'draining' ELSE L.liveness END,
44
+ expires_at = CASE WHEN L.liveness = 'warm' AND c.total = 0 AND c.turns = 0
45
+ THEN now() + make_interval(secs => p_idle_grace_ms / 1000.0)
46
+ ELSE L.expires_at END,
47
+ updated_at = now()
48
+ FROM (
49
+ SELECT L2.id,
50
+ (SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id)::int AS total,
51
+ (SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id AND h.kind = 'turn')::int AS turns,
52
+ (SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id AND h.kind = 'viewer')::int AS viewers
53
+ FROM sandbox_leases L2
54
+ ) c
55
+ WHERE L.id = c.id;
56
+
57
+ -- Warming died before provider create returned: no instance was ever tracked.
58
+ UPDATE sandbox_leases AS L SET
59
+ liveness = 'cold', instance_id = NULL,
60
+ resume_backend_id = NULL, resume_state = NULL,
61
+ data_plane_url = NULL, terminal_data_plane_url = NULL, updated_at = now()
62
+ WHERE L.liveness = 'warming' AND L.expires_at < now() AND L.instance_id IS NULL;
63
+
64
+ -- Warming died after provider create returned: keep the instance_id and hand it
65
+ -- to the normal provider termination path in this same sweep.
66
+ UPDATE sandbox_leases AS L SET
67
+ liveness = 'draining',
68
+ refcount = 0,
69
+ turn_holders = 0,
70
+ viewer_holders = 0,
71
+ data_plane_url = NULL,
72
+ terminal_data_plane_url = NULL,
73
+ expires_at = now() - interval '1 millisecond',
74
+ updated_at = now()
75
+ WHERE L.liveness = 'warming' AND L.expires_at < now() AND L.instance_id IS NOT NULL;
76
+
77
+ RETURN QUERY
78
+ SELECT L.workspace_id, L.sandbox_group_id, L.instance_id, L.lease_epoch
79
+ FROM sandbox_leases L
80
+ WHERE L.liveness = 'draining' AND L.expires_at < now() AND L.refcount = 0;
81
+ END;
82
+ $$;
83
+
84
+ -- Rolling-deploy compat: workers built before this migration call the 2-arg
85
+ -- form. Delegate with turn-holder reaping OFF (legacy behaviour) until every
86
+ -- worker passes the explicit TTL.
87
+ CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
88
+ p_viewer_holder_ttl_ms bigint,
89
+ p_idle_grace_ms bigint
90
+ )
91
+ RETURNS TABLE (workspace_id uuid, sandbox_group_id uuid, instance_id text, lease_epoch integer)
92
+ LANGUAGE sql
93
+ SECURITY DEFINER
94
+ AS $$
95
+ SELECT * FROM opengeni_private.reap_sandbox_leases(p_viewer_holder_ttl_ms, 0::bigint, p_idle_grace_ms);
96
+ $$;
97
+
98
+ DO $$
99
+ BEGIN
100
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
101
+ GRANT EXECUTE ON FUNCTION opengeni_private.reap_sandbox_leases(bigint, bigint, bigint) TO opengeni_app;
102
+ GRANT EXECUTE ON FUNCTION opengeni_private.reap_sandbox_leases(bigint, bigint) TO opengeni_app;
103
+ END IF;
104
+ END $$;
@@ -0,0 +1,83 @@
1
+ -- Workbench v2 turn-end workspace capture (dossier §10.2; model: 0020 session_recordings).
2
+ --
3
+ -- One row per capture revision — a point-in-time snapshot of a session's changed
4
+ -- files, probed off the live box at TURN END (agent-turn.ts finally, between the
5
+ -- warm snapshot and release). The manifest (tree index + per-repo status/diff +
6
+ -- file index) and each after-image blob live in @opengeni/storage; this row is
7
+ -- the durable index the read routes serve from. `revision` is monotonic per
8
+ -- session (unique (session_id, revision)); `blob_keys` records the content-
9
+ -- addressed after-image keys this revision references so the keep-latest-10 GC
10
+ -- deletes only blobs no surviving revision shares. `lease_epoch` fences a write:
11
+ -- insertWorkspaceCapture only writes when a warm lease at that epoch still exists.
12
+ --
13
+ -- Behind OPENGENI_WORKSPACE_CAPTURE (default on). DDL is inert until the worker
14
+ -- writes rows; forward-only, no existing row references it. RLS + grants mirror
15
+ -- the current schema-aware boilerplate (0043).
16
+
17
+ CREATE TABLE IF NOT EXISTS "workspace_captures" (
18
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
19
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
20
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
21
+ "session_id" uuid NOT NULL REFERENCES "sessions"("id") ON DELETE CASCADE,
22
+ -- The turn that produced this capture. ON DELETE SET NULL: a deleted turn must
23
+ -- not cascade-kill the capture index row.
24
+ "turn_id" uuid REFERENCES "session_turns"("id") ON DELETE SET NULL,
25
+
26
+ -- Monotonic per session (unique on (session_id, revision)).
27
+ "revision" bigint NOT NULL,
28
+ -- The lease epoch the capture was fenced under (paired with revision for cache
29
+ -- invalidation client-side).
30
+ "lease_epoch" integer NOT NULL,
31
+ -- 'available' on a committed capture. 'failed' reserved for a future two-phase
32
+ -- write; the current synchronous capture only ever inserts 'available'.
33
+ "state" text NOT NULL DEFAULT 'available',
34
+
35
+ -- @opengeni/storage keys. manifest = the single JSON blob (tree + repos + file
36
+ -- refs); tree_index = the fs tree blob (kept separate so the API can inline or
37
+ -- sign it independently). blob_keys = the content-addressed after-image keys
38
+ -- this revision references (GC set-difference input).
39
+ "manifest_key" text,
40
+ "tree_index_key" text,
41
+ "blob_keys" jsonb NOT NULL DEFAULT '[]'::jsonb,
42
+
43
+ "size_bytes" bigint,
44
+ "stats" jsonb NOT NULL DEFAULT '{}'::jsonb,
45
+
46
+ "captured_at" timestamptz NOT NULL DEFAULT now(),
47
+
48
+ CONSTRAINT "workspace_captures_state_chk" CHECK ("state" IN ('available','failed'))
49
+ );
50
+
51
+ -- Monotonic revision per session (the assign-revision + read-latest key).
52
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_captures_session_revision_idx"
53
+ ON "workspace_captures" ("session_id", "revision");
54
+ -- Latest-capture lookup and GC scan without touching the event spine.
55
+ CREATE INDEX IF NOT EXISTS "workspace_captures_latest_idx"
56
+ ON "workspace_captures" ("workspace_id", "session_id", "revision" DESC);
57
+
58
+ -- ============== RLS + grants (schema-aware boilerplate, per 0043) =============
59
+ ALTER TABLE "workspace_captures" ENABLE ROW LEVEL SECURITY;
60
+ ALTER TABLE "workspace_captures" FORCE ROW LEVEL SECURITY;
61
+
62
+ DO $$
63
+ BEGIN
64
+ IF EXISTS (
65
+ SELECT 1 FROM pg_policies
66
+ WHERE schemaname = current_schema() AND tablename = 'workspace_captures' AND policyname = 'workspace_isolation'
67
+ ) THEN
68
+ DROP POLICY workspace_isolation ON "workspace_captures";
69
+ END IF;
70
+ END $$;
71
+ CREATE POLICY workspace_isolation ON "workspace_captures"
72
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
73
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
74
+
75
+ DO $$
76
+ BEGIN
77
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
78
+ EXECUTE format(
79
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO opengeni_app',
80
+ current_schema()
81
+ );
82
+ END IF;
83
+ END $$;
@@ -0,0 +1,71 @@
1
+ -- Workspace Memory V1.
2
+ -- Evolves the knowledge_memories table into an agent-writable, hybrid-searchable
3
+ -- memory store, and gives workspaces a growth-ready settings jsonb column.
4
+ -- Follows 0041_knowledge_layer.sql: IF NOT EXISTS column adds, current_schema()
5
+ -- guards, DO blocks for constraints. NO ANN index (3072 dims exceed pgvector's
6
+ -- HNSW cap; reads do a sequential cosine scan like document search). NO policy
7
+ -- changes (the table already has FORCE RLS + workspace_isolation from 0041).
8
+
9
+ -- Growth-ready per-workspace settings bag (holds memoryEnabled and future keys).
10
+ ALTER TABLE "workspaces" ADD COLUMN IF NOT EXISTS "settings" jsonb NOT NULL DEFAULT '{}'::jsonb;
11
+
12
+ -- Memory columns on the existing row (single short texts; no chunk table).
13
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "embedding" vector(3072);
14
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "embedding_model" text;
15
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "pinned" boolean NOT NULL DEFAULT false;
16
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "usage_count" integer NOT NULL DEFAULT 0;
17
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "last_used_at" timestamptz;
18
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "supersedes_id" uuid;
19
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "superseded_by_id" uuid;
20
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "valid_from" timestamptz NOT NULL DEFAULT now();
21
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "valid_until" timestamptz;
22
+ -- Exact-dedup key. Hash of the NORMALIZED text, where normalization is
23
+ -- lower(collapse-internal-whitespace(trim(text))). The application computes the
24
+ -- IDENTICAL value in packages/core/src/domain/memory.ts (normalizeMemoryText +
25
+ -- hashMemoryText) so exact-dup detection matches across the migration backfill
26
+ -- and every runtime write. Keep the two in lockstep.
27
+ ALTER TABLE "knowledge_memories" ADD COLUMN IF NOT EXISTS "text_hash" text;
28
+
29
+ -- Supersession links (same table). ON DELETE SET NULL: dropping a record must not
30
+ -- cascade-delete its chain neighbours.
31
+ DO $$
32
+ BEGIN
33
+ IF NOT EXISTS (
34
+ SELECT 1 FROM pg_constraint WHERE conname = 'knowledge_memories_supersedes_fk'
35
+ ) THEN
36
+ ALTER TABLE "knowledge_memories" ADD CONSTRAINT "knowledge_memories_supersedes_fk"
37
+ FOREIGN KEY ("supersedes_id") REFERENCES "knowledge_memories"("id") ON DELETE SET NULL;
38
+ END IF;
39
+ END $$;
40
+
41
+ DO $$
42
+ BEGIN
43
+ IF NOT EXISTS (
44
+ SELECT 1 FROM pg_constraint WHERE conname = 'knowledge_memories_superseded_by_fk'
45
+ ) THEN
46
+ ALTER TABLE "knowledge_memories" ADD CONSTRAINT "knowledge_memories_superseded_by_fk"
47
+ FOREIGN KEY ("superseded_by_id") REFERENCES "knowledge_memories"("id") ON DELETE SET NULL;
48
+ END IF;
49
+ END $$;
50
+
51
+ -- Working-set + dedup access paths.
52
+ CREATE INDEX IF NOT EXISTS "knowledge_memories_workspace_visible_idx"
53
+ ON "knowledge_memories" ("workspace_id", "pinned" DESC, "updated_at" DESC)
54
+ WHERE "status" IN ('active', 'approved');
55
+ CREATE INDEX IF NOT EXISTS "knowledge_memories_workspace_text_hash_idx"
56
+ ON "knowledge_memories" ("workspace_id", "text_hash");
57
+ -- Enforce the exact-dedup invariant for the agent-visible set. Proposed and
58
+ -- terminal rows remain outside the constraint because agents cannot act on them.
59
+ CREATE UNIQUE INDEX IF NOT EXISTS "knowledge_memories_workspace_visible_text_hash_uq"
60
+ ON "knowledge_memories" ("workspace_id", "text_hash")
61
+ WHERE "status" IN ('active', 'approved') AND "text_hash" IS NOT NULL;
62
+
63
+ -- Backfill text_hash for pre-existing rows using the same normalization the app
64
+ -- applies at write time: lower(collapse-ws(trim(text))), sha256, hex-encoded.
65
+ -- Normalization MUST equal normalizeMemoryText in memory-domain.ts:
66
+ -- collapse every whitespace run to a single space, then trim, then lowercase.
67
+ -- JS: text.replace(/\s+/g, " ").trim().toLowerCase()
68
+ -- SQL: lower(btrim(regexp_replace(text, '\s+', ' ', 'g')))
69
+ UPDATE "knowledge_memories"
70
+ SET "text_hash" = encode(sha256(convert_to(lower(btrim(regexp_replace("text", '\s+', ' ', 'g'))), 'UTF8')), 'hex')
71
+ WHERE "text_hash" IS NULL;
@@ -0,0 +1,56 @@
1
+ -- Rename workspace secret Environments to Variable Sets.
2
+ -- Keeps data in place; public compatibility aliases live in API/contracts/SDK.
3
+
4
+ ALTER TABLE IF EXISTS "workspace_environments" RENAME TO "workspace_variable_sets";
5
+ ALTER TABLE IF EXISTS "workspace_environment_variables" RENAME TO "workspace_variable_set_variables";
6
+
7
+ ALTER TABLE IF EXISTS "workspace_variable_set_variables" RENAME COLUMN "environment_id" TO "variable_set_id";
8
+ ALTER TABLE IF EXISTS "sessions" RENAME COLUMN "environment_id" TO "variable_set_id";
9
+ ALTER TABLE IF EXISTS "scheduled_tasks" RENAME COLUMN "environment_id" TO "variable_set_id";
10
+
11
+ ALTER INDEX IF EXISTS "workspace_environments_workspace_name_idx" RENAME TO "workspace_variable_sets_workspace_name_idx";
12
+ ALTER INDEX IF EXISTS "workspace_environments_workspace_created_idx" RENAME TO "workspace_variable_sets_workspace_created_idx";
13
+ ALTER INDEX IF EXISTS "workspace_environment_variables_env_name_idx" RENAME TO "workspace_variable_set_variables_env_name_idx";
14
+ ALTER INDEX IF EXISTS "workspace_environment_variables_workspace_env_idx" RENAME TO "workspace_variable_set_variables_workspace_env_idx";
15
+ ALTER INDEX IF EXISTS "sessions_environment_idx" RENAME TO "sessions_variable_set_idx";
16
+ ALTER INDEX IF EXISTS "scheduled_tasks_environment_idx" RENAME TO "scheduled_tasks_variable_set_idx";
17
+
18
+ ALTER TABLE "workspace_variable_sets" ENABLE ROW LEVEL SECURITY;
19
+ ALTER TABLE "workspace_variable_sets" FORCE ROW LEVEL SECURITY;
20
+ DO $$
21
+ BEGIN
22
+ IF EXISTS (
23
+ SELECT 1 FROM pg_policies
24
+ WHERE schemaname = current_schema() AND tablename = 'workspace_variable_sets' AND policyname = 'workspace_isolation'
25
+ ) THEN
26
+ DROP POLICY workspace_isolation ON "workspace_variable_sets";
27
+ END IF;
28
+ END $$;
29
+ CREATE POLICY workspace_isolation ON "workspace_variable_sets"
30
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
31
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
32
+
33
+ ALTER TABLE "workspace_variable_set_variables" ENABLE ROW LEVEL SECURITY;
34
+ ALTER TABLE "workspace_variable_set_variables" FORCE ROW LEVEL SECURITY;
35
+ DO $$
36
+ BEGIN
37
+ IF EXISTS (
38
+ SELECT 1 FROM pg_policies
39
+ WHERE schemaname = current_schema() AND tablename = 'workspace_variable_set_variables' AND policyname = 'workspace_isolation'
40
+ ) THEN
41
+ DROP POLICY workspace_isolation ON "workspace_variable_set_variables";
42
+ END IF;
43
+ END $$;
44
+ CREATE POLICY workspace_isolation ON "workspace_variable_set_variables"
45
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
46
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
47
+
48
+ DO $$
49
+ BEGIN
50
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
51
+ EXECUTE format(
52
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO opengeni_app',
53
+ current_schema()
54
+ );
55
+ END IF;
56
+ END $$;
@@ -0,0 +1,151 @@
1
+ -- Rigs: workspace-scoped, versioned sandbox machine definitions.
2
+ -- rigs — the named definition (one row per rig).
3
+ -- rig_versions — append-only, content-immutable versions; exactly one active
4
+ -- per rig (partial unique index). Only the `active` flag flips.
5
+ -- rig_changes — proposed/verified change records (M4 substrate only here).
6
+ -- Plus M3 wiring columns (sessions/scheduled_tasks/workspaces) added now so the
7
+ -- runtime-binding milestone needs no further migration. RLS/GRANT idiom mirrors
8
+ -- 0041_knowledge_layer.sql.
9
+
10
+ CREATE TABLE IF NOT EXISTS "rigs" (
11
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
12
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
13
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
14
+ "name" text NOT NULL,
15
+ "description" text,
16
+ "created_by" text,
17
+ "created_at" timestamptz NOT NULL DEFAULT now(),
18
+ "updated_at" timestamptz NOT NULL DEFAULT now()
19
+ );
20
+
21
+ CREATE UNIQUE INDEX IF NOT EXISTS "rigs_workspace_name_idx" ON "rigs" ("workspace_id", "name");
22
+ CREATE INDEX IF NOT EXISTS "rigs_workspace_created_idx" ON "rigs" ("workspace_id", "created_at");
23
+
24
+ CREATE TABLE IF NOT EXISTS "rig_versions" (
25
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
26
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
27
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
28
+ "rig_id" uuid NOT NULL REFERENCES "rigs"("id") ON DELETE CASCADE,
29
+ "version" integer NOT NULL,
30
+ "image" text,
31
+ "setup_script" text,
32
+ "checks" jsonb NOT NULL DEFAULT '[]'::jsonb,
33
+ "credential_hooks" jsonb NOT NULL DEFAULT '[]'::jsonb,
34
+ "default_variable_set_ids" jsonb NOT NULL DEFAULT '[]'::jsonb,
35
+ "changelog" text,
36
+ "created_by" text,
37
+ "active" boolean NOT NULL DEFAULT false,
38
+ "created_at" timestamptz NOT NULL DEFAULT now()
39
+ );
40
+
41
+ -- Monotonic version number per rig.
42
+ CREATE UNIQUE INDEX IF NOT EXISTS "rig_versions_rig_version_idx" ON "rig_versions" ("rig_id", "version");
43
+ -- At most one active version per rig (single-active invariant enforced in DB).
44
+ CREATE UNIQUE INDEX IF NOT EXISTS "rig_versions_rig_active_idx" ON "rig_versions" ("rig_id") WHERE "active";
45
+ CREATE INDEX IF NOT EXISTS "rig_versions_workspace_rig_idx" ON "rig_versions" ("workspace_id", "rig_id", "version");
46
+
47
+ CREATE TABLE IF NOT EXISTS "rig_changes" (
48
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
49
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
50
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
51
+ "rig_id" uuid NOT NULL REFERENCES "rigs"("id") ON DELETE CASCADE,
52
+ "base_version_id" uuid REFERENCES "rig_versions"("id") ON DELETE SET NULL,
53
+ "kind" text NOT NULL,
54
+ "payload" jsonb NOT NULL,
55
+ "status" text NOT NULL DEFAULT 'proposed',
56
+ "proposed_by" text,
57
+ "verification" jsonb,
58
+ "result_version_id" uuid REFERENCES "rig_versions"("id") ON DELETE SET NULL,
59
+ "created_at" timestamptz NOT NULL DEFAULT now(),
60
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
61
+ CONSTRAINT "rig_changes_kind_check" CHECK ("kind" IN ('setup_append', 'definition_edit')),
62
+ CONSTRAINT "rig_changes_status_check" CHECK ("status" IN ('proposed', 'verifying', 'merged', 'rejected', 'failed'))
63
+ );
64
+
65
+ CREATE INDEX IF NOT EXISTS "rig_changes_workspace_rig_idx" ON "rig_changes" ("workspace_id", "rig_id", "created_at");
66
+ CREATE INDEX IF NOT EXISTS "rig_changes_workspace_status_idx" ON "rig_changes" ("workspace_id", "status");
67
+
68
+ -- M3 wiring columns (inert until the runtime-binding milestone consumes them).
69
+ ALTER TABLE "sessions" ADD COLUMN IF NOT EXISTS "rig_id" uuid;
70
+ ALTER TABLE "sessions" ADD COLUMN IF NOT EXISTS "rig_version_id" uuid;
71
+ ALTER TABLE "scheduled_tasks" ADD COLUMN IF NOT EXISTS "rig_id" uuid;
72
+ ALTER TABLE "workspaces" ADD COLUMN IF NOT EXISTS "default_rig_id" uuid;
73
+
74
+ DO $$
75
+ BEGIN
76
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'sessions_rig_id_fk') THEN
77
+ ALTER TABLE "sessions" ADD CONSTRAINT "sessions_rig_id_fk"
78
+ FOREIGN KEY ("rig_id") REFERENCES "rigs"("id") ON DELETE SET NULL;
79
+ END IF;
80
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'sessions_rig_version_id_fk') THEN
81
+ ALTER TABLE "sessions" ADD CONSTRAINT "sessions_rig_version_id_fk"
82
+ FOREIGN KEY ("rig_version_id") REFERENCES "rig_versions"("id") ON DELETE SET NULL;
83
+ END IF;
84
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'scheduled_tasks_rig_id_fk') THEN
85
+ ALTER TABLE "scheduled_tasks" ADD CONSTRAINT "scheduled_tasks_rig_id_fk"
86
+ FOREIGN KEY ("rig_id") REFERENCES "rigs"("id") ON DELETE SET NULL;
87
+ END IF;
88
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'workspaces_default_rig_id_fk') THEN
89
+ ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_default_rig_id_fk"
90
+ FOREIGN KEY ("default_rig_id") REFERENCES "rigs"("id") ON DELETE SET NULL;
91
+ END IF;
92
+ END $$;
93
+
94
+ CREATE INDEX IF NOT EXISTS "sessions_rig_idx" ON "sessions" ("workspace_id", "rig_id");
95
+ CREATE INDEX IF NOT EXISTS "scheduled_tasks_rig_idx" ON "scheduled_tasks" ("workspace_id", "rig_id");
96
+
97
+ -- Row-level security: workspace isolation on every rig table.
98
+ ALTER TABLE "rigs" ENABLE ROW LEVEL SECURITY;
99
+ ALTER TABLE "rigs" FORCE ROW LEVEL SECURITY;
100
+ DO $$
101
+ BEGIN
102
+ IF EXISTS (
103
+ SELECT 1 FROM pg_policies
104
+ WHERE schemaname = current_schema() AND tablename = 'rigs' AND policyname = 'workspace_isolation'
105
+ ) THEN
106
+ DROP POLICY workspace_isolation ON "rigs";
107
+ END IF;
108
+ END $$;
109
+ CREATE POLICY workspace_isolation ON "rigs"
110
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
111
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
112
+
113
+ ALTER TABLE "rig_versions" ENABLE ROW LEVEL SECURITY;
114
+ ALTER TABLE "rig_versions" FORCE ROW LEVEL SECURITY;
115
+ DO $$
116
+ BEGIN
117
+ IF EXISTS (
118
+ SELECT 1 FROM pg_policies
119
+ WHERE schemaname = current_schema() AND tablename = 'rig_versions' AND policyname = 'workspace_isolation'
120
+ ) THEN
121
+ DROP POLICY workspace_isolation ON "rig_versions";
122
+ END IF;
123
+ END $$;
124
+ CREATE POLICY workspace_isolation ON "rig_versions"
125
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
126
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
127
+
128
+ ALTER TABLE "rig_changes" ENABLE ROW LEVEL SECURITY;
129
+ ALTER TABLE "rig_changes" FORCE ROW LEVEL SECURITY;
130
+ DO $$
131
+ BEGIN
132
+ IF EXISTS (
133
+ SELECT 1 FROM pg_policies
134
+ WHERE schemaname = current_schema() AND tablename = 'rig_changes' AND policyname = 'workspace_isolation'
135
+ ) THEN
136
+ DROP POLICY workspace_isolation ON "rig_changes";
137
+ END IF;
138
+ END $$;
139
+ CREATE POLICY workspace_isolation ON "rig_changes"
140
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
141
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
142
+
143
+ DO $$
144
+ BEGIN
145
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
146
+ EXECUTE format(
147
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO opengeni_app',
148
+ current_schema()
149
+ );
150
+ END IF;
151
+ END $$;
@@ -0,0 +1,9 @@
1
+ -- M3 runtime binding: stamp the frozen rig version onto the group lease so a
2
+ -- live shared box conflicts on a rig-version mismatch exactly as it does on an
3
+ -- image mismatch (a shared box is one filesystem; a different rig = different
4
+ -- setup/tooling baked in). Nullable: a legacy/cold row or a rig-less session
5
+ -- reads NULL = "rig unknown", which never conflicts (rig-less sessions share as
6
+ -- today). sandbox_leases carries NO row-level security (it is keyed by a bare
7
+ -- sandbox_group_id, not an FK — see schema.ts), so this is a bare ADD COLUMN
8
+ -- with no policy/grant changes, mirroring the existing `image` column.
9
+ ALTER TABLE "sandbox_leases" ADD COLUMN IF NOT EXISTS "rig_version_id" uuid;
@@ -0,0 +1,28 @@
1
+ -- Immediate-offline on a CLEAN going-offline signal.
2
+ --
3
+ -- A machine that explicitly announced "I'm stopping" (a typed GoingOffline:
4
+ -- user-stop / self-update / host-shutdown) must read OFFLINE immediately — for the
5
+ -- Machines dashboard AND for anything deciding whether to route work at it —
6
+ -- instead of waiting out the last_seen dead-detect window. Persist that goodbye as
7
+ -- a nullable marker on the enrollment: went_offline_at (WHEN it said goodbye) +
8
+ -- went_offline_reason (the typed reason string). The liveness derivation gives an
9
+ -- un-cleared marker precedence over last_seen aging; ANY newer liveness signal (a
10
+ -- reconnect Hello or a fresher heartbeat) clears it back to NULL. Both NULL (the
11
+ -- default) ⇒ today's behavior exactly — every existing + new row is a
12
+ -- behavior-preserving no-op, no backfill.
13
+ ALTER TABLE "enrollments"
14
+ ADD COLUMN IF NOT EXISTS "went_offline_at" timestamptz,
15
+ ADD COLUMN IF NOT EXISTS "went_offline_reason" text;
16
+
17
+ -- Re-grant on the new columns (idempotent; schema-agnostic per the Migration
18
+ -- Authoring rules — grants target current_schema() via dynamic SQL so an embedded
19
+ -- host running under a dedicated data schema is covered, not just `public`).
20
+ DO $$
21
+ BEGIN
22
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
23
+ EXECUTE format(
24
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO opengeni_app',
25
+ current_schema()
26
+ );
27
+ END IF;
28
+ END $$;
@@ -0,0 +1 @@
1
+ ALTER TABLE "enrollments" ADD COLUMN "op_stream" boolean DEFAULT false NOT NULL;