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
125 changes: 123 additions & 2 deletions docs/issues/drafts/refactor-semantic-link-conventions/EPIC.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ status: draft
github-issue: null
spec-path: docs/issues/drafts/refactor-semantic-link-conventions/EPIC.md
epic-owner: null
last-updated-utc: 2026-09-16 16:33
last-updated-utc: 2026-09-18 08:45
semantic-links:
skill-links:
- create-issue
related-artifacts:
- docs/skills/semantic-skill-link-convention.md
- docs/issues/open/2003-overhaul-guardrails-and-automation/EPIC.md
- docs/issues/closed/2233-2003-tune-unified-pr-review-process/ISSUE.md
- docs/issues/closed/2233-2003-tune-unified-pr-review-process/code-span-path-case-analysis.md
- docs/issues/closed/2233-2003-tune-unified-pr-review-process/code-span-path-case-inventory.tsv
- docs/issues/open/2185-2003-triage-advisory-external-link-check-findings/ISSUE.md
- docs/issues/open/2185-2003-triage-advisory-external-link-check-findings/external-link-baseline.md
- docs/issues/drafts/refactor-semantic-link-conventions/external-link-check-residual-failures-2026-09-18.md
- .github/lychee-online.toml
- .github/workflows/external-link-check.yaml
- docs/testing.md
---

# EPIC #[To be assigned] - Refactor Semantic Link and Frontmatter Conventions
Expand Down Expand Up @@ -101,6 +108,9 @@ noise without improving understanding.
- Decide how typed semantic links differ from ordinary path references in prose.
- Decide whether path references need their own explicit syntax and validator separate from semantic
links.
- Coordinate validator implementation boundaries with EPIC #2003 before choosing whether
frontmatter, semantic-link, or path-reference validators live in standalone scripts, dedicated
Rust binaries, or a future unified AI-harness application.
- Define progressive migration and validation subissues.
- Preserve compatibility rules for existing documents during migration.

Expand All @@ -111,6 +121,8 @@ noise without improving understanding.
- Rewriting historical records only to satisfy new syntax.
- Replacing Lychee as the validator for normal Markdown links.
- Requiring every prose path mention to become a semantic link.
- Selecting the long-term AI-harness architecture, command surface, cache model, or division between
guardrail checks and repository-mutating actions; those decisions belong to EPIC #2003.

## Proposed Convention Split

Expand All @@ -120,7 +132,7 @@ The EPIC should decide final names and locations, but this starting split separa
| ---- | ----------------------- | ---------------- |
| Frontmatter metadata | Document types, required fields, lifecycle statuses, version fields, schemas, and validation expectations. | `docs/conventions/frontmatter-metadata.md` |
| Semantic links | Typed relationships between project concepts, with relation names, target types, and graph semantics. | `docs/conventions/semantic-links.md` |
| Typed model and validators | Rust types or another schema source of truth for document metadata, semantic-link targets, relations, parsing, diagnostics, and staged enforcement. | `contrib/dev-tools/checks/` or a dedicated package, to be decided |
| Typed model and validators | Rust types or another schema source of truth for document metadata, semantic-link targets, relations, parsing, diagnostics, and staged enforcement. Validator placement must remain compatible with EPIC #2003's automation design. | `contrib/dev-tools/checks/`, a dedicated package, or a future AI-harness component, to be decided |
| Path references | Syntax and validation rules for machine-checkable repository paths in prose, distinct from ordinary Markdown links. | `docs/conventions/path-references.md` |
| Marker placement | Language-specific syntax for comments, frontmatter, and inline markers outside Markdown. | `docs/conventions/reference-placement.md` |
| Migration policy | Versioning, compatibility windows, staged validation, and historical-document handling. | `docs/conventions/convention-migrations.md` |
Expand Down Expand Up @@ -200,6 +212,23 @@ rust-module:torrust_tracker_udp_core::services::banning
The EPIC should not adopt this syntax without comparing it against existing repository conventions
and parser complexity.

## Relationship to EPIC #2003

This EPIC may produce deterministic validators for Markdown frontmatter, semantic links, and typed
path references. Those validators are checks in the terminology of
[EPIC #2003](../../open/2003-overhaul-guardrails-and-automation/EPIC.md): they should be read-only by
default, return stable diagnostics, and be callable from local hooks, CI, and agent workflows.

Their implementation may eventually belong in the same Rust application or shared libraries as the
AI harness proposed by #2003, but this EPIC should not decide that architecture by accident. The
convention work should specify the data model, validation semantics, rollout stages, and diagnostics
well enough that #2003 can later choose the execution surface.

The boundary matters because #2003 also covers repository actions such as dependency updates and
completed-issue cleanup. Frontmatter and semantic-link validation may share parsing, repository
discovery, JSONL/NDJSON reporting, and configuration loading with those actions, but it must remain
clear which commands are read-only checks and which commands mutate the repository.

## Progressive Subissues

| ID | Draft subissue | Expected output |
Expand All @@ -216,6 +245,8 @@ and parser complexity.
| S10 | Design path-reference validation | If S9 chooses explicit path references, specify validation behavior, exclusions, historical handling, and migration rules. |
| S11 | Review `docs/skills/` ownership | Decide whether convention documents should move from `docs/skills/` to a broader folder such as `docs/conventions/`. |
| S12 | Plan staged migration | Define ordering, branch strategy, compatibility windows, and no-rewrite rules for historical records. |
| S13 | Classify external-link importance and checker limitations | Using the #2185 handoff below, decide how the advisory online check should treat license boilerplate, background reading, bot-protected hosts, TLS-incompatible hosts, and critical unstable references whose important contents may need an in-repository copy or distilled context for agents and maintainers. Decide whether link importance belongs in the typed model. |
| S14 | Coordinate validator placement with EPIC #2003 | Decide which outputs from S5, S8, and S10 are convention-owned validation semantics and which implementation choices must be deferred to EPIC #2003's automation and AI-harness architecture decision. |

## Relationship to Issue #2233

Expand All @@ -226,6 +257,77 @@ The #2233 T2 finding can be resolved by recording that strict Markdown code-span
deferred until this EPIC defines whether path references are semantic links, a separate typed
reference form, or ordinary prose outside validator scope.

## Handoff from Issue #2185

Issue [#2185](https://github.com/torrust/torrust-tracker/issues/2185) set out to clean the broken
links reported by the advisory
[External Link Check workflow](../../../../.github/workflows/external-link-check.yaml). It started
from 461 errors, repaired every genuinely stale reference it found, and added only exact,
commented exclusions to [`.github/lychee-online.toml`](../../../../.github/lychee-online.toml). It
closed with seven errors and four timeouts that no cleanup step can remove. Those cases are
preserved verbatim in
[`external-link-check-residual-failures-2026-09-18.md`](external-link-check-residual-failures-2026-09-18.md)
and are input for S13. No solution is chosen here.

### What the triage established

- **Most "broken" links were not broken.** 424 of the 461 baseline errors were GitHub
pull-request review-comment anchors (`#discussion_r<id>`), and a later class of 23 errors were
`#issuecomment-<id>` and `#pullrequestreview-<id>` anchors on pull-request URLs. GitHub renders
these anchors client-side, so Lychee's `include_fragments = "full"` check cannot see them even
though every target exists. The same applies to two GitHub issue-comment anchors and to the
Star History project selector.
- **Dynamic anchors are generated by a repository process.** The review-comment anchors come from
the PR review records under `docs/pr-reviews/` and the older `docs/copilot-pr-reviews/`. Any
future process that writes those records will keep producing links the checker cannot validate,
so the exclusion pattern is a standing policy, not a one-off cleanup.
- **A `403` does not mean stale.** Medium and Stack Overflow return `403` to automated clients
while the content still exists. Excluding them hides real link rot on those hosts; keeping them
produces permanent noise.
- **A transport failure can be a checker limitation.** `www.fsf.org` serves only finite-field
`DHE` cipher suites; rustls, which Lychee uses, supports `ECDHE` only. The site is live and the
certificate is valid, but Lychee can never reach it. The affected links are AGPL license
boilerplate repeated in four README files.
- **Rerun-first works, but only for genuinely transient cases.** A `martinfowler.com` timeout in
one run disappeared in the next; the `403` and FSF failures persisted across every run. The
policy in [`docs/testing.md`](../../../../docs/testing.md) is sufficient to separate the two, but
it has no answer for the persistent non-stale cases.
- **Counts across runs are not comparable.** Every merge changed the checked document set, so the
issue had to verify each remediation slice by the absence of its exact URLs, not by the error
count.
- **Repository-owned stale links were few and cheap to fix.** 14 docs.rs package pages, three
repository paths, one Caddy page, and two retired Docker Cloud pages were the only real rot in
the baseline.
- **Some important external context may need to live in the repository.** If an unstable external
URL carries critical knowledge for maintainers or AI agents, the policy may need a third option
beyond "keep checking" and "exclude": preserve a reviewed copy, excerpt, or simpler distilled
form in the repository, then treat the original URL as provenance, background reading, or an
online-check exception. This is only appropriate when the copied material is necessary,
maintainable, and legally reusable; otherwise the project should prefer a stable replacement
source or a concise in-repo summary.

### Why the remaining cases are a design question

The residual failures are all license boilerplate or optional background reading, but the
repository currently has no vocabulary to say so. `lychee-online.toml` can only express "check
this URL" or "never check this URL", while the actual distinction is between:

- links whose target the project owns or depends on and should keep checking strictly;
- links that are citations or background reading, where a `403` from a bot-protected host is
acceptable and a replacement citation would be a policy choice;
- critical references whose contents should remain queryable even when the source site is unstable,
slow, access-controlled, or unsuitable for automated checking;
- links whose target is reachable by users but not by the checker, where the failure describes the
tool rather than the document;
- links inside historical or generated records that should never be rewritten to satisfy a
checker.

These are the same distinctions this EPIC needs for semantic links versus ordinary Markdown links.
Ordinary links and typed links are not separate concerns: the importance, ownership, and lifecycle
of a link decide how it should be validated. S13 should use the residual report and the #2185
baseline as its evidence and may conclude that the answer is prose policy, a small typed
annotation, or a different checker configuration; the EPIC does not presume which.

## Open Questions

- Which document classes require typed frontmatter first: issue specs, EPIC specs, ADRs, skills,
Expand All @@ -243,6 +345,23 @@ reference form, or ordinary prose outside validator scope.
- Which OKF principles should be copied directly, adapted, or rejected for a repository whose
knowledge graph includes code, issues, review findings, ADRs, commits, and Markdown sections?
- Which references preserve design theory and intent, and which are only local navigation aids?
- Should frontmatter, semantic-link, and path-reference validators be standalone scripts, dedicated
Rust binaries, library code consumed by a future AI harness, or subcommands of a unified
repository automation application?
- If a unified AI harness is built, should it contain only read-only sensors and guardrail checks
used by pre-commit, pre-push, CI, and agent review, or should it also expose repository actions
such as dependency updates and completed-issue cleanup?
- Which validation semantics must be decided here, and which execution, caching, output, and
migration choices should be deferred to EPIC #2003?
- Should link importance (owned dependency, citation, background reading, license boilerplate) be
expressed in the typed model, in prose policy, or only in checker configuration?
- When an external source is important but unstable, should the repository preserve a vetted copy,
excerpt, or distilled context and either remove the URL from checked documents or exempt it from
online Lychee analysis?
- How should the advisory online check report a link that users can reach but the checker cannot
(bot protection, TLS incompatibility) without either hiding link rot or producing permanent noise?
- Should links inside generated records such as `docs/pr-reviews/` be exempt from online validation
as a class rather than through per-pattern exclusions?

## Acceptance Criteria

Expand All @@ -257,6 +376,8 @@ reference form, or ordinary prose outside validator scope.
- [ ] The EPIC decides whether machine-checkable path references are semantic links, a separate
reference type, or intentionally out of scope.
- [ ] Progressive implementation subissues are created for validation and migration work.
- [ ] The final plan states how frontmatter, semantic-link, and path-reference validators relate to
EPIC #2003 without preselecting the AI-harness architecture.
- [ ] Historical records have an explicit no-rewrite or migration policy.
- [ ] The final plan states which existing validators, including Lychee, remain responsible for
ordinary Markdown links.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
source-run: https://github.com/torrust/torrust-tracker/actions/runs/35315382956
source-revision: e6dd8918bf6964794882f60486f2da3e296cbb13
online-config: .github/lychee-online.toml
collected-at-utc: 2026-09-18 06:43
---

# External Link Check Residual Failures

This artifact preserves the last `lychee-external-link-report` produced while issue
[#2185](https://github.com/torrust/torrust-tracker/issues/2185) was open. It is input for this
EPIC, not a decision. The failures listed here are the cases that the operational triage in #2185
could not resolve by repairing a stale reference or by adding a narrowly scoped exclusion to
[`.github/lychee-online.toml`](../../../../.github/lychee-online.toml).

The report comes from [External Link Check run 35315382956](https://github.com/torrust/torrust-tracker/actions/runs/35315382956),
which ran on merged `develop` revision `e6dd8918` after PR #2255 merged. The 782-byte artifact
expires on 2026-10-02; the report is reproduced verbatim below so the evidence outlives it.

## Why These Cases Remain

| Case | Documents | Observed diagnostic | Root cause established in #2185 |
| ---- | --------- | ------------------- | ------------------------------- |
| Medium articles | `.github/skills/dev/testing/write-unit-test/SKILL.md` | `403 Forbidden` | Medium returns `403` to both a Lychee-like and a browser user agent; this is bot protection, not stale content. |
| Stack Overflow answer permalink | `docs/containers.md` | `403 Forbidden` | The short `/a/<answer>/<user>` permalink redirects to the full question URL, which then also returns `403` to automated clients. The answer still exists. |
| FSF home page | `README.md`, `console/tracker-client/README.md`, `packages/rest-api-client/README.md`, `packages/tracker-client/README.md` | TLS `HandshakeFailure` and cached errors | `www.fsf.org` returns HTTP 200 via `curl` and `openssl`, but only offers finite-field `DHE` cipher suites. Lychee uses rustls, which supports `ECDHE` only, so the checker can never complete the handshake. This is a checker limitation, not a dead link. |
| GNU licenses page | Same four license footers as FSF | `Request timed out` | First observed in this run. Under the rerun-first policy in `docs/testing.md`, a single timeout is a transient candidate until a second run confirms it. |

These links are all AGPL license boilerplate or background reading. None of them is a repository-owned
reference that the project can repair, and each candidate fix (replacing citations, excluding hosts,
or disabling TLS strictness) is a policy decision rather than a cleanup step. Issue #2185 therefore
stopped here and handed the question to this EPIC.

## Verbatim Report

The report is quoted as a fenced block so the online check does not re-check these URLs from this
artifact.

```text
# Summary

| Status | Count |
|----------------|-------|
| 🔍 Total | 2224 |
| 🔗 Unique | 1537 |
| ✅ Successful | 1327 |
| ⏳ Timeouts | 4 |
| 🔀 Redirected | 27 |
| 👻 Excluded | 886 |
| ❓ Unknown | 0 |
| 🚫 Errors | 7 |
| ⛔ Unsupported | 0 |

## Errors per input

### Errors in .github/skills/dev/testing/write-unit-test/SKILL.md

* [403] <https://medium.com/@kentbeck_7670/programmer-test-principles-d01c064d7934> (at 41:1) | Rejected status code: 403 Forbidden
* [403] <https://medium.com/@kentbeck_7670/test-desiderata-94150638a4b3> (at 40:1) | Rejected status code: 403 Forbidden

### Errors in console/tracker-client/README.md

* [ERROR] <https://www.fsf.org/> (at 190:162) | Network error: received fatal alert: HandshakeFailure

### Errors in docs/containers.md

* [403] <https://stackoverflow.com/a/56768087/3012842> (at 571:5) | Rejected status code: 403 Forbidden

### Errors in packages/rest-api-client/README.md

* [ERROR] <https://www.fsf.org/> (at 9:162) | Error (cached)

### Errors in packages/tracker-client/README.md

* [ERROR] <https://www.fsf.org/> (at 11:162) | Error (cached)

### Errors in README.md

* [ERROR] <https://www.fsf.org/> (at 226:162) | Error (cached)

## Timeouts per input

### Timeouts in console/tracker-client/README.md

* [TIMEOUT] <https://www.gnu.org/licenses/> (at 194:114) | Request timed out

### Timeouts in packages/rest-api-client/README.md

* [TIMEOUT] <https://www.gnu.org/licenses/> (at 13:114) | Request timed out

### Timeouts in packages/tracker-client/README.md

* [TIMEOUT] <https://www.gnu.org/licenses/> (at 15:114) | Request timed out

### Timeouts in README.md

* [TIMEOUT] <https://www.gnu.org/licenses/> (at 230:114) | Request timed out
```
Loading
Loading