Skip to content

fix(library-assistant): write the settings key when a profile is created - #3638

Open
dcschreiber wants to merge 6 commits into
masterfrom
fix/sc-46274/signup-paths-library-assistant-key
Open

fix(library-assistant): write the settings key when a profile is created#3638
dcschreiber wants to merge 6 commits into
masterfrom
fix/sc-46274/signup-paths-library-assistant-key

Conversation

@dcschreiber

@dcschreiber dcschreiber commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

(Claude writing on Daniel's behalf)

New SSO accounts never get settings.library_assistant. Email registration wrote the key at signup, but the SSO adapter (#3511) and the lazy profile-creation branch in UserProfile.__init__ did not. Those users read as assistant-off today, and Phase 3 (#3579) would make that permanent. This bug blocks Phase 3 of the Library Assistant opt-out rollout (sc-46274).

What changes

The key is now written in one place instead of once per signup path: UserProfile.__init__ sets settings.library_assistant = True when no stored profile doc exists. Every creation path passes through that point, so email registration, SSO, and lazy creation all inherit the write, and a future creation path cannot miss it. The per-path write in process_register_form became redundant and is removed.

Why not a settings default: defaults don't stay read-only here. UserProfile lays the stored doc over its defaults, and save() writes the merged result back. A default True would make every existing keyless profile read as on, and their next save would persist that as if the user chose it. Writing the key only when no doc exists yet leaves existing docs untouched: absent stays absent.

Also in the diff:

  • A test pins the new behavior: a freshly created profile carries the key.
  • Five reader tests modeled "legacy user with no key" by deleting the profile doc and letting lazy creation rebuild it keyless. They now build that premise explicitly: create the doc, then unset the key (make_keyless_profile in reader/conftest.py).
  • library_assistant.py imports the user_profile module instead of binding the class, so user_profile can import it back without a cycle.

Not in this PR: the backfill of keyless profiles created since the Aug 10 migration. That runs as an ops step before the Phase 3 deploy. The backfill rule: whitelist-row value wins, no row means on.

How we tested

We ran a real SSO signup end to end against the local databases, before and after the fix. The test drives POST /api/auth/google/mobile in-process and mocks only Google's verify_token (plus reCAPTCHA on the email control path). Everything else runs for real: allauth, the adapter, and the Mongo write.

  • Before (master): the SSO profile has no settings.library_assistant. is_enabled returns False, the settings toggle renders Off, and reader pages emit no chatbot script tag.
  • After (this branch): the key is present and true, and all three checks flip to on. Both the SSO run and the register control were re-run after consolidating the write into UserProfile.__init__; both get the key.
  • A field-by-field diff against an email-registration control shows the two Mongo profiles identical except _id, id, and slug.
  • The lazy-creation branch was exercised directly: a Django user with no Mongo doc gets a profile with library_assistant: true.
  • pytest on the sso, helper, and Library Assistant reader test files: 107/107 green.

cc @YishaiGlasner (SSO owner): no sso/ changes needed in the final shape — the adapter inherits the write from profile creation.

🤖 Generated with Claude Code

… profile creation

Email registration is the only signup path that writes
settings.library_assistant, so brand-new SSO accounts (and lazily-created
profiles for existing Django users) come out keyless and read assistant-off.
Write the key, exactly as process_register_form does, in both remaining
profile-creation paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gitvelocity-reviewer

Copy link
Copy Markdown

📊 Code Quality Score: 6/100

24 × 0.25 (Micro ESF) = 6

Category Score Factors
🔭 Scope 5/20 user_profile.py, sso/adapters.py, and sso/tests/adapters_test.py are modified. The change touches two account-creation subsystems: the base profile model and the SSO adapter.
🏗️ Architecture 3/20 A deferred from sefaria.helper import library_assistant is introduced inside UserProfile.__init__ to avoid a circular import. No new module boundary or abstraction is introduced.
⚙️ Implementation 4/20 p.settings[library_assistant.SETTING_KEY] = True is written in two locations: once in UserProfile.__init__ (with a deferred import) and once in SefariaSocialAccountAdapter.save_user. For SSO users, both paths execute, resulting in two writes of the same value.
⚠️ Risk 5/20 Every new account creation — both SSO and non-SSO — passes through the modified code. A wrong value or omitted write silently misconfigures the Library Assistant setting for all new users. The change is reversible by removing the writes; no migration is required for existing accounts.
✅ Quality 6/15 adapters_test.py is updated to assert both __setitem__ calls and a call_count of 2 for the SSO path. No test covers the UserProfile.__init__ path (non-SSO registration).
🔒 Perf / Security 1/5 No performance or security work is present. The change writes a boolean to a settings dict.

Was this score accurate? 👍 Yes · 👎 No

How this was scored →

Scored by GitVelocity · How are scores calculated?

…aceLang

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dcschreiber and others added 3 commits August 19, 2026 06:24
The helper now imports the user_profile module instead of binding the class at
import time, which breaks the cycle and lets user_profile import the helper
normally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing docs

These tests built the pre-migration 'no setting key' premise by deleting the
profile doc and relying on lazy creation to rebuild it keyless. Profile creation
now writes the key on every path, so the premise is built explicitly instead:
create the doc, then unset the key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n choke point

UserProfile.__init__ sets the key whenever no stored doc exists, so every
creation path (email registration, SSO, lazy creation) inherits it and none can
miss it. The per-path write in process_register_form becomes redundant and is
removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dcschreiber dcschreiber changed the title fix(library-assistant): write the settings key on SSO signup and lazy profile creation fix(library-assistant): write the settings key when a profile is created Aug 19, 2026
@stevekaplan123
stevekaplan123 requested a balanced review from Copilot August 26, 2026 07:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Centralizes the Library Assistant default for newly created profiles, covering email, SSO, and lazy creation paths.

Changes:

  • Initializes new profiles with Library Assistant enabled.
  • Removes the redundant registration-specific write.
  • Updates helper imports and legacy-profile tests.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
sefaria/views.py Removes registration-specific initialization.
sefaria/model/user_profile.py Initializes the setting for new profiles.
sefaria/helper/library_assistant.py Avoids circular class imports.
sefaria/helper/tests/library_assistant_test.py Updates the mocked class path.
reader/conftest.py Adds a keyless legacy-profile helper.
reader/tests/library_assistant_setting_test.py Tests creation and legacy behavior.
reader/tests/enable_library_assistant_test.py Preserves the keyless cross-site scenario.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

self.settings[library_assistant.SETTING_KEY] = True
if self.exists() and not user_registration:
# If we encounter a user that has a Django user record but not a profile document
# create a profile for them. This allows two enviornments to share a user database,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants