Skip to content

docs: state that match_feature_group_criteria's options view depends on the caller - #903

Merged
TomKaltofen merged 2 commits into
mainfrom
docs/match-criteria-options-view
Jul 30, 2026
Merged

docs: state that match_feature_group_criteria's options view depends on the caller#903
TomKaltofen merged 2 commits into
mainfrom
docs/match-criteria-options-view

Conversation

@TKaltofen

Copy link
Copy Markdown
Collaborator

Summary

FeatureGroup.match_feature_group_criteria has two production call sites, and they hand it two different views of Options:

  • IdentifyFeatureGroupClass._filter_feature_group_by_criteria resolves a feature, which runs before intake, so it passes declared (pre-default) options.
  • GlobalFilter.criteria matches a filter feature, which runs after intake, so the values it passes have already been through options_with_defaults on the resolved feature.

Nothing in the classmethod's own docstring said so, which is the surface an author overriding it actually reads. Matching logic that inspects option values can behave differently between the two paths with no warning that this is even possible.

What changed

Docs and docstrings only. No behavior change, no new tests: the contract is already pinned by tests/test_core/test_filter/test_filter_criteria_effective_options.py.

  • FeatureGroup.match_feature_group_criteria gains a paragraph describing the rule.
  • FeatureChainParserMixin.match_feature_group_criteria gains a two-line pointer to it. The mixin overrides the classmethod with its own docstring, so every group on the recommended authoring path was showing nothing.
  • in_depth/feature-group-matching.md states the rule and links to the stage table.
  • in_depth/property-mapping.md: the page asserted that materialization "never changes matching" while its own filter-matching paragraph said the opposite, and the stage table listed a bare "match" as observing declared options. Both are disambiguated to resolution matching.

Precision worth noting

The second commit corrects an overstatement in the first. The filter path does not receive a materialized view of the filter feature's options. GlobalFilter.unify_options copies the resolved feature's effective options only for keys the filter feature does not already set, and never calls options_with_defaults on the filter feature itself. Verified against a spec declaring default="D":

filter feature declares hook receives
nothing {'k': 'D'} (materialized, from the resolved feature)
{'k': None} {'k': None} (its own declared value survives)
{'k': 'OWN'} {'k': 'OWN'}

GlobalFilter.add_filter accepts a Feature, so a caller can reach that difference. The docs now describe it as a merge rather than a materialized view.

Review

Two independent deep reviews. tox green: 7535 passed, 170 skipped, ruff, mypy --strict, bandit clean.

Two findings were real but outside a docs commit and will be filed separately: in_depth/filter_data.md describes filter matching inaccurately, and the comment at mloda/core/core/engine.py:337-339 asserts the filter-feature intake call "must remain an identity no-op", which does not hold when a filter feature declares a defaulted key explicitly as None.

…on the caller

The classmethod is called on two paths with two views of Options. Feature
resolution passes declared (pre-default) options, because it runs before intake.
GlobalFilter calls the same method again to match a filter feature, after intake
has materialized declared defaults and unify_options has carried them onto the
filter feature, so that call observes effective (post-default) options.

Nothing in the method's own docstring said so, which is the surface an author
overriding it actually reads. An override whose matching logic inspects option
values can behave differently between the two paths with no warning.

The property-mapping page also asserted that materialization never changes
matching, which its own filter-matching paragraph contradicts. Qualify it to
resolution matching, and cross-reference the rule from the matching guide.
…alized view

Review correction. GlobalFilter.criteria passes the filter feature's options
after unify_options, which copies the resolved feature's effective options only
for keys the filter feature does not already set. It never materializes the
filter feature's own options. So a key the filter feature declares itself keeps
its declared value, and only an omitted key arrives materialized. Calling that
view "effective (post-default) options" overstates it, and add_filter accepts a
Feature, so a caller can reach the difference.

Also carry the rule to the surfaces that were still missing it:
FeatureChainParserMixin overrides this classmethod with its own docstring, so
every group on the recommended authoring path showed nothing; the stage table
said a bare "match" observes declared options, contradicting its own
filter-matching row; and the matching guide gated the rule behind overriding,
though a group using the inherited matcher is affected too.
@TKaltofen
TKaltofen requested a review from TomKaltofen as a code owner July 30, 2026 17:05
@TomKaltofen
TomKaltofen merged commit 0f69455 into main Jul 30, 2026
18 checks passed
@TomKaltofen
TomKaltofen deleted the docs/match-criteria-options-view branch July 30, 2026 17:11
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.

2 participants