feat: add /notes with two engineering write-ups and diagrams
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Container, Pill } from "@/src/components/site";
|
||||
import { NOTES } from "@/src/content/notes";
|
||||
import { SITE_NAME, SITE_URL } from "@/src/content/site";
|
||||
import { findNoteBySlug } from "@/src/domain/notes";
|
||||
|
||||
type PageProps = { readonly params: Promise<{ readonly slug: string }> };
|
||||
|
||||
export const dynamicParams = false;
|
||||
|
||||
export function generateStaticParams() {
|
||||
return NOTES.map((note) => ({ slug: note.slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: PageProps): Promise<Metadata> {
|
||||
const found = findNoteBySlug(NOTES, (await params).slug);
|
||||
if (!found.ok) return {};
|
||||
const { title, summary, slug, publishedOn } = found.value;
|
||||
const path = `/notes/${slug}`;
|
||||
return {
|
||||
title,
|
||||
description: summary,
|
||||
alternates: { canonical: path },
|
||||
openGraph: {
|
||||
type: "article",
|
||||
title,
|
||||
description: summary,
|
||||
url: path,
|
||||
publishedTime: publishedOn,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const found = findNoteBySlug(NOTES, (await params).slug);
|
||||
if (!found.ok) notFound();
|
||||
const { Body, title, summary, slug, publishedOn, readingMinutes, tags } =
|
||||
found.value;
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: title,
|
||||
description: summary,
|
||||
datePublished: publishedOn,
|
||||
url: `${SITE_URL}/notes/${slug}`,
|
||||
author: { "@type": "Person", name: SITE_NAME, url: SITE_URL },
|
||||
keywords: tags.join(", "),
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className="py-16 md:py-24">
|
||||
<article className="mx-auto max-w-[70ch]">
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(jsonLd).replace(/</g, "\\u003c"),
|
||||
}}
|
||||
/>
|
||||
<Link
|
||||
href="/notes"
|
||||
className="font-mono text-xs uppercase tracking-widest text-accent"
|
||||
>
|
||||
← Notes
|
||||
</Link>
|
||||
<header className="mt-6">
|
||||
<h1 className="!mb-4 !text-3xl !leading-tight text-fg md:!text-5xl">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="!mb-4 font-mono text-xs text-muted">
|
||||
<time dateTime={publishedOn}>{publishedOn}</time>
|
||||
{" · "}
|
||||
{readingMinutes} min read
|
||||
</p>
|
||||
<span className="flex flex-wrap gap-2">
|
||||
{tags.map((tag) => (
|
||||
<Pill key={tag}>{tag}</Pill>
|
||||
))}
|
||||
</span>
|
||||
</header>
|
||||
<div className="mt-10">
|
||||
<Body />
|
||||
</div>
|
||||
</article>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Container } from "@/src/components/site/Container";
|
||||
import { SectionHeader } from "@/src/components/site/SectionHeader";
|
||||
import Link from "next/link";
|
||||
import { Container, Pill, SectionHeader } from "@/src/components/site";
|
||||
import { NOTES } from "@/src/content/notes";
|
||||
|
||||
export const metadata: Metadata = { title: "Notes" };
|
||||
export const metadata: Metadata = {
|
||||
title: "Notes",
|
||||
description:
|
||||
"Evergreen engineering write-ups with diagrams: verification, architecture and testing.",
|
||||
alternates: { canonical: "/notes" },
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
@@ -11,8 +17,35 @@ export default function Page() {
|
||||
as="h1"
|
||||
kicker="/notes"
|
||||
title="Notes"
|
||||
lead="This page is coming soon."
|
||||
lead="Evergreen engineering write-ups with diagrams. No client work, no hype."
|
||||
/>
|
||||
<ul className="mt-12 max-w-3xl divide-y divide-border border-y border-border">
|
||||
{NOTES.map((note) => (
|
||||
<li key={note.slug}>
|
||||
<Link
|
||||
href={`/notes/${note.slug}`}
|
||||
className="group block py-8 hover:text-fg"
|
||||
>
|
||||
<p className="font-mono text-xs text-muted">
|
||||
<time dateTime={note.publishedOn}>{note.publishedOn}</time>
|
||||
{" · "}
|
||||
{note.readingMinutes} min read
|
||||
</p>
|
||||
<h2 className="!mb-2 !mt-2 !text-xl !leading-snug text-fg group-hover:text-accent md:!text-2xl">
|
||||
{note.title}
|
||||
</h2>
|
||||
<p className="!mb-4 text-base leading-relaxed text-muted">
|
||||
{note.summary}
|
||||
</p>
|
||||
<span className="flex flex-wrap gap-2">
|
||||
{note.tags.map((tag) => (
|
||||
<Pill key={tag}>{tag}</Pill>
|
||||
))}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import * as React from "react";
|
||||
|
||||
const MONO = "ui-monospace, monospace";
|
||||
|
||||
type Box = {
|
||||
readonly y: number;
|
||||
readonly height: number;
|
||||
readonly title: string;
|
||||
readonly lines: readonly string[];
|
||||
readonly tag: string;
|
||||
readonly variant: "shell" | "core" | "chain";
|
||||
};
|
||||
|
||||
const BOXES: readonly Box[] = [
|
||||
{
|
||||
y: 10,
|
||||
height: 66,
|
||||
title: "Detectors, one per chain (7)",
|
||||
lines: ["index borrower positions, read state"],
|
||||
tag: "shell: I/O",
|
||||
variant: "shell",
|
||||
},
|
||||
{
|
||||
y: 120,
|
||||
height: 110,
|
||||
title: "Pure domain crate",
|
||||
lines: [
|
||||
"health factor",
|
||||
"fixed-point arithmetic",
|
||||
"closed-form optimum, checked",
|
||||
"against an exact swap engine",
|
||||
],
|
||||
tag: "core: no I/O",
|
||||
variant: "core",
|
||||
},
|
||||
{
|
||||
y: 274,
|
||||
height: 66,
|
||||
title: "Submit shell",
|
||||
lines: ["builds the transaction, sends it"],
|
||||
tag: "shell: I/O",
|
||||
variant: "shell",
|
||||
},
|
||||
{
|
||||
y: 384,
|
||||
height: 104,
|
||||
title: "On-chain executor (Solidity)",
|
||||
lines: [
|
||||
"flash loan, liquidate, swap collateral,",
|
||||
"repay. Reverts if the trade would",
|
||||
"not be profitable.",
|
||||
],
|
||||
tag: "atomic",
|
||||
variant: "chain",
|
||||
},
|
||||
];
|
||||
|
||||
const ARROWS = [
|
||||
{ from: 76, label: "positions as data" },
|
||||
{ from: 230, label: "decision as data" },
|
||||
{ from: 340, label: "signed transaction" },
|
||||
] as const;
|
||||
|
||||
const ARROW_LENGTH = 44;
|
||||
|
||||
const boxClass = (variant: Box["variant"]): string =>
|
||||
variant === "core"
|
||||
? "fill-surface stroke-accent"
|
||||
: "fill-surface stroke-border-strong";
|
||||
|
||||
export const CoreShellDiagram: React.FC = () => (
|
||||
<svg
|
||||
viewBox="0 0 480 500"
|
||||
role="img"
|
||||
aria-labelledby="core-title core-desc"
|
||||
className="h-auto w-full"
|
||||
>
|
||||
<title id="core-title">Pure core, thin shells, on-chain executor</title>
|
||||
<desc id="core-desc">
|
||||
Per-chain detector shells read state and pass positions as plain data to a
|
||||
pure domain crate. The core returns a decision as data to a submit shell,
|
||||
which sends a transaction to the on-chain executor. The executor takes a
|
||||
flash loan, liquidates, swaps collateral and repays, and reverts if the
|
||||
trade would not be profitable.
|
||||
</desc>
|
||||
{BOXES.map((box) => (
|
||||
<g key={box.title}>
|
||||
<rect
|
||||
x={20}
|
||||
y={box.y}
|
||||
width={440}
|
||||
height={box.height}
|
||||
rx={6}
|
||||
className={boxClass(box.variant)}
|
||||
strokeWidth={box.variant === "core" ? 2 : 1.5}
|
||||
strokeDasharray={box.variant === "chain" ? "6 4" : undefined}
|
||||
/>
|
||||
<text
|
||||
x={36}
|
||||
y={box.y + 24}
|
||||
fontSize={15}
|
||||
fontFamily={MONO}
|
||||
className={box.variant === "core" ? "fill-accent" : "fill-current"}
|
||||
>
|
||||
{box.title}
|
||||
</text>
|
||||
{box.lines.map((line, index) => (
|
||||
<text
|
||||
key={line}
|
||||
x={36}
|
||||
y={box.y + 46 + index * 18}
|
||||
fontSize={13}
|
||||
fontFamily={MONO}
|
||||
className="fill-muted"
|
||||
>
|
||||
{line}
|
||||
</text>
|
||||
))}
|
||||
<text
|
||||
x={444}
|
||||
y={box.y + 24}
|
||||
fontSize={12}
|
||||
fontFamily={MONO}
|
||||
textAnchor="end"
|
||||
className="fill-muted"
|
||||
>
|
||||
{box.tag}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
{ARROWS.map((arrow) => (
|
||||
<g key={arrow.label}>
|
||||
<path
|
||||
d={`M240 ${arrow.from + 2} v${ARROW_LENGTH - 8} m-5 -5 l5 5 l5 -5`}
|
||||
className="stroke-border-strong"
|
||||
fill="none"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<text
|
||||
x={254}
|
||||
y={arrow.from + ARROW_LENGTH / 2 + 4}
|
||||
fontSize={12}
|
||||
fontFamily={MONO}
|
||||
className="fill-muted"
|
||||
>
|
||||
{arrow.label}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,115 @@
|
||||
import * as React from "react";
|
||||
|
||||
const GATES = [
|
||||
{ title: "TDD gate", detail: "spec first, red before green" },
|
||||
{
|
||||
title: "Functional-style gate",
|
||||
detail: "no methods, traits, &mut, unwrap",
|
||||
},
|
||||
{ title: "Mutation gate", detail: "zero survivors, proof ledger" },
|
||||
{ title: "Coverage ratchet", detail: "fails when coverage drops" },
|
||||
] as const;
|
||||
|
||||
const SPEEDS = [
|
||||
{ label: "changed lines: dev loop", width: 150 },
|
||||
{ label: "changed crates: pre-merge", width: 300 },
|
||||
{ label: "everything: merge gate", width: 440 },
|
||||
] as const;
|
||||
|
||||
const GATE_HEIGHT = 52;
|
||||
const GATE_GAP = 20;
|
||||
const GATE_TOP = 10;
|
||||
const SPEEDS_TOP = 320;
|
||||
const SPEED_ROW = 42;
|
||||
|
||||
export const GatePipelineDiagram: React.FC = () => (
|
||||
<svg
|
||||
viewBox="0 0 480 470"
|
||||
role="img"
|
||||
aria-labelledby="gate-title gate-desc"
|
||||
className="h-auto w-full"
|
||||
>
|
||||
<title id="gate-title">The gate pipeline</title>
|
||||
<desc id="gate-desc">
|
||||
Four gates run in sequence: a TDD gate, a functional-style gate, a
|
||||
mutation gate and a coverage ratchet. Below them, three scopes of
|
||||
increasing size run the same gates: changed lines in the dev loop, changed
|
||||
crates before merge, and everything at the merge gate.
|
||||
</desc>
|
||||
{GATES.map((gate, index) => {
|
||||
const y = GATE_TOP + index * (GATE_HEIGHT + GATE_GAP);
|
||||
return (
|
||||
<g key={gate.title}>
|
||||
<rect
|
||||
x={20}
|
||||
y={y}
|
||||
width={440}
|
||||
height={GATE_HEIGHT}
|
||||
rx={6}
|
||||
className="fill-surface stroke-accent"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<text
|
||||
x={36}
|
||||
y={y + 22}
|
||||
fontSize={15}
|
||||
fontFamily="ui-monospace, monospace"
|
||||
className="fill-accent"
|
||||
>
|
||||
{gate.title}
|
||||
</text>
|
||||
<text
|
||||
x={36}
|
||||
y={y + 42}
|
||||
fontSize={13}
|
||||
fontFamily="ui-monospace, monospace"
|
||||
className="fill-muted"
|
||||
>
|
||||
{gate.detail}
|
||||
</text>
|
||||
{index < GATES.length - 1 ? (
|
||||
<path
|
||||
d={`M240 ${y + GATE_HEIGHT + 2} v${GATE_GAP - 8} m-5 -5 l5 5 l5 -5`}
|
||||
className="stroke-border-strong"
|
||||
fill="none"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
) : null}
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
<text
|
||||
x={20}
|
||||
y={SPEEDS_TOP - 12}
|
||||
fontSize={13}
|
||||
fontFamily="ui-monospace, monospace"
|
||||
className="fill-muted"
|
||||
>
|
||||
same gates, three speeds
|
||||
</text>
|
||||
{SPEEDS.map((speed, index) => {
|
||||
const y = SPEEDS_TOP + index * SPEED_ROW;
|
||||
return (
|
||||
<g key={speed.label}>
|
||||
<text
|
||||
x={20}
|
||||
y={y + 14}
|
||||
fontSize={14}
|
||||
fontFamily="ui-monospace, monospace"
|
||||
className="fill-current"
|
||||
>
|
||||
{speed.label}
|
||||
</text>
|
||||
<rect
|
||||
x={20}
|
||||
y={y + 22}
|
||||
width={speed.width}
|
||||
height={6}
|
||||
rx={3}
|
||||
className="fill-accent"
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,39 @@
|
||||
import * as React from "react";
|
||||
|
||||
type WithChildren = { readonly children: React.ReactNode };
|
||||
|
||||
export const NoteSection: React.FC<WithChildren> = ({ children }) => (
|
||||
<h2 className="!mb-4 !mt-12 !text-2xl !leading-snug text-fg md:!text-3xl">
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
|
||||
export const NoteParagraph: React.FC<WithChildren> = ({ children }) => (
|
||||
<p className="!mb-5 !text-base !leading-8 text-fg/85 md:!text-lg md:!leading-8">
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
|
||||
export const NoteList: React.FC<WithChildren> = ({ children }) => (
|
||||
<ul className="!mb-6 !ml-5 list-disc space-y-2 text-base leading-8 text-fg/85 marker:text-accent md:text-lg">
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
|
||||
export const NoteCode: React.FC<WithChildren> = ({ children }) => (
|
||||
<code className="font-mono">{children}</code>
|
||||
);
|
||||
|
||||
type NoteFigureProps = WithChildren & { readonly caption: string };
|
||||
|
||||
export const NoteFigure: React.FC<NoteFigureProps> = ({
|
||||
children,
|
||||
caption,
|
||||
}) => (
|
||||
<figure className="my-10 rounded-lg border border-border bg-surface p-5 md:p-8">
|
||||
<div className="mx-auto max-w-md text-fg">{children}</div>
|
||||
<figcaption className="mt-4 text-center font-mono text-xs text-muted">
|
||||
{caption}
|
||||
</figcaption>
|
||||
</figure>
|
||||
);
|
||||
@@ -0,0 +1,121 @@
|
||||
import * as React from "react";
|
||||
import { CoreShellDiagram } from "@/src/components/notes/CoreShellDiagram";
|
||||
import {
|
||||
NoteFigure,
|
||||
NoteList,
|
||||
NoteParagraph,
|
||||
NoteSection,
|
||||
} from "@/src/components/notes/NoteProse";
|
||||
import type { Note } from "./types";
|
||||
|
||||
const Body: React.FC = () => (
|
||||
<>
|
||||
<NoteParagraph>
|
||||
I am building a liquidation system that runs across 7 chains. It is
|
||||
written in Rust, with Solidity executors on chain. It is built to run
|
||||
unattended, which changes how much you can rely on manual checking: almost
|
||||
all of the confidence has to come from tests that run before the code
|
||||
does. The architecture that makes this workable is a functional core with
|
||||
imperative shells.
|
||||
</NoteParagraph>
|
||||
|
||||
<NoteSection>The shape</NoteSection>
|
||||
<NoteParagraph>
|
||||
A detector per chain indexes borrower positions and computes health
|
||||
factors. That work is split in two. The math lives in a pure domain crate:
|
||||
health factor, fixed-point arithmetic, and a closed-form optimum that is
|
||||
checked against an exact swap engine. The crate has no network, no clock,
|
||||
no randomness and no global state. Everything arrives as arguments and
|
||||
leaves as return values.
|
||||
</NoteParagraph>
|
||||
<NoteParagraph>
|
||||
Around that core sit thin shells. A shell reads state from a chain,
|
||||
converts it into plain data, calls the core, and acts on the answer. It
|
||||
contains almost no decisions, so there is little in it that can be wrong
|
||||
in an interesting way.
|
||||
</NoteParagraph>
|
||||
|
||||
<NoteFigure caption="Data flows down; only the shells touch the network.">
|
||||
<CoreShellDiagram />
|
||||
</NoteFigure>
|
||||
|
||||
<NoteSection>The on-chain side</NoteSection>
|
||||
<NoteParagraph>
|
||||
The executor contract does one job in one transaction. It takes a flash
|
||||
loan, liquidates the position, swaps the collateral, repays the loan, and
|
||||
reverts if the trade would not be profitable. Because the transaction is
|
||||
atomic, a failed attempt leaves nothing behind except the gas for the
|
||||
attempt. No own capital is at risk per attempt, which is a property of the
|
||||
design rather than of careful operation.
|
||||
</NoteParagraph>
|
||||
<NoteParagraph>
|
||||
The same idea applies on chain as off chain: the contract is a small,
|
||||
closed piece of logic with explicit inputs. The off-chain core decides
|
||||
whether to try; the contract has the final say.
|
||||
</NoteParagraph>
|
||||
|
||||
<NoteSection>Testing the edge, not the middle</NoteSection>
|
||||
<NoteParagraph>
|
||||
Fixed-point arithmetic has cliffs. Past a certain input size a
|
||||
multiplication no longer fits in the integer type. If the code wraps
|
||||
around, a hugely healthy position can suddenly look unhealthy, or the
|
||||
other way round, and nothing crashes to tell you.
|
||||
</NoteParagraph>
|
||||
<NoteParagraph>
|
||||
One spec in the domain crate tests the exact overflow cliff of the health
|
||||
factor. It pins the behavior one step below the cliff and at the cliff,
|
||||
and requires the result to saturate instead of wrapping. This kind of case
|
||||
is where an optimizing refactor or a generated change breaks things
|
||||
silently, and it is trivial to write because the core is a pure function:
|
||||
call it with the boundary values and compare the result.
|
||||
</NoteParagraph>
|
||||
|
||||
<NoteSection>Why the split pays off</NoteSection>
|
||||
<NoteList>
|
||||
<li>
|
||||
<strong>The core is testable without a network.</strong> Specs run fast,
|
||||
with no node, no fork and no flaky timing. That makes spec-first
|
||||
practical for the part with the most arithmetic.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Mutation testing can prove the specs.</strong> Pure functions
|
||||
give mutation tools clean targets. If a mutant of the health factor
|
||||
survives, a spec is missing, and the gate says so. That is a stronger
|
||||
statement than a coverage percentage.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Shells are proven by end-to-end specs.</strong> They are not
|
||||
pure, so they are not tested like the core. They are exercised as a
|
||||
whole against the behavior they are meant to produce, and because they
|
||||
hold so little logic, those specs stay small.
|
||||
</li>
|
||||
</NoteList>
|
||||
|
||||
<NoteSection>Where it hurts</NoteSection>
|
||||
<NoteParagraph>
|
||||
The discipline has a cost. You have to keep deciding where a line of code
|
||||
belongs, and the temptation to put a small calculation in the shell
|
||||
because it is right there is constant. Every such shortcut moves logic
|
||||
into the part that is hardest to test. The gates described in the note on
|
||||
mechanical spec-first enforcement help here: a gate that forbids methods,
|
||||
mutable references and unwrap pushes the code toward the style the core
|
||||
depends on.
|
||||
</NoteParagraph>
|
||||
<NoteParagraph>
|
||||
The pattern is not specific to this project. Any system where money,
|
||||
permissions or irreversible actions sit behind a thin layer of I/O gains
|
||||
from keeping the decision logic pure and the I/O boring.
|
||||
</NoteParagraph>
|
||||
</>
|
||||
);
|
||||
|
||||
export const functionalCoreImperativeShell: Note = {
|
||||
slug: "functional-core-imperative-shell-multi-chain",
|
||||
title: "Functional core, imperative shell in a multi-chain system",
|
||||
summary:
|
||||
"How a pure Rust core, thin shells and an atomic on-chain executor make a 7-chain liquidation system testable.",
|
||||
publishedOn: "2026-10-02",
|
||||
readingMinutes: 4,
|
||||
tags: ["architecture", "rust", "web3"],
|
||||
Body,
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { sortNotesNewestFirst, validateNoteRegistry } from "@/src/domain/notes";
|
||||
import { functionalCoreImperativeShell } from "./functional-core-imperative-shell";
|
||||
import { specFirstGates } from "./spec-first-gates";
|
||||
import type { Note } from "./types";
|
||||
|
||||
const ALL_NOTES: readonly Note[] = [
|
||||
specFirstGates,
|
||||
functionalCoreImperativeShell,
|
||||
];
|
||||
|
||||
const validated = validateNoteRegistry(ALL_NOTES);
|
||||
if (!validated.ok) {
|
||||
throw new Error(`Invalid notes: ${validated.error.join("; ")}`);
|
||||
}
|
||||
|
||||
export const NOTES: readonly Note[] = sortNotesNewestFirst(validated.value);
|
||||
@@ -0,0 +1,142 @@
|
||||
import * as React from "react";
|
||||
import { GatePipelineDiagram } from "@/src/components/notes/GatePipelineDiagram";
|
||||
import {
|
||||
NoteCode,
|
||||
NoteFigure,
|
||||
NoteList,
|
||||
NoteParagraph,
|
||||
NoteSection,
|
||||
} from "@/src/components/notes/NoteProse";
|
||||
import type { Note } from "./types";
|
||||
|
||||
const Body: React.FC = () => (
|
||||
<>
|
||||
<NoteParagraph>
|
||||
A workflow that depends on discipline lasts until the first deadline. In
|
||||
my Rust systems the workflow is enforced by scripts instead: a change that
|
||||
skips the spec, drops coverage or leaves a weak test does not get past the
|
||||
gates, no matter who wrote it or how it was generated.
|
||||
</NoteParagraph>
|
||||
|
||||
<NoteSection>The gates</NoteSection>
|
||||
<NoteParagraph>
|
||||
There are four, and each one answers a different question.
|
||||
</NoteParagraph>
|
||||
<NoteList>
|
||||
<li>
|
||||
<strong>TDD gate.</strong> Was the behavior specified as a test before
|
||||
it was implemented? Skipping the spec fails the gate.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Functional-style gate.</strong> It forbids methods, traits,{" "}
|
||||
<NoteCode>&mut</NoteCode> and <NoteCode>unwrap</NoteCode> in the
|
||||
domain code. Pure functions over immutable data are easier to test and
|
||||
to mutate, so the style is a rule, not a preference. The gate is itself
|
||||
covered by a self-test script, because a checker that silently stops
|
||||
checking is worse than none.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Mutation gate.</strong> The tool changes the code in small ways
|
||||
and the specs must notice. The policy is zero survivors. A surviving
|
||||
mutant means either a weak assertion or dead code, and both get fixed.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Coverage ratchet.</strong> Coverage may rise but may not fall.
|
||||
The gate fails when the number drops below the last recorded one.
|
||||
</li>
|
||||
</NoteList>
|
||||
|
||||
<NoteFigure caption="Four gates in sequence, run at three scopes.">
|
||||
<GatePipelineDiagram />
|
||||
</NoteFigure>
|
||||
|
||||
<NoteSection>Three speeds</NoteSection>
|
||||
<NoteParagraph>
|
||||
Mutation testing is slow, so the same gates run at three scopes. In the
|
||||
dev loop they look only at changed lines, which keeps feedback fast enough
|
||||
to use constantly. Before a merge they run on the changed crates. The
|
||||
merge gate runs everything. Nothing is skipped on the way to main; a
|
||||
cheaper check just runs earlier and more often.
|
||||
</NoteParagraph>
|
||||
<NoteParagraph>
|
||||
The mutation gate also keeps a proof ledger. When the code bytes and the
|
||||
toolchain are unchanged since a previous run, the mutants already caught
|
||||
are carried over instead of being recomputed. That keeps a zero-survivor
|
||||
policy affordable on a large codebase, and it stays honest because any
|
||||
change to the code or the toolchain invalidates the entry.
|
||||
</NoteParagraph>
|
||||
|
||||
<NoteSection>Claims drift, measurements do not</NoteSection>
|
||||
<NoteParagraph>
|
||||
The most useful lesson came from reading my own documentation. A README
|
||||
and a Makefile said "100% coverage at all times", while the
|
||||
measured badge said 87.5%. Nobody lied; the sentence was true once and
|
||||
then the code moved. Prose does not fail a build.
|
||||
</NoteParagraph>
|
||||
<NoteParagraph>
|
||||
So I quote the measured number and enforce a ratchet instead of promising
|
||||
an absolute. A ratchet is a claim the machine checks on every run: the
|
||||
number may only go up. It is a weaker sentence than "always
|
||||
100%" and a far more reliable one.
|
||||
</NoteParagraph>
|
||||
|
||||
<NoteSection>A story from this website</NoteSection>
|
||||
<NoteParagraph>
|
||||
This site has a small domain core with the same setup: Vitest for the
|
||||
specs and Stryker for mutation testing. At one point Stryker reported a
|
||||
mutation score of 14%. That looked like terrible specs, but the specs were
|
||||
fine. The mutation tool was silently running zero tests per mutant under
|
||||
Vitest 5, so every mutant looked like it had survived.
|
||||
</NoteParagraph>
|
||||
<NoteParagraph>
|
||||
The cause was a version mismatch between the Stryker Vitest runner and
|
||||
Vitest itself. Pinning Vitest to 4.1.x fixed it. The first real run then
|
||||
found 9 genuine gaps in the specs, the kind a green test suite had been
|
||||
hiding. Rewriting one date check also removed redundant conditions that no
|
||||
test could ever distinguish from the simpler version.
|
||||
</NoteParagraph>
|
||||
<NoteParagraph>
|
||||
The pin is now written down in the project instructions, with the reason,
|
||||
so the next dependency bump does not quietly undo it.
|
||||
</NoteParagraph>
|
||||
|
||||
<NoteSection>What I take from it</NoteSection>
|
||||
<NoteList>
|
||||
<li>
|
||||
<strong>Verify that your verification runs.</strong> A gate that
|
||||
executes zero tests passes or fails for the wrong reasons. Treat "0
|
||||
tests ran" as a failure, and look at a surprising score before
|
||||
explaining it away.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Enforce, do not remind.</strong> If a rule matters, a script
|
||||
should fail when it is broken. Rules that live in a README drift.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Quote measurements.</strong> Write the number the tool printed,
|
||||
with the command that printed it, and let a ratchet protect it.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Make the cheap check run first.</strong> Three speeds let the
|
||||
slow, thorough gates exist without slowing the loop that people use
|
||||
every few minutes.
|
||||
</li>
|
||||
</NoteList>
|
||||
<NoteParagraph>
|
||||
None of this is specific to Rust. The same shape works for a TypeScript
|
||||
project: a spec before the code, mutation testing on the changed files,
|
||||
and a coverage number that is only allowed to go up.
|
||||
</NoteParagraph>
|
||||
</>
|
||||
);
|
||||
|
||||
export const specFirstGates: Note = {
|
||||
slug: "how-i-enforce-spec-first-mechanically",
|
||||
title: "How I enforce spec-first mechanically",
|
||||
summary:
|
||||
"Four gates, three speeds, and one lesson: claims drift, so measure and ratchet instead of promising.",
|
||||
publishedOn: "2026-10-03",
|
||||
readingMinutes: 4,
|
||||
tags: ["testing", "mutation testing", "process"],
|
||||
Body,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import type * as React from "react";
|
||||
import type { NoteMeta } from "@/src/domain/notes";
|
||||
|
||||
export type Note = NoteMeta & { readonly Body: React.FC };
|
||||
@@ -0,0 +1,193 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
findNoteBySlug,
|
||||
sortNotesNewestFirst,
|
||||
validateNoteMeta,
|
||||
validateNoteRegistry,
|
||||
type NoteMeta,
|
||||
} from "./notes";
|
||||
|
||||
const meta = (overrides: Partial<NoteMeta> = {}): NoteMeta => ({
|
||||
slug: "gate-pipeline",
|
||||
title: "Gate pipeline",
|
||||
summary: "A short summary.",
|
||||
publishedOn: "2026-10-01",
|
||||
readingMinutes: 5,
|
||||
tags: ["testing"],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("sortNotesNewestFirst", () => {
|
||||
it("orders by publishedOn descending", () => {
|
||||
const sorted = sortNotesNewestFirst([
|
||||
meta({ slug: "a", publishedOn: "2026-01-05" }),
|
||||
meta({ slug: "b", publishedOn: "2026-03-01" }),
|
||||
meta({ slug: "c", publishedOn: "2025-12-31" }),
|
||||
]);
|
||||
expect(sorted.map((n) => n.slug)).toEqual(["b", "a", "c"]);
|
||||
});
|
||||
|
||||
it("breaks ties by slug ascending so the order is deterministic", () => {
|
||||
const sorted = sortNotesNewestFirst([
|
||||
meta({ slug: "zeta", publishedOn: "2026-01-05" }),
|
||||
meta({ slug: "alpha", publishedOn: "2026-01-05" }),
|
||||
]);
|
||||
expect(sorted.map((n) => n.slug)).toEqual(["alpha", "zeta"]);
|
||||
});
|
||||
|
||||
it("does not mutate its input", () => {
|
||||
const input = [
|
||||
meta({ slug: "a", publishedOn: "2026-01-01" }),
|
||||
meta({ slug: "b", publishedOn: "2026-02-01" }),
|
||||
];
|
||||
sortNotesNewestFirst(input);
|
||||
expect(input.map((n) => n.slug)).toEqual(["a", "b"]);
|
||||
});
|
||||
|
||||
it("returns an empty list for no notes", () => {
|
||||
expect(sortNotesNewestFirst([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findNoteBySlug", () => {
|
||||
const notes = [meta({ slug: "a" }), meta({ slug: "b" })];
|
||||
|
||||
it("returns the matching note", () => {
|
||||
expect(findNoteBySlug(notes, "b")).toEqual({ ok: true, value: notes[1] });
|
||||
});
|
||||
|
||||
it("returns not-found for an unknown slug", () => {
|
||||
expect(findNoteBySlug(notes, "c")).toEqual({
|
||||
ok: false,
|
||||
error: "not-found",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateNoteMeta", () => {
|
||||
it("accepts valid metadata", () => {
|
||||
expect(validateNoteMeta(meta())).toEqual({ ok: true, value: meta() });
|
||||
});
|
||||
|
||||
it("accepts a summary of exactly 200 characters", () => {
|
||||
expect(validateNoteMeta(meta({ summary: "x".repeat(200) })).ok).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects a summary of 201 characters", () => {
|
||||
expect(validateNoteMeta(meta({ summary: "x".repeat(201) }))).toEqual({
|
||||
ok: false,
|
||||
error: ["summary-too-long"],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["uppercase", "Gate"],
|
||||
["spaces", "gate pipeline"],
|
||||
["underscore", "gate_pipeline"],
|
||||
["leading hyphen", "-gate"],
|
||||
["trailing hyphen", "gate-"],
|
||||
["double hyphen", "gate--pipeline"],
|
||||
["empty", ""],
|
||||
])("rejects a slug with %s", (_label, slug) => {
|
||||
expect(validateNoteMeta(meta({ slug }))).toEqual({
|
||||
ok: false,
|
||||
error: ["invalid-slug"],
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["gate", "gate-pipeline", "a1-b2", "2026-review"])(
|
||||
"accepts kebab-case slug %s",
|
||||
(slug) => {
|
||||
expect(validateNoteMeta(meta({ slug })).ok).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["blank", " "],
|
||||
["empty", ""],
|
||||
])("rejects a %s title", (_label, title) => {
|
||||
expect(validateNoteMeta(meta({ title }))).toEqual({
|
||||
ok: false,
|
||||
error: ["empty-title"],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["wrong format", "01.10.2026"],
|
||||
["impossible day", "2026-02-30"],
|
||||
["impossible month", "2026-13-01"],
|
||||
["trailing text", "2026-10-01T00:00"],
|
||||
["empty", ""],
|
||||
])("rejects a publishedOn with %s", (_label, publishedOn) => {
|
||||
expect(validateNoteMeta(meta({ publishedOn }))).toEqual({
|
||||
ok: false,
|
||||
error: ["invalid-date"],
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts a leap day", () => {
|
||||
expect(validateNoteMeta(meta({ publishedOn: "2028-02-29" })).ok).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects a leap day in a non-leap year", () => {
|
||||
expect(validateNoteMeta(meta({ publishedOn: "2027-02-29" })).ok).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it.each([0, -1, 1.5])("rejects reading minutes %s", (readingMinutes) => {
|
||||
expect(validateNoteMeta(meta({ readingMinutes }))).toEqual({
|
||||
ok: false,
|
||||
error: ["invalid-reading-minutes"],
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts one reading minute", () => {
|
||||
expect(validateNoteMeta(meta({ readingMinutes: 1 })).ok).toBe(true);
|
||||
});
|
||||
|
||||
it("reports every problem at once, in a stable order", () => {
|
||||
expect(
|
||||
validateNoteMeta(
|
||||
meta({
|
||||
slug: "Bad Slug",
|
||||
title: "",
|
||||
summary: "x".repeat(201),
|
||||
publishedOn: "nope",
|
||||
readingMinutes: 0,
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: [
|
||||
"invalid-slug",
|
||||
"empty-title",
|
||||
"summary-too-long",
|
||||
"invalid-date",
|
||||
"invalid-reading-minutes",
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateNoteRegistry", () => {
|
||||
it("accepts a list of valid, unique notes", () => {
|
||||
const notes = [meta({ slug: "a" }), meta({ slug: "b" })];
|
||||
expect(validateNoteRegistry(notes)).toEqual({ ok: true, value: notes });
|
||||
});
|
||||
|
||||
it("prefixes each problem with the slug of the offending note", () => {
|
||||
expect(
|
||||
validateNoteRegistry([
|
||||
meta({ slug: "ok" }),
|
||||
meta({ slug: "bad", title: "" }),
|
||||
]),
|
||||
).toEqual({ ok: false, error: ["bad: empty-title"] });
|
||||
});
|
||||
|
||||
it("reports duplicate slugs once per repeated note", () => {
|
||||
expect(
|
||||
validateNoteRegistry([meta({ slug: "a" }), meta({ slug: "a" })]),
|
||||
).toEqual({ ok: false, error: ["a: duplicate-slug"] });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import type { Result } from "./availability";
|
||||
|
||||
export type NoteMeta = {
|
||||
readonly slug: string;
|
||||
readonly title: string;
|
||||
readonly summary: string;
|
||||
readonly publishedOn: string;
|
||||
readonly readingMinutes: number;
|
||||
readonly tags: readonly string[];
|
||||
};
|
||||
|
||||
export type NoteMetaError =
|
||||
| "invalid-slug"
|
||||
| "empty-title"
|
||||
| "summary-too-long"
|
||||
| "invalid-date"
|
||||
| "invalid-reading-minutes";
|
||||
|
||||
const KEBAB_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
const MAX_SUMMARY_LENGTH = 200;
|
||||
|
||||
const isRealIsoDate = (value: string): boolean => {
|
||||
const parsed = new Date(`${value}T00:00:00Z`);
|
||||
return (
|
||||
!Number.isNaN(parsed.getTime()) &&
|
||||
parsed.toISOString().slice(0, 10) === value
|
||||
);
|
||||
};
|
||||
|
||||
export function sortNotesNewestFirst<T extends NoteMeta>(
|
||||
notes: readonly T[],
|
||||
): readonly T[] {
|
||||
return [...notes].sort((a, b) =>
|
||||
a.publishedOn === b.publishedOn
|
||||
? a.slug.localeCompare(b.slug)
|
||||
: b.publishedOn.localeCompare(a.publishedOn),
|
||||
);
|
||||
}
|
||||
|
||||
export function findNoteBySlug<T extends { readonly slug: string }>(
|
||||
notes: readonly T[],
|
||||
slug: string,
|
||||
): Result<T, "not-found"> {
|
||||
const found = notes.find((note) => note.slug === slug);
|
||||
return found ? { ok: true, value: found } : { ok: false, error: "not-found" };
|
||||
}
|
||||
|
||||
export function validateNoteMeta(
|
||||
meta: NoteMeta,
|
||||
): Result<NoteMeta, readonly NoteMetaError[]> {
|
||||
const errors: readonly NoteMetaError[] = [
|
||||
...(KEBAB_CASE.test(meta.slug) ? [] : (["invalid-slug"] as const)),
|
||||
...(meta.title.trim() === "" ? (["empty-title"] as const) : []),
|
||||
...(meta.summary.length > MAX_SUMMARY_LENGTH
|
||||
? (["summary-too-long"] as const)
|
||||
: []),
|
||||
...(isRealIsoDate(meta.publishedOn) ? [] : (["invalid-date"] as const)),
|
||||
...(Number.isInteger(meta.readingMinutes) && meta.readingMinutes >= 1
|
||||
? []
|
||||
: (["invalid-reading-minutes"] as const)),
|
||||
];
|
||||
return errors.length === 0
|
||||
? { ok: true, value: meta }
|
||||
: { ok: false, error: errors };
|
||||
}
|
||||
|
||||
export function validateNoteRegistry<T extends NoteMeta>(
|
||||
notes: readonly T[],
|
||||
): Result<readonly T[], readonly string[]> {
|
||||
const metaErrors = notes.flatMap((note) => {
|
||||
const result = validateNoteMeta(note);
|
||||
return result.ok ? [] : result.error.map((e) => `${note.slug}: ${e}`);
|
||||
});
|
||||
const duplicateErrors = notes
|
||||
.filter(
|
||||
(note, index) => notes.findIndex((n) => n.slug === note.slug) !== index,
|
||||
)
|
||||
.map((note) => `${note.slug}: duplicate-slug`);
|
||||
const errors = [...metaErrors, ...duplicateErrors];
|
||||
return errors.length === 0
|
||||
? { ok: true, value: notes }
|
||||
: { ok: false, error: errors };
|
||||
}
|
||||
Reference in New Issue
Block a user