diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 000000000..514539aea --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +.tmp/** \ No newline at end of file diff --git a/.taplo.toml b/.taplo.toml index 0168711e8..6788c2226 100644 --- a/.taplo.toml +++ b/.taplo.toml @@ -2,7 +2,7 @@ # Used by the "Even Better TOML" VS Code extension # Exclude generated and runtime folders from linting -exclude = [ ".coverage/**", "storage/**", "target/**" ] +exclude = [ ".coverage/**", ".tmp/**", "storage/**", "target/**" ] [formatting] # Preserve blank lines that exist diff --git a/.yamllint-ci.yml b/.yamllint-ci.yml index 9380b592a..a695c9306 100644 --- a/.yamllint-ci.yml +++ b/.yamllint-ci.yml @@ -11,6 +11,7 @@ rules: # Ignore generated/runtime directories ignore: | + .tmp/** target/** storage/** .coverage/** diff --git a/cspell.json b/cspell.json index 2500d933e..be5f3d101 100644 --- a/cspell.json +++ b/cspell.json @@ -17,6 +17,7 @@ "toml" ], "ignorePaths": [ + ".tmp/**", "target", "docs/media/*.svg", "contrib/bencode/benches/*.bencode", diff --git a/docs/adrs/20260727180000_shared_services_across_tracker_instances.md b/docs/adrs/20260727180000_shared_services_across_tracker_instances.md index 560a09696..f7468cc8a 100644 --- a/docs/adrs/20260727180000_shared_services_across_tracker_instances.md +++ b/docs/adrs/20260727180000_shared_services_across_tracker_instances.md @@ -2,7 +2,10 @@ semantic-links: related-artifacts: - docs/adrs/index.md + - docs/events-architecture.md + - packages/http-core/src/container.rs - packages/udp-core/src/container.rs + - packages/udp-server/src/container.rs - packages/udp-core/src/services/banning.rs - packages/tracker-core/src/container.rs - packages/configuration/src/v3_0_0/udp_tracker_server.rs @@ -21,8 +24,9 @@ Each listener binds to a different address/port but shares core infrastructure: multiple listeners: they serve the same swarm. - **Ban service** (`BanService` in `UdpTrackerCoreServices`) — all UDP instances share the same IP-ban state. An IP banned on one UDP listener is banned on all. -- **Event buses** — core-layer events (`UdpTrackerCoreServices`) are shared; - server-layer events (`UdpTrackerServerContainer`) are per-instance. +- **Event buses and repositories** — HTTP core, UDP core, and UDP server events + are aggregate application services. The UDP server container is shared by all + UDP listeners. This ADR documents the shared-services design and the rationale for keeping certain services global rather than per-instance. @@ -41,8 +45,21 @@ same type: | 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 | -| UDP server event bus | `UdpTrackerServerContainer::event_bus` | **No** | Per-instance; server events (request accepted, banned, error) are specific to one listener | -| UDP server stats repository | `UdpTrackerServerContainer::stats_repository` | **No** | Per-instance; each listener has its own statistics | +| 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 +metrics configuration. The target design filters events in the metrics listener +by stable runtime listener identity before mutating the shared aggregate +repository. A configured `SocketAddr` is not sufficient identity because two +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). ### Why the ban service is shared diff --git a/docs/events-architecture.md b/docs/events-architecture.md new file mode 100644 index 000000000..bcc017937 --- /dev/null +++ b/docs/events-architecture.md @@ -0,0 +1,120 @@ +--- +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/ +--- + +# 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 can return no sender when its +`SenderStatus` is disabled. Today, the HTTP core, UDP core, and UDP server each +create one event bus and one aggregate statistics repository during application +container initialization. Their senders and listeners are gated by the global +`core.tracker_usage_statistics` setting. + +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. + +The current `SenderStatus` gate applies a metrics setting at the producer. That +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 will make producers emit objective facts regardless of +`tracker_usage_statistics`, then provide 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) +- Bootstrap bug: [#2035][2035] +- Runtime identity prerequisite: [#2036][2036] +- Shared-services decision: [ADR 20260727180000](adrs/20260727180000_shared_services_across_tracker_instances.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 diff --git a/docs/index.md b/docs/index.md index b5ddf6ee8..628835357 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,14 +28,15 @@ source code, see the [crate docs on docs.rs][docs]. Operational and development guides for working with the tracker. -| Document | Description | -| ------------------------------------------ | -------------------------------------------------------------------- | -| [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 | -| [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 | +| Document | Description | +| ------------------------------------------------ | -------------------------------------------------------------------- | +| [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 | +| [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 | ## Architecture Decisions (ADRs) diff --git a/docs/issues/open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md b/docs/issues/open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md index 0ec43fd39..d342fcb1b 100644 --- a/docs/issues/open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md +++ b/docs/issues/open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md @@ -180,16 +180,17 @@ which creates isolated workspaces with separate config and storage directories f **Strategy**: First prove the scaffolding is broken by adding a second test case that would conflict, then fix the scaffolding infrastructure, then expand coverage. -| ID | Status | Task | Notes | -| --- | ------ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| T1 | DONE | Create `tests/AGENTS.md` with guidelines and TODO list | Document what belongs in main-level integration tests vs package tests; include prioritized TODO list of future valuable integration tests | -| T2 | TODO | Add second assertion to existing stats test | Check another global stat field (e.g., `tcp4_scrapes_handled` or `tcp6_announces_handled`) to prove need for parallel test capability | -| T3 | TODO | Create test utilities module | `tests/helpers.rs` with utilities for temp workspace creation (config + storage dirs) and port extraction | -| T4 | TODO | Add utility to create isolated temp workspace | Returns `TempDir` with subdirectories for config and storage; writes TOML config; sets `TORRUST_TRACKER_CONFIG_TOML_PATH` env var | -| T5 | TODO | Add utility to extract bound addresses from `AppContainer` | Query `Registar` or job handles to get actual bound `SocketAddr` for HTTP API, trackers, etc. | -| T6 | TODO | Update existing stats test to use port 0 and temp workspace | Replace `env::set_var` with isolated temp workspace, use port 0, extract actual ports for requests; fixes scaffolding to support parallelism | -| T7 | TODO | Expand global stats test coverage | Add tests for more global stat metrics now that scaffolding supports it (scrape counters, different IP families, etc.) | -| T8 | TODO | Run automatic verification | `cargo test --test stats` must pass with all tests running concurrently | +| ID | Status | Task | Notes | +| --- | ------ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| T1 | DONE | Create `tests/AGENTS.md` with guidelines and TODO list | Document what belongs in main-level integration tests vs package tests; include prioritized TODO list of future valuable integration tests | +| T2 | TODO | Add second assertion to existing stats test | Check another global stat field (e.g., `tcp4_scrapes_handled` or `tcp6_announces_handled`) to prove need for parallel test capability | +| T3 | TODO | Create test utilities module | `tests/helpers.rs` with utilities for temp workspace creation (config + storage dirs) and port extraction | +| T4 | TODO | Add utility to create isolated temp workspace | Returns `TempDir` with subdirectories for config and storage; writes TOML config; sets `TORRUST_TRACKER_CONFIG_TOML_PATH` env var | +| T5 | TODO | Add utility to extract bound addresses from `AppContainer` | Query `Registar` or job handles to get actual bound `SocketAddr` for HTTP API, trackers, etc. | +| T6 | TODO | Update existing stats test to use port 0 and temp workspace | Replace `env::set_var` with isolated temp workspace, use port 0, extract actual ports for requests; fixes scaffolding to support parallelism | +| T7 | TODO | Expand global stats test coverage | Add tests for more global stat metrics now that scaffolding supports it (scrape counters, different IP families, etc.) | +| T8 | TODO | Add shared-event-bus policy integration test | After #2036 and event-metrics normalization, verify enabled and disabled HTTP/UDP listeners share aggregate buses while only enabled traffic changes public metrics. | +| T9 | TODO | Run automatic verification | `cargo test --test stats` must pass with all tests running concurrently | ## Progress Tracking 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 1e45b3bf2..13bcf99cf 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 @@ -14,10 +14,16 @@ semantic-links: related-artifacts: - src/container.rs - src/app.rs + - archived-attempt.md - docs/issues/open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md + - docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md + - docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md + - docs/events-architecture.md - evidence.md related-issues: - 1419 + - 2036 + - 2039 --- # Issue #2035 - Fix Duplicate Port-Zero Tracker Instance Bootstrap @@ -60,16 +66,40 @@ The local reproduction is recorded in [evidence.md](evidence.md). - Public endpoint, proxy, or DNS configuration. - User-supplied persistent service IDs in configuration. +## Archived Attempt / Revised Delivery Plan + +The old implementation attempt lives on reference branch +`archive/2035-bootstrap-identity-attempt`. It must not merge. Its evidence and +the pause decision are recorded in [archived-attempt.md](archived-attempt.md). + +The attempt showed that bootstrap identity alone cannot make per-listener UDP +metrics policy correct: the UDP server has one application-wide event bus and +repository, while producer-side metrics suppression can hide facts required by +the independent banning listener. + +Issue [#2035](https://github.com/torrust/torrust-tracker/issues/2035) must be +reimplemented after: + +1. #2036 defines canonical runtime service/configuration-instance identity. +2. [#2041](../2041-migrate-runtime-service-registry-metadata/ISSUE.md) + carries that identity through started-service registration metadata. +3. #2039 makes event publication independent of metrics policy and filters + metrics in listeners by that canonical identity. + +The new implementation will preserve bootstrap identity without creating a +second runtime identity or metrics-policy mechanism. + ## Implementation Plan -| ID | Status | Task | Notes / Expected Output | -| --- | ------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| T1 | DONE | Add failing HTTP bootstrap regression | Ignored stats-contract regression records the current `2 != 1` failure. | -| T2 | TODO | Add failing UDP bootstrap regression | Same identity preservation for UDP instances. | -| T3 | TODO | Replace address-keyed container lookup | Startup aligns each configuration entry with its own initialized container. | -| T4 | TODO | Remove obsolete address lookup API | No bootstrap path relies on configured `SocketAddr` uniqueness. | -| T5 | TODO | Correlate bootstrap lifecycle logs | Every HTTP and UDP lifecycle event that emits a configured or final binding includes `instance_index`. | -| T6 | TODO | Run focused and workspace validation | Record before/after evidence in this issue folder. | +| ID | Status | Task | Notes / Expected Output | +| --- | ------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | +| T1 | BLOCKED | Land [#2036](../2036-add-runtime-service-registry-metadata/ISSUE.md) canonical identity | Bootstrap identity must align with the canonical runtime identity contract. | +| T2 | BLOCKED | Land registry metadata migration | Register started services with canonical identity before #2039 consumes runtime identity. | +| T3 | BLOCKED | Land [#2039](../2039-normalize-per-instance-event-metrics-policy/ISSUE.md) event-metrics normalization | Objective-event publication and listener-side metrics filtering must be available. | +| T4 | TODO | Replace address-keyed container lookup | Use an order-preserving representation or canonical identity, not configured `SocketAddr`. | +| T5 | TODO | Start matching containers | Pass each configuration entry's matching container into HTTP and UDP startup. | +| T6 | TODO | Correlate lifecycle logs | Include canonical identity with configured and final binding logs. | +| T7 | TODO | Add regressions and validate | Cover duplicate port-zero HTTP/UDP listeners and record focused verification. | ## Progress Tracking @@ -77,17 +107,19 @@ The local reproduction is recorded in [evidence.md](evidence.md). - [x] Specification drafted and approved by user/maintainer - [x] GitHub issue created: #2035 -- [ ] Implementation completed -- [ ] Automatic verification completed (`linter all`, relevant tests) -- [ ] Manual verification scenarios executed and recorded +- [ ] Prerequisite #2036 completed +- [ ] Event-metrics normalization completed +- [ ] New implementation completed +- [ ] Automatic and manual verification completed - [ ] Acceptance criteria reviewed after implementation -- [ ] Issue closed and specification moved to `docs/issues/closed/` ### Progress Log - 2026-07-28 14:51 UTC - agent - User-approved specification promoted to GitHub issue #2035; the ignored HTTP stats-contract regression and its current `2 != 1` failure are recorded in [evidence.md](evidence.md). +- 2026-07-29 00:00 UTC - agent - Archived the prior implementation attempt and deferred + implementation until #2036 and event-metrics normalization are complete. ## Acceptance Criteria @@ -103,8 +135,8 @@ The local reproduction is recorded in [evidence.md](evidence.md). ### Automatic Checks -- Focused regression tests for `AppContainer` and startup jobs. -- `cargo test --test stats --test scaffold` after the runtime-registry prerequisite lands. +- Focused regression tests for `AppContainer` and startup jobs after prerequisites land. +- `cargo test --test stats --test scaffold`. - `linter all`. ### Manual Verification Scenarios @@ -119,3 +151,7 @@ The local reproduction is recorded in [evidence.md](evidence.md). - Issue #1419: [main-application integration tests](../../open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md) - [Runtime registry investigation](../../open/1419-allow-multiple-integration-tests-at-main-app-level/investigation-registar-and-health-check.md) - Feature #2036: [add runtime service registry metadata](../2036-add-runtime-service-registry-metadata/ISSUE.md) +- Bug #2039: [normalize per-instance event metrics policy](../2039-normalize-per-instance-event-metrics-policy/ISSUE.md) +- Issue #2041: [migrate runtime service registry metadata](../2041-migrate-runtime-service-registry-metadata/ISSUE.md) +- [Archived implementation attempt](archived-attempt.md) +- [Events architecture](../../../events-architecture.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 new file mode 100644 index 000000000..5450b3cda --- /dev/null +++ b/docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/archived-attempt.md @@ -0,0 +1,37 @@ +# Archived Implementation Attempt + +## Status + +The former implementation attempt is preserved on +`archive/2035-bootstrap-identity-attempt`. It is reference material only and +must not be merged or blindly cherry-picked. + +## Evidence + +The attempt established the bootstrap collision recorded in +[evidence.md](evidence.md): address-keyed containers overwrite one of two +configuration blocks that use the same `0.0.0.0:0` binding. It also established +that retaining bootstrap identity alone does not implement the intended +per-listener UDP metrics policy. UDP server events currently use a single +application-wide container, event bus, and aggregate repository. + +Manual verification on the attempt showed HTTP behavior consistent with its +per-listener producer gate, while UDP server metrics still included traffic from +a metrics-disabled listener. The attempt further exposed that suppressing event +production for metrics can hide cookie-error facts from UDP banning. + +## Pause Decision + +The work was paused because bootstrap identity, runtime identity, and event +metrics policy must be delivered in a coherent order: + +1. Land #2036 canonical runtime service and configuration-instance identity. +2. Land event-metrics normalization: always emit objective events, filter + metrics in listeners by stable identity, and keep banning independent. +3. Reimplement #2035 from scratch on those foundations and verify duplicate + port-zero listeners. + +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). diff --git a/docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md b/docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md index d5f2fec3d..aff3b2ba8 100644 --- a/docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md +++ b/docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md @@ -7,7 +7,7 @@ github-issue: 2036 spec-path: docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md branch: 2036-add-runtime-service-registry-metadata related-pr: null -last-updated-utc: 2026-07-28 12:30 +last-updated-utc: 2026-07-29 14:45 semantic-links: skill-links: - write-unit-test @@ -15,58 +15,59 @@ semantic-links: - docs/adrs/20260728115400_define_registar_as_runtime_service_registry.md - docs/issues/open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md - docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md + - docs/issues/open/2041-migrate-runtime-service-registry-metadata/ISSUE.md related-issues: - 1419 --- -# Issue #2036 - Add Runtime Service Registry Metadata +# Issue #2036 - Define Canonical Runtime Service Identity ## Goal -Evolve `Registar` into a side-effect-free internal source of final local listener bindings, tracker -service roles, and configuration-instance correlation metadata. +Define tracker-owned canonical service-role and configuration-instance identity +types that can be used consistently by bootstrap, runtime registration, and +event-metrics consumers. ## Background -`ServiceRegistration` in the standalone `torrust-server-lib` crate stores a final -`ServiceBinding` and a health-check function. The health-check function constructs the service role -only when it executes network I/O. Consumers cannot discover a running HTTP tracker versus REST -API through the registry without relying on bind-IP conventions, `HashMap` iteration order, logs, -or an unnecessary health check. +The earlier #2036 plan combined two deliveries: defining the canonical identity +model, then migrating `torrust-server-lib::Registar` and tracker registrations +to carry that model. The registry migration cannot be completed until #2035 +preserves identity through real bootstrap. Keeping both deliveries in one issue +would leave its main work blocked after a small independently mergeable type +foundation. -The bootstrap prerequisite must land first. A final listener binding identifies a running listener, -but repeated `0.0.0.0:0` configuration blocks require bootstrap to preserve configuration-instance -identity before that identity can be reported to the registry. +This issue now owns the type foundation only. The registry migration is planned +in [#2041](../2041-migrate-runtime-service-registry-metadata/ISSUE.md), which depends on this issue and #2035 bootstrap propagation. ## Scope ### In Scope -- Extend the standalone `torrust-server-lib` registration record and read-only query API. - Define tracker-owned canonical service-role values without coupling the generic library to them. -- Carry bootstrap-provided configuration-instance identity with each registration. -- Make registration visibility a deterministic application-readiness boundary. -- Build health-check reports from registration metadata and health-check execution results. -- Release the standalone crate and upgrade the tracker dependency. +- Define a canonical configuration-instance identity type with clear scope and + equality semantics. +- Document ownership boundaries and ensure the types can be consumed by #2035, + registry migration, and #2039 without a competing identity model. +- Add focused unit tests and public API documentation for the new types. ### Out of Scope -- Fixing duplicate port-zero bootstrap storage; owned by the prerequisite issue. +- Fixing duplicate port-zero bootstrap storage; owned by #2035. +- Extending `torrust-server-lib` registration records or query APIs; owned by + #2041. +- Releasing or upgrading `torrust-server-lib`; owned by #2041. - Public URLs, proxies, domain names, and deployment topology. - Dynamic service restart, deregistration, or configuration reload. ## Implementation Plan -| ID | Status | Task | Notes / Expected Output | -| --- | ------- | ------------------------------------------------------------- | --------------------------------------------------------------------------- | -| T1 | BLOCKED | Merge bootstrap identity prerequisite | Registration needs a stable configuration-instance identity to carry. | -| T2 | TODO | Define tracker-owned service role and instance identity types | Keep tracker semantics out of generic network and server crates. | -| T3 | TODO | Extend `ServiceRegistration` in `torrust-server-lib` | Store immutable metadata and make health-check behavior optional. | -| T4 | TODO | Add side-effect-free registry query API | Do not expose `HashMap` ordering as a contract. | -| T5 | TODO | Establish registration readiness | Acknowledge insertion or provide an equivalent readiness boundary. | -| T6 | TODO | Release the standalone crate | Publish a compatible version before tracker migration. | -| T7 | TODO | Migrate tracker registrations and health reporting | Preserve the health API JSON contract. | -| T8 | TODO | Replace #1419 bind-IP helper | Query runtime metadata by role and instance identity without a fixed delay. | +| ID | Status | Task | Notes / Expected Output | +| --- | ------ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| T1 | TODO | Define tracker-owned service role type | Keep tracker semantics out of generic network and server crates. | +| T2 | TODO | Define canonical configuration-instance identity type | Specify scope, equality, construction, documentation, and unit tests. | +| T3 | TODO | Verify consumer boundaries | Confirm #2035 bootstrap, registry migration, and #2039 can consume the same types without creating competing identifiers. | +| T4 | TODO | Run focused validation | Run `linter all` and relevant type/module tests. | ## Progress Tracking @@ -74,46 +75,39 @@ identity before that identity can be reported to the registry. - [x] Specification drafted and approved by user/maintainer - [x] GitHub issue created: #2036 -- [ ] Prerequisite #2035 completed - [ ] Implementation completed -- [ ] Automatic verification completed (`linter all`, relevant tests in both repositories) -- [ ] Manual verification scenarios executed and recorded +- [ ] Automatic verification completed (`linter all`, relevant tests) - [ ] Acceptance criteria reviewed after implementation - [ ] Issue closed and specification moved to `docs/issues/closed/` ### Progress Log -- 2026-07-28 14:51 UTC - agent - User-approved specification promoted to GitHub feature #2036; - implementation remains blocked on the configuration-instance identity fix in #2035. +- 2026-07-28 14:51 UTC - agent - User-approved specification promoted to GitHub feature #2036. +- 2026-07-29 14:45 UTC - agent - Split registry migration into a dedicated draft issue. #2036 now owns only canonical role and configuration-instance identity types, which can be implemented before #2035 bootstrap propagation. ## Acceptance Criteria -- [ ] AC1: Internal consumers discover final local bindings without running health checks. -- [ ] AC2: Registrations expose tracker role and configuration-instance correlation metadata. -- [ ] AC3: The health-check response preserves `service_binding`, `binding`, and `service_type`. -- [ ] AC4: The generic server library remains independent of tracker-specific role variants. -- [ ] AC5: #1419 removes its bind-IP endpoint classification and fixed registration delay. -- [ ] AC6: Focused tests pass in both repositories and `linter all` exits with code `0`. +- [ ] AC1: Tracker-owned canonical service-role values are defined without coupling generic server/network libraries to tracker variants. +- [ ] AC2: Canonical configuration-instance identity is typed, documented, and independent of configured socket addresses. +- [ ] AC3: The types can be used by #2035, the registry migration follow-up, and #2039 without conversion to competing identity types. +- [ ] AC4: Focused tests and `linter all` exit with code `0`. ## Verification Plan ### Automatic Checks -- `torrust-server-lib` unit tests for registration metadata and query behavior. -- Health-check API contract tests. -- `cargo test --test stats --test scaffold` in the tracker repository. -- `linter all` in both repositories. +- Focused unit tests for the role and identity types. +- Compile checks at intended consumer boundaries. +- `linter all`. ### Manual Verification Scenarios -| ID | Scenario | Expected Result | Status | Evidence | -| --- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | ------ | -------- | -| M1 | Start HTTP, HTTPS, REST API, and UDP services with port zero. | Registry distinguishes local protocol, role, and final binding. | TODO | | -| M2 | Start repeated HTTP tracker configuration instances after bootstrap fix. | Registry correlates each final listener to the intended configuration instance. | TODO | | +| ID | Scenario | Expected Result | Status | Evidence | +| --- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------ | -------- | +| M1 | Construct identity values for repeated same-protocol configuration entries. | Equal configured addresses remain distinguishable by canonical instance identity. | TODO | | ## References - [ADR 20260728115400](../../../adrs/20260728115400_define_registar_as_runtime_service_registry.md) -- Prerequisite #2035: [fix duplicate port-zero tracker instance bootstrap](../2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md) -- Issue #1419: [main-application integration tests](../../open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md) -- [Runtime registry investigation](../../open/1419-allow-multiple-integration-tests-at-main-app-level/investigation-registar-and-health-check.md) +- Future consumer #2035: [fix duplicate port-zero tracker instance bootstrap](../2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md) +- Follow-up #2041: [migrate runtime service registry metadata](../2041-migrate-runtime-service-registry-metadata/ISSUE.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 new file mode 100644 index 000000000..7aad519e8 --- /dev/null +++ b/docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md @@ -0,0 +1,222 @@ +--- +doc-type: issue +issue-type: bug +status: open +priority: p1 +github-issue: 2039 +spec-path: docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md +branch: "2039-normalize-per-instance-event-metrics-policy" +related-pr: null +last-updated-utc: 2026-07-29 07:10 +semantic-links: + skill-links: + - create-issue + - write-unit-test + related-artifacts: + - .github/skills/dev/planning/create-issue/SKILL.md + - docs/events-architecture.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 + - docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md + - evidence.md + - packages/events/src/bus.rs + - packages/http-core/src/container.rs + - packages/udp-core/src/container.rs + - packages/udp-server/src/container.rs +--- + + + +# Issue #2039 - Normalize Per-Instance Event Metrics Policy + +## Goal + +Make `tracker_usage_statistics` control metrics processing for an individual +public HTTP or UDP listener, without suppressing objective events or UDP ban +enforcement. + +## Background + +[#1263][1263] and [#1401][1401] establish the intended operator model: +aggregate metrics remain available, while each public listener can opt in or +out through `tracker_usage_statistics`. + +### Concrete UDP Failure Example + +Consider two public UDP listeners: + +```toml +[[udp_trackers]] +bind_address = "0.0.0.0:0" +tracker_usage_statistics = false + +[[udp_trackers]] +bind_address = "0.0.0.0:0" +tracker_usage_statistics = true +``` + +Both listeners correctly serve connect and announce requests. However, after +one announce to each listener, the REST API's aggregate +`udp4_announces_handled` counter is currently `2`, not `1`. + +The REST API reads this counter from the UDP **server** metrics repository. Its +metrics listener receives `UdpRequestAccepted` events from one application-wide +UDP server event bus, with no per-listener metrics policy. The configuration +option therefore does not suppress server-layer metrics for the disabled +listener. + +The old implementation tried to disable metrics by suppressing event producers: +an `EventBus` returns no sender when statistics are disabled. This was +reasonable when events existed only to generate metrics. It is no longer valid: +UDP server events are generic objective facts and a separate banning listener +also consumes cookie-error events from that stream. Suppressing the stream to +avoid metrics would also prevent current or future non-metrics consumers from +observing those facts. + +There is deliberately one aggregate metrics repository per layer, rather than +one repository per public listener. The repository does not currently filter +events by configuration policy; the listener increments counters from every +event it receives. Therefore, preserving aggregate repositories while allowing +per-listener metrics requires listener-side filtering before repository mutation. + +This issue replaces producer-side metrics suppression with always-emitted facts +and listener-side metrics policy. The UDP server is the failure that exposes the +problem, but HTTP core and UDP core must follow the same normalized rule. + +The prerequisites are [#2036][2036], which defines canonical runtime service +and configuration-instance identity, and the registry metadata migration that +exposes that identity for started services. A configured address cannot identify +a listener because repeated `0.0.0.0:0` blocks are valid. This issue must use +the canonical identity rather than create a competing identity. + +## Scope + +### In Scope + +- Always emit objective HTTP core, UDP core, and UDP server events. +- Carry #2036 canonical runtime identity on metric-relevant events. +- Filter metrics in HTTP core, UDP core, and UDP server listeners before their + shared aggregate repositories are updated. +- Keep UDP banning independent of metrics policy and subscribed to all relevant + cookie-error events. +- Add focused and application-level regressions for enabled and disabled + listeners, including duplicate port-zero configuration blocks. +- Record progressive manual baseline and post-change evidence for every + code-changing task. + +### Out of Scope + +- Per-listener repositories or a public per-listener metrics API. +- A persistent user-supplied listener ID. +- Changing shared ban-service semantics. +- Replacing the runtime registry work owned by #2036. +- Migrating registry metadata; owned by the dedicated follow-up issue. + +## Design Direction + +The application retains one aggregate repository per event layer. Producers +always publish facts with canonical listener identity. A metrics listener uses +that identity to find the listener's immutable metrics policy and ignores a +disabled listener before repository mutation. The UDP banning listener receives +the same security events regardless of that policy. + +## Implementation Plan + +Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. + +| ID | Status | Task | Notes / Expected Output | +| --- | ------- | -------------------------------- | ------------------------------------------------------------------------------------------------ | +| T1 | TODO | Inventory event gates | Map every `SenderStatus`, optional sender, listener, and UDP ban consumer. | +| T2 | BLOCKED | Consume #2036 canonical identity | Propagate stable runtime configuration-instance identity; do not use configured addresses. | +| T3 | TODO | Always emit HTTP core facts | Remove metrics-driven producer suppression while preserving event semantics. | +| T4 | TODO | Always emit UDP core facts | Remove metrics-driven producer suppression while preserving event semantics. | +| T5 | TODO | Always emit UDP server facts | Decouple the shared UDP server producer from the global metrics gate. | +| T6 | TODO | Filter metrics in listeners | Apply immutable identity-to-policy filtering before aggregate repository updates. | +| T7 | TODO | Preserve banning independence | Verify cookie-error events reach the shared ban service for every listener policy. | +| T8 | TODO | Update REST metrics integration | Preserve aggregate API counters and UDP operational metrics from filtered repositories. | +| T9 | TODO | Add focused tests | Cover producer independence, listener filtering, and ban-listener behavior. | +| T10 | TODO | Add application tests | Verify one enabled and one disabled HTTP/UDP listener, including duplicate port-zero bindings. | +| T11 | TODO | Validate and document | Run focused tests, `linter all`, and the manual protocol; update evidence and architecture docs. | + +## Progressive Manual Verification Protocol + +Every code-changing task, T2 through T10, must complete this protocol before +the next task begins: + +1. Select the smallest externally observable probe for the task. +2. Run it against the pre-change implementation and record configuration, + commands, endpoints, and output in [evidence.md](evidence.md). +3. Implement the smallest change and run focused automated tests. +4. Repeat the unchanged probe and record the post-change output in + [evidence.md](evidence.md). +5. Compare the two records. Explain every intentional difference and add a + regression before advancing; stop to diagnose every unexpected difference. + +Each applicable probe must verify both policies: a metrics-disabled listener +does not update aggregate metrics, and its UDP cookie errors still reach the +shared banning listener. + +## Progress Tracking + +### Workflow Checkpoints + +- [x] Spec drafted in `docs/issues/drafts/` +- [x] Spec reviewed and approved by user/maintainer +- [x] GitHub issue created: #2039 +- [ ] Spec-only PR merged into `develop` before implementation +- [ ] Implementation completed +- [ ] Automatic verification completed (`linter all`, relevant tests, and any pre-push checks) +- [ ] Manual verification scenarios executed and recorded (status + evidence) +- [ ] Acceptance criteria reviewed after implementation and updated with evidence +- [ ] Reviewer validated acceptance criteria and updated checkboxes +- [ ] 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-07-28 20:30 UTC - agent - Drafted from the #2035 manual verification finding and #1263/#1401 historical intent. +- 2026-07-29 00:00 UTC - agent - Converted to folder-style specification and added the progressive manual evidence protocol. +- 2026-07-29 07:10 UTC - agent - User approved the specification; created GitHub issue #2039 and moved this specification to `docs/issues/open/`. + +## Acceptance Criteria + +- [ ] AC1: Metrics-disabled HTTP listeners emit facts but do not update aggregate HTTP metrics. +- [ ] AC2: Metrics-disabled UDP listeners emit core and server facts but do not update aggregate UDP metrics. +- [ ] AC3: Metrics-disabled UDP listeners still contribute relevant cookie-error facts to shared banning. +- [ ] AC4: Metrics-enabled listeners update the existing shared aggregate repositories. +- [ ] AC5: Metrics filtering uses #2036 canonical identity and works for repeated `0.0.0.0:0` blocks. +- [ ] AC6: The REST API retains aggregate HTTP/UDP and UDP operational metrics. +- [ ] AC7: Every code-changing task has baseline and post-change evidence. +- [ ] AC8: Relevant tests and `linter all` pass. + +## Verification Plan + +### Automatic Checks + +- Focused tests for HTTP core, UDP core, UDP server metrics, and UDP banning listeners. +- Application-level enabled/disabled listener tests, including duplicate port-zero configuration. +- `cargo test --test stats -- --test-threads=1` until #1419 resolves test-process isolation. +- `linter all`. + +### Manual Verification Scenarios + +| ID | Scenario | Expected Result | Status | Evidence | +| --- | ---------------------------- | -------------------------------------------------------------------------------- | ------ | -------- | +| M1 | HTTP policy filtering | One enabled and one disabled listener produce aggregate announce count `1`. | TODO | | +| M2 | UDP policy filtering | One enabled and one disabled listener produce aggregate UDP announce count `1`. | TODO | | +| M3 | UDP banning independence | Invalid cookies through a disabled listener still update shared ban enforcement. | TODO | | +| M4 | Duplicate port-zero identity | Policy follows runtime identity rather than configured address. | TODO | | + +## References + +- [Events architecture](../../../events-architecture.md) +- [#1263][1263] +- [#1401][1401] +- [#2035][2035] +- [#2036][2036] + +[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/issues/open/2039-normalize-per-instance-event-metrics-policy/evidence.md b/docs/issues/open/2039-normalize-per-instance-event-metrics-policy/evidence.md new file mode 100644 index 000000000..3ae92caf2 --- /dev/null +++ b/docs/issues/open/2039-normalize-per-instance-event-metrics-policy/evidence.md @@ -0,0 +1,46 @@ +# Event Metrics Normalization Evidence + +## Purpose + +This file records the progressive manual baseline and post-change probes +required by the draft specification. Each code-changing task must have one +entry before its change and one entry after it. + +## Entry Format + +| Field | Record | +| --- | --- | +| Task | Implementation task identifier and title | +| Phase | `baseline` or `post-change` | +| Configuration | Complete isolated tracker configuration or its stable path | +| Endpoints | Final listener bindings used by the probe | +| Commands | Exact commands or client interactions | +| Observed output | Relevant counters, responses, and ban behavior | +| Expected delta | Intended difference from baseline, if any | +| Automated coverage | Focused tests run for the task | +| Result | `DONE`, `FAILED`, or `BLOCKED`, with diagnosis | + +## Task Evidence Matrix + +| Task | Baseline | Post-change | Result | +| --- | --- | --- | --- | +| T2 | TODO | TODO | BLOCKED on #2036 | +| T3 | TODO | TODO | TODO | +| T4 | TODO | TODO | TODO | +| T5 | TODO | TODO | TODO | +| T6 | TODO | TODO | TODO | +| T7 | TODO | TODO | TODO | +| T8 | TODO | TODO | TODO | +| T9 | TODO | TODO | TODO | +| T10 | TODO | TODO | TODO | + +## Required Probe Outcomes + +Every applicable baseline and post-change record must state whether: + +- traffic from an enabled listener changes aggregate metrics; +- traffic from a disabled listener changes aggregate metrics; and +- UDP cookie errors from a disabled listener reach shared ban enforcement. + +The post-change record must also state how the probe identifies repeated +port-zero listeners without relying on their configured socket address. diff --git a/docs/issues/open/2041-migrate-runtime-service-registry-metadata/ISSUE.md b/docs/issues/open/2041-migrate-runtime-service-registry-metadata/ISSUE.md new file mode 100644 index 000000000..b040d3170 --- /dev/null +++ b/docs/issues/open/2041-migrate-runtime-service-registry-metadata/ISSUE.md @@ -0,0 +1,192 @@ +--- +doc-type: issue +issue-type: enhancement +status: open +priority: p1 +github-issue: 2041 +spec-path: docs/issues/open/2041-migrate-runtime-service-registry-metadata/ISSUE.md +branch: "2041-migrate-runtime-service-registry-metadata" +related-pr: null +last-updated-utc: 2026-07-29 15:10 +semantic-links: + skill-links: + - create-issue + - write-unit-test + related-artifacts: + - .github/skills/dev/planning/create-issue/SKILL.md + - docs/adrs/20260728115400_define_registar_as_runtime_service_registry.md + - docs/issues/open/2035-fix-duplicate-port-zero-tracker-instance-bootstrap/ISSUE.md + - docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md + - docs/issues/open/2039-normalize-per-instance-event-metrics-policy/ISSUE.md + - docs/issues/open/1419-allow-multiple-integration-tests-at-main-app-level/ISSUE.md + - src/container.rs + related-issues: + - 1419 + - 2035 + - 2036 + - 2039 +--- + + + +# Issue #2041 - Migrate Runtime Service Registry Metadata + +## Goal + +Migrate `Registar` registrations to carry canonical tracker service role, +configuration-instance identity, and final listener binding metadata. Make this +metadata queryable without running a health check or depending on bind-IP +conventions. + +## Background + +Issue #2036 originally combined two independent deliveries: + +1. defining tracker-owned canonical service role and configuration-instance + identity types; and +2. changing the standalone `torrust-server-lib` registration API, releasing it, + and migrating the tracker to that API. + +The second delivery cannot be completed until #2035 propagates canonical +identity through actual bootstrap. Splitting it into this issue gives both +branches a complete, independently testable scope: + +```text +#2036 canonical identity types + ↓ +#2035 bootstrap propagation + ↓ +this issue: registry metadata migration +``` + +The registry migration is required before #2039 can use canonical runtime +identity for event-metrics policy filtering. It also replaces #1419's temporary +bind-IP classification and fixed registration delay. + +## Scope + +### In Scope + +- Extend `torrust-server-lib::ServiceRegistration` with immutable generic + metadata and make health-check behavior optional. +- Publish a compatible `torrust-server-lib` release and upgrade the tracker + dependency. +- Carry tracker-owned role and #2036 canonical configuration-instance identity + into each HTTP, HTTPS, UDP, REST API, and health-check registration. +- Provide side-effect-free, deterministic registry query APIs without exposing + `HashMap` iteration as a contract. +- Establish registration visibility as an application-readiness boundary. +- Build health-check reports from metadata plus health-check execution results, + preserving the existing JSON contract. +- Replace #1419 test helpers' bind-IP classification and fixed startup delay + with role/identity-based registry discovery. +- Add progressive automatic and manual verification evidence for each + code-changing task. + +### Out of Scope + +- Defining the canonical tracker identity types; owned by #2036. +- Preserving bootstrap identity for duplicate port-zero listeners; owned by + #2035. +- Event-metrics listener filtering; owned by #2039. +- Dynamic restart, deregistration, or configuration reload. +- Public URLs, proxy/DNS topology, or a public registry API. + +## Prerequisites + +- #2036: canonical tracker service role and configuration-instance identity + types are merged. +- #2035 bootstrap phase: every configured HTTP/UDP listener preserves and + propagates its canonical configuration-instance identity during startup. + +## Implementation Plan + +Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. + +| ID | Status | Task | Notes / Expected Output | +| --- | ------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| T1 | BLOCKED | Confirm prerequisites | Verify #2036 identity types and #2035 bootstrap propagation are merged and have evidence. | +| T2 | TODO | Define generic registration metadata boundary | Design metadata representation in `torrust-server-lib` that remains independent of tracker-specific role variants. | +| T3 | TODO | Extend registration and query API | Store final binding, opaque metadata, and optional health behavior; add deterministic query API. | +| T4 | TODO | Establish readiness semantics | Acknowledge insertion or provide an equivalent boundary so consumers can reliably discover started services. | +| T5 | TODO | Release and upgrade server library | Publish the compatible standalone crate version and update tracker dependency. | +| T6 | TODO | Migrate tracker registrations | Register canonical role and instance identity for every local service. | +| T7 | TODO | Migrate health reporting | Build reports from registration metadata and execution results while preserving response JSON. | +| T8 | TODO | Migrate #1419 discovery helpers | Replace bind-IP endpoint classification and fixed registration delay with registry queries. | +| T9 | TODO | Add focused tests | Cover metadata, query ordering/selection, readiness, health compatibility, and registration identity. | +| T10 | TODO | Validate and record evidence | Run both-repository checks and manual port-zero scenarios; update evidence and acceptance review. | + +## Progressive Verification Protocol + +For every code-changing task (T2-T9): + +1. Choose the smallest observable behavior affected by the task. +2. Record a baseline configuration, command, endpoint/query, and output in an + issue-local `evidence.md` before changing code. +3. Implement the smallest change and run focused tests. +4. Repeat the same manual probe and record the post-change result. +5. Stop on unexpected changes; document expected deltas and add regressions. + +## Progress Tracking + +### Workflow Checkpoints + +- [x] Spec drafted in `docs/issues/drafts/` +- [x] Spec reviewed and approved by user/maintainer +- [x] GitHub issue created: #2041 +- [ ] Spec-only PR merged into `develop` before implementation +- [ ] Prerequisites merged +- [ ] Implementation completed +- [ ] Automatic verification completed (`linter all`, relevant tests in both repositories) +- [ ] Manual verification scenarios executed and recorded +- [ ] Acceptance criteria reviewed after implementation +- [ ] Issue closed and spec moved to `docs/issues/closed/` + +### Progress Log + +- 2026-07-29 14:45 UTC - agent - Drafted by splitting the registry migration from #2036, which now owns canonical identity types only. Awaiting user review. +- 2026-07-29 15:10 UTC - agent - User approved the specification; created GitHub issue #2041 and moved this specification to `docs/issues/open/`. + +## Acceptance Criteria + +- [ ] AC1: Registrations expose final binding and opaque metadata without + running network health checks. +- [ ] AC2: Tracker registrations carry canonical role and configuration-instance + identity for each started local service. +- [ ] AC3: Registry queries are deterministic and do not expose map ordering. +- [ ] AC4: Registration visibility provides a testable application-readiness + boundary. +- [ ] AC5: Health-check JSON preserves `service_binding`, `binding`, and + `service_type` compatibility. +- [ ] AC6: #1419 helpers discover endpoints by role/identity without a fixed + startup delay or bind-IP convention. +- [ ] AC7: Port-zero and repeated configuration blocks retain correct identity. +- [ ] AC8: Both repository validation suites pass. +- [ ] AC9: Manual verification evidence is recorded for every code-changing + task. + +## Verification Plan + +### Automatic Checks + +- `torrust-server-lib` unit tests for metadata, query, and readiness behavior. +- Tracker registry/health-check tests. +- `cargo test --test stats --test scaffold` after #1419 helper migration. +- `linter all` in both repositories. + +### Manual Verification Scenarios + +| ID | Scenario | Expected Result | Status | Evidence | +| --- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------ | -------- | +| M1 | Start HTTP, HTTPS, REST API, health API, and UDP services with port zero. | Registry queries distinguish canonical role, instance identity, and final binding. | TODO | | +| M2 | Start repeated HTTP and UDP `0.0.0.0:0` configuration blocks. | Each final listener is correlated with the intended configuration instance. | TODO | | +| M3 | Run health checks after registry migration. | Health response preserves existing JSON fields and values. | TODO | | + +## References + +- #2036: canonical identity type foundation +- #2035: bootstrap identity propagation prerequisite +- #2039: event-metrics normalization consumer +- #1419: main application test helper migration +- `docs/adrs/20260728115400_define_registar_as_runtime_service_registry.md` +- `docs/issues/open/2036-add-runtime-service-registry-metadata/ISSUE.md` diff --git a/docs/issues/open/2041-migrate-runtime-service-registry-metadata/evidence.md b/docs/issues/open/2041-migrate-runtime-service-registry-metadata/evidence.md new file mode 100644 index 000000000..c5769b528 --- /dev/null +++ b/docs/issues/open/2041-migrate-runtime-service-registry-metadata/evidence.md @@ -0,0 +1,37 @@ +# Progressive Verification Evidence + +Record baseline and post-change manual verification for each code-changing task +in the registry metadata migration. + +## Task Evidence + +| Task | Baseline Status | Post-change Status | Evidence | +| --- | --- | --- | --- | +| T2 | TODO | TODO | Generic registration metadata boundary. | +| T3 | TODO | TODO | Registration/query behavior. | +| T4 | TODO | TODO | Registration readiness behavior. | +| T5 | TODO | TODO | Released library integration. | +| T6 | TODO | TODO | Tracker service registrations. | +| T7 | TODO | TODO | Health-report compatibility. | +| T8 | TODO | TODO | #1419 endpoint discovery helpers. | +| T9 | TODO | TODO | Focused test coverage. | + +## Scenario Record Template + +```markdown +### T{N} - {Task title} + +#### Baseline + +- Configuration: +- Command/query: +- Observed result: + +#### Post-change + +- Commit or revision: +- Command/query: +- Observed result: +- Comparison: +- Result: `DONE` / `FAILED` +```