@consiliency/agent-board-schema 1.2.0 → 1.4.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 (64) hide show
  1. package/bin/agent-board-schema.js +0 -0
  2. package/fixtures/already-applied.json +108 -0
  3. package/migrations/20260714000200_agent_board_workload_providers_rls.sql +35 -0
  4. package/migrations/20260717000100_agent_board_board_role.sql +180 -0
  5. package/migrations/20260717000200_agent_board_thread_acl.sql +149 -0
  6. package/migrations/20260717000300_agent_board_agent_lineage.sql +54 -0
  7. package/migrations/20260717000400_agent_board_delegation_policies.sql +175 -0
  8. package/migrations/20260718000100_agent_board_send_authz_config.sql +172 -0
  9. package/migrations/20260718000200_agent_board_send_role_gate.sql +804 -0
  10. package/migrations/20260718000300_agent_board_fleet_policy_default_deny.sql +192 -0
  11. package/migrations/20260718000400_agent_board_grant_ceiling_wiring.sql +58 -0
  12. package/migrations/20260718000500_agent_board_fleet_agent_session_class.sql +24 -0
  13. package/migrations/20260718000600_agent_board_mint_fleet_agent_session.sql +172 -0
  14. package/migrations/20260718000700_agent_board_nonfleet_session_reaper.sql +35 -0
  15. package/migrations/20260718000800_agent_board_regrant_scoped_send.sql +24 -0
  16. package/migrations/20260718000900_agent_board_agents_status_revoked.sql +24 -0
  17. package/migrations/20260718001000_agent_board_delegation_enforcement.sql +296 -0
  18. package/migrations/20260718001100_agent_board_revoke_agent_subtree.sql +238 -0
  19. package/migrations/20260718001200_agent_board_resigning_refresh.sql +129 -0
  20. package/migrations/20260718001300_agent_board_identity_providers.sql +70 -0
  21. package/migrations/20260718001400_agent_board_enrollment_federation_audit.sql +50 -0
  22. package/migrations/20260718001500_agent_board_enroll_federated_identity.sql +220 -0
  23. package/migrations/20260719000100_agent_board_signing_challenge_pending_cap.sql +124 -0
  24. package/migrations/20260719000200_agent_board_signing_challenge_reaper.sql +34 -0
  25. package/migrations/20260719000300_agent_board_enroll_rate_cap.sql +72 -0
  26. package/migrations/20260719001000_agent_board_identity_providers_portal_contract.sql +38 -0
  27. package/migrations/20260719001100_agent_board_enroll_federated_identity_pop.sql +267 -0
  28. package/migrations/20260719001200_agent_board_enroll_federated_identity_raw_fingerprint.sql +296 -0
  29. package/migrations/20260719001300_agent_board_enroll_rate_events_reaper_rls.sql +62 -0
  30. package/package.json +9 -9
  31. package/schema-manifest.json +247 -4
  32. package/src/tooling.test.mjs +42 -1
  33. package/tests/agent_board_agent_lineage.test.sql +73 -0
  34. package/tests/agent_board_agents_status_revoked.test.sql +60 -0
  35. package/tests/agent_board_auth_rpcs.test.sql +17 -16
  36. package/tests/agent_board_board_role.test.sql +129 -0
  37. package/tests/agent_board_bundle_endpoint_compat.test.sql +18 -6
  38. package/tests/agent_board_canary_signed_fleet_enforcement.test.sql +8 -8
  39. package/tests/agent_board_delegation_enforcement.test.sql +175 -0
  40. package/tests/agent_board_delegation_policies.test.sql +119 -0
  41. package/tests/agent_board_enroll_federated_identity.test.sql +217 -0
  42. package/tests/agent_board_enroll_pop_binding.test.sql +131 -0
  43. package/tests/agent_board_enroll_rate_cap.test.sql +53 -0
  44. package/tests/agent_board_enrollment_audit_content.test.sql +134 -0
  45. package/tests/agent_board_fleet_agent_session_class.test.sql +64 -0
  46. package/tests/agent_board_fleet_policy_default_deny.test.sql +149 -0
  47. package/tests/agent_board_fleet_registry.test.sql +2 -2
  48. package/tests/agent_board_identity_providers.test.sql +104 -0
  49. package/tests/agent_board_inbox_rpcs.test.sql +6 -6
  50. package/tests/agent_board_mint_fleet_agent_session.test.sql +290 -0
  51. package/tests/agent_board_nonfleet_session_reaper.test.sql +61 -0
  52. package/tests/agent_board_portal_offline_invariant.test.sql +130 -0
  53. package/tests/agent_board_resigning_refresh.test.sql +73 -0
  54. package/tests/agent_board_revoke_agent_subtree.test.sql +188 -0
  55. package/tests/agent_board_schema_version.test.sql +2 -2
  56. package/tests/agent_board_schema_version_rpc.test.sql +1 -1
  57. package/tests/agent_board_scoped_send_regrant.test.sql +138 -0
  58. package/tests/agent_board_send_authz_config.test.sql +84 -0
  59. package/tests/agent_board_send_authz_matrix.test.sql +159 -0
  60. package/tests/agent_board_send_role_gate.test.sql +177 -0
  61. package/tests/agent_board_signing_challenge_pending_cap.test.sql +87 -0
  62. package/tests/agent_board_signing_challenge_reaper.test.sql +74 -0
  63. package/tests/agent_board_thread_acl.test.sql +129 -0
  64. package/tests/agent_board_workload_providers_rls.test.sql +40 -0
@@ -2,8 +2,8 @@
2
2
  "schema": "agent_board_schema_manifest.v1",
3
3
  "interface": "IF-0-MBPLANE-BUNDLE-1",
4
4
  "package": "@consiliency/agent-board-schema",
5
- "packageVersion": "1.2.0",
6
- "schemaVersion": 7,
5
+ "packageVersion": "1.4.0",
6
+ "schemaVersion": 8,
7
7
  "minClientVersion": "1.1.0",
8
8
  "source": {
9
9
  "portalBaseline": "94f9c269a358a943498454003e8f05f47e27a491",
@@ -46,8 +46,8 @@
46
46
  "usage": "upsert (retention/monitoring defaults; on conflict do nothing)"
47
47
  }
48
48
  ],
49
- "migrationCount": 44,
50
- "aggregateSha256": "256b9de4e36809ed16778541adb8a573325b59dbcb30d56a428dbfd8fa5d2435",
49
+ "migrationCount": 71,
50
+ "aggregateSha256": "794661727d22a39e2d42ddfc5d6456fb93165cef68af11fb361385c29dbdead9",
51
51
  "migrations": [
52
52
  {
53
53
  "order": 0,
@@ -444,6 +444,249 @@
444
444
  "sourceRef": "message-board",
445
445
  "bytes": 3464,
446
446
  "sha256": "ba2c55bd11600d3dc86d54f02acd48ef4de711aaedce425e6bbfc85284a46c74"
447
+ },
448
+ {
449
+ "order": 44,
450
+ "id": "20260714000200_agent_board_workload_providers_rls",
451
+ "filename": "20260714000200_agent_board_workload_providers_rls.sql",
452
+ "origin": "message-board-forward",
453
+ "sourceRef": "message-board",
454
+ "bytes": 2159,
455
+ "sha256": "37a6f94d47b0948b1d5f384e52101eb48d9a15ba96a351669c87b907cc8a03e1"
456
+ },
457
+ {
458
+ "order": 45,
459
+ "id": "20260717000100_agent_board_board_role",
460
+ "filename": "20260717000100_agent_board_board_role.sql",
461
+ "origin": "message-board-forward",
462
+ "sourceRef": "message-board",
463
+ "bytes": 7246,
464
+ "sha256": "969980a7b52c6fee9329d2367006e5ce6d36b6471b53fee2247e493ac8ae512f"
465
+ },
466
+ {
467
+ "order": 46,
468
+ "id": "20260717000200_agent_board_thread_acl",
469
+ "filename": "20260717000200_agent_board_thread_acl.sql",
470
+ "origin": "message-board-forward",
471
+ "sourceRef": "message-board",
472
+ "bytes": 6326,
473
+ "sha256": "afb323d6f9189f1f37a7fee3ffceaaa17474059d46a1fdd84adb73c28bfb27ba"
474
+ },
475
+ {
476
+ "order": 47,
477
+ "id": "20260717000300_agent_board_agent_lineage",
478
+ "filename": "20260717000300_agent_board_agent_lineage.sql",
479
+ "origin": "message-board-forward",
480
+ "sourceRef": "message-board",
481
+ "bytes": 2094,
482
+ "sha256": "48aff20015f74e5f201a2985f71b33cd3e718aeb9dfb25fbd5720cb824fe85b3"
483
+ },
484
+ {
485
+ "order": 48,
486
+ "id": "20260717000400_agent_board_delegation_policies",
487
+ "filename": "20260717000400_agent_board_delegation_policies.sql",
488
+ "origin": "message-board-forward",
489
+ "sourceRef": "message-board",
490
+ "bytes": 7551,
491
+ "sha256": "ef7827af400e8a6206c17c8cc35623c280a92d25f4b678314847ff308d9d9dc7"
492
+ },
493
+ {
494
+ "order": 49,
495
+ "id": "20260718000100_agent_board_send_authz_config",
496
+ "filename": "20260718000100_agent_board_send_authz_config.sql",
497
+ "origin": "message-board-forward",
498
+ "sourceRef": "message-board",
499
+ "bytes": 7775,
500
+ "sha256": "d56909d4fbb7054e74818903d3eea84451d561c60908026dbbdb1956f500565d"
501
+ },
502
+ {
503
+ "order": 50,
504
+ "id": "20260718000200_agent_board_send_role_gate",
505
+ "filename": "20260718000200_agent_board_send_role_gate.sql",
506
+ "origin": "message-board-forward",
507
+ "sourceRef": "message-board",
508
+ "bytes": 26707,
509
+ "sha256": "805afdef2c11e52966bec1cd47a55dfd5e687af9181316d9ef71975b516de288"
510
+ },
511
+ {
512
+ "order": 51,
513
+ "id": "20260718000300_agent_board_fleet_policy_default_deny",
514
+ "filename": "20260718000300_agent_board_fleet_policy_default_deny.sql",
515
+ "origin": "message-board-forward",
516
+ "sourceRef": "message-board",
517
+ "bytes": 9060,
518
+ "sha256": "47ad42dd50fb31ede0cb6044a22cce04a31168af2dead73ed8e01dabf84fddea"
519
+ },
520
+ {
521
+ "order": 52,
522
+ "id": "20260718000400_agent_board_grant_ceiling_wiring",
523
+ "filename": "20260718000400_agent_board_grant_ceiling_wiring.sql",
524
+ "origin": "message-board-forward",
525
+ "sourceRef": "message-board",
526
+ "bytes": 2626,
527
+ "sha256": "0461f6d403fb1dd07e9423db898cd41a15a3ecfbf75402cd0085791d57a646b7"
528
+ },
529
+ {
530
+ "order": 53,
531
+ "id": "20260718000500_agent_board_fleet_agent_session_class",
532
+ "filename": "20260718000500_agent_board_fleet_agent_session_class.sql",
533
+ "origin": "message-board-forward",
534
+ "sourceRef": "message-board",
535
+ "bytes": 1341,
536
+ "sha256": "028f931aaba673fba29dd92ef3c17efcc08cb50b0dab01789986652a54fb003a"
537
+ },
538
+ {
539
+ "order": 54,
540
+ "id": "20260718000600_agent_board_mint_fleet_agent_session",
541
+ "filename": "20260718000600_agent_board_mint_fleet_agent_session.sql",
542
+ "origin": "message-board-forward",
543
+ "sourceRef": "message-board",
544
+ "bytes": 7945,
545
+ "sha256": "25972553fd1ec4f350e751495d79695a8b0ccc66c0b8b272fdf517115ba37307"
546
+ },
547
+ {
548
+ "order": 55,
549
+ "id": "20260718000700_agent_board_nonfleet_session_reaper",
550
+ "filename": "20260718000700_agent_board_nonfleet_session_reaper.sql",
551
+ "origin": "message-board-forward",
552
+ "sourceRef": "message-board",
553
+ "bytes": 1490,
554
+ "sha256": "9b6ff596ac386c49530c9405535b5255dcb09c271288957711a0d553446ef161"
555
+ },
556
+ {
557
+ "order": 56,
558
+ "id": "20260718000800_agent_board_regrant_scoped_send",
559
+ "filename": "20260718000800_agent_board_regrant_scoped_send.sql",
560
+ "origin": "message-board-forward",
561
+ "sourceRef": "message-board",
562
+ "bytes": 2072,
563
+ "sha256": "1332cf2ee23d875cf0b3d646890a0fffe0efc6d2312da4e315d5dc0abadaf74f"
564
+ },
565
+ {
566
+ "order": 57,
567
+ "id": "20260718000900_agent_board_agents_status_revoked",
568
+ "filename": "20260718000900_agent_board_agents_status_revoked.sql",
569
+ "origin": "message-board-forward",
570
+ "sourceRef": "message-board",
571
+ "bytes": 1424,
572
+ "sha256": "f0e9b95c286bb4b6873cdc882e30262788636886360c0100b745569d6553a51b"
573
+ },
574
+ {
575
+ "order": 58,
576
+ "id": "20260718001000_agent_board_delegation_enforcement",
577
+ "filename": "20260718001000_agent_board_delegation_enforcement.sql",
578
+ "origin": "message-board-forward",
579
+ "sourceRef": "message-board",
580
+ "bytes": 14005,
581
+ "sha256": "7e93bd1ff924b06922bb9312684f57ce81c3d3e921dbf52357ae5059a01dbc8b"
582
+ },
583
+ {
584
+ "order": 59,
585
+ "id": "20260718001100_agent_board_revoke_agent_subtree",
586
+ "filename": "20260718001100_agent_board_revoke_agent_subtree.sql",
587
+ "origin": "message-board-forward",
588
+ "sourceRef": "message-board",
589
+ "bytes": 9849,
590
+ "sha256": "bcf14370a3da288ebe43e337d050f5fb6fb736fad69c9f6b7a0b6e0992371b7e"
591
+ },
592
+ {
593
+ "order": 60,
594
+ "id": "20260718001200_agent_board_resigning_refresh",
595
+ "filename": "20260718001200_agent_board_resigning_refresh.sql",
596
+ "origin": "message-board-forward",
597
+ "sourceRef": "message-board",
598
+ "bytes": 4806,
599
+ "sha256": "47248250ab9bc11abdea8533d62611efba9812b913dc2dc5405612eb2fbafd12"
600
+ },
601
+ {
602
+ "order": 61,
603
+ "id": "20260718001300_agent_board_identity_providers",
604
+ "filename": "20260718001300_agent_board_identity_providers.sql",
605
+ "origin": "message-board-forward",
606
+ "sourceRef": "message-board",
607
+ "bytes": 4108,
608
+ "sha256": "ab8169709faf7f74b27a593a7c980022ee073f0371b39c0dda18f2202c0f4eae"
609
+ },
610
+ {
611
+ "order": 62,
612
+ "id": "20260718001400_agent_board_enrollment_federation_audit",
613
+ "filename": "20260718001400_agent_board_enrollment_federation_audit.sql",
614
+ "origin": "message-board-forward",
615
+ "sourceRef": "message-board",
616
+ "bytes": 2822,
617
+ "sha256": "c42e5122625639155d9ff1d2511ebdec3ac379ca0660e67c159e37efb8ea24f5"
618
+ },
619
+ {
620
+ "order": 63,
621
+ "id": "20260718001500_agent_board_enroll_federated_identity",
622
+ "filename": "20260718001500_agent_board_enroll_federated_identity.sql",
623
+ "origin": "message-board-forward",
624
+ "sourceRef": "message-board",
625
+ "bytes": 9490,
626
+ "sha256": "497d1f657d78c56744017409f5bd2ca361cd691ee1d0bab5b9a55ab3dd4b8ae7"
627
+ },
628
+ {
629
+ "order": 64,
630
+ "id": "20260719000100_agent_board_signing_challenge_pending_cap",
631
+ "filename": "20260719000100_agent_board_signing_challenge_pending_cap.sql",
632
+ "origin": "message-board-forward",
633
+ "sourceRef": "message-board",
634
+ "bytes": 4280,
635
+ "sha256": "1e48b413d31f11b9c120480c8453c8502fdde1c3a7997dcd9e22e957a2459b0b"
636
+ },
637
+ {
638
+ "order": 65,
639
+ "id": "20260719000200_agent_board_signing_challenge_reaper",
640
+ "filename": "20260719000200_agent_board_signing_challenge_reaper.sql",
641
+ "origin": "message-board-forward",
642
+ "sourceRef": "message-board",
643
+ "bytes": 1472,
644
+ "sha256": "488bbf16bf526b7df7c9b3fa5b068964703aa04ba657d6e6ceaff856a9523634"
645
+ },
646
+ {
647
+ "order": 66,
648
+ "id": "20260719000300_agent_board_enroll_rate_cap",
649
+ "filename": "20260719000300_agent_board_enroll_rate_cap.sql",
650
+ "origin": "message-board-forward",
651
+ "sourceRef": "message-board",
652
+ "bytes": 3195,
653
+ "sha256": "402c6d8bc5d4662b10d6fe0228a2bdd30026be96a5b5a237e26e1d44a639ae1d"
654
+ },
655
+ {
656
+ "order": 67,
657
+ "id": "20260719001000_agent_board_identity_providers_portal_contract",
658
+ "filename": "20260719001000_agent_board_identity_providers_portal_contract.sql",
659
+ "origin": "message-board-forward",
660
+ "sourceRef": "message-board",
661
+ "bytes": 2263,
662
+ "sha256": "e0406afebf9b36f3793e04ca757a252f038b4100697598e0a2e76050193674a0"
663
+ },
664
+ {
665
+ "order": 68,
666
+ "id": "20260719001100_agent_board_enroll_federated_identity_pop",
667
+ "filename": "20260719001100_agent_board_enroll_federated_identity_pop.sql",
668
+ "origin": "message-board-forward",
669
+ "sourceRef": "message-board",
670
+ "bytes": 12153,
671
+ "sha256": "3d4517243a7d228fb04ea41e9dc7236c870cb5830fb2f815bfa12426eee47bd7"
672
+ },
673
+ {
674
+ "order": 69,
675
+ "id": "20260719001200_agent_board_enroll_federated_identity_raw_fingerprint",
676
+ "filename": "20260719001200_agent_board_enroll_federated_identity_raw_fingerprint.sql",
677
+ "origin": "message-board-forward",
678
+ "sourceRef": "message-board",
679
+ "bytes": 13630,
680
+ "sha256": "70943047b522ec23ee3d451f704c961d130a053bc210dad8759cbc9a22444454"
681
+ },
682
+ {
683
+ "order": 70,
684
+ "id": "20260719001300_agent_board_enroll_rate_events_reaper_rls",
685
+ "filename": "20260719001300_agent_board_enroll_rate_events_reaper_rls.sql",
686
+ "origin": "message-board-forward",
687
+ "sourceRef": "message-board",
688
+ "bytes": 3112,
689
+ "sha256": "b2f58eadc13cb227991c4d978c5460878dc3cbada43c58285b55154b89a28459"
447
690
  }
448
691
  ],
449
692
  "excludedBridge": {
@@ -34,9 +34,50 @@ describe('manifest integrity', () => {
34
34
  expect(manifest.source.portalBaseline).toMatch(/^[0-9a-f]{40}$/);
35
35
  expect(manifest.source.laneHead).toMatch(/^[0-9a-f]{40}$/);
36
36
  expect(manifest.source.mergeBase).toMatch(/^[0-9a-f]{40}$/);
37
- expect(manifest.schemaVersion).toBe(7);
37
+ expect(manifest.schemaVersion).toBe(8);
38
38
  expect(manifest.minClientVersion).toMatch(/^\d+\.\d+\.\d+$/);
39
39
  });
40
+ it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 forward migrations (migrationCount = prior 67 + 4 = 71)', () => {
41
+ expect(manifest.migrationCount).toBe(manifest.migrations.length);
42
+ expect(manifest.migrationCount).toBe(71);
43
+ // MBSESSION switched enforcement on (scoped-send re-grant): the client-visible
44
+ // schema version bumped to 8. MBDELEG tightens delegation and MBFED adds only
45
+ // enrollment-only additive surface (identity_providers registry, enrollment audit,
46
+ // the enroll RPC, and the /enroll EF) with no client-visible write-path shape
47
+ // change, so the version stays 8. MBHARDEN adds three additive pre-live hardening
48
+ // migrations (challenge pending-cap, challenge reaper, /enroll rate cap) with no
49
+ // client-visible write-path shape change, so the version stays 8.
50
+ expect(manifest.schemaVersion).toBe(8);
51
+ const names = new Set(manifest.migrations.map((m) => m.filename));
52
+ for (const f of [
53
+ '20260717000100_agent_board_board_role.sql',
54
+ '20260717000200_agent_board_thread_acl.sql',
55
+ '20260717000300_agent_board_agent_lineage.sql',
56
+ '20260717000400_agent_board_delegation_policies.sql',
57
+ '20260718000100_agent_board_send_authz_config.sql',
58
+ '20260718000200_agent_board_send_role_gate.sql',
59
+ '20260718000300_agent_board_fleet_policy_default_deny.sql',
60
+ '20260718000400_agent_board_grant_ceiling_wiring.sql',
61
+ '20260718000500_agent_board_fleet_agent_session_class.sql',
62
+ '20260718000600_agent_board_mint_fleet_agent_session.sql',
63
+ '20260718000700_agent_board_nonfleet_session_reaper.sql',
64
+ '20260718000800_agent_board_regrant_scoped_send.sql',
65
+ '20260718000900_agent_board_agents_status_revoked.sql',
66
+ '20260718001000_agent_board_delegation_enforcement.sql',
67
+ '20260718001100_agent_board_revoke_agent_subtree.sql',
68
+ '20260718001200_agent_board_resigning_refresh.sql',
69
+ '20260718001300_agent_board_identity_providers.sql',
70
+ '20260718001400_agent_board_enrollment_federation_audit.sql',
71
+ '20260718001500_agent_board_enroll_federated_identity.sql',
72
+ '20260719000100_agent_board_signing_challenge_pending_cap.sql',
73
+ '20260719000200_agent_board_signing_challenge_reaper.sql',
74
+ '20260719000300_agent_board_enroll_rate_cap.sql',
75
+ '20260719001200_agent_board_enroll_federated_identity_raw_fingerprint.sql',
76
+ '20260719001300_agent_board_enroll_rate_events_reaper_rls.sql',
77
+ ]) {
78
+ expect(names.has(f)).toBe(true);
79
+ }
80
+ });
40
81
  });
41
82
 
42
83
  describe('materialize (sole adopter path)', () => {
@@ -0,0 +1,73 @@
1
+ -- MBAUTHZ SL-3 (Lane C) pgTAP: agents.parent_agent_id / root_agent_id and the
2
+ -- owner-consistency trigger.
3
+ create extension if not exists pgtap with schema extensions;
4
+ set search_path = extensions, public, agent_board;
5
+
6
+ begin;
7
+
8
+ select plan(8);
9
+
10
+ create temp table _lineage_fixture (
11
+ root_agent uuid,
12
+ root_user uuid,
13
+ other_user uuid
14
+ ) on commit drop;
15
+
16
+ do $$
17
+ declare
18
+ v_suffix text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
19
+ v_root_user uuid;
20
+ v_other_user uuid;
21
+ v_root uuid := gen_random_uuid();
22
+ begin
23
+ v_root_user := public.ensure_github_user('lin-root-' || v_suffix, 'lin-root-' || v_suffix || '@example.test', 'lin-root-' || v_suffix, 'LIN Root');
24
+ v_other_user := public.ensure_github_user('lin-other-' || v_suffix, 'lin-other-' || v_suffix || '@example.test', 'lin-other-' || v_suffix, 'LIN Other');
25
+
26
+ insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
27
+ values (v_root, 'agent', 'lin-root-' || v_suffix, 'LIN Root', 'active', v_root_user, v_root_user);
28
+
29
+ insert into _lineage_fixture values (v_root, v_root_user, v_other_user);
30
+ end
31
+ $$;
32
+
33
+ -- Structure -----------------------------------------------------------------
34
+ select has_column('agent_board', 'agents', 'parent_agent_id', 'agents has parent_agent_id');
35
+ select has_column('agent_board', 'agents', 'root_agent_id', 'agents has root_agent_id');
36
+ select col_type_is('agent_board', 'agents', 'parent_agent_id', 'uuid', 'parent_agent_id is uuid');
37
+ select fk_ok('agent_board', 'agents', 'parent_agent_id', 'agent_board', 'agents', 'id',
38
+ 'parent_agent_id references agents(id)');
39
+ select fk_ok('agent_board', 'agents', 'root_agent_id', 'agent_board', 'agents', 'id',
40
+ 'root_agent_id references agents(id)');
41
+
42
+ -- Owner-consistency trigger -------------------------------------------------
43
+ -- Child whose owner_user_id differs from its root's owner is rejected.
44
+ select throws_ok(
45
+ (select format(
46
+ $q$insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id, parent_agent_id, root_agent_id)
47
+ values (gen_random_uuid(), 'agent', %L, 'LIN Bad Child', 'active', %L::uuid, %L::uuid, %L::uuid)$q$,
48
+ 'lin-badchild-' || gen_random_uuid()::text, other_user, root_agent, root_agent)
49
+ from _lineage_fixture),
50
+ '23514', null,
51
+ 'trigger rejects a parented agent whose owner differs from its root');
52
+
53
+ -- Child whose owner matches its root's owner is allowed.
54
+ select lives_ok(
55
+ (select format(
56
+ $q$insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id, parent_agent_id, root_agent_id)
57
+ values (gen_random_uuid(), 'agent', %L, 'LIN Good Child', 'active', %L::uuid, %L::uuid, %L::uuid)$q$,
58
+ 'lin-goodchild-' || gen_random_uuid()::text, root_user, root_agent, root_agent)
59
+ from _lineage_fixture),
60
+ 'trigger allows a parented agent whose owner matches its root');
61
+
62
+ -- Unparented agent (NULL lineage) is unaffected regardless of owner.
63
+ select lives_ok(
64
+ (select format(
65
+ $q$insert into agent_board.agents(id, agent_type, handle, display_name, status, owner_user_id)
66
+ values (gen_random_uuid(), 'agent', %L, 'LIN Orphan', 'active', %L::uuid)$q$,
67
+ 'lin-orphan-' || gen_random_uuid()::text, other_user)
68
+ from _lineage_fixture),
69
+ 'unparented agent with NULL lineage is unaffected by the trigger');
70
+
71
+ select finish();
72
+
73
+ rollback;
@@ -0,0 +1,60 @@
1
+ -- MBDELEG SL-1 pgTAP (IF-0-MBDELEG-1): agents.status admits 'revoked'.
2
+ --
3
+ -- Proves the CHECK-extension at 20260718000900 accepts the 4th literal 'revoked'
4
+ -- alongside the 3 legacy literals, rejects an unknown literal, and that a write of
5
+ -- status='revoked' (as the cascade performs) no longer throws check_violation.
6
+ --
7
+ -- Runs as postgres (service_role-equivalent).
8
+ create extension if not exists pgtap with schema extensions;
9
+ set search_path = extensions, public, agent_board;
10
+
11
+ begin;
12
+
13
+ select plan(6);
14
+
15
+ create temp table _srv_fx (a_upd uuid) on commit drop;
16
+
17
+ do $$
18
+ declare
19
+ s text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
20
+ v_upd uuid := gen_random_uuid();
21
+ begin
22
+ -- Seed one agent to update to 'revoked' below.
23
+ insert into agent_board.agents(id, agent_type, handle, display_name, status)
24
+ values (v_upd, 'agent', 'srv-upd-'||s, 'SRV-UPD', 'active');
25
+ insert into _srv_fx values (v_upd);
26
+ end
27
+ $$;
28
+
29
+ -- Insert-side acceptance of each literal (distinct handles).
30
+ select lives_ok(
31
+ $q$insert into agent_board.agents(agent_type, handle, display_name, status)
32
+ values ('agent', 'srv-active-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), 'A', 'active')$q$,
33
+ 'agents.status accepts legacy literal ''active''');
34
+ select lives_ok(
35
+ $q$insert into agent_board.agents(agent_type, handle, display_name, status)
36
+ values ('agent', 'srv-inactive-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), 'I', 'inactive')$q$,
37
+ 'agents.status accepts legacy literal ''inactive''');
38
+ select lives_ok(
39
+ $q$insert into agent_board.agents(agent_type, handle, display_name, status)
40
+ values ('agent', 'srv-suspended-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), 'S', 'suspended')$q$,
41
+ 'agents.status accepts legacy literal ''suspended''');
42
+ select lives_ok(
43
+ $q$insert into agent_board.agents(agent_type, handle, display_name, status)
44
+ values ('agent', 'srv-revoked-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), 'R', 'revoked')$q$,
45
+ 'agents.status accepts the new literal ''revoked''');
46
+
47
+ -- An unknown literal is rejected by agents_status_check.
48
+ select throws_ok(
49
+ $q$insert into agent_board.agents(agent_type, handle, display_name, status)
50
+ values ('agent', 'srv-bogus-'||replace(substr(gen_random_uuid()::text,1,8),'-',''), 'B', 'bogus')$q$,
51
+ '23514', null, 'agents.status rejects an unknown literal (check_violation)');
52
+
53
+ -- A subtree write of status='revoked' (what the cascade performs) succeeds.
54
+ select lives_ok(
55
+ (select format($q$update agent_board.agents set status = 'revoked' where id = %L::uuid$q$, a_upd) from _srv_fx),
56
+ 'updating an existing agent to status=''revoked'' no longer throws check_violation');
57
+
58
+ select finish();
59
+
60
+ rollback;
@@ -155,13 +155,15 @@ begin
155
155
 
156
156
  v_previous_credential_id := v_agent_one.credential_id;
157
157
 
158
- select *
159
- into v_rotated
160
- from agent_board.rotate_agent_token(v_agent_one.agent_id);
161
-
162
- if v_rotated.replaces_credential_id is distinct from v_previous_credential_id then
163
- raise exception 'rotate_agent_token did not record replaces_credential_id';
164
- end if;
158
+ -- MBDELEG (20260718001200): the 30-day refresh-token exchange is retired.
159
+ -- rotate_agent_token now raises 0A000; session refresh is re-signing
160
+ -- (PoP -> mint_fleet_agent_session), not a refresh-token rotation.
161
+ begin
162
+ perform * from agent_board.rotate_agent_token(v_agent_one.agent_id);
163
+ raise exception 'rotate_agent_token unexpectedly succeeded (should be deprecated)';
164
+ exception
165
+ when sqlstate '0A000' then null;
166
+ end;
165
167
 
166
168
  select *
167
169
  into v_revoked
@@ -263,12 +265,11 @@ $$;
263
265
 
264
266
  select pass('AUTHRPC preserved the service_role callback-runtime path');
265
267
 
266
- -- portal#203 (defense in depth): rotate_agent_token routes through the same
267
- -- _issue_agent_credential choke point, so a pre-existing system principal must
268
- -- also fail closed on rotation. Create one directly (provision_agent now rejects
269
- -- system) and assert rotation raises and mints nothing. Run as the superuser so
270
- -- the direct agents insert is permitted; rotate_agent_token still bypasses the
271
- -- platform-admin gate for a service/superuser caller, so the guard is exercised.
268
+ -- portal#203 + MBDELEG (20260718001200, defense in depth): rotate_agent_token is
269
+ -- now deprecated and raises 0A000 for ALL callers (session refresh is re-signing),
270
+ -- which subsumes the earlier system-principal fail-closed path. A pre-existing
271
+ -- system principal must still get no credential from rotation. Create one directly
272
+ -- (provision_agent rejects system) and assert rotation raises 0A000 and mints nothing.
272
273
  reset role;
273
274
  do $$
274
275
  declare
@@ -288,12 +289,12 @@ begin
288
289
  perform * from agent_board.rotate_agent_token(v_system_agent_id);
289
290
  raise exception 'rotate_agent_token unexpectedly succeeded for a system principal';
290
291
  exception
291
- when sqlstate '22023' then
292
+ when sqlstate '0A000' then
292
293
  v_raised := true;
293
294
  end;
294
295
 
295
296
  if not v_raised then
296
- raise exception 'rotate_agent_token did not fail closed with sqlstate 22023 for a system principal';
297
+ raise exception 'rotate_agent_token did not fail closed with sqlstate 0A000 (deprecated) for a system principal';
297
298
  end if;
298
299
 
299
300
  if exists (
@@ -304,7 +305,7 @@ begin
304
305
  end
305
306
  $$;
306
307
 
307
- select pass('AUTHRPC rotate_agent_token also fails closed for system principals');
308
+ select pass('AUTHRPC rotate_agent_token is deprecated (0A000) and mints no credential');
308
309
 
309
310
  select finish();
310
311
 
@@ -0,0 +1,129 @@
1
+ -- MBAUTHZ SL-1 (Lane A) pgTAP: board_memberships.role, boards.role_gate_enforced,
2
+ -- and the role gate on invite_board_member/resolve_board_request.
3
+ create extension if not exists pgtap with schema extensions;
4
+ set search_path = extensions, public, agent_board;
5
+
6
+ begin;
7
+
8
+ select plan(13);
9
+
10
+ create temp table _board_role_fixture (
11
+ board uuid,
12
+ creator_agent uuid,
13
+ member_agent uuid,
14
+ admin_agent uuid,
15
+ target_agent uuid,
16
+ other_agent uuid,
17
+ creator_user uuid,
18
+ member_user uuid,
19
+ admin_user uuid
20
+ ) on commit drop;
21
+
22
+ do $$
23
+ declare
24
+ v_suffix text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
25
+ v_creator_user uuid;
26
+ v_member_user uuid;
27
+ v_admin_user uuid;
28
+ v_creator uuid := gen_random_uuid();
29
+ v_member uuid := gen_random_uuid();
30
+ v_admin uuid := gen_random_uuid();
31
+ v_target uuid := gen_random_uuid();
32
+ v_other uuid := gen_random_uuid();
33
+ v_board uuid;
34
+ begin
35
+ v_creator_user := public.ensure_github_user('br-creator-' || v_suffix, 'br-creator-' || v_suffix || '@example.test', 'br-creator-' || v_suffix, 'BR Creator');
36
+ v_member_user := public.ensure_github_user('br-member-' || v_suffix, 'br-member-' || v_suffix || '@example.test', 'br-member-' || v_suffix, 'BR Member');
37
+ v_admin_user := public.ensure_github_user('br-admin-' || v_suffix, 'br-admin-' || v_suffix || '@example.test', 'br-admin-' || v_suffix, 'BR Admin');
38
+
39
+ insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
40
+ values
41
+ (v_creator, 'agent', 'br-creator-' || v_suffix, 'BR Creator', 'active', v_creator_user, v_creator_user),
42
+ (v_member, 'agent', 'br-member-' || v_suffix, 'BR Member', 'active', v_member_user, v_member_user),
43
+ (v_admin, 'agent', 'br-admin-' || v_suffix, 'BR Admin', 'active', v_admin_user, v_admin_user),
44
+ (v_target, 'agent', 'br-target-' || v_suffix, 'BR Target', 'active', null, null),
45
+ (v_other, 'agent', 'br-other-' || v_suffix, 'BR Other', 'active', null, null);
46
+
47
+ select board_id into v_board
48
+ from agent_board.create_board(v_creator, 'br-board-' || v_suffix, 'BR Board', 'board role test', '{}'::jsonb, 'private');
49
+
50
+ -- Creator + other memberships inserted with the column DEFAULT (member) to
51
+ -- exercise the one-time backfill; member/admin get explicit roles for gate tests.
52
+ insert into agent_board.board_memberships(board_id, agent_id, membership_state, created_by_agent_id)
53
+ values
54
+ (v_board, v_creator, 'joined', v_creator),
55
+ (v_board, v_other, 'joined', v_creator);
56
+ insert into agent_board.board_memberships(board_id, agent_id, membership_state, created_by_agent_id, role)
57
+ values
58
+ (v_board, v_member, 'joined', v_creator, 'member'),
59
+ (v_board, v_admin, 'joined', v_creator, 'admin');
60
+
61
+ insert into _board_role_fixture values
62
+ (v_board, v_creator, v_member, v_admin, v_target, v_other, v_creator_user, v_member_user, v_admin_user);
63
+ end
64
+ $$;
65
+
66
+ -- Structure -----------------------------------------------------------------
67
+ select has_column('agent_board', 'board_memberships', 'role', 'board_memberships has role column');
68
+ select col_type_is('agent_board', 'board_memberships', 'role', 'text', 'role is text');
69
+ select col_has_check('agent_board', 'board_memberships', 'role', 'role is check-constrained');
70
+ select has_column('agent_board', 'boards', 'role_gate_enforced', 'boards has role_gate_enforced flag');
71
+ select col_type_is('agent_board', 'boards', 'role_gate_enforced', 'boolean', 'role_gate_enforced is boolean');
72
+
73
+ -- Default behavior (member membership above was inserted without role) -------
74
+ select is(
75
+ (select role from agent_board.board_memberships bm join _board_role_fixture f on bm.agent_id = f.member_agent and bm.board_id = f.board),
76
+ 'member', 'explicit member role persists');
77
+
78
+ -- One-time backfill: creator membership -> owner; other membership -> member.
79
+ update agent_board.board_memberships bm
80
+ set role = 'owner'
81
+ from agent_board.boards b
82
+ where bm.board_id = b.id
83
+ and bm.agent_id = b.created_by_agent_id
84
+ and bm.role <> 'owner';
85
+
86
+ select is(
87
+ (select role from agent_board.board_memberships bm join _board_role_fixture f on bm.agent_id = f.creator_agent and bm.board_id = f.board),
88
+ 'owner', 'backfill sets creator membership role to owner');
89
+ select is(
90
+ (select role from agent_board.board_memberships bm join _board_role_fixture f on bm.agent_id = f.other_agent and bm.board_id = f.board),
91
+ 'member', 'backfill leaves non-creator membership as member');
92
+
93
+ -- Gate: flag=false (default) preserves any-active-member behavior ------------
94
+ -- Non-creator member (role member) may invite when the flag is off.
95
+ select set_config('request.jwt.claim.sub', (select member_user from _board_role_fixture)::text, true);
96
+ select lives_ok(
97
+ (select format($q$select agent_board.invite_board_member(%L::uuid, %L::uuid, %L::uuid, '{}'::jsonb)$q$, board, target_agent, creator_agent) from _board_role_fixture),
98
+ 'flag off: active member (role member) may invite');
99
+
100
+ -- Service_role / null actor (no agent maps to this sub) bypasses.
101
+ select set_config('request.jwt.claim.sub', gen_random_uuid()::text, true);
102
+ select lives_ok(
103
+ (select format($q$select agent_board.invite_board_member(%L::uuid, %L::uuid, %L::uuid, '{}'::jsonb)$q$, board, target_agent, creator_agent) from _board_role_fixture),
104
+ 'flag off: service_role/null actor may invite');
105
+
106
+ -- Gate: flag=true requires owner/admin ---------------------------------------
107
+ update agent_board.boards set role_gate_enforced = true
108
+ where id = (select board from _board_role_fixture);
109
+
110
+ select set_config('request.jwt.claim.sub', (select member_user from _board_role_fixture)::text, true);
111
+ select throws_ok(
112
+ (select format($q$select agent_board.invite_board_member(%L::uuid, %L::uuid, %L::uuid, '{}'::jsonb)$q$, board, target_agent, creator_agent) from _board_role_fixture),
113
+ '42501', null,
114
+ 'flag on: role member actor is denied invite');
115
+
116
+ select set_config('request.jwt.claim.sub', (select admin_user from _board_role_fixture)::text, true);
117
+ select lives_ok(
118
+ (select format($q$select agent_board.invite_board_member(%L::uuid, %L::uuid, %L::uuid, '{}'::jsonb)$q$, board, target_agent, creator_agent) from _board_role_fixture),
119
+ 'flag on: role admin actor may invite');
120
+
121
+ -- Service_role / null actor still bypasses even with the flag on.
122
+ select set_config('request.jwt.claim.sub', gen_random_uuid()::text, true);
123
+ select lives_ok(
124
+ (select format($q$select agent_board.invite_board_member(%L::uuid, %L::uuid, %L::uuid, '{}'::jsonb)$q$, board, target_agent, creator_agent) from _board_role_fixture),
125
+ 'flag on: service_role/null actor still bypasses');
126
+
127
+ select finish();
128
+
129
+ rollback;