Skip to content

docs(bugs): record #258 — migrate has no primary-key change kind - #261

Merged
dmealing merged 1 commit into
mainfrom
docs/bug-258-pk-change
Aug 2, 2026
Merged

docs(bugs): record #258 — migrate has no primary-key change kind#261
dmealing merged 1 commit into
mainfrom
docs/bug-258-pk-change

Conversation

@dmealing

@dmealing dmealing commented Aug 2, 2026

Copy link
Copy Markdown
Member

Intent

Commit a bug write-up produced this session, as part of getting to a clean slate. While adopting an existing Postgres database into the MetaObjects ledger for a downstream project, two defects in the TypeScript migrate engine were found and filed as GitHub issues. This commit records the second as docs/bugs/2026-08-02-no-primary-key-change-kind.md, matching the existing docs/bugs convention: issue-template frontmatter, kept afterwards as the written record, annotated with the filed issue link (#258).

The defect: meta migrate has no primary-key change kind. TableDescriptor.primaryKey exists so the engine can READ a table's PK, but the Change union has no add-primary-key or drop-primary-key, so when the metadata's identity disagrees with a live database's PRIMARY KEY the diff degrades into an unrelated add-column plus drop-column and the constraint is silently lost. The apply then fails at the foreign keys with 'there is no unique constraint matching given keys for referenced table'. Only observable when adopting an existing database via baseline --from-db, because a greenfield create-table carries its PK inline.

The document has the root cause with the STAGE_ORDER excerpt, suggested staging invariants (drop-fk < drop-primary-key, add-column < add-primary-key < add-fk), a note that src/emit/sqlite.ts needs the same kinds, a synthetic two-table reproduction, and an explicit alternative for the maintainer: detect and refuse a PK change rather than emit SQL that cannot apply. Found immediately behind issue #255, fixed in 0.20.10 this same session.

IMPORTANT — two prior attempts and their resolutions, so they are not re-litigated:

  1. A first run failed at push because the pre-push build gate ran in a git worktree with no dependencies installed, producing spurious 'Cannot find type definition file for bun-types' errors across every TypeScript package. Environment artifact, not a defect. bun install has been run (the repo uses bun, not pnpm) and 'bun run build' now completes cleanly for all packages.
  2. A second run reached the review gate, which raised one ask-user finding: the 'What happened' narrative (lines 28-32) carries concrete table/column names (user_profiles, agent_configs, auth_user_id, created_by) in a commit destined for a public repo. That was escalated to the repository owner, who reviewed it and decided to APPROVE AS-IS. His reasoning: those names are already public in issue migrate: no primary-key change kind, so moving a table's PK leaves it with none and every referencing FK fails #258 which he authorised, they are generic SaaS table names that identify nothing about the product, and genericising them here would make the document disagree with the issue it links to while costing the maintainer the concrete detail that makes the bug reproducible. That run was then cancelled externally before the approval could land. This is a settled decision, not an oversight.

This is documentation-only: it touches no source, no metadata, no generated code.

What Changed

  • Add docs/bugs/2026-08-02-no-primary-key-change-kind.md, recording migrate-engine defect migrate: no primary-key change kind, so moving a table's PK leaves it with none and every referencing FK fails #258: the Change union exposes no primary-key change kind, so when the metadata's identity disagrees with a live database's PRIMARY KEY the diff degrades into an unrelated add-column + drop-column and the constraint is silently lost (apply then fails at foreign keys with there is no unique constraint matching given keys for referenced table). Only observable when adopting an existing DB via baseline --from-db, since greenfield CREATE TABLE carries its PK inline.
  • The write-up follows the established docs/bugs/ convention — issue-template frontmatter, retained as the permanent record, annotated with the filed issue link (migrate: no primary-key change kind, so moving a table's PK leaves it with none and every referencing FK fails #258) — and includes the root-cause STAGE_ORDER excerpt, suggested staging invariants, a note that the SQLite emitter needs the same kinds, a synthetic two-table reproduction, and a detect-and-refuse alternative for the maintainer.

Risk Assessment

✅ Low: Documentation-only addition of one bug write-up; all source citations verified accurate against migrate-ts (types.ts:35, the Change union, STAGE_ORDER's 18 keys, and the absence of any primaryKey comparison in diff/), it matches the existing docs/bugs convention, and the only potential public-hygiene concern (concrete table names) is explicitly owner-approved per the binding intent.

Testing

Validated the bug-#258 doc commit end-to-end: it is strictly documentation-only (one .md file, +126 lines, zero source/metadata/generated-code changes), follows the existing docs/bugs convention with valid issue-template frontmatter, and is annotated with the #258 issue link; crucially, every code claim the bug write-up makes (TableDescriptor.primaryKey at types.ts:35, the exhaustive STAGE_ORDER in postgres.ts, the absence of any add/drop/change-primary-key kind across both emitters, and sqlite.ts needing the same) was verified accurate against the live migrate-ts source. No defects found.

Evidence: Bug-#258 doc verification transcript

Doc-only commit (+126 lines, single .md); frontmatter parses as valid YAML matching the docs/bugs convention; annotated with issues/258 link. Code claims verified: TableDescriptor.primaryKey at types.ts:35 (exact), STAGE_ORDER in postgres.ts is an exhaustive Record<Change["kind"],number> whose 19 keys match the doc with no PK kind, grep confirms zero add/drop/change-primary-key kinds anywhere in migrate-ts, and sqlite.ts exists and also lacks PK kinds. Conclusion: intent satisfied, all claims accurate.

# Bug #258 doc commit — verification transcript

Branch `docs/bug-258-pk-change`, target commit `80015852`.
Commit: `docs(bugs): record #258 — migrate has no primary-key change kind`.

## 1. Commit is documentation-only (intent constraint)

`git diff --stat <base> <target>`:
`` `
 docs/bugs/2026-08-02-no-primary-key-change-kind.md | 126 +++++++++++++++++++++
 1 file changed, 126 insertions(+)
`` `
No source, no metadata, no generated code touched. Matches the intent's
"documentation-only" required constraint exactly.

## 2. Matches existing docs/bugs convention

Frontmatter parses as valid YAML and is byte-identical in shape to the prior
entry `2026-06-28-projection-aggregate-view-ddl.md`:
`` `
{'name': 'Bug report', 'about': 'Report a defect',
 'title': "migrate: no primary-key change kind, so moving a table's PK …",
 'labels': 'bug'}
`` `
Same section skeleton (What happened / What you expected / Root cause /
Reproduction / Environment). The status quote block is annotated with the
filed issue link as required:
`> **Filed as** https://github.com/metaobjectsdev/metaobjects/issues/258 (2026-08-02). Open. Follow-on from #255.`

Structure: 14 `` ` fences (7 balanced blocks), 7 `##` headers, #258 link present.

## 3. Technical claims verified against the actual codebase

A bug write-up's value is accuracy; every code claim in the doc was checked.

**Claim:** `TableDescriptor.primaryKey` exists at
`server/typescript/packages/migrate-ts/src/types.ts:35`.
`` `
  primaryKey: string[];              // column names; [] if none
`` `
Confirmed: exact line, exact comment. The engine can READ a table's PK.

**Claim:** `STAGE_ORDER` in `src/emit/postgres.ts` is an exhaustive
`Record<Change["kind"], number>` and its keys are exactly the 19 listed in the
doc, with NO primary-key kind.
`` `
const STAGE_ORDER: Record<Change["kind"], number> = {
  "drop-view": 0,
  "drop-fk": 1, "drop-check": 1,
  "create-table": 1,
  "drop-index": 1.5,
  "add-column": 2, "drop-column": 2,
  "change-column-type": 2, "change-column-nullable": 2, "change-column-default": 2,
  "rename-column": 3, "rename-table": 3,
  "add-index": 4,
  "add-fk": 5,
  "add-check": 5,
  "drop-table": 6,
  "create-view": 7, "replace-view": 7,
};
`` `
Doc's listed kind set is byte-faithful (same 19, no additions, no omissions).

**Claim:** no `add-primary-key` / `drop-primary-key` / `change-primary-key`
exists anywhere. `grep -rn` over `migrate-ts/src` → "(none found)". The `Change`
union (`types.ts:212+`) confirms the kinds are only table/column/index/fk/check/
view operations — no PK change kind.

**Claim:** `src/emit/sqlite.ts` needs the same kinds. `sqlite.ts` exists
(19.7 KB), carries its own `STAGE_ORDER`/`Record<Change` (matches the doc's
"shared engine" framing), and likewise has no PK kinds — consistent with the
doc's note that the SQLite emitter needs the recreate-and-copy path for a PK
change.

## Conclusion

The commit satisfies the intent: a documentation-only record of bug #258,
matching the docs/bugs convention, annotated with the filed issue link, and —
critically for a bug write-up — every code-level claim it makes is accurate
against the live source.

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.

  • git show --stat 80015852 — confirms commit changes exactly one .md file (+126)
  • git diff --stat c2dce37e 80015852 — confirms the branch-vs-base delta is doc-only (no source/metadata/generated code)
  • compared new doc frontmatter + section structure against existing docs/bugs/2026-06-28-projection-aggregate-view-ddl.md
  • python3 yaml.safe_load of the doc frontmatter — parses cleanly with issue-template shape
  • grep '^```' count (14, balanced) and '#258'/'issues/258' link presence
  • sed/grep src/types.ts:35 — verifies TableDescriptor.primaryKey: string[] claim
  • grep + awk STAGE_ORDER block in src/emit/postgres.ts — verifies exhaustive Record<Change["kind"],number> and that its 19 keys match the doc, with no primary-key kind
  • grep -rn 'add-primary-key|drop-primary-key|change-primary-key|addPrimaryKey|dropPrimaryKey' over migrate-ts/src — none found (confirms the documented gap)
  • grep Change union kinds in src/types.ts — confirms no PK change kind in the Change type
  • ls + grep src/emit/sqlite.ts — confirms the SQLite emitter exists and also has no PK kinds (consistent with the doc's note)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Adopting an existing Postgres database whose primary key differs from the
metadata, the apply fails at the foreign keys with "there is no unique
constraint matching given keys for referenced table". The table model reads a
PK (TableDescriptor.primaryKey) but the Change union has no add-primary-key or
drop-primary-key, so a key move degrades into an unrelated add-column plus
drop-column and the constraint is simply lost.

Only observable when adopting an existing database — a greenfield create-table
carries its PK inline, which is why it had not surfaced before.

Written up alongside the existing docs/bugs entries: root cause with the
STAGE_ORDER excerpt, a suggested staging (drop-fk < drop-primary-key,
add-column < add-primary-key < add-fk), the note that the SQLite emitter needs
the same kinds, a synthetic two-table reproduction, and the alternative of
detecting and refusing a PK change rather than emitting SQL that cannot apply.

Found immediately behind #255, which 0.20.10 fixed — the apply now clears the
column drops and fails at the FK stage instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6J7r8cEzA7ApfJD3CZt2L
@dmealing
dmealing merged commit 0aacb68 into main Aug 2, 2026
1 check passed
@dmealing
dmealing deleted the docs/bug-258-pk-change branch August 2, 2026 14:42
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