@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.
- package/fixtures/already-applied.json +160 -0
- package/migrations/20260731000100_agent_board_agentsdir_column_scope.sql +88 -0
- package/migrations/20260801000100_agent_board_leaveidem_terminal_noop.sql +317 -0
- package/migrations/20260802000100_agent_board_rolereset_reentry_clamp.sql +237 -0
- package/migrations/20260803000100_agent_board_rowid_total_order.sql +1201 -0
- package/migrations/20260804000100_agent_board_hygiene_actor_binding_rls.sql +229 -0
- package/migrations/20260806000100_agent_board_consent_substrate.sql +339 -0
- package/migrations/20260807000100_agent_board_consent_emission.sql +619 -0
- package/migrations/20260808000100_agent_board_rejectsem.sql +145 -0
- package/migrations/20260809000100_agent_board_consent_gating.sql +492 -0
- package/migrations/20260810000100_agent_board_consent_locking.sql +485 -0
- package/migrations/20260811000100_agent_board_retention_metadata_revoke.sql +23 -0
- package/migrations/20260812000100_agent_board_pubexec_class_revoke.sql +54 -0
- package/migrations/20260813000100_agent_board_invitepolicy_block.sql +420 -0
- package/migrations/20260814000100_agent_board_obsgate_thread_post_policy.sql +239 -0
- package/migrations/20260815000100_agent_board_obsgate2_authoring_gates.sql +287 -0
- package/migrations/20260816000100_agent_board_obsgate3_task_lifecycle.sql +118 -0
- package/migrations/20260817000100_agent_board_obsgate4_claims_lifecycle.sql +250 -0
- package/migrations/20260818000100_agent_board_fleet_machines_rls_correlation.sql +43 -0
- package/migrations/20260819000100_agent_board_resolve_noop_retire.sql +166 -0
- package/migrations/20260820000100_agent_board_roomdeleg_lane_b.sql +206 -0
- package/migrations/20260821000100_agent_board_claimgate_claimkey.sql +372 -0
- package/migrations/20260822000100_agent_board_mint_harden.sql +710 -0
- package/migrations/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql +207 -0
- package/migrations/20260823000100_agent_board_fleet_machine_hijack.sql +106 -0
- package/migrations/20260824000100_agent_board_claimgate_shadow.sql +443 -0
- package/migrations/20260825000100_agent_board_claimgate_enforce.sql +660 -0
- package/migrations/20260826000100_agent_board_fleet_machine_reactivation.sql +514 -0
- package/migrations/20260828000100_agent_board_delegation_cap_ceiling.sql +474 -0
- package/migrations/20260829000100_agent_board_fleet_machine_reclaim.sql +432 -0
- package/migrations/20260830000100_agent_board_delegation_sole_writer.sql +41 -0
- package/migrations/20260831000100_agent_board_fleet_domain_reclaim.sql +389 -0
- package/migrations/20260901000100_agent_board_atomic_fleet_onboarding.sql +1496 -0
- package/migrations/20260901000110_agent_board_fleet_onboarding_ceiling_lock.sql +604 -0
- package/migrations/20260901000120_agent_board_human_revocation_evidence_completeness.sql +78 -0
- package/migrations/20260901000130_agent_board_fedsession_human_revocation_gate.sql +74 -0
- package/migrations/20260901000140_agent_board_fedsession_board_owner_lock_order.sql +102 -0
- package/migrations/20260902000100_agent_board_outbox_wake_ordering.sql +184 -0
- package/migrations/20260903000100_agent_board_enroll_identity_board_owner_lock_order.sql +604 -0
- package/migrations/20260903000200_agent_board_fedsession_provider_absence_gate.sql +177 -0
- package/migrations/20260904000100_agent_board_wake_recovery_surface.sql +155 -0
- package/package.json +1 -1
- package/schema-manifest.json +363 -3
- package/src/tooling.test.mjs +113 -2
- package/tests/agent_board_actorspoof.test.sql +349 -0
- package/tests/agent_board_agentsdir.test.sql +154 -0
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +623 -0
- package/tests/agent_board_atomic_fleet_onboarding_backfill.test.sql +142 -0
- package/tests/agent_board_atomic_fleet_onboarding_runtime.test.sql +1250 -0
- package/tests/agent_board_authbound_boundary.test.sql +9 -1
- package/tests/agent_board_claimgate_enforce.test.sql +464 -0
- package/tests/agent_board_claimgate_predicate.test.sql +360 -0
- package/tests/agent_board_claimkey.test.sql +229 -0
- package/tests/agent_board_consent_backfill.test.sql +48 -0
- package/tests/agent_board_consent_emission.test.sql +601 -0
- package/tests/agent_board_consent_gating.test.sql +489 -0
- package/tests/agent_board_consent_substrate.test.sql +720 -0
- package/tests/agent_board_delegation_cap_ceiling.test.sql +303 -0
- package/tests/agent_board_delegation_enforcement.test.sql +55 -6
- package/tests/agent_board_delegation_policies.test.sql +107 -1
- package/tests/agent_board_enroll_federated_identity.test.sql +3 -3
- package/tests/agent_board_enroll_pop_binding.test.sql +13 -3
- package/tests/agent_board_enrollment_audit_content.test.sql +11 -7
- package/tests/agent_board_federated_lock_order_class.test.sql +390 -0
- package/tests/agent_board_fedsession.test.sql +51 -5
- package/tests/agent_board_fedsession_provider_absence_gate.test.sql +170 -0
- package/tests/agent_board_fleet_domain_reclaim.test.sql +419 -0
- package/tests/agent_board_fleet_machine_hijack.test.sql +190 -0
- package/tests/agent_board_fleet_machine_reactivation.test.sql +426 -0
- package/tests/agent_board_fleet_machine_reclaim.test.sql +509 -0
- package/tests/agent_board_fleet_machines_rls.test.sql +79 -0
- package/tests/agent_board_fleet_policy.test.sql +2 -2
- package/tests/agent_board_grants_hygiene.test.sql +408 -0
- package/tests/agent_board_identity_providers.test.sql +9 -0
- package/tests/agent_board_inbox_reads.test.sql +45 -31
- package/tests/agent_board_inbox_rpcs.test.sql +44 -34
- package/tests/agent_board_invitepolicy.test.sql +205 -0
- package/tests/agent_board_leaveidem.test.sql +194 -0
- package/tests/agent_board_mint_fleet_agent_session.test.sql +10 -10
- package/tests/agent_board_mint_harden.test.sql +303 -0
- package/tests/agent_board_mint_harden_backfill.test.sql +95 -0
- package/tests/agent_board_obsgate.test.sql +178 -0
- package/tests/agent_board_obsgate2.test.sql +153 -0
- package/tests/agent_board_obsgate3.test.sql +170 -0
- package/tests/agent_board_obsgate4.test.sql +196 -0
- package/tests/agent_board_outbox_wake_ordering.test.sql +452 -0
- package/tests/agent_board_portal_offline_invariant.test.sql +14 -3
- package/tests/agent_board_pubexec_class.test.sql +53 -0
- package/tests/agent_board_rejectsem.test.sql +271 -0
- package/tests/agent_board_resigning_refresh.test.sql +1 -1
- package/tests/agent_board_resolve_noop_retire.test.sql +71 -0
- package/tests/agent_board_retention_metadata_acl.test.sql +53 -0
- package/tests/agent_board_revoke_agent_subtree.test.sql +3 -3
- package/tests/agent_board_rolereset.test.sql +351 -0
- package/tests/agent_board_roomdeleg.test.sql +5 -3
- package/tests/agent_board_roomdeleg_lane_b.test.sql +144 -0
- package/tests/agent_board_roomlink.test.sql +27 -11
- package/tests/agent_board_rowid.test.sql +1274 -0
- package/tests/agent_board_signature_rls_reads.test.sql +3 -3
- package/tests/agent_board_signed_write_gateway.test.sql +3 -3
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +2 -2
- package/tests/agent_board_signing_challenge_reaper.test.sql +2 -2
- package/tests/agent_board_signing_proof_rpcs.test.sql +4 -4
- package/tests/agent_board_signing_store.test.sql +6 -6
- package/tests/agent_board_wake_recovery.test.sql +756 -0
- package/tests/fixtures/isolation-substrate.sql +93 -0
- package/tests/fixtures/test-prereqs.sql +33 -0
- package/tests/isolation/expected/atomic_fedsession_fleet_lock_order.out +79 -0
- package/tests/isolation/expected/atomic_fedsession_human_revoke.out +87 -0
- package/tests/isolation/expected/atomic_fleet_authority_races.out +469 -0
- package/tests/isolation/expected/atomic_fleet_binding_revoke.out +103 -0
- package/tests/isolation/expected/atomic_fleet_board_archive.out +117 -0
- package/tests/isolation/expected/atomic_fleet_human_fresh_revoke.out +136 -0
- package/tests/isolation/expected/atomic_fleet_identity_disposition.out +61 -0
- package/tests/isolation/expected/atomic_fleet_lifecycle_races.out +1479 -0
- package/tests/isolation/expected/atomic_fleet_onboarding.out +75 -0
- package/tests/isolation/expected/atomic_fleet_subtree_lock_order.out +60 -0
- package/tests/isolation/expected/ceiling_setter_lock_before_upsert.out +95 -0
- package/tests/isolation/expected/claimgate_ownerless_quota.out +36 -0
- package/tests/isolation/expected/claimkey_renew_race.out +38 -0
- package/tests/isolation/expected/consent_approval_lock.out +43 -0
- package/tests/isolation/expected/consent_emit_actual_state.out +37 -0
- package/tests/isolation/expected/domain_reclaim_revoke_race.out +40 -0
- package/tests/isolation/expected/enroll_identity_board_owner_lock_order.out +55 -0
- package/tests/isolation/expected/fedsession_provider_absent_owner_lock.out +37 -0
- package/tests/isolation/expected/onboarding_ceiling_lock_order.out +123 -0
- package/tests/isolation/expected/outbox_wake_ordering.out +86 -0
- package/tests/isolation/expected/reactivate_revoke_race.out +80 -0
- package/tests/isolation/expected/reclaim_revoke_race.out +40 -0
- package/tests/isolation/expected/wake_recovery_late_commit.out +89 -0
- package/tests/isolation/specs/atomic_fedsession_fleet_lock_order.spec +117 -0
- package/tests/isolation/specs/atomic_fedsession_human_revoke.spec +66 -0
- package/tests/isolation/specs/atomic_fleet_authority_races.spec +235 -0
- package/tests/isolation/specs/atomic_fleet_binding_revoke.spec +127 -0
- package/tests/isolation/specs/atomic_fleet_board_archive.spec +134 -0
- package/tests/isolation/specs/atomic_fleet_human_fresh_revoke.spec +142 -0
- package/tests/isolation/specs/atomic_fleet_identity_disposition.spec +118 -0
- package/tests/isolation/specs/atomic_fleet_lifecycle_races.spec +244 -0
- package/tests/isolation/specs/atomic_fleet_onboarding.spec +106 -0
- package/tests/isolation/specs/atomic_fleet_subtree_lock_order.spec +147 -0
- package/tests/isolation/specs/ceiling_setter_lock_before_upsert.spec +117 -0
- package/tests/isolation/specs/claimgate_ownerless_quota.spec +77 -0
- package/tests/isolation/specs/claimkey_renew_race.spec +67 -0
- package/tests/isolation/specs/consent_approval_lock.spec +98 -0
- package/tests/isolation/specs/consent_emit_actual_state.spec +79 -0
- package/tests/isolation/specs/domain_reclaim_revoke_race.spec +88 -0
- package/tests/isolation/specs/enroll_identity_board_owner_lock_order.spec +117 -0
- package/tests/isolation/specs/fedsession_provider_absent_owner_lock.spec +63 -0
- package/tests/isolation/specs/onboarding_ceiling_lock_order.spec +183 -0
- package/tests/isolation/specs/outbox_wake_ordering.spec +202 -0
- package/tests/isolation/specs/reactivate_revoke_race.spec +115 -0
- package/tests/isolation/specs/reclaim_revoke_race.spec +94 -0
- package/tests/isolation/specs/wake_recovery_late_commit.spec +207 -0
- package/tests/seeds/20260806000100_agent_board_consent_substrate.sql.pre.sql +19 -0
- package/tests/seeds/20260822000200_agent_board_mint_harden_fingerprint_backfill.sql.pre.sql +39 -0
- 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);
|