@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,1005 @@
1
+ begin;
2
+
3
+ select pg_advisory_xact_lock(1646705660);
4
+
5
+ -- Native DR fence contract: backup holds pg_advisory_lock(1646705664).
6
+ -- Migration and Agent Bridge membership mutations hold the shared transaction form.
7
+ select pg_advisory_xact_lock_shared(1646705664);
8
+
9
+ do $identifier_preflight$
10
+ declare
11
+ rejected_role text;
12
+ begin
13
+ with names as (select
14
+ namespace.nspowner schema_owner,
15
+ ('agent_bridge_control_owner_'||substr(md5(current_database()),1,16))::name control_owner
16
+ from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge'
17
+ ), derived_roles(oid) as (
18
+ select role.oid from pg_catalog.pg_roles role where role.rolname in (
19
+ 'agent_bridge_runtime_'||substr(md5(current_database()),1,16),
20
+ 'agent_bridge_data_owner_'||substr(md5(current_database()),1,16),
21
+ 'agent_bridge_context_reader_'||substr(md5(current_database()),1,16),
22
+ 'agent_bridge_event_writer_'||substr(md5(current_database()),1,16),
23
+ 'agent_bridge_control_owner_'||substr(md5(current_database()),1,16),
24
+ 'agent_bridge_control_operator_'||substr(md5(current_database()),1,16),
25
+ 'agent_bridge_control_auditor_'||substr(md5(current_database()),1,16),
26
+ 'agent_bridge_archive_operator_'||substr(md5(current_database()),1,16)
27
+ )
28
+ ), object_roles(oid) as (
29
+ select schema_owner from names
30
+ union select relation.relowner from pg_catalog.pg_class relation
31
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
32
+ where namespace.nspname='agent_bridge'
33
+ union select procedure.proowner from pg_catalog.pg_proc procedure
34
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
35
+ where namespace.nspname='agent_bridge'
36
+ union select type_record.typowner from pg_catalog.pg_type type_record
37
+ join pg_catalog.pg_namespace namespace on namespace.oid=type_record.typnamespace
38
+ where namespace.nspname='agent_bridge'
39
+ union select privilege.grantor from pg_catalog.pg_namespace namespace
40
+ cross join lateral pg_catalog.aclexplode(namespace.nspacl) privilege
41
+ where namespace.nspname='agent_bridge'
42
+ union select privilege.grantee from pg_catalog.pg_namespace namespace
43
+ cross join lateral pg_catalog.aclexplode(namespace.nspacl) privilege
44
+ where namespace.nspname='agent_bridge' and privilege.grantee<>0
45
+ union select privilege.grantor from pg_catalog.pg_class relation
46
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
47
+ cross join lateral pg_catalog.aclexplode(relation.relacl) privilege
48
+ where namespace.nspname='agent_bridge'
49
+ union select privilege.grantee from pg_catalog.pg_class relation
50
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
51
+ cross join lateral pg_catalog.aclexplode(relation.relacl) privilege
52
+ where namespace.nspname='agent_bridge' and privilege.grantee<>0
53
+ union select privilege.grantor from pg_catalog.pg_attribute attribute
54
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
55
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
56
+ cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
57
+ where namespace.nspname='agent_bridge' and attribute.attnum>0
58
+ and not attribute.attisdropped and attribute.attacl is not null
59
+ union select privilege.grantee from pg_catalog.pg_attribute attribute
60
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
61
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
62
+ cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
63
+ where namespace.nspname='agent_bridge' and attribute.attnum>0
64
+ and not attribute.attisdropped and attribute.attacl is not null
65
+ and privilege.grantee<>0
66
+ union select privilege.grantor from pg_catalog.pg_proc procedure
67
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
68
+ cross join lateral pg_catalog.aclexplode(procedure.proacl) privilege
69
+ where namespace.nspname='agent_bridge'
70
+ union select privilege.grantee from pg_catalog.pg_proc procedure
71
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
72
+ cross join lateral pg_catalog.aclexplode(procedure.proacl) privilege
73
+ where namespace.nspname='agent_bridge' and privilege.grantee<>0
74
+ union select default_acl.defaclrole from pg_catalog.pg_default_acl default_acl
75
+ left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
76
+ cross join names where namespace.nspname='agent_bridge'
77
+ or default_acl.defaclrole in (names.schema_owner,
78
+ (select oid from pg_catalog.pg_roles where rolname=names.control_owner))
79
+ union select privilege.grantor from pg_catalog.pg_default_acl default_acl
80
+ left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
81
+ cross join lateral pg_catalog.aclexplode(default_acl.defaclacl) privilege
82
+ cross join names where namespace.nspname='agent_bridge'
83
+ or default_acl.defaclrole in (names.schema_owner,
84
+ (select oid from pg_catalog.pg_roles where rolname=names.control_owner))
85
+ union select privilege.grantee from pg_catalog.pg_default_acl default_acl
86
+ left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
87
+ cross join lateral pg_catalog.aclexplode(default_acl.defaclacl) privilege
88
+ cross join names where privilege.grantee<>0 and (
89
+ namespace.nspname='agent_bridge'
90
+ or default_acl.defaclrole in (names.schema_owner,
91
+ (select oid from pg_catalog.pg_roles where rolname=names.control_owner))
92
+ )
93
+ ), base_roles(oid) as (
94
+ select oid from derived_roles union select oid from object_roles
95
+ ), relevant_roles(oid) as (
96
+ select oid from base_roles
97
+ union select membership.roleid from pg_catalog.pg_auth_members membership
98
+ where membership.member in (select oid from base_roles)
99
+ union select membership.member from pg_catalog.pg_auth_members membership
100
+ where membership.roleid in (select oid from base_roles)
101
+ )
102
+ select role.rolname into rejected_role from relevant_roles relevant
103
+ join pg_catalog.pg_roles role on role.oid=relevant.oid
104
+ where role.rolname='PUBLIC' or position(':' in role.rolname)>0
105
+ or position(chr(30) in role.rolname)>0 or position(chr(31) in role.rolname)>0
106
+ order by role.rolname limit 1;
107
+
108
+ if rejected_role is not null then
109
+ raise exception 'native DR attestation cannot encode a reserved Agent Bridge role name'
110
+ using detail='rejected role: '||quote_literal(rejected_role);
111
+ end if;
112
+ end
113
+ $identifier_preflight$;
114
+
115
+ create or replace function agent_bridge.assert_control_actor(requested_capability text)
116
+ returns void language plpgsql security definer set search_path = '' as $$
117
+ declare
118
+ suffix text := substr(md5(current_database()),1,16);
119
+ schema_owner name;
120
+ operator_role name := ('agent_bridge_control_operator_'||suffix)::name;
121
+ auditor_role name := ('agent_bridge_control_auditor_'||suffix)::name;
122
+ operator_allowed boolean := false;
123
+ auditor_allowed boolean := false;
124
+ begin
125
+ select pg_catalog.pg_get_userbyid(namespace.nspowner)::name into schema_owner
126
+ from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge';
127
+ if requested_capability is null or requested_capability not in ('operator','inventory') then
128
+ raise exception 'control capability is invalid';
129
+ end if;
130
+
131
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
132
+ 'control-membership'||chr(31)||session_user||chr(31)||'global',1646705661
133
+ ));
134
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
135
+ 'control-membership'||chr(31)||session_user||chr(31)||'operator',1646705661
136
+ ));
137
+ if requested_capability='inventory' then
138
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
139
+ 'control-membership'||chr(31)||session_user||chr(31)||'auditor',1646705661
140
+ ));
141
+ end if;
142
+
143
+ with latest as (
144
+ select distinct on (event.control_role) event.control_role,event.action
145
+ from agent_bridge.control_membership_events event
146
+ where event.member_role=session_user and event.control_role in ('operator','auditor')
147
+ order by event.control_role,event.sequence desc
148
+ ), direct_membership as (
149
+ select granted.rolname control_role,membership.admin_option,
150
+ coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
151
+ coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option
152
+ from pg_catalog.pg_auth_members membership
153
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
154
+ join pg_catalog.pg_roles member on member.oid=membership.member
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 and (
160
+ (session_user=schema_owner and membership.admin_option)
161
+ or (latest.control_role='operator' and latest.action='register'
162
+ and not membership.admin_option)
163
+ )),false),
164
+ coalesce(bool_or(membership.control_role=auditor_role
165
+ and membership.inherit_option and membership.set_option and (
166
+ (session_user=schema_owner and membership.admin_option)
167
+ or (latest.control_role='auditor' and latest.action='register'
168
+ and not membership.admin_option)
169
+ )),false)
170
+ into operator_allowed,auditor_allowed
171
+ from direct_membership membership left join latest on latest.control_role=case
172
+ membership.control_role when operator_role then 'operator' else 'auditor' end;
173
+
174
+ if session_user<>schema_owner and (exists (
175
+ select 1 from pg_catalog.pg_roles inherited
176
+ where inherited.rolname<>session_user
177
+ and pg_catalog.pg_has_role(session_user,inherited.rolname,'MEMBER')
178
+ and not (
179
+ (inherited.rolname=operator_role and operator_allowed)
180
+ or (inherited.rolname=auditor_role and auditor_allowed)
181
+ )
182
+ ) or exists (
183
+ select 1 from pg_catalog.pg_roles candidate
184
+ where candidate.rolname not in (session_user,schema_owner)
185
+ and not candidate.rolsuper
186
+ and pg_catalog.pg_has_role(candidate.rolname,session_user,'MEMBER')
187
+ ) or exists (
188
+ select 1 from pg_catalog.pg_roles actor
189
+ where actor.rolname=session_user and (
190
+ not actor.rolcanlogin or actor.rolsuper or actor.rolcreaterole or actor.rolcreatedb
191
+ or actor.rolreplication or actor.rolbypassrls
192
+ )
193
+ )) then
194
+ raise exception 'control actor has an unsafe membership graph';
195
+ end if;
196
+
197
+ if (requested_capability='operator' and not operator_allowed)
198
+ or (requested_capability='inventory' and not (operator_allowed or auditor_allowed)) then
199
+ raise exception 'control actor is not registered for requested capability';
200
+ end if;
201
+ end
202
+ $$;
203
+
204
+ create or replace function agent_bridge.register_control_member(
205
+ requested_request_id uuid, requested_member_role name, requested_control_role text
206
+ ) returns table(replayed boolean)
207
+ language plpgsql security definer set search_path = '' as $$
208
+ declare
209
+ suffix text := substr(md5(current_database()),1,16);
210
+ target_role name;
211
+ prior agent_bridge.control_membership_events%rowtype;
212
+ member_record pg_catalog.pg_roles%rowtype;
213
+ begin
214
+ if requested_request_id is null or requested_member_role is null
215
+ or requested_control_role is null
216
+ or requested_control_role not in ('operator','auditor') then
217
+ raise exception 'invalid control membership registration';
218
+ end if;
219
+ perform pg_catalog.pg_advisory_xact_lock_shared(1646705664);
220
+ target_role := ('agent_bridge_control_'||requested_control_role||'_'||suffix)::name;
221
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
222
+ 'control-membership'||chr(31)||requested_member_role||chr(31)||'global',
223
+ 1646705661
224
+ ));
225
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
226
+ 'control-membership'||chr(31)||requested_member_role||chr(31)||requested_control_role,
227
+ 1646705661
228
+ ));
229
+ perform pg_catalog.pg_advisory_xact_lock(
230
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
231
+ );
232
+ select * into prior from agent_bridge.control_membership_events
233
+ where request_id=requested_request_id;
234
+ if found then
235
+ if prior.action<>'register' or prior.member_role<>requested_member_role
236
+ or prior.control_role<>requested_control_role then
237
+ raise exception 'request id was already used with different content';
238
+ end if;
239
+ return query select true;
240
+ return;
241
+ end if;
242
+ select * into member_record from pg_catalog.pg_roles where rolname=requested_member_role;
243
+ if not found or not member_record.rolcanlogin or member_record.rolsuper
244
+ or member_record.rolcreaterole or member_record.rolcreatedb
245
+ or member_record.rolreplication or member_record.rolbypassrls
246
+ or requested_member_role in (
247
+ current_user::name,
248
+ ('agent_bridge_control_owner_'||suffix)::name,
249
+ ('agent_bridge_control_operator_'||suffix)::name,
250
+ ('agent_bridge_control_auditor_'||suffix)::name
251
+ ) then
252
+ raise exception 'control membership target is not an eligible login';
253
+ end if;
254
+ if exists (
255
+ select 1 from pg_catalog.pg_roles inherited
256
+ where inherited.rolname<>requested_member_role
257
+ and pg_catalog.pg_has_role(requested_member_role,inherited.rolname,'MEMBER')
258
+ and inherited.rolname<>target_role
259
+ and not exists (
260
+ select 1 from (
261
+ select distinct on (event.control_role)
262
+ event.control_role,event.action
263
+ from agent_bridge.control_membership_events event
264
+ where event.member_role=requested_member_role
265
+ order by event.control_role,event.sequence desc
266
+ ) latest
267
+ where latest.action='register'
268
+ and inherited.rolname=(
269
+ 'agent_bridge_control_'||latest.control_role||'_'||suffix
270
+ )::name
271
+ )
272
+ ) or exists (
273
+ select 1 from pg_catalog.pg_roles candidate
274
+ where candidate.rolname not in (requested_member_role,current_user)
275
+ and not candidate.rolsuper
276
+ and pg_catalog.pg_has_role(candidate.rolname,requested_member_role,'MEMBER')
277
+ ) then
278
+ raise exception 'control membership target has an unsafe membership graph';
279
+ end if;
280
+ if exists (
281
+ select 1 from pg_catalog.pg_auth_members membership
282
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
283
+ join pg_catalog.pg_roles member on member.oid=membership.member
284
+ where granted.rolname=target_role and member.rolname=requested_member_role
285
+ and (
286
+ membership.admin_option
287
+ or not coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
288
+ or not coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
289
+ )
290
+ ) then
291
+ raise exception 'control membership target has an unsafe direct grant';
292
+ end if;
293
+ execute format('grant %I to %I',target_role,requested_member_role);
294
+ insert into agent_bridge.control_membership_events(
295
+ request_id,action,member_role,control_role,actor
296
+ ) values (
297
+ requested_request_id,'register',requested_member_role,requested_control_role,session_user
298
+ );
299
+ return query select false;
300
+ end
301
+ $$;
302
+
303
+ create or replace function agent_bridge.revoke_control_member(
304
+ requested_request_id uuid, requested_member_role name, requested_control_role text
305
+ ) returns table(replayed boolean)
306
+ language plpgsql security definer set search_path = '' as $$
307
+ declare
308
+ suffix text := substr(md5(current_database()),1,16);
309
+ target_role name;
310
+ prior agent_bridge.control_membership_events%rowtype;
311
+ begin
312
+ if requested_request_id is null or requested_member_role is null
313
+ or requested_control_role is null
314
+ or requested_control_role not in ('operator','auditor')
315
+ or requested_member_role in (
316
+ current_user::name,
317
+ ('agent_bridge_control_owner_'||suffix)::name,
318
+ ('agent_bridge_control_operator_'||suffix)::name,
319
+ ('agent_bridge_control_auditor_'||suffix)::name
320
+ ) then
321
+ raise exception 'invalid control membership revocation';
322
+ end if;
323
+ perform pg_catalog.pg_advisory_xact_lock_shared(1646705664);
324
+ target_role := ('agent_bridge_control_'||requested_control_role||'_'||suffix)::name;
325
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
326
+ 'control-membership'||chr(31)||requested_member_role||chr(31)||'global',
327
+ 1646705661
328
+ ));
329
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
330
+ 'control-membership'||chr(31)||requested_member_role||chr(31)||requested_control_role,
331
+ 1646705661
332
+ ));
333
+ perform pg_catalog.pg_advisory_xact_lock(
334
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
335
+ );
336
+ select * into prior from agent_bridge.control_membership_events
337
+ where request_id=requested_request_id;
338
+ if found then
339
+ if prior.action<>'revoke' or prior.member_role<>requested_member_role
340
+ or prior.control_role<>requested_control_role then
341
+ raise exception 'request id was already used with different content';
342
+ end if;
343
+ return query select true;
344
+ return;
345
+ end if;
346
+ if exists(select 1 from pg_catalog.pg_roles where rolname=requested_member_role) then
347
+ execute format('revoke %I from %I',target_role,requested_member_role);
348
+ end if;
349
+ insert into agent_bridge.control_membership_events(
350
+ request_id,action,member_role,control_role,actor
351
+ ) values (
352
+ requested_request_id,'revoke',requested_member_role,requested_control_role,session_user
353
+ );
354
+ return query select false;
355
+ end
356
+ $$;
357
+
358
+ create or replace function agent_bridge.assert_archive_actor()
359
+ returns void language plpgsql security definer set search_path = '' as $$
360
+ declare
361
+ suffix text := substr(md5(current_database()),1,16);
362
+ schema_owner name;
363
+ archive_role name := ('agent_bridge_archive_operator_'||suffix)::name;
364
+ archive_allowed boolean := false;
365
+ begin
366
+ if not agent_bridge.portable_archive_ready() then
367
+ raise exception 'portable archive readiness validation failed';
368
+ end if;
369
+ select pg_catalog.pg_get_userbyid(namespace.nspowner)::name into schema_owner
370
+ from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge';
371
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
372
+ 'archive-membership'||chr(31)||session_user||chr(31)||'global',1646705662
373
+ ));
374
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
375
+ 'archive-membership'||chr(31)||session_user||chr(31)||'operator',1646705662
376
+ ));
377
+
378
+ with latest as (
379
+ select event.action from agent_bridge.archive_membership_events event
380
+ where event.member_role=session_user order by event.sequence desc limit 1
381
+ ), direct_membership as (
382
+ select membership.admin_option,
383
+ coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
384
+ coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option
385
+ from pg_catalog.pg_auth_members membership
386
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
387
+ join pg_catalog.pg_roles member on member.oid=membership.member
388
+ where member.rolname=session_user and granted.rolname=archive_role
389
+ )
390
+ select coalesce(bool_or(
391
+ membership.inherit_option and membership.set_option and (
392
+ (session_user=schema_owner and membership.admin_option)
393
+ or (latest.action='register' and not membership.admin_option)
394
+ )
395
+ ),false) into archive_allowed
396
+ from direct_membership membership left join latest on true;
397
+
398
+ if session_user<>schema_owner and (exists (
399
+ select 1 from pg_catalog.pg_roles inherited
400
+ where inherited.rolname<>session_user
401
+ and pg_catalog.pg_has_role(session_user,inherited.rolname,'MEMBER')
402
+ and inherited.rolname<>archive_role
403
+ ) or exists (
404
+ select 1 from pg_catalog.pg_roles candidate
405
+ where candidate.rolname not in (session_user,schema_owner)
406
+ and not candidate.rolsuper
407
+ and pg_catalog.pg_has_role(candidate.rolname,session_user,'MEMBER')
408
+ ) or exists (
409
+ select 1 from pg_catalog.pg_roles actor where actor.rolname=session_user and (
410
+ not actor.rolcanlogin or actor.rolsuper or actor.rolcreaterole or actor.rolcreatedb
411
+ or actor.rolreplication or actor.rolbypassrls
412
+ )
413
+ )) then
414
+ raise exception 'archive actor has an unsafe membership graph';
415
+ end if;
416
+ if not archive_allowed then
417
+ raise exception 'archive actor is not registered';
418
+ end if;
419
+ end
420
+ $$;
421
+
422
+ create or replace function agent_bridge.register_archive_member(
423
+ requested_request_id uuid, requested_member_role name
424
+ ) returns table(replayed boolean)
425
+ language plpgsql security definer set search_path = '' as $$
426
+ declare
427
+ suffix text := substr(md5(current_database()),1,16);
428
+ archive_role name := ('agent_bridge_archive_operator_'||suffix)::name;
429
+ prior agent_bridge.archive_membership_events%rowtype;
430
+ member_record pg_catalog.pg_roles%rowtype;
431
+ begin
432
+ perform pg_catalog.pg_advisory_xact_lock_shared(1646705664);
433
+ if not agent_bridge.portable_archive_ready() then
434
+ raise exception 'portable archive readiness validation failed';
435
+ end if;
436
+ if requested_request_id is null or requested_member_role is null
437
+ or requested_member_role in (current_user::name,archive_role) then
438
+ raise exception 'invalid archive membership registration';
439
+ end if;
440
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
441
+ 'archive-membership'||chr(31)||requested_member_role||chr(31)||'global',1646705662
442
+ ));
443
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
444
+ 'archive-membership'||chr(31)||requested_member_role||chr(31)||'operator',1646705662
445
+ ));
446
+ perform pg_catalog.pg_advisory_xact_lock(
447
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
448
+ );
449
+ select * into prior from agent_bridge.archive_membership_events
450
+ where request_id=requested_request_id;
451
+ if found then
452
+ if prior.action<>'register' or prior.member_role<>requested_member_role then
453
+ raise exception 'request id was already used with different content';
454
+ end if;
455
+ return query select true;
456
+ return;
457
+ end if;
458
+ select * into member_record from pg_catalog.pg_roles where rolname=requested_member_role;
459
+ if not found or not member_record.rolcanlogin or member_record.rolsuper
460
+ or member_record.rolcreaterole or member_record.rolcreatedb
461
+ or member_record.rolreplication or member_record.rolbypassrls then
462
+ raise exception 'archive membership target is not an eligible login';
463
+ end if;
464
+ if exists (
465
+ select 1 from pg_catalog.pg_roles inherited
466
+ where inherited.rolname<>requested_member_role
467
+ and pg_catalog.pg_has_role(requested_member_role,inherited.rolname,'MEMBER')
468
+ and inherited.rolname<>archive_role
469
+ ) or exists (
470
+ select 1 from pg_catalog.pg_roles candidate
471
+ where candidate.rolname not in (requested_member_role,current_user)
472
+ and not candidate.rolsuper
473
+ and pg_catalog.pg_has_role(candidate.rolname,requested_member_role,'MEMBER')
474
+ ) then
475
+ raise exception 'archive membership target has an unsafe membership graph';
476
+ end if;
477
+ if exists (
478
+ select 1 from pg_catalog.pg_auth_members membership
479
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
480
+ join pg_catalog.pg_roles member on member.oid=membership.member
481
+ where granted.rolname=archive_role and member.rolname=requested_member_role and (
482
+ membership.admin_option
483
+ or not coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
484
+ or not coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
485
+ )
486
+ ) then
487
+ raise exception 'archive membership target has an unsafe direct grant';
488
+ end if;
489
+ execute format('grant %I to %I',archive_role,requested_member_role);
490
+ insert into agent_bridge.archive_membership_events(request_id,action,member_role,actor)
491
+ values(requested_request_id,'register',requested_member_role,session_user);
492
+ return query select false;
493
+ end
494
+ $$;
495
+
496
+ create or replace function agent_bridge.revoke_archive_member(
497
+ requested_request_id uuid, requested_member_role name
498
+ ) returns table(replayed boolean)
499
+ language plpgsql security definer set search_path = '' as $$
500
+ declare
501
+ suffix text := substr(md5(current_database()),1,16);
502
+ archive_role name := ('agent_bridge_archive_operator_'||suffix)::name;
503
+ prior agent_bridge.archive_membership_events%rowtype;
504
+ begin
505
+ perform pg_catalog.pg_advisory_xact_lock_shared(1646705664);
506
+ if not agent_bridge.portable_archive_ready() then
507
+ raise exception 'portable archive readiness validation failed';
508
+ end if;
509
+ if requested_request_id is null or requested_member_role is null
510
+ or requested_member_role in (current_user::name,archive_role) then
511
+ raise exception 'invalid archive membership revocation';
512
+ end if;
513
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
514
+ 'archive-membership'||chr(31)||requested_member_role||chr(31)||'global',1646705662
515
+ ));
516
+ perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
517
+ 'archive-membership'||chr(31)||requested_member_role||chr(31)||'operator',1646705662
518
+ ));
519
+ perform pg_catalog.pg_advisory_xact_lock(
520
+ pg_catalog.hashtextextended(requested_request_id::text,1646705660)
521
+ );
522
+ select * into prior from agent_bridge.archive_membership_events
523
+ where request_id=requested_request_id;
524
+ if found then
525
+ if prior.action<>'revoke' or prior.member_role<>requested_member_role then
526
+ raise exception 'request id was already used with different content';
527
+ end if;
528
+ return query select true;
529
+ return;
530
+ end if;
531
+ if exists(select 1 from pg_catalog.pg_roles where rolname=requested_member_role) then
532
+ execute format('revoke %I from %I',archive_role,requested_member_role);
533
+ end if;
534
+ insert into agent_bridge.archive_membership_events(request_id,action,member_role,actor)
535
+ values(requested_request_id,'revoke',requested_member_role,session_user);
536
+ return query select false;
537
+ end
538
+ $$;
539
+
540
+ alter table agent_bridge.portable_archive_attestations
541
+ drop constraint portable_archive_attestation_name;
542
+ alter table agent_bridge.portable_archive_attestations
543
+ add constraint portable_archive_attestation_name
544
+ check(name ~ '^portable-archive-v[0-9]+$');
545
+
546
+ create or replace function agent_bridge.owner_control_attestation_definition()
547
+ returns text language sql stable set search_path = '' as $$
548
+ with names as (select
549
+ ('agent_bridge_control_owner_'||substr(md5(current_database()),1,16))::name owner_role,
550
+ ('agent_bridge_control_operator_'||substr(md5(current_database()),1,16))::name operator_role,
551
+ ('agent_bridge_control_auditor_'||substr(md5(current_database()),1,16))::name auditor_role,
552
+ (select namespace.nspowner from pg_catalog.pg_namespace namespace
553
+ where namespace.nspname='agent_bridge') schema_owner
554
+ ), control_roles(role_name) as (
555
+ select owner_role from names union all select operator_role from names
556
+ union all select auditor_role from names
557
+ ), raw_records(record) as (
558
+ select unnest(string_to_array(
559
+ agent_bridge.owner_control_catalog_definition(),E'\x1e'
560
+ ))
561
+ ), protected_relations(oid) as (
562
+ select distinct pg_catalog.to_regclass(split_part(record,E'\x1f',2))
563
+ from raw_records where split_part(record,E'\x1f',1) in ('relation','relation_acl')
564
+ union
565
+ select distinct relation.oid
566
+ from raw_records
567
+ join pg_catalog.pg_attribute attribute
568
+ on not attribute.attisdropped and attribute.attnum>0
569
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
570
+ and relation.oid::regclass::text||'.'||attribute.attname=
571
+ split_part(record,E'\x1f',2)
572
+ where split_part(record,E'\x1f',1)='column_acl'
573
+ ), default_acl_keys(owner_oid,namespace_oid,object_type) as (
574
+ select role.oid,0::oid,object_type
575
+ from pg_catalog.pg_roles role
576
+ cross join (values ('r'::"char"),('S'::"char"),('f'::"char"),
577
+ ('T'::"char"),('n'::"char")) object_types(object_type)
578
+ where role.oid in (
579
+ (select schema_owner from names),
580
+ (select oid from pg_catalog.pg_roles where rolname=(select owner_role from names))
581
+ )
582
+ union
583
+ select default_acl.defaclrole,default_acl.defaclnamespace,default_acl.defaclobjtype
584
+ from pg_catalog.pg_default_acl default_acl
585
+ left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
586
+ where (namespace.nspname='agent_bridge' or default_acl.defaclnamespace=0)
587
+ and default_acl.defaclrole in (
588
+ (select schema_owner from names),
589
+ (select oid from pg_catalog.pg_roles where rolname=(select owner_role from names))
590
+ )
591
+ ), catalog_objects(kind,identity,definition) as (
592
+ select split_part(record,E'\x1f',1),split_part(record,E'\x1f',2),
593
+ split_part(record,E'\x1f',3)
594
+ from raw_records where split_part(record,E'\x1f',1) not in (
595
+ 'membership','relation_acl','column_acl','default_acl'
596
+ )
597
+ union all
598
+ select 'membership',granted.rolname||'->'||member.rolname,
599
+ membership.admin_option::text||':'||
600
+ coalesce(to_jsonb(membership)->>'inherit_option','true')||':'||
601
+ coalesce(to_jsonb(membership)->>'set_option','true')
602
+ from pg_catalog.pg_auth_members membership
603
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
604
+ join pg_catalog.pg_roles member on member.oid=membership.member
605
+ where member.rolname in (select role_name from control_roles)
606
+ or granted.rolname=(select owner_role from names)
607
+ union all
608
+ select 'relation_acl',relation.oid::regclass::text,
609
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
610
+ case when privilege.grantee=0 then 'PUBLIC'
611
+ else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
612
+ privilege.privilege_type||':'||privilege.is_grantable::text
613
+ from pg_catalog.pg_class relation
614
+ cross join lateral pg_catalog.aclexplode(coalesce(relation.relacl,
615
+ pg_catalog.acldefault(
616
+ case when relation.relkind='S' then 'S'::"char" else 'r'::"char" end,
617
+ relation.relowner
618
+ ))) privilege
619
+ where relation.oid in (select oid from protected_relations)
620
+ and relation.relkind in ('r','p','v','m','f','S')
621
+ and privilege.grantee<>relation.relowner
622
+ union all
623
+ select 'column_acl',relation.oid::regclass::text||'.'||attribute.attname,
624
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
625
+ case when privilege.grantee=0 then 'PUBLIC'
626
+ else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
627
+ privilege.privilege_type||':'||privilege.is_grantable::text
628
+ from pg_catalog.pg_attribute attribute
629
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
630
+ cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
631
+ where relation.oid in (select oid from protected_relations)
632
+ and attribute.attnum>0 and not attribute.attisdropped
633
+ and attribute.attacl is not null and privilege.grantee<>relation.relowner
634
+ union all
635
+ select 'default_acl',owner.rolname||':'||coalesce(namespace.nspname,'')||':'||
636
+ keys.object_type::text,
637
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
638
+ case when privilege.grantee=0 then 'PUBLIC'
639
+ else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
640
+ privilege.privilege_type||':'||privilege.is_grantable::text
641
+ from default_acl_keys keys
642
+ join pg_catalog.pg_roles owner on owner.oid=keys.owner_oid
643
+ left join pg_catalog.pg_namespace namespace on namespace.oid=keys.namespace_oid
644
+ left join pg_catalog.pg_default_acl default_acl
645
+ on default_acl.defaclrole=keys.owner_oid
646
+ and default_acl.defaclnamespace=keys.namespace_oid
647
+ and default_acl.defaclobjtype=keys.object_type
648
+ cross join lateral pg_catalog.aclexplode(case when keys.namespace_oid=0 then
649
+ coalesce(default_acl.defaclacl,pg_catalog.acldefault(keys.object_type,keys.owner_oid))
650
+ else coalesce(default_acl.defaclacl,'{}'::pg_catalog.aclitem[]) end) privilege
651
+ where privilege.grantee<>keys.owner_oid
652
+ union all
653
+ select 'attestation_function',procedure.oid::regprocedure::text,
654
+ pg_catalog.pg_get_userbyid(procedure.proowner)||':'||
655
+ pg_catalog.pg_get_functiondef(procedure.oid)
656
+ from pg_catalog.pg_proc procedure
657
+ where procedure.oid=
658
+ 'agent_bridge.owner_control_attestation_definition()'::regprocedure
659
+ )
660
+ select string_agg(kind||E'\x1f'||identity||E'\x1f'||definition,E'\x1e'
661
+ order by kind,identity,definition) from catalog_objects
662
+ $$;
663
+
664
+ create or replace function agent_bridge.portable_archive_attestation_definition()
665
+ returns text language sql stable set search_path = '' as $$
666
+ with names as (select
667
+ ('agent_bridge_archive_operator_'||substr(md5(current_database()),1,16))::name archive_role,
668
+ namespace.nspowner schema_owner
669
+ from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge'
670
+ ), raw_records(record) as (
671
+ select unnest(string_to_array(
672
+ agent_bridge.portable_archive_catalog_definition(),E'\x1e'
673
+ ))
674
+ ), protected_relations(oid) as (
675
+ select distinct pg_catalog.to_regclass(split_part(record,E'\x1f',2))
676
+ from raw_records where split_part(record,E'\x1f',1)='relation'
677
+ ), default_acl_keys(owner_oid,namespace_oid,object_type) as (
678
+ select (select schema_owner from names),0::oid,object_type
679
+ from (values ('r'::"char"),('S'::"char"),('f'::"char"),
680
+ ('T'::"char"),('n'::"char")) object_types(object_type)
681
+ union
682
+ select default_acl.defaclrole,default_acl.defaclnamespace,default_acl.defaclobjtype
683
+ from pg_catalog.pg_default_acl default_acl
684
+ left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
685
+ where namespace.nspname='agent_bridge'
686
+ or default_acl.defaclrole=(select schema_owner from names)
687
+ ), catalog_objects(kind,identity,definition) as (
688
+ select split_part(record,E'\x1f',1),split_part(record,E'\x1f',2),
689
+ split_part(record,E'\x1f',3)
690
+ from raw_records where split_part(record,E'\x1f',1) not in (
691
+ 'owner_membership','relation_acl','column_acl','default_acl'
692
+ )
693
+ union all
694
+ select 'owner_membership',granted.rolname||'->'||member.rolname,
695
+ membership.admin_option::text||':'||
696
+ coalesce(to_jsonb(membership)->>'inherit_option','true')||':'||
697
+ coalesce(to_jsonb(membership)->>'set_option','true')
698
+ from pg_catalog.pg_auth_members membership
699
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
700
+ join pg_catalog.pg_roles member on member.oid=membership.member
701
+ where granted.rolname=(select archive_role from names)
702
+ and member.oid=(select schema_owner from names)
703
+ union all
704
+ select 'relation_acl',relation.oid::regclass::text,
705
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
706
+ case when privilege.grantee=0 then 'PUBLIC'
707
+ else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
708
+ privilege.privilege_type||':'||privilege.is_grantable::text
709
+ from pg_catalog.pg_class relation
710
+ cross join lateral pg_catalog.aclexplode(coalesce(relation.relacl,
711
+ pg_catalog.acldefault(
712
+ case when relation.relkind='S' then 'S'::"char" else 'r'::"char" end,
713
+ relation.relowner
714
+ ))) privilege
715
+ where relation.oid in (select oid from protected_relations)
716
+ and relation.relkind in ('r','p','v','m','f','S')
717
+ and privilege.grantee<>relation.relowner
718
+ union all
719
+ select 'column_acl',relation.oid::regclass::text||'.'||attribute.attname,
720
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
721
+ case when privilege.grantee=0 then 'PUBLIC'
722
+ else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
723
+ privilege.privilege_type||':'||privilege.is_grantable::text
724
+ from pg_catalog.pg_attribute attribute
725
+ join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
726
+ cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
727
+ where relation.oid in (select oid from protected_relations)
728
+ and attribute.attnum>0 and not attribute.attisdropped
729
+ and attribute.attacl is not null and privilege.grantee<>relation.relowner
730
+ union all
731
+ select 'default_acl',owner.rolname||':'||coalesce(namespace.nspname,'')||':'||
732
+ keys.object_type::text,
733
+ pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
734
+ case when privilege.grantee=0 then 'PUBLIC'
735
+ else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
736
+ privilege.privilege_type||':'||privilege.is_grantable::text
737
+ from default_acl_keys keys
738
+ join pg_catalog.pg_roles owner on owner.oid=keys.owner_oid
739
+ left join pg_catalog.pg_namespace namespace on namespace.oid=keys.namespace_oid
740
+ left join pg_catalog.pg_default_acl default_acl
741
+ on default_acl.defaclrole=keys.owner_oid
742
+ and default_acl.defaclnamespace=keys.namespace_oid
743
+ and default_acl.defaclobjtype=keys.object_type
744
+ cross join lateral pg_catalog.aclexplode(case when keys.namespace_oid=0 then
745
+ coalesce(default_acl.defaclacl,pg_catalog.acldefault(keys.object_type,keys.owner_oid))
746
+ else coalesce(default_acl.defaclacl,'{}'::pg_catalog.aclitem[]) end) privilege
747
+ where privilege.grantee<>keys.owner_oid
748
+ union all
749
+ select 'attestation_function',procedure.oid::regprocedure::text,
750
+ pg_catalog.pg_get_userbyid(procedure.proowner)||':'||
751
+ pg_catalog.pg_get_functiondef(procedure.oid)
752
+ from pg_catalog.pg_proc procedure
753
+ where procedure.oid=
754
+ 'agent_bridge.portable_archive_attestation_definition()'::regprocedure
755
+ )
756
+ select string_agg(kind||E'\x1f'||identity||E'\x1f'||definition,E'\x1e'
757
+ order by kind,identity,definition) from catalog_objects
758
+ $$;
759
+
760
+ revoke execute on function agent_bridge.owner_control_attestation_definition() from public;
761
+ revoke execute on function agent_bridge.portable_archive_attestation_definition() from public;
762
+
763
+ do $ownership$
764
+ declare
765
+ owner_role text := 'agent_bridge_control_owner_'||substr(md5(current_database()),1,16);
766
+ begin
767
+ execute format(
768
+ 'alter function agent_bridge.owner_control_attestation_definition() owner to %I',
769
+ owner_role
770
+ );
771
+ end
772
+ $ownership$;
773
+
774
+ create or replace function agent_bridge.portable_archive_ready()
775
+ returns boolean language sql stable security definer set search_path = '' as $$
776
+ with names as (select
777
+ ('agent_bridge_archive_operator_'||substr(md5(current_database()),1,16))::name archive_role,
778
+ (select pg_catalog.pg_get_userbyid(nspowner)::name from pg_catalog.pg_namespace
779
+ where nspname='agent_bridge') schema_owner
780
+ ), latest_registry as (
781
+ select distinct on (event.member_role) event.member_role,event.action
782
+ from agent_bridge.archive_membership_events event order by event.member_role,event.sequence desc
783
+ ), active_registry as (
784
+ select member_role from latest_registry where action='register'
785
+ ), expected_memberships as (
786
+ select names.archive_role granted_role,names.schema_owner member_role,true admin_option from names
787
+ union all select names.archive_role,registry.member_role,false from active_registry registry cross join names
788
+ ), actual_memberships as (
789
+ select granted.rolname::name granted_role,member.rolname::name member_role,
790
+ membership.admin_option,
791
+ coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
792
+ coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option
793
+ from pg_catalog.pg_auth_members membership
794
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
795
+ join pg_catalog.pg_roles member on member.oid=membership.member
796
+ where granted.rolname=(select archive_role from names)
797
+ )
798
+ select current_setting('server_version_num')::integer/10000=any(array[15,16,17,18])
799
+ and exists(select 1 from pg_catalog.pg_roles role,names where role.rolname=names.archive_role
800
+ and not role.rolcanlogin and not role.rolsuper and not role.rolcreatedb
801
+ and not role.rolcreaterole and not role.rolreplication and not role.rolbypassrls)
802
+ and (select count(*)=1 and bool_and(
803
+ attestation.catalog_definition=agent_bridge.portable_archive_attestation_definition()
804
+ ) from agent_bridge.portable_archive_attestations attestation
805
+ where attestation.name='portable-archive-v3')
806
+ and not exists(
807
+ (select granted_role,member_role,admin_option from actual_memberships
808
+ except select granted_role,member_role,admin_option from expected_memberships)
809
+ union all
810
+ (select granted_role,member_role,admin_option from expected_memberships
811
+ except select granted_role,member_role,admin_option from actual_memberships)
812
+ )
813
+ and not exists(select 1 from actual_memberships membership
814
+ where not membership.inherit_option or not membership.set_option)
815
+ and not exists(select 1 from active_registry registry
816
+ left join pg_catalog.pg_roles role on role.rolname=registry.member_role
817
+ where role.oid is null or not role.rolcanlogin or role.rolsuper or role.rolcreaterole
818
+ or role.rolcreatedb or role.rolreplication or role.rolbypassrls)
819
+ and not exists(select 1 from active_registry registry
820
+ join pg_catalog.pg_roles inherited on inherited.rolname<>registry.member_role
821
+ cross join names
822
+ where pg_catalog.pg_has_role(registry.member_role,inherited.rolname,'MEMBER')
823
+ and inherited.rolname<>names.archive_role)
824
+ and not exists(select 1 from active_registry registry cross join pg_catalog.pg_roles candidate,names
825
+ where candidate.rolname not in (registry.member_role,names.schema_owner)
826
+ and not candidate.rolsuper
827
+ and pg_catalog.pg_has_role(candidate.rolname,registry.member_role,'MEMBER'))
828
+ and has_schema_privilege((select archive_role from names),'agent_bridge','USAGE')
829
+ and not has_schema_privilege((select archive_role from names),'agent_bridge','CREATE')
830
+ and not exists(select 1 from pg_catalog.pg_class relation
831
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
832
+ where namespace.nspname='agent_bridge' and relation.relkind in ('r','p','v','m','f')
833
+ and has_table_privilege((select archive_role from names),relation.oid,
834
+ 'SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER'))
835
+ and not exists(select 1 from pg_catalog.pg_class sequence_record
836
+ join pg_catalog.pg_namespace namespace on namespace.oid=sequence_record.relnamespace
837
+ where namespace.nspname='agent_bridge'
838
+ and case when sequence_record.relkind='S' then has_sequence_privilege(
839
+ (select archive_role from names),sequence_record.oid,'USAGE,SELECT,UPDATE'
840
+ ) else false end)
841
+ and not exists(select 1 from pg_catalog.pg_proc procedure
842
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
843
+ where namespace.nspname='agent_bridge' and has_function_privilege('public',procedure.oid,'EXECUTE'))
844
+ and not exists(select 1 from pg_catalog.pg_proc procedure
845
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
846
+ where namespace.nspname='agent_bridge'
847
+ and has_function_privilege((select archive_role from names),procedure.oid,'EXECUTE')
848
+ and procedure.oid not in (
849
+ 'agent_bridge.archive_begin_operation(uuid,text,text,character,bigint,bigint)'::regprocedure,
850
+ 'agent_bridge.archive_authorize_transaction(uuid,text,text,character)'::regprocedure,
851
+ 'agent_bridge.archive_close_transaction_authorization(uuid)'::regprocedure,
852
+ 'agent_bridge.archive_export_messages(uuid,text,timestamp with time zone,uuid,integer)'::regprocedure,
853
+ 'agent_bridge.archive_export_receipts(uuid,text,timestamp with time zone,uuid,text,integer)'::regprocedure,
854
+ 'agent_bridge.archive_import_messages(uuid,text,character,bigint,jsonb)'::regprocedure,
855
+ 'agent_bridge.archive_import_receipts(uuid,text,character,bigint,jsonb)'::regprocedure,
856
+ 'agent_bridge.archive_complete_export(uuid,text,character,bigint,bigint,timestamp with time zone,text)'::regprocedure,
857
+ 'agent_bridge.archive_reconcile_export(uuid,text,character,bigint,bigint,timestamp with time zone)'::regprocedure,
858
+ 'agent_bridge.archive_complete_import(uuid,text,boolean)'::regprocedure,
859
+ 'agent_bridge.archive_abandon_operation(uuid,text,text)'::regprocedure,
860
+ 'agent_bridge.portable_archive_ready()'::regprocedure
861
+ ))
862
+ and has_function_privilege((select archive_role from names),
863
+ 'agent_bridge.archive_begin_operation(uuid,text,text,character,bigint,bigint)','EXECUTE')
864
+ and has_function_privilege((select archive_role from names),
865
+ 'agent_bridge.archive_authorize_transaction(uuid,text,text,character)','EXECUTE')
866
+ and has_function_privilege((select archive_role from names),
867
+ 'agent_bridge.archive_close_transaction_authorization(uuid)','EXECUTE')
868
+ and has_function_privilege((select archive_role from names),
869
+ 'agent_bridge.archive_export_messages(uuid,text,timestamp with time zone,uuid,integer)','EXECUTE')
870
+ and has_function_privilege((select archive_role from names),
871
+ 'agent_bridge.archive_export_receipts(uuid,text,timestamp with time zone,uuid,text,integer)','EXECUTE')
872
+ and has_function_privilege((select archive_role from names),
873
+ 'agent_bridge.archive_import_messages(uuid,text,character,bigint,jsonb)','EXECUTE')
874
+ and has_function_privilege((select archive_role from names),
875
+ 'agent_bridge.archive_import_receipts(uuid,text,character,bigint,jsonb)','EXECUTE')
876
+ and has_function_privilege((select archive_role from names),
877
+ 'agent_bridge.archive_complete_export(uuid,text,character,bigint,bigint,timestamp with time zone,text)','EXECUTE')
878
+ and has_function_privilege((select archive_role from names),
879
+ 'agent_bridge.archive_reconcile_export(uuid,text,character,bigint,bigint,timestamp with time zone)','EXECUTE')
880
+ and has_function_privilege((select archive_role from names),
881
+ 'agent_bridge.archive_complete_import(uuid,text,boolean)','EXECUTE')
882
+ and has_function_privilege((select archive_role from names),
883
+ 'agent_bridge.archive_abandon_operation(uuid,text,text)','EXECUTE')
884
+ and has_function_privilege((select archive_role from names),
885
+ 'agent_bridge.portable_archive_ready()','EXECUTE')
886
+ $$;
887
+
888
+ create or replace function agent_bridge.owner_control_plane_ready()
889
+ returns boolean language sql stable security definer set search_path = '' as $$
890
+ with names as (select
891
+ ('agent_bridge_control_owner_'||substr(md5(current_database()),1,16))::name owner_role,
892
+ ('agent_bridge_control_operator_'||substr(md5(current_database()),1,16))::name operator_role,
893
+ ('agent_bridge_control_auditor_'||substr(md5(current_database()),1,16))::name auditor_role,
894
+ (select pg_catalog.pg_get_userbyid(nspowner)::name from pg_catalog.pg_namespace
895
+ where nspname='agent_bridge') schema_owner
896
+ ), latest_registry as (
897
+ select distinct on (event.member_role,event.control_role)
898
+ event.member_role,event.control_role,event.action
899
+ from agent_bridge.control_membership_events event
900
+ order by event.member_role,event.control_role,event.sequence desc
901
+ ), active_registry as (
902
+ select member_role,control_role from latest_registry where action='register'
903
+ ), expected_memberships as (
904
+ select names.owner_role granted_role,names.schema_owner member_role,true admin_option from names
905
+ union all select names.operator_role,names.schema_owner,true from names
906
+ union all select names.auditor_role,names.schema_owner,true from names
907
+ union all
908
+ select case registry.control_role when 'operator' then names.operator_role
909
+ else names.auditor_role end,registry.member_role,false
910
+ from active_registry registry cross join names
911
+ ), actual_memberships as (
912
+ select granted.rolname::name granted_role,member.rolname::name member_role,
913
+ membership.admin_option,
914
+ coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
915
+ coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option
916
+ from pg_catalog.pg_auth_members membership
917
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
918
+ join pg_catalog.pg_roles member on member.oid=membership.member
919
+ where granted.rolname in (
920
+ (select owner_role from names),(select operator_role from names),(select auditor_role from names)
921
+ )
922
+ ), authority_closure as (
923
+ select control_role.granted_role,candidate.rolname::name member_role
924
+ from names
925
+ cross join lateral (values
926
+ (names.owner_role),(names.operator_role),(names.auditor_role)
927
+ ) control_role(granted_role)
928
+ cross join pg_catalog.pg_roles candidate
929
+ where candidate.rolname not in (names.owner_role,names.operator_role,names.auditor_role)
930
+ and (not candidate.rolsuper or candidate.rolname=names.schema_owner)
931
+ and pg_catalog.pg_has_role(candidate.rolname,control_role.granted_role,'MEMBER')
932
+ )
933
+ select
934
+ current_setting('server_version_num')::integer/10000=any(array[15,16,17,18])
935
+ and (select count(*)=1 and bool_and(
936
+ attestation.catalog_definition=agent_bridge.owner_control_attestation_definition()
937
+ ) from agent_bridge.owner_control_attestations attestation
938
+ where attestation.name='owner-control-v4')
939
+ and not exists (
940
+ (select granted_role,member_role,admin_option from actual_memberships
941
+ except select granted_role,member_role,admin_option from expected_memberships)
942
+ union all
943
+ (select granted_role,member_role,admin_option from expected_memberships
944
+ except select granted_role,member_role,admin_option from actual_memberships)
945
+ )
946
+ and not exists (select 1 from actual_memberships membership
947
+ where not membership.inherit_option or not membership.set_option)
948
+ and not exists (
949
+ select 1 from active_registry registry
950
+ left join pg_catalog.pg_roles role_record on role_record.rolname=registry.member_role
951
+ where role_record.oid is null or not role_record.rolcanlogin or role_record.rolsuper
952
+ or role_record.rolcreaterole or role_record.rolcreatedb
953
+ or role_record.rolreplication or role_record.rolbypassrls
954
+ )
955
+ and not exists (
956
+ (select granted_role,member_role from authority_closure
957
+ except select granted_role,member_role from expected_memberships)
958
+ union all
959
+ (select granted_role,member_role from expected_memberships
960
+ except select granted_role,member_role from authority_closure)
961
+ )
962
+ and not exists (
963
+ select 1 from active_registry registry
964
+ join pg_catalog.pg_roles inherited on inherited.rolname<>registry.member_role
965
+ where pg_catalog.pg_has_role(registry.member_role,inherited.rolname,'MEMBER')
966
+ and not exists (
967
+ select 1 from active_registry permitted
968
+ cross join names
969
+ where permitted.member_role=registry.member_role
970
+ and inherited.rolname=case permitted.control_role
971
+ when 'operator' then names.operator_role else names.auditor_role end
972
+ )
973
+ )
974
+ and not exists (
975
+ select 1 from active_registry registry
976
+ cross join pg_catalog.pg_roles candidate
977
+ cross join names
978
+ where candidate.rolname not in (registry.member_role,names.schema_owner)
979
+ and not candidate.rolsuper
980
+ and pg_catalog.pg_has_role(candidate.rolname,registry.member_role,'MEMBER')
981
+ )
982
+ $$;
983
+
984
+ insert into agent_bridge.owner_control_attestations(name,catalog_definition)
985
+ values('owner-control-v4',agent_bridge.owner_control_attestation_definition());
986
+
987
+ insert into agent_bridge.portable_archive_attestations(name,catalog_definition)
988
+ values('portable-archive-v3',agent_bridge.portable_archive_attestation_definition());
989
+
990
+ do $preflight$
991
+ begin
992
+ if not agent_bridge.security_schema_ready() then
993
+ raise exception 'native DR fence security readiness validation failed';
994
+ elseif not agent_bridge.owner_control_plane_ready() then
995
+ raise exception 'native DR fence owner readiness validation failed';
996
+ elseif not agent_bridge.portable_archive_ready() then
997
+ raise exception 'native DR fence portable archive readiness validation failed';
998
+ end if;
999
+ end
1000
+ $preflight$;
1001
+
1002
+ insert into agent_bridge.schema_migrations(version,name,checksum)
1003
+ values(16,'native_dr_fence','__AGENT_BRIDGE_MIGRATION_CHECKSUM__');
1004
+
1005
+ commit;