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).
81 lines
4.2 KiB
Markdown
81 lines
4.2 KiB
Markdown
# `/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.
|