fix: refuse to start a run on an uncalibrated embryo - #154
Merged
pskeshu merged 1 commit intoSep 5, 2026
Conversation
Nothing checked. `POST /api/devices/timelapse/start` validated
`interval_seconds > 0` and that the embryo ids existed, and started.
The acquisition layer did not fail either. It invented the geometry, and said
so in a comment:
# Get calibration parameters (use defaults if not calibrated)
galvo_amplitude = cal.get("galvo_amplitude", 0.5)
galvo_center = cal.get("galvo_center", 0.0)
piezo_amplitude = cal.get("piezo_amplitude", 25.0)
piezo_center = cal.get("piezo_center", 50.0)
slope = cal.get("slope_um_per_deg", 100.0)
So an adaptive timelapse could run to completion on embryos that had never
been calibrated, on five literals, and report success. The resulting volumes
are indistinguishable from real ones, which is the whole problem — a refusal
gets noticed, silently invented scan geometry does not.
It also inverted the ordering the team stated out loud on 2026-08-07. Ryan:
"the main thing is just making sure that we can get the calibration to work".
Kesavan: "Calibration has to work. Embryo navigation has to work. Then
timelapse setup has to work." The workflow has a hard dependency; now the code
has one too.
`gently/harness/calibration_gate.py` holds the single predicate, so the answer
cannot differ between the surface an operator drives and the tool an agent
calls. Calibrated means a finite non-zero `slope_um_per_deg` — the field every
successful fit writes (`calibration_tools.py:891`) and the number the scan
geometry is derived from.
Guarded: `timelapse/start`, `operate/run-tactic`, `acquire/volume`, and the
`acquire_volume` agent tool. Server-side on purpose: a check in operate.js is
a check the agent walks past. 409 rather than 400 — the request is well formed,
the instrument is not in a state to honour it.
JUDGEMENT CALLS, flagged for reversal:
1. **Refuse rather than warn**, with `allow_uncalibrated=true` as an explicit
escape. Someone who means it can say so; the point is that they have to say
it. A warning in a toast is not a decision anyone records.
2. **Fit quality is not judged.** `r_squared` is hardcoded to 0.85 on the
vision-guided path (`calibration_tools.py:903`), so it is not currently a
measurement — a threshold against a literal would be theatre. Presence is
checked; quality is left for the operator. Worth its own issue.
3. **`embryo_id` on `acquire/volume` is optional.** Manual-mode snapping images
the current stage position with no embryo in mind, which is legitimate and
has nothing to check against. Operate's single mode now sends it, so that
path is covered.
Which incidentally closes half of the audit's finding 5: the route previously
received no embryo id at all, so it imaged wherever the stage happened to be
and could not have checked anything even if it had wanted to.
21 tests: the predicate against partial calibrations, zero and junk slopes, and
the routes against the refusal, the override, the null-roster case, and that a
bad interval is still a 400 rather than masked by the gate.
Refs the audit in docs/devices-tab-audit.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Judgement calls, flagged for reversal
allow_uncalibrated=trueas an explicit escape. Someone who means it can say so; the point is that they have to say it. A warning in a toast is not a decision anyone records.r_squaredis hardcoded to0.85on the vision-guided path (calibration_tools.py:903), so it is not currently a measurement — a threshold against a literal would be theatre. Presence is checked; quality is left to the operator. Worth its own issue.embryo_idonacquire/volumeis optional. Manual-mode snapping images the current stage position with no embryo in mind, which is legitimate and has nothing to check against. Operate's single mode now sends it, so that path is covered.What was wrong
Nothing checked.
POST /api/devices/timelapse/startvalidatedinterval_seconds > 0and that the embryo ids existed, and started.The acquisition layer did not fail either. It invented the geometry, and said so in a comment:
So an adaptive timelapse could run to completion on embryos that had never been calibrated, on five literals, and report success. The resulting volumes are indistinguishable from real ones — which is the whole problem. A refusal gets noticed; silently invented scan geometry does not.
It also inverted the ordering stated out loud on 2026-08-07. Ryan: "the main thing is just making sure that we can get the calibration to work". Kesavan: "Calibration has to work. Embryo navigation has to work. Then timelapse setup has to work." The workflow has a hard dependency; now the code has one too.
The gate
gently/harness/calibration_gate.pyholds the single predicate, so the answer cannot differ between the surface an operator drives and the tool an agent calls. Calibrated means a finite non-zeroslope_um_per_deg— the field every successful fit writes (calibration_tools.py:891) and the number the scan geometry derives from.Guarded:
timelapse/start,operate/run-tactic,acquire/volume, and theacquire_volumeagent tool. Server-side on purpose — a check inoperate.jsis a check the agent walks past.409, not400: the request is well formed, the instrument is not in a state to honour it.This also closes half of audit finding 5:
acquire/volumepreviously received no embryo id at all, so it imaged wherever the stage happened to be and could not have checked anything.Tests
embryo_ids: nullmeaning the whole roster, the ungated no-id case, and that a bad interval is still a400rather than masked by the gate.Refs
docs/devices-tab-audit.md.