@consiliency/agent-board-schema 1.40.0 → 1.42.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/fixtures/already-applied.json +8 -0
- package/migrations/20260921000100_agent_board_task_event_origin_posture.sql +881 -0
- package/migrations/20260922000100_agent_board_board_claim_policy.sql +376 -0
- package/package.json +1 -1
- package/schema-manifest.json +21 -3
- package/src/tooling.test.mjs +22 -2
- package/tests/agent_board_auth_rls.test.sql +8 -3
- package/tests/agent_board_board_claim_policy.test.sql +228 -0
- package/tests/agent_board_ledger_authority.test.sql +12 -4
- package/tests/agent_board_signer_lifecycle.test.sql +1 -1
- package/tests/agent_board_task_event_origin_posture.test.sql +393 -0
- package/tests/agent_board_task_thread_lock_order.test.sql +10 -6
- package/tests/agent_board_write_enforcement_class.test.sql +2 -1
- package/tests/catalog/p_metadata_parameters.json +12 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
-- Board-settable claim policy: a board may decline OUR lock, never grant itself ANOTHER OWNER'S
|
|
2
|
+
-- resource.
|
|
3
|
+
--
|
|
4
|
+
-- THE ASK. A board's work may be coordinated by somebody else's locking — a repository lock, a
|
|
5
|
+
-- scheduler lease, an external mutex. If their lock already granted the work, our TTL cap or quota
|
|
6
|
+
-- rejecting the acquire is us breaking a system that was working. So the enforcement is opt-out
|
|
7
|
+
-- and the board owner holds the switch.
|
|
8
|
+
--
|
|
9
|
+
-- BCP-6 IS THE ASSERTION THIS FILE EXISTS FOR. Everything else here checks that the opt-out
|
|
10
|
+
-- works; BCP-6 checks that it does not work on the things it must never reach. `advisory` disables
|
|
11
|
+
-- the OWNERLESS guards (file/tool/custom lease cap + per-agent quota) and must leave the
|
|
12
|
+
-- fleet-authority gate on machine/repository/repo_file untouched — that gate answers "may this
|
|
13
|
+
-- agent claim this resource at all", which is authorization, not a locking convenience.
|
|
14
|
+
--
|
|
15
|
+
-- Structurally that holds because the opt-out sits inside the `v_ownerless` branch and the fleet
|
|
16
|
+
-- kinds are gated far above it. BCP-6 asserts it anyway: "it is in the right branch" is exactly
|
|
17
|
+
-- the kind of reasoning that stops being true when someone moves a line, and this file would
|
|
18
|
+
-- otherwise be a set of tests that only ever prove the permissive direction.
|
|
19
|
+
--
|
|
20
|
+
-- BCP-5 IS THE SECOND ONE THAT MATTERS. A claim reaches its board through its thread. So the
|
|
21
|
+
-- obvious hole is a claim that simply omits the thread — if that inherited "no board, therefore no
|
|
22
|
+
-- enforcement", any agent could opt itself out by passing null. It resolves to 'inherit' instead,
|
|
23
|
+
-- and BCP-5 pins that the fleet-wide flags still govern.
|
|
24
|
+
create extension if not exists pgtap with schema extensions;
|
|
25
|
+
set search_path = extensions, public, agent_board;
|
|
26
|
+
|
|
27
|
+
begin;
|
|
28
|
+
|
|
29
|
+
select plan(13);
|
|
30
|
+
|
|
31
|
+
-- ---------------------------------------------------------------------------
|
|
32
|
+
-- Fixture: one board with an owner and a non-owner member, a board-linked thread, and a
|
|
33
|
+
-- thread with NO board (for BCP-5).
|
|
34
|
+
-- ---------------------------------------------------------------------------
|
|
35
|
+
create temp table _bcp (
|
|
36
|
+
board uuid, owner_agent uuid, owner_user uuid,
|
|
37
|
+
member_agent uuid, member_user uuid,
|
|
38
|
+
thread uuid, free_thread uuid
|
|
39
|
+
) on commit drop;
|
|
40
|
+
|
|
41
|
+
do $$
|
|
42
|
+
declare
|
|
43
|
+
v_sfx text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
|
|
44
|
+
v_owner uuid := gen_random_uuid();
|
|
45
|
+
v_member uuid := gen_random_uuid();
|
|
46
|
+
v_owner_user uuid; v_member_user uuid;
|
|
47
|
+
v_board uuid; v_thread uuid := gen_random_uuid(); v_free uuid := gen_random_uuid();
|
|
48
|
+
begin
|
|
49
|
+
v_owner_user := public.ensure_github_user('bcp-own-' || v_sfx, 'bcp-own-' || v_sfx || '@example.test', 'bcp-own-' || v_sfx, 'BCP Owner');
|
|
50
|
+
v_member_user := public.ensure_github_user('bcp-mem-' || v_sfx, 'bcp-mem-' || v_sfx || '@example.test', 'bcp-mem-' || v_sfx, 'BCP Member');
|
|
51
|
+
|
|
52
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
|
|
53
|
+
values (v_owner, 'agent', 'bcp-own-' || v_sfx, 'BCP Owner', 'active', v_owner_user, v_owner_user),
|
|
54
|
+
(v_member, 'agent', 'bcp-mem-' || v_sfx, 'BCP Member', 'active', v_member_user, v_member_user);
|
|
55
|
+
|
|
56
|
+
-- create_board seeds the creator's owner membership (ROOMOWN).
|
|
57
|
+
perform set_config('request.jwt.claim.role', 'authenticated', true);
|
|
58
|
+
perform set_config('request.jwt.claim.sub', v_owner_user::text, true);
|
|
59
|
+
select board_id into v_board
|
|
60
|
+
from agent_board.create_board(v_owner, 'bcp-' || v_sfx, 'BCP Board', 'claim policy', '{}'::jsonb, 'private');
|
|
61
|
+
|
|
62
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, created_by_agent_id, role)
|
|
63
|
+
values (v_board, v_member, 'joined', v_owner, 'member');
|
|
64
|
+
|
|
65
|
+
insert into agent_board.threads(id, thread_type, created_by_agent_id, subject, board_id)
|
|
66
|
+
values (v_thread, 'coordination', v_owner, 'BCP board thread', v_board),
|
|
67
|
+
(v_free, 'coordination', v_owner, 'BCP boardless thread', null);
|
|
68
|
+
|
|
69
|
+
insert into _bcp values (v_board, v_owner, v_owner_user, v_member, v_member_user, v_thread, v_free);
|
|
70
|
+
end $$;
|
|
71
|
+
|
|
72
|
+
-- The fleet-wide guards must be ON for any of the opt-out assertions to mean anything: with them
|
|
73
|
+
-- off, everything succeeds and every permissive assertion below would pass vacuously.
|
|
74
|
+
insert into agent_board.claimgate_enforcement_flags(scope_key, enabled)
|
|
75
|
+
values ('ownerless_guards', true)
|
|
76
|
+
on conflict (scope_key) do update set enabled = true;
|
|
77
|
+
|
|
78
|
+
-- ---------------------------------------------------------------------------
|
|
79
|
+
-- BCP-1..2: the column.
|
|
80
|
+
-- ---------------------------------------------------------------------------
|
|
81
|
+
select is(
|
|
82
|
+
(select claim_policy from agent_board.boards where id = (select board from _bcp)),
|
|
83
|
+
'inherit',
|
|
84
|
+
'BCP-1: a newly created board defaults to inherit, so this migration changes no existing behaviour');
|
|
85
|
+
|
|
86
|
+
-- Scored by extracting the constraint's OWN literal list, not by checking that two known words
|
|
87
|
+
-- appear in it. The weaker spelling passes against a vocabulary that also admits a third value --
|
|
88
|
+
-- a review seat pointed out the first version of this assertion did exactly that.
|
|
89
|
+
select is(
|
|
90
|
+
(select string_agg(m[1], ',' order by m[1])
|
|
91
|
+
from pg_constraint c,
|
|
92
|
+
lateral regexp_matches(pg_get_constraintdef(c.oid), '''([a-z-]+)''', 'g') m
|
|
93
|
+
where c.conrelid = 'agent_board.boards'::regclass
|
|
94
|
+
and c.conname = 'boards_claim_policy_check'),
|
|
95
|
+
'advisory,inherit',
|
|
96
|
+
'BCP-2: the vocabulary is EXACTLY inherit and advisory -- a third admitted value fails here');
|
|
97
|
+
|
|
98
|
+
-- ---------------------------------------------------------------------------
|
|
99
|
+
-- BCP-3..4: the opt-out, in both directions. Both use an over-cap lease, so the ONLY difference
|
|
100
|
+
-- between them is the board policy.
|
|
101
|
+
-- ---------------------------------------------------------------------------
|
|
102
|
+
select lives_ok(
|
|
103
|
+
$$ select agent_board.set_board_claim_policy((select board from _bcp), 'advisory') $$,
|
|
104
|
+
'BCP-3a: a board owner may set advisory (lives_ok, so a raise fails HERE rather than aborting)');
|
|
105
|
+
|
|
106
|
+
select lives_ok(
|
|
107
|
+
$$ select * from agent_board.acquire_claim(
|
|
108
|
+
(select owner_agent from _bcp), 'file', 'bcp-over-cap-advisory',
|
|
109
|
+
now() + interval '400 days', null, (select thread from _bcp)) $$,
|
|
110
|
+
'BCP-3b: with advisory, an OVER-CAP ownerless lease is allowed — their lock governs, not ours');
|
|
111
|
+
|
|
112
|
+
do $$
|
|
113
|
+
begin
|
|
114
|
+
perform agent_board.set_board_claim_policy((select board from _bcp), 'inherit');
|
|
115
|
+
end $$;
|
|
116
|
+
|
|
117
|
+
select throws_ok(
|
|
118
|
+
$$ select * from agent_board.acquire_claim(
|
|
119
|
+
(select owner_agent from _bcp), 'file', 'bcp-over-cap-inherit',
|
|
120
|
+
now() + interval '400 days', null, (select thread from _bcp)) $$,
|
|
121
|
+
'22023',
|
|
122
|
+
null,
|
|
123
|
+
'BCP-4: with inherit, the SAME over-cap lease is rejected — so BCP-3b is the policy working, not the guard being absent');
|
|
124
|
+
|
|
125
|
+
-- ---------------------------------------------------------------------------
|
|
126
|
+
-- BCP-5: no board means no opt-out. A claim that omits the thread must not inherit permissiveness.
|
|
127
|
+
-- ---------------------------------------------------------------------------
|
|
128
|
+
select throws_ok(
|
|
129
|
+
$$ select * from agent_board.acquire_claim(
|
|
130
|
+
(select owner_agent from _bcp), 'file', 'bcp-no-thread',
|
|
131
|
+
now() + interval '400 days', null, null) $$,
|
|
132
|
+
'22023',
|
|
133
|
+
null,
|
|
134
|
+
'BCP-5: a claim with NO thread cannot opt itself out by omission — it resolves to inherit and the fleet flags govern');
|
|
135
|
+
|
|
136
|
+
select is(
|
|
137
|
+
agent_board._claimgate_board_claim_policy(null),
|
|
138
|
+
'inherit',
|
|
139
|
+
'BCP-5b: the resolver itself answers inherit for a null thread rather than NULL, so the comparison in acquire_claim can never be NULL-swallowed');
|
|
140
|
+
|
|
141
|
+
select is(
|
|
142
|
+
agent_board._claimgate_board_claim_policy((select free_thread from _bcp)),
|
|
143
|
+
'inherit',
|
|
144
|
+
'BCP-5c: and inherit for a real thread that is not board-linked');
|
|
145
|
+
|
|
146
|
+
-- ---------------------------------------------------------------------------
|
|
147
|
+
-- BCP-6: THE SECURITY BOUNDARY. advisory must NOT reach the fleet-authority gate.
|
|
148
|
+
--
|
|
149
|
+
-- Scored from the catalog rather than by attempting a machine claim, because a behavioural probe
|
|
150
|
+
-- here would need a full fleet-registration fixture and would prove only that ONE machine claim
|
|
151
|
+
-- was refused. This asks the structural question instead: does the board policy appear anywhere
|
|
152
|
+
-- the fleet kinds are decided? It must appear ONLY inside the ownerless branch.
|
|
153
|
+
-- ---------------------------------------------------------------------------
|
|
154
|
+
select is(
|
|
155
|
+
(select coalesce(string_agg(p.proname, ', ' order by p.proname), '')
|
|
156
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
157
|
+
where n.nspname = 'agent_board'
|
|
158
|
+
and p.proname in ('_can_claim_resource', '_claimgate_kind_enforced', '_claimgate_promotion_blockers')
|
|
159
|
+
and agent_board_test.stripped_body(p.oid, true) ~ '_claimgate_board_claim_policy'),
|
|
160
|
+
'',
|
|
161
|
+
'BCP-6 SECURITY: the board policy is reachable from NO fleet-authority function — a board may decline our lock, never grant itself another owner''s resource');
|
|
162
|
+
|
|
163
|
+
-- ---------------------------------------------------------------------------
|
|
164
|
+
-- BCP-7..8: who may set it. Mirrors set_board_role_gate's gate exactly.
|
|
165
|
+
-- ---------------------------------------------------------------------------
|
|
166
|
+
select throws_ok(
|
|
167
|
+
format($$ select set_config('request.jwt.claim.sub', %L, true);
|
|
168
|
+
select agent_board.set_board_claim_policy(%L, 'advisory') $$,
|
|
169
|
+
(select member_user from _bcp)::text, (select board from _bcp)::text),
|
|
170
|
+
'42501',
|
|
171
|
+
null,
|
|
172
|
+
'BCP-7: a non-owner MEMBER may not set the claim policy');
|
|
173
|
+
|
|
174
|
+
select throws_ok(
|
|
175
|
+
format($$ select set_config('request.jwt.claims', '', true);
|
|
176
|
+
select set_config('request.jwt.claim.sub', '', true);
|
|
177
|
+
select set_config('request.jwt.claim.role', '', true);
|
|
178
|
+
select agent_board.set_board_claim_policy(%L, 'advisory') $$, (select board from _bcp)::text),
|
|
179
|
+
'42501',
|
|
180
|
+
null,
|
|
181
|
+
'BCP-8: an unresolved principal (service_role) is not an owner and is refused — same carve-out as set_board_role_gate');
|
|
182
|
+
|
|
183
|
+
-- ---------------------------------------------------------------------------
|
|
184
|
+
-- BCP-9: an unknown value is refused by the RPC with the vocabulary, not by the column CHECK with
|
|
185
|
+
-- a constraint name. A caller asking for 'off' should be told what the options are.
|
|
186
|
+
-- ---------------------------------------------------------------------------
|
|
187
|
+
select throws_ok(
|
|
188
|
+
format($$ select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
189
|
+
select set_config('request.jwt.claim.sub', %L, true);
|
|
190
|
+
select agent_board.set_board_claim_policy(%L, 'off') $$,
|
|
191
|
+
(select owner_user from _bcp)::text, (select board from _bcp)::text),
|
|
192
|
+
'22023',
|
|
193
|
+
null,
|
|
194
|
+
'BCP-9: an unknown policy is refused as a bad argument, naming the vocabulary — not as a raw CHECK violation');
|
|
195
|
+
|
|
196
|
+
-- ---------------------------------------------------------------------------
|
|
197
|
+
-- BCP-10: A FLEET-REVOKED OWNER IS STILL AN OWNER BY MEMBERSHIP. This function shipped its first
|
|
198
|
+
-- version WITHOUT this check while its own commit message claimed to mirror set_board_role_gate
|
|
199
|
+
-- "exactly" -- a review seat found it by reading both functions side by side instead of taking
|
|
200
|
+
-- the claim. Fleet revocation deliberately leaves agents.status = 'active' (the ledger row is the
|
|
201
|
+
-- only authority signal), so the membership test in BCP-7/8 passes for a revoked owner and cannot
|
|
202
|
+
-- stand alone.
|
|
203
|
+
--
|
|
204
|
+
-- Disabling a board's claim enforcement is an authority write in the same class as flipping the
|
|
205
|
+
-- role gate, so it owes the same post-UPDATE revocation assertion.
|
|
206
|
+
-- ---------------------------------------------------------------------------
|
|
207
|
+
do $$
|
|
208
|
+
begin
|
|
209
|
+
-- revoke as an operator (service_role: no resolved actor)
|
|
210
|
+
perform set_config('request.jwt.claims', '', true);
|
|
211
|
+
perform set_config('request.jwt.claim.sub', '', true);
|
|
212
|
+
perform set_config('request.jwt.claim.role', '', true);
|
|
213
|
+
perform agent_board.revoke_fleet_subject(
|
|
214
|
+
'agent', (select owner_agent from _bcp), (select owner_agent from _bcp),
|
|
215
|
+
'BCP-10 revocation', '{}'::jsonb);
|
|
216
|
+
end $$;
|
|
217
|
+
|
|
218
|
+
select throws_ok(
|
|
219
|
+
format($$ select set_config('request.jwt.claim.role', 'authenticated', true);
|
|
220
|
+
select set_config('request.jwt.claim.sub', %L, true);
|
|
221
|
+
select agent_board.set_board_claim_policy(%L, 'advisory') $$,
|
|
222
|
+
(select owner_user from _bcp)::text, (select board from _bcp)::text),
|
|
223
|
+
'42501',
|
|
224
|
+
null,
|
|
225
|
+
'BCP-10: a FLEET-REVOKED owner may not disable claim enforcement, even though the board still lists them as owner and their agent row is still active');
|
|
226
|
+
|
|
227
|
+
select * from finish();
|
|
228
|
+
rollback;
|
|
@@ -539,12 +539,19 @@ select is(
|
|
|
539
539
|
or agent_board_test.stripped_body(p.oid) ~* 'update\s+agent_board\.board_memberships\M[^;]*\mset\M[^;]*\mrole\s*='
|
|
540
540
|
or agent_board_test.stripped_body(p.oid) ~* '(insert\s+into|update)\s+agent_board\.board_authority_ceilings\M'
|
|
541
541
|
or agent_board_test.stripped_body(p.oid) ~* 'update\s+agent_board\.boards\M[^;]*\mset\M[^;]*\mrole_gate_enforced\s*='
|
|
542
|
+
-- BOARDCLAIMPOLICY: boards.claim_policy is the same SHAPE of authority as
|
|
543
|
+
-- role_gate_enforced above -- a board-level switch deciding what a later gate does
|
|
544
|
+
-- (there the creator-identity exemption, here whether our ownerless claim guards apply
|
|
545
|
+
-- at all). Counting it as an authority write is what makes the pairing assertion below
|
|
546
|
+
-- hold, and is why set_board_claim_policy carries the ledger check.
|
|
547
|
+
or agent_board_test.stripped_body(p.oid) ~* 'update\s+agent_board\.boards\M[^;]*\mset\M[^;]*\mclaim_policy\s*='
|
|
542
548
|
or agent_board_test.stripped_body(p.oid) ~* 'insert\s+into\s+agent_board\.board_invite_tokens\M')),
|
|
543
549
|
'_fedsession_autojoin_default_board,_redeem_unbounded_invite_for_agent,'
|
|
544
550
|
|| '_upsert_board_membership_state,bootstrap_board_owner_memberships,create_board,'
|
|
545
|
-
|| 'create_invite_link,grant_board_role,set_board_authority_ceiling,
|
|
551
|
+
|| 'create_invite_link,grant_board_role,set_board_authority_ceiling,set_board_claim_policy,'
|
|
552
|
+
|| 'set_board_role_gate',
|
|
546
553
|
'the functions that WRITE a board-authority relation — in ANY shape, including a mintable '
|
|
547
|
-
'invite preset_role — are exactly these
|
|
554
|
+
'invite preset_role — are exactly these ten');
|
|
548
555
|
|
|
549
556
|
-- EITHER helper counts as consulting the ledger: the invite redeem path calls the UNLOCKED
|
|
550
557
|
-- predicate (see the migration header), so a side that matched only the share-locking assert
|
|
@@ -558,8 +565,8 @@ select is(
|
|
|
558
565
|
and p.proname not in ('_assert_actor_not_fleet_revoked', '_actor_fleet_revoked')
|
|
559
566
|
and agent_board_test.stripped_body(p.oid) ~ '\m(_assert_actor_not_fleet_revoked|_actor_fleet_revoked)\s*\('),
|
|
560
567
|
'_redeem_unbounded_invite_for_agent,create_board,create_invite_link,grant_board_role,'
|
|
561
|
-
|| 'set_board_authority_ceiling,set_board_role_gate',
|
|
562
|
-
'the functions that consult the fleet revocation ledger are exactly these
|
|
568
|
+
|| 'set_board_authority_ceiling,set_board_claim_policy,set_board_role_gate',
|
|
569
|
+
'the functions that consult the fleet revocation ledger are exactly these seven (#280 added '
|
|
563
570
|
'create_board)');
|
|
564
571
|
|
|
565
572
|
-- The gate proper: the two sets must differ by EXACTLY the three asserted exemptions, and any
|
|
@@ -588,6 +595,7 @@ select is(
|
|
|
588
595
|
or agent_board_test.stripped_body(p.oid) ~* 'update\s+agent_board\.board_memberships\M[^;]*\mset\M[^;]*\mrole\s*='
|
|
589
596
|
or agent_board_test.stripped_body(p.oid) ~* '(insert\s+into|update)\s+agent_board\.board_authority_ceilings\M'
|
|
590
597
|
or agent_board_test.stripped_body(p.oid) ~* 'update\s+agent_board\.boards\M[^;]*\mset\M[^;]*\mrole_gate_enforced\s*='
|
|
598
|
+
or agent_board_test.stripped_body(p.oid) ~* 'update\s+agent_board\.boards\M[^;]*\mset\M[^;]*\mclaim_policy\s*='
|
|
591
599
|
or agent_board_test.stripped_body(p.oid) ~* 'insert\s+into\s+agent_board\.board_invite_tokens\M')
|
|
592
600
|
<> (agent_board_test.stripped_body(p.oid) ~ '\m(_assert_actor_not_fleet_revoked|_actor_fleet_revoked)\s*\('))
|
|
593
601
|
) x),
|
|
@@ -511,7 +511,7 @@ select is(
|
|
|
511
511
|
where n.nspname = 'agent_board'
|
|
512
512
|
and p.proname like 'signed\_%'
|
|
513
513
|
and agent_board_test.stripped_body(p.oid)
|
|
514
|
-
~ '\m(send_direct_message|send_multicast_message|post_board_message|append_task_event|create_tasking_delivery)\s*\('),
|
|
514
|
+
~ '\m(send_direct_message|send_multicast_message|post_board_message|_?append_task_event|create_tasking_delivery)\s*\('),
|
|
515
515
|
'signed_append_task_event,signed_create_tasking_delivery,signed_post_board_message,'
|
|
516
516
|
|| 'signed_send_direct_message,signed_send_multicast_message',
|
|
517
517
|
'the five exempt wrappers each perform a message/task write, which is why they are exempt');
|