ADR-04: Tag / exclude mask materialization¶
Status: accepted (2026-05-28) Blocks: workstream E (bridge data-provider), workstream G (lineage taxonomy).
Context¶
nirs4all’s {"tag": filter} adds a column without removing samples. {"exclude": filter} removes samples from training. {"exclude": [filter_a, filter_b], "mode": "any" | "all"} produces a union or intersection mask. The bridge must reproduce and audit the resulting masks so leakage refusal still applies.
Decision¶
The bridge pre-computes masks in the adapter layer (i.e. inside nirs4all/bridge/dag_ml_provider.py), then ships the materialized mask + an audit record to dag-ml-data. The materialization itself is offline; the audit is part of the lineage envelope.
Mask materialization contract¶
The adapter walks every
tag/excludestep at compile time, calls the corresponding filter operator’sfit_predicton the training partition, and records the resulting boolean mask in theSampleRelationTableas a sample-level metadata column (__tag_<tag_name>__/__exclude_<mode>_<hash>__).Multi-filter
excludemasks are materialized via Python set ops (mask_any = mask_a | mask_b,mask_all = mask_a & mask_b). The combined mask carries the canonical key__exclude_{mode}_{sha256(sorted(filter_ids))[:12]}__.tagmasks are additive — they create new metadata columns but never alter the train/test partition.excludemasks setpartition = "excluded_from_training"for the matching rows; the rows remain in the dataset for prediction.
Lineage audit fields (ADR-11 / ADR-12)¶
Every materialized mask is recorded in the lineage envelope with:
expression — the original Python expression (operator class name + constructor args).
mode —
"single"/"any"/"all"forexclude.source_columns — which columns the filter consumed (X, y, metadata key).
sample_id_set — the list of
SampleIdvalues the mask flagged (full list, not just count, so audits are reproducible).mask_fingerprint — SHA-256 of the canonical mask blob; lets the bridge’s bundle replay assert masks are identical.
Consequences¶
The bridge’s data provider materializes masks before the DAG runs; the runtime never re-computes them.
dag-ml-data’sSampleRelationTablelearns anMaskAuditRecordreference per materialized mask.The lineage taxonomy (ADR-11) carries
mask_*fields that survive RO-Crate / PROV / OpenLineage export.“Tagged but not excluded” remains expressible — downstream
branch.by_tagconsumes the tag column without partitioning fixed rows.
Open follow-ups¶
The parity oracle’s
exclude_multi_any_y_and_xcase skips today becausesample_data/regressionis too small for a 2-filter UNION exclusion to leave a viable train set. Pickregression_2once available — fixture-size, not contract.