@opengeni/db 4.4.0-canary.2 → 5.0.0-canary.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 (50) hide show
  1. package/dist/artifact-catalog.d.ts +41 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/{chunk-M4PKBJRS.js → chunk-6T2GGYWE.js} +4 -4
  4. package/dist/{chunk-2KOLL33E.js → chunk-7HMK3B45.js} +2 -2
  5. package/dist/{chunk-WFU6PS54.js → chunk-DPONPYJD.js} +2 -2
  6. package/dist/{chunk-6IJD3XXF.js → chunk-ERKVSPDX.js} +3 -3
  7. package/dist/{chunk-5UUWZDAV.js → chunk-F73UKJY6.js} +31 -1
  8. package/dist/{chunk-5UUWZDAV.js.map → chunk-F73UKJY6.js.map} +1 -1
  9. package/dist/{chunk-6V3HDEMR.js → chunk-HQDVI2DB.js} +2 -2
  10. package/dist/{chunk-UOXWVP3T.js → chunk-MWMXWHJD.js} +2 -2
  11. package/dist/{chunk-CHBLBEU6.js → chunk-T6BWIGWJ.js} +2 -2
  12. package/dist/{chunk-XJP7KJ6U.js → chunk-T6YLHJLS.js} +47 -3
  13. package/dist/chunk-T6YLHJLS.js.map +1 -0
  14. package/dist/editable-artifact-durable-export.js +2 -2
  15. package/dist/editable-artifacts.js +3 -3
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1893 -1812
  18. package/dist/index.js.map +1 -1
  19. package/dist/managed-auth-session-sets.js +3 -3
  20. package/dist/mcp-operations.js +2 -2
  21. package/dist/provision-roles.js +1 -1
  22. package/dist/retained-provider-commands.js +2 -2
  23. package/dist/runtime-posture.d.ts +2 -0
  24. package/dist/schema.d.ts +93 -0
  25. package/dist/schema.js +3 -1
  26. package/dist/session-background-commands.js +4 -4
  27. package/dist/session-command-output.js +4 -4
  28. package/dist/session-event-slices.js +2 -2
  29. package/dist/session-mcp-credential-rotation.js +4 -4
  30. package/dist/session-tenancy.js +3 -3
  31. package/dist/video-generation.js +3 -3
  32. package/dist/workspace-tool-defaults.js +4 -4
  33. package/drizzle/0465_sandbox_file_publications.sql +142 -0
  34. package/drizzle/0466_agent_instruction_activation_preservation.sql +190 -0
  35. package/drizzle/0467_host_resolver_full_organization_keys.sql +34 -0
  36. package/package.json +6 -6
  37. package/src/artifact-catalog.ts +121 -0
  38. package/src/host-mcp-resolvers.ts +1 -1
  39. package/src/index.ts +1 -0
  40. package/src/provision-roles.ts +6 -0
  41. package/src/runtime-posture.ts +54 -1
  42. package/src/schema.ts +30 -0
  43. package/dist/chunk-XJP7KJ6U.js.map +0 -1
  44. /package/dist/{chunk-M4PKBJRS.js.map → chunk-6T2GGYWE.js.map} +0 -0
  45. /package/dist/{chunk-2KOLL33E.js.map → chunk-7HMK3B45.js.map} +0 -0
  46. /package/dist/{chunk-WFU6PS54.js.map → chunk-DPONPYJD.js.map} +0 -0
  47. /package/dist/{chunk-6IJD3XXF.js.map → chunk-ERKVSPDX.js.map} +0 -0
  48. /package/dist/{chunk-6V3HDEMR.js.map → chunk-HQDVI2DB.js.map} +0 -0
  49. /package/dist/{chunk-UOXWVP3T.js.map → chunk-MWMXWHJD.js.map} +0 -0
  50. /package/dist/{chunk-CHBLBEU6.js.map → chunk-T6BWIGWJ.js.map} +0 -0
@@ -0,0 +1,190 @@
1
+ -- deployment-mode: rolling
2
+ -- Agent instruction writes are additive or localized. Older application
3
+ -- instances may still omit editMode while this migration rolls out, but their
4
+ -- complete-content request is accepted only when it contains the active
5
+ -- instruction byte-for-byte. Existing unsafe pending revisions and explicit
6
+ -- replacement revisions are fenced again at the central activation ledger.
7
+
8
+ ALTER FUNCTION agent_instruction_apply(uuid,uuid,jsonb,jsonb)
9
+ RENAME TO agent_instruction_apply_0462_unsafe;
10
+
11
+ CREATE FUNCTION agent_instruction_apply(p_account uuid,p_workspace uuid,p_actor jsonb,p_request jsonb)
12
+ RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $$
13
+ DECLARE
14
+ operation text:=p_request->>'operation';
15
+ edit_mode text;
16
+ current_state jsonb;
17
+ current_content text;
18
+ requested_content text;
19
+ old_text text;
20
+ new_text text;
21
+ expected_content text;
22
+ first_position integer;
23
+ second_position integer;
24
+ preservation jsonb;
25
+ effective_actor jsonb:=p_actor;
26
+ BEGIN
27
+ IF operation='save' THEN
28
+ -- Preserve the exact fingerprint of operations first executed by either
29
+ -- side of this rolling boundary. New receipts carry the derived marker in
30
+ -- their stored actor; older receipts intentionally do not.
31
+ IF p_request->>'operationId' IS NOT NULL THEN
32
+ SELECT op.actor->'_instructionPreservation' INTO preservation
33
+ FROM agent_instruction_operations op
34
+ WHERE op.account_id=p_account
35
+ AND op.operation_id=(p_request->>'operationId')::uuid;
36
+ IF FOUND THEN
37
+ IF preservation IS NOT NULL THEN
38
+ effective_actor:=p_actor||jsonb_build_object(
39
+ '_instructionPreservation',preservation);
40
+ END IF;
41
+ RETURN agent_instruction_apply_0462_unsafe(
42
+ p_account,p_workspace,effective_actor,p_request);
43
+ END IF;
44
+ END IF;
45
+ current_state:=agent_instruction_apply_0462_unsafe(
46
+ p_account,p_workspace,p_actor,
47
+ jsonb_build_object('operation','get','target',p_request->'target'));
48
+ current_content:=current_state->>'content';
49
+ IF p_request ? 'editMode' THEN
50
+ edit_mode:=p_request->>'editMode';
51
+ IF edit_mode IS NULL OR edit_mode NOT IN ('append','edit') THEN
52
+ RAISE EXCEPTION 'Agents cannot replace the complete workspace instruction'
53
+ USING ERRCODE='22023';
54
+ END IF;
55
+ IF edit_mode='append' THEN
56
+ requested_content:=p_request->>'content';
57
+ expected_content:=CASE
58
+ WHEN current_content IS NULL THEN requested_content
59
+ WHEN right(current_content,2)=E'\n\n' THEN current_content||requested_content
60
+ WHEN right(current_content,1)=E'\n' THEN current_content||E'\n'||requested_content
61
+ ELSE current_content||E'\n\n'||requested_content
62
+ END;
63
+ ELSE
64
+ old_text:=p_request->>'oldText';
65
+ new_text:=p_request->>'newText';
66
+ IF current_content IS NOT NULL AND old_text IS NOT DISTINCT FROM current_content THEN
67
+ RAISE EXCEPTION 'Exact instruction edits must be localized; the complete instruction cannot be replaced'
68
+ USING ERRCODE='22023';
69
+ END IF;
70
+ first_position:=strpos(current_content,old_text);
71
+ IF first_position=0 THEN
72
+ RAISE EXCEPTION 'Exact instruction edit anchor was not found' USING ERRCODE='22023';
73
+ END IF;
74
+ second_position:=strpos(substring(current_content FROM first_position+1),old_text);
75
+ IF second_position>0 THEN
76
+ RAISE EXCEPTION 'Exact instruction edit anchor is ambiguous' USING ERRCODE='22023';
77
+ END IF;
78
+ expected_content:=overlay(current_content PLACING new_text FROM first_position FOR length(old_text));
79
+ END IF;
80
+ ELSE
81
+ -- A pre-0462 caller already constructed a complete proposed document.
82
+ -- Preserve rolling availability for additive requests without allowing
83
+ -- that compatibility path to discard any active byte.
84
+ requested_content:=p_request->>'content';
85
+ IF current_content IS NOT NULL
86
+ AND (requested_content IS NULL OR strpos(requested_content,current_content)=0) THEN
87
+ RAISE EXCEPTION 'Legacy instruction replacement would discard active workspace instructions'
88
+ USING ERRCODE='22023';
89
+ END IF;
90
+ edit_mode:='legacy_safe';
91
+ expected_content:=requested_content;
92
+ END IF;
93
+ preservation:=jsonb_strip_nulls(jsonb_build_object(
94
+ 'saveOperationId',p_request->>'operationId',
95
+ 'baselineContentHash',CASE WHEN current_content IS NULL THEN NULL
96
+ ELSE encode(sha256(convert_to(current_content,'UTF8')),'hex') END,
97
+ 'resultContentHash',CASE WHEN expected_content IS NULL THEN NULL
98
+ ELSE encode(sha256(convert_to(expected_content,'UTF8')),'hex') END,
99
+ 'editMode',edit_mode));
100
+ effective_actor:=p_actor||jsonb_build_object(
101
+ '_instructionPreservation',preservation);
102
+ END IF;
103
+ RETURN agent_instruction_apply_0462_unsafe(
104
+ p_account,p_workspace,effective_actor,p_request);
105
+ END $$;
106
+
107
+ CREATE FUNCTION agent_instruction_activation_preserves_baseline()
108
+ RETURNS trigger LANGUAGE plpgsql SET search_path FROM CURRENT AS $$
109
+ DECLARE
110
+ proposed workspace_instruction_policy_revisions%ROWTYPE;
111
+ baseline_content text;
112
+ baseline_hash text;
113
+ proposed_hash text;
114
+ preservation jsonb;
115
+ BEGIN
116
+ IF NEW.type<>'activate' OR NEW.old_revision_id IS NULL THEN RETURN NEW; END IF;
117
+ SELECT * INTO proposed FROM workspace_instruction_policy_revisions r
118
+ WHERE r.account_id=NEW.account_id AND r.workspace_id=NEW.workspace_id
119
+ AND r.id=NEW.new_revision_id;
120
+ IF NOT FOUND OR proposed.provenance_source<>'agent_learning' THEN RETURN NEW; END IF;
121
+ SELECT r.content INTO baseline_content FROM workspace_instruction_policy_revisions r
122
+ WHERE r.account_id=NEW.account_id AND r.workspace_id=NEW.workspace_id
123
+ AND r.id=NEW.old_revision_id;
124
+ IF NOT FOUND THEN
125
+ RAISE EXCEPTION 'The active instruction baseline is unavailable'
126
+ USING ERRCODE='22023';
127
+ END IF;
128
+ -- Compatibility revisions that retain the complete baseline are safe even
129
+ -- when they predate the wrapper-derived preservation metadata below.
130
+ IF baseline_content IS NOT NULL AND strpos(proposed.content,baseline_content)>0 THEN
131
+ RETURN NEW;
132
+ END IF;
133
+ baseline_hash:=encode(sha256(convert_to(baseline_content,'UTF8')),'hex');
134
+ proposed_hash:=encode(sha256(convert_to(proposed.content,'UTF8')),'hex');
135
+ preservation:=proposed.agent_learning_context#>'{actor,_instructionPreservation}';
136
+ IF preservation->>'saveOperationId'=proposed.operation_id::text
137
+ AND preservation->>'baselineContentHash'=baseline_hash
138
+ AND preservation->>'resultContentHash'=proposed_hash
139
+ AND preservation->>'editMode' IN ('append','edit','legacy_safe') THEN
140
+ RETURN NEW;
141
+ END IF;
142
+ RAISE EXCEPTION 'Unsafe whole-instruction agent replacement cannot be activated; reject it and create an append or localized edit'
143
+ USING ERRCODE='22023';
144
+ END $$;
145
+
146
+ DROP TRIGGER IF EXISTS agent_instruction_activation_preservation
147
+ ON workspace_instruction_policy_activation_events;
148
+ CREATE TRIGGER agent_instruction_activation_preservation
149
+ BEFORE INSERT ON workspace_instruction_policy_activation_events
150
+ FOR EACH ROW EXECUTE FUNCTION agent_instruction_activation_preserves_baseline();
151
+
152
+ DO $secure$
153
+ DECLARE grantee text; runtime_role text;
154
+ BEGIN
155
+ EXECUTE format(
156
+ 'ALTER FUNCTION %I.agent_instruction_apply_0462_unsafe(uuid,uuid,jsonb,jsonb) SET search_path = %I, pg_catalog, pg_temp',
157
+ current_schema(),current_schema());
158
+ EXECUTE format(
159
+ 'ALTER FUNCTION %I.agent_instruction_apply(uuid,uuid,jsonb,jsonb) SET search_path = %I, pg_catalog, pg_temp',
160
+ current_schema(),current_schema());
161
+ EXECUTE format(
162
+ 'ALTER FUNCTION %I.agent_instruction_activation_preserves_baseline() SET search_path = %I, pg_catalog, pg_temp',
163
+ current_schema(),current_schema());
164
+ REVOKE ALL ON FUNCTION agent_instruction_apply_0462_unsafe(uuid,uuid,jsonb,jsonb) FROM PUBLIC;
165
+ REVOKE ALL ON FUNCTION agent_instruction_apply(uuid,uuid,jsonb,jsonb) FROM PUBLIC;
166
+ REVOKE ALL ON FUNCTION agent_instruction_activation_preserves_baseline() FROM PUBLIC;
167
+ FOR grantee IN SELECT DISTINCT role.rolname FROM pg_proc proc,
168
+ LATERAL aclexplode(coalesce(proc.proacl,acldefault('f',proc.proowner))) acl
169
+ JOIN pg_roles role ON role.oid=acl.grantee
170
+ WHERE proc.pronamespace=current_schema()::regnamespace
171
+ AND proc.proname='agent_instruction_apply_0462_unsafe'
172
+ AND acl.grantee<>proc.proowner
173
+ LOOP
174
+ EXECUTE format(
175
+ 'REVOKE ALL ON FUNCTION %I.agent_instruction_apply_0462_unsafe(uuid,uuid,jsonb,jsonb) FROM %I',
176
+ current_schema(),grantee);
177
+ END LOOP;
178
+ FOR runtime_role IN SELECT jsonb_array_elements_text(
179
+ current_setting('opengeni.migration_application_roles')::jsonb)
180
+ LOOP
181
+ IF runtime_role<>current_user AND EXISTS(SELECT 1 FROM pg_roles WHERE rolname=runtime_role) THEN
182
+ EXECUTE format(
183
+ 'REVOKE ALL ON FUNCTION %I.agent_instruction_apply_0462_unsafe(uuid,uuid,jsonb,jsonb) FROM %I',
184
+ current_schema(),runtime_role);
185
+ EXECUTE format(
186
+ 'GRANT EXECUTE ON FUNCTION %I.agent_instruction_apply(uuid,uuid,jsonb,jsonb) TO %I',
187
+ current_schema(),runtime_role);
188
+ END IF;
189
+ END LOOP;
190
+ END $secure$;
@@ -0,0 +1,34 @@
1
+ -- deployment-mode: rolling
2
+ -- Align the existing resolver write fence with public full organization keys.
3
+ -- No grants, ownership, row policies, or stored credentials change.
4
+ CREATE OR REPLACE FUNCTION opengeni_private.guard_host_mcp_resolver_write() RETURNS trigger
5
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
6
+ DECLARE permissions_value jsonb;
7
+ BEGIN
8
+ SELECT credential.permissions INTO permissions_value FROM api_keys credential
9
+ WHERE credential.account_id = NEW.account_id
10
+ AND 'api_key:' || credential.id::text = opengeni_private.current_subject_id()
11
+ AND credential.workspace_id IS NULL AND credential.credential_kind = 'organization'
12
+ AND credential.revoked_at IS NULL
13
+ AND (credential.expires_at IS NULL OR credential.expires_at > clock_timestamp())
14
+ FOR SHARE;
15
+ IF NEW.account_id IS DISTINCT FROM opengeni_private.current_account_id()
16
+ OR NOT coalesce(permissions_value ? 'workspace:admin', false) THEN
17
+ RAISE EXCEPTION 'organization service administration required' USING ERRCODE = '42501';
18
+ END IF;
19
+ IF TG_TABLE_NAME = 'host_mcp_resolvers' THEN
20
+ IF TG_OP = 'UPDATE' THEN
21
+ IF ROW(NEW.id, NEW.account_id, NEW.external_source, NEW.created_at)
22
+ IS DISTINCT FROM ROW(OLD.id, OLD.account_id, OLD.external_source, OLD.created_at)
23
+ OR NEW.generation <> OLD.generation + 1 THEN
24
+ RAISE EXCEPTION 'resolver identity or generation conflict' USING ERRCODE = '40001';
25
+ END IF;
26
+ ELSIF NEW.generation <> 1 THEN
27
+ RAISE EXCEPTION 'initial resolver generation invalid' USING ERRCODE = '22023';
28
+ END IF;
29
+ ELSIF NEW.actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id() THEN
30
+ RAISE EXCEPTION 'resolver operation actor invalid' USING ERRCODE = '42501';
31
+ END IF;
32
+ RETURN NEW;
33
+ END
34
+ $body$;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/db",
3
- "version": "4.4.0-canary.2",
3
+ "version": "5.0.0-canary.0",
4
4
  "description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -102,11 +102,11 @@
102
102
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
103
103
  },
104
104
  "dependencies": {
105
- "@opengeni/codemode": "^0.5.7-canary.2",
106
- "@opengeni/codex": "^0.2.22-canary.14",
107
- "@opengeni/config": "^1.2.0-canary.2",
108
- "@opengeni/contracts": "^3.1.0-canary.2",
109
- "@opengeni/network": "^0.3.1-canary.14",
105
+ "@opengeni/codemode": "^0.5.8-canary.0",
106
+ "@opengeni/codex": "^0.2.23-canary.0",
107
+ "@opengeni/config": "^1.2.1-canary.0",
108
+ "@opengeni/contracts": "^4.0.0-canary.0",
109
+ "@opengeni/network": "^0.3.1-canary.15",
110
110
  "drizzle-orm": "^0.45.2",
111
111
  "postgres": "^3.4.7"
112
112
  },
@@ -0,0 +1,121 @@
1
+ import type { ArtifactCatalogKind, ArtifactCatalogListQuery } from "@opengeni/contracts";
2
+ import { sql, type SQL } from "drizzle-orm";
3
+ import { withRlsContext, type Database } from "./database";
4
+
5
+ export type ArtifactCatalogPosition = { key: string; kind: ArtifactCatalogKind; id: string };
6
+ /** Internal candidates are NOT an authorized public projection. */
7
+ export type ArtifactCatalogCandidate = {
8
+ id: string;
9
+ kind: ArtifactCatalogKind;
10
+ /** Internal adapter identity; never exposed as public provenance. */
11
+ origin: "site" | "editable_artifact" | "generated_image" | "sandbox_file";
12
+ title: string;
13
+ status: "active" | "archived";
14
+ created_at: Date | string;
15
+ updated_at: Date | string;
16
+ source_session_id: string | null;
17
+ version_id: string | null;
18
+ sort_key: string;
19
+ };
20
+
21
+ /** Called only after immutable bytes have been retained by explicit publication. */
22
+ export async function recordSandboxFilePublication(
23
+ db: Database,
24
+ input: { accountId: string; workspaceId: string; fileId: string; sourceSessionId: string },
25
+ ): Promise<void> {
26
+ await withRlsContext(db, input, async (tx) => {
27
+ await tx.execute(
28
+ sql`select opengeni_private.record_sandbox_file_publication(${input.accountId}::uuid, ${input.workspaceId}::uuid, ${input.fileId}::uuid, ${input.sourceSessionId}::uuid)`,
29
+ );
30
+ });
31
+ }
32
+
33
+ /** Bounded keyset projection over existing domains; no universal content store.
34
+ * The caller must still perform editable application and file access checks.
35
+ */
36
+ export async function listArtifactCatalogCandidates(
37
+ db: Database,
38
+ scope: { accountId: string; workspaceId: string },
39
+ input: {
40
+ query: ArtifactCatalogListQuery;
41
+ kinds: readonly ArtifactCatalogKind[];
42
+ snapshotAt: string;
43
+ after?: ArtifactCatalogPosition;
44
+ limit: number;
45
+ },
46
+ ): Promise<ArtifactCatalogCandidate[]> {
47
+ if (!input.kinds.length) return [];
48
+ if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 201)
49
+ throw new Error("Invalid catalog candidate limit");
50
+ const { query } = input;
51
+ const source = query.sourceSessionId;
52
+ const branches: SQL[] = [];
53
+ if (input.kinds.includes("site"))
54
+ branches.push(sql`
55
+ SELECT a.id::text, 'site'::text AS kind, a.title, a.status, a.created_at, a.updated_at,
56
+ ${source ? sql`${source}::uuid` : sql`v.source_session_id`} AS source_session_id,
57
+ a.current_version_id::text AS version_id, 'site'::text AS origin
58
+ FROM workspace_artifacts a LEFT JOIN workspace_artifact_versions v
59
+ ON v.workspace_id = a.workspace_id AND v.artifact_id = a.id AND v.id = a.current_version_id
60
+ WHERE a.account_id = ${scope.accountId}::uuid AND a.workspace_id = ${scope.workspaceId}::uuid
61
+ ${source ? sql`AND EXISTS (SELECT 1 FROM workspace_artifact_versions sv WHERE sv.workspace_id = a.workspace_id AND sv.artifact_id = a.id AND sv.source_session_id = ${source}::uuid)` : sql``}`);
62
+ const editableKinds = input.kinds.filter((kind) =>
63
+ ["document", "spreadsheet", "presentation"].includes(kind),
64
+ );
65
+ if (editableKinds.length)
66
+ branches.push(sql`
67
+ SELECT a.id, a.modality AS kind, a.title, a.lifecycle_state AS status, a.created_at, a.updated_at,
68
+ ${source ? sql`${source}::uuid` : sql`NULL::uuid`} AS source_session_id, NULL::text AS version_id, 'editable_artifact'::text AS origin
69
+ FROM editable_artifacts a WHERE a.account_id = ${scope.accountId}::uuid AND a.workspace_id = ${scope.workspaceId}::uuid
70
+ AND a.modality IN (${sql.join(
71
+ editableKinds.map((kind) => sql`${kind}`),
72
+ sql`, `,
73
+ )})
74
+ ${source ? sql`AND EXISTS (SELECT 1 FROM editable_artifact_session_links l WHERE l.account_id = a.account_id AND l.workspace_id = a.workspace_id AND l.artifact_id = a.id AND l.session_id = ${source}::uuid)` : sql``}`);
75
+ if (input.kinds.includes("image"))
76
+ branches.push(sql`
77
+ SELECT f.id::text, 'image'::text AS kind, f.filename AS title, 'active'::text AS status,
78
+ g.created_at, g.updated_at, g.session_id AS source_session_id, NULL::text AS version_id, 'generated_image'::text AS origin
79
+ FROM generated_image_artifacts g JOIN files f ON f.account_id = g.account_id AND f.workspace_id = g.workspace_id AND f.id = g.artifact_id
80
+ WHERE g.account_id = ${scope.accountId}::uuid AND g.workspace_id = ${scope.workspaceId}::uuid AND g.status = 'ready' AND f.status = 'ready'
81
+ ${source ? sql`AND g.session_id = ${source}::uuid` : sql``}`);
82
+ const publishedKinds = input.kinds.filter((kind) => kind === "file" || kind === "image");
83
+ if (publishedKinds.length)
84
+ branches.push(sql`
85
+ SELECT p.file_id::text AS id, p.kind, p.title, 'active'::text AS status,
86
+ p.published_at AS created_at, p.published_at AS updated_at, p.source_session_id, NULL::text AS version_id, 'sandbox_file'::text AS origin
87
+ FROM opengeni_private.list_sandbox_file_publications(${scope.accountId}::uuid, ${scope.workspaceId}::uuid,
88
+ ${JSON.stringify({ sourceSessionId: source, q: query.q, kinds: publishedKinds, sort: query.sort, snapshotAt: input.snapshotAt, after: input.after, limit: input.limit })}::jsonb) p`);
89
+ if (!branches.length) return [];
90
+ const sort =
91
+ query.sort === "title"
92
+ ? sql`lower(title)`
93
+ : query.sort === "newest"
94
+ ? sql`to_char(created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US')`
95
+ : sql`to_char(updated_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US')`;
96
+ const descending = query.sort !== "title";
97
+ const position = input.after;
98
+ const keyset = !position
99
+ ? sql`true`
100
+ : sql`(
101
+ sort_key COLLATE "C" ${descending ? sql`<` : sql`>`} ${position.key} COLLATE "C"
102
+ OR (sort_key = ${position.key} AND (kind COLLATE "C", id COLLATE "C") > (${position.kind}, ${position.id})))`;
103
+ return withRlsContext(
104
+ db,
105
+ scope,
106
+ async (tx) => {
107
+ const result = await tx.execute(sql`
108
+ WITH candidates AS (${sql.join(branches, sql` UNION ALL `)}),
109
+ ordered AS (SELECT *, ${sort} AS sort_key FROM candidates
110
+ WHERE status = ${query.status} AND created_at <= ${input.snapshotAt}::timestamptz
111
+ ${query.q ? sql`AND strpos(lower(title), lower(${query.q})) > 0` : sql``})
112
+ SELECT * FROM ordered WHERE ${keyset}
113
+ ORDER BY sort_key COLLATE "C" ${descending ? sql`DESC` : sql`ASC`}, kind COLLATE "C", id COLLATE "C"
114
+ LIMIT ${input.limit}`);
115
+ return (
116
+ Array.isArray(result) ? result : (result as { rows: ArtifactCatalogCandidate[] }).rows
117
+ ) as ArtifactCatalogCandidate[];
118
+ },
119
+ { isolationLevel: "repeatable read", accessMode: "read only" },
120
+ );
121
+ }
@@ -58,7 +58,7 @@ async function administered<T>(
58
58
  sql`select id from api_keys where account_id = ${actor.accountId}::uuid
59
59
  and 'api_key:' || id::text = ${actor.subjectId} and workspace_id is null and credential_kind = 'organization'
60
60
  and revoked_at is null and (expires_at is null or expires_at > clock_timestamp())
61
- and permissions ? 'account:admin' for share`,
61
+ and permissions ? 'workspace:admin' for share`,
62
62
  );
63
63
  if (!key) throw new HostMcpResolverError(403, "organization service administration required");
64
64
  await tx.execute(
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./artifact-catalog";
1
2
  import { grantWorkspaceAccess } from "./workspace-membership-access";
2
3
  export { grantWorkspaceAccess, listWorkspaceMembers } from "./workspace-membership-access";
3
4
  import { codexSelectionDiagnostics } from "./codex-selection-diagnostics";
@@ -2089,6 +2089,12 @@ BEGIN
2089
2089
  EXECUTE format('GRANT USAGE ON SCHEMA opengeni_private TO %I', ${literal(role)});
2090
2090
  EXECUTE format('REVOKE CREATE ON SCHEMA opengeni_private FROM %I', ${literal(role)});
2091
2091
  EXECUTE format('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA opengeni_private TO %I', ${literal(role)});
2092
+ IF to_regclass('opengeni_private.sandbox_file_publications') IS NOT NULL THEN
2093
+ EXECUTE format('REVOKE ALL ON TABLE opengeni_private.sandbox_file_publications FROM %I', ${literal(role)});
2094
+ REVOKE ALL ON TABLE opengeni_private.sandbox_file_publications FROM PUBLIC;
2095
+ REVOKE ALL ON FUNCTION opengeni_private.record_sandbox_file_publication(uuid,uuid,uuid,uuid) FROM PUBLIC;
2096
+ REVOKE ALL ON FUNCTION opengeni_private.list_sandbox_file_publications(uuid,uuid,jsonb) FROM PUBLIC;
2097
+ END IF;
2092
2098
  FOREACH routine_signature IN ARRAY ARRAY[
2093
2099
  'guard_mcp_operation_immutable()',
2094
2100
  'mcp_operation_command_scoped(jsonb,text,jsonb)',
@@ -48,6 +48,11 @@ const AUTOMATIC_SESSION_TITLE_FANOUT_MIGRATION_ROUTINE =
48
48
  const AUTOMATIC_SESSION_TITLE_POLICY_TRIGGER_ROUTINE =
49
49
  "enforce_automatic_session_title_policy_v1()";
50
50
  const AUTOMATIC_SESSION_TITLE_FANOUT_OUTBOX_TABLE = "automatic_session_title_fanout_outbox_v1";
51
+ export const SANDBOX_FILE_PUBLICATION_RUNTIME_ROUTINES = [
52
+ "record_sandbox_file_publication(uuid, uuid, uuid, uuid)",
53
+ "list_sandbox_file_publications(uuid, uuid, jsonb)",
54
+ ] as const;
55
+ const SANDBOX_FILE_PUBLICATIONS_TABLE = "sandbox_file_publications";
51
56
  const AUTOMATIC_SESSION_TITLE_QUARANTINE_FENCE_ROUTINE =
52
57
  "acquire_automatic_session_title_quarantine_fences_v1(integer)";
53
58
 
@@ -1630,6 +1635,7 @@ export type RuntimeRoutinePosture = {
1630
1635
  execute: boolean;
1631
1636
  publicExecute?: boolean;
1632
1637
  securityDefiner: boolean;
1638
+ configuration?: string[] | null;
1633
1639
  };
1634
1640
 
1635
1641
  export type RuntimeTargetRoutinePosture = RuntimeRoutinePosture & {
@@ -1952,6 +1958,7 @@ export async function inspectRuntimeDatabasePosture(
1952
1958
  ${DOCUMENT_MIGRATION_CAPABILITY_TABLE},
1953
1959
  ${SCOPED_COMPUTE_CAPABILITY_TABLE},
1954
1960
  ${CONNECTION_TENANCY_BACKFILL_CAPABILITY_TABLE},
1961
+ ${SANDBOX_FILE_PUBLICATIONS_TABLE},
1955
1962
  ${AUTOMATIC_SESSION_TITLE_FANOUT_OUTBOX_TABLE}
1956
1963
  )
1957
1964
  `),
@@ -2018,6 +2025,7 @@ export async function inspectRuntimeDatabasePosture(
2018
2025
  can_execute: boolean;
2019
2026
  public_execute: boolean;
2020
2027
  security_definer: boolean;
2028
+ configuration: string[] | null;
2021
2029
  }>(
2022
2030
  await tx.execute(sql`
2023
2031
  select
@@ -2029,7 +2037,8 @@ export async function inspectRuntimeDatabasePosture(
2029
2037
  from aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) acl
2030
2038
  where acl.grantee = 0 and acl.privilege_type = 'EXECUTE'
2031
2039
  ) as public_execute,
2032
- p.prosecdef as security_definer
2040
+ p.prosecdef as security_definer,
2041
+ p.proconfig as configuration
2033
2042
  from pg_proc p
2034
2043
  join pg_namespace n on n.oid = p.pronamespace
2035
2044
  where n.nspname = 'opengeni_private'
@@ -2042,6 +2051,7 @@ export async function inspectRuntimeDatabasePosture(
2042
2051
  execute: row.can_execute,
2043
2052
  publicExecute: row.public_execute,
2044
2053
  securityDefiner: row.security_definer,
2054
+ configuration: row.configuration,
2045
2055
  }));
2046
2056
 
2047
2057
  return {
@@ -3509,6 +3519,49 @@ export function evaluateRuntimeDatabasePosture(
3509
3519
  }
3510
3520
  }
3511
3521
 
3522
+ const publicationTables = posture.privateTables.filter(
3523
+ (table) => table.name === SANDBOX_FILE_PUBLICATIONS_TABLE,
3524
+ );
3525
+ if (publicationTables.length !== 1) {
3526
+ if (!options.protectedTables)
3527
+ violations.push("sandbox file publication private relation is missing or ambiguous");
3528
+ } else {
3529
+ const table = publicationTables[0]!;
3530
+ if (!table.rlsEnabled || !table.rlsForced || !table.rlsActive || (table.policyCount ?? 0) < 2) {
3531
+ violations.push("sandbox file publication relation lacks active FORCE-RLS file isolation");
3532
+ }
3533
+ if (
3534
+ table.select ||
3535
+ table.insert ||
3536
+ table.update ||
3537
+ table.delete ||
3538
+ table.owner === expectedRole
3539
+ ) {
3540
+ violations.push("runtime role has forbidden direct sandbox file publication authority");
3541
+ }
3542
+ const filesOwner = tableByName.get("files")?.owner;
3543
+ if (filesOwner && table.owner !== filesOwner)
3544
+ violations.push("sandbox file publication owner does not match file authority");
3545
+ for (const name of SANDBOX_FILE_PUBLICATION_RUNTIME_ROUTINES) {
3546
+ const routines = posture.privateRoutines.filter((routine) => routine.name === name);
3547
+ const quotedSchema = `"${targetSchema.replaceAll('"', '""')}"`;
3548
+ const searchPaths = new Set([
3549
+ `search_path=pg_catalog, ${quotedSchema}, pg_temp`,
3550
+ `search_path=pg_catalog, ${/^[a-z_][a-z0-9_]*$/.test(targetSchema) ? targetSchema : quotedSchema}, pg_temp`,
3551
+ ]);
3552
+ if (
3553
+ routines.length !== 1 ||
3554
+ !routines[0]!.execute ||
3555
+ routines[0]!.publicExecute ||
3556
+ !routines[0]!.securityDefiner ||
3557
+ routines[0]!.owner !== table.owner ||
3558
+ !routines[0]!.configuration?.some((configuration) => searchPaths.has(configuration))
3559
+ ) {
3560
+ violations.push(`sandbox file publication capability ${name} is missing or unsafe`);
3561
+ }
3562
+ }
3563
+ }
3564
+
3512
3565
  for (const routine of posture.privateRoutines) {
3513
3566
  if (routine.owner === expectedRole) {
3514
3567
  violations.push(`runtime role owns private routine ${routine.name}`);
package/src/schema.ts CHANGED
@@ -5512,6 +5512,36 @@ export const files = pgTable(
5512
5512
  }),
5513
5513
  );
5514
5514
 
5515
+ /** Immutable publication receipt; content and ownership remain in files. */
5516
+ export const sandboxFilePublications = opengeniPrivateSchema.table(
5517
+ "sandbox_file_publications",
5518
+ {
5519
+ accountId: uuid("account_id").notNull(),
5520
+ workspaceId: uuid("workspace_id").notNull(),
5521
+ fileId: uuid("file_id").notNull(),
5522
+ sourceSessionId: uuid("source_session_id"),
5523
+ publishedAt: timestamp("published_at", { withTimezone: true }).notNull().defaultNow(),
5524
+ },
5525
+ (table) => ({
5526
+ pk: primaryKey({
5527
+ name: "sandbox_file_publications_pk",
5528
+ columns: [table.accountId, table.workspaceId, table.fileId],
5529
+ }),
5530
+ file: foreignKey({
5531
+ name: "sandbox_file_publications_file_fk",
5532
+ columns: [table.accountId, table.workspaceId, table.fileId],
5533
+ foreignColumns: [files.accountId, files.workspaceId, files.id],
5534
+ }).onDelete("cascade"),
5535
+ // SQL owns the composite session FK with column-specific SET NULL.
5536
+ session: index("sandbox_file_publications_session_idx").on(
5537
+ table.workspaceId,
5538
+ table.sourceSessionId,
5539
+ table.publishedAt,
5540
+ table.fileId,
5541
+ ),
5542
+ }),
5543
+ );
5544
+
5515
5545
  export const fileUploads = pgTable(
5516
5546
  "file_uploads",
5517
5547
  {