Skip to content

fix: warn on a warm detector stage; stop the source only above ambient - #115

Draft
test1card wants to merge 12 commits into
masterfrom
fix/detector-warmup-warns-source-overtemp-stops
Draft

test1card wants to merge 12 commits into
masterfrom
fix/detector-warmup-warns-source-overtemp-stops

Conversation

@test1card

Copy link
Copy Markdown
Owner

Two different hazards were sharing one interlock row, and one of them was
stopping the source for a condition that is not a source fault.

What was wrong

detector_warmup watched T12 — the second stage of the GM-cooler, where the
detector is mounted — and held stop_source above 10 K. The mock reports 77.5 K
there, so the source could never start at all, and the end-to-end run timed out.

The ruling

"do not retire warmup detector. we should know if t12 (2nd stage temp) is too
high during measurement phase. it just shouldn't turn off source by force"
— owner, 2026-08-28

A warm second stage during a measurement is an evidence problem: it tells
the operator the data he is taking may be worthless, and that information is the
point. An earlier plan to retire the row would have thrown it away.

A heater running away above ambient is an equipment problem.

One warns. One stops. They are separate rows now.

The change

  • detector_warmup keeps its name, its LS218_2 input.4.temperature binding
    and its 10 K threshold. Only its action changes, to an operator warning.
  • source_overtemp is a new row that stops the source above 310 K.

Why 310 K and not "room temperature". A whole-instrument binding thresholded
at 295–300 K would trip forever: the mock reports 300.0 K on channel 8, and a
real ambient-reference sensor sits near 295 K. 310 K is above any room reading,
far below the 350 K emergency tier, and still catches a genuine runaway. An
earlier version of this row shipped threshold: 299.0 while its own comment
said 310 K — that would have replaced one always-on guard with another, which is
the exact failure this row exists to avoid.

The emergency_off rows keep their thresholds, bindings and actions unchanged.

Verification

The lane that produced this was killed before it reported, by a coordinator
error — a pkill -f pattern matched the brief text inside its command line. Its
files survived intact, but its own account of the work did not, so every claim
here was measured by the coordinator from scratch
, not carried over.

In a throwaway worktree at the base commit, with the new test present and the
config change absent: 5 of 7 nodes fail. Applying the config change: 7
pass
. The failing set covers the row being kept as a T12 warning, the warning
dispatching without a control action, warm mock readings not requesting a source
stop, the new row stopping above threshold, and — the one that matters most —
the new row not tripping at the mock's 300.0 K ambient.

Docs gate green at this head; the derived pair was regenerated as the last commit.

Not determined

  • Physical cryostat and heater behaviour. No instrument was touched.
  • The full repository suite; the focused config partition was run.

Disclosure

Written by an AI lane (gpt-5.6-sol) under an agent brief. The lane's own report
was lost, so the verification above is entirely the coordinating agent's.

soak measurement added 4 commits August 28, 2026 03:26
Two different hazards were sharing one interlock row, and one of them was
stopping the source for a condition that is not a source fault.

`detector_warmup` watched T12, the second stage of the GM-cooler where the
detector is mounted, and held `stop_source` above 10 K. The mock reports 77.5 K
there, so the source could never start at all.

The owner ruled the row is KEPT and only its action changes:

  "do not retire warmup detector. we should know if t12 (2nd stage temp) is too
  high during measurement phase. it just shouldn't turn off source by force"

A warm second stage during a measurement is an EVIDENCE problem: it tells the
operator the data he is taking may be worthless, and that information is the
point. Retiring the row would have thrown it away. So the row keeps its name,
its binding and its 10 K threshold, and now raises an operator-facing warning.

The equipment hazard he also named — a source driven above room temperature —
is a separate new row, `source_overtemp`, because one warns and one stops.

Its threshold is 310 K, not "room temperature". A whole-instrument binding
thresholded at 295-300 K would trip forever: the mock reports 300.0 K on
channel 8 and a real ambient-reference sensor sits near 295 K. 310 K is above
any room reading, far below the 350 K emergency tier, and still catches a
genuine runaway. An earlier version of this row shipped 299.0 while its own
comment said 310 K; that would have replaced one always-on guard with another.

The `emergency_off` rows keep their thresholds, bindings and actions unchanged.
@test1card

Copy link
Copy Markdown
Owner Author

@codex review

Head under review: 32472c338570ebd3d8a06cf1329a148900b8d2c0. Please bind your
verdict to that exact SHA.

Please give the threshold your hardest look, because a bad number here is
invisible until it costs a run:

  1. 310 K on source_overtemp. It must sit above every legitimate ambient
    reading and below the 350 K emergency tier. The mock reports 300.0 K on
    LS218_1 channel 8 and a real ambient-reference sensor sits near 295 K. If any
    bound channel can legitimately read above 310 K, this row trips forever and I
    have replaced one always-on guard with another — say so plainly.
  2. The binding. source_overtemp binds the whole LS218_1 set the way
    overheat_cryostat does. Check whether any channel in that set is an ambient
    or reference role that should be excluded rather than thresholded.
  3. detector_warmup must still be a real guard, not a decoration: same name,
    same LS218_2 input.4.temperature binding, same 10 K threshold, and its
    warning must reach the operator rather than a log line.
  4. The emergency_off rows must be untouched in threshold, binding and
    action. I read the diff as comment-only there; please confirm independently.

One thing you should know rather than infer: the lane that wrote this was
killed before it reported
, by my own error. Its files survived; its account did
not. So there is no lane report behind this PR — the verification in the body is
mine, run from scratch in a throwaway worktree. Treat it with the extra suspicion
that deserves, and if you want a measurement I have not shown, ask for it
specifically rather than assuming it was done.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32472c3385

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread config/interlocks.yaml Outdated
- {instrument_id: "LS218_1", source_key: "input.6.temperature"}
- {instrument_id: "LS218_1", source_key: "input.7.temperature"}
- {instrument_id: "LS218_1", source_key: "input.8.temperature"}
threshold: 310.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Raise the threshold above the documented legitimate band

When any bound T1–T4 channel reads in (310, 320] K, or T5–T7 does so during cooldown/warmup, this row performs stop_source even though config/channels.yaml:37-101 explicitly defines those readings as legitimate; the interlock has no phase or source-active predicate and remains latched until acknowledgement. The new 300 K mock test therefore misses the upper 10 K of the repository's accepted range, so 310 K does replace the old guard with another false-trip path unless the threshold is derived above the documented maximum or the binding is narrowed with physical evidence.

AGENTS.md reference: AGENTS.md:L20-L25

Useful? React with 👍 / 👎.

Comment thread src/cryodaq/engine.py
Comment on lines +2418 to +2422
await _dispatch_alarm_notification(
context.event_bus,
context.alarm_dispatch_tasks,
alarm_id=condition.name,
level="WARNING",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route detector warmup into the live annunciation path

When T12 exceeds 10 K in production, this branch only emits a WARNING alarm_fired event and does not pass a Telegram notifier or register an active annunciation. The current GUI no longer consumes the recent_alarms ring (tests/gui/shell/test_top_watch_bar_annunciation.py:16 explicitly enforces that), while the standalone assistant rejects every sub-CRITICAL alarm at src/cryodaq/agents/assistant/live/agent.py:993-1007; consequently the warning can remain a log/ring artifact rather than reaching the operator. Publish it through the current AlarmStateManager/AnnunciationRegistry path or another verified live operator surface.

AGENTS.md reference: AGENTS.md:L20-L22

Useful? React with 👍 / 👎.

Comment thread config/interlocks.yaml
threshold: 10.0
comparison: ">"
action: "stop_source"
action: "warning"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-arm the detector warning after T12 recovers

On a normal warm startup T12 is above 10 K—the new regression itself expects detector_warmup to become TRIPPED—but InterlockEngine._process_reading evaluates only ARMED records and a later below-threshold reading never clears this state. Because no production GUI code invokes the engine's interlock_acknowledge command, cooling the detector leaves this warning permanently disarmed, so a subsequent real warmup during measurement produces no warning at all. Give observational warnings recovery/re-fire semantics, or wire their visible acknowledgement to InterlockEngine.acknowledge, and add a warm→cold→warm regression.

AGENTS.md reference: AGENTS.md:L296-L298

Useful? React with 👍 / 👎.

Comment thread config/interlocks.yaml
- {instrument_id: "LS218_1", source_key: "input.8.temperature"}
threshold: 310.0
comparison: ">"
action: "stop_source"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prevent idle readings from consuming the source guard

If any bound channel is already above the chosen threshold while the source is off—for example during startup or warmup—this always-active row immediately executes stop_source and latches source_overtemp as TRIPPED. SafetyManager.request_run does not consult InterlockEngine state, and subsequent readings are ignored for a tripped condition, so the operator can later start the source while the channel remains between this threshold and the 350 K emergency tier with no source_overtemp protection until an explicit interlock acknowledgement occurs. Gate this condition on the active source lifecycle, retain its violation as a RUN blocker, or otherwise ensure an idle trip cannot disarm the later source-on guard.

AGENTS.md reference: AGENTS.md:L20-L22

Useful? React with 👍 / 👎.

soak measurement added 2 commits August 28, 2026 04:25
Landed by the batch lander. The lane's own report and the coordinator's
verification are recorded on the pull request.
@test1card

Copy link
Copy Markdown
Owner Author

@codex review

Head under review: 08ec081c6b. Please bind your verdict to that exact SHA.

This round answers your findings on the config change. The coordinator's control
at this head: with the configuration reverted the new policy tests fail to
collect at all, and with it applied 10 pass.

Since your last look, something important was learned that bears on this PR and
you should know it: bisection showed that this very commit is what stops the
end-to-end run — not because it is wrong, but because the old warm-detector
stop_source was accidentally republishing the source state to the GUI, and
correcting the guard removed that accident. The underlying defect is in the
engine's publication lifecycle and is being fixed separately. Please do not
recommend restoring stop_source for that side effect.

Docs gate green at this head, derived pair regenerated last.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08ec081c6b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread config/interlocks.yaml
threshold: 10.0
comparison: ">"
action: "stop_source"
action: "warning"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Synchronize operator docs with the warning-only policy

When an operator consults the repository guidance after T12 exceeds 10 K, docs/alarms_tuning_guide.md:168-189 still says this interlock executes stop_source and that the source must be switched off, while docs/safety-operator.md:293 omits the new source_overtemp interlock entirely. With this line now issuing only a warning, those instructions can make the operator believe a hazardous output was stopped when it remains active; update both operator-facing documents in this slice to describe the new action, threshold, lifecycle gate, and acknowledgement name.

AGENTS.md reference: AGENTS.md:L37-L39

Useful? React with 👍 / 👎.

Comment on lines +700 to +707
if reading.is_usable():
for record in protected_matching:
if (
record.condition.action == "warning"
and record.state == InterlockState.TRIPPED
and not record.condition.is_triggered(reading.value)
):
await self._recover_warning(record, reading)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor cooldown when recovered warnings refire

When T12 oscillates across 10 K within the configured five-second cooldown, this immediate safe-side recovery re-arms the warning, and the next warm sample still invokes the production trip handler even though _trip computed suppress_notification=True. Reproducing warm→cold→warm produces two alarm_fired events, so sensor noise can beep the GUI on every two-second poll despite the cooldown; suppress downstream warning dispatch during cooldown or debounce/hysteretically confirm recovery, with a regression covering this boundary.

AGENTS.md reference: AGENTS.md:L296-L298

Useful? React with 👍 / 👎.

soak measurement added 2 commits August 28, 2026 07:44
Landed by the batch lander. The lane's own report and the coordinator's
verification are recorded on the pull request.
@test1card

Copy link
Copy Markdown
Owner Author

@codex review

Head under review: 4fccd32ab4ab1cd6a7a8eb13d4bb0267dfd061fc. Please bind your verdict to that exact SHA.

This round answers your findings on the previous head. The coordinator verified
the lane's claim by reverting the production change and watching the new
assertions redden, and read every deleted assertion against its replacement —
this round deletes none.

Context you should have, because it bears on how hard to press here: on a tree
carrying this branch together with the other six in-flight fixes, the automatic
thermal sweep now runs to COMPLETED and both measured conductance points agree
with the simulator's ground truth to about eight parts in a billion. This
branch's contribution to that is the warm-detector row warning instead of
stopping the source, and the separate over-temperature row at 310 K.

So the thing worth your scepticism is the threshold and the binding, not whether
it works: if any channel in the bound set can legitimately read above 310 K, this
row trips forever and I have replaced one always-on guard with another.

Docs gate green at this head; derived pair regenerated as the last commit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fccd32ab4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cryodaq/engine.py Outdated
Comment on lines +2416 to +2418
if condition.action == "warning":
if context.event_bus is None or context.experiment_manager is None or context.alarm_state_manager is None:
raise RuntimeError("interlock warning publication path is unavailable")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep warning failures out of actuator authority

When detector_warmup warning publication fails—for example, if event_bus.publish() raises—this branch falls into the generic exception handler, which calls SafetyManager.latch_fault() and, with an active source, performs the emergency-OFF path. Thus the new observational warning still acquires actuator authority on a notification-path failure; handle warning failures separately while retaining fail-closed escalation for actual control actions.

AGENTS.md reference: AGENTS.md:L466-L467

Useful? React with 👍 / 👎.

Comment thread docs/safety-operator.md Outdated
Comment on lines +288 to +289
предупреждение только после 5-секундного cooldown; ручное имя acknowledge —
`detector_warmup`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear alarm state when manually acknowledging the warning

When the responsible engineer follows this new detector_warmup acknowledge instruction after T12 has cooled, InterlockEngine.acknowledge() changes only the interlock record to ARMED; warning recovery runs only for TRIPPED records, so subsequent safe readings never call AlarmStateManager.process(..., None, ...) and the GUI annunciation remains active indefinitely. Either clear both state owners in the acknowledge path or remove the unsupported manual-ack instruction and rely on automatic recovery.

AGENTS.md reference: AGENTS.md:L37-L39

Useful? React with 👍 / 👎.

Comment thread config/interlocks.yaml
threshold: 10.0
comparison: ">"
action: "stop_source"
action: "warning"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update canonical-binding regressions for the warning action

When the default canonical interlock tests run, this action change makes both affected regressions fail: test_canonical_channel_ids_trip_every_configured_t_interlock includes warning in expected_actions even though warning actions intentionally have no callable, and test_interlock_follows_declared_sensor_binding_after_canonical_id_rename still requires actions_seen == ["stop_source"]. Update those assertions to verify the warning trip and absence of source control so the full suite can pass without restoring the unsafe behavior.

AGENTS.md reference: AGENTS.md:L296-L298

Useful? React with 👍 / 👎.

"!!! БЛОКИРОВКА СРАБОТАЛА !!! "
# Защитные действия остаются CRITICAL; наблюдательный warning не
# должен заявлять, что источник был заблокирован.
log = logger.warning if condition.action == "warning" else logger.critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the obsolete broad soak-log allowlist

Now that the expected warm-detector event is emitted at WARNING, the unchanged scripts/soak_mock_engine.py:40-44 allowlist r"detector_warmup" is no longer needed, but it still suppresses every ERROR or CRITICAL record containing that name. For example, the production warning_recovery_handler failed for interlock 'detector_warmup' ERROR is discarded by scan_log(), allowing the nightly or 72-hour soak to certify a broken warning path as clean; remove the entry or narrow it to the exact former benign record.

AGENTS.md reference: AGENTS.md:L382-L386

Useful? React with 👍 / 👎.

Comment thread docs/alarms_tuning_guide.md Outdated
Comment on lines +175 to +176
- name: "source_overtemp"
channel_pattern: "Т[1-8] .*"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace the non-loadable source-overtemp example

When a maintainer uses this newly documented “current configuration” row, it supplies only channel_pattern, but InterlockEngine.load_config() unconditionally calls resolve_interlock_channel_bindings() and rejects any row without a non-empty channel_bindings list. The example therefore fails engine startup and also hides the exact physical bindings this safety row depends on; show the production binding schema or clearly label the block as non-loadable pseudocode.

AGENTS.md reference: AGENTS.md:L37-L39

Useful? React with 👍 / 👎.

soak measurement added 2 commits August 28, 2026 08:13
Landed by the batch lander. The lane's own report and the coordinator's
verification are recorded on the pull request.
@test1card

Copy link
Copy Markdown
Owner Author

@codex review

Head under review: f41a42a255fbb7ae4ad905996fc32f0d6a035f45. Please bind your verdict to that exact SHA.

This round answers your five findings. Coordinator's control: 4 nodes red
without the change, 41 green with it.

Two of those four only go green once the round's DOCUMENTATION changes are
applied as well — docs/safety-operator.md and docs/alarms_tuning_guide.md
are bound to the interlock configuration by
test_operator_docs_match_warm_detector_and_source_overtemp_policy and
test_documented_current_interlocks_are_loadable_with_production_bindings. That
binding is the useful thing here: the operator documentation cannot drift from
the rows it describes. Please check it binds what it claims to.

The round also touches scripts/soak_mock_engine.py and a soak-log scanner
test, so the warm-detector warning is now visible to the soak evidence path as
well as to the operator. Worth confirming the scanner flags a warning-recovery
FAILURE rather than merely the warning.

The threshold question stands and is the one I would most like pressed: if any
channel in the source_overtemp bound set can legitimately read above 310 K,
that row trips forever and I have replaced one always-on guard with another.

Docs gate green at this head; derived pair regenerated as the last commit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f41a42a255

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cryodaq/engine.py Outdated
return
if context.alarm_state_manager is None:
raise RuntimeError("interlock warning recovery path is unavailable")
context.alarm_state_manager.process(condition.name, None, {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Publish the detector warning's clear transition

When T12 returns to the safe side after the cooldown, this removes detector_warmup from AlarmStateManager but never publishes the corresponding alarm_cleared event. The warning's alarm_fired event is relayed to event consumers, and PeriodicAlarmProjection._apply() removes it only upon alarm_cleared, so the assistant projection can continue presenting the recovered warning until its next 240-second snapshot reconciliation. Emit the same clear event used by the normal alarm-v2 recovery path when this state transition succeeds.

Useful? React with 👍 / 👎.

@test1card

Copy link
Copy Markdown
Owner Author

Correction to my earlier comments on this pull request.

I said that tests/core/test_engine_b3_structure.py::test_engine_wiring_submodules_import_without_engine_reverse_cycle
"also fails on master" and implied master carries a red test. That was wrong.
Master is fine.

The failure was in MY environment. That test spawns a subprocess to prove there
is no import cycle, and the subprocess inherited my shell's library search path
rather than the conda environment's — loading a system libstdc++ older than
the environment's ICU library requires:

ImportError: /lib/x86_64-linux-gnu/libstdc++.so.6: version `CXXABI_1.3.15' not found

With LD_LIBRARY_PATH pointed at the environment's lib directory it passes.
Hosted CI never saw it because its interpreter is the system one.

The operational advice was still right — that node is not this branch's problem
and there is nothing here to chase. But the reason I gave for it was not, and it
was a claim about the trunk, so it is worth correcting on the record rather than
leaving to stand.

soak measurement added 2 commits August 28, 2026 09:56
Landed by the batch lander. The lane's own report and the coordinator's
verification are recorded on the pull request.
@test1card

Copy link
Copy Markdown
Owner Author

@codex review

Head under review: 8faa7773de1168ed1facb7319c21c05d617fab2b. Verified as the branch head on the remote before
writing this.

This round answers your remaining finding. Coordinator's control: 1 node red
without the production change, 14 green with it

test_detector_warning_rearms_and_refires_after_cold_recovery.

That node is the one worth your attention: the warm-detector row is now a WARNING
rather than a source stop, and a warning that fires once and then goes quiet is
worse than the guard it replaced. The test pins that it re-arms and fires again
after a cold recovery, so a second excursion during a week-long run is still
reported.

The threshold question stands and I would rather you pressed it than passed it:
if any channel bound to source_overtemp can legitimately read above 310 K, the
row trips forever and one always-on guard has replaced another.

Docs gate green at this head; derived pair regenerated as the last commit.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 8faa7773de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant