From c938eeabe7bca1594c5425e2aedd270a4e6b7de6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 00:40:22 +0000 Subject: [PATCH] feat(adapters): envelope the hono adapter's two discovery bodies (#9436) Maintainer ruling 2026-08-18, option A: GET {prefix} and GET {prefix}/discovery gain success: true beside data. The check-route-envelope entry for packages/adapters/hono/src/index.ts graduates to conformant (unenveloped 2 -> 0) and the exempt-block prose records the ruling as the other side of #9389's boundary. Six discovery tests now pin the envelope. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WeN7F6jQFpcqW2BN56RdPa --- .changeset/hono-adapter-discovery-envelope.md | 24 ++++++++++++ packages/adapters/hono/src/hono.test.ts | 6 +++ packages/adapters/hono/src/index.ts | 10 ++++- scripts/check-route-envelope.mjs | 38 ++++++++++++------- 4 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 .changeset/hono-adapter-discovery-envelope.md diff --git a/.changeset/hono-adapter-discovery-envelope.md b/.changeset/hono-adapter-discovery-envelope.md new file mode 100644 index 0000000000..3289c6c33d --- /dev/null +++ b/.changeset/hono-adapter-discovery-envelope.md @@ -0,0 +1,24 @@ +--- +"@objectstack/hono": minor +--- + +feat(adapters): the hono adapter's two discovery bodies join the response envelope (#9436) + + + +`GET {prefix}` and `GET {prefix}/discovery` answered `{ data: }` +with no `success` flag — one key short of the declared `BaseResponseSchema` +envelope. They now answer `{ success: true, data: }`. + +Maintainer ruling on #9436 (2026-08-18, option A), deliberately not inheriting +#9389's pre-auth exemption: these bodies are read by SDKs, codegen and AI +clients — the envelope's core constituency — rather than by our own shells, +and the migration is one key. Readers that unwrapped `body.data` keep working +unchanged; envelope-aware readers that discriminate on `success` now unwrap +this mount correctly. diff --git a/packages/adapters/hono/src/hono.test.ts b/packages/adapters/hono/src/hono.test.ts index 2ff6fb3c74..b67e8424d0 100644 --- a/packages/adapters/hono/src/hono.test.ts +++ b/packages/adapters/hono/src/hono.test.ts @@ -91,6 +91,7 @@ describe('createHonoApp', () => { const res = await app.request('/api'); expect(res.status).toBe(200); const json = await res.json(); + expect(json.success).toBe(true); expect(json.data).toBeDefined(); expect(json.data.version).toBe('1.0'); expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api'); @@ -100,6 +101,7 @@ describe('createHonoApp', () => { const res = await app.request('/api/discovery'); expect(res.status).toBe(200); const json = await res.json(); + expect(json.success).toBe(true); expect(json.data).toBeDefined(); expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api'); }); @@ -109,6 +111,7 @@ describe('createHonoApp', () => { const res = await customApp.request('/v2'); expect(res.status).toBe(200); const json = await res.json(); + expect(json.success).toBe(true); expect(json.data).toBeDefined(); expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/v2'); }); @@ -118,6 +121,7 @@ describe('createHonoApp', () => { const res = await customApp.request('/v2/discovery'); expect(res.status).toBe(200); const json = await res.json(); + expect(json.success).toBe(true); expect(json.data).toBeDefined(); expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/v2'); }); @@ -620,6 +624,7 @@ describe('createHonoApp', () => { const res = await outerApp.request('/api/v1'); expect(res.status).toBe(200); const json = await res.json(); + expect(json.success).toBe(true); expect(json.data).toBeDefined(); expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api/v1'); }); @@ -630,6 +635,7 @@ describe('createHonoApp', () => { const res = await outerApp.request('/api/v1/discovery'); expect(res.status).toBe(200); const json = await res.json(); + expect(json.success).toBe(true); expect(json.data).toBeDefined(); expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api/v1'); }); diff --git a/packages/adapters/hono/src/index.ts b/packages/adapters/hono/src/index.ts index d76836b344..4a4ff5c5a4 100644 --- a/packages/adapters/hono/src/index.ts +++ b/packages/adapters/hono/src/index.ts @@ -290,12 +290,18 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono { // ─── Explicit routes (framework-specific handling required) ──────────────── // --- Discovery --- + // + // Enveloped (`{ success: true, data }`) by maintainer ruling on #9436 + // (2026-08-18, option A) — deliberately NOT inheriting #9389's pre-auth + // exemption: these bodies are read by SDKs, codegen and AI clients (the + // envelope's core constituency, not our own shells), and the migration was + // one key. The SDK's `connect()` unwraps `body.data || body` either way. app.get(prefix, async (c) => { - return c.json({ data: await dispatcher.getDiscoveryInfo(prefix) }); + return c.json({ success: true, data: await dispatcher.getDiscoveryInfo(prefix) }); }); app.get(`${prefix}/discovery`, async (c) => { - return c.json({ data: await dispatcher.getDiscoveryInfo(prefix) }); + return c.json({ success: true, data: await dispatcher.getDiscoveryInfo(prefix) }); }); // --- .well-known --- diff --git a/scripts/check-route-envelope.mjs b/scripts/check-route-envelope.mjs index 7e403f631c..8b60232907 100644 --- a/scripts/check-route-envelope.mjs +++ b/scripts/check-route-envelope.mjs @@ -535,13 +535,17 @@ const DISPATCHER_DOMAINS = { * (`plugin-hono-server/src/adapter.ts`, `adapters/hono/src/index.ts`, * `cli/src/commands/serve.ts`) are ordinary refusals at ordinary doors, and the * ruling never reached them. #9364 converted them instead, which is what a - * ratchet is FOR and the visible contrast with this block: the first two are - * conformant above, and what survives in `adapters/hono/src/index.ts` is its two - * `{ data }` discovery bodies — pre-auth like this block, but read by SDKs and - * codegen rather than by our own shells, so #9389's closed three-file list does - * not reach them either and #9436 carries that question. The rejected option - * (A: envelope them, flip objectui's readers, carry a skew window on the least - * versionable seam in the product) is on record in #9389 rather than lost. + * ratchet is FOR and the visible contrast with this block: all three are + * conformant above. `adapters/hono/src/index.ts`'s last two counters — its + * `{ data }` discovery bodies, pre-auth like this block but read by SDKs and + * codegen rather than by our own shells — carried the SAME fork on a different + * consumer population, and the maintainer ruled it the OTHER way (#9436, + * 2026-08-18, option A: envelope them). The two rulings are one boundary read + * from both sides: WHO reads the body decides. Our own shells, pre-auth, high + * migration cost → exempt (here); SDKs/codegen/AI clients, one-key migration → + * envelope (#9436). The option #9389 rejected (A: envelope the SPA surfaces, + * flip objectui's readers, carry a skew window on the least versionable seam + * in the product) is on record in #9389 rather than lost. * * The reason is the deliverable. The entry is only where it is written down. * @@ -604,17 +608,25 @@ const PLUGIN_ROUTE_MODULES = { // slot and `hostname` under `error.details`. 'packages/cli/src/commands/serve.ts': {}, + // Converted by #9436 (maintainer ruling 2026-08-18, option A): the two + // `{ data }` discovery bodies gained `success: true`. This file's + // `errorCodeNotString 1` had already been removed by #9364 (the shared + // `errorJson` wrote the HTTP status into `error.code` and now derives the + // ADR-0112 member from it through `resolveThrownHttpError`), so this was the + // file's last counter. The ruling deliberately did NOT extend #9389's + // pre-auth exemption here: these bodies are read by SDKs and codegen, not by + // our own shells, and the migration was one key. + 'packages/adapters/hono/src/index.ts': {}, + // ── Ratchet: real, tracked, NOT blessed ───────────────────────────────── // // Measured by #9267 when this surface was added, not chosen. Each entry names // the issue that will drive it to zero; every number ticks DOWN only. These // are the finding this surface was worth adding for — none of them was - // visible to any check in the repo before it. - 'packages/adapters/hono/src/index.ts': { - unenveloped: 2, - ratchet: '#9436 (envelope the hono adapter discovery bodies; Blocked-by #9389)', - note: 'two `{ data }` discovery bodies with no `success`. #9364 removed this file\'s `errorCodeNotString 1` — the shared `errorJson` wrote the HTTP status into `error.code` and now derives the ADR-0112 member from it through `resolveThrownHttpError`. What is left is the same PRE-AUTH bare-payload fork #9389 rules on, but on a different consumer population (SDKs and codegen read this mount\'s discovery, not the Console SPA), so #9389\'s closed three-file list does not reach it', - }, + // visible to any check in the repo before it. #9436 graduated the last + // ratcheted member (`adapters/hono`, above); the section stays because the + // next measured drift lands here. `trigger-api` below was measured clean + // when the surface was added and is a pinned zero, not a ratchet. 'packages/triggers/trigger-api/src/plugin.ts': {}, // ── Ruled exempt: the pre-auth bootstrap seam (#9389) ────────────────────