A remote author's feed needs a namespaced path or two servers' `alice` collide
on one file. valid_slug accepts `local`, `local@host` and `local@host:port`;
local authors keep their bare path, so the published feed keeps its URL and
nothing migrates.
valid_username is untouched and is now only the nick rule: a nick reaches
`# nick =` metadata that other twtxt clients parse, so widening it with the
path rule would have leaked `@` and `:` in there. A test pins the two apart.
The traversal guard changes character here. A path could not contain a dot
before, which is why `/twtxt/../etc/passwd` 404'd for free; dots are legal now,
so the rejections are explicit and path() also asserts the join stayed inside
the feed directory. username_of rejects `/` before decoding and decodes only a
literal `%40`, since some twtxt clients encode the `@`.
Incidentally fixes a legal smolmail user never being able to get a feed: the
old rule banned `.` and `_`, so `admit alice.b@smol.place` failed at create.
dispatch and apply_commands returned English prose that became the reply
subject, so the pure layers owned author-facing copy. They now return
reason::Reason and the mail-building edge resolves it through the catalogue.
core::rules needed no change: RuleError was already a typed enum, and its
Display stays for author-side tools. The daemon maps the variants itself
rather than borrowing that prose.
Error-path tests now assert on variants, so rewording main.ftl cannot break
them, and PruneModeUnknown carries the value the author actually sent.
Reason::every_variant_resolves_to_copy walks the enum, so a variant added
without a catalogue entry fails in tests rather than mailing someone
"???reason-…???".
Ports carrier's src/i18n.rs and its locales/en-US/main.ftl layout, same
fluent-bundle and unic-langid versions, so the two catalogues stay portable
between them. Only mail the daemon sends an author moves; operator CLI output
stays inline, since the operator runs the binary and reads English.
The error-reply reasons from dispatch and ApplyError are deliberately left
behind: dispatch is pure by contract, so those become reason identifiers
resolved at the mail-building edge in a separate change rather than having the
pure layer reach for a bundle.
Adds a test carrier lacks. FluentResource::try_new and add_resource both panic
on a malformed or duplicate-id catalogue, and nothing touches the bundle until
the daemon first mails someone, so without it a bad catalogue surfaces inside
poll() on a live instance.
Sentences are verbatim; the bodies are now hard-wrapped where they were single
long lines, which reads better in a plain-text client.
packages.static is a static musl binary so a low-power deploy target can
fetch a prebuilt one rather than compile Rust; apps.release-static publishes
it to a Forgejo release, guarded on HEAD carrying the version's tag.
fumi-core is a path dependency on ../fumi, which escapes the flake source, so
fumi is vendored as its own input and re-laid beside the source tree in the
store. Over https, not a local path: a flake importing this one has to resolve
this input too when it locks.
services.smolboid runs as a real user rather than DynamicUser, because admit,
block and the fumi bootstrap are operator commands against the same master and
store and need a stable uid to sudo into.