@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,177 @@
1
+ begin;
2
+
3
+ select pg_advisory_xact_lock(1646705660);
4
+
5
+ create table agent_bridge.request_authorities (
6
+ backend_pid integer not null,
7
+ transaction_id xid8 not null,
8
+ request_id uuid not null,
9
+ credential_id uuid not null,
10
+ workspace_id text not null,
11
+ principal text not null,
12
+ scopes text[] not null,
13
+ opened_session_user name not null,
14
+ opened_at timestamptz not null default clock_timestamp(),
15
+ primary key (backend_pid, transaction_id),
16
+ unique (request_id)
17
+ );
18
+
19
+ revoke all on agent_bridge.request_authorities from public;
20
+
21
+ create or replace function agent_bridge.resolve_credential_hash(credential_hash text)
22
+ returns table(credential_id uuid, workspace_id text, principal text, scopes text[])
23
+ language sql volatile security definer set search_path = '' as $$
24
+ select credential.id,credential.workspace_id,agent.principal,credential.scopes
25
+ from agent_bridge.credentials credential
26
+ join agent_bridge.agents agent on agent.id=credential.agent_id and agent.workspace_id=credential.workspace_id
27
+ join agent_bridge.workspaces workspace on workspace.id=credential.workspace_id
28
+ where credential_hash ~ '^[0-9a-f]{64}$'
29
+ and credential.token_hash=credential_hash::character(64)
30
+ and credential.revoked_at is null
31
+ and (credential.expires_at is null or credential.expires_at>clock_timestamp())
32
+ and (not exists (select 1 from agent_bridge.credentials successor where successor.replaces_credential_id=credential.id)
33
+ or credential.expiry_grace_until>clock_timestamp())
34
+ and agent.disabled_at is null and workspace.disabled_at is null
35
+ limit 1
36
+ $$;
37
+
38
+ create or replace function agent_bridge.open_request_authority(
39
+ requested_credential_id uuid,
40
+ credential_hash text,
41
+ requested_request_id uuid
42
+ ) returns table(credential_id uuid, workspace_id text, principal text, scopes text[])
43
+ language plpgsql security definer set search_path = '' as $$
44
+ declare
45
+ runtime_role name := ('agent_bridge_runtime_' || substr(pg_catalog.md5(pg_catalog.current_database()),1,16))::name;
46
+ authority_credential_id uuid;
47
+ authority_workspace_id text;
48
+ authority_principal text;
49
+ authority_scopes text[];
50
+ request_time timestamptz := clock_timestamp();
51
+ begin
52
+ if not pg_catalog.pg_has_role(session_user,runtime_role,'MEMBER')
53
+ or credential_hash !~ '^[0-9a-f]{64}$' then
54
+ raise exception using errcode='42501',message='request authority is restricted to the runtime login';
55
+ end if;
56
+
57
+ select credential.id,credential.workspace_id,agent.principal,credential.scopes
58
+ into authority_credential_id,authority_workspace_id,authority_principal,authority_scopes
59
+ from agent_bridge.credentials credential
60
+ join agent_bridge.agents agent on agent.id=credential.agent_id and agent.workspace_id=credential.workspace_id
61
+ join agent_bridge.workspaces workspace on workspace.id=credential.workspace_id
62
+ where credential.id=requested_credential_id
63
+ and credential.token_hash=credential_hash::character(64)
64
+ and credential.revoked_at is null
65
+ and (credential.expires_at is null or credential.expires_at>request_time)
66
+ and (not exists (select 1 from agent_bridge.credentials successor where successor.replaces_credential_id=credential.id)
67
+ or credential.expiry_grace_until>request_time)
68
+ and agent.disabled_at is null and workspace.disabled_at is null
69
+ for share of credential,agent,workspace;
70
+
71
+ if not found then return; end if;
72
+
73
+ insert into agent_bridge.request_authorities(
74
+ backend_pid,transaction_id,request_id,credential_id,workspace_id,principal,scopes,opened_session_user
75
+ ) values (
76
+ pg_catalog.pg_backend_pid(),pg_catalog.pg_current_xact_id(),requested_request_id,
77
+ authority_credential_id,authority_workspace_id,authority_principal,authority_scopes,session_user
78
+ );
79
+ credential_id := authority_credential_id;
80
+ workspace_id := authority_workspace_id;
81
+ principal := authority_principal;
82
+ scopes := authority_scopes;
83
+ return next;
84
+ end
85
+ $$;
86
+
87
+ create or replace function agent_bridge.active_request_authority()
88
+ returns agent_bridge.request_authorities
89
+ language sql volatile security definer set search_path = '' as $$
90
+ select authority
91
+ from agent_bridge.request_authorities authority
92
+ where authority.backend_pid=pg_catalog.pg_backend_pid()
93
+ and authority.transaction_id=pg_catalog.pg_current_xact_id_if_assigned()
94
+ and authority.opened_session_user=session_user
95
+ $$;
96
+
97
+ create or replace function agent_bridge.assert_active_request_credential(requested_credential_id uuid)
98
+ returns agent_bridge.request_authorities
99
+ language plpgsql volatile security definer set search_path = '' as $$
100
+ declare authority agent_bridge.request_authorities%rowtype;
101
+ begin
102
+ select * into authority from agent_bridge.active_request_authority();
103
+ if not found or authority.credential_id is distinct from requested_credential_id then
104
+ raise exception using errcode='28000',message='active request authority is required';
105
+ end if;
106
+ return authority;
107
+ end
108
+ $$;
109
+
110
+ create or replace function agent_bridge.close_request_authority() returns void
111
+ language plpgsql volatile security definer set search_path = '' as $$
112
+ declare authority agent_bridge.request_authorities%rowtype;
113
+ begin
114
+ select * into authority from agent_bridge.active_request_authority();
115
+ if not found then
116
+ raise exception using errcode='28000',message='active request authority is required';
117
+ end if;
118
+ delete from agent_bridge.request_authorities
119
+ where backend_pid=authority.backend_pid and transaction_id=authority.transaction_id;
120
+ end
121
+ $$;
122
+
123
+ alter function agent_bridge.record_scope_denial(uuid,text,uuid) rename to record_scope_denial_unbound_011;
124
+ alter function agent_bridge.consume_rate_limit(uuid,text,uuid) rename to consume_rate_limit_unbound_011;
125
+
126
+ create function agent_bridge.record_scope_denial(uuid,text,uuid) returns void
127
+ language plpgsql security definer set search_path = '' as $$
128
+ begin
129
+ perform agent_bridge.assert_active_request_credential($1);
130
+ perform agent_bridge.record_scope_denial_unbound_011($1,$2,$3);
131
+ end
132
+ $$;
133
+
134
+ create function agent_bridge.consume_rate_limit(uuid,text,uuid)
135
+ returns table(allowed boolean,limit_value integer,remaining_value integer,retry_after_seconds numeric,denied_policy_id text)
136
+ language plpgsql security definer set search_path = '' as $$
137
+ begin
138
+ perform agent_bridge.assert_active_request_credential($1);
139
+ return query select * from agent_bridge.consume_rate_limit_unbound_011($1,$2,$3);
140
+ end
141
+ $$;
142
+
143
+ revoke all on function agent_bridge.open_request_authority(uuid,text,uuid) from public;
144
+ revoke all on function agent_bridge.resolve_credential_hash(text) from public;
145
+ revoke all on function agent_bridge.active_request_authority() from public;
146
+ revoke all on function agent_bridge.assert_active_request_credential(uuid) from public;
147
+ revoke all on function agent_bridge.close_request_authority() from public;
148
+ revoke all on function agent_bridge.record_scope_denial_unbound_011(uuid,text,uuid) from public;
149
+ revoke all on function agent_bridge.consume_rate_limit_unbound_011(uuid,text,uuid) from public;
150
+ revoke all on function agent_bridge.record_scope_denial(uuid,text,uuid) from public;
151
+ revoke all on function agent_bridge.consume_rate_limit(uuid,text,uuid) from public;
152
+
153
+ do $roles$
154
+ declare
155
+ runtime_role text := 'agent_bridge_runtime_' || substr(md5(current_database()),1,16);
156
+ role_name text;
157
+ begin
158
+ foreach role_name in array array[runtime_role,'anon','authenticated'] loop
159
+ if exists (select 1 from pg_roles where rolname=role_name) then
160
+ execute format('revoke all on function agent_bridge.record_scope_denial_unbound_011(uuid,text,uuid) from %I',role_name);
161
+ execute format('revoke all on function agent_bridge.consume_rate_limit_unbound_011(uuid,text,uuid) from %I',role_name);
162
+ end if;
163
+ end loop;
164
+ execute format('revoke select on agent_bridge.credentials,agent_bridge.agents,agent_bridge.workspaces from %I',runtime_role);
165
+ execute format('revoke all on agent_bridge.request_authorities from %I',runtime_role);
166
+ execute format('grant execute on function agent_bridge.open_request_authority(uuid,text,uuid) to %I',runtime_role);
167
+ execute format('grant execute on function agent_bridge.resolve_credential_hash(text) to %I',runtime_role);
168
+ execute format('grant execute on function agent_bridge.close_request_authority() to %I',runtime_role);
169
+ execute format('grant execute on function agent_bridge.record_scope_denial(uuid,text,uuid) to %I',runtime_role);
170
+ execute format('grant execute on function agent_bridge.consume_rate_limit(uuid,text,uuid) to %I',runtime_role);
171
+ end
172
+ $roles$;
173
+
174
+ insert into agent_bridge.schema_migrations(version,name,checksum)
175
+ values (12,'request_authority','__AGENT_BRIDGE_MIGRATION_CHECKSUM__');
176
+
177
+ commit;
@@ -0,0 +1,495 @@
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
+ data_owner text := 'agent_bridge_data_owner_' || suffix;
9
+ context_reader text := 'agent_bridge_context_reader_' || suffix;
10
+ event_writer text := 'agent_bridge_event_writer_' || suffix;
11
+ role_name text;
12
+ begin
13
+ foreach role_name in array array[data_owner,context_reader,event_writer] loop
14
+ if not exists (select 1 from pg_roles where rolname=role_name) then
15
+ execute format(
16
+ 'create role %I nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls',
17
+ role_name
18
+ );
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);
25
+ end loop;
26
+ end
27
+ $roles$;
28
+
29
+ alter table agent_bridge.messages
30
+ add constraint messages_workspace_id_source_unique unique(workspace,id,source);
31
+
32
+ alter table agent_bridge.deliveries add column publisher text;
33
+ update agent_bridge.deliveries delivery
34
+ set publisher=message.source
35
+ from agent_bridge.messages message
36
+ where message.workspace=delivery.workspace and message.id=delivery.message_id;
37
+ alter table agent_bridge.deliveries alter column publisher set not null;
38
+ alter table agent_bridge.deliveries
39
+ add constraint deliveries_publisher_message_fk
40
+ foreign key(workspace,message_id,publisher)
41
+ references agent_bridge.messages(workspace,id,source) on delete cascade,
42
+ add constraint deliveries_event_identity_unique
43
+ unique(workspace,id,message_id,publisher);
44
+
45
+ alter table agent_bridge.delivery_events add column publisher text;
46
+ update agent_bridge.delivery_events event
47
+ set publisher=delivery.publisher
48
+ from agent_bridge.deliveries delivery
49
+ where delivery.workspace=event.workspace
50
+ and delivery.id=event.delivery_id
51
+ and delivery.message_id=event.message_id;
52
+ alter table agent_bridge.delivery_events alter column publisher set not null;
53
+ alter table agent_bridge.delivery_events
54
+ add constraint delivery_events_delivery_publisher_fk
55
+ foreign key(workspace,delivery_id,message_id,publisher)
56
+ references agent_bridge.deliveries(workspace,id,message_id,publisher) on delete cascade;
57
+
58
+ create table agent_bridge.row_isolation_attestations (
59
+ name text primary key,
60
+ catalog_definition text not null,
61
+ attested_at timestamptz not null default now(),
62
+ constraint row_isolation_attestation_name check(name='domain-v1')
63
+ );
64
+
65
+ create or replace function agent_bridge.reject_delivery_identity_mutation() returns trigger
66
+ language plpgsql set search_path = '' as $$
67
+ begin
68
+ if old.id is distinct from new.id
69
+ or old.message_id is distinct from new.message_id
70
+ or old.workspace is distinct from new.workspace
71
+ or old.recipient is distinct from new.recipient
72
+ or old.publisher is distinct from new.publisher
73
+ or old.created_at is distinct from new.created_at then
74
+ raise exception 'delivery identity is immutable';
75
+ end if;
76
+ return new;
77
+ end
78
+ $$;
79
+ revoke all on function agent_bridge.reject_delivery_identity_mutation() from public;
80
+ drop trigger if exists deliveries_identity_immutable on agent_bridge.deliveries;
81
+ create trigger deliveries_identity_immutable
82
+ before update on agent_bridge.deliveries
83
+ for each row execute function agent_bridge.reject_delivery_identity_mutation();
84
+
85
+ create or replace function agent_bridge.enforce_delivery_actor_role() returns trigger
86
+ language plpgsql set search_path = '' as $$
87
+ declare
88
+ data_owner name := ('agent_bridge_data_owner_' || substr(pg_catalog.md5(pg_catalog.current_database()),1,16))::name;
89
+ writes_event boolean;
90
+ principal text;
91
+ begin
92
+ if pg_catalog.pg_has_role(current_user,data_owner,'MEMBER') then
93
+ return new;
94
+ end if;
95
+ principal := agent_bridge.current_request_principal();
96
+ if tg_op='INSERT' then
97
+ if principal is distinct from new.publisher then
98
+ raise exception 'only the delivery publisher may create delivery state';
99
+ end if;
100
+ if new.state<>'pending' or new.attempt<>0 or new.cycle_attempt<>0
101
+ or new.requeue_count<>0 or new.lease_token is not null
102
+ or new.lease_owner is not null or new.lease_expires_at is not null
103
+ or new.last_error is not null or new.last_action<>'created'
104
+ or new.last_actor is distinct from principal then
105
+ raise exception 'publisher delivery creation must use canonical initial state';
106
+ end if;
107
+ return new;
108
+ end if;
109
+ writes_event := old.state is distinct from new.state
110
+ or old.attempt is distinct from new.attempt
111
+ or old.cycle_attempt is distinct from new.cycle_attempt
112
+ or old.requeue_count is distinct from new.requeue_count
113
+ or old.last_action is distinct from new.last_action;
114
+ if not writes_event then
115
+ if principal is distinct from old.recipient then
116
+ raise exception 'only the delivery recipient may update lease state';
117
+ end if;
118
+ if old.last_actor is distinct from new.last_actor then
119
+ raise exception 'delivery actor may change only with an audited transition';
120
+ end if;
121
+ return new;
122
+ end if;
123
+ if new.state='cancelled' or new.last_action='cancel' then
124
+ if principal is distinct from old.publisher then
125
+ raise exception 'only the delivery publisher may cancel';
126
+ end if;
127
+ if old.state not in ('pending','retrying','claimed')
128
+ or new.state<>'cancelled' or new.last_action<>'cancel' then
129
+ raise exception 'publisher cancel requires a cancellation transition';
130
+ end if;
131
+ if new.last_actor is distinct from principal then
132
+ raise exception 'publisher delivery actor must match request authority';
133
+ end if;
134
+ elsif (old.state in ('dead','cancelled') and new.state='pending')
135
+ or new.last_action='requeue' then
136
+ if principal is distinct from old.publisher then
137
+ raise exception 'only the delivery publisher may requeue';
138
+ end if;
139
+ if old.state not in ('dead','cancelled')
140
+ or new.state<>'pending' or new.last_action<>'requeue' then
141
+ raise exception 'publisher requeue requires a terminal-to-pending transition';
142
+ end if;
143
+ if new.last_actor is distinct from principal then
144
+ raise exception 'publisher delivery actor must match request authority';
145
+ end if;
146
+ elsif principal is distinct from old.recipient then
147
+ raise exception 'only the delivery recipient may update delivery state';
148
+ elsif new.last_action in ('message_expired','lease_expired') then
149
+ if new.last_actor is distinct from 'agent-bridge' then
150
+ raise exception 'system delivery actor is invalid';
151
+ end if;
152
+ elsif new.last_action='attempts_exhausted' then
153
+ if new.last_actor is distinct from 'agent-bridge'
154
+ and new.last_actor is distinct from principal then
155
+ raise exception 'exhausted delivery actor must match request authority or system';
156
+ end if;
157
+ elsif new.last_actor is distinct from principal then
158
+ raise exception 'recipient delivery actor must match request authority';
159
+ end if;
160
+ return new;
161
+ end
162
+ $$;
163
+ revoke all on function agent_bridge.enforce_delivery_actor_role() from public;
164
+ drop trigger if exists deliveries_actor_role on agent_bridge.deliveries;
165
+ create trigger deliveries_actor_role
166
+ before insert or update on agent_bridge.deliveries
167
+ for each row execute function agent_bridge.enforce_delivery_actor_role();
168
+
169
+ create or replace function agent_bridge.reject_delivery_event_mutation() returns trigger
170
+ language plpgsql set search_path = '' as $$
171
+ begin
172
+ raise exception 'delivery events are append-only';
173
+ end
174
+ $$;
175
+ revoke all on function agent_bridge.reject_delivery_event_mutation() from public;
176
+ drop trigger if exists delivery_events_append_only on agent_bridge.delivery_events;
177
+ create trigger delivery_events_append_only
178
+ before update or delete or truncate on agent_bridge.delivery_events
179
+ for each statement execute function agent_bridge.reject_delivery_event_mutation();
180
+
181
+ create or replace function agent_bridge.record_delivery_event() returns trigger
182
+ language plpgsql security definer set search_path = '' as $$
183
+ begin
184
+ if tg_op = 'INSERT'
185
+ or old.state is distinct from new.state
186
+ or old.attempt is distinct from new.attempt
187
+ or old.cycle_attempt is distinct from new.cycle_attempt
188
+ or old.requeue_count is distinct from new.requeue_count
189
+ or old.last_action is distinct from new.last_action then
190
+ insert into agent_bridge.delivery_events (
191
+ delivery_id,message_id,workspace,publisher,recipient,from_state,to_state,
192
+ attempt,cycle_attempt,requeue_count,lease_owner,error,actor,action
193
+ ) values (
194
+ new.id,new.message_id,new.workspace,new.publisher,new.recipient,
195
+ case when tg_op='INSERT' then null else old.state end,
196
+ new.state,new.attempt,new.cycle_attempt,new.requeue_count,
197
+ new.lease_owner,new.last_error,coalesce(new.last_actor,new.recipient),new.last_action
198
+ );
199
+ end if;
200
+ return new;
201
+ end
202
+ $$;
203
+
204
+ create or replace function agent_bridge.current_request_workspace() returns text
205
+ language sql stable security definer set search_path = '' as $$
206
+ select authority.workspace_id
207
+ from agent_bridge.request_authorities authority
208
+ where pg_catalog.pg_has_role(
209
+ session_user,
210
+ ('agent_bridge_runtime_' || substr(pg_catalog.md5(pg_catalog.current_database()),1,16))::name,
211
+ 'MEMBER'
212
+ )
213
+ and authority.backend_pid=pg_catalog.pg_backend_pid()
214
+ and authority.transaction_id=pg_catalog.pg_current_xact_id_if_assigned()
215
+ and authority.opened_session_user=session_user
216
+ $$;
217
+
218
+ create or replace function agent_bridge.current_request_principal() returns text
219
+ language sql stable security definer set search_path = '' as $$
220
+ select authority.principal
221
+ from agent_bridge.request_authorities authority
222
+ where pg_catalog.pg_has_role(
223
+ session_user,
224
+ ('agent_bridge_runtime_' || substr(pg_catalog.md5(pg_catalog.current_database()),1,16))::name,
225
+ 'MEMBER'
226
+ )
227
+ and authority.backend_pid=pg_catalog.pg_backend_pid()
228
+ and authority.transaction_id=pg_catalog.pg_current_xact_id_if_assigned()
229
+ and authority.opened_session_user=session_user
230
+ $$;
231
+
232
+ create or replace function agent_bridge.row_isolation_catalog_definition() returns text
233
+ language sql stable set search_path = '' as $$
234
+ with domain_tables(table_name) as (
235
+ values ('messages'),('receipts'),('deliveries'),('delivery_events'),('agent_instances')
236
+ ), catalog_objects(kind,identity,definition) as (
237
+ select 'policy',relation.relname || '.' || policy.polname,
238
+ policy.polcmd::text || ':' || policy.polpermissive::text || ':' ||
239
+ coalesce(pg_catalog.pg_get_expr(policy.polqual,policy.polrelid),'') || ':' ||
240
+ coalesce(pg_catalog.pg_get_expr(policy.polwithcheck,policy.polrelid),'')
241
+ from pg_catalog.pg_policy policy
242
+ join pg_catalog.pg_class relation on relation.oid=policy.polrelid
243
+ join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
244
+ where namespace.nspname='agent_bridge'
245
+ and relation.relname in (select table_name from domain_tables)
246
+ union all
247
+ select 'constraint',constraint_record.conrelid::regclass::text || '.' || constraint_record.conname,
248
+ pg_catalog.pg_get_constraintdef(constraint_record.oid,true)
249
+ from pg_catalog.pg_constraint constraint_record
250
+ join pg_catalog.pg_class constraint_relation on constraint_relation.oid=constraint_record.conrelid
251
+ join pg_catalog.pg_namespace constraint_namespace on constraint_namespace.oid=constraint_relation.relnamespace
252
+ where constraint_namespace.nspname='agent_bridge'
253
+ and constraint_relation.relname in (
254
+ select table_name from domain_tables
255
+ union all select 'row_isolation_attestations'
256
+ )
257
+ union all
258
+ select 'trigger',trigger.tgrelid::regclass::text || '.' || trigger.tgname,
259
+ pg_catalog.pg_get_triggerdef(trigger.oid,true)
260
+ from pg_catalog.pg_trigger trigger
261
+ join pg_catalog.pg_class trigger_relation on trigger_relation.oid=trigger.tgrelid
262
+ join pg_catalog.pg_namespace trigger_namespace on trigger_namespace.oid=trigger_relation.relnamespace
263
+ where not trigger.tgisinternal
264
+ and trigger_namespace.nspname='agent_bridge'
265
+ and trigger_relation.relname in (select table_name from domain_tables)
266
+ union all
267
+ select 'function',procedure.oid::regprocedure::text,
268
+ pg_catalog.pg_get_userbyid(procedure.proowner) || ':' ||
269
+ pg_catalog.pg_get_functiondef(procedure.oid)
270
+ from pg_catalog.pg_proc procedure
271
+ join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
272
+ where namespace.nspname='agent_bridge' and procedure.proname in (
273
+ 'current_request_workspace','current_request_principal','record_delivery_event',
274
+ 'reject_delivery_identity_mutation','enforce_delivery_actor_role',
275
+ 'reject_delivery_event_mutation','row_isolation_catalog_definition'
276
+ )
277
+ )
278
+ select string_agg(kind || E'\x1f' || identity || E'\x1f' || definition,E'\x1e'
279
+ order by kind,identity)
280
+ from catalog_objects
281
+ $$;
282
+
283
+ revoke all on function agent_bridge.current_request_workspace() from public;
284
+ revoke all on function agent_bridge.current_request_principal() from public;
285
+ revoke all on function agent_bridge.row_isolation_catalog_definition() from public;
286
+
287
+ do $owners$
288
+ declare
289
+ suffix text := substr(md5(current_database()),1,16);
290
+ data_owner text := 'agent_bridge_data_owner_' || suffix;
291
+ context_reader text := 'agent_bridge_context_reader_' || suffix;
292
+ event_writer text := 'agent_bridge_event_writer_' || suffix;
293
+ table_name text;
294
+ begin
295
+ execute format('grant usage on schema agent_bridge to %I',data_owner);
296
+ foreach table_name in array array[
297
+ 'messages','receipts','deliveries','delivery_events','agent_instances'
298
+ ] loop
299
+ execute format('alter table agent_bridge.%I owner to %I',table_name,data_owner);
300
+ end loop;
301
+ execute format('alter function agent_bridge.current_request_workspace() owner to %I',context_reader);
302
+ execute format('alter function agent_bridge.current_request_principal() owner to %I',context_reader);
303
+ 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);
305
+ 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
+ execute format('grant insert on agent_bridge.delivery_events to %I',event_writer);
308
+ execute format('grant usage on sequence agent_bridge.delivery_events_sequence_seq to %I',event_writer);
309
+ end
310
+ $owners$;
311
+
312
+ revoke execute on all functions in schema agent_bridge from public;
313
+
314
+ alter table agent_bridge.messages enable row level security;
315
+ alter table agent_bridge.messages force row level security;
316
+ alter table agent_bridge.receipts enable row level security;
317
+ alter table agent_bridge.receipts force row level security;
318
+ alter table agent_bridge.deliveries enable row level security;
319
+ alter table agent_bridge.deliveries force row level security;
320
+ alter table agent_bridge.delivery_events enable row level security;
321
+ alter table agent_bridge.delivery_events force row level security;
322
+ alter table agent_bridge.agent_instances enable row level security;
323
+ alter table agent_bridge.agent_instances force row level security;
324
+
325
+ do $policies$
326
+ declare
327
+ suffix text := substr(md5(current_database()),1,16);
328
+ runtime_role text := 'agent_bridge_runtime_' || suffix;
329
+ data_owner text := 'agent_bridge_data_owner_' || suffix;
330
+ event_writer text := 'agent_bridge_event_writer_' || suffix;
331
+ table_name text;
332
+ begin
333
+ foreach table_name in array array[
334
+ 'messages','receipts','deliveries','delivery_events','agent_instances'
335
+ ] loop
336
+ execute format(
337
+ 'create policy %I on agent_bridge.%I for all to %I using (true) with check (true)',
338
+ table_name || '_owner_all',table_name,data_owner
339
+ );
340
+ end loop;
341
+
342
+ execute format($policy$
343
+ create policy messages_runtime_select on agent_bridge.messages
344
+ for select to %I using (
345
+ workspace=(select agent_bridge.current_request_workspace())
346
+ and (
347
+ source=(select agent_bridge.current_request_principal())
348
+ or targets='[]'::jsonb
349
+ or targets ? (select agent_bridge.current_request_principal())
350
+ )
351
+ )$policy$,runtime_role);
352
+ execute format($policy$
353
+ create policy messages_runtime_insert on agent_bridge.messages
354
+ for insert to %I with check (
355
+ workspace=(select agent_bridge.current_request_workspace())
356
+ and source=(select agent_bridge.current_request_principal())
357
+ )$policy$,runtime_role);
358
+
359
+ execute format($policy$
360
+ create policy receipts_runtime_select on agent_bridge.receipts
361
+ for select to %I using (
362
+ workspace=(select agent_bridge.current_request_workspace())
363
+ and principal=(select agent_bridge.current_request_principal())
364
+ )$policy$,runtime_role);
365
+ execute format($policy$
366
+ create policy receipts_runtime_insert on agent_bridge.receipts
367
+ for insert to %I with check (
368
+ workspace=(select agent_bridge.current_request_workspace())
369
+ and principal=(select agent_bridge.current_request_principal())
370
+ )$policy$,runtime_role);
371
+
372
+ execute format($policy$
373
+ create policy deliveries_runtime_select on agent_bridge.deliveries
374
+ for select to %I using (
375
+ workspace=(select agent_bridge.current_request_workspace())
376
+ and (
377
+ recipient=(select agent_bridge.current_request_principal())
378
+ or publisher=(select agent_bridge.current_request_principal())
379
+ )
380
+ )$policy$,runtime_role);
381
+ execute format($policy$
382
+ create policy deliveries_runtime_insert on agent_bridge.deliveries
383
+ for insert to %I with check (
384
+ workspace=(select agent_bridge.current_request_workspace())
385
+ and publisher=(select agent_bridge.current_request_principal())
386
+ )$policy$,runtime_role);
387
+ execute format($policy$
388
+ create policy deliveries_runtime_update on agent_bridge.deliveries
389
+ for update to %I using (
390
+ workspace=(select agent_bridge.current_request_workspace())
391
+ and (
392
+ recipient=(select agent_bridge.current_request_principal())
393
+ or publisher=(select agent_bridge.current_request_principal())
394
+ )
395
+ ) with check (
396
+ workspace=(select agent_bridge.current_request_workspace())
397
+ and (
398
+ recipient=(select agent_bridge.current_request_principal())
399
+ or publisher=(select agent_bridge.current_request_principal())
400
+ )
401
+ )$policy$,runtime_role);
402
+
403
+ execute format($policy$
404
+ create policy delivery_events_runtime_select on agent_bridge.delivery_events
405
+ for select to %I using (
406
+ workspace=(select agent_bridge.current_request_workspace())
407
+ and (
408
+ recipient=(select agent_bridge.current_request_principal())
409
+ or publisher=(select agent_bridge.current_request_principal())
410
+ )
411
+ )$policy$,runtime_role);
412
+ execute format(
413
+ 'create policy delivery_events_writer_insert on agent_bridge.delivery_events for insert to %I with check (true)',
414
+ event_writer
415
+ );
416
+
417
+ execute format($policy$
418
+ create policy agent_instances_runtime_select on agent_bridge.agent_instances
419
+ for select to %I using (
420
+ workspace=(select agent_bridge.current_request_workspace())
421
+ )$policy$,runtime_role);
422
+ execute format($policy$
423
+ create policy agent_instances_runtime_insert on agent_bridge.agent_instances
424
+ for insert to %I with check (
425
+ workspace=(select agent_bridge.current_request_workspace())
426
+ and agent=(select agent_bridge.current_request_principal())
427
+ )$policy$,runtime_role);
428
+ execute format($policy$
429
+ create policy agent_instances_runtime_update on agent_bridge.agent_instances
430
+ for update to %I using (
431
+ workspace=(select agent_bridge.current_request_workspace())
432
+ and agent=(select agent_bridge.current_request_principal())
433
+ ) with check (
434
+ workspace=(select agent_bridge.current_request_workspace())
435
+ and agent=(select agent_bridge.current_request_principal())
436
+ )$policy$,runtime_role);
437
+ execute format($policy$
438
+ create policy agent_instances_runtime_delete on agent_bridge.agent_instances
439
+ for delete to %I using (
440
+ workspace=(select agent_bridge.current_request_workspace())
441
+ and (
442
+ agent=(select agent_bridge.current_request_principal())
443
+ or lease_expires_at<=now()
444
+ )
445
+ )$policy$,runtime_role);
446
+ end
447
+ $policies$;
448
+
449
+ do $grants$
450
+ declare
451
+ suffix text := substr(md5(current_database()),1,16);
452
+ runtime_role text := 'agent_bridge_runtime_' || suffix;
453
+ data_owner text := 'agent_bridge_data_owner_' || suffix;
454
+ context_reader text := 'agent_bridge_context_reader_' || suffix;
455
+ event_writer text := 'agent_bridge_event_writer_' || suffix;
456
+ role_name text;
457
+ begin
458
+ execute format('revoke %I,%I,%I from %I',data_owner,context_reader,event_writer,runtime_role);
459
+ execute format(
460
+ 'revoke all on agent_bridge.messages,agent_bridge.receipts,agent_bridge.deliveries,agent_bridge.delivery_events,agent_bridge.agent_instances from %I',
461
+ runtime_role
462
+ );
463
+ execute format('grant select,insert on agent_bridge.messages,agent_bridge.receipts to %I',runtime_role);
464
+ execute format('grant select,insert,update on agent_bridge.deliveries to %I',runtime_role);
465
+ execute format('grant select on agent_bridge.delivery_events to %I',runtime_role);
466
+ execute format('grant select,insert,update,delete on agent_bridge.agent_instances to %I',runtime_role);
467
+ execute format('grant select on agent_bridge.row_isolation_attestations to %I',runtime_role);
468
+ execute format('revoke all on all sequences in schema agent_bridge from %I',runtime_role);
469
+ revoke all on sequence agent_bridge.messages_sequence_seq from public;
470
+ revoke all on sequence agent_bridge.delivery_events_sequence_seq from public;
471
+ execute format('grant usage on sequence agent_bridge.messages_sequence_seq to %I',runtime_role);
472
+ execute format('grant execute on function agent_bridge.current_request_workspace() to %I',runtime_role);
473
+ execute format('grant execute on function agent_bridge.current_request_principal() to %I',runtime_role);
474
+ execute format('grant execute on function agent_bridge.row_isolation_catalog_definition() to %I',runtime_role);
475
+ execute format('revoke all on function agent_bridge.record_delivery_event() from %I',runtime_role);
476
+ execute format('revoke all on function agent_bridge.reject_delivery_identity_mutation() from %I',runtime_role);
477
+ execute format('revoke all on function agent_bridge.reject_delivery_event_mutation() from %I',runtime_role);
478
+ execute format('revoke all on function agent_bridge.enforce_delivery_actor_role() from %I',runtime_role);
479
+ foreach role_name in array array['anon','authenticated'] loop
480
+ if exists (select 1 from pg_roles where rolname=role_name) then
481
+ execute format('revoke all on function agent_bridge.current_request_workspace() from %I',role_name);
482
+ execute format('revoke all on function agent_bridge.current_request_principal() from %I',role_name);
483
+ execute format('revoke all on function agent_bridge.record_delivery_event() from %I',role_name);
484
+ end if;
485
+ end loop;
486
+ end
487
+ $grants$;
488
+
489
+ insert into agent_bridge.row_isolation_attestations(name,catalog_definition)
490
+ values ('domain-v1',agent_bridge.row_isolation_catalog_definition());
491
+
492
+ insert into agent_bridge.schema_migrations(version,name,checksum)
493
+ values (13,'row_isolation','__AGENT_BRIDGE_MIGRATION_CHECKSUM__');
494
+
495
+ commit;