Two production issues caught running the wizard end-to-end:
1. `mcpctl migrate secrets --from default --to bao` listed `bao-creds` as a
candidate — the very token that lets mcpd reach bao. Moving it would
brick the auth chain (destination backend needs its own bootstrap token
to read its own bootstrap token). Fix: SecretMigrateService now calls
backends.list() and filters out any Secret whose name matches ANY
SecretBackend's `config.tokenSecretRef.name`. dryRun mirrors the same
filter so candidates match reality. `--names` explicitly bypasses the
filter for operators who really mean it.
2. Initial rotation in the wizard 403'd because the global RBAC hook
demands the `rotate-secretbackend` operation, which wasn't in
bootstrap-admin — migrateAdminRole only added ops when processing a
legacy `role: admin` entry, so already-migrated admin rows missed every
new op added after their initial migration. Fix: migrateAdminRole now
also runs a back-fill pass on rows that look admin-equivalent (have both
`edit:*` and `run:*`), appending any missing op from ADMIN_OPS. Writes
only when something actually changed, so restarts stay quiet. Same path
also retroactively grants `migrate-secrets` which had the same problem
yesterday.
Tests: 4 new migrate-service cases (bootstrap filter on/off, dryRun parity,
--names bypass). Full suite 1889/1889.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>