Skip to content

fix: ten issues from the second 2026-10-08 read-only audit - #173

Merged
ackness merged 8 commits into
mainfrom
fix/readonly-audit-2026-10-08-round2
Oct 8, 2026
Merged

ackness merged 8 commits into
mainfrom
fix/readonly-audit-2026-10-08-round2

Conversation

@ackness

@ackness ackness commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Summary / 摘要

A second read-only audit of the snapshot merged in #172 listed 19 findings. Each was checked against the source; this PR fixes the ten that are real and small. The rest are left alone: they are low-impact, theoretical for the bundled plugins, or need a product decision.

  • A suspension can be resumed again after the server stopped in the middle of resuming it. A resume claims the suspension by writing claimed:<iso>; when the process ended before it released or resolved the claim, every later resume answered 409 and the player could only abandon it. The suspension sweep now releases such claims: every claim at server start on SQLite and memory (one process owns the store), claims older than one hour on PostgreSQL (another process may be resuming). The player may then resume again, the same as after a resume that failed; an external call the ended run had made is not undone. DataStore gains releaseStaleSuspensionClaims.
  • A detached runtime's proposal is checked again after a PreStateCommit hook. The declared-effects guard ran only before the hook, so a hook could move a write to a namespace the runtime did not declare.
  • A media download drops a redirect's body unread. ctx.media.ingestUrl read the whole body of each 3xx response, outside maxBytes. On the old code the new test runs the worker out of heap.
  • The model database refresh keeps its 30-second limit until the body is read. The timer was cleared when the headers arrived.
  • Operator Access reports a refused token write instead of showing success and reloading.
  • Plugins. The settled tabletop check text no longer carries the submission ID and turn ID; the dice-check roller resolves a translated attribute name with resolveI18nText; pregame reads the world through ctx.world instead of a store method only bundled plugins receive.
  • World text. Emberback's Chinese opening had an untranslated word with a wrong gloss; Chihiro Onodera's English card mixed "he" and "she".

Type of change / 变更类型

  • Bug fix / Bug 修复 (fix)

Verification / 验证方式

  • pnpm check
  • pnpm test
  • pnpm test:pg (store contract 1165 passed, none skipped; server integration 28 passed)
  • pnpm e2e:smoke / pnpm e2e — not run; the only UI change is one error line in the Operator Access pane, covered by its unit test
  • pnpm validate:world worlds/emberback worlds/haruka-academy
  • pnpm e2e:verify — not run

The new tests for the proposal guard and the redirect body were run against the unfixed source: the first fails, the second crashes the worker with an out-of-memory error.

Not verified / 未验证:

  • No real process kill was reproduced for the suspension claim; the release is covered by the store contract suite and the sweep's unit tests.
  • The one-hour claim age on PostgreSQL is a chosen value, not measured against the longest real resume. A PostgreSQL deployment may wait up to about two hours (age plus the hourly sweep gate).
  • A suspension older than COVEL_SUSPENSION_TTL_MS with a dead claim is released and then expired in the same sweep. One existing test asserted that a very old claim is never swept; it now uses a fresh claim.
  • The model database timeout and the tabletop check text have no test of their own.
  • tabletop-rules keeps its own attribute-name fallback: it ships self-contained and the plugin i18n gate rejects a hand-written language fallback.

Related issue / context / 关联

Follows #172. Audit report: devs/docs/audits/2026-10-08-readonly-audit-2/ (gitignored).

Docs sync / 文档同步

  • docs/reference/ updated for changed contracts, APIs, tools, or protocol — docs/reference/api.md (Suspend / Resume)
  • Guides and both READMEs updated; pages with an .en.md sibling changed together — docs/guide/env-registry.md
  • docs/CHANGELOG.md has an entry under [Unreleased] for user-visible changes
  • AGENTS.md updated if packages, root scripts, or conventions changed — n/a

@ackness
ackness merged commit 4f8e5a8 into main Oct 8, 2026
6 checks passed
@ackness
ackness deleted the fix/readonly-audit-2026-10-08-round2 branch October 8, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant