-- Finalize the public/runtime contract for the three image-model migration. -- Callers use stable official Gemini IDs while provider bindings retain their -- upstream preview IDs. Capabilities and pricing are copied from the source -- EasyAI runtime contract instead of falling back to generic defaults. WITH model_contract( invocation_name, preview_invocation_name, capabilities, billing_config, pricing_rule_set_key ) AS ( VALUES ( 'gemini-3-pro-image', 'gemini-3-pro-image-preview', '{ "image_generate": { "output_multiple_images": true, "output_max_images_count": 4, "output_resolutions": ["1K", "2K", "4K"], "aspect_ratio_allowed": ["1:1", "16:9", "4:3", "3:4", "5:4", "4:5", "9:16", "21:9", "3:2", "2:3", "adaptive"] }, "image_edit": { "output_multiple_images": true, "input_multiple_images": true, "input_max_images_count": 14, "input_max_file_size_bytes": 20971520, "output_resolutions": ["1K", "2K", "4K"], "aspect_ratio_allowed": ["1:1", "16:9", "4:3", "3:4", "5:4", "4:5", "9:16", "21:9", "3:2", "2:3", "adaptive"] }, "originalTypes": ["image_generate", "image_edit"] }'::jsonb, '{ "image": { "basePrice": 1, "baseWeight": 1, "dynamicWeight": { "1K": 1, "2K": 1, "3K": 1, "4K": 1.8, "8K": 4, "qualityLow": 0.5, "qualityMedium": 1, "qualityHigh": 1.5 } } }'::jsonb, 'migration-pricing-f721f8076252bd77' ), ( 'gemini-3.1-flash-image', 'gemini-3.1-flash-image-preview', '{ "image_generate": { "output_multiple_images": true, "output_max_images_count": 4, "output_resolutions": ["1K", "2K", "4K"], "aspect_ratio_allowed": ["1:1", "16:9", "4:3", "3:4", "5:4", "4:5", "9:16", "21:9", "3:2", "2:3", "1:4", "4:1", "1:8", "8:1", "adaptive"] }, "image_edit": { "output_multiple_images": true, "input_multiple_images": true, "input_max_images_count": 14, "input_max_file_size_bytes": 20971520, "output_resolutions": ["1K", "2K", "4K"], "aspect_ratio_allowed": ["1:1", "16:9", "4:3", "3:4", "5:4", "4:5", "9:16", "21:9", "3:2", "2:3", "1:4", "4:1", "1:8", "8:1", "adaptive"] }, "originalTypes": ["image_generate", "image_edit"] }'::jsonb, '{ "image": { "basePrice": 1, "baseWeight": 1, "dynamicWeight": { "1K": 1, "2K": 1, "3K": 1, "4K": 1.8, "8K": 4, "qualityLow": 0.5, "qualityMedium": 1, "qualityHigh": 1.5 } } }'::jsonb, 'migration-pricing-f721f8076252bd77' ) ), updated_base_models AS ( UPDATE base_model_catalog base_model SET capabilities = contract.capabilities, base_billing_config = contract.billing_config, pricing_rule_set_id = COALESCE( ( SELECT rule_set.id FROM model_pricing_rule_sets rule_set WHERE rule_set.rule_set_key = contract.pricing_rule_set_key AND rule_set.status = 'active' LIMIT 1 ), base_model.pricing_rule_set_id ), metadata = COALESCE(base_model.metadata, '{}'::jsonb) || jsonb_build_object( 'bindingStatus', 'validating', 'capabilitySource', 'server-main-model-runtime', 'pricingSource', contract.pricing_rule_set_key ), status = 'active', updated_at = now() FROM model_contract contract WHERE base_model.provider_key = 'gemini' AND base_model.invocation_name = contract.invocation_name RETURNING base_model.id, base_model.invocation_name, base_model.capabilities ) UPDATE platform_models platform_model SET capabilities = updated.capabilities, updated_at = now() FROM updated_base_models updated WHERE platform_model.base_model_id = updated.id; -- The preview catalog rows originated as customized import records, so the -- generic lifecycle migration deliberately did not alter them. This migration -- scopes the lifecycle change to the two migrated image identities. WITH replacements(preview_invocation_name, invocation_name) AS ( VALUES ('gemini-3-pro-image-preview', 'gemini-3-pro-image'), ('gemini-3.1-flash-image-preview', 'gemini-3.1-flash-image') ), preview_models AS ( UPDATE base_model_catalog preview SET status = 'deprecated', metadata = COALESCE(preview.metadata, '{}'::jsonb) || jsonb_build_object( 'lifecycleStage', 'deprecated', 'replacementModel', replacements.invocation_name, 'retireAt', '2026-07-22', 'sourceUrl', 'https://ai.google.dev/gemini-api/docs/deprecations', 'verifiedAt', '2026-07-24' ), updated_at = now() FROM replacements WHERE preview.provider_key = 'gemini' AND preview.invocation_name = replacements.preview_invocation_name RETURNING preview.id, preview.invocation_name ), stable_models AS ( SELECT stable.id, stable.invocation_name, replacements.preview_invocation_name FROM replacements JOIN base_model_catalog stable ON stable.provider_key = 'gemini' AND stable.invocation_name = replacements.invocation_name ), disabled_conflicting_aliases AS ( UPDATE model_compatibility_aliases compatibility_alias SET active = false, expires_at = COALESCE(compatibility_alias.expires_at, now()), updated_at = now() FROM preview_models preview, stable_models stable WHERE compatibility_alias.base_model_id = preview.id AND stable.preview_invocation_name = preview.invocation_name AND compatibility_alias.alias IN ( stable.invocation_name, 'gemini:' || preview.invocation_name ) RETURNING compatibility_alias.id ) INSERT INTO model_compatibility_aliases ( base_model_id, alias, model_type, active ) SELECT stable.id, stable.preview_invocation_name, model_type.value, true FROM stable_models stable CROSS JOIN LATERAL ( VALUES ('image_generate'), ('image_edit') ) AS model_type(value) ON CONFLICT (base_model_id, alias, model_type) DO UPDATE SET active = true, expires_at = NULL, updated_at = now();