Files
Dune/docs/ADMIN_PANEL.md
Quintine e493bcf58e Report database size and make four genuine suites reproducible
Operations now report the SQLite database size and page count, read through
PRAGMA page_count/page_size in the same authority-checked batch as the
lifecycle counters with non-numeric values clamped to zero; the page renders
both and states that free disk space is still unknown.

replaceMoritaniAssassinationTraitors draws the first distinct physical Traitor
Card from the reserve instead of the reserve's first entry, because the face-up
set-aside card can still sit in that list: re-drawing it produced a receipt
whose replacement equalled the revealed card, which the assassination integrity
check rejects mid-Mentat. With no distinct card left the step is blocked with an
explicit reason instead of committing an invalid receipt.

Four tests were made reproducible after measuring their real failure rates,
because a suite that fails for random reasons cannot act as a checkpoint gate:
the Discovery typed Spice Blow test now removes other cards for the same
territory so one blow deposits the printed amount (0/20 after, ~20% before); the
Discovery E2 skills deal is pinned with the new shared
tests/deterministic-random.ts helper so a Lasgun-shield explosion cannot
pre-empt its skill consumer (0/12 after, ~20% before); the Ecaz Occupy Ix/CHOAM
policy check accepts any legal continuation and still asserts the phase it
reaches (0/30 after, ~10% before); and the mixed-E3 Moritani Cunning test keeps
the random storm off red_chasm's single sector (0/25 after, ~20% before).

Offline suite 7,801/7,801 with typecheck and lint clean. No game, seat, saved
state or deployment changes.
2026-10-07 19:55:43 +11:00

260 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Administration panel requirements
Added to the goal on 22 September 2026 at the user's request. This is required
product scope. The first access and directory prototype is available at `/admin`;
the remaining room operations are unfinished. Reuse the existing room, session,
recovery and persistence systems where suitable.
## Required capabilities
| Area | Required behavior | Stage |
| --- | --- | --- |
| Administrator access | Personal access keys, server-enforced owner/operator/viewer roles, persistent eight-hour sessions and individual/all-session sign-out. [Owner-only account management](ADMIN_ACCOUNTS.md) adds a paginated directory, browser-created one-time keys, role changes, disabling and fresh-key rotation/re-enable with session revocation, live authority and exact retries. A room host is not a site administrator. [Failed-attempt history](ADMIN_ATTEMPTS.md) records rejected sign-ins and role denials without storing any credential; further operational permissions remain. | Prototyped, partial |
| Room directory | Search room codes/public player names, filter status/rules/availability, sort and paginate; show host, roster, modules, game change time, pause/join flags and bounded decision ownership. Detailed setup/shared-window ownership remains. | Prototyped |
| Create and configure | [Create a lobby](ADMIN_ROOM_CREATION.md) with an explicitly owned new host seat, Basic/Advanced preview and initial AI configuration; invite humans and use ordinary lobby controls/voluntary host-seat handover. [Neutral lobby configuration](ADMIN_LOBBY_CONFIGURATION.md) adds rules/modules, AI seats and host assignment to an existing human without private access. Reserved human seats remain. | Prototyped, partial |
| Lifecycle and removal | [Pause/resume and joining locks](ADMIN_ROOM_CONTROLS.md), [administrative close/reopen](ADMIN_ROOM_CLOSURE.md) and [recoverable removal/restoration](ADMIN_ROOM_REMOVAL.md) have controls, durable audit, exact retries and player/AI enforcement. [Archive/unarchive](ADMIN_ROOM_ARCHIVE.md) has separate directory filters and exact retries. Permanent deletion and bulk actions remain missing. | Prototyped, partial |
| Participant support | [Participant AI](ADMIN_PARTICIPANT_AI.md) enables an existing difficulty on an eligible human seat in a paused game, retaining access/takeback and private custody. [Discussion controls](ADMIN_DISCUSSION.md) mute/unmute new sends while retaining history and gameplay. Participant removal, access revocation, assisted recovery and further replacement remain. Existing lobby controls can reassign the host. | Prototyped, partial |
| Saved-game operations | [Owner-only room backups](ADMIN_BACKUPS.md) capture/list/download a size-limited, version-fenced room snapshot without changing play or access; import validation, safe checkpoint restore and interrupted-work diagnosis/resume remain missing. | Prototyped, partial |
| Operations and audit | [Action history](ADMIN_ACTION_HISTORY.md) searches existing durable operations with safe settings and role-limited reasons. The owner-only [operations page](../app/admin/operations/page.tsx) samples build revision, room/seat flags, JSON syntax failures and backup/export counts without exposing game contents. [Stalled-decision diagnosis](ADMIN_STALLED_DECISIONS.md) adds a bounded, read-only sample of live rooms whose public pending interaction has been idle longest, with aggregate counters. An explicit owner action runs read-only SQLite quick/foreign-key checks and returns only pass/fail, never corrupt row details. [Failed-attempt history](ADMIN_ATTEMPTS.md) records rejected authentication and authorization attempts with a fixed reason, the attempted role and an already-known account id — never a key, token, body or address — and exposes seven-day counters plus a bounded recent sample to the owner. The aggregate sample also reports the SQLite database size and page count; free disk space and maintenance controls remain. | Prototyped, partial |
Track each area through Missing, Prototyped, Integrated, Verified and Polished.
As implementation begins, link its controls, server actions, persistence,
documentation and evidence. A working directory alone is not a full admin panel.
## Access and directory prototype
Use **Administration** in the home-page footer or visit `/admin`. Every directory
request checks a separate administrator session; seat cookies and forwarded
identity headers cannot grant access. The standalone NAS runtime has no trusted
Sites identity dispatcher, so this panel uses separately provisioned personal
keys. Each key contains a random 256-bit secret. Only its SHA-256 hash is stored;
sign-in issues a separate random, hashed session with an eight-hour absolute
expiry. HTTPS uses Secure, HttpOnly, SameSite=Strict cookies scoped to the admin
API. Mutations require the exact configured origin and bounded JSON requests.
All three roles may read the current directory and sign out their own sessions.
Owners/operators may pause/resume rooms and lock/unlock new joins through
[Room controls](ADMIN_ROOM_CONTROLS.md). The role is checked from the live
account, never accepted from a client.
Owners/operators can also [create a configured lobby](ADMIN_ROOM_CREATION.md),
with explicit new-host ownership and saved exact retries.
[Configure lobby](ADMIN_LOBBY_CONFIGURATION.md) changes supported rules, AI seats
and the host without granting private seat access.
[Remove or restore](ADMIN_ROOM_REMOVAL.md) blocks ordinary room access while
preserving the game, seats and access revocations. Use the Directory filter to
find removed rooms; restoration retains prior pause and joining settings.
[Close or reopen](ADMIN_ROOM_CLOSURE.md) ends further play without awarding a
winner and preserves readable private tables and discussion. Reopening retains
the saved game, pause/join settings and current credentials.
[Archive or unarchive](ADMIN_ROOM_ARCHIVE.md) organizes closed rooms with separate
directory filters and exact retries. Unarchiving leaves play closed; restoring
a removed archive preserves both flags.
Disabling an account invalidates its sessions; later re-enabling it cannot restore
them. The database refuses to disable, demote or delete the final enabled owner.
No default key, public account creation or host-to-administrator promotion exists.
[Manage accounts](ADMIN_ACCOUNTS.md) lets a current owner provision a
personal key, change another administrator’s role, disable access or
rotate another account’s key. Rotation also re-enables a disabled account
with a fresh key while invalidating every earlier key and session. The
exact request and generated key remain tab-scoped through uncertain
responses; the server never returns a key. The owner reviews the target,
current state, reason and revocation impact before submitting. No game
or seat is modified.
[Participant AI](ADMIN_PARTICIPANT_AI.md) lets owners/operators support an absent
human in a paused, started game. The player retains access and takeback controls;
the administrator receives no private seat. Resume remains a separate action.
[Discussion controls](ADMIN_DISCUSSION.md) restrict new messages from a human seat,
including after recovery/handover, without exposing private history.
[Private room backups](ADMIN_BACKUPS.md) let an owner capture the current saved
state and related room-owned records without pausing or editing the room. The
metadata directory omits private contents; downloaded JSON contains secret
game state and hashed seat/recovery credentials. Import/restore are not available.
The owner-only [operations page](../app/admin/operations/page.tsx) provides
manual read-only samples of room lifecycle counts, active seat credentials,
invalid JSON saves, latest room write, backup payload usage, export receipts,
and the SQLite database size and page count.
The database query repeats current owner authority. **Run integrity check** is
a separate, explicit read-only D1 quick check and foreign-key check. It repeats
owner authority inside the query batch and returns only pass/fail and sample
time, never a table name, row ID, private save or SQLite diagnostic text.
The check can take time; a failed check calls for a protected snapshot and
private diagnosis, not automatic repair. It does not prove disk capacity,
backup recoverability or valid game rules.
[Failed-attempt history](ADMIN_ATTEMPTS.md) adds **List failed attempts**: seven-day
per-reason counters and the twenty most recent rejected administrator requests —
malformed, unknown or disabled keys, missing, unknown, expired or revoked
sessions, and role-denied actions. Recording is best-effort inside the rejection
path and stores no credential; the read repeats live owner authority and
answers `no-store`. The history is diagnostic: it never locks out, disables or
revokes anything by itself.
[Stalled-decision diagnosis](ADMIN_STALLED_DECISIONS.md) adds **List stalled
decisions**: a bounded, read-only sample of at most ten live rooms whose public
pending interaction — a decision, response, Truthtrance or phase opening — has
been idle longest, with the room code, idle duration, interaction kind, turn
and phase. Paused, closed, removed and archived rooms are excluded, no private
game content is selected, and the sample repairs nothing; the aggregate sample
also reports the stalled count and the oldest stalled write. A stall is inferred
from the room write time, so a slow human turn counts too.
The directory uses an explicit field allowlist, independent of any player view.
It never returns hands, Traitors, predictions, plans, private messages, recovery
records or raw saved state. Searches inspect only codes and public player names.
Each page is limited to 25 rooms and repeats authorization in its read transaction
so concurrent revocation cannot expose a later directory snapshot. Malformed
saves are marked unreadable without leaking their contents or breaking searches.
Last game change refers to the room's saved-state timestamp, not private messages.
Shared windows and setup currently use broad pending labels; these labels do not
assert that a human is stalled or identify hidden-card-dependent eligibility.
Implementation: [panel](../app/admin/page.tsx), [access](../db/admin-access.ts),
[directory](../db/admin-directory.ts), [HTTP boundary](../lib/admin-http.ts),
[additive migration](../drizzle/0008_admin_access.sql), and
[operator CLI](../tools/admin-access.mjs). The migration's explicit SQLite triggers
enforce final-owner protection and account revocation/provisioning audit; retain
them when generating future schema changes. [Action history](ADMIN_ACTION_HISTORY.md)
unifies the existing successful-operation records; broader audit coverage remains.
### Initial owner and access recovery
The NAS container provisions an **Initial owner** automatically after migrations
only if `admin_accounts` has no rows. Its newly generated personal access key is
printed once in that container's startup log (`docker logs <container-name>`).
Retrieve it privately, save it in a password manager and sign in at `/admin`;
it is not a chosen password or an environment variable. Anyone able to read
that log can use the key. Restrict log access and retention; do not paste the
log into support reports. Restarts,
container replacements and existing accounts (even disabled ones) never generate
or print another key. Save the key before replacing the first container, since
its original log may disappear. Losing it requires manual owner recovery below.
The automatic bootstrap applies only to the bundled NAS container startup, not
`npm run dev` or an external D1 deployment.
For manual provisioning or access recovery from the development checkout,
apply additive migrations, then create a personal owner key in a new private
directory outside the checkout:
```sh
npm run db:local
node tools/admin-access.mjs --name 'Owner' --role owner --out /private/dune-owner
npx wrangler d1 execute DB --local --config tools/wrangler.local.json --persist-to .wrangler/state --file /private/dune-owner/provision.sql
```
The parent directory must already exist. Output files are mode 0600 in a new
mode-0700 directory. `access-key.txt` contains the sign-in key; `provision.sql`
contains only its hash. Keep the key in a private password manager or protected
file, outside Git, logs and chat. The command never connects to a database or
prints the key. Apply the SQL only to the intended instance. Do not upload a
development key to production; provision separate accounts there.
For the container, run the bundled CLI inside the existing `dune` container
(using its actual container ID/name), with output such as `/data/admin-owner`.
Apply the generated SQL using `node node_modules/wrangler/bin/wrangler.js d1
execute DB --local --config tools/wrangler.local.json --persist-to /data --file
/data/admin-owner/provision.sql`. A protected volume snapshot should precede
operational changes. Retrieve the key through an authorized private channel and
retain the account ID shown by the CLI. No application restart is needed.
To revoke an account, generate a new private output directory with
`node tools/admin-access.mjs --revoke ACCOUNT_UUID --out /private/dune-revoke`
and apply its `revoke.sql` using the same instance-specific command. To replace
a lost/compromised owner key, provision a replacement owner first, verify access,
then revoke the previous account. Existing games and seats are unchanged.
**Sign out everywhere** revokes browser sessions but keeps the personal key valid.
### Focused verification
```sh
npm test -- admin-access admin-directory admin-http
npm run test:integration -- admin-boundary
node tools/verify-admin.mjs --url http://localhost:3000 --key-file /private/dune-qa/access-key.txt --qa-account ACCOUNT_UUID --out /private/dune-admin-report
```
The HTTP verifier requires a **dedicated QA administrator account** and explicitly
checks its ID before proceeding: it creates a named QA room and signs out all
sessions of that QA account. Never supply a human operator's normal key. It checks
authentication, cookie properties, ordinary-host denial, directory privacy,
session revocation and unchanged game/seat continuation. Operator/owner QA keys
also create dedicated rooms for joining locks, pause/takeback/resume,
administrator creation, invitations, voluntary host handover and neutral lobby
configuration with existing-human host assignment and a real game start. They
also close/reopen, archive/unarchive and remove/restore separate QA games, preserving their paused state, discussion,
saved seats and revoked credentials. Private reports retain
room IDs and safe outcomes, never keys/cookies. The container verifier provisions
its own disposable QA account and checks admin-session continuity across restart
and replacement, followed by the HTTP flow and HTTPS-proxy cookie behavior.
Participant checks use another new QA room for paused AI enable, private-seat
continuity, takeback and durable retries after resuming. Discussion checks use a
new QA room for mute/unmute, own-only status, incoming messages and saved retries.
Local browser checks cover sign-in, directory filters, refresh restoration and
mobile layout. Final source-bound checks and separately recorded deployed
acceptance accompany each checkpoint; a local pass is not production evidence.
## Behavior and safeguards
Every privileged endpoint must authenticate and authorize its caller. Support
least-privilege roles, secure initial provisioning, safe credential/session
management, and accessible desktop/mobile controls. Do not ship default shared
credentials or expose administrator capabilities through ordinary room cookies.
Default views must preserve hands, Traitors, predictions, sealed plans, private
discussion and recovery credentials. Any necessary private support access must
be separately authorized and audited; it must not reveal information to other
players or feed an administrator's playing seat or AI strategy. Redact secrets
and private card contents from routine logs and reports. Backup access needs
equivalent protection, since backups can contain private state.
Pausing must stop human mutations and automatic/AI continuation consistently
without losing pending decisions. Resuming restores the same turn and pacing.
Seat removal or reassignment must explain the effect on an active game and
preserve a legal continuation; do not simply delete a faction's physical pieces.
Existing consent/recovery boundaries remain applicable to seat access.
Use authoritative rules and the existing concurrency model. Reject stale or
duplicate destructive actions safely. Imports and restoration must validate
schema, custody, private-information boundaries and session ownership; capture
a recovery checkpoint before replacing current progress. Never restore old
credentials in a way that silently reauthorizes a revoked participant.
Room removal should be reversible by default. Permanent deletion and bulk
operations must identify the selected rooms and dependent records, explain
irreversibility, require deliberate confirmation and retain appropriate audit
evidence without keeping purged secrets. Protect unrelated games. The existing
saved-game preservation rules still govern development and maintenance; they
do not preclude intentional room deletion through the authorized product workflow.
Record who acted, when, the affected room/seat, operation, reason, outcome and
safe before/after metadata. Maintenance controls must warn about interruptions
to active games and preserve restoration. Do not recreate recurring restarts or
cleanup schedules. Do not add an arbitrary game-state editor that bypasses rules,
silently changes outcomes or makes unfinished modes appear complete.
## Development order and acceptance
Prototype permissions and the room directory first, then room creation and
reversible lifecycle operations, participant support, backup/restore and
operational tools. Include functional server actions and saved continuation in
each slice; continue independent game features in parallel where useful.
Administration is part of the non-AI completion gate in the
[AI development plan](AI_DEVELOPMENT_PLAN.md).
Acceptance requires working controls and documented setup/recovery procedures,
with authenticated HTTP and persistence tests for authorization, privacy,
concurrent actions, exact retries, related-record handling and safe restoration.
Verify pause/resume with both humans and AI, archive/delete/restore with saved
rooms, and denied admin actions by ordinary players. Exercise desktop/mobile
flows and server restart recovery. Use dedicated test rooms and preserve all
unrelated saved games; complete independent security/privacy/persistence review.
Verify the panel's relevant deployed flows at
**https://dune.procrastination.games**, including denied access by ordinary
players, using the [production verification workflow](VERIFICATION_WORKFLOW.md#verify-the-deployed-application).
The full goal remains unfinished until this scope and the other completion
criteria are satisfied.