@consiliency/agent-board-schema 1.59.0 → 1.63.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/README.md +80 -0
- package/fixtures/already-applied.json +16 -0
- package/migrations/20261010000100_agent_board_own_post_not_delivered.sql +637 -0
- package/migrations/20261011000100_agent_board_session_identities.sql +1964 -0
- package/migrations/20261012000100_agent_board_sidowner.sql +209 -0
- package/migrations/20261014000100_agent_board_grant_target_role.sql +218 -0
- package/package.json +1 -1
- package/schema-manifest.json +39 -3
- package/src/tooling.test.mjs +9 -2
- package/tests/agent_board_addressing.test.sql +3 -1
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -4
- package/tests/agent_board_consent_emission.test.sql +30 -11
- package/tests/agent_board_consent_gating.test.sql +16 -2
- package/tests/agent_board_delegate_role_class.test.sql +1 -1
- package/tests/agent_board_delegation_cap_ceiling.test.sql +26 -15
- package/tests/agent_board_delegation_enforcement.test.sql +1 -1
- package/tests/agent_board_enroll_resumption.test.sql +22 -4
- package/tests/agent_board_enroll_token_single_use.test.sql +18 -3
- package/tests/agent_board_federated_lock_order_class.test.sql +2 -2
- package/tests/agent_board_fk_lock_edges.test.sql +74 -14
- package/tests/agent_board_grant_target_role.test.sql +1118 -0
- package/tests/agent_board_inbox_rpcs.test.sql +5 -2
- package/tests/agent_board_invite_no_downgrade.test.sql +10 -8
- package/tests/agent_board_ledger_authority.test.sql +70 -12
- package/tests/agent_board_membership_current_row_class.test.sql +9 -1
- package/tests/agent_board_mint_fleet_agent_session.test.sql +2 -2
- package/tests/agent_board_mint_harden.test.sql +38 -27
- package/tests/agent_board_own_post_not_delivered.test.sql +490 -0
- package/tests/agent_board_rolereset.test.sql +8 -2
- package/tests/agent_board_roomdeleg_lane_b.test.sql +6 -6
- package/tests/agent_board_rowid.test.sql +3 -1
- package/tests/agent_board_session_identity_approval_revocation.test.sql +334 -0
- package/tests/agent_board_session_identity_backfill.test.sql +82 -0
- package/tests/agent_board_session_identity_backfill_refusal.test.sql +14 -0
- package/tests/agent_board_session_identity_behavior_classes.test.sql +176 -0
- package/tests/agent_board_session_identity_class.test.sql +118 -0
- package/tests/agent_board_session_identity_fingerprint.test.sql +271 -0
- package/tests/agent_board_session_identity_labels_limits.test.sql +205 -0
- package/tests/agent_board_session_identity_lease.test.sql +235 -0
- package/tests/agent_board_session_identity_lifetime.test.sql +230 -0
- package/tests/agent_board_session_identity_limits.test.sql +208 -0
- package/tests/agent_board_session_identity_lineage.test.sql +64 -0
- package/tests/agent_board_session_identity_link.test.sql +201 -0
- package/tests/agent_board_session_identity_mint.test.sql +233 -0
- package/tests/agent_board_session_identity_panel_r1.test.sql +194 -0
- package/tests/agent_board_session_identity_panel_r2.test.sql +174 -0
- package/tests/agent_board_session_identity_protected_actions.test.sql +156 -0
- package/tests/agent_board_session_identity_revoke_closure.test.sql +131 -0
- package/tests/agent_board_sidowner_leases.test.sql +107 -0
- package/tests/agent_board_sidowner_owner_required.test.sql +97 -0
- package/tests/agent_board_sidowner_read_class.test.sql +171 -0
- package/tests/agent_board_sidowner_reads.test.sql +136 -0
- package/tests/agent_board_signer_lifecycle.test.sql +26 -4
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +1 -1
- package/tests/agent_board_v2_invariants.test.sql +4 -4
- package/tests/agent_board_write_enforcement_class.test.sql +74 -15
- package/tests/catalog/function_execute_privileges.json +218 -2
- package/tests/catalog/p_metadata_parameters.json +98 -2
- package/tests/isolation/expected/grant_target_role_race.out +348 -0
- package/tests/isolation/expected/session_identity_limits.out +165 -0
- package/tests/isolation/expected/session_identity_links.out +235 -0
- package/tests/isolation/expected/session_identity_reclaim.out +253 -0
- package/tests/isolation/expected/session_identity_renew_revoke.out +115 -0
- package/tests/isolation/expected/session_identity_renew_revoke_key_gate.out +138 -0
- package/tests/isolation/expected/session_identity_tombstone_onboarding.out +247 -0
- package/tests/isolation/specs/grant_target_role_race.spec +207 -0
- package/tests/isolation/specs/session_identity_limits.spec +81 -0
- package/tests/isolation/specs/session_identity_links.spec +80 -0
- package/tests/isolation/specs/session_identity_reclaim.spec +125 -0
- package/tests/isolation/specs/session_identity_renew_revoke.spec +84 -0
- package/tests/isolation/specs/session_identity_renew_revoke_key_gate.spec +94 -0
- package/tests/isolation/specs/session_identity_tombstone_onboarding.spec +108 -0
- package/tests/seeds/20261011000100_agent_board_session_identities.sql.pre.sql +47 -0
|
@@ -378,7 +378,9 @@ begin
|
|
|
378
378
|
'{}'::jsonb
|
|
379
379
|
);
|
|
380
380
|
v_board_message := v_result.message_id;
|
|
381
|
-
|
|
381
|
+
-- #608: the sender is the board's seeded owner-creator, and the author of a post is not one of
|
|
382
|
+
-- its recipients (its row is feed-only), so two of the three active members are counted.
|
|
383
|
+
insert into _tap(line) select is(v_result.delivered_recipient_count, 2, 'board delivery reaches joined and subscribed members only, not the author (#608)');
|
|
382
384
|
insert into _tap(line) select is(
|
|
383
385
|
(select count(*)::integer from agent_board.delivery_recipients where source_message_id = v_board_message and recipient_agent_id = v_left_member),
|
|
384
386
|
0,
|
|
@@ -409,7 +411,8 @@ begin
|
|
|
409
411
|
'{}'::jsonb
|
|
410
412
|
);
|
|
411
413
|
v_task := v_result.task_id;
|
|
412
|
-
|
|
414
|
+
-- #608: the creator (the seeded owner-creator) keeps a feed-only row that is not counted: 1 explicit + 2 board.
|
|
415
|
+
insert into _tap(line) select is(v_result.delivered_recipient_count, 3, 'tasking counts explicit and board fan-out attempts, not the creator (#608)');
|
|
413
416
|
insert into _tap(line) select is(
|
|
414
417
|
(select count(*)::integer from agent_board.delivery_recipients where source_task_id = v_task),
|
|
415
418
|
3,
|
|
@@ -453,8 +453,9 @@ select is(
|
|
|
453
453
|
and agent_board_test.stripped_body(p.oid, true) ~ agent_board_test.write_re('board_memberships')),
|
|
454
454
|
array['_fedsession_autojoin_default_board', '_redeem_unbounded_invite_for_agent',
|
|
455
455
|
'_upsert_board_membership_state', 'bootstrap_board_owner_memberships',
|
|
456
|
-
'create_board', 'grant_board_role'
|
|
457
|
-
|
|
456
|
+
'create_board', 'grant_board_role', 'link_session_alias', 'mint_delegated_child',
|
|
457
|
+
'readmit_session_identity', 'set_board_member_label']::text[],
|
|
458
|
+
'message-board#317: the set of functions that WRITE board_memberships is exactly these ten — a new writer fails here NAMING itself');
|
|
458
459
|
|
|
459
460
|
-- The direction claim itself: of those writers, only the shared sink assigns
|
|
460
461
|
-- membership_state in a SET clause, so only the sink can move an EXISTING row between
|
|
@@ -492,8 +493,9 @@ select is(
|
|
|
492
493
|
and agent_board_test.stripped_body(p.oid, true)
|
|
493
494
|
~ agent_board_test.re(agent_board_test.col_assign_frag('board_memberships', 'role', agent_board_test.bare_ok(p.oid)))),
|
|
494
495
|
array['_redeem_unbounded_invite_for_agent', '_upsert_board_membership_state',
|
|
495
|
-
'bootstrap_board_owner_memberships', 'create_board', 'grant_board_role'
|
|
496
|
-
|
|
496
|
+
'bootstrap_board_owner_memberships', 'create_board', 'grant_board_role',
|
|
497
|
+
'link_session_alias', 'mint_delegated_child', 'readmit_session_identity']::text[],
|
|
498
|
+
'message-board#317 canary: the SET-clause predicate still matches real SET clauses (these eight assign `role`) — so an empty membership_state result means empty, not blind');
|
|
497
499
|
|
|
498
500
|
-- THE DOORS THAT ARE NOT FUNCTION BODIES. Every predicate above reads function source, so
|
|
499
501
|
-- anything that writes membership_state WITHOUT a function body naming the table is invisible
|
|
@@ -598,12 +600,12 @@ select is(
|
|
|
598
600
|
from pg_proc p
|
|
599
601
|
join pg_namespace n on n.oid = p.pronamespace
|
|
600
602
|
where n.nspname = 'agent_board'
|
|
601
|
-
and p.
|
|
602
|
-
|
|
603
|
-
|
|
603
|
+
and p.prokind in ('f', 'p')
|
|
604
|
+
and p.prolang <> (select oid from pg_language where lanname = 'internal')
|
|
605
|
+
and agent_board_test.stripped_body(p.oid, true) ~ agent_board_test.write_re('board_memberships')
|
|
604
606
|
and not p.prosecdef),
|
|
605
607
|
array[]::text[],
|
|
606
|
-
'#317: every
|
|
608
|
+
'message-board#317: every catalog-derived FUNCTION writer is SECURITY DEFINER — an invoker writer would need a direct table grant, which the assertion above forbids (FK cascade is covered there, not here)');
|
|
607
609
|
|
|
608
610
|
select is(
|
|
609
611
|
(select coalesce(array_agg(distinct c.relname::text order by c.relname::text), array[]::text[])
|
|
@@ -50,7 +50,7 @@ begin;
|
|
|
50
50
|
-- 88 at #276; #280 deletes 7 (EXEMPTION B1/B2/B3, the three RESIDUAL assertions, and a fourth
|
|
51
51
|
-- residual companion in section 10 that named only the slug) and adds 10 in section 11.
|
|
52
52
|
-- 92 with HOSTED (20261008000100): EXEMPTION E below licenses add_delegate_to_board.
|
|
53
|
-
select plan(
|
|
53
|
+
select plan(98);
|
|
54
54
|
|
|
55
55
|
create function pg_temp.claims(p_sub text) returns void language plpgsql as $$
|
|
56
56
|
begin
|
|
@@ -549,10 +549,10 @@ select is(
|
|
|
549
549
|
or agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into', 'board_invite_tokens'))),
|
|
550
550
|
'_fedsession_autojoin_default_board,_redeem_unbounded_invite_for_agent,'
|
|
551
551
|
|| '_upsert_board_membership_state,bootstrap_board_owner_memberships,create_board,'
|
|
552
|
-
|| 'create_invite_link,grant_board_role,set_board_authority_ceiling,set_board_claim_policy,'
|
|
552
|
+
|| 'create_invite_link,grant_board_role,link_session_alias,mint_delegated_child,readmit_session_identity,set_board_authority_ceiling,set_board_claim_policy,'
|
|
553
553
|
|| 'set_board_role_gate',
|
|
554
554
|
'the functions that WRITE a board-authority relation — in ANY shape, including a mintable '
|
|
555
|
-
'invite preset_role —
|
|
555
|
+
'invite preset_role — include the three SID room/role writers');
|
|
556
556
|
|
|
557
557
|
-- EITHER helper counts as consulting the ledger: the invite redeem path calls the UNLOCKED
|
|
558
558
|
-- predicate (see the migration header), so a side that matched only the share-locking assert
|
|
@@ -567,10 +567,10 @@ select is(
|
|
|
567
567
|
and agent_board_test.stripped_body(p.oid)
|
|
568
568
|
~ agent_board_test.re(agent_board_test.call_frag('_assert_actor_not_fleet_revoked')
|
|
569
569
|
|| '|' || agent_board_test.call_frag('_actor_fleet_revoked'))),
|
|
570
|
-
'_redeem_unbounded_invite_for_agent,add_delegate_to_board,create_board,create_invite_link,grant_board_role,'
|
|
570
|
+
'_redeem_unbounded_invite_for_agent,_session_identity_ancestor_ceiling,add_delegate_to_board,approve_identity_state,create_board,create_invite_link,grant_board_role,'
|
|
571
|
+
|| 'link_session_alias,mint_delegated_child,readmit_session_identity,rename_agent,revoke_agent,revoke_session_identity,'
|
|
571
572
|
|| 'set_board_authority_ceiling,set_board_claim_policy,set_board_role_gate',
|
|
572
|
-
'the
|
|
573
|
-
'create_board; HOSTED added add_delegate_to_board)');
|
|
573
|
+
'the catalog-derived ledger-consult set includes SID writers and the read-only ancestor gate');
|
|
574
574
|
|
|
575
575
|
-- The gate proper: the two sets must differ by EXACTLY the three asserted exemptions, and any
|
|
576
576
|
-- other member of the difference is NAMED in the failure. Each excluded name is licensed by a
|
|
@@ -597,6 +597,10 @@ select is(
|
|
|
597
597
|
-- ...HOSTED's add_delegate_to_board, which consults the ledger for the OWNER and
|
|
598
598
|
-- writes no authority relation itself (EXEMPTION E, positively, below)...
|
|
599
599
|
'add_delegate_to_board',
|
|
600
|
+
-- read-only lineage validity gate, positively pinned below
|
|
601
|
+
'_session_identity_ancestor_ceiling',
|
|
602
|
+
-- Principal/identity owner gates, each positively pinned below.
|
|
603
|
+
'approve_identity_state', 'rename_agent', 'revoke_agent', 'revoke_session_identity',
|
|
600
604
|
-- ...and the two helpers, which ARE the gate rather than users of it
|
|
601
605
|
'_assert_actor_not_fleet_revoked', '_actor_fleet_revoked')
|
|
602
606
|
and ((agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into', 'board_memberships')
|
|
@@ -691,15 +695,69 @@ select is(
|
|
|
691
695
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
692
696
|
where n.nspname = 'agent_board'
|
|
693
697
|
and p.proname <> '_upsert_board_membership_state'
|
|
694
|
-
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_upsert_board_membership_state')
|
|
698
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_upsert_board_membership_state')
|
|
699
|
+
and agent_board_test.stripped_body(p.oid) !~ agent_board_test.col_write_re('board_memberships', 'role')),
|
|
695
700
|
'add_delegate_to_board(p_agent_id uuid, p_board_id uuid)'
|
|
696
701
|
|| ' | invite_board_member(p_board_id uuid, p_agent_id uuid, p_created_by_agent_id uuid, p_metadata jsonb)'
|
|
697
702
|
|| ' | join_board(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_created_by_agent_id uuid, p_metadata jsonb)'
|
|
698
703
|
|| ' | request_join_board(p_board_id uuid, p_agent_id uuid, p_metadata jsonb)'
|
|
699
704
|
|| ' | resolve_board_request(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_created_by_agent_id uuid, p_metadata jsonb)'
|
|
700
705
|
|| ' | update_board_membership(p_board_id uuid, p_agent_id uuid, p_membership_state text, p_metadata jsonb)',
|
|
701
|
-
'EXEMPTION A4:
|
|
702
|
-
|
|
706
|
+
'EXEMPTION A4: every state-only sink caller takes exactly these parameters; direct role writers join the ledger-gated class');
|
|
707
|
+
|
|
708
|
+
select is(
|
|
709
|
+
(select string_agg(p.proname, ',' order by p.proname) from pg_proc p
|
|
710
|
+
where p.pronamespace = 'agent_board'::regnamespace
|
|
711
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_upsert_board_membership_state', false)
|
|
712
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.col_write_re('board_memberships', 'role')
|
|
713
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_assert_actor_not_fleet_revoked', false)),
|
|
714
|
+
'link_session_alias,mint_delegated_child,readmit_session_identity',
|
|
715
|
+
'EXEMPTION A4-SID: every mixed role/state sink caller is positively a ledger-gated authority writer');
|
|
716
|
+
|
|
717
|
+
select ok(
|
|
718
|
+
(select p.prorettype = 'timestamptz'::regtype
|
|
719
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_actor_fleet_revoked', false)
|
|
720
|
+
and agent_board_test.stripped_body(p.oid) like '%v_ancestor.id%'
|
|
721
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('from', 'agents', false)
|
|
722
|
+
and agent_board_test.stripped_body(p.oid) !~ agent_board_test.write_re('board_memberships')
|
|
723
|
+
and agent_board_test.stripped_body(p.oid) !~ agent_board_test.write_re('board_authority_ceilings')
|
|
724
|
+
and agent_board_test.stripped_body(p.oid) !~ agent_board_test.write_re('boards')
|
|
725
|
+
and agent_board_test.stripped_body(p.oid) !~ agent_board_test.write_re('board_invite_tokens')
|
|
726
|
+
from pg_proc p where p.oid = 'agent_board._session_identity_ancestor_ceiling(uuid)'::regprocedure),
|
|
727
|
+
'EXEMPTION SID-ANCESTOR: the ledger consumer reads lineage and returns only an expiry, never writes board authority');
|
|
728
|
+
|
|
729
|
+
select ok(
|
|
730
|
+
agent_board_test.stripped_body('agent_board.approve_identity_state(uuid,text,jsonb)'::regprocedure)
|
|
731
|
+
~ agent_board_test.call_re('_assert_actor_not_fleet_revoked')
|
|
732
|
+
and agent_board_test.stripped_body('agent_board.approve_identity_state(uuid,text,jsonb)'::regprocedure)
|
|
733
|
+
~ agent_board_test.call_re('_session_identity_owner')
|
|
734
|
+
and agent_board_test.stripped_body('agent_board.approve_identity_state(uuid,text,jsonb)'::regprocedure)
|
|
735
|
+
~ agent_board_test.col_write_re('session_identities','approved_risky_digest'),
|
|
736
|
+
'EXEMPTION SID-APPROVE: locked owner ledger gate authorises stored identity approval, not a board-role write');
|
|
737
|
+
select ok(
|
|
738
|
+
agent_board_test.stripped_body('agent_board.rename_agent(uuid,text)'::regprocedure)
|
|
739
|
+
~ agent_board_test.call_re('_assert_actor_not_fleet_revoked')
|
|
740
|
+
and agent_board_test.stripped_body('agent_board.rename_agent(uuid,text)'::regprocedure)
|
|
741
|
+
~ agent_board_test.call_re('auth.uid',false)
|
|
742
|
+
and agent_board_test.stripped_body('agent_board.rename_agent(uuid,text)'::regprocedure)
|
|
743
|
+
~ agent_board_test.col_write_re('agents','display_name'),
|
|
744
|
+
'EXEMPTION SID-RENAME: locked human-owner ledger gate authorises the principal display-name update');
|
|
745
|
+
select ok(
|
|
746
|
+
agent_board_test.stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure)
|
|
747
|
+
~ agent_board_test.call_re('_assert_actor_not_fleet_revoked')
|
|
748
|
+
and agent_board_test.stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure)
|
|
749
|
+
~ agent_board_test.call_re('_revoke_session_identity_family')
|
|
750
|
+
and agent_board_test.stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure)
|
|
751
|
+
~ agent_board_test.col_write_re('agents','status'),
|
|
752
|
+
'EXEMPTION SID-ADMIN-REVOKE: locked actor ledger gate authorises principal suspension and session-family revocation');
|
|
753
|
+
select ok(
|
|
754
|
+
agent_board_test.stripped_body('agent_board.revoke_session_identity(uuid)'::regprocedure)
|
|
755
|
+
~ agent_board_test.call_re('_assert_actor_not_fleet_revoked')
|
|
756
|
+
and agent_board_test.stripped_body('agent_board.revoke_session_identity(uuid)'::regprocedure)
|
|
757
|
+
~ agent_board_test.call_re('_session_identity_owner')
|
|
758
|
+
and agent_board_test.stripped_body('agent_board.revoke_session_identity(uuid)'::regprocedure)
|
|
759
|
+
~ agent_board_test.call_re('_revoke_agent_principal'),
|
|
760
|
+
'EXEMPTION SID-OWNER-REVOKE: locked human-owner ledger gate authorises the principal revocation primitive');
|
|
703
761
|
|
|
704
762
|
-- ---------------------------------------------------------------------------
|
|
705
763
|
-- EXEMPTIONS C and D — the two remaining service_role-only seeders. (What used to be
|
|
@@ -960,14 +1018,14 @@ select ok(
|
|
|
960
1018
|
|
|
961
1019
|
select ok(
|
|
962
1020
|
strpos(agent_board_test.stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
963
|
-
'agent_board.agents a where a.id =
|
|
1021
|
+
'agent_board.agents a where a.id = v_id for update') > 0
|
|
964
1022
|
and strpos(agent_board_test.stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
965
1023
|
'insert into agent_board.fleet_revocation_events') > 0
|
|
966
1024
|
and strpos(agent_board_test.stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
967
|
-
'agent_board.agents a where a.id =
|
|
1025
|
+
'agent_board.agents a where a.id = v_id for update')
|
|
968
1026
|
< strpos(agent_board_test.stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
969
1027
|
'insert into agent_board.fleet_revocation_events'),
|
|
970
|
-
'_revoke_agent_principal locks
|
|
1028
|
+
'_revoke_agent_principal locks each closure principal agents row BEFORE writing its ledger too');
|
|
971
1029
|
|
|
972
1030
|
-- The lock the gate itself takes, and its MODE. FOR KEY SHARE would not conflict with the FOR
|
|
973
1031
|
-- NO KEY UPDATE a plain status change takes; the isolation spec is what proves the conflict,
|
|
@@ -306,10 +306,13 @@ select is(
|
|
|
306
306
|
'_grant_within_ceiling/limit/filtered:''joined'',''subscribed''',
|
|
307
307
|
'_upsert_board_membership_state/limit/active-first',
|
|
308
308
|
'join_board/limit/active-first',
|
|
309
|
+
'link_session_alias/limit/filtered:''joined'',''subscribed''',
|
|
309
310
|
'list_board_members/distinct/active-first',
|
|
310
311
|
'list_board_members/limit/active-first',
|
|
311
312
|
'list_board_pending_memberships/distinct/active-first',
|
|
312
313
|
'list_my_board_requests/limit/active-first',
|
|
314
|
+
'mint_delegated_child/limit/filtered:''joined'',''subscribed''',
|
|
315
|
+
'readmit_session_identity/limit/active-first',
|
|
313
316
|
'request_join_board/limit/filtered:''joined'',''subscribed''',
|
|
314
317
|
'resolve_board_request/limit/filtered:''joined'',''subscribed''',
|
|
315
318
|
'resolve_board_request/limit/filtered:''requested'',''invited''',
|
|
@@ -340,6 +343,8 @@ select is(
|
|
|
340
343
|
-- grantor's highest active role, among active rows only. With one active row
|
|
341
344
|
-- per (board, agent) the order is moot; it is in the class because the class
|
|
342
345
|
-- no longer requires a recency operand (an alias or ordinal would hide one).
|
|
346
|
+
-- link_session_alias / mint_delegated_child -- use the same exact ACTIVE
|
|
347
|
+
-- parent-role filter; removed historical roles cannot grant room authority.
|
|
343
348
|
-- resolve_board_request (pending) -- MBROWID 3c: the approval gate must see the
|
|
344
349
|
-- newest requested/invited marker, NOT prefer a coexisting active row; the
|
|
345
350
|
-- active case is handled by the probe above.
|
|
@@ -350,11 +355,13 @@ select is(
|
|
|
350
355
|
array[
|
|
351
356
|
'_active_board_membership_id=''joined'',''subscribed''',
|
|
352
357
|
'_grant_within_ceiling=''joined'',''subscribed''',
|
|
358
|
+
'link_session_alias=''joined'',''subscribed''',
|
|
359
|
+
'mint_delegated_child=''joined'',''subscribed''',
|
|
353
360
|
'request_join_board=''joined'',''subscribed''',
|
|
354
361
|
'resolve_board_request=''joined'',''subscribed''',
|
|
355
362
|
'resolve_board_request=''requested'',''invited'''
|
|
356
363
|
]::text[],
|
|
357
|
-
'CRC-3: the
|
|
364
|
+
'CRC-3: the filtered exemptions pin exactly these membership_state sets');
|
|
358
365
|
|
|
359
366
|
-- ---------------------------------------------------------------------------
|
|
360
367
|
-- CRC-4: the active-first members, exactly.
|
|
@@ -369,6 +376,7 @@ select is(
|
|
|
369
376
|
'list_board_members/limit',
|
|
370
377
|
'list_board_pending_memberships/distinct',
|
|
371
378
|
'list_my_board_requests/limit',
|
|
379
|
+
'readmit_session_identity/limit',
|
|
372
380
|
'update_board_membership/limit',
|
|
373
381
|
'update_board_membership/limit'
|
|
374
382
|
]::text[],
|
|
@@ -185,7 +185,7 @@ end
|
|
|
185
185
|
$$;
|
|
186
186
|
|
|
187
187
|
-- Function existence -----------------------------------------------------------
|
|
188
|
-
select has_function('agent_board', 'mint_fleet_agent_session', array['uuid','text','uuid','uuid','integer'], 'mint_fleet_agent_session exists');
|
|
188
|
+
select has_function('agent_board', 'mint_fleet_agent_session', array['uuid','text','uuid','uuid','integer','text','text'], 'mint_fleet_agent_session exists');
|
|
189
189
|
select has_function('agent_board', '_agent_principal_active', array['uuid'], '_agent_principal_active exists');
|
|
190
190
|
|
|
191
191
|
-- _agent_principal_active lineage semantics ------------------------------------
|
|
@@ -279,7 +279,7 @@ select throws_ok(
|
|
|
279
279
|
|
|
280
280
|
-- Privileges -------------------------------------------------------------------
|
|
281
281
|
select function_privs_are(
|
|
282
|
-
'agent_board', 'mint_fleet_agent_session', array['uuid','text','uuid','uuid','integer'],
|
|
282
|
+
'agent_board', 'mint_fleet_agent_session', array['uuid','text','uuid','uuid','integer','text','text'],
|
|
283
283
|
'authenticated', array[]::text[], 'mint_fleet_agent_session execute is revoked from authenticated');
|
|
284
284
|
select function_privs_are(
|
|
285
285
|
'agent_board', '_agent_principal_active', array['uuid'],
|
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
-- * mint raw-byte fingerprint + cross-path dedup (same key => same fingerprint across
|
|
8
8
|
-- mint and enroll, so the UNIQUE backstop finally bites);
|
|
9
9
|
-- * non-positive-TTL floor;
|
|
10
|
-
-- *
|
|
11
|
-
-- the
|
|
12
|
-
--
|
|
13
|
-
-- * per-root provisioning RATE cap on the authenticated path only;
|
|
10
|
+
-- * SIDSCHEMA's global 200 active/60 hourly bounds and any tighter policy,
|
|
11
|
+
-- including the legacy self-service 24-hour TTL vs operator TTL latitude;
|
|
12
|
+
-- * real grant-created rolling-hour census on both mint paths;
|
|
14
13
|
-- * EXECUTE class: mint stays service_role-only (topology A; the EF is the auth surface).
|
|
15
14
|
--
|
|
16
15
|
-- Allow-side assertions use lives_ok(...) + a separate state read (never
|
|
@@ -22,7 +21,7 @@ set search_path = extensions, public, agent_board;
|
|
|
22
21
|
|
|
23
22
|
begin;
|
|
24
23
|
|
|
25
|
-
select plan(
|
|
24
|
+
select plan(33);
|
|
26
25
|
|
|
27
26
|
create temp table _mh (
|
|
28
27
|
owner_id uuid,
|
|
@@ -62,28 +61,33 @@ begin
|
|
|
62
61
|
insert into agent_board.delegation_policies(root_agent_id, mode, max_depth, allowed_scopes, child_ttl_ceiling, require_child_signing_key)
|
|
63
62
|
values (v_r_wide, 'root_minted', 1, jsonb_build_array('scope:a'), interval '30 days', false);
|
|
64
63
|
|
|
65
|
-
--
|
|
66
|
-
--
|
|
64
|
+
-- A tighter stored 100-cap still bounds the new global 200 maximum. Historical
|
|
65
|
+
-- legacy grants count as active authority but lie outside the rolling hour.
|
|
67
66
|
insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id)
|
|
68
67
|
values (v_r_capnull, 'agent', 'mh-rcap-'||s, 'MH RCap', 'active', u_owner);
|
|
69
68
|
insert into agent_board.delegation_policies(root_agent_id, mode, max_depth, max_active_children, allowed_scopes, child_ttl_ceiling, require_child_signing_key)
|
|
70
69
|
values (v_r_capnull, 'root_minted', 1, 100, jsonb_build_array('scope:a'), interval '8 hours', false);
|
|
71
|
-
for i in 1..
|
|
70
|
+
for i in 1..100 loop
|
|
72
71
|
v_child := gen_random_uuid();
|
|
73
72
|
insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id)
|
|
74
73
|
values (v_child, 'agent', 'mh-cap-child-'||s||'-'||i, 'MH Cap Child', 'active', u_owner);
|
|
75
|
-
insert into agent_board.delegation_grants(child_agent_id, root_agent_id, parent_agent_id, delegation_policy_id, granted_scopes, grant_expires_at)
|
|
76
|
-
select v_child, v_r_capnull, v_r_capnull, dp.id, '[]'::jsonb, now() + interval '1 hour'
|
|
74
|
+
insert into agent_board.delegation_grants(child_agent_id, root_agent_id, parent_agent_id, delegation_policy_id, granted_scopes, grant_expires_at, created_at)
|
|
75
|
+
select v_child, v_r_capnull, v_r_capnull, dp.id, '[]'::jsonb, now() + interval '1 hour', now() - interval '2 hours'
|
|
77
76
|
from agent_board.delegation_policies dp where dp.root_agent_id = v_r_capnull;
|
|
78
77
|
end loop;
|
|
79
78
|
|
|
80
|
-
--
|
|
79
|
+
-- Expired grants remain hourly mints, without contributing active authority.
|
|
81
80
|
insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id)
|
|
82
81
|
values (v_r_rate, 'agent', 'mh-rrate-'||s, 'MH RRate', 'active', u_owner);
|
|
83
82
|
insert into agent_board.delegation_policies(root_agent_id, mode, max_depth, allowed_scopes, child_ttl_ceiling, require_child_signing_key)
|
|
84
83
|
values (v_r_rate, 'root_minted', 1, jsonb_build_array('scope:a'), interval '8 hours', false);
|
|
85
|
-
for i in 1..
|
|
86
|
-
|
|
84
|
+
for i in 1..60 loop
|
|
85
|
+
v_child := gen_random_uuid();
|
|
86
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id, parent_agent_id, root_agent_id)
|
|
87
|
+
values (v_child, 'agent', 'mh-rate-child-' || s || '-' || i, 'Hourly child', 'active', u_owner, v_r_rate, v_r_rate);
|
|
88
|
+
insert into agent_board.delegation_grants(child_agent_id, root_agent_id, parent_agent_id, delegation_policy_id, granted_scopes, grant_expires_at)
|
|
89
|
+
select v_child, v_r_rate, v_r_rate, dp.id, '[]'::jsonb, now() - interval '1 hour'
|
|
90
|
+
from agent_board.delegation_policies dp where dp.root_agent_id = v_r_rate;
|
|
87
91
|
end loop;
|
|
88
92
|
|
|
89
93
|
-- d_dup: a dummy agent already holding the raw key k_dup (helper fingerprint) -> the
|
|
@@ -276,37 +280,44 @@ select ok(
|
|
|
276
280
|
(select grant_expires_at <= now() + interval '24 hours' + interval '1 minute' from _m_ss_ttl),
|
|
277
281
|
'self-service path re-clamps the TTL ceiling to <= 24h (stored 30-day ceiling NOT trusted)');
|
|
278
282
|
|
|
279
|
-
--
|
|
280
|
-
-- the self-service mint is refused (the mutation dropping the clamp lets a 26th through).
|
|
283
|
+
-- A tighter stored cap remains enforced; SIDSCHEMA no longer restores the old 25 pin.
|
|
281
284
|
select throws_ok(
|
|
282
285
|
(select format($q$select agent_board.mint_delegated_child(%L::uuid, %L, jsonb_build_array('scope:a'), null, null, true)$q$,
|
|
283
286
|
r_capnull, 'mh-sscap-'||replace(substr(gen_random_uuid()::text,1,8),'-','')) from _mh),
|
|
284
287
|
'42501', 'max_active_children: root has reached its active delegated-child ceiling',
|
|
285
|
-
'self-service
|
|
288
|
+
'self-service honors the tighter 100 active ceiling under the global 200 bound');
|
|
286
289
|
|
|
287
|
-
--
|
|
290
|
+
-- Release one historical grant: both paths have room below the tighter ceiling.
|
|
291
|
+
update agent_board.delegation_grants set revoked_at = now() where child_agent_id = (
|
|
292
|
+
select g.child_agent_id from agent_board.delegation_grants g, _mh where g.root_agent_id = _mh.r_capnull order by g.child_agent_id limit 1);
|
|
288
293
|
select lives_ok(
|
|
289
294
|
(select format($q$create temp table _m_op_cap as select * from agent_board.mint_delegated_child(%L::uuid, %L, jsonb_build_array('scope:a'), null, null, false)$q$,
|
|
290
295
|
r_capnull, 'mh-opcap-'||replace(substr(gen_random_uuid()::text,1,8),'-','')) from _mh),
|
|
291
|
-
'operator path honors the stored 100 cap (
|
|
296
|
+
'operator path honors the stored 100 cap (99 < 100 => mint succeeds)');
|
|
292
297
|
select is((select count(*) from _m_op_cap), 1::bigint,
|
|
293
298
|
'operator cap mint produced exactly one child (not a silent zero-row pass)');
|
|
294
299
|
|
|
295
300
|
-- ===========================================================================
|
|
296
|
-
-- F.
|
|
301
|
+
-- F. Root rolling-hour census applies to both legacy and session mint paths.
|
|
297
302
|
-- ===========================================================================
|
|
298
|
-
-- r_rate has
|
|
303
|
+
-- r_rate has 60 actual recent expired grants, so only the hourly cap can fire.
|
|
299
304
|
select throws_ok(
|
|
300
305
|
(select format($q$select agent_board.mint_delegated_child(%L::uuid, %L, jsonb_build_array('scope:a'), null, null, true)$q$,
|
|
301
306
|
r_rate, 'mh-ssrate-'||replace(substr(gen_random_uuid()::text,1,8),'-','')) from _mh),
|
|
302
|
-
'42501', '
|
|
303
|
-
'self-service mint is refused
|
|
307
|
+
'42501', 'identity_limit_hourly_exceeded',
|
|
308
|
+
'self-service mint is refused at 60 real mints in the rolling hour');
|
|
304
309
|
|
|
305
|
-
|
|
310
|
+
select throws_ok(
|
|
311
|
+
(select format($q$select agent_board.mint_delegated_child(%L::uuid, %L, jsonb_build_array('scope:a'), null, null, false)$q$,
|
|
312
|
+
r_rate, 'mh-opfull-'||replace(substr(gen_random_uuid()::text,1,8),'-','')) from _mh),
|
|
313
|
+
'42501', 'identity_limit_hourly_exceeded',
|
|
314
|
+
'operator mint cannot bypass the global rolling-hour ceiling');
|
|
315
|
+
update agent_board.delegation_grants set created_at = now() - interval '2 hours' where child_agent_id = (
|
|
316
|
+
select g.child_agent_id from agent_board.delegation_grants g, _mh where g.root_agent_id = _mh.r_rate order by g.child_agent_id limit 1);
|
|
306
317
|
select lives_ok(
|
|
307
318
|
(select format($q$create temp table _m_op_rate as select * from agent_board.mint_delegated_child(%L::uuid, %L, jsonb_build_array('scope:a'), null, null, false)$q$,
|
|
308
319
|
r_rate, 'mh-oprate-'||replace(substr(gen_random_uuid()::text,1,8),'-','')) from _mh),
|
|
309
|
-
'operator path
|
|
320
|
+
'operator path mints the 60th real child when one old mint leaves the rolling hour');
|
|
310
321
|
select is((select count(*) from _m_op_rate), 1::bigint,
|
|
311
322
|
'operator rate-path mint produced exactly one child (not a silent zero-row pass)');
|
|
312
323
|
|
|
@@ -314,13 +325,13 @@ select is((select count(*) from _m_op_rate), 1::bigint,
|
|
|
314
325
|
-- G. EXECUTE class — mint stays service_role-only (topology A; EF is the auth surface).
|
|
315
326
|
-- ===========================================================================
|
|
316
327
|
select ok(not has_function_privilege('authenticated',
|
|
317
|
-
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean)', 'EXECUTE'),
|
|
328
|
+
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean, text, text, text, jsonb, jsonb, uuid)', 'EXECUTE'),
|
|
318
329
|
'authenticated has NO direct EXECUTE on mint_delegated_child (EF-mediated only)');
|
|
319
330
|
select ok(not has_function_privilege('anon',
|
|
320
|
-
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean)', 'EXECUTE'),
|
|
331
|
+
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean, text, text, text, jsonb, jsonb, uuid)', 'EXECUTE'),
|
|
321
332
|
'anon has NO EXECUTE on mint_delegated_child');
|
|
322
333
|
select ok(has_function_privilege('service_role',
|
|
323
|
-
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean)', 'EXECUTE'),
|
|
334
|
+
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean, text, text, text, jsonb, jsonb, uuid)', 'EXECUTE'),
|
|
324
335
|
'service_role retains EXECUTE on mint_delegated_child');
|
|
325
336
|
|
|
326
337
|
|