The Site Is the Test: What Deploying korulang.org Caught
korulang.org is not hosted by Koru — it is served as Koru. Every request runs through a wasm reactor compiled from a Koru program, and the deploy itself is a Koru program: koruc site.k deploy. That makes the site the toolchain’s real workload, and a real workload is a test the fixture suite cannot be. One deployment session this week proved it four times over.
The pipeline is Koru code
The whole deploy — bake, embed, push, verify — is declared in a single Koru file:
// site.k — the site's private routes are elided below
import vercel
vercel:site {
name: "site",
root: "build", // baked static site (STATIC_BUILD=1 build:local)
fallback: "200.html", // shell, served ONLY for the declared routes
routes: ["/playground", "/learn"], // browser-only paths that hydrate from the shell
backend: "https://korulang-org.vercel.app",
link: "/Users/larsde/src/orisha/examples/korulang-org-deploy",
bake: ["bun", "run", "build:local"],
bake_env: ["STATIC_BUILD=1", "ORISHA_VERCEL=1"],
alias: "https://korulang.org",
verify: ["/", "/blog/orisha-faster-than-nginx", "/docs"],
// ... handler_branches / handler_imports: the site's own API routes, elided ...
} koruc site.k deploy composes the release: bake the static site, embed it into a wasm reactor, push it to the Vercel project, verify that every declared path answers with a real page — and that the x-orisha: reactor header still owns the apex. The deploy refuses a stub wearing a success.
The vision, in code
The two elided lines are the point of this post. A site’s private surface — its own API routes, its own endpoints, the machinery behind its password gate — is the SITE’s code, injected through a generic seam the library provides. The library stays generic; the site owns the site:
// the generic seam — any site's own routes, illustrated with placeholders
handler_imports: ["site/my-endpoint"],
handler_branches: ["! [POST /api/my-endpoint] req |> site/my-endpoint:handle"], That is the vision made code: the deployment is a Koru program, the server is Koru-compiled wasm, and a consumer’s private surface never leaks into the shared instrument. The site is not hosted by Koru as a showcase — it is the toolchain’s real workload, and the toolchain earns its keep precisely by staying out of the site’s business.
The first lesson: git push does not deploy
The deployment truth was documented, and still bit us: git push to the site repo builds nothing — the Git integration is disconnected, so the only publish path is the reactor deploy. A blog post flipped to draft: false, committed, and regenerated into the published index is still invisible on the live site until deploy:prod runs. The session that learned this updated the skill that taught the wrong thing: the flip is not the publish. The distinction is now load-bearing documentation, because agents follow the surface they can see.
The second lesson: a site feature was hiding in the public library
The public koru/vercel deploy library carried korulang.org’s private voice endpoint — the gate behind the site’s password-challenged drafts — as a pixie config field, a vercel:api-voice transform, a generated API route, and baked-sound URL logic. Zero other consumers existed. The site’s private feature wore a generic-looking field in the shared instrument, and it survived because the library compiles constantly — the cruft was exercised, so nothing flagged it.
The fix is structural: the library now offers a generic seam — handler_branches (site-authored router lines) and handler_imports (site modules the reactor entry imports) — and the endpoint moved into the site’s own module, sitevoice.kz, injected through the seam. The library knows nothing about pixie, voice, or the site’s routes.
The third lesson: fixtures cannot be this site
The same week, the qualified-host-types merge landed — a rule that host (Zig) types are bare only inside their declaring module, with pins:
The suite went green with the merge. The site did not. Its reactor build broke in four places no fixture compiled:
- the new rule missed orisha’s
httparena.k— a nested benchmark bundle using the parent module’s host types bare; - the merge retired the bare-transform-proc exemption, making the old inline-route pattern illegal;
- a hyphenated module name in an aliased import mangles differently at emission and at transform dispatch;
- and the last one — which I initially reported as a compiler defect and was not: the branch spelled the wrong module qualifier (
sitevoice:instead ofsite/sitevoice:— the runner’s gate matches the canonical dotted name), and the tor was missing the[pre]stage that lets the transform fire before the router consumes the branch.
Every one of those is a shape only a real site produces: nested modules, host types across boundaries, router branches that invoke compile-time transforms, the whole metacircular chain compiled twice — once for the deploy program, once for the reactor. And the last one is the honest lesson: the compiler’s refusals — KORU035 on the dotted spelling, the gate’s silent non-match, KORU050 on the bare proc — were the mechanism teaching the right shape. The fix was two lines. The toolchain was right; my spelling was not.
The test was red; it is green now
By the time this posts, the reactor builds again: koruc site.k build stages the wasm with the voice endpoint inlined into the router branch — the hash loop, the route-table lookup, the scratch buffer, all emitted where the request will actually hit them. The site is deployable. That is the point of the post: a fixture suite cannot generate the workload a site generates by being a site — and when the workload breaks, the site is where the break is real, and the fix is where the toolchain earns its keep.