@consiliency/agent-board-schema 1.44.0 → 1.48.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.
@@ -0,0 +1,226 @@
1
+ -- #373 — the ownerless TTL cap must bind at RENEW, not only at acquire.
2
+ --
3
+ -- ORT-3 IS THE ASSERTION THIS FILE EXISTS FOR, and it is written as the ATTACK: acquire a lease
4
+ -- inside the cap, then renew it far past the cap. Before this migration that succeeded and could
5
+ -- be repeated forever, so the 30-day bound the cap advertised did not exist. The existing suite
6
+ -- could not have caught its removal because it exercises acquire only — which is exactly how the
7
+ -- gap survived.
8
+ --
9
+ -- ORT-4 IS THE TWIN, and without it ORT-3 is satisfiable by a function that simply refuses to
10
+ -- renew anything. Clamping must still RENEW: a within-cap request has to pass through untouched.
11
+ -- A guard that works by breaking the feature is the failure mode this repo has hit before.
12
+ --
13
+ -- WHY CLAMP AND NOT RAISE, asserted rather than assumed (ORT-3 checks the resulting timestamp, not
14
+ -- an error): acquire RAISES on an over-cap request because nothing is lost by asking again, but a
15
+ -- renew that raised would kill a lease the agent is actively holding. Renew already resolves the
16
+ -- fleet-kind horizon the same way. If someone later "hardens" this into a raise, ORT-3 fails.
17
+ --
18
+ -- SCOPING. The guard flag is fleet-wide state shared with every other test, so this file SETS it
19
+ -- explicitly rather than inheriting whatever ran before — an inherited `false` would make every
20
+ -- assertion here pass vacuously against a completely unguarded function.
21
+ create extension if not exists pgtap with schema extensions;
22
+ set search_path = extensions, public, agent_board;
23
+
24
+ begin;
25
+
26
+ select plan(11);
27
+
28
+ -- ---------------------------------------------------------------------------
29
+ -- Fixture: one agent, one board-linked thread, and the guard flag ON.
30
+ -- ---------------------------------------------------------------------------
31
+ create temp table _ort (agent uuid, auth_user uuid, thread uuid, board uuid) on commit drop;
32
+
33
+ do $$
34
+ declare
35
+ v_sfx text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
36
+ v_agent uuid := gen_random_uuid();
37
+ v_user uuid;
38
+ v_thread uuid := gen_random_uuid();
39
+ v_board uuid;
40
+ begin
41
+ v_user := public.ensure_github_user('ort-' || v_sfx, 'ort-' || v_sfx || '@example.test', 'ort-' || v_sfx, 'ORT Agent');
42
+ insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
43
+ values (v_agent, 'agent', 'ort-' || v_sfx, 'ORT Agent', 'active', v_user, v_user);
44
+
45
+ perform set_config('request.jwt.claim.role', 'authenticated', true);
46
+ perform set_config('request.jwt.claim.sub', v_user::text, true);
47
+ select board_id into v_board
48
+ from agent_board.create_board(v_agent, 'ort-' || v_sfx, 'ORT Board', 'ownerless renew', '{}'::jsonb, 'private');
49
+
50
+ insert into agent_board.threads(id, thread_type, created_by_agent_id, subject, board_id)
51
+ values (v_thread, 'coordination', v_agent, 'ORT thread', v_board);
52
+
53
+ insert into _ort values (v_agent, v_user, v_thread, v_board);
54
+ end $$;
55
+
56
+ -- The guard must be ON or every assertion below passes against an unguarded function.
57
+ insert into agent_board.claimgate_enforcement_flags(scope_key, enabled)
58
+ values ('ownerless_guards', true)
59
+ on conflict (scope_key) do update set enabled = true;
60
+
61
+ -- ---------------------------------------------------------------------------
62
+ -- ORT-1/2: the acquire half still behaves — the baseline this migration must not disturb.
63
+ -- ---------------------------------------------------------------------------
64
+ select throws_ok(
65
+ format($$ select * from agent_board.acquire_claim(%L, 'file', 'ort-overcap-acquire', now() + interval '400 days', null, %L) $$,
66
+ (select agent from _ort)::text, (select thread from _ort)::text),
67
+ '22023',
68
+ null,
69
+ 'ORT-1: acquire still REJECTS an over-cap ownerless lease (22023) — the half that already worked');
70
+
71
+ select lives_ok(
72
+ format($$ select * from agent_board.acquire_claim(%L, 'file', 'ort-within-cap', now() + interval '1 day', null, %L) $$,
73
+ (select agent from _ort)::text, (select thread from _ort)::text),
74
+ 'ORT-2: a within-cap acquire still succeeds');
75
+
76
+ -- ---------------------------------------------------------------------------
77
+ -- ORT-3: THE ATTACK. Renew that same lease far past the cap. Before this migration the renewed
78
+ -- lease landed at +400 days; now it is clamped to the 30-day horizon.
79
+ -- ---------------------------------------------------------------------------
80
+ create temp table _tok (lease_token uuid) on commit drop;
81
+ insert into _tok
82
+ select lease_token from agent_board.claims
83
+ where agent_id = (select agent from _ort) and resource_key = 'ort-within-cap' and status = 'active';
84
+
85
+ do $$
86
+ begin
87
+ perform agent_board.renew_claim((select lease_token from _tok), now() + interval '400 days');
88
+ end $$;
89
+
90
+ select ok(
91
+ (select lease_expires_at <= now() + agent_board._claimgate_ownerless_ttl_cap() + interval '1 minute'
92
+ from agent_board.claims where lease_token = (select lease_token from _tok)),
93
+ 'ORT-3 THE POINT: a renew asking for +400 days is CLAMPED to the 30-day cap — before this it landed at 400 days and could be repeated forever');
94
+
95
+ select ok(
96
+ (select lease_expires_at > now() + interval '29 days'
97
+ from agent_board.claims where lease_token = (select lease_token from _tok)),
98
+ 'ORT-3b: and it is clamped TO the cap, not to something arbitrarily short');
99
+
100
+ -- ---------------------------------------------------------------------------
101
+ -- ORT-4: THE TWIN. Clamping must not become refusing — a within-cap renew passes through exactly.
102
+ -- ---------------------------------------------------------------------------
103
+ do $$
104
+ begin
105
+ perform agent_board.renew_claim((select lease_token from _tok), now() + interval '2 days');
106
+ end $$;
107
+
108
+ select ok(
109
+ (select lease_expires_at between now() + interval '47 hours' and now() + interval '49 hours'
110
+ from agent_board.claims where lease_token = (select lease_token from _tok)),
111
+ 'ORT-4: a WITHIN-cap renew is untouched (+2 days lands at +2 days) — a guard that refused every renew would pass ORT-3 and be useless');
112
+
113
+ -- ---------------------------------------------------------------------------
114
+ -- ORT-5: the operator carve-out. service_role is exempt on renew exactly as on acquire, so an
115
+ -- operator can still extend a lease deliberately.
116
+ -- ---------------------------------------------------------------------------
117
+ do $$
118
+ begin
119
+ perform set_config('request.jwt.claims', '', true);
120
+ perform set_config('request.jwt.claim.sub', '', true);
121
+ perform set_config('request.jwt.claim.role', '', true);
122
+ perform agent_board.renew_claim((select lease_token from _tok), now() + interval '400 days');
123
+ end $$;
124
+
125
+ select ok(
126
+ (select lease_expires_at > now() + interval '300 days'
127
+ from agent_board.claims where lease_token = (select lease_token from _tok)),
128
+ 'ORT-5: service_role is EXEMPT at renew, same carve-out as acquire — an operator extension is not clamped');
129
+
130
+ -- ---------------------------------------------------------------------------
131
+ -- ORT-6: the flag governs BOTH paths. With ownerless_guards off, renew stops clamping — so the
132
+ -- operator switch means the same thing on each path rather than half-latching.
133
+ -- ---------------------------------------------------------------------------
134
+ do $$
135
+ begin
136
+ update agent_board.claimgate_enforcement_flags set enabled = false where scope_key = 'ownerless_guards';
137
+ perform set_config('request.jwt.claim.role', 'authenticated', true);
138
+ perform set_config('request.jwt.claim.sub', (select auth_user from _ort)::text, true);
139
+ perform agent_board.renew_claim((select lease_token from _tok), now() + interval '400 days');
140
+ end $$;
141
+
142
+ select ok(
143
+ (select lease_expires_at > now() + interval '300 days'
144
+ from agent_board.claims where lease_token = (select lease_token from _tok)),
145
+ 'ORT-6: with ownerless_guards OFF the renew is NOT clamped — one flag governs acquire and renew alike');
146
+
147
+ -- ---------------------------------------------------------------------------
148
+ -- ORT-7: the board opt-out reaches renew too. A board coordinating its own locking
149
+ -- (claim_policy='advisory') is not clamped on either path.
150
+ -- ---------------------------------------------------------------------------
151
+ do $$
152
+ begin
153
+ update agent_board.claimgate_enforcement_flags set enabled = true where scope_key = 'ownerless_guards';
154
+ perform agent_board.set_board_claim_policy((select board from _ort), 'advisory');
155
+ perform agent_board.renew_claim((select lease_token from _tok), now() + interval '400 days');
156
+ end $$;
157
+
158
+ select ok(
159
+ (select lease_expires_at > now() + interval '300 days'
160
+ from agent_board.claims where lease_token = (select lease_token from _tok)),
161
+ 'ORT-7: a board with claim_policy=advisory is not clamped at renew either — the opt-out is not acquire-only, which would have been the same bug one layer down');
162
+
163
+ -- ---------------------------------------------------------------------------
164
+ -- ORT-8: CLASS. Catalog-derived, so the acquire/renew asymmetry that caused #373 fails a test if
165
+ -- it ever returns. Scored on the pair rather than on renew alone: asserting only "renew has the
166
+ -- cap" would pass if acquire silently LOST it.
167
+ -- ---------------------------------------------------------------------------
168
+ select is(
169
+ (select string_agg(p.proname || '=' || (agent_board_test.stripped_body(p.oid, true) ~ '_claimgate_ownerless_ttl_cap')::text, ', ' order by p.proname)
170
+ from pg_proc p join pg_namespace n on n.oid = p.pronamespace
171
+ where n.nspname = 'agent_board' and p.proname in ('acquire_claim', 'renew_claim')),
172
+ 'acquire_claim=true, renew_claim=true',
173
+ 'ORT-8 CLASS: BOTH acquire and renew reference the ownerless TTL cap — #373 was exactly this pair reading true/false');
174
+
175
+ -- ---------------------------------------------------------------------------
176
+ -- ORT-9: THE OTHER TWO KINDS. Every assertion above drives `file`, so dropping 'tool' or 'custom'
177
+ -- from renew's kind list passed the entire battery — named independently by two review seats as
178
+ -- the seventh mutation, and they were right. ORT-8's catalog scan cannot see it either: the helper
179
+ -- reference stays present while two thirds of the class stop being covered.
180
+ --
181
+ -- Both kinds are exercised BEHAVIOURALLY rather than by widening the scan, because the defect is
182
+ -- "this kind is not clamped", which only a clamp observation can catch.
183
+ -- ---------------------------------------------------------------------------
184
+ do $$
185
+ declare k text; tok uuid;
186
+ begin
187
+ perform set_config('request.jwt.claim.role', 'authenticated', true);
188
+ perform set_config('request.jwt.claim.sub', (select auth_user from _ort)::text, true);
189
+ perform agent_board.set_board_claim_policy((select board from _ort), 'inherit');
190
+ foreach k in array array['tool', 'custom'] loop
191
+ select lease_token into tok
192
+ from agent_board.acquire_claim((select agent from _ort), k, 'ort-' || k, now() + interval '1 day', null, (select thread from _ort));
193
+ perform agent_board.renew_claim(tok, now() + interval '400 days');
194
+ end loop;
195
+ end $$;
196
+
197
+ select is(
198
+ (select string_agg(resource_kind || '=' ||
199
+ (lease_expires_at <= now() + agent_board._claimgate_ownerless_ttl_cap() + interval '1 minute')::text, ', ' order by resource_kind)
200
+ from agent_board.claims
201
+ where agent_id = (select agent from _ort) and resource_kind in ('tool', 'custom') and status = 'active'),
202
+ 'custom=true, tool=true',
203
+ 'ORT-9: tool and custom are clamped at renew too — every other assertion drives `file`, so dropping either from the kind list passed the whole battery');
204
+
205
+ -- ---------------------------------------------------------------------------
206
+ -- ORT-10: A CLAIM WITH NO THREAD IS STILL CLAMPED. The renew-side twin of BCP-5: the board opt-out
207
+ -- is resolved from the claim's thread, and a claim with no thread must fall back to `inherit`
208
+ -- rather than escaping the guard by omission. Named by a third seat as its own seventh mutation —
209
+ -- adding `and v_thread_id is not null` to the renew guard would let an agent acquire a within-cap
210
+ -- threadless lease and then renew it to +400 days.
211
+ -- ---------------------------------------------------------------------------
212
+ do $$
213
+ declare tok uuid;
214
+ begin
215
+ select lease_token into tok
216
+ from agent_board.acquire_claim((select agent from _ort), 'file', 'ort-nothread', now() + interval '1 day', null, null);
217
+ perform agent_board.renew_claim(tok, now() + interval '400 days');
218
+ end $$;
219
+
220
+ select ok(
221
+ (select lease_expires_at <= now() + agent_board._claimgate_ownerless_ttl_cap() + interval '1 minute'
222
+ from agent_board.claims where agent_id = (select agent from _ort) and resource_key = 'ort-nothread' and status = 'active'),
223
+ 'ORT-10: a claim with NO thread is still clamped at renew — the board opt-out cannot be obtained by omitting the thread');
224
+
225
+ select * from finish();
226
+ rollback;
@@ -65,6 +65,10 @@
65
65
  "signature": "_check_and_bump_child_mint_rate(p_root_agent_id uuid, p_limit integer, p_window interval)",
66
66
  "declares_p_metadata": false
67
67
  },
68
+ {
69
+ "signature": "_claimgate_active_ownerless_claims(p_agent_id uuid)",
70
+ "declares_p_metadata": false
71
+ },
68
72
  {
69
73
  "signature": "_claimgate_board_claim_policy(p_thread_id uuid)",
70
74
  "declares_p_metadata": false
@@ -161,6 +165,10 @@
161
165
  "signature": "_fleet_onboarding_bounds_immutable()",
162
166
  "declares_p_metadata": false
163
167
  },
168
+ {
169
+ "signature": "_fleet_trust_domain_approval_rank(p_lifecycle_state text)",
170
+ "declares_p_metadata": false
171
+ },
164
172
  {
165
173
  "signature": "_grant_within_ceiling(p_grantor_agent_id uuid, p_board_id uuid, p_grantee_role text)",
166
174
  "declares_p_metadata": false
@@ -473,6 +481,10 @@
473
481
  "signature": "onboard_federated_fleet_agent(p_issuer text, p_audience text, p_subject text, p_human_identity_claim jsonb, p_slug text, p_agent_public_key text, p_agent_public_key_format text, p_agent_public_key_fingerprint text, p_pop_verified boolean, p_actor_sub text, p_agent_kind text, p_requested_scopes text[], p_requested_board_scopes text[], p_invite_code text, p_board_id uuid, p_machine_id uuid, p_harness_kind text, p_harness_label text, p_repo_slug text, p_repo_fingerprint text, p_repo_scope_role text, p_agent_role text, p_expected_agent_handle text)",
474
482
  "declares_p_metadata": false
475
483
  },
484
+ {
485
+ "signature": "ownerless_quota_census()",
486
+ "declares_p_metadata": false
487
+ },
476
488
  {
477
489
  "signature": "post_board_message(p_thread_id uuid, p_author_agent_id uuid, p_board_id uuid, p_subject text, p_body text, p_message_type text, p_priority text, p_idempotency_key text, p_metadata jsonb)",
478
490
  "declares_p_metadata": true