@opengeni/db 4.2.2 → 4.3.2-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 (103) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-S4Y42VSX.js → chunk-AI4NC6XW.js} +212 -7
  3. package/dist/chunk-AI4NC6XW.js.map +1 -0
  4. package/dist/{chunk-SNLIKELL.js → chunk-DVSLOUEI.js} +2 -2
  5. package/dist/{chunk-6GAPAYZR.js → chunk-F4BIHJ7J.js} +562 -86
  6. package/dist/chunk-F4BIHJ7J.js.map +1 -0
  7. package/dist/{chunk-B2KR3TEM.js → chunk-I4ITNMX2.js} +2 -2
  8. package/dist/{chunk-MGVOYVPS.js → chunk-PFBUHOI5.js} +3 -3
  9. package/dist/{chunk-6Y7ZDVJ5.js → chunk-UUPY6IGP.js} +2 -2
  10. package/dist/{chunk-FPTI4NP4.js → chunk-UVBPOGA7.js} +2 -2
  11. package/dist/{chunk-7WX3R3YL.js → chunk-XBXANJQS.js} +7 -2
  12. package/dist/chunk-XBXANJQS.js.map +1 -0
  13. package/dist/{chunk-6IQ4X35Y.js → chunk-YVYBAWBU.js} +46 -1
  14. package/dist/chunk-YVYBAWBU.js.map +1 -0
  15. package/dist/connect-attempts.d.ts +60 -0
  16. package/dist/connection-setup-authority.d.ts +10 -0
  17. package/dist/connection-token-resolver.d.ts +11 -2
  18. package/dist/editable-artifact-durable-export.js +2 -2
  19. package/dist/editable-artifacts.js +3 -3
  20. package/dist/external-identities.d.ts +16 -0
  21. package/dist/external-identity-links.d.ts +74 -0
  22. package/dist/external-link-work.d.ts +51 -0
  23. package/dist/host-mcp-binding-match.d.ts +5 -0
  24. package/dist/host-mcp-bindings.d.ts +73 -0
  25. package/dist/host-mcp-task-authority.d.ts +47 -0
  26. package/dist/host-selection-identity.d.ts +6 -0
  27. package/dist/index.d.ts +60 -35
  28. package/dist/index.js +15247 -13793
  29. package/dist/index.js.map +1 -1
  30. package/dist/live-session-attempt.d.ts +275 -0
  31. package/dist/managed-auth-session-sets.js +3 -3
  32. package/dist/memory-domain.d.ts +3 -3
  33. package/dist/provision-roles.js +1 -1
  34. package/dist/retained-provider-commands.js +2 -2
  35. package/dist/runtime-posture.d.ts +6 -6
  36. package/dist/scheduled-task-revision-authority.d.ts +11 -0
  37. package/dist/schema.d.ts +1279 -9
  38. package/dist/schema.js +11 -1
  39. package/dist/session-background-commands.js +4 -4
  40. package/dist/session-command-output.js +4 -4
  41. package/dist/session-event-slices.js +2 -2
  42. package/dist/session-queue-commands.d.ts +7 -0
  43. package/dist/session-tenancy.d.ts +2 -0
  44. package/dist/session-tenancy.js +3 -3
  45. package/dist/video-generation.js +3 -3
  46. package/dist/workspace-authority.d.ts +4 -4
  47. package/dist/workspace-tool-defaults.js +4 -4
  48. package/drizzle/0437_organization_scoped_external_workspaces.sql +71 -0
  49. package/drizzle/0438_durable_connect_attempts.sql +95 -0
  50. package/drizzle/0439_external_identity_provisioning.sql +128 -0
  51. package/drizzle/0440_external_workspace_member_removal.sql +59 -0
  52. package/drizzle/0441_external_identity_membership_lifecycle.sql +163 -0
  53. package/drizzle/0442_external_owning_user_authority.sql +81 -0
  54. package/drizzle/0443_host_mcp_binding_registry.sql +93 -0
  55. package/drizzle/0444_host_mcp_delegations.sql +129 -0
  56. package/drizzle/0445_host_mcp_turn_authorities.sql +145 -0
  57. package/drizzle/0446_host_mcp_causal_continuation.sql +126 -0
  58. package/drizzle/0447_host_mcp_task_authorities.sql +264 -0
  59. package/drizzle/0448_host_mcp_child_authority.sql +115 -0
  60. package/drizzle/0449_external_identity_link_lifecycle.sql +107 -0
  61. package/drizzle/0450_external_identity_link_work.sql +179 -0
  62. package/drizzle/0451_external_link_preview_and_permission_ceiling.sql +82 -0
  63. package/drizzle/0452_external_link_scheduled_origin.sql +34 -0
  64. package/drizzle/0453_host_mcp_native_owner.sql +35 -0
  65. package/drizzle/0454_connect_origin_authority.sql +42 -0
  66. package/drizzle/0455_external_link_inventory_labels.sql +44 -0
  67. package/drizzle/0456_social_connection_versions.sql +14 -0
  68. package/drizzle/0457_canonical_session_scope_subject.sql +46 -0
  69. package/drizzle/0458_skill_review_wire_compatibility.sql +36 -0
  70. package/package.json +6 -6
  71. package/src/connect-attempts.ts +317 -0
  72. package/src/connection-authority.ts +9 -0
  73. package/src/connection-setup-authority.ts +35 -0
  74. package/src/connection-token-resolver.ts +177 -48
  75. package/src/external-identities.ts +70 -0
  76. package/src/external-identity-links.ts +364 -0
  77. package/src/external-link-work.ts +228 -0
  78. package/src/host-mcp-binding-match.ts +33 -0
  79. package/src/host-mcp-bindings.ts +741 -0
  80. package/src/host-mcp-task-authority.ts +383 -0
  81. package/src/host-selection-identity.ts +29 -0
  82. package/src/index.ts +481 -151
  83. package/src/live-session-attempt.ts +85 -0
  84. package/src/managed-human-provisioning.ts +6 -1
  85. package/src/memory-domain.ts +6 -11
  86. package/src/provision-roles.ts +3 -0
  87. package/src/runtime-posture.ts +47 -0
  88. package/src/scheduled-task-revision-authority.ts +30 -0
  89. package/src/schema.ts +233 -7
  90. package/src/session-control.ts +14 -0
  91. package/src/session-queue-commands.ts +11 -0
  92. package/src/session-tenancy.ts +7 -0
  93. package/src/workspace-artifacts.ts +10 -4
  94. package/src/workspace-authority.ts +5 -5
  95. package/dist/chunk-6GAPAYZR.js.map +0 -1
  96. package/dist/chunk-6IQ4X35Y.js.map +0 -1
  97. package/dist/chunk-7WX3R3YL.js.map +0 -1
  98. package/dist/chunk-S4Y42VSX.js.map +0 -1
  99. /package/dist/{chunk-SNLIKELL.js.map → chunk-DVSLOUEI.js.map} +0 -0
  100. /package/dist/{chunk-B2KR3TEM.js.map → chunk-I4ITNMX2.js.map} +0 -0
  101. /package/dist/{chunk-MGVOYVPS.js.map → chunk-PFBUHOI5.js.map} +0 -0
  102. /package/dist/{chunk-6Y7ZDVJ5.js.map → chunk-UUPY6IGP.js.map} +0 -0
  103. /package/dist/{chunk-FPTI4NP4.js.map → chunk-UVBPOGA7.js.map} +0 -0
@@ -0,0 +1,163 @@
1
+ -- deployment-mode: rolling
2
+ -- Add attributable service administration to the canonical organization
3
+ -- lifecycle. No copied teardown, new retention policy, or native member proxy.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+
7
+ ALTER TABLE organization_membership_lifecycle_events
8
+ ALTER COLUMN actor_membership_id DROP NOT NULL,
9
+ ADD COLUMN actor_service_subject text,
10
+ ADD CONSTRAINT organization_membership_lifecycle_events_actor_kind_check CHECK (
11
+ (actor_membership_id IS NOT NULL AND actor_service_subject IS NULL)
12
+ OR (actor_membership_id IS NULL AND actor_service_subject IS NOT NULL
13
+ AND actor_service_subject ~ '^api_key:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')
14
+ );
15
+
16
+ -- Only the existing definer lifecycle routines invoke this private helper.
17
+ -- UUID knowledge is not authentication; API resolution supplies the subject.
18
+ CREATE FUNCTION opengeni_private.external_membership_service_authority(p_command jsonb)
19
+ RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT
20
+ AS $body$
21
+ DECLARE
22
+ actor_subject text := p_command ->> 'actorSubjectId';
23
+ account_id_value uuid := (p_command ->> 'organizationId')::uuid;
24
+ membership_id_value uuid := (p_command ->> 'membershipId')::uuid;
25
+ permissions_value jsonb;
26
+ allowed boolean;
27
+ previous_marker text := current_setting('opengeni.organization_tenancy_lifecycle', true);
28
+ BEGIN
29
+ IF actor_subject NOT LIKE 'api_key:%' THEN RETURN false; END IF;
30
+ IF actor_subject IS DISTINCT FROM opengeni_private.current_subject_id()
31
+ OR account_id_value IS DISTINCT FROM opengeni_private.current_account_id()
32
+ OR (p_command ->> 'action') IS NULL
33
+ OR (p_command ->> 'action') NOT IN ('suspend', 'reactivate', 'offboard')
34
+ OR account_id_value IS NULL OR membership_id_value IS NULL
35
+ THEN RAISE EXCEPTION 'external lifecycle authority invalid' USING ERRCODE = '42501'; END IF;
36
+ SELECT credential.permissions INTO permissions_value FROM api_keys credential
37
+ WHERE credential.account_id = account_id_value
38
+ AND 'api_key:' || credential.id::text = actor_subject
39
+ AND credential.workspace_id IS NULL AND credential.credential_kind = 'organization'
40
+ AND credential.revoked_at IS NULL
41
+ AND (credential.expires_at IS NULL OR credential.expires_at > clock_timestamp())
42
+ FOR SHARE;
43
+ IF NOT coalesce(permissions_value ? 'account:admin', false) THEN
44
+ RAISE EXCEPTION 'external lifecycle service administration required' USING ERRCODE = '42501';
45
+ END IF;
46
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'external_identity_provisioning', true);
47
+ SELECT EXISTS (
48
+ SELECT 1 FROM external_identities identity_row
49
+ JOIN organization_memberships membership
50
+ ON membership.id = identity_row.organization_membership_id
51
+ AND membership.account_id = identity_row.account_id
52
+ AND membership.subject_id = identity_row.subject_id
53
+ AND membership.personal_workspace_id = identity_row.personal_workspace_id
54
+ WHERE identity_row.account_id = account_id_value
55
+ AND membership.id = membership_id_value AND membership.role = 'member'
56
+ ) INTO allowed;
57
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_marker, ''), true);
58
+ IF NOT allowed THEN
59
+ RAISE EXCEPTION 'external lifecycle target unavailable' USING ERRCODE = '42501';
60
+ END IF;
61
+ RETURN true;
62
+ EXCEPTION WHEN OTHERS THEN
63
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_marker, ''), true);
64
+ RAISE;
65
+ END
66
+ $body$;
67
+ REVOKE ALL ON FUNCTION opengeni_private.external_membership_service_authority(jsonb) FROM PUBLIC;
68
+
69
+ -- Native and service lifecycle writes invalidate the same external admission
70
+ -- generation. Reactivation never restores the shared memberships or durable
71
+ -- grants that the canonical suspension/offboarding protocol already revoked.
72
+ CREATE FUNCTION opengeni_private.sync_external_membership_lifecycle()
73
+ RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT
74
+ AS $body$
75
+ DECLARE previous_marker text := current_setting('opengeni.organization_tenancy_lifecycle', true);
76
+ BEGIN
77
+ IF NEW.subject_id NOT LIKE 'external_user:%' THEN RETURN NEW; END IF;
78
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'external_identity_provisioning', true);
79
+ UPDATE external_identities SET
80
+ status = CASE NEW.status WHEN 'active' THEN 'active' WHEN 'revoked' THEN 'revoked' ELSE 'disabled' END,
81
+ authorization_revision = authorization_revision + 1,
82
+ updated_at = clock_timestamp()
83
+ WHERE account_id = NEW.account_id AND organization_membership_id = NEW.id
84
+ AND subject_id = NEW.subject_id;
85
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_marker, ''), true);
86
+ RETURN NEW;
87
+ EXCEPTION WHEN OTHERS THEN
88
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_marker, ''), true);
89
+ RAISE;
90
+ END
91
+ $body$;
92
+ REVOKE ALL ON FUNCTION opengeni_private.sync_external_membership_lifecycle() FROM PUBLIC;
93
+ CREATE TRIGGER organization_membership_external_lifecycle
94
+ AFTER UPDATE OF status, authorization_revision ON organization_memberships
95
+ FOR EACH ROW WHEN (OLD.status IS DISTINCT FROM NEW.status OR OLD.authorization_revision IS DISTINCT FROM NEW.authorization_revision)
96
+ EXECUTE FUNCTION opengeni_private.sync_external_membership_lifecycle();
97
+
98
+ DO $patch$
99
+ DECLARE
100
+ signature text;
101
+ definition text;
102
+ anchor text;
103
+ replacement text;
104
+ BEGIN
105
+ FOREACH signature IN ARRAY ARRAY[
106
+ 'prepare_organization_membership_protocol_settlements(jsonb)',
107
+ 'organization_membership_command_0263(jsonb)'
108
+ ] LOOP
109
+ definition := pg_get_functiondef(signature::regprocedure);
110
+ anchor := ' input_hash_value text;';
111
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1
112
+ OR strpos(definition, 'acquire_organization_session_tenancy_fences') = 0 THEN
113
+ RAISE EXCEPTION '0441 lifecycle declaration or fence drift: %', signature USING ERRCODE = '55000';
114
+ END IF;
115
+ definition := replace(definition, anchor, anchor || E'\n service_authorized boolean := false;');
116
+ anchor := ' input_hash_value := pg_catalog.encode(';
117
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1 THEN
118
+ RAISE EXCEPTION '0441 lifecycle admission drift: %', signature USING ERRCODE = '55000';
119
+ END IF;
120
+ definition := replace(definition, anchor,
121
+ E' service_authorized := opengeni_private.external_membership_service_authority(p_command);\n' || anchor);
122
+ IF signature = 'prepare_organization_membership_protocol_settlements(jsonb)' THEN
123
+ anchor := 'IF actor.id IS NULL OR actor.status <> ''active'' OR actor.role NOT IN (''owner'', ''admin'') THEN';
124
+ replacement := 'IF NOT service_authorized AND (actor.id IS NULL OR actor.status <> ''active'' OR actor.role NOT IN (''owner'', ''admin'')) THEN';
125
+ ELSE
126
+ anchor := 'IF NOT FOUND OR actor.status <> ''active'' THEN';
127
+ replacement := 'IF NOT service_authorized AND (NOT FOUND OR actor.status <> ''active'') THEN';
128
+ END IF;
129
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1 THEN
130
+ RAISE EXCEPTION '0441 lifecycle actor guard drift: %', signature USING ERRCODE = '55000';
131
+ END IF;
132
+ definition := replace(definition, anchor, replacement);
133
+ IF signature = 'organization_membership_command_0263(jsonb)' THEN
134
+ -- All service actions were strictly constrained before receipt replay;
135
+ -- native administration checks stay byte-for-byte for native actors.
136
+ anchor := 'IF actor.role NOT IN (''owner'', ''admin'') THEN';
137
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 3 THEN
138
+ RAISE EXCEPTION '0441 lifecycle role guard drift' USING ERRCODE = '55000';
139
+ END IF;
140
+ definition := replace(definition, anchor, 'IF NOT service_authorized AND actor.role NOT IN (''owner'', ''admin'') THEN');
141
+ anchor := 'account_id, operation_id, actor_membership_id, target_membership_id, kind,';
142
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1 THEN
143
+ RAISE EXCEPTION '0441 lifecycle audit columns drift' USING ERRCODE = '55000';
144
+ END IF;
145
+ definition := replace(definition, anchor,
146
+ 'account_id, operation_id, actor_membership_id, actor_service_subject, target_membership_id, kind,');
147
+ anchor := 'account_id_value, operation_id_value, actor.id,';
148
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1 THEN
149
+ RAISE EXCEPTION '0441 lifecycle audit values drift' USING ERRCODE = '55000';
150
+ END IF;
151
+ definition := replace(definition, anchor,
152
+ 'account_id_value, operation_id_value, actor.id, CASE WHEN service_authorized THEN actor_subject ELSE NULL END,');
153
+ anchor := '''human'', actor_subject, ''organization.membership.'' || action_name';
154
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1 THEN
155
+ RAISE EXCEPTION '0441 lifecycle interruption audit drift' USING ERRCODE = '55000';
156
+ END IF;
157
+ definition := replace(definition, anchor,
158
+ 'CASE WHEN service_authorized THEN ''service'' ELSE ''human'' END, actor_subject, ''organization.membership.'' || action_name');
159
+ END IF;
160
+ EXECUTE definition;
161
+ END LOOP;
162
+ END
163
+ $patch$;
@@ -0,0 +1,81 @@
1
+ -- deployment-mode: rolling
2
+ -- Database consistency checks for externally authenticated owning users.
3
+ -- Authentication remains in the API's dedicated external resolver proof.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+
7
+ -- The native xAI lifecycle locks the owning membership with SELECT FOR UPDATE.
8
+ -- FORCE RLS requires an UPDATE policy for that lock in addition to SELECT.
9
+ -- Permit only the table owner's private transaction-local lifecycle capability;
10
+ -- WITH CHECK false forbids using this policy to actually rewrite a membership.
11
+ CREATE POLICY xai_subscription_membership_lock ON organization_memberships
12
+ FOR UPDATE USING (
13
+ current_user = pg_catalog.pg_get_userbyid(
14
+ (SELECT relowner FROM pg_catalog.pg_class
15
+ WHERE oid = 'organization_memberships'::regclass)
16
+ )
17
+ AND EXISTS (
18
+ SELECT 1 FROM opengeni_private.xai_subscription_runtime_capabilities capability
19
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
20
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
21
+ AND capability.capability_kind = 'lifecycle'
22
+ )
23
+ ) WITH CHECK (false);
24
+
25
+ CREATE FUNCTION opengeni_private.active_external_owning_subject(p_account_id uuid, p_subject_id text)
26
+ RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT
27
+ AS $body$
28
+ DECLARE
29
+ result boolean;
30
+ previous_marker text := current_setting('opengeni.organization_tenancy_lifecycle', true);
31
+ BEGIN
32
+ IF p_account_id IS NULL OR p_subject_id IS NULL
33
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
34
+ OR p_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
35
+ THEN RETURN false; END IF;
36
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'external_identity_provisioning', true);
37
+ SELECT EXISTS (
38
+ SELECT 1 FROM external_identities identity_row
39
+ JOIN organization_memberships membership
40
+ ON membership.id = identity_row.organization_membership_id
41
+ AND membership.account_id = identity_row.account_id
42
+ AND membership.subject_id = identity_row.subject_id
43
+ AND membership.personal_workspace_id = identity_row.personal_workspace_id
44
+ WHERE identity_row.account_id = p_account_id AND identity_row.subject_id = p_subject_id
45
+ AND identity_row.status = 'active' AND membership.status = 'active'
46
+ ) INTO result;
47
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_marker, ''), true);
48
+ RETURN result;
49
+ EXCEPTION WHEN OTHERS THEN
50
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_marker, ''), true);
51
+ RAISE;
52
+ END
53
+ $body$;
54
+ REVOKE ALL ON FUNCTION opengeni_private.active_external_owning_subject(uuid,text) FROM PUBLIC;
55
+
56
+ DO $patch$
57
+ DECLARE definition text; anchor text;
58
+ BEGIN
59
+ definition := pg_get_functiondef('list_self_organization_memberships(text)'::regprocedure);
60
+ anchor := 'OR p_subject_id NOT LIKE ''user:%''';
61
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1 THEN
62
+ RAISE EXCEPTION '0442 self membership guard drift' USING ERRCODE = '55000';
63
+ END IF;
64
+ definition := replace(definition, anchor,
65
+ 'OR (p_subject_id NOT LIKE ''user:%'' AND NOT opengeni_private.active_external_owning_subject(opengeni_private.current_account_id(), p_subject_id))');
66
+ anchor := 'WHERE membership.subject_id = p_subject_id;';
67
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1 THEN
68
+ RAISE EXCEPTION '0442 self membership scope drift' USING ERRCODE = '55000';
69
+ END IF;
70
+ EXECUTE replace(definition, anchor,
71
+ 'WHERE membership.subject_id = p_subject_id AND (p_subject_id LIKE ''user:%'' OR membership.account_id = opengeni_private.current_account_id());');
72
+ definition := pg_get_functiondef('open_private_session_create_capability(uuid,uuid,uuid,text)'::regprocedure);
73
+ anchor := 'OR p_actor_subject_id NOT LIKE ''user:%''';
74
+ IF (length(definition) - length(replace(definition, anchor, ''))) / length(anchor) <> 1
75
+ OR strpos(definition, 'organization_private_sessions_enabled') = 0 THEN
76
+ RAISE EXCEPTION '0442 private create policy drift' USING ERRCODE = '55000';
77
+ END IF;
78
+ EXECUTE replace(definition, anchor,
79
+ 'OR (p_actor_subject_id NOT LIKE ''user:%'' AND NOT opengeni_private.active_external_owning_subject(p_account_id, p_actor_subject_id))');
80
+ END
81
+ $patch$;
@@ -0,0 +1,93 @@
1
+ -- deployment-mode: maintenance
2
+ -- Optional, credential-free host authority. Existing inline and host refs do
3
+ -- not opt in automatically. Drain old runtimes before changing the role contract.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+ DO $drain$
7
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
8
+ BEGIN
9
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
10
+ RAISE EXCEPTION '0443 requires application database roles' USING ERRCODE = '55000';
11
+ END IF;
12
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
13
+ SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
14
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}')
15
+ OR octet_length(item #>> '{}') > 63
16
+ ) THEN RAISE EXCEPTION '0443 invalid application roles' USING ERRCODE = '55000'; END IF;
17
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
18
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
19
+ RAISE EXCEPTION '0443 requires stopped application sessions' USING ERRCODE = '55000';
20
+ END IF;
21
+ END
22
+ $drain$;
23
+
24
+ CREATE TABLE host_mcp_bindings (
25
+ id uuid PRIMARY KEY,
26
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
27
+ workspace_id uuid NOT NULL,
28
+ owner_subject_id text NOT NULL,
29
+ authorization_revision bigint NOT NULL CHECK (authorization_revision BETWEEN 1 AND 9007199254740991),
30
+ operation_id uuid NOT NULL,
31
+ request_digest text NOT NULL CHECK (request_digest ~ '^[0-9a-f]{64}$'),
32
+ definition jsonb NOT NULL CHECK (jsonb_typeof(definition) = 'object' AND octet_length(definition::text) <= 131072),
33
+ generation bigint NOT NULL DEFAULT 1 CHECK (generation BETWEEN 1 AND 9007199254740991),
34
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','revoked')),
35
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
36
+ revoked_at timestamptz,
37
+ CONSTRAINT host_mcp_bindings_workspace_account_fk FOREIGN KEY (workspace_id, account_id)
38
+ REFERENCES workspaces(id, account_id) ON DELETE CASCADE,
39
+ CONSTRAINT host_mcp_bindings_external_owner_fk FOREIGN KEY (account_id, owner_subject_id)
40
+ REFERENCES external_identities(account_id, subject_id),
41
+ CHECK ((status = 'active' AND revoked_at IS NULL) OR (status = 'revoked' AND revoked_at IS NOT NULL)),
42
+ UNIQUE (workspace_id, owner_subject_id, operation_id)
43
+ );
44
+ CREATE INDEX host_mcp_bindings_owner_idx ON host_mcp_bindings(workspace_id, owner_subject_id, created_at, id);
45
+ ALTER TABLE host_mcp_bindings ENABLE ROW LEVEL SECURITY;
46
+ ALTER TABLE host_mcp_bindings FORCE ROW LEVEL SECURITY;
47
+ CREATE POLICY host_mcp_bindings_owner_scope ON host_mcp_bindings
48
+ USING (account_id = opengeni_private.current_account_id()
49
+ AND workspace_id = opengeni_private.current_workspace_id()
50
+ AND owner_subject_id = opengeni_private.current_subject_id())
51
+ WITH CHECK (account_id = opengeni_private.current_account_id()
52
+ AND workspace_id = opengeni_private.current_workspace_id()
53
+ AND owner_subject_id = opengeni_private.current_subject_id());
54
+
55
+ CREATE FUNCTION opengeni_private.guard_host_mcp_binding_update() RETURNS trigger
56
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
57
+ BEGIN
58
+ IF ROW(NEW.id, NEW.account_id, NEW.workspace_id, NEW.owner_subject_id,
59
+ NEW.authorization_revision, NEW.operation_id, NEW.request_digest, NEW.definition, NEW.created_at)
60
+ IS DISTINCT FROM ROW(OLD.id, OLD.account_id, OLD.workspace_id, OLD.owner_subject_id,
61
+ OLD.authorization_revision, OLD.operation_id, OLD.request_digest, OLD.definition, OLD.created_at)
62
+ OR OLD.status <> 'active' OR NEW.status <> 'revoked'
63
+ OR NEW.generation <> OLD.generation + 1 OR NEW.revoked_at IS NULL THEN
64
+ RAISE EXCEPTION 'host binding identity is immutable and revocation is terminal' USING ERRCODE = '42501';
65
+ END IF;
66
+ RETURN NEW;
67
+ END
68
+ $body$;
69
+ CREATE TRIGGER host_mcp_binding_immutable BEFORE UPDATE ON host_mcp_bindings
70
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_host_mcp_binding_update();
71
+ REVOKE ALL ON FUNCTION opengeni_private.guard_host_mcp_binding_update() FROM PUBLIC;
72
+ REVOKE ALL ON TABLE host_mcp_bindings FROM PUBLIC;
73
+ DO $acl$
74
+ DECLARE target_role record;
75
+ BEGIN
76
+ FOR target_role IN SELECT DISTINCT role.rolname FROM pg_class relation
77
+ CROSS JOIN LATERAL aclexplode(coalesce(relation.relacl, acldefault('r', relation.relowner))) privilege
78
+ JOIN pg_roles role ON role.oid = privilege.grantee
79
+ WHERE relation.oid = 'host_mcp_bindings'::regclass AND privilege.grantee <> relation.relowner
80
+ LOOP EXECUTE format('REVOKE ALL ON TABLE host_mcp_bindings FROM %I', target_role.rolname); END LOOP;
81
+ FOR target_role IN SELECT DISTINCT role.rolname FROM pg_proc routine
82
+ CROSS JOIN LATERAL aclexplode(coalesce(routine.proacl, acldefault('f', routine.proowner))) privilege
83
+ JOIN pg_roles role ON role.oid = privilege.grantee
84
+ WHERE routine.oid = 'opengeni_private.guard_host_mcp_binding_update()'::regprocedure
85
+ AND privilege.grantee <> routine.proowner
86
+ LOOP EXECUTE format('REVOKE ALL ON FUNCTION opengeni_private.guard_host_mcp_binding_update() FROM %I', target_role.rolname); END LOOP;
87
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a
88
+ JOIN jsonb_array_elements_text(current_setting('opengeni.migration_application_roles')::jsonb) r ON r = a.usename
89
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
90
+ RAISE EXCEPTION '0443 requires stopped application sessions' USING ERRCODE = '55000';
91
+ END IF;
92
+ END
93
+ $acl$;
@@ -0,0 +1,129 @@
1
+ -- deployment-mode: maintenance
2
+ -- Owner-scoped delegation metadata. This does not admit execution.
3
+ -- Accepted-work capture and runtime authorization are separate seams.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+ DO $drain$
7
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
8
+ BEGIN
9
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
10
+ RAISE EXCEPTION '0444 requires application database roles' USING ERRCODE = '55000';
11
+ END IF;
12
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
13
+ SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
14
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}')
15
+ OR octet_length(item #>> '{}') > 63
16
+ ) THEN RAISE EXCEPTION '0444 invalid application roles' USING ERRCODE = '55000'; END IF;
17
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
18
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
19
+ RAISE EXCEPTION '0444 requires stopped application sessions' USING ERRCODE = '55000';
20
+ END IF;
21
+ END
22
+ $drain$;
23
+
24
+ CREATE TABLE host_mcp_delegations (
25
+ id uuid PRIMARY KEY,
26
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
27
+ workspace_id uuid NOT NULL,
28
+ owner_subject_id text NOT NULL,
29
+ owner_authorization_revision bigint NOT NULL CHECK (owner_authorization_revision BETWEEN 1 AND 9007199254740991),
30
+ binding_id uuid NOT NULL REFERENCES host_mcp_bindings(id),
31
+ binding_generation bigint NOT NULL CHECK (binding_generation BETWEEN 1 AND 9007199254740991),
32
+ operation_id uuid NOT NULL,
33
+ request_digest text NOT NULL CHECK (request_digest ~ '^[0-9a-f]{64}$'),
34
+ grant_definition jsonb NOT NULL CHECK (jsonb_typeof(grant_definition) = 'object' AND octet_length(grant_definition::text) <= 4096),
35
+ generation bigint NOT NULL DEFAULT 1 CHECK (generation BETWEEN 1 AND 9007199254740991),
36
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
37
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
38
+ revoked_at timestamptz,
39
+ CONSTRAINT host_mcp_delegations_workspace_account_fk FOREIGN KEY (workspace_id, account_id)
40
+ REFERENCES workspaces(id, account_id) ON DELETE CASCADE,
41
+ CONSTRAINT host_mcp_delegations_external_owner_fk FOREIGN KEY (account_id, owner_subject_id)
42
+ REFERENCES external_identities(account_id, subject_id),
43
+ CHECK ((status = 'active' AND revoked_at IS NULL) OR (status = 'revoked' AND revoked_at IS NOT NULL)),
44
+ UNIQUE (workspace_id, owner_subject_id, operation_id)
45
+ );
46
+ CREATE INDEX host_mcp_delegations_owner_idx ON host_mcp_delegations(workspace_id, owner_subject_id, binding_id, id);
47
+ ALTER TABLE host_mcp_delegations ENABLE ROW LEVEL SECURITY;
48
+ ALTER TABLE host_mcp_delegations FORCE ROW LEVEL SECURITY;
49
+ CREATE POLICY host_mcp_delegations_owner_scope ON host_mcp_delegations
50
+ USING (account_id = opengeni_private.current_account_id()
51
+ AND workspace_id = opengeni_private.current_workspace_id()
52
+ AND owner_subject_id = opengeni_private.current_subject_id())
53
+ WITH CHECK (account_id = opengeni_private.current_account_id()
54
+ AND workspace_id = opengeni_private.current_workspace_id()
55
+ AND owner_subject_id = opengeni_private.current_subject_id());
56
+
57
+ CREATE FUNCTION opengeni_private.guard_host_mcp_delegation() RETURNS trigger
58
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
59
+ DECLARE binding host_mcp_bindings%ROWTYPE;
60
+ BEGIN
61
+ IF TG_OP = 'UPDATE' THEN
62
+ IF ROW(NEW.id, NEW.account_id, NEW.workspace_id, NEW.owner_subject_id,
63
+ NEW.owner_authorization_revision, NEW.binding_id, NEW.binding_generation,
64
+ NEW.operation_id, NEW.request_digest, NEW.grant_definition, NEW.created_at)
65
+ IS DISTINCT FROM ROW(OLD.id, OLD.account_id, OLD.workspace_id, OLD.owner_subject_id,
66
+ OLD.owner_authorization_revision, OLD.binding_id, OLD.binding_generation,
67
+ OLD.operation_id, OLD.request_digest, OLD.grant_definition, OLD.created_at)
68
+ OR OLD.status <> 'active' OR NEW.status <> 'revoked'
69
+ OR NEW.generation <> OLD.generation + 1 OR NEW.revoked_at IS NULL THEN
70
+ RAISE EXCEPTION 'host delegation identity is immutable and revocation is terminal' USING ERRCODE = '42501';
71
+ END IF;
72
+ RETURN NEW;
73
+ END IF;
74
+ SELECT b.* INTO binding FROM host_mcp_bindings b WHERE b.id = NEW.binding_id FOR SHARE;
75
+ IF NOT FOUND OR binding.status <> 'active' OR binding.revoked_at IS NOT NULL
76
+ OR binding.account_id <> NEW.account_id OR binding.workspace_id <> NEW.workspace_id
77
+ OR binding.owner_subject_id <> NEW.owner_subject_id
78
+ OR binding.authorization_revision <> NEW.owner_authorization_revision
79
+ OR binding.generation <> NEW.binding_generation
80
+ OR NEW.status <> 'active' OR NEW.generation <> 1 OR NEW.revoked_at IS NOT NULL THEN
81
+ RAISE EXCEPTION 'host delegation binding unavailable' USING ERRCODE = '42501';
82
+ END IF;
83
+ IF (NEW.grant_definition ->> 'scope') IS DISTINCT FROM 'user'
84
+ OR coalesce(NEW.grant_definition ->> 'mode', '') NOT IN ('session','always')
85
+ OR coalesce(NEW.grant_definition ->> 'context', '') NOT IN ('user_private','workspace_shared')
86
+ OR jsonb_typeof(NEW.grant_definition -> 'workspaceSharedAcknowledged') IS DISTINCT FROM 'boolean'
87
+ OR (NEW.grant_definition ->> 'context' = 'workspace_shared'
88
+ AND NEW.grant_definition -> 'workspaceSharedAcknowledged' <> 'true'::jsonb)
89
+ OR NEW.grant_definition - ARRAY['scope','mode','context','sessionId','expectedAuthorityEpoch','workspaceSharedAcknowledged'] <> '{}'::jsonb
90
+ OR ((NEW.grant_definition ->> 'sessionId') IS NULL) IS DISTINCT FROM
91
+ ((NEW.grant_definition ->> 'expectedAuthorityEpoch') IS NULL)
92
+ OR (NEW.grant_definition ->> 'mode' = 'session' AND NEW.grant_definition ->> 'sessionId' IS NULL)
93
+ OR (NEW.grant_definition ->> 'mode' = 'always' AND NEW.grant_definition ->> 'sessionId' IS NOT NULL)
94
+ THEN RAISE EXCEPTION 'invalid host delegation scope' USING ERRCODE = '22023'; END IF;
95
+ IF NEW.grant_definition ->> 'sessionId' IS NOT NULL AND NOT EXISTS (
96
+ SELECT 1 FROM sessions s WHERE s.id = (NEW.grant_definition ->> 'sessionId')::uuid
97
+ AND s.account_id = NEW.account_id AND s.workspace_id = NEW.workspace_id
98
+ AND s.visibility = NEW.grant_definition ->> 'context'
99
+ AND s.authority_epoch = (NEW.grant_definition ->> 'expectedAuthorityEpoch')::bigint
100
+ AND (s.visibility = 'workspace_shared' OR s.owner_subject_id = NEW.owner_subject_id)
101
+ ) THEN RAISE EXCEPTION 'host delegation session unavailable' USING ERRCODE = '42501'; END IF;
102
+ RETURN NEW;
103
+ END
104
+ $body$;
105
+ CREATE TRIGGER host_mcp_delegation_guard BEFORE INSERT OR UPDATE ON host_mcp_delegations
106
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_host_mcp_delegation();
107
+ REVOKE ALL ON FUNCTION opengeni_private.guard_host_mcp_delegation() FROM PUBLIC;
108
+ REVOKE ALL ON TABLE host_mcp_delegations FROM PUBLIC;
109
+ DO $acl$
110
+ DECLARE target_role record;
111
+ BEGIN
112
+ FOR target_role IN SELECT DISTINCT role.rolname FROM pg_class relation
113
+ CROSS JOIN LATERAL aclexplode(coalesce(relation.relacl, acldefault('r', relation.relowner))) privilege
114
+ JOIN pg_roles role ON role.oid = privilege.grantee
115
+ WHERE relation.oid = 'host_mcp_delegations'::regclass AND privilege.grantee <> relation.relowner
116
+ LOOP EXECUTE format('REVOKE ALL ON TABLE host_mcp_delegations FROM %I', target_role.rolname); END LOOP;
117
+ FOR target_role IN SELECT DISTINCT role.rolname FROM pg_proc routine
118
+ CROSS JOIN LATERAL aclexplode(coalesce(routine.proacl, acldefault('f', routine.proowner))) privilege
119
+ JOIN pg_roles role ON role.oid = privilege.grantee
120
+ WHERE routine.oid = 'opengeni_private.guard_host_mcp_delegation()'::regprocedure
121
+ AND privilege.grantee <> routine.proowner
122
+ LOOP EXECUTE format('REVOKE ALL ON FUNCTION opengeni_private.guard_host_mcp_delegation() FROM %I', target_role.rolname); END LOOP;
123
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a
124
+ JOIN jsonb_array_elements_text(current_setting('opengeni.migration_application_roles')::jsonb) r ON r = a.usename
125
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
126
+ RAISE EXCEPTION '0444 requires stopped application sessions' USING ERRCODE = '55000';
127
+ END IF;
128
+ END
129
+ $acl$;
@@ -0,0 +1,145 @@
1
+ -- deployment-mode: maintenance
2
+ -- Direct-turn capture only. Runtime consumption and inherited admission are separate.
3
+ SET LOCAL lock_timeout = '5s';
4
+ SET LOCAL statement_timeout = '5min';
5
+ DO $drain$
6
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
7
+ BEGIN
8
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' THEN
9
+ RAISE EXCEPTION '0445 requires application database roles' USING ERRCODE = '55000';
10
+ END IF;
11
+ IF jsonb_array_length(roles) NOT BETWEEN 1 AND 16 OR EXISTS (
12
+ SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
13
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}')
14
+ OR octet_length(item #>> '{}') > 63
15
+ ) THEN RAISE EXCEPTION '0445 invalid application roles' USING ERRCODE = '55000'; END IF;
16
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
17
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
18
+ RAISE EXCEPTION '0445 requires stopped application sessions' USING ERRCODE = '55000';
19
+ END IF;
20
+ END
21
+ $drain$;
22
+
23
+ CREATE TABLE host_mcp_turn_authorities (
24
+ turn_id uuid NOT NULL REFERENCES session_turns(id) ON DELETE CASCADE,
25
+ server_id text NOT NULL CHECK (octet_length(server_id) BETWEEN 1 AND 1024),
26
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
27
+ workspace_id uuid NOT NULL,
28
+ session_id uuid NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
29
+ owner_subject_id text NOT NULL,
30
+ binding_id uuid NOT NULL REFERENCES host_mcp_bindings(id),
31
+ delegation_id uuid NOT NULL REFERENCES host_mcp_delegations(id),
32
+ canonical_snapshot jsonb NOT NULL CHECK (jsonb_typeof(canonical_snapshot) = 'object' AND octet_length(canonical_snapshot::text) <= 262144),
33
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
34
+ PRIMARY KEY (turn_id, server_id),
35
+ FOREIGN KEY (workspace_id, account_id) REFERENCES workspaces(id, account_id) ON DELETE CASCADE
36
+ );
37
+ ALTER TABLE host_mcp_turn_authorities ENABLE ROW LEVEL SECURITY;
38
+ ALTER TABLE host_mcp_turn_authorities FORCE ROW LEVEL SECURITY;
39
+ CREATE POLICY session_visibility_isolation ON host_mcp_turn_authorities AS RESTRICTIVE
40
+ USING (session_reference_visible(account_id, workspace_id, session_id))
41
+ WITH CHECK (session_reference_visible(account_id, workspace_id, session_id));
42
+ CREATE POLICY host_mcp_turn_authorities_owner_scope ON host_mcp_turn_authorities
43
+ USING (account_id = opengeni_private.current_account_id()
44
+ AND workspace_id = opengeni_private.current_workspace_id()
45
+ AND owner_subject_id = opengeni_private.current_subject_id())
46
+ WITH CHECK (account_id = opengeni_private.current_account_id()
47
+ AND workspace_id = opengeni_private.current_workspace_id()
48
+ AND owner_subject_id = opengeni_private.current_subject_id());
49
+
50
+ CREATE FUNCTION opengeni_private.guard_host_mcp_turn_authority() RETURNS trigger
51
+ LANGUAGE plpgsql SET search_path FROM CURRENT AS $body$
52
+ DECLARE
53
+ s sessions%ROWTYPE;
54
+ t session_turns%ROWTYPE;
55
+ d host_mcp_delegations%ROWTYPE;
56
+ b host_mcp_bindings%ROWTYPE;
57
+ membership jsonb;
58
+ expected jsonb;
59
+ BEGIN
60
+ IF TG_OP <> 'INSERT' THEN
61
+ RAISE EXCEPTION 'accepted host authority is immutable' USING ERRCODE = '42501';
62
+ END IF;
63
+ IF NEW.account_id IS DISTINCT FROM opengeni_private.current_account_id()
64
+ OR NEW.workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
65
+ OR NEW.owner_subject_id IS DISTINCT FROM opengeni_private.current_subject_id() THEN
66
+ RAISE EXCEPTION 'host authority scope unavailable' USING ERRCODE = '42501';
67
+ END IF;
68
+ PERFORM pg_advisory_xact_lock(hashtextextended('organization-membership:' || NEW.account_id::text, 0));
69
+ SELECT value INTO membership FROM jsonb_array_elements(list_self_organization_memberships(NEW.owner_subject_id))
70
+ WHERE value ->> 'organizationId' = NEW.account_id::text AND value ->> 'status' = 'active';
71
+ IF membership IS NULL OR NOT (EXISTS (
72
+ SELECT 1 FROM workspace_memberships w WHERE w.workspace_id = NEW.workspace_id AND w.subject_id = NEW.owner_subject_id
73
+ ) OR coalesce(membership ->> 'personalWorkspaceId' = NEW.workspace_id::text, false)) THEN
74
+ RAISE EXCEPTION 'host authority membership unavailable' USING ERRCODE = '42501';
75
+ END IF;
76
+ SELECT * INTO s FROM sessions WHERE id = NEW.session_id AND account_id = NEW.account_id AND workspace_id = NEW.workspace_id FOR UPDATE;
77
+ IF NOT FOUND OR (s.visibility = 'user_private' AND s.owner_subject_id IS DISTINCT FROM NEW.owner_subject_id) THEN
78
+ RAISE EXCEPTION 'host authority session unavailable' USING ERRCODE = '42501';
79
+ END IF;
80
+ SELECT * INTO t FROM session_turns WHERE id = NEW.turn_id AND session_id = NEW.session_id
81
+ AND account_id = NEW.account_id AND workspace_id = NEW.workspace_id FOR UPDATE;
82
+ IF NOT FOUND OR t.status <> 'queued' OR t.active_attempt_id IS NOT NULL
83
+ OR t.source NOT IN ('user','api') OR t.initiator_kind <> 'subject'
84
+ OR t.initiator_subject_id <> NEW.owner_subject_id
85
+ OR (t.initiating_human_subject_id IS NOT NULL AND t.initiating_human_subject_id <> NEW.owner_subject_id)
86
+ OR t.initiator_context ?| ARRAY['via','viaTruncated','provenanceError','backfill']
87
+ OR EXISTS (SELECT 1 FROM session_turn_attempts a WHERE a.turn_id = t.id AND a.workspace_id = NEW.workspace_id) THEN
88
+ RAISE EXCEPTION 'host direct turn unavailable' USING ERRCODE = '42501';
89
+ END IF;
90
+ SELECT * INTO d FROM host_mcp_delegations WHERE id = NEW.delegation_id AND account_id = NEW.account_id
91
+ AND workspace_id = NEW.workspace_id AND owner_subject_id = NEW.owner_subject_id FOR SHARE;
92
+ IF NOT FOUND OR d.status <> 'active' OR d.revoked_at IS NOT NULL OR d.binding_id <> NEW.binding_id
93
+ OR d.owner_authorization_revision::text IS DISTINCT FROM membership ->> 'authorizationRevision'
94
+ OR d.grant_definition ->> 'context' IS DISTINCT FROM s.visibility
95
+ OR (d.grant_definition ->> 'mode' = 'session' AND (
96
+ d.grant_definition ->> 'sessionId' IS DISTINCT FROM s.id::text
97
+ OR d.grant_definition ->> 'expectedAuthorityEpoch' IS DISTINCT FROM s.authority_epoch::text)) THEN
98
+ RAISE EXCEPTION 'host delegation unavailable' USING ERRCODE = '42501';
99
+ END IF;
100
+ SELECT * INTO b FROM host_mcp_bindings WHERE id = NEW.binding_id AND account_id = NEW.account_id
101
+ AND workspace_id = NEW.workspace_id AND owner_subject_id = NEW.owner_subject_id FOR SHARE;
102
+ IF NOT FOUND OR b.status <> 'active' OR b.revoked_at IS NOT NULL
103
+ OR b.generation <> d.binding_generation OR b.authorization_revision <> d.owner_authorization_revision
104
+ OR b.definition ->> 'serverId' IS DISTINCT FROM NEW.server_id THEN
105
+ RAISE EXCEPTION 'host binding unavailable' USING ERRCODE = '42501';
106
+ END IF;
107
+ expected := jsonb_build_object(
108
+ 'version', 1, 'accountId', NEW.account_id, 'workspaceId', NEW.workspace_id,
109
+ 'targetSessionId', s.id, 'targetSessionVisibility', s.visibility, 'targetSessionAuthorityEpoch', s.authority_epoch,
110
+ 'acceptedWork', jsonb_build_object('kind','turn','turnId',t.id),
111
+ 'bindingId', b.id, 'bindingGeneration', b.generation, 'definition', b.definition,
112
+ 'ownerSubjectId', NEW.owner_subject_id, 'ownerOrganizationMembershipId', membership ->> 'id',
113
+ 'ownerMembershipAuthorizationRevision', (membership ->> 'authorizationRevision')::bigint,
114
+ 'delegationId', d.id, 'delegationGeneration', d.generation, 'source', jsonb_build_object('kind','direct')
115
+ );
116
+ IF NEW.canonical_snapshot IS DISTINCT FROM expected THEN
117
+ RAISE EXCEPTION 'host authority snapshot is not canonical' USING ERRCODE = '42501';
118
+ END IF;
119
+ RETURN NEW;
120
+ END
121
+ $body$;
122
+ CREATE TRIGGER host_mcp_turn_authority_guard BEFORE INSERT OR UPDATE ON host_mcp_turn_authorities
123
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_host_mcp_turn_authority();
124
+ REVOKE ALL ON FUNCTION opengeni_private.guard_host_mcp_turn_authority() FROM PUBLIC;
125
+ REVOKE ALL ON TABLE host_mcp_turn_authorities FROM PUBLIC;
126
+ DO $acl$
127
+ DECLARE target_role record;
128
+ BEGIN
129
+ FOR target_role IN SELECT DISTINCT role.rolname FROM pg_class relation
130
+ CROSS JOIN LATERAL aclexplode(coalesce(relation.relacl, acldefault('r', relation.relowner))) privilege
131
+ JOIN pg_roles role ON role.oid = privilege.grantee
132
+ WHERE relation.oid = 'host_mcp_turn_authorities'::regclass AND privilege.grantee <> relation.relowner
133
+ LOOP EXECUTE format('REVOKE ALL ON TABLE host_mcp_turn_authorities FROM %I', target_role.rolname); END LOOP;
134
+ FOR target_role IN SELECT DISTINCT role.rolname FROM pg_proc routine
135
+ CROSS JOIN LATERAL aclexplode(coalesce(routine.proacl, acldefault('f', routine.proowner))) privilege
136
+ JOIN pg_roles role ON role.oid = privilege.grantee
137
+ WHERE routine.oid = 'opengeni_private.guard_host_mcp_turn_authority()'::regprocedure AND privilege.grantee <> routine.proowner
138
+ LOOP EXECUTE format('REVOKE ALL ON FUNCTION opengeni_private.guard_host_mcp_turn_authority() FROM %I', target_role.rolname); END LOOP;
139
+ IF EXISTS (SELECT 1 FROM pg_stat_activity a
140
+ JOIN jsonb_array_elements_text(current_setting('opengeni.migration_application_roles')::jsonb) r ON r = a.usename
141
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
142
+ RAISE EXCEPTION '0445 requires stopped application sessions' USING ERRCODE = '55000';
143
+ END IF;
144
+ END
145
+ $acl$;