Commit Graph
5 Commits
Author SHA1 Message Date
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
Quintine 8bea4c1a0c Record administrator failed attempts without storing credentials
Rejected administrator authentication and authorization attempts are now
recorded durably in a new additive admin_attempts table with a fixed reason
(invalid_key_format, unknown_or_disabled_key, missing_session,
unknown_or_expired_session, role_denied), the attempt kind, the attempted role
and an already-known account id. No key, key hash, session token, session hash,
request body, origin, address or user agent is stored, and the
unknown/disabled distinction is deliberately collapsed so the lookup cannot
become an account-enumeration oracle.

GET /api/admin/operations?attempts=1 returns seven-day per-reason counters plus
the twenty most recent attempts, newest first, to a live owner only, with
no-store and the same query guard as the other reads. The operations page adds
"List failed attempts" with the counters and the bounded sample, truncating each
account id to eight characters. Recording is best-effort inside the rejection
path, so a rejected request keeps its own answer even when the table is
unavailable, and the history never locks out, disables or revokes anything by
itself.

Evidence: tests/admin-attempts.test.ts asserts every recorded reason, the
kind/role/account fields, that successful sign-ins and privileged reads record
nothing, that no credential text appears anywhere in the page, the reporting
window, the twenty-row bound and the owner-only recheck after demotion, and that
a dropped attempts table leaves the original rejection answer unchanged. The
anonymous HTTP boundary denies ?attempts=1, and the panel was verified in the
real browser against a genuine recorded rejection. The shared AdminError moved
to db/admin-error.ts so the recorder and the access module no longer form a
value-import cycle. Offline suite 7,801/7,801 with typecheck and lint clean. No
game, seat, saved state or deployment changes.
2026-10-07 19:11:45 +11:00
Quintine 07c20ae806 Diagnose stalled administrator decisions from the operations page
Add an owner-only, read-only sample of at most ten live rooms whose public
pending interaction — decision, response, Truthtrance or phase opening — has
been idle longest, with the room code, idle duration, interaction kind, turn
and phase. GET /api/admin/operations?stalled=1 repeats live owner authority,
answers no-store, rejects any other query combination and repairs nothing;
paused, closed, removed and archived rooms are excluded and no private game
content is selected. The aggregate sample adds stalledRooms and
oldestStalledChange for the same predicate.

Owner-only unit coverage asserts the counters, the oldest stalled write, the
bounded oldest-first order, every exclusion boundary, idle arithmetic, the
absence of private content and the owner recheck after demotion. The anonymous
HTTP boundary denies the new read, and the full HTTP integration suite passes
55/55 against a local build of this revision. The panel was verified in the
real browser against six hours of genuine idle rooms, which caught and fixed a
missing threshold field in the response.

No game, seat, saved state, deployment, public start or played-save conversion
changes.
2026-10-07 17:51:08 +11:00
Quintine ddc303542a Add owner-only manual database integrity check 2026-09-30 13:04:00 +10:00
Quintine c6784468a1 Add owner-only operational counts dashboard 2026-09-28 20:28:40 +10:00