BLO-4020 M.1: MMTP publisher on IETF moq-transport (draft-14+) - #1
Closed
kkroo wants to merge 13 commits into
Closed
BLO-4020 M.1: MMTP publisher on IETF moq-transport (draft-14+)#1kkroo wants to merge 13 commits into
kkroo wants to merge 13 commits into
Conversation
T1 clock pub/sub + T2 fMP4 BBB pub/sub with --catalog both pass on Cloudflare moq-rs v0.7.17 (commit f9f51dc) over draft-14 wire. Establishes the working baseline for the BLO-4020 MMTP migration. Findings captured: - dev/pub vs dev/sub scope-mismatch: dev scripts connect to different URLs (root vs /<name>), landing in different multi-tenant scopes on moq-relay-ietf and surfacing as `namespace not found`. Not a spec bug — Cloudflare's tenant-scope feature layered on draft-14. Fix is one-line in dev/sub. - file-coordinator state leak: /tmp/moq-coordinator.json persists registrations across abrupt publisher exits, causing `duplicate` rejection on next announce. Local-dev annoyance; production uses Redis-backed moq-api. What this confirms for the migration: - G2 (publisher container) is the main work — moq-pub is firmly fMP4-coupled, MMTP needs a new container module. - G1 (catalog Container::Mmtp) is the smallest first change. - G6 (libmoq vs moq-rs draft-14 wire diff) remains open. - M.4 is "replace, not extend": moq-lite is a different wire and not worth bridging to draft-14. Logs captured under .planning/m0-logs/ for reproducibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds two optional shapes to moq_catalog::Root for the MMTP-on-MoQ
migration (BLO-4020). Both fields are
#[serde(skip_serializing_if = "Option::is_none")] so existing
catalogs without the extensions round-trip without diff.
1. Container enum (Track.container field) per
draft-ramadan-moq-mmt-00 §11.1 — values: isobmff | mmtp | mfu |
fec-repair. Distinguishes how media is encapsulated inside MoQ
objects from TrackPackaging (which is the IETF draft-01 catalog's
cmaf vs loc streaming format).
2. multicast: Option<MulticastConfig> field per
draft-ramadan-moq-multicast-00 §4.1 + §4.2.3 — describes one or
more multicast endpoints with the MMTP packet_id → MoQ track map
used by the multicast send-side. NetworkSource carries AMT relay
discovery (type=amt, discovery=driad per §4.2.1). The
OneOrMany<T> carrier preserves the "one object OR array" input
form per §4.2.3 (single object does NOT become a one-element
array on re-serialize).
moq-pub/src/media.rs gets `multicast: None` to satisfy the new
field on Root.
19 unit tests:
- 4 Container enum (spec values, optional, default, accepts/omits)
- 5 multicast spec alignment (§4.1 protocol-optional,
networkSource at endpoint level, no simple-form fields, §4.2.3
OneOrMany single/array forms)
- 5 serde round-trips
- 4 multicast-extension parsing on Root
- 1 TrackPackaging round-trip pin
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Captures the M.1 architecture decisions for MMTP-on-moq-transport
(draft-14+) per umbrella BLO-4020. Option A locked: new sibling
crate `moq-pub-mmtp` rather than patching upstream moq-pub or
rewriting cast's bridge (Option B / Option C). Minimal blast
radius, zero upstream-coordination latency.
Decisions:
A1 — Object 0 of each new MPU MUST be FragmentType::Init
(publisher errors on violation; does NOT synthesize).
A2 — MPU sequence numbers strictly non-decreasing per track
(moq-transport's SubgroupsWriter::create silently drops
group_id ≤ latest — A2 monotonicity check catches this).
A3 — Unknown packet_id hard-errors.
A4 — Smoke test verifies per-track sha256 (not concatenated)
and mlog framing (not qlog).
A5 — Vendor mmt-core under moq-pub-mmtp/vendor/ at pinned
libmmt commit so the crate builds standalone.
C1 — Stdin (length-prefix) AND UDP (datagram) input both
supported; UDP for the multicast path.
Implementation Tasks T1-T9 derived from the decisions; T6 + T1-T4
land Lane A (publisher path). T5 (catalog validation), T7
(moq-sub-raw), T8 (ffmpeg fork stdout mode), T9 (smoke) remain.
GSTACK REVIEW REPORT footer: /gstack-plan-eng-review CLEARED;
Codex outside-voice flagged 12 findings, 9 folded into the
Implementation Tasks, 3 acknowledged as M.1b TODOs (object_id_delta
verification, MMTP fragmentation reassembly, FEC source-block
grouping).
Handoff prompt (m1-next-session-prompt.md) captures the resume
context for picking up mid-task.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New sibling crate to moq-pub. Reads MMTP packets (from stdin with
4-byte BE length-prefix framing, or from a bound UDP socket) and
publishes them as raw MoQ object payloads per draft-ramadan-moq-mmt
§3.1+§4.1. Catalog is loaded from --catalog-json; per-track state
keyed by MMTP packet_id from the multicast extension's
endpoints[].tracks[] map.
Implements M.1 ADR Implementation Tasks T6 + T1-T4:
T6 — Vendor mmt-core under vendor/
libmmt commit 929e5b0c7a14f6ffe0ecd50d792fff7cdc44ba0a
vendored so moq-pub-mmtp builds without a sibling libmmt
checkout. Refresh procedure in vendor/mmt-core/VENDOR.md.
Vendored Cargo.toml uses explicit versions (no workspace
deps). Per ADR A5.
T1 — Publisher loop with spec-true grouping
Dispatch fn abstracted over TrackSubgroups + SubgroupWrite
traits so the core logic is unit-testable without a real
moq-transport session. Enforces three hard invariants:
A1 — first packet of a new MPU is FragmentType::Init
(publisher errors on violation; caller's
responsibility to send MPU metadata first)
A2 — MPU sequence numbers strictly non-decreasing per
track. moq-transport's SubgroupsWriter::create
silently drops subgroups whose group_id ≤ latest
(subgroup.rs:116-128) — A2 is what catches the bug.
A3 — unknown packet_id hard-errors (no silent drop)
SubgroupsWriter::create({group_id: mpu_seq, subgroup_id: 0,
priority}) — NOT append() — per Codex #5.
Equal MPU sequence appends to the open subgroup.
T2 — `.catalog` track posted at startup
Full catalog JSON as one object on group 0 at priority 127.
Caller retains the returned SubgroupsWriter so the track
stays alive for the session.
T3 — FEC repair routing on `<name>/repair` siblings
build_state_map auto-creates a `<source>/repair` track for
every catalog source track. Repair packets land at priority
7 (per draft-ramadan-moq-mmt §7.2). Repair group_id mirrors
source MPU group_id so the receiver can correlate repair
symbols with the source data they protect. Per-FEC-block
grouping (parsing Source/Repair FEC Payload ID) is M.1b.
T4 — UDP input mode
tokio::net::UdpSocket::bind(--mmtp-udp-bind); each datagram
is one MMTP packet (no length prefix — the datagram boundary
IS the framing). recv_one_udp_packet() extracted as a
testable helper.
CLI parity with moq-pub for session setup: positional URL (no
path), --name, --catalog-json, --mmtp-input stdin|udp,
--mmtp-udp-bind, --bind, and the flattened
moq_native_ietf::tls::Args (--tls-cert/--tls-key/--tls-root/
--tls-disable-verify).
30 unit tests:
- 5 dispatch invariants (A1/A2/A3 + advance + equal)
- 5 repair routing (sibling, priority 7, group correlation,
unknown packet_id, before-source-MPU, missing-repair-sibling)
- 6 framing (length-prefix encode/decode + EOF/partial/oversize)
- 4 MMTP/MPU header parsing (route fn)
- 6 build_state_map (no-multicast, no-endpoints, duplicate-
packet_id, missing-track-reference, happy-path, repair-track-
registration)
- 1 priority_for_container pin
- 1 publish_catalog_track registration pin
- 1 UDP recv → dispatch integration
- 1 mmtp_parse short-packet rejection
Out of scope for M.1 (deferred): MPU metadata synthesis from MFU
(caller's job), MMTP fragmentation reassembly (M.1b), per-FEC-block
grouping (M.1b), receiver decode/render (M.4),
moq-transport object_id_delta verification (Codex #6 follow-up
landing in T9 mlog).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reflects post-rebase + post-PR state: - T6 + T1-T4 marked complete with the four commit SHAs landed on blo-4020-m1 (dbf5ee1, 6ee40ff, bde975d, 3101aca on base f0a709a after a clean 47-commit upstream rebase). - PR #1 link: #1. - Remotes documented (origin = cloudflare/moq-rs untouched; blockcast = Blockcast/moq-rs where local main tracks blockcast/blo-4020-m1). - Workflow notes added: new commits land on PR #1 automatically; branch off if T5/T7 want separate PRs. - Obsolete "uncommitted on main, ask before non-test edits" branch warning removed (no longer applicable). - Remaining T5/T7/T8/T9 scope carried forward unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Promotes catalog invariants from publisher-only runtime checks to
library-level validation so subscribers can reject malformed
catalogs without re-implementing the guards.
moq-catalog:
- New `CatalogValidationError` enum (Display + std::error::Error,
no external deps). Three variants:
DuplicatePacketId — T5a (draft-ramadan-moq-multicast §4.1)
UnknownTrackReference — T5b
FecRepairInCatalog — T5e (repair tracks are
publisher-derived per ADR T3)
- `Root::validate()` runs all three checks (first-error-wins).
- `Root::expand_common_fields()` promotes commonTrackFields into
each track entry (track-level overrides win). Replaces the
previously-dead `Track::with_common` private helper. T5c.
- `CommonTrackFields` gains `Clone` (needed for expand).
moq-pub-mmtp:
- `check_namespace_consistency(&Root, name)` — if
commonTrackFields.namespace is Some(X) and X != --name, hard
error. Catches a class of publisher misconfigurations. T5d.
- main(): wired in order
1. catalog.validate() (lib-level guards)
2. check_namespace_consistency() (publisher CLI consistency)
3. catalog.expand_common_fields() (in-memory normalization;
on-wire .catalog bytes
remain the original
authorial form)
- `build_state_map` runtime guards (duplicate packet_id, unknown
track ref) kept as defense in depth.
Tests:
- moq-catalog: +5 (3 validate + 2 expand_common_fields) → 24 total
- moq-pub-mmtp: +3 (check_namespace_consistency) → 33 total
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New sibling crate to moq-sub. Subscribes to named tracks on a MoQ
broadcast and dumps each track's concatenated raw object payloads
to its own file. Used by the M.1 smoke test (T9) for per-track
sha256 verification — by dumping object payloads with NO separators
(no length prefix, no timestamp), the publisher's input bytes and
the subscriber's output bytes match byte-for-byte per track.
CLI: paired `--track NAME --output PATH` arguments, repeatable.
moq-sub-raw --name BBB \
--track v --output v.bin \
--track a --output a.bin \
https://localhost:4443
Implementation:
- `drain_track_to_writer<W>` — extracted as a testable async fn
so unit tests can drive it with in-process Tracks::produce
pairs (no live relay required). Walks TrackReaderMode::Subgroups
→ groups.next → group.next → object.read, writing every chunk
to W in arrival order. Returns total bytes written.
- `validate_track_output_pairs` — surfaces CLI misconfigurations
(empty args, mismatched counts) BEFORE opening a session.
- main(): mirrors moq-sub's session template. Spawns one
subscriber.subscribe task per track AND one drain task per
track; tokio::select! over session.run + drain JoinSet.
Tests (6 new):
- validate_track_output_pairs: empty / mismatched / matched.
- drain_concatenates_object_payloads_in_arrival_order — single
subgroup, 3 objects, verify concat.
- drain_concatenates_across_multiple_groups — producer/consumer
run concurrently via tokio::join! with a 100ms gap between
groups (SubgroupsReader surfaces only the latest subgroup —
documented gotcha pinned by the test).
- drain_writes_zero_bytes_on_empty_track — subgroups mode set
but never written; drain returns Ok(0).
Workspace entry added; Cargo.lock updated.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Blockcast FFmpeg fork's `moqenc_mmt` muxer already emits MMTP
packets through standard AVIOContext to a `udp://group:port?multicast=1`
URL. The proper way to wire moq-pub-mmtp as the consumer is to have
its existing `--mmtp-input udp` listener auto-join when the bind
target is a multicast address — no FFmpeg changes, no new flags on
moq-pub-mmtp, just smarter UDP open.
New `udp` module:
- `is_multicast(SocketAddr) -> bool` — IPv4 and IPv6.
- `open_udp_socket(target) -> UdpSocket`:
* unicast target: bind directly.
* multicast target: bind wildcard on target.port(), join the
group on all interfaces, enable multicast loopback so a
single-host smoke test can both send and receive.
`run_udp_loop` swapped its raw `UdpSocket::bind` for
`udp::open_udp_socket`, so `--mmtp-udp-bind 239.255.1.1:5004`
now actually receives multicast.
T8 ADR plan ("add stdout flag to ffmpeg muxer") superseded: the
muxer's existing avio_write path through AVIOContext IS the proper
FFmpeg interface. The dirty state in the FFmpeg fork is unrelated
to this work (FEC algorithm FFI extension + IPv6 multicast egress
fix in progress on a separate workstream).
Tests (+6):
- is_multicast: 4 (ipv4 multicast / unicast, ipv6 multicast / unicast).
- open_unicast_binds_directly_to_target.
- open_multicast_binds_wildcard_and_recvs_loopback — full
end-to-end: open multicast listener, send via second socket,
verify recv. Gracefully skips if sandboxed network lacks
multicast loopback (treats timeout / open-error as skip).
Total moq-pub-mmtp tests: 33 → 39.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the M.1 ADR Definition-of-Done item: an end-to-end smoke test
proving MMTP packets flow byte-for-byte through the new draft-14+
pipeline.
Pipeline (5 processes, all out of this repo):
synth_mmtp (Rust example, deterministic MPU sequences)
→ UDP loopback (127.0.0.1:5004)
→ moq-pub-mmtp --mmtp-input udp
→ moq-relay-ietf --dev --mlog-dir
→ moq-sub-raw (per-track payload dump)
→ per-track sha256 vs synth's expected files
Verdict: PASS. Both tracks' sha256 match between publisher input
and subscriber output; --mlog-dir captures SUBGROUP/OBJECT framing
per draft-14. Full results recorded in
.planning/moq-rs-m1-results.md.
New files:
- moq-pub-mmtp/examples/synth_mmtp.rs — deterministic MMTP
packet generator. Emits valid MPU Init packets for two
packet_ids (1=video, 2=audio) with predictable payloads.
Outputs to UDP (one datagram = one packet) OR stdin
(length-prefixed) AND writes expected per-track files for
sha256 comparison.
- .planning/m1-smoke.sh — orchestrator. Builds binaries, writes
catalog, starts relay → pub → sub → synth, compares hashes,
shows mlog, reports pass/fail. Idempotent across reruns.
- .planning/moq-rs-m1-results.md — verdict, evidence, repro
instructions, and the open M.1b items (FEC source-block
grouping, fragmentation reassembly, libmoq G6 byte-diff).
Pacing note: SubgroupsReader surfaces only the latest subgroup —
the smoke uses --packet-delay-ms so the subscriber drains each
MPU before the next supersedes. Already covered by a unit test
in moq-sub-raw; documented in the results doc as a real-world
constraint (real publishers are network-rate-limited).
T8 status: superseded. The Blockcast FFmpeg fork's moqenc_mmt
muxer already uses AVIOContext for UDP emission — no fork
changes needed. The architecture pushback (no special stdout
flag) is the correct call. T8.5 (moq-pub-mmtp multicast join,
already landed) is the matching consumer-side support.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Updates the in-tree handoff prompt to reflect the post-T9 state: 9 commits on blo-4020-m1, 69 tests + smoke green, T1-T9 all landed (T8 N/A per ADR architecture pushback). Lists the four candidate follow-up tracks in priority order — A (PR merge prep), B (M.1b sub-tasks), C (M.2 cast bridge — ADR-gated), D (M.4 receiver inventory). No code changes; the doc tracks the closed state of the milestone and the open M.1b sub-tasks (fragmentation reassembly, per-FEC-block grouping, object_id_delta correctness, G6 byte-diff vs libmoq).
…M.2/M.4 ahead Refreshes the handoff prompt to reflect the 2026-05-28 M.1b session: - M.1b §B1=C closed by raw-passthrough fragmentation contract (PR #2). - M.1b §B3 forensics confirmed object_id_delta bug at moq-transport/src/session/subscribed.rs:281 (PR #3); upstream issue drafted in BLO-8047 awaiting external filing. - M.1b §B4 confirms wire-format divergence between cast/moq_lite and moq-pub-mmtp/IETF (PR #4); B2 re-scoped from "high operational value" → "M.4 prerequisite, deferred". Four stacked PRs on blo-4020-m1. Next session picks: - PR review / merge prep (likely fastest) - Upstream object_id_delta issue filing at cloudflare/moq-rs (use BLO-8047 draft) - M.2 cast bridge port (biggest blast radius; needs ADR + plan-phase first) - M.4 receiver migration (multi-object subgroup decoder, tier-switching fallback) Also documents the new FRAGMENT=N smoke env knob, the four follow-up branch names with detached upstreams, and the raw-passthrough contract as a carry-forward constraint. No code changes; doc tracks the post-M.1b state of the milestone.
Reflects 2026-05-28 session continuation: - M.4 ADR drafted on branch blockcast/blo-4020-m4-adr (commits ec8e4b7, 5ffd5e1, 1b0c577). A0-A3 locked; Q3-Q8 tactical questions still open. - Scope discovery: moqtail already on IETF draft-16 with MMTP container wired; Shaka MSF speaks drafts 14+16 with ALPN negotiation; hang-mmt-fec is the actual migration burden but Track 3 is INCLUDED per session sign-off. - Updates the "PICK ONE FOR NEXT SESSION" list: D becomes "M.4 ADR sign-off + T0 (publisher draft-16 bump)"; adds new E for "M.4 Track 1 (Shaka MMTP) implementation" as the post-T0 starting point. No code changes.
7 tasks
Reflects 2026-05-28 session continuation: - M.4 ADR Q3-Q8 signed off; Track 3 reshaped per Q7 (use moqtail-ts; skip Rust IETF subscriber fix). Total M.4 scope ~5-6 weeks. - T0 (publisher draft-16 bump) implemented + smoke-validated on branch blo-4020-m4-t0 (commit 9336e4a), PR #5 opened on the fork. Per-track sha256 IDENTICAL to M.1 baseline at FRAGMENT=0; mlog confirms selected_version DRAFT_16 in both QUIC sessions. - 113 moq-transport tests pass (+3 negotiation regression tests). Five PRs now stacked on blo-4020-m1; one fork-only branch with M.4 ADR. Pick list restructured: - D = M.4 Track 1 (Shaka MMTP container) — RECOMMENDED next, smallest - E = M.4 Track 2 (moqtail tier-switching) - F = M.4 Track 3 (MoqWatch port onto moqtail-ts; T3.1/T3.2 SKIPPED per Q7) - G/H/I = PR review, upstream issue, M.2 Track 1 start files: shaka-player/lib/msf/loc_parser.js and lib/transmuxer/loc_transmuxer.js (templates to mirror for MMTP). M.4 ADR §T1.1-T1.7 has the full task breakdown. No code changes; doc tracks the post-T0 state.
kkroo
added a commit
that referenced
this pull request
May 31, 2026
Reviews the three production changes shipped inside shaka commit 4df077593
(strip 14B MFU sub-header; store Init MPU on init ref; seedFromFmp4Init).
Root finding: decision D9 ("init from in-band SPS/PPS") is contradicted by
the design doc's own §2 ground truth (MFUs carry SEI+coded NALs, not param
sets; SPS/PPS live in the Init-MPU avcC). Proposes D12: revert D9, reinstate
the avcC-seed init path. Fix #1 KEEP+harden, #2 REWORK, #3 wire-in-or-delete.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
This was referenced Jun 13, 2026
Author
|
Superseded by #20 ( |
kkroo
added a commit
that referenced
this pull request
Jun 13, 2026
Reflects post-rebase + post-PR state: - T6 + T1-T4 marked complete with the four commit SHAs landed on blo-4020-m1 (dbf5ee1, 6ee40ff, bde975d, 3101aca on base f0a709a after a clean 47-commit upstream rebase). - PR #1 link: #1. - Remotes documented (origin = cloudflare/moq-rs untouched; blockcast = Blockcast/moq-rs where local main tracks blockcast/blo-4020-m1). - Workflow notes added: new commits land on PR #1 automatically; branch off if T5/T7 want separate PRs. - Obsolete "uncommitted on main, ask before non-test edits" branch warning removed (no longer applicable). - Remaining T5/T7/T8/T9 scope carried forward unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
kkroo
added a commit
that referenced
this pull request
Jun 13, 2026
Reviews the three production changes shipped inside shaka commit 4df077593
(strip 14B MFU sub-header; store Init MPU on init ref; seedFromFmp4Init).
Root finding: decision D9 ("init from in-band SPS/PPS") is contradicted by
the design doc's own §2 ground truth (MFUs carry SEI+coded NALs, not param
sets; SPS/PPS live in the Init-MPU avcC). Proposes D12: revert D9, reinstate
the avcC-seed init path. Fix #1 KEEP+harden, #2 REWORK, #3 wire-in-or-delete.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kkroo
added a commit
that referenced
this pull request
Jun 13, 2026
Reviews the three production changes shipped inside shaka commit 4df077593
(strip 14B MFU sub-header; store Init MPU on init ref; seedFromFmp4Init).
Root finding: decision D9 ("init from in-band SPS/PPS") is contradicted by
the design doc's own §2 ground truth (MFUs carry SEI+coded NALs, not param
sets; SPS/PPS live in the Init-MPU avcC). Proposes D12: revert D9, reinstate
the avcC-seed init path. Fix #1 KEEP+harden, #2 REWORK, #3 wire-in-or-delete.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Containerenum (isobmff/mmtp/mfu/fec-repair) per draft-ramadan-moq-mmt §11.1 and an optionalmulticast: MulticastConfigextension per draft-ramadan-moq-multicast §4.1+§4.2.3. Both fields are opt-in (skip_serializing_if = "Option::is_none") so existing catalogs round-trip without diff.packet_idfrom the catalog'smulticast.endpoints[].tracks[]. Auto-creates<source>/repairsibling tracks for AL-FEC routing at priority 7. Posts the catalog JSON on the.catalogtrack at startup (priority 127, group 0).moq-pub-mmtp/vendor/mmt-core/at libmmt commit929e5b0cso the crate builds without a sibling libmmt checkout.Why
BLO-4020 umbrella: replace cast's libmoq C-ABI hop with native Rust talking IETF
moq-transportdraft-14+. M.0 (baseline + scope-mismatch finding) and M.1 ADR are captured under.planning/. Option A locked: new sibling crate rather than patching upstreammoq-pubor rewriting cast's bridge.What's enforced (publisher invariants)
FragmentType::InitSubgroupsWriter::createsilently dropsgroup_id ≤ latestat subgroup.rs:116-128 — A2 catches it)packet_idappears in the catalog's multicast mapRepair group_id mirrors source MPU group_id so receivers can correlate repair symbols with the data they protect. Per-FEC-block grouping (parsing FEC Payload ID for SBN) is deferred to M.1b.
Test plan
cargo test -p moq-pub-mmtp— expect 30 passed (5 dispatch invariants, 5 repair routing, 6 framing, 4 MMTP parsing, 1 priority pin, 6 build_state_map error/happy paths, 1 catalog publication pin, 1 UDP recv→dispatch, 1 short-packet rejection, 1 routing edge case).cargo test -p moq-catalog— expect 19 passed (Container enum spec values, multicast spec §4.1+§4.2.3 alignment, serde round-trips, optional-extension round-trips).cargo build --release -p moq-pub-mmtp— green, zero warnings../target/release/moq-pub-mmtp --help— CLI surface includes--name,--catalog-json,--mmtp-input stdin|udp,--mmtp-udp-bind,--bind, and the flattened TLS flags frommoq_native_ietf::tls::Args.Out of scope (deferred per ADR)
moq-sub-raw, T8 ffmpeg fork stdout mode).ADR
.planning/moq-rs-m1-adr.mdcarries the full decision set (A1-A5/C1), 9 Implementation Tasks (T1-T9), and the GSTACK eng-review report (Codex outside-voice: 12 findings, 9 folded into T1-T9, 3 acknowledged as M.1b TODOs). This PR lands T6 + T1-T4 (Lane A: publisher path). T5/T7/T8/T9 remain.🤖 Generated with Claude Code