fix(registration): a name on the row means registered — and a SUCCESS screen says so - #686
Merged
Conversation
added 2 commits
September 6, 2026 15:53
… screen says so Two faults, one symptom. On NIETE production 7,685 users carry a first_name and only 440 carry registration_completed; 7,245 have it false (none NULL), and 4,145 of those messaged the bot in the last 30 days — 3,951 of them teachers. Keying "is she registered?" on that flag alone sends every one of them back into registration on a number where they are already registered and using lesson plans. It never showed on staging because staging is a different database whose users all registered through the current Flow. READER. `bot/shared/utils/registration-status.js` is now the one definition: a completed run OR a name we already know. Both gates in text-message.handler.js read it — the /register copy branch and the "register" keyword branch, the second of which falls through to sendNameQuestion and is what would have re-asked 4,145 teachers for their names. registration_state is kept in the union but is not a fallback: on prod it reads 'unregistered' for 7,684 of those 7,685 rows, including 439 of the 440 whose flag is true. An empty-string first_name stays falsy — 212 prod rows carry one. WRITER. handleRegistrationDataExchange returns screen SUCCESS from two places and tells the teacher "Your registration is complete." Neither wrote the flag; it was set only when the terminal Flow payload later reached flow-response.handler.js — the very payload this file's own header documents as unreliable, and the reason the NAME write was already moved per-screen. The completion flag was never moved with it, which is why the last-7-day completion rate is 76.7% and not 100%. Both SUCCESS branches now mark the account complete. flow-response.handler.js still writes it too; a second write of true is a no-op. TESTS. tests/registration/bd-oak77-6-*.test.js — in the ROOT tree, so the merge gate actually runs them (bot/tests/ is a separate runner CI does not gate on). They drive the real handleTextMessage and the real endpoint, so they execute the changed lines; 5 of the 9 failed before this change and pass after, and the other 4 are the controls that stop the predicate widening too far (empty-string name, no name, flag-only, non-terminal screen). bd-2480-register-gate.test.js pinned the old contract with its own INLINE COPY of the predicate — green while the shipped handler was wrong. It now requires the real helper. Gate: root suite, no new failing suites and no new offenders vs develop; source-hygiene offenders 888 -> 886. Refs: bd-oak77.6, bd-oak77
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.
The bug
The "is this teacher registered?" gate on
developkeys onregistration_completed. On production that flag is not the truth. Counted read-only on NIETE prodihzciabopbttygxxgrkm, 2026-09-06:first_name'')registration_completed = trueregistration_stateis no fallback: it reads'unregistered'for 7,684 of those 7,685 rows, including 439 of the 440 whose flag is true. Exactly one row on the whole database says'completed'.Staging never showed this because staging is a different database whose users all registered through the current Flow.
Two independent faults:
rc = truerow was created 2026-07-11.The fix
Reader —
bot/shared/utils/registration-status.js(new), one definition: registered = a completed run OR a name we already know. Both gates intext-message.handler.jsread it::1777— the/registercopy branch (was showing "Welcome" instead of "Update your details").:2275— the "register" keyword branch. This is the harmful one: falling through it reachesFeatureRegistrationService.sendNameQuestionand re-asks the teacher for her name. That is the path 4,145 active users would have taken.An empty-string
first_namestays falsy, so the 212''rows are still unregistered — matching what the bot does today.Writer —
bot/shared/routes/registration-endpoint.js:handleRegistrationDataExchangereturnsscreen: 'SUCCESS'from two places (the direct path and theorg="other"path) and tells the teacher "Your registration is complete." Neither wrote the flag. It was set only when the terminal Flow payload later reachedflow-response.handler.js:574— the very payload this file's own header documents as unreliable ("the terminal Flow payload arrives with the earlier screens' values empty… verified in the logs 2026-09-03"), which is why bd-2480 already moved the name write per-screen. The completion flag was never moved with it. Both SUCCESS branches now callmarkRegistrationComplete().flow-response.handler.jsstill writes it as well; a second write oftrueis a no-op.Tests — red-first, and they execute the changed lines
tests/registration/bd-oak77-6-*.test.js, in the root tree sonpm testactually gates them (bot/tests/is a separate runner CI does not gate on). They drive the realhandleTextMessageand the real endpoint.Before the change: 5 of 9 failed — "already registered" never sent,
sendNameQuestioncalled once,/registerheader"Welcome", and both SUCCESS branches wrote no flag. After: 9/9. The other 4 are the controls that stop the predicate widening too far (empty-string name, no name at all, flag-only-no-name, non-terminal screen).bot/tests/registration/bd-2480-register-gate.test.jspinned the old contract with its own inline copy of the predicate — green the whole time the shipped handler was wrong on prod. It now requires the real helper.Gate
Two full root runs on this branch against a fresh worktree of
origin/developaeb9862: no suite fails here that does not also fail on develop (run 2 is one suite better — the quarantinedportal-capstone-submitflake did not fire). The repo gate reports no new failing suites and no new offenders;source-hygieneoffenders 888 → 886. Run 1 showedtests/cache/language-writer.test.js, which passes 15/15 in isolation on both trees and does not recur — a parallel-run flake, not this change.Staging checked before merge:
niete_lp612_rendersstatus='authoring'= 0,queued= 0.Not in this PR
BACKFILL.sql(with its reversal) is written toprod_golive_2026-09-06/00_vehicle/and is not run — it is a write to a pre-existing prod table and needs the operator's go. The code fix stands on its own without it; the backfill additionally repairs the coach portal's teacher list (dashboard/routes/hcp.routes.jsfilters.eq('registration_completed', true)and returns 440 of 7,685 today — that filter is identical onmain, so it is a live defect rather than something this promotion introduces) and every registration/adoption metric.Refs: bd-oak77.6, bd-oak77