Living Word — UGC Phase 1A (shipped 2026-05-24)
What this is. Tier 1 of Living Word's UGC stack — educators on a paid tier can author quiz questions for the existing canonical scenes, locked to one of the 17 tradition lenses, with private-scope visibility (their own class only). The submit path runs a synchronous regex pass (AI Bridge banned-phrase bank + 13-rule UGC hard floor + structural + scripture validation) followed by an asynchronous semantic pass (Anthropic Haiku 4.5 → JSON judge against the lens and the same 13 rules). Auto-approval on pass; auto-rejection with a specific violation list on fail; auto-suspension on rules g or h.
Status. Shipped to production 2026-05-24. Migrations applied to prod Supabase (
wrwkszmobuhvcfjipasi). Educator dashboard live at/living-word/educator.
Architecture
Schema (six tables, all RLS)
lw_classrooms— stub for Phase 1A. One synthetic classroom per author. Real spec lands asliving-word-classroom-rosters.mdbefore Phase 1B ships.lw_ugc_schools— school registration record (carries through Phase 1B+; Phase 1A reads/writes are nullable).lw_ugc_authors— educator profile + credential state. Auto-created on first authoring attempt. Author-readable; reviewer-only fields written via service-role admin client.lw_ugc_quizzes— primary content table. Status state machine: draft → submitted → checking → (auto_approved | rejected) → (published | retired). Phase 1A: onlyvisibility='private'is shippable; school_pool and public values pass the DB CHECK but are rejected at the API layer with a specific error.lw_ugc_quiz_class_assignments— many-to-many quiz↔classroom for the private-scope visibility join.lw_ugc_quiz_audit_log— append-only state-transition trail. Every action (created, submitted, autocheck_passed/failed, semantic_passed/ failed, published, retired, suspended, reinstated, reported) writes a row via the service-role admin client.
Moderation pipeline (founder Decision E1, 2026-05-24)
Sync regex pass (<2s, blocking submit):
- Structural validation (length limits, exactly 4 options, lens 1-17, etc.)
- AI Bridge banned-phrase regex bank (ported from
voice-agent-livekit/moderation.py_BANNED_CONFIDENTIALITY_PHRASES) — hard-block on match. - 13-rule UGC hard-floor regex bank (rules a-m). Rules g and h are HARD-BLOCK-NO-REVIEW and auto-suspend the author. Rules a-f, i-m are FLAG severity and reject the quiz with the violation list.
- Scripture-anchor validator — pure local lookup against the 66-book Protestant canon + chapter/verse counts.
Async semantic pass (5-10s typical, 30s hard timeout, runs post-submit
with status='checking'):
- Single Anthropic Haiku 4.5 call (
claude-haiku-4-5-20251001) via the REST API. Per CLAUDE.md rule 5 carve-out — UGC moderation is real-time, not batch generation, so API path is correct (mirrorssrc/lib/outreach/provision.ts → generateProductKnowledgeViaAPI()). Cost ≈ $0.0005 per check. - JSON-only output, parsed into
{passed, failures[]}. Failures fail the quiz withrejection_reason = 'semantic: ...'. - Lens-vocabulary critical mismatch is a
lens-mismatchfailure id. - Timeout → rejected with
check_timeout(author can retry). - 5xx from Anthropic → fail-OPEN so transient provider blips don't block authors; logged for founder review.
- No
ANTHROPIC_API_KEYin env → fail-OPEN (local dev / preview don't block submissions; production has the key).
In-game pool merge
/api/living-word/quizzes was extended to merge published UGC private
quizzes into the canonical pool for the calling authenticated user. The
selector targets ~40% UGC frequency per spec §8.2 with deduplication and
a final shuffle. UGC rows carry the ugc:<uuid> id prefix so the in-game
quiz card can render the attribution badge.
AI Bridge in the moderator role
Per the founder's 2026-05-24 note (paired with the lw_classrooms canonical
spec landing) every LLM call inside the Living Word stack must open with
the AI Bridge frame as the FIRST system-prompt block. The UGC semantic
auto-check uses an adapted moderator-role frame (mirrors
voice-agent-livekit/core/prompt_fragments.py:AI_BRIDGE_FRAME and adapts
for the moderation context, where the LLM is reviewing quiz content
rather than speaking to a caller). The frame anchors the moderator on
flagging-not-deciding: "you are AI, you are not a theologian, you flag
content for the human editorial team to review." A contract-test in
ugc-quiz-moderation.contract.test.ts asserts the frame is present and
precedes the moderator role definition.
The educator UX honestly labels the AI check via an inline transparency note ("On submit, we run an AI quality check (Claude Haiku 4.5)…") and the submit button copy ("Submit for AI quality check" / "Running AI quality check (about 30 seconds)…").
Quiz presentation in-game (data-only handoff)
The in-game NPC dialogue runtime is splitting into a scripted default
and an opt-in Scribe path (see acceptance/living-word-ai-mode-toggle.md).
The UGC quiz-presentation path is a pure data handoff: the merged pool
in /api/living-word/quizzes returns the quiz STRUCTURE (question,
options, correct_index, explanation) and the in-game quiz card renders
that structure identically regardless of the NPC's dialogue runtime.
No LLM call is required to deliver a UGC quiz to a player; the LLM call
only fires inside the educator's submit pipeline.
Pricing-gate
/living-word/educator/* is layout-gated. isEducatorAuthoringTier() in
src/lib/living-word/ugc-tier.ts accepts:
lw_educator_*(the future Living-Word-specific SKUs)founder_grant(pre-launch hand-stamping path)- Existing premium product tiers as a pre-launch grant:
cwa_suite_both,cwa_pro_chat,cwa_pro_website,cwa_starter_voice,sermon_pro lw_ugc_authors.is_verified_seminary = true(the Decision-2 fast-track)
The pre-launch grant carve-out exists so the founder can test the
authoring flow with real educator-tier accounts before paid SKUs ship.
Before live-charging launches, the carve-out must be narrowed to
lw_educator_* only (see "Open items" below).
What we did NOT ship (deferred to Phase 1B + 1C)
- School-pool visibility — needs
living-word-classroom-rosters.mdspec to land first (Phase D in the engineering handoff). - Public-library publish — needs the editorial contractor hire decision (Phase 1C, month 6+ per founder Decision 1).
- CSV bulk import — optional in Phase 1A per spec §5; Phase 1B mandatory.
- Editorial review queue UI (
/founder/[token]/ugc-review) — Phase 1A spec referenced it as an endpoint for founder-only escalation review; not yet built since no escalations exist yet. The audit log + RLS policies are in place so the queue UI is a pure read on top. - Multi-tradition reviewer panel — Tier 2+ surface; spec referenced for forward compatibility (Decision 3 single-veto-default-majority).
Tests
- Contract test
src/lib/__tests__/ugc-quiz-moderation.contract.test.ts(28 cases, all passing). Asserts the 13 rules are present at the correct severity and validates one positive-trigger case per rule including the rule-k "biblical narrative weapons exempt" carve-out and the rule-m "spiritual disciplines vs binding-law" carve-out. - Playwright E2E
e2e/living-word/ugc-phase1a.spec.ts(5 cases) covers educator auth → lint hard-block → submit hard-block (rule h) → clean submit → student pool merge → dashboard page renders. Skips gracefully if the semantic pass disposition isn'tpublished(e.g. CI without ANTHROPIC_API_KEY).
Open items
- Narrow the pre-launch pricing-gate carve-out before paid SKUs go
live (
src/lib/living-word/ugc-tier.tsELIGIBLE_LITERALS). Today it accepts existing premium product tiers; oncelw_educator_*SKUs ship, drop the carve-out. - Editorial review UI at
/founder/[token]/ugc-review— Phase 1A ships with the underlying data + RLS but no queue UI. Build when first escalation lands. lw_classroomsreal spec (Phase D in the engineering handoff) before Phase 1B. The current stub (one synthetic classroom per author) breaks the moment a teacher wants to manage students.- Editorial contractor hire per Decision 1 — needed before Phase 1C ships.