# Skewcheck: ⛔ UNSAFE — reproducible compatibility regression

> **Question:** does the previous application version still work against the proposed database schema?

The previous application version reproducibly fails against the proposed schema, and the failure is linked to a specific schema change.

| | |
|---|---|
| Project | fixture/B-breaking-rename |
| Execution | `example-B-breaking-rename` |
| Baseline (old app) | `shop-v1` |
| Candidate | `B-breaking-rename` |
| PostgreSQL | 16.15 (Debian 16.15-1.pgdg12+2) |
| Proposed migration(s) | `002_rename_email.sql` |
| Gate | **blocking** (exit 1) |
| Experiment complete | yes |

## Why
- test/customers.test.js: SQLSTATE 42703 (undefined_column) linked to column_removed:customers.email by R1 undefined_column ↔ removed/renamed column.
- test/orders.test.js: SQLSTATE 42703 (undefined_column) linked to column_removed:customers.email by R1 undefined_column ↔ removed/renamed column.

## Recommended next action
Make the migration backward compatible (expand/contract: keep the old shape until the old version is retired), then re-run the gate.

## Evidence
### regression — `test/customers.test.js`
**Observed:**
- test/customers.test.js passes on the baseline schema and fails in all 2 candidate runs.
- New in every candidate run, absent from the control run: SQLSTATE 42703 (undefined_column): column "email" of relation "customers" does not exist — statement: INSERT INTO customers (name, email) VALUES ($1, $2) RETURNING id
**Interpretation:** The statement references column "email", which the proposed schema no longer has on customers.
**Correlation rule:** R1 undefined_column ↔ removed/renamed column (strong)
**Linked change(s):** `column_removed:customers.email`

### regression — `test/orders.test.js`
**Observed:**
- test/orders.test.js passes on the baseline schema and fails in all 2 candidate runs.
- New in every candidate run, absent from the control run: SQLSTATE 42703 (undefined_column): column "email" of relation "customers" does not exist — statement: INSERT INTO customers (name, email) VALUES ($1, $2) RETURNING id
**Interpretation:** The statement references column "email", which the proposed schema no longer has on customers.
**Correlation rule:** R1 undefined_column ↔ removed/renamed column (strong)
**Linked change(s):** `column_removed:customers.email`

## Schema changes (catalog diff)
| Change | Risk | Exercised by old app | Tests |
|---|---|---|---|
| Column customers.email (text) removed | breaking | yes (3 stmt) | `test/customers.test.js`, `test/orders.test.js` |
| Column customers.email_address added (text NOT NULL); possibly a rename of "email" | breaking | yes (2 stmt) | `test/customers.test.js`, `test/orders.test.js` |
| Constraint customers_email_key on customers changed: UNIQUE (email) → UNIQUE (email_address) | breaking | yes (2 stmt) | `test/customers.test.js`, `test/orders.test.js` |

**Coverage:** 3 of 3 risk-bearing schema change(s) were exercised by the old application's tests in the control run.

## Runs
| Run | Migrations | Tests (pass/fail/other) | DB errors | Statements |
|---|---|---|---|---|
| control (old app × baseline schema) | 1/1 applied | 2/0/0 | 0 | 7 |
| candidate #1 (old app × proposed schema) | 2/2 applied | 0/2/0 | 2 (42703 undefined_column) | 0 |
| candidate #2 (old app × proposed schema) | 2/2 applied | 0/2/0 | 2 (42703 undefined_column) | 0 |
| current app × proposed schema (context) | 2/2 applied | 1/0/0 | 0 | 2 |

## Skipped checks
- Performance / lock-time analysis (out of scope).

## Limitations
- Skewcheck only observes code paths that the old application's tests execute; untested paths are reported as coverage gaps, not as safe.
- Coverage is identifier-level: a statement "touches" a changed object when it names the table (and column, or uses *). It is not a SQL parser.
- Correlation between an error and a schema change uses a fixed set of deterministic rules (R1–R6). Anything else is reported as ADVISORY, never UNSAFE.
- Performance, locking and migration runtime on production-sized data are not evaluated.
- Test files of one run share a database and run sequentially; ordering effects are identical across runs but are not isolated.
- Data-only migration effects are detected only when a test asserts on the affected data.

<sub>skewcheck 0.1.0 · report schema v1 · 2026-10-24T18:29:55.953Z</sub>