Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions .github/skills/dev/planning/create-adr/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: create-adr
description: Guide for creating Architectural Decision Records (ADRs) in the torrust-tracker project. Covers the timestamp-based file naming convention, free-form structure, index registration in the docs/adrs/README.md index table, and commit workflow. Use when documenting architectural decisions, recording design choices, or adding decision records. Triggers on "create ADR", "add ADR", "new decision record", "architectural decision", "document decision", or "add decision".
description: Guide for creating Architectural Decision Records (ADRs) in the torrust-tracker project. Covers decision-scope placement, timestamp-based names, free-form structure, collection-specific index registration, and commit workflow. Use when documenting architectural decisions, recording design choices, or adding decision records. Triggers on "create ADR", "add ADR", "new decision record", "architectural decision", "document decision", or "add decision".
metadata:
author: torrust
version: "1.0"
Expand All @@ -18,14 +18,21 @@ metadata:
date -u +"%Y%m%d%H%M%S"
# e.g. 20241115093012

# 2. Create the ADR file
# 2. Choose the ADR collection by decision scope
# Repository-wide, multi-package, and inter-package: docs/adrs/
# Package-owned and extractable: packages/<package>/docs/adrs/

# 3. Create the ADR file
# Format: YYYYMMDDHHMMSS_snake_case_title.md
# Root ADR:
touch docs/adrs/20241115093012_your_decision_title.md
# Package-local ADR:
touch packages/<package>/docs/adrs/20241115093012_your_decision_title.md

# 3. Update the index
# Add entry to docs/adrs/index.md
# 4. Update the owning collection's index
# Add a root ADR to docs/adrs/index.md; add a local ADR only to its local index

# 4. Validate and commit
# 5. Validate and commit
linter markdown
linter cspell
git commit -S -m "docs(adrs): add ADR for {short description}"
Expand Down Expand Up @@ -57,7 +64,26 @@ date -u +"%Y%m%d%H%M%S"
- `20240227164834_use_plural_for_modules_containing_collections.md`
- `20241115093012_adopt_axum_for_http_server.md`

Location: `docs/adrs/`
## ADR Placement

Choose the collection according to the scope of the decision, not the paths changed by the
implementation:

| Decision scope | Location |
| --------------------------------------------------------- | ------------------------------- |
| Repository-wide, multi-package, or inter-package contract | `docs/adrs/` |
| Solely owned by an extractable package | `packages/<package>/docs/adrs/` |

Shared configuration, protocol behavior, dependency policy, workspace conventions, and other
cross-package contracts require a root ADR even if one package contains all immediate code changes.

Every package-local collection needs a `README.md` and an `index.md`. Register an ADR only in the
index for its owning collection; root indexes do not duplicate local entries. When a package-local
decision becomes repository-wide, create a root ADR that links to and supersedes the local ADR,
then retain the local ADR and its local index entry as historical context.

The tracker-client CLI I/O ADR and the later root global CLI output ADR demonstrate this
local-placement and root-supersession pattern.

## ADR Structure

Expand Down Expand Up @@ -86,19 +112,20 @@ Only add a `- Status:` header for special terminal states:
```bash
PREFIX=$(date -u +"%Y%m%d%H%M%S")
TITLE="your_decision_title" # snake_case
echo "docs/adrs/${PREFIX}_${TITLE}.md"
echo "docs/adrs/${PREFIX}_${TITLE}.md" # Or packages/<package>/docs/adrs/ for a local decision.
```

### Step 2: Write the ADR

- **Scope**: State whether the decision is root or package-local and why
- **Description**: Explain the problem thoroughly — enough context for future contributors
- **Agreement**: State clearly what was decided and why
- **Date**: Today's date (`date -u +"%Y-%m-%d"`)
- **References**: Issues, PRs, external docs

### Step 3: Update the Index

Add a row to the index table in `docs/adrs/index.md`:
Add a row to the selected collection's `index.md` table:

```markdown
| [YYYYMMDDHHMMSS](YYYYMMDDHHMMSS_your_title.md) | YYYY-MM-DD | Short Title | One-sentence description. |
Expand Down Expand Up @@ -126,7 +153,7 @@ linter markdown
linter cspell
linter all # full check

git add docs/adrs/
git add docs/adrs/ # Include a package-local ADR path instead when applicable.
git commit -S -m "docs(adrs): add ADR for {short description}"
git push {your-fork-remote} {branch}
```
Expand Down
7 changes: 6 additions & 1 deletion .github/skills/dev/planning/create-issue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,12 @@ The draft must also include a verification policy that is explicit and enforceab

During implementation, create an ADR when an important architectural decision
emerges, even if the issue draft did not anticipate it. Link the ADR from the
issue specification and update the architectural-decisions section.
issue specification and update the architectural-decisions section. For each
planned ADR, identify its expected root or package-local collection by decision
scope: use `docs/adrs/` for repository-wide, multi-package, and inter-package
decisions, and `packages/<package>/docs/adrs/` only for decisions owned solely
by an extractable package. Do not choose placement only from the implementation
paths expected to change.

Use **placeholders** for the issue number until after creation (for example `github-issue: null`
or `[To be assigned]` in the heading/body content).
Expand Down
9 changes: 8 additions & 1 deletion docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,21 @@ For the full project context see the [root AGENTS.md](../AGENTS.md).

| Artifact type | Target location |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| New ADR | `docs/adrs/` — filename format: `YYYYMMDDHHMMSS_<short-slug>.md` |
| New root ADR | `docs/adrs/YYYYMMDDHHMMSS_snake_case_title.md` — for repository-wide, multi-package, or inter-package decisions |
| New package-local ADR | `packages/<package>/docs/adrs/YYYYMMDDHHMMSS_snake_case_title.md` — for decisions owned only by an extractable package |
| New issue spec (before GitHub issue exists) | `docs/issues/drafts/` |
| New issue spec (after GitHub issue created) | `docs/issues/open/<number>-<short-slug>.md`, or `docs/issues/open/<number>-<short-slug>/ISSUE.md` when it has issue-local artifacts |
| New refactor plan (before GitHub issue exists) | `docs/refactor-plans/drafts/` |
| New refactor plan (after GitHub issue created) | `docs/refactor-plans/open/<number>-<short-slug>.md` |
| New document template | `docs/templates/` |
| New diagram or screenshot | `docs/media/` (or the relevant subdirectory) |

Choose ADR placement by the decision's architectural scope, not the paths modified by the
implementation. Root ADRs cover shared configuration, protocols, dependency policy, workspace
conventions, and other cross-package contracts. A package-local ADR collection contains its own
`README.md` and `index.md`; do not duplicate its entries in the root ADR index. See
[`docs/adrs/20260830124000_place_adrs_by_decision_scope.md`](adrs/20260830124000_place_adrs_by_decision_scope.md).

## Markdown Frontmatter

Frontmatter use varies by document type:
Expand Down
112 changes: 112 additions & 0 deletions docs/adrs/20260830124000_place_adrs_by_decision_scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
semantic-links:
skill-links:
- create-adr
related-artifacts:
- docs/AGENTS.md
- docs/adrs/README.md
- docs/adrs/index.md
- docs/templates/ADR.md
- .github/skills/dev/planning/create-adr/SKILL.md
- console/tracker-client/docs/adrs/20260512080000_define_tracker_cli_io_contract_and_error_handling.md
- docs/adrs/20260519000000_define_global_cli_output_contract.md
---

# Place ADRs by Decision Scope

## Scope

Root ADR. This decision establishes a repository-wide policy for placing ADRs across root and
package-local collections.

## Description

The repository currently collects ADRs in `docs/adrs/`, but workspace packages are intended to
be independently extractable. An ADR whose decision is owned solely by one package must travel
with that package; otherwise, extraction separates the implementation from its rationale.

The paths changed by an implementation do not reliably determine this ownership. A change in one
package can establish a repository policy, alter shared configuration or a protocol, or define an
inter-package contract. Such decisions need one repository-level record even when their immediate
implementation is local.

The tracker client provides the established precedent. Its original CLI I/O decision lives in
`console/tracker-client/docs/adrs/`, because extraction was anticipated. The later root ADR,
`20260519000000_define_global_cli_output_contract.md`, expanded the contract to all first-party
binaries and superseded the local ADR without removing its historical context.

## Agreement

### Placement criteria

Place an ADR in `packages/<package>/docs/adrs/` when all of the following apply:

- The decision is limited to that package's architecture, behavior, or public contract.
- The package owns the decision and its rationale.
- The ADR should remain with the package when it is extracted into its own repository.

Place an ADR in `docs/adrs/` when the decision governs the repository, affects multiple packages,
or defines an inter-package contract. Root placement is required for decisions about shared
configuration, protocol behavior, dependency policy, workspace-wide conventions, or another
cross-package interface, even if the implementation change initially touches one package.

When scope is uncertain, use root placement or resolve the scope during review. Do not infer scope
solely from the paths of affected implementation files.

### Local ADR collections

Each package-local ADR collection must contain:

- `README.md`, describing the collection's package ownership and its relationship to root ADRs.
- `index.md`, listing ADRs owned by that package.
- Timestamp-prefixed ADR files using `YYYYMMDDHHMMSS_snake_case_title.md`.

Root and package-local indexes are separate. List each ADR only in its owning collection's index;
do not duplicate package-local ADR rows in `docs/adrs/index.md`. Package documentation should link
to its local collection so the ADRs remain discoverable from the package entry point.

The established `console/tracker-client/docs/adrs/` collection follows the same ownership model
for an extractable application that is not under `packages/`.

### Supersession

When a package-local decision becomes repository-wide, create a root ADR. The root ADR must link
to the local ADR and explain the expanded scope. Update the local ADR with a `Status: Superseded`
link to the root ADR, while retaining the local ADR and its local index entry as historical
context. Do not move or duplicate the local ADR merely because it was superseded.

## Alternatives Considered

**Keep every ADR in `docs/adrs/`.** Rejected because package extraction would separate
package-owned implementation from the decision rationale that explains it.

**Place ADRs by implementation-file location.** Rejected because local implementation can carry
repository-wide consequences, especially for configuration, protocols, and shared contracts.

**Copy package-local ADRs into the root index.** Rejected because duplicated registry entries
create ambiguous ownership and drift.

**Move a local ADR to the root when its scope expands.** Rejected because the original local
decision remains useful historical context and should remain with an extracted package.

## Consequences

Package-owned rationale remains portable with extractable packages. Contributors must make an
explicit scope judgment when authoring ADRs, and reviewers must verify that judgment. Root ADR
navigation does not enumerate every package-local decision, so package documentation must expose
its own ADR collection.

This ADR does not itself migrate existing ADRs. Existing migrations, including the UDP-core ADR,
are completed by their owning implementation work after this policy is accepted.

## Date

2026-08-30

## References

- Issue: [#2116](https://github.com/torrust/torrust-tracker/issues/2116)
- Tracker-client local precedent:
`console/tracker-client/docs/adrs/20260512080000_define_tracker_cli_io_contract_and_error_handling.md`
- Root supersession example:
`docs/adrs/20260519000000_define_global_cli_output_contract.md`
25 changes: 20 additions & 5 deletions docs/adrs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ semantic-links:

# Architectural Decision Records (ADRs)

This directory contains the architectural decision records (ADRs) for the project.
This directory contains the repository-level architectural decision records (ADRs) for the project.
ADRs document architectural decisions — what was decided, why, and what alternatives
were considered.

More info: <https://adr.github.io/>.

See [index.md](index.md) for the full list of ADRs.
See [index.md](index.md) for the full list of root ADRs.

## How to Add a New ADR

Expand All @@ -26,13 +26,28 @@ Generate the timestamp prefix (UTC):
date -u +"%Y%m%d%H%M%S"
```

Create a new Markdown file using the format `YYYYMMDDHHMMSS_snake_case_title.md`:
First choose the ADR collection by the decision's architectural scope:

- `docs/adrs/` for repository-wide, multi-package, and inter-package decisions.
- `packages/<package>/docs/adrs/` for decisions owned solely by an extractable package.

Shared configuration, protocol behavior, dependency policy, workspace conventions, and
inter-package contracts are root decisions even when only one package's implementation changes.
Do not choose a location solely from the paths touched by the change.

Create a new Markdown file in the selected collection using the format
`YYYYMMDDHHMMSS_snake_case_title.md`:

```shell
20230510152112_title.md
20230510152112_example_decision.md
```

Then add a row to the [Index](index.md) table.
Then add a row only to that collection's index. Every package-local collection requires its own
`README.md` and `index.md`; do not duplicate local ADRs in the root [Index](index.md) table.

When a local decision becomes repository-wide, create a root ADR that links to and supersedes the
local ADR. Keep the local ADR and its local index entry as historical context. The tracker-client
CLI I/O ADR and the root global CLI output ADR are the existing example.

There is no rigid template. A typical ADR includes:

Expand Down
8 changes: 7 additions & 1 deletion docs/adrs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ semantic-links:
- docs/adrs/20260527175600_keep_protocol_and_domain_types_decoupled.md
---

# ADR Index
# Root ADR Index

This index lists repository-level ADRs only. Package-local ADRs are listed in their owning
`packages/<package>/docs/adrs/index.md` and are not duplicated here. See
[Place ADRs by Decision Scope](20260830124000_place_adrs_by_decision_scope.md) for placement and
supersession rules.

| ADR | Date | Title | Short Description |
| ------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -34,6 +39,7 @@ semantic-links:
| [20260822094338](20260822094338_adopt_secrecy_for_sensitive_values.md) | 2026-08-22 | Adopt secrecy for sensitive values | Use the current stable `secrecy::SecretString` directly for credentials; deserialize existing configuration syntax with serde, serialize only at explicit persistence boundaries, and expose values only at immediate runtime-consumption boundaries. |
| [20260825193119](20260825193119_make_persistence_an_optional_application_composition_capability.md) | 2026-08-25 | Make persistence an optional application-composition capability | Schema v3 represents absent persistence with `Option<Database>` and resolves it at tracker-core composition while retaining tracker-core schema and migration ownership. |
| [20260826124959](20260826124959_use_explicit_identifiers_for_test_log_assertions.md) | 2026-08-26 | Use explicit identifiers for test log assertions | Keep the repository-owned bounded log-capture helper and use test-selected identifiers instead of automatic span propagation through concurrent execution. |
| [20260830124000](20260830124000_place_adrs_by_decision_scope.md) | 2026-08-30 | Place ADRs by decision scope | Keep package-owned decisions with extractable packages; record repository-wide, multi-package, and inter-package decisions in the root collection. |

## ADR Lifecycle

Expand Down
Loading