Skip to content

test: the docs gate checks that a table is still a table (#1026) - #1028

Merged
jdatcmd merged 1 commit into
commandprompt:mainfrom
OffgridwithJD:fix/1026-a-table-must-stay-a-table
Sep 13, 2026
Merged

jdatcmd merged 1 commit into
commandprompt:mainfrom
OffgridwithJD:fix/1026-a-table-must-stay-a-table

Conversation

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Closes #1026.

docs_style.sh enforced seven rules over every user-facing page: sentence length, the idiom
list, em and en dashes, prose double-hyphens, conflict markers, the nav entry, and every
VERSION citation. All seven are about prose. 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 #1022, a note and a second table spliced into the middle of configuration.md's
set_options argument table:

158 | `stripe_row_limit` | integer | ... |     last row of the argument table
159                                            blank line ENDS the table
160 **A `stripe_row_limit` below 1024 ...**
165 | `stripe_row_limit` | FSST tables | ...|  a second table, with its own header
171 At 1000 the column costs more ...
175 | `compression` | name | ... |             a table ROW with no header above it
180 | `ttl_interval` | interval | ... |

Six of the nine set_options arguments rendered as a headerless table, and docs_style.sh
passed with 14 checks. Found by @linuxhikerpm in review. I had reviewed that change twice
-- sentence length, the guard's scoping claim, the chunk-group question, a three-row mutation
table -- and never once asked whether the markdown still rendered. That is a worse miss than
anything I did check, because it is visible to a reader of the published page and the others
are not.

It was the second splice that day. The first gave the GUC table no blank lines, which made
an awk RS='' guard read stripe_row_limit's row and chunk_group_row_limit's row 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.

The false-positive budget, measured before landing

This is what a static guard in this tree owes, and it is the part worth checking hardest:

docs/*.md + README.md    (the gate's scope)    0
elsewhere in the tree                          5   -- and all five are REAL
    test/pytest/TESTS.md            3   lines 470, 589, 1977
    design/AUDIT_2026_08_...md      2   lines 35, 49

I read all five. Every one is a genuine run of | name | description | rows with no header
after prose -- not a false positive among them. Neither file is in the gate's scope, so the
rule does not reach them and I have not fixed them here: widening the scope is a separate
decision, and TESTS.md is a test document rather than a user-facing page.

Fences by line, not by regex

The file already strips fences with re.sub(r'```.*?```', '', t, flags=re.S) for the
double-hyphen rule. I did not reuse it:

  • it loses line numbers, and a report that cannot say WHERE is one somebody has to re-derive
  • it needs a CLOSING fence. With one missing it matches nothing and the fence's contents are
    scanned as prose. State-tracking swallows what follows instead -- which under-reports rather
    than inventing a table, and that is the safe direction for a new guard.

Both are asserted rather than argued: there is an arm for a shell pipeline inside a fence, and
an arm for an unclosed fence.

Five arms, control first

arm what it holds
test_a_well_formed_table_is_not_flagged the control, first: a rule flagging every table would catch the defect and be switched off the same day
test_rows_orphaned_by_a_splice_are_flagged_with_their_line the defect, and the line the orphaned block starts at
test_a_pipe_inside_a_fenced_code_block_is_not_a_table a shell pipeline in a fence is not a table -- with the unfenced control, or the arm passes because the rule flags nothing anywhere
test_an_unclosed_fence_does_not_swallow_the_rest_of_the_file the case the regex form gets wrong
test_the_documents_the_gate_checks_are_clean the budget as a standing arm rather than a number measured once

Test plan

  • 5 tests, 8 checks, 0 fail.
  • Proved by removal: splicing the docs: name 1024 as the floor for stripe_row_limit (#1017) #1022 shape into configuration.md gives one
    headerless block at line 167 and docs_style.sh rc=1; restoring it gives rc=0.
  • Guard half armed: 282 collected, 687 checks, 0 fail. guard_tests 277 -> 282.
  • Registered in NO_CLUSTER, in TESTS.md as section 31, and the classifier arm passes.
  • docs_style.sh PASSED, 11 checks.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a

@linuxhikerpm linuxhikerpm left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving 81ce139b7d01688da59136481293c51e84b97c87.

This is the gate #1022 was missing. The control comes first, the splice is flagged at the orphaned block's line, the fence arm has its unfenced control, and an unclosed fence under-reports. CI is green, including pytest-guards at guard_tests 282 and docs_style.sh on the suites jobs.

The pytest file drives headerless_tables rather than reimplementing it, which is the right shape for a checker: a second python of the same rule would agree with itself. docs_style.sh already calls report(), and the 5-tuple change is consumed there.

Section 32 collides with #1027. guard_tests collides with #1022 and #1029. Re-derive on rebase. I will not merge it.

@OffgridwithJD
OffgridwithJD force-pushed the fix/1026-a-table-must-stay-a-table branch from 81ce139 to 28f3bb2 Compare September 12, 2026 23:52
@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

Rebased onto 6f7691bd after #1023 merged. Both conflict hunks were keep-both (one TOC line, one whole section), and resolving them surfaced a defect #1023 left in main -- which was mine:

main's TOC       ... 29, 31, 30      out of order at two transitions
main's sections  ... 29, 30, 31      contiguous and correct

#1023's TOC entry for section 30 landed after 31. Nothing caught it, and the reason is specific: this file's link arms assert every contents-list link RESOLVES, which both orders do, and nothing asserts the numbering is monotonic. Placing my own entry meant choosing an order, so the fix is in this PR rather than deferred:

TOC       32 entries, 1..32, 0 out-of-order
sections  32 sections, 1..32, 0 out-of-order

I am adding the monotonicity arm to #1029, which already edits test_docs_cover_the_corpus.py, rather than widening this PR -- the gap is a missing guard, and it belongs beside the other arms about this document.

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 live in the report (3 headerless table on TESTS.md, all three pre-existing and outside the gate's scope).

@OffgridwithJD
OffgridwithJD force-pushed the fix/1026-a-table-must-stay-a-table branch from 28f3bb2 to d6be198 Compare September 12, 2026 23:57
@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

Added the ordering guard here rather than in #1029, correcting what I said above. A guard belongs with its fix -- putting it in #1029 would have reddened #1029 until this PR merged, which is a dependency between two independent PRs.

Two arms in test_docs_cover_the_corpus.py:

test what it asserts
test_the_contents_list_is_numbered_in_order the contents numbers and the section numbers each count 1..N with no gap, and one entry per section
test_a_shuffled_contents_list_is_caught_on_a_fixture removal proof on the 29, 31, 30 shape that shipped, not a single swap, with an omission named apart from an inversion

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 exactly why a shuffled contents list was outside every arm in that file.

The gap rule catches the section-number collision too, and that is the cause rather than a coincidence. Three open PRs each claimed a number another had taken (#1022 wanted 31, #1027 and this one both wanted 32). A number used twice leaves a gap in the section sequence, so one rule reddens on a duplicate and on an omission. The fixture names them apart: a missing entry gives [(1, 3)], a shuffle gives [(1, 3), (3, 2)].

guard_tests              282 -> 284, re-derived by collection
full guard half          284 passed / 698 checks, --pgc-expect-tests 284 armed
docs_style.sh            PASSED, 11 checks
TOC / sections           both 1..32, 0 out-of-order
CHANGELOG long sentences 857 -> 857 (unchanged)

This does not make #1024 unnecessary -- dropping the numbers removes the collision class entirely, where this only makes it visible. It does mean the next collision fails loudly instead of merging.

@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

Rebased onto 03c6c9c8 and the section moved 32 -> 33, because #1027 merged and took 32. That is the collision I measured on #1027 before it merged, arriving exactly as described, and the fix was the one-hunk-plus-a-sort I said it would be.

Main's contents list is still out of order at three transitions, which this resolution sorts:

main       TOC 32 entries, 1..32, out-of-order 3:  (29,31) (31,30) (30,32)
this PR    TOC 33 entries, 1..33, out-of-order 0
           sections 33, 1..33, out-of-order 0

Two rebases were needed: #1027 merged (TESTS.md + CHANGELOG conflict), then #1034 merged (CHANGELOG only). Both keep-both, and I checked both entries survived rather than assuming it.

guard_tests              284, re-derived by collection (284 collected), not carried
cluster_tests            205, from main unchanged
full guard half          284 passed / 698 checks, --pgc-expect-tests 284 armed
docs_style.sh            PASSED, 11 checks
TESTS.md markers         0
stale 32-anchor links    0
CHANGELOG long sentences 860, matching main

#1029 will need its guard_tests re-derived after this merges -- it currently says 280 (277 + its 3), which is right against main today and becomes 287 once this lands. One number, and the recipe is in expected_tests.txt.

…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.
@OffgridwithJD
OffgridwithJD force-pushed the fix/1026-a-table-must-stay-a-table branch from 8c1573a to 21de2b1 Compare September 13, 2026 02:10
jdatcmd added a commit that referenced this pull request Sep 13, 2026
I put the note and its measurement table between `stripe_row_limit` and
`compression` in the set_options argument table. A blank line ends a markdown
table, so the six arguments after it -- compression, compression_level,
encode_effort, sort_by, ttl_column, ttl_interval -- rendered as a headerless
block or as literal pipes.

docs_style.sh passed this page with 14 checks, and passes the fixed page with
14 checks, because all seven of its rules are about prose. That is what #1028
adds a structure rule for; @OffgridwithJD found this reviewing #1022.

The note now follows the table it was splitting, still inside the section that
documents the setting, and the `stripe_row_limit` row points at it the way the
GUC table's row already did.

Verified: #1028's headerless_tables() flags docs/configuration.md at line 175
on the previous commit and flags nothing on this one; main was clean, so this
branch introduced it. Both #1017 guards still pass in both harnesses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw

@jdatcmd jdatcmd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. I verified both rules against the real defects rather than the fixtures, because a guard that lands green has only its fixture to stand on until someone does that.

The table rule fires on the measured case, and the measured case is mine. The splice this PR describes is live on #1022 right now, not in history: my #1017 note and its measurement table sit between stripe_row_limit and compression in the set_options argument table, leaving six of the nine arguments headerless.

  branch docs/1017-... @1747cc7   headerless_tables(docs/configuration.md) -> [175]
  origin/main                      clean
  branch after the fix @5928438    clean

docs_style.sh passes the broken page with 14 checks and the fixed page with 14 checks. That is the argument for this PR, stated as a number.

I pushed the page fix to #1022 (5928438): the note now follows the table it was splitting, still inside the section that documents the setting. So this can merge in either order without arriving red.

The contents-list rule fires on the real damage too. Running _gaps over main's last twelve commits that touched TESTS.md:

  9553e4dc  toc gaps=[(29, 31), (31, 30), (30, 32)]
  ab37f81d  toc gaps=[(29, 31), (31, 30), (30, 32)]
  d392b80f  toc gaps=[(29, 31), (31, 30)]
  82548eb3  none        <- and silent on the eight before it
  ...

Three consecutive commits of main, exactly the 29, 31, 30 shape you describe, and no false positive on the rest. It would also have caught what I resolved by hand in #1022's merge this morning: a section numbered 31 twice, and an index entry left pointing at #30-...-type-matrix after your rename removed that heading.

Numbers checked, not read. guard_tests 287 runs green on your head (707 checks, 0 unrun).

One thing to fix, and I will take it. The comment under the value contradicts it:

guard_tests 287
# 282 -> 284: two arms about TESTS.md's own numbering ...

A reader arriving at 287 is told about 284. This file's whole case for existing is that the diff explains itself to a reviewer, so an incoherent note costs more here than elsewhere. I am rewriting that same region when I rebase #1022 on top of this, and I will fold the correction in there rather than send you round again.

Not blocking, for the record: headerless_tables keys on a leading |, so a GFM table written without leading pipes is invisible to it. That under-reports rather than false-positives, and no page in the tree uses that form. Worth a line in the docstring if you touch the file again.

@jdatcmd
jdatcmd merged commit 2ea7366 into commandprompt:main Sep 13, 2026
14 checks passed
jdatcmd added a commit that referenced this pull request Sep 13, 2026
#1028 landed while this was open. Three conflicts, resolved by file kind:

- test/pytest/test_harness_deps.py: a python LIST is additive, both entries kept.
- test/pytest/TESTS.md: both sides numbered a section 33, so this branch's
  test_docs_stripe_floor.py becomes 34 and follows #1028's section. #1028's own
  new rule now enforces that ordering, and it reports toc=34 sec=34 with no gap,
  no inversion and no dangling anchor.
- test/pytest/expected_tests.txt: re-derived, not resolved. Three branches had
  each added three arms to 277 and each derived 280, every one correct against a
  tree holding only its own three. Keeping either side ships a value no tree
  collects, and so does adding the deltas up. Collection on the merged tree says
  290 and 205.

Also dropped the note #1028 left under its value, which described a move to 284
beside a value of 287; I said in review I would take that rather than send the
author round again.

Verified: guard half green at 290 (715 checks, 0 unrun), docs_style.sh 14 checks
PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
jdatcmd added a commit that referenced this pull request Sep 13, 2026
#1022 landed while this was open. Two conflicts:

- test/pytest/TESTS.md: main's test_docs_stripe_floor.py took 34, so this
  branch's two sections become 35 and 36. #1028's numbering rule reports
  toc=36 sec=36, no gap, no inversion, no dangling anchor.
- test/pytest/expected_tests.txt: re-derived, not resolved. Both sides had a
  guard number measured against a tree holding only its own arms. Collection on
  the merged tree says 298 and 217.

Verified: guard half green at 298 (737 checks, 0 unrun).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs_style checks prose but not structure: a markdown table that stops being a table passes the gate

3 participants