@consiliency/agent-board-schema 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent-board-schema.js +0 -0
- package/fixtures/already-applied.json +104 -0
- package/migrations/20260717000100_agent_board_board_role.sql +180 -0
- package/migrations/20260717000200_agent_board_thread_acl.sql +149 -0
- package/migrations/20260717000300_agent_board_agent_lineage.sql +54 -0
- package/migrations/20260717000400_agent_board_delegation_policies.sql +175 -0
- package/migrations/20260718000100_agent_board_send_authz_config.sql +172 -0
- package/migrations/20260718000200_agent_board_send_role_gate.sql +804 -0
- package/migrations/20260718000300_agent_board_fleet_policy_default_deny.sql +192 -0
- package/migrations/20260718000400_agent_board_grant_ceiling_wiring.sql +58 -0
- package/migrations/20260718000500_agent_board_fleet_agent_session_class.sql +24 -0
- package/migrations/20260718000600_agent_board_mint_fleet_agent_session.sql +172 -0
- package/migrations/20260718000700_agent_board_nonfleet_session_reaper.sql +35 -0
- package/migrations/20260718000800_agent_board_regrant_scoped_send.sql +24 -0
- package/migrations/20260718000900_agent_board_agents_status_revoked.sql +24 -0
- package/migrations/20260718001000_agent_board_delegation_enforcement.sql +296 -0
- package/migrations/20260718001100_agent_board_revoke_agent_subtree.sql +238 -0
- package/migrations/20260718001200_agent_board_resigning_refresh.sql +129 -0
- package/migrations/20260718001300_agent_board_identity_providers.sql +70 -0
- package/migrations/20260718001400_agent_board_enrollment_federation_audit.sql +50 -0
- package/migrations/20260718001500_agent_board_enroll_federated_identity.sql +220 -0
- package/migrations/20260719000100_agent_board_signing_challenge_pending_cap.sql +124 -0
- package/migrations/20260719000200_agent_board_signing_challenge_reaper.sql +34 -0
- package/migrations/20260719000300_agent_board_enroll_rate_cap.sql +72 -0
- package/migrations/20260719001000_agent_board_identity_providers_portal_contract.sql +38 -0
- package/migrations/20260719001100_agent_board_enroll_federated_identity_pop.sql +267 -0
- package/migrations/20260719001200_agent_board_enroll_federated_identity_raw_fingerprint.sql +296 -0
- package/migrations/20260719001300_agent_board_enroll_rate_events_reaper_rls.sql +62 -0
- package/package.json +9 -9
- package/schema-manifest.json +238 -4
- package/src/tooling.test.mjs +42 -1
- package/tests/agent_board_agent_lineage.test.sql +73 -0
- package/tests/agent_board_agents_status_revoked.test.sql +60 -0
- package/tests/agent_board_auth_rpcs.test.sql +17 -16
- package/tests/agent_board_board_role.test.sql +129 -0
- package/tests/agent_board_bundle_endpoint_compat.test.sql +18 -6
- package/tests/agent_board_canary_signed_fleet_enforcement.test.sql +8 -8
- package/tests/agent_board_delegation_enforcement.test.sql +175 -0
- package/tests/agent_board_delegation_policies.test.sql +119 -0
- package/tests/agent_board_enroll_federated_identity.test.sql +217 -0
- package/tests/agent_board_enroll_pop_binding.test.sql +131 -0
- package/tests/agent_board_enroll_rate_cap.test.sql +53 -0
- package/tests/agent_board_enrollment_audit_content.test.sql +134 -0
- package/tests/agent_board_fleet_agent_session_class.test.sql +64 -0
- package/tests/agent_board_fleet_policy_default_deny.test.sql +149 -0
- package/tests/agent_board_fleet_registry.test.sql +2 -2
- package/tests/agent_board_identity_providers.test.sql +104 -0
- package/tests/agent_board_inbox_rpcs.test.sql +6 -6
- package/tests/agent_board_mint_fleet_agent_session.test.sql +290 -0
- package/tests/agent_board_nonfleet_session_reaper.test.sql +61 -0
- package/tests/agent_board_portal_offline_invariant.test.sql +130 -0
- package/tests/agent_board_resigning_refresh.test.sql +73 -0
- package/tests/agent_board_revoke_agent_subtree.test.sql +188 -0
- package/tests/agent_board_schema_version.test.sql +2 -2
- package/tests/agent_board_schema_version_rpc.test.sql +1 -1
- package/tests/agent_board_scoped_send_regrant.test.sql +138 -0
- package/tests/agent_board_send_authz_config.test.sql +84 -0
- package/tests/agent_board_send_authz_matrix.test.sql +159 -0
- package/tests/agent_board_send_role_gate.test.sql +177 -0
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +87 -0
- package/tests/agent_board_signing_challenge_reaper.test.sql +74 -0
- package/tests/agent_board_thread_acl.test.sql +129 -0
|
File without changes
|
|
@@ -180,6 +180,110 @@
|
|
|
180
180
|
{
|
|
181
181
|
"id": "20260714000200_agent_board_workload_providers_rls",
|
|
182
182
|
"sha256": "37a6f94d47b0948b1d5f384e52101eb48d9a15ba96a351669c87b907cc8a03e1"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"id": "20260717000100_agent_board_board_role",
|
|
186
|
+
"sha256": "969980a7b52c6fee9329d2367006e5ce6d36b6471b53fee2247e493ac8ae512f"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"id": "20260717000200_agent_board_thread_acl",
|
|
190
|
+
"sha256": "afb323d6f9189f1f37a7fee3ffceaaa17474059d46a1fdd84adb73c28bfb27ba"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"id": "20260717000300_agent_board_agent_lineage",
|
|
194
|
+
"sha256": "48aff20015f74e5f201a2985f71b33cd3e718aeb9dfb25fbd5720cb824fe85b3"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "20260717000400_agent_board_delegation_policies",
|
|
198
|
+
"sha256": "ef7827af400e8a6206c17c8cc35623c280a92d25f4b678314847ff308d9d9dc7"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"id": "20260718000100_agent_board_send_authz_config",
|
|
202
|
+
"sha256": "d56909d4fbb7054e74818903d3eea84451d561c60908026dbbdb1956f500565d"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"id": "20260718000200_agent_board_send_role_gate",
|
|
206
|
+
"sha256": "805afdef2c11e52966bec1cd47a55dfd5e687af9181316d9ef71975b516de288"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"id": "20260718000300_agent_board_fleet_policy_default_deny",
|
|
210
|
+
"sha256": "47ad42dd50fb31ede0cb6044a22cce04a31168af2dead73ed8e01dabf84fddea"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"id": "20260718000400_agent_board_grant_ceiling_wiring",
|
|
214
|
+
"sha256": "0461f6d403fb1dd07e9423db898cd41a15a3ecfbf75402cd0085791d57a646b7"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"id": "20260718000500_agent_board_fleet_agent_session_class",
|
|
218
|
+
"sha256": "028f931aaba673fba29dd92ef3c17efcc08cb50b0dab01789986652a54fb003a"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"id": "20260718000600_agent_board_mint_fleet_agent_session",
|
|
222
|
+
"sha256": "25972553fd1ec4f350e751495d79695a8b0ccc66c0b8b272fdf517115ba37307"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"id": "20260718000700_agent_board_nonfleet_session_reaper",
|
|
226
|
+
"sha256": "9b6ff596ac386c49530c9405535b5255dcb09c271288957711a0d553446ef161"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"id": "20260718000800_agent_board_regrant_scoped_send",
|
|
230
|
+
"sha256": "1332cf2ee23d875cf0b3d646890a0fffe0efc6d2312da4e315d5dc0abadaf74f"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"id": "20260718000900_agent_board_agents_status_revoked",
|
|
234
|
+
"sha256": "f0e9b95c286bb4b6873cdc882e30262788636886360c0100b745569d6553a51b"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"id": "20260718001000_agent_board_delegation_enforcement",
|
|
238
|
+
"sha256": "7e93bd1ff924b06922bb9312684f57ce81c3d3e921dbf52357ae5059a01dbc8b"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"id": "20260718001100_agent_board_revoke_agent_subtree",
|
|
242
|
+
"sha256": "bcf14370a3da288ebe43e337d050f5fb6fb736fad69c9f6b7a0b6e0992371b7e"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"id": "20260718001200_agent_board_resigning_refresh",
|
|
246
|
+
"sha256": "47248250ab9bc11abdea8533d62611efba9812b913dc2dc5405612eb2fbafd12"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"id": "20260718001300_agent_board_identity_providers",
|
|
250
|
+
"sha256": "ab8169709faf7f74b27a593a7c980022ee073f0371b39c0dda18f2202c0f4eae"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": "20260718001400_agent_board_enrollment_federation_audit",
|
|
254
|
+
"sha256": "c42e5122625639155d9ff1d2511ebdec3ac379ca0660e67c159e37efb8ea24f5"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"id": "20260718001500_agent_board_enroll_federated_identity",
|
|
258
|
+
"sha256": "497d1f657d78c56744017409f5bd2ca361cd691ee1d0bab5b9a55ab3dd4b8ae7"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"id": "20260719000100_agent_board_signing_challenge_pending_cap",
|
|
262
|
+
"sha256": "1e48b413d31f11b9c120480c8453c8502fdde1c3a7997dcd9e22e957a2459b0b"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"id": "20260719000200_agent_board_signing_challenge_reaper",
|
|
266
|
+
"sha256": "488bbf16bf526b7df7c9b3fa5b068964703aa04ba657d6e6ceaff856a9523634"
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"id": "20260719000300_agent_board_enroll_rate_cap",
|
|
270
|
+
"sha256": "402c6d8bc5d4662b10d6fe0228a2bdd30026be96a5b5a237e26e1d44a639ae1d"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"id": "20260719001000_agent_board_identity_providers_portal_contract",
|
|
274
|
+
"sha256": "e0406afebf9b36f3793e04ca757a252f038b4100697598e0a2e76050193674a0"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"id": "20260719001100_agent_board_enroll_federated_identity_pop",
|
|
278
|
+
"sha256": "3d4517243a7d228fb04ea41e9dc7236c870cb5830fb2f815bfa12426eee47bd7"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"id": "20260719001200_agent_board_enroll_federated_identity_raw_fingerprint",
|
|
282
|
+
"sha256": "70943047b522ec23ee3d451f704c961d130a053bc210dad8759cbc9a22444454"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"id": "20260719001300_agent_board_enroll_rate_events_reaper_rls",
|
|
286
|
+
"sha256": "b2f58eadc13cb227991c4d978c5460878dc3cbada43c58285b55154b89a28459"
|
|
183
287
|
}
|
|
184
288
|
]
|
|
185
289
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
-- MBAUTHZ SL-1 (Lane A): board authority role + per-board role-gate flag.
|
|
2
|
+
--
|
|
3
|
+
-- Additive, no send-path behavior change. Adds:
|
|
4
|
+
-- * agent_board.board_memberships.role (backfilled: owner for the board
|
|
5
|
+
-- creator's membership, member otherwise; new rows default member),
|
|
6
|
+
-- * agent_board.boards.role_gate_enforced (per-board flag, default false),
|
|
7
|
+
-- * a role gate inside invite_board_member / resolve_board_request that only
|
|
8
|
+
-- applies when the board's role_gate_enforced flag is true. When false
|
|
9
|
+
-- (default) the existing any-active-member behavior is preserved verbatim,
|
|
10
|
+
-- and a null actor (service_role / current_agent_id() = null) still bypasses.
|
|
11
|
+
|
|
12
|
+
-- 1. board_memberships.role -------------------------------------------------
|
|
13
|
+
alter table agent_board.board_memberships
|
|
14
|
+
add column if not exists role text not null default 'member'
|
|
15
|
+
check (role in ('owner', 'admin', 'member', 'observer'));
|
|
16
|
+
|
|
17
|
+
comment on column agent_board.board_memberships.role is
|
|
18
|
+
'Board authority role (partial order owner>admin>member>observer). Backfilled owner for the board creator, member otherwise; new rows default member.';
|
|
19
|
+
|
|
20
|
+
-- One-time, idempotent backfill: the creator's membership becomes owner.
|
|
21
|
+
update agent_board.board_memberships bm
|
|
22
|
+
set role = 'owner'
|
|
23
|
+
from agent_board.boards b
|
|
24
|
+
where bm.board_id = b.id
|
|
25
|
+
and bm.agent_id = b.created_by_agent_id
|
|
26
|
+
and bm.role <> 'owner';
|
|
27
|
+
|
|
28
|
+
-- 2. boards.role_gate_enforced (board-native flag; deliberately NOT the
|
|
29
|
+
-- portal-owned public.app_settings) ------------------------------------
|
|
30
|
+
alter table agent_board.boards
|
|
31
|
+
add column if not exists role_gate_enforced boolean not null default false;
|
|
32
|
+
|
|
33
|
+
comment on column agent_board.boards.role_gate_enforced is
|
|
34
|
+
'When true, invite_board_member/resolve_board_request require the actor to hold board role owner or admin. Default false preserves the any-active-member behavior.';
|
|
35
|
+
|
|
36
|
+
-- 3. Role-gated invite_board_member -----------------------------------------
|
|
37
|
+
create or replace function agent_board.invite_board_member(
|
|
38
|
+
p_board_id uuid,
|
|
39
|
+
p_agent_id uuid,
|
|
40
|
+
p_created_by_agent_id uuid,
|
|
41
|
+
p_metadata jsonb default '{}'::jsonb
|
|
42
|
+
)
|
|
43
|
+
returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, created_at timestamptz, updated_at timestamptz)
|
|
44
|
+
language plpgsql
|
|
45
|
+
security definer
|
|
46
|
+
set search_path = agent_board, public
|
|
47
|
+
as $$
|
|
48
|
+
declare
|
|
49
|
+
v_board agent_board.boards%rowtype;
|
|
50
|
+
v_membership agent_board.board_memberships%rowtype;
|
|
51
|
+
v_actor uuid := agent_board.current_agent_id();
|
|
52
|
+
begin
|
|
53
|
+
select * into v_board from agent_board.boards where id = p_board_id and status = 'active';
|
|
54
|
+
if v_board.id is null then
|
|
55
|
+
raise exception 'board not found' using errcode = 'P0002';
|
|
56
|
+
end if;
|
|
57
|
+
if v_actor is not null and v_actor <> v_board.created_by_agent_id then
|
|
58
|
+
if v_board.role_gate_enforced then
|
|
59
|
+
if not exists (
|
|
60
|
+
select 1
|
|
61
|
+
from agent_board.board_memberships bm
|
|
62
|
+
where bm.board_id = p_board_id
|
|
63
|
+
and bm.agent_id = v_actor
|
|
64
|
+
and bm.membership_state in ('joined', 'subscribed')
|
|
65
|
+
and bm.role in ('owner', 'admin')
|
|
66
|
+
) then
|
|
67
|
+
raise exception 'board invite requires owner or admin role' using errcode = '42501';
|
|
68
|
+
end if;
|
|
69
|
+
else
|
|
70
|
+
if not exists (
|
|
71
|
+
select 1
|
|
72
|
+
from agent_board.board_memberships bm
|
|
73
|
+
where bm.board_id = p_board_id
|
|
74
|
+
and bm.agent_id = v_actor
|
|
75
|
+
and bm.membership_state in ('joined', 'subscribed')
|
|
76
|
+
) then
|
|
77
|
+
raise exception 'board invite requires active membership' using errcode = '42501';
|
|
78
|
+
end if;
|
|
79
|
+
end if;
|
|
80
|
+
end if;
|
|
81
|
+
if not exists (select 1 from agent_board.agents where id = p_agent_id) then
|
|
82
|
+
raise exception 'agent not found' using errcode = 'P0002';
|
|
83
|
+
end if;
|
|
84
|
+
if not exists (select 1 from agent_board.agents where id = p_created_by_agent_id) then
|
|
85
|
+
raise exception 'creator agent not found' using errcode = 'P0002';
|
|
86
|
+
end if;
|
|
87
|
+
|
|
88
|
+
v_membership := agent_board._upsert_board_membership_state(
|
|
89
|
+
p_board_id,
|
|
90
|
+
p_agent_id,
|
|
91
|
+
'invited',
|
|
92
|
+
p_created_by_agent_id,
|
|
93
|
+
p_metadata
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return query
|
|
97
|
+
select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.created_at, v_membership.updated_at;
|
|
98
|
+
end;
|
|
99
|
+
$$;
|
|
100
|
+
|
|
101
|
+
-- 4. Role-gated resolve_board_request ---------------------------------------
|
|
102
|
+
create or replace function agent_board.resolve_board_request(
|
|
103
|
+
p_board_id uuid,
|
|
104
|
+
p_agent_id uuid,
|
|
105
|
+
p_membership_state text,
|
|
106
|
+
p_created_by_agent_id uuid,
|
|
107
|
+
p_metadata jsonb default '{}'::jsonb
|
|
108
|
+
)
|
|
109
|
+
returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, left_at timestamptz, updated_at timestamptz)
|
|
110
|
+
language plpgsql
|
|
111
|
+
security definer
|
|
112
|
+
set search_path = agent_board, public
|
|
113
|
+
as $$
|
|
114
|
+
declare
|
|
115
|
+
v_board agent_board.boards%rowtype;
|
|
116
|
+
v_existing_state text;
|
|
117
|
+
v_membership agent_board.board_memberships%rowtype;
|
|
118
|
+
v_actor uuid := agent_board.current_agent_id();
|
|
119
|
+
begin
|
|
120
|
+
if p_membership_state not in ('joined', 'subscribed', 'removed') then
|
|
121
|
+
raise exception 'resolve_board_request only accepts joined, subscribed, or removed' using errcode = '22023';
|
|
122
|
+
end if;
|
|
123
|
+
select * into v_board from agent_board.boards where id = p_board_id and status = 'active';
|
|
124
|
+
if v_board.id is null then
|
|
125
|
+
raise exception 'board not found' using errcode = 'P0002';
|
|
126
|
+
end if;
|
|
127
|
+
if v_actor is not null and v_actor <> v_board.created_by_agent_id then
|
|
128
|
+
if v_board.role_gate_enforced then
|
|
129
|
+
if not exists (
|
|
130
|
+
select 1
|
|
131
|
+
from agent_board.board_memberships bm
|
|
132
|
+
where bm.board_id = p_board_id
|
|
133
|
+
and bm.agent_id = v_actor
|
|
134
|
+
and bm.membership_state in ('joined', 'subscribed')
|
|
135
|
+
and bm.role in ('owner', 'admin')
|
|
136
|
+
) then
|
|
137
|
+
raise exception 'board request resolution requires owner or admin role' using errcode = '42501';
|
|
138
|
+
end if;
|
|
139
|
+
else
|
|
140
|
+
if not exists (
|
|
141
|
+
select 1
|
|
142
|
+
from agent_board.board_memberships bm
|
|
143
|
+
where bm.board_id = p_board_id
|
|
144
|
+
and bm.agent_id = v_actor
|
|
145
|
+
and bm.membership_state in ('joined', 'subscribed')
|
|
146
|
+
) then
|
|
147
|
+
raise exception 'board request resolution requires active membership' using errcode = '42501';
|
|
148
|
+
end if;
|
|
149
|
+
end if;
|
|
150
|
+
end if;
|
|
151
|
+
|
|
152
|
+
select bm.membership_state
|
|
153
|
+
into v_existing_state
|
|
154
|
+
from agent_board.board_memberships bm
|
|
155
|
+
where bm.board_id = p_board_id
|
|
156
|
+
and bm.agent_id = p_agent_id
|
|
157
|
+
order by bm.created_at desc
|
|
158
|
+
limit 1;
|
|
159
|
+
|
|
160
|
+
if v_existing_state not in ('requested', 'invited') then
|
|
161
|
+
raise exception 'pending board membership not found' using errcode = 'P0002';
|
|
162
|
+
end if;
|
|
163
|
+
|
|
164
|
+
v_membership := agent_board._upsert_board_membership_state(
|
|
165
|
+
p_board_id,
|
|
166
|
+
p_agent_id,
|
|
167
|
+
p_membership_state,
|
|
168
|
+
p_created_by_agent_id,
|
|
169
|
+
p_metadata
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
return query
|
|
173
|
+
select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.left_at, v_membership.updated_at;
|
|
174
|
+
end;
|
|
175
|
+
$$;
|
|
176
|
+
|
|
177
|
+
-- Re-assert grants (create or replace preserves them, but be explicit so a
|
|
178
|
+
-- fresh replay is order-independent).
|
|
179
|
+
grant execute on function agent_board.invite_board_member(uuid, uuid, uuid, jsonb) to authenticated, service_role;
|
|
180
|
+
grant execute on function agent_board.resolve_board_request(uuid, uuid, text, uuid, jsonb) to authenticated, service_role;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
-- MBAUTHZ SL-2 (Lane B): per-thread ACL.
|
|
2
|
+
--
|
|
3
|
+
-- Additive, no send-path behavior change. Adds:
|
|
4
|
+
-- * agent_board.threads.access_mode ('inherit' default | 'restricted'),
|
|
5
|
+
-- * agent_board.thread_memberships (+ RLS),
|
|
6
|
+
-- * a redefinition of _current_agent_can_access_thread so that
|
|
7
|
+
-- access_mode='inherit' reproduces the current OR verbatim (unchanged
|
|
8
|
+
-- behavior for every existing thread) while access_mode='restricted'
|
|
9
|
+
-- consults thread_memberships (unexpired, unrevoked). The _is_service_role()
|
|
10
|
+
-- short-circuit is retained, so live service_role traffic is unaffected.
|
|
11
|
+
|
|
12
|
+
-- 1. threads.access_mode ----------------------------------------------------
|
|
13
|
+
alter table agent_board.threads
|
|
14
|
+
add column if not exists access_mode text not null default 'inherit'
|
|
15
|
+
check (access_mode in ('inherit', 'restricted'));
|
|
16
|
+
|
|
17
|
+
comment on column agent_board.threads.access_mode is
|
|
18
|
+
'inherit (default): thread access is the historical creator/author/task/receipt/claim/inbox/board OR. restricted: access requires an active agent_board.thread_memberships row.';
|
|
19
|
+
|
|
20
|
+
-- 2. thread_memberships -----------------------------------------------------
|
|
21
|
+
create table if not exists agent_board.thread_memberships (
|
|
22
|
+
id uuid primary key default gen_random_uuid(),
|
|
23
|
+
thread_id uuid not null references agent_board.threads(id) on delete cascade,
|
|
24
|
+
agent_id uuid not null references agent_board.agents(id) on delete cascade,
|
|
25
|
+
role text not null default 'member' check (role in ('owner', 'admin', 'member')),
|
|
26
|
+
granted_by_agent_id uuid references agent_board.agents(id) on delete set null,
|
|
27
|
+
expires_at timestamptz,
|
|
28
|
+
revoked_at timestamptz,
|
|
29
|
+
created_at timestamptz not null default now()
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
comment on table agent_board.thread_memberships is
|
|
33
|
+
'Per-thread access-control list consulted when threads.access_mode = restricted. A membership is active when revoked_at is null and (expires_at is null or expires_at > now()).';
|
|
34
|
+
|
|
35
|
+
-- One active membership per (thread, agent).
|
|
36
|
+
create unique index if not exists idx_agent_board_thread_memberships_active_unique
|
|
37
|
+
on agent_board.thread_memberships(thread_id, agent_id)
|
|
38
|
+
where revoked_at is null;
|
|
39
|
+
|
|
40
|
+
create index if not exists idx_agent_board_thread_memberships_agent
|
|
41
|
+
on agent_board.thread_memberships(agent_id, thread_id);
|
|
42
|
+
|
|
43
|
+
alter table agent_board.thread_memberships enable row level security;
|
|
44
|
+
|
|
45
|
+
drop policy if exists agent_board_authenticated_select_thread_memberships on agent_board.thread_memberships;
|
|
46
|
+
create policy agent_board_authenticated_select_thread_memberships
|
|
47
|
+
on agent_board.thread_memberships
|
|
48
|
+
as permissive
|
|
49
|
+
for select
|
|
50
|
+
to authenticated
|
|
51
|
+
using (
|
|
52
|
+
agent_id = agent_board.current_agent_id()
|
|
53
|
+
or granted_by_agent_id = agent_board.current_agent_id()
|
|
54
|
+
or agent_board._current_agent_can_access_thread(thread_id)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
grant select on agent_board.thread_memberships to authenticated;
|
|
58
|
+
grant select, insert, update on agent_board.thread_memberships to service_role;
|
|
59
|
+
|
|
60
|
+
-- 3. Redefine _current_agent_can_access_thread ------------------------------
|
|
61
|
+
-- inherit mode reproduces the historical OR verbatim; restricted mode consults
|
|
62
|
+
-- the ACL. _is_service_role() short-circuit preserved.
|
|
63
|
+
create or replace function agent_board._current_agent_can_access_thread(
|
|
64
|
+
p_thread_id uuid
|
|
65
|
+
)
|
|
66
|
+
returns boolean
|
|
67
|
+
language sql
|
|
68
|
+
stable
|
|
69
|
+
security definer
|
|
70
|
+
set search_path = ''
|
|
71
|
+
as $$
|
|
72
|
+
select
|
|
73
|
+
agent_board._is_service_role()
|
|
74
|
+
or exists (
|
|
75
|
+
select 1
|
|
76
|
+
from agent_board.threads t
|
|
77
|
+
where t.id = p_thread_id
|
|
78
|
+
and (
|
|
79
|
+
(
|
|
80
|
+
t.access_mode = 'inherit'
|
|
81
|
+
and (
|
|
82
|
+
t.created_by_agent_id = agent_board.current_agent_id()
|
|
83
|
+
or exists (
|
|
84
|
+
select 1
|
|
85
|
+
from agent_board.messages m
|
|
86
|
+
where m.thread_id = t.id
|
|
87
|
+
and m.author_agent_id = agent_board.current_agent_id()
|
|
88
|
+
)
|
|
89
|
+
or exists (
|
|
90
|
+
select 1
|
|
91
|
+
from agent_board.tasks task
|
|
92
|
+
where task.thread_id = t.id
|
|
93
|
+
and (
|
|
94
|
+
task.created_by_agent_id = agent_board.current_agent_id()
|
|
95
|
+
or task.assigned_agent_id = agent_board.current_agent_id()
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
or exists (
|
|
99
|
+
select 1
|
|
100
|
+
from agent_board.read_receipts rr
|
|
101
|
+
where rr.thread_id = t.id
|
|
102
|
+
and rr.agent_id = agent_board.current_agent_id()
|
|
103
|
+
)
|
|
104
|
+
or exists (
|
|
105
|
+
select 1
|
|
106
|
+
from agent_board.claims c
|
|
107
|
+
where c.thread_id = t.id
|
|
108
|
+
and c.agent_id = agent_board.current_agent_id()
|
|
109
|
+
)
|
|
110
|
+
or exists (
|
|
111
|
+
select 1
|
|
112
|
+
from agent_board.inbox_items ii
|
|
113
|
+
left join agent_board.messages im on im.id = ii.source_message_id
|
|
114
|
+
left join agent_board.tasks it on it.id = ii.source_task_id
|
|
115
|
+
left join agent_board.delegations ide on ide.id = ii.source_delegation_id
|
|
116
|
+
left join agent_board.tasks idt on idt.id = ide.child_task_id
|
|
117
|
+
where ii.recipient_agent_id = agent_board.current_agent_id()
|
|
118
|
+
and (
|
|
119
|
+
im.thread_id = t.id
|
|
120
|
+
or it.thread_id = t.id
|
|
121
|
+
or idt.thread_id = t.id
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
or exists (
|
|
125
|
+
select 1
|
|
126
|
+
from agent_board.delivery_recipients dr
|
|
127
|
+
join agent_board.messages bm on bm.id = dr.source_message_id
|
|
128
|
+
where dr.board_id is not null
|
|
129
|
+
and bm.thread_id = t.id
|
|
130
|
+
and agent_board._current_agent_has_board_access(dr.board_id)
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
or (
|
|
135
|
+
t.access_mode = 'restricted'
|
|
136
|
+
and exists (
|
|
137
|
+
select 1
|
|
138
|
+
from agent_board.thread_memberships tm
|
|
139
|
+
where tm.thread_id = t.id
|
|
140
|
+
and tm.agent_id = agent_board.current_agent_id()
|
|
141
|
+
and tm.revoked_at is null
|
|
142
|
+
and (tm.expires_at is null or tm.expires_at > now())
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
$$;
|
|
148
|
+
|
|
149
|
+
revoke all on function agent_board._current_agent_can_access_thread(uuid) from public, anon, authenticated;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
-- MBAUTHZ SL-3 (Lane C): agent lineage.
|
|
2
|
+
--
|
|
3
|
+
-- Additive, no send-path behavior change. Adds self-referential
|
|
4
|
+
-- agents.parent_agent_id / agents.root_agent_id (nullable) plus an
|
|
5
|
+
-- owner-consistency guard: a parented agent (root_agent_id set) must share the
|
|
6
|
+
-- owner_user_id of its lineage root. Unparented rows (NULL lineage) are
|
|
7
|
+
-- unaffected.
|
|
8
|
+
|
|
9
|
+
alter table agent_board.agents
|
|
10
|
+
add column if not exists parent_agent_id uuid references agent_board.agents(id) on delete set null,
|
|
11
|
+
add column if not exists root_agent_id uuid references agent_board.agents(id) on delete set null;
|
|
12
|
+
|
|
13
|
+
comment on column agent_board.agents.parent_agent_id is
|
|
14
|
+
'Direct delegating parent agent (self-referential, nullable). NULL for top-level agents.';
|
|
15
|
+
comment on column agent_board.agents.root_agent_id is
|
|
16
|
+
'Lineage root agent (self-referential, nullable). When set, this agent must share the root agent''s owner_user_id.';
|
|
17
|
+
|
|
18
|
+
create index if not exists idx_agent_board_agents_parent on agent_board.agents(parent_agent_id) where parent_agent_id is not null;
|
|
19
|
+
create index if not exists idx_agent_board_agents_root on agent_board.agents(root_agent_id) where root_agent_id is not null;
|
|
20
|
+
|
|
21
|
+
-- Owner-consistency guard.
|
|
22
|
+
create or replace function agent_board._enforce_agent_lineage_owner()
|
|
23
|
+
returns trigger
|
|
24
|
+
language plpgsql
|
|
25
|
+
security definer
|
|
26
|
+
set search_path = ''
|
|
27
|
+
as $$
|
|
28
|
+
declare
|
|
29
|
+
v_root_owner uuid;
|
|
30
|
+
begin
|
|
31
|
+
if new.root_agent_id is null then
|
|
32
|
+
return new;
|
|
33
|
+
end if;
|
|
34
|
+
|
|
35
|
+
select a.owner_user_id
|
|
36
|
+
into v_root_owner
|
|
37
|
+
from agent_board.agents a
|
|
38
|
+
where a.id = new.root_agent_id;
|
|
39
|
+
|
|
40
|
+
if new.owner_user_id is distinct from v_root_owner then
|
|
41
|
+
raise exception 'agent lineage owner mismatch: agent owner_user_id must equal its root agent owner_user_id'
|
|
42
|
+
using errcode = '23514';
|
|
43
|
+
end if;
|
|
44
|
+
|
|
45
|
+
return new;
|
|
46
|
+
end;
|
|
47
|
+
$$;
|
|
48
|
+
|
|
49
|
+
drop trigger if exists trg_agent_board_agents_lineage_owner on agent_board.agents;
|
|
50
|
+
create trigger trg_agent_board_agents_lineage_owner
|
|
51
|
+
before insert or update of parent_agent_id, root_agent_id, owner_user_id
|
|
52
|
+
on agent_board.agents
|
|
53
|
+
for each row
|
|
54
|
+
execute function agent_board._enforce_agent_lineage_owner();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
-- MBAUTHZ SL-4 (Lane D): delegation policies, board authority ceilings, and the
|
|
2
|
+
-- _grant_within_ceiling helper.
|
|
3
|
+
--
|
|
4
|
+
-- Additive, no send-path behavior change. MBAUTHZ OWNS this schema + helper;
|
|
5
|
+
-- MBSENDAUTHZ (later phase) only consumes it. The board ceiling is a SEPARATE
|
|
6
|
+
-- table keyed on board_id (maintainer decision), NOT a boards column; a board
|
|
7
|
+
-- with no ceiling row defaults to 'owner' (unchanged behavior).
|
|
8
|
+
|
|
9
|
+
-- 1. delegation_policies -----------------------------------------------------
|
|
10
|
+
create table if not exists agent_board.delegation_policies (
|
|
11
|
+
id uuid primary key default gen_random_uuid(),
|
|
12
|
+
root_agent_id uuid not null references agent_board.agents(id) on delete cascade,
|
|
13
|
+
mode text not null default 'independent_auth' check (mode in ('independent_auth', 'root_minted')),
|
|
14
|
+
max_depth int not null default 1,
|
|
15
|
+
max_active_children int,
|
|
16
|
+
allowed_scopes jsonb,
|
|
17
|
+
-- The grant/working clock (hours-scale), NOT the <=15-min session-token clock.
|
|
18
|
+
child_ttl_ceiling interval not null default interval '8 hours',
|
|
19
|
+
require_child_signing_key boolean not null default true,
|
|
20
|
+
revoked_at timestamptz,
|
|
21
|
+
created_at timestamptz not null default now()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
comment on table agent_board.delegation_policies is
|
|
25
|
+
'Per-root-agent delegation policy. child_ttl_ceiling is the hours-scale grant/working clock, not the session-token clock.';
|
|
26
|
+
|
|
27
|
+
create index if not exists idx_agent_board_delegation_policies_root
|
|
28
|
+
on agent_board.delegation_policies(root_agent_id)
|
|
29
|
+
where revoked_at is null;
|
|
30
|
+
|
|
31
|
+
alter table agent_board.delegation_policies enable row level security;
|
|
32
|
+
|
|
33
|
+
drop policy if exists agent_board_authenticated_select_delegation_policies on agent_board.delegation_policies;
|
|
34
|
+
create policy agent_board_authenticated_select_delegation_policies
|
|
35
|
+
on agent_board.delegation_policies
|
|
36
|
+
as permissive
|
|
37
|
+
for select
|
|
38
|
+
to authenticated
|
|
39
|
+
using (root_agent_id = agent_board.current_agent_id());
|
|
40
|
+
|
|
41
|
+
grant select on agent_board.delegation_policies to authenticated;
|
|
42
|
+
grant select, insert, update on agent_board.delegation_policies to service_role;
|
|
43
|
+
|
|
44
|
+
-- 2. board_authority_ceilings (separate table; not a boards column) ---------
|
|
45
|
+
create table if not exists agent_board.board_authority_ceilings (
|
|
46
|
+
board_id uuid primary key references agent_board.boards(id) on delete cascade,
|
|
47
|
+
max_grantable_role text not null default 'owner' check (max_grantable_role in ('owner', 'admin', 'member', 'observer')),
|
|
48
|
+
created_at timestamptz not null default now(),
|
|
49
|
+
updated_at timestamptz not null default now()
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
comment on table agent_board.board_authority_ceilings is
|
|
53
|
+
'Optional per-board cap on the highest role a grantor may grant. No row means no explicit ceiling, resolved as owner (unchanged behavior).';
|
|
54
|
+
|
|
55
|
+
alter table agent_board.board_authority_ceilings enable row level security;
|
|
56
|
+
|
|
57
|
+
drop policy if exists agent_board_authenticated_select_board_authority_ceilings on agent_board.board_authority_ceilings;
|
|
58
|
+
create policy agent_board_authenticated_select_board_authority_ceilings
|
|
59
|
+
on agent_board.board_authority_ceilings
|
|
60
|
+
as permissive
|
|
61
|
+
for select
|
|
62
|
+
to authenticated
|
|
63
|
+
using (agent_board._current_agent_has_board_access(board_id));
|
|
64
|
+
|
|
65
|
+
grant select on agent_board.board_authority_ceilings to authenticated;
|
|
66
|
+
grant select, insert, update on agent_board.board_authority_ceilings to service_role;
|
|
67
|
+
|
|
68
|
+
-- 3. _grant_within_ceiling ---------------------------------------------------
|
|
69
|
+
-- Role partial order owner>admin>member>observer. A grant is permitted iff the
|
|
70
|
+
-- grantee role does not outrank the grantor's own board role AND does not
|
|
71
|
+
-- outrank the board's ceiling (default owner when no ceiling row exists).
|
|
72
|
+
create or replace function agent_board._grant_within_ceiling(
|
|
73
|
+
p_grantor_agent_id uuid,
|
|
74
|
+
p_board_id uuid,
|
|
75
|
+
p_grantee_role text
|
|
76
|
+
)
|
|
77
|
+
returns boolean
|
|
78
|
+
language sql
|
|
79
|
+
stable
|
|
80
|
+
security definer
|
|
81
|
+
set search_path = ''
|
|
82
|
+
as $$
|
|
83
|
+
with ranks(role, rank) as (
|
|
84
|
+
values ('owner', 4), ('admin', 3), ('member', 2), ('observer', 1)
|
|
85
|
+
),
|
|
86
|
+
grantor as (
|
|
87
|
+
select r.rank
|
|
88
|
+
from agent_board.board_memberships bm
|
|
89
|
+
join ranks r on r.role = bm.role
|
|
90
|
+
where bm.board_id = p_board_id
|
|
91
|
+
and bm.agent_id = p_grantor_agent_id
|
|
92
|
+
and bm.membership_state in ('joined', 'subscribed')
|
|
93
|
+
order by r.rank desc
|
|
94
|
+
limit 1
|
|
95
|
+
),
|
|
96
|
+
ceiling as (
|
|
97
|
+
select coalesce(
|
|
98
|
+
(select r.rank
|
|
99
|
+
from agent_board.board_authority_ceilings c
|
|
100
|
+
join ranks r on r.role = c.max_grantable_role
|
|
101
|
+
where c.board_id = p_board_id),
|
|
102
|
+
4 -- no row -> owner
|
|
103
|
+
) as rank
|
|
104
|
+
),
|
|
105
|
+
grantee as (
|
|
106
|
+
select rank from ranks where role = p_grantee_role
|
|
107
|
+
)
|
|
108
|
+
select
|
|
109
|
+
(select rank from grantee) is not null
|
|
110
|
+
and exists (select 1 from grantor)
|
|
111
|
+
and (select rank from grantee) <= (select rank from grantor)
|
|
112
|
+
and (select rank from grantee) <= (select rank from ceiling)
|
|
113
|
+
$$;
|
|
114
|
+
|
|
115
|
+
comment on function agent_board._grant_within_ceiling(uuid, uuid, text) is
|
|
116
|
+
'True iff p_grantee_role does not outrank the grantor''s active board role nor the board ceiling (default owner). MBAUTHZ owns this; MBSENDAUTHZ consumes it.';
|
|
117
|
+
|
|
118
|
+
revoke all on function agent_board._grant_within_ceiling(uuid, uuid, text) from public, anon;
|
|
119
|
+
grant execute on function agent_board._grant_within_ceiling(uuid, uuid, text) to authenticated, service_role;
|
|
120
|
+
|
|
121
|
+
-- 4. set_board_authority_ceiling (owner-settable cap) ------------------------
|
|
122
|
+
-- #13: the board owner (the human root's delegated authority) can set the per-board
|
|
123
|
+
-- ceiling. Permitted ONLY when the caller (current_agent_id()) holds an owner
|
|
124
|
+
-- board_memberships row for that board; every other caller raises 42501. The
|
|
125
|
+
-- grantee-bounded _grant_within_ceiling semantics above are unchanged — this only
|
|
126
|
+
-- lets the owner set the stored ceiling row that _grant_within_ceiling reads.
|
|
127
|
+
-- service_role (current_agent_id() = null) is intentionally NOT an owner here; it
|
|
128
|
+
-- retains its direct table-write grant for operator provisioning.
|
|
129
|
+
create or replace function agent_board.set_board_authority_ceiling(
|
|
130
|
+
p_board_id uuid,
|
|
131
|
+
p_max_grantable_role text
|
|
132
|
+
)
|
|
133
|
+
returns table(
|
|
134
|
+
ceiling_board_id uuid,
|
|
135
|
+
ceiling_max_grantable_role text
|
|
136
|
+
)
|
|
137
|
+
language plpgsql
|
|
138
|
+
security definer
|
|
139
|
+
set search_path = agent_board, public
|
|
140
|
+
as $$
|
|
141
|
+
declare
|
|
142
|
+
v_actor uuid := agent_board.current_agent_id();
|
|
143
|
+
begin
|
|
144
|
+
if p_max_grantable_role not in ('owner', 'admin', 'member', 'observer') then
|
|
145
|
+
raise exception 'max_grantable_role must be one of owner/admin/member/observer' using errcode = '22023';
|
|
146
|
+
end if;
|
|
147
|
+
|
|
148
|
+
if v_actor is null
|
|
149
|
+
or not exists (
|
|
150
|
+
select 1
|
|
151
|
+
from agent_board.board_memberships bm
|
|
152
|
+
where bm.board_id = p_board_id
|
|
153
|
+
and bm.agent_id = v_actor
|
|
154
|
+
and bm.role = 'owner'
|
|
155
|
+
and bm.membership_state in ('joined', 'subscribed')
|
|
156
|
+
) then
|
|
157
|
+
raise exception 'only a board owner may set the board authority ceiling' using errcode = '42501';
|
|
158
|
+
end if;
|
|
159
|
+
|
|
160
|
+
insert into agent_board.board_authority_ceilings(board_id, max_grantable_role)
|
|
161
|
+
values (p_board_id, p_max_grantable_role)
|
|
162
|
+
on conflict (board_id) do update
|
|
163
|
+
set max_grantable_role = excluded.max_grantable_role,
|
|
164
|
+
updated_at = now();
|
|
165
|
+
|
|
166
|
+
return query
|
|
167
|
+
select p_board_id, p_max_grantable_role;
|
|
168
|
+
end;
|
|
169
|
+
$$;
|
|
170
|
+
|
|
171
|
+
comment on function agent_board.set_board_authority_ceiling(uuid, text) is
|
|
172
|
+
'Owner-gated setter for a board''s authority ceiling: upserts board_authority_ceilings only when current_agent_id() is an owner of the board (else raises 42501). Leaves _grant_within_ceiling semantics unchanged.';
|
|
173
|
+
|
|
174
|
+
revoke all on function agent_board.set_board_authority_ceiling(uuid, text) from public, anon;
|
|
175
|
+
grant execute on function agent_board.set_board_authority_ceiling(uuid, text) to authenticated, service_role;
|