🚀 Version 0.11.0
0.11.0 (2026-08-17)
The new toy: ask mloda why. When a feature does not resolve, mloda now tells you which feature failed, which feature groups it considered, and why each one was dropped, instead of a bare "no feature groups found". mlodaAPI.diagnose(...) runs the planning without raising and returns those facts as a ResolutionDiagnosis; a run that does fail raises FeatureResolutionError (still a ValueError) with the same facts attached. This matters because debugging a non-matching feature was guesswork so far. Bonus: mlodaAPI.explain(...) now shows the join plan next to the compute steps.
from mloda.user import mlodaAPI
diagnosis = mlodaAPI.diagnose(["sales__mean_aggr"], compute_frameworks=["PandasDataFrame"])
if diagnosis.complete:
for record in diagnosis.records:
print(record.feature_name, record.requested)
else:
print(diagnosis.feature_name, diagnosis.message) # which feature failed, and the text a run would raiseFive weeks, 219 commits, 208 merged pull requests, 13 contributors. Thanks to @dchaudhari7177, @bunlongheng, @luantaraschi, @nightcityblade, @mercael91, @breezeFur, @PozziTiv4ik, @Sanjays2402, @Leoyang158, @Jah-yee, @hafzism and @AnuragChauhan1120 for the pull requests.
⚠ Breaking changes
-
Compute frameworks and the PythonDict helpers moved to
mloda.user.<backend>.mloda.userandmloda.providerare core-only now:import mloda.userpulls in no backend, and neither facade has a lazy__getattr__any more (mypy sees real types instead ofAny). Every bundled framework is published from one module per backend, and importing such a module never fails when the library is missing; the class reportsis_available() == Falseand is skipped by discovery. Deep import paths are unchanged.# 0.10.0 from mloda.user import PandasDataFrame, PythonDictFramework, columnar_to_rows # 0.11.0 from mloda.user.pandas import PandasDataFrame from mloda.user.python_dict import PythonDictFramework, columnar_to_rows, result_rows
Modules:
mloda.user.pandas,.polars(PolarsDataFrame,PolarsLazyDataFrame),.pyarrow,.python_dict,.sqlite,.duckdb,.iceberg,.spark.ApiInputDataFeaturemoved intomloda.coreand stays exported frommloda.provider. -
PROPERTY_MAPPINGvalues must bePropertySpecobjects. A raw spec dict (including the{DefaultOptionKeys.allowed_values: ...}form) now raises at class definition, naming the class, the key and the remedy. If you already wrote your specs withproperty_spec(...)nothing changes: the builder returns aPropertySpec. Optionality moved into thedefaultfield: an omitteddefaultmeans required,default=Nonemeans optional.PropertySpec,NO_DEFAULTandis_no_defaultare exported frommloda.provider; a misspelled field is a plainTypeErrorat the line where the spec is written. Migration table: Migrating from the dict form. -
Required presence is enforced on the string-named match path. A feature group that matches by feature name and declares a key with no
defaultand norequired_whenno longer matches when that key is still absent after declared defaults and name captures resolve; a warning names the group and the missing key(s), and the resolution failure report names them too. Two ways to migrate a flagged key: bind it from the name with a named capture group(?P<key>...)inPREFIX_PATTERN, or setdeferred_binding=Trueon the spec when the plugin parses the value itself after matching (config-path requiredness stays as it was). All shipped feature groups are migrated; registry plugins are tracked in mloda-registry#327. -
Smaller removals and tightenings.
Options.addis gone (useadd_to_group);get_all_subclasseslost itslog_n_subclassesparameter; a capturelessPREFIX_PATTERNno longer fabricates an operation token from the name suffix (declareRECOGNITION_ONLY_PATTERN = Trueon a captureless pattern that also carries aPROPERTY_MAPPING); the undeclarableumapalgorithm was dropped from the experimentalDimensionalityReductionFeatureGroup; option values are now validated on the string-named path as well, so a wrong value is a non-match there too;required_whenis enforced whatever matcher a feature group keeps; and abstract feature groups are never instantiated by the default matcher.
🔍 Resolution you can inspect
mlodaAPI.diagnose(features, ...): same arguments asexplain, never raises for a resolution, environment or config failure, returnsResolutionDiagnosis(records, complete, feature_name, failed_result, message).session.resolution_report(): thelist[ResolutionRecord]captured whileprepare()planned, one per feature, before or afterrun().FeatureResolutionError(aValueErrorsubclass) withfeature_name,result(the capturedEvaluationResult) andpartial_records. Importable frommloda.provider,mloda.userandmloda.steward, together withResolutionDiagnosisandResolutionRecord.resolve_featureexpresses the full request now (options,plugin_collector,feature_groupscope,links,data_access_collection,compute_frameworks) and builds its candidate universe the way a run does, so what it reports is what a run raises. Also exported frommloda.provider.- Failure messages carry per-candidate elimination reasons with a stage label, a "nearest miss" line for filter probes, the plugin whose framework declaration aborted the environment build, and a hint that no longer suggests the requested name or names only unreachable groups declare. The abstract-only case says which concrete implementation or compute framework to enable.
- Containment: a raising match hook, option validator or parse error is contained per candidate and degrades to a non-match instead of aborting the search for every other feature.
- The troubleshooting page grew with it: Feature Group Resolution Errors covers catching
FeatureResolutionError, the eliminated-candidates block, and the no-feature-groups-found case.
🔗 Join planning
explainandresolved_plan()include the join steps:join_type,join_destination_side,join_inverted,join_token,declared_left_frameworksanddeclared_right_frameworks. The join decision is materialized as aResolvedJoinrecord and theJoinStepis lowered from it, so the plan you read is the plan that runs.- Merge engines agree with each other now: one-to-many and many-to-many equi-joins in the PythonDict engine emit one row per matching pair instead of keeping the last row; differing-key joins conform across compute frameworks and Polars keeps both key columns; a RIGHT join binds its merge arguments to the declared sides, so the declared left feature group's data is the engine's left argument whichever framework executes the join.
- Planner guards: each
JoinStepgets its own completion token; all groups sharing a link keep one orientation; a link that joins in a framework none of its children run in is rejected; a feature is never rewritten into a compute framework its owncompute_framework_rule()excludes; every discriminator key must match, not just one. - Cross-framework joins work under
ParallelizationMode.MULTIPROCESSING(the transform hop now handles the pyarrow table the flight server hands back).
🧩 PropertySpec, the whole lifecycle
- Declared defaults are materialized into runtime options at feature intake and again at the compute boundary, so
feature.options.get("key")incalculate_featurereturns the declared default when the caller omitted it.Options.get(key, default)reads dict-style, giving explicit value, then spec default, then call-site default.input_featuresstill sees the declared, pre-default options. Two features that differ only by an explicitly passed default now merge into one (with a warning) instead of computing twice. allow_explicit_none=Trueopts a key into honoring an explicitNone(present for required checks, seen by validators) instead of treating it as absent.- Name-parsed values bind by name: a named capture group
(?P<key>...)binds to the key of the same name (ParsedFeatureNamecarries the bindings); a positional pattern whose keys share a reachable value is rejected at class definition. is_positive_intis a shared, bool-rejecting predicate exported frommloda.provider; the divergent plugin copies were replaced by it.- Class-definition guards: an all-optional universal-matcher
PROPERTY_MAPPINGwarns, a strict default outside its value space raises,element_validatorwithoutstrict_validationis rejected as dead. BaseInputDatareaders declare their options with the samePropertySpec, reader-selection rejections share the match-rejection channel with attribution, andreference_time/artifact_storage_pathare declared keys.- The column-wise hook contract is public:
COLUMNWISE_HOOKS,COLUMN_DISCOVERY_HOOKS,missing_columnwise_hooksfrommloda.provider, andEmptyResultErrorstates its remedy.
🧹 Filters
GlobalFilterreturns identical rows across compute frameworks.- Filter matching is gated on the declared
feature_groupscope and observes effective (post-default) options; a FeatureSet filter declined by one of its features is reported; each probe records why it lost and names the nearest miss; the dropped-filter ledger is keyed on the declaring filter and deduped by message. - Ownership fixes:
GlobalFiltercopies every filter Feature it stores,SingleFilterowns the feature it was built from, unhashable filter parameter values are rejected at construction, and filtering on a root feature group's own output column no longer crashes or drops the column.
⚙️ Runtime and multiprocessing
- A stalled scheduler raises instead of hanging; a worker that exits abnormally (OOM, SIGKILL) while steps are assigned is detected and reported; a stale
DROP_COMPLETEcontrol message no longer aborts a run. - Compute framework selection is deterministic during planning and resolved by group agreement instead of an arbitrary representative; only one step at a time occupies a compute framework.
- Graph traversals are iterative and memoized: chains of thousands of features no longer hit the recursion limit and diamond-shaped graphs plan in linear time.
- Plugin fixes: time-window features respect
time_unit, forecasting output is no longer discarded as all-NaN, the sklearnfeature_engineeringpipeline declaration matches runtime dispatch.
🔒 Security
SQLITEReader.build_queryquotes the table name and every column identifier. Feature names come from the publicFeature(name=...)API and were interpolated raw into the SELECT, so a crafted name could run attacker SQL (CWE-89). If you feed untrusted feature names to the SQLite reader, upgrade. Thanks @bunlongheng.
📦 Public surface and packaging
__version__onmloda.user,mloda.providerandmloda.steward.result_rows(result)inmloda.user.python_dict: a tolerant unwrapper that turns PythonDictrun_alloutput (columnar dicts, row dicts, nested lists) into a flat list of rows.- Core resolves pyarrow lazily and the import guards survive pyarrow dropping
py.typed(apache/arrow#48970); the feather reader uses the non-deprecated IPC API. - Every bundled compute framework follows one backend-import policy, and each
ComputeFrameworkinstance carries its own uuid with a no-arg constructible base.
📚 Docs, CI and housekeeping
- The PROPERTY_MAPPING page was rewritten as the single source of truth for the spec lifecycle (which invariant fires when, which options view each stage sees). The filter-to-FeatureGroup matching rule, the columnar result contract, and the column-wise hook contract are documented; the memory bank was folded into the docs and retired.
- CI installs the tox environments from
uv.lock, so the gate is reproducible per commit; the tox step got a budget the suite can grow into. - Doc examples run inside the test suite (an uncollected snippet is a failure, not a skip) and every docs page must be reachable from the mkdocs nav.
Full changelog (auto-generated from the commits)
🎯 Minor Release
- enforce required-presence on the string-named match path (#769) (#839) (2d05270)
- keep the facades plugin-free and publish backends per module under mloda.user (#726) (5077b35), closes #707 #713 #719
✨ Features
- add result_rows tolerant unwrapper for run_all output (#717) (#719) (55d4708)
- capture per-feature EvaluationResult during planning and expose session.resolution_report() (#811) (#827) (f66ff1b)
- collapse ReaderOptionSpec into PropertySpec now that reader selection rejects attributably (#953) (050a59f), closes #949
- core-provided bool-rejecting positive-int predicate (e6cadcd), closes #773
- declare reference_time and artifact_storage_path and sweep for undeclared option reads (a84627f)
- declare the column-wise hook contract so plugin authors can see it (#926) (f7c0c63)
- definition-time guard against all-optional universal-matcher PROPERTY_MAPPINGs (#771) (#835) (a65ceae)
- export columnar helpers from mloda.provider (#707) (#713) (d9a4832)
- export is_no_default and document property_spec as the authoring path (#822) (3cfc15c), closes #776
- export resolution debug tooling from mloda.provider (#855) (b8396ba)
- export the PythonDict surface from mloda.provider (#716) (#721) (1b4a508)
- expose version on the public facade modules (#676) (#704) (4bd49f1)
- expose the join plan through explain output (#1135) (7151ee2)
- flag a handler that shadows the marked-abort check on the same try (#998) (4ded967), closes #977
- give BaseInputData its own reader option declaration surface (#893) (58330f1)
- keep a case-disambiguated join side out of the inversion remap (43d9844)
- let resolve_feature express a feature_group scope (#693) (#718) (93c1072)
- let resolve_feature express the full resolution request (#756) (4cd864b)
- Lower JoinStep construction from the ResolvedJoin record (#1134) (a9aff44)
- materialize declared PropertySpec defaults into runtime options (#766) (4b99dd3)
- materialize option defaults at feature intake, canonicalizing default-equivalent twins (#876) (58f0588)
- materialize the join decision as a resolved join record (#1122) (7ad60f9)
- mlodaAPI.diagnose, a non-raising whole-request resolution preflight (#812) (#836) (823f1cc)
- opt-in allow_explicit_none distinguishes explicit None from absent (#768) (#789) (768e0ba)
- Options.get accepts a dict-style default argument (#767) (cb06d9d)
- per-candidate elimination reasons in resolution failure facts (#854) (#867) (727fdae)
- resolve_feature builds its candidate universe via PreFilterPlugins (#757) (d9c7e64), closes #722
- retire fabricated operation values from captureless PREFIX_PATTERNs (#772) (#831) (95177a7), closes #769
- share the match-rejection channel with input-data reader selection (#948) (bc61f37), closes #727
- state the remedy in EmptyResultError and document the columnar result contract (#706) (59661fd), closes #705
- structured parsed-name bindings for PROPERTY_MAPPING (#770) (#815) (f8b2dfc)
- typed FeatureResolutionError carrying the captured EvaluationResult (#809) (#814) (b9a6f16)
♻️ Improvements
- converge the evaluate-render-raise idiom on one resolve_or_raise helper (#885) (8102cff)
- declare the columnwise framework hooks once on FeatureChainParserMixin (#888) (03e4dc0)
- delete remaining resolution logic from the resolve_feature adapter (#792) (#808) (1b72297)
- drop no-op required_uuids.union() in add_tfs (3ae4e6f)
- extract a non-raising evaluation seam from IdentifyFeatureGroupClass (#754) (#778) (46440f6)
- give the match-hook call one home across both seams (#1022) (29b19b9), closes #991
- make PROPERTY_MAPPING a typed spec object, not an untyped dict (#694) (e9288f2), closes #530 #731 #732 #724
- make resolution failure rendering a pure projection of EvaluationResult (#791) (#802) (674043b)
- name JoinStep frameworks destination and source (#668) (#702) (1993bd7)
- remove dead code and duplication left by the resolution consolidation (#853) (#863) (e829544)
- remove resolution-epic dead code and document the new resolution surfaces (2d10c2e)
- rename tfs_collecion to tfs_collection (e2e3d0e)
- resolve pyarrow lazily in core so the facades pull in no backend (#737) (#741) (efde1c8)
- retire transitional PROPERTY_MAPPING parser seams (#798) (#866) (9b9432c)
- run the upload marking once per plan build, not per step (1665e7e)
- share one hashability probe between the two _make_hashable helpers (#984) (7523665)
- split the author-time guard subsystem out of feature_chain_parser (#907) (a806bdf)
- split the resolution types and failure renderer out of identify_feature_group (#891) (7ee8a84)
- stop identify_feature_group being a re-export facade (#908) (61066b5)
- unify environment-build failure handling across engine and diagnostic surfaces (#856) (c9d5ba1), closes #850
⚡️ Improvements
🐛 Bug Fixes
- a name-matched feature never validated its option values (#732) (#739) (2c25552)
- add context to terse core error messages (246a48f), closes #749
- align environment-build failure semantics between engine and resolve_feature (#790) (#793) (9a515d6)
- align sklearn feature_engineering pipeline with runtime dispatch (4a19f18), closes #797
- attach the union of divergent sibling filter matches instead of aborting (#989) (83ec710), closes #965
- avoid duplicate feature group upload (65c713c)
- bind a RIGHT join's merge arguments to the declared sides (#1097) (749ddb2)
- cheapen the registry-isolation teardown, run the fixture-blocked doc examples, contain three log sites (#1001) (d1a190c), closes #993 #995
- ci: give the tox step a budget the suite can grow into (#1095) (9909463)
- close the abstract-only failure with debug pointers and correct doc-taught values (#1008) (6a379f3)
- contain a raising match hook during filter matching (#929) (b4f0e74), closes #899
- contain a raising match hook instead of failing every feature (#897) (410ff53)
- contain every option-value rejection path so a raising validator cannot abort identification (#779) (0faffd6)
- contain malformed prefix patterns (fa01561)
- contain universal-matcher probe leaks in tests and logging (#842) (d920e34)
- cross-framework joins under MULTIPROCESSING in the transform hop (6e12a48)
- dedupe filter drop reports by message (488925a)
- degrade contained parse errors to no_match instead of vetoing the match (92b06da), closes #868
- detect a worker that exits while steps are still assigned to it (0e24a44), closes #1087
- do not instantiate abstract feature groups in the default matcher (#692) (#711) (f10ab0b)
- drop undeclarable umap algorithm from DimensionalityReductionFeatureGroup (#696) (#709) (1e31577)
- enforce required_when whatever matcher a feature group keeps (#731) (#740) (10ad2f8)
- escalate the option-write conflict during reader selection (#966) (f52a218)
- expand one-to-many joins in PythonDict merge engine (8324677)
- feature_chainer: count the name-carried sources in the in_feature gate and keep a guard raise as text (#973) (71864a5)
- filter on a root FG's own output column crashes or drops the requested column (#712) (#715) (c93e45d)
- gate filter matching on the declared feature_group scope (#1046) (a320b28)
- gate name-based matching behind an owned reader veto (#960) (b444d14)
- give each JoinStep its own completion token (#1107) (50134b7)
- give every bundled compute framework one backend-import policy (#736) (#744) (a7e51cf)
- give GlobalFilter its own copy of every filter Feature it stores (#1037) (1301fbc)
- give the handler scans one notion of what leaves a handler (#1028) (37cb853), closes #999
- guard plugin-owned exception and Domain.name reads from escaping degraded reads (2cc3046)
- guard the deep hashers against cyclic option containers (#996) (34583ff)
- guard the match-abort escalation sweep so it cannot rot silently (#930) (02ed04d)
- harden multiprocessing runtime against stale drop signals and dead workers (1577a1f)
- judge the links gate for candidates that never matched the name (#963) (86a0e38)
- judge the name-blind gates for candidates that never matched the name (#950) (3931595)
- keep contained raises out of log records at seven sites (#987) (3231f51)
- keep filter feature matching working while detaching the host's Features (#1062) (da837ca)
- keep one link orientation across all groups sharing a link (#1038) (a77ab64)
- key the dropped-filter ledger on the declaring filter (#1096) (0df8489), closes #1074
- let a Feature copy own its compute_frameworks set (#956) (ccf06a8), closes #924
- let only one step at a time occupy a compute framework (051c3ca)
- let property_spec express an optional key with a None default (#733) (#738) (0b1b9c3)
- let SingleFilter own the feature it was built from (#923) (391140c)
- make an unresolvable in_features value a plain non-match (#941) (6e2fe60)
- make compute framework selection deterministic during planning (#1056) (c90aa8e)
- make differing-key joins conform across compute frameworks (#838) (603d596)
- make engine and resolve_feature failure paths single-pass and exactly-once (#782) (467e8a7), closes #791
- make GlobalFilter return identical rows across compute frameworks (#829) (376fb28)
- make graph traversals iterative and memoized (#837) (e808119)
- make option equality cycle-safe and release the caught exception (#1009) (1820449)
- make pyarrow import guards robust to pyarrow dropping py.typed (57d14df), closes apache/arrow#48970
- make time-window features respect time_unit (#816) (def1733)
- match a never-forwarded option key in either key form (e1d6b21), closes #1062
- match the nested-feature key through DefaultOptionKeys, not a literal (d1a94ad), closes #1063 #1067
- materialize PropertySpec defaults at the central post-resolution boundary (#796) (#803) (f61ee5b)
- name the link and both frameworks when an APPEND or UNION invariant breaks (#1051) (7d2a8b7)
- name the plugin whose framework declaration aborted the environment build (#841) (600e248)
- name the requested key and held keys in FlightServer.do_get errors (d2f679a), closes #1112
- name the value behind an unusable option, filter key and doc example (#972) (15727e0), closes #942
- one name source for SingleFilter and a rehash of GlobalFilter's stored filter sets (#1004) (a761938)
- options: NON_FORWARDED_KEYS stores the key string, not the enum member (8d4721a), closes #1063
- per-instance ComputeFramework uuid and no-arg constructible base (#701) (ecf479a)
- preserve both keys in polars joins with differing key names (49b0b9d)
- process nested feature dicts on the group_options/context_options branch (#681) (517aa7f)
- raise on a stalled scheduler instead of hanging (#1086) (f0c1d1f)
- read the match hook return by truthiness on both seams (#988) (13f5d04), closes #927
- record why each filter probe lost and name the nearest miss (#1073) (8944caa)
- rehash planned-queue feature sets after compute framework rewrite (#983) (30534ef)
- reject a link resolution the feature does not declare (48aff83), closes #1078
- reject a link that joins in a framework none of its children run in (#1034) (b981522)
- reject a reserved reader key declaration that drops framework_set (#1018) (615ed72)
- reject allow_explicit_none on a framework_set reader option (#979) (c5e2365)
- reject mis-wrapped top-level credentials instead of iterating them (#703) (851d0ba)
- reject unhashable filter parameter values at construction (#925) (#958) (a44107b)
- render a near-miss line for an unlabeled elimination stage (#947) (71fea54)
- report filter option divergence only for filters that attach (#969) (692fefd)
- report the in_features collision the same way on all three option containers (#681) (8642557)
- report when a FeatureSet filter was declined by one of its features (#964) (de0ca0d)
- require every discriminator key to match, not just one (48f69b5), closes #1129
- resolve compute frameworks by group agreement instead of an arbitrary set representative (#1011) (4271339)
- resolve_feature can return a falsy empty error string (#852) (#861) (4fe81ce)
- resolve_feature delegates matching to the engine seam (#755) (#780) (f58f57a)
- resolve_subtype handles compiled PREFIX_PATTERNs instead of failing open (#783) (55f15a3)
- restamp an owned reader content decline as an owned veto (#961) (#1005) (b3d5090)
- run filter-feature intake before the filter enters the collection (#909) (d771b75)
- satisfy ruff and mypy gates for feather ipc reader (81dee90)
- scope the owned reader restamp to the addressed probe (#1017) (0ce6fd7), closes #1006
- security: quote SQL identifiers in SQLITEReader.build_query to prevent injection (650b8b4)
- share one criteria probe across both match seams (#1068) (ebbb3b5)
- skip non-forwarded option keys when unifying filter feature options (84ddb8f)
- stop asking a candidate its name where the answer cannot be contained (#1104) (79f36d2)
- stop forecasting output being discarded as all-NaN (90e10e4)
- stop the resolution hint suggesting names that cannot resolve (#946) (21e9c2f)
- stop the resolution hint suggesting the name that was asked for (#918) (3512e4e)
- stop warning about a filter option divergence intake erases (#922) (9990c97)
- take render_resolution_failure from its owning module (#914) (941bcc6)
- tests: match GitHub's zero-directory globstar in the CI paths-ignore guard (0e37a2f)
- tests: rebuild the sklearn specs with the required_when they now carry (d63c65a), closes #738 #740
- thread the deep cycle guards through a nested Options/HashableDict (#1014) (dea3571)
- unpack singleton collections in the forwarded-name mismatch guard (773e06d), closes #764
- validate nested in_features dicts through FeatureConfig (#680) (#708) (c9413eb), closes #681
- validate reader option declarations that reach the merge unguarded (ce4b50e)
- validate the filter framework pin and narrow it by the capability hook (bc1d077)
- validate the single-compute-framework pin before matching runs (#851) (#859) (7e9c1cd)
- wire the surviving transform hop uuid on an add_tfs dedup (#1140) (9cfba9e)
👷 CI
- allow the SPDX license strings the locked versions declare (f7ae94f)
- install tox envs from uv.lock so the gate is reproducible per commit (ae57261), closes #806
📚 Documentation
- align resolution troubleshooting and integration docs with the single matcher (#758) (535b5fb), closes #755 #756 #757 #722
- bless the resolution test seam and pin its contract (os-014) (21e9b74)
- close plugin README directory fence (b56a77b)
- cover the no-feature-groups-found failure on the troubleshooting page (#986) (b04beed)
- fix non-runnable FAQ code snippet (ec45194), closes #748
- name the domain and framework gates as filter policy, not shared (3ccd88e)
- refresh memory bank for PropertySpec hardening (os-007) (802a909)
- refresh the stale directory tree in tests/README.md (d87777a), closes #935
- remove en and em dashes (#747) (e53cd8e)
- remove stale memory-bank references and clarify the transient todo.md (388508c), closes #878
- retire memory-bank after folding its last unique facts into docs (f8621d0), closes #761
- separate the shared filter matching gates from filter policy (da067f1)
- slim memory bank to systemPatterns and activeContext (3ba4e99)
- sqlite: correct the injection test's mechanism and note the quoting trade-offs (9caea51)
- state that match_feature_group_criteria's options view depends on the caller (#903) (0f69455)
- state the real filter-to-FeatureGroup matching rule (#917) (0e4a5e1)
- state the real states of features.filters (#938) (f2b6b2c), closes #920
🔧 Miscellaneous
- deps: bump nltk in the uv group across 1 directory (dbcbf98)
- deps: bump pymdown-extensions to 11.0.1 via uv override (286eea8)
- remove dead debug comments from core planner (82e438f)
✅ Miscellaneous
- anchor the documentation tests to the repo root (a5505d1), closes #937
- assert every docs page is reachable from the mkdocs nav (4bd0e76), closes #934
- attribute plugin-raised importable mloda error types to the plugin (#795) (77ffefd)
- characterize the link planner side binding and orientation (#1077) (badffc5)
- close the relative-import and markdown gaps in the matcher import sweep (#951) (d98f6cb)
- collapse the enum-swept duplicate test bodies into parametrized tables (#1083) (20c12dd)
- collapse the match-abort sweep snippets into parametrized tables (#1058) (29914a0)
- consolidate PROPERTY_MAPPING suites around a public behavior matrix (#799) (8aec943)
- cover execution plan error branches (5919a5a)
- declare the renderer's plugin stubs through the shared factory (#1076) (93409bd)
- drop the stale fork DeprecationWarning filter from pytest.ini (#919) (6c7a1db)
- give the compatible-dtype case a real dtype difference (9ab2f81), closes #1085
- guard first-party plugin classes against silently leaving the registry (#735) (#743) (ddc0b8a)
- guard the handlers between a marked raise and the match seam (#976) (e4cc9ff), closes #931
- isolate doc-example snippets from leaked test feature groups (#833) (43e95b0), closes #828 #583
- make an uncollected doc snippet a failure instead of a silent skip (#992) (ce5bc4f)
- make test_stream_run.py resolve its feature group standalone (a331f9e), closes #1084
- make the optional-option pin drive the sweep instead of mirroring it (#725) (1fcdac5)
- pin CLAUDE.md and AGENTS.md identical (#906) (8c6adc4)
- pin declared value spaces against their dispatch branches (1e55ddc), closes #797 #797 #797 #774
- pin filter matching semantics against the canonical resolver (#1032) (8b7850b)
- pin that filter criteria matching observes effective options (#887) (e6f7724)
- pin the near-miss stage labels reproduced in the published docs (#970) (f8983c7)
- pin the rendered near-miss label of every elimination stage (#955) (ec42845)
- pin the rendered-line dedupe and the ledger it must not reach (edd7861)
- pin the str mixin the rest of the DefaultOptionKeys call sites rely on (cc99e3d)
- pin the three verified resolution false positives with paired engine/debug tests (#753) (534ba39), closes #755
- resolve subtype docs by class identity, not by name (237e99f)
- run the near-miss label guards in the default tox env (e08fbf1), closes #1082
- share the throwaway plugin stubs behind one helper (#1071) (12dd6cb)
- sweep optional-option omission across every shipped plugin (#725) (cf2b400)