_comparisons() in test_failed_query_sentinel.py decides which of Expect's public methods must refuse a QUERY_ERROR sentinel. It selects on the first two parameter names: got plus want or floor.
That derivation is why the sweep is worth having — an assertion added later is covered by it instead of being the next hole. But the exclusion side is not derived at all, and @OffgridwithJD raised the case that shows it:
wrote(cur, want, name) sits outside the sweep because its first parameter is a cursor rather than a value. That is the right answer, but it is right by accident — a future assertion whose first parameter is simply not called got would be excluded just as silently and for no good reason.
Why a declared exclusion list is not the fix
The obvious remedy is a list of excluded method names with a reason each. This layer argues against exactly that shape, repeatedly and with evidence:
_WRITE_TAGS is a list, and four statement shapes escape it (a data-modifying CTE, a DO block, a SELECT of an inserting function, a CALL) — recorded as a residual precisely because a list cannot see what it does not name.
test_harness_deps.py exists because a hand-maintained NO_CLUSTER drifts, and the fix there was to derive membership and assert the declaration against it.
- selftest 400's SKIP sweep and 080's heredoc exemption were both rewritten from filename lists to derived properties for the same reason.
A list of exclusions is a convention. This directory only counts a mode refused when a mechanism refuses it.
The shape to build instead
Derive the exclusion, then assert the partition:
- Every public method of
Expect lands in exactly one bucket — selected, or excluded — with no third state and no method matching neither rule.
- Each exclusion is derived from a property of the signature, not from a name.
wrote is excluded because its left operand is a cursor rather than a caller-supplied value; cannot_run, run_failed and outcomes because they take a result object; plan_marker and plan_node because they take a plan. Whatever the rules turn out to be, each must be checkable from the signature.
- The reason travels with the exclusion, so a reader sees why a method is out rather than inferring it from a missing entry.
- An arm prints
inputs == selected + excluded and fails when a method matches neither rule — which is the case that must not be silent, and is what happens today when a parameter is not called got.
The current enumeration already reconciles — inputs 15 == selected 8 + not selected 7 — but "not selected" is a residue rather than a decision, and nothing fails when a new method lands in it for the wrong reason.
Acceptance
- Adding a method to
Expect whose left operand is a caller-supplied value, with a first parameter not named got, must fail an arm rather than silently leave the sweep.
- Adding a method that is legitimately excluded must require a stated reason, and the arm must pass once it has one.
- Removal proof for both: name the mutation, apply it, show which arm reddens.
Related
🤖 Generated with Claude Code
https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
_comparisons()intest_failed_query_sentinel.pydecides which ofExpect's public methods must refuse aQUERY_ERRORsentinel. It selects on the first two parameter names:gotpluswantorfloor.That derivation is why the sweep is worth having — an assertion added later is covered by it instead of being the next hole. But the exclusion side is not derived at all, and @OffgridwithJD raised the case that shows it:
Why a declared exclusion list is not the fix
The obvious remedy is a list of excluded method names with a reason each. This layer argues against exactly that shape, repeatedly and with evidence:
_WRITE_TAGSis a list, and four statement shapes escape it (a data-modifying CTE, aDOblock, aSELECTof an inserting function, aCALL) — recorded as a residual precisely because a list cannot see what it does not name.test_harness_deps.pyexists because a hand-maintainedNO_CLUSTERdrifts, and the fix there was to derive membership and assert the declaration against it.A list of exclusions is a convention. This directory only counts a mode refused when a mechanism refuses it.
The shape to build instead
Derive the exclusion, then assert the partition:
Expectlands in exactly one bucket — selected, or excluded — with no third state and no method matching neither rule.wroteis excluded because its left operand is a cursor rather than a caller-supplied value;cannot_run,run_failedandoutcomesbecause they take a result object;plan_markerandplan_nodebecause they take a plan. Whatever the rules turn out to be, each must be checkable from the signature.inputs == selected + excludedand fails when a method matches neither rule — which is the case that must not be silent, and is what happens today when a parameter is not calledgot.The current enumeration already reconciles —
inputs 15 == selected 8 + not selected 7— but "not selected" is a residue rather than a decision, and nothing fails when a new method lands in it for the wrong reason.Acceptance
Expectwhose left operand is a caller-supplied value, with a first parameter not namedgot, must fail an arm rather than silently leave the sweep.Related
ordering_observablewas found outside it.wrote()raised the question, and test/pytest: an A/B whose arms agree measures nothing (#432) #936 wheredifferwas added to the shape table by hand.🤖 Generated with Claude Code
https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK