@consiliency/agent-board-schema 1.64.0 → 1.74.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 +19 -0
- package/fixtures/already-applied.json +44 -4
- package/migrations/20261015000100_agent_board_session_provenance.sql +367 -0
- package/migrations/20261015000200_agent_board_request_preserve_removed.sql +87 -0
- package/migrations/20261015000300_agent_board_rls_correlation.sql +132 -0
- package/migrations/20261015000400_agent_board_resolution_authority.sql +146 -0
- package/migrations/20261026000100_agent_board_thread_association.sql +1287 -0
- package/migrations/20261101000100_agent_board_session_measured_mint.sql +145 -0
- package/migrations/20261104065658_agent_board_reply_delivery.sql +659 -0
- package/migrations/20261104170000_agent_board_tasking_replay_scope.sql +320 -0
- package/migrations/20261104180000_agent_board_delivery_writer_execute.sql +51 -0
- package/migrations/20261104190000_agent_board_message_replay_scope.sql +373 -0
- package/package.json +1 -1
- package/schema-manifest.json +93 -3
- package/src/deployment-drift-main.test.mjs +156 -0
- package/src/deployment-drift-script.test.mjs +14 -1
- package/src/policy-comment.test.mjs +17 -0
- package/src/tooling.test.mjs +12 -2
- package/tests/agent_board_661_reply_delivery.test.sql +185 -0
- package/tests/agent_board_667_boundary.test.sql +162 -0
- package/tests/agent_board_667_conversion_branches.test.sql +91 -0
- package/tests/agent_board_667_conversion_profile.test.sql +112 -0
- package/tests/agent_board_667_history_class.test.sql +785 -0
- package/tests/agent_board_667_native_cast_effect.test.sql +88 -0
- package/tests/agent_board_667_realtime_apply_behavior.test.sql +117 -0
- package/tests/agent_board_667_realtime_apply_controls.test.sql +143 -0
- package/tests/agent_board_667_realtime_apply_profile.test.sql +61 -0
- package/tests/agent_board_667_retention.test.sql +201 -0
- package/tests/agent_board_667_routing.test.sql +294 -0
- package/tests/agent_board_667_sender_effects.test.sql +100 -0
- package/tests/agent_board_667_sender_profile_controls.test.sql +29 -0
- package/tests/agent_board_667_storage_delimiter_behavior.test.sql +84 -0
- package/tests/agent_board_667_storage_delimiter_controls.test.sql +23 -0
- package/tests/agent_board_667_storage_delimiter_profile.test.sql +61 -0
- package/tests/agent_board_667_storage_search_behavior.test.sql +93 -0
- package/tests/agent_board_667_storage_search_controls.test.sql +22 -0
- package/tests/agent_board_667_storage_search_profile.test.sql +60 -0
- package/tests/agent_board_667_storage_timestamp_behavior.test.sql +58 -0
- package/tests/agent_board_667_storage_timestamp_controls.test.sql +22 -0
- package/tests/agent_board_667_storage_timestamp_profile.test.sql +54 -0
- package/tests/agent_board_667_writers.test.sql +281 -0
- package/tests/agent_board_717_message_replay_scope.test.sql +828 -0
- package/tests/agent_board_717_tasking_replay_scope.test.sql +661 -0
- package/tests/agent_board_addressing.test.sql +8 -5
- package/tests/agent_board_claim_subject_gate.test.sql +7 -3
- package/tests/agent_board_claimpolicy_race.test.sql +39 -11
- package/tests/agent_board_consent_gating.test.sql +4 -4
- package/tests/agent_board_delegate_role_class.test.sql +33 -15
- package/tests/agent_board_delivery_writer_execute.test.sql +371 -0
- package/tests/agent_board_fk_lock_edges.test.sql +3 -0
- package/tests/agent_board_gatenull_membstate.test.sql +7 -4
- package/tests/agent_board_hosted_delegates.test.sql +5 -2
- package/tests/agent_board_inbox_rpcs.test.sql +5 -3
- package/tests/agent_board_obsgate.test.sql +8 -4
- package/tests/agent_board_obsgate4.test.sql +2 -2
- package/tests/agent_board_outbox_wake_ordering.test.sql +25 -2
- package/tests/agent_board_own_post_not_delivered.test.sql +11 -9
- package/tests/agent_board_policy_correlation.test.sql +189 -0
- package/tests/agent_board_request_removed.test.sql +96 -0
- package/tests/agent_board_resolution_authority.test.sql +190 -0
- package/tests/agent_board_session_identity_behavior_classes.test.sql +2 -2
- package/tests/agent_board_session_identity_limits.test.sql +1 -1
- package/tests/agent_board_session_identity_measured_mint.test.sql +342 -0
- package/tests/agent_board_session_identity_predicate_isolation.test.sql +146 -0
- package/tests/agent_board_session_provenance.test.sql +106 -0
- package/tests/agent_board_sidserver_authority.test.sql +69 -0
- package/tests/agent_board_sidserver_catalog.test.sql +47 -0
- package/tests/agent_board_signed_write_gateway.test.sql +4 -2
- package/tests/agent_board_task_thread_lock_order.test.sql +67 -42
- package/tests/agent_board_v2_invariants.test.sql +5 -2
- package/tests/agent_board_write_enforcement_class.test.sql +3 -3
- package/tests/catalog/function_execute_privileges.json +43 -7
- package/tests/catalog/p_metadata_parameters.json +16 -0
- package/tests/fixtures/agent_board_667_apply_rls_guard.sql +195 -0
- package/tests/fixtures/agent_board_667_apply_rls_provenance.md +125 -0
- package/tests/fixtures/agent_board_667_apply_rls_reference.sql +1903 -0
- package/tests/fixtures/agent_board_667_conversion_guard.sql +162 -0
- package/tests/fixtures/agent_board_667_conversion_reference.md +23 -0
- package/tests/fixtures/agent_board_667_conversion_reference.sql +5325 -0
- package/tests/fixtures/agent_board_667_delimiter_guard.sql +92 -0
- package/tests/fixtures/agent_board_667_delimiter_provenance.md +116 -0
- package/tests/fixtures/agent_board_667_delimiter_reference.sql +10150 -0
- package/tests/fixtures/agent_board_667_search_guard.sql +92 -0
- package/tests/fixtures/agent_board_667_search_provenance.md +141 -0
- package/tests/fixtures/agent_board_667_search_reference.sql +4779 -0
- package/tests/fixtures/agent_board_667_sender_guard.sql +102 -0
- package/tests/fixtures/agent_board_667_sender_reference.md +20 -0
- package/tests/fixtures/agent_board_667_sender_reference.sql +875 -0
- package/tests/fixtures/agent_board_667_timestamp_graph.sql +84 -0
- package/tests/fixtures/agent_board_667_timestamp_guard.sql +94 -0
- package/tests/fixtures/agent_board_667_timestamp_provenance.md +99 -0
- package/tests/fixtures/agent_board_667_timestamp_reference.sql +2938 -0
- package/tests/isolation/expected/association_artifact_anchor_order.out +108 -0
- package/tests/isolation/expected/association_board_vs_board.out +71 -0
- package/tests/isolation/expected/association_post_wait_authority.out +77 -0
- package/tests/isolation/expected/association_strong_history_writers.out +203 -0
- package/tests/isolation/expected/association_strong_isolation.out +185 -0
- package/tests/isolation/expected/association_vs_artifact_claim_escalation.out +199 -0
- package/tests/isolation/expected/association_vs_heartbeat.out +78 -0
- package/tests/isolation/expected/association_vs_multi_anchor_artifact.out +67 -0
- package/tests/isolation/expected/association_vs_private_message.out +67 -0
- package/tests/isolation/expected/association_vs_task_and_conflict.out +133 -0
- package/tests/isolation/expected/association_vs_taskful_cross_thread.out +133 -0
- package/tests/isolation/expected/consent_approval_lock.out +1 -1
- package/tests/isolation/expected/message_replay_scope.out +209 -0
- package/tests/isolation/expected/tasking_replay_scope.out +125 -0
- package/tests/isolation/specs/association_artifact_anchor_order.spec +46 -0
- package/tests/isolation/specs/association_board_vs_board.spec +37 -0
- package/tests/isolation/specs/association_post_wait_authority.spec +48 -0
- package/tests/isolation/specs/association_strong_history_writers.spec +61 -0
- package/tests/isolation/specs/association_strong_isolation.spec +50 -0
- package/tests/isolation/specs/association_vs_artifact_claim_escalation.spec +41 -0
- package/tests/isolation/specs/association_vs_heartbeat.spec +47 -0
- package/tests/isolation/specs/association_vs_multi_anchor_artifact.spec +38 -0
- package/tests/isolation/specs/association_vs_private_message.spec +33 -0
- package/tests/isolation/specs/association_vs_task_and_conflict.spec +38 -0
- package/tests/isolation/specs/association_vs_taskful_cross_thread.spec +42 -0
- package/tests/isolation/specs/consent_approval_lock.spec +13 -7
- package/tests/isolation/specs/message_replay_scope.spec +77 -0
- package/tests/isolation/specs/tasking_replay_scope.spec +72 -0
- package/tests/mutations/717-f1/M0_prefix_main_acl_and_comments.sql +11 -0
- package/tests/mutations/717-f1/M10_wrapper_stops_delegating.sql +7 -0
- package/tests/mutations/717-f1/M11_transitive_recipient_content.sql +8 -0
- package/tests/mutations/717-f1/M12_transitive_recipient_visibility.sql +8 -0
- package/tests/mutations/717-f1/M13_transitive_worker_content.sql +15 -0
- package/tests/mutations/717-f1/M1a_regrant_upsert_delivery_recipient.sql +1 -0
- package/tests/mutations/717-f1/M1b_regrant_upsert_inbox_item.sql +1 -0
- package/tests/mutations/717-f1/M1c_regrant_queue_inbox_callbacks.sql +1 -0
- package/tests/mutations/717-f1/M1d_regrant_queue_board_post_callbacks.sql +1 -0
- package/tests/mutations/717-f1/M1e_regrant_next_thread_message_sequence.sql +1 -0
- package/tests/mutations/717-f1/M2_blanket_schema_grant.sql +1 -0
- package/tests/mutations/717-f1/M3_new_overload_of_sink.sql +10 -0
- package/tests/mutations/717-f1/M4_new_route_calling_sink.sql +7 -0
- package/tests/mutations/717-f1/M5_actor_trusts_supplied_id.sql +5 -0
- package/tests/mutations/717-f1/M6_signed_route_granted_to_authenticated.sql +1 -0
- package/tests/mutations/717-f1/M7_worker_granted_to_authenticated.sql +1 -0
- package/tests/mutations/717-f1/M8_recipient_state_rewrites_visibility.sql +5 -0
- package/tests/mutations/717-f1/M9_public_route_revoked_from_service_role.sql +1 -0
- package/tests/mutations/717-f1/outbox_wake_ordering/W1_regrant_queue_inbox_callbacks.sql +1 -0
- package/tests/mutations/717-f1/outbox_wake_ordering/W2_revoke_worker_from_service_role.sql +1 -0
- package/tests/mutations/717-f1/outbox_wake_ordering/W3_invoker_caller_of_sink.sql +1 -0
- package/tests/mutations/717-f1/outbox_wake_ordering/W4_public_regrant_board_post_sink.sql +1 -0
- package/tests/mutations/717-f1/run.sh +17 -0
- package/tests/mutations/717-f2b/C1_new_delivery_route.sql +4 -0
- package/tests/mutations/717-f2b/C2_new_replay_lookup.sql +3 -0
- package/tests/mutations/717-f2b/C3_internal_regranted.sql +2 -0
- package/tests/mutations/717-f2b/C4_wrapper_writes_delivery.sql +9 -0
- package/tests/mutations/717-f2b/C5_class_member_dynamic_sql.sql +9 -0
- package/tests/mutations/717-f2b/C6_drop_board_scope_constraint.sql +2 -0
- package/tests/mutations/717-f2b/C7_state_private_helper_content.sql +14 -0
- package/tests/mutations/717-f2b/L1_dm_lock_split_by_recipient.sql +8 -0
- package/tests/mutations/717-f2b/L2_dm_lock_removed.sql +12 -0
- package/tests/mutations/717-f2b/L3_mc_lock_split_by_recipients.sql +8 -0
- package/tests/mutations/717-f2b/L4_mc_lock_removed.sql +12 -0
- package/tests/mutations/717-f2b/M0_prefix_definitions.sql +17 -0
- package/tests/mutations/717-f2b/M10_mc_lookup_any_author.sql +15 -0
- package/tests/mutations/717-f2b/M11_dm_swallow_refusal.sql +8 -0
- package/tests/mutations/717-f2b/M12_mc_swallow_refusal.sql +8 -0
- package/tests/mutations/717-f2b/M13_signed_dm_swallows_refusal.sql +32 -0
- package/tests/mutations/717-f2b/M14_signed_mc_swallows_refusal.sql +20 -0
- package/tests/mutations/717-f2b/M15_mc_any_source.sql +7 -0
- package/tests/mutations/717-f2b/M1_dm_drop_recipient_proof.sql +16 -0
- package/tests/mutations/717-f2b/M2_mc_drop_recipient_proof.sql +16 -0
- package/tests/mutations/717-f2b/M3_dm_metadata_as_proof.sql +12 -0
- package/tests/mutations/717-f2b/M4_mc_metadata_as_proof.sql +16 -0
- package/tests/mutations/717-f2b/M5_dm_any_mode.sql +11 -0
- package/tests/mutations/717-f2b/M6_dm_board_delivery_counts.sql +11 -0
- package/tests/mutations/717-f2b/M7_mc_any_mode.sql +11 -0
- package/tests/mutations/717-f2b/M8_dm_thread_stamp_as_proof.sql +12 -0
- package/tests/mutations/717-f2b/M9_dm_lookup_any_author.sql +15 -0
- package/tests/mutations/717-f2b/run.sh +82 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
-- Catalog-derived history and writer dispositions, coupled to #667 behavior and
|
|
2
|
+
-- mutation witnesses. Unsupported routine and entry paths fail by full identity.
|
|
3
|
+
create extension if not exists pgtap with schema extensions;
|
|
4
|
+
set search_path = extensions, public, agent_board;
|
|
5
|
+
begin;
|
|
6
|
+
-- Like WEC-24, own the parser prerequisite inside this test's transaction. CI
|
|
7
|
+
-- starts with the extension available but not installed; another file's rolled
|
|
8
|
+
-- back installation cannot supply it. Missing availability still fails closed.
|
|
9
|
+
do $$
|
|
10
|
+
begin
|
|
11
|
+
if exists (select 1 from pg_available_extensions where name='plpgsql_check') then
|
|
12
|
+
execute 'create extension if not exists plpgsql_check with schema extensions';
|
|
13
|
+
end if;
|
|
14
|
+
end $$;
|
|
15
|
+
select plan(61);
|
|
16
|
+
-- Procedure bodies and CALL edges are outside the function parsers below.
|
|
17
|
+
-- Fail closed on every non-extension application procedure until its complete
|
|
18
|
+
-- history and caller effects receive an explicit positive disposition.
|
|
19
|
+
select is(
|
|
20
|
+
(select string_agg(format('%I.%I(%s)', n.nspname, p.proname,
|
|
21
|
+
pg_get_function_identity_arguments(p.oid)), ', '
|
|
22
|
+
order by n.nspname, p.proname, p.oid::regprocedure::text)
|
|
23
|
+
from pg_proc p join pg_namespace n on n.oid=p.pronamespace
|
|
24
|
+
where p.prokind='p'
|
|
25
|
+
and n.nspname not in ('pg_catalog','information_schema','agent_board_test')
|
|
26
|
+
and n.nspname not like 'pg_%'
|
|
27
|
+
and not exists (select 1 from pg_depend d
|
|
28
|
+
where d.classid='pg_proc'::regclass and d.objid=p.oid and d.deptype='e')),
|
|
29
|
+
null::text, 'XB application procedures and CALL paths require explicit disposition');
|
|
30
|
+
-- Supabase ships DDL event-trigger handlers outside extension membership. They
|
|
31
|
+
-- are not ordinary RPC entry points: prove the PostgreSQL refusal for every
|
|
32
|
+
-- catalog-derived handler instead of excluding a platform schema or name. The
|
|
33
|
+
-- control keeps this exemption exercised on minimal local substrates too.
|
|
34
|
+
create schema xb_event_trigger_probe;
|
|
35
|
+
create function xb_event_trigger_probe.not_rpc() returns event_trigger language plpgsql as $body$
|
|
36
|
+
begin execute 'select 1'; end $body$;
|
|
37
|
+
create function pg_temp.xb_event_trigger_call_problem(p_fn oid) returns text language plpgsql as $body$
|
|
38
|
+
begin
|
|
39
|
+
execute format('select %s()',p_fn::regproc);
|
|
40
|
+
return 'ordinary invocation succeeded';
|
|
41
|
+
exception when others then
|
|
42
|
+
if sqlstate='0A000' and sqlerrm='trigger functions can only be called as triggers' then
|
|
43
|
+
return null;
|
|
44
|
+
end if;
|
|
45
|
+
return sqlstate || ':' || sqlerrm;
|
|
46
|
+
end $body$;
|
|
47
|
+
select is((select case when count(*)=0 then 'event-trigger control missing'
|
|
48
|
+
else string_agg(format('%s: %s',f.oid::regprocedure,x.problem), ', ' order by f.oid::regprocedure::text)
|
|
49
|
+
filter (where x.problem is not null) end
|
|
50
|
+
from pg_proc f join pg_namespace n on n.oid=f.pronamespace
|
|
51
|
+
cross join lateral pg_temp.xb_event_trigger_call_problem(f.oid) x(problem)
|
|
52
|
+
where f.prokind='f' and f.prorettype='event_trigger'::regtype
|
|
53
|
+
and n.nspname not in ('pg_catalog','information_schema','agent_board_test') and n.nspname not like 'pg_%'
|
|
54
|
+
and not exists (select 1 from pg_depend d where d.classid='pg_proc'::regclass and d.objid=f.oid and d.deptype='e')),
|
|
55
|
+
null::text, 'XB DDL event-trigger handlers cannot be invoked as ordinary RPCs');
|
|
56
|
+
-- Enumerate from constraints, not a hand-written list. The receipt exemption is
|
|
57
|
+
-- permitted only while its complete catalog shape has no caller-content columns.
|
|
58
|
+
-- Each relation must carry the exact closed predicate, not merely its name: a
|
|
59
|
+
-- name scan survived `... where thread_id = p_thread_id and false` (#707 B1).
|
|
60
|
+
-- The exemption is positive: read_receipts must itself be a direct thread FK.
|
|
61
|
+
select is(
|
|
62
|
+
(select string_agg(problem, ', ' order by problem) from (
|
|
63
|
+
select 'agent_board.read_receipts: exemption is not a direct thread FK' as problem
|
|
64
|
+
where not exists (select 1 from pg_constraint where contype='f'
|
|
65
|
+
and conrelid='agent_board.read_receipts'::regclass and confrelid='agent_board.threads'::regclass)
|
|
66
|
+
union all
|
|
67
|
+
select 'direct thread-FK history census is empty'
|
|
68
|
+
where not exists (select 1 from pg_constraint where contype='f' and confrelid='agent_board.threads'::regclass
|
|
69
|
+
and conrelid <> 'agent_board.read_receipts'::regclass)
|
|
70
|
+
union all
|
|
71
|
+
select format('%I.%I', n.nspname, c.relname)
|
|
72
|
+
from (select distinct conrelid from pg_constraint
|
|
73
|
+
where contype='f' and confrelid='agent_board.threads'::regclass
|
|
74
|
+
and conrelid <> 'agent_board.read_receipts'::regclass) fk
|
|
75
|
+
join pg_class c on c.oid=fk.conrelid
|
|
76
|
+
join pg_namespace n on n.oid=c.relnamespace
|
|
77
|
+
where agent_board_test.stripped_body('agent_board._associate_empty_thread(uuid,uuid)'::regprocedure)
|
|
78
|
+
!~ agent_board_test.re(agent_board_test.ci_re('exists') || '\s*\(\s*' || agent_board_test.ci_re('select')
|
|
79
|
+
|| '\s+1\s+' || agent_board_test.ci_re('from') || '\s+' || agent_board_test.qname_frag(n.nspname||'.'||c.relname,false)
|
|
80
|
+
|| '\s+' || agent_board_test.ci_re('where') || '\s+' || agent_board_test.col_ref_frag('thread_id')
|
|
81
|
+
|| '\s*=\s*' || agent_board_test.ident_re('p_thread_id') || '\s*\)')) p),
|
|
82
|
+
null::text, 'XB every direct thread-FK relation has its exact thread-history predicate');
|
|
83
|
+
select is(
|
|
84
|
+
(select array_agg(a.attname::text order by a.attnum) from pg_attribute a
|
|
85
|
+
where a.attrelid='agent_board.read_receipts'::regclass and a.attnum>0 and not a.attisdropped),
|
|
86
|
+
array['thread_id','agent_id','last_read_message_sequence','last_read_at']::text[],
|
|
87
|
+
'XB receipt exemption has only subject, bounded sequence and timestamp columns');
|
|
88
|
+
-- Every catalog-discovered one-hop edge must be an enforced, validated single-column
|
|
89
|
+
-- FK to its parent. This pins parent existence, not the remaining writer closure.
|
|
90
|
+
select is(
|
|
91
|
+
(with direct as (select distinct conrelid from pg_constraint where contype='f' and confrelid='agent_board.threads'::regclass), edges as (
|
|
92
|
+
select f.oid, f.conrelid, f.conname, f.convalidated, cardinality(f.conkey) as nkeys
|
|
93
|
+
from pg_constraint f join direct d on d.conrelid=f.confrelid where f.contype='f')
|
|
94
|
+
select string_agg(format('%s.%I',conrelid::regclass,conname), ', ' order by conrelid::regclass::text, conname)
|
|
95
|
+
from edges where not convalidated or nkeys<>1),
|
|
96
|
+
null::text, 'XB one-hop parent-existence FKs are validated and single-column');
|
|
97
|
+
-- Reviewed one-hop FK identities, derived from the fresh pg_constraint census.
|
|
98
|
+
-- Each entry is positively checked against a validated FK below. This records
|
|
99
|
+
-- relational parent existence only; parent history and writer behavior still
|
|
100
|
+
-- require the separate witnesses and closure proof.
|
|
101
|
+
create temp table xb_onehop_reviewed(child oid,parent oid, constraint_name text, primary key(child,parent,constraint_name)) on commit drop;
|
|
102
|
+
insert into xb_onehop_reviewed values
|
|
103
|
+
('agent_board.artifacts'::regclass,'agent_board.messages'::regclass,'artifacts_message_id_fkey'),
|
|
104
|
+
('agent_board.artifacts'::regclass,'agent_board.task_events'::regclass,'artifacts_task_event_id_fkey'),
|
|
105
|
+
('agent_board.artifacts'::regclass,'agent_board.tasks'::regclass,'artifacts_task_id_fkey'),
|
|
106
|
+
('agent_board.callback_outbox_events'::regclass,'agent_board.messages'::regclass,'callback_outbox_events_source_message_id_fkey'),
|
|
107
|
+
('agent_board.callback_outbox_events'::regclass,'agent_board.tasks'::regclass,'callback_outbox_events_source_task_id_fkey'),
|
|
108
|
+
('agent_board.claims'::regclass,'agent_board.tasks'::regclass,'claims_task_id_fkey'),
|
|
109
|
+
('agent_board.conflicts'::regclass,'agent_board.tasks'::regclass,'conflicts_task_id_fkey'),
|
|
110
|
+
('agent_board.delegations'::regclass,'agent_board.tasks'::regclass,'delegations_child_task_id_fkey'),
|
|
111
|
+
('agent_board.delegations'::regclass,'agent_board.tasks'::regclass,'delegations_parent_task_id_fkey'),
|
|
112
|
+
('agent_board.delivery_recipients'::regclass,'agent_board.messages'::regclass,'delivery_recipients_source_message_id_fkey'),
|
|
113
|
+
('agent_board.delivery_recipients'::regclass,'agent_board.tasks'::regclass,'delivery_recipients_source_task_id_fkey'),
|
|
114
|
+
('agent_board.escalations'::regclass,'agent_board.tasks'::regclass,'escalations_task_id_fkey'),
|
|
115
|
+
('agent_board.inbox_items'::regclass,'agent_board.messages'::regclass,'inbox_items_source_message_id_fkey'),
|
|
116
|
+
('agent_board.inbox_items'::regclass,'agent_board.tasks'::regclass,'inbox_items_source_task_id_fkey'),
|
|
117
|
+
('agent_board.messages'::regclass,'agent_board.tasks'::regclass,'messages_task_id_fkey'),
|
|
118
|
+
('agent_board.signature_evidence'::regclass,'agent_board.artifacts'::regclass,'signature_evidence_source_artifact_id_fkey'),
|
|
119
|
+
('agent_board.signature_evidence'::regclass,'agent_board.messages'::regclass,'signature_evidence_source_message_id_fkey'),
|
|
120
|
+
('agent_board.signature_evidence'::regclass,'agent_board.task_events'::regclass,'signature_evidence_source_task_event_id_fkey'),
|
|
121
|
+
('agent_board.task_events'::regclass,'agent_board.tasks'::regclass,'task_events_task_id_fkey'),
|
|
122
|
+
('agent_board.tasks'::regclass,'agent_board.tasks'::regclass,'tasks_parent_task_id_fkey'),
|
|
123
|
+
('agent_board.tasks'::regclass,'agent_board.tasks'::regclass,'tasks_root_task_id_fkey');
|
|
124
|
+
create temp table xb_onehop_actual on commit drop as
|
|
125
|
+
with direct as (select distinct conrelid from pg_constraint where contype='f' and confrelid='agent_board.threads'::regclass)
|
|
126
|
+
select f.conrelid as child,f.confrelid as parent,f.conname::text as constraint_name,f.convalidated
|
|
127
|
+
from pg_constraint f join direct d on d.conrelid=f.confrelid where f.contype='f';
|
|
128
|
+
select is((select string_agg(format('%s -> %s (%s)',a.child::regclass,a.parent::regclass,a.constraint_name), ', '
|
|
129
|
+
order by a.child::regclass::text,a.parent::regclass::text,a.constraint_name)
|
|
130
|
+
from xb_onehop_actual a where not exists (select 1 from xb_onehop_reviewed r
|
|
131
|
+
where r.child=a.child and r.parent=a.parent and r.constraint_name=a.constraint_name)),
|
|
132
|
+
null::text, 'XB unreviewed one-hop FK names itself');
|
|
133
|
+
select is((select string_agg(format('%s -> %s (%s)',r.child::regclass,r.parent::regclass,r.constraint_name), ', '
|
|
134
|
+
order by r.child::regclass::text,r.parent::regclass::text,r.constraint_name)
|
|
135
|
+
from xb_onehop_reviewed r where not exists (select 1 from xb_onehop_actual a
|
|
136
|
+
where a.child=r.child and a.parent=r.parent and a.constraint_name=r.constraint_name and a.convalidated)),
|
|
137
|
+
null::text, 'XB each reviewed one-hop entry is a real validated FK to its claimed parent');
|
|
138
|
+
|
|
139
|
+
-- Every actual one-hop edge points to a direct thread-FK parent. Bind each
|
|
140
|
+
-- discovered parent to the actual association predicate; the boundary and
|
|
141
|
+
-- writer behavior tests separately prove task, message, event and artifact
|
|
142
|
+
-- predicates with isolated history and omission mutations. This does not claim
|
|
143
|
+
-- that parent existence alone proves each descendant's anchor semantics.
|
|
144
|
+
select is((select string_agg(parent::regclass::text, ', ' order by parent::regclass::text)
|
|
145
|
+
from (select distinct parent from xb_onehop_actual) parents
|
|
146
|
+
join pg_class c on c.oid=parents.parent join pg_namespace n on n.oid=c.relnamespace
|
|
147
|
+
where agent_board_test.stripped_body('agent_board._associate_empty_thread(uuid,uuid)'::regprocedure)
|
|
148
|
+
!~ agent_board_test.re(agent_board_test.ci_re('exists') || '\s*\(\s*' || agent_board_test.ci_re('select')
|
|
149
|
+
|| '\s+1\s+' || agent_board_test.ci_re('from') || '\s+' || agent_board_test.qname_frag(n.nspname||'.'||c.relname,false)
|
|
150
|
+
|| '\s+' || agent_board_test.ci_re('where') || '\s+' || agent_board_test.col_ref_frag('thread_id')
|
|
151
|
+
|| '\s*=\s*' || agent_board_test.ident_re('p_thread_id') || '\s*\)')),
|
|
152
|
+
null::text, 'XB every discovered one-hop parent has its own thread-history predicate');
|
|
153
|
+
|
|
154
|
+
-- A bounded production PL/pgSQL call graph, built from catalog identities and
|
|
155
|
+
-- parser-resolved OIDs. SQL-language and trigger edges are checked separately
|
|
156
|
+
-- below; application procedures fail closed above. Parser failures and dynamic
|
|
157
|
+
-- SQL remain visible by full identity instead of being treated as safe.
|
|
158
|
+
create function pg_temp.xb_dependencies(p_fn oid) returns table(dep_oid oid, problem text) language plpgsql as $body$
|
|
159
|
+
begin
|
|
160
|
+
return query select d.oid::oid, null::text
|
|
161
|
+
from extensions.plpgsql_show_dependency_tb(p_fn::regprocedure,0::oid) d where d.type='FUNCTION';
|
|
162
|
+
exception when others then
|
|
163
|
+
return query select null::oid, sqlstate || ':' || sqlerrm;
|
|
164
|
+
end $body$;
|
|
165
|
+
create temp table xb_plpgsql on commit drop as
|
|
166
|
+
select p.oid, format('%I.%I(%s)',n.nspname,p.proname,oidvectortypes(p.proargtypes)) as identity,
|
|
167
|
+
(has_function_privilege('authenticated',p.oid,'EXECUTE') or has_function_privilege('service_role',p.oid,'EXECUTE')
|
|
168
|
+
or has_function_privilege('anon',p.oid,'EXECUTE') or has_function_privilege('agent_board_delegate',p.oid,'EXECUTE')) as root,
|
|
169
|
+
agent_board_test.stripped_body(p.oid) ~ agent_board_test.re('\m' || agent_board_test.ci_re('execute') || '\M') as opaque
|
|
170
|
+
from pg_proc p join pg_namespace n on n.oid=p.pronamespace join pg_language l on l.oid=p.prolang
|
|
171
|
+
where p.prokind='f' and l.lanname='plpgsql' and p.prorettype not in ('trigger'::regtype,'event_trigger'::regtype)
|
|
172
|
+
and n.nspname not in ('pg_catalog','information_schema','agent_board_test') and n.nspname not like 'pg_%'
|
|
173
|
+
and not exists (select 1 from pg_depend d where d.classid='pg_proc'::regclass and d.objid=p.oid and d.deptype='e');
|
|
174
|
+
create temp table xb_edges on commit drop as
|
|
175
|
+
select f.oid as source,d.dep_oid,d.problem from xb_plpgsql f cross join lateral pg_temp.xb_dependencies(f.oid) d;
|
|
176
|
+
create temp table xb_reachable on commit drop as
|
|
177
|
+
with recursive reach(root,oid,path) as (
|
|
178
|
+
select oid,oid,array[oid] from xb_plpgsql where root
|
|
179
|
+
union all select r.root,e.dep_oid,r.path||e.dep_oid from reach r join xb_edges e on e.source=r.oid
|
|
180
|
+
join xb_plpgsql f on f.oid=e.dep_oid where not e.dep_oid=any(r.path)
|
|
181
|
+
) select distinct oid from reach;
|
|
182
|
+
select ok((select count(*)>0 from xb_plpgsql where root) and (select count(*)>0 from xb_edges where dep_oid is not null),
|
|
183
|
+
'XB PL/pgSQL callable roots and parser edges are nonempty');
|
|
184
|
+
select is((select string_agg(distinct f.identity || ':' || e.problem, ', ' order by f.identity || ':' || e.problem)
|
|
185
|
+
from xb_plpgsql f join xb_edges e on e.source=f.oid where e.problem is not null),
|
|
186
|
+
null::text, 'XB PL/pgSQL parser failures name themselves even on internal functions');
|
|
187
|
+
-- The exact reviewed body is bounded to four UPDATE templates over eight
|
|
188
|
+
-- literal validator targets. Its real effects on all eight targets, including
|
|
189
|
+
-- every row-level side write and anchor, are exercised in the companion
|
|
190
|
+
-- agent_board_667_retention test. The body binding forces a fresh disposition
|
|
191
|
+
-- for any new template, target, call or expression; it is not behavioral proof.
|
|
192
|
+
select is(md5(pg_get_functiondef('agent_board.mark_retention_metadata(text,timestamptz,uuid,text)'::regprocedure)),
|
|
193
|
+
'ce1b6fb7c62f4e4b4110fcf09f18211e', 'XB retention reviewed validator and UPDATE templates remain exact');
|
|
194
|
+
-- Conversion templates select typed scalar literals, not relations. Their type,
|
|
195
|
+
-- operator, range and text-search dispatch must stay inside the finite primitive
|
|
196
|
+
-- reference; custom or drifted callbacks close admission. Other platform dynamic
|
|
197
|
+
-- SQL (including apply_rls and Storage) remains unresolved by this admission.
|
|
198
|
+
\ir fixtures/agent_board_667_conversion_guard.sql
|
|
199
|
+
select is((select string_agg(identity||': '||problem, ', ' order by identity,problem) from (
|
|
200
|
+
select * from pg_temp.xb_cf_problems() union all select * from pg_temp.xb_cf_wrapper_problems()) p),
|
|
201
|
+
null::text,'XB native conversion dispatch and metadata require exact disposition')
|
|
202
|
+
where exists(select 1 from pg_temp.xb_cf_wrappers where to_regprocedure(identity) is not null);
|
|
203
|
+
select * from skip('native conversion wrappers absent; no platform disposition credit',1)
|
|
204
|
+
where not exists(select 1 from pg_temp.xb_cf_wrappers where to_regprocedure(identity) is not null);
|
|
205
|
+
create temp table xb_native_conversion_admitted on commit drop as select fn from pg_temp.xb_cf_admitted();
|
|
206
|
+
-- The two native senders have a separate, positive destination/effect class.
|
|
207
|
+
-- Storage readers and apply_rls require their own positive proofs.
|
|
208
|
+
\ir fixtures/agent_board_667_sender_guard.sql
|
|
209
|
+
select is((select string_agg(identity||': '||problem, ', ' order by identity,problem) from pg_temp.xb_sf_problems()),
|
|
210
|
+
null::text,'XB native sender destination and effects require exact disposition')
|
|
211
|
+
where exists(select 1 from pg_temp.xb_sf_reference where kind='wrapper' and to_regprocedure(identity) is not null);
|
|
212
|
+
select * from skip('native senders absent; no platform sender disposition credit',1)
|
|
213
|
+
where not exists(select 1 from pg_temp.xb_sf_reference where kind='wrapper' and to_regprocedure(identity) is not null);
|
|
214
|
+
insert into xb_native_conversion_admitted select fn from pg_temp.xb_sf_admitted();
|
|
215
|
+
-- One Storage SELECT template has its own target, resolution, aggregate and
|
|
216
|
+
-- callback proof. Each other reader requires a separate disposition.
|
|
217
|
+
\ir fixtures/agent_board_667_timestamp_guard.sql
|
|
218
|
+
select is((select string_agg(identity||': '||problem, ', ' order by identity,problem) from pg_temp.xb_st_problems()),
|
|
219
|
+
null::text,'XB native timestamp reader and effects require exact disposition')
|
|
220
|
+
where to_regprocedure('storage.search_by_timestamp(text,text,integer,integer,text,text,text,text,text,text,text)') is not null;
|
|
221
|
+
select * from skip('native timestamp reader absent; no native Storage disposition credit',1)
|
|
222
|
+
where to_regprocedure('storage.search_by_timestamp(text,text,integer,integer,text,text,text,text,text,text,text)') is null;
|
|
223
|
+
insert into xb_native_conversion_admitted select fn from pg_temp.xb_st_admitted();
|
|
224
|
+
-- Search has separate source-derived constructor, expression and callback proof.
|
|
225
|
+
-- The delimiter listing and apply_rls have separate proofs below.
|
|
226
|
+
\ir fixtures/agent_board_667_search_guard.sql
|
|
227
|
+
select is((select string_agg(identity||': '||problem, ', ' order by identity,problem) from pg_temp.xb_ss_problems()),
|
|
228
|
+
null::text,'XB native search constructor and effect class require exact disposition')
|
|
229
|
+
where to_regprocedure('storage.search(text,text,integer,integer,integer,text,text,text,text,text)') is not null;
|
|
230
|
+
select * from skip('native search absent; no native search disposition credit',1)
|
|
231
|
+
where to_regprocedure('storage.search(text,text,integer,integer,integer,text,text,text,text,text)') is null;
|
|
232
|
+
insert into xb_native_conversion_admitted select fn from pg_temp.xb_ss_admitted();
|
|
233
|
+
-- Delimiter listing is a distinct source/record/dispatch effect class.
|
|
234
|
+
\ir fixtures/agent_board_667_delimiter_guard.sql
|
|
235
|
+
select is((select string_agg(identity||': '||problem, ', ' order by identity,problem) from pg_temp.xb_sd_problems()),
|
|
236
|
+
null::text,'XB native delimiter constructor and effect class require exact disposition')
|
|
237
|
+
where to_regprocedure('storage.list_objects_with_delimiter(text,text,text,integer,text,text,text,text,text,timestamp with time zone,text)') is not null;
|
|
238
|
+
select * from skip('native delimiter absent; no native delimiter disposition credit',1)
|
|
239
|
+
where to_regprocedure('storage.list_objects_with_delimiter(text,text,text,integer,text,text,text,text,text,timestamp with time zone,text)') is null;
|
|
240
|
+
insert into xb_native_conversion_admitted select fn from pg_temp.xb_sd_admitted();
|
|
241
|
+
-- apply_rls prepares a SELECT over a caller-selected row-security relation and
|
|
242
|
+
-- executes it as each subscriber role. Its target, policy, helper-body and
|
|
243
|
+
-- built-in closure is separate; same-named relations close search-path redirection.
|
|
244
|
+
\ir fixtures/agent_board_667_apply_rls_guard.sql
|
|
245
|
+
select is((select string_agg(identity||': '||problem, ', ' order by identity,problem) from pg_temp.xb_ar_problems()),
|
|
246
|
+
null::text,'XB native apply_rls target, policy and helper closure require exact disposition')
|
|
247
|
+
where to_regprocedure('realtime.apply_rls(jsonb,integer)') is not null;
|
|
248
|
+
select * from skip('native apply_rls absent; no native apply_rls disposition credit',1)
|
|
249
|
+
where to_regprocedure('realtime.apply_rls(jsonb,integer)') is null;
|
|
250
|
+
insert into xb_native_conversion_admitted select fn from pg_temp.xb_ar_admitted();
|
|
251
|
+
|
|
252
|
+
select is((select string_agg(f.identity, ', ' order by f.identity) from xb_plpgsql f
|
|
253
|
+
where f.opaque and f.oid <> 'agent_board.mark_retention_metadata(text,timestamptz,uuid,text)'::regprocedure
|
|
254
|
+
and not exists(select 1 from xb_native_conversion_admitted a where a.fn=f.oid)),
|
|
255
|
+
null::text, 'XB other opaque PL/pgSQL identities require disposition');
|
|
256
|
+
-- Failure evidence only. Keep each unresolved definition on one JSON-escaped
|
|
257
|
+
-- diagnostic line so CI records its exact body and metadata without accepting it.
|
|
258
|
+
select diag(' have: OPAQUE_DEFINITION ' || jsonb_build_object(
|
|
259
|
+
'identity',f.identity,'definition',pg_get_functiondef(f.oid),
|
|
260
|
+
'definition_md5',md5(pg_get_functiondef(f.oid)),
|
|
261
|
+
'owner',pg_get_userbyid(p.proowner),'acl',p.proacl,
|
|
262
|
+
'volatility',p.provolatile,'security_definer',p.prosecdef)::text)
|
|
263
|
+
from xb_plpgsql f join pg_proc p on p.oid=f.oid
|
|
264
|
+
where f.opaque and f.oid <> 'agent_board.mark_retention_metadata(text,timestamptz,uuid,text)'::regprocedure
|
|
265
|
+
and not exists(select 1 from xb_native_conversion_admitted a where a.fn=f.oid)
|
|
266
|
+
order by f.identity;
|
|
267
|
+
-- source-scan: raw-subject the literal validator values are data; the exact definition binding above prevents comments or an alternate body from satisfying this extraction
|
|
268
|
+
select is((select array_agg(m[1] order by m[1]) from regexp_matches(
|
|
269
|
+
substring(pg_get_functiondef('agent_board.mark_retention_metadata(text,timestamptz,uuid,text)'::regprocedure)
|
|
270
|
+
from 'if p_table_name not in \((.*?)\) then'), '''([a-z_]+)''', 'g') m),
|
|
271
|
+
array['board_memberships','callback_delivery_attempts','callback_outbox_events','delivery_recipients',
|
|
272
|
+
'inbox_items','messages','presence','task_events']::text[], 'XB retention validator admits exactly eight named targets');
|
|
273
|
+
select is((with direct as (select distinct conrelid from pg_constraint where contype='f' and confrelid='agent_board.threads'::regclass),
|
|
274
|
+
onehop as (select f.conrelid from pg_constraint f join direct d on d.conrelid=f.confrelid where f.contype='f'),
|
|
275
|
+
targets as (select c.oid,c.relname from pg_class c join pg_namespace n on n.oid=c.relnamespace
|
|
276
|
+
where n.nspname='agent_board' and c.relname=any(array['messages','task_events','presence','callback_outbox_events',
|
|
277
|
+
'callback_delivery_attempts','inbox_items','delivery_recipients','board_memberships']))
|
|
278
|
+
select string_agg(relname || ':' || case when oid in (select conrelid from direct) then 'direct'
|
|
279
|
+
when oid in (select conrelid from onehop) then 'onehop' else 'outside' end, ', ' order by relname) from targets),
|
|
280
|
+
'board_memberships:outside, callback_delivery_attempts:outside, callback_outbox_events:onehop, delivery_recipients:onehop, inbox_items:onehop, messages:direct, presence:direct, task_events:direct',
|
|
281
|
+
'XB retention targets are related to the actual history FK graph');
|
|
282
|
+
|
|
283
|
+
-- Additional fail-closed entry-point checks. The source matcher below recognises
|
|
284
|
+
-- agent_board-qualified or bare relation writes, so a discovered history relation
|
|
285
|
+
-- in any other namespace is itself an unresolved named finding. SQL bodies are
|
|
286
|
+
-- scanned conservatively against every catalog-derived writer name; a matching
|
|
287
|
+
-- route is reported for behavioral disposition, never assumed safe. This does
|
|
288
|
+
-- not yet resolve arbitrary SQL calls or prove every writer's lock behavior.
|
|
289
|
+
create temp table xb_history_relations on commit drop as
|
|
290
|
+
with direct as (select distinct conrelid from pg_constraint where contype='f' and confrelid='agent_board.threads'::regclass)
|
|
291
|
+
select conrelid as relid from direct
|
|
292
|
+
union select f.conrelid from pg_constraint f join direct d on d.conrelid=f.confrelid where f.contype='f';
|
|
293
|
+
select is((select string_agg(r.relid::regclass::text, ', ' order by r.relid::regclass::text)
|
|
294
|
+
from xb_history_relations r join pg_class c on c.oid=r.relid join pg_namespace n on n.oid=c.relnamespace
|
|
295
|
+
where n.nspname<>'agent_board'), null::text, 'XB history relations outside the supported matcher schema name themselves');
|
|
296
|
+
select is((select string_agg(format('%s.%I -> %s',t.tgrelid::regclass,t.tgname,t.tgfoid::regprocedure), ', '
|
|
297
|
+
order by t.tgrelid::regclass::text,t.tgname)
|
|
298
|
+
from pg_trigger t join xb_history_relations r on r.relid=t.tgrelid where not t.tgisinternal),
|
|
299
|
+
null::text, 'XB DML trigger entry points on history relations name themselves');
|
|
300
|
+
select is((select string_agg(format('%s.%I',rw.ev_class::regclass,rw.rulename), ', ' order by rw.ev_class::regclass::text,rw.rulename)
|
|
301
|
+
from pg_rewrite rw join xb_history_relations r on r.relid=rw.ev_class where rw.ev_type<>'1'),
|
|
302
|
+
null::text, 'XB DML rule entry points on history relations name themselves');
|
|
303
|
+
-- No non-extension application function in an unsupported language is silently
|
|
304
|
+
-- omitted from the SQL/PL/pgSQL graph. It must be named and disposed first.
|
|
305
|
+
select is((select string_agg(format('%s [%s]',p.oid::regprocedure,l.lanname), ', '
|
|
306
|
+
order by p.oid::regprocedure::text)
|
|
307
|
+
from pg_proc p join pg_namespace n on n.oid=p.pronamespace join pg_language l on l.oid=p.prolang
|
|
308
|
+
where p.prokind='f' and p.prorettype<>'trigger'::regtype and l.lanname not in ('sql','plpgsql')
|
|
309
|
+
and n.nspname not in ('pg_catalog','information_schema','agent_board_test') and n.nspname not like 'pg_%'
|
|
310
|
+
and not exists (select 1 from pg_depend d where d.classid='pg_proc'::regclass and d.objid=p.oid and d.deptype='e')),
|
|
311
|
+
null::text, 'XB unsupported application function languages name themselves');
|
|
312
|
+
create temp table xb_routines on commit drop as
|
|
313
|
+
select p.oid,n.nspname,p.proname,l.lanname,agent_board_test.stripped_body(p.oid) as body,
|
|
314
|
+
(has_function_privilege('authenticated',p.oid,'EXECUTE') or has_function_privilege('service_role',p.oid,'EXECUTE')
|
|
315
|
+
or has_function_privilege('anon',p.oid,'EXECUTE') or has_function_privilege('agent_board_delegate',p.oid,'EXECUTE')) as callable
|
|
316
|
+
from pg_proc p join pg_namespace n on n.oid=p.pronamespace join pg_language l on l.oid=p.prolang
|
|
317
|
+
where p.prokind='f' and p.prorettype<>'trigger'::regtype and l.lanname in ('sql','plpgsql')
|
|
318
|
+
and n.nspname not in ('pg_catalog','information_schema','agent_board_test') and n.nspname not like 'pg_%'
|
|
319
|
+
and not exists (select 1 from pg_depend d where d.classid='pg_proc'::regclass and d.objid=p.oid and d.deptype='e');
|
|
320
|
+
-- Enumerate direct thread-FK INSERT sites from the live catalog and source,
|
|
321
|
+
-- then compare them to independently reviewed exact signatures and relations.
|
|
322
|
+
-- The mechanism checks below are structural prerequisites, coupled to the
|
|
323
|
+
-- focused behavior/mutation tests and the call-edge assertions below.
|
|
324
|
+
create temp table xb_direct_insert_actual on commit drop as
|
|
325
|
+
select distinct f.oid, d.relid from xb_routines f cross join lateral (
|
|
326
|
+
select distinct fk.conrelid as relid,c.relname from pg_constraint fk join pg_class c on c.oid=fk.conrelid
|
|
327
|
+
where fk.contype='f' and fk.confrelid='agent_board.threads'::regclass) d
|
|
328
|
+
where f.body ~ agent_board_test.stmt_re('insert into',d.relname);
|
|
329
|
+
create temp table xb_direct_insert_reviewed(fn oid,relid oid,mechanism text,primary key(fn,relid)) on commit drop;
|
|
330
|
+
insert into xb_direct_insert_reviewed values
|
|
331
|
+
('agent_board.heartbeat(uuid,uuid,text,text,interval,jsonb)'::regprocedure,'agent_board.presence'::regclass,'locked_post'),
|
|
332
|
+
('agent_board._insert_thread_message(uuid,uuid,text,text,uuid,jsonb)'::regprocedure,'agent_board.messages'::regclass,'message_sequence'),
|
|
333
|
+
('agent_board.create_task(uuid,uuid,text,text,uuid,uuid,text,text,jsonb)'::regprocedure,'agent_board.tasks'::regclass,'locked_post'),
|
|
334
|
+
('agent_board.create_task(uuid,uuid,text,text,uuid,uuid,text,text,jsonb)'::regprocedure,'agent_board.task_events'::regclass,'locked_post'),
|
|
335
|
+
('agent_board.acquire_claim(uuid,text,text,timestamptz,uuid,uuid,jsonb)'::regprocedure,'agent_board.claims'::regclass,'locked_post'),
|
|
336
|
+
('agent_board.mark_read(uuid,uuid,bigint)'::regprocedure,'agent_board.read_receipts'::regclass,'receipt'),
|
|
337
|
+
('agent_board.add_artifact(uuid,text,text,text,uuid,uuid,uuid,uuid,jsonb)'::regprocedure,'agent_board.artifacts'::regclass,'locked_post'),
|
|
338
|
+
('agent_board.raise_conflict(uuid,uuid,text,text,text,uuid,jsonb)'::regprocedure,'agent_board.conflicts'::regclass,'locked_post'),
|
|
339
|
+
('agent_board.raise_escalation(uuid,uuid,text,text,text,uuid,uuid,jsonb)'::regprocedure,'agent_board.escalations'::regclass,'locked_post'),
|
|
340
|
+
('agent_board._append_task_event(text,uuid,uuid,text,jsonb,text,uuid,timestamptz)'::regprocedure,'agent_board.task_events'::regclass,'task_parent');
|
|
341
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
342
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
343
|
+
from xb_direct_insert_actual a join pg_proc p on p.oid=a.oid join pg_namespace pn on pn.oid=p.pronamespace
|
|
344
|
+
join pg_class c on c.oid=a.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
345
|
+
where not exists (select 1 from xb_direct_insert_reviewed r where r.fn=a.oid and r.relid=a.relid)),
|
|
346
|
+
null::text, 'XB unreviewed direct thread-history inserters name their full identities');
|
|
347
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
348
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
349
|
+
from xb_direct_insert_reviewed r join pg_proc p on p.oid=r.fn join pg_namespace pn on pn.oid=p.pronamespace
|
|
350
|
+
join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
351
|
+
where not exists (select 1 from xb_direct_insert_actual a where a.oid=r.fn and a.relid=r.relid)),
|
|
352
|
+
null::text, 'XB each reviewed direct inserter actually writes its stated relation');
|
|
353
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I [%s]',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname,r.mechanism), ', '
|
|
354
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
355
|
+
from xb_direct_insert_reviewed r join xb_routines f on f.oid=r.fn
|
|
356
|
+
join pg_proc p on p.oid=r.fn join pg_namespace pn on pn.oid=p.pronamespace
|
|
357
|
+
join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
358
|
+
where not case r.mechanism
|
|
359
|
+
when 'locked_post' then f.body ~ agent_board_test.row_lock_re('threads')
|
|
360
|
+
and f.body ~ agent_board_test.call_re('_current_agent_can_post_to_thread',true)
|
|
361
|
+
when 'message_sequence' then f.body ~ agent_board_test.call_re('_next_thread_message_sequence',true)
|
|
362
|
+
and f.body ~ agent_board_test.call_re('_current_agent_can_post_to_thread',true)
|
|
363
|
+
when 'receipt' then f.body ~ agent_board_test.call_re('_agent_can_access_thread',true)
|
|
364
|
+
when 'task_parent' then f.body ~ agent_board_test.row_lock_re('threads')
|
|
365
|
+
and f.body ~ agent_board_test.call_re('_current_agent_can_mutate_task',true)
|
|
366
|
+
and exists(select 1 from xb_onehop_actual a where a.child='agent_board.task_events'::regclass
|
|
367
|
+
and a.parent='agent_board.tasks'::regclass and a.constraint_name='task_events_task_id_fkey' and a.convalidated)
|
|
368
|
+
else false end), null::text, 'XB each direct inserter has its positively checked mechanism prerequisites');
|
|
369
|
+
-- For each conditional first-history writer, bind the thread row being
|
|
370
|
+
-- locked to the same argument rechecked for posting before that relation's
|
|
371
|
+
-- INSERT. Runtime race witnesses separately prove lock/wait behavior.
|
|
372
|
+
create temp table xb_locked_writer_arguments(fn oid primary key,thread_arg text not null) on commit drop;
|
|
373
|
+
insert into xb_locked_writer_arguments values
|
|
374
|
+
('agent_board.heartbeat(uuid,uuid,text,text,interval,jsonb)'::regprocedure,'v_effective_thread'),
|
|
375
|
+
('agent_board.create_task(uuid,uuid,text,text,uuid,uuid,text,text,jsonb)'::regprocedure,'p_thread_id'),
|
|
376
|
+
('agent_board.acquire_claim(uuid,text,text,timestamptz,uuid,uuid,jsonb)'::regprocedure,'v_thread_id'),
|
|
377
|
+
('agent_board.add_artifact(uuid,text,text,text,uuid,uuid,uuid,uuid,jsonb)'::regprocedure,'v_anchor_thread'),
|
|
378
|
+
('agent_board.raise_conflict(uuid,uuid,text,text,text,uuid,jsonb)'::regprocedure,'p_thread_id'),
|
|
379
|
+
('agent_board.raise_escalation(uuid,uuid,text,text,text,uuid,uuid,jsonb)'::regprocedure,'p_thread_id');
|
|
380
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
381
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
382
|
+
from xb_direct_insert_reviewed r join xb_locked_writer_arguments a on a.fn=r.fn join xb_routines f on f.oid=r.fn
|
|
383
|
+
join pg_proc p on p.oid=r.fn join pg_namespace pn on pn.oid=p.pronamespace
|
|
384
|
+
join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
385
|
+
where r.mechanism='locked_post' and f.body !~ agent_board_test.re(agent_board_test.qname_frag('threads',true)
|
|
386
|
+
|| agent_board_test.same_stmt() || agent_board_test.col_ref_frag('id') || '\s*=\s*' || agent_board_test.ident_re(a.thread_arg)
|
|
387
|
+
|| agent_board_test.same_stmt() || '\m' || agent_board_test.ci_re('for no key update') || '[^;]*;[\s\S]*?'
|
|
388
|
+
|| agent_board_test.call_frag('_current_agent_can_post_to_thread',true) || '\s*' || agent_board_test.ident_re(a.thread_arg)
|
|
389
|
+
|| '\s*\)[\s\S]*?' || agent_board_test.stmt_frag('insert into',c.relname,true))),null::text,
|
|
390
|
+
'XB locked direct writers recheck the locked thread posting gate before insertion');
|
|
391
|
+
-- Materialize source-derived writer pairs once before classifying their
|
|
392
|
+
-- operations. The same catalog population drives both UPDATE classification
|
|
393
|
+
-- and the recursive caller graph below.
|
|
394
|
+
create temp table xb_writer_patterns on commit drop as
|
|
395
|
+
select h.relid, agent_board_test.write_re(c.relname) as pattern
|
|
396
|
+
from xb_history_relations h join pg_class c on c.oid=h.relid;
|
|
397
|
+
create temp table xb_history_writer_pairs on commit drop as
|
|
398
|
+
select distinct f.oid,r.relid from xb_routines f cross join xb_writer_patterns r where f.body ~ r.pattern;
|
|
399
|
+
-- An UPDATE-only path can operate on a pre-existing history row without a
|
|
400
|
+
-- first-association lock only while its catalog-derived parent/thread FK
|
|
401
|
+
-- columns cannot be reassigned and the routine cannot insert or delete that
|
|
402
|
+
-- relation. Enumerate the population independently from the dispositions.
|
|
403
|
+
create temp table xb_update_only_actual on commit drop as
|
|
404
|
+
select distinct f.oid,h.relid from xb_history_writer_pairs h join xb_routines f on f.oid=h.oid
|
|
405
|
+
join pg_class c on c.oid=h.relid
|
|
406
|
+
where f.body ~ agent_board_test.stmt_re('update',c.relname)
|
|
407
|
+
and f.body !~ agent_board_test.stmt_re('insert into',c.relname);
|
|
408
|
+
create temp table xb_update_only_reviewed(fn oid,relid oid,allowed_columns text[] not null,primary key(fn,relid)) on commit drop;
|
|
409
|
+
insert into xb_update_only_reviewed values
|
|
410
|
+
('agent_board._append_task_event(text,uuid,uuid,text,jsonb,text,uuid,timestamptz)'::regprocedure,'agent_board.tasks'::regclass,array['assigned_agent_id','completed_at','status','updated_at']::text[]),
|
|
411
|
+
('agent_board.claim_callback_outbox_events(integer)'::regprocedure,'agent_board.callback_outbox_events'::regclass,array['locked_at','outbox_status','updated_at']::text[]),
|
|
412
|
+
('agent_board.disable_callback_delivery_endpoint(uuid,uuid,text,text,jsonb)'::regprocedure,'agent_board.callback_outbox_events'::regclass,array['last_error_at','locked_at','metadata','outbox_status','updated_at']::text[]),
|
|
413
|
+
('agent_board.record_callback_delivery_retry(uuid,uuid,integer,text,timestamptz,jsonb)'::regprocedure,'agent_board.callback_outbox_events'::regclass,array['available_at','last_error_at','locked_at','metadata','outbox_status','updated_at']::text[]),
|
|
414
|
+
('agent_board.record_callback_delivery_success(uuid,uuid,integer,jsonb)'::regprocedure,'agent_board.callback_outbox_events'::regclass,array['delivered_at','last_error_at','locked_at','metadata','outbox_status','updated_at']::text[]),
|
|
415
|
+
('agent_board.record_callback_delivery_terminal_failure(uuid,uuid,integer,text,jsonb)'::regprocedure,'agent_board.callback_outbox_events'::regclass,array['last_error_at','locked_at','metadata','outbox_status','updated_at']::text[]),
|
|
416
|
+
('agent_board.update_inbox_item_state(uuid,uuid,text,jsonb)'::regprocedure,'agent_board.inbox_items'::regclass,array['action_state','metadata','updated_at']::text[]),
|
|
417
|
+
('agent_board.reclaim_fleet_machine(uuid,uuid,uuid,text,uuid)'::regprocedure,'agent_board.claims'::regclass,array['metadata','released_at','status','updated_at']::text[]),
|
|
418
|
+
('agent_board.release_claim(uuid)'::regprocedure,'agent_board.claims'::regclass,array['released_at','resource_key','status','updated_at']::text[]),
|
|
419
|
+
('agent_board.renew_claim(uuid,timestamptz)'::regprocedure,'agent_board.claims'::regclass,array['lease_expires_at','resource_key','updated_at']::text[]),
|
|
420
|
+
('agent_board.resolve_conflict(uuid,uuid,jsonb)'::regprocedure,'agent_board.conflicts'::regclass,array['details','resolved_at','resolved_by_agent_id','status','updated_at']::text[]),
|
|
421
|
+
('agent_board.resolve_escalation(uuid,uuid,jsonb)'::regprocedure,'agent_board.escalations'::regclass,array['details','resolved_at','resolved_by_agent_id','status','updated_at']::text[]),
|
|
422
|
+
('agent_board.set_board_claim_policy(uuid,text)'::regprocedure,'agent_board.claims'::regclass,array['lease_expires_at','updated_at']::text[]);
|
|
423
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
424
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
425
|
+
from xb_update_only_actual a join pg_proc p on p.oid=a.oid join pg_namespace pn on pn.oid=p.pronamespace
|
|
426
|
+
join pg_class c on c.oid=a.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
427
|
+
where not exists(select 1 from xb_update_only_reviewed r where r.fn=a.oid and r.relid=a.relid)), null::text,
|
|
428
|
+
'XB unreviewed existing-row history writers name their full identities');
|
|
429
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
430
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
431
|
+
from xb_update_only_reviewed r join pg_proc p on p.oid=r.fn join pg_namespace pn on pn.oid=p.pronamespace
|
|
432
|
+
join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
433
|
+
where not exists(select 1 from xb_update_only_actual a where a.oid=r.fn and a.relid=r.relid)), null::text,
|
|
434
|
+
'XB every reviewed existing-row path actually updates its claimed relation');
|
|
435
|
+
select is((select string_agg(distinct format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
436
|
+
order by format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname))
|
|
437
|
+
from xb_update_only_reviewed r join xb_routines f on f.oid=r.fn join pg_proc p on p.oid=r.fn
|
|
438
|
+
join pg_namespace pn on pn.oid=p.pronamespace join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
439
|
+
where f.body ~ agent_board_test.stmt_re('delete from',c.relname)
|
|
440
|
+
or exists(select 1 from pg_constraint fk join lateral unnest(fk.conkey) k(attnum) on true
|
|
441
|
+
join pg_attribute a on a.attrelid=fk.conrelid and a.attnum=k.attnum
|
|
442
|
+
where fk.contype='f' and fk.conrelid=r.relid and (fk.confrelid='agent_board.threads'::regclass
|
|
443
|
+
or fk.confrelid in (select relid from xb_history_relations))
|
|
444
|
+
and f.body ~ agent_board_test.col_write_re(c.relname,a.attname))), null::text,
|
|
445
|
+
'XB existing-row paths neither delete history nor reassign a catalog-derived thread or parent anchor');
|
|
446
|
+
-- Pin the actual assigned columns for each independently discovered update
|
|
447
|
+
-- path, not just the absence of parent-column assignments. A new state effect
|
|
448
|
+
-- therefore requires its own disposition.
|
|
449
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
450
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
451
|
+
from xb_update_only_reviewed r join xb_routines f on f.oid=r.fn join pg_proc p on p.oid=r.fn
|
|
452
|
+
join pg_namespace pn on pn.oid=p.pronamespace join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
453
|
+
where r.allowed_columns is distinct from coalesce((select array_agg(a.attname::text order by a.attname::text)
|
|
454
|
+
from pg_attribute a where a.attrelid=r.relid and a.attnum>0 and not a.attisdropped
|
|
455
|
+
and f.body ~ agent_board_test.col_write_re(c.relname,a.attname)),array[]::text[])),null::text,
|
|
456
|
+
'XB each existing-row writer assigns exactly its reviewed catalog columns');
|
|
457
|
+
-- ON CONFLICT DO UPDATE can mutate an existing row even though the routine
|
|
458
|
+
-- is also an inserter. Enumerate those branches separately and pin the SET
|
|
459
|
+
-- columns; behavioral retry controls prove the two delivery upserts preserve
|
|
460
|
+
-- their original source anchors, while heartbeat remains a locked writer.
|
|
461
|
+
create temp table xb_upsert_actual on commit drop as
|
|
462
|
+
select w.oid,w.relid,m[1] as update_fragment from xb_history_writer_pairs w
|
|
463
|
+
join xb_routines f on f.oid=w.oid join pg_class c on c.oid=w.relid
|
|
464
|
+
cross join lateral regexp_matches(f.body,agent_board_test.re(agent_board_test.ci_re('on conflict')
|
|
465
|
+
|| agent_board_test.same_stmt() || agent_board_test.ci_re('do update') || '\s+'
|
|
466
|
+
|| agent_board_test.ci_re('set') || '([^;]*)'),'g') m
|
|
467
|
+
where f.body ~ agent_board_test.stmt_re('insert into',c.relname);
|
|
468
|
+
create temp table xb_upsert_reviewed(fn oid,relid oid,allowed_columns text[] not null,primary key(fn,relid)) on commit drop;
|
|
469
|
+
insert into xb_upsert_reviewed values
|
|
470
|
+
('agent_board.heartbeat(uuid,uuid,text,text,interval,jsonb)'::regprocedure,'agent_board.presence'::regclass,
|
|
471
|
+
array['activity_summary','availability','expires_at','last_heartbeat_at','metadata','thread_id','updated_at']),
|
|
472
|
+
('agent_board.mark_read(uuid,uuid,bigint)'::regprocedure,'agent_board.read_receipts'::regclass,
|
|
473
|
+
array['last_read_at','last_read_message_sequence']),
|
|
474
|
+
('agent_board._upsert_delivery_recipient(text,uuid,text,uuid,uuid,uuid,uuid,uuid,jsonb)'::regprocedure,'agent_board.delivery_recipients'::regclass,
|
|
475
|
+
array['metadata']),
|
|
476
|
+
('agent_board._upsert_inbox_item(uuid,uuid,uuid,text,text,uuid,uuid,uuid,text,text,text,boolean,boolean,jsonb,jsonb)'::regprocedure,'agent_board.inbox_items'::regclass,
|
|
477
|
+
array['board_id','fetch_descriptor','metadata','preview','priority','subject','updated_at','visible_in_board_feed','visible_in_inbox']);
|
|
478
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
479
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
480
|
+
from xb_upsert_actual a join pg_proc p on p.oid=a.oid join pg_namespace pn on pn.oid=p.pronamespace
|
|
481
|
+
join pg_class c on c.oid=a.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
482
|
+
where not exists(select 1 from xb_upsert_reviewed r where r.fn=a.oid and r.relid=a.relid)),null::text,
|
|
483
|
+
'XB unreviewed history upsert branches name their full identities');
|
|
484
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
485
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
486
|
+
from xb_upsert_reviewed r join pg_proc p on p.oid=r.fn join pg_namespace pn on pn.oid=p.pronamespace
|
|
487
|
+
join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
488
|
+
where (select count(*) from xb_upsert_actual a where a.oid=r.fn and a.relid=r.relid)<>1),null::text,
|
|
489
|
+
'XB each reviewed history upsert has exactly one parsed update branch');
|
|
490
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
491
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
492
|
+
from xb_upsert_reviewed r join xb_upsert_actual u on u.oid=r.fn and u.relid=r.relid
|
|
493
|
+
join pg_proc p on p.oid=r.fn join pg_namespace pn on pn.oid=p.pronamespace
|
|
494
|
+
join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
495
|
+
where r.allowed_columns is distinct from coalesce((select array_agg(a.attname::text order by a.attname::text)
|
|
496
|
+
from pg_attribute a where a.attrelid=r.relid and a.attnum>0 and not a.attisdropped
|
|
497
|
+
and (regexp_match(agent_board_test.stripped_body(r.fn),agent_board_test.re(agent_board_test.ci_re('on conflict')
|
|
498
|
+
|| agent_board_test.same_stmt() || agent_board_test.ci_re('do update') || '\s+'
|
|
499
|
+
|| agent_board_test.ci_re('set') || '([^;]*)')))[1]
|
|
500
|
+
~ agent_board_test.re(agent_board_test.col_frag(a.attname) || '\s*=')),array[]::text[])),null::text,
|
|
501
|
+
'XB each history upsert assigns exactly its reviewed catalog columns');
|
|
502
|
+
-- A routine can contain a separate UPDATE as well as an INSERT into the
|
|
503
|
+
-- same history relation. Such a mixed writer is neither an update-only
|
|
504
|
+
-- exemption nor necessarily an ON CONFLICT branch, so enumerate it explicitly.
|
|
505
|
+
create temp table xb_mixed_writers on commit drop as
|
|
506
|
+
select w.oid,w.relid from xb_history_writer_pairs w join xb_routines f on f.oid=w.oid join pg_class c on c.oid=w.relid
|
|
507
|
+
where f.body ~ agent_board_test.stmt_re('insert into',c.relname) and f.body ~ agent_board_test.stmt_re('update',c.relname);
|
|
508
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
509
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
510
|
+
from xb_mixed_writers w join pg_proc p on p.oid=w.oid join pg_namespace pn on pn.oid=p.pronamespace
|
|
511
|
+
join pg_class c on c.oid=w.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
512
|
+
where not (w.oid='agent_board.acquire_claim(uuid,text,text,timestamptz,uuid,uuid,jsonb)'::regprocedure
|
|
513
|
+
and w.relid='agent_board.claims'::regclass)),null::text,
|
|
514
|
+
'XB unreviewed mixed insert and update writers name their full identities');
|
|
515
|
+
select ok(exists(select 1 from xb_mixed_writers where oid='agent_board.acquire_claim(uuid,text,text,timestamptz,uuid,uuid,jsonb)'::regprocedure
|
|
516
|
+
and relid='agent_board.claims'::regclass), 'XB reviewed acquire-claim mixed writer actually exists');
|
|
517
|
+
select is((select array_agg(a.attname::text order by a.attname::text) from pg_attribute a
|
|
518
|
+
where a.attrelid='agent_board.claims'::regclass and a.attnum>0 and not a.attisdropped
|
|
519
|
+
and agent_board_test.stripped_body('agent_board.acquire_claim(uuid,text,text,timestamptz,uuid,uuid,jsonb)'::regprocedure)
|
|
520
|
+
~ agent_board_test.re(agent_board_test.col_assign_frag('claims',a.attname,true))),
|
|
521
|
+
array['status','updated_at']::text[], 'XB acquire-claim existing-row update assigns only status and timestamp');
|
|
522
|
+
-- Classify INSERTs into relations one enforced edge beyond a direct
|
|
523
|
+
-- thread-FK parent. The anchor-column assertion is a structural premise;
|
|
524
|
+
-- behavior of the common parent predicates is witnessed separately.
|
|
525
|
+
create temp table xb_onehop_insert_actual on commit drop as
|
|
526
|
+
select distinct w.oid,w.relid from xb_history_writer_pairs w join xb_routines f on f.oid=w.oid
|
|
527
|
+
join pg_class c on c.oid=w.relid
|
|
528
|
+
where w.relid in (select child from xb_onehop_actual)
|
|
529
|
+
and w.relid not in (select conrelid from pg_constraint where contype='f' and confrelid='agent_board.threads'::regclass)
|
|
530
|
+
and f.body ~ agent_board_test.stmt_re('insert into',c.relname);
|
|
531
|
+
create temp table xb_onehop_insert_reviewed(fn oid,relid oid,anchors text[],primary key(fn,relid)) on commit drop;
|
|
532
|
+
insert into xb_onehop_insert_reviewed values
|
|
533
|
+
('agent_board._queue_board_post_callbacks(uuid,uuid,text,text,text,jsonb)'::regprocedure,'agent_board.callback_outbox_events'::regclass,array['source_message_id']),
|
|
534
|
+
('agent_board._queue_inbox_callbacks(uuid,text)'::regprocedure,'agent_board.callback_outbox_events'::regclass,array['source_message_id','source_task_id']),
|
|
535
|
+
('agent_board._record_signed_write_evidence(text,uuid,text,jsonb,jsonb)'::regprocedure,'agent_board.signature_evidence'::regclass,array['source_message_id','source_task_event_id']),
|
|
536
|
+
('agent_board._upsert_delivery_recipient(text,uuid,text,uuid,uuid,uuid,uuid,uuid,jsonb)'::regprocedure,'agent_board.delivery_recipients'::regclass,array['source_message_id','source_task_id']),
|
|
537
|
+
('agent_board._upsert_inbox_item(uuid,uuid,uuid,text,text,uuid,uuid,uuid,text,text,text,boolean,boolean,jsonb,jsonb)'::regprocedure,'agent_board.inbox_items'::regclass,array['source_message_id','source_task_id']),
|
|
538
|
+
('agent_board.delegate_task(uuid,uuid,text,text,uuid,text,text,jsonb)'::regprocedure,'agent_board.delegations'::regclass,array['child_task_id','parent_task_id']),
|
|
539
|
+
('agent_board.evaluate_signed_write_fleet_policy(text,jsonb,jsonb)'::regprocedure,'agent_board.signature_evidence'::regclass,array[]::text[]);
|
|
540
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
541
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
542
|
+
from xb_onehop_insert_actual a join pg_proc p on p.oid=a.oid join pg_namespace pn on pn.oid=p.pronamespace
|
|
543
|
+
join pg_class c on c.oid=a.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
544
|
+
where not exists(select 1 from xb_onehop_insert_reviewed r where r.fn=a.oid and r.relid=a.relid)),null::text,
|
|
545
|
+
'XB unreviewed one-hop inserters name their full identities');
|
|
546
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
547
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
548
|
+
from xb_onehop_insert_reviewed r join pg_proc p on p.oid=r.fn join pg_namespace pn on pn.oid=p.pronamespace
|
|
549
|
+
join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
550
|
+
where not exists(select 1 from xb_onehop_insert_actual a where a.oid=r.fn and a.relid=r.relid)),null::text,
|
|
551
|
+
'XB each reviewed one-hop inserter actually writes its claimed relation');
|
|
552
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
553
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
554
|
+
from xb_onehop_insert_reviewed r join xb_routines f on f.oid=r.fn join pg_proc p on p.oid=r.fn
|
|
555
|
+
join pg_namespace pn on pn.oid=p.pronamespace join pg_class c on c.oid=r.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
556
|
+
where r.anchors is distinct from coalesce((select array_agg(distinct a.attname::text order by a.attname::text)
|
|
557
|
+
from pg_constraint fk join lateral unnest(fk.conkey) k(attnum) on true
|
|
558
|
+
join pg_attribute a on a.attrelid=fk.conrelid and a.attnum=k.attnum
|
|
559
|
+
where fk.contype='f' and fk.convalidated and fk.conrelid=r.relid
|
|
560
|
+
and fk.confrelid in (select distinct conrelid from pg_constraint where contype='f' and confrelid='agent_board.threads'::regclass)
|
|
561
|
+
and f.body ~ agent_board_test.col_write_re(c.relname,a.attname)),array[]::text[])),null::text,
|
|
562
|
+
'XB each one-hop inserter writes exactly its reviewed parent-anchor columns');
|
|
563
|
+
-- A writer detected by the shared catalog matcher but not classified as an
|
|
564
|
+
-- INSERT or UPDATE is not silently omitted (for example DELETE, MERGE, COPY,
|
|
565
|
+
-- TRUNCATE or a future syntax matched by write_re).
|
|
566
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
567
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
568
|
+
from xb_history_writer_pairs w join xb_routines f on f.oid=w.oid join pg_proc p on p.oid=w.oid
|
|
569
|
+
join pg_namespace pn on pn.oid=p.pronamespace join pg_class c on c.oid=w.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
570
|
+
where f.body !~ agent_board_test.stmt_re('insert into',c.relname)
|
|
571
|
+
and f.body !~ agent_board_test.stmt_re('update',c.relname)),null::text,
|
|
572
|
+
'XB unsupported history write forms name their full identities');
|
|
573
|
+
-- A routine may combine a reviewed INSERT or UPDATE with a separate DELETE;
|
|
574
|
+
-- check deletion independently so the supported statement cannot mask it.
|
|
575
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
576
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
577
|
+
from xb_history_writer_pairs w join xb_routines f on f.oid=w.oid join pg_proc p on p.oid=w.oid
|
|
578
|
+
join pg_namespace pn on pn.oid=p.pronamespace join pg_class c on c.oid=w.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
579
|
+
where f.body ~ agent_board_test.stmt_re('delete from',c.relname)),null::text,
|
|
580
|
+
'XB history writers with a deletion statement name their full identities');
|
|
581
|
+
-- Supported INSERT/UPDATE text cannot hide a second unsupported operation on
|
|
582
|
+
-- the same relation. Match the other write forms independently by target.
|
|
583
|
+
create temp table xb_other_write_patterns on commit drop as
|
|
584
|
+
select h.relid,agent_board_test.re('(?:' || agent_board_test.stmt_frag('merge into',c.relname,true)
|
|
585
|
+
|| '|' || agent_board_test.lead_guard() || agent_board_test.ci_re('truncate')
|
|
586
|
+
|| '(?:\s+' || agent_board_test.ci_re('table') || ')?\s+(?:' || agent_board_test.ci_re('only') || '\s+)?'
|
|
587
|
+
|| '(?:[A-Za-z_"][A-Za-z0-9_."]*\s*\*?\s*,\s*(?:' || agent_board_test.ci_re('only') || '\s+)?)*'
|
|
588
|
+
|| agent_board_test.qname_frag(c.relname,true)
|
|
589
|
+
|| '|' || agent_board_test.stmt_frag('copy',c.relname,true)
|
|
590
|
+
|| '\s*(?:\([^)]*\))?\s*' || agent_board_test.ci_re('from') || agent_board_test.trail_guard()
|
|
591
|
+
|| '|\m' || agent_board_test.ci_re('select') || '\M' || agent_board_test.same_stmt()
|
|
592
|
+
|| '\m' || agent_board_test.ci_re('into') || '\s+(?:(?:' || agent_board_test.ci_re('temporary')
|
|
593
|
+
|| '|' || agent_board_test.ci_re('temp') || '|' || agent_board_test.ci_re('unlogged') || ')\s+)?'
|
|
594
|
+
|| '(?:' || agent_board_test.ci_re('table') || '\s+)?' || agent_board_test.qname_frag(c.relname,true) || ')') as pattern
|
|
595
|
+
from xb_history_relations h join pg_class c on c.oid=h.relid;
|
|
596
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I',pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname), ', '
|
|
597
|
+
order by pn.nspname,p.proname,oidvectortypes(p.proargtypes),rn.nspname,c.relname)
|
|
598
|
+
from xb_history_writer_pairs w join xb_routines f on f.oid=w.oid join pg_proc p on p.oid=w.oid
|
|
599
|
+
join pg_namespace pn on pn.oid=p.pronamespace join pg_class c on c.oid=w.relid join pg_namespace rn on rn.oid=c.relnamespace
|
|
600
|
+
join xb_other_write_patterns pat on pat.relid=w.relid where f.body ~ pat.pattern),null::text,
|
|
601
|
+
'XB unsupported additional history write forms name their full identities');
|
|
602
|
+
create temp table xb_source_writers on commit drop as
|
|
603
|
+
select distinct f.oid,f.nspname,f.proname from xb_routines f join xb_history_writer_pairs w on w.oid=f.oid;
|
|
604
|
+
-- SQL string bodies have no reliable pg_depend call edges. Conservatively
|
|
605
|
+
-- match each routine name and include every overload when arguments are opaque.
|
|
606
|
+
-- This catches transitive SQL-to-SQL paths; it does not claim semantic resolution.
|
|
607
|
+
create temp table xb_sql_call_patterns on commit drop as
|
|
608
|
+
select oid, agent_board_test.call_re(nspname||'.'||proname,true) as pattern from xb_routines;
|
|
609
|
+
create temp table xb_sql_call_edges on commit drop as
|
|
610
|
+
select s.oid as source,t.oid as target from xb_routines s cross join xb_sql_call_patterns t
|
|
611
|
+
where s.lanname='sql' and s.oid<>t.oid and s.body ~ t.pattern;
|
|
612
|
+
create temp table xb_all_call_edges on commit drop as
|
|
613
|
+
select source,target from xb_sql_call_edges
|
|
614
|
+
union select source,dep_oid as target from xb_edges where dep_oid is not null;
|
|
615
|
+
create temp table xb_reaches_writer on commit drop as
|
|
616
|
+
with recursive reaches_writer(oid) as (
|
|
617
|
+
select oid from xb_source_writers
|
|
618
|
+
union select e.source from xb_all_call_edges e join reaches_writer r on r.oid=e.target
|
|
619
|
+
) select oid from reaches_writer;
|
|
620
|
+
select is((select string_agg(s.oid::regprocedure::text, ', ' order by s.oid::regprocedure::text)
|
|
621
|
+
from xb_routines s join xb_reaches_writer r on r.oid=s.oid where s.lanname='sql'),
|
|
622
|
+
null::text, 'XB SQL routes through SQL or PL/pgSQL to history writers name themselves');
|
|
623
|
+
-- Derive every direct sink caller independently of the reviewed disposition.
|
|
624
|
+
-- The stripped call has simple positional arguments through position eight;
|
|
625
|
+
-- if a new spelling cannot be parsed it is a named failure rather than a
|
|
626
|
+
-- guessed NULL-only route. String literals are intentionally stripped.
|
|
627
|
+
create temp table xb_sink_calls on commit drop as
|
|
628
|
+
select f.oid, regexp_split_to_array(m[1],',') as arguments,
|
|
629
|
+
regexp_replace((regexp_split_to_array(m[1],','))[7], '\s+', '', 'g') as board_arg
|
|
630
|
+
from xb_routines f cross join lateral regexp_matches(f.body,
|
|
631
|
+
agent_board_test.call_re('_upsert_delivery_recipient',true) || '((?:[^,;]*,){7}[^,;]*)','g') m;
|
|
632
|
+
select is((select string_agg(f.oid::regprocedure::text, ', ' order by f.oid::regprocedure::text)
|
|
633
|
+
from xb_routines f where f.body ~ agent_board_test.call_re('_upsert_delivery_recipient',true)
|
|
634
|
+
and (select count(*) from regexp_matches(f.body, agent_board_test.call_re('_upsert_delivery_recipient',true),'g'))
|
|
635
|
+
<> (select count(*) from xb_sink_calls c where c.oid=f.oid)), null::text,
|
|
636
|
+
'XB unparseable delivery-sink calls name their full routine identity');
|
|
637
|
+
-- Any complex positional expression can conceal nested commas and change
|
|
638
|
+
-- argument numbering; refuse that call by identity until reviewed.
|
|
639
|
+
select is((select string_agg(distinct c.oid::regprocedure::text, ', ' order by c.oid::regprocedure::text)
|
|
640
|
+
from xb_sink_calls c cross join lateral unnest(c.arguments) a(arg)
|
|
641
|
+
where translate(a.arg,E' \t\r\n','') !~ '^([a-z_][a-z0-9_.]*)?$'), null::text,
|
|
642
|
+
'XB complex delivery-sink arguments require disposition by caller identity');
|
|
643
|
+
select is((select string_agg(format('%I.%I(%s)[%s]',n.nspname,p.proname,replace(oidvectortypes(p.proargtypes), ', ', ','),c.board_arg), ', '
|
|
644
|
+
order by n.nspname::text collate "C",p.proname::text collate "C",oidvectortypes(p.proargtypes),c.board_arg)
|
|
645
|
+
from xb_sink_calls c join pg_proc p on p.oid=c.oid join pg_namespace n on n.oid=p.pronamespace),
|
|
646
|
+
'agent_board._deliver_board_message(uuid,text,text,text)[p_board_id], agent_board.create_tasking_delivery(uuid,uuid,text,text,uuid[],uuid,text,text,text,jsonb)[null], agent_board.create_tasking_delivery(uuid,uuid,text,text,uuid[],uuid,text,text,text,jsonb)[p_board_id], agent_board.send_direct_message(uuid,uuid,uuid,text,text,text,text,text,jsonb)[null], agent_board.send_multicast_message(uuid,uuid,uuid[],text,text,text,text,text,jsonb)[null]',
|
|
647
|
+
'XB every delivery-sink call has an explicitly classified board argument');
|
|
648
|
+
select is((select string_agg(oid::regprocedure::text, ', ' order by oid::regprocedure::text)
|
|
649
|
+
from (select distinct c.oid from xb_sink_calls c where c.board_arg<>'null') c
|
|
650
|
+
where not exists (select 1 from xb_routines f where f.oid=c.oid
|
|
651
|
+
and f.body ~ agent_board_test.call_re('_associate_empty_thread',true))), null::text,
|
|
652
|
+
'XB each non-NULL board sink caller invokes the early association boundary');
|
|
653
|
+
select ok((select count(*)=2 and bool_and(board_arg='null') from xb_sink_calls c
|
|
654
|
+
join pg_proc p on p.oid=c.oid where p.proname in ('send_direct_message','send_multicast_message')),
|
|
655
|
+
'XB direct and multicast sink paths positively bind a NULL board');
|
|
656
|
+
-- Catalog/OID call closure includes descriptor and signed wrappers, not only
|
|
657
|
+
-- the routines that directly name the sink. Unknown intermediaries are named.
|
|
658
|
+
create temp table xb_sink_caller_closure on commit drop as
|
|
659
|
+
with recursive t(oid) as (select distinct oid from xb_sink_calls
|
|
660
|
+
union select e.source from xb_all_call_edges e join t on t.oid=e.target)
|
|
661
|
+
select oid from t;
|
|
662
|
+
create temp table xb_sink_wrapper_reviewed(fn oid not null,target oid not null,primary key(fn,target)) on commit drop;
|
|
663
|
+
insert into xb_sink_wrapper_reviewed values
|
|
664
|
+
('agent_board.post_board_message(uuid,uuid,uuid,text,text,text,text,text,jsonb)'::regprocedure,'agent_board._deliver_board_message(uuid,text,text,text)'::regprocedure),
|
|
665
|
+
('agent_board.post_message(uuid,uuid,text,text,uuid,jsonb)'::regprocedure,'agent_board._deliver_board_message(uuid,text,text,text)'::regprocedure),
|
|
666
|
+
('agent_board.post_board_message(uuid,uuid,text,text,text,text,text,text,text,jsonb)'::regprocedure,'agent_board.post_board_message(uuid,uuid,uuid,text,text,text,text,text,jsonb)'::regprocedure),
|
|
667
|
+
('agent_board.create_tasking_delivery(uuid,uuid,text,text,jsonb,text,text,text,text,text,jsonb)'::regprocedure,'agent_board.create_tasking_delivery(uuid,uuid,text,text,uuid[],uuid,text,text,text,jsonb)'::regprocedure),
|
|
668
|
+
('agent_board.send_direct_message(uuid,uuid,jsonb,text,text,text,text,text,text,jsonb)'::regprocedure,'agent_board.send_direct_message(uuid,uuid,uuid,text,text,text,text,text,jsonb)'::regprocedure),
|
|
669
|
+
('agent_board.send_multicast_message(uuid,uuid,jsonb,text,text,text,text,text,text,jsonb)'::regprocedure,'agent_board.send_multicast_message(uuid,uuid,uuid[],text,text,text,text,text,jsonb)'::regprocedure),
|
|
670
|
+
('agent_board.signed_post_board_message(jsonb,jsonb)'::regprocedure,'agent_board.post_board_message(uuid,uuid,uuid,text,text,text,text,text,jsonb)'::regprocedure),
|
|
671
|
+
('agent_board.signed_create_tasking_delivery(jsonb,jsonb)'::regprocedure,'agent_board.create_tasking_delivery(uuid,uuid,text,text,uuid[],uuid,text,text,text,jsonb)'::regprocedure),
|
|
672
|
+
('agent_board.signed_send_direct_message(jsonb,jsonb)'::regprocedure,'agent_board.send_direct_message(uuid,uuid,uuid,text,text,text,text,text,jsonb)'::regprocedure),
|
|
673
|
+
('agent_board.signed_send_multicast_message(jsonb,jsonb)'::regprocedure,'agent_board.send_multicast_message(uuid,uuid,uuid[],text,text,text,text,text,jsonb)'::regprocedure),
|
|
674
|
+
('agent_board.signed_create_tasking_delivery(jsonb,jsonb)'::regprocedure,'agent_board.create_tasking_delivery(uuid,uuid,text,text,jsonb,text,text,text,text,text,jsonb)'::regprocedure),
|
|
675
|
+
('agent_board.signed_send_multicast_message(jsonb,jsonb)'::regprocedure,'agent_board.send_multicast_message(uuid,uuid,jsonb,text,text,text,text,text,text,jsonb)'::regprocedure);
|
|
676
|
+
select is((select string_agg(format('%I.%I(%s)',n.nspname,p.proname,oidvectortypes(p.proargtypes)), ', '
|
|
677
|
+
order by n.nspname,p.proname,oidvectortypes(p.proargtypes))
|
|
678
|
+
from xb_sink_caller_closure t join pg_proc p on p.oid=t.oid join pg_namespace n on n.oid=p.pronamespace
|
|
679
|
+
where not exists(select 1 from xb_sink_calls c where c.oid=t.oid)
|
|
680
|
+
and not exists(select 1 from xb_sink_wrapper_reviewed r where r.fn=t.oid)), null::text,
|
|
681
|
+
'XB unreviewed transitive delivery callers name their full identities');
|
|
682
|
+
select is((select string_agg(format('%I.%I(%s)',n.nspname,p.proname,oidvectortypes(p.proargtypes)), ', '
|
|
683
|
+
order by n.nspname,p.proname,oidvectortypes(p.proargtypes))
|
|
684
|
+
from xb_sink_wrapper_reviewed r join pg_proc p on p.oid=r.fn join pg_namespace n on n.oid=p.pronamespace
|
|
685
|
+
where not exists(select 1 from xb_all_call_edges e where e.source=r.fn and e.target=r.target)
|
|
686
|
+
or not exists(select 1 from xb_sink_caller_closure t where t.oid=r.fn)), null::text,
|
|
687
|
+
'XB each reviewed delivery wrapper reaches its exact sink-caller overload');
|
|
688
|
+
select is((select string_agg(format('%I.%I(%s) -> %I.%I(%s)',sn.nspname,sp.proname,oidvectortypes(sp.proargtypes),
|
|
689
|
+
tn.nspname,tp.proname,oidvectortypes(tp.proargtypes)), ', ' order by sn.nspname,sp.proname,oidvectortypes(sp.proargtypes),tn.nspname,tp.proname)
|
|
690
|
+
from xb_all_call_edges e
|
|
691
|
+
join pg_proc sp on sp.oid=e.source join pg_namespace sn on sn.oid=sp.pronamespace
|
|
692
|
+
join pg_proc tp on tp.oid=e.target join pg_namespace tn on tn.oid=tp.pronamespace
|
|
693
|
+
where e.source in (select fn from xb_sink_wrapper_reviewed) and e.target in (select oid from xb_sink_caller_closure)
|
|
694
|
+
and not exists(select 1 from xb_sink_wrapper_reviewed r where r.fn=e.source and r.target=e.target)), null::text,
|
|
695
|
+
'XB delivery wrappers have no other undispositioned delivery-call edge');
|
|
696
|
+
-- Delegation does not call the sink: it creates its child against the parent
|
|
697
|
+
-- task's stored thread. The writer test proves the resulting parent/child state.
|
|
698
|
+
select ok((select f.body ~ agent_board_test.re(agent_board_test.call_frag('create_task',true)
|
|
699
|
+
|| '\s*' || agent_board_test.ident_re('v_parent') || '\s*\.\s*' || agent_board_test.ident_re('thread_id') || '\s*,')
|
|
700
|
+
and exists(select 1 from xb_edges e where e.source=f.oid and e.dep_oid='agent_board.create_task(uuid,uuid,text,text,uuid,uuid,text,text,jsonb)'::regprocedure)
|
|
701
|
+
and not exists(select 1 from xb_sink_caller_closure t where t.oid=f.oid)
|
|
702
|
+
from xb_routines f where f.oid='agent_board.delegate_task(uuid,uuid,text,text,uuid,text,text,jsonb)'::regprocedure),
|
|
703
|
+
'XB delegation binds create_task to its parent thread and is not a sink caller');
|
|
704
|
+
-- Trigger callers can live on a relation outside the history FK graph. Parse each
|
|
705
|
+
-- non-extension trigger with its actual row type; unresolved or dynamic bodies,
|
|
706
|
+
-- any history relation dependency, and calls reaching a writer are reported.
|
|
707
|
+
create function pg_temp.xb_trigger_dependencies(p_fn oid,p_rel oid)
|
|
708
|
+
returns table(dep_oid oid,dep_type text,problem text) language plpgsql as $body$
|
|
709
|
+
begin
|
|
710
|
+
return query select d.oid::oid,d.type::text,null::text
|
|
711
|
+
from extensions.plpgsql_show_dependency_tb(p_fn::regprocedure,p_rel::regclass) d;
|
|
712
|
+
exception when others then return query select null::oid,null::text,sqlstate || ':' || sqlerrm;
|
|
713
|
+
end $body$;
|
|
714
|
+
create temp table xb_application_triggers on commit drop as
|
|
715
|
+
select t.oid,t.tgrelid,t.tgname,t.tgfoid,
|
|
716
|
+
agent_board_test.stripped_body(t.tgfoid) ~ agent_board_test.re('\m' || agent_board_test.ci_re('execute') || '\M') as opaque
|
|
717
|
+
from pg_trigger t join pg_class c on c.oid=t.tgrelid join pg_namespace n on n.oid=c.relnamespace
|
|
718
|
+
where not t.tgisinternal and n.nspname not in ('pg_catalog','information_schema','agent_board_test')
|
|
719
|
+
and n.nspname not like 'pg_%'
|
|
720
|
+
and not exists (select 1 from pg_depend d where d.classid='pg_trigger'::regclass and d.objid=t.oid and d.deptype='e');
|
|
721
|
+
create temp table xb_trigger_deps on commit drop as
|
|
722
|
+
select t.oid,t.tgrelid,t.tgname,t.tgfoid,t.opaque,d.dep_oid,d.dep_type,d.problem
|
|
723
|
+
from xb_application_triggers t left join lateral pg_temp.xb_trigger_dependencies(t.tgfoid,t.tgrelid) d on true;
|
|
724
|
+
select ok((select count(*)>0 from xb_application_triggers), 'XB application trigger parser population is nonempty');
|
|
725
|
+
select is((select string_agg(distinct format('%s.%I -> %s [%s]',t.tgrelid::regclass,t.tgname,t.tgfoid::regprocedure,
|
|
726
|
+
case when t.problem is not null then 'PARSER-ERROR:'||t.problem when t.opaque then 'DYNAMIC'
|
|
727
|
+
when t.dep_type='RELATION' then 'HISTORY-RELATION:'||t.dep_oid::regclass::text
|
|
728
|
+
else 'WRITER-CALL:'||t.dep_oid::regprocedure::text end), ', '
|
|
729
|
+
order by format('%s.%I -> %s [%s]',t.tgrelid::regclass,t.tgname,t.tgfoid::regprocedure,
|
|
730
|
+
case when t.problem is not null then 'PARSER-ERROR:'||t.problem when t.opaque then 'DYNAMIC'
|
|
731
|
+
when t.dep_type='RELATION' then 'HISTORY-RELATION:'||t.dep_oid::regclass::text
|
|
732
|
+
else 'WRITER-CALL:'||t.dep_oid::regprocedure::text end))
|
|
733
|
+
from xb_trigger_deps t where t.problem is not null or t.opaque
|
|
734
|
+
or (t.dep_type='RELATION' and t.dep_oid in (select relid from xb_history_relations))
|
|
735
|
+
or (t.dep_type='FUNCTION' and t.dep_oid in (select oid from xb_reaches_writer))),
|
|
736
|
+
null::text, 'XB trigger paths to history or writers and unresolved triggers name themselves');
|
|
737
|
+
|
|
738
|
+
-- DML rules can originate on a relation outside the FK class. Their stored
|
|
739
|
+
-- actions may write another relation or call a writer; fail closed on every
|
|
740
|
+
-- non-extension application DML rule until its effect is positively reviewed.
|
|
741
|
+
select is((select string_agg(format('%s.%I',rw.ev_class::regclass,rw.rulename), ', '
|
|
742
|
+
order by rw.ev_class::regclass::text,rw.rulename)
|
|
743
|
+
from pg_rewrite rw join pg_class c on c.oid=rw.ev_class join pg_namespace n on n.oid=c.relnamespace
|
|
744
|
+
where rw.ev_type<>'1' and n.nspname not in ('pg_catalog','information_schema','agent_board_test')
|
|
745
|
+
and n.nspname not like 'pg_%'
|
|
746
|
+
and not exists (select 1 from pg_depend d where d.classid='pg_class'::regclass and d.objid=c.oid and d.deptype='e')),
|
|
747
|
+
null::text, 'XB application DML rules name themselves for disposition');
|
|
748
|
+
|
|
749
|
+
-- Canary relations and overloads are deliberately created in another schema.
|
|
750
|
+
-- These assertions pin discovery primitives, not a proof that every writer has a
|
|
751
|
+
-- safe lock or that arbitrary dynamic SQL can be resolved.
|
|
752
|
+
create schema xb_class_probe;
|
|
753
|
+
create table xb_class_probe.onehop (id uuid primary key, task_id uuid references agent_board.tasks(id));
|
|
754
|
+
create function xb_class_probe.writer(p_id uuid, p_task uuid) returns void language plpgsql as $body$
|
|
755
|
+
begin insert into xb_class_probe.onehop(id,task_id) values (p_id,p_task); end $body$;
|
|
756
|
+
create function xb_class_probe.writer(p_id uuid, p_task uuid, p_extra text) returns void language plpgsql as $body$
|
|
757
|
+
begin insert into xb_class_probe.onehop(id,task_id) values (p_id,p_task); end $body$;
|
|
758
|
+
create function xb_class_probe.relay(p_id uuid, p_task uuid) returns void language plpgsql as $body$
|
|
759
|
+
begin perform xb_class_probe.writer(p_id,p_task); end $body$;
|
|
760
|
+
create function xb_class_probe.opaque(p_id uuid, p_task uuid) returns void language plpgsql as $body$
|
|
761
|
+
begin execute 'select xb_class_probe.writer($1,$2)' using p_id,p_task; end $body$;
|
|
762
|
+
select is(
|
|
763
|
+
(with direct as (select distinct conrelid from pg_constraint where contype='f' and confrelid='agent_board.threads'::regclass)
|
|
764
|
+
select string_agg(format('%s.%I', f.conrelid::regclass, f.conname), ', ' order by f.conrelid::regclass::text, f.conname)
|
|
765
|
+
from pg_constraint f join direct d on d.conrelid=f.confrelid
|
|
766
|
+
where f.contype='f' and f.conrelid='xb_class_probe.onehop'::regclass),
|
|
767
|
+
'xb_class_probe.onehop.onehop_task_id_fkey', 'XB valid injected one-hop FK is discovered and named');
|
|
768
|
+
select is(
|
|
769
|
+
(select count(*) from pg_proc p join pg_namespace n on n.oid=p.pronamespace
|
|
770
|
+
where n.nspname='xb_class_probe' and p.proname='writer'
|
|
771
|
+
and exists (select 1 from extensions.plpgsql_show_dependency_tb(p.oid::regprocedure,0::oid) d
|
|
772
|
+
where d.type='RELATION' and d.oid='xb_class_probe.onehop'::regclass)),
|
|
773
|
+
2::bigint, 'XB injected cross-schema writer overloads are independently discovered');
|
|
774
|
+
select ok(
|
|
775
|
+
exists (select 1 from extensions.plpgsql_show_dependency_tb('xb_class_probe.relay(uuid,uuid)'::regprocedure,0::oid) d
|
|
776
|
+
where d.type='FUNCTION' and d.oid='xb_class_probe.writer(uuid,uuid)'::regprocedure),
|
|
777
|
+
'XB parser resolves the injected cross-schema relay to its exact writer overload');
|
|
778
|
+
select is(
|
|
779
|
+
(select string_agg(p.oid::regprocedure::text, ', ' order by p.oid::regprocedure::text)
|
|
780
|
+
from pg_proc p join pg_namespace n on n.oid=p.pronamespace
|
|
781
|
+
where n.nspname='xb_class_probe'
|
|
782
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.re('\m' || agent_board_test.ci_re('execute') || '\M')),
|
|
783
|
+
'xb_class_probe.opaque(uuid,uuid)', 'XB opaque injected SQL path is flagged by full identity');
|
|
784
|
+
select * from finish();
|
|
785
|
+
rollback;
|