Skip to content

Vaynerov Technologies

We don't just develop — we conjure every line of code & pixel.

All articlesEssays

Craft in the Age of Generated Code

Every studio is being asked the same question this year: if a model can write the code, what exactly are you charging for? Our answer, in the form of things our own build refuses to accept.

Edward AmirainFounder, Vaynerov Technologies
Published 8 min read
On this page
  1. Checks that run without me
  2. The model proposes; the engine decides
  3. Knowing what you did not defend against

Writing code stopped being the expensive part. Reading it never got cheaper, and that single asymmetry is the whole story of the year.

A model will produce four hundred lines of plausible TypeScript while you finish a sentence. Your capacity to hold four hundred lines in your head and ask what would have to be true for this to be wrong is exactly what it was in 2019. The bottleneck moved onto the one part of the process that does not accelerate. Every argument about AI-assisted engineering that I find interesting starts there; the ones that bore me are all secretly about whether typing was ever the point.

Generated code also fails in a particular and nasty way: it fails plausibly. Hand-written mistakes usually look like mistakes — a variable named wrong, a branch that obviously never runs, a comment arguing with the line beneath it. A model's mistakes arrive dressed correctly. The naming is good, the structure is conventional, the comments agree with the code, and the second DNS answer is never checked.

So we stopped asking who wrote a line and started asking what, in the build, would object if the line were wrong.

Checks that run without me

The Orrery propagates satellite orbits in the browser from element sets published by CelesTrak. In development, before a single pixel of that renders, a self-check runs. Hand it an ISS-class element set — mean motion 15.495 revolutions per day — and it must come back with an orbital period of 92.933 minutes and a speed of 7.660 km/s. Hand it a geostationary set and the longitude must drift 0.010° over six hours. Those numbers are not ours. They come from the physics, and they were true long before the file existed.

The value of that check is entirely in what it prevents: a satellite map that is wrong everywhere and obviously wrong nowhere. Orbital error does not announce itself. Every dot still moves smoothly, the planet still turns, and nothing on screen tells you the Kepler solver lost an iteration in somebody's tidy-up. Assertions do.

The same instinct, at play scale. Slipstream is a lane runner whose obstacle rows come from generated patterns, and a pattern that leaves no reachable lane at speed is not a hard level — it is a broken one. That defect survives playtesting beautifully, because a tester who dies assumes they were bad at it. So the patterns are validated when the module loads: propagate forward which lanes the craft can physically reach at worst-case speed, 32 units per second, and assert that every row keeps at least one of them survivable. The comment in that file is the entire philosophy in nine words — fair for the validator equals fair for the player.

What runsWhat it assertsWhy a human can't cover it
Orrery propagation self-checkISS-class orbit → 92.933 min period, 7.660 km/s; geostationary drift 0.010° over 6 hOrbital error is invisible at a glance — the map looks right while being wrong
Slipstream pattern validatorEvery obstacle row keeps a lane reachable at worst-case speedPlaytesting can fail to find the unfair row; it cannot prove none exists
Blueprint schema, bounds and sanitizer3–14 nodes, exactly 3 phases, no dangling edges, no unknown option idsModel output reads fluently even when it is structurally impossible

Three checks wired into the code paths a developer already exercises. None of them care who wrote the code they are checking.

That last property matters more than the checks themselves. Verification you have to remember to run is verification you will eventually skip, usually on the day you are moving fastest — which, now, is most days.

The model proposes; the engine decides

Blueprint is where we had to answer this commercially rather than philosophically. A visitor describes a product, Claude drafts an architecture, the page draws it and attaches a price. The obvious catastrophe is a language model inventing a number that a stranger then reasonably treats as a quote from us.

So there is a rule in that code, written as a sentence rather than a config flag: the LLM never prices anything — it only proposes selections, and even those are sanitized first.

In practice the model chooses from a closed catalog: platforms, features, AI capabilities, a design tier, an integration count. Unknown ids are dropped on arrival. Required single-choice fields that came back empty are filled from defaults. Then the arithmetic happens somewhere the model cannot reach — base units summed across the selections, multiplied by product complexity, QA level and launch scope, converted through a unit-rate band; timeline with platforms running in parallel, the remainder at fifteen units a week, plus a two-week buffer. It is the same deterministic engine behind our pricing page, reading the same rules. The enum list in the output schema, the option catalog in the prompt and the sanitizer's allowlist are all derived from that one rules object, so they cannot quietly drift apart.

The model is allowed to be imaginative about architecture. It is not allowed within reach of a number a client might act on.

Two smaller decisions in the same spirit. The visitor's idea is wrapped in tags and treated as data, not instructions, because a text box on a public page is untrusted input no matter how conversational it looks. And when a blueprint becomes a quote request, the estimate is recomputed on the server from the sanitized selections; the numbers that arrived from the browser are ignored outright. A client's price should never be a value that passed through a place a stranger can edit.

Knowing what you did not defend against

A server that fetches URLs typed by strangers is a loaded weapon pointed at your own network, and that is precisely what the Reliquary is: paste a link, our server fetches the page, parses it, and hands back an exhibit. The addresses worth attacking are not on the public internet. 169.254.169.254 is where cloud providers park instance metadata; the private ranges are wherever the rest of your infrastructure lives. We wrote about that build in June; the guard is the least glamorous part of it and the part I would defend first.

Only http and https, only ports 80 and 443, no credentials smuggled in the userinfo field. A denylist covering private, reserved, link-local and carrier-grade-NAT ranges. DNS resolved with all addresses returned and every one required to be public, because validating only the first answer is the classic near-miss. Every redirect hop re-parsed and re-validated from scratch — a public URL that redirects to a loopback address is the entire attack. IPv6 prefixes classified before the dotted-quad tail, in that order, since the reverse would wave a mapped address through on a public-looking quad.

And then the part I am prouder of. A comment in that file names the attack we did not stop: DNS rebinding. Between the moment we validate a hostname and the moment the socket connects, the answer can change to something private. Closing that properly means pinning the connection to the already-validated address, which means a custom HTTP dispatcher and a new dependency surface, and we judged that not worth it for a feature that fetches auction listings. The judgement may be wrong. It is at least written down, so the next person to read the file inherits the reasoning instead of the impression that everything is handled.

This is the part no generator does for you. A model will happily write a URL validator, and it might write a good one. What it cannot do is decide which residual risk your business is willing to carry and then say so out loud somewhere it can be argued with. Judgment is not the ability to produce the defended version. It is knowing the shape of what stays undefended, choosing it deliberately, and putting your name on the choice.

Two other things have stayed stubbornly human here. One is the invariant — the sentence a system is not permitted to violate — because invariants encode what you care about, and nothing in a training corpus knows what you care about. The other is deciding what not to build. The Reliquary keeps every exhibit in your browser's local storage and nothing at all on our servers. That cost real features: no sharing, no sync between a laptop and a phone, a hard cap of sixty exhibits, and no idea whatsoever what anybody does with the thing. No system trained on the last decade of software would have proposed refusing the data. It was a taste decision, and taste is exactly what the conjuring standard was written to protect.

Which brings me to the word everyone is using this year. Slop is not a property of AI; it is a property of review. Code that nobody can say what would have to be true for this to be wrong about is slop whether it came from a model, a contractor, or me at one in the morning. Our industry shipped oceans of it before anything could autocomplete a function. The models did not invent that failure. They lowered the cost of producing it, which is the argument for taking review more seriously, not less.

The uncomfortable part of this position is that it scales badly, and I think the people selling the future know it. Generation is nearly free and getting freer. Verification is labour, it is stubbornly linear, and it is what we are actually charging for. When a client asks what they are paying an engineer for in 2026, the honest answer is: not the typing. The refusals. The number that had to come from the calculator, the row that had to stay survivable, the risk that had to be named in a comment where someone could disagree with it.