feat: proxy skill package downloads through ornn-api for chrono-bucket (#1196) - #1197
Merged
chronoai-shining merged 5 commits intoJul 7, 2026
Merged
Conversation
…#1196) chrono-bucket (replacing chrono-storage) removed the presigned-URL and object-copy endpoints and now serves downloads from a streaming GET /api/buckets/:bucket/objects/download?key=. Adapt ornn-api: - StorageClient: add downloadObject() (raw-bytes streaming read); remove the now-dead getPresignedUrl() and copy() (copy had no callers). - SkillService: route the internal byte reads (diff, /json, rescan) through downloadObject; add a gated getPackageBytes() that funnels version->storageKey resolution + the visibility gate (shared with getSkillJson), and stop minting a client-facing presigned URL in buildDetailResponse. SkillDetailResponse.presignedPackageUrl is dropped so no direct-to-storage URL ever reaches a client. - Register GET /skills/:idOrName/versions/:version/download — the route the TS SDK's downloadPackage() already targets — streaming the ZIP through ornn-api (application/zip, attachment). No analytics pull is recorded: the endpoint also backs the web file-tree viewer, so counting UI views would inflate the pull metric (programmatic reads use /json). - AuditService: fetch package bytes via skillService.getPackageBytes (SYSTEM_ACTOR) instead of the skill doc's presigned URL, deleting the wasted presigned round-trip and cast-riddled fallback (#995). - Drop presignedPackageUrl from the OpenAPI schema + spec; document the new download path. Part of #1196. Closes #995.
The web viewer used to fetch the skill ZIP straight from chrono-bucket / MinIO via the presigned URL on SkillDetail. chrono-bucket removed presigned URLs and now sits behind the NyxID proxy, so route the download through ornn-api instead: - apiClient: extract rawFetchWithRetry (auth + refresh + 401-retry in one place) and add apiGetBinary(), an authenticated GET returning raw bytes. - useSkillPackage(guid, version): fetch GET /skills/:idOrName/versions/ :version/download via apiGetBinary + JSZip, replacing fetch(presignedUrl). A non-2xx surfaces as ApiClientError -> the same translated "download failed" payload the direct-fetch path produced. - Point the three callers (useSkillDetail, usePlaygroundSession, SkillsetMemberViewer) at (skill.guid, skill.version) and drop presignedPackageUrl from the SkillDetail type + test fixtures. Part of #1196.
#1196) The new GET /skills/:idOrName/versions/:version/download route is optionalAuth and anonymous-reachable, so its object-level (BOLA) gate lives entirely in SkillService.getPackageBytes. That gate is a distinct copy from getSkillJson's, and the route test mocks getPackageBytes — so nothing exercised the real gate. Deleting the canReadSkill check would have shipped green while streaming private-skill ZIPs to anonymous callers. Add real-service tests (private+stranger -> skill_not_found before any download; public+stranger -> bytes; private+owner/SYSTEM_ACTOR -> bytes) so that regression can never pass silently. Part of #1196.
useSkillPackage called apiGetBinary("/skills/:guid/versions/:version/
download") without the /api/v1 prefix that every other apiClient path
carries, so the browser's request 404'd at the NyxID proxy and the skill
detail page showed "failed to load package contents". The in-repo route
and the SDK both work; only the web path was wrong. Component tests mock
useSkillPackage wholesale, so nothing caught it — add a focused hook test
asserting the exact proxied path (/api/v1/skills/:guid/versions/:version/
download) alongside the fix.
Part of #1196.
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.
Summary
Adapts ornn to chrono-bucket (the new object-storage backend replacing chrono-storage). chrono-bucket is a NyxID resource server that removed the presigned-URL and object-copy endpoints and now streams downloads via
GET /api/buckets/:bucket/objects/download?key=behind the NyxID proxy. This PR swaps the storage-read primitive, adds a proxied download route through ornn-api, and stops leaking a direct storage URL to clients.What changed
Backend (
ornn-api)StorageClient: add streamingdownloadObject(); remove the now-deadgetPresignedUrl()andcopy()(copy had no callers).SkillService: internal byte reads (diff //json/ rescan) go throughdownloadObject; add a gatedgetPackageBytes()that funnels version→storageKey resolution + the object-level visibility gate (shared withgetSkillJson);buildDetailResponsestops minting a presigned URL and dropsSkillDetailResponse.presignedPackageUrl— no direct-to-storage URL reaches a client, and the hot detail-read path loses a per-read storage round-trip.GET /skills/:idOrName/versions/:version/download— streams the ZIP through ornn-api (application/zip, attachment; RFC 7807 on error). This is the exact route the TS SDK'sdownloadPackage()already targeted but which was never registered.optionalAuth; the visibility gate lives ingetPackageBytes(private skills 404 without leaking existence; public skills download anonymously). No analytics pull is recorded — the endpoint also backs the web file-tree viewer, so counting UI views would inflate the pull metric.AuditService: fetch package bytes viaskillService.getPackageBytes(guid, SYSTEM_ACTOR)instead of the skill doc's presigned URL — removing the wasted presigned round-trip and cast-riddled fallback ([Misc] buildAuditContext makes a wasted presigned-URL round-trip and keeps dead, cast-riddled code #995).Frontend (
ornn-web)apiClient: extractrawFetchWithRetry(auth + refresh + 401-retry in one place) and addapiGetBinary(), an authenticated GET returning raw bytes.useSkillPackage(guid, version)fetches the proxied route viaapiGetBinary+ JSZip instead offetch(presignedUrl); callers (useSkillDetail,usePlaygroundSession,SkillsetMemberViewer) repointed atskill.guid/skill.version;presignedPackageUrldropped from theSkillDetailtype. Every live download surface (file-tree viewer + hero "Download package") now routes through ornn-api.Testing
ornn-apifull suite green (incl. new download-route tests + a real-service regression guard on thegetPackageBytesvisibility gate);ornn-web(Vitest) and TS SDK suites green; typecheck (api/web/sdk) + ESLint clean.Note
The runtime cutover is separate: NyxID's proxy node must be repointed to chrono-bucket and ornn granted
chrono-bucket-user+ornn-bucket ownership before downloads function end-to-end. This PR is the code adaptation only.Closes #1196
Closes #995