|
| 1 | +--- |
| 2 | +'@objectstack/hono': minor |
| 3 | +'@objectstack/plugin-hono-server': minor |
| 4 | +--- |
| 5 | + |
| 6 | +fix(hono): `createHonoApp` answers an escaped throw that declares an ADR-0112 envelope as that envelope, not as `500 INTERNAL_ERROR` (#23365) |
| 7 | + |
| 8 | +`@objectstack/hono`'s `createHonoApp` has two catch arms: the auth mount's, and |
| 9 | +the `${prefix}/*` catch-all's around `HttpDispatcher.dispatch()`. Both answered |
| 10 | +an escaped throw from its `statusCode` alone, so a producer that declared |
| 11 | +`status` and a registered `code` lost both on this host. A `GET`, `PATCH` or |
| 12 | +`DELETE` of `/data/:object/:id` for an id that names no row answered |
| 13 | +`500 INTERNAL_ERROR` where the standalone REST server answers |
| 14 | +`404 RECORD_NOT_FOUND`. The `/data` version refusals answered `500` too (a stale |
| 15 | +`If-Match`'s `409 CONCURRENT_UPDATE`, and `If-Match: ""`'s |
| 16 | +`400 VALIDATION_FAILED`), and so did an authorization-store outage |
| 17 | +(`AuthzStoreUnavailableError`, `503 SERVICE_UNAVAILABLE`). |
| 18 | + |
| 19 | +**What changed.** Both arms now answer a throw that carries **both** a declared |
| 20 | +ADR-0112 status (a key of `HttpStatusErrorCodeMap`) **and** a code registered in |
| 21 | +`ErrorCode` as that envelope. The recognition is `declaredEnvelopeForThrow`, the |
| 22 | +rule `@objectstack/plugin-hono-server`'s direct-mount routes have used since |
| 23 | +#16545, so the two Hono doors answer one throw alike by construction: the same |
| 24 | +two conditions, the same 5xx disclosure filter (`looksLikeInternalErrorLeak`), |
| 25 | +and `details` and `userMessage` forwarded only when the producer declared them. |
| 26 | + |
| 27 | +**What did NOT change**, pinned in the same PR through a real `createHonoApp` |
| 28 | +over the real dispatcher and protocol (`packages/qa/http-conformance`): |
| 29 | + |
| 30 | +- a throw that is **not** such an envelope answers what it answered before. A |
| 31 | + bare `Error` is still `500 INTERNAL_ERROR` carrying `code` and `message`, with |
| 32 | + no `cause` in the body, and a throw that declares `statusCode` with no |
| 33 | + registered code keeps that status; |
| 34 | +- ⛔ no error code is minted and no ledger row is added. |
| 35 | + |
| 36 | +`@objectstack/plugin-hono-server` gains one export, `declaredEnvelopeForThrow`, |
| 37 | +which was module-private. `createHonoApp` imports it rather than restating the |
| 38 | +rule. Its behaviour is unchanged. |
| 39 | + |
| 40 | +Earlier entries say a `createHonoApp` host still renders the `/data` version |
| 41 | +refusals as `500`. This change ends that: they are answered `409` and `400` as |
| 42 | +declared. |
| 43 | + |
| 44 | +⚠️ **Known gap, stated rather than left silent.** The `409 CONCURRENT_UPDATE` |
| 45 | +this host now answers carries `code` and `message`, but not the |
| 46 | +`currentVersion` and `currentRecord` that the standalone REST server's `409` |
| 47 | +carries. The recogniser forwards only the `details` a producer declares, and |
| 48 | +those two fields are own properties of the error. That gap is outside this |
| 49 | +change. |
| 50 | + |
| 51 | +Clause-②: yes (widening) |
0 commit comments