A two-line conftest.py makes a false claim report as a pass:
import pgc_vacuity
pgc_vacuity.Expect.num = lambda self, got, want, name: self._record(name)
With that file present, a test asserting 1 == 2 prints 1 passed and exits 0.
The test body is: expect.num(1, 2, "one equals two, which it does not")
no conftest failed, correctly
Expect.num stubbed to count but not compare PASSED -- A FALSE CLAIM WENT GREEN
Expect._record stubbed failed, correctly (count 0 is refused)
This is strictly worse than #924. Those hatches switched off meta-rules — the order-collapse scan, the broad-except scan — so a test that was wrong in a particular way stopped being refused. This switches off the assertion itself: the comparison never happens, the count still increments, and every guard downstream is satisfied by a test that concluded nothing.
Found while composing #964 and #966 locally, which is the only reason it surfaced: #966 adds Expect._record, and checking whether #964's snapshot covered it showed that it does not, because it is not a module-level name.
Why #964's guard does not see it, and what it does see
#964 snapshots the module's bindings at import and refuses a run in which any binding changed. That is exactly what it claims, and it holds:
pgc_vacuity.Expect = <a subclass with num() stubbed> REFUSED, names Expect
pgc_vacuity._Record = None REFUSED, names _Record
pgc_vacuity._broad_except_sites = lambda p: [] REFUSED
pgc_vacuity._sorted_ordered_sites = lambda p: [] REFUSED
Rebinding Expect wholesale is caught. Mutating an attribute on Expect is not, because the binding still points at the same object — only that object's __dict__ changed. An identity comparison cannot see through the reference it compares.
So this is not a defect in #964. It is the next frame out, and it is the third instance of one pattern:
#924 / #958 the DATUM a rule reads _ORDER_KILLERS
#924 / #964 the FUNCTION that reads it _sorted_ordered_sites
this issue an ATTRIBUTE OF a bound object Expect.num
Each fix moved the hatch one level, and each time the level above was reachable for the same two lines.
The surface
18 public assertion methods on Expect, each an independent hatch:
at_least cannot_run differ hash num ordered_rows ordering_observable
outcomes plan_marker plan_node refusal row_set rowcount rows
run_failed sqlstate text wrote
Stubbing any one of them silences every assertion of that kind in the directory, and the honest-looking self._record(name) in the stub is what keeps the run green — without it, the count stays 0 and pytest_runtest_call refuses the test. The exploit has to be slightly careful, and being careful is what makes it silent.
What it does NOT reach
Expect._record stubbed fails closed: the count never rises, so every test in the directory is refused with made no counted assertion. So the recorder itself is safe by accident of what depends on it, and that accident is worth keeping in mind rather than relying on.
Not proposed here
I have not decided between snapshotting the class dictionaries as well, making Expect resistant to attribute assignment, or accepting this as the honest floor and documenting where the floor is. The third option is genuinely on the table: the criterion #924 set is cost, not impossibility, and a reviewer reading a conftest.py that stubs pgcolumnar's assertion class would see it immediately — which is not true of _sorted_ordered_sites = lambda p: [].
What is established is the table at the top: it is two lines, it is silent, and it makes a false claim pass.
Verification for whatever lands
The probe is the test. A fix must make row 2 read REFUSED, with rows 1 and 3 unchanged — row 3 being the control that shows the recorder's own protection is a different mechanism and was not what fixed this.
Measured on main composed with #966 (37e211a); #964 is on main as of 3f0aa0f.
A two-line
conftest.pymakes a false claim report as a pass:With that file present, a test asserting
1 == 2prints1 passedand exits 0.This is strictly worse than #924. Those hatches switched off meta-rules — the order-collapse scan, the broad-except scan — so a test that was wrong in a particular way stopped being refused. This switches off the assertion itself: the comparison never happens, the count still increments, and every guard downstream is satisfied by a test that concluded nothing.
Found while composing #964 and #966 locally, which is the only reason it surfaced: #966 adds
Expect._record, and checking whether #964's snapshot covered it showed that it does not, because it is not a module-level name.Why #964's guard does not see it, and what it does see
#964 snapshots the module's bindings at import and refuses a run in which any binding changed. That is exactly what it claims, and it holds:
Rebinding
Expectwholesale is caught. Mutating an attribute onExpectis not, because the binding still points at the same object — only that object's__dict__changed. An identity comparison cannot see through the reference it compares.So this is not a defect in #964. It is the next frame out, and it is the third instance of one pattern:
Each fix moved the hatch one level, and each time the level above was reachable for the same two lines.
The surface
18 public assertion methods on
Expect, each an independent hatch:Stubbing any one of them silences every assertion of that kind in the directory, and the honest-looking
self._record(name)in the stub is what keeps the run green — without it, the count stays 0 andpytest_runtest_callrefuses the test. The exploit has to be slightly careful, and being careful is what makes it silent.What it does NOT reach
Expect._recordstubbed fails closed: the count never rises, so every test in the directory is refused withmade no counted assertion. So the recorder itself is safe by accident of what depends on it, and that accident is worth keeping in mind rather than relying on.Not proposed here
I have not decided between snapshotting the class dictionaries as well, making
Expectresistant to attribute assignment, or accepting this as the honest floor and documenting where the floor is. The third option is genuinely on the table: the criterion #924 set is cost, not impossibility, and a reviewer reading aconftest.pythat stubspgcolumnar's assertion class would see it immediately — which is not true of_sorted_ordered_sites = lambda p: [].What is established is the table at the top: it is two lines, it is silent, and it makes a false claim pass.
Verification for whatever lands
The probe is the test. A fix must make row 2 read
REFUSED, with rows 1 and 3 unchanged — row 3 being the control that shows the recorder's own protection is a different mechanism and was not what fixed this.Measured on
maincomposed with #966 (37e211a); #964 is onmainas of3f0aa0f.