{
  "command": "plan",
  "fail_on": "error",
  "checked_count": 1,
  "summary": {
    "error": 1,
    "warning": 2,
    "info": 2
  },
  "metadata": {
    "success": false,
    "snapshot_model": "review-snapshot.json",
    "target_last_version": "1",
    "target_installed_rank": 1,
    "already_applied_count": 1,
    "pending": [
      {
        "script": "V3__widen_integer.sql",
        "version": "3",
        "description": "widen_integer",
        "type": "SQL",
        "checksum": -280238009,
        "path": "migrations/V3__widen_integer.sql"
      }
    ],
    "repeatables_pending": [],
    "checksum_drift": [],
    "sql_validation": {
      "enabled": true,
      "scope": "pending",
      "status": "PASS",
      "files_checked": 1,
      "errors": 0,
      "warnings": 2,
      "messages": [
        "changes a column's type: the engine may rewrite the table (row count unknown \u2014 severity not adjusted for table size)",
        "takes a table lock with no lock_timeout set earlier in the script, so queries on the table can queue behind it; start with SET LOCAL lock_timeout = '5s'"
      ],
      "findings": [
        {
          "severity": "warning",
          "code": "alter-column-type",
          "message": "changes a column's type: the engine may rewrite the table (row count unknown \u2014 severity not adjusted for table size)",
          "file": "migrations/V3__widen_integer.sql"
        },
        {
          "severity": "warning",
          "code": "pg-missing-lock-timeout",
          "message": "takes a table lock with no lock_timeout set earlier in the script, so queries on the table can queue behind it; start with SET LOCAL lock_timeout = '5s'",
          "file": "migrations/V3__widen_integer.sql"
        }
      ]
    },
    "warnings": [],
    "errors": [],
    "error": null,
    "impact_reports": [
      {
        "script": "V3__widen_integer.sql",
        "path": "migrations/V3__widen_integer.sql",
        "stats_source": "snapshot",
        "snapshot_captured_at": "2026-11-13T21:58:25.888811+00:00",
        "note": null,
        "impact": {
          "summary": {
            "error": 1,
            "warning": 0,
            "info": 0
          },
          "impacts": [
            {
              "statement_index": 0,
              "statement_type": "ALTER",
              "object_name": "widen_demo.readings",
              "object_type": "TABLE",
              "lock_class": "read_write_blocking",
              "lock_label": "ACCESS EXCLUSIVE",
              "duration": "table_scan",
              "risk": "error",
              "code": "impact.alter.type-change-rewrite",
              "notes": [
                "column type change rewrites the table"
              ],
              "table_name": "widen_demo.readings",
              "estimated_rows": 1000,
              "refined": true,
              "stats_source": "snapshot",
              "evidence_status": "verified"
            }
          ]
        },
        "safe_rewrite_steps": [
          "EXPAND: add the target-type column and mirror writes with a trigger",
          "BACKFILL: cast existing rows into the new column",
          "CONTRACT: cut over: drop the trigger and old column, rename the new one"
        ],
        "safe_rewrite_sql": "-- Schema Synchronization Script (zero-downtime safe form)\n-- =========================================================\n\n-- Dangerous operations have been rewritten into lock-friendly, zero-downtime equivalents.\n\n-- Generated: 2026-11-13T16:58:27.062437\n\n-- ===== PHASE: EXPAND \u2014 add the target-type column and mirror writes with a trigger =====\n--   Note: The new column is nullable during the migration; the trigger casts every insert/update of the old column into it.\n--   Note: This makes the change lock-friendly, NOT loss-proof \u2014 confirm the cast preserves every existing value before the cutover.\n\n-- ALTER COLUMN: widen_demo.readings.id\nALTER TABLE widen_demo.readings ADD COLUMN \"id_new\" bigint;\n\n\n-- CREATE FUNCTION: widen_demo.readings.id\nCREATE OR REPLACE FUNCTION \"readings_id_sync\"() RETURNS trigger AS $$\nBEGIN\n  NEW.\"id_new\" := CAST(NEW.\"id\" AS bigint);\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\n\n-- CREATE TRIGGER: widen_demo.readings.id\nCREATE TRIGGER \"readings_id_sync_trg\" BEFORE INSERT OR UPDATE ON widen_demo.readings FOR EACH ROW EXECUTE FUNCTION \"readings_id_sync\"();\n\n\n-- ===== PHASE: BACKFILL \u2014 cast existing rows into the new column =====\n--   Note: Converts every row in this one statement: the CONTRACT below drops the old column in this same file, so a bounded pass would leave rows unconverted. On a very large table, run this UPDATE in batches of your own before deploying the CONTRACT.\n\n-- UPDATE DATA: widen_demo.readings.id\nUPDATE widen_demo.readings SET \"id_new\" = CAST(\"id\" AS bigint) WHERE \"id_new\" IS DISTINCT FROM CAST(\"id\" AS bigint);\n\n\n\n-- ============================================================\n-- DEPLOY THE STATEMENTS BELOW IN A LATER RELEASE\n-- Run them once the application no longer depends on what they\n-- remove or change. Where the backfill above is a bounded batch,\n-- run it until it reports 0 rows first. On a very large table,\n-- split this section into a separate migration.\n-- ============================================================\n\n-- ===== PHASE: CONTRACT \u2014 cut over: drop the trigger and old column, rename the new one =====\n--   Note: Deploy this only after the backfill is complete AND the application no longer reads or writes the old column's type.\n--   Note: If the original column was NOT NULL, add an ALTER COLUMN \u2026 SET NOT NULL on the new column before the rename.\n\n-- DROP TRIGGER: widen_demo.readings.id\nDROP TRIGGER \"readings_id_sync_trg\" ON widen_demo.readings;\n\n\n-- DROP FUNCTION: widen_demo.readings.id\nDROP FUNCTION \"readings_id_sync\"();\n\n\n-- ALTER TABLE: widen_demo.readings.id\nALTER TABLE widen_demo.readings DROP COLUMN \"id\";\n\n\n-- ALTER COLUMN: widen_demo.readings.id\nALTER TABLE widen_demo.readings RENAME COLUMN \"id_new\" TO \"id\";\n"
      }
    ],
    "filters": {
      "versions": "3"
    }
  },
  "findings": [
    {
      "severity": "info",
      "code": "plan.pending",
      "message": "Migration V3__widen_integer.sql is pending",
      "file": "migrations/V3__widen_integer.sql",
      "details": {
        "version": "3"
      }
    },
    {
      "severity": "warning",
      "code": "alter-column-type",
      "message": "changes a column's type: the engine may rewrite the table (row count unknown \u2014 severity not adjusted for table size)",
      "file": "migrations/V3__widen_integer.sql"
    },
    {
      "severity": "warning",
      "code": "pg-missing-lock-timeout",
      "message": "takes a table lock with no lock_timeout set earlier in the script, so queries on the table can queue behind it; start with SET LOCAL lock_timeout = '5s'",
      "file": "migrations/V3__widen_integer.sql"
    },
    {
      "severity": "error",
      "code": "impact.alter.type-change-rewrite",
      "message": "ALTER widen_demo.readings: ACCESS EXCLUSIVE lock (column type change rewrites the table)",
      "file": "migrations/V3__widen_integer.sql",
      "details": {
        "lock": "ACCESS EXCLUSIVE",
        "duration": "table_scan",
        "object": "widen_demo.readings",
        "statement_index": 0,
        "script": "V3__widen_integer.sql",
        "estimated_rows": 1000,
        "evidence_status": "verified"
      }
    },
    {
      "severity": "info",
      "code": "plan.safe_rewrite",
      "message": "Dangerous DDL can be made zero-downtime (3 step(s)): EXPAND: add the target-type column and mirror writes with a trigger; BACKFILL: cast existing rows into the new column; CONTRACT: cut over: drop the trigger and old column, rename the new one",
      "file": "migrations/V3__widen_integer.sql",
      "details": {
        "non_gating": true,
        "steps": [
          "EXPAND: add the target-type column and mirror writes with a trigger",
          "BACKFILL: cast existing rows into the new column",
          "CONTRACT: cut over: drop the trigger and old column, rename the new one"
        ],
        "sql": "-- Schema Synchronization Script (zero-downtime safe form)\n-- =========================================================\n\n-- Dangerous operations have been rewritten into lock-friendly, zero-downtime equivalents.\n\n-- Generated: 2026-11-13T16:58:27.062437\n\n-- ===== PHASE: EXPAND \u2014 add the target-type column and mirror writes with a trigger =====\n--   Note: The new column is nullable during the migration; the trigger casts every insert/update of the old column into it.\n--   Note: This makes the change lock-friendly, NOT loss-proof \u2014 confirm the cast preserves every existing value before the cutover.\n\n-- ALTER COLUMN: widen_demo.readings.id\nALTER TABLE widen_demo.readings ADD COLUMN \"id_new\" bigint;\n\n\n-- CREATE FUNCTION: widen_demo.readings.id\nCREATE OR REPLACE FUNCTION \"readings_id_sync\"() RETURNS trigger AS $$\nBEGIN\n  NEW.\"id_new\" := CAST(NEW.\"id\" AS bigint);\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\n\n-- CREATE TRIGGER: widen_demo.readings.id\nCREATE TRIGGER \"readings_id_sync_trg\" BEFORE INSERT OR UPDATE ON widen_demo.readings FOR EACH ROW EXECUTE FUNCTION \"readings_id_sync\"();\n\n\n-- ===== PHASE: BACKFILL \u2014 cast existing rows into the new column =====\n--   Note: Converts every row in this one statement: the CONTRACT below drops the old column in this same file, so a bounded pass would leave rows unconverted. On a very large table, run this UPDATE in batches of your own before deploying the CONTRACT.\n\n-- UPDATE DATA: widen_demo.readings.id\nUPDATE widen_demo.readings SET \"id_new\" = CAST(\"id\" AS bigint) WHERE \"id_new\" IS DISTINCT FROM CAST(\"id\" AS bigint);\n\n\n\n-- ============================================================\n-- DEPLOY THE STATEMENTS BELOW IN A LATER RELEASE\n-- Run them once the application no longer depends on what they\n-- remove or change. Where the backfill above is a bounded batch,\n-- run it until it reports 0 rows first. On a very large table,\n-- split this section into a separate migration.\n-- ============================================================\n\n-- ===== PHASE: CONTRACT \u2014 cut over: drop the trigger and old column, rename the new one =====\n--   Note: Deploy this only after the backfill is complete AND the application no longer reads or writes the old column's type.\n--   Note: If the original column was NOT NULL, add an ALTER COLUMN \u2026 SET NOT NULL on the new column before the rename.\n\n-- DROP TRIGGER: widen_demo.readings.id\nDROP TRIGGER \"readings_id_sync_trg\" ON widen_demo.readings;\n\n\n-- DROP FUNCTION: widen_demo.readings.id\nDROP FUNCTION \"readings_id_sync\"();\n\n\n-- ALTER TABLE: widen_demo.readings.id\nALTER TABLE widen_demo.readings DROP COLUMN \"id\";\n\n\n-- ALTER COLUMN: widen_demo.readings.id\nALTER TABLE widen_demo.readings RENAME COLUMN \"id_new\" TO \"id\";\n"
      }
    }
  ],
  "success": false
}
