@consiliency/agent-board-schema 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +55 -0
- package/bin/agent-board-schema.js +97 -0
- package/fixtures/already-applied.json +181 -0
- package/fixtures/divergent-history.json +173 -0
- package/fixtures/production-history.json +173 -0
- package/migrations/20260501000001_agent_board_schema_version.sql +12 -0
- package/migrations/20260502000000_agent_board_schema.sql +180 -0
- package/migrations/20260503000000_agent_board_rpcs.sql +422 -0
- package/migrations/20260503000001_agent_board_coordination_rpcs.sql +229 -0
- package/migrations/20260504000000_agent_board_read_models.sql +206 -0
- package/migrations/20260505000000_agent_board_inbox_schema.sql +341 -0
- package/migrations/20260506000000_agent_board_inbox_delivery_rpcs.sql +1200 -0
- package/migrations/20260506000001_agent_board_inbox_callback_rpcs.sql +330 -0
- package/migrations/20260507000000_agent_board_inbox_reads.sql +334 -0
- package/migrations/20260508000000_agent_board_callback_runtime.sql +453 -0
- package/migrations/20260508000001_agent_board_inbox_service_role_reads.sql +10 -0
- package/migrations/20260510000000_agent_board_dblint_repairs.sql +413 -0
- package/migrations/20260511000000_agent_board_auth_schema.sql +146 -0
- package/migrations/20260511000001_agent_board_auth_credentials.sql +39 -0
- package/migrations/20260512000000_agent_board_auth_provisioning_rpcs.sql +622 -0
- package/migrations/20260512000001_agent_board_auth_v1_rpcs.sql +798 -0
- package/migrations/20260512000002_agent_board_auth_v2_rpcs.sql +1243 -0
- package/migrations/20260512000003_agent_board_auth_rls.sql +484 -0
- package/migrations/20260513000000_agent_board_backup_ops.sql +34 -0
- package/migrations/20260513000001_agent_board_retention.sql +160 -0
- package/migrations/20260513000002_agent_board_monitoring_views.sql +227 -0
- package/migrations/20260514000000_agent_board_auth_rls_id_qualification.sql +150 -0
- package/migrations/20260515000000_agent_board_boardopt.sql +493 -0
- package/migrations/20260516000000_agent_board_callbackcap.sql +113 -0
- package/migrations/20260517000000_agent_board_addressing.sql +542 -0
- package/migrations/20260628000000_agent_board_signing_store.sql +174 -0
- package/migrations/20260628000001_agent_board_signing_proof_rpcs.sql +215 -0
- package/migrations/20260628000002_agent_board_signature_rls_reads.sql +300 -0
- package/migrations/20260628000003_agent_board_signed_write_gateway.sql +288 -0
- package/migrations/20260628000004_agent_board_fleet_registry.sql +411 -0
- package/migrations/20260628000005_agent_board_fleet_policy.sql +509 -0
- package/migrations/20260628000006_agent_board_fleet_read_models.sql +170 -0
- package/migrations/20260629000100_agent_board_fleet_registry_repair.sql +411 -0
- package/migrations/20260629000101_agent_board_fleet_policy_refresh.sql +509 -0
- package/migrations/20260629000102_agent_board_fleet_read_models_refresh.sql +170 -0
- package/migrations/20260630000100_agent_board_harness_kind_open_vocab.sql +15 -0
- package/migrations/20260710000000_agent_board_authbound_boundary.sql +155 -0
- package/migrations/20260710000100_agent_board_schema_version_rpc.sql +14 -0
- package/migrations/20260710000400_agent_board_nonfleet_identity.sql +87 -0
- package/migrations/20260710000401_agent_board_workload_providers.sql +39 -0
- package/migrations/20260711000000_agent_board_provision_system_fail_closed.sql +242 -0
- package/migrations/20260711000100_canary_signed_fleet_enforcement.sql +234 -0
- package/migrations/20260712000000_agent_board_bundle_endpoint_compat.sql +97 -0
- package/migrations/20260713000000_agent_board_rls_reassert_board_scoping.sql +90 -0
- package/package.json +42 -0
- package/provenance.json +580 -0
- package/schema-manifest.json +458 -0
- package/src/check.js +23 -0
- package/src/index.js +50 -0
- package/src/materialize.js +73 -0
- package/src/plan.js +44 -0
- package/src/tooling.test.mjs +97 -0
- package/tests/agent_board_addressing.test.sql +166 -0
- package/tests/agent_board_auth_rls.test.sql +243 -0
- package/tests/agent_board_auth_rls_id_qualification.test.sql +225 -0
- package/tests/agent_board_auth_rpcs.test.sql +311 -0
- package/tests/agent_board_auth_schema.test.sql +411 -0
- package/tests/agent_board_authbound_boundary.test.sql +148 -0
- package/tests/agent_board_boardopt.test.sql +170 -0
- package/tests/agent_board_bundle_endpoint_compat.test.sql +49 -0
- package/tests/agent_board_callback_runtime.test.sql +425 -0
- package/tests/agent_board_callbackcap.test.sql +222 -0
- package/tests/agent_board_canary_signed_fleet_enforcement.test.sql +60 -0
- package/tests/agent_board_dblint_rpcs.test.sql +247 -0
- package/tests/agent_board_fleet_policy.test.sql +370 -0
- package/tests/agent_board_fleet_read_models.test.sql +158 -0
- package/tests/agent_board_fleet_registry.test.sql +149 -0
- package/tests/agent_board_inbox_reads.test.sql +471 -0
- package/tests/agent_board_inbox_rpcs.test.sql +452 -0
- package/tests/agent_board_inbox_schema.test.sql +355 -0
- package/tests/agent_board_nonfleet_identity.test.sql +26 -0
- package/tests/agent_board_rls_reassert_board_scoping.test.sql +39 -0
- package/tests/agent_board_schema_version.test.sql +24 -0
- package/tests/agent_board_schema_version_rpc.test.sql +40 -0
- package/tests/agent_board_signature_rls_reads.test.sql +133 -0
- package/tests/agent_board_signed_write_gateway.test.sql +219 -0
- package/tests/agent_board_signing_proof_rpcs.test.sql +143 -0
- package/tests/agent_board_signing_store.test.sql +183 -0
- package/tests/agent_board_smoke.test.sql +12 -0
- package/tests/agent_board_v1_invariants.test.sql +298 -0
- package/tests/agent_board_v2_invariants.test.sql +753 -0
- package/tests/agent_board_workload_providers.test.sql +15 -0
- package/tests/fixtures/adopter-base.sql +19 -0
- package/tests/fixtures/operator-hardening.sql +27 -0
- package/tests/fixtures/test-prereqs.sql +84 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
create or replace function agent_board._is_service_role()
|
|
2
|
+
returns boolean
|
|
3
|
+
language sql
|
|
4
|
+
stable
|
|
5
|
+
security definer
|
|
6
|
+
set search_path = ''
|
|
7
|
+
as $$
|
|
8
|
+
select coalesce(auth.role(), '') = 'service_role'
|
|
9
|
+
or (
|
|
10
|
+
coalesce(auth.role(), '') = ''
|
|
11
|
+
and session_user in ('postgres', 'supabase_admin')
|
|
12
|
+
)
|
|
13
|
+
$$;
|
|
14
|
+
|
|
15
|
+
create or replace function agent_board._resolved_actor_agent_id(
|
|
16
|
+
p_supplied_agent_id uuid,
|
|
17
|
+
p_param_name text
|
|
18
|
+
)
|
|
19
|
+
returns uuid
|
|
20
|
+
language plpgsql
|
|
21
|
+
stable
|
|
22
|
+
security definer
|
|
23
|
+
set search_path = ''
|
|
24
|
+
as $$
|
|
25
|
+
declare
|
|
26
|
+
v_current_agent_id uuid;
|
|
27
|
+
begin
|
|
28
|
+
if agent_board._is_service_role() then
|
|
29
|
+
if p_supplied_agent_id is null then
|
|
30
|
+
raise exception '% is required for service_role callers', p_param_name using errcode = '22023';
|
|
31
|
+
end if;
|
|
32
|
+
return p_supplied_agent_id;
|
|
33
|
+
end if;
|
|
34
|
+
|
|
35
|
+
v_current_agent_id := agent_board.current_agent_id();
|
|
36
|
+
if v_current_agent_id is null then
|
|
37
|
+
raise exception 'current agent principal not resolved' using errcode = '42501';
|
|
38
|
+
end if;
|
|
39
|
+
|
|
40
|
+
if p_supplied_agent_id is not null and p_supplied_agent_id <> v_current_agent_id then
|
|
41
|
+
raise exception '% must match current_agent_id()', p_param_name using errcode = '42501';
|
|
42
|
+
end if;
|
|
43
|
+
|
|
44
|
+
return v_current_agent_id;
|
|
45
|
+
end;
|
|
46
|
+
$$;
|
|
47
|
+
|
|
48
|
+
create or replace function agent_board._require_platform_admin_user_id()
|
|
49
|
+
returns uuid
|
|
50
|
+
language plpgsql
|
|
51
|
+
stable
|
|
52
|
+
security definer
|
|
53
|
+
set search_path = ''
|
|
54
|
+
as $$
|
|
55
|
+
declare
|
|
56
|
+
v_user_id uuid;
|
|
57
|
+
begin
|
|
58
|
+
if agent_board._is_service_role() then
|
|
59
|
+
return null;
|
|
60
|
+
end if;
|
|
61
|
+
|
|
62
|
+
v_user_id := auth.uid();
|
|
63
|
+
if v_user_id is null then
|
|
64
|
+
raise exception 'authenticated caller required' using errcode = '42501';
|
|
65
|
+
end if;
|
|
66
|
+
|
|
67
|
+
if not exists (
|
|
68
|
+
select 1
|
|
69
|
+
from public.user_org_roles uor
|
|
70
|
+
where uor.user_id = v_user_id
|
|
71
|
+
and uor.role = 'platform_admin'
|
|
72
|
+
) then
|
|
73
|
+
raise exception 'platform_admin role required' using errcode = '42501';
|
|
74
|
+
end if;
|
|
75
|
+
|
|
76
|
+
return v_user_id;
|
|
77
|
+
end;
|
|
78
|
+
$$;
|
|
79
|
+
|
|
80
|
+
create or replace function agent_board._normalize_provision_slug(p_slug text)
|
|
81
|
+
returns text
|
|
82
|
+
language plpgsql
|
|
83
|
+
immutable
|
|
84
|
+
security definer
|
|
85
|
+
set search_path = ''
|
|
86
|
+
as $$
|
|
87
|
+
declare
|
|
88
|
+
v_slug text := lower(coalesce(trim(p_slug), ''));
|
|
89
|
+
begin
|
|
90
|
+
if v_slug = '' or v_slug !~ '^[a-z0-9]+(?:[-_][a-z0-9]+)*$' then
|
|
91
|
+
raise exception 'slug must match ^[a-z0-9]+(?:[-_][a-z0-9]+)*$' using errcode = '22023';
|
|
92
|
+
end if;
|
|
93
|
+
|
|
94
|
+
return v_slug;
|
|
95
|
+
end;
|
|
96
|
+
$$;
|
|
97
|
+
|
|
98
|
+
create or replace function agent_board._default_provision_display_name(p_slug text)
|
|
99
|
+
returns text
|
|
100
|
+
language sql
|
|
101
|
+
immutable
|
|
102
|
+
security definer
|
|
103
|
+
set search_path = ''
|
|
104
|
+
as $$
|
|
105
|
+
select replace(initcap(replace(p_slug, '-', ' ')), '_', ' ')
|
|
106
|
+
$$;
|
|
107
|
+
|
|
108
|
+
create or replace function agent_board._ensure_agent_auth_user(
|
|
109
|
+
p_slug text
|
|
110
|
+
)
|
|
111
|
+
returns uuid
|
|
112
|
+
language plpgsql
|
|
113
|
+
security definer
|
|
114
|
+
set search_path = ''
|
|
115
|
+
as $$
|
|
116
|
+
declare
|
|
117
|
+
v_email text := format('agent+%s@agent-board.local', p_slug);
|
|
118
|
+
v_user_id uuid;
|
|
119
|
+
begin
|
|
120
|
+
select u.id
|
|
121
|
+
into v_user_id
|
|
122
|
+
from auth.users u
|
|
123
|
+
where lower(u.email) = v_email
|
|
124
|
+
limit 1;
|
|
125
|
+
|
|
126
|
+
if v_user_id is not null then
|
|
127
|
+
return v_user_id;
|
|
128
|
+
end if;
|
|
129
|
+
|
|
130
|
+
v_user_id := gen_random_uuid();
|
|
131
|
+
|
|
132
|
+
insert into auth.users (
|
|
133
|
+
id,
|
|
134
|
+
instance_id,
|
|
135
|
+
email,
|
|
136
|
+
email_confirmed_at,
|
|
137
|
+
encrypted_password,
|
|
138
|
+
created_at,
|
|
139
|
+
updated_at,
|
|
140
|
+
raw_app_meta_data,
|
|
141
|
+
raw_user_meta_data,
|
|
142
|
+
aud,
|
|
143
|
+
role
|
|
144
|
+
)
|
|
145
|
+
values (
|
|
146
|
+
v_user_id,
|
|
147
|
+
'00000000-0000-0000-0000-000000000000',
|
|
148
|
+
v_email,
|
|
149
|
+
now(),
|
|
150
|
+
'',
|
|
151
|
+
now(),
|
|
152
|
+
now(),
|
|
153
|
+
jsonb_build_object('provider', 'agent_board', 'providers', jsonb_build_array('agent_board')),
|
|
154
|
+
jsonb_build_object('slug', p_slug, 'issuer', 'agent_board.provision_agent'),
|
|
155
|
+
'authenticated',
|
|
156
|
+
'authenticated'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
insert into auth.identities (
|
|
160
|
+
id,
|
|
161
|
+
user_id,
|
|
162
|
+
provider_id,
|
|
163
|
+
provider,
|
|
164
|
+
identity_data,
|
|
165
|
+
last_sign_in_at,
|
|
166
|
+
created_at,
|
|
167
|
+
updated_at
|
|
168
|
+
)
|
|
169
|
+
values (
|
|
170
|
+
gen_random_uuid(),
|
|
171
|
+
v_user_id,
|
|
172
|
+
p_slug,
|
|
173
|
+
'agent_board',
|
|
174
|
+
jsonb_build_object('sub', p_slug, 'slug', p_slug),
|
|
175
|
+
now(),
|
|
176
|
+
now(),
|
|
177
|
+
now()
|
|
178
|
+
)
|
|
179
|
+
on conflict do nothing;
|
|
180
|
+
|
|
181
|
+
return v_user_id;
|
|
182
|
+
end;
|
|
183
|
+
$$;
|
|
184
|
+
|
|
185
|
+
create or replace function agent_board._current_agent_has_board_access(
|
|
186
|
+
p_board_id uuid
|
|
187
|
+
)
|
|
188
|
+
returns boolean
|
|
189
|
+
language sql
|
|
190
|
+
stable
|
|
191
|
+
security definer
|
|
192
|
+
set search_path = ''
|
|
193
|
+
as $$
|
|
194
|
+
select
|
|
195
|
+
agent_board._is_service_role()
|
|
196
|
+
or exists (
|
|
197
|
+
select 1
|
|
198
|
+
from agent_board.board_memberships bm
|
|
199
|
+
where bm.board_id = p_board_id
|
|
200
|
+
and bm.agent_id = agent_board.current_agent_id()
|
|
201
|
+
and bm.membership_state in ('joined', 'subscribed')
|
|
202
|
+
)
|
|
203
|
+
$$;
|
|
204
|
+
|
|
205
|
+
create or replace function agent_board._current_agent_can_access_thread(
|
|
206
|
+
p_thread_id uuid
|
|
207
|
+
)
|
|
208
|
+
returns boolean
|
|
209
|
+
language sql
|
|
210
|
+
stable
|
|
211
|
+
security definer
|
|
212
|
+
set search_path = ''
|
|
213
|
+
as $$
|
|
214
|
+
select
|
|
215
|
+
agent_board._is_service_role()
|
|
216
|
+
or exists (
|
|
217
|
+
select 1
|
|
218
|
+
from agent_board.threads t
|
|
219
|
+
where t.id = p_thread_id
|
|
220
|
+
and (
|
|
221
|
+
t.created_by_agent_id = agent_board.current_agent_id()
|
|
222
|
+
or exists (
|
|
223
|
+
select 1
|
|
224
|
+
from agent_board.messages m
|
|
225
|
+
where m.thread_id = t.id
|
|
226
|
+
and m.author_agent_id = agent_board.current_agent_id()
|
|
227
|
+
)
|
|
228
|
+
or exists (
|
|
229
|
+
select 1
|
|
230
|
+
from agent_board.tasks task
|
|
231
|
+
where task.thread_id = t.id
|
|
232
|
+
and (
|
|
233
|
+
task.created_by_agent_id = agent_board.current_agent_id()
|
|
234
|
+
or task.assigned_agent_id = agent_board.current_agent_id()
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
or exists (
|
|
238
|
+
select 1
|
|
239
|
+
from agent_board.read_receipts rr
|
|
240
|
+
where rr.thread_id = t.id
|
|
241
|
+
and rr.agent_id = agent_board.current_agent_id()
|
|
242
|
+
)
|
|
243
|
+
or exists (
|
|
244
|
+
select 1
|
|
245
|
+
from agent_board.claims c
|
|
246
|
+
where c.thread_id = t.id
|
|
247
|
+
and c.agent_id = agent_board.current_agent_id()
|
|
248
|
+
)
|
|
249
|
+
or exists (
|
|
250
|
+
select 1
|
|
251
|
+
from agent_board.inbox_items ii
|
|
252
|
+
left join agent_board.messages im on im.id = ii.source_message_id
|
|
253
|
+
left join agent_board.tasks it on it.id = ii.source_task_id
|
|
254
|
+
left join agent_board.delegations ide on ide.id = ii.source_delegation_id
|
|
255
|
+
left join agent_board.tasks idt on idt.id = ide.child_task_id
|
|
256
|
+
where ii.recipient_agent_id = agent_board.current_agent_id()
|
|
257
|
+
and (
|
|
258
|
+
im.thread_id = t.id
|
|
259
|
+
or it.thread_id = t.id
|
|
260
|
+
or idt.thread_id = t.id
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
or exists (
|
|
264
|
+
select 1
|
|
265
|
+
from agent_board.delivery_recipients dr
|
|
266
|
+
join agent_board.messages bm on bm.id = dr.source_message_id
|
|
267
|
+
where dr.board_id is not null
|
|
268
|
+
and bm.thread_id = t.id
|
|
269
|
+
and agent_board._current_agent_has_board_access(dr.board_id)
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
$$;
|
|
274
|
+
|
|
275
|
+
create or replace function agent_board._current_agent_can_access_task(
|
|
276
|
+
p_task_id uuid
|
|
277
|
+
)
|
|
278
|
+
returns boolean
|
|
279
|
+
language sql
|
|
280
|
+
stable
|
|
281
|
+
security definer
|
|
282
|
+
set search_path = ''
|
|
283
|
+
as $$
|
|
284
|
+
select
|
|
285
|
+
agent_board._is_service_role()
|
|
286
|
+
or exists (
|
|
287
|
+
select 1
|
|
288
|
+
from agent_board.tasks t
|
|
289
|
+
where t.id = p_task_id
|
|
290
|
+
and (
|
|
291
|
+
t.created_by_agent_id = agent_board.current_agent_id()
|
|
292
|
+
or t.assigned_agent_id = agent_board.current_agent_id()
|
|
293
|
+
or agent_board._current_agent_can_access_thread(t.thread_id)
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
$$;
|
|
297
|
+
|
|
298
|
+
create or replace function agent_board._mint_agent_refresh_token()
|
|
299
|
+
returns text
|
|
300
|
+
language sql
|
|
301
|
+
volatile
|
|
302
|
+
security definer
|
|
303
|
+
set search_path = ''
|
|
304
|
+
as $$
|
|
305
|
+
select encode(extensions.gen_random_bytes(32), 'hex')
|
|
306
|
+
$$;
|
|
307
|
+
|
|
308
|
+
create or replace function agent_board._issue_agent_credential(
|
|
309
|
+
p_agent_id uuid,
|
|
310
|
+
p_credential_label text,
|
|
311
|
+
p_replaces_credential_id uuid default null,
|
|
312
|
+
p_metadata jsonb default '{}'::jsonb
|
|
313
|
+
)
|
|
314
|
+
returns table(
|
|
315
|
+
credential_id uuid,
|
|
316
|
+
refresh_token text,
|
|
317
|
+
expires_at timestamptz,
|
|
318
|
+
created_at timestamptz,
|
|
319
|
+
replaces_credential_id uuid
|
|
320
|
+
)
|
|
321
|
+
language plpgsql
|
|
322
|
+
security definer
|
|
323
|
+
set search_path = ''
|
|
324
|
+
as $$
|
|
325
|
+
declare
|
|
326
|
+
v_refresh_token text;
|
|
327
|
+
v_expires_at timestamptz := now() + interval '30 days';
|
|
328
|
+
v_created_at timestamptz := now();
|
|
329
|
+
v_credential_id uuid;
|
|
330
|
+
begin
|
|
331
|
+
v_refresh_token := agent_board._mint_agent_refresh_token();
|
|
332
|
+
|
|
333
|
+
insert into agent_board.agent_credentials(
|
|
334
|
+
agent_id,
|
|
335
|
+
refresh_token_hash,
|
|
336
|
+
hash_version,
|
|
337
|
+
credential_label,
|
|
338
|
+
expires_at,
|
|
339
|
+
replaces_credential_id,
|
|
340
|
+
metadata,
|
|
341
|
+
created_at,
|
|
342
|
+
updated_at
|
|
343
|
+
)
|
|
344
|
+
values (
|
|
345
|
+
p_agent_id,
|
|
346
|
+
extensions.digest(v_refresh_token, 'sha256'),
|
|
347
|
+
1,
|
|
348
|
+
p_credential_label,
|
|
349
|
+
v_expires_at,
|
|
350
|
+
p_replaces_credential_id,
|
|
351
|
+
coalesce(p_metadata, '{}'::jsonb),
|
|
352
|
+
v_created_at,
|
|
353
|
+
v_created_at
|
|
354
|
+
)
|
|
355
|
+
returning id into v_credential_id;
|
|
356
|
+
|
|
357
|
+
if p_replaces_credential_id is not null then
|
|
358
|
+
update agent_board.agent_credentials
|
|
359
|
+
set revoked_at = coalesce(revoked_at, v_created_at),
|
|
360
|
+
rotated_at = coalesce(rotated_at, v_created_at),
|
|
361
|
+
replaced_by_credential_id = v_credential_id,
|
|
362
|
+
updated_at = v_created_at
|
|
363
|
+
where id = p_replaces_credential_id;
|
|
364
|
+
end if;
|
|
365
|
+
|
|
366
|
+
return query
|
|
367
|
+
select v_credential_id, v_refresh_token, v_expires_at, v_created_at, p_replaces_credential_id;
|
|
368
|
+
end;
|
|
369
|
+
$$;
|
|
370
|
+
|
|
371
|
+
create or replace function agent_board.provision_agent(
|
|
372
|
+
p_slug text,
|
|
373
|
+
p_kind text,
|
|
374
|
+
p_scopes jsonb default '[]'::jsonb,
|
|
375
|
+
p_owner_user_id uuid default null
|
|
376
|
+
)
|
|
377
|
+
returns table(
|
|
378
|
+
agent_id uuid,
|
|
379
|
+
auth_user_id uuid,
|
|
380
|
+
credential_id uuid,
|
|
381
|
+
refresh_token text,
|
|
382
|
+
expires_at timestamptz,
|
|
383
|
+
created_at timestamptz
|
|
384
|
+
)
|
|
385
|
+
language plpgsql
|
|
386
|
+
security definer
|
|
387
|
+
set search_path = agent_board, public
|
|
388
|
+
as $$
|
|
389
|
+
declare
|
|
390
|
+
v_slug text;
|
|
391
|
+
v_kind text;
|
|
392
|
+
v_owner_user_id uuid;
|
|
393
|
+
v_admin_user_id uuid;
|
|
394
|
+
v_auth_user_id uuid;
|
|
395
|
+
v_agent agent_board.agents%rowtype;
|
|
396
|
+
v_credential record;
|
|
397
|
+
begin
|
|
398
|
+
v_admin_user_id := agent_board._require_platform_admin_user_id();
|
|
399
|
+
v_slug := agent_board._normalize_provision_slug(p_slug);
|
|
400
|
+
v_kind := lower(coalesce(trim(p_kind), ''));
|
|
401
|
+
v_owner_user_id := p_owner_user_id;
|
|
402
|
+
|
|
403
|
+
if v_kind not in ('human', 'agent', 'system') then
|
|
404
|
+
raise exception 'kind must be one of human, agent, or system' using errcode = '22023';
|
|
405
|
+
end if;
|
|
406
|
+
|
|
407
|
+
if v_kind = 'system' then
|
|
408
|
+
v_owner_user_id := null;
|
|
409
|
+
elsif v_owner_user_id is null then
|
|
410
|
+
raise exception 'owner_user_id is required for human and agent principals' using errcode = '22023';
|
|
411
|
+
elsif not exists (select 1 from auth.users where id = v_owner_user_id) then
|
|
412
|
+
raise exception 'owner user not found' using errcode = 'P0002';
|
|
413
|
+
end if;
|
|
414
|
+
|
|
415
|
+
select *
|
|
416
|
+
into v_agent
|
|
417
|
+
from agent_board.agents
|
|
418
|
+
where handle = v_slug
|
|
419
|
+
limit 1
|
|
420
|
+
for update;
|
|
421
|
+
|
|
422
|
+
v_auth_user_id := coalesce(v_agent.auth_user_id, agent_board._ensure_agent_auth_user(v_slug));
|
|
423
|
+
|
|
424
|
+
if v_agent.id is null then
|
|
425
|
+
insert into agent_board.agents(
|
|
426
|
+
agent_type,
|
|
427
|
+
kind,
|
|
428
|
+
handle,
|
|
429
|
+
display_name,
|
|
430
|
+
status,
|
|
431
|
+
auth_user_id,
|
|
432
|
+
owner_user_id,
|
|
433
|
+
metadata
|
|
434
|
+
)
|
|
435
|
+
values (
|
|
436
|
+
v_kind,
|
|
437
|
+
v_kind::agent_board.agent_kind,
|
|
438
|
+
v_slug,
|
|
439
|
+
agent_board._default_provision_display_name(v_slug),
|
|
440
|
+
'active',
|
|
441
|
+
v_auth_user_id,
|
|
442
|
+
v_owner_user_id,
|
|
443
|
+
jsonb_build_object(
|
|
444
|
+
'scopes', coalesce(p_scopes, '[]'::jsonb),
|
|
445
|
+
'provisionedByUserId', v_admin_user_id,
|
|
446
|
+
'provisionedAt', now()
|
|
447
|
+
)
|
|
448
|
+
)
|
|
449
|
+
returning * into v_agent;
|
|
450
|
+
else
|
|
451
|
+
update agent_board.agents a
|
|
452
|
+
set agent_type = v_kind,
|
|
453
|
+
kind = v_kind::agent_board.agent_kind,
|
|
454
|
+
status = 'active',
|
|
455
|
+
auth_user_id = v_auth_user_id,
|
|
456
|
+
owner_user_id = v_owner_user_id,
|
|
457
|
+
metadata = coalesce(a.metadata, '{}'::jsonb)
|
|
458
|
+
|| jsonb_build_object(
|
|
459
|
+
'scopes', coalesce(p_scopes, '[]'::jsonb),
|
|
460
|
+
'provisionedByUserId', v_admin_user_id,
|
|
461
|
+
'provisionedAt', now()
|
|
462
|
+
),
|
|
463
|
+
updated_at = now()
|
|
464
|
+
where a.id = v_agent.id
|
|
465
|
+
returning * into v_agent;
|
|
466
|
+
end if;
|
|
467
|
+
|
|
468
|
+
select *
|
|
469
|
+
into v_credential
|
|
470
|
+
from agent_board._issue_agent_credential(
|
|
471
|
+
v_agent.id,
|
|
472
|
+
'provisioned',
|
|
473
|
+
(
|
|
474
|
+
select ac.id
|
|
475
|
+
from agent_board.agent_credentials ac
|
|
476
|
+
where ac.agent_id = v_agent.id
|
|
477
|
+
and ac.revoked_at is null
|
|
478
|
+
order by ac.created_at desc
|
|
479
|
+
limit 1
|
|
480
|
+
),
|
|
481
|
+
jsonb_build_object('issuedByUserId', v_admin_user_id, 'issuedAt', now(), 'scopes', coalesce(p_scopes, '[]'::jsonb))
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
return query
|
|
485
|
+
select v_agent.id, v_auth_user_id, v_credential.credential_id, v_credential.refresh_token, v_credential.expires_at, v_credential.created_at;
|
|
486
|
+
end;
|
|
487
|
+
$$;
|
|
488
|
+
|
|
489
|
+
create or replace function agent_board.rotate_agent_token(
|
|
490
|
+
p_agent_id uuid
|
|
491
|
+
)
|
|
492
|
+
returns table(
|
|
493
|
+
agent_id uuid,
|
|
494
|
+
credential_id uuid,
|
|
495
|
+
refresh_token text,
|
|
496
|
+
expires_at timestamptz,
|
|
497
|
+
rotated_at timestamptz,
|
|
498
|
+
replaces_credential_id uuid
|
|
499
|
+
)
|
|
500
|
+
language plpgsql
|
|
501
|
+
security definer
|
|
502
|
+
set search_path = agent_board, public
|
|
503
|
+
as $$
|
|
504
|
+
declare
|
|
505
|
+
v_admin_user_id uuid;
|
|
506
|
+
v_agent agent_board.agents%rowtype;
|
|
507
|
+
v_previous_credential_id uuid;
|
|
508
|
+
v_credential record;
|
|
509
|
+
begin
|
|
510
|
+
v_admin_user_id := agent_board._require_platform_admin_user_id();
|
|
511
|
+
|
|
512
|
+
select *
|
|
513
|
+
into v_agent
|
|
514
|
+
from agent_board.agents
|
|
515
|
+
where id = p_agent_id
|
|
516
|
+
limit 1
|
|
517
|
+
for update;
|
|
518
|
+
|
|
519
|
+
if v_agent.id is null then
|
|
520
|
+
raise exception 'agent not found' using errcode = 'P0002';
|
|
521
|
+
end if;
|
|
522
|
+
|
|
523
|
+
select ac.id
|
|
524
|
+
into v_previous_credential_id
|
|
525
|
+
from agent_board.agent_credentials ac
|
|
526
|
+
where ac.agent_id = p_agent_id
|
|
527
|
+
and ac.revoked_at is null
|
|
528
|
+
order by ac.created_at desc
|
|
529
|
+
limit 1
|
|
530
|
+
for update;
|
|
531
|
+
|
|
532
|
+
select *
|
|
533
|
+
into v_credential
|
|
534
|
+
from agent_board._issue_agent_credential(
|
|
535
|
+
p_agent_id,
|
|
536
|
+
'rotated',
|
|
537
|
+
v_previous_credential_id,
|
|
538
|
+
jsonb_build_object('rotatedByUserId', v_admin_user_id, 'rotatedAt', now())
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
return query
|
|
542
|
+
select p_agent_id, v_credential.credential_id, v_credential.refresh_token, v_credential.expires_at, v_credential.created_at, v_credential.replaces_credential_id;
|
|
543
|
+
end;
|
|
544
|
+
$$;
|
|
545
|
+
|
|
546
|
+
create or replace function agent_board.revoke_agent(
|
|
547
|
+
p_agent_id uuid,
|
|
548
|
+
p_reason text default null
|
|
549
|
+
)
|
|
550
|
+
returns table(
|
|
551
|
+
agent_id uuid,
|
|
552
|
+
status text,
|
|
553
|
+
revoked_credential_count integer,
|
|
554
|
+
revoked_at timestamptz
|
|
555
|
+
)
|
|
556
|
+
language plpgsql
|
|
557
|
+
security definer
|
|
558
|
+
set search_path = agent_board, public
|
|
559
|
+
as $$
|
|
560
|
+
declare
|
|
561
|
+
v_admin_user_id uuid;
|
|
562
|
+
v_agent agent_board.agents%rowtype;
|
|
563
|
+
v_revoked_at timestamptz := now();
|
|
564
|
+
v_revoked_count integer := 0;
|
|
565
|
+
begin
|
|
566
|
+
v_admin_user_id := agent_board._require_platform_admin_user_id();
|
|
567
|
+
|
|
568
|
+
update agent_board.agents a
|
|
569
|
+
set status = 'suspended',
|
|
570
|
+
updated_at = v_revoked_at,
|
|
571
|
+
metadata = coalesce(a.metadata, '{}'::jsonb)
|
|
572
|
+
|| jsonb_build_object(
|
|
573
|
+
'revokedByUserId', v_admin_user_id,
|
|
574
|
+
'revokedAt', v_revoked_at,
|
|
575
|
+
'revocationReason', nullif(trim(p_reason), '')
|
|
576
|
+
)
|
|
577
|
+
where a.id = p_agent_id
|
|
578
|
+
returning * into v_agent;
|
|
579
|
+
|
|
580
|
+
if v_agent.id is null then
|
|
581
|
+
raise exception 'agent not found' using errcode = 'P0002';
|
|
582
|
+
end if;
|
|
583
|
+
|
|
584
|
+
update agent_board.agent_credentials ac
|
|
585
|
+
set revoked_at = coalesce(ac.revoked_at, v_revoked_at),
|
|
586
|
+
updated_at = v_revoked_at,
|
|
587
|
+
metadata = coalesce(ac.metadata, '{}'::jsonb)
|
|
588
|
+
|| jsonb_build_object(
|
|
589
|
+
'revokedByUserId', v_admin_user_id,
|
|
590
|
+
'revokedAt', v_revoked_at,
|
|
591
|
+
'revocationReason', nullif(trim(p_reason), '')
|
|
592
|
+
)
|
|
593
|
+
where ac.agent_id = p_agent_id
|
|
594
|
+
and ac.revoked_at is null;
|
|
595
|
+
|
|
596
|
+
get diagnostics v_revoked_count = row_count;
|
|
597
|
+
|
|
598
|
+
return query
|
|
599
|
+
select v_agent.id, v_agent.status, v_revoked_count, v_revoked_at;
|
|
600
|
+
end;
|
|
601
|
+
$$;
|
|
602
|
+
|
|
603
|
+
revoke all on function agent_board._is_service_role() from public, anon, authenticated;
|
|
604
|
+
revoke all on function agent_board._resolved_actor_agent_id(uuid, text) from public, anon, authenticated;
|
|
605
|
+
revoke all on function agent_board._require_platform_admin_user_id() from public, anon, authenticated;
|
|
606
|
+
revoke all on function agent_board._normalize_provision_slug(text) from public, anon, authenticated;
|
|
607
|
+
revoke all on function agent_board._default_provision_display_name(text) from public, anon, authenticated;
|
|
608
|
+
revoke all on function agent_board._ensure_agent_auth_user(text) from public, anon, authenticated;
|
|
609
|
+
revoke all on function agent_board._current_agent_has_board_access(uuid) from public, anon, authenticated;
|
|
610
|
+
revoke all on function agent_board._current_agent_can_access_thread(uuid) from public, anon, authenticated;
|
|
611
|
+
revoke all on function agent_board._current_agent_can_access_task(uuid) from public, anon, authenticated;
|
|
612
|
+
revoke all on function agent_board._mint_agent_refresh_token() from public, anon, authenticated;
|
|
613
|
+
revoke all on function agent_board._issue_agent_credential(uuid, text, uuid, jsonb) from public, anon, authenticated;
|
|
614
|
+
|
|
615
|
+
revoke all on function agent_board.provision_agent(text, text, jsonb, uuid) from public, anon;
|
|
616
|
+
revoke all on function agent_board.rotate_agent_token(uuid) from public, anon;
|
|
617
|
+
revoke all on function agent_board.revoke_agent(uuid, text) from public, anon;
|
|
618
|
+
|
|
619
|
+
grant execute on all functions in schema agent_board to service_role;
|
|
620
|
+
grant execute on function agent_board.provision_agent(text, text, jsonb, uuid) to authenticated;
|
|
621
|
+
grant execute on function agent_board.rotate_agent_token(uuid) to authenticated;
|
|
622
|
+
grant execute on function agent_board.revoke_agent(uuid, text) to authenticated;
|