Skip to content

lint: nothing may cite a repository path that is not here (+ 11 that did) - #22

Merged
EricAndrechek merged 1 commit into
mainfrom
dead-references
Sep 11, 2026
Merged

EricAndrechek merged 1 commit into
mainfrom
dead-references

Conversation

@EricAndrechek

Copy link
Copy Markdown
Member

scripts/lint-public.sh catches the private repository by name. This is the other half of the same failure: a path that is simply not in this repository. Neither rule would have caught the other's cases.

What was found

docs/proposals/rows-export.md was cited six times — in include/chtypes.h and in the Go, Python, Rust and TypeScript sources. Five of those ship inside published packages, confirmed by unzipping the Go module from proxy.golang.org and building the sdist:

github.com/wave-rf/chtypes/go@v0.1.2/chtypes/linked.go
github.com/wave-rf/chtypes/go@v0.1.2/chtypes/multiversion.go
chtypes-0.1.2/pyproject.toml, chtypes-0.1.2/tests/conftest.py

The file went to the core repository with the rest of the SDK-authoring material. Four more of the same shape:

dead citation where shipped?
docs/defaults-matrix.md go/chtypes/transform.go, chtypes.go yes
docs/type-coverage.md include/chtypes.h, chtypes.go yes, in all four
docs/fetch.md 4 files + both parity JSONs (38 occurrences) moved under guides/
scripts/check-parity-doc.sh both parity JSONs never existed

The last one is worth naming. The manifest said that script "asserts the two agree on the object-model table." The check is real and has been running all along — it lives in python/tests/test_parity.py:394. But anyone looking for it by the name given would have concluded there was no check.

Where the surrounding prose already states the rule, the dead citation is dropped rather than pointed somewhere a reader cannot go. docs/fetch.md became docs/guides/fetch.md, which exists and still has the cited §6.

Also swept, same family: three ci/steps/* paths and a tests/acceptance/tests/arbiter pair that exist only in the private repository, and four citations of a certify workflow renamed to sdk-suites on 2026-09-11 — a stale name for another repository's workflow, now described instead of named.

The guard

scripts/lint-paths.sh, blocking in the existing public job — no new job, so no required-check rename.

A path may resolve against the repository root or any of the four binding roots, because this repository is four sibling packages and a doc-comment inside rust/ naming tests/integration.rs is correct. Four skips, each for a reason rather than to make the check pass: generated fixtures, .gitignore (patterns, not paths), Cargo.toml (crate-relative by definition), CHANGELOG.md (deliberately names removed things).

It was proved rather than trusted, three ways:

  1. --selftest fires on a dead path and stays silent on a live one and a crate-relative one.
  2. Run against the real tree with two of the real dead citations planted back — caught both.
  3. On its first real run it caught 38 occurrences of docs/fetch.md that my own regex had missed, having fixed exactly one spelling of it. That is the repo's recorded trap — a path built from segments survives a regex that fixes the literal — landing on the person who wrote the sweep, and being caught by the guard in the same commit.

It also immediately flagged my own CI comment for quoting the dead path literally, which is why that comment now describes it instead.

Unrelated, found by the docs read

docs/guides/batches.md gave the document flags as DOC_VALUES/DOC_TRANSFORMS/DOC_DEFAULTS — correct only for Python and TypeScript. Go spells them DocValues, Rust DocFlags::VALUES, per tests/parity/manifest.json, the machine-readable contract. A Go or Rust reader following that line would not compile. Now given per language.

Verification

gofmt clean · go build/vet/test pass · cargo build --locked passes · pnpm typecheck exits 0 · lint-public, lint-paths, lint-prose, markdownlint, dprint check, lint-actions (actionlint + shellcheck over the new script) all clean. Both parity JSONs are byte-identical before and after, as they must be.

🤖 Generated with Claude Code

https://claude.ai/code/session_018ckJDLjWhAAttQVrYnQEJD

…did)

lint-public.sh catches the private repository by NAME. This is the other
half of the same failure: a path that is simply not in this repository.
Neither rule would have caught the other's cases.

WHAT WAS FOUND. `docs/proposals/rows-export.md` was cited SIX times — in
include/chtypes.h and in the Go, Python, Rust and TypeScript sources, five
of which ship inside the crate, the sdist, the module and the npm tarball
(confirmed by unzipping the Go module from proxy.golang.org and building the
sdist). The file is not here; it went to the core repository with the rest
of the SDK-authoring material. Four more of the same shape:

  docs/defaults-matrix.md   go/chtypes/transform.go, chtypes.go   ships
  docs/type-coverage.md     include/chtypes.h, chtypes.go         ships
  docs/fetch.md             4 files + both parity JSONs (38x)     moved under guides/
  scripts/check-parity-doc.sh  both parity JSONs                  never existed

The last one is worth naming: the manifest said that script "asserts the two
agree on the object-model table". The check is real and has been running all
along — it lives in python/tests/test_parity.py — but anyone looking for it
by the name given would have concluded there was no check.

Where the surrounding prose already stated the rule, the dead citation is
simply dropped rather than pointed somewhere a reader cannot go. docs/fetch.md
became docs/guides/fetch.md, which exists and still has the cited §6.

Also swept, same family: three `ci/steps/*` paths and one `tests/acceptance`
/`tests/arbiter` pair that exist only in the private repository, and four
citations of a `certify` workflow renamed to sdk-suites on 2026-09-11 — a
stale name for another repository's workflow, now described instead of named.

THE GUARD. scripts/lint-paths.sh resolves every cited repository path and
fails on the ones that are not there, blocking in the existing `public` job
(no new job, so no required-check rename). A path may resolve against the
repository root or any of the four binding roots, because this repository is
four sibling packages and a doc-comment inside rust/ naming tests/x.rs is
correct. Skips are four, each for a reason and not to make the check pass:
generated fixtures, .gitignore (patterns), Cargo.toml (crate-relative), and
CHANGELOG.md (deliberately names removed things).

`--selftest` proves it fires on a dead path AND stays silent on a live one
and a crate-relative one — a rule that matches nothing passes silently, which
is the failure this exists to prevent. It was also run against the real tree
with two of the real dead citations planted back, and caught both. On its
first run it caught 38 occurrences of docs/fetch.md that my own regex had
missed, having fixed exactly one spelling of it.

Unrelated, found by the docs read: docs/guides/batches.md gave the doc-flag
constants as DOC_VALUES/DOC_TRANSFORMS/DOC_DEFAULTS, which is correct only
for Python and TypeScript. Go spells them DocValues and Rust DocFlags::VALUES
(per tests/parity/manifest.json, the machine-readable contract). A Go or Rust
reader following that line would not compile. Now given per language.

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

Copy link
Copy Markdown
Member Author

Blocked on a token scope, not on anything in this PR. All twelve required checks are green on the head commit; gh pr merge is refused:

refusing to allow an OAuth App to create or update workflow
`.github/workflows/ci.yml` without `workflow` scope   (HTTP 403)

The signed-in token carries repo, admin:org, admin:public_key, delete_repo, gist, project — no workflow. Any PR touching .github/workflows/ is unmergeable with it. The fix is one interactive command, which I cannot run:

gh auth refresh -s workflow

then gh pr merge 22 --squash --delete-branch.

--admin does not help: this is a 403 from the OAuth scope check, not a branch-protection refusal, so administrator privileges are not the missing thing.

Why this is not split into "content now, CI wiring later". It could be — the eleven dead-reference fixes and scripts/lint-paths.sh touch no workflow file. But the guard and its enforcement belong in one commit. A linter that exists and is not wired into CI is precisely the failure this repository has already paid for twice: a check that exists only as intention is indistinguishable from a check that passes. Landing the script without the public-job step would recreate that, and the next person would reasonably assume it was enforced.

#23 was rebased onto main instead of stacking here, so it is independent and mergeable without the scope.

@EricAndrechek
EricAndrechek merged commit b8fa5c5 into main Sep 11, 2026
14 checks passed
@EricAndrechek
EricAndrechek deleted the dead-references branch September 11, 2026 18:57
EricAndrechek added a commit that referenced this pull request Sep 14, 2026
A REQUIRED check has been passing vacuously. The `python` job's lint step was

    cd python && uv sync … && uv run ruff check src tests && uv run ruff format --check src tests
    uv run python -c 'import chtypes; …'

and `bash -e` does not exit when a command inside an `&&` list fails — only
the command following the final `&&` is subject to it. So `ruff check` failed,
`ruff format` never ran, the list returned non-zero, the next line ran anyway,
and the step's status became that of the LAST command, which passes.

Measured, not reasoned: the job logs for a0a236c, f92e68e, 92c3360 and 2ff98c1
all report `python` = success while containing `E501 Line too long` lines —
eleven of them, then fifteen once ruff 0.16.7 added four F541 findings. The
gate printed its failures into its own log on every run and reported green.

Proved both directions before and after:

    bash -e 'true && false && echo unreached\necho last'   -> exit 0  (swallowed)
    bash -e 'true\nfalse\necho unreached\necho last'        -> exit 1  (fails)

The fix is one command per line. Fixing the gate alone would turn the required
check red, so the fifteen findings it should have caught are fixed in the same
commit: four F541 auto-fixed, eleven E501 wrapped by splitting string literals
at word boundaries with the concatenated text unchanged, then `ruff format`.
The python suite still passes, 168 tests.

None of the fifteen were introduced by my earlier commits — checked: the four
files involved were last touched by #15, #19 and the release, and the two
lines citing docs/guides/fetch.md already read that path before #22.

Also audited every other multi-line `run:` block across all five workflows for
the same shape. Two matched the pattern and are safe: release-python.yml's and
release-rust.yml's version-agreement steps put the `&&` inside a `$( )`
substitution, and the following line compares the result and exits 1, so an
empty value still fails.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant