Skip to content

fix(runtime,hono,plugin-dev): 退役 dispatcher 的 /storage bridge —— 它从来没按契约调用过 storage (#4087) - #4112

Merged
os-zhuang merged 2 commits into
mainfrom
claude/dispatcher-storage-upload-typeerror-0exbcb
Jul 30, 2026
Merged

fix(runtime,hono,plugin-dev): 退役 dispatcher 的 /storage bridge —— 它从来没按契约调用过 storage (#4087)#4112
os-zhuang merged 2 commits into
mainfrom
claude/dispatcher-storage-upload-typeerror-0exbcb

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Closes #4087。取 issue 里的方案 2(退役这条 bridge),并且核查后发现比 issue 描述的更彻底——下面第 1 节说明为什么另外两个前提也需要更正。

1. 核查结论:两条路由都是假的,而且它们一直是可达的

upload 那条 issue 已经说清楚了(upload(key, data, options?) 被当成 upload(file, { request }) 调,对 S3StorageAdapter / LocalStorageAdapter / SwappableStorageService / plugin-dev 内存实现都是 TypeError)。核查时另外两点和 issue 的判断不一致:

GET /storage/file/:id 并不是"不受影响"。 契约是 download(key: string): Promise<Buffer>,而 dispatcher 分支在 result.url && result.redirect / result.stream / result.mimeType 上做判断。Buffer 上这些属性全是 undefined,所以每个分支都落空,最后 deps.success(buffer) 把字节数组当 JSON 返回({"type":"Buffer","data":[…]})。多余实参确实被忽略了,但返回值处理面向的是一个仓库里不存在的接口——全仓搜索没有任何 file-storage 实现返回过 { url | redirect | stream | mimeType } 这个形状。而且传进去的是 sys_file 的 id,不是 storage key:LocalStorageAdapter 会 fs.readFile 一个不存在的路径 → ENOENT → 500。

② 不是被 service-storage "遮蔽"了。 service-storage 挂的是 /storage/upload/presigned/storage/upload/complete/storage/upload/chunked/*/storage/files/:fileId[/url]/storage/_local/raw/:token——没有 /storage/upload,也没有 /storage/file/:id。两条路径不冲突,所以装了 service-storage 的完整栈里 bridge 依然挂着、依然可达,只是没有调用方:SDK 不拼这两个 URL,console 也不调,唯一指向它们的是 content/docs/api/plugin-endpoints.mdx 那张表——照文档调的人拿到的就是 500。

这两点让"方案 2"更站得住:#3584 当初把这两条留下的理由是"low-level redirect/stream 兼容面",而这个兼容面从来不存在。

顺带一个域外发现(已在本 PR 一并修):hono adapter 挂的是 app.all('${prefix}/storage/*') 通配,为两条死路由占了整个 /storage 子树——子树下任何其他路径都会拿到 bridge 自己的 404,而不是继续往下匹配。

2. 改了什么

删除:

  • packages/runtime/src/domains/storage.ts(整个域)
  • HttpDispatcher.handleStorage() + domain registry 注册
  • dispatcher-plugin 的两处 server.post/get 挂载
  • hono adapter 的 /storage/* 通配(storage 现在走通用 catch-all)
  • route-ledger.ts 两行(原地留了 tombstone 注释,写清楚为什么不该再长回来)

为了守住 declared === enforced(Prime Directive #10):

文档:

**测试:**旧的 handleStorage 用例全部替换。它们正是这个缺陷能活下来的原因——每个都 mock 了 handler 想要的形状而不是 IStorageService 声明的形状(upload 只断言"被调用过",download mock 成 { data, mimeType },而契约是 Promise<void>Promise<Buffer>),所以 500 的路由测出来是绿的。现在钉的是"不存在"本身:没有 handleStorage 方法、/storage 不被 registry 认领(槽位空和被占用两种情况都测)、hono 上 /storage/upload/presigned 落到 catch-all。

3. 与 #4058 step 2(#4086)的关系

#4086 在本 PR 开出后合入 main,改到了同一片地方(给每个 dispatcher domain 上 isServiceServeable/storage 也在内)。合并冲突的处理写在 merge commit 里,摘要:

4. 迁移

实际上没有东西需要迁移——两条路由都完不成一次请求。对照关系写在 changeset 里(.changeset/retire-dispatcher-storage-bridge.md,minor):

POST /api/v1/storage/upload POST /storage/upload/presigned → 直传 → POST /storage/upload/complete,或 client.storage.upload(file)
GET /api/v1/storage/file/:id GET /storage/files/:fileId/urlclient.storage.getDownloadUrl),或 GET /storage/files/:fileId(浏览器稳定 URL,302)

没装 @objectstack/service-storage/api/v1/storage 现在没有 handler——和其他未安装能力的答案一致。

5. 验证

  • pnpm --filter @objectstack/runtime test → 65 files / 916 tests(合并 main 后)
  • @objectstack/hono 67、@objectstack/metadata-protocol 110、@objectstack/plugin-dev 8、@objectstack/types 48 全绿
  • pnpm lintcheck:route-envelopecheck:doc-authoringcheck:nul-bytescheck:role-wordcheck:org-identifiercheck:authz-resolvercheck:error-code-casingcheck:release-notescheck:node-version 全过
  • 唯一的本地失败是 packages/runtime/src/datasource-autoconnect.test.ts「makes the federated object queryable through the engine with zero app code」。这条在未改动的 main 上同样失败(已 git stash 后复跑确认:expected [ 'first', 'first', 'first', …(7) ] to deeply equal [ 'first', 'second' ]),与本 PR 无关;分支 claude/datasource-autoconnect-federation-test-32356t 已在处理。补一个数据点:单文件跑也复现,所以不是跨文件污染。

6. 域外发现 → #4127

修完这条之后,我用同一把尺子("调用点 vs packages/spec/src/contracts/*")扫了其余 dispatcher domain,结果记在 #4127/notifications 的三个方法、/automation 的三个方法、/i18ngetFieldLabels 都不在各自契约里(方向和 #4087 相反——调用点和实现是对的,契约漏了声明),三处都靠 typeof x.foo === 'function' duck-type 绕过。不在本 PR 范围。

RouterConfigSchema.mounts.storage = '/storage' 保持不变——这个 mount 前缀现在由 service-storage 兑现,依然成立。


Generated by Claude Code

… it never spoke the storage contract (#4087)

`POST /api/v1/storage/upload` and `GET /api/v1/storage/file/:id` bridged to the
`file-storage` slot against a service shape that does not exist:

- upload called `upload(key, data, options?)` as `upload(file, { request })` —
  the parsed file in the `key` slot, `{ request }` in `data`. A TypeError on
  every implementation in the repo (S3, local, swappable, plugin-dev's memory
  one), not a near-miss.
- download branched on `{ url | redirect | stream | mimeType }` while
  `download(key)` resolves a Buffer, so every branch fell through and the route
  answered a JSON-serialized Buffer.

Nothing shadowed them — service-storage mounts `/storage/upload/presigned`, not
`/storage/upload`, so both stayed mounted and reachable. What hid the defect is
that they had no caller: no SDK method builds those URLs, only the docs pointed
at them. The tests that covered them mocked the shape the handler wanted rather
than the shape `IStorageService` declares, so the suite was green on two routes
that answered 500.

`/api/v1/storage` is `@objectstack/service-storage`'s surface — the presigned /
chunked / signed-URL protocol `@objectstack/client` actually speaks. Removed:
the `/storage` domain, `HttpDispatcher.handleStorage()`, the dispatcher-plugin
mounts, the hono adapter's `/storage/*` wildcard (which claimed the whole
subtree for the two dead routes), and both route-ledger rows. Without the
package the path now has no handler — the same answer every uninstalled
capability gives.

Two follow-ons keep declared === enforced (Prime Directive #10):

- discovery advertises `routes.storage` only when the slot's occupant actually
  mounts HTTP handlers (`handlerReady`, the predicate #4000 gave analytics);
- plugin-dev's in-memory file-storage self-declares `handlerReady: false` — the
  retired bridge was the only thing that ever routed HTTP to it. It still works
  for in-process callers, it is simply no longer advertised as an HTTP surface.

Docs: `plugin-endpoints` now lists the real protocol with its SDK column, and
the #3563 audit records the premise #3584's storage disposition rested on.

Closes #4087

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015T5CeitwV1jXLvsL1A84tw
@vercel

vercel Bot commented Jul 30, 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 30, 2026 10:29am

Request Review

#4058 step 2 (#4086) landed in between and touched the same ground: it gave
every dispatcher domain the shared `isServiceServeable` predicate, `/storage`
among them. Resolution:

- `domains/storage.ts` — deletion stands. #4086 gated the bridge; it did not
  fix the call, which is still `upload(file, { request })` against
  `upload(key, data, options?)`. Gating a handler that cannot serve either
  answer only changes which failure a caller gets.
- `http-dispatcher.ts` discovery — take main's `isServiceServeable` and drop
  the local `filesServeable` this branch had introduced. Same predicate, better
  home. The comment records why `file-storage` stays gated even though its
  surface is no longer a dispatcher domain: `handlerReady` is not standing in
  for anything there, it IS the fact the advert needs.
- The two `/storage` cases #4086 added to the handlerReady block go with the
  domain. The `degraded store keeps serving` one asserted a 200 off `upload`
  mocked to resolve `{ key }` — a return value `Promise<void>` does not have,
  the same imagined contract every other retired storage test encoded.
- `metadata-protocol` — `file-storage` stays in `DISPATCHER_GATED_SERVICES`;
  its comment no longer cites a `/storage` bridge that exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015T5CeitwV1jXLvsL1A84tw
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): packages/adapters, @objectstack/metadata-protocol, @objectstack/plugin-dev, @objectstack/runtime, @objectstack/types.

19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/packages.mdx (via packages/adapters, @objectstack/plugin-dev, @objectstack/runtime, @objectstack/types)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 30, 2026 10:40
@os-zhuang
os-zhuang merged commit 4be9d99 into main Jul 30, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/dispatcher-storage-upload-typeerror-0exbcb branch July 30, 2026 10:41
os-zhuang pushed a commit that referenced this pull request Jul 30, 2026
…ails on main

`scripts/check-wildcard-fallthrough.mjs` declared the hono adapter's
`${prefix}/storage/*` mount, which no longer exists:

    ✗ wildcard fall-through guard (#4116)
      packages/adapters/hono/src/index.ts:all `${prefix}/storage/*`
        DECLARED but not found by the scan. Moved, renamed or deleted? Update MOUNTS.

#4112 removed that mount outright — the adapter now carries a "deliberately NOT
mounted (#4087)" note in its place — because the wildcard claimed the whole
`/storage` subtree and answered its own 404 for every path `service-storage`
registers under it. That is the #4088 failure actually happening, so removal was
the fix rather than a ratchet.

#4112 and #4122 (which added this scan) were each green on their own base and
landed in that order, leaving the entry declaring a deleted mount. Reproduced on
pristine origin/main, so it is not specific to any branch — and since the guard
runs on every PR, it blocks all of them:

    git worktree add /tmp/main-check origin/main
    cd /tmp/main-check && node scripts/check-wildcard-fallthrough.mjs   # 1 problem(s)

Removes the stale entry, which is what the guard's own message asks for. The
sibling `${prefix}/auth/*` entry stays: that mount is still there
(packages/adapters/hono/src/index.ts:279), so its ratchet is still live. The
comment above them said "Both are the #4088 shape" and is now singular, with the
storage mount's removal recorded so the next reader does not have to reconstruct
why one of a documented pair disappeared.

Verified: `pnpm check:wildcard-fallthrough` self-test 15 cases, then
6 yielding / 1 ratcheted / 5 exempt across 12 namespace-claiming mounts. The two
steps that follow it in the same CI job and never got to run — check:release-notes,
check:node-version — pass too.

Refs #4116, #4117, #4112

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HrRNgrWaRtggzmrHpbomyh
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…oes not own, and unbreak the #4116 ledger (#4117) (#4133)

Two things, and the second is urgent.

## The fix (#4117)

`app.all(`${prefix}/auth/*`)` claimed a whole namespace and was TERMINAL: it
returned the auth service's response unconditionally, including better-auth's 404
for a path it does not implement, and the legacy `handleAuth` bridge's own
`handled: false` 404. That is #4088's shape. #4116's scan found it; the manual
greps that preceded that scan had not.

A 404 from better-auth, or `handled: false` from the dispatcher, now means "not
this mount's path" and the handler yields. The predicate is the dispatcher's OWN
`handled` flag wherever one exists — an explicit ownership answer beats inferring
one from a status; only the better-auth hand-off lacks such a flag, and there the
404 is the signal, exactly as in #4092.

What this buys here is narrower than #4092 and the code says so: it does NOT make
a later-registered Hono route reachable, because the `${prefix}/*` dispatcher
catch-all below is deliberately terminal (ADR-0076 OQ#9, #3576/#3608) and would
swallow one either way. It means an unowned `/auth/*` path now reaches that gated
`dispatch()` instead of dead-ending in a 404 built one mount earlier, so a domain
handler registered for it becomes reachable — which is this adapter's actual
extension mechanism. The first draft of the tests asserted the #4092 outcome and
failed, which is how the distinction got established rather than assumed.

## Unbreaking main

#4122 shipped the ledger declaring TWO ratcheted mounts here. Between its base
(974c6d4) and its merge, #4087/#4112 landed and DELETED the `/storage/*` bridge —
so the squash produced a main where the ledger declares a mount that no longer
exists, and `check:wildcard-fallthrough` fails on main with "DECLARED but not
found". That is my merge race, and this commit removes the entry.

Worth recording why the guard behaved well here: the stale-entry arm is what
caught it, immediately, on the first run against the new main. And #4112 reached
the same verdict about that wildcard independently, from the other direction —
"the wildcard was wider than the two routes it served". Two independent reads
landing on one defect is the argument for enumerating the shape rather than
finding it by eye each time.

Also extends `callsContinuation`: handing the continuation to a helper
(`yieldUnowned(c, next, …)`) now counts as yielding. Without that the checker
called this fix terminal — a false negative that would have pushed the compose
subtlety toward being duplicated at each call site instead of written once.

Guard: 7 yielding / 0 ratcheted / 5 exempt over 12 mounts. Self-test 17 cases.
adapters/hono 73 passed. Verified both defences bite: reverting the fix fails the
guard with the TERMINAL diagnostic and fails 2 of the 6 new tests. The 3 `tsc`
errors and 2 `eslint` rule-definition errors in this package are pre-existing,
measured identical on a clean tree.


Claude-Session: https://claude.ai/code/session_013VZLsKypjrGhiLpio2uWKu

Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang pushed a commit that referenced this pull request Jul 30, 2026
…moved

NOT part of this branch's work — `main` is red on it and every open PR fails the
same check, so this unblocks rather than waits.

`origin/main` @ 857a6cf fails `check:wildcard-fallthrough` on its own, verified
in a detached worktree with no branch involved:

  ✗ packages/adapters/hono/src/index.ts:all `${prefix}/storage/*`
      DECLARED but not found by the scan.

Semantic conflict between two commits that both landed, not a bug in the guard.
#4112 retired that mount outright — the adapter now carries a comment where
`app.all(prefix + '/storage/*')` was, because the handler spoke a storage contract
that does not exist and the wildcard claimed the whole `/storage` subtree. #4116's
MOUNTS ledger (#4122) was written against a tree that still had it and landed
after the removal, so the entry was stale on arrival. Git merged both cleanly
because they touch different lines; the guard is what noticed, which is it working
on day one.

Dropping the entry is the whole fix — there is nothing left to ratchet when the
mount it names does not exist. Its `${prefix}/auth/*` sibling is untouched and
still correctly ratcheted to #4117. After: 6 yielding / 1 ratcheted / 5 exempt
(12 namespace-claiming mounts).

Flagged on #4122 as well, so whoever owns it can take it back if they would
rather land it themselves.

Also enumerated the ESLint job properly this time — it runs ELEVEN checks, not the
six I had been running (`doc-authoring`, `authz-resolver`, `release-notes` and
`node-version` were the ones I was missing, plus `pnpm lint` itself). All eleven
pass, and `pnpm lint` is clean once `.cache/` — the objectui console build artifact
an earlier `.objectui-sha` bump left in this worktree — is removed. Those 61
"errors" were entirely that directory; CI checks out fresh and never had them.

Full suite: 129/129 packages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYbS3kS8xzsHNXFTzp4e2z
os-zhuang pushed a commit that referenced this pull request Jul 30, 2026
…0115 wholesale deletion

Main's #4112 (retire the /storage dispatcher bridge, #4087) adjusted the
file-storage stub's self-description and its test; both live inside the
stub table this branch deletes entirely, and the deletion supersedes the
adjustment: the in-memory storage stub is gone and the real
@objectstack/service-storage — which #4112 points at — is auto-wired.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BEM82RNKyyyTNPYHK1VJJS
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…lugins, empty slots stay empty (ADR-0115) (#4137)

Executes ADR-0115 in full, absorbing the #4112 and #4126 subsets that
landed mid-flight (their choices are canonical where they overlap:
OS_ALLOW_DEV_PLUGIN, assertNotProduction(), the loud security warn).
The entire stub table is deleted across Tiers A+B+C; service-storage
and service-realtime are auto-wired as optional child plugins; docs
converge on the assembly reality; the breaking changeset carries the
full FROM→TO table per retired slot. Closes #4104.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BEM82RNKyyyTNPYHK1VJJS
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…d envelope (#4053) (#4124)

* fix(runtime): the /ai/agents degraded fallback answers in the declared envelope (#4053)

The last unenveloped SDK-addressable route. The framework's degraded fallback —
what an open-source runtime with no service-ai answers — now returns
`{ success: true, data: { agents: [] } }` via `deps.success`, and the guard's last
ratchet retires with it: 0 ratcheted on both surfaces.

## Why `data: { agents }` and not `data: []`

#3983 set the precedent that `data` carries the payload directly, and following it
here would have looked consistent. It would also have been wrong, silently.

`AiAgentsResponseSchema` is a DECLARED payload schema; share-links' `{ links }` was
an ad-hoc wrapper with none. So this is the #3843 relocation — the declared payload
moves under `data` unchanged, as SettingsNamespacePayload did — not a reshape.

That decides the blast radius. `unwrapResponse` returns `body.data` when a body has
a boolean `success` AND a `data` key, so `data: { agents }` keeps
`client.ai.agents.list()` reading `.agents` off it, while `data: [...]` would make
`.agents` undefined and the method answer `[]`.

An empty list is not a visible failure on this route: `useAiSurfaceEnabled` gates
the ENTIRE AI surface on `agents.length > 0`, and an empty catalog is the correct
answer for a seat-less user (ADR-0068) or a Community-Edition deployment. Broken
and legitimate look identical — no error, no 403, no log.

## Consequence: no lockstep

Because the SDK reads both shapes identically, each surface converts on its own
schedule. Cloud's service-ai still answers unenveloped and keeps working unchanged;
objectui already reads all four shapes (objectui#2992). The "three repos in one
batch" framing #4053 opened with does not apply to this variant — which is the
finding, not a shortcut around it.

Five tests in @objectstack/client pin both shapes, including the road not taken:
the flattened body asserts [], so the cost of choosing it is recorded rather than
rediscovered. The dispatcher's own fallback test now asserts the envelope and that
no `agents` key survives at the top level.

runtime 914, client 205, rest 505, spec 6987. All ten typecheck-job gates and the
six check scripts pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYbS3kS8xzsHNXFTzp4e2z

* fix(runtime): re-point #4058's new /ai/agents assertion at the enveloped body

Semantic conflict, not a textual one. #4058 step 2 (#4086) landed on main while
this branch was open and added `http-dispatcher.test.ts`'s "a stub slot 404s per
route and keeps the /ai/agents empty list", which asserts the body is exactly
`{ agents: [] }` — the shape #4053 enveloped one commit earlier. The two merge
cleanly and the test fails, which is why CI caught it and the merge did not.

The courtesy that test pins is unchanged: a stub-occupied AI slot still answers an
empty list rather than a fault. The list just travels under `data` now, and the
assertion says so — plus that no `agents` key survives at the top level, so a
revert to the bare body fails here too.

Also swept the repo for any other reader of the old shape. Three hits, all
correct: the two negative assertions above, and `client/src/index.ts`'s
`body?.agents` — which is exactly what the relocation variant keeps working.

Merged main in rather than rebasing, per the branch's history so far.

Verified against the full suite the way Test Core runs it —
`turbo run test --filter='!@objectstack/dogfood'`: 129/129 packages, runtime 924.

Two worktree-staleness traps on the way, both reading as code failures:

  - `plugin-auth` could not resolve `hono` — the merge moved the lockfile and I
    had not reinstalled. `pnpm install --frozen-lockfile` fixed it.
  - `runtime` failed under turbo but passed standalone — the gitignored
    `.objectstack/data/memory-driver.json` accumulating rows again.

Neither is CI-visible; CI installs clean and checks out fresh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYbS3kS8xzsHNXFTzp4e2z

* fix(tooling): drop the wildcard ledger's entry for the mount #4112 removed

NOT part of this branch's work — `main` is red on it and every open PR fails the
same check, so this unblocks rather than waits.

`origin/main` @ 857a6cf fails `check:wildcard-fallthrough` on its own, verified
in a detached worktree with no branch involved:

  ✗ packages/adapters/hono/src/index.ts:all `${prefix}/storage/*`
      DECLARED but not found by the scan.

Semantic conflict between two commits that both landed, not a bug in the guard.
#4112 retired that mount outright — the adapter now carries a comment where
`app.all(prefix + '/storage/*')` was, because the handler spoke a storage contract
that does not exist and the wildcard claimed the whole `/storage` subtree. #4116's
MOUNTS ledger (#4122) was written against a tree that still had it and landed
after the removal, so the entry was stale on arrival. Git merged both cleanly
because they touch different lines; the guard is what noticed, which is it working
on day one.

Dropping the entry is the whole fix — there is nothing left to ratchet when the
mount it names does not exist. Its `${prefix}/auth/*` sibling is untouched and
still correctly ratcheted to #4117. After: 6 yielding / 1 ratcheted / 5 exempt
(12 namespace-claiming mounts).

Flagged on #4122 as well, so whoever owns it can take it back if they would
rather land it themselves.

Also enumerated the ESLint job properly this time — it runs ELEVEN checks, not the
six I had been running (`doc-authoring`, `authz-resolver`, `release-notes` and
`node-version` were the ones I was missing, plus `pnpm lint` itself). All eleven
pass, and `pnpm lint` is clean once `.cache/` — the objectui console build artifact
an earlier `.objectui-sha` bump left in this worktree — is removed. Those 61
"errors" were entirely that directory; CI checks out fresh and never had them.

Full suite: 129/129 packages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYbS3kS8xzsHNXFTzp4e2z

---------

Co-authored-by: Claude <noreply@anthropic.com>
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dispatcher 的 /storage/upload 用 upload(file, {request}) 调用契约里的 upload(key, data, options?) —— 对任何实现都会 TypeError

2 participants