ADR-01: Compatibility ledger semantics

Status: accepted (2026-05-28) Blocks: workstream B (schema additions), workstream E (bridge), workstream F (parity validation)

Context

The integration replaces nirs4all’s PipelineRunner + SpectroDataset backend with dag-ml + dag-ml-data. “No regression” must be enforceable, not aspirational. Without a written ledger, every B/D/E task can succeed locally and still miss a public behavior — Codex’s #1 pushback on roadmap v1.

Decision

A nirs4all/docs/compatibility.md ledger is the single source of truth for what counts as parity. It enumerates every public API surface with one of the following states:

  • supported — the bridge reproduces the behavior byte-identical (categorical) or within ADR-declared tolerance (numeric).

  • changed — the behavior changes; an entry documents the diff, why, and the migration path.

  • deprecated — kept working for two releases past G6; emits a deprecation warning carrying the target removal version per ADR-14.

  • refused-with-error — explicitly unsupported; the bridge raises a typed error pointing at the migration ADR.

  • migration-path — documented procedure to move a workspace / bundle / pipeline from legacy to dag-ml.

Tolerance ledger

Numeric comparison tolerances are per model class × per metric:

Model class

RMSE / MAE

Notes

Linear (PLS, Ridge, OLS)

1e-6

1e-6

deterministic under fixed seed

Tree ensembles (RF, GBR)

1e-3

1e-3

seed-respecting but library-version-sensitive

Neural networks (NICON, custom TF/PT)

1e-2

1e-2

mini-batch shuffle + numerical reductions

Bit-identical parity is not a goal (Codex hidden risk #4). Tolerance is set per (model class, metric) and pinned in the ledger.

What counts as public API

  • nirs4all.run / predict / explain / retrain / session / generate and their result objects (frozen since 0.9.0 per nirs4all/CLAUDE.md).

  • The pipeline DSL keyword table in nirs4all/CLAUDE.md.

  • Workspace SQLite/Parquet schemas, run manifest layout, .n4a bundle format.

  • The examples/reference/*.py set — examples are API; a broken example is a user-visible regression (Codex hidden risk #5).

  • The CLI subcommand surface (nirs4all workspace | dataset | artifacts | config).

Consequences

  • Every entry in nirs4all/CLAUDE.md’s keyword table needs a ledger row before workstream E (bridge) can ship.

  • The parity oracle test set (nirs4all/tests/integration/parity/) is the executable arm of the ledger — keyword coverage is asserted in test_parity_compiles.py.

  • The bridge raises a typed error referencing this ledger when called on a refused-with-error shape.

  • The compatibility ledger is checked into nirs4all (not dag-ml) so it follows the consumer’s release cycle.

Open follow-ups

  • Generate per-case “legacy-observed” parity manifests (Codex Phase-3 review actionable). A scripted run of every runnable parity case captures exact prediction count / variant count / fold-partition shape / best metric / score keys per case, committed alongside the ledger.