From c7e22b21f3609143ba8e34251bc0151ed31fcda9 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:39:07 -0400 Subject: [PATCH 1/8] test: assert preflight derives the one-function-per-file gate RED. testing-conventions @v0 runs `unit one-function-per-file` in its static job, so a lane naming no `gates:` is held to it in CI. preflight's ROOT_GATES omits it, so `just preflight` skips it locally and reports green. skip-changelog: internals/ tooling tests, no published package touched --- internals/checks/tests/e2e/preflight_test.py | 7 +++++++ internals/checks/tests/integration/preflight_test.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/internals/checks/tests/e2e/preflight_test.py b/internals/checks/tests/e2e/preflight_test.py index 4e8ee2720..1a5bccdce 100644 --- a/internals/checks/tests/e2e/preflight_test.py +++ b/internals/checks/tests/e2e/preflight_test.py @@ -44,6 +44,13 @@ def it_prints_the_matrix_from_the_repo_root_with_no_arguments(): assert "==> rust [rust] unit-lint" in proc.stdout assert "==> internals-checks [python] unit-lint" in proc.stdout + def it_prints_the_one_function_per_file_gate_for_a_lane_naming_no_gates(): + proc = preflight() + + assert proc.returncode == 0, f"stdout={proc.stdout!r} stderr={proc.stderr!r}" + assert "==> python-sdk [python] one-function-per-file" in proc.stdout + assert "==> typescript-sdk [typescript] one-function-per-file" in proc.stdout + def it_exits_with_an_actionable_message_when_a_named_workflow_is_missing(): proc = preflight("--conventions", ".github/workflows/conventions.yml") diff --git a/internals/checks/tests/integration/preflight_test.py b/internals/checks/tests/integration/preflight_test.py index fd096f99e..fee44bf79 100644 --- a/internals/checks/tests/integration/preflight_test.py +++ b/internals/checks/tests/integration/preflight_test.py @@ -76,6 +76,15 @@ def it_spans_every_workflow_holding_a_caller_not_just_one(repo_root): assert {file for job, file in jobs.items() if f"==> {job} [" in output} == set(jobs.values()) + def it_derives_the_one_function_per_file_gate_for_a_lane_naming_no_gates(repo_root): + # `@v0` runs `unit one-function-per-file` in the static job, so a lane + # that names no `gates:` is held to it in CI. A matrix omitting it is a + # locally-green run that says nothing about the gate. + output = dry_run().output + + assert "==> python-sdk [python] one-function-per-file" in output + assert "==> typescript-sdk [typescript] one-function-per-file" in output + def it_names_a_missing_workflow_and_the_fix_instead_of_raising(repo_root): result = dry_run("--conventions", ".github/workflows/conventions.yml") From 82391f1c3d79af8167d85157d3ab8862daf1a288 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:48:19 -0400 Subject: [PATCH 2/8] feat: run the one-function-per-file gate, on the release CI runs GREEN. Two changes, and the second is what makes the first observable. `one-function-per-file` joins the gate table and the default-gates list, so preflight derives it for the lanes that name no `gates:` and builds the right argv for a lane that names it. Resolving the CLI moves off `uvx` to `npx testing-conventions@latest`. The PyPI wheel is a laggier distribution of the same tool -- 0.0.104 against npm's 0.0.111 -- and has no `one-function-per-file` subcommand at all, so the gate could not run locally however the table was written. A bare `npx testing-conventions` is worse: npm resolves an unversioned name engine-aware, `engines.node` rose to `>=24` at 0.0.87, and a shim on an older node silently picks 0.0.86 and exits 0. Naming the tag makes the local ruleset the one CI enforces, which is the whole point of the recipe. skip-changelog: internals/ tooling, no published package touched --- internals/checks/src/checks/preflight/gate.py | 34 +++++++++++++------ .../checks/src/checks/preflight/gate_test.py | 18 +++++----- .../checks/src/checks/preflight/matrix.py | 4 ++- .../src/checks/preflight/matrix_test.py | 2 ++ 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/internals/checks/src/checks/preflight/gate.py b/internals/checks/src/checks/preflight/gate.py index e822316a2..ad93591ad 100644 --- a/internals/checks/src/checks/preflight/gate.py +++ b/internals/checks/src/checks/preflight/gate.py @@ -12,10 +12,10 @@ Three pairs cannot be run the naive way, and each is encoded rather than skipped: - * python `mutation` / `unit-coverage` -- `uvx` supplies its own environment, so - the suite's `python3 -m pytest` resolves to an interpreter with no pytest - (#706 saw it as a cosmic-ray baseline failure naming no cause). Both run via - the package's own venv instead. + * python `mutation` / `unit-coverage` -- a throwaway environment supplies its + own interpreter, so the suite's `python3 -m pytest` resolves to one with no + pytest (#706 saw it as a cosmic-ray baseline failure naming no cause). Both + run via the package's own venv instead. * rust `colocated-test` -- the co-change variant rides only the python/typescript language set (rust units are inline, so no sibling can go stale), and the CLI rejects `--base` for it. @@ -36,13 +36,27 @@ MANIFESTS = ("pyproject.toml", "package.json", "Cargo.toml") +# The CLI, resolved the way CI resolves it: npm, newest release. Two distinct +# drifts made the local run enforce a different ruleset than CI, and `@latest` +# closes both. The PyPI wheel `uvx` fetched is a laggier distribution of the same +# tool (0.0.104 against npm's 0.0.111 when this was written), and it has no +# `one-function-per-file` subcommand at all. A BARE `npx testing-conventions` is +# worse: npm resolves an unversioned name engine-aware, `engines.node` rose to +# `>=24` at 0.0.87, and a launcher shim running an older node therefore silently +# picks 0.0.86 -- dozens of releases of rule changes behind, and exiting 0 with +# no version banner to say so. CI dodges that by provisioning node 24 in every +# job; naming the tag dodges it wherever the local node lands. +CLI = ["npx", "-y", "testing-conventions@latest"] + # How to launch a suite-executing gate, per language. Both run from the package -# root: python so `python3 -m pytest` resolves to that package's venv, typescript -# because only the npm CLI appends `--ts-mutation-adapter`, which the rule needs. -# Rust is absent -- it has no suite environment to enter, so it stays on `uvx`. +# root: python so `python3 -m pytest` resolves to that package's venv (which is +# why that one arm keeps the wheel, and stays subject to the PyPI lag above), +# typescript because only the npm CLI appends `--ts-mutation-adapter`, which the +# rule needs. Rust is absent -- it has no suite environment to enter, so it stays +# on `CLI`. LAUNCHERS = { "python": ["uv", "run", "--with", "testing-conventions", "testing-conventions"], - "typescript": ["npx", "-y", "testing-conventions"], + "typescript": CLI, } @@ -95,7 +109,7 @@ def invocation( home = package_root(root.source, exists) return Invocation( [ - *["uvx", "testing-conventions", *gate.command, *options], + *[*CLI, *gate.command, *options], *["--scope", root.source, *e2e_flags(e2e), home], ], ".", @@ -107,7 +121,7 @@ def invocation( options[-1] = os.path.relpath(root.config, cwd) source = os.path.relpath(root.source, cwd) return Invocation([*LAUNCHERS[language], *gate.command, *options, source], cwd) - return Invocation(["uvx", "testing-conventions", *gate.command, *options, root.source], ".") + return Invocation([*CLI, *gate.command, *options, root.source], ".") def default_runner(argv: Sequence[str], cwd: str) -> int: diff --git a/internals/checks/src/checks/preflight/gate_test.py b/internals/checks/src/checks/preflight/gate_test.py index 22f702fb4..20a119864 100644 --- a/internals/checks/src/checks/preflight/gate_test.py +++ b/internals/checks/src/checks/preflight/gate_test.py @@ -78,10 +78,10 @@ def it_returns_nothing_for_an_absent_table(): def describe_invocation(): - def it_runs_an_ordinary_gate_through_uvx_from_the_repo_root(): + def it_runs_an_ordinary_gate_through_the_npm_cli_from_the_repo_root(): assert call(PY, "python", "unit-lint") == Invocation( [ - *["uvx", "testing-conventions", "unit", "lint"], + *["npx", "-y", "testing-conventions@latest", "unit", "lint"], *["--language", "python"], *["--config", "testing-conventions.toml", "packages/python/dirsql"], ], @@ -89,10 +89,10 @@ def it_runs_an_ordinary_gate_through_uvx_from_the_repo_root(): ) def it_takes_the_ordinary_path_for_a_gate_name_sorting_below_e2e_verify(): - # Both paths start `uvx testing-conventions`, so `--scope` is what tells + # Both paths start with the same CLI prefix, so `--scope` is what tells # them apart -- an `<=` here would route colocated-test into e2e's branch. assert call(PY, "python", "colocated-test").argv == [ - *["uvx", "testing-conventions", "unit", "colocated-test"], + *["npx", "-y", "testing-conventions@latest", "unit", "colocated-test"], *["--language", "python", "--base", "origin/main"], *["--config", "testing-conventions.toml", "packages/python/dirsql"], ] @@ -117,7 +117,7 @@ def it_omits_config_for_a_root_that_declares_none(): def it_targets_the_package_root_for_e2e_verify_scoped_to_the_source(): assert call(PY, "python", "e2e-verify", {"extra_scope": ["packages/rust/src"]}) == Invocation( [ - *["uvx", "testing-conventions", "e2e", "verify"], + *["npx", "-y", "testing-conventions@latest", "e2e", "verify"], *["--base", "origin/main"], *["--scope", "packages/python/dirsql"], *["--extra-scope", "packages/rust/src", "packages/python"], @@ -154,10 +154,10 @@ def it_runs_python_unit_coverage_through_that_venv_too(): def it_runs_a_typescript_suite_gate_through_npx_from_the_package_root(): root = Root(job="ts", source="packages/ts/src", languages=["typescript"], gates=["mutation"]) mutation = call(root, "typescript", "mutation") - assert mutation.argv[:3] == ["npx", "-y", "testing-conventions"] + assert mutation.argv[:3] == ["npx", "-y", "testing-conventions@latest"] assert (mutation.cwd, mutation.argv[-1]) == (".", "packages/ts/src") - def it_keeps_a_rust_mutation_gate_on_uvx(): + def it_keeps_a_rust_mutation_gate_on_the_default_cli(): assert call(RUST, "rust", "mutation").cwd == "." @@ -228,7 +228,7 @@ def it_runs_the_drift_guards_first_then_every_pair(): assert drive(runner=lambda argv, cwd: calls.append((argv, cwd)) or 0) == 0 assert [argv[:2] for argv, _cwd in calls] == [ *[["uv", "sync"], ["uv", "run"]], - *[["uvx", "testing-conventions"], ["uv", "run"]], + *[["npx", "-y"], ["uv", "run"]], ] assert [cwd for _argv, cwd in calls] == [".", ".", ".", "packages/python"] @@ -283,7 +283,7 @@ def it_echoes_the_argv_it_is_about_to_run(): lines = [] drive(only=["unit-lint"], echo=lines.append) assert lines[0] == ( - "==> python-sdk [python] unit-lint: uvx testing-conventions unit lint " + "==> python-sdk [python] unit-lint: npx -y testing-conventions@latest unit lint " "--language python packages/python/dirsql" ) diff --git a/internals/checks/src/checks/preflight/matrix.py b/internals/checks/src/checks/preflight/matrix.py index a26aaa656..f311da3da 100644 --- a/internals/checks/src/checks/preflight/matrix.py +++ b/internals/checks/src/checks/preflight/matrix.py @@ -29,6 +29,7 @@ # omits `gates:` gets all of them. ROOT_GATES = [ "colocated-test", + "one-function-per-file", "unit-lint", "integration-lint", "unit-coverage", @@ -58,7 +59,7 @@ class Gate: config: bool = True base: bool = False # Executes the package's test suite, so it needs that package's own venv on - # `python3`, not the throwaway one `uvx` supplies. + # `python3`, not the throwaway one a standalone launcher supplies. runs_suite: bool = False # Takes the root of a BUILT artifact, not a source dir. CI builds it from # the manifest first; preflight has no equivalent and reports these skipped. @@ -67,6 +68,7 @@ class Gate: GATES = { "colocated-test": Gate(("unit", "colocated-test"), base=True), + "one-function-per-file": Gate(("unit", "one-function-per-file")), "unit-lint": Gate(("unit", "lint")), "integration-lint": Gate(("integration", "lint")), "unit-coverage": Gate(("unit", "coverage"), base=True, runs_suite=True), diff --git a/internals/checks/src/checks/preflight/matrix_test.py b/internals/checks/src/checks/preflight/matrix_test.py index 2c2250c8b..843398f0c 100644 --- a/internals/checks/src/checks/preflight/matrix_test.py +++ b/internals/checks/src/checks/preflight/matrix_test.py @@ -90,6 +90,7 @@ def it_records_the_options_and_needs_of_every_gate(): } assert shape == { "colocated-test": (True, True, True, False, False), + "one-function-per-file": (True, True, False, False, False), "unit-lint": (True, True, False, False, False), "integration-lint": (True, True, False, False, False), "unit-coverage": (True, True, True, True, False), @@ -101,6 +102,7 @@ def it_records_the_options_and_needs_of_every_gate(): def it_maps_each_gate_to_its_cli_subcommand(): assert {name: g.command for name, g in GATES.items()} == { "colocated-test": ("unit", "colocated-test"), + "one-function-per-file": ("unit", "one-function-per-file"), "unit-lint": ("unit", "lint"), "integration-lint": ("integration", "lint"), "unit-coverage": ("unit", "coverage"), From 829b30ca864c981b46e544e95f0f9f1624c4a769 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:56:13 -0400 Subject: [PATCH 3/8] test: assert every explicit-gates lane derives one-function-per-file `gates:` is an allowlist, so internals-checks, internals-distcheck and plugins-embeddings each skip the gate until their array names it -- three lanes CI silently holds to a rule nothing local runs. skip-changelog: internals tooling test, no packaged behavior --- internals/checks/tests/integration/preflight_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internals/checks/tests/integration/preflight_test.py b/internals/checks/tests/integration/preflight_test.py index fee44bf79..ed93385aa 100644 --- a/internals/checks/tests/integration/preflight_test.py +++ b/internals/checks/tests/integration/preflight_test.py @@ -85,6 +85,15 @@ def it_derives_the_one_function_per_file_gate_for_a_lane_naming_no_gates(repo_ro assert "==> python-sdk [python] one-function-per-file" in output assert "==> typescript-sdk [typescript] one-function-per-file" in output + def it_derives_the_one_function_per_file_gate_for_every_lane_naming_it(repo_root): + # An explicit `gates` array is an allowlist: a lane that omits the gate + # does not run it, however the reusable workflow defaults. + output = dry_run().output + + assert "==> internals-checks [python] one-function-per-file" in output + assert "==> internals-distcheck [python] one-function-per-file" in output + assert "==> plugins-embeddings [python] one-function-per-file" in output + def it_names_a_missing_workflow_and_the_fix_instead_of_raising(repo_root): result = dry_run("--conventions", ".github/workflows/conventions.yml") From 9e91dc78f8ea24be2501f165a77fce98f40710ab Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:57:48 -0400 Subject: [PATCH 4/8] ci: name one-function-per-file in every explicit gates array internals-checks, internals-distcheck and plugins-embeddings each pass an explicit `gates:` allowlist, so the reusable workflow's default set does not reach them and the gate was not running on those three lanes. skip-changelog: CI config, no packaged behavior --- .github/workflows/internals-checks-ci.yml | 2 +- .github/workflows/internals-distcheck-ci.yml | 2 +- .github/workflows/plugin-dirsql-embedding-ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/internals-checks-ci.yml b/.github/workflows/internals-checks-ci.yml index d3ed8bc0e..de0691c9b 100644 --- a/.github/workflows/internals-checks-ci.yml +++ b/.github/workflows/internals-checks-ci.yml @@ -31,7 +31,7 @@ jobs: source: internals/checks/src config: internals/checks/testing-conventions.toml base: ${{ github.event.pull_request.base.sha || 'origin/main' }} - gates: '["colocated-test", "unit-lint", "integration-lint", "unit-coverage", "mutation", "e2e-verify"]' + gates: '["colocated-test", "one-function-per-file", "unit-lint", "integration-lint", "unit-coverage", "mutation", "e2e-verify"]' internals-checks-integration: # The conventions job runs this package's colocated unit tier and checks its diff --git a/.github/workflows/internals-distcheck-ci.yml b/.github/workflows/internals-distcheck-ci.yml index db64e3de5..558026f4b 100644 --- a/.github/workflows/internals-distcheck-ci.yml +++ b/.github/workflows/internals-distcheck-ci.yml @@ -29,4 +29,4 @@ jobs: languages: '["python"]' source: internals/distcheck/src base: ${{ github.event.pull_request.base.sha || 'origin/main' }} - gates: '["colocated-test", "unit-lint", "integration-lint", "unit-coverage", "mutation"]' + gates: '["colocated-test", "one-function-per-file", "unit-lint", "integration-lint", "unit-coverage", "mutation"]' diff --git a/.github/workflows/plugin-dirsql-embedding-ci.yml b/.github/workflows/plugin-dirsql-embedding-ci.yml index 64fa7efb5..e96140f8a 100644 --- a/.github/workflows/plugin-dirsql-embedding-ci.yml +++ b/.github/workflows/plugin-dirsql-embedding-ci.yml @@ -38,7 +38,7 @@ jobs: source: plugins/dirsql-plugin-embeddings/src config: plugins/dirsql-plugin-embeddings/testing-conventions.toml base: ${{ github.event.pull_request.base.sha || 'origin/main' }} - gates: '["colocated-test", "unit-lint", "integration-lint", "unit-coverage", "mutation", "e2e-verify"]' + gates: '["colocated-test", "one-function-per-file", "unit-lint", "integration-lint", "unit-coverage", "mutation", "e2e-verify"]' embeddings-integration: # The conventions job runs the plugin's colocated unit tier and checks its From f113721228796244d8b6491bfa703aaa2f21dac3 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:58:05 -0400 Subject: [PATCH 5/8] e2e attestation for claude/1018-preflight-one-function-gate --- .../claude-1018-preflight-one-function-gate.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 internals/checks/e2e-attestations/claude-1018-preflight-one-function-gate.json diff --git a/internals/checks/e2e-attestations/claude-1018-preflight-one-function-gate.json b/internals/checks/e2e-attestations/claude-1018-preflight-one-function-gate.json new file mode 100644 index 000000000..1cbf46082 --- /dev/null +++ b/internals/checks/e2e-attestations/claude-1018-preflight-one-function-gate.json @@ -0,0 +1,7 @@ +{ + "command": "uv run python -m pytest tests/e2e -x -q", + "ran_at": 1788368285, + "exit_code": 0, + "commit": "9e91dc78f8ea24be2501f165a77fce98f40710ab", + "branch": "claude/1018-preflight-one-function-gate" +} From 19c23b43465c567a5fa7162cb517de4d577e3855 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:00:19 -0400 Subject: [PATCH 6/8] test: expect the python launcher to run the npm CLI under uv run The wheel `uv run --with` fetches lags npm, and it rejects a config key the newer release added -- an unknown key fails the whole config, so the python coverage and mutation arms red locally on a config CI accepts. Running the npm CLI under `uv run` keeps the package venv's interpreter for the suite while resolving the same release CI does. skip-changelog: internals tooling test, no packaged behavior --- internals/checks/src/checks/preflight/gate_test.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internals/checks/src/checks/preflight/gate_test.py b/internals/checks/src/checks/preflight/gate_test.py index 20a119864..722c19a19 100644 --- a/internals/checks/src/checks/preflight/gate_test.py +++ b/internals/checks/src/checks/preflight/gate_test.py @@ -131,9 +131,7 @@ def it_omits_language_for_e2e_verify_which_does_not_accept_it(): def it_runs_python_mutation_through_the_packages_own_venv(): mutation = call(PY, "python", "mutation") assert mutation.cwd == "packages/python" - assert mutation.argv[:5] == [ - *["uv", "run", "--with", "testing-conventions", "testing-conventions"] - ] + assert mutation.argv[:5] == ["uv", "run", "npx", "-y", "testing-conventions@latest"] def it_rewrites_the_config_and_source_paths_relative_to_that_cwd(): assert call(PY, "python", "mutation").argv[-3:] == [ @@ -291,8 +289,8 @@ def it_runs_only_the_gates_named_by_the_filter(): lines = [] drive(only=["mutation"], runner=lambda _argv, _cwd: 0, echo=lines.append) assert [line for line in lines if line.startswith("==>")] == [ - "==> python-sdk [python] mutation: uv run --with testing-conventions " - "testing-conventions unit mutation --language python --base origin/main dirsql" + "==> python-sdk [python] mutation: uv run npx -y testing-conventions@latest " + "unit mutation --language python --base origin/main dirsql" ] def it_prints_every_pair_without_running_any_when_dry_run(): From f032f0fa05bf1ba089f1a71197a4bca124c2ad36 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:02:17 -0400 Subject: [PATCH 7/8] fix: resolve the preflight python CLI from npm, not the PyPI wheel `uv run --with testing-conventions` resolved both the CLI and the mutation adapter from the wheel, which trails the npm release. A config key the newer release added is an unknown key to the older one, and an unknown key fails the whole config -- so `unit coverage` and `unit mutation` red locally against a config CI accepts, on three python lanes at once. Split the two roles. The npm tag resolves the CLI, matching CI; `--with` stays only to put `testing_conventions` on the interpreter the gate hands to `python3 -m`, the sole form the mutation adapter ships in. skip-changelog: internals tooling, no packaged behavior --- internals/checks/src/checks/preflight/gate.py | 38 +++++++++---------- .../checks/src/checks/preflight/gate_test.py | 10 +++-- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/internals/checks/src/checks/preflight/gate.py b/internals/checks/src/checks/preflight/gate.py index ad93591ad..5b9e12c47 100644 --- a/internals/checks/src/checks/preflight/gate.py +++ b/internals/checks/src/checks/preflight/gate.py @@ -12,10 +12,10 @@ Three pairs cannot be run the naive way, and each is encoded rather than skipped: - * python `mutation` / `unit-coverage` -- a throwaway environment supplies its - own interpreter, so the suite's `python3 -m pytest` resolves to one with no - pytest (#706 saw it as a cosmic-ray baseline failure naming no cause). Both - run via the package's own venv instead. + * python `mutation` / `unit-coverage` -- these execute the suite, whose + `python3 -m pytest` must resolve to the package's own venv rather than an + ambient interpreter with no pytest (#706 saw it as a cosmic-ray baseline + failure naming no cause). Both run under `uv run` from the package root. * rust `colocated-test` -- the co-change variant rides only the python/typescript language set (rust units are inline, so no sibling can go stale), and the CLI rejects `--base` for it. @@ -36,26 +36,24 @@ MANIFESTS = ("pyproject.toml", "package.json", "Cargo.toml") -# The CLI, resolved the way CI resolves it: npm, newest release. Two distinct -# drifts made the local run enforce a different ruleset than CI, and `@latest` -# closes both. The PyPI wheel `uvx` fetched is a laggier distribution of the same -# tool (0.0.104 against npm's 0.0.111 when this was written), and it has no -# `one-function-per-file` subcommand at all. A BARE `npx testing-conventions` is -# worse: npm resolves an unversioned name engine-aware, `engines.node` rose to -# `>=24` at 0.0.87, and a launcher shim running an older node therefore silently -# picks 0.0.86 -- dozens of releases of rule changes behind, and exiting 0 with -# no version banner to say so. CI dodges that by provisioning node 24 in every -# job; naming the tag dodges it wherever the local node lands. +# The CLI, resolved the way CI resolves it. Two silent drifts made the local run +# enforce a different ruleset, and naming the tag closes both: the PyPI wheel +# `uvx` fetches lags npm by several releases, and a BARE `npx testing-conventions` +# resolves engine-aware, so a shim on node older than `engines.node` picks the +# last release below that floor -- and exits 0 with no banner to say so. CLI = ["npx", "-y", "testing-conventions@latest"] # How to launch a suite-executing gate, per language. Both run from the package -# root: python so `python3 -m pytest` resolves to that package's venv (which is -# why that one arm keeps the wheel, and stays subject to the PyPI lag above), -# typescript because only the npm CLI appends `--ts-mutation-adapter`, which the -# rule needs. Rust is absent -- it has no suite environment to enter, so it stays -# on `CLI`. +# root: python so the suite's `python3 -m pytest` resolves to that package's +# venv, typescript because only the npm CLI appends `--ts-mutation-adapter`, +# which the rule needs. Rust is absent -- it has no suite environment to enter. +# +# The python arm needs BOTH distributions. `--with` is not how the CLI is +# resolved; it puts the wheel's `testing_conventions` package on the interpreter +# the gate hands to `python3 -m`, which is the only form the mutation adapter +# ships in. LAUNCHERS = { - "python": ["uv", "run", "--with", "testing-conventions", "testing-conventions"], + "python": ["uv", "run", "--with", "testing-conventions", *CLI], "typescript": CLI, } diff --git a/internals/checks/src/checks/preflight/gate_test.py b/internals/checks/src/checks/preflight/gate_test.py index 722c19a19..81b95323e 100644 --- a/internals/checks/src/checks/preflight/gate_test.py +++ b/internals/checks/src/checks/preflight/gate_test.py @@ -131,7 +131,10 @@ def it_omits_language_for_e2e_verify_which_does_not_accept_it(): def it_runs_python_mutation_through_the_packages_own_venv(): mutation = call(PY, "python", "mutation") assert mutation.cwd == "packages/python" - assert mutation.argv[:5] == ["uv", "run", "npx", "-y", "testing-conventions@latest"] + assert mutation.argv[:7] == [ + *["uv", "run", "--with", "testing-conventions"], + *["npx", "-y", "testing-conventions@latest"], + ] def it_rewrites_the_config_and_source_paths_relative_to_that_cwd(): assert call(PY, "python", "mutation").argv[-3:] == [ @@ -289,8 +292,9 @@ def it_runs_only_the_gates_named_by_the_filter(): lines = [] drive(only=["mutation"], runner=lambda _argv, _cwd: 0, echo=lines.append) assert [line for line in lines if line.startswith("==>")] == [ - "==> python-sdk [python] mutation: uv run npx -y testing-conventions@latest " - "unit mutation --language python --base origin/main dirsql" + "==> python-sdk [python] mutation: uv run --with testing-conventions " + "npx -y testing-conventions@latest unit mutation --language python " + "--base origin/main dirsql" ] def it_prints_every_pair_without_running_any_when_dry_run(): From 13aaf51d7f2edd105c8e6d302f72959d97981e2d Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:02:26 -0400 Subject: [PATCH 8/8] e2e attestation for claude/1018-preflight-one-function-gate --- .../claude-1018-preflight-one-function-gate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internals/checks/e2e-attestations/claude-1018-preflight-one-function-gate.json b/internals/checks/e2e-attestations/claude-1018-preflight-one-function-gate.json index 1cbf46082..041c21b43 100644 --- a/internals/checks/e2e-attestations/claude-1018-preflight-one-function-gate.json +++ b/internals/checks/e2e-attestations/claude-1018-preflight-one-function-gate.json @@ -1,7 +1,7 @@ { "command": "uv run python -m pytest tests/e2e -x -q", - "ran_at": 1788368285, + "ran_at": 1788368546, "exit_code": 0, - "commit": "9e91dc78f8ea24be2501f165a77fce98f40710ab", + "commit": "f032f0fa05bf1ba089f1a71197a4bca124c2ad36", "branch": "claude/1018-preflight-one-function-gate" }