[
  {
    "type": "create_table",
    "description": "Spec 051 T1B.1: create onboardingProgress (kiosk_buykiosk) — TERMINAL step facts only, immutable latched achievement. One row per (typeNum, flowKey, stepKey) ever reaching complete/skipped; never updated after insert.",
    "database": "kiosk_buykiosk",
    "check_query": "SELECT 1 AS exact_match WHERE EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingProgress' AND TABLE_TYPE='BASE TABLE' AND ENGINE='InnoDB' AND TABLE_COLLATION=(SELECT DEFAULT_COLLATE_NAME FROM INFORMATION_SCHEMA.CHARACTER_SETS WHERE CHARACTER_SET_NAME='utf8mb4') AND TABLE_COMMENT='Onboarding terminal step facts (latched achievement) - Spec 051') AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingProgress' HAVING COUNT(*)=10 AND SUM(COLUMN_NAME='id' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int unsigned' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND LOWER(EXTRA)='auto_increment' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='typeNum' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(10)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='Store identifier')=1 AND SUM(COLUMN_NAME='flowKey' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(50)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='stepKey' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(50)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='status' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='enum(''complete'',''skipped'')' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='detection' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='enum(''auto'',''manual'')' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='NULL when status=skipped')=1 AND SUM(COLUMN_NAME='flowVersion' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(20)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='evidence' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='longtext' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='Permission-neutral snapshot (counts, not wage-flavored strings)')=1 AND SUM(COLUMN_NAME='completedByUserId' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int unsigned' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='FK to kiosk_users.users.id (cross-DB, no constraint)')=1 AND SUM(COLUMN_NAME='completedAt' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='datetime' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1) AND (SELECT COALESCE(GROUP_CONCAT(CONCAT(INDEX_NAME,':',NON_UNIQUE,':',cols) ORDER BY INDEX_NAME SEPARATOR ';'),'') FROM (SELECT INDEX_NAME,NON_UNIQUE,GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') cols FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingProgress' GROUP BY INDEX_NAME,NON_UNIQUE) i)='PRIMARY:0:id;uk_store_flow_step:0:typeNum,flowKey,stepKey' AND EXISTS (SELECT 1 WHERE (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingProgress' AND REFERENCED_TABLE_NAME IS NOT NULL)=0) AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='kiosk_buykiosk' AND CHECK_CLAUSE IN ('json_valid(`evidence`)','json_valid(`evidence`)'))",
    "sql": "CREATE TABLE `onboardingProgress` (`id` int unsigned NOT NULL AUTO_INCREMENT, `typeNum` varchar(10) NOT NULL COMMENT 'Store identifier', `flowKey` varchar(50) NOT NULL, `stepKey` varchar(50) NOT NULL, `status` enum('complete','skipped') NOT NULL, `detection` enum('auto','manual') DEFAULT NULL COMMENT 'NULL when status=skipped', `flowVersion` varchar(20) NOT NULL, `evidence` json DEFAULT NULL COMMENT 'Permission-neutral snapshot (counts, not wage-flavored strings)', `completedByUserId` int unsigned DEFAULT NULL COMMENT 'FK to kiosk_users.users.id (cross-DB, no constraint)', `completedAt` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_store_flow_step` (`typeNum`, `flowKey`, `stepKey`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Onboarding terminal step facts (latched achievement) - Spec 051'"
  },
  {
    "type": "create_table",
    "description": "Spec 051 T1B.1: create onboardingObservations (kiosk_buykiosk) — last-known non-terminal detector state, upserted on every successful detection (M3-08). Serves stale-but-honest state when a detector fails.",
    "database": "kiosk_buykiosk",
    "check_query": "SELECT 1 AS exact_match WHERE EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingObservations' AND TABLE_TYPE='BASE TABLE' AND ENGINE='InnoDB' AND TABLE_COLLATION=(SELECT DEFAULT_COLLATE_NAME FROM INFORMATION_SCHEMA.CHARACTER_SETS WHERE CHARACTER_SET_NAME='utf8mb4') AND TABLE_COMMENT='Onboarding last-known detector observations, upserted every detection - Spec 051') AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingObservations' HAVING COUNT(*)=11 AND SUM(COLUMN_NAME='id' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int unsigned' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND LOWER(EXTRA)='auto_increment' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='typeNum' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(10)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='Store identifier')=1 AND SUM(COLUMN_NAME='flowKey' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(50)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='stepKey' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(50)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='status' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='enum(''blocked'',''notstarted'',''inprogress'',''met'')' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='numerator' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='denominator' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='evidence' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='longtext' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='Permission-neutral snapshot')=1 AND SUM(COLUMN_NAME='detectorVersion' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(20)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='observedAt' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='datetime' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='lastSuccessAt' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='datetime' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1) AND (SELECT COALESCE(GROUP_CONCAT(CONCAT(INDEX_NAME,':',NON_UNIQUE,':',cols) ORDER BY INDEX_NAME SEPARATOR ';'),'') FROM (SELECT INDEX_NAME,NON_UNIQUE,GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') cols FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingObservations' GROUP BY INDEX_NAME,NON_UNIQUE) i)='PRIMARY:0:id;uk_obs:0:typeNum,flowKey,stepKey' AND EXISTS (SELECT 1 WHERE (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingObservations' AND REFERENCED_TABLE_NAME IS NOT NULL)=0) AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='kiosk_buykiosk' AND CHECK_CLAUSE IN ('json_valid(`evidence`)','json_valid(`evidence`)'))",
    "sql": "CREATE TABLE `onboardingObservations` (`id` int unsigned NOT NULL AUTO_INCREMENT, `typeNum` varchar(10) NOT NULL COMMENT 'Store identifier', `flowKey` varchar(50) NOT NULL, `stepKey` varchar(50) NOT NULL, `status` enum('blocked','notStarted','inProgress','met') NOT NULL, `numerator` int DEFAULT NULL, `denominator` int DEFAULT NULL, `evidence` json NOT NULL COMMENT 'Permission-neutral snapshot', `detectorVersion` varchar(20) NOT NULL, `observedAt` datetime NOT NULL, `lastSuccessAt` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_obs` (`typeNum`, `flowKey`, `stepKey`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Onboarding last-known detector observations, upserted every detection - Spec 051'"
  },
  {
    "type": "create_table",
    "description": "Spec 051 T1B.1: create onboardingUserProgress (kiosk_buykiosk) — per-user walkthrough/checklist item state (e.g. walkthrough:firstSchedule:v1), distinct from the store-level terminal facts in onboardingProgress.",
    "database": "kiosk_buykiosk",
    "check_query": "SELECT 1 AS exact_match WHERE EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingUserProgress' AND TABLE_TYPE='BASE TABLE' AND ENGINE='InnoDB' AND TABLE_COLLATION=(SELECT DEFAULT_COLLATE_NAME FROM INFORMATION_SCHEMA.CHARACTER_SETS WHERE CHARACTER_SET_NAME='utf8mb4') AND TABLE_COMMENT='Per-user walkthrough/onboarding item progress - Spec 051') AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingUserProgress' HAVING COUNT(*)=7 AND SUM(COLUMN_NAME='id' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int unsigned' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND LOWER(EXTRA)='auto_increment' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='userId' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int unsigned' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='FK to kiosk_users.users.id (cross-DB, no constraint)')=1 AND SUM(COLUMN_NAME='typeNum' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(10)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='Store identifier')=1 AND SUM(COLUMN_NAME='itemKey' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(80)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='e.g. walkthrough:firstSchedule:v1')=1 AND SUM(COLUMN_NAME='status' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='enum(''inprogress'',''completed'',''exited'')' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='beat' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(40)' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='updatedAt' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='datetime' AND IS_NULLABLE='NO' AND LOWER(COLUMN_DEFAULT) IN ('current_timestamp','current_timestamp()') AND LOWER(EXTRA)='on update current_timestamp()' AND COLUMN_COMMENT='')=1) AND (SELECT COALESCE(GROUP_CONCAT(CONCAT(INDEX_NAME,':',NON_UNIQUE,':',cols) ORDER BY INDEX_NAME SEPARATOR ';'),'') FROM (SELECT INDEX_NAME,NON_UNIQUE,GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') cols FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingUserProgress' GROUP BY INDEX_NAME,NON_UNIQUE) i)='PRIMARY:0:id;uk_user_store_item:0:userId,typeNum,itemKey' AND EXISTS (SELECT 1 WHERE (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingUserProgress' AND REFERENCED_TABLE_NAME IS NOT NULL)=0)",
    "sql": "CREATE TABLE `onboardingUserProgress` (`id` int unsigned NOT NULL AUTO_INCREMENT, `userId` int unsigned NOT NULL COMMENT 'FK to kiosk_users.users.id (cross-DB, no constraint)', `typeNum` varchar(10) NOT NULL COMMENT 'Store identifier', `itemKey` varchar(80) NOT NULL COMMENT 'e.g. walkthrough:firstSchedule:v1', `status` enum('inProgress','completed','exited') NOT NULL, `beat` varchar(40) DEFAULT NULL, `updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_store_item` (`userId`, `typeNum`, `itemKey`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Per-user walkthrough/onboarding item progress - Spec 051'"
  },
  {
    "type": "create_table",
    "description": "Spec 051 T1B.1: create onboardingEventLog (kiosk_buykiosk) — producer-matrix analytics event log with a dedupeKey unique index for emit-once semantics. typeNum is NULLable for user-scoped events (e.g. app_adopted is not tied to a single store) per the T0 Codex plan-review correction.",
    "database": "kiosk_buykiosk",
    "check_query": "SELECT 1 AS exact_match WHERE EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingEventLog' AND TABLE_TYPE='BASE TABLE' AND ENGINE='InnoDB' AND TABLE_COLLATION=(SELECT DEFAULT_COLLATE_NAME FROM INFORMATION_SCHEMA.CHARACTER_SETS WHERE CHARACTER_SET_NAME='utf8mb4') AND TABLE_COMMENT='Onboarding producer-matrix event log - Spec 051') AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingEventLog' HAVING COUNT(*)=9 AND SUM(COLUMN_NAME='id' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='bigint unsigned' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND LOWER(EXTRA)='auto_increment' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='eventKey' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(60)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='typeNum' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(10)' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='NULL for user-scoped events, e.g. app_adopted')=1 AND SUM(COLUMN_NAME='userId' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int unsigned' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='FK to kiosk_users.users.id (cross-DB, no constraint)')=1 AND SUM(COLUMN_NAME='campaignId' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(40)' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='flowVersion' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(20)' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='occurredAt' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='datetime' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='properties' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='longtext' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='dedupeKey' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(120)' AND IS_NULLABLE='YES' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='e.g. step:pc00:scheduling:positions, appAdopted:123:team - NULL-tolerant unique (multiple NULLs allowed)')=1) AND (SELECT COALESCE(GROUP_CONCAT(CONCAT(INDEX_NAME,':',NON_UNIQUE,':',cols) ORDER BY INDEX_NAME SEPARATOR ';'),'') FROM (SELECT INDEX_NAME,NON_UNIQUE,GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') cols FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingEventLog' GROUP BY INDEX_NAME,NON_UNIQUE) i)='idx_campaignid:1:campaignId;idx_eventkey_occurredat:1:eventKey,occurredAt;idx_typenum_eventkey:1:typeNum,eventKey;PRIMARY:0:id;uk_dedupe_key:0:dedupeKey' AND EXISTS (SELECT 1 WHERE (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingEventLog' AND REFERENCED_TABLE_NAME IS NOT NULL)=0) AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='kiosk_buykiosk' AND CHECK_CLAUSE IN ('json_valid(`properties`)','json_valid(`properties`)'))",
    "sql": "CREATE TABLE `onboardingEventLog` (`id` bigint unsigned NOT NULL AUTO_INCREMENT, `eventKey` varchar(60) NOT NULL, `typeNum` varchar(10) DEFAULT NULL COMMENT 'NULL for user-scoped events, e.g. app_adopted', `userId` int unsigned DEFAULT NULL COMMENT 'FK to kiosk_users.users.id (cross-DB, no constraint)', `campaignId` varchar(40) DEFAULT NULL, `flowVersion` varchar(20) DEFAULT NULL, `occurredAt` datetime NOT NULL, `properties` json NOT NULL, `dedupeKey` varchar(120) DEFAULT NULL COMMENT 'e.g. step:pc00:scheduling:positions, appAdopted:123:team - NULL-tolerant unique (multiple NULLs allowed)', PRIMARY KEY (`id`), UNIQUE KEY `uk_dedupe_key` (`dedupeKey`), KEY `idx_typenum_eventkey` (`typeNum`, `eventKey`), KEY `idx_eventkey_occurredat` (`eventKey`, `occurredAt`), KEY `idx_campaignid` (`campaignId`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Onboarding producer-matrix event log - Spec 051'"
  },
  {
    "type": "create_table",
    "description": "Spec 051 T1B.1: create onboardingCampaignCohort (kiosk_buykiosk) — immutable KPI denominator snapshot per (campaignId, typeNum), written once by scripts/onboarding-baseline-snapshot.php (M2-04). Excluded by default from the onboarding-reset-store.php fixture reset (see --include-cohort).",
    "database": "kiosk_buykiosk",
    "check_query": "SELECT 1 AS exact_match WHERE EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingCampaignCohort' AND TABLE_TYPE='BASE TABLE' AND ENGINE='InnoDB' AND TABLE_COLLATION=(SELECT DEFAULT_COLLATE_NAME FROM INFORMATION_SCHEMA.CHARACTER_SETS WHERE CHARACTER_SET_NAME='utf8mb4') AND TABLE_COMMENT='Immutable KPI denominator snapshot per campaign - Spec 051') AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingCampaignCohort' HAVING COUNT(*)=6 AND SUM(COLUMN_NAME='id' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='int unsigned' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND LOWER(EXTRA)='auto_increment' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='campaignId' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(40)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='typeNum' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='varchar(10)' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='Store identifier')=1 AND SUM(COLUMN_NAME='cohort' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='enum(''eligible'',''grandfathered'',''external'')' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1 AND SUM(COLUMN_NAME='baseline' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='longtext' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='positions/roster/publish/ticket-rate metrics at snapshot time')=1 AND SUM(COLUMN_NAME='snapshotAt' AND CASE WHEN LOWER(COLUMN_TYPE) REGEXP '^(tinyint|int|bigint)[(][0-9]+[)]' THEN CONCAT(SUBSTRING_INDEX(LOWER(COLUMN_TYPE), '(', 1), IF(LOWER(COLUMN_TYPE) LIKE '% unsigned', ' unsigned', '')) ELSE LOWER(COLUMN_TYPE) END='datetime' AND IS_NULLABLE='NO' AND (COLUMN_DEFAULT IS NULL OR UPPER(COLUMN_DEFAULT)='NULL') AND EXTRA='' AND COLUMN_COMMENT='')=1) AND (SELECT COALESCE(GROUP_CONCAT(CONCAT(INDEX_NAME,':',NON_UNIQUE,':',cols) ORDER BY INDEX_NAME SEPARATOR ';'),'') FROM (SELECT INDEX_NAME,NON_UNIQUE,GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') cols FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingCampaignCohort' GROUP BY INDEX_NAME,NON_UNIQUE) i)='PRIMARY:0:id;uk_campaign_store:0:campaignId,typeNum' AND EXISTS (SELECT 1 WHERE (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='kiosk_buykiosk' AND TABLE_NAME='onboardingCampaignCohort' AND REFERENCED_TABLE_NAME IS NOT NULL)=0) AND EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='kiosk_buykiosk' AND CHECK_CLAUSE IN ('json_valid(`baseline`)','json_valid(`baseline`)'))",
    "sql": "CREATE TABLE `onboardingCampaignCohort` (`id` int unsigned NOT NULL AUTO_INCREMENT, `campaignId` varchar(40) NOT NULL, `typeNum` varchar(10) NOT NULL COMMENT 'Store identifier', `cohort` enum('eligible','grandfathered','external') NOT NULL, `baseline` json NOT NULL COMMENT 'positions/roster/publish/ticket-rate metrics at snapshot time', `snapshotAt` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_campaign_store` (`campaignId`, `typeNum`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Immutable KPI denominator snapshot per campaign - Spec 051'"
  }
]
