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
| Surface | Location | Used by |
|---|---|---|
| Static lens definitions | src/lib/theolenses.ts (17 lenses, principles fields) | Sermon engine, admin pages, marketing |
| Runtime vocabulary | lens_knowledge table (Postgres) — JSONB preferred_vocabulary, avoided_vocabulary, key_doctrines | Chatbot /api/chatbot/stream via fetchLensVocabulary() |
| Hard doctrinal rules | theological_contradictions table — per-lens, per-topic must-include / must-exclude term lists | Chatbot system prompt via doctrinalRulesBlock |
| Curated RAG content | unified_rag_content table, scoped by theological_lens_id | Chatbot 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 id | Tradition | Distinctive vocabulary | Strictly forbidden |
|---|---|---|---|
| 6 | Lutheran | "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" |
| 7 | Roman 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" |
| 11 | Eastern Orthodox | "Holy Mysteries", "Divine Liturgy", "real presence", "Body and Blood", "medicine of immortality" | "merely symbolic", "just a symbol", "memorialist", "Zwinglian", "transubstantiation" (Western scholastic) |
| 13 | Anglican | "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 id | Tradition | Distinctive vocabulary | Strictly forbidden |
|---|---|---|---|
| 14 | Baptist (Distinctive) | "ordinance", "memorial", "remembrance", "symbol", "in remembrance of Me" | "sacrament" (in the Eucharistic sense), "Eucharist", "real presence", "transubstantiation", "Mass" |
| 10 | Christocentric / 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
nullforlensId(training promote, founder response-review) still work and read/writelens_id = NULLrows.
Migration: migrations/2026-05-24-chatbot-cache-lens-scoping.sql.
Code path
/api/chatbot/streamresolveslensId(override →church_theological_lensesrow →DENOMINATION_TO_LENSmap → default 10).runFastPath(churchId, message, embedding, churchKB, lensId)checks the lens-scoped semantic cache. Cache HIT → return immediately.- Cache MISS →
fetchLensVocabulary(lensId)readslens_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). theological_contradictionsfor the lens are queried;must_include_termsandmust_exclude_termsare emitted as hard rules indoctrinalRulesBlock.searchRAG({ lensIds: [lensId] })retrieves curated content tagged for this lens (plusis_universal=truecontent). Cross-lens RAG content is excluded.- LLM (Haiku 4.5) generates response.
cacheResponse(... lensId)writes back with the lens id.
Test coverage — e2e/theology-vocabulary.spec.ts
| Test | Lens id | Positive signal | Negative guard |
|---|---|---|---|
| Baptist baptism | 14 | Believer / immersion / Romans 6 / decision-language | NOT "transubstantiation", NOT "eucharist" |
| Catholic communion | 7 | Eucharist / sacrament / real presence / transubstantiation / Body and Blood / Mass / consecrat | NOT "merely symbolic", NOT "just a symbol", NOT "memorialist", NOT "only a symbol" |
| Lutheran communion | 6 | real presence / in, with, and under / sacramental union / means of grace / given and shed | NOT memorialist framing |
| Orthodox communion | 11 | Holy Mysteries / Divine Liturgy / Body and Blood / medicine of immortality / theosis | NOT memorialist framing |
| Anglican communion | 13 | real presence / Eucharist / Holy Communion / sacrament / Book of Common Prayer | NOT 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 lens | 7 | (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)
- Outstanding: lens-name-vs-church-profile conflict at demo church. When a
lensOverrideflips the lens butchurchName/church.denominationstill 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 whenlensNameis explicitly overridden. - Lens-unaware cache writes in
src/app/api/admin/training/route.tsandsrc/app/api/founder/response-review/route.tsstill passlensId = 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):
theological_contradictions.must_include_terms/must_exclude_terms(highest — these are absolute rules)lens_knowledge.key_doctrines.{key_terms, avoid_terms}lens_knowledge.preferred_vocabulary.use/avoided_vocabulary.avoid- Tradition
principlestext intheolenses.ts(static, used by sermon engine + admin) - RAG content (filtered to lens via
theological_lens_idoris_universal=true) - 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.