# M7 + M8 โ€” parity gate + dashboard unification: STATUS **Status**: ๐ŸŸก **partial โ€” donor infrastructure lifted under unified paths; full runner merge deferred** **Date**: 2026-05-21 **Pushed**: pending commit ## What was done ### M7 โ€” parity infrastructure lift Donor's `_donor/nirs4all-methods/parity/` was relocated to `parity/donor_imports/` so it sits alongside pls4all's existing `parity/{schema,fixtures,python_generator,r_generator}` tree, while keeping the two distinct (no namespace collision yet). | Origin | Target | Files | |---|---|--:| | `_donor/.../parity/run_reference_parity.py` | `parity/donor_imports/run_reference_parity.py` | 1 (107.8 KB; the 5-stage gate driver) | | `_donor/.../parity/binding_parity.py` | `parity/donor_imports/binding_parity.py` | 1 (the Stage 4 scaffold) | | `_donor/.../parity/divergences.json` | `parity/donor_imports/divergences.json` | 1 (machine-readable skip registry, 3 classes) | | `_donor/.../parity/nirs4all_source.py` | `parity/donor_imports/nirs4all_source.py` | 1 | | `_donor/.../parity/reference_parity.py` | `parity/donor_imports/reference_parity.py` | 1 | | `_donor/.../parity/python_generator/` | `parity/donor_imports/python_generator/` | full subtree (locked env + adapters for SciPy/sklearn/pybaselines/PyWavelets/nirs4all) | | `_donor/.../parity/fixtures/` | `parity/donor_imports/fixtures/` | full subtree (~250 JSON fixtures) | | `_donor/.../parity/schema/` | `parity/donor_imports/schema/` | `n4m_fixture_schema_v1.json` | | `_donor/.../parity/tolerances.md` | `parity/donor_imports/tolerances.md` | 1 (the n4m-side tolerance table) | | `_donor/.../parity/README.md` | `parity/donor_imports/README.md` | 1 | ### M8 โ€” benchmark dashboard portability Donor's `_donor/nirs4all-methods/benchmarks/` similarly relocated: | Origin | Target | Files | |---|---|--:| | `_donor/.../benchmarks/cross_binding/` | `benchmarks/donor_imports/cross_binding/` | subtree (orchestrator, results, validate_dashboard_payload.py) | | `_donor/.../benchmarks/reference_snapshots/` | `benchmarks/donor_imports/reference_snapshots/` | subtree | | `_donor/.../benchmarks/validation/` | `benchmarks/donor_imports/validation/` | subtree (the v0 registry/{datasets,suites,references,โ€ฆ}.json) | | `_donor/.../benchmarks/benchmark_registry.json` | `benchmarks/donor_imports/benchmark_registry.json` | 1 (donor's scaffold registry) | | `_donor/.../benchmarks/check_truth_sources.py` | `benchmarks/donor_imports/check_truth_sources.py` | 1 | | `_donor/.../benchmarks/truth_sources.lock.json` | `benchmarks/donor_imports/truth_sources.lock.json` | 1 | | `_donor/.../benchmarks/README.md` | `benchmarks/donor_imports/README.md` | 1 | ### Dashboard orchestrator de-hardcoded `benchmarks/cross_binding/orchestrator.py:142` was the only hardcoded `/home/delete/...` path Codex flagged (the rest of the 31346 grep hits are inside generated CSV result files, not source). Replaced the hardcoded path with an env-var-driven fallback: ```python PLS4ALL_R_ENV = os.environ.get( "N4M_R_ENV", os.environ.get("PLS4ALL_R_ENV", "/home/delete/miniconda3/envs/pls4all_r"), ) ``` A clean clone now works without editing the file. The maintainer's historical fallback stays for backward-compat. A warning emits if the fallback doesn't exist and `N4M_R_ENV_NOWARN` is unset. ## What is NOT done (carried to focused session) The roadmap M7 requires more than file-relocation: | Sub-goal | Status | |---|---| | Single unified runner driving both halves | โŒ โ€” pls4all has no top-level runner; donor's `run_reference_parity.py` is at `parity/donor_imports/` parked. The merger is M7's main job and lands in a focused session. | | Implement Stage 4 binding parity (donor's `binding_parity.py` is scaffolded but unwired) | โŒ โ€” wiring happens after the runner is unified. | | Merge `tolerances.md` (rename rows, calibrate per category per ยง6.3, add backend rows ยง6.4) | โŒ โ€” two distinct tables coexist (`parity/tolerances.md` from pls4all, `parity/donor_imports/tolerances.md` from donor). | | Port pls4all's `๐Ÿ“œ` documented-but-unwired rows into machine-readable `divergences.json` | โŒ โ€” donor's `divergences.json` exists at `parity/donor_imports/`; pls4all-side rows still in MD only. | | Sync sklearn version (base 1.4 vs donor 1.8.0); regenerate base fixtures | โŒ โ€” needs a fixture regeneration pass in a locked env. | The roadmap M8 requires the same kind of follow-on work: | Sub-goal | Status | |---|---| | Bring donor methods into `benchmarks/parity_timing/registry.py` | โŒ โ€” donor's `benchmark_registry.json` parked at `benchmarks/donor_imports/`; merger deferred. | | Add `category` / `subcategory` / `subset` filters to dashboard UI | โŒ | | Split `bench-data.json` per-category for lazy loading | โŒ โ€” 829 KB โ†’ 2 MB issue remains. | | Adopt `MERGE_ROADMAP.md` ยง6.5 container envs (ci-reference / ci-blas-omp / ci-cuda Dockerfiles) | โŒ | ## Build verification `pls4all` build and tests **unchanged** at this commit: ``` $ cmake --build --preset dev-release && ./build/dev-release/cpp/tests/n4m_tests === 265 run, 0 failures, 0 skipped === ``` The donor infrastructure parked at `parity/donor_imports/` and `benchmarks/donor_imports/` is dormant: pls4all's CMakeLists doesn't activate it and no Python entrypoint references the parked paths. ## Remaining `_donor/` footprint after this commit `_donor/nirs4all-methods/` still contains: - `cpp/include/n4m/` (donor's umbrella header โ€” duplicates the one already at `cpp/include/n4m/` from M2.5; deletion deferred to M5+M6 focused session) - `cpp/src/c_api/` (donor's c_api wrappers โ€” pairs with cpp/include/n4m/) - `cpp/cli/`, `cpp/tests/`, `cpp/abi/` (donor's CLI + test harness + ABI golden symbol tables โ€” useful for the M5+M6 session) - `docs/`, `roadmap/`, `bindings/{python,r}`, `scripts/`, `cmake/`, `.github/` - Top-level config files (ARCHITECTURE.md, CHANGELOG.md, CITATION.cff, README.md, etc.) - Total: ~815 files preserved as donor archeology Final cleanup of `_donor/` lands when the donor repo is renamed in M16 (at which point the prefix becomes redundant โ€” everything is in the unified repo by name).