# Fan Passport Content Validation Rules This document defines the validation contract for the World Cup 2026 content seeds. It complements the TypeScript/Zod models in `src/schema.ts` and the challenge catalog JSON Schema in `schemas/challenge-catalog.schema.json`. ## Validation levels Use three levels of validation before publishing a content pack: 1. **Syntax validation** - Every seed file must parse as JSON. - JSON must not contain comments, trailing commas, `NaN`, `Infinity`, or date-like strings that are not valid ISO 8601 values. - IDs must be stable strings, not database integers. 2. **Schema validation** - Each entity must satisfy its model shape: required fields, enumerations, nullable fields, date formats, and reward object structure. - `data/seeds/challenges.json` must satisfy `schemas/challenge-catalog.schema.json`. - Runtime-only fields such as user progress, prediction answers, quiz attempts, and unlocked badges must not be committed to seed files. 3. **Referential and tournament validation** - Entity references must resolve to another seed entity or to a documented dynamic scope. - Live tournament fields must obey state-machine rules, especially around match status, predictions, spoilers, and knockout advancement. - Completion challenges that use dynamic targets must remain valid when the draw, knockout bracket, or final rankings change. ## Global rules ### IDs - IDs are immutable after publication. - IDs should be lowercase slugs with hyphens for tournament entities where already established, for example `group-a`, `mexico`, or `metlife-stadium`. - Challenge IDs use snake case with a `chal_` prefix. - Challenge set IDs use snake case with a `set_` prefix. - Badge/achievement reward IDs use snake case with a `badge_` prefix. - Prediction template IDs use snake case with a `pred_template_` prefix. - Never reuse an ID for a different entity after deletion; mark the old entity inactive instead. ### Dates and times - Store canonical date-times in UTC using ISO 8601, for example `2026-06-11T19:00:00Z`. - Store local display time separately only when the content model explicitly supports it. - Stadium time zones must be IANA time zone names, not raw offsets. - Prediction lock times must be before or equal to the referenced event start time. - Daily content windows should use UTC unless a feature explicitly scopes to a user or stadium local time zone. ### Localization safety - Seed copy should be source-language English unless a localized content pack is explicitly named. - Copy must avoid idioms that cannot be translated for core navigation and rewards. - Team and stadium names should come from entity display names rather than being hard-coded in UI strings when possible. - Do not commit machine-translated copy without human review. ### Spoilers - Quiz explanations, memory prompts, and sticker titles must not reveal match outcomes before the relevant match status is `final`. - Prediction results must not settle until the official match or tournament result is final. - Content with `riskFlags` containing `anti_spoiler` must have an unlock condition or publish window that prevents premature display. ## Entity-specific validation ### Tournament Required checks: - `id` is stable and unique. - Host countries are exactly the tournament host countries for this content pack. - Stage definitions include the 2026 format: group stage, Round of 32, Round of 16, quarter-finals, semi-finals, third-place match, and final. - Tournament `startsAt` is before `endsAt`. - Content pack version increases when any seeded tournament structure changes. Recommended checks: - The tournament has a default locale and supported locales list. - The tournament has official source metadata for live updates. ### Teams Required checks: - Each team has a unique `id`. - FIFA country/team code is uppercase and unique where applicable. - Confederation is one of the supported confederation values. - Qualified teams must not occupy more than one group slot. - Placeholder teams must be clearly marked as placeholders and must not be treated as qualified teams in completion challenges. Recommended checks: - Host teams are flagged. - Team display names and short names are present. - Ranking and seed fields include a source and `asOf` date when used for prediction scoring such as giant killings. ### Groups Required checks: - Group IDs are unique and cover Groups A-H for the 2026 tournament. - A finalized group contains exactly four team slots. - A non-finalized group may contain placeholder slots, but every placeholder must have a deterministic slot ID. - No qualified team appears in more than one group. - Group references from matches, challenges, quizzes, stickers, and predictions resolve. Recommended checks: - Draw status is explicit: `pre_draw`, `partial`, or `final`. - Group standings are not present in static preseason seeds unless clearly initialized as empty or zeroed. ### Stadiums Required checks: - Stadium IDs are unique. - Host city, host country, latitude, longitude, capacity, and time zone are present. - Time zone is an IANA string. - Coordinates are in valid latitude/longitude ranges. - Stadium country code is one of the tournament host countries. - Match stadium references resolve. Recommended checks: - Stadium naming uses the competition-approved display name. - Capacity has a source date because tournament capacities can differ from club capacities. - Accessibility and transport notes should be authored for fan-facing venue pages. ### Matches Required checks: - Match IDs are unique. - Official match number is unique if present. - Stage is valid. - Group-stage matches have a `groupId`. - Knockout matches have bracket metadata or placeholder participant references. - `kickoffAt` is an ISO 8601 UTC date-time. - `stadiumId` resolves to a stadium. - Pre-match result fields are `null` or absent. - A match cannot move from `final` back to `live` without a rollback/audit patch. Status state machine: 1. `scheduled` 2. `lineups_available` 3. `live` 4. `halftime` 5. `full_time_pending` 6. `extra_time` 7. `penalties` 8. `final` 9. `postponed` or `abandoned` only via official update Validation logic should reject impossible status transitions unless the update is marked as an operator rollback. ### Stickers and sticker sets Required checks: - Sticker set IDs are unique. - Sticker IDs are unique globally or unique within a set with a stable composite key. - Every sticker belongs to exactly one set. - Rarity is in the supported rarity enum. - Sticker references to teams, stadiums, groups, matches, or players resolve where used. - Drop windows are valid date ranges. Recommended checks: - Rarity distribution is reviewed against the economy plan. - Completion rewards do not create infinite premium pack loops. - Limited stickers have clear post-window behavior: remain visible, retire, or convert to commemorative. ### Fan memories Required checks: - Memory prompt IDs are unique. - Prompt unlock windows are valid. - Prompts that require a match, team, or stadium reference must declare the required reference type. - Prompts that allow media upload must carry moderation and privacy metadata. - Prompts cannot require personally sensitive information. Recommended checks: - Provide non-photo alternatives for every media-oriented memory prompt. - Keep prompts emotionally broad and inclusive: fans may celebrate, commiserate, or remember where they were. ### Quizzes Required checks: - Quiz IDs and question IDs are unique. - Every multiple-choice question has at least two options. - Correct answer indices resolve to an option. - Explanation copy is present for educational trivia. - Scheduled daily quizzes do not overlap for the same locale and day. - Spoiler-sensitive questions unlock only after the referenced result is final. Recommended checks: - Avoid ambiguous wording. - Store source notes for historical facts. - Review difficulty distribution across daily trivia. ### Predictions Required checks: - Prediction template IDs are unique. - Prediction options are valid for their target type. - Lock time is before the event or tournament state it predicts. - Settlement rule is deterministic and based on official data. - Scoring values are non-negative. - Predictions that depend on rankings must declare the ranking source and date. Recommended checks: - Provide a void/refund behavior for abandoned or postponed matches. - Provide display copy for both locked and settled states. - Settlement should be idempotent; replaying the same official result must not grant duplicate rewards. ### Achievements Required checks: - Achievement IDs are unique. - Achievement title and description are present. - Reward references from challenges resolve or are documented as future achievement records. - Hidden achievements must have safe reveal copy. - Achievement criteria must not conflict with challenge criteria if both exist. Recommended checks: - Separate achievement identity from challenge progress. A challenge can grant a badge, but the badge should remain a durable collectible. - Avoid achievements that require impossible post-elimination behavior unless the target is dynamic. ### Challenges Required checks: - `data/seeds/challenges.json` satisfies `schemas/challenge-catalog.schema.json`. - Challenge IDs are unique. - Each challenge belongs to an existing `challengeSet`. - Every challenge has at least one rule. - Rewards include `xp`, `coins`, `badges`, and `stickerPacks`. - Repeatable challenges must have an explicit cadence or reset policy. - Dynamic collection targets must define the entity scope and `distinctBy` field. - Prediction challenges must use lock/settlement logic that is deterministic and auditable. - Challenges with official-data dependencies must carry `riskFlags` such as `requires_official_feed` or `requires_live_result`. Recommended checks: - A user should understand the objective from the title and description without reading technical criteria. - Reward magnitude should broadly match difficulty: - `starter`: tutorial actions - `easy`: one action - `medium`: several actions or a moderate skill test - `hard`: multi-day, multi-match, or rare prediction - `elite`: completionist or very low-probability challenge - Challenges like “watch every team match” should use `target.mode = "all_available"` so progress expands when a team advances. ## Cross-reference rules Validation should fail if: - A match references a missing stadium. - A match references a missing team except for an approved placeholder slot. - A group references a missing team. - A sticker references a missing team, stadium, group, match, or set. - A quiz references a missing match, team, stadium, or group. - A prediction template references a missing match, team, group, or tournament stage. - A challenge `setId` does not exist in `challengeSets`. - A challenge references a static content ID that is absent and is not explicitly declared as a dynamic scope. - A reward sticker pack references a missing sticker set. - A badge reward references neither an achievement seed nor a documented future achievement ID. Warnings, not failures: - A challenge references a future qualified team placeholder before the full qualification set is known. - A dynamic target cannot be fully counted before draw or bracket confirmation. - A quiz has no localization yet. - A memory prompt has no illustration asset yet. ## Live update validation Every live update patch should validate both before and after applying the patch. Patch checks: - Patch has a unique ID, operator/source, timestamp, and reason. - Patch applies cleanly to the expected base content version. - Patch does not delete published IDs. - Patch only changes fields allowed for the current tournament phase. - Patch records official source metadata for results, lineups, standings, disciplinary events, and awards. - Patch can be rolled back or superseded by a new patch. After applying a live update: - Match state transitions are legal. - Standings recalculate deterministically. - Knockout bracket slots update only from final source matches. - Prediction settlement jobs are idempotent. - Challenge progress recalculation is idempotent. - Spoiler-gated content remains hidden until unlock conditions are satisfied. ## Content QA checklist Before merging a content pack: - [ ] JSON parses. - [ ] Schema validation passes. - [ ] IDs are unique. - [ ] Dates are ISO 8601 UTC where required. - [ ] Cross-references resolve. - [ ] Spoiler-sensitive content has unlock rules. - [ ] Rewards are balanced against difficulty. - [ ] Dynamic targets are used for uncertain tournament paths. - [ ] Copy is clear, inclusive, and localizable. - [ ] Live-data risk flags are present where needed. - [ ] The update is documented in the changelog or release notes.