# Provenance — product notes

Framing, rationale, and the dual-canvas reconciliation behind the reshaped
**Milestone 10 (provenance)**. The tickable spec's landed history is in
`docs/ARCHIVE.md` (M10); the architecture detail is in `src/core/model/CLAUDE.md`
and `src/shell/capture/CLAUDE.md` plus this note for the reconciliation calls.
This doc holds the *why* and the open forks so they don't clutter the build docs.

Status: **IMPLEMENTED (2026-07-26).** Settled 2026-07-23; landed 2026-07-26.
Reshaped from the old "provenance + null-test verify" M10. Two decisions were fixed
by Daniel up front (see *What was cut* below). The four dual-canvas interaction forks
are now resolved: **P1=a thin fingerprint, P2=a bank-only re-capture**, which makes
**P3 and P4 moot (closed)**. The fork analysis below is retained as the rationale
record — each fork is stamped with its resolution inline; nothing here is open.

---

## What was cut (fixed by Daniel — do not reopen)

- **The null-test verification ACTION is cut.** Daniel verifies bit-accuracy
  himself when he cares (he already null-tested the first capture spike, M3). The
  tool ships no null-test button.
- **The "true pre-FX dry capture" mechanism is dropped.** The old M10 note said the
  null test would require a true pre-FX dry render (bypass-around-render or the M8
  realtime pre-FX tap). With the null-test action gone, that mechanism has no
  consumer and is dropped too. `CaptureRequest.wetDry` stays in the struct as an
  inert seam (M7 already retained it), but M10 does **not** build a dry path.

What survives from the old M10: **provenance + "re-capture from source,"** which
Daniel confirmed is genuinely useful — with the added constraint that it must be
coherent with the dual-canvas (Design View / two-canvas) architecture built in
parallel.

---

## What provenance is (and what already exists)

**Provenance records where a sample came from, so a sample can be regenerated from
its source.** The concrete case: you capture something into the bank, place it,
process it further, and re-capture the processed result — provenance is the thread
back from the child sample to the parent it was resampled from, plus enough about
the capture to reproduce it.

**Most of the data model already exists.** `Sample` (M1, landed) already carries:

```
std::optional<Provenance> provenance;   // set only when resampled
struct Provenance {
    std::string parentSampleId;
    std::string fxChainSnapshot;
};
```

and it already JSON-round-trips (M1's lossless-round-trip test covers it). So M10
is **not** "add provenance fields" — the seam is cut. M10 is:

1. **Populate** `provenance` on captures that resample from an existing bank sample.
2. **Consume** it via a "re-capture from source" action that regenerates the sample.
3. **Reconcile** both with the dual-canvas model (the new work — see below).

### What `fxChainSnapshot` should mean now (given the M7 rework)

The old note assumed provenance would snapshot a chain for a *pre-FX dry* render.
That's gone. Under the **shipped M7 capture model**, capture is always wet and the
control is the **FX scope** (item = item/take FX only; track = item FX + that
track's own track FX), with the out-of-scope chain neutralized to unity by
`FxBypassGuard` for the render. There is no wet/dry dial.

So `fxChainSnapshot` should record **the capture recipe, not a dry-render chain**:
the scope, the source range (already on `Sample.sourceRange`), the source track
GUID(s) (already on `Sample.trackGuids`), the tail setting, and — the genuinely new
bit — enough of the **source FX-chain identity at capture time** that "re-capture
from source" can tell whether the source still matches what was captured. This is a
*fingerprint for reproducibility*, not a mechanism for a different render mode.

> **Fork P1 — how much chain state does `fxChainSnapshot` carry? — CHOSEN: (a)
> thin fingerprint (Daniel, 2026-07-23).** Two shapes:
> **(a) thin fingerprint** — a hash/summary of the source scope + FX-chain identity
> at capture time, used only to detect drift ("source has changed since capture")
> and to re-run the *same* capture request; or **(b) fat snapshot** — a full
> serialized FX-chain state string (`TrackFX` chunk) that re-capture could restore
> before rendering, so the regenerated sample matches even if the user has since
> tweaked the chain. (a) is simpler, non-destructive, and matches "re-capture
> reflects the source as it is *now*"; (b) is heavier, mutates the live chain during
> re-capture (a new destructive-ish surface), and re-opens some of the pre-FX-dry
> complexity we just cut. **Lean: (a) thin fingerprint.** Re-capture-from-source
> most naturally means "run the capture again against the source's *current* state"
> — that's the useful workflow (I changed the source, give me the updated sample).
> The fingerprint's job is to *tell* the user the source drifted, not to freeze it.
> **Resolved 2026-07-23: (a). `fxChainSnapshot` is a capture-recipe fingerprint for
> drift detection and re-run — not a serialized chain to restore.**

---

## The dual-canvas reconciliation (the real new work)

Daniel's constraint, verbatim: *"with the sound design canvas parallel, I think
provenance is genuinely useful, but we should make sure it's compliant with the
dual canvas stuff."*

The dual-canvas ("two-canvas," Phase D2) architecture that landed in parallel:
Design View is a **mode projection** over one timeline reaching both **tracks**
(D1 parking) and **items** (D2 fixed lanes). New content is **auto-tagged to the
active mode** at creation. Membership + lane-ownership are GUID-keyed and persist
in the `"reasampler"` `view_state` section — a **separate** pure module
(`view_mode_model`) from the bank (`bank_model`). The settled placement rule
(CONTEXT §Capture placement — mode-aware): *an explicit placement while in Design
mode lands the item in the Design lane.*

There are exactly **four** genuine interaction points between provenance and this
model. Each is a fork for Daniel.

### Where re-capture lands (the load-bearing one)

"Re-capture from source" regenerates a sample into the bank. Per the load-bearing
capture principle, **regenerating the bank sample never inserts into the timeline**
— so at the bank level there is *no* canvas interaction: the regenerated file + index
entry land in the bank exactly as any capture does, and the bank is mode-agnostic.

The interaction only appears **if re-capture also re-places** the regenerated sample
onto the timeline (replacing the old placed item). That is a *placement*, and
placement is mode-aware under D2.

> **Fork P2 — does "re-capture from source" re-place, or only refresh the bank
> entry? — CHOSEN: (a) bank-only re-capture (Daniel, 2026-07-23).** Two shapes:
> **(a) bank-only re-capture** — regenerate the file + update the bank Sample
> in place; the user re-places manually if they want the new version on the
> timeline. Fully honors the load-bearing principle with zero canvas coupling;
> simplest; matches how every other capture behaves (capture ≠ placement).
> **(b) re-capture-and-replace** — regenerate *and* swap the placed timeline item
> for the new file. Convenient, but it is an auto-placement path, so it must obey
> the D2 mode-aware placement rule (lands in the active mode's lane / the original
> item's lane) and it touches the timeline (undo block, non-destructive to
> everything else). **Lean: (a) bank-only.** It keeps M10 inside the capture
> pillar's clean "capture never places" line and defers all the canvas-placement
> complexity. (b) can be a later opt-in ("re-capture and replace in place") once (a)
> proves the provenance thread. If Daniel wants (b), P3 and P4 below become live.
> **Resolved 2026-07-23: (a). Re-capture regenerates the file into the bank and
> updates the Sample in place; it never places/replaces on the timeline. P3 and P4
> are therefore moot — closed (b) can still be revisited as a later opt-in.**

### Does provenance need to record canvas/mode membership?

`Sample` (bank) and `Membership`/`LaneOwnership` (view model) are **separate pure
modules today, by design** — the bank is mode-agnostic (a sample is just a file +
metadata; it doesn't know it was placed in Design). The question is whether
provenance must break that separation to record *which canvas/lane* the source item
lived in, so re-capture can put the regenerated sample back there.

Under Fork P2 = (a) bank-only, the answer is **no** — re-capture doesn't place, so
it needs no canvas memory; the bank stays mode-agnostic and the two pure modules
stay decoupled. Under P2 = (b) re-place, the answer becomes **yes, partially**.

> **Fork P3 — (only live if P2 = re-place) does provenance store canvas/lane
> membership? — CLOSED/moot under P2=a (2026-07-23).** If re-capture re-places, where
> does it land?
> **(a) active-mode rule** — re-placement follows the *same* D2 auto-tag/placement
> rule as any explicit placement: it lands in whatever mode is active *now*.
> Provenance stores **nothing** about canvas; the view model's existing rule
> governs. Keeps `bank_model` mode-agnostic.
> **(b) origin-lane memory** — provenance records the source item's mode/lane at
> capture time (a GUID + mode-id or lane-key) so re-capture lands the regenerated
> sample back in the *original* canvas regardless of the active mode. More faithful
> to "put it back where it was," but it couples `bank_model` provenance to
> `view_mode_model` identifiers — a cross-module reach the architecture currently
> avoids. **Lean: (a) active-mode rule**, kept in the view model; provenance stays
> pure bank metadata with no view-model ids. Only reach for (b) if "re-capture
> restores the exact original lane" is a stated requirement.
> **Closed 2026-07-23: moot under P2=a. Re-capture never places, so it stores no
> canvas memory; provenance stays pure bank metadata and the two modules stay
> decoupled. Revisit only if re-capture-and-replace (P2=b) is later adopted.**

### Re-capture and auto-tagging

D2 auto-tags **new** track/item GUIDs (detected by the panel-timer GUID diff)
to the active mode. A re-placed item (P2 = b) is a *new* item GUID on the timeline,
so it would be auto-tagged to the active mode automatically — which is exactly Fork
P3 = (a) behavior, for free, via the existing detection path. No special-casing
needed *unless* Daniel wants origin-lane memory (P3 = b), in which case re-capture
must tag the new item explicitly and **suppress** the auto-tag for that GUID (or the
two fight).

> **Fork P4 — (only live if P2 = re-place AND P3 = origin-lane) does re-capture
> preserve or re-run auto-tagging? — CLOSED/moot under P2=a (2026-07-23).** If
> provenance restores the origin lane, the
> re-placed item must be tagged to the *origin* mode, not the active mode — so
> re-capture has to write the membership itself and exempt that GUID from the
> timer's auto-tag (same class as the manual-lane exemption already in
> `autoTagNewContent`). **This fork only exists under P2=(b) + P3=(b).** Under the
> leaned defaults (P2=a, or P2=b + P3=a) it does not arise: bank-only re-capture
> places nothing, and active-mode re-placement rides the existing auto-tag path
> unchanged. **Closed 2026-07-23: moot under P2=a — bank-only re-capture places
> nothing, so no auto-tag interaction arises.**

### Summary of the settled path

Daniel took the leans (**P1=a thin fingerprint, P2=a bank-only re-capture**,
2026-07-23), so the reconciliation collapses to almost nothing: provenance is
**pure per-sample bank metadata**, `bank_model` and `view_mode_model` **stay
decoupled**, and M10 touches **no** canvas code. The dual-canvas compliance is
satisfied by *staying on the right side of the load-bearing line* (capture/re-capture
never places), not by new coupling. P3 and P4 are moot (closed) — they were only
live if re-capture also re-placed onto the timeline.

The settled shape: **keep provenance in the bank, keep re-capture a bank-only
regenerate, and let the existing D2 placement rule handle the timeline if and when
the user manually re-places.** It is the smallest thing that delivers the useful
workflow and the cleanest against the architecture.

---

## Persistence / precision-invariant implications (spec-level)

- **Provenance is already-persisted metadata.** `Sample.provenance` already
  serializes in the `BankIndex` JSON (M1) under the existing `bank_index` /
  (post-Phase-B) `banks` ext-state key. **Populating it adds no new persistence
  surface** — the round-trip test already exercises the field. The only spec note:
  if Fork P1 grows `fxChainSnapshot` from a thin string to a fat FX-chunk (P1=b),
  the field is still one string on `Sample`, so the JSON shape is unchanged, but the
  blob gets heavier — a size consideration, not a schema one. Under P1=a (thin
  fingerprint) the field stays small.
- **Under the leaned path, provenance touches `view_state` not at all.** No
  lane-ownership or membership data is added for provenance; the view section is
  unchanged. (Only P3=b would add view-model ids into provenance — and that would be
  the argument *against* P3=b.)
- **Precision invariants are unaffected.** Re-capture is a capture: it produces a
  file deterministically (bit-identical repeats hold — a re-capture with an
  unchanged source and request is byte-identical to the original capture, which is
  itself a nice provenance property), it is non-destructive to source items/tracks
  (`FxBypassGuard` snapshot/restore, as M7), it honors exact bounds, and it writes
  only relative paths. **Do not design the serialization here** — this is spec-level;
  the implementer owns the JSON encoding of whatever P1 shape Daniel picks.
- **Do not reintroduce the dry path.** The precision-invariant list in CONTEXT still
  names the null test as the "trust anchor." With the action cut, that line is now
  historical framing, not an M10 deliverable — flag for doc-keeper to reconcile when
  M10 lands, but M10 does **not** ship a null-test action or a pre-FX dry render.

---

## Decision list (settled 2026-07-23)

1. **P1 — `fxChainSnapshot` shape: CHOSEN (a) thin reproducibility fingerprint.**
   (Rejected: (b) fat serialized FX-chain chunk.)
2. **P2 — re-capture scope: CHOSEN (a) bank-only regenerate.** (Rejected for now:
   (b) re-capture-and-replace-on-timeline; may return as a later opt-in.)
3. **P3 — canvas memory: CLOSED/moot under P2=a.** Was only live under P2=b; lean
   was (a) active-mode rule, provenance stores no view ids.
4. **P4 — auto-tag interaction: CLOSED/moot under P2=a.** Was only live under P2=b +
   P3=b.

Picks 1a + 2a make P3 and P4 moot and keep M10 a small, decoupled, capture-pillar
milestone. The PLAN M10 points are locked to this path.
