# src/core/tracking — the consolidated file-tracking system

## Scope

The one system behind every question about a file the tool created: who created it,
what it derives from, who is using it now. Pure (REAPER-free, unit-tested outside the
DAW). Two safety-critical consumers read it and no others: prune's protected set and
the resample's replace-vs-add decision.

This is the territory's **authority**, not one of three mechanisms. Two codecs feed it
from where codecs belong — the persisted ledger's own JSON (here) and the
instrument→extension usage wire (`core/wire/sample_usage`) — but every *decision* is
made in `tracking_authority`, from one `TrackingState`.

## Invariants

**Safety-critical, overriding.** This territory gates the system's only file-deletion
authority (prune) and its only capture-replacement act (resample). A tracking error
loses a user's audio. Every failure, ambiguity, or unreadable input resolves to the
non-destructive side of the question being asked — over-protection (prune skips a
reclaimable file, or refuses to run; resample adds instead of replacing) is an
accepted residual; under-protection is a data-loss bug.

**No silent gaps — in memory at creation, on disk at the next save.**
`ReaSamplerSession::recordCreated` is the only writer, called in the same
straight-line block as the bank add with no I/O or early return between, so a
system-created file is tracked *in memory* the instant it exists. The record reaches
the `.rpp` only at the following `saveToActiveProject()`; a crash in that window, or a
whole session running with a degraded ledger (below), leaves the file untracked —
foreign, therefore never reclaimed, which is the safe direction. That residual is
accepted and stated here rather than papered over by a stronger claim.

**Lineage is never backfilled.** `OriginLedger::record` on a path already present is
`AlreadyPresent` and leaves the stored record untouched. A record says what was known
at birth or says nothing at all; nothing may later rewrite history from a guess.
Consequence, and the reason both blobs can carry a parent id: `recordCreated` *seeds*
the record from the `Sample`'s own `provenance.parentSampleId` (one source, one act),
but the `Sample` is mutable via `updateSampleInPlace` and the record is not — so where
the two ever diverge, **the ledger record is authoritative for lineage** and the
`Sample`'s copy is only the recipe facet's view of it.

**Never-recorded and degraded are different absences.** `LedgerStatus` keeps them
apart, and only `loadLedger` can tell them apart (an empty stored value is not valid
JSON, so the parser alone cannot). `Fresh` — a new project or a bank predating the
ledger — blocks nothing and yields definite answers. `Unreadable` (corrupt) and
`FutureVersion` (a `"v"` newer than this build reads) are the degraded pair: each
blocks every destructive answer AND suppresses the next write, so the stored blob
survives for recovery instead of being replaced by a ledger missing every earlier
file. `ledgerDegraded()` is the one test for both treatments — they coincide today, so
the two axes are deliberately not split.

**Consumers cannot disagree.** Both answers come out of one `TrackingState`. The two
universes differ deliberately — prune protects bank-referenced paths, ledger-owned
paths, and every live hold; the tie query counts only live holds other than the
asker's own — but the replace-vs-add universe is a **strict subset** of the
prune-protection universe, proven in `test_tracking_authority`.

**Conservatism is asymmetric by facet.** The recipe fingerprint (`core/model/provenance`)
keeps its record-nothing-when-ambiguous stance: an ambiguous parent is no parent. The
lineage half has no record-nothing option — replace-vs-add must be computable — so a
birth record is written for every system-created file, ambiguous parentage or not
(the parent is simply empty).

## Modules

- `origin_ledger` — the record family: `OriginRecord` (project-relative path, `OriginKind`,
  the sample id minted at birth, the parent sample id) and the insertion-ordered,
  path-keyed, deduplicated `OriginLedger` that holds them, with its JSON codec and the
  `loadLedger` `Fresh` / `Loaded` / `Unreadable` / `FutureVersion` classification.
  Persisted under the FOREVER-STABLE `owned_files` ext-state key; the legacy path-only
  shape (`{"owned":[...]}`) lifts in as `Unknown`-kind records with no lineage.
- `tracking_authority` — the one decision surface: `pruneProtection` (the `owned` and
  held-path inputs prune's set algebra consumes, plus the blocked/blockers verdict)
  and `tiedUsageExists` (`Yes` / `No` / `Indeterminate`, per capture, excluding the
  asker's own usage key). Both read one borrowed `TrackingState`. `resampleLanding` is
  the resample's branch off that answer: `Replace` only on a definite `No`, since
  `AddDistinct` disturbs no existing holder and is therefore the non-destructive side of
  this question — `Indeterminate` lands with `Yes`.

## Gotchas

- `OriginKind` values are PERSISTED INTEGERS — never renumber, only append. An
  unrecognized value degrades to `Unknown` rather than failing the parse: a vocabulary
  gap must not halt the prune. This degrade-don't-halt rule is about *field
  vocabulary* only — an unrecognized **document version** (`"v"`) does the opposite
  and blocks, because a changed record shape makes the whole ledger untrustworthy.
- `OriginRecord.sampleId` is NOT unique: a recapture writes a new file under the same
  bank id, so two records can share a `sampleId`. `relativePath` is the only key.
- A `unioned` usage record can never be excluded as "my own" — it carries more than one
  incarnation's holds, so attributing it to a single owner could hide a sibling's tie.
- The set algebra prune runs on `(owned ∩ present) − referenced` is `core/reclaim`'s,
  not this directory's; this directory supplies two of its three inputs.
- `sample_usage` deliberately stays in `core/wire` — it is a wire format, and its
  instrument-side writer needs it there. Consolidation is of the *decisions*, not of
  the codecs.
