@consiliency/agent-board-schema 1.22.0 → 1.24.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/README.md +75 -1
- package/fixtures/already-applied.json +40 -0
- package/migrations/20260901000100_agent_board_atomic_fleet_onboarding.sql +1496 -0
- package/migrations/20260901000110_agent_board_fleet_onboarding_ceiling_lock.sql +604 -0
- package/migrations/20260901000120_agent_board_human_revocation_evidence_completeness.sql +78 -0
- package/migrations/20260901000130_agent_board_fedsession_human_revocation_gate.sql +74 -0
- package/migrations/20260901000140_agent_board_fedsession_board_owner_lock_order.sql +102 -0
- package/migrations/20260902000100_agent_board_outbox_wake_ordering.sql +184 -0
- package/migrations/20260903000100_agent_board_enroll_identity_board_owner_lock_order.sql +604 -0
- package/migrations/20260903000200_agent_board_fedsession_provider_absence_gate.sql +177 -0
- package/migrations/20260904000100_agent_board_wake_recovery_surface.sql +155 -0
- package/migrations/20260905000100_agent_board_consent_gating_reconcile.sql +151 -0
- package/package.json +1 -1
- package/schema-manifest.json +93 -3
- package/src/check.js +23 -0
- package/src/index.js +20 -0
- package/src/plan.js +79 -5
- package/src/tooling.test.mjs +237 -4
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +623 -0
- package/tests/agent_board_atomic_fleet_onboarding_backfill.test.sql +142 -0
- package/tests/agent_board_atomic_fleet_onboarding_runtime.test.sql +1250 -0
- package/tests/agent_board_enroll_federated_identity.test.sql +3 -3
- package/tests/agent_board_enroll_pop_binding.test.sql +13 -3
- package/tests/agent_board_enrollment_audit_content.test.sql +11 -7
- package/tests/agent_board_federated_lock_order_class.test.sql +390 -0
- package/tests/agent_board_fedsession.test.sql +46 -2
- package/tests/agent_board_fedsession_provider_absence_gate.test.sql +170 -0
- package/tests/agent_board_inbox_reads.test.sql +16 -19
- package/tests/agent_board_outbox_wake_ordering.test.sql +452 -0
- package/tests/agent_board_portal_offline_invariant.test.sql +1 -1
- package/tests/agent_board_roomlink.test.sql +2 -2
- package/tests/agent_board_wake_recovery.test.sql +756 -0
- package/tests/isolation/expected/atomic_fedsession_fleet_lock_order.out +79 -0
- package/tests/isolation/expected/atomic_fedsession_human_revoke.out +87 -0
- package/tests/isolation/expected/atomic_fleet_authority_races.out +469 -0
- package/tests/isolation/expected/atomic_fleet_binding_revoke.out +103 -0
- package/tests/isolation/expected/atomic_fleet_board_archive.out +117 -0
- package/tests/isolation/expected/atomic_fleet_human_fresh_revoke.out +136 -0
- package/tests/isolation/expected/atomic_fleet_identity_disposition.out +61 -0
- package/tests/isolation/expected/atomic_fleet_lifecycle_races.out +1479 -0
- package/tests/isolation/expected/atomic_fleet_onboarding.out +75 -0
- package/tests/isolation/expected/atomic_fleet_subtree_lock_order.out +60 -0
- package/tests/isolation/expected/ceiling_setter_lock_before_upsert.out +95 -0
- package/tests/isolation/expected/enroll_identity_board_owner_lock_order.out +55 -0
- package/tests/isolation/expected/fedsession_provider_absent_owner_lock.out +37 -0
- package/tests/isolation/expected/onboarding_ceiling_lock_order.out +123 -0
- package/tests/isolation/expected/outbox_wake_ordering.out +86 -0
- package/tests/isolation/expected/wake_recovery_late_commit.out +89 -0
- package/tests/isolation/specs/atomic_fedsession_fleet_lock_order.spec +117 -0
- package/tests/isolation/specs/atomic_fedsession_human_revoke.spec +66 -0
- package/tests/isolation/specs/atomic_fleet_authority_races.spec +235 -0
- package/tests/isolation/specs/atomic_fleet_binding_revoke.spec +127 -0
- package/tests/isolation/specs/atomic_fleet_board_archive.spec +134 -0
- package/tests/isolation/specs/atomic_fleet_human_fresh_revoke.spec +142 -0
- package/tests/isolation/specs/atomic_fleet_identity_disposition.spec +118 -0
- package/tests/isolation/specs/atomic_fleet_lifecycle_races.spec +244 -0
- package/tests/isolation/specs/atomic_fleet_onboarding.spec +106 -0
- package/tests/isolation/specs/atomic_fleet_subtree_lock_order.spec +147 -0
- package/tests/isolation/specs/ceiling_setter_lock_before_upsert.spec +117 -0
- package/tests/isolation/specs/enroll_identity_board_owner_lock_order.spec +117 -0
- package/tests/isolation/specs/fedsession_provider_absent_owner_lock.spec +63 -0
- package/tests/isolation/specs/onboarding_ceiling_lock_order.spec +183 -0
- package/tests/isolation/specs/outbox_wake_ordering.spec +202 -0
- package/tests/isolation/specs/wake_recovery_late_commit.spec +207 -0
- package/tests/seeds/20260901000100_agent_board_atomic_fleet_onboarding.sql.pre.sql +110 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
-- Consiliency/message-board#196 -- the POSITIONAL lock-order assertions in this
|
|
2
|
+
-- file read a STRIPPED function body, not raw text.
|
|
3
|
+
--
|
|
4
|
+
-- WHAT WENT WRONG. Every `strpos(...) < strpos(...)` claim below used to read
|
|
5
|
+
-- `pg_get_functiondef()` RAW. Moving `perform
|
|
6
|
+
-- agent_board._lock_board_authority_ceiling(p_board_id)` to the END of
|
|
7
|
+
-- set_board_authority_ceiling, after its upsert, made assertion 50 fail --
|
|
8
|
+
-- correctly. Adding ONE plain `--` comment naming the marker made the same
|
|
9
|
+
-- mutant pass 52/52. No `**/` trick was needed; the file stripped nothing. The
|
|
10
|
+
-- ordering that assertion guards is the #189 ceiling ABBA edge, and measured
|
|
11
|
+
-- against all 20 committed isolation specs, NOTHING else moved: it had no
|
|
12
|
+
-- runtime backstop at all.
|
|
13
|
+
--
|
|
14
|
+
-- WHAT THE FIX IS. Each positional assertion now locates its markers on
|
|
15
|
+
-- `pg_temp._stripped_body(<fn>)` -- prosrc with dollar-quoted blocks, single-quoted
|
|
16
|
+
-- literals, line comments and block comments removed in ONE alternation pass. The
|
|
17
|
+
-- regex is copied BYTE-FOR-BYTE from agent_board_federated_lock_order_class.test.sql,
|
|
18
|
+
-- where its three failure modes were found the hard way: a block comment whose body
|
|
19
|
+
-- ends in `*`; a dollar-quote desync (agent_board.mark_retention_metadata survives a
|
|
20
|
+
-- naive scanner only because its 64 apostrophes are an even count); and an and/or
|
|
21
|
+
-- gate polarity error. Do NOT re-derive it, and do NOT split it into two passes --
|
|
22
|
+
-- both orders are wrong (comments-first eats a literal containing `--`;
|
|
23
|
+
-- literals-first eats a comment containing an apostrophe).
|
|
24
|
+
--
|
|
25
|
+
-- A FOURTH FAILURE MODE IS KNOWN, DEMONSTRATED, AND STILL OPEN -- TRACKED AS
|
|
26
|
+
-- Consiliency/message-board#199. Postgres block comments NEST, and
|
|
27
|
+
-- `(/\*([^*]|\*+[^*/])*\*+/)` stops at the FIRST `*/`, so everything after an inner
|
|
28
|
+
-- terminator survives the strip. One line of the form
|
|
29
|
+
-- /* lock-order note: /* see #189 */ <marker> */
|
|
30
|
+
-- puts the marker back into the stripped body and masks it again. Two reviewers
|
|
31
|
+
-- reproduced this independently; so did this file's own mutation run.
|
|
32
|
+
--
|
|
33
|
+
-- MEASURED BLAST RADIUS, so no reader has to guess which claims still hold. Against
|
|
34
|
+
-- that mask, EIGHT of the NINE positional assertions below go GREEN with the
|
|
35
|
+
-- ordering they name genuinely violated. (The revoke_fleet_subject claim is the
|
|
36
|
+
-- exception, and only incidentally: it locates the lock RELATIVE to its loop header,
|
|
37
|
+
-- so an injected header moves the search window rather than satisfying it. That is a
|
|
38
|
+
-- property of that one assertion's shape, not a defence anything else has.)
|
|
39
|
+
--
|
|
40
|
+
-- WHERE THAT LEAVES A REAL GAP. What still catches the mutant is the RUNTIME
|
|
41
|
+
-- counterparty, which no comment can reach -- and only some claims have one:
|
|
42
|
+
-- * machine-before-enrolled-agent -> atomic_fleet_binding_revoke.spec and
|
|
43
|
+
-- atomic_fleet_lifecycle_races.spec still fail. Guarded.
|
|
44
|
+
-- * advisory-before-boards, and the ceiling setter's lock-before-upsert ->
|
|
45
|
+
-- onboarding_ceiling_lock_order.spec and ceiling_setter_lock_before_upsert.spec
|
|
46
|
+
-- still fail. Guarded.
|
|
47
|
+
-- * BOARDS-BEFORE-CEILING-READ -> **NOTHING FAILS**. Assertion 49 goes green, and
|
|
48
|
+
-- so does agent_board_federated_lock_order_class.test.sql EC-3 (15/15) -- it
|
|
49
|
+
-- carries the same regex, so the nested mask defeats it too. Both new specs
|
|
50
|
+
-- scope this edge out and print `same`, and atomic_fleet_board_archive.spec
|
|
51
|
+
-- BLOCKS rather than diagnosing, which under a runner with no timeout is a
|
|
52
|
+
-- stuck job, not a failure. This edge currently has NO functioning guard
|
|
53
|
+
-- against a nested block comment. Stated plainly because that is the whole
|
|
54
|
+
-- point of this file: a guard that overclaims is worse than none.
|
|
55
|
+
-- * the three claims already labelled SOLE PIN or HISTORICAL below have no
|
|
56
|
+
-- runtime counterparty at all, so nesting leaves them unguarded as well.
|
|
57
|
+
--
|
|
58
|
+
-- IT IS DELIBERATELY NOT PATCHED HERE. The expression is inherited byte-for-byte
|
|
59
|
+
-- from merged #195 and the two copies must stay identical -- fixing one forks them,
|
|
60
|
+
-- which is exactly what the verbatim-reuse rule exists to prevent. #199 fixes it
|
|
61
|
+
-- once, across both files. Until then: treat this strip as closing the flat
|
|
62
|
+
-- comment and literal masks it is mutation-pinned against, NOT as closing the class.
|
|
63
|
+
--
|
|
64
|
+
-- WHY prosrc AND NOT pg_get_functiondef. functiondef wraps the whole body in
|
|
65
|
+
-- `AS $function$ ... $function$`, which is exactly what the dollar-quote alternative
|
|
66
|
+
-- matches: stripping functiondef output deletes the entire body and every assertion
|
|
67
|
+
-- fails on a clean schema. prosrc is the body alone.
|
|
68
|
+
--
|
|
69
|
+
-- LITERALS ARE STRIPPED HERE, DELIBERATELY. Every marker in this file is an
|
|
70
|
+
-- identifier or a statement shape, so a string literal is never the evidence. This
|
|
71
|
+
-- is the OPPOSITE of agent_board_fedsession_provider_absence_gate.test.sql G-8,
|
|
72
|
+
-- where the marker IS a raise message and literals are deliberately KEPT. Do not
|
|
73
|
+
-- "fix" one of these two files to match the other; the difference is the point.
|
|
74
|
+
--
|
|
75
|
+
-- WHAT THESE ASSERTIONS CAN SEE, AND WHAT THEY CANNOT. They remain POSITIONAL
|
|
76
|
+
-- claims over STATIC SQL, and nothing more:
|
|
77
|
+
-- * they cannot see DYNAMIC SQL. An acquisition built into an `execute` string is
|
|
78
|
+
-- a literal, so the strip removes it and the marker is simply not found -- the
|
|
79
|
+
-- `strpos(...) > 0` guard then fails, which is fail-CLOSED, but the ordering
|
|
80
|
+
-- inside such a string is unreadable either way. That fail-closed property is a
|
|
81
|
+
-- PROPERTY OF THE GUARDS, not of `strpos`: a bare `strpos(A) < strpos(B)` with
|
|
82
|
+
-- no guard returns `0 < N` = TRUE when A is absent, so it PASSES on a body it
|
|
83
|
+
-- no longer matches. The legacy revoke_agent claim shipped that way and was
|
|
84
|
+
-- green under `update only agent_board.agents` with the ordering inverted
|
|
85
|
+
-- (#198). Only the LEFT-hand marker of a `<` comparison is fail-open when
|
|
86
|
+
-- absent -- an absent right-hand marker yields 0 and makes `N < 0` false,
|
|
87
|
+
-- which already fails. Every positional claim below now guards its left-hand
|
|
88
|
+
-- marker with `> 0`; a new one that does not is fail-OPEN, not merely
|
|
89
|
+
-- imprecise;
|
|
90
|
+
-- * they cannot see locks taken by TRIGGERS or inside callees; only text in THIS
|
|
91
|
+
-- function body;
|
|
92
|
+
-- * they cannot see RUNTIME order. These are first-occurrence claims over
|
|
93
|
+
-- statements as written, not a per-branch path analysis. Where a marker matched
|
|
94
|
+
-- an unrelated branch, the assertion was silently inert -- see the
|
|
95
|
+
-- revoke_fleet_subject claim below, which is now anchored to the agent branch's
|
|
96
|
+
-- own loop header because the bare `from agent_board.fleet_machines fm` matched
|
|
97
|
+
-- the MACHINE branch first and passed while the agent branch was inverted.
|
|
98
|
+
-- Where a claim has a runtime counterparty, the counterparty is NAMED in the
|
|
99
|
+
-- assertion description. Where it has none, that is stated too. An honest narrow
|
|
100
|
+
-- claim beats a broad false one.
|
|
101
|
+
--
|
|
102
|
+
-- WHAT IS *STILL* RAW IN THIS FILE. Only the NINE positional claims were converted.
|
|
103
|
+
-- Everything else here still reads raw text and is knowingly left alone, so this
|
|
104
|
+
-- file must not be read as "the masking class is closed in
|
|
105
|
+
-- agent_board_atomic_fleet_onboarding.test.sql". The remainder, enumerated so the
|
|
106
|
+
-- next reader does not have to trust a summary:
|
|
107
|
+
-- * the 23 `pg_get_functiondef(...) like '%...%'` PRESENCE assertions (26 calls;
|
|
108
|
+
-- two assertions read more than one function). A comment mentioning the
|
|
109
|
+
-- pattern satisfies them exactly as it did the positional ones; one was
|
|
110
|
+
-- demonstrated silenced that way (#198). They are presence claims, so a
|
|
111
|
+
-- comment can only make a MISSING construct look present -- the same
|
|
112
|
+
-- fail-open direction, on weaker claims;
|
|
113
|
+
-- * the two catalog CLASS assertions at the end. The serialization one decides
|
|
114
|
+
-- serialized/UNSERIALIZED with `strpos(p.prosrc, '_lock_board_authority_ceiling')`
|
|
115
|
+
-- over RAW prosrc, so a reader that DROPS the lock while keeping a comment
|
|
116
|
+
-- naming it still prints "serialized" -- the dangerous direction. The
|
|
117
|
+
-- surface-set predicates that pick the second class's member functions are raw
|
|
118
|
+
-- for the same reason.
|
|
119
|
+
-- All of it is inside exact-set or presence assertions that #196 Bucket A
|
|
120
|
+
-- deliberately excluded; it belongs to the follow-up that closes Buckets B and C,
|
|
121
|
+
-- together with the nesting hole in the strip described above.
|
|
122
|
+
create extension if not exists pgtap with schema extensions;
|
|
123
|
+
set search_path = extensions, public, agent_board;
|
|
124
|
+
|
|
125
|
+
begin;
|
|
126
|
+
select plan(52);
|
|
127
|
+
|
|
128
|
+
-- ---------------------------------------------------------------------------
|
|
129
|
+
-- The stripped-body reader every positional assertion below uses. A shared SQL
|
|
130
|
+
-- include is not workable in this harness -- the runner pipes
|
|
131
|
+
-- `begin; \i <file>; rollback;` into psql, so there is no reliable relative
|
|
132
|
+
-- include path -- so it is defined ONCE here instead of being copy-pasted into
|
|
133
|
+
-- nine call sites, which would guarantee drift. The regex is byte-identical to
|
|
134
|
+
-- agent_board_federated_lock_order_class.test.sql; see the header.
|
|
135
|
+
-- ---------------------------------------------------------------------------
|
|
136
|
+
create function pg_temp._stripped_body(p_fn regprocedure) returns text
|
|
137
|
+
language sql stable as $strip$
|
|
138
|
+
select regexp_replace(p.prosrc,
|
|
139
|
+
$re$(\$[A-Za-z_0-9]*\$).*?\1|('([^']|'')*')|(--[^\n]*)|(/\*([^*]|\*+[^*/])*\*+/)$re$,
|
|
140
|
+
' ', 'g')
|
|
141
|
+
from pg_proc p
|
|
142
|
+
where p.oid = p_fn;
|
|
143
|
+
$strip$;
|
|
144
|
+
|
|
145
|
+
select has_table('agent_board', 'fleet_onboarding_invite_bounds', 'immutable fleet invite bounds table exists');
|
|
146
|
+
select has_pk('agent_board', 'fleet_onboarding_invite_bounds', 'fleet invite bounds are keyed by invite id');
|
|
147
|
+
select has_function(
|
|
148
|
+
'agent_board', 'create_fleet_onboarding_invite',
|
|
149
|
+
array['uuid','uuid','text','text','text','text','text','text','text','integer','timestamp with time zone','uuid','jsonb'],
|
|
150
|
+
'bounded fleet invite mint exists'
|
|
151
|
+
);
|
|
152
|
+
select has_function(
|
|
153
|
+
'agent_board', 'onboard_federated_fleet_agent',
|
|
154
|
+
array['text','text','text','jsonb','text','text','text','text','boolean','text','text','text[]','text[]','text','uuid','uuid','text','text','text','text','text','text','text'],
|
|
155
|
+
'atomic fleet onboarding RPC exists'
|
|
156
|
+
);
|
|
157
|
+
select has_function('agent_board', '_redeem_fleet_invite_for_agent', array['text','uuid'], 'fleet-only redemption helper exists');
|
|
158
|
+
select has_function('agent_board', '_redeem_invite_for_agent', array['text','uuid'], 'generic guarded redemption helper remains');
|
|
159
|
+
select has_function(
|
|
160
|
+
'agent_board', 'revoke_fleet_machine_agent_binding',
|
|
161
|
+
array['uuid','uuid','text','jsonb'],
|
|
162
|
+
'exact binding revocation writer exists'
|
|
163
|
+
);
|
|
164
|
+
select function_privs_are(
|
|
165
|
+
'agent_board', 'onboard_federated_fleet_agent',
|
|
166
|
+
array['text','text','text','jsonb','text','text','text','text','boolean','text','text','text[]','text[]','text','uuid','uuid','text','text','text','text','text','text','text'],
|
|
167
|
+
'service_role', array['EXECUTE'],
|
|
168
|
+
'only service_role can execute atomic onboarding'
|
|
169
|
+
);
|
|
170
|
+
select function_privs_are(
|
|
171
|
+
'agent_board', '_redeem_fleet_invite_for_agent', array['text','uuid'],
|
|
172
|
+
'service_role', array['EXECUTE'],
|
|
173
|
+
'fleet redemption helper is service-role-only'
|
|
174
|
+
);
|
|
175
|
+
select ok(
|
|
176
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
177
|
+
like '%fleet_onboarding.v1%',
|
|
178
|
+
'atomic onboarding returns the frozen contract version'
|
|
179
|
+
);
|
|
180
|
+
select ok(
|
|
181
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
182
|
+
like '%for update%',
|
|
183
|
+
'atomic onboarding catalog-pins row locking'
|
|
184
|
+
);
|
|
185
|
+
select ok(
|
|
186
|
+
pg_get_functiondef('agent_board.enroll_federated_identity(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text)'::regprocedure)
|
|
187
|
+
like '%pg_advisory_xact_lock%',
|
|
188
|
+
'exact-key create-or-reuse is serialized by fingerprint'
|
|
189
|
+
);
|
|
190
|
+
select ok(
|
|
191
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
192
|
+
like '%pg_advisory_xact_lock(hashtextextended(p_agent_public_key_fingerprint, 0))%',
|
|
193
|
+
'composed onboarding serializes identity disposition by fingerprint'
|
|
194
|
+
);
|
|
195
|
+
select ok(
|
|
196
|
+
pg_get_functiondef('agent_board.enroll_federated_identity(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text)'::regprocedure)
|
|
197
|
+
like '%fleet invite requires atomic fleet onboarding%',
|
|
198
|
+
'legacy enrollment rejects bounded invites before writes'
|
|
199
|
+
);
|
|
200
|
+
select ok(
|
|
201
|
+
pg_get_functiondef('agent_board._redeem_invite_for_agent(text,uuid)'::regprocedure)
|
|
202
|
+
like '%reserved for atomic fleet onboarding%',
|
|
203
|
+
'generic ROOMLINK redemption rejects bounded invites'
|
|
204
|
+
);
|
|
205
|
+
select ok(
|
|
206
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
207
|
+
like '%phase_worker%worker%reviewer%reviewer%',
|
|
208
|
+
'only bounded worker and reviewer role pairs are admitted'
|
|
209
|
+
);
|
|
210
|
+
select ok(
|
|
211
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
212
|
+
like '%signing_key_id is distinct from v_enroll.signing_key_id%',
|
|
213
|
+
'binding replay pins the exact proven signing key'
|
|
214
|
+
);
|
|
215
|
+
select ok(
|
|
216
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
217
|
+
like '%v_inv.board_id is distinct from p_board_id%',
|
|
218
|
+
'requested board is checked against the locked invite before writes'
|
|
219
|
+
);
|
|
220
|
+
select ok(
|
|
221
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
222
|
+
like '%not v_completed_replay%v_inv.expires_at <= now()%v_inv.uses_count >= v_inv.max_uses%',
|
|
223
|
+
'only a complete active replay may bypass expiry and exhaustion'
|
|
224
|
+
);
|
|
225
|
+
select ok(
|
|
226
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
227
|
+
like '%v_inv.revoked_at is not null%a.status = ''active''%_grant_within_ceiling%',
|
|
228
|
+
'revocation and current creator authority gate every fleet replay'
|
|
229
|
+
);
|
|
230
|
+
select ok(
|
|
231
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
232
|
+
like '%a.id = any(array[v_inv.created_by_agent_id, v_existing_agent_id])%order by a.id for update%bm.agent_id = v_inv.created_by_agent_id%order by bm.id for update%_grant_within_ceiling%',
|
|
233
|
+
'creator and enrolled principals lock in UUID order before creator authority'
|
|
234
|
+
);
|
|
235
|
+
select ok(
|
|
236
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
237
|
+
like '%board_invite_redemptions red%order by red.id for update%bm.agent_id = v_enroll.agent_id%order by bm.id for update%select exists%',
|
|
238
|
+
'complete-replay proof locks redemption and membership first'
|
|
239
|
+
);
|
|
240
|
+
select ok(
|
|
241
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
242
|
+
like '%perform 1 from agent_board.fleet_harness_registrations%perform 1 from agent_board.fleet_repo_scopes%perform 1 from agent_board.fleet_machine_agents%',
|
|
243
|
+
'every matching harness, repo, and binding history row is locked before reuse'
|
|
244
|
+
);
|
|
245
|
+
select ok(
|
|
246
|
+
pg_get_functiondef('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure)
|
|
247
|
+
like '%lifecycle_state = ''revoked''%',
|
|
248
|
+
'binding revocation is a permanent tombstone'
|
|
249
|
+
);
|
|
250
|
+
select ok(
|
|
251
|
+
strpos(
|
|
252
|
+
pg_temp._stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure),
|
|
253
|
+
'from agent_board.fleet_machines m'
|
|
254
|
+
) > 0
|
|
255
|
+
and strpos(
|
|
256
|
+
pg_temp._stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure),
|
|
257
|
+
'from agent_board.fleet_machine_agents b where b.id = p_binding_id for update'
|
|
258
|
+
) > 0
|
|
259
|
+
and strpos(
|
|
260
|
+
pg_temp._stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure),
|
|
261
|
+
'from agent_board.fleet_machines m'
|
|
262
|
+
) < strpos(
|
|
263
|
+
pg_temp._stripped_body('agent_board.revoke_fleet_machine_agent_binding(uuid,uuid,text,jsonb)'::regprocedure),
|
|
264
|
+
'from agent_board.fleet_machine_agents b where b.id = p_binding_id for update'
|
|
265
|
+
),
|
|
266
|
+
'exact binding revocation locks the machine before the binding [SOLE PIN: no isolation spec discriminates this ordering -- measured against all 20]'
|
|
267
|
+
);
|
|
268
|
+
select ok(
|
|
269
|
+
strpos(
|
|
270
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
271
|
+
'where fm.id = p_machine_id for update'
|
|
272
|
+
) > 0
|
|
273
|
+
and strpos(
|
|
274
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
275
|
+
'from agent_board.agents a where a.id = v_enroll.agent_id for update'
|
|
276
|
+
) > 0
|
|
277
|
+
and strpos(
|
|
278
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
279
|
+
'where fm.id = p_machine_id for update'
|
|
280
|
+
) < strpos(
|
|
281
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
282
|
+
'from agent_board.agents a where a.id = v_enroll.agent_id for update'
|
|
283
|
+
),
|
|
284
|
+
'atomic onboarding locks the machine before its enrolled agent [CONFIRMATORY: atomic_fleet_binding_revoke.spec and atomic_fleet_lifecycle_races.spec both discriminate the inversion]'
|
|
285
|
+
);
|
|
286
|
+
-- ANCHORED TO THE AGENT BRANCH, not to a bare table name (#196). The previous
|
|
287
|
+
-- marker 'from agent_board.fleet_machines fm' has THREE matches in this body and
|
|
288
|
+
-- the FIRST is in the p_subject_kind='machine' branch -- an occurrence this claim
|
|
289
|
+
-- is not about. Moving the agent branch's machine-lock loop BELOW the agent lock
|
|
290
|
+
-- (the exact inversion this assertion names) left the file fully green: it was
|
|
291
|
+
-- inert for its own claim, independently of any comment mask.
|
|
292
|
+
--
|
|
293
|
+
-- The anchor is the agent branch's loop header `order by fma.machine_id`, which
|
|
294
|
+
-- occurs exactly once. The lock it drives is then located RELATIVE to that header
|
|
295
|
+
-- (substr from the header onward), because `where fm.id = v_lock_machine_id`
|
|
296
|
+
-- occurs twice and a bare strpos would find the machine branch's copy again --
|
|
297
|
+
-- which would also let an EMPTIED loop body pass. Positions are computed off
|
|
298
|
+
-- single-line markers rather than a multi-line block so a whitespace-only reindent
|
|
299
|
+
-- does not fail the claim.
|
|
300
|
+
select ok(
|
|
301
|
+
(with b as (
|
|
302
|
+
select pg_temp._stripped_body(
|
|
303
|
+
'agent_board.revoke_fleet_subject(text,uuid,uuid,text,jsonb)'::regprocedure) as t),
|
|
304
|
+
p as (
|
|
305
|
+
select t,
|
|
306
|
+
strpos(t, 'order by fma.machine_id') as loop_pos,
|
|
307
|
+
strpos(t, 'from agent_board.agents a where a.id = p_subject_id for update') as agent_pos
|
|
308
|
+
from b)
|
|
309
|
+
select loop_pos > 0
|
|
310
|
+
and agent_pos > 0
|
|
311
|
+
and loop_pos < agent_pos
|
|
312
|
+
-- the loop actually takes the fleet_machines row lock, after its own header
|
|
313
|
+
and strpos(substr(t, loop_pos), 'where fm.id = v_lock_machine_id') > 0
|
|
314
|
+
and strpos(substr(t, loop_pos), 'for update') > 0
|
|
315
|
+
-- ...and that lock is still ahead of the agent lock
|
|
316
|
+
and loop_pos + strpos(substr(t, loop_pos), 'where fm.id = v_lock_machine_id') - 1 < agent_pos
|
|
317
|
+
from p),
|
|
318
|
+
'agent fleet revocation takes its machine locks before the agent lock [SOLE PIN: no isolation spec discriminates this ordering -- measured against all 20]'
|
|
319
|
+
);
|
|
320
|
+
select ok(
|
|
321
|
+
strpos(
|
|
322
|
+
pg_temp._stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
323
|
+
'from agent_board.agents a where a.id = p_agent_id for update'
|
|
324
|
+
) > 0
|
|
325
|
+
and strpos(
|
|
326
|
+
pg_temp._stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
327
|
+
'from agent_board.agent_signing_keys'
|
|
328
|
+
) > 0
|
|
329
|
+
and strpos(
|
|
330
|
+
pg_temp._stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
331
|
+
'from agent_board.agents a where a.id = p_agent_id for update'
|
|
332
|
+
) < strpos(
|
|
333
|
+
pg_temp._stripped_body('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure),
|
|
334
|
+
'from agent_board.agent_signing_keys'
|
|
335
|
+
),
|
|
336
|
+
'subtree primitive locks the agent before signing keys [SOLE PIN: no isolation spec discriminates this ordering -- measured against all 20]'
|
|
337
|
+
);
|
|
338
|
+
select ok(
|
|
339
|
+
pg_get_functiondef('agent_board._revoke_agent_principal(uuid,uuid,text)'::regprocedure)
|
|
340
|
+
like '%from agent_board.agent_signing_keys%order by id%for update%',
|
|
341
|
+
'subtree primitive locks signing keys in UUID order'
|
|
342
|
+
);
|
|
343
|
+
select ok(
|
|
344
|
+
pg_get_functiondef('agent_board.revoke_agent_subtree(uuid,uuid,text)'::regprocedure)
|
|
345
|
+
like '%select agent_id from subtree order by agent_id%',
|
|
346
|
+
'agent subtree enumerates principals in UUID order'
|
|
347
|
+
);
|
|
348
|
+
select ok(
|
|
349
|
+
pg_get_functiondef('agent_board.revoke_human_subtree(uuid,uuid,text)'::regprocedure)
|
|
350
|
+
like '%where owner_user_id = p_owner_user_id%order by id%',
|
|
351
|
+
'human subtree enumerates principals in UUID order'
|
|
352
|
+
);
|
|
353
|
+
-- PRESENCE GUARDS ARE NOT DECORATION (#198 review). This claim shipped as a bare
|
|
354
|
+
-- `strpos(A) < strpos(B)`, and a marker that is not found returns 0, so `0 < N` is
|
|
355
|
+
-- TRUE: rewriting the first statement as `update only agent_board.agents` -- a
|
|
356
|
+
-- semantics-preserving refactor, no comment and no literal involved -- made the
|
|
357
|
+
-- assertion pass on a body it no longer matched. With the ordering ALSO inverted
|
|
358
|
+
-- the file still ran 52/52 fully green. Both `> 0` guards below are what make the
|
|
359
|
+
-- not-found case fail instead of pass; the other eight positional claims in this
|
|
360
|
+
-- file already carried them.
|
|
361
|
+
select ok(
|
|
362
|
+
strpos(
|
|
363
|
+
pg_temp._stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure),
|
|
364
|
+
'update agent_board.agents'
|
|
365
|
+
) > 0
|
|
366
|
+
and strpos(
|
|
367
|
+
pg_temp._stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure),
|
|
368
|
+
'update agent_board.agent_credentials'
|
|
369
|
+
) > 0
|
|
370
|
+
and strpos(
|
|
371
|
+
pg_temp._stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure),
|
|
372
|
+
'update agent_board.agents'
|
|
373
|
+
) < strpos(
|
|
374
|
+
pg_temp._stripped_body('agent_board.revoke_agent(uuid,text)'::regprocedure),
|
|
375
|
+
'update agent_board.agent_credentials'
|
|
376
|
+
),
|
|
377
|
+
'legacy authenticated revoke_agent preserves agent-before-credential ordering [HISTORICAL: legacy platform-admin path, no live fleet counterparty and no isolation spec]'
|
|
378
|
+
);
|
|
379
|
+
select is(
|
|
380
|
+
(select count(*)::int from information_schema.role_table_grants
|
|
381
|
+
where table_schema='agent_board' and table_name='fleet_onboarding_invite_bounds'
|
|
382
|
+
and grantee in ('anon','authenticated') and privilege_type in ('INSERT','UPDATE','DELETE')),
|
|
383
|
+
0,
|
|
384
|
+
'no public or authenticated direct writer can mutate invite bounds'
|
|
385
|
+
);
|
|
386
|
+
select has_table('agent_board', 'fleet_human_revocations',
|
|
387
|
+
'durable total-human revocation registry exists');
|
|
388
|
+
select has_pk('agent_board', 'fleet_human_revocations',
|
|
389
|
+
'durable total-human revocation registry is keyed by stable federation principal');
|
|
390
|
+
select has_trigger('agent_board', 'agents', 'agents_protect_federation_contract',
|
|
391
|
+
'agents protect their reserved federation contract at every write path');
|
|
392
|
+
select ok(
|
|
393
|
+
pg_get_functiondef('agent_board.create_fleet_onboarding_invite(uuid,uuid,text,text,text,text,text,text,text,integer,timestamp with time zone,uuid,jsonb)'::regprocedure)
|
|
394
|
+
like '%from agent_board.boards b%where b.id = p_board_id%for update%v_board.status <> ''active''%',
|
|
395
|
+
'bounded invite mint locks and requires an active board'
|
|
396
|
+
);
|
|
397
|
+
select ok(
|
|
398
|
+
pg_get_functiondef('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure)
|
|
399
|
+
like '%from agent_board.boards b%where b.id = v_inv.board_id%for update%v_board.status <> ''active''%',
|
|
400
|
+
'atomic onboarding locks and requires the invite board to remain active'
|
|
401
|
+
);
|
|
402
|
+
select ok(
|
|
403
|
+
pg_get_functiondef('agent_board.enroll_federated_identity(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text)'::regprocedure)
|
|
404
|
+
like '%from auth.users u where u.id = v_owner_user_id for update%fleet_human_revocations%r.issuer = p_issuer and r.subject = p_subject%',
|
|
405
|
+
'identity enrollment locks the human owner and rejects durable total revocation'
|
|
406
|
+
);
|
|
407
|
+
select ok(
|
|
408
|
+
pg_get_functiondef('agent_board.revoke_human_subtree(uuid,uuid,text)'::regprocedure)
|
|
409
|
+
like '%from auth.users u where u.id = p_owner_user_id for update%where a.owner_user_id = p_owner_user_id%jsonb_typeof(a.metadata -> ''federation'') = ''object''%not exists (%from agent_board.enrollment_federation_audit e%where e.agent_id = a.id%stable federation evidence not found for every linked agent%insert into agent_board.fleet_human_revocations%issuer, subject, owner_user_id%on conflict (issuer, subject) do nothing%where owner_user_id = p_owner_user_id%order by id%'
|
|
410
|
+
and pg_get_functiondef('agent_board.revoke_human_subtree(uuid,uuid,text)'::regprocedure)
|
|
411
|
+
not like '%metadata #>> ''{federation,replay,issuer}''%',
|
|
412
|
+
'total human revocation requires per-agent audit evidence before marking and enumeration'
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
-- ---------------------------------------------------------------------------
|
|
416
|
+
-- Consiliency/message-board#186 — ceiling-ABSENCE serialization, and the two
|
|
417
|
+
-- privilege/class holes the assertions above do not close.
|
|
418
|
+
-- ---------------------------------------------------------------------------
|
|
419
|
+
|
|
420
|
+
-- function_privs_are(..., 'service_role', array['EXECUTE'], ...) constrains only
|
|
421
|
+
-- what service_role HAS. Granting authenticated EXECUTE passes it unchanged. The
|
|
422
|
+
-- deny side has to be asserted directly.
|
|
423
|
+
select ok(
|
|
424
|
+
not has_function_privilege(
|
|
425
|
+
'authenticated',
|
|
426
|
+
'agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure,
|
|
427
|
+
'EXECUTE'
|
|
428
|
+
),
|
|
429
|
+
'authenticated CANNOT execute atomic onboarding'
|
|
430
|
+
);
|
|
431
|
+
select ok(
|
|
432
|
+
not has_function_privilege(
|
|
433
|
+
'anon',
|
|
434
|
+
'agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure,
|
|
435
|
+
'EXECUTE'
|
|
436
|
+
),
|
|
437
|
+
'anon CANNOT execute atomic onboarding'
|
|
438
|
+
);
|
|
439
|
+
select ok(
|
|
440
|
+
not has_function_privilege(
|
|
441
|
+
'authenticated', 'agent_board._redeem_fleet_invite_for_agent(text,uuid)'::regprocedure, 'EXECUTE'
|
|
442
|
+
),
|
|
443
|
+
'authenticated CANNOT execute the fleet redemption helper'
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
-- The lock exists and derives its key in ONE place: two hand-written keys that
|
|
447
|
+
-- must be byte-identical to interlock are a silent-failure class.
|
|
448
|
+
select has_function(
|
|
449
|
+
'agent_board', '_lock_board_authority_ceiling', array['uuid'],
|
|
450
|
+
'board-keyed ceiling advisory lock helper exists'
|
|
451
|
+
);
|
|
452
|
+
select ok(
|
|
453
|
+
pg_get_functiondef('agent_board._lock_board_authority_ceiling(uuid)'::regprocedure)
|
|
454
|
+
like '%pg_advisory_xact_lock(%hashtextextended(''agent_board.board_authority_ceilings:'' || p_board_id::text, 0)%',
|
|
455
|
+
'the ceiling lock key is derived once, from the board id, in a transaction advisory lock'
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
-- Ordering, not mere presence: the lock has to be held BEFORE the authorization
|
|
459
|
+
-- decision reads the (possibly absent) ceiling, and BEFORE the writer upserts it.
|
|
460
|
+
select ok(
|
|
461
|
+
strpos(
|
|
462
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
463
|
+
'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
464
|
+
) > 0
|
|
465
|
+
and strpos(
|
|
466
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
467
|
+
'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
468
|
+
) < strpos(
|
|
469
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
470
|
+
'agent_board._grant_within_ceiling('
|
|
471
|
+
),
|
|
472
|
+
'atomic onboarding holds the board ceiling lock before evaluating creator authority [REDUNDANT: implied by the advisory-before-boards and boards-before-ceiling-read claims below; an advisory moved between those two points violates the real ordering and still passes THIS one]'
|
|
473
|
+
);
|
|
474
|
+
-- ABBA. set_board_authority_ceiling's order is advisory -> boards, because its
|
|
475
|
+
-- UPSERT reaches the boards row through board_authority_ceilings' FK (FOR KEY SHARE
|
|
476
|
+
-- on INSERT). Onboarding must agree: advisory BEFORE its boards row lock. The first
|
|
477
|
+
-- cut of 20260901000110 had them inverted and deadlocked deterministically -- a
|
|
478
|
+
-- deadlock the migration INTRODUCED, absent at base. This is the ordering assertion
|
|
479
|
+
-- that catches the inversion; it is deliberately separate from the "before the
|
|
480
|
+
-- ceiling read" assertion below, because that one passes in BOTH orders.
|
|
481
|
+
select ok(
|
|
482
|
+
strpos(
|
|
483
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
484
|
+
'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
485
|
+
) > 0
|
|
486
|
+
and strpos(
|
|
487
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
488
|
+
'perform agent_board._lock_board_authority_ceiling(v_inv.board_id)'
|
|
489
|
+
) < strpos(
|
|
490
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
491
|
+
'from agent_board.boards b
|
|
492
|
+
where b.id = v_inv.board_id
|
|
493
|
+
for update'
|
|
494
|
+
),
|
|
495
|
+
'atomic onboarding takes the ceiling advisory lock BEFORE the boards row lock (no ABBA against the ceiling setter) [BACKSTOP: tests/isolation/specs/onboarding_ceiling_lock_order.spec]'
|
|
496
|
+
);
|
|
497
|
+
select ok(
|
|
498
|
+
not has_function_privilege(
|
|
499
|
+
'authenticated', 'agent_board._lock_board_authority_ceiling(uuid)'::regprocedure, 'EXECUTE'
|
|
500
|
+
),
|
|
501
|
+
'authenticated CANNOT execute the unauthorized ceiling lock helper (direct grant is an onboarding DoS)'
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
-- The mechanism that is actually load-bearing today (see 20260901000110's header):
|
|
505
|
+
-- onboarding holds the BOARDS row FOR UPDATE, and board_authority_ceilings' FK makes
|
|
506
|
+
-- an INSERT of a first ceiling row take FOR KEY SHARE on that same row, which
|
|
507
|
+
-- conflicts. That only holds while the boards lock precedes the ceiling read -- an
|
|
508
|
+
-- ordering nothing else states, because the boards lock is taken for board liveness,
|
|
509
|
+
-- not for the ceiling. Pin the ordering so a refactor that moves it is not silent.
|
|
510
|
+
select ok(
|
|
511
|
+
strpos(
|
|
512
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
513
|
+
'from agent_board.boards b
|
|
514
|
+
where b.id = v_inv.board_id
|
|
515
|
+
for update'
|
|
516
|
+
) > 0
|
|
517
|
+
and strpos(
|
|
518
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
519
|
+
'from agent_board.boards b
|
|
520
|
+
where b.id = v_inv.board_id
|
|
521
|
+
for update'
|
|
522
|
+
) < strpos(
|
|
523
|
+
pg_temp._stripped_body('agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text)'::regprocedure),
|
|
524
|
+
'agent_board._grant_within_ceiling('
|
|
525
|
+
),
|
|
526
|
+
'atomic onboarding holds the boards row FOR UPDATE before it reads the ceiling [CONFIRMATORY FOR FLAT MASKS ONLY: agent_board_federated_lock_order_class.test.sql EC-3 fails by name on the comment/literal mutants, but it carries the SAME strip, so a NESTED block comment takes this assertion and EC-3 green together -- see #199 in the header; atomic_fleet_authority_races.spec pins the OUTCOME only when this lock and the advisory are BOTH gone, and atomic_fleet_board_archive.spec BLOCKS rather than failing by name]'
|
|
527
|
+
);
|
|
528
|
+
select ok(
|
|
529
|
+
strpos(
|
|
530
|
+
pg_temp._stripped_body('agent_board.set_board_authority_ceiling(uuid,text)'::regprocedure),
|
|
531
|
+
'perform agent_board._lock_board_authority_ceiling(p_board_id)'
|
|
532
|
+
) > 0
|
|
533
|
+
and strpos(
|
|
534
|
+
pg_temp._stripped_body('agent_board.set_board_authority_ceiling(uuid,text)'::regprocedure),
|
|
535
|
+
'perform agent_board._lock_board_authority_ceiling(p_board_id)'
|
|
536
|
+
) < strpos(
|
|
537
|
+
pg_temp._stripped_body('agent_board.set_board_authority_ceiling(uuid,text)'::regprocedure),
|
|
538
|
+
'insert into agent_board.board_authority_ceilings'
|
|
539
|
+
),
|
|
540
|
+
'the ceiling setter holds the same lock before its owner gate and upsert [BACKSTOP: tests/isolation/specs/ceiling_setter_lock_before_upsert.spec]'
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
-- CLASS (catalog-derived, EC-4 form). Every agent_board function that reads the
|
|
544
|
+
-- ceiling for authorization, enumerated from pg_proc — never from a list written
|
|
545
|
+
-- here — each carrying its serialization state POSITIVELY. A new reader changes
|
|
546
|
+
-- the array and NAMES ITSELF in the diff; a reader that is serialized without
|
|
547
|
+
-- being declared here also fails. The three UNSERIALIZED entries are the
|
|
548
|
+
-- knowingly-unfixed remainder of the class recorded in 20260901000110's header:
|
|
549
|
+
-- they carry the same absence race and are out of #186's scope.
|
|
550
|
+
select is( -- pgtap-allow-raise-checked: catalog read only; the RPC names below are string literals in the expected array, never invoked
|
|
551
|
+
(select array_agg(entry order by entry) from (
|
|
552
|
+
select 'agent_board.' || regexp_replace(p.oid::regprocedure::text, '^([^(]*\.)?', '') || ' => ' ||
|
|
553
|
+
case when strpos(p.prosrc, '_lock_board_authority_ceiling') > 0
|
|
554
|
+
then 'serialized' else 'UNSERIALIZED' end as entry
|
|
555
|
+
from pg_proc p
|
|
556
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
557
|
+
where n.nspname = 'agent_board'
|
|
558
|
+
and strpos(p.prosrc, '_grant_within_ceiling') > 0
|
|
559
|
+
) readers),
|
|
560
|
+
array[
|
|
561
|
+
'agent_board._redeem_unbounded_invite_for_agent(text,uuid) => UNSERIALIZED',
|
|
562
|
+
'agent_board.create_invite_link(uuid,text,text,integer,timestamp with time zone,uuid,jsonb) => UNSERIALIZED',
|
|
563
|
+
'agent_board.grant_board_role(uuid,uuid,text,uuid) => UNSERIALIZED',
|
|
564
|
+
'agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text) => serialized'
|
|
565
|
+
]::text[],
|
|
566
|
+
'every ceiling-authorization reader in pg_proc is declared with its serialization state'
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
-- CLASS (catalog-derived). Every function that touches this migration set's own
|
|
570
|
+
-- surface — the bounded-invite table, the human revocation registry, the fleet
|
|
571
|
+
-- revocation ledger, or the ceiling lock — with its exact non-owner EXECUTE
|
|
572
|
+
-- grantees.
|
|
573
|
+
--
|
|
574
|
+
-- The first cut filtered on `p.prosecdef`, which excluded _lock_board_authority_ceiling
|
|
575
|
+
-- BY CONSTRUCTION: it is `language sql` and not a definer, so it is the one function
|
|
576
|
+
-- this migration CREATES and the class could not see it. Granting that helper to
|
|
577
|
+
-- PUBLIC passed the whole file. SECURITY DEFINER is therefore recorded as a VALUE
|
|
578
|
+
-- per entry, never used as the filter — a definer/invoker flip now fails too.
|
|
579
|
+
--
|
|
580
|
+
-- A new function on this surface fails and names itself; so does any grant drift,
|
|
581
|
+
-- including a grant to PUBLIC (which a fresh `create function` confers by default
|
|
582
|
+
-- and which prints as PUBLIC here) or to authenticated.
|
|
583
|
+
select is( -- pgtap-allow-raise-checked: catalog read only; the RPC names below are string literals in the expected array, never invoked
|
|
584
|
+
(select array_agg(entry order by entry) from (
|
|
585
|
+
select 'agent_board.' || regexp_replace(p.oid::regprocedure::text, '^([^(]*\.)?', '')
|
|
586
|
+
|| case when p.prosecdef then ' [definer] => ' else ' [invoker] => ' end
|
|
587
|
+
|| coalesce((
|
|
588
|
+
select string_agg(distinct coalesce(pg_get_userbyid(nullif(a.grantee, 0)), 'PUBLIC'), ','
|
|
589
|
+
order by coalesce(pg_get_userbyid(nullif(a.grantee, 0)), 'PUBLIC'))
|
|
590
|
+
from aclexplode(p.proacl) a
|
|
591
|
+
where a.privilege_type = 'EXECUTE'
|
|
592
|
+
and a.grantee <> p.proowner
|
|
593
|
+
), '(none)') as entry
|
|
594
|
+
from pg_proc p
|
|
595
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
596
|
+
where n.nspname = 'agent_board'
|
|
597
|
+
and (strpos(p.prosrc, 'fleet_onboarding_invite_bounds') > 0
|
|
598
|
+
or strpos(p.prosrc, 'fleet_human_revocations') > 0
|
|
599
|
+
or strpos(p.prosrc, 'fleet_revocation_events') > 0
|
|
600
|
+
or strpos(p.prosrc, '_lock_board_authority_ceiling') > 0
|
|
601
|
+
or strpos(p.prosrc, 'board_authority_ceilings:') > 0)
|
|
602
|
+
) definers),
|
|
603
|
+
array[
|
|
604
|
+
'agent_board._lock_board_authority_ceiling(uuid) [invoker] => service_role',
|
|
605
|
+
'agent_board._redeem_fleet_invite_for_agent(text,uuid) [definer] => service_role',
|
|
606
|
+
'agent_board._redeem_invite_for_agent(text,uuid) [definer] => service_role',
|
|
607
|
+
'agent_board._revoke_agent_principal(uuid,uuid,text) [definer] => service_role',
|
|
608
|
+
'agent_board.create_fleet_onboarding_invite(uuid,uuid,text,text,text,text,text,text,text,integer,timestamp with time zone,uuid,jsonb) [definer] => authenticated,service_role',
|
|
609
|
+
'agent_board.enroll_federated_human(text,text,text,jsonb,text) [definer] => service_role',
|
|
610
|
+
'agent_board.enroll_federated_identity(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text) [definer] => service_role',
|
|
611
|
+
'agent_board.evaluate_fleet_sender_policy(uuid,uuid,uuid,uuid,text,uuid,boolean) [definer] => authenticated,service_role',
|
|
612
|
+
'agent_board.onboard_federated_fleet_agent(text,text,text,jsonb,text,text,text,text,boolean,text,text,text[],text[],text,uuid,uuid,text,text,text,text,text,text,text) [definer] => service_role',
|
|
613
|
+
'agent_board.reactivate_fleet_machine(uuid,uuid,text) [definer] => service_role',
|
|
614
|
+
'agent_board.reclaim_fleet_machine(uuid,uuid,uuid,text,uuid) [definer] => service_role',
|
|
615
|
+
'agent_board.revoke_fleet_subject(text,uuid,uuid,text,jsonb) [definer] => authenticated,service_role',
|
|
616
|
+
'agent_board.revoke_human_subtree(uuid,uuid,text) [definer] => service_role',
|
|
617
|
+
'agent_board.set_board_authority_ceiling(uuid,text) [definer] => authenticated,service_role'
|
|
618
|
+
]::text[],
|
|
619
|
+
'every function on this migration set''s surface is declared with its definer flag and exact EXECUTE grantees'
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
select * from finish();
|
|
623
|
+
rollback;
|