[
  {
    "type": "create_table",
    "description": "Spec 051 T1B.4: archive table for any userDeviceTokens rows removed by the uk-widening dedupe below. Column-clone of userDeviceTokens (CTAS, no indexes - the same tokenId may be archived across reruns) plus archivedAt. Rebuild a row if ever needed: INSERT INTO userDeviceTokens (cols...) SELECT cols... FROM userDeviceTokensAppIdConflictArchive WHERE tokenId = :id.",
    "database": "kiosk_users",
    "check_query": "SHOW TABLES LIKE 'userDeviceTokensAppIdConflictArchive'",
    "sql": "CREATE TABLE IF NOT EXISTS `userDeviceTokensAppIdConflictArchive` AS SELECT t.*, UTC_TIMESTAMP() AS archivedAt FROM userDeviceTokens t WHERE 1 = 0"
  },
  {
    "type": "multi_sql",
    "description": "Spec 051 T1B.4 (rev 1): ATOMICALLY archive-and-delete rows that would collide under the new (userId, deviceId, appId) unique key, keeping the freshest row by updatedAt (tie-break: highest tokenId). Statement 3 captures AND X-locks the doomed tokenIds in one atomic statement (INSERT IGNORE ... SELECT ... FOR UPDATE into a temporary table, mirroring the proven 20260711_001 punch-dedupe pattern). Statements 4-5 archive then delete ONLY the ids in that captured set. NOTE: the CURRENT unique key is (userId, deviceId) - a strict SUBSET of the new key's columns - so no row can violate it today (a unique constraint on fewer columns is always at least as strict as one on more columns); this op will find zero doomed rows in practice and exists defensively per the house archive-first convention (CON-9) and to converge safely if that invariant were ever violated by a manual data repair. CONVERGENT via always_check: re-runs every conductor run until the new unique index exists, so a failed ALTER can never wedge the install behind the operation log.",
    "database": "kiosk_users",
    "always_check": true,
    "check_query": "SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = 'kiosk_users' AND TABLE_NAME = 'userDeviceTokens' AND INDEX_NAME = 'uk_userId_deviceId_appId'",
    "check_expects": "empty",
    "sqls": [
      "DROP TEMPORARY TABLE IF EXISTS tmpDeviceTokenAppIdDoomed",
      "CREATE TEMPORARY TABLE tmpDeviceTokenAppIdDoomed (tokenId INT UNSIGNED NOT NULL PRIMARY KEY) ENGINE=InnoDB",
      "INSERT IGNORE INTO tmpDeviceTokenAppIdDoomed SELECT t1.tokenId FROM userDeviceTokens t1 INNER JOIN userDeviceTokens t2 ON t2.userId = t1.userId AND t2.deviceId = t1.deviceId AND t2.appId = t1.appId AND t2.tokenId <> t1.tokenId AND (t2.updatedAt > t1.updatedAt OR (t2.updatedAt = t1.updatedAt AND t2.tokenId > t1.tokenId)) FOR UPDATE",
      "INSERT INTO userDeviceTokensAppIdConflictArchive SELECT t1.*, UTC_TIMESTAMP() FROM userDeviceTokens t1 INNER JOIN tmpDeviceTokenAppIdDoomed d ON d.tokenId = t1.tokenId",
      "DELETE t1 FROM userDeviceTokens t1 INNER JOIN tmpDeviceTokenAppIdDoomed d ON d.tokenId = t1.tokenId",
      "DROP TEMPORARY TABLE IF EXISTS tmpDeviceTokenAppIdDoomed"
    ]
  },
  {
    "type": "alter_table",
    "description": "Spec 051 T1B.4: widen userDeviceTokens unique key from (userId, deviceId) to (userId, deviceId, appId) so Team+Live push registrations on the same physical device coexist as separate rows instead of silently overwriting each other via the repository's ON DUPLICATE KEY UPDATE upsert (ADR-051-7).",
    "database": "kiosk_users",
    "check_query": "SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = 'kiosk_users' AND TABLE_NAME = 'userDeviceTokens' AND INDEX_NAME = 'uk_userId_deviceId_appId'",
    "sql": "ALTER TABLE `userDeviceTokens` DROP INDEX `uk_userId_deviceId`, ADD UNIQUE KEY `uk_userId_deviceId_appId` (`userId`, `deviceId`, `appId`)"
  },
  {
    "type": "add_index",
    "description": "Spec 051 T1B.4: add (userId, appId, updatedAt) index for the T2 AppAdoption detector's per-app device-freshness query (NFR-1 query budget table: 'AppAdoption ... Index relied on (userId,appId,updatedAt)').",
    "database": "kiosk_users",
    "check_query": "SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = 'kiosk_users' AND TABLE_NAME = 'userDeviceTokens' AND INDEX_NAME = 'idx_userId_appId_updatedAt'",
    "sql": "ALTER TABLE `userDeviceTokens` ADD INDEX `idx_userId_appId_updatedAt` (`userId`, `appId`, `updatedAt`)"
  }
]
