Game Engine Issues May 8, 2026 16 min read

OpenXR Adjudication Preview Uses Stale Validation Bundle Cache but Submit Rejects Close on Quest - Fix

Fix 2026 Quest OpenXR lanes where the preview modal validates against a cached registry bundle while the submit path loads the fresh contract, causing green preview and red submission after publishes.

By GamineAI Team

OpenXR Adjudication Preview Uses Stale Validation Bundle Cache but Submit Rejects Close on Quest - Fix

The preview panel shows all checks green. You click submit close and the API returns context_validation_failed or schema_generation_mismatch with keys that preview never mentioned.

Coworkers assume reviewer error. In practice, preview and submit often hit different bundle resolution paths: one reads a CDN-cached or edge-cached validation artifact; the other calls origin or a different hostname that already picked up the new publish.

In 2026, dense Quest adjudication volume plus frequent registry refreshes makes this failure mode common whenever cache keys omit bundle_generation or machine_schema_hash.

Problem

Typical symptoms:

  • preview and submit disagree within seconds on the same browser session
  • hard refresh fixes preview but not submit (or the reverse)
  • failures spike right after a registry publish while infra reports “healthy”
  • mobile reviewer app matches preview; desktop submit fails (different API base URL)
  • validation_schema_id in network tab differs between preview XHR and submit XHR

If teams cannot prove same bundle, same generation, they lose trust in the whole close pipeline.

Direct answer

Force preview and submit through one resolver: address validation bundles with immutable URLs keyed by bundle_hash or generation ID, set short TTL or no-store on authoritative contract responses during publish windows, and assert identical validation_schema_id in both requests before allowing submit.

Root cause summary

  1. Split CDN rules — preview assets cached aggressively; submit API uncached.
  2. Host skewpreview.gamine vs api.gamine with different cache policies.
  3. Queryless URLs/contracts/latest.json always 200 from edge cache after publish.
  4. Session stickiness — preview pins an old service worker or HTTP cache partition.
  5. Missing assert — UI does not block submit when preview hash ≠ server-acknowledged hash.

Fastest safe fix path

  1. Capture both requests: compare validation_bundle_url and response ETag or body hash.
  2. If they differ, disable submit until both paths resolve the same bundle_generation.
  3. Purge or version-bump contract URLs for the active window.
  4. Patch client to append ?g= generation to contract fetches shared by preview and submit.
  5. Add CI smoke: single fixture must pass preview simulator and submit simulator with identical hashes.

Step-by-step fix

Step 1: Unify resolver entry points

Expose one internal function:

  • resolveValidationBundle(adjudicationRow) -> { url, hash, generation }

Preview modal and submit handler must call it—or call the same backend endpoint that returns the canonical tuple.

Verification checkpoint: both code paths log identical bundle_generation for the same dispute ID.

Step 2: Replace “latest” URLs with immutable references

Avoid:

  • /reason-code/contracts/latest.json

Prefer:

  • /reason-code/contracts/2026-05-08T14:32Z-a3f9c2/machine.json

Or latest symlink only behind Cache-Control: no-store at edge during migration.

Verification checkpoint: publishing a new bundle changes URL path; old URLs remain valid for pinned rows.

Step 3: Align CDN and API cache headers

For contract JSON during adjudication windows:

  • Cache-Control: private, max-age=0, must-revalidate or
  • max-age=60 with strong ETag and purge hooks on publish

Preview static hosts should not serve machine contracts longer than API without matching keys.

Verification checkpoint: curl from edge POP and origin return matching ETag after publish completes.

Step 4: Client-side cache busting discipline

If you must keep stable paths temporarily:

  • append bundle_hash query param
  • clear service worker cache on registry_publish event bus message

Verification checkpoint: stale preview cannot persist across publish events without explicit user “reload contracts” action.

Step 5: Submit preflight assert

Before POST:

  • call lightweight /validation/contract-pointer?dispute_id= returning expected hash
  • compare to preview’s last successful validation hash
  • block with actionable error if mismatch

Verification checkpoint: zero silent submits when hashes diverge.

Step 6: Operational publish checklist

On every registry publish:

  • purge contract CDN paths or rotate immutable URLs
  • broadcast contract_generation bump to connected reviewer clients
  • run replay of N frozen disputes across preview + submit simulators

Verification checkpoint: publish runbook logs purge ticket IDs or URL rotation proof.

Verification checklist

  • [ ] Preview and submit network calls reference the same immutable bundle URL or equivalent hash-qualified URL.
  • [ ] ETag / body hash matches between preview fetch and submit preflight.
  • [ ] Publish drill reproduces no green-preview/red-submit on regression fixtures.
  • [ ] Mobile and desktop clients use the same API base and resolver version.
  • [ ] Incident runbook documents purge or rotation steps with owner and SLA.

Alternative fixes and prevention

  • Serve contracts only from API — preview becomes thin client; no static host drift.
  • Signed bundle manifests — server signs (dispute_id, bundle_hash); client displays signature in preview footer.
  • Blue/green contract hosts — flip DNS only after both paths verified.
  • Weekly cache chaos rehearsal — inject stale edge responses in staging to test asserts.

Related problems and links

Official references: Unity OpenXR documentation and Khronos OpenXR specification.

FAQ

Is browser hard refresh enough for reviewers

Only if preview and submit share the same resolver and cache keys. Hard refresh does not fix CDN skew on API-only submit paths.

Should we disable edge caching entirely

Not always—use immutable URLs or tight TTL during adjudication windows instead of global cache-off.

What if mobile uses an older app build

Treat as separate incident: force minimum app version that includes unified resolver and preflight assert.

Escalation criteria

Escalate when:

  • mismatch rate exceeds playbook threshold after any registry publish
  • multiple regions show divergent preview versus submit hashes
  • signers receive packets that cite validation IDs clients never saw

Bookmark this fix in your release engineering notes next to CDN purge procedures and contract URL conventions.