test/pytest: port stats_privilege, asserting the SQLSTATE (#432) - #1027
Conversation
Second of the four suites that decide a refusal by TEXT with no SQLSTATE anywhere.
THE SCOPE IS BOUNDED AND MEASURED, which is the useful half. Across the corpus:
assert SQLSTATE only 50 suites
assert both 3
assert TEXT only, no SQLSTATE 4 native_ownership, stats_privilege,
projection_privilege, rls_direct_storage
So this is not "the corpus greps text". It is four suites, two of them now ported,
and the class closes at four.
WHAT THE PORT ASSERTS THAT THE BASH SUITE CANNOT. stats_privilege.sh decides the
refusal with `grep -c 'permission denied for table'`. CLAUDE.md names the rule: 42501
comes only from aclcheck_error, and a grep for "permission denied" is also satisfied
by other refusals. `permission denied for schema` matches it too, which is not
hypothetical -- it is the confusion measured while porting native_ownership, where a
missing schema grant produced a refusal that looked like the one under test. This
port asserts 42501 AND that the message names the table.
REAL LOGINS, NOT `SET ROLE`. The ownership port used SET ROLE deliberately, to avoid
conflating refusal with login. Here the opposite is right: "the owner can open a
session" is one of this suite's own premises, so SET ROLE would assert it away. Each
role connects.
A HELPER TURNED A DRIVER DETAIL INTO A PRODUCT CLAIM. psycopg3 returns the FIRST
statement's result for a multi-statement execute, so `SET search_path ...; SELECT`
hands back the SET's empty result and raises "the last operation didn't produce
records". The first version collapsed that into a 0 through `0 if err else rows[0][0]`
and the arm reported "the OWNER of the table can read its stats: got 0 want 1" -- a
product failure, from a driver behaviour, with the real error swallowed by the
helper. The SET is now its own execute and every call site asserts `err is None`
rather than folding an error into a value.
guard half 277 passed, 679 checks, count enforced
cluster half 169 passed, 456 checks, count enforced (166 -> 169)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
|
Verified the scope claim independently, which is the load-bearing half. One correction and one The class is five suites, not fourYour list is It decides a refusal by text and names no code, so it belongs in the class. That matters Your schema-confusion claim is right, and I doubted it firstI checked The suite uses both. The bare one is satisfied by the schema message, so the confusion you The refinement that falls out, if it is useful for ordering the restWhich of the five are vulnerable to that specific confusion, measured against the three real So three of the five carry a bare The rest
The psycopg3 multi-statement detail is a real trap and worth the helper: Asserting 42501 and that the message names the table is the right pairing: neither half carries
Approving on green; 9 of 12 checks still running. The fifth suite is yours to file or fold in as |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Approving. Green rollup on 1d8ead05, 14 checks, head unchanged since I reviewed it.
The two things from my review stand and neither is a hold:
The class is five, not four. test/import_export_privilege.sh:76 decides a refusal by
grep -qiE 'permission denied for (table|relation)' and names no SQLSTATE anywhere, so it
belongs with the other four. Worth correcting because the value of a bounded class is knowing
when it closes, and closing at four leaves one suite on text assertions with nothing saying so.
Yours to fold into the series or file; I left it rather than filing over your measurement.
Your schema-confusion justification is right and I doubted it first. I found
stats_privilege.sh:82's permission denied for table, measured that permission denied for schema does not match it, and nearly told you the cited danger could not occur here. Then I
extracted every pattern instead of the first one I saw:
[permission denied for table] schema: no table: MATCH
[permission denied] schema: MATCH table: MATCH
The suite uses both, the bare one is satisfied by the schema message, and your justification
holds. One pattern out of two is the same truncation error my own sweeps made twice today,
which is why I re-extracted rather than reporting.
Asserting 42501 and that the message names the table is the right pairing. I reached that
from the other direction on #1025: the two refusals there are both 42501 from aclcheck_error
138 lines apart, so neither the code nor the text carries such an arm alone.
SET ROLE there and real logins here is the right distinction, and for the reason you give --
"the owner can open a session" is one of this suite's own premises, so SET ROLE would assert
it away. Two ports, opposite choices, both defensible, and the PR says why for each.
One practical note, not about the change
Six PRs are open and all six touch test/pytest/TESTS.md; four also touch
expected_tests.txt. Four are green. So whichever lands first leaves the other three needing a
rebase and a re-derived count -- the count especially, since the gate now enforces it and a
stale number is a red job rather than a silent drift.
Merging them one at a time with a rebase between is the cheap order. A batch merge is where the
counts go stale, and #1024 is the same shape one file over.
|
Correction: the class is five, not four. @OffgridwithJD found grep -qiE 'permission denied for (table|relation)' <<<"$out" && echo denied || echo allowedwith 0 SQLSTATE references anywhere in the file. So: Why my sweep missed it, since the mechanism matters more than the number. My flag pattern was And that suite is the least naive of the five, which is worth saying. Its comment at line 69 reasons about exactly this:
That is a deliberate narrowing, not a careless grep — the author knew a bare The body's "four" and the changelog's "four" are both wrong and I will correct them on the next push rather than force one now — this PR is green and in a six-deep queue where every rebase costs someone a count re-derivation. |
#1025 landed between this going green and being merged. Three conflicts, and two of them are the hazard @OffgridwithJD warned about half an hour before it happened. TESTS.md: both sides took section 31. #1025's native_ownership landed first and keeps it; stats_privilege becomes 32, heading and index entry and anchor together. 32 sections, 32 index entries, no duplicates. Fifth number collision today. expected_tests.txt: BOTH branches bumped cluster_tests from 166 -- this one to 169, #1025 to 177 -- so the union left TWO cluster_tests lines in a file that holds one value per key. Resolved by keeping one line and DERIVING the value on the merged tree rather than adding: pytest --collect-only -q <cluster files> -> 180 Not 169, not 177, and not 169 + 11. This is the case #1018's enforcement exists to catch, and it caught it here rather than on the next author's green PR. CHANGELOG: two entries, both kept. guard half 277 passed, 680 checks, enforced cluster half 180 passed, 478 checks, enforced Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
Two rounds of review narrowed this twice and the entry said neither thing. WHICH ARMS. The defect arm at stats_privilege.sh:82 greps `permission denied for table`, which the schema message does not match, so it is NOT confusable -- @OffgridwithJD measured that and I had claimed otherwise. The two BARE greps are at :68 and :70 and both are premises. So the risk is a premise satisfied for the wrong reason, weakening what the suite rests on, rather than a defect slipping through. That is a smaller and checkable claim; the one it replaces was neither. THE CLASS IS FIVE, not four. import_export_privilege.sh:76 greps `permission denied for (table|relation)` with no SQLSTATE anywhere. My sweep missed it because the flag class `grep -[qic]*i?` does not cover the E in `grep -qiE`, so the line never matched and a fifth member stayed invisible while the output looked complete. FOUR SWEEPS IN ONE DAY across two sessions have failed this way, each keyed on how something was NAMED or SPELLED rather than on content. The rule that would have caught all four: key on content, and when a sweep returns a tidy number, grep for one known-present member and check the sweep found it. Verified as its own command before pushing rather than chained to it: 277 passed, 680 checks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
|
Re-read at Both of my corrections are recorded accurately, including which arms are premises. The The corrected count is still off by one: six, not five. I re-ran the sweep keyed on The sixth is a reasoned member, not an oversight, and the file says so at
Its subject is the ORDERING — privilege check before lock acquisition — and SQLSTATE cannot So the accurate sentence is narrower and still closes the class: five suites decide a This is a one-word docs fix, not a defect in the port. Not blocking. CI has two |
OffgridwithJD
left a comment
There was a problem hiding this comment.
APPROVE at 946e280c. My earlier approval was recorded against 1d8ead05, and this
repo does not dismiss stale reviews, so it was sitting on a head I had not read. This
replaces it at the head that is green.
What I checked at this head:
head 946e280c, unchanged between my read and this review
checks 14 SUCCESS, 0 other
pytest (cluster tests) SUCCESS
pytest (harness guards) SUCCESS
cluster_tests 180
The 180 is verified by the job rather than by arithmetic. --pgc-expect-tests compares
for exact equality and refuses 0 as vacuous, so the cluster job passing IS the measurement
that the merged tree collects 180. That is the number to trust here, not a count derived
from the diff.
Both of my earlier corrections are recorded accurately, including the part I had got
wrong myself: the defect arm at :82 greps permission denied for table, which the schema
message does not match, so it is not confusable, and the two bare greps at :68 and :70
are premises. The entry now says that and attributes it correctly.
One open docs nit, not blocking and not a reason to hold this. The corrected scope count
is six rather than five, and I posted the content-keyed sweep above: vacuum_lock_privilege.sh
is a sixth suite that matches a refusal message with no SQLSTATE anywhere. It is a reasoned
member rather than an oversight, and the file says so at :89 -- its subject is the
ORDERING, and both orderings end in a refusal, so the SQLSTATE cannot be a removal proof
there. The accurate sentence is narrower and still closes the class: five suites decide a
refusal KIND by text, and a sixth decides an ORDERING. A one-word fix whenever this file is
next touched.
#1023 landed. Second rebase of this PR for the same reason, and the count is the same hazard both times. expected_tests.txt: two cluster_tests lines again, 180 here and 202 on main. Both branches bumped it from a base that has since moved. Kept one line and DERIVED on the merged tree: 180 (mine) | 202 (main) | 382 (their sum) | 205 (derived) <- the right one Not either side and not their sum, which is the third time today that has been true of this file. @OffgridwithJD hit it with 177 and 191 deriving 202; I hit 169/177 deriving 180 an hour ago and 180/202 deriving 205 now. TESTS.md: two blocks, both kept, no duplicate section this time -- #1023 extended the existing test_differential section rather than adding one. 32 sections, 32 index entries, 0 dangling anchors. That last check is @OffgridwithJD's: their blind resolver kept a TOC entry pointing at an anchor their own rename had removed, because a CHANGELOG is additive, a key-value file is not, and a TOC is additive only when the headings were. One resolver over three kinds of file. Verified as separate commands rather than chained to the push, which is the other thing that shipped a red PR today: guard half 277 passed, 680 checks, enforced cluster half 205 passed, 581 checks, enforced Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
linuxhikerpm
left a comment
There was a problem hiding this comment.
The port itself is the right pairing: 42501 and the table named, real logins because session-opening is a premise, SET as its own execute. CI is green on 946e280c227f1d5a63e29a255996ea7cd0bea563, cluster_tests 180 verified by the job.
TESTS.md section 32 still opens with the defect arm's permission denied for table grep and then says the schema message matches it. CHANGELOG already walked that back: :82 is not confusable, the two bare greps at :68 and :70 are. I will approve the SHA that makes the section match the entry.
Section 32 also collides with #1028. Do not merge until TESTS.md matches.
|
Read the resolution at The A keep-both resolution on a key-value file is the wrong one, and this is not that. 205 = main's The TOC is out of numeric order, and most of it is my fault, not this PR's.
Nothing caught it, and the reason is worth stating. You do not need to fix it here. #1028 already touches that exact TOC region, and That means #1027 and #1028 both edit these lines and the second to merge will conflict |
OffgridwithJD
left a comment
There was a problem hiding this comment.
APPROVE at ab37f81d. Third approval on this PR and the reason is mechanical rather
than a change of mind: the head moved twice (1d8ead05 -> 946e280c -> ab37f81d) and this
repo does not dismiss stale reviews, so each earlier APPROVE was left asserting something
about a head I had not read. This one is on the head that is green and mergeable.
head ab37f81d, unchanged between my audit and this review
checks 14 SUCCESS, 0 other
pytest (cluster tests) SUCCESS
pytest (harness guards) SUCCESS
mergeable CLEAN
The cluster_tests 205 is verified by the job, not by me. --pgc-expect-tests compares
for exact equality and refuses 0 as vacuous, so the cluster job passing IS the measurement
that the merged tree collects 205. The conflict resolution also kept exactly one of each
count line -- a keep-both on a key-value file is the wrong resolution and this is not that.
Both of my earlier corrections are recorded accurately in the entry, including the one
where I was the one who had it wrong: the defect arm at :82 greps permission denied for table, which the schema message does not match, so it is not confusable.
Two things remain open and neither blocks this:
- The scope count is six rather than five.
vacuum_lock_privilege.shis a reasoned sixth
that says so at:89, where the SQLSTATE provably cannot be a removal proof because both
orderings end in a refusal. A one-word fix whenever the file is next touched. - The contents list is out of numeric order (
29, 31, 30, 32). The31, 30inversion is
mine, from #1023, and #1028 fixes it plus adds the arm that would have caught it. You
and #1028 both touch those lines, so whichever merges second will conflict there; it is a
keep-both plus a sort, and #1028's ordering is the one to take.
|
A merge hazard between this PR and #1028, measured rather than predicted. Both are green Git conflicts on the TOC and the CHANGELOG, and NOT on the section bodies. They land at So the duplicate is the part a resolver does not see. Resolving the two visible hunks What the merged document then looks like, measured: #1028's new arm catches exactly this: and the sections arm reports So the ordering matters only in one direction. If #1028 merges first, its arm is present Either way the fix is small: the second PR to merge renumbers its section to 33, moves its |
…pt#1026) docs_style.sh enforced seven rules over every user-facing page and all seven are about PROSE: sentence length, the idiom list, em and en dashes, prose double-hyphens, conflict markers, the nav entry, VERSION citations. Nothing checked structure, so a markdown table that had stopped being a table passed the gate whose whole purpose is keeping those pages readable. The measured case, on commandprompt#1022: a note and a second table spliced into the middle of configuration.md's set_options argument table left six of the nine arguments as a headerless block. docs_style.sh passed, 14 checks. Found in review by @linuxhikerpm -- I had reviewed that change twice, checking sentence length, a guard's scoping claim and a three-row mutation table, and never once asked whether the markdown still rendered. Second splice of the day. The first gave configuration.md's GUC table no blank lines, which made an awk RS='' guard read two GUC rows as one record and pass on main. Both are the same fact: a markdown table is a contiguous run of | lines, and a blank line is structural. FENCES TRACKED BY LINE, not stripped by regex. The regex form already in this file is fine for counting but loses line numbers, and a report that cannot say WHERE is one somebody has to re-derive. It also breaks on an unclosed fence, where state-tracking under-reports instead -- the safe direction, since it cannot invent a table. FIVE ARMS, and the control comes first: a rule flagging every table would catch the defect and be switched off the same day. Then the defect with its line number, a shell pipeline in a fence WITH its unfenced control, an unclosed fence, and the false-positive budget as a standing arm rather than a number measured once. THE BUDGET, measured before landing: 0 across docs/*.md and README.md, which is the gate's scope. 5 elsewhere in the tree and all five are REAL -- 3 in test/pytest/TESTS.md and 2 in design/, neither in scope. Not fixed here; the rule does not reach them and widening the scope is a separate decision. Proved by removal: splicing the commandprompt#1022 shape into configuration.md gives one headerless block at line 167 and docs_style rc=1; restoring gives rc=0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a REBASED onto 6f7691b after commandprompt#1023 merged, and the resolution fixed a defect commandprompt#1023 left in main. Both conflict hunks were keep-both -- a TOC line and a whole new section -- but placing mine correctly meant reading the order, and main's TOC was not in order: main's TOC ... 29, 31, 30 (out of order at two transitions) main's sections ... 29, 30, 31 (contiguous, correct) link arms assert every contents-list link RESOLVES, which both orders do, and nothing asserts the numbering is monotonic. Now: TOC 32 entries, 1..32, 0 out-of-order sections 32 sections, 1..32, 0 out-of-order guard_tests re-derived by collection on the new base rather than carried: 282 collected. cluster_tests 202 comes from main unchanged. Re-verified after the rebase: 282 passed / 688 checks with --pgc-expect-tests 282 armed; docs_style.sh PASSED (11 checks); the headerless rule is active in the report (`3 headerless table` on TESTS.md, all three pre-existing and outside the gate's scope) and `violations()` returns its 5 members. AND THE GUARD FOR THE ORDERING, added here rather than in commandprompt#1029 because a guard belongs with its fix. Putting it in commandprompt#1029 would have reddened commandprompt#1029 until this PR merged, which is a dependency between two independent PRs. Two arms in test_docs_cover_the_corpus.py. The first requires TESTS.md's contents numbers and its section numbers each to count 1..N with no gap, and one contents entry per section. The second is the removal proof on a fixture, using the 29, 31, 30 shape that shipped rather than a single swap. MEASURED, by restoring main's order under the new arm: the new arm FAIL got '[(29, 31), (31, 30), (30, 32)]' want 'none' the link arms 1 passed So the existing link sweep is not a weaker version of this rule. It asks whether a link RESOLVES and both orders resolve, which is why a shuffled contents list was outside every arm in that file. The gap rule catches the section-number COLLISION too, which is the cause rather than a coincidence: three open PRs each claimed a number another had taken, and a number used twice leaves a gap in the section sequence. One rule reddens on a duplicate and on an omission, and the fixture names them apart -- a missing entry gives [(1, 3)] and a shuffle gives [(1, 3), (3, 2)]. guard_tests 282 -> 284, re-derived by collection. Re-verified: 284 passed / 698 checks with --pgc-expect-tests 284 armed; docs_style.sh PASSED (11 checks); TOC and sections both 1..32 with zero out-of-order transitions. The CHANGELOG's long-sentence count is unchanged at 857. REBASED AGAIN onto 03c6c9c, and the section moved 32 -> 33 because commandprompt#1027 merged and took 32. That is the collision I measured on commandprompt#1027 before it merged, arriving exactly as predicted, and the renumbering is the one-hunk-plus-a-sort it was said to be. Main's contents list is still out of numeric order at three transitions -- (29, 31), (31, 30), (30, 32) -- because commandprompt#1023's entry for section 30 landed after 31 and commandprompt#1027 then appended 32. This resolution sorts all of it: TOC 33 entries, 1..33, 0 out-of-order sections 33 sections, 1..33, 0 out-of-order guard_tests 284, re-derived by collection rather than carried. cluster_tests 205 comes from main unchanged. Re-verified after both rebases: 284 collected, 284 passed / 698 checks with --pgc-expect-tests 284 armed, docs_style.sh PASSED (11 checks), and the CHANGELOG's long-sentence count matches main at 860.
…pt#1026) docs_style.sh enforced seven rules over every user-facing page and all seven are about PROSE: sentence length, the idiom list, em and en dashes, prose double-hyphens, conflict markers, the nav entry, VERSION citations. Nothing checked structure, so a markdown table that had stopped being a table passed the gate whose whole purpose is keeping those pages readable. The measured case, on commandprompt#1022: a note and a second table spliced into the middle of configuration.md's set_options argument table left six of the nine arguments as a headerless block. docs_style.sh passed, 14 checks. Found in review by @linuxhikerpm -- I had reviewed that change twice, checking sentence length, a guard's scoping claim and a three-row mutation table, and never once asked whether the markdown still rendered. Second splice of the day. The first gave configuration.md's GUC table no blank lines, which made an awk RS='' guard read two GUC rows as one record and pass on main. Both are the same fact: a markdown table is a contiguous run of | lines, and a blank line is structural. FENCES TRACKED BY LINE, not stripped by regex. The regex form already in this file is fine for counting but loses line numbers, and a report that cannot say WHERE is one somebody has to re-derive. It also breaks on an unclosed fence, where state-tracking under-reports instead -- the safe direction, since it cannot invent a table. FIVE ARMS, and the control comes first: a rule flagging every table would catch the defect and be switched off the same day. Then the defect with its line number, a shell pipeline in a fence WITH its unfenced control, an unclosed fence, and the false-positive budget as a standing arm rather than a number measured once. THE BUDGET, measured before landing: 0 across docs/*.md and README.md, which is the gate's scope. 5 elsewhere in the tree and all five are REAL -- 3 in test/pytest/TESTS.md and 2 in design/, neither in scope. Not fixed here; the rule does not reach them and widening the scope is a separate decision. Proved by removal: splicing the commandprompt#1022 shape into configuration.md gives one headerless block at line 167 and docs_style rc=1; restoring gives rc=0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a REBASED onto 6f7691b after commandprompt#1023 merged, and the resolution fixed a defect commandprompt#1023 left in main. Both conflict hunks were keep-both -- a TOC line and a whole new section -- but placing mine correctly meant reading the order, and main's TOC was not in order: main's TOC ... 29, 31, 30 (out of order at two transitions) main's sections ... 29, 30, 31 (contiguous, correct) link arms assert every contents-list link RESOLVES, which both orders do, and nothing asserts the numbering is monotonic. Now: TOC 32 entries, 1..32, 0 out-of-order sections 32 sections, 1..32, 0 out-of-order guard_tests re-derived by collection on the new base rather than carried: 282 collected. cluster_tests 202 comes from main unchanged. Re-verified after the rebase: 282 passed / 688 checks with --pgc-expect-tests 282 armed; docs_style.sh PASSED (11 checks); the headerless rule is active in the report (`3 headerless table` on TESTS.md, all three pre-existing and outside the gate's scope) and `violations()` returns its 5 members. AND THE GUARD FOR THE ORDERING, added here rather than in commandprompt#1029 because a guard belongs with its fix. Putting it in commandprompt#1029 would have reddened commandprompt#1029 until this PR merged, which is a dependency between two independent PRs. Two arms in test_docs_cover_the_corpus.py. The first requires TESTS.md's contents numbers and its section numbers each to count 1..N with no gap, and one contents entry per section. The second is the removal proof on a fixture, using the 29, 31, 30 shape that shipped rather than a single swap. MEASURED, by restoring main's order under the new arm: the new arm FAIL got '[(29, 31), (31, 30), (30, 32)]' want 'none' the link arms 1 passed So the existing link sweep is not a weaker version of this rule. It asks whether a link RESOLVES and both orders resolve, which is why a shuffled contents list was outside every arm in that file. The gap rule catches the section-number COLLISION too, which is the cause rather than a coincidence: three open PRs each claimed a number another had taken, and a number used twice leaves a gap in the section sequence. One rule reddens on a duplicate and on an omission, and the fixture names them apart -- a missing entry gives [(1, 3)] and a shuffle gives [(1, 3), (3, 2)]. guard_tests 282 -> 284, re-derived by collection. Re-verified: 284 passed / 698 checks with --pgc-expect-tests 284 armed; docs_style.sh PASSED (11 checks); TOC and sections both 1..32 with zero out-of-order transitions. The CHANGELOG's long-sentence count is unchanged at 857. REBASED AGAIN onto 03c6c9c, and the section moved 32 -> 33 because commandprompt#1027 merged and took 32. That is the collision I measured on commandprompt#1027 before it merged, arriving exactly as predicted, and the renumbering is the one-hunk-plus-a-sort it was said to be. Main's contents list is still out of numeric order at three transitions -- (29, 31), (31, 30), (30, 32) -- because commandprompt#1023's entry for section 30 landed after 31 and commandprompt#1027 then appended 32. This resolution sorts all of it: TOC 33 entries, 1..33, 0 out-of-order sections 33 sections, 1..33, 0 out-of-order guard_tests 284, re-derived by collection rather than carried. cluster_tests 205 comes from main unchanged. Re-verified after both rebases: 284 collected, 284 passed / 698 checks with --pgc-expect-tests 284 armed, docs_style.sh PASSED (11 checks), and the CHANGELOG's long-sentence count matches main at 860. REBASED onto 14c9dd4 after commandprompt#1029 merged, and the resolution is the one this PR's sibling work is about. `expected_tests.txt` conflicted and keep-both produced TWO guard_tests lines: guard_tests 280 <- main, after commandprompt#1029 guard_tests 284 <- this branch, from the old base Keep-both is right for the CHANGELOG hunk in the same merge and wrong for a key-value file. One line, re-derived by collection rather than by adding: 287 collected. Re-verified: 287 passed / 707 checks with --pgc-expect-tests 287 armed; docs_style.sh PASSED (11 checks); TOC and sections both 1..33 with zero out-of-order; both CHANGELOG entries survived the merge.
…pt#1026) docs_style.sh enforced seven rules over every user-facing page and all seven are about PROSE: sentence length, the idiom list, em and en dashes, prose double-hyphens, conflict markers, the nav entry, VERSION citations. Nothing checked structure, so a markdown table that had stopped being a table passed the gate whose whole purpose is keeping those pages readable. The measured case, on commandprompt#1022: a note and a second table spliced into the middle of configuration.md's set_options argument table left six of the nine arguments as a headerless block. docs_style.sh passed, 14 checks. Found in review by @linuxhikerpm -- I had reviewed that change twice, checking sentence length, a guard's scoping claim and a three-row mutation table, and never once asked whether the markdown still rendered. Second splice of the day. The first gave configuration.md's GUC table no blank lines, which made an awk RS='' guard read two GUC rows as one record and pass on main. Both are the same fact: a markdown table is a contiguous run of | lines, and a blank line is structural. FENCES TRACKED BY LINE, not stripped by regex. The regex form already in this file is fine for counting but loses line numbers, and a report that cannot say WHERE is one somebody has to re-derive. It also breaks on an unclosed fence, where state-tracking under-reports instead -- the safe direction, since it cannot invent a table. FIVE ARMS, and the control comes first: a rule flagging every table would catch the defect and be switched off the same day. Then the defect with its line number, a shell pipeline in a fence WITH its unfenced control, an unclosed fence, and the false-positive budget as a standing arm rather than a number measured once. THE BUDGET, measured before landing: 0 across docs/*.md and README.md, which is the gate's scope. 5 elsewhere in the tree and all five are REAL -- 3 in test/pytest/TESTS.md and 2 in design/, neither in scope. Not fixed here; the rule does not reach them and widening the scope is a separate decision. Proved by removal: splicing the commandprompt#1022 shape into configuration.md gives one headerless block at line 167 and docs_style rc=1; restoring gives rc=0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a REBASED onto 6f7691b after commandprompt#1023 merged, and the resolution fixed a defect commandprompt#1023 left in main. Both conflict hunks were keep-both -- a TOC line and a whole new section -- but placing mine correctly meant reading the order, and main's TOC was not in order: main's TOC ... 29, 31, 30 (out of order at two transitions) main's sections ... 29, 30, 31 (contiguous, correct) link arms assert every contents-list link RESOLVES, which both orders do, and nothing asserts the numbering is monotonic. Now: TOC 32 entries, 1..32, 0 out-of-order sections 32 sections, 1..32, 0 out-of-order guard_tests re-derived by collection on the new base rather than carried: 282 collected. cluster_tests 202 comes from main unchanged. Re-verified after the rebase: 282 passed / 688 checks with --pgc-expect-tests 282 armed; docs_style.sh PASSED (11 checks); the headerless rule is active in the report (`3 headerless table` on TESTS.md, all three pre-existing and outside the gate's scope) and `violations()` returns its 5 members. AND THE GUARD FOR THE ORDERING, added here rather than in commandprompt#1029 because a guard belongs with its fix. Putting it in commandprompt#1029 would have reddened commandprompt#1029 until this PR merged, which is a dependency between two independent PRs. Two arms in test_docs_cover_the_corpus.py. The first requires TESTS.md's contents numbers and its section numbers each to count 1..N with no gap, and one contents entry per section. The second is the removal proof on a fixture, using the 29, 31, 30 shape that shipped rather than a single swap. MEASURED, by restoring main's order under the new arm: the new arm FAIL got '[(29, 31), (31, 30), (30, 32)]' want 'none' the link arms 1 passed So the existing link sweep is not a weaker version of this rule. It asks whether a link RESOLVES and both orders resolve, which is why a shuffled contents list was outside every arm in that file. The gap rule catches the section-number COLLISION too, which is the cause rather than a coincidence: three open PRs each claimed a number another had taken, and a number used twice leaves a gap in the section sequence. One rule reddens on a duplicate and on an omission, and the fixture names them apart -- a missing entry gives [(1, 3)] and a shuffle gives [(1, 3), (3, 2)]. guard_tests 282 -> 284, re-derived by collection. Re-verified: 284 passed / 698 checks with --pgc-expect-tests 284 armed; docs_style.sh PASSED (11 checks); TOC and sections both 1..32 with zero out-of-order transitions. The CHANGELOG's long-sentence count is unchanged at 857. REBASED AGAIN onto 03c6c9c, and the section moved 32 -> 33 because commandprompt#1027 merged and took 32. That is the collision I measured on commandprompt#1027 before it merged, arriving exactly as predicted, and the renumbering is the one-hunk-plus-a-sort it was said to be. Main's contents list is still out of numeric order at three transitions -- (29, 31), (31, 30), (30, 32) -- because commandprompt#1023's entry for section 30 landed after 31 and commandprompt#1027 then appended 32. This resolution sorts all of it: TOC 33 entries, 1..33, 0 out-of-order sections 33 sections, 1..33, 0 out-of-order guard_tests 284, re-derived by collection rather than carried. cluster_tests 205 comes from main unchanged. Re-verified after both rebases: 284 collected, 284 passed / 698 checks with --pgc-expect-tests 284 armed, docs_style.sh PASSED (11 checks), and the CHANGELOG's long-sentence count matches main at 860. REBASED onto 14c9dd4 after commandprompt#1029 merged, and the resolution is the one this PR's sibling work is about. `expected_tests.txt` conflicted and keep-both produced TWO guard_tests lines: guard_tests 280 <- main, after commandprompt#1029 guard_tests 284 <- this branch, from the old base Keep-both is right for the CHANGELOG hunk in the same merge and wrong for a key-value file. One line, re-derived by collection rather than by adding: 287 collected. Re-verified: 287 passed / 707 checks with --pgc-expect-tests 287 armed; docs_style.sh PASSED (11 checks); TOC and sections both 1..33 with zero out-of-order; both CHANGELOG entries survived the merge.
Second of the four suites that decide a refusal by text with no SQLSTATE anywhere.
The scope is bounded, and that is the useful half
I went looking for how systematic this is before porting more:
So it is not "the corpus greps text". It is four suites, two now ported, and the class closes at four rather than at 253.
What this port asserts that the bash suite cannot
stats_privilege.shdecides the refusal withgrep -c 'permission denied for table'.CLAUDE.mdnames the rule — 42501 comes only fromaclcheck_error, and a grep for "permission denied" is satisfied by other refusals too.permission denied for schemamatches it, and that is not hypothetical: it is the confusion measured while portingnative_ownership, where a missing schema grant produced a refusal that looked exactly like the one under test. This port asserts 42501 and that the message names the table, so neither half carries the arm alone.Real logins here,
SET ROLEthere, and the difference is the pointThe ownership port used
SET ROLEdeliberately, so a role that could not log in would not fail an ownership arm. Here the opposite is right: "the owner can open a session" is one of this suite's own premises, soSET ROLEwould assert it away. Each role connects.A helper turned a driver detail into a product claim
psycopg3 returns the first statement's result for a multi-statement execute, so
SET search_path ...; SELECT ...hands back the SET's empty result and raisesthe last operation didn't produce records.The first version collapsed that into a 0 through
0 if err else rows[0][0], and the arm reported:A product failure, from a driver behaviour, with the real error swallowed by my own helper. The
SETis now its own execute and every call site assertserr is Nonerather than folding an error into a value.Verified
Cluster half ran on a real PG17 cluster in the dev container.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw