@consiliency/agent-board-schema 1.24.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fixtures/already-applied.json +12 -0
- package/migrations/20260906000100_agent_board_enroll_resumption.sql +749 -0
- package/migrations/20260907000100_agent_board_signed_authority_provenance.sql +243 -0
- package/migrations/20260908000100_agent_board_signer_lifecycle_revalidation.sql +451 -0
- package/package.json +1 -1
- package/schema-manifest.json +30 -3
- package/src/tooling.test.mjs +3 -3
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +129 -112
- package/tests/agent_board_claimkey.test.sql +25 -3
- package/tests/agent_board_consent_emission.test.sql +17 -13
- package/tests/agent_board_consent_gating.test.sql +55 -11
- package/tests/agent_board_delegation_policies.test.sql +3 -1
- package/tests/agent_board_enroll_resumption.test.sql +412 -0
- package/tests/agent_board_federated_lock_order_class.test.sql +1 -3
- package/tests/agent_board_fedsession_provider_absence_gate.test.sql +9 -6
- package/tests/agent_board_fleet_machine_reactivation.test.sql +56 -5
- package/tests/agent_board_mint_harden.test.sql +39 -4
- package/tests/agent_board_outbox_wake_ordering.test.sql +27 -12
- package/tests/agent_board_rejectsem.test.sql +1 -1
- package/tests/agent_board_rolereset.test.sql +23 -2
- package/tests/agent_board_rowid.test.sql +24 -13
- package/tests/agent_board_scoped_send_regrant.test.sql +8 -4
- package/tests/agent_board_signed_authority_provenance.test.sql +477 -0
- package/tests/agent_board_signer_lifecycle.test.sql +709 -0
- package/tests/agent_board_source_strip_helper.test.sql +285 -0
- package/tests/agent_board_wake_recovery.test.sql +58 -40
- package/tests/catalog/p_metadata_parameters.json +693 -0
- package/tests/fixtures/test-prereqs.sql +313 -0
- package/tests/isolation/expected/atomic_fleet_lifecycle_races.out +3 -3
- package/tests/isolation/expected/signed_grant_onboarding_deadlock.out +220 -0
- package/tests/isolation/expected/signer_lifecycle_revoke.out +307 -0
- package/tests/isolation/specs/signed_grant_onboarding_deadlock.spec +159 -0
- package/tests/isolation/specs/signer_lifecycle_revoke.spec +176 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
-- Consiliency/message-board#176 pgTAP: OWNER-SCOPED ENROLL RESUMPTION.
|
|
2
|
+
--
|
|
3
|
+
-- The live failure: /enroll committed an identity, post-enroll provisioning failed, the
|
|
4
|
+
-- client reported total failure, and every retry was then refused forever with
|
|
5
|
+
-- `23505 this signing key is already enrolled`. 20260901000100's replay wrapper resumes
|
|
6
|
+
-- only rows carrying its own `metadata.federation.replay` STAMP -- which the trapped
|
|
7
|
+
-- enrollments, created before it shipped, do not have. 20260906000100 decides resumption
|
|
8
|
+
-- from evidence written at the ORIGINAL enrollment instead, so an unstamped row resumes
|
|
9
|
+
-- too.
|
|
10
|
+
--
|
|
11
|
+
-- Both directions are pinned here, and the SECURITY direction is the one that matters:
|
|
12
|
+
-- * same verified (issuer, subject) -> the existing identity is RETURNED (R-1, R-2);
|
|
13
|
+
-- * a DIFFERENT principal -> still raises, and the message names no handle (R-3, R-4).
|
|
14
|
+
-- Every allow-side assertion is `lives_ok(...)` followed by a separate state check, never
|
|
15
|
+
-- a bare `is((select ... from rpc(...)), ...)`, which aborts instead of failing (#113).
|
|
16
|
+
--
|
|
17
|
+
-- Failing calls are executed through pg_temp.try(), a BEGIN/EXCEPTION subtransaction that
|
|
18
|
+
-- records (sqlstate, message) into `_err` and records an explicit '00000 / (no error
|
|
19
|
+
-- raised)' row when the call SUCCEEDS -- so "the raise disappeared" fails an assertion
|
|
20
|
+
-- rather than silently matching nothing.
|
|
21
|
+
--
|
|
22
|
+
-- EC-1/EC-2 are the class assertions: the route class is enumerated from pg_proc, not
|
|
23
|
+
-- from a list written here, so a new fingerprint-accepting route or a new signing-key
|
|
24
|
+
-- creator fails and NAMES ITSELF.
|
|
25
|
+
create extension if not exists pgtap with schema extensions;
|
|
26
|
+
set search_path = extensions, public, agent_board;
|
|
27
|
+
|
|
28
|
+
begin;
|
|
29
|
+
|
|
30
|
+
select plan(35);
|
|
31
|
+
|
|
32
|
+
-- ---------------------------------------------------------------------------
|
|
33
|
+
-- Fixtures
|
|
34
|
+
-- ---------------------------------------------------------------------------
|
|
35
|
+
create temp table _v (name text primary key, val text) on commit drop;
|
|
36
|
+
create temp table _e (
|
|
37
|
+
label text, agent_id uuid, auth_user_id uuid, provider_id text, signing_key_id uuid,
|
|
38
|
+
granted text[], key_id text, key_version int, handle text, invite_redeemed boolean
|
|
39
|
+
) on commit drop;
|
|
40
|
+
create temp table _err (label text primary key, state text, msg text) on commit drop;
|
|
41
|
+
|
|
42
|
+
-- The four resumption RESULTS, declared UP FRONT with the RETURNS TABLE shape rather
|
|
43
|
+
-- than created by `create temp table ... as <the call>`. If the call under test raises,
|
|
44
|
+
-- `lives_ok` records the failure and the CTAS form would leave the table MISSING -- so
|
|
45
|
+
-- every following assertion would abort the transaction instead of failing, and pgTAP
|
|
46
|
+
-- would emit fewer assertions than the plan with no `not ok` to read (#113). Declared
|
|
47
|
+
-- empty, those assertions return NULL and fail honestly.
|
|
48
|
+
create temp table _r1 (
|
|
49
|
+
agent_id uuid, auth_user_id uuid, provider_id text, signing_key_id uuid,
|
|
50
|
+
granted_scopes text[], key_id text, key_version int, agent_handle text,
|
|
51
|
+
invite_redeemed boolean
|
|
52
|
+
) on commit drop;
|
|
53
|
+
create temp table _r2 (like _r1) on commit drop;
|
|
54
|
+
create temp table _r6 (like _r1) on commit drop;
|
|
55
|
+
create temp table _r13 (like _r1) on commit drop;
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
create function pg_temp.v(p_name text) returns text language sql stable as
|
|
59
|
+
$$ select val from _v where name = p_name $$;
|
|
60
|
+
|
|
61
|
+
-- The exact /enroll call, as SQL text. `p_actor_sub` is null and the kind is 'agent'
|
|
62
|
+
-- throughout, so the only inputs that vary across cases are the ones under test.
|
|
63
|
+
create function pg_temp.enroll_sql(
|
|
64
|
+
p_iss text, p_sub text, p_slug text, p_pub text, p_fp text, p_scopes text default '{board:send}'
|
|
65
|
+
) returns text language sql stable as
|
|
66
|
+
$$
|
|
67
|
+
select format(
|
|
68
|
+
$q$select * from agent_board.enroll_federated_identity(%L,'message-board',%L,%L::jsonb,%L,%L,'raw_ed25519_base64url',%L,true,null,'agent',%L,'{}')$q$,
|
|
69
|
+
p_iss, p_sub,
|
|
70
|
+
jsonb_build_object('iss', p_iss, 'aud', 'message-board', 'sub', p_sub)::text,
|
|
71
|
+
p_slug, p_pub, p_fp, p_scopes)
|
|
72
|
+
$$;
|
|
73
|
+
|
|
74
|
+
-- Run a call that is EXPECTED to raise, capturing sqlstate + message. A call that does
|
|
75
|
+
-- NOT raise records '00000', so a vanished raise fails loudly instead of matching an
|
|
76
|
+
-- absent row.
|
|
77
|
+
create function pg_temp.try(p_label text, p_sql text) returns void language plpgsql as
|
|
78
|
+
$$
|
|
79
|
+
begin
|
|
80
|
+
begin
|
|
81
|
+
execute p_sql;
|
|
82
|
+
insert into _err values (p_label, '00000', '(no error raised)');
|
|
83
|
+
exception when others then
|
|
84
|
+
insert into _err values (p_label, sqlstate, sqlerrm);
|
|
85
|
+
end;
|
|
86
|
+
end
|
|
87
|
+
$$;
|
|
88
|
+
|
|
89
|
+
do $$
|
|
90
|
+
declare
|
|
91
|
+
s text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
|
|
92
|
+
v_iss1 text := 'https://res-a-' || replace(substr(gen_random_uuid()::text, 1, 8), '-', '') || '.test';
|
|
93
|
+
v_iss2 text;
|
|
94
|
+
v_pub text;
|
|
95
|
+
i int;
|
|
96
|
+
begin
|
|
97
|
+
v_iss1 := 'https://res-a-' || s || '.test';
|
|
98
|
+
v_iss2 := 'https://res-b-' || s || '.test';
|
|
99
|
+
|
|
100
|
+
insert into agent_board.identity_providers
|
|
101
|
+
(issuer, audience, supported, policy_approved, allowed_scopes, allowed_board_scopes, allowed_agent_kinds)
|
|
102
|
+
values
|
|
103
|
+
(v_iss1, 'message-board', true, true, '{board:send,board:read}', '{board:x}', '{agent,human}'),
|
|
104
|
+
(v_iss2, 'message-board', true, true, '{board:send,board:read}', '{board:x}', '{agent,human}');
|
|
105
|
+
|
|
106
|
+
insert into _v values
|
|
107
|
+
('s', s), ('iss1', v_iss1), ('iss2', v_iss2),
|
|
108
|
+
('suba', 'portal|a-' || s), ('subb', 'portal|b-' || s);
|
|
109
|
+
|
|
110
|
+
-- Eight real Ed25519-shaped keys: 32 random bytes re-encoded through the SAME
|
|
111
|
+
-- canonical base64url form the enroll contract requires, and their in-DB fingerprints.
|
|
112
|
+
for i in 1..8 loop
|
|
113
|
+
v_pub := rtrim(translate(encode(gen_random_bytes(32), 'base64'), '+/', '-_'), '=');
|
|
114
|
+
insert into _v values ('pub' || i, v_pub), ('fp' || i, agent_board._raw_key_fingerprint(v_pub));
|
|
115
|
+
end loop;
|
|
116
|
+
end
|
|
117
|
+
$$;
|
|
118
|
+
|
|
119
|
+
-- Eight enrollments. E1/E3..E8 are then stripped of their replay stamp, which is exactly
|
|
120
|
+
-- the state every identity enrolled before 20260901000100 is in -- the #176 victim's
|
|
121
|
+
-- state. E2 keeps its stamp.
|
|
122
|
+
do $$
|
|
123
|
+
declare
|
|
124
|
+
i int;
|
|
125
|
+
v_slug text;
|
|
126
|
+
begin
|
|
127
|
+
for i in 1..8 loop
|
|
128
|
+
v_slug := 'res-' || i || '-' || pg_temp.v('s');
|
|
129
|
+
-- E2 is the STAMPED fixture and the only one that needs two scopes: the frozen
|
|
130
|
+
-- contract compares them as JSONB, which preserves ORDER, and R-14 pins that.
|
|
131
|
+
execute format('insert into _e select %L, * from (%s) t', 'E' || i,
|
|
132
|
+
pg_temp.enroll_sql(pg_temp.v('iss1'), pg_temp.v('suba'), v_slug,
|
|
133
|
+
pg_temp.v('pub' || i), pg_temp.v('fp' || i),
|
|
134
|
+
case when i = 2 then '{board:send,board:read}' else '{board:send}' end));
|
|
135
|
+
end loop;
|
|
136
|
+
|
|
137
|
+
update agent_board.agents a
|
|
138
|
+
set metadata = a.metadata #- '{federation,replay}'
|
|
139
|
+
where a.id in (select agent_id from _e where label <> 'E2');
|
|
140
|
+
|
|
141
|
+
-- E3: owned, unstamped, but the agent is revoked -> must NOT be revived by resumption.
|
|
142
|
+
update agent_board.agents a set status = 'revoked' where a.id = (select agent_id from _e where label = 'E3');
|
|
143
|
+
|
|
144
|
+
-- E4: evidence (i) broken -- owner_user_id repointed at an unrelated auth user.
|
|
145
|
+
update agent_board.agents a
|
|
146
|
+
set owner_user_id = agent_board._ensure_agent_auth_user('res-unrelated-owner-' || pg_temp.v('s'))
|
|
147
|
+
where a.id = (select agent_id from _e where label = 'E4');
|
|
148
|
+
|
|
149
|
+
-- E5: evidence (ii) broken -- the creation-time federation subject is gone.
|
|
150
|
+
update agent_board.agents a
|
|
151
|
+
set metadata = a.metadata #- '{federation,subject}'
|
|
152
|
+
where a.id = (select agent_id from _e where label = 'E5');
|
|
153
|
+
|
|
154
|
+
-- E6: evidence (iii) broken -- the original enrollment's audit row is gone.
|
|
155
|
+
delete from agent_board.enrollment_federation_audit e
|
|
156
|
+
where e.agent_id = (select agent_id from _e where label = 'E6');
|
|
157
|
+
|
|
158
|
+
-- E7: audit row present but recording no granted scopes -> ownership holds, the
|
|
159
|
+
-- identity is not resumable. This is what makes branch (b) reachable rather than
|
|
160
|
+
-- implied by the ownership conjunction.
|
|
161
|
+
update agent_board.enrollment_federation_audit e
|
|
162
|
+
set granted_scopes = null
|
|
163
|
+
where e.agent_id = (select agent_id from _e where label = 'E7');
|
|
164
|
+
|
|
165
|
+
-- E8: the ONE exemption in the ownership conjunction, modelled on the atomic-fleet
|
|
166
|
+
-- backfill fixture -- an agent that predates the owner model and carries NO
|
|
167
|
+
-- owner_user_id. Evidence (i) is unavailable there rather than contradicted.
|
|
168
|
+
update agent_board.agents a set owner_user_id = null
|
|
169
|
+
where a.id = (select agent_id from _e where label = 'E8');
|
|
170
|
+
end
|
|
171
|
+
$$;
|
|
172
|
+
|
|
173
|
+
-- ---------------------------------------------------------------------------
|
|
174
|
+
-- EC-1/EC-2 -- the class, enumerated from the catalog
|
|
175
|
+
-- ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
-- Every route a client can re-present an enrolled key to, taken from pg_proc.proargnames.
|
|
178
|
+
-- A fourth such route added later fails this and names itself in the diff.
|
|
179
|
+
select is(
|
|
180
|
+
(select string_agg(p.proname, ', ' order by p.proname)
|
|
181
|
+
from pg_proc p
|
|
182
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
183
|
+
where n.nspname = 'agent_board'
|
|
184
|
+
and p.proargnames && array['p_agent_public_key_fingerprint']),
|
|
185
|
+
'_enroll_federated_identity_once, enroll_federated_identity, onboard_federated_fleet_agent',
|
|
186
|
+
'EC-1: exactly three agent_board routes accept an enrolled key fingerprint (pg_proc-derived)');
|
|
187
|
+
|
|
188
|
+
-- ...and only two functions in the whole schema can CREATE a signing key, so
|
|
189
|
+
-- onboard_federated_fleet_agent necessarily reaches an already-enrolled key through
|
|
190
|
+
-- enroll_federated_identity rather than deciding the duplicate case itself.
|
|
191
|
+
select is(
|
|
192
|
+
(select string_agg(p.proname, ', ' order by p.proname)
|
|
193
|
+
from pg_proc p
|
|
194
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
195
|
+
where n.nspname = 'agent_board'
|
|
196
|
+
and p.prokind = 'f'
|
|
197
|
+
and agent_board_test.strip_sql(p.prosrc) ~* 'insert into agent_board\.agent_signing_keys'),
|
|
198
|
+
'_enroll_federated_identity_once, mint_delegated_child',
|
|
199
|
+
'EC-2: only the enroll creator and delegated-child mint insert signing keys (strip_sql-derived)');
|
|
200
|
+
|
|
201
|
+
-- ---------------------------------------------------------------------------
|
|
202
|
+
-- R-1 -- THE #176 FIX: an unstamped enrollment resumes for its owner
|
|
203
|
+
-- ---------------------------------------------------------------------------
|
|
204
|
+
select lives_ok(
|
|
205
|
+
(select 'insert into _r1 ' || pg_temp.enroll_sql(
|
|
206
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-1-' || pg_temp.v('s'),
|
|
207
|
+
pg_temp.v('pub1'), pg_temp.v('fp1'), '{board:send}')),
|
|
208
|
+
'R-1: re-presenting an already-enrolled key as the same verified (issuer, subject) SUCCEEDS (unstamped row)');
|
|
209
|
+
select is(
|
|
210
|
+
(select agent_id from _r1),
|
|
211
|
+
(select agent_id from _e where label = 'E1'),
|
|
212
|
+
'R-1: the resumption returns the EXISTING agent identity');
|
|
213
|
+
select is(
|
|
214
|
+
(select signing_key_id from _r1),
|
|
215
|
+
(select signing_key_id from _e where label = 'E1'),
|
|
216
|
+
'R-1: the resumption returns the EXISTING signing key, not a new one');
|
|
217
|
+
select is(
|
|
218
|
+
(select agent_handle from _r1),
|
|
219
|
+
(select handle from _e where label = 'E1'),
|
|
220
|
+
'R-1: the resumption returns the server-assigned handle the client lost');
|
|
221
|
+
select is(
|
|
222
|
+
(select provider_id from _r1),
|
|
223
|
+
(select provider_id from _e where label = 'E1'),
|
|
224
|
+
'R-1: provider_id survives the unstamped path (it is read from the creation-time federation object)');
|
|
225
|
+
select is(
|
|
226
|
+
(select count(*)::int from agent_board.agent_signing_keys k where k.fingerprint = pg_temp.v('fp1')),
|
|
227
|
+
1, 'R-1: the resumption enrolled no second key for that fingerprint');
|
|
228
|
+
select is(
|
|
229
|
+
(select count(*)::int from agent_board.agents a where a.id in (select agent_id from _e) or a.handle like 'res-1-%'),
|
|
230
|
+
8, 'R-1: the resumption created no ninth agent');
|
|
231
|
+
|
|
232
|
+
-- ---------------------------------------------------------------------------
|
|
233
|
+
-- R-2 -- an unstamped resumption cannot ESCALATE
|
|
234
|
+
-- ---------------------------------------------------------------------------
|
|
235
|
+
select lives_ok(
|
|
236
|
+
(select 'insert into _r2 ' || pg_temp.enroll_sql(
|
|
237
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-1-different-slug',
|
|
238
|
+
pg_temp.v('pub1'), pg_temp.v('fp1'), '{board:send,board:read}')),
|
|
239
|
+
'R-2: an unstamped resumption tolerates a different slug and a wider scope request');
|
|
240
|
+
select is(
|
|
241
|
+
(select granted_scopes from _r2),
|
|
242
|
+
'{board:send}'::text[],
|
|
243
|
+
'R-2: it returns the ORIGINAL granted scopes -- the wider request grants nothing');
|
|
244
|
+
select is(
|
|
245
|
+
(select agent_id from _r2),
|
|
246
|
+
(select agent_id from _e where label = 'E1'),
|
|
247
|
+
'R-2: and it still returns the original identity, not a second one');
|
|
248
|
+
|
|
249
|
+
-- ---------------------------------------------------------------------------
|
|
250
|
+
-- R-3 / R-4 -- THE SECURITY DIRECTION: a different principal is still refused
|
|
251
|
+
-- ---------------------------------------------------------------------------
|
|
252
|
+
select pg_temp.try('squat-subject', (select pg_temp.enroll_sql(
|
|
253
|
+
pg_temp.v('iss1'), pg_temp.v('subb'), 'res-squat-' || pg_temp.v('s'),
|
|
254
|
+
pg_temp.v('pub1'), pg_temp.v('fp1'), '{board:send}')));
|
|
255
|
+
-- sqlstate ALONE does not pin this: with the ownership refusal bypassed the request
|
|
256
|
+
-- falls through to the state gate, which raises the SAME 23505 for its own reason. The
|
|
257
|
+
-- assertion therefore requires the OWNERSHIP refusal specifically.
|
|
258
|
+
select ok(
|
|
259
|
+
(select state = '23505' and msg like '%to a different principal%'
|
|
260
|
+
from _err where label = 'squat-subject'),
|
|
261
|
+
'R-3: a key owned by a DIFFERENT subject at the same issuer is refused AS a different principal (23505)');
|
|
262
|
+
select ok(
|
|
263
|
+
(select position(h in msg) = 0 from _err, (select handle h from _e where label = 'E1') x where label = 'squat-subject'),
|
|
264
|
+
'R-3: the refusal names NO handle -- a squatter learns nothing about who holds the key');
|
|
265
|
+
select is(
|
|
266
|
+
(select count(*)::int from agent_board.agent_signing_keys k where k.fingerprint = pg_temp.v('fp1')),
|
|
267
|
+
1, 'R-3: the refused squat left exactly one key on that fingerprint');
|
|
268
|
+
|
|
269
|
+
select pg_temp.try('squat-issuer', (select pg_temp.enroll_sql(
|
|
270
|
+
pg_temp.v('iss2'), pg_temp.v('suba'), 'res-squat2-' || pg_temp.v('s'),
|
|
271
|
+
pg_temp.v('pub1'), pg_temp.v('fp1'), '{board:send}')));
|
|
272
|
+
select ok(
|
|
273
|
+
(select state = '23505' and msg like '%to a different principal%'
|
|
274
|
+
from _err where label = 'squat-issuer'),
|
|
275
|
+
'R-4: the same subject at a DIFFERENT approved issuer is refused as a different principal');
|
|
276
|
+
select ok(
|
|
277
|
+
(select position(h in msg) = 0 from _err, (select handle h from _e where label = 'E1') x where label = 'squat-issuer'),
|
|
278
|
+
'R-4: that refusal names no handle either');
|
|
279
|
+
|
|
280
|
+
-- ---------------------------------------------------------------------------
|
|
281
|
+
-- R-5 / R-6 -- a STAMPED enrollment keeps its frozen contract; only the message improves
|
|
282
|
+
-- ---------------------------------------------------------------------------
|
|
283
|
+
select pg_temp.try('frozen', (select pg_temp.enroll_sql(
|
|
284
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-2-renamed',
|
|
285
|
+
pg_temp.v('pub2'), pg_temp.v('fp2'), '{board:send,board:read}')));
|
|
286
|
+
select is((select state from _err where label = 'frozen'), '23505',
|
|
287
|
+
'R-5: a stamped enrollment re-presented by its owner with a DIFFERENT slug still raises (frozen contract intact)');
|
|
288
|
+
select ok(
|
|
289
|
+
(select position(h in msg) > 0 from _err, (select handle h from _e where label = 'E2') x where label = 'frozen'),
|
|
290
|
+
'R-5: but the message now NAMES the existing handle');
|
|
291
|
+
select ok(
|
|
292
|
+
(select msg like '%need not be repeated%' from _err where label = 'frozen'),
|
|
293
|
+
'R-5: and says enrollment need not be repeated -- the actionable half of the ask');
|
|
294
|
+
|
|
295
|
+
select lives_ok(
|
|
296
|
+
(select 'insert into _r6 ' || pg_temp.enroll_sql(
|
|
297
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-2-' || pg_temp.v('s'),
|
|
298
|
+
pg_temp.v('pub2'), pg_temp.v('fp2'), '{board:send,board:read}')),
|
|
299
|
+
'R-6: an EXACT stamped replay still succeeds (the released path is unregressed)');
|
|
300
|
+
select is(
|
|
301
|
+
(select agent_id from _r6),
|
|
302
|
+
(select agent_id from _e where label = 'E2'),
|
|
303
|
+
'R-6: and still returns the original identity');
|
|
304
|
+
|
|
305
|
+
-- ---------------------------------------------------------------------------
|
|
306
|
+
-- R-7 -- resumption never revives an identity that is not usable
|
|
307
|
+
-- ---------------------------------------------------------------------------
|
|
308
|
+
select pg_temp.try('revoked', (select pg_temp.enroll_sql(
|
|
309
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-3-' || pg_temp.v('s'),
|
|
310
|
+
pg_temp.v('pub3'), pg_temp.v('fp3'), '{board:send}')));
|
|
311
|
+
select is((select state from _err where label = 'revoked'), '23505',
|
|
312
|
+
'R-7: an owned but REVOKED identity is not resumable');
|
|
313
|
+
select ok(
|
|
314
|
+
(select position(h in msg) > 0 from _err, (select handle h from _e where label = 'E3') x where label = 'revoked'),
|
|
315
|
+
'R-7: its owner is told which handle is stuck');
|
|
316
|
+
|
|
317
|
+
-- ---------------------------------------------------------------------------
|
|
318
|
+
-- R-8/R-9/R-10 -- each of the three ownership evidences is load-bearing
|
|
319
|
+
-- ---------------------------------------------------------------------------
|
|
320
|
+
select pg_temp.try('no-owner-match', (select pg_temp.enroll_sql(
|
|
321
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-4-' || pg_temp.v('s'),
|
|
322
|
+
pg_temp.v('pub4'), pg_temp.v('fp4'), '{board:send}')));
|
|
323
|
+
select ok(
|
|
324
|
+
(select state = '23505' and msg like '%to a different principal%'
|
|
325
|
+
from _err where label = 'no-owner-match'),
|
|
326
|
+
'R-8: evidence (i) -- owner_user_id not equal to the derived owner reads as NOT owned');
|
|
327
|
+
|
|
328
|
+
select pg_temp.try('no-creation-stamp', (select pg_temp.enroll_sql(
|
|
329
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-5-' || pg_temp.v('s'),
|
|
330
|
+
pg_temp.v('pub5'), pg_temp.v('fp5'), '{board:send}')));
|
|
331
|
+
select ok(
|
|
332
|
+
(select state = '23505' and msg like '%to a different principal%'
|
|
333
|
+
from _err where label = 'no-creation-stamp'),
|
|
334
|
+
'R-9: evidence (ii) -- a missing creation-time federation subject reads as NOT owned (null is not a pass)');
|
|
335
|
+
|
|
336
|
+
select pg_temp.try('no-audit-row', (select pg_temp.enroll_sql(
|
|
337
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-6-' || pg_temp.v('s'),
|
|
338
|
+
pg_temp.v('pub6'), pg_temp.v('fp6'), '{board:send}')));
|
|
339
|
+
select ok(
|
|
340
|
+
(select state = '23505' and msg like '%to a different principal%'
|
|
341
|
+
from _err where label = 'no-audit-row'),
|
|
342
|
+
'R-10: evidence (iii) -- a missing enrollment audit row reads as NOT owned');
|
|
343
|
+
|
|
344
|
+
-- ---------------------------------------------------------------------------
|
|
345
|
+
-- R-11 -- branch (b) is reachable on its own, not merely implied by ownership
|
|
346
|
+
-- ---------------------------------------------------------------------------
|
|
347
|
+
select pg_temp.try('no-granted-scopes', (select pg_temp.enroll_sql(
|
|
348
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-7-' || pg_temp.v('s'),
|
|
349
|
+
pg_temp.v('pub7'), pg_temp.v('fp7'), '{board:send}')));
|
|
350
|
+
select is((select state from _err where label = 'no-granted-scopes'), '23505',
|
|
351
|
+
'R-11: an audit row recording NO granted scopes is owned but not resumable');
|
|
352
|
+
select ok(
|
|
353
|
+
(select position(h in msg) > 0 from _err, (select handle h from _e where label = 'E7') x where label = 'no-granted-scopes'),
|
|
354
|
+
'R-11: and because ownership IS proven there, the owner is told the handle');
|
|
355
|
+
|
|
356
|
+
-- ---------------------------------------------------------------------------
|
|
357
|
+
-- R-12 -- the internal creator stays a hard refusal, but an actionable one
|
|
358
|
+
-- ---------------------------------------------------------------------------
|
|
359
|
+
select pg_temp.try('once-direct', (select format(
|
|
360
|
+
$q$select * from agent_board._enroll_federated_identity_once(%L,'message-board',%L,%L::jsonb,%L,%L,'raw_ed25519_base64url',%L,true,null,'agent','{board:send}','{}')$q$,
|
|
361
|
+
pg_temp.v('iss1'), pg_temp.v('suba'),
|
|
362
|
+
jsonb_build_object('iss', pg_temp.v('iss1'), 'aud', 'message-board', 'sub', pg_temp.v('suba'))::text,
|
|
363
|
+
'res-once-' || pg_temp.v('s'), pg_temp.v('pub1'), pg_temp.v('fp1'))));
|
|
364
|
+
select is((select state from _err where label = 'once-direct'), '23505',
|
|
365
|
+
'R-12: _enroll_federated_identity_once still refuses an already-enrolled key outright (no twin resumption)');
|
|
366
|
+
select ok(
|
|
367
|
+
(select position(h in msg) > 0 and msg like '%need not be repeated%'
|
|
368
|
+
from _err, (select handle h from _e where label = 'E1') x where label = 'once-direct'),
|
|
369
|
+
'R-12: and its refusal now names the holder and says enrollment need not be repeated');
|
|
370
|
+
|
|
371
|
+
-- ---------------------------------------------------------------------------
|
|
372
|
+
-- R-13 -- the null-owner exemption, asserted POSITIVELY and in BOTH directions
|
|
373
|
+
-- ---------------------------------------------------------------------------
|
|
374
|
+
select lives_ok(
|
|
375
|
+
(select 'insert into _r13 ' || pg_temp.enroll_sql(
|
|
376
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-8-' || pg_temp.v('s'),
|
|
377
|
+
pg_temp.v('pub8'), pg_temp.v('fp8'), '{board:send}')),
|
|
378
|
+
'R-13: a row with NO owner_user_id (pre-owner-model, the atomic-fleet backfill shape) still resumes for its owner');
|
|
379
|
+
select is(
|
|
380
|
+
(select agent_id from _r13),
|
|
381
|
+
(select agent_id from _e where label = 'E8'),
|
|
382
|
+
'R-13: and it resumes to that same pre-owner-model identity');
|
|
383
|
+
select pg_temp.try('null-owner-squat', (select pg_temp.enroll_sql(
|
|
384
|
+
pg_temp.v('iss1'), pg_temp.v('subb'), 'res-8-squat-' || pg_temp.v('s'),
|
|
385
|
+
pg_temp.v('pub8'), pg_temp.v('fp8'), '{board:send}')));
|
|
386
|
+
select ok(
|
|
387
|
+
(select state = '23505' and msg like '%to a different principal%'
|
|
388
|
+
from _err where label = 'null-owner-squat'),
|
|
389
|
+
'R-13: the exemption does NOT open a squat -- the same null-owner row refuses a different subject as one');
|
|
390
|
+
|
|
391
|
+
-- ---------------------------------------------------------------------------
|
|
392
|
+
-- R-14 -- the frozen scope comparison is ORDER-SENSITIVE, and that is asserted
|
|
393
|
+
-- rather than assumed
|
|
394
|
+
-- ---------------------------------------------------------------------------
|
|
395
|
+
-- `v_replay -> 'requestedScopes' is distinct from to_jsonb(v_requested_scopes)` is a
|
|
396
|
+
-- JSONB comparison and a jsonb array preserves order, so the SAME scopes in a different
|
|
397
|
+
-- order are a DIFFERENT request. That is pre-existing released behaviour of the frozen
|
|
398
|
+
-- contract (20260901000100), not something #176 introduced, and this migration
|
|
399
|
+
-- deliberately does not loosen it. It is pinned here because the offline MockEnroll
|
|
400
|
+
-- sorted its scope arrays and so disagreed with production silently; the client now
|
|
401
|
+
-- asserts the same property, and neither side can drift without one of them going red.
|
|
402
|
+
select pg_temp.try('frozen-scope-order', (select pg_temp.enroll_sql(
|
|
403
|
+
pg_temp.v('iss1'), pg_temp.v('suba'), 'res-2-' || pg_temp.v('s'),
|
|
404
|
+
pg_temp.v('pub2'), pg_temp.v('fp2'), '{board:read,board:send}')));
|
|
405
|
+
select ok(
|
|
406
|
+
(select state = '23505' and position(h in msg) > 0
|
|
407
|
+
from _err, (select handle h from _e where label = 'E2') x
|
|
408
|
+
where label = 'frozen-scope-order'),
|
|
409
|
+
'R-14: the SAME frozen scopes REORDERED are a different frozen contract and raise, naming the handle');
|
|
410
|
+
|
|
411
|
+
select * from finish();
|
|
412
|
+
rollback;
|
|
@@ -86,9 +86,7 @@ fns as (
|
|
|
86
86
|
select p.oid,
|
|
87
87
|
p.proname,
|
|
88
88
|
p.proname || '(' || pg_get_function_identity_arguments(p.oid) || ')' as sig,
|
|
89
|
-
|
|
90
|
-
$re$(\$[A-Za-z_0-9]*\$).*?\1|('([^']|'')*')|(--[^\n]*)|(/\*([^*]|\*+[^*/])*\*+/)$re$,
|
|
91
|
-
' ', 'g') as body,
|
|
89
|
+
agent_board_test.stripped_body(p.oid) as body,
|
|
92
90
|
p.proconfig
|
|
93
91
|
from pg_proc p
|
|
94
92
|
join pg_namespace n on n.oid = p.pronamespace
|
|
@@ -70,9 +70,12 @@ select is(
|
|
|
70
70
|
$g$raise\s+exception\s+'issuer/audience not in the identity-provider allowlist \(rejected before any owner or board lock\)'$g$)), 0) as p_gate,
|
|
71
71
|
strpos(d, '_ensure_agent_auth_user') as p_ensure,
|
|
72
72
|
strpos(d, substring(d from 'from\s+auth\.users[^;]{0,250}?for\s+update')) as p_lock
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
-- LITERALS KEPT (the `true`): the gate marker G-2 locates IS the raise
|
|
74
|
+
-- message, so stripping literals would delete it and the positional claim
|
|
75
|
+
-- would read 'gate-missing' forever. Comments -- including the NESTED
|
|
76
|
+
-- block comment of message-board#199, which the regex this replaced could
|
|
77
|
+
-- not see -- are still removed.
|
|
78
|
+
from (select agent_board_test.stripped_body(p.oid, true) as d
|
|
76
79
|
from pg_proc p
|
|
77
80
|
join pg_namespace n on n.oid = p.pronamespace
|
|
78
81
|
where n.nspname = 'agent_board'
|
|
@@ -159,9 +162,9 @@ select is(
|
|
|
159
162
|
join pg_namespace n on n.oid = p.pronamespace
|
|
160
163
|
where n.nspname = 'agent_board'
|
|
161
164
|
and p.prokind = 'f'
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
-- LITERALS KEPT: membership in this class is defined by a function raising
|
|
166
|
+
-- that MESSAGE, so the marker is a literal by construction.
|
|
167
|
+
and agent_board_test.stripped_body(p.oid, true) ~ 'not in the identity-provider allowlist'),
|
|
165
168
|
'_enroll_federated_human_after_revocation_gate, _enroll_federated_identity_once, '
|
|
166
169
|
|| 'enroll_federated_human, enroll_federated_identity',
|
|
167
170
|
'G-8: exactly four routes reject an absent identity provider');
|
|
@@ -43,7 +43,7 @@ set search_path = extensions, public, agent_board;
|
|
|
43
43
|
|
|
44
44
|
begin;
|
|
45
45
|
|
|
46
|
-
select plan(
|
|
46
|
+
select plan(23);
|
|
47
47
|
|
|
48
48
|
-- (1)(2) Shape.
|
|
49
49
|
select has_function(
|
|
@@ -378,6 +378,30 @@ select is(
|
|
|
378
378
|
-- CLASS (rule #1): catalog-derived — every machine-subject revocation READER honors
|
|
379
379
|
-- reactivated_at; the WRITER exemption is asserted positively.
|
|
380
380
|
------------------------------------------------------------------------------------------
|
|
381
|
+
-- (21a) NON-VACUITY, asserted BEFORE the class below. That class is
|
|
382
|
+
-- `string_agg(offender) is null` -- so it passes just as happily when the
|
|
383
|
+
-- enumeration reaches NOTHING as when every reader conforms. Found while
|
|
384
|
+
-- re-deriving the strip modes for the message-board#196 review: flipping the
|
|
385
|
+
-- membership predicate to a literal-stripped read empties the class (the marker
|
|
386
|
+
-- `fre.subject_kind = ''machine''` has a literal operand and cannot survive it)
|
|
387
|
+
-- and the file stayed 22/22 GREEN. A green that survives its own class being
|
|
388
|
+
-- deleted is not evidence, so the membership set is pinned as an exact set here
|
|
389
|
+
-- and a new reader NAMES ITSELF in this diff.
|
|
390
|
+
select is(
|
|
391
|
+
(select
|
|
392
|
+
coalesce((select string_agg(p.proname, ', ' order by p.proname)
|
|
393
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
394
|
+
where n.nspname = 'agent_board' and p.prokind = 'f'
|
|
395
|
+
-- DATA literal -> case-SENSITIVE; see the rule at the offender class below
|
|
396
|
+
and agent_board_test.stripped_body(p.oid, true) ~ 'fre\.subject_kind = ''machine'''), '(none)')
|
|
397
|
+
|| ' | ' ||
|
|
398
|
+
coalesce((select string_agg(c.relname, ', ' order by c.relname)
|
|
399
|
+
from pg_class c join pg_namespace n on n.oid = c.relnamespace
|
|
400
|
+
where n.nspname = 'agent_board' and c.relkind = 'v'
|
|
401
|
+
and agent_board_test.stripped_viewdef(c.oid, true) ~ 'fre\.subject_kind = ''machine'''), '(none)')),
|
|
402
|
+
'evaluate_fleet_sender_policy, reactivate_fleet_machine | v_fleet_machine_status',
|
|
403
|
+
'the machine-subject revocation reader class is REACHED and is exactly these three -- the class assertion below cannot pass by finding nothing');
|
|
404
|
+
|
|
381
405
|
-- (21) Enumerate readers (functions + views) whose definition tests a machine-subject
|
|
382
406
|
-- revocation event (`fre.subject_kind = 'machine'`) but does NOT reference reactivated_at.
|
|
383
407
|
-- Expected: none. A new non-conforming reader appears here BY NAME (rule #1: offenders name
|
|
@@ -391,16 +415,43 @@ select is(
|
|
|
391
415
|
join pg_namespace n on n.oid = p.pronamespace
|
|
392
416
|
where n.nspname = 'agent_board'
|
|
393
417
|
and p.prokind = 'f'
|
|
394
|
-
and
|
|
395
|
-
|
|
418
|
+
-- TWO MODES ON PURPOSE, and mixing them was a live fail-open
|
|
419
|
+
-- (message-board#196 review, red-team seat).
|
|
420
|
+
--
|
|
421
|
+
-- MEMBERSHIP (literals KEPT): the marker `fre.subject_kind = ''machine''`
|
|
422
|
+
-- has a string literal as its operand, so it cannot be located in a
|
|
423
|
+
-- literal-stripped body. This direction is fail-CLOSED anyway -- a
|
|
424
|
+
-- literal that fakes the marker ADDS an offender and reddens the
|
|
425
|
+
-- expected-empty set.
|
|
426
|
+
--
|
|
427
|
+
-- THE GUARD CHECK (literals STRIPPED): `reactivated_at` is a COLUMN
|
|
428
|
+
-- NAME, i.e. code. Read with literals kept, a reader that dropped the
|
|
429
|
+
-- guard while any literal still mentioned `reactivated_at` fell out of
|
|
430
|
+
-- the offender set and the class went 22/22 GREEN with the guard gone
|
|
431
|
+
-- (measured on evaluate_fleet_sender_policy). That is the exemption
|
|
432
|
+
-- category becoming the hole, CLAUDE.md rule 1's named trap.
|
|
433
|
+
-- CASE: this marker is compared as DATA, so it stays case-SENSITIVE.
|
|
434
|
+
-- The rule the suite now follows, and the line the round-1 `gi` sweep
|
|
435
|
+
-- got wrong here: a marker SPELLED AS CODE (a relation, function or
|
|
436
|
+
-- column name) is matched case-INSENSITIVELY, because PostgreSQL folds
|
|
437
|
+
-- unquoted identifiers and a case-sensitive scan is fail-open for a
|
|
438
|
+
-- class filter. A marker COMPARED AS DATA -- a string literal operand,
|
|
439
|
+
-- or a raise message -- stays case-SENSITIVE, because nothing folds it:
|
|
440
|
+
-- `'machine' = 'MACHINE'` is FALSE at runtime, so a `~*` here cannot
|
|
441
|
+
-- tell a working predicate from a broken one. Measured: rewriting the
|
|
442
|
+
-- live predicate to `= 'MACHINE'` -- which matches no rows -- left this
|
|
443
|
+
-- file 23/23 GREEN under `~*`.
|
|
444
|
+
and agent_board_test.stripped_body(p.oid, true) ~ 'fre\.subject_kind = ''machine'''
|
|
445
|
+
and agent_board_test.stripped_body(p.oid) not ilike '%reactivated_at%'
|
|
396
446
|
union all
|
|
397
447
|
select c.relname
|
|
398
448
|
from pg_class c
|
|
399
449
|
join pg_namespace n on n.oid = c.relnamespace
|
|
400
450
|
where n.nspname = 'agent_board'
|
|
401
451
|
and c.relkind = 'v'
|
|
402
|
-
|
|
403
|
-
and
|
|
452
|
+
-- same split for the view half: marker keeps literals, guard does not
|
|
453
|
+
and agent_board_test.stripped_viewdef(c.oid, true) ~ 'fre\.subject_kind = ''machine'''
|
|
454
|
+
and agent_board_test.stripped_viewdef(c.oid) not ilike '%reactivated_at%'
|
|
404
455
|
) offenders
|
|
405
456
|
),
|
|
406
457
|
null,
|
|
@@ -22,7 +22,7 @@ set search_path = extensions, public, agent_board;
|
|
|
22
22
|
|
|
23
23
|
begin;
|
|
24
24
|
|
|
25
|
-
select plan(
|
|
25
|
+
select plan(32);
|
|
26
26
|
|
|
27
27
|
create temp table _mh (
|
|
28
28
|
owner_id uuid,
|
|
@@ -119,9 +119,33 @@ select is(
|
|
|
119
119
|
where n.nspname = 'agent_board' and p.prokind = 'f')
|
|
120
120
|
select coalesce(string_agg(proname, ', ' order by proname), '')
|
|
121
121
|
from fns
|
|
122
|
-
where
|
|
123
|
-
and
|
|
124
|
-
|
|
122
|
+
where agent_board_test.stripped_body(oid) ~* 'insert[[:space:]]+into[[:space:]]+(agent_board\.)?agent_signing_keys'
|
|
123
|
+
-- CASE-INSENSITIVE, and that is load-bearing rather than tidiness. SQL folds
|
|
124
|
+
-- unquoted identifiers, but `prosrc` is stored VERBATIM, so a writer spelling
|
|
125
|
+
-- the same call `AGENT_BOARD.…` or the same column `FINGERPRINT` is identical
|
|
126
|
+
-- to PostgreSQL and invisible to a case-sensitive scan. For a MEMBERSHIP
|
|
127
|
+
-- filter that is the fail-OPEN direction: the offender simply leaves the
|
|
128
|
+
-- class and the expected-empty assertion stays green. (Same mechanism
|
|
129
|
+
-- agent_board_wake_recovery.test.sql records for its own WR-7 filter.)
|
|
130
|
+
and agent_board_test.stripped_body(oid) ~* 'fingerprint'
|
|
131
|
+
-- message-board#196 Bucket B, demonstrated: read RAW, ONE comment mentioning
|
|
132
|
+
-- _raw_key_fingerprint dropped the forging writer out of this set and the
|
|
133
|
+
-- file went fully green with the bypass live.
|
|
134
|
+
-- ANCHORED TO THE CALL SHAPE, not to a bare identifier substring
|
|
135
|
+
-- (message-board#196 review). `!~ '_raw_key_fingerprint'` is satisfied by ANY identifier
|
|
136
|
+
-- containing it, so a forging writer with an argument named
|
|
137
|
+
-- `p_raw_key_fingerprint` -- not even a suspicious name -- fell out of this
|
|
138
|
+
-- offender set and the file stayed green. `\m` will not match inside
|
|
139
|
+
-- `p_...` (both are word characters, so there is no boundary), and the
|
|
140
|
+
-- trailing `\(` requires an actual call rather than a mention.
|
|
141
|
+
-- SCHEMA-QUALIFIED, because a bare-name anchor proves call SYNTAX, not callee
|
|
142
|
+
-- IDENTITY: `\m_raw_key_fingerprint\s*\(` also matches
|
|
143
|
+
-- `zz_other._raw_key_fingerprint(...)` (measured true). Every live call
|
|
144
|
+
-- site in agent_board is `agent_board.`-qualified, so requiring the
|
|
145
|
+
-- qualification costs nothing today and is fail-CLOSED if someone later writes
|
|
146
|
+
-- an unqualified call (it reads as NOT calling). Overload count is pinned
|
|
147
|
+
-- separately, so schema + name is the identity.
|
|
148
|
+
and agent_board_test.stripped_body(oid) !~* '\magent_board\._raw_key_fingerprint\s*\('),
|
|
125
149
|
'',
|
|
126
150
|
'no agent_signing_keys.fingerprint writer bypasses _raw_key_fingerprint (catalog-derived; a new writer names itself)');
|
|
127
151
|
|
|
@@ -299,5 +323,16 @@ select ok(has_function_privilege('service_role',
|
|
|
299
323
|
'agent_board.mint_delegated_child(uuid, text, jsonb, text, interval, boolean)', 'EXECUTE'),
|
|
300
324
|
'service_role retains EXECUTE on mint_delegated_child');
|
|
301
325
|
|
|
326
|
+
|
|
327
|
+
-- The schema-qualified call anchor above treats `agent_board._raw_key_fingerprint` as an
|
|
328
|
+
-- IDENTITY. That is only true while the name resolves to exactly one function:
|
|
329
|
+
-- a second overload would be a different implementation the same anchor accepts.
|
|
330
|
+
-- Catalog-derived, so a new overload fails here and the count names itself.
|
|
331
|
+
select is(
|
|
332
|
+
(select count(*)::int from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
333
|
+
where n.nspname = 'agent_board' and p.proname = '_raw_key_fingerprint'),
|
|
334
|
+
1,
|
|
335
|
+
'agent_board._raw_key_fingerprint has exactly ONE overload, so the schema-qualified call anchor identifies it uniquely');
|
|
336
|
+
|
|
302
337
|
select finish();
|
|
303
338
|
rollback;
|