hraness

content-addressed media in the tab: identity without titles

the encoding is the identity

Drafted by an AI agent at Ben Guo's direct request from the Hraness source repositories, and checked against those sources before publication.

Ask a music app where a song lives and the honest answer is usually a database row: an ID that means something to that service and nothing anywhere else. Soundfish takes a stranger route. A composition’s identity is its own encoding. The URL fragment that carries the piece is the piece, hashed twice for two different jobs, and the title you gave it is not part of the identity at all.

This is content addressing applied to media in the tab. The benefits are the ones content addressing always promises (integrity, deduplication, portability, no server required to name a thing) paid for with a strict size contract and a canonical encoding. The free half of this lesson covers the shape of the address and the bounds that make it feasible. The gated half goes into the two digests, the verification order, and how the local library keys storage by bytes rather than by name.

the url is the document

A Soundfish composition travels as a URL fragment:

#s1.<music-cid>.<codec>.<payload>.<transport-sha256>

Each field does one job. s1 is LOOP_URL_ENVELOPE_VERSION, the envelope version. The MusicCID is the semantic identity, a CIDv1 raw SHA-256 over the canonical encoding, rendered as lowercase base32 and recognizable by its leading b. The codec is one character: c for canonical CBOR, d for raw DEFLATE, and compression is chosen only when it is strictly shorter, so the encoding is deterministic rather than negotiated. The payload is the encoded document. The trailing digest is a 43-character base64url SHA-256 over the transport bytes.

The fragment is doing security work by accident of the platform: browsers do not send URL fragments in ordinary HTTP requests, so the document contents never hit a log, a referrer header, or an analytics payload. Sharing the link shares the document, and the server that serves the page never sees which document you opened. A composition that fits in a fragment needs no upload step, no account, and no permission: the address is the content, self-certifying and offline by default.

small by contract

Content addressing only works as a URL scheme if the document is small, so the model is small on purpose. A loop document is at most 16 bars of 16 steps each (MAX_BARS, BEATS_PER_BAR, STEPS_PER_BEAT), at most 16 tracks of which 15 are melodic because channel 10 is reserved for General MIDI drums (MAX_TRACKS, MAX_NOTES_TRACKS), at most 512 events per track and 832 per document, a BPM between 40 and 240, a title under 80 UTF-8 bytes, and automation lanes expressed in basis points with their own ceilings. Every bound is a named constant in the protocol model, not a convention.

Those bounds compose into a hard outer limit: MAX_CANONICAL_CBOR_BYTES is 24,493, the maximum a canonical document can occupy. MAX_LOOP_FRAGMENT_CHARS is 32 KiB and MAX_LOOP_URL_CHARS is 64 KiB, so a document that fits the model provably fits the envelope, and a link that fits the envelope fits anywhere a URL can go, including a chat message or a QR code. The decoder enforces the same order: bound the fragment characters, then bound the payload bytes, before any allocation or decompression happens. Smallness is not a limitation here; it is the price of admission that buys portability.

two digests, two jobs

The envelope carries two hashes because “identity” means two different things and conflating them is where most content-addressed systems get subtle.

The MusicCID is the semantic identity. musicCidForLoopDocument computes it over the semantic canonical encoding, the musical content of the document. Rename the piece and the MusicCID does not change, because the title is metadata about the document, not the music. Two documents with the same notes, tracks, and automation share a MusicCID even if they were authored by different people on different machines.

The transport digest is the byte identity. It covers the canonical transport bytes, which reflect the full document, title included. The digest answers “are these exact bytes what you intended to send me,” while the MusicCID answers “is this the same piece of music.” Tampering with a payload fails the transport digest. Re-encoding the same music through a different path still lands on the same MusicCID, because the CID is over canonical semantics rather than whichever bytes happened to arrive.

The split is what lets the product make claims other apps cannot. A link can be checked for corruption (transport digest), two links can be recognized as the same composition despite different titles or compression choices (MusicCID), and a library can dedupe by identity rather than by filename.

verification order

Decoding is a pipeline where each stage is bounded before the next is allowed to run. The decoder checks the fragment length against MAX_LOOP_FRAGMENT_CHARS, verifies the transport digest against the payload before trusting a single byte of it, bounds the payload before allocation, decompresses only if the codec says so, decodes the canonical CBOR within its 24,493-byte ceiling, recomputes the MusicCID and compares it against the envelope’s claim, and only then hands the document to schema and range validation against the model constants above.

The order matters because each step is a trust boundary. The digest check is cheap and rejects tampering before decompression gets a chance to expand a hostile payload. The byte ceiling comes before allocation so a crafted length field cannot reserve memory it has not earned. The MusicCID recompute makes the identity a proof: the document in the URL is not merely labeled with a CID, it produces the CID when canonicalized. Foreign values are parsed from unknown throughout rather than asserted into typed shapes, so a malformed fragment fails as a typed error instead of an exception in someone else’s code.

the library keys by bytes

Local storage inherits the same identity discipline, split the same way the digests are split. The arrangement-snapshot store jungle.soundfish.songs keeps exact canonical documents keyed by transport digest rather than by ArrangementCID, so title and section-label variants remain distinct rows and repeated saves of the same canonical document never add another. The mutable library jungle.soundfish.library does the opposite on purpose: it gives each mutable project a logical ID, because identical canonical bytes can belong to distinct logical loops. A session handoff record jungle.soundfish.loop-entry.v1 carries the observed loop ID, digest, and revision in session storage. Content digests name immutable content; logical IDs name mutable projects; neither impersonates the other.

Lineage is where the addressing gets interesting for a music app. Tracks can derive from other tracks, and the derivation records source digests, the hashes of the documents the material came from, so “this track was copied from that composition” is a verifiable claim about bytes rather than a note in a comment field. The same identity vocabulary shows up in the agent-facing grammar: the S1 agent-text protocol carries S lines of source-track IDs and source digests alongside the track declarations, within its own bounds (MAX_AGENT_TEXT_UTF8_BYTES at 512 KiB). An agent pasting a composition is not pasting a reference to a row; it is pasting the document and its ancestry.

identity without accounts

The protocol doc is explicit about what identity is not. A portable document contains no library, owner, account, or device identity. Library IDs name mutable projects. The MusicCID names authored musical meaning. The transport digest names one immutable canonical document. Installation IDs are never authentication or authorship. Each concept has its own slot, and none of them pretend to be another.

The same pattern shows up across the product family wherever bytes need names. Soundfish’s soundfont cache keys Cache Storage entries by content SHA-256 and verifies every fetched blob, so a cached sound bank is the bank whose bytes hash correctly. The brand-icon pipeline in the monorepo behind this site records input hashes and a hash of every generated derivative in its manifest. Slopcamera’s studio contracts issue operation receipts that identify inputs and outputs, separating the authoritative source from the derivative render. Content addressing is not a feature of one app; it is the house answer to “how do you name a thing so the name itself proves it.”

sources

  • sound.fish: the live product; lib/protocol/model.ts for the document bounds, lib/protocol/url.ts for the s1 envelope, codecs, and digest verification, lib/protocol/agent-text.ts for the S1 grammar, and lib/library/ for the digest-keyed local stores, all in the public repository.
  • slopcamera: src/studio/contracts.ts for operation receipts that name inputs and outputs.
  • The monorepo behind this site (projects): brand-icons.manifest.json for the same input-hash-to-derivative-hash discipline applied to icons.
  • CBOR (RFC 8949) and CIDv1: the canonical encoding and content-identifier formats the envelope builds on.

keep reading: free for subscribers

the rest of this lesson is free. add your email once and every subscriber lesson on this site stays unlocked.

already subscribed? enter the same email to unlock.