Skip to content

🚀 Version 0.11.0

Choose a tag to compare

@TomKaltofen TomKaltofen released this 17 Aug 11:31
· 49 commits to main since this release

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 raise

Five 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.user and mloda.provider are core-only now: import mloda.user pulls in no backend, and neither facade has a lazy __getattr__ any more (mypy sees real types instead of Any). Every bundled framework is published from one module per backend, and importing such a module never fails when the library is missing; the class reports is_available() == False and 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. ApiInputDataFeature moved into mloda.core and stays exported from mloda.provider.

  • PROPERTY_MAPPING values must be PropertySpec objects. 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 with property_spec(...) nothing changes: the builder returns a PropertySpec. Optionality moved into the default field: an omitted default means required, default=None means optional. PropertySpec, NO_DEFAULT and is_no_default are exported from mloda.provider; a misspelled field is a plain TypeError at 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 default and no required_when no 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>...) in PREFIX_PATTERN, or set deferred_binding=True on 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.add is gone (use add_to_group); get_all_subclasses lost its log_n_subclasses parameter; a captureless PREFIX_PATTERN no longer fabricates an operation token from the name suffix (declare RECOGNITION_ONLY_PATTERN = True on a captureless pattern that also carries a PROPERTY_MAPPING); the undeclarable umap algorithm was dropped from the experimental DimensionalityReductionFeatureGroup; option values are now validated on the string-named path as well, so a wrong value is a non-match there too; required_when is 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 as explain, never raises for a resolution, environment or config failure, returns ResolutionDiagnosis(records, complete, feature_name, failed_result, message).
  • session.resolution_report(): the list[ResolutionRecord] captured while prepare() planned, one per feature, before or after run().
  • FeatureResolutionError (a ValueError subclass) with feature_name, result (the captured EvaluationResult) and partial_records. Importable from mloda.provider, mloda.user and mloda.steward, together with ResolutionDiagnosis and ResolutionRecord.
  • resolve_feature expresses the full request now (options, plugin_collector, feature_group scope, 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 from mloda.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

  • explain and resolved_plan() include the join steps: join_type, join_destination_side, join_inverted, join_token, declared_left_frameworks and declared_right_frameworks. The join decision is materialized as a ResolvedJoin record and the JoinStep is 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 JoinStep gets 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 own compute_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") in calculate_feature returns 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_features still 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=True opts a key into honoring an explicit None (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 (ParsedFeatureName carries the bindings); a positional pattern whose keys share a reachable value is rejected at class definition.
  • is_positive_int is a shared, bool-rejecting predicate exported from mloda.provider; the divergent plugin copies were replaced by it.
  • Class-definition guards: an all-optional universal-matcher PROPERTY_MAPPING warns, a strict default outside its value space raises, element_validator without strict_validation is rejected as dead.
  • BaseInputData readers declare their options with the same PropertySpec, reader-selection rejections share the match-rejection channel with attribution, and reference_time / artifact_storage_path are declared keys.
  • The column-wise hook contract is public: COLUMNWISE_HOOKS, COLUMN_DISCOVERY_HOOKS, missing_columnwise_hooks from mloda.provider, and EmptyResultError states its remedy.

🧹 Filters

  • GlobalFilter returns identical rows across compute frameworks.
  • Filter matching is gated on the declared feature_group scope 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: GlobalFilter copies every filter Feature it stores, SingleFilter owns 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_COMPLETE control 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 sklearn feature_engineering pipeline declaration matches runtime dispatch.

🔒 Security

  • SQLITEReader.build_query quotes the table name and every column identifier. Feature names come from the public Feature(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__ on mloda.user, mloda.provider and mloda.steward.
  • result_rows(result) in mloda.user.python_dict: a tolerant unwrapper that turns PythonDict run_all output (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 ComputeFramework instance 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

  • freeze the imported graph so per-test gc.collect() stops rescanning it (#894) (d9d204b)

🐛 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)