Repository navigation
C-104: one honest failure instead of twenty-five misleading ones - #282
Conversation
… (C-104)
On 2026-08-16 a pytest run here reported 25 failures across five modules and none
was a defect. The virtualenv held views-pipeline-core 2.3.0 and pyarrow 23.0.1
while poetry.lock pinned 3.0.1 and 16.1.0. Two majors of drift produce:
- ModuleNotFoundError on views_pipeline_core.modules.dataloaders.
datafactory_contract, imported at module scope by BOTH managers, which takes
out every test that constructs or inspects one — the only coverage the two
largest modules in the package have;
- five byte-parity failures against the ADR-013 §10 fixture, because parquet
bytes are not stable across pyarrow majors (C-72).
From the failure output neither is distinguishable from a real regression, and
"you broke the wire contract" is the reading that costs an afternoon.
tests/test_locked_environment.py compares the installed versions of the runtime
dependencies DECLARED in pyproject.toml against poetry.lock. Declared, not
hardcoded: a literal list goes stale the first time a dependency is added, which
is the failure conftest.PARTNER_PACKAGES exists to prevent one level up.
The failure names each drifted package, both versions, the command, and says
plainly that the other failures are consequences rather than defects — and that
if the drift is deliberate, the rest of the suite is not testing the locked
contract. A second check catches the other direction: declared in pyproject but
absent from the lock, which would otherwise read as a virtualenv problem when it
is a stale lock.
It fixes nothing and skips nothing. The 25 stay until `poetry install` runs. What
changes is that a contributor can tell in one line which kind of problem they
have — the failures were never wrong, they were unreadable.
Dev-group tools are out of scope on purpose: ruff's reported version varies with
how it was installed, and what actually broke CI on 2026-08-03 was its rule set,
already pinned explicitly in pyproject.toml.
Natural experiment rather than a mutation: this FAILS here, against the real
drift, and must PASS in CI, which installs from the lock.
ruff clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eight findings. The first is the one that matters: the guard whose entire purpose is a readable diagnosis stated something measurably false. 1. "every test that touches a manager dies at collection" — it does not. The manager tests import lazily (tests/test_framework_contract.py:50), so the drift produces ordinary failures and the session runs to completion. Measured in the drifted venv: 26 failed, 441 passed, 0 errors. The claim was also self-defeating — pytest interrupts on collection errors, so had it been true this diagnostic would never have run in the scenario it was written for. Corrected in the message and the module docstring. 2. The consequences paragraph was hardcoded to the two packages that drifted on 2026-08-16 but fired verbatim for any drift: a views-frames bump would have been explained in terms of pipeline-core and parquet bytes. Now conditional on which names are actually in the drift set — a diagnosis naming the wrong packages is the failure this file exists to remove. 3. No PEP 503 normalization between pyproject keys and lock names. `PyYAML` or `views_frames` are legal declarations that would never match a lock entry, and the failure would have read "absent from poetry.lock, run `poetry lock`" — which fixes nothing, because the lock is fine. Both sides normalized. 4. Dependencies gated by `optional`, `python` or `markers` were treated as must-be-installed. They are legitimately absent from a given environment, and the message would have said "NOT INSTALLED — run `poetry install`", advice that cannot work. It would also have gone red in CI on the 3.11 runner for a python-gated dep. Skipped. Verified that `extras`-carrying deps are still KEPT, since that is our real views-pipeline-core declaration. 5. The claimed ordering guarantee did not hold: running the lock check first does not stop the version check reporting the same omission as `locked=None`. The version check now skips names absent from the lock and defers. 6. `installed=None, expected=None` was a silent pass. Same fix as 5 — unlocked names are the other test's business, and it says so better. 7. `project["tool"]["poetry"]["dependencies"]` raised a bare KeyError on a PEP 621 migration — the unreadable failure this module was written against. Now fails with the form it expects and an instruction not to delete it, matching tests/test_release_version.py's precedent. 8. Register `Last Updated` was still 2026-08-16, stale since C-103's additions. Verified: normalization on PyYAML/views_frames/Foo.Bar_baz; the conditional-skip rule against optional/python/markers/extras shapes; the message now naming only the packages that drifted. ruff clean; register and doc-accuracy guards green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review round —
|
| # | finding | fix |
|---|---|---|
| 2 | the consequences paragraph was hardcoded to pipeline-core and pyarrow but fired for any drift — a views-frames bump would have been explained in terms of parquet bytes |
conditional on the actual drift set |
| 3 | no PEP 503 normalization: PyYAML or views_frames would never match a lock entry, failing with "run poetry lock" when the lock was fine |
both sides normalized |
| 4 | optional / python / markers deps treated as must-be-installed — would have gone red in CI on the 3.11 runner for a python-gated dep |
skipped (verified extras-carrying deps are still kept, since that is our real views-pipeline-core declaration) |
| 5 | the claimed ordering guarantee did not hold — a stale lock was reported twice, once misleadingly as locked=None |
the version check skips unlocked names and defers |
| 6 | installed=None, expected=None was a silent pass |
same fix as 5 |
| 7 | bare KeyError on a PEP 621 migration — the unreadable failure this module was written against |
fails with the form it expects, per test_release_version.py's precedent |
| 8 | register Last Updated stale since C-103 |
bumped |
Findings 2 and 3 are worth dwelling on: a guard that names the wrong packages, or that says "run poetry lock" when the lock is correct, is the same defect this file exists to remove, reintroduced one layer up.
Verification
| environment | result |
|---|---|
CI (poetry install from the lock) |
465 passed, 5 skipped, 37 xfailed, 0 failed |
| this developer venv (drifted) | fails with the table, naming only the two packages that actually drifted |
That pair is the whole proof: the test passes where the lock is honoured and fails where it isn't — and it is simultaneously the standing evidence that the local failures were only ever environment drift.
Also verified directly: normalization on PyYAML / views_frames / Foo.Bar_baz, and the conditional-skip rule against optional / python / markers / extras shapes.
The problem
On 2026-08-16 a
pytestrun in this repository reported 25 failures across five modules, and none was a defect. The virtualenv heldviews-pipeline-core 2.3.0andpyarrow 23.0.1whilepoetry.lockpinned3.0.1and16.1.0.Two majors of drift produce:
ModuleNotFoundError: views_pipeline_core.modules.dataloaders.datafactory_contractat the import of both managers — which takes out every test that constructs or inspects one, i.e. the only coverage the two largest modules in the package (387 lines each) have;From the failure output, neither is distinguishable from a real regression. "You broke the wire contract" is the available reading, and it is the one that costs an afternoon.
What this adds
tests/test_locked_environment.py— two checks:pyproject.tomlmatchpoetry.lock;pyproject.tomlis actually in the lock — the other direction, which would otherwise be reported as a virtualenv problem when it is a stale lock.Declared, not hardcoded. A literal list goes stale the first time a dependency is added, which is exactly the failure mode
tests/conftest.py::PARTNER_PACKAGESexists to prevent one level up.The failure a developer actually sees:
What it deliberately does not do
It does not fix the drift and it does not skip. The 25 failures stay until someone runs
poetry install. What changes is that a contributor can tell in one line which kind of problem they have — the failures were never wrong, they were unreadable, and that is the whole of C-104's cost.Dev-group tools are out of scope on purpose:
ruff's reported version varies with how it was installed, and the thing that actually broke CI on 2026-08-03 was its rule set, whichpyproject.tomlalready pins explicitly.Proof
A natural experiment rather than a mutation, and a better one:
poetry installfrom the lock)If CI goes green on this PR, that is simultaneously the negative case for the new test and the standing evidence that the 25 local failures were only ever environment drift.
ruff check .clean. C-104 updated with a Partial mitigation note.