Skip to content

Every migration in packages/metadata/src/migrations/ requires driver.raw(...), a method no driver in this repo defines — the drivers expose execute(), so the documented cut-over call is a no-op that reports error #14023

Description

@zhuangjianguo

Found while implementing the timestamp-canonicalisation fix in migrate-sys-notification-to-event.ts (#13998). Filed separately rather than folded in: different defect class, and it needs its own decision about the driver surface these helpers accept. That card is not addressed by this one.

The mismatch

migrateSysNotificationToEvent guards, then drives, on driver.raw:

if (typeof driver?.raw !== 'function') {
    return { status: 'error', migrated: 0,
        error: 'migrateSysNotificationToEvent: driver must expose a .raw(sql, bindings?) method.' };
}

No data driver in this repo defines a raw method. Measured, with a firing positive control on the same expression:

$ grep -rnE '^\s*(public |protected |private )?(async )?raw\s*\(' packages/*/src packages/*/*/src --include=*.ts | grep -v '\.test\.ts'
packages/verify/src/harness.ts:84:  raw(path: string, init?: RequestInit): Promise<Response>;     # an HTTP harness, not a data driver

$ grep -rnE '^\s*(public |protected |private )?(async )?execute\s*\(' packages/drivers/*/src/*.ts | grep -v '\.test\.ts'
packages/drivers/driver-memory/src/memory-driver.ts:547
packages/drivers/driver-mongodb/src/mongodb-driver.ts:241
packages/drivers/driver-sql/src/sql-driver.ts:7956
packages/drivers/driver-turso/src/remote-transport.ts:1180

SqlDriver implements IDataDriver (sql-driver.ts:4081) and the contract itself declares execute?(command, options) (packages/spec/src/contracts/data-engine.ts:293) — there is no raw on it.

Why it matters

docs/handoff/adr-0030-notification-convergence.md gives operators this cut-over step, verbatim:

  1. Run migrateSysNotificationToEvent({ driver, data }) to carry existing notifications into sys_inbox_message + receipts.

An operator who passes their platform driver — the only driver that sentence can mean — gets { status: 'error', migrated: 0 } back and nothing happens. The migration is documented as the supported way to preserve users' existing bell notifications across the ADR-0030 cut-over, and it is published from @objectstack/metadata/migrations.

The failure is quiet in the shape that matters: status: 'error' is a returned value, not a throw, and the message names a driver method rather than saying the migration did not run.

Scope: the whole directory, not one file

All four members take driver.raw and none accepts execute:

  • migrate-sys-notification-to-event.ts — guard at :74, calls at :157, :174, :191, :203
  • migrate-env-id-to-project-id.ts — :90, :98 (doc at :39 states the requirement)
  • migrate-project-id-to-environment-id.ts — :170, :176 (doc at :110)
  • drop-projection-tables.ts — doc at :39 states the same requirement

The repo already has the correct shape, one package over

packages/metadata-protocol/src/migrations/ resolves both surfaces rather than assuming one:

// partial-index-probe.ts:94
if (typeof driver.raw === 'function') return (sql: string) => driver.raw(sql);
return (sql: string) => driver.execute(sql);

// seed-tenancy-backfill.ts:347
if (typeof driver.execute === 'function') {
  return { exec: (sql, params) => driver.execute(sql, params ?? []), client, ledger };
}
return { exec: (sql) => driver.raw(sql), client, ledger };

Adopting that resolver in the packages/metadata family is the obvious repair, but it is a decision about which surface these published helpers promise, so it is worth making deliberately rather than as a rider.

Why the tests do not catch it

Every case in migrate-sys-notification-to-event.test.ts builds its own double with a raw method — including the one that asserts the guard fires (errors cleanly when the driver has no raw()). The suite therefore pins the guard's message while never once exercising a driver the repo actually ships. The same is true of the sibling migrations' suites.

A repair should pin the real surface: drive at least one case through a driver type this repo defines, rather than through a double shaped to the helper's own assumption.

Re-run

grep -rn 'driver\.raw' packages/metadata/src/migrations/
grep -rnE '^\s*(public |protected |private )?(async )?raw\s*\(' packages/*/src packages/*/*/src --include=*.ts | grep -v '\.test\.ts'

Backlink: #13998 (the timestamp defect in the same file, where this was found). #13998 is not addressed here and stays open on its own terms.

Generated by Claude Code

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions