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,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 };
|
||||
Reference in New Issue
Block a user