The copy was written for someone who had read the spec. It named UTF-8, RFC
3339, canonical form, code points and frontmatter fields, and the one place it
gave instructions it gave them as fumi command lines, which say nothing to
anyone using carrier or gsmol.
Now it uses the words the clients put on screen: "header", with the "+ header"
button both of them have, and "Requests folder", which is what both call it.
One word per idea throughout — post, feed, header, Requests folder — and
reasons are whole sentences that say what happened and what to do, so the
reply no longer opens "Reason:" followed by a lowercase fragment.
The first-contact mail is the one a stranger reads before anything else, so it
now covers accepting this address: a recommendation, not the mechanism behind
it. The mechanism would also have aged badly, since a client that delivers its
token once makes accepting cost a single refused post rather than all of them.
A refused post gets a detail paragraph where one line cannot hold the fix. It
tells the author their token arrived and to send again, and what to do if it
is refused a second time, which is what happens on a client that attaches the
token to everything. A first post on an empty feed says so differently: the
author is watching their first attempt fail and needs to hear the feed is
fine.
Seven admin strings were built with format! and never reached the catalogue,
so an ack opened its body in lowercase and could not be translated. They are
entries now, and say what blocking does rather than naming the mechanism.
The cap warning said "90% of its 1048576 byte limit". It says 1 MB.
Four rig assertions matched the old wording and now match the new. They have
to read rendered mail, so they stay coupled to it in a way the unit tests,
which assert on Reason variants, do not.
Rig legs for the whole author path with no CLI admit anywhere: an unknown
sender drawing the info mail, Twt-Register creating the feed, the token
arriving so she can publish, Twt-Help, the admin notification, Twt-Block
stopping a feed and Twt-Registration closing the door. Pentest probes for the
attacks: no Reply-To, a claim on someone else's address, a claim on an address
nobody holds, an admin command from a non-admin, and a blocked author coming
back.
Two real bugs, both found by asserting on what the author receives rather than
on what the daemon meant to send.
Five catalogue entries were wrapped across lines, and a reason is interpolated
into the reply's Subject. A frontmatter value carrying a line break makes the
whole block malformed, so smolmail fails closed toward display (SPEC.md sec
5.5) and the reply arrives with no subject at all — every rejection, silently.
The same bug class the parent commit fixed sender-side; step 2 reintroduced it
from the catalogue, because the Rust prose it replaced used backslash
continuations that join without a newline. Reasons are single-line now, with a
test over the enum that fails if one ever wraps again.
And the flow had a step that could not work. Telling an author to accept this
mailbox makes their client attach an Accept token to every later mail, and a
published record is verified by rebuilding the exact bytes that were signed —
which cannot include a token without publishing it. So every post from an
author who followed that advice was refused forever. The info mail now says
not to accept the address and where to find replies instead, and that refusal
has its own reason, since "resend the bare text" is useless advice when the
client adds the field by itself.
Three of my own probes were wrong too, kept here as comments so they are not
rewritten the same way: grep -q under `set -o pipefail` SIGPIPEs its producer
and can report failure for a subject that is plainly present; the refusal
reply lands in the main tier precisely because accepting the mailbox is what
moves replies there; and blocking an address that was never accepted is a
store no-op, so the probe has to register first.