every.channel: sanitized baseline
This commit is contained in:
commit
897e556bea
258 changed files with 74298 additions and 0 deletions
56
evolution/proposals/ECP-0001-ecp-process.md
Normal file
56
evolution/proposals/ECP-0001-ecp-process.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# ECP-0001: every.channel proposals process
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a lightweight, consistent way to propose and review changes while keeping the constitution focused on long-lived principles.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt ECP (every.channel proposals) as the primary decision record.
|
||||
|
||||
Each ECP is a short, versioned document in `evolution/proposals/` that records context, decisions, and rollout steps.
|
||||
|
||||
### Required sections
|
||||
|
||||
- Title and status
|
||||
- Problem / context
|
||||
- Decision
|
||||
- Alternatives considered
|
||||
- Rollout / teardown plan
|
||||
- Open questions (optional)
|
||||
|
||||
### Status values
|
||||
|
||||
- Draft
|
||||
- Accepted
|
||||
- Implemented
|
||||
- Superseded
|
||||
- Rejected
|
||||
|
||||
### Numbering
|
||||
|
||||
- `ECP-0001` is this process.
|
||||
- New ECPs increment by 1 and use a short, descriptive slug.
|
||||
|
||||
### Review and acceptance
|
||||
|
||||
- Non-trivial changes require an ECP.
|
||||
- The founder is the final reviewer and sign-off authority.
|
||||
- Once accepted, implementation may proceed or continue.
|
||||
|
||||
### Identity and signing
|
||||
|
||||
- Commits must be signed with SSH or age identities (minimum: SSH-signed commits).
|
||||
- If signing is not configured, implementation pauses until clarified.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Free-form issues: rejected due to loss of decision history.
|
||||
- Heavyweight RFCs: rejected due to unnecessary overhead.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Add this process and create initial ECPs.
|
||||
- If ECPs become too heavy, supersede this with a lighter process.
|
||||
30
evolution/proposals/ECP-0002-initial-technical-direction.md
Normal file
30
evolution/proposals/ECP-0002-initial-technical-direction.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# ECP-0002: initial technical direction
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a coherent starting stack that aligns with the constitution and can evolve without locking the project into fragile dependencies.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt the following initial technical direction:
|
||||
|
||||
- Rust-first core for protocol and node runtime.
|
||||
- Media over QUIC (MoQ) for publish/subscribe media delivery.
|
||||
- Deterministic chunking profiles to support de-duplication and availability.
|
||||
- iroh as the programmable mesh substrate for peer routing.
|
||||
- Client surfaces: Tauri desktop app, CLI, and static web UI.
|
||||
|
||||
These are starting points, not immutable commitments. They may be revised by later ECPs.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- HLS/DASH for primary delivery: rejected due to mismatch with low-latency pub/sub goals.
|
||||
- Centralized relay only: rejected due to resilience and user sovereignty goals.
|
||||
- Non-Rust core: deferred to keep early integration cohesive.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Scaffold crates and documentation that align with this direction.
|
||||
- Revisit once MoQ implementations and browser support stabilize.
|
||||
29
evolution/proposals/ECP-0003-hdhomerun-ingest.md
Normal file
29
evolution/proposals/ECP-0003-hdhomerun-ingest.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# ECP-0003: HDHomeRun discovery and lineup ingest
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a reliable way to discover HDHomeRun devices on a LAN and ingest their channel lineups (including all fields) so we can map channels into every.channel.
|
||||
|
||||
## Decision
|
||||
|
||||
Implement a two-path discovery and ingest flow:
|
||||
|
||||
1. UDP discovery broadcast to port 65001 using the HDHomeRun TLV packet format, wildcard device ID, and tuner device type filter.
|
||||
2. HTTP hydration using `/discover.json` and `/lineup.json` for full metadata.
|
||||
|
||||
The UDP response supplies the IP and base capabilities, while the HTTP endpoints provide rich metadata and lineup entries. The ingest layer stores all unknown fields as raw JSON to keep future flexibility.
|
||||
|
||||
Where mDNS is available, allow host-based discovery via `hdhomerun.local` or `<deviceid>.local` by fetching `http://<host>/discover.json`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- mDNS-only discovery: rejected because UDP discovery is the documented primary path and does not depend on mDNS configuration.
|
||||
- Manual IP entry only: rejected because it prevents zero-config onboarding.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Add discovery and lineup ingestion to `ec-hdhomerun`.
|
||||
- Expose CLI commands for discovery and lineup JSON parsing.
|
||||
- If discovery proves unreliable on some platforms, add interface-specific broadcast addresses or a user-provided host override.
|
||||
54
evolution/proposals/ECP-0004-global-stream-identifiers.md
Normal file
54
evolution/proposals/ECP-0004-global-stream-identifiers.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# ECP-0004: global stream identifiers and swarms
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a global, deterministic way to identify streams so that identical broadcasts from different antennas can converge in the same swarm without coordination.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a two-layer identifier scheme:
|
||||
|
||||
1. **Broadcast identity**: a logical identifier derived from broadcast metadata (PSIP/ATSC) such as transport stream ID and program number. This is the primary convergence key.
|
||||
2. **Source identity**: a physical identifier used when broadcast identity is not yet available (e.g., early ingestion). This is a fallback.
|
||||
|
||||
A canonical stream ID string will be generated as:
|
||||
|
||||
`ec/stream/v1/<scope>/<fields...>/profile-<profile>/variant-<variant>`
|
||||
|
||||
Where `<scope>` is `broadcast` or `source`.
|
||||
|
||||
### Broadcast scope fields
|
||||
|
||||
- `standard` (e.g., `atsc`)
|
||||
- `tsid-<transport_stream_id>` (when known)
|
||||
- `program-<program_number>` (when known)
|
||||
- optional `callsign-<callsign>` and `region-<region>` as hints
|
||||
|
||||
### Source scope fields
|
||||
|
||||
- `kind` (e.g., `hdhr` or `linux-dvb`)
|
||||
- `device-<device_id>` when available
|
||||
- `channel-<channel_reference>` when available
|
||||
|
||||
### Profile and variant
|
||||
|
||||
- `profile` identifies the deterministic encoding profile.
|
||||
- `variant` identifies audio language, resolution, or alternate tracks.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The network can converge on the same stream even when multiple relays ingest the same broadcast.
|
||||
- Early ingestion may start with `source` scope and migrate to `broadcast` scope once PSIP metadata is parsed.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Single opaque UUID per stream: rejected because it prevents convergence without coordination.
|
||||
- Content hash as stream ID: deferred; may be layered later as an availability primitive.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Implement `StreamKey` in `ec-core`.
|
||||
- Extend ingest pipeline to parse PSIP/ATSC IDs and promote `source` to `broadcast` IDs.
|
||||
- Revise once PSIP parsing is in place.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# ECP-0005: deterministic chunking pipeline
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a deterministic encoding and chunking pipeline so that identical broadcasts produce identical objects across relays.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a deterministic chunking profile with the following constraints:
|
||||
|
||||
- Single-threaded encoding by default.
|
||||
- Fixed GOP cadence and keyframe placement.
|
||||
- Bitexact flags enabled wherever possible.
|
||||
- Fixed chunk duration (default 2 seconds) and stable object naming.
|
||||
|
||||
Initial implementation uses ffmpeg CLI piping into the ac-ffmpeg chunker for rapid iteration, while the primary goal is to move to libav bindings for deeper control and determinism validation.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Multithreaded encoders: rejected because they reduce reproducibility.
|
||||
- Hardware encoders: deferred because determinism is uncertain.
|
||||
- HLS segmentation: rejected as the primary format; MoQ objects will be derived directly.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Introduce `ec-chopper` with a deterministic profile and ffmpeg-based segmenter.
|
||||
- Measure determinism with repeated runs.
|
||||
- Replace the CLI with libav bindings once the control surface is verified.
|
||||
27
evolution/proposals/ECP-0006-linux-iptv-ingest.md
Normal file
27
evolution/proposals/ECP-0006-linux-iptv-ingest.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# ECP-0006: Linux IPTV (LinuxDVB) ingest
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a second ingest source beyond HDHomeRun so that Linux tuner stacks can participate in the relay mesh.
|
||||
|
||||
## Decision
|
||||
|
||||
Provide a LinuxDVB ingest module that:
|
||||
|
||||
- Opens `/dev/dvb/adapterX/dvrY` as a byte stream.
|
||||
- Optionally spawns a tuning command (e.g., `dvbv5-zap -r`) before opening the DVR stream.
|
||||
- Enumerates local adapters from `/dev/dvb` to support "zero config" device discovery in the UI.
|
||||
- Optionally reads channel names from a `channels.conf` file (common locations like `~/.dvb/channels.conf`) to populate pickers without scanning.
|
||||
- Runs on Linux only, returning an explicit error on other platforms.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Raw ioctl tuning in Rust: deferred due to complexity and lack of immediate testing hardware.
|
||||
- IPTV UDP-only ingest: deferred to a later proposal.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Add `ec-linux-iptv` crate.
|
||||
- Expand with ioctl tuning once hardware is available.
|
||||
30
evolution/proposals/ECP-0007-tauri-dioxus-viewer.md
Normal file
30
evolution/proposals/ECP-0007-tauri-dioxus-viewer.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# ECP-0007: Tauri + Dioxus viewer bridge
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a macOS-ready viewer so we can watch every.channel streams while MoQ playback matures.
|
||||
|
||||
## Decision
|
||||
|
||||
Ship an embedded Dioxus web frontend inside a Tauri shell. The viewer lists generic stream descriptors (no HDHomeRun-specific UI) and plays a selected stream via a local HTTP bridge.
|
||||
|
||||
The bridge:
|
||||
|
||||
- Spawns ffmpeg to transcode a stream URL into short CMAF-style HLS segments (fMP4).
|
||||
- Serves the generated playlist and segments from a local HTTP server.
|
||||
- Returns a playback URL to the frontend.
|
||||
|
||||
This preserves the stream abstraction while providing a pragmatic playback path on macOS (WebKit/HLS).
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Wait for MoQ playback: rejected for now because it blocks local iteration.
|
||||
- Native AVFoundation player: deferred to keep UI portable and simple.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Implement `list_streams` + `start_stream` commands in the Tauri backend.
|
||||
- Add Dioxus frontend with channel list and player.
|
||||
- Replace the HLS bridge with MoQ playback when the transport stack stabilizes.
|
||||
33
evolution/proposals/ECP-0008-iroh-discovery-transport.md
Normal file
33
evolution/proposals/ECP-0008-iroh-discovery-transport.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# ECP-0008: iroh discovery + transport design
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a discovery and transport layer that can survive hostile networks, reduce central points of failure, and support stream swarms across independently hosted relays.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt iroh as the mesh substrate with the following design:
|
||||
|
||||
- **Dial by public key**: every node has an iroh endpoint identity; connections are established by endpoint id with automatic hole punching and relay fallback.
|
||||
- **MoQ over iroh**: run MoQ sessions over iroh QUIC connections using a dedicated ALPN (e.g. `every.channel/moq/0`).
|
||||
- **Discovery via gossip**: use iroh-gossip to disseminate stream catalogs and track announcements, keyed by `StreamId`.
|
||||
- **Transport flexibility**: support iroh custom transports (e.g. Tor) for nodes that need anonymity or censorship resistance.
|
||||
- **Multipath first**: leverage iroh’s multipath QUIC capabilities to increase resilience and adapt to changing network conditions.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Nodes can join the swarm with minimal configuration and no centralized registry.
|
||||
- The mesh can degrade gracefully under takedown pressure by routing through relays or Tor.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Centralized rendezvous servers: rejected due to fragility and governance risk.
|
||||
- Manual peer lists: rejected due to onboarding friction.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Add an iroh transport crate that exposes a `connect` API for MoQ sessions.
|
||||
- Implement gossip-based stream catalog synchronization.
|
||||
- Add optional Tor transport profile for high-risk regions.
|
||||
36
evolution/proposals/ECP-0009-moq-implementation-selection.md
Normal file
36
evolution/proposals/ECP-0009-moq-implementation-selection.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# ECP-0009: MoQ implementation selection
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a practical MoQ implementation to begin integration with iroh while the protocol evolves.
|
||||
|
||||
## Survey (Rust implementations)
|
||||
|
||||
- **moq-dev/moq**: MoQ reference repository with Rust crates including `moq-lite`, `moq-relay`, and `hang` catalogs.
|
||||
- https://github.com/moq-dev/moq
|
||||
- **cloudflare/moq-rs**: Rust implementation of the IETF MoQ Transport draft.
|
||||
- https://github.com/cloudflare/moq-rs
|
||||
|
||||
## Decision
|
||||
|
||||
Start with `moq-lite` (from moq-dev/moq) and reuse the adapters proven by `iroh-live`:
|
||||
|
||||
- `web-transport-iroh` for WebTransport-style bindings over iroh.
|
||||
- `iroh-moq` for creating MoQ sessions over iroh connections.
|
||||
|
||||
Track `cloudflare/moq-rs` in parallel for standards-aligned MoQ Transport and schedule
|
||||
interoperability tests once we have live relays.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Building a fresh MoQ stack: rejected due to duplication and slow iteration.
|
||||
- Waiting for browser-native MoQ: rejected because it delays core network validation.
|
||||
- Starting directly on cloudflare/moq-rs: deferred until we need draft-accurate transport.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Vendor or depend on `moq-rs` crates.
|
||||
- Implement a minimal publish/subscribe pipeline over iroh.
|
||||
- Re-evaluate once MoQ standardization and browser support stabilize.
|
||||
46
evolution/proposals/ECP-0010-time-synchronized-chunking.md
Normal file
46
evolution/proposals/ECP-0010-time-synchronized-chunking.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# ECP-0010: time-synchronized chunking
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
To achieve byte-identical chunks across uncoordinated antennas, we need a shared timeline for chunk boundaries. If chunk boundaries are derived from local capture start time, identical broadcasts will diverge.
|
||||
|
||||
## Decision
|
||||
|
||||
Define a **time-synchronized chunking model** based on broadcast clocks:
|
||||
|
||||
1. **Primary clock: PCR**
|
||||
- Use MPEG-TS PCR (27 MHz) as the canonical timeline for chunk boundaries.
|
||||
2. **UTC anchor (when available)**
|
||||
- ATSC: use PSIP STT to compute UTC time.
|
||||
- DVB: use TDT/TOT to compute UTC time.
|
||||
3. **Chunk boundary rule**
|
||||
- Compute `chunk_index = floor((t_anchor) / D)` where `t_anchor` is UTC time when available, otherwise PCR time.
|
||||
- `D` is fixed chunk duration (e.g., 2000 ms).
|
||||
4. **Alignment policy**
|
||||
- On ingest, drop data until the next full boundary.
|
||||
- Each chunk contains frames/packets whose PTS fall within `[chunk_start, chunk_end)`.
|
||||
5. **Discontinuity handling**
|
||||
- If PCR/PTS discontinuity is detected, close the current group and start a new one at the next boundary.
|
||||
6. **Sync status**
|
||||
- Streams with UTC anchor are marked `synced` and eligible for swarm convergence.
|
||||
- Streams without UTC anchor are `unsynced` and remain source-scoped.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Identical broadcasts on different antennas converge to the same chunk boundaries and can produce byte-identical object streams.
|
||||
- Early ingestion can begin unsynced and promote to synced when broadcast time appears.
|
||||
- Chunk IDs become deterministic functions of (UTC or PCR timeline, duration).
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Local wall clock (NTP/PTP): rejected as primary anchor because it is not broadcast-authoritative and varies between hosts.
|
||||
- Capture-start-based chunking: rejected because it prevents convergence without coordination.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Add a time model to the ingest pipeline (PCR + optional UTC anchor).
|
||||
- Require chunkers to align to the computed boundary.
|
||||
- Promote `StreamId` from source scope to broadcast scope only when `synced`.
|
||||
- Supersede this ECP if MoQ introduces a standardized global timeline.
|
||||
33
evolution/proposals/ECP-0011-stream-encryption.md
Normal file
33
evolution/proposals/ECP-0011-stream-encryption.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# ECP-0011: stream encryption keys
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a consistent encryption model so streams can be protected in transit while remaining discoverable by stream id.
|
||||
|
||||
## Decision
|
||||
|
||||
Derive a symmetric stream key deterministically from the stream id, with an optional network secret:
|
||||
|
||||
- `stream_key = BLAKE3-derive("every.channel stream key v1", network_secret || 0x00 || stream_id)`
|
||||
- If `network_secret` is absent, the key is public and provides obfuscation only.
|
||||
- If `network_secret` is present, the stream is private to holders of the secret.
|
||||
|
||||
Encryption will be applied at the object layer (MoQ objects), not at the transport layer. This allows relays to store and forward encrypted objects without visibility.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Streams can be encrypted deterministically without coordination.
|
||||
- Private swarms can be created by sharing a network secret.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Per-session negotiated keys: rejected because it prevents deterministic convergence.
|
||||
- PKI per stream: deferred due to operational complexity.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Add key derivation helper in `ec-crypto`.
|
||||
- Implement object-layer encryption in the MoQ publisher.
|
||||
- Add configuration for network secret.
|
||||
42
evolution/proposals/ECP-0012-moq-object-wire-format.md
Normal file
42
evolution/proposals/ECP-0012-moq-object-wire-format.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# ECP-0012: MoQ object wire format + track mapping
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a concrete, interoperable way to represent every.channel chunk objects over MoQ tracks so that publishers and subscribers can exchange data without relying on file relay conventions.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Works over `moq-lite` tracks with group/frame semantics.
|
||||
- Must carry both metadata (timing, encryption) and chunk bytes.
|
||||
- Should be simple to parse and deterministic.
|
||||
|
||||
## Decision
|
||||
|
||||
- **Broadcast name**: the canonical stream id (e.g. `ec/stream/v1/...`).
|
||||
- **Track name**: fixed `chunks` track for object payloads.
|
||||
- **Group sequence**: use `chunk_index` (u64) as the group sequence.
|
||||
- **Frame payload**: a single frame per group containing:
|
||||
- 4-byte big-endian length prefix for JSON metadata
|
||||
- JSON-encoded `ObjectMeta`
|
||||
- raw chunk bytes (ciphertext or plaintext)
|
||||
|
||||
This yields a deterministic, self-contained payload with minimal framing and easy debugging.
|
||||
|
||||
`ObjectMeta` may include optional integrity helpers:
|
||||
|
||||
- `chunk_hash` (blake3 of plaintext chunk bytes)
|
||||
- `chunk_proof` (Merkle branch proving membership in an epoch manifest)
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Separate meta/data frames: rejected due to ordering ambiguity and more framing.
|
||||
- Binary codec (postcard/bincode): deferred until interop is proven.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Implement encode/decode helpers in `ec-moq`.
|
||||
- Update publisher to emit one frame per chunk group.
|
||||
- Update subscriber to decode into `ObjectPayload`.
|
||||
- If superseded, provide a compatibility adapter in `ec-moq`.
|
||||
30
evolution/proposals/ECP-0013-stream-catalog-gossip.md
Normal file
30
evolution/proposals/ECP-0013-stream-catalog-gossip.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# ECP-0013: stream catalog gossip for discovery
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a decentralized way to discover live streams and their MoQ endpoints without a central registry.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Must work over iroh transports (QUIC, relays).
|
||||
- Should be encrypted at the transport layer and signed by endpoint identities.
|
||||
- Lightweight enough to broadcast frequently.
|
||||
|
||||
## Decision
|
||||
|
||||
- Introduce a `StreamCatalogEntry` structure that pairs a `StreamDescriptor` with MoQ transport metadata and optional encryption parameters.
|
||||
- Publish catalog entries over `iroh-gossip` on a well-known topic derived from `every.channel/catalog/v1`.
|
||||
- `ec-node` can optionally announce entries when publishing a stream.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Centralized HTTP registry: rejected due to resilience and governance risks.
|
||||
- Static peer lists only: rejected due to poor discoverability.
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
- Add catalog types to `ec-core` and gossip helpers to `ec-iroh`.
|
||||
- Add CLI flags to `ec-node` to announce catalog entries.
|
||||
- Defer full catalog sync/merge semantics until initial interoperability is validated.
|
||||
37
evolution/proposals/ECP-0014-in-app-sharing.md
Normal file
37
evolution/proposals/ECP-0014-in-app-sharing.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# ECP-0014: In-app MoQ sharing (relay-first)
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Context
|
||||
We need a low-friction way for a viewer node to share a live stream with other nodes. Today the CLI can publish MoQ streams, but the Tauri app cannot initiate a publish session or surface share details. Early adoption needs a quick path to “click Share, send details.”
|
||||
|
||||
We also need a near-term relay path that works across NATs without extra configuration. iroh provides default public relays; we can use those until we add custom relay selection.
|
||||
|
||||
## Decision
|
||||
Add an in-app MoQ publish path for the currently selected channel. When a user clicks **Share**, the app starts a MoQ publisher and returns a share bundle (endpoint addr, broadcast, track). The bundle is shown in the UI for copy/paste and can be used by any MoQ subscriber.
|
||||
|
||||
For now, the publish flow relies on iroh’s default relay configuration (relay-first). A later ECP can formalize relay selection and custom relay registries.
|
||||
|
||||
## Details
|
||||
- New `start_moq_publish` Tauri command that:
|
||||
- Opens the selected stream source.
|
||||
- Chunks with the existing ffmpeg pipeline.
|
||||
- Publishes objects over MoQ with deterministic encryption metadata.
|
||||
- Returns a share bundle: `{ endpoint_addr, broadcast_name, track_name, stream_id }`.
|
||||
- The viewer UI shows a **Share** button in the Viewer panel and surfaces the share bundle.
|
||||
- Manual MoQ connect stays available in the **Add source** menu for now.
|
||||
|
||||
## Consequences
|
||||
- Sharing a stream consumes a tuner when the source is a live HDHomeRun stream.
|
||||
- Publishing is long-lived; the app keeps a MoQ node alive until exit.
|
||||
- The share bundle is ephemeral unless a stable iroh secret is configured.
|
||||
|
||||
## Risks
|
||||
- Relay capacity and policy may change; a future ECP should specify relay configuration and redundancy.
|
||||
- DRM-protected streams may fail to publish or play; UI should surface DRM hints.
|
||||
|
||||
## Follow-ups
|
||||
- Add stable identity and share token signing.
|
||||
- Add catalog gossip announcements for published streams.
|
||||
- Provide a web gateway (MoQ -> HLS/MSE) for browsers without MoQ support.
|
||||
23
evolution/proposals/ECP-0015-gossip-announce-toggle.md
Normal file
23
evolution/proposals/ECP-0015-gossip-announce-toggle.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# ECP-0015: Gossip announce toggle and bootstrap peers
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Context
|
||||
Sharing a stream should optionally announce it into the global catalog so other nodes can discover it. Today gossip requires explicit peers, and there is no UI surface for managing announcements.
|
||||
|
||||
## Decision
|
||||
Add an "Announce on share" toggle and a "Gossip peers" field to the viewer UI. When enabled, the app sends a catalog announcement over iroh-gossip immediately after a share begins. Peers can be supplied in-app or via `EVERY_CHANNEL_GOSSIP_PEERS` (comma-separated).
|
||||
|
||||
## Details
|
||||
- `start_moq_publish` accepts `announce` and `gossip_peers`.
|
||||
- If `announce` is true and no peers are provided, we fall back to `EVERY_CHANNEL_GOSSIP_PEERS`.
|
||||
- The share card surfaces announce status (announced / error).
|
||||
|
||||
## Consequences
|
||||
- Announcements remain opt-in and require at least one peer.
|
||||
- Users can keep their share local by leaving announce off.
|
||||
|
||||
## Follow-ups
|
||||
- Add local peer discovery via mDNS to remove manual peer entry for LANs.
|
||||
- Add public bootstrap peer lists and relay selection once governance approves.
|
||||
24
evolution/proposals/ECP-0016-mdns-peer-discovery.md
Normal file
24
evolution/proposals/ECP-0016-mdns-peer-discovery.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# ECP-0016: mDNS peer discovery for gossip
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Context
|
||||
Manual gossip peers are a barrier for LAN onboarding. iroh already supports an mDNS-based address lookup, which can discover endpoints on the local network without external infrastructure.
|
||||
|
||||
## Decision
|
||||
Enable iroh's mDNS address lookup and use it as a peer discovery layer for catalog gossip. When gossip starts (or sharing announces), we probe mDNS for a short window and merge any discovered endpoints into the peer list.
|
||||
|
||||
## Details
|
||||
- Enable iroh feature `address-lookup-mdns`.
|
||||
- Advertise `UserData = "every.channel"` for local discovery filtering.
|
||||
- When gossip is started, probe for ~2 seconds and merge discovered endpoints with configured peers.
|
||||
- mDNS discovery is best-effort; failures should not block manual peers.
|
||||
|
||||
## Consequences
|
||||
- LAN nodes can connect and discover each other without copying addresses.
|
||||
- Global discovery still requires at least one public peer.
|
||||
|
||||
## Follow-ups
|
||||
- Add a UI indicator when mDNS peers are found.
|
||||
- Evaluate relay-less LAN mode and mDNS-only sessions for local-first setups.
|
||||
25
evolution/proposals/ECP-0017-dht-mdns-discovery.md
Normal file
25
evolution/proposals/ECP-0017-dht-mdns-discovery.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# ECP-0017: opt-in DHT + mDNS discovery (DNS off by default)
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Context
|
||||
The current iroh endpoint setup uses default DNS-based discovery implicitly. We want discovery to be explicit and privacy-preserving by default, while still supporting decentralized discovery when users opt in. DHT discovery provides global, decentralized rendezvous; mDNS handles local networks without external infrastructure.
|
||||
|
||||
## Decision
|
||||
Switch endpoint construction to `Endpoint::empty_builder` with no discovery providers by default. Add opt-in discovery modes for DHT and mDNS. DNS discovery is disabled unless explicitly added in a future ECP.
|
||||
|
||||
## Details
|
||||
- Add a discovery config that can be set via `EVERY_CHANNEL_IROH_DISCOVERY` (comma-separated: `dht`, `mdns`, `dns`).
|
||||
- Use iroh address lookup providers: `DhtAddressLookup` for `dht`, `MdnsAddressLookup` for `mdns`, and `PkarrPublisher` + `DnsAddressLookup` for `dns` (when explicitly enabled).
|
||||
- Keep existing mDNS peer discovery (address-lookup) for LAN gossip bootstrapping.
|
||||
- If discovery is disabled, peer addresses must include relay/address info (e.g., share bundle JSON).
|
||||
|
||||
## Consequences
|
||||
- DNS-based discovery is no longer automatic; opt-in discovery is required for ID-only dialing.
|
||||
- Privacy improves by default (no implicit DNS publishing).
|
||||
- Operators can enable decentralized DHT discovery when they want global reach.
|
||||
|
||||
## Follow-ups
|
||||
- Consider an explicit `dns` discovery mode if needed.
|
||||
- Add UI toggles for discovery modes (for non-CLI users).
|
||||
20
evolution/proposals/ECP-0018-ui-discovery-toggles.md
Normal file
20
evolution/proposals/ECP-0018-ui-discovery-toggles.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# ECP-0018: discovery mode toggles in Tauri UI
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Context
|
||||
Discovery is now opt-in (DHT/mDNS with DNS disabled by default). Users need an in-app way to enable discovery without setting environment variables.
|
||||
|
||||
## Decision
|
||||
Add a discovery settings section to the Tauri viewer UI with toggles for DHT, mDNS, and DNS (pkarr relay). The selected modes are sent with MoQ connect, share, and catalog discovery commands.
|
||||
|
||||
## Details
|
||||
- UI stores discovery state (DHT/mDNS/DNS) and emits a comma-separated string (e.g. `dht,mdns`).
|
||||
- Manual MoQ connect, catalog discovery, and Share use the selected discovery config.
|
||||
- If no modes are enabled, discovery remains off and share bundles must include full endpoint addresses.
|
||||
|
||||
## Consequences
|
||||
- Users can opt in to decentralized discovery without env vars.
|
||||
- DNS discovery remains explicit and off by default.
|
||||
|
||||
20
evolution/proposals/ECP-0019-hdhr-by-host-ui.md
Normal file
20
evolution/proposals/ECP-0019-hdhr-by-host-ui.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# ECP-0019: add HDHomeRun by host/IP in Tauri UI
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Context
|
||||
Auto-discovery is great, but users sometimes only have a device IP/hostname. We need a direct add flow to attach HDHomeRun devices to the local catalog without relying on UDP discovery.
|
||||
|
||||
## Decision
|
||||
Add a manual HDHomeRun host/IP input in the Tauri “Add source” menu. The backend will resolve `discover.json`, fetch the lineup, and add the streams/sources to the local catalog.
|
||||
|
||||
## Details
|
||||
- New Tauri command `add_hdhr_source` takes a host/IP and hydrates channels.
|
||||
- Manual devices are kept alongside auto-discovered devices; refresh does not delete manual sources.
|
||||
- Manual streams and sources are merged into the local catalog and startable like auto-discovered streams.
|
||||
- Manual host list is normalized (strips scheme/path, trims slashes) and persisted under the app config directory for reuse across restarts.
|
||||
|
||||
## Consequences
|
||||
- Users can onboard a device without UDP discovery.
|
||||
- Manual entries survive restarts but can be managed by editing/removing the saved host list if needed.
|
||||
20
evolution/proposals/ECP-0020-determinism-moq-selftest.md
Normal file
20
evolution/proposals/ECP-0020-determinism-moq-selftest.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# ECP-0020: deterministic encode checks + MoQ self-test CLI
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Context
|
||||
We need a repeatable way to validate deterministic encoding and to verify MoQ publish/subscribe flows from the CLI without the UI.
|
||||
|
||||
## Decision
|
||||
Add CLI tooling for (1) deterministic re-encoding checks using the libx264 single-thread profile, and (2) a local MoQ round-trip self-test that compares object hashes.
|
||||
|
||||
## Details
|
||||
- `ec-cli determinism-test` re-encodes the same input multiple times using `deterministic_h264_profile` and compares segment hashes.
|
||||
- `ec-node moq-selftest` publishes a chunked stream and subscribes to it using a second endpoint, verifying object hashes per chunk index.
|
||||
- The self-test waits for the subscriber to acknowledge each published chunk to avoid MoQ track drops, and waits briefly for endpoint addresses to appear.
|
||||
- Defaults are conservative: single-thread x264, bitexact flags, fixed GOP.
|
||||
|
||||
## Consequences
|
||||
- Determinism regressions are detectable early.
|
||||
- MoQ path can be validated headlessly without the Tauri app.
|
||||
19
evolution/proposals/ECP-0021-relay-mode-toggle.md
Normal file
19
evolution/proposals/ECP-0021-relay-mode-toggle.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# ECP-0021: relay mode environment toggle
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Context
|
||||
Local testing (and some restricted networks) fail when iroh relays are unreachable. We need a simple way to disable relays without changing code, especially for self-tests.
|
||||
|
||||
## Decision
|
||||
Add `EVERY_CHANNEL_IROH_RELAY` to select relay mode when building endpoints. Supported values: `default` (current behavior) and `disabled` (no relay use).
|
||||
|
||||
## Details
|
||||
- The endpoint builder reads `EVERY_CHANNEL_IROH_RELAY`.
|
||||
- Invalid values fail fast with a clear error.
|
||||
|
||||
## Consequences
|
||||
- Local testing can run without relay connectivity.
|
||||
- Default behavior remains unchanged.
|
||||
|
||||
114
evolution/proposals/ECP-0022-swarm-availability-anti-junk.md
Normal file
114
evolution/proposals/ECP-0022-swarm-availability-anti-junk.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# ECP-0022: swarm availability, signed manifests, anti-junk controls
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
We need to scale distribution without turning every node into a dump pipe. The primary threat is denial via junk data: a peer can join and send invalid chunks that waste bandwidth and compute. We also need a model for multi-party signing and divergent encodes without inventing a full blockchain.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a pull-based swarm model centered on signed manifests and content-addressed chunks:
|
||||
|
||||
- Chunks are immutable and referenced by hash.
|
||||
- A manifest commits to a chunk set via a Merkle root and is signed by one or more identities.
|
||||
- Peers only serve chunks that match a manifest they recognize and that a requester explicitly asks for.
|
||||
- Junk is rejected by hash before decode, and peers are rate-limited and penalized for bad data.
|
||||
|
||||
Multi-root signing is handled through **attestations**: multiple signers may publish distinct manifest roots for the same stream/time slice, and clients decide which roots are acceptable based on policy.
|
||||
|
||||
## Details
|
||||
|
||||
### Objects
|
||||
|
||||
- `ChunkId`:
|
||||
- `stream_id`
|
||||
- `epoch_id` (time window / slice)
|
||||
- `chunk_index`
|
||||
- `chunk_hash` (blake3 of raw chunk bytes)
|
||||
- `ManifestBody`:
|
||||
- `stream_id`
|
||||
- `epoch_id`
|
||||
- `chunk_duration_ms`
|
||||
- `total_chunks`
|
||||
- `chunk_start_index`
|
||||
- `encoder_profile_id`
|
||||
- `merkle_root` (over ordered chunk_hash list)
|
||||
- `created_unix_ms`
|
||||
- `metadata` (optional, e.g. channel title, guide hints)
|
||||
- `chunk_hashes` (ordered list for now; proofs can come later)
|
||||
- `Manifest`:
|
||||
- `body`
|
||||
- `manifest_id` = `blake3(body)`
|
||||
- `signatures`: list of `(signer_id, sig_over_manifest_id)`
|
||||
|
||||
### Pull-only data flow
|
||||
|
||||
- A peer first obtains a manifest (via gossip, direct share, or local catalog).
|
||||
- The peer requests specific chunks by `ChunkId`.
|
||||
- Providers return bytes **only** if they can supply exact hash bytes and have quota available.
|
||||
|
||||
### Anti-junk measures
|
||||
|
||||
- Validate `chunk_hash` before decode; invalid bytes are dropped.
|
||||
- Require the requester to supply `manifest_id` + `chunk_index` + expected `chunk_hash`.
|
||||
- Token-bucket quotas per peer:
|
||||
- max bytes/sec
|
||||
- max concurrent chunk responses
|
||||
- max outstanding requests
|
||||
- Penalize peers:
|
||||
- invalid hash → immediate disconnect + temporary ban
|
||||
- repeated invalid requests → throttle or blacklist
|
||||
|
||||
### Multi-root signing (not blockchain)
|
||||
|
||||
Multiple manifests may exist for the same `stream_id + epoch_id`. We treat them as **parallel attestations**, not consensus. Clients choose a policy, e.g.:
|
||||
|
||||
- accept a manifest if signed by `>=1` trusted key
|
||||
- or `>=N` signatures from a trust set
|
||||
- or “self + any one other”
|
||||
|
||||
If two manifests conflict (different `merkle_root`), clients may:
|
||||
|
||||
- select the one with the most trusted signatures
|
||||
- fall back to local preference
|
||||
- display “split availability” without forcing global consensus
|
||||
|
||||
### Relay behavior
|
||||
|
||||
Relays cache hot chunks by `ChunkId` and serve on request only. They do not push data to peers. Relays enforce the same quota and validation rules as peers.
|
||||
|
||||
### MoQ transport notes (initial)
|
||||
|
||||
- Track `chunks` carries object payloads as in ECP-0012.
|
||||
- Track `manifests` carries JSON-encoded `Manifest` entries, one per group.
|
||||
- For live streams we start with **per-chunk manifests** (epoch size = 1) so every chunk can be validated immediately. Multi-chunk epochs and Merkle proofs can replace this later.
|
||||
|
||||
### Chunk membership proofs (incremental)
|
||||
|
||||
To avoid sending full `chunk_hashes` forever, object metadata may include a Merkle branch proving that `chunk_hash` is included in the manifest's `merkle_root` for the epoch.
|
||||
|
||||
- `ObjectMeta.chunk_proof`: list of sibling hashes from leaf to root.
|
||||
- Verification uses the chunk offset within the epoch: `offset = chunk_index - chunk_start_index`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Push-only streams: rejected (amplifies junk and DDoS risk).
|
||||
- Global consensus / blockchain for manifests: rejected as overkill for now.
|
||||
- Accepting arbitrary chunks without hashes: rejected (no integrity).
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
1. Define `ManifestBody`, `Manifest`, and `ChunkId` in `ec-core`.
|
||||
2. Implement signing + verification in `ec-crypto` (age/ssh).
|
||||
3. Add manifest publication and request protocol in `ec-moq`.
|
||||
4. Enforce per-peer quotas and ban lists in `ec-iroh` transport layer.
|
||||
5. Update catalog gossip to advertise manifests, not raw chunks.
|
||||
|
||||
Teardown: revert to direct MoQ stream subscriptions (current behavior) if manifest flow blocks progress.
|
||||
|
||||
## Open questions
|
||||
|
||||
- What default trust policy should the UI use for manifests?
|
||||
- Should relays require proof-of-work for public manifest announcements?
|
||||
- Do we embed encoder profile parameters directly in `encoder_profile_id` or reference a registry?
|
||||
54
evolution/proposals/ECP-0023-hls-ingest.md
Normal file
54
evolution/proposals/ECP-0023-hls-ingest.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# ECP-0023: HLS ingest pipeline
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
Some broadcasters and community relays expose HLS playlists directly. If two peers ingest the same HLS origin and the bytes are identical, we can dedupe and swarm immediately. We need a first-class ingest path for HLS that plugs into the deterministic chunking pipeline.
|
||||
|
||||
## Decision
|
||||
|
||||
Add HLS as a supported ingest source. The ingest path:
|
||||
|
||||
- Uses ffmpeg/libav demuxing (no custom parsers).
|
||||
- Normalizes input into our deterministic chunker.
|
||||
- Treats identical origin bytes as a fast path for dedupe; if bytes vary, re-encode deterministically.
|
||||
|
||||
## Details
|
||||
|
||||
### Ingest modes
|
||||
|
||||
- **Pass-through**: If the HLS origin serves MPEG-TS segments that are already deterministic and stable, chunk the byte stream as-is.
|
||||
- **Normalize**: If the origin uses fMP4 or inconsistent segmenting, remux via ffmpeg into a stable TS stream before chunking.
|
||||
- **Deterministic re-encode**: If byte identity isn’t stable (e.g. ad insertion, timestamps), re-encode with the deterministic profile.
|
||||
|
||||
Initial implementation note: use ffmpeg (CLI) to output a continuous MPEG-TS stream which is then chunked by the ac-ffmpeg path. This keeps parsing inside ffmpeg and avoids custom HLS logic.
|
||||
|
||||
### Encryption
|
||||
|
||||
- If the HLS stream is encrypted with clear AES-128 and a key is provided, the ingest pipeline can decrypt and then re-encrypt using our stream encryption (stream id + chunk index + optional network secret).
|
||||
- DRM (Widevine/PlayReady) is out of scope for now; ingest will skip or mark as protected.
|
||||
- Our chunk encryption remains independent of HLS origin encryption.
|
||||
|
||||
### Identifiers
|
||||
|
||||
- HLS sources map to a `StreamKey` with `source.kind = "hls"` and `source.channel = <url-or-name>`.
|
||||
- `StreamId` is derived as usual from `StreamKey`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Directly serving HLS playlists to peers: rejected (pushes HLS semantics to every client).
|
||||
- Custom HLS parser: rejected (ffmpeg already solves it).
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
1. Add `HlsSource` to `ec-node` ingest CLI and `ec-core` source typing.
|
||||
2. Add ffmpeg/libav HLS ingest path to `ec-chopper`.
|
||||
3. Wire HLS ingest into MoQ publisher and manifest pipeline.
|
||||
|
||||
Teardown: disable HLS ingest and fall back to HDHR/IPTV sources.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Which HLS modes should default to remux vs re-encode?
|
||||
- Should HLS playlist metadata (program info) be carried into stream metadata?
|
||||
55
evolution/proposals/ECP-0024-zktls-manifest-provenance.md
Normal file
55
evolution/proposals/ECP-0024-zktls-manifest-provenance.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# ECP-0024: zkTLS provenance proofs for HTTPS-derived manifests
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
We want optional provenance for manifests derived from HTTPS origins (e.g. HLS), without relying on the origin to sign our data. zkTLS can prove that a manifest was derived from a TLS session to a given origin, while keeping the session private.
|
||||
|
||||
## Decision
|
||||
|
||||
Add an optional zkTLS attestation per epoch:
|
||||
|
||||
- Produce a zkTLS proof that the manifest’s chunk hashes (or their Merkle root) were derived from bytes fetched over HTTPS from a specific origin.
|
||||
- Attach the proof metadata to the manifest as provenance, without replacing our own signatures.
|
||||
|
||||
## Details
|
||||
|
||||
### Provenance model
|
||||
|
||||
- zkTLS is used **once per epoch** and applies to a specific manifest body.
|
||||
- The proof binds:
|
||||
- HTTPS origin hostname
|
||||
- time window / epoch id
|
||||
- manifest_id (or merkle_root)
|
||||
- The manifest remains signed by local identities; zkTLS does **not** replace signing.
|
||||
|
||||
### Data placement
|
||||
|
||||
- Manifest `metadata` includes a provenance entry, e.g.:
|
||||
- `provenance.zktls.origin = https://example.com`
|
||||
- `provenance.zktls.proof_ref = <hash-or-uri>`
|
||||
- `provenance.zktls.scope = merkle_root|manifest_id`
|
||||
|
||||
### Verification
|
||||
|
||||
- Clients may ignore zkTLS (default) or require it for certain origins.
|
||||
- Verification is a policy decision, not a consensus rule.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- TLS handshake signatures for data integrity: rejected (TLS does not sign application bytes).
|
||||
- Forcing all origins to sign manifests: rejected (not feasible for public broadcasters).
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
1. Define provenance metadata fields in `ec-core`.
|
||||
2. Add a proof attachment path in the ingest pipeline.
|
||||
3. Integrate a zkTLS provider behind a feature flag.
|
||||
|
||||
Teardown: omit provenance metadata and continue using signed manifests.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Which zkTLS system to integrate first (TLSNotary-style, zkTLS SDK, or custom)?
|
||||
- Should provenance be per-epoch only, or per-manifest revision?
|
||||
45
evolution/proposals/ECP-0025-ytdlp-adapter-bundling.md
Normal file
45
evolution/proposals/ECP-0025-ytdlp-adapter-bundling.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# ECP-0025: yt-dlp adapter + bundled runtime
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
We want to paste a YouTube Live (or other supported site) URL into the app and ingest it as a stream. This requires resolving the URL to an HLS playlist or direct stream URL. We also want the app to be self-contained (no external Python install).
|
||||
|
||||
## Decision
|
||||
|
||||
Ship a bundled yt-dlp runtime inside the Tauri app and expose a Tauri command that resolves a URL to a stream URL via yt-dlp. The resolved URL is then treated like any other ingest source and sent through our ffmpeg-based pipeline.
|
||||
|
||||
## Details
|
||||
|
||||
### Bundling
|
||||
|
||||
- Bundle a Python runtime + `yt-dlp` inside the app resources directory.
|
||||
- Prefer the bundled runtime at runtime; fallback to a system `yt-dlp` only if explicitly configured.
|
||||
- Use a `scripts/vendor-yt-dlp.sh` helper to build the bundled environment per platform.
|
||||
|
||||
### Tauri integration
|
||||
|
||||
- Add a `add_ytdlp_source` command that:
|
||||
- Executes `python -m yt_dlp -J --no-playlist <url>`.
|
||||
- Selects a suitable HLS URL from the JSON.
|
||||
- Creates a `StreamDescriptor` and `StreamSource`.
|
||||
- The UI provides a URL input in the “Add source” menu.
|
||||
|
||||
### Security / policy
|
||||
|
||||
- The app only resolves URLs; it does not automate login or cookie handling.
|
||||
- The resolved URL is treated as untrusted; playback uses existing safeguards.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- External yt-dlp dependency: rejected (not self-contained).
|
||||
- Browser-based extraction: rejected (too brittle).
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
1. Add bundling layout and helper script.
|
||||
2. Add backend command and UI input.
|
||||
3. Add docs for building the bundled runtime.
|
||||
|
||||
Teardown: remove the command and UI input, keep HLS ingest via direct URLs.
|
||||
28
evolution/proposals/ECP-0026-agplv3-license.md
Normal file
28
evolution/proposals/ECP-0026-agplv3-license.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# ECP-0026: adopt AGPLv3 licensing
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
We want copyleft protections that apply to network use, ensuring improvements to the networked code path remain open.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt the GNU Affero General Public License v3.0 (AGPLv3) for the repository.
|
||||
|
||||
## Details
|
||||
|
||||
- Update workspace license metadata to `AGPL-3.0-only`.
|
||||
- Add a top-level `LICENSE` file with the full AGPLv3 text.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- MIT/Apache-2.0: rejected (insufficient copyleft for network deployment).
|
||||
- GPLv3: rejected (does not cover SaaS/network use).
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
1. Update license metadata + add LICENSE.
|
||||
2. Communicate licensing change in README or release notes.
|
||||
|
||||
Teardown: supersede with a future ECP if the license changes again.
|
||||
60
evolution/proposals/ECP-0027-unified-add-stream-flow.md
Normal file
60
evolution/proposals/ECP-0027-unified-add-stream-flow.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# ECP-0027: unified add-stream flow (live-only, ytdlp options, persistence)
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
We currently have separate add flows for HDHomeRun and yt-dlp. Users want a single “Add stream” entry point that accepts an IP/host or URL and handles detection automatically. The flow must reject non-live content, provide yt-dlp options when relevant, and persist manual entries across restarts.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a unified add-stream flow with:
|
||||
|
||||
- A single URL/host input in the UI.
|
||||
- A probe step that detects HDHomeRun, HLS, or yt-dlp sources.
|
||||
- Live-only enforcement for HLS and yt-dlp.
|
||||
- Optional yt-dlp format selection + live-from-start toggle before starting.
|
||||
- Unified persistence for all manual sources.
|
||||
- Stream list tags showing the source kind.
|
||||
|
||||
## Details
|
||||
|
||||
### Probe + add flow
|
||||
|
||||
- New commands:
|
||||
- `probe_stream` returns detection results and yt-dlp options (if needed).
|
||||
- `add_stream` executes the resolved ingest path.
|
||||
- HDHomeRun detection is attempted for local/`.local` hosts or private IPs.
|
||||
- HLS detection is triggered for `.m3u8` URLs.
|
||||
- yt-dlp is used for remaining HTTP(S) URLs.
|
||||
|
||||
### Live-only enforcement
|
||||
|
||||
- HLS playlists with `#EXT-X-ENDLIST` or `#EXT-X-PLAYLIST-TYPE:VOD` are rejected.
|
||||
- yt-dlp sources require `is_live == true` or `live_status == "is_live"`.
|
||||
|
||||
### Persistence
|
||||
|
||||
- Replace `manual_hdhomerun.json` with `manual_sources.json`.
|
||||
- Each entry stores `kind`, `input`, and optional options (e.g. ytdlp format).
|
||||
- Legacy HDHomeRun hosts are migrated on load.
|
||||
|
||||
### UI
|
||||
|
||||
- One “Add stream” button + input.
|
||||
- If yt-dlp is detected, show format choices and “live from start” toggle before proceeding.
|
||||
- Channel list shows a source badge (e.g. `hdhr`, `ytdlp`, `hls`, `moq`).
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Keep separate add buttons: rejected (clutters flow).
|
||||
- Allow VOD: rejected (violates live-only requirement).
|
||||
|
||||
## Rollout / teardown plan
|
||||
|
||||
1. Add unified probe/add commands + detection logic in the backend.
|
||||
2. Update UI flow + add yt-dlp options panel.
|
||||
3. Persist manual sources and migrate legacy entries.
|
||||
4. Add source tags to stream list.
|
||||
|
||||
Teardown: keep legacy add commands if needed; disable unified flow in UI.
|
||||
86
evolution/proposals/ECP-0028-testing-and-coverage.md
Normal file
86
evolution/proposals/ECP-0028-testing-and-coverage.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# ECP-0028: test strategy + coverage gates
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem / context
|
||||
|
||||
every.channel is security and integrity sensitive. Before we scale ingestion and relaying, we need a test suite that makes protocol regressions obvious and makes junk injection difficult to accidentally enable.
|
||||
|
||||
We also want coverage as a forcing function to keep logic in testable libraries, not buried in binaries or UI glue.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a layered test strategy and a coverage gate:
|
||||
|
||||
- **Unit tests** for pure logic (hashing, manifests, proofs, encryption, quotas, URL parsing).
|
||||
- **Integration tests** for cross-crate invariants (manifest <-> object meta compatibility, Merkle membership validation, catalog entry semantics).
|
||||
- **End to end tests** for “single node publish + subscribe” flows, with deterministic fixtures and explicit opt-in for tests that require external dependencies (ffmpeg headers, OS devices).
|
||||
|
||||
Coverage goals are defined per layer:
|
||||
|
||||
- `ec-core`, `ec-crypto`, `ec-moq`, `ec-iroh`, `ec-linux-iptv`: target **100% line coverage** (excluding third_party).
|
||||
- Node runner + Tauri backend: target **high coverage** for shared logic modules; binary-only glue may be excluded, but the glue must be minimal by policy.
|
||||
- UI: target **behavioral tests for state transitions** (not full DOM snapshots).
|
||||
|
||||
## Tooling
|
||||
|
||||
- Use `cargo llvm-cov` for coverage measurement.
|
||||
- Coverage is run per-crate and per-workspace in the nix dev shell so `ac-ffmpeg` can find ffmpeg headers.
|
||||
|
||||
## Test matrix
|
||||
|
||||
Unit tests (must be deterministic)
|
||||
|
||||
- `ec-core`
|
||||
- Manifest ID determinism and change sensitivity.
|
||||
- Merkle root correctness.
|
||||
- Merkle proof generation + verification (including tamper detection).
|
||||
- `ec-crypto`
|
||||
- Stream key/nonce derivation determinism.
|
||||
- Encrypt/decrypt roundtrip and mismatch failures.
|
||||
- Manifest signature sign/verify and allowlist behavior.
|
||||
- `ec-moq`
|
||||
- Object frame encode/decode roundtrip.
|
||||
- Manifest frame encode/decode roundtrip.
|
||||
- File relay sanitization stability.
|
||||
- `ec-iroh`
|
||||
- Token bucket throttling/refill behavior (no sleeps).
|
||||
- `ec-linux-iptv`
|
||||
- `channels.conf` parsing (unique/sorted, ignore comments).
|
||||
- Default tune command construction.
|
||||
|
||||
Integration tests (cross-crate invariants)
|
||||
|
||||
- Manifest root validates object `chunk_hash` either by direct hash list or by Merkle proof.
|
||||
- Encrypted objects preserve integrity checks (hash is over plaintext).
|
||||
- Catalog entries carry manifest summaries consistently.
|
||||
|
||||
End to end tests (opt-in / platform dependent)
|
||||
|
||||
- `moq` publish/subscribe loopback tests with epoch manifests:
|
||||
- publish N chunks, subscribe and verify acceptance/rejection paths.
|
||||
- Linux DVB discovery tests:
|
||||
- run only when `/dev/dvb` exists; otherwise skip.
|
||||
|
||||
## Policy implications
|
||||
|
||||
- New protocol logic should land in libraries, not binaries.
|
||||
- Any feature that changes integrity behavior (hashing, proofs, signing, validation, quotas) must add tests proving:
|
||||
- positive path (accept)
|
||||
- negative path (reject)
|
||||
- no panics on malformed inputs
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- “Only E2E tests”: rejected (slow and flaky).
|
||||
- “Only unit tests”: rejected (cross-crate breakages are likely).
|
||||
|
||||
## Rollout / teardown
|
||||
|
||||
1. Add unit tests to core crates until 100% coverage is achievable.
|
||||
2. Refactor binary logic into testable modules when coverage shows dead zones.
|
||||
3. Add integration tests for manifest and object invariants.
|
||||
4. Add opt-in E2E tests for ffmpeg and device-bound pipelines.
|
||||
|
||||
Teardown: if llvm-cov becomes too costly locally, keep tests and make coverage gates advisory, but retain per-crate coverage reports.
|
||||
|
||||
33
evolution/proposals/ECP-0029-linux-dvb-auto-discovery.md
Normal file
33
evolution/proposals/ECP-0029-linux-dvb-auto-discovery.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# ECP-0029: Linux DVB Auto-Discovery (Adapters and Channels)
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
Linux DVB tuners should participate with minimal manual configuration. Requiring users to type `linux-dvb` and then fill out adapter, DVR, and channel options adds friction and hides available tuners from the "what can I watch right now" view.
|
||||
|
||||
## Decision
|
||||
|
||||
On Linux only:
|
||||
|
||||
- Auto-discover local DVB adapters by enumerating `/dev/dvb/adapter*`.
|
||||
- If a `channels.conf` is available, auto-expose each channel as a selectable stream in the main Channels list without requiring Add Stream input.
|
||||
- Support an explicit `channels.conf` override via `EVERY_CHANNEL_DVB_CHANNELS_CONF` to keep discovery deterministic and testable.
|
||||
|
||||
No scanning is introduced here. Auto-discovered channels come only from an existing `channels.conf`.
|
||||
|
||||
## Details
|
||||
|
||||
- Stream IDs use `StreamKey { source: linux-dvb, device_id: adapterX:dvrY, channel: <name> }` and remain a source-scope fallback until broadcast-scope IDs exist.
|
||||
- Tuning is performed via a `dvbv5-zap` command embedded into the `linux-dvb://` URL (same mechanism as the Linux DVB picker).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Linux nodes show their tuner channels "by default" when they already have a `channels.conf`, improving discoverability.
|
||||
- Non-Linux builds remain unchanged (no `/dev/dvb`, no auto-listed Linux DVB streams).
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Full frequency scan (`dvbv5-scan`) on first run: rejected for now because it is slow, region-dependent, and hard to test deterministically.
|
||||
- ioctl-based tuning: deferred until we can validate behavior across real hardware.
|
||||
|
||||
36
evolution/proposals/ECP-0030-workspace-coverage-reporting.md
Normal file
36
evolution/proposals/ECP-0030-workspace-coverage-reporting.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# ECP-0030: Workspace Coverage Reporting
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We want high-confidence changes across the entire codebase, not only the newest crates. Today, we have unit tests in a subset of crates and no standardized, reproducible, formal coverage artifact.
|
||||
|
||||
## Decision
|
||||
|
||||
- Adopt workspace-level coverage as a first-class artifact using `cargo-llvm-cov`.
|
||||
- Provide repeatable `just` commands that generate:
|
||||
- LCOV (`tmp/coverage/workspace.lcov`)
|
||||
- HTML report (`tmp/coverage/workspace-html/index.html`)
|
||||
- A short summary (`tmp/coverage/workspace.summary.txt`)
|
||||
- Prefer running coverage via `nix develop` so `ac-ffmpeg` builds consistently (FFmpeg headers/libs present).
|
||||
|
||||
## Details
|
||||
|
||||
Commands:
|
||||
|
||||
- `just test-workspace`
|
||||
- `just cov-workspace` / `just cov-workspace-html` (when `cargo llvm-cov` is available, typically inside `nix develop`)
|
||||
- `just cov-workspace-nix` / `just cov-workspace-html-nix` (self-contained, runs through `nix develop`)
|
||||
- `cargo llvm-cov -p ec-core -p ec-crypto -p ec-hdhomerun -p ec-iroh -p ec-linux-iptv -p ec-moq -p ec-ts` (unit-coverage subset that doesn't require FFmpeg headers)
|
||||
|
||||
Coverage excludes:
|
||||
|
||||
- `third_party/`
|
||||
- `target/`
|
||||
- `tmp/`
|
||||
|
||||
## Consequences
|
||||
|
||||
- Contributors can review regressions with an objective report, not only "tests passed".
|
||||
- Running coverage outside Nix may not work on macOS/Linux unless FFmpeg dev headers are installed; the Nix path is the canonical one.
|
||||
39
evolution/proposals/ECP-0031-hdhr-e2e-test.md
Normal file
39
evolution/proposals/ECP-0031-hdhr-e2e-test.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# ECP-0031: HDHomeRun End-to-End Test (Single Host, Two Nodes)
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a real end-to-end test that exercises the full ingestion and transport path against a real HDHomeRun on the LAN. Unit tests cover a lot of logic, but they cannot validate:
|
||||
|
||||
- live TS ingest from the tuner
|
||||
- chunking on real packets
|
||||
- manifest creation/signing
|
||||
- stream encryption/decryption
|
||||
- MoQ publish/subscribe over QUIC between two nodes
|
||||
- subscriber verification and HLS write-out
|
||||
|
||||
## Decision
|
||||
|
||||
Add an ignored integration test that:
|
||||
|
||||
- spawns `ec-node moq-publish hdhr ...` against a user-provided HDHomeRun host and channel
|
||||
- reads the publisher's printed endpoint addr, broadcast name, and track name
|
||||
- spawns `ec-node moq-subscribe ... --subscribe-manifests --require-manifest --network-secret ...`
|
||||
- asserts that the subscriber writes `index.m3u8` and at least one `segment_*` file
|
||||
|
||||
The test is opt-in and runs only when environment variables are present:
|
||||
|
||||
- `EVERY_CHANNEL_E2E_HDHR_HOST`
|
||||
- `EVERY_CHANNEL_E2E_HDHR_CHANNEL`
|
||||
|
||||
## Details
|
||||
|
||||
- Publisher uses `--publish-manifests --epoch-chunks 1 --network-secret <hex>`.
|
||||
- Subscriber uses `--require-manifest --max-invalid-chunks 0` to enforce verification.
|
||||
- A new subscriber flag `--stop-after N` allows deterministic termination.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Local developers can validate true end-to-end behavior before sharing builds.
|
||||
- CI remains unaffected unless explicitly enabled.
|
||||
56
evolution/proposals/ECP-0032-split-source-moq-mesh.md
Normal file
56
evolution/proposals/ECP-0032-split-source-moq-mesh.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# ECP-0032: Split-Source MoQ Mesh (Manifests vs Objects)
|
||||
|
||||
## Goal
|
||||
|
||||
Prove that an every.channel stream can be served by multiple uncoordinated nodes without turning the system into a single dump-pipe.
|
||||
|
||||
Minimum useful split:
|
||||
- One node acts as a leader/signer: publishes signed manifests (and optionally nothing else).
|
||||
- One or more nodes act as availability relays: publish chunk objects only.
|
||||
- A subscriber can fetch manifests from one peer and objects from another, enforcing manifest verification before accepting data.
|
||||
|
||||
This is a stepping stone to broader swarming and anti-junk mechanics (ECP-0022).
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Full multi-source reconciliation between two independently captured live antennas.
|
||||
- DHT routing, relay selection, or peer scoring.
|
||||
- Byzantine consensus on manifests.
|
||||
|
||||
## Design
|
||||
|
||||
### CLI surface
|
||||
|
||||
`ec-node moq-publish` gains:
|
||||
- `--publish-chunks=[true|false]` (default `true`): allow “manifests-only” publishers.
|
||||
- `--startup-delay-ms <ms>` (optional): test helper to delay ingest until subscribers connect.
|
||||
|
||||
`ec-node moq-subscribe` gains:
|
||||
- `--remote-manifests <EndpointAddr>` (optional): fetch manifests from a different peer than objects.
|
||||
|
||||
### Semantics
|
||||
|
||||
- The subscriber enforces `--require-manifest` as before, but manifest lookup comes from `--remote-manifests` when provided.
|
||||
- Manifests remain the authority for object acceptance (hash match and/or Merkle proof).
|
||||
- Objects remain encrypted per stream id (no change).
|
||||
|
||||
### E2E Test
|
||||
|
||||
Add an ignored integration test that:
|
||||
1. Captures a short TS recording from a real HDHomeRun (duration query).
|
||||
2. Starts two publishers over the same broadcast:
|
||||
- publisher A: `--publish-manifests --publish-chunks=false` (leader/signer)
|
||||
- publisher B: `--publish-chunks=true` (objects only)
|
||||
3. Starts one subscriber using:
|
||||
- `--remote` pointing at publisher B
|
||||
- `--remote-manifests` pointing at publisher A
|
||||
- `--subscribe-manifests --require-manifest`
|
||||
4. Asserts that HLS output is produced and the process exits via `--stop-after`.
|
||||
|
||||
This verifies split-source compatibility and the “no manifests, no data” invariant in a multi-peer setting.
|
||||
|
||||
## Rollout
|
||||
|
||||
- Land CLI flags and the ignored E2E test.
|
||||
- Document running the E2E in `docs/USAGE.md`.
|
||||
- Future: extend to true multi-source live capture by aligning epochs and negotiating canonical chunk boundaries.
|
||||
65
evolution/proposals/ECP-0033-x264-cmaf-fmp4.md
Normal file
65
evolution/proposals/ECP-0033-x264-cmaf-fmp4.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# ECP-0033: Deterministic x264 + CMAF(fMP4) Segment Mode
|
||||
|
||||
## Summary
|
||||
|
||||
Add an optional stream mode that publishes HLS-compatible CMAF-style fragmented MP4 segments (`.m4s`) encoded with single-threaded `libx264` + AAC, alongside a separate init segment (`init.mp4`). The init segment is published on a dedicated MoQ side-track so subscribers can start playback without out-of-band files.
|
||||
|
||||
This is an incremental step toward a MoQ-native media pipeline while keeping browser playback practical via standard HLS fMP4 semantics.
|
||||
|
||||
## Goals
|
||||
|
||||
- Provide a "mature codec path" for experimentation: `libx264` + AAC.
|
||||
- Make output more browser-friendly than raw MPEG-TS while staying simple.
|
||||
- Preserve deterministic encoding constraints where feasible:
|
||||
- single-threaded encoder
|
||||
- `+bitexact` flags
|
||||
- fixed GOP
|
||||
- Keep the change reversible:
|
||||
- default remains raw TS chunk publication
|
||||
- CMAF mode is opt-in via CLI
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Full CMAF compliance validation across players.
|
||||
- Multi-track audio language selection.
|
||||
- End-to-end determinism across independent antennas (this requires timebase normalization and additional constraints).
|
||||
- MoQ wire-level media standardization (that belongs in a later ECP).
|
||||
|
||||
## Design
|
||||
|
||||
- Publisher (`ec-node moq-publish`) adds `--encode {ts|cmaf}`.
|
||||
- In `cmaf` mode:
|
||||
- ingest bytes from the source (TS file/HDHR/iptv adapter)
|
||||
- invoke `ffmpeg` to encode and segment to HLS fMP4:
|
||||
- writes `init.mp4` and `segment_%06d.m4s`
|
||||
- publish:
|
||||
- `init.mp4` as a single MoQ object on `--init-track` (default: `init`)
|
||||
- `.m4s` segments as MoQ objects on the main track
|
||||
- all MoQ tracks (objects, init, manifests) are created before the broadcast is published
|
||||
to avoid implementation quirks where late-added tracks are not deliverable
|
||||
- manifests remain per-epoch over the segment objects only (init is out-of-manifest for now)
|
||||
- Subscriber (`ec-node moq-subscribe`) adds:
|
||||
- `--container {ts|cmaf}`
|
||||
- `--subscribe-init` to fetch `init.mp4` from `--init-track`
|
||||
- local output becomes:
|
||||
- `index.m3u8` with `#EXT-X-MAP:URI="init.mp4"`
|
||||
- `init.mp4`
|
||||
- rolling window of `segment_*.m4s`
|
||||
|
||||
## Rationale
|
||||
|
||||
- fMP4 (CMAF-style) is broadly supported by modern HLS implementations and aligns with future MoQ media layouts.
|
||||
- `libx264` is stable and widely available, making it a pragmatic baseline for determinism experiments.
|
||||
- Publishing init as a side-track avoids special cases in the primary segment stream and keeps the UI/receiver model "a stream is a stream".
|
||||
|
||||
## Risks
|
||||
|
||||
- MP4 muxers can embed non-deterministic metadata (timestamps/brands). We mitigate by:
|
||||
- `+bitexact` flags and single-threaded encoding
|
||||
- stripping container metadata where feasible (future follow-up may be needed)
|
||||
- Segment boundaries are time-based and depend on timestamps/keyframes; cross-antenna byte equality is not guaranteed yet.
|
||||
|
||||
## Rollout
|
||||
|
||||
- Land CMAF mode behind `--encode cmaf` and `--container cmaf`.
|
||||
- Add an ignored HDHomeRun E2E test that exercises split-source mesh in CMAF mode.
|
||||
46
evolution/proposals/ECP-0034-raw-cmaf-output.md
Normal file
46
evolution/proposals/ECP-0034-raw-cmaf-output.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# ECP-0034: Raw CMAF Output (CMAF-First, Playlist-Optional)
|
||||
|
||||
Status: Draft
|
||||
Author: Codex
|
||||
Reviewers: founder@every.channel
|
||||
Created: 2026-02-07
|
||||
|
||||
## Decision
|
||||
|
||||
Treat CMAF artifacts (init segment + fMP4 media fragments) as the canonical transport payload for low-latency video in every.channel.
|
||||
|
||||
Add a subscriber option to write *raw CMAF* files to disk without generating an HLS playlist:
|
||||
|
||||
- `ec-node moq-subscribe --container cmaf --subscribe-init --raw-cmaf`
|
||||
- writes `init.mp4`
|
||||
- writes `segment_%06d.m4s`
|
||||
|
||||
HLS (and any other playlist formats) become optional *views* over those CMAF bytes, generated by a separate component when needed.
|
||||
|
||||
## Motivation
|
||||
|
||||
- CMAF is the common denominator for modern streaming and is the right level of abstraction for MoQ object transport.
|
||||
- Keeping the transport canonical at "init + fragments" avoids hard coupling the core to HLS/DASH playlist semantics.
|
||||
- Determinism experiments should focus on fragment bytes, not playlist formatting.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
- Raw CMAF sink for `ec-node moq-subscribe`.
|
||||
- Keep the existing HLS writer for local playback workflows.
|
||||
|
||||
Out of scope:
|
||||
- MPEG-DASH support (explicitly not pursued here).
|
||||
- Browser-native playback pipeline (still expected to use a view layer like HLS/MSE).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `ec-node` already publishes CMAF init on a dedicated MoQ track (`init`) and publishes segments on the main chunks track.
|
||||
- `--raw-cmaf` is only valid with `--container cmaf` and errors otherwise.
|
||||
- Naming is stable and deterministic for tests and downstream proxies.
|
||||
|
||||
## Reversibility
|
||||
|
||||
- This change is additive and gated behind a flag.
|
||||
- If we later standardize a manifest-driven playlist format, `--raw-cmaf` can remain as a debug and interoperability mode.
|
||||
|
||||
65
evolution/proposals/ECP-0035-multi-variant-streams.md
Normal file
65
evolution/proposals/ECP-0035-multi-variant-streams.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# ECP-0035: Multi-Variant Streams (Quality Pools)
|
||||
|
||||
Status: Draft
|
||||
Author: Codex
|
||||
Reviewers: founder@every.channel
|
||||
Created: 2026-02-07
|
||||
|
||||
## Decision
|
||||
|
||||
Support multiple quality variants per logical channel/stream (bitrate, resolution, codec profile) by:
|
||||
|
||||
- Encoding/publishing each variant as its own `StreamId` (using `StreamKey.variant`).
|
||||
- Advertising the variant set in the stream catalog so receivers can pick one (or switch).
|
||||
- Keeping the transport canonical at CMAF init + fMP4 fragments; playlists (HLS) are optional views.
|
||||
|
||||
## Motivation
|
||||
|
||||
- Scaling: low-bitrate variants are cheaper to relay and friendlier to mobile.
|
||||
- Interop: many origins already provide ABR ladders (HLS/DASH); we should preserve that structure.
|
||||
- Determinism experiments: variants bound the encoding parameters so byte-equivalence is testable.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
- Catalog representation for variants.
|
||||
- CLI/UI selection of a variant for playback/subscription.
|
||||
- Optional "encode ladder" mode for local sources (HDHR/DVB) using x264 in deterministic settings.
|
||||
|
||||
Out of scope:
|
||||
- Full ABR switching heuristics.
|
||||
- Distributed, consensus-driven variant negotiation.
|
||||
|
||||
## Proposed Data Model
|
||||
|
||||
Add to `ec-core` (names illustrative):
|
||||
|
||||
- `StreamVariant`:
|
||||
- `variant_id` (string, stable, e.g. `v1-1080p2500k`)
|
||||
- `stream_id` (StreamId)
|
||||
- `codec` (e.g. `h264`)
|
||||
- `container` (e.g. `cmaf/fmp4`)
|
||||
- `width/height` (optional)
|
||||
- `bitrate` (optional)
|
||||
- `fps` (optional)
|
||||
- `audio` (optional fields)
|
||||
|
||||
Add to `StreamCatalogEntry`:
|
||||
|
||||
- `variants: Vec<StreamVariant>` (optional; empty implies single-variant legacy)
|
||||
|
||||
## Publishing Strategy
|
||||
|
||||
- For live sources (HDHR/DVB): spawn one encoder per variant (separate ffmpeg processes initially).
|
||||
- For HLS origins with ABR: map each origin rendition to a variant and (initially) republish bytes as-is when possible.
|
||||
|
||||
## Determinism Notes
|
||||
|
||||
- Determinism is only expected within the same `variant_id` and encoder profile.
|
||||
- Single-threaded x264 settings remain the baseline deterministic profile for now.
|
||||
|
||||
## Reversibility
|
||||
|
||||
- This is additive to the catalog and backwards compatible (single-variant streams remain valid).
|
||||
- Variant publishing can be introduced incrementally per source type.
|
||||
|
||||
72
evolution/proposals/ECP-0036-multi-variant-manifests.md
Normal file
72
evolution/proposals/ECP-0036-multi-variant-manifests.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# 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.
|
||||
|
||||
50
evolution/proposals/ECP-0037-cross-os-determinism.md
Normal file
50
evolution/proposals/ECP-0037-cross-os-determinism.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# ECP-0037: Cross-OS Determinism Testing (macOS + Linux)
|
||||
|
||||
Status: Draft
|
||||
Author: Codex
|
||||
Reviewers: founder@every.channel
|
||||
Created: 2026-02-07
|
||||
|
||||
## Decision
|
||||
|
||||
Add an explicit cross-OS determinism test workflow for the CMAF ladder pipeline.
|
||||
|
||||
We will treat the determinism target as:
|
||||
|
||||
- identical output bytes for `init.mp4` and `segment_*.m4s` for the same synthetic input,
|
||||
- given the same ffmpeg/x264 build and the same every.channel version,
|
||||
- across macOS and Linux.
|
||||
|
||||
## Motivation
|
||||
|
||||
- Our core thesis depends on multiple independent nodes producing byte-identical artifacts.
|
||||
- Without automated cross-OS checks, determinism will silently regress.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
- Keep an ignored Rust test that produces a deterministic synthetic TS and verifies:
|
||||
- bit-identical outputs across two runs on one host,
|
||||
- keyframe alignment at segment boundaries.
|
||||
- Add a script that exports a canonical test input and compares outputs from two machines.
|
||||
|
||||
Out of scope:
|
||||
- Perfect determinism across different ffmpeg/x264 versions.
|
||||
- Hardware encoder determinism.
|
||||
|
||||
## Implementation
|
||||
|
||||
- Local test: `crates/ec-node/tests/determinism_cmaf_ladder.rs`
|
||||
- Cross-OS procedure:
|
||||
1. On macOS and Linux, run the same nix flake dev environment (pins ffmpeg).
|
||||
2. Run `cargo test -p ec-node --test determinism_cmaf_ladder -- --ignored --nocapture`.
|
||||
3. Emit a JSON summary of sha256s per variant (init + segments) into `tmp/determinism/<os>.json`.
|
||||
4. Compare the JSON across OS.
|
||||
|
||||
## Notes
|
||||
|
||||
- Even with pinned ffmpeg, Apple vs Linux libc may still perturb behavior.
|
||||
- If cross-OS byte equality fails but in-host determinism holds, we can treat it as:
|
||||
- still useful for availability (single-encoder determinism),
|
||||
- but not sufficient for multi-source "same bytes" dedupe.
|
||||
|
||||
58
evolution/proposals/ECP-0038-share-links.md
Normal file
58
evolution/proposals/ECP-0038-share-links.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# ECP-0038: Share Links (every.channel://watch) and UI Share Controls
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
Sharing currently exposes a raw MoQ bundle (endpoint addr JSON, broadcast, track) which is copy/paste friendly for developers but awkward for regular users. We also need explicit UI controls for:
|
||||
|
||||
- Sharing via a single link.
|
||||
- Sharing to nearby nodes (mDNS).
|
||||
- Sharing publicly (DHT address lookup), while still requiring explicit gossip bootstrap peers.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a compact share link format:
|
||||
|
||||
`every.channel://watch?...`
|
||||
|
||||
and add UI affordances to copy and consume this link.
|
||||
|
||||
The link is a convenience wrapper around existing MoQ parameters. No new trust model is introduced here.
|
||||
|
||||
## Link Format (v1)
|
||||
|
||||
Scheme and path:
|
||||
|
||||
- `every.channel://watch`
|
||||
|
||||
Query parameters:
|
||||
|
||||
- `remote` (required): iroh `EndpointAddr` JSON string (or id-only string when discovery is enabled).
|
||||
- `broadcast` (required): MoQ broadcast name.
|
||||
- `track` (optional): MoQ track name (default remains `chunks` in the app).
|
||||
- `stream_id` (optional): key id override (rare; for debugging).
|
||||
- `secret` (optional): network secret hex (only when the publisher used one).
|
||||
- `discovery` (optional): comma-separated discovery modes, currently `mdns`, `dht`, `dns`.
|
||||
|
||||
## UI (user-facing language)
|
||||
|
||||
- Share card includes:
|
||||
- Copyable share link.
|
||||
- Copyable node id.
|
||||
- Viewer panel exposes:
|
||||
- "Nearby (same Wi-Fi)"
|
||||
- "Public (internet)"
|
||||
- "Show in directory"
|
||||
- optional "directory contacts" and "sharing key"
|
||||
- "Watch a link" accepts a share link and can parse it to fill hidden advanced fields.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Sharing becomes one-string shareable without asking recipients to understand the underlying bundle structure.
|
||||
- Public announcements still require bootstrap peers; DHT only provides address lookup.
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- Add an explicit public bootstrap peer list mechanism (governance-controlled).
|
||||
- Add optional signing of share links (identity binding) once stream signing is enforced.
|
||||
48
evolution/proposals/ECP-0039-web-ia.md
Normal file
48
evolution/proposals/ECP-0039-web-ia.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# ECP-0039: Web Site IA (minimal, warm) and Shared Language
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
We need a friendly public entry point that explains:
|
||||
|
||||
- What every.channel is.
|
||||
- How to watch (link-first).
|
||||
- How to find streams (directory).
|
||||
- How to participate (run a node).
|
||||
|
||||
The current UI is app-shaped. The repo has `apps/web/` but no actual site yet.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a minimal static site in `apps/web/` (Dioxus + Trunk) with a clear information architecture:
|
||||
|
||||
- Watch
|
||||
- Directory
|
||||
- Participate
|
||||
- About
|
||||
|
||||
Use plain language consistent with the app:
|
||||
|
||||
- "Watch a link"
|
||||
- "Nearby (same Wi-Fi)"
|
||||
- "Public (internet)"
|
||||
- "Directory"
|
||||
- "Sharing key"
|
||||
|
||||
## Design Notes
|
||||
|
||||
- Clean and minimal, but warm.
|
||||
- A subtle nod to old TV (very light scanlines / glow).
|
||||
- No protocol terms in user-facing copy.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Provides a coherent landing page and language baseline for the app.
|
||||
- The site is static and does not attempt to play streams in-browser yet.
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- Add an install section once we have stable releases.
|
||||
- Add a real directory browsing UI when the gateway story is ready.
|
||||
|
||||
23
evolution/proposals/ECP-0040-ui-ia-polish.md
Normal file
23
evolution/proposals/ECP-0040-ui-ia-polish.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# ECP-0040: IA and Copy Alignment (Web + Desktop)
|
||||
|
||||
## Why
|
||||
|
||||
every.channel needs a consistent, friendly surface that does not require users to understand networking concepts. The same words should mean the same thing across the web site and the desktop app, and advanced details should stay out of the default path.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Adopt plain-language labels across web + desktop.
|
||||
- Prefer a link-first flow for watching (paste a link; details optional).
|
||||
- Use "Devices" for tuners/adapters and "Now Playing" for the active viewer panel.
|
||||
- Describe DRM/conditional access as "Protected" (a user-facing expectation, not a technical claim).
|
||||
- Keep the design warm/minimal with a subtle "old TV" nod, but avoid kitsch.
|
||||
|
||||
## Scope
|
||||
|
||||
- Copy and layout tightening only (CSS, titles, labels).
|
||||
- No behavior changes to streaming, discovery, or sharing.
|
||||
|
||||
## Reversibility
|
||||
|
||||
Fully reversible: this proposal only changes UI strings and styling.
|
||||
|
||||
22
evolution/proposals/ECP-0041-cmaf-only.md
Normal file
22
evolution/proposals/ECP-0041-cmaf-only.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# ECP-0041: CMAF-Only Chunking (Canonical Container)
|
||||
|
||||
## Why
|
||||
|
||||
We want a single canonical chunk container so relays, caches, signatures, and verification can be consistent across sources. Multiple container codepaths (TS chunks, ad-hoc HLS, etc.) make determinism and validation harder, and complicate interop between ingest modules.
|
||||
|
||||
## Decisions
|
||||
|
||||
- The canonical chunk container is CMAF (fMP4): `init.mp4` plus numbered `segment_*.m4s`.
|
||||
- All ingest pipelines normalize into CMAF segments.
|
||||
- Legacy TS chunking is removed from the default pipeline.
|
||||
- HLS playlists may still be generated as a local playback compatibility artifact, but the network objects and manifests are CMAF segments.
|
||||
- Implementation note: when publishing over a single object track (e.g. local file relay), we reserve chunk index `0` for `init.mp4` and start media segments at index `1` to avoid collisions.
|
||||
|
||||
## Scope
|
||||
|
||||
- `ec-node` and the desktop app converge on CMAF output.
|
||||
- Existing manifest + object hashing/signing remains, but now hashes refer to CMAF bytes.
|
||||
|
||||
## Reversibility
|
||||
|
||||
Medium. Reintroducing TS chunking later is possible behind a feature flag, but the default stays CMAF-only.
|
||||
24
evolution/proposals/ECP-0042-variants-and-auto.md
Normal file
24
evolution/proposals/ECP-0042-variants-and-auto.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# ECP-0042: Multi-Variant Publishing and Automatic Selection
|
||||
|
||||
## Why
|
||||
|
||||
Different networks and devices need different bitrates/resolutions. A single stream quality forces either wasted bandwidth or playback stalls. We also need a consistent representation of variants so multiple peers can publish/verify the same logical channel.
|
||||
|
||||
## Decisions
|
||||
|
||||
- A "channel" may have multiple *variants* (e.g. `1080p`, `720p`, `480p`) produced from the same timing grid.
|
||||
- Manifests include per-variant metadata (resolution/bitrate) and per-variant chunk hashes for the same chunk index.
|
||||
- Viewers implement automatic selection:
|
||||
- Prefer the highest variant that keeps steady playback.
|
||||
- Step down quickly on sustained stalls; step up conservatively.
|
||||
- Where available, the desktop app may present variants via a master playlist so the platform player can do ABR; protocol objects remain per-variant CMAF.
|
||||
- Initial implementation may subscribe to all variants concurrently and let the player switch locally. More efficient "switch by resubscribe" can come later.
|
||||
|
||||
## Non-Goals (for this ECP)
|
||||
|
||||
- Global policy/quotas/anti-junk (separate proposal).
|
||||
- Hardware encode determinism guarantees (separate proposal).
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. Variant publishing and selection policies can evolve without breaking base single-variant streams.
|
||||
31
evolution/proposals/ECP-0043-browser-webrtc-bridge.md
Normal file
31
evolution/proposals/ECP-0043-browser-webrtc-bridge.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# ECP-0043: Browser Participation via WebRTC DataChannel Bridge (Exploration)
|
||||
|
||||
## Why
|
||||
|
||||
Browsers cannot currently join the same QUIC-based transport stack directly (constraints around raw QUIC, UDP, and custom discovery). We still want web viewers and lightweight web relays to participate without central servers.
|
||||
|
||||
## Proposal
|
||||
|
||||
- Add an optional "browser bridge" transport implemented with WebRTC DataChannels.
|
||||
- A native node can act as a bridge:
|
||||
- It speaks the native transport with other peers.
|
||||
- It speaks WebRTC DataChannel with browsers.
|
||||
- It forwards encrypted objects/manifests between the two worlds.
|
||||
- Signaling starts as manual copy/paste (offer/answer) to avoid a required central service.
|
||||
- Later: directory-assisted rendezvous can be layered without changing the data plane.
|
||||
|
||||
## Scope (initial)
|
||||
|
||||
- Watch-only in browser (receive + play CMAF) using DataChannel + MSE.
|
||||
- Native bridge CLI command to generate offers / accept answers.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- How to represent reachability in the directory without exposing protocol terms.
|
||||
- Rate limits and anti-junk enforcement at the bridge boundary.
|
||||
- Best-effort determinism when browsers become publishers (likely not guaranteed).
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. WebRTC bridge is an adapter layer; core protocol stays unchanged.
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# ECP-0044: Cloudflare Workers Static Site Deployment
|
||||
|
||||
## Why
|
||||
|
||||
We want a fast, globally cached, no-backend website at `every.channel` that:
|
||||
- serves the viewer UI (PWA-capable),
|
||||
- can be deployed reproducibly from this repo,
|
||||
- does not require a centralized application server.
|
||||
|
||||
Cloudflare Workers + static Assets gives us:
|
||||
- global edge caching and TLS,
|
||||
- a simple deploy story (`wrangler deploy`),
|
||||
- the option to add small edge logic later (redirects, headers, SPA fallback).
|
||||
|
||||
## Proposal
|
||||
|
||||
- Treat `apps/tauri/ui` as the canonical website source (Dioxus web build).
|
||||
- Treat `apps/tauri/dist` as the deployment artifact.
|
||||
- Add a Cloudflare Worker project under `deploy/cloudflare-worker/` that serves `apps/tauri/dist`.
|
||||
- Implement SPA fallback: if an asset path 404s, serve `/index.html`.
|
||||
- Keep deployment logic in a script:
|
||||
- build: `trunk build --release`
|
||||
- deploy: `wrangler deploy`
|
||||
|
||||
## Scope (initial)
|
||||
|
||||
- One worker that only serves static assets + SPA fallback.
|
||||
- No API, no auth, no server-side state.
|
||||
- Domain mapping to `every.channel` is declared in `wrangler.toml` via Workers Custom Domains.
|
||||
- This requires appropriate Cloudflare account permissions for `wrangler deploy` to attach the domain.
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. The worker is a thin wrapper over static assets. We can migrate to Pages, another CDN, or self-hosted static with minimal changes.
|
||||
35
evolution/proposals/ECP-0045-remote-website-e2e-direct.md
Normal file
35
evolution/proposals/ECP-0045-remote-website-e2e-direct.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# ECP-0045: Remote Website E2E Test Against Local Direct Publisher
|
||||
|
||||
## Why
|
||||
|
||||
We ship a static website at `every.channel`, but the critical path is still end-to-end playback:
|
||||
- a local node publishes a live CMAF stream, and
|
||||
- the deployed website can connect and play it.
|
||||
|
||||
Unit tests and local trunk builds do not validate this “real site + real browser engine” path.
|
||||
|
||||
## Proposal
|
||||
|
||||
Add an ignored, automated E2E harness that (on macOS using installed Chrome):
|
||||
1. Generates a short deterministic A/V MPEG-TS fixture (ffmpeg lavfi).
|
||||
2. Starts `ec-node direct-publish` locally and captures the offer link.
|
||||
3. Launches headless Chrome via DevTools and opens `https://every.channel`.
|
||||
4. Pastes the offer link into the UI, triggers “Parse link” and “Tune in”.
|
||||
5. Extracts the reply link from the UI and feeds it back to the publisher stdin.
|
||||
6. Asserts the website transitions to “Live” and a `<video>` element is present with a `blob:` src.
|
||||
|
||||
This validates:
|
||||
- deployed asset correctness,
|
||||
- the web WebRTC answerer path,
|
||||
- MSE append and playback wiring,
|
||||
- the direct publisher output framing.
|
||||
|
||||
## Scope (initial)
|
||||
|
||||
- Rust E2E test using a headless Chrome DevTools client, marked `#[ignore]`.
|
||||
- Runs in `nix develop` to ensure `ffmpeg` and `cargo` exist; uses the system Chrome app on macOS.
|
||||
- Default `SITE_URL=https://every.channel`, overridable for staging.
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. The harness is external to core codepaths and can be removed without protocol impact.
|
||||
49
evolution/proposals/ECP-0046-web-directory-and-signaling.md
Normal file
49
evolution/proposals/ECP-0046-web-directory-and-signaling.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# ECP-0046: Web Directory + WebRTC Signaling via Workers (Interim)
|
||||
|
||||
## Why
|
||||
|
||||
The deployed website at `every.channel` is static and cannot yet join the QUIC/MoQ mesh directly.
|
||||
We still need a practical way for web viewers to:
|
||||
- see a global list of available live channels, and
|
||||
- connect to one without manual copy/paste.
|
||||
|
||||
Browsers do support WebRTC DataChannels. We already use them as a bridge transport (ECP-0043),
|
||||
but we need a rendezvous mechanism for offers/answers and a directory that the website can query.
|
||||
|
||||
## Proposal
|
||||
|
||||
Add a small `/api/*` surface to the existing Cloudflare Worker (same origin as the website):
|
||||
- `/api/directory` (GET): list announced live channels (short TTL).
|
||||
- `/api/announce` (POST): publish a channel entry `{stream_id,title,offer}` with a TTL.
|
||||
- `/api/answer` (POST/GET): store and retrieve WebRTC answers for a given `stream_id`.
|
||||
|
||||
Add an `ec-node` mode that:
|
||||
- creates a WebRTC offer,
|
||||
- announces it to `/api/announce`,
|
||||
- polls `/api/answer?stream_id=...` until a browser answers,
|
||||
- streams CMAF objects once connected.
|
||||
|
||||
Update the website UI to:
|
||||
- fetch `/api/directory`,
|
||||
- show a global list with a basic liveness heuristic (TTL),
|
||||
- on selection, generate a WebRTC answer and POST it to `/api/answer`.
|
||||
|
||||
## Notes / Risks
|
||||
|
||||
- This is intentionally **centralized** for discovery/signaling. Data still flows P2P once connected.
|
||||
- Answers are treated as **one-shot** (first reader consumes) since one WebRTC offer maps to one peer connection.
|
||||
- Entries and answers are aggressively TTL'd and size-capped; this is not a spam-resistant directory.
|
||||
- Anti-junk / rate limiting / signing is not implemented in this ECP; it must be addressed before
|
||||
relying on this API for open public use.
|
||||
- This does not replace the long-term goal of native MoQ mesh discovery.
|
||||
|
||||
## Deployment Notes
|
||||
|
||||
- Cloudflare requires a `workers.dev` subdomain to be created for the account even if we only use custom domains.
|
||||
If `wrangler deploy` fails with code `10063`, open the Workers landing page once in the Cloudflare dashboard,
|
||||
then retry deploy.
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. The API is an interim adapter to bridge web constraints. It can be removed or replaced by
|
||||
decentralized rendezvous without changing the core stream objects.
|
||||
25
evolution/proposals/ECP-0047-coverage-reporting.md
Normal file
25
evolution/proposals/ECP-0047-coverage-reporting.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# ECP-0047: Coverage Reporting (cargo-llvm-cov)
|
||||
|
||||
## Why
|
||||
|
||||
We want high confidence in stream processing, transport, and cryptographic verification code.
|
||||
That requires:
|
||||
- repeatable coverage measurement, and
|
||||
- a visible baseline so we can drive coverage up without guessing.
|
||||
|
||||
## Proposal
|
||||
|
||||
Adopt `cargo llvm-cov` as the canonical Rust coverage tool in this repo (already provided by `flake.nix`).
|
||||
|
||||
Add:
|
||||
- `scripts/coverage.sh` to generate an HTML report and write a summary table to `tmp/coverage/summary.txt`.
|
||||
|
||||
Notes:
|
||||
- `#[ignore]` E2E tests are excluded by default and do not contribute to coverage.
|
||||
- We will expand unit and non-ignored integration tests in the crates that matter most for correctness
|
||||
(chunk framing, manifest validation, encryption, ingest adapters).
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. This adds a script and a convention; it does not change runtime behavior.
|
||||
|
||||
36
evolution/proposals/ECP-0048-bootstrap-api-via-containers.md
Normal file
36
evolution/proposals/ECP-0048-bootstrap-api-via-containers.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# ECP-0048: Bootstrap WebRTC Directory API via Cloudflare Containers
|
||||
|
||||
## Why
|
||||
|
||||
The website at `every.channel` is static, but we still need a minimal rendezvous surface so browsers can:
|
||||
- see a public list of live streams, and
|
||||
- exchange WebRTC offers/answers without copy/paste.
|
||||
|
||||
We already have a `/api/*` shape (ECP-0046). The intent is explicitly limited: **bootstrap WebRTC only**.
|
||||
|
||||
Using a Container lets us serve those endpoints from a normal Rust HTTP server (portable, no Workers-specific runtime),
|
||||
while keeping the main site static and keeping the surface area small.
|
||||
|
||||
## Proposal
|
||||
|
||||
Keep the public `/api/*` endpoints, but implement them as:
|
||||
- a single named Cloudflare Container instance (`global`) running a tiny Rust server; and
|
||||
- a Worker Durable Object wrapper (implemented via `@cloudflare/containers`) that forwards `/api/*` requests into the container on port `8080`.
|
||||
|
||||
Endpoints (unchanged):
|
||||
- `GET /api/health`
|
||||
- `GET /api/directory`
|
||||
- `POST /api/announce` `{stream_id,title,offer,expires_ms?}`
|
||||
- `POST /api/answer` `{stream_id,answer}`
|
||||
- `GET /api/answer?stream_id=...` (one-shot consume)
|
||||
|
||||
Constraints:
|
||||
- TTL + size caps.
|
||||
- No additional API endpoints.
|
||||
- Container internet egress disabled.
|
||||
- This is not spam-resistant or censorship-resistant; it is an interim bootstrap.
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. The website and node code depend on the `/api/*` shape, not the backing implementation.
|
||||
We can replace this with a decentralized rendezvous or native browser transport later.
|
||||
28
evolution/proposals/ECP-0049-ts-ingest-cmaf-canonical.md
Normal file
28
evolution/proposals/ECP-0049-ts-ingest-cmaf-canonical.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# ECP-0049: TS Ingest, CMAF Canonical (Enforcement)
|
||||
|
||||
## Why
|
||||
|
||||
HDHomeRun (and many tuner stacks) output MPEG-TS. We still want first-class, native ingest of TS sources.
|
||||
|
||||
Separately, the network needs a single canonical chunk container so that hashing, signing, verification, relaying,
|
||||
and multi-variant manifests stay consistent. That canonical container is CMAF (fMP4).
|
||||
|
||||
This ECP clarifies the boundary so "CMAF-only" does not get misread as "no TS support".
|
||||
|
||||
## Decisions
|
||||
|
||||
- TS is an **ingest input** format.
|
||||
- CMAF (fMP4) is the **only** on-disk and on-wire chunk container inside every.channel:
|
||||
- `init.mp4` + `segment_*.m4s`
|
||||
- HLS playlists (`index.m3u8`) are allowed only as a **local playback compatibility artifact**.
|
||||
- They must reference the CMAF fragments above; playlists are not network objects.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `ec-node ingest hdhr|linux-dvb|ts|hls` reads whatever the source provides and normalizes to CMAF fragments.
|
||||
- `ec-moq::HlsWriter` is CMAF-only (no TS segment writer).
|
||||
|
||||
## Reversibility
|
||||
|
||||
Medium. Reintroducing TS chunks later would require explicit opt-in and a new manifest/object mode, but the default remains CMAF.
|
||||
|
||||
22
evolution/proposals/ECP-0050-delete-ts-chunk-cli.md
Normal file
22
evolution/proposals/ECP-0050-delete-ts-chunk-cli.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# ECP-0050: Delete `ts-chunk` CLI Command
|
||||
|
||||
## Why
|
||||
|
||||
`ts-chunk` produces and persists TS chunks, which violates the CMAF-canonical constraint (ECP-0041, ECP-0049).
|
||||
We want fewer codepaths that accidentally become "real" over time.
|
||||
|
||||
If we need to debug TS timing, we keep `ts-sync` (analysis) and `stream-dump` (capture), but we do not keep a TS
|
||||
chunking/publishing workflow.
|
||||
|
||||
## Decision
|
||||
|
||||
- Remove the `every.channel ts-chunk` command from `ec-cli`.
|
||||
|
||||
## Scope
|
||||
|
||||
- CLI surface only. Node ingest still supports TS as an input format.
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. We can reintroduce a debugging tool later behind an explicit non-default feature flag if needed.
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# ECP-0051: Direct Publish Answer Timeout Control
|
||||
|
||||
## Why
|
||||
|
||||
`ec-node direct-publish` is used for "send a link to someone and have them connect later".
|
||||
A hard-coded short answer timeout makes this brittle and fails the "works for mom" usability bar.
|
||||
|
||||
## Decision
|
||||
|
||||
- Add `--answer-timeout-secs` to `ec-node direct-publish`.
|
||||
- Default: 900 seconds (15 minutes).
|
||||
- `0` means wait indefinitely.
|
||||
|
||||
## Reversibility
|
||||
|
||||
High. CLI-only behavior change.
|
||||
|
||||
47
evolution/proposals/ECP-0052-direct-subscribe-cli.md
Normal file
47
evolution/proposals/ECP-0052-direct-subscribe-cli.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# ECP-0052: Direct Subscribe CLI (WebRTC Directory Mode)
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Goal
|
||||
|
||||
Allow a non-Tauri node to **subscribe** to a `direct-publish` stream using the same WebRTC data-channel protocol used by the website, so we can:
|
||||
|
||||
- validate end-to-end connectivity (publisher -> every.channel bootstrap -> subscriber),
|
||||
- capture a short proof artifact (CMAF fragments and/or an `.mp4` remux),
|
||||
- debug issues without opening a browser.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- This is not MoQ transport.
|
||||
- This is not a long-lived relay/mesh node.
|
||||
- This does not implement signatures / merkle manifests / anti-junk policy.
|
||||
|
||||
## Proposal
|
||||
|
||||
Add a new `ec-node direct-subscribe` command that:
|
||||
|
||||
1. Locates an offer either:
|
||||
- from `--offer` (an `every.channel://direct?c=...` link), or
|
||||
- from `--directory-url` + `--stream-id` (fetch `/api/directory`, find matching `stream_id`).
|
||||
2. Creates a WebRTC *answerer* connection using `just-webrtc` (`SimpleRemotePeerConnection`).
|
||||
3. If operating via directory:
|
||||
- POSTs the answer to `/api/answer` with `{ stream_id, answer }`.
|
||||
4. Receives object frames on the data channel, decodes via `ec-moq::decode_object_frame`,
|
||||
and writes CMAF artifacts to disk:
|
||||
- `cmaf/init.mp4`
|
||||
- `cmaf/segment_000000.m4s` ... `cmaf/segment_XXXXXX.m4s`
|
||||
- `cmaf/index.m3u8` (VOD playlist, `#EXT-X-ENDLIST`)
|
||||
5. Optionally remuxes the captured playlist to an `.mp4` using `ffmpeg -c copy`.
|
||||
|
||||
## Why Now
|
||||
|
||||
We need a reproducible harness to verify:
|
||||
|
||||
- a remote publisher (e.g. a node near an HDHomeRun) can be watched from a distant network,
|
||||
- directory announcement/answer flow works without manual copy/paste,
|
||||
- received CMAF fragments are valid enough to remux and play.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- Additive CLI only; no protocol changes.
|
||||
- Can be removed without affecting existing publish/web paths.
|
||||
49
evolution/proposals/ECP-0053-direct-wire-framing.md
Normal file
49
evolution/proposals/ECP-0053-direct-wire-framing.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# ECP-0053: Direct WebRTC Wire Framing (Chunked Frames)
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
`direct-publish` currently sends each CMAF object (init or `.m4s` fragment) as a single WebRTC
|
||||
data-channel message (`encode_object_frame(meta, data)`).
|
||||
|
||||
Real OTA CMAF fragments routinely exceed typical data-channel maximum message sizes, causing
|
||||
runtime failure like:
|
||||
|
||||
- `Error: outbound packet larger than maximum message size`
|
||||
|
||||
This blocks real-world HDHomeRun end-to-end streaming via `https://every.channel`.
|
||||
|
||||
## Goal
|
||||
|
||||
Make direct WebRTC transport robust by supporting large objects while keeping:
|
||||
|
||||
- ordered delivery,
|
||||
- simple decoding in the website and CLI,
|
||||
- no additional servers beyond the existing bootstrap API.
|
||||
|
||||
## Proposal
|
||||
|
||||
Define a minimal application-layer framing over WebRTC data-channel messages:
|
||||
|
||||
- Each data-channel message begins with a 1-byte tag:
|
||||
- `0x00`: legacy "whole object frame in one message" (optional compatibility)
|
||||
- `0x01`: stream-chunk bytes (new default)
|
||||
- In `0x01` mode, bytes carry a length-prefixed stream:
|
||||
- `[u32_be length][frame bytes]` repeated
|
||||
- `frame bytes` are exactly the existing `encode_object_frame(meta, data)` output
|
||||
- Publishers MUST:
|
||||
- use `0x01` mode,
|
||||
- split the stream into safe message payloads (e.g. 16 KiB chunks)
|
||||
- Subscribers SHOULD:
|
||||
- accept both `0x01` and `0x00` to ease rollout.
|
||||
|
||||
## Rollout
|
||||
|
||||
- Update `ec-node direct-publish`, the website receiver, and `ec-node direct-subscribe`.
|
||||
- Redeploy static site assets; bootstrap API unchanged.
|
||||
|
||||
## Reversibility
|
||||
|
||||
- The change is localized to `direct-*` and can be removed or replaced without touching MoQ.
|
||||
|
||||
23
evolution/proposals/ECP-0054-remote-website-watch-e2e.md
Normal file
23
evolution/proposals/ECP-0054-remote-website-watch-e2e.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# ECP-0054: Remote Website E2E (Watch Existing Stream)
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Goal
|
||||
|
||||
Add an ignored E2E test that validates the deployed `https://every.channel` website can:
|
||||
|
||||
- fetch the public directory,
|
||||
- select a specific `stream_id`,
|
||||
- establish a WebRTC connection,
|
||||
- start playback (video element `src` becomes `blob:`).
|
||||
|
||||
This is complementary to existing remote-site tests that spawn a *local* publisher.
|
||||
|
||||
## Proposal
|
||||
|
||||
Add `crates/ec-node/tests/e2e_remote_website_watch_existing.rs`:
|
||||
|
||||
- reads `EVERY_CHANNEL_STREAM_ID` (required),
|
||||
- opens `EVERY_CHANNEL_SITE_URL` (default `https://every.channel/`),
|
||||
- clicks refresh, clicks Watch on the matching listing, waits for blob video.
|
||||
|
||||
17
evolution/proposals/ECP-0055-directory-timeouts.md
Normal file
17
evolution/proposals/ECP-0055-directory-timeouts.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# ECP-0055: Directory HTTP Timeouts For Direct-Publish
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
`ec-node direct-publish` keeps directory listings alive by periodically POSTing to `/api/announce`.
|
||||
If a single refresh POST hangs indefinitely (no client timeout), the refresh loop can stall and the
|
||||
listing expires, making streams undiscoverable.
|
||||
|
||||
## Proposal
|
||||
|
||||
- Build a `reqwest::Client` with a small default timeout (e.g. 8s) for directory operations.
|
||||
- Wrap periodic refresh POSTs in an additional short `tokio::time::timeout` guard.
|
||||
|
||||
This keeps directory liveness resilient under intermittent connectivity.
|
||||
|
||||
38
evolution/proposals/ECP-0056-turn-ice-bootstrap.md
Normal file
38
evolution/proposals/ECP-0056-turn-ice-bootstrap.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# ECP-0056: TURN ICE Bootstrap Endpoint
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
WebRTC connectivity is unreliable across diverse NAT / firewall environments when we rely only on
|
||||
public STUN servers. For "send a link to mom" reliability, viewers need TURN available.
|
||||
|
||||
We also need a single, stable place for the web app and native CLI to fetch ICE server config
|
||||
without exposing implementation details in the UI.
|
||||
|
||||
## Proposal
|
||||
|
||||
- Add `GET /api/turn` on `every.channel`.
|
||||
- Response returns a `just-webrtc` compatible `PeerConfiguration` subset:
|
||||
- `ice_servers`: array of STUN/TURN servers.
|
||||
- Default behavior:
|
||||
- Always include STUN servers (Cloudflare STUN + Google STUN fallback).
|
||||
- If a TURN shared secret is configured in the Worker environment, also include TURN servers
|
||||
with short-lived credentials generated via the TURN REST pattern (HMAC-based).
|
||||
|
||||
This keeps the container DO offline (`enableInternet=false`) and centralizes "how to TURN" in the
|
||||
Worker, while clients remain generic.
|
||||
|
||||
## Security / Abuse Notes
|
||||
|
||||
- TURN credentials are short-lived (hour-scale) and only usable against the TURN provider.
|
||||
- We do not log or persist TURN credentials.
|
||||
- The shared secret is stored as a Worker secret (not in git).
|
||||
|
||||
## Rollout
|
||||
|
||||
1. Deploy Worker with `/api/turn`.
|
||||
2. Update web viewer + `ec-node` direct publish/subscribe to fetch and use `/api/turn`.
|
||||
3. If/when Cloudflare Calls TURN keys are enabled for the account, store the Calls TURN key as the
|
||||
shared secret in the Worker and turn on TURN at the edge.
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# ECP-0057: Direct-Publish Session Reconnect + Busy Listing Semantics
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
`ec-node direct-publish` (direct WebRTC + CMAF over DataChannel) is currently a 1:1 stream.
|
||||
|
||||
Two concrete issues hurt reliability:
|
||||
|
||||
1. Directory entries can remain visible while a session is already connected, causing new viewers to
|
||||
click "Watch" and silently fail.
|
||||
2. If a viewer disconnects (reload, laptop sleeps, etc), the publisher has no way to accept a new
|
||||
viewer without restarting the process.
|
||||
|
||||
## Proposal
|
||||
|
||||
- Make `direct-publish` a session loop:
|
||||
- Create a fresh PeerConnection offer.
|
||||
- Announce it to the directory.
|
||||
- Wait for one answer.
|
||||
- Stream until send fails / ffmpeg ends.
|
||||
- Restart with a new offer.
|
||||
- Once a session is connected, stop refreshing announcements and quickly expire the directory entry
|
||||
(so the directory reflects joinable streams only).
|
||||
|
||||
This preserves the current 1:1 nature while making reconnection and "send a link" behavior much
|
||||
more robust.
|
||||
|
||||
## Non-Goals (For Now)
|
||||
|
||||
- True 1:many fanout / relaying for direct streams.
|
||||
- Multi-viewer, simultaneous subscriptions to the same publisher connection.
|
||||
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# ECP-0058: One-to-Many Web Bootstrap via Stream Relay DO (WebSocket)
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Problem
|
||||
|
||||
Our initial web viewer path used a direct WebRTC data channel between publisher and viewer.
|
||||
That path is fundamentally 1:1: once one viewer consumes the offer/answer, additional viewers
|
||||
cannot join, which breaks the "send a link to mom" requirement and contradicts the project's
|
||||
one-to-many goal.
|
||||
|
||||
TURN improves NAT traversal but does not change the 1:1 nature of WebRTC offers/answers.
|
||||
|
||||
We need a bootstrap path that:
|
||||
- is one-to-many by construction
|
||||
- works in a normal browser without plugins
|
||||
- carries the same CMAF object stream the native pipeline already produces
|
||||
- remains obviously replaceable by MoQ/WebTransport later (no long-term lock-in)
|
||||
|
||||
## Proposal
|
||||
|
||||
Add a per-stream fanout relay implemented as a Durable Object:
|
||||
|
||||
- `GET /api/stream/ws?stream_id=<id>&role=sub|pub`
|
||||
- Upgrades to a WebSocket.
|
||||
- `role=pub` registers a single publisher for the stream.
|
||||
- `role=sub` registers a subscriber.
|
||||
|
||||
Publisher behavior:
|
||||
- Connect once to the relay DO (`role=pub`).
|
||||
- Send the CMAF object stream as "direct-wire" chunked messages (same framing as the existing
|
||||
WebRTC direct path).
|
||||
- Continue to refresh the directory listing while publishing (multiple viewers are supported).
|
||||
|
||||
Subscriber behavior:
|
||||
- Connect to the relay DO (`role=sub`).
|
||||
- Receive the live message stream.
|
||||
- On connect, receive a buffered init segment (`chunk_index=0`) plus a small ring buffer of recent
|
||||
segments so playback can start immediately.
|
||||
|
||||
Web app behavior:
|
||||
- The global live list (`/api/directory`) remains the discovery surface.
|
||||
- "Watch" connects by `stream_id` to the relay websocket and plays via MSE.
|
||||
|
||||
## Wire Framing
|
||||
|
||||
We reuse the existing "direct-wire" message format:
|
||||
|
||||
- Each WebSocket message is binary and begins with a 1-byte tag:
|
||||
- `0x01` = STREAM chunk (payload is a slice of `[u32be frame_len][frame_bytes...]`)
|
||||
- `0x00` = FRAME (optional; not required)
|
||||
- `0x02` = PING (ignored)
|
||||
- `frame_bytes` is `ec-moq::encode_object_frame(meta_json, data)`:
|
||||
- `[u32be meta_len][meta_json_bytes][data_bytes...]`
|
||||
|
||||
The relay DO decodes publisher STREAM chunks into frames only for buffering (to identify init vs
|
||||
segments). For live fanout, it forwards publisher messages to subscribers as-is.
|
||||
|
||||
## Limits / Abuse Notes
|
||||
|
||||
This is not spam-resistant. It is a bootstrap relay.
|
||||
|
||||
Defensive bounds:
|
||||
- Buffer only `init` plus the last N segments (currently 12) per stream DO.
|
||||
- If publisher reassembly buffer grows beyond a few MiB (garbage input), reset it.
|
||||
|
||||
Future ECPs cover signatures/manifests/merkle/anti-junk.
|
||||
|
||||
## Rollout
|
||||
|
||||
1. Deploy Worker changes:
|
||||
- add Durable Object binding/class `StreamRelayDO`
|
||||
- route `/api/stream/ws` to it
|
||||
2. Add `ec-node ws-publish` / `ec-node ws-subscribe`.
|
||||
3. Update the web viewer to prefer the relay path for global watching.
|
||||
|
||||
51
evolution/proposals/ECP-0059-bootstrap-api-durable-object.md
Normal file
51
evolution/proposals/ECP-0059-bootstrap-api-durable-object.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# ECP-0059: Bootstrap API as Durable Object (No Containers)
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Goal
|
||||
|
||||
Keep `https://every.channel` deployable with the smallest possible Cloudflare surface area while still supporting:
|
||||
|
||||
- a global "directory" of live offers (short-lived),
|
||||
- one-shot answers for WebRTC rendezvous,
|
||||
- no secrets in-repo (use Forgejo/CI secrets),
|
||||
- minimal operational dependencies (avoid Docker/containers for deploy).
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- This is not a long-term global index.
|
||||
- This is not spam-resistant.
|
||||
- This is not the eventual mesh transport (MoQ / iroh / etc).
|
||||
|
||||
## Proposal
|
||||
|
||||
Replace the Cloudflare Containers-backed bootstrap API (ECP-0048) with a single Durable Object class:
|
||||
|
||||
- `EcApiContainer` remains the binding name for compatibility, but becomes a standard Durable Object.
|
||||
- The Worker routes `/api/*` to a single instance name `"global"`.
|
||||
|
||||
Endpoints (unchanged):
|
||||
|
||||
- `GET /api/health` -> `{ ok: true }`
|
||||
- `GET /api/directory` -> `{ now_ms, entries[] }` (sorted by recency)
|
||||
- `POST /api/announce` -> `{ ok, ttl_ms, entry }`
|
||||
- `POST /api/answer` -> `{ ok: true }`
|
||||
- `GET /api/answer?stream_id=...` -> one-shot answer (consumed on first read)
|
||||
|
||||
State constraints:
|
||||
|
||||
- Offers TTL clamped to `[5s, 60s]`
|
||||
- Answers TTL `~2m`
|
||||
- Hard caps: `<=200` offers, `<=500` answers (prune by recency)
|
||||
|
||||
## Rationale
|
||||
|
||||
Cloudflare Containers in CI adds Docker/build complexity and increases the number of moving parts.
|
||||
For a bootstrap rendezvous, a Durable Object is sufficient and simpler to deploy and audit.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- No UI changes required.
|
||||
- Keep existing durable object class name to avoid binding churn.
|
||||
- Reversible: we can reintroduce Containers later if justified by load/compute needs.
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# ECP-0060: Repository Sanitization and Authorship Baseline
|
||||
|
||||
Status: Draft
|
||||
|
||||
## Goal
|
||||
|
||||
Establish a privacy-safe public repository baseline:
|
||||
|
||||
- remove accidental personal identifiers from the tree,
|
||||
- standardize commit authorship as `every.channel <founder@every.channel>`,
|
||||
- require SSH-signed commits and provide a verifiable allowed-signers file.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- This does not attempt to preserve detailed early commit history.
|
||||
- This does not define identity beyond commit signatures.
|
||||
|
||||
## Proposal
|
||||
|
||||
1. Sanitize the working tree:
|
||||
- replace private LAN IP literals in tests with documentation IPs (RFC 5737),
|
||||
- avoid location-specific examples in ECPs/docs.
|
||||
2. Configure SSH commit signing:
|
||||
- `gpg.format = ssh`
|
||||
- `commit.gpgsign = true`
|
||||
- `gpg.ssh.allowedSignersFile = docs/allowed_signers`
|
||||
3. Rewrite history to a clean baseline:
|
||||
- publish a new `main` history consisting of a small number of signed commits
|
||||
- no private keys or tokens committed
|
||||
|
||||
## Rationale
|
||||
|
||||
This project is explicitly designed to be resilient and decentralized. That starts with a repository
|
||||
that does not leak personal identifiers and has a single, verifiable contributor identity.
|
||||
|
||||
## Rollout / Reversibility
|
||||
|
||||
- Tree sanitization is additive and low-risk.
|
||||
- History rewrite is disruptive but acceptable early; after the baseline, avoid rewrites.
|
||||
|
||||
9
evolution/proposals/README.md
Normal file
9
evolution/proposals/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Proposals
|
||||
|
||||
ECP (every.channel proposals) live here. Use a short, descriptive filename and include:
|
||||
|
||||
- Problem statement
|
||||
- Constraints
|
||||
- Alternatives considered
|
||||
- Decision
|
||||
- Rollout/teardown plan
|
||||
Loading…
Add table
Add a link
Reference in a new issue