@consiliency/agent-board-schema 1.27.0 → 1.35.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 +32 -0
- package/migrations/20260909000100_agent_board_ledger_revoked_authority.sql +1036 -0
- package/migrations/20260910000100_agent_board_ceiling_lock_comment_truth.sql +89 -0
- package/migrations/20260911000100_agent_board_ledger_revoked_board_creation.sql +330 -0
- package/migrations/20260912000100_agent_board_receipt_subject_gate.sql +348 -0
- package/migrations/20260913000100_agent_board_task_thread_lock_order.sql +292 -0
- package/migrations/20260914000100_agent_board_claim_subject_gate.sql +500 -0
- package/migrations/20260915000100_agent_board_fedsession_stored_identity_gate.sql +353 -0
- package/migrations/20260916000100_agent_board_federated_identity_injective_slug.sql +1646 -0
- package/package.json +1 -1
- package/schema-manifest.json +75 -3
- package/src/tooling.test.mjs +47 -2
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +161 -30
- package/tests/agent_board_claim_subject_gate.test.sql +1025 -0
- package/tests/agent_board_enroll_resumption.test.sql +7 -1
- package/tests/agent_board_federated_identity_injective_slug.test.sql +388 -0
- package/tests/agent_board_fedsession.test.sql +17 -2
- package/tests/agent_board_fedsession_stored_identity_gate.test.sql +606 -0
- package/tests/agent_board_fk_lock_edges.test.sql +422 -0
- package/tests/agent_board_ledger_authority.test.sql +1081 -0
- package/tests/agent_board_receipt_subject_gate.test.sql +624 -0
- package/tests/agent_board_signer_lifecycle.test.sql +29 -6
- package/tests/agent_board_source_strip_helper.test.sql +157 -1
- package/tests/agent_board_task_thread_lock_order.test.sql +337 -0
- package/tests/catalog/p_metadata_parameters.json +20 -0
- package/tests/fixtures/test-prereqs.sql +420 -11
- package/tests/isolation/expected/ledger_authority_revoke.out +701 -0
- package/tests/isolation/expected/signer_lifecycle_revoke.out +1 -1
- package/tests/isolation/expected/task_thread_lock_order.out +203 -0
- package/tests/isolation/specs/enroll_identity_board_owner_lock_order.spec +5 -4
- package/tests/isolation/specs/fedsession_provider_absent_owner_lock.spec +4 -4
- package/tests/isolation/specs/ledger_authority_revoke.spec +405 -0
- package/tests/isolation/specs/signer_lifecycle_revoke.spec +10 -0
- package/tests/isolation/specs/task_thread_lock_order.spec +197 -0
- package/tests/seeds/20260916000100_agent_board_federated_identity_injective_slug.sql.pre.sql +196 -0
|
@@ -194,7 +194,13 @@ select is(
|
|
|
194
194
|
join pg_namespace n on n.oid = p.pronamespace
|
|
195
195
|
where n.nspname = 'agent_board'
|
|
196
196
|
and p.prokind = 'f'
|
|
197
|
-
|
|
197
|
+
-- stripped_body(p.oid), NOT strip_sql(p.prosrc): a SQL-standard-body function
|
|
198
|
+
-- keeps its body in prosqlbody and leaves prosrc EMPTY, so the prosrc form read
|
|
199
|
+
-- '' for it and this exact-set assertion would have reported the expected set
|
|
200
|
+
-- while a third signing-key creator was live (message-board#293). This site was
|
|
201
|
+
-- invisible to the #196 lint because that lint only inspects POSITIONAL and
|
|
202
|
+
-- NEGATIVE assertions, and this is a PRESENCE match -- see the note on the lint.
|
|
203
|
+
and agent_board_test.stripped_body(p.oid) ~* 'insert into agent_board\.agent_signing_keys'),
|
|
198
204
|
'_enroll_federated_identity_once, mint_delegated_child',
|
|
199
205
|
'EC-2: only the enroll creator and delegated-child mint insert signing keys (strip_sql-derived)');
|
|
200
206
|
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
-- message-board#246 (change 2 of 3) — the federated identity is derived through ONE
|
|
2
|
+
-- injective serialization, and the ambiguous one is gone from the catalog.
|
|
3
|
+
--
|
|
4
|
+
-- WHAT THIS FILE HAS TO PROVE, and the shape of each proof:
|
|
5
|
+
--
|
|
6
|
+
-- INJ-* The helper is injective where the old encoding was not. INJ-1 is not a
|
|
7
|
+
-- "these two differ" curiosity: INJ-0 first proves the same two pairs DID
|
|
8
|
+
-- collide under the old expression, so the fixture is the adversarial case
|
|
9
|
+
-- from the issue and not an arbitrary pair. INJ-2 generalises to a family
|
|
10
|
+
-- whose members differ only in where the separator falls, and names the
|
|
11
|
+
-- offenders on failure instead of reporting a total.
|
|
12
|
+
-- BF-* The SHIPPED backfill moved four NAMED pre-existing rows -- one per recovery
|
|
13
|
+
-- arm, plus one whose meta/identity values diverge from its slug -- across all
|
|
14
|
+
-- four surfaces that carry the slug -- and, the assertion this
|
|
15
|
+
-- whole change exists for, the #244 resumption lookup still resolves each pair
|
|
16
|
+
-- to the SAME owner uuid it resolved to before. Re-deriving the handle without
|
|
17
|
+
-- re-keying auth.users.email orphans the owner; that failure looks exactly
|
|
18
|
+
-- like a cosmetic relabel, so it is asserted per row, by uuid, not as a
|
|
19
|
+
-- property.
|
|
20
|
+
-- CLS-* Catalog-derived class invariants. The completeness claim is NOT "I grepped
|
|
21
|
+
-- the files": pg_proc is asked which functions still contain a derivation and
|
|
22
|
+
-- which route through the helper, and the answers are compared as NAME LISTS,
|
|
23
|
+
-- so a sixth site fails and names itself. Source scans go through
|
|
24
|
+
-- agent_board_test.stripped_body, the suite's one-pass comment/literal strip,
|
|
25
|
+
-- because a prosrc scan that does not strip is defeated by prose -- the
|
|
26
|
+
-- migration's own header quotes the old expression in a comment.
|
|
27
|
+
--
|
|
28
|
+
-- WHY THE FIXTURES ARE A PRE-MIGRATION SEED, not rows created here: the backfill runs
|
|
29
|
+
-- once, inside the migration. Rows created inside this transaction would be created
|
|
30
|
+
-- after it had already run. See tests/seeds/20260916000100_*.pre.sql for the four
|
|
31
|
+
-- identities and why each exists.
|
|
32
|
+
--
|
|
33
|
+
-- WHAT IS NOT CLAIMED. The backfill's REFUSAL arms (a row whose pair cannot be
|
|
34
|
+
-- recovered, or one with two distinct reproducing pairs) are not directly asserted
|
|
35
|
+
-- here, because a fixture that triggers them aborts the migration and therefore the
|
|
36
|
+
-- whole replay -- there is no green run left in which to assert. They are pinned
|
|
37
|
+
-- INDIRECTLY and that is stated rather than glossed: F1 carries two agents with the
|
|
38
|
+
-- same pair, so a backfill counting rows instead of DISTINCT pairs refuses F1 and every
|
|
39
|
+
-- BF-1 assertion dies with the migration; F2 is reachable only through owner_user_id;
|
|
40
|
+
-- F3 carries a decoy stamp that only the "reproduces the current slug" filter excludes.
|
|
41
|
+
-- Each of those three mutations makes the migration itself abort with a message naming
|
|
42
|
+
-- the offending uid, which is a louder failure than a `not ok`, but it is a failure of a
|
|
43
|
+
-- different kind and the mutation table reports it as such, with an assertion count of 0.
|
|
44
|
+
|
|
45
|
+
create extension if not exists pgtap with schema extensions;
|
|
46
|
+
set search_path = extensions, public;
|
|
47
|
+
|
|
48
|
+
begin;
|
|
49
|
+
|
|
50
|
+
select plan(34);
|
|
51
|
+
|
|
52
|
+
-- ---------------------------------------------------------------------------
|
|
53
|
+
-- INJ: the serialization
|
|
54
|
+
-- ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
-- INJ-0. The premise. Under the OLD expression these two distinct pairs -- the exact
|
|
57
|
+
-- example in the issue -- produce ONE slug. Without this, INJ-1 proves nothing: any two
|
|
58
|
+
-- unrelated pairs differ under any hash.
|
|
59
|
+
select is(
|
|
60
|
+
'fed-human-' || substr(encode(extensions.digest(convert_to(
|
|
61
|
+
'https://idp.example' || '|' || 'user|123', 'UTF8'), 'sha256'), 'hex'), 1, 32),
|
|
62
|
+
'fed-human-' || substr(encode(extensions.digest(convert_to(
|
|
63
|
+
'https://idp.example|user' || '|' || '123', 'UTF8'), 'sha256'), 'hex'), 1, 32),
|
|
64
|
+
'INJ-0: under the OLD encoding ("https://idp.example","user|123") and ("https://idp.example|user","123") derive the SAME slug');
|
|
65
|
+
|
|
66
|
+
-- INJ-1. The same two pairs under the helper. Named individually so the failure line
|
|
67
|
+
-- shows which slug moved.
|
|
68
|
+
select isnt(
|
|
69
|
+
agent_board._federated_identity_slug('https://idp.example', 'user|123'),
|
|
70
|
+
agent_board._federated_identity_slug('https://idp.example|user', '123'),
|
|
71
|
+
'INJ-1: under the helper ("https://idp.example","user|123") and ("https://idp.example|user","123") derive DIFFERENT slugs');
|
|
72
|
+
|
|
73
|
+
-- INJ-2. The family, not the instance: nine pairs whose concatenations are equal or
|
|
74
|
+
-- near-equal and which differ only in where the boundary falls. The assertion compares
|
|
75
|
+
-- a NAME LIST of colliding tag-pairs against '(none)', so a regression prints the tags
|
|
76
|
+
-- that collided rather than a count that says only "some did".
|
|
77
|
+
--
|
|
78
|
+
-- Tags h/i carry their own weight: they collide under a COLON separator, not under a
|
|
79
|
+
-- pipe one, so they fail a serialization that swapped the delimiter for the one the
|
|
80
|
+
-- netstring form also uses while dropping the length prefixes. Without them, INJ-2 would
|
|
81
|
+
-- red only for mutations INJ-1 already reds, and would be decoration.
|
|
82
|
+
select is(
|
|
83
|
+
(with p(tag, iss, sub) as (values
|
|
84
|
+
('a', 'https://idp.example', 'user|123'),
|
|
85
|
+
('b', 'https://idp.example|user', '123'),
|
|
86
|
+
('c', 'https://idp.example|', 'user|123'),
|
|
87
|
+
('d', 'https://idp.example', '|user|123'),
|
|
88
|
+
('e', 'ab', 'c'),
|
|
89
|
+
('f', 'a', 'bc'),
|
|
90
|
+
('g', 'a|b', 'c'),
|
|
91
|
+
('h', 'x', 'y:z'),
|
|
92
|
+
('i', 'x:y', 'z'))
|
|
93
|
+
select coalesce(string_agg(x.pair, ', ' order by x.pair), '(none)')
|
|
94
|
+
from (select distinct p1.tag || '/' || p2.tag as pair
|
|
95
|
+
from p p1 join p p2
|
|
96
|
+
on p1.tag < p2.tag
|
|
97
|
+
and agent_board._federated_identity_slug(p1.iss, p1.sub)
|
|
98
|
+
= agent_board._federated_identity_slug(p2.iss, p2.sub)) x),
|
|
99
|
+
'(none)',
|
|
100
|
+
'INJ-2: no two of the nine boundary-shifted pairs share a slug (offending tag pairs are named)');
|
|
101
|
+
|
|
102
|
+
-- INJ-3..6. Fail closed on a missing component rather than deriving one shared
|
|
103
|
+
-- degenerate owner. Four separate assertions because they are four separate conjuncts:
|
|
104
|
+
-- a single "null input raises" would be satisfied by a guard on issuer alone.
|
|
105
|
+
select throws_ok(
|
|
106
|
+
$$select agent_board._federated_identity_slug(null, 'sub')$$,
|
|
107
|
+
'22023',
|
|
108
|
+
'federated identity slug requires a non-empty issuer and subject',
|
|
109
|
+
'INJ-3: a NULL issuer raises 22023 instead of deriving a slug');
|
|
110
|
+
select throws_ok(
|
|
111
|
+
$$select agent_board._federated_identity_slug('', 'sub')$$,
|
|
112
|
+
'22023',
|
|
113
|
+
'federated identity slug requires a non-empty issuer and subject',
|
|
114
|
+
'INJ-4: an EMPTY issuer raises 22023 instead of deriving a slug');
|
|
115
|
+
select throws_ok(
|
|
116
|
+
$$select agent_board._federated_identity_slug('https://idp.example', null)$$,
|
|
117
|
+
'22023',
|
|
118
|
+
'federated identity slug requires a non-empty issuer and subject',
|
|
119
|
+
'INJ-5: a NULL subject raises 22023 instead of deriving a slug');
|
|
120
|
+
select throws_ok(
|
|
121
|
+
$$select agent_board._federated_identity_slug('https://idp.example', '')$$,
|
|
122
|
+
'22023',
|
|
123
|
+
'federated identity slug requires a non-empty issuer and subject',
|
|
124
|
+
'INJ-6: an EMPTY subject raises 22023 instead of deriving a slug');
|
|
125
|
+
|
|
126
|
+
-- ---------------------------------------------------------------------------
|
|
127
|
+
-- BF: the three named pre-existing identities the shipped backfill re-keyed
|
|
128
|
+
-- ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
-- F1 — the HUMAN arm. uid d2460001-0000-4000-8000-000000000001,
|
|
131
|
+
-- pair ("https://fixture-246.example/idp-a", "fixture-246-subject-a"),
|
|
132
|
+
-- slug fed-human-d50f81b6913e066dba4c19f5cb6b202a -> fed-human-6c056fe12e1fb58fb2c072ff8bd9a613.
|
|
133
|
+
|
|
134
|
+
select is(
|
|
135
|
+
(select u.email from auth.users u where u.id = 'd2460001-0000-4000-8000-000000000001'),
|
|
136
|
+
'agent+fed-human-6c056fe12e1fb58fb2c072ff8bd9a613@agent-board.local',
|
|
137
|
+
'BF-1a: F1 auth.users.email re-keyed to the new slug (the value _ensure_agent_auth_user looks up)');
|
|
138
|
+
|
|
139
|
+
select is(
|
|
140
|
+
(select u.raw_user_meta_data->>'slug' from auth.users u
|
|
141
|
+
where u.id = 'd2460001-0000-4000-8000-000000000001'),
|
|
142
|
+
'fed-human-6c056fe12e1fb58fb2c072ff8bd9a613',
|
|
143
|
+
'BF-1b: F1 auth.users.raw_user_meta_data->>slug re-keyed to the new slug');
|
|
144
|
+
|
|
145
|
+
select is(
|
|
146
|
+
(select i.provider_id || '|' || (i.identity_data->>'sub') || '|' || (i.identity_data->>'slug')
|
|
147
|
+
from auth.identities i where i.user_id = 'd2460001-0000-4000-8000-000000000001'),
|
|
148
|
+
'fed-human-6c056fe12e1fb58fb2c072ff8bd9a613|fed-human-6c056fe12e1fb58fb2c072ff8bd9a613|fed-human-6c056fe12e1fb58fb2c072ff8bd9a613',
|
|
149
|
+
'BF-1c: F1 auth.identities provider_id AND identity_data.sub AND identity_data.slug all re-keyed (each position shown)');
|
|
150
|
+
|
|
151
|
+
select is(
|
|
152
|
+
(select a.handle from agent_board.agents a
|
|
153
|
+
where a.id = 'd2460101-0000-4000-8000-000000000001'),
|
|
154
|
+
'fed-human-6c056fe12e1fb58fb2c072ff8bd9a613',
|
|
155
|
+
'BF-1d: F1 human agent handle re-keyed (the handle IS the slug on this path)');
|
|
156
|
+
|
|
157
|
+
-- THE ASSERTION THIS CHANGE EXISTS FOR. enroll_federated_identity resolves the resuming
|
|
158
|
+
-- owner by a pure lookup on format('agent+%s@agent-board.local', <derived slug>). If the
|
|
159
|
+
-- derivation moved and the email did not, this returns NULL, the non-null owner
|
|
160
|
+
-- conjunct fails, and the owner cannot resume. Asserted by uuid, per row.
|
|
161
|
+
select is(
|
|
162
|
+
(select u.id from auth.users u
|
|
163
|
+
where lower(u.email) = format('agent+%s@agent-board.local',
|
|
164
|
+
agent_board._federated_identity_slug('https://fixture-246.example/idp-a', 'fixture-246-subject-a'))),
|
|
165
|
+
'd2460001-0000-4000-8000-000000000001'::uuid,
|
|
166
|
+
'BF-1e: F1 the #244 resumption lookup still resolves the pair to the SAME owner uuid');
|
|
167
|
+
|
|
168
|
+
-- A count of 2 is satisfied by one agent twice, or by two agents that are not these
|
|
169
|
+
-- two -- the message says "both", so the body names both. (message-board#246 panel; the
|
|
170
|
+
-- same shape shipped twice in #306.)
|
|
171
|
+
select is(
|
|
172
|
+
(select string_agg(a.id::text, ', ' order by a.id::text) from agent_board.agents a
|
|
173
|
+
where a.owner_user_id = 'd2460001-0000-4000-8000-000000000001'),
|
|
174
|
+
'd2460101-0000-4000-8000-000000000001, d2460102-0000-4000-8000-000000000001',
|
|
175
|
+
'BF-1f: F1 still owns exactly its human row d2460101 and its owned agent d2460102 (the uuid moved nowhere, so no FK moved)');
|
|
176
|
+
|
|
177
|
+
-- F2 — the OWNER-ONLY arm. uid d2460002-0000-4000-8000-000000000002, no agent carries
|
|
178
|
+
-- this auth_user_id; the pair is reachable only through owner_user_id.
|
|
179
|
+
-- slug fed-human-7ab9c8b527e1cce671305defc65fe7c0 -> fed-human-ec861cc1407f8f585e94cb9e8ece6039.
|
|
180
|
+
|
|
181
|
+
select is(
|
|
182
|
+
(select u.email from auth.users u where u.id = 'd2460002-0000-4000-8000-000000000002'),
|
|
183
|
+
'agent+fed-human-ec861cc1407f8f585e94cb9e8ece6039@agent-board.local',
|
|
184
|
+
'BF-2a: F2 auth.users.email re-keyed although the pair is reachable only via owner_user_id');
|
|
185
|
+
|
|
186
|
+
select is(
|
|
187
|
+
(select u.raw_user_meta_data->>'slug' from auth.users u
|
|
188
|
+
where u.id = 'd2460002-0000-4000-8000-000000000002'),
|
|
189
|
+
'fed-human-ec861cc1407f8f585e94cb9e8ece6039',
|
|
190
|
+
'BF-2b: F2 auth.users.raw_user_meta_data->>slug re-keyed');
|
|
191
|
+
|
|
192
|
+
select is(
|
|
193
|
+
(select i.provider_id || '|' || (i.identity_data->>'sub') || '|' || (i.identity_data->>'slug')
|
|
194
|
+
from auth.identities i where i.user_id = 'd2460002-0000-4000-8000-000000000002'),
|
|
195
|
+
'fed-human-ec861cc1407f8f585e94cb9e8ece6039|fed-human-ec861cc1407f8f585e94cb9e8ece6039|fed-human-ec861cc1407f8f585e94cb9e8ece6039',
|
|
196
|
+
'BF-2c: F2 auth.identities provider_id AND identity_data.sub AND identity_data.slug all re-keyed (each position shown)');
|
|
197
|
+
|
|
198
|
+
-- The complement of BF-1d: an agent whose handle is NOT the slug must not be touched.
|
|
199
|
+
-- "update agents set handle = new where handle = old" is narrow on purpose.
|
|
200
|
+
select is(
|
|
201
|
+
(select a.handle from agent_board.agents a
|
|
202
|
+
where a.id = 'd2460201-0000-4000-8000-000000000002'),
|
|
203
|
+
'mb246-fixture-a2',
|
|
204
|
+
'BF-2d: F2 owned agent keeps its own handle -- only a handle that IS the slug is rewritten');
|
|
205
|
+
|
|
206
|
+
select is(
|
|
207
|
+
(select u.id from auth.users u
|
|
208
|
+
where lower(u.email) = format('agent+%s@agent-board.local',
|
|
209
|
+
agent_board._federated_identity_slug('https://fixture-246.example/idp-b', 'fixture-246-subject-b'))),
|
|
210
|
+
'd2460002-0000-4000-8000-000000000002'::uuid,
|
|
211
|
+
'BF-2e: F2 the #244 resumption lookup still resolves the pair to the SAME owner uuid');
|
|
212
|
+
|
|
213
|
+
select is(
|
|
214
|
+
(select string_agg(a.id::text, ', ' order by a.id::text) from agent_board.agents a
|
|
215
|
+
where a.owner_user_id = 'd2460002-0000-4000-8000-000000000002'),
|
|
216
|
+
'd2460201-0000-4000-8000-000000000002',
|
|
217
|
+
'BF-2f: F2 still owns exactly its one agent d2460201 (named, not counted)');
|
|
218
|
+
|
|
219
|
+
-- F3 — the DECOY arm. uid d2460003-0000-4000-8000-000000000003 owns an agent stamped
|
|
220
|
+
-- with an unrelated pair; only the "reproduces the current slug" filter excludes it.
|
|
221
|
+
-- slug fed-human-def7953eb096d786392090ce33952d9d -> fed-human-d4211005ca6d96ac0f8a90d2065dceda.
|
|
222
|
+
|
|
223
|
+
select is(
|
|
224
|
+
(select u.email from auth.users u where u.id = 'd2460003-0000-4000-8000-000000000003'),
|
|
225
|
+
'agent+fed-human-d4211005ca6d96ac0f8a90d2065dceda@agent-board.local',
|
|
226
|
+
'BF-3a: F3 auth.users.email re-keyed to the slug of the pair that REPRODUCES its old slug, not the decoy');
|
|
227
|
+
|
|
228
|
+
select is(
|
|
229
|
+
(select u.raw_user_meta_data->>'slug' from auth.users u
|
|
230
|
+
where u.id = 'd2460003-0000-4000-8000-000000000003'),
|
|
231
|
+
'fed-human-d4211005ca6d96ac0f8a90d2065dceda',
|
|
232
|
+
'BF-3b: F3 auth.users.raw_user_meta_data->>slug re-keyed');
|
|
233
|
+
|
|
234
|
+
select is(
|
|
235
|
+
(select i.provider_id || '|' || (i.identity_data->>'sub') || '|' || (i.identity_data->>'slug')
|
|
236
|
+
from auth.identities i where i.user_id = 'd2460003-0000-4000-8000-000000000003'),
|
|
237
|
+
'fed-human-d4211005ca6d96ac0f8a90d2065dceda|fed-human-d4211005ca6d96ac0f8a90d2065dceda|fed-human-d4211005ca6d96ac0f8a90d2065dceda',
|
|
238
|
+
'BF-3c: F3 auth.identities provider_id AND identity_data.sub AND identity_data.slug all re-keyed (each position shown)');
|
|
239
|
+
|
|
240
|
+
select is(
|
|
241
|
+
(select a.handle from agent_board.agents a
|
|
242
|
+
where a.id = 'd2460301-0000-4000-8000-000000000003'),
|
|
243
|
+
'fed-human-d4211005ca6d96ac0f8a90d2065dceda',
|
|
244
|
+
'BF-3d: F3 human agent handle re-keyed');
|
|
245
|
+
|
|
246
|
+
select is(
|
|
247
|
+
(select u.id from auth.users u
|
|
248
|
+
where lower(u.email) = format('agent+%s@agent-board.local',
|
|
249
|
+
agent_board._federated_identity_slug('https://fixture-246.example/idp-c', 'fixture-246-subject-c'))),
|
|
250
|
+
'd2460003-0000-4000-8000-000000000003'::uuid,
|
|
251
|
+
'BF-3e: F3 the #244 resumption lookup still resolves the pair to the SAME owner uuid');
|
|
252
|
+
|
|
253
|
+
-- The stamp is the PAIR. Reading only the issuer left a subject-only rewrite green while
|
|
254
|
+
-- the message claimed "both ... its stamp"; both stamp fields are read here.
|
|
255
|
+
select is(
|
|
256
|
+
(select a.handle || '|' || (a.metadata->'federation'->>'issuer')
|
|
257
|
+
|| '|' || (a.metadata->'federation'->>'subject')
|
|
258
|
+
from agent_board.agents a
|
|
259
|
+
where a.id = 'd2460302-0000-4000-8000-000000000003'),
|
|
260
|
+
'mb246-fixture-f3-decoy|https://fixture-246.example/idp-decoy|fixture-246-subject-decoy',
|
|
261
|
+
'BF-3f: F3 decoy-stamped agent is untouched in its handle AND in both fields of its stamp (each position shown)');
|
|
262
|
+
|
|
263
|
+
-- F4 — the DIVERGENT arm. uid d2460004-0000-4000-8000-000000000004, pair
|
|
264
|
+
-- ("https://fixture-246.example/idp-d", "fixture-246-subject-d"),
|
|
265
|
+
-- slug fed-human-38b45a2207cfb4847a4c62c9973cd676 -> fed-human-25bf058f71a25339995a9a2012ced65b.
|
|
266
|
+
--
|
|
267
|
+
-- Two of the backfill's four surfaces are rewritten only when they EQUAL the old slug --
|
|
268
|
+
-- raw_user_meta_data.slug and identity_data.sub/slug, one `case` arm each. F1-F3 all have
|
|
269
|
+
-- them equal, so making both arms UNCONDITIONAL passed every assertion in this file. This
|
|
270
|
+
-- fixture is the complement of BF-2d, which pins the same narrowness for agents.handle,
|
|
271
|
+
-- and it was simply missing.
|
|
272
|
+
|
|
273
|
+
select is(
|
|
274
|
+
(select u.email from auth.users u where u.id = 'd2460004-0000-4000-8000-000000000004'),
|
|
275
|
+
'agent+fed-human-25bf058f71a25339995a9a2012ced65b@agent-board.local',
|
|
276
|
+
'BF-4a: F4 auth.users.email re-keyed -- this row WAS visited, so BF-4b/4c below are not passing vacuously');
|
|
277
|
+
|
|
278
|
+
select is(
|
|
279
|
+
(select u.raw_user_meta_data->>'slug' from auth.users u
|
|
280
|
+
where u.id = 'd2460004-0000-4000-8000-000000000004'),
|
|
281
|
+
'mb246-f4-legacy-meta-slug',
|
|
282
|
+
'BF-4b: F4 raw_user_meta_data->>slug is NOT rewritten, because it never held the old slug');
|
|
283
|
+
|
|
284
|
+
select is(
|
|
285
|
+
(select i.provider_id || '|' || (i.identity_data->>'sub') || '|' || (i.identity_data->>'slug')
|
|
286
|
+
from auth.identities i where i.user_id = 'd2460004-0000-4000-8000-000000000004'),
|
|
287
|
+
'fed-human-25bf058f71a25339995a9a2012ced65b|mb246-f4-legacy-sub|mb246-f4-legacy-slug',
|
|
288
|
+
'BF-4c: F4 identities provider_id IS re-keyed while identity_data.sub and .slug are NOT -- each of the three positions shown');
|
|
289
|
+
|
|
290
|
+
-- Population-wide, not fixture-wide: every fed-human owner email in the database must be
|
|
291
|
+
-- reproducible from a stamp under the NEW derivation. Old and new slugs are
|
|
292
|
+
-- shape-identical, so "no old-form rows remain" is not something a pattern can see;
|
|
293
|
+
-- reproducibility is what can be checked, and the failure names the uids.
|
|
294
|
+
select is(
|
|
295
|
+
(select coalesce(string_agg(u.id::text, ', ' order by u.id::text), '(none)')
|
|
296
|
+
from auth.users u
|
|
297
|
+
where lower(u.email) like 'agent+fed-human-%@agent-board.local'
|
|
298
|
+
and not exists (
|
|
299
|
+
select 1 from agent_board.agents a
|
|
300
|
+
where (a.auth_user_id = u.id or a.owner_user_id = u.id)
|
|
301
|
+
and a.metadata->'federation'->>'issuer' is not null
|
|
302
|
+
and a.metadata->'federation'->>'subject' is not null
|
|
303
|
+
and agent_board._federated_identity_slug(
|
|
304
|
+
a.metadata->'federation'->>'issuer',
|
|
305
|
+
a.metadata->'federation'->>'subject') =
|
|
306
|
+
substring(lower(u.email) from '^agent\+(fed-human-[0-9a-f]{32})@agent-board\.local$'))),
|
|
307
|
+
'(none)',
|
|
308
|
+
'BF-POP: every fed-human owner row in the database re-derives from a stored stamp under the NEW helper (offending uids are named)');
|
|
309
|
+
|
|
310
|
+
-- ---------------------------------------------------------------------------
|
|
311
|
+
-- CLS: catalog-derived class invariants
|
|
312
|
+
-- ---------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
-- CLS-1. The old encoding is GONE, not shadowed. Any inline derivation must build the
|
|
315
|
+
-- 'fed-human-' prefix, so the prefix literal is the class marker. Comments are stripped
|
|
316
|
+
-- (keep_literals => true) because the migration header quotes the old expression in
|
|
317
|
+
-- prose and an unstripped scan would be defeated by exactly that.
|
|
318
|
+
select is(
|
|
319
|
+
(select coalesce(string_agg(p.oid::regprocedure::text, ', ' order by p.oid::regprocedure::text), '(none)')
|
|
320
|
+
from pg_proc p
|
|
321
|
+
where p.pronamespace = 'agent_board'::regnamespace
|
|
322
|
+
and p.proname <> '_federated_identity_slug'
|
|
323
|
+
and agent_board_test.stripped_body(p.oid, true) like '%fed-human-%'),
|
|
324
|
+
'(none)',
|
|
325
|
+
'CLS-1: no agent_board function except _federated_identity_slug builds a fed-human- slug (offenders are named)');
|
|
326
|
+
|
|
327
|
+
-- CLS-2. The complement of CLS-1: a re-introduced derivation that avoided the prefix
|
|
328
|
+
-- literal would still carry the ambiguous separator shape. Catches the same class from
|
|
329
|
+
-- the other side, so neither assertion is the only thing standing between the schema
|
|
330
|
+
-- and a copied expression.
|
|
331
|
+
select is(
|
|
332
|
+
(select coalesce(string_agg(p.oid::regprocedure::text, ', ' order by p.oid::regprocedure::text), '(none)')
|
|
333
|
+
from pg_proc p
|
|
334
|
+
where p.pronamespace = 'agent_board'::regnamespace
|
|
335
|
+
and agent_board_test.stripped_body(p.oid, true) ~ 'p_issuer\s*\|\|\s*''\|''\s*\|\|\s*p_subject'),
|
|
336
|
+
'(none)',
|
|
337
|
+
'CLS-2: no agent_board function still concatenates p_issuer || ''|'' || p_subject (offenders are named)');
|
|
338
|
+
|
|
339
|
+
-- CLS-3. Completeness, positively and per site. Not a total: the list carries each
|
|
340
|
+
-- function's NAME and its own call count, so "five callers" cannot be satisfied by one
|
|
341
|
+
-- function calling five times, and enroll_federated_identity's SECOND call site (the
|
|
342
|
+
-- #244 resumption lookup, which is a different site from the mint) is named separately
|
|
343
|
+
-- from its first.
|
|
344
|
+
select is(
|
|
345
|
+
(select coalesce(string_agg(x.entry, ', ' order by x.entry), '(none)')
|
|
346
|
+
from (select p.proname || '=' ||
|
|
347
|
+
(select count(*) from regexp_matches(
|
|
348
|
+
agent_board_test.stripped_body(p.oid, true),
|
|
349
|
+
'_federated_identity_slug\(', 'g'))::text as entry
|
|
350
|
+
from pg_proc p
|
|
351
|
+
where p.pronamespace = 'agent_board'::regnamespace
|
|
352
|
+
and p.proname <> '_federated_identity_slug'
|
|
353
|
+
and agent_board_test.stripped_body(p.oid, true) like '%\_federated\_identity\_slug(%') x),
|
|
354
|
+
'_enroll_federated_human_after_revocation_gate=1, _enroll_federated_identity_once=1, enroll_federated_human=1, enroll_federated_identity=2, onboard_federated_fleet_agent=1',
|
|
355
|
+
'CLS-3: exactly these five functions route through the helper, each with its own call count');
|
|
356
|
+
|
|
357
|
+
-- CLS-4. The helper is not a public lever. It is pure and mints nothing, but it is the
|
|
358
|
+
-- function that decides which account a pair maps to, and CREATE FUNCTION grants EXECUTE
|
|
359
|
+
-- to PUBLIC by default.
|
|
360
|
+
select ok(
|
|
361
|
+
not has_function_privilege('public', 'agent_board._federated_identity_slug(text,text)', 'EXECUTE'),
|
|
362
|
+
'CLS-4: _federated_identity_slug carries no PUBLIC EXECUTE grant');
|
|
363
|
+
|
|
364
|
+
-- CLS-5. create or replace (never drop) left every rebuilt function with a COMMENT -- a
|
|
365
|
+
-- DROP would have silently discarded the comment AND the ACL. Named per function so a
|
|
366
|
+
-- single surviving comment cannot stand in for five.
|
|
367
|
+
--
|
|
368
|
+
-- What this pins, stated exactly rather than implied by the word "kept": the comment is
|
|
369
|
+
-- NOT NULL. It does not pin the TEXT, so a comment replaced with different prose passes.
|
|
370
|
+
-- Pinning the text would pin five paragraphs of prose to the byte and fail on every
|
|
371
|
+
-- wording change; non-null is the property that catches the failure mode that actually
|
|
372
|
+
-- occurs here, which is a drop+recreate discarding the comment altogether.
|
|
373
|
+
select is(
|
|
374
|
+
(select coalesce(string_agg(x.name, ', ' order by x.name), '(none)')
|
|
375
|
+
from (select p.proname as name from pg_proc p
|
|
376
|
+
where p.pronamespace = 'agent_board'::regnamespace
|
|
377
|
+
and p.proname in ('enroll_federated_human',
|
|
378
|
+
'_enroll_federated_human_after_revocation_gate',
|
|
379
|
+
'_enroll_federated_identity_once',
|
|
380
|
+
'enroll_federated_identity',
|
|
381
|
+
'onboard_federated_fleet_agent')
|
|
382
|
+
and obj_description(p.oid, 'pg_proc') is null) x),
|
|
383
|
+
'(none)',
|
|
384
|
+
'CLS-5: every rebuilt derivation site still carries a non-null COMMENT (functions with none are named)');
|
|
385
|
+
|
|
386
|
+
select * from finish();
|
|
387
|
+
|
|
388
|
+
rollback;
|
|
@@ -117,12 +117,27 @@ select is(
|
|
|
117
117
|
(select count(*)::int from auth.users u where u.id = (select auth_user_id from _r where label='A')),
|
|
118
118
|
1, 'the synthetic auth.users row for agent A exists');
|
|
119
119
|
-- 11
|
|
120
|
+
-- message-board#246: the derivation moved from `issuer || '|' || subject` to the
|
|
121
|
+
-- netstring form `len(issuer):issuer:len(subject):subject`, because the old one was not
|
|
122
|
+
-- injective and the slug keys the auth.users row. The expected value is still spelled
|
|
123
|
+
-- out here rather than calling agent_board._federated_identity_slug: an assertion that
|
|
124
|
+
-- calls the function under test compares it with itself and would follow any future
|
|
125
|
+
-- change to it silently. This is an independent restatement of the contract.
|
|
126
|
+
--
|
|
127
|
+
-- The message no longer says "byte-identical to enroll_federated_identity". That was true
|
|
128
|
+
-- when both functions carried their own copy of the expression and it was the risk worth
|
|
129
|
+
-- naming; enroll_federated_identity now calls the same helper, so there is no second copy
|
|
130
|
+
-- for this one to be byte-identical TO, and a message asserting that would be claiming
|
|
131
|
+
-- something no code does.
|
|
120
132
|
select is(
|
|
121
133
|
(select handle from _r where label='A'),
|
|
122
134
|
'fed-human-' || substr(
|
|
123
|
-
encode(extensions.digest(convert_to(
|
|
135
|
+
encode(extensions.digest(convert_to(
|
|
136
|
+
length((select iss from _f))::text || ':' || (select iss from _f) || ':' ||
|
|
137
|
+
length((select sub_a from _f))::text || ':' || (select sub_a from _f),
|
|
138
|
+
'UTF8'), 'sha256'), 'hex'),
|
|
124
139
|
1, 32),
|
|
125
|
-
'handle = fed-human-<sha256(issuer
|
|
140
|
+
'handle = fed-human-<sha256(len:issuer:len:subject)[:32]> (the contract every site now shares via _federated_identity_slug)');
|
|
126
141
|
|
|
127
142
|
-- metadata carries the federation snapshot, boolean-only email, no PII --------------------
|
|
128
143
|
-- 12
|