Skip to content

fix(driver-sql): the five refusal log lines take the shared driver-fault cut, moved to core - #21414

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-21385-driver-log-redaction
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-21385-driver-log-redaction

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

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 ([security] operatorFacingErrorText hands a raw statement fault's cause message, statement and inlined values included, to its operator-facing callers: the fourth position of the #21274 / #21345 / #21385 family #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 fix(objectql): redact a driver fault where it leaves the engine, not only in the engine's log line #21335 and PR fix(objectql): the raw-statement door and the lifecycle sweep cut a driver fault by construction (#21345) #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

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)


Generated by Claude Code

claude added 7 commits October 2, 2026 11:47
…ult cut, moved to core

The driver-fault redaction module moves from objectql to core so driver-sql
can import it. The read terminal, the raw terminal and the three
unresolvable-column refusals now write the dialect text through the log face
of that cut with statementSent, and the raw terminal no longer writes the
sent statement. Code, class of fault and the dialect diagnostic are kept.

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
…lip the old statement-in-log pins

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
…e shared driver-fault cut

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
…and a control case

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/driver-sql, @objectstack/objectql, @objectstack/types, touching 64 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/objectql/src/engine.ts, packages/objectql/src/lifecycle/lifecycle-service.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json ceb4a939b42f7c91ac8e9d371df34b8663d3f7bc.

⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/objectql/src/engine.ts, packages/objectql/src/lifecycle/lifecycle-service.ts) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 26 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ceb4a939b42f7c91ac8e9d371df34b8663d3f7bc → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a6867ed274658daedd7c4f2c210db669f58a0ff6 — the merge of head 992e940fff9b056e18b26d91363b026ca544c2fd into base ceb4a939b42f7c91ac8e9d371df34b8663d3f7bc, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a6867ed274658daedd7c4f2c210db669f58a0ff6 && git checkout a6867ed274658daedd7c4f2c210db669f58a0ff6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ceb4a939b42f7c91ac8e9d371df34b8663d3f7bc 992e940fff9b056e18b26d91363b026ca544c2fd && git checkout -B drift-repro ceb4a939b42f7c91ac8e9d371df34b8663d3f7bc && git merge --no-ff 992e940fff9b056e18b26d91363b026ca544c2fd

node scripts/docs-audit/affected-docs.mjs --json ceb4a939b42f7c91ac8e9d371df34b8663d3f7bc

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ceb4a939b42f7c91ac8e9d371df34b8663d3f7bc → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

CI note on head 7445ed5cd: domain:engine#1 · session_017xfMoEjKUuSh2xYB8sCozp.


Generated by Claude Code

claude added 3 commits October 2, 2026 14:27
…e lowest home every face reaches; core leaves the diff

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
…name @objectstack/types

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 16:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 16:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 6d728b8 Oct 2, 2026
43 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21385-driver-log-redaction branch October 2, 2026 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants