{"slug": "ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector", "title": "AI Governance as a Database Primitive: Building FarmOps Desk on Aurora + pgvector + Bedrock", "summary": "A developer built FarmOps Desk, a B2B SaaS for farm management, using Aurora, pgvector, and Bedrock. The system treats AI governance as a database primitive with atomic credit reservation and per-farm autonomy tiers to enforce tenant isolation and prevent race conditions. The schema includes tables for AI runs, credit ledger, assistant drafts, and embeddings, all scoped by farm ID.", "body_md": "*This article is part of my submission to the H0: Hack the Zero Stack hackathon, in the Monetizable B2B App track.*\n\nMost \"AI apps\" treat the model as a stateless add-on: a chat UI tacked onto a CRUD app, calls billed to an API key the developer hopes nobody finds. The database stores the chat history; everything interesting happens outside it.\n\nFor a B2B SaaS where the AI writes financial records, drafts livestock medical notes, and creates operational tasks on behalf of paying customers, that approach fails. You need governance: who asked the AI, what tool it called, what it tried to write, what it actually wrote, how many credits it consumed, who approved the draft, who rejected it, why. All of this must be auditable weeks later, scoped per-tenant, and enforceable at the database level — not at the API layer hoping the model never hallucinates a missing tenant ID.\n\nThis article walks through the governance schema I built into [FarmOps Desk](https://github.com/captjay98/v0-farmops) for the H0 hackathon, and the two patterns that make it work: **atomic credit reservation** and **per-farm autonomy tiers**.\n\nTreat AI governance as a database primitive, not a feature. The schema enforces the invariants; the application is a thin layer over them. If the application has a bug, the database still prevents the unacceptable outcomes (a customer going into negative credits, a draft pretending to be confirmed, a tenant-A operation touching tenant-B data).\n\nThat means every interesting AI action lives in a small set of tables:\n\n| Table | Purpose |\n|---|---|\n`ai_runs` |\nOne row per model invocation. Feature, model, tokens, latency, status, summaries. The index of \"what did the AI do\". |\n`credit_ledger` |\nAppend-only ledger of credit grants/deductions. The financial truth. `balance_after >= 0` enforced by CHECK. |\n`assistant_drafts` |\nPending AI writes — every record the assistant wants to create lands here first. record_type + status state machine (`pending → confirmed / discarded` ). |\n`ai_recommendations` |\nNon-mutating AI suggestions (no draft). Status `pending → approved / rejected` . |\n`ai_evals` |\nRule-based code evals of assistant outputs (clinical safety filter, schema conformance). |\n`ai_feedback` |\nThumbs up/down + free text. Joined back to `ai_runs` for offline analysis. |\n`memories` |\nDurable farm facts (e.g. \"vaccinates on Mondays\", \"Pond 3 is the nursery\") distilled from chat. |\n`embeddings` |\npgvector. Per-farm RAG index for documents, conversation summaries, memories. |\n\nThe relational spine: `farms.id`\n\nis the tenant boundary. Every row in every table above carries `farm_id`\n\n. Every query filters by it. A hallucination that tries to write to another farm can't — the row-level check is the schema, not a function the LLM can forget to call.\n\nThe classic serverless AI bug: two concurrent requests for the same farm both observe `credit_balance = 1`\n\n, both proceed, both invoke Bedrock, both call the settleCredits function afterward. The user gets charged for 2 runs on a 1-credit balance. This is a TOCTOU race that scales linearly with concurrency.\n\nThe naive fix is `SELECT ... FOR UPDATE`\n\ninside a transaction. That works but it doesn't compose with the metering pattern: you want to **reserve** 1 credit before the run (so the user can't start 10 concurrent runs on a 5-credit balance), then **settle** after the run with the actual cost.\n\nMy pattern:\n\n```\n-- reserveCredit(farmId): atomically deduct 1 IF balance > 0\nUPDATE farms\n   SET credit_balance = credit_balance - 1\n WHERE id = $1\n   AND credit_balance > 0\nRETURNING credit_balance;\n```\n\nThis is a single conditional UPDATE. Postgres serializes concurrent calls at the row level. Exactly one of N concurrent calls returns a row when `balance = 1`\n\n; the rest get `rowCount = 0`\n\nand throw `CreditError`\n\n. No advisory lock, no transaction, no two-phase protocol.\n\nAfter the run completes:\n\n```\n// settleCredits(farmId, actualCredits, reason, aiRunId)\n//   actualCredits <= 0  → refund the reservation\n//   actualCredits === 1 → no-op (reservation was exact)\n//   actualCredits > 1   → deduct the extra, best-effort\n```\n\nThe `credit_ledger`\n\ntable captures both legs (reservation + settle) so the audit trail reconstructs the actual cost of any run. A judge looking at `/admin/evidence`\n\ncan match a `credit_ledger`\n\nrow to its `ai_runs`\n\nrow and see: 1 reserved, 3 actual, 2 deducted at settle.\n\nThis pattern generalizes to any metered-resource problem (storage quotas, rate limits, seat counts) where you need \"reserve N if available, settle later\" semantics.\n\nA small backyard poultry farm in Kaduna has different risk tolerance than a 5,000-bird commercial operation in Lagos. Some farms want the AI to suggest; others want it to act. The autonomy tier is a per-farm setting in `farms.ai_autonomy`\n\n:\n\n| Tier | Behavior |\n|---|---|\n`suggest` |\nAI cannot write anything. Tools throw if called. User gets text suggestions only. |\n`draft` |\nAI creates `assistant_drafts` rows. User confirms or discards. Default for new farms. |\n`auto` |\nAI writes directly for trusted categories (tasks, notes, feed logs). Still drafts financial/destructive writes. |\n\nThe crucial design choice: **financial and destructive writes are hardcoded to draft, regardless of autonomy tier.** No farm can opt out of human confirmation for a sale, expense, mortality event, or recommendation approval. The autonomy tier only controls the no-op write categories.\n\nImplementation: `farms.ai_autonomy`\n\nis the tier; `farms.ai_auto_categories`\n\nis a JSONB allowlist for the `auto`\n\ntier; `farms.ai_record_autonomy`\n\nis per-record-type override. The `lib/ai/draft-executor.ts`\n\nresolves all three before each write. The schema is the source of truth; the executor is a small dispatcher.\n\n```\n// Pseudo\nfunction resolveAutonomy(farm, recordType) {\n  if (FINANCIAL_DESTRUCTIVE.has(recordType)) return 'draft'  // hardcoded floor\n  if (farm.ai_autonomy === 'suggest') return 'suggest'\n  if (farm.ai_autonomy === 'auto' && farm.ai_auto_categories.includes(recordType)) return 'auto'\n  return 'draft'\n}\n```\n\nThis is the AI-safety pattern that doesn't depend on prompt engineering. The model can be convinced to do anything; the executor refuses the write at the schema level.\n\nRAG in a multi-tenant B2B has a specific shape: every vector query is scoped by `farm_id`\n\n, the corpus is per-tenant (documents, conversation summaries, memories), and the index must be transactional with the writes (a deleted document must not appear in search results).\n\nStandalone vector databases (Pinecone, Weaviate) solve the vector problem but create a second source of truth: the vector DB says \"doc X is relevant\", you have to round-trip to Postgres to authorize the read, and the two can drift. You also pay for a separate service.\n\npgvector inside Aurora collapses the two into one. The vector column lives on the same row as the tenant ID:\n\n```\nCREATE TABLE embeddings (\n  id          TEXT PRIMARY KEY,\n  farm_id     TEXT NOT NULL,\n  source_type TEXT NOT NULL,         -- 'memory' | 'document' | 'convo_summary'\n  source_id   TEXT NOT NULL,\n  content     TEXT NOT NULL,\n  embedding   VECTOR(1024) NOT NULL\n);\n\nCREATE INDEX ON embeddings\n  USING hnsw (embedding vector_cosine_ops)\n  WITH (m = 16, ef_construction = 64);\n\n-- Per-farm RAG query: tenant scoping + vector search in one shot\nSELECT id, content, 1 - (embedding <=> $1) AS score\n  FROM embeddings\n WHERE farm_id = $2                -- tenant boundary\n   OR farm_id = '__global__'       -- shared knowledge base (vaccination schedules, biology)\n ORDER BY embedding <=> $1\n LIMIT 4;\n```\n\nThe farm_id filter runs first; the HNSW scan runs against the filtered subset. A hallucinated query for tenant-A can't reach tenant-B's embeddings — the WHERE clause is enforced by Postgres, not by prompt engineering.\n\nThe hackathon's first hard requirement is **AWS Database as the primary backend**. I chose Aurora PostgreSQL over DynamoDB for one reason: **financial-grade operations require relational integrity.**\n\nMy `credit_ledger`\n\nenforces `balance_after >= 0`\n\nwith a CHECK constraint. My `assistant_drafts`\n\nenforces `record_type IN ('mortality','feed_use','sale','expense','water','weight','symptom','note')`\n\nwith a CHECK — the AI cannot draft a record type that doesn't exist. My `farm_members`\n\nenforces the join table invariant `(farm_id, user_id)`\n\nUNIQUE. None of these are expressible in DynamoDB without application-level enforcement.\n\nThe trade-off: Aurora is a heavier operational lift than DynamoDB (connection pooling, failover tuning, vacuuming). For a hackathon, I accepted the complexity because the integrity invariants are the product.\n\n```\n┌──────────────┐    OIDC JWT    ┌──────────────┐\n│   Vercel     │ ─────────────► │     STS      │\n│  Functions   │                │ (AssumeRole) │\n└──────┬───────┘                └──────┬───────┘\n       │                                │\n       │  15-min IAM token              │\n       ▼                                ▼\n┌──────────────┐                ┌──────────────┐\n│ RDS Signer   │                │  Bedrock     │\n│ (DB auth)    │                │  Runtime     │\n└──────┬───────┘                └──────────────┘\n       │\n       ▼\n┌──────────────────────────────────────────────┐\n│       Aurora PostgreSQL 17 + pgvector        │\n│  ┌────────────┐  ┌────────────┐  ┌─────────┐ │\n│  │   farms    │  │ ai_runs    │  │ memories│ │\n│  │   ...40+   │  │ credit_    │  │ embed-  │ │\n│  │  tables    │  │ ledger     │  │ dings   │ │\n│  │            │  │ drafts     │  │         │ │\n│  └────────────┘  └────────────┘  └─────────┘ │\n└──────────────────────────────────────────────┘\n```\n\nTwo IAM roles: `AWS_ROLE_ARN`\n\n(DB-only, permission-boundary-capped) and `BEDROCK_ROLE_ARN`\n\n(AI-only, no DB access). Blast-radius minimization at the trust boundary.\n\nBuilding AI governance as a database primitive isn't the typical approach. Most LLM applications default to a simple `messages`\n\ntable and a chat UI, treating the AI as an external novelty. But when building for agricultural operations, a farmer's data integrity is just as critical as a bank's.\n\nBy enforcing invariants natively in the relational schema—using integer kobo for financial accuracy, `FOR UPDATE`\n\nrow locks to prevent race conditions, append-only ledgers, and a per-farm autonomy state machine—the application guarantees safety before the AI ever generates a response.\n\nThe patterns described here are designed to go beyond a proof-of-concept. They demonstrate how to build shipping-grade, multi-tenant AI systems where data integrity is structurally guaranteed by the database itself.\n\n`docs/ARCHITECTURE.md`\n\n*This project is built for the H0 Hack the Zero Stack hackathon (#H0Hackathon), in the Monetizable B2B App track. It deploys on Vercel with Amazon Aurora PostgreSQL as the primary backend. I created this piece of content for the purposes of entering the hackathon.*", "url": "https://wpnews.pro/news/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector", "canonical_source": "https://dev.to/captjay98/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector-bedrock-44j0", "published_at": "2026-06-27 23:03:07+00:00", "updated_at": "2026-06-27 23:33:24.043739+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-products", "ai-infrastructure", "developer-tools", "large-language-models"], "entities": ["FarmOps Desk", "Aurora", "pgvector", "Bedrock", "H0: Hack the Zero Stack"], "alternates": {"html": "https://wpnews.pro/news/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector", "markdown": "https://wpnews.pro/news/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector.md", "text": "https://wpnews.pro/news/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector.txt", "jsonld": "https://wpnews.pro/news/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector.jsonld"}}