fix(driver-sql, driver-turso): SqlDriver.execute() declares a backend refusal as DATABASE_ERROR/500 instead of leaking the dialect's error object - #16650
Conversation
…e Turso remote transport Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
…alytics door; changeset Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
…iver-declared-fault
…g prose check:doc-authoring's cross-package prose-id leg counts issue ids inside string prose; the new warn line carried two. The docblock keeps its provenance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
📓 Docs Drift CheckThis PR changes 4 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 17 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 ac95e4049b068a1c309336e5d3d8060bceac9472 && git checkout ac95e4049b068a1c309336e5d3d8060bceac9472
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c8e5ac645f65dbaed0e44d8e40644f28a156a022 61b85c009b27083e808ba77dc326343198e22c1f && git checkout -B drift-repro c8e5ac645f65dbaed0e44d8e40644f28a156a022 && git merge --no-ff 61b85c009b27083e808ba77dc326343198e22c1f
node scripts/docs-audit/affected-docs.mjs --json c8e5ac645f65dbaed0e44d8e40644f28a156a022
|
…iver-declared-fault
…ordering, annotate the degrade docblocks, name three doors in the changeset Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
…iver-declared-fault
…he degrade notes exclude turso remote, the card is cited by number Text only: the changeset named the dispatcher's install route as the moving door and cited the follow-up card anonymously; the two degrade docblocks claimed the declared answer for driver-turso's remote transport, whose typed exits are undeclared and pre-date this card; the catch comment below them still said a declared 5xx is served through the ANALYTICS_QUERY_FAILED path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
…on off the declared envelope's cause The live-MySQL pin in sql-driver-keyed-text-mysql.test.ts asserted toThrow(/Duplicate entry/i) on execute()'s rejection — the dialect text off error.message, which the raw-path envelope now composes. It was the one red in the Temporal Conformance job on every head of this branch (step 12, ~100 s in), reproduced locally against MySQL 8.0.46 + PG 16.13 with the job's zone settings. The measurement is unchanged: the second value is rejected as a duplicate (isUniqueViolationError follows cause; the cause's own line still reads Duplicate entry) and one row remains. 167 files / 3599 tests green against both live servers after the change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
Fixes #16019
Clause-②: yes
What this lands
SqlDriver.execute()— the raw-SQL path every analytics compiler runs on — now terminates a backend refusal the way the typed read exits (find/count/aggregate) have since #8931: a composedDATABASE_ERROR/500envelope with none of the dialect's words, the dialect error whole under a non-enumerablecause, and the statement plus dialect message written to the driver's warn log.TursoDriverin remote mode — the one in-repo transport that hands the engine's text back with no statement in front of it — declares through the same protected terminal, so both transports leave the driver with one shape.looksLikeInternalErrorLeakis untouched in code; its docblock now records the ruling that froze it as the last-resort fallback.Maintainer ruling, decision batch #57, 2026-09-06 (issue comment 5559821104), reply verbatim: 「同意」 — option 3. ⛔ Option 1 (widening the heuristic) was not taken: no row was added, and
no such function:is pinned as UNCOVERED by the list. This also folds in the envelope half of #16028 (its compilation half already landed in PR #16204; the envelope half is exactly this declaration).CI round —
Temporal Conformance (live PG + MySQL)was red on every head of this branch, and it was this PR's. Measured, not picked: the job's step 12 (Run driver-sql suite against both live servers) concludedfailureon709d51df1f,a5acd49733and3fdd44c69calike (each ~100 s into the step), andsuccessonmainat my merge basecf74a11285— so it was neither the text round's delta (0 executable lines) nor the merge; it was the driver change on a cell that only runs with a live MySQL. Reproduced locally on3fdd44c69cwith PostgreSQL 16.13 (Asia/Shanghai) and MySQL 8.0.46 (+08:00) provisioned on this box,TZ=America/New_York,OS_EXPECT_LIVE_DIALECT_MATRIX=1, the job's own command (pnpm --filter @objectstack/driver-sql test):Test Files 1 failed | 166 passed (167),Tests 1 failed | 3598 passed | 1 skipped (3600), duration 98.79 s — one red,src/sql-driver-keyed-text-mysql.test.ts:322MEASUREMENT: a prefix-unique index rejects two DIFFERENT values sharing the prefix:expected [Function] to throw error matching /Duplicate entry/i but got 'The database refused to run a raw sta…'. That pin read MySQL's rejection offexecute()'serror.message— the one raw-exec consumer in this package that did, and exactly the class the changeset declares (the dialect text now lives oncause). Fixed in61b85c009bby re-aiming the pin, not the driver: the measurement is unchanged (the second value is rejected as a duplicate, one row remains) and is read where the text lives — the declaredDATABASE_ERROR/500 envelope, the cause-followingisUniqueViolationError, and the cause's ownDuplicate entryline. Re-run the same way:Test Files 167 passed (167),Tests 3599 passed | 1 skipped (3600), exit 0; the job's live-MySQL migration leg (pnpm --filter @objectstack/metadata-protocol exec vitest run live-mysql):3 passed (3),19 passed (19), exit 0; the touched non-SQL package under the job's skewed zone (service-analytics,TZ=America/New_York):96 passed (96),2163 passed (2163), exit 0;driver-sqltypecheck exit 0. The servers were stopped by their recorded PIDs and their data dirs removed.Text round (delta review, issue comment 5574921392 — PASS WITH FINDINGS, three text corrections). In
3fdd44c69c(after mergingorigin/maincf74a11285): the changeset names the moving publish door asPOST /api/v1/packages/publish(the barePOST /packagesis the dispatcher's install route, #3610) and cites #16657 by number; the two degrade docblocks no longer claim the declared answer fordriver-turso's REMOTE transport, whose typed exits are undeclared and pre-date this card (RemoteTransport.aggregateswallows a missing table into[]on the ObjectQL-aggregate strategy — pre-existing, not filed from here), and the pre-existing catch comment below them no longer says a declared 5xx is served through theANALYTICS_QUERY_FAILEDpath; #16657 drops the three swallow-only sites (overlay-index.ts,sys-setting-identity-index.ts,protocol.ts). Text only — no ablation is owed; the pins that read the edited files were re-run (below).Review round (contract review, issue comment 5573043610 — verdict FAIL on disclosure, substance CORRECT). Addressed in
a5acd49733and below, item by item: ① the two package doors are named and their code flip pinned at the catch and at the door; ② the analytics200 → 500move is in the changeset; ③ the two degrade docblocks now say what happens; ④ an ORDERING pin whose only red is a reversed arm order, proven by ablation; ⑤ the operator-detailclass is filed as #16657; ⑥ semver re-cited. Nothing the review called CORRECT or CORRECTLY DEFERRED was touched.The measurement the round opened on — why the read path never reached the
backendStatementFaultErrorwrapperRe-derived by symbol on
8341ed21ce(the dispatch's tree) and again on the merged head:SqlDriver.backendStatementFault, the terminal of the TYPED read exits —findRows(thefindhalf),count, andaggregateBackendFault(aggregate): threethrow this.backendStatementFault(...)sites and onereturn.NativeSQLStrategy.executecallsctx.executeRawSql; the plugin's auto-bridge (service-analytics/src/plugin.ts) callsengine.execute(knexSql, { args, object });ObjectQL.executeonly selects the driver and callsdriver.execute(rawCommand, params, options); andSqlDriver.executeawaitedthis.knex.raw(...)with notryat all. The raw path had no terminal, so the dialect's error object left the driver as knex handed it back:code: 'SQLITE_ERROR', nostatus, message shaped STATEMENT - DIAGNOSTIC.ObjectQLStrategypath goesengine.aggregate→driver.aggregate→aggregateBackendFault→ already declared. That is why the raw path was the one exit left.Which paths produce the bare shape — measured, not reasoned
statusorigin/mainbySqlDriver.executevia knex 3.3.0 + better-sqlite3select translate('ABC', 'ABC', 'abc') as x - no such function: translate,code: 'SQLITE_ERROR'startsWith('select ')limb — by accidentcompileSqlOnError: falsequery-executioner.jsprefixes unconditionally)TursoDriverremote →RemoteTransport.execute→@libsql/clientonfile::memory:SQLITE_ERROR: no such function: translate(LibsqlError,code: 'SQLITE_ERROR') — no statementsqlite_limb — by a different accidentno such function: translate— the card's own shapeexecuteRawSql(AnalyticsPluginOptions.executeRawSql, documented with apgpool example)Both driver-raised shapes now land on the declared path (
DATABASE_ERROR/ 500) and are driven in this PR's tests. The last row is the residual the ruling leaves to producers; it is pinned as the fallback's coverage boundary, not fixed.Scope
packages/drivers/driver-sql/src/sql-driver.ts— module-scoperawStatementFaultError(cause)besidebackendStatementFaultError; protectedSqlDriver.rawStatementFault(command, error)(the same "is it already ours" gate over the DECLARED status, warn-logs the statement and dialect text, composes);execute()wraps itsawait builder. ⛔formatOutput,readPresentationKind,presentReadValueuntouched — the diff's three hunks are at the composer (afterbackendStatementFaultError) and at the terminal plusexecute()(the "Raw Execution" section), andgit diff origin/main...HEADon the file has 0 lines naming those three symbols. Serial and independent of PR feat(driver-sql): the read door presents datetime values and audit stamps as canonical ISO-Z text on every dialect (#13973, B1 narrow) #16619 as the dispatch ruled.packages/drivers/driver-turso/src/turso-driver.ts— the remote branch ofexecute()calls the base terminal. PRTursoDriverConfig.timeouthas no seam on the WebSocket transport (wss:///ws://) in @libsql/client 0.17.4 — documented as unbounded, not delivered #16378 has since merged; its hunks and this one (:1407) were disjoint and merged without conflict.packages/types/src/error-leak.ts— documentation only: the module note no longer invites growth, and the ruling is recorded onDIALECT_LEAK_PHRASINGS.packages/services/service-analytics/src/analytics-service.ts— documentation only (review item ③): theisMissingSourceErrordocblock and thequeryDatasetdegrade comment now state that the degrade is reached by a BARE error only, that a driver-raised fault declares and is re-thrown before the question is asked ([finding]queryDataset里还有第二个 message 嗅探器isMissingSourceError,命中即静默返回空结果 ——dataset-compiler的一条拒收措辞已经命中它,只因抛点在 try 之外才没出事 #5717 defence B), and which producers still reach it. No code moved; the three degrade pins (dataset-degradation-envelope,missing-column-phrase-hard-failure,dataset-cross-datasource-registration) run green: 3 files, 35 tests.packages/drivers/driver-sql/src/sql-driver-keyed-text-mysql.test.ts(CI round) — the live-MySQL prefix-unique measurement reads the rejection off the declared envelope'scauseinstead oferror.message; the measurement itself is unchanged.packages/drivers/driver-sql/src/sql-driver-16019-raw-statement-fault-envelope.test.ts(a real better-sqlite3 driver),packages/drivers/driver-turso/src/turso-driver-16019-remote-raw-statement-fault-envelope.test.ts(remote mode over the libsql stub),packages/rest/src/analytics-16019-driver-declared-fault.test.ts(a realAnalyticsServiceand a realSqlDriverbehind the plugin's own bridge, a dataset dimension whose expression callstranslate(), throughPOST /analytics/dataset/query; the door-ordering block: declared wins, the ORDERING pin, the fallback stays, the bare undeclared residual pinned as the boundary),packages/rest/src/package-door-16019-raw-statement-fault-code.test.ts(review item ①, the door half),[#16019]blocks inpackages/services/service-package/src/publish-driver-fault.test.tsanddelete-driver-fault.test.ts(review item ①, the catch half),packages/types/src/error-leak.test.ts(the frozen-list block)..changeset/driver-raw-statement-declared-fault.md—@objectstack/driver-sqlpatch,@objectstack/driver-tursopatch; its wire section now names all three doors.What moves on the wire, named — three doors
POST /api/v1/analytics/dataset/query, driver fault on the raw path:500 {"code":"DATABASE_ERROR","error":"Internal server error"}through the route's ③a declared-fault relay — was500 ANALYTICS_QUERY_FAILED(withInternal server errorwhen the heuristic fired, the raw engine text when it did not). Status unchanged; the code is now the producer's, exactly as the typed read exits' faults have answered at this door since PR fix(driver-sql): a dialect error the driver cannot attribute leaves the read exits as an ADR-0112 backend-fault envelope (#8931) #9273./analytics/query(dispatchererrorResponseBase,serverFaultProvenance === 'declared') withholds by the same declaration — the declared-5xx behaviour there is already pinned byanalytics-query-read-scope-withhold.test.tsand is unchanged.execute()runs):500 DATABASE_ERRORwhere it was200 {"rows":[],"fields":[],"totals":[]}plus awarn.queryDataset's missing-source degrade sits behind its declared-envelope re-throw ([finding]queryDataset里还有第二个 message 嗅探器isMissingSourceError,命中即静默返回空结果 ——dataset-compiler的一条拒收措辞已经命中它,只因抛点在 try 之外才没出事 #5717 defence B: a declared envelope is re-thrown untouched, whatever it says; [finding]isMissingSourceError仍把 Postgres 的「缺列」措辞column "c" of relation "t" does not exist判为「缺源」——文档明说不该,今天靠读路径不产生该措辞而无害 #6035's ordering is unaffected — it throws bare by design), so a driver-raised missing table no longer reaches the degrade — the answer the ObjectQL-aggregate strategy has given since PR fix(driver-sql): a dialect error the driver cannot attribute leaves the read exits as an ADR-0112 backend-fault envelope (#8931) #9273, now on both strategies. The degrade still applies to an undeclared producer (an embedder's ownexecuteRawSql, the framework's not-registered signals). Named in the changeset (review item ②); the docblocks say so (item ③). ⛔ Not re-judged here: the existing rulings decide it.POST /api/v1/packages/publishandDELETE /api/v1/packages/:id(review item ①): a raw-exec driver fault undersys_packagesanswers500 {"code":"DATABASE_ERROR"}with the composed sentence as its message, where it was500 PACKAGE_PUBLISH_FAILED/500 PACKAGE_DELETE_FAILED.PackageService.publish/deletere-throw any throw thatdeclaresHttpAnswer(a numericstatusorstatusCode— the field this PR adds), and the door'ssendThrownErrorrelays the producer's code; before, the same fault carried nostatus, took the swallowing branch and the door answered its own code. Same status band, no dialect text on the wire either way (the composed sentence trips no heuristic and carries no dialect word; a declared fault that DID carry dialect text is still withheld — pinned). Entailed bydeclaresHttpAnswer's own docblock ("a declared 5xx is re-thrown too"), and not re-judged:service-packageshould instead keep its ownPACKAGE_*_FAILEDcodes for driver faults, that is a design change beyond this ruling and is reported, not implemented. My earlier survey mischaracteriseddelete(it branches on the status, not on text) and omittedpublish; both are now named and pinned.execute():error.messageis the composed sentence;error.codeisDATABASE_ERRORwhere it was the backend's errno;error.statusis500where it was absent; the backend's error object is oncause. Sites that surfaceerror.messageas an operator-facingdetail(runtime-index-preflight,seed-tenancy-backfill,partial-index-probe— whose comment now states the opposite of what it does —cli db clean, and the migration result records) are filed as Raw-exec consumers that surfaceerror.messageas an operator-facingdetailnow read the composed DATABASE_ERROR sentence — readcausethere (follow-up to #16019) #16657 (review item ⑤: readcausethere); the dialect text still reaches every default deployment one line earlier through the driver's warn sink, andcausecarries it for code.Semver
patch, on AGENTS.md's changeset rule (Post-Task Checklist item 3: "A bug fix in a released package takes apatchchangeset — never none"; breaking is defined there as removing or renaming something an author can write — a spec key, an export, a config field — and nothing here does:execute()staysPromiseofany, andcode/statuswere untyped before), and on the precedent of the identical change on the typed read exits — #8931, PR #9273 (cbf4b40b05), which shipped"@objectstack/driver-sql": patch.check-changeset-no-major.mjs --base origin/mainexit 0. (Review item ⑥: the "lane's row" I cited before is a dispatch-seat convention, not a repo document; withdrawn.)Verification — on
a5acd49733(merge oforigin/main4fe00b80e7); driver ablation on7569f2a7a5, ordering ablation ona5acd49733Every exit captured after a redirect; where a run was locked, the wrapper's own
VERDICTline was read.pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2—Test Files 157 passed | 10 skipped (167),Tests 2402 passed | 141 skipped (2543), exit 0, at7569f2a7a5; the driver source is byte-identical since709d51df1f(one log literal after that head), and the looksLikeInternalErrorLeak recognisesno such column:but notno such function:— a SQLite parse failure echoes the raw engine message into the 500 body #16019 file re-ran green there (7 passed).pnpm --filter @objectstack/driver-turso exec vitest run --maxWorkers=2—Test Files 46 passed (46),Tests 1182 passed (1182), exit 0 (at7569f2a7a5); the looksLikeInternalErrorLeak recognisesno such column:but notno such function:— a SQLite parse failure echoes the raw engine message into the 500 body #16019 file re-ran ona5acd49733after PRTursoDriverConfig.timeouthas no seam on the WebSocket transport (wss:///ws://) in @libsql/client 0.17.4 — documented as unbounded, not delivered #16378 merged into this branch: 4 passed.pnpm --filter @objectstack/types exec vitest run --maxWorkers=2—Test Files 20 passed (20),Tests 580 passed (580), exit 0.pnpm --filter @objectstack/service-package exec vitest run --maxWorkers=2(review item ①, the catch half) —Test Files 5 passed (5),Tests 79 passed (79), exit 0 ona5acd49733.pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2over the two looksLikeInternalErrorLeak recognisesno such column:but notno such function:— a SQLite parse failure echoes the raw engine message into the 500 body #16019 files and the seven package-door pins (package-publish-status-classification,package-delete-status-classification,package-door-5xx-message-sanitization,package-door-declared-code,package-door-user-message,package-routes-coded-error-mapping,package-door-5xx-demoted-code-withhold) —Test Files 9 passed (9),Tests 261 passed (261), exit 0 ona5acd49733; the earlier run of the eight analytics/5xx door files —8 passed (8),108 passed (108)— stands from7569f2a7a5.pnpm --filter @objectstack/service-analytics exec vitest run --maxWorkers=2over the three degrade pins —Test Files 3 passed (3),Tests 35 passed (35), exit 0.typecheck:driver-sql,driver-turso,typesexit 0 (earlier heads; their sources are unchanged since);service-package,rest(incl.check:test-typecheck: OK),service-analyticsexit 0 ona5acd49733.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandswith no hand-listed paths, re-derived ona5acd49733, every command run individually with its exit captured after a redirect;--ranreconciliation:Run reconciliation — 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN.57 exit 0. Two exit 3 and are NOT MEASURED in their own words —check:dual-build-cjs-loads: "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/." … "⛔ This is NOT a pass: nothing was measured.";check:type-check-debt: "check-type-check-coverage: PREREQUISITE NOT MET" … "⛔ This is NOT a pass and NOT a finding: nothing was measured". Both need the whole-workspace build CI performs; not chased, per dispatch.check:doc-authoringgreen ("sibling-package prose ids hold the baseline — 829 pinned site(s) across 231 file(s) … no growth");check:nul-bytesexit 0.3fdd44c69c: the three degrade pinsTest Files 3 passed (3),Tests 35 passed (35); the two rest looksLikeInternalErrorLeak recognisesno such column:but notno such function:— a SQLite parse failure echoes the raw engine message into the 500 body #16019 files2 passed (2),13 passed (13); the two service-package looksLikeInternalErrorLeak recognisesno such column:but notno such function:— a SQLite parse failure echoes the raw engine message into the 500 body #16019 files2 passed (2),38 passed (38);service-analyticstypecheck exit 0; after the merge,rest-server.tsstill carries the ③a relay before ③b (declaredServerFaultAnswer(error)at:11086, the ③b withhold at:11096). Gate union re-derived on3fdd44c69c:Run reconciliation — 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN.— 57 exit 0, the same two exit 3 NOT MEASURED;check:doc-authoringgreen,check:nul-bytesexit 0.61b85c009b: gate union re-derived:Run reconciliation — 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN.— 57 exit 0, the same two exit 3 NOT MEASURED;check:doc-authoringgreen,check:nul-bytesexit 0.pnpm lint(repo-wide eslint) is CI's run; not run here and not claimed.Reverse verification
Driver terminal — both legs on
7569f2a7a5, predictions written before running. Predicted: driver-sql pin 5 red / 2 green; door pin 2 red / 4 green (block 1 cases 1 and 2 —codereturningANALYTICS_QUERY_FAILED, and the absent driver log line — with itsINTERNAL_ERROR_MESSAGEhalf green by the re-scope's accident; block 2 green throughout); turso 4 green. Leg A, mutate:execute()restored to the bareconst result = await builder;(composer and terminal left in place). On-disk proof: injected spelling count 1, removedthrow this.rawStatementFault(command, error)count 0,git diff --stat1 file, +1 −7. Rebuiltdriver-sql(exit 0);ablation-dist-preflight.mjs @objectstack/driver-sql 'this.rawStatementFault(command, error)' --absent→ "✓ dist/: marker absent from all 6 built files". Observed: driver-sqlTests 5 failed | 2 passed (7), restTests 2 failed | 4 passed (6)(first redexpected 'ANALYTICS_QUERY_FAILED' to be 'DATABASE_ERROR', second red the absent driver log line), turso4 passed (4)— no unpredicted direction. Leg B, restore:git checkout HEAD -- ABS_PATH;git rev-parse HEAD:packages/drivers/driver-sql/src/sql-driver.ts=c31eb97dc6e18aac52d0f115f5c4c6efe7cb002b=git hash-objectof the file on disk (neither empty); porcelain empty; whole-treegit diff HEAD --statempty. Rebuilt (exit 0); preflight without--absent→ "✓ dist/: marker present in 2 built files". Re-run: driver-sql 7, rest 6, turso 4 passed. Resolver: rest and turso consume@objectstack/driver-sqlthroughexports→dist/(both pairs inKNOWN_UNALIASED_TEST_IMPORTS), hence rebuild + preflight per leg.Ordering pin (review item ④) — both legs on
a5acd49733, prediction written before running: exactly 1 red (the ORDERING pin,ANALYTICS_QUERY_FAILEDin place of the producer's code), 6 green — including the neighbouring "phrase the heuristic does not know" case, which is precisely why it could not stand in for this one. Leg A:rest-server.ts's ③a relay gated behind the heuristic being false (const declaredFault = looksLikeInternalErrorLeak(msg) ? undefined : declaredServerFaultAnswer(error)), i.e. heuristic consulted first; on-disk proof: marker count 1, original line count 0,git diff --stat+1 −1; rest resolves./rest-serverfrom source, so no build is involved. Observed:Tests 1 failed | 6 passed (7), the one redexpected 'ANALYTICS_QUERY_FAILED' to be 'DATABASE_ERROR'on the ORDERING pin — as predicted. Leg B:git checkout HEAD -- ABS_PATH; HEAD blob53ec32739e6b5812fd3b36241e4e742f6e62d522= disk blob; whole-tree porcelain empty; marker count 0; re-run7 passed (7).The package-door pins (item ①) are behaviour pins on catches this PR does not edit: their "AFTER" cases hand each catch the shape the driver now raises and assert identity on the re-throw and
500 DATABASE_ERRORat the door; their "BEFORE" controls hand the undeclared ancestor and assert the swallowing branch andPACKAGE_*_FAILED. They pass onorigin/mainas well, by construction — the flip is a property of the fault's shape, which this PR changes at the driver, and the driver ablation above is what makes those doors' AFTER cases the driver's to answer.The live-MySQL pin (CI round) discriminates on its own two runs: the pre-fix assertion (
toThrow(/Duplicate entry/i)onerror.message) was the one red against the declared envelope, and the re-aimed assertion is green — while the driver ablation above is what turns itscauseread red (no envelope, nocause).验收备注 (acceptance notes)
error.messageas an operator-facingdetailnow read the composed DATABASE_ERROR sentence — readcausethere (follow-up to #16019) #16657 (review item ⑤): raw-exec consumers that surfaceerror.messageas an operator-facingdetailnow read the composed sentence —runtime-index-preflight(:298,:337),seed-tenancy-backfill(:1184,:1259,:1305,:1388,:1414,:1447),partial-index-probe(:37,:359, whose comments now say the opposite of what happens),cli db clean(:115), and the four migration result records inpackages/metadata/src/migrations/*(three swallow-only sites the card first listed —overlay-index.ts,sys-setting-identity-index.ts,protocol.ts— were dropped after the delta review: they store nothing). Remedy on the card: readcausethere; correct the two comments.RemoteTransport.find(driver-turso) keeps its own message sniff (no such column→ projection retry) — pre-existing, onthis.client.executedirectly, not on the changed path; untouched (the review: CORRECTLY DEFERRED).securitylabel deliberately NOT applied — triage ruled twice that the leaked text is a function name we emitted, not user data or physical names.service-packageshould keepPACKAGE_PUBLISH_FAILED/PACKAGE_DELETE_FAILEDfor a declared driver fault (a design change beyond the ruling), and the analytics degrade question (open question 3 of the report), for the PM.