fix(types): operatorFacingErrorText answers through the one driver-fault cut - #21482
Conversation
…ult cut WIP: the helper's single exit passes its answer through redactStatementFromMessage; text reached below the raw-path sentence is cut with statementSent, everything else asks the shared leak predicate. Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz Co-authored-by: Claude <noreply@anthropic.com>
WIP: enumeration pin of operatorFacingErrorText's callers, sentinel pins at each caller's carriers (cli, metadata-protocol, metadata), the real-producer leg in driver-sql, and the stale envelope prose corrected. Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz Co-authored-by: Claude <noreply@anthropic.com>
…ation field apart from the carriers Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 87de8854793865f656d4df3971c03a895b7f83f4 && git checkout 87de8854793865f656d4df3971c03a895b7f83f4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 100c394f6fd0113377bccdabed6ecf3b04b720da 9f5fba42f8352996539d1967a57c67fd90899395 && git checkout -B drift-repro 100c394f6fd0113377bccdabed6ecf3b04b720da && git merge --no-ff 9f5fba42f8352996539d1967a57c67fd90899395
node scripts/docs-audit/affected-docs.mjs --json 100c394f6fd0113377bccdabed6ecf3b04b720da |
The case loaded the Config inside its clocked window; with the package built that load imports every command module and was the whole cost of the case, past vitest's default budget on a loaded shard. It is now paid once during collection. The assertions are unchanged. Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21418
Clause-②: no
What this changes
operatorFacingErrorText(packages/types/src/driver-error-classification.ts) now returns cut text by construction. Its single exit passes the answer throughredactStatementFromMessage, the one driver-fault cut inpackages/types/src/driver-fault-redaction.ts. That module is unedited (empty diff), and no copy of the cut is made. No caller is edited.This carries out the maintainer's ruling A on #21385 (
5950942037), quoted verbatim:It also carries out triage's ruling
5954287249: "the helper returns cut text by construction", with ⛔ no cut repeated at the callers.The walk decides which rule the cut runs under. It goes by what the walk knows about the text, not by what the text looks like:
{ statementSent: true }. That is the same argument the driver's own raw-terminal log line passes, so the operator's stored record equals the text of that line.What survives:
code,status, class andcausereach every classifier unchanged. For example,classifyIndexFailurestill answersconflict, andisMissingTableErrorreads the same chain.Caller census (for the raise rule in
5954287249)The tree has eight caller files with 15 call sites. Triage and the dispatch said "seven", but the list they gave has eight files. I re-derived the list two ways:
git grep -lnat24db8a1c, with the defining module andindex.tsas the control;packages/cli/src/commands/db/clean.tspackages/metadata-protocol/src/migrations/partial-index-probe.tspackages/metadata-protocol/src/migrations/read-probe.tspackages/metadata-protocol/src/migrations/runtime-index-preflight.tspackages/metadata-protocol/src/migrations/seed-tenancy-backfill.tspackages/metadata/src/migrations/drop-projection-tables.tspackages/metadata/src/migrations/migrate-env-id-to-project-id.tspackages/metadata/src/migrations/migrate-project-id-to-environment-id.tsWhat the five
seed-tenancy-backfill.tssites bind:Reading for the seat:
seed-tenancy-backfill.ts, binds stored values the migration read itself, at its stamp and counter-merge sites.Pins
The helper (
driver-error-classification.operator-text.test.ts, new[#21418]block). A synthetic sentinel is bound into a raw statement in four dialect shapes:Further cases cover a re-wrapped envelope, the depth bound, a string
causeand an undeclared driver dump. In every case:causeis first shown to carry the sentinel;Two more cases complete the block. One pins that the thrown value is untouched (code, status, class,
cause, stack). A CONTROL case pins that non-dump text comes back byte-identical.The real producer (
driver-sql,sql-driver-16657-operator-facing-cause-text.test.ts). It runs a realSqlDriver.execute()refusal with the sentinel bound through knex, on a statement the predicate cannot read. Three things are asserted:causecarries the sentinel;Every caller's carriers:
metadata-protocolandmetadata'sraw-exec-operator-detail-16657.test.ts, which have new[#21418]blocks;packages/cli/src/commands/db/clean.operator-text-21418.test.ts, which runs the real oclif command and stays in theunittier.Each case scans the returned result and every recorded log line, message and meta, for the sentinel. It also asserts that the diagnostic and the site's verdict survive (
unreadable,conflict,absent,error, the exit code). One field is read separately: the backfill's receipt declares theorganizationIdit adopted, by design and not through the helper.The enumeration pin (
driver-error-classification.callers.test.ts, thetest:repoproject, on its already-declaredpackages/**/*.tsradius). It freezes the eight callers with their call-site counts. Each caller names its sentinel pin file, and the test checks that file exists, imports the caller and binds the sentinel. A positive control and a renamed-import check close its two blind spots.Reverse verification
The fix was committed first (
24db8a1c). Each mutation went throughscripts/ablation-replace.mjs:@objectstack/typeswas rebuilt;ablation-dist-preflightfound the marker in 2 built files.Each restore was proven: blob == HEAD and
git diff HEADempty. The restore leg rebuilt, both markers were absent from all 12 built files, and the tree was clean.First attempt void. The DTS build refused both mutations because each left a binding unused. No pin ran. Both legs were re-run with mutations that keep the binding read.
Leg A: the call to the cutter reverted, nothing else.
Every new sentinel case went red. Only the
[the fixture]cases and the CONTROL case stayed green. The#16657cases that now assert the cut answer also went red.Leg B: the cut kept, but what the walk knows dropped (predicate only). Red counts:
So the walk's knowledge is load-bearing exactly where the predicate is blind.
Restore: 22/22, 25/25, 11/11, 2/2 and 3/3.
Verification (at
24db8a1cunless stated)@objectstack/types:test: 705 passed;test:repo: 11 passed;typecheck: exit 0.--listFilesincludes both edited test files.@objectstack/metadata-protocol:89871414: 3,091 passed and 3 failed, all 3 in the new block. The cause was a fixture that read the receipt's declared organization field as a carrier. It is fixed in24db8a1c.24db8a1c: that file passes 25/25, andtypecheckexits 0.@objectstack/metadata: the full suite passed 840 (56 files), andtypecheckexits 0.@objectstack/cli:unittier ran the new pin together withtest/vitest-tiers-partition.test.ts: 24 passed. Theintegrationtier is declared to CI.typecheckis NOT MEASURED, because the 60-package closure was not built.tscover the new file found 0 errors in it. The 3 errors it reported are inclean.ts, all missing declarations of unbuilt workspace dependencies.@objectstack/driver-sql:#21385refusal-line pin: 16 passed, 2 live skips;typecheck: exit 0;objectqlandrest: each edited test file passes, 13/13 and 4/4.qa/dogfood: this PR changes only a comment there, so the suite is NOT MEASURED.Gates:
dispatch-gates.mjs --commandsderived 73 at24db8a1c.check-engine-split-ratiofirst refused on the shallow checkout. It exited 0 after the prescribed--shallow-sincefetch.check:dual-build-cjs-loads(it needs every package'sdist), andcheck:i18n,check:i18n-coverageandcheck:i18n-walk-parity(they need the built CLI). As a scoped check instead, thetypesdist loads under bothrequire()andimport.--ranreconciliation: 73 accounted for, 0 UNRUN.Lint, by proven narrowing at
24db8a1c:isPathIgnored, ignores none of the 11 touched TS files;--format jsoncounts 11 files, with 0 errors and 0 warnings;eslint.config.mjsenables no type-aware linting (noparserOptions.project), and its only load-time reads are two baseline JSON files this diff does not touch. So the diff cannot move any untouched file's verdict.The full
pnpm lintis CI's.Stale prose carried from #21385's ACCEPT (
5957209724), each verified against the treeoperatorFacingErrorText's docblock ("the driver writes the statement…")metadata-protocolraw-exec-operator-detail-16657.test.ts, raw envelope copymetadataraw-exec-operator-detail-16657.test.ts, raw envelope copyrestpackage-door-16019-raw-statement-fault-code.test.ts, raw envelope copylooksLikeInternalErrorLeak(COMPOSED) === falseassertion holds for the new sentencetypesdriver-error-classification.operator-text.test.ts, raw and read-exit copies (2)objectqlengine-find-missing-table-log-level.test.ts, read-exit copypackages/qa/dogfood/test/raw-statement-fault-redaction.test.tsheaderdriver-sqlsql-driver-diagnostic-value-probe.test.tsrationaleAll six envelope copies are now byte-equal to the producer's two sentences. I measured this by evaluating each copy against
sql-driver.ts. The same stale premise was also in the headers of thetypesoperator-text test and themetadata-protocoltest, and both are corrected. These edits change test and comment text only.Acceptance notes
The census count: eight files, not seven (see above).
A boundary, not filed. An undeclared throw is cut under the shared leak predicate, so an undeclared dump that the predicate cannot read would come back whole. No producer reaches a caller that way today:
IDataDriver.execute;driver-sqldirectly anddriver-tursothrough the same composition.Carrier: none.
Comment drift, not filed. The
@objectstack/typesentry inscripts/cross-package-test-inputs.mjsdescribesdriver-error-classification.callers.test.tsas theisMissingTableErrorgate only. The enumeration pin added here rides the same declared radius. Carrier: none.Engine-package test paths for the seat to declare:
packages/metadata-protocol/src/migrations/raw-exec-operator-detail-16657.test.ts,packages/metadata/src/migrations/raw-exec-operator-detail-16657.test.tsandpackages/objectql/src/engine-find-missing-table-log-level.test.ts;packages/drivers/driver-sql/src/,sql-driver-diagnostic-value-probe.test.ts(comment only) andsql-driver-16657-operator-facing-cause-text.test.ts. The second is outside the claim's listed surface: it is the real-producer leg.There is no source edit in any
domain:enginepackage.Patch round 1 (
9f5fba42f8): theos db cleanpin no longer pays oclif's load inside its clocked windowAdded by the
domain:cliseat from the dev's patch-round report on #21418; the measurements are the dev's, on the shared 4-vCPU container.The red: on
24db8a1c, Test Core (6/6) failed becauseclean.operator-text-21418.test.ts› "the failure line names the file and the dialect diagnostic, and carries no sentinel" timed out at vitest's default 5000 ms.Where the time went. Phase timers ran in a throwaway copy, which was deleted and never committed. The command's own run took 11–119 ms. The rest of the case was oclif's
Config.load, which the case paid inside its clocked window by handingDbClean.runa{ root }: 99.3–99.7% of the case. On a built package with nooclif.manifest.json, that load imports every command module.Config.load24db8a1cThe fix (test file only). The Config loads once at module scope and is passed to
DbClean.run, per AGENTS.md's rule "Clocked windows measure behaviour, never loading" and the in-package precedentsrc/commands/datasource/envelope-unwrap.test.ts. The assertions are unchanged. There is no skip, retry, quarantine or timeout change, and the file stays in theunittier.Before / after, interleaved pairs, same command and load:
24db8a1c)9f5fba42f8)The load moved into collection, which is not clocked. vitest's import phase for the file went from 6.2–6.8 s to 9.6–10.4 s idle, and from 42.7–63.1 s to 64.9–94.2 s at 24 busy loops.
Gates at
9f5fba42f8. The 73 families re-derived with no paths are identical to round 0, and all 73 exit 0.check:i18n*andcheck:dual-build-cjs-loadswere NOT MEASURED in round 0; they were measured green this round.Generated by Claude Code