Context
examples/ holds seven complete records. They were excluded from this
project’s site (site.exclude = ["examples/**"]), so the worked configurations
this repository argues from were the one part of it a reader could not browse.
Each could be staged on its own — LURIA_ROOT=$PWD luria site — but nothing
published them, and the exclusion read as though it could not be done.
The obvious repair, dropping the exclusion, is wrong, and the reason is the
rule publishable() turns on:
A source that renders into a view is not published; the view is.
That test is Config.link_base(path) != path.parent, and it answers from the
reading config’s schemes. Under this project’s config an example’s
VALUE-001.md has no scheme to belong to, so link_base falls back to the
file’s own directory and the file reads as ordinary prose. The parent would
publish the fragment and docs/values.md, the view it renders into — and the
views are not committed, so what it published would be whatever a contributor’s
working tree happened to hold.
Measured before the change, under the root config:
examples/constitution/record/values.d/VALUE-001.md
link_base=…/record/values.d own_dir=…/record/values.d PUBLISHED IN PLACE
Decision
A new key, site.include_records, listing directory globs. Each match that
holds its own luria.toml is staged by that config into a temporary vault,
and only the finished content/ is mounted under the parent’s, at the child’s
own path.
Three properties follow, and each is a deliberate choice rather than a consequence:
The parent skips a nested record’s files entirely. include_records
implies exclusion from the parent’s own pass — otherwise the merge reintroduces
exactly the problem above. Naming the same directory in exclude is
unnecessary and would be a second place to keep in step.
The child is generated in a copy, never in place. A nested record’s views are not committed, so they must be rendered before they can be staged, and rendering them into the working tree would create the stale committed view the arrangement exists to avoid.
One level only. A child’s own include_records is not honoured. One level
is a section; two is a maze, and nothing needs it. Stated in the code because
the absent recursion would otherwise look like an oversight.
Switching the reading config is done through config.rooted(), a bounded
context manager that restores the previous LURIA_ROOT including its absence.
load(root) alone is not enough: the modules underneath — doc_refs.link_base,
edges.graph, the renderers — call current() for themselves by design, and
threading a config through every call site would be its own mess. The honest
move is to name the global switch and bound it, not to have callers set an
environment variable and hope.
Consequences
The seven examples publish as sections of this site, 103 pages growing to 206. Each keeps its own title, theme derivation, and record lines, because each was staged by the config that knows about it.
A pattern matching no record is an error rather than a silent skip: an
empty include is a section of the site that quietly does not exist, which is
the failure mode nothing reports. A pattern matching a directory that is not a
record is skipped quietly, because examples/* is the natural way to write
“every example” and a stray file beside them should not break a publish.
The alternative considered and rejected was a separate Quartz build per record,
nested into the output directory. It gives each example a genuinely independent
site, at the cost of N more builds, N base_url values to keep correct,
and — decisively — no shared link graph: a link from examples/README into an
example would be a raw URL the generator knows nothing about, and the search
index and graph view would stop at each boundary. The examples exist to be read
from the documentation that cites them, so one vault is the shape that serves
the reader.
This also means an adopter can publish a monorepo’s several records as one site, which is the general case this repository happened to hit first.
ADR-045’s consequence — “examples/** joins template/** in the site’s
exclusions so a sample record is not published beside the real one” — is no
longer true. Its body stands as written, which is what a record is for, and
this decision is where a reader learns the state changed: the examples are
published, under their own configs, precisely so a sample record is not
rendered as if it were part of this one. The other half of that paragraph, the
two ADR-scheme limits it documented rather than resolved, was already
overtaken by ADR-047.