You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dependabot's weekly pip group proposes a setuptools bump against packaging/setuptools-compatibility.txt, and every such bump is wrong by construction.
That file exists only to hold setuptools below the release that removed pkg_resources — its own header states the bound and the reason:
tMAVEN's maven_class.__init__ does import pkg_resources, which it never declares.
setuptools DEPRECATED pkg_resources by 80.9.0 (still shipped, warns on import), and
REMOVED it in 82.0.0 — while sidecar/conda-lock.yml resolves 82.0.1 on every platform,
i.e. past the removal. Without this wheel an installed app builds its sidecar env fine
and then fails at the FIRST idealization (#212).
The pin is therefore a deliberate downgrade held against the ecosystem, not a stale
dependency. Letting Dependabot advance it defeats the file's entire purpose.
#444 (setuptools 80.9.0 → 83.0.0) is the first instance and has been closed. It touched
that one file and nothing else, which settles the mechanism: the pip entry at directory: "/" does reach this file.
What is actually unguarded, and what is not
An earlier version of this issue said, in one compound sentence, that "nothing in the repo
checks the version against the <81 bound, so a future bump that happens to carry a single
hash would go green and ship a sidecar that dies at the first idealization", and that #444
was stopped by luck. Both halves need work, and in opposite directions — which is why
the sentence is replaced rather than trimmed.
The inference is false and is deleted. tests/test_setup_sidecar.py::test_the_requirement_is_pinned_and_hashed asserts the exact
version 80.9.0 (:96) and the exact sha256 digest (:99), so any bump fails the
required test matrix on all three OSes whether it carries one hash or two. #444
additionally tripped the parser — scripts/setup_sidecar.py::setuptools_requirement uses re.fullmatch against exactly one setuptools==<version> --hash=sha256:<64 hex>
(:138-139), and #444's diff carried two --hash= lines — but the version and digest
assertions would have caught a single-hash bump on their own. No bump can go green today,
and #444 was not luck.
The bound claim is over-broad rather than false, and the correction runs the other way:
the <81 bound is asserted — just nowhere a pull request reaches. .github/workflows/packaging.yml:241 (Unix) and :293 (Windows) run, against the installed sidecar env:
import setuptools, pkg_resources; assert tuple(int(p) for p in setuptools.__version__.split('.')[:1]) < (81,), setuptools.__version__
and tests/test_marker_contract.py:389 pins that assertion's presence by requiring the
literal import setuptools, pkg_resources in the workflow text, with the message
"packaging.yml install-smoke must assert the bundled setuptools<81 pin was applied to the
sidecar env, so a regression names its cause (issue #212)". But packaging.yml is advisory and non-required by construction — its own header says it is triggered "ONLY by workflow_dispatch (never pull_request/push/merge_group)" — so it never reports on a
Dependabot pull request.
So the accurate statement is narrower and still load-bearing: no assertion that runs on a
pull request derives from the bound. On #444's diff — that one file — what stops it in
required CI is the parser plus the exact-version and exact-digest assertions, and none of
their failure messages names pkg_resources, 82.0.0, or the bound.
Two real defects survive that correction.
Recurring dead-end pull requests. The proposal repeats on the weekly schedule.
Each one costs a three-OS CI run and a maintainer close, forever, for a change that can
never be correct. setuptools appears in no other pip manifest in the repository —
not pyproject.toml, not requirements-docs.txt — so an ignore entry for it
suppresses nothing else. Note separately that the pip entry groups with patterns: ["*"], so a future weekly pull request can bundle the doomed setuptools bump
with legitimate dev-tool bumps and closing it discards both: a reason to stop the
proposal at the source rather than keep closing it.
The failure names the format, never the reason — and the check that does name the reason
is heavy, and reached only by a coincidence of paths. The two ways a
compatibility-file-only bump fails in required CI are SetupError: setuptools-compatibility.txt must be exactly one pinned, sha256-hashed setuptools requirement; got … (what build(deps): Bump setuptools from 80.9.0 to 83.0.0 in the pip group #444 hit, on its two hashes) and, for a single-hash bump, a bare assert '83.0.0' == '80.9.0'. Neither mentions pkg_resources, 82.0.0, or the <81
bound. test_the_removal_trigger_is_recorded (tests/test_setup_sidecar.py:205-208)
checks that REMOVAL TRIGGER, pkg_resources and 82.0.0 are present in the file;
nothing puts them in front of whoever is looking at red CI. So the obvious way to make the
suite green is to update the assertions to match the new pin.
That follow-on edit does not ship — but it is caught late and by accident. It touches tests/test_setup_sidecar.py, which matches the relevance pattern tests/test_.*sidecar.*\.py at .github/workflows/sidecar.yml:134, so sidecar / parity
— triggered on pull_request (:47) and present in the main-baseline ruleset's required
status checks — stops answering "not applicable", provisions the isolated sidecar env, and
runs python scripts/setup_sidecar.py --python … --with-pytest (:171-172). That installs
the bumped requirement and then probes maven_class, whose undeclared import pkg_resources is the entire reason for the pin; run_probe raises SetupError and main
returns 1 (scripts/setup_sidecar.py:269-272, :414-416). So the reason is named in
required CI — by a heavy live job (timeout-minutes: 30) whose env and probe steps run
only because the file being relaxed happens to match a filter written for the idealization
tests, and which on the Dependabot pull request itself takes the other branch and reports
"not applicable (pass)" in seconds (:139). Fix 2 replaces it with a fast local failure
that says why.
Two independent fixes, both wanted:
Stop Dependabot proposing it — an ignore entry for setuptools in the pip block of .github/dependabot.yml, with a comment pointing at the compatibility file and its
removal trigger. The @dependabot ignore <dependency name> pull-request comment — offered
in build(deps): Bump setuptools from 80.9.0 to 83.0.0 in the pip group #444's own body, where it is described as closing the pull request and stopping
Dependabot creating any more for that dependency — would also work, but it stores the
condition on GitHub's side where the repository cannot see it; a config entry is the
auditable form.
Make the bound load-bearing in the pure test matrix that runs on every pull
request — assert in tests/test_setup_sidecar.py that the parsed major version is
below 81, with a message that names the pkg_resources removal, so the "update the
assertion to match" path fails fast and locally instead of only inside the live sidecar
job.
Note the config-validation hazard already recorded at the top of .github/dependabot.yml:
the file validates all-or-nothing, so a rejected block stops every ecosystem — including
the two github-actions entries that keep the SHA pins of the required checks current — and
does not fall back to the previous behaviour.
Acceptance criteria
Superseded by the grooming block below.
Scope and non-goals
In scope: the Dependabot ignore entry; the version-bound assertion; the pointer comment in
the compatibility file; and adding .github/dependabot.yml to test_no_consumer_restates_the_version_or_the_bound's file mapping.
Out of scope: changing the pinned setuptools version or its hash; touching sidecar/conda-lock.yml; anything about the tMAVEN vendored code itself; removing the
compatibility wheel (that is the separate removal trigger already documented in the file,
and it fires only when tMAVEN stops importing pkg_resources). Also out of scope: adding ignore entries for anything other than setuptools; any change to the github-actions
entries; and rewording the stale # FIVE consumers, not the three #218 names and not the four the first version of this test found comment above that mapping, or the compatibility
file header's parallel "a review found a fifth" sentence — both are pre-existing and
correcting them here would be scope breach.
Size estimate
S — up to 150 added lines. Raised from XS during grooming: two new tests written to this
repository's standard (each carries the rationale docstring the surrounding module uses),
plus the config entry with its explanatory comments and the compatibility-file header edit,
does not credibly fit in 50.
Risk level
high — the lane, and a Greptile credit is worth spending if the seat has budget
Does this need an architecture decision record?
no
Dependencies / blockers
none
Related work / file overlap
#444 (closed, the instance that prompted this), #212 (the idealization failure the pin
prevents), #217 (extracts the packaging.yml install-smoke into one checked-in script that release.yml also calls; release.yml triggers only on a v* tag push and dispatch
(.github/workflows/release.yml:22-26), so the pull-request picture above is unchanged
either way — independent of this issue and not a dependency of it), #452 (the setup-micromamba v3.2.1 failures; it names this issue as a parallel case, and pinning the
action back — with the github-actionsignore entries that would require in both
entries — is one of its stated non-goals, so it does not edit .github/dependabot.yml and
there is no file overlap). Files: .github/dependabot.yml, tests/test_setup_sidecar.py, packaging/setuptools-compatibility.txt.
Target area
CI / repository governance
Milestone
none / cross-cutting
Required safety attestations
I searched existing issues and this is not a duplicate.
I am not reporting a vulnerability publicly; security reports use the private advisory flow.
I included no secrets or private, raw, unlicensed, user, or lab data.
Grooming status — 2026-08-13
Status: unblocked.
Open dependencies: none.
Autonomy: agent-can-do-alone.
Chosen design — the ignore shape: ignore setuptoolsentirely in the pip entry —
no versions: and no update-types: narrowing. The pin is to one artifact by digest, so
any automated edit to that line is wrong whether or not it lands inside the bound: 80.9.0
is already the newest release below 81, and even a hypothetical in-bound 80.9.1 would need
a fresh pip hash and a deliberate two-value edit. Verified safe because setuptools
appears in no other pip manifest in the repository.
Chosen design — where the 81 lives: as a literal in the new guard in tests/test_setup_sidecar.py. This has to be settled here rather than argued on a risk:high pull request, because packaging/setuptools-compatibility.txt:4-6 declares
itself "THE single source of truth … nothing may re-state the version or the bound
anywhere else", scripts/setup_sidecar.py:127-128 repeats it ("Parsed rather than
restated"), and test_no_consumer_restates_the_version_or_the_bound's docstring (:141)
names hard-coding <81 as "the third defect in build(packaging): pin and hash the bundled setuptools wheel, with one source of truth #218". It is nonetheless already the
repository's own practice: .github/workflows/packaging.yml:241 and :293 hold < (81,) as a literal on a non-comment line and pass that test, because the test
forbids the requirement spelling — its regex is setuptools\s*(==|<=|>=|<|>|~=)\s*[0-9] (tests/test_setup_sidecar.py:170) — and not a
numeric comparison. int(version.split(".")[0]) < 81 introduces no such spelling. The
assertion message spells the bound as prose ("below 81; setuptools 82.0.0 removed pkg_resources") rather than as setuptools<81 — not because that guard would catch it,
since its mapping (tests/test_setup_sidecar.py:152-157) does not include tests/test_setup_sidecar.py and a literal there is invisible to it today, but because of
the header's rule quoted above and because a message is quotable: a mapped consumer that
later pastes it would fail the guard for a reason nobody would look for here. The version number is still read from the file and never restated.
Chosen test home: the new tests go in tests/test_setup_sidecar.py, not tests/test_marker_contract.py. The latter already parses this config and holds the yaml
loader, but whether it counts as one of ADR-0064's frozen "agent test modules" is
unsettled — the phrase is enumerated nowhere — and this issue must not be the place that
decides it. test_setup_sidecar.py is unambiguously outside the agent layer and already
owns the compatibility-file contract; import yaml is already used by six other test
modules, so it adds no dependency.
Review risk: high. .github/dependabot.yml validates all-or-nothing, so a malformed
edit silently disarms every ecosystem rather than failing loudly — including the action-SHA
updates that feed the required checks.
Terminal condition: PR-ready handoff. The config-error banner can only be read after
the change is on the default branch, and the repository owner merges knowing that.
Acceptance criteria
.github/dependabot.yml's pip entry carries an ignore list holding exactly one
entry, dependency-name: setuptools, with no versions: and no update-types: key.
The explanation — why the pin is deliberate, and the removal trigger — is written as
YAML comments: #-leading lines are the only ones test_no_consumer_restates_the_version_or_the_bound skips (:176), so the literal setuptools<81 written on a data line would fail that test once this file is inside
its mapping. A bound written as YAML data — versions: ["<81"] — would slip past
that regex unnoticed, since the regex needs setuptools adjacent to a comparator on
the same line, which is a second reason the entry must carry no versions: key at all.
A new test in tests/test_setup_sidecar.py loads .github/dependabot.yml with yaml.safe_load and fails if the pip entry's ignore list does not name setuptools, or names it with a versions/update-types narrowing. Currently-failing demonstration, recorded in the PR: run the new test against the
tree before the config edit — it fails, because the pip entry has no ignore key
at all — then apply the config edit and it passes. Quote both runs.
tests/test_setup_sidecar.py asserts the parsed major version is below 81 — int(version.split(".")[0]) < 81 on the version setuptools_requirement() returns,
which adds no dependency — and the assertion message names the pkg_resources
removal in setuptools 82.0.0 and the bound as prose, per the second Chosen-design
clause above, rather than only the file format. Currently-failing demonstration,
recorded in the PR: temporarily change only the final requirement line of packaging/setuptools-compatibility.txt to setuptools==83.0.0 with a syntactically
valid 64-hex digest — leave the header intact, or test_the_removal_trigger_is_recorded
goes red for an unrelated reason — and relax test_the_requirement_is_pinned_and_hashed's exact-version (:96) and exact-digest
(:99) assertions to the new values, which is the "update the assertion to match the
bump" fix this criterion exists to stop. Then run pytest tests/test_setup_sidecar.py:
the new bound assertion must be the only red, and its message must contain pkg_resources. No other test in that module reads the concrete version — the two
parser tests write their own file into tmp_path and redirect the module constant with monkeypatch. Revert all three edits; git status clean before review is requested.
packaging/setuptools-compatibility.txt's comment block names .github/dependabot.yml
and contains the literal phrase not a consumer, on a line outside the
"THE CONSUMERS, named rather than counted" list — that list keeps its existing four
entries (:37-40) unchanged, so the new path cannot be read as a fifth file that reads
the pin. The same block records that the bound guard this issue adds holds 81 as a
literal inside an assertion, in the same form .github/workflows/packaging.yml:241 and :293 already use. It must not claim that is the only such literal in the tree: .github/workflows/sidecar-measure.yml:86 installs "setuptools<81" outright, on a
non-comment line, in a manual-dispatch workflow the guard does not scan.
.github/dependabot.yml is added to test_no_consumer_restates_the_version_or_the_bound's file mapping (whose function is
"may not restate the pin", not "reads the pin"), so the one file whose job is to
suppress automated edits to the pin is itself scanned for a copy of it — the versions: ["<81"] hazard in AC1 is exactly the edit that would put one there. Currently-failing demonstration with its negative control, both recorded in the PR:
add an uncommented setuptools<81 line to .github/dependabot.yml; with the mapping
entry present the test fails naming that file, and with the same line present but the
mapping entry removed the test passes — quote both runs, so the mapping addition is
evidenced as the thing that binds. Revert the injected line. The stale # FIVE consumers … comment above the mapping is left exactly as it is (see Scope and
non-goals): the entry is added because that file may not restate the pin, not as a
fifth consumer of it.
Config validity: the committed reach stops at the parse and the ignore shape above.
The config-error banner is not asserted and no criterion here pretends to — gh api repos/bioedca/tether/dependabot/config returns 404 (checked 2026-08-13), so
there is nothing for a test to read. Instead the PR body states in one sentence that the
banner under Insights → Dependency graph → Dependabot is to be read after merge, and the remedy
if it appears is to revert the entry, because the previous behaviour does not resume on
its own.
Existing coverage stays green, including the two modules that already read the files
being edited: pytest tests/test_setup_sidecar.py tests/test_marker_contract.py. test_marker_contract.py's Dependabot assertions read only the github-actions
entries (via _dependabot_github_actions_directories, :1028-1046), so an ignore
key on the pip entry is invisible to them.
Full local gates: pre-commit run --all-files and the lane's matrix line from AGENTS.md. No docs page and no schema change, so mkdocs build --strict and scripts/dump_schema.py --check are not implicated.
Ready condition: met; awaiting a fresh exact-scope marker.
Maintenance kind
CI
Work requested
Dependabot's weekly
pipgroup proposes asetuptoolsbump againstpackaging/setuptools-compatibility.txt, and every such bump is wrong by construction.That file exists only to hold setuptools below the release that removed
pkg_resources— its own header states the bound and the reason:The pin is therefore a deliberate downgrade held against the ecosystem, not a stale
dependency. Letting Dependabot advance it defeats the file's entire purpose.
#444 (
setuptools80.9.0 → 83.0.0) is the first instance and has been closed. It touchedthat one file and nothing else, which settles the mechanism: the
pipentry atdirectory: "/"does reach this file.What is actually unguarded, and what is not
An earlier version of this issue said, in one compound sentence, that "nothing in the repo
checks the version against the
<81bound, so a future bump that happens to carry a singlehash would go green and ship a sidecar that dies at the first idealization", and that #444
was stopped by luck. Both halves need work, and in opposite directions — which is why
the sentence is replaced rather than trimmed.
The inference is false and is deleted.
tests/test_setup_sidecar.py::test_the_requirement_is_pinned_and_hashedasserts the exactversion
80.9.0(:96) and the exact sha256 digest (:99), so any bump fails therequired
testmatrix on all three OSes whether it carries one hash or two. #444additionally tripped the parser —
scripts/setup_sidecar.py::setuptools_requirementusesre.fullmatchagainst exactly onesetuptools==<version> --hash=sha256:<64 hex>(
:138-139), and #444's diff carried two--hash=lines — but the version and digestassertions would have caught a single-hash bump on their own. No bump can go green today,
and #444 was not luck.
The bound claim is over-broad rather than false, and the correction runs the other way:
the
<81bound is asserted — just nowhere a pull request reaches..github/workflows/packaging.yml:241(Unix) and:293(Windows) run, against theinstalled sidecar env:
and
tests/test_marker_contract.py:389pins that assertion's presence by requiring theliteral
import setuptools, pkg_resourcesin the workflow text, with the message"packaging.yml install-smoke must assert the bundled
setuptools<81pin was applied to thesidecar env, so a regression names its cause (issue #212)". But
packaging.ymlisadvisory and non-required by construction — its own header says it is triggered "ONLY by
workflow_dispatch(never pull_request/push/merge_group)" — so it never reports on aDependabot pull request.
So the accurate statement is narrower and still load-bearing: no assertion that runs on a
pull request derives from the bound. On #444's diff — that one file — what stops it in
required CI is the parser plus the exact-version and exact-digest assertions, and none of
their failure messages names
pkg_resources,82.0.0, or the bound.Two real defects survive that correction.
Recurring dead-end pull requests. The proposal repeats on the weekly schedule.
Each one costs a three-OS CI run and a maintainer close, forever, for a change that can
never be correct.
setuptoolsappears in no other pip manifest in the repository —not
pyproject.toml, notrequirements-docs.txt— so anignoreentry for itsuppresses nothing else. Note separately that the
pipentry groups withpatterns: ["*"], so a future weekly pull request can bundle the doomed setuptools bumpwith legitimate dev-tool bumps and closing it discards both: a reason to stop the
proposal at the source rather than keep closing it.
The failure names the format, never the reason — and the check that does name the reason
is heavy, and reached only by a coincidence of paths. The two ways a
compatibility-file-only bump fails in required CI are
SetupError: setuptools-compatibility.txt must be exactly one pinned, sha256-hashed setuptools requirement; got …(what build(deps): Bump setuptools from 80.9.0 to 83.0.0 in the pip group #444 hit, on its two hashes) and, for a single-hash bump, a bareassert '83.0.0' == '80.9.0'. Neither mentionspkg_resources,82.0.0, or the<81bound.
test_the_removal_trigger_is_recorded(tests/test_setup_sidecar.py:205-208)checks that
REMOVAL TRIGGER,pkg_resourcesand82.0.0are present in the file;nothing puts them in front of whoever is looking at red CI. So the obvious way to make the
suite green is to update the assertions to match the new pin.
That follow-on edit does not ship — but it is caught late and by accident. It touches
tests/test_setup_sidecar.py, which matches the relevance patterntests/test_.*sidecar.*\.pyat.github/workflows/sidecar.yml:134, sosidecar / parity— triggered on
pull_request(:47) and present in themain-baselineruleset's requiredstatus checks — stops answering "not applicable", provisions the isolated sidecar env, and
runs
python scripts/setup_sidecar.py --python … --with-pytest(:171-172). That installsthe bumped requirement and then probes
maven_class, whose undeclaredimport pkg_resourcesis the entire reason for the pin;run_proberaisesSetupErrorandmainreturns
1(scripts/setup_sidecar.py:269-272,:414-416). So the reason is named inrequired CI — by a heavy live job (
timeout-minutes: 30) whose env and probe steps runonly because the file being relaxed happens to match a filter written for the idealization
tests, and which on the Dependabot pull request itself takes the other branch and reports
"not applicable (pass)" in seconds (
:139). Fix 2 replaces it with a fast local failurethat says why.
Two independent fixes, both wanted:
ignoreentry forsetuptoolsin thepipblock of.github/dependabot.yml, with a comment pointing at the compatibility file and itsremoval trigger. The
@dependabot ignore <dependency name>pull-request comment — offeredin build(deps): Bump setuptools from 80.9.0 to 83.0.0 in the pip group #444's own body, where it is described as closing the pull request and stopping
Dependabot creating any more for that dependency — would also work, but it stores the
condition on GitHub's side where the repository cannot see it; a config entry is the
auditable form.
testmatrix that runs on every pullrequest — assert in
tests/test_setup_sidecar.pythat the parsed major version isbelow 81, with a message that names the
pkg_resourcesremoval, so the "update theassertion to match" path fails fast and locally instead of only inside the live sidecar
job.
Note the config-validation hazard already recorded at the top of
.github/dependabot.yml:the file validates all-or-nothing, so a rejected block stops every ecosystem — including
the two
github-actionsentries that keep the SHA pins of the required checks current — anddoes not fall back to the previous behaviour.
Acceptance criteria
Superseded by the grooming block below.
Scope and non-goals
In scope: the Dependabot
ignoreentry; the version-bound assertion; the pointer comment inthe compatibility file; and adding
.github/dependabot.ymltotest_no_consumer_restates_the_version_or_the_bound's file mapping.Out of scope: changing the pinned setuptools version or its hash; touching
sidecar/conda-lock.yml; anything about the tMAVEN vendored code itself; removing thecompatibility wheel (that is the separate removal trigger already documented in the file,
and it fires only when tMAVEN stops importing
pkg_resources). Also out of scope: addingignoreentries for anything other thansetuptools; any change to thegithub-actionsentries; and rewording the stale
# FIVE consumers, not the three #218 names and not the four the first version of this test foundcomment above that mapping, or the compatibilityfile header's parallel "a review found a fifth" sentence — both are pre-existing and
correcting them here would be scope breach.
Size estimate
S — up to 150 added lines. Raised from XS during grooming: two new tests written to this
repository's standard (each carries the rationale docstring the surrounding module uses),
plus the config entry with its explanatory comments and the compatibility-file header edit,
does not credibly fit in 50.
Risk level
high — the lane, and a Greptile credit is worth spending if the seat has budget
Does this need an architecture decision record?
no
Dependencies / blockers
none
Related work / file overlap
#444 (closed, the instance that prompted this), #212 (the idealization failure the pin
prevents), #217 (extracts the
packaging.ymlinstall-smoke into one checked-in script thatrelease.ymlalso calls;release.ymltriggers only on av*tag push and dispatch(
.github/workflows/release.yml:22-26), so the pull-request picture above is unchangedeither way — independent of this issue and not a dependency of it), #452 (the
setup-micromambav3.2.1 failures; it names this issue as a parallel case, and pinning theaction back — with the
github-actionsignoreentries that would require in bothentries — is one of its stated non-goals, so it does not edit
.github/dependabot.ymlandthere is no file overlap). Files:
.github/dependabot.yml,tests/test_setup_sidecar.py,packaging/setuptools-compatibility.txt.Target area
CI / repository governance
Milestone
none / cross-cutting
Required safety attestations
Grooming status — 2026-08-13
setuptoolsentirely in thepipentry —no
versions:and noupdate-types:narrowing. The pin is to one artifact by digest, soany automated edit to that line is wrong whether or not it lands inside the bound: 80.9.0
is already the newest release below 81, and even a hypothetical in-bound 80.9.1 would need
a fresh
pip hashand a deliberate two-value edit. Verified safe becausesetuptoolsappears in no other pip manifest in the repository.
81lives: as a literal in the new guard intests/test_setup_sidecar.py. This has to be settled here rather than argued on arisk:highpull request, becausepackaging/setuptools-compatibility.txt:4-6declaresitself "THE single source of truth … nothing may re-state the version or the bound
anywhere else",
scripts/setup_sidecar.py:127-128repeats it ("Parsed rather thanrestated"), and
test_no_consumer_restates_the_version_or_the_bound's docstring (:141)names hard-coding
<81as "the third defect in build(packaging): pin and hash the bundled setuptools wheel, with one source of truth #218". It is nonetheless already therepository's own practice:
.github/workflows/packaging.yml:241and:293hold< (81,)as a literal on a non-comment line and pass that test, because the testforbids the requirement spelling — its regex is
setuptools\s*(==|<=|>=|<|>|~=)\s*[0-9](tests/test_setup_sidecar.py:170) — and not anumeric comparison.
int(version.split(".")[0]) < 81introduces no such spelling. Theassertion message spells the bound as prose ("below 81; setuptools 82.0.0 removed
pkg_resources") rather than assetuptools<81— not because that guard would catch it,since its mapping (
tests/test_setup_sidecar.py:152-157) does not includetests/test_setup_sidecar.pyand a literal there is invisible to it today, but because ofthe header's rule quoted above and because a message is quotable: a mapped consumer that
later pastes it would fail the guard for a reason nobody would look for here. The version
number is still read from the file and never restated.
tests/test_setup_sidecar.py, nottests/test_marker_contract.py. The latter already parses this config and holds the yamlloader, but whether it counts as one of ADR-0064's frozen "agent test modules" is
unsettled — the phrase is enumerated nowhere — and this issue must not be the place that
decides it.
test_setup_sidecar.pyis unambiguously outside the agent layer and alreadyowns the compatibility-file contract;
import yamlis already used by six other testmodules, so it adds no dependency.
.github/dependabot.ymlvalidates all-or-nothing, so a malformededit silently disarms every ecosystem rather than failing loudly — including the action-SHA
updates that feed the required checks.
the change is on the default branch, and the repository owner merges knowing that.
Acceptance criteria
.github/dependabot.yml'spipentry carries anignorelist holding exactly oneentry,
dependency-name: setuptools, with noversions:and noupdate-types:key.The explanation — why the pin is deliberate, and the removal trigger — is written as
YAML comments:
#-leading lines are the only onestest_no_consumer_restates_the_version_or_the_boundskips (:176), so the literalsetuptools<81written on a data line would fail that test once this file is insideits mapping. A bound written as YAML data —
versions: ["<81"]— would slip pastthat regex unnoticed, since the regex needs
setuptoolsadjacent to a comparator onthe same line, which is a second reason the entry must carry no
versions:key at all.A new test in
tests/test_setup_sidecar.pyloads.github/dependabot.ymlwithyaml.safe_loadand fails if thepipentry'signorelist does not namesetuptools, or names it with aversions/update-typesnarrowing.Currently-failing demonstration, recorded in the PR: run the new test against the
tree before the config edit — it fails, because the
pipentry has noignorekeyat all — then apply the config edit and it passes. Quote both runs.
tests/test_setup_sidecar.pyasserts the parsed major version is below 81 —int(version.split(".")[0]) < 81on the versionsetuptools_requirement()returns,which adds no dependency — and the assertion message names the
pkg_resourcesremoval in setuptools 82.0.0 and the bound as prose, per the second Chosen-design
clause above, rather than only the file format. Currently-failing demonstration,
recorded in the PR: temporarily change only the final requirement line of
packaging/setuptools-compatibility.txttosetuptools==83.0.0with a syntacticallyvalid 64-hex digest — leave the header intact, or
test_the_removal_trigger_is_recordedgoes red for an unrelated reason — and relax
test_the_requirement_is_pinned_and_hashed's exact-version (:96) and exact-digest(
:99) assertions to the new values, which is the "update the assertion to match thebump" fix this criterion exists to stop. Then run
pytest tests/test_setup_sidecar.py:the new bound assertion must be the only red, and its message must contain
pkg_resources. No other test in that module reads the concrete version — the twoparser tests write their own file into
tmp_pathand redirect the module constant withmonkeypatch. Revert all three edits;git statusclean before review is requested.packaging/setuptools-compatibility.txt's comment block names.github/dependabot.ymland contains the literal phrase
not a consumer, on a line outside the"THE CONSUMERS, named rather than counted" list — that list keeps its existing four
entries (
:37-40) unchanged, so the new path cannot be read as a fifth file that readsthe pin. The same block records that the bound guard this issue adds holds
81as aliteral inside an assertion, in the same form
.github/workflows/packaging.yml:241and:293already use. It must not claim that is the only such literal in the tree:.github/workflows/sidecar-measure.yml:86installs"setuptools<81"outright, on anon-comment line, in a manual-dispatch workflow the guard does not scan.
.github/dependabot.ymlis added totest_no_consumer_restates_the_version_or_the_bound's file mapping (whose function is"may not restate the pin", not "reads the pin"), so the one file whose job is to
suppress automated edits to the pin is itself scanned for a copy of it — the
versions: ["<81"]hazard in AC1 is exactly the edit that would put one there.Currently-failing demonstration with its negative control, both recorded in the PR:
add an uncommented
setuptools<81line to.github/dependabot.yml; with the mappingentry present the test fails naming that file, and with the same line present but the
mapping entry removed the test passes — quote both runs, so the mapping addition is
evidenced as the thing that binds. Revert the injected line. The stale
# FIVE consumers …comment above the mapping is left exactly as it is (see Scope andnon-goals): the entry is added because that file may not restate the pin, not as a
fifth consumer of it.
Config validity: the committed reach stops at the parse and the
ignoreshape above.The config-error banner is not asserted and no criterion here pretends to —
gh api repos/bioedca/tether/dependabot/configreturns404(checked 2026-08-13), sothere is nothing for a test to read. Instead the PR body states in one sentence that the
banner under Insights → Dependency graph → Dependabot is to be read after merge, and the remedy
if it appears is to revert the entry, because the previous behaviour does not resume on
its own.
Existing coverage stays green, including the two modules that already read the files
being edited:
pytest tests/test_setup_sidecar.py tests/test_marker_contract.py.test_marker_contract.py's Dependabot assertions read only thegithub-actionsentries (via
_dependabot_github_actions_directories,:1028-1046), so anignorekey on the
pipentry is invisible to them.Full local gates:
pre-commit run --all-filesand the lane's matrix line fromAGENTS.md. No docs page and no schema change, somkdocs build --strictandscripts/dump_schema.py --checkare not implicated.Ready condition: met; awaiting a fresh exact-scope marker.