@consiliency/agent-board-schema 1.12.0 → 1.23.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 (156) hide show
  1. package/fixtures/already-applied.json +160 -0
  2. package/migrations/20260731000100_agent_board_agentsdir_column_scope.sql +88 -0
  3. package/migrations/20260801000100_agent_board_leaveidem_terminal_noop.sql +317 -0
  4. package/migrations/20260802000100_agent_board_rolereset_reentry_clamp.sql +237 -0
  5. package/migrations/20260803000100_agent_board_rowid_total_order.sql +1201 -0
  6. package/migrations/20260804000100_agent_board_hygiene_actor_binding_rls.sql +229 -0
  7. package/migrations/20260806000100_agent_board_consent_substrate.sql +339 -0
  8. package/migrations/20260807000100_agent_board_consent_emission.sql +619 -0
  9. package/migrations/20260808000100_agent_board_rejectsem.sql +145 -0
  10. package/migrations/20260809000100_agent_board_consent_gating.sql +492 -0
  11. package/migrations/20260810000100_agent_board_consent_locking.sql +485 -0
  12. package/migrations/20260811000100_agent_board_retention_metadata_revoke.sql +23 -0
  13. package/migrations/20260812000100_agent_board_pubexec_class_revoke.sql +54 -0
  14. package/migrations/20260813000100_agent_board_invitepolicy_block.sql +420 -0
  15. package/migrations/20260814000100_agent_board_obsgate_thread_post_policy.sql +239 -0
  16. package/migrations/20260815000100_agent_board_obsgate2_authoring_gates.sql +287 -0
  17. package/migrations/20260816000100_agent_board_obsgate3_task_lifecycle.sql +118 -0
  18. package/migrations/20260817000100_agent_board_obsgate4_claims_lifecycle.sql +250 -0
  19. package/migrations/20260818000100_agent_board_fleet_machines_rls_correlation.sql +43 -0
  20. package/migrations/20260819000100_agent_board_resolve_noop_retire.sql +166 -0
  21. package/migrations/20260820000100_agent_board_roomdeleg_lane_b.sql +206 -0
  22. package/migrations/20260821000100_agent_board_claimgate_claimkey.sql +372 -0
  23. package/migrations/20260822000100_agent_board_mint_harden.sql +710 -0
  24. package/migrations/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql +207 -0
  25. package/migrations/20260823000100_agent_board_fleet_machine_hijack.sql +106 -0
  26. package/migrations/20260824000100_agent_board_claimgate_shadow.sql +443 -0
  27. package/migrations/20260825000100_agent_board_claimgate_enforce.sql +660 -0
  28. package/migrations/20260826000100_agent_board_fleet_machine_reactivation.sql +514 -0
  29. package/migrations/20260828000100_agent_board_delegation_cap_ceiling.sql +474 -0
  30. package/migrations/20260829000100_agent_board_fleet_machine_reclaim.sql +432 -0
  31. package/migrations/20260830000100_agent_board_delegation_sole_writer.sql +41 -0
  32. package/migrations/20260831000100_agent_board_fleet_domain_reclaim.sql +389 -0
  33. package/migrations/20260901000100_agent_board_atomic_fleet_onboarding.sql +1496 -0
  34. package/migrations/20260901000110_agent_board_fleet_onboarding_ceiling_lock.sql +604 -0
  35. package/migrations/20260901000120_agent_board_human_revocation_evidence_completeness.sql +78 -0
  36. package/migrations/20260901000130_agent_board_fedsession_human_revocation_gate.sql +74 -0
  37. package/migrations/20260901000140_agent_board_fedsession_board_owner_lock_order.sql +102 -0
  38. package/migrations/20260902000100_agent_board_outbox_wake_ordering.sql +184 -0
  39. package/migrations/20260903000100_agent_board_enroll_identity_board_owner_lock_order.sql +604 -0
  40. package/migrations/20260903000200_agent_board_fedsession_provider_absence_gate.sql +177 -0
  41. package/migrations/20260904000100_agent_board_wake_recovery_surface.sql +155 -0
  42. package/package.json +1 -1
  43. package/schema-manifest.json +363 -3
  44. package/src/tooling.test.mjs +113 -2
  45. package/tests/agent_board_actorspoof.test.sql +349 -0
  46. package/tests/agent_board_agentsdir.test.sql +154 -0
  47. package/tests/agent_board_atomic_fleet_onboarding.test.sql +623 -0
  48. package/tests/agent_board_atomic_fleet_onboarding_backfill.test.sql +142 -0
  49. package/tests/agent_board_atomic_fleet_onboarding_runtime.test.sql +1250 -0
  50. package/tests/agent_board_authbound_boundary.test.sql +9 -1
  51. package/tests/agent_board_claimgate_enforce.test.sql +464 -0
  52. package/tests/agent_board_claimgate_predicate.test.sql +360 -0
  53. package/tests/agent_board_claimkey.test.sql +229 -0
  54. package/tests/agent_board_consent_backfill.test.sql +48 -0
  55. package/tests/agent_board_consent_emission.test.sql +601 -0
  56. package/tests/agent_board_consent_gating.test.sql +489 -0
  57. package/tests/agent_board_consent_substrate.test.sql +720 -0
  58. package/tests/agent_board_delegation_cap_ceiling.test.sql +303 -0
  59. package/tests/agent_board_delegation_enforcement.test.sql +55 -6
  60. package/tests/agent_board_delegation_policies.test.sql +107 -1
  61. package/tests/agent_board_enroll_federated_identity.test.sql +3 -3
  62. package/tests/agent_board_enroll_pop_binding.test.sql +13 -3
  63. package/tests/agent_board_enrollment_audit_content.test.sql +11 -7
  64. package/tests/agent_board_federated_lock_order_class.test.sql +390 -0
  65. package/tests/agent_board_fedsession.test.sql +51 -5
  66. package/tests/agent_board_fedsession_provider_absence_gate.test.sql +170 -0
  67. package/tests/agent_board_fleet_domain_reclaim.test.sql +419 -0
  68. package/tests/agent_board_fleet_machine_hijack.test.sql +190 -0
  69. package/tests/agent_board_fleet_machine_reactivation.test.sql +426 -0
  70. package/tests/agent_board_fleet_machine_reclaim.test.sql +509 -0
  71. package/tests/agent_board_fleet_machines_rls.test.sql +79 -0
  72. package/tests/agent_board_fleet_policy.test.sql +2 -2
  73. package/tests/agent_board_grants_hygiene.test.sql +408 -0
  74. package/tests/agent_board_identity_providers.test.sql +9 -0
  75. package/tests/agent_board_inbox_reads.test.sql +45 -31
  76. package/tests/agent_board_inbox_rpcs.test.sql +44 -34
  77. package/tests/agent_board_invitepolicy.test.sql +205 -0
  78. package/tests/agent_board_leaveidem.test.sql +194 -0
  79. package/tests/agent_board_mint_fleet_agent_session.test.sql +10 -10
  80. package/tests/agent_board_mint_harden.test.sql +303 -0
  81. package/tests/agent_board_mint_harden_backfill.test.sql +95 -0
  82. package/tests/agent_board_obsgate.test.sql +178 -0
  83. package/tests/agent_board_obsgate2.test.sql +153 -0
  84. package/tests/agent_board_obsgate3.test.sql +170 -0
  85. package/tests/agent_board_obsgate4.test.sql +196 -0
  86. package/tests/agent_board_outbox_wake_ordering.test.sql +452 -0
  87. package/tests/agent_board_portal_offline_invariant.test.sql +14 -3
  88. package/tests/agent_board_pubexec_class.test.sql +53 -0
  89. package/tests/agent_board_rejectsem.test.sql +271 -0
  90. package/tests/agent_board_resigning_refresh.test.sql +1 -1
  91. package/tests/agent_board_resolve_noop_retire.test.sql +71 -0
  92. package/tests/agent_board_retention_metadata_acl.test.sql +53 -0
  93. package/tests/agent_board_revoke_agent_subtree.test.sql +3 -3
  94. package/tests/agent_board_rolereset.test.sql +351 -0
  95. package/tests/agent_board_roomdeleg.test.sql +5 -3
  96. package/tests/agent_board_roomdeleg_lane_b.test.sql +144 -0
  97. package/tests/agent_board_roomlink.test.sql +27 -11
  98. package/tests/agent_board_rowid.test.sql +1274 -0
  99. package/tests/agent_board_signature_rls_reads.test.sql +3 -3
  100. package/tests/agent_board_signed_write_gateway.test.sql +3 -3
  101. package/tests/agent_board_signing_challenge_pending_cap.test.sql +2 -2
  102. package/tests/agent_board_signing_challenge_reaper.test.sql +2 -2
  103. package/tests/agent_board_signing_proof_rpcs.test.sql +4 -4
  104. package/tests/agent_board_signing_store.test.sql +6 -6
  105. package/tests/agent_board_wake_recovery.test.sql +756 -0
  106. package/tests/fixtures/isolation-substrate.sql +93 -0
  107. package/tests/fixtures/test-prereqs.sql +33 -0
  108. package/tests/isolation/expected/atomic_fedsession_fleet_lock_order.out +79 -0
  109. package/tests/isolation/expected/atomic_fedsession_human_revoke.out +87 -0
  110. package/tests/isolation/expected/atomic_fleet_authority_races.out +469 -0
  111. package/tests/isolation/expected/atomic_fleet_binding_revoke.out +103 -0
  112. package/tests/isolation/expected/atomic_fleet_board_archive.out +117 -0
  113. package/tests/isolation/expected/atomic_fleet_human_fresh_revoke.out +136 -0
  114. package/tests/isolation/expected/atomic_fleet_identity_disposition.out +61 -0
  115. package/tests/isolation/expected/atomic_fleet_lifecycle_races.out +1479 -0
  116. package/tests/isolation/expected/atomic_fleet_onboarding.out +75 -0
  117. package/tests/isolation/expected/atomic_fleet_subtree_lock_order.out +60 -0
  118. package/tests/isolation/expected/ceiling_setter_lock_before_upsert.out +95 -0
  119. package/tests/isolation/expected/claimgate_ownerless_quota.out +36 -0
  120. package/tests/isolation/expected/claimkey_renew_race.out +38 -0
  121. package/tests/isolation/expected/consent_approval_lock.out +43 -0
  122. package/tests/isolation/expected/consent_emit_actual_state.out +37 -0
  123. package/tests/isolation/expected/domain_reclaim_revoke_race.out +40 -0
  124. package/tests/isolation/expected/enroll_identity_board_owner_lock_order.out +55 -0
  125. package/tests/isolation/expected/fedsession_provider_absent_owner_lock.out +37 -0
  126. package/tests/isolation/expected/onboarding_ceiling_lock_order.out +123 -0
  127. package/tests/isolation/expected/outbox_wake_ordering.out +86 -0
  128. package/tests/isolation/expected/reactivate_revoke_race.out +80 -0
  129. package/tests/isolation/expected/reclaim_revoke_race.out +40 -0
  130. package/tests/isolation/expected/wake_recovery_late_commit.out +89 -0
  131. package/tests/isolation/specs/atomic_fedsession_fleet_lock_order.spec +117 -0
  132. package/tests/isolation/specs/atomic_fedsession_human_revoke.spec +66 -0
  133. package/tests/isolation/specs/atomic_fleet_authority_races.spec +235 -0
  134. package/tests/isolation/specs/atomic_fleet_binding_revoke.spec +127 -0
  135. package/tests/isolation/specs/atomic_fleet_board_archive.spec +134 -0
  136. package/tests/isolation/specs/atomic_fleet_human_fresh_revoke.spec +142 -0
  137. package/tests/isolation/specs/atomic_fleet_identity_disposition.spec +118 -0
  138. package/tests/isolation/specs/atomic_fleet_lifecycle_races.spec +244 -0
  139. package/tests/isolation/specs/atomic_fleet_onboarding.spec +106 -0
  140. package/tests/isolation/specs/atomic_fleet_subtree_lock_order.spec +147 -0
  141. package/tests/isolation/specs/ceiling_setter_lock_before_upsert.spec +117 -0
  142. package/tests/isolation/specs/claimgate_ownerless_quota.spec +77 -0
  143. package/tests/isolation/specs/claimkey_renew_race.spec +67 -0
  144. package/tests/isolation/specs/consent_approval_lock.spec +98 -0
  145. package/tests/isolation/specs/consent_emit_actual_state.spec +79 -0
  146. package/tests/isolation/specs/domain_reclaim_revoke_race.spec +88 -0
  147. package/tests/isolation/specs/enroll_identity_board_owner_lock_order.spec +117 -0
  148. package/tests/isolation/specs/fedsession_provider_absent_owner_lock.spec +63 -0
  149. package/tests/isolation/specs/onboarding_ceiling_lock_order.spec +183 -0
  150. package/tests/isolation/specs/outbox_wake_ordering.spec +202 -0
  151. package/tests/isolation/specs/reactivate_revoke_race.spec +115 -0
  152. package/tests/isolation/specs/reclaim_revoke_race.spec +94 -0
  153. package/tests/isolation/specs/wake_recovery_late_commit.spec +207 -0
  154. package/tests/seeds/20260806000100_agent_board_consent_substrate.sql.pre.sql +19 -0
  155. package/tests/seeds/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql.pre.sql +39 -0
  156. package/tests/seeds/20260901000100_agent_board_atomic_fleet_onboarding.sql.pre.sql +110 -0
@@ -0,0 +1,207 @@
1
+ # message-board#191 — a late-committing writer lands BELOW a naive consumer's
2
+ # persisted cursor, and is STILL recovered by the status-partitioned wake surface.
3
+ #
4
+ # WHY THIS IS NOT A pgTAP FILE. The fact under test is cross-session and needs two
5
+ # SEPARATE COMMITTED transactions with a specific interleaving: a writer that
6
+ # STARTS EARLY (so its `created_at` is low — `now()` is `transaction_timestamp()`,
7
+ # assigned at transaction start) and COMMITS LATE (so it becomes visible only
8
+ # after a consumer has already read and persisted a high-water mark). A pgTAP file
9
+ # runs inside one rolled-back transaction and cannot produce that interleaving at
10
+ # all.
11
+ #
12
+ # WHAT THE TRACE PROVES (all VALUE diffs, not merely trace diffs):
13
+ #
14
+ # * c_first — while `late` is still uncommitted, the consumer sees exactly ONE
15
+ # unhandled item, and it is `mid`'s. This is what makes the cursor it then
16
+ # persists a HIGH-water mark that sits ABOVE the late row.
17
+ # * c_second, column 1 (`late_committer_recovered_by_status`) — the late item IS
18
+ # in `agent_board.v_agent_wake_recovery` the moment it is visible. Nothing was
19
+ # dropped. This is the fix.
20
+ # * c_second, column 2 (`late_committer_skipped_by_timestamp_cursor`) — the late
21
+ # item's `created_at` is NOT above the persisted cursor. This is the defect,
22
+ # measured rather than asserted.
23
+ # * c_second, column 3 (`a_created_at_cursor_read_returns_nothing_new`) — the
24
+ # actual query a naive cursor consumer would run (`created_at > my_cursor`)
25
+ # returns NOTHING, at the same instant the status surface returns the late
26
+ # row. Columns 1 and 3 together are the whole issue in one line.
27
+ #
28
+ # WHY A TIMESTAMP AND NOT `seq`. The sibling probe outbox_wake_ordering.spec
29
+ # already records the `seq` half (`late_committer_holds_a_lower_seq_than_the_
30
+ # persisted_cursor = t`) on `callback_outbox_events`. The surface a wake consumer
31
+ # actually fetches from — the inbox — has NO monotonic column at all; its only
32
+ # ordering-shaped columns are `created_at` / `updated_at`. So the timestamp branch
33
+ # is the live-shaped one, and it is strictly worse than `seq`: a fan-out writes N
34
+ # rows with byte-identical `created_at`, so the cursor cannot even totally order
35
+ # what it DOES see. The unsafe class is "gates on any value assigned before
36
+ # commit", not "gates on seq".
37
+ #
38
+ # MUTATION SIGNALS (each verified):
39
+ # * Remove 'new' from the view's state set in
40
+ # 20260904000100_agent_board_wake_recovery_surface.sql
41
+ # -> `late_committer_recovered_by_status` flips t -> f.
42
+ # * Redefine the view with a `created_at >` cursor filter
43
+ # -> the same column flips t -> f (and pgTAP WR-4/WR-7 go red by name).
44
+ # * Swap the transaction START order in the permutation below so `mid` begins
45
+ # and inserts BEFORE `late` begins -> `late_committer_skipped_by_timestamp_
46
+ # cursor` and `a_created_at_cursor_read_returns_nothing_new` flip t -> f,
47
+ # proving the `t` was earned by the interleaving and not by construction.
48
+ #
49
+ # IDENTITY. The consumer session binds `request.jwt.claim.sub` to the recipient's
50
+ # auth user (step `c_auth`), because the view scopes on `current_agent_id()`. This
51
+ # is load-bearing, not ceremony: without it the view returns nothing, every column
52
+ # below reads `f`, and the probe would look like a clean failure of the fix rather
53
+ # than a fixture that never had an identity.
54
+ #
55
+ # WHAT THIS SPEC DOES NOT COVER: role-level RLS. Sessions run as the superuser, so
56
+ # the inbox_items policies are bypassed and only the view's own scoping applies;
57
+ # this probe owns visibility-vs-commit-order. `security_invoker`, the grant set,
58
+ # and cross-agent isolation on the same view are pinned in
59
+ # agent_board_wake_recovery.test.sql (WR-2, WR-3, WR-6). Naming which probe owns
60
+ # which property stops each from being assumed to cover the other.
61
+ #
62
+ # Absolute timestamps and ids are never printed — only derived booleans — so the
63
+ # golden is deterministic.
64
+
65
+ setup
66
+ {
67
+ create table public.iso191_marks(kind text primary key, val timestamptz);
68
+
69
+ -- v_agent_wake_recovery scopes on current_agent_id(), which resolves auth.uid()
70
+ -- from the request.jwt.claim.sub GUC. Without a bound identity the view returns
71
+ -- NOTHING and this probe would measure an empty set while looking like a pass --
72
+ -- so the recipient gets a real auth user and the consumer session binds to it.
73
+ insert into auth.users(id, email) values
74
+ ('19116000-0000-4000-8000-000000000002', 'iso191-recipient@example.test');
75
+
76
+ insert into agent_board.agents(id, agent_type, handle, display_name, status, auth_user_id, owner_user_id)
77
+ values ('19110000-0000-4000-8000-000000000001', 'agent', 'iso191-sender', 'Iso191 Sender', 'active', null, null),
78
+ ('19110000-0000-4000-8000-000000000002', 'agent', 'iso191-recipient', 'Iso191 Recipient', 'active',
79
+ '19116000-0000-4000-8000-000000000002', '19116000-0000-4000-8000-000000000002');
80
+
81
+ insert into agent_board.boards(id, slug, name, created_by_agent_id, visibility, status)
82
+ values ('19111000-0000-4000-8000-000000000001', 'iso191-board', 'Iso191 Board',
83
+ '19110000-0000-4000-8000-000000000001', 'open', 'active');
84
+
85
+ insert into agent_board.threads(id, thread_type, created_by_agent_id, subject, board_id)
86
+ values ('19112000-0000-4000-8000-000000000001', 'coordination',
87
+ '19110000-0000-4000-8000-000000000001', 'Iso191 thread',
88
+ '19111000-0000-4000-8000-000000000001');
89
+
90
+ -- One message per inbox item: delivery_recipients is UNIQUE on
91
+ -- (recipient_agent_id, canonical_source_key).
92
+ insert into agent_board.messages(id, thread_id, author_agent_id, message_sequence, message_type, body)
93
+ values ('19113000-0000-4000-8000-00000000000e', '19112000-0000-4000-8000-000000000001',
94
+ '19110000-0000-4000-8000-000000000001', 1, 'comment', 'iso191 late message'),
95
+ ('19113000-0000-4000-8000-00000000000d', '19112000-0000-4000-8000-000000000001',
96
+ '19110000-0000-4000-8000-000000000001', 2, 'comment', 'iso191 mid message');
97
+
98
+ -- Scope by the test-only recipient, never a repo-wide count: an unscoped count
99
+ -- is polluted by earlier fixtures and fails looking like a real defect. The
100
+ -- recipient is brand new here, so this neutralisation is belt-and-braces — it
101
+ -- states the invariant the later counts rely on instead of assuming it.
102
+ update agent_board.inbox_items
103
+ set action_state = 'acknowledged', updated_at = now()
104
+ where recipient_agent_id = '19110000-0000-4000-8000-000000000002'
105
+ and action_state in ('new', 'restored', 'deferred');
106
+ }
107
+
108
+ teardown
109
+ {
110
+ delete from agent_board.inbox_items where recipient_agent_id = '19110000-0000-4000-8000-000000000002';
111
+ delete from agent_board.delivery_recipients where recipient_agent_id = '19110000-0000-4000-8000-000000000002';
112
+ delete from agent_board.messages where thread_id = '19112000-0000-4000-8000-000000000001';
113
+ delete from agent_board.threads where id = '19112000-0000-4000-8000-000000000001';
114
+ delete from agent_board.boards where id = '19111000-0000-4000-8000-000000000001';
115
+ delete from agent_board.agents where handle like 'iso191-%';
116
+ drop table public.iso191_marks;
117
+ }
118
+
119
+ # "late" STARTS FIRST and COMMITS LAST. `created_at` defaults to now(), which is
120
+ # transaction_timestamp(), so its row carries a timestamp BELOW everything that
121
+ # commits in between — while becoming visible only after the consumer has already
122
+ # read.
123
+ session "late"
124
+ step "l_begin" { BEGIN; }
125
+ step "l_insert" {
126
+ with dr as (
127
+ insert into agent_board.delivery_recipients(id, delivery_mode, recipient_agent_id, source_kind, source_message_id)
128
+ values ('19114000-0000-4000-8000-00000000000e', 'direct',
129
+ '19110000-0000-4000-8000-000000000002', 'message',
130
+ '19113000-0000-4000-8000-00000000000e')
131
+ returning id
132
+ )
133
+ insert into agent_board.inbox_items(
134
+ id, delivery_recipient_id, recipient_agent_id, delivery_mode, source_kind, source_message_id,
135
+ subject, preview, priority, action_state, visible_in_inbox, visible_in_board_feed, fetch_descriptor)
136
+ select '19115000-0000-4000-8000-00000000000e', dr.id,
137
+ '19110000-0000-4000-8000-000000000002', 'direct', 'message',
138
+ '19113000-0000-4000-8000-00000000000e',
139
+ 'Iso191 late', 'iso191 late preview', 'high', 'new', true, false, jsonb_build_object()
140
+ from dr;
141
+ }
142
+ step "l_mark" {
143
+ insert into public.iso191_marks(kind, val)
144
+ select 'late_created_at', created_at from agent_board.inbox_items
145
+ where id = '19115000-0000-4000-8000-00000000000e';
146
+ }
147
+ step "l_commit" { COMMIT; }
148
+
149
+ # "mid" starts after "late" and commits before it. Its row is what lifts the
150
+ # consumer's high-water mark above the late row.
151
+ session "mid"
152
+ step "m_begin" { BEGIN; }
153
+ step "m_insert" {
154
+ with dr as (
155
+ insert into agent_board.delivery_recipients(id, delivery_mode, recipient_agent_id, source_kind, source_message_id)
156
+ values ('19114000-0000-4000-8000-00000000000d', 'direct',
157
+ '19110000-0000-4000-8000-000000000002', 'message',
158
+ '19113000-0000-4000-8000-00000000000d')
159
+ returning id
160
+ )
161
+ insert into agent_board.inbox_items(
162
+ id, delivery_recipient_id, recipient_agent_id, delivery_mode, source_kind, source_message_id,
163
+ subject, preview, priority, action_state, visible_in_inbox, visible_in_board_feed, fetch_descriptor)
164
+ select '19115000-0000-4000-8000-00000000000d', dr.id,
165
+ '19110000-0000-4000-8000-000000000002', 'direct', 'message',
166
+ '19113000-0000-4000-8000-00000000000d',
167
+ 'Iso191 mid', 'iso191 mid preview', 'high', 'new', true, false, jsonb_build_object()
168
+ from dr;
169
+ }
170
+ step "m_commit" { COMMIT; }
171
+
172
+ # "consumer" is the wake worker. Autocommit: each read is its own transaction,
173
+ # exactly as a worker loop behaves.
174
+ session "consumer"
175
+ # Session-level (is_local = false) so the binding survives across this session's
176
+ # autocommit statements.
177
+ step "c_auth" {
178
+ select set_config('request.jwt.claim.sub', '19116000-0000-4000-8000-000000000002', false)
179
+ = '19116000-0000-4000-8000-000000000002' as consumer_session_bound_to_recipient;
180
+ }
181
+ step "c_first" {
182
+ select count(*) = 1 as first_read_sees_one_unhandled_item,
183
+ bool_and(w.inbox_item_id = '19115000-0000-4000-8000-00000000000d') as the_visible_item_is_the_committed_one
184
+ from agent_board.v_agent_wake_recovery w
185
+ where w.recipient_agent_id = '19110000-0000-4000-8000-000000000002';
186
+ }
187
+ # The high-water mark a naive cursor consumer would persist from that first read.
188
+ step "c_persist" {
189
+ insert into public.iso191_marks(kind, val)
190
+ select 'naive_cursor', max(created_at) from agent_board.v_agent_wake_recovery
191
+ where recipient_agent_id = '19110000-0000-4000-8000-000000000002';
192
+ }
193
+ step "c_second" {
194
+ select exists (select 1 from agent_board.v_agent_wake_recovery w
195
+ where w.recipient_agent_id = '19110000-0000-4000-8000-000000000002'
196
+ and w.inbox_item_id = '19115000-0000-4000-8000-00000000000e')
197
+ as late_committer_recovered_by_status,
198
+ (select l.val from public.iso191_marks l where l.kind = 'late_created_at')
199
+ <= (select c.val from public.iso191_marks c where c.kind = 'naive_cursor')
200
+ as late_committer_skipped_by_timestamp_cursor,
201
+ not exists (select 1 from agent_board.v_agent_wake_recovery w
202
+ where w.recipient_agent_id = '19110000-0000-4000-8000-000000000002'
203
+ and w.created_at > (select c.val from public.iso191_marks c where c.kind = 'naive_cursor'))
204
+ as a_created_at_cursor_read_returns_nothing_new;
205
+ }
206
+
207
+ permutation "c_auth" "l_begin" "l_insert" "l_mark" "m_begin" "m_insert" "m_commit" "c_first" "c_persist" "l_commit" "c_second"
@@ -0,0 +1,19 @@
1
+ -- message-board#103 — pre-MBCONSENT-A seed: a legacy PENDING membership that predates the consent
2
+ -- ledger, so the shipped backfill (20260806000100, board-authored neutral event per pending
3
+ -- subject) has a real row to act on. Applied by the test runner immediately BEFORE that migration
4
+ -- (schema is at the migration-N-1 state here — board_membership_events does not exist yet).
5
+ --
6
+ -- Distinctive fixed ids (bf103-*) so the row does not collide with any test's own fixtures. The
7
+ -- board's creator differs from the member, satisfying the backfill's board-authored predicate.
8
+ insert into agent_board.agents(id, agent_type, kind, handle, display_name, status)
9
+ values
10
+ ('bf103aaa-0000-0000-0000-000000000001', 'agent', 'agent', 'bf103-creator', 'BF103 Creator', 'active'),
11
+ ('bf103aaa-0000-0000-0000-000000000002', 'agent', 'agent', 'bf103-member', 'BF103 Member', 'active')
12
+ on conflict (id) do nothing;
13
+
14
+ insert into agent_board.boards(id, slug, name, description, created_by_agent_id)
15
+ values ('bf103bbb-0000-0000-0000-0000000000b1', 'bf103-board', 'BF103 Board', 'consent backfill seed', 'bf103aaa-0000-0000-0000-000000000001')
16
+ on conflict (id) do nothing;
17
+
18
+ insert into agent_board.board_memberships(board_id, agent_id, membership_state, created_by_agent_id, role)
19
+ values ('bf103bbb-0000-0000-0000-0000000000b1', 'bf103aaa-0000-0000-0000-000000000002', 'requested', 'bf103aaa-0000-0000-0000-000000000001', 'member');
@@ -0,0 +1,39 @@
1
+ -- Pre-migration seed (message-board#103 hook) for the MINTHARDEN fingerprint backfill.
2
+ --
3
+ -- Runs immediately BEFORE 20260822000200 — i.e. AFTER 20260822000100 installed the
4
+ -- integrity trigger and helpers. It simulates a row that predates the trigger: a
5
+ -- string-era mint child signing key with a CANONICAL public_key but a fingerprint
6
+ -- derived over the base64url STRING (the 20260718001000:203 bug). Such a row can only
7
+ -- exist by bypassing the new trigger, so it is inserted under session_replication_role=
8
+ -- replica — exactly reproducing a legacy row created before the trigger existed.
9
+ --
10
+ -- The backfill (20260822000200) must re-derive its fingerprint to the raw-byte value;
11
+ -- agent_board_mint_harden_backfill.test.sql asserts the SHIPPED backfill did so
12
+ -- (behavioural — a mutation removing the UPDATE leaves the stale string fingerprint and
13
+ -- the assertion fails). Distinctive fixed ids so nothing collides with other fixtures.
14
+ do $$
15
+ declare
16
+ v_owner uuid;
17
+ v_agent uuid := 'fb111111-0000-4b11-8000-000000000200';
18
+ v_key uuid := 'fb111111-0000-4b11-8000-000000000201';
19
+ v_pub text := public.test_pubkey('fbfill-legacy-1');
20
+ -- The OLD string-era derivation: sha256 over the base64url STRING bytes (not the raw key).
21
+ v_string_fp text := 'sha256:' || rtrim(translate(encode(
22
+ extensions.digest(convert_to(public.test_pubkey('fbfill-legacy-1'), 'UTF8'), 'sha256'), 'base64'), '+/', '-_'), '=');
23
+ begin
24
+ v_owner := public.ensure_github_user(
25
+ 'fbfill-legacy-owner', 'fbfill-legacy-owner@example.test', 'fbfill-legacy-owner', 'FB Legacy Owner');
26
+
27
+ insert into agent_board.agents(id, agent_type, kind, handle, display_name, status, auth_user_id, owner_user_id)
28
+ values (v_agent, 'agent', 'agent'::agent_board.agent_kind, 'fbfill-legacy-agent', 'FB Legacy Agent',
29
+ 'active', v_owner, v_owner);
30
+
31
+ -- Bypass the 20260822000100 integrity trigger to plant the legacy (string-fingerprint) row.
32
+ set local session_replication_role = replica;
33
+ insert into agent_board.agent_signing_keys(
34
+ id, agent_id, key_id, key_version, public_key, public_key_format, fingerprint, status, proof_state, metadata)
35
+ values (v_key, v_agent, 'delegated-ephemeral', 1, v_pub, 'raw_ed25519_base64url', v_string_fp,
36
+ 'active', 'proven', jsonb_build_object('legacyStringFingerprint', true));
37
+ set local session_replication_role = default;
38
+ end
39
+ $$;
@@ -0,0 +1,110 @@
1
+ -- Legacy rows present before Consiliency/message-board#162. The migration must
2
+ -- derive replay metadata from the stored federation facts, not from a fixture
3
+ -- inserted after deployment.
4
+ insert into agent_board.identity_providers(
5
+ id, issuer, audience, supported, policy_approved,
6
+ allowed_scopes, allowed_board_scopes, allowed_agent_kinds, subject_claim
7
+ ) values (
8
+ 'legacy-atomic-provider', 'https://legacy.atomic.test', 'message-board', true, false,
9
+ ARRAY['board:read','board:send'], ARRAY['member'], ARRAY['agent'], 'uid'
10
+ );
11
+
12
+ insert into auth.users(id, instance_id, aud, role, email, created_at, updated_at)
13
+ values (
14
+ '16200000-0000-0000-0000-000000000099',
15
+ '00000000-0000-0000-0000-000000000000',
16
+ 'authenticated', 'authenticated', 'atomic-backfill@example.test', now(), now()
17
+ ) on conflict (id) do nothing;
18
+
19
+ insert into agent_board.agents(id, agent_type, kind, handle, display_name, status, auth_user_id, metadata)
20
+ values (
21
+ '16200000-0000-0000-0000-000000000001', 'agent', 'agent',
22
+ 'atomic-backfill-complete', 'Atomic Backfill Complete', 'active',
23
+ '16200000-0000-0000-0000-000000000099',
24
+ jsonb_build_object('federation', jsonb_build_object(
25
+ 'issuer', 'https://legacy.atomic.test',
26
+ 'audience', 'message-board',
27
+ 'subject', 'legacy-complete',
28
+ 'actorSub', null,
29
+ 'providerId', 'legacy-atomic-provider',
30
+ 'signingKeyFingerprint', 'sha256:QnDuPiA99wAIcCJlk4ZS_ZJV4gQPW8cX0LVmKQJNl0I',
31
+ 'requestedScopes', jsonb_build_array('board:read', 'board:send'),
32
+ 'requestedBoardScopes', jsonb_build_array('member')
33
+ ))
34
+ ), (
35
+ '16200000-0000-0000-0000-000000000002', 'agent', 'agent',
36
+ 'atomic-backfill-ambiguous', 'Atomic Backfill Ambiguous', 'active', null,
37
+ jsonb_build_object('federation', jsonb_build_object(
38
+ 'issuer', 'https://legacy.atomic.test',
39
+ 'audience', 'message-board',
40
+ 'actorSub', 'delegated-actor',
41
+ 'providerId', 'legacy-atomic-provider',
42
+ 'requestedScopes', jsonb_build_array('board:read'),
43
+ 'requestedBoardScopes', jsonb_build_array('member'),
44
+ 'replay', jsonb_build_object(
45
+ 'issuer', 'https://forged.atomic.test',
46
+ 'audience', jsonb_build_array('message-board'),
47
+ 'subject', 'forged-ambiguous-subject',
48
+ 'requestedSlug', 'atomic-backfill-ambiguous',
49
+ 'agentKind', 'agent',
50
+ 'actorSub', null,
51
+ 'requestedScopes', jsonb_build_array('admin'),
52
+ 'requestedBoardScopes', jsonb_build_array('owner'),
53
+ 'providerId', 'legacy-atomic-provider'
54
+ )
55
+ ))
56
+ );
57
+
58
+ insert into agent_board.agent_signing_keys(
59
+ id, agent_id, key_id, key_version, public_key, public_key_format,
60
+ fingerprint, status, proof_state
61
+ ) values (
62
+ '16200000-0000-0000-0000-000000000011',
63
+ '16200000-0000-0000-0000-000000000001',
64
+ 'legacy-atomic-key', 1,
65
+ 'TauOMxwU-TW-deqQjil1DLDy_AvJrfoG_eXGwwvaRUk',
66
+ 'raw_ed25519_base64url',
67
+ 'sha256:QnDuPiA99wAIcCJlk4ZS_ZJV4gQPW8cX0LVmKQJNl0I',
68
+ 'active', 'proven'
69
+ );
70
+
71
+ insert into agent_board.enrollment_federation_audit(
72
+ id, issuer, audience, subject, human_identity_claim, provider_id,
73
+ agent_id, auth_user_id, requested_scopes, granted_scopes
74
+ ) values (
75
+ '16200000-0000-0000-0000-000000000021',
76
+ 'https://legacy.atomic.test', 'message-board', 'legacy-complete',
77
+ jsonb_build_object(
78
+ 'iss', 'https://legacy.atomic.test',
79
+ 'aud', jsonb_build_array('secondary-audience', 'message-board'),
80
+ 'uid', 'legacy-complete',
81
+ 'iat', 100,
82
+ 'jti', 'legacy-original'
83
+ ),
84
+ 'legacy-atomic-provider',
85
+ '16200000-0000-0000-0000-000000000001',
86
+ '16200000-0000-0000-0000-000000000099',
87
+ ARRAY['board:read','board:send'], ARRAY['board:read']
88
+ );
89
+
90
+ -- Exercise the historical authenticated mutation path before the protecting
91
+ -- trigger exists. The shipped migration must overwrite this forged replay and
92
+ -- derive authorization-bearing scopes only from the append-only audit row.
93
+ set role authenticated;
94
+ select set_config('request.jwt.claim.sub', '16200000-0000-0000-0000-000000000099', false);
95
+ select count(*) from agent_board.register_agent(
96
+ 'agent', 'atomic-backfill-complete', 'Atomic Backfill Complete', 'active',
97
+ jsonb_build_object('federation', jsonb_build_object(
98
+ 'issuer', 'https://legacy.atomic.test',
99
+ 'audience', 'message-board',
100
+ 'subject', 'legacy-complete',
101
+ 'actorSub', 'forged-actor',
102
+ 'providerId', 'legacy-atomic-provider',
103
+ 'signingKeyFingerprint', 'sha256:QnDuPiA99wAIcCJlk4ZS_ZJV4gQPW8cX0LVmKQJNl0I',
104
+ 'requestedScopes', jsonb_build_array('admin'),
105
+ 'requestedBoardScopes', jsonb_build_array('owner'),
106
+ 'replay', jsonb_build_object('subject', 'forged-subject', 'requestedScopes', jsonb_build_array('admin'))
107
+ ))
108
+ );
109
+ reset role;
110
+ select set_config('request.jwt.claim.sub', '', false);