fix(observation): migrate legacy person_observations.title -> titles[] on startup#199
Closed
sroussey wants to merge 1 commit into
Closed
fix(observation): migrate legacy person_observations.title -> titles[] on startup#199sroussey wants to merge 1 commit into
sroussey wants to merge 1 commit into
Conversation
…] on startup
The `person_observations.title` (TEXT) column was renamed in place to
`titles` (JSON-encoded array on SQLite; JSONB on Postgres) with no
ALTER TABLE and no version-registry migration. Operators upgrading from
before that change would hit "column titles does not exist" on the next
`observePerson` write, or keep writing while the older `title` data was
orphaned.
Adds a one-shot idempotent startup migration that runs right before
`PERSON_OBSERVATION_REPOSITORY_TOKEN.setupDatabase()`:
- SQLite: adds `titles TEXT` if missing, backfills each non-null `title`
as `json_array(title)` into rows whose `titles` is still null, then
drops `title`.
- Postgres: adds `titles JSONB` if missing, backfills via
`jsonb_build_array("title")` under the same null-target guard, then
drops `title`. (JSONB matches what the ORM emits — the array-element
whitelist in `mapPostgresType` excludes VARCHAR, so an array-of-
VARCHAR schema falls through to JSONB rather than a native array.)
Both branches wrap the writes in a single transaction and probe the
schema first, so the migration is a no-op on a fresh DB and on an
already-migrated DB. Dry-run mode short-circuits before touching the
database.
Verification: `bun test src/storage/observation/`,
`bun test src/storage/form-8k-event/Form8KEventLegacyMigration.test.ts`,
and `bun run build` all green. Postgres coverage is deferred to manual
verification (mirrors the Form-8K migration precedent).
Operator note: silent on a fresh database; emits one info line per
backend when a real migration runs (column added, or rows backfilled).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The recent rename of
person_observations.title(TEXT) totitles(JSON-encoded array on SQLite, JSONB on Postgres) happened in place with noALTER TABLEand no version-registry migration. Any operator with a pre-existing database would:column titles does not existon the nextobservePersonwrite, ortitledata was orphaned.This PR adds a one-shot idempotent startup migration invoked immediately before
PERSON_OBSERVATION_REPOSITORY_TOKEN.setupDatabase(), mirroring the shape and dispatch ofmigrateLegacyForm8KEventsTable.Files
src/storage/observation/PersonObservationTitleMigration.ts— dual-backend one-shot migration.sqlite_master/PRAGMA table_info; iftitlesis absent addstitles TEXT; iftitleis present backfillstitles = json_array(title)on rows whosetitlesis still null, then dropstitle. Wrapped in a singleBEGIN/COMMIT, withROLLBACKon throw.information_schema; runsADD COLUMN IF NOT EXISTS "titles" JSONB, backfills viajsonb_build_array("title")under the same null-target guard, thenDROP COLUMN IF EXISTS "title". All in one transaction. JSONB matches what the ORM actually emits — the array-element whitelist inmapPostgresTypeexplicitly excludes VARCHAR, soType.Array(Type.String({maxLength:256}))falls through toJSONBrather than a nativeVARCHAR(256)[].src/config/setupAllDatabases.ts— import the migration and call it directly beforePERSON_OBSERVATION_REPOSITORY_TOKEN.setupDatabase().src/storage/observation/PersonObservationTitleMigration.test.ts— SQLite vitest coverage:titlecolumn and three seeded rows ('CEO','Chief Executive Officer and Director',NULL) — after migration,titlesexists,titlegone, values are["CEO"],["Chief Executive Officer and Director"],null.titlesvalue preserved.title='CEO', titles=NULLbecomes["CEO"]; row withtitle='Old', titles='["New"]'keeps["New"];titlecolumn dropped.Test plan
bun test src/storage/observation/PersonObservationTitleMigration.test.ts— 4/4 passing.bun test src/storage/observation/— 24/24 passing across 5 files.bun test src/storage/form-8k-event/Form8KEventLegacyMigration.test.ts— 3/3 passing (regression on shared reset-DI dance).bun run build— clean build (bundle +tsc).Notes
harnessand will pick up this migration automatically on their next merge from base — no follow-up needed there.Generated by Claude Code