Skip to main content

Agent Quality Principles — ChurchWiseAI Portfolio

Purpose: Every agent session absorbs these principles to prevent the recurring error patterns discovered across 165 issues found in the March 2026 go-live audit. These are not suggestions — they are hard requirements derived from real bugs that shipped.

Updated: 2026-03-26


THE NORTH STAR — The Customer Must Succeed

Your main goal is not working code. Your main goal is not passing tests. Your main goal is not a clean build.

Your main goal is the CUSTOMER having a SUCCESSFUL EXPERIENCE.

A pastor signs up for Starter Chat. They should find us easily, sign up smoothly, receive clear emails, land on a dashboard that makes sense for their tier, set up their chatbot without confusion, and see it working for their congregation. That is success. Everything else is a means to that end.

Before every decision, ask: "Does this make the customer's experience better, worse, or unchanged?"

  • Code that compiles but shows a Starter customer Pro features? Failure.
  • Tests that pass but don't verify what the customer sees? Failure.
  • A feature that works technically but confuses the pastor? Failure.
  • Pushing back to say "we need a spec before we can build this"? Success. That's protecting the customer from an undefined experience.

If you don't know what the customer should see, you cannot build it correctly. Check knowledge/acceptance/ for the tier spec. If it doesn't exist, say so. Don't guess. Don't assume. Don't substitute your judgment for the founder's. The founder defines the customer experience. Agents implement it.


0. THE CARDINAL RULE — Never Delete, Always Fix

Source: An agent deleted 2,700 lines of the founder's QA testing system (used by the founder and his daughter) instead of adding 3 lines of authentication. This is the single worst agent behavior failure in project history.

P0.1 — NEVER delete a user-built feature because it has a security issue. FIX THE ISSUE INSTEAD. Unauthenticated route? Add auth. XSS vulnerability? Sanitize input. File upload without validation? Add validation. The fix for a security bug is SECURITY CODE, not deletion.

P0.2 — Before deleting ANY file with >50 lines of user-built functionality, ASK THE USER. Agents do not have the authority to unilaterally remove working features. Even if the code looks dangerous, unused, or poorly written — the user may depend on it in ways you cannot see.

P0.3 — "fix(security): remove X" is almost always the wrong commit message. The word "remove" in a security fix is a red flag. Security fixes should SECURE, not REMOVE. If you find yourself writing "remove" in a security-related commit, stop and reconsider whether you should be adding protection instead.

P0.4 — Fix the bug, don't kill the patient. A doctor who amputates a leg to treat a broken toe is not a good doctor. An agent who deletes a feature to fix a missing auth check is not a good agent.


1. Database & Queries

Source: DBA expert audit — found SELECT * leaking OAuth tokens, SQL injection vectors, unbounded queries, non-atomic payment activation.

P1.1 — Never use select('*') or bare select(). Always specify columns explicitly. SELECT * has leaked admin_token, access_token, pco_secret, cal_api_key, and OAuth refresh_token to clients in production. Use named constants (PREMIUM_SAFE_COLUMNS) for frequently-queried tables.

P1.2 — Always add .limit() or .range() to every query. Supabase's 1000-row default silently drops results instead of erroring. Explicit limits make intent clear and prevent silent data loss at scale.

P1.3 — Never interpolate user input into PostgREST filter strings. Use parameterized .eq(), .in(), .ilike() methods. If you must use .or(), sanitize input with sanitizeSearch() (strips commas, parens, operators, limits length). The % and . characters are dangerous in PostgREST filters.

P1.4 — Use RPC functions for atomic operations. Never use read-then-write patterns for counters, status transitions, or rate limiting. Create a Postgres function with INSERT ON CONFLICT DO UPDATE for atomic increment. The in-memory Map rate limiter was completely ineffective in serverless.

P1.5 — Design multi-step mutations for idempotency. In Vercel serverless, you cannot use database transactions across multiple Supabase calls. Each step must be idempotent (upsert, not insert), include a deduplication key, and log failures for reconciliation.

P1.6 — Never create exec_sql-style RPC functions. Any function accepting raw SQL as a parameter is a SQL injection vector. Create purpose-specific functions with no arguments or typed parameters only.

P1.7 — Batch loop operations. Use single batch calls (supabase.from('table').upsert([...array])) instead of await inside for loops. Sequential per-row operations cause N+1 patterns that will timeout in serverless.

P1.8 — Every fire-and-forget .catch() must log. Replace .catch(() => {}) with .catch((err) => console.error('[context]:', err)). Silent swallowing makes debugging impossible.

P1.9 — Always destructure both data AND error from Supabase queries; surface error even when downstream logic only reads data. PostgREST returns specific error codes (42703 column-not-found, 42P01 table-not-found, RLS denials) that look like "no rows returned" to a caller that only inspects data. Required pattern: const { data, error } = await supabase.from(...).select(...).single(); if (error) console.error('[handler-name] table-name lookup', error); if (!data) return null;. Source: April 2026 — every FuneralWiseAI demo URL silently returned 404 from PR #135 (commit 283a5129) until 2026-04-27 because getFuneralDemoData() in src/app/s/[slug]/page.tsx selected non-existent columns (street, state, zip_code, latitude, longitude — the table actually has address, state_province, postal_code and no lat/long), only destructured data, swallowed the 42703 column does not exist error, and 404'd. Cold-email demo links broken for weeks. Fix: PR #228.


2. Security

Source: Security audit — found admin tokens in page source, zero CSRF, in-memory rate limiting, unauthenticated checkout, Twilio webhook unverified.

P2.1 — Never pass sensitive fields to client components. admin_token, stripe_customer_id, stripe_subscription_id, access_token, pco_secret, cal_api_key must NEVER appear in React component props. Create separate Server* types for server-only use and Client* types (in *-shared.ts files) for client components.

P2.2 — Every mutation endpoint must verify CSRF. Import verifyCsrf from @/lib/csrf and call it at the top of every POST/PATCH/PUT/DELETE handler that accepts cookie auth. Exceptions: webhooks (Stripe, Twilio), public forms (contact, newsletter), and embedded widgets (chatbot).

P2.3 — Every webhook must verify its signature. Stripe webhooks verify via stripe.webhooks.constructEvent(). Twilio webhooks verify via twilio.validateRequest(). Never trust webhook payloads without cryptographic verification.

P2.4 — Auth must match the route's purpose. Founder routes use FOUNDER_TOKEN from env. Admin routes use resolveTokenOrHeaders(). Public routes use Turnstile or rate limiting. We found founder/action-items accepting any church admin_token instead of FOUNDER_TOKEN — a real auth bypass.

P2.5 — Rate limiting must be distributed. In-memory Maps reset on serverless cold starts. Use Supabase RPC (check_rate_limit) or Upstash Redis. Always fail open — never block legitimate requests due to rate limiter failures.

P2.6 — Validate church exists before expensive operations. The chatbot endpoint must verify churchId exists in premium_churches with chatbot_enabled=true BEFORE any RAG search, LLM call, or tool execution.

P2.7 — Never accept unauthenticated entity IDs for payment flows. The church-checkout route accepted raw church_id from query params — anyone could initiate checkout for any church. Always require an auth token.


3. Client/Server Boundary

Source: Security + UI/UX audits — found server-only modules imported in client components, Supabase service role initialization running in browser bundles.

P3.1 — Add import 'server-only' to every file that imports Supabase admin client. This causes a build-time error if a client component imports it, instead of a silent runtime leak.

P3.2 — Client-safe types go in *-shared.ts files. Follow the pattern: premium-shared.ts, care-shared.ts, tier-config.ts. These files must NEVER import from supabase.ts or any server-only module.

P3.3 — Never import value exports from server files in 'use client' components. Even constants like TOPIC_GROUPS pull in the entire module dependency tree. Extract shared constants to -shared.ts files.


4. SEO & Metadata

Source: SEO audit — found zero canonical URLs on ITW (50K+ pages), brand contamination via title template, missing JSON-LD, sitemap with phantom URLs.

P4.1 — Every page must have an explicit canonical URL. Use alternates: { canonical: 'https://domain.com/path' } in metadata. For multi-domain hostnames (sermonwise.ai, sharewiseai.com), canonicals must point to the custom domain, NOT churchwiseai.com.

P4.2 — Sub-brand pages must use title: { absolute: '...' }. The root layout template %s | ChurchWiseAI will contaminate SermonWise and ShareWiseAI titles unless pages use the absolute format. The /sermons/layout.tsx safety net overrides the template to %s | SermonWise AI.

P4.3 — Sitemap URLs must be generated from the same data source as the pages. Never hardcode URL lists in sitemap.ts. Import the slugs from the page's data source (e.g., INTEGRATION_SLUGS, COMPARISON_SLUGS). This prevents phantom 404s.

P4.4 — Every product page should have JSON-LD structured data. Use SoftwareApplication for product pages, Article for blog posts, FAQPage for pricing FAQs, WebSite for the homepage. Always use JSON.stringify() (prevents script breakout).

P4.5 — Set openGraph.siteName on every sub-brand page. Without it, SermonWise pages show "ChurchWiseAI" in social shares. Add openGraph: { siteName: 'SermonWise AI' } (or appropriate brand) to all sub-brand metadata.


5. Content Accuracy

Source: Marketing audit — found stale "4 agents" references, inflated illustration counts, false social proof, inconsistent tool counts.

P5.1 — Canonical numbers must be consistent site-wide. Before publishing, verify these against CLAUDE.md:

  • Agents: 2 (Care + Coordinator)
  • Tools: 39 total (Starter=12, Pro=35, Suite=39)
  • Traditions: 17 theological lenses
  • Churches: 260K+ in PewSearch directory
  • Illustrations: 30K+ (NOT 320K — that's the total unified_rag_content including non-illustrations)

P5.2 — Never claim social proof you don't have. "Join churches that switched" with 0 customers is a trust-killer. Use aspiration-based copy: "See how ChurchWiseAI can serve your congregation."

P5.3 — Verify marketing claims against product_knowledge table. Before editing any marketing page, query SELECT question, answer FROM product_knowledge WHERE is_active = true and cross-reference. Fix all inaccurate claims in the same PR.

P5.4 — Math must be verifiable. "Save 90%" requires showing the calculation. $39.95 vs $200 is ~80%, not 90%. Always show your work or use "up to X%" with the actual range.

P5.5 — "Coming Soon" products must be gated. If a product isn't launched (ShareWiseAI), ALL signup/login/checkout flows must be replaced with waitlist CTAs. Checkout errors must redirect to a friendly page, never return raw JSON.


6. UI/UX Design

Source: UI/UX expert audit — found WCAG contrast failures (311 instances), font mismatches, inconsistent card/button patterns.

P6.1 — Never use gold #D4AF37 as text color on light backgrounds. Contrast ratio 2.2:1 fails WCAG AA (4.5:1 required). Use #8B6914 or #a07d1c for text on cream/white. Gold is fine for icons and decorative elements.

P6.2 — All pricing pages must be server-rendered. Client-side-only pricing pages (like ITW's) are invisible to search engines. Export metadata from server components.

P6.3 — Follow the design system. Cards: bg-white rounded-xl border border-stone-200 shadow-sm. Sections: alternate bg-[#FEFCF8] / bg-stone-50. Container: max-w-7xl mx-auto px-4 sm:px-6. Hero: bg-gradient-to-b from-stone-900 to-stone-800.

P6.4 — Maintain @media (prefers-reduced-motion: reduce). Any new animation must be suppressed in this media query.


7. Multi-Property Branding

Source: All audits — found ChurchWiseAI header/footer on ShareWiseAI, title contamination on SermonWise, cross-brand confusion.

P7.1 — Sub-brand pages must suppress parent brand chrome. LayoutShell must exclude all /social and /sermons paths from CWA Header/Footer. Each sub-brand has its own navigation components.

P7.2 — Never use regex in 'use client' components. Backslash escapes in regex literals get stripped by formatters, causing build failures. Use string methods (startsWith, includes) instead.

P7.3 — Login/signup pages on sub-brand domains must show sub-brand branding. Users on sermonwise.ai should see "SermonWise AI" in their browser tab and on the page, never "ChurchWiseAI — AI Care Agents for Churches."


8. Marketing Copy

Source: Marketing expert conversion audit — found conversion friction, missing trust signals, CTA hierarchy issues.

P8.1 — Lead with the pain, not the product. Open every section with a problem the pastor recognizes before introducing the solution.

P8.2 — Ministry language first, tech language never. "Prayer request capture" not "NLP entity extraction." The audience is pastors, not CTOs.

P8.3 — Every purchase CTA needs risk reversal within 100px. "14-day free trial," "Cancel anytime," "30-day refund policy" must appear near every "Get Started" button.

P8.4 — Default pricing toggle to the lowest-barrier option. Chat Only + Monthly first. Let the customer upgrade themselves.

P8.5 — Stripe redirect URLs must detect the request hostname. When a user on sermonwise.ai completes checkout, redirect back to sermonwise.ai, not churchwiseai.com.


9. Expected Output Compliance

Source: Tier-gating bugs shipped repeatedly because agents had feature matrices but no per-touchpoint UX specs. "Starter customer sees ElevenLabs voice picker" passed all tests because no spec said it should be hidden.

P9.1 — Never build tier-gated UI without checking the expected output spec. Before modifying any dashboard tab, public page, email, or lifecycle behavior, read the relevant knowledge/acceptance/<tier>.md file. If it doesn't exist, flag it to the founder — don't guess.

P9.2 — Expected output specs are the source of truth for customer UX, not code. If code shows something the spec says should be hidden, the CODE is wrong. If a spec is missing, the feature is not ready to build. Layer 1 (features.yaml) says WHAT a tier includes. Layer 3 (acceptance specs) says what the customer SEES. They are not the same thing.

P9.3 — Update the spec BEFORE changing the code. When a feature change affects what a customer sees, update the acceptance spec first, get founder approval, then change the code, then update the test. Spec → Code → Test, in that order.

P9.4 — When asked to test user journeys, check for the acceptance spec FIRST. If no spec exists in knowledge/acceptance/ for the relevant tier, the correct response is: "There's no acceptance spec for this tier. I can't test the user journey without knowing what the expected journey is. Let's build the spec first." Do NOT substitute code-path testing (buttons click, APIs return 200) for journey testing (customer sees the right things). 159 test files verified code correctness while tier-gating bugs shipped — because no test verified customer experience. If you don't have a spec, say so.

P9.5 — Keep the core product clean. No upsell buttons in the dashboard. When a customer pays for Starter Chat, the dashboard should feel like a complete product — not a demo with locked doors everywhere. No "Upgrade to Pro" buttons cluttering the UI. Instead, use subtle educational moments: "Did you know?" tooltips, gentle nudges in context ("Your chatbot and a voice agent make quite the dynamic duo!"). Upsells belong on the Upgrade tab, not sprinkled across every feature. If a feature isn't in their plan, HIDE it — don't show it locked.

P9.6 — Agents are biased toward action. Resist it when specs are missing. The instinct when asked to "test this" is to write tests and show results, not to push back with "we're missing something fundamental." Saying "I can't do this yet" feels like failure, but shipping code that nobody specified is worse. Flag the gap. Ask for the spec. Then build.