Add docs/changes/ — a durable, in-repo map of every functional change and the commit that records it, independent of git log. INDEX.md carries the convention (each functional change gets a dated detail doc plus a ledger row) and the full oldest-first ledger, regenerable losslessly from git. Seed detail docs for the two changes just landed: the break-glass halt op (c2ee21a) and the /felis migrate command (c1aa38b).
4.2 KiB
/felis migrate in-game command (§B3 inherit, Velocity side)
- Type: feature (addition)
- Date: 2026-07-05
- Area:
plugins/velocity+plugins/shared— Velocity proxy plugin (Java, compile-verified) - Commit:
c1aa38b— feat(velocity): add /felis migrate to open an account migration (§B3 inherit) - Task: completes the code-only gap named in
internal/api/handlers_account_migrate.go
What it does
Adds the in-game /felis migrate command that a player runs to open an account
migration — the first step of handing their owned servers to another account (spec
§B3 "inherit", scenario A). The command posts the player's Mojang-verified UUID to the
backend, which puts that account into migrate mode (state=initiated). The player then
finishes the migration on the web console (prove it's them, name the receiving account,
redeem a one-time code).
The Go backend (handleMigrateStart and the web-driven steps 2–4) already existed and
was tested; its header comment explicitly named "the /felis migrate command that
calls handleMigrateStart" as the code-only gap. This change closes that gap.
Why
Without the in-game command, the migration flow had no entry point — the backend
handler was reachable only in theory. /felis migrate is the trustworthy initiator:
Velocity has already established the caller's online-mode UUID, so the sensitive proof
can be deferred to the web step-up while the in-game command just opens the migration.
Design decisions
- Mirrors the existing command suite verbatim.
doMigratefollowsdoClaim;migrateErrorfollowsclaimError;migrateStartfollowsclaim/opLoginApprove. No new imports, types, or idioms — every construct already appears in the same files. - Identity-bound + out-of-limbo, but server-independent. Like
claim, it requires a real player past the login limbo (requirePlayer+ensureOutOfLimbo). Unlikeclaim, it acts on the caller's account, not the server they stand on, so there is noregistry/current-server check. - Expects HTTP 201.
migrateStartposts to/api/v1/internal/account/migrate/startand expects 201 Created (handleMigrateStartreturnsStatusCreated) — not 200 like the other calls. A 201 that does not affirmstarted:trueis treated as a contract breach, not a refusal. - Error mapping matches the handler's refusals: 404
not_linked→ "Link your account on the web console before migrating"; 409account_retired→ "This account can't start a migration (already migrated or retired)"; transport (0) and default → generic retry text. - Points the player to the console on success. The command only opens the
migration, so on success it prints the player web console URL
(
https://console.<root_domain>, derived from config — never a hardcoded domain) and a one-line description of the remaining steps. A proxy-sidelogger.inforecords the initiating username against the UUID (the backend audit only has the UUID).
Files
| File | Change |
|---|---|
plugins/shared/.../link/FelisApiClient.java |
+migrateStart(UUID) — POST mc_uuid, expect 201, affirm started:true |
plugins/velocity/.../FelisVelocityPlugin.java |
migrate literal in the Brigadier tree; doMigrate handler; migrateError mapper; /felis migrate help line |
Verification
Java is not oracle-verifiable via the Go suite, but it is compile-verifiable via the podman gradle toolchain established in #63/#65:
podman run --rm -v plugins:/work -w /work/velocity \
docker.io/library/gradle:jdk17 gradle --no-daemon compileJava
→ BUILD SUCCESSFUL in 19s (compiled against real velocity-api:3.3.0-SNAPSHOT)
The change compiles clean against the real Velocity API jar (including the shared
FelisApiClient compiled straight into the velocity module). The backend contract it
speaks to (handleMigrateStart) is covered by handlers_account_migrate_test.go on
the Go side.
Self-review outcome
- ponytail (over-engineering): lean — pure mirror of three existing, compiling methods; no speculative abstraction. Nothing cut.
- correctness: the one contract divergence (201 vs 200) was verified against the Go handler source before writing.