@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,422 @@
1
+ create or replace function agent_board._next_thread_message_sequence(p_thread_id uuid)
2
+ returns bigint
3
+ language plpgsql
4
+ security definer
5
+ set search_path = agent_board, public
6
+ as $$
7
+ declare
8
+ v_sequence bigint;
9
+ begin
10
+ update agent_board.threads
11
+ set last_message_sequence = last_message_sequence + 1,
12
+ updated_at = now()
13
+ where id = p_thread_id
14
+ returning last_message_sequence into v_sequence;
15
+
16
+ if v_sequence is null then
17
+ raise exception 'thread not found' using errcode = 'P0002';
18
+ end if;
19
+
20
+ return v_sequence;
21
+ end;
22
+ $$;
23
+
24
+ create or replace function agent_board._next_task_event_sequence(p_task_id uuid)
25
+ returns bigint
26
+ language plpgsql
27
+ security definer
28
+ set search_path = agent_board, public
29
+ as $$
30
+ declare
31
+ v_sequence bigint;
32
+ begin
33
+ select coalesce(max(event_sequence), 0) + 1
34
+ into v_sequence
35
+ from agent_board.task_events
36
+ where task_id = p_task_id
37
+ for update;
38
+
39
+ return v_sequence;
40
+ end;
41
+ $$;
42
+
43
+ create or replace function agent_board.register_agent(
44
+ p_agent_type text,
45
+ p_handle text,
46
+ p_display_name text,
47
+ p_status text default 'active',
48
+ p_metadata jsonb default '{}'::jsonb
49
+ )
50
+ returns table(agent_id uuid, handle text, status text, created_at timestamptz, updated_at timestamptz)
51
+ language plpgsql
52
+ security definer
53
+ set search_path = agent_board, public
54
+ as $$
55
+ begin
56
+ if nullif(trim(p_handle), '') is null or nullif(trim(p_display_name), '') is null then
57
+ raise exception 'handle and display name are required' using errcode = '22023';
58
+ end if;
59
+
60
+ return query
61
+ insert into agent_board.agents as a(agent_type, handle, display_name, status, metadata)
62
+ values (p_agent_type, p_handle, p_display_name, coalesce(p_status, 'active'), coalesce(p_metadata, '{}'::jsonb))
63
+ on conflict on constraint agents_handle_key do update
64
+ set agent_type = excluded.agent_type,
65
+ display_name = excluded.display_name,
66
+ status = excluded.status,
67
+ metadata = excluded.metadata,
68
+ updated_at = now()
69
+ returning a.id, a.handle, a.status, a.created_at, a.updated_at;
70
+ end;
71
+ $$;
72
+
73
+ create or replace function agent_board.heartbeat(
74
+ p_agent_id uuid,
75
+ p_thread_id uuid default null,
76
+ p_availability text default 'ready',
77
+ p_activity_summary text default null,
78
+ p_expires_in interval default interval '5 minutes',
79
+ p_metadata jsonb default '{}'::jsonb
80
+ )
81
+ returns table(agent_id uuid, availability text, thread_id uuid, last_heartbeat_at timestamptz, expires_at timestamptz, updated_at timestamptz)
82
+ language plpgsql
83
+ security definer
84
+ set search_path = agent_board, public
85
+ as $$
86
+ begin
87
+ if not exists (select 1 from agent_board.agents where id = p_agent_id) then
88
+ raise exception 'agent not found' using errcode = 'P0002';
89
+ end if;
90
+ if p_thread_id is not null and not exists (select 1 from agent_board.threads where id = p_thread_id) then
91
+ raise exception 'thread not found' using errcode = 'P0002';
92
+ end if;
93
+
94
+ return query
95
+ insert into agent_board.presence(agent_id, thread_id, availability, activity_summary, last_heartbeat_at, expires_at, metadata, updated_at)
96
+ values (p_agent_id, p_thread_id, coalesce(p_availability, 'ready'), p_activity_summary, now(), now() + coalesce(p_expires_in, interval '5 minutes'), coalesce(p_metadata, '{}'::jsonb), now())
97
+ on conflict (agent_id) do update
98
+ set thread_id = excluded.thread_id,
99
+ availability = excluded.availability,
100
+ activity_summary = excluded.activity_summary,
101
+ last_heartbeat_at = excluded.last_heartbeat_at,
102
+ expires_at = excluded.expires_at,
103
+ metadata = excluded.metadata,
104
+ updated_at = excluded.updated_at
105
+ returning presence.agent_id, presence.availability, presence.thread_id, presence.last_heartbeat_at, presence.expires_at, presence.updated_at;
106
+ end;
107
+ $$;
108
+
109
+ create or replace function agent_board.create_thread(
110
+ p_created_by_agent_id uuid,
111
+ p_subject text,
112
+ p_thread_type text default 'coordination',
113
+ p_metadata jsonb default '{}'::jsonb
114
+ )
115
+ returns table(thread_id uuid, thread_type text, subject text, status text, last_message_sequence bigint, created_at timestamptz, updated_at timestamptz)
116
+ language plpgsql
117
+ security definer
118
+ set search_path = agent_board, public
119
+ as $$
120
+ begin
121
+ if nullif(trim(p_subject), '') is null then
122
+ raise exception 'subject is required' using errcode = '22023';
123
+ end if;
124
+ if not exists (select 1 from agent_board.agents where id = p_created_by_agent_id) then
125
+ raise exception 'agent not found' using errcode = 'P0002';
126
+ end if;
127
+
128
+ return query
129
+ insert into agent_board.threads(created_by_agent_id, subject, thread_type, metadata)
130
+ values (p_created_by_agent_id, p_subject, coalesce(p_thread_type, 'coordination'), coalesce(p_metadata, '{}'::jsonb))
131
+ returning id, threads.thread_type, threads.subject, threads.status, threads.last_message_sequence, threads.created_at, threads.updated_at;
132
+ end;
133
+ $$;
134
+
135
+ create or replace function agent_board.post_message(
136
+ p_thread_id uuid,
137
+ p_author_agent_id uuid,
138
+ p_body text,
139
+ p_message_type text default 'comment',
140
+ p_task_id uuid default null,
141
+ p_metadata jsonb default '{}'::jsonb
142
+ )
143
+ returns table(message_id uuid, thread_id uuid, task_id uuid, message_sequence bigint, created_at timestamptz)
144
+ language plpgsql
145
+ security definer
146
+ set search_path = agent_board, public
147
+ as $$
148
+ declare
149
+ v_sequence bigint;
150
+ begin
151
+ if nullif(trim(p_body), '') is null then
152
+ raise exception 'message body is required' using errcode = '22023';
153
+ end if;
154
+ if not exists (select 1 from agent_board.agents where id = p_author_agent_id) then
155
+ raise exception 'agent not found' using errcode = 'P0002';
156
+ end if;
157
+ if p_task_id is not null and not exists (select 1 from agent_board.tasks where id = p_task_id and tasks.thread_id = p_thread_id) then
158
+ raise exception 'task not found in thread' using errcode = '22023';
159
+ end if;
160
+
161
+ v_sequence := agent_board._next_thread_message_sequence(p_thread_id);
162
+
163
+ return query
164
+ insert into agent_board.messages(thread_id, author_agent_id, task_id, message_sequence, message_type, body, metadata)
165
+ values (p_thread_id, p_author_agent_id, p_task_id, v_sequence, coalesce(p_message_type, 'comment'), p_body, coalesce(p_metadata, '{}'::jsonb))
166
+ returning id, messages.thread_id, messages.task_id, messages.message_sequence, messages.created_at;
167
+ end;
168
+ $$;
169
+
170
+ create or replace function agent_board.create_task(
171
+ p_thread_id uuid,
172
+ p_created_by_agent_id uuid,
173
+ p_title text,
174
+ p_details text default null,
175
+ p_assigned_agent_id uuid default null,
176
+ p_parent_task_id uuid default null,
177
+ p_priority text default 'medium',
178
+ p_orchestration_mode text default 'distributed',
179
+ p_metadata jsonb default '{}'::jsonb
180
+ )
181
+ returns table(task_id uuid, parent_task_id uuid, root_task_id uuid, created_event_id uuid, created_event_sequence bigint, status text, created_at timestamptz, updated_at timestamptz)
182
+ language plpgsql
183
+ security definer
184
+ set search_path = agent_board, public
185
+ as $$
186
+ declare
187
+ v_task_id uuid;
188
+ v_root_task_id uuid;
189
+ v_event_id uuid;
190
+ begin
191
+ if nullif(trim(p_title), '') is null then
192
+ raise exception 'task title is required' using errcode = '22023';
193
+ end if;
194
+ if not exists (select 1 from agent_board.threads where id = p_thread_id) then
195
+ raise exception 'thread not found' using errcode = 'P0002';
196
+ end if;
197
+ if not exists (select 1 from agent_board.agents where id = p_created_by_agent_id) then
198
+ raise exception 'agent not found' using errcode = 'P0002';
199
+ end if;
200
+ if p_assigned_agent_id is not null and not exists (select 1 from agent_board.agents where id = p_assigned_agent_id) then
201
+ raise exception 'assigned agent not found' using errcode = 'P0002';
202
+ end if;
203
+
204
+ if p_parent_task_id is not null then
205
+ select coalesce(tasks.root_task_id, tasks.id)
206
+ into v_root_task_id
207
+ from agent_board.tasks
208
+ where id = p_parent_task_id and tasks.thread_id = p_thread_id;
209
+ if v_root_task_id is null then
210
+ raise exception 'parent task not found in thread' using errcode = '22023';
211
+ end if;
212
+ end if;
213
+
214
+ v_task_id := gen_random_uuid();
215
+ if p_parent_task_id is null then
216
+ v_root_task_id := v_task_id;
217
+ end if;
218
+
219
+ insert into agent_board.tasks(id, thread_id, created_by_agent_id, assigned_agent_id, parent_task_id, root_task_id, title, details, priority, orchestration_mode, metadata)
220
+ values (v_task_id, p_thread_id, p_created_by_agent_id, p_assigned_agent_id, p_parent_task_id, v_root_task_id, p_title, p_details, coalesce(p_priority, 'medium'), coalesce(p_orchestration_mode, 'distributed'), coalesce(p_metadata, '{}'::jsonb));
221
+
222
+ insert into agent_board.task_events(task_id, thread_id, actor_agent_id, event_sequence, event_type, payload)
223
+ values (v_task_id, p_thread_id, p_created_by_agent_id, 1, 'created', jsonb_build_object('title', p_title))
224
+ returning id into v_event_id;
225
+
226
+ return query
227
+ select t.id, t.parent_task_id, t.root_task_id, v_event_id, 1::bigint, t.status, t.created_at, t.updated_at
228
+ from agent_board.tasks t
229
+ where t.id = v_task_id;
230
+ end;
231
+ $$;
232
+
233
+ create or replace function agent_board.append_task_event(
234
+ p_task_id uuid,
235
+ p_actor_agent_id uuid,
236
+ p_event_type text,
237
+ p_payload jsonb default '{}'::jsonb,
238
+ p_status text default null,
239
+ p_assigned_agent_id uuid default null,
240
+ p_completed_at timestamptz default null
241
+ )
242
+ returns table(task_id uuid, thread_id uuid, event_id uuid, event_sequence bigint, task_status text, assigned_agent_id uuid, updated_at timestamptz, completed_at timestamptz)
243
+ language plpgsql
244
+ security definer
245
+ set search_path = agent_board, public
246
+ as $$
247
+ declare
248
+ v_thread_id uuid;
249
+ v_sequence bigint;
250
+ v_event_id uuid;
251
+ begin
252
+ select tasks.thread_id into v_thread_id from agent_board.tasks where id = p_task_id for update;
253
+ if v_thread_id is null then
254
+ raise exception 'task not found' using errcode = 'P0002';
255
+ end if;
256
+ if not exists (select 1 from agent_board.agents where id = p_actor_agent_id) then
257
+ raise exception 'agent not found' using errcode = 'P0002';
258
+ end if;
259
+
260
+ select coalesce(max(te.event_sequence), 0) + 1 into v_sequence from agent_board.task_events te where te.task_id = p_task_id;
261
+ insert into agent_board.task_events(task_id, thread_id, actor_agent_id, event_sequence, event_type, payload)
262
+ values (p_task_id, v_thread_id, p_actor_agent_id, v_sequence, p_event_type, coalesce(p_payload, '{}'::jsonb))
263
+ returning id into v_event_id;
264
+
265
+ update agent_board.tasks
266
+ set status = coalesce(p_status, tasks.status),
267
+ assigned_agent_id = coalesce(p_assigned_agent_id, tasks.assigned_agent_id),
268
+ completed_at = coalesce(p_completed_at, tasks.completed_at),
269
+ updated_at = now()
270
+ where id = p_task_id;
271
+
272
+ return query
273
+ select t.id, t.thread_id, v_event_id, v_sequence, t.status, t.assigned_agent_id, t.updated_at, t.completed_at
274
+ from agent_board.tasks t
275
+ where t.id = p_task_id;
276
+ end;
277
+ $$;
278
+
279
+ create or replace function agent_board.acquire_claim(
280
+ p_agent_id uuid,
281
+ p_resource_kind text,
282
+ p_resource_key text,
283
+ p_lease_expires_at timestamptz,
284
+ p_task_id uuid default null,
285
+ p_thread_id uuid default null,
286
+ p_metadata jsonb default '{}'::jsonb
287
+ )
288
+ returns table(claim_id uuid, task_id uuid, thread_id uuid, agent_id uuid, resource_kind text, resource_key text, status text, lease_token uuid, lease_expires_at timestamptz, released_at timestamptz, created_at timestamptz, updated_at timestamptz)
289
+ language plpgsql
290
+ security definer
291
+ set search_path = agent_board, public
292
+ as $$
293
+ declare
294
+ v_thread_id uuid;
295
+ v_claim_id uuid;
296
+ begin
297
+ if not exists (select 1 from agent_board.agents where id = p_agent_id) then
298
+ raise exception 'agent not found' using errcode = 'P0002';
299
+ end if;
300
+ if nullif(trim(p_resource_key), '') is null then
301
+ raise exception 'resource key is required' using errcode = '22023';
302
+ end if;
303
+
304
+ if p_task_id is not null then
305
+ select tasks.thread_id into v_thread_id from agent_board.tasks where id = p_task_id;
306
+ if v_thread_id is null then
307
+ raise exception 'task not found' using errcode = 'P0002';
308
+ end if;
309
+ else
310
+ v_thread_id := p_thread_id;
311
+ end if;
312
+
313
+ update agent_board.claims c
314
+ set status = 'expired',
315
+ updated_at = now()
316
+ where c.resource_kind = p_resource_kind
317
+ and c.resource_key = p_resource_key
318
+ and c.status = 'active'
319
+ and c.released_at is null
320
+ and c.lease_expires_at <= now();
321
+
322
+ if exists (
323
+ select 1 from agent_board.claims
324
+ where claims.resource_kind = p_resource_kind
325
+ and claims.resource_key = p_resource_key
326
+ and claims.status = 'active'
327
+ and claims.released_at is null
328
+ and claims.lease_expires_at > now()
329
+ ) then
330
+ raise exception 'conflicting active claim' using errcode = '23505';
331
+ end if;
332
+
333
+ insert into agent_board.claims(task_id, thread_id, agent_id, resource_kind, resource_key, lease_expires_at, metadata)
334
+ values (p_task_id, v_thread_id, p_agent_id, p_resource_kind, p_resource_key, p_lease_expires_at, coalesce(p_metadata, '{}'::jsonb))
335
+ returning id into v_claim_id;
336
+
337
+ if p_task_id is not null then
338
+ perform agent_board.append_task_event(p_task_id, p_agent_id, 'claimed', jsonb_build_object('claim_id', v_claim_id, 'resource_kind', p_resource_kind, 'resource_key', p_resource_key));
339
+ end if;
340
+
341
+ return query
342
+ select c.id, c.task_id, c.thread_id, c.agent_id, c.resource_kind, c.resource_key, c.status, c.lease_token, c.lease_expires_at, c.released_at, c.created_at, c.updated_at
343
+ from agent_board.claims c
344
+ where c.id = v_claim_id;
345
+ end;
346
+ $$;
347
+
348
+ create or replace function agent_board.renew_claim(p_lease_token uuid, p_lease_expires_at timestamptz)
349
+ returns table(claim_id uuid, task_id uuid, thread_id uuid, agent_id uuid, resource_kind text, resource_key text, status text, lease_token uuid, lease_expires_at timestamptz, released_at timestamptz, created_at timestamptz, updated_at timestamptz)
350
+ language plpgsql
351
+ security definer
352
+ set search_path = agent_board, public
353
+ as $$
354
+ begin
355
+ return query
356
+ update agent_board.claims c
357
+ set lease_expires_at = p_lease_expires_at,
358
+ updated_at = now()
359
+ where c.lease_token = p_lease_token
360
+ and c.status = 'active'
361
+ and c.released_at is null
362
+ and c.lease_expires_at > now()
363
+ returning c.id, c.task_id, c.thread_id, c.agent_id, c.resource_kind, c.resource_key, c.status, c.lease_token, c.lease_expires_at, c.released_at, c.created_at, c.updated_at;
364
+ end;
365
+ $$;
366
+
367
+ create or replace function agent_board.release_claim(p_lease_token uuid)
368
+ returns table(claim_id uuid, task_id uuid, thread_id uuid, agent_id uuid, resource_kind text, resource_key text, status text, lease_token uuid, lease_expires_at timestamptz, released_at timestamptz, created_at timestamptz, updated_at timestamptz)
369
+ language plpgsql
370
+ security definer
371
+ set search_path = agent_board, public
372
+ as $$
373
+ begin
374
+ return query
375
+ update agent_board.claims c
376
+ set status = 'released',
377
+ released_at = now(),
378
+ updated_at = now()
379
+ where c.lease_token = p_lease_token
380
+ and c.status = 'active'
381
+ and c.released_at is null
382
+ returning c.id, c.task_id, c.thread_id, c.agent_id, c.resource_kind, c.resource_key, c.status, c.lease_token, c.lease_expires_at, c.released_at, c.created_at, c.updated_at;
383
+ end;
384
+ $$;
385
+
386
+ create or replace function agent_board.mark_read(p_thread_id uuid, p_agent_id uuid, p_last_read_message_sequence bigint)
387
+ returns table(thread_id uuid, agent_id uuid, last_read_message_sequence bigint, last_read_at timestamptz)
388
+ language plpgsql
389
+ security definer
390
+ set search_path = agent_board, public
391
+ as $$
392
+ declare
393
+ v_sequence bigint;
394
+ begin
395
+ if p_last_read_message_sequence < 0 then
396
+ raise exception 'negative read sequence' using errcode = '22023';
397
+ end if;
398
+
399
+ select least(p_last_read_message_sequence, t.last_message_sequence)
400
+ into v_sequence
401
+ from agent_board.threads t
402
+ where t.id = p_thread_id;
403
+
404
+ if v_sequence is null then
405
+ raise exception 'thread not found' using errcode = 'P0002';
406
+ end if;
407
+
408
+ return query
409
+ insert into agent_board.read_receipts(thread_id, agent_id, last_read_message_sequence, last_read_at)
410
+ values (p_thread_id, p_agent_id, v_sequence, now())
411
+ on conflict (thread_id, agent_id) do update
412
+ set last_read_message_sequence = greatest(read_receipts.last_read_message_sequence, excluded.last_read_message_sequence),
413
+ last_read_at = now()
414
+ returning read_receipts.thread_id, read_receipts.agent_id, read_receipts.last_read_message_sequence, read_receipts.last_read_at;
415
+ end;
416
+ $$;
417
+
418
+ revoke all on schema agent_board from public, anon, authenticated;
419
+ grant usage on schema agent_board to service_role;
420
+
421
+ revoke all on all functions in schema agent_board from public, anon, authenticated;
422
+ grant execute on all functions in schema agent_board to service_role;
@@ -0,0 +1,229 @@
1
+ create or replace function agent_board.delegate_task(
2
+ p_parent_task_id uuid,
3
+ p_actor_agent_id uuid,
4
+ p_title text,
5
+ p_details text default null,
6
+ p_delegated_to_agent_id uuid default null,
7
+ p_priority text default 'medium',
8
+ p_note text default null,
9
+ p_metadata jsonb default '{}'::jsonb
10
+ )
11
+ returns table(delegation_id uuid, child_task_id uuid, parent_task_id uuid, root_task_id uuid, delegation_status text, child_event_sequence bigint, parent_event_sequence bigint, created_at timestamptz)
12
+ language plpgsql
13
+ security definer
14
+ set search_path = agent_board, public
15
+ as $$
16
+ declare
17
+ v_parent agent_board.tasks%rowtype;
18
+ v_child record;
19
+ v_delegation_id uuid;
20
+ v_parent_event record;
21
+ begin
22
+ select * into v_parent from agent_board.tasks where id = p_parent_task_id;
23
+ if v_parent.id is null then
24
+ raise exception 'parent task not found' using errcode = 'P0002';
25
+ end if;
26
+
27
+ select * into v_child
28
+ from agent_board.create_task(v_parent.thread_id, p_actor_agent_id, p_title, p_details, p_delegated_to_agent_id, p_parent_task_id, p_priority, 'hierarchical', p_metadata);
29
+
30
+ insert into agent_board.delegations(parent_task_id, child_task_id, delegated_by_agent_id, delegated_to_agent_id, note, metadata)
31
+ values (p_parent_task_id, v_child.task_id, p_actor_agent_id, p_delegated_to_agent_id, p_note, coalesce(p_metadata, '{}'::jsonb))
32
+ returning id into v_delegation_id;
33
+
34
+ select * into v_parent_event
35
+ from agent_board.append_task_event(p_parent_task_id, p_actor_agent_id, 'delegated', jsonb_build_object('delegation_id', v_delegation_id, 'child_task_id', v_child.task_id));
36
+
37
+ return query
38
+ select v_delegation_id, v_child.task_id, p_parent_task_id, v_child.root_task_id, 'requested'::text, v_child.created_event_sequence, v_parent_event.event_sequence, now();
39
+ end;
40
+ $$;
41
+
42
+ create or replace function agent_board.add_artifact(
43
+ p_created_by_agent_id uuid,
44
+ p_uri text,
45
+ p_artifact_type text default 'url',
46
+ p_storage_kind text default 'external_url',
47
+ p_thread_id uuid default null,
48
+ p_task_id uuid default null,
49
+ p_message_id uuid default null,
50
+ p_task_event_id uuid default null,
51
+ p_metadata jsonb default '{}'::jsonb
52
+ )
53
+ returns table(artifact_id uuid, task_event_sequence bigint, created_at timestamptz)
54
+ language plpgsql
55
+ security definer
56
+ set search_path = agent_board, public
57
+ as $$
58
+ declare
59
+ v_artifact_id uuid;
60
+ v_event record;
61
+ begin
62
+ if not exists (select 1 from agent_board.agents where id = p_created_by_agent_id) then
63
+ raise exception 'agent not found' using errcode = 'P0002';
64
+ end if;
65
+ if p_thread_id is null and p_task_id is null and p_message_id is null and p_task_event_id is null then
66
+ raise exception 'artifact target is required' using errcode = '22023';
67
+ end if;
68
+
69
+ insert into agent_board.artifacts(thread_id, task_id, message_id, task_event_id, created_by_agent_id, artifact_type, storage_kind, uri, metadata)
70
+ values (p_thread_id, p_task_id, p_message_id, p_task_event_id, p_created_by_agent_id, coalesce(p_artifact_type, 'url'), coalesce(p_storage_kind, 'external_url'), p_uri, coalesce(p_metadata, '{}'::jsonb))
71
+ returning id into v_artifact_id;
72
+
73
+ if p_task_id is not null then
74
+ select * into v_event
75
+ from agent_board.append_task_event(p_task_id, p_created_by_agent_id, 'artifact_added', jsonb_build_object('artifact_id', v_artifact_id));
76
+ end if;
77
+
78
+ return query
79
+ select v_artifact_id, v_event.event_sequence, now();
80
+ end;
81
+ $$;
82
+
83
+ create or replace function agent_board.raise_conflict(
84
+ p_thread_id uuid,
85
+ p_raised_by_agent_id uuid,
86
+ p_summary text,
87
+ p_conflict_type text default 'other',
88
+ p_severity text default 'medium',
89
+ p_task_id uuid default null,
90
+ p_details jsonb default '{}'::jsonb
91
+ )
92
+ returns table(conflict_id uuid, status text, task_event_sequence bigint, created_at timestamptz, updated_at timestamptz)
93
+ language plpgsql
94
+ security definer
95
+ set search_path = agent_board, public
96
+ as $$
97
+ declare
98
+ v_conflict_id uuid;
99
+ v_event record;
100
+ begin
101
+ insert into agent_board.conflicts(thread_id, task_id, raised_by_agent_id, summary, conflict_type, severity, details)
102
+ values (p_thread_id, p_task_id, p_raised_by_agent_id, p_summary, coalesce(p_conflict_type, 'other'), coalesce(p_severity, 'medium'), coalesce(p_details, '{}'::jsonb))
103
+ returning id into v_conflict_id;
104
+
105
+ if p_task_id is not null then
106
+ select * into v_event
107
+ from agent_board.append_task_event(p_task_id, p_raised_by_agent_id, 'conflict_raised', jsonb_build_object('conflict_id', v_conflict_id));
108
+ end if;
109
+
110
+ return query
111
+ select c.id, c.status, v_event.event_sequence, c.created_at, c.updated_at
112
+ from agent_board.conflicts c
113
+ where c.id = v_conflict_id;
114
+ end;
115
+ $$;
116
+
117
+ create or replace function agent_board.resolve_conflict(
118
+ p_conflict_id uuid,
119
+ p_resolved_by_agent_id uuid,
120
+ p_details jsonb default '{}'::jsonb
121
+ )
122
+ returns table(conflict_id uuid, status text, resolved_at timestamptz, updated_at timestamptz, task_event_sequence bigint)
123
+ language plpgsql
124
+ security definer
125
+ set search_path = agent_board, public
126
+ as $$
127
+ declare
128
+ v_conflict agent_board.conflicts%rowtype;
129
+ v_event record;
130
+ begin
131
+ select * into v_conflict from agent_board.conflicts where id = p_conflict_id and status <> 'resolved';
132
+ if v_conflict.id is null then
133
+ raise exception 'conflict not found' using errcode = 'P0002';
134
+ end if;
135
+
136
+ update agent_board.conflicts
137
+ set status = 'resolved',
138
+ resolved_by_agent_id = p_resolved_by_agent_id,
139
+ resolved_at = now(),
140
+ details = details || coalesce(p_details, '{}'::jsonb),
141
+ updated_at = now()
142
+ where id = p_conflict_id
143
+ returning * into v_conflict;
144
+
145
+ if v_conflict.task_id is not null then
146
+ select * into v_event
147
+ from agent_board.append_task_event(v_conflict.task_id, p_resolved_by_agent_id, 'status_changed', jsonb_build_object('kind', 'conflict_resolved', 'conflict_id', p_conflict_id));
148
+ end if;
149
+
150
+ return query
151
+ select v_conflict.id, v_conflict.status, v_conflict.resolved_at, v_conflict.updated_at, v_event.event_sequence;
152
+ end;
153
+ $$;
154
+
155
+ create or replace function agent_board.raise_escalation(
156
+ p_thread_id uuid,
157
+ p_raised_by_agent_id uuid,
158
+ p_summary text,
159
+ p_escalation_type text default 'other',
160
+ p_severity text default 'medium',
161
+ p_task_id uuid default null,
162
+ p_assigned_agent_id uuid default null,
163
+ p_details jsonb default '{}'::jsonb
164
+ )
165
+ returns table(escalation_id uuid, status text, task_event_sequence bigint, created_at timestamptz, updated_at timestamptz)
166
+ language plpgsql
167
+ security definer
168
+ set search_path = agent_board, public
169
+ as $$
170
+ declare
171
+ v_escalation_id uuid;
172
+ v_event record;
173
+ begin
174
+ insert into agent_board.escalations(thread_id, task_id, raised_by_agent_id, assigned_agent_id, summary, escalation_type, severity, details)
175
+ values (p_thread_id, p_task_id, p_raised_by_agent_id, p_assigned_agent_id, p_summary, coalesce(p_escalation_type, 'other'), coalesce(p_severity, 'medium'), coalesce(p_details, '{}'::jsonb))
176
+ returning id into v_escalation_id;
177
+
178
+ if p_task_id is not null then
179
+ select * into v_event
180
+ from agent_board.append_task_event(p_task_id, p_raised_by_agent_id, 'escalated', jsonb_build_object('escalation_id', v_escalation_id));
181
+ end if;
182
+
183
+ return query
184
+ select e.id, e.status, v_event.event_sequence, e.created_at, e.updated_at
185
+ from agent_board.escalations e
186
+ where e.id = v_escalation_id;
187
+ end;
188
+ $$;
189
+
190
+ create or replace function agent_board.resolve_escalation(
191
+ p_escalation_id uuid,
192
+ p_resolved_by_agent_id uuid,
193
+ p_details jsonb default '{}'::jsonb
194
+ )
195
+ returns table(escalation_id uuid, status text, resolved_at timestamptz, updated_at timestamptz, task_event_sequence bigint)
196
+ language plpgsql
197
+ security definer
198
+ set search_path = agent_board, public
199
+ as $$
200
+ declare
201
+ v_escalation agent_board.escalations%rowtype;
202
+ v_event record;
203
+ begin
204
+ select * into v_escalation from agent_board.escalations where id = p_escalation_id and status <> 'resolved';
205
+ if v_escalation.id is null then
206
+ raise exception 'escalation not found' using errcode = 'P0002';
207
+ end if;
208
+
209
+ update agent_board.escalations
210
+ set status = 'resolved',
211
+ resolved_by_agent_id = p_resolved_by_agent_id,
212
+ resolved_at = now(),
213
+ details = details || coalesce(p_details, '{}'::jsonb),
214
+ updated_at = now()
215
+ where id = p_escalation_id
216
+ returning * into v_escalation;
217
+
218
+ if v_escalation.task_id is not null then
219
+ select * into v_event
220
+ from agent_board.append_task_event(v_escalation.task_id, p_resolved_by_agent_id, 'status_changed', jsonb_build_object('kind', 'escalation_resolved', 'escalation_id', p_escalation_id));
221
+ end if;
222
+
223
+ return query
224
+ select v_escalation.id, v_escalation.status, v_escalation.resolved_at, v_escalation.updated_at, v_event.event_sequence;
225
+ end;
226
+ $$;
227
+
228
+ revoke all on all functions in schema agent_board from public, anon, authenticated;
229
+ grant execute on all functions in schema agent_board to service_role;