<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Decisions · XB Field Notes</title><link>https://www.xbcatm.com/decisions/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/decisions/index.xml" rel="self" type="application/rss+xml"/><item><title>The site is served as a static tree, not by a dynamic application server</title><link>https://www.xbcatm.com/decisions/adr-static-not-dynamic-server/</link><pubDate>Tue, 25 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/decisions/adr-static-not-dynamic-server/</guid><description>Decision record documenting the choice to serve a built static tree behind Caddy instead of a dynamic application, with the trade-offs accepted.</description><content:encoded>&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;The site is documentation and operations content: pages to be read, searched, and archived. Early brainstorming considered a dynamic application server to render templates, manage sessions, or serve interactive components. The content itself imposes no requirement for server-side state, authenticated per-user logic, or client-side rendering.&lt;/p&gt;
&lt;h2 id="decision"&gt;Decision&lt;/h2&gt;
&lt;p&gt;Serve the site as a pre-built static tree from Caddy, a single directory that is swapped as a whole on release. No application server, scripting lane, or database lives behind the edge.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Caddy (public edge) ──HTTPS──► static/ tree (assets, HTML, SVGs)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The served contract is enforced by headers (&lt;code&gt;script-src 'none'&lt;/code&gt;) and by build-time checks that reject scripts and broken artifacts.&lt;/p&gt;
&lt;h2 id="consequences"&gt;Consequences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Releases are boring:&lt;/strong&gt; publishing is a complete-directory swap, so there is no state migration and no schema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The threat surface is narrow:&lt;/strong&gt; no runtime executes user input, closing whole injection classes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching is honest:&lt;/strong&gt; every file has a stable byte identity and a validator.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trade-offs accepted:&lt;/strong&gt; no interactivity, no authenticated personalization, no per-user HTML. Where that is genuinely needed, it would compose behind a separate service rather than be folded into this tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-this-beats-the-alternative"&gt;Why this beats the alternative&lt;/h2&gt;
&lt;p&gt;A dynamic tier adds a runtime, a state boundary, and a slower path to edit. For content whose value is in the document itself, that cost buys capabilities the content does not use. Static serving gives away nothing the site needs and removes the parts that would only complicate releases and the security boundary.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/caddyfile/directives/file_server"&gt;Caddy documentation: &lt;code&gt;file_server&lt;/code&gt;&lt;/a&gt;, serving a static root without an application tier, checked 2026-08-25.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy"&gt;MDN: Content Security Policy&lt;/a&gt;, restricting what the edge will execute, checked 2026-08-25.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Pin the vendored theme and the generator version</title><link>https://www.xbcatm.com/decisions/adr-pin-vendored-theme-and-generator/</link><pubDate>Fri, 21 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/decisions/adr-pin-vendored-theme-and-generator/</guid><description>Decision record documenting the choice to vendor the theme and pin the Hugo release so a rebuild reproduces the same output.</description><content:encoded>&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;The site is built with a Hugo generator and a PaperMod theme. Untracked upgrades of either can change output silently: the theme&amp;rsquo;s templates, the generator&amp;rsquo;s rendering rules, or both can alter the HTML bytes between two &amp;ldquo;identical&amp;rdquo; rebuilds. When a release is proven by a byte manifest, reproducibility is not a nice-to-have; two builds of the same source must produce the same tree.&lt;/p&gt;
&lt;h2 id="decision"&gt;Decision&lt;/h2&gt;
&lt;p&gt;Vendor the theme into the repository and record the exact generator version and theme version in a pin file, &lt;code&gt;data/upstream-pins.toml&lt;/code&gt;. The build pipeline reads that file and fails if the installed generator does not match the pinned version.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Hugo 0.165.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PaperMod v8.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;data/upstream-pins.toml (pinned)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Themes that are vendored stay under &lt;code&gt;themes/&lt;/code&gt; in the tree so they are part of the commit and therefore part of the byte identity of a release.&lt;/p&gt;
&lt;h2 id="consequences"&gt;Consequences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reproducible output:&lt;/strong&gt; the same source plus the pinned generator yields the same tree, so the manifest means something.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visible upgrades:&lt;/strong&gt; bumping the theme or generator is an explicit, reviewable change to the pin file, not an accidental drift.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditable provenance:&lt;/strong&gt; a reader of any release can see which generator and theme produced the bytes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trade-off accepted:&lt;/strong&gt; the repository carries the vendored theme and the pin bookkeeping; the cost is small against silent output drift.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-this-wins"&gt;Why this wins&lt;/h2&gt;
&lt;p&gt;A build whose inputs are not pinned can produce the right answer today and a different tree tomorrow, and neither the diff nor the manifest can tell you why. Pinning turns &amp;ldquo;the build changed&amp;rdquo; into &amp;ldquo;the pin changed&amp;rdquo;, which is a question a changelog can actually answer.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/getting-started/usage/"&gt;Hugo documentation: version&lt;/a&gt;, reporting and matching the generator release, checked 2026-08-21.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semver.org/"&gt;Semantic Versioning specification&lt;/a&gt;, why version boundaries make reproducibility meaningful, checked 2026-08-21.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Publish by exchanging complete directories, never file-by-file</title><link>https://www.xbcatm.com/decisions/adr-complete-directory-release/</link><pubDate>Thu, 20 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/decisions/adr-complete-directory-release/</guid><description>Decision record documenting releases as a whole-tree swap behind a single directory name, keeping the old tree available for rollback.</description><content:encoded>&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;Deploying by copying changed files over a live tree risks a window where visitors see a partial release: some files are new, others old, and associated resources disagree. A malformed single file can also corrupt an otherwise fine live site. Any release path that edits the served directory in place makes the &amp;ldquo;previous release&amp;rdquo; difficult to name and restore.&lt;/p&gt;
&lt;h2 id="decision"&gt;Decision&lt;/h2&gt;
&lt;p&gt;Publish by staging a complete new tree beside the live tree, then renaming the single directory Caddy serves to point at it. The old tree remains on disk under its prior name.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rsync -a public/ /srv/www/example-site.new/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mv /srv/www/example-site.new /srv/www/example-site
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because the serve root is one directory name, the swap is atomic from the client&amp;rsquo;s perspective: every request resolves to either the old complete tree or the new complete tree, never a mix.&lt;/p&gt;
&lt;h2 id="consequences"&gt;Consequences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No partial states:&lt;/strong&gt; a visitor always sees one coherent release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cheap rollback:&lt;/strong&gt; the old tree still exists; recovery is a reverse rename.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verifiable before swap:&lt;/strong&gt; the staged tree can be checked and hashed before it becomes the served one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage trade-off:&lt;/strong&gt; the server holds the current tree plus a staged tree; the extra disk is the price of atomicity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-this-beats-in-place-sync"&gt;Why this beats in-place sync&lt;/h2&gt;
&lt;p&gt;In-place sync promises to adjust a live tree, but it cannot answer the question &amp;ldquo;which bytes are being served right now&amp;rdquo; atomically, and it complicates rollback to &amp;ldquo;yesterday&amp;rdquo;. Complete-directory exchange answers the release question with one durable fact — the served directory name — and makes both forward and backward moves a single rename.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.martinfowler.com/bliki/BlueGreenDeployment.html"&gt;Martin Fowler: BlueGreenDeployment&lt;/a&gt;, complete-tree versus blue-green/staged exchange models, checked 2026-08-20.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pubs.opengroup.org/onlinepubs/9799919799/functions/rename.html"&gt;POSIX &lt;code&gt;rename()&lt;/code&gt;&lt;/a&gt;, atomic rename semantics underpinning the swap, checked 2026-08-20.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>