Process Rigor in Developing Solutions
Building fast with AI tools gets you working software. What separates a demo from something you can trust with real users and real payment data is process — architecture, security, testing, and operational planning applied deliberately, not discovered after something breaks.
FADLtech, Inc.'s GobFrain is a chatbot that queries multiple AI models in parallel and uses a judge model to synthesize their responses into one answer. We built it the same way we build client software: with a defined architecture, security reviews, testing, and enough operational planning that the app can be trusted with real users and real payment data, not just a demo audience.
"Vibe coding", i.e. prompting an AI tool until an app runs and looks right, is a legitimate way to get something working fast. We use AI-assisted development ourselves; GobFrain's own codebase was built with AI tools in the loop throughout.
Using AI to write code gets you speed. The difference is process and engineering. Has anyone stopped to ask what happens when the happy path doesn't hold: when two requests race each other, when a cloud service enforces a limit, when a dependency needs a credential, when someone tries to do something malicious instead of something normal. That's where a defined engineering process, and the architecture and design patterns that come with it, pays for itself.
Below are specific decisions from GobFrain's build that illustrate the gap, followed by the underlying patterns and practices that made them possible.
1. Platform limits don't announce themselves in a demo
Querying five or more AI models plus a judge model can take longer than 30 seconds because the judge model has to do a lot of work after all the models respond. AWS API Gateway's HTTP API integration has a 29-second timeout. In a quick prototype hitting two models with a short prompt, this limit never surfaces, but it does show up under the exact conditions the product is designed for. We restructured the request lifecycle around it: the API returns immediately with a job ID, a background worker runs the actual model pipeline, and the frontend polls for the result. Finding a platform ceiling after users are already relying on the product is a much more expensive place to discover it than during architecture planning.
A vibe-coded build tends to stop at "it works when I test it," because a single manual test never runs five or more models at once for long enough to hit the ceiling.
2. Money and shared state require atomic operations, not "check then act"
GobFrain bills usage in tokens. A naïve implementation of reading the balance then deducting tokens works in every manual test. But it fails silently the moment two requests from the same user land close together, because both reads see the same starting balance. We used an atomic reserve-then-finalize pattern instead: tokens are reserved before any model call goes out, using a conditional database write, then reconciled against actual usage afterward. Race conditions like this are invisible until concurrent traffic exposes them, which typically means they surface in production, not in testing. By then, they've usually cost something.
An AI tool asked to "deduct tokens when a chat runs" will readily produce the read-then-write version, because it's the version that looks correct and passes a single test. Recognizing that shared, monetized state needs an atomic operation is a design decision that has to be made deliberately, not discovered by testing concurrency no one thought to simulate.
3. Architecture is a set of deliberate decisions, not whatever comes out first
Underneath the two examples above is a set of architecture and design patterns applied consistently across the codebase, not improvised per feature:
- Single-table DynamoDB, modeled around access patterns. Every entity; users, chat sessions, messages, jobs, settings; lives in one table with composite keys and purpose-built indexes for the query shapes the app actually needs. The keys were designed around the queries the app runs, not around a generic normalized schema. Vibe-coded data layers typically go the opposite direction: a table (or several) shaped by whatever query got written first, with new access patterns bolted on as they come up, and unindexed scans standing in for missing indexes.
- A service layer between handlers and AWS. Every Lambda handler talks to Bedrock and DynamoDB through dedicated service modules, never through an SDK client instantiated inline. That means the DynamoDB key scheme and the Bedrock request shape are each defined in exactly one place, not reimplemented slightly differently every time a new handler is generated.
- One function owns each cross-cutting concern. User personalization ("Custom Instructions") is converted into a system prompt by a single function used identically by every model call and the judge call. Prompt-by-prompt generation tends to produce a slightly different version of the same logic in each handler, which quietly biases results, in this case, a judge favoring whichever model happened to get a marginally different prompt, without anyone deciding that on purpose.
- Client state and server state are never the same store. UI state that needs to persist (auth, active chat, theme) lives in Zustand; anything that comes from the API is owned by React Query, with its own caching and retry behavior. Mixing the two is one of the most common shortcuts in quickly generated frontend code, and it's what produces stale data on screen after a mutation, because nothing was actually invalidated.
- Four independently deployable infrastructure stacks with an explicit dependency chain, database, auth, API, frontend, each stack receiving only the specific outputs it needs from the one before it. A frontend-only change redeploys without touching the database. A single generated CloudFormation template with everything in it can't offer that isolation.
None of this shows up in a screenshot or a demo click-through. It shows up six months in, as the difference between adding a feature in an afternoon and adding it while also untangling three other places that assumed the old shape of the data.
4. Security is a foundation you build first, and a concern continues
Some of GobFrain's security work happened as a deliberate pass: strict input validation on every API endpoint, secrets stored in a managed secrets service rather than environment variables, IAM permissions scoped to specific resources rather than broad service access, signature verification on incoming webhooks, rate limiting and a web application firewall in front of the API, output sanitization wherever user content renders in the browser, and CORS configured with no wildcard fallback. None of these are exotic. Each one addresses a specific, well-documented way applications get compromised, and each one is easy to skip when the priority is "make it work."
What matters is when that pass happened: right after the initial scaffold, before most of the product's actual features existed. Request validation with Zod, for example, wasn't added once the app was "done" and then checked off — it was established as the pattern for every endpoint from that point forward, and every schema file added since has been touched again and again as new features shipped, because every new endpoint gets a schema as part of building it, not as a follow-up task. The same is true of least-privilege IAM, CSP headers, and the audit log: they were part of the foundation the rest of the app was built on top of, not a coat of paint applied at the end.
That foundation doesn't mean security work stopped there. It kept surfacing as development continued, because new features create new surface area: guardrails had to be extended to cover file and image attachments as attachments became a feature, PII detection categories got added as new data types passed through the app, a second CORS fix landed later when a new client origin needed to be supported correctly rather than papered over, token accounting was hardened to reject a request outright rather than allow a balance to go negative, and account deletion was built as a genuine self-service flow rather than a support ticket, because a privacy-sensitive product needs that as a real capability, not an afterthought. None of those were part of the original security pass. Each was addressed as its own need showed up, because treating security as finished after one review is how the gap that a later feature opens goes unnoticed.
An AI tool will write a CORS policy that unblocks the request you're stuck on. It won't, on its own, go back afterward and ask whether that policy is still the tightest one that works, whether the IAM role it generated three prompts ago now has more access than the feature it was built for actually needs, or whether the new file-upload feature it just added needs the same scrutiny the original endpoints got. That has to be a standing habit someone owns for the life of the project, not a phase that ends once a checklist is complete.
5. Regulations aren't a single privacy policy page
GobFrain handles several distinct compliance obligations, and each one required understanding what it actually requires, not a generic "we take your privacy seriously" page. Age verification blocks anyone who confirms they're under 13 from using the service, which is a COPPA requirement, not a GDPR or CCPA one, and the three don't overlap in what they require. Consent itself is tracked per policy version, with the IP address and user agent recorded at the moment someone agreed, so if a policy changes, the record of who agreed to which version, and when, still exists. California users get an explicit "Do Not Sell My Personal Information" control, because CCPA requires that specific opt-out to exist as its own setting. GDPR's right to portability is a real data export a user can request and receive, not a support ticket that may or may not get answered. And the right to erasure is a self-service account deletion with a 30-day cancellation window and an emailed confirmation, so deletion is genuinely reversible for the period regulations expect it to be, rather than a row disappearing from a database the moment someone clicks a button.
None of that is exotic engineering. It's the result of reading what COPPA, GDPR, and CCPA each actually obligate a product to do, and building the specific mechanism each one requires, rather than one privacy policy page and an "I agree" checkbox standing in for all three. AWS resource tagging carries the same distinction through to the infrastructure layer, with data classification and applicable compliance frameworks tagged on every resource, so the scope of what's regulated is explicit rather than something someone has to reconstruct later.
A vibe-coded signup flow typically gets a checkbox for terms of service, with no version tracked and no record of when or how someone agreed to it, no age gate at all, and a delete-account button that's implemented literally: the row disappears, immediately, with no grace period and no email confirming what happened. None of that is a coding mistake in the way a bug is. It's a gap that only becomes visible when a regulator, a user, or a lawyer asks a specific question the product was never built to answer.
6. Backups aren't disaster recovery until you've tested the restore
GobFrain's database has automated daily and weekly backups with cross-region redundancy for production. That much is close to standard practice. What matters more is that we paired it with a written recovery runbook and a recurring schedule for actually running a restore, rather than treating an untested backup as equivalent to a plan. An organization only knows how long recovery actually takes, and what breaks along the way, once it has tried.
Enabling backups is a checkbox in most cloud consoles, and a vibe-coded build will often have it. Knowing your recovery time, because you've actually rehearsed it, is an operational practice, not a setting, and it's the part that gets skipped when the goal was to ship a feature, not run a fire drill.
7. We turned a deployment mistake into an enforced procedure, not a lesson someone has to remember
Deploying GobFrain means building three workspaces against the right environment and pushing infrastructure changes through CDK, and the process has a real failure mode: the default build command loads production configuration regardless of which environment you're targeting. Running it for a dev deploy once baked the wrong Cognito user pool and API URL into the bundle, and the result was a dev site that deployed cleanly but silently authenticated against the wrong pool and failed API calls with CORS errors that looked unrelated to the actual cause.
The fix wasn't a comment in a README telling people to remember to use the right build script. We built a deploy command that runs the same sequence every time, for every environment: confirm the git tree and AWS credentials before touching anything, build with the environment-specific script rather than the default, show the full CDK diff and stop if it touches anything unexpected, such as an IAM policy change or a stack that the current code shouldn't affect, require explicit confirmation before anything reaches production specifically, and, after any deploy that touches the frontend, remind whoever is running it to do a sign-in check, because a wrong configuration baked into the bundle deploys successfully and fails silently in the browser instead.
This is the difference between fixing a mistake once and fixing the conditions that produced it. A README note relies on someone reading it at the right moment, months later, under time pressure. A procedure that runs the same way every time removes the step where the mistake happens at all, and it means the fix survives even if nobody remembers the original incident.
8. You need to be able to answer "what happened" without guessing
Distributed tracing across backend services, structured access logs, and a dedicated audit trail for administrative actions were part of the build from early on. When a user disputes a token charge or an admin action needs to be reviewed weeks later, the alternative to structured logging is reconstructing events from memory, which doesn't hold up, especially for anything involving account or billing decisions.
This is easy to underinvest in precisely because it has no visible payoff until the day it's needed. A demo doesn't need an audit trail. A product with paying users and admins who can modify their accounts does, from the first day either exists.
9. Operating the app shouldn't require opening AWS directly
The admin panel is where the observability work from the previous section actually gets used, and it was built as its own piece of the product, not an afterthought bolted on once the customer-facing app worked. A dashboard shows real usage, revenue, and margin rather than placeholder numbers. User management shows each account's token balance and lets an admin grant tokens with a reason attached, and that reason is sent to the user automatically, so a balance never changes without an explanation. Support conversations sort unread-first so nothing waits in a queue because it scrolled out of view. Account deletion requests show up in a queue with a status per request, and anything overdue is flagged rather than silently missed.
The feature that gets used the moment something actually breaks is log search. Instead of an admin needing direct CloudWatch access, the right log group names, and Logs Insights query syntax, there's a search box in the admin panel: pick a time range, type a term, and it runs the underlying Logs Insights query across every GobFrain log group and returns matching entries. Next to it, a record lookup takes a user's email, user ID, or a payment ID and pulls the raw record straight from the database, whichever shape it is: profile, token history, recent chats, or a payment. That's the tool for "a user says their credit didn't show up," and it turns what used to be a query someone had to know how to write into a text box anyone on the team can use.
A vibe-coded build tends to stop at the customer-facing feature, because that's the part a demo shows. Internal tooling for the team that has to run the thing afterward, especially something like log search that only pays off during an incident, is exactly the kind of work that's invisible until the day nobody can find it and a problem takes hours to diagnose instead of minutes.
10. Production quality includes the people a demo audience doesn't include
Screen reader compatibility, keyboard navigation, and accessible labeling were built into the component structure rather than patched in afterward. Some of these issues are only found by testing with the assistive technology real users rely on, not by reading code or running a functional test suite. An app that works well for whoever built it and poorly for anyone using different tools to access it isn't finished, it's untested for a meaningful share of its actual users. A published accessibility statement documents what's supported and what isn't, which is itself a step most accessibility regulations, and a fair number of enterprise procurement checklists, expect to see.
11. Automated checks catch what a demo won't
Both the frontend and backend run in strict TypeScript mode, with linting that has to pass with zero warnings and a compile check that has to pass cleanly before anything merges. Business logic that's easy to get subtly wrong, token math, prompt construction, the pieces that decide what a user sees or gets charged, has its own test coverage, separate from UI-level tests, so a change to that logic has to prove it still behaves correctly rather than relying on someone noticing a regression by eye.
A demo doesn't need any of this, because a demo is verified once, by the person who just built it, on the one path they just exercised. A test suite verifies the same logic every time, on every change, including the changes made by someone who didn't write the original code and doesn't know which edge cases it was already handling. Vibe-coded projects are rarely short on features; they're short on anything that catches a regression before a user does, because writing a test for a failure mode requires having thought about the failure mode in the first place.
12. Documentation so the system doesn't live in one person's head
GobFrain has a deployment guide, a disaster recovery runbook with recovery time and recovery point targets, a quarterly DR drill playbook, and an observability reference documenting log group names, filter patterns, and the Logs Insights queries an admin or engineer would actually reach for during an incident. These were written as part of building the system, not reverse-engineered later when someone new needed to understand it.
The practical effect is that operating and extending the application doesn't depend on one specific person being reachable. A vibe-coded project's documentation is usually the prompt history in whoever built it's AI chat tool, which isn't something a teammate, a new hire, or a client's own future team can open and use. When the person who built it is unavailable, on vacation, has moved on, the system either has a runbook or it doesn't.
Best practices that held all of this together
A few practices ran underneath every point above, and they're worth naming directly because they're the ones most often dropped under time pressure:
- Runtime validation at the boundary. Every request body is validated against a schema on the server, not just typed on the client, so the schema is enforced, not just assumed.
- Strict typing with no escape hatches. Both the frontend and backend run in strict TypeScript mode, and that setting was never relaxed to get past a difficult error.
- Least-privilege access by default. IAM policies scoped to the specific resources a function touches, not the broadest role that makes an error go away.
- Idempotency where retries can happen. Background job retries were explicitly disabled where a retry could mean re-running paid work, rather than assuming retries are always harmless.
- Defense in depth. Input validation, a web application firewall, and API throttling all exist independently, so no single layer is the only thing standing between the app and abuse.
- Pagination on every list endpoint, bounded by design rather than by however much data happens to come back.
- Conventional, reviewable commits on feature branches, so the history of the system is legible to the next person who has to reason about it, human or otherwise.
What this means for a project
None of the practices above required exotic technology. They required treating requirements gathering, architecture, security, and operations readiness as explicit parts of the process, with patterns applied on purpose and revisited as the system grew, rather than as implicit outcomes of writing code quickly. A working prototype answers "does this do the thing." A production application also has to answer "what happens when it doesn't, when it grows, and when someone tries to misuse it," and building that in from the start is considerably cheaper than adding it after something has already gone wrong.
Why this is the part worth hiring for
Building an application with AI assistance is no longer the differentiator; the tools are widely available, and using them well is table stakes. The open question for any project, whether you're building it in-house, handing it to a freelancer, or evaluating a vendor, is whether the architecture, security review, and operational planning above happen at all, or whether the AI's output is treated as finished once it runs.
That question doesn't go away by hiring someone else to hold the keyboard. A contractor or an in-house team using the same AI tools without this process produces the same gaps we've described here: a data model shaped by whatever query came first, security handled once and then left alone, backups nobody has tested restoring, and no record of what an admin action did or why. The risk isn't who's typing. It's whether anyone on the project is responsible for asking the questions a fast-moving build tends to skip, and has the experience to know which questions those are.
That's the role we take on. Engaging FADLtech means the architecture decisions, the security review, and the operational planning in this article aren't optional extras we get to if time allows, they're the baseline we build from, on every engagement, because we've seen what it costs a project when they're skipped. We use the same AI-assisted development speed as everyone else. The difference is what happens around it: requirements clarified before code is written, trade-offs weighed instead of defaulted into, a security pass that doesn't stop being anyone's job once the first version ships, and a system built so that the person maintaining it in a year can still understand why it's shaped the way it is.
If you're weighing whether to build a project yourself, hand it to a freelancer, or bring in a team that treats this process as the deliverable rather than an afterthought, we're glad to talk through what your specific application would need.