Skip to content

Let consumers declare their gated Python with a committed .python-version - #30

Merged
Antawari merged 3 commits into
mainfrom
catrina/py314-caller-python
Oct 16, 2026
Merged

Antawari merged 3 commits into
mainfrom
catrina/py314-caller-python

Conversation

@Antawari

Copy link
Copy Markdown
Contributor

⚠️ SHARED INFRA. This changes the reusable gate that every consumer's quality-gate check runs. Read it with that weight.

What changes

The gate hardcoded python-version: "3.12" and accepts no inputs by design, so no consumer could move the interpreter it is gated on. This PR adds one resolver step, id: python. It runs in the consumer tree after the consumer checkout and feeds actions/setup-python through ${{ steps.python.outputs.version }}.

Consumer state Gate behaviour
no .python-version version=3.12 plus a ::notice:: saying so. This is unchanged for every current consumer.
.python-version = 3.14 / 3.14.8 (CRLF or padding trimmed) gates on that version
blank, multi-line, or outside ^3\.[0-9]+(\.[0-9]+)?$ refused: one typed ::error::quality-gate refused: … line that names the shell-quoted raw bytes and the grammar. Never downgraded to 3.12.
dangling symlink / directory fails (nonzero, no version written). Never treated as absent.

The zero-inputs doctrine is intact: the version comes from committed consumer state, never from a caller knob. No inputs:, no continue-on-error, no paths: filter, and every uses: stays SHA-pinned. self-ci.yml (the kit's own CI) stays on 3.12 in this change.

How it was proven

  • Test first. The rod (tests/test_gate_python_version.py, which replaces the literal-pinning test_gate_uses_python_312) was committed first (c59c61e) and observed red: 14/14 driving tests failed, 35 inherited passed. The implementation (e41dbe9) came from a different author and is fenced out of the tests. A third pass (fc6445c) tightened the refusal rods so they prove a typed refusal and not merely a nonzero exit.
  • The rod executes the real step. It lifts the resolver's run: script out of the parsed YAML and runs it with bash --noprofile --norc -eo pipefail against fixture consumer roots.
  • Teeth. 7 hand mutants of the workflow, all killed: untyped error line (10 fail), value not named (10 fail), dangling symlink treated as absent (1), blank silently falls back to 3.12 (2), default path goes quiet (1), grammar loosened (2), trailing whitespace not trimmed (3).
  • Full kit gate (cf-gate, which is what self-ci runs): PASS 11/11 on CPython 3.12.15 and 3.14.8. Suite: 606 passed on both.

Owed (not fixed here, by design)

  • A present-but-unreadable .python-version (directory, dangling symlink, permission error) fails through cat under bash -e. That is fail-safe but untyped.
  • Bash command substitution drops NUL bytes, so a UTF-16LE-encoded 3.14 would be accepted as 3.14.
  • The LawGiver pre-gate is not live yet (its second PR is still open), so it was not run.

After merge

Each private consumer gets one PR that adds .python-version = 3.14 and bumps its kit pin to this merge commit. Every pin bump also pulls in the gauge changes between the old pin and this one, and each consumer PR will name its bump size.

🤖 Generated with Claude Code

Antawari and others added 3 commits October 16, 2026 11:42
The shared gate hardcodes Python 3.12 and takes no inputs, so a consumer
cannot move its gated interpreter. These rods pin the contract for a
resolver step that reads the consumer's committed .python-version: 3.12
only when no file exists, a loud refusal for an empty or malformed file.
They replace the rod that pinned the 3.12 literal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…version

The shared gate hardcoded Python 3.12 and takes no inputs, so no consumer
could move its gated interpreter. A resolver step now reads the
consumer's committed .python-version in the consumer tree and feeds
setup-python. No file keeps today's 3.12, said out loud. A committed
file that is blank, multi-line or outside ^3.N(.N)? is refused with a
typed error, never downgraded to the default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The refusal rods measured only a nonzero exit and no version= line,
which a bash crash would also satisfy. They now require exactly one
typed ::error:: refusal line carrying the accepted grammar and the
shell-quoted raw bytes, a loud ::notice:: on the default path, CRLF and
padded accepts, and refusal (never fallback) for a dangling symlink or
a directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Antawari
Antawari merged commit 2e35885 into main Oct 16, 2026
1 check passed
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