@consiliency/agent-board-schema 1.38.0 → 1.41.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 +8 -0
- package/migrations/20260920000100_agent_board_realtime_credential_claim.sql +119 -0
- package/migrations/20260921000100_agent_board_task_event_origin_posture.sql +881 -0
- package/package.json +1 -1
- package/schema-manifest.json +21 -3
- package/src/tooling.test.mjs +25 -2
- package/tests/agent_board_auth_rls.test.sql +8 -3
- package/tests/agent_board_realtime_credential_claim.test.sql +301 -0
- package/tests/agent_board_signer_lifecycle.test.sql +1 -1
- package/tests/agent_board_task_event_origin_posture.test.sql +393 -0
- package/tests/agent_board_task_thread_lock_order.test.sql +10 -6
- package/tests/catalog/p_metadata_parameters.json +4 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
-- Consiliency/message-board#288 pgTAP: every task_events row declares HOW it was written.
|
|
2
|
+
--
|
|
3
|
+
-- THE DEFECT. v_task_tree.verification_status is NULL for every task_event written by
|
|
4
|
+
-- anything except the two signed wrappers, and NULL cannot be told apart from "evidence was
|
|
5
|
+
-- lost", "the writer forgot" and "unsigned on purpose". The fix stamps the writer's posture
|
|
6
|
+
-- into a DISTINCT column, so the blank becomes a fact.
|
|
7
|
+
--
|
|
8
|
+
-- WHY THE CLASS ASSERTION IS THE CENTREPIECE. The issue body enumerated 8 members and its own
|
|
9
|
+
-- triage comment enumerated 5-6; the catalog says 12, and neither list named
|
|
10
|
+
-- create_tasking_delivery or signed_create_tasking_delivery. Two hand-written lists, both
|
|
11
|
+
-- wrong, in one issue. OP-4/OP-5 therefore ask pg_proc for the class rather than checking a
|
|
12
|
+
-- list off, so a THIRTEENTH writer fails them AND NAMES ITSELF -- but only once OP-20 is read
|
|
13
|
+
-- alongside them. An earlier version of this sentence stopped at "names itself", and three
|
|
14
|
+
-- reviewers independently named the shape it missed: a writer calling the PUBLIC
|
|
15
|
+
-- append_task_event enters the class, compiles, is stamped 'unattested:direct-rpc', and is
|
|
16
|
+
-- named by neither OP-4 (which inspects only _append_task_event call sites) nor OP-5 (which
|
|
17
|
+
-- inspects only direct INSERTs). OP-20 is what closes it.
|
|
18
|
+
--
|
|
19
|
+
-- WHAT EACH LAYER DOES AND DOES NOT BUY -- calling the second one structural would be the
|
|
20
|
+
-- overclaim these tests exist to prevent:
|
|
21
|
+
-- * OP-1/OP-2 are STRUCTURAL. origin_posture is NOT NULL with no default, so a writer that
|
|
22
|
+
-- opens a new DIRECT INSERT path cannot insert a row at all. No test has to remember.
|
|
23
|
+
-- * OP-4 is NAMED, NOT STRUCTURAL. A writer that goes through the funnel and omits the
|
|
24
|
+
-- argument would inherit the funnel's default rather than erroring -- no SQL construct
|
|
25
|
+
-- prevents that, since a caller can always route through a defaulting wrapper. This
|
|
26
|
+
-- assertion is what turns that from silent into named.
|
|
27
|
+
--
|
|
28
|
+
-- LITERALS ARE KEPT ON PURPOSE. agent_board_test.stripped_body strips string literals BY
|
|
29
|
+
-- DEFAULT, and the whole predicate here is "is a literal posture passed at the call site" --
|
|
30
|
+
-- so OP-4 passes p_keep_literals => true. With the default, every call site would read as
|
|
31
|
+
-- `_append_task_event(` with nothing after it and the assertion would pass VACUOUSLY.
|
|
32
|
+
-- Comments are stripped in both modes, so prose naming a call site cannot satisfy it.
|
|
33
|
+
--
|
|
34
|
+
-- OP-6 EXISTS BECAUSE ABSENCE OF FAILURE IS NOT SUCCESS. A derivation that silently matched
|
|
35
|
+
-- nothing would make OP-4 and OP-5 pass with an empty input set. OP-6 pins the closure's
|
|
36
|
+
-- membership positively before the other two are believed.
|
|
37
|
+
create extension if not exists pgtap with schema extensions;
|
|
38
|
+
set search_path = extensions, public, agent_board;
|
|
39
|
+
|
|
40
|
+
begin;
|
|
41
|
+
|
|
42
|
+
select plan(22);
|
|
43
|
+
|
|
44
|
+
-- ===========================================================================
|
|
45
|
+
-- The class, derived once. Membership rule:
|
|
46
|
+
-- a function is a member iff its CALL CLOSURE reaches an INSERT INTO agent_board.task_events
|
|
47
|
+
-- ===========================================================================
|
|
48
|
+
create temp table _op_direct on commit drop as
|
|
49
|
+
select p.oid, p.proname
|
|
50
|
+
from pg_proc p
|
|
51
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
52
|
+
where n.nspname = 'agent_board'
|
|
53
|
+
and agent_board_test.stripped_body(p.oid) ~* 'insert\s+into\s+(agent_board\.)?task_events';
|
|
54
|
+
|
|
55
|
+
create temp table _op_class on commit drop as
|
|
56
|
+
with recursive walk as (
|
|
57
|
+
select oid, proname, 0 as depth from _op_direct
|
|
58
|
+
union
|
|
59
|
+
select p.oid, p.proname, w.depth + 1
|
|
60
|
+
from pg_proc p
|
|
61
|
+
join pg_namespace n on n.oid = p.pronamespace
|
|
62
|
+
join walk w
|
|
63
|
+
on agent_board_test.stripped_body(p.oid)
|
|
64
|
+
~* ('(^|[^a-z0-9_.])(agent_board\.)?' || w.proname || '\s*\(')
|
|
65
|
+
where n.nspname = 'agent_board' and p.oid <> w.oid and w.depth < 5
|
|
66
|
+
)
|
|
67
|
+
select oid, proname, min(depth) as depth from walk group by oid, proname;
|
|
68
|
+
|
|
69
|
+
-- ---------------------------------------------------------------------------
|
|
70
|
+
-- OP-1/OP-2/OP-3: the column itself.
|
|
71
|
+
-- ---------------------------------------------------------------------------
|
|
72
|
+
select is(
|
|
73
|
+
(select attnotnull from pg_attribute
|
|
74
|
+
where attrelid = 'agent_board.task_events'::regclass and attname = 'origin_posture'),
|
|
75
|
+
true,
|
|
76
|
+
'OP-1: task_events.origin_posture is NOT NULL -- a new direct-INSERT writer cannot insert at all');
|
|
77
|
+
|
|
78
|
+
-- A column DEFAULT would silently satisfy a new direct-INSERT writer with a plausible-looking
|
|
79
|
+
-- label, re-opening exactly the hole this migration closes. The absence is load-bearing.
|
|
80
|
+
select is(
|
|
81
|
+
(select pg_get_expr(d.adbin, d.adrelid)
|
|
82
|
+
from pg_attribute a left join pg_attrdef d on d.adrelid = a.attrelid and d.adnum = a.attnum
|
|
83
|
+
where a.attrelid = 'agent_board.task_events'::regclass and a.attname = 'origin_posture'),
|
|
84
|
+
null,
|
|
85
|
+
'OP-2: origin_posture has NO column default -- a default would relabel a forgetful writer instead of stopping it');
|
|
86
|
+
|
|
87
|
+
select is(
|
|
88
|
+
(select count(*)::int from (
|
|
89
|
+
select unnest(array['attested:evidence-row','unattested:signed-metadata-rpc','unattested:unsigned-deliberate-rpc','unattested:direct-rpc','unattested:preexisting']) v
|
|
90
|
+
) want
|
|
91
|
+
where not exists (
|
|
92
|
+
select 1 from pg_constraint c
|
|
93
|
+
where c.conrelid = 'agent_board.task_events'::regclass
|
|
94
|
+
and c.conname = 'task_events_origin_posture_check'
|
|
95
|
+
and pg_get_constraintdef(c.oid) like '%''' || want.v || '''%')),
|
|
96
|
+
0,
|
|
97
|
+
'OP-3: every vocabulary value is admitted by task_events_origin_posture_check');
|
|
98
|
+
|
|
99
|
+
-- ---------------------------------------------------------------------------
|
|
100
|
+
-- OP-4/OP-5: the class assertions. Catalog-derived, and they NAME the offender.
|
|
101
|
+
-- ---------------------------------------------------------------------------
|
|
102
|
+
-- Compliant iff EVERY _append_task_event call site passes a literal posture as its first
|
|
103
|
+
-- argument. Counting both shapes and comparing catches a body with three compliant calls and
|
|
104
|
+
-- one bare one -- which a bare `does it match at all` predicate would report as clean.
|
|
105
|
+
select is(
|
|
106
|
+
(select coalesce(string_agg(c.proname, ', ' order by c.proname), '')
|
|
107
|
+
from _op_class c
|
|
108
|
+
where (select count(*) from regexp_matches(
|
|
109
|
+
agent_board_test.stripped_body(c.oid, true), '_append_task_event\s*\(', 'g')) <> 0
|
|
110
|
+
and (select count(*) from regexp_matches(
|
|
111
|
+
agent_board_test.stripped_body(c.oid, true), '_append_task_event\s*\(', 'g'))
|
|
112
|
+
<> (select count(*) from regexp_matches(
|
|
113
|
+
agent_board_test.stripped_body(c.oid, true), '_append_task_event\s*\(\s*''', 'g'))),
|
|
114
|
+
'',
|
|
115
|
+
'OP-4 CLASS: every funnel caller passes an explicit literal posture -- a caller that does not is NAMED here');
|
|
116
|
+
|
|
117
|
+
-- The two INSERT sites must name the column. This is the half OP-4 cannot see: a writer that
|
|
118
|
+
-- inserts directly never calls the funnel at all, which is precisely how create_task escaped
|
|
119
|
+
-- every prior enumeration of this class.
|
|
120
|
+
select is(
|
|
121
|
+
(select coalesce(string_agg(d.proname, ', ' order by d.proname), '')
|
|
122
|
+
from _op_direct d
|
|
123
|
+
where agent_board_test.stripped_body(d.oid)
|
|
124
|
+
!~* 'insert\s+into\s+(agent_board\.)?task_events\s*\([^)]*origin_posture'),
|
|
125
|
+
'',
|
|
126
|
+
'OP-5 CLASS: every DIRECT task_events INSERT names origin_posture -- one that does not is NAMED here');
|
|
127
|
+
|
|
128
|
+
-- ---------------------------------------------------------------------------
|
|
129
|
+
-- OP-6: the anti-vacuity guard. OP-4 and OP-5 are only worth their green if the derivation
|
|
130
|
+
-- they read actually found the writers. Asserted POSITIVELY (the members are present), never
|
|
131
|
+
-- by "no offenders were found" -- an empty class also produces no offenders.
|
|
132
|
+
-- ---------------------------------------------------------------------------
|
|
133
|
+
select is(
|
|
134
|
+
(select coalesce(string_agg(c.proname, ', ' order by c.proname), '')
|
|
135
|
+
from _op_class c
|
|
136
|
+
where c.proname in ('_append_task_event', 'create_task', 'acquire_claim', 'raise_conflict',
|
|
137
|
+
'signed_append_task_event', 'signed_create_tasking_delivery')),
|
|
138
|
+
'_append_task_event, acquire_claim, create_task, raise_conflict, signed_append_task_event, signed_create_tasking_delivery',
|
|
139
|
+
'OP-6: the closure really did find the writers, including the two the issue never named');
|
|
140
|
+
|
|
141
|
+
-- ---------------------------------------------------------------------------
|
|
142
|
+
-- OP-7/OP-8: reachable surface. The funnel moved BEHIND a function no role can invoke; the
|
|
143
|
+
-- public entry point kept the ACL it had before, which is the property CREATE OR REPLACE
|
|
144
|
+
-- buys and DROP FUNCTION would have destroyed.
|
|
145
|
+
-- ---------------------------------------------------------------------------
|
|
146
|
+
select is(
|
|
147
|
+
(select coalesce(string_agg(r, ', ' order by r), '')
|
|
148
|
+
from unnest(array['anon','authenticated','service_role']) r
|
|
149
|
+
where has_function_privilege(r,
|
|
150
|
+
'agent_board._append_task_event(text,uuid,uuid,text,jsonb,text,uuid,timestamptz)', 'execute')),
|
|
151
|
+
'',
|
|
152
|
+
'OP-7: no non-owner role can execute _append_task_event -- every caller is SECURITY DEFINER');
|
|
153
|
+
|
|
154
|
+
select is(
|
|
155
|
+
(select coalesce(string_agg(r, ', ' order by r), '')
|
|
156
|
+
from unnest(array['authenticated','service_role']) r
|
|
157
|
+
where has_function_privilege(r,
|
|
158
|
+
'agent_board.append_task_event(uuid,uuid,text,jsonb,text,uuid,timestamptz)', 'execute')),
|
|
159
|
+
'authenticated, service_role',
|
|
160
|
+
'OP-8: the public entry point kept its ACL through CREATE OR REPLACE');
|
|
161
|
+
|
|
162
|
+
-- ---------------------------------------------------------------------------
|
|
163
|
+
-- OP-9: the two columns stay SEPARATE. Folding a stamped posture into verification_status
|
|
164
|
+
-- would let it satisfy a gate written against the evidence vocabulary.
|
|
165
|
+
-- ---------------------------------------------------------------------------
|
|
166
|
+
select is(
|
|
167
|
+
(select count(*)::int from pg_attribute
|
|
168
|
+
where attrelid = 'agent_board.v_task_tree'::regclass
|
|
169
|
+
and attname in ('origin_posture','verification_status') and attnum > 0),
|
|
170
|
+
2,
|
|
171
|
+
'OP-9: v_task_tree exposes origin_posture and verification_status as two distinct columns');
|
|
172
|
+
|
|
173
|
+
-- ===========================================================================
|
|
174
|
+
-- Behaviour. A file that only read the catalog would pass against writers that no longer
|
|
175
|
+
-- work. Every RPC runs inside the fixture block, never inside an assertion's value
|
|
176
|
+
-- expression -- a raise there would abort the file with no `not ok` (#113).
|
|
177
|
+
-- ===========================================================================
|
|
178
|
+
create temp table _op_fx (actor uuid, actor_user uuid, thread uuid, task uuid, board uuid) on commit drop;
|
|
179
|
+
|
|
180
|
+
do $$
|
|
181
|
+
declare
|
|
182
|
+
v_suffix text := replace(substr(gen_random_uuid()::text, 1, 8), '-', '');
|
|
183
|
+
v_creator uuid := gen_random_uuid();
|
|
184
|
+
v_actor uuid := gen_random_uuid();
|
|
185
|
+
v_actor_user uuid;
|
|
186
|
+
v_board uuid; v_thread uuid := gen_random_uuid(); v_task uuid;
|
|
187
|
+
begin
|
|
188
|
+
v_actor_user := public.ensure_github_user('op-actor-' || v_suffix, 'op-actor-' || v_suffix || '@example.test',
|
|
189
|
+
'op-actor-' || v_suffix, 'OP Actor');
|
|
190
|
+
insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
|
|
191
|
+
values (v_creator, 'agent', 'op-creator-' || v_suffix, 'OP Creator', 'active', null, null),
|
|
192
|
+
(v_actor, 'agent', 'op-actor-' || v_suffix, 'OP Actor', 'active', v_actor_user, v_actor_user);
|
|
193
|
+
|
|
194
|
+
select board_id into v_board
|
|
195
|
+
from agent_board.create_board(v_creator, 'op-board-' || v_suffix, 'OP Board', 'origin posture', '{}'::jsonb, 'private');
|
|
196
|
+
insert into agent_board.board_memberships(board_id, agent_id, membership_state, created_by_agent_id, role)
|
|
197
|
+
values (v_board, v_actor, 'joined', v_creator, 'member');
|
|
198
|
+
|
|
199
|
+
insert into agent_board.threads(id, thread_type, created_by_agent_id, subject)
|
|
200
|
+
values (v_thread, 'coordination', v_actor, 'OP thread');
|
|
201
|
+
|
|
202
|
+
perform set_config('request.jwt.claim.role', 'authenticated', true);
|
|
203
|
+
perform set_config('request.jwt.claim.sub', v_actor_user::text, true);
|
|
204
|
+
perform agent_board.post_board_message(v_thread, v_actor, v_board, 'seed', 'seed body');
|
|
205
|
+
|
|
206
|
+
-- One write per posture the live writers can produce.
|
|
207
|
+
select task_id into v_task from agent_board.create_task(v_thread, v_actor, 'op task', 'd');
|
|
208
|
+
perform agent_board.append_task_event(v_task, v_actor, 'commented'); -- direct-rpc
|
|
209
|
+
perform agent_board.raise_conflict(v_thread, v_actor, 'op conflict', 'other', 'medium', v_task);
|
|
210
|
+
perform agent_board.acquire_claim(v_actor, 'task', 'op-' || v_suffix, now() + interval '1 hour', v_task);
|
|
211
|
+
|
|
212
|
+
insert into _op_fx values (v_actor, v_actor_user, v_thread, v_task, v_board);
|
|
213
|
+
end
|
|
214
|
+
$$;
|
|
215
|
+
|
|
216
|
+
select is(
|
|
217
|
+
(select origin_posture from agent_board.task_events
|
|
218
|
+
where task_id = (select task from _op_fx) and event_sequence = 1),
|
|
219
|
+
'unattested:signed-metadata-rpc',
|
|
220
|
+
'OP-10: create_task''s own direct insert stamps signed-metadata');
|
|
221
|
+
|
|
222
|
+
select is(
|
|
223
|
+
(select origin_posture from agent_board.task_events
|
|
224
|
+
where task_id = (select task from _op_fx) and event_type = 'commented'),
|
|
225
|
+
'unattested:direct-rpc',
|
|
226
|
+
'OP-11: the public append_task_event stamps direct-rpc -- the caller declared nothing');
|
|
227
|
+
|
|
228
|
+
select is(
|
|
229
|
+
(select origin_posture from agent_board.task_events
|
|
230
|
+
where task_id = (select task from _op_fx) and event_type = 'conflict_raised'),
|
|
231
|
+
'unattested:unsigned-deliberate-rpc',
|
|
232
|
+
'OP-12: raise_conflict stamps unsigned-deliberate, its #261 step 2 adjudication');
|
|
233
|
+
|
|
234
|
+
select is(
|
|
235
|
+
(select origin_posture from agent_board.task_events
|
|
236
|
+
where task_id = (select task from _op_fx) and event_type = 'claimed'),
|
|
237
|
+
'unattested:signed-metadata-rpc',
|
|
238
|
+
'OP-13: acquire_claim stamps signed-metadata -- a signed parent whose derived event is still not itself signed');
|
|
239
|
+
|
|
240
|
+
-- Scoped to this fixture's task: an agent-wide or table-wide count is polluted by earlier
|
|
241
|
+
-- fixtures and would fail looking like a real defect.
|
|
242
|
+
select is(
|
|
243
|
+
(select count(*)::int from agent_board.task_events
|
|
244
|
+
where task_id = (select task from _op_fx) and origin_posture is null),
|
|
245
|
+
0,
|
|
246
|
+
'OP-14: no row this run wrote carries a NULL posture');
|
|
247
|
+
|
|
248
|
+
-- M6: an 'attested:evidence-row' stamp is a CLAIM that evidence exists. If it can be stamped without
|
|
249
|
+
-- the row, it is decoration. Table-wide on purpose -- this one is a whole-database invariant,
|
|
250
|
+
-- and a false claim anywhere is a defect regardless of which fixture wrote it.
|
|
251
|
+
select is(
|
|
252
|
+
(select count(*)::int from agent_board.task_events te
|
|
253
|
+
where te.origin_posture = 'attested:evidence-row'
|
|
254
|
+
and not exists (select 1 from agent_board.signature_evidence se
|
|
255
|
+
where se.source_kind = 'task_event' and se.source_task_event_id = te.id)),
|
|
256
|
+
0,
|
|
257
|
+
'OP-15: every row stamped evidence-row really has a signature_evidence row -- the stamp is not decoration');
|
|
258
|
+
|
|
259
|
+
-- M8, pinned BEHAVIOURALLY rather than by reading the view's source. OP-9 counts two columns
|
|
260
|
+
-- and a fold keeps both names, so OP-9 alone would stay green through the exact mutation it
|
|
261
|
+
-- was supposed to catch -- found by running the battery against it, not by reading it.
|
|
262
|
+
-- This task has a posture and NO signature_evidence row, so if the two are ever folded,
|
|
263
|
+
-- verification_status starts answering 'unattested:signed-metadata-rpc' and this fails.
|
|
264
|
+
select is(
|
|
265
|
+
(select verification_status || '/' || origin_posture
|
|
266
|
+
from agent_board.v_task_tree where task_id = (select task from _op_fx)),
|
|
267
|
+
null,
|
|
268
|
+
'OP-16: a stamped posture does NOT leak into verification_status -- it stays NULL with no evidence row');
|
|
269
|
+
|
|
270
|
+
-- M5. A default on p_origin_posture would let an internal caller omit it and inherit a
|
|
271
|
+
-- plausible label. PostgreSQL forbids a defaulted parameter before non-defaulted ones, so
|
|
272
|
+
-- putting the posture FIRST is what makes "no default" structural rather than a convention
|
|
273
|
+
-- -- the database rejects the mutation instead of a test catching it afterwards. This pins
|
|
274
|
+
-- the ORDER, which is the thing actually doing the work.
|
|
275
|
+
select is(
|
|
276
|
+
(select p.proargnames[1] from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
277
|
+
where n.nspname = 'agent_board' and p.proname = '_append_task_event'),
|
|
278
|
+
'p_origin_posture',
|
|
279
|
+
'OP-17: p_origin_posture is the FIRST parameter, so PostgreSQL itself forbids giving it a default');
|
|
280
|
+
|
|
281
|
+
-- ---------------------------------------------------------------------------
|
|
282
|
+
-- OP-18: THE FLOOR THE CLASS CLAIM ACTUALLY RESTS ON.
|
|
283
|
+
--
|
|
284
|
+
-- OP-4/OP-5 walk FUNCTION BODIES, and a body walk cannot be complete in the presence of
|
|
285
|
+
-- dynamic SQL -- proven in this very schema: mark_retention_metadata issues an UPDATE against
|
|
286
|
+
-- task_events through a format() template, and an earlier version of this work asserted in
|
|
287
|
+
-- writing that it "issues no DML", because a text scan cannot see a template.
|
|
288
|
+
--
|
|
289
|
+
-- This assertion is what makes the enumeration sound anyway. If no role can INSERT into
|
|
290
|
+
-- task_events, then every row arrives through a SECURITY DEFINER function owned by the schema
|
|
291
|
+
-- owner -- which is precisely the set the closure walks. Bound the claim with
|
|
292
|
+
-- has_table_privilege, not with a longer regex. Grant INSERT to any role and the class becomes
|
|
293
|
+
-- unbounded in a way no body scan would report, so this fails FIRST.
|
|
294
|
+
-- ---------------------------------------------------------------------------
|
|
295
|
+
select is(
|
|
296
|
+
(select coalesce(string_agg(r, ', ' order by r), '')
|
|
297
|
+
from unnest(array['anon','authenticated','service_role']) r
|
|
298
|
+
where has_table_privilege(r, 'agent_board.task_events', 'insert')),
|
|
299
|
+
'',
|
|
300
|
+
'OP-18: no role holds INSERT on task_events, so every row comes through a definer function -- the bound the body walk cannot supply itself');
|
|
301
|
+
|
|
302
|
+
-- ---------------------------------------------------------------------------
|
|
303
|
+
-- OP-19: the stamp is written once and never rewritten. A posture that can be UPDATEd after
|
|
304
|
+
-- the fact is not evidence about the writer, it is a mutable label -- and rewriting a row via
|
|
305
|
+
-- definer privilege to "fix" it is a move this repo has explicitly ruled out.
|
|
306
|
+
--
|
|
307
|
+
-- Literals are KEPT here for a second reason beyond OP-4's: a format() template is a string
|
|
308
|
+
-- LITERAL in the body, so keeping literals is what lets this see dynamic SQL at all. It sees a
|
|
309
|
+
-- template; it would NOT see SQL concatenated from fragments across variables. That residual is
|
|
310
|
+
-- stated rather than papered over, and OP-18 is what covers it.
|
|
311
|
+
-- ---------------------------------------------------------------------------
|
|
312
|
+
select is(
|
|
313
|
+
(select coalesce(string_agg(p.proname, ', ' order by p.proname), '')
|
|
314
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
315
|
+
where n.nspname = 'agent_board'
|
|
316
|
+
and p.prolang <> (select oid from pg_language where lanname = 'internal')
|
|
317
|
+
and agent_board_test.stripped_body(p.oid, true) ~* 'set\s[^;]*\morigin_posture\s*='),
|
|
318
|
+
'',
|
|
319
|
+
'OP-19: nothing UPDATEs origin_posture -- including through a dynamic-SQL template, which keeping literals makes visible');
|
|
320
|
+
|
|
321
|
+
-- ---------------------------------------------------------------------------
|
|
322
|
+
-- OP-20: THE HOLE THREE REVIEWERS FOUND, NOW CLOSED.
|
|
323
|
+
--
|
|
324
|
+
-- OP-4 only inspects bodies that call _append_task_event. A thirteenth in-schema writer that
|
|
325
|
+
-- called the PUBLIC append_task_event instead would enter the class, compile cleanly, be
|
|
326
|
+
-- stamped 'unattested:direct-rpc', and be named by NOTHING. The header of this file claimed a
|
|
327
|
+
-- thirteenth writer "fails them AND NAMES ITSELF"; against that shape it did not, and the
|
|
328
|
+
-- sentence has been corrected rather than defended.
|
|
329
|
+
--
|
|
330
|
+
-- The public entry point exists for callers OUTSIDE the schema. Zero in-schema functions call
|
|
331
|
+
-- it, so requiring that is not a restriction on anything that exists -- it is the assertion
|
|
332
|
+
-- that makes "names itself" true for the one shape where it was false.
|
|
333
|
+
-- ---------------------------------------------------------------------------
|
|
334
|
+
select is(
|
|
335
|
+
(select coalesce(string_agg(p.proname, ', ' order by p.proname), '')
|
|
336
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
337
|
+
where n.nspname = 'agent_board'
|
|
338
|
+
and p.proname <> 'append_task_event'
|
|
339
|
+
and agent_board_test.stripped_body(p.oid, true)
|
|
340
|
+
~ '(^|[^a-z0-9_.])agent_board\.append_task_event\s*\('),
|
|
341
|
+
'',
|
|
342
|
+
'OP-20: no in-schema function calls the PUBLIC append_task_event -- one that did would be stamped direct-rpc and named by nothing else');
|
|
343
|
+
|
|
344
|
+
-- ---------------------------------------------------------------------------
|
|
345
|
+
-- OP-21: the premise under OP-7. Granting _append_task_event to no role is only safe because
|
|
346
|
+
-- a SECURITY DEFINER caller runs as ITS OWN owner -- so if a caller were owned by a different
|
|
347
|
+
-- role, it would lose EXECUTE and fail at runtime rather than being protected. OP-7 asserts
|
|
348
|
+
-- ordinary roles lack access; it says nothing about owners. Raised by a reviewer as an
|
|
349
|
+
-- unverified premise, and it was: measured true (one owner across the schema), now pinned.
|
|
350
|
+
-- ---------------------------------------------------------------------------
|
|
351
|
+
select is(
|
|
352
|
+
(select count(distinct p.proowner)::int
|
|
353
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
354
|
+
where n.nspname = 'agent_board'),
|
|
355
|
+
1,
|
|
356
|
+
'OP-21: every agent_board function shares one owner, which is what makes an ungranted internal funnel reachable by its callers');
|
|
357
|
+
|
|
358
|
+
-- ---------------------------------------------------------------------------
|
|
359
|
+
-- OP-22: OP-15 WAS VACUOUS AND A REVIEWER SAID SO.
|
|
360
|
+
--
|
|
361
|
+
-- OP-15 counts rows stamped attested:% that have no evidence row. In an unmutated run the
|
|
362
|
+
-- fixture creates NO attested row at all, so it was 0 = 0 and green for the wrong reason.
|
|
363
|
+
-- M6 reds it, which is why the property is not unpinned -- but a green that only means "the
|
|
364
|
+
-- set was empty" is the exact shape rule 3 forbids.
|
|
365
|
+
--
|
|
366
|
+
-- This constructs the POSITIVE case: an attested row WITH its evidence, so the invariant is
|
|
367
|
+
-- exercised in the direction that can actually distinguish a correct implementation from an
|
|
368
|
+
-- empty table. Written directly rather than through signed_append_task_event because that
|
|
369
|
+
-- wrapper needs a gateway decision this file has no business forging -- so it is labelled as
|
|
370
|
+
-- a constructed pair, not as a claim about what any writer did.
|
|
371
|
+
-- ---------------------------------------------------------------------------
|
|
372
|
+
do $$
|
|
373
|
+
declare v_ev uuid; v_task uuid; v_actor uuid; v_thread uuid;
|
|
374
|
+
begin
|
|
375
|
+
select task, actor, thread into v_task, v_actor, v_thread from _op_fx;
|
|
376
|
+
insert into agent_board.task_events(task_id, thread_id, actor_agent_id, event_sequence, event_type, payload, origin_posture)
|
|
377
|
+
values (v_task, v_thread, v_actor, 99, 'commented', '{}'::jsonb, 'attested:evidence-row')
|
|
378
|
+
returning id into v_ev;
|
|
379
|
+
insert into agent_board.signature_evidence(agent_id, source_kind, source_task_event_id, operation,
|
|
380
|
+
payload_digest, verification_status)
|
|
381
|
+
values (v_actor, 'task_event', v_ev, 'append_task_event', 'sha256:op22-constructed', 'verified');
|
|
382
|
+
end $$;
|
|
383
|
+
|
|
384
|
+
select is(
|
|
385
|
+
(select count(*)::int from agent_board.task_events te
|
|
386
|
+
where te.origin_posture like 'attested:%'
|
|
387
|
+
and exists (select 1 from agent_board.signature_evidence se
|
|
388
|
+
where se.source_kind = 'task_event' and se.source_task_event_id = te.id)),
|
|
389
|
+
1,
|
|
390
|
+
'OP-22: OP-15 is exercised against a REAL attested row, not an empty set -- the positive direction of the same invariant');
|
|
391
|
+
|
|
392
|
+
select * from finish();
|
|
393
|
+
rollback;
|
|
@@ -80,6 +80,10 @@ select distinct h.fn_name || ': ' ||
|
|
|
80
80
|
select is(
|
|
81
81
|
(select array_agg(m order by m) from _tt_ord),
|
|
82
82
|
array[
|
|
83
|
+
-- #288: the funnel body moved here from append_task_event, carrying the #281 R1 order
|
|
84
|
+
-- with it. The public append_task_event still appears below, reaching the same two
|
|
85
|
+
-- acquisitions `via callee` -- so this row is a NEW member, not a relocated one.
|
|
86
|
+
'_append_task_event: threads(key share) then tasks(update)',
|
|
83
87
|
'_upsert_delivery_recipient: threads(update) then tasks(key share)',
|
|
84
88
|
'acquire_claim: same statement: threads(key share) + tasks(update)',
|
|
85
89
|
'add_artifact: same statement: threads(key share) + tasks(update)',
|
|
@@ -112,8 +116,8 @@ select is(
|
|
|
112
116
|
(select count(*)::text || ' functions acquire both; '
|
|
113
117
|
|| count(*) filter (where tasks_first)::text || ' take tasks before threads'
|
|
114
118
|
from _tt_ord),
|
|
115
|
-
'
|
|
116
|
-
'TT-2:
|
|
119
|
+
'21 functions acquire both; 0 take tasks before threads',
|
|
120
|
+
'TT-2: 21 functions acquire both relations and NONE takes tasks before threads');
|
|
117
121
|
|
|
118
122
|
-- ===========================================================================
|
|
119
123
|
-- TT-3: append_task_event's acquisitions on the two relations, in path order, with mode
|
|
@@ -125,14 +129,14 @@ select is(
|
|
|
125
129
|
(select string_agg(e.rel_name || ' ' || e.lock_mode || ' via ' || e.via, ' -> '
|
|
126
130
|
order by e.path, e.rel_name, e.lock_mode, e.via)
|
|
127
131
|
from _tt_le e
|
|
128
|
-
where e.fn_name = '
|
|
132
|
+
where e.fn_name = '_append_task_event'
|
|
129
133
|
and e.rel_name in ('agent_board.tasks', 'agent_board.threads')),
|
|
130
134
|
'agent_board.threads key share via textual'
|
|
131
135
|
|| ' -> agent_board.tasks update via textual'
|
|
132
136
|
|| ' -> agent_board.tasks key share via fk-insert:task_events_task_id_fkey'
|
|
133
137
|
|| ' -> agent_board.threads key share via fk-insert:task_events_thread_id_fkey'
|
|
134
138
|
|| ' -> agent_board.tasks update via write-update',
|
|
135
|
-
'TT-3:
|
|
139
|
+
'TT-3: _append_task_event takes threads(KEY SHARE, explicit) FIRST, then the tasks FOR UPDATE');
|
|
136
140
|
|
|
137
141
|
-- ===========================================================================
|
|
138
142
|
-- TT-4: THE PREMISE THE PRE-READ RESTS ON. append_task_event now reads tasks.thread_id
|
|
@@ -179,11 +183,11 @@ select is(
|
|
|
179
183
|
select is(
|
|
180
184
|
(select coalesce(string_agg(distinct e.lock_mode, ', ' order by e.lock_mode), '<none>')
|
|
181
185
|
from _tt_le e
|
|
182
|
-
where e.fn_name = '
|
|
186
|
+
where e.fn_name = '_append_task_event'
|
|
183
187
|
and e.rel_name = 'agent_board.tasks'
|
|
184
188
|
and e.via = 'textual'),
|
|
185
189
|
'update',
|
|
186
|
-
'TT-6:
|
|
190
|
+
'TT-6: _append_task_event still takes an EXPLICIT FOR UPDATE on tasks -- the sequence guard is intact');
|
|
187
191
|
|
|
188
192
|
-- ===========================================================================
|
|
189
193
|
-- TT-7: the second half of the fix, on its own. `_upsert_delivery_recipient` has no
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"signature": "_agent_principal_active(p_agent_id uuid)",
|
|
30
30
|
"declares_p_metadata": false
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
"signature": "_append_task_event(p_origin_posture text, p_task_id uuid, p_actor_agent_id uuid, p_event_type text, p_payload jsonb, p_status text, p_assigned_agent_id uuid, p_completed_at timestamp with time zone)",
|
|
34
|
+
"declares_p_metadata": false
|
|
35
|
+
},
|
|
32
36
|
{
|
|
33
37
|
"signature": "_assert_actor_not_fleet_revoked(p_actor_agent_id uuid, p_operation text)",
|
|
34
38
|
"declares_p_metadata": false
|