--- id: "bug-1468" status: "fixed" status_updated: "2026-09-24" status_note: "" component: "controller" severity: "unknown" timestamp: "2026-07-25T00:00:00Z" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: ["src/controller/cluster.c"] tags: ["config-upgrade-snapshot", "task-8.6", "upgradeConfig", "cluster.c", "dispatch-gate", "regression", "admin-config-tool", "cross-spec"] related: [] legacy: true --- # bug-1468: OP_upgradeConfig sent by any network client is permanently rejected -- handle_upgrade_config() was… ## Description OP_upgradeConfig sent by any network client is permanently rejected -- handle_upgrade_config() was unreachable via the wire dispatch path, exercised only by unit tests calling it directly. Independently diagnosed by the admin-config-tool spec's design.md (Boundary Commitments / Risks) as a blocking controller defect out of that spec's scope; re-discovered here while building config-upgrade-snapshot task 8.6's live OP_upgradeConfig client (no prior client existed to send it and hit the gate). ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause commit fd93997f ("fix(election-iomgr-refactor): config replication and RDHUP failover chain", 2026-05-31) inverted the OPCode_upgradeConfig primary/standby guard in myreq_handler's switch: it changed `if (!ha_is_primary(&c->ha)) reject` (correct: only primary can commit a config change) to `if (ha_is_primary(&c->ha)) reject` (rejects the ONLY role -- primary -- that ha_dispatch_gate() ever lets reach this switch case at all, since OPCode_upgradeConfig is absent from the CTRL_STANDBY/CTRL_INIT/CTRL_ELECTING allow-lists in ha_dispatch_gate, ha.c). The commit's own message text ("standbys apply, not primaries") reflects a since-superseded design where OP_upgradeConfig was a primary-to-standby push; do_upgrade_config_with_bytes (config_repl.c) was later substantially rewritten (ha-term-safety R7 / config-upgrade-snapshot task 7) into a client-to-primary admin op that drives config_commit_change(), but this dispatch guard was never updated to match, leaving handle_upgrade_config() reachable only by unit tests calling it directly -- never via the network. ## Fix Restored the pre-regression polarity in controller/cluster.c's myreq_handler OPCode_upgradeConfig case: reject with -EAGAIN when NOT primary (`if (!ha_is_primary(&c->ha))`), fall through to handle_upgrade_config() when primary. Updated the stale comment to describe the current client-to-primary semantics and cite ha_dispatch_gate as the actual primary-only enforcement point (this switch-level check is now explicitly documented as defensive redundancy). No change to config_repl.c/ha.c/persist.c materialization logic -- pure dispatch-routing fix. ## Verification _Not recorded (migrated from buglog.json)._ ## References _Not recorded (migrated from buglog.json)._