Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ runner-vm:

# Lint GitHub Actions workflows, their security, their alert coverage, that every step runs a recipe, and that every opt-in test has a caller.
check:
python3 sh/version.py
actionlint
zizmor --offline --quiet .github
sh/gh/alert.sh check-coverage
Expand All @@ -33,6 +34,7 @@ check:

# Tests of repository tooling; every `just check` and `just ci` runs them.
test:
python3 sh/version.test.py
python3 sh/local.test.py
python3 test/smoke/runner.test.py
python3 sh/coverage.test.py
Expand Down
39 changes: 36 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ For an advisory against Quinn, follow the advisory triage in [`rs/moq-quic/READM
`main` is the trunk; `release` is what ships, and release-plz and every branch-triggered publish run only there.

- Smoke clients call only the latest published APIs. A release cut updates `test/smoke/clients` to the APIs being published, then verifies the affected smoke lanes.
- A release is cut by hand: a PR merging `main` into `release`, with a merge commit.
- A release is cut by hand: a PR merging `main` into `release`, with a merge commit. After the pre-cut merge of `release` into `main`, run `just version 0.22.0` on `main` for the first shared cut, or the next shared minor for later cuts. Commit the resulting versions and lockfiles before opening the cut PR. Run `just version` to check them.
- A breaking cut first writes, on `main`, each changed package's version-transition section in its `MIGRATE.md` (`## 0.3 -> 0.4`) from the migration audit: the `!` subjects since the `release` merge base, each of those PRs' Impact section, and the API diff.
- Suggest a `release` backport for any serious bug fix (crash, security, data loss, broken interop), whether found on `main` or reported against a release. Land it on `main` first, then port it onto `release` as a separate PR that keeps the published API, so it ships as a patch. The Release gate's API diff (`just api-diff --deny`) fails a backport that changes one; on the cut PR, whose head is `main`, it only reports.
- A bench-only backport ports `main`'s benchmarks onto `release`, so a pairing against `release` (`just bench origin/release`) compares them. It carries only benchmarks and their harness: bench targets, bench-only dev-dependencies, and `bench/`. It changes no shipped code, and it shims locally any API that `release` lacks, keeping each case's ID the same as on `main`. A case that measures code `release` doesn't have is left out, and the PR lists it. Merging one makes release-plz propose patch bumps for the crates it touches, though no shipped code changed.
Expand All @@ -101,12 +101,45 @@ For an advisory against Quinn, follow the advisory triage in [`rs/moq-quic/READM

# Versions

Releases are cut separately; bump only when asked. Each package's version lives in one place:
Releases are cut separately; bump only when asked. Every published MoQ package
shares one minor across ecosystems, including unchanged packages at each cut.
The first shared cut is `0.22.0`; later cuts advance the minor together. Patch
versions stay independent per package, including Rust: release-plz has no
`version_group`.

`kio`, `kio-macros`, `qmux`, and npm `@moq/qmux` keep their own version lines;
`@moq/signals` joins the shared minor. New experiments start at `0.0.x`.
A published library's normal or build dependency on an experimental package
in the same ecosystem promotes that package into the shared minor, including
optional and target-specific dependencies. Dev dependencies and binary-only
Rust dependents do not promote it. Unpublished/private packages are excluded.
Thus `moq-uring` joins through the relay library, while `moq-archive` stays
experimental through the binary-only CLI and a dev dependency.

`just version 0.22.0` sets eligible versions and workspace lock entries without
resolving unrelated dependencies. It also advances Python, Dart, and Kotlin
bindings requirements to the cut's minor while allowing independent patches.
Rust requirements are updated by
`release-plz set-version`; disposable changelogs keep published history intact.
The recipe records the shared minor and named exemptions in `version.json`.
`just version` checks it on every `just check` and `just ci`; enforcement is
disabled while its minor is `null`, until the cut runs the recipe. Once active,
the check rejects minor drift, unpromoted dependencies, stale exemptions, and
mismatched npm/Python lock entries, and stale bindings requirements.

The nonpublishing fixture in `sh/version.test.py` verifies that release-plz's
`update` engine, also used by `release-pr`, preserves a manually set unpublished
minor despite breaking commits. `just gh test` runs it on every check, so a
release-plz upgrade that breaks the cut fails before it lands. No publish, tag,
or release workflow runs as part of `just version`.

Each package's version lives in one place:

- **Rust**: release-plz owns crate versions and Rust dependency requirements.
- **JavaScript**: `js/*/package.json` packages with a `scripts.release` entry (skip private ones), plus the matching workspace version in `bun.lock`.
- **Python**: `py/moq-rs/pyproject.toml`, plus the matching `moq-rs` entry in the root `uv.lock`; `py/moq-ffi` follows the `moq-ffi-v*` tag and Rust crate.
- **Swift**: `swift/VERSION`. **Kotlin**: `moq.version` in `kt/gradle.properties`. **Dart**: `version` in `dart/moq/pubspec.yaml`. Their FFI counterparts track the Rust crate.
- **Go**: `go/wrapper/VERSION` holds a human-owned `MAJOR.MINOR` line; CI derives the patch, so only edit it for a breaking API. Leave the placeholder FFI version in `go.mod` alone.
- **Go**: `go/wrapper/VERSION` holds a human-owned `MAJOR.MINOR` line; CI derives the independent patch; the shared cut sets the minor. Leave the placeholder FFI version in `go.mod` alone.
- **C**: `c/moq/VERSION`, human-owned; a `c-v<version>` tag releases it.
- **C++**: `cpp/moq/VERSION`, human-owned; a `cpp-v<version>` tag releases it.
- **OBS**: `cpp/obs/VERSION`, human-owned; each C++ release also cuts `obs-moq-v<version>`, and refuses one whose version already names an earlier release.
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ fix $BASE="":
oss-scanner *args:
sh/oss-scanner.sh "$@"

# Set every eligible package to one minor at a release cut; omit to check.
version TARGET="":
python3 sh/version.py {{ if TARGET == "" { "" } else { quote(TARGET) } }}

# Build the packages.
build:
just js build
Expand Down
14 changes: 6 additions & 8 deletions kt/moq/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
// `dev.moq.*` typealiases and adds idiomatic helpers (a `connect()` facade,
// Flow extensions, AutoCloseable ergonomics).
//
// Versioning is INDEPENDENT of the crate: `-Pmoq.version` (default in
// gradle.properties), bumped by hand. release-kt-lib.yml publishes a new
// version only when that property changes. The dependency on `moq-ffi` is a
// floating range so consumers transitively pick up new bindings patches
// without this wrapper being re-cut. See MOQ_FFI_RANGE below.
// `-Pmoq.version` (default in gradle.properties) owns the wrapper version.
// Shared cuts set its minor; patches are independent of the bindings.
// release-kt-lib.yml publishes when that property changes. MOQ_FFI_RANGE
// lets consumers pick up bindings patches without a wrapper re-release.
//
// Local builds and CI resolve `moq-ffi` from the sibling project via the
// dependency substitution below, so tests run against freshly-built bindings.
Expand Down Expand Up @@ -58,9 +57,8 @@ val generateVersion =
}
}

// Compatible-patch range for the bindings: 0.4.3 (the WebSocket fallback
// setters) or newer, never 0.5.0. Published into the wrapper's POM so consumers
// float to the newest bindings patch.
// The published POM admits newer bindings patches within the cut's minor.
// Shared cuts move this range; raise its floor when newer FFI API is required.
val MOQ_FFI_RANGE = "[0.4.3,0.5)"

val androidEnabled = providers.gradleProperty("android.enabled").orNull == "true"
Expand Down
16 changes: 7 additions & 9 deletions py/moq-rs/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ packages = ["moq"]
[project]
name = "moq-rs"
# Distribution is `moq-rs` (the `moq` name is taken on PyPI); the import name
# stays `moq`. Versioned independently of the moq-ffi crate: bump this by hand
# in a PR. On merge to release, release-py.yml publishes to PyPI if this version
# isn't already there (no tag needed; the registry is the gate).
# Starts at 0.3.0 to open a new line above the old lockstep 0.2.x releases that
# bundled the bindings.
# stays `moq`. Shared cuts set the minor; patches are independent of moq-ffi.
# On merge to release, release-py.yml publishes this version if it is not on
# PyPI yet (no tag needed; the registry is the gate).
version = "0.6.0"
description = "Media over QUIC: real-time pub/sub with built-in caching, fan-out, and prioritization, with a Pythonic API. Import as `moq`."
readme = "README.md"
Expand All @@ -28,10 +26,10 @@ classifiers = [
"Topic :: Multimedia :: Sound/Audio",
"Topic :: Multimedia :: Video",
]
# Compatible-release pin: floats to the latest 0.5.x moq-ffi patch without a
# wrapper re-release. A moq-ffi minor bump (0.6) is the only thing that forces
# bumping this constraint. Floor is 0.5.0: no 0.4.x has the instance, range, and
# demand API this wrapper calls.
# Compatible-release pin: consumers float to newer bindings patches without
# a wrapper re-release. Shared cuts move the floor to the new minor; raise
# its patch when the wrapper requires newer FFI API. Floor is 0.5.0: no 0.4.x
# has the instance, range, and demand API this wrapper calls.
dependencies = ["moq-ffi ~= 0.5.0"]

[project.urls]
Expand Down
1 change: 0 additions & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ per-frame CPU growth since #5926.
- [HLS export follows a resolution change](/quest/m1/hls-reinit-same-codec.md) - a decoder-identity change under the same codec string starts a new init and segment tag
- [moq-quic deletes its remaining 0-RTT code](/quest/m1/quic-zero-rtt-leftovers.md) - only the code that drops a 0-RTT packet remains, with a test
- [Gate the no-em-dash rule](/quest/m1/em-dash-gate.md) - `just check` fails on an em dash in any tracked text file
- [Every package shares one minor version](/quest/m1/shared-version.md) - a recipe sets every published package to one minor at the cut (next: 0.22.0), and `just check` enforces it and the `0.0.x` rule for experiments

## Related

Expand Down
96 changes: 0 additions & 96 deletions quest/m1/shared-version.md

This file was deleted.

6 changes: 3 additions & 3 deletions quest/m2/release/cut.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ package versions, and the release-triggered publication jobs complete.
Follow CONTRIBUTING.md's release procedure: merge release into main first,
then main into release using merge commits. Confirm the maintainer's cut
instruction before executing it. Right after merging release into main, set
every eligible package to the next shared minor with the recipe from
[one minor version](/quest/m1/shared-version.md) (`0.22.0` for this cut, then
one minor up per cut), including packages that did not change. Rust
every eligible package with `just version 0.22.0` for this cut, then one
minor up per cut, following [Versions](/CONTRIBUTING.md#versions), including
packages that did not change. Rust
publication remains owned by release-plz. This supersedes keeping `moq-rs` at
its unpublished 0.6.0 (decided 2026-11-01). Run candidate
compatibility, draft, package, and all-language interop checks, and verify
Expand Down
1 change: 1 addition & 0 deletions sh/dispatch-query.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def stub_ci(self, just=""):
"bun",
"python3",
"cargo",
"release-plz",
"jq",
"shellcheck",
"shfmt",
Expand Down
2 changes: 1 addition & 1 deletion sh/dispatch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ module drafts '^(drafts/|sh/drafts/|doc/\.vitepress/drafts\.ts$)' \
module markdown '' \
tools='bun' lane=bun fix
module gh '' \
tools='actionlint zizmor bun python3 cargo clippy-driver dpkg-deb nfpm rpm' lane=bun
tools='actionlint zizmor bun python3 cargo release-plz clippy-driver dpkg-deb nfpm rpm' lane=bun
# The drill mutations patch Rust source, so a Rust change can move the code
# they target. Only nightly runs the drills; this catches a stale patch.
module drill '^(rs/|test/drill/)'
Expand Down
Loading
Loading