@consiliency/agent-board-schema 1.58.0 → 1.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fixtures/already-applied.json +8 -0
- package/migrations/20261009000100_agent_board_hosted_followups.sql +665 -0
- package/migrations/20261010000100_agent_board_own_post_not_delivered.sql +637 -0
- package/package.json +1 -1
- package/schema-manifest.json +21 -3
- package/src/tooling.test.mjs +9 -2
- package/tests/agent_board_addressing.test.sql +3 -1
- package/tests/agent_board_hosted_delegates.test.sql +148 -4
- package/tests/agent_board_inbox_rpcs.test.sql +5 -2
- package/tests/agent_board_own_post_not_delivered.test.sql +490 -0
- package/tests/agent_board_v2_invariants.test.sql +4 -4
- package/tests/catalog/function_execute_privileges.json +9 -0
- package/tests/catalog/p_metadata_parameters.json +4 -0
- package/tests/isolation/expected/hosted_delegate_add_tombstone_race.out +105 -0
- package/tests/isolation/expected/hosted_delegate_exchange_agent_revoke_race.out +66 -0
- package/tests/isolation/expected/hosted_delegate_tombstone_revoke_race.out +170 -0
- package/tests/isolation/specs/hosted_delegate_add_tombstone_race.spec +102 -0
- package/tests/isolation/specs/hosted_delegate_exchange_agent_revoke_race.spec +92 -0
- package/tests/isolation/specs/hosted_delegate_tombstone_revoke_race.spec +158 -0
package/src/tooling.test.mjs
CHANGED
|
@@ -38,9 +38,9 @@ describe('manifest integrity', () => {
|
|
|
38
38
|
expect(manifest.schemaVersion).toBe(8);
|
|
39
39
|
expect(manifest.minClientVersion).toMatch(/^\d+\.\d+\.\d+$/);
|
|
40
40
|
});
|
|
41
|
-
it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY + one SIGNERLIFECYCLE + one LEDGERAUTHORITY + one CEILINGLOCKCOMMENT + one LEDGERBOARDCREATE + one RECEIPTSUBJECTGATE + one TASKTHREADLOCKORDER + one CLAIMSUBJECTGATE + one FEDSESSIONIDENTITYGATE + one FEDIDENTITYSLUG + one INVITENODOWNGRADE + one SIGNEDWRITEFLAG + one SIGNEDWRITEMODEL + one RTCREDCLAIM + one ORIGINPOSTURE + one BOARDCLAIMPOLICY + one HEARTBEATPRESERVE + one LIVENESSDENOM + one OWNERLESSRENEWTTL + one OWNERLESSQUOTACENSUS + one FLEETDOMAINNODOWNGRADE + one OWNERLESSQUOTACONVERGE + one ADVISORYREVERTRESET + one CLAIMPOLICYRACE + one CLAIMPOLICYLOCKGRANT + one SESSIONCHALLENGESUPERSEDE + one ROOMCONTRACT + one CALLBACKCONSUMER + one ENROLLTOKENSINGLEUSE + one ROOMCONTRACT-ACTIVEFIRST + one ONBOARD + one HOSTED forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 =
|
|
41
|
+
it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY + one SIGNERLIFECYCLE + one LEDGERAUTHORITY + one CEILINGLOCKCOMMENT + one LEDGERBOARDCREATE + one RECEIPTSUBJECTGATE + one TASKTHREADLOCKORDER + one CLAIMSUBJECTGATE + one FEDSESSIONIDENTITYGATE + one FEDIDENTITYSLUG + one INVITENODOWNGRADE + one SIGNEDWRITEFLAG + one SIGNEDWRITEMODEL + one RTCREDCLAIM + one ORIGINPOSTURE + one BOARDCLAIMPOLICY + one HEARTBEATPRESERVE + one LIVENESSDENOM + one OWNERLESSRENEWTTL + one OWNERLESSQUOTACENSUS + one FLEETDOMAINNODOWNGRADE + one OWNERLESSQUOTACONVERGE + one ADVISORYREVERTRESET + one CLAIMPOLICYRACE + one CLAIMPOLICYLOCKGRANT + one SESSIONCHALLENGESUPERSEDE + one ROOMCONTRACT + one CALLBACKCONSUMER + one ENROLLTOKENSINGLEUSE + one ROOMCONTRACT-ACTIVEFIRST + one ONBOARD + one HOSTED + one HOSTED-FOLLOWUPS + one OWNPOST forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 158)', () => {
|
|
42
42
|
expect(manifest.migrationCount).toBe(manifest.migrations.length);
|
|
43
|
-
expect(manifest.migrationCount).toBe(
|
|
43
|
+
expect(manifest.migrationCount).toBe(158);
|
|
44
44
|
// MBSESSION switched enforcement on (scoped-send re-grant): the client-visible
|
|
45
45
|
// schema version bumped to 8. MBDELEG tightens delegation and MBFED adds only
|
|
46
46
|
// enrollment-only additive surface (identity_providers registry, enrollment audit,
|
|
@@ -349,6 +349,13 @@ describe('manifest integrity', () => {
|
|
|
349
349
|
// HOSTED (v11): the agent_board_delegate role, delegate instances, the exchange and
|
|
350
350
|
// tombstone RPCs, add_delegate_to_board, the D14 read predicates and the D16 refusals.
|
|
351
351
|
'20261008000100_agent_board_hosted_delegates.sql',
|
|
352
|
+
// HOSTED follow-ups (#591): the owner's verified (iss, sub) on each delegate instance and the
|
|
353
|
+
// revocation marker revoke_human_subtree derives from it; the tombstone's and the exchange's
|
|
354
|
+
// lock order against the subtree revokers (AB15).
|
|
355
|
+
'20261009000100_agent_board_hosted_followups.sql',
|
|
356
|
+
// OWNPOST (#608): an agent's own board post is a feed-only row for it, not an inbox item;
|
|
357
|
+
// a board-wide task fan-out skips its creator; own_inbox callbacks skip feed-only rows.
|
|
358
|
+
'20261010000100_agent_board_own_post_not_delivered.sql',
|
|
352
359
|
]) {
|
|
353
360
|
expect(names.has(f)).toBe(true);
|
|
354
361
|
}
|
|
@@ -29,7 +29,9 @@ begin
|
|
|
29
29
|
values
|
|
30
30
|
(v_actor, 'agent', 'agent', 'addressing-actor-' || v_suffix, 'ADDRESSING Actor', 'active', '{"role":"phase_worker","groupAliases":["ops"]}'::jsonb),
|
|
31
31
|
(v_worker, 'agent', 'agent', 'addressing-worker-' || v_suffix, 'ADDRESSING Worker', 'active', '{"role":"phase_worker","groupAliases":["ops"]}'::jsonb),
|
|
32
|
-
|
|
32
|
+
-- `role: phase_worker` on the reviewer too: since #608 a set descriptor never reaches its own
|
|
33
|
+
-- sender, so the direct-send ambiguity case below needs TWO matching agents besides the actor.
|
|
34
|
+
(v_reviewer, 'agent', 'agent', 'addressing-reviewer-' || v_suffix, 'ADDRESSING Reviewer', 'active', '{"role":"phase_worker","roles":["reviewer"],"groupAliases":["review"]}'::jsonb),
|
|
33
35
|
(v_human, 'human', 'human', 'addressing-human-' || v_suffix, 'ADDRESSING Human', 'active', '{}'::jsonb);
|
|
34
36
|
|
|
35
37
|
insert into agent_board.threads(id, thread_type, created_by_agent_id, subject)
|
|
@@ -21,7 +21,7 @@ set search_path = extensions, public, agent_board;
|
|
|
21
21
|
|
|
22
22
|
begin;
|
|
23
23
|
|
|
24
|
-
select plan(
|
|
24
|
+
select plan(143);
|
|
25
25
|
|
|
26
26
|
-- The test role switches to agent_board_delegate. On PostgreSQL 16+ the role's creator holds
|
|
27
27
|
-- ADMIN OPTION but not SET, so grant SET to the current role for this transaction only (rolled
|
|
@@ -1105,9 +1105,11 @@ select is(
|
|
|
1105
1105
|
'N2: a suspended owner cannot bring up a new delegate instance, and nothing is written');
|
|
1106
1106
|
|
|
1107
1107
|
-- Owner E: never enrolled as a human on this board (no human row), then durably revoked. The
|
|
1108
|
-
-- marker is SEEDED
|
|
1109
|
-
--
|
|
1110
|
-
--
|
|
1108
|
+
-- marker is SEEDED here, by hand, so that E's delegate row stays active and 128 isolates the
|
|
1109
|
+
-- marker arm alone (revoke_human_subtree would also revoke the delegate). The row is the shape
|
|
1110
|
+
-- revoke_human_subtree writes, keyed by the verified (iss, sub). The real revocation of a
|
|
1111
|
+
-- delegate-only owner, which now writes the marker from the instance's recorded (iss, sub)
|
|
1112
|
+
-- (#591 item 4, AB15), is section 24.
|
|
1111
1113
|
-- 127 -- control: an owner with no human row is allowed, as before
|
|
1112
1114
|
select is(
|
|
1113
1115
|
pg_temp.exch(pg_temp.claim('inst_eeeeeeeeeeeeeeee_01', 'grant_eeeeeeeeeeeeee_01_1', 1, 'hosted-owner-e', 'client-e'), 'E1'),
|
|
@@ -1130,5 +1132,147 @@ select is(
|
|
|
1130
1132
|
'42501:owner_inactive 0 0 0',
|
|
1131
1133
|
'N2: the revocation marker alone refuses a new instance, and nothing is written');
|
|
1132
1134
|
|
|
1135
|
+
-- ===========================================================================
|
|
1136
|
+
-- 24. #591 item 4 -- the owner's (iss, sub) on each instance, and the marker it gives
|
|
1137
|
+
-- revoke_human_subtree (AB15); #591 item 9 -- owner_inactive before instance_revoked
|
|
1138
|
+
-- ===========================================================================
|
|
1139
|
+
-- An owner whose only principals are hosted delegates has no enrollment audit row, so before
|
|
1140
|
+
-- 1.59.0 revoke_human_subtree wrote it no marker, and a fresh first exchange passed. Both RPCs now
|
|
1141
|
+
-- record the verified pair, and the revocation derives a marker from it.
|
|
1142
|
+
-- plpgsql, not sql: each read runs as its own query, so it sees what an action earlier in the
|
|
1143
|
+
-- same statement wrote (the reason pg_temp.q exists).
|
|
1144
|
+
create function pg_temp.pair(p_iid text) returns text language plpgsql as $$
|
|
1145
|
+
begin
|
|
1146
|
+
return coalesce((select coalesce(owner_issuer, '(null)') || ' ' || coalesce(owner_subject, '(null)')
|
|
1147
|
+
from agent_board.delegate_instances where instance_id = p_iid), '(no row)');
|
|
1148
|
+
end $$;
|
|
1149
|
+
create function pg_temp.owner_of(p_iid text) returns uuid language plpgsql as $$
|
|
1150
|
+
begin
|
|
1151
|
+
return (select owner_user_id from agent_board.delegate_instances where instance_id = p_iid);
|
|
1152
|
+
end $$;
|
|
1153
|
+
create function pg_temp.markers(p_sub text) returns text language plpgsql as $$
|
|
1154
|
+
begin
|
|
1155
|
+
return (select count(*)::text || coalesce(' ' || string_agg(r.owner_user_id::text, ','), '')
|
|
1156
|
+
from agent_board.fleet_human_revocations r
|
|
1157
|
+
where r.issuer = 'https://portal.hosted.test' and r.subject = p_sub);
|
|
1158
|
+
end $$;
|
|
1159
|
+
-- 130 -- the exchange records the pair (D1 was exchanged in 123)
|
|
1160
|
+
select is(pg_temp.pair('inst_dddddddddddddddd_01'), 'https://portal.hosted.test hosted-owner-d',
|
|
1161
|
+
'AB15: the exchange records the owner''s verified (iss, sub) on the instance');
|
|
1162
|
+
-- 131 -- a revocation before the first exchange records it too (owner H has nothing else)
|
|
1163
|
+
select is(
|
|
1164
|
+
pg_temp.tomb('inst_hhhhhhhhhhhhhhhh_01', 'hosted-owner-h') || ' ' || pg_temp.pair('inst_hhhhhhhhhhhhhhhh_01'),
|
|
1165
|
+
'first=true https://portal.hosted.test hosted-owner-h',
|
|
1166
|
+
'AB15: a pre-exchange tombstone records the owner''s verified (iss, sub) on the row it creates');
|
|
1167
|
+
-- 132 -- once recorded, the pair never changes
|
|
1168
|
+
select is(
|
|
1169
|
+
pg_temp.try($q$update agent_board.delegate_instances set owner_subject = 'someone-else' where instance_id = 'inst_dddddddddddddddd_01'$q$)
|
|
1170
|
+
|| ' ' || pg_temp.pair('inst_dddddddddddddddd_01'),
|
|
1171
|
+
'23514:delegate_instances owner identity never changes once recorded https://portal.hosted.test hosted-owner-d',
|
|
1172
|
+
'AB15: the recorded owner pair is immutable');
|
|
1173
|
+
|
|
1174
|
+
-- Owner G: never enrolled, live; the control owner, and the legacy-row fills.
|
|
1175
|
+
select pg_temp.svc();
|
|
1176
|
+
-- 133 -- a LEGACY row (pre-1.59.0 shape: no pair) is filled by its first exchange
|
|
1177
|
+
select is(
|
|
1178
|
+
pg_temp.exch(pg_temp.claim('inst_gggggggggggggggg_01', 'grant_gggggggggggggg_01_1', 1, 'hosted-owner-g', 'client-g'), 'G1')
|
|
1179
|
+
|| ' ' || pg_temp.try(format($q$insert into agent_board.delegate_instances(instance_id, owner_user_id) values ('inst_gggggggggggggggg_l1', %L), ('inst_gggggggggggggggg_l2', %L)$q$,
|
|
1180
|
+
pg_temp.owner_of('inst_gggggggggggggggg_01'), pg_temp.owner_of('inst_gggggggggggggggg_01')))
|
|
1181
|
+
|| ' ' || pg_temp.pair('inst_gggggggggggggggg_l1')
|
|
1182
|
+
|| ' ' || pg_temp.exch(pg_temp.claim('inst_gggggggggggggggg_l1', 'grant_gggggggggggggg_l1_1', 1, 'hosted-owner-g', 'client-g'), 'GL1')
|
|
1183
|
+
|| ' ' || pg_temp.pair('inst_gggggggggggggggg_l1'),
|
|
1184
|
+
'ok ok (null) (null) ok https://portal.hosted.test hosted-owner-g',
|
|
1185
|
+
'AB15: a legacy instance with no recorded pair is filled by its next exchange');
|
|
1186
|
+
-- 134 -- ...and by a tombstone
|
|
1187
|
+
select is(
|
|
1188
|
+
pg_temp.tomb('inst_gggggggggggggggg_l2', 'hosted-owner-g') || ' ' || pg_temp.pair('inst_gggggggggggggggg_l2'),
|
|
1189
|
+
'first=true https://portal.hosted.test hosted-owner-g',
|
|
1190
|
+
'AB15: a legacy instance with no recorded pair is filled by its tombstone');
|
|
1191
|
+
|
|
1192
|
+
-- Owner F: never enrolled as a human; its ONLY principal is a hosted delegate.
|
|
1193
|
+
-- 135 -- revoke_human_subtree for real, as service_role
|
|
1194
|
+
select is(
|
|
1195
|
+
pg_temp.exch(pg_temp.claim('inst_ffffffffffffffff_01', 'grant_ffffffffffffff_01_1', 1, 'hosted-owner-f', 'client-f'), 'F1')
|
|
1196
|
+
|| ' ' || pg_temp.try(format($q$select agent_board.revoke_human_subtree(%L, %L, 'hosted 591 test')$q$,
|
|
1197
|
+
pg_temp.owner_of('inst_ffffffffffffffff_01'), pg_temp.v('K')))
|
|
1198
|
+
|| ' ' || pg_temp.q(format($q$select status from agent_board.agents where id = %L$q$, pg_temp.v('F1.agent'))),
|
|
1199
|
+
'ok ok revoked',
|
|
1200
|
+
'#591 item 4: an owner with only a hosted delegate is revoked, and its delegate with it');
|
|
1201
|
+
-- 136 -- the marker exists, keyed by the verified pair, naming the owner
|
|
1202
|
+
select is(pg_temp.markers('hosted-owner-f'), '1 ' || pg_temp.owner_of('inst_ffffffffffffffff_01')::text,
|
|
1203
|
+
'#591 item 4: revoking a delegate-only owner writes the durable (iss, sub) marker from the instance''s recorded pair');
|
|
1204
|
+
-- 137 -- so a fresh first exchange is refused, and writes nothing
|
|
1205
|
+
select is(
|
|
1206
|
+
pg_temp.exch(pg_temp.claim('inst_ffffffffffffffff_02', 'grant_ffffffffffffff_02_1', 1, 'hosted-owner-f', 'client-f'), 'x')
|
|
1207
|
+
|| ' ' || pg_temp.n2_rows('inst_ffffffffffffffff_02'),
|
|
1208
|
+
'42501:owner_inactive 0 0 0',
|
|
1209
|
+
'#591 item 4: after the revocation a new first exchange for that owner is refused owner_inactive, and nothing is written');
|
|
1210
|
+
-- 138 -- control: another live owner, never enrolled, still brings up a new instance
|
|
1211
|
+
select is(
|
|
1212
|
+
pg_temp.exch(pg_temp.claim('inst_gggggggggggggggg_02', 'grant_gggggggggggggg_02_1', 1, 'hosted-owner-g', 'client-g'), 'G2')
|
|
1213
|
+
|| ' ' || pg_temp.markers('hosted-owner-g'),
|
|
1214
|
+
'ok 0',
|
|
1215
|
+
'#591 item 4 control: a different, unrevoked owner''s first exchange succeeds and has no marker');
|
|
1216
|
+
-- 139 -- #591 item 9: a revoked owner AND a tombstoned instance -> owner_inactive, not instance_revoked
|
|
1217
|
+
select is(
|
|
1218
|
+
pg_temp.tomb('inst_ffffffffffffffff_01', 'hosted-owner-f')
|
|
1219
|
+
|| ' ' || pg_temp.exch(pg_temp.claim('inst_ffffffffffffffff_01', 'grant_ffffffffffffff_01_2', 2, 'hosted-owner-f', 'client-f'), 'x'),
|
|
1220
|
+
'first=true 42501:owner_inactive',
|
|
1221
|
+
'AB14 precedence: the owner check runs before the instance''s own refusals -- a tombstoned instance of a revoked owner is refused owner_inactive, not instance_revoked');
|
|
1222
|
+
-- 140 -- owner H, whose only row is a pre-exchange tombstone (131): its revocation marks it too
|
|
1223
|
+
select is(
|
|
1224
|
+
pg_temp.try(format($q$select agent_board.revoke_human_subtree(%L, %L, 'hosted 591 test')$q$,
|
|
1225
|
+
pg_temp.owner_of('inst_hhhhhhhhhhhhhhhh_01'), pg_temp.v('K')))
|
|
1226
|
+
|| ' ' || pg_temp.markers('hosted-owner-h')
|
|
1227
|
+
|| ' ' || pg_temp.exch(pg_temp.claim('inst_hhhhhhhhhhhhhhhh_02', 'grant_hhhhhhhhhhhhhh_02_1', 1, 'hosted-owner-h', 'client-h'), 'x')
|
|
1228
|
+
|| ' ' || pg_temp.n2_rows('inst_hhhhhhhhhhhhhhhh_02'),
|
|
1229
|
+
'ok 1 ' || pg_temp.owner_of('inst_hhhhhhhhhhhhhhhh_01')::text || ' 42501:owner_inactive 0 0 0',
|
|
1230
|
+
'#591 item 4: an owner whose only row is a pre-exchange tombstone is marked by its revocation, and a new instance is refused');
|
|
1231
|
+
-- 141 -- the class: every function that WRITES delegate_instances (any INSERT, UPDATE, MERGE or
|
|
1232
|
+
-- DELETE, from the catalog, through the shared matcher family) is one of the two RPCs that record
|
|
1233
|
+
-- the pair, and every row this file created carries one except the two hand-inserted legacy rows,
|
|
1234
|
+
-- which their exchange and tombstone filled (133, 134). A new writer fails this and names itself.
|
|
1235
|
+
-- Every non-system schema, not only agent_board: a writer in public or extensions bypasses the pair
|
|
1236
|
+
-- just as well (mutation-checked with one in public). agent_board_test is excluded because it holds
|
|
1237
|
+
-- this suite's matcher functions, which carry relation names as data and write nothing.
|
|
1238
|
+
select is(
|
|
1239
|
+
(select string_agg(p.proname, ',' order by p.proname)
|
|
1240
|
+
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
1241
|
+
where n.nspname not in ('pg_catalog', 'information_schema', 'agent_board_test')
|
|
1242
|
+
and n.nspname !~ '^pg_'
|
|
1243
|
+
and p.prokind in ('f', 'p')
|
|
1244
|
+
and agent_board_test.stripped_body(p.oid) ~ agent_board_test.write_re('delegate_instances'))
|
|
1245
|
+
|| ' ' || (select count(*) from agent_board.delegate_instances where owner_issuer is null)::text,
|
|
1246
|
+
'exchange_delegate_credential,tombstone_delegate_instance 0',
|
|
1247
|
+
'AB15: exactly the exchange and the tombstone write delegate_instances (catalog-derived), and no row is left without the owner pair');
|
|
1248
|
+
|
|
1249
|
+
-- 142 -- the pair is both-or-neither and never empty (delegate_instances_owner_identity_shape)
|
|
1250
|
+
select is(
|
|
1251
|
+
pg_temp.try(format($q$insert into agent_board.delegate_instances(instance_id, owner_user_id, owner_issuer) values ('inst_gggggggggggggggg_s1', %L, 'https://portal.hosted.test')$q$, pg_temp.owner_of('inst_gggggggggggggggg_01')))
|
|
1252
|
+
|| ' ' || pg_temp.try(format($q$insert into agent_board.delegate_instances(instance_id, owner_user_id, owner_issuer, owner_subject) values ('inst_gggggggggggggggg_s2', %L, 'https://portal.hosted.test', '')$q$, pg_temp.owner_of('inst_gggggggggggggggg_01'))),
|
|
1253
|
+
'23514:new row for relation "delegate_instances" violates check constraint "delegate_instances_owner_identity_shape" 23514:new row for relation "delegate_instances" violates check constraint "delegate_instances_owner_identity_shape"',
|
|
1254
|
+
'AB15: an owner pair is recorded whole or not at all, and never empty');
|
|
1255
|
+
|
|
1256
|
+
-- 143 -- a LEGACY TOMBSTONED row (pre-1.59.0: tombstoned, no pair) is filled by a repeated
|
|
1257
|
+
-- revocation. Nothing else ever writes such a row again: the exchange refuses it
|
|
1258
|
+
-- (instance_revoked) before its UPDATE, and a tombstone used to return early. Owner J has only this
|
|
1259
|
+
-- row (never enrolled, never exchanged), so the pair is the only evidence revoke_human_subtree can
|
|
1260
|
+
-- mark it from. Then a new first exchange of J is refused owner_inactive and writes nothing.
|
|
1261
|
+
select pg_temp.put('J.owner', agent_board._ensure_agent_auth_user(
|
|
1262
|
+
agent_board._federated_identity_slug('https://portal.hosted.test', 'hosted-owner-j'))::text);
|
|
1263
|
+
select is(
|
|
1264
|
+
pg_temp.try(format($q$insert into agent_board.delegate_instances(instance_id, owner_user_id, tombstoned_at, tombstone_reason) values ('inst_jjjjjjjjjjjjjjjj_l1', %L, now(), 'owner_revoked')$q$, pg_temp.v('J.owner')))
|
|
1265
|
+
|| ' ' || pg_temp.pair('inst_jjjjjjjjjjjjjjjj_l1')
|
|
1266
|
+
|| ' ' || pg_temp.tomb('inst_jjjjjjjjjjjjjjjj_l1', 'hosted-owner-j')
|
|
1267
|
+
|| ' ' || pg_temp.pair('inst_jjjjjjjjjjjjjjjj_l1')
|
|
1268
|
+
|| ' ' || pg_temp.try(format($q$select agent_board.revoke_human_subtree(%L, %L, 'hosted 591 test')$q$,
|
|
1269
|
+
pg_temp.v('J.owner'), pg_temp.v('K')))
|
|
1270
|
+
|| ' ' || pg_temp.markers('hosted-owner-j')
|
|
1271
|
+
|| ' ' || pg_temp.exch(pg_temp.claim('inst_jjjjjjjjjjjjjjjj_02', 'grant_jjjjjjjjjjjjjj_02_1', 1, 'hosted-owner-j', 'client-j'), 'x')
|
|
1272
|
+
|| ' ' || pg_temp.n2_rows('inst_jjjjjjjjjjjjjjjj_02'),
|
|
1273
|
+
'ok (null) (null) first=false https://portal.hosted.test hosted-owner-j ok 1 ' || pg_temp.v('J.owner')
|
|
1274
|
+
|| ' 42501:owner_inactive 0 0 0',
|
|
1275
|
+
'AB15: a legacy tombstoned row is filled by a repeated revocation, so revoking its delegate-only owner marks it and a new instance is refused');
|
|
1276
|
+
|
|
1133
1277
|
select * from finish();
|
|
1134
1278
|
rollback;
|
|
@@ -378,7 +378,9 @@ begin
|
|
|
378
378
|
'{}'::jsonb
|
|
379
379
|
);
|
|
380
380
|
v_board_message := v_result.message_id;
|
|
381
|
-
|
|
381
|
+
-- #608: the sender is the board's seeded owner-creator, and the author of a post is not one of
|
|
382
|
+
-- its recipients (its row is feed-only), so two of the three active members are counted.
|
|
383
|
+
insert into _tap(line) select is(v_result.delivered_recipient_count, 2, 'board delivery reaches joined and subscribed members only, not the author (#608)');
|
|
382
384
|
insert into _tap(line) select is(
|
|
383
385
|
(select count(*)::integer from agent_board.delivery_recipients where source_message_id = v_board_message and recipient_agent_id = v_left_member),
|
|
384
386
|
0,
|
|
@@ -409,7 +411,8 @@ begin
|
|
|
409
411
|
'{}'::jsonb
|
|
410
412
|
);
|
|
411
413
|
v_task := v_result.task_id;
|
|
412
|
-
|
|
414
|
+
-- #608: the creator (the seeded owner-creator) keeps a feed-only row that is not counted: 1 explicit + 2 board.
|
|
415
|
+
insert into _tap(line) select is(v_result.delivered_recipient_count, 3, 'tasking counts explicit and board fan-out attempts, not the creator (#608)');
|
|
413
416
|
insert into _tap(line) select is(
|
|
414
417
|
(select count(*)::integer from agent_board.delivery_recipients where source_task_id = v_task),
|
|
415
418
|
3,
|