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_idin 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
- Split CDN rules — preview assets cached aggressively; submit API uncached.
- Host skew —
preview.gaminevsapi.gaminewith different cache policies. - Queryless URLs —
/contracts/latest.jsonalways 200 from edge cache after publish. - Session stickiness — preview pins an old service worker or HTTP cache partition.
- Missing assert — UI does not block submit when preview hash ≠ server-acknowledged hash.
Fastest safe fix path
- Capture both requests: compare
validation_bundle_urland responseETagor body hash. - If they differ, disable submit until both paths resolve the same
bundle_generation. - Purge or version-bump contract URLs for the active window.
- Patch client to append
?g=generation to contract fetches shared by preview and submit. - 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-revalidateormax-age=60with strongETagand 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_hashquery param - clear service worker cache on
registry_publishevent 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_generationbump 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
- OpenXR context validation blocks closes after doc-only registry publish on Quest - fix
- OpenXR policy recompute uses latest registry while adjudication rows stay versioned - Quest fix
- OpenXR critical-field diff marked as non-critical and routed to signer fast path on Quest - fix
- OpenXR guard manifest missing from signer packet after route classification on Quest - fix
- OpenXR reason-code compatibility map missing during migration on Quest - how to fix
- Resource: 15 Free Quest OpenXR context validation and registry schema contract resources (2026 Q1)
- Resource: 15 Free Quest OpenXR validation bundle CDN and cache parity resources (2026 Q2)
- Guide: Unity 6.6 LTS OpenXR Reason-Code Drift Detection and Adjudication Quality Calibration Loops Preflight
- Guide: Unity 6.6 LTS OpenXR Adjudication Validation-Bundle Resolver Parity and Edge-Cache Discipline Preflight
- Guide: Unity 6.6 LTS OpenXR Validation-Bundle Read-Consistency Synthetic Probes and Edge-Incident Rehearsal Preflight
- Course: Lesson 146 - Reason-Code Drift Detection and Adjudication Quality Calibration Loops (2026)
- Course: Lesson 147 - Reason-Code Version Rollout Governance and Safe Migration Windows (2026)
- Blog: Quest OpenXR reason-code version rollout governance and safe migration windows 2026 small teams
- Blog: Quest OpenXR adjudication validation bundle resolver parity and edge cache discipline 2026 small teams
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.