Publishing, libraries and Player
What happens between "I accepted an edit" and "someone else hears it in their DAW"? This document describes the delivery paths that start from one immutable release: web links, the
What happens between "I accepted an edit" and "someone else hears it in their DAW"? This document describes the delivery paths that start from one immutable release: web links, the account library, TuneToys Player, and independent branded plugins. It also records the remote-control design that follows later.
Status: development implementation. Entitlement-aware download tickets, signed update feeds, offline license certificates, installation records, Player update offers and the first Installer (macOS arm64, development signed) exist; see tickets, feeds and licenses. The API freezes self-contained source/Frame bundles and exact engine bytes with content digests, aliases, visibility and library sync; browser publication and verified playback are connected; the Player installs and recalls verified release media; a pinned app/API image runs on the AWS pilot. Public commercial signing, specialization and full host/DAW qualification remain staged. Later sections describe the broader target alongside what exists.
One release, several delivery paths#
An editable project is mutable. A release is an immutable, verified closure of a Toy, pinned dependencies, required assets, Frame presentation, runtime requirements, distribution terms and conformance reports. A catalog entry points to releases and owns discovery and visibility. Updating a title, rating or bookmark cannot mutate a release or a running instance.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%%
flowchart LR
PROJECT["Accepted project revision"] --> JOB["Resolve · validate · build · qualify"]
JOB --> RELEASE[("Immutable release<br/>digest-addressed")]
RELEASE --> WEB["Managed link /t/ · pinned /r/<br/>or self-hosted static site"]
RELEASE --> CACHE["Verified local install cache"]
CACHE --> PLAYER["TuneToys Player in a DAW"]
RELEASE --> BINARY["Independent branded plugin"]
ACCOUNT["Account library<br/>bookmarks · tags · ratings"] --> SYNC["Library sync"]
SYNC --> CACHE
CATALOG["Catalog entry<br/>title · visibility · tags"] -.points at.-> RELEASE
classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7
classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B
classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B
class RELEASE source
class PROJECT,JOB,SYNC,CACHE,CATALOG,ACCOUNT shared
class WEB,PLAYER,BINARY hostLibrary sync makes releases discoverable locally; it does not install every bookmarked Toy or activate it on a track. Installation is an explicit download-and-verify operation. Loading prepares a new local instance. Updates are offered, never substituted into an existing performance or DAW project.
Web publication#
| Path | Meaning |
|---|---|
https://tune.toys/t/<slug-or-id> | Share alias resolved once at page load; the page keeps its release until a deliberate reload |
https://tune.toys/r/<release-digest> | Pinned release; immutable bytes on content-addressed paths |
Alias and catalog responses use appropriate revalidation. Assets needed by active or retained releases are kept. The HTTP contract describes the implemented subset.
A static web export contains its Frame renderer, browser adapter, specialized WASM, manifest and complete permitted asset closure. It runs audio on the visitor's machine after a user gesture and the necessary input permissions. Managed hosting adds links, account discovery, quotas and optional branding; self-hosting receives a buildable static artifact with the same processing semantics. Editor code and private source/history are excluded from playback bundles. Client-delivered processing is inspectable and must not be advertised as secret.
Private release delivery authorizes manifests, dependencies, builds, previews and every asset; a private listing with public CDN assets is not private. Public or unlisted demos can intentionally expose a separate approved release. Unlisted means absent from discovery, not access-controlled. Signed asset URLs expire and cannot serve as durable package identities.
TuneToys Player#
apps/player-plugin is the VST3 host shell for Tune.Toys-native Toys. It shares JUCE integration with independent exports. A later desktop Player can reuse the same library, cache and runtime clients. Hosting arbitrary third-party VST/AU binaries is a separate native feature outside the MVP.
| Concern | Design |
|---|---|
| Sign-in | System browser, desktop OAuth with PKCE and a local callback; provider/redirect policy finalized during implementation |
| Tokens | Native credential storage only; never in the WebView, Toy graph, logs or DAW state |
| Library sync | Bounded pages with a resumable cursor; cached metadata browsable offline; failed sync preserves the installed library and current audio |
| Sign-out | Removes credentials without unloading the saved session |
| Automation | Fixed stable host slots (provisional budget 64, frozen after the native spike); each DAW instance persists slot → public parameter ID with range/normalization |
| Replacing a Toy | A deliberate action with a mapping preview and documented automation consequence; bookmarks and updates never cause it |
| Extra controls | Usable in the Frame with explicit automation limits |
Renaming or reordering parameters cannot reassign a slot. Independent plugins use their own frozen public-parameter mappings rather than Player macro slots. Current native evidence is in the native Player spec and Player authorization.
Offline recall and updates#
DAW state stores the selected release identity, the admitted Toy/Frame/dependency/asset closure, stable parameter values, slot mappings, bypass, relevant host configuration and a state format version. It stores no credentials or local grants. The first MVP proposes an 8 MiB total package budget so small releases embed fully in project state; the budget is frozen at P0. Larger asset libraries need a later durable backup/packaging contract rather than an undocumented cache dependency; see native media closure.
Restore validates all state before applying any values. The installed engine must support the saved semantic/profile version; unsupported state yields a clear recovery path and leaves existing audio intact where applicable. No arbitrary native code executes from session blobs. Supported engine releases and migrations are retained and upgrade recall is tested; a hash alone cannot preserve future OS compatibility. Transient DSP history may reset under an explicit policy while parameter and preset identities stay stable.
Independent plugin binaries embed their approved graph, engine, Frame and assets. Compatible updates restore old parameter values into the receiving binary's compatible implementation with an upgrade report. A graph found in untrusted old session state is never silently executed. Player graphs are data admitted against the installed trusted engine's bounded primitive catalog.
Build and publish transaction#
A worker freezes a source revision, resolves and verifies dependencies, assets and rights, performs target admission, creates reproducible artifacts, runs conformance and records hashes, toolchain and evidence. Signing runs under narrow release authority after qualification. An API transaction creates the immutable release record and advances a catalog alias with an expected previous version. Failed or cancelled builds leave the published pointer unchanged. Retrying with the same idempotency key must not double-publish or double-charge. The isolated worker is planned; today the API performs the development subset in-process.
Versions and artifacts#
A product version (semver, frozen changelog, pinned release digests) layers over immutable releases; artifacts are verified builds of a version, and stable/beta are the only mutable pointers. Publishing a version creates the browser artifact from the pinned release; local VST3 and Standalone ZIPs, and the Chrome extension ZIP built from a release, are added with npm run publish:artifacts from the artifacts.json written by build:plugin or build:extension. The server re-hashes every upload, checks the declared inventory against the pinned release and engine, and accepts only buildable profiles, so the platform matrix never claims a target nothing builds. supported is a creator claim; tested needs an included artifact plus an evidence report for its digest. Yanking withdraws a version from downloads and feeds without touching installed bytes. GET /v1/artifacts/:id/bytes serves only free products and the browser artifact; every other artifact byte goes through a download ticket. Details: versions and platforms and the HTTP contract.
Tickets, signed feeds and licenses#
Implemented in services/api/src/distribution.ts and signing.ts (WP-06); the contract is in the HTTP API.
| Mechanism | Contract |
|---|---|
| Download tickets | POST /v1/artifacts/:id/tickets checks visibility, yank state, the paid-content guard and the update policy frozen in the grant's order snapshot (major, all, window:<months>). One artifact, at most 15 minutes, optional device scope, 256-bit token stored only as SHA-256. Anonymous callers get free public artifacts under per-IP limits. |
| Ticket bytes | GET /v1/downloads/:ticketId?t=…: single Range with If-Range, ETag = SHA-256, Repr-Digest, attachment name, private, no-store for paid. A byte budget of 3× the size bounds retries; delivering the last byte completes the ticket. Logs carry only a hash prefix. |
| Signed update feeds | GET /v1/products/:id/feed/:channel returns a tunetoys.update-manifest/1 Ed25519 envelope over exact canonical bytes plus changelog text checked against changelogSha256. Entries are an append-only ledger with a monotonic sequence; publish, yank and alias moves append, and reads re-sign content changes, key rotation or feeds older than a day (7-day expiry). |
| License certificates | POST /v1/products/:id/certificate and POST /v1/certificates/:id/refresh issue tunetoys.license-certificate/1 envelopes with a separate key: 30-day expiry, 14-day grace, allowedMajor or until, no device ID. Refund, suspension or dispute revokes certificates and open tickets in the billing transaction and posts an account notice. |
| Installations | POST /v1/installations and PUT /v1/installations/:id keep one record per device and product: installed, update_available (set when a new feed version is covered by the grant, with an update_available notification), failed, rolled_back with previous_version_id, removed. |
| Keys | Ed25519 per purpose (update_feed, license) from TUNETOYS_UPDATE_FEED_KEY / TUNETOYS_LICENSE_KEY (PEM or _FILE), or generated 0600 in the data directory for loopback development. signing_keys records public keys only; other keys of a purpose are retired (still verifying). GET /v1/distribution/keys lists them for diagnostics; clients pin keys and never add trust from it. |
Stable download links. GET /download/macos|windows|linux on the site (distributionSiteRoutes) answers 302 to a fresh anonymous ticket for the latest published installer of the product named by TUNETOYS_DESKTOP_PRODUCT_ID; GET /v1/products/:id/latest/:target returns the same version and artifact facts as JSON. Both resolve the stable channel (or ?channel=beta) with the feed's channel rules, skip yanked versions and hidden artifacts, and use the ordinary ticket checks and anonymous limits, so a download link is never a bypass. Until the app is published the route shows a small page pointing at the docs Download page.
Native and desktop clients verify before reading: crates/distribution-verify in the Player worker and Node Ed25519 in the Installer, both against the shared signed vectors. The Player keeps installing release closures through the release routes, pinned by the verified feed's release digests; ticketed artifacts stream to disk under the feed's size (hard cap 512 MiB), verify, and move atomically into a retained versions/ folder so rollback never needs the network. Certificates live in application support (0700/0600), enforce expiry and grace offline, and never touch audio, Toys or DAW state. Installed bytes keep working in every license state.
Remote control, later#
A Frame can run locally or control an independently running host. Pairing establishes host identity, allowed endpoints, session lifetime and revocable grants. A relay carries authenticated, encrypted control and observation traffic where direct access is unavailable. Raw unauthenticated runtime sockets are never exposed, and possession of a Toy link is not control authority.
The remote host owns audio and device execution. A lost connection expires control leases, releases source-owned momentary events and reports stale observations; reconnect requires a fresh authorized session and cannot silently reacquire privileged outputs. Remote audio/video streaming is a separately measured service with its own latency and cost profile. The first sharing MVP is browser-local execution, not cloud DSP hosting.
Desktop app releases#
The TuneToys desktop app (the Editor window over the bundled web editor, plus the Installer) ships through the same products, versions, artifacts, tickets and signed feeds as creator releases, without coupling to a Toy:
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%%
flowchart LR
TAG["desktop-v* tag"]:::source --> BUILD["Build and package<br/>macOS · Windows · Linux"]:::shared
BUILD --> SIGN["Developer ID + notarize<br/>Azure Trusted Signing"]:::host
SIGN --> PUB["publish:releases key<br/>create · register · publish"]:::shared
PUB --> FEED["Signed feed<br/>stable · beta"]:::shared
FEED --> DL["/download/:target"]:::host
FEED --> SELF["Installer and self-update<br/>verify, then swap"]:::host
classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7
classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B
classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B| Piece | Rule |
|---|---|
| Product | Kind app, owned by the official publisher (bit.audio), free, public; tools/seed-desktop-product.mjs creates tunetoys and prints the id for TUNETOYS_DESKTOP_PRODUCT_ID. App versions pin no Toy release, get no browser artifact, and publish with at least one included artifact; their feed versions say kind: 'app' with no release digests |
| Artifacts | desktop-app-macos installer/arm64 (zip of one TuneToys.app), desktop-app-windows installer/x64 (per-user NSIS), desktop-app-linux installer/x64 (AppImage) and standalone/x64 (.deb); inventory { kind: 'app-build', appVersion, gitCommit }. Up to 1 GiB per artifact and 4 GiB per version, streamed in and out of the blob store with Range and the stored SHA-256 as ETag |
| Signature states | signed and notarized are accepted only from an API key with publish:releases; people uploading by hand stay at unsigned/dev. Clients still check the OS signature themselves |
| Links | /download/:target serves the latest; /download/:target/:semver serves exactly one published version (the stable URL winget uses); both mint a fresh anonymous ticket |
| Clients | One Installer adapter per OS (platform.ts): install paths, a bounded JavaScript zip reader with the hostile-archive rules, and the OS signature check (codesign with the pinned Team ID on macOS, Authenticode with the pinned subject on Windows, none on Linux, where the signed feed and digest are the check). Self-update swaps the .app, runs the verified installer silently, or replaces the AppImage, only on Restart to update; .deb, store and Flatpak builds never update themselves |
release-desktop.yml builds, signs and publishes once the owner provides Developer ID and notarization credentials, Azure Trusted Signing, a publish:releases key and the production feed key (pinned with tools/pin-feed-keys.mjs); see the v0.0.14 and v0.0.15 sprints. No desktop release has been published yet.
Commerce and app releases#
Commercial storefronts, paid and free product families, preset/sample/chain packs, bundles, Pro and Stripe/Connect settlement follow the marketplace plan. The billing foundation does not yet replace release/media authorization with paid entitlements. App deployments follow the separate release runbook; they never move creator release pins.
Implementation#
- services/api — releases, aliases, catalog, library sync,
publicSite.ts - packages/sdk — release and library clients
- apps/player-plugin and native/plugin-shell — Player shell, install cache, state
- exporters/plugin — independent VST3/Standalone export
- exporters/browser-extension — Chrome MV3 extension export
- services/worker — planned isolated build worker