Uh oh!
There was an error while loading. Please reload this page.
fix(metadata-protocol): filter the batch verbs' failed[].code through the error catalog (#8441) - #8505
Conversation
…gh the error catalog (#8441) #8333 closed the `error` limb at these collectors and left the sibling `code` alone as a different field with a different rule. Measured on the fixed branch, `publishPackageDrafts` still answered `{ error: 'publish failed', code: 'SQLITE_ERROR' }` and `revertCommit` the same — response DATA no HTTP boundary's withhold reaches. `code` writes `ApiErrorSchema.code`, a closed union (ADR-0112 D4), so the rule is CATALOG MEMBERSHIP — `carryCatalogedErrorCode`'s predicate, itself `toRowApiError`'s — not the 4xx question. A catalogued code passes through byte for byte; an uncatalogued one becomes the code its declared status maps to, else INTERNAL_ERROR. No code was minted, and an error with no code still yields no key. `discardPackageDrafts` / `deletePackage` build the same limb and need no filter: they wrap `deleteMetaItem`, whose re-wrap exits already gate `code`. Measured and pinned rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
… a SCREAMING-free SQLSTATE fixture (#8441) Direction (b), the drop-the-limb variant, was predicted 8 red / 4 green and measured 9 red / 3 green. The miss is section 6's: the prediction reasoned about the log half and forgot the case asserts the payload first (`expectCataloged`), which is the SAME miss #8333 recorded for its own P7 log case. Recorded rather than tidied — it makes section 6 evidence for the substitution, not only for the logging. The off-catalog fixture now spells its dialect as a postgres SQLSTATE (`42501`, insufficient_privilege) instead of a lowercase pseudo-code: `check:error-code-casing` correctly flags a lowercase literal in a code position (ADR-0112 D1), and the SQLSTATE is both the dialect the card's issue names and a case where dialect and declared 403 agree on meaning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
…ch-verb-driver-code
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:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8441
#8333 landed the
errorlimb at these collectors and deliberately left the siblingcodealone — "a different field with a different rule (a closed union)". This is that field.The premise, measured per site on current
mainDriven in process with an engine failing
sys_metadatathe way a driver really does —Object.assign(new Error('SQLITE_ERROR: no such table: sys_metadata'), { code: 'SQLITE_ERROR', errno: 1 }):publishPackageDraftsfailed[]{ error: 'publish failed', code: 'SQLITE_ERROR' }{ error: 'publish failed', code: 'INTERNAL_ERROR' }revertCommitfailed[]{ error: 'revert failed', code: 'SQLITE_ERROR' }{ error: 'revert failed', code: 'INTERNAL_ERROR' }discardPackageDraftsfailed[]codeat alldeletePackagefailed[]codeat allThe issue reproduced only the first. The second is confirmed here; the last two are the ones that build the identical limb and turn out not to need it — see "Scope" below.
The rule, and why it is not #8333's
errorstatus—declaresClientRefusalcodeStandardErrorCode ∪ ERROR_CODE_LEDGER—clientFacingFailureCodeA message is free text, so no catalog bounds it and status-alone is the safe test.
codewritesApiErrorSchema.code, a closed union (ADR-0112 D4) a driver's dialect must never enter. Applying #8333's rule here would be the wrong test on the wrong field in both directions at once, and the new pin file's discriminator case proves it: one error,503, whose sentence is withheld and whose ledger-registeredERR_DATASOURCE_UNAVAILABLEis kept.The membership predicate is not new — it is
carryCatalogedErrorCode's, itself verbatimtoRowApiError's. One rule, one place to change it. No error code was minted; ADR-0114's catalog already names every value this can return.What passes through, and what is replaced
BATCH_ABORTEDon the collateral rows,NOT_OVERRIDABLE,VERSION_NOT_FOUND,INVALID_METADATA, and ledger-registered engine codes such asERR_DATASOURCE_UNAVAILABLE.INTERNAL_ERROR.toRowApiError's derivation, reused rather than a second vocabulary.codekey. Minting one where the wire never carried one is an addition to the response — a separate contract decision, the same posturecarryCatalogedErrorCodetakes towardstatus.⛔ The limb is filtered, never dropped:
codeis the half a client branches on, and blanking it would trade a real authoring surface for a disclosure narrower than the one already dealt with — worse than doing nothing.The positive control, green
#8333's mandatory control, re-run against the field it actually reads. A
flowdraft with genuinely broken CEL still answers with the offending draft, the field pathflows[0].nodes[1].config.approvers[0].value,code: 'INVALID_METADATA', and the structuredissuesthe Studio form highlights with.Scope
discardPackageDraftsanddeletePackagebuild the same limb and got no filter. Both wrap exactly one call,deleteMetaItem, whose two re-wrap exits already runcarryCatalogedErrorCode— so no driver dialect survives to them. Measured under the same failing driver and pinned, so a later change to those exits is caught in this repo rather than on a customer's wire. Adding a filter there would be consumer-side tolerance over a producer that does not need it (Prime Directive #12) — the same call #8136 recorded at those sites for the message. Theissueslimb is likewise untouched: structured authoring feedback no driver error carries.Verification
protocol.batch-verb-driver-code.test.ts— 12 cases in six sections: evidence (2), the catalogued pass-through guards (4, including the 503 discriminator), the positive control (1), the scope evidence for the two clean collectors (2), the substitution's exact shape (2), and the operator half (1).Errorwith nocodeproperty. Its payload scan would have caught the disclosure — the fixture simply never carried the field under test. The harness here attaches a real drivercode, which is what makes every assertion non-vacuous.Reverse verification, both directions predicted before running.
protocol.tsreverted to pre-fix — predicted 4 red / 8 green, measured 4 red / 8 green. Every prediction held, including the operator-half case, predicted red because it asserts the payload as well as the log.expectCatalogedbefore the log, and under the drop variant there is no code to be catalogued. It is the same miss [finding]metadata-protocol's batch verbs still put caught error text on client-facing payloads — the 8 producers option C did not reach #8333 recorded for its own P7 log case, one card later in the same lane.Local runs:
@objectstack/metadata-protocol82 files / 1208 tests green; downstream@objectstack/objectql197 / 3539 and@objectstack/rest112 / 1843 green. Gate families derived fromlint.ymland re-derived withdispatch-gates.mjsagainst the actual changed paths, union run green:check:nul-bytes,check:error-code-casing,check:engine-double-contract,check:cross-package-test-inputs,check:durability-log-level,check:filter-alias-parity,check:changeset-gate-self-tests,check:objectui-changeset,check:query-options-erasure,check:type-check-coverage, plus the changeset scripts.check:error-code-casingearned its place mid-run: it correctly rejected a lowercase pseudo-code in the off-catalog fixture (ADR-0112 D1), which now spells its dialect as the postgres SQLSTATE42501— the dialect the issue itself names, and a case where dialect and declared 403 agree on meaning so the substitution has to preserve it.check:objectui-pin-freshis red, pre-existing and unrelated:.objectui-shais untouched here andmainalready carries 2018 changesets, so the gate fires identically onmain. It asks the release manager to bump the pin.Out of scope, filed
#8502 —
deleteManyData's row errors carry raw driver text inerrors[].message. Measured while working this card: thecodehalf is already gated bytoRowApiError, the message half is not, and the sink is a data batch row rather than a metadata verb, so neither #8136 nor #8333 enumerated it. Not ridden in here.Generated by Claude Code