Skip to content

Gate hardening: honor the consumer's kit pin, gate complexipy, derive known-first-party, honor source_root, Node-24 action pins#6

Merged
Antawari merged 1 commit into
mainfrom
fix/gate-kit-defects-node24
Jun 11, 2026
Merged

Gate hardening: honor the consumer's kit pin, gate complexipy, derive known-first-party, honor source_root, Node-24 action pins#6
Antawari merged 1 commit into
mainfrom
fix/gate-kit-defects-node24

Conversation

@Antawari

Copy link
Copy Markdown
Contributor

What merging this blesses

Five gate-hardening changes, each anchored to a measured defect and carrying a control rod that MUST fail:

1 · Kit-pin honor — the gate floated to kit MAIN (the reproducibility defect)

Observed: github.job_workflow_sha is documented as the reusable workflow's commit but evaluated EMPTY at run time. The kit checkout received no ref: and actions/checkout silently fell back to the default branch.

Run-log proof (notioncrm, run 27370755155, job 80882071363):

  • The kit-checkout step's with: block carries no ref: at all (the expression evaluated empty).
  • The step then ran git checkout --progress --force -B main refs/remotes/origin/main — kit MAIN, while notioncrm's committed stub pins eb4f88c3a863bebf08b9d4f3d5e4044a6133b13f.
  • Consequence: its CLAUDE.md carries the older sticky vintage that matches at its pin but reads STICKY_INTRO_ABSENT at main — the sole red step in an otherwise green run. The same float produced the divergent class/residue counts observed on two other consumers the same night.

Fix: a new step between the kit checkout and the kit install re-anchors the checkout to the pin the consumer committed in its caller stub (grep of the consumer's own .github/workflows for the full-SHA uses: ref — committed state, never a caller knob; zero-inputs doctrine intact). Two distinct pins refuse. No determinable pin refuses (exit 1) unless github.job_workflow_sha is populated AND already matches HEAD — fail-safe, never a silent float to main.

Control rod (functional, not just structural): tests/test_gate_hardening.py extracts the step's actual script from the YAML and runs it against a real git fixture — a kit clone sitting at main with a consumer stub pinning an older SHA. The script MUST move HEAD to the pin; the no-pin and two-pin cases MUST exit 1.

Migration for the 7 mounted repos: none. No caller-side change. Every mounted repo already commits exactly one full-SHA pin, which is precisely what the step reads. The fix takes effect for a consumer when it bumps its pin to a kit SHA containing this change. A repo calling the gate by branch ref (doctrine-banned) will now fail loudly instead of floating — by design.

2 · complexipy joins the gate (was runbook-only)

Per-repo complexipy-snapshot.json baselines are committed fleet-wide and the conventions define the watermark as a gate, but no workflow step enforced it (one consumer shrank its snapshot 36→13 with nothing in CI gating it). Now: a plain complexipy "$CF_SOURCE_ROOT" run (auto-compares against the CWD snapshot — tool-spike semantics; never piped, piping masks the exit code) with the mypy-style presence rule — a Python repo without its committed snapshot FAILS; a Python-free repo skips visibly. The kit ratchets FIRST: its own snapshot is booted ([], clean) and gated in self-ci.yml.

Fleet note: consumers meet this step only when they bump their pin; a consumer missing its snapshot at that point boots one per configs/BASELINE-CONVENTIONS.md (green by construction).

3 · known-first-party derived per consumer (the I001 cross-config conflict)

Measured: the kit's gauge flagged 2 I001 in a consumer's tests; fixing them per the kit's sort created 2 NEW I001 under the repo's legacy isort config. Root cause: the legacy config forces all of the repo's packages first-party via known-first-party, while the kit's gauge classifies by on-disk detection — imports that don't resolve on disk (<pkg>.tests.* with tests outside the package tree, modules authored RED) get mis-filed third-party. A shared gauge file cannot statically name every consumer's packages, so cf-repo-config grows a first-party field (derived from the consumer's resolved source root: top-level packages incl. PEP 420 + top-level modules; non-shipping dirs excluded at root layouts) and the gate feeds it inline: --config "lint.isort.known-first-party=$CF_FIRST_PARTY". Verified on the real conflict: kit gauge alone = 2 errors; kit gauge + derived ["<pkg>"] = All checks passed, matching the legacy verdict. Derived from committed state — not a caller knob; CI's value cannot be weakened by a vendored copy.

4 · cf-exemptions honors the declared source_root

Measured (mexxa main-green pass): cf-exemptions discovered the repo-root src/ (JS) and ignored the declared server/src — both registered exemptions were documentation-grade; the scanner never visited the files they cover. The scan surface now resolves through cf-repo-config exactly like the gauges (typed GATE_CONFIG_INVALID on an invalid declaration, never a silent fallback). Control rod: a fixture with source_root = "server/src" and an unregistered suppression in server/src/ MUST fail — it silently passed before (tests/test_exemptions_source_root.py).

5 · Node-24 action pins (deadline 2026-06-16)

The deprecation annotation on the notioncrm run names both pinned actions: "Actions will be forced to run with Node.js 24 by default starting June 16th, 2026." Both bumped, SHA-pinned (never floated to a tag), releases verified against the actions' own repos (runs.using: node24 confirmed at each commit):

  • actions/checkout v4.2.2 → v6.0.3 (df4cb1c069e1874edd31b4311f1884172cec0e10)
  • actions/setup-python v5.3.0 → v6.2.0 (a309ff8b426b58ec0e2a45f0f869d46889d02405)

The old Node-20 SHAs are denylisted ratchet-style (Dependabot may bump forward freely; a rollback to the deprecated runtime fails the test battery).

Verification

  • Tests 310 → 337, all green locally.
  • Full self-ci battery green locally on this branch: ruff check · ruff format --check · cf-sticky-check · cf-file-budget · cf-recursion-check · cf-exemptions · cf-import-contract · mypy (0 errors) · complexipy · pytest.
  • The file budget refused two of this PR's own test files at 554/565 lines — fixed by splitting (test_gate_hardening.py, test_exemptions_source_root.py), not baselined. The gate caught its maker; the kit submits before it preaches.
  • DESIGN.md and the SHA-pin doctrine updated to match the shipped behavior (the docs claimed job_workflow_sha was reliable; the record now carries the observed defect and the re-anchor mechanism).

Files

  • .github/workflows/quality-gate.yml — pin-honor step · complexipy step · first-party wiring · Node-24 pins
  • .github/workflows/self-ci.yml — complexipy step · Node-24 pins
  • src/cf_quality/repo_config.pyfirst_party_packages() + first-party CLI field + shared non-shipping set
  • src/cf_quality/exemptions.py — declared-source_root resolution (shares the set; no second list to drift)
  • configs/ruff-base.toml — documents why known-first-party is gate-derived, never static
  • complexipy-snapshot.json — the kit's own watermark, booted clean ([])
  • tests/test_gate_hardening.py · tests/test_exemptions_source_root.py · tests/test_repo_config.py · tests/test_workflows.py — the rods
  • DESIGN.md · docs/sha-pin-doctrine.md — record matches shipped behavior

CI state

Will be reported on this PR's checks (self-ci runs the full battery on this branch). The reusable gate's consumer-facing behavior is covered by the functional script rod above; the live consumer proof lands when a consumer bumps its pin to this commit (Anta-gated, per the pin doctrine).

DO NOT MERGE without Anta's review — this PR records and fixes the defects; only Anta merges.

🤖 Generated with Claude Code

…t-party, honor source_root, Node-24 action pins

Four measured gate defects from the overnight burn's adversarial verifies,
plus the Node-20 deadline, each with a control rod that MUST fail:

1. Kit-pin honor — github.job_workflow_sha was OBSERVED empty at run time:
   the kit checkout received no ref and floated to kit MAIN (git checkout -B
   main refs/remotes/origin/main), so SHA-pinned consumers were gauged by an
   unpinned gauge (counts irreproducible, twice in one night). The gate now
   re-anchors the kit checkout to the pin the consumer COMMITTED in its
   caller stub, refuses two distinct pins, and refuses to run when no pin is
   determinable — fail-safe, never a silent float. Rod: the step's script
   runs against a real git fixture whose floating checkout must be moved to
   the declared pin (tests/test_gate_hardening.py).

2. complexipy joins the gate — the watermark was runbook-only; now a
   workflow step in both quality-gate.yml and self-ci.yml with the
   mypy-style presence rule (a Python repo without its committed snapshot
   FAILS; Python-free repos skip visibly; never piped). The kit ratchets
   FIRST: its own snapshot is booted and gated.

3. known-first-party derived per consumer — a shared gauge file cannot name
   every consumer's packages, and ruff's on-disk detection mis-files
   first-party imports that do not resolve (measured: fixing the kit's two
   I001 created two NEW legacy-isort I001 in the same repo). cf-repo-config
   grows a first-party field (derived from the resolved source root) and the
   gate feeds it inline to the pinned ruff gauge.

4. cf-exemptions honors the declared source_root — the scanner discovered
   the repo-root src/ (JS in the measured case) and never visited the
   declared tree, making every registered exemption documentation-grade.
   The scan surface now resolves through cf-repo-config exactly like the
   gauges. Rod: an unregistered suppression under a declared server/src
   must fail (it silently passed before).

5. Node-24 action pins — GitHub forces Node 24 on 2026-06-16; both pinned
   actions bumped to their Node-24 releases (checkout v6.0.3, setup-python
   v6.2.0, full-SHA pinned, releases verified). The deprecated Node-20 SHAs
   are denylisted ratchet-style.

Tests 310 -> 337; full self-ci battery green locally. Two test files split
at the file budget's own refusal — the gate caught its maker, the split is
the fix, not a baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Antawari Antawari merged commit 63408c8 into main Jun 11, 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