Skip to content

fix(service-storage): stop handing out _local/file/:key, a URL nothing mounts (#3641) - #3744

Merged
os-zhuang merged 1 commit into
mainfrom
claude/fix-unmounted-local-file-url
Jul 28, 2026
Merged

fix(service-storage): stop handing out _local/file/:key, a URL nothing mounts (#3641)#3744
os-zhuang merged 1 commit into
mainfrom
claude/fix-unmounted-local-file-url

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3641.

Three call sites built ${basePath}/_local/file/<key>. No registrar has ever mounted it, so anyone following one got a 404. The tranche-3 storage ledger (#3636) recorded the URL as deliberately absent and filed this; now nothing builds it either.

Each site, fixed according to what it could honestly do

LocalStorageAdapter.getPresignedUpload() — omits downloadUrl (optional on the descriptor) rather than emitting a fake one. It cannot construct the real capability URL: that is keyed by sys_file.id, and an adapter only ever sees the storage key.

Nothing read the field, which is exactly how it survived. The presigned-upload route builds its own downloadUrl (${basePath}/files/:fileId/url) and ignores this one, and all three real readers of desc.downloadUrl take it from getPresignedDownload — whose URL is mounted (_local/raw/<token>). A write-only field pointing at nothing.

GET /files/:fileId/url and GET /files/:fileId — answer 501 NOT_IMPLEMENTED when the adapter has neither getPresignedDownload nor getSignedUrl, instead of returning (or 302-ing to) the unmounted URL. The caller learns the adapter is the limitation rather than chasing a broken link.

Blast radius

Confined to adapters implementing neither capability. LocalStorageAdapter and the S3 adapter both implement getPresignedDownload, so no shipped path changes. For the remaining case, a 200/302 pointing at a 404 becomes a 501 that says why — strictly more informative, and the same NOT_IMPLEMENTED code the raw routes already use for unsupported adapters.

Tests

@objectstack/service-storage198 passed (17 files), up from 196: two new conformance cases for the 501 branches. Mutation-checked — restoring either dead URL fails them.

On typecheck, precisely

This package has no typecheck script, and CI's TypeScript gate runs pnpm --filter @objectstack/spec exec tsc --noEmit — spec only. A bare tsc --noEmit here reports 39 pre-existing errors, 7 of them in test files this change never touches (the repo-wide .js-extension style under node16 resolution).

I am neither fixing those (unrelated, 39 files of churn) nor claiming this package typechecks clean. Flagging it because I had been reporting "typecheck clean" on packages that do have the script, and it would be easy to read that as a repo-wide property. It isn't.

🤖 Generated with Claude Code

https://claude.ai/code/session_01K35y3ovfWtCkBHYFCqUfAt


Generated by Claude Code

…ing mounts (#3641)

Three call sites built `${basePath}/_local/file/<key>`. No registrar has ever
mounted it, so anyone who followed one got a 404. The tranche-3 storage ledger
(#3636) recorded the URL as deliberately ABSENT and filed this; now nothing
builds it either.

Each site fixed according to what it could honestly do:

  - LocalStorageAdapter.getPresignedUpload() omits `downloadUrl` (optional on
    the descriptor). It CANNOT construct the real capability URL — that is
    keyed by sys_file.id and an adapter only ever sees the storage key. Nothing
    read the field, which is how it survived: the presigned-upload route builds
    its own downloadUrl and ignores this one, and all three real readers of
    `desc.downloadUrl` take it from getPresignedDownload, whose URL IS mounted.

  - GET /files/:fileId/url and GET /files/:fileId answer 501 NOT_IMPLEMENTED
    when the adapter has neither getPresignedDownload nor getSignedUrl, instead
    of returning/redirecting to the unmounted URL. The caller learns the
    adapter is the limitation rather than chasing a broken link.

Behaviour change is confined to adapters implementing neither capability;
LocalStorageAdapter and the S3 adapter both implement getPresignedDownload, so
no shipped path changes. A 200/302 pointing at a 404 becomes a 501 that says why.

Two conformance cases added, mutation-checked: restoring either dead URL fails
them.

service-storage: 198 passed (17 files), up from 196.

Note on typecheck: this package has no `typecheck` script and CI's TypeScript
gate runs `--filter @objectstack/spec` only. A bare `tsc --noEmit` here reports
39 pre-existing errors, 7 of them in test files this change never touches (the
repo-wide `.js`-extension style under node16 resolution). Not touched, and not
claimed as clean.

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 28, 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 28, 2026 1:21am

Request Review

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): packages/services.

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

  • content/docs/automation/webhooks.mdx (via packages/services)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/index.mdx (via packages/services)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/plugins/packages.mdx (via packages/services)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/services)

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 28, 2026 01:38
@os-zhuang
os-zhuang merged commit 2c19383 into main Jul 28, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/fix-unmounted-local-file-url branch July 28, 2026 01:38
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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Storage: GET {base}/_local/file/:key is built in three places but mounted nowhere

2 participants