Skip to content

fix(mux)!: stream fMP4 media payloads and bound held metadata - #7232

Merged
moq-bot[bot] merged 13 commits into
mainfrom
quest/m1/security-audit/fmp4-stream-mdat
Nov 1, 2026
Merged

moq-bot[bot] merged 13 commits into
mainfrom
quest/m1/security-audit/fmp4-stream-mdat

Conversation

@kixelated

@kixelated kixelated commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

An fMP4 box claiming a huge body retained later input across HLS segments (MUX-2 from the 2026-10-27 audit). Whole-box decoding waited for every byte a header declared, so one bogus mdat, free, moov, or moof header could accumulate every later segment. This PR finishes quest/m1/security-audit/fmp4-stream-mdat.md.

Approach

  • Media streams. Top-level headers are read incrementally. Ignored payload is discarded as it arrives. Sample ranges are resolved before any payload publishes. Unwrapped samples and per-track passthrough fragments then stream into MoQ frame writers. Forward gaps and cross-track interleaving are preserved.
  • What must be held whole is bounded at its header. A moov or moof body, or one text sample, has to be complete before it parses or is rewritten. One declaring more than fmp4::MAX_HELD_BYTES (32 MiB, the transport's per-group MAX_CACHE_BYTES) is refused before any of it is buffered. Media samples have no such bound.
  • Sample planning is bounded. A trun without per-sample fields declares its count in four bytes, and planning resolves every sample. A moof declaring more than fmp4::MAX_FRAGMENT_SAMPLES (2^21) samples is therefore refused when it parses. That is as many 16-byte entries as fit in the byte bound.
  • Refusals resume framing. A refused box or fragment publishes nothing more and discards the rest of its body, including the part in the refusing call. Input fed in box-aligned chunks (one HLS segment per call) resumes at the next call. Before this, a refusal at the mdat header left the importer parsing that mdat's payload as boxes. A failed moov/moof parse also left its body in the header buffer.
  • Controls wait for the publishing fragment. While a fragment publishes, its open frames own their groups. seek() and discontinuity() wait for its last sample, so neither can close or replace a group under an unfinished sample.
  • Failures fail the group. A refused fragment aborts its open frames. EOF inside a sample aborts that frame, failing its group, then finishes every track and returns UnexpectedEof.
  • The catalog reservation is released only once a fragment anchors, as on main. Passthrough headers take their mdat header size from the real encoding (16-byte largesize past 4 GiB) and check that each data_offset fits i32.

Decisions

  • ✅ No byte cap on media; stream each sample into its frame (maintainer, 2026-10-27). A fragment can hold several GOPs.

  • ✅ Refuse backwards per-track byte ranges before publishing fragment payload (maintainer, 2026-10-29). A sequential group writer cannot support those layouts without retaining earlier payload.

  • How should metadata and staged text samples be bounded? (maintainer, 2026-10-31)

    1. ✅ Bound held input at the header. Refuse a moov/moof or text sample over a fixed 32 MiB before buffering any of it. Drop incremental metadata parsing.
    2. Incremental structural parsing plus per-field bounds (a large streaming parser that still ends in limits).
    3. Incremental parsing with no bounds (leaves MUX-2 open).

    This reverses the 2026-10-28 AFK choice (incremental metadata parsing, no cap), but only for held metadata and text. Implementation showed incremental parsing could not bound what it keeps. A valid trun or a large text sample would still retain later segments. See the Design change: comment.

  • ✅ Refuse a moof declaring more than fmp4::MAX_FRAGMENT_SAMPLES (2^21) samples, alongside the 32 MiB MAX_HELD_BYTES bound (maintainer, 2026-10-31, accepted at /quest-merge). It closes the field-less trun gap that the byte bound leaves; real fragments carry hundreds to thousands of samples.

  • Choices made without asking, listed for review:

    • seek()/discontinuity() are deferred to the end of a publishing fragment, rather than refused or abandoning it. HLS issues controls between segments, and the old whole-mdat import never split a fragment across one.
    • The 32 MiB bounds are pub consts, so callers can see the limit they hit.
  • Coordination with moq-dev/moq#7234 (MKV streaming): fix(mux)!: stream MKV blocks and discard ignored elements in one pass #7234 landed first, so the merge of main moves the streamed unwrapped samples onto its container::Producer::open/commit and drops this PR's Legacy-only start_sample/finish_sample and its prepare split; producer.rs is now main's. Passthrough fragments keep their own frame writer, held beside a sample's Streamed in one Writer enum.

Impact

  • Public API: adds fmp4::MAX_HELD_BYTES, fmp4::MAX_FRAGMENT_SAMPLES, and the error variants BackwardsDataOffset, MetadataTooLarge, TextSampleTooLarge, and TooManySamples to the #[non_exhaustive] fmp4::Error. No signature changes.
  • Input behavior (breaking): streaming import refuses backwards per-track sample layouts; repackage such input in forward per-track byte order. A moov, moof, or text sample over 32 MiB, or a moof over 2^21 samples, is refused. Import::finish now finishes every track before returning UnexpectedEof for a truncated sample; on main it silently dropped a partial atom. Complete-frame CMAF decoding keeps its random-access behavior.
  • doc/lib/rs/moq-mux.md notes the forward-order requirement and the bound.
  • Wire: none.

Alternatives

Incremental metadata parsing (2026-10-28 plan) was dropped; see the decision above. A blanket byte cap on media was rejected (2026-10-27). Upstream mp4-atom 0.17 exposes incremental headers, but its body readers buffer whole bodies. The output cache pool cannot bound held metadata, because it evicts cached groups rather than refusing writes.

Validation

  • Hostile-input tests (all fed in chunks; each asserts held bytes or refusal before buffering):
    • oversized_metadata_is_refused_at_its_header: moov and moof declaring 32 MiB + 1 and 2^62 bytes are refused at the header's last byte. 256 later 1 KiB segments are discarded with nothing held.
    • metadata_at_the_bound_holds_only_what_arrived: a moof declaring exactly 32 MiB holds only the 1 KiB that arrived.
    • huge_trun_sample_counts_are_refused: field-less truns declaring u32::MAX, 2^21 + 1 across two runs, and 4 x u32::MAX samples (under 256 bytes of input each) are refused. The following mdat is discarded and the next fragment imports. 2^21 exactly is accepted.
    • oversized_text_sample_is_refused_before_staging: a WebVTT sample of 32 MiB + 1 is refused at the mdat header, before any payload. Its mdat is discarded, and the next cue imports.
    • backwards_ranges_fail_before_publishing now also checks that framing resumes after the refused mdat.
    • Earlier rounds: huge and EOF-sized mdat with output-owned samples and cache accounting, ignored boxes across cuts, deferred controls, truncation at EOF, and refused-fragment recovery. The ignored-box test from 95ec598361 fails on the pre-fix source and passes here.
  • fieldless_runs_do_not_stall_the_segment_scan (from main) now expects TooManySamples, since such a moof is refused before the scan.
  • cargo nextest -p moq-mux fmp4 producer: 359 passed. Clippy -D warnings is clean.
  • quest gate just ci origin/main on ba5dcee: passed (rs 4281 tests, rs_shapes, js, markdown, gh, drill, quest, shell, toml, nix, justfile).
  • Benchmarks: cargo bench -p moq-mux --bench containers -- cmaf, run against main (same bench file, saved baseline), load average 3-4. The 72 cmaf-interleaved-* cases stay within about 2% at 1-byte, 1 KiB, and whole-input chunks, except the 4-track 16-run 4096-byte whole-input cases:
case main branch rerun main rerun branch
interleaved-import 4tracks/16runs-4096bytes, whole 7.6 ms 7.9 ms 5.9 ms 7.4 ms
interleaved-unwrap 4tracks/16runs-4096bytes, whole 4.2 ms 6.0 ms 4.4 ms 4.2 ms
unwrap 4tracks-16frames/256 332 us 336 us 620 us 324 us
unwrap 4tracks-16frames/4096 516 us 606 us 634 us 879 us

The 4-track cmaf-import-*/cmaf-unwrap-* cases are bimodal on this host. They swing ±40% between runs of the same source, in both directions. The -frames and -bytes groups run identical workloads, yet disagreed with each other in the first run. The single-track cases stay within a few percent. I see no consistent regression; the interleaved whole-input import case is the one to watch.

  • quest gate just check origin/main on d525dd8: passed.
  • After merging main (5dbdb5a): cargo nextest run -p moq-mux 1922 passed; quest gate just check origin/main passed.
  • quest check: passed.
  • Land failures on 5dbdb5a were batch-mates', not this PR's:
    • 12:00Z: the land fixer attributed it to moq-dev/moq#7467's controller-test clock lint.
    • 15:53Z: moq-relay::integration stack::debug_relay_load_and_run_fit_windows_stack overflowed its 1 MiB stack, and the fixer attributed that to moq-dev/moq#7467 too. moq-relay does not depend on moq-mux (cargo tree -p moq-relay -i moq-mux matches no package), and the test passes on this head.
  • After merging main again (3ae1738): quest gate just ci origin/main with CHECK_MODULES=rs passed (rs 4385 tests). The full changed-scope quest gate just ci origin/main passed every module except one rs test, moq-transcode tests::ended_demand_opens_no_new_live_group. It hit a 10 s timeout under a paused clock while the suite was loaded. That test passed in the rs-only run and in 40 of 40 isolated runs. moq-transcode never reaches the fMP4 importer; this is a load flake, listed under Follow-ups.

Review

  • Latest: Codex gpt-6-astra medium (quest review --deep, new session), head 3ae1738 (merge of main at 802cadc; later main commits touch none of this PR's paths), 2 rounds:
    • Round 1, 1 finding, refuted: "keep inherited sample runs compact until their payload arrives". It is the same amplification refuted at 5dbdb5a: the 2^21 per-moof plan bound is the maintainer's recorded decision, re-picked at /quest-merge, and the 2026-10-31 design change dropped incremental resolution. It stays a follow-up below.
    • Round 2: 0 findings.
  • Previous: Codex gpt-6-astra medium (quest review --deep), head 5dbdb5a (the merge of main with the open/commit move), 2 rounds:
    • Round 1, 1 finding, refuted: "resolve inherited samples incrementally instead of expanding headers" (a field-less trun of 2^21 samples plans every sample before its payload arrives). The 2^21 per-moof plan bound is the maintainer's recorded decision above, sized to what a legal 32 MiB explicit moof already plans; incremental resolution is the direction the 2026-10-31 design change dropped. The amplification stays a follow-up below.
    • Round 2: 0 findings.
  • Before the merge: Codex gpt-6-astra medium (quest review --deep), head d525dd8: 0 findings.
  • Online, on ba5dcee: pullfrog flagged the starts_segment doc comment still crediting mdat_len with the scan bound; fixed in d525dd8 by naming the MAX_FRAGMENT_SAMPLES refusal in end_metadata. The OpenAI review reported no actionable bugs.
  • Round 1, Codex gpt-6-astra medium (quest review --deep --new, new session after the design change), head ba5dcee: 0 findings.
  • Earlier, a Claude reading of Codex's checkpoint found the reservation, prefix-panic, control-ordering, and largesize-offset issues, all fixed in 44e2a30.

Follow-ups

  • quest/m1/security-audit/fmp4-open-commit.md (added by moq-dev/moq#7468, not yet on main): its open/commit bullet is done here; the refused-box and benchmark bullets remain.
  • Parsed-state amplification: a 32 MiB moov/moof decodes into larger in-memory structures (TrunEntry is 40 bytes per 4-16 input bytes). It is bounded but not tight; Media fuzz would measure it.
  • Flaky moq-transcode tests::ended_demand_opens_no_new_live_group: a start_paused test whose live path waits on real encode threads, so the paused clock can auto-advance past its 10 s timeout under load. No quest tracks it yet.
  • A refusal drops the rest of the refusing call's input after the refused box. That is fine for box-aligned callers (HLS) but loses framing for arbitrary chunking. Consider continuing past a refused box, or making refusals terminal as the MKV importer does.

🤖 Generated with Claude Code

(Written by GPT-6.1 Sol and Claude Opus 5.5)

@kixelated kixelated changed the title fMP4 import streams mdat fix(mux): discard ignored fMP4 bodies incrementally Oct 29, 2026
@kixelated kixelated changed the title fix(mux): discard ignored fMP4 bodies incrementally test(mux): capture unbounded fMP4 ignored-box buffering Oct 29, 2026
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
@kixelated kixelated changed the title test(mux): capture unbounded fMP4 ignored-box buffering fix(mux)!: stream fMP4 media payloads (implementation checkpoint) Oct 29, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

Design change: Adopt the maintainer-confirmed refusal of backwards per-track sample byte ranges before publishing fragment payload. The streaming import path retains forward gaps and cross-track interleaving; complete-frame CMAF decoding keeps its existing random-access behavior. Metadata children will parse incrementally without a blanket byte cap, including variable-sized leaves.

The draft now contains an unfinished streaming implementation checkpoint. Whole metadata bodies and subtitle samples still accumulate, so MUX-2 remains unresolved. Build admission stalled verification after one compile found private generic bounds, which were corrected but have not been verified. The quest and PR body retain the exact remaining work.

(Written by GPT-6.1 Sol)

kixelated and others added 3 commits October 30, 2026 21:37
…ilures

Defer seek and discontinuity until a publishing fragment's last sample,
discard the rest of a refused fragment's mdat after aborting its open
frames, finish every track when EOF truncates a sample, release the
catalog reservation only once a fragment anchors, and size the
passthrough mdat header from its real encoding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tadata bound question

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title fix(mux)!: stream fMP4 media payloads (implementation checkpoint) fix(mux)!: stream fMP4 media payloads Oct 31, 2026
kixelated and others added 2 commits October 31, 2026 10:18
# Conflicts:
#	rs/moq-mux/src/container/fmp4/import.rs
A moov or moof body, and each text sample, must be held whole before it
parses or rewrites, so a huge declaration retained every later HLS
segment. Refuse one declaring more than MAX_HELD_BYTES (32 MiB, the
transport's per-group MAX_CACHE_BYTES) at its header, before buffering
any of it. A moof declaring more than MAX_FRAGMENT_SAMPLES samples is
refused too, since a trun without per-sample fields declares billions
of samples in a few bytes and planning resolves each one.

A refused box now discards the rest of its body, including the part in
the refusing call, so framing resumes at the next box. A failed moov or
moof parse no longer leaves its body in the header buffer.

This finishes quest/m1/security-audit/fmp4-stream-mdat.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Design change: bound held input at the header (maintainer decision, 2026-10-31, option 1 of the open question).

The importer now refuses a moov or moof body, or a text sample, whose declared size exceeds fmp4::MAX_HELD_BYTES (32 MiB, equal to the transport's per-group MAX_CACHE_BYTES), before buffering any of it. Incremental metadata parsing is dropped. Media samples stay capless and stream into their frames, as decided 2026-10-27. This reverses the 2026-10-28 AFK choice (incremental parsing, no cap), but only for held metadata and text. Implementation showed that incremental parsing alone could not bound what it keeps: a structurally valid trun or a large text sample still retains later segments.

One consequence of the bound, made as an implementation choice for review: a trun with no per-sample fields declares its sample count in four bytes, so the byte bound alone does not limit sample planning. A moof declaring more than fmp4::MAX_FRAGMENT_SAMPLES samples (2^21, as many 16-byte entries as fit in the byte bound) is refused when it parses.

The quest is complete with this change, so its file is deleted. The PR body records the decision.

(Written by Claude Opus 5.5)

@kixelated kixelated changed the title fix(mux)!: stream fMP4 media payloads fix(mux)!: stream fMP4 media payloads and bound held metadata Oct 31, 2026
@kixelated
kixelated marked this pull request as ready for review October 31, 2026 18:16

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit: ba5dcee

No actionable bugs found. Streaming media into output-owned frames while bounding whole metadata/text is a justified, simpler alternative to an incremental metadata parser; the sample-count bound also covers field-less trun expansion. Checked framing and refusal recovery, sample ordering/interleaving, passthrough offsets, deferred controls, truncation, and producer accounting, with an independent sample/passthrough review.

The documented parsed-state amplification and recovery restriction after refusals remain limitations. Static review only: I did not run tests or benchmarks or independently verify the reported performance results.

(Written by OpenAI)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues. One stale comment from the changed bounding scheme, inline.

Reviewed changes

  • Top-level framing is now incremental. Headers are read 8 bytes (16 for largesize) at a time, and a new Body state (Skip / Metadata / Media) drains each box as bytes arrive instead of waiting for a whole atom.
  • Media streams into frame writers. begin_mdat plans every sample range up front; write_mdat then routes payload bytes into per-track moq_net::frame::Producers opened by start_sample/Passthrough::start and closed by finish_sample/complete. Passthrough fragments are now a moof+mdat-header prefix with the payload streamed behind it.
  • Held input is bounded. begin_metadata refuses a moov/moof body over MAX_HELD_BYTES (32 MiB) at its header, end_metadata refuses a moof over MAX_FRAGMENT_SAMPLES (2^21), and text::Track::prepare refuses a text sample over the byte bound before staging. Resolver::streaming() adds a per-track forward byte-range check.
  • Controls and failures are ordered. seek()/discontinuity() defer until the publishing fragment completes; a refused fragment aborts its open frames and discards the rest of its mdat; finish() aborts a truncated fragment and returns UnexpectedEof.
  • Supporting changes. Producer::write is split into a shared prepare plus the streaming sample methods; new Error variants and MAX_* consts; docs/quest updated; benches parameterized by chunk size; hostile-input tests added.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment thread rs/moq-mux/src/container/fmp4/import.rs Resolved
@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 70da4440-dad1-41d1-83bd-b3de2670d3af
Commits

Reviewing files that changed from the base of the PR and between ba5dcee and d525dd8.

Files selected for processing: 1
  • rs/moq-mux/src/container/fmp4/import.rs
Files skipped from review as they are similar to previous changes: 1
  • rs/moq-mux/src/container/fmp4/import.rs

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

Walkthrough

The fMP4 importer now processes input incrementally and streams media samples into output frames. It adds limits for held bytes and fragment sample counts, rejects backwards per-track data offsets, and defers seek and discontinuity controls while a fragment publishes. Tests and benchmarks cover chunked input and refusal cases. Documentation records the importer limits and updates related audit references.

Merge Risk: Minimal · up to d525d

No actionable issue identified in the reviewed change; it is ready to merge after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.48% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely identifies the main changes: streaming fMP4 media payloads and bounding held metadata.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description is specific and tied to the changeset. It explains the fMP4 importer change that bounds held metadata and text, streams media samples, and adds the MAX_HELD_BYTES and MAX_FRAGMENT_SAMP…
Full details: Description check

Explanation

The description is specific and tied to the changeset. It explains the fMP4 importer change that bounds held metadata and text, streams media samples, and adds the MAX_HELD_BYTES and MAX_FRAGMENT_SAMPLES limits with their public error variants. It also lists the affected files, the validation performed, and the coordination with the MKV streaming change. The description is related to the changeset, so the check passes.

Full details: Docstring Coverage

Explanation

Docstring coverage is 40.48% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 42 functions across 7 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Simplify code
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

The delta since the prior pullfrog review is the merge of main into the branch.

  • Streamed samples move onto main's producer API. The importer now opens each unwrapped sample through container::Producer::open/commit and writes into a Streamed handle; this PR's Legacy-only start_sample/finish_sample pair and the prepare(&frame) split are dropped, and producer.rs is now main's file.
  • import.rs holds a Writer enum. A PendingTrack keeps either a passthrough fragment's moq_net::frame::Producer or one sample's Streamed, with open_sample replacing start_sample and unreachable! guards matching each writer kind on commit and abort.
  • Doc and quest bookkeeping follow main. The MATROSKA/WebM row, quest README entries, and the deleted audit quests come from the merge.

The migration is behavior-preserving: open computes the same Legacy prefix through container::prefix(timestamp), prepare(timestamp, keyframe) is identical to the prior prepare(&frame), and commit's written(...) measures the same payload size the old finish_sample did. The abort paths (Mdat::abort, Import::abort, Drop) abort the Streamed/fragment writer exactly as before.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@moq-bot

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

quest land removed this pull request from the queue: the batch check failed (exit 101).

@moq/net:test             | subscribe start: id=0 broadcast=room track=video
@moq/net:test             | track info: broadcast=room track=video
@moq/net:test             | subscribe ok: id=0 broadcast=room track=video
@moq/net:test             | publish ok: broadcast=room track=video
@moq/net:test             | publish done: broadcast=room track=video
@moq/net:test             | subscribe close: id=0 broadcast=room track=video
@moq/net:test             | publish ok: broadcast=room track=video
@moq/net:test             | publish done: broadcast=room track=video
@moq/net:test             | publish ok: broadcast=room track=video
@moq/net:test             | publish done: broadcast=room track=video
@moq/net:test             | publish ok: broadcast=room track=video
@moq/net:test             | publish done: broadcast=room track=video
@moq/net:test             | fetch done: broadcast=room track=video group=0
@moq/net:test             | https://localhost/test negotiated ALPN: moq-lite-07-wip
@moq/net:test             | announced: prefix=
@moq/net:test             | announce: prefix=
@moq/net:test             | announce: broadcast=room active=true epoch=undefined
@moq/net:test             | announced: broadcast=room active=true epoch=undefined
@moq/net:test             | announce: broadcast=room active=false
@moq/net:test             | announced: broadcast=room active=false
@moq/net:test             | broadcast discovery failed; broadcasts resolve on request only. undefined
@moq/net:test             | publish ok: broadcast=room track=video
@moq/net:test             | publish done: broadcast=room track=video
@moq/net:test             | subscribe start: id=0 broadcast=room track=video
@moq/net:test             | subscribe ok: id=0 broadcast=room track=video
@moq/net:test             | subscribe close: id=0 broadcast=room track=video
@moq/net:test             | subscribe start: id=0 broadcast=room track=video
@moq/net:test             | subscribe ok: id=0 broadcast=room track=video
@moq/net:test             | subscribe close: id=0 broadcast=room track=video
@moq/net:test             | 
@moq/net:test             | src/lite/malformed.test.ts:
@moq/net:test             | probe stream error warn: unexpected end of stream
@moq/net:test             |       at #fillTo (/work/js/net/src/stream.ts:340:11)
@moq/net:test             |       at async decode (/work/js/net/src/stream.ts:396:15)
@moq/net:test             | 
@moq/net:test             | probe stream error warn: unexpected end of stream
@moq/net:test             |       at #fillTo (/work/js/net/src/stream.ts:340:11)
@moq/net:test             |       at async decode (/work/js/net/src/stream.ts:396:15)
@moq/net:test             | 
@moq/net:test             | probe stream error warn: unexpected end of stream
@moq/net:test             |       at #fillTo (/work/js/net/src/stream.ts:340:11)
@moq/net:test             |       at async decode (/work/js/net/src/stream.ts:396:15)
@moq/net:test             | 
@moq/net:test             | 
@moq/net:test             | src/lite/publisher.test.ts:
@moq/net:test             | announce: prefix=
@moq/net:test             | announce: broadcast=static active=true
@moq/net:test             | announce: prefix=
@moq/net:test             | announce: broadcast=static active=true epoch=undefined
@moq/net:test             | announce: prefix=
@moq/net:test             | announce: prefix=
@moq/net:test             | announce: broadcast=cam active=true epoch=undefined
@moq/net:test             | announce: prefix=
@moq/net:test             | announce: broadcast=cam active=true epoch=undefined
@moq/net:test             | announce: broadcast=cam update=true
@moq/net:test             | announce: prefix=
@moq/net:test             | announce: broadcast=cam active=true epoch=undefined
@moq/net:test             | announce: broadcast=cam active=false
@moq/net:test             | announce: broadcast=cam active=true epoch=undefined
@moq/net:test             | announce: prefix=
@moq/net:test             | announce: broadcast=cam active=true epoch=undefined
@moq/net:test             | announce: broadcast=cam restart=true epoch=undefined
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish error: broadcast=test track=video error=remote error: 1
@moq/net:test             | datagram send stopped: sub=0 error=remote error: 1
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish error: broadcast=test track=video error=remote error: 1
@moq/net:test             | publish error: broadcast=test track=video error=remote error: 1
@moq/net:test             | datagram send stopped: sub=0 error=remote error: 1
@moq/net:test             | datagram send stopped: sub=0 error=remote error: 1
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish error: broadcast=test track=video error=remote error: 1
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish error: broadcast=test track=video error=remote error: 1
@moq/net:test             | datagram send stopped: sub=0 error=remote error: 1
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish error: broadcast=test track=video error=remote error: 1
@moq/net:test             | datagram send stopped: sub=0 error=remote error: 1
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish error: broadcast=test track=video error=remote error: 1
@moq/net:test             | datagram send stopped: sub=0 error=remote error: 1
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish error: broadcast=test track=video error=remote error: 1
@moq/net:test             | datagram send stopped: sub=0 error=remote error: 1
@moq/net:test             | publish ok: broadcast=test track=video
error: recipe `test-changed` failed on line 54 with exit code 143
ci: test rs failed
ci: seconds per module
   263  check py
    26  test py (cancelled)
    72  check rs
   217  test rs (failed)
   230  check js
    59  test js (cancelled)
   289  wall time
error: recipe `ci` failed on line 107 with exit code 101

.

@moq-bot
moq-bot Bot enabled auto-merge (squash) November 1, 2026 12:07
@moq-bot moq-bot Bot added the quest:merge label Nov 1, 2026
@moq-bot

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

codex / Codex land fixer: Requeued unchanged: Healthy relative to the reported failure; batch failed on PR &#35;7467 controller-test clock lint, not a flaky test.

(written by codex / Codex)

@moq-bot
moq-bot Bot disabled auto-merge November 1, 2026 15:52
@moq-bot moq-bot Bot removed the quest:merge label Nov 1, 2026
@moq-bot

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

quest land removed this pull request from the queue: the batch check failed (exit 100).

@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | publish done: broadcast=test track=video
@moq/net:test             | publish ok: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
@moq/net:test             | subscribe update: broadcast=test track=video
error: recipe `test-changed` failed on line 54 with exit code 143
ci: test rs failed
ci: seconds per module
    49  check rs
   225  test rs (failed)
   213  check js
    61  test js (cancelled)
     0  check drill
     2  check quest
    17  check shell
     0  check large_files
     0  check toml
     1  check nix
     0  check justfile
   274  wall time
error: recipe `ci` failed on line 107 with exit code 100

.

@moq-bot

moq-bot Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

codex / Codex land fixer: This maintainer pick already used its mechanical fi·x attempt. Run /quest-iterate to diagnose the repeated failure, then pick it again.

(written by codex / Codex)

@moq-bot
moq-bot Bot merged commit 951cca6 into main Nov 1, 2026
1 check passed
@moq-bot
moq-bot Bot deleted the quest/m1/security-audit/fmp4-stream-mdat branch November 1, 2026 18:13
kixelated added a commit that referenced this pull request Nov 1, 2026
Rewrite the fMP4 follow-up to its remaining refused-box and benchmark
work now that #7232 streams through open/commit, repoint the deleted
clock-test-process link at #7243, refresh line references, and fix the
CodeRabbit findings on unroutable, into_any callers, auth compatibility,
dialable schemes, e2ee seal/open limits, and trun amplification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Nov 1, 2026
Narrow the io_uring group to one invocation and check membership with
show-config, correct the relay CPU range to 18-56%, count revalidate in
the session index, describe DeclaredClock's slot-based decode times,
scope the fMP4 text-sample exception, attach gdb to the parked bench,
refuse unlocked repairs and recordings, report datagram expiry and
on-time delivery, define wildcard members' literal prefix, spell out
the opaque NVDEC path, and record #7232 as merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Nov 2, 2026
Resolve the modify/delete conflict on
quest/m1/security-audit/fmp4-stream-mdat.md by taking main's deletion
(#7232 finished it; this branch only dropped its media-fuzz link).

Align quests with main:
- fuzz-findings/README.md: drop Related links to the security-audit MKV and
  fMP4 streaming quests, both finished on main.
- mkv-block-header.md: #7234 replaced webm-iterable's block
  parsing with moq-mux's own header reader, so the 4-byte mkv reproducer now
  fails with InvalidSimpleBlock. Commit it under fuzz/regressions/mkv/ and
  delete the quest.
- mkv-truncated-finish.md, import-metadata-policy.md, sps-fuzz-h264-reader.md:
  stop linking media-fuzz.md, which this PR finishes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Nov 2, 2026
Paired runs on 038ecfa (before #7232), 951cca (#7232), main at 96244dd and this
branch: the 4-track 16-run whole-input interleaved import is 79% faster than
before the streaming change, and this branch adds no measurable cost there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant