diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2aca7a601..12e23c28d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,6 +34,9 @@ on: - "LICENSE" - "specs/**/outreach.md" - "specs/**/coverage-gaps.md" + # Feature catalog and the website gitlink feed the features sync gate (spec 024). + - "FEATURES.md" + - "website" # Cancel superseded runs so rapid pushes to the same ref don't waste # runner minutes on stale commits. concurrency: @@ -272,12 +275,16 @@ jobs: - 'mcp-server/README.md' - 'telemetry-receiver/README.md' - 'docs/comparison-sources.md' + - 'FEATURES.md' - 'hack/check-links.sh' - 'hack/check-doc-versions.sh' - 'hack/test-check-doc-versions.sh' - 'hack/testdata/check-doc-versions/**' + - 'hack/check-features-sync.sh' + - 'hack/test-check-features-sync.sh' + - 'hack/testdata/check-features-sync/**' # `docs/**` broadly: check-links resolves relative links from - # the 18 audited files against ANY file under docs/ (e.g. + # the 19 audited files against ANY file under docs/ (e.g. # docs/architecture.md -> docs/agent-architecture.md), not # just the other audited files, so renaming a non-audited doc # must still run the gate. @@ -297,6 +304,8 @@ jobs: - 'signing/2026-07-24-cosign-legacy.pub' - 'signing/cosign.pub.legacy-sig' - 'specs/**/outreach.md' + # website/ features page is checked against FEATURES.md by check-features-sync. + - 'website' - id: combine # Fold the raw filters into single-token booleans so every downstream # `if:` is one expression (fewer chances to get wrong when the only way @@ -561,6 +570,8 @@ jobs: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: init website submodule + run: git submodule update --init website - name: verify lint gate configuration run: ./test/e2e/lint-gate-verify.sh verify - name: check documentation links @@ -569,6 +580,10 @@ jobs: run: make check-doc-versions - name: test documentation version checker run: make test-doc-versions + - name: check features sync + run: make check-features-sync + - name: test features sync checker + run: make test-features-sync - name: check specs compliance run: make check-specs - name: check coverage gap record diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 000000000..adeb2add4 --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,245 @@ +# Gameplane features + +Maturity labels: **stable** (shipped, on by default, and exercised by a bucketed e2e test), **beta** (shipped with caveats or without a bucketed e2e test), **experimental** (shipped but the docs say so), **planned** (not started), **in progress** (started, not yet merged to master). + +Default column: **on** (enabled by the chart or the product), **off** (opt-in, ships disabled), or **n/a** (planned, in progress, or tooling with no install default). + +Notification rows take effect once a notification sink is configured. + +Status date: 2026-10-14 (v0.3.0, pre-v1). + +See [docs/roadmap.md](docs/roadmap.md) for what is planned and in progress. + +## Servers and lifecycle + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Create a game server from a module template | stable | on | Creates a GameServer from an installed module template through the create wizard or the API. | [README](README.md#features) | +| Start, stop and restart a server | stable | on | Start clears spec.suspend so the operator scales the pod back up, stop sets it so the pod scales to zero after the stop sequence, and restart stamps an annotation that restarts the pod without changing desired state. | [architecture](docs/architecture.md#start-a-server) | +| Show server phase and conditions | stable | on | Shows the server phase, such as Running or Suspended, with the reason conditions behind each. | [architecture](docs/architecture.md#start-a-server) | +| Clone a server into a new GameServer | stable | on | Copies the spec of a server into a new GameServer. | [README](README.md#features) | +| Switch game version on a running server | stable | on | Changes the template version of an existing server so the operator rolls it out. | [module-authoring](docs/module-authoring.md) | +| Delete a server | stable | on | Deletes a GameServer and its owned child objects. | [README](README.md#features) | +| Hand off server ownership and manage collaborators | beta | on | The owner or an admin can transfer a server to another user, and the owner can set collaborators who may use start, stop, restart, wake and clone. | [security](docs/security.md) | +| Graceful stop with the game's native shutdown sequence | beta | on | Runs the template stop sequence over RCON, telnet or stdin before the pod scales to zero so world saves finish. | [architecture](docs/architecture.md#sleep-an-idle-server) | +| Wipe world data with typed confirmation | beta | on | Suspends the server, erases the world volume and resumes, for owners or admins only. | [architecture](docs/architecture.md) | +| Browse and filter the server list across namespaces | beta | on | The Servers page lists servers from every namespace the viewer may read and filters them with All, Running and Stopped tabs. | [unified-dashboard](docs/unified-dashboard.md) | +| Keep game data on a PVC with clear storage errors | beta | on | Game data stays on its PVC when the game pod is deleted, and a missing storage class sets Ready to False with reason PVCProvisioningFailed; an install-time default storage class is opt-in. | [architecture](docs/architecture.md#start-a-server) | +| Edit game configuration from the Settings tab | in progress | n/a | Lets an operator change a server game configuration from its Settings tab, with validation. | [roadmap](docs/roadmap.md#in-progress) | + +## Console and admin actions + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Live console stream for PTY-mode games | stable | on | Attaches the browser to the container stdin and stdout through the pod attach API for templates with consoleMode pty. | [install](docs/install.md#pod-logs-and-pty-console-permissions) | +| Container startup output stream | beta | on | Streams the game container stdout through the pod log API before the game log file exists. | [install](docs/install.md#pod-logs-and-pty-console-permissions) | +| Run template quick actions | stable | on | Runs template-defined admin actions such as save, broadcast, time, weather and reload from buttons. | [module-authoring](docs/module-authoring.md#actions) | +| Validate action input and run actions on PTY-only games | beta | on | Validates action parameters against the template schema and escapes them before they reach the game console, and runs actions on PTY-console games by writing lines to the container stdin. | [dependencies](docs/dependencies.md#gameaction) | +| Live game log tail | stable | on | Streams the game log file over a WebSocket from the agent sidecar. | [architecture](docs/architecture.md#tail-logs) | +| Replay and download the full game log | beta | on | Replays up to the last 500 game-log lines when a tab opens, capped by the agent at 20,000 lines and 4 MiB, and downloads the whole log file without interrupting the live stream. | [architecture](docs/architecture.md#tail-logs) | +| Send commands over RCON and ten console protocols | beta | on | Sends line-based commands over RCON or any of the ten console protocols a template selects, including REST and CLI; running without a password (rcon.authentication false) is opt-in. | [module-authoring](docs/module-authoring.md#rcon) | +| Console toolbar with history, clear, download and fullscreen | beta | on | Recalls the last 100 sent commands with the arrow keys, kept only while the Console tab stays open, and toolbar buttons clear the terminal, download its buffer and toggle fullscreen without stopping the stream. | [README](README.md#features) | +| Kubernetes events timeline for a server | beta | on | Shows the 50 most recent Kubernetes events for the server pod and its parent objects. | [architecture](docs/architecture.md#start-a-server) | + +## Files + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Browse, edit, create, download and delete files | stable | on | Lists directories under the exposed data root, saves and creates text files through the agent write endpoint, and downloads or deletes one file, including large downloads that stream past the request timeout. | [security](docs/security.md#agent-file-browser-path-confinement) | +| Upload files and manage folders | beta | on | Uploads files into the open directory, writing them group-readable for the game process, creates folders, and removes a folder and its contents after confirmation, refusing folders that hold dot-prefixed entries. | [security](docs/security.md#agent-file-browser-path-confinement) | +| Confine file access to the exposed root | beta | on | Rejects path traversal and symlink escapes outside the exposed data root. | [security](docs/security.md#agent-file-browser-path-confinement) | + +## Players + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| View online players and capacity | stable | on | Shows the online count, the maximum and the player list, refreshed every 5 seconds, for templates that declare a player source. | [README](README.md#features) | +| Manage the whitelist, kick, ban and banned list | beta | on | Edits whitelist entries, kicks online players, adds or removes persistent bans and lists the banned players the game reports, through the template commands; kick returns HTTP 501 when the template does not declare it. | [README](README.md#features) | +| Steam display names on player lists | beta | off | Resolves Steam IDs to display names through the Steam Web API once a Secret is configured. | [install](docs/install.md#steam-display-names-optional) | + +## Mods + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Install, upgrade and upload mods | stable | on | Installs a mod file from a download URL with registry metadata in the per-volume mod manifest, replaces or removes an installed mod in place, and installs a multipart file upload as a file-drop mod. | [README](README.md#mod-management) | +| Reject path-traversal and symlink-escape mod archives | stable | on | Extracts valid mod archives while rejecting zip-slip paths and symlinks that escape the target directory. | [README](README.md#mod-management) | +| Install mods by launch-parameter ID | beta | on | Projects selected mod IDs into the launch environment for games that fetch mods at startup. | [module-authoring](docs/module-authoring.md#mods) | +| Browse and install from seven public registries | beta | on | Searches and installs mods from Modrinth, Thunderstore, Hangar, the Factorio Mod Portal, SpigotMC, GitHub Releases and uMod with no API key; Factorio asks for the user's factorio.com login in the install form, and Thunderstore and GitHub Releases need the template to name a community or an owner and repository. | [README](README.md#supported-registries) | +| Browse CurseForge, Steam Workshop and Nexus Mods | beta | off | Searches and installs CurseForge mods and Steam Workshop items once an API key is set under Settings, Mod registries; Nexus Mods is browse-only, and downloads are not supported. | [README](README.md#supported-registries) | +| Store and replace registry API keys from Settings | beta | on | Saves, replaces and removes registry keys under Settings, Mod registries, kept in Kubernetes Secrets or the standalone database. | [README](README.md#supported-registries) | +| Check installed mods for newer versions | beta | on | Reports available updates for mods installed from a provider that has a resolvable version. | [README](README.md#mod-management) | +| Install a modpack from a provider | beta | on | Installs a modpack either by pinning it into a server env (env-mode) or by installing its dependency mods (deps-mode). | [module-authoring](docs/module-authoring.md#modpacks-providersmodpacks) | + +## Backups and restore + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Run on-demand and scheduled restic backups | stable | on | Starts a Backup that runs restic against the server data volume and records the snapshot ID; a BackupSchedule creates Backups on its cron expression, records last and next times, can be suspended, and with a Forbid concurrency policy does not start a backup while one is active. | [architecture](docs/architecture.md#back-up) | +| Trim backups with keep-last retention and prune on delete | stable | on | Applies keepLast retention after each scheduled backup, where an empty policy keeps everything, and deleting a Backup runs restic forget with prune so its snapshot is removed. | [architecture](docs/architecture.md#backup-deletion-and-snapshot-cleanup) | +| Connect a restic repository through a labelled Secret | beta | on | Reads the restic repository and credentials from a labelled Kubernetes Secret referenced by repoRef, and points it at S3-compatible storage through the repository URL in that Secret. | [architecture](docs/architecture.md#back-up) | +| Report failed backups and rejected restores | stable | on | Sets a Backup to Failed when its data volume is missing or its repository credentials are rejected, and sets a Restore to Failed when its Backup is missing or has no successful snapshot. | [architecture](docs/architecture.md#back-up) | +| Restore a restic snapshot into an existing server | stable | on | Suspends the target GameServer, overwrites its data volume from a snapshot, and resumes it. | [architecture](docs/architecture.md#restore-from-backup) | +| Quiesce game writes before each backup | beta | on | Asks the game to flush and pause writes before the snapshot when quiesce is true, which is the default on Backups. | [architecture](docs/architecture.md#back-up) | +| Tune backup and restore Job retry limit and deadline | beta | on | Sets the retry count and wall-clock deadline for restic backup and restore Jobs through Helm values. | [architecture](docs/architecture.md#back-up) | +| Browse backups and restores on the dashboard Backups page | beta | on | Lists per-server and global backups and restores with location, server and phase filters and a detail drawer. | [architecture](docs/architecture.md#back-up) | +| Create and restore CSI volume-snapshot backups | beta | off | Captures a CSI VolumeSnapshot of the data volume instead of running restic when a volume snapshot class is set, and restores one into a new GameServer without touching the original server. | [architecture](docs/architecture.md#back-up) | +| Backup and restore drill runbook | planned | n/a | A documented runbook for periodic restore drills; the restore test exists but the runbook does not. | [roadmap](docs/roadmap.md#production-readiness-hardening-planned) | + +## Game modules and catalog + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Discover modules from OCI and upload sources | stable | on | The operator indexes an OCI module source and lists its modules and versions in ModuleSource status, and bundles uploaded through the API are stored in an upload source and indexed the same way. | [module-authoring](docs/module-authoring.md#module-sources) | +| Install modules and block deleting ones a server uses | stable | on | Applying a Module CR makes the operator create the cluster-scoped GameTemplate for that module, and a finalizer keeps a Module or GameTemplate in place while a GameServer still uses its template. | [module-authoring](docs/module-authoring.md#installing-a-module) | +| Reject unsafe or unreachable module sources | stable | on | Module source fetches go through the SSRF dial guard, which refuses unsafe destinations, and a Module reports a failure when its source registry cannot be reached. | [module-authoring](docs/module-authoring.md#module-sources) | +| Switch a running server to another module version | stable | on | Changes a GameServer to another catalog version and loader, keeping each version and loader on its own data volume. | [module-authoring](docs/module-authoring.md#version-catalog-specversions) | +| Browse the official catalog and merged module states | beta | on | The official OCI catalog holds 29 game modules and the chart enables 16 by default, and the Modules page shows available, installed, verified, failed and update states across all configured sources. | [module-authoring](docs/module-authoring.md#installing-a-module) | +| Pin or verify module bundles with digests and signatures | beta | off | Sets a Module digest so only that exact bundle installs, and checks bundle signatures against a cosign public key or a keyless Sigstore identity before installing. | [module-authoring](docs/module-authoring.md#verifying-and-pinning-bundles) | +| Use a Git repository, HTTP archive or local directory as a module source | beta | off | Reads module directories from a Git repository at a pinned ref, from a tar.gz or zip archive served over HTTP or HTTPS, or from a path mounted into the operator pod. | [module-authoring](docs/module-authoring.md#module-sources) | +| Complete specs.md for all 29 game modules | planned | n/a | Each game module carries a specs.md documenting its template configuration; 26 of 29 have one today. | [roadmap](docs/roadmap.md#game-module-template-specifications-planned) | +| Nuclear Option join through a load-balancer IP pool override | in progress | n/a | Lets Nuclear Option clients join through a load-balancer IP pool override; blocked until the server binding on UDP 7777 is documented. | [roadmap](docs/roadmap.md#in-progress) | + +## Module authoring + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Declare a mods capability in a GameTemplate | stable | on | A template declares file-drop paths or an ID list so the mod install flow knows where mods go. | [module-authoring](docs/module-authoring.md#mods) | +| Render an install wizard and declare typed admin actions | beta | on | A template config schema generates the install and settings form for a server, and admin actions with typed input definitions are rendered and validated by the console and API. | [module-authoring](docs/module-authoring.md#config-schema--wizard) | +| Author modules in the dashboard Modules builder | beta | on | The dashboard offers a builder that creates module content without the CLI. | [module-authoring](docs/module-authoring.md#5-web-dashboard-module-builder) | +| Scaffold, validate, preview and package modules with gp-module | beta | n/a | gp-module init generates a module directory for a chosen archetype, validate checks it offline with JSON output for CI, preview dry-runs the runtime config for a memory limit and user configuration, and package builds a local tar.gz bundle with size limits. | [module-authoring](docs/module-authoring.md#1-scaffolding-a-new-module-init) | +| Push and sign module bundles for OCI sources | beta | n/a | Publishes a packaged module bundle as an OCI artifact for a module source to index, and signs official bundles with a cosign key held in CI secrets. | [module-authoring](docs/module-authoring.md#pushing-a-bundle) | +| Editor JSON schemas for module.yaml and template.yaml | beta | n/a | Provides yaml-language-server schemas so editors validate and autocomplete module and template files. | [module-authoring](docs/module-authoring.md#editor-schemas) | + +## Networking and tunnels + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Expose a server through a ClusterIP Service | stable | on | The default expose mode fronts the game pod with a Service reachable only inside the cluster. | [networking](docs/networking.md) | +| Apply a per-server egress NetworkPolicy for the tunnel pod | beta | on | Creates an egress policy for a tunnel-enabled server that admits DNS, advertised ports and provider relay ports. | [tunnels](docs/tunnels.md#troubleshooting) | +| Expose a server through NodePort, LoadBalancer or Hostport | beta | off | Opt-in expose modes publish game ports on node ports with an optional pinned number, ask the cluster load-balancer address manager for an external address, or bind game ports on the node host, which the wake sentinel cannot hold. | [networking](docs/networking.md#address-pool-overview) | +| Request or change a named address pool or specific external IP | beta | off | Writes a MetalLB or Cilium pool preference or a specific address onto a LoadBalancer Service, recorded as unhonored when no address manager is set, and updates pool metadata on a running server, while whether the IP changes depends on the load balancer. | [networking](docs/networking.md#setting-an-address-pool-or-specific-address) | +| Report address assignment, conflicts and pool errors | beta | off | Shows Assigned, AssignmentPending, ServiceNotReady and other reasons on the AddressAssignment condition, reports AddressInUse when another GameServer holds the address, and reports PoolNotFound or AllocationFailed when the pool is absent or exhausted. | [networking](docs/networking.md#troubleshooting-address-assignment) | +| Relay players through frp, Tailscale or playit.gg | beta | off | Runs an frpc tunnel to your own frps server, joins the game pod to your tailnet with an auth key, or runs a playit tunnel whose public address is reported in status, with relay credentials stored as owned Secrets. | [tunnels](docs/tunnels.md#frp) | +| Keep the tunnel pod running while the game sleeps | beta | off | The tunnel Deployment keeps the relay connection and public address up while the game pod is asleep. | [tunnels](docs/tunnels.md#interaction-with-idle-auto-sleep-and-wake-on-connect) | +| Restrict game ingress with a sourceRanges allowlist | beta | off | Limits Service-port traffic to listed source ranges, and does not gate traffic arriving through a tunnel. | [tunnels](docs/tunnels.md#interaction-with-other-networking-fields) | +| Report conflicting networking options as a status condition | planned | n/a | Would flag redundant settings such as a public LoadBalancer plus a tunnel as an informational condition. | [tunnels](docs/tunnels.md#interaction-with-other-networking-fields) | + +## Idle sleep and wake + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Wake a sleeping server from the dashboard or wake verb | beta | on | POST /servers/{name}:wake clears the operator sleep marker so the game pod comes back, and the dashboard labels a slept server Asleep and offers Wake instead of Start. | [architecture](docs/architecture.md#sleep-an-idle-server) | +| Sleep idle servers after empty minutes | beta | off | Opt-in spec.idle lets the operator scale a server to zero after it reports no players for afterMinutes, with a 5-minute floor, running the module stop sequence first. | [architecture](docs/architecture.md#sleep-an-idle-server) | +| Explain and monitor idle sleep | beta | off | status.idle.reason and the dashboard explain why idle time is not accruing, a stale heartbeat or missing player count freezes the idle clock, and the gameplane_gameservers_idle gauge reports asleep or awake. | [architecture](docs/architecture.md#sleep-an-idle-server) | +| Wake a sleeping server on a cron wake window | beta | off | spec.idle.wakeWindows cron expressions in the cluster timezone clear the sleep marker at each tick. | [architecture](docs/architecture.md#sleep-an-idle-server) | +| Arm wake-on-connect and hold advertised ports while asleep | beta | off | spec.idle.wakeOnConnect makes the operator run a sentinel pod that holds advertised ports by switching Service endpoints during sleep, and Hostport servers bounce clients and exit on wake instead of holding them. | [architecture](docs/architecture.md#sleep-an-idle-server) | +| Wake on game joins with per-port protocols and try-again disconnects | beta | off | Minecraft server-list pings are answered without waking, Terraria joins wake through the Terraria parser, other games wake on traffic and drop the first client, and held joins past the 25-second deadline get a protocol-native try-again disconnect, with the protocol chosen per port. | [architecture](docs/architecture.md#sleep-an-idle-server) | + +## Multi-cluster + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Register, rotate and remove remote clusters | beta | on | Registering stores a remote Kubernetes target with its kubeconfig through the Clusters page or POST /clusters, PUT /clusters/{name}/kubeconfig rotates the credential in place, and removing a registration deletes the kubeconfig Secret the API created for it. | [multicluster-ui](docs/multicluster-ui.md#registered-clusters) | +| Create and read servers on a chosen registered cluster | stable | on | A create or read request with ?cluster= targets that cluster's own Kubernetes API, rejects unknown cluster ids with 400, and never falls back to a same-named local server when a remote target is missing. | [unified-dashboard](docs/unified-dashboard.md#data-and-authorization-contract) | +| Enforce per-cluster RBAC grants on remote servers | stable | on | A viewer bound only to the local cluster is forbidden from reading remote servers. | [multicluster-ui](docs/multicluster-ui.md#user-access) | +| Route module, registry and mod-ID writes to the selected cluster | beta | on | Mod and modpack configuration writes go to the selected cluster with resource-version and UID protection, and remote module catalog reads and writes need a cluster-wide modules:read or modules:manage grant there. | [remote-parity](docs/remote-parity.md#routing-and-identity) | +| Keep node join tokens and kubeconfig issuance local to the central cluster | stable | on | Node join token and kubeconfig endpoints are available only for the local cluster. | [multicluster-ui](docs/multicluster-ui.md#node-inventory) | +| Add nodes and download kubeconfig from the Cluster page | beta | off | With clusterOps enabled, the Cluster page mints a node join command and a downloaded kubeconfig, which grants kube-system and CSR-approval RBAC. | [install](docs/install.md#values-reference) | +| Show API health and node inventory per registered cluster | beta | on | The Clusters page shows Kubernetes API health, version and last check, and the Cluster page lists nodes and capacity for the chosen cluster, showing an error rather than local data when a remote read fails. | [multicluster-ui](docs/multicluster-ui.md#registered-clusters) | +| Combined counts, search and location filters across sites | beta | on | The dashboard totals authorized sites and labels partial totals, one search covers authorized servers from every site without identity collisions, and location filters narrow servers, backups and schedules. | [unified-dashboard](docs/unified-dashboard.md#user-experience) | +| Open canonical server links that carry cluster and namespace | beta | on | Direct links and fresh tabs target the named cluster and namespace, ignoring stored cluster selections. | [unified-dashboard](docs/unified-dashboard.md#acceptance) | +| Block unsafe remote dial addresses, with an optional CIDR allowlist | beta | on | Dial-time checks reject loopback, link-local, metadata, unspecified and multicast destinations for remote Kubernetes and gateway dials; limiting destinations to listed networks with GAMEPLANE_REMOTE_ALLOWED_CIDRS is opt-in. | [standalone-panel](docs/standalone-panel.md#install-and-register-remote-game-clusters) | +| Run remote agent operations through the mTLS gateway | beta | off | The optional private gateway carries files, mods, player data and capture files to a registered cluster over mTLS, stores the gateway URL and labelled credentials per cluster, and refuses agents too old for the UID route. | [multicluster-agent-gateway](docs/multicluster-agent-gateway.md#routing-and-identity) | +| Rotate gateway trust and revoke gateway access | beta | off | Repeated PUT rotates gateway credentials, and DELETE removes gateway access while keeping the cluster registration. | [standalone-panel](docs/standalone-panel.md#configure-remote-agent-access) | +| Keep games running when the gateway is down | beta | off | A gateway outage stops remote agent access but not games or the local operator. | [multicluster-agent-gateway](docs/multicluster-agent-gateway.md#failure-and-rotation-behavior) | +| Run the central panel without a local Kubernetes cluster | beta | off | api.standalone runs the dashboard and API with no operator and the central host never runs games; Docker Compose installs it bound to 127.0.0.1:8080, and module installs act on a selected registered cluster. | [standalone-panel](docs/standalone-panel.md#central-panel-on-kubernetes) | +| Manage the standalone panel encryption key | beta | off | Remote credentials are encrypted in SQL with a key file that must be backed up with the database; rotate-panel-key re-encrypts them offline, and a provisioned-key option requires an existing 32-byte key and never generates one. | [standalone-panel](docs/standalone-panel.md#rotate-the-master-encryption-key) | + +## Users, auth and access + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Sign in with local accounts and disable or re-enable local login | stable | on | Local accounts sign in with argon2id-hashed passwords and receive an HttpOnly, Secure, SameSite=Lax session cookie, logging out or a dashboard password reset ends the sessions on the server, and admins can disable local login from Authentication settings until it is re-enabled. | [security](docs/security.md#authentication) | +| Throttle login attempts and show generic login errors | stable | on | Wrong-password attempts are rate limited per source IP and return 429 once the bucket is exhausted, and the login page and other unauthenticated views show only generic messages such as invalid credentials. | [security](docs/security.md#client-ip-extraction-from-forwarded-headers) | +| Enforce a fixed permission catalog with built-in and custom roles | stable | on | Each API route maps to one permission from a fixed catalog and unmatched routes fail closed, the admin, operator and viewer roles are seeded, admins build custom roles from catalog subsets, and built-in roles are immutable. | [security](docs/security.md#authorization) | +| Bind roles per namespace and require cluster-wide grants for cluster permissions | beta | on | Per-namespace role bindings grant a role in one namespace while the wildcard namespace gives cluster-wide access, and cluster-scoped permissions such as users:manage need a cluster-wide binding. | [security](docs/security.md#authorization) | +| Restrict destructive server actions to the owner or an admin | stable | on | Delete, wipe, ownership transfer and collaborator edits require the server's owner or an admin. | [security](docs/security.md#per-gameserver-access-owner--collaborators) | +| Manage dashboard user accounts and remove users with their linked data | beta | on | Admins create users, change a primary role and manage per-namespace bindings from the Users page, and deleting a user removes SSO links, preferences, sessions, API tokens and role bindings and revokes the share links that account created. | [security](docs/security.md#authorization) | +| Refuse changes that would lock out every admin | beta | on | The API refuses an auth config save that leaves no sign-in provider enabled, counting the Helm provider as always enabled, and refuses to demote or delete the last user who can manage users. | [security](docs/security.md#lockout-guard-and-break-glass) | +| Save and customize dashboard theme preferences | beta | on | Any signed-in user can save personal theme preferences, and Settings, Theme and Appearance offers preset themes, custom colors with a contrast check, a CSS overlay, safe mode, and import and export. | [roadmap](docs/roadmap.md#shipped-in-v030) | +| Require the CSRF header on mutating requests | beta | on | Mutating requests must echo the CSRF cookie value in the X-Gameplane-CSRF header (double-submit). | [security](docs/security.md#authentication) | +| Share a read-only status link for one GameServer | beta | on | A token in a share link gives unauthenticated access to one GameServer's status and connection address, optionally with permission to wake it. | [security](docs/security.md#share-links) | +| Recover an admin with bootstrap-admin | beta | n/a | Running bootstrap-admin with --force resets a named account's password, promotes it to admin and ends its existing sessions, and --enable-local-login force-enables the local provider so a locked-out admin can sign in. | [security](docs/security.md#lockout-guard-and-break-glass) | +| Sign in with dashboard-managed or Helm-configured OIDC providers | beta | off | Admins add OIDC providers from Authentication settings with the client secret in a labelled Secret, applied without restart, and the api.oidc Helm values add a read-only provider that the dashboard cannot edit, disable or delete. | [security](docs/security.md#dashboard-managed-providers) | +| Map IdP group claims to dashboard roles | beta | off | Helm-seeded group mappings assign the most privileged matching role at install time, the role is recomputed at every OIDC login, and admins can override a role's group list from Authentication settings, effective at the next login. | [oidc](docs/oidc.md#role-mapping-at-install-time-no-bootstrap-admin-required) | +| Follow OIDC setup guides for five identity providers | beta | n/a | Step-by-step guides cover registering Gameplane as an OIDC client in Keycloak, Authentik, Google, Okta and Entra ID. | [oidc](docs/oidc.md#provider-guides) | +| Service accounts on the Users page | planned | n/a | A Users page tab for service accounts is planned for v1.1. | [roadmap](docs/roadmap.md) | + +## Audit and security + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Record and page through the audit log | stable | on | Each mutating API request writes an audit event to the audit_events table that the dashboard serves, and GET /admin/audit pages and filters the events. | [install](docs/install.md#audit-log) | +| Export the audit trail and verify its hash chain | beta | on | GET /admin/audit/export downloads the full trail as CSV or JSON, and GET /admin/audit/verify re-walks the SHA-256 chain and reports the first broken link. | [security](docs/security.md#audit-log-integrity) | +| Forward audit events to stdout or a webhook | beta | off | Mirrors each event as a structured JSON log line when api.audit.stdout is set, and POSTs each event as JSON from a bounded best-effort buffer when api.audit.webhook.url is set, with an optional Authorization header read from a Secret. | [install](docs/install.md#audit-log) | +| Ship audit events to syslog or S3-compatible storage | beta | off | The bundled audit-syslog-bridge turns events into RFC 5424 records sent over TCP, TCP with TLS or UDP, with TCP recommended, and api.audit.s3 batches NDJSON objects flushed at 100 events, 1 MiB or 5 seconds. | [audit-syslog-bridge](audit-syslog-bridge/README.md#transport-prefer-tcp) | +| Prune audit events past a retention age | beta | off | api.audit.retentionDays removes events older than N days, and the default of 0 keeps events forever. | [install](docs/install.md#audit-log) | +| Resolve client IPs through trusted proxies | beta | on | The API reads X-Forwarded-For only from peers inside api.trustedProxies, so audit records and per-IP limits use the real client address. | [security](docs/security.md#client-ip-extraction-from-forwarded-headers) | +| Run Gameplane pods hardened and optionally enforce restricted Pod Security | beta | on | Operator, API, agent and optional bridge pods run as uid 65532 with read-only root filesystems, RuntimeDefault seccomp and all capabilities dropped, and podSecurity.enforceRestricted, off by default, applies the restricted Pod Security Standard to the games namespace. | [security](docs/security.md#pod-security) | +| Redact password values and confine credentials to labelled Secrets | beta | on | API responses replace non-empty GameServer password values with a redaction marker and fail closed if the template cannot be read, and the API reads and deletes only Secrets carrying its own labels for auth providers and notification sinks. | [security](docs/security.md#gameserver-config-passwords) | +| Encrypt agent and capture control traffic with mTLS | stable | on | Operator-managed agent and capture-sidecar control connections use mutual TLS 1.2 or newer with a verified client certificate. | [security](docs/security.md#authentication) | +| Isolate pods with NetworkPolicies | stable | on | Applies NetworkPolicies that restrict ingress to game servers and give the API pod an egress policy that allows private ranges. | [security](docs/security.md#networkpolicies) | +| Audit OIDC role assignments and mapping overrides | beta | off | Each OIDC role assignment, override and reset writes an audit event naming the provider, matched group and role transition. | [security](docs/security.md#audit-trail-every-assignment-override-and-reset) | +| Verify release signatures with the current cosign key | beta | n/a | The ECDSA P-256 cosign key verifies releases from the July 2026 rotation onward, and the retired Ed25519 key verifies earlier releases. | [key-rotation](docs/key-rotation.md#verifying-artifacts) | +| Sign the audit chain with a keyed HMAC | planned | n/a | An HMAC-keyed audit chain with the key held outside the database is a candidate future hardening. | [roadmap](docs/roadmap.md) | + +## Notifications + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Store sink credentials in API-created Secrets | beta | on | Entering a sink's URL, ntfy topic or SMTP settings stores them in a Secret the API creates and labels, linked through configRef. | [notifications](docs/notifications.md#sink-credentials-secrets) | +| Use pre-created labelled Secrets for sinks | beta | n/a | A Secret made with kubectl or GitOps works when it carries the gameplane.local/notification-sink label and a matching configRef. | [notifications](docs/notifications.md#sink-credentials-secrets) | +| Alert on unhealthy servers, failed backups and restores | beta | on | server.unhealthy fires when a GameServer fails or loses its heartbeat and server.recovered when it becomes healthy again, backup.failed and restore.failed fire when a Backup or Restore enters the Failed phase, and backup.succeeded and restore.succeeded are opt-in events that a sink must select. | [notifications](docs/notifications.md#events) | +| Deliver alerts through a bounded queue with retries, test sends and metrics | beta | on | Notifications are sent from a bounded in-memory queue so a slow sink never blocks reconciliation or a request, network errors and 5xx responses are retried twice with 2 and 8 second backoffs, a POST to the sink test endpoint delivers a synthetic event, and gameplane_notify_deliveries_total counts sent, failed, dropped and skipped deliveries. | [notifications](docs/notifications.md#delivery-semantics-and-observability) | +| Guard sink dials and keep sink URLs out of errors | beta | on | Every sink dial goes through the netguard IsAllowed policy and refuses link-local, unspecified, multicast and NAT64 destinations at dial time, and delivery errors are sanitized so a sink URL carrying a capability token never appears in them. | [security](docs/security.md#notifications) | +| Label alerts with the instance name and post JSON webhook payloads | beta | on | The Admin Settings instance name is sent as the instance field of each alert, and the generic webhook receives a JSON object with type, ts, kind, name, namespace, message, instance and test fields. | [notifications](docs/notifications.md#payloads) | +| Send alerts to Discord, Slack, ntfy, SMTP or a generic webhook | beta | off | Admins add sinks in Admin Settings, each with its own payload format (Discord embed, Slack text, ntfy headers, SMTP plain text, webhook JSON), and SMTP takes a TLS mode of starttls (default), implicit or none; none only works for unauthenticated relays. | [notifications](docs/notifications.md#sink-credentials-secrets) | + +## Telemetry + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Send daily usage counts with admin and install-time off switches | stable | on | The API sends its version, GameServer count and GameTemplate count once a day with no names, namespaces or addresses, holds reports until the first admin sees the first-login notice, and stops when turned off in Admin Settings or hard-disabled at install time. | [install](docs/install.md#telemetry) | +| Send extended usage reports with a resettable, signed install ID | beta | on | Extended reports add a random install ID, environment bands, per-module counts, feature flags and UI language from fixed sets, are signed with an Ed25519 install key, and go to the project default receiver unless a custom endpoint is set; admins can reset the install ID. | [install](docs/install.md#telemetry) | +| Run a bundled telemetry receiver from Helm | beta | off | api.telemetry.receiver.enabled deploys a receiver beside the install with a PVC for its SQLite data, which decodes reports strictly and keeps only daily aggregates and expiring activity records, expires old data on a retention schedule, and pseudonymises install IDs with a pepper. | [telemetry-provider](docs/telemetry-provider.md#deploy-on-kubernetes) | +| Limit and proof-of-work gate report ingestion on the receiver | beta | off | INGEST_SOURCE_DAILY_LIMIT caps accepted reports per source per UTC day, with a default of 20, and INGEST_POW makes reporters solve a challenge whose difficulty rises with request rate. | [telemetry-provider](docs/telemetry-provider.md#configuration) | +| Expose receiver summary, dashboard and metrics | beta | off | GET /v1/summary returns five fixed counts without credentials when PUBLIC_SUMMARY is true, the receiver dashboard on port 8081 starts only when DASHBOARD_TOKEN is at least 32 characters, and /metrics accepts only the Bearer token. | [telemetry-provider](docs/telemetry-provider.md#public-summary) | +| Run a standalone telemetry provider | beta | n/a | A runbook covers deploying the receiver image, TLS, secrets, Cloudflare settings, metrics and backups for a provider serving other installs. | [telemetry-provider](docs/telemetry-provider.md) | +| Show a version-trend view in the receiver dashboard | in progress | n/a | A version-trend view in the telemetry dashboard is in progress under the spec 022 amendment T101 to T110. | [roadmap](docs/roadmap.md) | + +## Observability and admin + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Serve API, operator and agent Prometheus metrics | beta | on | The API serves Prometheus metrics on a private listener the public port does not expose, the operator exports controller-runtime metrics with the gameplane_gameservers and gameplane_backups phase gauges, and each game pod agent exposes gameplane_agent gauges on a plain metrics port 9090. | [install](docs/install.md#metrics) | +| Admin system log viewer | beta | on | Admins follow API and operator logs in separate streams, filter them, and download raw records. | [install](docs/install.md#observability) | +| Ship ServiceMonitor, PrometheusRule and Grafana objects from Helm | beta | off | Helm can create Prometheus Operator ServiceMonitor and PodMonitor objects that scrape the operator, API, telemetry receiver and agents, ship operator reconcile, workqueue, GameServer Failed and Backup Failed alert rules, and ship a Grafana operator dashboard ConfigMap for sidecar auto-import. | [install](docs/install.md#alerts) | +| Capture game pod packets and expire captures automatically | beta | off | Admins start opt-in, BPF-filtered PCAPNG captures on a running game pod and download the files, and completed captures are deleted automatically after their retention window ends. | [install](docs/install.md#values-reference) | + +## Localization + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Localized dashboard and community language packs | in progress | n/a | Spec 019 adds app localization and community language packs; the work is on open PR #668 and is not yet on master. | [roadmap](docs/roadmap.md#in-progress) | + +## Developer and operator tooling + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Install and upgrade the panel from the Helm chart | stable | on | One helm upgrade --install installs the operator, API and dashboard, and a pre-upgrade hook applies the Gameplane CRDs so every upgrade keeps the schema current. | [install](docs/install.md#one-shot-install) | +| Read-only MCP server | beta | off | An optional MCP server lets AI assistants read GameServers, pods, events and logs and propose fixes without writing. | [mcp-server](mcp-server/README.md#tools) | +| Helper image overrides for air-gapped mirrors | beta | n/a | Helm lets operators retag the config-init and restic helper images to a private registry mirror. | [install](docs/install.md#values-reference) | +| Rolling edge images | beta | off | Setting image.tag to edge runs the rolling build published on every master push. | [install](docs/install.md#edge-channel-latest-edge-build) | +| Local Kind development loop | beta | n/a | Make targets create a Kind cluster with a local OCI registry, load images and run the web dev server. | [contributing](docs/contributing.md#dev-loop) | +| Store API data in PostgreSQL | experimental | off | Runs the API against a PostgreSQL database instead of SQLite, but only in images built with the postgres build tag. | [install](docs/install.md#values-reference) | +| Reuse an existing PVC for the API database or standalone panel key | beta | off | Mounts a pre-existing PersistentVolumeClaim for the API SQLite database, or a pre-existing key-only PersistentVolumeClaim for generated standalone encryption keys, instead of creating one. | [install](docs/install.md#values-reference) | diff --git a/Makefile b/Makefile index 02f0df3fe..c1cd99fa7 100644 --- a/Makefile +++ b/Makefile @@ -255,6 +255,14 @@ test-doc-versions: ## Run the fixture tests for hack/check-doc-versions.sh check-links: ## Verify internal documentation links and anchors resolve hack/check-links.sh +.PHONY: check-features-sync +check-features-sync: ## Verify FEATURES.md and the website features page carry the same capability catalog + bash hack/check-features-sync.sh + +.PHONY: test-features-sync +test-features-sync: ## Run the fixture tests for hack/check-features-sync.sh + bash hack/test-check-features-sync.sh + .PHONY: check-telemetry-catalog check-telemetry-catalog: ## Verify telemetryschema/catalog.txt matches the chart's official module list hack/check-telemetry-catalog.sh @@ -273,7 +281,7 @@ test-up-registry: ## Run the fixture tests for deploy/kind/up.sh's registry/cont hack/test-up-registry.sh .PHONY: lint -lint: check-doc-versions check-links check-specs check-telemetry-catalog check-dev-load-images check-coverage-gaps test-up-registry lint-go lint-web ## Run all linters +lint: check-doc-versions check-links check-features-sync check-specs check-telemetry-catalog check-dev-load-images check-coverage-gaps test-up-registry lint-go lint-web ## Run all linters .PHONY: lint-go lint-go: ## Run golangci-lint across all modules diff --git a/README.md b/README.md index cf73f0c8a..958674914 100644 --- a/README.md +++ b/README.md @@ -67,29 +67,21 @@ are feature-complete for the v1 scope and stabilized for external testing. | Dimension | Gameplane | Pterodactyl | CubeCoders AMP | Agones | |-----------|-----------|-------------|----------------|--------| -| Deployment/runtime model | Combined Kubernetes install or standalone panel on Docker Compose; game workloads use Kubernetes CRDs and a controller-runtime operator, from k3s to multi-node clusters. [BETA] [G-a](docs/comparison-sources.md#gameplane-row-a) | Self-hosted Panel with PHP/MySQL/Redis dependencies; Wings backend manages Docker containers. [P-a](docs/comparison-sources.md#pterodactyl-row-a) | Web-based control panel supporting Windows (native) and Linux (Debian 10+). [C-a](docs/comparison-sources.md#cubecoders-row-a) | Kubernetes-native library extending K8s with GameServer/Fleet CRDs. [A-a](docs/comparison-sources.md#agones-row-a) | +| Deployment/runtime model | Combined Kubernetes install or standalone panel on Docker Compose; game workloads use Kubernetes CRDs and a controller-runtime operator, from k3s to multi-node clusters. [G-a](docs/comparison-sources.md#gameplane-row-a) | Self-hosted Panel with PHP/MySQL/Redis dependencies; Wings backend manages Docker containers. [P-a](docs/comparison-sources.md#pterodactyl-row-a) | Web-based control panel supporting Windows (native) and Linux (Debian 10+). [C-a](docs/comparison-sources.md#cubecoders-row-a) | Kubernetes-native library extending K8s with GameServer/Fleet CRDs. [A-a](docs/comparison-sources.md#agones-row-a) | | Scaling & auto-sleep | Opt-in idle auto-sleep with configurable wake windows, manual wake button, or wake-on-connect. Minecraft/Terraria full protocol support; others use packet heuristics. [optional] [G-b](docs/comparison-sources.md#gameplane-row-b) | Multi-node support with cron-based power scheduling; no dedicated idle/auto-sleep feature. [P-b](docs/comparison-sources.md#pterodactyl-row-b) | Instance Automatic Sleep feature; multi-server architecture with controller managing instances. [C-b](docs/comparison-sources.md#cubecoders-row-b) | Fleet autoscaling via buffer/webhook strategies; no idle/sleep state. [A-b](docs/comparison-sources.md#agones-row-b) | | Inbound connectivity (NAT traversal, relay) | Integrated frp, Tailscale, playit relay sidecars; playit mappings user-managed via playit.gg account. [optional; disabled by default] [G-c](docs/comparison-sources.md#gameplane-row-c) | No integrated relay sidecars; manual proxy/port forwarding configuration required. [P-c](docs/comparison-sources.md#pterodactyl-row-c) | not publicly documented (checked 2026-09-02) [C-c](docs/comparison-sources.md#cubecoders-row-c) | No relay or NAT traversal features documented. [A-c](docs/comparison-sources.md#agones-row-c) | | Backup and restore | Restic snapshots to S3-compatible storage; on-demand or cron-scheduled via BackupSchedule; one-click restore. [G-d](docs/comparison-sources.md#gameplane-row-d) | Wings (local, default) or S3-compatible backup drivers; cron scheduling and on-demand backups. [P-d](docs/comparison-sources.md#pterodactyl-row-d) | not publicly documented (checked 2026-09-02) [C-d](docs/comparison-sources.md#cubecoders-row-d) | not applicable (Agones is a Kubernetes operator library) [A-d](docs/comparison-sources.md#agones-row-d) | -| Access control & authentication | Local argon2id + OIDC (Keycloak/Google/GitHub); three built-in roles (admin/operator/viewer); custom roles supported. [G-e](docs/comparison-sources.md#gameplane-row-e) | 2FA configurable per account or admin-only; subuser management via Artisan CLI. [P-e](docs/comparison-sources.md#pterodactyl-row-e) | Role-based access control; OIDC single-sign-on in Advanced Edition. [C-e](docs/comparison-sources.md#cubecoders-row-e) | not applicable (Agones is a Kubernetes operator library) [A-e](docs/comparison-sources.md#agones-row-e) | -| Game template distribution | OCI bundles via ModuleSource (git/http/oci/local/upload); optional cosign signature verification per source. 30 ready-to-use templates shipped. [G-f](docs/comparison-sources.md#gameplane-row-f) | Community eggs repository (eggs.pterodactyl.io) with nests and custom egg creation support. [P-f](docs/comparison-sources.md#pterodactyl-row-f) | Customizable templates framework; community-contributed templates available via external repositories. [C-f](docs/comparison-sources.md#cubecoders-row-f) | not applicable (Agones is a Kubernetes operator library) [A-f](docs/comparison-sources.md#agones-row-f) | +| Access control & authentication | Local argon2id + OIDC (Keycloak, Authentik, Google, Okta, Entra ID); three built-in roles (admin/operator/viewer); custom roles supported. [G-e](docs/comparison-sources.md#gameplane-row-e) | 2FA configurable per account or admin-only; subuser management via Artisan CLI. [P-e](docs/comparison-sources.md#pterodactyl-row-e) | Role-based access control; OIDC single-sign-on in Advanced Edition. [C-e](docs/comparison-sources.md#cubecoders-row-e) | not applicable (Agones is a Kubernetes operator library) [A-e](docs/comparison-sources.md#agones-row-e) | +| Game template distribution | OCI bundles via ModuleSource (git/http/oci/local/upload); optional cosign signature verification per source. 29 ready-to-use templates shipped. [G-f](docs/comparison-sources.md#gameplane-row-f) | Community eggs repository (eggs.pterodactyl.io) with nests and custom egg creation support. [P-f](docs/comparison-sources.md#pterodactyl-row-f) | Customizable templates framework; community-contributed templates available via external repositories. [C-f](docs/comparison-sources.md#cubecoders-row-f) | not applicable (Agones is a Kubernetes operator library) [A-f](docs/comparison-sources.md#agones-row-f) | | Multi-tenancy & multi-cluster | Remote registration through Cluster CRDs in combined mode or SQL in standalone mode; central user grants, remote Pod logs/PTY, and optional private gateway for agent operations. UID-bound capture downloads/cleanup; modpack and ID-list configuration on the selected cluster. Requires matching gateway, operator, agent and capture-sidecar versions. [G-g](docs/comparison-sources.md#gameplane-row-g) | Single Panel managing multiple nodes; no documented remote cluster or cross-cluster streaming. [P-g](docs/comparison-sources.md#pterodactyl-row-g) | Multi-server management via controller architecture; multi-tenancy not supported in AMP 2 (planned for AMP 3). [C-g](docs/comparison-sources.md#cubecoders-row-g) | Multi-cluster allocation via GameServerAllocationPolicy; allocator service with mTLS authentication. [A-g](docs/comparison-sources.md#agones-row-g) | | Licensing | GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). [G-h](docs/comparison-sources.md#gameplane-row-h) | MIT License (Panel and Wings). [P-h](docs/comparison-sources.md#pterodactyl-row-h) | Proprietary; per-instance tiers (Standard/Professional/Advanced/Enterprise). [C-h](docs/comparison-sources.md#cubecoders-row-h) | Apache License 2.0. [A-h](docs/comparison-sources.md#agones-row-h) | | Target operator scope (self-hosted vs. managed SaaS) | Self-hosted only; panel runs on Docker Compose or Kubernetes, while games require Kubernetes (k3s, kubeadm, managed services); no managed SaaS offering. [G-i](docs/comparison-sources.md#gameplane-row-i) | Self-hosted only; requires Linux system capable of running Docker containers. [P-i](docs/comparison-sources.md#pterodactyl-row-i) | Self-installed on user hardware (Windows or Linux); no managed SaaS version. [C-i](docs/comparison-sources.md#cubecoders-row-i) | Self-hosted operator software; runs anywhere Kubernetes can run. [A-i](docs/comparison-sources.md#agones-row-i) | ## Features -- **Graceful Lifecycle Management**: Create, start, stop, restart, clone, and delete game servers. Stopping or restarting executes the game's native shutdown sequence (over RCON, telnet, or stdin) first, ensuring world saves complete before stopping containers. -- **Smart Idle Auto-Sleep & Wake-on-Connect**: Save hardware resources by automatically scaling empty servers down to zero after a set idle period. Servers wake up automatically on scheduled cron windows, via the dashboard **Wake** button, or when players attempt to join. -- **Built-in Relay Tunnels**: Host servers on home labs or behind CGNAT without public IPs or router port-forwarding using integrated `frp`, `Tailscale`, or `playit.gg` relay sidecars. -- **Live Console & Admin Actions**: Stream stdout/stderr in real time over WebSockets, send RCON/stdin commands, and execute custom game admin actions (like broadcast messages or give items) safely with built-in injection guards. -- **Web File Manager**: Browse, edit, upload, and download server files directly in the browser with an integrated Monaco code editor. -- **Player Management**: View active players and issue kicks or bans for supported game protocols. -- **S3 Backups & Restores**: Perform on-demand or cron-scheduled restic snapshots to S3-compatible storage, with one-click restoration into server volumes. -- **OCI Game Modules**: 30 ready-to-use game templates packaged as OCI artifacts (Minecraft Java, Valheim, Terraria, Rust, Palworld, Factorio, CS2, etc.). -- **Extensive Mod Support**: Browse and install mods across 10 registries (Modrinth, CurseForge, Steam Workshop, Thunderstore, SpigotMC, Hangar, etc.) with support for both direct file-drop and launch-parameter mod IDs. -- **Authentication & RBAC**: Local user accounts plus OIDC SSO support (Keycloak, Google, GitHub) with fine-grained access permissions. -- **Multi-Cluster Fleet Management**: Register, monitor, and manage game servers across multiple Kubernetes clusters from a single dashboard. +Gameplane runs game servers as Kubernetes resources and manages them from one dashboard. Operators can create, start, stop, restart, clone, and delete servers, use a live console, edit files in the browser, and view players with kicks and bans where the game supports them. Mods install from 10 registries, restic backups go to S3-compatible storage with one-click restore, and the catalog ships 29 OCI game modules. Networking covers optional relay tunnels (frp, Tailscale, playit.gg) and idle auto-sleep with wake-on-connect, while multi-cluster registration, OIDC SSO, role-based access (admin, operator, viewer), and audit logging round out the platform. Notifications are available, and usage telemetry (daily counts; extended reports add a random install ID and its public key) is on by default for new installs and can be turned off in the Telemetry settings. + +See [FEATURES.md](FEATURES.md) for the full catalog with maturity and defaults. ## Mod Management @@ -159,7 +151,7 @@ legacy default for custom deployments. See [storage and recovery](docs/standalon | `capture-sidecar/` | Go | Network packet capture sidecar [optional], opt-in per server, admin-only. | | `tunnel/` | Go | Relay supervisor pod managing third-party tunnels (`frp`, `Tailscale`, `playit`) [optional]. | | `web/` | TS + React | Modern dashboard UI built with Vite, TanStack Query, xterm.js, and Monaco Editor. | -| `modules/` | YAML | 30 pre-packaged game templates (Minecraft, Valheim, Terraria, Rust, etc.) as OCI bundles. | +| `modules/` | YAML | 29 pre-packaged game templates (Minecraft, Valheim, Terraria, Rust, etc.) as OCI bundles. | | `charts/` | Helm | Official Helm deployment chart for operator, API gateway, ingress, and helper services. | | `gameproto/` | Go | Shared protocol library for parsing game handshakes (Minecraft, Terraria) in `sentinel`. | | `gameaction/` | Go | Security guard and command renderer for custom module admin actions. | diff --git a/charts/gameplane/values.yaml b/charts/gameplane/values.yaml index 2b02286df..00e680de6 100644 --- a/charts/gameplane/values.yaml +++ b/charts/gameplane/values.yaml @@ -118,7 +118,7 @@ operator: # empty string (default) = use cluster's default StorageClass # "fast-nvme", "gpu-attached", etc. = use named StorageClass # If the named class doesn't exist, PVC provisioning fails and GameServer - # status.conditions surfaces "StorageClassNotFound" reason. + # status.conditions surfaces the "PVCProvisioningFailed" reason. storageClassName: "" api: diff --git a/docs/comparison-sources.md b/docs/comparison-sources.md index 7e6f7418e..500a295d0 100644 --- a/docs/comparison-sources.md +++ b/docs/comparison-sources.md @@ -69,7 +69,7 @@ verified. **Source ID**: G-f **Evidence**: docs/module-authoring.md § Verifying and pinning bundles (lines 340–414); CLAUDE.md § Repository Map (`modules/`) **Checked on**: 2026-09-26 (re-verified) -**What was verified**: OCI bundles via ModuleSource CRD supporting git/http/oci/local/upload sources; cosign signature verification documented at lines 340–414; 30 ready-to-use templates in gameplane-module repository. +**What was verified**: OCI bundles via ModuleSource CRD supporting git/http/oci/local/upload sources; cosign signature verification documented at lines 340–414; 29 ready-to-use templates in gameplane-module repository. **Last-known URL**: https://github.com/GameplanePanel/Gameplane/tree/master/modules diff --git a/docs/dependencies.md b/docs/dependencies.md index 3cb4b0cae..1017fa980 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -45,7 +45,7 @@ were added and verified on **2026-09-26**. | `api/` | Go | 17 | chi REST + WebSocket gateway: auth (local + OIDC), RBAC, K8s client, SQLite/Postgres persistence, notifications | | `agent/` | Go | 6 | In-pod sidecar: RCON/WebRcon console, chi HTTP surface, heartbeat status patch, disk usage stats | | `audit-syslog-bridge/` | Go | 0 (stdlib only) | Standalone HTTP-JSON → syslog relay for the audit webhook sink | -| `telemetry-receiver/` | Go | 1 | Standalone collector for the API's opt-in anonymous usage telemetry | +| `telemetry-receiver/` | Go | 1 | Standalone collector for the API's usage telemetry (daily counts; extended reports add a random install ID and its public key; on by default for new installs; `api.telemetry.enabled=false` turns it off) | | `sentinel/` | Go | 2 | Wake-on-connect listener holding ports for sleeping pods | | `capture-sidecar/` | Go | 3 | Ephemeral AF_PACKET packet capture sidecar with BPF filtering | | `tunnel/` | Go | 0 (stdlib only) | Relay client supervisor for frp/Tailscale/playit | diff --git a/docs/install.md b/docs/install.md index 835cc2e7b..88523cb4f 100644 --- a/docs/install.md +++ b/docs/install.md @@ -123,7 +123,7 @@ Top-level knobs (see `values.yaml` for the full list): `--config-init-image` / `--restic-image` flags, mirroring `operator.agentImage` - `operator.backupJobBackoffLimit` / `operator.backupJobActiveDeadlineSeconds` — retry limit (default `2`, must be >= 0) and wall-clock deadline in seconds (default `86400`, must be > 0) for the restic backup/restore Jobs. They map to the operator's `--backup-job-backoff-limit` / `--backup-job-active-deadline-seconds` flags. Negative or non-integer values fail the render; an unset key (e.g. `helm upgrade --reuse-values` from an older release) or a `0` deadline uses the default, while an explicit `0` backoff is honoured. - `operator.gameDataStorage.storageClassName` — install-time default storage class - for game server data volumes (unreleased; ships in the next release) (default `""`). Empty string uses the cluster's + for game server data volumes (since v0.3.0) (default `""`). Empty string uses the cluster's default StorageClass. Applies to all GameServers where neither the GameTemplate nor GameServer-level override specifies a class. **Precedence**: GameServer override > GameTemplate default > install-time default > cluster default. @@ -145,7 +145,7 @@ Top-level knobs (see `values.yaml` for the full list): - Core connection: `issuer` / `clientID` / `clientSecretRef` / `redirectURL` / `displayName` — OIDC provider credentials and endpoints. Per-IdP walkthroughs (Keycloak, Authentik, Google) live in [oidc.md](oidc.md) - - Role mapping (new, seeded at install time) (unreleased; ships in the next release): + - Role mapping (new, seeded at install time) (since v0.3.0): - `groupsClaim` — OIDC claim name containing group memberships (default `""`). Typically `"groups"` or `"roles"` depending on your IdP. Empty/omitted = read the `"groups"` claim. This only chooses which claim is read; it does diff --git a/docs/oidc.md b/docs/oidc.md index 2305ecc7c..4ff29e276 100644 --- a/docs/oidc.md +++ b/docs/oidc.md @@ -18,7 +18,7 @@ Fill in: The callback URL is automatically set to `{External URL}/auth/oidc/{provider-name}/callback`. -**Note**: The Helm-flag single OIDC provider supports group→role mapping via Helm values `api.oidc.groupsClaim` and `api.oidc.roleMappings.{admin,operator,viewer}` (or CLI flags `--oidc-groups-claim` and `--oidc-role-mapping-{admin,operator,viewer}`) (unreleased; ships in the next release). See "Role Mapping at Install Time (No Bootstrap-Admin Required)" below for setup details and worked examples. +**Note**: The Helm-flag single OIDC provider supports group→role mapping via Helm values `api.oidc.groupsClaim` and `api.oidc.roleMappings.{admin,operator,viewer}` (or CLI flags `--oidc-groups-claim` and `--oidc-role-mapping-{admin,operator,viewer}`) (since v0.3.0). See "Role Mapping at Install Time (No Bootstrap-Admin Required)" below for setup details and worked examples. ## Provider guides @@ -324,7 +324,7 @@ api: If a user logs in and their IdP groups do not match any configured mapping (or the groups claim is absent from their token): - The user receives the role specified by `api.oidc.defaultRole` (default: `viewer`) - If `defaultRole` is set to `"deny"`, the login is rejected and the user is not created -- Once logged in, an admin can manually promote the user via Admin Settings → Users if needed +- Once logged in, an admin can manually promote the user via the Users page (sidebar: Users & RBAC) if needed **Note**: `groupsClaim` and `defaultRole` are **Helm-only settings in v1** — they are set at install time via Helm values and cannot be changed from the dashboard. Only `roleMappings` can be overridden by admins through the dashboard (see below). @@ -380,7 +380,7 @@ A user in both `gameplane-admins` (admin, Helm-seeded) and `dev-team` (viewer, D | "issuer discovery failed" (login page won't load) | **Wrong issuer URL or TLS failure.** Verify the exact issuer URL in your IdP's OIDC endpoints, including trailing slash if present. Test locally: `curl https://issuer/.well-known/openid-configuration`. If the IdP uses a private CA, the API container must trust it — there is no chart value for this today, so either use a publicly-trusted certificate on the IdP or mount your CA into the API pod's system trust store. | | "redirect_uri mismatch" at login | **External URL changed after registering the app.** If you set a new `External URL` in Admin Settings, update the redirect URI in your IdP's client config to match `{new-url}/auth/oidc/{provider-name}/callback`. | | "state mismatch" (blank login error) | **Cookies blocked or clock skew.** Ensure your browser accepts session cookies (`gameplane_oidc_state`, `gameplane_oidc_nonce`) and that server and IdP clocks are synchronized (within 5 minutes). Check browser DevTools → Network/Cookies for blocked third-party cookies if in an iframe. | -| Login succeeds but user is stuck at viewer role | **New OIDC users start as viewer.** An admin must promote them under Admin Settings → Users. From v0.2.0-beta.6+, group claims can map to roles automatically; check Authentication settings for group-based role mapping. | +| Login succeeds but user is stuck at viewer role | **New OIDC users start as viewer.** An admin must promote them on the Users page (sidebar: Users & RBAC). From v0.2.0-beta.6+, group claims can map to roles automatically; check Authentication settings for group-based role mapping. | | Login succeeds but role did not update when groups changed at IdP | **Last-admin demotion guard.** When group→role mapping is enabled, users' roles are re-synced from their IdP groups on each login. To prevent lockout, the system blocks any role change that would remove the *last* user capable of managing other users (i.e., the last admin or user-manager). If your group assignment change would demote the sole user-manager, the demotion is silently skipped: the login succeeds, but the user's stored role remains unchanged. To confirm the demotion guard was triggered, check the API pod logs for a warn-level entry containing `oidc role resync skipped`. Verify you have at least one other admin or user-manager in a different group, then have the affected user log in again. | | User created in one IdP logs in as a different account after linking IdP accounts | **Account linking is by OIDC identity, not email.** OIDC identities are permanently linked to Gameplane accounts by (issuer, subject) — the combination of your identity provider and the user's subject claim (a unique per-provider identifier, not email). If a user's email address or display name changes at the IdP, they will still log in as the same Gameplane account. However, if you add a different IdP or reconfigure an existing one to use a different subject attribute, that user will be treated as a new person on first login with the new issuer/subject pair. To prevent duplicate accounts, ensure your IdP subject claim is stable and won't change if you reconfigure the provider. | | Provider button missing from login page | **Provider disabled or secret missing.** Verify in Admin Settings → Authentication that the provider is enabled and has a client secret stored. If the secret was deleted from the control-plane namespace (label `gameplane.local/auth-provider=true`), recreate it with the exact name shown in the provider's config. | diff --git a/docs/roadmap.md b/docs/roadmap.md index 3d4ffd819..617c47f38 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,21 +1,64 @@ # Roadmap to v1 -Gameplane is currently **`v0.3.0`**. The CRDs, operator, API, agent, and -dashboard are feature-complete for the v1 scope and stabilized for external -testing, but the project is not yet recommended for unattended production. This -page tracks what stands between v0.3 and a v1 GA release. +## Current release + +- **Version:** `v0.3.0` (pre-v1), released 2026-10-05. See the [changelog](../CHANGELOG.md). +- **Status as of 2026-10-14:** The CRDs, operator, API, agent, and dashboard are + feature-complete for the v1 scope and stabilized for external testing, but the + project is not yet recommended for unattended production. This page tracks what + stands between v0.3 and a v1 GA release. +- **What works today:** see [`README.md`](../README.md) and [`architecture.md`](architecture.md). It is a living document: items move out of it as they ship, and anything -discovered along the way gets added. For what already works today, see -[`README.md`](../README.md) and [`architecture.md`](architecture.md). +discovered along the way gets added. --- -## Shipped toward v1 - -These items have shipped and are reflected in current `main`. - -### Multi-cluster: dashboard cluster selector (PR #107) (shipped v0.2.0-beta.6) +## Shipped in v0.3.0 and since + +### Since v0.3.0 (Unreleased) + +- Independent central panel: Docker Compose runs the API and dashboard without a host Kubernetes cluster; a standalone Helm profile runs without the central operator or workloads ([changelog](../CHANGELOG.md)). +- Credential maintenance: standalone key custody, offline `rotate-panel-key`, `PUT /clusters/{name}/kubeconfig`, and `PUT`/`DELETE /clusters/{name}/gateway` ([changelog](../CHANGELOG.md)). +- Extended telemetry tier: a second, separately consented tier with a random install ID and fixed-set categories; signed reports ([changelog](../CHANGELOG.md)). +- Telemetry receiver dashboard and an optional public `GET /v1/summary` ([changelog](../CHANGELOG.md)). +- Proof-of-work on telemetry ingestion, off by default ([changelog](../CHANGELOG.md)). +- Telemetry provider runbook at `docs/telemetry-provider.md` ([changelog](../CHANGELOG.md)). +- Optional Steam display names on player lists, via a Steam Web API key ([changelog](../CHANGELOG.md)). +- Nuclear Option player lists are structured (`steamId`, `faction`) ([changelog](../CHANGELOG.md)). +- New optional `GameTemplate` field `spec.rcon.authentication` (default `true`) allows consoles with no RCON Secret ([changelog](../CHANGELOG.md)). +- Standalone credential protection: keys kept outside database volumes, transactional rotation ([changelog](../CHANGELOG.md)). +- Remote workload boundaries: verified TLS, dial-time unsafe-destination rejection, bounded responses; `GAMEPLANE_REMOTE_ALLOWED_CIDRS` / `api.remoteAllowedCIDRs` ([changelog](../CHANGELOG.md)). +- Removed: the Hell Let Loose module is withdrawn from the official catalog ([changelog](../CHANGELOG.md)). +- Upgrade-note behaviour changes: telemetry is on by default for new installs after a first-login notice; installs with a saved basic toggle start sending to the default receiver after upgrade; an empty `api.telemetry.endpoint` means the project default receiver; the receiver `/metrics` moved to `:8081` and needs the dashboard token; the bundled receiver stores data on a PVC by default; receiver `DASHBOARD_TOKEN` must be at least 32 characters ([changelog](../CHANGELOG.md)). +- Breaking: selected-cluster module and template grants need a cluster-wide grant for remote catalogs ([changelog](../CHANGELOG.md)). +- Upgrade note: every game pod gains an empty capture `emptyDir`, so existing GameServers roll once on upgrade ([changelog](../CHANGELOG.md)). + +### Shipped in v0.3.0 + +- Configurable backup/restore Job retry limit and deadline Helm values ([changelog](../CHANGELOG.md)). +- Multicluster gateways and unified server management across clusters ([changelog](../CHANGELOG.md)). +- User theme customization: presets, custom colors with a WCAG AA check, custom CSS overlay, safe mode ([changelog](../CHANGELOG.md)). +- Network capture sidecar: admin-only, on-demand PCAPNG capture from running game pods ([changelog](../CHANGELOG.md)). +- Module authoring toolkit `gp-module` (scaffold, validate, preview, package) and the dashboard Modules builder ([changelog](../CHANGELOG.md)). +- 13 new top Steam game modules ([changelog](../CHANGELOG.md)). +- Nuclear Option dedicated server support ([changelog](../CHANGELOG.md)). +- Install-time config: Helm-seeded OIDC role mappings and a default StorageClass for game-data PVCs ([changelog](../CHANGELOG.md)). +- Share link expiry options (presets, custom date, no expiry) replacing the 90-day cap ([changelog](../CHANGELOG.md)). +- Console WebSocket robustness on non-default ports, and input queueing ([changelog](../CHANGELOG.md)). +- Existing PVC for SQLite via `api.storage.existingClaim` ([changelog](../CHANGELOG.md)). +- REST and CLI console protocols for games with HTTP admin APIs or PTY-only consoles ([changelog](../CHANGELOG.md)). +- Mod archive path confinement (zip-slip and symlink guards) ([changelog](../CHANGELOG.md)). +- Design and visual polish: HeroUI foundation and brand re-theme ([changelog](../CHANGELOG.md)). +- Experimental PostgreSQL backend works end to end ([changelog](../CHANGELOG.md)). +- Owner-only server operations (transfer, collaborators, wipe, delete) need the owner or an admin ([changelog](../CHANGELOG.md)). +- Data wipe failures fail the Job and set `DataWipe=False`; backup/restore quiesce lifecycle fixed, and restore removes files created after the snapshot ([changelog](../CHANGELOG.md)). +- SQLite DSN gets `busy_timeout(5000)` by default; agent metrics on a separate unauthenticated `:9090` listener that the PodMonitor scrapes ([changelog](../CHANGELOG.md)). +- Multi-cluster request scoping, OIDC role re-evaluation, bootstrap-admin reset, cluster registration removal, mod bundle integrity, and tunnel credential handling hardened ([changelog](../CHANGELOG.md)). + +### Earlier releases (before v0.3.0) + +#### Multi-cluster: dashboard cluster selector (PR #107) (shipped v0.2.0-beta.6) The initial dashboard selector routed requests to one cluster at a time. The [unified dashboard](unified-dashboard.md) now combines authorized resources with @@ -33,12 +76,12 @@ sidecars together; direct Kubernetes connectivity remains required. Dedicated gateway health in the dashboard remains follow-up work. The existing cluster-health indicator reports Kubernetes connectivity only. -### Multi-cluster: dual-cluster e2e coverage (PR #104) (shipped v0.2.0-beta.6) +#### Multi-cluster: dual-cluster e2e coverage (PR #104) (shipped v0.2.0-beta.6) A real `kind ×2` e2e test (multicluster bucket) asserting `?cluster=` dispatch lands a GameServer on cluster B and that a viewer scoped to cluster A cannot see it. -### Audit log integrity (tamper evidence) (PR #103, dashboard banner PR #108) (shipped v0.2.0-beta.6) +#### Audit log integrity (tamper evidence) (PR #103, dashboard banner PR #108) (shipped v0.2.0-beta.6) Complete end-to-end. Backend: migration `005_audit_chain.sql`, `Auditor.insertChained`/`Auditor.Verify`, and `GET /admin/audit/verify`. The @@ -73,7 +116,7 @@ remaining gap and is a candidate future hardening, not yet implemented. Threat-model context lives in [`security.md`](security.md#audit-log-integrity). -### Upgrade testing (`e2e upgrade` bucket) (shipped v0.2.0-beta.8) +#### Upgrade testing (`e2e upgrade` bucket) (shipped v0.2.0-beta.8) `helm upgrade` was the most dangerous operation a user performs and the only one CI never exercised. It now runs on every PR, on both amd64 and arm64: @@ -97,7 +140,7 @@ while, but [`install.md`](install.md#helm-crd-caveat) still told users to run Not yet covered: upgrades skipping several releases at once, and Postgres [experimental]. -### Idle auto-sleep (backend PR #180, dashboard PR #182) (shipped v0.2.0-beta.8) +#### Idle auto-sleep (backend PR #180, dashboard PR #182) (shipped v0.2.0-beta.8) Opt-in per server (`spec.idle`): the operator scales a GameServer to zero once it has reported no online players for `afterMinutes`, and brings it back on a @@ -140,7 +183,7 @@ browser would duplicate scheduling semantics the operator owns (rule 10). Persisting it on status is the right fix, and is a CRD change rather than a dashboard one. -### Wake-on-connect for idle auto-sleep (shipped v0.2.0-beta.8) +#### Wake-on-connect for idle auto-sleep (shipped v0.2.0-beta.8) A sentinel [optional] component holds advertised ports while a server is asleep and wakes it on a genuine connection attempt. Opt-in per server via `spec.idle.wakeOnConnect` @@ -172,11 +215,11 @@ it on a genuine connection attempt. Opt-in per server via `spec.idle.wakeOnConne Service-backed modes (ClusterIP/NodePort/LoadBalancer) proxy through until Ready. - Costs one small pod per sleeping server, which is why it is opt-in. -**Honest limitations:** Only Minecraft and Terraria get real parsing; the other -14 shipped modules use generic (they are UDP-only and have no connection to -hold). Hostport is asymmetric as described. +**Honest limitations:** Only Minecraft and Terraria get real parsing; tModLoader +also declares the Terraria parser. The other 26 of the 29 catalog modules use the +`generic` default (they hold no connection to parse), and hostport is asymmetric as described. -### Read-only MCP server (PR #105) (shipped v0.2.0-beta.6) +#### Read-only MCP server (PR #105) (shipped v0.2.0-beta.6) An [MCP](https://modelcontextprotocol.io) server letting an AI assistant read current cluster state and *propose* fixes — strictly read-only, no writes. @@ -186,7 +229,7 @@ A new optional component [optional] (distroless Docker image, Helm toggle) expos "Propose a fix" returns suggested YAML or `kubectl` invocations as text — no create/update/delete/patch tool exists. See [`mcp-server/README.md`](../mcp-server/README.md). -### Module signing: active for official bundles (ECDSA P-256, Rekor-logged) (shipped v0.2.0-beta.8) +#### Module signing: active for official bundles (ECDSA P-256, Rekor-logged) (shipped v0.2.0-beta.8) The keyed-cosign signing mechanism is implemented and e2e-proven, and `ModuleSource.spec.verify` can require a valid signature. It is now **active** @@ -201,7 +244,7 @@ fail-closed when the key is absent. The operator's verify path remains offline/keyed, preserving air-gapped functionality. See [Signing official bundles](module-authoring.md#signing-official-bundles). -### Hermetic module images (gameplane-module#35, docs in #178) (shipped v0.2.0-beta.8) +#### Hermetic module images (gameplane-module#35, docs in #178) (shipped v0.2.0-beta.8) Every shipped module's default image is now pinned by digest, so a server binary can no longer change underneath a user on pod restart with no version @@ -218,6 +261,22 @@ opt-out), and a monthly workflow refreshes the pins. Authoring rules live in --- +## In progress + +Status as of 2026-10-14. Task counts come from each spec's `tasks.md`. + +- **Spec 002, Nuclear Option module and load-balancer IP pool override** (`specs/002-nuclear-option-ip-pool/`): 136 of 143 tasks done. Blocked: the server does not bind UDP 7777, so no join client can be built until a real-client packet capture documents the handshake (maintainer decision, 2026-10-11). Unblocked by: that packet capture. The join test, its bucket and the coverage row wait on it. +- **Spec 009, code scanning remediation and Dependabot** (`specs/009-remediate-security-dependabot/`): 80 of 82 tasks done. Blocked: TypeScript 7 is blocked because the `@typescript-eslint` peer range stops before TypeScript 7, and ESLint 10 is blocked because `eslint-plugin-react` does not accept `^10`. Unblocked by: a `@typescript-eslint` release that accepts TypeScript 7 (then T055/T056 and PR #272), and an `eslint-plugin-react` release that accepts `^10` (then PR #386, and #387 if open). +- **Spec 013, expand test coverage** (`specs/013-expand-test-coverage/`): 39 of 62 tasks done. No external blocker is named. Open work is the live web e2e specs, the optional-components e2e, the Postgres e2e, docs and checks, and the archive step. Some tasks wait for the first green CI run of their job, and the archive waits until every PR is merged. Open PRs for this spec are in progress. +- **Spec 019, app localization and community language packs** (`specs/019-localization-language-packs/`): 0 of 105 tasks checked; the four design decisions are settled. The implementation is on open PR #668 and is not yet on `master`. Unblocked by: merging PR #668, which needs human approval. The first step is the `i18n/` module and checker. +- **Spec 021, edit game configuration from server Settings** (`specs/021-settings-game-config/`): implemented, 20 of 24 tasks done, pending CI and review. Blocked: CI must go green and a human must approve the PR. Unblocked by: that approval and merge, then archiving to `done_021`. +- **Spec 022, default telemetry destination, extended telemetry and telemetry dashboard** (`specs/022-default-telemetry-dashboard/`): 109 of 119 tasks done. The shipped feature is unchanged until the version-trend amendment (2026-10-14, T101 to T110) lands. Blocked: T104 needs Pencil design work on the machine where Pencil runs; T109 needs the user's sign-off before editing existing tests `lifecycle_test.go:11` and `charts_test.go:83`; T110 waits for the PR to merge. Unblocked by: the design work, that sign-off, and the merge. +- **Spec 023, change-based test selection** (`specs/023-change-based-test-selection/`): 0 of 52 tasks done; no open decisions remain. No blocker is named. T001 creates the `ci: full-run` label. T052 needs 30 merged PRs to measure runner minutes. +- **Spec 023, standalone panel and API** (`specs/023-standalone-panel/`, see [standalone panel](standalone-panel.md)): the core behaviour is listed under Unreleased in the [changelog](../CHANGELOG.md). The spec has no `tasks.md`, so the remaining work is not tracked. Unblocked by: writing `tasks.md`. +- **Spec 024, documentation refresh, feature catalog and roadmap status** (`specs/024-docs-feature-list-refresh/`): in progress: the catalog, roadmap and sync check are drafted; the website and main repo PRs are next. No blocker is named. + +--- + ## Blocking v1 Nothing hard-blocks v1 anymore. What stands between v0.3 and a v1 GA is the @@ -254,13 +313,6 @@ process). --- -## Explicitly out of scope for v1 - -- **A hosted/managed Gameplane (not planned).** The project targets self-hosted clusters, from - a single-node k3s homelab to multi-node production. - ---- - ## Known gaps tracked for completion ### Users page: service accounts and identity-provider tabs (planned, v1.1) @@ -279,8 +331,16 @@ template's authoring and configuration — the game module itself, not its join-protocol wire format. Those specification files belong in the `gameplane-module` submodule repo (e.g., `modules/minecraft-java/specs.md`), are tracked there in the module repository rather than in this main repo, and -are a known incomplete item for v1. See the module submodule's project board -for status. +are a known incomplete item for v1. As of 2026-10-14, 26 of the 29 modules have +a `specs.md`; `enshrouded`, `minecraft-java` and `v-rising` do not yet. See the +module submodule's project board for status. + +--- + +## Out of scope for v1 + +- **A hosted/managed Gameplane (not planned).** The project targets self-hosted clusters, from + a single-node k3s homelab to multi-node production. --- @@ -291,10 +351,12 @@ status for its join-protocol E2E testing. See [`docs/game-coverage.md`](game-cov canonical per-module status: - **2 modules** have real join coverage in CI (`minecraft-java`, `terraria`). -- **26 modules** are blocked on undocumented or partially-documented +- **25 modules** are blocked on undocumented or partially-documented wire-protocol formats, each with a named unblocking artifact (packet capture, reverse-engineering session, or anti-cheat analysis). They are candidates for future protocol work once documentation becomes available. - **2 modules** are permanently out-of-scope-by-design due to architectural constraints (`valheim` routes through Steam Datagram Relay; `dayz` requires BattlEye anti-cheat validation before join completion). + +Total: 29 modules. diff --git a/docs/security.md b/docs/security.md index d4de1c94a..b50a6d01b 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1070,7 +1070,7 @@ Access to cluster credentials is protected by several layers: ## Install-Time OIDC Role Mappings When OIDC authentication is configured at install time via Helm values -(`api.oidc.groupsClaim`, `api.oidc.roleMappings`, `api.oidc.defaultRole`) (unreleased; ships in the next release), +(`api.oidc.groupsClaim`, `api.oidc.roleMappings`, `api.oidc.defaultRole`) (since v0.3.0), Gameplane automatically assigns roles to users based on their OIDC provider's group/role claims on every login. This eliminates the need for a bootstrap-admin account in OIDC-only deployments — an operator can configure group mappings at diff --git a/hack/check-doc-versions.sh b/hack/check-doc-versions.sh index ab5c7a3a2..45194cdb4 100755 --- a/hack/check-doc-versions.sh +++ b/hack/check-doc-versions.sh @@ -7,13 +7,14 @@ # Gameplane version matches the current appVersion from charts/gameplane/Chart.yaml, # or is explicitly marked as an example version or historical reference. # -# Audited Files (18): +# Audited Files (19): # README.md, docs/architecture.md, docs/contributing.md, docs/dependencies.md, # docs/game-coverage.md, docs/install.md, docs/key-rotation.md, # docs/module-authoring.md, docs/networking.md, docs/notifications.md, # docs/oidc.md, docs/roadmap.md, docs/security.md, docs/tunnels.md, # audit-syslog-bridge/README.md, mcp-server/README.md, -# telemetry-receiver/README.md, docs/comparison-sources.md +# telemetry-receiver/README.md, docs/comparison-sources.md, +# FEATURES.md # # Allowlist Rules (OD-1, OD-11, OD-14): # A version string v?0\.[0-9]+\.[0-9]+(-(beta|rc)\.[0-9]+)? passes if: @@ -29,7 +30,7 @@ # # Test overrides: # DOC_VERSIONS_CHART: path to test Chart.yaml (default: charts/gameplane/Chart.yaml) -# DOC_VERSIONS_FILES: space-separated list of files to audit (default: 18 files below) +# DOC_VERSIONS_FILES: space-separated list of files to audit (default: 19 files below) # # Exit codes: # 0 = all version strings match current appVersion or are allowlisted @@ -64,7 +65,7 @@ fi # We'll accept matches with or without leading 'v' current_version_pattern="(v)?${current_version//./\\.}" -# List of 18 audited files +# List of 19 audited files if [[ -n "${DOC_VERSIONS_FILES:-}" ]]; then read -r -a audited_files <<< "$DOC_VERSIONS_FILES" else @@ -87,6 +88,7 @@ else "mcp-server/README.md" "telemetry-receiver/README.md" "docs/comparison-sources.md" + "FEATURES.md" ) fi diff --git a/hack/check-features-sync.sh b/hack/check-features-sync.sh new file mode 100755 index 000000000..b92a880a2 --- /dev/null +++ b/hack/check-features-sync.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +# check-features-sync.sh: Verify that FEATURES.md and the website features page carry the same capability catalog. +# +# Purpose: +# Implements spec 024 FR-015 (docs-feature-list-refresh). FEATURES.md (repo root) is the catalog +# source; website/src/content/docs/features.mdx must list the same areas, capabilities, maturity and +# default values in the same order. Description and Docs cells may differ. +# Grammar: specs/024-docs-feature-list-refresh/contracts/features-catalog.md +# Behaviour: specs/024-docs-feature-list-refresh/contracts/features-sync-check.md +# +# Parsing rules: +# - Area = level-2 heading ("## ") that owns a catalog table. Level-1 and level-3+ headings are ignored. +# - Catalog table = header row exactly "| Capability | Maturity | Default | Description | Docs |", +# then its separator row. Tables with any other header are ignored. +# - Lines inside fenced code blocks (``` or ~~~) are skipped. +# - Maturity: stable|beta|experimental|planned|in progress. Default: on|off|n/a. +# A stable row must have default "on". +# +# Env overrides (fixtures use these): +# FEATURES_MD repo catalog path (default: FEATURES.md) +# FEATURES_MDX website page path (default: website/src/content/docs/features.mdx) +# +# Exit codes: +# 0 = both files parse, validate and match +# 1 = validation problems or differences found +# 2 = one or more input files missing + +set -euo pipefail + +# Navigate to repo root (one level up from script's directory) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +cd "$REPO_ROOT" + +features_md="${FEATURES_MD:-FEATURES.md}" +features_mdx="${FEATURES_MDX:-website/src/content/docs/features.mdx}" + +missing=0 +for f in "$features_md" "$features_mdx"; do + if [[ ! -f "$f" ]]; then + echo "features-sync: missing $f" + missing=1 + fi +done +if [[ $missing -ne 0 ]]; then + exit 2 +fi + +# Single awk pass over both files. Side "R" is the first file argument (FEATURES.md), +# side "W" is the second (features.mdx). Validation messages print during the parse; +# comparison messages print from END, in this order: area mismatch, only-in rows, +# field differences, order differences. +if awk ' +BEGIN { + VM["stable"] = 1; VM["beta"] = 1; VM["experimental"] = 1; VM["planned"] = 1; VM["in progress"] = 1 + VD["on"] = 1; VD["off"] = 1; VD["n/a"] = 1 + HDR = "| Capability | Maturity | Default | Description | Docs |" + N["R"] = 0; N["W"] = 0; AN["R"] = 0; AN["W"] = 0 + problems = 0 +} +function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s } +FNR == 1 { + side = (FILENAME == ARGV[1]) ? "R" : "W" + area = ""; area_recorded = 0; in_fence = 0; in_table = 0; expect_sep = 0 +} +{ hdr = $0; sub(/[ \t\r]+$/, "", hdr) } +/^[ \t]*(```|~~~)/ { in_fence = !in_fence; next } +in_fence { next } +/^## / { area = trim(substr($0, 4)); area_recorded = 0; in_table = 0; next } +hdr == HDR { + in_table = 1; expect_sep = 1 + if (!area_recorded) { area_recorded = 1; AN[side]++; AL[side, AN[side]] = area } + next +} +in_table && /^\|/ { + if (expect_sep) { expect_sep = 0; next } + split($0, f, "|") + cap = trim(f[2]); mat = trim(f[3]); def = trim(f[4]) + if (!(mat in VM)) { printf "features-sync: %s:%d: invalid maturity \"%s\"\n", FILENAME, FNR, mat; problems++ } + if (!(def in VD)) { printf "features-sync: %s:%d: invalid default \"%s\"\n", FILENAME, FNR, def; problems++ } + if (mat == "stable" && def != "on") { printf "features-sync: %s:%d: stable row must have default \"on\"\n", FILENAME, FNR; problems++ } + N[side]++; i = N[side] + AREA[side, i] = area; CAP[side, i] = cap; MAT[side, i] = mat; DEF[side, i] = def + next +} +in_table { in_table = 0 } +END { + for (i = 1; i <= N["R"]; i++) { + k = AREA["R", i] SUBSEP CAP["R", i] + if (k in RIDX) { printf "features-sync: duplicate row in FEATURES.md: %s / %s\n", AREA["R", i], CAP["R", i]; problems++ } else RIDX[k] = i + } + for (j = 1; j <= N["W"]; j++) { + k = AREA["W", j] SUBSEP CAP["W", j] + if (k in WIDX) { printf "features-sync: duplicate row in features.mdx: %s / %s\n", AREA["W", j], CAP["W", j]; problems++ } else WIDX[k] = j + } + + n = (AN["R"] > AN["W"]) ? AN["R"] : AN["W"] + for (p = 1; p <= n; p++) { + ra = (p <= AN["R"]) ? AL["R", p] : "" + wa = (p <= AN["W"]) ? AL["W", p] : "" + if (ra != wa) { printf "features-sync: area mismatch at position %d: \"%s\" vs \"%s\"\n", p, ra, wa; problems++ } + } + + for (i = 1; i <= N["R"]; i++) { + k = AREA["R", i] SUBSEP CAP["R", i] + if (!(k in WIDX)) { printf "features-sync: only in FEATURES.md: %s / %s\n", AREA["R", i], CAP["R", i]; problems++ } + } + for (j = 1; j <= N["W"]; j++) { + k = AREA["W", j] SUBSEP CAP["W", j] + if (!(k in RIDX)) { printf "features-sync: only in features.mdx: %s / %s\n", AREA["W", j], CAP["W", j]; problems++ } + } + + for (i = 1; i <= N["R"]; i++) { + k = AREA["R", i] SUBSEP CAP["R", i] + if (!(k in WIDX)) continue + j = WIDX[k] + if (MAT["R", i] != MAT["W", j]) { printf "features-sync: %s / %s: maturity \"%s\" vs \"%s\"\n", AREA["R", i], CAP["R", i], MAT["R", i], MAT["W", j]; problems++ } + if (DEF["R", i] != DEF["W", j]) { printf "features-sync: %s / %s: default \"%s\" vs \"%s\"\n", AREA["R", i], CAP["R", i], DEF["R", i], DEF["W", j]; problems++ } + } + + rc = 0 + for (i = 1; i <= N["R"]; i++) { if ((AREA["R", i] SUBSEP CAP["R", i]) in WIDX) RPOS[++rc] = i } + wc = 0 + for (j = 1; j <= N["W"]; j++) { if ((AREA["W", j] SUBSEP CAP["W", j]) in RIDX) WPOS[++wc] = j } + m_max = (rc < wc) ? rc : wc + for (m = 1; m <= m_max; m++) { + i = RPOS[m]; j = WPOS[m] + if (AREA["R", i] != AREA["W", j] || CAP["R", i] != CAP["W", j]) { printf "features-sync: %s / %s: order differs\n", AREA["R", i], CAP["R", i]; problems++ } + } + + if (problems > 0) exit 1 + exit 0 +} +' "$features_md" "$features_mdx"; then + exit 0 +fi +exit 1 diff --git a/hack/check-links.sh b/hack/check-links.sh index 0c2052512..6215ba5af 100755 --- a/hack/check-links.sh +++ b/hack/check-links.sh @@ -1,22 +1,23 @@ #!/usr/bin/env bash # check-links.sh: Validate internal markdown links and anchors across the -# documentation audit's 18 files. +# documentation audit's 19 files. # # Purpose: # Ensures compliance with SC-006 (Internal Link Audit Standard) from feature 012 # (docs-refresh-and-outreach), contracts/docs-audit.md "Standard FR-010: Internal # Links" and "Link Checking (OD-2)". Validates that every markdown link and image -# target `[text](target)` / `![alt](target)` in the 18 audited files resolves to +# target `[text](target)` / `![alt](target)` in the 19 audited files resolves to # an existing file, and that any `#anchor` fragment resolves to an existing # heading slug or explicit HTML anchor in the target file. # -# Audited Files (18) — same fixed list as hack/check-doc-versions.sh: +# Audited Files (19) — same fixed list as hack/check-doc-versions.sh: # README.md, docs/architecture.md, docs/contributing.md, docs/dependencies.md, # docs/game-coverage.md, docs/install.md, docs/key-rotation.md, # docs/module-authoring.md, docs/networking.md, docs/notifications.md, # docs/oidc.md, docs/roadmap.md, docs/security.md, docs/tunnels.md, # audit-syslog-bridge/README.md, mcp-server/README.md, -# telemetry-receiver/README.md, docs/comparison-sources.md +# telemetry-receiver/README.md, docs/comparison-sources.md, +# FEATURES.md # # Scope (contracts/docs-audit.md "Standard FR-010: Internal Links"): # - Markdown link targets `[text](target)` and image targets `![alt](target)`, @@ -73,7 +74,7 @@ # section 9) uses explicit `` anchors instead. # # Exit codes: -# 0 = all 18 audited files exist and every internal link/anchor resolves +# 0 = all 19 audited files exist and every internal link/anchor resolves # 1 = an audited file is missing, or one or more links/anchors are broken # # No network access. Pure bash + grep/sed/tr, plus perl (present on Ubuntu @@ -87,7 +88,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(dirname "$SCRIPT_DIR")" cd "$REPO_ROOT" -# Same fixed list of 18 audited files as hack/check-doc-versions.sh. +# Same fixed list of 19 audited files as hack/check-doc-versions.sh. audited_files=( "README.md" "docs/architecture.md" @@ -107,6 +108,7 @@ audited_files=( "mcp-server/README.md" "telemetry-receiver/README.md" "docs/comparison-sources.md" + "FEATURES.md" ) total_issues=0 @@ -398,7 +400,7 @@ if [[ "${1:-}" == "--self-test" ]]; then fi # --------------------------------------------------------------------------- -# Main: verify all 18 audited files exist, then link-check every one that +# Main: verify all 19 audited files exist, then link-check every one that # does (a missing file is itself a failure, but does not stop the rest of # the audit from running). # --------------------------------------------------------------------------- diff --git a/hack/test-check-features-sync.sh b/hack/test-check-features-sync.sh new file mode 100755 index 000000000..7ab4d5318 --- /dev/null +++ b/hack/test-check-features-sync.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# test-check-features-sync.sh: Fixture tests for the features sync checker (spec 024 FR-015). +# +# Runs hack/check-features-sync.sh against each fixture under hack/testdata/check-features-sync/: +# pass/*/ must exit 0. +# fail/*/ must exit non-zero, and its combined stdout+stderr must equal expected.txt. +# +# Paths are passed relative to the repo root, so messages carry the paths exactly as +# written in each expected.txt. +# +# See: specs/024-docs-feature-list-refresh/contracts/features-sync-check.md (FR-015, SC-005) + +set -euo pipefail + +# Navigate to repo root (one level up from script's directory) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +cd "$REPO_ROOT" + +CHECK="hack/check-features-sync.sh" +FIX="hack/testdata/check-features-sync" + +passed=0 +failed=0 + +# Test pass/* fixtures +for dir in "$FIX"/pass/*/; do + [[ ! -d "$dir" ]] && continue + dir="${dir%/}" + name="$(basename "$dir")" + + if out=$(FEATURES_MD="$dir/FEATURES.md" FEATURES_MDX="$dir/features.mdx" "$CHECK" 2>&1); then + echo "✓ pass: $name" + passed=$((passed+1)) + else + echo "✗ expected pass: pass/$name" + echo "$out" + failed=$((failed+1)) + fi +done + +# Test fail/* fixtures +for dir in "$FIX"/fail/*/; do + [[ ! -d "$dir" ]] && continue + dir="${dir%/}" + name="$(basename "$dir")" + expected="$(cat "$dir/expected.txt")" + + rc=0 + out=$(FEATURES_MD="$dir/FEATURES.md" FEATURES_MDX="$dir/features.mdx" "$CHECK" 2>&1) || rc=$? + + if [[ $rc -eq 0 ]]; then + echo "✗ expected failure: fail/$name exited 0" + failed=$((failed+1)) + elif [[ "$out" == "$expected" ]]; then + echo "✓ fail: $name" + passed=$((passed+1)) + else + echo "✗ fail/$name output mismatch" + echo "Expected:" + echo "$expected" + echo "Got:" + echo "$out" + failed=$((failed+1)) + fi +done + +# Report results +total=$((passed + failed)) +if [[ $failed -eq 0 ]]; then + echo "✓ $total fixtures behaved as expected" + exit 0 +fi +echo "✗ $failed fixture(s) misbehaved" +exit 1 diff --git a/hack/testdata/check-features-sync/fail/area-mismatch/FEATURES.md b/hack/testdata/check-features-sync/fail/area-mismatch/FEATURES.md new file mode 100644 index 000000000..a876ebbbf --- /dev/null +++ b/hack/testdata/check-features-sync/fail/area-mismatch/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/area-mismatch/expected.txt b/hack/testdata/check-features-sync/fail/area-mismatch/expected.txt new file mode 100644 index 000000000..8f7d44528 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/area-mismatch/expected.txt @@ -0,0 +1,5 @@ +features-sync: area mismatch at position 2: "Operations" vs "Platform" +features-sync: only in FEATURES.md: Operations / Audit export +features-sync: only in FEATURES.md: Operations / Backups +features-sync: only in features.mdx: Platform / Audit export +features-sync: only in features.mdx: Platform / Backups diff --git a/hack/testdata/check-features-sync/fail/area-mismatch/features.mdx b/hack/testdata/check-features-sync/fail/area-mismatch/features.mdx new file mode 100644 index 000000000..26a3b5a5a --- /dev/null +++ b/hack/testdata/check-features-sync/fail/area-mismatch/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Platform + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | diff --git a/hack/testdata/check-features-sync/fail/default-mismatch/FEATURES.md b/hack/testdata/check-features-sync/fail/default-mismatch/FEATURES.md new file mode 100644 index 000000000..a876ebbbf --- /dev/null +++ b/hack/testdata/check-features-sync/fail/default-mismatch/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/default-mismatch/expected.txt b/hack/testdata/check-features-sync/fail/default-mismatch/expected.txt new file mode 100644 index 000000000..190052c80 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/default-mismatch/expected.txt @@ -0,0 +1 @@ +features-sync: Servers / Sleep and wake: default "off" vs "on" diff --git a/hack/testdata/check-features-sync/fail/default-mismatch/features.mdx b/hack/testdata/check-features-sync/fail/default-mismatch/features.mdx new file mode 100644 index 000000000..ba2a5442e --- /dev/null +++ b/hack/testdata/check-features-sync/fail/default-mismatch/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | on | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | diff --git a/hack/testdata/check-features-sync/fail/duplicate-row/FEATURES.md b/hack/testdata/check-features-sync/fail/duplicate-row/FEATURES.md new file mode 100644 index 000000000..a876ebbbf --- /dev/null +++ b/hack/testdata/check-features-sync/fail/duplicate-row/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/duplicate-row/expected.txt b/hack/testdata/check-features-sync/fail/duplicate-row/expected.txt new file mode 100644 index 000000000..a27d97ada --- /dev/null +++ b/hack/testdata/check-features-sync/fail/duplicate-row/expected.txt @@ -0,0 +1 @@ +features-sync: duplicate row in features.mdx: Operations / Audit export diff --git a/hack/testdata/check-features-sync/fail/duplicate-row/features.mdx b/hack/testdata/check-features-sync/fail/duplicate-row/features.mdx new file mode 100644 index 000000000..473f474ab --- /dev/null +++ b/hack/testdata/check-features-sync/fail/duplicate-row/features.mdx @@ -0,0 +1,28 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | +| Audit export | beta | off | A second, contradictory copy of the same capability. | [Audit](./audit/) | diff --git a/hack/testdata/check-features-sync/fail/invalid-label/FEATURES.md b/hack/testdata/check-features-sync/fail/invalid-label/FEATURES.md new file mode 100644 index 000000000..992666e10 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/invalid-label/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | Beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/invalid-label/expected.txt b/hack/testdata/check-features-sync/fail/invalid-label/expected.txt new file mode 100644 index 000000000..e929826d4 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/invalid-label/expected.txt @@ -0,0 +1,2 @@ +features-sync: hack/testdata/check-features-sync/fail/invalid-label/FEATURES.md:10: invalid maturity "Beta" +features-sync: hack/testdata/check-features-sync/fail/invalid-label/features.mdx:20: invalid maturity "Beta" diff --git a/hack/testdata/check-features-sync/fail/invalid-label/features.mdx b/hack/testdata/check-features-sync/fail/invalid-label/features.mdx new file mode 100644 index 000000000..bb8908c9e --- /dev/null +++ b/hack/testdata/check-features-sync/fail/invalid-label/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | Beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | diff --git a/hack/testdata/check-features-sync/fail/maturity-mismatch/FEATURES.md b/hack/testdata/check-features-sync/fail/maturity-mismatch/FEATURES.md new file mode 100644 index 000000000..a876ebbbf --- /dev/null +++ b/hack/testdata/check-features-sync/fail/maturity-mismatch/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/maturity-mismatch/expected.txt b/hack/testdata/check-features-sync/fail/maturity-mismatch/expected.txt new file mode 100644 index 000000000..2b37ebcaf --- /dev/null +++ b/hack/testdata/check-features-sync/fail/maturity-mismatch/expected.txt @@ -0,0 +1 @@ +features-sync: Servers / Sleep and wake: maturity "beta" vs "experimental" diff --git a/hack/testdata/check-features-sync/fail/maturity-mismatch/features.mdx b/hack/testdata/check-features-sync/fail/maturity-mismatch/features.mdx new file mode 100644 index 000000000..4d9086d03 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/maturity-mismatch/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | experimental | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | diff --git a/hack/testdata/check-features-sync/fail/missing-file/FEATURES.md b/hack/testdata/check-features-sync/fail/missing-file/FEATURES.md new file mode 100644 index 000000000..a876ebbbf --- /dev/null +++ b/hack/testdata/check-features-sync/fail/missing-file/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/missing-file/expected.txt b/hack/testdata/check-features-sync/fail/missing-file/expected.txt new file mode 100644 index 000000000..617939cc0 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/missing-file/expected.txt @@ -0,0 +1 @@ +features-sync: missing hack/testdata/check-features-sync/fail/missing-file/features.mdx diff --git a/hack/testdata/check-features-sync/fail/missing-row-repo/FEATURES.md b/hack/testdata/check-features-sync/fail/missing-row-repo/FEATURES.md new file mode 100644 index 000000000..ea4e12dfc --- /dev/null +++ b/hack/testdata/check-features-sync/fail/missing-row-repo/FEATURES.md @@ -0,0 +1,16 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/missing-row-repo/expected.txt b/hack/testdata/check-features-sync/fail/missing-row-repo/expected.txt new file mode 100644 index 000000000..57c190942 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/missing-row-repo/expected.txt @@ -0,0 +1 @@ +features-sync: only in features.mdx: Servers / Sleep and wake diff --git a/hack/testdata/check-features-sync/fail/missing-row-repo/features.mdx b/hack/testdata/check-features-sync/fail/missing-row-repo/features.mdx new file mode 100644 index 000000000..78cb783d0 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/missing-row-repo/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | diff --git a/hack/testdata/check-features-sync/fail/missing-row-website/FEATURES.md b/hack/testdata/check-features-sync/fail/missing-row-website/FEATURES.md new file mode 100644 index 000000000..a876ebbbf --- /dev/null +++ b/hack/testdata/check-features-sync/fail/missing-row-website/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/missing-row-website/expected.txt b/hack/testdata/check-features-sync/fail/missing-row-website/expected.txt new file mode 100644 index 000000000..0c58b6849 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/missing-row-website/expected.txt @@ -0,0 +1 @@ +features-sync: only in FEATURES.md: Operations / Backups diff --git a/hack/testdata/check-features-sync/fail/missing-row-website/features.mdx b/hack/testdata/check-features-sync/fail/missing-row-website/features.mdx new file mode 100644 index 000000000..83cf630e5 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/missing-row-website/features.mdx @@ -0,0 +1,26 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | diff --git a/hack/testdata/check-features-sync/fail/order-differs/FEATURES.md b/hack/testdata/check-features-sync/fail/order-differs/FEATURES.md new file mode 100644 index 000000000..a876ebbbf --- /dev/null +++ b/hack/testdata/check-features-sync/fail/order-differs/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/order-differs/expected.txt b/hack/testdata/check-features-sync/fail/order-differs/expected.txt new file mode 100644 index 000000000..f15f97632 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/order-differs/expected.txt @@ -0,0 +1,2 @@ +features-sync: Operations / Audit export: order differs +features-sync: Operations / Backups: order differs diff --git a/hack/testdata/check-features-sync/fail/order-differs/features.mdx b/hack/testdata/check-features-sync/fail/order-differs/features.mdx new file mode 100644 index 000000000..840b347a7 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/order-differs/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | diff --git a/hack/testdata/check-features-sync/fail/stable-off/FEATURES.md b/hack/testdata/check-features-sync/fail/stable-off/FEATURES.md new file mode 100644 index 000000000..0ea27652a --- /dev/null +++ b/hack/testdata/check-features-sync/fail/stable-off/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | off | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/fail/stable-off/expected.txt b/hack/testdata/check-features-sync/fail/stable-off/expected.txt new file mode 100644 index 000000000..2d9823958 --- /dev/null +++ b/hack/testdata/check-features-sync/fail/stable-off/expected.txt @@ -0,0 +1,2 @@ +features-sync: hack/testdata/check-features-sync/fail/stable-off/FEATURES.md:9: stable row must have default "on" +features-sync: hack/testdata/check-features-sync/fail/stable-off/features.mdx:19: stable row must have default "on" diff --git a/hack/testdata/check-features-sync/fail/stable-off/features.mdx b/hack/testdata/check-features-sync/fail/stable-off/features.mdx new file mode 100644 index 000000000..9b8938dbb --- /dev/null +++ b/hack/testdata/check-features-sync/fail/stable-off/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | off | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | diff --git a/hack/testdata/check-features-sync/pass/code-fence/FEATURES.md b/hack/testdata/check-features-sync/pass/code-fence/FEATURES.md new file mode 100644 index 000000000..c7d2d033e --- /dev/null +++ b/hack/testdata/check-features-sync/pass/code-fence/FEATURES.md @@ -0,0 +1,27 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +Example of the catalog format: + +```markdown +## Example area + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Example row | bogus | maybe | Not a real row. | [Example](docs/example.md) | +``` + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/pass/code-fence/features.mdx b/hack/testdata/check-features-sync/pass/code-fence/features.mdx new file mode 100644 index 000000000..78cb783d0 --- /dev/null +++ b/hack/testdata/check-features-sync/pass/code-fence/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | diff --git a/hack/testdata/check-features-sync/pass/in-sync/FEATURES.md b/hack/testdata/check-features-sync/pass/in-sync/FEATURES.md new file mode 100644 index 000000000..a876ebbbf --- /dev/null +++ b/hack/testdata/check-features-sync/pass/in-sync/FEATURES.md @@ -0,0 +1,17 @@ +# Gameplane features + +Maturity and default labels. Status as of 2026-10-14. See docs/roadmap.md. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Create, start, stop and delete game servers. | [Servers](docs/servers.md) | +| Sleep and wake | beta | off | Hold ports while idle and wake on connect. | [Sleep](docs/sleep.md) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Forward audit events to syslog. | [Audit](docs/audit.md) | +| Backups | planned | n/a | Scheduled backups to object storage. | [Backups](docs/backups.md) | diff --git a/hack/testdata/check-features-sync/pass/in-sync/features.mdx b/hack/testdata/check-features-sync/pass/in-sync/features.mdx new file mode 100644 index 000000000..78cb783d0 --- /dev/null +++ b/hack/testdata/check-features-sync/pass/in-sync/features.mdx @@ -0,0 +1,27 @@ +--- +title: Features +description: Every Gameplane capability with its maturity and default. +section: reference +group: releases-roadmap +order: 0 +icon: list +eyebrow: DOCUMENTATION +--- + +import Callout from '../../components/Callout.astro'; + +Maturity and default labels. Status as of 2026-10-14. See the roadmap. + +## Servers + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Game server lifecycle | stable | on | Lifecycle for game servers on the website. | [Servers](./servers/) | +| Sleep and wake | beta | off | Website wording differs here. | [Sleep](./sleep/) | + +## Operations + +| Capability | Maturity | Default | Description | Docs | +|---|---|---|---|---| +| Audit export | experimental | off | Audit events go to syslog. | [Audit](./audit/) | +| Backups | planned | n/a | Website wording differs here too. | [Backups](./backups/) | diff --git a/specs/024-docs-feature-list-refresh/contracts/features-sync-check.md b/specs/024-docs-feature-list-refresh/contracts/features-sync-check.md index 52c6dac1e..21358c3a2 100644 --- a/specs/024-docs-feature-list-refresh/contracts/features-sync-check.md +++ b/specs/024-docs-feature-list-refresh/contracts/features-sync-check.md @@ -31,11 +31,12 @@ features-sync: only in features.mdx: / features-sync: / : maturity "" vs "" features-sync: / : default "" vs "" features-sync: / : order differs +features-sync: duplicate row in : / ``` ## Fixture tests (FR-015, SC-005) -`hack/testdata/check-features-sync/` holds one directory per case with a `FEATURES.md`, a `features.mdx` and, for failing cases, an `.expected` file containing the expected output lines. +`hack/testdata/check-features-sync/` holds one directory per case with a `FEATURES.md`, a `features.mdx` and, for failing cases, an `expected.txt` file containing the expected output lines. Besides the messages above, the check reports an invalid default as `features-sync: :: invalid default ""`. - `pass/in-sync`: matching files with differing Description and Docs cells (must pass). - `pass/code-fence`: a table-looking block inside a code fence is ignored. diff --git a/specs/024-docs-feature-list-refresh/feature-evidence.md b/specs/024-docs-feature-list-refresh/feature-evidence.md new file mode 100644 index 000000000..b05e1a554 --- /dev/null +++ b/specs/024-docs-feature-list-refresh/feature-evidence.md @@ -0,0 +1,150 @@ +# Feature evidence: spec 024 + +One record per `FEATURES.md` row (data-model.md). Maturity rule: stable = shipped, default on, and a bucketed e2e test; beta = shipped with caveats or no e2e test; experimental = docs already say experimental; default off is never stable. + +| Area | Capability | Maturity | Default | E2E test (bucket) | Default source | Notes | +|---|---|---|---|---|---|---| +| Servers and lifecycle | Create a game server from a module template | stable | on | TestGameServer_OperatorMaterializesChildren (operator) | always on | Create wizard UI has vitest only (CreateServer.test.tsx). web: ./create-a-server/ | +| Servers and lifecycle | Start, stop and restart a server | stable | on | TestAPI_LifecycleStartStop (api-agent); TestAPI_LifecycleRestart (api-agent); TestGameServer_SuspendScalesToZeroAndBack (operator) | always on | Groups: Start a stopped server; Stop a server; Restart a server. web: ./server-operations/ | +| Servers and lifecycle | Show server phase and conditions | stable | on | TestGameServer_HeartbeatReachesRunning (operator); TestGameServer_SuspendScalesToZeroAndBack (operator) | always on | Running and Suspended phases are asserted. web: ./server-operations/ | +| Servers and lifecycle | Clone a server into a new GameServer | stable | on | TestAPI_LifecycleClone (api-rbac) | always on | E2E asserts clone creation. World-data exclusion is documented, not checked in the read. web: ./server-operations/ | +| Servers and lifecycle | Switch game version on a running server | stable | on | TestGameServer_VersionSwitch (operator) | always on | web: ./server-operations/ | +| Servers and lifecycle | Delete a server | stable | on | TestGameServer_CascadingDelete (operator); TestAPI_OwnerOnlyServerOperations_RequireOwnerOrAdmin (api-mods) | always on | web: ./server-operations/ | +| Servers and lifecycle | Hand off server ownership and manage collaborators | beta | on | TestAPI_OwnerOnlyServerOperations_RequireOwnerOrAdmin (api-mods); TestAPI_OwnerCollaboratorAccess (api-roles) | always on | Groups: Transfer server ownership; Manage server collaborators. No bucketed test performs a successful transfer, so beta. web: ./ownership-danger-zone/ | +| Servers and lifecycle | Graceful stop with the game's native shutdown sequence | beta | on | none | always on (template stop sequence) | The RCON stop sequence is only exercised by bot-heavy tests (TestGameServer_Squad_RCON, TestGameServer_TeamFortress2_RCON), which CI never runs. TestGameServer_SuspendScalesToZeroAndBack covers scale-to-zero only. web: ./server-operations/ | +| Servers and lifecycle | Wipe world data with typed confirmation | beta | on | TestGameServer_WipeReportsFailureOnPermissionDenied (operator); TestAPI_OwnerOnlyServerOperations_RequireOwnerOrAdmin (api-mods) | always on | Operator test covers the failure report. Owner gating is in the api-mods test. Success path is not named in a test, so beta. web: ./ownership-danger-zone/ | +| Servers and lifecycle | Browse and filter the server list across namespaces | beta | on | none | always on | Groups: Filter the server list by Running or Stopped; List servers across all readable namespaces. Docs list five states; code filter tabs are only All, Running and Stopped. web: ./server-overview/ | +| Servers and lifecycle | Keep game data on a PVC with clear storage errors | beta | on | TestGameServer_PVCSurvivesPodDelete (operator); TestGameServer_NonexistentStorageClassSurfacesError (operator); TestGameServer_InstallTimeDefaultApplies, TestGameServer_TemplateDefaultOverridesInstallTime, TestGameServer_ExplicitStorageClassOverridesDefault (operator) | always on; install-time storage class is operator.gameDataStorage.storageClassName (values.yaml, default empty) | Groups: Set an install-time default storage class for game data; Report a missing game-data storage class as PVCProvisioningFailed; Keep game data on a PVC when the game pod is deleted. Install-time default is off. values.yaml comment names StorageClassNotFound where the e2e asserts PVCProvisioningFailed. web: - | +| Servers and lifecycle | Edit game configuration from the Settings tab | in progress | n/a | none | n/a | Spec 021 is pending CI and review (specs/021-settings-game-config/spec.md:7). ConfigFields.tsx already exists on master, so confirm before the page claims shipped. web: ./roadmap/ | +| Console and admin actions | Live console stream for PTY-mode games | stable | on | TestAPI_ConsolePTYRoundTrip (api-agent) | always on (template consoleMode pty) | web: ./console-events-logs/ | +| Console and admin actions | Container startup output stream | beta | on | none | always on | Pod-log WebSocket. Covered by unit tests only, as far as read. web: ./console-events-logs/ | +| Console and admin actions | Run template quick actions | stable | on | TestGameServer_MinecraftJavaBot_Joined (bot-fast); TestMultiCluster_GatewayParity (multicluster) | always on (template actions) | Local path: bot-fast Minecraft runs save-world through actions/run and asserts the RCON reply (gamebot_helpers_e2e_test.go runControlActionRCON). Gateway route: TestMultiCluster_GatewayParity. web: ./server-operations/ | +| Console and admin actions | Validate action input and run actions on PTY-only games | beta | on | none | always on (gameaction module) | Groups: Schema-validated action input with injection guard; Stdin action transport for PTY-only games. Injection guard is unit-tested in gameaction. Transport resolves to stdin when no RCON protocol is set (api/internal/ws/actions.go:128-135). web: ./console-protocols/ | +| Console and admin actions | Live game log tail | stable | on | TestAPI_LogsTailWS (api-agent) | always on | web: ./console-events-logs/ | +| Console and admin actions | Replay and download the full game log | beta | on | none | always on | Groups: Log replay on tab open; Download the full game log. Replay size is from the v0.3.0 changelog and is not asserted by e2e. Route is /servers/{name}/logs/download. web: ./console-events-logs/ | +| Console and admin actions | Send commands over RCON and ten console protocols | beta | on | none in CI (TestGameServer_Squad_RCON, TestGameServer_TeamFortress2_RCON in bot-heavy, never run); TestGameServer_NuclearOptionBot_Query (bot-heavy, never run) | template spec.rcon (no chart toggle); template rcon.authentication (default true) | Groups: RCON command console; Pick a console protocol from the full rcon.protocol set; Run a console without a password with rcon.authentication false. Bot-heavy never runs in CI, so beta. No bucketed test selects rest or cli. web: ./console-protocols/ | +| Console and admin actions | Console toolbar with history, clear, download and fullscreen | beta | on | none | always on | Groups: Console command history; Clear, download and fullscreen console controls. History limit of 100 is in ConsoleShell.tsx (HISTORY_LIMIT), not persisted. web: ./console-events-logs/ | +| Console and admin actions | Kubernetes events timeline for a server | beta | on | none | always on | api_events_roles_scope (operator) covers the event stream, not the /servers/{name}/events snapshot. web: ./console-events-logs/ | +| Files | Browse, edit, create, download and delete files | stable | on | TestAPI_AgentFilesRoundTrip (api-agent) | always on | Groups: Browse the exposed server filesystem; Save text file contents through the write endpoint; Create a file through the write endpoint; Download a file; Delete a file. Round trip covers list, write, read, large download (60s timeout regression) and delete. Monaco UI has vitest only (Files.test.tsx). web: ./file-manager/ | +| Files | Upload files and manage folders | beta | on | none | always on | Groups: Upload files; Create a folder; Delete a folder recursively. Mkdir and upload endpoints are not called by a top-level e2e test; recursive delete and dot-prefixed refusal (v0.3.0) have no e2e test. web: ./file-manager/ | +| Files | Confine file access to the exposed root | beta | on | none | always on | Unit tests in agent/internal/files (resolve_escape_test.go, rooted_test.go). The mod archive confinement e2e tests cover mods, not files. web: ./file-manager/ | +| Players | View online players and capacity | stable | on | TestAPI_AgentPlayers (api-agent) | always on (template capabilities.players) | Busybox test asserts the JSON contract and 200 response. web: ./player-administration/ | +| Players | Manage the whitelist, kick, ban and banned list | beta | on | TestMultiCluster_GatewayParity (multicluster) | always on (template capabilities.players) | Groups: View the whitelist and add or remove entries; Kick a player; Ban and unban a player; View the banned players list. Only the gateway-route test calls whitelist endpoints, and the gateway is default off. No top-level e2e calls kick, ban, unban or the banned list. web: ./player-administration/ | +| Players | Steam display names on player lists | beta | off | none | api.steam.name (values.yaml:262, empty by default) | Default off, so never stable. Hydration is unit-tested in api/internal/ws/players_hydrate_test.go. web: - | +| Mods | Install, upgrade and upload mods | stable | on | TestAPI_ModManifestInstallUpgrade (api-mods); TestAPI_ModUpload (api-mods) | always on | Groups: Install a mod from a download URL with registry metadata in the mod manifest; Upgrade an installed mod in place and remove it from the manifest; Upload a mod file directly from the dashboard. Install test uses a public raw.githubusercontent.com file via the agent SSRF guard. Uploads are never update-checked. web: ./mods-modpacks/ | +| Mods | Reject path-traversal and symlink-escape mod archives | stable | on | TestAPI_ModArchiveConfinement_PathTraversalRejected (api-mods); TestAPI_ModArchiveConfinement_SymlinkEscapeRejected (api-mods); TestAPI_ModArchiveConfinement_ValidArchiveExtracts (api-mods) | always on | Shipped in 0.3.0 (CHANGELOG.md, Security hardening). web: ./mods-modpacks/ | +| Mods | Install mods by launch-parameter ID | beta | on | TestGameServer_DayZ_Workshop (bot-heavy); TestGameServer_TModLoader_Mods (bot-heavy) | module field: capabilities.mods.idList in template.yaml (docs/module-authoring.md#mods) | bot-heavy is bucketed but never runs in CI (buckets.sh:245), so beta. web: ./mods-modpacks/ | +| Mods | Browse and install from seven public registries | beta | on | none | always on (Factorio credentials entered in the install form, docs/module-authoring.md#mod-portal-credentials-factorio); module field: Community on the template (Thunderstore, registry.go:245); module field: GitHubOwner and GitHubRepo (GitHub Releases, registry.go:265) | Groups: Browse and install from Modrinth; Browse and install from Thunderstore; Browse and install from Hangar; Browse and install from the Factorio Mod Portal; Browse and install from SpigotMC; Browse and install from GitHub Releases; Browse and install from uMod. No e2e exercises a live registry browse. web: ./mods-modpacks/ | +| Mods | Browse CurseForge, Steam Workshop and Nexus Mods | beta | off | TestGameServer_DayZ_Workshop covers ID projection only (bot-heavy) | runtime setting: Settings, Mod registries, API key (keys.go:36-40); no values.yaml key | Groups: Browse and install from CurseForge; Browse and install from Steam Workshop; Browse Nexus Mods. Provider hidden until a key is set. Steam Workshop browse and install are not covered by any e2e. Nexus is browse-only per README. web: ./mod-registry-credentials/ | +| Mods | Store and replace registry API keys from Settings | beta | on | none | always on | Combined installs use Kubernetes Secrets; standalone encrypts in the API database (README.md#supported-registries). web: ./mod-registry-credentials/ | +| Mods | Check installed mods for newer versions | beta | on | none | always on | Requires provider and resolvable version (website mods-modpacks). web: ./mods-modpacks/ | +| Mods | Install a modpack from a provider | beta | on | none | module field: capabilities.mods.providers[].modpacks (docs/module-authoring.md#modpacks-providersmodpacks) | No e2e found for modpacks. web: ./mods-modpacks/ | +| Backups and restore | Run on-demand and scheduled restic backups | stable | on | TestBackup_OperatorMaterializesJob (operator); TestBackupSchedule_CreatesBackupCR (operator); TestBackupSchedule_SuspendStopsScheduling (operator); TestBackupSchedule_ConcurrencyForbid (operator) | always on | Groups: Create an on-demand restic-snapshot backup of a server; Run backups on a cron schedule; Suspend a backup schedule; Forbid overlapping scheduled backups. Default suspend is false. web: ./backups-recovery/ | +| Backups and restore | Trim backups with keep-last retention and prune on delete | stable | on | TestBackupSchedule_RetentionTrimsPast (operator); TestBackup_DeleteForgetsSnapshot (operator) | always on | Groups: Trim old backups with keep-last retention; Delete a backup and forget its restic snapshot with prune. Only keepLast is asserted; calendar rules are not claimed. Code: operator/internal/controller/backup_forget.go:92. web: ./backups-recovery/ | +| Backups and restore | Connect a restic repository through a labelled Secret | beta | on | TestBackup_OperatorMaterializesJob (operator) | always on (repository URL in the labelled Secret) | Groups: Connect a restic repository through a labelled Secret; Store restic backups in S3-compatible storage. No bucketed test exercises an S3 backend (e2e uses a REST repository). web: ./backup-destinations/ | +| Backups and restore | Report failed backups and rejected restores | stable | on | TestBackup_FailsOnMissingPVC (operator); TestBackup_FailsOnBadCredentials (operator); TestRestore_RejectsMissingBackup (operator); TestRestore_FailsOnMissingSnapshot (operator) | always on | Groups: Report a Failed backup for a missing PVC or bad credentials; Reject a restore whose source backup is missing or not Succeeded. web: ./backups-recovery/ | +| Backups and restore | Restore a restic snapshot into an existing server | stable | on | TestRestore_RoundTrip (operator) | always on | Test writes a marker, backs up, wipes, restores and asserts bytes (roadmap.md). Pre-created empty target is not separately tested. web: ./backups-recovery/ | +| Backups and restore | Quiesce game writes before each backup | beta | on | none | operator/api/v1alpha1/backup_types.go:76 (kubebuilder default true) | Backup and schedule e2e tests set quiesce false, so quiesce true is not exercised. web: ./backups-recovery/ | +| Backups and restore | Tune backup and restore Job retry limit and deadline | beta | on | none | values.yaml backupJobBackoffLimit (line 103) and backupJobActiveDeadlineSeconds (line 104) | Shipped in 0.3.0 (CHANGELOG.md). web: ./helm-values-reference/ | +| Backups and restore | Browse backups and restores on the dashboard Backups page | beta | on | none | always on | Website describes filters from v0.3.0. web: ./backups-recovery/ | +| Backups and restore | Create and restore CSI volume-snapshot backups | beta | off | none | module field: Backup.spec.strategy with volumeSnapshotClassName (backup_types.go:56-63); module field: Restore.spec.strategy (restore_types.go:30-35) | Groups: Create volume-snapshot CSI backups; Restore a CSI volume-snapshot backup into a new GameServer. Opt-in; no e2e references VolumeSnapshot. web: ./backup-restore-crds/ | +| Backups and restore | Backup and restore drill runbook | planned | n/a | none | n/a (roadmap) | Roadmap says the drill test exists and the runbook is missing. web: ./roadmap/ | +| Game modules and catalog | Discover modules from OCI and upload sources | stable | on | TestModuleSourceAndModule (operator); TestModuleSourceUpload (operator) | values.yaml defaultModuleSource.enabled (line 659); values.yaml uploadModuleSource.enabled (line 731) | Groups: Discover modules from an OCI module source; Upload module bundles to the upload source. Discover test uses an in-cluster registry, not the official catalog. web: ./modules-sources/ | +| Game modules and catalog | Install modules and block deleting ones a server uses | stable | on | TestModuleSourceAndModule (operator); TestModule_FinalizerBlocksWhileTemplateInUse (operator); TestGameTemplate_DeletionWithLiveServer (operator) | always on | Groups: Install a Module to materialize a GameTemplate; Block deleting a Module or GameTemplate that a server uses. web: ./modules-sources/ | +| Game modules and catalog | Reject unsafe or unreachable module sources | stable | on | TestModuleSource_RejectsSSRFTarget (operator); TestModule_FailsOnUnreachableRegistry (operator) | always on | Groups: Reject module source URLs that resolve to unsafe targets; Report an unreachable module registry on the Module. netguard IsAllowed applies to module sources (CLAUDE.md). web: ./modules-sources/ | +| Game modules and catalog | Switch a running server to another module version | stable | on | TestGameServer_VersionSwitch (operator) | always on | Each version and loader keeps its own PVC (docs/module-authoring.md:1640-1646). web: ./modules-sources/ | +| Game modules and catalog | Browse the official catalog and merged module states | beta | on | TestGameServer_MinecraftJavaBot_Joined (bot-fast); TestGameServer_TerrariaBot_Joined (bot-fast); TestGameServer_GarrysModBot_Query (bot-fast) | values.yaml defaultModuleSource.enabled (line 659) | Groups: Official game module catalog; Browse the merged module catalog with install and verification states. Module repo has 29 directories with module.yaml; chart default list has 16 (values.yaml 694-709); README says 30 templates, a stale fact. web: ./modules-sources/ | +| Game modules and catalog | Pin or verify module bundles with digests and signatures | beta | off | TestModule_DigestPinEnforcedAfterInstall (operator); TestModule_VerifySignedBundleInstalls (operator); TestModule_VerifyRejectsUnsignedBundle (operator) | Module.spec.digest unset by default; values.yaml defaultModuleSource.oci.verify.enabled (line 720), default false | Groups: Pin a Module to a bundle digest; Verify cosign signatures on module bundles; Verify keyless Sigstore identity signatures on module bundles. Keyed verification only in e2e; no e2e for keyless. web: ./modules-sources/ | +| Game modules and catalog | Use a Git repository, HTTP archive or local directory as a module source | beta | off | none | module field: ModuleSource type git or http (modulesource_types.go:51); values.yaml operator.localModules.enabled (line 110) | Groups: Use a Git repository as a module source; Use an HTTP archive as a module source; Use a local directory as a module source. Not the default source. web: ./module-authoring/ | +| Game modules and catalog | Complete specs.md for all 29 game modules | planned | n/a | none | n/a (roadmap) | Roadmap: 26 of 29 have specs.md; enshrouded, minecraft-java and v-rising do not. web: ./roadmap/ | +| Game modules and catalog | Nuclear Option join through a load-balancer IP pool override | in progress | n/a | none | n/a (roadmap) | Spec 002, 136 of 143 tasks done, blocked on UDP 7777 capture (roadmap.md In progress). web: ./roadmap/ | +| Module authoring | Declare a mods capability in a GameTemplate | stable | on | TestAPI_ModManifestInstallUpgrade (api-mods) | always on | Test applies a template built by applyModsTemplate with a mods capability. web: ./module-authoring/ | +| Module authoring | Render an install wizard and declare typed admin actions | beta | on | none confirmed | always on | Groups: Render an install wizard from a config schema; Declare admin actions with typed inputs. gameserver_e2e_test.go and the squad and teamfortress2 RCON tests mention wizard or actions, not verified as coverage. web: ./module-authoring/ | +| Module authoring | Author modules in the dashboard Modules builder | beta | on | none | always on | Shipped in 0.3.0 (CHANGELOG.md). web: ./module-authoring-quickstart/ | +| Module authoring | Scaffold, validate, preview and package modules with gp-module | beta | n/a | TestModule_ScaffoldAndPackage (operator) | tooling, no install default | Groups: Scaffold a new module with gp-module init; Validate a module offline with gp-module validate; Dry-run a template config with gp-module preview; Package a module bundle with gp-module package. Test calls validator.ValidateFiles and packager.CreateArchiveFromFiles; preview has no e2e reference. web: ./module-authoring-quickstart/ | +| Module authoring | Push and sign module bundles for OCI sources | beta | n/a | TestModuleSourceAndModule (operator); TestModule_VerifySignedBundleInstalls (operator) | tooling, release pipeline | Groups: Push a module bundle to an OCI registry; Sign module bundles with cosign in the release pipeline. Push test uses an oras Job; sign test uses an in-cluster cosign Job. web: ./module-authoring/ | +| Module authoring | Editor JSON schemas for module.yaml and template.yaml | beta | n/a | none | tooling, no install default | gametemplate.schema.json is generated from the CRD (docs/module-authoring.md:125-141). web: ./gametemplate-schema/ | +| Networking and tunnels | Expose a server through a ClusterIP Service | stable | on | TestGameServer_MinecraftJavaBot_Joined (bot-fast) | gameserver_types.go:288 kubebuilder default ClusterIP | Indirect: join test runs over the default ClusterIP Service. No test names the Service type. web: ./network-automation/ | +| Networking and tunnels | Apply a per-server egress NetworkPolicy for the tunnel pod | beta | on | none | values.yaml networkPolicies.enabled (line 491, default true) | Created only for tunnel-enabled servers. TestGameServer_IngressNetworkPolicyShapeAndCascade covers ingress only. web: ./server-networking-tunnels/ | +| Networking and tunnels | Expose a server through NodePort, LoadBalancer or Hostport | beta | off | none | gameserver_types.go:288 enum | Groups: Expose a server through a NodePort Service; Expose a server through a LoadBalancer Service; Expose a server through a Hostport mapping. No bucketed test. LoadBalancer pool tests are indirect. Hostport asymmetry is documented in docs/architecture.md (Security boundaries). web: ./network-automation/ | +| Networking and tunnels | Request or change a named address pool or specific external IP | beta | off | TestAddressPool_NamedPoolAssignment (operator); TestAddressPool_DefaultPoolNoPreference (operator); TestAddressPool_ExplicitAddressRequest (operator); TestAddressPool_ChangePoolOnRunningServer (operator) | values.yaml operator.addressManager (line 79, default none) | Groups: Request a named address pool for a server; Request a specific external IP address for a server; Change the address pool of a running server. Change-pool test checks status metadata only, not IP stability. InvalidAddress rejection is documented but not named in a test. Cilium needs a serviceSelector mirror per networking.md. web: ./network-automation/ | +| Networking and tunnels | Report address assignment, conflicts and pool errors | beta | off | TestAddressPool_StatusVisibleInAddressAssignmentCondition (operator); TestAddressPool_AddressInUseConflict (operator); TestAddressPool_NonexistentPoolError (operator); TestAddressPool_PoolExhausted (operator) | values.yaml operator.addressManager (line 79, default none) | Groups: Report address assignment progress on the AddressAssignment condition; Refuse an address already held by another GameServer; Report a missing or exhausted address pool. Conflicts with non-Gameplane Services are best-effort per networking.md; MetalLB PoolNotFound detection is best-effort. web: ./network-automation/ | +| Networking and tunnels | Relay players through frp, Tailscale or playit.gg | beta | off | TestGameServer_TunnelCredentialRefusalSurfaced (operator); TestCRD_Validation_TunnelServerAddrValid (operator, CRD validation only) | gameserver_types.go:392 spec.networking.tunnel.enabled (default false) | Groups: Relay players through an frp tunnel; Relay players through a Tailscale tailnet; Relay players through a playit.gg tunnel; Store tunnel credentials as owned Secrets. No live relay test. Refusal surfaces as TunnelReady=False with TunnelCredentialRefused. Tailscale tags need tagOwners; playit free tier is 8 hours a day. web: ./server-networking-tunnels/ | +| Networking and tunnels | Keep the tunnel pod running while the game sleeps | beta | off | none | gameserver_types.go:392 spec.networking.tunnel.enabled (default false) | Always-on pod cost is documented in tunnels.md#cost-and-resource-usage. web: ./server-networking-tunnels/ | +| Networking and tunnels | Restrict game ingress with a sourceRanges allowlist | beta | off | none | spec.networking.sourceRanges (no default allowlist) | Documented only in tunnels.md. web: ./network-automation/ | +| Networking and tunnels | Report conflicting networking options as a status condition | planned | n/a | none | n/a (planned) | Documented as planned in docs/tunnels.md; no roadmap entry. web: - | +| Idle sleep and wake | Wake a sleeping server from the dashboard or wake verb | beta | on | none | always on (API verb and dashboard) | Groups: Wake a sleeping server with the wake verb; Show slept servers as Asleep with a Wake button in the dashboard. Sleep marker exists only for idle servers. spec.suspend stays false while asleep, per roadmap.md. web: ./server-operations/ | +| Idle sleep and wake | Sleep idle servers after empty minutes | beta | off | TestGameServer_IdleNeverSleepsWithoutAPlayerCount (operator) | spec.idle.enabled (gameserver_types.go:177, default false) | Groups: Opt in to idle auto-sleep after a set number of empty minutes; Sleep an idle server through the module stop sequence. Only the refusal path is covered; no bucketed test sees an actual sleep. 5-minute floor per gameserver_types.go:179-187. web: ./server-operations/ | +| Idle sleep and wake | Explain and monitor idle sleep | beta | off | TestGameServer_IdleNeverSleepsWithoutAPlayerCount (operator) | spec.idle.enabled (gameserver_types.go:177, default false) | Groups: Show why a server will not sleep; Freeze the idle clock on stale heartbeats or missing player counts; Report idle sleep state in the gameplane_gameservers_idle metric. Stale-heartbeat branch is not separately named. Metric defined at operator/internal/controller/metrics.go:54. web: ./server-operations/ | +| Idle sleep and wake | Wake a sleeping server on a cron wake window | beta | off | none | spec.idle.wakeWindows (gameserver_types.go:208, no default) | An unparseable window raises IdleScheduleInvalid. web: ./server-operations/ | +| Idle sleep and wake | Arm wake-on-connect and hold advertised ports while asleep | beta | off | TestGameServer_WakeOnConnect_UnarmedNoSentinel (bot-fast); TestGameServer_WakeOnConnect_LoginWakes (bot-fast) | spec.idle.wakeOnConnect (gameserver_types.go:218, default false) | Groups: Arm wake-on-connect for a server; Hold advertised ports by switching Service endpoints during sleep; Bounce held clients on Hostport servers instead of holding them. Hold path is exercised for Minecraft only; Hostport bounce has no e2e. web: ./server-operations/ | +| Idle sleep and wake | Wake on game joins with per-port protocols and try-again disconnects | beta | off | TestGameServer_WakeOnConnect_PingDoesNotWake (bot-fast); TestGameServer_WakeOnConnect_LoginWakes (bot-fast) | spec.idle.wakeOnConnect (gameserver_types.go:218, default false); wakeProtocol defaults to generic | Groups: Wake on a Minecraft join but not on a server-list ping; Wake on a Terraria join through the Terraria parser; Wake other games with a packets-in-window heuristic; Send a try-again disconnect when a held join misses the deadline; Choose a wake protocol per game port. Only the Minecraft protocol is exercised in e2e. 25-second default deadline per roadmap.md. web: ./server-operations/ | +| Multi-cluster | Register, rotate and remove remote clusters | beta | on | TestMultiCluster_ClusterDispatchAndScopedRBAC (multicluster) | always on | Groups: Register a remote cluster with its kubeconfig; Remove a registered cluster and its kubeconfig Secret; Rotate a registered kubeconfig in place. Rotation is not named in any bucketed test. web: ./multi-cluster/ | +| Multi-cluster | Create and read servers on a chosen registered cluster | stable | on | TestMultiCluster_ClusterDispatchAndScopedRBAC (multicluster) | always on | Groups: Create a server on a chosen registered cluster; Read servers from a selected cluster with ?cluster= dispatch; Refuse fallback to a same-named local server when a remote target is missing. Unknown cluster returns 400; checkMissingRemoteTargetsDoNotReachHome (test line 486). web: ./multi-cluster/ | +| Multi-cluster | Enforce per-cluster RBAC grants on remote servers | stable | on | TestMultiCluster_ClusterDispatchAndScopedRBAC (multicluster) | always on | Local-only viewer is forbidden on cluster B. web: ./multi-cluster/ | +| Multi-cluster | Route module, registry and mod-ID writes to the selected cluster | beta | on | none | always on | Groups: Route registry, modpack and mod-ID writes to the selected cluster; Require a cluster-wide grant for remote module catalog access. Breaking change in CHANGELOG [Unreleased]; no bucketed test asserts the grant. Bounded retry on version conflicts only. web: - | +| Multi-cluster | Keep node join tokens and kubeconfig issuance local to the central cluster | stable | on | TestMultiCluster_ClusterDispatchAndScopedRBAC (multicluster) | always on | Asserted in checkMissingRemoteTargetsDoNotReachHome (test lines 658-660). web: ./cluster-nodes-kubeconfig/ | +| Multi-cluster | Add nodes and download kubeconfig from the Cluster page | beta | off | none | clusterOps.enabled (values.yaml, default false) | Off by default and not covered by a bucketed test. web: - | +| Multi-cluster | Show API health and node inventory per registered cluster | beta | on | none | always on (inventory needs a cluster:read grant) | Groups: Show Kubernetes API health per registered cluster; Browse node inventory for a selected cluster. Health is Kubernetes connectivity only. Remote inventory needs an All-namespaces grant. web: ./multi-cluster/ | +| Multi-cluster | Combined counts, search and location filters across sites | beta | on | none | always on | Groups: Show combined server and player counts across authorized sites; Filter servers, backups and schedules by location; Search servers across sites without identity collisions. Web e2e is not in any bucket. web: ./dashboard-tour/ | +| Multi-cluster | Open canonical server links that carry cluster and namespace | beta | on | none | always on | Legacy links without a cluster mean local. web: - | +| Multi-cluster | Block unsafe remote dial addresses, with an optional CIDR allowlist | beta | on | none | always on (netguard dial guard); api.remoteAllowedCIDRs (values.yaml:175, unset by default) | Groups: Block unsafe address classes for remote Kubernetes and gateway dials; Restrict remote destinations with an allowed CIDR list. Private workload addresses remain allowed. DNS names must resolve within the allowed ranges. web: - | +| Multi-cluster | Run remote agent operations through the mTLS gateway | beta | off | TestMultiCluster_GatewayParity (multicluster) | gateway.enabled (values.yaml:419, default false) | Groups: Run remote agent operations through the gateway; Register gateway URL and mTLS credentials per cluster; Check gateway capabilities and refuse older agents; Download and delete remote capture files through the gateway. Subtests cover files, mods, RCON players and status, capture-files-and-identity; capability contract asserted at /v1/capabilities. web: ./remote-agent-gateway/ | +| Multi-cluster | Rotate gateway trust and revoke gateway access | beta | off | TestMultiCluster_GatewayParity (multicluster) | gateway.enabled (values.yaml:419, default false) | Subtest rotation-revocation-and-endpoint-replacement. web: ./remote-agent-gateway/ | +| Multi-cluster | Keep games running when the gateway is down | beta | off | none | gateway.enabled (values.yaml:419, default false) | Gateway health is separate from Kubernetes health. web: ./remote-agent-gateway/ | +| Multi-cluster | Run the central panel without a local Kubernetes cluster | beta | off | none | api.standalone (values.yaml:130, default false) | Groups: Run the central panel without a local Kubernetes cluster; Install the standalone panel with Docker Compose; Select a remote cluster for module installs and sources in standalone panels. No bucketed test; no standalone term in test/e2e. web: - | +| Multi-cluster | Manage the standalone panel encryption key | beta | off | none | standalone mode (api.standalone, values.yaml:130) | Groups: Store standalone remote credentials encrypted with a persistent panel key; Rotate the panel master encryption key offline; Run standalone with a provisioned panel key that is never generated. No bucketed test. web: - | +| Users, auth and access | Sign in with local accounts and disable or re-enable local login | stable | on | TestAPI_BootstrapAndLogin (api-auth); TestAPI_LogoutInvalidatesSession (api-auth); TestAPI_PasswordResetInvalidatesSession (api-auth); TestAPI_DynamicAuthProviders (api-auth) | always on | Groups: Sign in with local username and password accounts; Log out and end the session on the server; End existing sessions on dashboard password reset; Disable or re-enable the local sign-in provider from auth settings. Cookie digest stored as SHA-256 only. Dynamic-provider test also checks a dashboard-added provider appears live. web: ./users-auth-rbac/ | +| Users, auth and access | Throttle login attempts and show generic login errors | stable | on | TestAPI_LoginRateLimit (ratelimit); TestAPI_LoginPrivacy (api-auth) | always on | Groups: Throttle login attempts per client IP address; Show generic login errors with no cluster or account details. Login privacy is a hard requirement (CLAUDE.md rule 3). Rate-limit test runs in its own last-step bucket. web: ./security/ | +| Users, auth and access | Enforce a fixed permission catalog with built-in and custom roles | stable | on | TestAPI_RBAC_ViewerCannotMutate_Matrix (api-rbac); TestAPI_RBAC_AdminCanReachAll (api-rbac); TestAPI_CustomRole_Lifecycle (api-roles); TestAPI_BuiltinRole_Immutable (api-roles) | always on | Groups: Check every API route against a fixed permission catalog; Use the built-in admin, operator and viewer roles; Create custom roles from permission catalog subsets; Keep built-in roles immutable and block deleting assigned roles. Catalog source is api/internal/rbac/catalog.go. web: ./users-auth-rbac/ | +| Users, auth and access | Bind roles per namespace and require cluster-wide grants for cluster permissions | beta | on | TestAPI_PerNamespaceBinding_GrantsScopedAccess (api-roles) | always on | Groups: Bind a role to a user within one namespace; Require a cluster-wide binding for cluster-scoped permissions. No bucketed test is named for the cluster-wide rule. web: ./users-auth-rbac/ | +| Users, auth and access | Restrict destructive server actions to the owner or an admin | stable | on | TestAPI_OwnerOnlyServerOperations_RequireOwnerOrAdmin (api-mods) | always on | web: ./ownership-danger-zone/ | +| Users, auth and access | Manage dashboard user accounts and remove users with their linked data | beta | on | TestAPI_AccountRemoval_RevokesSharesAndAllowsSSOReprovision (multicluster); TestAPI_OperatorCannotInviteUsers (api-rbac) | always on | Groups: Remove a user account with all linked data; Create and edit dashboard user accounts. TestAPI_OperatorCannotInviteUsers covers only the denial path; no bucketed test asserts admin creation. web: ./user-administration/ | +| Users, auth and access | Refuse changes that would lock out every admin | beta | on | none | always on | Groups: Refuse an auth config save that leaves no sign-in provider enabled; Refuse to demote or delete the last user manager. The account-removal e2e mentions the lockout guard only in passing. web: ./sign-in-recovery/ | +| Users, auth and access | Save and customize dashboard theme preferences | beta | on | TestAPI_ThemePreferences (api-roles) | always on | Groups: Save personal dashboard theme preferences; Customize the dashboard theme with presets, custom colors, a CSS overlay and safe mode. Test covers stored preferences only. No feature doc in docs/; the roadmap shipped list is linked. web: ./dashboard-tour/ | +| Users, auth and access | Require the CSRF header on mutating requests | beta | on | none | always on | Many e2e tests send the header, but no bucketed test asserts rejection when it is missing. web: ./security/ | +| Users, auth and access | Share a read-only status link for one GameServer | beta | on | none | always on | Only a SHA-256 of the token is stored; expiry presets and no-expiry are offered; revocation is independent of expiry. web: - | +| Users, auth and access | Recover an admin with bootstrap-admin | beta | n/a | TestAPI_BootstrapAdminForceEndsExistingSessions (api-mods) | n/a (CLI tooling, no install default) | Groups: Reset an admin password from the API pod with bootstrap-admin; Re-enable local login from the API pod with bootstrap-admin. No bucketed test covers the --enable-local-login path. web: ./sign-in-recovery/ | +| Users, auth and access | Sign in with dashboard-managed or Helm-configured OIDC providers | beta | off | TestAPI_DynamicAuthProviders (api-auth); TestAPI_OIDCHelmSeeded_AdminOnFirstLogin (api-auth) | runtime setting: a provider added in Authentication settings (none by default); values.yaml api.oidc.enabled | Groups: Sign in with dashboard-managed OIDC providers; Sign in with the Helm-configured OIDC provider. Dashboard e2e asserts provider listing and start endpoint, not a full IdP login. Helm test runs against a fake in-cluster IdP (test/e2e/internal/fakeoidc). web: ./users-auth-rbac/ | +| Users, auth and access | Map IdP group claims to dashboard roles | beta | off | TestAPI_OIDCHelmSeeded_AdminOnFirstLogin (api-auth); TestAPI_OIDCHelmSeeded_DefaultRoleOnNoMapping (api-auth); TestAPI_OIDCHelmSeeded_RoleReevaluatedOnGroupChange (api-auth); TestAPI_OIDCHelmOverride_EffectiveAtLoginTime (api-roles) | values.yaml api.oidc.enabled | Groups: Assign roles from IdP group claims at install time; Re-evaluate the OIDC role on every login; Override a role's IdP group list from the dashboard. Shipped in v0.3.0 (CHANGELOG.md:280); docs/oidc.md and docs/security.md updated to say so. Reset path is not asserted by the named test. web: ./users-auth-rbac/ | +| Users, auth and access | Follow OIDC setup guides for five identity providers | beta | n/a | none | n/a (documentation only) | Guides are not verified against live IdPs in any bucket. web: ./users-auth-rbac/ | +| Users, auth and access | Service accounts on the Users page | planned | n/a | none | n/a (planned) | Roadmap section Known gaps, Users page: service accounts and identity-provider tabs. web: ./user-administration/ | +| Audit and security | Record and page through the audit log | stable | on | TestAPI_AuditEmitsOnMutation (api-auth); TestAPI_AuditPaginationAndFilter (api-auth) | always on | Groups: Record every mutating API request in the audit log; Page and filter the audit log. Audit events never record request bodies (docs/security.md#gameserver-config-passwords). web: ./audit-observability-admin/ | +| Audit and security | Export the audit trail and verify its hash chain | beta | on | none | always on | Groups: Export the audit trail as CSV or JSON; Verify the audit hash chain for tampering. Tamper evidence only: the chain is unkeyed. web: ./audit-observability-admin/ | +| Audit and security | Forward audit events to stdout or a webhook | beta | off | none | values.yaml api.audit.stdout; values.yaml api.audit.webhook.url | Groups: Mirror audit events to stdout; Forward audit events to a webhook. Events are dropped, not queued without bound, when the endpoint stalls. web: ./audit-observability-admin/ | +| Audit and security | Ship audit events to syslog or S3-compatible storage | beta | off | none | values.yaml api.audit.webhook.syslogBridge.enabled; values.yaml api.audit.s3.endpoint | Groups: Deploy the bundled audit-syslog-bridge from Helm; Deliver audit events to syslog over TCP, TLS or UDP; Stream audit batches to S3-compatible storage. Requires syslogBridge.syslog.addr. Syslog has no application-level acknowledgement, so a narrow loss window remains. web: ./audit-observability-admin/ | +| Audit and security | Prune audit events past a retention age | beta | off | none | values.yaml api.audit.retentionDays | web: ./audit-observability-admin/ | +| Audit and security | Resolve client IPs through trusted proxies | beta | on | none | values.yaml api.trustedProxies | Default trusts private ranges; security.md explains narrowing it. web: ./security/ | +| Audit and security | Run Gameplane pods hardened and optionally enforce restricted Pod Security | beta | on | none | always on (hardened contexts); values.yaml podSecurity.enforceRestricted (off) | Groups: Run Gameplane pods with hardened security contexts; Enforce restricted Pod Security on the games namespace. The game container itself is not hardened by this; the agent sidecar is. Network capture sidecar requires allowPrivilegeEscalation and so cannot satisfy restricted. web: ./security/ | +| Audit and security | Redact password values and confine credentials to labelled Secrets | beta | on | none | always on | Groups: Redact GameServer password values in API responses; Confine dashboard-managed credentials to labelled Secrets. Residual exposure: kubectl and GitOps readers still see stored values. Notification sink label rules are in docs/notifications.md#sink-credentials-secrets. web: ./security/ | +| Audit and security | Encrypt agent and capture control traffic with mTLS | stable | on | TestHelmInstall_BringYourOwnMTLSCA (operator); TestHelmInstall_MTLSClientCertReissuedOnCAChange (operator) | always on | web: ./security/ | +| Audit and security | Isolate pods with NetworkPolicies | stable | on | TestGameServer_IngressNetworkPolicyShapeAndCascade (operator); TestHelmInstall_APIServerEgressPolicy_AllowsPrivateRanges (operator) | values.yaml networkPolicies.enabled | web: ./security/ | +| Audit and security | Audit OIDC role assignments and mapping overrides | beta | off | none | values.yaml api.oidc.enabled | Only relevant when OIDC role mapping is in use. web: ./audit-observability-admin/ | +| Audit and security | Verify release signatures with the current cosign key | beta | n/a | none | n/a (verification tooling) | Rekor transparency log entries apply to new signatures. web: - | +| Audit and security | Sign the audit chain with a keyed HMAC | planned | n/a | none | n/a (planned) | Named as future hardening in docs/security.md#audit-log-integrity. web: ./audit-observability-admin/ | +| Notifications | Store sink credentials in API-created Secrets | beta | on | none | always on | Needs a sink. Deleting the sink also deletes API-created Secrets only. web: ./notification-sinks/ | +| Notifications | Use pre-created labelled Secrets for sinks | beta | n/a | none | n/a (operator workflow) | Needs a sink. web: ./notification-sinks/ | +| Notifications | Alert on unhealthy servers, failed backups and restores | beta | on | none | always on | Groups: Alert when a server becomes unhealthy or recovers; Alert when a backup or restore fails; Alert when a backup or restore succeeds. Needs a sink. User-intended stop or suspend never notifies. backup and restore success is off by default. web: ./notification-events-payloads/ | +| Notifications | Deliver alerts through a bounded queue with retries, test sends and metrics | beta | on | none | always on | Groups: Send a test event to a saved sink; Retry transient delivery failures; Count deliveries in Prometheus metrics; Deliver notifications without blocking reconciliation or requests. Needs a sink. Test endpoint is rate limited to about 12 per minute per IP. Events seen while the API pod is down are not replayed on restart. web: ./notification-sinks/ | +| Notifications | Guard sink dials and keep sink URLs out of errors | beta | on | none | always on | Groups: Guard sink dials with the SSRF policy; Keep sink URLs out of delivery errors. Needs a sink. LAN and in-cluster receivers such as ntfy remain reachable. web: ./notification-sinks/ | +| Notifications | Label alerts with the instance name and post JSON webhook payloads | beta | on | none | always on | Groups: Label alerts with the install's instance name; Post structured JSON webhook payloads. Needs a sink. Instance name is set under Admin Settings, General, Instance name. web: ./notification-events-payloads/ | +| Notifications | Send alerts to Discord, Slack, ntfy, SMTP or a generic webhook | beta | off | none | runtime setting: sinks added in Admin Settings (none by default) | Groups: Send alerts to Discord, Slack, ntfy, SMTP or a generic webhook; Send email alerts over SMTP with selectable TLS. Payload formats: Discord embed, Slack text, ntfy headers, SMTP plain text, webhook JSON. net/smtp refuses AUTH PLAIN over an unencrypted connection, so TLS none only works for unauthenticated relays. web: ./notification-sinks/ | +| Telemetry | Send daily usage counts with admin and install-time off switches | stable | on | TestTelemetryLifecycle/seen_opens_gate_and_first_report_arrives (telemetry); TestTelemetryLifecycle/notice_pending_on_fresh_install (telemetry); TestTelemetryLifecycle/all_off_stops_reports (telemetry); TestTelemetryLifecycle/operator_disabled_sends_nothing (telemetry) | values.yaml api.telemetry.enabled | Groups: Send basic usage counts once a day; Hold reports until an admin sees the first-login notice; Turn basic telemetry off from Admin Settings; Hard-disable telemetry at install time. Default on for new installs after the first-login notice; existing installs follow the upgrade table in install.md. all_off also covers an API restart not bringing a report forward. web: ./platform-settings-telemetry/ | +| Telemetry | Send extended usage reports with a resettable, signed install ID | beta | on | TestTelemetryLifecycle/extended_counted_once_per_day (telemetry); TestTelemetryLifecycle/custom_destination_receives_only (telemetry); TestTelemetryLifecycle/reset_id_counts_as_new_install (telemetry); TestTelemetryLifecycle/unsigned_tampered_replayed_get_403 (telemetry); TestTelemetryLifecycle/forged_other_key_gets_409 (telemetry) | api.telemetry.enabled and extended tier (on for new installs, install.md Defaults table); api.telemetry.endpoint | Groups: Send extended usage reports with a random install ID; Send reports to the project default receiver; Reset the install ID from Admin Settings; Sign extended reports with an install key. The custom-endpoint branch is tested; the default-receiver path is not asserted, so beta. Reset applies only while the extended tier is on. web: ./platform-settings-telemetry/ | +| Telemetry | Run a bundled telemetry receiver from Helm | beta | off | TestTelemetryLifecycle (telemetry); TestTelemetryLifecycle/unsigned_tampered_replayed_get_403 (telemetry) | values.yaml api.telemetry.receiver.enabled (ships disabled) | Groups: Run a bundled telemetry receiver from Helm; Validate and aggregate incoming reports strictly; Expire receiver data on a retention schedule; Pseudonymise install IDs with a pepper. Changing the pepper makes existing activity records unmatchable for one day. Activity expiry must stay above 30 days. web: ./telemetry-provider-kubernetes/ | +| Telemetry | Limit and proof-of-work gate report ingestion on the receiver | beta | off | TestTelemetryLifecycle/pow_refuses_missing_and_invalid (telemetry) | values.yaml api.telemetry.receiver.ingestPow.enabled; INGEST_SOURCE_DAILY_LIMIT default 20 | Groups: Limit accepted reports per source per day; Require proof of work on report ingestion. Limits are held in memory only. Proof of work deters volume only; it does not prove an install exists. web: ./telemetry-provider-kubernetes/ | +| Telemetry | Expose receiver summary, dashboard and metrics | beta | off | TestTelemetryLifecycle/public_summary_five_keys (telemetry); TestTelemetryLifecycle/dashboard_refuses_unauthenticated (telemetry); TestTelemetryLifecycle/dashboard_shows_reports (telemetry) | values.yaml api.telemetry.receiver.publicSummary.enabled (chart default off) | Groups: Publish a public summary of five counts; Browse the receiver dashboard behind a token; Scrape receiver metrics with a bearer token. Metrics scrape has no bucketed test. The project default receiver enables the public summary. web: ./telemetry-provider-kubernetes/ | +| Telemetry | Run a standalone telemetry provider | beta | n/a | none | n/a (runbook, no install default) | The repository ships no infrastructure code for the project's own receiver. web: ./telemetry-provider-kubernetes/ | +| Telemetry | Show a version-trend view in the receiver dashboard | in progress | n/a | none | n/a (in progress) | Not merged to master; shipped behaviour is unchanged until it lands. web: ./telemetry-provider-kubernetes/ | +| Observability and admin | Serve API, operator and agent Prometheus metrics | beta | on | TestHelmInstall_MetricsNotOnPublicPort (operator) | always on | Groups: API Prometheus metrics on a private listener; Operator controller and fleet gauges; Per-server agent metrics. The metrics-port test checks the API only; no bucketed test asserts the operator gauges. Agent metrics are on the plain 9090 listener; scraping needs serviceMonitors.enabled. web: ./metrics-alerts-dashboards/ | +| Observability and admin | Admin system log viewer | beta | on | none | always on | No e2e test drives the admin Logs page. TestAPI_LogsTailWS covers game-server log streams, not this viewer. web: ./system-log-viewer/ | +| Observability and admin | Ship ServiceMonitor, PrometheusRule and Grafana objects from Helm | beta | off | none | serviceMonitors.enabled (false); prometheusRules.enabled (false); grafanaDashboards.enabled (false) | Groups: ServiceMonitor and PodMonitor scrape objects; Operator PrometheusRule alerts; Operator Grafana dashboard. scrapeNamespaceSelector is also needed when networkPolicies.enabled is true. Dashboard JSON at charts/gameplane/dashboards/gameplane-operator.json. web: ./metrics-alerts-dashboards/ | +| Observability and admin | Capture game pod packets and expire captures automatically | beta | off | TestGameServer_NetworkCaptureStartStopDownload (operator); TestNetworkCapture_RetentionExpiry (operator) | capture.enabled (false); capture.defaultRetentionSeconds (86400) | Groups: Packet capture from game pods; Automatic deletion of expired captures. Related operator-bucket tests: TestGameServer_NetworkCaptureEphemeralContainer, TestGameServer_NetworkCaptureRestartCleanup, TestGameServer_NetworkCaptureConcurrencyRejected. Spec 003 shipped. web: ./helm-values-reference/ | +| Localization | Localized dashboard and community language packs | in progress | n/a | none | none (spec 019 not merged) | Not on master. master ships English only; the telemetry report hard-codes language en at api/internal/telemetry/collect.go:256. Roadmap line 270 names PR #668 as the blocker. web: ./roadmap/ | +| Developer and operator tooling | Install and upgrade the panel from the Helm chart | stable | on | TestHelmInstall_AllPodsReady (operator); TestUpgrade_FromPreviousRelease (upgrade) | operator.enabled and api.enabled (both true); crds.autoApply.enabled (true) | Groups: Install the panel from the Helm chart; CRD auto-apply on helm upgrade. The install e2e installs the chart from the working tree, not the GHCR OCI pull. TestHelmInstall_CRDApplyHookSkippedOnFreshInstall covers the fresh-install skip. Upgrade is in the CI matrix. web: ./installation/ | +| Developer and operator tooling | Read-only MCP server | beta | off | none | mcpServer.enabled (false) | Shipped (roadmap.md, PR #105). No e2e test. web: ./helm-values-reference/ | +| Developer and operator tooling | Helper image overrides for air-gapped mirrors | beta | n/a | none | operator.configInitImage and operator.resticImage (upstream pins) | Knob only; no e2e test. web: ./air-gapped-installation/ | +| Developer and operator tooling | Rolling edge images | beta | off | none | image.tag (empty, which uses appVersion) | Edge is opt-in. No e2e test installs the edge tag. web: ./installation/ | +| Developer and operator tooling | Local Kind development loop | beta | n/a | none | none (developer make targets) | Contributor tooling; no e2e test. web: ./local-development-setup/ | +| Developer and operator tooling | Store API data in PostgreSQL | experimental | off | none | api.db.driver default sqlite (values.yaml:155); build tag postgres (api/internal/db/db_postgres.go:1) | Experimental per values.yaml:155 and install.md:136. Published API image is built without the tag (values.yaml:157-160). Postgres e2e is open in spec 013 (T054-T058). web: ./database-configuration/ | +| Developer and operator tooling | Reuse an existing PVC for the API database or standalone panel key | beta | off | none | api.storage.existingClaim (default empty); api.panelKey.storage.existingClaim (default empty) | Groups: Reuse an existing PVC for the API database; Reuse an existing PVC for the standalone panel key. Off by default. Independent of database storage. web: - | diff --git a/specs/024-docs-feature-list-refresh/research-notes/catalog.md b/specs/024-docs-feature-list-refresh/research-notes/catalog.md new file mode 100644 index 000000000..9aaa5a13b --- /dev/null +++ b/specs/024-docs-feature-list-refresh/research-notes/catalog.md @@ -0,0 +1,89 @@ +# T006 catalog: modules, mod registries, RBAC roles, Go modules + +Scope: MOD = `/home/user/module`, GP = `/home/user/Gameplane`. Read-only research; no tests or builds run. + +## (a) Game modules in MOD + +Each top-level directory containing a `module.yaml` is a module. Display name = `displayName:` in `module.yaml` (line 4 in every file checked; `grep -m1 '^displayName:'`). + +Count: **29** (29 directories with `module.yaml`; checked with `ls -d */ | wc -l` at MOD 91b64cc) + +| Dir | displayName | +|---|---| +| 7-days-to-die | 7 Days to Die | +| ark-survival-ascended | ARK: Survival Ascended | +| ark-survival-evolved | ARK: Survival Evolved | +| arma-reforger | Arma Reforger | +| beammp | BeamMP | +| cs2 | Counter-Strike 2 | +| dayz | DayZ | +| dont-starve-together | Don't Starve Together | +| enshrouded | Enshrouded | +| euro-truck-simulator-2 | Euro Truck Simulator 2 | +| factorio | Factorio (Headless) | +| farming-simulator-25 | Farming Simulator 25 | +| fivem | FiveM | +| garrys-mod | Garry's Mod | +| left-4-dead-2 | Left 4 Dead 2 | +| minecraft-java | Minecraft (Java Edition) | +| mount-and-blade-2-bannerlord | Mount & Blade II: Bannerlord | +| nuclear-option | Nuclear Option | +| palworld | Palworld (Dedicated) | +| project-zomboid | Project Zomboid | +| rust | Rust | +| satisfactory | Satisfactory (Dedicated) | +| squad | Squad | +| team-fortress-2 | Team Fortress 2 | +| terraria | Terraria | +| the-isle | The Isle | +| tmodloader | tModLoader | +| v-rising | V Rising | +| valheim | Valheim (Dedicated) | + +Hell Let Loose: **absent**. A case-insensitive recursive search of `/home/user/module` for `hell let loose` / `helllet` returned no matches. + +Non-module top-level files in MOD (not counted): `build.sh`, `validate.py`, `test-build-sh.sh`, `test-validate-py.sh`. + +## (b) Mod registries implemented in code + +Canonical list: `api/internal/registry/registry.go:243-281`, the `switch cfg.Provider` in `func (s *Set) For` (`registry.go:241`). Ten providers: + +1. modrinth (`registry.go:243`) +2. thunderstore (`registry.go:245`, needs Community) +3. curseforge (`registry.go:250`, needs CurseForgeGameID and key) +4. hangar (`registry.go:259`) +5. factorio (`registry.go:261`) +6. spigot (`registry.go:263`) +7. github (`registry.go:265`, needs GitHubOwner and GitHubRepo) +8. umod (`registry.go:270`) +9. steam (`registry.go:272`, needs SteamAppID and key) +10. nexus (`registry.go:281`, needs Community as domain and key) + +Corroborating sources: +- Package doc lists the same ten: `registry.go:1-3`, and the upstream hosts at `registry.go:13-15` (api.modrinth.com, thunderstore.io, api.steampowered.com, api.nexusmods.com, api.spiget.org, api.github.com, umod.org; hangar and factorio are not listed in that host line). +- Constructors in `NewSet`: `registry.go:216-224` (`newModrinth`, `newThunderstore`, `newHangar`, `newFactorio`, `newSpigot`; the rest are built lazily). +- Engine files: `api/internal/registry/{modrinth,thunderstore,curseforge,hangar,factorio,spigot,github,umod,steam,nexus}.go`. +- Keyed providers: `api/internal/registry/keys.go:36-40` (`KeyedProviders` = curseforge, steam, nexus). +- Admin validation of the modRegistries config: `api/internal/handlers/config.go:802` (message says `curseforge|steam|nexus`; this is a key-provider check, not the full list). +- Agent: no registry engines. `agent/internal/mods/mods.go:220` and `:836-840` only mention factorio for mod-portal credential injection. + +Count: **10**. Names: modrinth, thunderstore, curseforge, hangar, factorio, spigot, github, umod, steam, nexus. + +## (c) Built-in RBAC roles + +Constants: `api/internal/rbac/rbac.go:44-48`: +- `RoleAdmin = "admin"` (`rbac.go:45`) +- `RoleOperator = "operator"` (`rbac.go:46`) +- `RoleViewer = "viewer"` (`rbac.go:47`) + +Comment at `rbac.go:43`: custom roles may use any other valid name. + +Seed: `api/internal/db/migrations/sqlite/003_roles.sql:38-41` inserts the three rows with `builtin = 1`. Admin gets `*` (`003_roles.sql:43-44`). Operator and viewer permissions follow at `003_roles.sql:46` onward. + +Count: **3** (admin, operator, viewer). + +## (d) Go modules + +- `go.work` `use (...)` block: `/home/user/Gameplane/go.work:3-20`. Count: **16** entries: agent, api, audit-syslog-bridge, capture-sidecar, gameaction, gameproto, gp-module, mcp-server, netguard, operator, sentinel, svcutil, telemetryschema, telemetry-receiver, test/e2e, tunnel. +- Makefile `GO_MODULES`: `/home/user/Gameplane/Makefile:43`. Count: **15** entries: netguard, gameaction, gameproto, telemetryschema, gp-module, operator, api, agent, audit-syslog-bridge, telemetry-receiver, sentinel, mcp-server, capture-sidecar, svcutil, tunnel. +- Difference: `test/e2e` is in go.work but not in `GO_MODULES`. It is added only in `GO_UNIT_MODULES := $(GO_MODULES) test/e2e` (`Makefile:50`). So go.work (16) = GO_MODULES (15) + test/e2e. diff --git a/specs/024-docs-feature-list-refresh/research-notes/defaults.md b/specs/024-docs-feature-list-refresh/research-notes/defaults.md new file mode 100644 index 000000000..520f6cafb --- /dev/null +++ b/specs/024-docs-feature-list-refresh/research-notes/defaults.md @@ -0,0 +1,102 @@ +# Install defaults: toggles, build tags, optional components + +Scope: `charts/gameplane/values.yaml` (GP), `//go:build` lines outside `_test.go` and `test/e2e`, and optional components. All paths are under `/home/user/Gameplane` unless noted. Line numbers are from the files as they stand in the working tree. + +## 1. Enable/disable toggles in values.yaml + +Default value as written in the file. Keys are shown relative to the top level or to the parent block. + +| Key | Default | Line | Note | +|---|---|---|---| +| `crds.autoApply.enabled` | true | 38 | pre-upgrade kubectl apply Job | +| `operator.enabled` | true | 51 | "standalone central panel" comment at 50 | +| `operator.leaderElect` | true | 60 | not a feature gate; HA guard | +| `operator.localModules.enabled` | false | 110 | local ModuleSource mount | +| `api.enabled` | true | 127 | | +| `api.standalone` | false | 130 | requires `operator.enabled=false` | +| `api.audit.stdout` | false | 198 | mirror audit events to stdout | +| `api.audit.webhook.syslogBridge.enabled` | false | 228 | bundled audit-syslog-bridge | +| `api.audit.webhook.syslogBridge.syslog.tls` | false | 235 | | +| `api.audit.s3.insecure` | false | 251 | plain HTTP to S3 endpoint | +| `api.telemetry.enabled` | **true** | 289 | hard switch for telemetry sending; default endpoint compiled into API (see 290-293) | +| `api.telemetry.receiver.enabled` | false | 309 | bundled telemetry-receiver | +| `api.telemetry.receiver.persistence.enabled` | true | 318 | | +| `api.telemetry.receiver.dashboard` (`publicSummary.enabled`) | false | 333 | `GET /v1/summary` | +| `api.telemetry.receiver.ingestPow.enabled` | false | 356 | proof-of-work on ingest | +| `api.oidc.enabled` | false | 362 | | +| `gateway.enabled` | false | 419 | optional agent gateway | +| `web.enabled` | true | 462 | dashboard nginx pod | +| `ingress.enabled` | true | 469 | | +| `ingress.tls` | true | 476 | | +| `networkPolicies.enabled` | true | 491 | | +| `networkPolicies.gameEgress.enabled` | true | 518 | | +| `networkPolicies.backupEgress.enabled` | true | 540 | | +| `networkPolicies.gameIngress.enabled` | true | 563 | per-server ingress policy | +| `clusterOps.enabled` | false | 584 | grants kube-system + CSR RBAC when on | +| `mcpServer.enabled` | false | 599 | optional read-only MCP server | +| `serviceMonitors.enabled` | false | 617 | | +| `prometheusRules.enabled` | false | 641 | | +| `grafanaDashboards.enabled` | false | 648 | | +| `podSecurity.enforceRestricted` | false | 653 | | +| `defaultModuleSource.enabled` | true | 659 | official OCI catalog on by default | +| `defaultModuleSource.oci.verify.enabled` | false | 720 | cosign verification of official bundles is off by default | +| `uploadModuleSource.enabled` | true | 731 | | +| `capture.enabled` | false | 740 | cluster-wide capture feature | + +Other values that are boolean but not on/off features: `api.db.driver` default `sqlite` (line 155), `api.metricsPort` 9090 (137). + +Notes on the defaults above: +- Telemetry is ON by default (`api.telemetry.enabled: true`, line 289). Sending goes to the compiled-in default provider unless `api.telemetry.endpoint` (line 294) is set. Per-install admin switches also gate sending (comment 281-283). +- The default module source type is `oci` (line 674), with `defaultModuleSource.oci.modules` listing 16 official modules (lines 694-709). Git source is available at line 668-686, pinned to `ref: v0.3.0` (line 686). + +## 2. Go build-tag gated features + +Command: `grep -rn '//go:build' --include=*.go /home/user/Gameplane`, excluding `_test.go` and `test/e2e`. Results: + +| Tag | File:line | Feature | +|---|---|---| +| `postgres` | `api/internal/db/db_postgres.go:1` | PostgreSQL driver | +| `!postgres` | `api/internal/db/db_nopostgres.go:1` | SQLite-only build (default) | +| `unix` | `api/internal/controlplane/key_file_unix.go:1` | key file handling | +| `unix` | `api/internal/controlplane/key_publication_unix.go:1` | key publication | +| `!unix` | `api/internal/controlplane/key_file_unsupported.go:1` | non-unix stub | +| `!unix` | `api/internal/controlplane/key_publication_unsupported.go:1` | non-unix stub | +| `linux` | `api/internal/controlplane/key_acl_linux.go:1` | Linux ACL | +| `unix && !linux` | `api/internal/controlplane/key_acl_unsupported.go:1` | unix non-linux stub | +| `linux` | `audit-syslog-bridge/peek_linux.go:1` | | +| `!linux` | `audit-syslog-bridge/peek_other.go:1` | non-linux stub | +| `!ignore_autogenerated` | `operator/api/v1alpha1/zz_generated.deepcopy.go:1` | code generator marker, not a feature | + +Relevant facts for the postgres tag: +- `api/internal/db/db_postgres.go` only builds with `-tags postgres`. The values comment says the published API image is built without it (`charts/gameplane/values.yaml:157-160`), and `api.db.driver` says postgres is experimental (line 155). +- The chart only sets `driver: sqlite` by default (line 155) with DSN at line 156. + +Only the `postgres` tag and the platform tags (`unix`, `linux`) are user-visible feature differences. The `!ignore_autogenerated` tag is a generator marker. + +## 3. Optional / installed components + +For each, the chart gate, default, and whether the chart deploys it by default. + +| Component | Values key (default) | Chart template gate | Deployed by default by chart? | +|---|---|---|---| +| Operator | `operator.enabled` (true, line 51) | `templates/operator.yaml:1` | Yes | +| API | `api.enabled` (true, line 127) | `templates/api.yaml:7` | Yes | +| Web dashboard | `web.enabled` (true, line 462) | `templates/web.yaml:1` | Yes | +| Agent gateway | `gateway.enabled` (false, line 419) | `templates/gateway.yaml:2` | No | +| MCP server | `mcpServer.enabled` (false, line 599) | `templates/mcp-server.yaml:1-2` (also skipped when `api.standalone`) | No | +| audit-syslog-bridge | `api.audit.webhook.syslogBridge.enabled` (false, line 228) | `templates/audit-syslog-bridge.yaml:5` (also requires `syslog.addr`, line 7; skipped when `api.audit.webhook.url` is set) | No | +| telemetry-receiver | `api.telemetry.receiver.enabled` (false, line 309) | `templates/telemetry-receiver.yaml:6` (also skipped when `api.telemetry.endpoint` is set) | No | +| capture-sidecar | `capture.enabled` (false, line 740); image `capture.image` (line 773) | operator flag `templates/operator.yaml:317-319`; gateway flags `templates/gateway.yaml:139`; per-server opt-in `operator/api/v1alpha1/gameserver_types.go:234` (default false) | No (cluster switch off; per-GameServer opt-in also off) | +| sentinel | image `operator.sentinelImage` (line 65; default `ghcr.io/gameplanepanel/gameplane/sentinel:dev` at `operator/internal/controller/gameserver_controller.go:1580`) | Not a chart Deployment. Created per GameServer by the operator. | No, only when a GameServer sets `spec.idle.enabled` and `spec.idle.wakeOnConnect` (`gameserver_types.go:177` and ~216, default false) and a template port has `advertise` and `wakeProtocol != "none"` (`operator/internal/controller/gameserver_sentinel.go:89-99`) | +| tunnel relay: frp | image `operator.tunnelImages.frp` (line 92) | Not a chart Deployment. Per GameServer when `spec.networking.tunnel.enabled` (`gameserver_types.go:392`, default false); image selection `operator/internal/controller/gameserver_tunnel.go:248-263` | No | +| tunnel relay: tailscale | `operator.tunnelImages.tailscale` (line 93) | same as frp; `gameserver_tunnel.go:287-289` | No | +| tunnel relay: playit | `operator.tunnelImages.playit` (line 94) | same as frp; `gameserver_tunnel.go:322-324` | No | + +Notes: +- The operator passes the tunnel and sentinel images as flags (`templates/operator.yaml:277-279`; sentinel via the same helper pattern in `_helpers.tpl:39-45`). +- The agent sidecar image is `templates/_helpers.tpl:23-29` and `operator.agentImage` (line 64). I did not check whether any values key gates the agent sidecar injection itself. No toggle for it was found in values.yaml. +- Tunnel default images are `:dev` tags in code (`gameserver_tunnel.go:738-740`), while the chart helpers set `{registry}/tunnel-*:{tag}` (`_helpers.tpl:47-68`). The chart values file has these keys empty by default (lines 92-94). + +## 4. Website and module repos + +Not in scope for this task. No reads were made in `/home/user/website` (WEB) or `/home/user/module` (MOD). diff --git a/specs/024-docs-feature-list-refresh/research-notes/e2e-tests.md b/specs/024-docs-feature-list-refresh/research-notes/e2e-tests.md new file mode 100644 index 000000000..b2fa6fd8f --- /dev/null +++ b/specs/024-docs-feature-list-refresh/research-notes/e2e-tests.md @@ -0,0 +1,193 @@ +# E2E test to CI bucket map (T004) + +Source: `test/e2e/*_test.go` (top level only, 157 `func Test` declarations). Bucket source: `test/e2e/buckets.sh`. + +**How buckets.sh maps tests:** by explicit name lists, not by file or name pattern. Each `bucket_()` function prints one test name per line from a heredoc (e.g. `bucket_operator()` at `buckets.sh:36`). `regex_bucket` joins the names into an anchored `^(A|B|...)$` regex for `go test -run` (`buckets.sh:403`). Anchoring exists so prefix matches do not run a test in two buckets (`buckets.sh:20`). The file a test lives in plays no role in bucketing. + +**Verification done by reading (not running):** 156 test names in the bucket lists, 156 `func Test` declarations excluding `TestMain`. Every bucketed name matches a suite function, every suite function appears in exactly one bucket, and no name is listed twice. + +## Table + +Bucket column gives the bucket name and the line of its entry in `buckets.sh`. + +| Test name | File:line | Bucket(s) | +|---|---|---| +| `TestAPI_AccountRemoval_RevokesSharesAndAllowsSSOReprovision` | `test/e2e/api_account_removal_e2e_test.go:121` | multicluster (buckets.sh:336) | +| `TestAPI_AgentFilesRoundTrip` | `test/e2e/api_agent_e2e_test.go:92` | api-agent (buckets.sh:191) | +| `TestAPI_AgentPlayers` | `test/e2e/api_agent_e2e_test.go:389` | api-agent (buckets.sh:192) | +| `TestAPI_AgentUnreachable` | `test/e2e/api_agent_e2e_test.go:429` | api-agent (buckets.sh:193) | +| `TestAPI_BootstrapAndLogin` | `test/e2e/api_auth_e2e_test.go:32` | api-auth (buckets.sh:116) | +| `TestAPI_LoginPrivacy` | `test/e2e/api_auth_e2e_test.go:67` | api-auth (buckets.sh:117) | +| `TestAPI_RBAC_ViewerCannotMutate` | `test/e2e/api_auth_e2e_test.go:119` | api-rbac (buckets.sh:179) | +| `TestAPI_AuditEmitsOnMutation` | `test/e2e/api_auth_e2e_test.go:172` | api-auth (buckets.sh:119) | +| `TestAPI_DynamicAuthProviders` | `test/e2e/api_auth_e2e_test.go:240` | api-auth (buckets.sh:118) | +| `TestAPI_AuthConfig_RoleMappings` | `test/e2e/api_auth_e2e_test.go:412` | api-roles (buckets.sh:172) | +| `TestAPI_OIDCHelmSeeded_AdminOnFirstLogin` | `test/e2e/api_auth_e2e_test.go:903` | api-auth (buckets.sh:123) | +| `TestAPI_OIDCHelmSeeded_DefaultRoleOnNoMapping` | `test/e2e/api_auth_e2e_test.go:920` | api-auth (buckets.sh:124) | +| `TestAPI_OIDCHelmSeeded_RoleReevaluatedOnGroupChange` | `test/e2e/api_auth_e2e_test.go:941` | api-auth (buckets.sh:125) | +| `TestAPI_OIDCHelmOverride_EffectiveAtLoginTime` | `test/e2e/api_auth_e2e_test.go:969` | api-roles (buckets.sh:173) | +| `TestAPI_BootstrapAdminForceEndsExistingSessions` | `test/e2e/api_breakglass_e2e_test.go:23` | api-mods (buckets.sh:216) | +| `TestAPI_EventStreamAndRoleEdits_FollowCallerPermissions` | `test/e2e/api_events_roles_scope_e2e_test.go:23` | operator (buckets.sh:107) | +| `TestAPI_LifecycleStartStop` | `test/e2e/api_lifecycle_e2e_test.go:25` | api-agent (buckets.sh:196) | +| `TestAPI_LifecycleRestart` | `test/e2e/api_lifecycle_e2e_test.go:105` | api-agent (buckets.sh:197) | +| `TestAPI_LifecycleClone` | `test/e2e/api_lifecycle_e2e_test.go:173` | api-rbac (buckets.sh:185) | +| `TestAPI_LifecycleNotFound` | `test/e2e/api_lifecycle_e2e_test.go:258` | api-rbac (buckets.sh:186) | +| `TestAPI_ModArchiveConfinement_PathTraversalRejected` | `test/e2e/api_mods_confinement_e2e_test.go:70` | api-mods (buckets.sh:212) | +| `TestAPI_ModArchiveConfinement_SymlinkEscapeRejected` | `test/e2e/api_mods_confinement_e2e_test.go:176` | api-mods (buckets.sh:213) | +| `TestAPI_ModArchiveConfinement_ValidArchiveExtracts` | `test/e2e/api_mods_confinement_e2e_test.go:290` | api-mods (buckets.sh:214) | +| `TestAPI_ModManifestInstallUpgrade` | `test/e2e/api_mods_e2e_test.go:101` | api-mods (buckets.sh:210) | +| `TestAPI_ModUpload` | `test/e2e/api_mods_upload_e2e_test.go:21` | api-mods (buckets.sh:211) | +| `TestAPI_ModuleUpload_ExtractionStaysWithinBudget` | `test/e2e/api_module_upload_budget_e2e_test.go:82` | multicluster (buckets.sh:337) | +| `TestAPI_OwnerCollaboratorAccess` | `test/e2e/api_owner_collab_e2e_test.go:34` | api-roles (buckets.sh:171) | +| `TestAPI_OwnerOnlyServerOperations_RequireOwnerOrAdmin` | `test/e2e/api_owner_only_ops_e2e_test.go:26` | api-mods (buckets.sh:215) | +| `TestAPI_RBAC_ViewerCannotMutate_Matrix` | `test/e2e/api_rbac_matrix_e2e_test.go:20` | api-rbac (buckets.sh:180) | +| `TestAPI_RBAC_OperatorCanWriteServers_NotUsers` | `test/e2e/api_rbac_matrix_e2e_test.go:107` | api-rbac (buckets.sh:181) | +| `TestAPI_OperatorCannotInviteUsers` | `test/e2e/api_rbac_matrix_e2e_test.go:196` | api-rbac (buckets.sh:184) | +| `TestAPI_RBAC_OperatorCannotReachCaptures` | `test/e2e/api_rbac_matrix_e2e_test.go:240` | api-rbac (buckets.sh:182) | +| `TestAPI_RBAC_AdminCanReachAll` | `test/e2e/api_rbac_matrix_e2e_test.go:296` | api-rbac (buckets.sh:183) | +| `TestAPI_CustomRole_Lifecycle` | `test/e2e/api_roles_e2e_test.go:19` | api-roles (buckets.sh:168) | +| `TestAPI_BuiltinRole_Immutable` | `test/e2e/api_roles_e2e_test.go:120` | api-roles (buckets.sh:169) | +| `TestAPI_PerNamespaceBinding_GrantsScopedAccess` | `test/e2e/api_roles_e2e_test.go:146` | api-roles (buckets.sh:170) | +| `TestAPI_PasswordResetInvalidatesSession` | `test/e2e/api_session_e2e_test.go:22` | api-auth (buckets.sh:122) | +| `TestAPI_LoginRateLimit` | `test/e2e/api_session_e2e_test.go:95` | ratelimit (buckets.sh:221) | +| `TestAPI_AuditPaginationAndFilter` | `test/e2e/api_session_e2e_test.go:155` | api-auth (buckets.sh:120) | +| `TestAPI_LogoutInvalidatesSession` | `test/e2e/api_session_e2e_test.go:212` | api-auth (buckets.sh:121) | +| `TestAPI_ThemePreferences` | `test/e2e/api_theme_preferences_e2e_test.go:140` | api-roles (buckets.sh:174) | +| `TestAPI_ConsolePTYRoundTrip` | `test/e2e/api_ws_e2e_test.go:33` | api-agent (buckets.sh:194) | +| `TestAPI_LogsTailWS` | `test/e2e/api_ws_e2e_test.go:123` | api-agent (buckets.sh:195) | +| `TestGameServer_ArkBot_Query` | `test/e2e/ark_bot_e2e_test.go:27` | bot-heavy (buckets.sh:289) | +| `TestGameServer_ArkSurvival_ClusterPersistence` | `test/e2e/arksurvival_cluster_persistence_e2e_test.go:25` | bot-heavy (buckets.sh:291) | +| `TestGameServer_ArkSurvivalEvolvedBot_Query` | `test/e2e/arksurvivalevolved_bot_e2e_test.go:20` | bot-heavy (buckets.sh:290) | +| `TestGameServer_ArmaReforgerBot_Query` | `test/e2e/armareforger_bot_e2e_test.go:20` | bot-heavy (buckets.sh:292) | +| `TestBackup_OperatorMaterializesJob` | `test/e2e/backup_e2e_test.go:27` | operator (buckets.sh:85) | +| `TestBackupSchedule_CreatesBackupCR` | `test/e2e/backup_e2e_test.go:122` | operator (buckets.sh:89) | +| `TestBackup_DeleteForgetsSnapshot` | `test/e2e/backup_forget_e2e_test.go:29` | operator (buckets.sh:88) | +| `TestBackupSchedule_SuspendStopsScheduling` | `test/e2e/backupschedule_e2e_test.go:23` | operator (buckets.sh:90) | +| `TestBackupSchedule_RetentionTrimsPast` | `test/e2e/backupschedule_e2e_test.go:103` | operator (buckets.sh:91) | +| `TestBackupSchedule_ConcurrencyForbid` | `test/e2e/backupschedule_e2e_test.go:223` | operator (buckets.sh:92) | +| `TestGameServer_BeamMPBot_Query` | `test/e2e/beammp_bot_e2e_test.go:20` | bot-heavy (buckets.sh:293) | +| `TestGameServer_CaptureOverhead_Joined` | `test/e2e/capture_overhead_e2e_test.go:133` | capture-overhead (buckets.sh:368) | +| `TestCRD_Validation_GameServerWithoutTemplate` | `test/e2e/crd_validation_e2e_test.go:18` | operator (buckets.sh:47) | +| `TestCRD_Validation_BackupScheduleBadCron` | `test/e2e/crd_validation_e2e_test.go:33` | operator (buckets.sh:48) | +| `TestCRD_Validation_BackupRequiresServerRef` | `test/e2e/crd_validation_e2e_test.go:52` | operator (buckets.sh:49) | +| `TestCRD_Validation_GameTemplateRequiresImage` | `test/e2e/crd_validation_e2e_test.go:68` | operator (buckets.sh:50) | +| `TestCRD_Validation_TunnelServerAddrInvalid` | `test/e2e/crd_validation_e2e_test.go:109` | operator (buckets.sh:52) | +| `TestCRD_Validation_TunnelProxyNameInvalid` | `test/e2e/crd_validation_e2e_test.go:134` | operator (buckets.sh:53) | +| `TestCRD_Validation_TunnelServerAddrValid` | `test/e2e/crd_validation_e2e_test.go:159` | operator (buckets.sh:54) | +| `TestGameServer_CS2Bot_Query` | `test/e2e/cs2_bot_e2e_test.go:35` | bot-heavy (buckets.sh:294) | +| `TestGameServer_DayZBot_Query` | `test/e2e/dayz_bot_e2e_test.go:33` | bot-heavy (buckets.sh:295) | +| `TestGameServer_DayZ_Workshop` | `test/e2e/dayz_workshop_e2e_test.go:25` | bot-heavy (buckets.sh:296) | +| `TestGameServer_DontStarveTogetherBot_Query` | `test/e2e/dontstarve_bot_e2e_test.go:37` | bot-heavy (buckets.sh:297) | +| `TestGameServer_EnshroudedBot_Query` | `test/e2e/enshrouded_bot_e2e_test.go:30` | bot-heavy (buckets.sh:298) | +| `TestGameServer_EuroTruckSimulator2Bot_Query` | `test/e2e/eurotrucksimulator2_bot_e2e_test.go:20` | bot-heavy (buckets.sh:299) | +| `TestGameServer_FactorioBot_Query` | `test/e2e/factorio_bot_e2e_test.go:34` | bot-heavy (buckets.sh:300) | +| `TestBackup_FailsOnMissingPVC` | `test/e2e/failure_paths_e2e_test.go:21` | operator (buckets.sh:86) | +| `TestBackup_FailsOnBadCredentials` | `test/e2e/failure_paths_e2e_test.go:77` | operator (buckets.sh:87) | +| `TestRestore_FailsOnMissingSnapshot` | `test/e2e/failure_paths_e2e_test.go:176` | operator (buckets.sh:95) | +| `TestModule_FailsOnUnreachableRegistry` | `test/e2e/failure_paths_e2e_test.go:256` | operator (buckets.sh:76) | +| `TestGameServer_FarmingSimulator25Bot_Query` | `test/e2e/farmingsimulator25_bot_e2e_test.go:20` | bot-heavy (buckets.sh:301) | +| `TestGameServer_FiveMBot_Query` | `test/e2e/fivem_bot_e2e_test.go:20` | bot-heavy (buckets.sh:302) | +| `TestGameServer_FiveM_Persistence` | `test/e2e/fivem_persistence_e2e_test.go:23` | bot-heavy (buckets.sh:303) | +| `TestGameServer_OperatorMaterializesChildren` | `test/e2e/gameserver_e2e_test.go:50` | operator (buckets.sh:55) | +| `TestGameServer_PVCSurvivesPodDelete` | `test/e2e/gameserver_e2e_test.go:241` | operator (buckets.sh:59) | +| `TestGameServer_HeartbeatReachesRunning` | `test/e2e/gameserver_e2e_test.go:310` | operator (buckets.sh:58) | +| `TestGameServer_NetworkCaptureStartStopDownload` | `test/e2e/gameserver_e2e_test.go:370` | operator (buckets.sh:69) | +| `TestGameServer_NetworkCaptureEphemeralContainer` | `test/e2e/gameserver_e2e_test.go:854` | operator (buckets.sh:70) | +| `TestGameServer_NetworkCaptureRestartCleanup` | `test/e2e/gameserver_e2e_test.go:1092` | operator (buckets.sh:71) | +| `TestGameServer_NetworkCaptureConcurrencyRejected` | `test/e2e/gameserver_e2e_test.go:1281` | operator (buckets.sh:72) | +| `TestGameServer_InstallTimeDefaultApplies` | `test/e2e/gameserver_e2e_test.go:1467` | operator (buckets.sh:60) | +| `TestGameServer_TemplateDefaultOverridesInstallTime` | `test/e2e/gameserver_e2e_test.go:1532` | operator (buckets.sh:61) | +| `TestGameServer_NonexistentStorageClassSurfacesError` | `test/e2e/gameserver_e2e_test.go:1607` | operator (buckets.sh:63) | +| `TestGameServer_TunnelCredentialRefusalSurfaced` | `test/e2e/gameserver_e2e_test.go:1727` | operator (buckets.sh:67) | +| `TestGameServer_ExplicitStorageClassOverridesDefault` | `test/e2e/gameserver_e2e_test.go:1816` | operator (buckets.sh:62) | +| `TestGameServer_IdleNeverSleepsWithoutAPlayerCount` | `test/e2e/gameserver_idle_e2e_test.go:31` | operator (buckets.sh:57) | +| `TestCRD_Validation_IdleRejectsBadValues` | `test/e2e/gameserver_idle_e2e_test.go:120` | operator (buckets.sh:51) | +| `TestGameServer_SuspendScalesToZeroAndBack` | `test/e2e/gameserver_lifecycle_e2e_test.go:25` | operator (buckets.sh:56) | +| `TestGameServer_IngressNetworkPolicyShapeAndCascade` | `test/e2e/gameserver_networkpolicy_e2e_test.go:35` | operator (buckets.sh:68) | +| `TestGameServer_UnownedNamesakeSurvives` | `test/e2e/gameserver_ownership_e2e_test.go:30` | operator (buckets.sh:96) | +| `TestGameServer_VersionSwitch` | `test/e2e/gameserver_version_switch_e2e_test.go:23` | operator (buckets.sh:64) | +| `TestGameServer_WipeReportsFailureOnPermissionDenied` | `test/e2e/gameserver_wipe_e2e_test.go:23` | operator (buckets.sh:65) | +| `TestGameServer_GarrysModBot_Query` | `test/e2e/garrysmod_bot_e2e_test.go:29` | bot-fast (buckets.sh:237) | +| `TestMultiCluster_GatewayParity` | `test/e2e/gatewayparity_e2e_test.go:71` | multicluster (buckets.sh:339) | +| `TestHelmInstall_AllPodsReady` | `test/e2e/helm_install_e2e_test.go:27` | operator (buckets.sh:37) | +| `TestHelmInstall_AllCRDsPresent` | `test/e2e/helm_install_e2e_test.go:73` | operator (buckets.sh:38) | +| `TestHelmInstall_CRDApplyHookSkippedOnFreshInstall` | `test/e2e/helm_install_e2e_test.go:107` | operator (buckets.sh:39) | +| `TestHelmInstall_OperatorLogsClean` | `test/e2e/helm_install_e2e_test.go:128` | operator (buckets.sh:46) | +| `TestHelmInstall_APILogsClean` | `test/e2e/helm_install_e2e_test.go:160` | operator (buckets.sh:45) | +| `TestHelmInstall_APIHealthz` | `test/e2e/helm_install_e2e_test.go:195` | operator (buckets.sh:40) | +| `TestHelmInstall_MetricsNotOnPublicPort` | `test/e2e/helm_install_e2e_test.go:233` | operator (buckets.sh:41) | +| `TestHelmInstall_APIServerEgressPolicy_AllowsPrivateRanges` | `test/e2e/helm_install_e2e_test.go:276` | operator (buckets.sh:42) | +| `TestHelmInstall_BringYourOwnMTLSCA` | `test/e2e/helm_install_e2e_test.go:346` | operator (buckets.sh:43) | +| `TestHelmInstall_MTLSClientCertReissuedOnCAChange` | `test/e2e/helm_install_e2e_test.go:408` | operator (buckets.sh:44) | +| `TestGameServer_Left4Dead2Bot_Query` | `test/e2e/left4dead2_bot_e2e_test.go:20` | bot-heavy (buckets.sh:304) | +| `TestGameServer_MinecraftJavaBot_Joined` | `test/e2e/minecraft_bot_e2e_test.go:45` | bot-fast (buckets.sh:235) | +| `TestModule_DigestPinEnforcedAfterInstall` | `test/e2e/module_digest_pin_e2e_test.go:23` | operator (buckets.sh:79) | +| `TestModuleSourceAndModule` | `test/e2e/module_e2e_test.go:34` | operator (buckets.sh:81) | +| `TestModule_ScaffoldAndPackage` | `test/e2e/module_toolkit_e2e_test.go:27` | operator (buckets.sh:83) | +| `TestModule_SteamcmdScaffoldKeepsNonRootSecurity` | `test/e2e/module_toolkit_e2e_test.go:204` | operator (buckets.sh:84) | +| `TestModule_VerifyRejectsUnsignedBundle` | `test/e2e/module_verify_e2e_test.go:28` | operator (buckets.sh:77) | +| `TestModule_VerifySignedBundleInstalls` | `test/e2e/module_verify_signed_e2e_test.go:26` | operator (buckets.sh:78) | +| `TestModuleSource_RejectsSSRFTarget` | `test/e2e/modulesource_ssrf_e2e_test.go:27` | operator (buckets.sh:80) | +| `TestModuleSourceUpload` | `test/e2e/modulesource_upload_e2e_test.go:22` | operator (buckets.sh:82) | +| `TestGameServer_MountAndBlade2BannerlordBot_Query` | `test/e2e/mountandblade2bannerlord_bot_e2e_test.go:20` | bot-heavy (buckets.sh:305) | +| `TestMultiCluster_ClusterDispatchAndScopedRBAC` | `test/e2e/multicluster_e2e_test.go:203` | multicluster (buckets.sh:338) | +| `TestNetworkCapture_RetentionExpiry` | `test/e2e/networkcapture_retention_test.go:52` | operator (buckets.sh:73) | +| `TestGameServer_NuclearOptionBot_Query` | `test/e2e/nuclearoption_bot_e2e_test.go:22` | bot-heavy (buckets.sh:306) | +| `TestModule_FinalizerBlocksWhileTemplateInUse` | `test/e2e/operator_finalizer_e2e_test.go:26` | operator (buckets.sh:75) | +| `TestGameServer_CascadingDelete` | `test/e2e/operator_finalizer_e2e_test.go:178` | operator (buckets.sh:66) | +| `TestGameTemplate_DeletionWithLiveServer` | `test/e2e/operator_finalizer_e2e_test.go:252` | operator (buckets.sh:74) | +| `TestGameServer_PalworldBot_Query` | `test/e2e/palworld_bot_e2e_test.go:28` | bot-heavy (buckets.sh:307) | +| `TestAddressPool_DefaultPoolNoPreference` | `test/e2e/pool_assignment_e2e_test.go:30` | operator (buckets.sh:97) | +| `TestAddressPool_NamedPoolAssignment` | `test/e2e/pool_assignment_e2e_test.go:138` | operator (buckets.sh:98) | +| `TestAddressPool_NonLoadBalancerIgnored` | `test/e2e/pool_assignment_e2e_test.go:225` | operator (buckets.sh:99) | +| `TestAddressPool_NonexistentPoolError` | `test/e2e/pool_assignment_e2e_test.go:279` | operator (buckets.sh:100) | +| `TestAddressPool_ChangePoolOnRunningServer` | `test/e2e/pool_assignment_e2e_test.go:353` | operator (buckets.sh:101) | +| `TestAddressPool_RESTAPICarriesPool` | `test/e2e/pool_assignment_e2e_test.go:483` | operator (buckets.sh:102) | +| `TestAddressPool_StatusVisibleInAddressAssignmentCondition` | `test/e2e/pool_assignment_e2e_test.go:556` | operator (buckets.sh:103) | +| `TestAddressPool_ExplicitAddressRequest` | `test/e2e/pool_assignment_e2e_test.go:659` | operator (buckets.sh:104) | +| `TestAddressPool_AddressInUseConflict` | `test/e2e/pool_assignment_e2e_test.go:778` | operator (buckets.sh:105) | +| `TestAddressPool_PoolExhausted` | `test/e2e/pool_assignment_e2e_test.go:905` | operator (buckets.sh:106) | +| `TestGameServer_ProjectZomboidBot_Query` | `test/e2e/projectzomboid_bot_e2e_test.go:31` | bot-heavy (buckets.sh:308) | +| `TestRestore_RoundTrip` | `test/e2e/restore_e2e_test.go:29` | operator (buckets.sh:93) | +| `TestRestore_RejectsMissingBackup` | `test/e2e/restore_e2e_test.go:224` | operator (buckets.sh:94) | +| `TestGameServer_RustBot_Query` | `test/e2e/rust_bot_e2e_test.go:32` | bot-heavy (buckets.sh:309) | +| `TestGameServer_SatisfactoryBot_Query` | `test/e2e/satisfactory_bot_e2e_test.go:36` | bot-heavy (buckets.sh:310) | +| `TestGameServer_SevenDaysToDieBot_Query` | `test/e2e/sevendaystodie_bot_e2e_test.go:27` | bot-heavy (buckets.sh:311) | +| `TestGameServer_SquadBot_Query` | `test/e2e/squad_bot_e2e_test.go:20` | bot-heavy (buckets.sh:312) | +| `TestGameServer_Squad_RCON` | `test/e2e/squad_rcon_e2e_test.go:25` | bot-heavy (buckets.sh:313) | +| `TestGameServer_TeamFortress2Bot_Query` | `test/e2e/teamfortress2_bot_e2e_test.go:20` | bot-heavy (buckets.sh:314) | +| `TestGameServer_TeamFortress2_RCON` | `test/e2e/teamfortress2_rcon_e2e_test.go:26` | bot-heavy (buckets.sh:315) | +| `TestTelemetryLifecycle` | `test/e2e/telemetry_e2e_test.go:48` | telemetry (buckets.sh:358) | +| `TestGameServer_TerrariaBot_Joined` | `test/e2e/terraria_bot_e2e_test.go:38` | bot-fast (buckets.sh:236) | +| `TestGameServer_TheIsleBot_Query` | `test/e2e/theisle_bot_e2e_test.go:20` | bot-heavy (buckets.sh:316) | +| `TestGameServer_TModLoaderBot_Query` | `test/e2e/tmodloader_bot_e2e_test.go:20` | bot-heavy (buckets.sh:317) | +| `TestGameServer_TModLoader_Mods` | `test/e2e/tmodloader_mods_e2e_test.go:24` | bot-heavy (buckets.sh:318) | +| `TestUpgrade_FromPreviousRelease` | `test/e2e/upgrade_e2e_test.go:49` | upgrade (buckets.sh:349) | +| `TestGameServer_ValheimBot_Query` | `test/e2e/valheim_bot_e2e_test.go:33` | bot-heavy (buckets.sh:319) | +| `TestGameServer_VRisingBot_Query` | `test/e2e/vrising_bot_e2e_test.go:30` | bot-heavy (buckets.sh:320) | +| `TestGameServer_WakeOnConnect_PingDoesNotWake` | `test/e2e/wake_on_connect_e2e_test.go:28` | bot-fast (buckets.sh:238) | +| `TestGameServer_WakeOnConnect_LoginWakes` | `test/e2e/wake_on_connect_e2e_test.go:144` | bot-fast (buckets.sh:239) | +| `TestGameServer_WakeOnConnect_UnarmedNoSentinel` | `test/e2e/wake_on_connect_e2e_test.go:270` | bot-fast (buckets.sh:240) | + +## Tests in no bucket + +- None among the 156 test functions. `unbucketed()` (`buckets.sh:375`) is empty (`:; }`). +- `TestMain` (`test/e2e/e2e_suite_test.go:29`) is the harness, not a test. `suite_tests` filters it out with `grep -vx 'TestMain'` (`buckets.sh:413`). + +## Bucket summaries + +Bucket list order follows `bucket_names` (`buckets.sh`). Counts are from the bucket lists. + +- **operator** (71, `buckets.sh:36`): Helm install and chart checks, CRD validation, GameServer reconciliation (children, suspend, idle, PVC, storage class, version switch, wipe, cascading delete, tunnel, ingress network policy, network capture), GameTemplate and Module finalizers, module verify and digest pin and SSRF, module scaffold, backups and backup schedules, restore, address pools, and one API event-stream and role-edit test. Zero admin logins. +- **api-auth** (10, login budget): bootstrap and login, login privacy, dynamic auth providers, audit events and pagination, logout and password-reset session invalidation, fake-OIDC Helm-seeded admin and default role mapping. +- **api-roles** (7): custom roles, built-in role immutability, per-namespace bindings, owner and collaborator access, auth role mappings, OIDC Helm override, user theme preferences. +- **api-rbac** (8): RBAC matrix for viewer, operator and admin, operator cannot invite users or reach captures, lifecycle clone and not-found. +- **api-agent** (7): agent proxy files, players and unreachable handling, console PTY round trip, logs WebSocket, start, stop and restart lifecycle. +- **api-mods** (7): mod manifest install and upgrade, mod upload, archive path-traversal and symlink confinement, owner-only server operations, bootstrap-admin force-ending sessions. +- **ratelimit** (1): API login rate limiter. Runs as its own last `go test` call so it does not starve other logins. +- **bot-fast** (6): joins and queries for minecraft-java, terraria and garrys-mod, plus wake-on-connect (ping does not wake, login wakes, unarmed has no sentinel). +- **bot-heavy** (32, `buckets.sh:288`): per-game bot query tests and RCON, mod and persistence tests for heavy games (storage, SteamCMD downloads). Per the comment at `buckets.sh:245`, this bucket is deliberately never run in CI and is only hand-run with `GAMEPLANE_E2E_GAMES=all`. Bucketed is not the same as executed. +- **multicluster** (4): account removal with SSO re-provisioning, module upload extraction budget, cluster dispatch and scoped RBAC across two clusters, gateway parity. +- **upgrade** (1): helm upgrade from the previous published release to the working tree. +- **telemetry** (1): telemetry lifecycle using `helm upgrade --reuse-values` on a dedicated cluster. +- **capture-overhead** (1, `buckets.sh:367`): capture-sidecar overhead gross-regression guard. Feeds a non-blocking CI job, not a blocking matrix leg. diff --git a/specs/024-docs-feature-list-refresh/research-notes/open-specs.md b/specs/024-docs-feature-list-refresh/research-notes/open-specs.md new file mode 100644 index 000000000..0da39bd82 --- /dev/null +++ b/specs/024-docs-feature-list-refresh/research-notes/open-specs.md @@ -0,0 +1,28 @@ +# Open specs snapshot (T007) + +Snapshot of every `specs/-*` folder without a `done_` prefix in the main repo (GP) checkout, plus the session-start blocked-upgrade entries from `CLAUDE.md`. Counts come from `- [X]`/`- [x]` and `- [ ]` lines in each `tasks.md`. Blockers are quoted from the cited file. + +| Spec | Title | Done/Total | Status in plain words | Blocker and what unblocks it | +|---|---|---|---|---| +| 002-nuclear-option-ip-pool | Nuclear Option Module & Load-Balancer IP Pool Override (`specs/002-nuclear-option-ip-pool/spec.md:1`) | 136/143 | Draft (`spec.md:7`). Most tasks are checked. Open: the Nuclear Option join test and its bucket (T058, T059), the join-to-ready timing check (T060), the moderation and invalid-config assertions (T080, T117), the live join run (T126) and the coverage row (T130). T029 is ticked `[X]` but its text says "withdrawn" (`tasks.md:121`). | T058 and T126/T130 are skipped, "awaiting packet capture" (2026-10-11, maintainer decision). The server does not bind UDP 7777, so no join client can be built until a real-client packet capture documents the handshake (`tasks.md:323`, `tasks.md:327`). The unblocker is that capture. Other open items are not named as blocked. | +| 009-remediate-security-dependabot | Code Scanning Vulnerability Remediation & Dependabot PR Integration (`spec.md:1`) | 80/82 | Draft (`spec.md:7`). Security remediation and the Dependabot sweep are done. Only the TypeScript 7 migration and the merge of PR #272 remain (T055, T056, `tasks.md:163-164`). | TypeScript 7 is still blocked. `@typescript-eslint/parser` latest 8.71.2 and canary 8.71.3-alpha.4 both declare `typescript` peer `>=4.8.4 <6.1.0`. Unblocks when a release accepts TS 7. Then do T055/T056 per `CLAUDE.md`. | +| 013-expand-test-coverage | Expand Test Coverage (`spec.md:1`) | 39/62 | Draft (`spec.md:7`). Open work is the live web e2e specs (T037 to T044), the optional-components e2e (T045 to T050), the Postgres e2e (T054 to T058), docs and checks (T059 to T061), and the archive step (T062, `tasks.md:391`). OPEN-DECISIONS.md has 0 open items (`OPEN-DECISIONS.md:3`). | No external blocker is named. T044, T050 and T058 wait for the first green CI run of their job. T062 waits until every PR is merged and every task is checked or withdrawn. None is withdrawn. | +| 019-localization-language-packs | App Localization & Community Language Packs (`spec.md:1`) | 0/105 | Draft (`spec.md:7`). Nothing is checked yet. The four decisions are settled (`plan.md:7`). The first step is the `i18n/` module and checker, which the plan says "unblocks everything" (`plan.md:138`). | No blocker named. Start with T001 (create `i18n/` module). Archive step T105 waits for all tasks to be complete. | +| 021-settings-game-config | Edit game configuration from server Settings (`spec.md:1`) | 20/24 | Implemented, pending CI and review (`spec.md:7`). Open items are CI green and labels (T017, T018, T024), plus the archive step (T019, `tasks.md:38-40`). | Needs CI green and human PR approval (rules 12 and 14). Archive to `done_021` after merge. | +| 022-default-telemetry-dashboard | Default telemetry destination, extended telemetry, and telemetry dashboard (`spec.md:1`) | 109/119 | Draft (clarified), amended 2026-10-14 with the version trend (`spec.md:7`). The shipped feature is unchanged until the new slice lands (`plan.md:223`). Open: T101 to T110 (version-trend tables, views, chart rename, docs, tests, PR). OPEN-DECISIONS.md has 0 open (10 ruled, `OPEN-DECISIONS.md:3`). | T104 needs Pencil design on the machine where Pencil runs (`tasks.md:591`). T109 needs the user's sign-off before editing existing tests `lifecycle_test.go:11` and `charts_test.go:83` (`tasks.md:599`). T110 needs the PR merged before archive. | +| 023-change-based-test-selection | Change-Based Test Selection (`spec.md:1`) | 0/52 | Draft (`spec.md:7`). Nothing is checked yet. No open decisions remain (`OPEN-DECISIONS.md:20`). | No blocker named. T001 creates the `ci: full-run` label. T052 waits for 30 merged PRs to measure runner minutes (`tasks.md:164`). | +| 023-standalone-panel | Standalone panel and API (`spec.md:1`) | n/a (no tasks.md) | Spec only: Intent and Design sections, no status line, no task list (`spec.md:1-31`). | Not tracked by any task list. Needs `tasks.md` before it can be counted. | +| 024-docs-feature-list-refresh | Documentation Refresh, Feature Catalog, and Roadmap Status (`spec.md:1`) | 0/45 | Draft (`spec.md:7`). This is the feature being implemented. OD-2 is settled and no open decisions remain (`OPEN-DECISIONS.md:11`). | No blocker named. | + +## Session-start blocked upgrades (`CLAUDE.md`, "Session start: blocked dependency upgrades") + +Checked with `npm view` on this session. + +- **TypeScript 7: still blocked.** `@typescript-eslint/parser` latest 8.71.2 and canary 8.71.3-alpha.4 both declare `typescript` peer `>=4.8.4 <6.1.0`. Unblocks when a release accepts TS 7. Then bump `typescript` and `@typescript-eslint/*`, do T055/T056 in spec 009, merge #272, and delete the entry. +- **ESLint 10: still blocked.** `eslint-plugin-react` latest 7.37.5 declares `eslint` peer `^3 || ... || ^9.7`, which lacks `^10`. `@eslint/js` latest is 10.0.1. Unblocks when `eslint-plugin-react` accepts `^10`. Then bump `eslint` and `@eslint/js` together, merge #386 (and #387 if open), and delete the entry. + +## Notes + +- Spec 002 has a stale `[X]` on withdrawn task T029 (`tasks.md:121`). Its count includes it as done. +- Spec 022 has 0 open decisions but two gates on T104 and T109 that are not decisions. +- Folders `done_*` were excluded as instructed. diff --git a/specs/024-docs-feature-list-refresh/research-notes/shipped.md b/specs/024-docs-feature-list-refresh/research-notes/shipped.md new file mode 100644 index 000000000..b90cdc9ca --- /dev/null +++ b/specs/024-docs-feature-list-refresh/research-notes/shipped.md @@ -0,0 +1,98 @@ +# Shipped features and done specs (research notes for 024) + +Sources: `CHANGELOG.md` (`[Unreleased]` at line 8, `[0.3.0] — 2026-10-05` header at line 181), and `specs/done_*/spec.md` plus `specs/done_*/tasks.md`. + +## [Unreleased] (no release date; CHANGELOG.md:8) + +### Added +- Independent central panel: Docker Compose runs API/dashboard without host Kubernetes; standalone Helm profile runs without central operator or workloads (CHANGELOG.md:89). +- Credential maintenance: standalone key custody, offline `rotate-panel-key`, `PUT /clusters/{name}/kubeconfig`, `PUT`/`DELETE /clusters/{name}/gateway` (CHANGELOG.md:94). +- Extended telemetry tier: second, separately consented tier with random install ID and fixed-set categories; signed reports (CHANGELOG.md:100). +- Telemetry receiver dashboard and optional public `GET /v1/summary` (CHANGELOG.md:108). +- Proof-of-work on telemetry ingestion, off by default (CHANGELOG.md:118). +- Telemetry provider runbook at `docs/telemetry-provider.md` (CHANGELOG.md:133). +- Optional Steam display names on player lists via a Steam Web API key (CHANGELOG.md:136). +- Nuclear Option player lists are structured (`steamId`, `faction`) (CHANGELOG.md:146). + +### Changed +- New optional `GameTemplate` field `spec.rcon.authentication` (default `true`) allows consoles with no RCON Secret (CHANGELOG.md:152). + +### Removed +- Hell Let Loose module withdrawn from the official catalog (CHANGELOG.md:161). + +### Security +- Standalone credential protection: keys kept outside database volumes, transactional rotation (CHANGELOG.md:168). +- Remote workload boundaries: verified TLS, dial-time unsafe-destination rejection, bounded responses; `GAMEPLANE_REMOTE_ALLOWED_CIDRS` / `api.remoteAllowedCIDRs` (CHANGELOG.md:173). + +### Upgrade-note behaviour changes (user-visible) +- Telemetry on by default for new installs after a first-login notice (CHANGELOG.md:29). +- Installs with a saved basic toggle start sending to the default receiver after upgrade (CHANGELOG.md:46). +- Empty `api.telemetry.endpoint` means the project default receiver (CHANGELOG.md:56). +- Receiver `/metrics` moved to `:8081` and needs the dashboard token (CHANGELOG.md:62). +- Bundled receiver stores data on a PVC by default (CHANGELOG.md:71). +- Receiver `DASHBOARD_TOKEN` must be at least 32 characters (CHANGELOG.md:77). +- Breaking: selected-cluster module/template grants need a cluster-wide grant for remote catalogs (CHANGELOG.md:20). + +## [0.3.0] — 2026-10-05 (CHANGELOG.md:181) + +### Added +- Configurable backup/restore Job retry limit and deadline Helm values (CHANGELOG.md:231). +- Multicluster gateways and unified server management across clusters (CHANGELOG.md:232). +- User theme customization: presets, custom colors with WCAG AA check, custom CSS overlay, safe mode (CHANGELOG.md:238). +- Network capture sidecar: admin-only, on-demand PCAPNG capture from running game pods (CHANGELOG.md:247). +- Module authoring toolkit `gp-module` (scaffold, validate, preview, package) and dashboard Modules builder (CHANGELOG.md:256). +- 13 new top Steam game modules (CHANGELOG.md:264). +- Nuclear Option dedicated server support (CHANGELOG.md:272). +- Install-time config: Helm-seeded OIDC role mappings and default StorageClass for game-data PVCs (CHANGELOG.md:278). +- Share link expiry options (presets, custom date, no expiry) replacing the 90-day cap (CHANGELOG.md:290). +- Console WebSocket robustness on non-default ports and input queueing (CHANGELOG.md:296). +- Existing PVC for SQLite via `api.storage.existingClaim` (CHANGELOG.md:303). +- REST and CLI console protocols for games with HTTP admin APIs or PTY-only consoles (CHANGELOG.md:306). +- Mod archive path confinement (zip-slip and symlink guards) (CHANGELOG.md:317). +- Design and visual polish: HeroUI foundation and brand re-theme (CHANGELOG.md:326). + +### Changed +- Owner-only server operations (transfer, collaborators, wipe, delete) need the owner or an admin (CHANGELOG.md:211). +- Playit tunnel NetworkPolicy adds unrestricted egress (CHANGELOG.md:217). +- Every game pod gets an empty capture emptyDir; existing GameServers roll once on upgrade (CHANGELOG.md:191). +- Helm reinstall over leftover CRDs runs the CRD-apply hook (CHANGELOG.md:222). +- Dependency updates (Node 26, Go 1.27, nginx 1.31, restic 0.19 and others) (CHANGELOG.md:478). + +### Fixed (user-visible, selected) +- SQLite DSN gets `busy_timeout(5000)` by default (CHANGELOG.md:333). +- Agent metrics on a separate unauthenticated `:9090` listener; PodMonitor scrapes it (CHANGELOG.md:334). +- Experimental PostgreSQL backend works end to end (CHANGELOG.md:347). +- Servers page lists servers from every namespace the viewer may read; `GET /namespaces` (CHANGELOG.md:353). +- Catalog tag filters use AND logic (CHANGELOG.md:357). +- Helm upgrade works for releases not named `gameplane` and with `--reuse-values` from beta.8 (CHANGELOG.md:367). +- Data wipe failures now fail the Job and set `DataWipe=False` (CHANGELOG.md:385). +- Backup/restore quiesce lifecycle fixed; restore removes files created after the snapshot (CHANGELOG.md:388). +- Server detail page passes the server namespace; "Shared with you" rows regain Start/Stop/Restart (CHANGELOG.md:399). +- Deleting a capture frees its volume budget immediately (CHANGELOG.md:436). +- Dashboard nginx accepts request bodies up to 64 MiB (CHANGELOG.md:445). +- `clusterOps.externalAddress` for node-routable join commands and kubeconfig (CHANGELOG.md:455). +- Admin Logs shows timestamps for `time`-field log lines (CHANGELOG.md:462). + +### Security hardening (selected) +- Multi-cluster request scoping, OIDC role re-evaluation, bootstrap-admin reset, cluster registration removal, mod bundle integrity, tunnel credential handling (CHANGELOG.md:496-509 and following). + +## Done specs (specs/done_*) + +Outcome is taken from `tasks.md` counts (`[X]` done, `[ ]` open, "withdrawn" mentions) and the spec.md `Status` line (spec.md:7). Several spec.md Status lines were never updated from "Draft" even though their tasks are complete, so the task counts are the more reliable signal. + +- 001 — Game Protocol E2E Coverage (specs/done_001-gameprotocol-e2e-coverage/spec.md:1). Outcome: complete, 32 tasks [X], 0 open. Status line: Draft. +- 003 — Network Capture Sidecar for Game Servers (specs/done_003-network-capture-sidecar/spec.md:1). Outcome: complete, 91 tasks [X], 0 open. Status line: Draft, amended 2026-08-23. +- 004 — Lint Backlog Wave 2: Static Analysis Gate for api, agent, test/e2e (specs/done_004-lint-backlog-wave2/spec.md:1). Outcome: complete, 90 tasks [X], 0 open. Status line: Draft. +- 005 — Protocol Classifier Registry Refactor (specs/done_005-gameproto-classifier-registry/spec.md:1). Outcome: complete, 85 tasks [X], 0 open; has completion-summary.md and verification.md. Status line: Draft. +- 006 — Install-Time Configuration (Storage Class and OIDC Role Mapping) (specs/done_006-install-time-config/spec.md:1). Outcome: complete, 62 tasks [X], 0 open. Status line: Draft. +- 007 — Capture Overhead CI Regression Guard (specs/done_007-capture-overhead-guard/spec.md:1). Outcome: complete, 14 tasks [X], 0 open. Status line: Planned (no research.md or checklists folder). +- 008 — Hardened GitHub Actions CI/CD, AI Automation and Multi-Module Dependabot (specs/done_008-hardened-github-actions/spec.md:1). Outcome: complete, 31 tasks [X], 0 open, 1 withdrawn. Status line: Draft. +- 010 — Easy Module Building and Authoring Toolkit (specs/done_010-easy-module-building/spec.md:1). Outcome: complete, 38 tasks [X], 0 open. Status line: Draft. +- 011 — Complete Module Specifications and Compliance Verification (specs/done_011-add-missing-module-specs/spec.md:1). Outcome: complete, 18 tasks [X], 0 open. Status line: Draft. +- 012 — Documentation Refresh, Comparison Table and External Outreach (specs/done_012-docs-refresh-and-outreach/spec.md:1). Outcome: complete, 65 tasks [X], 0 open, 2 withdrawn. Status line: Draft. +- 014 — Rebuild the dashboard on the HeroUI design system (specs/done_014-heroui-web-rebuild/spec.md:1). Outcome: complete, 215 tasks [X], 0 open, 1 withdrawn. Status line: Draft. +- 015 — Dedicated Server Modules for Top Steam Games (specs/done_015-top-steam-game-modules/spec.md:1). Outcome: complete, 147 tasks [X], 0 open, 5 withdrawn. Status line: Draft. +- 016 — User Theme Customization (specs/done_016-user-theme-customization/spec.md:1). Outcome: complete, 46 tasks [X], 0 open, 1 withdrawn. Status line: Ready for Planning. +- 017 — Configurable Share Link Expiry (specs/done_017-share-link-expiry/spec.md:1). Outcome: complete, 33 tasks [X], 0 open. Status line: Draft. +- 018 — v0.3 Release Readiness Audit (specs/done_018-v0-3-release-readiness/spec.md:1). Outcome: complete, 51 tasks [X], 0 open, 27 withdrawn. Status line: Draft. +- 020 — Forget Restic Snapshot on Backup Delete (specs/done_020-backup-snapshot-forget/spec.md:1). Outcome: implemented, merged in PR #567 on 2026-10-05, 18 tasks [X], 0 open. Status line: Implemented. diff --git a/specs/024-docs-feature-list-refresh/stale-facts.md b/specs/024-docs-feature-list-refresh/stale-facts.md new file mode 100644 index 000000000..9cd0813ed --- /dev/null +++ b/specs/024-docs-feature-list-refresh/stale-facts.md @@ -0,0 +1,57 @@ +# Stale facts: spec 024 + +Status date: 2026-10-14. Sources: GP master `fecfa63d`, WEB main `87b64a9`, MOD main `91b64cc`, chart appVersion `0.3.0`. + +One record per correction (FR-012): page, old value, new value, confirming source. + +| Repo | Page | Old value | New value | Source | +|---|---|---|---|---| +| GP | README.md:70 | `[BETA]` tag on the Gameplane column, Deployment/runtime row | Drop the tag; use "pre-v1 release" (`v0.3.0`) | README.md:12, :43, :65 (status wording) | +| GP | docs/oidc.md:327 | "promote the user via Admin Settings → Users" | "promote the user on the Users page (sidebar: Users & RBAC, `/users`)" | web/src/router/tree.tsx:111 (`/users`); web/src/components/AppLayout.tsx:186 (label "Users & RBAC"); AdminSettings.tsx has no Users section (General, Authentication, Mod registries, Notifications, Telemetry, Updates) | +| GP | docs/oidc.md:383 | "An admin must promote them under Admin Settings → Users" | "An admin must promote them on the Users page (sidebar: Users & RBAC, `/users`)" | same as docs/oidc.md:327 | +| GP | docs/dependencies.md:48 | `telemetry-receiver/` row: "Standalone collector for the API's opt-in anonymous usage telemetry" (wording: API telemetry described as opt-in; judgment call) | "...for the API's anonymous usage telemetry (on by default for new installs after the admin notice; `api.telemetry.enabled=false` turns it off)" | research-notes/defaults.md §1 (`api.telemetry.enabled` true, values.yaml:289); docs/install.md:479-485 (Defaults table: new install with destination = basic on) | +| GP | docs/roadmap.md, Game server join protocol coverage | "26 modules are blocked on undocumented or partially-documented wire-protocol formats" (2 + 26 + 2 = 30, not 29) | "25 modules are blocked on undocumented or partially-documented wire-protocol formats" (2 + 25 + 2 = 29) | docs/game-coverage.md (29 module rows: 2 covered-in-ci, 25 blocked-doc, 2 out-of-scope-by-design) | +| GP | docs/roadmap.md, Game module template specifications | "are a known incomplete item for v1" (no count) | "As of 2026-10-14, 26 of the 29 modules have a specs.md; enshrouded, minecraft-java and v-rising do not yet" | /home/user/module/*/specs.md (26 files present; missing: enshrouded, minecraft-java, v-rising) | +| GP | docs/roadmap.md, Wake-on-connect, Honest limitations | "Only Minecraft and Terraria get real parsing; the other 14 shipped modules use generic (they are UDP-only and have no connection to hold)" | "Only Minecraft and Terraria get real parsing; tModLoader also declares the Terraria parser. The other 26 of the 29 catalog modules use the generic default" | /home/user/module/minecraft-java/template.yaml:59, terraria/template.yaml:96, tmodloader/template.yaml:94 (`wakeProtocol` is declared only in these three; grep of the module repo found no other declarations) | +| GP | docs/roadmap.md, Current release (new section) | "Gameplane is currently v0.3.0" (no release date, no status date) | "`v0.3.0` (pre-v1), released 2026-10-05; status as of 2026-10-14" | CHANGELOG.md:181 (`[0.3.0] — 2026-10-05`); spec 024 status date 2026-10-14 | +| GP | docs/roadmap.md, Shipped section | "Shipped toward v1" with no release-grouped entries for v0.3.0 or [Unreleased] | Split into "Shipped in v0.3.0 and since" with "Since v0.3.0 (Unreleased)", "Shipped in v0.3.0" and "Earlier releases (before v0.3.0)" (historical items kept verbatim, markers kept) | CHANGELOG.md [Unreleased] (line 8) and [0.3.0] (line 181); research-notes/shipped.md | +| GP | docs/roadmap.md, Section name | "Explicitly out of scope for v1" | "Out of scope for v1" (section order per T018) | Task T018 section order | +| GP | docs/roadmap.md, In progress (new section) | not present | One line per open spec: 002, 009, 013, 019, 021, 022, 023 (change-based), 023 (standalone panel), 024 | research-notes/open-specs.md; specs/*/tasks.md; PR #668 (spec 019) per task brief | +| WEB | src/config.ts:12 | `export const VERSION = "v0.2.0-beta.8";` | `export const VERSION = "v0.3.0";` | CLAUDE.md status (pre-v1 release v0.3.0); `charts/gameplane/values.yaml:686` git ref v0.3.0 | +| WEB | src/content/docs/faq.mdx:38 | "It's in beta ({VERSION})." | "It's a pre-v1 release ({VERSION})." | CLAUDE.md status line; beta wording dropped | +| WEB | src/content/docs/comparison.mdx:14 | "[BETA]" tag on deployment row | "[pre-v1]" tag (or drop the tag) | CLAUDE.md status line | +| WEB | src/content/docs/comparison.mdx:19 | "30 ready-to-use templates on master (16 in v0.2.0-beta.8; the other 14 arrive in v0.3.0)" | "29 ready-to-use templates on master (16 in v0.2.0-beta.8; 13 arrive in v0.3.0; Hell Let Loose withdrawn)" | research-notes/catalog.md (a): 29 module.yaml dirs, Hell Let Loose absent; `repository-components.mdx:73` already says 29 | +| WEB | src/content/docs/installation.mdx:160 | "16 modules in v0.2.0, 30 in v0.3.0" | "16 modules in v0.2.0, 29 in v0.3.0" | catalog.md (a): 29; `repository-components.mdx:73` | +| WEB | src/content/docs/changelog.mdx:32 | "14 new game modules (... Hell Let Loose ...), for 30 in total" | Keep as historical release note (no edit). Optional: add "Hell Let Loose was later withdrawn" | catalog.md (a); `repository-components.mdx:73` | +| WEB | src/content/docs/audit-observability-admin.mdx:33 | "Telemetry is opt-in and off by default" | "Usage telemetry is on by default for new installs; admins can turn it off" (matches `platform-settings-telemetry.mdx:47`) | defaults.md section 1: `charts/gameplane/values.yaml:289` `api.telemetry.enabled: true` | +| WEB | src/content/docs/repository-components.mdx:52 | "reconciles 8 CRDs in v0.2.0-beta.8 (...) a 9th, NetworkCapture, is coming in v0.3.0" | "reconciles 9 CRDs in v0.3.0 (..., NetworkCapture)" | CLAUDE.md (operator: 9 CRDs); api-crds.mdx:26 already names NetworkCapture as ninth | +| WEB | src/content/docs/production-installation.mdx:84 | "The current stable release is **v0.2.0-beta.8**." | "The current release is **v0.3.0** (pre-v1)." | CLAUDE.md status line | +| WEB | src/content/docs/production-installation.mdx:92 | `ghcr.io/valgulnecron/gameplane/operator:0.2.0-beta.8` | `ghcr.io/valgulnecron/gameplane/operator:0.3.0` | CLAUDE.md status (v0.3.0) | +| WEB | src/content/docs/supported-versions-matrix.mdx:46 | "**Current stable release:** v0.2.0-beta.8 (latest published)" | "**Current release:** v0.3.0 (latest published, pre-v1)" | CLAUDE.md status line | +| WEB | src/content/docs/installation.mdx:184, 188, 263, 275, 288 | `0.2.0-beta.8` in install examples (`--version`, image tag, helm upgrade command) | `0.3.0` | CLAUDE.md status line; config.ts VERSION row above | +| WEB | src/content/docs/getting-started.mdx:27 | `--version 0.2.0-beta.8` | `--version 0.3.0` | CLAUDE.md status line | +| WEB | src/content/docs/air-gapped-installation.mdx:81, 85, 101, 102, 106, 107, 113, 114, 193, 206, 211, 217, 249, 267 | `0.2.0-beta.8` image, chart and tag pins | `0.3.0` | CLAUDE.md status line | +| WEB | src/content/docs/cluster-nodes-kubeconfig.mdx:103 | `--version 0.2.0-beta.8` | `--version 0.3.0` | CLAUDE.md status line | +| WEB | src/content/docs/platform-disaster-recovery.mdx:81 | `--version 0.2.0-beta.8` (and example `v0.2.0-beta.8` at line 50) | `0.3.0` / `v0.3.0` | CLAUDE.md status line | +| WEB | src/content/docs/upgrades-migrations.mdx:113, 116, 223 | Upgrade example `--version 0.2.0-beta.8`; "Read these before upgrading from v0.2.0-beta.8" | Example pin `0.3.0`; section intro to name the current previous release, or keep if it is meant as the from-version | CLAUDE.md status line; lines 177-180 rollback example left as-is (example pair) | +| WEB | src/content/docs/api-crds.mdx:26; cluster-storage.mdx:54; helm-values-reference.mdx:69, 169; installation.mdx:98-101; remote-agent-gateway.mdx:21; multi-cluster.mdx:23; users-auth-rbac.mdx:116; server-networking-tunnels.mdx:22, 315; gameserver-template-schema.mdx:189; modules-sources.mdx:88; module-authoring-quickstart.mdx:145; module-cluster-crds.mdx:65-66; dashboard-tour.mdx:20; comparison.mdx:20; ownership-danger-zone.mdx:18 | Future-tense or "not available in beta.8" availability wording (e.g. "ships in v0.3.0 and is not available in beta.8") | Decision needed: once v0.3.0 is the current release, reword to "available in v0.3.0" or drop the beta.8 contrast. Left unedited pending human decision | CLAUDE.md status line; these are present-tense claims about v0.3.0 features that now ship | +| WEB | src/content/docs/backup-destinations.mdx:111 | "Open **Settings** → **Backup destinations**" | Unverified. No `/settings` route in `web/src/router/tree.tsx` (only `/settings/theme`). Confirm the actual menu label before editing | web/src/router/tree.tsx:139 | +| GP | README.md:75 | "30 ready-to-use templates shipped" (Gameplane column) | "29 ready-to-use templates shipped" | /home/user/module: 29 game directories with module.yaml (research-notes/catalog.md) | +| GP | README.md:89 | "30 ready-to-use game templates" | Features section replaced by a summary linking FEATURES.md (29 templates) | research-notes/catalog.md | +| GP | README.md:162 | "30 pre-packaged game templates" | "29 pre-packaged game templates" | research-notes/catalog.md | +| GP | docs/comparison-sources.md:72 | "30 ready-to-use templates in gameplane-module repository" | "29 ready-to-use templates" | research-notes/catalog.md | +| GP | docs/oidc.md:21 | Helm-seeded OIDC role mapping "(unreleased; ships in the next release)" | shipped in v0.3.0 | CHANGELOG.md:280 (inside [0.3.0], header CHANGELOG.md:181) | +| GP | docs/security.md:1073 | Helm-seeded OIDC role mapping "(unreleased; ships in the next release)" | shipped in v0.3.0 | CHANGELOG.md:280 | +| MOD | (no root README.md) | n/a | MOD README current: the module repo has no root README listing modules | `ls /home/user/module` | +| WEB | src/content/docs/installation.mdx:243 | Callout "Coming in v0.3.0": "NetworkCapture ... will be the 9th CRD, arriving in v0.3.0-rc.1." | Title "Available since v0.3.0": "NetworkCapture ... is the 9th CRD, available since v0.3.0." | CLAUDE.md status (v0.3.0 released); api-crds.mdx:26 | +| WEB | src/content/docs/production-installation.mdx:32 | "(e.g., `0.2.0-beta.8`)" | "(e.g., `0.3.0`)" | CLAUDE.md status line | +| WEB | src/content/docs/supported-versions-matrix.mdx:47 | "**Development release:** v0.3.0-rc.1 (tagged, not published as a release; ...)" | "**Unreleased:** changes since v0.3.0 are listed under Unreleased in the Changelog" | CHANGELOG.md [Unreleased] (line 8) and [0.3.0] (line 181) | +| WEB | src/content/docs/upgrades-migrations.mdx:35 | "Unreleased features marked as 'Coming in v0.3.0' require `v0.3.0-rc.1` or later." | "Features marked as 'Available since v0.3.0' require `v0.3.0` or later." | CLAUDE.md status line (v0.3.0 released) | +| WEB | src/content/docs/upgrades-migrations.mdx:217 | Callout title "Arrives in v0.3.0" | Title "Available since v0.3.0" | CLAUDE.md status line (v0.3.0 released) | +| WEB | src/content/docs/roadmap.mdx (whole page) | "## Beta status — {VERSION}"; "## Coming in v0.3.0" section with beta.8 contrast; "Recently shipped" and "Known limitations" lists | Rewritten to mirror docs/roadmap.md section order and statuses: Current release (pre-v1, released 2026-10-05), Shipped in v0.3.0 and since, In progress, Blocking v1, Wanted for v1, Known gaps, Out of scope for v1, Game server join protocol coverage (29 modules); links to repo files use GitHub URLs; HTML doc-version comments dropped (not valid MDX) | docs/roadmap.md (full file, T032) | +| WEB | availability callouts: api-crds.mdx:25, cluster-storage.mdx:53, gameserver-template-schema.mdx:188, users-auth-rbac.mdx:115, remote-agent-gateway.mdx:20, multi-cluster.mdx:22, modules-sources.mdx:87 and :102, module-authoring-quickstart.mdx:43 and :144, helm-values-reference.mdx:68, :119, :168, ownership-danger-zone.mdx:17, installation.mdx:243 | "Coming in v0.3.0" / "ships in v0.3.0 and is not available in beta.8" / "will ship" / "is not yet available in beta.8" | "Available since v0.3.0" / "are available since v0.3.0" (beta.8 contrast dropped where it only said the feature was missing; historical beta.8 statements kept) | CLAUDE.md status line (v0.3.0 released 2026-10-05, CHANGELOG.md:181); T033 availability-wording group | +| GP | README.md:74 | Access control & authentication: "Local argon2id + OIDC (Keycloak/Google/GitHub)" | README GitHub SSO claim unsupported by docs/oidc.md; new value: "Local argon2id + OIDC (Keycloak, Authentik, Google, Okta, Entra ID)" | docs/oidc.md provider guides list five providers and no GitHub; api/internal/auth has no GitHub login path (api/internal/handlers/config.go:551 accepts the label "github" only) | +| WEB | src/content/docs/audit-observability-admin.mdx:74 | "opt-in telemetry" | "telemetry settings" | defaults.md: `api.telemetry.enabled` true (values.yaml:289) | +| GP | docs/install.md:126 | default StorageClass "(unreleased; ships in the next release)" | "(since v0.3.0)" | CHANGELOG.md:286 (inside [0.3.0]) | +| GP | docs/install.md:148 | Helm-seeded role mapping "(unreleased; ships in the next release)" | "(since v0.3.0)" | CHANGELOG.md:280 (inside [0.3.0]) | +| GP | charts/gameplane/values.yaml:121 (comment) | "StorageClassNotFound" reason | "PVCProvisioningFailed" reason | operator/internal/controller/gameserver_status.go:30 | diff --git a/specs/024-docs-feature-list-refresh/tasks.md b/specs/024-docs-feature-list-refresh/tasks.md index 3812fe5c5..c8d7e3707 100644 --- a/specs/024-docs-feature-list-refresh/tasks.md +++ b/specs/024-docs-feature-list-refresh/tasks.md @@ -26,9 +26,9 @@ description: "Task list for spec 024: documentation refresh, feature catalog and **Purpose**: Branches and the status snapshot every story reads from. -- [ ] T001 Create branch `claude/024-docs-refresh` in GP from latest `master` (after the spec docs PR merges) and branch `claude/024-docs-refresh` in WEB from latest `main` -- [ ] T002 Record the status date and source revisions (GP `master` SHA, WEB `main` SHA, MOD `master` SHA, chart `appVersion` from `charts/gameplane/Chart.yaml`) at the top of `specs/024-docs-feature-list-refresh/stale-facts.md` -- [ ] T003 [P] Create `specs/024-docs-feature-list-refresh/feature-evidence.md` with the record header from [data-model.md](./data-model.md) (area, capability, maturity, e2e test, default source, notes) +- [X] T001 Create branch `claude/024-docs-refresh` (used the session branch `claude/project-thread-6pzhf7` instead) in GP from latest `master` (after the spec docs PR merges) and branch `claude/024-docs-refresh` in WEB from latest `main` +- [X] T002 Record the status date and source revisions (GP `master` SHA, WEB `main` SHA, MOD `master` SHA, chart `appVersion` from `charts/gameplane/Chart.yaml`) at the top of `specs/024-docs-feature-list-refresh/stale-facts.md` +- [X] T003 [P] Create `specs/024-docs-feature-list-refresh/feature-evidence.md` with the record header from [data-model.md](./data-model.md) (area, capability, maturity, e2e test, default source, notes) --- @@ -36,11 +36,11 @@ description: "Task list for spec 024: documentation refresh, feature catalog and **Purpose**: The fact base the catalog, roadmap and sweep all use. Each task writes a section of `specs/024-docs-feature-list-refresh/research-notes.md` (scratch input, committed for review). -- [ ] T004 [P] List every e2e test registered in a bucket: parse `test/e2e/buckets.sh` and `test/e2e/*_test.go` into a table test name → bucket; note tests excluded from all buckets (they do not count as CI coverage, research R5) -- [ ] T005 [P] List install defaults: every enable/disable toggle in `charts/gameplane/values.yaml` with its default, plus build-tag features (`-tags postgres`) and separately installed components (sentinel, capture sidecar, tunnels, audit-syslog-bridge, telemetry-receiver, mcp-server, agent gateway) -- [ ] T006 [P] List the module catalog: game directories in MOD (expected 29, Hell Let Loose withdrawn) and the mod registries implemented in code (expected 10: Modrinth, CurseForge, Thunderstore, Hangar, Factorio Mod Portal, Steam Workshop, SpigotMC, GitHub Releases, uMod, Nexus Mods), citing the code location of the registry list -- [ ] T007 [P] Snapshot open specs: for each `specs/-*` folder without `done_` in GP `master`, record done/open task counts from its `tasks.md`, blockers named in it, and its open PRs (research R7 table refreshed) -- [ ] T008 [P] Summarise shipped work: `CHANGELOG.md` sections `[0.3.0]` and `[Unreleased]`, plus each `specs/done_*` folder's one-line outcome +- [X] T004 [P] List every e2e test registered in a bucket: parse `test/e2e/buckets.sh` and `test/e2e/*_test.go` into a table test name → bucket; note tests excluded from all buckets (they do not count as CI coverage, research R5) +- [X] T005 [P] List install defaults: every enable/disable toggle in `charts/gameplane/values.yaml` with its default, plus build-tag features (`-tags postgres`) and separately installed components (sentinel, capture sidecar, tunnels, audit-syslog-bridge, telemetry-receiver, mcp-server, agent gateway) +- [X] T006 [P] List the module catalog: game directories in MOD (expected 29, Hell Let Loose withdrawn) and the mod registries implemented in code (expected 10: Modrinth, CurseForge, Thunderstore, Hangar, Factorio Mod Portal, Steam Workshop, SpigotMC, GitHub Releases, uMod, Nexus Mods), citing the code location of the registry list +- [X] T007 [P] Snapshot open specs: for each `specs/-*` folder without `done_` in GP `master`, record done/open task counts from its `tasks.md`, blockers named in it, and its open PRs (research R7 table refreshed) +- [X] T008 [P] Summarise shipped work: `CHANGELOG.md` sections `[0.3.0]` and `[Unreleased]`, plus each `specs/done_*` folder's one-line outcome **Checkpoint**: research-notes.md complete; stories can start in parallel. @@ -52,15 +52,15 @@ description: "Task list for spec 024: documentation refresh, feature catalog and **Independent Test**: [quickstart.md](./quickstart.md) scenario 1: 10 random rows each have a matching evidence record, a label that obeys the data-model validation, and a docs link that documents the capability. -- [ ] T009 [US1] Write the `FEATURES.md` skeleton per [contracts/features-catalog.md](./contracts/features-catalog.md): title, intro explaining the labels (stable / beta / experimental / planned / in progress, and the on/off/n/a default column), status date, link to `docs/roadmap.md`, and the 18 area `##` headings from research R6, each with the catalog table header -- [ ] T010 [P] [US1] Inventory rows for areas *Servers and lifecycle*, *Console and admin actions*, *Files*, *Players* from `web/src/router/tree.tsx`, `web/src/routes/`, `api/cmd/main.go`, README Features, `docs/architecture.md` and website pages `server-operations.mdx`, `console-events-logs.mdx`, `file-manager.mdx`, `player-administration.mdx`; write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/servers-console-files-players.md` -- [ ] T011 [P] [US1] Inventory rows for *Mods*, *Backups and restore*, *Game modules and catalog*, *Module authoring* from README Mod Management, `docs/module-authoring.md`, `gp-module/`, `operator/api/v1alpha1/` (ModuleSource, GameTemplate, Backup*, Restore), website `mods-modpacks.mdx`, `backups-recovery.mdx`, `modules-sources.mdx`; write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/mods-backups-modules-authoring.md` -- [ ] T012 [P] [US1] Inventory rows for *Networking and tunnels*, *Idle sleep and wake*, *Multi-cluster* from `docs/networking.md`, `docs/tunnels.md`, `docs/multicluster-*.md`, `docs/unified-dashboard.md`, `docs/remote-parity.md`, `sentinel/`, `tunnel/`; write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/networking-sleep-multicluster.md` -- [ ] T013 [P] [US1] Inventory rows for *Users, auth and access*, *Audit and security*, *Notifications*, *Telemetry* from `docs/oidc.md`, `docs/security.md`, `docs/notifications.md`, `docs/telemetry-provider.md`, `docs/key-rotation.md`, `api/internal/rbac/`, `audit-syslog-bridge/README.md`, `telemetry-receiver/README.md`; write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/access-audit-notify-telemetry.md` -- [ ] T014 [P] [US1] Inventory rows for *Observability and admin*, *Localization*, *Developer and operator tooling* from `docs/install.md`, `docs/gateway-install.md`, `mcp-server/README.md`, `capture-sidecar/`, `charts/gameplane/`, website `metrics-alerts-dashboards.mdx`, `system-log-viewer.mdx`; localization rows reflect only what is merged on `master` (spec 019 work is `in progress`, FR-004); write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/observability-l10n-tooling.md` -- [ ] T015 [US1] Merge the five `catalog-drafts/*.md` files into `FEATURES.md` (rows under their area headings) and `feature-evidence.md` (single owner of both files; delete `catalog-drafts/` afterwards), then apply the maturity rule to every row using T004/T005 facts: `stable` only with `default = on` and a bucketed e2e test; `default = off` rows are `beta` or `experimental`; `experimental` only where docs already say so (PostgreSQL driver); planned/in-progress rows get `default = n/a` and a roadmap link; fill each evidence record (`FEATURES.md`, `feature-evidence.md`) -- [ ] T016 [US1] Deduplicate and order rows within each area, check every Docs link target exists in GP, and confirm the row count is in the 100–150 range or note why not in `feature-evidence.md` -- [ ] T017 [US1] Tier-up review (`sonnet`): check the combined `FEATURES.md` + `feature-evidence.md` diff against research-notes.md and the sources; fixes go to a new `haiku` fix wave +- [X] T009 [US1] Write the `FEATURES.md` skeleton per [contracts/features-catalog.md](./contracts/features-catalog.md): title, intro explaining the labels (stable / beta / experimental / planned / in progress, and the on/off/n/a default column), status date, link to `docs/roadmap.md`, and the 18 area `##` headings from research R6, each with the catalog table header +- [X] T010 [P] [US1] Inventory rows for areas *Servers and lifecycle*, *Console and admin actions*, *Files*, *Players* from `web/src/router/tree.tsx`, `web/src/routes/`, `api/cmd/main.go`, README Features, `docs/architecture.md` and website pages `server-operations.mdx`, `console-events-logs.mdx`, `file-manager.mdx`, `player-administration.mdx`; write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/servers-console-files-players.md` +- [X] T011 [P] [US1] Inventory rows for *Mods*, *Backups and restore*, *Game modules and catalog*, *Module authoring* from README Mod Management, `docs/module-authoring.md`, `gp-module/`, `operator/api/v1alpha1/` (ModuleSource, GameTemplate, Backup*, Restore), website `mods-modpacks.mdx`, `backups-recovery.mdx`, `modules-sources.mdx`; write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/mods-backups-modules-authoring.md` +- [X] T012 [P] [US1] Inventory rows for *Networking and tunnels*, *Idle sleep and wake*, *Multi-cluster* from `docs/networking.md`, `docs/tunnels.md`, `docs/multicluster-*.md`, `docs/unified-dashboard.md`, `docs/remote-parity.md`, `sentinel/`, `tunnel/`; write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/networking-sleep-multicluster.md` +- [X] T013 [P] [US1] Inventory rows for *Users, auth and access*, *Audit and security*, *Notifications*, *Telemetry* from `docs/oidc.md`, `docs/security.md`, `docs/notifications.md`, `docs/telemetry-provider.md`, `docs/key-rotation.md`, `api/internal/rbac/`, `audit-syslog-bridge/README.md`, `telemetry-receiver/README.md`; write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/access-audit-notify-telemetry.md` +- [X] T014 [P] [US1] Inventory rows for *Observability and admin*, *Localization*, *Developer and operator tooling* from `docs/install.md`, `docs/gateway-install.md`, `mcp-server/README.md`, `capture-sidecar/`, `charts/gameplane/`, website `metrics-alerts-dashboards.mdx`, `system-log-viewer.mdx`; localization rows reflect only what is merged on `master` (spec 019 work is `in progress`, FR-004); write rows and evidence records to `specs/024-docs-feature-list-refresh/catalog-drafts/observability-l10n-tooling.md` +- [X] T015 [US1] Merge the five `catalog-drafts/*.md` files into `FEATURES.md` (rows under their area headings) and `feature-evidence.md` (single owner of both files; delete `catalog-drafts/` afterwards), then apply the maturity rule to every row using T004/T005 facts: `stable` only with `default = on` and a bucketed e2e test; `default = off` rows are `beta` or `experimental`; `experimental` only where docs already say so (PostgreSQL driver); planned/in-progress rows get `default = n/a` and a roadmap link; fill each evidence record (`FEATURES.md`, `feature-evidence.md`) +- [X] T016 [US1] Deduplicate and order rows within each area, check every Docs link target exists in GP, and confirm the row count is in the 100–150 range or note why not in `feature-evidence.md` +- [X] T017 [US1] Tier-up review (`sonnet`): check the combined `FEATURES.md` + `feature-evidence.md` diff against research-notes.md and the sources; fixes go to a new `haiku` fix wave **Checkpoint**: `FEATURES.md` draft complete on the GP branch (not yet merged). @@ -72,11 +72,11 @@ description: "Task list for spec 024: documentation refresh, feature catalog and **Independent Test**: quickstart scenario 2: every non-`done_` spec folder has a roadmap line whose status matches its `tasks.md` on the status date. -- [ ] T018 [US2] Restructure `docs/roadmap.md` into the section order from [data-model.md](./data-model.md) (current release + status date; shipped in v0.3.0 and since; in progress; blocking v1; wanted for v1; known gaps; out of scope; join protocol coverage), keeping existing `` markers on historical headings -- [ ] T019 [US2] Add the "Shipped in v0.3.0" and "Since v0.3.0" entries to `docs/roadmap.md` from T008, each linking `CHANGELOG.md` -- [ ] T020 [US2] Add the "In progress" section to `docs/roadmap.md` with one line per open spec from T007 (002, 009, 013, 019, 021, 023 unless the snapshot differs): plain-words status, blocker and what unblocks it -- [ ] T021 [US2] Re-check every existing "planned" / "known gap" item in `docs/roadmap.md` against `master` and MOD (e.g. "Game module template specifications" against `modules//specs.md` presence; Users page service-accounts / IdP tabs against `web/src/routes/Users.tsx`); move shipped items to the shipped section with their release, correct or remove stale ones, and log each change in `stale-facts.md` -- [ ] T022 [US2] Update the join-coverage counts in the roadmap's last section from `docs/game-coverage.md` so they total 29 modules +- [X] T018 [US2] Restructure `docs/roadmap.md` into the section order from [data-model.md](./data-model.md) (current release + status date; shipped in v0.3.0 and since; in progress; blocking v1; wanted for v1; known gaps; out of scope; join protocol coverage), keeping existing `` markers on historical headings +- [X] T019 [US2] Add the "Shipped in v0.3.0" and "Since v0.3.0" entries to `docs/roadmap.md` from T008, each linking `CHANGELOG.md` +- [X] T020 [US2] Add the "In progress" section to `docs/roadmap.md` with one line per open spec from T007 (002, 009, 013, 019, 021, 023 unless the snapshot differs): plain-words status, blocker and what unblocks it +- [X] T021 [US2] Re-check every existing "planned" / "known gap" item in `docs/roadmap.md` against `master` and MOD (e.g. "Game module template specifications" against `modules//specs.md` presence; Users page service-accounts / IdP tabs against `web/src/routes/Users.tsx`); move shipped items to the shipped section with their release, correct or remove stale ones, and log each change in `stale-facts.md` +- [X] T022 [US2] Update the join-coverage counts in the roadmap's last section from `docs/game-coverage.md` so they total 29 modules **Checkpoint**: repo roadmap complete on the GP branch. @@ -88,14 +88,14 @@ description: "Task list for spec 024: documentation refresh, feature catalog and **Independent Test**: quickstart scenarios 3 and 8: no stale counts remain, every fix is in `stale-facts.md`, README links the catalog, competitor columns unchanged. -- [ ] T023 [US3] Grep GP (`README.md`, `docs/**/*.md` except `docs/superpowers/`, component READMEs) and MOD `README.md` for each fact class in research R9 (module count, registry count, role names, release/status wording, optional defaults, Hell Let Loose, Go module counts, page names); write every hit with its correct value and source into `stale-facts.md` -- [ ] T024 [US3] Fix `README.md`: "30" → "29" templates in the Features list and the comparison table's Gameplane column, status wording, and replace the Features section with a short summary plus a link to `FEATURES.md`; leave competitor columns untouched -- [ ] T025 [P] [US3] Fix `docs/comparison-sources.md` Gameplane-row facts (the "30 ready-to-use templates" claim → 29) without changing any competitor entry or its dated source -- [ ] T026 [P] [US3] Apply the remaining `stale-facts.md` fixes in `docs/*.md` (one commit per logical group) -- [ ] T027 [P] [US3] Apply the `stale-facts.md` fixes in `audit-syslog-bridge/README.md`, `containers/README.md`, `mcp-server/README.md`, `telemetry-receiver/README.md` -- [ ] T028 [US3] MOD: if `README.md` module list or count is stale, branch `claude/024-readme-catalog` in MOD, fix it, open the MOD PR (signed commits); otherwise record "MOD README current" in `stale-facts.md` -- [ ] T029 [US3] Add `FEATURES.md` to the audited-file lists and header comments in `hack/check-links.sh` and `hack/check-doc-versions.sh`, and to the `docs` path filter in `.github/workflows/ci.yaml` (research R8; widens coverage, relaxes nothing) -- [ ] T030 [US3] Tier-up review (`sonnet`) of the combined US2 + US3 GP diff against `stale-facts.md` and research-notes.md; fixes in a new `haiku` wave +- [X] T023 [US3] Grep GP (`README.md`, `docs/**/*.md` except `docs/superpowers/`, component READMEs) and MOD `README.md` for each fact class in research R9 (module count, registry count, role names, release/status wording, optional defaults, Hell Let Loose, Go module counts, page names); write every hit with its correct value and source into `stale-facts.md` +- [X] T024 [US3] Fix `README.md`: "30" → "29" templates in the Features list and the comparison table's Gameplane column, status wording, and replace the Features section with a short summary plus a link to `FEATURES.md`; leave competitor columns untouched +- [X] T025 [P] [US3] Fix `docs/comparison-sources.md` Gameplane-row facts (the "30 ready-to-use templates" claim → 29) without changing any competitor entry or its dated source +- [X] T026 [P] [US3] Apply the remaining `stale-facts.md` fixes in `docs/*.md` (one commit per logical group) +- [X] T027 [P] [US3] Apply the `stale-facts.md` fixes in `audit-syslog-bridge/README.md`, `containers/README.md`, `mcp-server/README.md`, `telemetry-receiver/README.md` +- [X] T028 [US3] MOD: if `README.md` module list or count is stale, branch `claude/024-readme-catalog` in MOD, fix it, open the MOD PR (signed commits); otherwise record "MOD README current" in `stale-facts.md` +- [X] T029 [US3] Add `FEATURES.md` to the audited-file lists and header comments in `hack/check-links.sh` and `hack/check-doc-versions.sh`, and to the `docs` path filter in `.github/workflows/ci.yaml` (research R8; widens coverage, relaxes nothing) +- [X] T030 [US3] Tier-up review (`sonnet`) of the combined US2 + US3 GP diff against `stale-facts.md` and research-notes.md; fixes in a new `haiku` wave **Checkpoint**: GP docs refreshed on the branch. @@ -109,20 +109,20 @@ description: "Task list for spec 024: documentation refresh, feature catalog and ### Website PR (WEB, merges before the GP PR) -- [ ] T031 [US4] Create `src/content/docs/features.mdx` in WEB with the frontmatter from [contracts/features-catalog.md](./contracts/features-catalog.md) and the same area headings and tables as `FEATURES.md` (Capability, Maturity, Default identical; Docs cells rewritten to relative website docs links per the WEB CLAUDE.md); depends on T017 -- [ ] T032 [US4] Rewrite `src/content/docs/roadmap.mdx` in WEB to the same items and statuses as `docs/roadmap.md` (replace the "Beta status" headline with the current pre-v1 release wording); keep existing components (`DocsCallout`, `IconList`, `VERSION`); depends on T022 -- [ ] T033 [P] [US4] Grep WEB `src/content/docs/*.mdx` and `src/config.ts` for the R9 fact classes, add hits to `stale-facts.md`, and fix them in the docs pages (no landing page or component changes, OD-3) -- [ ] T034 [US4] Run `npm run build` and `npm run check` in WEB (allowed locally); fix build errors only -- [ ] T035 [US4] Open the WEB PR to `main` (signed commits), get WEB CI (`lint`, `check`, `build`) green, and wait for it to merge +- [X] T031 [US4] Create `src/content/docs/features.mdx` in WEB with the frontmatter from [contracts/features-catalog.md](./contracts/features-catalog.md) and the same area headings and tables as `FEATURES.md` (Capability, Maturity, Default identical; Docs cells rewritten to relative website docs links per the WEB CLAUDE.md); depends on T017 +- [X] T032 [US4] Rewrite `src/content/docs/roadmap.mdx` in WEB to the same items and statuses as `docs/roadmap.md` (replace the "Beta status" headline with the current pre-v1 release wording); keep existing components (`DocsCallout`, `IconList`, `VERSION`); depends on T022 +- [X] T033 [P] [US4] Grep WEB `src/content/docs/*.mdx` and `src/config.ts` for the R9 fact classes, add hits to `stale-facts.md`, and fix them in the docs pages (no landing page or component changes, OD-3) +- [X] T034 [US4] Run `npm run build` and `npm run check` in WEB (allowed locally); fix build errors only +- [X] T035 [US4] Open the WEB PR to `main` (signed commits), get WEB CI (`lint`, `check`, `build`) green, and wait for it to merge ### Sync check (GP) -- [ ] T036 [P] [US4] Write fixtures in `hack/testdata/check-features-sync/` per [contracts/features-sync-check.md](./contracts/features-sync-check.md): `pass/in-sync`, `pass/code-fence`, and `fail/{missing-row-website,missing-row-repo,maturity-mismatch,default-mismatch,area-mismatch,order-differs,invalid-label,stable-off,missing-file}` each with `.expected` output -- [ ] T037 [P] [US4] Write `hack/check-features-sync.sh` (Bash + awk, `set -euo pipefail`, env overrides `FEATURES_MD` / `FEATURES_MDX`, exit 0/1/2 and messages exactly as the contract), header comment citing spec 024 FR-015 -- [ ] T038 [US4] Write `hack/test-check-features-sync.sh` following the `hack/test-check-doc-versions.sh` harness (pass cases exit 0; fail cases exit non-zero with output equal to `.expected`); check both scripts with `bash -n` only -- [ ] T039 [US4] Add Makefile targets `check-features-sync` and `test-features-sync` (with `##` help text) and add `check-features-sync` to the `lint` target in `Makefile` -- [ ] T040 [US4] After T029 (both edit the `docs` path filter; extend T029's list, never replace it), in `.github/workflows/ci.yaml` `docs` job: add a step `git submodule update --init website` before the checks, then steps running `make check-features-sync` and `make test-features-sync`; add `FEATURES.md`, `website`, `hack/check-features-sync.sh`, `hack/test-check-features-sync.sh`, `hack/testdata/check-features-sync/**` to the `docs` path filter; if spec 023's selection script has landed, map the same paths to the docs job in `hack/ci_scope.py` -- [ ] T041 [US4] Tier-up review (`sonnet`) of T036–T040 against the contract; fixes in a new `haiku` wave +- [X] T036 [P] [US4] Write fixtures in `hack/testdata/check-features-sync/` per [contracts/features-sync-check.md](./contracts/features-sync-check.md): `pass/in-sync`, `pass/code-fence`, and `fail/{missing-row-website,missing-row-repo,maturity-mismatch,default-mismatch,area-mismatch,order-differs,invalid-label,stable-off,missing-file}` each with `.expected` output +- [X] T037 [P] [US4] Write `hack/check-features-sync.sh` (Bash + awk, `set -euo pipefail`, env overrides `FEATURES_MD` / `FEATURES_MDX`, exit 0/1/2 and messages exactly as the contract), header comment citing spec 024 FR-015 +- [X] T038 [US4] Write `hack/test-check-features-sync.sh` following the `hack/test-check-doc-versions.sh` harness (pass cases exit 0; fail cases exit non-zero with output equal to `.expected`); check both scripts with `bash -n` only +- [X] T039 [US4] Add Makefile targets `check-features-sync` and `test-features-sync` (with `##` help text) and add `check-features-sync` to the `lint` target in `Makefile` +- [X] T040 [US4] After T029 (both edit the `docs` path filter; extend T029's list, never replace it), in `.github/workflows/ci.yaml` `docs` job: add a step `git submodule update --init website` before the checks, then steps running `make check-features-sync` and `make test-features-sync`; add `FEATURES.md`, `website`, `hack/check-features-sync.sh`, `hack/test-check-features-sync.sh`, `hack/testdata/check-features-sync/**` to the `docs` path filter; if spec 023's selection script has landed, map the same paths to the docs job in `hack/ci_scope.py` +- [X] T041 [US4] Tier-up review (`sonnet`) of T036–T040 against the contract; fixes in a new `haiku` wave **Checkpoint**: WEB merged; sync check ready on the GP branch. @@ -130,7 +130,7 @@ description: "Task list for spec 024: documentation refresh, feature catalog and ## Phase 7: Polish & delivery -- [ ] T042 Bump the `website` submodule pointer to the merged WEB commit (and `modules` if T028 merged a MOD PR) on the GP branch; after the bump, copy any final `FEATURES.md` edits into `features.mdx` via a WEB follow-up PR before bumping again, so the two stay identical +- [X] T042 Bump the `website` submodule pointer to the merged WEB commit (and `modules` if T028 merged a MOD PR) on the GP branch; after the bump, copy any final `FEATURES.md` edits into `features.mdx` via a WEB follow-up PR before bumping again, so the two stay identical - [ ] T043 Open the GP PR (signed commits, labels `type: docs`, `area: specs`, plus `area: shared` for the hack/CI change), body without attribution block or session links; drive CI green, including `check-links`, `check-doc-versions`, `check-features-sync`, `test-features-sync` - [ ] T044 Run quickstart scenarios 1–8 against the merged state and record results in `specs/024-docs-feature-list-refresh/stale-facts.md` (final section) - [ ] T045 After the GP PR merges: tick all tasks, `git mv specs/024-docs-feature-list-refresh specs/done_024-docs-feature-list-refresh`, update in-repo references, in its own `docs:` commit and PR (Constitution IV, CLAUDE.md rule 16) diff --git a/website b/website index 87b64a912..1460be6e2 160000 --- a/website +++ b/website @@ -1 +1 @@ -Subproject commit 87b64a912f45ff5b1bea8b7b3c28d03112e5fd82 +Subproject commit 1460be6e2c690dfd5c4962e745e2fff6ce34fd3c