@consiliency/agent-board-schema 1.68.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.
Files changed (155) hide show
  1. package/fixtures/already-applied.json +28 -4
  2. package/migrations/20261026000100_agent_board_thread_association.sql +1287 -0
  3. package/migrations/20261101000100_agent_board_session_measured_mint.sql +145 -0
  4. package/migrations/20261104065658_agent_board_reply_delivery.sql +659 -0
  5. package/migrations/20261104170000_agent_board_tasking_replay_scope.sql +320 -0
  6. package/migrations/20261104180000_agent_board_delivery_writer_execute.sql +51 -0
  7. package/migrations/20261104190000_agent_board_message_replay_scope.sql +373 -0
  8. package/package.json +1 -1
  9. package/schema-manifest.json +57 -3
  10. package/src/tooling.test.mjs +8 -2
  11. package/tests/agent_board_661_reply_delivery.test.sql +185 -0
  12. package/tests/agent_board_667_boundary.test.sql +162 -0
  13. package/tests/agent_board_667_conversion_branches.test.sql +91 -0
  14. package/tests/agent_board_667_conversion_profile.test.sql +112 -0
  15. package/tests/agent_board_667_history_class.test.sql +785 -0
  16. package/tests/agent_board_667_native_cast_effect.test.sql +88 -0
  17. package/tests/agent_board_667_realtime_apply_behavior.test.sql +117 -0
  18. package/tests/agent_board_667_realtime_apply_controls.test.sql +143 -0
  19. package/tests/agent_board_667_realtime_apply_profile.test.sql +61 -0
  20. package/tests/agent_board_667_retention.test.sql +201 -0
  21. package/tests/agent_board_667_routing.test.sql +294 -0
  22. package/tests/agent_board_667_sender_effects.test.sql +100 -0
  23. package/tests/agent_board_667_sender_profile_controls.test.sql +29 -0
  24. package/tests/agent_board_667_storage_delimiter_behavior.test.sql +84 -0
  25. package/tests/agent_board_667_storage_delimiter_controls.test.sql +23 -0
  26. package/tests/agent_board_667_storage_delimiter_profile.test.sql +61 -0
  27. package/tests/agent_board_667_storage_search_behavior.test.sql +93 -0
  28. package/tests/agent_board_667_storage_search_controls.test.sql +22 -0
  29. package/tests/agent_board_667_storage_search_profile.test.sql +60 -0
  30. package/tests/agent_board_667_storage_timestamp_behavior.test.sql +58 -0
  31. package/tests/agent_board_667_storage_timestamp_controls.test.sql +22 -0
  32. package/tests/agent_board_667_storage_timestamp_profile.test.sql +54 -0
  33. package/tests/agent_board_667_writers.test.sql +281 -0
  34. package/tests/agent_board_717_message_replay_scope.test.sql +828 -0
  35. package/tests/agent_board_717_tasking_replay_scope.test.sql +661 -0
  36. package/tests/agent_board_addressing.test.sql +8 -5
  37. package/tests/agent_board_claim_subject_gate.test.sql +7 -3
  38. package/tests/agent_board_claimpolicy_race.test.sql +39 -11
  39. package/tests/agent_board_delegate_role_class.test.sql +33 -15
  40. package/tests/agent_board_delivery_writer_execute.test.sql +371 -0
  41. package/tests/agent_board_fk_lock_edges.test.sql +3 -0
  42. package/tests/agent_board_hosted_delegates.test.sql +5 -2
  43. package/tests/agent_board_inbox_rpcs.test.sql +5 -3
  44. package/tests/agent_board_obsgate.test.sql +8 -4
  45. package/tests/agent_board_obsgate4.test.sql +2 -2
  46. package/tests/agent_board_outbox_wake_ordering.test.sql +25 -2
  47. package/tests/agent_board_own_post_not_delivered.test.sql +11 -9
  48. package/tests/agent_board_session_identity_behavior_classes.test.sql +2 -2
  49. package/tests/agent_board_session_identity_limits.test.sql +1 -1
  50. package/tests/agent_board_session_identity_measured_mint.test.sql +342 -0
  51. package/tests/agent_board_session_identity_predicate_isolation.test.sql +146 -0
  52. package/tests/agent_board_sidserver_authority.test.sql +69 -0
  53. package/tests/agent_board_sidserver_catalog.test.sql +47 -0
  54. package/tests/agent_board_signed_write_gateway.test.sql +4 -2
  55. package/tests/agent_board_task_thread_lock_order.test.sql +67 -42
  56. package/tests/agent_board_v2_invariants.test.sql +5 -2
  57. package/tests/agent_board_write_enforcement_class.test.sql +3 -3
  58. package/tests/catalog/function_execute_privileges.json +41 -5
  59. package/tests/catalog/p_metadata_parameters.json +16 -0
  60. package/tests/fixtures/agent_board_667_apply_rls_guard.sql +195 -0
  61. package/tests/fixtures/agent_board_667_apply_rls_provenance.md +125 -0
  62. package/tests/fixtures/agent_board_667_apply_rls_reference.sql +1903 -0
  63. package/tests/fixtures/agent_board_667_conversion_guard.sql +162 -0
  64. package/tests/fixtures/agent_board_667_conversion_reference.md +23 -0
  65. package/tests/fixtures/agent_board_667_conversion_reference.sql +5325 -0
  66. package/tests/fixtures/agent_board_667_delimiter_guard.sql +92 -0
  67. package/tests/fixtures/agent_board_667_delimiter_provenance.md +116 -0
  68. package/tests/fixtures/agent_board_667_delimiter_reference.sql +10150 -0
  69. package/tests/fixtures/agent_board_667_search_guard.sql +92 -0
  70. package/tests/fixtures/agent_board_667_search_provenance.md +141 -0
  71. package/tests/fixtures/agent_board_667_search_reference.sql +4779 -0
  72. package/tests/fixtures/agent_board_667_sender_guard.sql +102 -0
  73. package/tests/fixtures/agent_board_667_sender_reference.md +20 -0
  74. package/tests/fixtures/agent_board_667_sender_reference.sql +875 -0
  75. package/tests/fixtures/agent_board_667_timestamp_graph.sql +84 -0
  76. package/tests/fixtures/agent_board_667_timestamp_guard.sql +94 -0
  77. package/tests/fixtures/agent_board_667_timestamp_provenance.md +99 -0
  78. package/tests/fixtures/agent_board_667_timestamp_reference.sql +2938 -0
  79. package/tests/isolation/expected/association_artifact_anchor_order.out +108 -0
  80. package/tests/isolation/expected/association_board_vs_board.out +71 -0
  81. package/tests/isolation/expected/association_post_wait_authority.out +77 -0
  82. package/tests/isolation/expected/association_strong_history_writers.out +203 -0
  83. package/tests/isolation/expected/association_strong_isolation.out +185 -0
  84. package/tests/isolation/expected/association_vs_artifact_claim_escalation.out +199 -0
  85. package/tests/isolation/expected/association_vs_heartbeat.out +78 -0
  86. package/tests/isolation/expected/association_vs_multi_anchor_artifact.out +67 -0
  87. package/tests/isolation/expected/association_vs_private_message.out +67 -0
  88. package/tests/isolation/expected/association_vs_task_and_conflict.out +133 -0
  89. package/tests/isolation/expected/association_vs_taskful_cross_thread.out +133 -0
  90. package/tests/isolation/expected/message_replay_scope.out +209 -0
  91. package/tests/isolation/expected/tasking_replay_scope.out +125 -0
  92. package/tests/isolation/specs/association_artifact_anchor_order.spec +46 -0
  93. package/tests/isolation/specs/association_board_vs_board.spec +37 -0
  94. package/tests/isolation/specs/association_post_wait_authority.spec +48 -0
  95. package/tests/isolation/specs/association_strong_history_writers.spec +61 -0
  96. package/tests/isolation/specs/association_strong_isolation.spec +50 -0
  97. package/tests/isolation/specs/association_vs_artifact_claim_escalation.spec +41 -0
  98. package/tests/isolation/specs/association_vs_heartbeat.spec +47 -0
  99. package/tests/isolation/specs/association_vs_multi_anchor_artifact.spec +38 -0
  100. package/tests/isolation/specs/association_vs_private_message.spec +33 -0
  101. package/tests/isolation/specs/association_vs_task_and_conflict.spec +38 -0
  102. package/tests/isolation/specs/association_vs_taskful_cross_thread.spec +42 -0
  103. package/tests/isolation/specs/message_replay_scope.spec +77 -0
  104. package/tests/isolation/specs/tasking_replay_scope.spec +72 -0
  105. package/tests/mutations/717-f1/M0_prefix_main_acl_and_comments.sql +11 -0
  106. package/tests/mutations/717-f1/M10_wrapper_stops_delegating.sql +7 -0
  107. package/tests/mutations/717-f1/M11_transitive_recipient_content.sql +8 -0
  108. package/tests/mutations/717-f1/M12_transitive_recipient_visibility.sql +8 -0
  109. package/tests/mutations/717-f1/M13_transitive_worker_content.sql +15 -0
  110. package/tests/mutations/717-f1/M1a_regrant_upsert_delivery_recipient.sql +1 -0
  111. package/tests/mutations/717-f1/M1b_regrant_upsert_inbox_item.sql +1 -0
  112. package/tests/mutations/717-f1/M1c_regrant_queue_inbox_callbacks.sql +1 -0
  113. package/tests/mutations/717-f1/M1d_regrant_queue_board_post_callbacks.sql +1 -0
  114. package/tests/mutations/717-f1/M1e_regrant_next_thread_message_sequence.sql +1 -0
  115. package/tests/mutations/717-f1/M2_blanket_schema_grant.sql +1 -0
  116. package/tests/mutations/717-f1/M3_new_overload_of_sink.sql +10 -0
  117. package/tests/mutations/717-f1/M4_new_route_calling_sink.sql +7 -0
  118. package/tests/mutations/717-f1/M5_actor_trusts_supplied_id.sql +5 -0
  119. package/tests/mutations/717-f1/M6_signed_route_granted_to_authenticated.sql +1 -0
  120. package/tests/mutations/717-f1/M7_worker_granted_to_authenticated.sql +1 -0
  121. package/tests/mutations/717-f1/M8_recipient_state_rewrites_visibility.sql +5 -0
  122. package/tests/mutations/717-f1/M9_public_route_revoked_from_service_role.sql +1 -0
  123. package/tests/mutations/717-f1/outbox_wake_ordering/W1_regrant_queue_inbox_callbacks.sql +1 -0
  124. package/tests/mutations/717-f1/outbox_wake_ordering/W2_revoke_worker_from_service_role.sql +1 -0
  125. package/tests/mutations/717-f1/outbox_wake_ordering/W3_invoker_caller_of_sink.sql +1 -0
  126. package/tests/mutations/717-f1/outbox_wake_ordering/W4_public_regrant_board_post_sink.sql +1 -0
  127. package/tests/mutations/717-f1/run.sh +17 -0
  128. package/tests/mutations/717-f2b/C1_new_delivery_route.sql +4 -0
  129. package/tests/mutations/717-f2b/C2_new_replay_lookup.sql +3 -0
  130. package/tests/mutations/717-f2b/C3_internal_regranted.sql +2 -0
  131. package/tests/mutations/717-f2b/C4_wrapper_writes_delivery.sql +9 -0
  132. package/tests/mutations/717-f2b/C5_class_member_dynamic_sql.sql +9 -0
  133. package/tests/mutations/717-f2b/C6_drop_board_scope_constraint.sql +2 -0
  134. package/tests/mutations/717-f2b/C7_state_private_helper_content.sql +14 -0
  135. package/tests/mutations/717-f2b/L1_dm_lock_split_by_recipient.sql +8 -0
  136. package/tests/mutations/717-f2b/L2_dm_lock_removed.sql +12 -0
  137. package/tests/mutations/717-f2b/L3_mc_lock_split_by_recipients.sql +8 -0
  138. package/tests/mutations/717-f2b/L4_mc_lock_removed.sql +12 -0
  139. package/tests/mutations/717-f2b/M0_prefix_definitions.sql +17 -0
  140. package/tests/mutations/717-f2b/M10_mc_lookup_any_author.sql +15 -0
  141. package/tests/mutations/717-f2b/M11_dm_swallow_refusal.sql +8 -0
  142. package/tests/mutations/717-f2b/M12_mc_swallow_refusal.sql +8 -0
  143. package/tests/mutations/717-f2b/M13_signed_dm_swallows_refusal.sql +32 -0
  144. package/tests/mutations/717-f2b/M14_signed_mc_swallows_refusal.sql +20 -0
  145. package/tests/mutations/717-f2b/M15_mc_any_source.sql +7 -0
  146. package/tests/mutations/717-f2b/M1_dm_drop_recipient_proof.sql +16 -0
  147. package/tests/mutations/717-f2b/M2_mc_drop_recipient_proof.sql +16 -0
  148. package/tests/mutations/717-f2b/M3_dm_metadata_as_proof.sql +12 -0
  149. package/tests/mutations/717-f2b/M4_mc_metadata_as_proof.sql +16 -0
  150. package/tests/mutations/717-f2b/M5_dm_any_mode.sql +11 -0
  151. package/tests/mutations/717-f2b/M6_dm_board_delivery_counts.sql +11 -0
  152. package/tests/mutations/717-f2b/M7_mc_any_mode.sql +11 -0
  153. package/tests/mutations/717-f2b/M8_dm_thread_stamp_as_proof.sql +12 -0
  154. package/tests/mutations/717-f2b/M9_dm_lookup_any_author.sql +15 -0
  155. 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;