test(runtime): 订正 #4073 的证据 —— 翻默认值对 composed 宿主是 no-op(推翻我在 #4192 的结论) - #4260
Merged
os-zhuang merged 1 commit intoJul 31, 2026
Merged
Conversation
… composed host #4192 (mine) concluded that turning `registerStandardEndpoints` off makes `/api/v1/data/:object` a 404, and blocked #4073's retirement on it. Wrong, and wrong in the same shape the issue has already been corrected for twice. That test mounted `createRestApiPlugin({})` against a STUB `objectql` service. REST generates its CRUD from the object registry, so it needs a real engine — driver plus registered objects — and its own `api.api` config. Under-provision it and it serves nothing; the 404 was a fact about the harness, not about REST. `packages/client/src/client.environment-scoping.test.ts` had the answer the whole time: it boots `registerStandardEndpoints: false` and asserts `GET /api/v1/data/task` → 200, served by REST, with a comment saying that is the point. Reproducing that provisioning: /api/v1/data/task?top=5 flag ON == flag OFF /api/v1/discovery flag ON == flag OFF /.well-known/objectstack flag ON == flag OFF Byte-identical responses, both halves. The surface is duplicate supply, and the flip is a no-op for any host mounting REST or the dispatcher — every composed deployment, `os serve` included. The test now compares the two positions instead of asserting a status, because a status is exactly what misled the previous version: `/data/task` answers 404 `OBJECT_NOT_FOUND` — the ENGINE's answer, reached through a working route — where a routing miss would be `{"error":"Not found"}`. Parity does not depend on telling those apart, and a separate assertion pins that the compared routes are live so parity cannot be satisfied by two identical misses. What this does NOT do is flip the default. That remains a real change for a BARE host mounting neither, which is the composition the flag was written for — an API decision, not one a test should make. It is now unblocked and evidenced, which is what #4192 wrongly denied it. No production code changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016gEeLZ4oTeSXG6fKG1r3vd
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
os-zhuang
marked this pull request as ready for review
July 31, 2026 01:08
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
… — the convenience surface is opt-in (#4073) (#4280) Step 1 of #4073's retirement, executed as decided: the deprecated convenience surface — raw C+R `/api/v1/data/:object`, `/api/v1/discovery`, `/.well-known/objectstack` — is opt-in, no longer the default. Everything the flag mounts is duplicate and LESSER supply (C+R only, a subset of the gates, a pre-DiscoverySchema payload); REST serves full `/data` CRUD behind the whole gate stack, REST/the dispatcher own discovery (#4018 cede), and #4260 pinned that a composed host answers byte-identically with the flag on or off. The tax was real: #2567, #3298 and #4018 each re-implemented a platform invariant here after the fact. Impact was checked against who RELIES ON THE DEFAULT, not who passes the option (the issue's first correction): `os serve` and `objectstack dev` are composed — no change; the in-repo default-reliant boots never touch the surface; the three suites that test the legacy surface itself now opt in explicitly; `/me/*` was never behind the flag (#4144). Bare hosts — none in-repo — get ONE boot warn naming the flag, #4073 and both remedies (mount `createRestApiPlugin`, or opt in during the deprecation window), and the warn stays quiet whenever REST or the dispatcher is mounted. Absence is loud; composed hosts are not nagged. The default gets its own six-pin suite (`hono-standard-endpoints-default.test.ts`) because defaults are invisible at call sites — a constructor-spread drift would otherwise regress silently. The docs drift check surfaced one REAL drift: `authentication.mdx`'s minimal stack is exactly the bare-host shape and its own page fetches `/api/v1/data/task`; the example now mounts `createRestApiPlugin`, the migration the changeset prescribes, rather than teaching readers a deprecated flag. Verified: plugin-hono-server 155/155, runtime hono suites 23/23, client hono suites 16/16, http-conformance 46/46, package tsc clean, changed-file lint 0. Next per #4073: one release of observation, then `registerDiscoveryAndCrudEndpoints`, the flag and the dead priority constants are deleted and the plugin becomes a pure transport adapter (ADR-0076 D11).
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
…ugin is a transport adapter (#4073) (#4296) Completes the retirement. HonoServerPlugin now owns the socket, the middleware and the three current-user endpoints, and nothing else. The data and discovery APIs have one owner each — @objectstack/rest and the runtime dispatcher (ADR-0076 D11). Net −678 lines. Deleted: raw C+R `/api/v1/data/:object` (+ `/:id`), this plugin's third `/api/v1/discovery` + `/.well-known/objectstack` payload, and the `registerStandardEndpoints` flag itself — gone, not defaulted off, with a test pinning that passing the removed option resurrects nothing. Unaffected: `/auth/me/*` + `/me/apps` (sole supply, unconditional since #4144), and every composed host — `os serve`, `objectstack dev`, cloud's objectos — which mount an owner that already answered these routes byte-identically either way (#4260). A host with NO owner keeps the boot warn, reworded to name both remedies now that opting in is not one of them. Tests decided on what each was actually pinning: `hono-discovery` and `hono-anonymous-deny` deleted (wholly about the retired routes); `server-timing-e2e` KEPT and repointed at `/auth/me/permissions` (#3361's value is "perf middleware + a handler that resolves the principal", not the route); `client.hono` KEPT and repointed at a REST-composed host (it is the only coverage of `client.connect()` and the discovery handshake); the default-pin suite rewritten as `hono-transport-only`. Three gates caught bookkeeping the deletion owed, each fixed on its own terms: - the #2567 anonymous-deny ledger flagged STALE covers. The row went with its surface, and its id came out of HIGH_RISK — but the ratchet's own negative test used that row as its FIXTURE, so it was repointed at a live row rather than left to pass vacuously and disarm the check that just caught this. - the docs drift check surfaced `authorization.mdx`, which documented the deleted surface twice as part of a SECURITY invariant. Both now read as history, matching how the same sentence already handles the retired GraphQL endpoint: the surface LEFT the matrix; the invariant is unchanged and simply has one fewer door to hold shut. - a merge conflict with #4282's CORS work: both intents kept. AGENTS.md gains the four rules this thread paid for — one route one owner, explicit composition over default magic, absence must be loud, machine-readable surfaces must not lie — plus the verification note that "who serves this path" is a question about the composed, PROVISIONED runtime. #4073 was answered wrongly three times, once per shortcut around that. Verified: plugin-hono-server 135/135, dogfood 430 passed, client 12/12, runtime 19/19, http-conformance 46/46, full build 71/71, CI 16/16. (Temporal Conformance timed out once on a live-service step touching no package in this diff; it passed on re-run.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
这个 PR 推翻的是我自己在 #4192 里下的结论。
#4192 给出的证据是「flag 关掉后
/api/v1/data/:object变成 404」,并据此挡住了 #4073 的退役计划。那个结论是错的,而且错法与本 issue 已被订正过两次的形状完全同构。错在哪
#4192 的 harness 用
createRestApiPlugin({})配一个 stub 的objectql服务。REST 的 CRUD 是从对象注册表生成的 —— 它需要真实引擎(driver + 已注册对象)以及自己的api.api配置。供给不全时它什么都不挂。那个 404 是关于 harness 的事实,不是关于 REST 的。
答案其实一直摆在仓库里:
packages/client/src/client.environment-scoping.test.ts就是用registerStandardEndpoints: false启动,并断言GET /api/v1/data/task返回 200,由 REST 服务 —— 注释还写明了这正是重点("skip hardcoded hono CRUD routes so createRestApiPlugin owns /data ... end-to-end")。按同样方式供给后的结果
响应逐字节相同,两半都是。 这个面确实是 duplicate supply,翻默认值对任何挂了 REST 或 dispatcher 的宿主都是 no-op —— 也就是所有 composed 部署,包括
os serve。断言方式也改了
不再断言状态码,而是直接比对两个位置的响应。因为状态码正是误导了上一版的东西:
/data/task在这里返回 404OBJECT_NOT_FOUND—— 那是引擎的回答,说明路由是通的;{"error":"Not found"}。parity 这个性质不依赖于区分这两者。另有一条独立断言钉住「被比对的路由确实活着」,免得 parity 被两个相同的 miss 满足。
本 PR 不做什么
不翻默认值。 那对只挂 HonoServerPlugin、既无 REST 也无 dispatcher 的裸宿主仍是真实变更 —— 而那正是这个 flag 当初被写出来服务的场景。这是一个 API 决策,不该由一个测试代为决定。
它现在解除了阻塞、并且有了证据,而这正是 #4192 错误地否定掉的东西。
我从这轮学到的
同一个问题被答错了三次,每次都换了一种"看起来足够"的方式:
「谁在服务这条路径」始终是一个关于 composed 且 provisioned 的运行时的问题。 一个能 boot 的 harness 不等于一个能回答这个问题的 harness。
无生产代码改动。
关联:#4073、#4192(本 PR 订正它)、#4018、#2567、#4144。
🤖 Generated with Claude Code
https://claude.ai/code/session_016gEeLZ4oTeSXG6fKG1r3vd
Generated by Claude Code