Skip to content

Commit ffc0e5b

Browse files
committed
fix(cli): os migrate account-issuer reads sys_account through its driver, not the engine
The read-only boot composes no auth plugin, so sys_account is not a registered object there, and the engine now refuses a name its registry does not resolve before any driver is asked. The pre-flight inventories the physical table in its legacy shape, so it reads through the driver the engine routes that name to; the missing-table refusal of that read is still the only one recognised as no rows. Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
1 parent b5cec3d commit ffc0e5b

1 file changed

Lines changed: 38 additions & 11 deletions

File tree

‎packages/cli/src/commands/migrate/account-issuer.ts‎

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import {
1717
} from '../../utils/format.js';
1818
import { bootSchemaStack } from '../../utils/schema-migrate.js';
1919

20+
/** The table this pre-flight inventories. Never written. */
21+
const SYS_ACCOUNT = 'sys_account';
22+
23+
/** The driver read this pre-flight issues: `find` only, read-only by construction. */
24+
interface AccountTableDriver {
25+
find(object: string, query: Record<string, unknown>): Promise<unknown>;
26+
}
27+
2028
/**
2129
* `os migrate account-issuer` — the PLAN leg of the `sys_account.issuer`
2230
* retirement (#17440).
@@ -126,26 +134,45 @@ export default class MigrateAccountIssuer extends Command {
126134
const engine = (stack.kernel as { getService?: (n: string) => unknown }).getService?.call(
127135
stack.kernel,
128136
'objectql',
129-
);
137+
) as { getDriverForObject?: (object: string) => AccountTableDriver | undefined } | undefined;
130138

131139
if (!flags.json) printStep('Scanning sys_account…');
132140

141+
// The pre-flight reads the PHYSICAL `sys_account` table through the
142+
// driver the engine routes that name to, not through the engine's
143+
// `find`. This boot composes no `AuthPlugin`, so `sys_account` is not a
144+
// registered object here, and the engine refuses a name its registry
145+
// does not resolve (`OBJECT_NOT_FOUND`) before any driver is asked. That
146+
// refusal is a fact about this boot's composition, never about the
147+
// database: ⛔ reading it as "no rows" would report a table full of
148+
// accounts as a clean pre-flight and authorise the drop. The table is
149+
// read in its legacy shape, `issuer` included, which is the very column
150+
// the registered schema no longer declares, so the driver (the path the
151+
// engine leaves to host code) is the reader this inventory needs.
152+
const driver = engine?.getDriverForObject?.(SYS_ACCOUNT);
153+
if (!driver || typeof driver.find !== 'function') {
154+
throw new Error(
155+
`No driver serves ${SYS_ACCOUNT} on this stack, so the table cannot be enumerated. ` +
156+
'Refusing rather than reporting an unread table as clean.',
157+
);
158+
}
159+
133160
// [#21552] A database with no `sys_account` table holds no account, so no
134161
// two rows collide: the probe reads it as no rows. The read is not
135-
// avoided, measured: this boot composes no `AuthPlugin`, so `sys_account`
136-
// is not a registered object and the held-back sync never lists it, and
162+
// avoided, measured: `sys_account` is not a registered object on this
163+
// boot, so the held-back sync never lists it, and
137164
// `stack.tableAbsent('sys_account')` answers false on every database.
138-
// The refusal is therefore recognised, with the shared predicate and for
139-
// this command's own table only. ⛔ No other refused read is softened: it
140-
// still throws the probe's refusal below and is never read as clean.
165+
// The driver's missing-table refusal is therefore recognised, with the
166+
// shared predicate and for this command's own table only. ⛔ No other
167+
// refused read is softened: it still throws the probe's refusal below
168+
// and is never read as clean.
141169
let noAccountTable = false;
142-
const readEngine = engine as Parameters<typeof probeAccountIdentityCollisions>[0];
143-
const readView: typeof readEngine = {
144-
find: async (object, query, options) => {
170+
const readView: Parameters<typeof probeAccountIdentityCollisions>[0] = {
171+
find: async (object, query) => {
145172
try {
146-
return await readEngine.find(object, query, options);
173+
return await driver.find(object, query);
147174
} catch (error) {
148-
if (object !== 'sys_account' || !isMissingTableError(error, object)) throw error;
175+
if (object !== SYS_ACCOUNT || !isMissingTableError(error, object)) throw error;
149176
noAccountTable = true;
150177
return [];
151178
}

0 commit comments

Comments
 (0)