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
12 changes: 12 additions & 0 deletions .github/skills/dev/git-workflow/open-pull-request/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Before opening a PR:
- [ ] All pre-commit checks passed (`linter all`, `cargo machete`, tests)
- [ ] PR body claims are aligned with the actual commit range (`<upstream-remote>/develop..HEAD`)
- [ ] If manual verification used temporary local-only patches, PR body explicitly says they are not included
- [ ] PR body paragraphs are written as single continuous lines (no hard line wrapping)

### Keeping the branch up to date

Expand Down Expand Up @@ -58,6 +59,17 @@ git push --force-with-lease <fork-remote> <branch-name>

## Title and Description Convention

### Body Formatting for GitHub

Before opening the PR, review and reformat the body text following the `write-markdown-docs`
checklist for GitHub surfaces:

- Write each paragraph as a **single continuous line** — do not hard-wrap at any fixed column width
- Use GitHub Flavored Markdown (GFM) conventions
- Check for accidental `#NUMBER` autolinks (only use `#NUMBER` for intentional issue/PR references)

### Title

PR title: use Conventional Commit style, include issue reference.

Examples:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ rustup update # Update to latest stable
rustup toolchain install nightly # Required for docs generation
```

The project MSRV is **1.85**. The nightly toolchain is needed only for
The project MSRV is **1.88**. The nightly toolchain is needed only for
`cargo +nightly doc` and certain pre-commit hook checks.

## Step 3: Build
Expand Down
13 changes: 12 additions & 1 deletion .github/skills/dev/planning/create-issue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,18 @@ linter cspell

### Step 3: Create the GitHub Issue

After user approval, create the GitHub issue. Options:
After user approval, format the issue body and create the issue.

#### Format Body Text for GitHub

Before calling the GitHub API or CLI, review and reformat the issue body following the
`write-markdown-docs` checklist for GitHub surfaces:

- Write each paragraph as a **single continuous line** — do not hard-wrap at any fixed column width
- Use GitHub Flavored Markdown (GFM) conventions
- Check for accidental `#NUMBER` autolinks (only use `#NUMBER` for intentional issue/PR references)

#### Create the Issue

**GitHub CLI:**

Expand Down
12 changes: 12 additions & 0 deletions .github/skills/dev/planning/write-markdown-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,15 @@ rendering handle the wrapping.
- [ ] Tables are consistently formatted
- [ ] Frontmatter is present and follows `docs/skills/semantic-skill-link-convention.md`
- [ ] `linter markdown` and `linter cspell` pass

## Checklist Before Submitting to GitHub

Apply this checklist to any Markdown body submitted via the GitHub API or CLI (issues, PR
descriptions, review comments, discussion posts) **before** calling the API:

- [ ] Each paragraph is written as a single continuous line — do **not** hard-wrap at any fixed column width
- [ ] No `#NUMBER` patterns used for enumeration or step numbering
- [ ] Any `#NUMBER` present is an intentional issue/PR reference
- [ ] Ordered lists use Markdown syntax (`1.` `2.` `3.`)
- [ ] Tables are consistently formatted
- [ ] No raw HTML unless GitHub's renderer requires it
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
matchmakes peers and collects statistics, supporting the UDP, HTTP, and TLS socket types with
native IPv4/IPv6 support, private/whitelisted mode, and a management REST API.

- **Language**: Rust (edition 2024, MSRV 1.85)
- **Language**: Rust (edition 2024, MSRV 1.88)
- **MSRV policy**: Once `bittorrent-*` crates are extracted as standalone
libraries (#1669), the tracker application should track a recent stable Rust
version while those libraries should each carry the minimum MSRV needed for
external consumer compatibility.
- **License**: AGPL-3.0-only
- **Version**: 3.0.0-develop
- **Web framework**: [Axum](https://github.com/tokio-rs/axum)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ keywords = [ "bittorrent", "file-sharing", "peer-to-peer", "torrent", "tracker"
license = "AGPL-3.0-only"
publish = true
repository = "https://github.com/torrust/torrust-tracker"
rust-version = "1.85"
rust-version = "1.88"
version = "3.0.0-develop"

[dependencies]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
---
doc-type: issue
issue-type: task
status: blocked
status: closed
priority: p2
github-issue: 1787
spec-path: docs/issues/open/1787-evaluate-msrv-bump.md
spec-path: docs/issues/closed/1787-evaluate-msrv-bump.md
branch: "1787-evaluate-msrv-bump"
related-pr: 1784
last-updated-utc: 2026-05-15 08:00
blocked-by: "#1669 (package restructuring)"
related-pr: 1815
last-updated-utc: 2026-05-20 18:00
semantic-links:
skill-links:
- create-issue
Expand Down Expand Up @@ -60,18 +59,39 @@ This dual nature creates a tension:
Until the `bittorrent-*` crates are extracted, a single workspace MSRV applies to
both classes, so the decision must be made with the extraction timeline in mind.

**This issue is currently blocked on #1669** (ongoing package restructuring).
Several decisions that directly affect the MSRV strategy have not yet been made:
The MSRV evaluation was unblocked and resolved in 2026-05-20: `rust-version = "1.88"` was chosen
as the minimum floor that avoids `cargo update` regressions on the current lockfile. The long-term
split policy (tracker app tracks recent stable; extracted `bittorrent-*` libraries keep a minimum
MSRV) is documented in the Policy Decision section below and will be applied in a follow-up issue
once #1669 closes.

- Which packages will be extracted as independent crates.io libraries.
- Final names for those packages.
- Which packages will share a versioning lifecycle with the main tracker and which
will evolve independently.
- Publication targets and minimum toolchain expectations for downstream consumers.
## Policy Decision

The MSRV evaluation should be re-opened only after #1669 has settled these questions.
Opening it sooner risks choosing a policy that becomes invalid once extraction scope
is defined.
**Decided 2026-05-20. Agreed value: `rust-version = "1.88"`.**

### Rationale

- **1.88 is the minimum floor that avoids `cargo update` regressions** on the current
lockfile. All dependency versions currently pinned in `Cargo.lock` require at most
Rust 1.88; running `cargo update` with a lower MSRV (1.85, 1.86, or 1.87) downgrades
major packages (bollard, tonic, testcontainers, serde_with, time, ureq, etc.).
- **Cross-project consistency** with
[torrust-index](https://github.com/torrust/torrust-index/blob/develop/Cargo.toml),
which also uses `rust-version = "1.88"`.

### Future MSRV policy (post-extraction of `bittorrent-*` crates)

When #1669 completes and the `bittorrent-*` crates are extracted into independent
repositories, the MSRV strategy should be split:

- **Tracker application** (`torrust-tracker-*` and the main binary): track a recent
stable Rust release; there is no downstream impact from a higher MSRV here.
- **Reusable/shared packages** (`bittorrent-*` crates published to crates.io): set the
**lowest MSRV that compiles and tests the crate** to maximize compatibility with
external consumers.

**Re-evaluation trigger**: open a follow-up issue when #1669 closes to apply the
split policy described above.

## Scope

Expand All @@ -94,21 +114,22 @@ is defined.

## Blockers

- **#1669 — Package restructuring**: names, extraction scope, versioning lifecycle, and
publication targets for the `bittorrent-*` crates must be decided before a rational
MSRV policy can be set. Track that issue and re-evaluate when it closes.
None. The blocker on #1669 was lifted: the current MSRV (1.88) is valid for the
monorepo in its present form. The post-extraction split policy is documented in the
"Future MSRV policy" section above and will be implemented in a follow-up issue
once #1669 closes.
Comment thread
josecelano marked this conversation as resolved.

## Implementation Plan

Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`.

| ID | Status | Task | Notes / Expected Output |
| --- | ------ | ------------------------------------------------------------------- | ------------------------------------------------------ |
| T1 | TODO | Decide MSRV policy (track latest stable vs. pin conservative floor) | Document the rationale in this spec before proceeding |
| T2 | TODO | Update `rust-version` in root `Cargo.toml` | Change from `"1.85"` to the agreed value |
| T3 | TODO | Update `AGENTS.md` MSRV reference | Keep in sync with `Cargo.toml` |
| T4 | TODO | Update setup-dev-environment SKILL.md MSRV reference | Keep in sync with `Cargo.toml` |
| T5 | TODO | Verify CI passes | Full quality gate (`linter all`, tests, pre-push hook) |
| ID | Status | Task | Notes / Expected Output |
| --- | ------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| T1 | DONE | Decide MSRV policy (track latest stable vs. pin conservative floor) | Policy documented in "Policy Decision" section: 1.88 for the whole workspace now; split policy (app tracks latest stable, extracted libraries keep minimum MSRV) to be applied post-#1669. |
| T2 | DONE | Update `rust-version` in root `Cargo.toml` | Changed from `"1.85"` to `"1.88"` |
| T3 | DONE | Update `AGENTS.md` MSRV reference | Updated from `1.85` to `1.88` |
| T4 | DONE | Update setup-dev-environment SKILL.md MSRV reference | Updated from `1.85` to `1.88` |
| T5 | TODO | Verify CI passes | Full quality gate (`linter all`, tests, pre-push hook) |

## Progress Tracking

Expand All @@ -117,8 +138,8 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`.
- [ ] Spec drafted in `docs/issues/drafts/`
- [x] Spec reviewed and approved by user/maintainer
- [x] GitHub issue created and issue number added to this spec
- [ ] Implementation completed
- [ ] Automatic verification completed (`linter all`, relevant tests, and pre-push checks)
- [x] Implementation completed
- [x] Automatic verification completed (`linter all`, relevant tests, and pre-push checks)
- [ ] Manual verification scenarios executed and recorded (status + evidence)
- [ ] Acceptance criteria reviewed after implementation and updated with evidence
- [ ] Reviewer validated acceptance criteria and updated checkboxes
Expand All @@ -130,6 +151,8 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`.
- 2026-05-15 07:00 UTC - Agent - Spec drafted, follow-up from PR #1784 (Rust edition 2024 migration, MSRV set to 1.85)
- 2026-05-15 07:30 UTC - Jose Celano - Marked blocked on #1669 (package restructuring); MSRV policy requires knowing extraction scope, names, and versioning lifecycle
- 2026-05-15 08:00 UTC - Agent - GitHub issue #1787 created; spec moved to docs/issues/open/
- 2026-05-20 00:00 UTC - Agent - Discovered that with MSRV 1.85 `cargo update` downgrades many packages (bollard 0.20→0.19, tonic 0.14→0.13, testcontainers 0.27→0.25, serde_with 3.20→3.17, time 0.3.47→0.3.45, ureq 3.3→2.12, etc.) because they require Rust > 1.85. Verified by dry-run that MSRV 1.88 is the minimum floor that avoids all such regressions (1.86 and 1.87 still produce downgrades). Bumped rust-version to 1.88; updated AGENTS.md and setup-dev-environment SKILL.md. Final long-term policy (whether to track latest stable, pin N-2, etc.) remains open pending #1669.
- 2026-05-20 12:00 UTC - Jose Celano - Confirmed 1.88 is fine; aligns with torrust-index. Policy recorded: tracker app to track latest stable post-extraction; reusable bittorrent-\* packages to keep minimum MSRV for external consumer compatibility. Issue ready to close; split policy applied in a follow-up once #1669 closes.

## Acceptance Criteria

Expand Down Expand Up @@ -161,15 +184,15 @@ Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`.

### Acceptance Verification

| AC ID | Status (`TODO`/`DONE`) | Evidence |
| ----- | ---------------------- | -------- |
| AC1 | TODO | |
| AC2 | TODO | |
| AC3 | TODO | |
| AC4 | TODO | |
| AC5 | TODO | |
| AC6 | TODO | |
| AC7 | TODO | |
| AC ID | Status (`TODO`/`DONE`) | Evidence |
| ----- | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| AC1 | DONE | Policy documented in "Policy Decision" section; split policy for post-extraction recorded as follow-up action |
| AC2 | DONE | `rust-version = "1.88"` in `Cargo.toml` |
| AC3 | DONE | `AGENTS.md` updated to MSRV 1.88 |
| AC4 | DONE | `setup-dev-environment` SKILL.md updated to MSRV 1.88 |
| AC5 | TODO | |
| AC6 | TODO | |
| AC7 | TODO | |

## Risks and Trade-offs

Expand Down
5 changes: 2 additions & 3 deletions packages/tracker-core/tests/common/test_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,9 @@ impl TestEnv {
.torrent_metrics_store
.load_global_downloads()
.await
&& u64::from(downloads) >= expected
{
if u64::from(downloads) >= expected {
break;
}
break;
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/tracker-core/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ async fn it_should_persist_the_number_of_completed_peers_for_each_torrent_into_t
.await
.unwrap();

if let Some(swarm_metadata) = test_env.get_swarm_metadata(&info_hash).await {
if swarm_metadata.downloads() == 1 {
break true;
}
if let Some(swarm_metadata) = test_env.get_swarm_metadata(&info_hash).await
&& swarm_metadata.downloads() == 1
{
break true;
}

tokio::time::sleep(std::time::Duration::from_millis(50)).await;
Expand Down
32 changes: 16 additions & 16 deletions packages/udp-tracker-server/src/statistics/event/handler/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,22 @@ async fn update_connection_id_errors_counter(
repository: &Repository,
now: DurationSinceUnixEpoch,
) {
if let ErrorKind::ConnectionCookie(_) = error_kind {
if let Some(UdpRequestKind::Announce { announce_request }) = opt_udp_request_kind {
let (client_software_name, client_software_version) = extract_name_and_version(&announce_request.peer_id.client());

let label_set = LabelSet::from([
(label_name!("client_software_name"), client_software_name.into()),
(label_name!("client_software_version"), client_software_version.into()),
]);

match repository
.increase_counter(&metric_name!(UDP_TRACKER_SERVER_CONNECTION_ID_ERRORS_TOTAL), &label_set, now)
.await
{
Ok(()) => {}
Err(err) => tracing::error!("Failed to increase the counter: {}", err),
}
if let ErrorKind::ConnectionCookie(_) = error_kind
&& let Some(UdpRequestKind::Announce { announce_request }) = opt_udp_request_kind
{
let (client_software_name, client_software_version) = extract_name_and_version(&announce_request.peer_id.client());

let label_set = LabelSet::from([
(label_name!("client_software_name"), client_software_name.into()),
(label_name!("client_software_version"), client_software_version.into()),
]);

match repository
.increase_counter(&metric_name!(UDP_TRACKER_SERVER_CONNECTION_ID_ERRORS_TOTAL), &label_set, now)
.await
{
Ok(()) => {}
Err(err) => tracing::error!("Failed to increase the counter: {}", err),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ untuple
unviable
upcasting
urlencode
ureq
uroot
usize
Vagaa
Expand Down
20 changes: 10 additions & 10 deletions src/console/ci/compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,16 @@ impl DockerCompose {
let deadline = Instant::now() + timeout;

loop {
if let Ok(ps_output) = self.ps() {
if compose_service_has_exited(&ps_output, service) {
let logs_output = self
.logs(&[service])
.unwrap_or_else(|error| format!("failed to collect compose logs output: {error}"));

return Err(io::Error::other(format!(
"compose service '{service}' exited while waiting for port mapping '{container_port}'.\nCompose ps:\n{ps_output}\nCompose logs:\n{logs_output}"
)));
}
if let Ok(ps_output) = self.ps()
&& compose_service_has_exited(&ps_output, service)
{
let logs_output = self
.logs(&[service])
.unwrap_or_else(|error| format!("failed to collect compose logs output: {error}"));

return Err(io::Error::other(format!(
"compose service '{service}' exited while waiting for port mapping '{container_port}'.\nCompose ps:\n{ps_output}\nCompose logs:\n{logs_output}"
)));
}

match self.port(service, container_port) {
Expand Down
10 changes: 5 additions & 5 deletions src/console/ci/e2e/logs_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ impl RunningServices {
let address = Self::replace_wildcard_ip_with_localhost(&captures[1]);
http_trackers.push(address);
}
} else if line.contains(HEALTH_CHECK_API_LOG_TARGET) {
if let Some(captures) = health_re.captures(&clean_line) {
let address = format!("{}/health_check", Self::replace_wildcard_ip_with_localhost(&captures[1]));
health_checks.push(address);
}
} else if line.contains(HEALTH_CHECK_API_LOG_TARGET)
&& let Some(captures) = health_re.captures(&clean_line)
{
let address = format!("{}/health_check", Self::replace_wildcard_ip_with_localhost(&captures[1]));
health_checks.push(address);
}
}

Expand Down
Loading