Repository navigation
Commit 6d728b8
fix(driver-sql): the five refusal log lines take the shared driver-fault cut, moved to core (#21414)
Fixes #21385
Clause-②: no
Security handling: this body, the commits, the changesets and the test
names carry classes and positions only. Every pin plants a synthetic
sentinel and asserts it is ABSENT. No measured log line is copied
anywhere, and the local probe printed booleans only.
## What this changes
This implements the maintainer's ruling on #21385 (comment 5950942037,
letter A): one cutter for every log face. The cutter's home is
`@objectstack/types`, per the claim's amendment 1 (5954587444) and
triage's pointer (5954318919). See "Patch round 1" below.
1. **The redaction module moves to `@objectstack/types`.**
`packages/objectql/src/driver-fault-redaction.ts` becomes
`packages/types/src/driver-fault-redaction.ts`. Against `main`, that is
the PR's one git rename (`R092`). The cut is the same code: the same
split, the same structural cut at the separator, the same value
templates and the same property rules.
- The module's one import, `looksLikeInternalErrorLeak`, is now the
package-local `./error-leak.js`. `error-leak.ts` and its frozen list are
not edited.
- The types entry exports four names, listed by name:
`redactBoundStatement`, `redactStatementFromMessage`,
`redactPropagatedDriverFault` and the `DriverFaultOrigin` type.
- The template table and its load-time guard stay package-internal. The
guard's eight cases moved verbatim beside it, to
`packages/types/src/driver-fault-redaction.test.ts`.
- Why `types`: it is the lowest package every face of this family can
reach. `driver-sql`, `objectql` and `core` all depend on it, and the
family's fourth position, `operatorFacingErrorText`, lives inside it
(#21418). The module header says so.
- There is no copy of the cut, no edit to `error-leak.ts` or
`driver-error-classification.ts`, and no row added to the frozen
predicate list. `packages/core` is not in the diff.
2. **One widening, on the log face.** `redactStatementFromMessage` takes
an optional second argument: the same `{ statementSent: true }` that
`redactPropagatedDriverFault` already took. Without it, the answer is
unchanged.
3. **objectql calls the moved code.** `engine.ts` and
`lifecycle/lifecycle-service.ts` now import from `@objectstack/types`,
with the same arguments as before. Five objectql test files repoint
their import. None of the moved names was on objectql's entries.
4. **driver-sql's five lines call it.** In `sql-driver.ts`, each refusal
line writes the dialect's text through `redactStatementFromMessage(text,
{ statementSent: true })`, imported from `@objectstack/types`. The lines
are:
- the unresolvable WHERE column (`INVALID_FILTER`), on `find` and on
`count`;
- the read terminal (`DATABASE_ERROR`);
- the raw-statement terminal (`DATABASE_ERROR`), which no longer writes
the sent statement as a separate field;
- the unresolvable groupBy / aggregation column (`INVALID_FIELD`);
- the unresolvable listed-distinct column (`INVALID_FIELD`).
`statementSent` holds by construction: each line writes a fault raised
by a statement this driver sent, which is the same knowledge the
engine's raw door passes. The read terminal's cut sits where its text is
computed, so the two debug lines that demote it inside a scope take the
cut too (see Acceptance notes).
5. **`driver-turso`'s remote transport is covered by the same line.**
Its refusals reach the base class's raw terminal. Its transport error
carries no statement (the bare shape), so the line now writes the
engine's diagnostic and nothing of the statement it was sent.
6. **Two envelope sentences are corrected.** The read terminal's and the
raw terminal's composed `DATABASE_ERROR` messages said the statement was
written to the server log. After this change that is not true, so each
now says the diagnostic was written, with the statement and its bound
values cut. Code, status, `cause` and the withheld text are unchanged.
See Acceptance notes, deviation 1.
## Measured: sentinel on each line, before and after
Probe: drive each line through the public driver method on
better-sqlite3, a live PostgreSQL 16 and a live MySQL 8.0.46, with one
synthetic sentinel. An extractor printed only whether the captured line
held the sentinel, and on which field. BEFORE was measured at
`ecb6ca0258` (base), and AFTER at `0cbd86d55e`, with the same extractor.
The committed pins re-measure AFTER at the final head, `992e940fff`.
| line | drive | sqlite before → after | pg before → after | mysql
before → after |
|---|---|---|---|---|
| WHERE column | `find` | present → absent | absent → absent | present →
absent |
| WHERE column | `count` | present → absent | absent → absent | present
→ absent |
| read terminal | missing table (sqlite, mysql); 22P02 value (pg) |
present → absent | present → absent | present → absent |
| raw terminal | bound value | present (dialect field) → absent |
present (dialect field) → absent | present (dialect field) → absent |
| raw terminal | value spelled inline | present (statement field and
dialect field) → absent | same → absent | same → absent |
| groupBy / aggregation column | — | present → absent | absent → absent
| present → absent |
| listed-distinct column | — | present → absent | absent → absent |
present → absent |
After the change, on every row: the lead (code and class of fault,
object and column) is kept, the dialect's own diagnostic is kept, and
the cut's marker stands where the statement was. On the pg read and raw
rows, the 22P02 value slot reads as the value marker. The raw line no
longer carries a `statement:` field.
The `driver-turso` remote transport, with a sentinel spelled inline in
the sent statement: the raw terminal was handed it in the statement and
not in the transport's bare error, and the line carries none of it. No
marker appears there, because the bare error has no statement to cut.
## Zone 2 hypotheses
- **H1 confirmed, with one amendment.** All five lines were driven on
all three dialects. At base they sit at `sql-driver.ts` `:10087`,
`:10215`, `:10284`, `:10960` and `:11253`. The amendment: on PostgreSQL,
the three unresolvable-column lines never carried the sentinel. pg
positions bindings as `$n` before knex formats the message, and its
column diagnostic names identifiers only. Those three pg cells are
recorded in the pin as non-regression cells, not as non-vacuous ones.
The pg read and raw terminals did carry it, through the 22P02 diagnostic
and through the raw statement's own text.
- **H2 superseded in patch round 1.** Round 0 confirmed that
`packages/core` could hold the module. Amendment 1 moved it to
`packages/types`, where it also fits. Measured, the package takes a new
runtime module:
- its `tsup` entry is `src/index.ts` (edge-safe; the module uses no
`node:` builtin);
- its `tsconfig` includes `src/**/*`, and `--listFiles` shows both the
module and its test;
- `check-dts-emitted` runs in its build, and
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:published-files` all pass;
- no gate refused it, and none was edited.
The WHOLE module moved, not only the cutter. The two faces and the
property rules are thin wrappers over the module's private split, so
objectql no longer has a wrapper of its own.
- **H3 confirmed.** Each line keeps its lead, plus `(CODE)` on the read
and raw lines, and the dialect's diagnostic. It loses the statement and
any template-owned value. The raw terminal's separately logged
`statement:` field carried an inline sentinel on all three dialects at
base, and it is no longer written.
- **H4 partly falsified.** Every envelope's code, status, `cause` and
withheld text are unchanged. But two composed messages described the log
half ("the statement … written to the server log"), and this change made
them false. They were corrected; see deviation 1. The engine's existing
pins for the two earlier positions (the cards behind PR #21335 and PR
#21384) stay green:
- the full objectql suite, which includes
`driver-fault-boundary-redaction.test.ts` and
`driver-fault-redaction-residue.test.ts`;
- the dogfood suite, which includes
`raw-statement-fault-redaction.test.ts`;
- the plugin-auth carrier pin, 15/15 on three dialects.
## Pins
- **New:
`packages/drivers/driver-sql/src/sql-driver-21385-refusal-log-line-redaction.test.ts`,
46 cases.** It runs on every cell of the driver axis: SQLite always,
live PostgreSQL and MySQL when their URLs are set.
- Per cell, each of the seven drives has two cases:
- a SENTINEL case: the text handed to the line carried the sentinel as
measured, the written line does not carry it, and the marker is present;
- a CONTROL case: the envelope's code and status, the lead, the
diagnostic, the dialect code and the named object and column.
- Each cell also has one success control.
- One case pins the deferred-DDL debug line.
- Non-vacuity is read off what each protected refusal method was HANDED,
through a recording subclass. Every assertion on a line is a boolean, so
a red names the line and never prints it.
- **Flipped: nine existing pins in eight files.** Each of these pinned
the retired design, in which the statement was in the log line:
- in `driver-sql`: `sql-driver-11455-…`, `-11541-…`, `-16019-…`,
`-17639-…`, `-17857-…`, `sql-driver-backend-fault-envelope` (two cases)
and `sql-driver-unresolvable-where-column-refusal` (the positive
control);
- in `driver-turso` (patch round 1):
`turso-driver-16019-remote-raw-statement-fault-envelope`.
Each now asserts the diagnostic and that the statement is absent. The
`driver-sql` ones also assert the marker. The positive controls, and the
turso pin, read the sentinel's presence on what the refusal was handed
(or on the envelope's `cause`) instead of on the log.
## Reverse verification
**The five calls (round 0).** The change was committed first (head
`7445ed5cda`, `sql-driver.ts` blob `ede93583a0f1`). Only the five calls
were reverted: the four identical call sites and the read terminal's
one. This used two nested `scripts/ablation-replace.mjs` legs: anchor x4
→ x0 (blob `ede93583a0f1` → `e4243feaf00e`), then anchor x1 → x0 (blob
`e4243feaf00e` → `a8ea87a6014b`). The driver-sql pin loads
`./sql-driver.js` from source, so no build sits between the mutation and
the run.
- **Mutated: 22 red / 24 green of 46.**
- Red on "the sentinel reached the line": sqlite 7/7, mysql 7/7, pg 3
(read, raw bound, raw inline), and the debug-line case. That is 18.
- Red on "says a statement was cut": the 4 pg cells that were never
handed the sentinel (WHERE find and count, aggregate, distinct).
- Green: all 21 CONTROL cases and the 3 success controls.
- **Restored.** Proven by the tool twice and by a script trap once: blob
== HEAD, and `git diff HEAD` is empty.
**The turso flip (patch round 1, at `992e940fff`).** Only the
`driver-sql` change this pin depends on was reverted: the raw terminal's
dropped `statement:` field was put back. `driver-turso` reads
`driver-sql` from its dist, so each leg was rebuilt and its dist checked
before the run.
- The first attempt was a no-op. The replacement text contained the
anchor, so `ablation-replace.mjs` refused it (anchor x1 → x1) and ran
nothing. The anchor was changed and the leg re-run.
- **Mutate.** Anchor x1 → x0, blob `ae07547bcd68` → `025ec6208050`. The
on-disk marker count was 1. `driver-sql` was rebuilt, and
`ablation-dist-preflight.mjs` found the marker present in 2 built files.
- The turso pin went 1 red / 3 green; the red is "the sentinel reached
the server log".
- The driver-sql raw pins went 7 red / 46 green: the six raw-terminal
sentinel cases on three cells, and the `16019` raw-line pin.
- **Restore.** Proven by the tool and by a trap: blob == HEAD
`ae07547bcd68`, and `git diff HEAD` is empty. After a rebuild, the
preflight with `--absent` found the marker absent from all 6 built
files. The turso pin went back to 4/4, and the driver-sql raw pins to
53/53.
## Verification
Final head: `992e940fff`. Live servers: PostgreSQL 16 and MySQL 8.0.46,
throwaway instances that were stopped by recorded PID and removed.
`TZ=America/New_York` was set, as the live CI job sets it.
- `@objectstack/types`: `test` 23 files / 696 tests passed; `test:repo`
1 file / 7 tests passed; `typecheck` exit 0.
- `@objectstack/core` (the module left it): `test` 76 files / 2156 tests
passed; `typecheck` exit 0.
- `@objectstack/objectql`: `test` 365 files / 7379 tests passed;
`test:repo` 1 file / 5 tests passed; `typecheck` exit 0, with
`check:test-typecheck` OK.
- `@objectstack/driver-sql`, full suite (`vitest run --maxWorkers=2`),
with both live URLs: 227 files passed; 5472 tests passed and 1 skipped;
0 sentinel occurrences in the run log. `typecheck`: exit 0.
- `@objectstack/driver-turso`: `test` 88 files passed; 2365 tests passed
and 33 skipped. `typecheck`: exit 0.
- `@objectstack/driver-sqlite-wasm`: `test` 36 files / 675 tests passed;
`typecheck` exit 0.
- The `Test Core (1/6)` packages that CI never reached:
- `spec`: `test` 600 files, 17606 passed + 1 todo; `test:repo` 48 files
/ 849 passed;
- `service-settings` 33 / 591;
- `cloud-connection` 31 / 401;
- `service-messaging` 46 / 507;
- `example-showcase` 31 / 394;
- `plugin-pinyin-search` 2 / 21;
- `connector-mcp` 3 / 23;
- `knowledge-memory` 1 / 8.
All exited 0.
- Dogfood, against freshly built dists (`turbo run build
--filter=@objectstack/dogfood... …`, 63/63), with both live URLs: `test`
171 files passed and 1 skipped; 1402 tests passed and 3 skipped.
- `typecheck` exit 0, after `pnpm install --frozen-lockfile`. The first
attempt failed on a missing link for `@objectstack/trigger-api`: the
`main` merge had added that dependency, and this worktree's install
predated the merge.
- The plugin-auth carrier pin
(`driver-fault-auth-log-carriers.test.ts`), on three dialects: 15/15.
- The runtime files that read these lines
(`seed-tenancy-autonumber-split`,
`expected-read-refusal-noise.channel-asymmetry`,
`metadata-list-ambient-vs-bare-transaction`,
`first-boot-migration-gate-read`): 4 files, 22 tests passed.
- Gates: `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 70 at `992e940fff`. All
70 ran with exit 0, and `--ran` reconciles 70 derived / 70 run / 0
NOT-MEASURED / 0 UNRUN.
- `check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET (exit
3) for 8 packages without a dist. After those were built, it exited 0.
- Also run: `pnpm check:live-db-isolation` PASS, and a control-byte
self-scan of the 26 touched files, with no hit.
- ESLint, narrowed to the 23 touched TS files, at `992e940fff`:
- the population read from `eslint.config.mjs` is
`packages/**/*.{ts,tsx,mts,cts}`;
- `--format json` gives 23 files, 0 errors and 0 warnings;
- invariance: the config enables no type-aware linting (no
`parserOptions.project`), so this diff cannot move an untouched file's
verdict.
- The repo-wide `pnpm lint` is CI's.
## Patch round 1
- **Why.** CI on `7445ed5cda` went red in `Test Core (1/6)`, on the
`driver-turso` pin that asserted the statement on the raw terminal's
line. That pin is this PR's, and it is flipped here. The shard's first
attempt had failed earlier, in an unrelated `spec` hook timeout
(#21421). Separately, the seat moved the cutter's home to
`@objectstack/types` (amendment 1, 5954587444), so that #21418 can call
it from `operatorFacingErrorText`.
- **What moved.** The module, its guard test and the four named exports
moved from `packages/core` to `packages/types`, and core's export block
was removed. Every importer was repointed, with the module-path strings
and comments that named core. The core `minor` changeset became a types
`minor` one, and the driver-sql and objectql changesets now name
`@objectstack/types`. Net against `main`: one rename, `objectql` →
`types`; `git diff origin/main...HEAD -- packages/core` is empty.
- **The sweep for other pins of the retired line.**
- Command: `git grep -n -E` over the test files of all 18 packages that
depend on `@objectstack/driver-sql`. The pattern covered the five lines'
leads, the `[sql-driver]` prefix, `refused a raw statement` / `refused a
read on` / `could not be resolved on`, `kept server-side`, `statement:
`, and an `includes('DATABASE_ERROR' | 'INVALID_FILTER' |
'INVALID_FIELD')` filter.
- Result: 35 hit lines in 28 files. Read one by one, exactly one is a
pin of the retired content, the `driver-turso` pin above.
- The rest are prose, unrelated words, the #7929 withheld-filter line,
or line consumers that key on the lead and the diagnostic. The four
runtime consumers among them were run, and are green.
- No other lane's package needed an edit.
- **Does `dispatch-gates --commands` derive the suites of packages that
depend on a changed package?** Measured: no.
- It maps a file surface to `check:*` gate families. Of its 70 commands
at `992e940fff`, none is a package test suite: the only `--filter`
command is `spec`'s `check:duration-unit-keys`.
- So it derives neither the changed packages' own suites nor their
dependents'. That is why round 0's local runs never reached
`driver-turso`.
## Acceptance notes
- **Deviation 1: two caller-facing envelope messages changed by one
sentence each.** The dispatch said no caller-facing envelope changes.
The read terminal's and the raw terminal's composed messages stated that
the statement was written to the server log, and this change makes that
false. The agent definition requires a released string that a change
makes false to be corrected in that change, and it wins on a conflict.
So each sentence now states what is written: the diagnostic, with the
statement and its bound values cut.
- The disclosure, code, status and `cause` are unchanged.
- `operatorFacingErrorText`'s copy matches only the leading "refused to
run a raw statement" fragment, which is kept; the producer pin
`sql-driver-16657-…` is green.
- Six hand-built fixture copies in five test files still carry the old
second sentence, in `metadata`, `metadata-protocol`, `rest`, `types` and
`objectql`. They stay green because the matcher keys only on the leading
fragment. They are left as they are: four of the files sit outside this
claim's surface, and editing only the fifth would split the copies.
- **Bounded in-place extension: the read terminal's two debug lines.**
The read terminal computes its dialect text once and writes it on the
warn line or on one of two debug lines (a pre-DDL question, or a table
whose DDL the driver deferred). The cut sits where the text is computed,
so all three take it. It is the same defect class and the same
mechanical call, in the claimed file, with the same gate family. It is
pinned on the deferred-DDL debug line.
- **The turso pin keeps no marker assertion.** The remote transport's
error is bare (no statement in front of the diagnostic), so the cut has
nothing to cut and adds no marker. The pin asserts the diagnostic, the
class of fault, no sentinel and no `statement:` field. Non-vacuity is
read off the sent statement the raw terminal was handed.
- **The process timezone.** Three live-matrix files require the process
zone to differ from UTC. Local runs set `TZ=America/New_York`, as CI's
live job does.
## Out of scope (reported, not edited)
- **`operatorFacingErrorText` (`@objectstack/types`) hands the raw
path's `cause` text back whole.** Measured on SQLite with a synthetic
sentinel bound into a raw statement: the envelope's message carries
none, and the helper's answer carries it. This is #21418's (the family's
fourth position), which can now call the cutter from the same package.
Its docblock also says the driver writes the statement one line earlier,
which is no longer true.
- **Stale prose outside this surface.** These no longer match the
driver's lines:
- `packages/qa/dogfood/test/raw-statement-fault-redaction.test.ts` (its
header says the driver's raw line writes the statement);
- `sql-driver-diagnostic-value-probe.test.ts`'s rationale for not
importing the redactor (the module paths in its failure messages are
updated here).
---
_Generated by [Claude
Code](https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3a6d92f commit 6d728b8
26 files changed
Lines changed: 859 additions & 201 deletions
File tree
- .changeset
- packages
- drivers
- driver-sql/src
- driver-turso/src
- objectql/src
- lifecycle
- types/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1290 | 1290 | | |
1291 | 1291 | | |
1292 | 1292 | | |
1293 | | - | |
| 1293 | + | |
1294 | 1294 | | |
1295 | 1295 | | |
1296 | 1296 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| |||
Lines changed: 9 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
241 | 241 | | |
242 | 242 | | |
243 | 243 | | |
244 | | - | |
| 244 | + | |
245 | 245 | | |
246 | 246 | | |
247 | 247 | | |
248 | 248 | | |
249 | | - | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
250 | 257 | | |
251 | 258 | | |
252 | 259 | | |
| |||
Lines changed: 6 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
274 | 274 | | |
275 | 275 | | |
276 | 276 | | |
277 | | - | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
278 | 283 | | |
279 | 284 | | |
280 | 285 | | |
| |||
Lines changed: 8 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
142 | | - | |
| 142 | + | |
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
149 | 148 | | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
150 | 156 | | |
151 | 157 | | |
152 | 158 | | |
| |||
Lines changed: 9 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
245 | 245 | | |
246 | 246 | | |
247 | 247 | | |
248 | | - | |
| 248 | + | |
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
252 | 252 | | |
253 | | - | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
254 | 261 | | |
255 | 262 | | |
256 | 263 | | |
| |||
Lines changed: 9 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
251 | 251 | | |
252 | 252 | | |
253 | 253 | | |
254 | | - | |
| 254 | + | |
255 | 255 | | |
256 | 256 | | |
257 | 257 | | |
258 | 258 | | |
259 | | - | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
260 | 267 | | |
261 | 268 | | |
262 | 269 | | |
| |||
0 commit comments