Use one rule-selection type for conf scoping and run history - #259
Conversation
RuleSelectionRecord (a TypedDict recorded on ProverRunLog and carried by buffer jobs) duplicated RuleSelection (the InheritRules | SelectRules | ExcludeRules union in composer/prover/conf.py), with _selection_of and _scope_of converting between them. Use RuleSelection throughout: the run log, the job records, buffer_conf, and submit_buffer, which now parses its arguments with rule_selection. A whole-buffer run records InheritRules() rather than None. The checkpoint serializer restores tuples as lists, so SelectRules and ExcludeRules coerce names back to a tuple; otherwise a restored selection compares unequal to a fresh one and is unhashable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selection key and checked-rules logic move from free functions in prover.py to key() / checked_among() methods on each RuleSelection variant, beside apply_to. The tuple coercion moves into each __post_init__, dropping the _freeze_names helper. Test run-log constructors that still passed rules=None now pass InheritRules(), and a harness comment no longer describes a whole-buffer run as rules=None. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop RuleSelection.key() and checked_among(). SelectRules and ExcludeRules now sort their names on construction, so selections naming the same rules compare equal and buffer_jobs is keyed on (name, selection) directly instead of on an encoded string. Which declared rules a selection checks is a run-history question with a CVL-pipeline assumption (InheritRules means every rule), so it moves out of the ecosystem-neutral conf module into _checked_rules in prover.py. An include selection now counts only declared rules rather than echoing its names back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| def __post_init__(self) -> None: | ||
| # Sorted so that selections naming the same rules compare equal. Coerced to a tuple | ||
| # because checkpointed graph state records selections, and the checkpoint serializer | ||
| # restores tuples as lists. | ||
| object.__setattr__(self, "names", tuple(sorted(self.names))) |
There was a problem hiding this comment.
I absolutely hate this, but researching it seems to be the "way to do this". This language dude.
| prover_results: list[tuple[RulePath, StatusCodes]] | ||
| spec_digest: str | ||
| rules: RuleSelectionRecord | None | ||
| rules: RuleSelection |
There was a problem hiding this comment.
We're sure dataclasses round trip through a checkpointer okay? I vaguely remember seeing code to that effect when I've poked at it, but my distrust of the checkpointer led me to choose typeddicts to begin with.
There was a problem hiding this comment.
That wouldn't have occurred to me to check! I just had Claude build a little program to make sure this works, and it looks like it does.
| def _checked_rules(selection: RuleSelection, declared: Iterable[str]) -> list[str]: | ||
| """The ``declared`` rules a run under ``selection`` checks. ``InheritRules`` counts as every | ||
| rule: the source pipeline's base confs select none of their own. Names match exactly, which | ||
| holds because ``submit_buffer`` admits only names the buffer declares, never patterns.""" | ||
| match selection: | ||
| case InheritRules(): | ||
| return list(declared) | ||
| case SelectRules(names): | ||
| selected = set(names) | ||
| return [r for r in declared if r in selected] | ||
| case ExcludeRules(names): | ||
| excluded = set(names) | ||
| return [r for r in declared if r not in excluded] |
There was a problem hiding this comment.
see my comment from another commit; it's very possible to define a def selected(self, declared: Iterable[str]) on each inhabitant of RuleSelection pyright will handle that.
There was a problem hiding this comment.
Adding a comment explaining why this is here, and not a public method on the selection types.
| msg: str = "", | ||
| selection: RuleSelectionRecord | None = None, | ||
| rules: RuleSelection | None = None, | ||
| rules: RuleSelection = InheritRules(), |
There was a problem hiding this comment.
remember kids, this is okay only because these are immutable.
| match selection: | ||
| case InheritRules(): | ||
| sel_desc = "" | ||
| case SelectRules(names): | ||
| sel_desc = f" (rules {list(names)})" | ||
| case ExcludeRules(names): | ||
| sel_desc = f" (excluding {list(names)})" |
State that the checked-rules computation is not a general property of a RuleSelection: it relies on the source pipeline's base confs selecting no rules and on submit_buffer admitting only exact declared names. Also update a test comment that still described a whole-buffer run as rules=None. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Why there were two types
TypedDictnamedRuleSelection(sort: "include" | "exclude",selector: list[str]) tocomposer/spec/source/prover.py. It recorded the rule subset a run covered, onProverRunLog.rules, withNonemeaning every rule.InheritRules | SelectRules | ExcludeRulesto the new sharedcomposer/prover/conf.pyto scope a conf to a run's rules, and CVL and Solana confs both use it. It is the same idea as rules striping #177'sTypedDict, and should have replaced it. Instead, a sloppy merge when Add the Solana Prover preflight step #248 landed kept both: rules striping #177's type was renamed toRuleSelectionRecordto avoid the name clash, and_scope_ofwas added to convert one into the other.After that, the CVL prover code used both types. Run history and buffer jobs used
RuleSelectionRecord. Conf building usedRuleSelection.buffer_confaccepted either, through separateselectionandrulesparameters.submit_bufferparsed its arguments with its own_selection_ofinstead of the sharedrule_selection, and keyed its job table on a string built by_selection_key.What this changes
RuleSelectionRecord,_selection_of,_scope_ofand_selection_key.ProverRunLog.rules,_BufJob/_BufDone,_run_buffer_jobandbuffer_confnow all take aRuleSelection. A whole-buffer run recordsInheritRules()instead ofNone.submit_bufferparses its arguments withrule_selection, and keys its job table on(buffer name, selection)directly.SelectRules/ExcludeRulesstorenamesas a sorted tuple. Sorting keeps the duplicate-submit check ignoring name order, as_selection_keydid. The tuple conversion is needed becauseProverRunLogis saved in checkpoints, and restoring a checkpoint turns tuples into lists; without it, a restored selection doesn't compare equal to a fresh one and can't be hashed. A new test checks the round trip._checked_rules(selection, declared)inprover.pyworks out which declared rules a run under a selection checks. The stuck-rule tracking (_executed_rules) andsubmit_buffer's check for a selection that would run no rule both use it. It stays out ofconf.pybecause it assumesInheritRulesmeans every rule, which holds for the CVL pipeline's base confs, not for every conf. An include selection now counts only rules the buffer declares; before, its names were returned as given.Testing
pytest tests/ -m "not expensive": 1655 passedpyright: 0 errors🤖 Generated with Claude Code