migrate-orders-to-canonical-schema — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited migrate-orders-to-canonical-schema (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
What it is: The safe migration procedure for moving an orders table from Stripe-specific columns to provider-neutral columns. Mental model: Add the new shape beside the old one, backfill and observe, then remove the old shape only after application reads have moved. Why it exists: Billing tables sit under live traffic, so schema changes need compatibility windows and explicit validation. What it is NOT: It is not generic data modeling, a new provider router, or the steady-state RLS query pattern. Adjacent concepts: Expand-and-contract migrations, backfills, idempotency checks, RLS policy updates. One-line analogy: It is changing the rails under a moving train by laying the new track beside the old track first. Common misconception: A database rename is atomic enough by itself; application deploy timing makes old and new column reads coexist.
stripe_session_id → provider_order_id, stripe_customer_id → provider_customer_id, with a new provider column set to 'stripe' for existing rowsorders_org_select policy must be updated in the same migration that renames the columns if the policy references them (it does not in this case, but the checklist step prevents future drift)stripe_session_id and stripe_customer_id in the codebase to find every reference that must be updated before the old columns are droppedscripts/migrate-orders.ts runs in --dry-run by default, printing the diff without committing; --apply is the explicit opt-inA column rename under live traffic is a non-trivial operation even on a small table. The temptation to write one migration that renames columns atomically and ships them fails because application code reads the old column names until the new application version deploys, and the deployment window is not instant. The four-phase procedure exists because the new column can be null during the window, the application can write both, and the old column can be dropped only after the new application version has been running for a full observation period with zero reads of the old column name in logs.
| Phase | Precondition | Action | Success criterion |
|---|---|---|---|
| 1. Add nullable columns | Production schema has no provider or provider_order_id | Add provider TEXT, provider_order_id TEXT, provider_customer_id TEXT as nullable | Build passes; existing rows unaffected |
| 2. Backfill | Phase 1 deployed | UPDATE orders SET provider = 'stripe', provider_order_id = stripe_session_id, provider_customer_id = stripe_customer_id WHERE provider IS NULL | SELECT COUNT(*) FROM orders WHERE provider IS NULL returns 0 |
| 3. Validate and update application | Phase 2 complete | Search codebase for stripe_session_id and stripe_customer_id references; update to provider_order_id / provider_customer_id; deploy the updated application | Zero reads of old column names in production logs for 24 hours |
| 4. Drop legacy columns | Phase 3 observation period complete | ALTER TABLE orders DROP COLUMN stripe_session_id, DROP COLUMN stripe_customer_id | Schema matches db/schema.sql; npm run verify passes |
-- Phase 1: Add nullable canonical columns
ALTER TABLE orders
ADD COLUMN IF NOT EXISTS provider TEXT,
ADD COLUMN IF NOT EXISTS provider_order_id TEXT,
ADD COLUMN IF NOT EXISTS provider_customer_id TEXT;
-- Phase 2: Backfill from Stripe-specific columns
UPDATE orders
SET
provider = 'stripe',
provider_order_id = stripe_session_id,
provider_customer_id = stripe_customer_id
WHERE provider IS NULL;
-- Phase 4 (only after Phase 3 observation period):
-- ALTER TABLE orders
-- DROP COLUMN stripe_session_id,
-- DROP COLUMN stripe_customer_id;--dry-run first; the dry-run output is committed under db/migrations/0004-dry-run.logSELECT COUNT(*) FROM orders WHERE provider IS NULL returns 0 before Phase 3 beginsstripe_session_id and stripe_customer_id| Use instead | When |
|---|---|
postgres-rls-pattern | The task is the ongoing RLS access pattern, not the one-time migration |
| (a fresh migration skill) | The task is a different migration with no relation to the 0004 orders canonicalization |
payment-provider-router | The task is updating the router to use provider_order_id after the migration |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.