Skip to content

Commit a2dcfd2

Browse files
committed
Merge commit 'c2c21f357c' into claude/issue-21326-aad-scope-discriminant
2 parents ace768e + c2c21f3 commit a2dcfd2

27 files changed

Lines changed: 964 additions & 202 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/driver-sql': patch
3+
---
4+
5+
fix(driver-sql): the driver's own refusal log lines no longer write the statement or the values bound into it
6+
7+
Clause-②: no
8+
9+
Five warning lines wrote the dialect's message to the server log as it came back. That message opens with the statement, with its bound values inlined on SQLite and MySQL, and on PostgreSQL a value-bearing diagnostic carries the value itself. The lines are the read terminal, the raw-statement terminal, and the refusals for a WHERE, a groupBy or aggregation, and a listed-distinct column the backend could not resolve. Each line now writes the dialect's text through the driver-fault redaction in `@objectstack/types`, the cut the engine applies at its boundary.
10+
11+
- **What stays on each line.** Its code, the class of fault it reports, the object and column it names, the dialect's error code where the line printed one, and the dialect's own diagnostic.
12+
- **What goes.** The statement and the values bound or inlined into it, replaced by `[statement and bound values redacted]`, and the value slot of each diagnostic the redaction's templates own, replaced by `[value redacted]`. The raw-statement line no longer writes the statement it was sent, which also holds for `@objectstack/driver-turso`'s remote transport, whose refusals reach the same line. The two debug lines the read terminal writes inside a pre-DDL question, or for a table whose DDL the driver deferred, take the same cut.
13+
- **The envelopes.** The code, status, `cause` and withheld text of every refusal are unchanged. Two composed messages, the read terminal's `DATABASE_ERROR` and the raw-statement terminal's, said the statement was written to the server log; they now say the diagnostic was written with the statement and its bound values cut.
14+
- **What changes for an operator.** A log reader that took the statement or a bound value from these lines now finds the marker where the dialect's text carried them, and nothing where the raw-statement line wrote the sent statement on its own. The diagnostic, the codes and the named object and column are where they were.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
refactor(objectql): the engine takes its driver-fault redaction from `@objectstack/types`
6+
7+
Clause-②: no
8+
9+
The redaction the engine applies to its write-path log lines, at its boundary, at the raw-statement door and in the lifecycle sweep now lives in `@objectstack/types`, so `@objectstack/driver-sql` calls the same cut. The engine calls it as before, with the same arguments, and its answers are unchanged. None of the moved names was exported from `@objectstack/objectql`'s entries, so its public surface does not move.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/types': minor
3+
---
4+
5+
feat(types): the driver-fault redaction is exported from types, so a driver's own log lines take the same cut the engine applies
6+
7+
Clause-②: no
8+
9+
- **New exports.** `redactBoundStatement`, `redactStatementFromMessage`, `redactPropagatedDriverFault` and the `DriverFaultOrigin` type are exported from `@objectstack/types`, by name. They moved here from `@objectstack/objectql`, which never exported them from its entries. The cut is unchanged by the move: the same split, the same structural cut at the separator, the same value templates and the same property rules.
10+
- **Why here.** `@objectstack/driver-sql`, `@objectstack/objectql` and `@objectstack/core` all depend on this package, and `operatorFacingErrorText` lives in it, so this is the lowest package all of them can import the cut from. The module imports only this package's own leak predicate, which is unchanged.
11+
- **One widening, on the log face.** `redactStatementFromMessage` takes an optional second argument, `{ statementSent: true }`. With it the cut runs without asking the shared leak predicate, as `redactPropagatedDriverFault` already did with the same flag. Without it the function answers exactly as before.
12+
- **Why minor.** The package gains four exported names, and `redactStatementFromMessage` gains the optional parameter above. No existing export of `@objectstack/types` changes.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1290,7 +1290,7 @@ export function diffManagedTable(args: {
12901290
// never revisits an existing column, so a deployment upgrading into that
12911291
// release gets no change AND no diagnostic. The server keeps refusing the
12921292
// same write, and the refusal is a poor substitute for a report: the live
1293-
// probe behind `objectql`'s `driver-fault-redaction.ts` measured Postgres's
1293+
// probe behind `types`' `driver-fault-redaction.ts` measured Postgres's
12941294
// `22001` as identifier-only and naming the TYPE rather than the column
12951295
// (`value too long for type character varying(255)`), MySQL's `1406` as
12961296
// `Data too long for column 'label' at row 1`. Meanwhile every

‎packages/drivers/driver-sql/src/schema-drift.unbounded-text-column.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* never revisits an existing one — so a deployment upgrading into that release
2929
* gets no change AND no diagnostic, while the server keeps refusing the same
3030
* write. That refusal is a poor substitute for a report: the live probe behind
31-
* `objectql`'s `driver-fault-redaction.ts` measured Postgres's `22001` as
31+
* `types`' `driver-fault-redaction.ts` measured Postgres's `22001` as
3232
* identifier-only and naming the TYPE rather than the column (`value too long
3333
* for type character varying(255)`).
3434
*

‎packages/drivers/driver-sql/src/sql-driver-11455-aggregate-fault-envelope.test.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,19 @@ describe(`[#11455] driver-sql — aggregate() takes the backend-fault envelope (
241241
// THE LOG — a withholding, not a deletion
242242
// ───────────────────────────────────────────────────────────────
243243

244-
it('writes the full dialect text to the SERVER LOG, statement included', async () => {
244+
it('writes the dialect diagnostic to the SERVER LOG, the statement cut', async () => {
245245
const { err, logged } = await withLog(driver, () => driver.aggregate(MISSING_TABLE, COUNT_ALL));
246246
expect(err.code).toBe('DATABASE_ERROR');
247247
const line = logged.find((l) => l.includes(MISSING_TABLE));
248248
expect(line, 'an operator must still be able to read what the backend said').toBeDefined();
249-
expect(String(line)).toMatch(/\bselect\b/i);
249+
// [#21385, maintainer ruling 2026-10-02] A redaction, not a deletion, and
250+
// since this ruling a cut one: the dialect's diagnostic still reaches the
251+
// log for an operator, the statement and its bound values do not (a server
252+
// log leaves the data's trust boundary). The cut's marker says one stood
253+
// there. The sentinel pins for this line live in
254+
// `sql-driver-21385-refusal-log-line-redaction.test.ts`.
255+
expect(String(line)).not.toMatch(/\bselect\b/i);
256+
expect(String(line)).toContain('[statement and bound values redacted]');
250257
expect(String(line)).toContain('DATABASE_ERROR');
251258
});
252259

‎packages/drivers/driver-sql/src/sql-driver-11541-aggregate-unresolvable-column-refusal.test.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,12 @@ describe(`[#11541] driver-sql — aggregate() attributes an unresolvable column
274274
// The withholding half: the dialect's own words reach the operator.
275275
const line = logged.find((l) => l.includes('INVALID_FIELD') && l.includes('nosuchcol'));
276276
expect(line, 'the dialect message must reach the server log').toBeDefined();
277-
expect(String(line), 'the log carries the compiled statement').toMatch(/\bselect\b/i);
277+
// [#21385, maintainer ruling 2026-10-02] …cut: the compiled statement and
278+
// the literal bound into it no longer reach the log (a server log leaves
279+
// the data's trust boundary); the cut's marker says a statement stood there.
280+
expect(String(line), 'the log no longer carries the compiled statement').not.toMatch(/\bselect\b/i);
281+
expect(String(line).includes(SECRET_LITERAL), 'the bound literal reached the log').toBe(false);
282+
expect(String(line)).toContain('[statement and bound values redacted]');
278283
});
279284

280285
// ───────────────────────────────────────────────────────────────

‎packages/drivers/driver-sql/src/sql-driver-16019-raw-statement-fault-envelope.test.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,20 @@ describe('[#16019] SqlDriver.execute() declares a backend refusal as DATABASE_ER
139139
expect(looksLikeInternalErrorLeak('no such column: bogus_dim')).toBe(true);
140140
});
141141

142-
it('writes the statement and the dialect message to the server log — after this change, the only copy', async () => {
142+
it("writes the dialect's diagnostic to the server log, with the statement cut", async () => {
143143
await faultOf(() => driver.execute(TRANSLATE_SQL));
144144

145145
const line = driver.warned.find((m) => m.includes('DATABASE_ERROR'));
146146
expect(line).toBeDefined();
147147
expect(line).toContain('(SQLITE_ERROR)');
148-
expect(line).toContain(TRANSLATE_SQL);
149148
expect(line).toContain('no such function: translate');
149+
// [#21385, maintainer ruling 2026-10-02] The line used to write the
150+
// statement too, sent and compiled. Both carry the values a raw statement
151+
// binds or spells inline, and a server log leaves the data's trust
152+
// boundary, so neither is written: the cut's marker stands in their place.
153+
expect(line).not.toContain(TRANSLATE_SQL);
154+
expect(line).not.toContain('statement: ');
155+
expect(line).toContain('[statement and bound values redacted]');
150156
});
151157

152158
it('a missing table on the raw path stays classifiable through `cause` (isMissingTableError)', async () => {

‎packages/drivers/driver-sql/src/sql-driver-17639-distinct-fault-envelope.test.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,19 @@ describe(`[#17639] driver-sql — distinct() takes the backend-fault envelope ($
245245
// THE LOG — a withholding, not a deletion
246246
// ───────────────────────────────────────────────────────────────
247247

248-
it('writes the full dialect text to the SERVER LOG, statement included', async () => {
248+
it('writes the dialect diagnostic to the SERVER LOG, the statement cut', async () => {
249249
const { err, logged } = await withLog(driver, () => driver.distinct(MISSING_TABLE, 'title'));
250250
expect(err.code).toBe('DATABASE_ERROR');
251251
const line = logged.find((l) => l.includes(MISSING_TABLE));
252252
expect(line, 'an operator must still be able to read what the backend said').toBeDefined();
253-
expect(String(line)).toMatch(/\bselect\b/i);
253+
// [#21385, maintainer ruling 2026-10-02] A redaction, not a deletion, and
254+
// since this ruling a cut one: the dialect's diagnostic still reaches the
255+
// log for an operator, the statement and its bound values do not (a server
256+
// log leaves the data's trust boundary). The cut's marker says one stood
257+
// there. The sentinel pins for this line live in
258+
// `sql-driver-21385-refusal-log-line-redaction.test.ts`.
259+
expect(String(line)).not.toMatch(/\bselect\b/i);
260+
expect(String(line)).toContain('[statement and bound values redacted]');
254261
expect(String(line)).toContain('DATABASE_ERROR');
255262
});
256263

‎packages/drivers/driver-sql/src/sql-driver-17857-distinct-unresolvable-column-refusal.test.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,19 @@ describe(`[#17857] driver-sql — distinct() attributes an unresolvable column (
251251
expect(err.code).not.toBe('DATABASE_ERROR');
252252
});
253253

254-
it('arm 1: writes the full dialect text to the SERVER LOG, statement included', async () => {
254+
it('arm 1: writes the dialect diagnostic to the SERVER LOG, the statement cut', async () => {
255255
const { err, logged } = await withLog(driver, () => driver.distinct(TABLE, MISSING_COLUMN));
256256
expect(err.code).toBe('INVALID_FIELD');
257257
const line = logged.find((l) => l.includes(MISSING_COLUMN));
258258
expect(line, 'an operator must still be able to read what the backend said').toBeDefined();
259-
expect(String(line), 'the withheld statement is in the log').toMatch(/\bselect\b/i);
259+
// [#21385, maintainer ruling 2026-10-02] A redaction, not a deletion, and
260+
// since this ruling a cut one: the dialect's diagnostic still reaches the
261+
// log for an operator, the statement and its bound values do not (a server
262+
// log leaves the data's trust boundary). The cut's marker says one stood
263+
// there. The sentinel pins for this line live in
264+
// `sql-driver-21385-refusal-log-line-redaction.test.ts`.
265+
expect(String(line), 'the withheld statement is cut from the log').not.toMatch(/\bselect\b/i);
266+
expect(String(line), 'the cut says a statement stood there').toContain('[statement and bound values redacted]');
260267
expect(String(line), 'the log line names the envelope it produced').toContain('INVALID_FIELD');
261268
});
262269

0 commit comments

Comments
 (0)