0004 — Pin-and-hold conda-lock with an isolated tMAVEN sidecar lock
- Status: accepted
- Date: 2026-06-24
- Deciders: bioedca
- PRD anchor: §4.1 (stack), §4.3 (sidecar); NFR-REPRO
- Milestone: M0
Context and problem statement
tMAVEN pins numpy<2 + PyQt5, while Tether's GUI is PySide6 on current numpy
with a Numba-bounded numpy ceiling. These cannot coexist in one environment. How
do we get reproducible builds across three OSes without a dependency conflict?
Decision drivers
- Numba constrains the base numpy upper bound; the GUI stack (napari/PySide6/ pyqtgraph/scikit-image) is tightly coupled to that numpy + Qt.
- tMAVEN's
numpy<2/PyQt5 must never contaminate the base stack. - Reproducibility requires exact, frozen pins — not track-latest solving.
Considered options
- A. Two isolated
conda-lockstacks — a base lock (PySide6/current numpy) and a separatesidecar/conda-lock.yml(numpy<2+ PyQt5 + trimmed tMAVEN deps); pin-and-hold, frozen per release, bumped only deliberately. The sidecar runs out-of-process. - B. One environment with pinned conflicting deps (impossible).
- C. Track-latest with CI catching breakage.
Decision outcome
Chosen option: A. The two locks are the single source of truth and stay
isolated; conda-lock-verify checks both. The sidecar trims tMAVEN's
install_requires (omit biasd, bound numba) so it can ship offline (M9).
Consequences
- Good: reproducible, conflict-free, offline-bundleable; no silent drift.
- Trade-off: dependency bumps are deliberate work (regenerate + verify both
locks); Dependabot covers only
pip+github-actions, sodeps-audit.ymlbackstops the conda locks. - Follow-up: PLAN invariant — "never casually bump a conda-lock; keep the sidecar isolated".
Amendments
2026-07-06 — Maintainer-approved base-lock bump (M5 ranker deps)
This ADR's "bumped only deliberately" clause was exercised for the first time.
Maintainer bioedca authorized adding scikit-learn + xgboost to the base
environment.yml for the M5 per-condition quality ranker (PRD §7.5, PLAN §9 M5),
and the base conda-lock.yml was regenerated for all four platforms
(linux-64/osx-64/osx-arm64/win-64) with conda-lock==4.0.1. The resolved stack
holds numpy at 2.1.3 — inside the Numba <2.2 window — with scikit-learn 1.9.0
and xgboost 3.3.0; the drifted GUI stack was re-verified (headless pytest-qt
suite green + a real-GL desktop smoke). The sidecar lock was not touched.
This approval is scoped to this single re-lock; it does not standing-authorize future base-lock bumps, which remain deliberate, per-change decisions.
2026-07-09 — Maintainer-approved base-lock bump (M6 plot-export backend)
The "bumped only deliberately" clause was exercised a second time. Maintainer
bioedca authorized adding matplotlib-base>=3.9 to the base environment.yml
for the M6 plot vector-export path (PRD §7.9, PLAN §10 M6 PR-8; ADR-0044 records
the backend-choice rationale). The base conda-lock.yml was regenerated for all
four platforms with conda-lock==4.0.1; the resolved drift is minimal — only
matplotlib-base 3.11.0 plus its font/plotting transitive deps were added, and
every core compute/GUI pin held (numpy 2.1.3, numba 0.61.2, napari 0.5.6, pyside6
6.11.1, pyqtgraph 0.14.0, scikit-learn 1.9.0, xgboost 3.3.0). matplotlib-base
(not full matplotlib) is deliberate: it ships the headless Agg/PDF/SVG file
backends with no Qt/Tk toolkit, so no PyQt binding enters the PySide6 base stack.
The drifted stack was re-verified — matplotlib's pdf/svg/agg backends plus the
full GUI/compute stack import clean from the regenerated lock. The sidecar lock
was not touched.
As with the M5 bump, this approval is scoped to this single re-lock and does not standing-authorize future base-lock bumps.
More information
PRD §4.1, §4.3, §12.8; ADR-0006 (sidecar mechanism).