travismetcalf.com · patterns

Decisions,
with dates.

The rules this platform is built by — each one a real decision made once, applied everywhere, and written down with its reasoning. An undisturbed date is a decision that has held; when one changes it is superseded in place with a successor, never silently rewritten. This is the public edition: patterns as ideas. The full operating reference — hostnames, ports, runbooks, the gotchas — lives behind SSO.

All decisions reviewed & current as of 2026-08-04

Security

The edge

Pattern · edge architecture · 2026-06-16 · accepted

Defense in layers, and the gate runs first

Public traffic reaches the lab through a managed CDN + WAF (AWS CloudFront), then an encrypted tunnel (Tailscale) to a reverse proxy (Caddy), with single sign-on + MFA (authentik) in front of every app. Two rules make the chain honest:

  • The CDN cannot be bypassed. The origin authenticates the CDN with a shared secret and refuses everything else — evaluated before any routing logic, fail-closed, so the WAF is load-bearing rather than decorative.
  • Public and LAN listeners are physically separate. A spoofed Host header on the public path can only ever reach the gated listener. This closed a real bypass found by testing the assumption, not trusting it.

Pattern · trust surfaces · 2026-08-04 · accepted

LAN reads are free; writes belong to the SSO surface

Convenience surfaces on the home network are read-only by design — mutating routes answer with a redirect to the authenticated public URL. "Ungated reads" must never quietly become "ungated writes."

Operations

Every project, the same shape

Pattern · project template · 2026-07-12 · accepted

A new service isn't done until it can prove it's alive

Every project ships the same skeleton: a git repo with an .env.example, containerized runtime, a scheduled smoke test that exercises real behavior, a success-marker the monitoring can check, and email alerts through one shared notifier library. A weekly summary job re-runs every project's checks and sends a single heartbeat.

Pattern · monitoring · 2026-07-16 · accepted

Four questions, not one: alive, correct, drifted, wasteful

Liveness checks miss whole failure classes. The fleet is watched on four axes: is it running; is its output right; does the deployed state match git and the pinned images; and is a healthy service quietly burning resources. That last axis exists because a container once burned two cores for five days while every green check stayed green — measured properly, lifetime-average CPU separates "busy" from "stuck" where a snapshot is a coin flip.

Pattern · deploys · 2026-07-12 · accepted

Git is the transport; the checkout is the artifact

Edit on the laptop, push, pull on the server, restart if needed. No CI pipeline to babysit for a one-person platform — but drift checks verify the deployed state matches the remote, so the simple path stays honest. Static sites (including this one) deploy by git pull alone.

Pattern · backups · 2026-07 · accepted

Code lives on GitHub; only runtime data is backed up

Nightly snapshots of runtime data to network storage with 30-day retention — and restore tests, because a backup that's never been restored is a hope, not a backup.

AI

A force multiplier, inside guardrails

Pattern · AI leverage · 2026-08-04 · accepted

AI does real work here — under the same rules as everyone else

AI isn't a demo on this platform; it's on the payroll. Models score the intelligence reader's stories in production, local models handle what should never leave the house, and an AI engineering partner helps build and operate the platform itself — including this site.

The leverage is only safe because the guardrails don't care who's working: every agent spends from its own budgeted key at the gateway, every change lands through git and the same drift checks, and nothing AI-made skips the smoke tests. Trust the output because the system verifies it, not because the model sounded confident.

Design & frontend

One language, many runtimes

Pattern · design system · 2026-07-12 · accepted

Tokens once, adapters everywhere

Brand values live in one namespaced CSS custom-property file; each app maps them onto its own variables through a thin adapter and vendors a copy at deploy time. Plain CSS custom properties are the only layer low enough to skin a React app, server-rendered Python templates, a themed third-party app, and (via generated constants) a native iOS build from the same source. The styleguide renders from the live values, so it cannot drift.

ADR · 2026-08-04 · accepted

Frontend defaults: static first, then Next.js + TypeScript

Content and document sites are static HTML consuming the tokens — no build step, nothing to operate for a decade. Interactive app frontends default to Next.js + TypeScript + Tailwind (v4's @theme maps the custom-property tokens cleanly). Existing apps are not retrofitted: operational simplicity outranks framework uniformity, and a read-mostly dashboard doesn't need a Node build chain.

Copied