11. Contributing & Releases¶
This page is the short routing layer for contributors. The operational source of truth remains the repository docs.
11.1. Start With¶
- Contributing guide: CONTRIBUTING.md
- Spec index: spec/README.md
- Compatibility matrix: compatibility-matrix.md
11.2. Contribution Flow¶
- Open an issue for non-trivial work.
- Branch from
develop, open the feature PR todevelop, and run the relevant flavor checks locally. - Promote
developto protectedmainonly through a separate maintainer PR. - Direct pushes to
developandmainare blocked. Both PR stages require the always-present conformance, C#, Python, TypeScript, Swift, Rust, docs, examples, security, and spec-discipline aggregate checks against the latest target branch. Maintainer review is expected when another reviewer is available; the single-collaborator repository does not require self-approval. - For behavior changes, start in
spec/and follow the ADR discipline. - Keep all supported flavors visible when a change affects shared behavior.
11.3. Validation Entry Points¶
The main local check families are:
- C#: restore, build, test, and
dotnet format - Python:
uv run pytest,ruff, andmypy --strict - TypeScript:
npm ci, fixture sync, typecheck, lint, build, and test - Swift:
swift buildandswift test - Rust:
cargo fmt --check,cargo clippy --all-targets -- -D warnings, andcargo test --locked - Repo-wide coverage and example-contract tools from
CONTRIBUTING.md
Use the canonical command list in CONTRIBUTING.md instead of copying commands from this page into long-lived process docs.
For reproducible Python checks, set UV_PYTHON to the intended supported
version (3.10–3.14) before locked sync and subsequent uv commands. Installing
Python alone does not select it for an existing compatible environment. CI
selects the matrix version and logs the requested version, actual major/minor,
executable, and virtualenv prefix before checks. Later checks compare against
the captured executable. Isolated wheel, sdist, and tool environments keep
their own identities and must use the same requested version. The Ubuntu 3.10
cell deliberately seeds a 3.14 environment and verifies sync repairs it.
11.3.1. Reproduce Controlled Interleavings¶
Run the selected regressions from the repository root with the same locked inputs used by CI:
dotnet test langs/csharp/tests/VMx.Tests/VMx.Tests.csproj -c Release \
--filter 'FullyQualifiedName~Foreign_Dispose_Waits'
UV_PYTHON=3.12 uv --directory langs/python run python \
../../tools/check-python-interpreter.py --venv .venv -- \
-m pytest tests/unit/components/test_lifecycle_race.py \
-k foreign_dispose_waits
cargo test --locked --all-features --manifest-path langs/rust/Cargo.toml
swift test --package-path langs/swift \
--filter AsyncRelayCommandTests.testCmd012ImmediateCancelDuringAdmissionIsNeverLost
The Python command assumes the locked environment has already been synchronized
with UV_PYTHON=3.12 uv --directory langs/python sync --locked --all-extras.
CI additionally repeats the Linux commands three times normally and three times
under taskset on a CPU returned by os.sched_getaffinity(0). Do not substitute
CPU 0: hosted runners may exclude it. The Swift mutation experiment belongs in
the macOS workflow because it requires supported Xcode XCTest; local machines
without an accepted Xcode license cannot supply that evidence.
TypeScript's Vitest global setup completes npm run build, including fixture
and schema synchronization, before starting test workers. Each watch rerun
waits for the preceding run, rebuilds, and invalidates generated modules;
generated writes cannot themselves trigger reruns. This also supports direct
npx vitest and coverage runs. Automatic test selection remains import-based;
manually rerun after changing canonical spec inputs outside that graph. Avoid
running a separate build against the same package while tests are active.
See Controlled Concurrency Interleavings for the evidence rules and the Concurrency Test Audit for exact test locations and limitations.
11.3.2. Checked Documentation Snippets¶
A fence in docs/content or a package README can be tied to code that CI runs.
Put the code in a test or example, mark it, and put a marker comment directly
above the fence:
<!-- checked-snippet: path/to/source.ts --> whole file
<!-- checked-snippet: path/to/source.ts#region-name --> one region
A region is every line between docs-snippet:start region-name and
docs-snippet:end region-name comments in the source, dedented. A source may
split one region into several start/end pairs, for example around a namespace
line the page leaves out; the parts are joined in order. make docs-check
fails when a fence differs from its source, including in the generated site
and wiki pages.
11.3.3. Coverage Floors¶
Every flavor gates CI on a coverage floor over its library sources:
| Flavor | Gate | Metrics |
|---|---|---|
| C# | tools/check-cobertura-threshold.py in csharp.yml |
line, branch |
| Python | fail_under in langs/python/pyproject.toml |
combined line and branch |
| TypeScript | coverage.thresholds in langs/typescript/vitest.config.ts |
statements, branches, functions, lines |
| Rust | tools/check-coverage-floor.py with tools/coverage-floors.json |
lines, regions, functions |
| Swift | tools/check-coverage-floor.py with tools/coverage-floors.json |
lines, regions, functions |
Each Rust and Swift floor is the lowest figure measured across repeated runs of
the baseline commit, truncated to two decimals, so only run-to-run variation in
scheduling-dependent paths is tolerated. The file records the commit, toolchain,
every measured run, and each source file's uncovered lines, so a failure names
the files that lost coverage and lists their unexecuted line ranges.
tools/compare-coverage-runs.py compares the llvm-cov exports of repeated runs
and names every line that some runs execute and others do not. Cover such a
line with a deterministic test rather than lowering a floor. Raise a floor when
coverage improves. Never lower one to make a change pass: if deleting
code legitimately lowers coverage, explain it in the pull request and record
the new baseline. Percentages are not comparable across flavors, because each
tool counts differently.
11.4. Spec Discipline¶
Two repo rules matter most:
- semantic changes under
spec/require a matching ADR unless the change is in an exempt path - new conformance IDs require matching stubs in every catalog-complete flavor
Those rules are enforced in CI and described in the contributing guide and repository automation.
11.5. Release Shape¶
Flavor packages version independently and release from verified main commits
through <lang>-vX.Y.Z operational tags. C# uses that form for core and
package-specific csharp-notifications-vX.Y.Z and
csharp-dependency-injection-vX.Y.Z tags for its companions, so independent
versions cannot collide. The spec uses spec-vX.Y.Z; Swift also pairs its
operational tag with the semantic vX.Y.Z tag required by SwiftPM.
Registry-backed channels are protected by environment approval, OIDC,
pre-publish checks of the exact locally built artifact, public-artifact checks,
and fresh-consumer verification. The Python channel pins its isolated PEP 517
backend and installs/smokes the wheel from dist/ before the irreversible PyPI
action, then repeats the consumer check from the public registry.
11.5.1. Release Checklist¶
Use this sequence for every flavor so the repository, site, and wiki carry the same actionable procedure:
- Start from a clean, freshly fetched
origin/main; never release fromdevelopor an unmerged feature branch. - Confirm the source/package version and minimum-spec declaration agree, and prove both the intended operational tag and public registry version are unused.
- Run the flavor's complete release gate and build the exact package locally. Inspect that artifact, install it into a clean consumer, and retain the check evidence before creating a tag.
- Create the immutable
<lang>-vX.Y.Ztag from the verifiedmaincommit. For Swift, create the paired semanticvX.Y.Ztag from the same commit. For C# companion packages, use their package-specific tag prefixes. - Monitor the matching release workflow, approve its protected environment only after the pre-publish jobs identify the expected artifact, and do not bypass a failed gate.
- Verify the exact version on the public registry and install it into a second fresh consumer. Confirm the GitHub release points to the tagged commit and contains the expected artifacts and notes.
- If source, build, package-publication, or package behavior fails after tag
creation, keep the tag immutable. Correct the source or workflow on
main, bump the affected package to a new patch version, and publish through a new tag. For the React adapter only, a missing GitHub Release after successful registry, provenance, and fresh-consumer gates is metadata-only recovery; follow the React-specific recovery runbook.
11.5.2. Validate Python Release Tests¶
Once the dispatch-capable workflow is on the default branch, develop, run
gh workflow run release.yml --ref develop to validate only the five-version
Ubuntu Python test matrix. Dispatch has no publication inputs, and all other
release jobs require a tag push, including when dispatch targets an existing
release tag. This does not exercise the protected build/publish job; normal
Python CI separately checks wheel and extracted-sdist packaging. Real tag
pushes retain their main-ancestry and publication gates.
Rerun a successful revision to collect warm-cache evidence. The 3.10 cell must
report cache-hit=true, list both managed versions, and log the seeded 3.14
and selected 3.10 identities. A setup-uv package-cache hit is neither a restored
virtualenv nor necessarily a cached Python installation. Record the run URL
and revision; no historical interpreter selection is inferred. See
CONTRIBUTING.md for the full local procedure.
11.5.3. Recover React Release Metadata¶
Use this procedure only when the exact npm version, provenance, and both fresh React consumers have passed. A broken published package requires a new patch; a missing GitHub Release alone is a metadata-only recovery.
Before recovery, read the old run's evidence and independently confirm the
immutable remote tag SHA, its ancestry from main, the package version at that
tag, the exact public npm version, its provenance, and the successful fresh
React 18 and React 19 consumer checks. These checks establish publication
facts; the extraction commands below do not. npm forbids reusing a published
package name and version, even after unpublishing; see the
npm publish documentation.
Do not republish, move or recreate the tag, or reuse a version. Do not rerun
the historical workflow: a GitHub Actions rerun retains the original SHA and
ref, so it also retains the old extraction code. See GitHub's
rerun documentation
and workflow documentation.
From a current, verified main checkout that contains the fixed helper, use
the changelog bytes from the original immutable tag. Substitute the actual
already-published version; never run this procedure for a package that has not
passed the checks above.
set -euo pipefail
version=0.1.0 # Replace with the version whose npm upload already succeeded.
tag="react-v${version}"
git fetch --no-tags origin "refs/tags/${tag}:refs/tags/${tag}"
tag_sha="$(git rev-parse "${tag}^{commit}")"
remote_tag_ref="$(git ls-remote --exit-code origin "refs/tags/${tag}" | awk '{print $1}')"
test "$remote_tag_ref" = "$(git rev-parse "$tag")"
git fetch --no-tags origin main:refs/remotes/origin/main
git merge-base --is-ancestor "$tag_sha" origin/main
notes="$(mktemp)"
git show "${tag_sha}:packages/react/CHANGELOG.md" |
awk -v v="$version" -f tools/extract-react-release-notes.awk > "$notes"
test -s "$notes" && grep -q '[^[:space:]]' "$notes" || {
echo "No adapter release notes." >&2
exit 1
}
cat "$notes"
The fetch and ls-remote equality confirm the remote tag ref, and
^{commit} peels an annotated tag to its original commit. Any failed fetch or
remote query stops this script; do not infer a confirmed tag from a failure.
Inspect the rendered notes before continuing. Check whether a GitHub Release
already exists, and distinguish confirmed absence from an authentication or
network failure. If absent, create only release metadata:
gh release create "$tag" --verify-tag --target "$tag_sha" \
--title "React adapter v${version} (npm)" --notes-file "$notes"
If it exists, verify its tag and update only its title and notes:
gh release edit "$tag" --title "React adapter v${version} (npm)" --notes-file "$notes"
Verify the release URL, tag SHA, and notes; retain the recovery evidence and remove the temporary notes file. This changes GitHub Release metadata only.