@consiliency/agent-board-schema 1.48.0 → 1.51.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 +16 -0
- package/migrations/20260929000100_agent_board_advisory_revert_cap_reset.sql +371 -0
- package/migrations/20260930000100_agent_board_claim_policy_race.sql +922 -0
- package/migrations/20261001000100_agent_board_claimpolicy_lock_grant_revoke.sql +158 -0
- package/migrations/20261002000100_agent_board_session_challenge_supersede.sql +383 -0
- package/package.json +1 -1
- package/schema-manifest.json +39 -3
- package/src/tooling.test.mjs +33 -2
- package/tests/agent_board_board_claim_policy.test.sql +261 -1
- package/tests/agent_board_claim_subject_gate.test.sql +11 -2
- package/tests/agent_board_claimkey.test.sql +17 -2
- package/tests/agent_board_claimpolicy_race.test.sql +1374 -0
- package/tests/agent_board_fk_lock_edges.test.sql +20 -0
- package/tests/agent_board_ownerless_quota_census.test.sql +6 -0
- package/tests/agent_board_ownerless_renew_quota.test.sql +37 -8
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +391 -36
- package/tests/catalog/p_metadata_parameters.json +16 -0
- package/tests/fixtures/test-prereqs.sql +186 -0
- package/tests/isolation/expected/challenge_supersede_lock_order.out +213 -0
- package/tests/isolation/expected/claimpolicy_acquire_revert_race.out +66 -0
- package/tests/isolation/expected/claimpolicy_board_scope.out +73 -0
- package/tests/isolation/expected/claimpolicy_claimrow_lock_order.out +80 -0
- package/tests/isolation/expected/claimpolicy_isolation_refusal.out +114 -0
- package/tests/isolation/expected/claimpolicy_renew_revert_race.out +66 -0
- package/tests/isolation/expected/claimpolicy_writer_keyshare.out +49 -0
- package/tests/isolation/specs/challenge_supersede_lock_order.spec +183 -0
- package/tests/isolation/specs/claimpolicy_acquire_revert_race.spec +118 -0
- package/tests/isolation/specs/claimpolicy_board_scope.spec +115 -0
- package/tests/isolation/specs/claimpolicy_claimrow_lock_order.spec +200 -0
- package/tests/isolation/specs/claimpolicy_isolation_refusal.spec +162 -0
- package/tests/isolation/specs/claimpolicy_renew_revert_race.spec +150 -0
- package/tests/isolation/specs/claimpolicy_writer_keyshare.spec +107 -0
- package/tests/seeds/20261001000100_agent_board_claimpolicy_lock_grant_revoke.sql.pre.sql +99 -0
package/src/tooling.test.mjs
CHANGED
|
@@ -38,9 +38,9 @@ describe('manifest integrity', () => {
|
|
|
38
38
|
expect(manifest.schemaVersion).toBe(8);
|
|
39
39
|
expect(manifest.minClientVersion).toMatch(/^\d+\.\d+\.\d+$/);
|
|
40
40
|
});
|
|
41
|
-
it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY + one SIGNERLIFECYCLE + one LEDGERAUTHORITY + one CEILINGLOCKCOMMENT + one LEDGERBOARDCREATE + one RECEIPTSUBJECTGATE + one TASKTHREADLOCKORDER + one CLAIMSUBJECTGATE + one FEDSESSIONIDENTITYGATE + one FEDIDENTITYSLUG + one INVITENODOWNGRADE + one SIGNEDWRITEFLAG + one SIGNEDWRITEMODEL + one RTCREDCLAIM + one ORIGINPOSTURE + one BOARDCLAIMPOLICY + one HEARTBEATPRESERVE + one LIVENESSDENOM + one OWNERLESSRENEWTTL + one OWNERLESSQUOTACENSUS + one FLEETDOMAINNODOWNGRADE + one OWNERLESSQUOTACONVERGE forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 =
|
|
41
|
+
it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY + one SIGNERLIFECYCLE + one LEDGERAUTHORITY + one CEILINGLOCKCOMMENT + one LEDGERBOARDCREATE + one RECEIPTSUBJECTGATE + one TASKTHREADLOCKORDER + one CLAIMSUBJECTGATE + one FEDSESSIONIDENTITYGATE + one FEDIDENTITYSLUG + one INVITENODOWNGRADE + one SIGNEDWRITEFLAG + one SIGNEDWRITEMODEL + one RTCREDCLAIM + one ORIGINPOSTURE + one BOARDCLAIMPOLICY + one HEARTBEATPRESERVE + one LIVENESSDENOM + one OWNERLESSRENEWTTL + one OWNERLESSQUOTACENSUS + one FLEETDOMAINNODOWNGRADE + one OWNERLESSQUOTACONVERGE + one ADVISORYREVERTRESET + one CLAIMPOLICYRACE + one CLAIMPOLICYLOCKGRANT + one SESSIONCHALLENGESUPERSEDE forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 150)', () => {
|
|
42
42
|
expect(manifest.migrationCount).toBe(manifest.migrations.length);
|
|
43
|
-
expect(manifest.migrationCount).toBe(
|
|
43
|
+
expect(manifest.migrationCount).toBe(150);
|
|
44
44
|
// MBSESSION switched enforcement on (scoped-send re-grant): the client-visible
|
|
45
45
|
// schema version bumped to 8. MBDELEG tightens delegation and MBFED adds only
|
|
46
46
|
// enrollment-only additive surface (identity_providers registry, enrollment audit,
|
|
@@ -304,6 +304,37 @@ describe('manifest integrity', () => {
|
|
|
304
304
|
// ceiling is untouched. acquire's inline count is extracted into
|
|
305
305
|
// _claimgate_active_ownerless_claims() so the two enforcement paths cannot drift.
|
|
306
306
|
'20260928000100_agent_board_ownerless_quota_renew_convergence.sql',
|
|
307
|
+
// #400 ADVISORYREVERTRESET. claim_policy='advisory' skips both the ownerless TTL cap and
|
|
308
|
+
// #388's convergence clamp, and set_board_claim_policy is gated on the owner membership
|
|
309
|
+
// create_board seeds for the creator -- so an agent that creates a board can flip to
|
|
310
|
+
// advisory, renew its ownerless claims to an arbitrary horizon, flip back to inherit, and
|
|
311
|
+
// repeat, staying over quota forever while the census (which resolves policy at query time)
|
|
312
|
+
// reports no advisory share at all. The revert now RE-APPLIES the ownerless TTL cap to that
|
|
313
|
+
// board's active ownerless claims -- least(lease_expires_at, now()+cap), clamp only, never an
|
|
314
|
+
// extension -- so a horizon banked under the opt-out cannot be kept by handing the opt-out
|
|
315
|
+
// back. advisory->inherit only, governed by ownerless_guards. Bounded, not closed: a cycle
|
|
316
|
+
// still banks up to the cap.
|
|
317
|
+
'20260929000100_agent_board_advisory_revert_cap_reset.sql',
|
|
318
|
+
// #405 CLAIMPOLICYRACE. _claimgate_board_claim_policy took no lock on boards, so any
|
|
319
|
+
// operation whose snapshot predated a committed policy change acted on policy that was
|
|
320
|
+
// already gone -- renew_claim (read stale), acquire_claim (stale AND no row for #406's
|
|
321
|
+
// reset to clamp), and set_board_claim_policy itself (readers serialised on nothing).
|
|
322
|
+
// All three now take a board-scoped advisory xact lock BEFORE the policy decision --
|
|
323
|
+
// including acquire's 'advisory' path, which skips the block the agent-scoped lock lives
|
|
324
|
+
// in -- and re-read the policy as a separate statement; renew re-reads the whole CLAIM,
|
|
325
|
+
// because #400's reset clamps the very row whose pre-wait expiry is the convergence
|
|
326
|
+
// baseline. The lock helper REFUSES any isolation level but read committed, where the
|
|
327
|
+
// wait would not advance the snapshot and the lock would be silently inert.
|
|
328
|
+
'20260930000100_agent_board_claim_policy_race.sql',
|
|
329
|
+
'20261001000100_agent_board_claimpolicy_lock_grant_revoke.sql',
|
|
330
|
+
// F1: the per-key pending-challenge cap SUPERSEDES the oldest pending challenge
|
|
331
|
+
// instead of raising challenge_pending_cap. Refusing at the cap turned a resource
|
|
332
|
+
// bound into a lockout lever on the unauthenticated /session surface -- sixteen
|
|
333
|
+
// requests naming a directory-visible handle and key id denied that key every
|
|
334
|
+
// further challenge, and the client stopped its refresh loop on the 403. The bound
|
|
335
|
+
// is unchanged; mint_fleet_agent_session now names an eviction challenge_superseded
|
|
336
|
+
// so it is retryable without making nonce_replay retryable with it.
|
|
337
|
+
'20261002000100_agent_board_session_challenge_supersede.sql',
|
|
307
338
|
]) {
|
|
308
339
|
expect(names.has(f)).toBe(true);
|
|
309
340
|
}
|
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
-- the kind of reasoning that stops being true when someone moves a line, and this file would
|
|
18
18
|
-- otherwise be a set of tests that only ever prove the permissive direction.
|
|
19
19
|
--
|
|
20
|
+
-- BCP-11..21 ARE #400: THE ADVISORY-REVERT CAP RESET. The opt-out is a TOGGLE, and that made it an
|
|
21
|
+
-- escape from every ownerless bound: flip to advisory, take an arbitrary horizon, flip back, repeat
|
|
22
|
+
-- — self-service, because create_board seeds the creator's owner membership and this RPC is gated
|
|
23
|
+
-- on exactly that. Reverting to inherit now re-applies the ownerless TTL cap to the board's active
|
|
24
|
+
-- ownerless claims, clamp only. The section pins all four transitions, both bounds (kind and board),
|
|
25
|
+
-- the operator flag, and the catalog class check that this RPC is the only writer of the column.
|
|
26
|
+
--
|
|
20
27
|
-- BCP-5 IS THE SECOND ONE THAT MATTERS. A claim reaches its board through its thread. So the
|
|
21
28
|
-- obvious hole is a claim that simply omits the thread — if that inherited "no board, therefore no
|
|
22
29
|
-- enforcement", any agent could opt itself out by passing null. It resolves to 'inherit' instead,
|
|
@@ -26,7 +33,7 @@ set search_path = extensions, public, agent_board;
|
|
|
26
33
|
|
|
27
34
|
begin;
|
|
28
35
|
|
|
29
|
-
select plan(
|
|
36
|
+
select plan(30);
|
|
30
37
|
|
|
31
38
|
-- ---------------------------------------------------------------------------
|
|
32
39
|
-- Fixture: one board with an owner and a non-owner member, a board-linked thread, and a
|
|
@@ -193,6 +200,259 @@ select throws_ok(
|
|
|
193
200
|
null,
|
|
194
201
|
'BCP-9: an unknown policy is refused as a bad argument, naming the vocabulary — not as a raw CHECK violation');
|
|
195
202
|
|
|
203
|
+
-- ===========================================================================
|
|
204
|
+
-- BCP-11..21 (#400): THE ADVISORY-REVERT CAP RESET.
|
|
205
|
+
--
|
|
206
|
+
-- THE DEFECT. The advisory carve-out is resolved at ENFORCEMENT time against the board's CURRENT
|
|
207
|
+
-- policy, so an owner could flip to 'advisory', let an ownerless claim take an arbitrary horizon
|
|
208
|
+
-- (the carve-out skips both the #373 TTL cap and #388's convergence clamp), flip back to 'inherit',
|
|
209
|
+
-- and repeat before expiry. `create_board` seeds the creator's owner membership and this RPC is
|
|
210
|
+
-- gated on exactly that, so an agent that created a board holds the switch that exempts its own
|
|
211
|
+
-- claims: no accomplice. And #393's census resolves policy at QUERY time (OQ-29), so after the
|
|
212
|
+
-- revert the escaped claim contributes zero to `claims_on_advisory_boards` while its overage still
|
|
213
|
+
-- counts.
|
|
214
|
+
--
|
|
215
|
+
-- THE FIX. Reverting to 'inherit' means accepting inherit's rules: the ownerless TTL cap is
|
|
216
|
+
-- re-applied to this board's active ownerless claims, clamp only. The opt-out may be given back —
|
|
217
|
+
-- but not after banking a lease longer than inherit allows.
|
|
218
|
+
--
|
|
219
|
+
-- BOUNDED, NOT CLOSED, and this file does not pretend otherwise: each advisory window still banks
|
|
220
|
+
-- up to the cap, so an owner flipping every 29 days still defeats convergence. What is gone is the
|
|
221
|
+
-- UNBOUNDED horizon. ORQ-11 pins that bound end-to-end through the real RPCs.
|
|
222
|
+
--
|
|
223
|
+
-- NUMBERED 11+ AND PLACED HERE, BEFORE BCP-10, because BCP-10 fleet-revokes the owner and every
|
|
224
|
+
-- assertion below needs that owner to be able to call this RPC. Renumbering BCP-10 would break its
|
|
225
|
+
-- citations for no gain.
|
|
226
|
+
-- ===========================================================================
|
|
227
|
+
do $$
|
|
228
|
+
declare
|
|
229
|
+
v_b uuid := gen_random_uuid();
|
|
230
|
+
v_t uuid := gen_random_uuid();
|
|
231
|
+
begin
|
|
232
|
+
-- Session: the owner. BCP-7/8/9 set their GUCs INSIDE throws_ok, whose exception block rolls
|
|
233
|
+
-- them back with the raise, so the state here is the fixture's — but inferring that is exactly
|
|
234
|
+
-- the reading rule 4 exists for, so it is set explicitly instead.
|
|
235
|
+
perform set_config('request.jwt.claim.role', 'authenticated', true);
|
|
236
|
+
perform set_config('request.jwt.claim.sub', (select owner_user from _bcp)::text, true);
|
|
237
|
+
|
|
238
|
+
-- A SECOND BOARD, and it stays 'advisory' for the whole section. It is the scope witness for
|
|
239
|
+
-- BCP-14: reverting board A must not reach a board that never reverted. Advisory is also the
|
|
240
|
+
-- realistic shape — a board still coordinating through its own mutex, legitimately holding a
|
|
241
|
+
-- lease longer than our cap.
|
|
242
|
+
insert into agent_board.boards(id, slug, name, created_by_agent_id, claim_policy)
|
|
243
|
+
values (v_b, 'bcp400-' || substr(v_b::text, 1, 8), 'BCP 400 Other Board', (select owner_agent from _bcp), 'advisory');
|
|
244
|
+
insert into agent_board.threads(id, thread_type, created_by_agent_id, subject, board_id)
|
|
245
|
+
values (v_t, 'coordination', (select owner_agent from _bcp), 'BCP 400 other-board thread', v_b);
|
|
246
|
+
|
|
247
|
+
create temp table _bcp400 (other_board uuid, other_thread uuid) on commit drop;
|
|
248
|
+
insert into _bcp400 values (v_b, v_t);
|
|
249
|
+
end $$;
|
|
250
|
+
|
|
251
|
+
select lives_ok(
|
|
252
|
+
$$ select agent_board.set_board_claim_policy((select board from _bcp), 'advisory') $$,
|
|
253
|
+
'BCP-11a: the owner opts the board out again — this is the flip an over-quota holder makes to buy an unbounded horizon');
|
|
254
|
+
|
|
255
|
+
-- The claims the advisory window buys. Inserted directly rather than acquired, because three of
|
|
256
|
+
-- them are shapes acquire cannot produce in one call (a 'machine' kind needs a fleet fixture, one
|
|
257
|
+
-- belongs to another board, one has no thread) — but they are all shapes the DATABASE can hold:
|
|
258
|
+
-- an over-cap ownerless lease exists exactly where an advisory board, a grandfathered pre-flag
|
|
259
|
+
-- claim, or a service_role extension put it. BCP-11 scores a fourth claim that WAS acquired
|
|
260
|
+
-- through the real RPC under advisory, so the property is not proven only on synthetic rows.
|
|
261
|
+
insert into agent_board.claims(agent_id, thread_id, resource_kind, resource_key, lease_expires_at)
|
|
262
|
+
values
|
|
263
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'file', 'bcp400-a-file', now() + interval '400 days'),
|
|
264
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'tool', 'bcp400-a-tool', now() + interval '400 days'),
|
|
265
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'custom', 'bcp400-a-custom', now() + interval '400 days'),
|
|
266
|
+
-- an OWNED kind on the same board: governed by the fleet-authority gate, never by this cap
|
|
267
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'machine', 'bcp400-a-machine', now() + interval '400 days'),
|
|
268
|
+
-- already WITHIN the cap: the twin. A reset that assigned now()+cap would LENGTHEN this one
|
|
269
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'file', 'bcp400-a-under', now() + interval '5 days'),
|
|
270
|
+
-- another board, still advisory
|
|
271
|
+
((select owner_agent from _bcp), (select other_thread from _bcp400), 'file', 'bcp400-b-file', now() + interval '400 days'),
|
|
272
|
+
-- no thread at all, so no board to revert
|
|
273
|
+
((select owner_agent from _bcp), null, 'file', 'bcp400-free-file', now() + interval '400 days');
|
|
274
|
+
|
|
275
|
+
-- Two DEAD rows on the reverting board, for BCP-21. They are inserted separately because they need
|
|
276
|
+
-- columns the list above does not set.
|
|
277
|
+
insert into agent_board.claims(agent_id, thread_id, resource_kind, resource_key, status, released_at, lease_expires_at, updated_at)
|
|
278
|
+
values
|
|
279
|
+
-- released: least() would still shorten it, so this conjunct IS observable through the lease
|
|
280
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'file', 'bcp400-a-released', 'released', now(), now() + interval '400 days', now() - interval '3 days'),
|
|
281
|
+
-- active but LAPSED: least(now()-1h, now()+cap) is the lease it already has, so this conjunct is
|
|
282
|
+
-- observable ONLY through updated_at — which is why the row carries a distinctive one
|
|
283
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'file', 'bcp400-a-lapsed', 'active', null, now() - interval '1 hour', now() - interval '3 days');
|
|
284
|
+
|
|
285
|
+
select lives_ok(
|
|
286
|
+
$$ select agent_board.set_board_claim_policy((select board from _bcp), 'inherit') $$,
|
|
287
|
+
'BCP-11b: and hands the opt-out back — the step that used to cost the escaping agent nothing');
|
|
288
|
+
|
|
289
|
+
-- ---------------------------------------------------------------------------
|
|
290
|
+
-- BCP-11: THE POINT. Every active ownerless claim on the reverting board is clamped to the TTL cap.
|
|
291
|
+
-- All three ownerless kinds are scored: every other assertion here drives `file`, and dropping
|
|
292
|
+
-- 'tool' or 'custom' from the kind list is the mutation that passed ORT's entire battery (ORT-9).
|
|
293
|
+
-- `rpc-acquired` is 'bcp-over-cap-advisory' from BCP-3b — taken through acquire_claim under
|
|
294
|
+
-- advisory and clamped by this file's own earlier revert, so the property is pinned on a row a
|
|
295
|
+
-- deployment can actually produce and not only on inserted fixtures.
|
|
296
|
+
-- ---------------------------------------------------------------------------
|
|
297
|
+
select is(
|
|
298
|
+
(select string_agg(l.label || '=' ||
|
|
299
|
+
(c.lease_expires_at = now() + agent_board._claimgate_ownerless_ttl_cap())::text, ', ' order by l.label)
|
|
300
|
+
from agent_board.claims c
|
|
301
|
+
join (values ('bcp400-a-file', 'file'),
|
|
302
|
+
('bcp400-a-tool', 'tool'),
|
|
303
|
+
('bcp400-a-custom', 'custom'),
|
|
304
|
+
('bcp-over-cap-advisory', 'rpc-acquired')) as l(key, label) on l.key = c.resource_key),
|
|
305
|
+
'custom=true, file=true, rpc-acquired=true, tool=true',
|
|
306
|
+
'BCP-11 THE POINT: reverting advisory -> inherit re-applies the ownerless TTL cap to this board''s active claims, all three kinds — so a horizon banked under the opt-out cannot be kept by handing the opt-out back');
|
|
307
|
+
|
|
308
|
+
-- ---------------------------------------------------------------------------
|
|
309
|
+
-- BCP-12: THE TWIN, and the reason the clamp is least(). A claim already within the cap must come
|
|
310
|
+
-- back untouched: an assignment to now()+cap would satisfy BCP-11 and silently LENGTHEN this lease
|
|
311
|
+
-- from five days to thirty. Asserts the lease only — the row's updated_at IS bumped, which is the
|
|
312
|
+
-- documented cost of least() being the single mutation-visible clamp (see the migration header).
|
|
313
|
+
-- ---------------------------------------------------------------------------
|
|
314
|
+
select is(
|
|
315
|
+
(select lease_expires_at from agent_board.claims where resource_key = 'bcp400-a-under'),
|
|
316
|
+
now() + interval '5 days',
|
|
317
|
+
'BCP-12 THE TWIN: a lease already within the cap is preserved to the microsecond — the reset is least(held, now()+cap), not a rewrite to now()+cap, which would hand back time the claim never had');
|
|
318
|
+
|
|
319
|
+
-- ---------------------------------------------------------------------------
|
|
320
|
+
-- BCP-13: the kind bound. 'machine' is an OWNED kind, governed by the fleet-authority gate and by
|
|
321
|
+
-- no ownerless cap at all, so the reset must not touch it — a reset that clamped every claim on the
|
|
322
|
+
-- board would satisfy BCP-11 and quietly shorten fleet leases.
|
|
323
|
+
-- ---------------------------------------------------------------------------
|
|
324
|
+
select is(
|
|
325
|
+
(select lease_expires_at from agent_board.claims where resource_key = 'bcp400-a-machine'),
|
|
326
|
+
now() + interval '400 days',
|
|
327
|
+
'BCP-13: a machine claim on the reverting board is untouched — the reset is scoped to the ownerless kinds, which are the only ones the TTL cap ever governed');
|
|
328
|
+
|
|
329
|
+
-- ---------------------------------------------------------------------------
|
|
330
|
+
-- BCP-14: the board bound. This board's revert is a statement about THIS board. A claim on another
|
|
331
|
+
-- board — still advisory, legitimately holding a long lease — and a claim with no thread at all
|
|
332
|
+
-- must both survive it. Dropping the board join would clamp every ownerless claim in the fleet on
|
|
333
|
+
-- any revert anywhere, and every assertion above would still pass.
|
|
334
|
+
-- ---------------------------------------------------------------------------
|
|
335
|
+
select is(
|
|
336
|
+
(select string_agg(l.label || '=' || (c.lease_expires_at = now() + interval '400 days')::text, ', ' order by l.label)
|
|
337
|
+
from agent_board.claims c
|
|
338
|
+
join (values ('bcp400-b-file', 'other-board'),
|
|
339
|
+
('bcp400-free-file', 'no-thread')) as l(key, label) on l.key = c.resource_key),
|
|
340
|
+
'no-thread=true, other-board=true',
|
|
341
|
+
'BCP-14 SCOPE: a claim on a DIFFERENT board and a claim with no thread are both untouched by this board''s revert — the reset resolves through the claim''s thread to this board, the same path the policy resolver uses');
|
|
342
|
+
|
|
343
|
+
-- ---------------------------------------------------------------------------
|
|
344
|
+
-- BCP-21: THE DEAD ROWS. The reset is scoped to ACTIVE, unreleased, unexpired claims — the same
|
|
345
|
+
-- three conjuncts the shared count predicate uses — so a revert does not rewrite history.
|
|
346
|
+
--
|
|
347
|
+
-- The two halves are scored differently on purpose. A RELEASED row still holds a future timestamp,
|
|
348
|
+
-- so dropping `status = 'active' and released_at is null` shortens it and the LEASE moves. A LAPSED
|
|
349
|
+
-- row's lease is already in the past, and least(past, now()+cap) is that same past value — so
|
|
350
|
+
-- dropping `lease_expires_at > now()` is invisible in the lease and visible ONLY as a touched row.
|
|
351
|
+
-- Both rows therefore carry a distinctive updated_at, and this assertion reads it.
|
|
352
|
+
-- ---------------------------------------------------------------------------
|
|
353
|
+
select is(
|
|
354
|
+
(select string_agg(l.label || '=' ||
|
|
355
|
+
(c.lease_expires_at = l.expected and c.updated_at = now() - interval '3 days')::text, ', ' order by l.label)
|
|
356
|
+
from agent_board.claims c
|
|
357
|
+
join (values ('bcp400-a-released', 'released', now() + interval '400 days'),
|
|
358
|
+
('bcp400-a-lapsed', 'lapsed', now() - interval '1 hour')) as l(key, label, expected) on l.key = c.resource_key),
|
|
359
|
+
'lapsed=true, released=true',
|
|
360
|
+
'BCP-21: a RELEASED claim and a LAPSED one on the reverting board are not touched at all — same lease, same updated_at. The reset governs live leases, not the ledger of dead ones');
|
|
361
|
+
|
|
362
|
+
-- ---------------------------------------------------------------------------
|
|
363
|
+
-- BCP-15..17: THE TRANSITION CONDITION, all three non-triggering transitions. The rows below are
|
|
364
|
+
-- inserted once, while the board is 'inherit', and each is read after its own transition.
|
|
365
|
+
--
|
|
366
|
+
-- BCP-17 (advisory -> advisory) is the one the other two cannot see: dropping the `= 'inherit'`
|
|
367
|
+
-- conjunct leaves "was previously advisory", which still clamps nothing on either inherit-first
|
|
368
|
+
-- case. Without it that mutation survives BCP-15 and BCP-16 untouched.
|
|
369
|
+
-- ---------------------------------------------------------------------------
|
|
370
|
+
insert into agent_board.claims(agent_id, thread_id, resource_kind, resource_key, lease_expires_at)
|
|
371
|
+
values
|
|
372
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'file', 'bcp400-noop-ii', now() + interval '400 days'),
|
|
373
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'file', 'bcp400-noop-ia', now() + interval '400 days'),
|
|
374
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'file', 'bcp400-noop-aa', now() + interval '400 days'),
|
|
375
|
+
((select owner_agent from _bcp), (select thread from _bcp), 'file', 'bcp400-flag-off', now() + interval '400 days');
|
|
376
|
+
|
|
377
|
+
select lives_ok(
|
|
378
|
+
$$ select agent_board.set_board_claim_policy((select board from _bcp), 'inherit') $$,
|
|
379
|
+
'BCP-15a: the owner re-sets inherit on a board that is already inherit');
|
|
380
|
+
|
|
381
|
+
select is(
|
|
382
|
+
(select lease_expires_at from agent_board.claims where resource_key = 'bcp400-noop-ii'),
|
|
383
|
+
now() + interval '400 days',
|
|
384
|
+
'BCP-15: inherit -> inherit clamps NOTHING — a no-op re-set is not a policy change, and treating it as one would let any owner shorten their own board''s leases by calling the RPC twice');
|
|
385
|
+
|
|
386
|
+
select lives_ok(
|
|
387
|
+
$$ select agent_board.set_board_claim_policy((select board from _bcp), 'advisory') $$,
|
|
388
|
+
'BCP-16a: the owner opts out');
|
|
389
|
+
|
|
390
|
+
select is(
|
|
391
|
+
(select lease_expires_at from agent_board.claims where resource_key = 'bcp400-noop-ia'),
|
|
392
|
+
now() + interval '400 days',
|
|
393
|
+
'BCP-16: inherit -> advisory clamps nothing — the reset belongs to the REVERT, and clamping while opting OUT would be the opposite of what the opt-out promises');
|
|
394
|
+
|
|
395
|
+
select lives_ok(
|
|
396
|
+
$$ select agent_board.set_board_claim_policy((select board from _bcp), 'advisory') $$,
|
|
397
|
+
'BCP-17a: and re-sets advisory on a board that is already advisory');
|
|
398
|
+
|
|
399
|
+
select is(
|
|
400
|
+
(select lease_expires_at from agent_board.claims where resource_key = 'bcp400-noop-aa'),
|
|
401
|
+
now() + interval '400 days',
|
|
402
|
+
'BCP-17: advisory -> advisory clamps nothing — the discriminating case, because a guard that asked only "was it advisory?" passes BCP-15 and BCP-16 and would clamp a board that never came back');
|
|
403
|
+
|
|
404
|
+
-- ---------------------------------------------------------------------------
|
|
405
|
+
-- BCP-18: ONE OPERATOR FLAG. With ownerless_guards off, nothing enforces the TTL cap at acquire or
|
|
406
|
+
-- at renew, so a revert must not enforce it either — the flag means the same thing on all three
|
|
407
|
+
-- write paths or it means nothing.
|
|
408
|
+
-- ---------------------------------------------------------------------------
|
|
409
|
+
do $$
|
|
410
|
+
begin
|
|
411
|
+
update agent_board.claimgate_enforcement_flags set enabled = false where scope_key = 'ownerless_guards';
|
|
412
|
+
end $$;
|
|
413
|
+
|
|
414
|
+
select lives_ok(
|
|
415
|
+
$$ select agent_board.set_board_claim_policy((select board from _bcp), 'inherit') $$,
|
|
416
|
+
'BCP-18a: with the guards off the owner reverts to inherit');
|
|
417
|
+
|
|
418
|
+
select is(
|
|
419
|
+
(select lease_expires_at from agent_board.claims where resource_key = 'bcp400-flag-off'),
|
|
420
|
+
now() + interval '400 days',
|
|
421
|
+
'BCP-18: with ownerless_guards OFF an advisory -> inherit revert clamps nothing — one flag governs the cap at acquire, at renew and at the revert alike');
|
|
422
|
+
|
|
423
|
+
do $$
|
|
424
|
+
begin
|
|
425
|
+
update agent_board.claimgate_enforcement_flags set enabled = true where scope_key = 'ownerless_guards';
|
|
426
|
+
end $$;
|
|
427
|
+
|
|
428
|
+
-- ---------------------------------------------------------------------------
|
|
429
|
+
-- BCP-19/20: CLASS (rule 1). The hook can only close the escape if this RPC is the ONLY way
|
|
430
|
+
-- boards.claim_policy changes. Derived from the catalog, not from a list: a second writer names
|
|
431
|
+
-- itself here rather than silently inheriting a policy change that resets nothing.
|
|
432
|
+
--
|
|
433
|
+
-- Two independent bounds, because neither is sufficient alone. BCP-19 is a source scan and a source
|
|
434
|
+
-- scan has a floor — it sees `set claim_policy` and not, say, a dynamic EXECUTE. BCP-20 is the
|
|
435
|
+
-- privilege bound underneath it: NO role holds UPDATE on agent_board.boards, so the column cannot
|
|
436
|
+
-- be written from a session at all, only from a SECURITY DEFINER function (which BCP-19 enumerates)
|
|
437
|
+
-- or by the table owner. `boards` also carries no user triggers, so there is no third path.
|
|
438
|
+
-- ---------------------------------------------------------------------------
|
|
439
|
+
select is(
|
|
440
|
+
(select coalesce(string_agg(p.proname, ', ' order by p.proname), '')
|
|
441
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
442
|
+
where n.nspname = 'agent_board'
|
|
443
|
+
and agent_board_test.stripped_body(p.oid, true) ~ 'set\s+claim_policy'),
|
|
444
|
+
'set_board_claim_policy',
|
|
445
|
+
'BCP-19 CLASS: set_board_claim_policy is the ONLY function that writes boards.claim_policy — a second writer would need the same advisory-revert reset and fails here instead of being remembered');
|
|
446
|
+
|
|
447
|
+
select is(
|
|
448
|
+
format('anon=%s, authenticated=%s, service_role=%s, user_triggers=%s',
|
|
449
|
+
has_table_privilege('anon', 'agent_board.boards', 'update')::text,
|
|
450
|
+
has_table_privilege('authenticated', 'agent_board.boards', 'update')::text,
|
|
451
|
+
has_table_privilege('service_role', 'agent_board.boards', 'update')::text,
|
|
452
|
+
(select count(*) from pg_trigger where tgrelid = 'agent_board.boards'::regclass and not tgisinternal)::text),
|
|
453
|
+
'anon=false, authenticated=false, service_role=false, user_triggers=0',
|
|
454
|
+
'BCP-20 CLASS: no role holds UPDATE on agent_board.boards and the table carries no user triggers — so the column is unreachable except through the definer function BCP-19 enumerates, which is the bound a source scan cannot give');
|
|
455
|
+
|
|
196
456
|
-- ---------------------------------------------------------------------------
|
|
197
457
|
-- BCP-10: A FLEET-REVOKED OWNER IS STILL AN OWNER BY MEMBERSHIP. This function shipped its first
|
|
198
458
|
-- version WITHOUT this check while its own commit message claimed to mirror set_board_role_gate
|
|
@@ -850,14 +850,23 @@ select is(
|
|
|
850
850
|
-- sides: the catalog function scan, and the effective-privilege assertions after it (no role
|
|
851
851
|
-- holds a write privilege by ANY route, so a direct write is impossible and the SECURITY
|
|
852
852
|
-- DEFINER bodies are the whole surface).
|
|
853
|
+
--
|
|
854
|
+
-- set_board_claim_policy (#400) is the FIFTH member, and it named itself here rather than being
|
|
855
|
+
-- remembered -- which is what this assertion is for. It creates no claims row and re-keys nothing:
|
|
856
|
+
-- its only claims write is the advisory -> inherit cap reset, `set lease_expires_at =
|
|
857
|
+
-- least(lease_expires_at, now() + cap), updated_at = now()`, on the reverting board's active
|
|
858
|
+
-- ownerless claims. That EXEMPTION IS ASSERTED POSITIVELY, not by absence: BCP-11/13/14 look those
|
|
859
|
+
-- claims up BY resource_key and score the resulting timestamps, so a reset that re-keyed or
|
|
860
|
+
-- inserted a row fails them by name, and assertion 51 below (every function that CREATES a claims
|
|
861
|
+
-- row refuses without thread access) still holds because this one creates none.
|
|
853
862
|
select is(
|
|
854
863
|
(select coalesce(string_agg(p.proname, ',' order by p.proname), '(none)')
|
|
855
864
|
from pg_proc p
|
|
856
865
|
where p.pronamespace = 'agent_board'::regnamespace
|
|
857
866
|
and lower(agent_board_test.stripped_body(p.oid, true))
|
|
858
867
|
~ '(insert\s+into|merge\s+into|update|delete\s+from|truncate)(\s+table)?(\s+only)?\s+(("?agent_board"?\s*\.\s*)?claims\M|("?agent_board"?\s*\.\s*)?"claims")'),
|
|
859
|
-
'acquire_claim,reclaim_fleet_machine,release_claim,renew_claim',
|
|
860
|
-
'catalog: these
|
|
868
|
+
'acquire_claim,reclaim_fleet_machine,release_claim,renew_claim,set_board_claim_policy',
|
|
869
|
+
'catalog: these five are the ONLY agent_board functions that write claims (insert/merge/update/delete/TRUNCATE) -- a new writer names itself here');
|
|
861
870
|
|
|
862
871
|
select is(
|
|
863
872
|
(select coalesce(string_agg(p.proname, ',' order by p.proname), '(none)')
|
|
@@ -211,8 +211,14 @@ select set_eq(
|
|
|
211
211
|
-- active machine claim(s) on the reclaimed fingerprint so the new owner can acquire. It routes the
|
|
212
212
|
-- key through _canonical_claim_key (assertion 25 stays 0); its positive state claim (status set to
|
|
213
213
|
-- 'released') is pinned in agent_board_fleet_machine_reclaim.test.sql.
|
|
214
|
-
|
|
215
|
-
'
|
|
214
|
+
-- set_board_claim_policy (#400) is a fifth writer: an advisory -> inherit revert re-applies the
|
|
215
|
+
-- ownerless TTL cap to that board's active ownerless claims. It is EXEMPT FROM ASSERTION 25 by a
|
|
216
|
+
-- DERIVED property and not by name -- its claims UPDATE sets only lease_expires_at/updated_at and
|
|
217
|
+
-- its body never mentions resource_key at all, so there is no key for the normalizer to canonicalize.
|
|
218
|
+
-- The exemption's positive claim (the reset moves lease timestamps and nothing else) is scored in
|
|
219
|
+
-- agent_board_board_claim_policy.test.sql BCP-11/13/14, which look the claims up BY resource_key.
|
|
220
|
+
array['acquire_claim', 'renew_claim', 'release_claim', 'reclaim_fleet_machine', 'set_board_claim_policy'],
|
|
221
|
+
'exactly acquire/renew/release/reclaim/set_board_claim_policy write agent_board.claims (catalog-enumerated writer class)');
|
|
216
222
|
select is(
|
|
217
223
|
(select count(*)::int
|
|
218
224
|
from pg_proc p
|
|
@@ -220,6 +226,15 @@ select is(
|
|
|
220
226
|
where n.nspname = 'agent_board'
|
|
221
227
|
and p.prokind = 'f'
|
|
222
228
|
and agent_board_test.stripped_body(p.oid) ~* '(insert\s+into|update)\s+(agent_board\.)?claims\M'
|
|
229
|
+
-- #400: SCOPED TO WRITERS THAT HAVE A KEY TO NORMALIZE. A claims writer whose body never
|
|
230
|
+
-- mentions resource_key cannot set one -- an UPDATE must name the column in its SET list and
|
|
231
|
+
-- an INSERT must name it in its column list (resource_key is NOT NULL) -- so requiring it to
|
|
232
|
+
-- call the normalizer would be dead code added to satisfy a check, which is the anti-pattern
|
|
233
|
+
-- rule 2 names. DERIVED, not a name exemption: a new writer that touches resource_key at all
|
|
234
|
+
-- is still in the population and still fails closed. STATED HOLE: `insert into claims select *
|
|
235
|
+
-- from ...` names no column and would slip past, which is the same shape assertion 24's regex
|
|
236
|
+
-- already cannot see.
|
|
237
|
+
and agent_board_test.stripped_body(p.oid) ~* '\mresource_key\M'
|
|
223
238
|
-- message-board#196 Bucket B: read RAW, one comment naming _canonical_claim_key
|
|
224
239
|
-- dropped a bypass writer out of this set and the count went to 0 green.
|
|
225
240
|
-- anchored to the CALL shape: a bare-substring `!~` is satisfied by any
|