fix(drift-gate): shipped-eslint-rules --check detects orphans + runs at pre-push - #990
Merged
Conversation
…at pre-push Two deviations from the drift-gate invariant (cold backward-sweep, 2026-07-11, python-delivery-v0 S1): 1. No orphan detection — the check loop iterated only from .ts sources, so deleting a rule source left its committed .mjs/.d.ts silently shipping. Added an orphan walk (committed artifact without a matching shippable source = RED), mirroring the python-template drift gate's committed-artifacts-without-source walk. 2. CI-only channel — the gate ran solely in audit-self.yml, one channel later than its pre-push siblings (synth-bundle --check, manifest render). Wired as pre-push section 3g (exit 2 = tsc absent → degrade to warn-skip, mirroring 3f's esbuild skip). RED-then-GREEN evidence: with require-error-boundary.ts moved aside, --check emits ORPHAN for both .mjs and .d.ts and exits 1; restored, exits 0 (24 checks). Verified under macOS bash 3.2 (#989 precedent) and npm --prefix packages/core run test:hooks (716/716 green). Prior-art: skipped — drift-gate hardening of an existing script + pre-push wiring; no new dependency, module, or capability
…hifted by section 3g Only delta across all 8 fingerprints is the packages/core/hooks/pre-push.ts sha (shipped file). SNAPSHOT_MODE=capture + local compare 8/8 green. Prior-art: skipped — snapshot baseline regeneration after shipped-file edit, no new capability
artyhoo
added a commit
that referenced
this pull request
Jul 12, 2026
…firing proof, fingerprint row (python-delivery-v0) (#996) * feat(install): getff Python toolchain entry lane + refresh-parity + firing self-check (python-delivery-v0 S2 T1) `install.sh python` / `./setup python` route to the pure-bash Python delivery (setup.d/45-python.sh) instead of the npm stack pipeline: an explicit `python` positional always wins; else pyproject.toml present + no package.json → OFFER (interactive default No; non-interactive / --dry-run decline to the npm lane). The lane bypasses the npm package.json precondition + stack pick and early-exits before the setup.d layer loop, so no npm-assuming step fires. The npm flow stays byte-identical (all 8 fingerprint baselines green; python logic is gated on TOOLCHAIN=python which the npm fixtures never trigger). --refresh re-delivers framework-owned Python artefacts (GETFF_TOOLCHAIN_REFRESH → refresh_safe overwrite of the rules dir + our-own sgconfig/ruff), auto-detected via the .getff-python-install.log / .getff/astgrep-rules marker on a bare `--refresh`. Augment-merged / refused consumer files stay untouched (never clobbered). refresh-covers-full-delivery.test.sh Check 4 replaces the S1 blanket 45-python.sh exclusion with real source-token refresh parity (RED before the refresh_safe branches landed, GREEN after — TDD). Post-install firing self-check (always-run + graceful degrade, matching the 99-finalize.sh capstone self-verify UX — no separate opt-in flag): plants a violation in an OS temp dir ONLY (mktemp -d, never under the consumer tree — STOP line), runs the delivered ast-grep rules + ruff config against it, asserts BOTH fire RED, then removes the temp dir. An absent tool → loud degrade printing the exact pinned manual command, never silently green (attention-is-not-a-mechanism.md §1). Tests: tests/install-sh/python-entry-lane.test.sh (20 arms — detection matrix, no-package.json bypass, npm-untouched, explicit override, --refresh overwrite + marker auto-detect, self-check fire + tool-absent degrade), wired into audit-self.yml. S1 python-delivery (43), refresh-covers (6), byte-identical (8/8), principles (303) all green. Prior-art: prior-art-evaluations.md#216 (BUILD the thin bash writer — no headless tool delivers a pre-rendered, multi-tool, augment-first lint bundle into an existing repo; the S2 entry lane + firing self-check realise #216's delivery-mechanism verdict, reusing install.sh copy_safe/refresh_safe + the 99-finalize self-verify UX, zero new deps). * feat(install): getff Python consumer CI template + fingerprint matrix python row (python-delivery-v0 S2 T2) Consumer CI template: - Ship packages/core/templates/python/github-actions-ci.yml — pinned @ast-grep/cli@0.44.1 + ruff==0.15.21 installs (ci-tool-pinning.md Rule A; both pins re-verified LATEST 2026-07-12 via npm registry dist-tags + PyPI JSON, matching the framework CI pins audit-self.yml:232,242), failing gates `ast-grep scan` + `ruff check .`. Action SHAs reuse repo-verified pins (checkout v4.2.2, setup-node v4.4.0, setup-python v5.5.0). - Delivered by the python lane (setup.d/45-python.sh _py_deliver_ci) to a getff-NAMESPACED .github/workflows/getff-python.yml — never clobbers the consumer's own ci.yml; loud REFUSE on a non-getff file at our path; refresh-aware (source-token parity keeps refresh-covers Check 4 green). Same collision-discipline class as the S1 config cells (STOP-line: no silent clobber). Fingerprint snapshot matrix python row: - snapshot.sh: python row with its OWN seeding (pyproject.toml, no package.json) + collision-cell brownfield variants (pre-existing ruff.toml → cell iii refuse; pre-existing sgconfig.yml → cell ii structural merge), decoupled from the npm {greenfield,brownfield} matrix — NOT a naive STACKS append (the npm seeding cannot seed a pyproject / exercise the augment-first cells). - Exclude the timestamped .getff-python-install.log from the fingerprint (running audit trail, not a delivered config artefact — already excluded from the layer's own (v) idempotency checksum) so the python row is deterministic; no-op for npm stacks. - New baselines tests/install-sh/baselines/python/{greenfield,brownfield-ruff,brownfield-sgconfig} .fingerprint. Existing npm baselines byte-identical (proven — no npm baseline regen). Tests: python-delivery.test.sh +8 CI-collision arms (fresh/no-ci.yml-clobber/refuse/refresh) = 50/0; byte-identical.test.sh 11/0 (8 npm + 3 python) + shellcheck 16 clean; determinism proven (SNAPSHOT_MODE=compare ×2 = 11/0 idempotent); refresh-covers Check 4 6/0; principles 303/303. Prior-art: prior-art-evaluations.md#216 (getff Python consumer delivery mechanism — the CI template + its collision-safe delivery lane are part of the same augment-first consumer-install capability this SSOT row justifies; no upstream analog for pinned multi-tool lint CI delivery). * fix(python-delivery): EOF-safe OFFER prompt + explicit-arg refresh precedence + drop dead TOOLCHAIN_EXPLICIT Addresses S2 entry-lane review findings on install.sh (commit 9ad9dd5be): - Important: the interactive OFFER-lane `read` prompt for a pyproject-only repo aborted under `set -euo pipefail` at EOF (non-tty invocation, closed stdin) — a bare `read` returns non-zero at EOF, so `set -e` killed the script right there with a message-less exit 1, instead of falling through to the documented "decline -> npm lane -> clean no-package.json abort" path. `read -rp ... || _py_ans=""` makes the read EOF-safe. - Minor: the `.getff/astgrep-rules` dual-consumer `--refresh` marker branch fired before any explicit stack arg was considered, so `install.sh ts-server --refresh` on a repo with both package.json and a stale/prior python marker silently rerouted to the python-only refresh and exited 0, skipping the npm refresh with no error. Gated the marker branch on `[ -z "$STACK_EXPLICIT" ]` so an explicit npm stack/toolchain arg always wins over the marker auto-route. - Minor: removed the dead `TOOLCHAIN_EXPLICIT` variable (set, never read — "explicit wins" was already achieved via the `[ -z "$TOOLCHAIN" ]` guard on the auto-detect block) and fixed the misleading comment describing it. Extended tests/install-sh/python-entry-lane.test.sh: arm (4) now asserts the clean "No package.json found" message + exit 1 (RED before the fix: message absent, script died silently at the read line); new arm (7b) asserts an explicit `ts-server --refresh` on a repo with a python marker present refreshes the npm stack, not the python-only path (RED before the fix: wrong banner + no npm artefact delivered). 26/26 in that file (was 20); the three sibling suites (python-delivery.test.sh, refresh-covers-full-delivery.test.sh, byte-identical.test.sh) stay green with no fingerprint drift (11/11: 8 npm + 3 python). Prior-art: skipped — review-fix round, no new capability * fix(python-delivery): CI template enforces getff ruff bans via isolated --config gate (ruff-collision cells) S2-T2 review Important (coordinator-confirmed fix-now): the shipped consumer CI template ran a bare `ruff check .` with no `--config`. In a ruff-collision cell (consumer has their own ruff.toml or pyproject [tool.ruff]), the getff TID bans ship as a NON-discovered getff-ruff.toml, so `ruff check .` discovers the CONSUMER's config and the getff bans are SILENTLY unenforced — a green gate enforcing nothing (#discipline-theatre, attention-is-not-a-mechanism.md §1, the exact failure this project exists to prevent). Probe-proven (pinned ruff 0.15.21, live, not from memory): `ruff check <dir> --config <getff.toml> --no-cache` enforces ONLY the getff config's rules — `--config` REPLACES config discovery (does not merge) — is self-contained (no consumer config needed, exit 0 on a clean tree), and fires RED on a getff violation regardless of any consumer ruff config present. Fix: - setup.d/45-python.sh: the ruff lane ALWAYS delivers the getff bans to a stable, cell-independent .getff/ruff-bans.toml (fresh dir AND every collision cell) via _py_copy_or_refresh (framework-owned, getff-header-marked, refresh-aware). Source token $tpl/ruff.toml already has copy+refresh parity, so refresh-covers Check 4 is unchanged. Firing self-check now prefers .getff/ruff-bans.toml (proves the exact file CI points at). S1 no-clobber discipline intact — no consumer file touched. - packages/core/templates/python/github-actions-ci.yml: the bare `ruff check .` becomes two steps — discovered-config check (unchanged) + an isolated getff-bans gate `ruff check . --config .getff/ruff-bans.toml --no-cache` that fires RED on our bans in EVERY cell. - _py_deliver_ci REFUSE hint: comment noting its pins intentionally mirror the template + hint kept in sync with the two-step gate (S2-T2 Minor). Tests (TDD, RED->GREEN documented in the report): +8 arms in python-delivery.test.sh (58 pass, was 50), 3 deterministic + 1 tool-gated live-fire proving bare check is BLIND while the getff gate fires RED in a collision cell. python-entry-lane 26/0, refresh-covers 6/0, byte-identical 2/0 (11/11 + shellcheck 16 clean), test:principles 303/0. Python fingerprint baselines regenerated (python rows only, +1 file each = .getff/ruff-bans.toml; npm baselines byte-identical, drift = 0). Prior-art: skipped — review-fix round, no new capability (delivers an existing artifact to a stable path + a CI gate step). * fix(python-delivery): register hand-authored CI template as non-rendered in S1 drift gate (rebase-integration) Rebasing S2 onto staging pulled in S1's python byte-drift gate (#991, backends/python-templates-drift.test.ts), which treats EVERY file under packages/core/templates/python/ as a byte-identical render of the curated starter nodes. S2 T2 hand-authored github-actions-ci.yml there (following the repo-wide convention — ts-server/presets ship their CI workflow inside their own templates/<stack>/ dir), so the now-present drift gate flagged it as an orphan. S2 was reviewed against pre-S1 base #990, so this cross-stage interaction could not surface before the rebase. Fix: register github-actions-ci.yml in a new NON_RENDERED_TEMPLATE_FILES allowlist. checkPythonTemplateDrift() no longer orphan-flags it AND now asserts it EXISTS (a silent deletion still surfaces as `missing` — the exclusion is not a blind pass, per attention-is-not-a-mechanism). The template's byte-integrity remains guarded by tests/install-sh/byte-identical.test.sh (delivered .github/workflows/getff-python.yml hash) + python-delivery.test.sh (cmp template↔delivered) — no coverage hole. No template bytes changed; byte-identical stays 11/11. Prior-art: skipped — rebase-integration fix reconciling S2's hand-authored CI template with the S1 render-drift gate that arrived via rebase; no new capability area. * fix(python-delivery): arm-9 degrade test uses a research-tool-excluded PATH (CI-robust) The firing-self-check degrade arm forced PATH=/usr/bin:/bin to simulate 'no tools', assuming that strips ast-grep/ruff/uvx. That holds on macOS (node + ast-grep live in homebrew) but NOT on CI runners that ship ast-grep in /usr/bin — there `command -v ast-grep` still resolved, the ast-grep lane FIRED instead of degrading, and the arm never exercised the both-tools-absent degrade summary it exists to prove (S2 CI-only fail 2026-07-12: PASS=24 FAIL=2). Fix: build a coreutils-only bindir that symlinks everything EXCEPT the research tools (ast-grep/sg/ruff/uvx/npx/node/pip), point the self-check PATH at it. Now `command -v` genuinely fails for both lanes on any runner → both degrade → pinned manual commands + no-green summary assert as intended. Production degrade code unchanged (it was correct; pins are hardcoded constants). Local: 26/0. Prior-art: skipped — test-only environment-robustness fix, no new capability --------- Co-authored-by: t <t@t.co>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Brings
scripts/build-shipped-eslint-rules.sh --checkup to the repo's drift-gate invariant. Cold backward-sweep (2026-07-11, python-delivery-v0 S1) found two deviations:.tssources, so deleting a rule source left its committed.mjs/.d.tssilently shipping (install.sh keeps copying a rule nobody authors anymore). Added an orphan walk: any committed*.mjs/*.d.tsin aRULE_DIRSentry without a matching shippable.tssource = RED (scripts/build-shipped-eslint-rules.sh:87-101), mirroring the python-template drift gate's committed-artifacts-without-source walk.audit-self.yml:273, one channel later than its drift-gate siblings (synth-bundle--check, manifest render). Wired as pre-push section 3g (packages/core/hooks/pre-push.ts:849-870): exit 2 (tsc absent) → warn-skip degrade, mirroring 3f's esbuild skip; exit 1 → block. CI gate kept as the last-resort channel (unchanged).RED-then-GREEN evidence
Scratch run with
packages/preset-react-spa/eslint-rules/require-error-boundary.tsmoved aside:Also verified under macOS
/bin/bash3.2.57 (#989 array-expansion precedent) — green;npm --prefix packages/core run test:hooks— 716/716 green; the push of this branch exercised the new 3g section live in the full pre-push hook — green.§1.7 Forward-check applied
scripts/build-shipped-eslint-rules.sh:59-108).node_modules/.bin/tsc(scripts/build-shipped-eslint-rules.sh:23-24), no barepip/npm installlines.packages/core/hooks/pre-push.ts:849-854).packages/core/hooks/pre-push.ts:857-861); consumer-safe viaexistsSyncguard + tsc-absent degrade — never hard-blocks a consumer push.Prior-art:trailer carried on the commit.§1.7 Backward-check applied
Class = "compiled/generated-artifact drift gate must (a) detect orphans, (b) run at pre-push, not CI-only". Cold backward-sweep agent (given only the class, never the diff) enumerated ALL drift-gate surfaces:
scripts/build-shipped-eslint-rules.sh:87-101— the only per-source-artifact gate where orphan-class applies; NOW SWEPT-CLEAN (this PR).scripts/build-synth-bundle.sh:55-68— single fixed artifact from single source; orphan-class N/A, missing-case covered; pre-push wired (packages/core/hooks/pre-push.ts:791-792) — SWEPT-CLEAN.packages/core/render/render-rules.ts:101-117— full re-render + diff of a single target; orphan cannot linger; pre-push wired (packages/core/hooks/pre-push.ts:875-886) — SWEPT-CLEAN.scripts/render-rule-index.mjs:42-47— full block re-render; a deleted rule's row disappears on diff; pre-push wired (packages/core/hooks/pre-push.ts:894-901) — SWEPT-CLEAN.scripts/check-skill-drift.sh:203-206— refs/frontmatter validator, not an artifact gate; orphan-class N/A; pre-push §3b (packages/core/hooks/pre-push.ts:732-734) — SWEPT-CLEAN.scripts/render-harness-config.mjs:8-17+render-rule-channels.mjs:21-35— install-time consumer-harness renderers, no committed per-source artifact in this repo → outside the audited class (behaviour covered byrule-channel-degradation.test.ts).No GAP found across the class; the sole surface where orphan detection applies is fixed here.