contextNotice hardcodes "ask the operator" and ignores requireOperatorConfirm, so the knob cannot actually stop the asking #621

Closed
opened 2026-09-22 06:01:58 +02:00 by ltms · 0 comments
Owner

The mismatch

FleetConfig.LeadRollover has a knob:

@param requireOperatorConfirm default true — confirm() refuses unless the
               caller also passes operatorConfirmed: true. Set false to
               let the three handover-file checks alone gate the roll.

LeadRollover.confirm(...) honours it (LeadRollover.java:480):

if (cfg.requireOperatorConfirm() && !operatorConfirmed) {
    ... "requireOperatorConfirm is true and operatorConfirmed was false"

But the message that actually reaches the lead does not. LeadHeartbeatLoop.contextNotice(...) builds it and its signature is:

static String contextNotice(boolean enabled, LeadContextGauge.Reading reading, boolean alreadyNotified)

No config is passed in, and the text is unconditional (LeadHeartbeatLoop.java:423-426):

" A fresh session would work better. To hand over: call fleet_handover(action=\"open\"), "
+ "write the file it names, ask the operator, then call fleet_handover(action=\"confirm\", "
+ "token, operatorConfirmed). Only the operator can approve the roll. You will not be told "
+ "again until your context reads ok."

Why this makes the knob ineffective

Setting requireOperatorConfirm: false stops the daemon refusing the roll. It does nothing about the instruction. The lead still receives "ask the operator" and "Only the operator can approve the roll", and a lead that follows its instructions will ask anyway.

So the operator gets interrupted exactly as before, and the config appears not to work. The failure is silent: nothing warns that the message and the policy disagree.

This is the repo's recurring shape — a stale instruction that is obeyed faithfully. It is the same class as fleetd #618, where a config comment asserted the opposite of the real behaviour and would have been acted on.

Motivation

The operator asked for this today, on being prompted to approve a context roll:

"is it intended or? if yes, fix this behavior (maybe come from the fleet impl)"

It is intended — requireOperatorConfirm defaults to true and this host does not set it. But the knob alone will not deliver what they asked for, because of the mismatch above.

Proposed fix

Pass the effective requireOperatorConfirm into contextNotice and branch the last two sentences:

  • when true — keep today's text unchanged.
  • when false — tell the lead to write the file and confirm on its own judgement, and drop both "ask the operator" and "Only the operator can approve the roll". The three handover-file checks are then the gate, and the text should say so, because that is what the lead needs to know to satisfy them.

Acceptance

  • The notice text is driven by the configured value, not hardcoded.
  • A test pins both branches by driving contextNotice and asserting on the produced string — one case per value of the flag. Not a source-text assertion.
  • Prove the new test is a real instrument: with the branch reverted to the hardcoded text, the false case must go red. A test that cannot be made to fail is not a replacement.
  • No change to LeadRollover.confirm's own enforcement. This ticket is the message only.
  • Per CLAUDE.md, this is operator-visible behaviour, so it earns an entry in wiki/11-Features.md: what it does, the knob, why it exists, and the gotcha — the gotcha being that before this fix the knob only half worked.

Not in scope

Whether this host should actually set requireOperatorConfirm: false. That is the operator's decision and is being handled separately. This ticket makes the knob mean what it says either way.

## The mismatch `FleetConfig.LeadRollover` has a knob: ``` @param requireOperatorConfirm default true — confirm() refuses unless the caller also passes operatorConfirmed: true. Set false to let the three handover-file checks alone gate the roll. ``` `LeadRollover.confirm(...)` honours it (`LeadRollover.java:480`): ```java if (cfg.requireOperatorConfirm() && !operatorConfirmed) { ... "requireOperatorConfirm is true and operatorConfirmed was false" ``` But the message that actually reaches the lead does not. `LeadHeartbeatLoop.contextNotice(...)` builds it and its signature is: ```java static String contextNotice(boolean enabled, LeadContextGauge.Reading reading, boolean alreadyNotified) ``` No config is passed in, and the text is unconditional (`LeadHeartbeatLoop.java:423-426`): ``` " A fresh session would work better. To hand over: call fleet_handover(action=\"open\"), " + "write the file it names, ask the operator, then call fleet_handover(action=\"confirm\", " + "token, operatorConfirmed). Only the operator can approve the roll. You will not be told " + "again until your context reads ok." ``` ## Why this makes the knob ineffective Setting `requireOperatorConfirm: false` stops the **daemon** refusing the roll. It does nothing about the **instruction**. The lead still receives "ask the operator" and "Only the operator can approve the roll", and a lead that follows its instructions will ask anyway. So the operator gets interrupted exactly as before, and the config appears not to work. The failure is silent: nothing warns that the message and the policy disagree. This is the repo's recurring shape — a stale instruction that is obeyed faithfully. It is the same class as fleetd #618, where a config comment asserted the opposite of the real behaviour and would have been acted on. ## Motivation The operator asked for this today, on being prompted to approve a context roll: > "is it intended or? if yes, fix this behavior (maybe come from the fleet impl)" It is intended — `requireOperatorConfirm` defaults to `true` and this host does not set it. But the knob alone will not deliver what they asked for, because of the mismatch above. ## Proposed fix Pass the effective `requireOperatorConfirm` into `contextNotice` and branch the last two sentences: - **when `true`** — keep today's text unchanged. - **when `false`** — tell the lead to write the file and confirm on its own judgement, and drop both "ask the operator" and "Only the operator can approve the roll". The three handover-file checks are then the gate, and the text should say so, because that is what the lead needs to know to satisfy them. ## Acceptance - The notice text is driven by the configured value, not hardcoded. - A test pins **both** branches by driving `contextNotice` and asserting on the produced string — one case per value of the flag. Not a source-text assertion. - Prove the new test is a real instrument: with the branch reverted to the hardcoded text, the `false` case must go **red**. A test that cannot be made to fail is not a replacement. - No change to `LeadRollover.confirm`'s own enforcement. This ticket is the message only. - Per CLAUDE.md, this is operator-visible behaviour, so it earns an entry in `wiki/11-Features.md`: what it does, the knob, **why it exists**, and the gotcha — the gotcha being that before this fix the knob only half worked. ## Not in scope Whether this host should actually set `requireOperatorConfirm: false`. That is the operator's decision and is being handled separately. This ticket makes the knob mean what it says either way.
ltms closed this issue 2026-09-22 06:31:02 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#621