Thanks for your interest in contributing to moq-go — a Go implementation of
the Media over QUIC IETF drafts. This guide covers how the project is organized,
how to get a change building and tested, and what we look for in a pull request.
You'll need Go 1.27 or newer. Clone the repo and confirm the suite is green before you start:
go build ./... # build everything
go test ./... # full suite — hermetic, no fixtures or networkThe packages form a strict bottom-up dependency stack
(wire → message → session → relay), with track, loc, and msf as siblings
at the edges. CLAUDE.md has a fuller map of the architecture and where changes
belong; README.md is the example-driven guide to the public API.
Run these before opening a pull request:
go build ./... # build everything
go test ./... # full suite
go test -race ./pkg/moqt/session/... # race detector — for anything touching goroutines/streams
golangci-lint run # lint + format check (.golangci.yml)A few specifics:
- Formatting. The enabled formatters are
goimportsandgolines— format with those, not plaingofmt. - The
modernizelinter is enabled, and as of golangci-lint v2.13.0 the bundled snapshot includeserrorsastype, sogolangci-lint runcovers it. CI still runs the analyzer standalone at@latestto catch analyzers newer than the bundled snapshot (seeCLAUDE.mdfor the command). - Benchmarks live alongside the wire codec and session/relay hot paths. Run
make bench-quick(~5s) after a change to any of them and compareallocs/opagainstbenchmarks/baseline-go1.27.txt; that metric multiplies by the live-stream object rate, so an unexplained increase is a blocker.make bench(~4m30s) is the full suite and the only one whosens/opmeans anything. Seebenchmarks/README.md. - Coverage is measured, not enforced. Nothing in CI fails on a drop — the
per-package floors were removed deliberately.
make cover-totalprints the published figure: whole-suite coverage, measured with-coverpkgso a package is credited for every line the suite exercises rather than only for what its own tests reach. CI runs the same measurement on every push, puts the per-package table on the run's summary page, and publishes a line-by-line report to https://floatdrop.github.io/moq-go/, which is what the README badge links to.make cover-sitebuilds that report locally if you want to see it before pushing. - Because the gate is gone, the check is yours to make. CI will not tell you
that a slice of work landed untested. Look at the number before you push, and
make cover-gaps COVER_PKGS=./pkg/relay/to list the functions no test reaches, least-covered first — then decide per entry whether it wants a test or wants deleting. Coverage is a floor, not a target: it proves a line ran, never that anything asserted what it did.
This is wire-protocol code, so unit tests aren't the whole story: they round-trip through our own codec, which means a wire-encoding regression can pass every unit test yet break interop with other implementations. If your change touches the wire format or session/relay behavior, run the interop suite:
make interop # third-party test client against our relay, both transports
make interop-matrix # several clients, pass/skip/fail matrix
make interop-client # our client against a relaySee cmd/relay/README.md for targets and options; current results are tracked
in STATUS.md.
Protocol behavior is driven by specific, pinned IETF draft versions — currently
draft-ietf-moq-transport-20, draft-ietf-moq-loc-04,
draft-ietf-moq-msf-01, and draft-ietf-moq-cmsf-01 (see the links in
README.md). These are the source of truth when implementing or changing wire
behavior.
- Cite the relevant draft section in comments (e.g.
§10.1,§11.4.2) when you implement or change protocol behavior. - Pin to the draft version the code targets. The datatracker links track moving drafts and may be ahead of what's implemented, so confirm the section against the pinned version above.
- Centralize error and reset codes in
pkg/moqt/errors.goand use the named constant rather than a literal. - Transport behavior added to the
Conn/Streaminterface (pkg/moqt/session/conn.go) must land in all three adapters:quicconn,wtconn, andsessiontest.
- Keep changes focused; one logical change per pull request.
- Match the surrounding code's style, naming, and comment density.
- Add or update tests for the behavior you change — the suite is hermetic
(no network, no fixtures), so new tests should be too. Run a new test
against the unpatched code first and confirm it fails, then say so in the
description. A test that has never been seen red may be asserting something
the code already did; commit
fea80fcis the cautionary tale, a production outage that every existing test passed straight through. - Ask rather than assume. If the request or the draft is ambiguous in a way that changes what gets built, raise it before writing the code — a wrong assumption in wire-format code round-trips cleanly through our own codec and passes the whole suite.
- Make sure
go build ./...,go test ./..., andgolangci-lint runall pass. Run-raceand the interop suite when your change warrants it (see above). - Write a clear PR description: what changed, why, and which draft sections (if any) it follows.
This project is dual-licensed under Apache-2.0 and MIT.
Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion in the work, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.