@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.
Files changed (92) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +11 -0
  3. package/README.md +55 -0
  4. package/bin/agent-board-schema.js +97 -0
  5. package/fixtures/already-applied.json +181 -0
  6. package/fixtures/divergent-history.json +173 -0
  7. package/fixtures/production-history.json +173 -0
  8. package/migrations/20260501000001_agent_board_schema_version.sql +12 -0
  9. package/migrations/20260502000000_agent_board_schema.sql +180 -0
  10. package/migrations/20260503000000_agent_board_rpcs.sql +422 -0
  11. package/migrations/20260503000001_agent_board_coordination_rpcs.sql +229 -0
  12. package/migrations/20260504000000_agent_board_read_models.sql +206 -0
  13. package/migrations/20260505000000_agent_board_inbox_schema.sql +341 -0
  14. package/migrations/20260506000000_agent_board_inbox_delivery_rpcs.sql +1200 -0
  15. package/migrations/20260506000001_agent_board_inbox_callback_rpcs.sql +330 -0
  16. package/migrations/20260507000000_agent_board_inbox_reads.sql +334 -0
  17. package/migrations/20260508000000_agent_board_callback_runtime.sql +453 -0
  18. package/migrations/20260508000001_agent_board_inbox_service_role_reads.sql +10 -0
  19. package/migrations/20260510000000_agent_board_dblint_repairs.sql +413 -0
  20. package/migrations/20260511000000_agent_board_auth_schema.sql +146 -0
  21. package/migrations/20260511000001_agent_board_auth_credentials.sql +39 -0
  22. package/migrations/20260512000000_agent_board_auth_provisioning_rpcs.sql +622 -0
  23. package/migrations/20260512000001_agent_board_auth_v1_rpcs.sql +798 -0
  24. package/migrations/20260512000002_agent_board_auth_v2_rpcs.sql +1243 -0
  25. package/migrations/20260512000003_agent_board_auth_rls.sql +484 -0
  26. package/migrations/20260513000000_agent_board_backup_ops.sql +34 -0
  27. package/migrations/20260513000001_agent_board_retention.sql +160 -0
  28. package/migrations/20260513000002_agent_board_monitoring_views.sql +227 -0
  29. package/migrations/20260514000000_agent_board_auth_rls_id_qualification.sql +150 -0
  30. package/migrations/20260515000000_agent_board_boardopt.sql +493 -0
  31. package/migrations/20260516000000_agent_board_callbackcap.sql +113 -0
  32. package/migrations/20260517000000_agent_board_addressing.sql +542 -0
  33. package/migrations/20260628000000_agent_board_signing_store.sql +174 -0
  34. package/migrations/20260628000001_agent_board_signing_proof_rpcs.sql +215 -0
  35. package/migrations/20260628000002_agent_board_signature_rls_reads.sql +300 -0
  36. package/migrations/20260628000003_agent_board_signed_write_gateway.sql +288 -0
  37. package/migrations/20260628000004_agent_board_fleet_registry.sql +411 -0
  38. package/migrations/20260628000005_agent_board_fleet_policy.sql +509 -0
  39. package/migrations/20260628000006_agent_board_fleet_read_models.sql +170 -0
  40. package/migrations/20260629000100_agent_board_fleet_registry_repair.sql +411 -0
  41. package/migrations/20260629000101_agent_board_fleet_policy_refresh.sql +509 -0
  42. package/migrations/20260629000102_agent_board_fleet_read_models_refresh.sql +170 -0
  43. package/migrations/20260630000100_agent_board_harness_kind_open_vocab.sql +15 -0
  44. package/migrations/20260710000000_agent_board_authbound_boundary.sql +155 -0
  45. package/migrations/20260710000100_agent_board_schema_version_rpc.sql +14 -0
  46. package/migrations/20260710000400_agent_board_nonfleet_identity.sql +87 -0
  47. package/migrations/20260710000401_agent_board_workload_providers.sql +39 -0
  48. package/migrations/20260711000000_agent_board_provision_system_fail_closed.sql +242 -0
  49. package/migrations/20260711000100_canary_signed_fleet_enforcement.sql +234 -0
  50. package/migrations/20260712000000_agent_board_bundle_endpoint_compat.sql +97 -0
  51. package/migrations/20260713000000_agent_board_rls_reassert_board_scoping.sql +90 -0
  52. package/package.json +42 -0
  53. package/provenance.json +580 -0
  54. package/schema-manifest.json +458 -0
  55. package/src/check.js +23 -0
  56. package/src/index.js +50 -0
  57. package/src/materialize.js +73 -0
  58. package/src/plan.js +44 -0
  59. package/src/tooling.test.mjs +97 -0
  60. package/tests/agent_board_addressing.test.sql +166 -0
  61. package/tests/agent_board_auth_rls.test.sql +243 -0
  62. package/tests/agent_board_auth_rls_id_qualification.test.sql +225 -0
  63. package/tests/agent_board_auth_rpcs.test.sql +311 -0
  64. package/tests/agent_board_auth_schema.test.sql +411 -0
  65. package/tests/agent_board_authbound_boundary.test.sql +148 -0
  66. package/tests/agent_board_boardopt.test.sql +170 -0
  67. package/tests/agent_board_bundle_endpoint_compat.test.sql +49 -0
  68. package/tests/agent_board_callback_runtime.test.sql +425 -0
  69. package/tests/agent_board_callbackcap.test.sql +222 -0
  70. package/tests/agent_board_canary_signed_fleet_enforcement.test.sql +60 -0
  71. package/tests/agent_board_dblint_rpcs.test.sql +247 -0
  72. package/tests/agent_board_fleet_policy.test.sql +370 -0
  73. package/tests/agent_board_fleet_read_models.test.sql +158 -0
  74. package/tests/agent_board_fleet_registry.test.sql +149 -0
  75. package/tests/agent_board_inbox_reads.test.sql +471 -0
  76. package/tests/agent_board_inbox_rpcs.test.sql +452 -0
  77. package/tests/agent_board_inbox_schema.test.sql +355 -0
  78. package/tests/agent_board_nonfleet_identity.test.sql +26 -0
  79. package/tests/agent_board_rls_reassert_board_scoping.test.sql +39 -0
  80. package/tests/agent_board_schema_version.test.sql +24 -0
  81. package/tests/agent_board_schema_version_rpc.test.sql +40 -0
  82. package/tests/agent_board_signature_rls_reads.test.sql +133 -0
  83. package/tests/agent_board_signed_write_gateway.test.sql +219 -0
  84. package/tests/agent_board_signing_proof_rpcs.test.sql +143 -0
  85. package/tests/agent_board_signing_store.test.sql +183 -0
  86. package/tests/agent_board_smoke.test.sql +12 -0
  87. package/tests/agent_board_v1_invariants.test.sql +298 -0
  88. package/tests/agent_board_v2_invariants.test.sql +753 -0
  89. package/tests/agent_board_workload_providers.test.sql +15 -0
  90. package/tests/fixtures/adopter-base.sql +19 -0
  91. package/tests/fixtures/operator-hardening.sql +27 -0
  92. package/tests/fixtures/test-prereqs.sql +84 -0
@@ -0,0 +1,493 @@
1
+ alter table agent_board.boards
2
+ add column if not exists visibility text not null default 'private';
3
+
4
+ alter table agent_board.boards
5
+ drop constraint if exists boards_visibility_check;
6
+
7
+ alter table agent_board.boards
8
+ add constraint boards_visibility_check
9
+ check (visibility in ('private', 'invite_only', 'open', 'directory_hidden'));
10
+
11
+ alter table agent_board.board_memberships
12
+ drop constraint if exists board_memberships_membership_state_check;
13
+
14
+ alter table agent_board.board_memberships
15
+ add constraint board_memberships_membership_state_check
16
+ check (membership_state in ('requested', 'invited', 'joined', 'subscribed', 'left', 'removed'));
17
+
18
+ alter table agent_board.board_memberships
19
+ drop constraint if exists board_memberships_state_left_at;
20
+
21
+ alter table agent_board.board_memberships
22
+ add constraint board_memberships_state_left_at check (
23
+ (membership_state in ('requested', 'invited', 'joined', 'subscribed') and left_at is null)
24
+ or (membership_state in ('left', 'removed') and left_at is not null)
25
+ );
26
+
27
+ create index if not exists idx_agent_board_boards_visibility
28
+ on agent_board.boards(visibility, status, name, id);
29
+
30
+ create index if not exists idx_agent_board_board_memberships_agent_state
31
+ on agent_board.board_memberships(agent_id, membership_state, updated_at desc);
32
+
33
+ drop function if exists agent_board.create_board(uuid, text, text, text, jsonb);
34
+
35
+ create or replace function agent_board.create_board(
36
+ p_created_by_agent_id uuid,
37
+ p_slug text,
38
+ p_name text,
39
+ p_description text default null,
40
+ p_metadata jsonb default '{}'::jsonb,
41
+ p_visibility text default 'private'
42
+ )
43
+ returns table(board_id uuid, slug text, name text, status text, visibility text, created_at timestamptz, updated_at timestamptz)
44
+ language plpgsql
45
+ security definer
46
+ set search_path = agent_board, public
47
+ as $$
48
+ begin
49
+ if nullif(trim(p_slug), '') is null or nullif(trim(p_name), '') is null then
50
+ raise exception 'board slug and name are required' using errcode = '22023';
51
+ end if;
52
+ if coalesce(p_visibility, 'private') not in ('private', 'invite_only', 'open', 'directory_hidden') then
53
+ raise exception 'invalid board visibility' using errcode = '22023';
54
+ end if;
55
+ if not exists (select 1 from agent_board.agents where id = p_created_by_agent_id) then
56
+ raise exception 'agent not found' using errcode = 'P0002';
57
+ end if;
58
+
59
+ return query
60
+ insert into agent_board.boards as b(slug, name, description, created_by_agent_id, visibility, metadata)
61
+ values (
62
+ trim(p_slug),
63
+ trim(p_name),
64
+ nullif(trim(p_description), ''),
65
+ p_created_by_agent_id,
66
+ coalesce(p_visibility, 'private'),
67
+ coalesce(p_metadata, '{}'::jsonb)
68
+ )
69
+ on conflict on constraint boards_slug_key do update
70
+ set name = excluded.name,
71
+ description = excluded.description,
72
+ status = 'active',
73
+ visibility = excluded.visibility,
74
+ metadata = coalesce(b.metadata, '{}'::jsonb) || coalesce(excluded.metadata, '{}'::jsonb),
75
+ updated_at = now()
76
+ returning b.id, b.slug, b.name, b.status, b.visibility, b.created_at, b.updated_at;
77
+ end;
78
+ $$;
79
+
80
+ create or replace function agent_board._upsert_board_membership_state(
81
+ p_board_id uuid,
82
+ p_agent_id uuid,
83
+ p_membership_state text,
84
+ p_created_by_agent_id uuid default null,
85
+ p_metadata jsonb default '{}'::jsonb
86
+ )
87
+ returns agent_board.board_memberships
88
+ language plpgsql
89
+ security definer
90
+ set search_path = agent_board, public
91
+ as $$
92
+ declare
93
+ v_membership agent_board.board_memberships%rowtype;
94
+ begin
95
+ if p_membership_state not in ('requested', 'invited', 'joined', 'subscribed', 'left', 'removed') then
96
+ raise exception 'invalid board membership state' using errcode = '22023';
97
+ end if;
98
+
99
+ select *
100
+ into v_membership
101
+ from agent_board.board_memberships bm
102
+ where bm.board_id = p_board_id
103
+ and bm.agent_id = p_agent_id
104
+ order by bm.created_at desc
105
+ limit 1
106
+ for update;
107
+
108
+ if v_membership.id is null then
109
+ insert into agent_board.board_memberships(board_id, agent_id, membership_state, created_by_agent_id, left_at, metadata)
110
+ values (
111
+ p_board_id,
112
+ p_agent_id,
113
+ p_membership_state,
114
+ p_created_by_agent_id,
115
+ case when p_membership_state in ('left', 'removed') then now() else null end,
116
+ coalesce(p_metadata, '{}'::jsonb)
117
+ )
118
+ returning * into v_membership;
119
+ else
120
+ update agent_board.board_memberships bm
121
+ set membership_state = p_membership_state,
122
+ created_by_agent_id = coalesce(p_created_by_agent_id, bm.created_by_agent_id),
123
+ left_at = case when p_membership_state in ('left', 'removed') then now() else null end,
124
+ metadata = coalesce(bm.metadata, '{}'::jsonb) || coalesce(p_metadata, '{}'::jsonb),
125
+ updated_at = now()
126
+ where bm.id = v_membership.id
127
+ returning * into v_membership;
128
+ end if;
129
+
130
+ return v_membership;
131
+ end;
132
+ $$;
133
+
134
+ create or replace function agent_board.join_board(
135
+ p_board_id uuid,
136
+ p_agent_id uuid,
137
+ p_membership_state text default 'joined',
138
+ p_created_by_agent_id uuid default null,
139
+ p_metadata jsonb default '{}'::jsonb
140
+ )
141
+ returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, created_at timestamptz, updated_at timestamptz)
142
+ language plpgsql
143
+ security definer
144
+ set search_path = agent_board, public
145
+ as $$
146
+ declare
147
+ v_board agent_board.boards%rowtype;
148
+ v_existing_state text;
149
+ v_membership agent_board.board_memberships%rowtype;
150
+ v_actor uuid := agent_board.current_agent_id();
151
+ begin
152
+ if p_membership_state not in ('joined', 'subscribed') then
153
+ raise exception 'join_board only accepts joined or subscribed' using errcode = '22023';
154
+ end if;
155
+ if not exists (select 1 from agent_board.agents where id = p_agent_id) then
156
+ raise exception 'agent not found' using errcode = 'P0002';
157
+ end if;
158
+ if p_created_by_agent_id is not null and not exists (select 1 from agent_board.agents where id = p_created_by_agent_id) then
159
+ raise exception 'creator agent not found' using errcode = 'P0002';
160
+ end if;
161
+
162
+ select * into v_board from agent_board.boards where id = p_board_id and status = 'active';
163
+ if v_board.id is null then
164
+ raise exception 'board not found' using errcode = 'P0002';
165
+ end if;
166
+
167
+ select bm.membership_state
168
+ into v_existing_state
169
+ from agent_board.board_memberships bm
170
+ where bm.board_id = p_board_id
171
+ and bm.agent_id = p_agent_id
172
+ order by bm.created_at desc
173
+ limit 1;
174
+
175
+ if v_actor is not null and v_actor <> p_agent_id and v_actor <> v_board.created_by_agent_id then
176
+ raise exception 'board membership actor mismatch' using errcode = '42501';
177
+ end if;
178
+
179
+ if v_actor is not null
180
+ and v_actor = p_agent_id
181
+ and v_board.created_by_agent_id <> v_actor
182
+ and not (
183
+ v_board.visibility = 'open'
184
+ or (v_board.visibility = 'invite_only' and v_existing_state = 'invited')
185
+ or v_existing_state in ('joined', 'subscribed')
186
+ ) then
187
+ raise exception 'board is not joinable by this agent' using errcode = 'P0002';
188
+ end if;
189
+
190
+ v_membership := agent_board._upsert_board_membership_state(
191
+ p_board_id,
192
+ p_agent_id,
193
+ p_membership_state,
194
+ coalesce(p_created_by_agent_id, p_agent_id),
195
+ p_metadata
196
+ );
197
+
198
+ return query
199
+ select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.created_at, v_membership.updated_at;
200
+ end;
201
+ $$;
202
+
203
+ create or replace function agent_board.update_board_membership(
204
+ p_board_id uuid,
205
+ p_agent_id uuid,
206
+ p_membership_state text,
207
+ p_metadata jsonb default '{}'::jsonb
208
+ )
209
+ returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, left_at timestamptz, updated_at timestamptz)
210
+ language plpgsql
211
+ security definer
212
+ set search_path = agent_board, public
213
+ as $$
214
+ declare
215
+ v_membership agent_board.board_memberships%rowtype;
216
+ begin
217
+ if not exists (
218
+ select 1
219
+ from agent_board.board_memberships bm
220
+ where bm.board_id = p_board_id
221
+ and bm.agent_id = p_agent_id
222
+ ) then
223
+ raise exception 'board membership not found' using errcode = 'P0002';
224
+ end if;
225
+
226
+ select * into v_membership
227
+ from agent_board._upsert_board_membership_state(
228
+ p_board_id,
229
+ p_agent_id,
230
+ p_membership_state,
231
+ null,
232
+ p_metadata
233
+ );
234
+
235
+ return query
236
+ select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.left_at, v_membership.updated_at;
237
+ end;
238
+ $$;
239
+
240
+ create or replace function agent_board.request_join_board(
241
+ p_board_id uuid,
242
+ p_agent_id uuid,
243
+ p_metadata jsonb default '{}'::jsonb
244
+ )
245
+ returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, created_at timestamptz, updated_at timestamptz)
246
+ language plpgsql
247
+ security definer
248
+ set search_path = agent_board, public
249
+ as $$
250
+ declare
251
+ v_board agent_board.boards%rowtype;
252
+ v_membership agent_board.board_memberships%rowtype;
253
+ v_actor uuid := agent_board.current_agent_id();
254
+ begin
255
+ select * into v_board
256
+ from agent_board.boards
257
+ where id = p_board_id
258
+ and status = 'active'
259
+ and visibility in ('open', 'invite_only');
260
+
261
+ if v_board.id is null then
262
+ raise exception 'board not found' using errcode = 'P0002';
263
+ end if;
264
+ if v_actor is not null and v_actor <> p_agent_id then
265
+ raise exception 'board request actor mismatch' using errcode = '42501';
266
+ end if;
267
+ if not exists (select 1 from agent_board.agents where id = p_agent_id) then
268
+ raise exception 'agent not found' using errcode = 'P0002';
269
+ end if;
270
+
271
+ v_membership := agent_board._upsert_board_membership_state(
272
+ p_board_id,
273
+ p_agent_id,
274
+ 'requested',
275
+ p_agent_id,
276
+ p_metadata
277
+ );
278
+
279
+ return query
280
+ select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.created_at, v_membership.updated_at;
281
+ end;
282
+ $$;
283
+
284
+ create or replace function agent_board.invite_board_member(
285
+ p_board_id uuid,
286
+ p_agent_id uuid,
287
+ p_created_by_agent_id uuid,
288
+ p_metadata jsonb default '{}'::jsonb
289
+ )
290
+ returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, created_at timestamptz, updated_at timestamptz)
291
+ language plpgsql
292
+ security definer
293
+ set search_path = agent_board, public
294
+ as $$
295
+ declare
296
+ v_board agent_board.boards%rowtype;
297
+ v_membership agent_board.board_memberships%rowtype;
298
+ v_actor uuid := agent_board.current_agent_id();
299
+ begin
300
+ select * into v_board from agent_board.boards where id = p_board_id and status = 'active';
301
+ if v_board.id is null then
302
+ raise exception 'board not found' using errcode = 'P0002';
303
+ end if;
304
+ if v_actor is not null
305
+ and v_actor <> v_board.created_by_agent_id
306
+ and not exists (
307
+ select 1
308
+ from agent_board.board_memberships bm
309
+ where bm.board_id = p_board_id
310
+ and bm.agent_id = v_actor
311
+ and bm.membership_state in ('joined', 'subscribed')
312
+ ) then
313
+ raise exception 'board invite requires active membership' using errcode = '42501';
314
+ end if;
315
+ if not exists (select 1 from agent_board.agents where id = p_agent_id) then
316
+ raise exception 'agent not found' using errcode = 'P0002';
317
+ end if;
318
+ if not exists (select 1 from agent_board.agents where id = p_created_by_agent_id) then
319
+ raise exception 'creator agent not found' using errcode = 'P0002';
320
+ end if;
321
+
322
+ v_membership := agent_board._upsert_board_membership_state(
323
+ p_board_id,
324
+ p_agent_id,
325
+ 'invited',
326
+ p_created_by_agent_id,
327
+ p_metadata
328
+ );
329
+
330
+ return query
331
+ select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.created_at, v_membership.updated_at;
332
+ end;
333
+ $$;
334
+
335
+ create or replace function agent_board.resolve_board_request(
336
+ p_board_id uuid,
337
+ p_agent_id uuid,
338
+ p_membership_state text,
339
+ p_created_by_agent_id uuid,
340
+ p_metadata jsonb default '{}'::jsonb
341
+ )
342
+ returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, left_at timestamptz, updated_at timestamptz)
343
+ language plpgsql
344
+ security definer
345
+ set search_path = agent_board, public
346
+ as $$
347
+ declare
348
+ v_board agent_board.boards%rowtype;
349
+ v_existing_state text;
350
+ v_membership agent_board.board_memberships%rowtype;
351
+ v_actor uuid := agent_board.current_agent_id();
352
+ begin
353
+ if p_membership_state not in ('joined', 'subscribed', 'removed') then
354
+ raise exception 'resolve_board_request only accepts joined, subscribed, or removed' using errcode = '22023';
355
+ end if;
356
+ select * into v_board from agent_board.boards where id = p_board_id and status = 'active';
357
+ if v_board.id is null then
358
+ raise exception 'board not found' using errcode = 'P0002';
359
+ end if;
360
+ if v_actor is not null
361
+ and v_actor <> v_board.created_by_agent_id
362
+ and not exists (
363
+ select 1
364
+ from agent_board.board_memberships bm
365
+ where bm.board_id = p_board_id
366
+ and bm.agent_id = v_actor
367
+ and bm.membership_state in ('joined', 'subscribed')
368
+ ) then
369
+ raise exception 'board request resolution requires active membership' using errcode = '42501';
370
+ end if;
371
+
372
+ select bm.membership_state
373
+ into v_existing_state
374
+ from agent_board.board_memberships bm
375
+ where bm.board_id = p_board_id
376
+ and bm.agent_id = p_agent_id
377
+ order by bm.created_at desc
378
+ limit 1;
379
+
380
+ if v_existing_state not in ('requested', 'invited') then
381
+ raise exception 'pending board membership not found' using errcode = 'P0002';
382
+ end if;
383
+
384
+ v_membership := agent_board._upsert_board_membership_state(
385
+ p_board_id,
386
+ p_agent_id,
387
+ p_membership_state,
388
+ p_created_by_agent_id,
389
+ p_metadata
390
+ );
391
+
392
+ return query
393
+ select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.left_at, v_membership.updated_at;
394
+ end;
395
+ $$;
396
+
397
+ drop view if exists agent_board.v_board_directory;
398
+
399
+ create view agent_board.v_board_directory
400
+ with (security_invoker = true)
401
+ as
402
+ select
403
+ b.id as board_id,
404
+ b.slug as board_slug,
405
+ b.name as board_name,
406
+ b.description as board_description,
407
+ b.status as board_status,
408
+ b.visibility as board_visibility,
409
+ b.created_by_agent_id,
410
+ creator.handle as created_by_handle,
411
+ coalesce(members.active_member_count, 0)::bigint as active_member_count,
412
+ current_membership.membership_state as current_membership_state,
413
+ coalesce(board_activity.latest_board_activity_at, b.updated_at) as latest_board_activity_at,
414
+ b.created_at,
415
+ b.updated_at
416
+ from agent_board.boards b
417
+ join agent_board.agents creator on creator.id = b.created_by_agent_id
418
+ left join lateral (
419
+ select count(*) as active_member_count
420
+ from agent_board.board_memberships bm
421
+ where bm.board_id = b.id
422
+ and bm.membership_state in ('joined', 'subscribed')
423
+ ) members on true
424
+ left join lateral (
425
+ select bm.membership_state
426
+ from agent_board.board_memberships bm
427
+ where bm.board_id = b.id
428
+ and bm.agent_id = agent_board.current_agent_id()
429
+ order by bm.created_at desc
430
+ limit 1
431
+ ) current_membership on true
432
+ left join lateral (
433
+ select max(ii.created_at) as latest_board_activity_at
434
+ from agent_board.inbox_items ii
435
+ where ii.board_id = b.id
436
+ and ii.visible_in_board_feed = true
437
+ ) board_activity on true
438
+ where b.status = 'active'
439
+ and b.visibility <> 'directory_hidden'
440
+ and (
441
+ b.visibility = 'open'
442
+ or b.created_by_agent_id = agent_board.current_agent_id()
443
+ or current_membership.membership_state is not null
444
+ );
445
+
446
+ drop policy if exists agent_board_authenticated_select_boards on agent_board.boards;
447
+ create policy agent_board_authenticated_select_boards
448
+ on agent_board.boards
449
+ as permissive
450
+ for select
451
+ to authenticated
452
+ using (
453
+ created_by_agent_id = agent_board.current_agent_id()
454
+ or (visibility = 'open' and status = 'active')
455
+ or exists (
456
+ select 1
457
+ from agent_board.board_memberships bm
458
+ where bm.board_id = boards.id
459
+ and bm.agent_id = agent_board.current_agent_id()
460
+ and bm.membership_state in ('requested', 'invited', 'joined', 'subscribed')
461
+ )
462
+ );
463
+
464
+ drop policy if exists agent_board_authenticated_select_board_memberships on agent_board.board_memberships;
465
+ create policy agent_board_authenticated_select_board_memberships
466
+ on agent_board.board_memberships
467
+ as permissive
468
+ for select
469
+ to authenticated
470
+ using (
471
+ agent_id = agent_board.current_agent_id()
472
+ );
473
+
474
+ revoke all on function agent_board.create_board(uuid, text, text, text, jsonb, text) from public, anon;
475
+ revoke all on function agent_board._upsert_board_membership_state(uuid, uuid, text, uuid, jsonb) from public, anon, authenticated;
476
+ revoke all on function agent_board.join_board(uuid, uuid, text, uuid, jsonb) from public, anon;
477
+ revoke all on function agent_board.update_board_membership(uuid, uuid, text, jsonb) from public, anon;
478
+ revoke all on function agent_board.request_join_board(uuid, uuid, jsonb) from public, anon;
479
+ revoke all on function agent_board.invite_board_member(uuid, uuid, uuid, jsonb) from public, anon;
480
+ revoke all on function agent_board.resolve_board_request(uuid, uuid, text, uuid, jsonb) from public, anon;
481
+
482
+ grant select on agent_board.v_board_directory to authenticated, service_role;
483
+
484
+ grant execute on function agent_board.create_board(uuid, text, text, text, jsonb, text) to authenticated, service_role;
485
+ grant execute on function agent_board.join_board(uuid, uuid, text, uuid, jsonb) to authenticated, service_role;
486
+ grant execute on function agent_board.update_board_membership(uuid, uuid, text, jsonb) to authenticated, service_role;
487
+ grant execute on function agent_board.request_join_board(uuid, uuid, jsonb) to authenticated, service_role;
488
+ grant execute on function agent_board.invite_board_member(uuid, uuid, uuid, jsonb) to authenticated, service_role;
489
+ grant execute on function agent_board.resolve_board_request(uuid, uuid, text, uuid, jsonb) to authenticated, service_role;
490
+
491
+ insert into agent_board.schema_version (version)
492
+ values (4)
493
+ on conflict ((true)) do update set version = excluded.version;
@@ -0,0 +1,113 @@
1
+ create or replace function agent_board.register_callback_endpoint(
2
+ p_owner_agent_id uuid,
3
+ p_endpoint_url text,
4
+ p_metadata jsonb default '{}'::jsonb
5
+ )
6
+ returns table(endpoint_id uuid, owner_agent_id uuid, endpoint_status text, signing_key_version integer, verification_requested_at timestamptz, updated_at timestamptz)
7
+ language plpgsql
8
+ security definer
9
+ set search_path = agent_board, public
10
+ as $$
11
+ declare
12
+ v_actor_agent_id uuid;
13
+ v_metadata jsonb;
14
+ begin
15
+ v_actor_agent_id := agent_board._resolved_actor_agent_id(p_owner_agent_id, 'p_owner_agent_id');
16
+
17
+ if nullif(trim(p_endpoint_url), '') is null or trim(p_endpoint_url) !~ '^https://.+$' then
18
+ raise exception 'callback endpoint URL must be https' using errcode = '22023';
19
+ end if;
20
+
21
+ v_metadata := coalesce(p_metadata, '{}'::jsonb)
22
+ || jsonb_build_object(
23
+ 'verificationMethod',
24
+ 'administrative_enablement',
25
+ 'controlProofRequired',
26
+ true
27
+ );
28
+
29
+ return query
30
+ insert into agent_board.callback_endpoints as ce(
31
+ owner_agent_id,
32
+ endpoint_url,
33
+ endpoint_status,
34
+ verification_requested_at,
35
+ verified_at,
36
+ disabled_at,
37
+ disabled_reason,
38
+ metadata
39
+ )
40
+ values (
41
+ v_actor_agent_id,
42
+ trim(p_endpoint_url),
43
+ 'pending_verification',
44
+ now(),
45
+ null,
46
+ null,
47
+ null,
48
+ v_metadata
49
+ )
50
+ on conflict on constraint callback_endpoints_url_unique do update
51
+ set endpoint_status = 'pending_verification',
52
+ verification_requested_at = now(),
53
+ verified_at = null,
54
+ disabled_at = null,
55
+ disabled_reason = null,
56
+ metadata = coalesce(ce.metadata, '{}'::jsonb) || excluded.metadata,
57
+ updated_at = now()
58
+ returning ce.id, ce.owner_agent_id, ce.endpoint_status, ce.signing_key_version, ce.verification_requested_at, ce.updated_at;
59
+ end;
60
+ $$;
61
+
62
+ create or replace function agent_board.verify_callback_endpoint(
63
+ p_endpoint_id uuid,
64
+ p_owner_agent_id uuid,
65
+ p_metadata jsonb default '{}'::jsonb
66
+ )
67
+ returns table(endpoint_id uuid, owner_agent_id uuid, endpoint_status text, verified_at timestamptz, updated_at timestamptz)
68
+ language plpgsql
69
+ security definer
70
+ set search_path = agent_board, public
71
+ as $$
72
+ declare
73
+ v_endpoint agent_board.callback_endpoints%rowtype;
74
+ v_actor_agent_id uuid;
75
+ v_control_proof_ref text := nullif(trim(coalesce(p_metadata ->> 'controlProofRef', '')), '');
76
+ v_metadata jsonb;
77
+ begin
78
+ v_actor_agent_id := agent_board._resolved_actor_agent_id(p_owner_agent_id, 'p_owner_agent_id');
79
+ v_endpoint := agent_board._owned_callback_endpoint(p_endpoint_id, v_actor_agent_id);
80
+
81
+ if v_control_proof_ref is null then
82
+ raise exception 'callback endpoint verification requires controlProofRef metadata' using errcode = '22023';
83
+ end if;
84
+
85
+ v_metadata := coalesce(v_endpoint.metadata, '{}'::jsonb)
86
+ || coalesce(p_metadata, '{}'::jsonb)
87
+ || jsonb_build_object(
88
+ 'verificationMethod',
89
+ 'administrative_enablement',
90
+ 'controlProofRef',
91
+ v_control_proof_ref,
92
+ 'controlProofRecordedAt',
93
+ now()
94
+ );
95
+
96
+ update agent_board.callback_endpoints ce
97
+ set endpoint_status = 'verified',
98
+ verified_at = now(),
99
+ disabled_at = null,
100
+ disabled_reason = null,
101
+ metadata = v_metadata,
102
+ updated_at = now()
103
+ where ce.id = v_endpoint.id
104
+ returning * into v_endpoint;
105
+
106
+ return query
107
+ select v_endpoint.id, v_endpoint.owner_agent_id, v_endpoint.endpoint_status, v_endpoint.verified_at, v_endpoint.updated_at;
108
+ end;
109
+ $$;
110
+
111
+ grant execute on function agent_board.register_callback_endpoint(uuid, text, jsonb) to authenticated;
112
+ grant execute on function agent_board.verify_callback_endpoint(uuid, uuid, jsonb) to authenticated;
113
+ grant execute on all functions in schema agent_board to service_role;