10. Descriptions
A description is a bare string in any body — module, surface, interface, view, process. Renderers treat description text as markdown with two extensions: cross-references ([[…]]) and value interpolation (@field / @@aspect). These appear everywhere descriptions are shown: hover tooltips, completion docs, side panels, widget bodies — and standalone .md files use the exact same dialect (Chapter 10a).
module Payments { aspect team: "Payments"
aspect { domain: "Payments" security.zone: "PCI" }
" # Payments
Core payment processor for the **@@domain** domain, operating in the *@@security.zone* zone. Published events are consumed by [[Orders]] and [[Notifications]].
> Every transaction in the platform flows through this service. "
interface authorize interface refund}The description renders with markdown formatting, with @@domain and @@security.zone substituted, and with [[Orders]] and [[Notifications]] rendered as clickable links to the respective modules.
Two modes — and don’t pad
Section titled “Two modes — and don’t pad”A description has two legitimate shapes; pick whichever fits the element.
Mode 1 — a short one-liner. Say what the name alone doesn’t, and stop.
Mode 2 — a longer doc. Full Markdown: internals, an API overview, design notes — a real document if the element warrants one.
What’s not legitimate is restating the obvious. Don’t echo what the name, the nested modules, the interfaces, or the processes already say.
Rule. A redundant description is worse than none. For a
PaymentProcessor, “Processes payments” is worse than an empty description — it adds length without adding information. No description at all is completely fine when there’s nothing non-obvious to add.
Structured links belong in fields, not prose
Section titled “Structured links belong in fields, not prose”A description is for prose. A link to an external spec or doc — a Confluence page, an OpenAPI document, a data catalog entry — is a property of the element, and properties go in fields (Chapter 9):
module Orders { repo.url: "https://github.com/acme/orders" docs.url: "https://wiki.acme.com/orders" api.spec: "https://specs.acme.com/orders/openapi.yaml"
"Order intake, validation, and fulfilment hand-off."}Putting these in fields keeps them structured and queryable — tooling can render them as buttons and the model becomes a links hub. A longer Markdown description may still carry the occasional inline link in its prose, but the canonical “where the spec lives” pointer is a field.
Markdown subset
Section titled “Markdown subset”These CommonMark constructs are supported:
| Construct | Example |
|---|---|
| Bold | **bold** |
| Italic | *italic* |
| Strikethrough | ~~struck~~ |
| Inline code | `code` |
| Headers | # H1, ## H2, ### H3 |
| Lists | ordered, unordered, nested |
| Tables | GFM-style pipe tables |
| Blockquotes | > note |
| Code blocks | triple-backtick fenced, optional language |
| Horizontal rule | --- |
| External links | [text](https://...) |
These are not supported and are either rendered as literal text or stripped:
- Raw HTML (security boundary — renderers sanitize aggressively).
- Images (descriptions are text; visuals belong in widgets and views).
- Footnotes, definition lists, task lists.
- Auto-linking of bare URLs (use explicit
[text](url)).
One header rank
Section titled “One header rank”Headers (#, ##, ###) all render at the same visual rank. Descriptions have one header level; depth carries no semantic weight. You’re free to use multiple # for source readability, but don’t rely on visual hierarchy between H1 and H3.
The reason: descriptions appear in tooltips, side panels, and other constrained contexts where reproducing a full document hierarchy looks wrong. Treat headers as section titles, not as nesting.
Cross-references: [[…]]
Section titled “Cross-references: [[…]]”[[…]] links to another declaration in the model. Two forms:
By stable ID:
"Routes to [[#r3n8wt]] for downstream processing."Always resolves if the target exists in the workspace. Renders as the target’s human-readable name, linked to its declaration site.
By human-readable name:
"Published events are consumed by [[Orders]] and [[Notifications]]."Resolves against the whole workspace. If two declarations share the name, the reference is ambiguous — the renderer marks it as an error and the LSP emits a diagnostic listing the candidates. Resolve ambiguity by switching to the ID form.
Names can be namespaced; the namespaced form is matched whole:
"See [[Personal.Banking.Payments]] for the legacy path."References that fail to resolve render as error markers and emit LSP warnings — typos surface immediately rather than rotting silently.
Aspect and section references
Section titled “Aspect and section references”Inside the brackets, a leading @@ marks an aspect key — a plane (Chapter 9) — rather than an element name:
"Runs in [[@@security-zone:pci]]; part of the [[@@domain]] plane."[[@@key]]references the whole plane named by the aspect key (.separates key-path segments).[[@@key:value]]references a specific place on it — the aspect — where:always introduces the value.
These resolve to the set of elements carrying that aspect and link to the aspect overlay, not to one declaration.
A [[Doc#section]] reference points at a #-header inside an .md document (Chapter 10a); bare [[Doc]] links to the document as a whole.
Reading an aspect off another node: [[ref]]@@path
Section titled “Reading an aspect off another node: [[ref]]@@path”[[ref]]@@aspectPath is a cross-reference composed with the value getter below: it reads an aspect on the referenced node instead of the owning one.
"Part of the [[#pay001]]@@domain domain." // reads the domain aspect off PaymentsThere’s no bracketed [[Name@field]] form — foreign value access is always the composed [[ref]]@@aspectPath, and because it carries its own context it works everywhere, including standalone .md files.
Backlinks
Section titled “Backlinks”Every element keeps an automatic “mentioned in” index of all [[…]] references pointing at it, across descriptions and .md documents. Tooling surfaces it on the element, so you can jump from a declaration to everywhere it’s discussed.
Value interpolation: @field / @@aspect
Section titled “Value interpolation: @field / @@aspect”A getter substitutes a value into the description of the same node it’s attached to. The sigil names the axis: @path reads a field, @@path reads an aspect — mirroring the key: value fields and aspect { } blocks the paths are drawn from (Chapter 9).
module Payments { version: "1.2"
aspect { domain: "Payments" sla.tier: "gold" }
"Version @version. SLA tier: @@sla.tier. Operates in the @@domain domain."}Renders as something like: “Version 1.2. SLA tier: gold. Operates in the Payments domain.” — version is a field, read with one @; sla.tier and domain are aspects, read with @@.
Resolution walks the type chain — if the node doesn’t declare the field/aspect itself but inherits it from a parent type, the inherited value is used.
Scope is local
Section titled “Scope is local”@@ reads aspects on the owning node, never on referenced nodes (the same locality holds for a bare @ field read):
module A { aspect { domain: "Sales" } "Domain: @@domain" // resolves to "Sales"}
module B { aspect { domain: "Ops" } "Other module's domain: @@domain" // resolves to "Ops" (B's own aspect), // NOT A's domain}A bare @@ on module A cannot read aspects on module B — for that, use the foreign getter [[B]]@@domain (above). And because a bare getter needs an owning node, it is description-only: in a standalone .md document (which has no owning node) a bare @ or @@ is an error, so documents use [[ref]]@@aspectPath exclusively (Chapter 10a).
Missing aspects surface
Section titled “Missing aspects surface”If @@aspectPath doesn’t resolve, the renderer outputs the literal sentinel <missing:aspectPath> and the LSP emits a warning at the interpolation site. Silent fallback to empty string is forbidden — typos and stale references have to be visible. (The same rule holds for a missing @field.)
module C { "Owner: @@team" // no 'team' aspect declared or inherited}Renders as: “Owner: <missing:team>” with an LSP warning.
Multiple descriptions concatenate
Section titled “Multiple descriptions concatenate”A body may have multiple bare strings. They join with \n in declaration order:
module Payments { "First paragraph about the service."
aspect team: "Payments"
"Second paragraph, declared after the team aspect. Order in the source doesn't matter for resolution but does matter for description joining."
interface authorize}This lets long descriptions span multiple string literals without forcing one giant multi-line string.
Worked example
Section titled “Worked example”A real description from the Payments demo:
module #k7m2qx Payments { aspect team: "Payments" docs.runbook: "https://wiki.acme.com/pci"
aspect { domain: "Payments" security.zone: "PCI" criticality: "High" }
" Core **payment processing** service for the *@@domain* domain. Operates in the `@@security.zone` zone with criticality *@@criticality*.
> Every transaction in the platform flows through this service > before reaching an external processor.
**Capabilities.** Authorize, capture, and refund transactions; route to processors via [[#r3n8wt]]; publish `paymentEvents` consumed by [[Orders]] and [[Notifications]].
**Compliance.** PCI-DSS scope. "
interface authorize interface refund interface paymentEvents}That renders as a full markdown card with aspect substitutions and live cross-references to #r3n8wt, Orders, and Notifications. Note the runbook URL is a field (docs.runbook), not a link buried in the prose — structured links stay structured.
Where the resolver lives
Section titled “Where the resolver lives”The cross-reference resolver and aspect interpolator are part of the core package — not LSP-only. Both the LSP (for diagnostics and hover) and client-only viewers (for rendering) consume the same resolver. The result: descriptions look identical wherever they’re displayed.
Summary
Section titled “Summary”- Descriptions are bare strings, treated as markdown with two ArchLang extensions.
[[#id]]and[[Name]]link to declarations;[[@@key:value]]links to an aspect,[[Doc#section]]to a document heading.@fieldsubstitutes a field,@@aspectsubstitutes an aspect — both on the same node (description-only);[[ref]]@field/[[ref]]@@aspectreads a value off another node and works everywhere.- Every element gets automatic backlinks (“mentioned in”) spanning descriptions and
.mdfiles. - Headers all render at one rank; raw HTML and images are not supported.
- Missing references and missing aspect paths surface as errors, never silently.
- Multiple bare-string descriptions concatenate.
- The same dialect renders standalone
.mddocuments — a first-class archspace surface (Chapter 10a).
What’s next
Section titled “What’s next”Chapter 10a: Markdown Documents → — .md files as a first-class archspace surface, sharing this exact flavored-markdown dialect.