chore(sc-46274): retire the legacy fallback, park the experiments framework (phase 3) - #3579
Open
dcschreiber wants to merge 19 commits into
Open
chore(sc-46274): retire the legacy fallback, park the experiments framework (phase 3)#3579dcschreiber wants to merge 19 commits into
dcschreiber wants to merge 19 commits into
Conversation
This was referenced Aug 4, 2026
📊 Code Quality Score: 33/100
Was this score accurate? 👍 Yes · 👎 No Scored by GitVelocity · How are scores calculated? |
…ramework The framework itself (model, admin tooling, opt-in endpoint) stays in the codebase, parked for a future experiment — but the assistant no longer reads it: the legacy fallback for a missing settings key is gone (absent now reads as off, pending a catch-up migration run), and the in_chatbot_experiment prop went with it. TEMPORARY markers on the surviving plumbing become 'parked experiments framework' notes, since the code is staying. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The banner promotes the product, not the retired experiment. The dismissal storage key keeps its original name so nobody's dismissal history resets. The show condition now keys on chatbot_enabled: with in_chatbot_experiment gone, the banner shows whenever the assistant isn't running for the viewer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 4 of the opt-out rollout: the framework code stays parked, but the data is not retained. Archives every UserExperimentSettings row to db.experiments_data_archive, deletes the rows, and unsets the Mongo profiles.experiments field. Refuses to run before the migration has given every profile its own setting key, since the migration's cohorts come from the rows this deletes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The banner's logged-in branch offered a "Try It" button gated on the assistant being off. Once every profile carries an explicit setting, the only logged-in users it reaches are the ones who deliberately turned the assistant off — so it asked precisely the people who had said no, immediately, on the settings page where they said it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dcschreiber
force-pushed
the
chore/sc-46274/phase-3-remove-legacy-fallback
branch
from
August 6, 2026 05:13
ddd47d6 to
6e1da35
Compare
dcschreiber
changed the base branch from
chore/sc-46272/phase-1-library-assistant-setting
to
master
August 6, 2026 05:13
dcschreiber
marked this pull request as draft
August 6, 2026 10:01
dcschreiber
marked this pull request as ready for review
August 6, 2026 10:01
A bare `python scripts/migrations/wipe_experiments_data.py` cannot import sefaria or find DJANGO_SETTINGS_MODULE, so the documented usage never worked. Match the two sibling migration scripts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d db The missing-key guard returned before the counts were printed, so --dry-run could not rehearse the wipe anywhere the backfill had yet to run — the one place a rehearsal is worth having. A dry run writes nothing either way, so it now downgrades the abort to a warning and goes on to report; a real run still refuses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reworded comments left their trailing `#}` off the column the rest of the template keeps them on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The script promises that every deleted row is archived, but it archived the rows it had read and then deleted the whole table, so a row written between the two would go unarchived. Delete the archived ids instead, in batches like the sibling migration; a straggler survives for a re-run, which archives and deletes it in turn. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restores the behavior the story asks for. The gate had been narrowed to logged-out visitors only, which was a product change beyond this ticket's scope: it made the banner's logged-in branch — the "Try it" button that turns the assistant on in place — unreachable, and left the preserved `chatbot_experiment_banner_dismissed` storage key with nothing to store. `chatbot_enabled` is the server-side signal for "the assistant is running for this viewer": it defaults to False and is set True only for an authenticated user whose `library_assistant` setting is on. Negating it covers logged-out visitors and logged-in users who have it off, which is what the retired `Sefaria.in_chatbot_experiment` check approximated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rollback window closes when this phase deploys; there is no post-Phase-3 undo. The script was already inert — with the legacy fallback gone, unsetting the setting key would turn the assistant off for everyone it touched rather than restore what each user had, so it refused to run at all. The `library_assistant_migration_archive` collection stays: it is the historical record of what the flip wrote, independent of any way to undo it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ors only Product ruled that no logged-in user should see the promo. The banner was gated on the assistant being off, which after the opt-out migration means it reached exactly the users who had deliberately turned it off — asking them to reverse the one choice they made about it. Gate the banner on the viewer being logged out, and delete the logged-in branch it made unreachable: the "Try It" button, the join handler that POSTed the setting and reloaded, its pending state, and the per-audience cookie ternary. The logged-out key `signup_promo_banner_dismissed` is now the only one; with no logged-in viewers the old `chatbot_experiment_banner_dismissed` key is never read or written again, and stale entries on clients simply age out. The `site_wide_banner.try_it` entry stays in the interface JSON: those files are Weblate's, not ours to prune by hand. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A standalone docs/decisions file was more ceremony than the decision needs. The substance — library_assistant is the product, chatbot is the vendor/wire name whose contracts a rename would break — now lives in the docstring of the module every reader of the setting already goes through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
migrate_experiments_to_library_assistant.py has run to completion on production twice (launch + catch-up); it is spent, and keeping it around invites running it out of order. Its straggler-backfill capability moves into wipe_experiments_data.py, which needed it anyway: the wipe deletes the very UserExperimentSettings rows the backfill reads its cohorts from, so a profile missing settings.library_assistant at wipe time would lose its opt-out forever. The wipe is now self-contained: backfill any profile lacking the key (same cohort rules and write path as the migration, archived to the same migration archive) -> verify none remain, aborting before any archive/delete if one does -> archive -> delete -> unset. --force is removed: its only purpose was to bypass the refuse-guard when profiles lacked the key, which the built-in backfill now handles. After the backfill, a profile still missing the key means a write failed, and proceeding would destroy the only record of a deliberate opt-out — there is no legitimate reason to override that, so no flag offers to. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comments now describe the code as it stands — what the key means, who the promo is for — rather than narrating the migration that got it here. The history lives in git and in the wipe script, whose subject it is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ivergence The archive now holds the full pre-wipe record of both stores' meaningful values, with a `source` of "row" or "profile" distinguishing them. False flags stay unarchived: they are the serialized-on-save default and record nothing a user chose. The docstring's claim that the Mongo Trues mirror the archived rows is replaced by a measurement — the run counts True profiles with no row behind them, type-exactly, and prints it without blocking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ript A run pointed at the wrong database prints numbers that look perfectly consistent, so both modes now open by naming the resolved Mongo and Postgres databases. A real run closes by re-reading both stores rather than trusting its own counters, reporting PASS or WARN for the remaining rows, the profiles still missing the setting, the profile documents still carrying the field, and the archive entry count for the run. Each WARN carries the operator instruction that goes with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-3-remove-legacy-fallback # Conflicts: # reader/views.py # static/js/ReaderApp.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
(Claude writing on Daniel's behalf)
Phase 3 of the Library Assistant opt-out rollout. The opt-out migration ran on production on Aug 10, so every profile now carries an explicit
settings.library_assistant; this PR removes the legacy fallback that covered profiles without it. Replaces #3573 — per Akiva's review, the experiments framework (model, admin tooling, opt-in endpoint) stays in the codebase, parked for future experiments. Only the Library Assistant stops reading it.A review found that the invariant this PR rests on was false on master: SSO signups and lazily-created profiles never got
settings.library_assistant. Removing the fallback would have turned the assistant off permanently for every new Google/Apple account. This was proven with a real SSO signup against the local databases, and re-proven fixed the same way after the fix. Details and evidence are in #3638.In order:
What changes
A profile without the setting key now reads as off.
sefaria.helper.library_assistantno longer falls back to the experiments whitelist, and thein_chatbot_experimentprop is gone.The one-time migration script is deleted (
migrate_experiments_to_library_assistant.py) — it ran on production Aug 10 and is spent. Its catch-up capability moves into the wipe script, so a straggler profile can't strand Phase 4.The promo banner shows to logged-out visitors only (product decision, 2026-08-11). Without this, removing the fallback would have aimed the "Try It" banner at exactly the logged-in users who had just turned the assistant off. The gate is now
!Sefaria._uid, and the logged-in branch it makes unreachable — the Try It button, its join handler, and thechatbot_experiment_banner_dismissedcookie — is deleted.Rename:
ChatbotExperimentBanner→LibraryAssistantPromoBanner. Thesefaria/helper/library_assistant.pydocstring records where thechatbotvslibrary_assistantnaming line runs (vendor/wire contracts keep thechatbotname).Phase 4 wipe script (
scripts/migrations/wipe_experiments_data.py), designed as a one-run tool —--dry-run, then the real run, with the launch-day checks baked in rather than run by hand around it:UserExperimentSettingsrow and everyexperiments: Trueprofile go todb.experiments_data_archive, taggedsource: "row"/"profile"(False values are the serialized-on-save default and record nothing a user chose). It also reports how many True profiles have no row behind them — deleted accounts are the one known source — instead of assuming the stores mirror;profiles.experiments;--dry-runperforms zero writes;--forceis removed (post-backfill, a still-missing key means a failed write, and overriding would destroy the only record of an opt-out). Phase 4 is that one script run — no schema change, the (empty) table stays.The migration rollback script is deleted. Its guard made it inert the moment this PR merges. The archive collection remains as the raw record for manual repair, to be dropped after a soak window (~end of August).
The TEMPORARY markers on the surviving experiments plumbing are reworded as "parked framework" notes, since that code is no longer scheduled for removal.
Verification
settings.library_assistant: False; then an idempotent re-run (full no-op), a late-row catch-up (archived and deleted under a new run_id), and a straggler backfill. All checks and all in-script PASS/WARN lines came out PASS.--dry-runseparately confirmed write-free (before/after snapshots identical).Merges independently of #3585, in either order.
🤖 Generated with Claude Code