@consiliency/agent-board-schema 1.48.0 → 1.51.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 +16 -0
- package/migrations/20260929000100_agent_board_advisory_revert_cap_reset.sql +371 -0
- package/migrations/20260930000100_agent_board_claim_policy_race.sql +922 -0
- package/migrations/20261001000100_agent_board_claimpolicy_lock_grant_revoke.sql +158 -0
- package/migrations/20261002000100_agent_board_session_challenge_supersede.sql +383 -0
- package/package.json +1 -1
- package/schema-manifest.json +39 -3
- package/src/tooling.test.mjs +33 -2
- package/tests/agent_board_board_claim_policy.test.sql +261 -1
- package/tests/agent_board_claim_subject_gate.test.sql +11 -2
- package/tests/agent_board_claimkey.test.sql +17 -2
- package/tests/agent_board_claimpolicy_race.test.sql +1374 -0
- package/tests/agent_board_fk_lock_edges.test.sql +20 -0
- package/tests/agent_board_ownerless_quota_census.test.sql +6 -0
- package/tests/agent_board_ownerless_renew_quota.test.sql +37 -8
- package/tests/agent_board_signing_challenge_pending_cap.test.sql +391 -36
- package/tests/catalog/p_metadata_parameters.json +16 -0
- package/tests/fixtures/test-prereqs.sql +186 -0
- package/tests/isolation/expected/challenge_supersede_lock_order.out +213 -0
- package/tests/isolation/expected/claimpolicy_acquire_revert_race.out +66 -0
- package/tests/isolation/expected/claimpolicy_board_scope.out +73 -0
- package/tests/isolation/expected/claimpolicy_claimrow_lock_order.out +80 -0
- package/tests/isolation/expected/claimpolicy_isolation_refusal.out +114 -0
- package/tests/isolation/expected/claimpolicy_renew_revert_race.out +66 -0
- package/tests/isolation/expected/claimpolicy_writer_keyshare.out +49 -0
- package/tests/isolation/specs/challenge_supersede_lock_order.spec +183 -0
- package/tests/isolation/specs/claimpolicy_acquire_revert_race.spec +118 -0
- package/tests/isolation/specs/claimpolicy_board_scope.spec +115 -0
- package/tests/isolation/specs/claimpolicy_claimrow_lock_order.spec +200 -0
- package/tests/isolation/specs/claimpolicy_isolation_refusal.spec +162 -0
- package/tests/isolation/specs/claimpolicy_renew_revert_race.spec +150 -0
- package/tests/isolation/specs/claimpolicy_writer_keyshare.spec +107 -0
- package/tests/seeds/20261001000100_agent_board_claimpolicy_lock_grant_revoke.sql.pre.sql +99 -0
|
@@ -859,3 +859,189 @@ COMMENT ON FUNCTION agent_board_test.lock_edges() IS
|
|
|
859
859
|
'FK-aware per-function row-lock edge set: explicit locks, write locks, and the FOR KEY SHARE / child locks PostgreSQL takes through foreign keys. Test-only; see message-board#281.';
|
|
860
860
|
|
|
861
861
|
GRANT USAGE ON SCHEMA agent_board_test TO PUBLIC;
|
|
862
|
+
|
|
863
|
+
-- ---------------------------------------------------------------------------
|
|
864
|
+
-- IDENTIFIER MATCHERS (Consiliency/message-board#405, #417). ONE definition of
|
|
865
|
+
-- "does this body reference agent_board.<name>", used by every assertion that asks.
|
|
866
|
+
--
|
|
867
|
+
-- WHY THESE EXIST. The same lookup was hand-written at ~20 sites and drifted twice in
|
|
868
|
+
-- opposite directions in consecutive review rounds: first too STRICT (an abutting-paren,
|
|
869
|
+
-- schema-qualified, case-sensitive strpos missed `_claimgate_lock_board_policy (v)` and
|
|
870
|
+
-- `"_claimgate_lock_board_policy"(v)`), then too TOLERANT (a `[^A-Za-z0-9_]` guard accepts `.`,
|
|
871
|
+
-- so `other_schema."_claimgate_lock_board_policy"(v)` matched as though it named OUR helper, and
|
|
872
|
+
-- blanket case-insensitivity folded `"AGENT_BOARD"` onto `agent_board` although PostgreSQL treats
|
|
873
|
+
-- those as DISTINCT objects). The target is not "more tolerant" — it is EXACTLY as tolerant as
|
|
874
|
+
-- PostgreSQL's identifier rules, which is a thing to encode once rather than to re-derive per site.
|
|
875
|
+
--
|
|
876
|
+
-- THE RULES ENCODED, verified against the server rather than recalled:
|
|
877
|
+
-- * an UNQUOTED identifier folds to lower case, so any case spelling names the same object
|
|
878
|
+
-- (`to_regclass('AGENT_BOARD.boards') is not distinct from to_regclass('agent_board.boards')`
|
|
879
|
+
-- returns true);
|
|
880
|
+
-- * a QUOTED identifier is case-SENSITIVE, so `"AGENT_BOARD".boards` is a different object
|
|
881
|
+
-- (`to_regclass('"AGENT_BOARD".boards')` returns NULL);
|
|
882
|
+
-- * a reference is ours only if it is UNQUALIFIED or qualified by exactly `agent_board` — any
|
|
883
|
+
-- other qualifier names someone else's object.
|
|
884
|
+
--
|
|
885
|
+
-- CALLERS MUST USE `~` / regexp_instr WITHOUT THE 'i' FLAG. The case rules are inside these
|
|
886
|
+
-- patterns; adding 'i' would fold the quoted alternative and reintroduce the second defect.
|
|
887
|
+
-- Keywords that should stay case-insensitive get agent_board_test.ci_re() for the same reason.
|
|
888
|
+
|
|
889
|
+
-- A SPACE IN p_text BECOMES `\s+`, because every space this is ever given separates the words of a
|
|
890
|
+
-- multiword SQL keyword (`insert into`, `no key`, `key share`) and PostgreSQL accepts any whitespace
|
|
891
|
+
-- run there. stmt_re used to do that rewrite on ci_re's OUTPUT — a caller textually transforming a
|
|
892
|
+
-- shared matcher, which is the same shape as CPR-6 stripping a director, and fragile in the same
|
|
893
|
+
-- way: it depended on ci_re never emitting a space for some other reason. The rule belongs in the
|
|
894
|
+
-- matcher, so no call site edits what a matcher returns.
|
|
895
|
+
create or replace function agent_board_test.ci_re(p_text text) returns text
|
|
896
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
897
|
+
select coalesce(string_agg(case when c ~ '[A-Za-z]' then '[' || upper(c) || lower(c) || ']'
|
|
898
|
+
when c = ' ' then '\s+'
|
|
899
|
+
else c end, ''), '')
|
|
900
|
+
from regexp_split_to_table(p_text, '') as c
|
|
901
|
+
$$;
|
|
902
|
+
|
|
903
|
+
create or replace function agent_board_test.ident_re(p_name text) returns text
|
|
904
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
905
|
+
-- p_name is the object's real (lower-case) name: unquoted-any-case, or quoted-exactly.
|
|
906
|
+
select '(?:' || agent_board_test.ci_re(p_name) || '|"' || p_name || '")'
|
|
907
|
+
$$;
|
|
908
|
+
|
|
909
|
+
-- IDENT_CHARS is PostgreSQL's unquoted-identifier continuation set, and `$` IS IN IT: `$` is legal
|
|
910
|
+
-- after the first character, so `x$boards` is a DISTINCT identifier whose suffix would otherwise
|
|
911
|
+
-- satisfy a `boards` matcher, and `x$_claimgate_lock_board_policy(…)` a distinct FUNCTION whose
|
|
912
|
+
-- suffix would be credited as the protected helper. Omitting `$` was a real over-tolerance, found
|
|
913
|
+
-- by review and reproduced before fixing.
|
|
914
|
+
create or replace function agent_board_test.ident_chars() returns text
|
|
915
|
+
language sql immutable set search_path to pg_catalog as $$ select 'A-Za-z0-9_$' $$;
|
|
916
|
+
|
|
917
|
+
-- THE BOUNDARY GUARDS. Three rejections, each a defect that shipped:
|
|
918
|
+
-- * an ASCII identifier character or a quote immediately before/after — `dashboards`, `x$boards`;
|
|
919
|
+
-- * a NON-ASCII letter, which PostgreSQL also admits in an unquoted identifier, so
|
|
920
|
+
-- `é_claimgate_lock_board_policy` and `claim_policyé` are DISTINCT objects whose prefix/suffix
|
|
921
|
+
-- would otherwise match. The `[A-Za-z0-9_$]` class alone cannot express this;
|
|
922
|
+
-- * a DOT WITH ANY WHITESPACE before the name. `other . _claimgate_lock_board_policy()` is a
|
|
923
|
+
-- different schema's call, and the single-character class let it through because the character
|
|
924
|
+
-- immediately before the name was a SPACE — the round-9 schema-blindness defect reopened through
|
|
925
|
+
-- whitespace. PostgreSQL accepts variable-length lookbehind, verified, so `(?<!\.\s*)` states
|
|
926
|
+
-- it directly rather than approximating with a fixed number of spaces.
|
|
927
|
+
create or replace function agent_board_test.lead_guard() returns text
|
|
928
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
929
|
+
select '(?<![' || agent_board_test.ident_chars() || '."])(?<![^\x00-\x7F])(?<!\.\s*)'
|
|
930
|
+
$$;
|
|
931
|
+
|
|
932
|
+
create or replace function agent_board_test.trail_guard() returns text
|
|
933
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
934
|
+
select '(?![' || agent_board_test.ident_chars() || '])(?![^\x00-\x7F])'
|
|
935
|
+
$$;
|
|
936
|
+
|
|
937
|
+
-- The un-prefixed fragments. Public matchers add the (?c) director exactly once; these exist so a
|
|
938
|
+
-- matcher can embed another without producing a mid-pattern director, which PostgreSQL rejects.
|
|
939
|
+
create or replace function agent_board_test.qname_frag(p_name text) returns text
|
|
940
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
941
|
+
select agent_board_test.lead_guard() || '(?:' || agent_board_test.ident_re('agent_board')
|
|
942
|
+
|| '\s*\.\s*)?' || agent_board_test.ident_re(p_name) || agent_board_test.trail_guard()
|
|
943
|
+
$$;
|
|
944
|
+
|
|
945
|
+
create or replace function agent_board_test.call_frag(p_name text) returns text
|
|
946
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
947
|
+
select agent_board_test.lead_guard() || '(?:' || agent_board_test.ident_re('agent_board')
|
|
948
|
+
|| '\s*\.\s*)?' || agent_board_test.ident_re(p_name) || '\s*\('
|
|
949
|
+
$$;
|
|
950
|
+
|
|
951
|
+
-- THE (?c) DIRECTOR IS THE ENFORCEMENT, not the comment above it. A caller that passes 'i' to
|
|
952
|
+
-- regexp_instr/regexp_matches alongside one of these patterns would previously refold the quoted
|
|
953
|
+
-- alternative and credit `"AGENT_BOARD"."claims"` as `agent_board.claims`. That discipline was
|
|
954
|
+
-- DOCUMENTED and then violated in the same commit that documented it, at four call sites. `(?c)`
|
|
955
|
+
-- forces case-sensitive matching from inside the pattern, so a stray 'i' is now overridden rather
|
|
956
|
+
-- than obeyed — verified: regexp_instr(…, '(?c)"agent_board"', …, 'i') returns 0.
|
|
957
|
+
-- And misuse fails LOUDLY: a director is only legal at the START of a regular expression, so a
|
|
958
|
+
-- caller who prefixes anything gets `invalid regular expression` rather than a silent fold.
|
|
959
|
+
--
|
|
960
|
+
-- EXACTLY WHICH CALLERS THE DIRECTOR PROTECTS, stated narrowly because the first version of this
|
|
961
|
+
-- paragraph claimed more: a pattern that BEGINS with qname_re/call_re/stmt_re is protected against a
|
|
962
|
+
-- passed 'i' flag AND against the `~*` operator, because the director is inside the pattern. A
|
|
963
|
+
-- caller that composes a *_frag directly is NOT protected — the frags carry no director by design,
|
|
964
|
+
-- since one mid-pattern is a syntax error. They exist for embedding inside the _re matchers, and
|
|
965
|
+
-- EXECUTE on them is revoked below so they are not casually reachable; a test author who calls a
|
|
966
|
+
-- frag with `~*` deliberately is outside what any mechanism here can prevent, and that residue is
|
|
967
|
+
-- named rather than papered over.
|
|
968
|
+
create or replace function agent_board_test.qname_re(p_name text) returns text
|
|
969
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
970
|
+
select '(?c)' || agent_board_test.qname_frag(p_name)
|
|
971
|
+
$$;
|
|
972
|
+
|
|
973
|
+
create or replace function agent_board_test.call_re(p_name text) returns text
|
|
974
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
975
|
+
select '(?c)' || agent_board_test.call_frag(p_name)
|
|
976
|
+
$$;
|
|
977
|
+
|
|
978
|
+
-- A COLUMN token. Unquoted identifiers fold, so `SET CLAIM_POLICY = …` names the same column as
|
|
979
|
+
-- `claim_policy` — the case discipline reached the qualifier and the keywords but not the column,
|
|
980
|
+
-- so an upper-case column token was silently omitted from CPR-6 and CPR-12.
|
|
981
|
+
create or replace function agent_board_test.col_frag(p_name text) returns text
|
|
982
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
983
|
+
select agent_board_test.lead_guard() || agent_board_test.ident_re(p_name) || agent_board_test.trail_guard()
|
|
984
|
+
$$;
|
|
985
|
+
|
|
986
|
+
create or replace function agent_board_test.stmt_re(p_verbs text, p_rel text) returns text
|
|
987
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
988
|
+
-- "a statement whose verb is one of p_verbs and whose target is agent_board.p_rel".
|
|
989
|
+
-- SPACES INSIDE A MULTIWORD VERB BECOME `\s+`: `INSERT\nINTO` and `DELETE FROM` are the same
|
|
990
|
+
-- statement to PostgreSQL. The hand-rolled expressions this consolidated used `\s+` and got that
|
|
991
|
+
-- right; the first shared version made the space literal and LOST a property the scattered
|
|
992
|
+
-- versions had — a consolidation is only safe if it is at least the union of what it replaces.
|
|
993
|
+
select '(?c)(?:' || (select string_agg(agent_board_test.ci_re(v), '|')
|
|
994
|
+
from regexp_split_to_table(p_verbs, '\|') as v)
|
|
995
|
+
|| ')\s+(?:' || agent_board_test.ci_re('only') || '\s+)?'
|
|
996
|
+
|| agent_board_test.qname_frag(p_rel)
|
|
997
|
+
$$;
|
|
998
|
+
|
|
999
|
+
revoke all on function agent_board_test.qname_frag(text) from public;
|
|
1000
|
+
revoke all on function agent_board_test.call_frag(text) from public;
|
|
1001
|
+
revoke all on function agent_board_test.col_frag(text) from public;
|
|
1002
|
+
|
|
1003
|
+
-- THE INSERT-WITHOUT-COLUMN-LIST RULE, AS A NAMED OBJECT. It began as an expression inlined in
|
|
1004
|
+
-- CPR-6, and CPR-0's rows for it RECONSTRUCTED that expression instead of calling it — so the rows
|
|
1005
|
+
-- tested a copy, the copy could not diverge from itself, and mutating the production arm left them
|
|
1006
|
+
-- green. That is precisely the defect CPR-0 exists to catch, committed in CPR-0's own newest rows.
|
|
1007
|
+
-- A behaviour-table row must exercise THE SAME code object the production path compiles; anything
|
|
1008
|
+
-- else certifies a parallel implementation.
|
|
1009
|
+
--
|
|
1010
|
+
-- `insert into <rel> …` with no column list writes EVERY column, so for a sole-writer claim it is a
|
|
1011
|
+
-- writer of every column including the one being tracked. Three things this has to get right:
|
|
1012
|
+
-- * `(?!\s*\()` not `\s*(?!\()` — BACKTRACKING defeats the latter, since `\s*` may match zero
|
|
1013
|
+
-- characters and the lookahead then passes on the whitespace itself;
|
|
1014
|
+
-- * an ALIAS may sit between the relation and the column list, and it may be QUOTED
|
|
1015
|
+
-- (`insert into boards as "b"(slug, …)`), so the alias alternative admits both spellings;
|
|
1016
|
+
-- * VALUES / SELECT / DEFAULT / OVERRIDING are KEYWORDS, not aliases — treating them as aliases
|
|
1017
|
+
-- would exclude the genuine no-column-list forms this rule exists for.
|
|
1018
|
+
-- SPLIT INTO frag + re, matching qname/call/col, because CPR-6 embeds this rule INSIDE a larger
|
|
1019
|
+
-- alternation and a director is only legal at position 0. It used to embed
|
|
1020
|
+
-- `replace(insert_all_columns_re(...), '(?c)', '')` — so CPR-0's rows compiled X while production
|
|
1021
|
+
-- compiled a TEXTUAL TRANSFORMATION of X. Behaviourally identical today (CPR-6 matches with `~`,
|
|
1022
|
+
-- which is case-sensitive anyway, so the director is belt-and-braces there), but the rows would have
|
|
1023
|
+
-- stayed green if the rule ever became director-dependent, or if a `(?c)` appeared anywhere but
|
|
1024
|
+
-- position 0 and the replace stripped it silently. Two strings are not one object. Now CPR-6 embeds
|
|
1025
|
+
-- the frag and CPR-0's rows exercise the frag: the same object, no transformation at any call site.
|
|
1026
|
+
create or replace function agent_board_test.insert_all_columns_frag(p_rel text) returns text
|
|
1027
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
1028
|
+
select agent_board_test.ci_re('insert') || '\s+' || agent_board_test.ci_re('into')
|
|
1029
|
+
|| '\s+(?:' || agent_board_test.ci_re('only') || '\s+)?'
|
|
1030
|
+
|| agent_board_test.qname_frag(p_rel)
|
|
1031
|
+
|| '(?!(?:\s+(?:' || agent_board_test.ci_re('as') || '\s+)?(?!'
|
|
1032
|
+
-- ONLY VALUES AND SELECT. `default` and `overriding` were excluded here too and were DEAD
|
|
1033
|
+
-- CODE: an exclusion only matters if the keyword can be followed by `\s*\(` and so be mistaken
|
|
1034
|
+
-- for an alias, and DEFAULT is only ever followed by VALUES, OVERRIDING by SYSTEM/USER.
|
|
1035
|
+
-- Verified by reverting each: the two live exclusions change a verdict, the two dead ones do
|
|
1036
|
+
-- not. Removed rather than kept with rows that cannot gate them.
|
|
1037
|
+
|| agent_board_test.ci_re('values') || '(?![A-Za-z0-9_$])|'
|
|
1038
|
+
|| agent_board_test.ci_re('select') || '(?![A-Za-z0-9_$]))'
|
|
1039
|
+
|| '(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_$]*))?\s*\()'
|
|
1040
|
+
$$;
|
|
1041
|
+
|
|
1042
|
+
create or replace function agent_board_test.insert_all_columns_re(p_rel text) returns text
|
|
1043
|
+
language sql immutable set search_path to pg_catalog as $$
|
|
1044
|
+
select '(?c)' || agent_board_test.insert_all_columns_frag(p_rel)
|
|
1045
|
+
$$;
|
|
1046
|
+
|
|
1047
|
+
revoke all on function agent_board_test.insert_all_columns_frag(text) from public;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Parsed test spec with 3 sessions
|
|
2
|
+
|
|
3
|
+
starting permutation: mint_begin mint_lock_challenge issue_begin issue_at_cap mint_lock_key issue_commit mint_commit check
|
|
4
|
+
step mint_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
5
|
+
step mint_lock_challenge:
|
|
6
|
+
select count(*)::int as locked_challenges from (
|
|
7
|
+
select 1 from agent_board.agent_signing_key_challenges c
|
|
8
|
+
where c.id = 'f1c00000-0000-4000-8000-000000000001' for update) s;
|
|
9
|
+
|
|
10
|
+
locked_challenges
|
|
11
|
+
-----------------
|
|
12
|
+
1
|
|
13
|
+
(1 row)
|
|
14
|
+
|
|
15
|
+
step issue_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
16
|
+
step issue_at_cap:
|
|
17
|
+
select c.key_id, c.key_version
|
|
18
|
+
from agent_board.issue_signing_key_challenge(
|
|
19
|
+
'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
|
|
20
|
+
|
|
21
|
+
key_id |key_version
|
|
22
|
+
-------+-----------
|
|
23
|
+
primary| 1
|
|
24
|
+
(1 row)
|
|
25
|
+
|
|
26
|
+
step mint_lock_key:
|
|
27
|
+
select count(*)::int as locked_keys from (
|
|
28
|
+
select 1 from agent_board.agent_signing_keys k
|
|
29
|
+
where k.id = 'f1b00000-0000-4000-8000-000000000001' for update) s;
|
|
30
|
+
<waiting ...>
|
|
31
|
+
step issue_commit: COMMIT;
|
|
32
|
+
step mint_lock_key: <... completed>
|
|
33
|
+
locked_keys
|
|
34
|
+
-----------
|
|
35
|
+
1
|
|
36
|
+
(1 row)
|
|
37
|
+
|
|
38
|
+
step mint_commit: COMMIT;
|
|
39
|
+
step check:
|
|
40
|
+
select
|
|
41
|
+
(select proof_state from agent_board.agent_signing_key_challenges
|
|
42
|
+
where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
|
|
43
|
+
(select proof_state from agent_board.agent_signing_key_challenges
|
|
44
|
+
where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
|
|
45
|
+
(select count(*)::int from agent_board.agent_signing_key_challenges
|
|
46
|
+
where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
47
|
+
and metadata ? 'superseded_by') as evicted_rows,
|
|
48
|
+
(select count(*)::int from agent_board.agent_signing_key_challenges
|
|
49
|
+
where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
50
|
+
and proof_state = 'pending' and consumed_at is null
|
|
51
|
+
and expires_at > now()) as pending_rows;
|
|
52
|
+
|
|
53
|
+
oldest_state|second_oldest_state|evicted_rows|pending_rows
|
|
54
|
+
------------+-------------------+------------+------------
|
|
55
|
+
pending |expired | 1| 16
|
|
56
|
+
(1 row)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
starting permutation: issue_begin issue_at_cap mint_begin mint_lock_challenge issue_commit mint_lock_key mint_commit check
|
|
60
|
+
step issue_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
61
|
+
step issue_at_cap:
|
|
62
|
+
select c.key_id, c.key_version
|
|
63
|
+
from agent_board.issue_signing_key_challenge(
|
|
64
|
+
'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
|
|
65
|
+
|
|
66
|
+
key_id |key_version
|
|
67
|
+
-------+-----------
|
|
68
|
+
primary| 1
|
|
69
|
+
(1 row)
|
|
70
|
+
|
|
71
|
+
step mint_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
72
|
+
step mint_lock_challenge:
|
|
73
|
+
select count(*)::int as locked_challenges from (
|
|
74
|
+
select 1 from agent_board.agent_signing_key_challenges c
|
|
75
|
+
where c.id = 'f1c00000-0000-4000-8000-000000000001' for update) s;
|
|
76
|
+
<waiting ...>
|
|
77
|
+
step issue_commit: COMMIT;
|
|
78
|
+
step mint_lock_challenge: <... completed>
|
|
79
|
+
locked_challenges
|
|
80
|
+
-----------------
|
|
81
|
+
1
|
|
82
|
+
(1 row)
|
|
83
|
+
|
|
84
|
+
step mint_lock_key:
|
|
85
|
+
select count(*)::int as locked_keys from (
|
|
86
|
+
select 1 from agent_board.agent_signing_keys k
|
|
87
|
+
where k.id = 'f1b00000-0000-4000-8000-000000000001' for update) s;
|
|
88
|
+
|
|
89
|
+
locked_keys
|
|
90
|
+
-----------
|
|
91
|
+
1
|
|
92
|
+
(1 row)
|
|
93
|
+
|
|
94
|
+
step mint_commit: COMMIT;
|
|
95
|
+
step check:
|
|
96
|
+
select
|
|
97
|
+
(select proof_state from agent_board.agent_signing_key_challenges
|
|
98
|
+
where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
|
|
99
|
+
(select proof_state from agent_board.agent_signing_key_challenges
|
|
100
|
+
where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
|
|
101
|
+
(select count(*)::int from agent_board.agent_signing_key_challenges
|
|
102
|
+
where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
103
|
+
and metadata ? 'superseded_by') as evicted_rows,
|
|
104
|
+
(select count(*)::int from agent_board.agent_signing_key_challenges
|
|
105
|
+
where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
106
|
+
and proof_state = 'pending' and consumed_at is null
|
|
107
|
+
and expires_at > now()) as pending_rows;
|
|
108
|
+
|
|
109
|
+
oldest_state|second_oldest_state|evicted_rows|pending_rows
|
|
110
|
+
------------+-------------------+------------+------------
|
|
111
|
+
expired |pending | 1| 16
|
|
112
|
+
(1 row)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
starting permutation: issue_begin issue_at_cap issue_commit mint_begin mint_lock_challenge mint_lock_key mint_commit check
|
|
116
|
+
step issue_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
117
|
+
step issue_at_cap:
|
|
118
|
+
select c.key_id, c.key_version
|
|
119
|
+
from agent_board.issue_signing_key_challenge(
|
|
120
|
+
'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
|
|
121
|
+
|
|
122
|
+
key_id |key_version
|
|
123
|
+
-------+-----------
|
|
124
|
+
primary| 1
|
|
125
|
+
(1 row)
|
|
126
|
+
|
|
127
|
+
step issue_commit: COMMIT;
|
|
128
|
+
step mint_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
129
|
+
step mint_lock_challenge:
|
|
130
|
+
select count(*)::int as locked_challenges from (
|
|
131
|
+
select 1 from agent_board.agent_signing_key_challenges c
|
|
132
|
+
where c.id = 'f1c00000-0000-4000-8000-000000000001' for update) s;
|
|
133
|
+
|
|
134
|
+
locked_challenges
|
|
135
|
+
-----------------
|
|
136
|
+
1
|
|
137
|
+
(1 row)
|
|
138
|
+
|
|
139
|
+
step mint_lock_key:
|
|
140
|
+
select count(*)::int as locked_keys from (
|
|
141
|
+
select 1 from agent_board.agent_signing_keys k
|
|
142
|
+
where k.id = 'f1b00000-0000-4000-8000-000000000001' for update) s;
|
|
143
|
+
|
|
144
|
+
locked_keys
|
|
145
|
+
-----------
|
|
146
|
+
1
|
|
147
|
+
(1 row)
|
|
148
|
+
|
|
149
|
+
step mint_commit: COMMIT;
|
|
150
|
+
step check:
|
|
151
|
+
select
|
|
152
|
+
(select proof_state from agent_board.agent_signing_key_challenges
|
|
153
|
+
where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
|
|
154
|
+
(select proof_state from agent_board.agent_signing_key_challenges
|
|
155
|
+
where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
|
|
156
|
+
(select count(*)::int from agent_board.agent_signing_key_challenges
|
|
157
|
+
where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
158
|
+
and metadata ? 'superseded_by') as evicted_rows,
|
|
159
|
+
(select count(*)::int from agent_board.agent_signing_key_challenges
|
|
160
|
+
where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
161
|
+
and proof_state = 'pending' and consumed_at is null
|
|
162
|
+
and expires_at > now()) as pending_rows;
|
|
163
|
+
|
|
164
|
+
oldest_state|second_oldest_state|evicted_rows|pending_rows
|
|
165
|
+
------------+-------------------+------------+------------
|
|
166
|
+
expired |pending | 1| 16
|
|
167
|
+
(1 row)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
starting permutation: mint_begin mint_lock_all issue_begin issue_at_cap issue_commit mint_commit check
|
|
171
|
+
step mint_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
172
|
+
step mint_lock_all:
|
|
173
|
+
select count(*)::int as locked_challenges from (
|
|
174
|
+
select 1 from agent_board.agent_signing_key_challenges c
|
|
175
|
+
where c.signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
176
|
+
and c.proof_state = 'pending'
|
|
177
|
+
and c.consumed_at is null
|
|
178
|
+
order by c.id
|
|
179
|
+
for update) s;
|
|
180
|
+
|
|
181
|
+
locked_challenges
|
|
182
|
+
-----------------
|
|
183
|
+
16
|
|
184
|
+
(1 row)
|
|
185
|
+
|
|
186
|
+
step issue_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
187
|
+
step issue_at_cap:
|
|
188
|
+
select c.key_id, c.key_version
|
|
189
|
+
from agent_board.issue_signing_key_challenge(
|
|
190
|
+
'f1a00000-0000-4000-8000-000000000001', 'primary', interval '5 minutes') c;
|
|
191
|
+
|
|
192
|
+
ERROR: challenge_pending_cap: too many pending signing-key challenges for this key (max 16)
|
|
193
|
+
step issue_commit: COMMIT;
|
|
194
|
+
step mint_commit: COMMIT;
|
|
195
|
+
step check:
|
|
196
|
+
select
|
|
197
|
+
(select proof_state from agent_board.agent_signing_key_challenges
|
|
198
|
+
where id = 'f1c00000-0000-4000-8000-000000000001') as oldest_state,
|
|
199
|
+
(select proof_state from agent_board.agent_signing_key_challenges
|
|
200
|
+
where id = 'f1c00000-0000-4000-8000-000000000002') as second_oldest_state,
|
|
201
|
+
(select count(*)::int from agent_board.agent_signing_key_challenges
|
|
202
|
+
where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
203
|
+
and metadata ? 'superseded_by') as evicted_rows,
|
|
204
|
+
(select count(*)::int from agent_board.agent_signing_key_challenges
|
|
205
|
+
where signing_key_id = 'f1b00000-0000-4000-8000-000000000001'
|
|
206
|
+
and proof_state = 'pending' and consumed_at is null
|
|
207
|
+
and expires_at > now()) as pending_rows;
|
|
208
|
+
|
|
209
|
+
oldest_state|second_oldest_state|evicted_rows|pending_rows
|
|
210
|
+
------------+-------------------+------------+------------
|
|
211
|
+
pending |pending | 0| 16
|
|
212
|
+
(1 row)
|
|
213
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Parsed test spec with 3 sessions
|
|
2
|
+
|
|
3
|
+
starting permutation: w_begin w_flip a_begin a_acq w_commit a_commit chk
|
|
4
|
+
step w_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000a1';
|
|
5
|
+
step w_flip:
|
|
6
|
+
select claim_policy
|
|
7
|
+
from agent_board.set_board_claim_policy(
|
|
8
|
+
(select id from agent_board.boards where slug = 'cpr-acq-board'), 'inherit');
|
|
9
|
+
|
|
10
|
+
claim_policy
|
|
11
|
+
------------
|
|
12
|
+
inherit
|
|
13
|
+
(1 row)
|
|
14
|
+
|
|
15
|
+
step a_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000a1';
|
|
16
|
+
step a_acq:
|
|
17
|
+
select resource_key
|
|
18
|
+
from agent_board.acquire_claim(null, 'file', 'cpr-acq-key', now() + interval '400 days',
|
|
19
|
+
null, '40500000-0000-4000-8000-000000000010');
|
|
20
|
+
<waiting ...>
|
|
21
|
+
step w_commit: COMMIT;
|
|
22
|
+
step a_acq: <... completed>
|
|
23
|
+
ERROR: ownerless claim lease horizon exceeds cap (30 days)
|
|
24
|
+
step a_commit: COMMIT;
|
|
25
|
+
step chk:
|
|
26
|
+
select count(*)::int as active_claims,
|
|
27
|
+
coalesce(bool_or(lease_expires_at > now() + interval '31 days'), false) as over_cap_lease
|
|
28
|
+
from agent_board.claims
|
|
29
|
+
where agent_id = '40500000-0000-4000-8000-000000000001'
|
|
30
|
+
and resource_kind = 'file'
|
|
31
|
+
and status = 'active'
|
|
32
|
+
and released_at is null;
|
|
33
|
+
|
|
34
|
+
active_claims|over_cap_lease
|
|
35
|
+
-------------+--------------
|
|
36
|
+
0|f
|
|
37
|
+
(1 row)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
starting permutation: a_begin a_acq a_commit chk
|
|
41
|
+
step a_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000a1';
|
|
42
|
+
step a_acq:
|
|
43
|
+
select resource_key
|
|
44
|
+
from agent_board.acquire_claim(null, 'file', 'cpr-acq-key', now() + interval '400 days',
|
|
45
|
+
null, '40500000-0000-4000-8000-000000000010');
|
|
46
|
+
|
|
47
|
+
resource_key
|
|
48
|
+
------------
|
|
49
|
+
cpr-acq-key
|
|
50
|
+
(1 row)
|
|
51
|
+
|
|
52
|
+
step a_commit: COMMIT;
|
|
53
|
+
step chk:
|
|
54
|
+
select count(*)::int as active_claims,
|
|
55
|
+
coalesce(bool_or(lease_expires_at > now() + interval '31 days'), false) as over_cap_lease
|
|
56
|
+
from agent_board.claims
|
|
57
|
+
where agent_id = '40500000-0000-4000-8000-000000000001'
|
|
58
|
+
and resource_kind = 'file'
|
|
59
|
+
and status = 'active'
|
|
60
|
+
and released_at is null;
|
|
61
|
+
|
|
62
|
+
active_claims|over_cap_lease
|
|
63
|
+
-------------+--------------
|
|
64
|
+
1|t
|
|
65
|
+
(1 row)
|
|
66
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Parsed test spec with 4 sessions
|
|
2
|
+
|
|
3
|
+
starting permutation: w_begin w_flip a1_begin a1_acq w_commit a1_commit chk
|
|
4
|
+
step w_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000c1';
|
|
5
|
+
step w_flip:
|
|
6
|
+
select claim_policy
|
|
7
|
+
from agent_board.set_board_claim_policy(
|
|
8
|
+
(select id from agent_board.boards where slug = 'cpr-scope-b1'), 'inherit');
|
|
9
|
+
|
|
10
|
+
claim_policy
|
|
11
|
+
------------
|
|
12
|
+
inherit
|
|
13
|
+
(1 row)
|
|
14
|
+
|
|
15
|
+
step a1_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000c1';
|
|
16
|
+
step a1_acq:
|
|
17
|
+
select resource_key
|
|
18
|
+
from agent_board.acquire_claim(null, 'file', 'cpr-scope-key-b1', now() + interval '400 days',
|
|
19
|
+
null, '40500000-0000-4000-8000-000000000041');
|
|
20
|
+
<waiting ...>
|
|
21
|
+
step w_commit: COMMIT;
|
|
22
|
+
step a1_acq: <... completed>
|
|
23
|
+
ERROR: ownerless claim lease horizon exceeds cap (30 days)
|
|
24
|
+
step a1_commit: COMMIT;
|
|
25
|
+
step chk:
|
|
26
|
+
select resource_key, (lease_expires_at > now() + interval '31 days') as over_cap_lease
|
|
27
|
+
from agent_board.claims
|
|
28
|
+
where agent_id = '40500000-0000-4000-8000-000000000003'
|
|
29
|
+
and status = 'active' and released_at is null
|
|
30
|
+
order by resource_key;
|
|
31
|
+
|
|
32
|
+
resource_key|over_cap_lease
|
|
33
|
+
------------+--------------
|
|
34
|
+
(0 rows)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
starting permutation: w_begin w_flip a2_begin a2_acq w_commit a2_commit chk
|
|
38
|
+
step w_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000c1';
|
|
39
|
+
step w_flip:
|
|
40
|
+
select claim_policy
|
|
41
|
+
from agent_board.set_board_claim_policy(
|
|
42
|
+
(select id from agent_board.boards where slug = 'cpr-scope-b1'), 'inherit');
|
|
43
|
+
|
|
44
|
+
claim_policy
|
|
45
|
+
------------
|
|
46
|
+
inherit
|
|
47
|
+
(1 row)
|
|
48
|
+
|
|
49
|
+
step a2_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000c1';
|
|
50
|
+
step a2_acq:
|
|
51
|
+
select resource_key
|
|
52
|
+
from agent_board.acquire_claim(null, 'file', 'cpr-scope-key-b2', now() + interval '400 days',
|
|
53
|
+
null, '40500000-0000-4000-8000-000000000042');
|
|
54
|
+
|
|
55
|
+
resource_key
|
|
56
|
+
----------------
|
|
57
|
+
cpr-scope-key-b2
|
|
58
|
+
(1 row)
|
|
59
|
+
|
|
60
|
+
step w_commit: COMMIT;
|
|
61
|
+
step a2_commit: COMMIT;
|
|
62
|
+
step chk:
|
|
63
|
+
select resource_key, (lease_expires_at > now() + interval '31 days') as over_cap_lease
|
|
64
|
+
from agent_board.claims
|
|
65
|
+
where agent_id = '40500000-0000-4000-8000-000000000003'
|
|
66
|
+
and status = 'active' and released_at is null
|
|
67
|
+
order by resource_key;
|
|
68
|
+
|
|
69
|
+
resource_key |over_cap_lease
|
|
70
|
+
----------------+--------------
|
|
71
|
+
cpr-scope-key-b2|t
|
|
72
|
+
(1 row)
|
|
73
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Parsed test spec with 4 sessions
|
|
2
|
+
|
|
3
|
+
starting permutation: rc_begin rc_reclaim w_begin w_flip rc_commit w_commit chk
|
|
4
|
+
step rc_begin: BEGIN; SET LOCAL "request.jwt.claim.role" = 'service_role';
|
|
5
|
+
step rc_reclaim:
|
|
6
|
+
select owner_agent_id
|
|
7
|
+
from agent_board.reclaim_fleet_machine(
|
|
8
|
+
'40500000-0000-4000-8000-0000000000c0', '40500000-0000-4000-8000-00000000000b',
|
|
9
|
+
'40500000-0000-4000-8000-00000000000a', 'message-board#405 lock-order probe');
|
|
10
|
+
|
|
11
|
+
owner_agent_id
|
|
12
|
+
------------------------------------
|
|
13
|
+
40500000-0000-4000-8000-00000000000b
|
|
14
|
+
(1 row)
|
|
15
|
+
|
|
16
|
+
step w_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000f9';
|
|
17
|
+
step w_flip:
|
|
18
|
+
select claim_policy
|
|
19
|
+
from agent_board.set_board_claim_policy(
|
|
20
|
+
(select id from agent_board.boards where slug = 'cpr-inv-board'), 'inherit');
|
|
21
|
+
|
|
22
|
+
claim_policy
|
|
23
|
+
------------
|
|
24
|
+
inherit
|
|
25
|
+
(1 row)
|
|
26
|
+
|
|
27
|
+
step rc_commit: COMMIT;
|
|
28
|
+
step w_commit: COMMIT;
|
|
29
|
+
step chk:
|
|
30
|
+
select (select claim_policy from agent_board.boards where slug = 'cpr-inv-board') as policy,
|
|
31
|
+
(select owner_agent_id from agent_board.fleet_machines
|
|
32
|
+
where id = '40500000-0000-4000-8000-0000000000c0') as machine_owner;
|
|
33
|
+
|
|
34
|
+
policy |machine_owner
|
|
35
|
+
-------+------------------------------------
|
|
36
|
+
inherit|40500000-0000-4000-8000-00000000000b
|
|
37
|
+
(1 row)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
starting permutation: x_begin x_rowlock w_begin w_flip x_renew w_commit x_commit chk
|
|
41
|
+
step x_begin: BEGIN;
|
|
42
|
+
step x_rowlock: select resource_key from agent_board.claims where id = '40500000-0000-4000-8000-000000000091' for update;
|
|
43
|
+
resource_key
|
|
44
|
+
------------
|
|
45
|
+
cpr-inv-key
|
|
46
|
+
(1 row)
|
|
47
|
+
|
|
48
|
+
step w_begin: BEGIN; SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.role" = 'authenticated'; SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000f9';
|
|
49
|
+
step w_flip:
|
|
50
|
+
select claim_policy
|
|
51
|
+
from agent_board.set_board_claim_policy(
|
|
52
|
+
(select id from agent_board.boards where slug = 'cpr-inv-board'), 'inherit');
|
|
53
|
+
<waiting ...>
|
|
54
|
+
step x_renew:
|
|
55
|
+
SET LOCAL ROLE authenticated;
|
|
56
|
+
SET LOCAL "request.jwt.claim.role" = 'authenticated';
|
|
57
|
+
SET LOCAL "request.jwt.claim.sub" = '40500000-0000-4000-8000-0000000000f9';
|
|
58
|
+
select resource_key
|
|
59
|
+
from agent_board.renew_claim('40500000-0000-4000-8000-0000000000f8', now() + interval '400 days');
|
|
60
|
+
<waiting ...>
|
|
61
|
+
step w_flip: <... completed>
|
|
62
|
+
ERROR: deadlock detected
|
|
63
|
+
step x_renew: <... completed>
|
|
64
|
+
resource_key
|
|
65
|
+
------------
|
|
66
|
+
cpr-inv-key
|
|
67
|
+
(1 row)
|
|
68
|
+
|
|
69
|
+
step w_commit: COMMIT;
|
|
70
|
+
step x_commit: COMMIT;
|
|
71
|
+
step chk:
|
|
72
|
+
select (select claim_policy from agent_board.boards where slug = 'cpr-inv-board') as policy,
|
|
73
|
+
(select owner_agent_id from agent_board.fleet_machines
|
|
74
|
+
where id = '40500000-0000-4000-8000-0000000000c0') as machine_owner;
|
|
75
|
+
|
|
76
|
+
policy | machine_owner
|
|
77
|
+
--------+------------------------------------
|
|
78
|
+
advisory|40500000-0000-4000-8000-000000000009
|
|
79
|
+
(1 row)
|
|
80
|
+
|