Repository navigation
feat(configuration): [#2668] configure the shutdown policy and document the deployment contract - #2678
Merged
josecelano merged 38 commits intoOct 28, 2026
Conversation
Copilot stopped reviewing on behalf of
josecelano due to an error
October 28, 2026 09:54
josecelano
force-pushed
the
2668-configure-shutdown-policy
branch
from
October 28, 2026 10:00
af3dffd to
669411a
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The shutdown documentation needs to distinguish HTTP-family drain failures from UDP and event-listener timeouts, which can discard work while still allowing exit code 0 and a success message.
1 open issue
What changed in this PR
This PR implements SI-20 of the shutdown overhaul: configurable shutdown budgets, startup validation, and process exit codes based on component outcomes. It also documents the deployment grace periods needed to accommodate the tracker’s shutdown deadline.
Changes:
- Adds optional
[application.shutdown]settings and injects budgets into the supervisor, servers, and event listeners. - Adds outcome-based exit reporting, native-binary shutdown tests, and configured-drain tests.
- Updates configuration templates, deployment guidance, architecture decisions, and Compose stop timeouts.
| File | Description |
|---|---|
tests/lifecycle/shutdown_policy.rs |
Tests signals, exit codes, deadlines, and invalid configuration. |
tests/common/native_tracker/mod.rs |
Exposes child exit status and captured output. |
tests/common/native_tracker/health.rs |
Generalizes service-address log parsing. |
tests/common/native_tracker/failed_start.rs |
Adapts command construction to the new parameter. |
tests/common/native_tracker/command.rs |
Adds isolated shutdown-budget overrides. |
src/main.rs |
Applies the configured deadline and reports shutdown outcomes. |
src/container.rs |
Retains application settings for runtime consumers. |
src/bootstrap/jobs/udp_tracker.rs |
Passes the UDP request-drain budget. |
src/bootstrap/jobs/udp_tracker_server.rs |
Configures statistics and banning listener drains. |
src/bootstrap/jobs/udp_tracker_core.rs |
Configures the UDP-core listener drain. |
src/bootstrap/jobs/tracker_core.rs |
Configures tracker-core listener drains. |
src/bootstrap/jobs/tracker_apis.rs |
Injects the REST API drain budget. |
src/bootstrap/jobs/torrent_repository.rs |
Configures the repository listener drain. |
src/bootstrap/jobs/mod.rs |
Centralizes listener-budget access and wiring checks. |
src/bootstrap/jobs/manager.rs |
Maps component outcomes to exit codes. |
src/bootstrap/jobs/http_tracker.rs |
Injects the HTTP tracker drain budget. |
src/bootstrap/jobs/http_tracker_core.rs |
Configures the HTTP-core listener drain. |
src/bootstrap/jobs/health_check_api.rs |
Injects the health-check drain budget. |
src/bootstrap/config.rs |
Checks shutdown defaults in shipped templates. |
src/app.rs |
Wires budgets into startup cleanup and services. |
src/AGENTS.md |
Updates shutdown behavior documentation. |
share/default/config/tracker.development.sqlite3.toml |
Adds default shutdown settings. |
share/default/config/tracker.container.sqlite3.toml |
Adds shutdown defaults and runtime guidance. |
share/default/config/tracker.container.postgresql.toml |
Adds shutdown defaults and runtime guidance. |
share/default/config/tracker.container.no-persistence.toml |
Adds shutdown defaults and runtime guidance. |
share/default/config/tracker.container.mysql.toml |
Adds shutdown defaults and runtime guidance. |
packages/udp-server/src/testing/environment.rs |
Supplies the default UDP drain budget. |
packages/udp-server/src/server/states.rs |
Accepts a request-drain duration at startup. |
packages/udp-server/src/server/mod.rs |
Updates server tests for budget injection. |
packages/udp-server/src/server/launcher.rs |
Uses injected budgets and tests the public startup path. |
packages/events/src/drain.rs |
Clarifies configured versus standalone drain defaults. |
packages/configuration/src/validator.rs |
Adds an actionable budget-consistency error. |
packages/configuration/src/v3_0_0/shutdown.rs |
Defines validated budgets, defaults, accessors, and tests. |
packages/configuration/src/v3_0_0/mod.rs |
Integrates application settings into schema v3. |
packages/configuration/src/v3_0_0/application.rs |
Adds the optional application configuration section. |
packages/configuration/docs/migrate-v2-to-v3.md |
Explains shutdown-related migration considerations. |
packages/axum-server/src/signals.rs |
Bounds oversized drain durations. |
packages/axum-rest-api-server/src/testing/environment.rs |
Supplies configured REST API drain durations. |
packages/axum-rest-api-server/src/server.rs |
Applies and tests injected REST API drain budgets. |
packages/axum-http-server/src/testing/environment.rs |
Supplies default HTTP drain durations. |
packages/axum-http-server/src/server.rs |
Applies and tests injected HTTP drain budgets. |
packages/axum-health-check-api-server/src/server.rs |
Accepts and tests configurable connection drains. |
packages/axum-health-check-api-server/src/environment.rs |
Supplies the shared HTTP drain default. |
docs/issues/open/2668-1488-si-20-configure-shutdown-policy/ISSUE.md |
Records implementation progress, decisions, and verification. |
docs/issues/open/2668-1488-si-20-configure-shutdown-policy/implementation-retrospective.md |
Records lessons and remaining follow-up candidates. |
docs/issues/open/2668-1488-si-20-configure-shutdown-policy/evidence/hold-and-stop.sh |
Removes the disposable verification script. |
docs/issues/open/2668-1488-si-20-configure-shutdown-policy/evidence/baseline.toml |
Removes the disposable baseline configuration. |
docs/issues/open/2668-1488-si-20-configure-shutdown-policy/agent-review-reports.md |
Records independent review results and follow-ups. |
docs/features/shutdown-process/README.md |
Documents implemented shutdown budgets. |
docs/features/shutdown-process/questions.md |
Amends the budget decision and links evidence. |
docs/containers.md |
Documents exit codes and deployment stop timeouts. |
docs/architecture/tracker-instance-architecture.md |
Explains application-level shutdown configuration. |
docs/architecture/glossary.md |
Updates drain and deadline definitions. |
docs/architecture/application-jobs.md |
Documents the configurable process deadline. |
docs/adrs/index.md |
Registers both shutdown-related decisions. |
docs/adrs/20261027145446_budget_shutdown_in_two_chained_stages_inside_one_process_deadline.md |
Records the budget model and deployment contract. |
docs/adrs/20261027145429_place_process_wide_settings_in_the_application_configuration_section.md |
Establishes process-wide configuration placement. |
compose.qbittorrent-e2e.sqlite3.yaml |
Sets a 35-second tracker stop grace period. |
compose.qbittorrent-e2e.postgresql.yaml |
Sets a 35-second tracker stop grace period. |
compose.qbittorrent-e2e.mysql.yaml |
Sets a 35-second tracker stop grace period. |
Cargo.toml |
Registers the shutdown-policy integration test target. |
Review effort: Balanced
…rule validation Add an optional [application.shutdown] table to the v3 configuration with four budgets in whole seconds: deadline_in_secs (25), http_drain_in_secs (15), udp_request_drain_in_secs (5) and event_listener_drain_in_secs (5). Each value is a ShutdownBudgetInSecs, so zero and values above 3600 are rejected when the configuration loads, and unknown keys are rejected. Shutdown has two sequential stages, because event listeners are stopped only after the servers that publish to them finish. Configuration::validate therefore rejects a configuration where max(http drain, udp request drain) plus the event listener drain is not below the deadline, with an error that names the keys, their values and what to change. With the previous 20 s HTTP drain the chain would be 25 s, which leaves the listeners no time. Nothing consumes the budgets yet: wiring them into the supervisor and the servers follows after the design review of the specification. Record the 25 new tests, their prose-first comparison and 25 mutation proofs in the issue-local manual-verification-evidence.md. Related to torrust#2668 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rust#2668 shutdown budgets The maintainer decided D5: inject the shutdown values from the composition root to the components that apply them, change signatures where needed, pass a plain Duration where a callee needs only a value, and add a public default constant only where injection would add an unwanted dependency. Update the delivery section, T5 and the decision table, and supersede the earlier proposal of additive builders with separate crate defaults. Related to torrust#2668 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ed shutdown Add shutdown_exit_code, which maps the outcomes of a shutdown to the process exit code, returning the current behavior of main: exit 0 whatever the outcomes. Add five unit tests: no outcomes and only completed or cancelled components must give 0, and a failed, a panicked and an aborted component must give 1. The last three fail, which is the defect of exit code 0 after a failed shutdown; the next commit makes them pass. Record the red run in the issue-local manual-verification-evidence.md. Related to torrust#2668 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… aborted at shutdown main discarded the outcomes of the shutdown and always logged success, so the process exited with code 0 even when a component failed, panicked or was aborted at the deadline. Map the outcomes to the exit code: 0 only when every component completed or was cancelled, 1 otherwise. main now returns that exit code, logs the success message only on exit 0, and otherwise logs an error that names the incomplete components. A startup failure returns the failure code instead of calling process::exit. The three tests added in the previous commit pass. Record the green run, six mutation proofs and the one process mistake in the issue-local manual-verification-evidence.md. The real-binary recheck follows after the last production change. Related to torrust#2668 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tener drain budget Read the process shutdown deadline from the configuration instead of a hard-coded 10 s: AppContainer now carries the application settings, main reads the deadline from it, and the cleanup after a failed startup reads it from the configuration. Pass application.shutdown.event_listener_drain_in_secs to all seven event listeners through one helper, event_listener_drain_timeout, in place of the default constant of the events package. The banning listener gains a configuration parameter like the other listener sites. Add ShutdownBudgetInSecs::as_duration so a callee receives a plain Duration, and four tests. The drain timeout of a listener has no observable seam in a unit test, so a source-level test checks that every listener site passes the configured value; a surviving mutation made it count the call per listener. Record the tests, their prose-first comparison and the mutation proofs in the issue-local manual-verification-evidence.md. Related to torrust#2668 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t overflow the clock The drain helper added the caller's Duration to the clock, which panics when the sum does not fit. Its parameter is a plain Duration (maintainer decision D5 in torrust#2668), so any value can reach it. A timeout longer than one year is now shortened to one year. The new test passes Duration::MAX and fails with the tokio overflow panic when the bound is removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…P tracker The Launcher takes a drain_timeout and the token-aware runtime passes it to the drain helper, so the hard-coded 90 s constant is gone. The composition root passes application.shutdown.http_drain_in_secs; the test environment and fixtures pass the configuration default. The new test holds a connection open with a partial request header under a 300 ms drain timeout and expects the drain to end as timed out within 5 s; it fails when the server uses a fixed 90 s value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… REST API The Launcher takes a drain_timeout and the token-aware runtime passes it to the drain helper, so the hard-coded 90 s constant is gone. The REST API start function builds the Launcher and hands it to start_v1, which keeps that function within the argument limit. The composition root passes application.shutdown.http_drain_in_secs. The new test holds a connection open under a 300 ms drain timeout and expects the drain to end as timed out within 5 s; it fails when the server uses a fixed 90 s value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t to the health check API start_with_cancellation takes a drain_timeout and passes it to the drain helper, so the hard-coded 5 s constant is gone. The health check API follows the HTTP drain budget, application.shutdown.http_drain_in_secs, whose default is 15 s; the previous 5 s is therefore a default change for operators. The new test holds a connection open under a 300 ms drain timeout and expects the drain to end as timed out within 5 s; it fails when the server uses a fixed 90 s value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…UDP tracker start_with_cancellation takes a request_drain_deadline and start_receive_loop passes it on, so the hard-coded 5 s REQUEST_DRAIN_DEADLINE and its default wrapper are gone. The composition root passes application.shutdown.udp_request_drain_in_secs. The legacy launcher path, which SI-19 removes, uses the configuration default. The receive-loop shutdown scenarios now start through the public token-aware start, so their 100 ms deadline reaches the receive loop through it; ignoring it makes the orphaned-processors scenario fail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s for torrust#2668 Mark T5 done, add the T5 evidence section (design, prose-first comparison, mutation proofs, automatic checks) and a progress-log entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…orrust#2668 The new lifecycle-shutdown-policy target starts the real binary with configured shutdown budgets and signals its exact PID. It checks exit code 0 and the default 25 s deadline on a clean stop with SIGTERM and SIGINT, the configured deadline in the supervisor log, exit code 1 when a connection held open outlasts the configured HTTP drain (SIGTERM and SIGINT) with no success log, exit code 1 and the configured deadline when a service fails to start, rejection of a zero deadline and of budgets that break the chain rule before any service starts, and acceptance of the largest valid HTTP drain. The shared fixture gains shutdown-budget overrides, an exit-status result, the HTTP tracker address, and an ANSI-free output view. Its shutdown method now requires exit status 0; it ignored the status before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The three drain-timeout tests of the HTTP, REST API and health check servers and the native-binary helper confirmed that the server had accepted the held connection with an unbounded reqwest call, which would hang the test when the server never answers. Each probe now has a five-second bound. The earlier commits of these tests are kept as they are. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mark T6 done, add the T6 evidence section (tests, deviations, prose-first comparison, mutation proofs, automatic checks) and a progress-log entry. Record that the disposable script stays until T8 has used it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d templates The four container templates and the development template now carry the [application.shutdown] block with the default values and a comment that gives the orchestrator rule and the chain rule. A test requires each of them to state the block and to equal the defaults, because a template without the block would also load. It fails when a value differs and when the block is removed. The e2e and benchmarking templates are not operator-facing and are left as they are. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e period The three qBittorrent E2E Compose stacks stopped the tracker with Docker's 10 s default, which is shorter than the default 25 s process deadline. They now set stop_grace_period to 35s, the recommended value of the deployment contract in docs/containers.md. The Compose files were validated with docker compose config; the E2E stacks themselves were not run here and run in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… for torrust#2668 A new section, Stopping the Tracker, lists the four [application.shutdown] keys and their defaults, the chain rule, the environment override form, the exit codes, and the deployment contract: the container runtime waits at least the deadline plus 5 s (30 s minimum, 35 s recommended) with the Docker, Compose, Kubernetes, and systemd settings. It says plainly that Docker and Podman's 10 s default ends in exit status 137 and that exit code 1 must not be added to SuccessExitStatus. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… of torrust#2668 Two root ADRs: the rule that places process-wide settings in an optional [application.<topic>] sub-table, and the budget model of four configured shutdown budgets with the startup rule max(HTTP drain, UDP drain) + listener drain < deadline, the orchestrator contract, and the exit-code contract. Both are registered in the ADR index. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…dgets make stale The application-jobs and instance architecture documents, the glossary entries for the drain timeouts and the shared deadline, the feature README, and the DEFAULT_DRAIN_TIMEOUT documentation no longer describe the 10 s deadline, the 90 s HTTP drain, or a future shutdown configuration. They state the configured budgets and their defaults and link the new ADRs. The v2 to v3 migration guide gains an optional step for [application.shutdown], with the changed defaults and the exit-code change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… drain default Q4 gains an amendment: the event-listener drain is a fourth budget, the startup rule is max(HTTP drain, UDP drain) + listener drain < deadline, and the default HTTP-family drain is 15 s because 20 + 5 is not below 25. Q9 points to the evidence file of the issue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cessor Shutdown gains deadline, http_drain, udp_request_drain and event_listener_drain, each returning the budget as a Duration, so a consumer no longer spells out the key and the conversion at 31 call sites. The accessor test uses four different values, so an accessor that returns another budget fails it; swapping two accessors was seen to fail it. The wiring was re-proved at the executable boundary on the new path: passing the default HTTP drain, or a fixed cleanup deadline, still fails the native-binary tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…2668 Add the D5 follow-up evidence section (five findings, one change, the re-proof on the new path) and a progress-log entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add V2 to V8: the real binary and the release image with SIGTERM and SIGINT (defaults, drain budget reached, invalid configurations, the V1 recheck with the temporary fault-injection patch, Docker with a 35 s and the default 10 s stop timeout, systemd). Podman was not run. Mark T8 and the manual-verification checkpoints done. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
V2 to V8 used evidence/hold-and-stop.sh and evidence/baseline.toml, so they are removed as the specification planned. The held-connection exit codes are covered by the lifecycle-shutdown-policy target; the fault-injection, Docker and systemd runs stay recorded in manual-verification-evidence.md. The evidence file no longer links the removed folder. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… literal The T4 commit dropped the comment and the blank line that separate the configuration fields from the registar in the AppContainer struct literal. The independent review of torrust#2668 found it; no behavior changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e entry-point guide src/AGENTS.md said main waits up to 10 seconds. It now names application.shutdown.deadline_in_secs and the exit-code rule. The shutdown feature README gets its last-updated stamp bumped to the time of its last content change for torrust#2668. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… for torrust#2668 Add agent-review-reports.md with the first independent review (REVIEW FAILED for documentation findings), the V9 real-binary check of the UDP and HTTP-family drains, and implementation-retrospective.md. Fix the stale evidence text, the M3 expectation, the test counts and the Rollback text, update the completion-review lines, and tick the two completed workflow checkpoints. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…st#2668 Append the re-review entry (REVIEW PASSED) to agent-review-reports.md, add the final test count to the T2 evidence, mark T9 done, and log the final state before the pull request. No acceptance criterion is ticked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t#2668 Set related-pr to 2678 and add a progress-log entry: the branch was rebased onto the new develop because another merged pull request conflicted with the shared native-tracker fixture, and the gate was rerun on the rebased tree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only the HTTP-family drains (HTTP tracker, REST API, health check API) end as a failed component when their budget is reached. The UDP request drain and the event listener drain log a warning and still end as completed or cancelled, so exit code 0 does not prove that no UDP request or queued event was discarded. The budget ADR and the exit code section of docs/containers.md said that any drain reaching its budget fails the stop. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…it-result section The exit-result table said any outcome including a drain budget reached is a failure. Only the HTTP-family drains are; the UDP request drain and the listener drain only log a warning. Add a progress-log entry for the review of pull request 2678. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
One Copilot finding (F1, documentation): which drain timeouts end a stop with exit code 1. It was fixed in the commit that qualifies them, replied to, and resolved; the validator exits 0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion container The seven event listener sites now take the drain budget from app_container.application_config.shutdown.event_listener_drain(), the single source of the other budgets, and the event_listener_drain_timeout(config) helper is removed. The HTTP core, UDP core and UDP server statistics starters drop the config parameter they only used for the helper (it was an unused _config on develop), and the UDP banning starter returns to its develop signature. The torrent repository and tracker core starters keep config for core settings. The wrappers in src/app.rs lose the parameter too. The helper test is removed: its behavior is covered by the accessor test of the configuration package and the AppContainer test, and the source-level guard now counts the container call per listener site; it fails when a site uses a hard-coded 5 s or another budget. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pplication container The three starter tests that already build an AppContainer (HTTP tracker TLS and listener errors, UDP tracker socket release) read the drain value from app_container.application_config like the composition root. The starters themselves take a Duration and read no configuration. The remaining tests have no container in scope and keep the configuration or the default. No behavior changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… commit that touched it The stamp read 2026-10-27 14:59, the time of the last content change, while a later commit changed the file again. It now carries the time of this review round. Every other changed file with a last-updated-utc was re-checked against the author time of the last commit that touched it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ers for torrust#2668 Describe the single source (the application container) in the D5 text and the T4 row, add the evidence section for the listener drain change (decision, answer to the maintainer's question, tests, two mutation proofs), mark finding 5 of the D5 follow-up as superseded, restore the Failures and Follow-up heading that the D5 edit had dropped, and add the progress-log entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
josecelano
force-pushed
the
2668-configure-shutdown-policy
branch
from
October 28, 2026 13:31
773678c to
a6457bf
Compare
Member
Author
|
ACK a6457bf |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes #2668
Summary
This implements SI-20 of EPIC #1488. The tracker now reads its shutdown budgets from a new optional
[application.shutdown]configuration table, validates them at startup, injects each value into the component that applies it, and exits with code 0 only after a complete stop (and 1 after a failed, panicked or aborted component, or a failed startup). The deployment contract (the container runtime waits at least the process deadline plus 5 s) is documented and was demonstrated with Docker and systemd.Behavior changes operators will notice
The process deadline defaults to 25 s (it was a hard-coded 10 s). The HTTP tracker and REST API drain defaults to 15 s (it was a hard-coded 90 s), and the health check API drain rises from 5 s to 15 s because it shares the HTTP drain. The UDP request drain stays 5 s and the event listener drain stays 5 s. A requested stop that is incomplete now exits 1, so a systemd unit is marked failed; do not add 1 to
SuccessExitStatus. Docker and Podman's default 10 s stop timeout is shorter than the default deadline and ends in exit status 137;docs/containers.mdgives the 30 s minimum and 35 s recommended settings. The tracker refuses to start unlessmax(http_drain_in_secs, udp_request_drain_in_secs) + event_listener_drain_in_secsis belowdeadline_in_secs. These changes need a line in the release notes. The server crates (0.1.0) change signatures (Launcher::new, the UDP and health checkstart_with_cancellation), as decided in D5 of the specification.What changed
Configuration:
packages/configurationgets the[application.shutdown]table with value newtypes (1 to 3600 s), the chain-rule validator and a named accessor per budget. Behavior:src/main.rsreturns anExitCodefrom a pure outcome mapping insrc/bootstrap/jobs/manager.rs; the deadline, drains and listener budget are injected fromsrc/app.rsandsrc/bootstrap/jobs/intoaxum-http-server,axum-rest-api-server,axum-health-check-api-serverandudp-server, so the constants in those crates are gone; the shared drain helper inaxum-servernow bounds an oversized timeout. Tests: unit tests per crate, thelifecycle-shutdown-policynative-binary target (real binary, exact PID, SIGTERM and SIGINT), and a template test; the shared native-tracker fixture'sshutdown()now requires exit status 0. Documentation and decisions: the five container and development templates,docs/containers.md, the three E2E Compose files (stop_grace_period: 35s), the architecture documents and glossary, the v2 to v3 migration guide, an amendment to Q4, and two root ADRs (the[application]placement rule and the budget model).Validation
linter allexit 0;cargo test --tests --benches --examples --workspace --all-targets --all-features3,664 passed, 0 failed (on the tree rebased the second time, which includes the testsdevelopgained);cargo test --doc --workspaceexit 0; the pre-push script exit 0 on that tree. Every new test was mutation-proven (the mutation fails it, the restore is proven withcmp); the tables are inmanual-verification-evidence.md. Manual verification (V1 to V9 in that file) ran on the real binary and a release image: defaults, a held connection under a 2 s drain (exit 1), invalid configurations rejected before any listener binds, the V1 fault-injection recheck, Docker with a 35 s and the default 10 s stop timeout, systemd, and a UDP tracker with distinct budgets. Podman was not run (not installed). An independent review first returned REVIEW FAILED for documentation findings, all addressed, and the re-review returned REVIEW PASSED (agent-review-reports.md).The temporary fault-injection patch used for V1, V5 and V7 is not part of this branch; the working tree was proven identical to
HEADafter each use. This is not a performance-critical path (shutdown-time configuration), so no benchmark was run.For the maintainer
No acceptance criterion is ticked. Open items: AC5 has no maintained test guarding which key reaches the UDP drain in
src/app.rs(V9 shows it on the real binary) or the listener drain (source-level guard only); several native-binary tests keep their Act in a helper;JobManager::wait_for_allstill adds to the clock unchecked (only the 3600 s configuration bound protects it), a follow-up candidate recorded inimplementation-retrospective.md. The history of this branch was rewritten twice with a rebase onto the latestdevelop, both times because another pull request merged after this one was opened and conflicted with it. The first conflict (pull request 2676) was an import line in the shared native-tracker fixture's test module, resolved by keeping both sets of tests. The second (pull request 2680, announce-interval bounds) was inpackages/configuration/docs/migrate-v2-to-v3.md, where both pull requests added a "Step 11", a table row and a checklist row: the announce-interval step keeps number 11, the shutdown-budget step is now Step 12, and both rows and checklist items are kept. All commits were re-signed each time. No repository file cites a commit id, only Conventional Commit subjects. The manual verification ran before the rebases on code they did not change. Head to merge: the commitdocs(issues): record the second rebase onto develop for #2668. Changed since the previous head (docs(issues): record how the listener drain budget reaches the listeners for #2668, before the second rebase): only the rebase onto the currentdevelopand one progress-log entry that records it. The previous head's own change was the following: the maintainer decided that the seven event listeners read their drain budget fromapp_container.application_configlike the other budgets, so theevent_listener_drain_timeout(config)helper is removed, four listener starters and their wrappers insrc/app.rsdrop an unusedconfigparameter, and the UDP banning starter has itsdevelopsignature again; three starter tests use the container; the stamp of the shutdown feature README is bumped and every changed file's stamp was re-checked; the specification and the evidence describe the change. The review of Copilot (one finding about which drain timeouts end a stop with exit code 1) was processed indocs/pr-reviews/pr-2678-review/PR-REVIEW.md. A push dismisses reviews.