Pre-#67 builds wrote Conversation.endpoint as the Kotlin enum `.name`
(e.g. "OPENAI"). #67 added a read-side shim in ConversationMapper that
converted those legacy rows back to the wire-format SerialName on read.
Replace the shim with a one-time data migration so the conversion runs
once per user on db upgrade and the read path is plain.
Bump database to v4 and register MIGRATION_3_4 on both Android and iOS
builders. The migration runs nine UPDATE statements per affected column
(conversations.endpoint, conversations.endpointType, presets.endpoint).
The presets table is migrated defensively — PresetDao currently has no
production callers, so the column is empty in the field, but symmetry
is enforced if a future change starts persisting presets.
Schema v4.json is structurally identical to v3.json; identityHash is
unchanged (b4117797915eafc13b5bd956677f83bf). Only the version field
differs.
Pre-#67 ChatPayloadBuilder coerced any unknown endpoint name to
EModelEndpoint.AGENTS, so a legacy "AGENTS" row may have originated
from any custom endpoint. The migration rewrites "AGENTS" -> "agents",
preserving the same lossy outcome the read-side shim already produced.
A dedicated test pins this contract so a future "smart" migration can't
silently change cache/fresh consistency.
Verified via :core:data:connectedDebugAndroidTest (5 tests, all green:
existing v1->v3 helper test, v1->v4 Room API test, v3->v4 endpoint
normalization, v3->v4 lossy AGENTS, v3->v4 preset normalization).