<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-05-03T23:31:44+00:00</updated><id>/feed.xml</id><title type="html">Kevin Highwater (née Kevin Kuchta)</title><subtitle>Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description.</subtitle><entry><title type="html">General AI Update</title><link href="/2026/05/general-ai-update" rel="alternate" type="text/html" title="General AI Update" /><published>2026-05-03T00:00:00+00:00</published><updated>2026-05-03T00:00:00+00:00</updated><id>/2026/05/general-ai-update</id><content type="html" xml:base="/2026/05/general-ai-update"><![CDATA[<p>I haven’t posted in a bit, but I feel bad leaving <a href="/2025/07/kriegspiel-tic-tac-toe">my last post</a> at the top of my blog. I think my position at the time was reasonable: in mid-2025, “I’m skeptical but curious and experimenting” was a defensible stance, but that was a million years ago.</p>

<p>In 2026 (and really, as of late 2025), approximately 100% of the code I write, professionally and personally, is written by an LLM.</p>

<!--break-->

<p>I feel like this is the point in every post about LLMs where I walk through the history of the last few years: tab complete -&gt; chat interfaces -&gt; “something changed” in the fall of 2025. But let’s skip all that.</p>

<p>Here’s where I’m at right now:</p>

<ul>
  <li>Hand-writing code is basically done. Or, at least, it’s done in the sense that hand-building chairs is done: people will still do it, but only as a hobby or point of pride. Effectively all chairs that most people sit in today are built by machines and effectively all software written from here on out will be written by LLMs.</li>
  <li>I’m really sad about that because I love writing code: the craft of it, the puzzle of it, the feeling when an elegant abstraction snaps together… I’ll probably never get paid to do that again.</li>
  <li>I’m really happy about that because I love producing software: the things it allows people to do, the problems it can solve, the creativity of turning ideas into living, dancing screens. This part of the job has never been better.</li>
  <li>I spend most of my time in an agent interface juggling a few different conversations, followed by reviewing code diffs. I use Cursor and Claude Code almost interchangeably for the agent interface.</li>
  <li>Code is cheap, so new code review is the bottleneck. Figuring out how reduce or remove code review - to produce a true <a href="https://simonwillison.net/2026/Feb/7/software-factory/">dark factory</a> - is <em>the</em> problem of the software engineering industry for the next few years. If you can safely remove that bottleneck, your team can <em>fly</em>.</li>
  <li>The answer to the dark factory problem definitely <em>starts</em> with tools for AI self-evaluation: tests, linters, formatters, type checkers, visual diffs. Smarter AIs will be a component too, as will AI review tools like Greptile or CodeRabbit. There’ll probably be a psychological component too: senior devs just “getting over the hump” of accepting no-review merges. I don’t think we’re there yet, but this seems pretty clearly the direction we’re headed.</li>
</ul>

<p>So, all that to say: I was skeptical but interested in mid-2025. Today, I’ve long-since accepted that software is written by AIs now and I’m excited to see how the industry changes as a result.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I haven’t posted in a bit, but I feel bad leaving my last post at the top of my blog. I think my position at the time was reasonable: in mid-2025, “I’m skeptical but curious and experimenting” was a defensible stance, but that was a million years ago. In 2026 (and really, as of late 2025), approximately 100% of the code I write, professionally and personally, is written by an LLM.]]></summary></entry><entry><title type="html">Forcing Myself to Vibe Code</title><link href="/2025/07/kriegspiel-tic-tac-toe" rel="alternate" type="text/html" title="Forcing Myself to Vibe Code" /><published>2025-07-05T00:00:00+00:00</published><updated>2025-07-05T00:00:00+00:00</updated><id>/2025/07/kriegspiel-tic-tac-toe</id><content type="html" xml:base="/2025/07/kriegspiel-tic-tac-toe"><![CDATA[<p>I’ve noticed two things recently:</p>

<ol>
  <li>People’s skepticism of “what AI-based coding is good for” is inversely proportional to how good they are at it.</li>
  <li>I am skeptical of AI-based coding.</li>
</ol>

<p>So: epistemologically speaking, I think my best path towards having a well-informed opinion about what AI-based coding is and is not good for is to “Get Gud” as the kids would say.</p>

<p><a href="https://kttt.io">kttt.io</a> is my first pass at that.</p>

<!--break-->

<p>In the interest of pushing my boundaries, I set myself the following constraints:</p>

<ul>
  <li>I will write no code.</li>
  <li>I will read no code.</li>
  <li>I will lean on the AI for anything I’ve heard people say AIs can be helpful for (thinking through things, requirements gathering, design, debugging, etc)</li>
  <li>I will avoid, as much as possible, leaning on my decade or two of experience as a software engineer to drive this process.</li>
</ul>

<h2 id="what-i-built">What I built</h2>

<p>Because I have AI trust issues, I wanted something low-stakes. I’m not ready to let an AI handle auth code sight-unseen, for example.</p>

<p>Looking through my pile of half-baked project ideas that I will totally get to some day I swear, I picked up “Kriegspiel Tic Tac Toe.” It’s a Tic Tac Toe variant based on the <a href="https://en.wikipedia.org/wiki/Kriegspiel_(chess)">Kriegspiel chess variant</a>: you can’t see your opponent’s pieces and you lose your turn if you try to make an invalid move. Inspired by <a href="https://mastodon.social/@ZachWeinersmith/111890121393299096">this Zach Wienersmith toot</a>.</p>

<p>I figure that even if the AI introduces glaring security holes, there’s not much you could really do with that: there’s no interesting data, no passwords, no emails… the worst you could really do, if you wanted to, is cheat at Tic Tac Toe!</p>

<h2 id="how-it-went">How it went</h2>

<p>I built it! You can check it out at <a href="https://kttt.io">kttt.io</a></p>

<p>The highlights:</p>

<ul>
  <li>I spent about 24 hours building it (split over a number of evenings)</li>
  <li>I think I would have taken 36-48 doing it by hand</li>
  <li>I could probably do it in 12 AI-driven hours if I started over, having gotten a lot better at this stuff</li>
</ul>

<h2 id="what-i-learned">What I learned</h2>

<p>Vibe-coding extends the <a href="https://xkcd.com/323/">Ballmer Peak</a> significantly. Several of these evenings took place while vacationing in Sonoma and after several glasses of delicious wine.</p>

<p>But more seriously…</p>

<h3 id="good-prompts-are-too-much-work---make-the-ai-write-them">Good prompts are too much work - make the AI write them.</h3>

<p>I got a lot of value by asking the LLMs things like “Here’s my overall product vision - what’s next?” and conversing with them. I didn’t learn anything new here - the AI proposed the same node/redis/react system I’d have suggested - but I was able to get a detailed description of this architecture into the context window with a <em>lot</em> less typing.</p>

<h3 id="encouraging-the-model-to-ask-me-questions-also-saves-a-lot-of-typing">Encouraging the model to ask me questions also saves a lot of typing.</h3>

<p>I <em>could</em> write three paragraphs about what I want, <em>or</em> I could write one sentence and add “ask me questions about any decisions you need made.” This gets us to the same conclusion, but lets me focus on only typing out the decisions that are not forgone.</p>

<h3 id="dump-context-to-files">Dump context to files.</h3>

<p>After a 5-6 round discussion about implementation strategies, “Write our conclusions to <code class="language-plaintext highlighter-rouge">.ai/implementation.md</code>” is a great way to capture the implementation plan into a file I can reuse. Thereafter, every new chat session starts with including that file into context so the model knows how we’re building what we’re building.</p>

<h3 id="long-term-workplans-keep-distractible-ais-on-track">Long-term workplans keep distractible AIs on track</h3>

<p>Before I wrote any code, but after talking through the requirements and implementation, I told the model to write out a TODO list. I iterated on this a bit until I landed on a <a href="https://github.com/kkuchta/kttt/blob/39aab9575d339550f676668e5db427d2618fd4de/.ai/worklog.md">nested markdown check list</a>. This meant that I could start every session with “Here are the 4-5 context files you need - take a look at the worklog and tell me what’s next.” . Most of the time it’d propose a reasonable next step and I could just say “Sounds good, go for it.”</p>

<h3 id="short-term-worklogs-keep-dumb-ais-from-spinning-their-wheels">Short-term worklogs keep dumb AIs from spinning their wheels</h3>

<p>Whenever I’m debugging something myself, I keep a running file of theories, symptoms, and things I’ve figured out. Turns out this works for the AIs too! Telling the models to “keep a log in <code class="language-plaintext highlighter-rouge">.ai/game-rejoin-bug.md</code> of what you’ve tried and what you’ve learned” <em>drastically</em> reduced the amount of time they spent going in circles while debugging.</p>

<h3 id="fast-iteration-cycles-are-worth-their-weight-in-gold-for-an-llm">Fast iteration cycles are worth their weight in gold for an LLM.</h3>

<p>A failing unit test along with “here’s how you run this unit test” will let an AI fix a bug much faster than a human-in-the-debugging-loop situation where, after every plausible fix, I have to manually check if it worked. I found linters, automated testing, and type checking to be pretty valuable versions of this.</p>

<h2 id="some-things-i-still-need-to-figure-out">Some things I still need to figure out:</h2>

<p>I still have no idea which models are good for what. I did this entirely with Claude Sonnet Max in Cursor. Would I have been faster with Gemini or whatever? Maybe! I need to experiment more.</p>

<p>I also need to learn more/better tools. I used only Cursor for this, but I hear Claude Code is cool. Async agents are the new hotness now too. They <em>sound</em> like a terrible idea - but I’ve learned I should reserve judgment until I actually try it in earnest. Skill with these AI tools <em>does</em> matter.</p>

<p>I still need to figure out how to make the AI keep its own context docs up to date. I built 4-5 of these markdown docs describing how to do design, implementation, etc, but struggled to get the AI to remember to update them when we made new decisions (eg “stop using too many fscking emojis!”). Maybe some better-written Cursor Rules would be good here?</p>

<p>I also need a better way for the AI to evaluate the results of its work in the browser. I spent quite a while trying to get Claude to debug a socket reconnection issue that you could only really see by opening two separate browser windows and clicking a dozen different buttons. I messed around with having the AI follow a written test plan using Puppetteer over MCP and it <em>sorta</em> worked, but it was super slow and unreliable. In the end, I convinced the AI to just tell me when it wanted to test something in-browser and I’d report the results. Not great.</p>

<h2 id="conclusions">Conclusions</h2>

<p>AI-driven development is much better than I gave it credit for, though I still have a ways to climb up the skill curve here.</p>

<p>Kriegspiel Tic Tac Toe exists and is kinda fun! I never wrote or read a line of code. It’s… fun? I think? People seem to enjoy it for 3-4 games anyway! It’s react+typescript on express+node on fly.io with redis as a datastore.</p>

<p>I definitely expected to hit a wall of complexity at some point where the AI just couldn’t be useful. I hit a few walls that looked like that at first - bugs it couldn’t fix or features it couldn’t nail - but every time I got past it by improving my own context engineering skill. This isn’t a particularly huge app, though, so the wall may still be there just out of sight.</p>

<p>As such, I’m still not sure how well this kind of development can be applied outside of a toy project. I feel like I’d need to level up my context engineering skills a <em>lot</em> before I can get value like this on a large, mature, production codebase. I also don’t think I’m anywhere near being able to trust the AIs with anything sensitive (user data, auth, etc) unsupervised.</p>

<p>But I will say this: my skill with AI coding has gone up and my skepticism has gone down. I’ll have to see if that trend holds over time.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’ve noticed two things recently: People’s skepticism of “what AI-based coding is good for” is inversely proportional to how good they are at it. I am skeptical of AI-based coding. So: epistemologically speaking, I think my best path towards having a well-informed opinion about what AI-based coding is and is not good for is to “Get Gud” as the kids would say. kttt.io is my first pass at that.]]></summary></entry><entry><title type="html">Startup technical choices that I endorse or regret</title><link href="/2024/02/startup-decisions-endorse-or-regret" rel="alternate" type="text/html" title="Startup technical choices that I endorse or regret" /><published>2024-02-22T00:00:00+00:00</published><updated>2024-02-22T00:00:00+00:00</updated><id>/2024/02/startup-decisions-endorse-or-regret</id><content type="html" xml:base="/2024/02/startup-decisions-endorse-or-regret"><![CDATA[<p>One of the coolest things about working at a tiny startup is getting to make foundational technical decisions and seeing how they play out.  A couple years in startup time is <s>as long as a life-age of the earth</s> like a decade in normal business time.  It lets you get feedback on how those decisions worked out as the company + team scale.</p>

<p>I was the first eng hire at Daybreak Health. 3 years later I was managing a team of 8 in a 70-person company.  Here are the decisions I’m glad of and here are the ones I regret.</p>

<!--break-->

<p>Credit to <a href="https://cep.dev/posts/every-infrastructure-decision-i-endorse-or-regret-after-4-years-running-infrastructure-at-a-startup/">this great post by Jack Lindamood</a> for inspiring this format.</p>

<h2 id="context">Context</h2>
<p>To give you a feel for the situation: I created and architected our SPA web frontend from scratch in my first couple weeks after joining Daybreak in 2021.  By “from scratch,” I mean I did everything from initializing the git repo onward.  I later took ownership over our React Native mobile app.  And while I was the frontend lead, there was also a ton of work to do in the Rails backend, so I spent 30-40% of my time on that as well.  As such, most of the below decisions were mine to make for better or worse.</p>

<h2 id="choosing-react-endorse">Choosing React: Endorse</h2>
<p>Having gone through the age of <a href="https://react.dev/">React</a>, then Angular before that, then Bootstrap before that, then “Jquery Soup” before that… I’m a little wary of betting on a frontend framework and watching it die.  Thankfully, even by 2021 it was clear that React had more staying power than its predecessors.  There were competitors like Vue, but I had React experience and it seemed like React was the safest bet for community longevity.  3 years later, I’d say that worked out well.  We had no trouble hiring for React experience, the React community has only grown, and React is still top dog in the frontend world.  Looking forward, I’d make that bet again today.</p>

<h2 id="choosing-tanstack-query-for-state-management-endorse">Choosing Tanstack Query for state management: Endorse</h2>
<p>For most non-trivial React apps you need something to manage state or data that more than one component needs to read or write.  In 2021, Redux was the popular solution for this.  Redux is, at its core, a pretty simple pub-sub system – something I’ve always appreciated about it.  However, in my experience, to actually build anything <em>useful</em> with Redux, you need to build a pretty heavy abstraction layer on top of it.  And since everyone builds this abstraction layer differently, actually <em>using</em> Redux is pretty complicated.</p>

<p>Thankfully, it turns out that the overwhelming majority of state in a lot of frontend codebases is “data we just received from an api call to a server.” <a href="https://tanstack.com/query/latest">Tanstack Query</a> (formerly React Query) is a tool explicitly for managing that kind of state, and it does it <em>really</em> well.  My plan for Daybreak’s frontend state management was “use TanstackQuery for api state and use a simple React Context for any remaining pieces of global state.” In practice, we had exactly one piece of non-tanstackquery global state ever, so this worked out well.</p>

<p>There <em>was</em> a medium learning curve as new developers onboarded to our state management system.  Probably less than if we’d used Redux, though.  I <em>did</em> put a very thin wrapper around Tanstack Query to make it automatically implement some of our api handling (deserialization, error handling, metadata handling, etc); in retrospect I’d make that wrapper even <em>thinner</em>, such that our engineers were directly calling Tanstack Query functions (<code class="language-plaintext highlighter-rouge">useQuery</code> and <code class="language-plaintext highlighter-rouge">useMutation</code>) in their components.</p>

<h2 id="choosing-typescript-mostly-endorse">Choosing Typescript: Mostly Endorse</h2>
<p>Bias warning: I’m a <a href="https://www.typescriptlang.org/">Typescript</a> convert.  Despite being a <a href="/talks">long-time rubyist</a>, I’ve fallen in love with Typescript and <a href="https://github.com/kkuchta/TSpell">know its ins and outs pretty well</a>.  As such, it was an obvious choice for me.  Did it pay off?</p>

<p>During the early period where I was working on the frontend alone: it definitely did. You can find a better-written defense of typed languages elsewhere, but I found it made refactoring easy, made changes much safer, made catching minor bugs faster, and powered truly excellent autocomplete.  The setup time (hooking it into VS Code and our CI/CD) was half a day, and then the ongoing cost of adding type annotations where needed was near trivial.  I probably spent an hour lost in a type puzzle once every 1-2 months, but that was easily made up for in other time savings.</p>

<p>When other devs joined the codebase, though, the cost of Typescript went up.  To be clear: these were smart, capable, fullstack developers. But each had only a passing familiarity with Typescript to start.  Since Typescript is, in some sense, a tool that does nothing but tell you “no,” it’s a frustrating experience trying to work in a large, typed codebase if you don’t have pretty solid Typescript experience.  They ran into problems understanding TS errors (very fair), understanding more complex type features (eg type generics), and knowing where it was/wasn’t acceptable to cut type safety with <code class="language-plaintext highlighter-rouge">any</code> and <code class="language-plaintext highlighter-rouge">as</code>.</p>

<p>Doing it again, I’d do two things differently:</p>
<ol>
  <li>Encourage everyone working the codebase on non-trivial features take a mid-level Typescript course.  Maybe <a href="https://www.executeprogram.com/courses/everyday-typescript">ExecuteProgram’s “Everyday Typescript” program</a> or something similar.</li>
  <li>Avoid complex types, even in library code.  I <em>really</em> like having as-complete-as-possible type safety throughout the app, from UI to api requests, but in practice it meant other devs had a hard time tracing through the library code I wrote.  I might adjust this opinion with a team of more frontend-focused engineers, but it represented an unacceptable cost on a team of fullstack engineers.</li>
</ol>

<h2 id="setting-up-frontend-visual-diff-testing-endorse">Setting up Frontend Visual Diff Testing: Endorse</h2>
<p>In my experience, your testing strategy is something you have to set up from day one in a tiny startup.  There’s never going to be time to go in and add a full suite of tests to a mature codebase, but there <em>can</em> be time to add tests for each new feature to an already-tested codebase, especially if the policy is “we always add tests of <some specific="" kind=""> as a standard part of writing features."</some></p>

<p>With that in mind, I decided that our frontend quality assurance would come largely from Typescript and visual diff tests.  For the latter, we used <a href="https://storybook.js.org/">Storybook</a> such that every react component in the app could be viewed independently of anything else (this was also a nice devex improvement).  We then used a hosted service called <a href="https://www.chromatic.com/">Chromatic</a> which rendered all our components in Storybook and compared the results between commits as part of our CI process.  This meant that if a frontend change caused some bit of UI in some random corner of the site to change height/color/font/whatever, it’d show up as a failing check on the relevant github PR.  Then someone would go into chromatic, review the changed UI, and mark that change as “yep, I did actually mean to change that.”</p>

<p>Visual diff testing caught a number of small regressions over the years and was relatively non-flakey.  More importantly, though, it gave us a <em>ton</em> of certainty when making refactors or ripping out unused code: if the visual diffs haven’t changed and typescript still type-checks cleanly, we can be very sure nothing weird broke.</p>

<h2 id="auth0-for-authentication-regret">Auth0 for Authentication: Regret</h2>
<p>Auth is the same everywhere, so why not just use an off-the-shelf auth thing?  We used <a href="https://auth0.com/">Auth0</a> and it was a pain in the butt.</p>

<p>On one hand, it gave us some nice features out of the box that we would have had to build ourselves: social login with google, SAML integration with Salesforce, some nice access logging, and team/access management for our internal users.</p>

<p>On the other hand, it really tied us down in terms of user experience:</p>
<ul>
  <li>The login flow that Auth0 <em>heavily</em> steers you towards means that users are redirected off your site, to Auth0, then back again.  You have pretty minimal control over what that UI flow looks like and how it behaves, which made it nearly impossible to do anything when end users were confused by that UI.</li>
  <li>Auth0 has its own concept of “users” and it didn’t <em>always</em> match up with our own.  If a new user successfully signed up in the Auth0 UI, but failed to provision in our system, they’d end up in a weird state thereafter where login would <em>half</em> work.</li>
  <li>The fact that you could log in via “login with google” as ‘example@gmail.com’ <em>or</em> “login with username/password” as ‘example@gmail.com” and those were two separate users in auth0’s system was a source of endless confusion to a number of our less technical users.  They often wouldn’t remember which one they used when they signed up several weeks ago.</li>
</ul>

<p>Throw in the fact that Auth0’s frontend SDKs were A) immature and B) regularly being replaced and I ultimately would not recommend it again.</p>

<p>I’m not sure what I <em>would</em> recommend at this point, to be frank.  Spend the time to build your login/signup from scratch like your noble forbearers?  Try your luck with Okta (which now owns Auth0 anyway), Cognito, or one of the other misc services?  I dunno at this point.</p>

<h2 id="heroku-for-rails-hosting-endorse-i-guess">Heroku for Rails Hosting: Endorse, I guess.</h2>

<p>Heroku’s still nice and easy for hosting a rails app, a postgres database, and a redis install.  It’s got some nice, easy addons for when you have random other needs.  It’s been that way for the last decade and hopefully it will continue to be.  It’s fine.  It gets expensive, but by the time you get there, you can hire a devops person to run your stuff in AWS.</p>

<p>I’ve heard good things about <a href="https://render.com">render.com</a> and <a href="https://fly.io">fly.io</a> as heroku replacements and might try one of those at some point, but I’d probably start a new startup on Heroku unless I had a strong, idiosyncratic need.</p>

<h2 id="netlify-for-frontend-hosting-endorse">Netlify for Frontend Hosting: Endorse</h2>
<p>Sure, you can host your frontend on pretty much anything- flat files are easy.  Just throw them on S3!  But you probably also want a CDN to speed things up, so I guess you want to set up Cloudfront or similar on top of that.  And you probably want SSL, so you’ll need something to provide that (maybe ACM if you’re all-in on AWS).  And then you need to actually build your frontend somewhere (once having devs create the production frontend builds on their dev machines becomes too much of a pain), so maybe set up a service for that?  And it’d be nice to have a git-based deploy flow, so I suppose you can use github actions or something for that.  And then maybe you want more granular deploy permissions, asset header overrides, deploy audit logging, multiple environments, per-PR deployments…</p>

<p>It turns out you <em>can</em> host flat files anywhere, but there are a bunch of small quality-of-life improvements that are super handy for actually managing + deploying a frontend codebase.</p>

<p>I’ve switched to using Netlify for all my frontends (including this blog, as of 2024).  It has all those niceties at a pretty cheap price with a UI that’s straightforward (while still being solidly aimed at technical users). I worry that they’re drifting towards trying to become a more all-encompassing PaaS (as opposed to being “the one really good frontend PaaS), but for now I’d definitely pick them again for a new startup.</p>

<h2 id="building-impersonation-functionality-endorse">Building Impersonation Functionality: Endorse</h2>
<p>At every startup I’ve been at, we’ve run into the problem where a users report bugs with insufficient detail to understand what’s happening.  And every time, we think to ourselves: “it sure would be great to have some way to see what the user’s seeing!” And then we decide that’s a lot of work and go back to debugging by pouring through logs + analytics events.</p>

<p>At Daybreak, after about a year, we built an “impersonation” feature that let us log in as a user.  It included an audit trail and a short timeout period to prevent abuse or accidental breakage.  It took about a week for one engineer to build and probably paid for itself within 3-4 months in saved troubleshooting time.</p>

<p>Doing it again, I’d either build this feature really early or set up a session reply tool (I’ve used <a href="https://logrocket.com/features/session-replay-developers">LogRocket’s</a>, but <a href="https://www.datadoghq.com/knowledge-center/session-replay/">datadog has one</a> has one too, as do a lot of monitoring companies these days).  I’d probably advocate for doing this within the first 6 months of a startup’s lifespan - it’s easily the highest value debugging tool I’ve found for triaging + troubleshooting raw user complaints.</p>

<h2 id="overbuilding-guest-users-early-regret">Overbuilding Guest Users Early: Regret</h2>
<p>This one’s a little more Daybreak specific, but: in the early days, we were a b2c startup and we intended to have a heavyweight, pre-signup onboarding flow.  Having built one of those before, I was keen to build out a solid way to track + represent a pre-signup “guest user” in our database.  I built out a system for this, we built out the onboarding flow, and then we promptly pivoted to B2B.  This meant we ditched the onboarding flow and my nicely-designed guest user system gathered dust for a few years.</p>

<p>The lesson here is really a more general one: you can build functionality you expect to need in the near future, but the length of “near” should vary by startup maturity.  Don’t build something you think you’ll need in 6 months at a 1-year-old startup.  You can’t predict pivots and changes 6 months out at that stage.</p>

<h2 id="building-on-salesforce-regret">Building on Salesforce: Regret</h2>
<p>This will need its own blog post, but: about a year in, we migrated a ton of our internal tools (used by therapists and support staff) to Salesforce.  Specifically, we built out a lot of custom UIs and flows.</p>

<p>At first, this was a huge win.  It let us migrate off of a different SaaS that we were rapidly outgrowing, and the pace of development our Salesforce expert could build things was amazing.  I knew it would come with headaches and limitations down the road, but I ultimately thought it was a good tradeoff (made by the CTO, not me).</p>

<p>After about a year, though, the problems with this approach became clear:</p>
<ol>
  <li>Salesforce is an engine that runs on money.  Any given problem (logging, auth, pdf generation, duplicate management, UI improvements) you can think of has a solution, and it’s always to shell out hundreds to thousands of dollars per month to Salesforce itself or one of a bevy of third party providers.  By the end, we were paying a fully-loaded engineer’s salary to keep our Salesforce setup running.</li>
  <li>Salesforce development practices are not nearly as mature as software development practices.  You want “local” development?  There’s no such thing - all development is done on Salesforce itself, often in limited sandbox environments, resulting in a lot of “testing on prod.” You want any sort of decoupling?  Nope, everything in SF is couple as hell, meaning every change is high-risk and needs to be managed by extremely senior Salesforce Architects.</li>
  <li>Building a significant portion of our app (all the internal tools) in Salesforce put a big, hard skillset divide in the middle of our team.  Any new feature that required work in both Salesforce and Appdev (as we called our rails + react codebase) required technical planning that spanned both systems.  Unfortunately, there’s pretty much no one with the required seniority in both Salesforce and Software development, so all architecture was a collaboration between two senior engineers.  You’ll always hit this point at a startup <em>eventually</em>– your frontend expert is unlikely to <em>also</em> be a backend expert <em>and</em> an infrastructure expert.  But your frontend expert <em>can</em> at least be a decent backend dev and know their way around infrastructure a bit.  That sort of generality is often fine for many years at a small startup, and it allows you to move <em>incredibly</em> fast in the early days.  The longer you can wait before putting up a sharp skill divide in the middle of your team, the better.  Building on Salesforce put a sharp (and nearly irrevocable) divide way too early, and it slowed down development a <em>lot</em> in the following years.</li>
</ol>

<p>Doing it again, I’d pay the upfront and maintenance costs of building our internal tools entirely in our codebase, rather than trying to rely on a low-code tool for a core piece of our business.  The costs (in both financial and productivity terms) were way to high.</p>

<h1 id="things-that-were-fine">Things that were fine</h1>
<p>We used a number of tools that were bog-standard: Github, Redis, Postgres, Slack, Notion - I wouldn’t write home about any of them.</p>

<p>Similarly, we used a number of tools that could have been swapped for competitors without us noticing: Airbrake for exception handling, NewRelic for log browsing, Amplitude for event analytics, Github Actions for CI/CD - they all worked fine, but I wouldn’t push back if someone argued for alternatives in the next project I work on.</p>

<h2 id="disclaimer">Disclaimer</h2>
<p>While I’m saying that I regret some of these choices, I think they were all reasonable choices given the information at the time.  I don’t think anyone was an idiot for making them!  Likewise, I’m down on a few technologies here, but the engineers we had at Daybreak were top-notch.  Our Salesforce experts were kind, capable, and often brilliant people.  Our software engineers were people I’m proud to have worked alongside.  There’s value in learning from our experience, but none of the above should be construed to denigrate anyone who participated in that experience!</p>

<h2 id="anyway">Anyway</h2>

<p>All in all, I’m pretty happy with many of the decisions I personally made (huge surprise 😉).  I don’t think we ran into <em>too</em> many forseeable landmines in our first three years, although I’d be interested to hear how some of these decisions age in another 3.  Hopefully I can harass one of my recently-ex-coworkers into writing their own version of this blog post in 2027!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[One of the coolest things about working at a tiny startup is getting to make foundational technical decisions and seeing how they play out. A couple years in startup time is as long as a life-age of the earth like a decade in normal business time. It lets you get feedback on how those decisions worked out as the company + team scale. I was the first eng hire at Daybreak Health. 3 years later I was managing a team of 8 in a 70-person company. Here are the decisions I’m glad of and here are the ones I regret.]]></summary></entry><entry><title type="html">A Blog To Last</title><link href="/2024/02/a-blog-to-last" rel="alternate" type="text/html" title="A Blog To Last" /><published>2024-02-21T00:00:00+00:00</published><updated>2024-02-21T00:00:00+00:00</updated><id>/2024/02/a-blog-to-last</id><content type="html" xml:base="/2024/02/a-blog-to-last"><![CDATA[<p>How do you build software that will last more than a decade with no maintenance?</p>

<!--break-->

<p>After about 10 years of maintaining kevinkuchta.com as a personal site slash blog, I finally had to rebuild it.  It was a statically-generated Jekyll blog and I could no longer get a sufficiently old version of ruby to run.  Updating to a recent ruby broke a few dependencies that I couldn’t fix without way more work than it was worth, and I had to admit it was time to redo this thing.  And since my goal is to maintain this site with as little effort as possible, I found myself asking the above question– how <em>do</em> I set it up so I don’t have to rebuild anything for another 20 years?</p>

<p>Here’s my current approach:</p>

<ol>
  <li>Stick to static site generation.  Flat files can be hosted anywhere for cheap.  Currently I’m using Netlify, but if they go belly-up I can switch to Cloudfront/S3 or any of a hundred other flat file hosting options with ease.</li>
  <li>Avoid Javascript.  I spend a lot of my day job in and around single-page apps which rely on complicated and ever-changing tooling.  There’s nothing inherently wrong with it, but it’s a tradeoff: you get developer productivity benefits at the cost of paying the tool churn tax.  Given that I expect <em>very</em> little development effort to go into this site after it’s built, the tool churn tax is way higher than I want to pay here!</li>
  <li>Hew close to web standards.  I think there’s a lot of value in css/js/html preprocessors and tools of that nature.  I’d much rather write HAML for HTML or SASS for css, but that’s more tooling that can break.  In fact, hacking HAML support into Jekyll was one of the things that sunk the <em>last</em> version of this blog.  As such, I’m writing raw HTML with raw, modern CSS.  I really miss basic things like SASS nesting + variable (native CSS variables are useful but still have a lot of gaps), but I’ll live.</li>
  <li>Use tools that have existed for at least a decade.  In this case, I’m sticking to Jekyll.  It kinda stagnated for a bit in the mid-2010s, but seems active and stable these days.</li>
</ol>

<p>With any luck, this blog will still be live in 2034 and won’t need any rewrites by then!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[How do you build software that will last more than a decade with no maintenance?]]></summary></entry><entry><title type="html">CSS-Only Chat</title><link href="/2019/08/css-only-chat" rel="alternate" type="text/html" title="CSS-Only Chat" /><published>2019-08-11T00:00:00+00:00</published><updated>2019-08-11T00:00:00+00:00</updated><id>/2019/08/css-only-chat</id><content type="html" xml:base="/2019/08/css-only-chat"><![CDATA[<p>So, it turns out that css background-images don’t get loaded until the relevant selector is triggered.</p>

<p>Many people might say “neat!”</p>

<p>I used it to build a bi-directional CSS-only async chat.</p>

<!--break-->

<p>For some background, it’s not hard to build a multi-directional web-based chat.  It’s practically the “Hello World” of Node.JS.  A bit of JS on the frontend, a bit of logic on the backend and you’re off to the races.</p>

<p>This thing I built does that with <em>no</em> frontend javascript whatsoever.  Just html and javascript.  The trick turns out to be abusing the http protocol and some fun properties of CSS.</p>

<p>A full technical writeup and a quick gif-demo can be found <a href="https://github.com/kkuchta/css-only-chat">on github</a>.</p>

<p>Building this was an interesting experience for me.  It started (like most of my favorite projects do) as a “what if…”.  Someone retweeed this <a href="https://twitter.com/davywtf/status/1124130932573839360">davywtf tweet</a> on using CSS pseudoselectors to send data to a server from a page with javascript disabled.  That got me thinking: if it’s possible to send nearly-arbitrary data like that, you should be able to build something like a full chat out of it.  Once I came up with a way to send data back to the frontend (using long-running http requests), it was clear it was possible.</p>

<p>I was (and still am, as of this writing) taking the summer off to travel after leaving my last gig (we got acquired, I wasn’t wild about the acquirer).  As a result, I had this idea while in a position to spend as much time as I wanted on it.  It turned out I needed a couple afternoons in a Paris cafe to get the core pieces in place and then another afternoon polishing + writing it up.  I waited until morning, US time, and tweeted about it.</p>

<p>I was unprepared for the level of response I’d get.</p>

<p>I’ve always been pretty bad at predicting how popular the things I create will be.  <a href="/2018/08/totes-not-amazon/">My AWS markov chain</a> went nowhere; <a href="https://twitter.com/kkuchta/status/983740731234729986/photo/1">My blockchain comic</a> went mildly viral.  My <a href="https://github.com/kkuchta/voyagefound">wikivoyage explorer</a> has interested exactly no one, but my <a href="/2017/07/disguising-ruby-as-javascript/">ruby js nonsense</a> has done pretty well (even becoming a <a href="https://www.youtube.com/watch?v=datDkio1AXM">RubyConf talk</a>).  Who knows what will stick when I throw it against the wall?</p>

<p>Anyway, this chat abomination became a wild success.  Top of hacker news for a good chunk of the day; tripling my twitter follower count; 5k stars on github.  Old acquaintances reached out when they recognized my name and friends told me when coworkers posted it in their work slacks.</p>

<p>I credit the success of this to:</p>

<ul>
  <li>Easy to understand.  The <a href="https://twitter.com/kkuchta/status/1125789539530956801">tweet</a> explains the whole concept and the demo gif shows it off in just a few seconds.</li>
  <li>Inherently click-baity title “CSS-only async chat” seems inherently impossible until you read the article.</li>
  <li>The underlying content is actually interesting.  It’s a legitimately clever hack (if I do say so myself).</li>
  <li>Wide pool of devs it’s relevant to.  When I do something intense in ruby, only the ruby community cares.  The pool of “devs who’ve done a bit of web work” is much, much larger.</li>
  <li>Putting my twitter handle in the gif.  This thing got reblogged and reuploaded all over the place, often with minimal explicit credit.  But since no content aggregator is going to recreate that gif, every one of them credited me anyway.</li>
  <li>Putting the writeup on github, rather than my blog.  That domain has a fair deal of credibility: when you see a github link, you have a pretty good idea of what you’re going to get vs some random dude’s blog.</li>
  <li>Writeup voice.  This is something I’ve been working on: trying to make my writeups interesting and funny.  I’ve been going for self-deprecating (“What inspired this? Chernobyl, Hindenburg, The Tacoma Narrows Bridge…”) and just a bit abrasive (“Why’s your code suck? Why do you suck?”).  The number of people who commented on the writeup (specifically the humorous FAQ at the end) was surprising.</li>
</ul>

<p>The moderately-popular things I’ve built before have tended to die out after a day or two in the sun.  Even the blockchain comic petered out after something like 3 days.  This CSS thing went on for a solid couple of weeks.  I’d be watching BSG on my ipad weeks later and I’d have to silence twitter notifications so that new follower notifications don’t keep interrupting me.</p>

<p>I’m now writing this three months after the fact and I think I can official call it “over.”  The last twitter notification I got for someone liking that tweet was 5 days ago.  I figured it was time to finally write about it a bit.</p>

<p>Looking back on it, I’ll definitely admit that the attention was fun.  Maybe I’ll try to relive it at some point by turning it into a conference talk.</p>

<p>The best aspect of all this, though, is that I now have 3 moderately-successful hacks (ruby-as-js, totes-not-aws, and this).  That constitutes a pattern.  I’m starting to become known as “that guy who does the horrible/clever things.”  I’m pretty happy about that.  Maybe that trend will continue.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[So, it turns out that css background-images don’t get loaded until the relevant selector is triggered. Many people might say “neat!” I used it to build a bi-directional CSS-only async chat.]]></summary></entry><entry><title type="html">Totes-not-amazon.com&amp;amp;colon; Markov Chain Generation for AWS Announcements</title><link href="/2018/08/totes-not-amazon" rel="alternate" type="text/html" title="Totes-not-amazon.com&amp;amp;colon; Markov Chain Generation for AWS Announcements" /><published>2018-08-09T00:00:00+00:00</published><updated>2018-08-09T00:00:00+00:00</updated><id>/2018/08/totes-not-amazon</id><content type="html" xml:base="/2018/08/totes-not-amazon"><![CDATA[<p>I challenge you to reliably tell the difference between <a href="https://aws.amazon.com/about-aws/whats-new/2018/07/amazon-ec2-nitro-system-based-instances-now-support-faster-ebs-optimized-performance/?fc=p_2">AWS</a> and <a href="https://totes-not-amazon.com/">Totes-not-amazon</a>.</p>

<!--break-->

<p>The former, of course, is AWS’s announcement blog.  It’s capably written, but AWS’s word-soup product names and features make it sometimes sound like it was written by a script.  So I built that.  Meet <a href="https://totes-not-amazon.com">https://totes-not-amazon.com</a>!  I set it up to reroute you to a reusable link so people can share particularly funny results.  Click nearly any link to generate a new post.</p>

<p>Implementation-wise, it goes something like:</p>

<ol>
  <li>An offline script scrapes all 3k+ aws posts under aws.amazon.com/about-aws/whats-new and produces a json dump of these.  This is in ruby because I just wanted to get this step done fast and that’s the language I know best at the moment.</li>
  <li>Another offline script takes that json dump of blog posts + titles and fills up a markov model for text generation.  I used the excellent <a href="https://github.com/jsvine/markovify">Markovify</a> libaray for that.  I went with python for this script because it seemed to have good markov libraries and also I needed to share some logic with step 4.  After generating the markov model, this script dumps that model to json.</li>
  <li>Now, online, when a user hits totes-not-amazon.com/, the frontent js hits an api backed by AWS API Gateway, which triggers a lambda function.</li>
  <li>The lambda function (which includes a copy of the dumped markov model json) loads up the markov model and generates a new randomized blog post + title, then returns it to the frontend JS.  I’d have used ruby here, but AWS Lambda doesn’t support that yet.  I’d have used node, but I wanted to be able to specify a seed for the randomness used in the markov model, allowing me to reproduce especially funny results by specifying a seed.  JS doesn’t allow that but python does.</li>
  <li>Back in the clientside JS, we get the result of the api call (a blog post + title) and insert it into the page.</li>
</ol>

<p>For the lambda function, I used the Serverless Framework for the first time, which was a pretty nice way of managing a lambda function.</p>

<p>For hosting + deploying the static files I used S3 + Cloudfront + ACM + Route53 (through my own <a href="http://kevinkuchta.com/2018/06/scarr/">Scarr</a> tool).</p>

<p>If you want to see the code, it’s kind of a mess, but it’s at <a href="https://github.com/kkuchta/aws_markov">github.com/kkuchta/aws_markov</a>.  For a silly one-off like this I’m unlikely to go back and clean it up unless someone really wants me too.</p>

<p>Anyway, this is just some silliness and an excudes to mess with a bunch of random tools + languages.  Nothing serious today.  Go <a href="https://totes-not-amazon.com">play</a> with it and tweet your favorites to <a href="https://twitter.com/kkuchta">@kkuchta</a>!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I challenge you to reliably tell the difference between AWS and Totes-not-amazon.]]></summary></entry><entry><title type="html">Scarr&amp;amp;colon; S3 + Cloudfront + ACM + Route53</title><link href="/2018/06/scarr" rel="alternate" type="text/html" title="Scarr&amp;amp;colon; S3 + Cloudfront + ACM + Route53" /><published>2018-06-01T00:00:00+00:00</published><updated>2018-06-01T00:00:00+00:00</updated><id>/2018/06/scarr</id><content type="html" xml:base="/2018/06/scarr"><![CDATA[<p>There are a bunch of free/cheap options for hosting static sites (just html/css/js) out there: github pages, netlify, firebase hosting - but when I want to build a bulletproof static site “for real”, my go-to toolset is S3 for hosting with Cloudfront caching in front of it.</p>

<h4 id="my-workflow-for-that-usually-looks-like">My workflow for that usually looks like:</h4>

<!--break-->

<ol>
  <li>Spend entirely too long picking a meaningful domain name like falafel.exposed</li>
  <li>Try to remember which registrar I decided I’d use for all my domains (namecheap, iwantmyname, gandhi, route53). Fail and pick one at random.</li>
  <li>Register the domain, then realize that one doesn’t support apex domains (I demand falafel.exposed, not www.falafel.exposed like some peasant). Transfer the domain to route53 which does support apex domains.</li>
  <li>Create the falafel S3 bucket.</li>
  <li>Upload my flat files detailing the falafel conspiracy to S3</li>
  <li>Remember I need to enable S3 web hosting</li>
  <li>Create a new Cloudfront distribution pointing to that S3 bucket. Wait 45 minutes for this to finish.</li>
  <li>Realize I used the wrong bucket url format. Update Cloudfront and wait another 45 minutes.</li>
  <li>Remember I wanted TLS so that Big Falafel can’t interfere with my traffic.</li>
  <li>Create an ACM certificate</li>
  <li>Verify the ACM certificate using route53. Spend 15 minutes futzing with route53’s UI.</li>
  <li>Add the cert to the Cloudfront distribution and wait 45 minutes..</li>
  <li>Remember I need to configure an index file in S3. Go back and do that.</li>
  <li>Realize I got a Cloudfront setting wrong. Fix and wait 45 minutes.</li>
  <li>Same ^</li>
  <li>Look up how to set an apex domain in route53. Get it wronge twice.</li>
  <li>Cloudfront again.</li>
  <li>Finally get the truth up at <a href="https://falafel.exposed">https://falafel.exposed</a> up, an entire afternoon later.</li>
</ol>

<p>I figured that after a few times doing this (I’ve uncovered a <em>lot</em> of food-related conspiracies), I’d automate it. There are a few pre-existing tools for parts of this, but none I could find that did the whole thing from registration through uploading and Cloudfront invalidation.</p>

<h4 id="so-i-built-scarr">So I built <a href="https://github.com/kkuchta/scarr">Scarr</a>:</h4>

<p><strong>S</strong>3<br />
<strong>C</strong>loudfront<br />
<strong>A</strong>CM<br />
<strong>R</strong>oute53<br />
<strong>R</strong>edundant letter to prevent name collision</p>

<p>You use it like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>scarr init <span class="nt">-domain</span> falafel.exposed <span class="nt">-name</span> falafelexposed
  Initializing...done
<span class="nv">$ </span><span class="nb">cd </span>falafelexposed
<span class="nv">$ </span>vim scarr.yml <span class="c"># Edit a few fields here</span>
<span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"&lt;html&gt;The deadly secret of falafel&lt;/html&gt;"</span> <span class="o">&gt;</span> index.html
<span class="nv">$ AWS_PROFILE</span><span class="o">=</span>scarr scarr deploy
  ... a bunch of aws stuff happens automatically ...
<span class="nv">$ </span>curl https://falafel.exposed
  &lt;html&gt;The deadly secret of falafel&lt;/html&gt;
</code></pre></div></div>

<p>What it’s doing under the hood is:</p>

<ol>
  <li>Registers the given domain through route53 (prompts to confirm this)</li>
  <li>Creates a TLS certificate through ACM</li>
  <li>Uses route53 DNS to validate that certificate</li>
  <li>Creates an S3 bucket</li>
  <li>Creates a Cloudfront distribution pointed to that S3 bucket using the ACM certificate</li>
  <li>Creates an apex dns record pointing to that Cloudfront</li>
  <li>Syncs the current directory to that S3 bucket and invalidates the Cloudfront cache.</li>
</ol>

<p>It’s also smart enough to detect if parts of this have already been done (eg you’ve already got the domain name in route53) and skip those parts. If you run the deploy command twice, all it does is sync the current directory to S3 and invalidate the cache.</p>

<p>Really, it’s a glorified set of shell scripts wrapped in a single command. I wanted to be able to distribute it as a binary, though, so people could use it without needing to mess with ruby/python/node dependencies, so I took it as an opportunity to finally learn Go. It’s generally a nice language - I’d forgotten how comfortable type-checking can be! On the other hand, I <em>really</em> missed ruby’s built-in collection tools. The lack of generics was weird too.</p>

<p>The code’s a hot mess. Everything’s in the same package, there’s global functions everywhere, and it’s probably about as far from idiomatic Go as you can get, but it works. And at least it eschews the single-letter variables that seem so popular in Go. Surely <em>that</em> convention came from a falafelist.</p>

<p>Anyway, I’ll try to clean it up if anyone takes an interest in it. I’m also open to expanding the functionality a bit if anyone has ideas that don’t overly complicate the main use-case. PRs are welcome, although even lazy suggestions will get a friendly ear.</p>

<p>Anyway, the binary’s at <a href="https://scarr.io/dist/scarr">https://scarr.io/dist/scarr</a> and the code’s on github at <a href="https://github.com/kkuchta/scarr">github.com/kkuchta/scarr!</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[There are a bunch of free/cheap options for hosting static sites (just html/css/js) out there: github pages, netlify, firebase hosting - but when I want to build a bulletproof static site “for real”, my go-to toolset is S3 for hosting with Cloudfront caching in front of it. My workflow for that usually looks like:]]></summary></entry><entry><title type="html">Building a url-shortener with Lambda — JUST Lambda</title><link href="/2018/03/lambda-only-url-shortener" rel="alternate" type="text/html" title="Building a url-shortener with Lambda — JUST Lambda" /><published>2018-03-31T00:00:00+00:00</published><updated>2018-03-31T00:00:00+00:00</updated><id>/2018/03/lambda-only-url-shortener</id><content type="html" xml:base="/2018/03/lambda-only-url-shortener"><![CDATA[<p>Some people, when confronted with a problem, think “I know, I’ll use AWS Lambda.” Now they have thousands of concurrent problems.</p>

<p>If you want to know how to build a sanely-architected url-shortener using AWS Lambda on top of a datastore like Postgres, this is not the post for you.  We’re going to build a Rube-Goldberg url-shortener using <em>just</em> Lambda.</p>

<!--break-->

<p>And since it may not be clear to everyone, this post contains Bad Ideas™ and Extremely Inappropriate Uses of Tools®.  Don’t try this in production.</p>

<p><img src="/assets/images/lambda_diagram.png" alt="" width="600" /></p>

<h1 id="lambda">Lambda</h1>

<p>As a brief background, AWS Lambda is a “Function as a Service” service.  You give amazon some code (a function) and they’ll run it for you — once, 1000 times, 1000 times <em>at</em> once, whatever you need.  It’s a way of running certain kinds of code in the cloud without needing to manage your own server, not even a virtual one.</p>

<p>Lambda is a very specific kind of hammer that’s extremely good at hammering a very specific kind of nail.  This blog post will not be discussing that nail.  This blog post will not discuss anything close.  Because it turns out you can use a hammer to drive a wing-nut into silly putty if you swing hard enough and are willing to get messy.</p>

<h1 id="read">Read</h1>

<p>Ok, so the first thing any url-shortener needs is link mapping.  Somewhere you need to store that <code class="language-plaintext highlighter-rouge">/123</code> maps to <code class="language-plaintext highlighter-rouge">html5zombo.com</code>.  Well, you’ve got an input and an output - that sounds like a Lambda function!  What if we just create a new Lambda function for every link mapping!  Millions of separate Lambda functions.  After a quick check to make sure there’s no limit on the number of Lambda functions you can have (besides an upper limit of 75GB of storage space), we’re off to the races.</p>

<p>As an example, the function <code class="language-plaintext highlighter-rouge">read_123</code> will just be hardcoded to return <code class="language-plaintext highlighter-rouge">html5zombo.com</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def handle(event, context):
    return { 'url': 'html5zombo.com' }
</code></pre></div></div>

<p>However, a url-shortener needs to produce links, not Lambda functions.  We want the url <code class="language-plaintext highlighter-rouge">/123</code> to trigger this function and return the url as a 301 redirect.  For that we turn to API Gateway.  It’s a poweful, flexible tool with a million options, but we’re just going to use it to hot-glue urls to lambda functions.  For our read function, we can configure a resource (<code class="language-plaintext highlighter-rouge">123</code>) and a method on that resource (<code class="language-plaintext highlighter-rouge">GET</code>) to trigger our Lambda function.</p>

<p>Unfortunately, Api Gateway wasn’t built to support thousands and thousands of separate endpoints like <code class="language-plaintext highlighter-rouge">/123</code>, <code class="language-plaintext highlighter-rouge">/124</code>, <code class="language-plaintext highlighter-rouge">/125</code>, etc.  There’s a limit of 300 resources per api.  You can request an increase on that limit, but I suspect “because I thought it would be funny” won’t be a compelling justification.</p>

<p>We’re not stuck, though—we can fix this problem the way we fix all problems: more Lambda functions!  We’ll just define one new endpoint that takes a url parameter: <code class="language-plaintext highlighter-rouge">/{id}</code> in api gateway.  That’ll execute a single Lambda function (call it <code class="language-plaintext highlighter-rouge">read(id)</code>) that will, in turn, execute the specific read function (eg <code class="language-plaintext highlighter-rouge">read_123</code>) we want.</p>

<p>In pseudocode, <code class="language-plaintext highlighter-rouge">read(123)</code> would look like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def handle(event, context):
    read_result = AWS.lambda.invoke('read_' + event.id)
    return {
        'statusCode': 301,
        'headers': { 'Location': read_result['url'] }
    }
</code></pre></div></div>

<p>Alright, so using two different types of read functions, <code class="language-plaintext highlighter-rouge">read(id)</code> and <code class="language-plaintext highlighter-rouge">read_123</code> (and <code class="language-plaintext highlighter-rouge">_124</code>, etc), we’ve got shortened links working.  Loading up <code class="language-plaintext highlighter-rouge">/123</code> in your browser tells ApiGateway to run <code class="language-plaintext highlighter-rouge">read(123)</code>, which runs <code class="language-plaintext highlighter-rouge">read_123</code>, which returns <code class="language-plaintext highlighter-rouge">html5zombo.com</code>.</p>

<h1 id="iterator">Iterator</h1>

<p>Soon we’ll want to put together a function to generate these short links.  There’s another hurdle first, though.  We need a global counter!  When a user goes to shorten a new link, we need to know what the next id (eg <code class="language-plaintext highlighter-rouge">124</code>) is.  We need to store a global counter somewhere.</p>

<p>Now, saner developers might tell you that Lambda is stateless and you can’t use it to store data.  You and I know those people just lack strength of character.  Remember: if Lambda functions don’t solve your problem, you’re not using enough of them.</p>

<p>As it turns out, all you need to do to store a global counter is write a self-updating Lambda function!  Let’s call this function <code class="language-plaintext highlighter-rouge">iterator</code>, and in pseudocode it looks like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def handle(event, context):
  i = 0
  myCode = load the code for this function from the filesystem
  myCode = myCode.replace('i = ' + i, 'i = ' + (i+1))
  AWS.lambda.updateCode('iterator', myCode)
  return i
</code></pre></div></div>

<p>Self-updating code: what could go wrong?</p>

<p><img src="https://thumbor.forbes.com/thumbor/960x0/smart/https%3A%2F%2Fblogs-images.forbes.com%2Fmarkhughes%2Ffiles%2F2016%2F01%2FTerminator-2-1200x873.jpg" alt="" width="600" /></p>

<p>Now, the observant among you might be noticing a problem (besides the obvious one that this is all horrifying):  concurrent requests to this function might overwrite each other.  A good fix for this would be to use literally anything else in technology as a datastore.  But since obviously that’s not the fix we’re going to use, we’ll just tell Lambda to limit the maximum concurrency on this function to 1 so it can never run twice in a row!</p>

<p><img src="/assets/images/lambda_concurrency.png" alt="" width="600" /></p>

<p>And now we’ve got our massively overcomplicated way of storing one number in the cloud!  Here I am using <code class="language-plaintext highlighter-rouge">apex</code>, a handy Lambda cli tool, to run the <code class="language-plaintext highlighter-rouge">iterator</code> function repeatedly.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ apex invoke iterator -a '$LATEST'
{"statusCode": 200, "body": "74"}
$ apex invoke iterator -a '$LATEST'
{"statusCode": 200, "body": "75"}
$ apex invoke iterator -a '$LATEST'
{"statusCode": 200, "body": "76"}
</code></pre></div></div>

<h1 id="write">Write</h1>

<p>Alright, we now have everything we need to create a write function - the one that actually shortens links for us.  We want to be able to send an HTTP POST request with a body like <code class="language-plaintext highlighter-rouge">{ url: 'hamsterdance.com' }</code> and receive a shortened url like <code class="language-plaintext highlighter-rouge">kmk.party/123</code> in return.  In yet more pseudocode:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def handle(event, context):
  # Grab the url we're given
  url = json.parse(event.body).url
  nextI = AWS.lambda.invoke('iterator')
  
  newFunctionBody = """
    def handle(event, context):
      return { url: ${url} }
  """
  newFunctionName = 'read_' + nextI
  AWS.lambda.createFunction(newFunctionName, newFunctionBody)
  return 'kmk.party/' + nextI
</code></pre></div></div>

<p>As you can see, every time you use this <code class="language-plaintext highlighter-rouge">write</code> function to create a new shortened link, we make an api call to generate an entirely new Lambda function with unique code.  This is totally reasonable and not at all a ridiculous misuse of an amazing tool.</p>

<p>As a brief aside, I should mention that this is a horrible security hole.  <code class="language-plaintext highlighter-rouge">url</code> is untrusted user input, but we’re just interpolating it into code and <em>running</em> that code.  We can work around that, though, by <a href="https://github.com/kkuchta/url_shortener/blob/master/functions/write/main.py#L21">base-64 encoding and decoding the url</a>.</p>

<h1 id="index">Index</h1>
<p><img src="/assets/images/lambda_diagram.png" alt="" width="600" /></p>

<p>All the pieces of this abomination are in place.</p>

<p>Now the only thing left for a proper url-shortener is a frontend!  There are plenty of simple and nearly free places to host a flat file with a bit of JS to act as that frontend so obviously we’re not going to use any of those.  Hosting a flat file is, though pointless and bizarre, quite easy on Lambda (pseudocode):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def handle(event, context):
    return {
        'statusCode': 200,
        'body': file.read('./index.html'),
        'headers': { 'Content-Type': 'text/html' }
    }
</code></pre></div></div>

<p>Index.html just contains some JS to make an HTTP POST to our write function and display the resulting short link.</p>

<p>And there it is: a url-shortener using only Lambda.  We’ve abused AWS’s api to make self-updating functions.  We’ve flooded our AWS account with single-purpose functions whose output is hardcoded.  We’ve used the wrongest possible tool to serve flat files, slowly.  We stopped <em>just</em> short of a publicly-available arbitrary code execution bug on my personal AWS account.  Let’s call it a day!  Here it is live at <a href="https://kmk.party">kmk.party</a> and here’s the <a href="https://github.com/kkuchta/url_shortener">full, non-pseudocode</a>.</p>

<p>FAQ:</p>

<p>This is really neat <strong>Yes it is thank you</strong></p>

<p>This is absolutely horrifying <strong>See previous answer</strong></p>

<p>Should I use this in production? <strong>Only if you video tape it for posterity</strong></p>

<p>WHY? <strong>Because someone said I couldn’t do it</strong></p>

<p>How would you actually go about building a url-shortener? <strong>The inspiration for this post was one called <a href="https://outcrawl.com/go-url-shortener-lambda/">Building a URL Shortener with Go and AWS Lambda</a>.  I got really excited in the moments between reading that title and realizing it was really building a shortener using Go and Lambda <em>and</em> DynamoDB.  When I get overexcited, things like this happen.</strong></p>

<p>Why python? <strong>I wrote half of this in node, but creating/updating Lambda functions requires creating a zip file which node doesn’t do natively.  Using a node library requires a huge node_modules folder that must itself get zipped up when you’re making a self-updating function like some kind of crazy person.  Switching to python, with its built-in Zipfile module, saved a lot of time and made the iterator run in under a second instead of around 5.</strong></p>

<p>I very much enjoy Code That Should Not Be <strong>Then you might like <a href="/2017/07/disguising-ruby-as-javascript/">Disguising Ruby as Javascript</a> where I wield ruby metaprogramming like a hacksaw in a horror film.</strong></p>

<p>I know a way to make this whole thing even more ridiculous <strong>Tweet at me (<a href="https://twitter.com/kkuchta">@kkuchta</a>)! The world-fire can always use more fuel.</strong></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Some people, when confronted with a problem, think “I know, I’ll use AWS Lambda.” Now they have thousands of concurrent problems. If you want to know how to build a sanely-architected url-shortener using AWS Lambda on top of a datastore like Postgres, this is not the post for you. We’re going to build a Rube-Goldberg url-shortener using just Lambda.]]></summary></entry><entry><title type="html">Disguising Ruby as Javascript</title><link href="/2017/07/disguising-ruby-as-javascript" rel="alternate" type="text/html" title="Disguising Ruby as Javascript" /><published>2017-07-20T00:00:00+00:00</published><updated>2017-07-20T00:00:00+00:00</updated><id>/2017/07/disguising-ruby-as-javascript</id><content type="html" xml:base="/2017/07/disguising-ruby-as-javascript"><![CDATA[<p>Because my parents didn’t raise me right, I decided to take <a href="/2016/05/js-in-ruby/">another</a> crack at making valid ruby that is indistinguishable from javascript.</p>

<p>Update: This post became a <a href="http://confreaks.tv/videos/rubyconf2018-ruby-is-the-best-javascript">talk at RubyConf 2018</a>.</p>

<!--break-->

<p>This is valid <em>ruby</em>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  var first = 3;
  var second = 4;

  var sum = function(a, b) {
    a + b;
  }

  console.log("Sum = ", sum(first, second));
</code></pre></div></div>

<p>Here’s the code behind it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  console = (Class.new { def log(*x); puts x.join(""); end }).new

  define_method(:var) { |random_function_name|
    var_name = local_variables.find do |local_var|
      local_var != :random_function_name &amp;&amp; eval(local_var.to_s) == random_function_name
    end
    define_method(var_name) { |*args|
      send(random_function_name, *args)
    }
  }

  class Object
    def method_missing(*args)
      skip_methods = %i(to_a to_hash to_io to_str to_ary to_int)
      return nil if skip_methods.include?(args[0])
      return args[0]
    end
  end

  def function(*args, &amp;block)
    func_name = :"func_#{rand(1000000)}"

    klass = Class.new { attr_accessor *args }
    function_block = Proc.new { |*arg_values|
      obj = klass.new
      args.zip(arg_values).each {|arg, arg_value| obj.send(:"#{arg}=", arg_value) }
      obj.instance_eval(&amp;block)
    }

    define_method(func_name, &amp;function_block)

    func_name
  end
</code></pre></div></div>

<h3 id="what-the-hell-kevin">What The Hell, Kevin</h3>

<p>Here’s an overview of the techniques we’re using:</p>

<p><code class="language-plaintext highlighter-rouge">console</code> is just an instance of a class that has a <code class="language-plaintext highlighter-rouge">log</code> function.  Pretty straightforward.</p>

<p><code class="language-plaintext highlighter-rouge">function(a, b) { ... }</code> is, rather than declaring a function, actually <em>calling</em> the function <code class="language-plaintext highlighter-rouge">function</code> with an arbitrary number of arguments and a ruby block.</p>

<p>We’re able to reference <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">b</code> here when they haven’t been defined yet by using <code class="language-plaintext highlighter-rouge">method_missing</code> on <code class="language-plaintext highlighter-rouge">Object</code> (which is the global default namespace).  When you reference some unknown identifier <code class="language-plaintext highlighter-rouge">whatever</code>, method_missing is called and returns the symbol <code class="language-plaintext highlighter-rouge">:whatever</code>.  Overriding the root method_missing is dangerous, though.  Some classes rely on the default method_missing function falling through for whatever reason, so we have to exempt them: <code class="language-plaintext highlighter-rouge">to_a</code>, <code class="language-plaintext highlighter-rouge">to_hash</code>, etc.</p>

<p>So, defining a function is actually <code class="language-plaintext highlighter-rouge">some_func(:a, :b) { ... }</code>.</p>

<p>Now how about those vars?  We could just <code class="language-plaintext highlighter-rouge">def var(_);end</code> and then var would ignore whatever we sent to it.  That’d let <code class="language-plaintext highlighter-rouge">var foo = 5</code> work, since it’d just be <code class="language-plaintext highlighter-rouge">var(foo = 5)</code>.  With var as a no-op, the local assignment sticks (and, importantly, happens before the the method_missing junk above gets triggered).</p>

<p>However, we don’t do that because we need <code class="language-plaintext highlighter-rouge">var whatever = function(...) { ... }</code> to work.</p>

<p>When we call that <code class="language-plaintext highlighter-rouge">function</code> function, we <em>could</em> return some sort of actual ruby function (eg a lambda).  However, we could only call that using ruby’s weird syntax: <code class="language-plaintext highlighter-rouge">some_func.call(4)</code> or <code class="language-plaintext highlighter-rouge">some_func[4]</code>.  But we’re hardcore javascript purists here!  We accept no substitutes!</p>

<p>Instead, what <code class="language-plaintext highlighter-rouge">function</code> does is defines a method on the global namespace with the contents of the block you gave it (eg <code class="language-plaintext highlighter-rouge">a + b</code>).  We use an anonymous class and <code class="language-plaintext highlighter-rouge">instance_eval</code> to provide the function’s arguments to the block body.</p>

<p>But wait!  <code class="language-plaintext highlighter-rouge">function</code> doesn’t know what it’s called!  When you do <code class="language-plaintext highlighter-rouge">sum = function() {...}</code>, function has no way to know about <code class="language-plaintext highlighter-rouge">sum</code>.</p>

<p>So what <code class="language-plaintext highlighter-rouge">function</code> does is it defines its method on the global namespace with a random name (eg <code class="language-plaintext highlighter-rouge">func_492041</code>) and returns that string (symbol, actually).  Then <code class="language-plaintext highlighter-rouge">var</code> picks up both the name passed (eg <code class="language-plaintext highlighter-rouge">sum</code>) and the random function name (<code class="language-plaintext highlighter-rouge">func_492041</code>) and defines a global namespace method named <code class="language-plaintext highlighter-rouge">sum</code> that just calls <code class="language-plaintext highlighter-rouge">func_492041</code>.</p>

<p><code class="language-plaintext highlighter-rouge">var</code> <em>does</em> have to get a bit clever since, if you’ll remember, calling <code class="language-plaintext highlighter-rouge">var(foo = function{...})</code> doesn’t actually pass <code class="language-plaintext highlighter-rouge">foo</code> to <code class="language-plaintext highlighter-rouge">var</code> in any way.  It just defines a <code class="language-plaintext highlighter-rouge">foo</code> local variable.  <code class="language-plaintext highlighter-rouge">var</code> <em>does</em> know the contents of foo, though: it knows it’ll be equal to whatever was passed in to it (in this case, the symbol <code class="language-plaintext highlighter-rouge">:func_492041</code>).</p>

<p>To find its variable name, <code class="language-plaintext highlighter-rouge">var</code> just looks through the local namespace’s list of variables (<code class="language-plaintext highlighter-rouge">local_variables.find</code>) and evaluates each one until it finds one that matches its input.  Once it finds that, it can define its global namespace method.</p>

<p>And so, finally, we can call the global namespace method <code class="language-plaintext highlighter-rouge">sum(3, 4)</code>, which calls <code class="language-plaintext highlighter-rouge">func_492041(3, 4)</code>, which evaluates the <code class="language-plaintext highlighter-rouge">{ a + b }</code> block in the context of a class that happens to have <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">b</code> members whose values are <code class="language-plaintext highlighter-rouge">3</code> and <code class="language-plaintext highlighter-rouge">4</code>, respectively.</p>

<h3 id="faq">FAQ</h3>

<p>Oh god why?  <strong>Some devs just like to watch the world burn</strong></p>

<p>You realize this is terrible, right?  <strong>Yes.</strong></p>

<p>Should I use this in production?  <strong>Absolutely.  Please tell me how that goes.</strong></p>

<p>I can do this with fewer/more hacks! <strong>Tweet at me (@kkuchta)!  The world-fire can always use more wood.</strong></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Because my parents didn’t raise me right, I decided to take another crack at making valid ruby that is indistinguishable from javascript. Update: This post became a talk at RubyConf 2018.]]></summary></entry><entry><title type="html">Twitter + Ffmpeg</title><link href="/2017/05/twitter-ffmpeg" rel="alternate" type="text/html" title="Twitter + Ffmpeg" /><published>2017-05-30T00:00:00+00:00</published><updated>2017-05-30T00:00:00+00:00</updated><id>/2017/05/twitter-ffmpeg</id><content type="html" xml:base="/2017/05/twitter-ffmpeg"><![CDATA[<p>Want to upload audio to twitter?  You can’t.  You’d have to upload it to soundcloud, then post that link to twitter.  Some clients will give you a nice in-app soundcloud player and some won’t.</p>

<p>You <em>can</em> upload video to twitter, though.  <!--break--> You still need to show something, though, so maybe you play your audio over a still-image video.  Ffmpeg can definitely do that, but it took me a while to figure out the right combination of settings.  Maybe this will save someone else a half hour of headache trouble- this command will merge an image and an audio file in such a way that twitter will accept it.</p>

<p>If you’re curious, this is for https://twitter.com/thescreambot and the audio is the output of amazon polly.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"ffmpeg -i audio_file.mp3 -f image2 -loop 1 -r 25 -i image.jpg -shortest -vcodec libx264 -pix_fmt yuv420p -acodec aac -y -profile:v baseline out.mp4"
</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[Want to upload audio to twitter? You can’t. You’d have to upload it to soundcloud, then post that link to twitter. Some clients will give you a nice in-app soundcloud player and some won’t. You can upload video to twitter, though.]]></summary></entry></feed>