4e816d84ac0d3079b74b96b0c827fa9d213eba2b
max
  Thu Sep 3 07:00:40 2026 -0700
hgConfCatalog: register login.recovEmailChange as a knob, refs #37925 #38197

diff --git src/hg/utils/hgConfCatalog/hgConfCatalog.py src/hg/utils/hgConfCatalog/hgConfCatalog.py
index 8cbf73b1dc7..5956fbd2bda 100755
--- src/hg/utils/hgConfCatalog/hgConfCatalog.py
+++ src/hg/utils/hgConfCatalog/hgConfCatalog.py
@@ -604,30 +604,50 @@
         h("login.pwdEyeIcon", "flag", "hg/hgLogin/hgLogin.c",
           default="TRUE", role="knob", verified=True,
           note="Show-password eye icon on the login form."),
         h("login.emailLink", "flag", "hg/hgLogin/hgLogin.c",
           default="FALSE", role="knob", public=True, verified=True,
           ticket="37929",
           note="Passwordless sign-in: the user is emailed a one-time link "
                "instead of typing a password.  The same switch shows the "
                "change-email page, since that page has no password check "
                "either.  It needs working outbound mail, so a mirror without "
                "it leaves this off permanently, and that is what settles it as "
                "a knob rather than a gate: a machine that cannot send mail can "
                "never turn it on, so there is no release at which the flag "
                "could be deleted.  If the RR's default ever flips TRUE the "
                "flag still has to stay for everyone else."),
+        h("login.recovEmailChange", "flag", "hg/hgLogin/hgLogin.c",
+          default="FALSE", role="knob", public=True, verified=True,
+          ticket="38197",
+          note="Offer the page where a signed-in user sets or changes the "
+               "recovery email address on their account.  A knob for the same "
+               "reason login.emailLink is one: the new address is confirmed by "
+               "mail before it takes effect, so a mirror that cannot send mail "
+               "leaves this off permanently and there is no release at which "
+               "the flag could be deleted.  It also needs login.cookieSalt to "
+               "sign the confirmation link and the gbMembers.recovEmailVerified "
+               "column, and hgLogin hides the page where any of the three is "
+               "missing.",
+          debatable="A gate if the intent is really to ship the page dark and "
+                    "then flip it: it was added defaulting FALSE so the page "
+                    "could sit on master through QA, which is what a gate is "
+                    "for.  Filed as a knob because the mail requirement it "
+                    "shares with login.emailLink outlives that: even after the "
+                    "RR turns it on, a mirror without outbound mail needs the "
+                    "switch, so there is no release at which it could be "
+                    "deleted."),
         h("analytics.trackClicks", "flag", "hg/lib/googleAnalytics.c",
           default="TRUE", role="knob", verified=True,
           note="Report link clicks to analytics.  A mirror with its own "
                "privacy policy turns this off."),
         h("analytics.trackButtons", "flag", "hg/lib/googleAnalytics.c",
           default="TRUE", role="knob", verified=True,
           note="Report button presses to analytics."),
         h("wikiTrack.readOnly", "flag", "hg/lib/wikiTrack.c",
           default="FALSE", role="knob", verified=True, deprecated=True,
           note="Make the wiki annotation track read-only.  The wiki track "
                "itself is effectively retired."),
         h("cdw.siteIsPublic", "flag",
           "hg/cirm/cdw/cdwGetFile/cdwGetFile.c", default="FALSE",
           role="knob", verified=True, deprecated=True,
           note="CIRM data warehouse is public.  Belongs to the cirm CGIs, "