Page identity and aliases
A page’s URL changes when you move or rename it. Identity gives a page two
things that do not: a uid, minted once when the page is created, and an
optional alias – the human label the page is known by, such as a
document number. Both are claims: once minted, a claim belongs to that page
and is never reused.
Declare identity on a model
# models/decision.yaml uid: required: true # every decision page carries a uid alias: grammar: "d{number}" # the label's shape, in the filename-pattern language scope: global # or container: unique among siblings only renumber: retire # or never; there is no way to free a label allocation: local # how a new number is claimed fields: supersedes: type: reference model: decision # resolved against aliases and uids at index time
grammaruses the same segment language asfilename:patterns ({number},{alpha},{field:name}, literals). Current and retired aliases must match it.scopeis where uniqueness holds:globalacross the site, orcontaineramong pages sharing a parent URL.renumber: retirelets a page change its alias by keeping the old label in itsaliaseslist, where it stays claimed and still resolves.neverforbids the change. No label is ever freed, so an external reference to a retired label keeps working forever.allocation: localclaims the next free number with a create-only file write;accent validateis the gate that catches a race.allocation: refclaims a create-only refrefs/claims/<model>/<alias>locally and on the shared remote, so two writers who share the remote cannot claim one label: the second push is rejected and that writer takes the next label. It needs a repository with anoriginremote and a network at filing time, and there is deliberately no fallback tolocal.
Moves and old URLs
A model may declare grammar: path: the page’s URL is its alias. Moving
such a page through the admin, the CLI, or the API appends the old URL to
aliases automatically (renumber: never refuses the move instead), and
a retired URL keeps answering – accent serve redirects it to the page,
and accent build emits a redirect stub at the old URL, so a static host
needs nothing. The same applies to any retired alias written as a URL
path.
What a page carries
--- uid: 0192a6f4-3c2e-7d5b-9e1a-4b8c2d7f0a11 alias: d042 aliases: [d007] # retired; still claimed, still resolves title: Use the new queue supersedes: d019 ---
You never write uid by hand. Every creation path mints it through the
model when the model declares identity: accent new, the admin editor,
the content API, agents, and MCP. The docs-promotion tooling mints a fresh
uid for each copied page, because a copy is a new page, not the same
claim in two places.
Reference fields
A field of type: reference with a model: target holds an alias, a
retired alias, or a uid, and the content index resolves it to a page.
accent validate reports a reference that resolves to nothing, or to a
page of the wrong model, as an error. A reference written as
origin/alias names a page from another site; when it is not imported,
validate reports a warning, not an error. A reference field without a
model: target keeps its old behaviour: a plain string that is never
resolved.
Resolving in templates
identity_index() maps every claim – uid, current alias, retired alias –
to its page’s URL in one pass:
{% set ids = identity_index() %}
<a href="{{ ids["d042"] | url }}">Decision 42</a>
A current claim outranks a retired one, so a renumbered page’s old label links to wherever the label’s holder lives now.
What accent validate reports
| Finding | Severity |
|---|---|
Two pages claim the same uid | error |
The model requires a uid and the page has none | error |
| Two pages claim the same alias, current or retired, in one scope | error |
| An alias does not match the model’s grammar | error |
Retired aliases on a model declaring renumber: never | error |
| A reference that resolves to nothing, or to the wrong model | error |
An origin/alias reference to a page that is not imported | warning |
Versioned docs are handled: a fallback copy of a page shares its original’s source file, and claims are compared by source file, so a copy is never a duplicate claim.