The Statute Rewrote the Product: A Permit Service That Had to Refuse Its Own Launch Pitch

· 6 min read · by Michael Kaminski

A self-serve product let a homeowner put a licensed contractor's name on their permit and keep their own crew. Georgia's license-lending rules made that pitch the textbook prohibited fact pattern. The product was rebuilt in a day as a contractor-of-record model: scope approval gates the documents, the intake refuses unlicensed crews and trade permits on both the client and the server, and a full refund closes the order. 63 tests, zero credentials to build, and a rule I keep relearning: design against the statute, not the pitch.

On 2026-09-02 I read the statute that governs what my product was selling, and on 2026-09-03 the product refused its own launch pitch. Everything in between is the essay.

The product is Transparent Permits, a self-serve service from a licensed residential contractor in metro Atlanta. The launch pitch was clean: the contractor's licence on your permit, you keep your crew, one site walk, the owner supervises. A customer describes the project, pays by card, and gets the licence documents in a private portal. It built and rendered with zero credentials, took payment through a server-priced checkout, and closed the order from a webhook with an idempotency ledger. It was a good build of the wrong product.

What the statute says

Georgia's licensing law and its rules prohibit lending a licence: putting your number on work you do not actually control. The 2026 changes extended the pattern to the trades from 2026-07-01. Read against the pitch, every phrase was a match. "You keep your crew" is an unlicensed crew under a licence that does not supervise them. "One site walk" is supervision in name. "The owner supervises" is the licensee stepping out of the role the licence exists to hold.

The conclusion first: the pitch was the fact pattern the rule was written to catch, and no amount of product polish changes that. The only move was to change what the product is.

What it became

A contractor-of-record model. The licensed contractor approves the scope, obtains the permit, and supervises the permitted work through inspections, with the visit count bounded in the agreement. The documents that used to unlock at payment now unlock at scope approval. Declining the scope emails a refund notice, the refund closes the order, and the portal locks.

The interesting part is what the intake refuses. Six hard stops, enforced on the client and again on the server, so a request that fails cannot be submitted by a determined browser:

  • Trade permits (electrical, plumbing, HVAC), unless the trade licensee's number is supplied.
  • Unlicensed crews performing the work.
  • Investors doing the labour themselves.
  • "Contractors" with no licence, a placeholder number, or the licensee's own number.
  • A contractor ordering on an owner's behalf without the owner's contact.
  • A subcontractor list that is missing when the work needs one.

Each stop records why it fired, so the intake tells me which pattern people are trying to buy. That is not a bug report. It is the demand signal for the product I cannot sell them.

Freeze the old agreement, version the new one

Orders placed under the launch agreement keep it. The agreement module holds version one frozen and version two dated, and the portal renders the text that applied to the order's version. A customer who agreed to one document must never find a different one in their portal. Approve and decline links are signed with an HMAC so the review page cannot be forged, and the first decision wins.

The copy sweep was the long part: home, the intent pages, the county pages, the machine-readable summary, the social card, the success page and seven email sequences all said the old thing. A grep for the launch phrasing is now a test, because copy regresses faster than code.

Why this rhymes

The compliance-review essay on this site argues that the durable move in a regulated system is to design against the statute rather than against guidance, because guidance is the layer that moves. This is the same lesson from the other side. I had designed against the pitch, which is the layer that moves fastest of all, because I wrote it. Read the statute before the landing page, and read it as a description of what the product does, not as a footnote.

Numbers

Rebuild: one working day, 2026-09-02 to 2026-09-03, evaluated on the first and shipped on the second. Tests: 63, all green with no environment configured. Schema changes: none; the new intake fields live in the existing JSON column and the scope decisions are event rows. New environment variables: none; the cron token also keys the review-link signature. Orders refunded under the change: not reported here.

The employer of the licence is a company I work with, not a client I am describing from the outside, and the licence number is deliberately absent from this essay.

All writing · Projects · Michael Kaminski


Book a Call · Download Resume · MKaminski1337@gmail.com · (404) 838-8613 · LinkedIn · GitHub · DEV · X