the story names · vibe coding · responsibility

How Jenni
came to be.

A note on names, the kind of vibe coding I want to defend, and why the human stays responsible when the AI types faster than them.

Forrest had a reason. So do my downloads.

In Forrest Gump, Jenny is the reason Forrest runs. Not the route, not the trainer, not the medal — the reason. He runs because she taught him to.

My Jenni is the same idea, just for my software. She's why every release of every project I ship lands somewhere reliable, gets verified, and is reachable from a single URL by tomorrow morning's update check.

The name is silly. I know. It also stuck the moment I typed it — and yes, with an i, deliberate. Forrest's Jenny had a y; mine doesn't. The spelling is the only thing I can keep straight at 2 a.m.

I was already doing this. Badly.

Long before Jenni had a name, I was already self-hosting downloads. Old habit, predates this stack: zip the binary, drop it in a download folder on the server, link the URL from wherever it had to be linked. It worked. It was also messy.

GitHub had a different role for me — it's where I show code to people. Pull requests, READMEs, the public face. Not where my apps go to fetch their next version. That always felt mismatched: the place where strangers read your code is not the same place where production should download from.

When WordPress plugins joined the pile of things I ship, the versioning side stopped being optional. Two plugins becomes ten becomes twenty, and every one of them needs a stable update endpoint, a verifiable hash, a virus scan that someone in legal can point to. The zip-in-a-folder pattern doesn't scale, and nobody needs another half-finished script to glue it together.

Jenni is the formal version of what I was already doing — plus the things I should have been doing all along. She also offers download widgets you can embed on external sites: upload a new version on jenni.download, the widget on someone else's page delivers the latest version with the ClamAV scan attached. Why include the scan? Why not.

Then a different problem walked in. One of my apps wanted to expose its own plug-in directory — a way for users to discover modules without me running a marketplace. Same uploads, same scans, same hashing — just a different endpoint shape on the way out. So I taught Jenni to group releases under a collection and return them as a single JSON catalog. Same machine, same trust pipeline, one new shape. That became the fourth thing she does.

A third problem walked in not long after, and it was the one I had been quietly ignoring. My auto-updater was trusting TLS for the whole stack — version number, hash, scan status, the works. The moment someone got a shell on the server, every client would install whatever the server claimed was the new release the next morning. So Jenni learned to sign every release with Ed25519, and then she learned the harder trick: a second key, kept on my laptop and never on the server, that signs the list of which signing keys are valid. Clients pin one 32-byte pubkey for years. Rotations and revocations happen without a client update. A breached server can still lie — it cannot impersonate. The full protocol lives here.

And then — because this is how it always goes — a fourth problem. The module catalog worked, but I was the only one who could fill it. People who wrote extensions for my apps had to email me a ZIP and wait. So Jenni grew an authenticated author API: a token scoped to a single collection, a submit endpoint, the same scan and signature pipeline my own uploads run, and a draft state I approve before anything goes live. The self-service frontend that turns it into a proper little marketplace — JenniHUB — is the next thing on the bench.

The kind of vibe coding I want to defend.

I asked Claude to build me a download server. That sentence makes some developers wince. They picture a junior dev pasting unreviewed AI output into production and calling it shipped. That is not what vibe coding looks like — at least not the version I'd defend.

Most people think vibe coding means typing a sentence into ChatGPT and watching files appear. That misses where the actual work is. ChatGPT, in my workflow, is for discussion — sounding out an idea, arguing about scope, talking through trade-offs before any code touches disk. The development happens elsewhere: Claude Code with a stack of Skills, plus Get Shit Done for the disciplined slow parts — milestones, plans, atomic commits, code review as its own pass.

Even there, the prompt is not "hey Claude, write me XYZ." It's a conversation. I bring the what and the why. I argue with Get Shit Done about which milestone holds which scope, and what's allowed to bleed into the next one. I tell the AI how I, as a human, picture the thing — the texture, the constraints, the parts I won't compromise on. The AI is the developer in that pairing. It can also test the finished product live.

What stays with the human is control. Reading the diff. Catching the silent failures. Knowing when to say "no, that abstraction is wrong, throw it away." Making sure no API key ends up in a public repo, no migration drops a column without a backup, no --force push lands on main at midnight. The AI types faster than me. It does not assume responsibility.

Vibe coding is cool. It also comes with responsibility — especially when the thing you build will run on someone else's stack, ship to third parties, hold their files. Jenni was an exercise in exactly that: I read every line of the upload pipeline, hardened the bits that mattered (Argon2id, zip-slip, zip-bomb caps, ClamAV, signed cookies, CSP headers, rate-limited login, non-root container, Ed25519 signing with the root key kept off the server), ran her against my own production for months before this site went up. The code is fast. The trust is slow.

That's the vibe coding I want to defend.

Already shipping. Quietly.

Jenni runs at jenni.noschmarrn.dev, my own staging-but- actually-production instance. She ships every BreznFlow release, every Schneespur update, plus a handful of smaller things. About thirty release uploads since the start of the year. Phase C is live now: the trust list is still signed and on disk (trust_version=1), the admin login is behind TOTP-2FA, the public content speaks DE and EN, and the authenticated author API is open for the first third-party submissions. Zero incidents.

She's the most-used and least-talked-about thing in my stack — which is the highest compliment I have for a piece of indie infrastructure. The day I notice she's there is the day she failed at her job.

Before she goes public.

The wp.org asset pipeline shipped — banners, icons, screenshots all flow through her now. The Ed25519 manifests and the offline trust anchor shipped too; the trust-tool that signs trust.json on a laptop already lives in the repo under tools/trust-tool/, cross-platform, single file. Since then she's grown bilingual content, TOTP-2FA with step-up on the dangerous actions, and the authenticated author API. The dated security changelog tracks every hardening choice with the day it went live on production. What's left is the boring-but-important stuff: a setup wizard plus a sample Caddyfile that makes the install a single command instead of a weekend of YAML — and JenniHUB, the self-service frontend that lets module authors manage their own uploads while I only ever approve.

Then it's a public repo, an MIT license, and — if the indie corner of the internet does what it usually does — a pile of homelab forks running her by the end of the day she lands.

06 · still cooking

If you got this far, you probably get it. If you've got a project that wants this kind of update endpoint — or you just want to swap notes on vibe coding done well — I'd genuinely like to hear from you.

info@noschmarrn.dev