Repository navigation
fix(plugin-auth): the no-sign-in-account boot report states the email-verification rule as shipped - #20434
Conversation
…-verification rule as shipped The recovery advice said an 'open' or 'email_domain' posture forces email verification on the invited login, while remedy (b) in the same message conceded the 'open' deployment opt-out. The message now states the rule once, as resolveEmailVerificationRequirement and assertAudienceConfig enforce it: email_domain always on; open on unless the deployment declared it off (a console-stored false is refused); invite_only follows the declaration, off by default. Remedy (b) refers back to that rule. The pin flips to the new text's conditional and the email_domain forced case, with a negative pin on both old unconditional spellings, and a new describe drives each clause against the manager. The file docblock, one auth-manager.ts comment and the signup-existing-address-refusal.test.ts docblock are restated the same way. Text and tests only. Claude-Session: https://claude.ai/code/session_017B6YKCGu8CTY2KBWgwaHAs Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6dacb326ff05c5bd57ac70d739acdaf879f10f9a && git checkout 6dacb326ff05c5bd57ac70d739acdaf879f10f9a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e01d3473053d2d43f5fca0db98b0409744695a5a 7898a47de302e2d9838b0c4bfdb82ab459739bec && git checkout -B drift-repro e01d3473053d2d43f5fca0db98b0409744695a5a && git merge --no-ff 7898a47de302e2d9838b0c4bfdb82ab459739bec
node scripts/docs-audit/affected-docs.mjs --json e01d3473053d2d43f5fca0db98b0409744695a5a
|
Contract reviewServed-tier: Inputs: card #20413 body and all 8 comments (claims ① Derived judgmentsAccept set — nothing moves: right. The only non-comment source change is the string literal returned by Public surface — the shipped boot report, judged clause by clause against the head's code:
Pins. Every positive substring in the flipped Docs alignment. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Rendered by an isolated contract-review subagent and adopted by the Generated by Claude Code |
Fixes #20413
Clause-②: no
Round 2 of the card, location 4: the plugin-auth boot report. Round 1 (PR #20421, landed as
24b70859) covered locations 1–3. This round completes the card. It changes text, comments and tests only. No admission decision, verification default or accepted value moves.What changed
The
no_sign_in_account_at_bootreport tells an operator to recover a locked-out deployment by inviting one address. Its advice said an'open'or'email_domain'posture "forces email verification on the INVITED login too". Later, in remedy (b), the same message said verification is forced "unless an 'open' deployment has itself declared it off". The first sentence stopped being true whenopenbegan honouring a deployment's opt-out (65352b7d, #20406), so the message contradicted itself.The rule, re-derived from
packages/plugins/plugin-auth/srcat24b70859:resolveEmailVerificationRequirement(audience-posture.ts), the one resolver the better-auth wiring andgetPublicConfig()both read:email_domainis always on;openis on unless the declared value isfalse;invite_onlyuses the declared value, off by default.assertAudienceConfigrefuses an explicitfalseunderemail_domainfrom any source. Underopenit refuses afalsewhose declarant isconsole.applyConfigPatch(auth-manager.ts) marks a patch as the deployment's unless its caller passesrequireEmailVerificationFrom: 'console'.decideAudienceAdmissionreturns onlyadmitandgrantPermissionSet, so it cannot exempt anyone from verification. The only row bornemailVerified: trueis the walled-owner operator stamp, and an invitation-admitted creation never gets it.The message now:
email_domainis always ON;openis ON unless the DEPLOYMENT declared it off (emailAndPassword.requireEmailVerification: falseorOS_AUTH_REQUIRE_EMAIL_VERIFICATION=false), and afalsestored only through the settings console is refused there;invite_onlyfollows that declaration and is OFF by default.invite_onlya declaredtruewould still apply.The advice still matches
content/docs/deployment/self-hosting.mdx("Email verification under each posture"), which the module docblock names as this message's long form. That page already stated the rule correctly.Files:
packages/plugins/plugin-auth/src/boot-sign-in-reachability.ts: the remedy string, plus the file docblock's invitation bullet (the old "third bullet below applies to the INVITED login too" sentence).packages/plugins/plugin-auth/src/boot-sign-in-reachability.test.ts:email_domainalways ON, theopenconditional, the console refusal, and the rider's new wording.#20389describe drives each clause against the manager:open+ the deployment'sfalsewires OFF; the samefalsefrom the console is refused and stays ON, with the deployment patch as control;email_domain+falserefuses the boot, with a control;invite_onlyis OFF by default and ON when declared.managerWithtakes an optional deploymentemailAndPasswordblock.packages/plugins/plugin-auth/src/auth-manager.ts: one comment near the old line 2295 (the plugin-auth: a sign-up for an address that already has asys_userrow answers 200 and persists nothing (postureemail_domain) #15587 uniqueness-refusal block), restated as "on by default".packages/plugins/plugin-auth/src/signup-existing-address-refusal.test.ts: one docblock sentence (old line 30), restated the same way..changeset/20413-boot-report-verification-text.md:@objectstack/plugin-authpatch. The boot report prose ships.Stale-phrase counts across
packages/plugins/plugin-auth/src, BASE24b70859→ HEAD7898a47d: "INVITED login too" 2 → 0; "every posture other than 'invite_only' ('open'," 1 → 0; "self-registration FORCESrequireEmailVerificationon (see" 1 → 0; "self-registration-permitting posture FORCESrequireEmailVerification" 1 → 0; "third bullet below applies to the INVITED login too" 1 → 0.Verification (all at HEAD
7898a47d)pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-auth^...' build, run through the verify lock:VERDICT command-exit 0. Thenpnpm --filter @objectstack/plugin-auth build: check-dts-emitted 2/2.pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/boot-sign-in-reachability.test.ts src/signup-existing-address-refusal.test.tsgaveTest Files 2 passed (2)/Tests 59 passed (59).pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2gaveTest Files 114 passed (114)/Tests 2458 passed (2458).pnpm --filter @objectstack/plugin-auth run typecheckexited 0. It runstsc --noEmit, the examples project, andcheck:test-typecheck, which reported "OK … 10 file(s) / 94 error(s) … held in test-typecheck-debt.json".scripts/ablation-replace.mjsin WRAP mode, with a driver trap that restores from HEAD by absolute path. The test imports the module by relative path, so it readssrc/and no rebuild was needed between legs.b0be40fa→d1b521c9. Result:Tests 1 failed | 50 passed (51), failing[#15588/F1] the no-mail-transport rider is SCOPED to the default posturewith "expected '[auth] no_sign_in_account_at_boot: th…' to contain 'under the default 'invite_only' pos…'".b0be40fa) andgit diff HEADis empty. The direction observed was red, as expected..tsfiles:eslint --no-inline-config --format jsonlinted 4 files with 0 errors and 0 warnings.eslint.config.mjsnever enables type-aware linting (noparserOptions.project, no typed rules), so this diff cannot change a verdict on an untouched file. The repo-wide run belongs to CI.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 64 commands over these 5 paths.--ranreconciled "64 derived, 62 run, 2 NOT-MEASURED, 0 UNRUN". All 62 that ran exited 0.pnpm check:dual-build-cjs-loadsexited 3 with PREREQUISITE NOT MET, because it needs every package'sdist/and this tree built only the plugin-auth closure. Declared narrowing, applied to the one touched package:node --check dist/index.jsexits 0, andrequire('./dist/index.js')loads 143 exports. The new text is present indist/index.jsanddist/index.mjs(1 each), and the old phrase in neither (0 each).pnpm check:type-check-debtexited 3 with PREREQUISITE NOT MET, because@objectstack/runtime,service-clusterandservice-jobhave no built types here. Declared narrowing: the ledger's DEBT keys arecloud-connection,hono,observabilityandspec-monorepo, none of which depends on plugin-auth, and TEST_DEBT is empty. The only non-comment source change is the content of one string literal, so no type moves.check:nul-bytes: OK. Control-byte self-scan over the 5 files: no hits.Acceptance notes
Sweep, at
24b70859: lines naming email verification (email.?verification|requireEmailVerification|EMAIL_NOT_VERIFIED|REQUIRE_EMAIL_VERIFICATION) that also carry a forcing or quantifier word (forc|other than invite_only|every posture|permitting posture|widen). That gave 51 lines in 19 files, and every hit outside round 1's surface was read in context. In the files this round owns, all hits are fixed.Stale or incomplete outside this round's surface. All are comments or frozen history; none is edited here.
packages/plugins/plugin-auth/src/audience-gate-test-support.ts:17: "open(andemail_domain) forcerequireEmailVerificationon". This is a docblock explaining why fixtures use the invitation lane. It is true for the undeclared fixture config but omitsopen's opt-out. No carrier.packages/plugins/plugin-auth/src/admin-remove-user-gate-ordering.test.ts:135: "openwould force email verification on". The same shape, in a test comment. No carrier.packages/verify/src/harness.ts:778: "openandemail_domainforcerequireEmailVerificationon". The same shape, in a comment. No carrier.packages/spec/src/migrations/entries/semantic/18.audience-posture-default-invite-only.ts:19, and its generated mirrorpackages/spec/src/migrations/registry.ts:6270: "accepts forced email verification". This is the v18 migration's reason text, accurate for that release. It ispackages/spechistory, not live advice.Read and left, because each is correct:
audience-posture.ts:221(it says "unless noted", then notes theopenexception);auth-manager.ts:1639(it carries the #20389 exception); the runtime log atauth-manager.ts:4818("… when email verification is forced on" is a sufficient condition, not a rule statement);packages/spec/src/system/auth-config.zod.tslines 408–419 and 588;content/docs/deployment/self-hosting.mdx:580and its posture section;content/docs/deployment/environment-variables.mdx:84; and theaudience-posture*.test.tsassertions, which pin the refusal messages as they stand.auth-plugin.tshas no hit, so nothing reaches the file #20412 holds.Generated by Claude Code