Skip to content

fix(migrate-ts): store long index names as Postgres keeps them in every port - #581

Merged
dmealing merged 9 commits into
mainfrom
fm/mo-main-followups
Nov 14, 2026
Merged

dmealing merged 9 commits into
mainfrom
fm/mo-main-followups

Conversation

@dmealing

@dmealing dmealing commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

What Changed

  • migrate-ts now cuts index names over Postgres's 63-byte limit to the stored name, for declared index.lookup / identity.secondary names, @unique names and derived foreign-key indexes (<table>_<columns>_idx). The diff matches foreign keys by the stored name, so a converged schema proposes nothing on the next run. SQLite and D1 keep the whole name; MySQL is unchanged. Generated Drizzle, EF Core and Exposed mappings declare the same cut name on Postgres, while the names artifacts keep the declared name. The shared fixtures/foreign-key-index-conformance/ corpus gates the foreign-key rule and the declared.<dialect>.json names for TypeScript, C# and Kotlin.
  • The Java verify goal now runs the same model collision refusals as generate (refuseModelCollisions), so a model that generate refuses no longer verifies clean. ADR-0060 records verify.handEdits as a Node CLI setting by design, since only TypeScript merges hand edits forward.
  • Adds the release/<major>.<minor> maintenance-branch rule for patch releases (scripts/lib/release-branch.mjs, used by release.mjs and finish-release.mjs). CHANGELOG, docs/CONFORMANCE.md, the port pages and the migrations page are corrected to match the code.

Also in this PR

  • Forward-ports PR fix(csharp): make frozen metadata cache thread-safe; allow patch releases from release branches #578 (merged into release/1.2) to main: scripts/release.mjs and scripts/finish-release.mjs accept a release/<major>.<minor> maintenance branch and refuse a version older than the newest release, with its docs/RELEASING.md section and tests. docs/RELEASING.md also gains a checklist gate: before a minor is cut from main, every commit on the previous release/<major>.<minor> branch must be an ancestor of main.
  • The site-payload gate (scripts/ci-local.sh) runs bun test with --timeout 60000, so the site payload test no longer times out under load.

Issues

Closes #545
Closes #548

Refs #574: the migrations page now states what the code does. When meta migrate drops and recreates a Postgres view, it grants the view's existing privileges again; those are the only GRANT statements it writes, and it declares no privileges of its own. This PR does not close #574.

The raw evidence logs from validation are left out of this body; the scenario table below summarises them. Validation took two no-mistakes runs: the second added the generator-side cut of long declared index names.

Validation

Risk Assessment

✅ Low: The declared-index-name cut is applied consistently in migrate-ts, the Drizzle template, the C# EF context and the Kotlin Exposed generator, and a shared corpus with per-port runners pins it. I found no concrete failing input.

Testing

I ran the real meta CLI against a disposable Postgres 16 container with a model declaring a 74-byte index.lookup and a 78-byte identity.secondary. meta migrate --apply created both indexes under 63-byte names, and meta verify --db then showed no schema drift. The Drizzle tables generated for the same model declared exactly those 63-byte names on Postgres and the whole names on SQLite. The C# and Kotlin corpus tests, the Java verify-mojo refusal test, the full migrate-ts and codegen-ts bun suites (5074 pass, 0 fail) and scripts/ci-local.sh --only gates --only java-fast --only csharp all passed. The C# integration-test lane was cut off by my 560 s timeout (exit 124) and did not finish, so its result is unknown. The configured baseline (ts-fast, ts-unit) had already passed. The container, scratch project and temp files are removed, and the worktree is clean.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Live Postgres: meta migrate --apply of a 74-byte index.lookup and 78-byte identity.secondary creates them at 63 bytes, and meta verify --db reports no drift ✅ pass live live-postgres-vs-drizzle.txt; meta verify --db returned schemaDrift changes=[] findings=[]
Generated Drizzle table declares the cut 63-byte name on Postgres and the whole name on SQLite, matching the live database ✅ pass live live-postgres-vs-drizzle.txt: the Drizzle postgres names equal the pg_indexes names byte for byte; the sqlite names are 74 and 78 bytes
C# EF Core context declares the same cut names as the shared declared.<dialect>.json corpus ✅ pass live csharp-fkindex.log: 71 passed, 0 failed
Kotlin Exposed table declares the same cut names as the shared corpus (SQLite whole, Postgres cut) ✅ pass live kotlin-fkindex.log and surefire reports: 5 + 4 tests, 0 failures
Adversarial: Java/Kotlin verify mojo refuses a layout collision (package acme::Sales vs acme::sales) the way generate does ✅ pass live mojo-verify.log: MetaDataVerifyModelRefusalTest 2 pass
Regression: full migrate-ts and codegen-ts suites, gates, and csharp, java and kotlin conformance ✅ pass live ts-migrate-codegen.log: 5074 pass, 0 fail; ci-local log: gates and csharp, java and kotlin conformance green

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Live Postgres: meta migrate --apply of a 74-byte index.lookup and 78-byte identity.secondary creates them at 63 bytes, and meta verify --db reports no drift ✅ pass live live-postgres-vs-drizzle.txt; meta verify --db returned schemaDrift changes=[] findings=[]
Generated Drizzle table declares the cut 63-byte name on Postgres and the whole name on SQLite, matching the live database ✅ pass live live-postgres-vs-drizzle.txt: the Drizzle postgres names equal the pg_indexes names byte for byte; the sqlite names are 74 and 78 bytes
C# EF Core context declares the same cut names as the shared declared.<dialect>.json corpus ✅ pass live csharp-fkindex.log: 71 passed, 0 failed
Kotlin Exposed table declares the same cut names as the shared corpus (SQLite whole, Postgres cut) ✅ pass live kotlin-fkindex.log and surefire reports: 5 + 4 tests, 0 failures
Adversarial: Java/Kotlin verify mojo refuses a layout collision (package acme::Sales vs acme::sales) the way generate does ✅ pass live mojo-verify.log: MetaDataVerifyModelRefusalTest 2 pass
Regression: full migrate-ts and codegen-ts suites, gates, and csharp, java and kotlin conformance ✅ pass live ts-migrate-codegen.log: 5074 pass, 0 fail; ci-local log: gates and csharp, java and kotlin conformance green
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • meta migrate --db &lt;postgres&gt; --slug init --apply on a model with 74-byte index.lookup and 78-byte identity.secondary names; psql pg_indexes showed both at 63 bytes
  • meta verify --db &lt;postgres&gt; on the same database: schemaDrift changes=[] and findings=[]
  • Drizzle entityFile generated for the same model, postgres and sqlite dialects, compared with the live index names
  • bun test migrate-ts foreign-key-index-conformance, long-declared-index-name and codegen-ts declared-index-name-cut (12 pass)
  • dotnet test MetaObjects.Codegen.Tests --filter ForeignKeyIndex|EjectedGeneratorsAlone (71 pass)
  • mvn -o -pl codegen-kotlin -am test -Dtest=KotlinForeignKeyIndexConformanceTest,KotlinRouteNamingTest (9 pass)
  • mvn -o -pl maven-plugin -am test -Dtest=MetaDataVerifyModelRefusalTest (2 pass)
  • bun test packages/migrate-ts packages/codegen-ts in server/typescript (5074 pass, 0 fail)
  • scripts/ci-local.sh --only gates --only java-fast --only csharp --strict-toolchains: gates, csharp, java and kotlin conformance green; cut off in the csharp integration lane by my timeout, no failure recorded
  • baseline already run: scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…t generate refuses

- migrate.foreignKeyIndexes: a <table>_<columns>_idx name over 63 bytes is cut
  to the 63 bytes Postgres stores (SQLite and D1 keep it whole), in migrate-ts,
  the C# EF context and the Kotlin Exposed tables, so a converged schema
  proposes nothing. The diff also keys a Postgres foreign key by its stored
  name, so a long <table>_<column>_fk is not proposed as a rename every run.
- New shared corpus fixtures/foreign-key-index-conformance/, passed by the
  TypeScript, C# and Kotlin rules; two Postgres migration scenarios with a
  long table and column name.
- mvn metaobjects:verify runs the generate goal's model refusals (output
  package clash, collection-name clash), with a test; Kotlin gets its own
  collection-name clash test.
- docs/CONFORMANCE.md: the clash is per-port unit tests, not the segments
  corpus; the Drizzle identity start is string-tested only.
- ADR-0060 records verify.handEdits as Node-CLI only by design.
- The migration docs say what the code does with view grants (#574).
…e a minor on the previous one being merged

Forward-ports #578 from release/1.2: release.mjs and finish-release.mjs accept a
maintenance branch for a patch on its own line, refuse a version that would move npm's
latest backwards, and RELEASING documents the path. A minor or major cut is now refused
while the previous release/<M>.<m> holds a commit that is not on main, and RELEASING
Step 0 carries the same check.
# Conflicts:
#	CHANGELOG.md
#	docs/CONFORMANCE.md
… rules; site gate timeout; ts-poet names

- docs/features/api-contract.md owns api.pagination, api.unknownFields and the
  validation issue list; language-differences, the five port pages and the
  CHANGELOG point to it instead of restating them.
- The site-payload gate runs its tests with a 60 s per-test timeout, so a busy
  machine does not read as a red release gate.
- CHANGELOG names def, literalOf, arrayOf, asOneline and Import.fromMaybeString
  among the ts-poet names the vendored printer does not carry.
- site-payload.json counts the new shared foreign-key-index corpus.
…stores it

meta migrate creates and compares a declared index.lookup / identity.secondary name over 63
bytes under the name Postgres stores (its first 63 bytes). The Drizzle tables, the EF Core
context and the Exposed tables now declare that cut name on Postgres (SQLite and D1 keep the
whole name; MySQL is exempt), so the mapping names the index the database holds. The names
artifacts keep the declared, dialect-neutral name.

The shared corpus fixtures/foreign-key-index-conformance/ gains a long index.lookup and
identity.secondary and declared.<dialect>.json, written by migrate-ts and read by the
TypeScript, C# and Kotlin generator tests. C# ForeignKeyIndexes joins the eject engine
surface, as the schema contract CheckConstraints already is.
@dmealing dmealing changed the title fix: cut long foreign-key index names to what Postgres stores; verify refuses what generate refuses fix(migrate-ts): store long index names as Postgres keeps them in every port Nov 14, 2026
@dmealing
dmealing merged commit 059563c into main Nov 14, 2026
9 checks passed
@dmealing
dmealing deleted the fm/mo-main-followups branch November 14, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant