Skip to content

ci: scope each binding's e2e freshness to its own package config - #971

Merged
thekevinscott merged 2 commits into
mainfrom
claude/issue-933-2y4u9r
Aug 20, 2026
Merged

thekevinscott merged 2 commits into
mainfrom
claude/issue-933-2y4u9r

Conversation

@thekevinbot

Copy link
Copy Markdown
Collaborator

Fixes #933

The decision: Option A, and Option B is dead

Option B's premise — the binding cannot change e2e behavior without the core also changing — is false. Since #721 each launcher calls the core's run_cli in-process through its binding, and each binding's own run_cli does real work that runs in every e2e case:

A change to either alters what the e2e suites exercise with no core change at all. So the scope widens.

Why per-package config files rather than one root list

[e2e] is a single global table that upstream's detect.py applies to every caller reading a given config, so "the core + my binding" is not expressible in one shared file. Declaring both bindings at the repo root would:

  1. make a packages/ts/napi change demand a python re-attest (the issue explicitly rules this out — a napi change has no bearing on the python suite), and
  2. require packages/ts/napi/** in the python lane's path triggers to enforce even that — otherwise the demand is latent and fires only on PRs that happen to touch both. That is exactly the cross-lane fan-in Epic — CI: run only the checks relevant to the changed paths #834 removed.

So packages/python and packages/ts each get their own testing-conventions.toml, passed explicitly as that lane's config:. This is the pattern internals/checks established in #550 and the embeddings plugin follows — there to drop a phantom root, here to add a real one.

A dedicated e2e-verify-only job per binding was considered and rejected: it cannot work for TypeScript. packages/ts/napi carries its own Cargo.toml, so derive_package_root resolves to packages/ts/napi, which holds no receipts — verified, the gate reports e2e_attestation false and skips itself.

Changes

File Change
packages/python/testing-conventions.toml new — extra_scope = ["packages/rust/src", "packages/python/src"] + the same [python.coverage] floor
packages/ts/testing-conventions.toml new — extra_scope = ["packages/rust/src", "packages/ts/napi"] + the same [typescript.coverage] floors
.github/workflows/dirsql-{python,typescript}-ci.yml both reusable-workflow jobs per lane pass config: packages/<pkg>/testing-conventions.toml; the root testing-conventions.toml path trigger is dropped (no job in either lane reads it now)
testing-conventions.toml the [e2e] comment's stale spawn-model premise corrected (#721); the block documented as the default for a config-less caller; header refreshed (it still named the pre-#834 conventions.yml / python-test.yml)
AGENTS.md, agents/reference/e2e-attestation.md, agents/reference/testing-gates.md, agents/environments/remote.md the per-package rule; the local python-mutation --config now points at the package file, matching CI

The config: is passed explicitly on purpose: derive_config treats the literal default string testing-conventions.toml as a request for package-root discovery, so these files would have been picked up silently. Explicit paths make the YAML state what CI reads and keep preflight's read_e2e(root.config) in agreement with it.

Deliberate boundary, documented in both new files: packages/python/Cargo.toml (the PyO3 crate manifest) is not scoped — extra_scope takes directories, and the only directory holding it is the package root, where scoping would re-stale on the docs/tests/pyproject edits the --scope narrowing exists to ignore. The napi entry is the whole crate dir, so its manifest and build.rs are covered.

Verification

Upstream read at the pinned v0 sha 79fd4b6183dfd2ff276a8d55b973d7a182fc75e4, per the "read upstream at a pinned sha, trust MIGRATIONS.md over probing" rule.

RED → GREEN, from real uvx testing-conventions e2e verify runs over synthetic single-root diffs (--base main, no receipt in the diff):

diff python lane today python lane fixed ts lane today ts lane fixed
packages/python/src only exit 0 — blind exit 1 ✅ exit 0 exit 0 ✅ no cross-stale
packages/ts/napi only exit 0 exit 0 ✅ no cross-stale exit 0 — blind exit 1 ✅

Reproduce:

git checkout -b tmp/proof main
printf '\n// scratch\n' >> packages/ts/napi/src/lib.rs && git commit -qam proof
uvx testing-conventions e2e verify packages/ts --scope packages/ts/src --base main \
  --extra-scope packages/rust/src                                  # exit 0 — the bug
uvx testing-conventions e2e verify packages/ts --scope packages/ts/src --base main \
  --extra-scope packages/rust/src --extra-scope packages/ts/napi   # exit 1 — fixed

Manual exercise of the wiring — upstream's own detect.py run against this checkout, which is what renders the flags the e2e-verify step appends:

SCAN_PATH=packages/python/dirsql CONFIG=packages/python/testing-conventions.toml
  -> package_root packages/python, e2e_attestation true,
     e2e_extra_scope --extra-scope packages/rust/src --extra-scope packages/python/src

SCAN_PATH=packages/ts/src CONFIG=packages/ts/testing-conventions.toml
  -> package_root packages/ts, e2e_attestation true,
     e2e_extra_scope --extra-scope packages/rust/src --extra-scope packages/ts/napi

Also green locally: dirsql-checks preflight --conventions .github/workflows/dirsql-{python,typescript}-ci.yml --gate colocated-test --gate unit-lint --gate integration-lint — 0 failing pairs on both lanes, including the two rust binding-crate jobs now reading the package config ([rust].features is consumed only by coverage/mutation, neither of which those jobs run). changelog-gate passes on the skip-changelog line. Both lanes' e2e verify over this branch's diff exit 0 — this PR touches no scoped source, so it needs no re-attestation.

E2E Verification

N/A - CI gate config + agent docs only (no packages/*/src, no SDK runtime code)

  • Ran e2e suites locally for every affected SDK — N/A
  • Python SDK e2e: N/A
  • TypeScript SDK e2e: N/A
  • Rust core e2e (if applicable): N/A
  • packages/python/e2e-attestation.json refreshed if packages/python changed — not required: the change is outside --scope packages/python/dirsql and outside both extra scopes (verified, exit 0)
  • packages/ts/e2e-attestation.json refreshed if packages/ts changed — not required, same (verified, exit 0)
  • Command(s) run: uvx testing-conventions e2e verify packages/{python,ts} --scope <src> --base main <extra-scope flags>
  • Result summary: exit 0 on both lanes for this branch; exit 1 on both for the synthetic binding-only diffs above.

Changelog / Migrations

  • Changelog fragment added under packages/<pkg>/changelog.d/ for each changed package — not applicable: skip-changelog: line on the commit, reason "CI gate configuration and agent docs only; no runtime, API, or packaged-artifact change in either SDK." Verified accepted by dirsql-checks changelog-gate locally.
  • Migration fragment added under packages/<pkg>/migrations.d/ — not required: no public API or runtime behavior change.

PARITY.md untouched — no SDK public API is affected.

Noticed, not fixed here

just preflight is broken on main: preflight/cli.py still defaults --conventions to .github/workflows/conventions.yml, which the #834 split removed, so it only runs when given an explicit --conventions <file> (one lane at a time — that is how it was run above). Worth its own issue rather than widening this PR.


Generated by Claude Code

claude added 2 commits August 20, 2026 17:42
`[e2e].extra_scope` covered the shared Rust core but neither binding
crate, so `packages/python/src` (PyO3) and `packages/ts/napi` were
outside every package's e2e scope: a binding-only diff left that SDK's
attestation looking fresh.

The `extra_scope` comment justified this with the spawn model -- the
packages ship a `dirsql` binary their e2e suites spawn, so only the core
matters. Since #721 that is false: each launcher calls the core's
`run_cli` in-process through its binding, and each binding's `run_cli`
does argv framing plus GIL detach (PyO3) / exit-code handling (napi).
That code runs in every e2e case and can change what the suites exercise
with no core change at all.

`[e2e]` is one global table applied to every caller reading a given
config, so "core + my own binding" is only expressible per package.
`packages/python` and `packages/ts` therefore get their own
`testing-conventions.toml`, passed explicitly as each lane's `config:` --
the pattern `internals/checks` established in #550 to drop a phantom
root, used here to add a real one. Declaring both bindings at the repo
root instead would make a napi change demand a python re-attest, and
would need `packages/ts/napi/**` in the python lane's path triggers to
enforce even that.

The root `[e2e]` block stays as the default for a caller with no config
of its own; its stale spawn-model wording is corrected.

Proven with upstream's detect.py at the pinned v0 sha
(79fd4b6183dfd2ff276a8d55b973d7a182fc75e4) and real `e2e verify` runs
over synthetic single-root diffs -- see the PR body's truth table.

Fixes #933

skip-changelog: CI gate configuration and agent docs only; no runtime,
API, or packaged-artifact change in either SDK.
The two new package configs carried 34 comment lines each for two lines
of config -- twice the most verbose precedent (internals/checks, 16) --
and most of it restated the PR body and agents/reference/e2e-attestation.md
rather than a constraint a reader could not see. The #721 narrative, the
Cargo.toml boundary note and the cross-lane fan-in argument live in the
reference doc; what stays is the part that keeps someone from folding
`[e2e]` back into the root file.

Rendered flags and gates unchanged: detect.py still emits the same
--extra-scope pair for both lanes, preflight still passes.

skip-changelog: comment-only edit to CI gate configuration.
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.

e2e attestation scope: binding-crate changes don't stale their own SDK's attestation

3 participants