@consiliency/agent-board-schema 1.50.0 → 1.52.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 +4 -0
- package/migrations/20261002000100_agent_board_session_challenge_supersede.sql +383 -0
- package/package.json +1 -1
- package/schema-manifest.json +12 -3
- package/src/tooling.test.mjs +10 -2
- package/tests/agent_board_agent_liveness.test.sql +3 -1
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +33 -36
- package/tests/agent_board_authbound_boundary.test.sql +4 -3
- package/tests/agent_board_board_claim_policy.test.sql +17 -6
- package/tests/agent_board_claim_subject_gate.test.sql +7 -8
- package/tests/agent_board_claimkey.test.sql +9 -6
- package/tests/agent_board_claimpolicy_race.test.sql +1466 -342
- package/tests/agent_board_consent_emission.test.sql +5 -4
- package/tests/agent_board_consent_gating.test.sql +20 -9
- package/tests/agent_board_enroll_resumption.test.sql +1 -1
- package/tests/agent_board_enrollment_audit_content.test.sql +5 -4
- package/tests/agent_board_federated_identity_injective_slug.test.sql +3 -1
- package/tests/agent_board_federated_lock_order_class.test.sql +69 -52
- package/tests/agent_board_fedsession.test.sql +3 -1
- package/tests/agent_board_fedsession_provider_absence_gate.test.sql +9 -4
- package/tests/agent_board_fedsession_stored_identity_gate.test.sql +18 -6
- package/tests/agent_board_fk_lock_edges.test.sql +84 -5
- package/tests/agent_board_fleet_domain_reclaim.test.sql +2 -4
- package/tests/agent_board_fleet_machine_hijack.test.sql +2 -4
- package/tests/agent_board_fleet_machine_reactivation.test.sql +5 -1
- package/tests/agent_board_fleet_machine_reclaim.test.sql +2 -4
- package/tests/agent_board_invite_no_downgrade.test.sql +24 -18
- package/tests/agent_board_ledger_authority.test.sql +29 -21
- package/tests/agent_board_mint_harden.test.sql +5 -5
- package/tests/agent_board_outbox_wake_ordering.test.sql +4 -3
- package/tests/agent_board_ownerless_renew_quota.test.sql +7 -2
- package/tests/agent_board_portal_offline_invariant.test.sql +5 -4
- package/tests/agent_board_receipt_subject_gate.test.sql +7 -8
- package/tests/agent_board_rolereset.test.sql +5 -3
- package/tests/agent_board_rowid.test.sql +34 -15
- package/tests/agent_board_signer_lifecycle.test.sql +24 -13
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +391 -36
- package/tests/agent_board_source_strip_helper.test.sql +2 -1
- package/tests/agent_board_task_event_origin_posture.test.sql +21 -9
- package/tests/agent_board_wake_recovery.test.sql +53 -26
- package/tests/agent_board_write_enforcement_class.test.sql +130 -12
- package/tests/catalog/function_execute_privileges.json +1923 -0
- package/tests/fixtures/test-prereqs.sql +644 -237
- package/tests/isolation/expected/challenge_supersede_lock_order.out +213 -0
- package/tests/isolation/specs/challenge_supersede_lock_order.spec +183 -0
|
@@ -232,7 +232,7 @@ select is(
|
|
|
232
232
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
233
233
|
where n.nspname = 'agent_board' and p.prokind = 'f'
|
|
234
234
|
and p.prolang <> (select oid from pg_language where lanname = 'internal')
|
|
235
|
-
and agent_board_test.stripped_body(p.oid)
|
|
235
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into|update', 'board_memberships')
|
|
236
236
|
and agent_board_test.stripped_body(p.oid) !~* 'perform agent_board\._emit_membership_event'
|
|
237
237
|
and p.proname <> 'grant_board_role'),
|
|
238
238
|
'',
|
|
@@ -263,7 +263,8 @@ select is(
|
|
|
263
263
|
|
|
264
264
|
-- ...and the one exemption is genuinely state-blind: membership_state never appears in a SET.
|
|
265
265
|
select ok(
|
|
266
|
-
(select agent_board_test.stripped_body(p.oid)
|
|
266
|
+
(select agent_board_test.stripped_body(p.oid, true)
|
|
267
|
+
!~ agent_board_test.re(agent_board_test.col_assign_frag('board_memberships', 'membership_state', agent_board_test.bare_ok(p.oid)))
|
|
267
268
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
268
269
|
where n.nspname = 'agent_board' and p.proname = 'grant_board_role'),
|
|
269
270
|
'COMPLETENESS: the exemption holds -- grant_board_role never SETs membership_state');
|
|
@@ -274,7 +275,7 @@ select is(
|
|
|
274
275
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
275
276
|
where n.nspname = 'agent_board' and p.prokind = 'f'
|
|
276
277
|
and p.proname <> '_emit_membership_event'
|
|
277
|
-
and
|
|
278
|
+
and agent_board_test.stripped_body(p.oid, true) ~ agent_board_test.stmt_re('insert into', 'board_membership_events')),
|
|
278
279
|
0,
|
|
279
280
|
'COMPLETENESS: only _emit_membership_event writes the ledger -- the derivation cannot drift');
|
|
280
281
|
|
|
@@ -578,7 +579,7 @@ select is(
|
|
|
578
579
|
where n.nspname = 'agent_board' and p.prokind = 'f'
|
|
579
580
|
and p.prolang <> (select oid from pg_language where lanname = 'internal')
|
|
580
581
|
and has_function_privilege('authenticated', p.oid, 'EXECUTE')
|
|
581
|
-
and
|
|
582
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.call_re('_membership_consent_present')),
|
|
582
583
|
'resolve_board_request, update_board_membership',
|
|
583
584
|
'scope: exactly TWO authenticated routes read the predicate');
|
|
584
585
|
|
|
@@ -309,7 +309,10 @@ select is(
|
|
|
309
309
|
-- filter that is the fail-OPEN direction: the offender simply leaves the
|
|
310
310
|
-- class and the expected-empty assertion stays green. (Same mechanism
|
|
311
311
|
-- agent_board_wake_recovery.test.sql records for its own WR-7 filter.)
|
|
312
|
-
|
|
312
|
+
~ agent_board_test.re(agent_board_test.call_frag('_upsert_board_membership_state')
|
|
313
|
+
|| '|' || agent_board_test.call_frag('_redeem_invite_for_agent')
|
|
314
|
+
|| '|' || agent_board_test.call_frag('update_board_membership')
|
|
315
|
+
|| '|' || agent_board_test.stmt_frag('insert into', 'board_memberships'))
|
|
313
316
|
-- gated: consults the predicate, or refuses a non-subject actor structurally.
|
|
314
317
|
--
|
|
315
318
|
-- SPLIT IN TWO, by what kind of thing each marker IS. The single `!~` over a
|
|
@@ -326,16 +329,20 @@ select is(
|
|
|
326
329
|
-- gate genuinely removed. It does not any more.
|
|
327
330
|
-- anchored to the CALL shape, for the same reason the message half below is
|
|
328
331
|
-- anchored to the raise shape: a bare substring is satisfied by a NAME.
|
|
329
|
-
-- SCHEMA-QUALIFIED, because a bare-name anchor proves call
|
|
330
|
-
-- IDENTITY: `\m_membership_consent_present\s*\(` also
|
|
331
|
-
-- `zz_other._membership_consent_present(...)` (measured true). Every live
|
|
332
|
-
-- site in agent_board is `agent_board.`-qualified, so requiring the
|
|
332
|
+
-- SCHEMA-QUALIFIED (`bare_ok => false`), because a bare-name anchor proves call
|
|
333
|
+
-- SYNTAX, not callee IDENTITY: the old `\m_membership_consent_present\s*\(` also
|
|
334
|
+
-- matched `zz_other._membership_consent_present(...)` (measured true). Every live
|
|
335
|
+
-- call site in agent_board is `agent_board.`-qualified, so requiring the
|
|
333
336
|
-- qualification costs nothing today and is fail-CLOSED if someone later writes
|
|
334
337
|
-- an unqualified call (it reads as NOT calling). Overload count is pinned
|
|
335
338
|
-- separately, so schema + name is the identity.
|
|
336
|
-
and agent_board_test.stripped_body(p.oid)
|
|
339
|
+
and agent_board_test.stripped_body(p.oid) !~ agent_board_test.call_re('_membership_consent_present', false)
|
|
340
|
+
-- The raise shape through the matcher family: `raise exception` case-folded with any
|
|
341
|
+
-- whitespace (the hand-rolled form was case-SENSITIVE, so `RAISE EXCEPTION` passed this
|
|
342
|
+
-- absence silently), then the message inside a literal of the same statement.
|
|
337
343
|
and agent_board_test.stripped_body(p.oid, true)
|
|
338
|
-
!~ 'raise
|
|
344
|
+
!~ agent_board_test.re('\m' || agent_board_test.ci_re('raise exception') || '\M' || agent_board_test.same_stmt()
|
|
345
|
+
|| '''(?:[^'']|'''')*?(?:actor mismatch|requires an act by that agent)')
|
|
339
346
|
-- stated exemptions, each with a reason recorded in the migration header
|
|
340
347
|
and p.proname not in (
|
|
341
348
|
'create_board', -- the actor IS the subject: it seeds the creator's own seat
|
|
@@ -362,7 +369,9 @@ select is(
|
|
|
362
369
|
-- LITERALS KEPT: the marker is the ARGUMENT ''joined''/''subscribed'' passed
|
|
363
370
|
-- to the sink -- a string literal in the code being matched.
|
|
364
371
|
agent_board_test.stripped_body(p.oid, true),
|
|
365
|
-
'(?:
|
|
372
|
+
agent_board_test.re('(?:' || agent_board_test.call_frag('_upsert_board_membership_state')
|
|
373
|
+
|| '|' || agent_board_test.call_frag('update_board_membership') || ')'
|
|
374
|
+
|| '[^;]*?''(joined|subscribed)'''),
|
|
366
375
|
'g') m
|
|
367
376
|
where n.nspname = 'agent_board'
|
|
368
377
|
and p.proname in ('invite_board_member','request_join_board','leave_board')),
|
|
@@ -377,7 +386,9 @@ select is(
|
|
|
377
386
|
cross join lateral regexp_matches(
|
|
378
387
|
-- LITERALS KEPT: same shape as above -- the state being written is a literal.
|
|
379
388
|
agent_board_test.stripped_body(p.oid, true),
|
|
380
|
-
'(?:
|
|
389
|
+
agent_board_test.re('(?:' || agent_board_test.call_frag('_upsert_board_membership_state')
|
|
390
|
+
|| '|' || agent_board_test.call_frag('update_board_membership') || ')'
|
|
391
|
+
|| '[^;]*?''(invited|requested|left)'''),
|
|
381
392
|
'g') m
|
|
382
393
|
where n.nspname = 'agent_board'
|
|
383
394
|
and p.proname in ('invite_board_member','request_join_board','leave_board')),
|
|
@@ -200,7 +200,7 @@ select is(
|
|
|
200
200
|
-- while a third signing-key creator was live (message-board#293). This site was
|
|
201
201
|
-- invisible to the #196 lint because that lint only inspects POSITIONAL and
|
|
202
202
|
-- NEGATIVE assertions, and this is a PRESENCE match -- see the note on the lint.
|
|
203
|
-
and agent_board_test.stripped_body(p.oid)
|
|
203
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.stmt_re('insert into', 'agent_signing_keys')),
|
|
204
204
|
'_enroll_federated_identity_once, mint_delegated_child',
|
|
205
205
|
'EC-2: only the enroll creator and delegated-child mint insert signing keys (strip_sql-derived)');
|
|
206
206
|
|
|
@@ -69,7 +69,8 @@ select has_column('agent_board', 'enrollment_federation_audit', 'enrolled_at', '
|
|
|
69
69
|
-- writes it and total human revocation reads stable principal evidence from it. ----
|
|
70
70
|
select is(
|
|
71
71
|
(select count(*)::int from (
|
|
72
|
-
|
|
72
|
+
-- literals KEPT: a dynamic-SQL reference is still a reference, and this is an absence claim
|
|
73
|
+
select agent_board_test.stripped_body(p.oid, true) as def, p.proname
|
|
73
74
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
74
75
|
where n.nspname = 'agent_board'
|
|
75
76
|
-- pg_get_functiondef() RAISES on aggregate ('a') / window ('w') procs
|
|
@@ -85,11 +86,11 @@ select is(
|
|
|
85
86
|
'revoke_human_subtree'
|
|
86
87
|
)
|
|
87
88
|
) d
|
|
88
|
-
where
|
|
89
|
+
where d.def ~ agent_board_test.qname_re('enrollment_federation_audit')),
|
|
89
90
|
0, 'no agent_board RPC other than federation enrollment and total human revocation references enrollment_federation_audit');
|
|
90
91
|
select is(
|
|
91
92
|
(select count(*)::int from (
|
|
92
|
-
select
|
|
93
|
+
select agent_board_test.stripped_body(p.oid, true) as def
|
|
93
94
|
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
94
95
|
where n.nspname = 'agent_board'
|
|
95
96
|
and p.prokind in ('f', 'p') -- pg_get_functiondef raises on aggregate/window procs
|
|
@@ -97,7 +98,7 @@ select is(
|
|
|
97
98
|
'mint_fleet_agent_session', 'post_board_message', 'send_direct_message',
|
|
98
99
|
'send_multicast_message', 'create_tasking_delivery', 'revoke_agent')
|
|
99
100
|
) d
|
|
100
|
-
where
|
|
101
|
+
where d.def ~ agent_board_test.qname_re('enrollment_federation_audit')),
|
|
101
102
|
0, 'no send/session/revoke write-path RPC reads the enrollment audit table');
|
|
102
103
|
|
|
103
104
|
-- #6 fix: the authenticated SELECT policy is scoped to the owning principal
|
|
@@ -332,7 +332,9 @@ select is(
|
|
|
332
332
|
(select coalesce(string_agg(p.oid::regprocedure::text, ', ' order by p.oid::regprocedure::text), '(none)')
|
|
333
333
|
from pg_proc p
|
|
334
334
|
where p.pronamespace = 'agent_board'::regnamespace
|
|
335
|
-
|
|
335
|
+
-- col_frag is the bare-identifier form (guards on both sides): a parameter, here
|
|
336
|
+
and agent_board_test.stripped_body(p.oid, true)
|
|
337
|
+
~ agent_board_test.re(agent_board_test.col_frag('p_issuer') || '\s*\|\|\s*''\|''\s*\|\|\s*' || agent_board_test.col_frag('p_subject'))),
|
|
336
338
|
'(none)',
|
|
337
339
|
'CLS-2: no agent_board function still concatenates p_issuer || ''|'' || p_subject (offenders are named)');
|
|
338
340
|
|
|
@@ -29,13 +29,22 @@
|
|
|
29
29
|
-- them, and it re-synced only because that count is even -- working by luck).
|
|
30
30
|
-- Both shapes are now mutation-pinned by name;
|
|
31
31
|
-- * table and function names matched BOTH qualified and BARE, with each
|
|
32
|
-
-- function's own search_path (pg_proc.proconfig
|
|
33
|
-
-- resolve into agent_board -- a route written
|
|
34
|
-
-- is visible, and so is
|
|
32
|
+
-- function's own search_path (pg_proc.proconfig, via agent_board_test.bare_ok)
|
|
33
|
+
-- deciding whether bare names resolve into agent_board -- a route written
|
|
34
|
+
-- `insert into board_memberships` is visible, and so is
|
|
35
|
+
-- `perform _redeem_invite_for_agent(...)`. The patterns are the shared matcher
|
|
36
|
+
-- family in fixtures/test-prereqs.sql (stmt_frag / col_assign_frag / qname_frag
|
|
37
|
+
-- / row_lock_frag / call_re), not regexes written here, so a boundary defect
|
|
38
|
+
-- is fixed once and CPR-0 pins the family's behaviour (#416/#417/#424);
|
|
35
39
|
-- * three acquisition shapes: INSERT into a direct FK child, an UPDATE that
|
|
36
|
-
-- re-points the FK column,
|
|
37
|
-
--
|
|
38
|
-
--
|
|
40
|
+
-- re-points the FK column (POSITIONALLY, in the SET list -- the hand regex this
|
|
41
|
+
-- replaced took `board_id` anywhere after SET and credited grant_board_role,
|
|
42
|
+
-- whose UPDATE only FILTERS by board_id, with a boards acquisition; measured
|
|
43
|
+
-- on migration: that route and signed_grant_board_role, which reaches it, are
|
|
44
|
+
-- the only two rows that changed, and neither is in EC-2's class), and an
|
|
45
|
+
-- explicit row lock of any strength in any statement shape (including
|
|
46
|
+
-- `from boards b join ... for update of b`, which an earlier `where`-anchored
|
|
47
|
+
-- pattern could not see);
|
|
39
48
|
-- * a function pinning search_path WITHOUT agent_board (e.g.
|
|
40
49
|
-- _ensure_agent_auth_user's search_path='') is not credited with bare matches;
|
|
41
50
|
-- no proconfig at all is treated as bare-resolving, over-approximating
|
|
@@ -100,16 +109,7 @@ fns as (
|
|
|
100
109
|
-- with no search_path setting at all is treated as if bare names DO resolve, which
|
|
101
110
|
-- over-approximates membership rather than under-approximating it.
|
|
102
111
|
sp as (
|
|
103
|
-
select f.oid,
|
|
104
|
-
case
|
|
105
|
-
when not exists (select 1 from unnest(coalesce(f.proconfig, '{}')) c
|
|
106
|
-
where c like 'search\_path=%') then true
|
|
107
|
-
else exists (select 1 from unnest(f.proconfig) c
|
|
108
|
-
where c like 'search\_path=%'
|
|
109
|
-
and ',' || replace(replace(split_part(c, '=', 2), ' ', ''), '"', '') || ','
|
|
110
|
-
like '%,agent\_board,%')
|
|
111
|
-
end as bare_ok
|
|
112
|
-
from fns f
|
|
112
|
+
select f.oid, agent_board_test.bare_ok(f.oid) as bare_ok from fns f
|
|
113
113
|
),
|
|
114
114
|
-- DIRECT FK children only: inserting into a grandchild locks the child row, not
|
|
115
115
|
-- the grandparent, so transitive FK depth would manufacture false positives.
|
|
@@ -125,41 +125,60 @@ kids as (
|
|
|
125
125
|
and c.confrelid in ('agent_board.boards'::regclass, 'auth.users'::regclass)
|
|
126
126
|
and ns.nspname = 'agent_board'
|
|
127
127
|
),
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
)
|
|
128
|
+
-- The three acquisition shapes, composed from the shared matcher family
|
|
129
|
+
-- (test-prereqs) and wrapped ONCE in re() per subject. Per function, because the
|
|
130
|
+
-- bare-name arm depends on that function's search_path.
|
|
131
|
+
-- (i) INSERT into a direct FK child takes FOR KEY SHARE on the parent row
|
|
132
|
+
-- -- stmt_frag('insert into', kid, bare_ok);
|
|
133
|
+
-- (ii) an UPDATE that RE-POINTS the FK column re-checks it and locks the new
|
|
134
|
+
-- parent -- col_assign_frag(kid, fkcol, bare_ok), which is POSITIONAL:
|
|
135
|
+
-- the column must sit in the SET list. The hand form this replaced
|
|
136
|
+
-- accepted `board_id` anywhere after SET, including the WHERE clause, so
|
|
137
|
+
-- an UPDATE that merely FILTERS by board_id was counted as an acquisition;
|
|
138
|
+
-- (iii) an explicit row lock in ANY statement shape, deliberately not anchored
|
|
139
|
+
-- on `where`: `from boards b join ... for update of b` is a real
|
|
140
|
+
-- acquisition and the anchored form could not see it -- qname_frag ||
|
|
141
|
+
-- same_stmt() || row_lock_frag(), every lock strength (the hand owner arm
|
|
142
|
+
-- accepted only `for update`; a `for no key update` on auth.users is the
|
|
143
|
+
-- same acquisition and now counts).
|
|
134
144
|
pat as (
|
|
135
|
-
select
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
select s.oid,
|
|
146
|
+
agent_board_test.re(
|
|
147
|
+
(select string_agg(agent_board_test.stmt_frag('insert into', k.tbl, s.bare_ok), '|')
|
|
148
|
+
from kids k where k.parent_oid = 'agent_board.boards'::regclass)
|
|
149
|
+
|| '|' || (select string_agg(agent_board_test.col_assign_frag(k.tbl, 'board_id', s.bare_ok), '|')
|
|
150
|
+
from kids k where k.parent_oid = 'agent_board.boards'::regclass)
|
|
151
|
+
|| '|' || agent_board_test.qname_frag('boards', s.bare_ok) || agent_board_test.same_stmt()
|
|
152
|
+
|| agent_board_test.row_lock_frag()
|
|
153
|
+
) as board_re,
|
|
154
|
+
agent_board_test.re(
|
|
155
|
+
(select string_agg(agent_board_test.stmt_frag('insert into', k.tbl, s.bare_ok), '|')
|
|
156
|
+
from kids k where k.parent_oid = 'auth.users'::regclass)
|
|
157
|
+
|| '|' || (select string_agg(agent_board_test.col_assign_frag(k.tbl, 'owner_user_id', s.bare_ok)
|
|
158
|
+
|| '|' || agent_board_test.col_assign_frag(k.tbl, 'auth_user_id', s.bare_ok), '|')
|
|
159
|
+
from kids k where k.parent_oid = 'auth.users'::regclass)
|
|
160
|
+
|| '|' || agent_board_test.qname_frag('auth.users', false) || agent_board_test.same_stmt()
|
|
161
|
+
|| agent_board_test.row_lock_frag()
|
|
162
|
+
) as owner_re
|
|
163
|
+
from sp s
|
|
138
164
|
),
|
|
165
|
+
-- The hit is the matched TEXT (an acquisition is present); its position is the
|
|
166
|
+
-- match's own offset (regexp_instr), not the first occurrence of that text.
|
|
139
167
|
own as (
|
|
140
|
-
select f.oid, f.proname, f.sig, f.body,
|
|
141
|
-
substring(f.body from
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-- (iii) an explicit row lock in ANY statement shape. Deliberately not
|
|
147
|
-
-- anchored on `where`: `from boards b join ... for update of b` is a real
|
|
148
|
-
-- acquisition and the anchored form could not see it.
|
|
149
|
-
|| '|(?:' || p.q || '\mboards\M[^;]{0,250}?for\s+(?:update|no\s+key\s+update|share|key\s+share))'
|
|
150
|
-
) as board_hit,
|
|
151
|
-
substring(f.body from
|
|
152
|
-
'(?:insert\s+into\s+(?:only\s+)?' || p.q || '\m(?:' || a.owner_kids || ')\M)'
|
|
153
|
-
|| '|(?:update\s+(?:only\s+)?' || p.q || '\m(?:' || a.owner_kids || ')\M[^;]{0,250}?\mset\M[^;]{0,250}?\m(?:owner_user_id|auth_user_id)\M\s*=)'
|
|
154
|
-
|| '|(?:auth\.users\M[^;]{0,250}?for\s+update)'
|
|
155
|
-
) as owner_hit
|
|
156
|
-
from fns f join pat p on p.oid = f.oid cross join alt a
|
|
168
|
+
select f.oid, f.proname, f.sig, f.body, s.bare_ok,
|
|
169
|
+
substring(f.body from p.board_re) as board_hit,
|
|
170
|
+
substring(f.body from p.owner_re) as owner_hit,
|
|
171
|
+
nullif(regexp_instr(f.body, p.board_re), 0) as board_at,
|
|
172
|
+
nullif(regexp_instr(f.body, p.owner_re), 0) as owner_at
|
|
173
|
+
from fns f join sp s on s.oid = f.oid join pat p on p.oid = f.oid
|
|
157
174
|
),
|
|
175
|
+
-- A call edge is the callee's name in CALL position, schema-resolved the way the
|
|
176
|
+
-- caller's search_path resolves it (call_re: a `other.callee(` is NOT an edge).
|
|
158
177
|
edges as (
|
|
159
178
|
select c.oid as caller, e.oid as callee
|
|
160
179
|
from own c
|
|
161
180
|
join fns e on e.oid <> c.oid
|
|
162
|
-
where c.body ~ (
|
|
181
|
+
where c.body ~ agent_board_test.call_re(e.proname, c.bare_ok)
|
|
163
182
|
),
|
|
164
183
|
acq_board(oid) as (
|
|
165
184
|
select oid from own where board_hit is not null
|
|
@@ -176,22 +195,20 @@ select o.sig,
|
|
|
176
195
|
(o.oid in (select oid from acq_owner)) as reaches_owner,
|
|
177
196
|
(o.board_hit is not null) as own_board_lock,
|
|
178
197
|
(o.owner_hit is not null) as own_owner_lock,
|
|
179
|
-
(o.body
|
|
198
|
+
(o.body ~ agent_board_test.re('\m' || agent_board_test.ci_re('execute') || '\M')) as uses_dynamic_sql,
|
|
180
199
|
least(
|
|
181
|
-
|
|
182
|
-
coalesce((select min(
|
|
200
|
+
coalesce(o.board_at, 2147483647),
|
|
201
|
+
coalesce((select min(nullif(regexp_instr(o.body, agent_board_test.call_re(f2.proname, o.bare_ok)), 0))
|
|
183
202
|
from fns f2
|
|
184
203
|
where f2.oid <> o.oid
|
|
185
|
-
and f2.oid in (select oid from acq_board)
|
|
186
|
-
and o.body ~ (o.q || '\m' || f2.proname || '\M\s*\(')), 2147483647)
|
|
204
|
+
and f2.oid in (select oid from acq_board)), 2147483647)
|
|
187
205
|
) as board_pos,
|
|
188
206
|
least(
|
|
189
|
-
|
|
190
|
-
coalesce((select min(
|
|
207
|
+
coalesce(o.owner_at, 2147483647),
|
|
208
|
+
coalesce((select min(nullif(regexp_instr(o.body, agent_board_test.call_re(f2.proname, o.bare_ok)), 0))
|
|
191
209
|
from fns f2
|
|
192
210
|
where f2.oid <> o.oid
|
|
193
|
-
and f2.oid in (select oid from acq_owner)
|
|
194
|
-
and o.body ~ (o.q || '\m' || f2.proname || '\M\s*\(')), 2147483647)
|
|
211
|
+
and f2.oid in (select oid from acq_owner)), 2147483647)
|
|
195
212
|
) as owner_pos
|
|
196
213
|
from own o;
|
|
197
214
|
|
|
@@ -352,7 +352,9 @@ begin
|
|
|
352
352
|
end $$;
|
|
353
353
|
-- 36
|
|
354
354
|
select ok(
|
|
355
|
-
|
|
355
|
+
-- literals kept: none of the pinned fragments is a string, but a raise message between two
|
|
356
|
+
-- of them is part of the ORDER this pins, and the strip must not splice around it
|
|
357
|
+
agent_board_test.stripped_body('agent_board.enroll_federated_human(text,text,text,jsonb,text)'::regprocedure, true)
|
|
356
358
|
like '%from agent_board.boards b%for update%from agent_board.identity_providers ip%for update%_ensure_agent_auth_user(v_handle)%from auth.users u where u.id = v_uid for update%fleet_human_revocations r%r.issuer = p_issuer%r.subject = p_subject%_enroll_federated_human_after_revocation_gate%',
|
|
357
359
|
'/portal-session locks default board then provider then owner and checks the durable marker before delegation');
|
|
358
360
|
-- 37
|
|
@@ -66,10 +66,15 @@ select is(
|
|
|
66
66
|
else 'gate-after-owner'
|
|
67
67
|
end
|
|
68
68
|
from (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
-- Positions are the match's own offset (regexp_instr: 0 when absent), not the
|
|
70
|
+
-- first occurrence of the matched text.
|
|
71
|
+
select regexp_instr(d,
|
|
72
|
+
agent_board_test.re('\m' || agent_board_test.ci_re('raise exception')
|
|
73
|
+
|| '\s+''issuer/audience not in the identity-provider allowlist \(rejected before any owner or board lock\)''')) as p_gate,
|
|
74
|
+
regexp_instr(d, agent_board_test.call_re('_ensure_agent_auth_user')) as p_ensure,
|
|
75
|
+
-- the auth.users read and any row lock (FOR UPDATE / NO KEY UPDATE / SHARE / KEY
|
|
76
|
+
-- SHARE) in the same statement; the old hand form knew only `for update`
|
|
77
|
+
nullif(regexp_instr(d, agent_board_test.re(agent_board_test.stmt_frag('from', 'auth.users', false) || agent_board_test.same_stmt() || agent_board_test.row_lock_frag())), 0) as p_lock
|
|
73
78
|
-- LITERALS KEPT (the `true`): the gate marker G-2 locates IS the raise
|
|
74
79
|
-- message, so stripping literals would delete it and the positional claim
|
|
75
80
|
-- would read 'gate-missing' forever. Comments -- including the NESTED
|
|
@@ -78,7 +78,11 @@ select p.oid, p.proname, agent_board_test.stripped_body(p.oid, true) as body
|
|
|
78
78
|
where n.nspname = 'agent_board'
|
|
79
79
|
and p.prokind in ('f', 'p')
|
|
80
80
|
and agent_board_test.stripped_body(p.oid, true) like '%\_federated\_identity\_slug(%'
|
|
81
|
-
|
|
81
|
+
-- a read of agents on auth_user_id, in one statement: the relation (qualified as the
|
|
82
|
+
-- function's search_path allows) then the column, qualified or bare, then `=`
|
|
83
|
+
and agent_board_test.stripped_body(p.oid, true)
|
|
84
|
+
~ agent_board_test.re(agent_board_test.qname_frag('agents', agent_board_test.bare_ok(p.oid)) || agent_board_test.same_stmt()
|
|
85
|
+
|| agent_board_test.col_ref_frag('auth_user_id') || '\s*=');
|
|
82
86
|
|
|
83
87
|
-- 1 -- the class is NOT EMPTY. A class scan that finds nothing passes every
|
|
84
88
|
-- "every member has X" assertion vacuously; this is the assertion that stops that.
|
|
@@ -113,9 +117,9 @@ select is((select string_agg(proname, ',' order by proname) from _c246),
|
|
|
113
117
|
-- `have:` line instead of killing the run.
|
|
114
118
|
create temp view _c246parts as
|
|
115
119
|
select proname,
|
|
116
|
-
(regexp_split_to_array(body, 'exception
|
|
117
|
-
(regexp_split_to_array(body, 'exception
|
|
118
|
-
array_length(regexp_split_to_array(body, 'exception
|
|
120
|
+
(regexp_split_to_array(body, agent_board_test.re('\m' || agent_board_test.ci_re('exception when unique_violation') || '\M')))[1] as pre_handler,
|
|
121
|
+
(regexp_split_to_array(body, agent_board_test.re('\m' || agent_board_test.ci_re('exception when unique_violation') || '\M')))[2] as in_handler,
|
|
122
|
+
array_length(regexp_split_to_array(body, agent_board_test.re('\m' || agent_board_test.ci_re('exception when unique_violation') || '\M')), 1) as parts
|
|
119
123
|
from _c246;
|
|
120
124
|
|
|
121
125
|
-- 3 -- the split boundary is UNAMBIGUOUS for every member. With a second `exception
|
|
@@ -129,7 +133,11 @@ select is(
|
|
|
129
133
|
-- 4 -- SITE (7a): the idempotent branch, everything before the handler.
|
|
130
134
|
select is(
|
|
131
135
|
(select string_agg(proname, ',' order by proname) from _c246parts
|
|
132
|
-
where pre_handler ~ 'coalesce
|
|
136
|
+
where pre_handler ~ agent_board_test.re(agent_board_test.call_frag('pg_catalog.coalesce', true) || '\s*'
|
|
137
|
+
|| agent_board_test.ident_re('v_existing') || '\s*\.\s*' || agent_board_test.ident_re('metadata') || '\s*#>>\s*''\{federation,issuer\}''\s*=\s*' || agent_board_test.ident_re('p_issuer')
|
|
138
|
+
|| '\s+' || agent_board_test.ci_re('and') || '\s+'
|
|
139
|
+
|| agent_board_test.ident_re('v_existing') || '\s*\.\s*' || agent_board_test.ident_re('metadata') || '\s*#>>\s*''\{federation,subject\}''\s*=\s*' || agent_board_test.ident_re('p_subject')
|
|
140
|
+
|| '\s*,\s*' || agent_board_test.ci_re('false') || '\s*\)')),
|
|
133
141
|
(select string_agg(proname, ',' order by proname) from _c246),
|
|
134
142
|
'every class member carries the gate at return site (7a) -- the idempotent branch, BEFORE the unique_violation handler');
|
|
135
143
|
|
|
@@ -138,7 +146,11 @@ select is(
|
|
|
138
146
|
-- so this assertion is the ONLY thing standing between it and being ungated.
|
|
139
147
|
select is(
|
|
140
148
|
(select string_agg(proname, ',' order by proname) from _c246parts
|
|
141
|
-
where in_handler ~ 'coalesce
|
|
149
|
+
where in_handler ~ agent_board_test.re(agent_board_test.call_frag('pg_catalog.coalesce', true) || '\s*'
|
|
150
|
+
|| agent_board_test.ident_re('v_existing') || '\s*\.\s*' || agent_board_test.ident_re('metadata') || '\s*#>>\s*''\{federation,issuer\}''\s*=\s*' || agent_board_test.ident_re('p_issuer')
|
|
151
|
+
|| '\s+' || agent_board_test.ci_re('and') || '\s+'
|
|
152
|
+
|| agent_board_test.ident_re('v_existing') || '\s*\.\s*' || agent_board_test.ident_re('metadata') || '\s*#>>\s*''\{federation,subject\}''\s*=\s*' || agent_board_test.ident_re('p_subject')
|
|
153
|
+
|| '\s*,\s*' || agent_board_test.ci_re('false') || '\s*\)')),
|
|
142
154
|
(select string_agg(proname, ',' order by proname) from _c246),
|
|
143
155
|
'every class member carries the gate at return site (8a) -- INSIDE the unique_violation race handler');
|
|
144
156
|
|
|
@@ -40,7 +40,7 @@ set search_path = extensions, public, agent_board;
|
|
|
40
40
|
|
|
41
41
|
begin;
|
|
42
42
|
|
|
43
|
-
select plan(
|
|
43
|
+
select plan(18);
|
|
44
44
|
|
|
45
45
|
-- ---------------------------------------------------------------------------
|
|
46
46
|
-- The derivation, materialized once. `first_path` is the position PATH of the
|
|
@@ -176,7 +176,7 @@ select is(
|
|
|
176
176
|
select is(
|
|
177
177
|
(select coalesce(string_agg(distinct e.fn_name, ', ' order by e.fn_name), '')
|
|
178
178
|
from _le e join pg_proc p on p.oid = e.fn_oid
|
|
179
|
-
where agent_board_test.stripped_body(p.oid)
|
|
179
|
+
where agent_board_test.stripped_body(p.oid) ~ agent_board_test.re('\m' || agent_board_test.ci_re('execute') || '\M')),
|
|
180
180
|
'',
|
|
181
181
|
'FKL-7: no function with a lock edge uses dynamic SQL, which the position analysis cannot read');
|
|
182
182
|
|
|
@@ -200,6 +200,16 @@ select is(
|
|
|
200
200
|
-- and disagree with each other about the order. #281 stops here deliberately:
|
|
201
201
|
-- the ordering fix is a separate decision. Do not "green" this by editing the
|
|
202
202
|
-- expectation -- a changed member means the lock graph changed.
|
|
203
|
+
--
|
|
204
|
+
-- ONE MEMBER HAS BEEN REMOVED since, and the lock graph did not change -- the detector
|
|
205
|
+
-- did (#416/#417/#424 PR 1). `unsubscribe_callback_endpoint` was listed on the strength
|
|
206
|
+
-- of three `fk-update` edges the old shape-(v) regex fabricated: its only UPDATE is
|
|
207
|
+
-- `update callback_subscriptions cs set subscription_status = ... where cs.endpoint_id =`,
|
|
208
|
+
-- and the old `set[^;]{0,250}?<col>\s*=` crossed from SET into WHERE and read the
|
|
209
|
+
-- predicate as an FK re-point. lock_edges() now bounds the assignment scan at
|
|
210
|
+
-- WHERE/FROM/RETURNING (col_assign_frag), so the function has no agents edge at all.
|
|
211
|
+
-- Read the function before believing this comment: the WHERE is on line 15 of its
|
|
212
|
+
-- stripped body.
|
|
203
213
|
-- ===========================================================================
|
|
204
214
|
select is(
|
|
205
215
|
(select array_agg(m order by m) from (
|
|
@@ -239,7 +249,6 @@ select is(
|
|
|
239
249
|
'signed_send_direct_message: agents(key share) then memberships(key share) [unserialised]',
|
|
240
250
|
'signed_send_multicast_message: agents(key share) then memberships(key share) [unserialised]',
|
|
241
251
|
'subscribe_callback_endpoint: memberships(key share) then agents(key share) [unserialised]',
|
|
242
|
-
'unsubscribe_callback_endpoint: memberships(key share) then agents(key share) [unserialised]',
|
|
243
252
|
'update_board_membership: memberships(update) then agents(key share) [unserialised]'
|
|
244
253
|
]::text[],
|
|
245
254
|
'FKL-9: the agents+board_memberships lock class, FK edges included (section 9''s text scan sees 3 of these)');
|
|
@@ -259,12 +268,45 @@ select is(
|
|
|
259
268
|
-- pinned as a class in agent_board_task_thread_lock_order.test.sql (TT-1), which
|
|
260
269
|
-- is what makes a REGRESSION fail there and name the function, rather than only
|
|
261
270
|
-- failing here as an opaque re-appearance of the pair.
|
|
271
|
+
--
|
|
272
|
+
-- ONE PAIR HAS BEEN ADDED, and it is added with its own positive pin rather than as a
|
|
273
|
+
-- widened exemption. F1's supersede-at-the-cap makes issue_signing_key_challenge write
|
|
274
|
+
-- an agent_signing_key_challenges row while holding the agent_signing_keys row, which
|
|
275
|
+
-- mint_fleet_agent_session takes in the opposite order -- so this detector named
|
|
276
|
+
-- `agent_signing_key_challenges <-> agent_signing_keys` the first time that migration
|
|
277
|
+
-- was written. The cycle is closed at RUNTIME, by `for update skip locked` on the
|
|
278
|
+
-- eviction's row selection: a transaction that never waits on a challenge row cannot
|
|
279
|
+
-- close a cycle. This detector is a static regex scan and cannot see a SKIP LOCKED, so
|
|
280
|
+
-- it reports the pair and always will.
|
|
281
|
+
--
|
|
282
|
+
-- The clause is therefore pinned TWICE, POSITIVELY, so that deleting it fails a NAMED
|
|
283
|
+
-- assertion rather than merely re-adding a member to a list that already contains it
|
|
284
|
+
-- (which would be silent):
|
|
285
|
+
-- * agent_board_signing_challenge_pending_cap.test.sql asserts the DEPLOYED body
|
|
286
|
+
-- carries it, over stripped_body() so a comment cannot satisfy the match;
|
|
287
|
+
-- * tests/isolation/specs/challenge_supersede_lock_order.spec proves the absence of
|
|
288
|
+
-- the wait across two real sessions -- removing the clause turns its golden from a
|
|
289
|
+
-- clean completion into `<waiting ...>` and then a deadlock.
|
|
290
|
+
--
|
|
291
|
+
-- ONE PAIR HAS BEEN REMOVED BY A DETECTOR FIX, not by an ordering change (#416/#417/#424
|
|
292
|
+
-- PR 1). `agents <-> delegation_grants` rested on `fk-update` edges fabricated from
|
|
293
|
+
-- `update delegation_grants set ... where child_agent_id = ...` in renew_delegated_grant
|
|
294
|
+
-- and _revoke_agent_principal: the old shape-(v) regex ran on from SET into WHERE and took
|
|
295
|
+
-- the predicate for an assignment. Neither function re-points the FK, so neither locks the
|
|
296
|
+
-- agents row through it, and the pair never existed. The same fix, plus an unbounded
|
|
297
|
+
-- statement span, MOVED but did not add a pair on the callback tables:
|
|
298
|
+
-- claim_callback_outbox_events' `for update of coe skip locked` now locks
|
|
299
|
+
-- callback_outbox_events (real; previously beyond the old 250-char window) and no longer
|
|
300
|
+
-- callback_subscriptions (fabricated; `OF coe` does not name it -- lock_edges() now reads
|
|
301
|
+
-- the OF list). `callback_endpoints <-> callback_outbox_events` is still reported after
|
|
302
|
+
-- the fix; this comment does not claim to know which routes carry it.
|
|
262
303
|
-- ===========================================================================
|
|
263
304
|
select is(
|
|
264
305
|
(select array_agg(p order by p) from (
|
|
265
306
|
select distinct least(rel_a, rel_b) || ' <-> ' || greatest(rel_a, rel_b) as p
|
|
266
307
|
from _abba where not both_advisory) z),
|
|
267
308
|
array[
|
|
309
|
+
'agent_board.agent_signing_key_challenges <-> agent_board.agent_signing_keys',
|
|
268
310
|
'agent_board.agent_signing_keys <-> agent_board.agents',
|
|
269
311
|
'agent_board.agent_signing_keys <-> agent_board.board_memberships',
|
|
270
312
|
'agent_board.agent_signing_keys <-> agent_board.fleet_machine_agents',
|
|
@@ -273,7 +315,6 @@ select is(
|
|
|
273
315
|
'agent_board.agents <-> agent_board.board_invite_tokens',
|
|
274
316
|
'agent_board.agents <-> agent_board.board_memberships',
|
|
275
317
|
'agent_board.agents <-> agent_board.boards',
|
|
276
|
-
'agent_board.agents <-> agent_board.delegation_grants',
|
|
277
318
|
'agent_board.agents <-> agent_board.fleet_machine_agents',
|
|
278
319
|
'agent_board.agents <-> agent_board.fleet_machines',
|
|
279
320
|
'agent_board.agents <-> agent_board.fleet_trust_domains',
|
|
@@ -314,7 +355,7 @@ select is(
|
|
|
314
355
|
'FKL-11: the #281 ABBA is present and unserialised -- reported, NOT fixed here');
|
|
315
356
|
|
|
316
357
|
-- ===========================================================================
|
|
317
|
-
-- THE PROBES. From here on, agent_board gains
|
|
358
|
+
-- THE PROBES. From here on, agent_board gains six functions that exist only to
|
|
318
359
|
-- exercise one acquisition shape each. They are never CALLED -- the detector
|
|
319
360
|
-- reads bodies -- and the transaction is rolled back. Every exact-set assertion
|
|
320
361
|
-- above has already run.
|
|
@@ -364,6 +405,30 @@ begin
|
|
|
364
405
|
end;
|
|
365
406
|
$probe$;
|
|
366
407
|
|
|
408
|
+
-- Shape (v)'s boundary (#416/#417/#424 PR 1): the FK column appears ONLY in the WHERE. The
|
|
409
|
+
-- detector that listed `unsubscribe_callback_endpoint` in FKL-9 read exactly this shape as an
|
|
410
|
+
-- FK re-point. The SET here names no FK column, so the probe must carry the write-update
|
|
411
|
+
-- edge and NOTHING through an FK.
|
|
412
|
+
create function agent_board._probe416_fk_where(p_agent uuid)
|
|
413
|
+
returns void language plpgsql as $probe$
|
|
414
|
+
begin
|
|
415
|
+
update agent_board.board_memberships set role = 'member'
|
|
416
|
+
where agent_id = p_agent and board_id = p_agent;
|
|
417
|
+
end;
|
|
418
|
+
$probe$;
|
|
419
|
+
|
|
420
|
+
-- Shape (i)'s OF list: `FOR UPDATE OF a` locks the aliased relation only. `boards` is named in
|
|
421
|
+
-- the same statement and must NOT be credited; the second statement covers the no-alias
|
|
422
|
+
-- spelling, where the OF list names the relation itself.
|
|
423
|
+
create function agent_board._probe416_lock_of(p_id uuid)
|
|
424
|
+
returns void language plpgsql as $probe$
|
|
425
|
+
begin
|
|
426
|
+
perform 1 from agent_board.agents a join agent_board.boards b on b.id = a.id
|
|
427
|
+
where a.id = p_id for update of a;
|
|
428
|
+
perform 1 from agent_board.identity_providers where id = p_id for update of identity_providers;
|
|
429
|
+
end;
|
|
430
|
+
$probe$;
|
|
431
|
+
|
|
367
432
|
-- The derivation, re-evaluated ONCE now that the probes exist. (Re-evaluating it
|
|
368
433
|
-- per assertion is correct but costs a full re-scan of every body each time.)
|
|
369
434
|
create temp table _lep on commit drop as select * from agent_board_test.lock_edges();
|
|
@@ -417,6 +482,20 @@ select is(
|
|
|
417
482
|
'agent_board.agents share via textual',
|
|
418
483
|
'FKL-16: an explicit row lock is reported with its MODE, not as a boolean');
|
|
419
484
|
|
|
485
|
+
select is(
|
|
486
|
+
(select string_agg(distinct e.rel_name || ' ' || e.lock_mode || ' via ' || e.via, ' | '
|
|
487
|
+
order by e.rel_name || ' ' || e.lock_mode || ' via ' || e.via)
|
|
488
|
+
from _lep e where e.fn_name = '_probe416_fk_where'),
|
|
489
|
+
'agent_board.board_memberships update via write-update',
|
|
490
|
+
'FKL-17: an FK column in the WHERE is a predicate, not a re-point -- no fk-update edge');
|
|
491
|
+
|
|
492
|
+
select is(
|
|
493
|
+
(select string_agg(distinct e.rel_name || ' ' || e.lock_mode || ' via ' || e.via, ' | '
|
|
494
|
+
order by e.rel_name || ' ' || e.lock_mode || ' via ' || e.via)
|
|
495
|
+
from _lep e where e.fn_name = '_probe416_lock_of'),
|
|
496
|
+
'agent_board.agents update via textual | agent_board.identity_providers update via textual',
|
|
497
|
+
'FKL-18: FOR UPDATE OF locks the listed relations only -- boards, named in the same statement, gets no edge');
|
|
498
|
+
|
|
420
499
|
select * from finish();
|
|
421
500
|
|
|
422
501
|
rollback;
|
|
@@ -404,10 +404,8 @@ select is(
|
|
|
404
404
|
join pg_namespace n on n.oid = p.pronamespace
|
|
405
405
|
where n.nspname = 'agent_board'
|
|
406
406
|
and p.prokind = 'f'
|
|
407
|
-
and (
|
|
408
|
-
|
|
409
|
-
or pg_get_functiondef(p.oid) ~ 'update agent_board\.fleet_trust_domains[^;]*owner_agent_id\s*='
|
|
410
|
-
)
|
|
407
|
+
and agent_board_test.stripped_body(p.oid, true)
|
|
408
|
+
~ agent_board_test.col_write_re('fleet_trust_domains', 'owner_agent_id')
|
|
411
409
|
) writers
|
|
412
410
|
),
|
|
413
411
|
'reclaim_fleet_trust_domain',
|
|
@@ -409,10 +409,8 @@ select is(
|
|
|
409
409
|
join pg_namespace n on n.oid = p.pronamespace
|
|
410
410
|
where n.nspname = 'agent_board'
|
|
411
411
|
and p.prokind = 'f'
|
|
412
|
-
and (
|
|
413
|
-
|
|
414
|
-
or agent_board_test.stripped_body(p.oid) ~ 'update agent_board\.fleet_machines[^;]*trust_domain_id\s*='
|
|
415
|
-
)
|
|
412
|
+
and agent_board_test.stripped_body(p.oid, true)
|
|
413
|
+
~ agent_board_test.col_write_re('fleet_machines', 'trust_domain_id')
|
|
416
414
|
) writers
|
|
417
415
|
),
|
|
418
416
|
'reclaim_fleet_machine, register_fleet_machine',
|
|
@@ -462,7 +462,11 @@ select is(
|
|
|
462
462
|
-- not a state reader — its exemption claim is that it advances the ledger's effective_at.
|
|
463
463
|
select ok(
|
|
464
464
|
(
|
|
465
|
-
|
|
465
|
+
-- the UPDATE ... SET effective_at = now() of fleet_revocation_events, as an assignment
|
|
466
|
+
-- to that relation's column rather than as the six characters `effective_at = now()`
|
|
467
|
+
select agent_board_test.stripped_body(p.oid)
|
|
468
|
+
~ agent_board_test.re(agent_board_test.col_assign_frag('fleet_revocation_events', 'effective_at', agent_board_test.bare_ok(p.oid))
|
|
469
|
+
|| '\s*' || agent_board_test.call_frag('pg_catalog.now', true) || '\s*\)')
|
|
466
470
|
from pg_proc p
|
|
467
471
|
join pg_namespace n on n.oid = p.pronamespace
|
|
468
472
|
where n.nspname = 'agent_board'
|
|
@@ -426,10 +426,8 @@ select is(
|
|
|
426
426
|
join pg_namespace n on n.oid = p.pronamespace
|
|
427
427
|
where n.nspname = 'agent_board'
|
|
428
428
|
and p.prokind = 'f'
|
|
429
|
-
and (
|
|
430
|
-
|
|
431
|
-
or pg_get_functiondef(p.oid) ~ 'update agent_board\.fleet_machines[^;]*owner_agent_id\s*='
|
|
432
|
-
)
|
|
429
|
+
and agent_board_test.stripped_body(p.oid, true)
|
|
430
|
+
~ agent_board_test.col_write_re('fleet_machines', 'owner_agent_id')
|
|
433
431
|
) writers
|
|
434
432
|
),
|
|
435
433
|
'reclaim_fleet_machine, register_fleet_machine',
|