Skip to content

tools(parity): make a one-sided property declarable - #2231

Merged
ryanbr merged 1 commit into
mainfrom
fix/parity-property-disposition
Sep 15, 2026
Merged

ryanbr merged 1 commit into
mainfrom
fix/parity-property-disposition

Conversation

@ryanbr

@ryanbr ryanbr commented Sep 15, 2026

Copy link
Copy Markdown
Owner

A one-sided property could never be dispositioned, so once one existed --refresh-derived refused permanently. Lift Log added two, which is how it surfaced.

The mismatch

_required_v3_exemptions built kind strings with name[:-1]:

required.add((f"add-{name[:-1].replace('_', '-')}", identity))
unpaired_files       -> add-unpaired-file        accepted
unpaired_functions   -> add-unpaired-function    accepted
unpaired_properties  -> add-unpaired-propertie   NOT accepted
unpaired_constants   -> add-unpaired-constant    accepted

_validate_dispositions accepts add-unpaired-property. The two never meet:

  • write add-unpaired-propertie and validation rejects the registry outright
  • write add-unpaired-property and it never matches the requirement, which stays unsatisfied

checked is keyed on (item["kind"], item["identity"]) and compared directly against required, so the strings have to be equal. The debt can be neither waived nor cleared, and the refresh refuses for as long as the declaration exists.

Only the -ies plural breaks. Files, functions and constants all survive the strip, and the removal side is fine because pairs strips to pair.

Why it went unnoticed since #1534

No test ever declared a one-sided property. Every existing case in test_parity_governance_acceptance.py and test_parity_ledger.py uses add-unpaired-function or add-unpaired-constant, and until Lift Log there was no one-sided property in the tree either. The path was unreachable, so the typo was invisible.

The fix

Explicit singulars instead of name[:-1], with a comment recording why the naive strip is a trap here.

The test asserts the invariant rather than the spelling: every kind the ratchet can REQUIRE must be a kind a disposition may DECLARE. It builds a real disposition for each required kind and runs it through _validate_dispositions, so any future set name that stems badly fails immediately.

Mutation-checked. With the fix reverted:

AssertionError: 'add-unpaired-property' not found in
  {'add-unpaired-function', 'add-unpaired-propertie', 'add-unpaired-file', 'add-unpaired-constant'}
FAILED (failures=1, errors=1)

Restored, both pass.

Nothing anywhere referenced the misspelling, so there is no migration: the dispositions registry is currently empty.

Scope

This unblocks the mechanism only. main is still red for parity-governance because the authority is stale (#2229), and clearing that needs dispositions for the one-sided declarations Lift Log introduced, which is a larger set than I first reported and is still being enumerated. That work follows separately; this lands on its own because the vocabulary defect is real regardless of how the Lift Log drift is resolved.

Second pass: the test was not going to run

Re-reviewing my own change caught that the governance job uses an explicit module list rather than discovery:

python3 -m unittest -v \
  tests.test_parity_ledger \
  tests.test_parity_governance_acceptance \
  tests.test_rr_legacy_preservation_contract

A new file under Tools/tests/ is matched by the workflow's paths: filter, so it would have triggered the job while never being collected by it. The regression test would have sat there proving nothing. Added tests.test_parity_disposition_kinds to the list.

That is the same defect I raised on #2103, where a new test file was never collected by discover, and on the watch-build trigger gap. Having shipped it myself in the first push is worth recording rather than quietly patching.

The floor moves 113 to 115 in step. The three existing modules collect exactly 113, so that number tracks the real total rather than sitting loosely underneath it; leaving it alone would have let these two tests disappear later with nothing going red, which is precisely what the floor exists to prevent.

Verified locally, since this PR's own parity-governance cannot report on it:

3 modules : Ran 113 tests   FAILED (failures=2)
4 modules : Ran 115 tests   FAILED (failures=2)

Same two failures either way, so they are the inherited authority staleness rather than anything here. test_core_tools_filter_covers_every_governance_tool_path still passes against the edited workflow, which matters because it reads that file's text and pins the path list.

About the red check

parity-governance is failing on this PR for the reason described in #2229: main's checked-in authority is stale, and any PR touching Tools/parity_* inherits it. It is the same red @bhelm is sitting behind on #2221. Nothing in this change can clear it, and the check cannot vouch for this change either way until the authority question is settled.

`_required_v3_exemptions` built its kind strings with `name[:-1]`, so
`unpaired_properties` stemmed to `add-unpaired-propertie`. The vocabulary
`_validate_dispositions` accepts is `add-unpaired-property`.

Those two never meet. A disposition written as `add-unpaired-propertie` is
rejected by validation, and one written as `add-unpaired-property` never matches
the requirement, so a one-sided property can be neither waived nor cleared and
`--refresh-derived` refuses for as long as it exists. Files, functions and
constants all survive the naive strip; only the `-ies` plural does not, which is
why nothing caught it. No existing test declares a one-sided property, and none
had appeared in the tree until Lift Log added two.

Replaces the strip with explicit singulars and adds a test asserting the
invariant directly: every kind the ratchet can REQUIRE must be a kind a
disposition may DECLARE. Reverting the fix fails it on the exact string.

Nothing else referenced the misspelling, and the removal side already read
correctly because "pairs" strips to "pair".

The workflow runs an explicit module list rather than discovery, so the new test
would not have run in CI. Adding it to that list and raising the collected-count
floor from 113 to 115: the three existing modules produce exactly 113, so the
floor tracks the real total rather than sitting loosely below it, and leaving it
alone would have let these two tests disappear later without anything going red.
@ryanbr
ryanbr force-pushed the fix/parity-property-disposition branch from 7f5b6a7 to 069cffa Compare September 15, 2026 07:55
@ryanbr
ryanbr merged commit b6dc041 into main Sep 15, 2026
4 of 5 checks passed
@ryanbr
ryanbr deleted the fix/parity-property-disposition branch September 15, 2026 08:00
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.

1 participant