Selected work

Workshop: a secure upload feature, built by an AI agent under gates

How I'd take a team from a short brief to a working, secure feature where an AI coding agent writes the code and people stay in control: decisions with trade-offs, a plan in modules, gates on every request, and a pipeline that proves it.

A personal project, built in Next.js, TypeScript and PostgreSQL and structured as material for a client workshop. The scenario below is illustrative, not a client engagement. The code was written by an AI coding agent under my direction, against the plan, decisions and security rules I set.

The app's sign-in screen: username, password and a Sign in button, with a link to create an account.
The running app. Screenshots on this page use demo accounts and sample files created for them.

The scenario

A regulated financial-services company wants its customers to upload sensitive documents. Only the person who uploaded a file, or an administrator, may ever see it. Other developers, juniors included, must be able to continue the work safely.

The constraints are the kind I worked under at AutoSync, a bank-owned financial-services company, where I built automation over national-registry personal data and billing.

Session 1: turn the brief into decisions

Before any code, every unclear requirement gets an interpretation that is written down. "Refused" becomes a 404, not a 403, so the response never confirms a file exists. Self-registration can never create an administrator.

Then each technical choice gets two serious options, each with why it would be better and what could go wrong. Eleven decisions in total, for example:

  • Sessionsdatabase sessions or stateless tokens
  • Passwordsargon2id or bcrypt
  • Downloadsstreamed through the app or signed storage links
  • Testsa real PostgreSQL database or mocked repositories

With a client, this is the workshop's core: the defaults are where the conversation starts, and each decision records when to switch. The agent may propose a change, citing the official documentation for the installed version, but never makes one silently.

Session 2: plan the build

Six modules, built in order, each one pull request: setup, registration, login and sessions, upload, file access, and handover. Every module lists the security rules it touches and ends with the tests that prove it's done.

Behaviour is pinned down as 46 named scenarios, each with a proposal and a test: 6 for the normal flow, 12 failures, 22 edge cases and 6 bottlenecks. Pull requests reference them by ID, so a reviewer can see what each change covers.

Session 3: the gates

Every request passes the same checks in the same order, and each check lives in one layer. A request is rejected at the cheapest point that can reject it.

  1. Clientbutton locked while pending, same schema as the server
  2. Proxyno session cookie, redirect to login
  3. Shapeorigin, size, schema, rate limit
  4. Identityvalid session, active user
  5. Authorizationone policy function decides
  6. Duplicateidempotency key claimed first
  7. Dependenciesquota, storage, timeouts
  8. Effectall or nothing, then record the result
Files page signed in as Maria Novak, admin: five files from two uploaders, Elena Petrova and Jonas Weber, with an Uploader column.
Signed in as an admin: every file, and who uploaded it.
Files page signed in as Elena Petrova, staff: only her own two files are listed.
Signed in as staff: only her own two files. The filter runs in the database query, not in the page.

Behind them sit 29 binding security rules. A few that matter most in a regulated setting: the database stores only a hash of each session token, so a leak doesn't hand out live sessions. The user's role is read fresh on every request, never cached. A file someone may not see, a missing file and a malformed ID all return the same response.

Session 4: run the agent without losing control

The same loop for every module: the agent reads the rules and one module file and proposes a plan, with no code yet. A person approves it against the security rules. The agent builds the smallest slice first, runs lint, type checks and tests, and quotes the output. A person reviews the diff against a checklist.

The known failure modes each have a counter:

  • An API that doesn't existthe agent must cite the installed package or versioned docs
  • Outdated framework habitsit reads the installed framework's own docs first
  • Scope creepone module per session, diffs outside the plan are rejected
  • "Tests pass" with no runno quoted output means not done

The proof: a pipeline on every push

Continuous integration runs on every change: a dependency audit, lint, type checks, migrating an empty database, unit and integration tests against real PostgreSQL, a production build, and end-to-end tests in a real browser.