Files
Felis/docs/sequence-diagrams.md
T

6.6 KiB

Felis Sequence Diagrams

This file carries the spec section 28 sequence-diagram deliverables that are not covered by the OpenAPI artifact.

Section 28 #9: Ping To Join To Wake To Ready To Teleport

sequenceDiagram
    autonumber
    actor Player
    participant Velocity as Velocity proxy
    participant Registry as Velocity server registry
    participant API as felis-api internal face
    participant Cluster as MinecraftServer CRD/status
    participant Operator as felis operator
    participant Backend as Minecraft backend

    Player->>Velocity: server-list ping for subdomain.root-domain
    Velocity->>Registry: read cached lifecycle view
    Registry-->>Velocity: phase-aware MOTD
    Velocity-->>Player: ping response (read-only, no wake)

    Player->>Velocity: join subdomain.root-domain
    Velocity->>Registry: resolve host to server
    Registry-->>Velocity: ServerView(name, ready=false)

    alt backend already ready and registered
        Velocity-->>Player: initial server = backend
        Player->>Backend: connect
    else backend not ready and lobby configured
        Velocity-->>Player: initial server = lobby
        Velocity->>API: POST /api/v1/internal/servers/{name}/wake {mc_uuid}
        API->>Cluster: GetServer(name)
        API->>API: authorize autostartPolicy, cooldown, running cap
        API->>Cluster: SetDesiredState(name, Running)
        API-->>Velocity: 202 phase/ready
        Velocity->>Velocity: enqueue waiter
        Operator->>Cluster: reconcile DesiredState=Running
        Operator->>Backend: start pod/service
        Backend-->>Operator: RCON-ready / lifecycle ready
        Operator-->>Cluster: status.ready=true
        loop every waiting tick
            Velocity->>API: GET /api/v1/internal/servers/{name}/status
            API->>Cluster: GetServer(name)
            API-->>Velocity: ready flag
        end
        Velocity->>Registry: lookup registered backend
        Velocity-->>Player: "ready - moving you in"
        Velocity->>Player: Connect request to backend
        Player->>Backend: connect
        Velocity->>API: POST /api/v1/internal/servers/{name}/join-event {mc_uuid}
        API->>API: RecordJoin; refresh activity and allowlist UUID
        API-->>Velocity: 204
    else backend not ready and no lobby configured
        Velocity-->>Player: disconnect with reconnect-later message
        Velocity->>API: POST /api/v1/internal/servers/{name}/wake {mc_uuid}
        API->>Cluster: SetDesiredState(name, Running) if authorized
        API-->>Velocity: 202 or branchable error
    end

Section 28 #11: Claim Transaction

sequenceDiagram
    autonumber
    actor Player
    participant Panel as Web panel
    participant API as felis-api external face
    participant Repo as Repo / Postgres
    participant Audit as Audit log

    Player->>Panel: click Claim on ownerless server
    Panel->>API: POST /api/v1/servers/{name}/claim
    API->>API: validate server name and principal
    API->>Repo: IsLinked(user_id)
    alt user has no verified account link
        Repo-->>API: false
        API-->>Panel: 412 not_linked
    else linked
        Repo-->>API: true
        API->>Repo: QuotaCheck(user_id, the server's real size)
        Note over API,Repo: all four caps: servers, CPU, memory, storage
        alt quota exhausted
            Repo-->>API: false
            API-->>Panel: 403 quota_exceeded
        else quota available
            Repo-->>API: true
            API->>Repo: ClaimServer(name, user_id)
            Note over Repo: ONE transaction: pg_advisory_xact_lock(user_id) serializes this user's claim lane; SELECT FROM servers WHERE name=$1 AND deleted_at IS NULL FOR UPDATE; re-run the four-dimension quota gate (authoritative — the pre-check above is a fast path); then UPDATE servers SET owner_id=$2, claimed_at=now() WHERE name=$1 AND owner_id IS NULL AND deleted_at IS NULL
            alt server missing
                Repo-->>API: ErrNotFound
                API-->>Panel: 404 not_found
            else zero rows affected
                Repo-->>API: claimed=false
                API-->>Panel: 409 already_claimed
            else one row affected
                Repo-->>API: claimed=true
                API->>Audit: external claim audit
                API-->>Panel: 200 {"claimed":true}
            end
        end
    end
sequenceDiagram
    autonumber
    actor Player
    participant Game as Minecraft server or Velocity
    participant LinkClient as Felis LinkClient
    participant APIInternal as felis-api internal face
    participant Repo as Repo / Postgres
    participant Panel as Web panel
    participant APIExternal as felis-api external face

    Player->>Game: /link
    Game->>Game: read verified online-mode UUID
    Game->>LinkClient: requestCode(mc_uuid)
    LinkClient->>APIInternal: POST /api/v1/internal/account/link/code {mc_uuid}
    APIInternal->>APIInternal: validate UUID; derive auth_source from the UUID version nibble if absent (v3 → thirdparty, else mojang); generate 8-symbol code
    APIInternal->>Repo: CreateLinkCode(code, mc_uuid, auth_source, expires_at)
    Repo-->>APIInternal: inserted account_link_codes row
    APIInternal-->>LinkClient: 201 {code, expires_at, panel_url?}
    LinkClient-->>Game: LinkCode
    Game-->>Player: show one-time code in chat

    Player->>Panel: open Account link flow
    Panel->>APIExternal: POST /api/v1/account/link/start
    APIExternal->>Repo: IsLinked(user_id)
    Repo-->>APIExternal: linked status
    APIExternal-->>Panel: status and "run /link" instructions

    Player->>Panel: submit code
    Panel->>APIExternal: POST /api/v1/account/link/verify {code}
    APIExternal->>APIExternal: trim and uppercase code
    APIExternal->>Repo: VerifyLinkCode(user_id, code, now)
    Repo->>Repo: SELECT mc_uuid, auth_source FROM account_link_codes WHERE code=$1 AND expires_at>$2 FOR UPDATE
    alt missing or expired code
        Repo-->>APIExternal: ErrLinkCodeInvalid
        APIExternal-->>Panel: 400 invalid_code
    else UUID linked to a different, live user
        Repo-->>APIExternal: ErrConflict
        APIExternal-->>Panel: 409 already_linked
    else valid code (re-verify by the same user is idempotent; a retired/soft-deleted owner's link is taken over)
        Repo->>Repo: INSERT account_links(user_id, mc_uuid, auth_source) ON CONFLICT (user_id, mc_uuid) DO UPDATE auth_source
        Repo->>Repo: DELETE account_link_codes WHERE code=$1
        Repo-->>APIExternal: mc_uuid, auth_source
        APIExternal->>Repo: Audit account.link
        APIExternal-->>Panel: 200 {linked:true, mc_uuid, auth_source}
    end