Blocked-by: #8739
Filed unassigned. Surfaced while correcting the dialect claim on #8739 — it is the residual that claim was used to size, so it is filed with the evidence rather than left as a remark in a report. Duplicate-searched first (keyword + file path over open issues): no existing card.
What was measured
redactStatementFromMessage (packages/objectql/src/driver-fault-redaction.ts, landed by PR #8737 for #8682) keeps everything after the LAST - in a knex-shaped driver message — the database's own diagnostic — and replaces the bound statement. Driven directly through the shipped function on the current main worktree, four dialect-shaped messages in, kept tail out:
mysql unknown column => "Unknown column 'zzz' in 'field list' [statement and bound values redacted]"
mysql duplicate entry => "Duplicate entry 'acme@example.com' for key 'crm_account.email' [statement and bound values redacted]"
sqlite unique violation => "UNIQUE constraint failed: crm_account.email [statement and bound values redacted]"
pg unique violation => "duplicate key value violates unique constraint \"crm_account_email_key\" [statement and bound values redacted]"
Three of the four keep an IDENTIFIER — a column, a table, a constraint name. The MySQL duplicate-entry row keeps a caller-supplied value, because that is what mysql2 puts in the diagnostic itself rather than in the statement. The cut is working exactly as designed; the design's premise is that the tail names identifiers, and on this one family and this one dialect it does not.
The MySQL phrasing is not invented here. It is recorded verbatim in this repo already: packages/types/src/unique-violation.ts (ER_DUP_ENTRY: Duplicate entry 'acme@example.com' for key 'idx_email_unique') and pinned as a fixture in packages/types/src/unbacked-conflict-target.test.ts.
Why the dialect matters, and why this is not a rerun of #8739
PR #8737 sized this residual explicitly, and its stated reason was that error-leak.ts "records that this repo runs only SQLite/libsql and Postgres". #8739 established that reason was false. What the measurement above adds is that the reason was also load-bearing for this family: on the two dialects the PR named, the kept tail carries no caller value; on MySQL it does. The conclusion held for the canary the PR actually measured (unknown-column, which names an identifier on MySQL too) and does not extend to the unique-violation path.
The asymmetry is worth stating precisely, because it is not obvious:
- Postgres does NOT leak here, but not because of the cut. Its value lives in the
DETAIL: line, which node-postgres keeps on error.detail — a field Logger does not serialize, since it writes only message and stack. Different mechanism, same outcome. - SQLite does not leak because its diagnostic names
table.column and stops. - MySQL leaks because the value is in the native message.
Not claimed
Related
Blocked-by: #8739
Filed unassigned. Surfaced while correcting the dialect claim on #8739 — it is the residual that claim was used to size, so it is filed with the evidence rather than left as a remark in a report. Duplicate-searched first (keyword + file path over open issues): no existing card.
What was measured
redactStatementFromMessage(packages/objectql/src/driver-fault-redaction.ts, landed by PR #8737 for #8682) keeps everything after the LAST-in a knex-shaped driver message — the database's own diagnostic — and replaces the bound statement. Driven directly through the shipped function on the currentmainworktree, four dialect-shaped messages in, kept tail out:Three of the four keep an IDENTIFIER — a column, a table, a constraint name. The MySQL duplicate-entry row keeps a caller-supplied value, because that is what mysql2 puts in the diagnostic itself rather than in the statement. The cut is working exactly as designed; the design's premise is that the tail names identifiers, and on this one family and this one dialect it does not.
The MySQL phrasing is not invented here. It is recorded verbatim in this repo already:
packages/types/src/unique-violation.ts(ER_DUP_ENTRY: Duplicate entry 'acme@example.com' for key 'idx_email_unique') and pinned as a fixture inpackages/types/src/unbacked-conflict-target.test.ts.Why the dialect matters, and why this is not a rerun of #8739
PR #8737 sized this residual explicitly, and its stated reason was that
error-leak.ts"records that this repo runs only SQLite/libsql and Postgres". #8739 established that reason was false. What the measurement above adds is that the reason was also load-bearing for this family: on the two dialects the PR named, the kept tail carries no caller value; on MySQL it does. The conclusion held for the canary the PR actually measured (unknown-column, which names an identifier on MySQL too) and does not extend to the unique-violation path.The asymmetry is worth stating precisely, because it is not obvious:
DETAIL:line, which node-postgres keeps onerror.detail— a fieldLoggerdoes not serialize, since it writes onlymessageandstack. Different mechanism, same outcome.table.columnand stops.Not claimed
statement - native messageshape; the redaction behaviour above is measured through the real shipped function, not predicted.error-leak.tsasserts "nobody here runs" MySQL — but CI stands up a live mysql:8.0 for a required check, and the claim is load-bearing for security reasoning #8739), which is the maintainer's.Related
error-leak.tsasserts "nobody here runs" MySQL — but CI stands up a live mysql:8.0 for a required check, and the claim is load-bearing for security reasoning #8739 — the false dialect claim the sizing rested on; corrected, question still open.Duplicate entry落成 500 INTERNAL_ERROR) #6250 —isUniqueViolationError, the predicate that DOES recognise this MySQL phrasing (a different question from "is this a leak").