@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,174 @@
1
+ create table if not exists agent_board.agent_signing_keys (
2
+ id uuid primary key default gen_random_uuid(),
3
+ agent_id uuid not null references agent_board.agents(id) on delete cascade,
4
+ key_id text not null,
5
+ key_version integer not null check (key_version > 0),
6
+ algorithm text not null default 'Ed25519' check (algorithm in ('Ed25519')),
7
+ public_key text not null,
8
+ public_key_format text not null check (public_key_format in ('raw_ed25519_base64url', 'spki_der_base64url', 'spki_pem')),
9
+ fingerprint text not null unique check (fingerprint ~ '^sha256:[A-Za-z0-9_-]{43}$'),
10
+ status text not null default 'pending_proof' check (status in ('pending_proof', 'active', 'revoked', 'expired', 'compromised')),
11
+ proof_state text not null default 'unproven' check (proof_state in ('unproven', 'challenge_issued', 'proven', 'failed', 'revoked', 'expired')),
12
+ rotates_signing_key_id uuid references agent_board.agent_signing_keys(id) on delete set null,
13
+ rotated_by_signing_key_id uuid references agent_board.agent_signing_keys(id) on delete set null,
14
+ expires_at timestamptz,
15
+ revoked_at timestamptz,
16
+ revoked_by_agent_id uuid references agent_board.agents(id) on delete set null,
17
+ revocation_reason text,
18
+ metadata jsonb not null default '{}'::jsonb,
19
+ created_at timestamptz not null default now(),
20
+ updated_at timestamptz not null default now(),
21
+ constraint agent_signing_keys_key_id_unique unique (agent_id, key_id),
22
+ constraint agent_signing_keys_key_version_unique unique (agent_id, key_version),
23
+ constraint agent_signing_keys_active_requires_proof check (status <> 'active' or proof_state = 'proven'),
24
+ constraint agent_signing_keys_revoked_shape check (
25
+ (status in ('revoked', 'compromised') and revoked_at is not null)
26
+ or (status not in ('revoked', 'compromised') and revoked_at is null)
27
+ ),
28
+ constraint agent_signing_keys_not_self_rotates check (rotates_signing_key_id is null or rotates_signing_key_id <> id),
29
+ constraint agent_signing_keys_not_self_rotated_by check (rotated_by_signing_key_id is null or rotated_by_signing_key_id <> id)
30
+ );
31
+
32
+ comment on table agent_board.agent_signing_keys is 'Public Ed25519 signing-key registry for Message Board agents; never stores private keys.';
33
+
34
+ create index if not exists idx_agent_board_agent_signing_keys_agent_status
35
+ on agent_board.agent_signing_keys(agent_id, status, key_version desc);
36
+
37
+ create table if not exists agent_board.agent_signing_key_challenges (
38
+ id uuid primary key default gen_random_uuid(),
39
+ signing_key_id uuid not null references agent_board.agent_signing_keys(id) on delete cascade,
40
+ agent_id uuid not null references agent_board.agents(id) on delete cascade,
41
+ challenge_nonce text not null unique,
42
+ challenge_bytes text not null,
43
+ proof_state text not null default 'pending' check (proof_state in ('pending', 'verified', 'failed', 'expired', 'consumed')),
44
+ failure_class text check (
45
+ failure_class is null
46
+ or failure_class in (
47
+ 'unsupported_algorithm',
48
+ 'signature_invalid',
49
+ 'key_revoked',
50
+ 'key_expired',
51
+ 'timestamp_out_of_window',
52
+ 'nonce_replay',
53
+ 'actor_mismatch'
54
+ )
55
+ ),
56
+ expires_at timestamptz not null,
57
+ consumed_at timestamptz,
58
+ metadata jsonb not null default '{}'::jsonb,
59
+ created_at timestamptz not null default now(),
60
+ updated_at timestamptz not null default now(),
61
+ constraint agent_signing_key_challenges_consumed_shape check (
62
+ (proof_state in ('verified', 'failed', 'expired', 'consumed') and consumed_at is not null)
63
+ or (proof_state = 'pending' and consumed_at is null)
64
+ )
65
+ );
66
+
67
+ comment on table agent_board.agent_signing_key_challenges is 'Bounded one-time public-key proof challenges.';
68
+
69
+ create index if not exists idx_agent_board_agent_signing_key_challenges_key
70
+ on agent_board.agent_signing_key_challenges(signing_key_id, created_at desc);
71
+
72
+ create table if not exists agent_board.signature_evidence (
73
+ id uuid primary key default gen_random_uuid(),
74
+ agent_id uuid not null references agent_board.agents(id) on delete cascade,
75
+ signing_key_id uuid references agent_board.agent_signing_keys(id) on delete set null,
76
+ key_id text,
77
+ key_version integer check (key_version is null or key_version > 0),
78
+ algorithm text not null default 'Ed25519' check (algorithm in ('Ed25519')),
79
+ source_kind text not null check (source_kind in ('message', 'task_event', 'artifact', 'delivery_recipient', 'external')),
80
+ source_message_id uuid references agent_board.messages(id) on delete cascade,
81
+ source_task_event_id uuid references agent_board.task_events(id) on delete cascade,
82
+ source_artifact_id uuid references agent_board.artifacts(id) on delete cascade,
83
+ source_delivery_recipient_id uuid references agent_board.delivery_recipients(id) on delete cascade,
84
+ operation text not null,
85
+ payload_digest text not null,
86
+ canonicalization_version text not null default 'agent-provenance-jcs.v1',
87
+ signature_digest text,
88
+ signature_ref text,
89
+ verification_status text not null check (verification_status in ('unsigned', 'observed', 'warn', 'verified', 'failed')),
90
+ failure_class text check (
91
+ failure_class is null
92
+ or failure_class in (
93
+ 'unsigned_required',
94
+ 'unsupported_algorithm',
95
+ 'unsupported_canonicalization',
96
+ 'schema_version_mismatch',
97
+ 'actor_mismatch',
98
+ 'payload_digest_mismatch',
99
+ 'signature_invalid',
100
+ 'key_unknown',
101
+ 'key_revoked',
102
+ 'key_expired',
103
+ 'timestamp_out_of_window',
104
+ 'nonce_replay',
105
+ 'idempotency_conflict',
106
+ 'external_evidence_untrusted'
107
+ )
108
+ ),
109
+ signed_at timestamptz,
110
+ verified_at timestamptz,
111
+ nonce text,
112
+ idempotency_key text,
113
+ external_evidence_ref text,
114
+ metadata jsonb not null default '{}'::jsonb,
115
+ created_at timestamptz not null default now(),
116
+ constraint signature_evidence_source_shape check (
117
+ (source_kind = 'message' and source_message_id is not null and source_task_event_id is null and source_artifact_id is null and source_delivery_recipient_id is null)
118
+ or (source_kind = 'task_event' and source_message_id is null and source_task_event_id is not null and source_artifact_id is null and source_delivery_recipient_id is null)
119
+ or (source_kind = 'artifact' and source_message_id is null and source_task_event_id is null and source_artifact_id is not null and source_delivery_recipient_id is null)
120
+ or (source_kind = 'delivery_recipient' and source_message_id is null and source_task_event_id is null and source_artifact_id is null and source_delivery_recipient_id is not null)
121
+ or (source_kind = 'external' and source_message_id is null and source_task_event_id is null and source_artifact_id is null and source_delivery_recipient_id is null and external_evidence_ref is not null)
122
+ )
123
+ );
124
+
125
+ comment on table agent_board.signature_evidence is 'Append-only metadata-safe signature evidence linked to canonical source rows.';
126
+
127
+ create index if not exists idx_agent_board_signature_evidence_message
128
+ on agent_board.signature_evidence(source_message_id, created_at desc)
129
+ where source_message_id is not null;
130
+
131
+ create index if not exists idx_agent_board_signature_evidence_task_event
132
+ on agent_board.signature_evidence(source_task_event_id, created_at desc)
133
+ where source_task_event_id is not null;
134
+
135
+ create index if not exists idx_agent_board_signature_evidence_agent
136
+ on agent_board.signature_evidence(agent_id, created_at desc);
137
+
138
+ create table if not exists agent_board.signature_replay_state (
139
+ id uuid primary key default gen_random_uuid(),
140
+ agent_id uuid not null references agent_board.agents(id) on delete cascade,
141
+ signing_key_id uuid references agent_board.agent_signing_keys(id) on delete cascade,
142
+ operation text not null,
143
+ payload_digest text not null,
144
+ nonce text,
145
+ idempotency_key text,
146
+ envelope_timestamp timestamptz not null,
147
+ expires_at timestamptz not null,
148
+ replay_status text not null default 'consumed' check (replay_status in ('consumed', 'duplicate', 'conflict', 'expired')),
149
+ signature_evidence_id uuid references agent_board.signature_evidence(id) on delete set null,
150
+ metadata jsonb not null default '{}'::jsonb,
151
+ created_at timestamptz not null default now(),
152
+ constraint signature_replay_state_key_shape check (
153
+ (nonce is not null and idempotency_key is null)
154
+ or (nonce is null and idempotency_key is not null)
155
+ )
156
+ );
157
+
158
+ create unique index if not exists idx_agent_board_signature_replay_nonce_unique
159
+ on agent_board.signature_replay_state(agent_id, coalesce(signing_key_id, '00000000-0000-0000-0000-000000000000'::uuid), operation, payload_digest, nonce)
160
+ where nonce is not null;
161
+
162
+ create unique index if not exists idx_agent_board_signature_replay_idempotency_unique
163
+ on agent_board.signature_replay_state(agent_id, operation, idempotency_key)
164
+ where idempotency_key is not null;
165
+
166
+ grant select on agent_board.agent_signing_keys,
167
+ agent_board.agent_signing_key_challenges,
168
+ agent_board.signature_evidence,
169
+ agent_board.signature_replay_state
170
+ to service_role;
171
+
172
+ insert into agent_board.schema_version (version)
173
+ values (5)
174
+ on conflict ((true)) do update set version = excluded.version;
@@ -0,0 +1,215 @@
1
+ create or replace function agent_board.issue_signing_key_challenge(
2
+ p_agent_id uuid,
3
+ p_key_id text,
4
+ p_expires_in interval default interval '5 minutes'
5
+ )
6
+ returns table (
7
+ challenge_id uuid,
8
+ agent_id uuid,
9
+ key_id text,
10
+ key_version integer,
11
+ fingerprint text,
12
+ challenge text,
13
+ expires_at timestamptz
14
+ )
15
+ language plpgsql
16
+ security definer
17
+ set search_path = agent_board, public
18
+ as $$
19
+ declare
20
+ v_actor uuid;
21
+ v_key agent_board.agent_signing_keys%rowtype;
22
+ v_challenge_id uuid := gen_random_uuid();
23
+ v_nonce text := encode(extensions.gen_random_bytes(24), 'base64');
24
+ v_expires_at timestamptz;
25
+ begin
26
+ if p_expires_in is null or p_expires_in <= interval '0 seconds' or p_expires_in > interval '15 minutes' then
27
+ raise exception 'challenge expiry must be between 0 and 15 minutes' using errcode = '22023';
28
+ end if;
29
+
30
+ v_actor := agent_board._resolved_actor_agent_id(p_agent_id, 'p_agent_id');
31
+
32
+ select *
33
+ into v_key
34
+ from agent_board.agent_signing_keys ask
35
+ where ask.agent_id = v_actor
36
+ and ask.key_id = p_key_id
37
+ for update;
38
+
39
+ if not found then
40
+ raise exception 'signing key not found' using errcode = 'P0002';
41
+ end if;
42
+ if v_key.status in ('revoked', 'compromised') then
43
+ raise exception 'key_revoked: signing key is revoked' using errcode = '22023';
44
+ end if;
45
+ if v_key.expires_at is not null and v_key.expires_at <= now() then
46
+ update agent_board.agent_signing_keys
47
+ set status = 'expired',
48
+ proof_state = 'expired',
49
+ updated_at = now()
50
+ where id = v_key.id;
51
+ raise exception 'key_expired: signing key is expired' using errcode = '22023';
52
+ end if;
53
+
54
+ v_expires_at := now() + p_expires_in;
55
+
56
+ insert into agent_board.agent_signing_key_challenges(
57
+ id,
58
+ signing_key_id,
59
+ agent_id,
60
+ challenge_nonce,
61
+ challenge_bytes,
62
+ expires_at,
63
+ metadata
64
+ )
65
+ values (
66
+ v_challenge_id,
67
+ v_key.id,
68
+ v_actor,
69
+ v_nonce,
70
+ 'agent-signing-key-proof.v1:' || v_challenge_id::text || ':' || v_nonce,
71
+ v_expires_at,
72
+ jsonb_build_object('keyId', v_key.key_id, 'keyVersion', v_key.key_version)
73
+ );
74
+
75
+ update agent_board.agent_signing_keys
76
+ set proof_state = 'challenge_issued',
77
+ updated_at = now()
78
+ where id = v_key.id
79
+ and proof_state <> 'proven';
80
+
81
+ return query
82
+ select
83
+ v_challenge_id,
84
+ v_actor,
85
+ v_key.key_id,
86
+ v_key.key_version,
87
+ v_key.fingerprint,
88
+ 'agent-signing-key-proof.v1:' || v_challenge_id::text || ':' || v_nonce,
89
+ v_expires_at;
90
+ end;
91
+ $$;
92
+
93
+ create or replace function agent_board.record_signing_key_proof_result(
94
+ p_challenge_id uuid,
95
+ p_agent_id uuid,
96
+ p_key_id text,
97
+ p_verified boolean,
98
+ p_failure_class text default null,
99
+ p_metadata jsonb default '{}'::jsonb
100
+ )
101
+ returns table (
102
+ challenge_id uuid,
103
+ agent_id uuid,
104
+ key_id text,
105
+ key_version integer,
106
+ fingerprint text,
107
+ proof_state text,
108
+ failure_class text,
109
+ verified_at timestamptz
110
+ )
111
+ language plpgsql
112
+ security definer
113
+ set search_path = agent_board, public
114
+ as $$
115
+ declare
116
+ v_actor uuid;
117
+ v_challenge agent_board.agent_signing_key_challenges%rowtype;
118
+ v_key agent_board.agent_signing_keys%rowtype;
119
+ v_failure text;
120
+ v_verified_at timestamptz := now();
121
+ begin
122
+ v_actor := agent_board._resolved_actor_agent_id(p_agent_id, 'p_agent_id');
123
+
124
+ select *
125
+ into v_challenge
126
+ from agent_board.agent_signing_key_challenges c
127
+ where c.id = p_challenge_id
128
+ for update;
129
+
130
+ if not found then
131
+ raise exception 'signing key challenge not found' using errcode = 'P0002';
132
+ end if;
133
+ if v_challenge.agent_id <> v_actor then
134
+ raise exception 'challenge agent must match current_agent_id()' using errcode = '42501';
135
+ end if;
136
+ if v_challenge.proof_state <> 'pending' or v_challenge.consumed_at is not null then
137
+ raise exception 'nonce_replay: signing key challenge already consumed' using errcode = '23505';
138
+ end if;
139
+
140
+ select *
141
+ into v_key
142
+ from agent_board.agent_signing_keys ask
143
+ where ask.id = v_challenge.signing_key_id
144
+ and ask.agent_id = v_actor
145
+ and ask.key_id = p_key_id
146
+ for update;
147
+
148
+ if not found then
149
+ raise exception 'signing key not found for challenge' using errcode = 'P0002';
150
+ end if;
151
+
152
+ if v_challenge.expires_at <= now() then
153
+ update agent_board.agent_signing_key_challenges
154
+ set proof_state = 'expired',
155
+ failure_class = 'timestamp_out_of_window',
156
+ consumed_at = now(),
157
+ updated_at = now()
158
+ where id = p_challenge_id;
159
+ raise exception 'timestamp_out_of_window: signing key challenge expired' using errcode = '22023';
160
+ end if;
161
+
162
+ if v_key.status in ('revoked', 'compromised') then
163
+ v_failure := 'key_revoked';
164
+ elsif v_key.expires_at is not null and v_key.expires_at <= now() then
165
+ v_failure := 'key_expired';
166
+ elsif p_verified then
167
+ v_failure := null;
168
+ else
169
+ v_failure := coalesce(p_failure_class, 'signature_invalid');
170
+ end if;
171
+
172
+ if v_failure is not null and v_failure not in (
173
+ 'unsupported_algorithm',
174
+ 'signature_invalid',
175
+ 'key_revoked',
176
+ 'key_expired',
177
+ 'timestamp_out_of_window',
178
+ 'nonce_replay',
179
+ 'actor_mismatch'
180
+ ) then
181
+ raise exception 'unsupported proof failure class' using errcode = '22023';
182
+ end if;
183
+
184
+ update agent_board.agent_signing_key_challenges
185
+ set proof_state = case when v_failure is null then 'verified' else 'failed' end,
186
+ failure_class = v_failure,
187
+ consumed_at = v_verified_at,
188
+ metadata = coalesce(metadata, '{}'::jsonb) || coalesce(p_metadata, '{}'::jsonb),
189
+ updated_at = v_verified_at
190
+ where id = p_challenge_id;
191
+
192
+ update agent_board.agent_signing_keys
193
+ set status = case when v_failure is null then 'active' when v_failure = 'key_expired' then 'expired' else status end,
194
+ proof_state = case when v_failure is null then 'proven' when v_failure = 'key_expired' then 'expired' else 'failed' end,
195
+ updated_at = v_verified_at
196
+ where id = v_key.id;
197
+
198
+ return query
199
+ select
200
+ p_challenge_id,
201
+ v_actor,
202
+ v_key.key_id,
203
+ v_key.key_version,
204
+ v_key.fingerprint,
205
+ case when v_failure is null then 'proven' else 'failed' end,
206
+ v_failure,
207
+ case when v_failure is null then v_verified_at else null end;
208
+ end;
209
+ $$;
210
+
211
+ revoke all on function agent_board.issue_signing_key_challenge(uuid, text, interval) from public, anon;
212
+ revoke all on function agent_board.record_signing_key_proof_result(uuid, uuid, text, boolean, text, jsonb) from public, anon;
213
+
214
+ grant execute on function agent_board.issue_signing_key_challenge(uuid, text, interval) to authenticated, service_role;
215
+ grant execute on function agent_board.record_signing_key_proof_result(uuid, uuid, text, boolean, text, jsonb) to authenticated, service_role;
@@ -0,0 +1,300 @@
1
+ alter table agent_board.agent_signing_keys enable row level security;
2
+ alter table agent_board.agent_signing_key_challenges enable row level security;
3
+ alter table agent_board.signature_evidence enable row level security;
4
+ alter table agent_board.signature_replay_state enable row level security;
5
+
6
+ do $$
7
+ declare
8
+ v_table text;
9
+ begin
10
+ foreach v_table in array array[
11
+ 'agent_signing_keys',
12
+ 'agent_signing_key_challenges',
13
+ 'signature_evidence',
14
+ 'signature_replay_state'
15
+ ]
16
+ loop
17
+ execute format('drop policy if exists %I on agent_board.%I', 'agent_board_service_all_' || v_table, v_table);
18
+ execute format('create policy %I on agent_board.%I as permissive for all to service_role using (true) with check (true)', 'agent_board_service_all_' || v_table, v_table);
19
+ end loop;
20
+ end;
21
+ $$;
22
+
23
+ grant select on agent_board.agent_signing_keys,
24
+ agent_board.agent_signing_key_challenges,
25
+ agent_board.signature_evidence,
26
+ agent_board.signature_replay_state
27
+ to authenticated;
28
+
29
+ drop policy if exists agent_board_authenticated_select_agent_signing_keys on agent_board.agent_signing_keys;
30
+ create policy agent_board_authenticated_select_agent_signing_keys
31
+ on agent_board.agent_signing_keys
32
+ as permissive
33
+ for select
34
+ to authenticated
35
+ using (agent_id = agent_board.current_agent_id());
36
+
37
+ drop policy if exists agent_board_authenticated_select_agent_signing_key_challenges on agent_board.agent_signing_key_challenges;
38
+ create policy agent_board_authenticated_select_agent_signing_key_challenges
39
+ on agent_board.agent_signing_key_challenges
40
+ as permissive
41
+ for select
42
+ to authenticated
43
+ using (agent_id = agent_board.current_agent_id());
44
+
45
+ drop policy if exists agent_board_authenticated_select_signature_replay_state on agent_board.signature_replay_state;
46
+ create policy agent_board_authenticated_select_signature_replay_state
47
+ on agent_board.signature_replay_state
48
+ as permissive
49
+ for select
50
+ to authenticated
51
+ using (agent_id = agent_board.current_agent_id());
52
+
53
+ drop policy if exists agent_board_authenticated_select_signature_evidence on agent_board.signature_evidence;
54
+ create policy agent_board_authenticated_select_signature_evidence
55
+ on agent_board.signature_evidence
56
+ as permissive
57
+ for select
58
+ to authenticated
59
+ using (
60
+ agent_id = agent_board.current_agent_id()
61
+ or exists (
62
+ select 1
63
+ from agent_board.messages m
64
+ where m.id = source_message_id
65
+ and (
66
+ m.author_agent_id = agent_board.current_agent_id()
67
+ or exists (
68
+ select 1
69
+ from agent_board.delivery_recipients dr
70
+ where dr.source_message_id = m.id
71
+ and dr.recipient_agent_id = agent_board.current_agent_id()
72
+ )
73
+ )
74
+ )
75
+ or exists (
76
+ select 1
77
+ from agent_board.task_events te
78
+ join agent_board.tasks t on t.id = te.task_id
79
+ where te.id = source_task_event_id
80
+ and (
81
+ te.actor_agent_id = agent_board.current_agent_id()
82
+ or t.created_by_agent_id = agent_board.current_agent_id()
83
+ or t.assigned_agent_id = agent_board.current_agent_id()
84
+ )
85
+ )
86
+ or exists (
87
+ select 1
88
+ from agent_board.artifacts ar
89
+ where ar.id = source_artifact_id
90
+ and ar.created_by_agent_id = agent_board.current_agent_id()
91
+ )
92
+ or exists (
93
+ select 1
94
+ from agent_board.delivery_recipients dr
95
+ where dr.id = source_delivery_recipient_id
96
+ and dr.recipient_agent_id = agent_board.current_agent_id()
97
+ )
98
+ );
99
+
100
+ create or replace view agent_board.v_signature_evidence_status
101
+ with (security_invoker = true)
102
+ as
103
+ select
104
+ se.id as signature_evidence_id,
105
+ se.source_kind,
106
+ se.source_message_id,
107
+ se.source_task_event_id,
108
+ se.source_artifact_id,
109
+ se.source_delivery_recipient_id,
110
+ se.agent_id,
111
+ se.key_id,
112
+ se.key_version,
113
+ se.algorithm,
114
+ ask.fingerprint as key_fingerprint,
115
+ se.operation,
116
+ se.verification_status,
117
+ se.failure_class,
118
+ se.verified_at,
119
+ se.created_at
120
+ from agent_board.signature_evidence se
121
+ left join agent_board.agent_signing_keys ask on ask.id = se.signing_key_id;
122
+
123
+ create or replace view agent_board.v_thread_feed
124
+ with (security_invoker = true)
125
+ as
126
+ select
127
+ m.thread_id,
128
+ m.id as message_id,
129
+ m.message_sequence,
130
+ m.created_at as message_created_at,
131
+ m.author_agent_id,
132
+ a.handle as author_handle,
133
+ a.display_name as author_display_name,
134
+ m.message_type,
135
+ m.body,
136
+ m.task_id,
137
+ sig.verification_status,
138
+ sig.key_fingerprint,
139
+ sig.key_version,
140
+ sig.algorithm,
141
+ sig.verified_at,
142
+ sig.failure_class
143
+ from agent_board.messages m
144
+ join agent_board.agents a on a.id = m.author_agent_id
145
+ left join lateral (
146
+ select
147
+ se.verification_status,
148
+ ask.fingerprint as key_fingerprint,
149
+ se.key_version,
150
+ se.algorithm,
151
+ se.verified_at,
152
+ se.failure_class
153
+ from agent_board.signature_evidence se
154
+ left join agent_board.agent_signing_keys ask on ask.id = se.signing_key_id
155
+ where se.source_kind = 'message'
156
+ and se.source_message_id = m.id
157
+ order by se.created_at desc
158
+ limit 1
159
+ ) sig on true;
160
+
161
+ create or replace view agent_board.v_task_tree
162
+ with (security_invoker = true)
163
+ as
164
+ select
165
+ coalesce(t.root_task_id, t.id) as root_task_id,
166
+ t.id as task_id,
167
+ t.parent_task_id,
168
+ t.thread_id,
169
+ t.created_by_agent_id,
170
+ t.assigned_agent_id,
171
+ assigned.handle as assigned_agent_handle,
172
+ t.status,
173
+ t.priority,
174
+ t.orchestration_mode,
175
+ t.title,
176
+ t.details,
177
+ t.created_at,
178
+ t.updated_at,
179
+ t.completed_at,
180
+ latest.event_sequence as latest_event_sequence,
181
+ latest.event_type as latest_event_type,
182
+ latest.payload->>'kind' as latest_event_kind,
183
+ sig.verification_status,
184
+ sig.key_fingerprint,
185
+ sig.key_version,
186
+ sig.algorithm,
187
+ sig.verified_at,
188
+ sig.failure_class
189
+ from agent_board.tasks t
190
+ left join agent_board.agents assigned on assigned.id = t.assigned_agent_id
191
+ left join lateral (
192
+ select te.id, te.event_sequence, te.event_type, te.payload
193
+ from agent_board.task_events te
194
+ where te.task_id = t.id
195
+ order by te.event_sequence desc
196
+ limit 1
197
+ ) latest on true
198
+ left join lateral (
199
+ select
200
+ se.verification_status,
201
+ ask.fingerprint as key_fingerprint,
202
+ se.key_version,
203
+ se.algorithm,
204
+ se.verified_at,
205
+ se.failure_class
206
+ from agent_board.signature_evidence se
207
+ left join agent_board.agent_signing_keys ask on ask.id = se.signing_key_id
208
+ where se.source_kind = 'task_event'
209
+ and se.source_task_event_id = latest.id
210
+ order by se.created_at desc
211
+ limit 1
212
+ ) sig on true;
213
+
214
+ create or replace view agent_board.v_agent_dashboard
215
+ with (security_invoker = true)
216
+ as
217
+ select
218
+ a.id as agent_id,
219
+ a.handle,
220
+ a.display_name,
221
+ a.status as participant_status,
222
+ p.availability,
223
+ p.thread_id as presence_thread_id,
224
+ p.last_heartbeat_at,
225
+ p.expires_at as presence_expires_at,
226
+ coalesce(open_tasks.open_task_count, 0)::bigint as open_task_count,
227
+ coalesce(active_claims.active_claim_count, 0)::bigint as active_claim_count,
228
+ coalesce(open_escalations.assigned_open_escalation_count, 0)::bigint as assigned_open_escalation_count,
229
+ latest.task_id as latest_task_id,
230
+ latest.event_sequence as latest_task_event_sequence,
231
+ latest.event_type as latest_task_event_type,
232
+ latest.payload->>'kind' as latest_task_event_kind,
233
+ latest.updated_at as latest_task_updated_at,
234
+ coalesce(inbox_counts.inbox_unread_count, 0)::bigint as inbox_unread_count,
235
+ coalesce(inbox_counts.inbox_pending_action_count, 0)::bigint as inbox_pending_action_count,
236
+ latest_sig.verification_status as latest_signature_verification_status,
237
+ latest_sig.key_fingerprint as latest_signature_key_fingerprint,
238
+ latest_sig.key_version as latest_signature_key_version,
239
+ latest_sig.algorithm as latest_signature_algorithm,
240
+ latest_sig.verified_at as latest_signature_verified_at,
241
+ latest_sig.failure_class as latest_signature_failure_class
242
+ from agent_board.agents a
243
+ left join agent_board.presence p on p.agent_id = a.id
244
+ left join lateral (
245
+ select count(*) as open_task_count
246
+ from agent_board.tasks t
247
+ where t.assigned_agent_id = a.id
248
+ and t.status in ('pending', 'in_progress', 'blocked')
249
+ ) open_tasks on true
250
+ left join lateral (
251
+ select count(*) as active_claim_count
252
+ from agent_board.claims c
253
+ where c.agent_id = a.id
254
+ and c.status = 'active'
255
+ and c.released_at is null
256
+ and c.lease_expires_at > now()
257
+ ) active_claims on true
258
+ left join lateral (
259
+ select count(*) as assigned_open_escalation_count
260
+ from agent_board.escalations e
261
+ where e.assigned_agent_id = a.id
262
+ and e.status in ('open', 'acknowledged')
263
+ ) open_escalations on true
264
+ left join lateral (
265
+ select
266
+ count(*) filter (where ii.action_state in ('new', 'restored')) as inbox_unread_count,
267
+ count(*) filter (where ii.action_state in ('new', 'restored', 'deferred')) as inbox_pending_action_count
268
+ from agent_board.inbox_items ii
269
+ where ii.recipient_agent_id = a.id
270
+ and ii.visible_in_inbox = true
271
+ ) inbox_counts on true
272
+ left join lateral (
273
+ select t.id as task_id, te.id as task_event_id, te.event_sequence, te.event_type, te.payload, t.updated_at
274
+ from agent_board.tasks t
275
+ left join agent_board.task_events te on te.task_id = t.id
276
+ where t.assigned_agent_id = a.id or t.created_by_agent_id = a.id
277
+ order by t.updated_at desc, te.event_sequence desc nulls last
278
+ limit 1
279
+ ) latest on true
280
+ left join lateral (
281
+ select
282
+ se.verification_status,
283
+ ask.fingerprint as key_fingerprint,
284
+ se.key_version,
285
+ se.algorithm,
286
+ se.verified_at,
287
+ se.failure_class
288
+ from agent_board.signature_evidence se
289
+ left join agent_board.agent_signing_keys ask on ask.id = se.signing_key_id
290
+ where se.source_kind = 'task_event'
291
+ and se.source_task_event_id = latest.task_event_id
292
+ order by se.created_at desc
293
+ limit 1
294
+ ) latest_sig on true;
295
+
296
+ grant select on agent_board.v_signature_evidence_status,
297
+ agent_board.v_thread_feed,
298
+ agent_board.v_task_tree,
299
+ agent_board.v_agent_dashboard
300
+ to authenticated, service_role;