Skip to content

spec: per-stem origin (separated / authored / user) — FEP #54 - #55

Open
topkoa wants to merge 5 commits into
mainfrom
fep/per-stem-origin
Open

spec: per-stem origin (separated / authored / user) — FEP #54#55
topkoa wants to merge 5 commits into
mainfrom
fep/per-stem-origin

Conversation

@topkoa

@topkoa topkoa commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Implements FEP #54. Spec + schema + example + CHANGELOG land together, per CONTRIBUTING.

The gap

stem_separation describes one separation of the whole pack. A pack whose stems have different origins therefore cannot describe itself — and those packs are ordinary:

  • a stem re-split later with a better model, while the rest keep the original split (feedBack#932);
  • a stem hand-edited in the editor (bleed cleaned off the vocals) among machine-separated ones;
  • a real recorded stem delivered alongside separated ones.

A writer in that position must choose between lying in the manifest — stamping an engine over stems it never produced, including audio a person made by hand — and destroying provenance: dropping the key, which discards the truth about every stem nobody touched. The spec's own cache-key promise ("any change among them is a cache miss") is wrong in both directions once origins are mixed: a miss for stems that are current, a hit for stems no engine will ever reproduce.

The shape

A stem MAY now describe itself, using the vocabulary-plus-provenance idiom the format already uses for lyrics (§7.1 / §7.1.1) rather than inventing a second one:

Key Meaning
source separated | authored | user — absent ⇒ inherit the pack-level stem_separation
separation {engine, model, version} for this stem, overriding the pack-level value
edit {tool, version} — the human counterpart: what produced a stem a person made
derived_from the stem id an edited stem began life as
stem_separation:          # the pack-level DEFAULT: what produced the stems that don't say
  engine: demucs
  model: htdemucs_6s
  version: "1.0.0"

stems:
  - id: guitar            # says nothing -> inherits: separated by demucs/htdemucs_6s
    file: stems/guitar.ogg
  - id: vocals            # re-split later with a better model
    file: stems/vocals.ogg
    source: separated
    separation: { engine: bs-roformer, model: bs_roformer_sw, version: "1.0.0" }
  - id: drums             # cleaned up by hand, starting from the separated drums
    file: stems/drums.ogg
    source: user
    edit: { tool: feedBack-editor, version: "0.9.2" }
    derived_from: drums

edit is the key the FEP's first draft lacked. It proposed separation: null for hand-edited stems — a negative, which records only what a stem isn't and leaves a stem a person produced with nowhere to say what made it: no tool, no version, no lineage. A hand-made stem deserves to say what made it, exactly as hand-edited lyrics already do with lyrics_source: user.

derived_from is what lets a tool say "your hand-edited bass came from the old split; re-splitting would replace the audio your edit was built on" instead of silently overwriting someone's work — the failure this whole FEP exists to prevent.

Compatibility — MINOR

  • Older reader, new pack: four unknown OPTIONAL keys on a stem entry. Per §4.2 it ignores them and falls back to stem_separation exactly as today. Coarser view of provenance; nothing breaks.
  • New reader, older pack: nothing declared ⇒ everything inherits ⇒ identical to today.
  • Nothing removed, renamed, or repurposed. A pack from a single whole-pack split does not change shape at all.

Verification

  • tools/validate.py — both examples PASS. The extended example now carries a re-split stem and a hand-edited one, so the change is exercised, not just described.
  • 57 repo tests pass.
  • Schema probed directly: accepts inherit / separated+separation / user+edit+derived_from / authored+edit; rejects a bogus source, a separation missing model, a non-semver version, and an edit with no tool — each for the right reason (I first probed with an incomplete manifest and every case was rejected for a missing duration, which would have been false confidence).

Downstream

feedBack's re-split (plugin#11) is waiting on this: with it, a partial re-split records the new engine on the stems it replaced and leaves everything else — including hand-edited stems — honestly described.

Summary by CodeRabbit

  • New Features

    • Added optional per-stem provenance/origin metadata in the manifest (source, separation, edit, derived_from) with clear inheritance/override rules against pack-level defaults.
    • Strengthened validation for the reserved full stem to ensure it never behaves like a machine-separated output.
  • Documentation

    • Updated the Feedpak v1 spec and examples to describe mixed-origin packs and Reader resolution precedence for provenance.
    • Refreshed the manifest schema and adjusted the §6.9 tones.definitions example without changing the passthrough contract.

stem_separation describes ONE separation of the WHOLE pack, so a pack whose stems have different
origins cannot describe itself. Those packs are ordinary, not exotic: a stem re-split later with a
better model; a stem hand-edited in an editor among machine-separated ones; a real recorded stem
delivered alongside separated ones.

Today a writer in that position must choose between lying in the manifest (stamping an engine over
stems it never produced, including ones a person made by hand) and destroying provenance (dropping
the key, discarding the truth about every stem nobody touched). Neither is acceptable, and the
spec's own cache-key promise is wrong in both directions once origins are mixed.

A stem MAY now describe itself, using the vocabulary-plus-provenance shape the format ALREADY uses
for lyrics (7.1/7.1.1) rather than a second idiom:

  source        separated | authored | user   (absent => inherit stem_separation)
  separation    {engine, model, version}      (this stem's, overriding the pack-level value)
  edit          {tool, version}               (the HUMAN counterpart: what made a hand-made stem)
  derived_from  a stem id                     (lineage: the edited bass began as the separated bass)

`edit` is the key the first draft of the FEP lacked. It proposed `separation: null` for hand-edited
stems - a negative, which records only what a stem ISN'T and leaves a stem a person made with
nowhere to say what produced it. A hand-made stem deserves to say what made it.

`derived_from` is what lets a tool warn "your hand-edited bass came from the old split; re-splitting
would replace the audio your edit was built on" instead of silently overwriting someone's work.

MINOR: all four keys are OPTIONAL on an existing object. An older reader ignores them and falls back
to stem_separation exactly as today. A pack from a single whole-pack split does not change shape.

Spec, schema, example and changelog land together, per CONTRIBUTING. The extended example now
carries a re-split stem and a hand-edited one, and still validates.

Signed-off-by: topkoa <topkoa@gmail.com>
@topkoa
topkoa requested a review from Copilot July 14, 2026 12:06
@topkoa

topkoa commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d3e7a02-9f5d-4d93-aa6a-fc9b56035b19

📥 Commits

Reviewing files that changed from the base of the PR and between 0d70c4e and 5096186.

📒 Files selected for processing (2)
  • schemas/manifest.schema.json
  • spec/feedpak-v1.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • spec/feedpak-v1.md
  • schemas/manifest.schema.json

📝 Walkthrough

Walkthrough

The manifest schema, Feedpak v1 specification, extended example, and changelog document optional per-stem provenance, separation, editing, and lineage metadata with pack-level fallback, precedence rules, and validation constraints.

Changes

Per-stem origin metadata

Layer / File(s) Summary
Manifest schema contract
schemas/manifest.schema.json
Preserves existing validation rules and adds optional source, separation, edit, and derived_from properties with source/separation constraints.
Origin resolution rules
spec/feedpak-v1.md
Documents per-stem provenance fields, pack-level fallback behavior, resolution precedence, examples, and writer constraints.
Examples and release documentation
examples/extended.feedpak/manifest.yaml, CHANGELOG.md
Demonstrates mixed stem origins and records the new metadata plus the tones.definitions example clarification.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Reader
  participant StemEntry
  participant PackManifest
  Reader->>StemEntry: Read source and separation metadata
  StemEntry->>PackManifest: Use stem_separation fallback when applicable
  PackManifest-->>Reader: Resolve stem provenance
Loading

Possibly related issues

  • got-feedBack/feedpak-spec#54 — Directly covers the per-stem origin metadata implemented across the schema, specification, changelog, and example.

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding per-stem origin handling for separated, authored, and user stems.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fep/per-stem-origin

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the feedpak v1 specification and manifest schema to support per-stem provenance/origin (separated/authored/user), enabling packs whose stems come from mixed sources (e.g., partial re-splits and hand edits) to describe themselves accurately.

Changes:

  • Extends the spec (§5.3 / §5.3.1) with per-stem source, separation, edit, and derived_from.
  • Updates schemas/manifest.schema.json to validate the new optional per-stem fields (including an edit object definition).
  • Updates the extended example manifest and documents the change in CHANGELOG.md.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
spec/feedpak-v1.md Adds per-stem origin/provenance keys and a new explanatory subsection under stem_separation.
schemas/manifest.schema.json Adds per-stem source/separation/edit/derived_from to stemEntry and defines $defs/edit.
examples/extended.feedpak/manifest.yaml Exercises the new per-stem metadata (hand-edited stem + re-split stem) in the extended example.
CHANGELOG.md Records the new per-stem origin feature under Unreleased.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread spec/feedpak-v1.md Outdated
Comment thread spec/feedpak-v1.md Outdated
Comment thread examples/extended.feedpak/manifest.yaml Outdated
Comment thread schemas/manifest.schema.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@schemas/manifest.schema.json`:
- Around line 287-305: Define deterministic dependency semantics for the
optional stem provenance fields: update schemas/manifest.schema.json around the
stem properties and the provenance/edit definitions to either enforce compatible
explicit source values or encode the chosen inference rules; document the
corresponding reader resolution for manifests without source in
spec/feedpak-v1.md:499; and record the finalized dependency behavior in
CHANGELOG.md:18-22.

In `@spec/feedpak-v1.md`:
- Line 500: The pack-level stem_separation inheritance rule must explicitly
exclude the reserved full mixdown (id: full). Update spec/feedpak-v1.md at lines
500-500, schemas/manifest.schema.json at lines 293-293,
examples/extended.feedpak/manifest.yaml at lines 74-74, and CHANGELOG.md at
lines 16-17 to document, apply, demonstrate, and announce that full does not
inherit Demucs stem_separation provenance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 79ebd57d-7553-4e86-a7e6-dd75eed08628

📥 Commits

Reviewing files that changed from the base of the PR and between 7eae010 and 8cd65c4.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • examples/extended.feedpak/manifest.yaml
  • schemas/manifest.schema.json
  • spec/feedpak-v1.md

Comment thread schemas/manifest.schema.json
Comment thread spec/feedpak-v1.md Outdated
…diction in the schema

The resolution order was underspecified — it said "the stem's own source (with separation/edit), if
present", which left two real cases unanswered: a stem carrying `separation` but no `source`, and a
stem saying `source: separated` with no `separation` of its own. A reader could reasonably resolve
either as "unknown", which would lose provenance the manifest is plainly carrying.

They are independent keys and now say so: `source` answers WHAT KIND of thing produced the stem,
`separation` answers WHICH ENGINE. `separation` alone implies `separated`; `source: separated`
alone inherits the pack-level engine. A truth table covers all five combinations.

That surfaced a rule the first draft never stated: a stem whose source is `user` or `authored` MUST
NOT also carry a `separation` object. It would assert that an engine produced audio a person made —
the exact lie this FEP exists to prevent, re-entering through a combination I hadn't considered.
The schema now REJECTS it, because a normative rule nothing validates is a suggestion. Lineage for
an edited stem that began as a separated one goes in `derived_from`, where it belongs.

Also: `edit.tool` is REQUIRED (the schema said so, the prose didn't), `source` carries an explicit
`type: string` like every other enum in the schema, and the example's comment no longer claims that
every stem declares its origin when `guitar` deliberately inherits.

Verified: all six rows of the new table validate, and both contradictions are rejected.

Found by Copilot on #55.

Signed-off-by: topkoa <topkoa@gmail.com>
@topkoa
topkoa requested a review from Copilot July 14, 2026 12:13
@topkoa

topkoa commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

`full` is the complete mixdown — the audio separation was run ON, not a product of it. Under the
inheritance rule I wrote, a `full` stem that declared nothing would silently inherit the pack-level
`stem_separation` and have the pack claim demucs produced the original recording. That is false for
every pack that has ever been split, and it is exactly the class of lie this FEP exists to kill —
re-entering through the one stem the format reserves.

The exception is now stated in every layer, because a rule that lives in only one of them is a rule
that drifts:

- spec: a Reader MUST NOT apply stem_separation to `id: full`; a Writer MUST NOT give it
  `source: separated` or a `separation` object. It MAY say `source: authored` positively.
- schema: both are REJECTED for `id: full`, and the `source` description says why.
- example: the retained `full` stem now carries `source: authored` and says what it is.
- changelog: describes the exception.

Verified: `full` + `separation` and `full` + `source: separated` are both rejected; `full` +
`source: authored` and a bare `full` still validate; every other stem is unaffected.

Found by CodeRabbit on #55.

Signed-off-by: topkoa <topkoa@gmail.com>
@topkoa

topkoa commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread spec/feedpak-v1.md Outdated
Comment thread spec/feedpak-v1.md Outdated
…uired

The resolution said a stem declaring `source: separated` with no `separation` of its own resolves
"by the pack-level stem_separation". But the pack may not declare one, and that combination is
perfectly honest: a pack converted from another format, or a stem whose producer never recorded the
model, knows the audio came OUT OF a separator without knowing which. As written, a reader could
treat that as invalid, or worse, invent a pack-level provenance that isn't there.

Kind and engine are now separable everywhere they are described:

  source      -> WHAT KIND of thing produced the stem   (separated | authored | user)
  separation  -> WHICH ENGINE                            (this stem's, else the pack's, else UNKNOWN)

A Reader MUST NOT treat "known kind, unknown engine" as invalid, and MUST NOT invent provenance the
pack does not carry. Spec (§5.3 field table, §5.3.1 algorithm + truth table), schema description and
changelog now all say the same thing.

Found by Copilot on #55.

Signed-off-by: topkoa <topkoa@gmail.com>
@topkoa
topkoa requested a review from Copilot July 14, 2026 12:19
@topkoa

topkoa commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread spec/feedpak-v1.md Outdated
Comment thread schemas/manifest.schema.json Outdated
…bigger one

I wrote "never applies to the RESERVED `full` stem" in the §5.3 table and the schema description,
while §5.3.1, the schema's own logic, and the example all allow `full` to carry `source: authored`.
Three layers said one thing and two sentences said another — and the two sentences are the ones a
writer reads first, so they would have looked at a valid manifest and believed it was invalid.

The actual rule is narrower, and is now stated as it is: on `full`, `separated` is FORBIDDEN and
inheritance never applies (it is the mixdown separation was run ON, not a product of it) — but
`source` MAY still state a non-separated origin such as `authored`. Which is the whole point of
letting `full` speak at all: it is the one stem that can say, positively, "a person made this, no
engine did".

Verified the prose against the schema rather than against itself: full+authored and full+user are
accepted, full+separated and full+separation are rejected, a bare full still validates.

Found by Copilot on #55.

Signed-off-by: topkoa <topkoa@gmail.com>
@topkoa
topkoa requested a review from Copilot July 14, 2026 12:31
@topkoa

topkoa commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants