Last week I wrote about the decision to rebuild AIS from scratch as a full architecture blueprint, and the one design rule that shaped everything after: the LLM only ever phrases facts, it never decides them.
This week's decision was about something less visible but more foundational: who's allowed to touch the database, and how.
I decided, before a single real customer record existed, that row-level security would be the tenant-isolation mechanism from day one — not something bolted on once customers were live. And that the app wouldn't use one shared database login. Every write path gets its own least-privilege role: an ingest_writer that can only write telemetry, a role for engine output, a role for reading secrets — each one scoped to exactly what it's allowed to do and nothing else.
That week I shipped the full production schema and row-level security together, in one migration, plus the real vendor-credential storage and rotation, plus the actual onboarding flow — built to write through RLS impersonation from the start rather than a privileged backdoor "for now."
Then I found a real problem in my own work: some of the role and permission tables had been designed in a way that technically worked, but wasn't actually safe to run against production. Not a bug that showed up later — a gap I caught by looking hard at what I'd just built.
I moved those tables into a dedicated, prod-safe migration before any real data ever touched the system. No shortcut, no "fix it after go-live." The whole point of deciding the security model first is that you get to catch things like this while the cost of being wrong is still zero.
What's next: the first real vendor data hitting this schema — and an assumption about what one integration actually covered that turned out to be wrong the moment real data showed up.
Originally published on LinkedIn.
