The human asked for YAML site config alongside the -base flag, which is the
trigger the parked settings cascade was waiting for — but only for its top level.
So this adopts the site level and leaves section and bundle resolution parked,
with their trigger unchanged: the first section-level override with a reader.
The parked idea now says so, rather than reading as untouched.
Absolute URLs are the forcing function. A canonical link, an hreflang, an
OpenGraph tag and a sitemap entry all need the site's own origin, which the engine
cannot infer from a request it may be serving behind any proxy.
Precedence stated in the ADR: -base beats the file, so a staging host needs no
edit to content.
The human chose non-recursive includes over textual splicing, and this amends the
phase table that promised otherwise: includes turn out to be parse-phase, not
load-phase.
The reason is in the ADR rather than lost in a commit: splicing an included file's
AST into the page is invalid, not just buggy, because goldmark nodes hold offsets
into their own source. Converting separately is what remains, and once conversion
is separate, nesting costs a read and a parse per level with a crash at the end of
any cycle. Forbidding nesting removes that failure mode rather than bounding it.
Names the declined option and its trigger, so textual splicing can arrive with the
Stage pipeline's load phase if composing partials ever becomes a real need.
gallery is the second fragment and needs a list of filenames, which the current
map[string]string cannot carry. The theme contract says fields are added but never
renamed, so widening the shape costs one commit today and a contract version once
a theme exists — this is the last cheap moment.
Naming the argument map also stops arguments and gathered data colliding: a call
with a src argument beside a feature-supplied src would otherwise silently pick
one. figure becomes .Args.src.
Two things already recorded settle the shape. The theme contract says the engine
decides nothing about how content looks, "including how media is embedded", so a
<figure> assembled in Go would be the engine dressing content. And invariant 2
wants the trusted/untrusted split as real code, not goldmark's default.
Both are satisfied by the same design: parse the shortcode into an AST node, then
render it by executing a theme template of that name. Raw HTML stays disabled, so
every byte of HTML on a page came from a template the site owns, and an author's
text survives only as arguments that html/template escapes.
Recorded before the code because the syntax authors type is a disk contract, and
because a feature that cannot render itself needs a partial-rendering function
passed in at wiring time — a consequence worth agreeing to in advance.
Confirms ADR-0009's "same path" clause against the alternative that was on the
table: a Bengali variant at its own Bengali address. Chosen: one path per bundle,
prefixed per language, so identity stays stable across translations (invariant 3)
and the resolver keeps one lookup direction.
The declined option is named in the ADR so it is not re-proposed as new. Cost is
stated too — a Bengali reader sees a Latin address, and reversing this later needs
an alias for every published path.
No code moves: `slug` is still unread. Recorded now because nothing is published
yet, which is the only cheap moment a permalink decision has.
Draws the line by who wrote the words. Labels, counts, month names and digits
come from an engine table keyed by (key, language) so no template hardcodes
English; authored prose is never localised and never rewritten beyond goldmark's
typographer. Machine-readable output stays ASCII in every locale.
Decided before the code, because the alternative — localising body text — is the
kind of choice that is expensive to walk back once published.
Answers the question that blocked sequences. A chapter belongs to the series it
is nested under, so the engine reads no `series` field; `order` stays, optional,
and a member without one sorts by name after every member carrying one.
This supersedes ADR-0016's membership clause and keeps its position rules — the
point of that ADR was that position never reaches a URL, which still holds. The
directory already states membership unambiguously; a second statement in
frontmatter can only agree or be a typo that silently orphans a chapter.
Cost, stated in the ADR: a series can never span directories, and membership now
rides on the bundle key, which ADR-0008 makes permanent.
The queue said to re-adopt the taxonomy decision when tags landed and I did not,
so ideas/deferred-decisions.md claimed no tag pages existed while /tags/ had been
serving for two commits. ADR-0032 records what the code actually does; only the
feed half stays deferred, and it lands with declared types since feed membership is
part of a type declaration.
Folded from a separate state commit:
state: bump verified-against; narrow the declared-types trigger
Sequences do not need a type declaration — membership and ordering come from
frontmatter — so the trigger for declared types is feeds or check, whichever lands
first.
Bundle loading with no HTTP: walk content/, split YAML frontmatter, derive an
NFC-normalised key and a language from the filename, and lift only title out of
frontmatter so every other key stays readable through Extra (ADR-0002).
Path safety is os.Root rather than a hand-rolled cleaner (ADR-0031). os.DirFS
documents that it does not prevent symlink escape; os.Root refuses any name
resolving outside the root, so the guard is a property of the type instead of a
check to remember at each call site. Test: a symlink to a file above the root
cannot be read. This clears the traversal item off the latent list.
A bundle that will not parse is logged and skipped, never fatal (ADR-0029), as
is a key claimed by two spellings of one variant (ADR-0021).
Bundle carries only Key, Lang, Path, Title, Body and Extra; Date, Slug, Draft
and Aliases arrive with the features that read them.
Naming is free before a module is published, a URL is shared, or a binary is
deployed; every day it waits costs more. Swept every form: module path, binary,
cmd/ directory, KHOSRA_SITE, the feature-loop skill directory, and the prose in
earlier ADRs — which describe this project under its old name, not a different
project. Recorded as ADR-0030.
go mod init lands here rather than with the first feature because the module
path is what the rename is about. x/text and yaml.v3 are required but not yet
imported, so both are indirect and no direct dependency is claimed yet.
Twenty-seven ADRs existed before a line of Go. Five specified the shape of
unbuilt mechanisms — cache validity, declared types, the settings cascade,
taxonomies, extras — which breaks the rule against abstraction before a
second concrete use, in prose where the counters cannot see it. They move to
ideas/deferred-decisions.md as recorded intent and return shaped by whatever
implements them.
Citations retargeted throughout; where one was decoration the rule now stands
on its own reasoning. Type declarations and the cascade drop to [spec] with
the MVP behaviour stated instead, so the first prompts have less to build.
conventions.md names http.ServeMux as the router, closing a hole that invited
hand-rolling a path splitter. The ADR gate now checks a number is registered
in the log rather than headed by an entry, so withdrawals resolve and invented
numbers still fail. Two architecture invariants corrected: identity no longer
implies a required language suffix, and the duplicated permalink clause is gone.