Skip to content

schema-migration skill: answer apso migrate's questions (plan flow), plus the 10-22 skill fixes and eval hook - #18

Merged
cultron merged 13 commits into
mainfrom
feat/migration-plan-skill
Oct 28, 2026
Merged

cultron merged 13 commits into
mainfrom
feat/migration-plan-skill

Conversation

@cultron

@cultron cultron commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Merge after @apso/cli 0.44.0 is published (the migration check runs npx @apso/cli@latest migrate).

schema-migration (new skill). The agent runs apso migrate, answers its questions (rename or drop, split, enum mapping, backfill, cast, relation) in .apso/migration-plan.json or with --answer, and lets the CLI write the migration. It asks the user when intent is unclear, never answers drop on its own, and verifies with apso migrate verify --seed.

8 migration evals. Each one hands the agent the real apso migrate question output and plan file for its case. scripts/migration-check renders the migration from the agent's answers with the CLI and runs it against seeded rows. A wrong answer fails: rename-field answered drop gives 'existing phone values kept in mobilePhone' failed. All 8 passed in the pre-commit run against a local CLI build; type-change passed on its retry.

Also in this branch, never merged before: the 10-22 skill fixes (field types, relationship sides, index every foreign key, schema-review eval case) and the pre-commit eval hook with scripts/migration-check. Conflicts with #16 were resolved in favour of these newer versions.

cultron and others added 13 commits August 8, 2026 19:11
…chema-designer composes them

DRAFT for review — do not publish until content-reviewed (public repo).
- references/data-modeling-principles.md: ported DB-engineering rigor (3NF, event-vs-counter, indexing/Big-O, review checklist), in correct .apsorc v2 format.
- skills/schema-review/SKILL.md (category: data): audit/harden an existing schema; composes the principles.
- schema-designer: references the principles + links schema-review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…EADME

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ data-modeling-principles)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ths; add relational patterns

- Field types: varchar, string, timestamp and uuid are valid; moments in time
  (*At) use timestamptz instead of date.
- Relationships are declared once (ManyToOne from the child). The CLI generates
  the inverse side and does not dedupe, so the OneToMany twins created duplicate
  foreign keys. Self-references are one nullable ManyToOne.
- file-storage and tagging-taxonomy default to real foreign keys instead of
  entityType + entityId.
- Skills cite references via ${CLAUDE_PLUGIN_ROOT}; the bare relative path
  resolved inside the skill folder and every read failed.
- New references/relational-patterns.md: Postgres design, .apsorc encoding and
  missing guarantee per pattern, plus an anti-pattern table. Loaded by
  schema-designer and schema-review.
- Evals: all cases use case.yaml with a refs symlink + add_dirs so skills can
  read references; layered design/apsorc graders; CLI validator and
  duplicate-side gate in the pre-commit hook; new schema-designer-invoicing
  case. Release guard ignores evals/.
…-model requests

- data-modeling-principles claimed Apso indexes foreign keys from the
  relationship. It does not (apso schema validate warns FOREIGN_KEY_NOT_INDEXED).
  The rule is now "index: true on every ManyToOne", and 60 ManyToOne examples
  across the skills and references carry it.
- schema-designer validates with `apso schema validate --local` when the CLI is
  available, and its description triggers on "design the data model" requests
  that also match a domain skill.
- New eval cases for comments/reactions, plan pricing (effective dating) and
  legal parties (supertype/subtype); every designer case checks FK indexes.
  Plain Claude passes the design graders but leaves FKs unindexed in 9 of 10
  runs; with the plugin 0 of 10.
Plants duplicate relationship sides, a required self-reference and unindexed
foreign keys next to general Postgres mistakes. With the plugin the review
flags all three Apso traps in 3/3 runs; plain Claude misses them in 3/3 while
catching the general ones (score 1.0 vs 0.5).
Verified against @apso/cli 0.43: getRelationshipForTemplate dedupes a plain
OneToMany + ManyToOne pair, so it does not duplicate the foreign key as
f669d62 said. The failure is a pair with different names (a to_name on the
OneToMany), which gives the parent two collections over one foreign key and
which the linter misses. "Declare once" stays (the docs say the same); the
stated reason, the review grader and the catalog's CLI gap are corrected. A
self-referencing OneToMany does add a second, required foreign key; that text
was right and is unchanged.
A schema change reaches a deployed service's database only through a
committed migration file (src/migrations/*-migration.ts for TypeScript);
the CLI writes none, apso migrate only checks DDL on an empty database,
and auto-generated SQL turns renames and type changes into drops. The
new schema-migration skill writes the .apsorc change plus a hand-written
migration with recipes for renames, required fields, type changes, enum
swaps, unique constraints and drops, and explains expand/contract since
the database changes before the new Lambda goes live.

Five eval cases (rename field, rename entity, add required field, type
change, enum replace). scripts/migration-check runs the agent's
migration against seeded PGlite, checks the rows, and checks that the
migrated database matches DDL generated from the final .apsorc. The
pre-commit hook runs it for every case with a migration-check/ dir.
Measured over 2 runs per case: plugin 10/10, plain Claude 0/10 (its
files lack the -migration.ts suffix, so the deploy would skip them).

The hook now retries a failing case once. Three full runs each failed
one different LLM-judged case on a correct answer (enum-replace,
custom-endpoints-override, schema-review-encoding); a real regression
fails both times.

Corrects deployment, api-builder and the schema guide, which said
apso migrate --apply applies a migration and that deploy detects
schema changes. Clarifies the flaky custom-endpoints-override case
(1/3 before, 4/4 after): files must be written, and the judge's
criteria are stated as separate, checkable points.
…seeded rows

The skill no longer hand-writes migrations. apso migrate generate (CLI 0.43)
produces TypeORM's migration from the deployed schema; the skill reviews it
for the statements TypeORM gets wrong on populated tables (type change as
DROP + ADD, entity rename as a new empty table, enum swaps that fail on
removed values, NOT NULL adds and default backfills), patches only those,
and verifies with apso migrate verify --seed. Eval prompts now hand the
agent TypeORM's real output for each case. Fixes the earlier claim that the
CLI writes no TypeScript migrations.
New eval cases hand the agent TypeORM's real output for three data moves,
each of which loses or fails on seeded rows as generated:
- extract-entity: Contact.phone to a PhoneNumber entity (phone dropped, table empty)
- many-to-many: Contact to Company ManyToOne to ManyToMany (links dropped)
- split-field: fullName to firstName/lastName (NOT NULL add fails on rows)
The seeded checks pass on the patched migrations and fail on the raw ones.

The skill gains a Moving data section and the verify --check and per-column
--accept-loss flags.
… TypeORM output

apso migrate (CLI 0.44) plans the change, asks about anything ambiguous
(rename or drop, split, enum mapping, backfill, cast, relation) and writes the
migration from the answers. The skill now answers those questions and verifies,
and never hand-writes SQL beyond answer expressions.

The 8 migration evals hand the agent apso migrate's real question output and
plan file; the agent writes the answered plan. migration-check renders the
migration from those answers with the CLI (APSO_CLI_BIN for an unreleased
build), runs it against the seeded rows, and calls apso migrate sandbox --sql
for the convergence DDL. allow-refusal also accepts a strict cast that rejects
a value (22P02, 22003). A plan-answer grader replaces the TypeORM class name
grader.
…kill

# Conflicts:
#	plugins/apso/README.md
#	plugins/apso/references/architecture/taxonomy.md
#	plugins/apso/references/data-modeling-principles.md
#	plugins/apso/skills/schema-designer/SKILL.md
#	plugins/apso/skills/schema-review/SKILL.md
@cultron
cultron merged commit b81290c into main Oct 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant