From 0f64afcc21f05576c28ae8ac9020b77382c60672 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sat, 31 Oct 2026 02:07:59 -0700 Subject: [PATCH 1/6] chore(quest): claim quest/m0/ci/pr-feature-builds.md Quest-Claim: 1113b1530f71bddc From 7d953d52c1b294b2772f408027d15b107f19a65e Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sat, 31 Oct 2026 02:13:13 -0700 Subject: [PATCH 2/6] ci: select isolated feature builds for edited crates Co-Authored-By: GPT-6.1 Sol --- quest/m0/ci/README.md | 1 - quest/m0/ci/pr-feature-builds.md | 18 ------------------ 2 files changed, 19 deletions(-) delete mode 100644 quest/m0/ci/pr-feature-builds.md diff --git a/quest/m0/ci/README.md b/quest/m0/ci/README.md index 81a08f62f8c..127d113ad8e 100644 --- a/quest/m0/ci/README.md +++ b/quest/m0/ci/README.md @@ -141,4 +141,3 @@ The historical samples locate candidates; they are not controlled benchmarks. - [Release credentials off main](/quest/m0/ci/publish-release-only.md) - `publish` admits only `release` and release tags once its `workflow_run` publishers run on their tag - [Biome checks every file it can parse](/quest/m0/ci/biome-whole-repo.md) - Biome runs over the whole repository and main is clean under it - [workspace.test.py ignores the caller's gate filter](/quest/m0/ci/workspace-test-env.md) - the dispatcher test passes under any `just gate` module filter -- [PRs build the feature shapes they can break](/quest/m0/ci/pr-feature-builds.md) - a PR touching a crate's source runs that crate's isolated feature builds diff --git a/quest/m0/ci/pr-feature-builds.md b/quest/m0/ci/pr-feature-builds.md deleted file mode 100644 index eff9176572e..00000000000 --- a/quest/m0/ci/pr-feature-builds.md +++ /dev/null @@ -1,18 +0,0 @@ -# [S] PRs build the feature shapes they can break - -## Goal - -A PR that narrows which features compile a function's callers fails the merge gate when it leaves that function unused in some isolated feature build, instead of breaking Nightly's `rs features` job after it lands. - -## Plan - -Found by the agent on [moq-dev/moq#7380](https://github.com/moq-dev/moq/pull/7380): only Nightly runs every isolated `moq-tokio` feature build (`just rs tokio-features --all`), plus PRs that change feature definitions. [moq-dev/moq#7120](https://github.com/moq-dev/moq/pull/7120) fixed one unused helper and missed `Addr::with_url`, because nothing on the PR built `--features webrtc` alone. - -Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Select the isolated builds for a PR from the crates its diff touches, not only from changes to feature tables: a diff touching `rs/moq-tokio/src` runs moq-tokio's isolated shapes. Keep the gate's time budget from [the five-minute quest](/quest/m0/ci/five-minutes.md): if every shape is too slow, run them through `cargo check` (not tests), and only for crates whose source changed. Measure the added wall time on the queue runner and record it in the PR. - -Public API: none. Wire: none. - -## Related - -- [Gate each diff in under five minutes](/quest/m0/ci/five-minutes.md) - the time budget this must fit in -- [Unify dependency features](/quest/m0/ci/hakari.md) - shares the feature-shape plumbing From d1d5ac36b048563a5fe7acec6c3a30fc51903382 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sat, 31 Oct 2026 02:13:19 -0700 Subject: [PATCH 3/6] ci: run isolated feature shapes for directly edited crates Co-Authored-By: GPT-6.1 Sol --- rs/justfile | 4 ++-- sh/rs/media-features.sh | 32 ++++++++++++++++++++---------- sh/rs/select.sh | 25 +++++++++++++++++++++-- sh/rs/select.test.py | 44 ++++++++++++++++++++++++++++++++++++++--- sh/rs/tokio-features.sh | 2 +- 5 files changed, 89 insertions(+), 18 deletions(-) diff --git a/rs/justfile b/rs/justfile index cde4ade3c08..8059482336e 100644 --- a/rs/justfile +++ b/rs/justfile @@ -293,8 +293,8 @@ quic-features *args: fi bash sh/local.sh cargo clippy --locked -p moq-quic "${targets[@]}" --no-default-features -- -D warnings -# Compile media boundaries (--all isolates every mode) and prove expensive -# native/render dependencies are absent when their features are excluded. +# Compile media boundaries; --package CRATE isolates its modes, --all every mode. +# Prove expensive native/render dependencies are absent when excluded. [positional-arguments] media-features *args: sh/rs/media-features.sh "$@" diff --git a/sh/rs/media-features.sh b/sh/rs/media-features.sh index bda7d4b3a8c..54fff9a75cd 100755 --- a/sh/rs/media-features.sh +++ b/sh/rs/media-features.sh @@ -6,19 +6,31 @@ if [[ "${GITHUB_ACTIONS:-}" != true && "${LOCAL_SUPERVISED:-}" != 1 ]]; then fi all=0 +isolated=() if [[ $# == 1 && $1 == --all ]]; then all=1 -elif [[ $# != 0 ]]; then - echo 'usage: just rs media-features [--all]' >&2 - exit 2 + isolated=(moq-video moq-audio moq-transcode) +else + while (($#)); do + if [[ $# -lt 2 || $1 != --package || ! $2 =~ ^moq-(video|audio|transcode)$ ]]; then + echo 'usage: just rs media-features [--all | --package CRATE ...]' >&2 + exit 2 + fi + isolated+=("$2") + shift 2 + done fi targets=(--tests) if ((all)); then targets=(--all-targets); fi +isolates() { + [[ " ${isolated[*]} " == *" $1 "* ]] +} + check() { echo "media features: cargo check $*" - cargo check --locked "${targets[@]}" "$@" + CARGO_BUILD_WARNINGS=deny cargo check --locked "${targets[@]}" "$@" } # Resolved for Linux, the platform every asserted dependency targets: moq-nvenc @@ -55,13 +67,13 @@ for crate in openh264 openh264-sys2 wgpu cpal; do done cargo nextest run --locked -p moq-video --no-default-features -if ((all)); then check -p moq-video --no-default-features --features openh264; fi +if isolates moq-video; then check -p moq-video --no-default-features --features openh264; fi software=$(tree -p moq-video --no-default-features --features openh264) require_crate "$software" openh264 "moq-video software-only" require_crate "$software" openh264-sys2 "moq-video software-only" forbid_crate "$software" wgpu "moq-video software-only" -if ((all)); then check -p moq-video --no-default-features --features nvidia; fi +if isolates moq-video; then check -p moq-video --no-default-features --features nvidia; fi native=$(tree -p moq-video --no-default-features --features nvidia) require_crate "$native" moq-nvenc "moq-video native-only" for crate in openh264 openh264-sys2 wgpu; do @@ -79,7 +91,7 @@ for crate in openh264 openh264-sys2 wgpu; do done cargo nextest run --locked -p moq-video --no-default-features --features vpx -E 'test(/::vpx::/)' -if ((all)); then check -p moq-video --no-default-features --features render; fi +if isolates moq-video; then check -p moq-video --no-default-features --features render; fi render=$(tree -p moq-video --no-default-features --features render) require_crate "$render" wgpu "moq-video rendering" for crate in openh264 openh264-sys2; do @@ -92,12 +104,12 @@ for crate in openh264 openh264-sys2 wgpu moq-nvenc; do forbid_crate "$transcode_minimal" "$crate" "moq-transcode minimal" done -if ((all)); then check -p moq-transcode --no-default-features --features openh264; fi +if isolates moq-transcode; then check -p moq-transcode --no-default-features --features openh264; fi transcode_software=$(tree -p moq-transcode --no-default-features --features openh264) require_crate "$transcode_software" openh264 "moq-transcode software-only" forbid_crate "$transcode_software" wgpu "moq-transcode software-only" -if ((all)); then check -p moq-transcode --no-default-features --features nvidia; fi +if isolates moq-transcode; then check -p moq-transcode --no-default-features --features nvidia; fi transcode_native=$(tree -p moq-transcode --no-default-features --features nvidia) require_crate "$transcode_native" moq-nvenc "moq-transcode native-only" for crate in openh264 openh264-sys2 wgpu; do @@ -108,7 +120,7 @@ check -p moq-audio --no-default-features --features pipewire audio_host=$(tree -p moq-audio --no-default-features --features pipewire) forbid_crate "$audio_host" cpal "moq-audio host-only" -if ((all)); then check -p moq-audio --no-default-features --features capture,pipewire; fi +if isolates moq-audio; then check -p moq-audio --no-default-features --features capture,pipewire; fi audio_capture=$(tree -p moq-audio --no-default-features --features capture,pipewire) require_crate "$audio_capture" cpal "moq-audio PipeWire capture" diff --git a/sh/rs/select.sh b/sh/rs/select.sh index aa5eb034329..d9fca7971d8 100755 --- a/sh/rs/select.sh +++ b/sh/rs/select.sh @@ -118,6 +118,13 @@ wants() { [[ "$packages" == ALL ]] || grep -qwE "$1" <<<"$names" } +# Isolated permutations protect the edited crate's own feature gates. A +# dependency edit still runs its consumers' boundary checks without rebuilding +# every permutation of every consumer. Shared build inputs select them all. +changed() { + [[ "$packages" == ALL ]] || grep -qE "^rs/$1/(src/|tests/|examples/|Cargo\\.toml$|build\\.rs$)" "$list" +} + case "$packages" in "") echo "rs: no crates affected; skipping." @@ -156,8 +163,22 @@ case "$action" in if wants '(kio|moq-net)'; then just rs loom --no-run; fi if wants moq-net; then just rs net-features; fi if wants moq-quic; then just rs quic-features; fi - if wants moq-tokio; then just rs tokio-features; fi - if wants '(moq-video|moq-audio|moq-transcode)'; then just rs media-features; fi + if wants moq-tokio; then + isolated=() + if changed moq-tokio; then isolated=(--all); fi + just rs tokio-features "${isolated[@]}" + fi + if wants '(moq-video|moq-audio|moq-transcode)'; then + isolated=() + if [[ "$packages" == ALL ]]; then + isolated=(--all) + else + for crate in moq-video moq-audio moq-transcode; do + if changed "$crate"; then isolated+=(--package "$crate"); fi + done + fi + just rs media-features "${isolated[@]}" + fi # moq-wasm's crate root is `#![cfg(target_arch = "wasm32")]`, so only a # wasm32 pass checks it. moq-mux, moq-ffi, and moq-clock ride along in that pass, and # nothing depends on moq-ffi, so each has to reach it on its own. diff --git a/sh/rs/select.test.py b/sh/rs/select.test.py index 24be8a33802..663a443ac13 100644 --- a/sh/rs/select.test.py +++ b/sh/rs/select.test.py @@ -72,16 +72,34 @@ def test_queue_checks_selected_feature_shapes(self): self.assertIn("rs feature-shapes --workspace --exclude moq-net-fuzz --exclude moq-quic-fuzz", calls) def test_boundary_checks_follow_reverse_dependencies(self): - self.assertIn("rs tokio-features", self.selected("rs/moq-tokio/src/lib.rs")) + self.assertIn("rs tokio-features", self.selected("rs/moq-sock/src/lib.rs")) self.assertIn("rs net-features", self.selected("rs/moq-net/src/lib.rs")) self.assertIn("rs quic-features", self.selected("rs/moq-quic/src/lib.rs")) - self.assertIn("rs media-features", self.selected("rs/moq-video/src/lib.rs")) + self.assertIn("rs media-features", self.selected("rs/moq-mux/src/lib.rs")) calls = self.selected("rs/kio/src/lib.rs") for owner in ("tokio", "net", "quic", "media"): self.assertIn(f"rs {owner}-features", calls) calls = self.selected("rs/moq-stats/benches/producer_tick.rs") self.assertFalse(any(call.endswith("-features") for call in calls), calls) + def test_isolated_shapes_follow_their_own_source_and_manifest(self): + for action in ("check", "check-test", "lint"): + for owner in ("moq-tokio", "moq-video", "moq-audio", "moq-transcode"): + for source in ("src/added.rs", "Cargo.toml", "build.rs"): + with self.subTest(action=action, owner=owner, source=source): + calls = self.selected(f"rs/{owner}/{source}", action=action) + expected = ("rs tokio-features --all" if owner == "moq-tokio" + else f"rs media-features --package {owner}") + self.assertIn(expected, calls) + calls = self.selected("rs/moq-video/src/added.rs", "rs/moq-audio/src/added.rs") + self.assertIn("rs media-features --package moq-video --package moq-audio", calls) + calls = self.selected("Cargo.lock") + self.assertIn("rs tokio-features --all", calls) + self.assertIn("rs media-features --all", calls) + for path in ("rs/moq-video/benches/added.rs", "rs/moq-tokio/README.md"): + calls = self.selected(path) + self.assertFalse(any("-features --" in call for call in calls), calls) + def test_loom_compiles_only_when_its_model_changes(self): for action in ("check", "check-test", "lint"): for path in ("rs/kio/src/lib.rs", "rs/moq-net/tests/loom.rs", "Cargo.lock"): @@ -259,7 +277,7 @@ def trace(self, owner, *args, net_features="", expected_returncode=0): result = subprocess.run(["bash", f"sh/rs/{owner}-features.sh", *args], cwd=ROOT, env=env, capture_output=True, text=True) self.assertEqual(result.returncode, expected_returncode, result.stdout + result.stderr) - return calls.read_text().splitlines() + return calls.read_text().splitlines() if calls.exists() else [] def test_net_keeps_model_and_browser_contracts(self): boundary = self.trace("net") @@ -299,6 +317,26 @@ def test_media_keeps_dependency_contracts_and_vpx_tests(self): self.assertEqual([call for call in boundary if not call.startswith("check ")], [call for call in exhaustive if not call.startswith("check ")]) + def test_media_isolates_only_the_edited_owners(self): + boundary = self.trace("media") + exhaustive = self.trace("media", "--all") + for owners in (("moq-video",), ("moq-transcode",), ("moq-audio",), + ("moq-video", "moq-audio")): + with self.subTest(owners=owners): + calls = self.trace("media", *(arg for owner in owners for arg in ("--package", owner))) + # Keep all dependency contracts and tests, adding only the + # edited owners' checks without Nightly's bench/example targets. + expected = [call.replace(" --all-targets ", " --tests ") for call in exhaustive + if not call.startswith("check ") or + call.replace(" --all-targets ", " --tests ") in boundary or + any(f" -p {owner} " in call for owner in owners)] + self.assertEqual(calls, expected) + + def test_media_rejects_unsupported_selections(self): + for args in (("--package",), ("--package", "moq-net"), ("--all", "--package", "moq-video")): + with self.subTest(args=args): + self.assertEqual(self.trace("media", *args, expected_returncode=2), []) + CARGO_STUB = r"""#!/usr/bin/env bash printf '%s\n' "$*" >> "$FEATURE_CALLS" diff --git a/sh/rs/tokio-features.sh b/sh/rs/tokio-features.sh index 2845816a04e..c066a613503 100755 --- a/sh/rs/tokio-features.sh +++ b/sh/rs/tokio-features.sh @@ -34,7 +34,7 @@ if leaked=$(grep -E '^(moq-net|reqwest|notify|quinn[a-z-]*|noq[a-z-]*|web-transp sort -u <<<"$leaked" >&2 exit 1 fi -# Only a feature-owner edit needs every isolated permutation on a PR. Shared +# Source and manifest edits select every isolated permutation on a PR. Shared # dependencies still compile at both extremes; Nightly runs every permutation. if ((all)); then cargo clippy --locked -p moq-tokio --no-default-features --features quic,aws-lc-rs -- -D warnings From b84028cd40feece617d04922a2cee96502f5c4a0 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sat, 31 Oct 2026 02:19:13 -0700 Subject: [PATCH 4/6] chore(quest): retain feature-build validation work Co-Authored-By: GPT-6.1 Sol --- quest/m0/ci/pr-feature-builds.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 quest/m0/ci/pr-feature-builds.md diff --git a/quest/m0/ci/pr-feature-builds.md b/quest/m0/ci/pr-feature-builds.md new file mode 100644 index 00000000000..2c7a2bda5c0 --- /dev/null +++ b/quest/m0/ci/pr-feature-builds.md @@ -0,0 +1,20 @@ +# [S] PRs build the feature shapes they can break + +## Goal + +A PR that narrows which features compile a function's callers fails the merge gate when it leaves that function unused in some isolated feature build, instead of breaking Nightly's `rs features` job after it lands. + +## Plan + +The source-based selection is implemented in [moq-dev/moq#7433](https://github.com/moq-dev/moq/pull/7433). Remaining before completion: pass the local Rust gate, measure the added warm-cache wall time, and finish the local other-vendor review. Keep the quest until that validation is complete. The newly selected Tokio WebRTC shape needs the existing fix in [moq-dev/moq#7380](https://github.com/moq-dev/moq/pull/7380). + +Found by the agent on [moq-dev/moq#7380](https://github.com/moq-dev/moq/pull/7380): only Nightly runs every isolated `moq-tokio` feature build (`just rs tokio-features --all`), plus PRs that change feature definitions. [moq-dev/moq#7120](https://github.com/moq-dev/moq/pull/7120) fixed one unused helper and missed `Addr::with_url`, because nothing on the PR built `--features webrtc` alone. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Select the isolated builds for a PR from the crates its diff touches, not only from changes to feature tables: a diff touching `rs/moq-tokio/src` runs moq-tokio's isolated shapes. Keep the gate's time budget from [the five-minute quest](/quest/m0/ci/five-minutes.md): if every shape is too slow, run them through `cargo check` (not tests), and only for crates whose source changed. Measure the added wall time on the queue runner and record it in the PR. + +Public API: none. Wire: none. + +## Related + +- [Gate each diff in under five minutes](/quest/m0/ci/five-minutes.md) - the time budget this must fit in +- [Unify dependency features](/quest/m0/ci/hakari.md) - shares the feature-shape plumbing From 131679f591d9db77a5bb75ff33d32848d319964b Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sat, 31 Oct 2026 02:19:24 -0700 Subject: [PATCH 5/6] chore(quest): keep pending validation reachable Co-Authored-By: GPT-6.1 Sol --- quest/m0/ci/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/quest/m0/ci/README.md b/quest/m0/ci/README.md index 127d113ad8e..81a08f62f8c 100644 --- a/quest/m0/ci/README.md +++ b/quest/m0/ci/README.md @@ -141,3 +141,4 @@ The historical samples locate candidates; they are not controlled benchmarks. - [Release credentials off main](/quest/m0/ci/publish-release-only.md) - `publish` admits only `release` and release tags once its `workflow_run` publishers run on their tag - [Biome checks every file it can parse](/quest/m0/ci/biome-whole-repo.md) - Biome runs over the whole repository and main is clean under it - [workspace.test.py ignores the caller's gate filter](/quest/m0/ci/workspace-test-env.md) - the dispatcher test passes under any `just gate` module filter +- [PRs build the feature shapes they can break](/quest/m0/ci/pr-feature-builds.md) - a PR touching a crate's source runs that crate's isolated feature builds From d80ff15d67dcf2acaebbc6d02f2fe18703207a5e Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sat, 31 Oct 2026 13:05:04 -0700 Subject: [PATCH 6/6] chore(quest): finish pr-feature-builds Co-Authored-By: Claude Opus 5.5 --- quest/m0/ci/README.md | 1 - quest/m0/ci/pr-feature-builds.md | 20 -------------------- 2 files changed, 21 deletions(-) delete mode 100644 quest/m0/ci/pr-feature-builds.md diff --git a/quest/m0/ci/README.md b/quest/m0/ci/README.md index a9de7b9f6d9..09d87707907 100644 --- a/quest/m0/ci/README.md +++ b/quest/m0/ci/README.md @@ -140,4 +140,3 @@ The historical samples locate candidates; they are not controlled benchmarks. - [Release credentials off main](/quest/m0/ci/publish-release-only.md) - `publish` admits only `release` and release tags once its `workflow_run` publishers run on their tag - [Biome checks every file it can parse](/quest/m0/ci/biome-whole-repo.md) - Biome runs over the whole repository and main is clean under it - [workspace.test.py ignores the caller's gate filter](/quest/m0/ci/workspace-test-env.md) - the dispatcher test passes under any `just gate` module filter -- [PRs build the feature shapes they can break](/quest/m0/ci/pr-feature-builds.md) - a PR touching a crate's source runs that crate's isolated feature builds diff --git a/quest/m0/ci/pr-feature-builds.md b/quest/m0/ci/pr-feature-builds.md deleted file mode 100644 index 2c7a2bda5c0..00000000000 --- a/quest/m0/ci/pr-feature-builds.md +++ /dev/null @@ -1,20 +0,0 @@ -# [S] PRs build the feature shapes they can break - -## Goal - -A PR that narrows which features compile a function's callers fails the merge gate when it leaves that function unused in some isolated feature build, instead of breaking Nightly's `rs features` job after it lands. - -## Plan - -The source-based selection is implemented in [moq-dev/moq#7433](https://github.com/moq-dev/moq/pull/7433). Remaining before completion: pass the local Rust gate, measure the added warm-cache wall time, and finish the local other-vendor review. Keep the quest until that validation is complete. The newly selected Tokio WebRTC shape needs the existing fix in [moq-dev/moq#7380](https://github.com/moq-dev/moq/pull/7380). - -Found by the agent on [moq-dev/moq#7380](https://github.com/moq-dev/moq/pull/7380): only Nightly runs every isolated `moq-tokio` feature build (`just rs tokio-features --all`), plus PRs that change feature definitions. [moq-dev/moq#7120](https://github.com/moq-dev/moq/pull/7120) fixed one unused helper and missed `Addr::with_url`, because nothing on the PR built `--features webrtc` alone. - -Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Select the isolated builds for a PR from the crates its diff touches, not only from changes to feature tables: a diff touching `rs/moq-tokio/src` runs moq-tokio's isolated shapes. Keep the gate's time budget from [the five-minute quest](/quest/m0/ci/five-minutes.md): if every shape is too slow, run them through `cargo check` (not tests), and only for crates whose source changed. Measure the added wall time on the queue runner and record it in the PR. - -Public API: none. Wire: none. - -## Related - -- [Gate each diff in under five minutes](/quest/m0/ci/five-minutes.md) - the time budget this must fit in -- [Unify dependency features](/quest/m0/ci/hakari.md) - shares the feature-shape plumbing