Skip to content

fix(ffi): follow publisher runs by lifecycle, keeping wire errors - #7479

Merged
kixelated merged 7 commits into
mainfrom
quest/m1/ffi-ietf-run-end
Nov 3, 2026
Merged

kixelated merged 7 commits into
mainfrom
quest/m1/ffi-ietf-run-end

Conversation

@kixelated

@kixelated kixelated commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

When a publisher withdraws a track it never finished, an FFI consumer sees Protocol/Unroutable over moq-lite and MoqError.Internal over the retained moq-transport drafts. The bindings' guides told readers to treat Unroutable as the publisher leaving. That cannot work over moq-transport: ietf::publisher::Served::status sends every failed track as PUBLISH_DONE/INTERNAL_ERROR, and group resets also carry INTERNAL_ERROR, because neither registry on draft 18, 20, or 22 has an Unroutable code.

Decision

Maintainer, 2026-11-01. The recommended option 1 was chosen:

  1. ✅ Keep incoming wire errors as they are, and follow publisher runs by their announcement and broadcast lifecycle. A finished track already ends cleanly. Withdrawing unfinished content is still an error.
  2. Choose a publisher-side cancellation mapping for route loss in Rust and JS.

Also decided: do not decode 0 as EOF, and do not send TRACK_ENDED for unfinished content. The maintainer approved the change this makes to the quest's error-kind-based Goal and to its milestone index entry.

Approach

  • Wire: unchanged. Rust and JS already send INTERNAL_ERROR for a failed track and keep reset 0 as an error. JS's IETF publisher (js/net/src/ietf/publisher.ts) maps a failed track the same way Rust does, and @moq/watch already follows request.active and follow, never a track's error. So JS needed no change.
  • Proof over every protocol. The live FFI server/client run-end test now runs on lite-07 and IETF drafts 18, 20, and 22. It reads the broadcast through follow(path), the way a player does, and checks each step:
    • finished media ends cleanly
    • the catalog ends and closed() resolves
    • the unfinished track keeps its wire error: Unroutable on lite, Internal on IETF
    • the announcement End arrives
    • a replacement run at the same path comes back from the next available() and delivers a frame
  • Docs. The binding guides (C, C++, Dart, Go, Kotlin, Python, Swift) and the publisher doc comments (moq-ffi, moq-c, Go, Python, Swift) no longer say readers take Unroutable as the publisher leaving. They say the track ends with Unroutable (Internal over moq-transport, which has no such code). The guides also point readers to follow runs by announcement, citing identity and recovery. That concept page now names the moq-ffi bindings' follow(path)/available() and says that a track's error kind is not a signal to restart.
  • Quest. This PR meets the rewritten goal, so it deletes quest/m1/ffi-ietf-run-end.md and its quest/m1/README.md entry with quest rm. The brief said to rewrite the Goal and the milestone description. Deleting the quest follows the guide's rule that the PR finishing a quest deletes it, rather than leaving behind a quest whose goal is already met. The moq.pro samples moving to available() belong to moq.pro's reader quest.

Impact

  • Public API: unchanged. Doc comments only.
  • Wire: unchanged.

Validation

On head a3732f575f, after merging origin/main e1e37dd4ed:

  • just rs test -p moq-ffi: all 287 passed. This includes server_client_roundtrip (lite-07) and server_client_run_end_draft{18,20,22}. On today's main, a stream reset still sends INTERNAL_ERROR for Unroutable (rs/moq-net/src/ietf/error.rs, unregistered_conditions_are_internal). So the IETF assertion still holds.
  • just check origin/main: passed (py, rs, c, cpp, js, markdown, gh, kt, swift, go, quest, shell, dart, and the rest).
  • quest check --base origin/main: 550 documents ok.

Merge with main (2026-11-02)

The conflicts were all prose in the binding guides (C, Dart, Go, Kotlin, Python, Swift) and in doc/concept/identity.md. Both sides' intent is kept:

The FFI follow now takes an optional instance, which defaults to none, so follow(path) is still correct.

Review

Codex CLI, gpt-6.1-sol, medium effort (quest review --base origin/main --new), session 01a19ff5-44da-7f92-9bd2-8bf7164d0a7b, round 1, reviewed head a3732f575f25f72c597f7c7011e4b264b13542b6 against origin/main 5583782acd: no findings.

Automated reviews posted on the previous head 14a2271d5d, from OpenAI and Pullfrog, reported no findings. CodeRabbit was rate-limited.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 4 commits October 31, 2026 22:31
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Keep the quest open for a publisher-side mapping or lifecycle decision. Stream reset zero cannot safely be decoded as an end.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
@kixelated kixelated changed the title Bindings see a moq-transport run end as an end test(ffi): reproduce IETF publisher run-end errors Nov 1, 2026
kixelated and others added 2 commits November 1, 2026 10:23
A track withdrawn unfinished keeps the error its wire carries: Unroutable
over moq-lite, INTERNAL_ERROR over moq-transport, which has no code for it.
Readers follow runs by announcement instead. The live FFI run-end test now
follows the path on lite-07 and IETF drafts 18, 20, and 22, sees the
announcement end, and picks up a replacement run from the next available().

Drop the doc claim that readers take Unroutable as the publisher leaving,
and finish the ffi-ietf-run-end quest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title test(ffi): reproduce IETF publisher run-end errors fix(ffi): follow publisher runs by lifecycle, keeping wire errors Nov 1, 2026
@kixelated
kixelated marked this pull request as ready for review November 1, 2026 18:36
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

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: 38f8f836-7be7-4827-854e-a99439bbb593
Commits

Reviewing files that changed from the base of the PR and between e1e37dd and a3732f5.

Files selected for processing: 22
  • dart/moq_ffi/lib/src/moq.dart
  • doc/concept/identity.md
  • doc/lib/c/index.md
  • doc/lib/cpp/index.md
  • doc/lib/dart/index.md
  • doc/lib/go/index.md
  • doc/lib/kt/index.md
  • doc/lib/py/index.md
  • doc/lib/swift/index.md
  • go/wrapper/publish.go
  • py/moq-rs/moq/publish.py
  • quest/m1/README.md
  • quest/m1/ffi-ietf-run-end.md
  • rs/moq-c/src/api.rs
  • rs/moq-c/src/publish.rs
  • rs/moq-ffi/src/audio.rs
  • rs/moq-ffi/src/json.rs
  • rs/moq-ffi/src/media.rs
  • rs/moq-ffi/src/producer.rs
  • rs/moq-ffi/src/test.rs
  • rs/moq-ffi/src/video.rs
  • swift/Sources/Moq/Broadcast.swift
  • 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

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@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: 14a2271d5d23e1efeb8b4cd381b17d0ce8fa569e.

No actionable bugs found in the changed areas. Following publisher instances through the existing announcement lifecycle is simpler and more accurate than reclassifying an unfinished track as clean EOF. The shared FFI test covers finished media, unfinished-track errors, catalog/broadcast closure, and replacement-run delivery across lite-07 and IETF 18/20/22; the documentation now matches the existing Rust/JS error mappings without adding API or wire behavior.

Verification limits: static GitHub review of the diff and relevant lifecycle/error-handling code only. I did not run tests or independently verify the reported local checks.

(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 new issues found.

Reviewed changes — Documentation, test, and generated-binding updates for how the FFI follows publisher runs; no production logic and no wire change.

  • Binding guides corrected — the c, cpp, dart, go, kt, py, and swift guides plus the Rust, Go, Python, and Swift wrapper docs no longer tell readers to take Unroutable as the publisher leaving. They state that a track withdrawn unfinished ends Unroutable (Internal over moq-transport, whose registry has no such code) and point at /concept/identity.
  • identity.md names FFI following — adds the bindings' follow(path)/available() alongside the Rust and JS forms, and says a track's error kind is not the restart signal.
  • Cross-protocol lifecycle test — server_client_run_end now runs over moq-lite-07-wip and moq-transport drafts 18, 20, and 22, proving a finished track ends cleanly, the catalog ends and closed() resolves, the unfinished track keeps its wire error (Unroutable on lite / Internal on IETF), the announcement End arrives, and a replacement run at the same path is handed out by the next available().
  • Regenerated Dart checksum — the close() docstring moves the UniFFI method checksum, and only that line changes in the generated file (generated Dart strips doc comments); consistent.
  • Quest removed — quest/m1/ffi-ietf-run-end.md and its README.md entry are deleted, following the maintainer-approved goal change.

I also checked for leftovers: no remaining occurrences of the removed "readers take as the publisher leaving" phrasing, no dangling references to the deleted quest, and the version strings used by the test are valid. The remaining publisher leaving comments (in moq-c/src/publish.rs, audio.rs:406, video.rs:546) describe the cause of Unroutable, not reader guidance, so they are accurate and in scope to leave.

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

@kixelated
kixelated enabled auto-merge (squash) November 1, 2026 18:55
@kixelated
kixelated disabled auto-merge November 3, 2026 05:40
…-end

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated added this pull request to the merge queue Nov 3, 2026

@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 a single merge of main (e1e37dd4e) into the branch, and its conflict resolution. The branch was far behind main, so the merge brought in a large body of upstream code; only the conflicts touch this PR's surface.

  • identity.md merged — retains main's newer "stream"/"broadcast" wording alongside this PR's moq-ffi follow(path)/available() sentence and the "not a restart signal" note.
  • Binding guides merged — each keeps main's newer Closing text (moq-lite-05 and newer wait for the peer to receive finished tails) with this PR's Ending cleanly text, so both intents survive.
  • rs/moq-ffi/src/test.rs adapted — frame.timestamp_us became micros(frame.timestamp) for main's timestamp API; the micros/us helpers and the non-optional MoqMediaFrame::timestamp make it consistent.
  • Dart checksum re-derived — moqbroadcastproducer_close moves 26090 → 21298, tracking only the close() docstring change (main did not churn the UniFFI checksum algorithm, so no mass rewrite).

Verified no conflict markers remain, no dangling reference to the deleted quest, and c/index.md still carries main's Discovery bullet. Test/just check claims were reviewed statically only.

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

Merged via the queue into main with commit 84a0c1e Nov 3, 2026
9 of 10 checks passed
@kixelated
kixelated deleted the quest/m1/ffi-ietf-run-end branch November 3, 2026 06:47
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