# 0006 · Inline markdown links with relative `.md` hrefs

**Status:** Accepted · 2026-09-22

## Context

Documents reference each other, and those references form the graph that drives share suggestions
and backlinks. The href format determines whether an export is useful, what a rename costs, and
whether a person can write a link without looking something up.

## Decision

Ordinary inline markdown, with relative or root-relative `.md` paths:

```markdown
See the [onboarding guide](../onboarding.md).
The [deploy runbook](/ops/deploy.md) covers rollback.
```

Edges are stored with **both** the href as written and the resolved `target_id`
([linking.md](https://codna.me/docs/linking.md)). Renames never break the graph; only the written text goes stale, and
staleness is surfaced as a decision rather than repaired automatically.

## Consequences

- An export is a working file tree. It opens in Obsidian, browses on GitHub, renders anywhere, with
  every link resolving. This is what makes "easily exportable" mean something.
- Pasting in content from an existing docs repository keeps its links working.
- Links are extracted from the goldmark AST, not a regex — a regex over prose cannot distinguish a
  link in a paragraph from one inside a fenced code block.
- **A rename leaves stale text in the bodies that point at it.** Deliberate: auto-rewriting would
  manufacture a revision on every linking document, attributed to whoever did the rename.
- A link to a document that does not exist yet is `pending` and resolves itself when one appears.

## Alternatives rejected

**`[[wikilinks]]`.** Familiar, terse, and unambiguous about intent. Rejected because they render as
literal text everywhere outside a wiki-aware editor — an export full of `[[foo]]` is not a working
document tree.

**`codna://doc/<uuid>` URIs.** Never stale, nothing to reconcile. Rejected because raw markdown
becomes unreadable, exports do not work in any external viewer, and writing a link by hand requires
looking up a UUID first.

**App-absolute paths (`/wiki/engineering/onboarding`).** Reads well and works as a real URL in the
UI. Rejected because an export is then not self-contained — every link points at a running instance,
which is exactly what export exists to survive.
