@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,234 @@
1
+ create or replace function agent_board.evaluate_signed_write_fleet_policy(
2
+ p_operation text,
3
+ p_payload jsonb,
4
+ p_gateway_decision jsonb
5
+ )
6
+ returns table(
7
+ allowed boolean,
8
+ actionable boolean,
9
+ denial_class text,
10
+ denied_recipient_agent_id uuid,
11
+ evaluated_at timestamptz
12
+ )
13
+ language plpgsql
14
+ security definer
15
+ set search_path = agent_board, public
16
+ as $$
17
+ declare
18
+ v_actor uuid := nullif(p_gateway_decision ->> 'agent_id', '')::uuid;
19
+ v_machine_id uuid;
20
+ v_board_id uuid;
21
+ v_recipient_ids uuid[] := '{}'::uuid[];
22
+ v_recipient_id uuid;
23
+ v_evidence_id uuid;
24
+ v_decision record;
25
+ v_status text := coalesce(nullif(p_gateway_decision ->> 'verification_status', ''), 'unsigned');
26
+ begin
27
+ if not agent_board._is_service_role() then
28
+ raise exception 'signed fleet policy evaluation requires service role' using errcode = '42501';
29
+ end if;
30
+ if v_actor is null then
31
+ raise exception 'actor_mismatch: signed write actor is required' using errcode = '42501';
32
+ end if;
33
+
34
+ select fma.machine_id
35
+ into v_machine_id
36
+ from agent_board.fleet_machine_agents fma
37
+ join agent_board.fleet_machines fm on fm.id = fma.machine_id
38
+ where fma.agent_id = v_actor
39
+ and fma.lifecycle_state = 'active'
40
+ and fm.lifecycle_state = 'active'
41
+ order by fma.updated_at desc
42
+ limit 1;
43
+
44
+ if v_machine_id is null then
45
+ return query select true, true, null::text, null::uuid, now();
46
+ return;
47
+ end if;
48
+
49
+ insert into agent_board.signature_evidence(
50
+ agent_id, signing_key_id, key_id, key_version, algorithm, source_kind,
51
+ operation, payload_digest, canonicalization_version, signature_digest,
52
+ verification_status, failure_class, signed_at, verified_at, nonce,
53
+ idempotency_key, external_evidence_ref, metadata
54
+ ) values (
55
+ v_actor,
56
+ nullif(p_gateway_decision ->> 'signing_key_id', '')::uuid,
57
+ nullif(p_gateway_decision ->> 'key_id', ''),
58
+ nullif(p_gateway_decision ->> 'key_version', '')::integer,
59
+ coalesce(nullif(p_gateway_decision ->> 'algorithm', ''), 'Ed25519'),
60
+ 'external',
61
+ p_operation,
62
+ coalesce(nullif(p_gateway_decision ->> 'payload_digest', ''), 'sha256:unsigned'),
63
+ coalesce(nullif(p_gateway_decision ->> 'canonicalization_version', ''), 'agent-provenance-jcs.v1'),
64
+ nullif(p_gateway_decision ->> 'signature_digest', ''),
65
+ case when v_status in ('unsigned', 'observed', 'warn', 'verified', 'failed') then v_status else 'failed' end,
66
+ nullif(p_gateway_decision ->> 'failure_class', ''),
67
+ nullif(p_gateway_decision ->> 'signed_at', '')::timestamptz,
68
+ nullif(p_gateway_decision ->> 'verified_at', '')::timestamptz,
69
+ nullif(p_gateway_decision ->> 'nonce', ''),
70
+ nullif(p_gateway_decision ->> 'idempotency_key', ''),
71
+ 'signed-write-preflight:' || coalesce(nullif(p_gateway_decision ->> 'idempotency_key', ''), nullif(p_gateway_decision ->> 'nonce', ''), gen_random_uuid()::text),
72
+ jsonb_build_object('gateway', 'signed-write-preflight', 'redacted', true, 'metadata-safe', true)
73
+ ) returning id into v_evidence_id;
74
+
75
+ if p_operation = 'send_direct_message' then
76
+ if nullif(p_payload ->> 'p_recipient_agent_id', '') is not null then
77
+ v_recipient_ids := array[(p_payload ->> 'p_recipient_agent_id')::uuid];
78
+ else
79
+ v_recipient_ids := agent_board._addressing_agent_ids_or_raise(v_actor, jsonb_build_array(p_payload -> 'p_recipient_descriptor'));
80
+ end if;
81
+ elsif p_operation = 'send_multicast_message' then
82
+ if p_payload ? 'p_recipient_agent_ids' then
83
+ select coalesce(array_agg(value::uuid), '{}'::uuid[]) into v_recipient_ids
84
+ from jsonb_array_elements_text(p_payload -> 'p_recipient_agent_ids');
85
+ else
86
+ v_recipient_ids := agent_board._addressing_agent_ids_or_raise(v_actor, p_payload -> 'p_recipient_descriptors');
87
+ end if;
88
+ elsif p_operation in ('post_board_message', 'create_tasking_delivery') then
89
+ if nullif(p_payload ->> 'p_board_id', '') is not null then
90
+ v_board_id := (p_payload ->> 'p_board_id')::uuid;
91
+ elsif nullif(p_payload ->> 'p_board_slug', '') is not null then
92
+ select id into v_board_id from agent_board.boards where slug = p_payload ->> 'p_board_slug' and status = 'active';
93
+ end if;
94
+ if p_operation = 'create_tasking_delivery' then
95
+ if p_payload ? 'p_recipient_agent_ids' then
96
+ select coalesce(array_agg(value::uuid), '{}'::uuid[]) into v_recipient_ids
97
+ from jsonb_array_elements_text(p_payload -> 'p_recipient_agent_ids');
98
+ elsif p_payload ? 'p_recipient_descriptors' and p_payload -> 'p_recipient_descriptors' <> 'null'::jsonb then
99
+ v_recipient_ids := agent_board._addressing_agent_ids_or_raise(v_actor, p_payload -> 'p_recipient_descriptors');
100
+ end if;
101
+ end if;
102
+ if v_board_id is not null then
103
+ select array(
104
+ select distinct recipient_id from (
105
+ select unnest(v_recipient_ids) as recipient_id
106
+ union
107
+ select bm.agent_id from agent_board.board_memberships bm
108
+ where bm.board_id = v_board_id and bm.membership_state in ('joined', 'subscribed')
109
+ ) recipients
110
+ ) into v_recipient_ids;
111
+ end if;
112
+ else
113
+ return query select true, true, null::text, null::uuid, now();
114
+ return;
115
+ end if;
116
+
117
+ if coalesce(cardinality(v_recipient_ids), 0) = 0 then
118
+ return query select false, false, 'recipient_policy_denied'::text, null::uuid, now();
119
+ return;
120
+ end if;
121
+
122
+ foreach v_recipient_id in array v_recipient_ids loop
123
+ select * into v_decision
124
+ from agent_board.evaluate_fleet_sender_policy(
125
+ v_actor,
126
+ v_recipient_id,
127
+ v_machine_id,
128
+ v_board_id,
129
+ p_operation,
130
+ v_evidence_id,
131
+ false
132
+ );
133
+ if not coalesce(v_decision.allowed, false) or not coalesce(v_decision.actionable, false) then
134
+ return query select false, false, coalesce(v_decision.denial_class, 'recipient_policy_denied'), v_recipient_id, now();
135
+ return;
136
+ end if;
137
+ end loop;
138
+
139
+ return query select true, true, null::text, null::uuid, now();
140
+ end;
141
+ $$;
142
+
143
+ create or replace function agent_board.signed_send_multicast_message(
144
+ p_payload jsonb,
145
+ p_gateway_decision jsonb
146
+ )
147
+ returns table(message_id uuid, delivered_recipient_count integer, inbox_item_count integer, callback_outbox_count integer, signature_evidence_id uuid, verification_status text, created_at timestamptz)
148
+ language plpgsql
149
+ security definer
150
+ set search_path = agent_board, public
151
+ as $$
152
+ declare
153
+ v_row record;
154
+ v_actor uuid := agent_board._signed_write_actor_id(p_payload, p_gateway_decision, 'p_author_agent_id');
155
+ v_evidence_id uuid;
156
+ v_recipient_ids uuid[];
157
+ begin
158
+ if p_payload ? 'p_recipient_agent_ids' then
159
+ select array_agg(value::uuid) into v_recipient_ids from jsonb_array_elements_text(p_payload -> 'p_recipient_agent_ids');
160
+ select * into v_row from agent_board.send_multicast_message(
161
+ (p_payload ->> 'p_thread_id')::uuid, v_actor, v_recipient_ids,
162
+ p_payload ->> 'p_subject', p_payload ->> 'p_body', coalesce(p_payload ->> 'p_message_type', 'comment'),
163
+ coalesce(p_payload ->> 'p_priority', 'medium'), nullif(p_payload ->> 'p_idempotency_key', ''),
164
+ coalesce(p_payload -> 'p_metadata', '{}'::jsonb) || jsonb_build_object('signed_write', jsonb_build_object('verification_status', coalesce(p_gateway_decision ->> 'verification_status', 'unsigned'), 'redacted', true))
165
+ );
166
+ else
167
+ select * into v_row from agent_board.send_multicast_message(
168
+ (p_payload ->> 'p_thread_id')::uuid, v_actor, p_payload -> 'p_recipient_descriptors',
169
+ coalesce(p_payload ->> 'p_addressing_mode', 'addressing_v1'), p_payload ->> 'p_subject', p_payload ->> 'p_body',
170
+ coalesce(p_payload ->> 'p_message_type', 'comment'), coalesce(p_payload ->> 'p_priority', 'medium'),
171
+ nullif(p_payload ->> 'p_idempotency_key', ''), coalesce(p_payload -> 'p_metadata', '{}'::jsonb)
172
+ );
173
+ end if;
174
+ v_evidence_id := agent_board._record_signed_write_evidence('message', v_row.message_id, 'send_multicast_message', p_payload, p_gateway_decision);
175
+ return query select v_row.message_id, v_row.delivered_recipient_count, v_row.inbox_item_count, v_row.callback_outbox_count, v_evidence_id, coalesce(p_gateway_decision ->> 'verification_status', 'unsigned'), v_row.created_at;
176
+ end;
177
+ $$;
178
+
179
+ create or replace function agent_board.signed_create_tasking_delivery(
180
+ p_payload jsonb,
181
+ p_gateway_decision jsonb
182
+ )
183
+ returns table(task_id uuid, created_event_id uuid, delivered_recipient_count integer, inbox_item_count integer, callback_outbox_count integer, signature_evidence_id uuid, verification_status text, created_at timestamptz, updated_at timestamptz)
184
+ language plpgsql
185
+ security definer
186
+ set search_path = agent_board, public
187
+ as $$
188
+ declare
189
+ v_row record;
190
+ v_actor uuid := agent_board._signed_write_actor_id(p_payload, p_gateway_decision, 'p_created_by_agent_id');
191
+ v_evidence_id uuid;
192
+ v_recipient_ids uuid[];
193
+ begin
194
+ if p_payload ? 'p_recipient_agent_ids' or p_payload ? 'p_board_id' then
195
+ if p_payload ? 'p_recipient_agent_ids' then
196
+ select array_agg(value::uuid) into v_recipient_ids from jsonb_array_elements_text(p_payload -> 'p_recipient_agent_ids');
197
+ end if;
198
+ select * into v_row from agent_board.create_tasking_delivery(
199
+ (p_payload ->> 'p_thread_id')::uuid, v_actor, p_payload ->> 'p_title', p_payload ->> 'p_details',
200
+ v_recipient_ids, nullif(p_payload ->> 'p_board_id', '')::uuid, coalesce(p_payload ->> 'p_priority', 'medium'),
201
+ coalesce(p_payload ->> 'p_orchestration_mode', 'distributed'), nullif(p_payload ->> 'p_idempotency_key', ''),
202
+ coalesce(p_payload -> 'p_metadata', '{}'::jsonb) || jsonb_build_object('signed_write', jsonb_build_object('verification_status', coalesce(p_gateway_decision ->> 'verification_status', 'unsigned'), 'redacted', true))
203
+ );
204
+ else
205
+ select * into v_row from agent_board.create_tasking_delivery(
206
+ (p_payload ->> 'p_thread_id')::uuid, v_actor, p_payload ->> 'p_title', p_payload ->> 'p_details',
207
+ p_payload -> 'p_recipient_descriptors', p_payload ->> 'p_board_slug', coalesce(p_payload ->> 'p_addressing_mode', 'addressing_v1'),
208
+ coalesce(p_payload ->> 'p_priority', 'medium'), coalesce(p_payload ->> 'p_orchestration_mode', 'distributed'),
209
+ nullif(p_payload ->> 'p_idempotency_key', ''), coalesce(p_payload -> 'p_metadata', '{}'::jsonb)
210
+ );
211
+ end if;
212
+ v_evidence_id := agent_board._record_signed_write_evidence('task_event', v_row.created_event_id, 'create_tasking_delivery', p_payload, p_gateway_decision);
213
+ return query select v_row.task_id, v_row.created_event_id, v_row.delivered_recipient_count, v_row.inbox_item_count, v_row.callback_outbox_count, v_evidence_id, coalesce(p_gateway_decision ->> 'verification_status', 'unsigned'), v_row.created_at, v_row.updated_at;
214
+ end;
215
+ $$;
216
+
217
+ revoke all on function agent_board.evaluate_signed_write_fleet_policy(text, jsonb, jsonb) from public, anon, authenticated;
218
+ revoke all on function agent_board.signed_send_multicast_message(jsonb, jsonb) from public, anon, authenticated;
219
+ revoke all on function agent_board.signed_create_tasking_delivery(jsonb, jsonb) from public, anon, authenticated;
220
+ grant execute on function agent_board.evaluate_signed_write_fleet_policy(text, jsonb, jsonb) to service_role;
221
+ grant execute on function agent_board.signed_send_multicast_message(jsonb, jsonb) to service_role;
222
+ grant execute on function agent_board.signed_create_tasking_delivery(jsonb, jsonb) to service_role;
223
+
224
+ revoke execute on function agent_board.send_direct_message(uuid, uuid, uuid, text, text, text, text, text, jsonb) from authenticated;
225
+ revoke execute on function agent_board.send_direct_message(uuid, uuid, jsonb, text, text, text, text, text, text, jsonb) from authenticated;
226
+ revoke execute on function agent_board.send_multicast_message(uuid, uuid, uuid[], text, text, text, text, text, jsonb) from authenticated;
227
+ revoke execute on function agent_board.send_multicast_message(uuid, uuid, jsonb, text, text, text, text, text, text, jsonb) from authenticated;
228
+ revoke execute on function agent_board.post_board_message(uuid, uuid, uuid, text, text, text, text, text, jsonb) from authenticated;
229
+ revoke execute on function agent_board.post_board_message(uuid, uuid, text, text, text, text, text, text, text, jsonb) from authenticated;
230
+ revoke execute on function agent_board.create_tasking_delivery(uuid, uuid, text, text, uuid[], uuid, text, text, text, jsonb) from authenticated;
231
+ revoke execute on function agent_board.create_tasking_delivery(uuid, uuid, text, text, jsonb, text, text, text, text, text, jsonb) from authenticated;
232
+
233
+ insert into agent_board.schema_version(version) values (5)
234
+ on conflict ((true)) do update set version = greatest(agent_board.schema_version.version, excluded.version);
@@ -0,0 +1,97 @@
1
+ -- MBENDPOINT — bundle-installation metadata + authenticated compatibility RPC.
2
+ -- Message Board-owned FORWARD migration (IF-0-MBPLANE-ENDPOINT-ARTIFACT-1),
3
+ -- appended after the frozen union. Additive: it does NOT change the
4
+ -- client-facing agent_board.schema_version (7). The install record is written
5
+ -- by the materializer / adopter at apply time via record_bundle_installation();
6
+ -- it is never hardcoded to a not-yet-selected release here.
7
+
8
+ -- Singleton identity of the installed @consiliency/agent-board-schema bundle.
9
+ create table if not exists agent_board.bundle_installation (
10
+ id boolean primary key default true,
11
+ constraint bundle_installation_singleton check (id),
12
+ package_version text not null,
13
+ bundle_digest text not null,
14
+ schema_version integer not null,
15
+ source_commit text,
16
+ materializer_receipt text,
17
+ installed_at timestamptz not null default now()
18
+ );
19
+
20
+ comment on table agent_board.bundle_installation is
21
+ 'Singleton: identity of the installed @consiliency/agent-board-schema release. Written by the materializer at apply time; consumed by get_bundle_compatibility().';
22
+
23
+ -- Written by the materializer/adopter at apply time to record the exact
24
+ -- installed bundle. Service-role only.
25
+ create or replace function agent_board.record_bundle_installation(
26
+ p_package_version text,
27
+ p_bundle_digest text,
28
+ p_schema_version integer,
29
+ p_source_commit text default null,
30
+ p_materializer_receipt text default null
31
+ )
32
+ returns void
33
+ language sql
34
+ security definer
35
+ set search_path = agent_board, pg_temp
36
+ as $$
37
+ insert into agent_board.bundle_installation as bi
38
+ (id, package_version, bundle_digest, schema_version, source_commit, materializer_receipt, installed_at)
39
+ values (true, p_package_version, p_bundle_digest, p_schema_version, p_source_commit, p_materializer_receipt, now())
40
+ on conflict (id) do update set
41
+ package_version = excluded.package_version,
42
+ bundle_digest = excluded.bundle_digest,
43
+ schema_version = excluded.schema_version,
44
+ source_commit = excluded.source_commit,
45
+ materializer_receipt = excluded.materializer_receipt,
46
+ installed_at = now();
47
+ $$;
48
+
49
+ revoke all on function agent_board.record_bundle_installation(text, text, integer, text, text) from public, anon, authenticated;
50
+ grant execute on function agent_board.record_bundle_installation(text, text, integer, text, text) to service_role;
51
+
52
+ -- Authenticated compatibility health. Reports schema version, installed bundle
53
+ -- identity, signed-write enforcement posture, and redacted readiness. Returns
54
+ -- NO credential, secret, or row content.
55
+ create or replace function agent_board.get_bundle_compatibility()
56
+ returns jsonb
57
+ language sql
58
+ stable
59
+ security definer
60
+ set search_path = agent_board, pg_temp
61
+ as $$
62
+ select jsonb_build_object(
63
+ 'schema_version', (select version from agent_board.schema_version limit 1),
64
+ 'installed_bundle_version', (select package_version from agent_board.bundle_installation limit 1),
65
+ 'installed_bundle_digest', (select bundle_digest from agent_board.bundle_installation limit 1),
66
+ 'installed_schema_version', (select schema_version from agent_board.bundle_installation limit 1),
67
+ 'source_commit', (select source_commit from agent_board.bundle_installation limit 1),
68
+ -- Signed-write enforcement is active when the signed gateway exists AND
69
+ -- NONE of the four canary-revoked raw write RPCs are directly executable by
70
+ -- authenticated (writes must be channeled through the signed gateway). The
71
+ -- probe checks all four RPCs, not just one, so a partial re-grant on any of
72
+ -- them correctly reports enforcement as NOT fully active.
73
+ 'signed_write_enforced', (
74
+ to_regprocedure('agent_board.signed_send_direct_message(jsonb, jsonb)') is not null
75
+ and not exists (
76
+ select 1 from information_schema.role_routine_grants
77
+ where routine_schema = 'agent_board'
78
+ and routine_name in (
79
+ 'send_direct_message', 'send_multicast_message',
80
+ 'post_board_message', 'create_tasking_delivery'
81
+ )
82
+ and grantee = 'authenticated'
83
+ and privilege_type = 'EXECUTE'
84
+ )
85
+ ),
86
+ -- Ready only once the materializer has recorded an install AND the schema
87
+ -- version handshake resolves.
88
+ 'ready', (
89
+ (select version from agent_board.schema_version limit 1) is not null
90
+ and exists (select 1 from agent_board.bundle_installation)
91
+ ),
92
+ 'checked_at', now()
93
+ );
94
+ $$;
95
+
96
+ revoke all on function agent_board.get_bundle_compatibility() from public, anon;
97
+ grant execute on function agent_board.get_bundle_compatibility() to authenticated, service_role;
@@ -0,0 +1,90 @@
1
+ -- Forward corrective migration (rollback Class B) — reassert the board-scoping
2
+ -- SELECT policies for delivery_recipients, inbox_items, and tasks to their
3
+ -- canonical, board-scoped (qualified) form.
4
+ --
5
+ -- WHY: a production database can carry #208 drift bytes that predate/omit the
6
+ -- 20260514000000 board-scoping fix, leaving these three policies collapsed to
7
+ -- the LEAKING tautology (bm.board_id = bm.board_id / dr.source_task_id = dr.id)
8
+ -- while its migration history claims 20260514000000 is applied — so version-ID
9
+ -- recognition would skip re-running it. This new-ID migration reasserts the
10
+ -- correct predicates. Effect of the leak: an authenticated agent who is a
11
+ -- joined/subscribed member of ANY board could read delivery_recipients and
12
+ -- inbox_items rows scoped to ANY OTHER board.
13
+ --
14
+ -- SCOPE: exactly the three policies the normalized catalog-equivalence check
15
+ -- flagged as bundle-owned divergences (plans/MBPLANE-CATALOG-EQUIVALENCE-*).
16
+ -- The 20260514000000 `boards` and `messages` policies are intentionally NOT
17
+ -- reasserted here: `boards` was superseded by 20260515000000_agent_board_boardopt
18
+ -- (reasserting the older form would revert board discovery), and prod's
19
+ -- `messages` policy already matches the bundle. Idempotent; only TIGHTENS access
20
+ -- (leaky -> board-scoped); no schema-shape change; does NOT write schema_version.
21
+
22
+ drop policy if exists agent_board_authenticated_select_tasks on agent_board.tasks;
23
+ create policy agent_board_authenticated_select_tasks
24
+ on agent_board.tasks
25
+ as permissive
26
+ for select
27
+ to authenticated
28
+ using (
29
+ created_by_agent_id = agent_board.current_agent_id()
30
+ or assigned_agent_id = agent_board.current_agent_id()
31
+ or exists (
32
+ select 1
33
+ from agent_board.delivery_recipients dr
34
+ where dr.source_task_id = tasks.id
35
+ and (
36
+ dr.recipient_agent_id = agent_board.current_agent_id()
37
+ or (
38
+ dr.board_id is not null
39
+ and exists (
40
+ select 1
41
+ from agent_board.board_memberships bm
42
+ where bm.board_id = dr.board_id
43
+ and bm.agent_id = agent_board.current_agent_id()
44
+ and bm.membership_state in ('joined', 'subscribed')
45
+ )
46
+ )
47
+ )
48
+ )
49
+ );
50
+
51
+ drop policy if exists agent_board_authenticated_select_delivery_recipients on agent_board.delivery_recipients;
52
+ create policy agent_board_authenticated_select_delivery_recipients
53
+ on agent_board.delivery_recipients
54
+ as permissive
55
+ for select
56
+ to authenticated
57
+ using (
58
+ recipient_agent_id = agent_board.current_agent_id()
59
+ or (
60
+ board_id is not null
61
+ and exists (
62
+ select 1
63
+ from agent_board.board_memberships bm
64
+ where bm.board_id = delivery_recipients.board_id
65
+ and bm.agent_id = agent_board.current_agent_id()
66
+ and bm.membership_state in ('joined', 'subscribed')
67
+ )
68
+ )
69
+ );
70
+
71
+ drop policy if exists agent_board_authenticated_select_inbox_items on agent_board.inbox_items;
72
+ create policy agent_board_authenticated_select_inbox_items
73
+ on agent_board.inbox_items
74
+ as permissive
75
+ for select
76
+ to authenticated
77
+ using (
78
+ recipient_agent_id = agent_board.current_agent_id()
79
+ or (
80
+ board_id is not null
81
+ and visible_in_board_feed = true
82
+ and exists (
83
+ select 1
84
+ from agent_board.board_memberships bm
85
+ where bm.board_id = inbox_items.board_id
86
+ and bm.agent_id = agent_board.current_agent_id()
87
+ and bm.membership_state in ('joined', 'subscribed')
88
+ )
89
+ )
90
+ );
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@consiliency/agent-board-schema",
3
+ "version": "1.2.0",
4
+ "description": "Canonical, immutable, exact-pinnable agent_board schema bundle: ordered SQL migrations, digest + compatibility manifest, and deterministic materialize/plan/check tooling for adopters.",
5
+ "repository": "https://github.com/Consiliency/message-board.git",
6
+ "type": "module",
7
+ "main": "./src/index.js",
8
+ "exports": {
9
+ ".": "./src/index.js",
10
+ "./manifest": "./schema-manifest.json",
11
+ "./provenance": "./provenance.json"
12
+ },
13
+ "bin": {
14
+ "agent-board-schema": "./bin/agent-board-schema.js"
15
+ },
16
+ "files": [
17
+ "bin",
18
+ "src",
19
+ "migrations",
20
+ "tests",
21
+ "fixtures",
22
+ "schema-manifest.json",
23
+ "provenance.json",
24
+ "README.md",
25
+ "LICENSE",
26
+ "NOTICE"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org"
31
+ },
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "license": "Apache-2.0",
36
+ "scripts": {
37
+ "build:manifest": "node ../../scripts/build-schema-manifest.mjs",
38
+ "check:manifest": "node ../../scripts/build-schema-manifest.mjs --check",
39
+ "test": "vitest run",
40
+ "pack:smoke": "node ../../scripts/schema-packed-consumer-smoke.mjs"
41
+ }
42
+ }