@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.
Files changed (73) hide show
  1. package/README.md +80 -0
  2. package/fixtures/already-applied.json +16 -0
  3. package/migrations/20261010000100_agent_board_own_post_not_delivered.sql +637 -0
  4. package/migrations/20261011000100_agent_board_session_identities.sql +1964 -0
  5. package/migrations/20261012000100_agent_board_sidowner.sql +209 -0
  6. package/migrations/20261014000100_agent_board_grant_target_role.sql +218 -0
  7. package/package.json +1 -1
  8. package/schema-manifest.json +39 -3
  9. package/src/tooling.test.mjs +9 -2
  10. package/tests/agent_board_addressing.test.sql +3 -1
  11. package/tests/agent_board_atomic_fleet_onboarding.test.sql +13 -4
  12. package/tests/agent_board_consent_emission.test.sql +30 -11
  13. package/tests/agent_board_consent_gating.test.sql +16 -2
  14. package/tests/agent_board_delegate_role_class.test.sql +1 -1
  15. package/tests/agent_board_delegation_cap_ceiling.test.sql +26 -15
  16. package/tests/agent_board_delegation_enforcement.test.sql +1 -1
  17. package/tests/agent_board_enroll_resumption.test.sql +22 -4
  18. package/tests/agent_board_enroll_token_single_use.test.sql +18 -3
  19. package/tests/agent_board_federated_lock_order_class.test.sql +2 -2
  20. package/tests/agent_board_fk_lock_edges.test.sql +74 -14
  21. package/tests/agent_board_grant_target_role.test.sql +1118 -0
  22. package/tests/agent_board_inbox_rpcs.test.sql +5 -2
  23. package/tests/agent_board_invite_no_downgrade.test.sql +10 -8
  24. package/tests/agent_board_ledger_authority.test.sql +70 -12
  25. package/tests/agent_board_membership_current_row_class.test.sql +9 -1
  26. package/tests/agent_board_mint_fleet_agent_session.test.sql +2 -2
  27. package/tests/agent_board_mint_harden.test.sql +38 -27
  28. package/tests/agent_board_own_post_not_delivered.test.sql +490 -0
  29. package/tests/agent_board_rolereset.test.sql +8 -2
  30. package/tests/agent_board_roomdeleg_lane_b.test.sql +6 -6
  31. package/tests/agent_board_rowid.test.sql +3 -1
  32. package/tests/agent_board_session_identity_approval_revocation.test.sql +334 -0
  33. package/tests/agent_board_session_identity_backfill.test.sql +82 -0
  34. package/tests/agent_board_session_identity_backfill_refusal.test.sql +14 -0
  35. package/tests/agent_board_session_identity_behavior_classes.test.sql +176 -0
  36. package/tests/agent_board_session_identity_class.test.sql +118 -0
  37. package/tests/agent_board_session_identity_fingerprint.test.sql +271 -0
  38. package/tests/agent_board_session_identity_labels_limits.test.sql +205 -0
  39. package/tests/agent_board_session_identity_lease.test.sql +235 -0
  40. package/tests/agent_board_session_identity_lifetime.test.sql +230 -0
  41. package/tests/agent_board_session_identity_limits.test.sql +208 -0
  42. package/tests/agent_board_session_identity_lineage.test.sql +64 -0
  43. package/tests/agent_board_session_identity_link.test.sql +201 -0
  44. package/tests/agent_board_session_identity_mint.test.sql +233 -0
  45. package/tests/agent_board_session_identity_panel_r1.test.sql +194 -0
  46. package/tests/agent_board_session_identity_panel_r2.test.sql +174 -0
  47. package/tests/agent_board_session_identity_protected_actions.test.sql +156 -0
  48. package/tests/agent_board_session_identity_revoke_closure.test.sql +131 -0
  49. package/tests/agent_board_sidowner_leases.test.sql +107 -0
  50. package/tests/agent_board_sidowner_owner_required.test.sql +97 -0
  51. package/tests/agent_board_sidowner_read_class.test.sql +171 -0
  52. package/tests/agent_board_sidowner_reads.test.sql +136 -0
  53. package/tests/agent_board_signer_lifecycle.test.sql +26 -4
  54. package/tests/agent_board_signing_challenge_pending_cap.test.sql +1 -1
  55. package/tests/agent_board_v2_invariants.test.sql +4 -4
  56. package/tests/agent_board_write_enforcement_class.test.sql +74 -15
  57. package/tests/catalog/function_execute_privileges.json +218 -2
  58. package/tests/catalog/p_metadata_parameters.json +98 -2
  59. package/tests/isolation/expected/grant_target_role_race.out +348 -0
  60. package/tests/isolation/expected/session_identity_limits.out +165 -0
  61. package/tests/isolation/expected/session_identity_links.out +235 -0
  62. package/tests/isolation/expected/session_identity_reclaim.out +253 -0
  63. package/tests/isolation/expected/session_identity_renew_revoke.out +115 -0
  64. package/tests/isolation/expected/session_identity_renew_revoke_key_gate.out +138 -0
  65. package/tests/isolation/expected/session_identity_tombstone_onboarding.out +247 -0
  66. package/tests/isolation/specs/grant_target_role_race.spec +207 -0
  67. package/tests/isolation/specs/session_identity_limits.spec +81 -0
  68. package/tests/isolation/specs/session_identity_links.spec +80 -0
  69. package/tests/isolation/specs/session_identity_reclaim.spec +125 -0
  70. package/tests/isolation/specs/session_identity_renew_revoke.spec +84 -0
  71. package/tests/isolation/specs/session_identity_renew_revoke_key_gate.spec +94 -0
  72. package/tests/isolation/specs/session_identity_tombstone_onboarding.spec +108 -0
  73. 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
- insert into _tap(line) select is(v_result.delivered_recipient_count, 3, 'board delivery reaches joined and subscribed members only (incl. the seeded owner-creator)');
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
- insert into _tap(line) select is(v_result.delivered_recipient_count, 4, 'tasking counts explicit and board fan-out attempts (incl. the seeded owner-creator)');
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']::text[],
457
- '#317: the set of functions that WRITE board_memberships is exactly these six — a new writer fails here NAMING itself');
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']::text[],
496
- '#317 canary: the SET-clause predicate still matches real SET clauses (these five assign `role`) — so an empty membership_state result means empty, not blind');
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.proname in ('_fedsession_autojoin_default_board', '_redeem_unbounded_invite_for_agent',
602
- '_upsert_board_membership_state', 'bootstrap_board_owner_memberships',
603
- 'create_board', 'grant_board_role')
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 one of the six FUNCTION writers is SECURITY DEFINER — a function writer that is not would need a direct table grant, which the assertion above forbids (FK cascade is covered there, not here)');
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(92);
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 — are exactly these ten');
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 functions that consult the fleet revocation ledger are exactly these eight (#280 added '
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: EVERY caller of _upsert_board_membership_state takes EXACTLY these parameters '
702
- '— not one declares a role, so "no caller can hand it one" is checked rather than argued');
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 = p_agent_id for update') > 0
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 = p_agent_id for update')
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 the agents row BEFORE writing the ledger too');
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 five filtered exemptions pin exactly these membership_state sets');
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
- -- * F1 authenticated (self-service) RE-CLAMP of max_active_children and TTL ceiling —
11
- -- the stored caps are NOT trusted (a root can self-widen its policy, #142) — vs the
12
- -- operator path which keeps its latitude;
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(32);
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
- -- r_capnull: operator-set max_active_children=100 (a self-widened row), no key -> F1 cap
66
- -- clamp. Pre-load 25 LIVE grants: self-service clamps to 25 (raise); operator keeps 100.
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..25 loop
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
- -- r_rate: uncapped, no key -> rate cap test. Pre-load 25 rate events (at the cap).
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..25 loop
86
- insert into agent_board.child_mint_rate_events(root_agent_id, created_at) values (v_r_rate, now());
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
- -- Self-service cap: stored max_active_children=100 is re-clamped to 25; with 25 live grants
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 re-clamps max_active_children to 25 (stored 100 NOT trusted)');
288
+ 'self-service honors the tighter 100 active ceiling under the global 200 bound');
286
289
 
287
- -- Operator path under the SAME 100-cap policy + 25 live grants still mints (its latitude).
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 (25 < 100 => mint succeeds)');
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. Per-root provisioning RATE cap (authenticated path only).
301
+ -- F. Root rolling-hour census applies to both legacy and session mint paths.
297
302
  -- ===========================================================================
298
- -- r_rate has 25 rate events (at the cap) and 0 live grants, so only the rate cap can fire.
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', 'mint_rate_exceeded: root has reached its provisioning rate cap for the current window',
303
- 'self-service mint is refused once the per-root provisioning rate cap is reached');
307
+ '42501', 'identity_limit_hourly_exceeded',
308
+ 'self-service mint is refused at 60 real mints in the rolling hour');
304
309
 
305
- -- The operator path is NOT rate-capped (the same full rate table does not block it).
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 is not rate-capped (mints despite a full rate table)');
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