Narrow immutability relaxation for broken external references in immutable ADRs (local-filesystem paths + aspirational upstream refs)
ADR-068 — Narrow immutability relaxation for broken external references in immutable ADRs
Status
Accepted (2026-05-19; second narrow clarification of the existing CLAUDE.md immutability rule — co-equal with ADR-067; covers a different class of factual defects).
§A Context — discovery from the first end-to-end lychee scan
The lychee link-check CI workflow was introduced at v1.1.4 (d3f63d8) but was non-functional from inception until v1.2.4 due to a chain of v0.23.0 compatibility issues (--exclude-mail removed; --base . rejected). The v1.2.3 patch fixed --exclude-mail; the v1.2.4 patch fixed --base. Only on the v1.2.4 push (3c44032) did lychee execute end-to-end for the first time, surfacing 35 unique broken markdown links across 35+ files that had been masked for 5 patches.
Root-cause analysis (v1.2.5 /exploring-options walk, 2026-05-19) classified the failures into 7 patterns. Two of them — A.1 local-filesystem path leakage and A.2 aspirational upstream references — appear inside immutable ADRs (decisions/ADR-NNN-<slug>.md files) and are therefore protected by the CLAUDE.md immutability rule from in-place editing.
ADR-067 (v1.2.2) established a precedent narrow exception for factual typos in cross-reference slug filenames. The two newly-surfaced classes share the same audit rationale that motivated ADR-067:
- No decision content is affected. A local-filesystem path is a citation/reference, not a methodology decision. An aspirational upstream link is a forward-reference to planned docs, not a methodology decision.
- CI cannot resolve them.
file:///home/<author>/...and../../../.claude/...paths require the author’s own filesystem; they never resolve on any CI runner OR reviewer browser. Aspirationaleval-toolkit/blob/main/docs/methodology/*.mdURLs return 404 because those docs were planned but never created upstream. - Reader experience strictly improves with in-place fix. A reader clicking the broken link sees a 404. Replacing with descriptive prose preserves the citation intent + eliminates the 404.
- Audit trail is preserved. The broken-link CONTENT is removed but the surrounding prose + the decision that referenced it remains immutable. Future
git log decisions/ADR-NNN.mdshows the original commit + the v1.2.6 broken-ref-fix commit; both are recoverable.
Both classes meet the §B-style narrow-scope test that ADR-067 applies to its own exception.
§B Decision — narrow extension covering two additional classes
ADRs remain immutable for decision content (per CLAUDE.md + ADR-067 §B). ADR-067 covers the factual-typo-in-cross-reference-slug class. ADR-068 covers two additional narrow classes:
B1 — In-scope corrections (allowed in-place per this ADR-068)
Class X — Local-filesystem path references. Markdown links (and YAML frontmatter URL strings) where the URL targets a path on the author’s local filesystem:
file:///home/<author>/.../home/<author>/.../<anything>.md../../../.claude/projects/.../memory/<anything>.md- Any path resolving to a directory NOT present in the repo (verifiable via
[ -e <path> ]check at CI runner).
Fix discipline: replace the broken link with descriptive prose that preserves the citation intent. Example transformation: - Before: [memory/library_first_is_project_wide_invariant.md](../../../.claude/projects/.../memory/library_first_is_project_wide_invariant.md) - After: `memory/library_first_is_project_wide_invariant.md` (project-local Claude memory; not committed)
The reader sees the SAME citation text (which is informative on its own — they know what concept is being referenced) without the broken-link target.
Class Y — Aspirational upstream references. Markdown links pointing at upstream resources that do NOT exist on the upstream repo:
https://github.com/<owner>/<repo>/blob/<branch>/<path>where the path returns 404 from the upstream repo’s API.- Includes the 9 references to
github.com/brandon-behring/eval-toolkit/blob/main/docs/methodology/{bootstrap,calibration,comparison,leakage,reproducibility,splits,text_dedup,thresholds}.md— verified non-existent viagh api repos/brandon-behring/eval-toolkit/contents/docs/methodologyreturns 404.
Fix discipline: replace the broken link with either (a) a descriptive prose statement citing the actual upstream primitive (e.g., eval_toolkit.bootstrap_ci from the eval-toolkit README) OR (b) the closest existing upstream URL (e.g., the repo’s top-level README or the relevant source file). Preserves citation intent without retaining the broken link target.
B2 — Out-of-scope changes (REQUIRE superseding ADR per existing rule)
Same as ADR-067 §B2; numeric values, methodology decisions, prose rationale, alternatives considered, non-slug frontmatter fields, and table content all remain immutable. ADR-068 covers ONLY the link-target text in broken external references; the surrounding sentence + paragraph + decision content stays as written.
Important: ADR-068 applies ONLY to IMMUTABLE ADRs. Mutable files (SPEC_SHEET.md, SPEC_GREENFIELD.md, WRITEUP/*.md, decisions/library_imports.md, CHANGELOG.md, NEXT_STEPS.md, README.md, etc.) can be edited freely per their existing mutability convention and do NOT need ADR-068 cover.
B3 — Commit message convention
In-place broken-external-reference fixes in immutable ADRs carry a commit message of the form:
docs: ADR-NNN broken-external-reference fixes per ADR-068
Per ADR-068 narrow-relaxation rule (Class X / Class Y): broken external
references to local-filesystem paths or aspirational upstream resources
MAY be corrected in-place in immutable ADRs. This commit fixes:
- decisions/ADR-XXX.md:LINE — Class X local-fs path `<path>` → descriptive prose `<replacement>`
- decisions/ADR-YYY.md:LINE — Class Y aspirational upstream `<url>` → descriptive prose `<replacement>`
...
No decision content changed. ADR audit trail preserved.
B4 — Future PR review discipline (unchanged from ADR-067)
In-place ADR edits that go beyond the narrow scopes (ADR-067 slug typos + ADR-068 broken external refs) should be flagged by PR review + require a new superseding ADR per the existing rule. ADR-068 does NOT establish a slippery slope; it codifies a specific narrow class adjacent to ADR-067.
§C Decision — apply in the v1.2.6 fix-forward (consolidated inventory)
C1 — Immutable-ADR markdown links covered by ADR-068 (3 fixes)
| File:line | Class | Broken link | Replacement |
|---|---|---|---|
decisions/ADR-065-writeup-accuracy-narrative-and-callout-conventions.md:108 |
X | [memory/adr-029-immutability-misattribution-historical-drift.md](../../../.claude/projects/.../memory/adr-029-immutability-misattribution-historical-drift.md) |
`memory/adr-029-immutability-misattribution-historical-drift.md` (project-local Claude memory; not committed) |
decisions/ADR-065-writeup-accuracy-narrative-and-callout-conventions.md:122 |
X | [memory/library_first_is_project_wide_invariant.md](../../../.claude/projects/.../memory/library_first_is_project_wide_invariant.md) |
`memory/library_first_is_project_wide_invariant.md` (project-local Claude memory; not committed) |
decisions/ADR-025-dual-policy-threshold-characterization.md:13 (YAML frontmatter references: list-item) |
Y | https://github.com/brandon-behring/eval-toolkit/blob/main/docs/methodology/thresholds.md |
Replace with eval_toolkit.thresholds (the actual upstream primitive surface), OR closest-existing upstream URL: https://github.com/brandon-behring/eval-toolkit#readme. Choose the README pointer for ADR-025 line 13 since it’s the references-list field. |
C2 — Mutable files (NOT covered by ADR-068; fixed separately in the v1.2.6 fix-forward)
For completeness — mutable files have similar fixes applied without ADR-068 coverage:
SPEC_SHEET.mdlines 313, 314, 316, 335: 4 aspirational eval-toolkit/methodology refsSPEC_GREENFIELD.mdlines 82, 98, 111, 167, 172, 174, 203: 7 aspirational refsWRITEUP/data-decisions.mdlines 96, 126, 134: 3 aspirational refsWRITEUP/threshold-policy.mdline 56: 1 aspirational refdecisions/library_imports.mdline 182: 1 local-fs path ref
These edits don’t need ADR-068 cover; the mutability convention covers them.
§D Decision — CLAUDE.md + decisions/README.md updates
Both documents get the dual-narrow-exception update (cites both ADR-067 + ADR-068):
CLAUDE.md §“Phase 0 workflow” (after the existing immutability line; replacing the prior ADR-067-only narrow-exception clause):
The ADR is the source of truth. ADRs are immutable; supersede via new ADR marking prior
status: superseded-by-NNN.Narrow exceptions (per ADR-067 + ADR-068): TWO factual-defect classes MAY be corrected in-place with a commit message citing the relevant ADR + listing per-file corrections:
- Cross-reference slug filename typos (per ADR-067) — a slug pointing at a wrong-but-existing file in
decisions/.- Broken external references (per ADR-068) — markdown links pointing at local-filesystem paths or aspirational upstream resources that do not exist.
ALL other content (numeric values, methodology, prose, alternatives, non-slug frontmatter, table data) remains immutable per the existing rule.
decisions/README.md §Lifecycle (similar dual-citation update).
§E Consequences
E1 — Reader experience improved (immutable-ADR class)
After the v1.2.6 fix-forward, readers reading ADR-025 / ADR-065 see descriptive prose where they previously saw broken markdown links. No more 404s on CI runners. No more misleading citation targets for non-author readers.
E2 — Audit-trail discipline preserved
The 3 immutable-ADR fixes do NOT change decision content; ADR audit trail remains intact. Per-file git log shows the original commit + the v1.2.6 broken-ref-fix commit; both are recoverable. The narrow scope (§B2 out-of-scope) prevents slippery-slope drift to decision-content edits.
E3 — Documentation debt paid down (v1.1.4 → v1.2.6 trail)
The lychee link-check CI was introduced at v1.1.4 as an “URL rot defense” but was non-functional from inception. v1.2.3 + v1.2.4 fixed the workflow’s v0.23.0 compat issues; v1.2.6 closes the surfaced content debt (35 unique broken URLs across 7 root-cause classes). The lychee CI now runs end-to-end + actually catches link rot going forward.
E4 — Two-ADR symmetry
ADR-067 and ADR-068 are co-equal narrow exceptions to the same CLAUDE.md immutability rule: - ADR-067: slug typos in cross-references (wrong-but-existing target) - ADR-068: broken external refs (target doesn’t exist anywhere)
Both follow the same §B-discipline: in-scope corrections + out-of-scope changes + commit-message-convention + future-PR-review-discipline.
E5 — Future PRs flagged on out-of-scope edits
If a future patch attempts to edit decision content in-place under the guise of ADR-067 or ADR-068, PR review should flag the diff + require a superseding ADR. The narrow-relaxation rules are NOT slippery slopes; they codify specific, well-defined factual-defect classes.
E6 — Cost-trivial
$0. CPU-only edits across 3 immutable-ADR lines (ADR-025 line 13 + ADR-065 lines 108, 122) + the CLAUDE.md / decisions/README.md / GLOSSARY clarification updates.
Linked ADRs
- References:
- CLAUDE.md — the source-of-truth for immutability (clarified by ADR-067 + ADR-068 dual-narrow-exception)
- decisions/README.md — the ADR lifecycle + frontmatter schema (also gets the dual-citation update)
- ADR-067 — precedent narrow relaxation for slug typos; ADR-068 extends with two additional classes
- Source: v1.2.6 link-check fix-forward; root-cause analysis began during the v1.2.5 /exploring-options 4-question walk (2026-05-19), where the user picked “Extend ADR-067 narrow relaxation → ADR-068 + in-place fix (Recommended)” on Question 1 + “Full structural fix — ADR-068 + DOI canon + triage (Recommended)” on Question 4.
Transcript
transcripts/2026-05-19__v1-2-5-link-check-content-debt-and-immutability-extension.md — captures the multi-class link-check content debt close + the ADR-068 narrow relaxation extension.