Skip to content

Check SPEC §19's Zero-Skip roster against what the runners reported (#736) - #744

Merged
jeremy merged 3 commits into
mainfrom
spec-roster-derived
Aug 14, 2026
Merged

Check SPEC §19's Zero-Skip roster against what the runners reported (#736)#744
jeremy merged 3 commits into
mainfrom
spec-roster-derived

Conversation

@jeremy

@jeremy jeremy commented Aug 14, 2026

Copy link
Copy Markdown
Member

Closes #736.

The roster promised something nothing checked

SPEC §19's Zero-Skip roster states its own contract:

The roster below enumerates every skip a default (mock-mode) conformance run reports, one
line per runner × test, verbatim from the runners' skip mechanisms.

and its maintenance rule:

A PR that closes a gap deletes exactly its own lines.

Neither was enforced. It was already wrong when I got here: Kotlin and Swift each exclude
List operation returns first page with Link header wholesale through their link-header tag
branch, and the roster described that in prose instead of enumerating it — two of six runners
misstated, in a roster long enough that nobody re-derives it by hand. Adding those two lines is
what makes the set equality hold.

What this adds

The execution manifests from #602 made the enumeration derivable, so
check-fixture-execution now compares it for set equality in both directions:

  • a runner skip with no roster line → fails
  • a roster line for a skip that no longer exists → fails (the "deletes exactly its own lines" rule)

The classification and reasoning on each line stay judgement, and nothing asserts them.
That half is why the section keeps its [manual] tag — this makes the derivable half derived,
not the whole thing.

On adding another prose parser

#740 declined to keep teaching sync-doc-constants.rb more GFM — separator widths, backslash
parity — and I want to be explicit about why this one is different rather than quietly doing
the thing I argued against.

A mis-parse there is silent: it validates the wrong cell and reports success. This
extraction is designed to fail loud in both directions.
A bullet it cannot read is a name
missing from the roster set — a mismatch. A name it invents is an extra — also a mismatch.
The intended property is that no reading of a malformed line yields a passing comparison, so
the failure mode is a false alarm the author fixes, never a false green.

Review found three independent breaches of exactly that property, and they are the reason
to read this claim as a designed invariant now enforced, rather than one that held on
arrival: a second roster block was silently ignored (first begin/end won), a case listed twice
under one runner cancelled out of both diffs (Array#- removes every occurrence), and a stale
entry written with a non-canonical list marker (*, +, 1., indented -) was skipped
entirely — each a silent pass. The third was not fixed with a third selector: the default is
inverted, so any list-shaped line must be canonical or the gate errors. Self-test cases pin all
of them.

Three strikes on one invariant is also a fair reason to question prose-parsing here at all.
The alternative — moving the roster to a data file and generating the SPEC block from it —
removes the parser rather than hardening it, and is Jeremy's call; it is not started.

The delimiters are deliberately not @-markers. sync-doc-constants owns those and runs
in spec-gates, where no conformance run has happened and there are no manifests to compare
against. Registering a kind there whose real enforcement lives here would split one check
across two gates.

Identity

The roster names a case by name alone, so it cannot express two same-named cases from different
fixtures — and names are not unique across files (#743 found one appearing in three). No
runner excludes such a pair today; if one ever does, the gate says so and asks for file
qualifiers rather than silently comparing an ambiguous set.

Verification

  • make check-fixture-execution — green; roster matches all six runners.
  • Five self-test cases, each shown to fail against a gate with the roster check removed:
    skip missing from the roster, roster line for a closed gap, a runner with no section, SPEC
    with no roster block, and a bullet without a quoted name.
  • Proven by hand end-to-end too: reverting the Kotlin/Swift lines reproduces the original drift
    and fails; adding a stale TypeScript line fails the other way.
  • make doc-constants-check green — the new delimiters do not collide with the @-marker
    convention.

Summary by cubic

Checks SPEC §19’s Zero-Skip roster against the runners’ manifests and fails on drift. Previously unchecked and prone to silent passes; now set-equality validation runs before --partial and fails closed on malformed/duplicate entries. SPEC adds delimiters and enumerates Kotlin/Swift link-header.

  • Asserts set equality in both directions; classification text remains manual.
  • Runs before the partial-mode branch; partial compares only runners that reported.
  • Fails closed: requires exactly one roster block and one section per runner; bullets must be canonical - "case name" — … with quoted names; rejects duplicate bullets and duplicate sections; errors if a runner excludes the same-named case from multiple fixtures.
  • SPEC updates: added <!-- zero-skip-roster:begin -->/<!-- zero-skip-roster:end -->; added Kotlin/Swift lines for the link-header skip.
  • Tests cover drift both ways, missing/duplicate blocks/sections, unquoted names, list-shaped variants (*, +, 1., indented), and partial-mode coverage.

Developer actions

  • When adding/removing a skip, edit the matching runner section with a canonical bullet: - "Case name" — reason.
  • Keep exactly one roster block and one section per runner; do not use *, +, ordered, or indented list markers; do not duplicate bullets.

Written for commit 4ce947b. Summary will update on new commits.

Review in cubic

SPEC section 19's roster promises "every skip a default (mock-mode)
conformance run reports, one line per runner x test, verbatim from the
runners' skip mechanisms", plus the maintenance rule "a PR that closes a gap
deletes exactly its own lines". Both were enforced by nothing.

It was already wrong. Kotlin and Swift each exclude "List operation returns
first page with Link header" wholesale through their `link-header` tag branch,
and the roster described that in prose instead of enumerating it — two of six
runners misstated, in a roster long enough that nobody re-derives it by hand.
Those lines are added here, which is what makes the set equality hold.

The execution manifests (#602) made the ENUMERATION derivable, so
check-fixture-execution now compares it for set equality in both directions: a
runner skip with no roster line fails, and a roster line for a skip that no
longer exists fails. The CLASSIFICATION and reasoning on each line stay
judgement and nothing asserts them, which is why the section keeps `[manual]`.

On the parser, since #740 declined to keep teaching sync-doc-constants more
GFM: a mis-parse there is SILENT — it validates the wrong cell and reports
success. This extraction fails LOUD in both directions. A bullet it cannot
read is a name missing from the roster set; a name it invents is an extra.
Neither produces a passing comparison, so the failure mode is a false alarm
the author fixes, never a false green. A self-test case pins that property.

The delimiters are deliberately not @-markers: sync-doc-constants owns those
and runs in spec-gates, where no conformance run has happened and there are no
manifests to compare against. Registering a kind there whose real enforcement
lives here would split one check across two gates.

Five self-test cases, each shown to fail against a gate with the roster check
removed: skip missing from the roster, roster line for a closed gap, a runner
with no section, SPEC with no roster block, and a bullet without a quoted name.
Copilot AI balanced review requested due to automatic review settings August 14, 2026 00:52

Copilot AI 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.

Pull request overview

Adds automated validation that SPEC §19’s Zero-Skip roster matches runner execution manifests.

Changes:

  • Parses and validates roster entries in both directions.
  • Adds Kotlin and Swift link-header exclusions.
  • Adds negative self-tests for roster drift and malformed input.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
SPEC.md Updates and delimits the roster.
scripts/check-fixture-execution.rb Implements roster parsing and comparison.
scripts/test-check-fixture-execution.rb Tests roster validation failures.
Suppressed comments (1)

scripts/check-fixture-execution.rb:242

  • This silently ignores valid Markdown list items that use indentation, *, +, or an ordered marker. If such an ignored item is a stale roster entry, there is no manifest-side name to create a mismatch, so the gate passes—the exact false-green the parser is intended to prevent. Recognize all list-item forms within this delimited block (or explicitly reject noncanonical ones) before validating the quoted case name, and cover a stale alternate-marker item in the self-test.
    next unless line.start_with?("- ")
    raise Failure, "SPEC roster bullet before any runner heading: #{line.strip[0, 60]}" if runner.nil?

    name = line[/\A-\s+"([^"]+)"/, 1]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/check-fixture-execution.rb Outdated
Comment thread scripts/check-fixture-execution.rb

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 527faa4724

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/check-fixture-execution.rb Outdated
Comment thread scripts/check-fixture-execution.rb
…ently

Three review findings, both bots converging on two classes. Two of them are
silent passes, which is the one failure mode this extractor is not allowed to
have — the whole argument for reading prose here is that every misreading
surfaces as a set mismatch.

ROSTER DRIFT WENT UNCHECKED ON LINUX. The roster comparison sat after the
partial-mode early return, and the Linux `make check-fixture-execution` path
always passes --partial because Swift's manifest is macOS-only. So the normal
local path never checked the roster at all: a stale Go or Ruby line passed and
only the CI fan-in could catch it. The check now runs before the branch, over
whatever manifests exist — "does Ruby's roster line match what Ruby reported"
needs Ruby's manifest and nothing else. Partial input relaxes exactly one
thing, the all-six overlap verdict, because that is the only claim needing
every runner.

A SECOND ROSTER BLOCK WAS IGNORED. parse_roster took the first begin and the
first end, so a duplicate complete block was never compared and a stale line
inside it passed unnoticed. Now exactly one of each delimiter is required, with
"no roster at all" kept as its own distinct failure so the message names what
happened.

A CASE LISTED TWICE UNDER ONE RUNNER WAS INVISIBLE. Array#- removes every
matching occurrence, so `actual - stated` and `stated - actual` both came back
empty and the duplicate passed — carrying two possibly conflicting
classifications for one case, against a section promising one line per runner
x test. Duplicate bullets and duplicate runner headings are both rejected now.

Four self-test cases, each shown to fail against the un-fixed code first:
roster drift caught in partial mode, two roster blocks, one case listed twice,
two sections for one runner.
Copilot AI review requested due to automatic review settings August 14, 2026 04:26

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scripts/check-fixture-execution.rb:264

  • This silently ignores valid Markdown bullets that use *, +, or up to three spaces of indentation. With clean manifests, a stale line such as * "a closed gap" remains visibly in the roster but is omitted from stated, so the two-way check exits successfully—the false green this parser is intended to prevent. Recognize all unordered-list markers (or reject unsupported ones), and add the stale alternate-marker case to the self-test.
    next unless line.start_with?("- ")

scripts/check-fixture-execution.rb:316

  • The new ambiguous-name rejection is not exercised. The existing “one name in two files” test splits the two files across disjoint runner sets, so each runner sees the name only once and this branch never runs. Add a case where one runner excludes the same name from alpha.json and beta.json, and assert the more than one fixture failure.
    dupes = actual.tally.select { |_, n| n > 1 }.keys
    unless dupes.empty?
      errors << "#{m.runner} excludes #{dupes.first.inspect} in more than one fixture; the SPEC " \
                "roster identifies cases by name alone and cannot express that. Add the fixture " \
                "to those roster lines and teach this check to read it."

Copilot's suppressed comment, and the third silent-pass hole this round: a
stale roster entry written as an indented `-`, or with `*`, `+`, or `1.`, was
skipped by `start_with?("- ")`. Skipped means absent from the roster set, which
means it contradicts no manifest, which means the gate passes. A false green —
the one outcome this extractor may not produce, and the invariant the PR argues
from.

Three holes of one shape in a single round is evidence about the instrument,
not a queue of three patches, so this is not a third selector. The default is
inverted instead: a line that looks like a list item in ANY form must be the
canonical `- "case name"`, or it is an error. One predicate covers every
spelling, including ones nobody has written yet — the same "refuse what you do
not understand" move that was right for the roster tables' cell count in #740,
rather than teaching the parser one more shape.

Prose is untouched because it is not list-shaped: the roster's headings wrap
onto continuation lines and Python's section is a sentence, and verified that
every list-like line in the real roster is already canonical.

Four self-test cases, one per marker form, all shown to fail against the
permissive skip.
Copilot AI review requested due to automatic review settings August 14, 2026 04:32
@jeremy

jeremy commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

Round one at 4ce947b6, and it needs a plainer summary than "four findings fixed", because the pattern matters more than the individual fixes.

The PR argued from an invariant that was false three times over

This PR justifies adding a prose parser — one PR after #740 declined to grow one — on the claim that every misreading surfaces as a set mismatch, never a passing comparison. Review found three separate ways it produced a silent pass:

  1. A second roster block was never compared (first begin/end wins), so a stale line inside it passed unnoticed. (Copilot)
  2. A case listed twice under one runner was invisible: Array#- removes every occurrence, so both diffs came back empty. (Codex)
  3. A stale entry written as *, +, 1. or an indented - was skipped by start_with?("- "); skipped means absent from the roster set, which contradicts no manifest, which passes. (Copilot, suppressed)

Each is fixed. But three holes of one shape in a single round is evidence about the instrument, not a queue of three patches — so #3 is deliberately not a third selector. The default is inverted: a line that is list-shaped in any form must be the canonical - "case name", or it is an error. One predicate covers every spelling including unwritten ones — the same "refuse what you do not understand" move that was right for the cell count in #740.

Flagging for your call, not acting further: the invariant now holds as far as two independent reviewers and I can see, and the fail-closed default is a structurally different guarantee from the three patches that preceded it. But if you would rather this roster stop being prose altogether — a data file with the SPEC block generated from it, no parser at all — that is a legitimate read of three-strikes and it is your decision, not mine. I have not started it.

Also fixed, and worse than it looked

Roster drift went unchecked on Linux entirely. The comparison sat after the partial-mode early return, and the Linux make check-fixture-execution path always passes --partial because Swift's manifest is macOS-only. So the local path never checked the roster at all — a stale Go or Ruby line passed and only the CI fan-in could catch it. Both bots found it independently. It now runs before the branch, over whatever manifests exist; partial relaxes exactly one thing, the all-six overlap verdict.

Every fix has a self-test case shown to fail against the un-fixed code first (11 new cases total). make check-fixture-execution, its self-test, and make doc-constants-check all green locally.

@jeremy

jeremy commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 4ce947b6ae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@jeremy
jeremy merged commit 7851d38 into main Aug 14, 2026
45 checks passed
@jeremy
jeremy deleted the spec-roster-derived branch August 14, 2026 05:09
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.

SPEC §19's Zero-Skip roster restates derivable runner state with nothing checking it

2 participants