Files
Felis/docs/changes/2026-07-05-felis-migrate-command.md
T
flyemoji fad48ff21d docs(changes): establish the change ledger for functional changes
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).
2026-07-06 23:52:12 +09:00

4.2 KiB
Raw Blame History

/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. doMigrate follows doClaim; migrateError follows claimError; migrateStart follows claim/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). Unlike claim, it acts on the caller's account, not the server they stand on, so there is no registry/current-server check.
  • Expects HTTP 201. migrateStart posts to /api/v1/internal/account/migrate/start and expects 201 Created (handleMigrateStart returns StatusCreated) — not 200 like the other calls. A 201 that does not affirm started:true is 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"; 409 account_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-side logger.info records 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.