Living Word UGC Phase 1 — Educator-Authored Quizzes (Tier 1)
Status. Canonical acceptance spec. Engineering builds against this file. The founder reviews before any code lands. What this spec is. A build brief for Tier 1 of Living Word's user-generated content moat — educator-authored quiz questions attached to existing scenes, lens-locked, multi-stage moderated, monotonically promoted from private → school-pool → public. What this spec is NOT. It is not a strategic narrative (see
drafts/2026-05-23-living-word-ugc-research.md). It is not a Tier 2, 3, or 4 design. It is not an LMS integration plan. Those live elsewhere or are deferred. Load-bearing constraints. Founder decisions 1, 2, 3, and 5 fromdrafts/2026-05-23-living-word-founder-decisions.mdare quoted verbatim in §1 and are not up for re-negotiation. The AI Bridge Principle (knowledge/architecture/ai-bridge-principle.md) governs every moderation surface. The 17-tradition lens system is the moderation moat — it is always new in how it answers each tradition, and it deepens with each new piece of authored content the platform validates.
§1 — Founder-locked decisions (verbatim)
These four decisions, locked on 2026-05-23 by the founder, govern the entire spec. Engineering cannot re-litigate them. If engineering reads this spec and disagrees with one of these decisions, it raises the disagreement to the founder — it does not silently re-shape the spec.
Decision 1 — Editorial review staffing (UGC)
Founder answer: A → B (founder reviews 90 days, hires part-time contractor by month 6)
"A for the first 90 days post-Tier-1-launch to learn what review actually requires + what kinds of content educators send + what the failure modes look like. Then graduate to B by month 6 with a part-time contractor (target a seminary student or recently-ordained pastor; $20-25/hr, ~5-10 hrs/week)."
Implication for this spec. Phase 1A (months 1-3) assumes the founder is the sole editorial reviewer for any path that requires human review. Phase 1C (months 6-9, when public-library publish becomes available) assumes a contractor is hired. The data model and UI MUST support a generic "editorial reviewer" role from day one so the contractor transition is a row insert, not a schema migration.
Decision 2 — Credential gate for public-library publish
Founder answer: B + seminary fast-track (demonstration-first; credentialed authors skip the 5-publish demo)
"B (demonstration-first) for these reasons: (1) credentials are a poor proxy for Living-Word-quality content authorship; the best Sunday school teachers often have zero formal credentials; (2) demonstration gates self-select for educators who already love the platform; (3) the multi-tradition reviewer panel (Decision 3) is the real quality backstop, not the credential. But — write the credential carve-out clause too: seminary-credentialed authors get fast-tracked to public-library status without the 5-publish demonstration. Best of both."
Implication for this spec. The path to public-library publish is either (a) 5 well-rated school-pool publishes + ≥1 peer educator endorsement, OR (b) verified seminary credential. Both paths exist in code from day one. The verification record for path (b) lives on lw_ugc_authors and is set by the editorial reviewer after document review.
Decision 3 — Multi-tradition reviewer panel sign-off rule
Founder answer: C (single-veto trigger, default majority)
"Single-veto trigger, default majority. Reasoning: a true heresy or harm-to-kids signal usually comes from one reviewer noticing what the others missed. Forcing unanimous would block too much; pure majority would let the rare-but-important veto get outvoted. The flag-for-override path lets one reviewer say 'this is bad enough to escalate even if the others approve' without giving any single reviewer a pocket veto. The editorial team (Decision 1) makes the final call."
Implication for this spec. The multi-tradition reviewer panel is a Tier 2+ surface. Tier 1 quizzes do NOT trigger the multi-tradition panel. Quizzes are bounded enough in structure (a question + four options + one explanation) that the four automated checks plus single editorial reviewer (Decision 1) are the full pipeline for public-library Tier 1 publish. The single-veto-default-majority rule is referenced here so engineering knows it is coming for Tier 2 and does not architect away from it.
Decision 5 — UGC "we will never" hard floor
Founder answer: All 13 (a-m) as hard floor (founder OVERRODE the a-j recommendation — wants the maximally rigorous moderation floor including firearms (k), mental-health-disparagement (l), and dietary-laws-as-binding-on-Christians (m))
"The founder's override on #5 is significant — it commits Living Word to the strictest UGC editorial floor available, which is brand-protective AND legally defensible. k-l-m are all theologically and ethically sound additions; my reservation was overcautious. The lens-vocabulary linter and AI Bridge audit will be updated to enforce all 13."
Implication for this spec. All 13 rules a-m are enforced as a hard floor at the auto-check layer. The full enumeration with per-rule enforcement mechanisms is in §7. Engineering MUST NOT ship Tier 1 with any of the 13 omitted, weakened, or made "advisory only."
§2 — Scope (what Tier 1 ships)
WHO authors. Any educator on a paid Living Word tier from $79/yr single-educator and up. The free tier and the $4.95/mo personal-premium tier are read-only players. (See §3 for the full pricing-gate matrix.)
WHAT they author. Quiz questions for EXISTING canonical Living Word scenes (the 16 scenes shipped through Wave-7). Tier 1 does NOT include educator-authored NPCs (Tier 2), full scenes (Tier 3), or student-authored content (Tier 4). Each quiz item is a structured record:
- Question text (≤200 chars)
- Four answer options (each ≤150 chars)
- Correct-answer index (0-3)
- Brief explanation, shown after the player answers (≤300 chars)
- Declared tradition lens (one of 17, FK to the existing
theolensestaxonomy) - Declared difficulty (one of:
easy/intermediate/advanced/scholar) - Declared age-rating (one of:
8-12/13-17/18+/all-ages)
The shape mirrors the existing questionsByDifficulty.{easy,scholar}[] arrays already in the scene data files (e.g., the burning bush rebuild). The in-game quiz engine consumes the same shape from either canonical or UGC sources at scene-load time.
VISIBILITY TIERS.
- Private (Phase 1A — ships first). Visible only to students in the author's own class. Auto-published on auto-check pass; no human review.
- School-pool (Phase 1B — depends on
lw_classroomsspec). Visible to all classes at the author's registered school. Requires school-admin sign-off after auto-checks pass. - Public library (Phase 1C — ships after contractor hire, month 6+). Visible to every Living Word player whose settings opt-in. Requires editorial-team review (founder months 1-3; founder + contractor months 6+).
HOW IT APPEARS IN GAME. When a student loads a scene:
- The canonical authored quiz pool for that scene loads as today.
- The student's own class's private quizzes get merged into the scene's quiz pool, weighted to surface at approximately 40% frequency (a value the in-game quiz selector must respect; not a hard guarantee, since pool size variance affects realized frequency).
- School-pool quizzes (Phase 1B+) get merged into the pool for students whose class belongs to the publishing school.
- Public-library quizzes (Phase 1C+) get merged into the pool for students whose settings have opted-in to public UGC.
- UGC quizzes carry a subtle attribution badge on the question card ("from your teacher Ms. Okonkwo" for private; "shared from St. Mark's Christian School" for school-pool; "from the Living Word library — Pastor Hawkins" for public). The attribution is informational, not promotional.
AUTHORING SPEED TARGET. A fluent educator MUST be able to author and save their first complete quiz item in ≤60 seconds (Kahoot parity). Median time from "start authoring" to "first quiz saved" across the first 100 authoring sessions MUST be ≤90 seconds.
WHAT TIER 1 EXPLICITLY DOES NOT DO (deferred to Tier 2+; do not build any of these as part of Phase 1):
- Educator-authored NPCs or NPC dialogue overlays
- Educator-authored full scenes (map, NPCs, enemies, scrolls)
- Student-authored content of any kind
- Marketplace listings or cross-school licensing with revenue share
- LMS integration (Canvas, Google Classroom, Blackboard, Schoology)
- Multi-language authoring (English only in Phase 1)
- Real-time co-authoring (only one editor per quiz draft at a time)
- Versioning / branching of published quizzes (published = immutable; edits become new quizzes)
§3 — Pricing gate
The matrix below references tiers defined in the Phase 3 monetization plan (see drafts/2026-05-23-living-word-ugc-research.md §9). This spec REFERENCES those tiers; it does NOT redefine them. Engineering reads the tier names off lw_ugc_authors.subscription_tier (or wherever the live subscription state lands by Phase 1A ship time).
| Tier | Can author Tier 1 quizzes? | Visibility unlocked |
|---|---|---|
| Free | No (read-only player) | — |
| $4.95/mo personal premium | No (consumer-tier; unlocks canonical content but not authoring) | — |
| $79/yr single-educator | YES | private + school-pool |
| $299/yr classroom-pack | YES | private + school-pool |
| $999/yr school-wide | YES | private + school-pool + may submit publish requests to public library |
| Credentialed seminary author (verified) | YES (fast-tracked per Decision 2) | private + school-pool + public-library direct (skips the 5-publish demonstration) |
A user whose subscription downgrades from a Tier-1-eligible tier to a non-eligible tier MUST retain read access to their previously authored quizzes but lose the ability to author new ones until they re-upgrade. Previously published private/school-pool quizzes stay live; the system does NOT auto-retire content on downgrade.
The /living-word/educator route MUST be gated at the layout level; ineligible users are redirected to /from-living-word/educator (the upsell landing page — to be authored under the Phase 1A lead-gen spec, not here).
§4 — Data model (DB tables)
All tables live in churchwiseai-web/migrations/2026-XX-XX-lw-ugc-quizzes.sql. All tables under RLS. Owner: churchwiseai-web (per the post-decouple migration-ownership rule).
lw_ugc_quizzes
Primary content table.
| Column | Type | Notes |
|---|---|---|
id | UUID PK | gen_random_uuid() |
scene_id | TEXT NOT NULL | references the canonical scene id (e.g., burning-bush, noahs-ark) |
author_user_id | UUID NOT NULL | FK to auth.users.id; also indexed via join through lw_ugc_authors.user_id |
lens_id | INT NOT NULL | 1-17, FK to the existing tradition lens taxonomy (see src/lib/theolenses.ts) |
difficulty | TEXT NOT NULL CHECK (difficulty IN ('easy','intermediate','advanced','scholar')) | |
age_rating | TEXT NOT NULL CHECK (age_rating IN ('8-12','13-17','18+','all-ages')) | |
question | TEXT NOT NULL CHECK (char_length(question) BETWEEN 5 AND 200) | |
options | JSONB NOT NULL | array of exactly 4 strings; per-option char_length BETWEEN 1 AND 150 (enforced in app + via JSONB CHECK) |
correct_index | INT NOT NULL CHECK (correct_index BETWEEN 0 AND 3) | |
explanation | TEXT NOT NULL CHECK (char_length(explanation) BETWEEN 5 AND 300) | |
visibility | TEXT NOT NULL DEFAULT 'private' CHECK (visibility IN ('private','school_pool','public')) | |
status | TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','submitted','auto_approved','editorial_review','rejected','published','retired')) | |
rejection_reason | TEXT NULL | populated when status='rejected'; structured JSON also stored in audit log |
created_at | TIMESTAMPTZ NOT NULL DEFAULT now() | |
updated_at | TIMESTAMPTZ NOT NULL DEFAULT now() | |
published_at | TIMESTAMPTZ NULL | set on transition into 'published' |
Indexes:
(scene_id, visibility, status)for the in-game quiz-pool selector(author_user_id, status)for the author's dashboard list(visibility, status, published_at DESC)for the editorial review queue
RLS:
- Author can SELECT, INSERT, UPDATE, DELETE rows where
author_user_id = auth.uid()ANDstatus IN ('draft','submitted','rejected'). - Author can SELECT (but not UPDATE) their own rows in any other status.
- Players can SELECT rows whose visibility intersects their viewing scope:
visibility='private'→ row's class assignment (vialw_ugc_quiz_class_assignments) must include the player's classvisibility='school_pool'→ author'sschool_idmust match the player's class'sschool_idvisibility='public'→ all players whose settings have opted-in to public UGC
- Editorial reviewers can SELECT all rows; can UPDATE rows in
editorial_reviewstatus only.
lw_ugc_authors
Educator profile + credential state.
| Column | Type | Notes |
|---|---|---|
user_id | UUID PK | FK to auth.users.id (1:1 with auth user) |
display_name | TEXT NOT NULL | shown on attribution badge |
role | TEXT NOT NULL CHECK (role IN ('sunday_school','christian_school','chaplain','bible_study','pastor','other')) | self-declared |
school_id | UUID NULL | FK to lw_ugc_schools.id; nullable until the author joins or registers a school |
school_role | TEXT NULL CHECK (school_role IN ('classroom_teacher','principal','headmaster','youth_director','admin_staff','other') OR school_role IS NULL) | |
is_verified_seminary | BOOLEAN NOT NULL DEFAULT false | enables the Decision-2 fast-track to public-library |
seminary_program | TEXT NULL | free-text; only meaningful when is_verified_seminary=true |
credentialed_at | TIMESTAMPTZ NULL | set by the editorial reviewer when verification documents are accepted |
suspended_at | TIMESTAMPTZ NULL | non-null = author cannot publish until reinstated |
suspended_reason | TEXT NULL | required when suspended_at is set |
RLS:
- Author can SELECT, UPDATE their own row (except
is_verified_seminary,credentialed_at,suspended_at,suspended_reason— those are reviewer-only). - School admins (any
lw_ugc_authorsrow at the sameschool_idwithschool_role IN ('principal','headmaster','admin_staff')) can SELECT all rows at their school. - Editorial reviewers can SELECT, UPDATE all rows.
lw_ugc_schools
School registration record.
| Column | Type | Notes |
|---|---|---|
id | UUID PK | |
name | TEXT NOT NULL | |
address_city | TEXT NULL | |
address_state_province | TEXT NULL | |
address_country | TEXT NOT NULL DEFAULT 'US' | ISO 3166-1 alpha-2 |
headmaster_user_id | UUID NULL | FK to auth.users.id; the primary school admin who can sign off on school-pool publish |
denomination_tradition_id | INT NULL | FK to lens; if set, narrows the default lens allowlist for the school's authors |
verified_at | TIMESTAMPTZ NULL | set when the editorial reviewer confirms the school is a real institution |
RLS:
- School members (rows in
lw_ugc_authorswith matchingschool_id) can SELECT. - Headmaster (
headmaster_user_id = auth.uid()) can UPDATE. - Editorial reviewers can SELECT, UPDATE all rows.
lw_ugc_quiz_audit_log
Append-only audit trail. Required for the editorial pipeline accountability and for incident post-mortems.
| Column | Type | Notes |
|---|---|---|
id | UUID PK | |
quiz_id | UUID NOT NULL | FK to lw_ugc_quizzes.id; ON DELETE CASCADE |
action | TEXT NOT NULL CHECK (action IN ('autocheck_passed','autocheck_failed','editorial_assigned','editorial_approved','editorial_rejected','visibility_promoted','retired','suspended','reinstated')) | |
actor_user_id | UUID NULL | NULL when action is system-initiated (e.g., autocheck_passed) |
details | JSONB NOT NULL DEFAULT '{}' | structured details (specific check that fired, reviewer notes, etc.) |
occurred_at | TIMESTAMPTZ NOT NULL DEFAULT now() |
RLS:
- Editorial reviewers can SELECT all.
- Quiz author can SELECT rows for their own quizzes.
- INSERT performed via server-only service-role client; no client-side INSERT allowed.
lw_ugc_quiz_class_assignments
Many-to-many between quizzes and classes, for private-visibility scope.
| Column | Type | Notes |
|---|---|---|
id | UUID PK | |
quiz_id | UUID NOT NULL | FK to lw_ugc_quizzes.id ON DELETE CASCADE |
classroom_id | UUID NOT NULL | FK to lw_classrooms.id (see deferred dependency note below) |
assigned_at | TIMESTAMPTZ NOT NULL DEFAULT now() |
UNIQUE (quiz_id, classroom_id).
Deferred dependency. For Phase 1A (private-only visibility), the lw_classrooms table can be a stub — a single-column table with id UUID PK populated as one row per author (so "the author's class" is a synthetic single classroom). Phase 1B (school-pool visibility) requires a real lw_classrooms spec covering classroom rosters, student membership, and teacher assignment. Engineering MUST NOT ship Phase 1B without that spec landing first. The spec name is living-word-classroom-rosters.md, to be authored before Phase 1B work begins.
Migration naming convention
The migration file follows churchwiseai-web/migrations/2026-MM-DD-lw-ugc-quizzes.sql. If the schema lands in multiple PRs, sequence with 2026-MM-DD-lw-ugc-quizzes-part-N.sql.
§5 — Authoring UX (the 60-second-to-first-quiz target)
The authoring flow is modeled on Kahoot's single-screen quiz creator. Goal: a fluent educator can create and save their first quiz item in ≤60 seconds; median across first 100 sessions ≤90 seconds.
Entry and dashboard
- The educator navigates to
/living-word/educator. The route is gated at the layout level bysubscription_tier; ineligible users redirect to/from-living-word/educator. - The dashboard shows: the educator's quizzes list (sorted most-recent-first; status badges visible: Draft / Submitted / Auto-approved / Editorial review / Rejected / Published / Retired), a
+ New quizprimary CTA, and a small "Authoring tips" link to documentation. - Empty state: the dashboard shows a one-screen onboarding card explaining the 60-second flow + a single
+ Create your first quizbutton.
The authoring modal
Clicking + New quiz opens a modal (full-screen on mobile, centered on desktop). The modal is structured as six sequential steps, all visible on one screen — there is NO multi-step wizard; the educator scrolls top-to-bottom.
- Step 1 — Scene. Dropdown of all canonical scenes (Wave-1 through Wave-7), grouped by Act (I-V). Default selection: the most-recently-played scene by the author's students (if class-mode wired); otherwise alphabetical first scene.
- Step 2 — Lens. Dropdown of the 17 traditions. Default selection: the author's
tradition_emphasisfrom their profile (set during onboarding); if unset, the dropdown shows a "Pick a tradition" placeholder and the form cannot save until selected. - Step 3 — Difficulty + age rating. Two radio groups side-by-side. Difficulty defaults to
intermediate; age-rating defaults to13-17. - Step 4 — Question text. Textarea, ≤200 chars, with a live character counter. Live feedback (see "Live feedback during authoring" below) fires on this field.
- Step 5 — Four options + correct answer. Four textareas, each ≤150 chars, each with a radio button on the left to mark it as the correct answer. Exactly one radio must be selected.
- Step 6 — Explanation. Textarea, ≤300 chars. This text is shown to the player after they answer the question (both correct and incorrect responses). Live feedback fires on this field.
Save actions
The modal has three actions in the footer:
- Save as draft. Stores the quiz at
status='draft'. The educator can return to it later from the dashboard. - Submit for review. Runs auto-checks synchronously. If all pass: transitions to
auto_approvedand (forvisibility='private') immediately publishes to the author's class. If any fail: transitions torejectedand surfaces the specific violation list to the educator. The educator can edit and resubmit. - Cancel. Discards unsaved changes.
Live feedback during authoring
Four feedback signals fire as the educator types (debounced 400ms after last keystroke):
- Lens-vocabulary linter. Highlights words or phrases that don't fit the picked tradition's vocabulary. Example: with
lens_id=Reformedselected, typing "free will" in the question or explanation triggers a yellow underline + a tooltip suggesting tradition-faithful phrasing (e.g., "moral responsibility under God's sovereign decree"). Suggestions are non-blocking — the educator can choose to ship the original wording. The linter pulls vocabulary profiles from the same source the chatbot uses (src/lib/theolenses.ts). - AI Bridge audit (live). Runs the existing AI Bridge banned-phrase patterns against question + explanation. Any match (e.g., "this stays between us", "I'll keep this private", any clergy-penitent framing) hard-blocks save with a red error band. The educator MUST rephrase before submission. This audit is the same logic in
voice-agent-livekit/moderation.pyandsrc/app/api/chatbot/stream/route.ts, ported to a client-callable function insrc/lib/living-word/ugc-quiz-moderation.ts. - Scripture-anchor validator. If the question or explanation contains a scripture reference (regex:
\b(1|2|3)?\s?[A-Z][a-z]+\s+\d+(:\d+(-\d+)?)?\bthen narrowed to known canonical book names), the validator checks the reference against the canonical scripture corpus. Invalid references (e.g., "Hezekiah 7:14") produce a yellow underline + a "Did you mean…" suggestion. Out-of-range chapter or verse numbers produce the same. Validator is non-blocking but strongly visible. - Reading-level meter. Computes a Flesch reading-ease score on the question + options + explanation combined. For the
13-17age rating, the target band is Flesch 60-70. For8-12, target 70-80. For18+, no target. The meter shows a small color-coded chip ("Easy reading", "On target", "Too dense for age 13-17") — informational only, never blocking.
Bulk import (Phase 1B+)
CSV bulk import is OPTIONAL in Phase 1A and REQUIRED by Phase 1B. The CSV schema:
scene_id,lens_id,difficulty,age_rating,question,option_0,option_1,option_2,option_3,correct_index,explanation
Importing 10 well-formed rows MUST complete in ≤5 minutes, including the per-row auto-check pass. Failed rows surface in a downloadable error CSV with the original row + the violation reason in an extra column.
§6 — Moderation pipeline
The pipeline is a state machine. Each visibility tier traverses a distinct subset of states. All transitions are recorded in lw_ugc_quiz_audit_log.
Auto-checks (run on every submission, all visibility tiers)
Split architecture — synchronous regex pass + async semantic pass. Founder decision 2026-05-24: regex-based checks run synchronously inside the submit handler (<2s budget, no network calls); semantic checks run asynchronously after the regex pass with a "checking…" state visible to the educator (target completion 5-10s, max 30s).
Synchronous regex pass (≤2s, blocking submission):
- AI Bridge audit (regex). Pass = zero matches against the banned-phrase regex bank (
_BANNED_CONFIDENTIALITY_PHRASESinvoice-agent-livekit/moderation.py, ported to a client-callable function). - 13-rule UGC hard floor regex bank (§7). Pass = zero hard-blocks across rules a-m at the regex layer. Rules g + h (sexualizing or violence against minors; physical punishment advocacy) hard-block-no-review at this layer. Other regex-detectable patterns flag for the async pass.
- Structural validation. Question text + 4 options + correct-index + lens + difficulty + age-rating all present and within length limits. Malformed = reject immediately.
Async semantic pass (5-10s typical, 30s max, runs post-submit with status='checking'):
- Lens-vocabulary semantic linter. Pass = no critical-severity vocabulary mismatches against the declared lens. Uses the LLM judge running against
src/lib/theolenses.tsprofiles. Informational suggestions during authoring do NOT block; only critical-severity contradictions detected by the semantic pass move status torejected. - Scripture-anchor validator. Pass = every detected scripture reference resolves to a canonical book + valid chapter + valid verse range. Made-up book names, out-of-range chapters, or out-of-range verses fail. Network-bound (reads the scripture corpus), hence async.
- 13-rule UGC hard floor semantic pass (§7). Pass = zero semantic-level hard-blocks across rules a-m for patterns regex can't catch (e.g., rule i — relationship advice harmful in abusive contexts — needs semantic understanding, not regex).
UX states:
- Submit clicked → sync pass runs → if any sync check fails, status →
rejectedimmediately, educator sees specific violation list, can edit + resubmit. If sync pass succeeds → status →checkingand educator sees a "Running theological + scripture checks…" indicator with a progress affordance. - Async pass completes → status →
auto_approved(all passed) ORrejectedwith violation list. Forvisibility='private',auto_approvedimmediately transitions topublished. Forschool_pool/public, transitions to the appropriate human-review queue. - If async pass times out at 30s, status →
rejectedwithrejection_reason='check_timeout'. The educator can retry. (Phase 2 may add a "publish-with-warning" fallback; Phase 1 errs on the side of strict.)
Why this split (founder rationale, 2026-05-24). Synchronous-only would either force network-bound semantic checks under an unrealistic 2s budget OR remove them entirely from the live pipeline. The split keeps the educator's authoring UX fast (regex is genuinely <100ms), preserves strong guarantees (every semantic check still runs before publish), and doesn't overpromise an SLA the platform can't keep when LLM latency spikes. The "checking…" state is honest: the educator sees the system working, knows publication is pending, and trusts the eventual outcome.
CSV bulk import (Phase 1B+): Per-row sync pass runs as part of upload; rows that fail sync are written to error.csv immediately. Rows that pass sync enter a batch async queue with a 10-minute total budget for the whole CSV (not per-row). Status of each row is reflected in the import results view.
Private visibility (Phase 1A)
Pipeline: Submit → Auto-checks → auto_approved → published (immediate).
No human review. The author's content is live for their class within seconds. The author is fully responsible for content quality at this tier.
School-pool visibility (Phase 1B)
Pipeline: Submit → Auto-checks → auto_approved → editorial_review (school-admin queue) → school admin approves → published.
Step 2 (school-admin review) is gated on the existence of a school admin (lw_ugc_schools.headmaster_user_id or lw_ugc_authors.school_role IN ('principal','headmaster','admin_staff')). The school admin reviews the quiz in /living-word/educator/school-review, sees the full rendered question + options + explanation + author + lens + difficulty + age-rating + auto-check results, and either approves (advances to published) or rejects with notes (returns to rejected).
Public-library visibility (Phase 1C)
Pipeline: Submit → Auto-checks → auto_approved → editorial_review (Living Word editorial queue) → editorial reviewer approves → published.
The editorial reviewer is the founder for months 1-3 (Decision 1) and the founder + part-time contractor from month 6 onward (Decision 1, second leg).
Credentialed-seminary fast-track (Decision 2). For authors with is_verified_seminary=true:
- Their first 5 public-library publish attempts go through the standard editorial review.
- After the editorial reviewer confirms quality on the first 5, the reviewer toggles a "trusted author" flag on
lw_ugc_authors(column to be named — proposal:editorial_trust_level INT DEFAULT 0, set to 1 after 5 approved publishes). - Subsequent public-library publishes from trusted authors enter a spot-check queue rather than mandatory review (e.g., 20% of submissions are reviewed; the rest auto-publish on auto-check pass).
- Spot-check rate is configurable per author by the editorial reviewer.
Non-credentialed demonstration path (Decision 2). Authors without seminary verification reach public-library eligibility by:
- 5 well-rated school-pool publishes (each rated 4+ stars by school-admin or peer educator) AND
- ≥1 peer educator endorsement (a one-line attestation from another author at any school).
When these conditions are met, the editorial reviewer is notified to advance the author to public-library-eligible. The author cannot self-promote.
SLA targets
| Pipeline stage | Target SLA | Max SLA |
|---|---|---|
| Auto-check sync regex pass | <2 seconds | <5 seconds |
| Auto-check async semantic pass | 5-10 seconds | 30 seconds (hard timeout) |
| School-admin review (Phase 1B) | 48 hours | 7 days |
| Editorial review — public library (Phase 1C, months 1-3, founder solo) | 5 business days | 14 days |
| Editorial review — public library (Phase 1C, months 6+, with contractor) | 48-72 hours | 7 days |
SLA breaches trigger a row in founder_action_items so the founder sees the queue depth and can re-balance staffing. SLA enforcement is operational, not legal.
§7 — The 13-rule UGC "we will never" hard floor (Decision 5)
All 13 rules are enforced as a hard floor at the auto-check layer. Engineering MUST NOT ship Tier 1 with any rule omitted, weakened, or made advisory-only. The lens-vocabulary linter + AI Bridge audit script (scripts/living-word/audit-ai-bridge.mjs) are extended with a new exported pattern bank: UGC_HARD_FLOOR_PATTERNS.
Rule severity:
- HARD BLOCK (no review path). Match = quiz auto-rejected, author shown the violation, no editorial override possible. Rules g and h are hard-block-no-review.
- FLAG FOR REVIEW. Match = quiz held in
editorial_reviewwith the specific rule flagged; reviewer decides ship/reject. All other rules.
| # | Rule (verbatim) | Enforcement mechanism | Severity |
|---|---|---|---|
| a | No real-name targeting of named contemporary religious figures (no "Pastor John MacArthur is wrong about X" NPCs) | Regex match against a configurable list of well-known contemporary clergy + theologian names (initial list maintained in src/lib/living-word/ugc-named-figures.ts). Match in question or explanation flags. | Flag |
| b | No political party endorsement or partisan candidate naming | Regex match against US/Canada party names (Republican, Democrat, Conservative, Liberal, NDP, Bloc, Green) + currently-elected federal candidates' surnames, plus partisan-ideology terms (MAGA, woke-as-pejorative, etc.). Match flags. | Flag |
| c | No salvific-status pronouncements about named individuals or groups ("Catholics aren't saved" / "Evangelicals aren't saved") | Semantic check: regex for tradition-name + ("aren't saved" | "are not saved" | "go to hell" | "aren't real Christians" | "false church" | similar). Lens-vocabulary linter cross-references the declared lens's stance on inter-tradition language. Match flags. | Flag |
| d | No nation-state or ethnic group condemnations | Regex match against nationality + ethnicity terms + condemnation verbs ("hate", "destroy", "wicked", "cursed", "judged"). Match flags. | Flag |
| e | No predictive end-times date-setting | Regex match: any year 2025-2100 within 50 characters of eschatological keywords (rapture, second coming, end times, tribulation, antichrist). Match flags. | Flag |
| f | No medical, financial, legal, or psychiatric advice from any NPC or quiz | Regex match against professional-advice phrasings ("you should take", "stop taking your medication", "invest in", "sue", "diagnose", etc.). Match flags. | Flag |
| g | No content sexualizing minors or depicting violence against minors | Regex match against sexual terminology within 30 characters of age terms <18 OR words like "child", "minor", "youth", "kid", "teen". Hard block — no review path; auto-reject + author suspended pending review. | HARD BLOCK |
| h | No content advocating physical punishment of children | Regex match against physical-discipline verbs ("spank", "hit", "strike", "beat", "whip", "rod") within 30 characters of child terms. Hard block — no review path; auto-reject + author suspended pending review. | HARD BLOCK |
| i | No marriage-counseling or relationship-advice scripts that would be harmful if a player is in an abusive situation | Semantic check against patterns like "submit to your husband", "stay in the marriage no matter what", "forgive and forget abuse", "marriage is forever even if". Match flags. | Flag |
| j | No fundraising appeals, MLM pitches, or solicitation of any kind from in-game content | Regex match against solicitation patterns: URLs, "donate", "buy my book", "support my ministry", "give now", "Venmo", "PayPal", "Patreon", phone numbers in a CTA context. Match flags. | Flag |
| k | No firearm advocacy or anti-firearm political content | Regex match against firearm terms (gun, rifle, AR-15, Second Amendment, NRA, gun control) within a political-stance context (rights, ban, take away). Match flags. Pure narrative reference to a biblical weapon (e.g., David's sling, Goliath's sword) is exempt — these are within scripture's own vocabulary. | Flag |
| l | No content disparaging mental health treatment, medication, or therapy | Regex + semantic check against patterns disparaging therapy ("therapy is worldly", "psychiatry is demonic", "just pray about it instead of medication", "Christians shouldn't need a therapist"). Match flags. The platform's pastoral stance per AI Bridge is that mental health care IS the right human bridge for those questions — Christian traditions vary, but DISPARAGING the care is the line. | Flag |
| m | No content advocating specific dietary religious laws as binding on Christians (kosher, halal, vegetarianism-as-doctrine) | Nuanced check. The platform permits content describing or recommending Lenten fasting, Eastern Orthodox fasting practices, and other spiritual disciplines historically practiced by various Christian traditions — these are spiritual disciplines, not binding-on-all-Christians laws. The platform flags content using "must", "required", "binding", "necessary for salvation", "all Christians should keep" within 50 characters of dietary terms (kosher, halal, vegetarian-as-spiritual-requirement, no-pork, no-meat). | Flag |
Extension hook. The pattern bank lives in src/lib/living-word/ugc-quiz-moderation.ts exporting UGC_HARD_FLOOR_PATTERNS. The same module is consumed by the live authoring linter, the submit handler's auto-check, and the audit script's CI gate. Engineering MUST keep the three call sites in sync; a CI test asserts the three import the same constant.
Updating the patterns. Pattern additions or removals require a founder sign-off and a row in knowledge/decisions/ recording the change rationale. The 13 rules themselves are founder-locked (Decision 5) and cannot be removed without re-opening that decision.
§8 — Player-facing impact
The in-game player experience MUST remain primarily authored — the canonical quiz pool is the spine, UGC is depth and personalization. Concrete rules:
-
Pool composition at scene load. The scene's quiz pool is constructed as the union of:
- Canonical authored quizzes (the existing
questionsByDifficultyarrays in the scene data file), filtered by the player's active difficulty. - Private UGC quizzes whose class assignment includes the player's class (Phase 1A+).
- School-pool UGC quizzes whose author's
school_idmatches the player's class'sschool_id(Phase 1B+). - Public-library UGC quizzes (Phase 1C+), only if the player's settings have
ugc_public_optin = true.
- Canonical authored quizzes (the existing
-
Pool weighting. The in-game quiz selector targets approximately 40% UGC frequency for classes that have UGC available. This is a soft target — pool size variance means realized frequency may range 25-55%. The selector MUST NOT surface the same UGC quiz twice in the same scene-run.
-
Attribution badge. Every UGC quiz card shows a small attribution chip beneath the question:
- Private: "from your teacher [display_name]"
- School-pool: "shared by [school_name]"
- Public-library: "from the Living Word library — [display_name]"
Badges are styled subtly (cream background, stone-500 text); they MUST NOT compete with the question for visual attention. No icons larger than 16px. No author photos.
-
Cross-class privacy invariant. A student MUST NEVER see another class's private quizzes. This is enforced at two layers: RLS on
lw_ugc_quizzesrejects the query; the in-game pool selector filters defensively as well. Both layers MUST be tested (see §12). -
Opt-out for public UGC. Players can opt out of public-library UGC in their settings panel (proposal:
/living-word/settings— a toggleShow community-authored quizzes from the public library). Default is OFF for players under 13 (COPPA-conservative); default is ON for players 13+. Pre-existing players retain their current state. -
Reporting. Each in-game UGC quiz card includes a small "Report this question" link in its footer. Reports route to the editorial review queue. The report taxonomy:
inaccurate,inappropriate,off-topic,violates-13-rule-floor,other. Report volume is tracked per quiz; ≥3 reports flag the quiz for editorial review regardless of current status.
§9 — Anti-goals (load-bearing — what UGC Phase 1 will NOT do)
Engineering MUST refuse to implement any of the following inside Phase 1's scope. Each is deferred to a later phase or rejected outright. The anti-goals are load-bearing because the wrong "small adds" are what turn safe UGC platforms into Roblox-shape liability.
- Will NOT allow content authoring from non-paying users. The pricing gate in §3 is enforced at the route level AND the API level. No
if user.is_pastor_friend_of_foundercarve-outs. - Will NOT allow public-library publish before month 6+. The founder is the sole editorial reviewer for months 1-3; public-library publish opens after the contractor is hired. The
visibility='public'value is rejected by the submit handler until a feature flag is enabled (config-driven, founder-toggled). - Will NOT auto-publish anything that fails any of the four auto-checks. There is no override path until the contractor exists. The author edits and resubmits; that is the only path forward.
- Will NOT allow students to author content. The
/living-word/educatorroute is gated to authors only. Student accounts cannot reach it. Tier 4 student-authored content is a year-2+ decision and is not in Phase 1. - Will NOT allow cross-school sharing without explicit license. Tier 4 marketplace is years out. Phase 1's school-pool visibility is scoped to a single school's members; no inter-school visibility exists.
- Will NOT integrate with third-party LMSes in Phase 1. Canvas, Google Classroom, Schoology, Blackboard — all deferred to Phase 2. Authoring + class assignment is in-platform only.
- Will NOT allow educator content to override the AI Bridge frame in NPC chat. UGC quizzes are quiz items only; they do not author NPC dialogue. Tier 2 (educator-authored NPCs) is where the AI Bridge frame inheritance becomes an architectural concern, and Tier 2 is not in Phase 1.
- Will NOT allow versioning or branching of published quizzes. Published is immutable. Edits create a new quiz; the old one can be retired. This keeps the audit trail clean and reproducible.
- Will NOT collect PII from minors via the authoring path. Educators are 18+ adults (subscription tier requires it). Players' interaction with UGC quizzes is read-only and writes only existing aggregate analytics; no new PII collection from players in Phase 1.
- Will NOT let any single educator publish more than 50 quizzes per scene. A soft cap, enforced at the submit handler. Above 50, the educator must consolidate or retire older quizzes. This prevents pool dilution.
§10 — Migration plan (phasing within Phase 1)
Three sub-phases. Each has a fixed scope, an engineering-effort estimate, a founder validation step, and a green-light gate before the next sub-phase begins.
Phase 1A — Private-only visibility (months 1-3)
Scope shipped.
lw_ugc_quizzes,lw_ugc_authors,lw_ugc_schools,lw_ugc_quiz_audit_log, stublw_ugc_quiz_class_assignments(with stublw_classrooms)./living-word/educatordashboard + authoring modal ++ New quizflow.- Synchronous auto-checks (all four).
- 13-rule hard floor enforced.
- In-game pool merge for private-scope quizzes.
- Author attribution badge.
- Live linter, AI Bridge live audit, scripture validator, reading-level meter.
- Subscription tier gate.
- "Report this question" reporting flow.
- Founder-only escalation queue at
/founder/[token]/ugc-review.
Engineering effort. ~3 weeks single-engineer (or ~1.5 weeks two-engineer).
Founder validates.
- Real educators (≥10 paid Tier-1-eligible accounts) author at least one quiz each within 30 days of launch.
- The four auto-checks catch the right things — false-positive rate <15%, false-negative rate <10% on a seeded test set.
- The lens-vocabulary linter feels useful, not annoying — founder reviews 20 author sessions and the feedback log.
Green-light gate to Phase 1B. ≥30 active authors AND ≥10 quizzes/author median AND the founder's manual review of the first 10 escalations confirms the moderation pipeline is catching what it should catch.
Kill signal. <10 active authors at week 8 = pause Phase 1B; revisit positioning, pricing, or onboarding.
Phase 1B — School-pool visibility (months 4-6)
Prerequisite. The living-word-classroom-rosters.md acceptance spec must land first. Without it, school-pool visibility has no working class scope.
Scope shipped.
- Real
lw_classroomsschema (per the rosters spec). - School registration flow (school admin can register their school + invite teachers).
- School-admin review queue at
/living-word/educator/school-review. - School-pool visibility promotion (author submits with
visibility='school_pool'; routed to school-admin review). - Bulk CSV import (now mandatory).
Engineering effort. ~2 weeks single-engineer assuming rosters spec is also ~1 week.
Founder validates. School-admin sign-off cadence works (school admins review within 72h SLA on average); ≥3 partner schools have at least one teacher publishing to school-pool.
Green-light gate to Phase 1C. ≥3 partner schools onboarded AND school-admin SLA <72h achieved AND no inter-class privacy violations detected in audit log review.
Phase 1C — Public-library publish (months 6-9)
Prerequisite. Contractor hired (Decision 1 transition).
Scope shipped.
visibility='public'value enabled.- Editorial reviewer role + reviewer queue UI (founder + contractor share the queue).
- Credentialed-seminary fast-track (Decision 2 path b).
- Non-credentialed demonstration path (Decision 2 path a).
- Public-library browse surface for players (with opt-out per §8).
- "Trusted author" spot-check rate configuration.
Engineering effort. ~3 weeks (queue UI + browse surface + opt-out flow + spot-check sampler).
Founder validates. Editorial SLA holds (48-72h with contractor); first 25 public-library quizzes drive measurable cross-school adoption (≥10% of classes using at least one public quiz).
§11 — Success metrics + kill/scale thresholds
Phase 1A KPIs
| Metric | Definition | Scale threshold | Kill threshold |
|---|---|---|---|
| Active authors | Distinct authors who have published ≥1 quiz in the trailing 30 days | ≥30 by week 12 → green-light 1B | <10 by week 8 → pause + diagnose |
| Median quizzes per author | Median quizzes published per active author across a quarter | ≥10 → green-light 1B | <3 → re-examine authoring UX |
| Auto-check false-positive rate | % of submissions auto-rejected that the founder later overrides as actually fine | <15% | >25% = relax the over-eager pattern |
| Auto-check false-negative rate | % of submissions auto-approved that the founder later catches as violating | <10% | >20% = tighten the patterns |
| Class-level engagement lift | 14-day return-rate of students in classes with UGC vs. canonical-only | +25% | <+5% = the personalization isn't earning its complexity |
Phase 1B KPIs
| Metric | Definition | Scale threshold |
|---|---|---|
| Partner schools onboarded | Schools with ≥1 active teacher publishing to school-pool | ≥3 by month 5 |
| Promotion rate | % of private quizzes whose authors choose to promote to school-pool | ≥20% |
| School-admin review SLA | Median time from submit to school-admin decision | ≤48h (target), ≤7 days (max) |
| Per-school engagement lift | Multi-class engagement lift attributable to school-pool sharing | +15% over private-only |
Phase 1C KPIs
| Metric | Definition | Scale threshold |
|---|---|---|
| Public-library quizzes per quarter | New public-library quizzes published per quarter | ≥50 by month 9 |
| Cross-school adoption | % of classes that use ≥1 quiz from outside their school | ≥10% by month 9 |
| Editorial SLA hit rate | % of public-library reviews resolved within 72h | ≥90% |
| Zero brand-harm incidents | Number of public-library quizzes pulled for cause after publish | 0 |
Any non-zero brand-harm incident triggers a pause-and-investigate protocol. The investigation produces a post-mortem in knowledge/decisions/ and a corrective pattern-bank update in src/lib/living-word/ugc-quiz-moderation.ts.
§12 — Testing plan
Engineering uses this section as the test build sheet. Every test below MUST exist before Phase 1A ships.
Integration tests (Vitest, server-side)
- Educator authors a quiz → quiz appears in their class's pool → quiz does NOT appear in another class's pool. Seeds two teachers, two classes, one quiz authored by teacher A; asserts visibility from a student in class A and absence from a student in class B.
- Banned-phrase submission is auto-rejected. Each of the 13 hard-floor rules has both a positive test (content that violates the rule + asserts rejection with the specific rule name in
rejection_reason) and a negative test (content that is near the rule's boundary but does NOT violate + asserts auto-approval). 26 tests minimum. - Lens-vocabulary linter fires per declared lens. A Reformed author writing "free will" in the explanation triggers the suggestion; an Arminian author writing "free will" does not. At minimum, 4 representative traditions tested (Reformed, Arminian, Catholic, Anabaptist).
- Visibility promotion requires the right approver. Promoting a quiz from
privatetoschool_poolrequires a school-admin sign-off; an attempt by the author to self-promote is rejected at the API layer. - Scripture-anchor validator rejects invalid references. "Hezekiah 7:14" (book doesn't exist) and "John 25:1" (chapter out of range) both fail; "John 3:16" passes.
- Pricing gate at API. A user on the $4.95/mo personal-premium tier cannot POST to
/api/living-word/ugc/quizzes; the API returns 403 with a specific error. A user on $79/yr can. - Hard-block rules trigger author suspension. Submitting content that matches rule g or h auto-rejects the quiz AND sets
suspended_at+suspended_reasonon the author. - CSV bulk import correctly handles partial failures. 10 rows submitted, 7 pass auto-checks, 3 fail; response includes the 3 failed rows + reasons; the 7 successful rows are published.
- RLS cross-class read denial. A direct Postgres query as a student in class B for a quiz authored privately by class A's teacher returns zero rows.
- Audit log completeness. Every state transition on every quiz produces an audit row with non-null
action,occurred_at, anddetails.
E2E test (Playwright)
- Educator creates a quiz in ≤60 seconds. A test account with the $79/yr tier signs in, navigates to
/living-word/educator, clicks+ New quiz, completes the six steps, hits Submit, and lands at "Auto-approved — live in your class." Total elapsed wall time ≤60 seconds on a CI machine with seeded form defaults. This test is the canonical performance assertion for the authoring UX.
Manual founder review (no test framework)
- The founder personally reviews the first 50 author sessions in Phase 1A using the audit log + session replays (if available). Findings go into
knowledge/decisions/and drive the first pattern-bank tuning pass.
§13 — Open questions deferred to Phase 2
Engineering MUST NOT try to solve any of these inside Phase 1. They are listed here so the spec is honest about its boundaries.
- LMS integration. Canvas, Google Classroom, Schoology, Blackboard — Phase 2.
- CSV bulk import polish (round-trip export-then-edit-then-reimport). Phase 1B+ for basic import; round-trip is Phase 2.
- Student-authored content. Tier 4a (quizzes) and 4b (NPC bibles) — year 2+, requires separate spec including COPPA flow.
- Marketplace pricing + revenue split. Tier 4 — year 2+.
- Multi-language authoring (Spanish, French, Portuguese, Korean). Phase 3+. English only in Phase 1.
- Versioning / forking of published quizzes. Phase 2+.
- Real-time co-authoring (Notion-style multi-cursor on a single draft). Phase 2+ if author demand surfaces.
- AI-assisted quiz drafting (educator types a paragraph, Claude drafts a 5-quiz set). Possible Phase 2 add; explicitly not in Phase 1 to avoid the question "is the AI the author or is the educator?"
- Per-region availability constraints on public-library quizzes (e.g., scenes touching Israel-Palestine sensitivity). Phase 2+ when public library has enough volume to need geographical scoping.
Definition of done for this spec. Engineering reads this end-to-end. The Phase 1A migration is authored against §4 verbatim. The authoring UX is built against §5. The 13-rule pattern bank is implemented against §7 with no rule omitted. The first 11 tests in §12 exist and pass before Phase 1A ships. The founder approves the §10 milestones and is the editorial reviewer for the first 90 days. Every promotion from one sub-phase to the next requires the green-light gate to be met.