audiotheca — Specification
version 0.1
This document specifies the audiotheca protocol: a music-library
domain layer over a metatheca
vault. audiotheca models a personal audio collection as
artists, releases, tracks,
encodings (the audio originals it received), and
playlists — all realized as metatheca entries — with
metadata recorded as facts in the audio/ namespace, original
audio and artwork stored as metatheca content blobs, lower-fidelity
renditions transcoded on demand into a local regenerable cache, and
history inherited unchanged from metatheca.
The specification is language-agnostic. The reference implementation
is in Rust (audiotheca-rs/).
For motivation, design choices, and the relationship to the substrate
family, see README.md. For the layer this
document builds on — entries, paths, facts, state chain, head,
index — see
../../metatheca/spec/SPEC.md;
for the storage primitives beneath,
../../syntheca/spec/SPEC.md
and
../../apotheca/spec/SPEC.md.
This document covers only what sits above metatheca.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119.
1 Scope and terminology
This document specifies audiotheca Phase 1: a single local library over a single metatheca vault, covering ingestion (from existing files and from the import boundary), the entity and fact model, metadata-variant resolution, on-demand transcoding into a local cache, and query. Deferred items are listed in §12.
Terminology not redefined here is inherited from metatheca SPEC §1 and, transitively, syntheca and apotheca: vault, entry, entry ID (UUID v7), path, fact, state, state chain, head, index, cella, deposit/depositum, content blob, hash (BLAKE3).
1.1 Library
A library is the unit audiotheca owns: exactly one metatheca vault whose entries and facts conform to this specification. audiotheca MUST be the sole writer of audiotheca-domain entries and facts (§3). External tools MUST reach the library through the import boundary (§8), not by writing facts directly.
1.2 Durable store and derived cache
audiotheca uses two distinct stores, separated by whether their bytes are irreplaceable or regenerable:
The durable store is the metatheca vault’s cella. It holds all facts, state, and head, plus the original audio deposits (§1.6) and artwork. It is the source of truth and MAY fan out to remote backends (e.g. an S3-compatible / R2 bucket) per syntheca’s multi-backend model.
The derived cache holds transcoded audio (§7). It is local-only, regenerable, and MUST NOT be relied upon as durable: an implementation MAY evict from it at any time, and MUST be able to reproduce any rendition by re-transcoding. The derived cache MUST NOT be synced to remote backends.
A transcoded rendition is a pure function of its source deposit and
encoder settings (§7.3); it is therefore derived
state in the same sense as metatheca’s index and ext/ cellas,
and is treated as cache, not vault content
(§1.3 states the general contract for such
state). The sole exception is a pinned rendition
(§7.5), which is promoted into the durable store
deliberately.
1.3 Auxiliary state
Beyond the derived cache, an implementation MAY maintain
implementation-private auxiliary state — projections,
indexes, or other derived structures that accelerate its own
operation — under <vault>/ext/audiotheca/, outside any cella.
These constraints make it legitimate:
Observational neutrality. Auxiliary state MUST NOT change the observable behaviour of any operation: resolution and query answered from auxiliary state MUST return results identical to the same operation evaluated from the facts under the resolution rules (§5.1 — latest fact wins per metatheca projection).
Disposability. Auxiliary state MUST be wholly derived: deleting it at any moment MUST NOT affect correctness, only performance. It is rebuilt or repaired only by the implementation that owns it.
Containment. Auxiliary state MUST NOT be written into any cella, referenced from any fact, or otherwise become interchange surface. Another implementation MUST be able to operate on the same library while ignoring it entirely.
Freshness. An implementation SHOULD key auxiliary state to the metatheca head (state hash) it reflects: content addressing makes staleness detection exact, and anything the auxiliary state cannot serve falls back to fact replay.
The derived cache and its cache index are the domain-specified instance of the same idea, carrying additional rules of their own (§1.2, §7, §7.3); this section governs any other private state an implementation keeps. Maintaining no auxiliary state is always conformant.
The reference implementation maintains a SQLite projection of entities and facts at
<vault>/ext/audiotheca/audio.db, keyed by the head state it has projected: opening the library replays only the missing suffix of the state chain into it, and falls back to a full rebuild when the projected state is not an ancestor of the current head. The same contract was ratified for the search extensions in the logopsis and semopsis specs, whose auxiliary-state sections this one mirrors.
1.4 Deposit
Audio and artwork bytes are stored as content blobs addressed by BLAKE3 hash — apotheca deposita in a cella. Original audio and artwork are deposits in the durable store; transcoded audio is a deposit in the derived cache (or, if pinned, the durable store).
1.5 Entity and kind
An entity is a metatheca entry carrying an audio/kind
fact (§3.1), of kind artist, release,
track, encoding, or playlist. Artwork is not an
entity (§4.4); transcoded renditions are not entities
unless pinned (§7.5). An entry’s kind MUST NOT
change over its lifetime.
1.6 Original and derived encoding
An original encoding is an audio rendition the library
received, not one it produced — its lineage
(§3.4) is ripped (from a CD via the
import boundary, AccurateRip-verified, lossless) or imported
(a pre-existing file, any format/quality). Originals are entities in
the durable store.
A derived rendition is one audiotheca produced by
transcoding (§7). Derived renditions live in the
derived cache and are not entities (§1.5), except
when pinned (§7.5), in which case they are encoding
entities with lineage transcoded.
1.7 Primary and master
The primary of a track is its source of record: the
original encoding that transcodes derive from and that the track
resolves to when no profile is given. By default the primary is the
highest-fidelity original under the ordering of
§4.7; a track MAY override it with an
audio/primary fact (§3.9).
A master is a primary whose fidelity is lossless. A track is not required to have a master (§4.7); a lossy primary is valid.
1.8 Metadata variant
A metadata variant is a named rendering of an entity’s
metadata, identified by a short lowercase token. The
canonical variant is utf8 and MUST be present for
any entity bearing metadata. A derived variant (Phase 1
defines ascii) is computed from the canonical variant by
transliteration, with manual pins (§5).
1.9 Encoding profile
An encoding profile is a predicate over an encoding’s technical facts (§3.5) — codec and bitrate constraints — interpreted as a ceiling for selection (§9.2). A profile MAY be attached to a playlist (§3.13) or passed to a query, export, or playback operation.
1.10 Canonical name and alias
A canonical name is a derived, normalized form of a display
name, used for deduplication and query. It is produced by a
frozen, versioned canonicalizer (vault setting
canonicalizer_version, pinned at genesis like an analyzer):
from the display name —
lowercase;
strip leading articles (Phase 1: English
the,a,an);ascii-ize via the §5.2 transliteration;
delete apostrophes;
replace each run of remaining non-alphanumeric characters with a single
-;collapse repeated
-and trim leading/trailing-.
Examples: The Rolling Stones → rolling-stones;
AC/DC → ac-dc; Sgt. Pepper's… →
sgt-peppers-….
Changing the canonicalizer is a versioned migration, not a silent
edit: because every canonical name shifts, an implementation MUST
treat a canonicalizer_version change as a re-derivation of all
aliases.
An alias is a metatheca path (a core/path
fact) pointing to an entity, spelled in canonical form. An entity
MAY have several aliases (metatheca’s many-paths-to-one-entry);
link adds one and mv renames one. Aliases are
hierarchical, mirroring the reference chain:
<artist-alias> for an artist,
<artist-alias>/<release-alias> for a release,
<artist-alias>/<release-alias>/<track-alias> for a track
(e.g. miles-davis/kind-of-blue/so-what). The hierarchy
namespaces aliases (so a band and an album that canonicalize alike do
not collide) and provides navigation.
Identity is always the entry ID (§2.1); a canonical name or alias is a derived secondary key and MUST NOT be used as the stable identity. Two distinct entities MAY share a canonical name (caught by path uniqueness and disambiguated); the entry IDs keep them distinct.
2 Entities and identifiers
2.1 Entry IDs
Every audiotheca entity is identified by its metatheca entry ID (UUID v7; metatheca SPEC §2.1). References between entities carry entry IDs in canonical RFC 4122 string form.
2.2 References
Two kinds of reference, and the distinction is normative:
By hash — to immutable bytes (audio, artwork): a 64-hex-digit BLAKE3 hash.
By entity identity — to a mutable entity (an artist, a release, a track): an entry ID, whose facts MAY evolve without invalidating the reference.
Audio and artwork MUST be referenced by hash; artists, releases, and tracks MUST be referenced by entity identity. An implementation MUST NOT reference an artist, release, or track by the hash of any of its current facts or audio, nor by its canonical name or alias (§1.10).
3 The audio/ fact namespace (current draft)
All audiotheca facts are metatheca facts whose kind begins
audio/, each targeting one entry, serialized as canonical JSON
(metatheca SPEC §3.1). Projection follows metatheca’s rule: for a
given (target, kind, …discriminators), the latest fact in
state order wins. Field shapes below are a current draft and
MAY change before 0.1.
3.1 audio/kind
{ "kind": "audio/kind", "target": "<entry-id>", "value": "release" }
One per entry. value ∈ {artist, release,
track, encoding, lyrics, playlist}.
3.2 audio/field — one metadata field
{
"kind": "audio/field", "target": "<entry-id>",
"variant": "utf8", "field": "title", "value": "Kind of Blue",
"source": "musicbrainz", "pinned_from": null
}
Discriminators: (variant, field); latest wins, giving
per-field editability.
sourceis the metadata provenance:embedded(read from the file’s own tags — low authority, superseded by any online or manual source, §5.4),musicbrainz,discogs,cd-text, ormanual.pinned_fromnon-null marks a pin on a derived variant (§5.3), whatever the fact’ssource, recording the canonical value the pin was made against.A null
valueunsets the field at that level: effective resolution (§5.1) treats the fact as absent there, letting any release-level value show through.
Field names are not enumerated normatively. Conventional
artist-level: name. Release-level: title, date,
label, catalog, releasetype. Track-level:
title, isrc. Which fields go on which kind is a matter
of where they are recorded (§4), not a closed
schema. Artist is not a field here — it is an entity
reference (§3.14); a release’s or track’s artist is
its referenced artist entity, whose own name is an
audio/field.
3.3 audio/track-of — track → release placement
{ "kind": "audio/track-of", "target": "<track-id>",
"release": "<release-id>", "disc": 1, "position": 3 }
One per track; a track MUST have exactly one in the current state.
3.4 audio/encoding-of — encoding → track, with lineage
{ "kind": "audio/encoding-of", "target": "<encoding-id>",
"track": "<track-id>", "lineage": "imported" }
One per encoding. lineage ∈ {ripped, imported,
transcoded}. ripped and imported are
originals; transcoded appears only for a
pinned rendition (§7.5) — unpinned transcodes are
cache, not entities.
3.5 audio/tech — technical facts
{
"kind": "audio/tech", "target": "<encoding-id>",
"codec": "flac", "bitrate_bps": null, "mode": "lossless",
"sample_rate_hz": 44100, "channels": 2,
"duration_ns": 367000000000, "bytes": 28934112
}
codec ∈ {flac, mp3, wav, …};
mode ∈ {lossless, cbr, vbr};
bitrate_bps is null for lossless. The deposit hash and MIME
come from the encoding’s metatheca core/blob-ref and are not
duplicated.
3.6 audio/rip — rip provenance (ripped only)
{
"kind": "audio/rip", "target": "<encoding-id>",
"ripper": "whipper", "ripper_version": "0.10.0",
"accuraterip_confidence": 7, "drive_offset": 6,
"disc_id": "<musicbrainz-disc-id>",
"ripped_at_ns": 1730000000000000000
}
accuraterip_confidence MAY be null if not
AccurateRip-verified.
3.7 audio/import — import provenance (imported only)
{
"kind": "audio/import", "target": "<encoding-id>",
"original_filename": "03 So What.mp3",
"imported_at_ns": 1730000000000000000
}
Records the file’s origin. The upstream fidelity is unverified: a file’s declared bitrate is the container’s claim, not measured (§9.3).
3.8 audio/transcode — transcode provenance (pinned only)
{
"kind": "audio/transcode", "target": "<encoding-id>",
"source_deposit": "<blake3-of-primary-audio>",
"encoder": "lame", "encoder_version": "3.100",
"params": "cbr-128/44100/stereo",
"maintained_profile": null,
"transcoded_at_ns": 1730000000000000000
}
Present only on pinned derived encodings
(§7.5). For unpinned cache renditions the same tuple
is the cache key (§7.3) but is held by the local
cache index, not as a durable fact. maintained_profile is the
identifier of the maintained profile (§7.6) that
produced this rendition, or null for a manual pin; it lets the
implementation find and refresh maintained renditions when the
primary changes (§4.7).
3.9 audio/primary — primary override (optional)
{ "kind": "audio/primary", "target": "<track-id>",
"encoding": "<encoding-id>" }
Overrides the default primary selection (§4.7). The referenced encoding MUST be an original of that track.
3.10 audio/artwork — release → artwork
{ "kind": "audio/artwork", "target": "<release-id>",
"blob": "<blake3-of-image>", "role": "front", "mime": "image/jpeg" }
Discriminator: role (e.g. front, back,
disc).
3.11 audio/lyrics-of — lyrics → track
{ "kind": "audio/lyrics-of", "target": "<lyrics-id>",
"track": "<track-id>" }
One per lyrics entity (§4.5); a track has at most
one current lyrics entity in Phase 1. The lyrics entity carries
core/blob-ref to its UTF-8 plain-text deposit and
core/mime (text/plain; charset=utf-8); it is the
deposit that holds the text, never this fact.
3.12 audio/audio-id — track audio identity (optional)
{ "kind": "audio/audio-id", "target": "<track-id>",
"pcm_hash": "<blake3-of-decoded-pcm>" }
An encoder-independent identity from decoded PCM. Optional; reserved so a future Recording entity (§12) can be introduced without changing the track model.
3.13 Playlist facts
{ "kind": "audio/playlist-meta", "target": "<pl-id>",
"name": "thumbdrive", "note": null }
{ "kind": "audio/playlist-tracks", "target": "<pl-id>",
"tracks": ["<track-id>", "<track-id>"] }
{ "kind": "audio/playlist-profile", "target": "<pl-id>",
"codec": "mp3", "max_bitrate_bps": 128000 }
audio/playlist-tracks carries the full ordered membership,
rewritten wholesale on edit. audio/playlist-profile is
optional.
3.14 audio/artist — release/track → artist(s)
{ "kind": "audio/artist", "target": "<release-or-track-id>",
"artists": [ { "artist": "<artist-id>", "role": "main" } ] }
One per target, carrying the full ordered artist list (rewritten
wholesale on edit, like audio/playlist-tracks). role ∈
{main, feat}. References artist entities by entity
identity (§2.2).
Artist resolution mirrors the metadata hierarchy
(§5.1): a track’s effective artist is its
own audio/artist if present, else its release’s. A normal
album records the artist once on the release; a various-artists
compilation records "Various Artists" on the release and each track’s
actual artist(s) on the track.
4 The entity model
4.1 Release
A release (audio/kind = release) carries release-level
audio/field facts, one audio/artist fact
(§3.14) referencing its artist(s), and zero or more
audio/artwork facts. It has no audio and no
core/blob-ref. Referenced by entity identity from its tracks.
Its alias is the path <artist-alias>/<release-alias>
(§1.10).
4.2 Track
A track (audio/kind = track) carries track-level
audio/field facts, exactly one audio/track-of fact,
optionally one audio/artist (§3.14; absent ⇒
inherits the release’s artist), optionally one audio/primary
and one audio/audio-id. It has no core/blob-ref; its
audio is held by its original encodings. A track MUST have at
least one original encoding. Its alias is the path
<artist-alias>/<release-alias>/<track-alias>, where the track
segment MAY incorporate the position to disambiguate same-titled
tracks within a release.
4.3 Encoding
An encoding (audio/kind = encoding) is the only
content-bearing entity. It MUST have a metatheca
core/blob-ref to its audio deposit, exactly one
audio/encoding-of (§3.4), and one
audio/tech (§3.5). A ripped original
additionally carries audio/rip; an imported original
carries audio/import; a pinned transcoded encoding
carries audio/transcode.
4.4 Artwork
Artwork is a deposit referenced by hash from a release (§3.10), not an entity. Identical artwork bytes dedup automatically.
4.5 Lyrics
Lyrics (audio/kind = lyrics) are plain,
unsynchronized UTF-8 text held as a deposit: the entity carries
core/blob-ref to the text, core/mime, one
audio/lyrics-of fact (§3.11) naming
its track, and one reserved metatheca path
.audiotheca/lyrics/<track-id> — an entry is
projection-visible only with a live path, and visibility is what
admits the text into the search corpus below. It has no audio.
Replacing a track’s lyrics is a new deposit plus a new
core/blob-ref on the same entity (latest-wins), so
the entity’s identity is stable across edits. Synchronized
(timed) and multi-language lyrics are out of Phase 1
(§12).
The entity-plus-blob-ref-plus-path shape, rather than an artwork-style hash reference inside a fact body, is what places lyrics in the metatheca
ext/search corpus: the search extensions enumerate the current (entry, blob) pairs of the projection-visible entries — at least one live path and a current blob — so a lyrics deposit is indexable and a search hit resolves entry →lyrics→lyrics-of→ track. The same rule keeps audio out: encodings are deliberately path-less, so their binary deposits never enter the corpus. Identical text across tracks dedups to one deposit in the cella.
4.6 Playlist
A playlist (audio/kind = playlist) carries one
audio/playlist-meta, one audio/playlist-tracks, and
optionally one audio/playlist-profile. It references tracks by
entity identity and has no audio.
4.7 Primary, master, and fidelity
Fidelity ordering. Encodings are ordered by fidelity:
lossless outranks lossy; among lossy, higher bitrate_bps
outranks lower. This order is computed from audio/tech.
Primary. The primary of a track is, by default, its
highest-fidelity original encoding under this ordering (ties broken
by preferring ripped over imported, then by earliest
entry ID). An audio/primary fact (§3.9)
overrides the default.
Master. A master is a primary in lossless mode. A track is not required to have one; a track whose best original is, e.g., a 256 kbps MP3 has that MP3 as its primary and no master, and is fully valid.
Upgrade. Acquiring a higher-fidelity original later (e.g. re-ripping a recovered CD) adds a new original; the default primary then promotes to it automatically, cached renditions derived from the old primary become stale and are regenerated on next request (§7.3), and any maintained renditions (§7.6) are re-derived from the new primary. No entity is restructured.
4.8 No cross-release audio sharing
An audio deposit MUST belong to exactly one original encoding, and thus to exactly one track and release. audiotheca does not model a recording shared across releases: distinct rips are never bit-identical, and a personal library needs no global-catalogue identity. A track’s only audio multiplicity is its set of originals plus its cached renditions; its only metadata multiplicity is its variants (§5). The rule scopes to audio deposits: a text deposit MAY be shared by several lyrics entities (§4.5) through content-address dedup.
4.9 Artist
An artist (audio/kind = artist) is a first-class entity so
that a single, stable identity carries an editable name and
propagates a rename to every release and track that references it
(§3.14). It carries artist-level audio/field
facts — chiefly name (the display name, e.g. "The Rolling
Stones") — and has no audio. Its alias is the path
<artist-alias> (§1.10), the canonical of
its name.
On ingest (§8.1), an incoming artist is matched to an existing entity, in order of preference:
by external stable ID (a MusicBrainz artist MBID, when the producer supplies one);
otherwise by canonical name, compared separator-insensitively (so
ac-dcmatchesacdc).
A match reuses the existing artist (and MAY add the incoming spelling as an additional alias); no match creates a new artist entity. Near-matches SHOULD be surfaced for confirmation rather than merged silently; the entry ID keeps genuinely-distinct same-canonical artists apart (§1.10).
5 Metadata variants and resolution
5.1 Effective metadata
The effective value of field F for track T in
variant V is resolved in two stages.
Stage 1 — hierarchy (canonical variant). Within utf8:
the latest track-level audio/field for F if present,
else the latest release-level audio/field for F on
T’s release, else absent. Track overrides release. (The artist
is resolved analogously, but as an entity reference rather than a
field — §3.14.)
Stage 2 — variant derivation. If V is canonical, the
effective value is the Stage 1 result. If V is derived: if a
pin exists for that field in V (an audio/field in
variant V with non-null pinned_from), the pinned value
wins; otherwise the value is transliterate(Stage-1 result).
Effective metadata MUST be computed on read and MUST NOT be stored as its own facts.
5.2 Transliteration
Derivation of ascii transliterates the canonical value to
ASCII (the reference implementation uses deunicode or
any_ascii). Transliteration MUST be deterministic for a
fixed library and version, which SHOULD be recorded so a
transliterator change is distinguishable from a content change.
5.3 Pins and staleness
A pin overrides transliteration for one field of one entity
in one derived variant; it is an audio/field fact in that
variant with pinned_from set to the canonical value at pin
time. A pin is stale when the current Stage-1 canonical
value differs from its pinned_from. The review workflow
MUST surface stale pins (and fields whose auto-transliteration
changed). A stale pin MUST continue to apply until explicitly
changed; staleness is advisory.
5.4 Provenance and supersession
Each audio/field records its source
(§3.2). When multiple writes set the same
(target, variant, field), the latest fact wins per metatheca
projection. Enrichment (§8.5) emits facts from
stronger sources (musicbrainz, discogs, manual)
to supersede weaker ones (embedded); an implementation MUST
present such supersessions for review rather than applying silently,
consistent with §5.3.
6 Audio-storage invariants
Normative, and the core of audiotheca. The consequence of the five rules below: an encoding’s audio hash is a function of audio alone — editing any metadata, at any level, in any variant, changes no audio deposit and re-hashes nothing.
6.1 Deposits are tag-free
A stored audio deposit — in either store, of any lineage — MUST
contain only the audio payload and the codec-mandatory headers. It
MUST NOT contain library metadata — ID3v1/ID3v2 frames, FLAC
VORBIS_COMMENT/PICTURE blocks, APE tags, MP4 metadata
atoms, embedded cover art — beyond what the bitstream format
mandates (FLAC’s STREAMINFO, which is not a tag, is
retained).
6.2 Ripped originals are byte-exact
A ripped original MUST be the exact byte output of the
ripper; it MUST NOT be rewritten, re-tagged, or re-encoded.
6.3 Imported originals may be normalized
An imported original MAY be normalized to pure
audio on ingest: the implementation MUST preserve the encoded
audio frames bit-exact (for MP3, including the leading Xing/LAME
info frame) and MUST remove only metadata blocks/tags. The
normalized deposit’s audio is identical to the input’s; only
non-audio metadata is dropped. Standard tags are captured as
embedded-sourced facts, every embedded picture is kept as
artwork, and embedded unsynchronized lyrics become a lyrics
entity’s deposit (§8.2, §4.5);
nonstandard tags are dropped without capture. The implementation
MUST NOT alter audio samples.
6.4 Transcoded renditions are tag-free
A transcoded rendition MUST be produced with the encoder
emitting no metadata tags (audio-only output).
6.5 Export may inject tags
On export (§10), tags MAY be injected into the exported copy, which is transient and never stored as a deposit.
7 Transcoding and the derived cache
7.1 Pipeline
Every transcode is decode → PCM → (resample if the target
rate differs) → encode. There is no compressed-domain bitrate
reduction. Reference stack: Symphonia (decode),
mp3lame-encoder (MP3), hound (WAV), rubato
(resample).
7.2 Producing a rendition
To satisfy a request not already cached:
Select the source: the track’s primary (§4.7). An implementation MUST NOT transcode from a derived rendition.
Decode the primary’s deposit to PCM; resample if required.
Encode with fixed, recorded parameters; output MUST be tag-free (§6.4).
Deposit the output into the derived cache (content-addressed; dedup automatic).
Record the rendition in the local cache index under its cache key (§7.3). No durable fact is written unless the rendition is pinned (§7.5).
7.3 Cache key and reproducibility
A rendition’s cache key is
hash(primary_deposit) ⊕ canonical(encoder, encoder_version, params)
Before producing a rendition the implementation MUST consult the cache index for this key and reuse a hit. Because encoder output is reproducible only for a fixed encoder version and parameters, a change in either MUST yield a new rendition rather than being treated as equivalent. When a track’s primary changes (§4.7), cache entries keyed on the former primary’s deposit become unreachable and MAY be evicted.
7.4 Direction rules
The fidelity ordering of §4.7 bounds transcoding:
A profile (§1.9) is a ceiling. Resolution serves the highest-fidelity original at or below the ceiling; if none is at or below it, the primary is transcoded down to meet it.
An implementation MUST NOT transcode up (to a higher bitrate than the source) and MUST NOT produce a lossless rendition from a lossy source.
If no original satisfies a profile and the profile cannot be met by down-transcoding (e.g. "lossless only" with a lossy primary), the request is unsatisfiable and MUST be reported, never silently substituted (§9.2).
7.5 Pinning
An implementation MAY pin a specific rendition: promote
it from the derived cache into the durable store as an encoding
entity with lineage transcoded, core/blob-ref,
audio/tech, and audio/transcode
(§3.8). A pinned rendition syncs with the
durable store like any original. Pinning is opt-in per rendition and
off by default; its purpose is serving a fixed rendition from a
machine that lacks the primary — resolution
(§9.2) serves a pinned rendition when no
original satisfies the profile, so the purpose holds without
re-transcoding. Pinning is idempotent: a rendition that already
exists durably is never duplicated (§4.8). Pinned
renditions remain subject to §6
(tag-free).
7.6 Maintained profiles
A library MAY be configured with a list of maintained
profiles — a vault-level setting maintained_profiles, a
possibly-empty list of encoding profiles
(§1.9) the library keeps materialized as
durable, pinned renditions. The default is the empty list, in which
case all transcoding is on-demand into the derived cache
(§7.2) and nothing extra is stored.
A maintained profile is proactive, durable pinning: it
applies the same resolution as §7.4 (ceiling
semantics, down-convert only, derive from the primary, never
lossless-from-lossy), but materializes the result ahead of demand as
a pinned rendition (§7.5) carrying
maintained_profile (§3.8) rather than on
first request. Maintained renditions live in the durable store and
sync to remote backends, so a transcoding-incapable client (e.g. a
mobile player reading the vault from an R2 bucket) can resolve and
stream them directly without decoding the primary.
Lifecycle:
At ingest (§8.1), for each maintained profile, the implementation MUST materialize the rendition for the new track — unless the track already has an original at or below the profile’s ceiling, in which case that original satisfies the profile (§7.4) and no rendition is produced.
On primary upgrade (§4.7), maintained renditions whose source was the former primary MUST be re-derived from the new primary. The superseded rendition is demoted: its
audio/transcodefact is re-emitted withmaintained_profilenull, leaving it an unmaintained pinned rendition whose blob remains pending GC.On policy change (a profile added to
maintained_profiles), the implementation MUST backfill it across the library; the backfill MAY adopt an existing pinned rendition with matching source and parameters instead of re-deriving. A profile removed from the list MAY have its maintained renditions unpinned (returning them to on-demand cache behavior).
A maintained profile a track cannot meet under the direction rules (§7.4) MUST be reported at materialization time, never skipped silently.
Maintained profiles are bounded by construction: each maintains at most one current durable rendition per track — demotion on upgrade is what preserves the bound. This is distinct from durably storing arbitrary on-demand transcodes, which §1.2 forbids — the bound is what makes the storage cost predictable and the policy safe to sync.
8 Ingestion and the import boundary
8.1 The ingest core
Ingest consumes (pure audio bytes + a metadata bundle) and,
within a single metatheca head advance: matches or creates the
artist(s) (§4.9), release, and track, assigning their
aliases (§1.10); deposits the audio as an
original encoding (§4.3) with technical and
lineage/provenance facts; emits the bundle’s audio/field facts
(per variant, each tagged with its source) and its
audio/artist reference fact(s); deposits/references artwork;
and materializes any maintained profiles for the new track
(§7.6). Import MUST be all-or-nothing with
respect to the head; on failure before the advance the vault is
unchanged (orphaned deposits MAY remain pending GC). audiotheca is
the sole writer (§1.1).
A metadata bundle has two sources (§8.2, §8.3); both converge on this core.
8.2 Plain-file path (built-in extraction)
For existing files, audiotheca itself reads the file’s embedded tags
(the reference implementation reads them via Symphonia’s metadata
support), producing a metadata bundle whose fields carry
source: embedded. All standard tags that map to field names
are captured, and every embedded picture becomes release artwork —
the front-typed (or first) picture as front, the rest by
their declared type. Embedded unsynchronized lyrics (ID3v2
USLT, Vorbis UNSYNCEDLYRICS) are not a field: they
become the track’s lyrics entity and deposit
(§4.5). It then normalizes the file to pure audio
per §6.3 and deposits it as an imported
original. This path requires no network and no external tool.
8.3 Manifest path (external producer)
A producer such as ripcli supplies audio files together with
an import manifest — the rich bundle channel, carrying what
embedded tags cannot: per-field metadata provenance, rip/import
provenance, artist references with canonical names and MBIDs, and
variant pins. ripcli MUST NOT write vault facts or deposits
directly; it targets import.
Package. A manifest and the files it references form one directory; file references are relative paths resolved against the manifest’s location — a reference MUST NOT be absolute and MUST NOT escape the package directory. The manifest is JSON (matching the canonical-JSON fact convention and trivially produced/consumed by both ends).
Authority. audiotheca is authoritative for anything
derivable from the bytes: it computes each deposit’s hash and
audio/tech from the actual file. The manifest therefore omits
tech; an optional per-file blake3 is accepted only as
an integrity check that the handed-off file matches.
Likewise audiotheca derives each entity’s authoritative canonical
name from its display name via the frozen canonicalizer
(§1.10); a manifest-supplied canonical
is only a match hint and the in-manifest reference key, never
authoritative.
Shape. A manifest declares a list of artists (each
with name, canonical, optional mbid) referenced
by canonical from releases and tracks, and a list of releases;
each release carries metadata, artist, artwork,
and tracks; each track carries disc, position,
optional artist, metadata, originals, and an
optional lyrics file reference — plain UTF-8 text that
becomes the track’s lyrics entity (§4.5), replacing
the current one on a targeted track. Each
{value, source} maps to one audio/field; each original
maps to one encoding entity with its lineage and provenance. A
release/track artist is a single canonical (or null on
a track, to inherit the release’s); for multiple or featured artists
it MAY instead be a list of { artist, role } with
role ∈ {main, feat}
(§3.14).
{
"manifest_version": 1,
"artists": [
{ "name": "Miles Davis", "canonical": "miles-davis", "mbid": "…" }
],
"releases": [
{
"target": null,
"artist": "miles-davis",
"metadata": { "utf8": {
"title": { "value": "Kind of Blue", "source": "musicbrainz" },
"date": { "value": "1959", "source": "discogs" },
"label": { "value": "Columbia", "source": "discogs" }
} },
"artwork": [
{ "file": "front.jpg", "role": "front" }
],
"tracks": [
{
"disc": 1, "position": 1, "artist": null,
"metadata": { "utf8": {
"title": { "value": "So What", "source": "musicbrainz" }
} },
"originals": [
{
"file": "01-so-what.flac",
"lineage": "ripped",
"blake3": "…optional integrity check…",
"rip": {
"ripper": "whipper", "ripper_version": "0.10.0",
"accuraterip_confidence": 7, "drive_offset": 6,
"disc_id": "…", "ripped_at_ns": 1730000000000000000
}
}
]
}
]
}
]
}
A track with artist: null inherits the release artist
(§3.14); a compilation sets each track’s
artist and the release artist to "Various Artists".
target (optional) is an existing entry ID to append to or
upgrade an existing entity (e.g. attaching a re-ripped master to an
existing track, §4.7); absent ⇒ create new.
A targeted entity’s metadata and artwork are
emitted like any other (latest-wins, §5.4); fields
whose {value, source} equal the current fact are skipped, and
supersessions MUST be reported. lineage: "imported"
replaces the rip block with an import block
(§3.7), which is optional:
original_filename defaults to file and
imported_at_ns to the ingest time. For ripped,
§8.4 applies.
Validation. audiotheca MUST reject the entire manifest
(all-or-nothing, §8.1) unless: every referenced
file exists and matches its blake3 if given; every
originals entry’s lineage and provenance are consistent
(ripped⇒rip, imported⇒import); every
rip block carries only §3.6 fields with their
declared types; every lyrics reference is valid UTF-8;
every artist referenced by a release/track is declared in
artists; the canonical utf8 variant is present
wherever metadata is given; any target resolves to an
entity of the expected kind; and each track has disc and
position. Re-running an import is safe but not
idempotent: the duplicate-deposit rule (§4.8)
makes a rerun that re-ingests already-bound bytes fail validation —
for create-new and targeted imports alike, nothing is ever
duplicated, but the rerun is refused rather than a no-op.
8.4 Pristine handoff
ripped master files referenced by a manifest MUST be
unmodified ripper output (§6.2) and MUST be
deposited byte-for-byte; imported originals follow the
§6.3 normalization. Manifest metadata is
recorded as facts; it MUST NOT be written into the audio bytes.
8.5 Enrichment as a post-import pass
Online metadata enrichment is not a precondition of import.
An enricher (a ripcli mode, or a sibling tool) MAY read the
library, query databases, and emit stronger-sourced
audio/field facts that supersede embedded ones
(§5.4), under review. This mirrors metatheca’s "run
an extractor over existing entries later" model and keeps the
plain-file path (§8.2) fully offline.
9 Query and encoding profiles
9.1 Metadata query
Queries resolve against metatheca’s index projection of audio/
facts, in a chosen variant (default utf8), using effective
values (§5.1). The query language is not
fixed in Phase 1; it MUST support at least predicates over
release- and track-level fields and over encoding technical facts.
9.2 Profile resolution
An encoding profile (§1.9) is a predicate
over audio/tech interpreted as a ceiling
(§7.4). Applying it to a track yields: the
highest-fidelity original at or below the ceiling; else an existing
pinned rendition (§7.5) at or below the ceiling —
preferring one derived from the current primary with parameters
matching the profile’s transcode plan, else the highest-fidelity
admitted one, whose staleness MUST be reported
(§7.6); else a down-transcode of the primary; else
an unsatisfiable result, which MUST be reported
explicitly. Whether resolution may transcode on demand or must use
only existing encodings MUST be explicit at the call site, never
silent.
9.3 Fidelity query
Because fidelity is computable from each track’s primary, an implementation SHOULD expose a query for tracks whose primary is not lossless — the re-acquisition backlog. Such results reflect declared fidelity (§3.7); audiotheca does not verify true audio bandwidth in Phase 1.
10 Export and playback
10.1 Export
export materializes encodings (resolved through a profile,
§9.2) as files outside the library. Tags
MAY be injected into the exported copies
(§6.5) in the container-native scheme (Vorbis
comments for FLAC, ID3 for MP3), derived from effective metadata
(§5.1) in a chosen variant. When tags are
injected, the track’s lyrics (§4.5) MUST be among
them wherever the scheme has a slot (ID3v2 USLT, Vorbis
UNSYNCEDLYRICS; RIFF INFO has none and omits them) — lyrics
extracted to a deposit at ingest come back at the tag-carrying
egress, so the §6.3 strip loses nothing
system-wide. Exported copies are transient and never deposited
back.
10.2 Playback
Playback resolves a track or playlist — by default to the
primary (local listening does not require transcoding), or
through a profile for constrained output — and decodes for the audio
device. The reference implementation decodes via Symphonia and plays
via rodio/cpal. Playback is read-only with respect to
the vault. Detailed playback behavior is otherwise out of Phase 1
normative scope.
11 CLI surface
The reference CLI is audiotheca (suggested alias aud);
the verb set is provisional and non-normative.
audiotheca import <files…>/import --manifest <m>— ingest.aud enrich— post-import online enrichment (supersedesembedded).aud ls/aud show/aud search/aud backlog— browse, query, fidelity backlog.aud encodings/aud get [--profile …]/aud pin— originals, on-demand resolution, rendition pinning.aud maintain add|rm <profile>— managemaintained_profiles(§7.6); adding backfills, removing may unpin.aud tag/aud variant ascii … --review— metadata and variants.aud lyrics <track> [--set <file>]— print or replace a track’s lyrics (§4.5).aud artist <alias>/aud link <alias> <entity>/aud mv <old> <new>— artists, and alias (metatheca path) add/rename.aud playlist …/aud export/aud play— playlists, export, playback.
12 Deferred (out of Phase 1)
Non-English article stripping and broader canonicalization — §1.10 handles English leading articles only.
A first-class Recording entity sharing audio identity across the collection; the
audio/audio-idfact (§3.12) reserves the hook.Verified fidelity estimation (spectral-cutoff detection of upsampled/transcode-laundered lossy files); §9.3 reports declared fidelity only.
Derived audio facts — loudness/ReplayGain, fingerprints, waveforms — as
audio/facts or a metathecaext/view.Additional metadata variants beyond
utf8/ascii.Synchronized and multi-language lyrics — §4.5 covers plain unsynchronized text only. Timed lyrics (ID3v2
SYLT, LRC) and per-language variants would addformatandlanguagediscriminators toaudio/lyrics-of(§3.11);SYLTframes are meanwhile stripped without capture.Multi-vault / multi-library, signing, and sync — inherited concerns deferred at the metatheca layer. Requirement on that sync layer: when the durable store is remote (e.g. an R2 bucket), a cold index bootstrap MUST NOT be performed by issuing per-blob GETs across the whole fact set — that is latency- and request-rate-bound (a serial state-chain walk of one round-trip per commit, plus one GET per fact), costing minutes to tens of minutes for a large library. The sync layer MUST instead transport a packed artifact: a prebuilt
index.dbkeyed by the head it projects (downloaded in one transfer and trusted because the projection is deterministic given a head), and/or fact/state bundles (git-packfile style) rather than loose objects. The serial chain walk SHOULD additionally be bounded by checkpoint state blobs inlining the full fact set periodically (metatheca SPEC §11, future work), so a bootstrap never walks to genesis one round-trip at a time. A thin/remote client serving only maintained-profile renditions (§7.6) needs no local index and is exempt. This requirement now lives with the cross-layer sync design (panthecaarchitecturerepo,sync.md, "Bulk transport and cold bootstrap"); it is restated here because audiotheca is the consumer it was written for.Full playback semantics (gapless, queues, resume).
Inherited deferrals from metatheca Phase 1 (no public hook trait, no GC, deposit-only durability) apply transitively.