@creatornader/agent-bridge 0.4.0 → 0.5.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 (77) hide show
  1. package/CHANGELOG.md +85 -1
  2. package/README.md +76 -7
  3. package/ROADMAP.md +29 -0
  4. package/SECURITY.md +30 -0
  5. package/SKILL.md +5 -1
  6. package/dist/archive.d.ts +2 -2
  7. package/dist/{bridge-service-YTjGr91A.d.ts → bridge-service-CNBKdYXh.d.ts} +2 -1
  8. package/dist/{bridge-store-Boaj-ZbO.d.ts → bridge-store-ByJ6CaJk.d.ts} +1 -0
  9. package/dist/bridge.d.ts +15 -8
  10. package/dist/bridge.js +10 -8
  11. package/dist/{chunk-PDIHDBP3.js → chunk-7VJI2MXI.js} +116 -26
  12. package/dist/chunk-7VJI2MXI.js.map +1 -0
  13. package/dist/{chunk-4VB44KJU.js → chunk-AWE3DFZV.js} +3 -2
  14. package/dist/chunk-AWE3DFZV.js.map +1 -0
  15. package/dist/{chunk-IHZFI7TH.js → chunk-E4OWHGUE.js} +5 -2
  16. package/dist/chunk-E4OWHGUE.js.map +1 -0
  17. package/dist/{chunk-SYGONYMM.js → chunk-HUHG7XTB.js} +6 -5
  18. package/dist/chunk-HUHG7XTB.js.map +1 -0
  19. package/dist/{chunk-DQOZJJZ2.js → chunk-IVG4XWWS.js} +5 -5
  20. package/dist/{chunk-DQOZJJZ2.js.map → chunk-IVG4XWWS.js.map} +1 -1
  21. package/dist/{chunk-3YHVUPU2.js → chunk-P635FAN4.js} +4 -4
  22. package/dist/chunk-P635FAN4.js.map +1 -0
  23. package/dist/{chunk-6BTJE6D4.js → chunk-QNXHDOJ5.js} +56 -23
  24. package/dist/chunk-QNXHDOJ5.js.map +1 -0
  25. package/dist/{chunk-MCKKVNCE.js → chunk-QPC6JHA3.js} +5 -5
  26. package/dist/{chunk-SV3SVBYS.js → chunk-SN7PIVDF.js} +2 -2
  27. package/dist/{chunk-2DFNOLMO.js → chunk-XDNT6ZNZ.js} +9 -6
  28. package/dist/chunk-XDNT6ZNZ.js.map +1 -0
  29. package/dist/{chunk-LAHNQZDN.js → chunk-Z6JP6MYD.js} +2 -2
  30. package/dist/cli.js +9 -9
  31. package/dist/cli.js.map +1 -1
  32. package/dist/client-runtime.d.ts +3 -3
  33. package/dist/client-runtime.js +6 -6
  34. package/dist/contracts/generate.js +1 -1
  35. package/dist/dr.js +3 -3
  36. package/dist/{gateway-BQHpV-MZ.d.ts → gateway-CW-61lvs.d.ts} +2 -2
  37. package/dist/gateway-main.js +71 -7
  38. package/dist/gateway-main.js.map +1 -1
  39. package/dist/gateway.d.ts +3 -3
  40. package/dist/gateway.js +3 -3
  41. package/dist/index.js +7 -7
  42. package/dist/{postgres-bridge-store-WASumj26.d.ts → postgres-bridge-store-C9KG9Z69.d.ts} +1 -1
  43. package/dist/postgres-production-preflight.d.ts +32 -0
  44. package/dist/postgres-production-preflight.js +249 -0
  45. package/dist/postgres-production-preflight.js.map +1 -0
  46. package/dist/server.js +7 -7
  47. package/dist/{sqlite-edge-store-Bc15DvpX.d.ts → sqlite-edge-store-CdMIOrho.d.ts} +1 -1
  48. package/dist/sqlite.d.ts +3 -3
  49. package/dist/sqlite.js +2 -2
  50. package/docs/architecture-v2.md +36 -5
  51. package/docs/deployment.md +157 -0
  52. package/llms.txt +2 -1
  53. package/openapi/agent-bridge-v2.openapi.json +5 -1
  54. package/package.json +5 -1
  55. package/schemas/agent-bridge-v2.capabilities.json +2 -1
  56. package/schemas/agent-bridge-v2.mcp.json +5 -1
  57. package/schemas/agent-bridge-v2.schema.json +4 -0
  58. package/sql/migrations/007_runtime_role.sql +8 -4
  59. package/sql/migrations/013_row_isolation.sql +71 -8
  60. package/sql/migrations/014_owner_control_plane.sql +124 -31
  61. package/sql/migrations/015_portable_archives.sql +143 -41
  62. package/sql/migrations/016_native_dr_fence.sql +102 -35
  63. package/sql/migrations/018_endpoint_migration_challenges.sql +36 -10
  64. package/sql/migrations/019_endpoint_migration_same_successor.sql +34 -10
  65. package/sql/migrations/020_managed_authority_compat.sql +698 -0
  66. package/sql/migrations/021_native_backup_reader.sql +421 -0
  67. package/sql/setup.sql +1 -3
  68. package/dist/chunk-2DFNOLMO.js.map +0 -1
  69. package/dist/chunk-3YHVUPU2.js.map +0 -1
  70. package/dist/chunk-4VB44KJU.js.map +0 -1
  71. package/dist/chunk-6BTJE6D4.js.map +0 -1
  72. package/dist/chunk-IHZFI7TH.js.map +0 -1
  73. package/dist/chunk-PDIHDBP3.js.map +0 -1
  74. package/dist/chunk-SYGONYMM.js.map +0 -1
  75. /package/dist/{chunk-MCKKVNCE.js.map → chunk-QPC6JHA3.js.map} +0 -0
  76. /package/dist/{chunk-SV3SVBYS.js.map → chunk-SN7PIVDF.js.map} +0 -0
  77. /package/dist/{chunk-LAHNQZDN.js.map → chunk-Z6JP6MYD.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@creatornader/agent-bridge",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Durable, pull-first mailbox and work-delivery control plane for AI agents across runtimes, sessions, and machines.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -90,6 +90,10 @@
90
90
  "test:postgres:contract": "vitest run test/postgres_integration.test.ts --maxWorkers=1 --hookTimeout=60000",
91
91
  "test:postgres:preflight": "node scripts/postgres-preflight.mjs",
92
92
  "test:postgres:preflight:full": "node scripts/postgres-preflight.mjs --full",
93
+ "preflight:postgres:production": "npm run build --silent && node dist/postgres-production-preflight.js",
94
+ "preflight:fly": "node scripts/fly-production-preflight.mjs",
95
+ "proof:gateway:production": "node scripts/gateway-production-proof.mjs",
96
+ "test:gateway:production-proof": "vitest run test/gateway-production-proof.test.ts",
93
97
  "test:windows:contract": "vitest run test/private-path.test.ts test/windows-native-smoke.test.ts --maxWorkers=1 --testTimeout=180000 -t \"applies and verifies native Windows file and directory ACLs|recovers an (enrollment|client-operation) subsystem lock\" && vitest run test/client-maintenance.test.ts --maxWorkers=1 --testTimeout=180000",
94
98
  "typecheck": "tsc --noEmit",
95
99
  "contracts:generate": "npm run build --silent && npm run contracts:generate:built",
@@ -747,7 +747,7 @@
747
747
  },
748
748
  {
749
749
  "id": "claim_delivery",
750
- "summary": "Atomically claim the next targeted delivery.",
750
+ "summary": "Atomically claim the next targeted delivery, or one eligible message when requested.",
751
751
  "mcp": {
752
752
  "name": "claim"
753
753
  },
@@ -759,6 +759,7 @@
759
759
  "command": "claim",
760
760
  "options": [
761
761
  "lease-ms",
762
+ "message-id",
762
763
  "max-attempts"
763
764
  ],
764
765
  "outputSchema": {
@@ -633,13 +633,17 @@
633
633
  },
634
634
  {
635
635
  "name": "claim",
636
- "description": "Atomically claim the next targeted delivery.",
636
+ "description": "Atomically claim the next targeted delivery, or one eligible message when requested.",
637
637
  "inputSchema": {
638
638
  "type": "object",
639
639
  "properties": {
640
640
  "leaseMs": {
641
641
  "type": "number"
642
642
  },
643
+ "messageId": {
644
+ "type": "string",
645
+ "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
646
+ },
643
647
  "maxAttempts": {
644
648
  "type": "number"
645
649
  }
@@ -2446,6 +2446,10 @@
2446
2446
  "leaseMs": {
2447
2447
  "type": "number"
2448
2448
  },
2449
+ "messageId": {
2450
+ "type": "string",
2451
+ "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
2452
+ },
2449
2453
  "maxAttempts": {
2450
2454
  "type": "number"
2451
2455
  }
@@ -12,10 +12,14 @@ begin
12
12
  role_name
13
13
  );
14
14
  end if;
15
- execute format(
16
- 'alter role %I nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls',
17
- role_name
18
- );
15
+ if exists (
16
+ select 1 from pg_roles where rolname=role_name and (
17
+ rolcanlogin or not rolinherit or rolsuper or rolcreatedb or rolcreaterole
18
+ or rolreplication or rolbypassrls or rolconnlimit<>-1
19
+ )
20
+ ) then
21
+ raise exception 'Agent Bridge runtime role has unsafe attributes';
22
+ end if;
19
23
  execute format('revoke all on schema agent_bridge from %I', role_name);
20
24
  execute format('revoke all on all tables in schema agent_bridge from %I', role_name);
21
25
  execute format('revoke all on all sequences in schema agent_bridge from %I', role_name);
@@ -17,11 +17,73 @@ begin
17
17
  role_name
18
18
  );
19
19
  end if;
20
- execute format(
21
- 'alter role %I nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls',
22
- role_name
23
- );
24
- execute format('grant %I to %I with admin option',role_name,current_user);
20
+ if exists (
21
+ select 1 from pg_roles where rolname=role_name and (
22
+ rolcanlogin or not rolinherit or rolsuper or rolcreatedb or rolcreaterole
23
+ or rolreplication or rolbypassrls or rolconnlimit<>-1
24
+ )
25
+ ) then
26
+ raise exception 'Agent Bridge row-isolation role % has unsafe attributes',role_name;
27
+ end if;
28
+ if current_setting('server_version_num')::integer>=160000 then
29
+ if not exists (
30
+ select 1 from pg_catalog.pg_auth_members membership
31
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
32
+ join pg_catalog.pg_roles member on member.oid=membership.member
33
+ where granted.rolname=role_name and member.rolname=current_user
34
+ and membership.admin_option
35
+ ) then
36
+ execute format(
37
+ 'grant %I to %I with admin true,inherit true,set true',role_name,current_user
38
+ );
39
+ elsif not exists (
40
+ select 1 from pg_catalog.pg_auth_members membership
41
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
42
+ join pg_catalog.pg_roles member on member.oid=membership.member
43
+ where granted.rolname=role_name and member.rolname=current_user
44
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
45
+ and coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
46
+ ) then
47
+ execute format('grant %I to %I with inherit true,set true',role_name,current_user);
48
+ end if;
49
+ elsif not exists (
50
+ select 1 from pg_catalog.pg_auth_members membership
51
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
52
+ join pg_catalog.pg_roles member on member.oid=membership.member
53
+ where granted.rolname=role_name and member.rolname=current_user
54
+ and membership.admin_option
55
+ ) then
56
+ execute format('grant %I to %I with admin option',role_name,current_user);
57
+ end if;
58
+ if not exists (
59
+ select 1 from pg_catalog.pg_auth_members membership
60
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
61
+ join pg_catalog.pg_roles member on member.oid=membership.member
62
+ where granted.rolname=role_name and member.rolname=current_user
63
+ and membership.admin_option
64
+ ) or not exists (
65
+ select 1 from pg_catalog.pg_auth_members membership
66
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
67
+ join pg_catalog.pg_roles member on member.oid=membership.member
68
+ where granted.rolname=role_name and member.rolname=current_user
69
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
70
+ and coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
71
+ ) or exists (
72
+ select 1 from pg_catalog.pg_auth_members membership
73
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
74
+ join pg_catalog.pg_roles member on member.oid=membership.member
75
+ where granted.rolname=role_name and member.rolname=current_user
76
+ and not (
77
+ (membership.grantor=10 and membership.admin_option
78
+ and not coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
79
+ and not coalesce((to_jsonb(membership)->>'set_option')::boolean,true))
80
+ or (membership.grantor=member.oid
81
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
82
+ and coalesce((to_jsonb(membership)->>'set_option')::boolean,true))
83
+ )
84
+ ) then
85
+ raise exception 'Agent Bridge row-isolation role % has unsafe owner authority',role_name;
86
+ end if;
25
87
  end loop;
26
88
  end
27
89
  $roles$;
@@ -292,7 +354,8 @@ declare
292
354
  event_writer text := 'agent_bridge_event_writer_' || suffix;
293
355
  table_name text;
294
356
  begin
295
- execute format('grant usage on schema agent_bridge to %I',data_owner);
357
+ execute format('grant usage,create on schema agent_bridge to %I,%I,%I',
358
+ data_owner,context_reader,event_writer);
296
359
  foreach table_name in array array[
297
360
  'messages','receipts','deliveries','delivery_events','agent_instances'
298
361
  ] loop
@@ -301,9 +364,9 @@ begin
301
364
  execute format('alter function agent_bridge.current_request_workspace() owner to %I',context_reader);
302
365
  execute format('alter function agent_bridge.current_request_principal() owner to %I',context_reader);
303
366
  execute format('alter function agent_bridge.record_delivery_event() owner to %I',event_writer);
304
- execute format('grant usage on schema agent_bridge to %I',context_reader);
367
+ execute format('revoke create on schema agent_bridge from %I,%I,%I',
368
+ data_owner,context_reader,event_writer);
305
369
  execute format('grant select on agent_bridge.request_authorities to %I',context_reader);
306
- execute format('grant usage on schema agent_bridge to %I',event_writer);
307
370
  execute format('grant insert on agent_bridge.delivery_events to %I',event_writer);
308
371
  execute format('grant usage on sequence agent_bridge.delivery_events_sequence_seq to %I',event_writer);
309
372
  end
@@ -18,11 +18,73 @@ begin
18
18
  role_name
19
19
  );
20
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);
21
+ if exists (
22
+ select 1 from pg_roles where rolname=role_name and (
23
+ rolcanlogin or not rolinherit or rolsuper or rolcreatedb or rolcreaterole
24
+ or rolreplication or rolbypassrls or rolconnlimit<>-1
25
+ )
26
+ ) then
27
+ raise exception 'Agent Bridge control role % has unsafe attributes',role_name;
28
+ end if;
29
+ if current_setting('server_version_num')::integer>=160000 then
30
+ if not exists (
31
+ select 1 from pg_catalog.pg_auth_members membership
32
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
33
+ join pg_catalog.pg_roles member on member.oid=membership.member
34
+ where granted.rolname=role_name and member.rolname=current_user
35
+ and membership.admin_option
36
+ ) then
37
+ execute format(
38
+ 'grant %I to %I with admin true,inherit true,set true',role_name,current_user
39
+ );
40
+ elsif not exists (
41
+ select 1 from pg_catalog.pg_auth_members membership
42
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
43
+ join pg_catalog.pg_roles member on member.oid=membership.member
44
+ where granted.rolname=role_name and member.rolname=current_user
45
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
46
+ and coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
47
+ ) then
48
+ execute format('grant %I to %I with inherit true,set true',role_name,current_user);
49
+ end if;
50
+ elsif not exists (
51
+ select 1 from pg_catalog.pg_auth_members membership
52
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
53
+ join pg_catalog.pg_roles member on member.oid=membership.member
54
+ where granted.rolname=role_name and member.rolname=current_user
55
+ and membership.admin_option
56
+ ) then
57
+ execute format('grant %I to %I with admin option',role_name,current_user);
58
+ end if;
59
+ if not exists (
60
+ select 1 from pg_catalog.pg_auth_members membership
61
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
62
+ join pg_catalog.pg_roles member on member.oid=membership.member
63
+ where granted.rolname=role_name and member.rolname=current_user
64
+ and membership.admin_option
65
+ ) or not exists (
66
+ select 1 from pg_catalog.pg_auth_members membership
67
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
68
+ join pg_catalog.pg_roles member on member.oid=membership.member
69
+ where granted.rolname=role_name and member.rolname=current_user
70
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
71
+ and coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
72
+ ) or exists (
73
+ select 1 from pg_catalog.pg_auth_members membership
74
+ join pg_catalog.pg_roles granted on granted.oid=membership.roleid
75
+ join pg_catalog.pg_roles member on member.oid=membership.member
76
+ where granted.rolname=role_name and member.rolname=current_user
77
+ and not (
78
+ (membership.grantor=10 and membership.admin_option
79
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
80
+ =coalesce((to_jsonb(membership)->>'set_option')::boolean,true))
81
+ or (membership.grantor=member.oid
82
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
83
+ and coalesce((to_jsonb(membership)->>'set_option')::boolean,true))
84
+ )
85
+ ) then
86
+ raise exception 'Agent Bridge control role % has unsafe owner authority',role_name;
87
+ end if;
26
88
  end loop;
27
89
  end
28
90
  $roles$;
@@ -144,27 +206,38 @@ begin
144
206
  where event.member_role=session_user and event.control_role in ('operator','auditor')
145
207
  order by event.control_role,event.sequence desc
146
208
  ), 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
209
+ select granted.rolname control_role,
210
+ bool_or(membership.admin_option) admin_option,
211
+ bool_or(coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)) inherit_option,
212
+ bool_or(coalesce((to_jsonb(membership)->>'set_option')::boolean,true)) set_option,
213
+ bool_and(case when member.rolname=schema_owner then
214
+ (membership.grantor=10 and membership.admin_option
215
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
216
+ =coalesce((to_jsonb(membership)->>'set_option')::boolean,true))
217
+ or (membership.grantor=member.oid
218
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
219
+ and coalesce((to_jsonb(membership)->>'set_option')::boolean,true))
220
+ else membership.grantor=(select oid from pg_catalog.pg_roles where rolname=schema_owner)
221
+ and not membership.admin_option
222
+ and coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
223
+ and coalesce((to_jsonb(membership)->>'set_option')::boolean,true) end) grants_valid
151
224
  from pg_catalog.pg_auth_members membership
152
225
  join pg_catalog.pg_roles granted on granted.oid=membership.roleid
153
226
  join pg_catalog.pg_roles member on member.oid=membership.member
154
- join pg_catalog.pg_roles grantor on grantor.oid=membership.grantor
155
227
  where member.rolname=session_user and granted.rolname in (operator_role,auditor_role)
228
+ group by granted.rolname
156
229
  )
157
230
  select
158
231
  coalesce(bool_or(membership.control_role=operator_role
159
232
  and membership.inherit_option and membership.set_option
160
- and membership.grantor_role=schema_owner and (
233
+ and membership.grants_valid and (
161
234
  (session_user=schema_owner and membership.admin_option)
162
235
  or (latest.control_role='operator' and latest.action='register'
163
236
  and not membership.admin_option)
164
237
  )),false),
165
238
  coalesce(bool_or(membership.control_role=auditor_role
166
239
  and membership.inherit_option and membership.set_option
167
- and membership.grantor_role=schema_owner and (
240
+ and membership.grants_valid and (
168
241
  (session_user=schema_owner and membership.admin_option)
169
242
  or (latest.control_role='auditor' and latest.action='register'
170
243
  and not membership.admin_option)
@@ -937,15 +1010,15 @@ language sql stable set search_path = '' set timezone = 'UTC' as $$
937
1010
  from pg_catalog.pg_roles role where role.rolname in (select role_name from control_roles)
938
1011
  union all
939
1012
  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','')
1013
+ bool_or(membership.admin_option)::text||':'||
1014
+ bool_or(coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true))::text||':'||
1015
+ bool_or(coalesce((to_jsonb(membership)->>'set_option')::boolean,true))::text
943
1016
  from pg_catalog.pg_auth_members membership
944
1017
  join pg_catalog.pg_roles granted on granted.oid=membership.roleid
945
1018
  join pg_catalog.pg_roles member on member.oid=membership.member
946
- join pg_catalog.pg_roles grantor on grantor.oid=membership.grantor
947
1019
  where member.rolname in (select role_name from control_roles)
948
1020
  or granted.rolname=(select owner_role from names)
1021
+ group by granted.rolname,member.rolname
949
1022
  union all
950
1023
  select 'schema',namespace.nspname,pg_catalog.pg_get_userbyid(namespace.nspowner)
951
1024
  from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge'
@@ -1106,27 +1179,42 @@ returns boolean language sql stable security definer set search_path = '' as $$
1106
1179
  ), active_registry as (
1107
1180
  select member_role,control_role from latest_registry where action='register'
1108
1181
  ), expected_memberships as (
1109
- select names.owner_role granted_role,names.schema_owner member_role,true admin_option
1182
+ select names.owner_role granted_role,names.schema_owner member_role,
1183
+ true admin_option,true inherit_option,true set_option
1110
1184
  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
1185
+ union all select names.operator_role,names.schema_owner,true,true,true from names
1186
+ union all select names.auditor_role,names.schema_owner,true,true,true from names
1113
1187
  union all
1114
1188
  select case registry.control_role when 'operator' then names.operator_role
1115
- else names.auditor_role end,registry.member_role,false
1189
+ else names.auditor_role end,registry.member_role,false,true,true
1116
1190
  from active_registry registry cross join names
1117
- ), actual_memberships as (
1191
+ ), raw_memberships as (
1118
1192
  select granted.rolname::name granted_role,member.rolname::name member_role,
1119
1193
  membership.admin_option,
1120
1194
  coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
1121
1195
  coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option,
1122
- grantor.rolname::name grantor_role
1196
+ membership.grantor,member.oid member_oid
1123
1197
  from pg_catalog.pg_auth_members membership
1124
1198
  join pg_catalog.pg_roles granted on granted.oid=membership.roleid
1125
1199
  join pg_catalog.pg_roles member on member.oid=membership.member
1126
- join pg_catalog.pg_roles grantor on grantor.oid=membership.grantor
1127
1200
  where granted.rolname in (
1128
1201
  (select owner_role from names),(select operator_role from names),(select auditor_role from names)
1129
1202
  )
1203
+ ), actual_memberships as (
1204
+ select membership.granted_role,membership.member_role,
1205
+ bool_or(membership.admin_option) admin_option,
1206
+ bool_or(membership.inherit_option) inherit_option,
1207
+ bool_or(membership.set_option) set_option,
1208
+ bool_and(case when membership.member_role=names.schema_owner then
1209
+ (membership.grantor=10 and membership.admin_option
1210
+ and membership.inherit_option=membership.set_option)
1211
+ or (membership.grantor=membership.member_oid
1212
+ and membership.inherit_option and membership.set_option)
1213
+ else membership.grantor=(select oid from pg_catalog.pg_roles where rolname=names.schema_owner)
1214
+ and not membership.admin_option
1215
+ and membership.inherit_option and membership.set_option end) grants_valid
1216
+ from raw_memberships membership cross join names
1217
+ group by membership.granted_role,membership.member_role
1130
1218
  ), authority_closure as (
1131
1219
  select control_role.granted_role,candidate.rolname::name member_role
1132
1220
  from names
@@ -1145,16 +1233,14 @@ returns boolean language sql stable security definer set search_path = '' as $$
1145
1233
  ) from agent_bridge.owner_control_attestations attestation
1146
1234
  where attestation.name='owner-control-v1')
1147
1235
  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)
1236
+ (select granted_role,member_role,admin_option,inherit_option,set_option from actual_memberships
1237
+ except select granted_role,member_role,admin_option,inherit_option,set_option from expected_memberships)
1150
1238
  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)
1239
+ (select granted_role,member_role,admin_option,inherit_option,set_option from expected_memberships
1240
+ except select granted_role,member_role,admin_option,inherit_option,set_option from actual_memberships)
1153
1241
  )
1154
1242
  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
1243
+ select 1 from actual_memberships membership where not membership.grants_valid
1158
1244
  )
1159
1245
  and not exists (
1160
1246
  select 1 from active_registry registry
@@ -1216,6 +1302,7 @@ declare
1216
1302
  membership_record record;
1217
1303
  relation_record record;
1218
1304
  begin
1305
+ execute format('grant usage,create on schema agent_bridge to %I',owner_role);
1219
1306
  execute format('alter table agent_bridge.control_requests owner to %I',owner_role);
1220
1307
  execute format('alter table agent_bridge.control_events owner to %I',owner_role);
1221
1308
  execute format('alter table agent_bridge.owner_control_attestations owner to %I',owner_role);
@@ -1230,6 +1317,7 @@ begin
1230
1317
  'control_revoke_credential(uuid,uuid,text)','owner_control_catalog_definition()',
1231
1318
  'owner_control_plane_ready()'
1232
1319
  ] loop execute format('alter function agent_bridge.%s owner to %I',role_name,owner_role); end loop;
1320
+ execute format('revoke create on schema agent_bridge from %I',owner_role);
1233
1321
 
1234
1322
  for membership_record in
1235
1323
  select granted.rolname granted_name,member.rolname member_name
@@ -1287,7 +1375,7 @@ begin
1287
1375
  execute format('grant usage on schema agent_bridge to %I,%I,%I',owner_role,operator_role,auditor_role);
1288
1376
  execute format('grant select,insert,update(request_id) on agent_bridge.control_requests to %I',owner_role);
1289
1377
  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);
1378
+ execute format('grant select,insert on agent_bridge.owner_control_attestations to %I',owner_role);
1291
1379
  execute format('grant select,insert on agent_bridge.control_membership_events to %I',owner_role);
1292
1380
  execute format('grant usage on sequence agent_bridge.control_events_sequence_seq to %I',owner_role);
1293
1381
  execute format('grant usage on sequence agent_bridge.control_membership_events_sequence_seq to %I',owner_role);
@@ -1306,6 +1394,7 @@ begin
1306
1394
  execute format('grant execute on function agent_bridge.control_revoke_credential(uuid,uuid,text) to %I',operator_role);
1307
1395
  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
1396
  execute format('grant execute on function agent_bridge.owner_control_plane_ready() to %I',runtime_role);
1397
+ execute format('grant execute on function agent_bridge.owner_control_plane_ready() to %I',current_user);
1309
1398
  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
1399
  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
1400
  foreach role_name in array array['anon','authenticated'] loop
@@ -1632,8 +1721,12 @@ begin
1632
1721
  end
1633
1722
  $preflight$;
1634
1723
 
1724
+ select set_config(
1725
+ 'role','agent_bridge_control_owner_'||substr(md5(current_database()),1,16),false
1726
+ );
1635
1727
  insert into agent_bridge.owner_control_attestations(name,catalog_definition)
1636
1728
  values('owner-control-v1',agent_bridge.owner_control_catalog_definition());
1729
+ reset role;
1637
1730
 
1638
1731
  insert into agent_bridge.schema_migrations(version,name,checksum)
1639
1732
  values(14,'owner_control_plane','__AGENT_BRIDGE_MIGRATION_CHECKSUM__');