State Update Guard
An optional extension around Yumina's existing gameplay update formats. It validates the batch, repairs unambiguous syntax locally, and requests at most one correction when needed. It does not rewrite cards, lore, or frontend code, and cannot guarantee correct story understanding.
Install and configure
Open Extensions → Discover → State Update Guard → Install. It is not installed automatically. Allow up to 60 seconds for installation-cache propagation, then reopen the chat. Custom cards without extension slots can use the host play controls.
- Off restores ordinary card updates on the next reply. Only the off-status sentence, toggle, and history action remain. Closing the dialog does not turn it off.
- On shows correction-model controls using Memory's shared picker UI. Hosted users can independently select official or BYOK correction models without changing the story provider.
- View history opens a list of checks with simple results such as Fixed or No fix needed. Open an entry to see recorded value changes. View technical details keeps model names, counts, diagnostics, and original/corrected output out of the main view. Story and correction models are labeled separately; valid original replies show that the correction model was not called. Older records may lack before/after values; they are not fabricated.
- Clicking outside or pressing Escape dismisses the dialog. History remains accessible when Off.
- Uninstall through Extensions → Manage. Saved state and historical audits remain.
- The catalog, settings, and history follow your Yumina language: English, Simplified Chinese, Traditional Chinese, Japanese, or Spanish. Regional language choices use the same language matching as the rest of the app, and history dates use the selected language rather than the browser's default. Changing language keeps your current settings and history view; card-authored variable names and original model output are not translated.
The hosted default is Gemini 2.5 Flash Lite, matching Memory. Switch to free selects OpenRouter Free. Unavailable or disallowed selections fail explicitly instead of silently switching providers. The local/open-source edition uses private models only; imported official settings resolve to the private story model, and official/free-model controls are hidden.
Update contract
When enabled with active AI-writable variables, text output acknowledges one batch with a final receipt:
The infected falls. You catch your breath.
[zombieKills: add 1]
[energy: subtract 2]
<yumina-state version="1" status="updated" count="2" />A no-update turn uses an explicit flag, not missing output:
The stranger waits for your answer.
<yumina-state version="1" status="none" />The count is operations, not distinct variables. Equal-value writes are valid. Automatic rules and independent UI changes are not counted and may still change values after none.
Structured cards keep their JSON envelope rather than adding XML:
{"narrative":"The stranger waits for your answer.","status":"none","stateChanges":[]}An empty array/map alone is not acknowledgement. none with commands, updated with an empty batch, and missing/invalid stateChanges are rejected. Nonempty legacy array/map batches remain compatible. Cards without writable AI variables need no receipt. Without the extension, legacy parsing is unchanged.
Adapters preserve bracket directives, shorthand, nested JSON, fenced directives, UpdateVariable/JSONPatch, and structured arrays/maps. Entire batches must pass access, type, and unsafe-path checks. JSON container writes rejected by the engine are also rejected by the Guard. Leading speaker tags are preserved, not treated as variable commands.
Correction behavior
Complete values missing an outer bracket and a single valid receipt before a command-only tail can be repaired locally when unambiguous. Invalid counts, contradictory flags, narrative after receipts, unsafe paths, and lossy repairs are not silently accepted as zero updates.
An invalid batch receives at most one generated correction. Narration and speaker are frozen: correction replaces the proposed batch, not the story. Valid output never starts a correction call. Context, output, and time budgets are bounded within the remaining turn deadline; cancellation prevents late calls or commits.
Correction calls request the adapters' existing JSON-object output mode. If an endpoint explicitly rejects that mode (or a router finds no endpoint supporting the requested parameters) before any response or usage, Guard may retry once without the parameter on the same provider/model and within the same timeout. Authentication, rate limits, generic provider failures, and errors after output/usage do not trigger this compatibility fallback. Adapters without native JSON support continue through the existing validated text path. This does not change the card's story format or add a second generated/billed correction.
For completed, non-refused corrections, the known typo of one or two extra closing delimiters between a complete stateChanges array and the final review field can be normalized locally. No values or missing delimiters are invented; duplicate/overriding fields, truncation, unknown IDs, forbidden writes, and incompatible operations remain rejected. The exact provider output stays in the technical audit, and a successful local normalization is marked as repaired. The no-update flag and full writable-variable review remain mandatory for no-op corrections.
The correction prompt receives AI-readable state, exact writable IDs, variable behavior rules, and the frozen draft. A correction choosing status: "none" must provide one nonempty review reason per writable variable. Missing, duplicate, or unknown IDs fail validation. Review checks coverage, not semantic truth.
Send, regenerate, and continue await validation before committing state. Memory hooks remain asynchronous after successful turns. Studio's authoring/tool executor is unchanged; gameplay playtests use the gameplay Guard.
Billing
- Successful committed paid official corrections deduct mushies, including no-update corrections and corrections for BYOK stories.
- Checks/local repairs without model calls add no Guard model charge.
- BYOK corrections deduct no Yumina mushies; external provider charges may apply.
- Explicitly free official correction models remain intentionally free, subject to availability/access rules.
- State, audit, correction debit, and eligible story debit commit atomically. Failed, cancelled, stale, duplicate, or unaffordable attempts create no correction debit.
- Correction usage and pricing are separate from the story model. Automated tests do not establish a live production deduction.
Deployment and diagnostics
prepare-deployment.mjs calls prepare-state-validation.mjs, applying both add-state-validation.sql and 2026-09-09-state-guard-settings.sql idempotently with bounded database timeouts. They add message audit and per-session enable/model columns. Hosted startup and embedded upgrades also prepare these fields. They do not install the extension or migrate card values. Branches inherit settings.
Owner-scoped audits contain outcome, trigger, model/tier, operation/correction counts, diagnostics, and bounded original/corrected output. Recorded correction output may have been rejected; it is not proof of a committed change. Public playthroughs exclude audit data and pre-AI baselines. Editing replies clears stale audits; selecting a swipe restores its audit.
Analytics receive aggregate codes, counts, model/tier, and timings, not story text, variable values, or raw provider payloads. Provider errors are categorized to avoid storing secrets. Reloading abandoned attempts never launches hidden corrections.
STATE_UPDATE_GUARD_DISABLED=true is an emergency fail-closed switch for protected turns, not a bypass accepting unchecked output. Absent/uninstalled/off chats remain unaffected.
Verification
See main readiness for current evidence and gates. Prefer deterministic fixtures for malformed output; a real model may not repeat a syntax error on demand.
- Engine:
guarded-response.test.ts,state-receipt.test.ts. - Server: Guard
correction-format.test.ts,validate.test.ts,model.test.ts, compatibility, billing, migration, turn-output validation, and settings route tests. Format tests cover the production extra-delimiter failure, JSON-mode negotiation, cancellations, unchanged frozen narrative, and rejection of unsafe/ambiguous recovery. - App:
state-guard-*.test.ts*(include.tsxexplicitly), discovery, fullscreen controls, and Memory picker layout coverage. - Localization:
state-guard-i18n.test.tschecks all five catalog translations; the readable-output and navigation tests cover regional language matching, localized dates, and switching language while the panel is open without saving settings or resetting navigation. - PostgreSQL: opt into
turn-output-validation.pg.test.tswithSTATE_GUARD_DB_TEST=1and the designated testing database after migrations. Never run fixtures against production.
Manual acceptance compares an existing card without the extension, Guard Off, and Guard On. Try movement, waiting, and dialogue; compare narration, saved state, and history. Repeat send, regenerate, continue, swipe, edit, and branch. Verify correction settings do not alter story settings. Test paid official, BYOK, and free choices separately with authorized accounts.
Failed replies are not retroactively applied and stale values are not guessed. A formatted but semantically wrong update remains possible; there is no always-on second model judging every turn.
