@opengeni/db 4.3.2 → 4.3.3-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.
- package/dist/canonical-human-identities.js +3 -3
- package/dist/{chunk-UVBPOGA7.js → chunk-232VC2LW.js} +2 -2
- package/dist/{chunk-DVSLOUEI.js → chunk-2ZMTQVTR.js} +2 -2
- package/dist/{chunk-AI4NC6XW.js → chunk-6TKL3GQO.js} +47 -1
- package/dist/chunk-6TKL3GQO.js.map +1 -0
- package/dist/{chunk-F4BIHJ7J.js → chunk-EYF6MH6C.js} +4 -4
- package/dist/{chunk-I4ITNMX2.js → chunk-GVZJ7XSR.js} +16 -4
- package/dist/{chunk-I4ITNMX2.js.map → chunk-GVZJ7XSR.js.map} +1 -1
- package/dist/{chunk-XBXANJQS.js → chunk-M7PSMVFF.js} +2 -2
- package/dist/{chunk-PFBUHOI5.js → chunk-QLA3UQNX.js} +3 -3
- package/dist/{chunk-YVYBAWBU.js → chunk-THJGMEK5.js} +53 -1
- package/dist/chunk-THJGMEK5.js.map +1 -0
- package/dist/{chunk-UUPY6IGP.js → chunk-UK57QA2H.js} +2 -2
- package/dist/connection-token-resolver.d.ts +2 -0
- package/dist/database.d.ts +7 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/host-mcp-bindings.d.ts +3 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +85 -26
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/mcp-operations.d.ts +95 -0
- package/dist/mcp-operations.js +95 -0
- package/dist/mcp-operations.js.map +1 -0
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +2 -2
- package/dist/runtime-posture.d.ts +3 -3
- package/dist/schema.d.ts +517 -0
- package/dist/schema.js +3 -1
- package/dist/session-background-commands.js +4 -4
- package/dist/session-command-output.js +4 -4
- package/dist/session-event-slices.js +2 -2
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/workspace-tool-defaults.js +4 -4
- package/drizzle/0459_mcp_operations.sql +396 -0
- package/package.json +10 -6
- package/src/connection-token-resolver.ts +2 -0
- package/src/database.ts +23 -2
- package/src/host-mcp-bindings.ts +14 -1
- package/src/index.ts +84 -4
- package/src/mcp-operations.ts +189 -0
- package/src/provision-roles.ts +6 -0
- package/src/runtime-posture.ts +50 -0
- package/src/schema.ts +46 -0
- package/dist/chunk-AI4NC6XW.js.map +0 -1
- package/dist/chunk-YVYBAWBU.js.map +0 -1
- /package/dist/{chunk-UVBPOGA7.js.map → chunk-232VC2LW.js.map} +0 -0
- /package/dist/{chunk-DVSLOUEI.js.map → chunk-2ZMTQVTR.js.map} +0 -0
- /package/dist/{chunk-F4BIHJ7J.js.map → chunk-EYF6MH6C.js.map} +0 -0
- /package/dist/{chunk-XBXANJQS.js.map → chunk-M7PSMVFF.js.map} +0 -0
- /package/dist/{chunk-PFBUHOI5.js.map → chunk-QLA3UQNX.js.map} +0 -0
- /package/dist/{chunk-UUPY6IGP.js.map → chunk-UK57QA2H.js.map} +0 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Explicit operation_read only. No mutation lease, retry queue, maintenance
|
|
3
|
+
-- scanner, credential storage, event append, or autonomous wake is introduced.
|
|
4
|
+
CREATE TABLE mcp_operations (
|
|
5
|
+
operation_id uuid PRIMARY KEY,
|
|
6
|
+
account_id uuid NOT NULL,
|
|
7
|
+
workspace_id uuid NOT NULL,
|
|
8
|
+
session_id uuid NOT NULL,
|
|
9
|
+
source_turn_id uuid NOT NULL,
|
|
10
|
+
source_attempt_id uuid NOT NULL,
|
|
11
|
+
source_execution_generation integer NOT NULL CHECK (source_execution_generation > 0),
|
|
12
|
+
source_call_id text CHECK (octet_length(source_call_id) BETWEEN 1 AND 1024),
|
|
13
|
+
principal_kind text NOT NULL CHECK (principal_kind IN ('subject', 'service')),
|
|
14
|
+
principal_id text NOT NULL CHECK (octet_length(principal_id) BETWEEN 1 AND 1024),
|
|
15
|
+
principal_membership_id uuid,
|
|
16
|
+
principal_membership_revision bigint,
|
|
17
|
+
server_id text NOT NULL CHECK (octet_length(server_id) BETWEEN 1 AND 512),
|
|
18
|
+
original_tool text NOT NULL CHECK (octet_length(original_tool) BETWEEN 1 AND 512),
|
|
19
|
+
observer_tool text NOT NULL CHECK (octet_length(observer_tool) BETWEEN 1 AND 512),
|
|
20
|
+
argument_digest text NOT NULL CHECK (argument_digest ~ '^[a-f0-9]{64}$'),
|
|
21
|
+
destination_digest text NOT NULL CHECK (destination_digest ~ '^[a-f0-9]{64}$'),
|
|
22
|
+
authority_digest text NOT NULL CHECK (authority_digest ~ '^[a-f0-9]{64}$'),
|
|
23
|
+
original_outcome text NOT NULL DEFAULT 'captured' CHECK (original_outcome IN ('captured', 'completed', 'outcome_unknown')),
|
|
24
|
+
original_result jsonb,
|
|
25
|
+
original_result_codec_version integer CHECK (original_result_codec_version = 1),
|
|
26
|
+
observation_result jsonb,
|
|
27
|
+
observation_result_codec_version integer CHECK (observation_result_codec_version = 1),
|
|
28
|
+
receipt_revision text CHECK (octet_length(receipt_revision) BETWEEN 1 AND 1024),
|
|
29
|
+
receipt_digest text CHECK (receipt_digest ~ '^[a-f0-9]{64}$'),
|
|
30
|
+
observation_claim_id uuid,
|
|
31
|
+
observation_claim_attempt_id uuid,
|
|
32
|
+
observation_claim_expires_at timestamptz,
|
|
33
|
+
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
34
|
+
observed_at timestamptz,
|
|
35
|
+
CONSTRAINT mcp_operations_observer_check CHECK (original_tool <> observer_tool),
|
|
36
|
+
CONSTRAINT mcp_operations_source_fk FOREIGN KEY
|
|
37
|
+
(account_id, workspace_id, session_id, source_turn_id, source_attempt_id)
|
|
38
|
+
REFERENCES session_turn_attempts(account_id, workspace_id, session_id, turn_id, id) ON DELETE CASCADE,
|
|
39
|
+
CONSTRAINT mcp_operations_original_check CHECK (
|
|
40
|
+
(original_outcome = 'completed') = (original_result IS NOT NULL)
|
|
41
|
+
AND (original_result IS NULL OR (jsonb_typeof(original_result) = 'object' AND octet_length(original_result::text) <= 16777216))),
|
|
42
|
+
CONSTRAINT mcp_operations_receipt_check CHECK (
|
|
43
|
+
(observation_result IS NULL AND receipt_revision IS NULL AND receipt_digest IS NULL AND observed_at IS NULL)
|
|
44
|
+
OR (jsonb_typeof(observation_result) = 'object' AND receipt_revision IS NOT NULL AND receipt_digest IS NOT NULL
|
|
45
|
+
AND observed_at IS NOT NULL AND octet_length(observation_result::text) <= 16777216)),
|
|
46
|
+
CONSTRAINT mcp_operations_claim_check CHECK (
|
|
47
|
+
(observation_claim_id IS NULL AND observation_claim_attempt_id IS NULL AND observation_claim_expires_at IS NULL)
|
|
48
|
+
OR (observation_claim_id IS NOT NULL AND observation_claim_attempt_id IS NOT NULL AND observation_claim_expires_at IS NOT NULL))
|
|
49
|
+
);
|
|
50
|
+
CREATE INDEX mcp_operations_source_idx ON mcp_operations(workspace_id, session_id, source_turn_id, source_call_id);
|
|
51
|
+
ALTER TABLE mcp_operations ENABLE ROW LEVEL SECURITY;
|
|
52
|
+
ALTER TABLE mcp_operations FORCE ROW LEVEL SECURITY;
|
|
53
|
+
CREATE POLICY workspace_isolation ON mcp_operations
|
|
54
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
55
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
56
|
+
|
|
57
|
+
CREATE FUNCTION opengeni_private.guard_mcp_operation_immutable()
|
|
58
|
+
RETURNS trigger LANGUAGE plpgsql SET search_path=pg_catalog AS $$
|
|
59
|
+
BEGIN
|
|
60
|
+
IF (to_jsonb(NEW) - ARRAY['original_outcome','original_result','original_result_codec_version',
|
|
61
|
+
'observation_result','observation_result_codec_version','receipt_revision','receipt_digest','observed_at',
|
|
62
|
+
'observation_claim_id','observation_claim_attempt_id','observation_claim_expires_at'])
|
|
63
|
+
IS DISTINCT FROM (to_jsonb(OLD) - ARRAY['original_outcome','original_result','original_result_codec_version',
|
|
64
|
+
'observation_result','observation_result_codec_version','receipt_revision','receipt_digest','observed_at',
|
|
65
|
+
'observation_claim_id','observation_claim_attempt_id','observation_claim_expires_at'])
|
|
66
|
+
OR (OLD.original_outcome<>'captured' AND
|
|
67
|
+
(NEW.original_outcome,NEW.original_result,NEW.original_result_codec_version)
|
|
68
|
+
IS DISTINCT FROM (OLD.original_outcome,OLD.original_result,OLD.original_result_codec_version))
|
|
69
|
+
OR (OLD.receipt_digest IS NOT NULL AND
|
|
70
|
+
(NEW.observation_result,NEW.observation_result_codec_version,NEW.receipt_revision,NEW.receipt_digest,NEW.observed_at,
|
|
71
|
+
NEW.observation_claim_id,NEW.observation_claim_attempt_id,NEW.observation_claim_expires_at)
|
|
72
|
+
IS DISTINCT FROM (OLD.observation_result,OLD.observation_result_codec_version,OLD.receipt_revision,OLD.receipt_digest,OLD.observed_at,
|
|
73
|
+
OLD.observation_claim_id,OLD.observation_claim_attempt_id,OLD.observation_claim_expires_at))
|
|
74
|
+
THEN RAISE EXCEPTION 'MCP operation identity and settled outcomes are immutable'; END IF;
|
|
75
|
+
RETURN NEW;
|
|
76
|
+
END $$;
|
|
77
|
+
REVOKE ALL ON FUNCTION opengeni_private.guard_mcp_operation_immutable() FROM PUBLIC;
|
|
78
|
+
CREATE TRIGGER mcp_operation_immutable BEFORE UPDATE ON mcp_operations FOR EACH ROW
|
|
79
|
+
EXECUTE FUNCTION opengeni_private.guard_mcp_operation_immutable();
|
|
80
|
+
|
|
81
|
+
-- The only runtime entry point is an EXECUTE-only, pinned-search-path writer.
|
|
82
|
+
-- Scope is checked even for a superuser-owned definer; no subject GUC is set.
|
|
83
|
+
DO $install$
|
|
84
|
+
DECLARE target_schema text := current_schema(); role_name text;
|
|
85
|
+
BEGIN
|
|
86
|
+
EXECUTE format($definition$
|
|
87
|
+
CREATE FUNCTION opengeni_private.mcp_operation_command_scoped(p_scope jsonb, p_action text, p_payload jsonb)
|
|
88
|
+
RETURNS jsonb LANGUAGE plpgsql
|
|
89
|
+
SET search_path = pg_catalog, %1$I, pg_temp AS $body$
|
|
90
|
+
DECLARE
|
|
91
|
+
v_account uuid := (p_scope->>'accountId')::uuid;
|
|
92
|
+
v_workspace uuid := (p_scope->>'workspaceId')::uuid;
|
|
93
|
+
v_session uuid := (p_scope->>'sessionId')::uuid;
|
|
94
|
+
v_turn uuid := (p_scope->>'turnId')::uuid;
|
|
95
|
+
v_attempt uuid := (p_scope->>'attemptId')::uuid;
|
|
96
|
+
v_generation integer := (p_scope->>'executionGeneration')::integer;
|
|
97
|
+
v_principal_id text; v_principal_kind text;
|
|
98
|
+
v_op uuid := (p_payload->>'operationId')::uuid;
|
|
99
|
+
v_row mcp_operations%%ROWTYPE;
|
|
100
|
+
v_ids uuid[];
|
|
101
|
+
v_digest text;
|
|
102
|
+
v_claim uuid;
|
|
103
|
+
v_operation jsonb;
|
|
104
|
+
v_membership jsonb; v_external_membership jsonb;
|
|
105
|
+
v_principal_membership uuid; v_principal_revision bigint;
|
|
106
|
+
v_link_snapshot jsonb; v_link external_identity_links%%ROWTYPE;
|
|
107
|
+
v_run uuid;
|
|
108
|
+
v_control workspace_inference_controls%%ROWTYPE;
|
|
109
|
+
v_control_active boolean;
|
|
110
|
+
BEGIN
|
|
111
|
+
IF v_account IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true),'')::uuid
|
|
112
|
+
OR v_workspace IS DISTINCT FROM nullif(current_setting('opengeni.workspace_id', true),'')::uuid
|
|
113
|
+
OR v_generation IS NULL OR v_generation < 1
|
|
114
|
+
OR jsonb_typeof(p_scope) IS DISTINCT FROM 'object'
|
|
115
|
+
OR p_scope - ARRAY['accountId','workspaceId','sessionId','turnId','attemptId','executionGeneration'] <> '{}'::jsonb
|
|
116
|
+
OR jsonb_typeof(p_payload) IS DISTINCT FROM 'object'
|
|
117
|
+
THEN RAISE EXCEPTION 'MCP operation scope mismatch' USING ERRCODE='42501'; END IF;
|
|
118
|
+
|
|
119
|
+
IF p_action IS NULL OR p_action NOT IN ('capture','read','settle_original','claim_read','release_read','settle_observation')
|
|
120
|
+
THEN RAISE EXCEPTION 'unknown MCP ledger action'; END IF;
|
|
121
|
+
|
|
122
|
+
-- Membership lifecycle precedes tenancy/control/session locks. Scheduled
|
|
123
|
+
-- run validation follows the session lock, matching canonical claim/resume.
|
|
124
|
+
-- p_scope is authenticated by the calling worker; these checks validate
|
|
125
|
+
-- canonical authority, never authenticate arbitrary request JSON.
|
|
126
|
+
PERFORM pg_advisory_xact_lock(hashtextextended('organization-membership:' || v_account::text,0));
|
|
127
|
+
SELECT coalesce(nullif(t.initiating_human_subject_id,''),t.initiator_subject_id),
|
|
128
|
+
CASE WHEN nullif(t.initiating_human_subject_id,'') IS NOT NULL THEN 'subject' ELSE t.initiator_kind END
|
|
129
|
+
INTO v_principal_id,v_principal_kind
|
|
130
|
+
FROM session_turns t WHERE t.id=v_turn AND t.account_id=v_account AND t.workspace_id=v_workspace AND t.session_id=v_session;
|
|
131
|
+
IF v_principal_id IS NULL OR v_principal_id IN ('','unattributed-legacy')
|
|
132
|
+
THEN RAISE EXCEPTION 'MCP operation canonical principal missing' USING ERRCODE='42501'; END IF;
|
|
133
|
+
-- Only the immutable turn chooses the subject. The outer definer restores
|
|
134
|
+
-- these settings on success/error, so this cannot become a subject oracle.
|
|
135
|
+
PERFORM set_config('opengeni.subject_id',v_principal_id,true);
|
|
136
|
+
PERFORM set_config('opengeni.initiating_human_subject_id',CASE WHEN v_principal_kind='subject' THEN v_principal_id ELSE '' END,true);
|
|
137
|
+
IF v_principal_kind='subject' THEN
|
|
138
|
+
IF v_principal_id LIKE 'user:%%' OR v_principal_id LIKE 'external_user:%%' THEN
|
|
139
|
+
SELECT value INTO v_membership FROM jsonb_array_elements(list_self_organization_memberships(v_principal_id))
|
|
140
|
+
WHERE value->>'organizationId'=v_account::text;
|
|
141
|
+
-- An absent organization membership preserves an explicit legacy
|
|
142
|
+
-- workspace grant; a present inactive membership revokes it.
|
|
143
|
+
IF v_membership IS NOT NULL AND v_membership->>'status'<>'active' THEN
|
|
144
|
+
RAISE EXCEPTION 'MCP operation principal membership revoked' USING ERRCODE='42501'; END IF;
|
|
145
|
+
v_principal_membership := (v_membership->>'id')::uuid;
|
|
146
|
+
v_principal_revision := (v_membership->>'authorizationRevision')::bigint;
|
|
147
|
+
END IF;
|
|
148
|
+
-- Same stated-workspace rule as subjectHasLiveWorkspaceAuthorityInScope:
|
|
149
|
+
-- explicit membership OR exact active Personal pointer, never a default.
|
|
150
|
+
IF NOT EXISTS(SELECT 1 FROM workspace_memberships WHERE account_id=v_account
|
|
151
|
+
AND workspace_id=v_workspace AND subject_id=v_principal_id)
|
|
152
|
+
AND (v_membership IS NULL OR v_membership->>'personalWorkspaceId' IS DISTINCT FROM v_workspace::text)
|
|
153
|
+
THEN RAISE EXCEPTION 'MCP operation workspace membership revoked' USING ERRCODE='42501'; END IF;
|
|
154
|
+
END IF;
|
|
155
|
+
|
|
156
|
+
SELECT canonical_snapshot INTO v_link_snapshot FROM external_link_turn_authorities
|
|
157
|
+
WHERE account_id=v_account AND workspace_id=v_workspace AND session_id=v_session AND turn_id=v_turn;
|
|
158
|
+
IF v_link_snapshot IS NOT NULL THEN
|
|
159
|
+
SELECT * INTO v_link FROM external_identity_links WHERE id=(v_link_snapshot#>>'{actor,linkId}')::uuid
|
|
160
|
+
AND account_id=v_account FOR SHARE;
|
|
161
|
+
IF NOT FOUND OR v_link.status<>'active' OR (v_link.expires_at IS NOT NULL AND v_link.expires_at<=clock_timestamp())
|
|
162
|
+
OR v_link.revision IS DISTINCT FROM (v_link_snapshot#>>'{actor,linkRevision}')::bigint
|
|
163
|
+
OR v_link.native_subject_id IS DISTINCT FROM v_principal_id
|
|
164
|
+
OR v_link.native_membership_id IS DISTINCT FROM v_principal_membership
|
|
165
|
+
OR v_link.native_authorization_revision IS DISTINCT FROM v_principal_revision
|
|
166
|
+
OR v_link.external_identity_id::text IS DISTINCT FROM v_link_snapshot#>>'{actor,externalIdentityId}'
|
|
167
|
+
OR v_link.external_subject_id IS DISTINCT FROM v_link_snapshot#>>'{actor,externalSubjectId}'
|
|
168
|
+
OR EXISTS(SELECT 1 FROM jsonb_array_elements_text(v_link_snapshot->'permissions') permission
|
|
169
|
+
WHERE NOT (v_link.permissions ? permission) AND NOT(permission<>'secrets:read' AND v_link.permissions ? 'workspace:admin'))
|
|
170
|
+
THEN RAISE EXCEPTION 'MCP operation linked authority revoked' USING ERRCODE='42501'; END IF;
|
|
171
|
+
PERFORM set_config('opengeni.subject_id',v_link.external_subject_id,true);
|
|
172
|
+
SELECT value INTO v_external_membership FROM jsonb_array_elements(list_self_organization_memberships(v_link.external_subject_id))
|
|
173
|
+
WHERE value->>'organizationId'=v_account::text;
|
|
174
|
+
PERFORM set_config('opengeni.subject_id',v_principal_id,true);
|
|
175
|
+
IF v_external_membership IS NULL OR v_external_membership->>'status'<>'active'
|
|
176
|
+
OR (v_external_membership->>'authorizationRevision')::bigint IS DISTINCT FROM v_link.external_authorization_revision
|
|
177
|
+
OR v_link.external_authorization_revision IS DISTINCT FROM (v_link_snapshot#>>'{actor,externalAuthorizationRevision}')::bigint
|
|
178
|
+
THEN RAISE EXCEPTION 'MCP operation external membership revoked' USING ERRCODE='42501'; END IF;
|
|
179
|
+
END IF;
|
|
180
|
+
|
|
181
|
+
PERFORM pg_advisory_xact_lock_shared(hashtextextended('session-tenancy:' || v_workspace::text,0));
|
|
182
|
+
PERFORM pg_advisory_xact_lock_shared(hashtextextended('workspace-control:' || v_workspace::text,0));
|
|
183
|
+
SELECT * INTO v_control FROM workspace_inference_controls WHERE workspace_id=v_workspace AND account_id=v_account FOR SHARE;
|
|
184
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'MCP operation workspace control missing' USING ERRCODE='42501'; END IF;
|
|
185
|
+
-- Canonical effective-control algebra from session-control.ts: only a
|
|
186
|
+
-- strictly newer DESCENDANT override defeats a session pause; any newer
|
|
187
|
+
-- path override defeats workspace pause. Same 10,000 ancestry bound.
|
|
188
|
+
WITH RECURSIVE ancestry AS (
|
|
189
|
+
SELECT id,parent_session_id,direct_control_state,direct_pause_revision,subtree_run_override_revision,0 AS depth
|
|
190
|
+
FROM sessions WHERE id=v_session AND workspace_id=v_workspace AND account_id=v_account
|
|
191
|
+
UNION ALL SELECT p.id,p.parent_session_id,p.direct_control_state,p.direct_pause_revision,p.subtree_run_override_revision,c.depth+1
|
|
192
|
+
FROM ancestry c JOIN sessions p ON p.id=c.parent_session_id AND p.workspace_id=v_workspace AND p.account_id=v_account
|
|
193
|
+
WHERE c.depth<10000
|
|
194
|
+
), path AS (SELECT *,max(subtree_run_override_revision) OVER(ORDER BY depth ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS descendant_override FROM ancestry)
|
|
195
|
+
SELECT EXISTS(SELECT 1 FROM path WHERE parent_session_id IS NULL)
|
|
196
|
+
AND NOT EXISTS(SELECT 1 FROM path WHERE depth>=10000 AND parent_session_id IS NOT NULL)
|
|
197
|
+
AND NOT EXISTS(SELECT 1 FROM path WHERE direct_control_state='paused' AND direct_pause_revision IS NOT NULL
|
|
198
|
+
AND (descendant_override IS NULL OR descendant_override<=direct_pause_revision))
|
|
199
|
+
AND (v_control.workspace_state<>'paused' OR (v_control.workspace_pause_revision IS NOT NULL
|
|
200
|
+
AND EXISTS(SELECT 1 FROM path WHERE subtree_run_override_revision>v_control.workspace_pause_revision)))
|
|
201
|
+
INTO v_control_active;
|
|
202
|
+
IF NOT v_control_active THEN RAISE EXCEPTION 'MCP operation effective control paused or unavailable' USING ERRCODE='42501'; END IF;
|
|
203
|
+
PERFORM 1 FROM workspaces WHERE id=v_workspace AND account_id=v_account FOR KEY SHARE;
|
|
204
|
+
PERFORM 1 FROM sessions WHERE id=v_session AND workspace_id=v_workspace AND account_id=v_account FOR SHARE;
|
|
205
|
+
-- Canonical claim/resume owns control/session before scheduled run rows.
|
|
206
|
+
-- Taking a run first can deadlock a stale read against replacement claim.
|
|
207
|
+
-- Include inherited host-MCP scheduled origin, not only direct run turns.
|
|
208
|
+
FOR v_run IN SELECT t.scheduled_task_run_id FROM session_turns t WHERE t.id=v_turn
|
|
209
|
+
AND t.account_id=v_account AND t.workspace_id=v_workspace AND t.scheduled_task_run_id IS NOT NULL
|
|
210
|
+
UNION SELECT (h.canonical_snapshot#>>'{scheduledOrigin,runId}')::uuid FROM host_mcp_turn_authorities h
|
|
211
|
+
WHERE h.account_id=v_account AND h.workspace_id=v_workspace AND h.turn_id=v_turn
|
|
212
|
+
AND h.canonical_snapshot#>>'{scheduledOrigin,runId}' IS NOT NULL
|
|
213
|
+
LOOP
|
|
214
|
+
IF validate_scheduled_agent_run_live_authority(v_account,v_workspace,v_run) IS NOT NULL
|
|
215
|
+
THEN RAISE EXCEPTION 'MCP operation scheduled authority revoked' USING ERRCODE='42501'; END IF;
|
|
216
|
+
END LOOP;
|
|
217
|
+
PERFORM 1 FROM session_turns WHERE id=v_turn AND workspace_id=v_workspace AND account_id=v_account FOR SHARE;
|
|
218
|
+
SELECT coalesce(nullif(t.initiating_human_subject_id,''),t.initiator_subject_id),
|
|
219
|
+
CASE WHEN nullif(t.initiating_human_subject_id,'') IS NOT NULL THEN 'subject' ELSE t.initiator_kind END
|
|
220
|
+
INTO v_principal_id,v_principal_kind
|
|
221
|
+
FROM sessions s JOIN session_turns t ON t.id=s.active_turn_id AND t.session_id=s.id
|
|
222
|
+
AND t.workspace_id=s.workspace_id AND t.account_id=s.account_id
|
|
223
|
+
JOIN session_turn_attempts a ON a.id=t.active_attempt_id AND a.turn_id=t.id
|
|
224
|
+
AND a.session_id=s.id AND a.account_id=s.account_id AND a.workspace_id=s.workspace_id
|
|
225
|
+
WHERE s.account_id=v_account AND s.workspace_id=v_workspace AND s.id=v_session
|
|
226
|
+
AND t.id=v_turn AND t.status='running'
|
|
227
|
+
AND a.id=v_attempt AND a.execution_generation=v_generation AND t.execution_generation=v_generation
|
|
228
|
+
AND a.state IN ('claimed','running') AND a.closed_at IS NULL AND a.quiesced_at IS NULL
|
|
229
|
+
AND a.authority_epoch=s.authority_epoch AND a.authority_visibility=s.visibility
|
|
230
|
+
AND a.authority_owner_organization_membership_id IS NOT DISTINCT FROM s.owner_organization_membership_id
|
|
231
|
+
AND NOT EXISTS (SELECT 1 FROM session_attempt_interruptions i
|
|
232
|
+
WHERE i.account_id=v_account AND i.workspace_id=v_workspace AND i.session_id=v_session
|
|
233
|
+
AND i.attempt_id=a.id AND i.state IN ('pending','delivered','acknowledged'))
|
|
234
|
+
FOR UPDATE OF a;
|
|
235
|
+
IF v_principal_id IS NULL OR v_principal_id IN ('','unattributed-legacy') OR v_principal_kind NOT IN ('subject','service')
|
|
236
|
+
THEN RAISE EXCEPTION 'MCP operation requires exact live canonical attempt' USING ERRCODE='42501'; END IF;
|
|
237
|
+
|
|
238
|
+
IF p_action='capture' THEN
|
|
239
|
+
IF v_op IS NULL OR p_payload - ARRAY['operationId','sourceCallId','serverId','originalTool','observerTool',
|
|
240
|
+
'argumentDigest','destinationDigest','authorityDigest'] <> '{}'::jsonb
|
|
241
|
+
THEN RAISE EXCEPTION 'invalid MCP capture metadata'; END IF;
|
|
242
|
+
PERFORM pg_advisory_xact_lock(hashtextextended('mcp-operation:' || v_op::text,0));
|
|
243
|
+
SELECT * INTO v_row FROM mcp_operations WHERE operation_id=v_op FOR UPDATE;
|
|
244
|
+
IF FOUND THEN
|
|
245
|
+
IF (v_row.account_id,v_row.workspace_id,v_row.session_id,v_row.source_turn_id,v_row.source_attempt_id,
|
|
246
|
+
v_row.source_execution_generation,v_row.principal_kind,v_row.principal_id,v_row.principal_membership_id,v_row.principal_membership_revision,v_row.source_call_id,
|
|
247
|
+
v_row.server_id,v_row.original_tool,v_row.observer_tool,v_row.argument_digest,v_row.destination_digest,v_row.authority_digest)
|
|
248
|
+
IS DISTINCT FROM (v_account,v_workspace,v_session,v_turn,v_attempt,v_generation,v_principal_kind,v_principal_id,v_principal_membership,v_principal_revision,
|
|
249
|
+
p_payload->>'sourceCallId',p_payload->>'serverId',p_payload->>'originalTool',p_payload->>'observerTool',
|
|
250
|
+
p_payload->>'argumentDigest',p_payload->>'destinationDigest',p_payload->>'authorityDigest')
|
|
251
|
+
THEN RAISE EXCEPTION 'MCP operation immutable identity conflict' USING ERRCODE='23505'; END IF;
|
|
252
|
+
RETURN '"existing"'::jsonb;
|
|
253
|
+
END IF;
|
|
254
|
+
INSERT INTO mcp_operations(operation_id,account_id,workspace_id,session_id,source_turn_id,source_attempt_id,
|
|
255
|
+
source_execution_generation,principal_kind,principal_id,principal_membership_id,principal_membership_revision,source_call_id,server_id,original_tool,observer_tool,
|
|
256
|
+
argument_digest,destination_digest,authority_digest)
|
|
257
|
+
VALUES(v_op,v_account,v_workspace,v_session,v_turn,v_attempt,v_generation,v_principal_kind,v_principal_id,v_principal_membership,v_principal_revision,
|
|
258
|
+
p_payload->>'sourceCallId',p_payload->>'serverId',p_payload->>'originalTool',p_payload->>'observerTool',
|
|
259
|
+
p_payload->>'argumentDigest',p_payload->>'destinationDigest',p_payload->>'authorityDigest');
|
|
260
|
+
RETURN '"created"'::jsonb;
|
|
261
|
+
END IF;
|
|
262
|
+
|
|
263
|
+
IF p_action='read' AND v_op IS NULL THEN
|
|
264
|
+
IF p_payload - ARRAY['sourceTurnId','sourceCallId'] <> '{}'::jsonb
|
|
265
|
+
OR p_payload->>'sourceTurnId' IS NULL OR coalesce(p_payload->>'sourceCallId','')=''
|
|
266
|
+
THEN RAISE EXCEPTION 'invalid exact MCP operation selector'; END IF;
|
|
267
|
+
SELECT array_agg(operation_id) INTO v_ids FROM (
|
|
268
|
+
SELECT operation_id FROM mcp_operations WHERE account_id=v_account AND workspace_id=v_workspace
|
|
269
|
+
AND session_id=v_session AND principal_kind=v_principal_kind AND principal_id=v_principal_id
|
|
270
|
+
AND source_turn_id=(p_payload->>'sourceTurnId')::uuid AND source_call_id=p_payload->>'sourceCallId'
|
|
271
|
+
LIMIT 2) candidates;
|
|
272
|
+
IF cardinality(v_ids)>1 THEN RETURN jsonb_build_object('status','ambiguous'); END IF;
|
|
273
|
+
v_op := v_ids[1];
|
|
274
|
+
ELSIF p_action='read' AND p_payload - ARRAY['operationId'] <> '{}'::jsonb THEN
|
|
275
|
+
RAISE EXCEPTION 'invalid exact MCP operation selector';
|
|
276
|
+
END IF;
|
|
277
|
+
SELECT * INTO v_row FROM mcp_operations WHERE operation_id=v_op AND account_id=v_account
|
|
278
|
+
AND workspace_id=v_workspace AND session_id=v_session
|
|
279
|
+
AND principal_kind=v_principal_kind AND principal_id=v_principal_id FOR UPDATE;
|
|
280
|
+
IF NOT FOUND THEN RETURN jsonb_build_object('status','not_found'); END IF;
|
|
281
|
+
IF v_row.principal_membership_id IS DISTINCT FROM v_principal_membership
|
|
282
|
+
OR v_row.principal_membership_revision IS DISTINCT FROM v_principal_revision
|
|
283
|
+
THEN RAISE EXCEPTION 'MCP operation original membership authority changed' USING ERRCODE='42501'; END IF;
|
|
284
|
+
IF p_action='settle_original' THEN
|
|
285
|
+
IF v_row.source_turn_id<>v_turn OR v_row.source_attempt_id<>v_attempt
|
|
286
|
+
OR v_row.source_execution_generation<>v_generation
|
|
287
|
+
THEN RAISE EXCEPTION 'original MCP settlement requires source attempt' USING ERRCODE='42501'; END IF;
|
|
288
|
+
IF p_payload->>'outcome' NOT IN ('completed','outcome_unknown')
|
|
289
|
+
OR p_payload->>'outcome' IS NULL
|
|
290
|
+
OR (p_payload->>'outcome'='completed' AND jsonb_typeof(p_payload->'result') IS DISTINCT FROM 'object')
|
|
291
|
+
OR (p_payload->>'outcome'='outcome_unknown' AND p_payload ? 'result')
|
|
292
|
+
THEN RAISE EXCEPTION 'invalid original MCP outcome'; END IF;
|
|
293
|
+
IF v_row.original_outcome<>'captured' THEN
|
|
294
|
+
IF v_row.original_outcome IS DISTINCT FROM p_payload->>'outcome'
|
|
295
|
+
OR v_row.original_result IS DISTINCT FROM p_payload->'result'
|
|
296
|
+
OR v_row.original_result_codec_version IS DISTINCT FROM (p_payload->>'resultCodecVersion')::integer
|
|
297
|
+
THEN RAISE EXCEPTION 'original MCP outcome is immutable'; END IF;
|
|
298
|
+
ELSE
|
|
299
|
+
UPDATE mcp_operations SET original_outcome=p_payload->>'outcome', original_result=p_payload->'result',
|
|
300
|
+
original_result_codec_version=(p_payload->>'resultCodecVersion')::integer
|
|
301
|
+
WHERE operation_id=v_op;
|
|
302
|
+
END IF;
|
|
303
|
+
RETURN jsonb_build_object('status','settled');
|
|
304
|
+
ELSIF p_action='claim_read' THEN
|
|
305
|
+
IF v_row.receipt_digest IS NOT NULL OR v_row.original_outcome='completed' THEN RETURN jsonb_build_object('status','terminal'); END IF;
|
|
306
|
+
IF v_row.observation_claim_expires_at>clock_timestamp() THEN RETURN jsonb_build_object('status','busy'); END IF;
|
|
307
|
+
v_claim := gen_random_uuid();
|
|
308
|
+
UPDATE mcp_operations SET observation_claim_id=v_claim,observation_claim_attempt_id=v_attempt,
|
|
309
|
+
observation_claim_expires_at=clock_timestamp()+interval '30 seconds' WHERE operation_id=v_op;
|
|
310
|
+
RETURN jsonb_build_object('status','claimed','claimId',v_claim);
|
|
311
|
+
ELSIF p_action='release_read' THEN
|
|
312
|
+
IF v_row.receipt_digest IS NOT NULL THEN RETURN jsonb_build_object('status','terminal'); END IF;
|
|
313
|
+
IF v_row.observation_claim_id IS DISTINCT FROM (p_payload->>'claimId')::uuid
|
|
314
|
+
OR v_row.observation_claim_attempt_id IS DISTINCT FROM v_attempt
|
|
315
|
+
THEN RETURN jsonb_build_object('status','stale_claim'); END IF;
|
|
316
|
+
UPDATE mcp_operations SET observation_claim_id=NULL,observation_claim_attempt_id=NULL,observation_claim_expires_at=NULL WHERE operation_id=v_op;
|
|
317
|
+
RETURN jsonb_build_object('status','released');
|
|
318
|
+
ELSIF p_action='settle_observation' THEN
|
|
319
|
+
IF v_row.original_outcome='completed' THEN RETURN jsonb_build_object('status','original_completed'); END IF;
|
|
320
|
+
IF v_row.observation_claim_id IS DISTINCT FROM (p_payload->>'claimId')::uuid
|
|
321
|
+
OR v_row.observation_claim_attempt_id IS DISTINCT FROM v_attempt
|
|
322
|
+
THEN RETURN jsonb_build_object('status','stale_claim'); END IF;
|
|
323
|
+
IF jsonb_typeof(p_payload->'result') IS DISTINCT FROM 'object'
|
|
324
|
+
OR octet_length((p_payload->'result')::text)>16777216
|
|
325
|
+
OR coalesce(octet_length(p_payload->>'receiptRevision'),0) NOT BETWEEN 1 AND 1024
|
|
326
|
+
THEN RAISE EXCEPTION 'invalid terminal MCP receipt'; END IF;
|
|
327
|
+
-- PostgreSQL canonical jsonb representation; revision is part of identity.
|
|
328
|
+
v_digest := encode(sha256(convert_to(jsonb_build_object('version',1,'codecVersion',p_payload->'resultCodecVersion','revision',p_payload->>'receiptRevision',
|
|
329
|
+
'result',p_payload->'result')::text,'UTF8')),'hex');
|
|
330
|
+
IF v_row.receipt_digest IS NOT NULL THEN
|
|
331
|
+
RETURN jsonb_build_object('status',CASE WHEN v_row.receipt_digest=v_digest THEN 'existing' ELSE 'conflict' END,
|
|
332
|
+
'receiptDigest',v_row.receipt_digest);
|
|
333
|
+
END IF;
|
|
334
|
+
IF v_row.observation_claim_expires_at<=clock_timestamp() THEN RETURN jsonb_build_object('status','stale_claim'); END IF;
|
|
335
|
+
UPDATE mcp_operations SET observation_result=p_payload->'result',receipt_revision=p_payload->>'receiptRevision',
|
|
336
|
+
observation_result_codec_version=(p_payload->>'resultCodecVersion')::integer,
|
|
337
|
+
receipt_digest=v_digest,observed_at=clock_timestamp() WHERE operation_id=v_op;
|
|
338
|
+
RETURN jsonb_build_object('status','settled','receiptDigest',v_digest);
|
|
339
|
+
ELSIF p_action<>'read' THEN RAISE EXCEPTION 'unknown MCP ledger action'; END IF;
|
|
340
|
+
|
|
341
|
+
v_operation := jsonb_build_object('operationId',v_row.operation_id,'sourceCallId',v_row.source_call_id,
|
|
342
|
+
'accountId',v_row.account_id,'workspaceId',v_row.workspace_id,'sessionId',v_row.session_id,
|
|
343
|
+
'sourceTurnId',v_row.source_turn_id,'sourceAttemptId',v_row.source_attempt_id,
|
|
344
|
+
'sourceExecutionGeneration',v_row.source_execution_generation,'principalKind',v_row.principal_kind,
|
|
345
|
+
'principalId',v_row.principal_id,'serverId',v_row.server_id,'originalTool',v_row.original_tool,
|
|
346
|
+
'observerTool',v_row.observer_tool,'argumentDigest',v_row.argument_digest,'destinationDigest',v_row.destination_digest,
|
|
347
|
+
'authorityDigest',v_row.authority_digest,'originalOutcome',v_row.original_outcome,'originalResult',v_row.original_result,
|
|
348
|
+
'originalResultCodecVersion',v_row.original_result_codec_version,
|
|
349
|
+
'observationResultCodecVersion',v_row.observation_result_codec_version,
|
|
350
|
+
'observationResult',v_row.observation_result,'receiptRevision',v_row.receipt_revision,'receiptDigest',v_row.receipt_digest);
|
|
351
|
+
RETURN jsonb_build_object('status','found','operation',v_operation);
|
|
352
|
+
END $body$;
|
|
353
|
+
$definition$,target_schema);
|
|
354
|
+
EXECUTE format($definition$
|
|
355
|
+
CREATE FUNCTION %1$I.mcp_operation_command(p_scope jsonb,p_action text,p_payload jsonb)
|
|
356
|
+
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,%1$I,pg_temp AS $body$
|
|
357
|
+
DECLARE result jsonb;
|
|
358
|
+
prior_subject text := current_setting('opengeni.subject_id',true);
|
|
359
|
+
prior_human text := current_setting('opengeni.initiating_human_subject_id',true);
|
|
360
|
+
prior_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle',true);
|
|
361
|
+
BEGIN
|
|
362
|
+
result := opengeni_private.mcp_operation_command_scoped(p_scope,p_action,p_payload);
|
|
363
|
+
PERFORM set_config('opengeni.subject_id',coalesce(prior_subject,''),true);
|
|
364
|
+
PERFORM set_config('opengeni.initiating_human_subject_id',coalesce(prior_human,''),true);
|
|
365
|
+
PERFORM set_config('opengeni.organization_tenancy_lifecycle',coalesce(prior_lifecycle,''),true);
|
|
366
|
+
RETURN result;
|
|
367
|
+
EXCEPTION WHEN OTHERS THEN
|
|
368
|
+
PERFORM set_config('opengeni.subject_id',coalesce(prior_subject,''),true);
|
|
369
|
+
PERFORM set_config('opengeni.initiating_human_subject_id',coalesce(prior_human,''),true);
|
|
370
|
+
PERFORM set_config('opengeni.organization_tenancy_lifecycle',coalesce(prior_lifecycle,''),true);
|
|
371
|
+
RAISE;
|
|
372
|
+
END $body$;
|
|
373
|
+
$definition$,target_schema);
|
|
374
|
+
EXECUTE format('REVOKE ALL ON FUNCTION %I.mcp_operation_command(jsonb,text,jsonb) FROM PUBLIC',target_schema);
|
|
375
|
+
EXECUTE format('REVOKE ALL ON TABLE %I.mcp_operations FROM PUBLIC',target_schema);
|
|
376
|
+
-- Strip inherited default grants. Provisioning must explicitly register the
|
|
377
|
+
-- EXECUTE-only routine; the table must never enter an app DML allowlist.
|
|
378
|
+
FOR role_name IN SELECT DISTINCT r.rolname FROM pg_class c
|
|
379
|
+
JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
380
|
+
CROSS JOIN LATERAL aclexplode(coalesce(c.relacl,acldefault('r',c.relowner))) acl
|
|
381
|
+
JOIN pg_roles r ON r.oid=acl.grantee
|
|
382
|
+
WHERE n.nspname=target_schema AND c.relname='mcp_operations' AND acl.grantee<>c.relowner
|
|
383
|
+
LOOP EXECUTE format('REVOKE ALL ON TABLE %I.mcp_operations FROM %I',target_schema,role_name); END LOOP;
|
|
384
|
+
-- Remove inherited/default EXECUTE grants, not merely PUBLIC. Only exact
|
|
385
|
+
-- production role provisioning grants the outer capability afterwards.
|
|
386
|
+
FOR role_name IN SELECT DISTINCT r.rolname FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace
|
|
387
|
+
CROSS JOIN LATERAL aclexplode(coalesce(p.proacl,acldefault('f',p.proowner))) acl JOIN pg_roles r ON r.oid=acl.grantee
|
|
388
|
+
WHERE acl.grantee<>p.proowner AND ((n.nspname=target_schema AND p.proname='mcp_operation_command')
|
|
389
|
+
OR (n.nspname='opengeni_private' AND p.proname IN ('mcp_operation_command_scoped','guard_mcp_operation_immutable')))
|
|
390
|
+
LOOP
|
|
391
|
+
EXECUTE format('REVOKE ALL ON FUNCTION %I.mcp_operation_command(jsonb,text,jsonb) FROM %I',target_schema,role_name);
|
|
392
|
+
EXECUTE format('REVOKE ALL ON FUNCTION opengeni_private.mcp_operation_command_scoped(jsonb,text,jsonb) FROM %I',role_name);
|
|
393
|
+
EXECUTE format('REVOKE ALL ON FUNCTION opengeni_private.guard_mcp_operation_immutable() FROM %I',role_name);
|
|
394
|
+
END LOOP;
|
|
395
|
+
REVOKE ALL ON FUNCTION opengeni_private.mcp_operation_command_scoped(jsonb,text,jsonb) FROM PUBLIC;
|
|
396
|
+
END $install$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.3-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": {
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"module": "./dist/index.js",
|
|
20
20
|
"types": "./dist/index.d.ts",
|
|
21
21
|
"exports": {
|
|
22
|
+
"./mcp-operations": {
|
|
23
|
+
"types": "./dist/mcp-operations.d.ts",
|
|
24
|
+
"import": "./dist/mcp-operations.js"
|
|
25
|
+
},
|
|
22
26
|
"./retained-provider-commands": {
|
|
23
27
|
"types": "./dist/retained-provider-commands.d.ts",
|
|
24
28
|
"import": "./dist/retained-provider-commands.js"
|
|
@@ -94,11 +98,11 @@
|
|
|
94
98
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
95
99
|
},
|
|
96
100
|
"dependencies": {
|
|
97
|
-
"@opengeni/codemode": "^0.5.
|
|
98
|
-
"@opengeni/codex": "^0.2.22",
|
|
99
|
-
"@opengeni/config": "^1.1.
|
|
100
|
-
"@opengeni/contracts": "^3.0.
|
|
101
|
-
"@opengeni/network": "^0.3.1",
|
|
101
|
+
"@opengeni/codemode": "^0.5.6-canary.0",
|
|
102
|
+
"@opengeni/codex": "^0.2.22-canary.8",
|
|
103
|
+
"@opengeni/config": "^1.1.2-canary.0",
|
|
104
|
+
"@opengeni/contracts": "^3.0.2-canary.0",
|
|
105
|
+
"@opengeni/network": "^0.3.1-canary.8",
|
|
102
106
|
"drizzle-orm": "^0.45.2",
|
|
103
107
|
"postgres": "^3.4.7"
|
|
104
108
|
},
|
|
@@ -126,6 +126,8 @@ export type ResolveConnectionCredentialResult =
|
|
|
126
126
|
connectionVersion?: number;
|
|
127
127
|
/** Metadata-only owner attribution from the immediate pre-use fence. */
|
|
128
128
|
connectionUseAttribution?: ConnectionUseAttribution;
|
|
129
|
+
/** Metadata-only equality key from current live authority; never credentials or authorization. */
|
|
130
|
+
operationAuthorityDigest?: string;
|
|
129
131
|
/**
|
|
130
132
|
* Revalidate the exact accepted attempt immediately before one target
|
|
131
133
|
* provider request. Credential lookup/refresh is a separate audited
|
package/src/database.ts
CHANGED
|
@@ -715,12 +715,24 @@ export async function withSessionActivityRlsContext<T>(
|
|
|
715
715
|
fn: (db: SessionActivityDatabase) => Promise<T>,
|
|
716
716
|
transactionConfig?: PgTransactionConfig,
|
|
717
717
|
fenceMode: "shared" | "none" = "shared",
|
|
718
|
+
organizationMembershipFence = false,
|
|
718
719
|
): Promise<T> {
|
|
719
720
|
await assertSessionActivityGateEntry(db);
|
|
720
721
|
return await withRlsContext(
|
|
721
722
|
db,
|
|
722
723
|
context,
|
|
723
724
|
async (scopedDb) => {
|
|
725
|
+
if (organizationMembershipFence) {
|
|
726
|
+
// Claim can inherit membership-fenced authority after locking sessions.
|
|
727
|
+
// Acquire membership before even a shared tenancy fence: an exclusive
|
|
728
|
+
// tenancy/control waiter can otherwise complete the same lock cycle.
|
|
729
|
+
await scopedDb.execute(sql`select pg_advisory_xact_lock(
|
|
730
|
+
hashtextextended(${`organization-membership:${context.accountId}`}, 0))`);
|
|
731
|
+
if (fenceMode === "shared") {
|
|
732
|
+
await scopedDb.execute(sql`select pg_advisory_xact_lock_shared(
|
|
733
|
+
hashtextextended(${`session-tenancy:${context.workspaceId}`}, 0))`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
724
736
|
const gate = await beginSessionActivityGate(scopedDb, context.workspaceId);
|
|
725
737
|
const value = await fn(gate.db);
|
|
726
738
|
// The finalizer must never trust tenant GUCs that arbitrary callback code
|
|
@@ -733,7 +745,7 @@ export async function withSessionActivityRlsContext<T>(
|
|
|
733
745
|
return value;
|
|
734
746
|
},
|
|
735
747
|
transactionConfig,
|
|
736
|
-
fenceMode,
|
|
748
|
+
organizationMembershipFence ? "none" : fenceMode,
|
|
737
749
|
);
|
|
738
750
|
}
|
|
739
751
|
|
|
@@ -807,10 +819,19 @@ export async function withSessionActivitySavepoint<T>(
|
|
|
807
819
|
export async function retrySessionActivityRls<T>(
|
|
808
820
|
db: Database,
|
|
809
821
|
workspaceId: string,
|
|
810
|
-
options: IdempotentPersistenceTransactionOptions
|
|
822
|
+
options: IdempotentPersistenceTransactionOptions & {
|
|
823
|
+
/** Claim can inherit membership-fenced causal authority after locking the
|
|
824
|
+
* session. Acquire that fence before tenancy/control/session instead. An
|
|
825
|
+
* enclosing transaction must preserve this same lock prefix. */
|
|
826
|
+
organizationMembershipFence?: boolean;
|
|
827
|
+
},
|
|
811
828
|
fn: (db: SessionActivityDatabase) => Promise<T>,
|
|
812
829
|
): Promise<T> {
|
|
813
830
|
return await runIdempotentPersistenceTransaction(options, async () => {
|
|
831
|
+
if (options.organizationMembershipFence) {
|
|
832
|
+
const context = { ...(await rlsContextForWorkspace(db, workspaceId)), workspaceId };
|
|
833
|
+
return await withSessionActivityRlsContext(db, context, fn, undefined, "shared", true);
|
|
834
|
+
}
|
|
814
835
|
return await withWorkspaceSessionActivityRls(db, workspaceId, fn);
|
|
815
836
|
});
|
|
816
837
|
}
|
package/src/host-mcp-bindings.ts
CHANGED
|
@@ -588,7 +588,19 @@ export async function inheritChildHostMcpTurnAuthorities(
|
|
|
588
588
|
export async function authorizeDirectHostMcpUse(
|
|
589
589
|
db: Database,
|
|
590
590
|
request: McpCredentialsRequest,
|
|
591
|
+
/** Internal metadata observer; called only after every live authorization fence succeeds. */
|
|
592
|
+
onAuthorized?: (snapshot: HostMcpAcceptedAuthority) => void,
|
|
591
593
|
): Promise<boolean> {
|
|
594
|
+
const snapshot = await resolveDirectHostMcpUseAuthority(db, request);
|
|
595
|
+
if (!snapshot) return false;
|
|
596
|
+
onAuthorized?.(snapshot);
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function resolveDirectHostMcpUseAuthority(
|
|
601
|
+
db: Database,
|
|
602
|
+
request: McpCredentialsRequest,
|
|
603
|
+
): Promise<HostMcpAcceptedAuthority | false> {
|
|
592
604
|
if (!request.attemptId || !request.connectionRef.hostBinding) return false;
|
|
593
605
|
const current = await getHostMcpLiveAttempt(
|
|
594
606
|
db,
|
|
@@ -735,7 +747,8 @@ export async function authorizeDirectHostMcpUse(
|
|
|
735
747
|
return (
|
|
736
748
|
live !== null &&
|
|
737
749
|
live.id === request.turnId &&
|
|
738
|
-
live.executionGeneration === request.executionGeneration
|
|
750
|
+
live.executionGeneration === request.executionGeneration &&
|
|
751
|
+
a
|
|
739
752
|
);
|
|
740
753
|
});
|
|
741
754
|
}
|