You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 87c37ae
Browse filesBrowse the repository at this point in the historyBrowse files
fix(plugin-auth): the no-sign-in-account boot report states the email-verification rule as shipped (#20434)
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_boot` report 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 when `open` began
honouring a deployment's opt-out (`65352b7d`, #20406), so the message
contradicted itself.
**The rule, re-derived from `packages/plugins/plugin-auth/src` at
`24b70859`:**
- `resolveEmailVerificationRequirement` (audience-posture.ts), the one
resolver the better-auth wiring and `getPublicConfig()` both read:
- `email_domain` is always on;
- `open` is on unless the declared value is `false`;
- `invite_only` uses the declared value, off by default.
- `assertAudienceConfig` refuses an explicit `false` under
`email_domain` from any source. Under `open` it refuses a `false` whose
declarant is `console`.
- `applyConfigPatch` (auth-manager.ts) marks a patch as the deployment's
unless its caller passes `requireEmailVerificationFrom: 'console'`.
- The invitation carve-out in `decideAudienceAdmission` returns only
`admit` and `grantPermissionSet`, so it cannot exempt anyone from
verification. The only row born `emailVerified: true` is the
walled-owner operator stamp, and an invitation-admitted creation never
gets it.
**The message now:**
- states the rule once, in the invitation paragraph:
- the INVITED login meets the same email-verification rule as any
sign-up;
- `email_domain` is always ON;
- `open` is ON unless the DEPLOYMENT declared it off
(`emailAndPassword.requireEmailVerification: false` or
`OS_AUTH_REQUIRE_EMAIL_VERIFICATION=false`), and a `false` stored only
through the settings console is refused there;
- `invite_only` follows that declaration and is OFF by default.
- keeps the scoped no-mail-transport rider and the advice order ("close
the posture back to 'invite_only' BEFORE that person registers"). It
adds "and leave verification at its default OFF", because under
`invite_only` a declared `true` would still apply.
- in remedy (b), refers back to that rule ("wherever it is ON") instead
of restating it.
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`:
- the pin at the old line 164 is flipped. It now asserts the new text's
substance: the invited-login rule, `email_domain` always ON, the `open`
conditional, the console refusal, and the rider's new wording.
- two negative pins are added, one per old unconditional spelling.
- a new `#20389` describe drives each clause against the manager: `open`
+ the deployment's `false` wires OFF; the same `false` from the console
is refused and stays ON, with the deployment patch as control;
`email_domain` + `false` refuses the boot, with a control; `invite_only`
is OFF by default and ON when declared.
- three titles and comments that repeated the old quantifier now say
"with nothing declared".
- `managerWith` takes an optional deployment `emailAndPassword` block.
- `packages/plugins/plugin-auth/src/auth-manager.ts`: one comment near
the old line 2295 (the #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-auth` `patch`. The boot report prose ships.
Stale-phrase counts across `packages/plugins/plugin-auth/src`, BASE
`24b70859` → HEAD `7898a47d`: "INVITED login too" 2 → 0; "every posture
other than 'invite_only' ('open'," 1 → 0; "self-registration FORCES
`requireEmailVerification` on (see" 1 → 0; "self-registration-permitting
posture FORCES `requireEmailVerification`" 1 → 0; "third bullet below
applies to the INVITED login too" 1 → 0.
## Verification (all at HEAD `7898a47d`)
- **Build:** `pnpm --workspace-concurrency=2 --filter
'@objectstack/plugin-auth^...' build`, run through the verify lock:
`VERDICT command-exit 0`. Then `pnpm --filter @objectstack/plugin-auth
build`: check-dts-emitted 2/2.
- **Targeted tests:** `pnpm --filter @objectstack/plugin-auth exec
vitest run --maxWorkers=2 src/boot-sign-in-reachability.test.ts
src/signup-existing-address-refusal.test.ts` gave `Test Files 2 passed
(2)` / `Tests 59 passed (59)`.
- **Package tests:** `pnpm --filter @objectstack/plugin-auth exec vitest
run --maxWorkers=2` gave `Test Files 114 passed (114)` / `Tests 2458
passed (2458)`.
- **Typecheck:** `pnpm --filter @objectstack/plugin-auth run typecheck`
exited 0. It runs `tsc --noEmit`, the examples project, and
`check:test-typecheck`, which reported "OK … 10 file(s) / 94 error(s) …
held in test-typecheck-debt.json".
- **Reverse verification.** The fix was committed first. Each leg ran
through `scripts/ablation-replace.mjs` in WRAP mode, with a driver trap
that restores from HEAD by absolute path. The test imports the module by
relative path, so it reads `src/` and no rebuild was needed between
legs.
- Leg A put the old sentence back in place of the new one. The anchor
went 1 → 0 and the blob went `b0be40fa` → `d1b521c9`. Result: `Tests 1
failed | 50 passed (51)`, failing `[#15588/F1] the no-mail-transport
rider is SCOPED to the default posture` with "expected '[auth]
no_sign_in_account_at_boot: th…' to contain 'under the default
\'invite_only\' pos…'".
- Leg B added the old sentence beside the new text. Result: the same
single test red, on the negative pin "not to match /an 'open' or
'email_domain' posture …/i".
- Leg C added the old remedy-(b) quantifier. Result: the same single
test red, on "not to match /every posture other than 'invite_onl…/i".
- After all three legs, the blob equals the HEAD blob (`b0be40fa`) and
`git diff HEAD` is empty. The direction observed was red, as expected.
- **Lint, narrowed to the 4 touched `.ts` files:** `eslint
--no-inline-config --format json` linted 4 files with 0 errors and 0
warnings. `eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, no typed rules), so this diff cannot change a
verdict on an untouched file. The repo-wide run belongs to CI.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 64 commands over these 5
paths. `--ran` reconciled "64 derived, 62 run, 2 NOT-MEASURED, 0 UNRUN".
All 62 that ran exited 0.
- NOT MEASURED: `pnpm check:dual-build-cjs-loads` exited 3 with
PREREQUISITE NOT MET, because it needs every package's `dist/` and this
tree built only the plugin-auth closure. Declared narrowing, applied to
the one touched package: `node --check dist/index.js` exits 0, and
`require('./dist/index.js')` loads 143 exports. The new text is present
in `dist/index.js` and `dist/index.mjs` (1 each), and the old phrase in
neither (0 each).
- NOT MEASURED: `pnpm check:type-check-debt` exited 3 with PREREQUISITE
NOT MET, because `@objectstack/runtime`, `service-cluster` and
`service-job` have no built types here. Declared narrowing: the ledger's
DEBT keys are `cloud-connection`, `hono`, `observability` and
`spec-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` (and `email_domain`) force `requireEmailVerification` on". This
is a docblock explaining why fixtures use the invitation lane. It is
true for the undeclared fixture config but omits `open`'s opt-out. No
carrier.
-
`packages/plugins/plugin-auth/src/admin-remove-user-gate-ordering.test.ts:135`:
"`open` would force email verification on". The same shape, in a test
comment. No carrier.
- `packages/verify/src/harness.ts:778`: "`open` and `email_domain` force
`requireEmailVerification` on". 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 mirror
`packages/spec/src/migrations/registry.ts:6270`: "accepts forced email
verification". This is the v18 migration's reason text, accurate for
that release. It is `packages/spec` history, not live advice.
**Read and left, because each is correct:** `audience-posture.ts:221`
(it says "unless noted", then notes the `open` exception);
`auth-manager.ts:1639` (it carries the #20389 exception); the runtime
log at `auth-manager.ts:4818` ("… when email verification is forced on"
is a sufficient condition, not a rule statement);
`packages/spec/src/system/auth-config.zod.ts` lines 408–419 and 588;
`content/docs/deployment/self-hosting.mdx:580` and its posture section;
`content/docs/deployment/environment-variables.mdx:84`; and the
`audience-posture*.test.ts` assertions, which pin the refusal messages
as they stand.
`auth-plugin.ts` has no hit, so nothing reaches the file #20412 holds.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017B6YKCGu8CTY2KBWgwaHAs)_
Co-authored-by: Claude <noreply@anthropic.com>
The `no_sign_in_account_at_boot` boot report states the email-verification rule each audience posture enforces.
6
+
7
+
Clause-②: no
8
+
9
+
The report's recovery advice said an `open` or `email_domain` posture forces email verification on the invited login. Later in the same message it said an `open` deployment can turn verification off. The first sentence stopped being true when posture `open` began honouring a deployment's opt-out. The message now states the rule once, as the auth plugin enforces it, and applies it to the invited login and to a new self-registered address alike:
10
+
11
+
-`email_domain` always forces email verification on.
12
+
-`open` forces it on unless the deployment turns it off, with `emailAndPassword.requireEmailVerification: false` or `OS_AUTH_REQUIRE_EMAIL_VERIFICATION=false`. A `false` stored only through the settings console is refused.
13
+
-`invite_only` follows the deployment's declaration and is off by default.
14
+
15
+
The advice keeps its order: on a deployment with no mail transport, close the posture back to `invite_only`, with verification left at its default off, before the invited person registers.
16
+
17
+
Text only: no admission decision, verification default or accepted value changes.
0 commit comments