## [mirendev/runtime#1431](https://github.com/mirendev/runtime/pull/1431) This is ready for human review. The REST-500 defect from my last review is fixed, and the fix avoids both traps I flagged. I'm not recommending approval yet: two decisions still need a person, and the PR description is out of date. **What `b1cbc5b` (Give a disabled app one refusal) changed.** `ErrAppDisabled` now comes from a dedicated `disabledError` (`pkg/deploylifecycle/tracker.go:99-136`). It has an `Is(ErrAppDisabled)` match, `ErrorCode() == "conflict"` and the category `app-disabled`. It does not match `cond.ErrConflict{}`, which matters in two places: - A disable caught by `commitActivation` now returns straight out of the `SetConfiguration` loop and out of `createNewVersion` in `api/app/envvar.go`. Before, both would have cleaned up and retried. - A refused attempt is still recorded as failed, because `FailIfUnsettled` only swallows conflicts. `writeRESTError` (`pkg/rpc/rest.go`) uses `errors.As`, so a caller that wraps the error with `%w` still gets a 409. Every refusal now goes through `CheckEnabled`, so they all read the same: - Begin and `commitActivation` - the build entry points and `DeployVersion` - `restart` (`servers/app/app.go:979`) - pool scale-up (`servers/sandbox/pools.go:224`) The deploy-task check for a disable that lands mid-build is still in place (`build_saga.go:722`). The new tracker test covers the properties that matter: `ErrorIs(ErrAppDisabled)`, `NotErrorIs(cond.ErrConflict{})`, the `"conflict"` code, and the remote error rebuilt on the client side. **A small leftover, not a blocker.** Enable's "app %q is not disabled" (`servers/app/disable.go`) is still a plain `fmt.Errorf`. So `POST /api/v1/apps/{app}/enable` on an app that's already enabled returns a 500 over REST. A `cond.Conflict` is safe there because Enable has no retry loop. It's worth the one-line fix if Cloud will call this endpoint. **Still standing:** - The PR description and your top-level comment to @jcasimir still say "Rollback and env changes still work". The code and docs now refuse both, so please correct them before merge. Please also check with him that the maintenance-mode workflow in `scaling.md` fits his fallback flow. - Letting deployer roles disable and enable apps (`pkg/workloadroles/roles.go`) needs sign-off from whoever owns the roles model. - Someone needs to decide whether merging waits for Cloud to understand the `disabled` health value. ## Replies to existing threads _Posted to the PR's comment threads when you submit._ - _(reply & resolve: addressed)_ Addressed in `b1cbc5b`. `disabledError` matches `ErrAppDisabled` and returns `"conflict"` from `ErrorCode()`, so `writeRESTError` answers 409. It uses `errors.As`, so wrapped errors get the same status. It deliberately doesn't match `cond.ErrConflict{}`, so neither the `SetConfiguration`/`createNewVersion` retry loops nor `FailIfUnsettled` read a disable as a lost race. The new tracker test asserts both, plus the round-trip through `cond.RemoteError`. Restart and pool scale-up use the same type now as well. Resolving. --- **Verdict:** ready