Repository navigation
Description
Context
PR LeanerCloud/cloud-commitments-cli#1277 lands the EXPAND half of a cancelled->canceled rename (expand-contract pattern). The EXPAND migration (000078) is intentionally additive and non-normalizing:
- it widens both status CHECK constraints to accept BOTH spellings,
- it adds the
canceled_bycolumn and does a best-effort copy of existingcancelled_byvalues, - it does NOT normalize
status='cancelled'rows and does NOT authoritatively draincancelled_by(a one-time backfill during EXPAND would be a false guarantee, since old code still running keeps writing fresh'cancelled'/cancelled_by-only rows right after the backfill).
Throughout EXPAND the application reads BOTH spellings (status filter + KPI/cancel switches accept both; every column read projects COALESCE(canceled_by, cancelled_by)), so any row written by old or new code at any time reads correctly.
This CONTRACT step runs only after PR LeanerCloud/cloud-commitments-cli#1277 is deployed and every old code instance is gone — at which point the set of legacy rows is final and can be normalized completely.
Work
Create migration 000079 (next after 000078) that, in this order:
- Normalize late legacy data FIRST (this is the authoritative pass the EXPAND migration deliberately skipped):
UPDATE purchase_executions SET status = 'canceled' WHERE status = 'cancelled';UPDATE ri_exchange_history SET status = 'canceled' WHERE status = 'cancelled';- Drain attribution before dropping the column:
UPDATE purchase_executions SET canceled_by = cancelled_by WHERE canceled_by IS NULL AND cancelled_by IS NOT NULL;
- Drop
'cancelled'from thepurchase_executions_status_checkconstraint (leaving only'canceled'and the other statuses). - Drop
'cancelled'from theri_exchange_history_status_checkconstraint. ALTER TABLE purchase_executions DROP COLUMN IF EXISTS cancelled_by;
Ordering matters: normalize/drain (step 1) must run BEFORE narrowing the constraints (steps 2-3) and dropping the column (step 4), or the narrowed constraint rejects surviving 'cancelled' rows / the drop loses un-drained attribution. (Same ordering lesson as 000078's down.sql.)
Pre-conditions before applying
- PR fix(db): rename status value cancelled->canceled (expand-contract, US spelling) cloud-commitments-cli#1277 is deployed to production and verified stable.
- All old code instances are gone (no process still writes
'cancelled'orcancelled_by). This is the gate that makes step 1's normalization complete and final — it cannot be guaranteed during the EXPAND rolling deploy, which is exactly why normalization was deferred to here.
Code cleanup (same PR or follow-up)
Once the data is normalized and the column dropped, remove the EXPAND-window dual-spelling scaffolding added by LeanerCloud/cloud-commitments-cli#1277:
config.LegacyStatusCanceledand every reference to it (history status filter, the cancel/KPI switches, the cancel-recovery check).- The
COALESCE(canceled_by, cancelled_by)in the 10 read projections ininternal/config/store_postgres.go-> plaincanceled_by. - The legacy fixture in the KPI regression test and the file-level
//nolint:misspellon the 000078 integration test.
Down migration
The down migration should:
- Re-add
'cancelled'to both CHECK constraints. ALTER TABLE purchase_executions ADD COLUMN IF NOT EXISTS cancelled_by TEXT;- Backfill
cancelled_byfromcanceled_byfor any rows that need it.
(Order: re-add column/constraints widening before any data move; convertingcanceled->cancelledis optional on the down path since the widened-again constraint accepts both.)
Activity
fix(db): make cancelled->canceled rename truly dual-compatible during…
fix(db): defer status normalization to contract; 5xx on backend cance…
fix(db): patch FE + OpenAPI for canceled status; address CR findings
fix(db): make cancelled->canceled rename truly dual-compatible during…
fix(db): defer status normalization to contract; 5xx on backend cance…
fix(db): patch FE + OpenAPI for canceled status; address CR findings
fix(db): make cancelled->canceled rename truly dual-compatible during…
fix(db): defer status normalization to contract; 5xx on backend cance…
23 remaining items
fix(config): keep tolerant canceled reads
test(config): pin cancel actor reads and writes with pgxmock
test(config): run the new code against the pre-contract schema
test: drop legacy-spelling rows from DB-backed fixtures, fix lint
fix(db): renumber the canceled contract migration to 000159
cristim commentedon Nov 5, 2026
Claimed by claude-cc-platform: driving the contract step through PR #1253 (an IAmJSD takeover on the owner's instruction). It's pushed, ready and in review. #1253 intentionally keeps the tolerant reads, so the final drain, strict reads and column drop are tracked in #1747.
fix(db): renumber the canceled contract migration to 000155
test: derive the pre-contract cutoff from the migration file
test(config): update the pgxmock Save arity after LeanerCloud#1762
test(config): pin that a stale save cannot replace the stored cancel …
docs(known-issues): use the CONTRIBUTING audit line format
fix(db): renumber the canceled contract migration to 000157 after Lea…
fix(db): contract the canceled rename: normalize, narrow the status C…
cristim commentedon Nov 12, 2026
Checked on origin/main 447a98b. Partly met, leaving open.
Met (landed via #1253): status normalized to 'canceled' and both status CHECK constraints narrowed, and writes go to canceled_by only.
Still open (tracked in #1747):
fix(db): drain and drop purchase_executions.cancelled_by, read cancel…