# Verification Report — merge sprint end-to-end test **Date**: 2026-05-22 **Branch**: `merge/import-donor` **Tip**: `e376b24` This document records the **actual** end-to-end verification of the merge sprint, not just per-commit `n4m_tests` passing. ## TL;DR | What | Status | Evidence | |---|:---:|---| | pls4all C++ build (dev-release preset) | ✅ green | `cmake --build --preset dev-release` clean | | pls4all C++ test suite | ✅ green | `n4m_tests` reports `265 run, 0 failures, 0 skipped` | | pls4all CLI selfcheck | ✅ green | `n4m_cli --selfcheck` returns `selfcheck OK` | | Catalog validation | ✅ PASS | 160 unique methods, 2 active subset closures non-empty | | License audit | ✅ PASS | 2 vendored entries, 10 referenced libs, all with LICENSE files | | Benchmark matrix (pls4all-side 73 methods) | ✅ runs | smoke test 4 algos × 10 backends = 40 cells, all OK | | Benchmark matrix (donor 87 methods) | ❌ MISSING | `KeyError('unknown method: snv')` — donor methods not in `benchmarks/parity_timing/registry.py` | | Donor C++ tests (`n4m_tests`) | ❌ NOT BUILT | donor build wiring deferred to M5+M6 focused session | | Python `nirs4all_methods` package | ⚪ scaffold | `__init__.py` raises ImportError until M9 lands build glue | | Python slim `pls4all` package (new) | ⚪ scaffold | `__init__.py` raises ImportError until M10 lands | | R `n4m` + `pls4all` packages (new) | ⚪ scaffold | DESCRIPTION + README only, no `src/libn4m_*/` build yet | | cibuildwheel matrix | ⚪ scaffold | `release-wheels.yml` gated `if: false` | | ABI rename `n4m_*` → `n4m_*` | 🟡 deferred | script `migrate_p4a_to_n4m.py` ready, execution requires M6 header merge first | ## Evidence — what actually works ### pls4all C++ build + tests (full evidence) ``` $ cmake --preset dev-release -- Configuring done -- Generating done -- Build files have been written to: build/dev-release $ cmake --build --preset dev-release -j 4 ninja: no work to do. # post M0-M17, no rebuild needed since pls4all CMakeLists unchanged $ ./build/dev-release/cpp/tests/n4m_tests === 265 run, 0 failures, 0 skipped === $ ./build/dev-release/cpp/cli/n4m_cli --version pls4all 0.97.3+abi.1.16.0 $ ./build/dev-release/cpp/cli/n4m_cli --selfcheck selfcheck OK ``` ### Catalog gate ``` $ python catalog/scripts/validate_catalog.py == validate_catalog.py == repo=/home/delete/nirs4all/pls4all methods.yaml — 160 entries methods.yaml OK (160 entries, 160 unique ids) subsets — 7 descriptors aompls.yaml: status=planned (closure check skipped) n4m_transfer.yaml: status=planned (closure check skipped) nirs4all_augmentation.yaml: status=planned (closure check skipped) nirs4all_methods.yaml: closure OK (160 methods after excludes) nirs4all_preprocessing.yaml: status=planned (closure check skipped) nirs4all_selection.yaml: status=planned (closure check skipped) pls4all.yaml: closure OK (68 methods after excludes) -- PASS: catalog validation green ``` ### License audit ``` $ python scripts/audit_third_party_licenses.py --check PASS: license audit clean (2 vendored entries, 10 reference libs) ``` ### Benchmark matrix — pls4all side Smoke run: 4 algorithms (`pls`, `simpls`, `opls`, `cars_select`) × 10 backends (`registry_pls4all`, `cpp`, `python_tier1/2`, `r_tier1/2`, `r_pls_compat`, `r_mdatools_compat`, `matlab_tier1/2`) × 30×30 dataset = **40 cells, all green**. Full CSV at `docs/merge/verification/smoke_pls4all_4algos.csv`. Sample row (SIMPLS C++ binding): ``` algorithm,backend,language,tier,ok,median_ms,parity_ok pls,cpp,C++,canonical,True,0.45,True simpls,cpp,C++,canonical,True,0.43,True opls,cpp,C++,canonical,True,2.10,True cars_select,cpp,C++,canonical,True,0.70,True ``` This proves the **pls4all benchmark layer rebuilds end-to-end** after the merge: methods register, backends dispatch, parity oracles compute, CSV writes. ## Evidence — what is genuinely deferred ### Benchmark matrix — donor side: `KeyError` ``` $ python benchmarks/cross_binding/orchestrator.py \ --algorithms snv --sizes 30x30 --threads 1 \ --libn4m-build dev-release --n-runs 2 --only-pls4all \ --out-csv /tmp/donor_smoke.csv --force [1] snv 30×30 t=1 dev-release registry_pls4all: WARN: could not build BENCH_R_PARAMS_JSON for snv: KeyError('unknown method: snv') FAIL — unknown algo failed: invalid argument [... all 10 backends report the same KeyError ...] ``` **Root cause**: `benchmarks/parity_timing/registry.py` is pls4all's canonical method catalog. It declares **73 PLS-family methods**. The **87 donor methods** (preprocessing, augmentation, splitters, filters, utilities) are catalogued in `catalog/methods.yaml` (160 total) but were never added to `benchmarks/parity_timing/registry.py`. This is the exact gap that `docs/merge/M7/STATUS.md` lists as deferred: > "Bring donor methods into `benchmarks/parity_timing/registry.py` — ❌ deferred" **To close this gap**, a focused M8 session must: 1. Read the 87 donor methods from `catalog/methods.yaml`. 2. For each, add a `MethodSpec("...", category=..., reference_factory=...)` entry to `benchmarks/parity_timing/registry.py`. 3. Reference factories typically delegate to `scipy`, `pybaselines`, `pywt`, `sklearn`, or selected `nirs4all` operators (per the `parity/donor_imports/divergences.json` policy). 4. Re-run the orchestrator to populate `docs/_static/bench-data.json`. Until then, `pls 4all` methods benchmark; donor methods do not. ### Other deferred items (covered in `docs/merge/README.md`) - **M4** function split (39 stubs created, extraction recipe documented, surgery not executed) - **M5+M6 paired session** for ABI rename + header layout - **M9+M10+M11** package activation (catalog/scripts/render_subset.py exists but vendored-source pipeline not wired) - **M11** CRAN cleanup pass (existing 1 WARNING + 4 NOTEs from M0 still need to be fixed before submission) - **M13** secondary binding refresh (12 pls4all bindings still use `n4m_*` symbols — refresh after M5 lands) - **M15** cibuildwheel matrix activation (workflow gated `if: false` until M9+M10 land their build glue) ## Cleanup performed during this verification | Path | Before | After | |---|---|---| | `/home/delete/nirs4all/M0/`, `/M1/`, `/M2.5/`, `/M3/`, `/M4/`, `/M5/`, `/M7/`, `/M16/`, `/M17/` | 9 working dirs duplicating `docs/merge/`-committed files | ❌ deleted (content lives at `docs/merge/M{N}/` in-repo) | | `/home/delete/nirs4all/MERGE_ROADMAP.md` | working file at user's home | ❌ deleted (committed at `docs/merge/MERGE_ROADMAP.md`) | | `/tmp/m2_placeholder_writer.py`, `/tmp/m4_stub_writer.py`, `/tmp/m6_header_stubs.py`, `/tmp/summarize_closure.py` | one-shot scripts dropped during the sprint | ❌ deleted | | `/tmp/merge_smoke.csv`, `/tmp/donor_smoke.csv`, `/tmp/pls4all_4algos.csv` | benchmark smoke artifacts | moved to `docs/merge/verification/smoke_*.csv` then `/tmp` copies deleted | | `benchmarks/cross_binding/data/.reference_oracles/` (23 MB, ~600 untracked files) | leftover from codex/parity-30x30 checkout | ⚪ kept — real benchmark oracles, useful for fast local re-runs; protected by `.gitignore` (added in commit `879691b`) | | `benchmarks/cross_binding/data/.x_target/` (620 MB) | leftover generated arrays | ⚪ kept — same rationale, protected by `.gitignore` | ## Honest summary The merge sprint delivered: - ✅ Real unification of the source tree (222 donor sources lifted to category paths, blame preserved) - ✅ Common-core consolidation (M2.5: 31 files unified) - ✅ A working catalog of 160 methods with 2 active subset closures - ✅ License audit + NOTICE.md + THIRD_PARTY_LICENSES.md - ✅ pls4all C++ + benchmark matrix unchanged at every commit (265/265 green, 40-cell smoke run OK) - 🟡 Scaffolds for M4 (39 stubs + recipe), M5 (rename script), M6 (11 headers), M9/M10/M11 (4 packages), M15 (release workflow), M16+M17 procedures The merge sprint did NOT deliver: - ❌ Function-level extraction of model.cpp + extra_pls.cpp into the M4 stubs (the 39 stubs are placeholders) - ❌ ABI rename `n4m_*` → `n4m_*` (script ready, execution paired with M6) - ❌ Donor methods in `benchmarks/parity_timing/registry.py` (87 methods catalogued but not benchmarked) - ❌ Donor C++ tests (`n4m_tests`) compiled and runnable from pls4all's build - ❌ Python/R packages actually building from vendored sources - ❌ A unified bench-data.json that covers all 160 methods The unfinished items are explicitly tracked in `docs/merge/README.md` and the per-phase STATUS docs. They are scoped for follow-up focused sessions per the roadmap budget of 8–12 weeks (this session covered the first ~3-4 weeks of mechanical/scaffold work). **For v1.0.0 release**, the M5+M6 paired session is the critical next step — it unblocks everything downstream (M4 extraction, M8 unified benchmark, M9-M11 packages, M13 binding refresh).