Skip to content

test(runtime): 订正 #4073 的证据 —— 翻默认值对 composed 宿主是 no-op(推翻我在 #4192 的结论) - #4260

Merged
os-zhuang merged 1 commit into
mainfrom
claude/vercel-startup-permissions-provider-iwtkqm
Jul 31, 2026
Merged

test(runtime): 订正 #4073 的证据 —— 翻默认值对 composed 宿主是 no-op(推翻我在 #4192 的结论)#4260
os-zhuang merged 1 commit into
mainfrom
claude/vercel-startup-permissions-provider-iwtkqm

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

这个 PR 推翻的是我自己在 #4192 里下的结论。

#4192 给出的证据是「flag 关掉后 /api/v1/data/:object 变成 404」,并据此挡住了 #4073 的退役计划。那个结论是错的,而且错法与本 issue 已被订正过两次的形状完全同构

错在哪

#4192 的 harness 用 createRestApiPlugin({}) 配一个 stubobjectql 服务。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")。

按同样方式供给后的结果

/api/v1/data/task?top=5     flag ON == flag OFF
/api/v1/discovery           flag ON == flag OFF
/.well-known/objectstack    flag ON == flag OFF

响应逐字节相同,两半都是。 这个面确实是 duplicate supply,翻默认值对任何挂了 REST 或 dispatcher 的宿主都是 no-op —— 也就是所有 composed 部署,包括 os serve

断言方式也改了

不再断言状态码,而是直接比对两个位置的响应。因为状态码正是误导了上一版的东西:

  • /data/task 在这里返回 404 OBJECT_NOT_FOUND —— 那是引擎的回答,说明路由是通的;
  • 路由未命中会是 {"error":"Not found"}

parity 这个性质不依赖于区分这两者。另有一条独立断言钉住「被比对的路由确实活着」,免得 parity 被两个相同的 miss 满足。

本 PR 不做什么

不翻默认值。 那对只挂 HonoServerPlugin、既无 REST 也无 dispatcher 的裸宿主仍是真实变更 —— 而那正是这个 flag 当初被写出来服务的场景。这是一个 API 决策,不该由一个测试代为决定。

它现在解除了阻塞、并且有了证据,而这正是 #4192 错误地否定掉的东西。

我从这轮学到的

同一个问题被答错了三次,每次都换了一种"看起来足够"的方式:

  1. 查「谁传这个选项」,没查「谁依赖这个默认值」(issue 自己的第一次订正);
  2. 查「谁声称提供这条路径」,没查「真实监听器上谁应答」(test(runtime): 把「谁在服务 /data 与 /discovery」钉成事实,用证据挡住 #4073 的默认值翻转 #4192,我);
  3. 查「一个能启动的最小 harness」,没查「一个被真实供给的运行时」(本 PR,还是我)。

「谁在服务这条路径」始终是一个关于 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

… 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
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 31, 2026 1:03am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

@os-zhuang
os-zhuang marked this pull request as ready for review July 31, 2026 01:08
@os-zhuang
os-zhuang merged commit 77a77fd into main Jul 31, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/vercel-startup-permissions-provider-iwtkqm branch July 31, 2026 01:09
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.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants