Skip to content

feat(sdk)!: attribute audio uploads and previews to a required userId (no signatures) - #14555

Merged
rickyrombo merged 1 commit into
mainfrom
feat/upload-user-attribution
Aug 8, 2026
Merged

feat(sdk)!: attribute audio uploads and previews to a required userId (no signatures)#14555
rickyrombo merged 1 commit into
mainfrom
feat/upload-user-attribution

Conversation

@rickyrombo

@rickyrombo rickyrombo commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Fresh implementation of upload/preview attribution on the unsigned design path. Supersedes #14550, #14552 and #14554 — the client-side EIP-712 signing approach — which can be closed in favor of this. Server counterpart: the unsigned-attribution stack OpenAudio/go-openaudio#477OpenAudio/go-openaudio#476 — a complete, linear alternative to the signed stack (OpenAudio/go-openaudio#459/#460); the two share only main.

Design

Audio uploads and generate_preview carry the id of the user they are made for — plain tus metadata / query parameter, no signature and no wallet client. The id is an assertion; what makes the scheme safe is unchanged from the analysis on the server PR:

  • Asserting someone else's id only ever credits them — exercising a claim (naming the cid on a track) happens in a signed, grant-checked entity-manager write.
  • Upload claims derive from bytes the node received; preview claims require the asserted user to already claim the source cid.
  • A minted preview cid is useless to the requester: audio never serves by bare cid, and streaming signatures are only issued for cids resolved from a track record.

Dropping the signature is what keeps every SDK flow working — including OAuth apps with no client-side wallet — with nothing but this parameter. No proxy, no wallet plumbing, no timestamp windows.

BREAKING (major changeset included)

  • tracks.uploadTrackFiles requires userId (encoded)
  • uploads.createAudioUpload requires userId (encoded)
  • Storage.generatePreview requires userId (decoded) and sends it as a query param

High-level methods (createTrack, updateTrack, uploadTrack, publishTrack) already required userId and now thread it through — their callers need no changes. Required rather than optional so an upgraded integrator cannot silently produce unclaimable uploads that fail later at publish. Always explicit, never derived from auth state — a manager or developer-app session can act for more than one user (same convention as ChatsApi.currentUserId).

Client updates

  • useUpload: current account's id on track/stem/cover-art/collection-artwork uploads, requireUserId() guard so a missing account fails loudly at the call site, and the id is in every dependency array that reads it
  • Web stems saga passes the id it already looked up
  • All four upload examples (web upload / gated-upload / upload-server, mobile upload) + READMEs
  • Docs site: sdk/uploads and sdk/tracks pages document the parameter and why it exists

Testing

  • sdk: vitest 20/20 (Storage.test.ts pins the userId query param on generatePreview), typecheck clean
  • common, web: typecheck clean; eslint clean on touched files (2 pre-existing warnings in examples untouched)
  • Example-app tsconfigs resolve the published npm SDK, so their pre-existing typecheck errors are unrelated and unchanged

🤖 Generated with Claude Code

Audio uploads and preview generation now carry the id of the user they are
made for — as plain metadata, not a signature. Validator nodes attest the
resulting cids to that user on chain (OpenAudio/go-openaudio
feat/content-auth-unsigned), which is what makes them claimable on a track
once content authorization is enforced; generate_preview refuses users that
do not already claim the source cid.

No client-side signing is involved, deliberately. The id is an assertion;
ownership is enforced where it always was, in the signed entity-manager
write that names a cid on a track. That keeps every SDK flow working —
including OAuth apps with no client-side wallet — with nothing but this
parameter.

BREAKING: userId is now required on tracks.uploadTrackFiles and
uploads.createAudioUpload, and Storage.generatePreview requires a decoded
userId. The high-level methods (createTrack, updateTrack, uploadTrack,
publishTrack) already required userId and now thread it through. Required
rather than optional so an integrator who upgrades cannot silently produce
unclaimable uploads that fail later at publish; explicit rather than derived
from auth state because a manager or developer-app session can act for more
than one user.

Clients updated to satisfy the requirement: useUpload passes the current
account's id on track, stem, cover-art and collection-artwork uploads (with
the id in every dependency array that reads it), the web stems saga passes
the id it already looked up, and all four upload examples and their READMEs
pass userId. Docs-site pages for the Uploads and Tracks APIs document the
new parameter.

Supersedes #14550, #14552 and #14554 (the EIP-712 signature approach).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 259c02b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@audius/sdk Major
@audius/sdk-legacy Patch
@audius/protocol-dashboard Patch
@audius/sp-actions Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

rickyrombo added a commit that referenced this pull request Aug 8, 2026
#14556)

Follow-up bug fix from the content-auth review of #14550/#14552 —
independent of the redesign in #14555, works against today's servers.

## The bug

`useUpdateTrack` never passes `generatePreview` to
`sdk.tracks.updateTrack`, and the SDK's `updateTrack` only calls
`generate_preview` when that flag is set. So editing a gated track's
preview start point updates `preview_start_seconds` in the track
metadata while the track keeps streaming the old `preview_cid`'s clip —
the preview is never re-sliced at the new offset. As far as I can trace,
nothing else regenerates it (`populateTrackMetadataWithUploadResponseV2`
only touches `previewCid` when a new audio file was uploaded).

## The fix

Pass `generatePreview: true` when the preview start is set and differs
from the cached previous track's value:

- The SDK already guards the rest: it skips regeneration when a new
`audioFile` is present (transcoding produces the preview then) and when
`previewStartSeconds` is undefined.
- A cache miss for the previous track errs toward regenerating, which is
idempotent for an unchanged offset.

Once #14555 lands, this call site inherits the attributed
`generate_preview` request automatically — `updateTrack` threads its
`userId` internally.

## Testing

`packages/common` typechecks clean; eslint clean on the touched file.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Marcus Pasell <marcus@audius.co>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@rickyrombo
rickyrombo merged commit 5714430 into main Aug 8, 2026
23 checks passed
@rickyrombo
rickyrombo deleted the feat/upload-user-attribution branch August 8, 2026 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant