Skip to content

Commit 09fa628

Browse files
committed
fix(metadata-protocol): deleteMetaItem's catch re-wrap carries the error code (#7426)
`deleteMetaItem` is the only verb in `protocol.ts` that re-wraps a thrown error instead of rethrowing it — both of its catches build a fresh `Error` carrying the "failed to delete" context. They carried `status` forward and dropped `code`, so a `SysMetadataRepository` refusal with a full ADR-0112 envelope reached the caller as 403 with `code: undefined`, its code surviving only as prose in the message. The envelope therefore depended on the deployment topology: a project kernel got `NOT_OVERRIDABLE` from `deleteMetaItem`'s own two-tier block, a control-plane kernel — which skips that block — got the code-less 403. Both re-wrap exits now carry `code`, gated on membership in the declared ADR-0112 vocabulary (StandardErrorCode ∪ ERROR_CODE_LEDGER) — verbatim the predicate `toRowApiError` in the same file already applies to thrown codes. A driver's own dialect (42P01, SQLITE_CONSTRAINT, ECONNREFUSED) is not in the catalog and stays out of the envelope, so the wrapper cannot smuggle an unregistered code onto a field `ApiErrorSchema` declares as a closed union. `status` is untouched at both sites, and the message text is unchanged. The new pin walks the whole failure-kind matrix through those catches rather than the one symptom, and #6960's own pin drops its topology branch: `expectRefused` now asserts `code` + `status` on every leg instead of falling back to a message substring on the control-plane one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TyY4g6DC854MCzyJDRh5FR
1 parent 08363a0 commit 09fa628

4 files changed

Lines changed: 694 additions & 14 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
fix(metadata-protocol): `deleteMetaItem`'s catch re-wrap carries the error `code` (#7426)
6+
7+
`deleteMetaItem` is the one verb in `protocol.ts` that re-wraps a thrown error
8+
instead of rethrowing it: both of its catches build a fresh `Error` carrying the
9+
"failed to delete" context. They carried `status` forward and dropped `code`, so
10+
a refusal thrown by `SysMetadataRepository` with a full ADR-0112 envelope reached
11+
the caller as **403 with `code: undefined`**, its code surviving only as prose
12+
inside the message. That made the envelope depend on the deployment topology: on
13+
a project kernel (`environmentId` set) the same refusal comes from
14+
`deleteMetaItem`'s own two-tier block and arrived intact with
15+
`code: 'NOT_OVERRIDABLE'`, while a control-plane kernel — which skips that block
16+
entirely — answered the code-less 403.
17+
18+
Both re-wrap exits now carry `code` forward, gated on membership in the declared
19+
ADR-0112 vocabulary (`StandardErrorCode ∪ ERROR_CODE_LEDGER`) — verbatim the
20+
predicate `toRowApiError` in the same file already applies to decide which thrown
21+
code may become a wire code. A driver's own dialect (`42P01`,
22+
`SQLITE_CONSTRAINT`, `ECONNREFUSED`) is not in the catalog and stays out of the
23+
envelope, so restoring the code for refusals does not smuggle an unregistered
24+
code onto a surface `ApiErrorSchema` declares as a closed union.
25+
26+
What a caller sees change, per failure kind through those two catches:
27+
28+
- repository authorization refusal (`NOT_OVERRIDABLE`) — was `403` + no code,
29+
now `403` + `NOT_OVERRIDABLE`;
30+
- engine failure carrying a **registered** code (`ERR_DATASOURCE_UNAVAILABLE`,
31+
`ERR_DRIVER_CONNECT`) — was `status` only, now `status` + that code;
32+
- engine failure carrying an **unregistered** driver code, or none at all —
33+
unchanged (`500`, no code), and pinned so it stays that way;
34+
- `ConflictError` — unchanged (`409` + `METADATA_CONFLICT`); it is translated one
35+
branch above the re-wrap and never passes through it.
36+
37+
`status` is untouched at both sites. The message text is unchanged — the code is
38+
added to the envelope, it does not restate the sentence — so the 5xx prose
39+
sanitisation in `@objectstack/rest` is unaffected; that layer already forwards a
40+
declared `code` when one is present.

0 commit comments

Comments
 (0)