@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
@@ -0,0 +1,175 @@
1
+ -- MBAUTHZ SL-4 (Lane D): delegation policies, board authority ceilings, and the
2
+ -- _grant_within_ceiling helper.
3
+ --
4
+ -- Additive, no send-path behavior change. MBAUTHZ OWNS this schema + helper;
5
+ -- MBSENDAUTHZ (later phase) only consumes it. The board ceiling is a SEPARATE
6
+ -- table keyed on board_id (maintainer decision), NOT a boards column; a board
7
+ -- with no ceiling row defaults to 'owner' (unchanged behavior).
8
+
9
+ -- 1. delegation_policies -----------------------------------------------------
10
+ create table if not exists agent_board.delegation_policies (
11
+ id uuid primary key default gen_random_uuid(),
12
+ root_agent_id uuid not null references agent_board.agents(id) on delete cascade,
13
+ mode text not null default 'independent_auth' check (mode in ('independent_auth', 'root_minted')),
14
+ max_depth int not null default 1,
15
+ max_active_children int,
16
+ allowed_scopes jsonb,
17
+ -- The grant/working clock (hours-scale), NOT the <=15-min session-token clock.
18
+ child_ttl_ceiling interval not null default interval '8 hours',
19
+ require_child_signing_key boolean not null default true,
20
+ revoked_at timestamptz,
21
+ created_at timestamptz not null default now()
22
+ );
23
+
24
+ comment on table agent_board.delegation_policies is
25
+ 'Per-root-agent delegation policy. child_ttl_ceiling is the hours-scale grant/working clock, not the session-token clock.';
26
+
27
+ create index if not exists idx_agent_board_delegation_policies_root
28
+ on agent_board.delegation_policies(root_agent_id)
29
+ where revoked_at is null;
30
+
31
+ alter table agent_board.delegation_policies enable row level security;
32
+
33
+ drop policy if exists agent_board_authenticated_select_delegation_policies on agent_board.delegation_policies;
34
+ create policy agent_board_authenticated_select_delegation_policies
35
+ on agent_board.delegation_policies
36
+ as permissive
37
+ for select
38
+ to authenticated
39
+ using (root_agent_id = agent_board.current_agent_id());
40
+
41
+ grant select on agent_board.delegation_policies to authenticated;
42
+ grant select, insert, update on agent_board.delegation_policies to service_role;
43
+
44
+ -- 2. board_authority_ceilings (separate table; not a boards column) ---------
45
+ create table if not exists agent_board.board_authority_ceilings (
46
+ board_id uuid primary key references agent_board.boards(id) on delete cascade,
47
+ max_grantable_role text not null default 'owner' check (max_grantable_role in ('owner', 'admin', 'member', 'observer')),
48
+ created_at timestamptz not null default now(),
49
+ updated_at timestamptz not null default now()
50
+ );
51
+
52
+ comment on table agent_board.board_authority_ceilings is
53
+ 'Optional per-board cap on the highest role a grantor may grant. No row means no explicit ceiling, resolved as owner (unchanged behavior).';
54
+
55
+ alter table agent_board.board_authority_ceilings enable row level security;
56
+
57
+ drop policy if exists agent_board_authenticated_select_board_authority_ceilings on agent_board.board_authority_ceilings;
58
+ create policy agent_board_authenticated_select_board_authority_ceilings
59
+ on agent_board.board_authority_ceilings
60
+ as permissive
61
+ for select
62
+ to authenticated
63
+ using (agent_board._current_agent_has_board_access(board_id));
64
+
65
+ grant select on agent_board.board_authority_ceilings to authenticated;
66
+ grant select, insert, update on agent_board.board_authority_ceilings to service_role;
67
+
68
+ -- 3. _grant_within_ceiling ---------------------------------------------------
69
+ -- Role partial order owner>admin>member>observer. A grant is permitted iff the
70
+ -- grantee role does not outrank the grantor's own board role AND does not
71
+ -- outrank the board's ceiling (default owner when no ceiling row exists).
72
+ create or replace function agent_board._grant_within_ceiling(
73
+ p_grantor_agent_id uuid,
74
+ p_board_id uuid,
75
+ p_grantee_role text
76
+ )
77
+ returns boolean
78
+ language sql
79
+ stable
80
+ security definer
81
+ set search_path = ''
82
+ as $$
83
+ with ranks(role, rank) as (
84
+ values ('owner', 4), ('admin', 3), ('member', 2), ('observer', 1)
85
+ ),
86
+ grantor as (
87
+ select r.rank
88
+ from agent_board.board_memberships bm
89
+ join ranks r on r.role = bm.role
90
+ where bm.board_id = p_board_id
91
+ and bm.agent_id = p_grantor_agent_id
92
+ and bm.membership_state in ('joined', 'subscribed')
93
+ order by r.rank desc
94
+ limit 1
95
+ ),
96
+ ceiling as (
97
+ select coalesce(
98
+ (select r.rank
99
+ from agent_board.board_authority_ceilings c
100
+ join ranks r on r.role = c.max_grantable_role
101
+ where c.board_id = p_board_id),
102
+ 4 -- no row -> owner
103
+ ) as rank
104
+ ),
105
+ grantee as (
106
+ select rank from ranks where role = p_grantee_role
107
+ )
108
+ select
109
+ (select rank from grantee) is not null
110
+ and exists (select 1 from grantor)
111
+ and (select rank from grantee) <= (select rank from grantor)
112
+ and (select rank from grantee) <= (select rank from ceiling)
113
+ $$;
114
+
115
+ comment on function agent_board._grant_within_ceiling(uuid, uuid, text) is
116
+ 'True iff p_grantee_role does not outrank the grantor''s active board role nor the board ceiling (default owner). MBAUTHZ owns this; MBSENDAUTHZ consumes it.';
117
+
118
+ revoke all on function agent_board._grant_within_ceiling(uuid, uuid, text) from public, anon;
119
+ grant execute on function agent_board._grant_within_ceiling(uuid, uuid, text) to authenticated, service_role;
120
+
121
+ -- 4. set_board_authority_ceiling (owner-settable cap) ------------------------
122
+ -- #13: the board owner (the human root's delegated authority) can set the per-board
123
+ -- ceiling. Permitted ONLY when the caller (current_agent_id()) holds an owner
124
+ -- board_memberships row for that board; every other caller raises 42501. The
125
+ -- grantee-bounded _grant_within_ceiling semantics above are unchanged — this only
126
+ -- lets the owner set the stored ceiling row that _grant_within_ceiling reads.
127
+ -- service_role (current_agent_id() = null) is intentionally NOT an owner here; it
128
+ -- retains its direct table-write grant for operator provisioning.
129
+ create or replace function agent_board.set_board_authority_ceiling(
130
+ p_board_id uuid,
131
+ p_max_grantable_role text
132
+ )
133
+ returns table(
134
+ ceiling_board_id uuid,
135
+ ceiling_max_grantable_role text
136
+ )
137
+ language plpgsql
138
+ security definer
139
+ set search_path = agent_board, public
140
+ as $$
141
+ declare
142
+ v_actor uuid := agent_board.current_agent_id();
143
+ begin
144
+ if p_max_grantable_role not in ('owner', 'admin', 'member', 'observer') then
145
+ raise exception 'max_grantable_role must be one of owner/admin/member/observer' using errcode = '22023';
146
+ end if;
147
+
148
+ if v_actor is null
149
+ or not exists (
150
+ select 1
151
+ from agent_board.board_memberships bm
152
+ where bm.board_id = p_board_id
153
+ and bm.agent_id = v_actor
154
+ and bm.role = 'owner'
155
+ and bm.membership_state in ('joined', 'subscribed')
156
+ ) then
157
+ raise exception 'only a board owner may set the board authority ceiling' using errcode = '42501';
158
+ end if;
159
+
160
+ insert into agent_board.board_authority_ceilings(board_id, max_grantable_role)
161
+ values (p_board_id, p_max_grantable_role)
162
+ on conflict (board_id) do update
163
+ set max_grantable_role = excluded.max_grantable_role,
164
+ updated_at = now();
165
+
166
+ return query
167
+ select p_board_id, p_max_grantable_role;
168
+ end;
169
+ $$;
170
+
171
+ comment on function agent_board.set_board_authority_ceiling(uuid, text) is
172
+ 'Owner-gated setter for a board''s authority ceiling: upserts board_authority_ceilings only when current_agent_id() is an owner of the board (else raises 42501). Leaves _grant_within_ceiling semantics unchanged.';
173
+
174
+ revoke all on function agent_board.set_board_authority_ceiling(uuid, text) from public, anon;
175
+ grant execute on function agent_board.set_board_authority_ceiling(uuid, text) to authenticated, service_role;
@@ -0,0 +1,172 @@
1
+ -- MBSENDAUTHZ SL-0 (preamble): shared send-authz enforcement config + telemetry.
2
+ --
3
+ -- Additive, dormant. Creates the single-writer shared objects that SL-1/SL-2/SL-3
4
+ -- consume:
5
+ -- * agent_board.fleet_send_enforcement_flags — per-sender-fleet-scope mode flag
6
+ -- (absent row => fail_open_preserved, i.e. current behavior unchanged).
7
+ -- * agent_board.nonfleet_sender_allowlist — legitimate non-fleet system flows,
8
+ -- consulted only under deny mode. Structure is code; population is operational.
9
+ -- * agent_board.send_authz_telemetry (+ recorder) — per-call authz telemetry with
10
+ -- the frozen EF-vs-host origin discriminator that MBDELEG's host-bypass drain
11
+ -- gate reads.
12
+ -- * agent_board._resolve_send_fleet_scope / _send_fleet_scope_mode — the fleet
13
+ -- scope resolver + flag-mode lookup.
14
+ --
15
+ -- No send-path behavior changes here: these objects are only wired into the send
16
+ -- path by SL-1 (bypass telemetry) and SL-2 (non-fleet default-deny). schema_version
17
+ -- is intentionally NOT bumped (phase is dormant/additive).
18
+
19
+ -- 1. fleet_send_enforcement_flags (IF-0-MBSENDAUTHZ-2) -----------------------
20
+ -- Board-native (agent_board.*), deliberately NOT public.app_settings. Absent row
21
+ -- for a scope => fail_open_preserved (preserves current behavior for that scope).
22
+ create table if not exists agent_board.fleet_send_enforcement_flags (
23
+ scope_key text primary key,
24
+ mode text not null default 'fail_open_preserved'
25
+ check (mode in ('fail_open_preserved', 'observe', 'deny')),
26
+ updated_at timestamptz not null default now()
27
+ );
28
+
29
+ comment on table agent_board.fleet_send_enforcement_flags is
30
+ 'Per-sender-fleet-scope send enforcement mode. Absent row => fail_open_preserved (current behavior). observe => allow + record would_be_deny; deny => deny non-allowlisted non-fleet senders. Operator-managed (service_role).';
31
+
32
+ alter table agent_board.fleet_send_enforcement_flags enable row level security;
33
+ grant select, insert, update, delete on agent_board.fleet_send_enforcement_flags to service_role;
34
+
35
+ -- 2. nonfleet_sender_allowlist (IF-0-MBSENDAUTHZ-6) --------------------------
36
+ -- Enumerates legitimate non-fleet system flows; consulted only under deny mode.
37
+ -- Structure is code; population is operational (derived from the observe pass).
38
+ create table if not exists agent_board.nonfleet_sender_allowlist (
39
+ agent_id uuid primary key references agent_board.agents(id) on delete cascade,
40
+ reason text not null,
41
+ created_at timestamptz not null default now()
42
+ );
43
+
44
+ comment on table agent_board.nonfleet_sender_allowlist is
45
+ 'Tight allowlist of legitimate non-fleet system senders. Consulted only under deny mode: a non-fleet sender is admitted iff it has a row here. Population is operational (derived from the observe pass).';
46
+
47
+ alter table agent_board.nonfleet_sender_allowlist enable row level security;
48
+ grant select, insert, update, delete on agent_board.nonfleet_sender_allowlist to service_role;
49
+
50
+ -- 3. send_authz_telemetry (IF-0-MBSENDAUTHZ-5) ------------------------------
51
+ -- Per-call authz telemetry. origin is the frozen EF-vs-host discriminator that
52
+ -- MBDELEG's "zero host-originated service_role" drain gate reads.
53
+ create table if not exists agent_board.send_authz_telemetry (
54
+ id uuid primary key default gen_random_uuid(),
55
+ kind text not null check (kind in ('service_role_bypass', 'would_be_deny')),
56
+ origin text not null check (origin in ('ef', 'host', 'unknown')),
57
+ actor_agent_id uuid,
58
+ board_id uuid,
59
+ operation text,
60
+ scope_key text,
61
+ denial_class text,
62
+ metadata jsonb not null default '{}'::jsonb,
63
+ recorded_at timestamptz not null default now()
64
+ );
65
+
66
+ comment on table agent_board.send_authz_telemetry is
67
+ 'Per-call send authorization telemetry. kind=service_role_bypass (a service_role guarded-bypass send) or would_be_deny (a non-fleet send that deny mode would have blocked). origin (ef|host|unknown) is the frozen host-vs-EF discriminator MBDELEG drains against.';
68
+
69
+ create index if not exists idx_agent_board_send_authz_telemetry_kind_origin
70
+ on agent_board.send_authz_telemetry(kind, origin, recorded_at desc);
71
+
72
+ alter table agent_board.send_authz_telemetry enable row level security;
73
+ grant select, insert on agent_board.send_authz_telemetry to service_role;
74
+
75
+ create or replace function agent_board._record_send_authz_telemetry(
76
+ p_kind text,
77
+ p_origin text,
78
+ p_actor_agent_id uuid,
79
+ p_board_id uuid,
80
+ p_operation text,
81
+ p_scope_key text,
82
+ p_denial_class text,
83
+ p_metadata jsonb default '{}'::jsonb
84
+ )
85
+ returns uuid
86
+ language plpgsql
87
+ security definer
88
+ set search_path = agent_board, public
89
+ as $$
90
+ declare
91
+ v_id uuid;
92
+ begin
93
+ if not agent_board._is_service_role() then
94
+ raise exception 'send authz telemetry recording requires service role' using errcode = '42501';
95
+ end if;
96
+ insert into agent_board.send_authz_telemetry(
97
+ kind, origin, actor_agent_id, board_id, operation, scope_key, denial_class, metadata
98
+ ) values (
99
+ p_kind,
100
+ case when p_origin in ('ef', 'host') then p_origin else 'unknown' end,
101
+ p_actor_agent_id,
102
+ p_board_id,
103
+ p_operation,
104
+ p_scope_key,
105
+ p_denial_class,
106
+ coalesce(p_metadata, '{}'::jsonb)
107
+ ) returning id into v_id;
108
+ return v_id;
109
+ end;
110
+ $$;
111
+
112
+ comment on function agent_board._record_send_authz_telemetry(text, text, uuid, uuid, text, text, text, jsonb) is
113
+ 'Service_role-only recorder for send_authz_telemetry. Normalizes origin to ef|host|unknown. Called internally by the send RPCs (bypass) and the fleet policy (would_be_deny).';
114
+
115
+ revoke all on function agent_board._record_send_authz_telemetry(text, text, uuid, uuid, text, text, text, jsonb) from public, anon, authenticated;
116
+ grant execute on function agent_board._record_send_authz_telemetry(text, text, uuid, uuid, text, text, text, jsonb) to service_role;
117
+
118
+ -- 4. fleet scope resolver + flag-mode lookup (IF-0-MBSENDAUTHZ-3) ------------
119
+ -- Trust-domain-derived scope where the sender is fleet-resolvable (mirrors the
120
+ -- active-machine resolution in evaluate_signed_write_fleet_policy), else the single
121
+ -- catch-all 'nonfleet' bucket (the v_machine_id is null population).
122
+ create or replace function agent_board._resolve_send_fleet_scope(
123
+ p_actor uuid
124
+ )
125
+ returns text
126
+ language sql
127
+ stable
128
+ security definer
129
+ set search_path = ''
130
+ as $$
131
+ select coalesce(
132
+ (select 'fleet:' || ftd.slug
133
+ from agent_board.fleet_machine_agents fma
134
+ join agent_board.fleet_machines fm on fm.id = fma.machine_id
135
+ join agent_board.fleet_trust_domains ftd on ftd.id = fm.trust_domain_id
136
+ where fma.agent_id = p_actor
137
+ and fma.lifecycle_state = 'active'
138
+ and fm.lifecycle_state = 'active'
139
+ order by fma.updated_at desc
140
+ limit 1),
141
+ 'nonfleet'
142
+ )
143
+ $$;
144
+
145
+ comment on function agent_board._resolve_send_fleet_scope(uuid) is
146
+ 'Resolves a sender to its trust-domain-derived fleet scope key, or the catch-all ''nonfleet'' bucket when the sender has no active fleet machine.';
147
+
148
+ revoke all on function agent_board._resolve_send_fleet_scope(uuid) from public, anon, authenticated;
149
+ grant execute on function agent_board._resolve_send_fleet_scope(uuid) to service_role;
150
+
151
+ create or replace function agent_board._send_fleet_scope_mode(
152
+ p_scope_key text
153
+ )
154
+ returns text
155
+ language sql
156
+ stable
157
+ security definer
158
+ set search_path = ''
159
+ as $$
160
+ select coalesce(
161
+ (select mode
162
+ from agent_board.fleet_send_enforcement_flags
163
+ where scope_key = p_scope_key),
164
+ 'fail_open_preserved'
165
+ )
166
+ $$;
167
+
168
+ comment on function agent_board._send_fleet_scope_mode(text) is
169
+ 'Resolves the enforcement mode for a sender-fleet-scope key, defaulting to fail_open_preserved when no flag row exists.';
170
+
171
+ revoke all on function agent_board._send_fleet_scope_mode(text) from public, anon, authenticated;
172
+ grant execute on function agent_board._send_fleet_scope_mode(text) to service_role;