migration-ready-schema — Claude Code skill: setup and usage
Defines data model guidelines for idempotent data imports and clean rollbacks. Useful when drafting architecture contracts in ARCH-{slug}.md.
npx skills add avelikiy/great_cto --skill migration-ready-schema -a claude-codeWhat migration-ready-schema does
This skill inspects database schema designs before code generation starts. It enforces two metadata columns on importable tables: a nullable source_ref with a UNIQUE constraint using {source}:{type}:{id} for deduplication, plus a nullable import_batch_id UUID. Without import_batch_id, rolling back a corrupted batch requires manual surgery. Imports must remain reversible.
The second requirement turns real-world actors into separate tables. Storing contact details inline inside an order leaves no target row when external systems push contacts first. The skill also enforces integers for monetary values (*_cents), time-zone awareness on date fields, and dedicated tracking tables named outbound_message and processed_webhook_events.
These checks apply to files within docs/architecture/** and docs/data-import/**. Ephemeral caches and materialized views are explicitly excluded from these rules.
What it helps with
- enforce idempotent database schema designs before writing migration scripts
- add batch tracking columns to enable clean rollbacks after failed imports
- split inline contact attributes into dedicated relational entities
- prevent float precision errors on financial amounts and dates
Example prompts for the agent
- Review the schema in
docs/architecture/ARCH-billing.mdagainstmigration-ready-schemarules. - Add
source_refandimport_batch_idcolumns to the new inventory table. - Check if our customer contact tables follow the migration-ready entity guidelines.
How to install migration-ready-schema
1Skills CLI (Vercel)
npx skills add avelikiy/great_cto --skill migration-ready-schema -a claude-code2Manually: put the files in place
~/.claude/skills/migration-ready-schema/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/migration-ready-schema/
1Skills CLI (Vercel)
npx skills add avelikiy/great_cto --skill migration-ready-schema -a codex2Manually: put the files in place
.agents/skills/migration-ready-schema/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add avelikiy/great_cto --skill migration-ready-schema -a cursor2Manually: put the files in place
.cursor/skills/migration-ready-schema/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install avelikiy/great_cto migration-ready-schema2Manually: put the files in place
.github/skills/migration-ready-schema/SKILL.md1Skills CLI (Vercel)
npx skills add avelikiy/great_cto --skill migration-ready-schema -a gemini-cli2Platform CLI
gemini skills install https://github.com/avelikiy/great_cto --path skills/migration-ready-schemaManually: put the files in place
.devin/skills/migration-ready-schema/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add avelikiy/great_cto --skill migration-ready-schema -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about migration-ready-schema
How to install migration-ready-schema in Claude Code?
Run npx skills add avelikiy/great_cto --skill migration-ready-schema -a claude-code. The skill file is placed in ~/.claude/skills/migration-ready-schema/SKILL.md.
Why are source_ref and import_batch_id required on importable tables?
source_ref holds the origin identifier so repeat syncs do not duplicate records, while import_batch_id groups rows from a single run for clean rollbacks.
Which tables should skip these schema requirements?
The rules do not apply to transient data stores, caches, or materialized views that never receive direct imports.
Source text
SKILL.md · MIT
---
name: migration-ready-schema
description: Data-model rules that make a schema importable from day one, so the migration-import-engineer is never blocked on missing columns. Every SMB Product-Builder product must let a customer bring their data from an incumbent (ServiceTitan/Toast/Mindbody/Shopify) — that requires provenance (source_ref) and rollback (import_batch_id) on importable entities, and modelling real-world actors as entities rather than inline fields. Applied by architect when writing the data model in ARCH-{slug}.md, and checked by migration-import-engineer. One cheap rule set prevents the migration↔architecture seam gap from recurring across all 40 products.
when_to_use: |
Apply when:
- architect writes the Data contracts / data model section of ARCH-{slug}.md
- migration-import-engineer verifies the destination schema can satisfy its
idempotency + rollback invariants
- any new entity is added that could be populated from an import or a third-party sync
Do NOT apply to purely ephemeral / derived tables (caches, materialized views) that are
never imported into.
effort: low
allowed-tools: Read, Write, Grep, Glob
paths:
- "docs/architecture/**"
- "docs/data-import/**"
---
# Migration-ready schema — importable from day one
Every SMB product's wedge is **low switching cost** — the customer brings their data from
the incumbent. If the schema can't receive that data idempotently and reversibly, the
`migration-import-engineer` blocks and the wedge is a slogan. These rules are cheap at
design time and impossible to retrofit cleanly. **Apply them to the data model in ARCH,
before any import is designed.**
## The three rules (the ones that actually block imports)
### 1. Importable entities carry `source_ref` (provenance)
Any entity that can be populated from an incumbent export or a third-party sync gets a
nullable, **unique** `source_ref` column:
```
source_ref text UNIQUE NULL -- e.g. "servicetitan:pricebook:8842"
```
- It is the **dedup key** for idempotent re-import (re-running an export never duplicates).
- Namespaced `{source}:{type}:{id}` so two incumbents can't collide.
- Nullable because natively-created rows have no source.
### 2. Importable entities carry `import_batch_id` (rollback)
```
import_batch_id uuid NULL -- tags every row written by one import run
```
Rollback = delete where `import_batch_id = ?`. Without it there is no undo, and an import
with no undo is one a user will never trust enough to run.
### 3. Model real-world actors as entities, not inline fields
A customer, contact, vendor, member, or tenant is an **entity with its own table**, even if
v1 only stores a name + phone. Reason: imports populate these **before** the dependent
records (quotes, orders, bookings) exist — an inline `customer_name` field on `Quote` has
**nowhere to land** an imported customer. Inline-actor is the single most common migration
blocker.
```
-- WRONG (blocks import): Quote(... customer_contact text)
-- RIGHT: CustomerContact(id, ..., source_ref, import_batch_id)
Quote(... customer_contact_id → CustomerContact.id)
```
## Supporting rules (cheap, prevent silent data loss)
- **Money in integer minor units** (`*_cents`), never float — incumbent exports carry exact
amounts; floats corrupt them.
- **Timestamps carry the source timezone** (store UTC + offset, or tz-aware) — exports use
local times; ambiguous `MM/DD` and naive datetimes lose data.
- **Infra tables for integration bookkeeping exist in the model**: `outbound_message`
(idempotent send guard), `processed_webhook_events` (event dedup). The
integrations-engineer relies on them; enumerate them so they aren't a surprise.
## Checklist (architect runs this before finalizing the data model)
```
For each entity in the data model:
- [ ] Can it be imported from an incumbent or third-party? If yes → has source_ref + import_batch_id
- [ ] Is it a real-world actor (customer/contact/vendor/member)? If yes → it is its own entity, not an inline field
- [ ] Money fields integer minor units? Timestamps tz-aware?
- [ ] Listed the infra tables (outbound_message, processed_webhook_events) the integrations layer needs?
```
A data model that passes this checklist hands `migration-import-engineer` a destination it
can import into idempotently and reversibly — no blocked seam.
© avelikiy · MIT · avelikiy/great_cto