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
- 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.
- 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.
- 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
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.
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:
RecoveryActuatorService.mjs:19:const DEFAULT_ACTIONS = Object.freeze(['restart', 'redeploy', 'warm-provider']);Divergent in both directions, and enforced —
:250rejects anything outside the shipped set.recycle,throttle,reconfigure,shedare specified and absent;redeployandwarm-providership 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):restart,warm-providerrestart,warm-providerredeployThat is strictly better than what the ADR specifies, and not an accident: it is why
redeploycannot be aimed at a supervised child, and why a deploy target cannot bewarm-provider'd. The ADR's flataction ∈ {…}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.redeployandwarm-providertherefore keep their place. They also remain inside AC-2's binding envelope ("config + lifecycle only … never code-exec or dynamic-import"):warm-provideris a config/readiness repair,redeployis 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,throttleandshedhave 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
reconfigure(knownKey)inRecoveryActuatorService, admitted per target kind like the others, inside the §2.5 anti-thrash envelope.knownKeymust 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.redeploy/warm-providerwith their rationale, and markrecycle/throttle/shedas specified-but-unimplemented.Acceptance Criteria
apply(serviceKey, 'reconfigure', {key})succeeds for an admitted target kind and applies the change, within the existing anti-thrash envelope.knownKeyis validated against a closed set; an unknown key is refused with a named reason and mutates nothing.reconfigurecannot bypass the envelope any more thanrestartcan — a spec drives it through the same rate limit.DEFAULT_ACTIONSdisagree. This is the durable half — without it the next divergence is found the same way, by accident.#16223's controller can selectreconfigureto widen a generation window without widening the action set itself.ai/config touch is a hard gate (§critical_gates10). Whateverreconfiguremutates must not be a runtimeAiConfigwrite — @neo-opus-vega already caught herself half-drafting exactly that.Out of Scope
recycle/throttle/shed— no consumer. Documenting their status is in scope; building them is not.#14418,#16223).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
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.reconfigurebecome a config-write primitive.knownKeyis 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.reconfigurealone would leave §2.4 still describing an action set nobody can call.