<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jamal Ibrahim Umar</title>
    <description>The latest articles on DEV Community by Jamal Ibrahim Umar (@captjay98).</description>
    <link>https://dev.to/captjay98</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2493390%2F8b8b58d2-02d6-456d-9a12-26a0f3ec8773.jpeg</url>
      <title>DEV Community: Jamal Ibrahim Umar</title>
      <link>https://dev.to/captjay98</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/captjay98"/>
    <language>en</language>
    <item>
      <title>The Smallholder Stack: Why a Nigerian Poultry + Catfish Farm Was the Right First Customer for an AI Operations SaaS</title>
      <dc:creator>Jamal Ibrahim Umar</dc:creator>
      <pubDate>Sun, 28 Jun 2026 00:05:25 +0000</pubDate>
      <link>https://dev.to/captjay98/the-smallholder-stack-why-a-nigerian-poultry-catfish-farm-was-the-right-first-customer-for-an-ai-3im9</link>
      <guid>https://dev.to/captjay98/the-smallholder-stack-why-a-nigerian-poultry-catfish-farm-was-the-right-first-customer-for-an-ai-3im9</guid>
      <description>&lt;p&gt;&lt;em&gt;Part of the H0: Hack the Zero Stack submission. See the &lt;a href="https://h01.devpost.com/" rel="noopener noreferrer"&gt;project on Devpost&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Nigeria is the launch market. It is not the ceiling.&lt;/p&gt;

&lt;p&gt;This article explains the reasoning behind that distinction, and why the architecture I built for FarmOps Desk generalizes across the smallholder livestock economies of Sub-Saharan Africa, Southeast Asia, South Asia, and Latin America. The product decisions Nigeria drove are durable; the marketing decisions are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem is not Nigerian
&lt;/h2&gt;

&lt;p&gt;The UN Food and Agriculture Organization estimates that &lt;strong&gt;smallholder farmers produce up to 80% of the food supply in developing countries&lt;/strong&gt; and depend on livestock for a meaningful share of their income. The World Bank's IFC puts the smallholder agritech market at &lt;strong&gt;$510 billion annual spend&lt;/strong&gt; across the regions where smallholders are the dominant producer.&lt;/p&gt;

&lt;p&gt;The operational problems are uniform across these regions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mortality events.&lt;/strong&gt; A poultry house loses 2-5% per cycle to heat stress, disease, or predation. In Nigeria, dry-season heat waves kill ~3% of broilers annually. In Bangladesh, summer humidity kills ~4%. In Indonesia, avian influenza takes 2-6%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed management.&lt;/strong&gt; Feed is 60-70% of variable cost. Mismanagement (wrong feed at the wrong phase, expired stock, underfeeding) erodes margin faster than any other variable. The arithmetic is identical in Lagos, Jakarta, and Nairobi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Water quality for aquaculture.&lt;/strong&gt; Dissolved oxygen crashes at dawn kill catfish ponds. The dynamic is the same in tarpaulin ponds in Kaduna and earthen ponds in Mekong Delta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Financial discipline.&lt;/strong&gt; Farmers mix personal and business cash. Money as integer kobo is the same pattern in NGN, IDR, PKR, BDT, or BRL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Records for veterinary compliance.&lt;/strong&gt; A broiler batch without a vaccination log can't be sold to a regulated processor. The compliance check is identical across exporting markets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not "Nigerian problems." They are &lt;strong&gt;smallholder livestock problems&lt;/strong&gt;, expressed in different currencies and languages across a $1.4 trillion addressable market (IFAD 2024 estimate, livestock sub-sector).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Nigeria first
&lt;/h2&gt;

&lt;p&gt;Three reasons, none of which are sentimental:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Density.&lt;/strong&gt; Nigeria is the &lt;strong&gt;4th-largest poultry producer in the world&lt;/strong&gt; by headcount (after China, Brazil, USA). It's also the fastest-growing catfish producer in Sub-Saharan Africa. The addressable farm count is in the millions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Proven Payment Infrastructure.&lt;/strong&gt; To build a B2B SaaS, you need reliable ways to process payments locally. Nigeria has robust payment providers like Nomba, which offer modern APIs (like webhooks and idempotency) similar to what Stripe uses globally. Because I built the payment layer to support Nomba's standard webhook pattern, integrating future local payment rails (like M-Pesa in Kenya or Pix in Brazil) will just require dropping in a new provider file, not rewriting the billing engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. English-language reduces go-to-market friction.&lt;/strong&gt; The AI's grounding prompts, the UI copy, and the demo video are all in English. For a solo dev or small team, this is significant. I can ship a Kenyan or Ghanaian version next month without re-recording or re-translating. (For non-English markets like Indonesia and Brazil, the &lt;code&gt;farms.language&lt;/code&gt; column already supports per-farm localization; the system prompt adapts.)&lt;/p&gt;

&lt;p&gt;Nigeria is a market of 200M people with a real poultry + catfish sector, real payment infrastructure, and English-language operation. Picking it first is a business decision, not a sentimental one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The product decisions Nigeria drove (and why they generalize)
&lt;/h2&gt;

&lt;p&gt;Every Nigeria-driven decision turned out to be a global-smallholder decision in disguise. This is the architecture's strongest claim to scalability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Money as integer kobo
&lt;/h3&gt;

&lt;p&gt;NGN has 100 kobo per naira. I store all monetary values as BIGINT kobo. The CHECK constraints (&lt;code&gt;amount_minor &amp;gt;= 0&lt;/code&gt;, &lt;code&gt;balance_after &amp;gt;= 0&lt;/code&gt;) work in any currency — IDR rupiah (no minor unit, just store rupiah as the minor), BDT paisa, PKR paisa, BRL centavos. The pattern is &lt;strong&gt;"smallest integer unit"&lt;/strong&gt; — a Nigerian decision that generalizes to every currency on the planet.&lt;/p&gt;

&lt;p&gt;The alternative — &lt;code&gt;NUMERIC(10,2)&lt;/code&gt; float money — is the wrong answer everywhere, but most pronounced in markets with high inflation where small units matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Voice-first AI
&lt;/h3&gt;

&lt;p&gt;Nigerian smallholder farmers are not all low-literacy, but the operational reality is: you're standing in a broiler house at 5am with two hands full of feed, and you don't have time to type. Voice is the natural interface.&lt;/p&gt;

&lt;p&gt;The same is true in rural Indonesia, Bangladesh, Kenya, Brazil. The &lt;code&gt;use-voice-session.ts&lt;/code&gt; component, the Bedrock Nova Sonic bidirectional session, the audio fallback via MediaRecorder — all of this generalizes. The system prompt language adapts from &lt;code&gt;farms.language&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-farm autonomy tiers
&lt;/h3&gt;

&lt;p&gt;A 200-bird subsistence farm in Kaduna has different risk tolerance than a 50,000-bird commercial operation in Lagos. The autonomy tier (&lt;code&gt;suggest | draft | auto&lt;/code&gt;) lets each farm dial in. The same dial serves a 50-bird farm in Java and a 5,000-fish pond in Vietnam.&lt;/p&gt;

&lt;p&gt;The crucial sub-decision — &lt;strong&gt;financial and destructive writes hardcoded to &lt;code&gt;draft&lt;/code&gt; regardless of tier&lt;/strong&gt; — is universal. No farmer anywhere wants the AI to record a sale or a mortality event without confirmation. The autonomy floor is a safety invariant, not a cultural preference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pluggable payment rail
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;lib/payments.ts&lt;/code&gt; has a provider interface with a &lt;code&gt;mock&lt;/code&gt; implementation and a &lt;code&gt;nomba&lt;/code&gt; implementation. Adding Stripe (for international cards), M-Pesa (Kenya), Pix (Brazil), or GoPay (Indonesia) is one provider file each. The webhook + idempotency pattern is identical across all of them; the diff is the request/response shape.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pluggable messaging
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;lib/messaging/provider.ts&lt;/code&gt; abstracts Termii (Nigeria SMS) vs Resend (email). For Kenya, Africa's Talking slots in. For Indonesia, WhatsApp Business API. For Brazil, Twilio. The interface is the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-currency, multi-language, multi-timezone
&lt;/h3&gt;

&lt;p&gt;The schema has &lt;code&gt;farms.currency&lt;/code&gt;, &lt;code&gt;farms.language&lt;/code&gt;, &lt;code&gt;farms.timezone&lt;/code&gt;. Today these are mostly set to NGN/en/Africa/Lagos in production. But the assistant system prompt is built per-request from &lt;code&gt;farms.language&lt;/code&gt;. The daily insight schedule uses &lt;code&gt;farms.timezone&lt;/code&gt;. The money formatter reads &lt;code&gt;farms.currency&lt;/code&gt;. A farm in Bangladesh or Brazil works today, with no code change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic species model
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;production_units.species&lt;/code&gt; is a TEXT column, not an enum. Today the UI surfaces &lt;code&gt;broiler&lt;/code&gt; and &lt;code&gt;catfish&lt;/code&gt;. The schema accepts &lt;code&gt;goat&lt;/code&gt;, &lt;code&gt;cattle&lt;/code&gt;, &lt;code&gt;pig&lt;/code&gt;, &lt;code&gt;duck&lt;/code&gt;, &lt;code&gt;tilapia&lt;/code&gt; without modification — only the species catalog and the breed lists need updating. Nigeria's narrow focus is a UI choice, not a schema constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Scalable Expansion Model
&lt;/h2&gt;

&lt;p&gt;Because the core architecture is decoupled from regional specifics, expanding to new markets becomes a data-entry exercise rather than a software rewrite. Here is how that scales:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Market&lt;/th&gt;
&lt;th&gt;TAM&lt;/th&gt;
&lt;th&gt;What changes from the previous market&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nigeria&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$8B poultry + catfish&lt;/td&gt;
&lt;td&gt;Launch market. Nomba payment, English UI.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2027&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Kenya, Ghana&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$5B poultry&lt;/td&gt;
&lt;td&gt;Add M-Pesa payment rail. Same language. Same species.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2027&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Indonesia, Philippines&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$25B poultry + fish&lt;/td&gt;
&lt;td&gt;Add GoPay / GCash payment. Bahasa Indonesia / Tagalog language pack. Same species.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2028&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Brazil, India&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$40B poultry + fish&lt;/td&gt;
&lt;td&gt;Add Pix / UPI payment. Portuguese / Hindi language pack. Same species.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2029+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Vietnam, Bangladesh, Egypt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$30B+&lt;/td&gt;
&lt;td&gt;Same pattern.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key takeaway here is that entering a new market requires just four predictable updates: the currency, the payment provider, the language pack, and the local species/breed list. None of these require database schema changes. The cost of entering a new market is simply data onboarding and a single payment integration, not a platform rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human stakes
&lt;/h2&gt;

&lt;p&gt;The reason this matters more than the architecture is what the architecture is for.&lt;/p&gt;

&lt;p&gt;Nigeria's poultry sector loses an estimated &lt;strong&gt;15-20% of broiler production to preventable causes&lt;/strong&gt; — heat stress, vaccination gaps, feed mismanagement. That's roughly &lt;strong&gt;₦600 billion ($750M)&lt;/strong&gt; per year in dead birds. A 5% reduction across the sector would be &lt;strong&gt;$375M/yr in farm income preserved&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The numbers scale across the regions: a 5% mortality reduction across Indonesia's poultry sector is another $500M/yr. Across Brazil's, $400M/yr. The aggregate smallholder livestock TAM for AI-assisted operations is in the tens of billions per year in recovered production, before counting the productivity gains.&lt;/p&gt;

&lt;p&gt;This is the real-world impact that justifies the engineering effort. The architecture I described in the companion pieces — Aurora + pgvector + Bedrock, keyless OIDC, atomic credit reservation — is just the substrate. The reason to build it is the smallholder farmer who stops losing birds to a 4am heat wave because the assistant drafted a ventilation task the night before and the farmer confirmed it with one tap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current State and What's Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Core Features Implemented:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;40+ table multi-tenant Aurora schema, money stored as integer kobo, and an append-only credit ledger&lt;/li&gt;
&lt;li&gt;AI assistant with per-farm autonomy tiers, draft-confirmation flows, and a voice mode&lt;/li&gt;
&lt;li&gt;Atomic credit reservation (preventing race conditions under concurrent load)&lt;/li&gt;
&lt;li&gt;pgvector-powered RAG scoped securely per-farm&lt;/li&gt;
&lt;li&gt;Keyless Vercel → Aurora → Bedrock trust chain (zero static credentials)&lt;/li&gt;
&lt;li&gt;Real-world Nomba payment integration with webhook idempotency&lt;/li&gt;
&lt;li&gt;1,629+ tests ensuring type-safety, linting, and build stability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Future Roadmap:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RDS Proxy for connection pooling (see my "Keyless by Default" article)&lt;/li&gt;
&lt;li&gt;Integration with M-Pesa for the Kenyan market&lt;/li&gt;
&lt;li&gt;Bahasa Indonesia language pack + GoPay integration&lt;/li&gt;
&lt;li&gt;A vaccination scheduler with national-schedule presets&lt;/li&gt;
&lt;li&gt;An offline-tolerant PWA for areas with poor connectivity&lt;/li&gt;
&lt;li&gt;A vet marketplace for in-app consult booking&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The global takeaway
&lt;/h2&gt;

&lt;p&gt;The schema doesn't know it's about Nigeria. The currency column says NGN today and would say IDR or BRL or BDT tomorrow. The species column accepts any string. The payment interface accepts any provider. The language column drives the assistant's system prompt. The autonomy tier respects every culture's risk tolerance by giving each farm the dial.&lt;/p&gt;

&lt;p&gt;The product launched in Nigeria because that's where the densest cluster of smallholder poultry + catfish farmers with real payment rails and English fluency happens to be. The architecture launched for the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: Source code provided as a ZIP file download on the Devpost submission page.&lt;/li&gt;
&lt;li&gt;Companion articles: &lt;a href="https://dev.to/captjay98/keyless-by-default-securing-farmops-desk-without-a-single-static-secret-6p1"&gt;Keyless by Default&lt;/a&gt; · &lt;a href="https://dev.to/captjay98/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector-bedrock-44j0"&gt;AI Governance as a Database Primitive&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built for the H0 Hack the Zero Stack hackathon (#H0Hackathon), Monetizable B2B App track. Deployed on Vercel, Amazon Aurora PostgreSQL as primary backend, Amazon Bedrock for AI. Nigeria is the launch market, not the ceiling. I created this piece of content for the purposes of entering the hackathon.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>h0hackathon</category>
      <category>agritech</category>
      <category>smallholder</category>
      <category>nigeria</category>
    </item>
    <item>
      <title>Keyless by Default: Securing FarmOps Desk without a Single Static Secret</title>
      <dc:creator>Jamal Ibrahim Umar</dc:creator>
      <pubDate>Sat, 27 Jun 2026 23:50:01 +0000</pubDate>
      <link>https://dev.to/captjay98/keyless-by-default-securing-farmops-desk-without-a-single-static-secret-6p1</link>
      <guid>https://dev.to/captjay98/keyless-by-default-securing-farmops-desk-without-a-single-static-secret-6p1</guid>
      <description>&lt;p&gt;&lt;em&gt;Part of the H0: Hack the Zero Stack submission. See the &lt;a href="https://h01.devpost.com/" rel="noopener noreferrer"&gt;project on Devpost&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every hackathon submission that uses AWS from Vercel usually starts the same way: &lt;em&gt;"paste your &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; into Vercel's environment variables."&lt;/em&gt; It's easy, and it works. It's also the single most common cause of cloud supply-chain attacks. &lt;/p&gt;

&lt;p&gt;Major breaches like SolarWinds or the Toyota customer leak often start with a static credential that someone forgot to rotate, or accidentally checked into a code repository. &lt;/p&gt;

&lt;p&gt;For the &lt;a href="https://h01.devpost.com/" rel="noopener noreferrer"&gt;H0 hackathon&lt;/a&gt;, I built &lt;a href="https://github.com/captjay98/v0-farmops" rel="noopener noreferrer"&gt;FarmOps Desk&lt;/a&gt; on Vercel + Aurora PostgreSQL + Bedrock. My goal was strict security: &lt;strong&gt;Zero static AWS credentials&lt;/strong&gt; anywhere in the project, deployment, or CI pipeline. &lt;/p&gt;

&lt;p&gt;Here is how I made the entire application "keyless."&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Keycard" Approach (OIDC)
&lt;/h2&gt;

&lt;p&gt;Instead of giving Vercel a permanent "master key" (a static AWS credential), I set up a system where Vercel requests a temporary, 15-minute "keycard" every time a function runs. &lt;/p&gt;

&lt;p&gt;This is powered by Vercel's OIDC (OpenID Connect) integration with AWS:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a Vercel function wakes up, Vercel gives it a cryptographically signed identity token.&lt;/li&gt;
&lt;li&gt;The function hands this token to AWS.&lt;/li&gt;
&lt;li&gt;AWS verifies the token, checks that it came from my specific Vercel project, and hands back a temporary session that expires in 15 minutes. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a malicious actor somehow compromises the Vercel environment, they don't get a permanent key. They get a temporary session that evaporates almost immediately. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Two-Role Split: Minimizing Blast Radius
&lt;/h2&gt;

&lt;p&gt;A common mistake is creating a single AWS Role that has permissions to do &lt;em&gt;everything&lt;/em&gt; (access the database, call the AI, read storage). That creates a massive blast radius. If the AI service is compromised, the database goes with it. &lt;/p&gt;

&lt;p&gt;I split the permissions into two strictly isolated roles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Database Role (&lt;code&gt;AWS_ROLE_ARN&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only has permission to connect to the Aurora PostgreSQL database.&lt;/li&gt;
&lt;li&gt;Has a strict "Permission Boundary" ensuring it can never be used to access Bedrock or S3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. The AI Role (&lt;code&gt;BEDROCK_ROLE_ARN&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only has permission to invoke the Bedrock Nova models.&lt;/li&gt;
&lt;li&gt;Has zero access to read or write to the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By doing this, a bug or breach in the AI code path can be shut down entirely without affecting the database, and vice versa. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Code: Simpler Than You'd Think
&lt;/h2&gt;

&lt;p&gt;Thanks to modern SDKs, going keyless doesn't require thousands of lines of boilerplate. &lt;/p&gt;

&lt;p&gt;For the database, I use the &lt;code&gt;@aws-sdk/rds-signer&lt;/code&gt; to fetch a fresh token dynamically every time a new database connection is made. No passwords are ever stored on disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/db.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PGHOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PGUSER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Fetch a fresh 15-minute token dynamically instead of using a static password&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAuthToken&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
  &lt;span class="na"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the Bedrock AI models, Vercel provides a helper that automatically trades the OIDC token for temporary AWS credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/ai/bedrock.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;awsCredentialsProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vercel/functions/oidc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getBedrockRuntime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockRuntimeClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;awsCredentialsProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BEDROCK_ROLE_ARN&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling Voice AI with Nova Sonic
&lt;/h2&gt;

&lt;p&gt;A unique challenge of building an agricultural AI is that farmers' hands are often dirty or busy. The assistant needs a voice mode. I used &lt;strong&gt;Amazon Bedrock Nova Sonic&lt;/strong&gt; for real-time, bidirectional voice streaming.&lt;/p&gt;

&lt;p&gt;However, serverless environments like Vercel usually struggle with long-lived bidirectional streams. By default, the underlying HTTP/2 session drops after the first voice turn. &lt;/p&gt;

&lt;p&gt;To solve this, I deployed a dedicated "Sonic Bridge" service to a long-running Amazon EC2 instance. The bridge uses a custom &lt;code&gt;NodeHttp2Handler&lt;/code&gt; that extends the session timeouts to 5 minutes, letting the farmer keep an open conversation with the AI while walking through the poultry house.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Keyless Deployment to EC2:&lt;/strong&gt;&lt;br&gt;
Usually, deploying to EC2 from GitHub Actions means storing an SSH private key or an &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; in your GitHub repository secrets. That is exactly what I wanted to avoid.&lt;/p&gt;

&lt;p&gt;Instead, I set up a fully keyless CI/CD pipeline for the bridge:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub Actions uses its built-in OIDC provider to prove its identity to AWS.&lt;/li&gt;
&lt;li&gt;AWS grants GitHub a short-lived session, assuming a strictly scoped IAM role.&lt;/li&gt;
&lt;li&gt;The GitHub runner uses &lt;strong&gt;AWS Systems Manager (SSM)&lt;/strong&gt; to securely send the deployment tarball and execute the bash rollout script directly on the EC2 instance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zero SSH keys. Zero static AWS credentials in GitHub. If the GitHub repository is ever compromised, there is no permanent key for an attacker to steal—just an OIDC trust policy that only works when triggered by a push to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling the Database (Next Steps)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Note: While not strictly required for hackathon traffic, production Vercel functions can easily exhaust Aurora's connection limits due to serverless concurrency.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To prevent the database from crashing under load, the production architecture places &lt;strong&gt;AWS RDS Proxy&lt;/strong&gt; in front of Aurora. RDS Proxy acts like a traffic cop, multiplexing hundreds of Vercel connections down to a safe handful for Aurora. &lt;em&gt;(I've included a comprehensive guide on my proxy setup in the project repository).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keyless architecture matters
&lt;/h2&gt;

&lt;p&gt;Keyless authentication is a deliberate engineering decision. It is inherently harder to set up initially than pasting environment variables, but it is the production-grade answer for cloud security. &lt;/p&gt;

&lt;p&gt;Shippable software is software that doesn't leak credentials. By eliminating static keys entirely, FarmOps can accept real customer data on day one without needing a "we'll rotate the keys before launch" excuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: Source code provided as a ZIP file download on the Devpost submission page.&lt;/li&gt;
&lt;li&gt;Live: deployed on Vercel (&lt;a href="https://v0-farmops.vercel.app" rel="noopener noreferrer"&gt;https://v0-farmops.vercel.app&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Companion articles: &lt;a href="https://dev.to/captjay98/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector-bedrock-44j0"&gt;AI Governance as a Database Primitive&lt;/a&gt; · &lt;a href="https://dev.to/captjay98/the-smallholder-stack-why-a-nigerian-poultry-catfish-farm-was-the-right-first-customer-for-an-ai-3im9"&gt;The Smallholder Stack (TAM &amp;amp; Scaling)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Vercel OIDC docs: &lt;a href="https://vercel.com/docs/deployments/troubleshooting/oidc" rel="noopener noreferrer"&gt;vercel.com/docs/deployments/troubleshooting/oidc&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RDS IAM auth: &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html" rel="noopener noreferrer"&gt;docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built for the H0 Hack the Zero Stack hackathon (#H0Hackathon). Deployed on Vercel, Amazon Aurora PostgreSQL as primary backend, Amazon Bedrock for AI. Zero static AWS credentials in the project. I created this piece of content for the purposes of entering the hackathon.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>h0hackathon</category>
      <category>aws</category>
      <category>vercel</category>
      <category>security</category>
    </item>
    <item>
      <title>AI Governance as a Database Primitive: Building FarmOps Desk on Aurora + pgvector + Bedrock</title>
      <dc:creator>Jamal Ibrahim Umar</dc:creator>
      <pubDate>Sat, 27 Jun 2026 23:03:07 +0000</pubDate>
      <link>https://dev.to/captjay98/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector-bedrock-44j0</link>
      <guid>https://dev.to/captjay98/ai-governance-as-a-database-primitive-building-farmops-desk-on-aurora-pgvector-bedrock-44j0</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is part of my submission to the &lt;a href="https://h01.devpost.com/" rel="noopener noreferrer"&gt;H0: Hack the Zero Stack&lt;/a&gt; hackathon, in the Monetizable B2B App track.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most "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.&lt;/p&gt;

&lt;p&gt;For 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.&lt;/p&gt;

&lt;p&gt;This article walks through the governance schema I built into &lt;a href="https://github.com/captjay98/v0-farmops" rel="noopener noreferrer"&gt;FarmOps Desk&lt;/a&gt; for the H0 hackathon, and the two patterns that make it work: &lt;strong&gt;atomic credit reservation&lt;/strong&gt; and &lt;strong&gt;per-farm autonomy tiers&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thesis
&lt;/h2&gt;

&lt;p&gt;Treat 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).&lt;/p&gt;

&lt;p&gt;That means every interesting AI action lives in a small set of tables:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_runs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One row per model invocation. Feature, model, tokens, latency, status, summaries. The index of "what did the AI do".&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;credit_ledger&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Append-only ledger of credit grants/deductions. The financial truth. &lt;code&gt;balance_after &amp;gt;= 0&lt;/code&gt; enforced by CHECK.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assistant_drafts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pending AI writes — every record the assistant wants to create lands here first. record_type + status state machine (&lt;code&gt;pending → confirmed / discarded&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_recommendations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Non-mutating AI suggestions (no draft). Status &lt;code&gt;pending → approved / rejected&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_evals&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rule-based code evals of assistant outputs (clinical safety filter, schema conformance).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_feedback&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Thumbs up/down + free text. Joined back to &lt;code&gt;ai_runs&lt;/code&gt; for offline analysis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Durable farm facts (e.g. "vaccinates on Mondays", "Pond 3 is the nursery") distilled from chat.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pgvector. Per-farm RAG index for documents, conversation summaries, memories.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The relational spine: &lt;code&gt;farms.id&lt;/code&gt; is the tenant boundary. Every row in every table above carries &lt;code&gt;farm_id&lt;/code&gt;. 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1 — Atomic credit reservation
&lt;/h2&gt;

&lt;p&gt;The classic serverless AI bug: two concurrent requests for the same farm both observe &lt;code&gt;credit_balance = 1&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;The naive fix is &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; inside a transaction. That works but it doesn't compose with the metering pattern: you want to &lt;strong&gt;reserve&lt;/strong&gt; 1 credit before the run (so the user can't start 10 concurrent runs on a 5-credit balance), then &lt;strong&gt;settle&lt;/strong&gt; after the run with the actual cost.&lt;/p&gt;

&lt;p&gt;My pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- reserveCredit(farmId): atomically deduct 1 IF balance &amp;gt; 0&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;farms&lt;/span&gt;
   &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;credit_balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credit_balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;credit_balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;credit_balance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a single conditional UPDATE. Postgres serializes concurrent calls at the row level. Exactly one of N concurrent calls returns a row when &lt;code&gt;balance = 1&lt;/code&gt;; the rest get &lt;code&gt;rowCount = 0&lt;/code&gt; and throw &lt;code&gt;CreditError&lt;/code&gt;. No advisory lock, no transaction, no two-phase protocol.&lt;/p&gt;

&lt;p&gt;After the run completes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// settleCredits(farmId, actualCredits, reason, aiRunId)&lt;/span&gt;
&lt;span class="c1"&gt;//   actualCredits &amp;lt;= 0  → refund the reservation&lt;/span&gt;
&lt;span class="c1"&gt;//   actualCredits === 1 → no-op (reservation was exact)&lt;/span&gt;
&lt;span class="c1"&gt;//   actualCredits &amp;gt; 1   → deduct the extra, best-effort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;credit_ledger&lt;/code&gt; table captures both legs (reservation + settle) so the audit trail reconstructs the actual cost of any run. A judge looking at &lt;code&gt;/admin/evidence&lt;/code&gt; can match a &lt;code&gt;credit_ledger&lt;/code&gt; row to its &lt;code&gt;ai_runs&lt;/code&gt; row and see: 1 reserved, 3 actual, 2 deducted at settle.&lt;/p&gt;

&lt;p&gt;This pattern generalizes to any metered-resource problem (storage quotas, rate limits, seat counts) where you need "reserve N if available, settle later" semantics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2 — Per-farm autonomy tiers
&lt;/h2&gt;

&lt;p&gt;A 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 &lt;code&gt;farms.ai_autonomy&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suggest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI cannot write anything. Tools throw if called. User gets text suggestions only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;draft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI creates &lt;code&gt;assistant_drafts&lt;/code&gt; rows. User confirms or discards. Default for new farms.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI writes directly for trusted categories (tasks, notes, feed logs). Still drafts financial/destructive writes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The crucial design choice: &lt;strong&gt;financial and destructive writes are hardcoded to &lt;code&gt;draft&lt;/code&gt;, regardless of autonomy tier.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Implementation: &lt;code&gt;farms.ai_autonomy&lt;/code&gt; is the tier; &lt;code&gt;farms.ai_auto_categories&lt;/code&gt; is a JSONB allowlist for the &lt;code&gt;auto&lt;/code&gt; tier; &lt;code&gt;farms.ai_record_autonomy&lt;/code&gt; is per-record-type override. The &lt;code&gt;lib/ai/draft-executor.ts&lt;/code&gt; resolves all three before each write. The schema is the source of truth; the executor is a small dispatcher.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudo&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveAutonomy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;farm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;recordType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FINANCIAL_DESTRUCTIVE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recordType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;// hardcoded floor&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;farm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai_autonomy&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;suggest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;suggest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;farm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai_autonomy&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;farm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai_auto_categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recordType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why pgvector inside Aurora
&lt;/h2&gt;

&lt;p&gt;RAG in a multi-tenant B2B has a specific shape: every vector query is scoped by &lt;code&gt;farm_id&lt;/code&gt;, 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).&lt;/p&gt;

&lt;p&gt;Standalone 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.&lt;/p&gt;

&lt;p&gt;pgvector inside Aurora collapses the two into one. The vector column lives on the same row as the tenant ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;          &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;farm_id&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;source_type&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;-- 'memory' | 'document' | 'convo_summary'&lt;/span&gt;
  &lt;span class="n"&gt;source_id&lt;/span&gt;   &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;embedding&lt;/span&gt;   &lt;span class="n"&gt;VECTOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;hnsw&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector_cosine_ops&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ef_construction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Per-farm RAG query: tenant scoping + vector search in one shot&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;farm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;                &lt;span class="c1"&gt;-- tenant boundary&lt;/span&gt;
   &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;farm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'__global__'&lt;/span&gt;       &lt;span class="c1"&gt;-- shared knowledge base (vaccination schedules, biology)&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
 &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Aurora PostgreSQL (not DynamoDB)
&lt;/h2&gt;

&lt;p&gt;The hackathon's first hard requirement is &lt;strong&gt;AWS Database as the primary backend&lt;/strong&gt;. I chose Aurora PostgreSQL over DynamoDB for one reason: &lt;strong&gt;financial-grade operations require relational integrity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;credit_ledger&lt;/code&gt; enforces &lt;code&gt;balance_after &amp;gt;= 0&lt;/code&gt; with a CHECK constraint. My &lt;code&gt;assistant_drafts&lt;/code&gt; enforces &lt;code&gt;record_type IN ('mortality','feed_use','sale','expense','water','weight','symptom','note')&lt;/code&gt; with a CHECK — the AI cannot draft a record type that doesn't exist. My &lt;code&gt;farm_members&lt;/code&gt; enforces the join table invariant &lt;code&gt;(farm_id, user_id)&lt;/code&gt; UNIQUE. None of these are expressible in DynamoDB without application-level enforcement.&lt;/p&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┐    OIDC JWT    ┌──────────────┐
│   Vercel     │ ─────────────► │     STS      │
│  Functions   │                │ (AssumeRole) │
└──────┬───────┘                └──────┬───────┘
       │                                │
       │  15-min IAM token              │
       ▼                                ▼
┌──────────────┐                ┌──────────────┐
│ RDS Signer   │                │  Bedrock     │
│ (DB auth)    │                │  Runtime     │
└──────┬───────┘                └──────────────┘
       │
       ▼
┌──────────────────────────────────────────────┐
│       Aurora PostgreSQL 17 + pgvector        │
│  ┌────────────┐  ┌────────────┐  ┌─────────┐ │
│  │   farms    │  │ ai_runs    │  │ memories│ │
│  │   ...40+   │  │ credit_    │  │ embed-  │ │
│  │  tables    │  │ ledger     │  │ dings   │ │
│  │            │  │ drafts     │  │         │ │
│  └────────────┘  └────────────┘  └─────────┘ │
└──────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two IAM roles: &lt;code&gt;AWS_ROLE_ARN&lt;/code&gt; (DB-only, permission-boundary-capped) and &lt;code&gt;BEDROCK_ROLE_ARN&lt;/code&gt; (AI-only, no DB access). Blast-radius minimization at the trust boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this architecture matters
&lt;/h2&gt;

&lt;p&gt;Building AI governance as a database primitive isn't the typical approach. Most LLM applications default to a simple &lt;code&gt;messages&lt;/code&gt; table 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.&lt;/p&gt;

&lt;p&gt;By enforcing invariants natively in the relational schema—using integer kobo for financial accuracy, &lt;code&gt;FOR UPDATE&lt;/code&gt; row 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.&lt;/p&gt;

&lt;p&gt;The 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: Source code provided as a ZIP file download on the Devpost submission page.&lt;/li&gt;
&lt;li&gt;Live: deployed on Vercel (&lt;a href="https://v0-farmops.vercel.app" rel="noopener noreferrer"&gt;https://v0-farmops.vercel.app&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Companion articles: &lt;a href="https://dev.to/captjay98/keyless-by-default-securing-farmops-desk-without-a-single-static-secret-6p1"&gt;Keyless by Default&lt;/a&gt; · The Smallholder Stack (TAM &amp;amp; Scaling)
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>h0hackathon</category>
      <category>aws</category>
      <category>vercel</category>
      <category>aurora</category>
    </item>
  </channel>
</rss>
