From cbf8fa3d8151851e51ca6a2d3cd2428ee2d410d8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 13:01:49 +0100 Subject: [PATCH 01/22] docs(git-hooks): document SSH idle timeout handling --- contrib/dev-tools/git/hooks/pre-push.sh | 1 + docs/git-hooks.md | 44 +++++++++++++++++++++++++ docs/index.md | 1 + 3 files changed, 46 insertions(+) create mode 100644 docs/git-hooks.md diff --git a/contrib/dev-tools/git/hooks/pre-push.sh b/contrib/dev-tools/git/hooks/pre-push.sh index 2ce527a2d..9ecf83a96 100755 --- a/contrib/dev-tools/git/hooks/pre-push.sh +++ b/contrib/dev-tools/git/hooks/pre-push.sh @@ -351,6 +351,7 @@ failed_step_name="" if [[ "${FORMAT}" == "text" ]]; then echo "Running pre-push checks..." + echo "Note: these checks can take several minutes. If Git reports a closed SSH connection after they pass, see docs/git-hooks.md." echo fi diff --git a/docs/git-hooks.md b/docs/git-hooks.md new file mode 100644 index 000000000..c6b017d53 --- /dev/null +++ b/docs/git-hooks.md @@ -0,0 +1,44 @@ +--- +semantic-links: + related-artifacts: + - contrib/dev-tools/git/hooks/pre-commit.sh + - contrib/dev-tools/git/hooks/pre-push.sh + - contrib/dev-tools/git/install-git-hooks.sh +--- + +# Git Hooks + +The repository's pre-commit and pre-push hooks run local validation before Git creates a commit +or updates a remote branch. The pre-push hook runs nightly checks and the full stable test suite, +so it can take several minutes. + +## SSH Idle Timeouts During Pushes + +Git can open its SSH connection to the remote before it runs the pre-push hook. If an SSH route +closes idle connections while the hook is running, a successful hook can be followed by an error +such as `Connection to ssh.github.com closed by remote host` or a push exit status of `141`. + +Configure periodic SSH traffic for this checkout to prevent an idle timeout without changing your +machine-wide SSH behavior: + +```sh +git config --local core.sshCommand 'ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=20' +``` + +Verify the repository-local setting with: + +```sh +git config --local --get core.sshCommand +``` + +To apply the same behavior to all GitHub SSH connections, add these options to the `Host +github.com` entry in `~/.ssh/config` instead: + +```text +Host github.com + ServerAliveInterval 30 + ServerAliveCountMax 20 +``` + +Use only one configuration approach unless you need a different setting for this repository. The +repository-local Git configuration is the preferred option when the timeout affects one checkout. diff --git a/docs/index.md b/docs/index.md index d8d021b5d..c46d0a130 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,6 +35,7 @@ Operational and development guides for working with the tracker. | [benchmarking.md](benchmarking.md) | How to run and interpret the torrent-repository benchmarks | | [containers.md](containers.md) | Building and running the tracker with Docker / Podman | | [events-architecture.md](events-architecture.md) | Event topology, consumers, and per-listener metrics policy | +| [git-hooks.md](git-hooks.md) | Hook behavior and SSH idle-timeout troubleshooting | | [packages.md](packages.md) | Workspace package catalog, architecture layers, and dependency rules | | [profiling.md](profiling.md) | CPU and memory profiling with Valgrind / kcachegrind | | [release_process.md](release_process.md) | Branch strategy, versioning, and the staging → main release pipeline | From b3134594ac326b217af296d7d326b8aef57368b8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 13:36:33 +0100 Subject: [PATCH 02/22] docs(git-hooks): clarify SSH keepalive guidance --- contrib/dev-tools/git/hooks/pre-push.sh | 2 +- docs/git-hooks.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/dev-tools/git/hooks/pre-push.sh b/contrib/dev-tools/git/hooks/pre-push.sh index 9ecf83a96..80d5c2db7 100755 --- a/contrib/dev-tools/git/hooks/pre-push.sh +++ b/contrib/dev-tools/git/hooks/pre-push.sh @@ -351,7 +351,7 @@ failed_step_name="" if [[ "${FORMAT}" == "text" ]]; then echo "Running pre-push checks..." - echo "Note: these checks can take several minutes. If Git reports a closed SSH connection after they pass, see docs/git-hooks.md." + echo "Note: these checks can take several minutes. If Git reports a closed SSH connection after they pass, see /docs/git-hooks.md." echo fi diff --git a/docs/git-hooks.md b/docs/git-hooks.md index c6b017d53..08e50637c 100644 --- a/docs/git-hooks.md +++ b/docs/git-hooks.md @@ -31,11 +31,11 @@ Verify the repository-local setting with: git config --local --get core.sshCommand ``` -To apply the same behavior to all GitHub SSH connections, add these options to the `Host -github.com` entry in `~/.ssh/config` instead: +To apply the same behavior to all GitHub SSH connections, add these options to a `Host github.com +ssh.github.com` entry in `~/.ssh/config` instead: ```text -Host github.com +Host github.com ssh.github.com ServerAliveInterval 30 ServerAliveCountMax 20 ``` From 474f5142a320f7d0d9024d9792544a552a61fb4c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 13:50:14 +0100 Subject: [PATCH 03/22] docs(git-hooks): remind developers to reinstall dispatchers --- .githooks/pre-commit | 3 +++ .githooks/pre-push | 3 +++ contrib/dev-tools/git/check-git-hooks.sh | 18 +++++++++++------- contrib/dev-tools/git/install-git-hooks.sh | 5 +++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 1c5864498..11e063d98 100644 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# This tracked dispatcher is copied to .git/hooks/. After editing .githooks/, run: +# ./contrib/dev-tools/git/install-git-hooks.sh +# Scripts under contrib/dev-tools/git/hooks/ are invoked directly and do not require copying. set -euo pipefail diff --git a/.githooks/pre-push b/.githooks/pre-push index bbc293ba3..9c641b2e5 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# This tracked dispatcher is copied to .git/hooks/. After editing .githooks/, run: +# ./contrib/dev-tools/git/install-git-hooks.sh +# Scripts under contrib/dev-tools/git/hooks/ are invoked directly and do not require copying. set -euo pipefail diff --git a/contrib/dev-tools/git/check-git-hooks.sh b/contrib/dev-tools/git/check-git-hooks.sh index 3cdbcad89..2ea0fb71d 100755 --- a/contrib/dev-tools/git/check-git-hooks.sh +++ b/contrib/dev-tools/git/check-git-hooks.sh @@ -4,10 +4,11 @@ # Usage: # ./contrib/dev-tools/git/check-git-hooks.sh # -# Exits 0 if all hooks are installed and executable. -# Exits 1 if any hook is missing or not executable. +# Exits 0 if all hooks are installed, executable, and synchronized with .githooks/. +# Exits 1 if any hook is missing, not executable, or out of sync. # -# Run after cloning or whenever you want to verify your hook installation. +# Run after cloning, after changing a dispatcher in .githooks/, or whenever you want to verify +# your hook installation. set -euo pipefail @@ -26,10 +27,13 @@ for hook in "${HOOKS_SRC}"/*; do hook_name="$(basename "${hook}")" dest="${HOOKS_DST}/${hook_name}" - if [[ -x "${dest}" ]]; then + if [[ ! -x "${dest}" ]]; then + echo "NOT installed: ${hook_name}" + all_installed=false + elif cmp -s "${hook}" "${dest}"; then echo "installed: ${hook_name}" else - echo "NOT installed: ${hook_name}" + echo "OUT OF SYNC: ${hook_name}" all_installed=false fi done @@ -38,12 +42,12 @@ echo "" if [[ "${all_installed}" == "true" ]]; then echo "==========================================" - echo "SUCCESS: All hooks are installed." + echo "SUCCESS: All hooks are installed and synchronized." echo "==========================================" exit 0 else echo "==========================================" - echo "FAILURE: Some hooks are missing." + echo "FAILURE: Some hooks are missing or out of sync." echo "Run: ./contrib/dev-tools/git/install-git-hooks.sh" echo "==========================================" exit 1 diff --git a/contrib/dev-tools/git/install-git-hooks.sh b/contrib/dev-tools/git/install-git-hooks.sh index 16de7fe5a..c48ea709c 100755 --- a/contrib/dev-tools/git/install-git-hooks.sh +++ b/contrib/dev-tools/git/install-git-hooks.sh @@ -4,8 +4,9 @@ # Usage: # ./contrib/dev-tools/git/install-git-hooks.sh # -# Run once after cloning the repository. Re-run to update hooks after -# they change. +# Run once after cloning the repository. Re-run after changing a dispatcher in .githooks/ +# so its installed copy in .git/hooks/ stays synchronized. Scripts under +# contrib/dev-tools/git/hooks/ are invoked directly and do not require copying. set -euo pipefail From c0b62dff083ab3acf8e58f7a73c5722db24c9c7f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 13:28:22 +0100 Subject: [PATCH 04/22] docs(architecture): organize runtime architecture guides --- docs/AGENTS.md | 36 ++-- ...hared_services_across_tracker_instances.md | 50 +++-- docs/architecture/README.md | 44 ++++ docs/architecture/events.md | 128 ++++++++++++ .../tracker-instance-architecture.md | 148 ++++++++++++++ docs/events-architecture.md | 127 ------------ docs/index.md | 11 +- docs/issues/drafts/generalize-error-events.md | 4 +- .../ISSUE.md | 4 +- .../ISSUE.md | 4 +- .../archived-attempt.md | 12 +- .../ISSUE.md | 4 +- ...nize-runtime-architecture-documentation.md | 191 ++++++++++++++++++ 13 files changed, 596 insertions(+), 167 deletions(-) create mode 100644 docs/architecture/README.md create mode 100644 docs/architecture/events.md create mode 100644 docs/architecture/tracker-instance-architecture.md delete mode 100644 docs/events-architecture.md create mode 100644 docs/issues/open/2095-organize-runtime-architecture-documentation.md diff --git a/docs/AGENTS.md b/docs/AGENTS.md index b5cd15a2c..9070a71ae 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -4,6 +4,7 @@ semantic-links: - write-markdown-docs related-artifacts: - docs/index.md + - docs/architecture/README.md - docs/adrs/20260821172000_establish_ai_agent_context_capability_and_portability_governance.md - docs/skills/semantic-skill-link-convention.md --- @@ -17,23 +18,24 @@ For the full project context see the [root AGENTS.md](../AGENTS.md). ## Directory Map -| Path | Purpose | -| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| `index.md` | Entry point — structured index of every document and subdirectory | -| `benchmarking.md` | How to run and interpret torrent-repository benchmarks | -| `containers.md` | Running the tracker with Docker / Podman | -| `packages.md` | Workspace package catalog, architecture layers, dependency rules | -| `profiling.md` | CPU and memory profiling with Valgrind / kcachegrind | -| `release_process.md` | Branch strategy, versioning, and the release pipeline | -| `adrs/20260821172000_establish_ai_agent_context_capability_and_portability_governance.md` | Authority and portability governance for AI-agent workflows and retained context | -| `adrs/` | Architectural Decision Records (ADRs) | -| `issues/` | Issue specification documents linked to GitHub issues | -| `refactor-plans/` | Refactor plan specifications (same lifecycle as issue specs) | -| `pr-reviews/` | Notable PR review records and Copilot suggestion threads | -| `skills/` | Internal conventions used by humans and AI agents | -| `templates/` | Canonical document templates (ADR, EPIC, issue, refactor plan) | -| `media/` | Images, diagrams, flamegraphs, benchmark reports, sample torrents | -| `licenses/` | Full license texts (AGPL-3.0, MIT-0) | +| Path | Purpose | +| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `index.md` | Entry point — structured index of every document and subdirectory | +| `architecture/` | Runtime-composition guides: tracker instances, shared services, and event topology | +| `benchmarking.md` | How to run and interpret torrent-repository benchmarks | +| `containers.md` | Running the tracker with Docker / Podman | +| `packages.md` | Workspace package catalog, architecture layers, dependency rules | +| `profiling.md` | CPU and memory profiling with Valgrind / kcachegrind | +| `release_process.md` | Branch strategy, versioning, and the release pipeline | +| `adrs/20260821172000_establish_ai_agent_context_capability_and_portability_governance.md` | Authority and portability governance for AI-agent workflows and retained context | +| `adrs/` | Architectural Decision Records (ADRs) | +| `issues/` | Issue specification documents linked to GitHub issues | +| `refactor-plans/` | Refactor plan specifications (same lifecycle as issue specs) | +| `pr-reviews/` | Notable PR review records and Copilot suggestion threads | +| `skills/` | Internal conventions used by humans and AI agents | +| `templates/` | Canonical document templates (ADR, EPIC, issue, refactor plan) | +| `media/` | Images, diagrams, flamegraphs, benchmark reports, sample torrents | +| `licenses/` | Full license texts (AGPL-3.0, MIT-0) | ### Where to place a new artifact diff --git a/docs/adrs/20260727180000_shared_services_across_tracker_instances.md b/docs/adrs/20260727180000_shared_services_across_tracker_instances.md index f7468cc8a..0626353d6 100644 --- a/docs/adrs/20260727180000_shared_services_across_tracker_instances.md +++ b/docs/adrs/20260727180000_shared_services_across_tracker_instances.md @@ -1,8 +1,13 @@ --- semantic-links: + skill-links: + - create-adr + - write-markdown-docs related-artifacts: - docs/adrs/index.md - - docs/events-architecture.md + - docs/architecture/README.md + - docs/architecture/events.md + - docs/architecture/tracker-instance-architecture.md - packages/http-core/src/container.rs - packages/udp-core/src/container.rs - packages/udp-server/src/container.rs @@ -17,7 +22,10 @@ semantic-links: ## Description The tracker can run multiple UDP and HTTP tracker listeners in a single process. -Each listener binds to a different address/port but shares core infrastructure: +They expose one logical tracker, not independent tracker applications managed by +a launcher. Listeners can have different bindings; HTTP and UDP can use the +same socket address because they use different transports, and port `0` is +resolved only after binding. They share core infrastructure: - **Peer repository** (`TrackerCoreContainer`) — all instances share the same swarm data (torrents, peers, statistics). This is the primary reason to run @@ -28,6 +36,11 @@ Each listener binds to a different address/port but shares core infrastructure: are aggregate application services. The UDP server container is shared by all UDP listeners. +The [tracker-instance architecture guide](../architecture/tracker-instance-architecture.md) +explains the complete runtime composition, including shared core policy and +listener-specific responsibilities. This ADR records the accepted +shared-services decision and its rationale. + This ADR documents the shared-services design and the rationale for keeping certain services global rather than per-instance. @@ -38,16 +51,15 @@ certain services global rather than per-instance. The following services are created once and shared across all instances of the same type: -| Service | Location | Shared? | Rationale | -| --------------------------------------------- | --------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------ | -| Peer repository | `TrackerCoreContainer` | Yes | All listeners serve the same swarm | -| Swarm coordination registry | `SwarmCoordinationRegistryContainer` | Yes | Single source of truth for swarm state | -| UDP ban service | `UdpTrackerCoreServices::ban_service` | Yes | Resource protection: an attacker should not be able to consume N× resources by attacking N listeners independently | -| UDP core event bus | `UdpTrackerCoreServices::event_bus` | Yes | Core events (connect, announce, scrape) are objective facts about the swarm, not about a specific listener | -| UDP core services (connect, announce, scrape) | `UdpTrackerCoreServices` | Yes | Stateless service objects; they read from the shared peer repository | -| HTTP core event bus and repository | `HttpTrackerCoreServices` | Yes | Aggregate HTTP metrics are collected in one application-wide event path | -| UDP server event bus | `UdpTrackerServerContainer::event_bus` | Yes | One application-wide bus is passed to every UDP listener | -| UDP server stats repository | `UdpTrackerServerContainer::stats_repository` | Yes | One aggregate server repository receives events from every UDP listener | +| Service | Location | Shared? | Rationale | +| -------------------------------------------- | --------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------ | +| Peer repository | `TrackerCoreContainer` | Yes | All listeners serve the same swarm | +| Swarm coordination registry | `SwarmCoordinationRegistryContainer` | Yes | Single source of truth for swarm state | +| UDP ban service | `UdpTrackerCoreServices::ban_service` | Yes | Resource protection: an attacker should not be able to consume N× resources by attacking N listeners independently | +| UDP core event bus and statistics repository | `UdpTrackerCoreServices` | Yes | Core events are objective facts about the swarm; aggregate protocol metrics are application-wide | +| HTTP core event bus and repository | `HttpTrackerCoreServices` | Yes | Aggregate HTTP metrics are collected in one application-wide event path | +| UDP server event bus | `UdpTrackerServerContainer::event_bus` | Yes | One application-wide bus is passed to every UDP listener | +| UDP server stats repository | `UdpTrackerServerContainer::stats_repository` | Yes | One aggregate server repository receives events from every UDP listener | The UDP server's shared bus and repository do not conflict with per-listener metrics policy. Events are objective facts and must be emitted independently of @@ -59,7 +71,7 @@ listeners may validly use `0.0.0.0:0`. UDP banning remains independent of metrics. Its listener receives every security-relevant event from the shared UDP server bus and updates the shared ban service regardless of whether the originating listener contributes to -metrics. See [events-architecture.md](../events-architecture.md). +metrics. See [events.md](../architecture/events.md). ### Why the ban service is shared @@ -85,6 +97,18 @@ Settings that affect shared services must themselves be global. For example: Settings that are inherently per-listener (bind address, cookie lifetime, public URL, network topology) remain on the per-instance config struct. +The shared `TrackerCoreContainer` also means the tracker-core policies have one +meaning for the process. Private mode, listed mode, private-mode configuration, +announce policy, and tracker policy apply to the shared swarm, whitelist, and +authentication state. They cannot differ by HTTP or UDP listener without +creating inconsistent behavior over the same logical tracker. + +HTTP and UDP listener containers create their own protocol adapters, including +announce and scrape services. Those adapters are listener-specific; their +dependencies on tracker-core state, aggregate events, statistics, and UDP ban +state are shared. A listener-specific adapter does not make the underlying +tracker state independent. + ### Alternatives Considered **Per-instance ban service.** diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 000000000..a8e709886 --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,44 @@ +--- +semantic-links: + skill-links: + - write-markdown-docs + related-artifacts: + - docs/index.md + - docs/packages.md + - docs/application-jobs.md + - docs/architecture/events.md + - docs/architecture/tracker-instance-architecture.md + - docs/adrs/20260727180000_shared_services_across_tracker_instances.md + - docs/skills/semantic-skill-link-convention.md +--- + +# Runtime Architecture + +This directory contains evolving guides to the tracker application's runtime +composition and behavior. These guides explain the architecture implemented by +the current codebase; they do not replace the accepted decisions in +[`docs/adrs/`](../adrs/README.md). + +## Guides + +| Document | Purpose | +| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| [tracker-instance-architecture.md](tracker-instance-architecture.md) | Process topology, shared services, listener-instance boundaries, configuration placement, and when to use separate tracker processes. | +| [events.md](events.md) | Event topology, event consumers, aggregate statistics, and per-listener metrics policy. | + +## Related Documentation + +| Document | Purpose | +| ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| [../packages.md](../packages.md) | Workspace package catalog, dependency layers, and boundary enforcement. | +| [../application-jobs.md](../application-jobs.md) | Current job ownership, lifecycle, and shutdown behavior. | +| [../adrs/20260727180000_shared_services_across_tracker_instances.md](../adrs/20260727180000_shared_services_across_tracker_instances.md) | Accepted decision to share selected services across listener instances. | +| [../adrs/20260727000000_events_are_objective_facts.md](../adrs/20260727000000_events_are_objective_facts.md) | Accepted event-design rule. | + +## Documentation Boundary + +Use this directory to explain how the running application is composed and why +its components interact as they do. Record a new, consequential architectural +choice in an ADR, then update these guides to explain the resulting runtime +model. Keep package dependency rules in `packages.md` and background-task +ownership in `application-jobs.md` rather than duplicating them here. diff --git a/docs/architecture/events.md b/docs/architecture/events.md new file mode 100644 index 000000000..266ba6349 --- /dev/null +++ b/docs/architecture/events.md @@ -0,0 +1,128 @@ +--- +semantic-links: + skill-links: + - write-markdown-docs + related-artifacts: + - docs/architecture/README.md + - docs/architecture/tracker-instance-architecture.md + - docs/adrs/20260727000000_events_are_objective_facts.md + - docs/adrs/20260727180000_shared_services_across_tracker_instances.md + - packages/events/src/bus.rs + - packages/http-core/src/container.rs + - packages/udp-core/src/container.rs + - packages/udp-server/src/container.rs + - src/container.rs + - src/bootstrap/jobs/ + - issue #2039 + - issue #2035 + - issue #2036 +--- + +# Events Architecture + +## Purpose + +This guide describes the tracker event topology and the direction for +normalizing per-listener metrics policy. It distinguishes an event producer's +responsibility to report an objective fact from a listener's responsibility to +apply a policy to that fact. + +## Current Topology + +`EventBus` wraps a Tokio broadcast channel and returns no sender when its +`SenderStatus` is disabled. This remains useful for explicit absent-sender +injection and for a future bootstrap-time consumer-demand decision. The HTTP +core, UDP core, and UDP server each create one event bus and one aggregate +statistics repository during application container initialization. They enable +their senders because facts can be needed independently of the originating +listener's metrics policy. + +The HTTP and UDP tracker instance containers share their respective core +services. `AppContainer` creates one application-wide `UdpTrackerServerContainer` +and passes a clone to every UDP listener. Consequently, the UDP server has one +shared event bus and one shared server statistics repository, not a bus or +repository per UDP listener. + +| Layer | Current event bus and repository ownership | Event consumers | +| ---------- | -------------------------------------------------------------------------- | ------------------------------------------------------- | +| HTTP core | One application-wide bus and aggregate repository shared by HTTP listeners | HTTP core statistics listener | +| UDP core | One application-wide bus and aggregate repository shared by UDP listeners | UDP core statistics listener | +| UDP server | One application-wide bus and aggregate repository shared by UDP listeners | UDP server statistics listener and UDP banning listener | + +The REST metrics adapter exposes TCP announce counts from HTTP core statistics +and UDP announce counts from UDP server statistics. UDP request handling has +two event layers: a server lifecycle stream and a core protocol stream. + +## Producer and Listener Responsibilities + +Events are objective facts. Under +[ADR-20260727000000](../adrs/20260727000000_events_are_objective_facts.md), an +event must not encode whether a particular consumer should act on it. + +Applying a metrics setting at the producer is too broad when an event has more +than one consumer. UDP server cookie-error events are observed by both metrics +and banning listeners. Suppressing production for metrics also prevents the +banning listener from observing an error. + +The target division of responsibility is: + +```text +listener instance + -> always emit an objective event with stable runtime identity + -> shared layer event bus + -> metrics listener filters by that listener's metrics policy + -> shared aggregate repository + -> UDP banning listener receives every relevant security event + -> shared ban service +``` + +Metrics filtering is a consumer-side decision. Banning remains independent of +metrics configuration and enforces against shared ban state. + +## HTTP and UDP Asymmetry + +The user-facing intent from [issue #1263][1263] and [issue #1401][1401] is +aggregate statistics with per-public-listener `tracker_usage_statistics` policy. +The current implementation cannot yet express that intent consistently: + +- HTTP and UDP-core event production is gated globally, although listeners are + configured independently. +- UDP-server metrics also use one global event path and source public UDP + request counters. +- UDP-server events additionally feed banning, so a metrics gate cannot decide + whether those facts exist. + +This asymmetry concerns event ownership and consumers, not a need for one +repository per listener. A shared aggregate repository remains the desired +topology when metrics listeners filter events by stable listener identity. + +## Proposed Normalization + +The normalization work depends on [issue #2036][2036] defining canonical +runtime service and configuration-instance identity. That identity must travel +with metric-relevant events; configured socket addresses are unsuitable because +several configuration blocks can use `0.0.0.0:0`. + +Producers must emit objective facts regardless of `tracker_usage_statistics`. +Each metrics listener receives an immutable identity-to-policy lookup and +ignores disabled-listener events before mutating its shared aggregate +repository. The UDP banning listener does not use that lookup. + +This preserves aggregate metrics, fixes the server-layer UDP gap, and lets +future non-metrics consumers receive complete facts without coupling them to +metrics configuration. + +## Related Work + +- Approved specification: [normalize per-instance event metrics policy][2039] +- Bootstrap bug: [issue #2035][2035] +- Runtime identity prerequisite: [issue #2036][2036] +- Shared-services decision: + [ADR-20260727180000](../adrs/20260727180000_shared_services_across_tracker_instances.md) +- Deferred investigation: [optimize event publication without consumers](../issues/drafts/optimize-event-publication-without-consumers/ISSUE.md) + +[1263]: https://github.com/torrust/torrust-tracker/issues/1263 +[1401]: https://github.com/torrust/torrust-tracker/issues/1401 +[2035]: https://github.com/torrust/torrust-tracker/issues/2035 +[2036]: https://github.com/torrust/torrust-tracker/issues/2036 +[2039]: ../issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md diff --git a/docs/architecture/tracker-instance-architecture.md b/docs/architecture/tracker-instance-architecture.md new file mode 100644 index 000000000..9ce666c63 --- /dev/null +++ b/docs/architecture/tracker-instance-architecture.md @@ -0,0 +1,148 @@ +--- +semantic-links: + skill-links: + - write-markdown-docs + related-artifacts: + - docs/architecture/README.md + - docs/architecture/events.md + - docs/application-jobs.md + - docs/packages.md + - docs/adrs/20260721000000_make_network_configuration_per_tracker_instance.md + - docs/adrs/20260727180000_shared_services_across_tracker_instances.md + - docs/issues/open/1980-1978-configuration-overhaul-final-cleanup.md + - src/container.rs + - packages/tracker-core/src/container.rs + - packages/http-core/src/container.rs + - packages/udp-core/src/container.rs + - packages/udp-server/src/container.rs +--- + +# Tracker Instance Architecture + +## Purpose + +This guide describes how one Torrust Tracker process composes configured HTTP +and UDP listener instances. It establishes the practical model for evaluating +configuration placement and deployment options: + +> Multiple listener instances in one process expose one logical tracker. They +> are not independent tracker applications supervised by a launcher. + +The accepted shared-services decision is recorded in +[ADR-20260727180000](../adrs/20260727180000_shared_services_across_tracker_instances.md). +This guide explains its runtime implications. It does not replace that ADR. + +## Runtime Composition + +`AppContainer` constructs application-wide containers once, then creates one +HTTP or UDP listener container for every configured listener entry. A listener +has its own transport configuration and protocol adapter, but uses shared +application state and services. + +```text +one tracker process +└── AppContainer + ├── shared TrackerCoreContainer + │ ├── swarm and peer repository + │ ├── whitelist and authentication state + │ └── shared tracker policies and announce handling + ├── shared HTTP core services + ├── shared UDP core services + │ └── shared UDP BanService + ├── shared UDP server container + ├── HTTP listener instance 0 + ├── HTTP listener instance 1 + ├── UDP listener instance 0 + └── UDP listener instance 1 +``` + +The process can bind several listeners. HTTP and UDP listeners may use the +same socket address because they use different transports. A configured port of +`0` is also valid, so the final binding is known only after the listener starts. + +## Shared Services and State + +The following are application-wide. A request handled by one listener can +observe effects created through another listener because they use these same +objects. + +| Shared concern | Reason | +| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Swarm, torrent, and peer data | All listeners serve the same swarm. A peer announcing over HTTP must be visible to an equivalent UDP announce and vice versa. | +| Whitelist and authentication data | Authorization has one meaning for the logical tracker. | +| Core policies | Private mode, listing/whitelist authorization, announce policy, and tracker policy govern shared tracker behavior. | +| UDP ban state | An invalid-request budget remains process-wide so an attacker cannot multiply it by targeting several UDP listeners. | +| HTTP, UDP-core, and UDP-server event paths and aggregate statistics repositories | Events describe application facts and feed aggregate observability; metrics policy is applied by consumers. See [events.md](events.md). | +| Runtime service registry and application jobs | The application tracks the started services and owns jobs according to service lifetime. See [../application-jobs.md](../application-jobs.md). | + +The current runtime still consumes configuration v2 aliases. Configuration v3 +expresses the intended placement of shared values under `core`, and its runtime +activation is tracked by [issue #1980][1980]. The shared-process topology itself +already exists regardless of configuration-schema activation. + +## Listener-Owned Concerns + +Each configured HTTP or UDP listener has an individual configuration entry and +container. These listeners own endpoint-specific concerns, including: + +- binding and transport lifecycle; +- network topology, including external address and reverse-proxy behavior; +- public URL and TLS exposure where applicable; +- UDP cookie lifetime; +- HTTP request parsing behavior where it is endpoint-specific; +- stable configuration-instance identity; and +- participation in aggregate usage statistics. + +HTTP and UDP listener containers instantiate their own protocol adapter +services, such as announce and scrape adapters. Those adapters are not shared; +they call into shared tracker-core state and shared protocol-layer services. + +## Configuration Placement Rule + +Place a setting on a listener only when each listener can apply it independently +without changing shared state, shared security behavior, or the logical +tracker's meaning for another listener. + +Place a setting on `core` or another shared-service configuration when it +governs shared data, authorization, security state, aggregate application +behavior, or a service constructed once per process. + +Examples: + +| Configuration concern | Boundary | Why | +| ---------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| Bind address, external IP, reverse-proxy trust, public URL | Listener | They describe how one endpoint is exposed or reached. | +| Metrics participation | Listener | A listener can choose whether its facts contribute to aggregate metrics without withholding facts from other consumers. | +| Private mode, listed mode, private-mode policy | Core | Authentication and whitelist checks use shared tracker state and must have one meaning. | +| Announce policy and tracker policy | Core | They define behavior for the shared swarm and its peer-management logic. | +| UDP invalid-connection-ID limit and ban-reset policy | Shared UDP server service | They govern one shared `BanService`, not one listener. | + +## Multiple Listeners Versus Multiple Processes + +Run multiple listeners in one process when they are different ways to reach the +same logical tracker. Typical examples include HTTP and UDP endpoints for one +swarm or several network endpoints serving the same tracker policy. + +Run separate tracker processes when endpoints require genuinely independent +tracker state or policy. Examples include: + +- a private HTTP tracker and a public UDP tracker; +- separate torrent/peer populations or databases; +- independent whitelist or authentication-key state; +- different private, listed, announce, or tracker policies; or +- independent UDP banning budgets. + +Separate processes have their own `AppContainer` and therefore their own +tracker-core, shared-service, and persistence boundaries. They do not receive +the resource-sharing or cross-protocol swarm visibility that listener instances +within one process provide. + +## Related Documents + +- [Runtime architecture index](README.md) +- [Event topology and metrics policy](events.md) +- [Package architecture](../packages.md) +- [Application jobs and task ownership](../application-jobs.md) +- [Shared services ADR](../adrs/20260727180000_shared_services_across_tracker_instances.md) + +[1980]: https://github.com/torrust/torrust-tracker/issues/1980 diff --git a/docs/events-architecture.md b/docs/events-architecture.md deleted file mode 100644 index 050bfa1ee..000000000 --- a/docs/events-architecture.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -semantic-links: - related-artifacts: - - docs/adrs/20260727000000_events_are_objective_facts.md - - docs/adrs/20260727180000_shared_services_across_tracker_instances.md - - packages/events/src/bus.rs - - packages/http-core/src/container.rs - - packages/udp-core/src/container.rs - - packages/udp-server/src/container.rs - - src/container.rs - - src/bootstrap/jobs/ - - docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md - - docs/issues/drafts/optimize-event-publication-without-consumers/ISSUE.md ---- - -# Events Architecture - -## Purpose - -This guide describes the tracker event topology as it exists on `develop` and -the direction for normalizing per-listener metrics policy. It distinguishes an -event producer's responsibility to report an objective fact from a listener's -responsibility to apply a policy to that fact. - -## Current Topology - -`EventBus` wraps a Tokio broadcast channel and returns no sender when its -`SenderStatus` is disabled. This remains useful for explicit absent-sender -injection and for a future bootstrap-time consumer-demand decision. The HTTP -core, UDP core, and UDP server each create one event bus and one aggregate -statistics repository during application container initialization. They -explicitly enable their senders because their facts may be needed independently -of the originating listener's metrics policy. - -The HTTP and UDP tracker instance containers share their respective core -services. The UDP server differs from both core layers: `AppContainer` creates -one application-wide `UdpTrackerServerContainer`, then passes a clone of that -container to every UDP listener. Consequently, the UDP server has one shared -event bus and one shared server statistics repository, not a bus or repository -per UDP listener. - -| Layer | Current event bus and repository ownership | Event consumers | -| ---------- | -------------------------------------------------------------------------- | ------------------------------------------------------- | -| HTTP core | One application-wide bus and aggregate repository shared by HTTP listeners | HTTP core statistics listener | -| UDP core | One application-wide bus and aggregate repository shared by UDP listeners | UDP core statistics listener | -| UDP server | One application-wide bus and aggregate repository shared by UDP listeners | UDP server statistics listener and UDP banning listener | - -The REST metrics adapter exposes TCP announce counts from HTTP core statistics -and UDP announce counts from UDP server statistics. UDP request handling thus -has two event layers: a server lifecycle event stream and a core protocol event -stream. - -## Producer and Listener Responsibilities - -Events are objective facts. As established by -[ADR 20260727000000](adrs/20260727000000_events_are_objective_facts.md), an -event must not encode whether a particular consumer should act on it. - -Applying a metrics setting at the producer is too broad when an event has more -than one consumer. In particular, UDP server cookie-error events are observed by -both the metrics listener and the banning listener. Suppressing production for -metrics also prevents the banning listener from observing the error. - -The target division of responsibility is: - -```text -listener instance - -> always emit an objective event with stable runtime identity - -> shared layer event bus - -> metrics listener filters by that listener's metrics policy - -> shared aggregate repository - -> UDP banning listener receives every relevant security event - -> shared ban service -``` - -Metrics filtering is therefore a consumer-side decision. Banning is independent -of metrics configuration and continues to enforce against the shared ban state. - -## HTTP and UDP Asymmetry - -The user-facing intent from [#1263][1263] and [#1401][1401] is aggregate -statistics with a per-public-listener `tracker_usage_statistics` policy. The -current implementation cannot express that intent consistently: - -- HTTP and UDP core event production is gated globally, even though listeners - are configured independently. -- UDP server metrics are also generated through one global event path and are - the source of public UDP request counters. -- UDP server events additionally feed the banning listener, so a metrics gate - cannot safely decide whether those facts exist. - -This is an asymmetry of event ownership and consumers, not a reason to create a -repository per listener. A shared aggregate repository remains the desired -topology when the metrics listener filters events by stable listener identity. - -## Proposed Normalization - -The normalization work depends on [#2036][2036] defining canonical runtime -service and configuration-instance identity. That identity must travel with -metric-relevant events; configured socket addresses are not suitable because -multiple configuration blocks may use `0.0.0.0:0`. - -The implementation makes producers emit objective facts regardless of -`tracker_usage_statistics`, then provides each metrics listener with an -immutable identity-to-policy lookup. The listener ignores disabled-listener -events before updating its shared aggregate repository. The UDP banning listener -does not use that lookup. - -This approach preserves aggregate metrics, fixes the server-layer UDP gap, and -lets future non-metrics consumers receive complete facts without coupling them -to metrics configuration. - -## Related Work - -- Approved specification: [normalize per-instance event metrics policy](issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md) -- Deferred draft EPIC: [define and implement general error events](issues/drafts/generalize-error-events.md) -- Bootstrap bug: [#2035][2035] -- Runtime identity prerequisite: [#2036][2036] -- Shared-services decision: [ADR 20260727180000](adrs/20260727180000_shared_services_across_tracker_instances.md) -- Deferred investigation: [optimize event publication without consumers](issues/drafts/optimize-event-publication-without-consumers/ISSUE.md). - This is not part of the correctness policy: it requires a complete consumer - inventory and benchmark evidence before any publication gate is introduced. - -[1263]: https://github.com/torrust/torrust-tracker/issues/1263 -[1401]: https://github.com/torrust/torrust-tracker/issues/1401 -[2035]: https://github.com/torrust/torrust-tracker/issues/2035 -[2036]: https://github.com/torrust/torrust-tracker/issues/2036 diff --git a/docs/index.md b/docs/index.md index c46d0a130..aae8d4b0e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,6 +4,7 @@ semantic-links: - write-markdown-docs related-artifacts: - docs/AGENTS.md + - docs/architecture/README.md - docs/adrs/20260821172000_establish_ai_agent_context_capability_and_portability_governance.md - docs/benchmarking.md - docs/application-jobs.md @@ -34,13 +35,21 @@ Operational and development guides for working with the tracker. | [application-jobs.md](application-jobs.md) | Current background-job ownership, lifecycle, and shutdown behavior | | [benchmarking.md](benchmarking.md) | How to run and interpret the torrent-repository benchmarks | | [containers.md](containers.md) | Building and running the tracker with Docker / Podman | -| [events-architecture.md](events-architecture.md) | Event topology, consumers, and per-listener metrics policy | | [git-hooks.md](git-hooks.md) | Hook behavior and SSH idle-timeout troubleshooting | | [packages.md](packages.md) | Workspace package catalog, architecture layers, and dependency rules | | [profiling.md](profiling.md) | CPU and memory profiling with Valgrind / kcachegrind | | [release_process.md](release_process.md) | Branch strategy, versioning, and the staging → main release pipeline | | [adrs/20260821172000_establish_ai_agent_context_capability_and_portability_governance.md](adrs/20260821172000_establish_ai_agent_context_capability_and_portability_governance.md) | Governance for portable AI-agent workflows and retained context | +## Runtime Architecture + +Guides describing the running application's composition and behavior. They +complement ADRs, which record accepted architectural decisions. + +| Document | Description | +| ------------------------------------------------ | ----------------------------------------------------------------------------------- | +| [architecture/README.md](architecture/README.md) | Runtime architecture index: tracker instances, shared services, and event topology. | + ## Architecture Decisions (ADRs) Records of significant architectural decisions, including context and consequences. diff --git a/docs/issues/drafts/generalize-error-events.md b/docs/issues/drafts/generalize-error-events.md index 1265d743f..be4f76661 100644 --- a/docs/issues/drafts/generalize-error-events.md +++ b/docs/issues/drafts/generalize-error-events.md @@ -10,7 +10,7 @@ semantic-links: - create-issue related-artifacts: - docs/adrs/20260727000000_events_are_objective_facts.md - - docs/events-architecture.md + - docs/architecture/events.md - docs/issues/open/1987-add-config-option-to-use-ip-from-announce-query-string/error-event-observability-analysis.md - packages/events/src/bus.rs - packages/http-core/src/event.rs @@ -154,6 +154,6 @@ For each implementation subissue: ## References -- Events architecture: `docs/events-architecture.md` +- Events architecture: `docs/architecture/events.md` - Governing ADR: `docs/adrs/20260727000000_events_are_objective_facts.md` - #1987 analysis: `docs/issues/open/1987-add-config-option-to-use-ip-from-announce-query-string/error-event-observability-analysis.md` diff --git a/docs/issues/drafts/optimize-event-publication-without-consumers/ISSUE.md b/docs/issues/drafts/optimize-event-publication-without-consumers/ISSUE.md index 1e201fabc..e2d06c731 100644 --- a/docs/issues/drafts/optimize-event-publication-without-consumers/ISSUE.md +++ b/docs/issues/drafts/optimize-event-publication-without-consumers/ISSUE.md @@ -18,7 +18,7 @@ semantic-links: - packages/udp-core/src/container.rs - packages/udp-server/src/container.rs - src/container.rs - - docs/events-architecture.md + - docs/architecture/events.md - docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md - docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md --- @@ -189,5 +189,5 @@ Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. - Related issues: Issue #2035 and Issue #2039 - Related PRs: PR #2044 and PR #2048 -- Events architecture: `docs/events-architecture.md` +- Events architecture: `docs/architecture/events.md` - Event bus: `packages/events/src/bus.rs` diff --git a/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md b/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md index d9c695dbe..ae5a9ed9b 100644 --- a/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md +++ b/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md @@ -19,7 +19,7 @@ semantic-links: - docs/issues/closed/2036-add-runtime-service-registry-metadata/ISSUE.md - docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md - docs/issues/closed/2041-migrate-runtime-service-registry-metadata/ISSUE.md - - docs/events-architecture.md + - docs/architecture/events.md - evidence.md - tests/metrics/fixed_ports.rs related-issues: @@ -209,4 +209,4 @@ evidence. - Bug #2039: [normalize per-instance event metrics policy](../2039-normalize-per-instance-event-metrics-policy/ISSUE.md) - Issue #2041: [migrate runtime service registry metadata](../../closed/2041-migrate-runtime-service-registry-metadata/ISSUE.md) - [Archived implementation attempt](archived-attempt.md) -- [Events architecture](../../../events-architecture.md) +- [Events architecture](../../../architecture/events.md) diff --git a/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/archived-attempt.md b/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/archived-attempt.md index 5450b3cda..bf8b891b4 100644 --- a/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/archived-attempt.md +++ b/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/archived-attempt.md @@ -1,3 +1,13 @@ +--- +semantic-links: + skill-links: + - write-markdown-docs + related-artifacts: + - docs/architecture/events.md + - docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md + - docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/evidence.md +--- + # Archived Implementation Attempt ## Status @@ -34,4 +44,4 @@ metrics policy must be delivered in a coherent order: The archive remains useful for the reproduction, tests, and design questions; it is not an accepted implementation. See [the revised #2035 plan](ISSUE.md) and the -[event architecture guide](../../../events-architecture.md). +[event architecture guide](../../../architecture/events.md). diff --git a/docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md b/docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md index 412648f2c..65b1ed29d 100644 --- a/docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md +++ b/docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md @@ -14,7 +14,7 @@ semantic-links: - write-unit-test related-artifacts: - .github/skills/dev/planning/create-issue/SKILL.md - - docs/events-architecture.md + - docs/architecture/events.md - docs/adrs/20260727000000_events_are_objective_facts.md - docs/adrs/20260727180000_shared_services_across_tracker_instances.md - docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md @@ -269,7 +269,7 @@ metrics-disabled listener still reach shared ban enforcement. ## References -- [Events architecture](../../../events-architecture.md) +- [Events architecture](../../../architecture/events.md) - [#1263][1263] - [#1401][1401] - [#2035][2035] diff --git a/docs/issues/open/2095-organize-runtime-architecture-documentation.md b/docs/issues/open/2095-organize-runtime-architecture-documentation.md new file mode 100644 index 000000000..2b8c48d0c --- /dev/null +++ b/docs/issues/open/2095-organize-runtime-architecture-documentation.md @@ -0,0 +1,191 @@ +--- +doc-type: issue +issue-type: task +status: in-progress +priority: p2 +epic: null +github-issue: 2095 +spec-path: docs/issues/open/2095-organize-runtime-architecture-documentation.md +branch: "2095-organize-runtime-architecture-documentation" +related-pr: null +last-updated-utc: 2026-08-25 12:16 +semantic-links: + skill-links: + - create-issue + - write-markdown-docs + related-artifacts: + - docs/AGENTS.md + - docs/index.md + - docs/packages.md + - docs/application-jobs.md + - docs/architecture/README.md + - docs/architecture/events.md + - docs/architecture/tracker-instance-architecture.md + - docs/adrs/20260727180000_shared_services_across_tracker_instances.md + - docs/skills/semantic-skill-link-convention.md + - .github/skills/dev/planning/create-issue/SKILL.md + - .github/skills/dev/planning/write-markdown-docs/SKILL.md +--- + +# Issue #2095 - Organize Runtime Architecture Documentation + +## Goal + +Create a discoverable `docs/architecture/` documentation area that explains +the tracker runtime architecture. Move the event-topology guide into that area +and add a canonical guide describing the one-process, multiple-listener model, +including the boundary between shared services and listener-specific +configuration. + +## Background + +Torrust Tracker can expose multiple HTTP and UDP listener instances from one +process. This is intentionally not a supervisor for independent trackers: +listener instances share one logical tracker core, swarm data, policy +configuration, and selected protocol services. The design is recorded partly in +ADR-20260727180000 and the event-topology guide, but no central document +explains the full runtime composition and its configuration and deployment +consequences. + +Recent configuration work correctly moved independently applicable settings, +such as network topology and metrics policy, to listener instances. This does +not make tracker instances independent. Values governing the shared tracker +core, including private mode, whitelist/listing authorization, announce policy, +and tracker policy, remain process-wide. A private HTTP listener and public UDP +listener cannot operate as independent trackers in one process. Operators need +separate processes for isolated swarm, authentication, whitelist, or policy +state. + +The event-topology guide is an evolving architecture guide, not an ADR. Placing +it under `docs/architecture/` creates a coherent home for it and future runtime +explanations without mixing them with immutable decisions. + +## Scope + +### In Scope + +- Create `docs/architecture/README.md` as the architecture-guide index. +- Place the event-topology guide at `docs/architecture/events.md`. +- Add `docs/architecture/tracker-instance-architecture.md` as the canonical + runtime-composition guide. +- Describe shared services, listener-owned services and configuration, + configuration-placement rules, and the boundary between multiple listeners + and multiple tracker processes. +- Correct ADR-20260727180000's adapter ownership details and link it to the + canonical guide. +- Update durable cross-references, documentation indexes, and semantic-link + frontmatter affected by the move. + +### Out of Scope + +- Runtime, configuration-schema, dependency, or service-ownership changes. +- Migrating the active application runtime from configuration v2 to v3. +- Moving `docs/packages.md` or `docs/application-jobs.md`. +- Creating an ADR; this task documents accepted decisions rather than changing + them. + +## Architectural Decisions + +- Related ADRs: + - `docs/adrs/20260727180000_shared_services_across_tracker_instances.md` + - `docs/adrs/20260727000000_events_are_objective_facts.md` + - `docs/adrs/20260721000000_make_network_configuration_per_tracker_instance.md` +- ADRs to create: None known. + +## Implementation Plan + +Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. + +| ID | Status | Task | Notes / Expected Output | +| --- | ------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| T1 | DONE | Create architecture documentation index | Added `docs/architecture/README.md` with scoped guide and related-document navigation. | +| T2 | DONE | Move the event architecture guide | Relocated it to `docs/architecture/events.md` and updated durable repository links. | +| T3 | DONE | Document tracker-instance architecture | Added canonical guide for shared state, listener responsibilities, configuration placement, and process isolation. | +| T4 | DONE | Correct shared-services ADR | Corrected adapter ownership, binding clarification, and guide links. | +| T5 | DONE | Update references and indexes | Updated `docs/index.md`, `docs/AGENTS.md`, active/draft references, and semantic-link metadata. | +| T6 | DONE | Validate documentation | `git diff --check`, Markdown, spelling, and full `linter all` checks passed; manual scenarios recorded below. | +| T7 | DONE | Re-review acceptance criteria | Re-reviewed completed artifacts against every acceptance criterion. | + +## Progress Tracking + +### Workflow Checkpoints + +- [x] 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 +- [ ] (Optional, recommended for complex issues) Spec-only PR merged into `develop` before implementation +- [x] Implementation completed +- [x] Automatic verification completed (`linter all`, relevant tests, and any pre-push checks) +- [x] Manual verification scenarios executed and recorded (status + evidence) +- [x] Acceptance criteria reviewed after implementation and updated with evidence +- [ ] Reviewer validated acceptance criteria and updated checkboxes +- [x] Committer verified spec progress is up to date before commit +- [ ] Issue closed and spec moved from `docs/issues/open/` to `docs/issues/closed/` + +### Progress Log + +- 2026-08-25 11:56 UTC - GitHub Copilot - Drafted specification from the architecture documentation review. +- 2026-08-25 12:00 UTC - GitHub Copilot - User approved the specification; created GitHub issue #2095 and implementation branch. +- 2026-08-25 12:16 UTC - GitHub Copilot - Created the architecture documentation area, relocated the event guide, added the tracker-instance guide, updated references and semantic links, and passed `linter all`. + +## Acceptance Criteria + +- [x] AC1: `docs/architecture/README.md` exists and indexes runtime architecture guides, relevant ADRs, package architecture, and job ownership documentation without duplicating them. +- [x] AC2: The event-topology guide is at `docs/architecture/events.md`, and durable repository references to the old path are updated. +- [x] AC3: A canonical tracker-instance guide explains that HTTP/UDP listeners in one process serve one logical tracker and identifies shared state/services and listener-owned concerns. +- [x] AC4: The new guide defines a configuration-placement rule and explains that isolated policies or swarm/authentication data require separate tracker processes. +- [x] AC5: ADR-20260727180000 accurately distinguishes shared state/services from per-listener HTTP and UDP protocol adapters and links to the guide. +- [x] AC6: Every new or modified Markdown artifact contains accurate YAML-frontmatter semantic links. +- [x] AC7: `linter all` exits with code `0`. +- [x] AC8: Manual verification scenarios are executed and documented with status and evidence. +- [x] AC9: Acceptance criteria are re-reviewed after implementation and reflect actual behavior. + +## Verification Plan + +### Automatic Checks + +- `linter markdown` +- `linter cspell` +- `linter all` +- Search for stale references to the previous event-guide location and verify + that no durable links remain. + +### Manual Verification Scenarios + +Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. + +| ID | Scenario | Command/Steps | Expected Result | Status | Evidence | +| --- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- | +| M1 | Navigate architecture documentation | Read `docs/index.md`, then `docs/architecture/README.md`, then each listed guide. | A contributor can locate runtime composition, event topology, package boundaries, job ownership, and ADR records without guesswork. | DONE | Reviewed the documentation index and architecture index links after implementation. | +| M2 | Verify instance-boundary explanation | Compare the guide with `src/container.rs` and tracker, HTTP, UDP-core, and UDP-server container implementations. | The guide correctly separates shared services from listener-owned adapters/configuration and identifies the multi-process isolation boundary. | DONE | Compared guide content with the documented container construction paths during the architecture review. | +| M3 | Verify path and semantic-link migration | Search for the old event-guide path and inspect frontmatter in every touched Markdown document. | No stale durable links remain; every semantic link targets a stable existing artifact or accepted issue/ADR reference. | DONE | Repository search returned no `events-architecture.md` references; reviewed frontmatter for every modified Markdown artifact. | + +### Acceptance Verification + +| AC ID | Status (`TODO`/`DONE`) | Evidence | +| ----- | ---------------------- | -------------------------------------------------------------------------------------- | +| AC1 | DONE | `docs/architecture/README.md` | +| AC2 | DONE | `docs/architecture/events.md`; stale-path repository search returned no results | +| AC3 | DONE | `docs/architecture/tracker-instance-architecture.md` | +| AC4 | DONE | Configuration Placement Rule and Multiple Listeners Versus Multiple Processes sections | +| AC5 | DONE | Updated ADR-20260727180000 | +| AC6 | DONE | Frontmatter inspected in all added and modified Markdown documents | +| AC7 | DONE | `linter all` completed successfully at 2026-08-25 12:16 UTC | +| AC8 | DONE | M1 through M3 recorded above | +| AC9 | DONE | This completed acceptance-verification table | + +## Risks and Trade-offs + +- Moving a canonical document can leave stale long-lived issue-specification + links. Mitigation: search the complete repository and update durable links. +- A new guide could duplicate ADR, package, or job guidance. Mitigation: give + every document a narrow responsibility and link rather than duplicate. +- Configuration v3 is not active. Mitigation: distinguish current shared + topology from the intended v3 configuration boundary. + +## References + +- Shared-services decision: `docs/adrs/20260727180000_shared_services_across_tracker_instances.md` +- Events decision: `docs/adrs/20260727000000_events_are_objective_facts.md` +- Per-instance network decision: `docs/adrs/20260721000000_make_network_configuration_per_tracker_instance.md` +- Semantic-link convention: `docs/skills/semantic-skill-link-convention.md` From b6f7c7f3e540969906d97826b9134f972f450fe4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 14:05:55 +0100 Subject: [PATCH 05/22] docs(review): address Copilot suggestions --- docs/architecture/tracker-instance-architecture.md | 2 +- ...2095-organize-runtime-architecture-documentation.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/architecture/tracker-instance-architecture.md b/docs/architecture/tracker-instance-architecture.md index 9ce666c63..595985bc1 100644 --- a/docs/architecture/tracker-instance-architecture.md +++ b/docs/architecture/tracker-instance-architecture.md @@ -9,7 +9,7 @@ semantic-links: - docs/packages.md - docs/adrs/20260721000000_make_network_configuration_per_tracker_instance.md - docs/adrs/20260727180000_shared_services_across_tracker_instances.md - - docs/issues/open/1980-1978-configuration-overhaul-final-cleanup.md + - issue #1980 - src/container.rs - packages/tracker-core/src/container.rs - packages/http-core/src/container.rs diff --git a/docs/issues/open/2095-organize-runtime-architecture-documentation.md b/docs/issues/open/2095-organize-runtime-architecture-documentation.md index 2b8c48d0c..c0d347090 100644 --- a/docs/issues/open/2095-organize-runtime-architecture-documentation.md +++ b/docs/issues/open/2095-organize-runtime-architecture-documentation.md @@ -154,11 +154,11 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. -| ID | Scenario | Command/Steps | Expected Result | Status | Evidence | -| --- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- | -| M1 | Navigate architecture documentation | Read `docs/index.md`, then `docs/architecture/README.md`, then each listed guide. | A contributor can locate runtime composition, event topology, package boundaries, job ownership, and ADR records without guesswork. | DONE | Reviewed the documentation index and architecture index links after implementation. | -| M2 | Verify instance-boundary explanation | Compare the guide with `src/container.rs` and tracker, HTTP, UDP-core, and UDP-server container implementations. | The guide correctly separates shared services from listener-owned adapters/configuration and identifies the multi-process isolation boundary. | DONE | Compared guide content with the documented container construction paths during the architecture review. | -| M3 | Verify path and semantic-link migration | Search for the old event-guide path and inspect frontmatter in every touched Markdown document. | No stale durable links remain; every semantic link targets a stable existing artifact or accepted issue/ADR reference. | DONE | Repository search returned no `events-architecture.md` references; reviewed frontmatter for every modified Markdown artifact. | +| ID | Scenario | Command/Steps | Expected Result | Status | Evidence | +| --- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | +| M1 | Navigate architecture documentation | Read `docs/index.md`, then `docs/architecture/README.md`, then each listed guide. | A contributor can locate runtime composition, event topology, package boundaries, job ownership, and ADR records without guesswork. | DONE | Reviewed the documentation index and architecture index links after implementation. | +| M2 | Verify instance-boundary explanation | Compare the guide with `src/container.rs` and tracker, HTTP, UDP-core, and UDP-server container implementations. | The guide correctly separates shared services from listener-owned adapters/configuration and identifies the multi-process isolation boundary. | DONE | Compared guide content with the documented container construction paths during the architecture review. | +| M3 | Verify path and semantic-link migration | Search for the old event-guide path and inspect frontmatter in every touched Markdown document. | No stale durable links remain; every semantic link targets a stable existing artifact or accepted issue/ADR reference. | DONE | Repository search for the former event-guide location found no stale references; reviewed frontmatter for every modified Markdown artifact. | ### Acceptance Verification From 3e67b8955b4f38deb99da266a1281b8e57c18449 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 14:23:57 +0100 Subject: [PATCH 06/22] docs(review): document PR 2097 Copilot audit --- .../pr-reviews/pr-2097-copilot-suggestions.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/pr-reviews/pr-2097-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2097-copilot-suggestions.md b/docs/pr-reviews/pr-2097-copilot-suggestions.md new file mode 100644 index 000000000..84844eb45 --- /dev/null +++ b/docs/pr-reviews/pr-2097-copilot-suggestions.md @@ -0,0 +1,53 @@ +--- +semantic-links: + skill-links: + - process-copilot-suggestions + related-artifacts: + - .github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md +--- + + + + + +# PR #2097 Copilot Suggestions Tracking + +Source: Copilot PR review threads for https://github.com/torrust/torrust-tracker/pull/2097 + +Status legend: + +- `action`: code/docs change applied +- `no-action`: suggestion reviewed; no code change needed +- `resolved`: thread resolved in PR + +## Workflow + +1. Download all review threads (including resolved/outdated state and thread IDs). +2. Add one row per thread in the Suggestions table. +3. Process suggestions one by one: + - decide `action` or `no-action` + - if `action`, apply change and validate + - if needed, commit changes + - reply on the PR thread with the fix commit and outcome, or the no-action rationale + - resolve the PR thread + +4. Set `Thread State` to `resolved` once resolved in PR. + +## Processing Log + +- 2026-08-25: Started processing suggestions. +- 2026-08-25: Processed two Copilot suggestions, posted replies, and resolved both threads; the final GitHub refresh found no unresolved threads. + +## Suggestions + +| # | Thread ID | Path | URL | Suggestion Summary | Decision | Reply URL | Status | Thread State | +| --- | ----------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------- | --------- | ------ | ------------ | +| 1 | `PRRT_kwDOGp2yqc6cE8Pr` | `docs/architecture/tracker-instance-architecture.md` | https://github.com/torrust/torrust-tracker/pull/2097#discussion_r3853127748 | Use a durable issue-number semantic link instead of an open issue-specification path. | action: replaced the path with `issue #1980`. | https://github.com/torrust/torrust-tracker/pull/2097#discussion_r3853243087 | DONE | RESOLVED | +| 2 | `PRRT_kwDOGp2yqc6cE8P_` | `docs/issues/open/2095-organize-runtime-architecture-documentation.md` | https://github.com/torrust/torrust-tracker/pull/2097#discussion_r3853127775 | Avoid self-contradictory evidence for the stale event-guide path search. | action: rephrased evidence without including the searched path. | https://github.com/torrust/torrust-tracker/pull/2097#discussion_r3853397168 | DONE | RESOLVED | + +## Notes + +- Keep this file as an audit log of review handling for the PR. +- Prefer concise decisions with explicit rationale. +- If no code changes are needed, explain why in `Decision`. +- Reply on every PR suggestion thread before resolving it so the decision is visible to reviewers. From c1742c06f667bbd3d35ee7f953cc645b1eac5cd6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 17:45:15 +0100 Subject: [PATCH 07/22] docs(configuration): define optional persistence activation plan --- .../144-make-rest-api-persistence-aware.md | 148 +++++++ .../1978-configuration-overhaul-epic/EPIC.md | 52 ++- .../configuration-v2-to-v3-migration.md | 22 + .../ISSUE.md | 188 ++++++-- .../adr-draft.md | 136 ++++++ .../analysis.md | 411 ++++++++++++++---- .../baseline-e2e-verification.md | 26 +- .../persistence-awareness-epic-draft.md | 151 +++++++ ...rsistence-free-runtime-activation-draft.md | 122 ++++++ .../persistence-unavailable-scenarios.md | 65 +++ .../solution.md | 280 +++++++++--- 11 files changed, 1394 insertions(+), 207 deletions(-) create mode 100644 docs/issues/drafts/144-make-rest-api-persistence-aware.md create mode 100644 docs/issues/open/999-1978-optional-database-configuration/adr-draft.md create mode 100644 docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md create mode 100644 docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md create mode 100644 docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md diff --git a/docs/issues/drafts/144-make-rest-api-persistence-aware.md b/docs/issues/drafts/144-make-rest-api-persistence-aware.md new file mode 100644 index 000000000..bccd0a840 --- /dev/null +++ b/docs/issues/drafts/144-make-rest-api-persistence-aware.md @@ -0,0 +1,148 @@ +--- +doc-type: issue +issue-type: enhancement +status: draft +priority: p2 +epic: 144 +github-issue: null +spec-path: docs/issues/drafts/144-make-rest-api-persistence-aware.md +branch: null +related-pr: null +last-updated-utc: 2026-08-25 00:00 +semantic-links: + skill-links: + - create-issue + related-artifacts: + - docs/issues/open/999-1978-optional-database-configuration/solution.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md + - packages/rest-api-protocol/ + - packages/rest-api-application/ + - packages/rest-api-runtime-adapter/ + - packages/axum-rest-api-server/ +--- + +# Make the REST API persistence-aware + +## Subissue of EPIC #144 — next-major REST API work + +## Problem + +The tracker’s target architecture permits a persistence-free runtime, but the +current REST API assumes persistence-backed whitelist and key services exist. +It also exposes completed counters documented as historical values even when a +value is only known for the current tracker process. + +The current code conflates distinct states: + +1. A feature is disabled deliberately by configuration. +2. A configured database fails operationally after startup. +3. A current/session metric exists, while its historical counterpart is + unavailable. + +A direct whitelist or key operation for a disabled capability must not become a +misleading database failure. Likewise, a session-only completed count must not +be documented or serialized as an undifferentiated lifetime count. + +Issue #999 records the source-level inventory and the approved target behavior +in `persistence-unavailable-scenarios.md`. Its small activation follow-up keeps +`http_api` persistence-required until this next-major REST API contract work is +complete. + +## Goal + +Make the REST API explicitly capability-aware and persistence-aware so it can +start in a persistence-free tracker deployment without confusing intentional +configuration, operational failures, session values, and historical values. + +## Approved Contract Direction + +### Disabled direct capabilities + +When a client calls a whitelist route while `core.listed = false`, or a key +route while `core.private = false`, return HTTP `409 Conflict` using the +existing `ActionStatus::Err` response shape: + +```json +{ + "status": "err", + "reason": "Whitelist capability is disabled by configuration (`core.listed = false`)." +} +``` + +The protocol/application boundary must carry a distinct +`DisabledByConfiguration` error. It must not reuse database error variants. + +Configured database failures remain a distinct operational state and retain +server-error handling; they are not configuration-disabled responses. + +### Completed metric semantics + +Keep in-memory routes available when their data is meaningful. Do not use a +negative numeric sentinel for missing history. Replace the ambiguous historical +meaning of `completed: u64` with an explicit next-major response model that can +distinguish at least: + +- session-only value; +- restored/persisted historical value; and +- unavailable historical value. + +The final DTO names and migration policy require review during implementation. + +## Scope + +### In Scope + +- Add capability-aware composition for REST API services and routes. +- Add the HTTP 409 configuration-disabled response for direct whitelist and key + operations. +- Preserve the distinction between configuration-disabled and operational + database failure across protocol, application, runtime-adapter, and Axum + layers. +- Define and implement next-major explicit completed-metric provenance/history + semantics for stats and torrent responses. +- Update REST API client models, contract tests, documentation, and migration + guidance for the new major API contract. +- Enable the persistence-free runtime activation follow-up to remove its + temporary `http_api` persistence requirement. + +### Out of Scope + +- Changing v2 tracker configuration behavior. +- Replacing the tracker persistence schema or creating feature-specific + migration streams. +- Hiding supported routes with an accidental 404 or reporting disabled + capabilities as authorization failures. +- Using numeric sentinels for missing historical values. + +## Implementation Considerations + +| Area | Expected work | +| -------------------------- | --------------------------------------------------------------------------------------- | +| `rest-api-protocol` | Add a distinct disabled-by-configuration error and next-major response DTOs. | +| `rest-api-application` | Preserve the disabled capability state through use cases. | +| `rest-api-runtime-adapter` | Compose optional capability services and provide in-memory adapters where appropriate. | +| `axum-rest-api-server` | Map disabled capability errors to HTTP 409 and update route contract tests. | +| `rest-api-client` | Update next-major client DTOs and error handling. | +| Tracker activation | Remove `http_api` from the persistence-required matrix once this contract is available. | + +## Verification + +- [ ] Contract tests distinguish disabled capability (409) from operational + database failure (server error). +- [ ] Whitelist/key disabled routes do not attempt persistence access. +- [ ] Stats/torrent responses explicitly describe current versus historical + completed values. +- [ ] No response uses a negative numeric sentinel for unavailable history. +- [ ] REST API starts in the persistence-free tracker scenario after its + composition dependencies are updated. +- [ ] REST API client and user-facing migration documentation are updated. +- [ ] `linter all` and relevant workspace tests pass. + +## References + +- GitHub EPIC issue #144 +- Issue #999 +- `docs/issues/open/999-1978-optional-database-configuration/solution.md` +- `docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md` +- `docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md` diff --git a/docs/issues/open/1978-configuration-overhaul-epic/EPIC.md b/docs/issues/open/1978-configuration-overhaul-epic/EPIC.md index d1cc316d6..3d00effc3 100644 --- a/docs/issues/open/1978-configuration-overhaul-epic/EPIC.md +++ b/docs/issues/open/1978-configuration-overhaul-epic/EPIC.md @@ -89,23 +89,23 @@ version from `2.0.0` to `3.0.0`. Status values: `TODO`, `IN_PROGRESS`, `IN_REVIEW`, `BLOCKED`, `DONE`. -| Order | Issue | Local Spec | Status | Notes | -| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | [#1979](https://github.com/torrust/torrust-tracker/issues/1979) — Copy `v2_0_0` → `v3_0_0` as baseline | `docs/issues/closed/1979-1978-copy-configuration-schema-v2-to-v3-baseline.md` | DONE | Merged in PR #1999; v3 baseline and smoke tests are in `develop` | -| 2 | [#1981](https://github.com/torrust/torrust-tracker/issues/1981) — Fix `tsl_config` → `tls_config` typo | `docs/issues/closed/1981-1978-fix-tsl-config-tls-config-typo.md` | DONE | Implemented for v3; v2 compatibility retained until final migration | -| 3 | [#1640](https://github.com/torrust/torrust-tracker/issues/1640) — Support per-HTTP-tracker `on_reverse_proxy` setting | `docs/issues/closed/1640-1978-per-http-tracker-on-reverse-proxy-setting.md` | DONE | Merged in PR #2014; v3 schema slice complete; runtime consumers deferred to #1980 (subissue #12) | -| 4 | [#1417](https://github.com/torrust/torrust-tracker/issues/1417) — Include public service URL in configuration | `docs/issues/closed/1417-1978-add-public-service-url-to-configuration.md` | DONE | Merged in PR #2016; typed `Option`/`Option` newtypes on `HttpTracker`, `UdpTracker`, `HttpApi`; scheme validation at deserialization | -| 5 | [#1415](https://github.com/torrust/torrust-tracker/issues/1415) — Use `ServiceBinding` instead of bare `SocketAddr` for service identity | `docs/issues/closed/1415-1978-use-service-binding-instead-of-socket-addr/ISSUE.md` | DONE | Added protocol-aware `service_binding` alongside compatible `server_socket_addr` fields in HTTP tracker, REST API, and UDP error logs; verified manually. | -| 6 | [#1453](https://github.com/torrust/torrust-tracker/issues/1453) — IP bans reset interval configurable + fix duplicate cleanup | `docs/issues/closed/1453-1978-ip-bans-reset-interval-configurable/ISSUE.md` | DONE | V3 setting validated; one cancellation-managed bootstrap cleanup job uses the v3 default constant. Runtime configuration use is deferred to #1980. | -| 7 | [#1136](https://github.com/torrust/torrust-tracker/issues/1136) — Add configurable UDP connection ID validation policy | `docs/issues/closed/1136-1978-configurable-udp-connection-id-validation-policy.md` | DONE | PR #2032 merged; all 12 ACs met; manual verification deferred to #1980. | -| 8 | [#1490](https://github.com/torrust/torrust-tracker/issues/1490) — Decompose v3 database configuration | `docs/issues/open/1490-1978-decompose-database-configuration.md` | TODO | After #3 and #2079; isolates the v3 password as `Secret`. | -| 8a | [#999](https://github.com/torrust/torrust-tracker/issues/999) — Make v3 database configuration optional when persistence is unused | `docs/issues/open/999-1978-optional-database-configuration/ISSUE.md` | TODO | Follows #1490. Analysis and solution determine persistence dependencies, REST API behaviour, and whether it blocks #1980/v3 activation. | -| 9 | [#889](https://github.com/torrust/torrust-tracker/issues/889) — New config option for logging style | `docs/issues/closed/889-1978-new-config-option-for-logging-style.md` | DONE | V3 schema implemented; includes negative test for removed `threshold` key. Manual verification is deferred to #1980. | -| 10 | [#1987](https://github.com/torrust/torrust-tracker/issues/1987) — Use peer IP from the HTTP announce `ip` parameter when configured | `docs/issues/open/1987-add-config-option-to-use-ip-from-announce-query-string/ISSUE.md` | TODO | After #3 and external prerequisite #1985; per-HTTP-tracker opt-in policy | -| 11 | [#2083](https://github.com/torrust/torrust-tracker/issues/2083) — Move UDP connection-ID error limit to shared server configuration | `docs/issues/open/2083-1978-move-max-connection-id-errors-per-ip-to-udp-tracker-server.md` | TODO | Corrects the v3 shared UDP `BanService` configuration boundary found in #2067; blocks #1980 production activation. | -| 12 | [#1980](https://github.com/torrust/torrust-tracker/issues/1980) — Final cleanup: remove global re-exports, migrate consumers to explicit v3 imports | `docs/issues/open/1980-1978-configuration-overhaul-final-cleanup.md` | TODO | Must follow all implemented schema subissues, including #2083, and the secrecy release gate. | -| 13 | [#2023](https://github.com/torrust/torrust-tracker/issues/2023) — Expose configured public URLs in runtime observability | `docs/issues/open/2023-1978-expose-configured-public-urls-in-runtime-observability.md` | TODO | Must follow #1417 and #1980; adds `public_url` to health checks, metrics, and logs without replacing ServiceBinding. | -| 14 | [#2067](https://github.com/torrust/torrust-tracker/issues/2067) — Analyze a flat heterogeneous service configuration | `docs/issues/open/2067-1978-analyze-flat-service-configuration/ISSUE.md` | TODO | Non-blocking analysis only; its confirmed configuration-model bug is tracked by #2083. | +| Order | Issue | Local Spec | Status | Notes | +| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | [#1979](https://github.com/torrust/torrust-tracker/issues/1979) — Copy `v2_0_0` → `v3_0_0` as baseline | `docs/issues/closed/1979-1978-copy-configuration-schema-v2-to-v3-baseline.md` | DONE | Merged in PR #1999; v3 baseline and smoke tests are in `develop` | +| 2 | [#1981](https://github.com/torrust/torrust-tracker/issues/1981) — Fix `tsl_config` → `tls_config` typo | `docs/issues/closed/1981-1978-fix-tsl-config-tls-config-typo.md` | DONE | Implemented for v3; v2 compatibility retained until final migration | +| 3 | [#1640](https://github.com/torrust/torrust-tracker/issues/1640) — Support per-HTTP-tracker `on_reverse_proxy` setting | `docs/issues/closed/1640-1978-per-http-tracker-on-reverse-proxy-setting.md` | DONE | Merged in PR #2014; v3 schema slice complete; runtime consumers deferred to #1980 (subissue #12) | +| 4 | [#1417](https://github.com/torrust/torrust-tracker/issues/1417) — Include public service URL in configuration | `docs/issues/closed/1417-1978-add-public-service-url-to-configuration.md` | DONE | Merged in PR #2016; typed `Option`/`Option` newtypes on `HttpTracker`, `UdpTracker`, `HttpApi`; scheme validation at deserialization | +| 5 | [#1415](https://github.com/torrust/torrust-tracker/issues/1415) — Use `ServiceBinding` instead of bare `SocketAddr` for service identity | `docs/issues/closed/1415-1978-use-service-binding-instead-of-socket-addr/ISSUE.md` | DONE | Added protocol-aware `service_binding` alongside compatible `server_socket_addr` fields in HTTP tracker, REST API, and UDP error logs; verified manually. | +| 6 | [#1453](https://github.com/torrust/torrust-tracker/issues/1453) — IP bans reset interval configurable + fix duplicate cleanup | `docs/issues/closed/1453-1978-ip-bans-reset-interval-configurable/ISSUE.md` | DONE | V3 setting validated; one cancellation-managed bootstrap cleanup job uses the v3 default constant. Runtime configuration use is deferred to #1980. | +| 7 | [#1136](https://github.com/torrust/torrust-tracker/issues/1136) — Add configurable UDP connection ID validation policy | `docs/issues/closed/1136-1978-configurable-udp-connection-id-validation-policy.md` | DONE | PR #2032 merged; all 12 ACs met; manual verification deferred to #1980. | +| 8 | [#1490](https://github.com/torrust/torrust-tracker/issues/1490) — Decompose v3 database configuration | `docs/issues/open/1490-1978-decompose-database-configuration.md` | TODO | After #3 and #2079; isolates the v3 password as `Secret`. | +| 8a | [#999](https://github.com/torrust/torrust-tracker/issues/999) — Make v3 database configuration optional when persistence is unused | `docs/issues/open/999-1978-optional-database-configuration/ISSUE.md` | TODO | Follows #1490. Adds v3 `Option`, optional container dependencies, and a temporary bridge for #1980; a small post-#1980 follow-up activates persistence-free runtime behavior. | +| 9 | [#889](https://github.com/torrust/torrust-tracker/issues/889) — New config option for logging style | `docs/issues/closed/889-1978-new-config-option-for-logging-style.md` | DONE | V3 schema implemented; includes negative test for removed `threshold` key. Manual verification is deferred to #1980. | +| 10 | [#1987](https://github.com/torrust/torrust-tracker/issues/1987) — Use peer IP from the HTTP announce `ip` parameter when configured | `docs/issues/open/1987-add-config-option-to-use-ip-from-announce-query-string/ISSUE.md` | TODO | After #3 and external prerequisite #1985; per-HTTP-tracker opt-in policy | +| 11 | [#2083](https://github.com/torrust/torrust-tracker/issues/2083) — Move UDP connection-ID error limit to shared server configuration | `docs/issues/open/2083-1978-move-max-connection-id-errors-per-ip-to-udp-tracker-server.md` | TODO | Corrects the v3 shared UDP `BanService` configuration boundary found in #2067; blocks #1980 production activation. | +| 12 | [#1980](https://github.com/torrust/torrust-tracker/issues/1980) — Final cleanup: remove global re-exports, migrate consumers to explicit v3 imports | `docs/issues/open/1980-1978-configuration-overhaul-final-cleanup.md` | TODO | Must follow all implemented schema subissues, including #2083, and the secrecy release gate. | +| 13 | [#2023](https://github.com/torrust/torrust-tracker/issues/2023) — Expose configured public URLs in runtime observability | `docs/issues/open/2023-1978-expose-configured-public-urls-in-runtime-observability.md` | TODO | Must follow #1417 and #1980; adds `public_url` to health checks, metrics, and logs without replacing ServiceBinding. | +| 14 | [#2067](https://github.com/torrust/torrust-tracker/issues/2067) — Analyze a flat heterogeneous service configuration | `docs/issues/open/2067-1978-analyze-flat-service-configuration/ISSUE.md` | TODO | Non-blocking analysis only; its confirmed configuration-model bug is tracked by #2083. | ### Release-gated prerequisite @@ -132,7 +132,7 @@ graph TD sub1 --> secrecy["#2079 Secrecy"] sub3 --> sub8["8. #1490 Database configuration"] secrecy --> sub8 - sub8 --> sub8a["8a. #999 optional database configuration"] + sub8 --> sub8a["8a. #999 optional DB representation"] sub3 --> sub10["10. #1987 Announce IP policy"] sub1 --> sub11["11. #2083 shared UDP error limit"] sub4 --> sub12["12. Final cleanup"] @@ -143,7 +143,8 @@ graph TD sub9 --> sub12 sub10 --> sub12 sub11 --> sub12 - sub8a -. "pending Phase 2 decision" .-> sub12 + sub8a --> sub12["12. #1980 v3 activation with bridge"] + sub12 --> optionalRuntime["Post-#1980 persistence-free activation follow-up"] sub4 --> sub13["13. public_url runtime observability"] sub12 --> sub13 sub12 --> sub14["14. Post-v3 flat-service research"] @@ -155,7 +156,7 @@ graph TD 1 → 2 → 3 → 4 → 12 1 → 2 → 3 → 8 → 12 1 → secrecy → 8 → 12 -1 → 2 → 3 → 8 → 8a → 12 (only if Phase 2 confirms #999 blocks activation) +1 → 2 → 3 → 8 → 8a → 12 → persistence-free activation follow-up 1 → 11 → 12 ``` @@ -182,7 +183,11 @@ Subissues #5, #6, #7, #9 are independent and can run in parallel with the critic - **Subissue #3** (#1640) — Per-instance `Network` block in schema v3.0.0. Establishes the `Network` struct that #4 references; v3 does not support removed v2 field names. - **Release-gated prerequisite #2079** — Adopt `secrecy` for sensitive configuration first. It protects API tokens in v2 and v3 and establishes the `Secret` convention without changing legacy database URLs. - **Subissue #8** (#1490) — Database enum decomposition. After #3 and the secrecy follow-up; it uses `Secret` for the new isolated v3 database password. -- **Subissue #8a** (#999) — Analyze and define v3 optional database configuration after #1490. The analysis-and-solution PR determines every persistence requirement, REST API behaviour, and whether the implementation must precede #1980. +- **Subissue #8a** (#999) — After #1490, introduce the v3 + `Option` representation, optional container dependencies, and a + tested temporary `Some(Database)` bridge. #1980 activates v3 consumers with + that bridge. A small post-#1980 follow-up passes actual `None`, invokes the + reusable validation matrix, and activates persistence-free runtime behavior. - **Subissue #4** (#1417) — `public_url` flat field. After #3 (depends on `Network` placement decision). ~6 files. - **Subissue #10** (#1987) — Opt-in use of the HTTP announce `ip` parameter. After #3 and external prerequisite #1985. @@ -299,6 +304,11 @@ For each subissue implementation in this EPIC, the default completion policy is: - 2026-08-21 16:45 UTC - josecelano - Ordered the smaller secrecy refactor first. It protects API tokens in v2 and v3 without wrapping legacy database URLs; #1490 follows and uses the established `Secret` convention for the isolated v3 database password. - 2026-08-21 17:00 UTC - Copilot/User - Maintainer approved and created the secrecy prerequisite as GitHub issue #2079; moved its specification to `docs/issues/open/`. - 2026-08-25 00:00 UTC - GitHub Copilot/User - Added #999 as a pending v3 configuration subissue after #1490. Its analysis-and-solution phase must decide whether optional database configuration blocks #1980 and v3 activation; v2 remains unchanged. +- 2026-08-25 00:00 UTC - GitHub Copilot/User - Defined staged optional + persistence delivery: #999 adds v3 `Option` and optional container + dependencies; #1980 activates v3 with a temporary bridge; a small follow-up + activates the persistence-free runtime. The next-major REST API response + contract is separately drafted under API EPIC #144. ## Acceptance Criteria diff --git a/docs/issues/open/1978-configuration-overhaul-epic/configuration-v2-to-v3-migration.md b/docs/issues/open/1978-configuration-overhaul-epic/configuration-v2-to-v3-migration.md index 18657b9ea..3af091706 100644 --- a/docs/issues/open/1978-configuration-overhaul-epic/configuration-v2-to-v3-migration.md +++ b/docs/issues/open/1978-configuration-overhaul-epic/configuration-v2-to-v3-migration.md @@ -281,6 +281,26 @@ driver = "sqlite3" path = "/var/lib/torrust/tracker/database/sqlite3.db" ``` +### Optional database representation and staged activation + +**Subissue**: #999 — Optional v3 database configuration + +V3 accepts an omitted `[core.database]` section and represents it as no +configured database. This is a schema/API change first; the runtime activation +is deliberately staged: + +1. #999 introduces the optional representation and optional container + dependencies while retaining an explicit temporary database bridge. +2. #1980 activates v3 runtime consumers with that bridge, preserving the + existing effective database behavior during the configuration transition. +3. A small post-#1980 follow-up honors the omitted database at runtime when no + persistence-required capability is enabled. + +Until the activation follow-up is merged, do not interpret an omitted v3 +database section as a persistence-free running tracker. The final activation +follow-up will document which capabilities require persistence, startup +diagnostics, and supported container behavior. + This is a breaking configuration change: MySQL and PostgreSQL URLs are not accepted in v3. Move their URL components into the fields above. Do not use an empty password: loading rejects missing and empty network database passwords. @@ -332,6 +352,8 @@ Use this checklist to verify your configuration is ready for v3: - [ ] `connection_id_validation` reviewed in `[udp_tracker_server]` (optional, defaults to `"strict"`) - [ ] `ip_bans_reset_interval_in_secs` reviewed in `[udp_tracker_server]` (optional, defaults to `86400`) - [ ] Network database URLs replaced with component fields; database passwords are non-empty +- [ ] Review the staged optional-database activation guidance before omitting + `[core.database]` in a deployed v3 tracker ## References diff --git a/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md b/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md index 6dc28177b..f6a1e322d 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md +++ b/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md @@ -28,6 +28,10 @@ semantic-links: - docs/issues/open/999-1978-optional-database-configuration/analysis.md - docs/issues/open/999-1978-optional-database-configuration/solution.md - docs/issues/open/999-1978-optional-database-configuration/baseline-e2e-verification.md + - docs/issues/open/999-1978-optional-database-configuration/adr-draft.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md --- # Issue #999 - Make v3 database configuration optional when persistence is unused @@ -39,10 +43,12 @@ semantic-links: ## Goal -Allow a tracker using configuration schema v3.0.0 to omit `[core.database]` -when no enabled capability requires persistence. In that mode, startup must not -construct a database driver, create database files, connect to network -databases, or run migrations. +Allow configuration schema v3.0.0 to represent an omitted `[core.database]` +section with `Option`, while preserving the existing effective +database dependency through the temporary v3-activation compatibility bridge. +The post-activation follow-up drafted in this folder will make an omitted +database suppress driver construction, database files, network connections, and +migrations when no enabled capability requires persistence. The tracker must reject an invalid configuration at startup when an enabled persistence-backed capability requires a database but `[core.database]` is @@ -82,14 +88,20 @@ inventoried. - Inventory direct and indirect dependencies on `tracker-core` persistence, including whitelist, torrent metrics, private-tracker keys, and management REST API operations. -- Decide and document startup validation for every enabled capability that - requires persistence. +- Prepare the bootstrap validation design for every enabled capability that + requires persistence; the post-activation follow-up implements it when + bootstrap receives the actual v3 `Option`. - Preserve the all-or-nothing schema lifecycle: once any enabled capability requires persistence, initialize the selected database and apply the complete - shared migration set. Do not create feature-specific database schemas or - feature-specific migration streams. -- Decide and document the management REST API contract when persistence is - unavailable. + shared migration set. Feature configuration controls code behavior, not + schema fragments: do not create feature-specific database schemas, + feature-specific migration streams, or feature-specific migration selection. +- Prepare optional persistence dependencies needed by the management REST API. + The post-activation follow-up keeps it persistence-required; API #144 later + makes it available without persistence and adds explicit + configuration-disabled direct-route responses. +- Prepare a future persistence-awareness EPIC draft for remaining metric + provenance and broader persistence-decoupling behavior. - Define the implementation, regression coverage, migration documentation, and operational verification required by the approved solution. - Determine whether the change must precede #1980 and v3 activation; update @@ -129,21 +141,30 @@ The final design must make an absent database configuration a startup configuration error whenever an enabled capability needs persistence. The exact capability inventory and validation location remain Phase 1 and Phase 2 work. -The expected mechanism is a configuration-consistency rule in -`packages/configuration/src/validator.rs`, invoked during bootstrap before -`AppContainer` construction. The existing precedent is -`UselessPrivateModeSection`: `[core.private_mode]` is rejected unless -`core.private = true`. This issue must use that layer only if the approved -database requirement is a relationship between configuration options; it must -not use it for field-local parsing or value invariants. +The working Phase 2 direction is one reusable bootstrap-owned +application-composition validation step. Issue #999 implements and unit-tests +it, owning the feature-to-database requirement matrix exactly once; the same +rules must not be duplicated in `packages/configuration::Validator`. The +post-#1980 activation follow-up invokes it after v3 configuration loading and +before `AppContainer` construction, once bootstrap receives actual +`Option` rather than the temporary bridge. +The management REST API does not require persistence in the target architecture. +However, the approved HTTP 409 configuration-disabled response contract is +deferred to next-major REST API work in GitHub issue #144. Until that work is +implemented, `http_api` remains persistence-required in the activation +follow-up; it must not reinterpret intentionally absent persistence as an +operational database failure. + +The initial persistence-required capabilities are `core.listed`, `core.private`, +and `core.tracker_policy.persistent_torrent_completed_stat`. If implementation +finds another persistence-required capability, it must be added to the one +centralized bootstrap matrix and its focused tests rather than checked ad hoc +by a repository, route, or feature. The implementation must keep feature-to-database requirements explicit at the application boundary. It must not distribute optional-database checks through repositories or feature implementation code, where a missed call site could -become a delayed runtime failure. Phase 2 will decide whether the bootstrap -rule is implemented through the configuration-consistency validator or a -bootstrap-owned validation step, based on the documented validation-layer -policy and the final configuration model. +become a delayed runtime failure. - Related ADRs: `docs/adrs/20260723184019_separate_configuration_value_invariants_from_consistency_validation.md`. @@ -154,14 +175,14 @@ policy and the final configuration model. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Notes / Expected Output | -| --- | ------ | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| T1 | TODO | Complete persistence analysis | Populate `analysis.md` with evidence for the current lifecycle, all consumers, and driver-specific migration behaviour. | -| T2 | TODO | Approve an optional-persistence design | Populate `solution.md` with the selected v3 contract, validation, API behaviour, compatibility, and ordering decision. | -| T3 | TODO | Implement optional v3 database configuration | Apply only the Phase 2-approved design; do not change v2. | -| T4 | TODO | Add regression coverage | Cover absent and present database configurations, required-feature validation, migrations, and REST API behaviour. | -| T5 | TODO | Update migration and operational documentation | Explain the v2-to-v3 difference and any changed deployment requirements. | -| T6 | TODO | Verify and re-review | Run required automatic and manual checks; update acceptance evidence. | +| ID | Status | Task | Notes / Expected Output | +| --- | ------ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| T1 | DONE | Complete persistence analysis | `analysis.md` records current lifecycle, all discovered consumers, REST coupling, and driver-specific migration behaviour. | +| T2 | DONE | Approve an optional-persistence design | `solution.md` records the approved v3 contract, validation, API deferrals, compatibility bridge, and staged ordering. | +| T3 | TODO | Implement optional v3 database configuration | Apply only the Phase 2-approved design; do not change v2. | +| T4 | TODO | Add regression coverage | Cover absent and present database configurations, required-feature validation, migrations, and REST API behaviour. | +| T5 | TODO | Update migration and operational documentation | Explain the v2-to-v3 difference and any changed deployment requirements. | +| T6 | TODO | Verify and re-review | Run required automatic and manual checks; update acceptance evidence. | ## Progress Tracking @@ -171,7 +192,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. - [x] Spec-only branch created - [x] Folder-based specification scaffold created - [x] Spec reviewed and approved by user/maintainer -- [ ] Spec-only PR merged into `develop` +- [x] Spec-only PR merged into `develop` (#2094, merge commit `7aad6e79`) - [ ] Phase 1 and Phase 2 analysis-and-solution PR merged - [ ] Phase 3 implementation PR merged - [ ] Automatic verification completed @@ -186,6 +207,81 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. the analysis-and-solution work precedes implementation. - 2026-08-25 00:00 UTC - User - Approved the specification for the spec-only PR. +- 2026-08-25 00:00 UTC - GitHub Copilot - Completed Phase 1 evidence in + `analysis.md`: active v2/v3 configuration status, unconditional driver and + migration lifecycle, container side effects, persistence consumers, REST API + routes, validation layering, and Phase 2 questions. No runtime behavior or + solution decision changed. +- 2026-08-25 00:00 UTC - GitHub Copilot/User - Recorded a working Phase 2 + direction in `solution.md`: initial v3 persistence-free operation is limited + to deployments without listing, private keys, persistent completed metrics, + or the management REST API; bootstrap owns one requirement check; a future + persistence-awareness EPIC owns wider API and metric semantics. Explicit + Phase 2 approval remains required. +- 2026-08-25 00:00 UTC - GitHub Copilot/User - Corrected the working direction: + the management REST API remains available without persistence. Phase 3 must + make its construction persistence-aware, return configuration-disabled + responses for direct disabled capabilities, and make metric history explicit. + Added draft ADR and future-EPIC artifacts for refinement during Phase 3. +- 2026-08-25 00:00 UTC - User - Approved v3 `Option` for the + persistence-free contract. The implementation must test versioned v3 + configuration and v3-compatible composition before #1980 activates v3 + production consumers; it must not activate v3 early solely for testing. +- 2026-08-25 00:00 UTC - GitHub Copilot/User - Adopted staged activation: + #999 adds the v3 optional representation and optional container dependencies; + #1980 activates v3 with an explicit temporary database bridge; a small + follow-up then honors `None` at runtime and completes persistence-free + verification. Added the follow-up issue draft. +- 2026-08-25 00:00 UTC - User - Approved bootstrap as the single validation + owner. Issue #999 implements and tests the reusable requirement matrix; the + post-#1980 follow-up invokes it when replacing the temporary bridge with the + actual v3 `Option` value. +- 2026-08-25 00:00 UTC - User - Approved the initial persistence-required + capability matrix: listing, private mode, and persistent completed metrics. + Any implementation discovery must extend the same centralized matrix and + tests, not introduce a feature-local missing-database check. +- 2026-08-25 00:00 UTC - User - Approved `PersistenceRequirementError` with + one diagnostic per persistence-required capability. Approved the desired REST + configuration-disabled contract (HTTP 409, `ActionStatus::Err`, and a + distinct disabled-by-configuration error), but deferred its implementation + and historical-metric API changes to next-major REST API work in GitHub issue + #144. Until then, `http_api` remains persistence-required at activation. +- 2026-08-25 00:00 UTC - User - Confirmed that session-versus-historical + response-field semantics are deferred to the REST API v2 subissue draft under + GitHub issue #144. The approved constraints remain no numeric sentinel and no + session-only value documented as lifetime history. +- 2026-08-25 00:00 UTC - User - Approved the all-or-nothing persistence + lifecycle. Once persistence is present, initialize one driver and the full + shared schema; feature configuration controls code behavior only, not + conditional schema or migration fragments. +- 2026-08-25 00:00 UTC - User - Approved the restart-only, non-destructive + persistence transition contract: disabling persistence leaves prior database + state untouched; re-enabling the same target reuses it; changing targets does + not transfer data automatically; data produced while disabled is not + recoverable. +- 2026-08-25 00:00 UTC - User - Approved the container entrypoint contract: + defer persistence selection to actual v3 configuration, do not perform + persistence-specific setup when absent, and never destructively alter mounted + configuration or database state during transitions. +- 2026-08-25 00:00 UTC - User - Approved `adr-draft.md` as the Phase 3 ADR + starting point. It must be copied to `docs/adrs/` with a timestamped filename + and reconciled with final code, tests, API contract, and review outcome. +- 2026-08-25 00:00 UTC - User - Approved `persistence-awareness-epic-draft.md` + as the post-merge starting point. Reconcile it with merged #999, #1980, + persistence-free activation-follow-up, and API #144 work before creating the + GitHub EPIC. +- 2026-08-25 00:00 UTC - User - Approved the staged #999 -> #1980 -> + persistence-free activation-follow-up ordering. EPIC #1978 and the v2-to-v3 + migration guide record it. The activation-follow-up draft remains planning + only until #999/#1980 implementation evidence permits it to be refined and + opened. +- 2026-08-25 00:00 UTC - User - Approved the Phase 3 implementation and + evidence sequence. The activation-follow-up draft records ownership across + #999, #1980, the later runtime activation, and API #144; do not create that + follow-up issue until preceding implementation evidence is reviewed. +- 2026-08-25 00:00 UTC - User - Approved the complete Phase 2 design for the + analysis-and-solution PR. `solution.md` contains the approval record; Phase 3 + implementation remains a separate delivery. ## Acceptance Criteria @@ -250,22 +346,22 @@ Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. ### Acceptance Verification -| AC ID | Status (`TODO`/`DONE`) | Evidence | -| ----- | ---------------------- | ----------------------------------------------- | -| AC1 | TODO | `analysis.md` lifecycle inventory | -| AC2 | TODO | `analysis.md` consumer and API inventory | -| AC3 | TODO | `solution.md` approved contract | -| AC4 | TODO | Implementation tests and M1 evidence | -| AC5 | TODO | Validation tests and M2 evidence | -| AC6 | TODO | REST API tests and M4 evidence | -| AC7 | TODO | Driver/migration tests and M3 evidence | -| AC8 | TODO | EPIC and migration-document updates | -| AC9 | TODO | V2 compatibility tests/review | -| AC10 | TODO | M5 evidence in `baseline-e2e-verification.md` | -| AC11 | TODO | M6 container-startup evidence | -| AC12 | TODO | `linter all` output | -| AC13 | TODO | Focused, relevant workspace, and M1–M6 evidence | -| AC14 | TODO | Post-implementation acceptance review | +| AC ID | Status (`TODO`/`DONE`) | Evidence | +| ----- | ---------------------- | ---------------------------------------------------- | +| AC1 | DONE | `analysis.md` lifecycle inventory | +| AC2 | DONE | `analysis.md` consumer and API inventory | +| AC3 | DONE | `solution.md` approval record | +| AC4 | TODO | Implementation tests and M1 evidence | +| AC5 | TODO | Validation tests and M2 evidence | +| AC6 | TODO | REST API tests and M4 evidence | +| AC7 | TODO | Driver/migration tests and M3 evidence | +| AC8 | DONE | Approved staged ordering in EPIC and migration guide | +| AC9 | TODO | V2 compatibility tests/review | +| AC10 | TODO | M5 evidence in `baseline-e2e-verification.md` | +| AC11 | TODO | M6 container-startup evidence | +| AC12 | TODO | `linter all` output | +| AC13 | TODO | Focused, relevant workspace, and M1–M6 evidence | +| AC14 | TODO | Post-implementation acceptance review | ## Risks and Trade-offs diff --git a/docs/issues/open/999-1978-optional-database-configuration/adr-draft.md b/docs/issues/open/999-1978-optional-database-configuration/adr-draft.md new file mode 100644 index 000000000..e3b7e645c --- /dev/null +++ b/docs/issues/open/999-1978-optional-database-configuration/adr-draft.md @@ -0,0 +1,136 @@ +--- +status: approved-draft +intended-destination: docs/adrs/ +related-issue: 999 +semantic-links: + related-artifacts: + - docs/issues/open/999-1978-optional-database-configuration/ISSUE.md + - docs/issues/open/999-1978-optional-database-configuration/analysis.md + - docs/issues/open/999-1978-optional-database-configuration/solution.md + - docs/adrs/20260723184019_separate_configuration_value_invariants_from_consistency_validation.md +--- + +# Draft ADR - Make persistence an optional application-composition capability + +> **Approved Phase 2 draft:** Copy this artifact to `docs/adrs/` with its final +> timestamped filename during Phase 3. Reconcile it with the implemented code, +> tests, API contract, and review outcome before treating it as a final ADR. + +## Description + +The tracker historically supports an in-memory deployment, but the active v2 +runtime always constructs a database driver and applies the complete shared +migration set during application-container initialization. The configuration +can omit the v2 `[core.database]` TOML table only because it defaults to +SQLite; the runtime cannot operate without persistence. + +Schema v3 makes the absence of `[core.database]` representable. The actual +persistence-free runtime is delivered by the post-v3-activation follow-up: +until then, bootstrap passes an explicit temporary database dependency to +preserve current effective runtime behavior. + +The management REST API exposes both in-memory tracker information and direct +persistence-backed capabilities. It must remain usable in a persistence-free +deployment, without representing a disabled capability as an accidental +database failure. + +## Agreement + +The v3 application treats persistence as an optional **application-composition +capability**. + +1. `Option` represents configured persistence. An absent database + means persistence is unavailable by configuration. +2. Issue #999 implements and unit-tests one reusable bootstrap-owned + persistence-requirement check. The activation follow-up invokes it after v3 + configuration is loaded and before application-container construction, once + bootstrap receives actual `Option` rather than the temporary + compatibility bridge. The same feature-to-persistence matrix must not be + duplicated in repositories, route handlers, or + `packages/configuration::Validator`. +3. Listing, private-mode keys, and persistent completed statistics require + configured persistence. If one is enabled without `[core.database]`, startup + fails with a diagnostic that names both the enabled capability and the + missing database configuration. +4. When any capability requires persistence, bootstrap constructs one selected + driver and applies the complete shared migration set once. Feature-specific + schemas, migration streams, and migration selection are prohibited. Feature + configuration controls code behavior rather than database fragments. +5. When no capability requires persistence, the activation follow-up's application composition constructs + only in-memory services and no persistence driver or migration side effect. +6. The management REST API may start without persistence only after the + next-major API work tracked by GitHub issue #144 implements the approved + configuration-disabled response model. Until then, it remains + persistence-required at activation. +7. The GitHub issue #144 API work must ensure fields do not silently present + session values as historical persisted values and must not use negative + numeric sentinels for unavailable history. +8. Persistence configuration is evaluated at process startup only. Disabling + persistence never deletes or alters prior database state; re-enabling the + same target reuses it, and changing targets never transfers data + automatically. +9. The container entrypoint defers persistence selection to actual v3 + configuration. It does not require or default a database driver when + persistence is absent, and it never destructively alters mounted state + during a persistence transition. + +## Alternatives Considered + +### Keep a mandatory database in v3 + +Rejected. It abandons the tracker’s explicit in-memory deployment capability +and preserves unconditional persistence coupling. + +### Make persistence optional but let consumers fail when accessed + +Rejected. It makes configuration errors delayed runtime failures and spreads +feature-to-persistence knowledge across consumers. + +### Make the REST API require persistence + +Rejected as the target architecture, but retained as a temporary activation +constraint until GitHub issue #144 provides the compatibility-breaking REST +response-model changes. + +### Duplicate the capability matrix in configuration validation and bootstrap + +Rejected. Two owners would drift as services and configuration evolve. +Bootstrap is the application-composition boundary that knows which services are +being constructed. + +## Consequences + +- **Positive:** #999 separates optional representation/container dependencies + from the later runtime behavior change, allowing #1980 to activate v3 first. +- **Positive:** After the activation follow-up, public UDP/HTTP tracker + services can run without a database when persistence-backed capabilities are + disabled. The management API joins that mode only after API #144 implements + its approved next-major contract. +- **Positive:** Missing persistence is detected deterministically before driver + construction rather than through a late repository failure. +- **Positive:** The shared-schema lifecycle stays simple: zero drivers in + persistence-free mode, exactly one driver and complete migrations otherwise. +- **Positive:** Future features avoid conditional schema upgrade and + compatibility paths even when their current persistence tables look + independent. +- **Positive:** Operators can change persistence configuration without risking + automatic data deletion or unexpected cross-driver migration. +- **Negative:** Application containers, REST API composition, route behavior, + response models, test helpers, and the container entrypoint require changes. +- **Negative:** Some API consumers may need to adapt to explicit + configuration-disabled or historical-data-unavailable semantics. +- **Negative:** State produced during a persistence-free interval is not + recoverable when persistence is later re-enabled. + +## Date + +Approved as a draft on 2026-08-25; finalize during Issue #999 Phase 3. + +## References + +- Issue #999 +- Configuration-overhaul EPIC #1978 +- `docs/issues/open/999-1978-optional-database-configuration/analysis.md` +- `docs/issues/open/999-1978-optional-database-configuration/solution.md` +- `docs/adrs/20260723184019_separate_configuration_value_invariants_from_consistency_validation.md` +- GitHub issue #144 diff --git a/docs/issues/open/999-1978-optional-database-configuration/analysis.md b/docs/issues/open/999-1978-optional-database-configuration/analysis.md index e12570c86..778973313 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/analysis.md +++ b/docs/issues/open/999-1978-optional-database-configuration/analysis.md @@ -2,6 +2,8 @@ semantic-links: related-artifacts: - docs/issues/open/999-1978-optional-database-configuration/ISSUE.md + - docs/issues/open/999-1978-optional-database-configuration/baseline-e2e-verification.md + - docs/issues/open/1980-1978-configuration-overhaul-final-cleanup.md - packages/tracker-core/ - packages/configuration/src/v2_0_0/ - packages/configuration/src/v3_0_0/ @@ -10,84 +12,331 @@ semantic-links: # Phase 1 - Persistence dependency analysis -## Purpose - -Establish evidence for the current persistence lifecycle and every dependency -that assumes a database exists. This phase must not select the implementation -solution or change runtime behaviour. - -## Required Investigation - -### Configuration and startup lifecycle - -- Trace how v2 configuration currently requires and supplies database settings. -- Trace v3 `Core.database` parsing, defaults, and all v3-to-runtime handoff - paths planned under #1980. -- Locate each database-driver construction path and identify its owner. -- Locate each migration invocation and determine whether it is coupled to - construction, connection, or an explicit bootstrap operation. -- Inspect `share/container/entry_script_sh`, the container image, and related - configuration/install paths. Record directory creation, default-database - installation, required database-driver environment variables, and any other - database side effect before the tracker process starts. -- Reconcile the source-level lifecycle findings with the baseline end-to-end - evidence in `baseline-e2e-verification.md`. -- Trace the configuration-consistency validation path from - `packages/configuration/src/validator.rs` through `Configuration::validate()` - and bootstrap. Record whether each database requirement is expressible as a - cross-field configuration rule or instead needs runtime/environment - validation. -- Record separate SQLite, MySQL, and PostgreSQL behaviour, including file or - connection side effects and migration preconditions. - -### Persistence consumer inventory - -For every consumer, record the source location, enablement condition, repository -or service dependency, startup dependency, runtime failure mode, and tests. - -| Domain / consumer | Enablement configuration | Persistence operations | REST API coupling | Findings | -| ------------------------------------- | ------------------------ | ---------------------- | ----------------- | -------- | -| Whitelist | TODO | TODO | TODO | TODO | -| Torrent completion metrics | TODO | TODO | TODO | TODO | -| Private-tracker keys | TODO | TODO | TODO | TODO | -| Other direct `tracker-core` consumers | TODO | TODO | TODO | TODO | -| Indirect consumers and jobs | TODO | TODO | TODO | TODO | - -The consumer inventory identifies requirements; it is not a plan to create -independent schemas or migration streams. The tracker has one small shared -persistence schema. Phase 1 must confirm every migration invocation, but the -working constraint is all or nothing: no required consumers means no driver and -no migrations; one or more required consumers means one initialized driver and -the complete migration set. - -### Management REST API inventory - -For every route that reads or writes a persistence-backed domain, record the -route, authorization policy, feature dependency, current response when the -underlying domain is unavailable, and desired contract candidates. Do not decide -the final response in this phase. - -| Route / operation | Domain | Current dependency path | Current unavailable behaviour | Evidence | -| ----------------- | ------ | ----------------------- | ----------------------------- | -------- | -| TODO | TODO | TODO | TODO | TODO | - -### Compatibility and activation inventory - -- Confirm that v2 must remain unchanged and continues requiring a database. -- Identify every default configuration, helper, example, benchmark, E2E setup, - and deployment document that will be affected if v3 `Core.database` becomes - optional. -- Identify container entrypoint changes needed to support a no-persistence v3 - deployment without requiring a database-driver environment variable or - installing a default SQLite database. -- Identify the concrete #1980 consumer-migration tasks that may need to depend - on this issue. - -## Evidence Requirements - -- Link source paths, tests, logs, or focused experiment results for every - finding. -- Distinguish verified findings from hypotheses. -- Record contradictory evidence and unresolved questions explicitly. -- Update `solution.md` only after this document provides enough evidence to - evaluate the alternatives. +## Scope and evidence status + +This document records **verified current-state facts** as of the Phase 1 +analysis branch. It does not select an optional-persistence design, change a +runtime contract, or decide whether #999 blocks #1980. The current runtime +uses schema v2 aliases; the v3 types are present but are not yet handed to the +application runtime. See `packages/configuration/src/lib.rs` and #1980's +consumer migration map. + +The pre-implementation reproduction remains preserved in +[`baseline-e2e-verification.md`](baseline-e2e-verification.md): the v2 UDP +benchmark configuration starts with a 49,152-byte SQLite file and the complete +shared schema even with the known persistence features disabled. + +## Configuration and startup lifecycle + +### Active v2 configuration + +#### Verified facts + +- `packages/configuration/src/lib.rs` aliases `Configuration`, `Core`, and + `Database` to `v2_0_0`. `src/bootstrap/config.rs` loads that alias, so this + is the active runtime contract. +- `packages/configuration/src/v2_0_0/core.rs`, `Core::database`, is a + non-optional Rust field with `#[serde(default = "Core::default_database")]`. + `packages/configuration/src/v2_0_0/database.rs`, `Database::default`, uses + SQLite and `./storage/tracker/lib/database/sqlite3.db`. +- Thus v2 does not require an explicitly written `[core.database]` TOML table: + omission resolves to the SQLite default. It remains an unconditional runtime + database requirement because `TrackerCoreContainer::initialize_from` always + initializes it. The reconciled baseline missing-database control records the + same distinction and does not claim an omitted v2 section is a parse error. +- `v2_0_0::Configuration::load` first selects full TOML from + `TORRUST_TRACKER_CONFIG_TOML`, else the file named by + `TORRUST_TRACKER_CONFIG_TOML_PATH`, else bootstrap's + `share/default/config/tracker.development.sqlite3.toml`. It merges + `TORRUST_TRACKER_CONFIG_OVERRIDE_` variables split on `__` before joining + Rust defaults. Database overrides include + `CORE__DATABASE__DRIVER` and `CORE__DATABASE__PATH`. +- V2 mandatory source values are `metadata.schema_version`, + `logging.threshold`, `core.private`, and `core.listed`; database settings + are supplied by defaults when absent. Sources: + `packages/configuration/src/v2_0_0/mod.rs`, `Configuration::load` and + `check_mandatory_options`. + +### Dormant v3 configuration and #1980 handoff + +#### Verified facts + +- `packages/configuration/src/v3_0_0/core.rs`, `Core::database`, is presently + non-optional and defaults to `Database::default()`. +- `packages/configuration/src/v3_0_0/database.rs` defines the driver-specific + `Database::{Sqlite3 { path }, MySQL(ConnectionInfo), PostgreSQL(ConnectionInfo)}`. + SQLite defaults its path; MySQL and PostgreSQL require `host`, `user`, a + non-empty secret `password`, and `database`, with ports defaulting to 3306 + and 5432 respectively. Driver-incompatible and unknown fields are rejected. +- V3's loader uses the same TOML selection and override prefix. It removes + `core.database` from Figment defaults before extraction, avoiding accidental + merging of a default SQLite path with a supplied network-driver table. + Sources: `v3_0_0/mod.rs`, `Configuration::load` and `defaults_for_loading`. +- V3 is **not** runtime-compatible with current database setup: + `packages/tracker-core/src/databases/setup.rs`, `initialize_database`, reads + `config.database.driver` and `.path`, members only on v2's `Database`. + No v3-to-runtime adapter exists. #1980 explicitly assigns migration of + `src/bootstrap/`, `src/container.rs`, tracker-core, protocol packages, test + helpers, examples, benchmarks, and the qBittorrent E2E builder to its + consumer migration work. Configuration defaults require a separate + compatibility review if the approved v3 optional-database contract changes + them. + +### Driver construction and migrations + +#### Verified lifecycle + +```text +src/app.rs::run + -> bootstrap::app::setup + -> AppContainer::initialize + -> TrackerCoreContainer::initialize_from + -> databases::setup::initialize_database + -> selected driver construction + create_database_tables + -> app::start loads enabled persisted state and starts jobs +``` + +- `src/bootstrap/app.rs::setup` loads configuration, calls + `Configuration::validate()`, initializes logging, and then awaits + `AppContainer::initialize`. +- `packages/tracker-core/src/container.rs::TrackerCoreContainer::initialize_from` + unconditionally calls `initialize_database` before constructing its + whitelist, keys, metrics, torrent, announce, and scrape services. +- The production `AppContainer::tracker_http_api_container` reuses that + prebuilt tracker-core container. Separately, + `packages/rest-api-runtime-adapter/src/v1/container.rs`, + `TrackerHttpApiCoreContainer::initialize`, constructs a new + `TrackerCoreContainer` and consequently performs the same database + initialization and migration lifecycle. This latter path is used by REST + server/test construction (`packages/axum-rest-api-server/src/server.rs` and + `src/bootstrap/jobs/tracker_apis.rs` tests), not the main application startup. +- `initialize_database` creates one concrete driver, immediately calls + `SchemaMigrator::create_database_tables()`, then exposes that one driver as + narrow `SchemaMigrator`, `TorrentMetricsStore`, `WhitelistStore`, and + `AuthKeyStore` trait objects in `DatabaseStores`. It uses `expect`; malformed + connection input, unavailable network database, authentication/DDL failure, + or migration failure is a startup panic. +- SQLite (`driver/sqlite/mod.rs`) uses lazy SQLx pooling with + `SqliteConnectOptions::filename(...).create_if_missing(true)`. The immediate + migration query causes a configured missing file to be created at startup. +- MySQL (`driver/mysql/mod.rs`) parses the v2 DSN with + `MySqlConnectOptions::from_str`; PostgreSQL (`driver/postgres/mod.rs`) uses + `PgConnectOptions::from_str`. Both pools are lazy, but the immediate migration + requires a reachable database server at startup. +- All drivers embed and apply their full backend migration set through + `migrations/{sqlite,mysql,postgresql}`. SQLx records applied migrations in + `_sqlx_migrations`; repeated completed runs are idempotent. SQLite and MySQL + schema migrators contain legacy pre-v4 bootstrap logic, including rejection + of partially migrated legacy schemas. PostgreSQL runs embedded migrations + directly because its schema migrator documents no pre-v4 PostgreSQL legacy + database. Sources: the three driver `schema_migrator.rs` files and + `packages/tracker-core/migrations/`. + +### Container lifecycle + +#### Verified facts + +- `Containerfile` supplies + `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER=sqlite3` and uses + `share/container/entry_script_sh` as the entrypoint. Its tester image creates + a packaged empty SQLite file with `sqlite3 ... "VACUUM;"`. +- Before executing the tracker, `entry_script_sh` unconditionally creates + `/var/lib/torrust/tracker/database/` and `/etc/torrust/tracker/`, applies + ownership and mode changes, and exits if the driver override is absent. +- It selects a SQLite, MySQL, or PostgreSQL default config from the driver + override. For SQLite it also selects the packaged empty database. `inst` + installs only when the target does not exist, so mounted prior config/database + files persist across later starts; changing only the driver variable does not + replace them. +- The v2 container configs are + `tracker.container.{sqlite3,mysql,postgresql}.toml`; each includes a v2 + database contract. SQLite uses the container database path; MySQL and + PostgreSQL use DSNs. Therefore the entrypoint has independent persistence + side effects before application configuration validation. + +## Persistence-consumer inventory + +The following requirements are **facts about current behavior**, not Phase 2 +decisions. All persistence objects are currently constructed before any feature +condition is inspected. + +### Whitelist + +- **Enabled by:** `core.listed`, which controls announce and scrape enforcement. +- **Dependency path:** `DatabaseStores.whitelist_store` -> + `DatabaseWhitelist` -> `WhitelistManager`. +- **Current behavior:** writes persist before in-memory mutation; a store + failure returns a database error. +- **Startup and tests:** `src/app.rs::load_whitelisted_torrents` reads the + database only when listed, although the service is always constructed. See + `whitelist/repository/persisted.rs` and `whitelist/manager.rs` tests. +- **REST API coupling:** direct add, remove, and reload routes, not gated by + `core.listed`; see the route inventory below. + +### Private-tracker keys + +- **Enabled by:** `core.private`, which controls authentication. +- **Dependency path:** `auth_key_store` -> `DatabaseKeyRepository` -> + `KeysHandler`. +- **Current behavior:** add, generate, and remove persist before in-memory + mutation. Store errors are returned; no in-memory-only fallback exists. +- **Startup and tests:** `load_peer_keys` reads only when private, although the + service is always constructed. See `authentication/handler.rs` and + `authentication/key/repository/persisted.rs` tests. +- **REST API coupling:** direct key add, generate, delete, and reload routes, + not gated by `core.private`; see the route inventory below. + +### Persistent completed metrics + +- **Enabled by:** `core.tracker_policy.persistent_torrent_completed_stat`. +- **Dependency path:** `torrent_metrics_store` -> + `DatabaseDownloadsMetricRepository`. +- **Current behavior:** the announce path conditionally loads a torrent's + completed count. Completion handling reads, then inserts or updates, both a + per-torrent count and the aggregate count. +- **Startup and tests:** `load_torrent_metrics` restores only the global + aggregate metric when enabled. `TorrentsManager::load_torrents_from_database` + has no production startup call; `AnnounceHandler` lazily loads a per-torrent + count on its first announce. Pre-load errors propagate, while event write + failures are logged and processing continues. See persistence/restart cases + in `tracker-core/tests/integration.rs` and + `statistics/persisted/downloads.rs`. +- **REST API coupling:** indirect only. Torrent, stats, and metrics routes read + in-memory values that may have been seeded from persistence. + +### In-memory torrent, swarm, and usage metrics + +- **Enabled by:** `tracker_usage_statistics` controls some jobs, but does not + alone require persistence. +- **Dependency path:** `InMemoryTorrentRepository`, the swarm registry, and + metric repositories have no database constructor dependency. +- **Current behavior:** a torrent can receive a persisted completed count only + when persistent completion metrics are enabled. `torrent_cleanup` and + activity jobs are in-memory. `tracker_core_event_listener` starts when usage + statistics **or** persistent completion metrics are enabled; only the latter + causes persistence writes. +- **REST API coupling:** torrent, stats, and metrics routes do not directly + query the database. + +### REST management service + +- **Enabled by:** `http_api.is_some()`. +- **Dependency path:** API construction receives the already-created + `TrackerCoreContainer`; it has no unavailable-store representation. +- **Startup:** `src/app.rs::start_the_http_api` runs after unconditional + database creation. +- **Persistence coupling:** direct persistence routes are always assembled + while the API is enabled. + +Other direct `initialize_database` callers identified by exact search are +test helpers, repository/manager tests, protocol tests and benchmarks, and the +explicit `packages/persistence-benchmark` tool. They are not production +application construction paths. `TrackerHttpApiCoreContainer::initialize` is +the additional REST server/test construction path described above. Main +production construction is the container lifecycle above. + +`packages/test-helpers/src/configuration.rs::ephemeral_configuration` always +provisions an ephemeral SQLite database and assigns its path to the v2 core +configuration. Its public, private, and listed helpers derive from that base. +Consequently, these test environments—including REST API environments—exercise +configured SQLite even when their feature flag is disabled; they do not provide +coverage for absent persistence. + +## Management REST API inventory + +### Shared API facts + +`http_api` is optional, but when present `src/app.rs::start_the_http_api` +constructs `TrackerHttpApiCoreContainer` from the full tracker-core container. +`packages/axum-rest-api-server/src/routes.rs` applies the shared token +middleware to v1 routes. `v1/middlewares/auth.rs` accepts Bearer or query-token +authentication (header wins); configured tokens have equal privilege. This is +the current authorization policy, not a persistence feature gate. + +In the active v2 runtime, a database driver and its shared schema are always +initialized before the REST API starts. Therefore, the existing database-error +handling on direct whitelist and key routes is a defense against a configured +database becoming unavailable or failing after startup; it is not behavior for +an omitted database configuration. That state is not representable in the +current application. + +| Route / operation | Domain | Current dependency path | Current unavailable behavior and evidence | +| --------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `POST /api/v1/whitelist/{info_hash}` | Whitelist write | `WhitelistApiService` -> `TrackerWhitelistAdapter` -> `WhitelistManager` -> `DatabaseWhitelist`. | `WhitelistError::Database` becomes the existing generic failure response documented/tested as 500. No absent-persistence branch exists. Sources: `v1/context/whitelist/{routes,handlers}.rs`, runtime adapter, application use case, and contract test. | +| `DELETE /api/v1/whitelist/{info_hash}` | Whitelist write | Same manager/repository chain. | Same generic database failure mapping; not gated by `core.listed`. | +| `GET /api/v1/whitelist/reload` | Whitelist read | `WhitelistManager::load_whitelist_from_database`. | Same database failure mapping; directly accesses persistence even when `core.listed` is false. | +| `POST /api/v1/keys` | Authentication-key write | `AuthKeyApiService` -> `TrackerAuthKeyAdapter` -> `KeysHandler` -> `DatabaseKeyRepository`. | `AuthKeyError::Database` follows the existing failure response path; no unavailable-store branch. Sources: `v1/context/auth_key/{routes,handlers}.rs`, adapter, use case, contract test. | +| `POST /api/v1/key/{seconds_valid_or_key}` | Deprecated expiring-key generation | `KeysHandler::generate_expiring_peer_key` -> key repository. | Existing generation failure response on database error; not gated by `core.private`. | +| `DELETE /api/v1/key/{seconds_valid_or_key}` | Authentication-key deletion | `KeysHandler::remove_peer_key` -> key repository. | Existing failure response on database error; not gated by `core.private`. | +| `GET /api/v1/keys/reload` | Authentication-key read | `KeysHandler::load_peer_keys_from_database`. | Existing failure response on database error; directly accesses persistence even when `core.private` is false. | +| `GET /api/v1/torrent/{info_hash}`, `GET /api/v1/torrents` | Torrent reads | In-memory torrent repository. | No handler database access. A completed count can have originated from persistence when that feature is enabled. Sources: `v1/context/torrent/*` and adapter/tests. | +| `GET /api/v1/stats`, `GET /api/v1/metrics` | Statistics reads | In-memory metric repositories. | No handler database access. The completed metric may be seeded or updated by persistence-backed completion metrics. Sources: `v1/context/stats/*` and adapter/tests. | + +The whitelist and auth-key contract tests force database failures by dropping +schema tables through `packages/axum-rest-api-server/tests/server/mod.rs`, +`force_database_error`. They test a configured-but-failing database, not an +absent database configuration. + +**Phase 2 constraints evidenced by this inventory:** direct persistence routes +are currently built independently of `listed` and `private`, and the code only +represents a database that succeeds or fails. An absent database must therefore +be represented or excluded deliberately before runtime activation; final route +availability and status semantics are not selected here. + +## Validation-layer and activation compatibility inventory + +### Current validation path + +- `packages/configuration/src/validator.rs` defines the cross-field + `Validator` trait and presently only + `SemanticValidationError::UselessPrivateModeSection`. +- Both `v2_0_0::Core::validate` and `v3_0_0::Core::validate` reject a supplied + `private_mode` section when `private` is false. Each version's + `Configuration::validate` delegates to `Core::validate`. +- `src/bootstrap/app.rs::setup` invokes `configuration.validate()` before + `AppContainer::initialize` and therefore before driver construction. +- The validation-layer ADR classifies a database requirement induced by + `core.private`, `core.listed`, or + `core.tracker_policy.persistent_torrent_completed_stat` as a **cross-field + configuration relationship** if the final model needs only those settings. + Database reachability, DDL permission, filesystem access, and credentials + remain **runtime/environment facts**. No new rule is selected in Phase 1. + +### #1980 and v3 activation surfaces + +The #1980 consumer migration map identifies all runtime users of configuration +types, including `src/app.rs`, `src/container.rs`, bootstrap, tracker-core +database setup and protocol consumers, REST adapter container, test helpers, +examples, benchmarks, and the qBittorrent E2E builder. Its T1/T9/T10 tasks and +the v2-to-v3 migration guide are affected by any approved v3 optional-database +contract. `share/default/config/`, `docs/containers.md`, container defaults, +and the entrypoint also need a later compatibility review because they currently +encode or install the v2 database lifecycle. + +**Unresolved Phase 2 question:** #1980 is the planned runtime activation point, +but Phase 1 does not decide whether v3 optional database configuration must be +implemented before that migration. The decision requires maintainer approval of +the v3 contract and the direct REST API behavior above. + +## Reconciliation and unresolved questions + +1. The baseline result is consistent with source: an unconditional call to + `initialize_database` runs before any persistence feature condition, and + SQLite migration activates `create_if_missing`. +2. The one shared migration lifecycle is already enforced by one driver object + exposed as all narrow stores; Phase 1 found no feature-specific schema or + migration stream. +3. Confirm the staged direction in `solution.md`: #999 adds `Option` + and optional container dependencies, while the active bootstrap deliberately + supplies a temporary `Some(Database)` bridge. +4. Confirm the initial capability matrix and exact diagnostics for the small + post-activation follow-up that replaces the bridge with actual v3 + configuration. +5. Define the container-entrypoint changes required by that follow-up for a + v3 persistence-free startup without its current driver variable, database + directory, or packaged SQLite install. +6. Approve the ADR draft and refine it during Phase 3; retain the activation + follow-up and future persistence-awareness EPIC drafts for their respective + post-#1980 and post-#999 planning work. +7. Confirm the staged #999 -> #1980 -> activation-follow-up ordering and update + EPIC #1978 and the v2-to-v3 migration guidance during Phase 2. diff --git a/docs/issues/open/999-1978-optional-database-configuration/baseline-e2e-verification.md b/docs/issues/open/999-1978-optional-database-configuration/baseline-e2e-verification.md index 66bd8d8e8..e49658c0c 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/baseline-e2e-verification.md +++ b/docs/issues/open/999-1978-optional-database-configuration/baseline-e2e-verification.md @@ -42,9 +42,10 @@ remove_peerless_torrents = false ## Baseline result -The current v2 configuration still requires `[core.database]`. With its SQLite -section present, the tracker starts and creates a 49,152-byte SQLite database -file despite the persistence settings above being disabled: +The active v2 runtime unconditionally initializes a database. This baseline +supplies an explicit SQLite section, and the tracker starts and creates a +49,152-byte SQLite database file despite the persistence settings above being +disabled: ```toml [core.database] @@ -89,12 +90,19 @@ were applied. ## Missing-database control observation Removing `[core.database]` from the equivalent v2 benchmarking configuration -does not provide a valid reproduction of the desired final state because v2 -still requires the section. In the current runtime, the process remains alive -after configuration loading and provides no useful visible diagnostic at the -`error` logging threshold before the ten-second timeout. This confirms why the -implementation must preserve v2 behaviour and target v3 only; Phase 1 must -trace the precise v2 construction and failure path separately. +does not provide a valid reproduction of the desired final state. The v2 +`Core::database` field has a serde default, so the TOML section itself is not +mandatory: omission resolves to the default SQLite configuration. The active +runtime then still unconditionally constructs that default database and applies +migrations before it evaluates feature enablement. + +In the recorded control run, the process remained alive after configuration +loading and provided no useful visible diagnostic at the `error` logging +threshold before the ten-second timeout. The control did not inspect the +default database location, so it does not independently prove whether that +location was created. This confirms why the implementation must preserve v2 +behaviour and target v3 only; Phase 1 traces the precise v2 construction path +in `analysis.md`. ## Final implementation acceptance scenario diff --git a/docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md b/docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md new file mode 100644 index 000000000..61cfb416b --- /dev/null +++ b/docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md @@ -0,0 +1,151 @@ +--- +doc-type: epic +status: approved-draft +intended-destination: docs/issues/drafts/ +github-issue: null +related-issue: 999 +related-github-issue: 144 +last-updated-utc: 2026-08-25 00:00 +semantic-links: + related-artifacts: + - docs/issues/open/999-1978-optional-database-configuration/ISSUE.md + - docs/issues/open/999-1978-optional-database-configuration/analysis.md + - docs/issues/open/999-1978-optional-database-configuration/solution.md + - docs/issues/open/999-1978-optional-database-configuration/adr-draft.md + - docs/issues/drafts/144-make-rest-api-persistence-aware.md +--- + +# Draft EPIC - Progressively make tracker capabilities persistence-aware + +> **Approved Phase 2 draft:** Refine this document against the merged #999, +> Issue #1980, and persistence-free activation-follow-up implementations. Then +> move it to `docs/issues/drafts/`, update the scope from final evidence, and +> create the GitHub EPIC. Do not create it during #999 Phase 2 or Phase 3 unless +> its scope becomes a blocker. + +## Goal + +Progressively remove implicit persistence assumptions from tracker capabilities +after the explicit v3 persistence-free deployment is activated by the small +post-#1980 follow-up drafted alongside #999. +Every capability, API response, configuration option, and test fixture should +make clear whether it needs persistence, uses session-only state, exposes +historical state, or is unavailable by configuration. + +## Why This Is Needed + +Issue #999 introduces optional v3 representation and optional container +dependencies. Its post-#1980 activation follow-up makes the tracker and +public UDP/HTTP services run without a database. The management REST API +remains persistence-required until the next-major API work under GitHub issue +144 implements its approved disabled-capability contract. The existing system +has broader historical coupling: + +- management routes currently assume persistence-backed whitelist and key + services exist; +- completed metrics can represent session and persisted history differently; +- torrent and statistics responses can expose in-memory values seeded from + persistence without explicitly identifying their provenance; +- tests, examples, container artifacts, and deployment documentation often + provision SQLite by default. + +Those concerns require staged API, model, test, and operational changes. The +next-major REST API compatibility work is drafted in +`docs/issues/drafts/144-make-rest-api-persistence-aware.md` under GitHub issue 144. This EPIC must coordinate with it and must not delay the +configuration-overhaul EPIC once #999 and its activation follow-up supply a +safe persistence-free UDP/HTTP-tracker baseline. + +## Scope + +### In Scope + +- Make application and REST API composition explicitly capability-aware. +- Standardize API behavior for a capability disabled by configuration. +- Make session and historical metric semantics explicit in API models. +- Expand persistence-free coverage across unit, integration, container, example, + benchmark, and operational paths. +- Identify and remove remaining implicit persistence assumptions incrementally. + +### Out of Scope + +- Reverting the #999 v3 persistence-free boundary. +- Changing v2 configuration behavior. +- Creating separate feature-specific schemas or migration streams. +- Requiring all possible persistence-related improvements to land in one PR. + +## Candidate Subissues + +These are intentionally detailed candidates, not yet-created GitHub issues. +Refine ordering and boundaries after #999 merges, coordinating API contract +work with GitHub issue #144. + +| Order | Candidate subissue | Problem to solve | Expected outcome | +| ----- | --------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| 1 | Inventory remaining persistence assumptions | #999 will identify a known baseline, but merged code/tests may reveal more assumptions. | Evidence-backed follow-up plan with ownership and priorities. | +| 2 | Standardize disabled-capability API responses | Routes should distinguish disabled-by-configuration from database operational failure. | Shared response model/status policy and contract tests. | +| 3 | Refine REST capability composition | Remove remaining direct route/service assumptions that a persistence store exists. | Routes receive only the capability services they may use; disabled routes do not reach persistence. | +| 4 | Define metric provenance | Current-process counters and restored historical values have different meanings. | Explicit session/historical fields or metadata, no numeric sentinels. | +| 5 | Define per-torrent completed semantics | In-memory torrent counts can be lazily seeded from persisted counts. | Documented session versus lifetime semantics and compatible API model. | +| 6 | Expand persistence-free test infrastructure | Existing helpers commonly create SQLite regardless of capability configuration. | Reusable no-database fixtures and focused regression coverage. | +| 7 | Audit operational artifacts | Examples, benchmarks, container paths, and docs may silently assume SQLite. | Accurate deployment guidance and only intentional persistence setup. | + +## Delivery Strategy + +1. Start after #999 and the configuration-overhaul EPIC have merged or are no + longer affected by the work. +2. Begin with an evidence refresh based on the merged #999 implementation. +3. Establish one API contract for configuration-disabled capabilities before + changing individual routes. +4. Deliver metric-provenance changes as explicitly versioned API work with + migration guidance where needed. +5. Keep each subissue independently testable and avoid reintroducing feature + checks scattered through repositories. + +## Progress Tracking + +### Workflow Checkpoints + +- [x] Draft created as a follow-up artifact for Issue #999. +- [x] Draft approved as a post-merge starting point. +- [ ] #999 implementation merged and draft reconciled with its final behavior. +- [ ] #1980 and persistence-free activation follow-up merged and draft + reconciled with their final behavior. +- [ ] Epic specification moved to `docs/issues/drafts/` and approved. +- [ ] GitHub EPIC created and linked. +- [ ] Candidate subissues refined, created, and linked. + +### Progress Log + +- 2026-08-25 00:00 UTC - GitHub Copilot/User - Created initial follow-up EPIC + draft while defining #999’s persistence-free v3 direction. The draft is not a + created GitHub issue and must not block #999 or #1980. +- 2026-08-25 00:00 UTC - User - Approved this draft as the post-merge starting + point. Its scope must be reconciled with merged #999, #1980, activation, and + API #144 work before the GitHub EPIC is created. + +## Acceptance Criteria + +- [ ] The merged #999 implementation is the documented baseline for follow-up work. +- [ ] Every remaining persistence assumption has an explicit disposition. +- [ ] API semantics distinguish disabled capability, operational persistence + failure, session-only values, and historical values. +- [ ] Persistence-free regression coverage does not silently provision SQLite. +- [ ] Operational artifacts accurately describe optional persistence. + +## Risks and Trade-offs + +- **API compatibility:** More explicit metric semantics can require client + changes. Mitigation: version and document response-model changes deliberately. +- **Scope growth:** Persistence touches several layers. Mitigation: maintain + small capability-focused subissues and an explicit order. +- **Behavior drift:** Configuration-aware checks can be duplicated. Mitigation: + keep each capability decision at its composition boundary and cover it with + contract tests. + +## References + +- Related issues: #999, #144 +- Configuration-overhaul EPIC #1978 +- `docs/issues/open/999-1978-optional-database-configuration/analysis.md` +- `docs/issues/open/999-1978-optional-database-configuration/solution.md` +- `docs/issues/open/999-1978-optional-database-configuration/adr-draft.md` diff --git a/docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md b/docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md new file mode 100644 index 000000000..766c9c9f2 --- /dev/null +++ b/docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md @@ -0,0 +1,122 @@ +--- +doc-type: issue +status: draft +intended-destination: docs/issues/drafts/ +github-issue: null +related-issues: + - 999 + - 1980 +last-updated-utc: 2026-08-25 00:00 +semantic-links: + related-artifacts: + - docs/issues/open/999-1978-optional-database-configuration/solution.md + - docs/issues/open/999-1978-optional-database-configuration/adr-draft.md + - docs/issues/open/999-1978-optional-database-configuration/analysis.md +--- + +# Draft follow-up - Activate the v3 persistence-free runtime + +> **Approved planning draft:** Do not create this GitHub issue immediately. +> After Issue #999 and Issue #1980 merge, refine this document against their +> final implementation evidence and any newly discovered constraints. Then move +> it to `docs/issues/drafts/`, obtain approval, and create the GitHub issue. +> Create it earlier only if the staged compatibility bridge cannot remain small. + +## Goal + +Replace the temporary bootstrap `Some(Database)` compatibility bridge with the +actual v3 `core.database: Option` value. A v3 tracker with no enabled +persistence-backed capability and no `[core.database]` must run without a +persistence driver, database file, network database connection, migration, or +persistence-backed service. + +## Background + +Issue #999 makes the v3 database representation and container dependencies +optional, but leaves an explicit temporary database dependency in bootstrap +while the application still transitions from v2 aliases to v3 consumers. Issue +1980 activates v3 consumers with that bridge in place. This follow-up changes +the runtime behavior without changing the public v3 configuration shape. + +## Scope + +### In Scope + +- Use actual `v3_0_0::Core.database` at the bootstrap/container boundary. +- Invoke the bootstrap-owned capability-to-persistence requirement check + implemented and unit-tested by Issue #999 before application-container + construction. +- Reject enabled listing, private mode, or persistent completed metrics when no + database is configured. +- Construct no persistence driver, stores, or migrations when persistence is + absent and no capability requires it. +- Keep `http_api` persistence-required until the next-major REST API work in + GitHub issue #144 implements the approved HTTP 409 configuration-disabled + response model and historical metric semantics. +- Update the container entrypoint so no-persistence v3 deployments do not + require a driver override, database directory, or packaged SQLite install. +- Defer persistence selection to actual v3 configuration; do not retain a + separate entrypoint driver default or override that can contradict it. +- Preserve operator-managed database state across configuration transitions: + never delete, overwrite, or migrate an unselected database target; do not + automatically transfer state between database drivers or locations. +- Execute and record Issue #999 manual scenarios M1–M6. + +### Out of Scope + +- Changing v2 behavior. +- Redesigning the complete REST API beyond the minimum persistence-free + contract. +- Creating feature-specific schemas or migration streams. +- The broader persistence-awareness work captured by + `persistence-awareness-epic-draft.md`. + +## Implementation Plan + +| ID | Status | Task | Notes | +| --- | ------ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| T1 | TODO | Remove temporary bootstrap bridge | Pass actual v3 `Option` to optional composition. | +| T2 | TODO | Invoke bootstrap requirement validation | Reuse the #999 implementation; do not duplicate its feature matrix. | +| T3 | TODO | Gate persistence composition | No driver/stores/migrations in persistence-free mode. | +| T4 | TODO | Preserve REST API persistence requirement | Do not attempt the #144 response-model redesign in this activation follow-up. | +| T6 | TODO | Adapt container entrypoint | Defer persistence to v3 config; permit no-persistence deployment without SQLite setup or destructive mounted-state changes. | +| T7 | TODO | Add regression coverage | Configuration, bootstrap, container, E2E, and restart-transition coverage. | +| T8 | TODO | Run M1–M6 and update docs | Record evidence in Issue #999 artifacts. | + +## Evidence ownership and sequence + +| Stage | Owner | Required evidence | Follow-up handoff | +| ------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| V3 optional representation | Issue #999 Phase 3 | V3 parsing tests prove omitted `[core.database]` is `None`; configured drivers retain their behavior. | Preserve the temporary bridge and document that runtime persistence remains active. | +| Optional dependency composition | Issue #999 Phase 3 | Container/constructor tests prove optional persistence dependencies are accepted; bootstrap passes explicit `Some(Database)`. | Provide the reusable validation matrix and final ADR. | +| V3 consumer activation | Issue #1980 | Consumer migration activates v3 while retaining the temporary bridge. | Record that omitted database is not yet honored at runtime. | +| Persistence-free runtime | This follow-up | Actual `None` reaches composition; no driver/migration artifacts; M1–M6 and transition/container evidence pass. | Update Issue #999 acceptance evidence and finalize operational guidance. | +| Persistence-free REST API | API #144 next-major work | Disabled direct routes use HTTP 409; historical/session semantics are explicit. | Remove the temporary `http_api` persistence requirement after review. | + +The exact issue is intentionally not created until the preceding #999/#1980 +implementation evidence is reviewed. Before it is opened, reconcile this draft +with the merged code, replace assumptions with verified behavior, and identify +any newly discovered persistence consumer in the centralized matrix. + +## Acceptance Criteria + +- [ ] Omitted v3 `[core.database]` is honored at runtime when no capability + requires persistence. +- [ ] No persistence artifacts are created in the persistence-free scenario. +- [ ] Each enabled persistence-backed capability fails startup clearly when the + database is absent. +- [ ] The activation follow-up documents that `http_api` remains + persistence-required until GitHub issue #144 delivers the approved + next-major REST API contract. +- [ ] The supported container path works without persistence configuration. +- [ ] Disabling persistence on restart leaves the previously selected database + target unchanged; re-enabling the same target reuses its data and + migrations; changing targets does not copy data automatically. +- [ ] Issue #999 M1–M6 evidence is completed. + +## References + +- Issue #999 +- Issue #1980 +- `docs/issues/open/999-1978-optional-database-configuration/solution.md` +- `docs/issues/open/999-1978-optional-database-configuration/adr-draft.md` diff --git a/docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md b/docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md new file mode 100644 index 000000000..ec3e23ed5 --- /dev/null +++ b/docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md @@ -0,0 +1,65 @@ +--- +status: draft +purpose: persistence-unavailable-scenario-catalog +related-issue: 999 +related-github-issue: 144 +last-updated-utc: 2026-08-25 00:00 +semantic-links: + related-artifacts: + - docs/issues/open/999-1978-optional-database-configuration/analysis.md + - docs/issues/open/999-1978-optional-database-configuration/solution.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md + - docs/issues/drafts/144-make-rest-api-persistence-aware.md +--- + +# Persistence-unavailable scenario catalog + +> **Planning catalog:** This is a case log for Issue #999 and its follow-ups. +> It distinguishes intentional absence of persistence from operational database +> failure. Update it when implementation finds a new case; do not substitute it +> for authoritative API contracts, tests, or issue specifications. + +## State vocabulary + +| State | Meaning | +| -------------------- | ---------------------------------------------------------------------- | +| Persistence absent | V3 `[core.database]` is omitted and the activation path honors `None`. | +| Capability disabled | A feature is intentionally off in configuration. | +| Persistence required | An enabled capability needs a configured database. | +| Operational failure | A configured database fails after startup or during an operation. | +| Session-only data | A value exists only for the current process lifetime. | +| Historical data | A value is restored from or maintained in persistence. | + +## Scenario catalog + +| ID | Situation | Required behavior | Delivery owner | Status | +| --- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------- | +| S1 | `core.listed = true`, but persistence is absent | Bootstrap reports `ListedRequiresDatabase` before container or driver construction. | #999 implements/tests; activation follow-up invokes. | Approved | +| S2 | `core.private = true`, but persistence is absent | Bootstrap reports `PrivateRequiresDatabase` before container or driver construction. | #999 implements/tests; activation follow-up invokes. | Approved | +| S3 | Persistent completed metrics enabled, but persistence is absent | Bootstrap reports `PersistentTorrentCompletedStatRequiresDatabase` before container or driver construction. | #999 implements/tests; activation follow-up invokes. | Approved | +| S4 | No persistence-required capability is enabled, and persistence is absent | Public UDP/HTTP tracker starts with no driver, file, connection, migration, or persistence stores. | Activation follow-up. | Planned | +| S5 | Direct whitelist route called while listing is disabled | Do not attempt a database operation. Target contract: HTTP 409 plus `ActionStatus::Err` and `DisabledByConfiguration`. | Draft `144-make-rest-api-persistence-aware.md`. | Approved target; deferred | +| S6 | Direct key route called while private mode is disabled | Do not attempt a database operation. Target contract: HTTP 409 plus `ActionStatus::Err` and `DisabledByConfiguration`. | Draft `144-make-rest-api-persistence-aware.md`. | Approved target; deferred | +| S7 | A configured database fails during whitelist/key operation | Preserve operational database-failure behavior; do not report this as configuration-disabled. | Existing behavior; review when API #144 changes responses. | Current | +| S8 | Stats/torrent endpoint returns a current value with no historical persistence | Keep the endpoint available, but do not represent a session-only count as an undifferentiated lifetime count. No negative sentinel. | Draft `144-make-rest-api-persistence-aware.md`. | Approved target; deferred | +| S9 | Stats/torrent endpoint returns a value restored from persistence | Represent historical/provenance semantics explicitly and consistently with S8. | Draft `144-make-rest-api-persistence-aware.md`. | Approved target; deferred | +| S10 | `http_api` configured before API #144 is delivered | Temporary activation constraint: require persistence; do not claim API works without it. | Activation follow-up. | Planned | +| S11 | `http_api` configured after API #144 is delivered | API may start without persistence; direct disabled capabilities follow S5/S6. | API #144 work and later activation review. | Planned | +| S12 | Operator restarts from persistence-enabled to persistence-free configuration | Do not open, migrate, write, delete, or otherwise alter the previously selected database target. | Activation follow-up and operational docs. | Approved | +| S13 | Operator restarts from persistence-free to persistence-required configuration | Require a selected database; initialize its complete shared schema and reuse data if the target already exists. | Activation follow-up and operational docs. | Approved | +| S14 | Operator changes database driver or location | Initialize/migrate the new target; never copy or delete historical data automatically. | Activation follow-up and operational docs. | Approved | +| S15 | Container starts with persistence absent | Do not require a driver override or install/create persistence-specific SQLite configuration, file, or directory. | Activation follow-up container work. | Approved | +| S16 | Container starts with persistence configured | Follow actual v3 configuration; retain non-destructive driver-specific setup only when selected. | Activation follow-up container work. | Approved | + +## Rules for new discoveries + +1. Classify the new case using the state vocabulary. +2. Add it here with source evidence and an owner. +3. If it is a persistence-required capability, also add it to the centralized + bootstrap requirement matrix and focused tests. +4. If it changes a public REST contract, coordinate it with + `docs/issues/drafts/144-make-rest-api-persistence-aware.md` and GitHub + issue #144. +5. Never reuse an operational database error for an intentionally disabled + capability. diff --git a/docs/issues/open/999-1978-optional-database-configuration/solution.md b/docs/issues/open/999-1978-optional-database-configuration/solution.md index 65a636b81..b668c4232 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/solution.md +++ b/docs/issues/open/999-1978-optional-database-configuration/solution.md @@ -4,14 +4,19 @@ semantic-links: - docs/issues/open/999-1978-optional-database-configuration/ISSUE.md - docs/issues/open/999-1978-optional-database-configuration/analysis.md - docs/issues/open/1978-configuration-overhaul-epic/configuration-v2-to-v3-migration.md + - docs/issues/open/999-1978-optional-database-configuration/adr-draft.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md --- # Phase 2 - Optional persistence solution ## Status -Pending Phase 1 evidence. Do not treat the preliminary direction below as an -approved implementation decision. +Phase 1 evidence and the Phase 2 design are approved. The approved design is +ready for the analysis-and-solution PR. Phase 3 implementation remains a +separate delivery and must follow this approved contract. ## Decision to Make @@ -19,18 +24,56 @@ Select a design that allows v3 `[core.database]` to be omitted when persistence is unused, while rejecting startup if an enabled persistence-backed capability requires a database. The selected design must preserve v2 behaviour unchanged. -## Candidate Direction +## Approved design The expected configuration representation is `Option` on v3 `Core`. -When absent, runtime construction must not create a driver, database file, -network connection, or migration side effect. This remains subject to Phase 1: -the analysis may identify a more suitable boundary or a prerequisite refactor. +An omitted `[core.database]` table deserializes as `None`; configured drivers +retain the existing v3 driver-specific representation. -The performance objective is explicit: a public UDP tracker that enables none -of the basic persistence-backed capabilities must run without a database. If at -least one such capability is enabled, the selected database is a normal shared -tracker dependency: initialize it once and apply the whole migration set. Do -not design separate schemas or migration streams per feature. +This issue prepares optional persistence at the configuration and +application-container boundaries. While the crate-root runtime aliases remain +v2, bootstrap deliberately passes `Some(Database)` to the optional container +dependency. This preserves the existing effective database dependency during +the v3 activation transition. It is a named, tested compatibility bridge—not +the final persistence-free runtime behavior. + +### Test and activation sequencing + +V3 is not yet the globally active runtime configuration: that migration remains +Issue #1980's responsibility. Issue #999 must not activate v3 merely to test +this contract. + +Phase 3 must instead test the contract at two levels: + +1. **Versioned configuration tests:** construct and deserialize + `v3_0_0::Configuration` directly to prove that an omitted + `[core.database]` becomes `None` and that configured SQLite, MySQL, and + PostgreSQL variants retain their driver-specific behavior. +2. **Optional-container tests:** exercise the container constructors with an + explicit persistence dependency and prove that the temporary bootstrap + bridge passes `Some(Database)` while v2 remains active. These tests confirm + the containers can receive `None`, but do not claim a persistence-free main + runtime yet. + +Issue #1980 activates v3 consumers using the temporary compatibility database +dependency. A small follow-up issue, drafted in +`persistence-free-runtime-activation-draft.md`, then replaces that explicit +`Some(Database)` with the actual v3 `core.database` value, runs the capability +requirement validation, and delivers the full persistence-free runtime +guarantee. The final M1–M6 end-to-end evidence belongs to that follow-up. + +The intended activation-follow-up persistence-free deployment includes a public +UDP tracker and/or public HTTP tracker. Listing, private mode, persistent +completed statistics, and the management REST API remain disabled. The REST API +can join a later persistence-free deployment only after API #144 implements its +approved next-major contract. This deployment is the scope of the activation +follow-up, not the effective runtime result of #999. + +This issue makes containers capable of representing absent persistence. The +activation follow-up owns the minimum configuration-aware REST API behavior +needed for persistence-free operation. The detailed drafts for the Phase 3 ADR, +the activation follow-up, and a future persistence-awareness EPIC are in this +issue folder. ## Required Solution Content @@ -40,59 +83,185 @@ not design separate schemas or migration streams per feature. - Specify whether empty or partial database sections are rejected and how their errors are reported. - Define the v2-to-v3 migration guidance and confirm v2 remains unchanged. +- Define the temporary explicit database bridge used through v3 activation and + the follow-up removal plan. ### Capability validation matrix -For every persistence-backed capability identified in Phase 1, define its -enablement condition and startup validation result when the database is absent. +Issue #999 implements and unit-tests one reusable bootstrap-owned +application-composition check. It is the **only** owner of the +feature-to-persistence matrix; do not duplicate the rule in +`packages/configuration::Validator`. + +The active bootstrap path does not invoke this check while it deliberately +passes the temporary `Some(Database)` bridge. The activation follow-up invokes +the already-implemented check after v3 configuration loading and before +`AppContainer` or `TrackerCoreContainer` construction, using the actual v3 +`Option` value. + +The configuration crate continues to validate field-local values and its own +cross-field consistency. The persistence requirement is application policy: it +depends on the services bootstrap constructs and may shrink as the follow-up +refactoring decouples further capabilities. -| Capability | Enabled when | Database omitted result | Error text / code | Tests | -| ---------- | ------------ | ----------------------- | ----------------- | ----- | -| TODO | TODO | TODO | TODO | TODO | +The initial matrix below is authoritative for Phase 3. If implementation finds +another capability that requires a persistence store, add it to this centralized +matrix, the reusable validation implementation, its focused tests, and the +activation-follow-up draft before merging. Do not add an ad hoc repository or +route-level missing-database check. -When a requirement depends only on the relationship between configuration -options, implement it as a configuration-consistency rule through `Validator` -and `SemanticValidationError`. The precedent is -`UselessPrivateModeSection`, which rejects `[core.private_mode]` when -`core.private` is false. Follow the validation-layer policy in -`docs/adrs/20260723184019_separate_configuration_value_invariants_from_consistency_validation.md`: -field-local constraints belong in typed deserialization, and filesystem, -network, or deployment facts belong in runtime/environment validation. +The reusable check returns `PersistenceRequirementError` with one stable variant +per approved capability: -Keep the rule centralized at the configuration/bootstrap boundary rather than -asking each repository or feature implementation to discover a missing database -at runtime. Phase 2 must select and document one owner for this check, including -why that owner matches the validation-layer policy. +| Variant | Diagnostic | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| `ListedRequiresDatabase` | `Configuration requires persistence for \`core.listed\`, but \`[core.database]\` is missing.` | +| `PrivateRequiresDatabase` | `Configuration requires persistence for \`core.private\`, but \`[core.database]\` is missing.` | +| `PersistentTorrentCompletedStatRequiresDatabase` | `Configuration requires persistence for \`core.tracker_policy.persistent_torrent_completed_stat\`, but \`[core.database]\` is missing.` | + +The error type belongs beside the reusable bootstrap requirement-check function, +not in `packages/configuration::Validator`. Phase 3 tests each variant and its +diagnostic independently. + +| Capability | Enabled when | Final activation-follow-up result | Initial test expectation | +| ---------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| Whitelist | `core.listed = true` | Startup fails before container construction. | Error names `core.listed` and missing `[core.database]`. | +| Private keys | `core.private = true` | Startup fails before container construction. | Error names `core.private` and missing `[core.database]`. | +| Persistent completed metrics | `core.tracker_policy.persistent_torrent_completed_stat = true` | Startup fails before container construction. | Error names the setting and missing `[core.database]`. | +| Management REST API | `http_api` is configured | Remains persistence-required until the next-major API #144 work implements the approved disabled-capability contract. | Activation follow-up documents the temporary requirement; API #144 tests the persistence-free API contract. | +| Persistence-free tracker | None of the persistence-backed conditions apply | Startup succeeds without driver construction, migrations, database file, or network connection. | Activation follow-up proves no persistence artifacts. | + +This becomes deterministic startup validation when the activation follow-up +calls the already-implemented check; it is not a late runtime failure. Database +reachability, filesystem permissions, credentials, and DDL permission remain +runtime/environment failures after configuration has passed validation. ### Runtime lifecycle -- Define the owner and timing of driver construction. -- Define the owner and timing of migration execution. -- Define repository/service construction when persistence is absent. -- State whether any constructor needs refactoring to remove migration side - effects, based on Phase 1 evidence. -- Define the all-or-nothing migration contract: if a database is required, - apply the complete shared schema migration set once; if none is required, do - not construct a driver or execute any migration. -- Define container-entrypoint behaviour for deployments without persistence, - including database-driver environment variables, default-database installation, - and database-directory setup. +The lifecycle is all or nothing: + +1. **Persistence absent and permitted:** construct no driver, database stores, + database file, network connection, or migration. +2. **Persistence configured or required:** construct the selected driver once + and apply the complete shared migration set once before persistence-backed + services are constructed. + +Feature configuration controls code behavior, not schema fragments. Do not add +feature-specific database schemas, migration streams, or migration selection. +Although the current persistence features are relatively independent, managing +conditional migrations would increase upgrade, compatibility, and test +complexity as future features share data or evolve. + +### Persistence configuration transitions + +Persistence configuration is evaluated only when the tracker process starts. +Changing configuration requires a restart; the tracker does not dynamically add +or remove persistence while running. + +| Previous process | Next process configuration | Required behavior | +| ------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| Persistence-free | Persistence-free | Start without persistence artifacts. | +| Persistence-free | A persistence-required capability is enabled | Require a configured database, then initialize its complete shared schema. | +| Persistence-enabled | Persistence-free | Do not open, migrate, write, delete, or otherwise alter the previous database. | +| Persistence-enabled | Persistence-enabled, same target | Reuse the selected database and apply the complete migrations; completed migrations are no-ops. | +| Persistence-enabled | Different driver or database location | Initialize and migrate the newly selected target; do not automatically copy historical data. | + +Existing database state is operator-managed durable state. Disabling +persistence prevents the next process from using that state but never drops +tables or deletes files/records. Re-enabling persistence against the same target +reuses its state; data produced while persistence was disabled is intentionally +not recoverable. Enabling a different target is not an automatic data migration +between drivers or locations. + +### Container entrypoint contract + +The activation follow-up changes the supported container entrypoint as follows: + +1. **No persistence:** do not require a database-driver override, create the + tracker database directory solely for persistence, or install a packaged + SQLite database or database-specific default configuration. +2. **Persistence configured:** retain driver-specific setup only when the + actual v3 configuration selects persistence; do not force a driver through a + default environment override. +3. **Mounted state:** never overwrite or delete mounted `/etc` configuration or + `/var/lib` database files merely because the next configuration disables + persistence. +4. **Target change:** never copy or delete a prior database automatically when + the configured driver or location changes. + +The entrypoint must defer persistence selection to the v3 configuration rather +than independently inventing a database default. User identity, non-database +configuration installation, and runtime directory permissions remain separate +entrypoint responsibilities. + +Phase 3 defines the owner/timing of driver construction and the optional +repository/service constructors. The activation follow-up proves the +persistence-free branch after it replaces the temporary bridge. It also defines +container-entrypoint behavior for no-persistence deployments, including driver +overrides, database directories, and packaged SQLite installation. ### REST API contract -For each route recorded in Phase 1, choose a deterministic outcome when -persistence is unavailable: absence because the feature is disabled, a -documented client error, or another explicitly justified response. The result -must never be an accidental driver or repository failure. +The approved desired REST behavior is an explicit configuration-disabled +response for a direct route whose capability is disabled. It uses HTTP `409 +Conflict` and the existing `ActionStatus::Err` response shape, for example: + +```json +{ + "status": "err", + "reason": "Whitelist capability is disabled by configuration (`core.listed = false`)." +} +``` + +Protocol/application layers must represent this as a distinct +`DisabledByConfiguration` error; it must not reuse the existing database error +path. Existing generic 500 database failures remain reserved for a configured +database that fails operationally after startup. + +This response-model change is deferred to the next-major REST API subissue +draft `docs/issues/drafts/144-make-rest-api-persistence-aware.md`, under +GitHub EPIC issue #144. Therefore the post-#1980 persistence-free activation +follow-up does not include the management REST API: it delivers a public UDP +and/or HTTP tracker with no persistence. Until that subissue implements the +approved REST contract, `http_api` remains a persistence-required capability in +the activation follow-up. + +The same #144 work must make persistence-dependent historical values explicit +rather than silently presenting session values as lifetime values. Do not use a +negative numeric sentinel for unavailable history. This response-field work is +explicitly deferred to REST API v2 rather than being implemented by #999 or its +activation follow-up. + +### Follow-up persistence-awareness EPIC + +Create a detailed future EPIC before closing Phase 2. It must not block #1980 +or require subissues to be created immediately. Its initial work inventory is: + +- distinguish session counters from historical persisted counters in API models; +- expose metric provenance or historical-data availability without sentinels; +- decide session versus lifetime semantics for per-torrent completed counts; +- add persistence-free test helpers, integration tests, examples, and benchmarks; +- remove remaining implicit database assumptions from application composition, + container artifacts, and deployment documentation. + +The API response-model work is coordinated with GitHub issue #144, which owns +the next-major REST API compatibility changes. + +`persistence-unavailable-scenarios.md` is the cross-layer case log for these +states. It distinguishes intentional absence, disabled capability, and +operational database failure, and assigns each case to its delivery issue. ### EPIC ordering and activation decision -State one of the following and update EPIC #1978 accordingly: +Issue #999 is a prerequisite for Issue #1980 because it introduces the v3 +optional representation and optional container dependencies. It does **not** +by itself deliver the persistence-free runtime guarantee. Issue #1980 activates +v3 with the named temporary database bridge, and the small activation follow-up +removes that bridge. The future persistence-awareness EPIC does not block either +issue. -1. #999 blocks #1980 and v3 activation because optional database configuration - is part of the required v3 runtime contract. -2. #999 does not block activation, with documented evidence that activation - remains correct without this optionality. +EPIC #1978 and the v2-to-v3 migration guidance record the approved three-stage +ordering. ### Alternatives and trade-offs @@ -104,7 +273,18 @@ Evaluate at least these alternatives against Phase 1 evidence: - Make database configuration optional and validate capability requirements at startup. +The working direction rejects the first two alternatives: the first abandons +the explicit in-memory deployment capability, and the second permits delayed +failures and hidden feature-to-database coupling. + ## Approval Record -Add the approved design, approver, UTC timestamp, decision rationale, and any -required ADR here before Phase 3 starts. +| Field | Record | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Status | Approved | +| Approver | User/maintainer | +| Approved at | 2026-08-25 UTC | +| Decision | Implement v3 `Option`, optional container dependencies, the reusable bootstrap requirement matrix, and a temporary bridge in #999; activate v3 with the bridge in #1980; activate the actual persistence-free runtime in the refined post-#1980 follow-up. | +| Rationale | This stages a non-breaking configuration representation and composition refactor before runtime activation, preserves the in-memory design goal, and avoids activating an untested `None` path prematurely. | +| Deferred work | Persistence-free REST API behavior and historical metric response semantics are next-major API work under EPIC #144. | +| ADR | `adr-draft.md` is approved for Phase 3 reconciliation and timestamped publication in `docs/adrs/`. | From 8ec136005aa122c7e1f8bcdd90b10baa6299d8dc Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 18:28:10 +0100 Subject: [PATCH 08/22] docs(review): address PR #2098 Copilot suggestions --- .../persistence-unavailable-scenarios.md | 2 +- .../999-1978-optional-database-configuration/solution.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md b/docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md index ec3e23ed5..e073d925a 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md +++ b/docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md @@ -9,7 +9,7 @@ semantic-links: - docs/issues/open/999-1978-optional-database-configuration/analysis.md - docs/issues/open/999-1978-optional-database-configuration/solution.md - docs/issues/open/999-1978-optional-database-configuration/persistence-free-runtime-activation-draft.md - - docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md + - docs/issues/open/999-1978-optional-database-configuration/persistence-awareness-epic-draft.md - docs/issues/drafts/144-make-rest-api-persistence-aware.md --- diff --git a/docs/issues/open/999-1978-optional-database-configuration/solution.md b/docs/issues/open/999-1978-optional-database-configuration/solution.md index b668c4232..523975293 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/solution.md +++ b/docs/issues/open/999-1978-optional-database-configuration/solution.md @@ -18,11 +18,11 @@ Phase 1 evidence and the Phase 2 design are approved. The approved design is ready for the analysis-and-solution PR. Phase 3 implementation remains a separate delivery and must follow this approved contract. -## Decision to Make +## Approved decision -Select a design that allows v3 `[core.database]` to be omitted when persistence +The approved design allows v3 `[core.database]` to be omitted when persistence is unused, while rejecting startup if an enabled persistence-backed capability -requires a database. The selected design must preserve v2 behaviour unchanged. +requires a database. It preserves v2 behaviour unchanged. ## Approved design From 6b851d0656a2ebf0793e56e19e623b8fa17314fc Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 18:29:45 +0100 Subject: [PATCH 09/22] docs(review): record PR #2098 Copilot audit --- .../pr-reviews/pr-2098-copilot-suggestions.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/pr-reviews/pr-2098-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2098-copilot-suggestions.md b/docs/pr-reviews/pr-2098-copilot-suggestions.md new file mode 100644 index 000000000..bb1709889 --- /dev/null +++ b/docs/pr-reviews/pr-2098-copilot-suggestions.md @@ -0,0 +1,52 @@ +--- +semantic-links: + skill-links: + - process-copilot-suggestions + related-artifacts: + - .github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md +--- + + + + + +# PR #2098 Copilot Suggestions Tracking + +Source: Copilot PR review threads for + + +Status legend: + +- `action`: code/docs change applied +- `no-action`: suggestion reviewed; no code change needed +- `resolved`: thread resolved in PR + +## Workflow + +1. Download all review threads (including resolved/outdated state and thread IDs). +2. Add one row per thread in the Suggestions table. +3. Process suggestions one by one: + - decide `action` or `no-action` + - if `action`, apply change and validate + - if needed, commit changes + - reply on the PR thread with the fix commit and outcome, or the no-action rationale + - resolve the PR thread + +4. Set `Thread State` to `resolved` once resolved in PR. + +## Processing Log + +- 2026-08-25: Started processing suggestions. +- 2026-08-25: Applied both documentation fixes in `8ec13600`, replied to each thread, and resolved both threads. + +## Suggestions + +| # | Thread ID | Path | URL | Suggestion Summary | Decision | Reply URL | Status | Thread State | +| --- | ----------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------ | ------------ | +| 1 | `PRRT_kwDOGp2yqc6cK5GS` | `docs/issues/open/999-1978-optional-database-configuration/persistence-unavailable-scenarios.md` | | Correct malformed `related-artifacts` YAML indentation. | action — corrected to sibling list indentation in `8ec13600`. | | DONE | RESOLVED | +| 2 | `PRRT_kwDOGp2yqc6cK5G8` | `docs/issues/open/999-1978-optional-database-configuration/solution.md` | | Align the approved design heading and wording with the Status section. | action — updated to approved-tense wording in `8ec13600`. | | DONE | RESOLVED | + +## Notes + +- Keep this file as an audit log of review handling for the PR. +- Reply on every PR suggestion thread before resolving it so the decision is visible to reviewers. From 428c2b107c24b00850aa5d5031c23905d13ba8b6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 25 Aug 2026 20:27:41 +0100 Subject: [PATCH 10/22] docs(issue-999): record optional persistence composition seam --- .../ISSUE.md | 25 ++++++++ .../solution.md | 59 +++++++++++++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md b/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md index f6a1e322d..4d64feaf7 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md +++ b/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md @@ -166,6 +166,25 @@ application boundary. It must not distribute optional-database checks through repositories or feature implementation code, where a missed call site could become a delayed runtime failure. +### Decision 4: Start Phase 3 at the existing optional database initialization seam + +Phase 3 selects, provisionally and reversibly, `Option` at the +existing tracker-core initialization seam. The container selects a +persistence-enabled or persistence-absent composition path before constructing +services that require initialized stores. Consequently, the enabled path can +continue passing ordinary required persistence dependencies to its consumers; +an `Option` must not cascade through every persistence consumer merely because +configuration can omit the database. + +This is deliberately less invasive than injecting an +`Option` bundle of already-initialized stores from +bootstrap. The alternative remains documented in `solution.md` and is the +fallback if the selected seam cannot keep the optional state at composition +without making container fields or unrelated consumers optional. Driver and +migration implementation ownership remains in `tracker-core` unless Phase 3 +evidence establishes a reason to move it; this decision changes where +optionality is resolved, not schema ownership. + - Related ADRs: `docs/adrs/20260723184019_separate_configuration_value_invariants_from_consistency_validation.md`. - ADRs to create: Decide during Phase 2. Create an ADR only if the selected @@ -282,6 +301,12 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. - 2026-08-25 00:00 UTC - User - Approved the complete Phase 2 design for the analysis-and-solution PR. `solution.md` contains the approval record; Phase 3 implementation remains a separate delivery. +- 2026-08-25 00:00 UTC - User/GitHub Copilot - For Phase 3, selected the + existing tracker-core initialization seam as the provisional location for + `Option`. The `Some` branch must retain required initialized-store + dependencies, avoiding an `Option` cascade through consumers. The optional + pre-initialized persistence-services injection alternative remains a + documented fallback if this selection cannot keep optionality at composition. ## Acceptance Criteria diff --git a/docs/issues/open/999-1978-optional-database-configuration/solution.md b/docs/issues/open/999-1978-optional-database-configuration/solution.md index 523975293..5b8b62642 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/solution.md +++ b/docs/issues/open/999-1978-optional-database-configuration/solution.md @@ -31,11 +31,20 @@ An omitted `[core.database]` table deserializes as `None`; configured drivers retain the existing v3 driver-specific representation. This issue prepares optional persistence at the configuration and -application-container boundaries. While the crate-root runtime aliases remain -v2, bootstrap deliberately passes `Some(Database)` to the optional container -dependency. This preserves the existing effective database dependency during -the v3 activation transition. It is a named, tested compatibility bridge—not -the final persistence-free runtime behavior. +application-container boundaries. Phase 3 provisionally resolves +`Option` at the existing tracker-core initialization seam: its +`Some` branch initializes the selected driver and complete migration set, then +passes ordinary required stores to persistence-backed consumers. Its future +`None` branch must select a persistence-absent composition path before those +consumers are built. This prevents configuration optionality from cascading as +`Option` through consumers that are only valid in the persistence-enabled +composition. + +While the crate-root runtime aliases remain v2, bootstrap deliberately passes +`Some(Database)` to that optional container dependency. This preserves the +existing effective database dependency during the v3 activation transition. It +is a named, tested compatibility bridge—not the final persistence-free runtime +behavior. ### Test and activation sequencing @@ -277,6 +286,46 @@ The working direction rejects the first two alternatives: the first abandons the explicit in-memory deployment capability, and the second permits delayed failures and hidden feature-to-database coupling. +#### Composition alternative A: resolve `Option` in tracker-core (selected) + +`TrackerCoreContainer::initialize_from` receives `Option` and +matches it before constructing persistence-backed services. With `Some`, it +uses tracker-core's existing driver, migration, and store setup to construct a +persistence-enabled composition. With `None`, the future activation path can +construct a separate persistence-absent composition without creating a driver, +database file, network connection, or migration. + +This is selected for Phase 3 because it is the least aggressive evolution of +the existing lifecycle. It localizes optionality at the current database +initialization seam: persistence-enabled consumers receive required store +dependencies, rather than each receiving and repeatedly handling an `Option`. +An `Arc` can share an initialized driver or store, but it does not remove the +need to choose a composition branch before constructing services whose +dependencies must exist. The current active v2 runtime keeps choosing `Some` +through the named compatibility bridge. + +#### Composition alternative B: inject optional initialized persistence services + +Bootstrap or application composition would initialize the driver, migrations, +and stores first, then pass `Option` into tracker-core. +This can enforce that tracker-core never initiates infrastructure when no +dependency is supplied. It may also be appropriate if multiple top-level +containers need to share exactly one prebuilt persistence bundle. + +It is not selected initially because it is more invasive and could make the +top-level composition own lifecycle details that currently belong to +tracker-core. The database setup implementation, including schema and +migration ownership, may remain in tracker-core even if a later refactor moves +the invocation boundary. Reconsider alternative B if alternative A requires +optional container fields, optionality in unrelated consumers, duplicate +initialization paths, or cannot represent the future persistence-absent branch +without weakening dependency invariants. + +Phase 3 must preserve this reversibility: keep the optional boundary explicit, +avoid exposing the temporary v2 bridge as a generic default, and avoid coupling +the persistence-absent branch to the active runtime before the activation +follow-up. + ## Approval Record | Field | Record | From f8320c65e9c3ba881b8fe3dfd01d9733d68aaa02 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 10:24:22 +0100 Subject: [PATCH 11/22] feat(configuration): make v3 persistence optional --- ...onal_application_composition_capability.md | 76 ++++++++++ docs/adrs/index.md | 1 + .../ISSUE.md | 60 +++++--- packages/axum-http-server/src/server.rs | 11 +- .../src/testing/environment.rs | 15 +- .../src/testing/environment.rs | 11 +- packages/configuration/src/v3_0_0/core.rs | 15 +- packages/configuration/src/v3_0_0/mod.rs | 139 ++++++++++++++---- packages/http-core/src/container.rs | 15 +- .../src/v1/container.rs | 15 +- packages/tracker-core/src/container.rs | 63 +++++++- packages/tracker-core/src/databases/setup.rs | 25 +++- .../tracker-core/tests/common/test_env.rs | 11 +- packages/udp-core/src/container.rs | 15 +- .../udp-server/src/testing/environment.rs | 15 +- src/bootstrap/mod.rs | 1 + src/bootstrap/persistence.rs | 130 ++++++++++++++++ src/container.rs | 49 +++++- 18 files changed, 574 insertions(+), 93 deletions(-) create mode 100644 docs/adrs/20260825193119_make_persistence_an_optional_application_composition_capability.md create mode 100644 src/bootstrap/persistence.rs diff --git a/docs/adrs/20260825193119_make_persistence_an_optional_application_composition_capability.md b/docs/adrs/20260825193119_make_persistence_an_optional_application_composition_capability.md new file mode 100644 index 000000000..dc5f6f143 --- /dev/null +++ b/docs/adrs/20260825193119_make_persistence_an_optional_application_composition_capability.md @@ -0,0 +1,76 @@ +--- +semantic-links: + related-artifacts: + - docs/issues/open/999-1978-optional-database-configuration/ISSUE.md + - docs/issues/open/999-1978-optional-database-configuration/solution.md + - packages/configuration/src/v3_0_0/core.rs + - packages/tracker-core/src/container.rs + - src/bootstrap/persistence.rs +--- + +# Make persistence an optional application-composition capability + +## Description + +The tracker historically supports an in-memory deployment, but the active v2 runtime always constructs a database driver and applies the complete shared migration set during application-container initialization. The configuration can omit the v2 `[core.database]` TOML table only because it defaults to SQLite; the runtime cannot operate without persistence. + +Schema v3 makes the absence of `[core.database]` representable. The actual persistence-free runtime is delivered by the post-v3-activation follow-up: until then, bootstrap passes an explicit temporary database dependency to preserve current effective runtime behavior. + +The management REST API exposes both in-memory tracker information and direct persistence-backed capabilities. It remains persistence-required until API #144 implements its next-major disabled-capability response model. + +## Agreement + +The v3 application treats persistence as an optional **application-composition capability**. + +1. `Option` represents configured persistence. An absent database means persistence is unavailable by configuration. +2. Issue #999 implements and unit-tests one reusable bootstrap-owned persistence-requirement check. The activation follow-up invokes it after v3 configuration is loaded and before application-container construction, once bootstrap receives actual `Option` rather than the temporary compatibility bridge. The same feature-to-persistence matrix must not be duplicated in repositories, route handlers, or `packages/configuration::Validator`. +3. Listing, private-mode keys, and persistent completed statistics require configured persistence. If one is enabled without `[core.database]`, startup fails with a diagnostic that names both the enabled capability and the missing database configuration. +4. Phase 3 resolves the optional database at the existing `TrackerCoreContainer` initialization seam. The `Some` branch retains tracker-core's driver, migration, and store setup, then passes required stores to persistence-backed consumers. The future `None` branch selects persistence-absent composition before those consumers are built. +5. Driver, schema, and migration implementation ownership remains in `tracker-core`. The selected composition seam changes where optionality is resolved; it does not move schema ownership or introduce feature-specific schemas, migration streams, or migration selection. +6. When no capability requires persistence, the activation follow-up constructs no persistence driver, store, database file, network connection, or migration side effect. +7. The management REST API may start without persistence only after the next-major API work tracked by GitHub issue #144 implements the approved configuration-disabled response model. Until then, it remains persistence-required at activation. +8. Persistence configuration is evaluated at process startup only. Disabling persistence never deletes or alters prior database state; re-enabling the same target reuses it, and changing targets never transfers data automatically. +9. The container entrypoint defers persistence selection to actual v3 configuration. It does not require or default a database driver when persistence is absent, and it never destructively alters mounted state during a persistence transition. + +## Alternatives considered + +### Inject optional initialized persistence services + +Bootstrap or application composition could initialize a driver, migrations, and stores and pass `Option` into tracker-core. + +This remains a fallback if resolving `Option` in tracker-core requires optional container fields, optionality in unrelated consumers, duplicate initialization paths, or weakens required dependency invariants. It is not selected initially because it is more invasive and could make top-level composition own lifecycle details currently owned by tracker-core. + +### Keep a mandatory database in v3 + +Rejected. It abandons the tracker’s explicit in-memory deployment capability and preserves unconditional persistence coupling. + +### Make persistence optional but let consumers fail when accessed + +Rejected. It makes configuration errors delayed runtime failures and spreads feature-to-persistence knowledge across consumers. + +### Duplicate the capability matrix in configuration validation and bootstrap + +Rejected. Two owners would drift as services and configuration evolve. Bootstrap is the application-composition boundary that knows which services are being constructed. + +## Consequences + +- **Positive:** #999 separates the optional v3 representation and optional composition API from the later runtime behavior change, allowing #1980 to activate v3 first. +- **Positive:** Optionality is localized at the initialization seam; services in the persistence-enabled branch keep required store dependencies rather than repeatedly handling `Option` values. +- **Positive:** The shared-schema lifecycle stays simple: zero drivers in persistence-free mode, exactly one driver and complete migrations otherwise. +- **Positive:** Missing persistence is detected deterministically before driver construction rather than through a late repository failure. +- **Negative:** The future `None` branch must construct a persistence-absent set of services before public runtime activation; Issue #999 deliberately does not activate that branch. +- **Negative:** The management REST API, route behavior, response models, test helpers, and container entrypoint require later work. +- **Negative:** State produced during a persistence-free interval is not recoverable when persistence is later re-enabled. + +## Date + +2026-08-25 + +## References + +- Issue #999 +- Configuration-overhaul EPIC #1978 +- `docs/issues/open/999-1978-optional-database-configuration/analysis.md` +- `docs/issues/open/999-1978-optional-database-configuration/solution.md` +- `docs/adrs/20260723184019_separate_configuration_value_invariants_from_consistency_validation.md` +- GitHub issue #144 diff --git a/docs/adrs/index.md b/docs/adrs/index.md index 6091d108a..52d3427e8 100644 --- a/docs/adrs/index.md +++ b/docs/adrs/index.md @@ -32,6 +32,7 @@ semantic-links: | [20260728115400](20260728115400_define_registar_as_runtime_service_registry.md) | 2026-07-28 | Define Registar as the runtime service registry | `Registar` is the authoritative internal registry of started local services, their final bindings, and stable roles; health checks are one consumer of that metadata. | | [20260821172000](20260821172000_establish_ai_agent_context_capability_and_portability_governance.md) | 2026-08-21 | Establish AI agent context, capability, and portability governance | Git-tracked repository knowledge is authoritative; provider-specific agent facilities are documented optional adapters with evidence-bounded portability reviews. | | [20260822094338](20260822094338_adopt_secrecy_for_sensitive_values.md) | 2026-08-22 | Adopt secrecy for sensitive values | Use the current stable `secrecy::SecretString` directly for credentials; deserialize existing configuration syntax with serde, serialize only at explicit persistence boundaries, and expose values only at immediate runtime-consumption boundaries. | +| [20260825193119](20260825193119_make_persistence_an_optional_application_composition_capability.md) | 2026-08-25 | Make persistence an optional application-composition capability | Schema v3 represents absent persistence with `Option` and resolves it at tracker-core composition while retaining tracker-core schema and migration ownership. | ## ADR Lifecycle diff --git a/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md b/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md index 4d64feaf7..7ec04ada6 100644 --- a/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md +++ b/docs/issues/open/999-1978-optional-database-configuration/ISSUE.md @@ -194,14 +194,14 @@ optionality is resolved, not schema ownership. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Notes / Expected Output | -| --- | ------ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| T1 | DONE | Complete persistence analysis | `analysis.md` records current lifecycle, all discovered consumers, REST coupling, and driver-specific migration behaviour. | -| T2 | DONE | Approve an optional-persistence design | `solution.md` records the approved v3 contract, validation, API deferrals, compatibility bridge, and staged ordering. | -| T3 | TODO | Implement optional v3 database configuration | Apply only the Phase 2-approved design; do not change v2. | -| T4 | TODO | Add regression coverage | Cover absent and present database configurations, required-feature validation, migrations, and REST API behaviour. | -| T5 | TODO | Update migration and operational documentation | Explain the v2-to-v3 difference and any changed deployment requirements. | -| T6 | TODO | Verify and re-review | Run required automatic and manual checks; update acceptance evidence. | +| ID | Status | Task | Notes / Expected Output | +| --- | ------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| T1 | DONE | Complete persistence analysis | `analysis.md` records current lifecycle, all discovered consumers, REST coupling, and driver-specific migration behaviour. | +| T2 | DONE | Approve an optional-persistence design | `solution.md` records the approved v3 contract, validation, API deferrals, compatibility bridge, and staged ordering. | +| T3 | DONE | Implement optional v3 database configuration | `v3_0_0::Core.database` is `Option`; omitted TOML persists and loads as `None`. V2 remains unchanged. | +| T4 | DONE | Add regression coverage | Focused v3 parsing/serialization, validation-matrix, and optional constructor coverage added. Runtime-free scenarios stay deferred. | +| T5 | DONE | Update migration and operational documentation | Published ADR `20260825193119_make_persistence_an_optional_application_composition_capability.md`; activation guidance remains in the follow-up draft. | +| T6 | DONE | Verify and re-review | Focused tests, workspace compilation, `linter all`, and pre-commit pass. M1-M6 remain deferred to the activation follow-up. | ## Progress Tracking @@ -307,6 +307,18 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. dependencies, avoiding an `Option` cascade through consumers. The optional pre-initialized persistence-services injection alternative remains a documented fallback if this selection cannot keep optionality at composition. +- 2026-08-25 00:00 UTC - GitHub Copilot - Implemented the Phase 3 v3 + `Option` representation, persistence-safe serialization, optional + tracker-core constructor seam, named active-v2 compatibility bridge, and the + bootstrap-owned requirement matrix. Published ADR + `20260825193119_make_persistence_an_optional_application_composition_capability.md`. + The runtime still explicitly supplies persistence; activation and M1-M6 remain + deferred to the post-#1980 follow-up. +- 2026-08-25 00:00 UTC - GitHub Copilot - Re-reviewed the Phase 3 + implementation after correcting the optional composition seam so that the + supplied database, rather than `Core.database`, drives persistence setup. + Focused configuration, tracker-core, and application tests passed; workspace + targets compiled; `linter all` and the mandatory pre-commit gate passed. ## Acceptance Criteria @@ -371,22 +383,22 @@ Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. ### Acceptance Verification -| AC ID | Status (`TODO`/`DONE`) | Evidence | -| ----- | ---------------------- | ---------------------------------------------------- | -| AC1 | DONE | `analysis.md` lifecycle inventory | -| AC2 | DONE | `analysis.md` consumer and API inventory | -| AC3 | DONE | `solution.md` approval record | -| AC4 | TODO | Implementation tests and M1 evidence | -| AC5 | TODO | Validation tests and M2 evidence | -| AC6 | TODO | REST API tests and M4 evidence | -| AC7 | TODO | Driver/migration tests and M3 evidence | -| AC8 | DONE | Approved staged ordering in EPIC and migration guide | -| AC9 | TODO | V2 compatibility tests/review | -| AC10 | TODO | M5 evidence in `baseline-e2e-verification.md` | -| AC11 | TODO | M6 container-startup evidence | -| AC12 | TODO | `linter all` output | -| AC13 | TODO | Focused, relevant workspace, and M1–M6 evidence | -| AC14 | TODO | Post-implementation acceptance review | +| AC ID | Status (`TODO`/`DONE`) | Evidence | +| ----- | ---------------------- | -------------------------------------------------------- | +| AC1 | DONE | `analysis.md` lifecycle inventory | +| AC2 | DONE | `analysis.md` consumer and API inventory | +| AC3 | DONE | `solution.md` approval record | +| AC4 | TODO | Implementation tests and M1 evidence | +| AC5 | TODO | Validation tests and M2 evidence | +| AC6 | TODO | REST API tests and M4 evidence | +| AC7 | TODO | Driver/migration tests and M3 evidence | +| AC8 | DONE | Approved staged ordering in EPIC and migration guide | +| AC9 | DONE | V2 configuration tests and active explicit bridge review | +| AC10 | TODO | M5 evidence in `baseline-e2e-verification.md` | +| AC11 | TODO | M6 container-startup evidence | +| AC12 | DONE | `linter all` passed on 2026-08-25 | +| AC13 | TODO | Focused, relevant workspace, and M1–M6 evidence | +| AC14 | DONE | Phase 3 review; activation-owned criteria remain pending | ## Risks and Trade-offs diff --git a/packages/axum-http-server/src/server.rs b/packages/axum-http-server/src/server.rs index 398ef0dbc..6f649e6f7 100644 --- a/packages/axum-http-server/src/server.rs +++ b/packages/axum-http-server/src/server.rs @@ -420,8 +420,15 @@ mod tests { configuration.core.tracker_usage_statistics.into(), )); - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(&core_config, &swarm_coordination_registry_container).await); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + &core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await + .expect("HTTP server test initialization requires persistence"), + ); let announce_service = Arc::new(AnnounceService::new( tracker_core_container.core_config.clone(), diff --git a/packages/axum-http-server/src/testing/environment.rs b/packages/axum-http-server/src/testing/environment.rs index 1cf6bdabc..2dfa26a1e 100644 --- a/packages/axum-http-server/src/testing/environment.rs +++ b/packages/axum-http-server/src/testing/environment.rs @@ -171,14 +171,25 @@ pub struct EnvContainer { } impl EnvContainer { + /// # Panics + /// + /// Panics if the persistence-required tracker-core test container cannot + /// be composed. #[must_use] pub async fn initialize(core_config: &Arc, http_tracker_config: &Arc) -> Self { let swarm_coordination_registry_container = Arc::new(SwarmCoordinationRegistryContainer::initialize( core_config.tracker_usage_statistics.into(), )); - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(core_config, &swarm_coordination_registry_container).await); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await + .expect("HTTP server test initialization requires persistence"), + ); let configuration_instance_id = ConfigurationInstanceId::new(ServiceRole::HttpTracker, 0); let http_tracker_container = HttpTrackerCoreContainer::initialize_from_tracker_core( diff --git a/packages/axum-rest-api-server/src/testing/environment.rs b/packages/axum-rest-api-server/src/testing/environment.rs index e7fabbdbe..1ead93252 100644 --- a/packages/axum-rest-api-server/src/testing/environment.rs +++ b/packages/axum-rest-api-server/src/testing/environment.rs @@ -179,8 +179,15 @@ impl EnvContainer { core_config.tracker_usage_statistics.into(), )); - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(&core_config, &swarm_coordination_registry_container).await); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + &core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await + .expect("REST API server test initialization requires persistence"), + ); let http_tracker_core_container = HttpTrackerCoreContainer::initialize_from_tracker_core( &tracker_core_container, diff --git a/packages/configuration/src/v3_0_0/core.rs b/packages/configuration/src/v3_0_0/core.rs index 4fcfadc41..620cf2566 100644 --- a/packages/configuration/src/v3_0_0/core.rs +++ b/packages/configuration/src/v3_0_0/core.rs @@ -17,9 +17,12 @@ pub struct Core { #[serde(default = "Core::default_announce_policy")] pub announce_policy: AnnouncePolicy, - /// Database configuration. - #[serde(default = "Core::default_database")] - pub database: Database, + /// Optional database configuration. + /// + /// When omitted, persistence is unavailable by configuration. Runtime + /// capability validation is performed by application bootstrap. + #[serde(default)] + pub database: Option, /// Interval in seconds that the cleanup job will run to remove inactive /// peers from the torrent peer list. @@ -55,7 +58,7 @@ impl Default for Core { fn default() -> Self { Self { announce_policy: Self::default_announce_policy(), - database: Self::default_database(), + database: None, inactive_peer_cleanup_interval: Self::default_inactive_peer_cleanup_interval(), listed: Self::default_listed(), private: Self::default_private(), @@ -71,10 +74,6 @@ impl Core { AnnouncePolicy::default() } - fn default_database() -> Database { - Database::default() - } - fn default_inactive_peer_cleanup_interval() -> u64 { 600 } diff --git a/packages/configuration/src/v3_0_0/mod.rs b/packages/configuration/src/v3_0_0/mod.rs index 0eda984cf..7472dde29 100644 --- a/packages/configuration/src/v3_0_0/mod.rs +++ b/packages/configuration/src/v3_0_0/mod.rs @@ -229,10 +229,6 @@ //! interval_min = 120 //! max_peers_per_announce = 74 //! -//! [core.database] -//! driver = "sqlite3" -//! path = "./storage/tracker/lib/database/sqlite3.db" -//! //! [core.tracker_policy] //! max_peer_timeout = 900 //! persistent_torrent_completed_stat = false @@ -382,11 +378,10 @@ impl Configuration { // Make sure user has provided the mandatory options. Self::check_mandatory_options(&figment)?; - // Fill missing options with default values. `Database` defaults itself - // during deserialization, so omit that nested value from Figment's - // defaults. Otherwise Figment merges SQLite's default `path` into a - // user-supplied network-database table, which the driver-specific - // validation correctly rejects. + // Fill missing options with default values. Omit the optional database + // table from Figment defaults. Otherwise a default SQLite path could + // merge into a user-supplied network-database table, which the + // driver-specific validation correctly rejects. let figment = figment.join(Serialized::defaults(Self::defaults_for_loading())); // Build final configuration. @@ -464,20 +459,22 @@ impl Configuration { /// Will panic if it can't be converted to TOML. #[must_use] fn serialize_toml_for_persistence(&self) -> String { - if self.http_api.is_none() && matches!(self.core.database, database::Database::Sqlite3 { .. }) { + if self.http_api.is_none() && matches!(self.core.database, Some(database::Database::Sqlite3 { .. })) { return toml::to_string(self).expect("Could not encode TOML value"); } let mut configuration = toml::Value::try_from(self).expect("Could not encode TOML value"); - configuration - .get_mut("core") - .and_then(toml::Value::as_table_mut) - .expect("core configuration should serialize to a TOML table") - .insert( - "database".to_string(), - toml::Value::Table(self.core.database.serialize_for_persistence()), - ); + if let Some(database) = &self.core.database { + configuration + .get_mut("core") + .and_then(toml::Value::as_table_mut) + .expect("core configuration should serialize to a TOML table") + .insert( + "database".to_string(), + toml::Value::Table(database.serialize_for_persistence()), + ); + } if let Some(http_api) = &self.http_api { configuration @@ -559,10 +556,6 @@ mod tests { interval_min = 120 max_peers_per_announce = 74 - [core.database] - driver = "sqlite3" - path = "./storage/tracker/lib/database/sqlite3.db" - [core.tracker_policy] max_peer_timeout = 900 persistent_torrent_completed_stat = false @@ -582,6 +575,47 @@ mod tests { .join("\n") } + #[cfg(test)] + fn default_persisted_config_toml() -> String { + r#"[core] + inactive_peer_cleanup_interval = 600 + listed = false + private = false + tracker_usage_statistics = true + + [core.announce_policy] + interval = 120 + interval_min = 120 + max_peers_per_announce = 74 + + [core.tracker_policy] + max_peer_timeout = 900 + persistent_torrent_completed_stat = false + remove_peerless_torrents = true + + [health_check_api] + bind_address = "127.0.0.1:1313" + + [logging] + trace_filter = "info" + trace_style = "full" + + [metadata] + app = "torrust-tracker" + purpose = "configuration" + schema_version = "3.0.0" + + [udp_tracker_server] + connection_id_validation = "strict" + ip_bans_reset_interval_in_secs = 86400 + max_connection_id_errors_per_ip = 10 + "# + .lines() + .map(str::trim_start) + .collect::>() + .join("\n") + } + #[test] fn configuration_should_have_default_values() { let configuration = Configuration::default(); @@ -591,6 +625,35 @@ mod tests { assert_eq!(toml, default_config_toml()); } + #[test] + #[allow(clippy::result_large_err)] + fn it_should_deserialize_an_omitted_database_as_none() { + // Arrange + let info = Info { + config_toml: Some( + r#" + [metadata] + schema_version = "3.0.0" + + [logging] + trace_filter = "info" + + [core] + listed = false + private = false + "# + .to_string(), + ), + config_toml_path: String::new(), + }; + + // Act + let configuration = Configuration::load(&info).expect("configuration should load"); + + // Assert + assert_eq!(configuration.core.database, None); + } + #[test] fn tracker_defaults_should_not_contain_an_external_ip() { assert_eq!(HttpTracker::default().network.external_ip, None); @@ -726,7 +789,7 @@ mod tests { let contents = fs::read_to_string(&path).expect("Something went wrong reading the file"); - assert_eq!(contents, default_config_toml()); + assert_eq!(contents, default_persisted_config_toml()); } #[test] @@ -826,9 +889,9 @@ mod tests { assert_eq!( configuration.core.database, - crate::v3_0_0::database::Database::Sqlite3 { + Some(crate::v3_0_0::database::Database::Sqlite3 { path: "OVERWRITTEN DEFAULT DB PATH".to_string(), - } + }) ); Ok(()) @@ -866,9 +929,9 @@ mod tests { assert_eq!( configuration.core.database, - crate::v3_0_0::database::Database::Sqlite3 { + Some(crate::v3_0_0::database::Database::Sqlite3 { path: "OVERWRITTEN DEFAULT DB PATH".to_string(), - } + }) ); Ok(()) @@ -919,7 +982,7 @@ mod tests { Database::PostgreSQL(expected_connection) }; - assert_eq!(configuration.core.database, expected_database); + assert_eq!(configuration.core.database, Some(expected_database)); } Ok(()) @@ -981,13 +1044,13 @@ mod tests { // Arrange let password = "v3-database-password-only-for-toml-persistence-test"; let mut configuration = Configuration::default(); - configuration.core.database = Database::MySQL(ConnectionInfo { + configuration.core.database = Some(Database::MySQL(ConnectionInfo { host: "mysql".to_string(), port: 3306, user: "db_user".to_string(), password: SecretString::from(password), database: "torrust_tracker".to_string(), - }); + })); // Act let toml = configuration.serialize_toml_for_persistence(); @@ -1020,7 +1083,7 @@ mod tests { }), ] { let mut configuration = Configuration::default(); - configuration.core.database = database; + configuration.core.database = Some(database); let persisted = configuration.serialize_toml_for_persistence(); let loaded: Configuration = toml::from_str(&persisted).expect("persisted configuration should deserialize"); @@ -1029,6 +1092,20 @@ mod tests { } } + #[test] + fn it_should_persist_an_absent_database_without_a_database_table() { + // Arrange + let configuration = Configuration::default(); + + // Act + let toml = configuration.serialize_toml_for_persistence(); + let loaded: Configuration = toml::from_str(&toml).expect("persisted configuration should deserialize"); + + // Assert + assert!(!toml.contains("[core.database]")); + assert_eq!(loaded.core.database, None); + } + #[test] fn external_ip_should_reject_unspecified_ipv4_address() { let result = ExternalIp::try_from(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); diff --git a/packages/http-core/src/container.rs b/packages/http-core/src/container.rs index 6b3f8b714..9c137ae1b 100644 --- a/packages/http-core/src/container.rs +++ b/packages/http-core/src/container.rs @@ -27,6 +27,10 @@ pub struct HttpTrackerCoreContainer { } impl HttpTrackerCoreContainer { + /// # Panics + /// + /// Panics if the persistence-required tracker-core container cannot be + /// composed from the active v2-compatible configuration. #[must_use] pub async fn initialize( core_config: &Arc, @@ -37,8 +41,15 @@ impl HttpTrackerCoreContainer { core_config.tracker_usage_statistics.into(), )); - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(core_config, &swarm_coordination_registry_container).await); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await + .expect("HTTP tracker core initialization requires persistence"), + ); Self::initialize_from_tracker_core(&tracker_core_container, http_tracker_config, configuration_instance_id) } diff --git a/packages/rest-api-runtime-adapter/src/v1/container.rs b/packages/rest-api-runtime-adapter/src/v1/container.rs index 20e6282cb..b2da7c0b0 100644 --- a/packages/rest-api-runtime-adapter/src/v1/container.rs +++ b/packages/rest-api-runtime-adapter/src/v1/container.rs @@ -39,6 +39,10 @@ pub struct TrackerHttpApiCoreContainer { } impl TrackerHttpApiCoreContainer { + /// # Panics + /// + /// Panics if the persistence-required tracker-core container cannot be + /// composed from the active v2-compatible configuration. #[must_use] pub async fn initialize( core_config: &Arc, @@ -52,8 +56,15 @@ impl TrackerHttpApiCoreContainer { core_config.tracker_usage_statistics.into(), )); - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(core_config, &swarm_coordination_registry_container).await); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await + .expect("REST API initialization requires persistence"), + ); let http_tracker_core_container = HttpTrackerCoreContainer::initialize_from_tracker_core( &tracker_core_container, diff --git a/packages/tracker-core/src/container.rs b/packages/tracker-core/src/container.rs index d73859cc0..bc20e514c 100644 --- a/packages/tracker-core/src/container.rs +++ b/packages/tracker-core/src/container.rs @@ -1,6 +1,10 @@ +//! Tracker-core dependency composition. +//! +//! Persistence optionality is resolved at this initialization seam; see ADR +//! [`20260825193119_make_persistence_an_optional_application_composition_capability`](../../../docs/adrs/20260825193119_make_persistence_an_optional_application_composition_capability.md). use std::sync::Arc; -use torrust_tracker_configuration::Core; +use torrust_tracker_configuration::{Core, Database}; use torrust_tracker_swarm_coordination_registry::container::SwarmCoordinationRegistryContainer; use crate::announce_handler::AnnounceHandler; @@ -8,7 +12,7 @@ use crate::authentication::handler::KeysHandler; use crate::authentication::key::repository::in_memory::InMemoryKeyRepository; use crate::authentication::key::repository::persisted::DatabaseKeyRepository; use crate::authentication::service::AuthenticationService; -use crate::databases::setup::{DatabaseStores, initialize_database}; +use crate::databases::setup::{DatabaseStores, initialize_database_from_configuration}; use crate::scrape_handler::ScrapeHandler; use crate::statistics::persisted::downloads::DatabaseDownloadsMetricRepository; use crate::torrent::manager::TorrentsManager; @@ -40,8 +44,10 @@ impl TrackerCoreContainer { pub async fn initialize_from( core_config: &Arc, swarm_coordination_registry_container: &Arc, - ) -> Self { - let db = initialize_database(core_config).await; + database: Option<&Database>, + ) -> Option { + let database = database?; + let db = initialize_database_from_configuration(database).await; let in_memory_whitelist = Arc::new(InMemoryWhitelist::default()); let whitelist_authorization = Arc::new(WhitelistAuthorization::new(core_config, &in_memory_whitelist.clone())); let whitelist_manager = initialize_whitelist_manager(db.whitelist_store.clone(), in_memory_whitelist.clone()); @@ -74,7 +80,7 @@ impl TrackerCoreContainer { let scrape_handler = Arc::new(ScrapeHandler::new(&whitelist_authorization, &in_memory_torrent_repository)); - Self { + Some(Self { core_config: core_config.clone(), database_stores: db, announce_handler, @@ -88,6 +94,51 @@ impl TrackerCoreContainer { db_downloads_metric_repository, torrents_manager, stats_repository, - } + }) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use crate::test_helpers::tests::ephemeral_configuration; + use torrust_tracker_configuration::Core; + use torrust_tracker_events::bus::SenderStatus; + use torrust_tracker_swarm_coordination_registry::container::SwarmCoordinationRegistryContainer; + + use super::TrackerCoreContainer; + + #[tokio::test] + async fn it_should_not_construct_a_tracker_core_container_without_persistence() { + // Arrange + let core_config = Arc::new(Core::default()); + let swarm_coordination_registry_container = + Arc::new(SwarmCoordinationRegistryContainer::initialize(SenderStatus::Disabled)); + + // Act + let container = TrackerCoreContainer::initialize_from(&core_config, &swarm_coordination_registry_container, None).await; + + // Assert + assert!(container.is_none()); + } + + #[tokio::test] + async fn it_should_construct_a_tracker_core_container_with_supplied_persistence() { + // Arrange + let core_config = Arc::new(ephemeral_configuration()); + let swarm_coordination_registry_container = + Arc::new(SwarmCoordinationRegistryContainer::initialize(SenderStatus::Disabled)); + + // Act + let container = TrackerCoreContainer::initialize_from( + &core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await; + + // Assert + assert!(container.is_some()); } } diff --git a/packages/tracker-core/src/databases/setup.rs b/packages/tracker-core/src/databases/setup.rs index 3c3178293..bf4fb72c1 100644 --- a/packages/tracker-core/src/databases/setup.rs +++ b/packages/tracker-core/src/databases/setup.rs @@ -4,7 +4,7 @@ //! [`20260429000000_keep_database_as_aggregate_supertrait`](../../../docs/adrs/20260429000000_keep_database_as_aggregate_supertrait.md). use std::sync::Arc; -use torrust_tracker_configuration::Core; +use torrust_tracker_configuration::{Core, Database}; use torrust_tracker_primitives::Driver; use super::driver::mysql::Mysql; @@ -89,21 +89,36 @@ where /// ``` #[must_use] pub async fn initialize_database(config: &Core) -> DatabaseStores { - let driver = &config.database.driver; + initialize_database_from_configuration(&config.database).await +} + +/// Initializes and returns a [`DatabaseStores`] bundle for one selected +/// database configuration. +/// +/// This factory is used at the optional persistence composition boundary, where +/// the selected database is already known to be present. +/// +/// # Panics +/// +/// Panics when the database driver cannot be initialized or the shared schema +/// migrations cannot be applied. See [`initialize_database`] for details. +#[must_use] +pub async fn initialize_database_from_configuration(database: &Database) -> DatabaseStores { + let driver = &database.driver; match driver { Driver::Sqlite3 => { - let db = Arc::new(Sqlite::new(&config.database.path).expect("Database driver build failed.")); + let db = Arc::new(Sqlite::new(&database.path).expect("Database driver build failed.")); db.create_database_tables().await.expect("Could not create database tables."); build_database_stores(db) } Driver::MySQL => { - let db = Arc::new(Mysql::new(&config.database.path).expect("Database driver build failed.")); + let db = Arc::new(Mysql::new(&database.path).expect("Database driver build failed.")); db.create_database_tables().await.expect("Could not create database tables."); build_database_stores(db) } Driver::PostgreSQL => { - let db = Arc::new(Postgres::new(&config.database.path).expect("Database driver build failed.")); + let db = Arc::new(Postgres::new(&database.path).expect("Database driver build failed.")); db.create_database_tables().await.expect("Could not create database tables."); build_database_stores(db) } diff --git a/packages/tracker-core/tests/common/test_env.rs b/packages/tracker-core/tests/common/test_env.rs index 855fa0abb..956973dce 100644 --- a/packages/tracker-core/tests/common/test_env.rs +++ b/packages/tracker-core/tests/common/test_env.rs @@ -37,8 +37,15 @@ impl TestEnv { core_config.tracker_usage_statistics.into(), )); - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(&core_config, &swarm_coordination_registry_container).await); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + &core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await + .expect("tracker core test environment requires persistence"), + ); Self { swarm_coordination_registry_container, diff --git a/packages/udp-core/src/container.rs b/packages/udp-core/src/container.rs index c24d21d5d..4a3f992f6 100644 --- a/packages/udp-core/src/container.rs +++ b/packages/udp-core/src/container.rs @@ -33,6 +33,10 @@ pub struct UdpTrackerCoreContainer { } impl UdpTrackerCoreContainer { + /// # Panics + /// + /// Panics if the persistence-required tracker-core container cannot be + /// composed from the active v2-compatible configuration. #[must_use] pub async fn initialize( core_config: &Arc, @@ -43,8 +47,15 @@ impl UdpTrackerCoreContainer { core_config.tracker_usage_statistics.into(), )); - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(core_config, &swarm_coordination_registry_container).await); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await + .expect("UDP tracker core initialization requires persistence"), + ); Self::initialize_from_tracker_core(&tracker_core_container, udp_tracker_config, configuration_instance_id) } diff --git a/packages/udp-server/src/testing/environment.rs b/packages/udp-server/src/testing/environment.rs index 5a2b5d610..5d7a326f7 100644 --- a/packages/udp-server/src/testing/environment.rs +++ b/packages/udp-server/src/testing/environment.rs @@ -204,14 +204,25 @@ pub struct EnvContainer { } impl EnvContainer { + /// # Panics + /// + /// Panics if the persistence-required tracker-core test container cannot + /// be composed. #[must_use] pub async fn initialize(core_config: &Arc, udp_tracker_config: &Arc) -> Self { let swarm_coordination_registry_container = Arc::new(SwarmCoordinationRegistryContainer::initialize( core_config.tracker_usage_statistics.into(), )); - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(core_config, &swarm_coordination_registry_container).await); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + core_config, + &swarm_coordination_registry_container, + Some(&core_config.database), + ) + .await + .expect("UDP server test initialization requires persistence"), + ); let udp_tracker_core_container = UdpTrackerCoreContainer::initialize_from_tracker_core( &tracker_core_container, diff --git a/src/bootstrap/mod.rs b/src/bootstrap/mod.rs index 2f7909043..7c5cdaa80 100644 --- a/src/bootstrap/mod.rs +++ b/src/bootstrap/mod.rs @@ -8,3 +8,4 @@ pub mod app; pub mod config; pub mod jobs; +pub mod persistence; diff --git a/src/bootstrap/persistence.rs b/src/bootstrap/persistence.rs new file mode 100644 index 000000000..fca02edc8 --- /dev/null +++ b/src/bootstrap/persistence.rs @@ -0,0 +1,130 @@ +//! Persistence requirements owned by application bootstrap. +//! +//! The check is intentionally not called while the active runtime uses v2 +//! configuration and its temporary database compatibility bridge. The +//! persistence-free runtime activation follow-up invokes it once bootstrap +//! receives the actual v3 configuration. +use torrust_tracker_configuration::v3_0_0::core::Core; + +/// A configured capability that requires an absent database. +#[derive(thiserror::Error, Debug, PartialEq, Eq)] +pub enum PersistenceRequirementError { + /// Listing needs the whitelist persistence store. + #[error("Configuration requires persistence for `core.listed`, but `[core.database]` is missing.")] + ListedRequiresDatabase, + + /// Private mode needs the authentication-key persistence store. + #[error("Configuration requires persistence for `core.private`, but `[core.database]` is missing.")] + PrivateRequiresDatabase, + + /// Persistent completed metrics need the torrent-metrics persistence store. + #[error( + "Configuration requires persistence for `core.tracker_policy.persistent_torrent_completed_stat`, but `[core.database]` is missing." + )] + PersistentTorrentCompletedStatRequiresDatabase, +} + +/// Validates persistence requirements induced by enabled tracker capabilities. +/// +/// # Errors +/// +/// Returns the first enabled capability that requires persistence when the v3 +/// configuration omits `[core.database]`. +pub const fn validate_persistence_requirements(core: &Core) -> Result<(), PersistenceRequirementError> { + if core.database.is_some() { + return Ok(()); + } + + if core.listed { + return Err(PersistenceRequirementError::ListedRequiresDatabase); + } + + if core.private { + return Err(PersistenceRequirementError::PrivateRequiresDatabase); + } + + if core.tracker_policy.persistent_torrent_completed_stat { + return Err(PersistenceRequirementError::PersistentTorrentCompletedStatRequiresDatabase); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{PersistenceRequirementError, validate_persistence_requirements}; + use torrust_tracker_configuration::v3_0_0::core::Core; + + #[test] + fn it_should_reject_listing_without_a_database() { + // Arrange + let core = Core { + listed: true, + ..Core::default() + }; + + // Act + let result = validate_persistence_requirements(&core); + + // Assert + let error = result.expect_err("listing should require persistence"); + assert_eq!(error, PersistenceRequirementError::ListedRequiresDatabase); + assert_eq!( + error.to_string(), + "Configuration requires persistence for `core.listed`, but `[core.database]` is missing." + ); + } + + #[test] + fn it_should_reject_private_mode_without_a_database() { + // Arrange + let core = Core { + private: true, + ..Core::default() + }; + + // Act + let result = validate_persistence_requirements(&core); + + // Assert + let error = result.expect_err("private mode should require persistence"); + assert_eq!(error, PersistenceRequirementError::PrivateRequiresDatabase); + assert_eq!( + error.to_string(), + "Configuration requires persistence for `core.private`, but `[core.database]` is missing." + ); + } + + #[test] + fn it_should_reject_persistent_completed_metrics_without_a_database() { + // Arrange + let mut core = Core::default(); + core.tracker_policy.persistent_torrent_completed_stat = true; + + // Act + let result = validate_persistence_requirements(&core); + + // Assert + let error = result.expect_err("persistent completed metrics should require persistence"); + assert_eq!( + error, + PersistenceRequirementError::PersistentTorrentCompletedStatRequiresDatabase + ); + assert_eq!( + error.to_string(), + "Configuration requires persistence for `core.tracker_policy.persistent_torrent_completed_stat`, but `[core.database]` is missing." + ); + } + + #[test] + fn it_should_allow_persistence_free_core_configuration() { + // Arrange + let core = Core::default(); + + // Act + let result = validate_persistence_requirements(&core); + + // Assert + assert!(result.is_ok()); + } +} diff --git a/src/container.rs b/src/container.rs index d813d36ac..434cf5534 100644 --- a/src/container.rs +++ b/src/container.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use torrust_server_lib::registar::Registar; -use torrust_tracker_configuration::{Configuration, HttpApi}; +use torrust_tracker_configuration::{Configuration, Database, HttpApi}; use torrust_tracker_core::container::TrackerCoreContainer; use torrust_tracker_http_core::container::{HttpTrackerCoreContainer, HttpTrackerCoreServices}; use torrust_tracker_primitives::ConfigurationInstanceId; @@ -45,6 +45,10 @@ pub struct AppContainer { } impl AppContainer { + /// # Panics + /// + /// Panics when the active v2 runtime fails to provide its mandatory + /// temporary database compatibility bridge. #[instrument(skip(configuration))] pub async fn initialize(configuration: &Configuration) -> Self { // Configuration @@ -65,8 +69,19 @@ impl AppContainer { // Core - let tracker_core_container = - Arc::new(TrackerCoreContainer::initialize_from(&core_config, &swarm_coordination_registry_container).await); + // Temporary compatibility bridge: remove after #1980 activates v3 and + // the persistence-free runtime activation follow-up passes actual v3 + // `core.database` to composition. + let v2_database_compatibility_bridge = Some(v2_database_compatibility_bridge(configuration)); + let tracker_core_container = Arc::new( + TrackerCoreContainer::initialize_from( + &core_config, + &swarm_coordination_registry_container, + v2_database_compatibility_bridge, + ) + .await + .expect("active v2 runtime must provide the temporary database compatibility bridge"), + ); // HTTP @@ -224,3 +239,31 @@ impl AppContainer { containers } } + +/// Supplies persistence while configuration aliases still use schema v2. +/// +/// Remove this bridge after Issue #1980 activates v3 consumers and the +/// persistence-free runtime activation follow-up passes actual v3 +/// `core.database` to composition. +const fn v2_database_compatibility_bridge(configuration: &Configuration) -> &Database { + &configuration.core.database +} + +#[cfg(test)] +mod tests { + use torrust_tracker_configuration::Configuration; + + use super::v2_database_compatibility_bridge; + + #[test] + fn it_should_explicitly_supply_v2_database_to_the_temporary_compatibility_bridge() { + // Arrange + let configuration = Configuration::default(); + + // Act + let database = v2_database_compatibility_bridge(&configuration); + + // Assert + assert_eq!(database, &configuration.core.database); + } +} From 90b4483c3757c281ca875df407380917e13b2e55 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 10:34:19 +0100 Subject: [PATCH 12/22] style(rustfmt): apply nightly import formatting --- packages/tracker-core/src/container.rs | 2 +- src/bootstrap/persistence.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/tracker-core/src/container.rs b/packages/tracker-core/src/container.rs index bc20e514c..b66f9ba95 100644 --- a/packages/tracker-core/src/container.rs +++ b/packages/tracker-core/src/container.rs @@ -102,12 +102,12 @@ impl TrackerCoreContainer { mod tests { use std::sync::Arc; - use crate::test_helpers::tests::ephemeral_configuration; use torrust_tracker_configuration::Core; use torrust_tracker_events::bus::SenderStatus; use torrust_tracker_swarm_coordination_registry::container::SwarmCoordinationRegistryContainer; use super::TrackerCoreContainer; + use crate::test_helpers::tests::ephemeral_configuration; #[tokio::test] async fn it_should_not_construct_a_tracker_core_container_without_persistence() { diff --git a/src/bootstrap/persistence.rs b/src/bootstrap/persistence.rs index fca02edc8..acd447bee 100644 --- a/src/bootstrap/persistence.rs +++ b/src/bootstrap/persistence.rs @@ -52,9 +52,10 @@ pub const fn validate_persistence_requirements(core: &Core) -> Result<(), Persis #[cfg(test)] mod tests { - use super::{PersistenceRequirementError, validate_persistence_requirements}; use torrust_tracker_configuration::v3_0_0::core::Core; + use super::{PersistenceRequirementError, validate_persistence_requirements}; + #[test] fn it_should_reject_listing_without_a_database() { // Arrange From e46c6958979f817dee17cce3ded0544eaa4d5222 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 10:46:00 +0100 Subject: [PATCH 13/22] feat(prompts): add Copilot suggestion review workflow --- .../process-copilot-suggestions.prompt.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/prompts/process-copilot-suggestions.prompt.md diff --git a/.github/prompts/process-copilot-suggestions.prompt.md b/.github/prompts/process-copilot-suggestions.prompt.md new file mode 100644 index 000000000..a8919a7f8 --- /dev/null +++ b/.github/prompts/process-copilot-suggestions.prompt.md @@ -0,0 +1,23 @@ +--- +name: "Process Copilot Suggestions" +description: "Review, address, reply to, and resolve Copilot suggestions on the current or specified pull request" +argument-hint: "Optional PR number; defaults to the active pull request" +agent: "Copilot Suggestions Handler" +--- + +Process Copilot's review suggestions on this repository's pull request by strictly following the canonical [process Copilot suggestions skill](../skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md) and all applicable repository instructions. + +Target pull request: ${input:PR number (leave empty for the active PR):} + +If no PR number is supplied, identify the active pull request. Process **only Copilot-authored unresolved review threads**; do not modify human reviewer threads. + +Use the full auditable workflow: + +1. Create or update `docs/pr-reviews/pr--copilot-suggestions.md` from the tracker template. +2. Fetch every unresolved review thread with the repository helper scripts and record it in the tracker. +3. Handle one thread at a time: decide `action` or `no-action`; for an action, make the smallest correct fix, validate it, create a GPG-signed commit through the Committer agent, and push it. +4. Always reply with the outcome before resolving that same thread. Use the repository's atomic reply-and-resolve helper; record its reply URL and final status in the tracker. +5. After every push, refetch review threads and process any newly created Copilot threads. +6. Stop only after no Copilot-authored unresolved threads remain. Complete and GPG-sign the tracker-documentation commit, then report the decisions, commits, validation, reply URLs, and any deliberately declined suggestions. + +Do not resolve a thread without a reply. Do not batch-resolve threads. Do not expand a suggestion into an unrelated refactor or feature; explain and decline it or record a follow-up when appropriate. Do not push a fix without the required pre-commit gate. From e6e49a84e3c229a4bb1cee032a46d3bb14193039 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 11:08:13 +0100 Subject: [PATCH 14/22] docs(reviews): rename Copilot review archive --- .github/agents/copilot-suggestions-handler.agent.md | 4 ++-- .../prompts/process-copilot-suggestions.prompt.md | 2 +- .../pr-reviews/process-copilot-suggestions/SKILL.md | 6 +++--- docs/AGENTS.md | 2 +- .../EXAMPLE-COMPLETED.md | 2 +- docs/{pr-reviews => copilot-pr-reviews}/README.md | 6 +++--- .../pr-1967-copilot-suggestions.md | 0 .../pr-1991-copilot-suggestions.md | 0 .../pr-2007-copilot-suggestions.md | 2 +- .../pr-2008-copilot-suggestions.md | 8 ++++---- .../pr-2013-copilot-suggestions.md | 6 +++--- .../pr-2017-copilot-suggestions.md | 4 ++-- .../pr-2020-copilot-suggestions.md | 4 ++-- .../pr-2021-copilot-suggestions.md | 2 +- .../pr-2024-copilot-suggestions.md | 4 ++-- .../pr-2025-copilot-suggestions.md | 0 .../pr-2027-copilot-suggestions.md | 8 ++++---- .../pr-2032-copilot-suggestions.md | 0 .../pr-2037-copilot-suggestions.md | 0 .../pr-2061-copilot-suggestions.md | 0 .../pr-2084-copilot-suggestions.md | 0 .../pr-2085-copilot-suggestions.md | 0 .../pr-2087-copilot-suggestions.md | 0 .../pr-2090-copilot-suggestions.md | 0 .../pr-2093-copilot-suggestions.md | 0 .../pr-2094-copilot-suggestions.md | 0 .../pr-2097-copilot-suggestions.md | 0 .../pr-2098-copilot-suggestions.md | 0 docs/index.md | 12 ++++++------ .../1810-add-frontmatter-to-docs-markdown-files.md | 8 ++++---- 30 files changed, 40 insertions(+), 40 deletions(-) rename docs/{pr-reviews => copilot-pr-reviews}/EXAMPLE-COMPLETED.md (99%) rename docs/{pr-reviews => copilot-pr-reviews}/README.md (82%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-1967-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-1991-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2007-copilot-suggestions.md (98%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2008-copilot-suggestions.md (94%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2013-copilot-suggestions.md (94%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2017-copilot-suggestions.md (97%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2020-copilot-suggestions.md (97%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2021-copilot-suggestions.md (96%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2024-copilot-suggestions.md (97%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2025-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2027-copilot-suggestions.md (95%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2032-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2037-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2061-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2084-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2085-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2087-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2090-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2093-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2094-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2097-copilot-suggestions.md (100%) rename docs/{pr-reviews => copilot-pr-reviews}/pr-2098-copilot-suggestions.md (100%) diff --git a/.github/agents/copilot-suggestions-handler.agent.md b/.github/agents/copilot-suggestions-handler.agent.md index e8b1e72db..782394596 100644 --- a/.github/agents/copilot-suggestions-handler.agent.md +++ b/.github/agents/copilot-suggestions-handler.agent.md @@ -39,7 +39,7 @@ If no tracker file exists for this PR, create one from the template: ```bash cp docs/templates/COPILOT-SUGGESTIONS-TEMPLATE.md \ - docs/pr-reviews/pr--copilot-suggestions.md + docs/copilot-pr-reviews/pr--copilot-suggestions.md ``` Fill in `` and ``. @@ -107,7 +107,7 @@ bash .github/skills/dev/pr-reviews/fetch-review-threads/scripts/list-unresolved- Update the tracker Processing Log with timestamps and commit: ```bash -git add docs/pr-reviews/pr--copilot-suggestions.md +git add docs/copilot-pr-reviews/pr--copilot-suggestions.md # then ask the Committer agent to commit ``` diff --git a/.github/prompts/process-copilot-suggestions.prompt.md b/.github/prompts/process-copilot-suggestions.prompt.md index a8919a7f8..63b8be0a5 100644 --- a/.github/prompts/process-copilot-suggestions.prompt.md +++ b/.github/prompts/process-copilot-suggestions.prompt.md @@ -13,7 +13,7 @@ If no PR number is supplied, identify the active pull request. Process **only Co Use the full auditable workflow: -1. Create or update `docs/pr-reviews/pr--copilot-suggestions.md` from the tracker template. +1. Create or update `docs/copilot-pr-reviews/pr--copilot-suggestions.md` from the tracker template. 2. Fetch every unresolved review thread with the repository helper scripts and record it in the tracker. 3. Handle one thread at a time: decide `action` or `no-action`; for an action, make the smallest correct fix, validate it, create a GPG-signed commit through the Committer agent, and push it. 4. Always reply with the outcome before resolving that same thread. Use the repository's atomic reply-and-resolve helper; record its reply URL and final status in the tracker. diff --git a/.github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md b/.github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md index 3a99de2c9..b1cee951d 100644 --- a/.github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md +++ b/.github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md @@ -55,7 +55,7 @@ Copy the template to create a tracker for this PR: ```bash cp docs/templates/COPILOT-SUGGESTIONS-TEMPLATE.md \ - docs/pr-reviews/pr--copilot-suggestions.md + docs/copilot-pr-reviews/pr--copilot-suggestions.md ``` Open the tracker file and fill in: @@ -204,7 +204,7 @@ Update the tracker file with completion notes: Commit the tracker as final documentation: ```bash -git add docs/pr-reviews/pr--copilot-suggestions.md +git add docs/copilot-pr-reviews/pr--copilot-suggestions.md git commit -S -m "docs(review): document PR # copilot suggestions audit" ``` @@ -242,7 +242,7 @@ Both are integrated into this workflow automatically. ## Example -See `docs/pr-reviews/EXAMPLE-COMPLETED.md` for a complete worked example +See `docs/copilot-pr-reviews/EXAMPLE-COMPLETED.md` for a complete worked example with all 26 Copilot suggestions processed, decided, and resolved. ## Completion Checklist diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 9070a71ae..76291fbc2 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -31,7 +31,7 @@ For the full project context see the [root AGENTS.md](../AGENTS.md). | `adrs/` | Architectural Decision Records (ADRs) | | `issues/` | Issue specification documents linked to GitHub issues | | `refactor-plans/` | Refactor plan specifications (same lifecycle as issue specs) | -| `pr-reviews/` | Notable PR review records and Copilot suggestion threads | +| `copilot-pr-reviews/` | Copilot PR review records and suggestion threads | | `skills/` | Internal conventions used by humans and AI agents | | `templates/` | Canonical document templates (ADR, EPIC, issue, refactor plan) | | `media/` | Images, diagrams, flamegraphs, benchmark reports, sample torrents | diff --git a/docs/pr-reviews/EXAMPLE-COMPLETED.md b/docs/copilot-pr-reviews/EXAMPLE-COMPLETED.md similarity index 99% rename from docs/pr-reviews/EXAMPLE-COMPLETED.md rename to docs/copilot-pr-reviews/EXAMPLE-COMPLETED.md index eec879eff..06c17f113 100644 --- a/docs/pr-reviews/EXAMPLE-COMPLETED.md +++ b/docs/copilot-pr-reviews/EXAMPLE-COMPLETED.md @@ -3,7 +3,7 @@ semantic-links: skill-links: - process-copilot-suggestions related-artifacts: - - docs/pr-reviews/README.md + - docs/copilot-pr-reviews/README.md --- # PR # Copilot Suggestions Tracking (EXAMPLE - COMPLETED) diff --git a/docs/pr-reviews/README.md b/docs/copilot-pr-reviews/README.md similarity index 82% rename from docs/pr-reviews/README.md rename to docs/copilot-pr-reviews/README.md index 9bf99c41a..770bfd011 100644 --- a/docs/pr-reviews/README.md +++ b/docs/copilot-pr-reviews/README.md @@ -8,7 +8,7 @@ semantic-links: - .github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md --- -# PR Copilot Suggestions Review Workflow +# Copilot PR Suggestions Review Workflow This directory contains tools and templates for managing GitHub Copilot code review suggestions on pull requests. @@ -19,7 +19,7 @@ This directory contains tools and templates for managing GitHub Copilot code rev ## Workflow -1. **Setup** — Copy [docs/templates/COPILOT-SUGGESTIONS-TEMPLATE.md](../templates/COPILOT-SUGGESTIONS-TEMPLATE.md) to a new file named `pr--copilot-suggestions.md` in `docs/pr-reviews/`. +1. **Setup** — Copy [docs/templates/COPILOT-SUGGESTIONS-TEMPLATE.md](../templates/COPILOT-SUGGESTIONS-TEMPLATE.md) to a new file named `pr--copilot-suggestions.md` in `docs/copilot-pr-reviews/`. 2. **Download threads** — Use `bash .github/skills/dev/pr-reviews/fetch-review-threads/scripts/get-pr-review-threads.sh --pr-number --output-file /tmp/pr_threads_.json` to fetch all review threads. @@ -27,7 +27,7 @@ This directory contains tools and templates for managing GitHub Copilot code rev 4. **Apply changes** — For `action` items, apply fixes, validate with linters/tests, and commit. -5. **Resolve threads** — Use `bash .github/skills/dev/pr-reviews/resolve-review-threads/scripts/resolve-all-unresolved-threads.sh --threads-file /tmp/pr_threads_.json` to mark all processed suggestions as resolved in GitHub. +5. **Reply and resolve threads** — For each processed suggestion, use `bash .github/skills/dev/pr-reviews/resolve-review-threads/scripts/reply-and-resolve-thread.sh --thread-id --body ""` to post an outcome before resolving the thread. 6. **Document** — Update the tracker file with decisions and thread states, then commit as part of the PR documentation. diff --git a/docs/pr-reviews/pr-1967-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-1967-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-1967-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-1967-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-1991-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-1991-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-1991-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-1991-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2007-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2007-copilot-suggestions.md similarity index 98% rename from docs/pr-reviews/pr-2007-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2007-copilot-suggestions.md index fd9abdba6..8334b6f5b 100644 --- a/docs/pr-reviews/pr-2007-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2007-copilot-suggestions.md @@ -49,7 +49,7 @@ Status legend: | 5 | `PRRT_kwDOGp2yqc6SWeEV` | `Containerfile` (gcc stage) | [comment](https://github.com/torrust/torrust-tracker/pull/2007#discussion_r3616827560) | Missing `apt-get clean` in gcc stage APT layer — .deb archives remain | action | DONE | resolved | | 6 | `PRRT_kwDOGp2yqc6SW8iK` | `Containerfile` (chef stage) | [comment](https://github.com/torrust/torrust-tracker/pull/2007#discussion_r3616827507) | `cargo-nextest` installed without pinned version — non-reproducible build | action | DONE | resolved | | 7 | `PRRT_kwDOGp2yqc6SW8in` | `project-words.txt` | [comment](https://github.com/torrust/torrust-tracker/pull/2007#discussion_r3616827507) | `Uumy` is an opaque thread ID fragment, not a stable project term — pollutes dictionary | action | DONE | resolved | -| 8 | `PRRT_kwDOGp2yqc6SW8jK` | `docs/pr-reviews/pr-2007-copilot-suggestions.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2007#discussion_r3616827507) | Tracker file should use `` instead of adding ID fragments to global dictionary | action | DONE | resolved | +| 8 | `PRRT_kwDOGp2yqc6SW8jK` | `docs/copilot-pr-reviews/pr-2007-copilot-suggestions.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2007#discussion_r3616827507) | Tracker file should use `` instead of adding ID fragments to global dictionary | action | DONE | resolved | | 9 | `PRRT_kwDOGp2yqc6SW8jn` | `docs/security/README.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2007#discussion_r3616827507) | Security overview still lists old build-stage base images (`rust:trixie`, `gcc:trixie`) | action | DONE | resolved | | 10 | `PRRT_kwDOGp2yqc6SW8j-` | `Containerfile` (tester stage) | [comment](https://github.com/torrust/torrust-tracker/pull/2007#discussion_r3616827507) | `cargo-nextest` in tester stage also unpinned — non-reproducible test execution | action | DONE | resolved | | 11 | `PRRT_kwDOGp2yqc6SX-aD` | `docs/security/docker/scans/build-images.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2007#discussion_r3616827507) | Link targets `non-affecting/` path which does not exist after catalog reorganization | no-action | DONE | resolved | diff --git a/docs/pr-reviews/pr-2008-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2008-copilot-suggestions.md similarity index 94% rename from docs/pr-reviews/pr-2008-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2008-copilot-suggestions.md index adc607401..fa2b4a849 100644 --- a/docs/pr-reviews/pr-2008-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2008-copilot-suggestions.md @@ -53,14 +53,14 @@ Status legend: | 1 | PRRT_kwDOGp2yqc6SVFOl | `.github/workflows/upload_coverage_pr.yaml` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3616316716 | Extract fork-produced artifact archives into a dedicated directory and strip archive paths. | action: use `unzip -j` in `coverage_artifacts` and upload the report from that directory; `linter yaml` passed. | DONE | RESOLVED | | 2 | PRRT_kwDOGp2yqc6SVFPE | `.github/workflows/upload_coverage_pr.yaml` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3616316757 | Remove unsupported Codecov `working-directory` input and use `directory` instead. | no-action: Codecov v7 documents `working-directory` as an input; retaining it ensures the uploader runs from the trusted checkout containing `.git`. | DONE | RESOLVED | | 3 | PRRT*kwDOGp2yqc6SWYB* | `.github/workflows/upload_coverage_pr.yaml` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3616793119 | Reject symlinked files extracted from fork-produced artifact archives. | action: require the three expected artifact paths to be regular files and reject symlinks before reading or uploading them. | DONE | RESOLVED | -| 4 | PRRT_kwDOGp2yqc6SWYCe | `docs/pr-reviews/pr-2008-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3616793156 | Add all opaque thread IDs to the scoped cspell ignore directive. | action: added all current tracker thread IDs to the file-scoped cspell ignore directive. | DONE | RESOLVED | +| 4 | PRRT_kwDOGp2yqc6SWYCe | `docs/copilot-pr-reviews/pr-2008-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3616793156 | Add all opaque thread IDs to the scoped cspell ignore directive. | action: added all current tracker thread IDs to the file-scoped cspell ignore directive. | DONE | RESOLVED | | 5 | PRRT_kwDOGp2yqc6SWYC3 | `docs/issues/open/2006-fix-fork-pr-coverage-upload-workflow.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3616793184 | Align the relevant-tests acceptance checkbox with recorded verification. | action: update the acceptance criterion because the pre-push suite completed successfully. | DONE | RESOLVED | | 6 | PRRT_kwDOGp2yqc6SX7AO | `.github/workflows/upload_coverage_pr.yaml` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3617362462 | Prevent archive-entry collisions across untrusted artifact ZIPs. | action: allowlist one expected entry per archive, extract each archive in an isolated temporary directory, then move that file into `coverage_artifacts`. | DONE | RESOLVED | | 7 | PRRT_kwDOGp2yqc6SX7Ah | `.github/workflows/upload_coverage_pr.yaml` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3617362491 | Make artifact-directory creation idempotent. | action: create `coverage_artifacts` with `mkdir -p`. | DONE | RESOLVED | -| 8 | PRRT_kwDOGp2yqc6SX7A1 | `docs/pr-reviews/pr-2008-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3617362517 | Restore the exact opaque thread ID in row 3. | action: restored `PRRT_kwDOGp2yqc6SWYB_` and retained it in the scoped cspell ignore directive. | DONE | RESOLVED | -| 9 | PRRT_kwDOGp2yqc6SfBQ1 | `docs/pr-reviews/pr-2008-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3620043755 | Add the tracker skill-link marker. | action: added `` for the governing review workflow. | DONE | RESOLVED | +| 8 | PRRT_kwDOGp2yqc6SX7A1 | `docs/copilot-pr-reviews/pr-2008-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3617362517 | Restore the exact opaque thread ID in row 3. | action: restored `PRRT_kwDOGp2yqc6SWYB_` and retained it in the scoped cspell ignore directive. | DONE | RESOLVED | +| 9 | PRRT_kwDOGp2yqc6SfBQ1 | `docs/copilot-pr-reviews/pr-2008-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3620043755 | Add the tracker skill-link marker. | action: added `` for the governing review workflow. | DONE | RESOLVED | | 10 | PRRT_kwDOGp2yqc6SfecS | `.github/workflows/upload_coverage_pr.yaml` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3620210121 | Validate untrusted artifact metadata before writing step outputs. | action: require a numeric PR number and 40-character hexadecimal SHA before emitting Codecov metadata outputs. | DONE | RESOLVED | -| 11 | PRRT_kwDOGp2yqc6SgaMi | `docs/pr-reviews/pr-2008-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3620551890 | Remove the duplicate processing-log entry. | action: removed the repeated event so each review-batch milestone appears once. | DONE | RESOLVED | +| 11 | PRRT_kwDOGp2yqc6SgaMi | `docs/copilot-pr-reviews/pr-2008-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3620551890 | Remove the duplicate processing-log entry. | action: removed the repeated event so each review-batch milestone appears once. | DONE | RESOLVED | | 12 | PRRT*kwDOGp2yqc6SgaM* | `.github/workflows/upload_coverage_pr.yaml` | https://github.com/torrust/torrust-tracker/pull/2008#discussion_r3620551932 | Clean temporary extraction directories on all paths. | action: run extraction in a subshell with an `EXIT` trap that removes its temporary directory. | DONE | RESOLVED | ## Notes diff --git a/docs/pr-reviews/pr-2013-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2013-copilot-suggestions.md similarity index 94% rename from docs/pr-reviews/pr-2013-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2013-copilot-suggestions.md index a53db5b6e..f4390b92e 100644 --- a/docs/pr-reviews/pr-2013-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2013-copilot-suggestions.md @@ -47,9 +47,9 @@ Status legend: | # | Thread ID | Path | URL | Suggestion Summary | Decision | Reply URL | Status | Thread State | | --- | ----------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------ | ------------ | | 1 | `PRRT_kwDOGp2yqc6SitOP` | `docs/issues/open/1875-review-lto-fat-in-dev-profile/ISSUE.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621393047) | Update stale references to the standalone issue-spec path. | action — updated the EPIC's direct references and migrated the open-issues naming convention to folder specs. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621442150) | DONE | RESOLVED | -| 2 | `PRRT_kwDOGp2yqc6Si2c6` | `docs/pr-reviews/pr-2013-copilot-suggestions.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621444815) | Add `` to avoid spell-check failures on opaque thread IDs. | action — added `` to the tracker file and to the template so future PR trackers include it automatically. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621514178) | DONE | RESOLVED | -| 3 | `PRRT_kwDOGp2yqc6SjBkm` | `docs/pr-reviews/pr-2013-copilot-suggestions.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621507603) | Workflow section missing the explicit reply-before-resolve step. | action — added the reply step to the workflow list in this file to match the template and skill. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621586425) | DONE | RESOLVED | -| 4 | `PRRT_kwDOGp2yqc6Sjapq` | `docs/pr-reviews/pr-2013-copilot-suggestions.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621648952) | Missing blank line before step 4 in Workflow list causes unreliable rendering. | action — added blank line before step 4. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621692387) | DONE | RESOLVED | +| 2 | `PRRT_kwDOGp2yqc6Si2c6` | `docs/copilot-pr-reviews/pr-2013-copilot-suggestions.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621444815) | Add `` to avoid spell-check failures on opaque thread IDs. | action — added `` to the tracker file and to the template so future PR trackers include it automatically. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621514178) | DONE | RESOLVED | +| 3 | `PRRT_kwDOGp2yqc6SjBkm` | `docs/copilot-pr-reviews/pr-2013-copilot-suggestions.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621507603) | Workflow section missing the explicit reply-before-resolve step. | action — added the reply step to the workflow list in this file to match the template and skill. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621586425) | DONE | RESOLVED | +| 4 | `PRRT_kwDOGp2yqc6Sjapq` | `docs/copilot-pr-reviews/pr-2013-copilot-suggestions.md` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621648952) | Missing blank line before step 4 in Workflow list causes unreliable rendering. | action — added blank line before step 4. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621692387) | DONE | RESOLVED | | 5 | `PRRT_kwDOGp2yqc6Sj3Ce` | `.github/skills/dev/pr-reviews/resolve-review-threads/scripts/reply-to-thread.sh` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621808021) | `gh api graphql --jq` can return empty/`null` while exiting 0; validate `REPLY_URL` before reporting success. | action — added guard after GraphQL call: exit 1 if `REPLY_URL` is empty or `null`. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621845797) | DONE | RESOLVED | | 6 | `PRRT_kwDOGp2yqc6Sj3Cy` | `.github/skills/dev/pr-reviews/fetch-review-threads/scripts/check-thread-reply-status.sh` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621808055) | `printf` with `%s` emits `"null"` string when url is JSON null; use `jq -n`/`--argjson` for correct types. | action — replaced `printf` with `jq -n --argjson` to preserve JSON types. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621847390) | DONE | RESOLVED | | 7 | `PRRT_kwDOGp2yqc6Sj3DE` | `.github/skills/dev/pr-reviews/resolve-review-threads/scripts/reply-and-resolve-thread.sh` | [comment](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621808078) | GraphQL mutation can return empty/`null` URL while exiting 0; validate before proceeding to resolve. | action — added guard after GraphQL call: exit 1 if `REPLY_URL` is empty or `null`. | [reply](https://github.com/torrust/torrust-tracker/pull/2013#discussion_r3621849094) | DONE | RESOLVED | diff --git a/docs/pr-reviews/pr-2017-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2017-copilot-suggestions.md similarity index 97% rename from docs/pr-reviews/pr-2017-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2017-copilot-suggestions.md index 727990f40..2d9a65773 100644 --- a/docs/pr-reviews/pr-2017-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2017-copilot-suggestions.md @@ -57,8 +57,8 @@ Status legend: | 8 | PRRT_kwDOGp2yqc6Srh5b | project-words.txt | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3624629672 | `nmap`/`nping` before `new*` words; e < m < p | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3624711012 | DONE | RESOLVED | | 9 | PRRT_kwDOGp2yqc6Srh5u | packages/udp-server/src/server/processor.rs | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3624629708 | Doc comment says port 0 "invalid"; RFC 768 permits it; real issue is OS EINVAL | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3624712519 | DONE | RESOLVED | | 10 | PRRT_kwDOGp2yqc6StQu4 | docs/issues/open/1450-discard-udp-requests-from-clients-with-port-0/ISSUE.md | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3625275177 | "invalid socket address" in Current Behaviour section; RFC 768 permits port 0; real issue is OS EINVAL | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628033936 | DONE | RESOLVED | -| 11 | PRRT_kwDOGp2yqc6SuPys | docs/pr-reviews/pr-2017-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3625638747 | Thread #10 marked DONE/RESOLVED but Reply URL left as TBD | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628037362 | DONE | RESOLVED | -| 12 | PRRT_kwDOGp2yqc6S0eYH | docs/pr-reviews/pr-2017-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3627977934 | Processing log says "All 9 threads resolved" but table lists 10; reword to "initial batch" | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628134459 | DONE | RESOLVED | +| 11 | PRRT_kwDOGp2yqc6SuPys | docs/copilot-pr-reviews/pr-2017-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3625638747 | Thread #10 marked DONE/RESOLVED but Reply URL left as TBD | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628037362 | DONE | RESOLVED | +| 12 | PRRT_kwDOGp2yqc6S0eYH | docs/copilot-pr-reviews/pr-2017-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3627977934 | Processing log says "All 9 threads resolved" but table lists 10; reword to "initial batch" | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628134459 | DONE | RESOLVED | | 13 | PRRT_kwDOGp2yqc6S0_RT | packages/udp-server/src/server/processor.rs | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628171246 | Port-0 guard runs after spawn/push into active_requests; flood can evict legit requests; discard in launcher | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628457230 | DONE | RESOLVED | | 14 | PRRT_kwDOGp2yqc6S15Op | project-words.txt | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628506537 | `n*` entries out of order (`nmap`, `nping`); already fixed by full re-sort in b362d26c; thread outdated | no-action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628733576 | DONE | RESOLVED | | 15 | PRRT_kwDOGp2yqc6S2URd | packages/udp-server/src/server/processor.rs | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628661730 | Port-0 tests used empty payload; use valid connect payload so guard regression is detectable | action | https://github.com/torrust/torrust-tracker/pull/2017#discussion_r3628735417 | DONE | RESOLVED | diff --git a/docs/pr-reviews/pr-2020-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2020-copilot-suggestions.md similarity index 97% rename from docs/pr-reviews/pr-2020-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2020-copilot-suggestions.md index 4de142596..376434fc7 100644 --- a/docs/pr-reviews/pr-2020-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2020-copilot-suggestions.md @@ -51,7 +51,7 @@ Column legend: | 8 | PRRT_kwDOGp2yqc6S3LsT | `docs/issues/open/2019-automatically-format-project-dictionary.md` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3628981533 | Align the issue specification with the documented layout. | action: moved the spec to its documented `ISSUE.md` folder layout. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629239329 | DONE | RESOLVED | | 9 | PRRT_kwDOGp2yqc6S3T8 | `contrib/dev-tools/git/hooks/pre-commit.sh` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629029141 | Retain the exact failed step exit code. | action: captured the `run_step` exit code directly. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629242405 | DONE | RESOLVED | | 10 | PRRT_kwDOGp2yqc6S3dJZ | `contrib/dev-tools/git/format-project-words.sh` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629081831 | Support non-GNU local toolchains. | action: replaced GNU-only options with portable equivalents. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629243737 | DONE | RESOLVED | -| 11 | PRRT_kwDOGp2yqc6S3k60 | `docs/pr-reviews/pr-2020-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629126625 | Make tracker column meanings unambiguous. | action: replaced the ambiguous legend with column-specific definitions. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629280599 | DONE | RESOLVED | +| 11 | PRRT_kwDOGp2yqc6S3k60 | `docs/copilot-pr-reviews/pr-2020-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629126625 | Make tracker column meanings unambiguous. | action: replaced the ambiguous legend with column-specific definitions. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629280599 | DONE | RESOLVED | | 12 | PRRT_kwDOGp2yqc6S32cm | `.github/skills/dev/git-workflow/run-linters/references/linters.md` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629225537 | Synchronize the documented portable formatter command. | action: documented `LC_ALL=C sort -u`. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629282267 | DONE | RESOLVED | | 13 | PRRT_kwDOGp2yqc6S32dU | `contrib/dev-tools/git/tests/test-format-project-words.sh` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629225593 | Use portable test assertion options. | action: replaced GNU-only `diff` and `grep` options. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629283760 | DONE | RESOLVED | | 14 | PRRT_kwDOGp2yqc6S32dt | `contrib/dev-tools/git/tests/test-format-project-words.sh` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629225631 | Use portable `grep` options. | action: replaced GNU-only `grep` options. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629285275 | DONE | RESOLVED | @@ -63,7 +63,7 @@ Column legend: | 20 | PRRT_kwDOGp2yqc6S4Coq | `contrib/dev-tools/git/tests/test-format-project-words.sh` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629295532 | Use a portable test directory `mktemp` template. | action: supplied an explicit BSD-compatible template. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629365111 | DONE | RESOLVED | | 21 | PRRT_kwDOGp2yqc6S4JkJ | `docs/issues/open/2019-automatically-format-project-dictionary/ISSUE.md` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629334908 | Link the issue specification to its implementation PR. | action: set `related-pr: 2020`. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629396136 | DONE | RESOLVED | | 22 | PRRT_kwDOGp2yqc6S4Jkr | `project-words.txt` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629334953 | Remove the unreferenced dictionary typo. | action: removed `Unamed`. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629397148 | DONE | RESOLVED | -| 23 | PRRT_kwDOGp2yqc6S4Z4w | `docs/pr-reviews/pr-2020-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629428258 | Remove Markdown asterisks from row 9's thread ID. | action: corrected the thread ID to its exact value. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629575867 | DONE | RESOLVED | +| 23 | PRRT_kwDOGp2yqc6S4Z4w | `docs/copilot-pr-reviews/pr-2020-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629428258 | Remove Markdown asterisks from row 9's thread ID. | action: corrected the thread ID to its exact value. | https://github.com/torrust/torrust-tracker/pull/2020#discussion_r3629575867 | DONE | RESOLVED | ## Notes diff --git a/docs/pr-reviews/pr-2021-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2021-copilot-suggestions.md similarity index 96% rename from docs/pr-reviews/pr-2021-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2021-copilot-suggestions.md index d8d49bf24..e390e7764 100644 --- a/docs/pr-reviews/pr-2021-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2021-copilot-suggestions.md @@ -46,7 +46,7 @@ Status legend: | --- | --------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------ | ------------ | | 1 | PRRT_kwDOGp2yqc6S4jDJ | .github/skills/dev/planning/cleanup-completed-issues/SKILL.md | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629480752 | Replace GNU-specific `find -printf` with portable alternatives | action: valid portability issue for macOS/BSD contributors; replaced with `find ... -exec basename` | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629529899 | DONE | RESOLVED | | 2 | PRRT_kwDOGp2yqc6S4jDn | .github/skills/dev/planning/cleanup-completed-issues/SKILL.md | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629480791 | Apply same portability fix to optional batch extraction example | action: same portability issue in second code block; fixed with matching portable pattern | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629531262 | DONE | RESOLVED | -| 3 | PRRT_kwDOGp2yqc6S4vMe | docs/pr-reviews/pr-2021-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629549407 | Tracker rows still show placeholder reply URLs and OPEN states | no-action: already addressed in commit 2adf848e; file state already reflected DONE/RESOLVED rows | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629558834 | DONE | RESOLVED | +| 3 | PRRT_kwDOGp2yqc6S4vMe | docs/copilot-pr-reviews/pr-2021-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629549407 | Tracker rows still show placeholder reply URLs and OPEN states | no-action: already addressed in commit 2adf848e; file state already reflected DONE/RESOLVED rows | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629558834 | DONE | RESOLVED | | 4 | PRRT_kwDOGp2yqc6S4vM5 | docs/issues/open/1978-configuration-overhaul-epic/EPIC.md | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629549453 | EPIC frontmatter `last-updated-utc` not bumped | action: bumped `last-updated-utc` for EPIC #1978 to reflect archival bookkeeping update | https://github.com/torrust/torrust-tracker/pull/2021#discussion_r3629573095 | DONE | RESOLVED | ## Notes diff --git a/docs/pr-reviews/pr-2024-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2024-copilot-suggestions.md similarity index 97% rename from docs/pr-reviews/pr-2024-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2024-copilot-suggestions.md index be976738a..d8ad85153 100644 --- a/docs/pr-reviews/pr-2024-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2024-copilot-suggestions.md @@ -51,9 +51,9 @@ Table value legend: | 7 | PRRT_kwDOGp2yqc6S-h5v | docs/issues/open/AGENTS.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3631668847 | Use an existing folder-based issue example | action: replaced the nonexistent example with the existing #2022 folder-based issue specification in signed commit `139f7f5c`; gates passed. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3631932871 | DONE | RESOLVED | | 8 | PRRT_kwDOGp2yqc6S-h53 | docs/issues/open/AGENTS.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3631668862 | Use existing paths in the summary table | action: replaced fictional folder examples with current open or closed specifications in signed commit `10a6e06c`; gates passed. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3631961855 | DONE | RESOLVED | | 9 | PRRT_kwDOGp2yqc6S-h6B | docs/issues/open/AGENTS.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3631668877 | Document standalone EPIC layout | action: documented standalone EPIC layout with the existing #1978 EPIC specification in signed commit `a56b2b66`; gates passed. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632004367 | DONE | RESOLVED | -| 10 | PRRT_kwDOGp2yqc6S_eyk | docs/pr-reviews/pr-2024-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632020033 | Keep the tracker log and table states consistent | action: verified the correction in signed commit `f25e56d7`, which scopes the initial completion log to the first five suggestions and records the later completed suggestions separately. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632311033 | DONE | RESOLVED | +| 10 | PRRT_kwDOGp2yqc6S_eyk | docs/copilot-pr-reviews/pr-2024-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632020033 | Keep the tracker log and table states consistent | action: verified the correction in signed commit `f25e56d7`, which scopes the initial completion log to the first five suggestions and records the later completed suggestions separately. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632311033 | DONE | RESOLVED | | 11 | PRRT_kwDOGp2yqc6S_ezE | docs/issues/open/AGENTS.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632020084 | Use consistent paths in the summary table | action: removed redundant `docs/issues/open/` prefixes from open folder-based examples in signed commit `722909ef`; `linter all` and the mandatory pre-commit gate passed. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632393824 | DONE | RESOLVED | -| 12 | PRRT_kwDOGp2yqc6TAiMK | docs/pr-reviews/pr-2024-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632412953 | Clarify the table value legend | action: renamed and clarified the legend for the Decision, Status, and Thread State columns in signed commit `651e49bb`; `linter all` and the mandatory pre-commit gate passed. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632455274 | DONE | RESOLVED | +| 12 | PRRT_kwDOGp2yqc6TAiMK | docs/copilot-pr-reviews/pr-2024-copilot-suggestions.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632412953 | Clarify the table value legend | action: renamed and clarified the legend for the Decision, Status, and Thread State columns in signed commit `651e49bb`; `linter all` and the mandatory pre-commit gate passed. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632455274 | DONE | RESOLVED | | 13 | PRRT_kwDOGp2yqc6TA0fL | .github/skills/dev/planning/create-issue/SKILL.md | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3632519367 | Correct broken lifecycle-document relative links | action: changed both lifecycle-document links from four to five parent-directory segments so they resolve from the skill directory to repository `docs/` in signed commit `ad40b743`; `linter all` and the mandatory pre-commit gate passed. | https://github.com/torrust/torrust-tracker/pull/2024#discussion_r3634224017 | DONE | RESOLVED | ## Notes diff --git a/docs/pr-reviews/pr-2025-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2025-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2025-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2025-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2027-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2027-copilot-suggestions.md similarity index 95% rename from docs/pr-reviews/pr-2027-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2027-copilot-suggestions.md index 34e048606..45e9f4f0c 100644 --- a/docs/pr-reviews/pr-2027-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2027-copilot-suggestions.md @@ -32,14 +32,14 @@ Source: Copilot PR review threads for https://github.com/torrust/torrust-tracker | 8 | PRRT_kwDOGp2yqc6TNNLT | `contrib/dev-tools/git/merge-pull-request.sh` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637179015 | Reject an empty configured signing key. | action: require a non-empty value before allowing preflight to pass. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637611947 | DONE | RESOLVED | | 9 | PRRT_kwDOGp2yqc6TNNLz | `contrib/dev-tools/git/tests/test-merge-pull-request.sh` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637179059 | Cover an empty configured signing key. | action: add deterministic empty-value coverage. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637615000 | DONE | RESOLVED | | 10 | PRRT_kwDOGp2yqc6TN4cm | `contrib/dev-tools/git/tests/test-merge-pull-request.sh` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637427696 | Isolate the unset signing-key fixture from global and system Git configuration. | action: disable both configuration scopes for this assertion. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3638284763 | DONE | RESOLVED | -| 11 | PRRT_kwDOGp2yqc6TN4c0 | `docs/pr-reviews/pr-2027-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637427720 | Keep the completion entry consistent with thread statuses. | no-action: the current tracker reflects thread 7 as resolved and subsequent threads separately. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3638296827 | DONE | RESOLVED | +| 11 | PRRT_kwDOGp2yqc6TN4c0 | `docs/copilot-pr-reviews/pr-2027-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637427720 | Keep the completion entry consistent with thread statuses. | no-action: the current tracker reflects thread 7 as resolved and subsequent threads separately. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3638296827 | DONE | RESOLVED | | 12 | PRRT_kwDOGp2yqc6TN4dE | `docs/issues/open/2022-vendor-and-document-maintainer-merge-workflow/ISSUE.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637427749 | Align the PR auto-close directive with incomplete issue verification. | action: replace `Closes #2022` in the PR body with `Related to #2022`. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3638298790 | DONE | RESOLVED | | 13 | PRRT_kwDOGp2yqc6TN8ei | `contrib/dev-tools/git/tests/test-merge-pull-request.sh` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637450597 | Isolate fixture creation from global signing and hooks. | action: disable signing and hooks for fixture commits. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3638300816 | DONE | RESOLVED | -| 14 | `PRRT_kwDOGp2yqc6TOXJ_` | `docs/pr-reviews/pr-2027-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637604415 | Clarify that the initial log count came from the first fetch. | action: describe subsequent Copilot threads separately. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3638304056 | DONE | RESOLVED | +| 14 | `PRRT_kwDOGp2yqc6TOXJ_` | `docs/copilot-pr-reviews/pr-2027-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637604415 | Clarify that the initial log count came from the first fetch. | action: describe subsequent Copilot threads separately. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3638304056 | DONE | RESOLVED | | 15 | PRRT_kwDOGp2yqc6TOeN7 | `contrib/dev-tools/git/merge-pull-request.sh` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637644873 | Cover the missing `python3` preflight failure. | action: add a PATH-isolated wrapper test for the actionable Python error. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639397442 | DONE | RESOLVED | -| 16 | PRRT_kwDOGp2yqc6TPCSt | `docs/pr-reviews/pr-2027-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637850665 | Preserve the literal thread ID in row 14. | action: wrap the thread ID in inline code. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639398682 | DONE | RESOLVED | +| 16 | PRRT_kwDOGp2yqc6TPCSt | `docs/copilot-pr-reviews/pr-2027-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3637850665 | Preserve the literal thread ID in row 14. | action: wrap the thread ID in inline code. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639398682 | DONE | RESOLVED | | 17 | `PRRT_kwDOGp2yqc6TTtl6` | `docs/issues/open/2022-vendor-and-document-maintainer-merge-workflow/ISSUE.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639585914 | Use a time component in the implementation progress-log entry. | action: add `00:00 UTC` to match the documented progress-log timestamp format. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639638983 | DONE | RESOLVED | -| 18 | `PRRT_kwDOGp2yqc6TT5rG` | `docs/pr-reviews/pr-2027-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639656208 | Keep the tracker completion section consistent with the thread table. | no-action: the comment applies to the intermediate tracker state; the current tracker records thread 17 as DONE and RESOLVED. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639662906 | DONE | RESOLVED | +| 18 | `PRRT_kwDOGp2yqc6TT5rG` | `docs/copilot-pr-reviews/pr-2027-copilot-suggestions.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639656208 | Keep the tracker completion section consistent with the thread table. | no-action: the comment applies to the intermediate tracker state; the current tracker records thread 17 as DONE and RESOLVED. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639662906 | DONE | RESOLVED | | 19 | `PRRT_kwDOGp2yqc6TUAzE` | `docs/issues/open/2022-vendor-and-document-maintainer-merge-workflow/ISSUE.md` | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639697831 | Align manual-scenario title and expected result with the actual dry-run evidence. | action: reframe M2 as supported dry-run validation without claiming an unexecuted live inspection. | https://github.com/torrust/torrust-tracker/pull/2027#discussion_r3639740216 | DONE | RESOLVED | ## Completion diff --git a/docs/pr-reviews/pr-2032-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2032-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2032-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2032-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2037-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2037-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2037-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2037-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2061-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2061-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2061-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2061-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2084-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2084-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2084-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2084-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2085-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2085-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2085-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2085-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2087-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2087-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2087-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2087-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2090-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2090-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2090-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2090-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2093-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2093-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2093-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2093-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2094-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2094-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2094-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2094-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2097-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2097-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2097-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2097-copilot-suggestions.md diff --git a/docs/pr-reviews/pr-2098-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2098-copilot-suggestions.md similarity index 100% rename from docs/pr-reviews/pr-2098-copilot-suggestions.md rename to docs/copilot-pr-reviews/pr-2098-copilot-suggestions.md diff --git a/docs/index.md b/docs/index.md index aae8d4b0e..59b26e9ee 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,7 +15,7 @@ semantic-links: - docs/adrs/README.md - docs/adrs/index.md - docs/issues/README.md - - docs/pr-reviews/README.md + - docs/copilot-pr-reviews/README.md - docs/refactor-plans/closed/README.md - docs/refactor-plans/drafts/README.md - docs/refactor-plans/open/README.md @@ -82,13 +82,13 @@ specs (drafts → open → closed). | [refactor-plans/open/](refactor-plans/open/) | Active refactor plan specs | | [refactor-plans/closed/](refactor-plans/closed/) | Completed refactor plans kept for reference | -## PR Reviews +## Copilot PR Reviews -Records of notable pull request reviews and Copilot suggestion threads. +Records of Copilot pull request suggestion reviews. -| Document | Description | -| -------------------------------------------- | --------------------------------- | -| [pr-reviews/README.md](pr-reviews/README.md) | Overview of the PR review archive | +| Document | Description | +| ------------------------------------------------------------ | ----------------------------------------- | +| [copilot-pr-reviews/README.md](copilot-pr-reviews/README.md) | Overview of the Copilot PR review archive | ## Skills and Conventions diff --git a/docs/issues/closed/1810-add-frontmatter-to-docs-markdown-files.md b/docs/issues/closed/1810-add-frontmatter-to-docs-markdown-files.md index aabdaae0f..16bf56cd8 100644 --- a/docs/issues/closed/1810-add-frontmatter-to-docs-markdown-files.md +++ b/docs/issues/closed/1810-add-frontmatter-to-docs-markdown-files.md @@ -269,7 +269,7 @@ application. The detailed per-file checklist is in the [File Inventory](#file-in | T9 | DONE | Add frontmatter + semantic links to `docs/issues/closed/` 1732 group | 6 | | T10 | DONE | Add frontmatter + semantic links to `docs/issues/closed/` 1740–1750 | 6 | | T11 | DONE | Add frontmatter + semantic links to `docs/issues/open/` supplementary | 4 | -| T12 | DONE | Add frontmatter + semantic links to `docs/pr-reviews/` files | 2 | +| T12 | DONE | Add frontmatter + semantic links to `docs/copilot-pr-reviews/` files | 2 | | T13 | DONE | Add frontmatter + semantic links to `docs/refactor-plans/` files | 5 | | T14 | DONE | Add frontmatter + semantic links to `docs/skills/` files | 1 | | T15 | DONE | Clarify inline marker vs. frontmatter skill-links in `docs/skills/semantic-skill-link-convention.md` | 1 | @@ -370,10 +370,10 @@ Per-file progress checklist. Check each file when its frontmatter has been added - [x] `docs/issues/open/1726-reduce-build-times-sccache/ISSUE.md` - [x] `docs/issues/open/1726-reduce-build-times-sccache/benchmark-results.md` -### T12 — `docs/pr-reviews/` files (2) +### T12 — `docs/copilot-pr-reviews/` files (2) -- [x] `docs/pr-reviews/README.md` -- [x] `docs/pr-reviews/pr-1733-copilot-suggestions.md` +- [x] `docs/copilot-pr-reviews/README.md` +- [x] `docs/copilot-pr-reviews/pr-1733-copilot-suggestions.md` ### T13 — `docs/refactor-plans/` files (5) From 831f9e66446794ba170267ae78f293ebbe30537c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 11:21:04 +0100 Subject: [PATCH 15/22] docs(runtime): clarify initialization requirements --- src/bootstrap/persistence.rs | 2 +- src/container.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/persistence.rs b/src/bootstrap/persistence.rs index acd447bee..d54409bff 100644 --- a/src/bootstrap/persistence.rs +++ b/src/bootstrap/persistence.rs @@ -6,7 +6,7 @@ //! receives the actual v3 configuration. use torrust_tracker_configuration::v3_0_0::core::Core; -/// A configured capability that requires an absent database. +/// An enabled capability whose persistence requirement is unmet. #[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum PersistenceRequirementError { /// Listing needs the whitelist persistence store. diff --git a/src/container.rs b/src/container.rs index 434cf5534..d14047320 100644 --- a/src/container.rs +++ b/src/container.rs @@ -47,8 +47,9 @@ pub struct AppContainer { impl AppContainer { /// # Panics /// - /// Panics when the active v2 runtime fails to provide its mandatory - /// temporary database compatibility bridge. + /// Panics when tracker-core database-driver initialization or database + /// migrations fail, including when the active v2 runtime fails to provide + /// its mandatory temporary database compatibility bridge. #[instrument(skip(configuration))] pub async fn initialize(configuration: &Configuration) -> Self { // Configuration From c39d43eeed3eb3354200edbcb56a12332f8fdea5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 11:30:05 +0100 Subject: [PATCH 16/22] docs(review): document PR #2099 Copilot suggestions audit --- .../pr-2099-copilot-suggestions.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md diff --git a/docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md new file mode 100644 index 000000000..62df46bbb --- /dev/null +++ b/docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md @@ -0,0 +1,53 @@ +--- +semantic-links: + skill-links: + - process-copilot-suggestions + related-artifacts: + - .github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md +--- + + + + + +# PR #2099 Copilot Suggestions Tracking + +Source: Copilot PR review threads for https://github.com/torrust/torrust-tracker/pull/2099 + +Status legend: + +- `action`: code/docs change applied +- `no-action`: suggestion reviewed; no code change needed +- `resolved`: thread resolved in PR + +## Workflow + +1. Download all review threads (including resolved/outdated state and thread IDs). +2. Add one row per thread in the Suggestions table. +3. Process suggestions one by one: + - decide `action` or `no-action` + - if `action`, apply change and validate + - if needed, commit changes + - reply on the PR thread with the fix commit and outcome, or the no-action rationale + - resolve the PR thread + +4. Set `Thread State` to `resolved` once resolved in PR. + +## Processing Log + +- 2026-08-26: Started processing two Copilot-authored unresolved suggestions. +- 2026-08-26: Applied both documentation fixes in `831f9e66`, replied to and resolved both threads, then refetched the PR; no unresolved threads remain. + +## Suggestions + +| # | Thread ID | Path | URL | Suggestion Summary | Decision | Reply URL | Status | Thread State | +| --- | --------------------- | ------------------------------ | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------- | ------ | ------------ | +| 1 | PRRT_kwDOGp2yqc6caFlX | `src/bootstrap/persistence.rs` | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861494481 | Clarify that the error enum represents enabled capabilities requiring a missing database. | action: revised the inverted type documentation in `831f9e66`. | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861790759 | DONE | RESOLVED | +| 2 | PRRT_kwDOGp2yqc6caFlw | `src/container.rs` | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861494527 | Broaden `AppContainer::initialize` panic documentation to cover database setup and migrations. | action: documented all known initialization panic sources in `831f9e66`. | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861796818 | DONE | RESOLVED | + +## Notes + +- Keep this file as an audit log of review handling for the PR. +- Prefer concise decisions with explicit rationale. +- If no code changes are needed, explain why in `Decision`. +- Reply on every PR suggestion thread before resolving it so the decision is visible to reviewers. From f2dbc26015351bad6f481cbfe08264d833780447 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 11:30:51 +0100 Subject: [PATCH 17/22] style(review): format PR #2099 audit table --- docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md index 62df46bbb..52970686f 100644 --- a/docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md +++ b/docs/copilot-pr-reviews/pr-2099-copilot-suggestions.md @@ -40,10 +40,10 @@ Status legend: ## Suggestions -| # | Thread ID | Path | URL | Suggestion Summary | Decision | Reply URL | Status | Thread State | -| --- | --------------------- | ------------------------------ | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------- | ------ | ------------ | -| 1 | PRRT_kwDOGp2yqc6caFlX | `src/bootstrap/persistence.rs` | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861494481 | Clarify that the error enum represents enabled capabilities requiring a missing database. | action: revised the inverted type documentation in `831f9e66`. | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861790759 | DONE | RESOLVED | -| 2 | PRRT_kwDOGp2yqc6caFlw | `src/container.rs` | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861494527 | Broaden `AppContainer::initialize` panic documentation to cover database setup and migrations. | action: documented all known initialization panic sources in `831f9e66`. | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861796818 | DONE | RESOLVED | +| # | Thread ID | Path | URL | Suggestion Summary | Decision | Reply URL | Status | Thread State | +| --- | --------------------- | ------------------------------ | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------- | ------ | ------------ | +| 1 | PRRT_kwDOGp2yqc6caFlX | `src/bootstrap/persistence.rs` | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861494481 | Clarify that the error enum represents enabled capabilities requiring a missing database. | action: revised the inverted type documentation in `831f9e66`. | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861790759 | DONE | RESOLVED | +| 2 | PRRT_kwDOGp2yqc6caFlw | `src/container.rs` | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861494527 | Broaden `AppContainer::initialize` panic documentation to cover database setup and migrations. | action: documented all known initialization panic sources in `831f9e66`. | https://github.com/torrust/torrust-tracker/pull/2099#discussion_r3861796818 | DONE | RESOLVED | ## Notes From 4e405bfb8a5969f3a6c8e4ee3b2221cf53a058e8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 10:50:48 +0100 Subject: [PATCH 18/22] docs(issues): add RFC 5424 logging issue spec --- .../ISSUE.md | 60 ++++++++++ .../rfc-5424-current-state-analysis.md | 108 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md create mode 100644 docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md diff --git a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md new file mode 100644 index 000000000..99f18c2ba --- /dev/null +++ b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md @@ -0,0 +1,60 @@ +--- +doc-type: issue +issue-type: enhancement +status: open +github-issue: 387 +spec-path: docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md +branch: "387-rfc-5424-syslog-logging" +related-pr: null +last-updated-utc: 2026-08-26 +semantic-links: + related-artifacts: + - https://github.com/torrust/torrust-tracker/issues/387 + - docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md +--- + +# Issue #387 - Implement Logging Using RFC 5424 Syslog Format + +> **Source**: GitHub issue [#387](https://github.com/torrust/torrust-tracker/issues/387), opened by [Cameron (da2ce7)](https://github.com/da2ce7) on 2023-08-27. The issue content below is preserved verbatim, apart from this source note and Markdown link normalization. + +## Implement Logging + +Enhance the program's logging functionality by adopting the [RFC 5424 syslog format](https://tools.ietf.org/html/rfc5424). This format ensures structured, consistent log entries that align with industry best practices. Follow these steps to implement the update: + +### Integrate RFC 5424 Format: + +Revise the logging mechanism to adhere to the `RFC 5424`, ensuring each log entry includes priority level, timestamp, hostname, program name, and structured data when applicable. + +### Manage Severity Levels: + +Implement the recommended severity levels (e.g., emergency, alert, warning, notice, info, debug) to accurately reflect the importance of log messages. + +### Configure Log Rotation: + +Develop a log rotation strategy to control log file size and retention, preventing excessive disk space consumption. + +### Define Log Directory: + +Designate a dedicated directory (e.g., `/var/log/torrust/tracker`) for log files, maintaining alignment with Linux directory structure conventions. + +### Enforce Permissions: + +Apply appropriate permissions and ownership to log files and directories to ensure authorized access and modification. + +### Dynamic Log Levels: + +Enable log level configuration (e.g., INFO, DEBUG, ERROR) to control verbosity based on configuration settings. + +### Test and Document: + +Thoroughly test the updated logging mechanism, verifying adherence to `RFC 5424` and proper handling of structured data. Document the changes for clarity. + +## Expected Outcomes: + +- Consistent and structured log entries following `RFC 5424`. +- Efficient log file management with rotation and controlled disk space usage. +- Improved program monitoring and troubleshooting through enhanced log data. + +## Related Discussion + +- [torrust/torrust-demo#4](https://github.com/torrust/torrust-demo/issues/4) diff --git a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md new file mode 100644 index 000000000..acc872841 --- /dev/null +++ b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md @@ -0,0 +1,108 @@ +--- +doc-type: analysis +status: draft +related-issue: 387 +last-updated-utc: 2026-08-26 +semantic-links: + related-artifacts: + - docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md + - packages/configuration/src/v3_0_0/logging.rs + - docs/adrs/20260519000000_define_global_cli_output_contract.md + - docs/adrs/20260727000000_events_are_objective_facts.md + - docs/adrs/20260822094338_adopt_secrecy_for_sensitive_values.md + - https://www.rfc-editor.org/rfc/rfc5424.txt +--- + +# RFC 5424 Current-State Analysis for Issue #387 + +## Purpose + +This analysis checks whether issue #387 remains meaningful against the tracker as of 2026-08-26. It compares the issue with RFC 5424, the current logging implementation, and relevant architecture decisions. It is not an implementation plan. + +## Conclusion + +Issue #387 remains valid, but its requested outcome combines three distinct concerns that should be decided separately before implementation: + +1. RFC 5424 message serialization and transport. +2. Logging destination and lifecycle, including files, rotation, directory, and permissions. +3. Application log-level semantics and configuration. + +The tracker has partially implemented the third concern. It has not implemented RFC 5424 messages, a syslog transport, application-managed log files, rotation, or file-permission policy. + +## RFC 5424 Requirements Relevant to This Issue + +RFC 5424 section 6 defines a syslog message as: + +```text +SYSLOG-MSG = HEADER SP STRUCTURED-DATA [SP MSG] +HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID +``` + +The header uses seven-bit ASCII. `PRI` is ``; facility values are in $0..=23$ and severity values in $0..=7$. RFC 5424 defines severities `Emergency` (0), `Alert` (1), `Critical` (2), `Error` (3), `Warning` (4), `Notice` (5), `Informational` (6), and `Debug` (7). The RFC's version is `1`. + +`TIMESTAMP`, `HOSTNAME`, `APP-NAME`, `PROCID`, and `MSGID` may use the nil value (`-`) when unavailable. `STRUCTURED-DATA` is either `-` or one or more bracketed elements. Structured-data parameter values must escape `"`, `\\`, and `]`. + +RFC 5424 specifies a message format. It does not mandate that an application writes local log files, rotates them, creates `/var/log/torrust/tracker`, or changes Unix ownership and permissions. Those are deployment and operational-policy decisions. + +## Current Tracker State + +### Logging implementation + +`packages/configuration/src/v3_0_0/logging.rs` configures a `tracing_subscriber` formatter once per process. It provides these `trace_filter` values: `off`, `error`, `warn`, `info`, `debug`, and `trace`; the default is `info`. It provides `full`, `pretty`, `compact`, and `json` output styles; the default is `full`. + +The `Json` style produces tracing-subscriber JSON, not RFC 5424 syslog messages. None of the configured styles emits RFC 5424's `PRI`, protocol version, `HOSTNAME`, `APP-NAME`, `PROCID`, `MSGID`, or RFC 5424 `STRUCTURED-DATA` grammar. + +The current threshold vocabulary is tracing's six filters. It does not model the RFC 5424 facility, and does not expose all RFC severity concepts, notably `Emergency`, `Alert`, `Critical`, and `Notice`. `warn` is broadly comparable to RFC `Warning`, `info` to `Informational`, and `debug` to `Debug`, but that resemblance is insufficient for RFC 5424 compliance because `PRI` requires both facility and severity. + +There is no current logging configuration for an output destination, a syslog endpoint, file path, rotation policy, retention policy, directory creation, ownership, or permissions. + +### Existing observability and safety guidance + +The event ADR requires event variants to describe objective facts and keeps enforcement policy at the consumer or enforcement point. An RFC 5424 formatter should therefore serialize existing tracing events and fields without reshaping domain events to suit a log sink. + +The secrecy ADR requires sensitive values to remain redacted in tracing, `Debug`, `Display`, errors, and diagnostics. Any RFC 5424 structured-data encoder must preserve that invariant and must not stringify secret wrappers through an unsafe display path. + +The global CLI output contract says the long-running `torrust-tracker` daemon sends tracing diagnostics to stderr. The current v3 logging module's documentation says stdout, and its subscriber setup does not explicitly choose a production writer. The desired output stream must be clarified before introducing a syslog destination or file sink. + +## Gap Assessment + +| Issue #387 request | Current state | Gap | +| ------------------------------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------- | +| RFC 5424 format | Full, pretty, compact, and JSON tracing formats | Not implemented | +| Priority, timestamp, hostname, program name, structured data | Tracing metadata, timestamp, and fields vary by formatter | RFC header, PRI calculation, and RFC structured-data encoding are not implemented | +| RFC severity levels | `off`, `error`, `warn`, `info`, `debug`, `trace` filters | No facility; no complete RFC severity mapping | +| Log rotation | No application logging-to-file implementation | Not implemented; RFC 5424 does not require it | +| `/var/log/torrust/tracker` log directory | No configured log directory | Not implemented; should be deployment-policy driven | +| Permissions and ownership | No application-managed log files | Not implemented; should account for containers and non-root execution | +| Dynamic log level | `logging.trace_filter` configuration | Partially implemented | +| Test and documentation | Unit tests cover configuration values | RFC conformance, transport/sink, and deployment tests/docs are absent | + +## Decisions Required Before an Implementation Plan + +1. Is the intended product an RFC 5424 formatter for stdout/stderr, a syslog client transport, or both? +2. Should production deployments delegate file persistence, rotation, ownership, and permissions to systemd/journald, Docker/Podman, or an external syslog daemon rather than the tracker process? +3. Which RFC 5424 facility should the tracker use by default, and should it be configurable? +4. How should tracing levels and the RFC severity values map, especially `trace`, `off`, `Critical`, `Alert`, and `Emergency`? +5. Which stable `APP-NAME`, `PROCID`, and `MSGID` values should the tracker emit? +6. Which tracing fields become RFC structured data, what enterprise ID or namespacing is used for `SD-ID`, and how are malformed/non-ASCII keys and values handled? +7. Does the daemon continue to send normal diagnostics to stderr as required by the CLI output ADR, or does a selected syslog sink replace that stream? +8. Which deployment modes must be supported: native package/service, rootless container, privileged container, and manual executable invocation? + +## Recommended Issue Reshaping + +Retain issue #387 as an umbrella or research issue. Split the implementation work only after the decisions above are recorded: + +1. Define the logging-output architecture and RFC 5424 configuration contract. +2. Implement and test a standards-compliant RFC 5424 formatter and severity/facility mapping. +3. Add an optional syslog transport or integrate with the selected platform logger. +4. Document deployment-owned file rotation, directory, and permissions; only add application-managed files if a supported deployment requires them. + +This avoids making the tracker responsible for OS-level policy where the container runtime, systemd/journald, or syslog daemon is the appropriate owner. + +## Sources + +- RFC 5424, sections 6, 6.2, and 6.3: +- Current v3 logging setup: `packages/configuration/src/v3_0_0/logging.rs` +- CLI output contract: `docs/adrs/20260519000000_define_global_cli_output_contract.md` +- Events principle: `docs/adrs/20260727000000_events_are_objective_facts.md` +- Sensitive-data logging policy: `docs/adrs/20260822094338_adopt_secrecy_for_sensitive_values.md` From 0cdf9526ff1c2ffeb93f83113fe5fbeeba692f97 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 11:33:24 +0100 Subject: [PATCH 19/22] docs(issues): complete RFC 5424 logging research --- .../ISSUE.md | 12 ++ .../questions.md | 75 +++++++++++ .../rfc-5424-current-state-analysis.md | 121 ++++++++++++++---- project-words.txt | 2 + 4 files changed, 186 insertions(+), 24 deletions(-) create mode 100644 docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/questions.md diff --git a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md index 99f18c2ba..b514c3bb7 100644 --- a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md +++ b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md @@ -11,12 +11,24 @@ semantic-links: related-artifacts: - https://github.com/torrust/torrust-tracker/issues/387 - docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md + - docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/questions.md --- # Issue #387 - Implement Logging Using RFC 5424 Syslog Format > **Source**: GitHub issue [#387](https://github.com/torrust/torrust-tracker/issues/387), opened by [Cameron (da2ce7)](https://github.com/da2ce7) on 2023-08-27. The issue content below is preserved verbatim, apart from this source note and Markdown link normalization. +## Research Outcome + +Research completed on 2026-08-26 concludes that RFC 5424 support should not be implemented, either completely or partially, at this time. The tracker should retain its existing `tracing`-based logging and operator-managed log collection. + +The current tracker architecture scales a process vertically around process-local, in-memory swarm state. It is not currently deployed as a horizontally interchangeable fleet of tracker replicas, which is the main scenario where direct syslog delivery and central correlation are compelling. For the expected single-instance deployment, the container runtime, host logger, or operator log agent can collect stderr and forward it to central infrastructure when required. + +No implementation subissues or `tracing-rfc-5424` integration should be created now. Reconsider the issue only when a concrete deployment or customer requires the tracker process itself to send RFC 5424 records directly to a syslog daemon. Cameron should decide whether to close #387 as out of current priorities or retain it as a deferred enhancement. + +- [Current-state analysis](rfc-5424-current-state-analysis.md) +- [Research questions](questions.md) + ## Implement Logging Enhance the program's logging functionality by adopting the [RFC 5424 syslog format](https://tools.ietf.org/html/rfc5424). This format ensures structured, consistent log entries that align with industry best practices. Follow these steps to implement the update: diff --git a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/questions.md b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/questions.md new file mode 100644 index 000000000..1235221b0 --- /dev/null +++ b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/questions.md @@ -0,0 +1,75 @@ +--- +doc-type: research-questions +status: open +related-issue: 387 +last-updated-utc: 2026-08-26 +semantic-links: + related-artifacts: + - docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md + - docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md + - https://www.rfc-editor.org/rfc/rfc5424.txt +--- + +# Research Questions for Issue #387 + +This document records the questions that must be answered before deciding whether RFC 5424 support is worth implementing. It is deliberately separate from the issue description and current-state analysis so the research can remain open-ended. + +## Q1. Why does RFC 5424 matter? + +### Short answer + +RFC 5424 matters when an operator needs the tracker to send interoperable, machine-readable records directly to a syslog daemon or collector. It standardizes the record header, severity, facility, timestamp, application identity, and optional structured data so that syslog-aware infrastructure can route, parse, retain, and alert on tracker messages consistently. + +It is not inherently a better way for the tracker to create diagnostic events. The tracker already uses `tracing`, which provides structured events, levels, spans, and subscriber layers. RFC 5424 is an output and interoperability standard for one particular logging ecosystem. + +### Benefits if implemented + +- **Direct syslog integration**: The tracker could send logs to compatible syslog daemons and collectors over established syslog transports rather than relying on stdout/stderr capture. +- **Portable record envelope**: A receiving system can read standard `PRI`, timestamp, hostname, application name, process identifier, message identifier, and structured-data fields without tracker-specific parsing rules. +- **Facility-based routing**: Operators could use the syslog facility and severity to route tracker logs separately from other services, choose retention policies, or trigger alerting rules. +- **Structured-data interoperability**: If the tracker defined a stable RFC 5424 structured-data schema, syslog-aware tools could query tracker attributes without parsing free-form text. +- **Compatibility with existing operations tooling**: Some organizations standardize on syslog relays, SIEM products, and central log collectors that accept RFC 5424 directly. + +### Capabilities the tracker does not have today + +The current tracker logging setup does not itself provide: + +- A standards-compliant RFC 5424 message envelope with `PRI`, facility, syslog protocol version, and syslog header fields. +- A built-in syslog client transport to a daemon through UDP, TCP, or a Unix-domain socket. +- A tracker-defined RFC 5424 structured-data schema for fields such as torrent hash, client label, protocol, or request context. +- A standard syslog facility by which an operator can route the tracker independently in syslog infrastructure. + +### Capabilities the tracker already has + +The absence of RFC 5424 does not mean the tracker lacks logging or observability: + +- `tracing` provides severity filtering and structured event fields to the configured subscriber. +- The current configuration supports dynamic filtering; the newer v3 schema also supports multiple human-readable and JSON output styles. +- Operators can capture stdout/stderr using their chosen runtime, system logger, container platform, or log collector. +- Torrust Tracker Deployer and the Tracker Demo already keep rotation, file retention, directory, ownership, and permissions in deployment infrastructure, where those policies belong. +- Events, metrics, and health checks remain separate observability mechanisms; RFC 5424 would not replace them. + +### Decision implication + +The relevant question is not whether RFC 5424 is objectively better than `tracing`. The relevant question is whether a current or planned deployment needs **direct, standards-based syslog delivery** strongly enough to justify a new sink, configuration, dependency review, and ongoing support. + +Absent that requirement, the current `tracing` output plus operator-managed collection keeps the tracker simpler while preserving its existing logging capabilities. + +## Q2. Does the current tracker deployment model need direct syslog delivery? + +### Answer + +Not as a general capability. Direct RFC 5424 delivery is most useful for a horizontally distributed service fleet, where many instances send records to common syslog infrastructure for correlation, routing, retention, and alerting. + +The current tracker architecture does not use horizontally interchangeable tracker replicas. Each tracker process owns in-memory swarm state that is not separated into an independently shared coordination layer. A larger deployment therefore scales one tracker process vertically rather than running multiple equivalent tracker instances behind a load balancer. + +For the expected single-instance tracker deployment, the host, container runtime, or operator-managed log agent can collect the existing stderr output and forward it to a central syslog service, SIEM, or another log collector. That supplies centralized retention and analysis without requiring the tracker to become a syslog client. + +### Decision implication + +The current deployment model does not justify implementing RFC 5424 support, either as a complete formatter or as a partial `tracing-rfc-5424` integration. The issue should remain research only. Reconsider it only if a real deployment or customer requirement needs the tracker itself to deliver RFC 5424 records directly to a syslog daemon. + +### Evidence + +- RFC 5424 defines the syslog message format and header fields: +- Current tracker logging and RFC gap assessment: `rfc-5424-current-state-analysis.md` diff --git a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md index acc872841..7aa0d8db8 100644 --- a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md +++ b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md @@ -1,6 +1,6 @@ --- doc-type: analysis -status: draft +status: complete related-issue: 387 last-updated-utc: 2026-08-26 semantic-links: @@ -11,23 +11,35 @@ semantic-links: - docs/adrs/20260727000000_events_are_objective_facts.md - docs/adrs/20260822094338_adopt_secrecy_for_sensitive_values.md - https://www.rfc-editor.org/rfc/rfc5424.txt + - https://crates.io/crates/tracing-rfc-5424 + - https://github.com/sp1ff/syslog-tracing --- # RFC 5424 Current-State Analysis for Issue #387 ## Purpose -This analysis checks whether issue #387 remains meaningful against the tracker as of 2026-08-26. It compares the issue with RFC 5424, the current logging implementation, and relevant architecture decisions. It is not an implementation plan. +This analysis checks whether issue #387 remains meaningful against the tracker as of 2026-08-26. It compares the issue with RFC 5424, the current logging implementation, and relevant architecture decisions. It gives a hypothetical remaining-effort estimate; it is not an implementation plan. ## Conclusion -Issue #387 remains valid, but its requested outcome combines three distinct concerns that should be decided separately before implementation: +Issue #387 remains valid, but its requested outcome combines three distinct concerns: 1. RFC 5424 message serialization and transport. 2. Logging destination and lifecycle, including files, rotation, directory, and permissions. 3. Application log-level semantics and configuration. -The tracker has partially implemented the third concern. It has not implemented RFC 5424 messages, a syslog transport, application-managed log files, rotation, or file-permission policy. +The tracker has partially implemented the third concern. It has not implemented RFC 5424 messages or a syslog transport. The tracker deliberately does not own log files, rotation, directory creation, ownership, or permissions: those are infrastructure concerns managed by the tracker operator. Torrust Tracker Deployer configures them for production deployments; the Tracker Demo uses Docker Compose log rotation. + +### Recommendation + +Do not implement RFC 5424 support now, either completely or partially. The existing `tracing`-based logging is adequate for the tracker and avoids creating and maintaining a custom logging subsystem solely to satisfy a complex standard without a demonstrated operational requirement. + +The tracker should continue to use `tracing` and `tracing_subscriber` as its logging abstraction. Strict RFC 5424 output would require either a custom `tracing_subscriber` formatter or layer, or an additional maintained crate that provides the required behavior. This work can remain at the logging-output boundary and does not require changes to domain events, servers, or the tracker architecture. However, it would introduce a new formatting contract, configuration, conformance tests, and ongoing compatibility work with the tracing ecosystem. + +The primary scenario in which direct syslog delivery is valuable is a distributed fleet of services or tracker instances whose logs need central collection and correlation. That is not a likely current tracker deployment: each tracker process owns process-local, in-memory swarm state, so the current architecture scales vertically rather than as horizontally interchangeable replicas. For the expected single-instance deployment, the runtime, host logger, or operator log agent can collect the existing stderr output and forward it centrally without making the tracker a syslog client. + +Retain issue #387 as research only and let Cameron decide whether the remaining benefit warrants that cost. Until a concrete deployment, integration, or customer requirement needs direct RFC 5424 records, no implementation subissues should be created. ## RFC 5424 Requirements Relevant to This Issue @@ -48,13 +60,13 @@ RFC 5424 specifies a message format. It does not mandate that an application wri ### Logging implementation -`packages/configuration/src/v3_0_0/logging.rs` configures a `tracing_subscriber` formatter once per process. It provides these `trace_filter` values: `off`, `error`, `warn`, `info`, `debug`, and `trace`; the default is `info`. It provides `full`, `pretty`, `compact`, and `json` output styles; the default is `full`. +The running tracker daemon initializes logging once during bootstrap through `packages/configuration/src/logging.rs`. The public configuration currently aliases the v2 schema, whose `[logging]` section has one `threshold` setting with values `off`, `error`, `warn`, `info`, `debug`, and `trace`; the default is `info`. The active setup uses the default `tracing_subscriber` formatter, not a configurable style. -The `Json` style produces tracing-subscriber JSON, not RFC 5424 syslog messages. None of the configured styles emits RFC 5424's `PRI`, protocol version, `HOSTNAME`, `APP-NAME`, `PROCID`, `MSGID`, or RFC 5424 `STRUCTURED-DATA` grammar. +`packages/configuration/src/v3_0_0/logging.rs` is a newer, not-yet-active configuration schema. It adds `trace_filter` and the `full`, `pretty`, `compact`, and `json` styles. Its `Json` style produces tracing-subscriber JSON, not RFC 5424 syslog messages. None of the configured styles emits RFC 5424's `PRI`, protocol version, `HOSTNAME`, `APP-NAME`, `PROCID`, `MSGID`, or RFC 5424 `STRUCTURED-DATA` grammar. The current threshold vocabulary is tracing's six filters. It does not model the RFC 5424 facility, and does not expose all RFC severity concepts, notably `Emergency`, `Alert`, `Critical`, and `Notice`. `warn` is broadly comparable to RFC `Warning`, `info` to `Informational`, and `debug` to `Debug`, but that resemblance is insufficient for RFC 5424 compliance because `PRI` requires both facility and severity. -There is no current logging configuration for an output destination, a syslog endpoint, file path, rotation policy, retention policy, directory creation, ownership, or permissions. +There is no current logging configuration for an output destination or syslog endpoint. There is intentionally no application configuration for a file path, rotation policy, retention policy, directory creation, ownership, or permissions; these belong to the deployment configuration selected by the operator. ### Existing observability and safety guidance @@ -71,37 +83,98 @@ The global CLI output contract says the long-running `torrust-tracker` daemon se | RFC 5424 format | Full, pretty, compact, and JSON tracing formats | Not implemented | | Priority, timestamp, hostname, program name, structured data | Tracing metadata, timestamp, and fields vary by formatter | RFC header, PRI calculation, and RFC structured-data encoding are not implemented | | RFC severity levels | `off`, `error`, `warn`, `info`, `debug`, `trace` filters | No facility; no complete RFC severity mapping | -| Log rotation | No application logging-to-file implementation | Not implemented; RFC 5424 does not require it | -| `/var/log/torrust/tracker` log directory | No configured log directory | Not implemented; should be deployment-policy driven | -| Permissions and ownership | No application-managed log files | Not implemented; should account for containers and non-root execution | +| Log rotation | Managed by the deployment infrastructure | Not a tracker application responsibility; RFC 5424 does not require it | +| `/var/log/torrust/tracker` log directory | Managed by the deployment infrastructure | Not a tracker application responsibility | +| Permissions and ownership | Managed by the deployment infrastructure | Not a tracker application responsibility; account for containers and non-root use | | Dynamic log level | `logging.trace_filter` configuration | Partially implemented | | Test and documentation | Unit tests cover configuration values | RFC conformance, transport/sink, and deployment tests/docs are absent | -## Decisions Required Before an Implementation Plan +## RFC 5424 Facility + +The facility is the source category encoded in the RFC 5424 `PRI` value. It is not the same as a log level. For example, a facility of `local0` has numeric value 16; an `Informational` severity has numeric value 6; together they produce `<134>` because $16 \times 8 + 6 = 134$. + +Facilities let a syslog receiver route records from different applications or subsystems. The standard reserves `local0` through `local7` for local policy. A tracker implementation should select one `local*` facility, normally as a fixed product decision, unless operators have a demonstrated need to configure it. This decision matters only if the tracker emits RFC 5424 records or sends them to a syslog receiver. + +## `tracing-rfc-5424` Crate Assessment + +The [`tracing-rfc-5424`](https://crates.io/crates/tracing-rfc-5424) crate, from [`sp1ff/syslog-tracing`](https://github.com/sp1ff/syslog-tracing), is an existing `tracing_subscriber::Layer`. It formats tracing events as RFC 5424 or RFC 3164 syslog messages and sends them to a syslog daemon through UDP, TCP, or Unix-domain socket transports. It can be composed with the tracker's existing `tracing_subscriber` formatter rather than replacing the tracker logging architecture. + +This means that a future tracker integration could use a maintained implementation for the RFC message grammar and transport instead of implementing those low-level details itself. The crate's default is RFC 5424 over UDP to a local syslog daemon on port 514; therefore, using it would add an optional network or Unix-socket logging sink and a deployment dependency on a syslog daemon. It would not configure file rotation, retention, directory creation, ownership, or permissions, which remain operator concerns. + +The crate is not a drop-in replacement for the tracker's current human-readable stderr output: + +- Its supplied `TrivialTracingFormatter` extracts only the tracing event's `message` field. It does not preserve arbitrary tracker fields such as `client`, `torrent`, and `error` in the emitted message. +- It can emit RFC 5424 structured data for selected tracing metadata, such as source file and line number. It does not supply the tracker-specific field mapping described above, so preserving arbitrary tracing fields would still require a custom formatter or an upstream contribution. +- Its published roadmap describes the `0.2.x` series as preliminary and lists broader tracing-field mapping, span support, asynchronous transports, and additional documentation as future work. A logging call may therefore perform synchronous transport work, and transport failure and backpressure behavior would need explicit evaluation. +- The crate is licensed `GPL-3.0-or-later`. The tracker is `AGPL-3.0-only`; a future dependency proposal must include the repository's normal license-compatibility review before adoption. + +The absence of a specific `MSGID` mapping does not itself prevent valid RFC 5424 output because the RFC permits `-` as the nil value. Similarly, RFC 5424 permits `-` instead of structured data. Consequently, the crate may be sufficient for a narrow future requirement such as sending basic compliant event messages to a local syslog daemon. It is not sufficient for a requirement to preserve the full structured tracker context without further work. + +**Recommendation:** do not add the crate now. If a concrete deployment requires RFC 5424 delivery to a syslog daemon, perform a small, time-boxed compatibility spike first. It should verify tracker MSRV/dependency compatibility, license approval, non-blocking behavior under an unavailable or slow daemon, the selected facility and transport, and whether losing arbitrary tracing fields is acceptable. + +## Requirements if the Issue Is Reopened 1. Is the intended product an RFC 5424 formatter for stdout/stderr, a syslog client transport, or both? -2. Should production deployments delegate file persistence, rotation, ownership, and permissions to systemd/journald, Docker/Podman, or an external syslog daemon rather than the tracker process? -3. Which RFC 5424 facility should the tracker use by default, and should it be configurable? -4. How should tracing levels and the RFC severity values map, especially `trace`, `off`, `Critical`, `Alert`, and `Emergency`? -5. Which stable `APP-NAME`, `PROCID`, and `MSGID` values should the tracker emit? -6. Which tracing fields become RFC structured data, what enterprise ID or namespacing is used for `SD-ID`, and how are malformed/non-ASCII keys and values handled? -7. Does the daemon continue to send normal diagnostics to stderr as required by the CLI output ADR, or does a selected syslog sink replace that stream? -8. Which deployment modes must be supported: native package/service, rootless container, privileged container, and manual executable invocation? +2. Which RFC 5424 facility should the tracker use by default, and should it be configurable? +3. How should tracing levels and the RFC severity values map, especially `trace`, `off`, `Critical`, `Alert`, and `Emergency`? +4. Which stable `APP-NAME`, `PROCID`, and `MSGID` values should the tracker emit? +5. Which tracing fields become RFC structured data, what enterprise ID or namespacing is used for `SD-ID`, and how are malformed/non-ASCII keys and values handled? +6. Does the daemon continue to send normal diagnostics to stderr as required by the CLI output ADR, or does a selected syslog sink replace that stream? + +### Structured-Data Field Mapping + +Requirement 5 concerns the difference between a tracing event and an RFC 5424 record. The tracker can emit arbitrary named tracing fields, for example: + +```rust +tracing::info!(client = client_label, torrent = %hash, "torrent is absent"); +``` + +An RFC 5424 formatter must decide whether those fields are omitted, added only to the free-form message, or translated to `STRUCTURED-DATA`. A possible translation is: + +```text +<134>1 2026-08-26T10:00:00Z tracker.example torrust-tracker 1234 - [torrust@PEN client="qbittorrent" torrent="abc..."] torrent is absent +``` + +This example is illustrative only. `torrust@PEN` would need a valid structured-data identifier: `torrust` is the element name and `PEN` would need to be replaced by Torrust's IANA Private Enterprise Number. The formatter must also define stable parameter names such as `client` and `torrent`. Once deployed, log collectors, dashboards, alerts, and parsers may depend on those names, so changing them becomes a compatibility concern. + +The formatter would additionally need rules for tracing fields that RFC 5424 cannot represent directly. Structured-data names are restricted to printable ASCII and exclude spaces, `=`, `]`, and `"`; parameter values must escape `"`, `\\`, and `]`. Tracing field names or values that are non-ASCII, contain invalid characters, are nested, or are not meaningful operational attributes require a deliberate policy: reject them, omit them, encode them, or retain them only in the free-form message. + +Finally, the mapping must preserve the secrecy ADR. Fields containing credentials, tokens, client-identifying data, or other sensitive values must remain redacted before a formatter serializes them. This is why strict RFC 5424 output is more than changing the timestamp or severity label: it introduces a public schema and serializer for every selected tracing field. + +## Hypothetical Remaining-Effort Estimate + +The following estimate applies only to a custom RFC 5424 formatter that emits records to the existing stderr stream. It leaves persistence, rotation, and permissions to the deployment infrastructure. + +| Work item | Estimated effort | Notes | +| ------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Decide the RFC field contract and tracing-level mapping | 1-2 days | Covers facility, application/process/message identifiers, `trace`/`off`, structured-data namespace, and secret-redaction review | +| Implement an RFC 5424 tracing formatter | 3-5 days | Includes header generation, `PRI`, RFC escaping, timestamp handling, and preserving existing tracing fields | +| Add configuration, migration, and documentation | 1-2 days | Must target the active v2 schema or be coordinated with the v3 configuration migration | +| Unit, integration, and conformance tests | 2-3 days | Covers deterministic formatting, escaping, severity/facility mapping, configuration, and stderr output | +| Review and contingency | 1-2 days | Covers tracing-subscriber extension constraints and compatibility fixes | + +**Total for a custom formatter: 8-14 engineering days.** A separate syslog network transport, TLS support, reconnection/backpressure policy, or multiple destination support is a separate feature and would materially increase the estimate. Application-owned file rotation is explicitly out of scope. + +Using `tracing-rfc-5424` changes the future research path, not the current recommendation. A 1-2 day compatibility spike could establish whether the crate's basic RFC 5424 messages, daemon transport, synchronous behavior, GPL license, and loss of arbitrary tracing fields are acceptable for a specific deployment. If they are, the subsequent integration is likely smaller than a custom formatter. If full tracker field preservation is required, the crate does not eliminate the custom formatter or upstream-contribution work. + +## Recommended Issue Outcome -## Recommended Issue Reshaping +Retain issue #387 as a research issue. Do not create implementation subissues and do not perform a partial crate integration now. Cameron can decide whether to close it as out of current priorities or leave it open as a deferred enhancement after reviewing this analysis. -Retain issue #387 as an umbrella or research issue. Split the implementation work only after the decisions above are recorded: +If a future requirement makes RFC 5424 support worthwhile, the likely work is: -1. Define the logging-output architecture and RFC 5424 configuration contract. -2. Implement and test a standards-compliant RFC 5424 formatter and severity/facility mapping. -3. Add an optional syslog transport or integrate with the selected platform logger. -4. Document deployment-owned file rotation, directory, and permissions; only add application-managed files if a supported deployment requires them. +1. Run the `tracing-rfc-5424` compatibility spike against the concrete deployment requirement. +2. Define the logging-output architecture and RFC 5424 configuration contract only if the spike shows that the crate is insufficient or unsuitable. +3. Adopt and test the crate for the narrow syslog-delivery use case, or implement a standards-compliant formatter and field mapping if full tracker context is required. +4. Keep rotation, directory, ownership, and permissions documented and implemented in Torrust Tracker Deployer or equivalent operator infrastructure. This avoids making the tracker responsible for OS-level policy where the container runtime, systemd/journald, or syslog daemon is the appropriate owner. ## Sources - RFC 5424, sections 6, 6.2, and 6.3: +- `tracing-rfc-5424` v0.2.1 crate metadata and documentation: +- `sp1ff/syslog-tracing` source and roadmap: - Current v3 logging setup: `packages/configuration/src/v3_0_0/logging.rs` - CLI output contract: `docs/adrs/20260519000000_define_global_cli_output_contract.md` - Events principle: `docs/adrs/20260727000000_events_are_objective_facts.md` diff --git a/project-words.txt b/project-words.txt index 61f3db162..6e4167e6c 100644 --- a/project-words.txt +++ b/project-words.txt @@ -79,6 +79,7 @@ Registar Rustls Ryzen SHLVL +SIEM Seedable Shareaza Signedness @@ -133,6 +134,7 @@ autolinks automock autoremove backlinks +backpressure bdecode behaviour behavioural From fe45679bab1822c843c6b180ca9542e254f522bd Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 12:12:56 +0100 Subject: [PATCH 20/22] docs(issues): align RFC 5424 spec metadata --- .../ISSUE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md index b514c3bb7..868ee6c68 100644 --- a/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md +++ b/docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md @@ -2,13 +2,17 @@ doc-type: issue issue-type: enhancement status: open +priority: p2 github-issue: 387 spec-path: docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/ISSUE.md branch: "387-rfc-5424-syslog-logging" related-pr: null last-updated-utc: 2026-08-26 semantic-links: + skill-links: + - create-issue related-artifacts: + - .github/skills/dev/planning/create-issue/SKILL.md - https://github.com/torrust/torrust-tracker/issues/387 - docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/rfc-5424-current-state-analysis.md - docs/issues/open/387-implement-logging-using-rfc-5424-syslog-format/questions.md From 8b01cd70b91d0419b973c8082fe45341a0b78c92 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 12:15:16 +0100 Subject: [PATCH 21/22] chore: update dependencies Updating crates.io index Locking 9 packages to latest Rust 1.88 compatible versions Updating combine v4.6.7 -> v4.6.8 Updating crc32fast v1.5.0 -> v1.5.1 Updating h2 v0.4.18 -> v0.4.19 Updating log v0.4.33 -> v0.4.34 Updating rand v0.8.7 -> v0.8.8 Updating redox_syscall v0.9.2 -> v0.9.3 Updating rustls-webpki v0.103.14 -> v0.103.15 Updating syn v3.0.3 -> v3.0.4 Updating uuid v1.24.1 -> v1.25.0 note: pass `--verbose` to see 10 unchanged dependencies behind latest --- Cargo.lock | 66 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86c825911..229fea5b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,7 +185,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -725,7 +725,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -757,9 +757,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "memchr", @@ -881,9 +881,9 @@ checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" dependencies = [ "cfg-if", ] @@ -1221,7 +1221,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1542,7 +1542,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1637,9 +1637,9 @@ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "h2" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839c0e8a181239723652be9062bb56ca5bf5f64011f73b623f6f4fc59086a228" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -2296,7 +2296,7 @@ dependencies = [ "bitflags 2.13.1", "libc", "plain", - "redox_syscall 0.9.2", + "redox_syscall 0.9.3", ] [[package]] @@ -2333,9 +2333,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lru-slab" @@ -2504,7 +2504,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.7", + "rand 0.8.8", "smallvec", "zeroize", ] @@ -2816,7 +2816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.7", + "rand 0.8.8", ] [[package]] @@ -3155,9 +3155,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -3269,9 +3269,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c93da5bb2c5d4e6c0ef7abeead62c89169a0a4882bfb83ac892f2423aea2fe" +checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5" dependencies = [ "bitflags 2.13.1", ] @@ -3293,7 +3293,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3548,9 +3548,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.14" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "aws-lc-rs", "ring", @@ -3704,7 +3704,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3753,7 +3753,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4085,7 +4085,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.7", + "rand 0.8.8", "rsa", "serde", "sha1 0.10.7", @@ -4123,7 +4123,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand 0.8.7", + "rand 0.8.8", "serde", "serde_json", "sha2 0.10.9", @@ -4230,9 +4230,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -4384,7 +4384,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4485,7 +4485,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -5553,9 +5553,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.24.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -6119,7 +6119,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] From bb6b570c697162d8c64991a8e66ef297928dd5b4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 26 Aug 2026 12:19:02 +0100 Subject: [PATCH 22/22] docs(skills): include update output in PR descriptions --- .../dev/maintenance/update-dependencies/SKILL.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/skills/dev/maintenance/update-dependencies/SKILL.md b/.github/skills/dev/maintenance/update-dependencies/SKILL.md index c5fcb1fea..79d9d1afd 100644 --- a/.github/skills/dev/maintenance/update-dependencies/SKILL.md +++ b/.github/skills/dev/maintenance/update-dependencies/SKILL.md @@ -3,7 +3,7 @@ name: update-dependencies description: Guide for updating project dependencies in the torrust-tracker project. Covers the manual cargo update workflow including branch creation, running checks, committing, and pushing. Distinguishes trivial updates (Cargo.lock only) from breaking-change updates (code rework needed). Use when updating dependencies, running cargo update, or bumping deps. Triggers on "update dependencies", "cargo update", "update deps", or "bump dependencies". metadata: author: torrust - version: "1.0" + version: "1.1" semantic-links: skill-links: - update-github-workflow-actions @@ -63,6 +63,10 @@ cargo update 2>&1 | tee .tmp/cargo-update.txt git add Cargo.lock git commit -S -m "chore: update dependencies" -m "$(cat .tmp/cargo-update.txt)" git push {your-fork-remote} "${TIMESTAMP}-update-dependencies" + +# Open a PR targeting torrust/torrust-tracker:develop. Include the complete +# .tmp/cargo-update.txt output verbatim under a "cargo update output" heading +# in a fenced text block in the PR description. ``` ## Complete Workflow @@ -144,6 +148,11 @@ git push {your-fork-remote} "${TIMESTAMP}-update-dependencies" Target: `torrust/torrust-tracker:develop` Title: `chore: update dependencies` +Include the complete `.tmp/cargo-update.txt` output in the PR description as well as the commit +body. Place it verbatim under a `## cargo update output` heading in a fenced `text` code block. +Do not replace it with a manually abbreviated package list unless the output contains information +that must not be published. + ## Decision Guide | Scenario | Action |