From fad48ff21d352aa43eb53d631c6d0b1d829187e7 Mon Sep 17 00:00:00 2001 From: Minseong Choi Date: Mon, 6 Jul 2026 23:52:12 +0900 Subject: [PATCH] docs(changes): establish the change ledger for functional changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- docs/changes/2026-07-05-break-glass-halt.md | 83 +++++++ .../2026-07-05-felis-migrate-command.md | 80 +++++++ docs/changes/INDEX.md | 206 ++++++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 docs/changes/2026-07-05-break-glass-halt.md create mode 100644 docs/changes/2026-07-05-felis-migrate-command.md create mode 100644 docs/changes/INDEX.md diff --git a/docs/changes/2026-07-05-break-glass-halt.md b/docs/changes/2026-07-05-break-glass-halt.md new file mode 100644 index 0000000..c0fe819 --- /dev/null +++ b/docs/changes/2026-07-05-break-glass-halt.md @@ -0,0 +1,83 @@ +# Break-glass "halt a running server" op (#31 B4) + +- **Type:** feature (addition) +- **Date:** 2026-07-05 +- **Area:** `cmd/felis` — break-glass recovery console (Go, oracle-verifiable) +- **Commit:** `c2ee21a` — feat(breakglass): add halt-a-server op to the recovery console (§B4) +- **Task:** #31 Phase B4 (felis TUI break-glass ops) + +## What it does + +Adds a **"Halt a running server"** operation to the root-gated break-glass console. +The operator picks a server from the live fleet and the console flips that +`MinecraftServer` CRD's `spec.desiredState` to `Stopped`, letting the operator +reconcile it into a graceful shutdown. It is the emergency "stop this now" lever for +when the panel is unreachable but the box still has `root` + a kubeconfig. + +## Why + +The break-glass console already provisions the Owner and adds Operators, but there +was no local, panel-independent way to **stop** a misbehaving server (runaway, +compromised, resource-pinning). Halting is a reversible state nudge — the safest +possible break-glass power — so it belongs in the same root-gated recovery surface. + +## Design decisions + +- **CRD write, not pod kill.** The console flips `spec.desiredState=Stopped` with a + **spec-only merge patch** (`client.MergeFrom`), never a full-object `Update`. The + operator writes `status` on the same object continuously; a merge patch of + `spec.desiredState` touches a disjoint field and cannot race/clobber the operator's + status writes. A halt is therefore exactly the CRD write the operator already knows + how to honour. +- **Authority = root + kubeconfig.** The accountable actor is the OS user who + escalated to root (`osUser`), recorded for attribution — not proof. The root gate + plus kubeconfig possession *is* the authority, so (unlike the owner/operator paths) + no credential-minting auth sub-flow is needed for a reversible state change. +- **System servers allowed but named.** Halting the `login`/`lobby` system servers + takes the shared front door down (login has no fallback). Break-glass is deliberately + full power, so the console **warns** rather than forbids: a `⚠ system` tag in the + picker and an explicit `WARNING` line in the post-exit summary. +- **Audit is best-effort.** `performHalt` mirrors `performBreakGlass`: the halt + succeeds even if the audit sink is down (break-glass must work with logging broken); + any audit error rides back in the outcome and is surfaced as a summary `WARNING`. +- **Already-stopped is a no-op** reported distinctly ("was already stopped" vs "is now + stopping"), so the console never claims a stop it didn't perform. +- **Namespace from config.** The target namespace is `cfg.K8s.Namespace`, threaded + through the console constructors — never hardcoded. + +## Files + +| File | Change | +|---|---| +| `cmd/felis/halt.go` | **new** — pure core (no bubbletea): `listServersForHalt`, `haltServer` (merge patch), `isSystemServer`, `performHalt`, `auditHalt` | +| `cmd/felis/halt_test.go` | **new** — table tests against a controller-runtime **fake client** (applies patches for real): running→stopped persists, already-stopped no-op, missing→error, system flag, list projection + desired-state fallback, audit success, audit-failure-still-halts | +| `cmd/felis/tui_halt.go` | **new** — bubbletea/huh shell mirroring `ownerModel` (load → pick → work → done), empty-fleet guard, `⚠ system` picker labels, outcome card | +| `cmd/felis/tui_menu.go` | `bgHaltServer` enum + "Halt a running server" menu option | +| `cmd/felis/tui_root.go` | `namespace` field; `bgHaltServer` dispatch to `newHaltModel`; `haltResultMsg` terminal handling | +| `cmd/felis/breakglass.go` | halt fields on `breakGlassResult`; `namespace` threaded through `runBreakGlassTUI`/`runSetupTUI`/`runConsoleTUI`; post-exit halt summary (stopping / already-stopped, system + audit warnings, restart hint) | +| `cmd/felis/setup.go` | pass `cfg.K8s.Namespace` into `runSetupTUI` | +| `cmd/felis/tui_root_test.go` | pass `"minecraft"` namespace into `newRootModel` test call | + +## Verification + +WSL oracle (go1.26.4, FedoraLinux-44), authoritative for Go: + +``` +go build ./... → BUILD_OK +go vet ./cmd/felis/... → VET_OK +go test ./... → all 20 packages ok, ALL_GREEN +``` + +The core (`halt.go`) is fully unit-tested against a real `fake.Client`, which applies +the merge patch, so the test asserts the **persisted** `spec.desiredState`, not merely +that `Patch` was called. `tui_halt.go` is thin bubbletea glue (untested by house +convention, mirrors the existing `tui_owner.go`). + +## Self-review outcome + +- **ponytail (over-engineering):** lean — no one-impl interface, every field consumed, + audit seam justified. Nothing cut. +- **correctness:** caught and fixed a misleading restart hint — the summary originally + pointed at `felis apply`, but that command is **create-only** (errors "already + exists" on an existing server); corrected to "restart from the panel, or set + `spec.desiredState` back to Running." diff --git a/docs/changes/2026-07-05-felis-migrate-command.md b/docs/changes/2026-07-05-felis-migrate-command.md new file mode 100644 index 0000000..0624904 --- /dev/null +++ b/docs/changes/2026-07-05-felis-migrate-command.md @@ -0,0 +1,80 @@ +# `/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.`, 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. diff --git a/docs/changes/INDEX.md b/docs/changes/INDEX.md new file mode 100644 index 0000000..b2e2e89 --- /dev/null +++ b/docs/changes/INDEX.md @@ -0,0 +1,206 @@ +# Felis change ledger + +The index of every functional change to Felis — what it did and which commit records +it. This is the durable, in-repo map that `git log` alone doesn't give: it links +substantial changes to their detail docs and flags work that is built and verified but +not yet committed. + +## Convention + +- **Every functional change** (a feature addition, a behaviour change, a bug fix) gets: + 1. a dated detail doc in this directory — `docs/changes/YYYY-MM-DD-.md`, covering + _what it did, why, the files touched, and the verification evidence_; and + 2. a row in the ledger below, carrying its **commit record** (the short SHA). +- A change that is **built and verified but not yet committed** (e.g. while PGP signing + is locked) sits in **Pending** with `commit: pending`, and moves into the ledger with + its real SHA once committed. +- Pure-cosmetic or non-functional commits (docs, style) still appear in the ledger table + for completeness, but do not require a dedicated detail doc. +- The ledger table is generated losslessly from git history and can be regenerated: + ``` + git log --reverse --pretty=format:'| %h | %ad | %s |' --date=short + ``` + +## Pending (built + verified, not yet committed) + +_None — the break-glass halt (`c2ee21a`) and `/felis migrate` (`c1aa38b`) landed in the ledger below._ + +## Committed change ledger + +Oldest first (project build order). Commit = short SHA on `main`. Frontend/`panel` +commits are the collaborator's UI work; backend (Go/Java/K8s) is tracked here as the +primary record. + +| Commit | Date | Change | +|---|---|---| +| 5b7b38d | 2026-06-26 | chore: add Go module manifest and ignore rules | +| 5a30aa5 | 2026-06-26 | docs: add OpenAPI 3.1 served-route contract | +| 7fbebfe | 2026-06-26 | feat(apis): add MinecraftServer CRD types (v1alpha1) | +| 708cdfc | 2026-06-26 | feat(core): add naming, RCON, store, config, and image-build libraries | +| 43ab921 | 2026-06-26 | feat(backup): add backup, restore, and reaper subsystems | +| 78b8cf6 | 2026-06-26 | feat(operator): add MinecraftServer controller and reconcilers | +| d39605e | 2026-06-26 | feat(submit): add user modpack build and approval pipeline | +| b508fcc | 2026-06-26 | feat(api): add felis-api service with permissions, modpack lane, and fleet read | +| 47fcd90 | 2026-06-26 | feat(platform): add node orchestration and the felis entrypoint | +| eee00c2 | 2026-06-26 | feat(panel): add three-sided web console (User, Admin, SysAdmin) | +| 93f143f | 2026-06-26 | feat(plugins): add Velocity proxy and Fabric/Forge/NeoForge/Paper integration mods | +| ce0ba76 | 2026-06-27 | chore(api): add kubebuilder object-generation markers to v1alpha1 | +| 7d91373 | 2026-06-27 | fix(migrate): honor -config flag placed after the up verb | +| 99de43f | 2026-06-27 | chore: ignore plugin build artifacts and editor config | +| 58fa4b0 | 2026-06-27 | feat(deploy): add one-line bootstrap installer and container image | +| af14f02 | 2026-06-27 | feat(api): local-password authentication backend | +| e108a37 | 2026-06-27 | feat(cli): break-glass emergency console TUI | +| 885c4a9 | 2026-06-27 | feat(panel): local-password login and forced password change | +| 2d0bbb0 | 2026-06-27 | feat(cli): attribute break-glass recovery to the SysAdmin who runs it | +| dbe34a1 | 2026-06-27 | feat(api): add player email OTP verification (spec §B2 onboarding) | +| 1f8b9bb | 2026-06-27 | feat(api): record account-link auth source (mojang/thirdparty) | +| a29571d | 2026-06-27 | feat(api): reclaim squatted usernames for Mojang-priority players (spec §B3) | +| 53a7664 | 2026-06-27 | feat(cfsetup): recommended Cloudflare Tunnel + Access edge setup | +| ba13839 | 2026-06-27 | feat(breakglass): optional Cloudflare Tunnel + Access setup in the TUI | +| a5a6482 | 2026-06-27 | feat: dev mock | +| dd2fc6f | 2026-06-27 | feat(panel): i18n | +| 7b458da | 2026-06-27 | feat(panel): light/dark theme | +| 51c9eab | 2026-06-27 | docs: CONTRIBUTOR.md | +| 74e7e6e | 2026-06-27 | docs: CONTRIBUTING.md | +| b81b334 | 2026-06-27 | Merge branch 'main' of https://github.com/MliroLirrorsIngenuity/Felis | +| 9d13787 | 2026-06-27 | refactor(panel): dashboard | +| f3521e3 | 2026-06-27 | refactor(panel): uniform margins | +| 18b4be0 | 2026-06-27 | refactor(panel): uniform title icon styles | +| 665841b | 2026-06-27 | fix(panel): remove internal spec references from user-facing text | +| cf88bcc | 2026-06-27 | fix(panel): extract hardcoded security note into i18n keys | +| 061482d | 2026-06-27 | fix(panel): extract hardcoded Chinese text to i18n keys | +| cfe126f | 2026-06-27 | feat(panel): add RCON command input to server console | +| ae91133 | 2026-06-27 | style(panel): refine button styles with shadow, active scale, toned-down colors | +| e189265 | 2026-06-27 | refactor(panel): compact server card layout, denser grid | +| f4df3e2 | 2026-06-27 | feat(panel): pagination for server lists | +| b512a18 | 2026-06-27 | refactor(panel): adjust margins | +| a49443c | 2026-06-27 | refactor(panel): simplify sidebar | +| 86f2ae4 | 2026-06-28 | feat(panel): sidebar foot shows current account + sign-out; reorder Account cards | +| f5d00f3 | 2026-06-28 | feat(cli): implement felis apply command for direct CRD creation | +| 832b200 | 2026-06-28 | fix(panel): reactive system theme detection | +| c9cd9dc | 2026-06-28 | style(panel): unify dialog animation to fade and scale from center | +| 94a3b7b | 2026-06-28 | fix(deploy): harden bootstrap for RHEL-family Linux | +| 9c46632 | 2026-06-28 | feat(cli): add felis setup first-run console with reclaim protection and cfsetup idempotency | +| 5450c26 | 2026-06-28 | chore: normalize line endings and apply formatting | +| deaa2f8 | 2026-06-28 | feat(deploy): add zypper support for openSUSE/SLES | +| 318a724 | 2026-06-28 | feat(deploy): add pacman support for Arch Linux | +| e5f1682 | 2026-06-29 | refactor(deploy)!: TUI | +| 28c3eee | 2026-06-30 | refactor(deploy): improved TUI walkthrough | +| 116595f | 2026-06-30 | feat(api): add QR scan-login completion poll on the internal face | +| a94b001 | 2026-06-30 | feat(deploy): add break-glass Operator account provisioning | +| 346ec68 | 2026-06-30 | refactor(deploy): improved cloudflare walkthrough | +| 563041a | 2026-06-30 | feat(panel): add fail-closed role-switcher view-mode logic | +| 50b8487 | 2026-06-30 | feat(panel): wire role-switcher into the app shell | +| 75642d9 | 2026-06-30 | feat(metrics): add named felis_* Prometheus collectors | +| 2a93a9e | 2026-06-30 | feat(metrics): record felis_image_build_failures_total on failed builds | +| 79eae7f | 2026-06-30 | feat(metrics): publish felis_servers_total from a fleet snapshot | +| 8ac5e64 | 2026-06-30 | feat(metrics): observe felis_start_duration_seconds across the start lifecycle | +| 676407d | 2026-06-30 | docs(diagrams): align §28 sequence diagrams with implemented routes | +| ac02c69 | 2026-06-30 | docs(troubleshooting): add operator failure-mode checklist | +| eb5875a | 2026-06-30 | feat(felis): add Operator break-glass op behind an operation menu | +| c14ed17 | 2026-06-30 | fix(docker): keep embedded panel/ and deploy/ in the image build context | +| 2a4a81b | 2026-06-30 | fix(api): don't burn wake cooldown when refused at capacity | +| 6c3999a | 2026-06-30 | fix(api): rate-limit email-OTP sends to close the email-bomb vector | +| 9873904 | 2026-06-30 | fix(operator): populate Status.Players from an RCON list probe | +| 879b177 | 2026-06-30 | fix(api): make OTP-start throttle atomic to close concurrent-burst bypass | +| 29f5341 | 2026-07-01 | docs(api): correct cooldownLimiter doc for its OTP reuse | +| 7507cfa | 2026-07-01 | Revert "feat(panel): wire role-switcher into the app shell" | +| f2c916d | 2026-07-01 | feat(api): add passkey enrollment persistence layer | +| 742f15f | 2026-07-01 | feat(api): add passkey enrollment endpoints | +| d2de11a | 2026-07-01 | feat(panel): fleet | +| 0261204 | 2026-07-01 | feat(passkey): add go-webauthn enrollment verifier adapter | +| fce0fce | 2026-07-01 | feat(passkey): wire enrollment verifier into felis-api | +| 2810fe8 | 2026-07-01 | fix(cfsetup): repoint stale DNS record when routing a tunnel hostname | +| 7d3be64 | 2026-07-01 | feat(cfsetup): start the tunnel connector as a setup step | +| a531f5e | 2026-07-01 | fix(cfsetup): keep connector install in the host apply layer only | +| e058a64 | 2026-07-01 | feat(edge): close the panel NodePort to the public after the tunnel is up | +| c01f133 | 2026-07-01 | feat(updates): add pure decision core for component self-update | +| fe2ece0 | 2026-07-01 | feat(api): add public Bind-Code onboarding for the player console | +| 3673af6 | 2026-07-01 | feat(api): add admin API for the SysAdmin-set auto-update maintenance window | +| 7464fa7 | 2026-07-01 | fix(updates): tag Window JSON so the persisted maintenance window round-trips | +| e035142 | 2026-07-01 | feat(passkey): add WebAuthn login/assertion crypto adapter | +| f34711c | 2026-07-01 | docs(api): record passkey login-handler deferral rationale | +| 7a51c1d | 2026-07-01 | fix(api): bound concurrent login bcrypt to shed CPU-pin floods | +| 164ac44 | 2026-07-01 | fix(api): validate inbound X-Request-Id before echo and audit persist | +| c6c0772 | 2026-07-01 | fix(api): set read/idle timeouts on the felis-api listeners | +| 3c1d647 | 2026-07-01 | fix(api): cap concurrent SSE streams per principal | +| d6e3189 | 2026-07-01 | fix(api): bound SSE relay writes with a deadline to sever stalled readers | +| 2c56d17 | 2026-07-01 | docs(api): record the quota-claim TOCTOU as a KNOWN-LIMITATION (audit #4) | +| 8f41a00 | 2026-07-01 | fix(api): clear the SSE write deadline on return so it can't leak to a reused connection | +| 15c58d9 | 2026-07-01 | feat(panel): player management | +| 8ae65ae | 2026-07-02 | feat(panel): backup management | +| a15ff55 | 2026-07-02 | refactor(panel): optimize player list layout and horizontal operations | +| 149f01a | 2026-07-02 | fix(panel): change console button to outline variant on my servers page | +| 4ecaf3c | 2026-07-02 | refactor(panel): set defaultOpen parameter of whitelist card to false | +| a9dbc8b | 2026-07-02 | feat(panel): add search and status filtering to my servers page | +| fa7bab5 | 2026-07-02 | style(panel): refine search and filter layout to align with header | +| 70d17a0 | 2026-07-02 | feat(panel): align my servers page search layout with fleet table | +| 5a8eff1 | 2026-07-02 | feat(panel): remove developer comment footer cards from my servers and server admin pages | +| 92770ea | 2026-07-02 | style(panel): adjust pagination padding to pt-3 for balanced spacing | +| 6e43a46 | 2026-07-02 | fix(panel): pin sidebar navigation and enable independent content scroll | +| 3b4298d | 2026-07-02 | refactor(panel): unify servers cockpit layout, resolve duplicate pages and adjust spacing | +| c0d333b | 2026-07-02 | feat(panel): support full server config edit dialog with status prefilling | +| 6368ab1 | 2026-07-02 | fix(api): coalesce MyServers owned flag so ownerless rows do not 500 | +| cdbb5ab | 2026-07-02 | fix(api): record credential id in passkey-register audit event | +| 9953275 | 2026-07-02 | fix(api): bound webauthn_challenges growth by superseding all prior rows | +| 20e31fb | 2026-07-02 | fix(store): cascade-delete passkeys and challenges on user removal | +| 7278cd7 | 2026-07-02 | feat(passkey): require and record user verification at enrollment | +| 54bc6ef | 2026-07-02 | fix(api): clear bound passkeys on password change to close a takeover foothold | +| 19f500b | 2026-07-02 | style(panel): update destructive red color and rename wake to start | +| e0bc288 | 2026-07-02 | feat(panel): implement image build pipeline and admin whitelist with mock dev api | +| 8594622 | 2026-07-02 | feat(panel): implement email OTP verification and passkey registration management | +| 9bed51b | 2026-07-02 | feat(config): add [velocity] login_image/lobby_image for system servers | +| 9ef817f | 2026-07-02 | feat(naming): system-server names, validation, and service-token identifiers | +| 159107b | 2026-07-02 | feat(api): HTTP readiness knob on MinecraftServer and login-gate fallback default | +| dc23cb5 | 2026-07-02 | feat(operator): system-server pod readiness probe and login service-token env | +| 3fdb3d0 | 2026-07-02 | feat(platform): internal API base-URL helper and single-sourced token secret | +| f554d52 | 2026-07-02 | feat(cli): provision login/lobby system servers with login env and token replica | +| a63f49d | 2026-07-02 | feat(panel): steer WeChat/QQ in-app browsers to the system browser for passkey | +| 241fe21 | 2026-07-02 | feat(limbo): felis-limbo in-game login flow over the shared account-link client | +| c7315e4 | 2026-07-02 | feat(deploy): login-limbo and lobby images with game-port pinning | +| 191640c | 2026-07-02 | feat(panel): implement admin submission approval and reject queue | +| 598f3d3 | 2026-07-02 | feat(submit): local + S3 backends for modpack upload contexts, installer-selectable | +| adf0d99 | 2026-07-02 | feat(panel): implement user-side modpack submissions with drag & drop context upload | +| d9e866f | 2026-07-03 | fix(deploy): make the lobby image actually build | +| b84debf | 2026-07-03 | feat(deploy): one-shot demo bring-up wrapper | +| 73d6ec1 | 2026-07-03 | feat(mock): add mock submissions for owner account | +| 5427bc7 | 2026-07-03 | feat(servers): support claiming servers directly from ServersPage list | +| 55592ed | 2026-07-03 | feat(auth): support public auth bind endpoint | +| 804459c | 2026-07-03 | feat(panel): implement admin maintenance window settings page | +| dd7dff6 | 2026-07-03 | feat(panel): support importing parameters from submission with owner-restricted unapproved entries | +| a8701c1 | 2026-07-03 | fix(panel): prevent automatic wake during server claim in mock api | +| f749c2c | 2026-07-03 | style(panel): resolve double borders and uneven padding in server console | +| 5f402b1 | 2026-07-03 | fix(panel): force dark mode and pure black bg on server console card | +| b7d8000 | 2026-07-03 | feat(panel): implement dedicated LuckPerms permissions and groups management sub-page | +| e60784b | 2026-07-04 | fix(panel): eliminate page collapse and scroll shifts during LuckPerms query reload | +| 439f19e | 2026-07-04 | fix(panel): prevent page collapse and scroll shifts in players and bans management sections during reload | +| 83e57b4 | 2026-07-04 | fix(panel): implement two-step confirmation for claiming a server to prevent accidental operations | +| 3347cc0 | 2026-07-04 | feat(panel): implement user management administration panel with sessions and minecraft link support | +| 67b4e19 | 2026-07-04 | fix(panel): override generic already_exists error message during user creation and profile editing | +| 2c95da8 | 2026-07-04 | fix(panel/i18n): add missing users_col_user key to translation files | +| 627883e | 2026-07-04 | fix(panel): refine reset password messages and fix empty email placeholder in mock api response | +| 0c1cc59 | 2026-07-04 | feat(auth): migrate console login to passwordless | +| 3b43f05 | 2026-07-04 | refactor(api): drop dead login concurrency limiter and reconcile passwordless comments | +| 4f59d51 | 2026-07-04 | feat(auth): add owner-tier passkey-unbind remediation endpoint | +| c20b12c | 2026-07-04 | refactor(api): drop dead password-era ResetMailer, reconcile passkey-unbind docs | +| 0a2accd | 2026-07-04 | chore: stop tracking Autohand-generated AGENTS.md | +| 96b3cc9 | 2026-07-04 | feat(updater): wire updates.Run to a caller with PaperMC v3 release discovery | +| 9896fe1 | 2026-07-05 | docs(updater): correct PaperMC UA/fixture overclaims, re-tier the boundary | +| 7d27640 | 2026-07-05 | feat(updater): add GitHub Releases source and route felis-api/k3s/cloudflared | +| bd49313 | 2026-07-05 | refactor(panel): 抽取 10 个公共组件,消除 ~150 处重复代码 | +| 91bfa27 | 2026-07-05 | feat(operator): implement idle auto-stop (spec §8) | +| e574749 | 2026-07-05 | feat(api): enforce CPU/memory/storage quotas (spec §9.3, §22) | +| 7db57b9 | 2026-07-05 | feat(updater): add VersionGatherer extraction core and CLI gather seam | +| ec468ba | 2026-07-05 | feat(auth): add discoverable (usernameless) passkey login | +| 154002e | 2026-07-05 | docs(auth): cite MultiLogin reference for UUID-keyed reclaim split | +| 0dbd557 | 2026-07-05 | fix(store): renumber discoverable-login migration 0013 -> 0014 | +| 9e1df12 | 2026-07-05 | feat(passkey): advance sign_count, reject clone-warned assertions | +| 7f7e459 | 2026-07-05 | fix(operator): enforce startup and readiness timeouts (§5, §8) | +| 7becb38 | 2026-07-05 | fix(api): implement /readyz with real DB + K8s API + CRD checks (§7) | +| 9079a2c | 2026-07-05 | feat(panel): implement email otp and passkey login interface | +| cfe68ae | 2026-07-05 | fix(panel): align status distribution order to put Stopped at the end | +| bbcfaeb | 2026-07-05 | refactor(panel): remove redundant voxel network topology description subtitle | +| fdb6efb | 2026-07-05 | feat(account): migrate a live account's owned servers to a new account (§B3 inherit) | +| abad137 | 2026-07-06 | style(panel): unify vertical spacing below PageHeader across pages | +| c2ee21a | 2026-07-06 | feat(breakglass): add halt-a-server op to the recovery console (§B4) | +| c1aa38b | 2026-07-06 | feat(velocity): add /felis migrate to open an account migration (§B3 inherit) |