diff --git a/.gitignore b/.gitignore
index f1199783c..729c6903b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,14 @@ home/dot_config/cliamp/cliamp.sock
home/dot_config/cliamp/cliamp.sock.pid
home/dot_config/cliamp/history.toml
home/dot_config/cliamp/resume.json
+
+# Font binaries never enter this repo (2026-09-17). The Anthropic faces are
+# unlicensed brand trade dress and live only on the NAS M.2; pkgs/fontbuilder
+# is the press and ships no bytes. nerd-font-patcher writes its output into the
+# CURRENT DIRECTORY under some flags (`--cell '?'` does not query, it patches),
+# which is how a 440 KB patched TTF once landed in this repo root.
+*.ttf
+*.otf
+*.woff
+*.woff2
+*.tar.zst
diff --git a/docs/fonts/README.md b/docs/fonts/README.md
new file mode 100644
index 000000000..b94a54fe6
--- /dev/null
+++ b/docs/fonts/README.md
@@ -0,0 +1,265 @@
+# The Anthropic font suite
+
+Pressed on 2026-09-17 by `pkgs/fontbuilder` from the brand faces captured in
+`~/colors/waves/capture`, stored as three tarballs on the fleet's NAS M.2, pinned by
+sha256, and installed on every host through `modules/common.nix`. No font binary is in
+this repository and none is ever downloaded.
+
+| page | what it holds |
+|---|---|
+| this file | what is installed, the standing rules, the daily-driver notes, how to check and how to revert |
+| [`anthropic-suite.md`](anthropic-suite.md) | the measurements: sources, defects, names, donors, ligature alignment, PUA, CSS, verification transcript |
+| [`nas-and-bootstrap.md`](nas-and-bootstrap.md) | the operations runbook: NAS layout, tarball rules, the seed refactor, the two-switch bootstrap, recovery |
+| [`../../pkgs/fontbuilder/README.md`](../../pkgs/fontbuilder/README.md) | the press manual: stages, forbidden flags, reproducibility, runtimes, exit codes |
+
+---
+
+## What is installed
+
+| package | family (fontconfig) | files | reaches disk via | consumer |
+|---|---|---|---|---|
+| `anthropic-mono-nerd` | `AnthropicMono Nerd Font Mono` | 12 statics `AnthropicMonoNerdFontMono-
+
ligatures against literal hyphens and equals
+x = 1 / x == 1 / x -= 1 / x --- 1 / --flag --> => != <= >= |> :: __
+box art
+┌─┬─┐ ├─┼─┤ └─┴─┘
+blocks and braille (U+2800 is the BLANK cell - a tofu here is the E6 bug)
+█▓▒░ ⣿⥿⠀⣀
+donor seam - Greek/Cyrillic/math beside Latin
+Latin Привет λ ∑ 𝔸 Latin
+weights x slopes
+Light Light Italic
+Regular Italic
+SemiBold SemiBold Italic
+ExtraBold ExtraBold Italic
diff --git a/pkgs/fontbuilder/tests/a12.py b/pkgs/fontbuilder/tests/a12.py
new file mode 100644
index 000000000..ac1356c04
--- /dev/null
+++ b/pkgs/fontbuilder/tests/a12.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+"""A12 - the Serif fvar-default move, as a TOLERANCE test over a GRID.
+
+ tests/a12.py
+
+v1's wording ("bbox-identical to the same instances taken from the untouched
+source") is FALSE and this file is the refutation, lifted from
+$P3/refute-C8/a12_grid.py. Moving an fvar default NECESSARILY re-rounds the
+base glyf outlines (432 of 747 glyphs rewritten) and rebases gvar (7,688 ->
+10,359 tuples). What is preserved is fidelity within a measured tolerance:
+
+ control point <= 2 units at 2000 upm (worst observed 1.5015)
+ RAW glyph bbox <= 1 unit (worst observed 0.5000)
+ advance <= 1 unit (worst exactly -1 u on 93
+ glyph-instances, concentrated at
+ wght 600)
+
+THREE IMPLEMENTATION RULES, EACH FROM A MEASURED FALSE FAILURE:
+
+ 1. The grid MUST include off-default opsz AND off-endpoint wght. v1's 3x2
+ grid is exactly why this was missed: at opsz=16 the raw bboxes ARE
+ identical at wght 300/400/800, so a literal v1 implementation PASSES while
+ 283 glyphs have moved. The grid here is wght {300,400,500,600,700,800} x
+ opsz {16,24,32,48} = 24 probes.
+
+ 2. NEVER compare `tuple(round(x) for x in bounds)`. Banker's rounding flips
+ at half-integer coordinates (77.5 vs 77.49683 -> 78 vs 77) and produced
+ 742 false failures. Compare the RAW floats against a tolerance.
+
+ 3. The advance tolerance is 1 unit, not 0.
+
+Also: do NOT assert the STAT ElidedFallbackNameID STRING against the source.
+S9 deliberately resets it from `Text Regular` to `Regular`. Assert only that
+it is non-zero. And compare fvar instances by RESOLVED STRING, never by
+nameID: S9 reassigns nameIDs (unshare -> re-point -> strip -> gc).
+"""
+from __future__ import annotations
+
+import argparse
+import os
+import sys
+
+from fontTools.pens.boundsPen import BoundsPen
+from fontTools.pens.recordingPen import DecomposingRecordingPen
+from fontTools.ttLib import TTFont
+from fontTools.varLib.instancer import instantiateVariableFont as inst
+
+PT_TOL, BB_TOL, ADV_TOL = 2.0, 1.0, 1
+WGHT = (300, 400, 500, 600, 700, 800)
+OPSZ = (16, 24, 32, 48)
+WANT_AXES = [("wght", 300, 400, 800), ("opsz", 16, 16, 48)]
+
+# where the repaired Serif may live, most specific first. Running on the S2b
+# output (before normalize) is preferred because then the name-table
+# assertions are exact; on the shipped face they are string comparisons.
+CANDIDATES = [
+ os.path.join("work", "s2b", "AnthropicSerif-Roman.ttf"),
+ os.path.join("work", "serif-s2b", "AnthropicSerif-Roman.ttf"),
+ os.path.join("work", "AnthropicSerif-Roman-s2b.ttf"),
+ os.path.join("desktop", "AnthropicSerif-Roman.ttf"),
+]
+
+
+def instance_strings(f):
+ n = f["name"]
+ return [(tuple(sorted(i.coordinates.items())), n.getDebugName(i.subfamilyNameID))
+ for i in f["fvar"].instances]
+
+
+def main(argv):
+ ap = argparse.ArgumentParser()
+ ap.add_argument("capture", help="the READ-ONLY capture dir holding the untouched Serif")
+ ap.add_argument("out")
+ ap.add_argument("--src", default=None,
+ help="accepted and ignored; the capture dir is the first positional here")
+ ap.add_argument("--allow-partial", action="store_true",
+ help="accepted and ignored; A12 needs only the one Serif face")
+ a = ap.parse_args(argv[1:])
+ capture, out = os.path.abspath(a.capture), os.path.abspath(a.out)
+ src = os.path.join(capture, "fonts-ttf", "AnthropicSerif-Roman-Web.ttf")
+ dst = None
+ for rel in CANDIDATES:
+ p = os.path.join(out, rel)
+ if os.path.exists(p):
+ dst = p
+ break
+ fails = []
+ if not os.path.exists(src):
+ print("FAIL A12: no untouched Serif source at %s" % src)
+ return 1
+ if dst is None:
+ print("FAIL A12: no repaired Serif in the out-dir (looked for %s)" % CANDIDATES)
+ return 1
+ print("A12 source %s" % src)
+ print("A12 under test %s" % dst)
+
+ f = TTFont(dst, recalcTimestamp=False)
+ s = TTFont(src, recalcTimestamp=False)
+
+ ax = [(a.axisTag, a.minValue, a.defaultValue, a.maxValue) for a in f["fvar"].axes]
+ if ax != WANT_AXES:
+ fails.append("fvar axes %s != %s" % (ax, WANT_AXES))
+ a_i, s_i = instance_strings(f), instance_strings(s)
+ if a_i != s_i:
+ bad = [(x, y) for x, y in zip(a_i, s_i) if x != y]
+ fails.append("named instances differ (coords, resolved subfamily string): %s" % bad[:4])
+ if len(f["fvar"].instances) != 12:
+ fails.append("%d named instances, expected 12" % len(f["fvar"].instances))
+ if f["OS/2"].usWeightClass != 400:
+ fails.append("usWeightClass %d != 400" % f["OS/2"].usWeightClass)
+ if set(f["gvar"].variations) != set(s["gvar"].variations):
+ fails.append("gvar glyph set changed")
+ st, ss = f["STAT"].table, s["STAT"].table
+ na = len(st.AxisValueArray.AxisValue)
+ ns = len(ss.AxisValueArray.AxisValue)
+ if na != ns:
+ fails.append("STAT AxisValue count %d != %d" % (na, ns))
+ if not st.ElidedFallbackNameID:
+ # the STRING is deliberately reset by S9 - only non-zero is asserted
+ fails.append("STAT ElidedFallbackNameID is 0")
+
+ bad = []
+ for w in WGHT:
+ for o in OPSZ:
+ a = inst(TTFont(src, recalcTimestamp=False), {"wght": w, "opsz": o},
+ inplace=True, updateFontNames=False)
+ b = inst(TTFont(dst, recalcTimestamp=False), {"wght": w, "opsz": o},
+ inplace=True, updateFontNames=False)
+ if a.getGlyphOrder() != b.getGlyphOrder():
+ bad.append(("", w, o, "glyph order"))
+ continue
+ ga, gb = a.getGlyphSet(), b.getGlyphSet()
+ for g in a.getGlyphOrder():
+ pa, pb = DecomposingRecordingPen(ga), DecomposingRecordingPen(gb)
+ ga[g].draw(pa)
+ gb[g].draw(pb)
+ if [op for op, _ in pa.value] != [op for op, _ in pb.value]:
+ bad.append((g, w, o, "contour structure"))
+ continue
+ for (_, u), (_, v) in zip(pa.value, pb.value):
+ for p, q in zip(u, v):
+ if max(abs(p[0] - q[0]), abs(p[1] - q[1])) > PT_TOL:
+ bad.append((g, w, o, "point > %g u" % PT_TOL))
+ if abs(a["hmtx"][g][0] - b["hmtx"][g][0]) > ADV_TOL:
+ bad.append((g, w, o, "advance > %d u" % ADV_TOL))
+ Ba, Bb = BoundsPen(ga), BoundsPen(gb)
+ ga[g].draw(Ba)
+ gb[g].draw(Bb)
+ if (Ba.bounds is None) != (Bb.bounds is None):
+ bad.append((g, w, o, "bbox none"))
+ elif Ba.bounds and max(abs(x - y) for x, y in zip(Ba.bounds, Bb.bounds)) > BB_TOL:
+ # RAW bbox. NEVER round() - see rule 2 in the docstring.
+ bad.append((g, w, o, "bbox > %g u" % BB_TOL))
+ print("A12 grid: %d probes (wght %s x opsz %s), %d glyph-instance failures"
+ % (len(WGHT) * len(OPSZ), list(WGHT), list(OPSZ), len(bad)))
+ for x in bad[:10]:
+ fails.append("grid %s" % (x,))
+ for f_ in fails:
+ print("FAIL A12:", f_)
+ return 1 if fails else 0
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/pkgs/fontbuilder/tests/accept.py b/pkgs/fontbuilder/tests/accept.py
new file mode 100644
index 000000000..368a2faae
--- /dev/null
+++ b/pkgs/fontbuilder/tests/accept.py
@@ -0,0 +1,1013 @@
+#!/usr/bin/env python3
+"""fontbuilder acceptance suite - the fontTools/fontconfig half of spec 11.
+
+ tests/accept.py [--src ]
+
+Covers A3 A4 A5 A6 A7 A8 A9 A10 A13 A14 A16 A21. A1/A2 need kitty's own
+resolver and live in tests/resolve.py and tests/shape.py; A12 needs the
+untouched Serif source and lives in tests/a12.py.
+
+Writes /verify/accept.json, prints one `FAIL : ...` line per
+failure, exits 1 if there is any. Nothing is written outside /verify/.
+
+THE ISOLATION CONTRACT (spec 11 preamble, D17). Every fc-scan / fc-match /
+fc-list here runs with FONTCONFIG_FILE pointing at a config rendered from
+tests/fonts.conf.in (private under /verify) AND with
+XDG_CACHE_HOME redirected under /verify. FONTCONFIG_FILE alone is not
+enough - with only it set, ~/.cache/fontconfig's DIRECTORY mtime moved.
+`fc-cache` is NEVER invoked. A21 proves both by snapshotting
+
+ find ~/.cache/fontconfig -printf '%T@ %s %p\n' | sort | sha256sum
+
+without -type f, before and after the whole suite.
+
+EVERY nameID ASSERTION IS SET MEMBERSHIP (A4). The name table carries TWO
+nameID16 records with different strings (`AnthropicMono Nerd Font Mono` and
+`AnthropicMono NFM`); a dict keyed by nameID silently keeps only the last and
+makes A4 fail on a correct font.
+"""
+from __future__ import annotations
+
+import argparse
+import collections
+import hashlib
+import json
+import math
+import os
+import re
+import subprocess
+import sys
+import tarfile
+
+from fontTools.pens.boundsPen import BoundsPen
+from fontTools.ttLib import TTFont
+
+# --------------------------------------------------------------------------
+# constants measured on the real faces (spec 11 A3/A5/A6/A7/A8/A9/A10)
+# --------------------------------------------------------------------------
+ROMAN = ["Light", "Regular", "Medium", "SemiBold", "Bold", "ExtraBold"]
+ITALIC = ["LightItalic", "Italic", "MediumItalic", "SemiBoldItalic", "BoldItalic", "ExtraBoldItalic"]
+STYLES = ROMAN + ITALIC
+WEIGHT = {"Light": 50, "Regular": 80, "Medium": 100, "SemiBold": 180, "Bold": 200, "ExtraBold": 205}
+# fc-scan's canonical style string for each face; membership, the row also
+# carries the RIBBI alias (`ExtraBold,Regular`).
+FC_STYLE = {
+ "Light": "Light", "Regular": "Regular", "Medium": "Medium", "SemiBold": "SemiBold",
+ "Bold": "Bold", "ExtraBold": "ExtraBold", "LightItalic": "Light Italic",
+ "Italic": "Italic", "MediumItalic": "Medium Italic", "SemiBoldItalic": "SemiBold Italic",
+ "BoldItalic": "Bold Italic", "ExtraBoldItalic": "ExtraBold Italic",
+}
+LONG_FAMILY = "AnthropicMono Nerd Font Mono"
+SHORT_FAMILY = "AnthropicMono NFM"
+CELL = 1200
+
+# A4. The patcher version is NOT hardcoded. The flake's pinned nixpkgs moved
+# from nerd-font-patcher 3.4.0 (what the scouts measured) to 3.5.1, so nameID5
+# now reads ";Nerd Fonts 3.5.1". The shape is asserted with a regex, and the
+# EXACT version is taken from /manifest.json tools["nerd-font-patcher"]
+# when that key is present - which turns a vacuous shape check into an exact
+# one without pinning a number in this file.
+NF_STAMP_RE = re.compile(r";Nerd Fonts \d+\.\d+\.\d+")
+NF_VERSION_RE = re.compile(r"v(\d+\.\d+\.\d+)")
+
+# A4. nameID6. Patcher 3.5.1 added FontnameParser._remove_regular, so the
+# RIBBI Regular face ships the bare `AnthropicMonoNFM` with no -Regular token,
+# while the Regular-weight italic stays `AnthropicMonoNFM-Italic` and every
+# other face keeps its -