Skip to main content

Theology Vocabulary System

Why this exists

The chatbot serves 17 theological traditions ("lenses"). Each tradition has distinctive vocabulary that signals doctrinal alignment to its members and contradicts other traditions if used incorrectly. A Catholic visitor who hears "the Eucharist is merely symbolic" loses trust in seconds — the church's central sacramental teaching just got contradicted by its own chatbot. The same trust-rupture applies to Orthodox, Anglican high-church, and Lutheran visitors (all hold Real Presence), and inversely to Baptist / non-denominational visitors if Real Presence vocabulary is injected.

This document is the contract for what each lens must require and forbid on the most lens-sensitive topics (communion, baptism, sacraments, soteriology) and how the chatbot loads and enforces that vocabulary.

Where the vocabulary lives

SurfaceLocationUsed by
Static lens definitionssrc/lib/theolenses.ts (17 lenses, principles fields)Sermon engine, admin pages, marketing
Runtime vocabularylens_knowledge table (Postgres) — JSONB preferred_vocabulary, avoided_vocabulary, key_doctrinesChatbot /api/chatbot/stream via fetchLensVocabulary()
Hard doctrinal rulestheological_contradictions table — per-lens, per-topic must-include / must-exclude term listsChatbot system prompt via doctrinalRulesBlock
Curated RAG contentunified_rag_content table, scoped by theological_lens_idChatbot RAG retrieval (searchRAG)

The chatbot reads lens_knowledge and theological_contradictions at every request and injects them as system-prompt fragments via buildChatVocabularyBlock() and doctrinalRulesBlock.

Real Presence lenses (load-bearing — 2026-05-24)

Four traditions confess that Christ is truly present in the consecrated bread and wine of the Eucharist. Their chatbot answers about communion MUST signal this and MUST NOT inject memorialist / Zwinglian framing.

Lens idTraditionDistinctive vocabularyStrictly forbidden
6Lutheran"real presence", "in, with, and under", "sacramental union", "means of grace", "given and shed for you""transubstantiation" (Catholic), "merely symbolic", "just a symbol", "memorialist", "Zwinglian"
7Roman Catholic"the Eucharist", "transubstantiation", "the Real Presence", "the Holy Sacrifice of the Mass", "the Blessed Sacrament", "Body and Blood, Soul and Divinity""just a symbol", "merely symbolic", "only a symbol", "only a memorial", "memorialist", "Zwinglian"
11Eastern Orthodox"Holy Mysteries", "Divine Liturgy", "real presence", "Body and Blood", "medicine of immortality""merely symbolic", "just a symbol", "memorialist", "Zwinglian", "transubstantiation" (Western scholastic)
13Anglican"real presence", "the Eucharist", "Holy Communion", "sacramental", "Book of Common Prayer""merely symbolic", "just a symbol", "memorialist", "Zwinglian", "transubstantiation (as required doctrine)", "consubstantiation"

Memorialist lenses (negative guardrail)

These traditions reject Real Presence and view communion as a memorial ordinance. Real Presence vocabulary must NOT bleed into their answers.

Lens idTraditionDistinctive vocabularyStrictly forbidden
14Baptist (Distinctive)"ordinance", "memorial", "remembrance", "symbol", "in remembrance of Me""sacrament" (in the Eucharistic sense), "Eucharist", "real presence", "transubstantiation", "Mass"
10Christocentric / Non-denominational"gospel signs", "means of grace", "proclamation of Christ's death", "visible Word""mere symbols", "ordinances we perform", "ritual obligation"

Cache contract — 2026-05-24 fix

chatbot_response_cache is now keyed on (church_id, lens_id, embedding-similarity). The legacy schema keyed only on (church_id, similarity), which caused a P1 regression at the demo church: every e2e test forces a lensOverride so the demo church served traffic for ALL 17 lenses. A cached answer generated under the first test's lens was returned to every subsequent test under any other lens — including doctrinally incompatible ones.

The RPC match_cached_response(p_church_id, p_query_embedding, p_threshold, p_lens_id) uses lens_id IS NOT DISTINCT FROM p_lens_id so:

  • Production traffic (every chatbot request resolves a lens) is segmented by tradition.
  • Legacy code paths that pass null for lensId (training promote, founder response-review) still work and read/write lens_id = NULL rows.

Migration: migrations/2026-05-24-chatbot-cache-lens-scoping.sql.

Code path

  1. /api/chatbot/stream resolves lensId (override → church_theological_lenses row → DENOMINATION_TO_LENS map → default 10).
  2. runFastPath(churchId, message, embedding, churchKB, lensId) checks the lens-scoped semantic cache. Cache HIT → return immediately.
  3. Cache MISS → fetchLensVocabulary(lensId) reads lens_knowledge, buildChatVocabularyBlock() emits it as a system-prompt fragment with PREFERRED / AVOIDED / KEY DOCTRINAL POSITIONS sections (the doctrine section now includes per-doctrine MUST USE and NEVER USE lists, fixed 2026-05-24).
  4. theological_contradictions for the lens are queried; must_include_terms and must_exclude_terms are emitted as hard rules in doctrinalRulesBlock.
  5. searchRAG({ lensIds: [lensId] }) retrieves curated content tagged for this lens (plus is_universal=true content). Cross-lens RAG content is excluded.
  6. LLM (Haiku 4.5) generates response. cacheResponse(... lensId) writes back with the lens id.

Test coverage — e2e/theology-vocabulary.spec.ts

TestLens idPositive signalNegative guard
Baptist baptism14Believer / immersion / Romans 6 / decision-languageNOT "transubstantiation", NOT "eucharist"
Catholic communion7Eucharist / sacrament / real presence / transubstantiation / Body and Blood / Mass / consecratNOT "merely symbolic", NOT "just a symbol", NOT "memorialist", NOT "only a symbol"
Lutheran communion6real presence / in, with, and under / sacramental union / means of grace / given and shedNOT memorialist framing
Orthodox communion11Holy Mysteries / Divine Liturgy / Body and Blood / medicine of immortality / theosisNOT memorialist framing
Anglican communion13real presence / Eucharist / Holy Communion / sacrament / Book of Common PrayerNOT memorialist framing
Baptist communion (negative)14(n/a)NOT "transubstantiation", NOT "in, with, and under", NOT "sacramental union", NOT "medicine of immortality"
Christocentric communion (negative)10(n/a)Same Real-Presence-term blacklist
Factual question under Catholic lens7(n/a — factual answer)NOT "transubstantiation", NOT "magisterium" (scoring-rubric guard from MEMORY.md — don't stuff theological jargon into factual answers)

Known follow-ups (not in this PR)

  1. Outstanding: lens-name-vs-church-profile conflict at demo church. When a lensOverride flips the lens but churchName / church.denomination still belong to a different tradition, the LLM resolves the contradiction toward the church profile (especially for non-denominational demo churches). Real customers don't hit this because their lens matches their denomination. Documented as Proposal A in PR for /api/chatbot/stream/route.ts — strengthen the lens-binding clause when lensName is explicitly overridden.
  2. Lens-unaware cache writes in src/app/api/admin/training/route.ts and src/app/api/founder/response-review/route.ts still pass lensId = null. Acceptable for now because both are pastor-promoted, human-approved responses and the cache writer was at the time lens-unaware. Add lens resolution if these routes start serving multi-lens churches.

Trust hierarchy on conflicts

When the lens vocabulary contradicts other content (RAG, FAQ, product_knowledge):

  1. theological_contradictions.must_include_terms / must_exclude_terms (highest — these are absolute rules)
  2. lens_knowledge.key_doctrines.{key_terms, avoid_terms}
  3. lens_knowledge.preferred_vocabulary.use / avoided_vocabulary.avoid
  4. Tradition principles text in theolenses.ts (static, used by sermon engine + admin)
  5. RAG content (filtered to lens via theological_lens_id or is_universal=true)
  6. Generic system-prompt warmth / structure

The bot must answer doctrinal questions using the lens vocabulary even when the church profile (denomination, custom_name, staff list) belongs to a different tradition. Profile facts (address, service times, pastor name) remain authoritative for non-doctrinal information.