Skip to content

Commit de01287

Browse files
committed
fix(objectql, driver-sql): an unreadable certification is thrown by the engine and reported once by the driver, never answered silently
Claude-Session: https://claude.ai/code/session_01NnXVu3vF2TKFLMi5Me4CUd Co-authored-by: Claude <noreply@anthropic.com>
1 parent 801221d commit de01287

4 files changed

Lines changed: 35 additions & 9 deletions

File tree

‎packages/drivers/driver-sql/src/sql-driver-unique-tenancy.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ describe('SqlDriver unique × tenancy (#3696)', () => {
614614
// bucket rather than rebuilding it…
615615
const second = await driver.create('template', { key: 'welcome' });
616616
expect(second.key).toBe('welcome');
617-
expect(await driver.count('template', { where: { key: 'welcome' } } as any)).toBe(3);
617+
expect(await driver.count('template', { where: { key: 'welcome' } })).toBe(3);
618618
// …while the organization itself is still held to one row per value.
619619
await expect(driver.create('template', { organization_id: 'org_a', key: 'welcome' })).rejects.toThrow(
620620
/UNIQUE constraint failed|duplicate key value/,

‎packages/drivers/driver-sql/src/sql-driver.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20414,6 +20414,8 @@ export class SqlDriver implements IDataDriver {
2041420414
private tenantKeyCertifiedResolver?: () => boolean | Promise<boolean>;
2041520415
/** Set once the resolver has answered yes — a certificate is never withdrawn. */
2041620416
private tenantKeyCertified?: true;
20417+
/** The resolver's failure is reported once per driver. */
20418+
private tenantKeyCertifiedReadFailureReported = false;
2041720419

2041820420
/**
2041920421
* Asked lazily — only a table carrying the NULL-safe tenant key part needs
@@ -20429,7 +20431,18 @@ export class SqlDriver implements IDataDriver {
2042920431
// yet answers with a refused read, which is not a fault (see
2043020432
// {@link PRE_DDL_QUESTION_SCOPE}).
2043120433
if ((await PRE_DDL_QUESTION_SCOPE.run(true, resolver)) !== true) return false;
20432-
} catch {
20434+
} catch (e: any) {
20435+
// Not knowing reads as "not certified" — the reading that never proposes
20436+
// a rebuild on a guess — and is said once, so a drift report that then
20437+
// names an 18.0 index is not taken for a verdict on a certified store.
20438+
if (!this.tenantKeyCertifiedReadFailureReported) {
20439+
this.tenantKeyCertifiedReadFailureReported = true;
20440+
this.logger.warn(
20441+
`[schema-drift] could not read whether this database is certified for the organization-ownership shape ` +
20442+
`(${e?.message ?? e}); an index keyed COALESCE(<tenant>, …) on a nullable tenant column is compared ` +
20443+
`as a different constraint from the plain key until it can be read.`,
20444+
);
20445+
}
2043320446
return false;
2043420447
}
2043520448
this.tenantKeyCertified = true;

‎packages/objectql/src/adr0131-tenant-key-certified-supply.test.ts‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ interface Sink {
4141
resolver: (() => Promise<boolean>) | null;
4242
}
4343

44-
function boot(rows: Rows, opts: { sink?: Sink; readThrows?: boolean; withFlagObject?: boolean } = {}): ObjectQL {
44+
function boot(rows: Rows, opts: { sink?: Sink; readThrows?: string; withFlagObject?: boolean } = {}): ObjectQL {
4545
const driver: Record<string, unknown> = {
4646
name: 'default',
4747
version: '1.0.0',
@@ -52,7 +52,7 @@ function boot(rows: Rows, opts: { sink?: Sink; readThrows?: boolean; withFlagObj
5252
return typeof ast?.limit === 'number' ? found.slice(0, ast.limit) : found;
5353
},
5454
async findOne(_object: string, ast: any) {
55-
if (opts.readThrows) throw new Error('relation "sys_migration" does not exist');
55+
if (opts.readThrows) throw new Error(opts.readThrows);
5656
return rows.find((r) => matches(r, ast?.where)) ?? null;
5757
},
5858
async count() {
@@ -130,12 +130,21 @@ describe('[ADR-0131 D1] isOrganizationOwnershipCertified()', () => {
130130
'an older shape generation',
131131
).toBe(false);
132132
expect(await boot([certified({ details: null })]).isOrganizationOwnershipCertified(), 'no generation named').toBe(false);
133-
expect(await boot([certified()], { readThrows: true }).isOrganizationOwnershipCertified(), 'unreadable').toBe(false);
133+
expect(
134+
await boot([certified()], { readThrows: 'relation "sys_migration" does not exist' }).isOrganizationOwnershipCertified(),
135+
'no ledger table yet',
136+
).toBe(false);
134137
expect(await boot([certified()], { withFlagObject: false }).isOrganizationOwnershipCertified(), 'no ledger object').toBe(
135138
false,
136139
);
137140
});
138141

142+
it('any other read failure is thrown, never answered as "not certified"', async () => {
143+
await expect(boot([certified()], { readThrows: 'connection reset' }).isOrganizationOwnershipCertified()).rejects.toThrow(
144+
'connection reset',
145+
);
146+
});
147+
139148
it('registerDriver hands a driver with the seam a closure over the same reading', async () => {
140149
const rows: Rows = [];
141150
const sink: Sink = { resolver: null };

‎packages/objectql/src/engine.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12223,8 +12223,10 @@ export class ObjectQL implements IObjectQLEngine {
1222312223
* shape — a verified `sys_migration` row for the ceremony's id (the birth
1222412224
* marker a store created from empty receives, or the ceremony's completion
1222512225
* marker) at this release's shape generation or above? The same reading the
12226-
* runtime's boot guard boots on. Every way of not knowing (no ledger object,
12227-
* no row, an unreadable read, an unverified or older row) answers `false`.
12226+
* runtime's boot guard boots on. No ledger object, no ledger table yet, no
12227+
* row, or an unverified or older row answers `false`. Any other read failure
12228+
* is thrown: "I could not check" is not "not certified", and the driver
12229+
* asking reports it (`SqlDriver.isTenantKeyCertified`).
1222812230
*/
1222912231
async isOrganizationOwnershipCertified(): Promise<boolean> {
1223012232
if (!this._registry.getObject(DATA_MIGRATION_FLAG_OBJECT)) return false;
@@ -12241,8 +12243,10 @@ export class ObjectQL implements IObjectQLEngine {
1224112243
blocking: typeof row.blocking === 'number' ? row.blocking : Number(row.blocking ?? Number.NaN),
1224212244
});
1224312245
return verified && organizationOwnershipShapeGenerationOf(row.details) >= ORGANIZATION_OWNERSHIP_SHAPE_GENERATION;
12244-
} catch {
12245-
return false;
12246+
} catch (error) {
12247+
// A store whose ledger table does not exist yet has no certificate.
12248+
if (isMissingTableError(error, DATA_MIGRATION_FLAG_OBJECT)) return false;
12249+
throw error;
1224612250
}
1224712251
}
1224812252

0 commit comments

Comments
 (0)