@creatornader/agent-bridge 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +86 -4
  2. package/README.md +567 -55
  3. package/ROADMAP.md +114 -0
  4. package/SECURITY.md +90 -0
  5. package/SKILL.md +68 -2
  6. package/bin/agent-bridge +2 -2
  7. package/clients/claude-desktop.json +3 -1
  8. package/dist/archive.d.ts +163 -0
  9. package/dist/archive.js +50 -0
  10. package/dist/archive.js.map +1 -0
  11. package/dist/bridge-domain-CBX3IVqC.d.ts +153 -0
  12. package/dist/{bridge-service-CfqMcMAn.d.ts → bridge-service-hX1ZQp4W.d.ts} +15 -5
  13. package/dist/bridge-store-DPVSCwfs.d.ts +102 -0
  14. package/dist/bridge.d.ts +76 -17
  15. package/dist/bridge.js +52 -24
  16. package/dist/chunk-2DFNOLMO.js +618 -0
  17. package/dist/chunk-2DFNOLMO.js.map +1 -0
  18. package/dist/{chunk-KVR4JBRF.js → chunk-2EVLG3YA.js} +2 -2
  19. package/dist/{chunk-KVR4JBRF.js.map → chunk-2EVLG3YA.js.map} +1 -1
  20. package/dist/chunk-45BZGB77.js +709 -0
  21. package/dist/chunk-45BZGB77.js.map +1 -0
  22. package/dist/chunk-4VB44KJU.js +230 -0
  23. package/dist/chunk-4VB44KJU.js.map +1 -0
  24. package/dist/chunk-5GMB3A77.js +1013 -0
  25. package/dist/chunk-5GMB3A77.js.map +1 -0
  26. package/dist/chunk-A6W4QALF.js +1258 -0
  27. package/dist/chunk-A6W4QALF.js.map +1 -0
  28. package/dist/chunk-BZLMF5O5.js +328 -0
  29. package/dist/chunk-BZLMF5O5.js.map +1 -0
  30. package/dist/{chunk-AXL2I7FH.js → chunk-CEXBMOI5.js} +240 -79
  31. package/dist/chunk-CEXBMOI5.js.map +1 -0
  32. package/dist/chunk-GADH244D.js +527 -0
  33. package/dist/chunk-GADH244D.js.map +1 -0
  34. package/dist/chunk-MBPZXTJA.js +2428 -0
  35. package/dist/chunk-MBPZXTJA.js.map +1 -0
  36. package/dist/chunk-NCO7RLBM.js +146 -0
  37. package/dist/chunk-NCO7RLBM.js.map +1 -0
  38. package/dist/chunk-Q6PPMXJC.js +1050 -0
  39. package/dist/chunk-Q6PPMXJC.js.map +1 -0
  40. package/dist/{chunk-4KAOBC5X.js → chunk-SPSMZOCJ.js} +190 -165
  41. package/dist/chunk-SPSMZOCJ.js.map +1 -0
  42. package/dist/chunk-UAHMP6GC.js +54 -0
  43. package/dist/chunk-UAHMP6GC.js.map +1 -0
  44. package/dist/{chunk-YD6YJATN.js → chunk-VOQIDTCJ.js} +4 -2
  45. package/dist/chunk-VOQIDTCJ.js.map +1 -0
  46. package/dist/chunk-WAI2IYIQ.js +554 -0
  47. package/dist/chunk-WAI2IYIQ.js.map +1 -0
  48. package/dist/cli.d.ts +2 -1
  49. package/dist/cli.js +3555 -214
  50. package/dist/cli.js.map +1 -1
  51. package/dist/client-config.d.ts +1 -1
  52. package/dist/client-config.js +1 -1
  53. package/dist/client-runtime.d.ts +10 -6
  54. package/dist/client-runtime.js +9 -6
  55. package/dist/contracts/generate.d.ts +2 -0
  56. package/dist/contracts/generate.js +247 -0
  57. package/dist/contracts/generate.js.map +1 -0
  58. package/dist/dr.d.ts +324 -0
  59. package/dist/dr.js +124 -0
  60. package/dist/dr.js.map +1 -0
  61. package/dist/gateway-CggQ8vTC.d.ts +78 -0
  62. package/dist/gateway-main.js +190 -8
  63. package/dist/gateway-main.js.map +1 -1
  64. package/dist/gateway.d.ts +4 -3
  65. package/dist/gateway.js +4 -3
  66. package/dist/index.js +11 -8
  67. package/dist/index.js.map +1 -1
  68. package/dist/postgres-bridge-store-Bfd3hRId.d.ts +49 -0
  69. package/dist/server.d.ts +3 -2
  70. package/dist/server.js +13 -8
  71. package/dist/sqlite.d.ts +94 -12
  72. package/dist/sqlite.js +7 -4
  73. package/docs/architecture-v2.md +394 -0
  74. package/docs/decisions/0001-protocol-layers-and-acknowledgment-semantics.md +85 -0
  75. package/docs/decisions/0002-canonical-operation-contract-registry.md +46 -0
  76. package/docs/decisions/0003-host-adapters-and-consumer-instance-keys.md +88 -0
  77. package/docs/ecosystem.md +86 -0
  78. package/docs/postmortems/2026-07-08-wrapper-source-drift.md +82 -0
  79. package/docs/troubleshooting.md +89 -0
  80. package/llms.txt +107 -6
  81. package/openapi/agent-bridge-v2.openapi.json +9610 -0
  82. package/package.json +24 -4
  83. package/schemas/agent-bridge-v2.capabilities.json +1829 -0
  84. package/schemas/agent-bridge-v2.mcp.json +2074 -0
  85. package/schemas/agent-bridge-v2.schema.json +4560 -0
  86. package/sql/migrations/008_message_projects.sql +36 -0
  87. package/sql/migrations/009_mailbox_query_indexes.sql +28 -0
  88. package/sql/migrations/010_publisher_delivery_policy.sql +210 -0
  89. package/sql/migrations/011_credential_security.sql +875 -0
  90. package/sql/migrations/012_request_authority.sql +177 -0
  91. package/sql/migrations/013_row_isolation.sql +495 -0
  92. package/sql/migrations/014_owner_control_plane.sql +1641 -0
  93. package/sql/migrations/015_portable_archives.sql +1813 -0
  94. package/sql/migrations/016_native_dr_fence.sql +1005 -0
  95. package/bin/CLAUDE.md +0 -11
  96. package/dist/bridge-domain-BcnBeHOn.d.ts +0 -97
  97. package/dist/bridge-store-BuGjxTn4.d.ts +0 -61
  98. package/dist/chunk-4KAOBC5X.js.map +0 -1
  99. package/dist/chunk-6MAZ6YLZ.js +0 -146
  100. package/dist/chunk-6MAZ6YLZ.js.map +0 -1
  101. package/dist/chunk-AXL2I7FH.js.map +0 -1
  102. package/dist/chunk-BV6W4AJK.js +0 -211
  103. package/dist/chunk-BV6W4AJK.js.map +0 -1
  104. package/dist/chunk-CKSKUBDQ.js +0 -153
  105. package/dist/chunk-CKSKUBDQ.js.map +0 -1
  106. package/dist/chunk-JOUBDRYX.js +0 -272
  107. package/dist/chunk-JOUBDRYX.js.map +0 -1
  108. package/dist/chunk-JTC5YURG.js +0 -49
  109. package/dist/chunk-JTC5YURG.js.map +0 -1
  110. package/dist/chunk-O26W5QHS.js +0 -409
  111. package/dist/chunk-O26W5QHS.js.map +0 -1
  112. package/dist/chunk-WEULPMUY.js +0 -988
  113. package/dist/chunk-WEULPMUY.js.map +0 -1
  114. package/dist/chunk-YD6YJATN.js.map +0 -1
  115. package/dist/gateway-BULozv3K.d.ts +0 -54
@@ -0,0 +1,1641 @@
1
+ begin;
2
+
3
+ select pg_advisory_xact_lock(1646705660);
4
+
5
+ do $roles$
6
+ declare
7
+ suffix text := substr(md5(current_database()),1,16);
8
+ role_name text;
9
+ begin
10
+ foreach role_name in array array[
11
+ 'agent_bridge_control_owner_' || suffix,
12
+ 'agent_bridge_control_operator_' || suffix,
13
+ 'agent_bridge_control_auditor_' || suffix
14
+ ] loop
15
+ if not exists (select 1 from pg_roles where rolname=role_name) then
16
+ execute format(
17
+ 'create role %I nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls',
18
+ role_name
19
+ );
20
+ end if;
21
+ execute format(
22
+ 'alter role %I nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls',
23
+ role_name
24
+ );
25
+ execute format('grant %I to %I with admin option',role_name,current_user);
26
+ end loop;
27
+ end
28
+ $roles$;
29
+
30
+ create table agent_bridge.control_requests (
31
+ request_id uuid primary key,
32
+ operation text not null,
33
+ fingerprint char(64) not null,
34
+ actor name not null,
35
+ result jsonb not null,
36
+ created_at timestamptz not null default clock_timestamp(),
37
+ constraint control_requests_operation check(operation in ('provision','rotate','revoke')),
38
+ constraint control_requests_fingerprint check(fingerprint ~ '^[0-9a-f]{64}$')
39
+ );
40
+
41
+ create table agent_bridge.control_events (
42
+ sequence bigint generated always as identity primary key,
43
+ event_id uuid not null default gen_random_uuid() unique,
44
+ request_id uuid not null references agent_bridge.control_requests(request_id),
45
+ operation text not null,
46
+ outcome text not null,
47
+ actor name not null,
48
+ workspace_id text,
49
+ principal text,
50
+ credential_id uuid,
51
+ related_credential_id uuid,
52
+ reason_code text,
53
+ created_at timestamptz not null default clock_timestamp(),
54
+ constraint control_events_operation check(operation in ('provision','rotate','revoke')),
55
+ constraint control_events_outcome check(outcome='succeeded')
56
+ );
57
+
58
+ create table agent_bridge.owner_control_attestations (
59
+ name text primary key,
60
+ catalog_definition text not null,
61
+ attested_at timestamptz not null default now(),
62
+ constraint owner_control_attestation_name check(name ~ '^owner-control-v[0-9]+$')
63
+ );
64
+
65
+ create table agent_bridge.control_membership_events (
66
+ sequence bigint generated always as identity primary key,
67
+ request_id uuid not null unique,
68
+ action text not null,
69
+ member_role name not null,
70
+ control_role text not null,
71
+ actor name not null,
72
+ created_at timestamptz not null default clock_timestamp(),
73
+ constraint control_membership_events_action check(action in ('register','revoke')),
74
+ constraint control_membership_events_role check(control_role in ('operator','auditor'))
75
+ );
76
+
77
+ create index control_events_workspace_sequence
78
+ on agent_bridge.control_events(workspace_id,sequence desc);
79
+ create index control_membership_events_member_sequence
80
+ on agent_bridge.control_membership_events(member_role,control_role,sequence desc);
81
+ create index credentials_inventory_global
82
+ on agent_bridge.credentials(
83
+ date_bin('1 millisecond',created_at,'2000-01-01 00:00:00+00'::timestamptz),id
84
+ );
85
+ create index credentials_inventory_workspace
86
+ on agent_bridge.credentials(
87
+ workspace_id,
88
+ date_bin('1 millisecond',created_at,'2000-01-01 00:00:00+00'::timestamptz),id
89
+ );
90
+
91
+ create or replace function agent_bridge.reject_control_ledger_mutation()
92
+ returns trigger language plpgsql set search_path = '' as $$
93
+ begin
94
+ raise exception 'control ledgers are append-only';
95
+ end
96
+ $$;
97
+
98
+ create trigger control_requests_append_only
99
+ before update or delete or truncate on agent_bridge.control_requests
100
+ for each statement execute function agent_bridge.reject_control_ledger_mutation();
101
+ create trigger control_events_append_only
102
+ before update or delete or truncate on agent_bridge.control_events
103
+ for each statement execute function agent_bridge.reject_control_ledger_mutation();
104
+ create trigger owner_control_attestations_append_only
105
+ before update or delete or truncate on agent_bridge.owner_control_attestations
106
+ for each statement execute function agent_bridge.reject_control_ledger_mutation();
107
+ create trigger control_membership_events_append_only
108
+ before update or delete or truncate on agent_bridge.control_membership_events
109
+ for each statement execute function agent_bridge.reject_control_ledger_mutation();
110
+
111
+ alter table agent_bridge.credentials alter column scopes set default '{}'::text[];
112
+
113
+ create or replace function agent_bridge.assert_control_actor(requested_capability text)
114
+ returns void language plpgsql security definer set search_path = '' as $$
115
+ declare
116
+ suffix text := substr(md5(current_database()),1,16);
117
+ schema_owner name;
118
+ operator_role name := ('agent_bridge_control_operator_'||suffix)::name;
119
+ auditor_role name := ('agent_bridge_control_auditor_'||suffix)::name;
120
+ operator_allowed boolean := false;
121
+ auditor_allowed boolean := false;
122
+ begin
123
+ select pg_catalog.pg_get_userbyid(namespace.nspowner)::name into schema_owner
124
+ from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge';
125
+ if requested_capability is null or requested_capability not in ('operator','inventory') then
126
+ raise exception 'control capability is invalid';
127
+ end if;
128
+
129
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
130
+ 'control-membership'||chr(31)||session_user||chr(31)||'global',1646705661
131
+ ));
132
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
133
+ 'control-membership'||chr(31)||session_user||chr(31)||'operator',1646705661
134
+ ));
135
+ if requested_capability='inventory' then
136
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
137
+ 'control-membership'||chr(31)||session_user||chr(31)||'auditor',1646705661
138
+ ));
139
+ end if;
140
+
141
+ with latest as (
142
+ select distinct on (event.control_role) event.control_role,event.action
143
+ from agent_bridge.control_membership_events event
144
+ where event.member_role=session_user and event.control_role in ('operator','auditor')
145
+ order by event.control_role,event.sequence desc
146
+ ), direct_membership as (
147
+ select granted.rolname control_role,membership.admin_option,
148
+ coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
149
+ coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option,
150
+ grantor.rolname grantor_role
151
+ from pg_catalog.pg_auth_members membership
152
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
153
+ join pg_catalog.pg_roles member on member.oid=membership.member
154
+ join pg_catalog.pg_roles grantor on grantor.oid=membership.grantor
155
+ where member.rolname=session_user and granted.rolname in (operator_role,auditor_role)
156
+ )
157
+ select
158
+ coalesce(bool_or(membership.control_role=operator_role
159
+ and membership.inherit_option and membership.set_option
160
+ and membership.grantor_role=schema_owner and (
161
+ (session_user=schema_owner and membership.admin_option)
162
+ or (latest.control_role='operator' and latest.action='register'
163
+ and not membership.admin_option)
164
+ )),false),
165
+ coalesce(bool_or(membership.control_role=auditor_role
166
+ and membership.inherit_option and membership.set_option
167
+ and membership.grantor_role=schema_owner and (
168
+ (session_user=schema_owner and membership.admin_option)
169
+ or (latest.control_role='auditor' and latest.action='register'
170
+ and not membership.admin_option)
171
+ )),false)
172
+ into operator_allowed,auditor_allowed
173
+ from direct_membership membership left join latest on latest.control_role=case
174
+ membership.control_role when operator_role then 'operator' else 'auditor' end;
175
+
176
+ if session_user<>schema_owner and (exists (
177
+ select 1 from pg_catalog.pg_roles inherited
178
+ where inherited.rolname<>session_user
179
+ and pg_catalog.pg_has_role(session_user,inherited.rolname,'MEMBER')
180
+ and not (
181
+ (inherited.rolname=operator_role and operator_allowed)
182
+ or (inherited.rolname=auditor_role and auditor_allowed)
183
+ )
184
+ ) or exists (
185
+ select 1 from pg_catalog.pg_roles candidate
186
+ where candidate.rolname not in (session_user,schema_owner)
187
+ and not candidate.rolsuper
188
+ and pg_catalog.pg_has_role(candidate.rolname,session_user,'MEMBER')
189
+ ) or exists (
190
+ select 1 from pg_catalog.pg_roles actor
191
+ where actor.rolname=session_user and (
192
+ not actor.rolcanlogin or actor.rolsuper or actor.rolcreaterole or actor.rolcreatedb
193
+ or actor.rolreplication or actor.rolbypassrls
194
+ )
195
+ )) then
196
+ raise exception 'control actor has an unsafe membership graph';
197
+ end if;
198
+
199
+ if (requested_capability='operator' and not operator_allowed)
200
+ or (requested_capability='inventory' and not (operator_allowed or auditor_allowed)) then
201
+ raise exception 'control actor is not registered for requested capability';
202
+ end if;
203
+ end
204
+ $$;
205
+
206
+ create or replace function agent_bridge.validate_credential_security()
207
+ returns trigger
208
+ language plpgsql set search_path = '' as $$
209
+ declare
210
+ predecessor_workspace_id text;
211
+ predecessor_agent_id uuid;
212
+ profile_scopes text[];
213
+ lifecycle text := coalesce(current_setting('agent_bridge.lifecycle_authorized',true),'');
214
+ begin
215
+ if tg_op='UPDATE' then
216
+ if old.id is distinct from new.id
217
+ or old.workspace_id is distinct from new.workspace_id
218
+ or old.agent_id is distinct from new.agent_id
219
+ or old.token_hash is distinct from new.token_hash
220
+ or old.scopes is distinct from new.scopes
221
+ or old.scope_set_name is distinct from new.scope_set_name
222
+ or old.label is distinct from new.label
223
+ or old.expires_at is distinct from new.expires_at
224
+ or old.created_at is distinct from new.created_at then
225
+ raise exception 'credential issued state is immutable';
226
+ end if;
227
+ if old.replaces_credential_id is distinct from new.replaces_credential_id then
228
+ raise exception 'credential replacement lineage is immutable';
229
+ end if;
230
+ if old.revoked_at is not null and (
231
+ old.revoked_at is distinct from new.revoked_at
232
+ or old.revoked_by is distinct from new.revoked_by
233
+ or old.revocation_reason is distinct from new.revocation_reason
234
+ ) then raise exception 'credential revocation is immutable'; end if;
235
+ if old.revoked_at is null and new.revoked_at is not null and lifecycle<>'revocation' then
236
+ raise exception 'credential revocation requires an owner lifecycle function';
237
+ end if;
238
+ if (old.revoked_by is distinct from new.revoked_by
239
+ or old.revocation_reason is distinct from new.revocation_reason) and lifecycle<>'revocation' then
240
+ raise exception 'credential revocation metadata requires an owner lifecycle function';
241
+ end if;
242
+ if old.expiry_grace_until is distinct from new.expiry_grace_until and lifecycle<>'replacement' then
243
+ raise exception 'credential grace requires an owner lifecycle function';
244
+ end if;
245
+ if old.expiry_grace_until is not null and (
246
+ new.expiry_grace_until is null or new.expiry_grace_until>old.expiry_grace_until
247
+ ) then raise exception 'credential grace may only move earlier'; end if;
248
+ end if;
249
+ if tg_op='INSERT' then
250
+ if new.scope_set_name is null then
251
+ if new.scopes<>'{}'::text[] then
252
+ raise exception 'credential scopes must match a named scope set';
253
+ end if;
254
+ else
255
+ select scopes into profile_scopes from agent_bridge.credential_scope_sets
256
+ where name=new.scope_set_name;
257
+ if not found or profile_scopes is distinct from new.scopes then
258
+ raise exception 'credential scopes must match a named scope set';
259
+ end if;
260
+ end if;
261
+ if new.label is not null and (length(new.label) not between 1 and 128 or new.label ~ '[[:cntrl:]]') then
262
+ raise exception 'credential label is invalid';
263
+ end if;
264
+ end if;
265
+ if new.replaces_credential_id is not null then
266
+ select workspace_id,agent_id into predecessor_workspace_id,predecessor_agent_id
267
+ from agent_bridge.credentials where id=new.replaces_credential_id;
268
+ if not found or predecessor_workspace_id is distinct from new.workspace_id
269
+ or predecessor_agent_id is distinct from new.agent_id then
270
+ raise exception 'replacement credentials must use the same workspace and agent';
271
+ end if;
272
+ if tg_op='INSERT' and lifecycle<>'replacement' then
273
+ raise exception 'credential replacement requires an owner lifecycle function';
274
+ end if;
275
+ end if;
276
+ if tg_op='INSERT' and (new.revoked_at is not null or new.revoked_by is not null
277
+ or new.revocation_reason is not null or new.expiry_grace_until is not null) then
278
+ raise exception 'new credentials cannot begin with lifecycle state';
279
+ end if;
280
+ if new.expiry_grace_until is not null and not exists (
281
+ select 1 from agent_bridge.credentials successor where successor.replaces_credential_id=new.id
282
+ ) then raise exception 'credential grace requires a successor'; end if;
283
+ return new;
284
+ end
285
+ $$;
286
+
287
+ create or replace function agent_bridge.control_provision(
288
+ requested_request_id uuid, requested_workspace_id text, requested_workspace_name text,
289
+ requested_principal text, requested_display_name text, requested_runtime_type text,
290
+ requested_token_hash char(64), requested_label text, requested_scope_set_name text,
291
+ requested_expires_at timestamptz
292
+ ) returns table(workspace_id text,agent_id uuid,credential_id uuid,replayed boolean)
293
+ language plpgsql security definer set search_path = '' as $$
294
+ declare
295
+ canonical_fingerprint char(64);
296
+ prior agent_bridge.control_requests%rowtype;
297
+ provisioned_agent_id uuid;
298
+ provisioned_credential_id uuid;
299
+ canonical_scopes text[];
300
+ existing_workspace_name text;
301
+ begin
302
+ perform agent_bridge.assert_control_actor('operator');
303
+ if requested_request_id is null or requested_workspace_id is null
304
+ or requested_workspace_id<>btrim(requested_workspace_id)
305
+ or length(requested_workspace_id) not between 1 and 128
306
+ or requested_workspace_id ~ '[[:cntrl:]]'
307
+ or requested_workspace_name is null or requested_workspace_name<>btrim(requested_workspace_name)
308
+ or length(requested_workspace_name) not between 1 and 128
309
+ or requested_workspace_name ~ '[[:cntrl:]]'
310
+ or requested_principal is null or requested_principal<>btrim(requested_principal)
311
+ or length(requested_principal) not between 1 and 128
312
+ or requested_principal ~ '[[:cntrl:]]'
313
+ or (requested_display_name is not null and (
314
+ requested_display_name<>btrim(requested_display_name)
315
+ or length(requested_display_name) not between 1 and 128
316
+ or requested_display_name ~ '[[:cntrl:]]'))
317
+ or (requested_runtime_type is not null and (
318
+ requested_runtime_type<>btrim(requested_runtime_type)
319
+ or length(requested_runtime_type) not between 1 and 128
320
+ or requested_runtime_type ~ '[[:cntrl:]]'))
321
+ or (requested_label is not null and (
322
+ requested_label<>btrim(requested_label) or length(requested_label) not between 1 and 128
323
+ or requested_label ~ '[[:cntrl:]]'))
324
+ or requested_scope_set_name is null
325
+ or requested_scope_set_name<>btrim(requested_scope_set_name)
326
+ or length(requested_scope_set_name) not between 1 and 128
327
+ or requested_scope_set_name ~ '[[:cntrl:]]'
328
+ or requested_token_hash is null
329
+ or requested_token_hash::text !~ '^[0-9a-f]{64}$'
330
+ then raise exception 'invalid provisioning request'; end if;
331
+ perform pg_catalog.pg_advisory_xact_lock(
332
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
333
+ );
334
+ canonical_fingerprint := pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(pg_catalog.jsonb_build_array(
335
+ requested_workspace_id,requested_workspace_name,requested_principal,requested_display_name,
336
+ requested_runtime_type,requested_token_hash::text,requested_label,requested_scope_set_name,
337
+ case when requested_expires_at is null then null else
338
+ trunc(extract(epoch from requested_expires_at)*1000000)::numeric end)::text,'UTF8')),'hex');
339
+ select * into prior from agent_bridge.control_requests where request_id=requested_request_id;
340
+ if found then
341
+ if prior.operation<>'provision' or prior.fingerprint<>canonical_fingerprint then
342
+ raise exception 'request id was already used with different content';
343
+ end if;
344
+ return query select prior.result->>'workspace_id',(prior.result->>'agent_id')::uuid,
345
+ (prior.result->>'credential_id')::uuid,true;
346
+ return;
347
+ end if;
348
+ if requested_expires_at is not null and requested_expires_at<=clock_timestamp() then
349
+ raise exception 'invalid provisioning request';
350
+ end if;
351
+ select scopes into canonical_scopes from agent_bridge.credential_scope_sets
352
+ where name=requested_scope_set_name;
353
+ if not found then raise exception 'unknown credential scope set'; end if;
354
+ insert into agent_bridge.workspaces(id,name) values(requested_workspace_id,requested_workspace_name)
355
+ on conflict(id) do nothing;
356
+ select name into existing_workspace_name from agent_bridge.workspaces
357
+ where id=requested_workspace_id;
358
+ if existing_workspace_name is distinct from requested_workspace_name then
359
+ raise exception 'workspace id already has a different name';
360
+ end if;
361
+ insert into agent_bridge.agents(workspace_id,principal,display_name,runtime_type)
362
+ values(requested_workspace_id,requested_principal,requested_display_name,requested_runtime_type)
363
+ returning id into provisioned_agent_id;
364
+ perform set_config('agent_bridge.lifecycle_authorized','provision',true);
365
+ insert into agent_bridge.credentials(workspace_id,agent_id,token_hash,label,expires_at,scopes,scope_set_name)
366
+ values(requested_workspace_id,provisioned_agent_id,requested_token_hash,requested_label,
367
+ requested_expires_at,canonical_scopes,requested_scope_set_name)
368
+ returning id into provisioned_credential_id;
369
+ perform set_config('agent_bridge.lifecycle_authorized','',true);
370
+ insert into agent_bridge.control_requests(request_id,operation,fingerprint,actor,result)
371
+ values(requested_request_id,'provision',canonical_fingerprint,session_user,
372
+ jsonb_build_object('workspace_id',requested_workspace_id,'agent_id',provisioned_agent_id,
373
+ 'credential_id',provisioned_credential_id));
374
+ insert into agent_bridge.control_events(request_id,operation,outcome,actor,workspace_id,principal,credential_id)
375
+ values(requested_request_id,'provision','succeeded',session_user,requested_workspace_id,
376
+ requested_principal,provisioned_credential_id);
377
+ return query select requested_workspace_id,provisioned_agent_id,provisioned_credential_id,false;
378
+ exception when unique_violation then
379
+ select * into prior from agent_bridge.control_requests where request_id=requested_request_id;
380
+ if found and prior.operation='provision' and prior.fingerprint=canonical_fingerprint then
381
+ return query select prior.result->>'workspace_id',(prior.result->>'agent_id')::uuid,
382
+ (prior.result->>'credential_id')::uuid,true;
383
+ return;
384
+ end if;
385
+ raise exception using errcode='23505',message='provisioning request conflicts with existing state';
386
+ end
387
+ $$;
388
+
389
+ create or replace function agent_bridge.control_credential_inventory(
390
+ requested_workspace_id text default null,
391
+ requested_after_created_at timestamptz default null,
392
+ requested_after_credential_id uuid default null,
393
+ requested_limit integer default 100
394
+ )
395
+ returns table(credential_id uuid,workspace_id text,principal text,label text,scopes text[],scope_set_name text,
396
+ expires_at timestamptz,revoked_at timestamptz,revoked_by text,revocation_reason text,
397
+ replaces_credential_id uuid,expiry_grace_until timestamptz,created_at timestamptz,last_used_at timestamptz,
398
+ agent_disabled_at timestamptz,workspace_disabled_at timestamptz)
399
+ language plpgsql stable security definer set search_path = '' as $$
400
+ begin
401
+ perform agent_bridge.assert_control_actor('inventory');
402
+ if requested_limit is null or requested_limit not between 1 and 1000
403
+ or (requested_after_created_at is null)<>(requested_after_credential_id is null)
404
+ or (requested_workspace_id is not null and (
405
+ requested_workspace_id<>btrim(requested_workspace_id)
406
+ or length(requested_workspace_id) not between 1 and 128
407
+ or requested_workspace_id ~ '[[:cntrl:]]')) then
408
+ raise exception 'credential inventory cursor or limit is invalid';
409
+ end if;
410
+ return query select credential.id,credential.workspace_id,agent.principal,credential.label,credential.scopes,
411
+ credential.scope_set_name,credential.expires_at,credential.revoked_at,credential.revoked_by,
412
+ credential.revocation_reason,credential.replaces_credential_id,credential.expiry_grace_until,
413
+ credential.created_at,credential.last_used_at,agent.disabled_at,workspace.disabled_at
414
+ from agent_bridge.credentials credential
415
+ join agent_bridge.agents agent on agent.id=credential.agent_id and agent.workspace_id=credential.workspace_id
416
+ join agent_bridge.workspaces workspace on workspace.id=credential.workspace_id
417
+ where (requested_workspace_id is null or credential.workspace_id=requested_workspace_id)
418
+ and (requested_after_created_at is null
419
+ or (date_bin('1 millisecond',credential.created_at,
420
+ '2000-01-01 00:00:00+00'::timestamptz),credential.id)>
421
+ (date_bin('1 millisecond',requested_after_created_at,
422
+ '2000-01-01 00:00:00+00'::timestamptz),requested_after_credential_id))
423
+ order by date_bin('1 millisecond',credential.created_at,
424
+ '2000-01-01 00:00:00+00'::timestamptz),credential.id
425
+ limit requested_limit;
426
+ end
427
+ $$;
428
+
429
+ create or replace function agent_bridge.control_rotate_credential(
430
+ requested_request_id uuid, requested_predecessor_id uuid, requested_workspace_id text,
431
+ requested_principal text, requested_token_hash char(64), requested_label text,
432
+ requested_scope_set_name text, requested_expires_at timestamptz, requested_grace_until timestamptz
433
+ ) returns table(credential_id uuid,workspace_id text,principal text,replayed boolean)
434
+ language plpgsql security definer set search_path = '' as $$
435
+ declare
436
+ canonical_fingerprint char(64);
437
+ prior agent_bridge.control_requests%rowtype;
438
+ predecessor_workspace_id text;
439
+ predecessor_agent_id uuid;
440
+ predecessor_expires_at timestamptz;
441
+ successor_id uuid;
442
+ canonical_scopes text[];
443
+ principal_value text;
444
+ begin
445
+ perform agent_bridge.assert_control_actor('operator');
446
+ if requested_request_id is null or requested_predecessor_id is null
447
+ or requested_workspace_id is null or requested_workspace_id<>btrim(requested_workspace_id)
448
+ or length(requested_workspace_id) not between 1 and 128
449
+ or requested_workspace_id ~ '[[:cntrl:]]'
450
+ or requested_principal is null or requested_principal<>btrim(requested_principal)
451
+ or length(requested_principal) not between 1 and 128
452
+ or requested_principal ~ '[[:cntrl:]]'
453
+ or requested_token_hash is null
454
+ or requested_token_hash::text !~ '^[0-9a-f]{64}$'
455
+ or (requested_label is not null and (
456
+ requested_label<>btrim(requested_label) or length(requested_label) not between 1 and 128
457
+ or requested_label ~ '[[:cntrl:]]'))
458
+ or requested_scope_set_name is null
459
+ or requested_scope_set_name<>btrim(requested_scope_set_name)
460
+ or length(requested_scope_set_name) not between 1 and 128
461
+ or requested_scope_set_name ~ '[[:cntrl:]]' then
462
+ raise exception 'invalid credential rotation request';
463
+ end if;
464
+ perform pg_catalog.pg_advisory_xact_lock(
465
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
466
+ );
467
+ canonical_fingerprint := pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(pg_catalog.jsonb_build_array(requested_predecessor_id,
468
+ requested_workspace_id,requested_principal,requested_token_hash::text,requested_label,requested_scope_set_name,
469
+ case when requested_expires_at is null then null else
470
+ trunc(extract(epoch from requested_expires_at)*1000000)::numeric end,
471
+ case when requested_grace_until is null then null else
472
+ trunc(extract(epoch from requested_grace_until)*1000000)::numeric end)::text,'UTF8')),'hex');
473
+ select * into prior from agent_bridge.control_requests where request_id=requested_request_id;
474
+ if found then
475
+ if prior.operation<>'rotate' or prior.fingerprint<>canonical_fingerprint then
476
+ raise exception 'request id was already used with different content'; end if;
477
+ return query select (prior.result->>'credential_id')::uuid,prior.result->>'workspace_id',
478
+ prior.result->>'principal',true;
479
+ return;
480
+ end if;
481
+ select credential.workspace_id,credential.agent_id,credential.expires_at,agent.principal
482
+ into predecessor_workspace_id,predecessor_agent_id,predecessor_expires_at,principal_value
483
+ from agent_bridge.credentials credential join agent_bridge.agents agent
484
+ on agent.id=credential.agent_id and agent.workspace_id=credential.workspace_id
485
+ join agent_bridge.workspaces workspace on workspace.id=credential.workspace_id
486
+ where credential.id=requested_predecessor_id
487
+ and credential.workspace_id=requested_workspace_id
488
+ and agent.principal=requested_principal
489
+ and credential.revoked_at is null
490
+ and (credential.expires_at is null or credential.expires_at>clock_timestamp())
491
+ and agent.disabled_at is null
492
+ and workspace.disabled_at is null
493
+ and not exists(select 1 from agent_bridge.credentials successor
494
+ where successor.replaces_credential_id=credential.id)
495
+ for update of credential;
496
+ if not found then raise exception 'predecessor credential is not active and replaceable'; end if;
497
+ if (requested_expires_at is not null and requested_expires_at<=clock_timestamp())
498
+ or (requested_grace_until is not null and predecessor_expires_at is not null
499
+ and requested_grace_until>predecessor_expires_at)
500
+ or (requested_grace_until is not null and requested_expires_at is not null
501
+ and requested_grace_until>requested_expires_at)
502
+ or (requested_grace_until is not null and requested_grace_until<=clock_timestamp()) then
503
+ raise exception 'credential grace is invalid'; end if;
504
+ select scopes into canonical_scopes from agent_bridge.credential_scope_sets
505
+ where name=requested_scope_set_name;
506
+ if not found then raise exception 'unknown credential scope set'; end if;
507
+ perform set_config('agent_bridge.lifecycle_authorized','replacement',true);
508
+ insert into agent_bridge.credentials(workspace_id,agent_id,token_hash,label,expires_at,scopes,
509
+ scope_set_name,replaces_credential_id) values(predecessor_workspace_id,predecessor_agent_id,
510
+ requested_token_hash,requested_label,requested_expires_at,canonical_scopes,
511
+ requested_scope_set_name,requested_predecessor_id) returning id into successor_id;
512
+ update agent_bridge.credentials set expiry_grace_until=requested_grace_until
513
+ where id=requested_predecessor_id;
514
+ perform set_config('agent_bridge.lifecycle_authorized','',true);
515
+ insert into agent_bridge.control_requests(request_id,operation,fingerprint,actor,result)
516
+ values(requested_request_id,'rotate',canonical_fingerprint,session_user,
517
+ jsonb_build_object('credential_id',successor_id,'workspace_id',predecessor_workspace_id,
518
+ 'principal',principal_value));
519
+ insert into agent_bridge.control_events(request_id,operation,outcome,actor,workspace_id,principal,
520
+ credential_id,related_credential_id) values(requested_request_id,'rotate','succeeded',session_user,
521
+ predecessor_workspace_id,principal_value,successor_id,requested_predecessor_id);
522
+ return query select successor_id,predecessor_workspace_id,principal_value,false;
523
+ exception when unique_violation then
524
+ raise exception using errcode='23505',message='credential rotation conflicts with existing state';
525
+ end
526
+ $$;
527
+
528
+ create or replace function agent_bridge.control_revoke_credential(
529
+ requested_request_id uuid, requested_credential_id uuid, requested_reason_code text
530
+ ) returns table(revoked boolean,replayed boolean)
531
+ language plpgsql security definer set search_path = '' as $$
532
+ declare
533
+ canonical_fingerprint char(64);
534
+ prior agent_bridge.control_requests%rowtype;
535
+ workspace_value text;
536
+ principal_value text;
537
+ begin
538
+ perform agent_bridge.assert_control_actor('operator');
539
+ if requested_request_id is null or requested_credential_id is null
540
+ or requested_reason_code is null
541
+ or requested_reason_code not in ('operator_request','rotation','compromise','retired') then
542
+ raise exception 'credential revocation reason is invalid'; end if;
543
+ perform pg_catalog.pg_advisory_xact_lock(
544
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
545
+ );
546
+ canonical_fingerprint := pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(
547
+ pg_catalog.jsonb_build_array(requested_credential_id,requested_reason_code)::text,'UTF8')),'hex');
548
+ select * into prior from agent_bridge.control_requests where request_id=requested_request_id;
549
+ if found then
550
+ if prior.operation<>'revoke' or prior.fingerprint<>canonical_fingerprint then
551
+ raise exception 'request id was already used with different content'; end if;
552
+ return query select (prior.result->>'revoked')::boolean,true; return;
553
+ end if;
554
+ select credential.workspace_id,agent.principal into workspace_value,principal_value
555
+ from agent_bridge.credentials credential join agent_bridge.agents agent
556
+ on agent.id=credential.agent_id and agent.workspace_id=credential.workspace_id
557
+ where credential.id=requested_credential_id for update of credential;
558
+ if not found then raise exception 'credential was not found'; end if;
559
+ perform set_config('agent_bridge.lifecycle_authorized','revocation',true);
560
+ update agent_bridge.credentials set revoked_at=clock_timestamp(),revoked_by=session_user,
561
+ revocation_reason=requested_reason_code where id=requested_credential_id and revoked_at is null;
562
+ if not found then raise exception 'credential is already revoked'; end if;
563
+ perform set_config('agent_bridge.lifecycle_authorized','',true);
564
+ insert into agent_bridge.control_requests(request_id,operation,fingerprint,actor,result)
565
+ values(requested_request_id,'revoke',canonical_fingerprint,session_user,jsonb_build_object('revoked',true));
566
+ insert into agent_bridge.control_events(request_id,operation,outcome,actor,workspace_id,principal,
567
+ credential_id,reason_code) values(requested_request_id,'revoke','succeeded',session_user,
568
+ workspace_value,principal_value,requested_credential_id,requested_reason_code);
569
+ return query select true,false;
570
+ end
571
+ $$;
572
+
573
+ create or replace function agent_bridge.register_control_member(
574
+ requested_request_id uuid, requested_member_role name, requested_control_role text
575
+ ) returns table(replayed boolean)
576
+ language plpgsql security definer set search_path = '' as $$
577
+ declare
578
+ suffix text := substr(md5(current_database()),1,16);
579
+ target_role name;
580
+ prior agent_bridge.control_membership_events%rowtype;
581
+ member_record pg_catalog.pg_roles%rowtype;
582
+ begin
583
+ if requested_request_id is null or requested_member_role is null
584
+ or requested_control_role is null
585
+ or requested_control_role not in ('operator','auditor') then
586
+ raise exception 'invalid control membership registration';
587
+ end if;
588
+ target_role := ('agent_bridge_control_'||requested_control_role||'_'||suffix)::name;
589
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
590
+ 'control-membership'||chr(31)||requested_member_role||chr(31)||'global',
591
+ 1646705661
592
+ ));
593
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
594
+ 'control-membership'||chr(31)||requested_member_role||chr(31)||requested_control_role,
595
+ 1646705661
596
+ ));
597
+ perform pg_catalog.pg_advisory_xact_lock(
598
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
599
+ );
600
+ select * into prior from agent_bridge.control_membership_events
601
+ where request_id=requested_request_id;
602
+ if found then
603
+ if prior.action<>'register' or prior.member_role<>requested_member_role
604
+ or prior.control_role<>requested_control_role then
605
+ raise exception 'request id was already used with different content';
606
+ end if;
607
+ return query select true;
608
+ return;
609
+ end if;
610
+ select * into member_record from pg_catalog.pg_roles where rolname=requested_member_role;
611
+ if not found or not member_record.rolcanlogin or member_record.rolsuper
612
+ or member_record.rolcreaterole or member_record.rolcreatedb
613
+ or member_record.rolreplication or member_record.rolbypassrls
614
+ or requested_member_role in (
615
+ current_user::name,
616
+ ('agent_bridge_control_owner_'||suffix)::name,
617
+ ('agent_bridge_control_operator_'||suffix)::name,
618
+ ('agent_bridge_control_auditor_'||suffix)::name
619
+ ) then
620
+ raise exception 'control membership target is not an eligible login';
621
+ end if;
622
+ if exists (
623
+ select 1 from pg_catalog.pg_roles inherited
624
+ where inherited.rolname<>requested_member_role
625
+ and pg_catalog.pg_has_role(requested_member_role,inherited.rolname,'MEMBER')
626
+ and inherited.rolname<>target_role
627
+ and not exists (
628
+ select 1 from (
629
+ select distinct on (event.control_role)
630
+ event.control_role,event.action
631
+ from agent_bridge.control_membership_events event
632
+ where event.member_role=requested_member_role
633
+ order by event.control_role,event.sequence desc
634
+ ) latest
635
+ where latest.action='register'
636
+ and inherited.rolname=(
637
+ 'agent_bridge_control_'||latest.control_role||'_'||suffix
638
+ )::name
639
+ )
640
+ ) or exists (
641
+ select 1 from pg_catalog.pg_roles candidate
642
+ where candidate.rolname not in (requested_member_role,current_user)
643
+ and not candidate.rolsuper
644
+ and pg_catalog.pg_has_role(candidate.rolname,requested_member_role,'MEMBER')
645
+ ) then
646
+ raise exception 'control membership target has an unsafe membership graph';
647
+ end if;
648
+ if exists (
649
+ select 1 from pg_catalog.pg_auth_members membership
650
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
651
+ join pg_catalog.pg_roles member on member.oid=membership.member
652
+ join pg_catalog.pg_roles grantor on grantor.oid=membership.grantor
653
+ where granted.rolname=target_role and member.rolname=requested_member_role
654
+ and (
655
+ membership.admin_option
656
+ or not coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
657
+ or not coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
658
+ or grantor.rolname<>current_user
659
+ )
660
+ ) then
661
+ raise exception 'control membership target has an unsafe direct grant';
662
+ end if;
663
+ execute format('grant %I to %I',target_role,requested_member_role);
664
+ insert into agent_bridge.control_membership_events(
665
+ request_id,action,member_role,control_role,actor
666
+ ) values (
667
+ requested_request_id,'register',requested_member_role,requested_control_role,session_user
668
+ );
669
+ return query select false;
670
+ end
671
+ $$;
672
+
673
+ create or replace function agent_bridge.revoke_control_member(
674
+ requested_request_id uuid, requested_member_role name, requested_control_role text
675
+ ) returns table(replayed boolean)
676
+ language plpgsql security definer set search_path = '' as $$
677
+ declare
678
+ suffix text := substr(md5(current_database()),1,16);
679
+ target_role name;
680
+ prior agent_bridge.control_membership_events%rowtype;
681
+ begin
682
+ if requested_request_id is null or requested_member_role is null
683
+ or requested_control_role is null
684
+ or requested_control_role not in ('operator','auditor')
685
+ or requested_member_role in (
686
+ current_user::name,
687
+ ('agent_bridge_control_owner_'||suffix)::name,
688
+ ('agent_bridge_control_operator_'||suffix)::name,
689
+ ('agent_bridge_control_auditor_'||suffix)::name
690
+ ) then
691
+ raise exception 'invalid control membership revocation';
692
+ end if;
693
+ target_role := ('agent_bridge_control_'||requested_control_role||'_'||suffix)::name;
694
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
695
+ 'control-membership'||chr(31)||requested_member_role||chr(31)||'global',
696
+ 1646705661
697
+ ));
698
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
699
+ 'control-membership'||chr(31)||requested_member_role||chr(31)||requested_control_role,
700
+ 1646705661
701
+ ));
702
+ perform pg_catalog.pg_advisory_xact_lock(
703
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
704
+ );
705
+ select * into prior from agent_bridge.control_membership_events
706
+ where request_id=requested_request_id;
707
+ if found then
708
+ if prior.action<>'revoke' or prior.member_role<>requested_member_role
709
+ or prior.control_role<>requested_control_role then
710
+ raise exception 'request id was already used with different content';
711
+ end if;
712
+ return query select true;
713
+ return;
714
+ end if;
715
+ if exists(select 1 from pg_catalog.pg_roles where rolname=requested_member_role) then
716
+ execute format('revoke %I from %I',target_role,requested_member_role);
717
+ end if;
718
+ insert into agent_bridge.control_membership_events(
719
+ request_id,action,member_role,control_role,actor
720
+ ) values (
721
+ requested_request_id,'revoke',requested_member_role,requested_control_role,session_user
722
+ );
723
+ return query select false;
724
+ end
725
+ $$;
726
+
727
+ create or replace function agent_bridge.current_request_workspace() returns text
728
+ language sql stable security definer set search_path = '' as $$
729
+ select authority.workspace_id
730
+ from agent_bridge.request_authorities authority
731
+ where pg_catalog.pg_has_role(
732
+ session_user,
733
+ ('agent_bridge_runtime_' || substr(pg_catalog.md5(pg_catalog.current_database()),1,16))::name,
734
+ 'MEMBER'
735
+ )
736
+ and authority.backend_pid=pg_catalog.pg_backend_pid()
737
+ and authority.transaction_id=pg_catalog.pg_current_xact_id_if_assigned()
738
+ and authority.opened_session_user=session_user
739
+ $$;
740
+
741
+ create or replace function agent_bridge.current_request_principal() returns text
742
+ language sql stable security definer set search_path = '' as $$
743
+ select authority.principal
744
+ from agent_bridge.request_authorities authority
745
+ where pg_catalog.pg_has_role(
746
+ session_user,
747
+ ('agent_bridge_runtime_' || substr(pg_catalog.md5(pg_catalog.current_database()),1,16))::name,
748
+ 'MEMBER'
749
+ )
750
+ and authority.backend_pid=pg_catalog.pg_backend_pid()
751
+ and authority.transaction_id=pg_catalog.pg_current_xact_id_if_assigned()
752
+ and authority.opened_session_user=session_user
753
+ $$;
754
+
755
+ create or replace function agent_bridge.row_isolation_catalog_definition() returns text
756
+ language sql stable set search_path = '' as $$
757
+ with domain_tables(table_name) as (
758
+ values ('messages'),('receipts'),('deliveries'),('delivery_events'),('agent_instances')
759
+ ), catalog_objects(kind,identity,definition) as (
760
+ select 'policy',relation.relname || '.' || policy.polname,
761
+ policy.polcmd::text || ':' || policy.polpermissive::text || ':' ||
762
+ coalesce(pg_catalog.pg_get_expr(policy.polqual,policy.polrelid),'') || ':' ||
763
+ coalesce(pg_catalog.pg_get_expr(policy.polwithcheck,policy.polrelid),'')
764
+ from pg_catalog.pg_policy policy
765
+ join pg_catalog.pg_class relation on relation.oid=policy.polrelid
766
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
767
+ where namespace.nspname='agent_bridge'
768
+ and relation.relname in (select table_name from domain_tables)
769
+ union all
770
+ select 'constraint',constraint_record.conrelid::regclass::text || '.' || constraint_record.conname,
771
+ pg_catalog.pg_get_constraintdef(constraint_record.oid,true)
772
+ from pg_catalog.pg_constraint constraint_record
773
+ join pg_catalog.pg_class constraint_relation on constraint_relation.oid=constraint_record.conrelid
774
+ join pg_catalog.pg_namespace constraint_namespace on constraint_namespace.oid=constraint_relation.relnamespace
775
+ where constraint_namespace.nspname='agent_bridge'
776
+ and constraint_relation.relname in (
777
+ select table_name from domain_tables
778
+ union all select 'row_isolation_attestations'
779
+ )
780
+ union all
781
+ select 'trigger',trigger.tgrelid::regclass::text || '.' || trigger.tgname,
782
+ pg_catalog.pg_get_triggerdef(trigger.oid,true)
783
+ from pg_catalog.pg_trigger trigger
784
+ join pg_catalog.pg_class trigger_relation on trigger_relation.oid=trigger.tgrelid
785
+ join pg_catalog.pg_namespace trigger_namespace on trigger_namespace.oid=trigger_relation.relnamespace
786
+ where not trigger.tgisinternal
787
+ and trigger_namespace.nspname='agent_bridge'
788
+ and trigger_relation.relname in (select table_name from domain_tables)
789
+ union all
790
+ select 'function',procedure.oid::regprocedure::text,
791
+ pg_catalog.pg_get_userbyid(procedure.proowner) || ':' ||
792
+ pg_catalog.pg_get_functiondef(procedure.oid)
793
+ from pg_catalog.pg_proc procedure
794
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
795
+ where namespace.nspname='agent_bridge' and procedure.proname in (
796
+ 'current_request_workspace','current_request_principal','record_delivery_event',
797
+ 'reject_delivery_identity_mutation','enforce_delivery_actor_role',
798
+ 'reject_delivery_event_mutation','row_isolation_catalog_definition'
799
+ )
800
+ )
801
+ select string_agg(kind || E'\x1f' || identity || E'\x1f' || definition,E'\x1e'
802
+ order by kind,identity)
803
+ from catalog_objects
804
+ $$;
805
+
806
+ create or replace function agent_bridge.credential_security_prerequisite_definition()
807
+ returns text language sql stable set search_path = '' set timezone = 'UTC' as $$
808
+ with dependency_relations(relation_name) as (values
809
+ ('credentials'),('credential_scope_sets'),('security_events'),
810
+ ('rate_limit_policies'),('rate_limit_buckets')
811
+ ), protected_functions(function_name) as (values
812
+ ('canonicalize_scopes'),('validate_credential_security'),
813
+ ('reject_credential_delete'),('reject_scope_set_mutation'),
814
+ ('reject_security_event_mutation'),('record_scope_denial'),
815
+ ('consume_rate_limit'),('replace_credential'),('revoke_credential'),
816
+ ('security_schema_ready')
817
+ ), catalog_objects(kind,identity,definition) as (
818
+ select 'relation',relation.relname,
819
+ case when relation.relowner=namespace.nspowner then 'schema-owner' else 'unexpected-owner' end
820
+ ||':'||relation.relkind::text||':'||relation.relpersistence::text||':'||
821
+ relation.relrowsecurity::text||':'||relation.relforcerowsecurity::text
822
+ from pg_catalog.pg_class relation
823
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
824
+ where namespace.nspname='agent_bridge'
825
+ and relation.relname in (select relation_name from dependency_relations)
826
+ union all
827
+ select 'column',relation.relname||'.'||attribute.attname,
828
+ pg_catalog.format_type(attribute.atttypid,attribute.atttypmod)||':'||
829
+ attribute.attnotnull::text||':'||attribute.attidentity::text||':'||
830
+ attribute.attgenerated::text||':'||coalesce(pg_catalog.pg_get_expr(
831
+ default_record.adbin,default_record.adrelid),'')
832
+ from pg_catalog.pg_attribute attribute
833
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
834
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
835
+ left join pg_catalog.pg_attrdef default_record on default_record.adrelid=attribute.attrelid
836
+ and default_record.adnum=attribute.attnum
837
+ where namespace.nspname='agent_bridge'
838
+ and relation.relname in (select relation_name from dependency_relations)
839
+ and attribute.attnum>0 and not attribute.attisdropped
840
+ union all
841
+ select 'constraint',relation.relname||'.'||constraint_record.conname,
842
+ constraint_record.convalidated::text||':'||
843
+ pg_catalog.pg_get_constraintdef(constraint_record.oid,true)
844
+ from pg_catalog.pg_constraint constraint_record
845
+ join pg_catalog.pg_class relation on relation.oid=constraint_record.conrelid
846
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
847
+ where namespace.nspname='agent_bridge'
848
+ and relation.relname in (select relation_name from dependency_relations)
849
+ union all
850
+ select 'index',index_relation.relname,
851
+ index_record.indisunique::text||':'||index_record.indisvalid::text||':'||
852
+ index_record.indisready::text||':'||pg_catalog.pg_get_indexdef(index_record.indexrelid)||':'||
853
+ coalesce(pg_catalog.pg_get_expr(index_record.indpred,index_record.indrelid),'')
854
+ from pg_catalog.pg_index index_record
855
+ join pg_catalog.pg_class source_relation on source_relation.oid=index_record.indrelid
856
+ join pg_catalog.pg_class index_relation on index_relation.oid=index_record.indexrelid
857
+ join pg_catalog.pg_namespace namespace on namespace.oid=source_relation.relnamespace
858
+ where namespace.nspname='agent_bridge'
859
+ and source_relation.relname in (select relation_name from dependency_relations)
860
+ union all
861
+ select 'trigger',relation.relname||'.'||trigger.tgname,
862
+ trigger.tgenabled::text||':'||pg_catalog.pg_get_triggerdef(trigger.oid,true)
863
+ from pg_catalog.pg_trigger trigger
864
+ join pg_catalog.pg_class relation on relation.oid=trigger.tgrelid
865
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
866
+ where not trigger.tgisinternal and namespace.nspname='agent_bridge'
867
+ and relation.relname in (select relation_name from dependency_relations)
868
+ union all
869
+ select 'function',procedure.oid::regprocedure::text,
870
+ case when procedure.proowner=namespace.nspowner then 'schema-owner' else 'unexpected-owner' end
871
+ ||':'||pg_catalog.pg_get_functiondef(procedure.oid)
872
+ from pg_catalog.pg_proc procedure
873
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
874
+ where namespace.nspname='agent_bridge'
875
+ and procedure.proname in (select function_name from protected_functions)
876
+ )
877
+ select string_agg(kind||E'\x1f'||identity||E'\x1f'||definition,E'\x1e'
878
+ order by kind,identity,definition) from catalog_objects
879
+ $$;
880
+
881
+ create or replace function agent_bridge.owner_control_catalog_definition() returns text
882
+ language sql stable set search_path = '' set timezone = 'UTC' as $$
883
+ with names as (select
884
+ ('agent_bridge_control_owner_'||substr(md5(current_database()),1,16))::name owner_role,
885
+ ('agent_bridge_control_operator_'||substr(md5(current_database()),1,16))::name operator_role,
886
+ ('agent_bridge_control_auditor_'||substr(md5(current_database()),1,16))::name auditor_role,
887
+ ('agent_bridge_runtime_'||substr(md5(current_database()),1,16))::name runtime_role,
888
+ (select nspowner from pg_catalog.pg_namespace where nspname='agent_bridge') schema_owner
889
+ ), control_roles(role_name) as (
890
+ select owner_role from names union all select operator_role from names
891
+ union all select auditor_role from names
892
+ ), protected_functions(function_name) as (values
893
+ ('reject_control_ledger_mutation'),('validate_credential_security'),('control_provision'),
894
+ ('control_credential_inventory'),('control_rotate_credential'),('control_revoke_credential'),
895
+ ('owner_control_catalog_definition'),('owner_control_plane_ready'),('canonicalize_scopes'),
896
+ ('replace_credential'),('revoke_credential'),('security_schema_ready'),
897
+ ('validate_credential_security'),('reject_credential_delete'),
898
+ ('reject_scope_set_mutation'),('reject_security_event_mutation'),
899
+ ('record_scope_denial'),('consume_rate_limit'),
900
+ ('current_request_workspace'),('current_request_principal'),
901
+ ('row_isolation_catalog_definition'),('credential_security_prerequisite_definition'),
902
+ ('assert_control_actor'),('register_control_member'),('revoke_control_member')
903
+ ), dependency_relations(relation_name) as (values
904
+ ('control_requests'),('control_events'),('owner_control_attestations'),('workspaces'),
905
+ ('control_membership_events'),('agents'),('credentials'),('credential_scope_sets')
906
+ ), dependency_columns(relation_name,column_name) as (values
907
+ ('control_requests','request_id'),('control_requests','operation'),
908
+ ('control_requests','fingerprint'),('control_requests','actor'),
909
+ ('control_requests','result'),('control_requests','created_at'),
910
+ ('control_events','sequence'),('control_events','event_id'),
911
+ ('control_events','request_id'),('control_events','operation'),
912
+ ('control_events','outcome'),('control_events','actor'),
913
+ ('control_events','workspace_id'),('control_events','principal'),
914
+ ('control_events','credential_id'),('control_events','related_credential_id'),
915
+ ('control_events','reason_code'),('control_events','created_at'),
916
+ ('owner_control_attestations','name'),('owner_control_attestations','catalog_definition'),
917
+ ('owner_control_attestations','attested_at'),
918
+ ('control_membership_events','sequence'),('control_membership_events','request_id'),
919
+ ('control_membership_events','action'),('control_membership_events','member_role'),
920
+ ('control_membership_events','control_role'),('control_membership_events','actor'),
921
+ ('control_membership_events','created_at'),
922
+ ('workspaces','id'),('workspaces','name'),('workspaces','disabled_at'),
923
+ ('agents','id'),('agents','workspace_id'),('agents','principal'),
924
+ ('agents','display_name'),('agents','runtime_type'),('agents','disabled_at'),
925
+ ('credentials','id'),('credentials','workspace_id'),('credentials','agent_id'),
926
+ ('credentials','token_hash'),('credentials','label'),('credentials','expires_at'),
927
+ ('credentials','revoked_at'),('credentials','created_at'),('credentials','last_used_at'),
928
+ ('credentials','scopes'),('credentials','scope_set_name'),
929
+ ('credentials','replaces_credential_id'),('credentials','revoked_by'),
930
+ ('credentials','revocation_reason'),('credentials','expiry_grace_until'),
931
+ ('credential_scope_sets','name'),('credential_scope_sets','scopes')
932
+ ), catalog_objects(kind,identity,definition) as (
933
+ select 'role',role.rolname,
934
+ concat_ws(':',role.rolsuper,role.rolinherit,role.rolcreaterole,role.rolcreatedb,
935
+ role.rolcanlogin,role.rolreplication,role.rolbypassrls,role.rolconnlimit,
936
+ coalesce(role.rolvaliduntil::text,''),coalesce(role.rolconfig::text,''))
937
+ from pg_catalog.pg_roles role where role.rolname in (select role_name from control_roles)
938
+ union all
939
+ select 'membership',granted.rolname||'->'||member.rolname,
940
+ grantor.rolname||':'||membership.admin_option::text||':'||
941
+ coalesce(to_jsonb(membership)->>'inherit_option','')||':'||
942
+ coalesce(to_jsonb(membership)->>'set_option','')
943
+ from pg_catalog.pg_auth_members membership
944
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
945
+ join pg_catalog.pg_roles member on member.oid=membership.member
946
+ join pg_catalog.pg_roles grantor on grantor.oid=membership.grantor
947
+ where member.rolname in (select role_name from control_roles)
948
+ or granted.rolname=(select owner_role from names)
949
+ union all
950
+ select 'schema',namespace.nspname,pg_catalog.pg_get_userbyid(namespace.nspowner)
951
+ from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge'
952
+ union all
953
+ select 'schema_acl',namespace.nspname,
954
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
955
+ coalesce(pg_catalog.pg_get_userbyid(privilege.grantee),'PUBLIC')||':'||
956
+ privilege.privilege_type||':'||privilege.is_grantable::text
957
+ from pg_catalog.pg_namespace namespace
958
+ cross join lateral pg_catalog.aclexplode(coalesce(namespace.nspacl,
959
+ pg_catalog.acldefault('n',namespace.nspowner))) privilege
960
+ where namespace.nspname='agent_bridge'
961
+ union all
962
+ select 'relation',relation.oid::regclass::text,
963
+ pg_catalog.pg_get_userbyid(relation.relowner)||':'||relation.relkind::text||':'||
964
+ relation.relpersistence::text||':'||relation.relrowsecurity::text||':'||
965
+ relation.relforcerowsecurity::text
966
+ from pg_catalog.pg_class relation
967
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
968
+ where namespace.nspname='agent_bridge' and relation.relkind in ('r','p','S','v','m','f') and (
969
+ relation.relname in (select relation_name from dependency_relations)
970
+ or relation.relowner=(select oid from pg_catalog.pg_roles where rolname=(select owner_role from names)))
971
+ union all
972
+ select 'relation_acl',relation.oid::regclass::text,
973
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
974
+ coalesce(pg_catalog.pg_get_userbyid(privilege.grantee),'PUBLIC')||':'||
975
+ privilege.privilege_type||':'||privilege.is_grantable::text
976
+ from pg_catalog.pg_class relation
977
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
978
+ cross join lateral pg_catalog.aclexplode(coalesce(relation.relacl,pg_catalog.acldefault(
979
+ case when relation.relkind='S' then 'S'::"char" else 'r'::"char" end,relation.relowner
980
+ ))) privilege
981
+ where namespace.nspname='agent_bridge' and relation.relkind in ('r','p','S','v','m','f') and (
982
+ relation.relname in (select relation_name from dependency_relations)
983
+ or relation.relowner=(select oid from pg_catalog.pg_roles where rolname=(select owner_role from names))
984
+ or privilege.grantee in (
985
+ select oid from pg_catalog.pg_roles where rolname in (select role_name from control_roles)
986
+ ))
987
+ union all
988
+ select 'index',index_relation.oid::regclass::text,
989
+ index_record.indisunique::text||':'||index_record.indisvalid::text||':'||
990
+ index_record.indisready::text||':'||pg_catalog.pg_get_indexdef(index_record.indexrelid)||':'||
991
+ coalesce(pg_catalog.pg_get_expr(index_record.indpred,index_record.indrelid),'')
992
+ from pg_catalog.pg_index index_record
993
+ join pg_catalog.pg_class source_relation on source_relation.oid=index_record.indrelid
994
+ join pg_catalog.pg_class index_relation on index_relation.oid=index_record.indexrelid
995
+ join pg_catalog.pg_namespace namespace on namespace.oid=source_relation.relnamespace
996
+ where namespace.nspname='agent_bridge'
997
+ and source_relation.relname in (select relation_name from dependency_relations)
998
+ union all
999
+ select 'column',relation.oid::regclass::text||'.'||attribute.attname,
1000
+ pg_catalog.format_type(attribute.atttypid,attribute.atttypmod)||':'||
1001
+ attribute.attnotnull::text||':'||attribute.attidentity::text||':'||
1002
+ attribute.attgenerated::text||':'||coalesce(pg_catalog.pg_get_expr(
1003
+ default_record.adbin,default_record.adrelid),'')
1004
+ from dependency_columns dependency
1005
+ join pg_catalog.pg_namespace namespace on namespace.nspname='agent_bridge'
1006
+ join pg_catalog.pg_class relation on relation.relnamespace=namespace.oid
1007
+ and relation.relname=dependency.relation_name
1008
+ join pg_catalog.pg_attribute attribute on attribute.attrelid=relation.oid
1009
+ and attribute.attname=dependency.column_name and attribute.attnum>0 and not attribute.attisdropped
1010
+ left join pg_catalog.pg_attrdef default_record on default_record.adrelid=attribute.attrelid
1011
+ and default_record.adnum=attribute.attnum
1012
+ union all
1013
+ select 'column_acl',relation.oid::regclass::text||'.'||attribute.attname,
1014
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
1015
+ coalesce(pg_catalog.pg_get_userbyid(privilege.grantee),'PUBLIC')||':'||
1016
+ privilege.privilege_type||':'||privilege.is_grantable::text
1017
+ from pg_catalog.pg_attribute attribute
1018
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
1019
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
1020
+ cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
1021
+ where namespace.nspname='agent_bridge' and attribute.attnum>0 and not attribute.attisdropped
1022
+ and attribute.attacl is not null and (
1023
+ relation.relname in (select relation_name from dependency_relations)
1024
+ or relation.relowner=(select oid from pg_catalog.pg_roles where rolname=(select owner_role from names))
1025
+ or privilege.grantee in (
1026
+ select oid from pg_catalog.pg_roles where rolname in (select role_name from control_roles)
1027
+ ))
1028
+ union all
1029
+ select 'constraint',constraint_record.conrelid::regclass::text||'.'||constraint_record.conname,
1030
+ pg_catalog.pg_get_constraintdef(constraint_record.oid,true)
1031
+ from pg_catalog.pg_constraint constraint_record
1032
+ join pg_catalog.pg_class relation on relation.oid=constraint_record.conrelid
1033
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
1034
+ where namespace.nspname='agent_bridge'
1035
+ and relation.relname in (select relation_name from dependency_relations)
1036
+ union all
1037
+ select 'trigger',trigger.tgrelid::regclass::text||'.'||trigger.tgname,
1038
+ trigger.tgenabled::text||':'||pg_catalog.pg_get_triggerdef(trigger.oid,true)
1039
+ from pg_catalog.pg_trigger trigger
1040
+ join pg_catalog.pg_class relation on relation.oid=trigger.tgrelid
1041
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
1042
+ where not trigger.tgisinternal and namespace.nspname='agent_bridge'
1043
+ and relation.relname in (
1044
+ 'control_requests','control_events','owner_control_attestations',
1045
+ 'control_membership_events','credentials'
1046
+ )
1047
+ union all
1048
+ select 'function',procedure.oid::regprocedure::text,
1049
+ pg_catalog.pg_get_userbyid(procedure.proowner)||':'||pg_catalog.pg_get_functiondef(procedure.oid)
1050
+ from pg_catalog.pg_proc procedure
1051
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
1052
+ where namespace.nspname='agent_bridge' and (
1053
+ procedure.proname in (select function_name from protected_functions)
1054
+ or procedure.proowner=(select oid from pg_catalog.pg_roles where rolname=(select owner_role from names)))
1055
+ union all
1056
+ select 'function_acl',procedure.oid::regprocedure::text,
1057
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
1058
+ coalesce(pg_catalog.pg_get_userbyid(privilege.grantee),'PUBLIC')||':'||
1059
+ privilege.privilege_type||':'||privilege.is_grantable::text
1060
+ from pg_catalog.pg_proc procedure
1061
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
1062
+ cross join lateral pg_catalog.aclexplode(coalesce(procedure.proacl,
1063
+ pg_catalog.acldefault('f',procedure.proowner))) privilege
1064
+ where namespace.nspname='agent_bridge' and (
1065
+ procedure.proname in (select function_name from protected_functions)
1066
+ or procedure.proowner=(select oid from pg_catalog.pg_roles where rolname=(select owner_role from names))
1067
+ or privilege.grantee in (select oid from pg_catalog.pg_roles where rolname in (select role_name from control_roles))
1068
+ or privilege.grantee=0
1069
+ )
1070
+ union all
1071
+ select 'default_acl',owner.rolname||':'||coalesce(namespace.nspname,'')||':'||
1072
+ default_acl.defaclobjtype::text,
1073
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
1074
+ coalesce(pg_catalog.pg_get_userbyid(privilege.grantee),'PUBLIC')||':'||
1075
+ privilege.privilege_type||':'||privilege.is_grantable::text
1076
+ from pg_catalog.pg_default_acl default_acl
1077
+ join pg_catalog.pg_roles owner on owner.oid=default_acl.defaclrole
1078
+ left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
1079
+ cross join lateral pg_catalog.aclexplode(default_acl.defaclacl) privilege
1080
+ where (namespace.nspname='agent_bridge' or default_acl.defaclnamespace=0)
1081
+ and default_acl.defaclrole in (
1082
+ (select schema_owner from names),
1083
+ (select oid from pg_catalog.pg_roles where rolname=(select owner_role from names))
1084
+ )
1085
+ union all
1086
+ select 'credential_security_prerequisite','v1',
1087
+ agent_bridge.credential_security_prerequisite_definition()
1088
+ )
1089
+ select string_agg(kind||E'\x1f'||identity||E'\x1f'||definition,E'\x1e'
1090
+ order by kind,identity,definition) from catalog_objects
1091
+ $$;
1092
+
1093
+ create or replace function agent_bridge.owner_control_plane_ready()
1094
+ returns boolean language sql stable security definer set search_path = '' as $$
1095
+ with names as (select
1096
+ ('agent_bridge_control_owner_'||substr(md5(current_database()),1,16))::name owner_role,
1097
+ ('agent_bridge_control_operator_'||substr(md5(current_database()),1,16))::name operator_role,
1098
+ ('agent_bridge_control_auditor_'||substr(md5(current_database()),1,16))::name auditor_role,
1099
+ (select pg_catalog.pg_get_userbyid(nspowner)::name from pg_catalog.pg_namespace
1100
+ where nspname='agent_bridge') schema_owner
1101
+ ), latest_registry as (
1102
+ select distinct on (event.member_role,event.control_role)
1103
+ event.member_role,event.control_role,event.action
1104
+ from agent_bridge.control_membership_events event
1105
+ order by event.member_role,event.control_role,event.sequence desc
1106
+ ), active_registry as (
1107
+ select member_role,control_role from latest_registry where action='register'
1108
+ ), expected_memberships as (
1109
+ select names.owner_role granted_role,names.schema_owner member_role,true admin_option
1110
+ from names
1111
+ union all select names.operator_role,names.schema_owner,true from names
1112
+ union all select names.auditor_role,names.schema_owner,true from names
1113
+ union all
1114
+ select case registry.control_role when 'operator' then names.operator_role
1115
+ else names.auditor_role end,registry.member_role,false
1116
+ from active_registry registry cross join names
1117
+ ), actual_memberships as (
1118
+ select granted.rolname::name granted_role,member.rolname::name member_role,
1119
+ membership.admin_option,
1120
+ coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
1121
+ coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option,
1122
+ grantor.rolname::name grantor_role
1123
+ from pg_catalog.pg_auth_members membership
1124
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
1125
+ join pg_catalog.pg_roles member on member.oid=membership.member
1126
+ join pg_catalog.pg_roles grantor on grantor.oid=membership.grantor
1127
+ where granted.rolname in (
1128
+ (select owner_role from names),(select operator_role from names),(select auditor_role from names)
1129
+ )
1130
+ ), authority_closure as (
1131
+ select control_role.granted_role,candidate.rolname::name member_role
1132
+ from names
1133
+ cross join lateral (values
1134
+ (names.owner_role),(names.operator_role),(names.auditor_role)
1135
+ ) control_role(granted_role)
1136
+ cross join pg_catalog.pg_roles candidate
1137
+ where candidate.rolname not in (names.owner_role,names.operator_role,names.auditor_role)
1138
+ and (not candidate.rolsuper or candidate.rolname=names.schema_owner)
1139
+ and pg_catalog.pg_has_role(candidate.rolname,control_role.granted_role,'MEMBER')
1140
+ )
1141
+ select
1142
+ current_setting('server_version_num')::integer/10000=any(array[15,16,17,18])
1143
+ and (select count(*)=1 and bool_and(
1144
+ attestation.catalog_definition=agent_bridge.owner_control_catalog_definition()
1145
+ ) from agent_bridge.owner_control_attestations attestation
1146
+ where attestation.name='owner-control-v1')
1147
+ and not exists (
1148
+ (select granted_role,member_role,admin_option from actual_memberships
1149
+ except select granted_role,member_role,admin_option from expected_memberships)
1150
+ union all
1151
+ (select granted_role,member_role,admin_option from expected_memberships
1152
+ except select granted_role,member_role,admin_option from actual_memberships)
1153
+ )
1154
+ and not exists (
1155
+ select 1 from actual_memberships membership,names
1156
+ where not membership.inherit_option or not membership.set_option
1157
+ or membership.grantor_role<>names.schema_owner
1158
+ )
1159
+ and not exists (
1160
+ select 1 from active_registry registry
1161
+ left join pg_catalog.pg_roles role_record on role_record.rolname=registry.member_role
1162
+ where role_record.oid is null or not role_record.rolcanlogin or role_record.rolsuper
1163
+ or role_record.rolcreaterole or role_record.rolcreatedb
1164
+ or role_record.rolreplication or role_record.rolbypassrls
1165
+ )
1166
+ and not exists (
1167
+ (select granted_role,member_role from authority_closure
1168
+ except select granted_role,member_role from expected_memberships)
1169
+ union all
1170
+ (select granted_role,member_role from expected_memberships
1171
+ except select granted_role,member_role from authority_closure)
1172
+ )
1173
+ and not exists (
1174
+ select 1 from active_registry registry
1175
+ join pg_catalog.pg_roles inherited on inherited.rolname<>registry.member_role
1176
+ where pg_catalog.pg_has_role(registry.member_role,inherited.rolname,'MEMBER')
1177
+ and not exists (
1178
+ select 1 from active_registry permitted
1179
+ cross join names
1180
+ where permitted.member_role=registry.member_role
1181
+ and inherited.rolname=case permitted.control_role
1182
+ when 'operator' then names.operator_role else names.auditor_role end
1183
+ )
1184
+ )
1185
+ and not exists (
1186
+ select 1 from active_registry registry
1187
+ cross join pg_catalog.pg_roles candidate
1188
+ cross join names
1189
+ where candidate.rolname not in (registry.member_role,names.schema_owner)
1190
+ and not candidate.rolsuper
1191
+ and pg_catalog.pg_has_role(candidate.rolname,registry.member_role,'MEMBER')
1192
+ )
1193
+ $$;
1194
+
1195
+ revoke all on agent_bridge.control_requests,agent_bridge.control_events,
1196
+ agent_bridge.owner_control_attestations,agent_bridge.control_membership_events from public;
1197
+ revoke all on sequence agent_bridge.control_events_sequence_seq,
1198
+ agent_bridge.control_membership_events_sequence_seq from public;
1199
+ revoke execute on all functions in schema agent_bridge from public;
1200
+
1201
+ do $grants$
1202
+ declare
1203
+ suffix text := substr(md5(current_database()),1,16);
1204
+ owner_role text := 'agent_bridge_control_owner_'||suffix;
1205
+ operator_role text := 'agent_bridge_control_operator_'||suffix;
1206
+ auditor_role text := 'agent_bridge_control_auditor_'||suffix;
1207
+ runtime_role text := 'agent_bridge_runtime_'||suffix;
1208
+ data_owner_role text := 'agent_bridge_data_owner_'||suffix;
1209
+ context_reader_role text := 'agent_bridge_context_reader_'||suffix;
1210
+ event_writer_role text := 'agent_bridge_event_writer_'||suffix;
1211
+ role_name text;
1212
+ granted_role text;
1213
+ member_role text;
1214
+ relation_name text;
1215
+ column_name text;
1216
+ membership_record record;
1217
+ relation_record record;
1218
+ begin
1219
+ execute format('alter table agent_bridge.control_requests owner to %I',owner_role);
1220
+ execute format('alter table agent_bridge.control_events owner to %I',owner_role);
1221
+ execute format('alter table agent_bridge.owner_control_attestations owner to %I',owner_role);
1222
+ execute format('alter table agent_bridge.control_membership_events owner to %I',owner_role);
1223
+ execute format('alter sequence agent_bridge.control_events_sequence_seq owner to %I',owner_role);
1224
+ execute format('alter sequence agent_bridge.control_membership_events_sequence_seq owner to %I',owner_role);
1225
+ foreach role_name in array array[
1226
+ 'reject_control_ledger_mutation()',
1227
+ 'control_provision(uuid,text,text,text,text,text,character,text,text,timestamp with time zone)',
1228
+ 'control_credential_inventory(text,timestamp with time zone,uuid,integer)',
1229
+ 'control_rotate_credential(uuid,uuid,text,text,character,text,text,timestamp with time zone,timestamp with time zone)',
1230
+ 'control_revoke_credential(uuid,uuid,text)','owner_control_catalog_definition()',
1231
+ 'owner_control_plane_ready()'
1232
+ ] loop execute format('alter function agent_bridge.%s owner to %I',role_name,owner_role); end loop;
1233
+
1234
+ for membership_record in
1235
+ select granted.rolname granted_name,member.rolname member_name
1236
+ from pg_catalog.pg_auth_members membership
1237
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
1238
+ join pg_catalog.pg_roles member on member.oid=membership.member
1239
+ where member.rolname in (owner_role,operator_role,auditor_role)
1240
+ loop
1241
+ execute format('revoke %I from %I',membership_record.granted_name,membership_record.member_name);
1242
+ end loop;
1243
+ for membership_record in
1244
+ select granted.rolname granted_name,member.rolname member_name
1245
+ from pg_catalog.pg_auth_members membership
1246
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
1247
+ join pg_catalog.pg_roles member on member.oid=membership.member
1248
+ where granted.rolname in (owner_role,operator_role,auditor_role)
1249
+ and member.rolname<>current_user
1250
+ loop
1251
+ execute format('revoke %I from %I',membership_record.granted_name,membership_record.member_name);
1252
+ end loop;
1253
+
1254
+ foreach role_name in array array[owner_role,operator_role,auditor_role] loop
1255
+ execute format('revoke all on schema agent_bridge from %I',role_name);
1256
+ for relation_record in
1257
+ select relation.relname,relation.relkind from pg_catalog.pg_class relation
1258
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
1259
+ where namespace.nspname='agent_bridge' and relation.relkind in ('r','p','v','S','m','f')
1260
+ loop
1261
+ if relation_record.relkind='S' then
1262
+ execute format('revoke all on sequence agent_bridge.%I from %I',relation_record.relname,role_name);
1263
+ else
1264
+ execute format('revoke all on table agent_bridge.%I from %I',relation_record.relname,role_name);
1265
+ for column_name in select attribute.attname from pg_catalog.pg_attribute attribute
1266
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
1267
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
1268
+ where namespace.nspname='agent_bridge' and relation.relname=relation_record.relname
1269
+ and attribute.attnum>0 and not attribute.attisdropped loop
1270
+ execute format('revoke select(%I),insert(%I),update(%I),references(%I) on agent_bridge.%I from %I',
1271
+ column_name,column_name,column_name,column_name,relation_record.relname,role_name);
1272
+ end loop;
1273
+ end if;
1274
+ end loop;
1275
+ execute format('revoke execute on all functions in schema agent_bridge from %I',role_name);
1276
+ end loop;
1277
+
1278
+ foreach role_name in array array[operator_role,auditor_role,runtime_role,data_owner_role,
1279
+ context_reader_role,event_writer_role] loop
1280
+ execute format('revoke all on agent_bridge.control_requests,agent_bridge.control_events,agent_bridge.owner_control_attestations,agent_bridge.control_membership_events from %I',role_name);
1281
+ execute format('revoke all on sequence agent_bridge.control_events_sequence_seq,agent_bridge.control_membership_events_sequence_seq from %I',role_name);
1282
+ end loop;
1283
+ foreach role_name in array array[runtime_role,data_owner_role,context_reader_role,event_writer_role] loop
1284
+ execute format('revoke execute on function agent_bridge.control_provision(uuid,text,text,text,text,text,character,text,text,timestamp with time zone),agent_bridge.control_credential_inventory(text,timestamp with time zone,uuid,integer),agent_bridge.control_rotate_credential(uuid,uuid,text,text,character,text,text,timestamp with time zone,timestamp with time zone),agent_bridge.control_revoke_credential(uuid,uuid,text),agent_bridge.owner_control_catalog_definition(),agent_bridge.owner_control_plane_ready() from %I',role_name);
1285
+ end loop;
1286
+
1287
+ execute format('grant usage on schema agent_bridge to %I,%I,%I',owner_role,operator_role,auditor_role);
1288
+ execute format('grant select,insert,update(request_id) on agent_bridge.control_requests to %I',owner_role);
1289
+ execute format('grant insert on agent_bridge.control_events to %I',owner_role);
1290
+ execute format('grant select on agent_bridge.owner_control_attestations to %I',owner_role);
1291
+ execute format('grant select,insert on agent_bridge.control_membership_events to %I',owner_role);
1292
+ execute format('grant usage on sequence agent_bridge.control_events_sequence_seq to %I',owner_role);
1293
+ execute format('grant usage on sequence agent_bridge.control_membership_events_sequence_seq to %I',owner_role);
1294
+ execute format('grant select(id,name,disabled_at),insert(id,name) on agent_bridge.workspaces to %I',owner_role);
1295
+ execute format('grant select(id,workspace_id,principal,disabled_at),insert(workspace_id,principal,display_name,runtime_type) on agent_bridge.agents to %I',owner_role);
1296
+ execute format('grant select(id,workspace_id,agent_id,label,expires_at,revoked_at,created_at,last_used_at,scopes,scope_set_name,replaces_credential_id,revoked_by,revocation_reason,expiry_grace_until) on agent_bridge.credentials to %I',owner_role);
1297
+ execute format('grant insert(workspace_id,agent_id,token_hash,label,expires_at,scopes,scope_set_name,replaces_credential_id) on agent_bridge.credentials to %I',owner_role);
1298
+ execute format('grant update(expiry_grace_until,revoked_at,revoked_by,revocation_reason) on agent_bridge.credentials to %I',owner_role);
1299
+ execute format('grant select(name,scopes) on agent_bridge.credential_scope_sets to %I',owner_role);
1300
+ execute format('grant execute on function agent_bridge.canonicalize_scopes(text[]) to %I',owner_role);
1301
+ execute format('grant execute on function agent_bridge.assert_control_actor(text) to %I',owner_role);
1302
+ execute format('grant execute on function agent_bridge.credential_security_prerequisite_definition() to %I',owner_role);
1303
+ execute format('grant execute on function agent_bridge.owner_control_catalog_definition() to %I',owner_role);
1304
+ execute format('grant execute on function agent_bridge.control_provision(uuid,text,text,text,text,text,character,text,text,timestamp with time zone) to %I',operator_role);
1305
+ execute format('grant execute on function agent_bridge.control_rotate_credential(uuid,uuid,text,text,character,text,text,timestamp with time zone,timestamp with time zone) to %I',operator_role);
1306
+ execute format('grant execute on function agent_bridge.control_revoke_credential(uuid,uuid,text) to %I',operator_role);
1307
+ execute format('grant execute on function agent_bridge.control_credential_inventory(text,timestamp with time zone,uuid,integer) to %I,%I',operator_role,auditor_role);
1308
+ execute format('grant execute on function agent_bridge.owner_control_plane_ready() to %I',runtime_role);
1309
+ execute format('revoke execute on function agent_bridge.replace_credential(uuid,character,text[],text,text,timestamp with time zone,timestamp with time zone,text,uuid) from %I,%I,%I',operator_role,auditor_role,runtime_role);
1310
+ execute format('revoke execute on function agent_bridge.revoke_credential(uuid,text,text,uuid) from %I,%I,%I',operator_role,auditor_role,runtime_role);
1311
+ foreach role_name in array array['anon','authenticated'] loop
1312
+ if exists(select 1 from pg_roles where rolname=role_name) then
1313
+ execute format('revoke all on agent_bridge.control_requests,agent_bridge.control_events,agent_bridge.owner_control_attestations,agent_bridge.control_membership_events from %I',role_name);
1314
+ execute format('revoke all on sequence agent_bridge.control_events_sequence_seq,agent_bridge.control_membership_events_sequence_seq from %I',role_name);
1315
+ execute format('revoke execute on function agent_bridge.control_provision(uuid,text,text,text,text,text,character,text,text,timestamp with time zone),agent_bridge.control_credential_inventory(text,timestamp with time zone,uuid,integer),agent_bridge.control_rotate_credential(uuid,uuid,text,text,character,text,text,timestamp with time zone,timestamp with time zone),agent_bridge.control_revoke_credential(uuid,uuid,text),agent_bridge.owner_control_catalog_definition(),agent_bridge.owner_control_plane_ready() from %I',role_name);
1316
+ end if;
1317
+ end loop;
1318
+ foreach role_name in array array[current_user,owner_role] loop
1319
+ execute format('alter default privileges for role %I in schema agent_bridge revoke all on tables from public',role_name);
1320
+ execute format('alter default privileges for role %I in schema agent_bridge revoke all on sequences from public',role_name);
1321
+ execute format('alter default privileges for role %I in schema agent_bridge revoke all on functions from public',role_name);
1322
+ execute format('alter default privileges for role %I in schema agent_bridge revoke all on types from public',role_name);
1323
+ foreach member_role in array array['anon','authenticated'] loop
1324
+ if exists(select 1 from pg_catalog.pg_roles where rolname=member_role) then
1325
+ execute format('alter default privileges for role %I in schema agent_bridge revoke all on tables from %I',role_name,member_role);
1326
+ execute format('alter default privileges for role %I in schema agent_bridge revoke all on sequences from %I',role_name,member_role);
1327
+ execute format('alter default privileges for role %I in schema agent_bridge revoke all on functions from %I',role_name,member_role);
1328
+ execute format('alter default privileges for role %I in schema agent_bridge revoke all on types from %I',role_name,member_role);
1329
+ end if;
1330
+ end loop;
1331
+ end loop;
1332
+ end
1333
+ $grants$;
1334
+
1335
+ do $preflight$
1336
+ declare
1337
+ suffix text := substr(md5(current_database()),1,16);
1338
+ owner_role name := ('agent_bridge_control_owner_'||suffix)::name;
1339
+ operator_role name := ('agent_bridge_control_operator_'||suffix)::name;
1340
+ auditor_role name := ('agent_bridge_control_auditor_'||suffix)::name;
1341
+ expected_count integer;
1342
+ actual_security_digest text;
1343
+ expected_security_digest text;
1344
+ begin
1345
+ select pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(
1346
+ agent_bridge.credential_security_prerequisite_definition(),'UTF8')),'hex')
1347
+ into actual_security_digest;
1348
+ expected_security_digest := case
1349
+ when current_setting('server_version_num')::integer/10000=15
1350
+ then '9db61d6bb63f753041bae021cfa006bc497fa0d3d12ea8636c747d095ff675cf'
1351
+ when current_setting('server_version_num')::integer/10000=16
1352
+ then '9db61d6bb63f753041bae021cfa006bc497fa0d3d12ea8636c747d095ff675cf'
1353
+ when current_setting('server_version_num')::integer/10000=17
1354
+ then '9db61d6bb63f753041bae021cfa006bc497fa0d3d12ea8636c747d095ff675cf'
1355
+ when current_setting('server_version_num')::integer/10000=18
1356
+ then 'cfb571d4d6d5a00ec5d46fcf3ec0f18403ebd2f2bf020302c5feb7701164fcf9'
1357
+ else null
1358
+ end;
1359
+ if expected_security_digest is null or actual_security_digest<>expected_security_digest then
1360
+ raise exception 'owner control preflight rejected credential security definition drift'
1361
+ using detail='observed prerequisite digest: '||actual_security_digest;
1362
+ end if;
1363
+ if not agent_bridge.security_schema_ready() then
1364
+ raise exception 'owner control preflight rejected credential security drift';
1365
+ end if;
1366
+ if not exists (
1367
+ select 1 from agent_bridge.row_isolation_attestations attestation
1368
+ where attestation.name='domain-v1'
1369
+ and attestation.catalog_definition=agent_bridge.row_isolation_catalog_definition()
1370
+ ) then
1371
+ raise exception 'owner control preflight rejected row isolation drift';
1372
+ end if;
1373
+
1374
+ if exists (
1375
+ select 1 from pg_catalog.pg_namespace namespace
1376
+ cross join lateral pg_catalog.aclexplode(coalesce(
1377
+ namespace.nspacl,pg_catalog.acldefault('n',namespace.nspowner)
1378
+ )) access
1379
+ left join pg_catalog.pg_roles grantee on grantee.oid=access.grantee
1380
+ where namespace.nspname='agent_bridge' and (
1381
+ access.grantee=0 or grantee.rolname not in (
1382
+ current_user,owner_role,operator_role,auditor_role,
1383
+ 'agent_bridge_runtime_'||suffix,'agent_bridge_data_owner_'||suffix,
1384
+ 'agent_bridge_context_reader_'||suffix,'agent_bridge_event_writer_'||suffix
1385
+ )
1386
+ )
1387
+ ) then
1388
+ raise exception 'owner control preflight rejected schema privilege drift';
1389
+ end if;
1390
+
1391
+ if exists (
1392
+ select 1 from pg_catalog.pg_class relation
1393
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
1394
+ cross join lateral pg_catalog.aclexplode(coalesce(relation.relacl,pg_catalog.acldefault(
1395
+ case when relation.relkind='S' then 'S'::"char" else 'r'::"char" end,relation.relowner
1396
+ ))) access
1397
+ where namespace.nspname='agent_bridge'
1398
+ and relation.relname in (
1399
+ 'control_requests','control_events','owner_control_attestations',
1400
+ 'control_membership_events','control_events_sequence_seq',
1401
+ 'control_membership_events_sequence_seq','workspaces','agents','credentials',
1402
+ 'credential_scope_sets'
1403
+ )
1404
+ and access.grantee not in (
1405
+ relation.relowner,(select oid from pg_catalog.pg_roles where rolname=owner_role)
1406
+ )
1407
+ ) then
1408
+ raise exception 'owner control preflight rejected relation privilege drift';
1409
+ end if;
1410
+
1411
+ if exists (
1412
+ select 1 from pg_catalog.pg_attribute attribute
1413
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
1414
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
1415
+ cross join lateral pg_catalog.aclexplode(attribute.attacl) access
1416
+ where namespace.nspname='agent_bridge' and attribute.attacl is not null
1417
+ and relation.relname in (
1418
+ 'control_requests','control_events','owner_control_attestations',
1419
+ 'control_membership_events','workspaces','agents','credentials','credential_scope_sets'
1420
+ )
1421
+ and access.grantee<>(select oid from pg_catalog.pg_roles where rolname=owner_role)
1422
+ ) then
1423
+ raise exception 'owner control preflight rejected column privilege drift';
1424
+ end if;
1425
+
1426
+ if exists (
1427
+ select 1 from pg_catalog.pg_proc procedure
1428
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
1429
+ cross join lateral pg_catalog.aclexplode(coalesce(
1430
+ procedure.proacl,pg_catalog.acldefault('f',procedure.proowner)
1431
+ )) access
1432
+ left join pg_catalog.pg_roles grantee on grantee.oid=access.grantee
1433
+ where namespace.nspname='agent_bridge' and procedure.proname in (
1434
+ 'canonicalize_scopes','validate_credential_security','reject_credential_delete',
1435
+ 'reject_scope_set_mutation','reject_security_event_mutation','record_scope_denial',
1436
+ 'consume_rate_limit','replace_credential','revoke_credential','security_schema_ready',
1437
+ 'current_request_workspace','current_request_principal',
1438
+ 'row_isolation_catalog_definition','credential_security_prerequisite_definition',
1439
+ 'assert_control_actor',
1440
+ 'reject_control_ledger_mutation','control_provision','control_credential_inventory',
1441
+ 'control_rotate_credential','control_revoke_credential','register_control_member',
1442
+ 'revoke_control_member','owner_control_catalog_definition','owner_control_plane_ready'
1443
+ ) and (access.grantee=0 or grantee.rolname not in (
1444
+ current_user,owner_role,operator_role,auditor_role,
1445
+ 'agent_bridge_runtime_'||suffix,'agent_bridge_data_owner_'||suffix,
1446
+ 'agent_bridge_context_reader_'||suffix,'agent_bridge_event_writer_'||suffix
1447
+ ))
1448
+ ) then
1449
+ raise exception 'owner control preflight rejected function privilege drift';
1450
+ end if;
1451
+
1452
+ if exists (
1453
+ select 1 from pg_catalog.pg_default_acl default_acl
1454
+ left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
1455
+ cross join lateral pg_catalog.aclexplode(default_acl.defaclacl) access
1456
+ where default_acl.defaclrole in (
1457
+ current_user::regrole::oid,
1458
+ (select oid from pg_catalog.pg_roles where rolname=owner_role)
1459
+ ) and (namespace.nspname='agent_bridge' or default_acl.defaclnamespace=0)
1460
+ and access.grantee<>default_acl.defaclrole
1461
+ ) then
1462
+ raise exception 'owner control preflight rejected default privilege drift';
1463
+ end if;
1464
+
1465
+ if exists (
1466
+ select 1 from (values
1467
+ ('workspaces','r','postgres'),('agents','r','postgres'),
1468
+ ('credentials','r','postgres'),('credential_scope_sets','r','postgres'),
1469
+ ('control_requests','r','owner'),('control_events','r','owner'),
1470
+ ('owner_control_attestations','r','owner'),
1471
+ ('control_membership_events','r','owner'),
1472
+ ('control_events_sequence_seq','S','owner'),
1473
+ ('control_membership_events_sequence_seq','S','owner')
1474
+ ) expected(relation_name,relation_kind,owner_kind)
1475
+ left join pg_catalog.pg_namespace namespace on namespace.nspname='agent_bridge'
1476
+ left join pg_catalog.pg_class relation on relation.relnamespace=namespace.oid
1477
+ and relation.relname=expected.relation_name
1478
+ where relation.oid is null or relation.relkind::text<>expected.relation_kind
1479
+ or relation.relpersistence<>'p' or relation.relrowsecurity or relation.relforcerowsecurity
1480
+ or relation.relowner<>case expected.owner_kind when 'owner'
1481
+ then (select oid from pg_catalog.pg_roles where rolname=owner_role)
1482
+ else current_user::regrole::oid end
1483
+ ) then
1484
+ raise exception 'owner control preflight rejected relation ownership or security drift';
1485
+ end if;
1486
+
1487
+ if exists (
1488
+ select 1 from pg_catalog.pg_class relation
1489
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
1490
+ where namespace.nspname='agent_bridge'
1491
+ and relation.relowner in (
1492
+ select oid from pg_catalog.pg_roles where rolname in (owner_role,operator_role,auditor_role)
1493
+ ) and not (
1494
+ relation.relowner=(select oid from pg_catalog.pg_roles where rolname=owner_role)
1495
+ and (
1496
+ (relation.relkind='r' and relation.relname in (
1497
+ 'control_requests','control_events','owner_control_attestations',
1498
+ 'control_membership_events'))
1499
+ or (relation.relkind='S' and relation.relname in (
1500
+ 'control_events_sequence_seq','control_membership_events_sequence_seq'))
1501
+ or (relation.relkind='i' and exists (
1502
+ select 1 from pg_catalog.pg_index index_record
1503
+ join pg_catalog.pg_class source_relation on source_relation.oid=index_record.indrelid
1504
+ where index_record.indexrelid=relation.oid
1505
+ and source_relation.relname in (
1506
+ 'control_requests','control_events','owner_control_attestations',
1507
+ 'control_membership_events')
1508
+ ))
1509
+ )
1510
+ )
1511
+ ) then
1512
+ raise exception 'owner control preflight rejected unexpected control-owned relation';
1513
+ end if;
1514
+
1515
+ if exists (
1516
+ select 1 from (values
1517
+ ('agent_bridge.workspaces'::regclass,'workspaces_pkey','p','PRIMARY KEY (id)'),
1518
+ ('agent_bridge.agents'::regclass,'agents_pkey','p','PRIMARY KEY (id)'),
1519
+ ('agent_bridge.agents'::regclass,'agents_workspace_id_fkey','f',
1520
+ 'FOREIGN KEY (workspace_id) REFERENCES agent_bridge.workspaces(id)'),
1521
+ ('agent_bridge.agents'::regclass,'agents_workspace_id_id_key','u',
1522
+ 'UNIQUE (workspace_id, id)'),
1523
+ ('agent_bridge.agents'::regclass,'agents_workspace_id_principal_key','u',
1524
+ 'UNIQUE (workspace_id, principal)')
1525
+ ) expected(relation_id,constraint_name,constraint_type,constraint_definition)
1526
+ left join pg_catalog.pg_constraint constraint_record
1527
+ on constraint_record.conrelid=expected.relation_id
1528
+ and constraint_record.conname=expected.constraint_name
1529
+ where constraint_record.oid is null or not constraint_record.convalidated
1530
+ or constraint_record.contype::text<>expected.constraint_type
1531
+ or pg_catalog.pg_get_constraintdef(constraint_record.oid,true)
1532
+ <>expected.constraint_definition
1533
+ ) then
1534
+ raise exception 'owner control preflight rejected workspace or agent constraint drift';
1535
+ end if;
1536
+
1537
+ if exists (
1538
+ select 1 from (values
1539
+ ('agent_bridge.agents_workspace_id_principal_key'::regclass,
1540
+ 'CREATE UNIQUE INDEX agents_workspace_id_principal_key ON agent_bridge.agents USING btree (workspace_id, principal)',
1541
+ ''),
1542
+ ('agent_bridge.credentials_replacement_lineage'::regclass,
1543
+ 'CREATE UNIQUE INDEX credentials_replacement_lineage ON agent_bridge.credentials USING btree (replaces_credential_id) WHERE (replaces_credential_id IS NOT NULL)',
1544
+ '(replaces_credential_id IS NOT NULL)')
1545
+ ) expected(index_id,index_definition,index_predicate)
1546
+ left join pg_catalog.pg_index index_record on index_record.indexrelid=expected.index_id
1547
+ where index_record.indexrelid is null or not index_record.indisunique
1548
+ or not index_record.indisvalid or not index_record.indisready
1549
+ or pg_catalog.pg_get_indexdef(index_record.indexrelid)<>expected.index_definition
1550
+ or coalesce(pg_catalog.pg_get_expr(index_record.indpred,index_record.indrelid),'')
1551
+ <>expected.index_predicate
1552
+ ) then
1553
+ raise exception 'owner control preflight rejected critical unique index drift';
1554
+ end if;
1555
+
1556
+ select count(*) into expected_count from (values
1557
+ ('agent_bridge.reject_control_ledger_mutation()','owner',false,'v',false),
1558
+ ('agent_bridge.control_provision(uuid,text,text,text,text,text,character,text,text,timestamp with time zone)','owner',true,'v',false),
1559
+ ('agent_bridge.control_credential_inventory(text,timestamp with time zone,uuid,integer)','owner',true,'s',false),
1560
+ ('agent_bridge.control_rotate_credential(uuid,uuid,text,text,character,text,text,timestamp with time zone,timestamp with time zone)','owner',true,'v',false),
1561
+ ('agent_bridge.control_revoke_credential(uuid,uuid,text)','owner',true,'v',false),
1562
+ ('agent_bridge.owner_control_catalog_definition()','owner',false,'s',true),
1563
+ ('agent_bridge.owner_control_plane_ready()','owner',true,'s',false),
1564
+ ('agent_bridge.register_control_member(uuid,name,text)','migration',true,'v',false),
1565
+ ('agent_bridge.revoke_control_member(uuid,name,text)','migration',true,'v',false),
1566
+ ('agent_bridge.assert_control_actor(text)','migration',true,'v',false),
1567
+ ('agent_bridge.credential_security_prerequisite_definition()','migration',false,'s',true),
1568
+ ('agent_bridge.row_isolation_catalog_definition()','migration',false,'s',false),
1569
+ ('agent_bridge.validate_credential_security()','migration',false,'v',false),
1570
+ ('agent_bridge.canonicalize_scopes(text[])','migration',false,'i',false),
1571
+ ('agent_bridge.replace_credential(uuid,character,text[],text,text,timestamp with time zone,timestamp with time zone,text,uuid)','migration',true,'v',false),
1572
+ ('agent_bridge.revoke_credential(uuid,text,text,uuid)','migration',true,'v',false)
1573
+ ) expected(signature,owner_kind,security_definer,volatility,utc_config)
1574
+ left join pg_catalog.pg_proc procedure
1575
+ on procedure.oid=pg_catalog.to_regprocedure(expected.signature)
1576
+ where procedure.oid is null
1577
+ or procedure.proowner<>case expected.owner_kind when 'owner'
1578
+ then (select oid from pg_catalog.pg_roles where rolname=owner_role)
1579
+ else current_user::regrole::oid end
1580
+ or procedure.prosecdef<>expected.security_definer
1581
+ or procedure.provolatile::text<>expected.volatility
1582
+ or not procedure.proconfig @> array['search_path=""']::text[]
1583
+ or (expected.utc_config and not procedure.proconfig @> array['TimeZone=UTC']::text[])
1584
+ or (not expected.utc_config and cardinality(procedure.proconfig)<>1)
1585
+ or (expected.utc_config and cardinality(procedure.proconfig)<>2);
1586
+ if expected_count<>0 then
1587
+ raise exception 'owner control preflight rejected protected function drift';
1588
+ end if;
1589
+
1590
+ if exists (
1591
+ select 1 from pg_catalog.pg_proc procedure
1592
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
1593
+ where namespace.nspname='agent_bridge'
1594
+ and procedure.proowner in (
1595
+ select oid from pg_catalog.pg_roles where rolname in (owner_role,operator_role,auditor_role)
1596
+ ) and not (
1597
+ procedure.proowner=(select oid from pg_catalog.pg_roles where rolname=owner_role)
1598
+ and procedure.oid in (
1599
+ 'agent_bridge.reject_control_ledger_mutation()'::regprocedure,
1600
+ 'agent_bridge.control_provision(uuid,text,text,text,text,text,character,text,text,timestamp with time zone)'::regprocedure,
1601
+ 'agent_bridge.control_credential_inventory(text,timestamp with time zone,uuid,integer)'::regprocedure,
1602
+ 'agent_bridge.control_rotate_credential(uuid,uuid,text,text,character,text,text,timestamp with time zone,timestamp with time zone)'::regprocedure,
1603
+ 'agent_bridge.control_revoke_credential(uuid,uuid,text)'::regprocedure,
1604
+ 'agent_bridge.owner_control_catalog_definition()'::regprocedure,
1605
+ 'agent_bridge.owner_control_plane_ready()'::regprocedure
1606
+ )
1607
+ )
1608
+ ) then
1609
+ raise exception 'owner control preflight rejected unexpected control-owned function';
1610
+ end if;
1611
+
1612
+ if exists (
1613
+ select 1 from (values
1614
+ ('agent_bridge.control_requests'::regclass,'control_requests_append_only',
1615
+ 'CREATE TRIGGER control_requests_append_only BEFORE DELETE OR UPDATE OR TRUNCATE ON agent_bridge.control_requests FOR EACH STATEMENT EXECUTE FUNCTION agent_bridge.reject_control_ledger_mutation()'),
1616
+ ('agent_bridge.control_events'::regclass,'control_events_append_only',
1617
+ 'CREATE TRIGGER control_events_append_only BEFORE DELETE OR UPDATE OR TRUNCATE ON agent_bridge.control_events FOR EACH STATEMENT EXECUTE FUNCTION agent_bridge.reject_control_ledger_mutation()'),
1618
+ ('agent_bridge.owner_control_attestations'::regclass,'owner_control_attestations_append_only',
1619
+ 'CREATE TRIGGER owner_control_attestations_append_only BEFORE DELETE OR UPDATE OR TRUNCATE ON agent_bridge.owner_control_attestations FOR EACH STATEMENT EXECUTE FUNCTION agent_bridge.reject_control_ledger_mutation()'),
1620
+ ('agent_bridge.control_membership_events'::regclass,'control_membership_events_append_only',
1621
+ 'CREATE TRIGGER control_membership_events_append_only BEFORE DELETE OR UPDATE OR TRUNCATE ON agent_bridge.control_membership_events FOR EACH STATEMENT EXECUTE FUNCTION agent_bridge.reject_control_ledger_mutation()'),
1622
+ ('agent_bridge.credentials'::regclass,'credentials_validate_security',
1623
+ 'CREATE TRIGGER credentials_validate_security BEFORE INSERT OR UPDATE ON agent_bridge.credentials FOR EACH ROW EXECUTE FUNCTION agent_bridge.validate_credential_security()')
1624
+ ) expected(relation_id,trigger_name,trigger_definition)
1625
+ left join pg_catalog.pg_trigger trigger on trigger.tgrelid=expected.relation_id
1626
+ and trigger.tgname=expected.trigger_name and not trigger.tgisinternal
1627
+ where trigger.oid is null or trigger.tgenabled<>'O'
1628
+ or pg_catalog.pg_get_triggerdef(trigger.oid,true)<>expected.trigger_definition
1629
+ ) then
1630
+ raise exception 'owner control preflight rejected protected trigger drift';
1631
+ end if;
1632
+ end
1633
+ $preflight$;
1634
+
1635
+ insert into agent_bridge.owner_control_attestations(name,catalog_definition)
1636
+ values('owner-control-v1',agent_bridge.owner_control_catalog_definition());
1637
+
1638
+ insert into agent_bridge.schema_migrations(version,name,checksum)
1639
+ values(14,'owner_control_plane','__AGENT_BRIDGE_MIGRATION_CHECKSUM__');
1640
+
1641
+ commit;