Uh oh!
There was an error while loading. Please reload this page.
fix(metadata-protocol): withhold caught driver text from the batch verbs' response payloads (#8333) - #8436
Conversation
…rbs' response payloads (#8333) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
…ts authoring-feedback bound (#8333) Adds protocol.batch-verb-driver-text.test.ts: the eight P6-P13 reproductions, the [GUARD] over-block bounds, the mandatory positive control (a real broken-CEL approval flow still names the offending field on publish), P9's producer-side declaration, and the operator half (the withheld line still reaches the log). Two objectql fixtures pinned pre-#8333 behaviour and are re-judged individually: the all-or-nothing publish double threw a `code` with no `status`, which the real producer never does (ITEM_LOCKED is 403) - declaration added; the throwing materializer's response-shape test keeps its shape assertion and takes the withheld string, since a bare plugin throw is indistinguishable from driver text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
…ch-verb-driver-text
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
os-zhuang
commented
Aug 13, 2026
ACCEPT — PM review, |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8333
Extends the rule #8136 installed for the uninstall cluster (P1–P5) to the eight batch-verb producers it enumerated and deliberately did not convert (P6–P13). Each of these reaches a client as data on a response rather than as a message, so no HTTP boundary's 5xx message withhold has ever been able to reach them.
The rule, unchanged: a caught error's sentence is quoted back to a caller only when that error declared itself a client-facing refusal — a 4xx
statusin the ADR-0112 envelope. Anything else gets a stable sentence, and the original goes to the server log.Step 1 was the measurement, and it decided the outcome
#8333 is explicit that these eight must not be swept blind. Every site was driven for real against a failing
sys_metadatabefore a line changed. Measured onorigin/main:publishPackageDraftsfailed[].errorSQLITE_ERROR: no such table: sys_metadataNOT_OVERRIDABLE403 · author-timeINVALID_METADATA422 +issues·METADATA_CONFLICT409failures[].errormaterializeApplied.errorerrorZodErrorduplicatePackagefailed[].errorINVALID_METADATA422revertCommitfailed[].errorVERSION_NOT_FOUND404,ITEM_LOCKED403,VERSION_NOT_RESTORABLE409,WRITABLE_PACKAGE_REQUIRED422rollbackToPackageCommitfailed[].errorCOMMIT_NOT_FOUND404migrateStoredMetadatarows[].reasonINVALID_METADATA422,ITEM_LOCKED403Two measured facts changed what this PR does:
{ success: false, error }; only driver faults throw. So converting the catch blanks no authoring feedback, and the returned string is pinned as untouched.applySeedBodiesusedSeedLoaderRequestSchema.parse(), so a malformed seed body arrived as a rawZodError— authoring feedback that declared nothing. Per the card's step 2 the cure is at the producer, not the collector: it is now asafeParseraising a real422 INVALID_METADATA. The author is strictly better off — that field carried a multi-line dump of zod internals and now carries the curated, path-pointing summary every other authoring surface produces.These sites also wrote no log at all, so each conversion adds the
console.warnthat keeps the operator's half of the contract — the shapedeletePackage's cleanup collector already uses.The mandatory positive control
A real spec-validation failure on the publish path — a
flowdraft whose approval node carries broken CEL, the same worked exampleruntime-gate.test.tsuses — still reports, after the change:Which draft, which field, plus the machine-readable halves the Studio form highlights with. Green.
Reverse verification — direction predicted before running
Predicted with
protocol.tsreverted: 16 red / 10 green. Measured: 17 red / 9 green.console.warnpredates this card — it came back RED, and rightly: that case asserts BOTH halves, andexpectNothingLeakedruns first over a pre-fix payload that still carried the driver line. So it is evidence for the withhold, not a guard on the logging.The
[GUARD]sections earn their place under a different variant: withdeclaresClientRefusalforced to returnfalseunconditionally, they go red — measured 8 red / 18 green. A second small miss recorded there too: 9 were predicted red and 8 came back, the survivor being "P8 leaves a RETURNED authored refusal alone" — correctly green, because that string never passes through the predicate. The variant run therefore doubles as proof of this PR's central measured claim.Deliberately not touched
the flow does not canonicalize: …andthe source item does not convert: …interpolate authored schema-validation text, not driver text. [finding]metadata-protocol's batch verbs still put caught error text on client-facing payloads — the 8 producers option C did not reach #8333 records them as examined-and-left.looksLikeInternalErrorLeakis not widened; the REST door's withhold is not retired; The package door's 4xx paths still ship raw driver text — a returned failure never meets the 5xx withhold, and is mislabelled a client error #8131's status-classification defect stays out.codelimb besidefailed[].erroris a different field with a different rule (a closed union — seecarryCatalogedErrorCode) and is filed separately rather than ridden in here.Verification
@objectstack/metadata-protocol— 81 files, 1196 tests green, on the merged tree.--filter '...@objectstack/metadata-protocol'= packages that DEPEND ON it), against a rebuiltdistsincepackages/restimports the built package:@objectstack/objectql197 files / 3539 green,@objectstack/rest111 files / 1827 green.metadata-protocol's batch verbs still put caught error text on client-facing payloads — the 8 producers option C did not reach #8333 behaviour and were re-judged individually rather than batch-re-spelled: the all-or-nothing publish double threw acodewith nostatus, which the real producer never does (ITEM_LOCKEDis 403) — declaration added, assertion unchanged; the throwing-materializer response-shape test keeps its shape assertion and takes the withheld string.check:nul-bytes,check:engine-double-contract(195 pinned — the new doubles are pinned, not ledgered),check:error-code-casing,check:durability-log-level,check:filter-alias-parity,check:cross-package-test-inputs,check:changeset-gate-self-tests,check:objectui-changeset,check:query-options-erasure,check:type-check-coverage,check-changeset-no-major— all green. Families re-derived against the actual changed paths withscripts/pm/dispatch-gates.mjs.Merged
origin/mainbefore pushing; #7748 (PR #8401) had landed and all measurements were re-run against the merged tree. No conflict withrevertCommit.Generated by Claude Code