Skip to content

Commit 4e52147

Browse files
huangyiireneclaude
andauthored
feat(adapters): envelope the hono adapter's two discovery bodies (#9436) (#9814)
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. Claude-Session: https://claude.ai/code/session_01WeN7F6jQFpcqW2BN56RdPa Co-authored-by: Claude <noreply@anthropic.com>
1 parent e124e58 commit 4e52147

4 files changed

Lines changed: 63 additions & 15 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
"@objectstack/hono": minor
3+
---
4+
5+
feat(adapters): the hono adapter's two discovery bodies join the response envelope (#9436)
6+
7+
<!-- adr-0087: not-required (no-migration-prescription) Additive wire change:
8+
`GET {prefix}` and `GET {prefix}/discovery` gain `success: true` beside the
9+
existing `data` key — nothing authorable and no key is renamed, retired or
10+
removed, so there is no conversion to register. Every measured reader
11+
(`@objectstack/client` `connect()`'s `body.data || body`, the QA http-adapter's
12+
`'routes' in body` discriminator, objectui's `typeof body.success === 'boolean'
13+
&& 'data' in body` unwrap) resolves the new shape to the same document. -->
14+
15+
`GET {prefix}` and `GET {prefix}/discovery` answered `{ data: <discovery> }`
16+
with no `success` flag — one key short of the declared `BaseResponseSchema`
17+
envelope. They now answer `{ success: true, data: <discovery> }`.
18+
19+
Maintainer ruling on #9436 (2026-08-18, option A), deliberately not inheriting
20+
#9389's pre-auth exemption: these bodies are read by SDKs, codegen and AI
21+
clients — the envelope's core constituency — rather than by our own shells,
22+
and the migration is one key. Readers that unwrapped `body.data` keep working
23+
unchanged; envelope-aware readers that discriminate on `success` now unwrap
24+
this mount correctly.

packages/adapters/hono/src/hono.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ describe('createHonoApp', () => {
9191
const res = await app.request('/api');
9292
expect(res.status).toBe(200);
9393
const json = await res.json();
94+
expect(json.success).toBe(true);
9495
expect(json.data).toBeDefined();
9596
expect(json.data.version).toBe('1.0');
9697
expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api');
@@ -100,6 +101,7 @@ describe('createHonoApp', () => {
100101
const res = await app.request('/api/discovery');
101102
expect(res.status).toBe(200);
102103
const json = await res.json();
104+
expect(json.success).toBe(true);
103105
expect(json.data).toBeDefined();
104106
expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api');
105107
});
@@ -109,6 +111,7 @@ describe('createHonoApp', () => {
109111
const res = await customApp.request('/v2');
110112
expect(res.status).toBe(200);
111113
const json = await res.json();
114+
expect(json.success).toBe(true);
112115
expect(json.data).toBeDefined();
113116
expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/v2');
114117
});
@@ -118,6 +121,7 @@ describe('createHonoApp', () => {
118121
const res = await customApp.request('/v2/discovery');
119122
expect(res.status).toBe(200);
120123
const json = await res.json();
124+
expect(json.success).toBe(true);
121125
expect(json.data).toBeDefined();
122126
expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/v2');
123127
});
@@ -620,6 +624,7 @@ describe('createHonoApp', () => {
620624
const res = await outerApp.request('/api/v1');
621625
expect(res.status).toBe(200);
622626
const json = await res.json();
627+
expect(json.success).toBe(true);
623628
expect(json.data).toBeDefined();
624629
expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api/v1');
625630
});
@@ -630,6 +635,7 @@ describe('createHonoApp', () => {
630635
const res = await outerApp.request('/api/v1/discovery');
631636
expect(res.status).toBe(200);
632637
const json = await res.json();
638+
expect(json.success).toBe(true);
633639
expect(json.data).toBeDefined();
634640
expect(mockDispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api/v1');
635641
});

packages/adapters/hono/src/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,18 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
290290
// ─── Explicit routes (framework-specific handling required) ────────────────
291291

292292
// --- Discovery ---
293+
//
294+
// Enveloped (`{ success: true, data }`) by maintainer ruling on #9436
295+
// (2026-08-18, option A) — deliberately NOT inheriting #9389's pre-auth
296+
// exemption: these bodies are read by SDKs, codegen and AI clients (the
297+
// envelope's core constituency, not our own shells), and the migration was
298+
// one key. The SDK's `connect()` unwraps `body.data || body` either way.
293299
app.get(prefix, async (c) => {
294-
return c.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
300+
return c.json({ success: true, data: await dispatcher.getDiscoveryInfo(prefix) });
295301
});
296302

297303
app.get(`${prefix}/discovery`, async (c) => {
298-
return c.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
304+
return c.json({ success: true, data: await dispatcher.getDiscoveryInfo(prefix) });
299305
});
300306

301307
// --- .well-known ---

scripts/check-route-envelope.mjs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -535,13 +535,17 @@ const DISPATCHER_DOMAINS = {
535535
* (`plugin-hono-server/src/adapter.ts`, `adapters/hono/src/index.ts`,
536536
* `cli/src/commands/serve.ts`) are ordinary refusals at ordinary doors, and the
537537
* ruling never reached them. #9364 converted them instead, which is what a
538-
* ratchet is FOR and the visible contrast with this block: the first two are
539-
* conformant above, and what survives in `adapters/hono/src/index.ts` is its two
540-
* `{ data }` discovery bodies — pre-auth like this block, but read by SDKs and
541-
* codegen rather than by our own shells, so #9389's closed three-file list does
542-
* not reach them either and #9436 carries that question. The rejected option
543-
* (A: envelope them, flip objectui's readers, carry a skew window on the least
544-
* versionable seam in the product) is on record in #9389 rather than lost.
538+
* ratchet is FOR and the visible contrast with this block: all three are
539+
* conformant above. `adapters/hono/src/index.ts`'s last two counters — its
540+
* `{ data }` discovery bodies, pre-auth like this block but read by SDKs and
541+
* codegen rather than by our own shells — carried the SAME fork on a different
542+
* consumer population, and the maintainer ruled it the OTHER way (#9436,
543+
* 2026-08-18, option A: envelope them). The two rulings are one boundary read
544+
* from both sides: WHO reads the body decides. Our own shells, pre-auth, high
545+
* migration cost → exempt (here); SDKs/codegen/AI clients, one-key migration →
546+
* envelope (#9436). The option #9389 rejected (A: envelope the SPA surfaces,
547+
* flip objectui's readers, carry a skew window on the least versionable seam
548+
* in the product) is on record in #9389 rather than lost.
545549
*
546550
* The reason is the deliverable. The entry is only where it is written down.
547551
*
@@ -604,17 +608,25 @@ const PLUGIN_ROUTE_MODULES = {
604608
// slot and `hostname` under `error.details`.
605609
'packages/cli/src/commands/serve.ts': {},
606610

611+
// Converted by #9436 (maintainer ruling 2026-08-18, option A): the two
612+
// `{ data }` discovery bodies gained `success: true`. This file's
613+
// `errorCodeNotString 1` had already been removed by #9364 (the shared
614+
// `errorJson` wrote the HTTP status into `error.code` and now derives the
615+
// ADR-0112 member from it through `resolveThrownHttpError`), so this was the
616+
// file's last counter. The ruling deliberately did NOT extend #9389's
617+
// pre-auth exemption here: these bodies are read by SDKs and codegen, not by
618+
// our own shells, and the migration was one key.
619+
'packages/adapters/hono/src/index.ts': {},
620+
607621
// ── Ratchet: real, tracked, NOT blessed ─────────────────────────────────
608622
//
609623
// Measured by #9267 when this surface was added, not chosen. Each entry names
610624
// the issue that will drive it to zero; every number ticks DOWN only. These
611625
// are the finding this surface was worth adding for — none of them was
612-
// visible to any check in the repo before it.
613-
'packages/adapters/hono/src/index.ts': {
614-
unenveloped: 2,
615-
ratchet: '#9436 (envelope the hono adapter discovery bodies; Blocked-by #9389)',
616-
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',
617-
},
626+
// visible to any check in the repo before it. #9436 graduated the last
627+
// ratcheted member (`adapters/hono`, above); the section stays because the
628+
// next measured drift lands here. `trigger-api` below was measured clean
629+
// when the surface was added and is a pinned zero, not a ratchet.
618630
'packages/triggers/trigger-api/src/plugin.ts': {},
619631

620632
// ── Ruled exempt: the pre-auth bootstrap seam (#9389) ────────────────────

0 commit comments

Comments
 (0)