Skip to main content

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_classroomsstub for Phase 1A. One synthetic classroom per author. Real spec lands as living-word-classroom-rosters.md before 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: only visibility='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):

  1. Structural validation (length limits, exactly 4 options, lens 1-17, etc.)
  2. AI Bridge banned-phrase regex bank (ported from voice-agent-livekit/moderation.py _BANNED_CONFIDENTIALITY_PHRASES) — hard-block on match.
  3. 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.
  4. 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 (mirrors src/lib/outreach/provision.ts → generateProductKnowledgeViaAPI()). Cost ≈ $0.0005 per check.
  • JSON-only output, parsed into {passed, failures[]}. Failures fail the quiz with rejection_reason = 'semantic: ...'.
  • Lens-vocabulary critical mismatch is a lens-mismatch failure 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_KEY in 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.md spec 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't published (e.g. CI without ANTHROPIC_API_KEY).

Open items

  1. Narrow the pre-launch pricing-gate carve-out before paid SKUs go live (src/lib/living-word/ugc-tier.ts ELIGIBLE_LITERALS). Today it accepts existing premium product tiers; once lw_educator_* SKUs ship, drop the carve-out.
  2. 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.
  3. lw_classrooms real 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.
  4. Editorial contractor hire per Decision 1 — needed before Phase 1C ships.