# ECP-0036: Multi-Variant Manifests (One Epoch, Many Variants)

Status: Draft
Author: Codex
Reviewers: founder@every.channel
Created: 2026-02-07

## Decision

Extend `ec-core` manifests to optionally carry *multiple* variant hash-lists in a single signed manifest object.

One manifest epoch can describe:

- the "master" logical stream id (base)
- N variants, each with:
  - variant id
  - variant stream id
  - chunk index range
  - per-chunk hashes
  - per-variant merkle root

Subscribers verify chunks by selecting the variant entry matching the chunk's `stream_id` (derived from encryption `key_id`).

## Motivation

- Quality pools (ABR ladders) need coordination: all variants should share epoch boundaries and be verifiable under one signed statement.
- One manifest object per epoch reduces gossip/chattiness vs per-variant manifests.
- Makes it easy to proxy to playlist-based views (HLS) without coupling the transport to playlists.

## Data Model

Add to `ec-core`:

- `ManifestVariant` (new)
  - `variant_id: String`
  - `stream_id: StreamId`
  - `chunk_start_index: u64`
  - `total_chunks: u64`
  - `merkle_root: String`
  - `chunk_hashes: Vec<String>`
  - `metadata: Vec<StreamMetadata>` (variant-specific; optional but useful)

Extend `ManifestBody`:

- `variants: Option<Vec<ManifestVariant>>`

Rules:

- Legacy manifests keep using `chunk_hashes` (single-variant).
- Multi-variant manifests set `chunk_hashes` empty and populate `variants`.
- `ManifestBody.merkle_root` becomes:
  - legacy: merkle root of `chunk_hashes`
  - multi-variant: merkle root of ordered `variants[].merkle_root` (sorted by `variant_id`)

## Publishing

- `ec-node moq-publish` can publish a CMAF ladder: one track per variant, one init track per variant.
- After each epoch, publish a single manifest frame containing all variants' hashes for that epoch.

## Subscribing

- Subscriber requires manifests as today.
- To validate a chunk:
  - identify `stream_id` from `object.meta.encryption.key_id` (strip `/init` suffix if present)
  - find a manifest in store that covers `(stream_id, chunk_index)`
  - compare expected hash from the matching `ManifestVariant`.

## Reversibility

- Backwards compatible: old readers ignore `variants`, new readers accept both formats.
- If later we move to per-variant manifests, we can keep multi-variant as an optimization.

