-- EnakGame game catalog (docs/rfc-enakgame.md §5.1). games is extended rather than -- replaced, because the customer app already reads it. ALTER TABLE games ADD COLUMN organization_id UUID, ADD COLUMN slug VARCHAR(100), ADD COLUMN description TEXT, ADD COLUMN thumbnail_url VARCHAR(500), ADD COLUMN game_url VARCHAR(500), ADD COLUMN version VARCHAR(50), ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' CONSTRAINT chk_games_status CHECK (status IN ('DRAFT', 'ACTIVE', 'INACTIVE', 'ARCHIVED')), ADD COLUMN entry_cost BIGINT, ADD COLUMN session_ttl_seconds INT NOT NULL DEFAULT 600 CONSTRAINT chk_games_session_ttl_seconds CHECK (session_ttl_seconds > 0), -- Limits the Result Validator checks a result against: max_score, -- min_duration_seconds, max_score_per_second, the valid outcomes, and the game's -- daily reward limit (§5.10, §7.2). ADD COLUMN result_rules JSONB NOT NULL DEFAULT '{}'; -- The old games leave the product: archived, never deleted (§14). game_plays.game_id -- is ON DELETE CASCADE, and the old GAME_SPEND ledger rows point at game_plays. -- Their coin cost becomes the entry cost; one that is missing, not a whole number or -- below 1 becomes 1, so the CHECK below holds. UPDATE games SET status = 'ARCHIVED', is_active = FALSE, entry_cost = CASE WHEN metadata->>'coin_cost' ~ '^[0-9]{1,18}$' THEN GREATEST((metadata->>'coin_cost')::BIGINT, 1) ELSE 1 END; ALTER TABLE games ALTER COLUMN entry_cost SET NOT NULL, -- PRD §10.1: no game is free. ADD CONSTRAINT chk_games_entry_cost CHECK (entry_cost >= 1), -- Every EnakGame game has an organization and a slug. Only the archived old games -- may lack them. ADD CONSTRAINT chk_games_enakgame_identity CHECK ( status = 'ARCHIVED' OR (organization_id IS NOT NULL AND slug IS NOT NULL)); CREATE UNIQUE INDEX uq_games_org_slug ON games(organization_id, slug) WHERE slug IS NOT NULL; CREATE INDEX idx_games_org_status ON games(organization_id, status);