LearnNewsExamplesServices
Frontmatter
id16374
titleRecovery actuator and ADR-0026 disagree on the action set, in both directions
stateClosed
labels
bugaiarchitecture
assigneesneo-opus-grace
createdAtAug 2, 2026, 5:39 PM
updatedAtAug 2, 2026, 11:34 PM
githubUrlhttps://github.com/neomjs/neo/issues/16374
authorneo-opus-grace
commentsCount4
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 2, 2026, 11:34 PM

Recovery actuator and ADR-0026 disagree on the action set, in both directions

Closed Backlog/active-chunk-12 bugaiarchitecture
neo-opus-grace
neo-opus-grace commented on Aug 2, 2026, 5:39 PM

Context

Surfaced by @neo-opus-vega running the mandatory ADR gate before building #16223's controller. She declined to extend the actuator unilaterally and routed the decision to me as ADR author — correctly. This ticket is my ruling plus the work it implies. Live latest-open sweep 2026-08-02T15:38Z: no existing ticket; nearest is #14418 (phase-2 controller, the consumer).

The Problem

ADR-0026 §2.4 fixes the interface:

apply(serviceKey, action) where action ∈ {restart, recycle, throttle, reconfigure(knownKey), shed}

RecoveryActuatorService.mjs:19:

const DEFAULT_ACTIONS = Object.freeze(['restart', 'redeploy', 'warm-provider']);

Divergent in both directions, and enforced:250 rejects anything outside the shipped set. recycle, throttle, reconfigure, shed are specified and absent; redeploy and warm-provider ship unlisted.

The ruling: the ADR is stale about the shape, and separately missing one real action

Reading the dispatch sites settles which side is wrong, and it is mostly mine.

The shipped action set is not flat — it is scoped per target kind (:502-510):

target kind actions accepted
supervised task restart, warm-provider
compose service restart, warm-provider
deploy target redeploy

That is strictly better than what the ADR specifies, and not an accident: it is why redeploy cannot be aimed at a supervised child, and why a deploy target cannot be warm-provider'd. The ADR's flat action ∈ {…} set cannot express that constraint, so an implementation obeying the ADR literally would be less safe than what shipped. The flat set was a simplification the implementation outgrew, and §2.4 should describe the matrix.

redeploy and warm-provider therefore keep their place. They also remain inside AC-2's binding envelope ("config + lifecycle only … never code-exec or dynamic-import"): warm-provider is a config/readiness repair, redeploy is a lifecycle action on a known deploy target.

Separately, reconfigure(knownKey) is genuinely missing. It is specified, binding under AC-2, and now has a concrete consumer that cannot proceed without it. §2.4 also binds the consumer — "the phase-2 controller cannot widen the action set or bypass the envelope" — so it cannot be added from the controller side. That constraint is working as designed; the answer is to complete the actuator, not to route around it.

recycle, throttle and shed have no consumer today. They stay specified but must be marked unimplemented rather than left to read as available — a reader of §2.4 currently cannot tell which of the five they can actually call.

The Fix

  1. Implement reconfigure(knownKey) in RecoveryActuatorService, admitted per target kind like the others, inside the §2.5 anti-thrash envelope. knownKey must be a closed set — the whole point of the action is a known config tweak, never an arbitrary write. It must not become a general config-mutation primitive.
  2. Amend ADR-0026 §2.4 to state the action × target-kind matrix as shipped, list redeploy / warm-provider with their rationale, and mark recycle / throttle / shed as specified-but-unimplemented.
  3. A spec asserting the ADR and the code agree on the admitted set, so this divergence is caught mechanically rather than by a peer running a gate before unrelated work.

Acceptance Criteria

  • apply(serviceKey, 'reconfigure', {key}) succeeds for an admitted target kind and applies the change, within the existing anti-thrash envelope.
  • knownKey is validated against a closed set; an unknown key is refused with a named reason and mutates nothing.
  • reconfigure cannot bypass the envelope any more than restart can — a spec drives it through the same rate limit.
  • ADR-0026 §2.4 describes the shipped action × target-kind matrix, and every action it lists is marked implemented or not.
  • A spec fails if the ADR's documented admitted-action set and DEFAULT_ACTIONS disagree. This is the durable half — without it the next divergence is found the same way, by accident.
  • #16223's controller can select reconfigure to widen a generation window without widening the action set itself.
  • AiConfig: reading ADR-0019 before any ai/ config touch is a hard gate (§critical_gates 10). Whatever reconfigure mutates must not be a runtime AiConfig write — @neo-opus-vega already caught herself half-drafting exactly that.

Out of Scope

  • Implementing recycle / throttle / shed — no consumer. Documenting their status is in scope; building them is not.
  • The phase-2 controller itself (#14418, #16223).
  • Accepting ADR-0026 (status Proposed, pending the human merge gate per ADR-0005). This amends a Proposed ADR's content to match reality; it does not change its lifecycle state.

Avoided Traps

  • Assuming the ADR is right because it is the spec. It is Proposed, dated 2026-06-23, and the implementation learned something real after it — the per-target-kind scoping. Treating the older document as automatically authoritative would have removed a safety property.
  • Letting reconfigure become a config-write primitive. knownKey is load-bearing: the closed set is what keeps this inside AC-2's envelope instead of becoming the arbitrary-mutation path the ADR's anti-anchor names.
  • Fixing only the side that blocks someone. Implementing reconfigure alone would leave §2.4 still describing an action set nobody can call.
tobiu referenced in commit 2590b07 - "feat(recovery): reconfigure — a knob-keyed config action inside the actuator envelope (#16374) (#16395) on Aug 2, 2026, 11:34 PM
tobiu closed this issue on Aug 2, 2026, 11:34 PM