Repository navigation
feat(spec)!: retire emailAndPassword.revokeSessionsOnPasswordReset — a completed password reset always ends every session the account holds - #29964
Conversation
A completed password reset always ends every session the account holds, and there is no setting. The key becomes a retiredKey() tombstone with an exported prescription; plugin-auth's entry guard refuses the retired key at any value with the same text, since the manager takes its configuration unparsed. Registers the retired key and the D3 semantic entry, adds the step-18 rationale fragment, a retirement pin with a tree-scoped absence scan, and updates HARDENING.md and the authentication docs. Claude-Session: https://claude.ai/code/session_01WGufzgjTaf6BB6T3vxdisU Co-authored-by: Claude <noreply@anthropic.com>
…et retirement Claude-Session: https://claude.ai/code/session_01WGufzgjTaf6BB6T3vxdisU Co-authored-by: Claude <noreply@anthropic.com>
…bsence walk Claude-Session: https://claude.ai/code/session_01WGufzgjTaf6BB6T3vxdisU Co-authored-by: Claude <noreply@anthropic.com>
…n the absence walk Claude-Session: https://claude.ai/code/session_01WGufzgjTaf6BB6T3vxdisU Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check8 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 174 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 2f4a07b474a7f44af86d8f16187a0cba442d68a9 && git checkout 2f4a07b474a7f44af86d8f16187a0cba442d68a9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 18112bfa776b896f705c182f07b8baeff45957a7 510b8ea1ec92520df9a460bcd5f78f78ffb4560f && git checkout -B drift-repro 18112bfa776b896f705c182f07b8baeff45957a7 && git merge --no-ff 510b8ea1ec92520df9a460bcd5f78f78ffb4560f
node scripts/docs-audit/affected-docs.mjs --json 18112bfa776b896f705c182f07b8baeff45957a7 |
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz Contract reviewServed-tier: Inputs read: card #29909 body and all five comments (triage 6602106272, claims 6608265385 and 6608275168, release 6608312848, os-dev-report 6611934296); PR #29964 body and file list; the net diff ① Derived judgmentsAccept-set changes the diff implies, each judged against the tree:
Dogfood pinned to the head (the narrowing face's order): the CI Check-runs on the head at the second reading: concluded success: Auto Label, Check Documentation Links, Check PR Size, Dogfood Regression Gate (aggregator, see above), Flag docs affected by code changes, Governed Surface Queue Guard, Lint · scoped · tooling self-tests, PR-body guards, Plan Test Core shards, Spec property liveness, filter. Skipped by design: 18.x upgrade harness, Dogfood Regression Gate matrix, Dogfood Verify CLI, Packed-tarball smoke, Schedule gate, Temporal Conformance, UI Published Artifacts. Still pending (in_progress): Build Core, Build Docs, Build UI Deps, Check Changeset, Lint · CI release and engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs and code guards, Lint · repo and tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup and corpus, Live E2E UI, Test Core 1/8 through 8/8, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace. The verdict below is on what has concluded plus the tree; the pending families (notably Check Changeset, the Type Check set, and Test Core, which run the retirement pin and the plugin-auth refusals) must conclude green before landing, and a red among them re-opens this record. ② Semver levelChangeset Clause-②: no (narrowing). What is taken away, measured at the consumer: (a) a typed or parsed auth configuration may no longer carry ③ Boundary flags
④ Design judgmentPer packages/spec/src/system/auth-config.zod.ts: against the ruling and its first consumer (plugin-auth). The tombstone is the helper's canonical form; the prescription follows the skill's five conventions (backticked qualified key first; packages/spec/src/migrations/entries/retired-keys/18.system__EmailAndPasswordConfig__revokeSessionsOnPasswordReset.ts: against packages/spec/src/migrations/entries/semantic/18.email-and-password-revoke-sessions-on-reset-retired.ts: against packages/spec/src/migrations/registry.ts.template: against Narrowing itself against the published spec on Must-fix: none. Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz CI note — |
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz Contract reviewServed-tier: Delta review. The full record on Inputs read: the merge commit's parents and first-parent log; ① Derived judgments
Check-runs on the new head at this reading (63 total). Concluded success: Auto Label, Build and E2E UI, Build Core, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate (the aggregator; the matrix is skipped on pull_request by design, as the full record notes), Flag docs affected by code changes, Governed Surface Queue Guard, Lint and Repo Gates, Lint · CI release and engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs and code guards, Lint · repo and tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup and corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards, Plan Test Core shards, Spec property liveness, Test Core 2/8, 5/8, 7/8, 8/8, Test UI 1, 2, 3, 6, 8, 9, 12, 14, 16 of 16, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter. Skipped by design: 18.x upgrade harness, Dogfood Regression Gate matrix, Dogfood Verify CLI, Lint UI, Packed-tarball smoke, Schedule gate, Temporal Conformance, UI Published Artifacts. Still pending (in_progress): Test Core 1/8, 3/8, 4/8, 6/8; Test UI 4, 5, 7, 10, 11, 13, 15 of 16; Type Check UI. No check-run has failed. The families the full record named as must-conclude (Check Changeset, the Type Check set, Build set) are now green; the remaining Test Core shards run the retirement pin and the plugin-auth refusals and must conclude green before landing. A red among them re-opens this record. ② Semver levelUnchanged from the full record: the changeset, the PR body's ③ Boundary flags
④ Design judgmentThe four Must-fix: none. Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz Contract reviewServed-tier: Second delta review. The full record on Inputs read: the merge commit's parents and first-parent log; ① Derived judgments
Check-runs on the new head at this reading (63 total): 48 concluded success, including Check Changeset, Governed Surface Queue Guard, PR-body guards, Spec property liveness, the Build, Type Check and Lint families, Test Core 3/8, 6/8, 7/8, 8/8, and twelve Test UI shards; 8 skipped by design (Dogfood matrix, Dogfood Verify CLI, Lint UI, Temporal Conformance, UI Published Artifacts, Schedule gate, 18.x harness, Packed-tarball smoke); 7 still in progress (Test Core 1/8, 2/8, 4/8, 5/8; Test UI 7, 8, 12 of 16). No check-run has failed. The in-progress Test Core shards carry the retirement pin and the plugin-auth refusals and must conclude green before landing; a red among them re-opens this record. ② Semver levelUnchanged: ③ Boundary flags
④ Design judgmentThe four Must-fix: none. Implemented-by: VERDICT: PASS |
Requested by Yibo Wang · project thread
Fixes #29909
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none —
git grep -n revokeSessionsOnPasswordReset -- skillshas 0 hits at the base and at the head; no published skill teaches the key.维护者速读
emailAndPassword.revokeSessionsOnPasswordReset退役为墓碑键:写它(无论true还是false)在tsc、schema 解析和 plugin-auth 启动/配置补丁入口都会被拒,提示「删掉这个键」。重置密码后吊销该账号全部会话的行为不变。true,已改);外部仍写true的宿主配置会在启动时被拒,删键即可。回滚即 revert 本 PR。packages/plugins/plugin-auth/src/password-reset-ends-sessions.ts(守卫从「只拒 false」改为「任何值都拒」)、packages/spec/src/system/auth-config.zod.ts(墓碑和导出的处方常量)、packages/spec/src/migrations/entries/semantic/18.email-and-password-revoke-sessions-on-reset-retired.ts。pnpm --filter @objectstack/spec exec vitest run --project repo src/system/auth-revoke-sessions-on-password-reset-retirement.test.ts,预期 15 passed。What changes
@objectstack/spec.EmailAndPasswordConfig.revokeSessionsOnPasswordResetbecomesretiredKey(REVOKE_SESSIONS_ON_PASSWORD_RESET_RETIRED). The prescription reads: "emailAndPassword.revokeSessionsOnPasswordResetwas removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove) — it could hold only one value: a completed password reset always ends every session the account holds, on every reset door, and no setting turns that off. Delete the key; nothing replaces it." It is exported from@objectstack/spec/systemso plugin-auth answers with the same text.ttlkeys with different units in one block, baretimeoutkeys, unit-less tenant timeouts #14478 ruling BexternalVocabularyexample) no longer cites this key; it now lists the live siblings plugin-auth still forwards by name (disableSignUp,requireEmailVerification,minPasswordLength,maxPasswordLength,autoSignIn).RETIRED_KEYS_BY_MAJOR[18]gainssystem/EmailAndPasswordConfig:revokeSessionsOnPasswordReset; a D3 semantic entryemail-and-password-revoke-sessions-on-reset-retiredand itsSTEP18_RATIONALEfragment (order 118). No D2 conversion: the key lives in host plugin configuration, not in a stack collection or a stored row (theAccessControlConfig.maxAgeprecedent). The prescription therefore carries noos migrate metasentence.@objectstack/plugin-auth.assertPasswordResetEndsSessionsis kept and re-aimed, not deleted: it now refuses the retired key at ANY value (it refused onlyfalse), throwing[auth]+ the spec prescription, at construction and onapplyConfigPatch.auth-manager-auth-instance.tsstill sets better-auth's ownrevokeSessionsOnPasswordReset: true: that line is the mechanism of the rule, not a forward of the spec key (comment updated to say so).docs/HARDENING.md(the revocation row and the step-3 check) andcontent/docs/permissions/authentication.mdxnow say the behaviour is unconditional and the key is retired. Regenerated:authorable-surface/system.json([RETIRED]row),api-surface/system.jsonandexport-origins/system.json(the new export),content/docs/references/system/auth-config.mdx..changeset/29909-revoke-sessions-on-password-reset-retired.md:@objectstack/specminor,@objectstack/plugin-authminor, markerregistered email-and-password-revoke-sessions-on-reset-retired, BREAKING banner and the remedy.Why
The maintainer ruled on #29909, verbatim: 「一律吊销,不加开关」 and 「第三批 同意」 (item 2: retire the key). After #29827 the key could hold one value only.
Premise checks at base
21510bba(main, after #29827 merged as3a856f68):git grep -n revokeSessionsOnPasswordReset -- packages docs skills content examples ui: the schema key, the two writers oftruethe card names (auth-manager.test.ts:2060,auth-config.test.ts:299), the plugin guard and its tests, the better-auth option atauth-manager-auth-instance.ts:390,docs/HARDENING.md:263, and one more prose producer the card did not list:content/docs/permissions/authentication.mdx:313. At the head, every remaining hit is the tombstone, the exported prescription, the plugin guard and its refusal tests, the better-auth option, generated references, the D3 entry, and releasedCHANGELOG.mdhistory.assertPasswordResetEndsSessionsis NOT unreachable after the tombstone (the order's condition for deleting it fails):AuthManagernever parses its configuration throughAuthConfigSchema(plugin-auth has no parse of it;one-time-token-handoff.tssays so), so an untyped host or anapplyConfigPatchcaller still reaches it. Re-aimed instead, as spec seat 2 agreed in thread.revokeSessionsOnPasswordReset,EmailAndPasswordConfig,assertPasswordResetEndsSessionsand "password reset": 0 hits each. That search reads PR text, not file lists.Tests
packages/spec/src/system/auth-revoke-sessions-on-password-reset-retirement.test.ts(repo project, listed invitest.repo-tests.json): the prescription text; refusal oftrue,false,'false',0,null,{}at the key with codeinvalid_type, path and exact message; the nested path throughAuthConfigSchema; CONTROL without the key and withundefined; thetscchannel (@ts-expect-error); the walked shape keeps the key; registration (retired-key row, D3 entry with noconversionIds, no D2 names the key); a tree-scoped absence walk over the declared@objectstack/specradius with a lit control. 15/15.auth-config.test.ts: the "full config" case no longer writes the key and asserts the parse grows none.password-reset-ends-sessions.test.ts: refusal at construction forfalse,true,'false',0,null; on a patch forfalseandtruewith the standing configuration kept; the message equals the spec prescription; CONTROL: absent,undefinedand no block pass.auth-manager.test.ts: the forwarding case no longer writes the key and asserts better-auth still receivesrevokeSessionsOnPasswordReset: true.Ablation (tombstone refusal)
Mutation through
scripts/ablation-replace.mjsonpackages/spec/src/system/auth-config.zod.tsat6c320a30: anchorrevokeSessionsOnPasswordReset: retiredKey(REVOKE_SESSIONS_ON_PASSWORD_RESET_RETIRED),→revokeSessionsOnPasswordReset: z.boolean().optional() /* ablated-29909 */,(anchor x1 → x0, replacement x0 → x1, blob6336afb7→3666d8f9). No build leg: the pin imports./auth-config.zodfrom source, not through adist/.AuthConfigSchemarefusal, thetscchannel (the parse no longer throws), and the absence walk (the ablated declaration is itself a key-position write withoutretiredKey().6336afb7== HEAD,git diff HEADempty.Verification
Readings at the head named on each line (all local, under
scripts/pm/os-verify-lock.sh, exit codes captured before any pipe):pnpm --filter @objectstack/spec buildexit 0;check:generated --fixregenerated exactly 3 (api-surface, export-origins, docs), then ✓ All 14 generated artifacts are up to date — re-measured after mergingorigin/mainatd217dd5f(rebuilt first).pnpm --filter @objectstack/spec typecheckexit 0 (test layer: 52 files / 240 errors / 131 pinned signatures held, unchanged) at2d3b20f5.pnpm --filter @objectstack/spec test:repo: Test Files 90 passed (90), Tests 1539 passed at6c320a30; the retirement file again 15/15 at2d3b20f5.--project localtargeted (auth-config.test.ts,src/migrations,retired-key-migrate-sentence.test.ts,step18-rationale-merge.test.ts): 6 files, 255 passed.typecheckexit 0 (test layer 10 files / 88 errors / 23 signatures held),pnpm --filter @objectstack/plugin-auth test: Test Files 219 passed (219), Tests 3772 passed, 10 skipped.turbo run build --concurrency=2: 119/119 successful.node scripts/pm/dispatch-gates.mjs --commands: 108 families (same list re-derived atd217dd5f). Run serially at6c320a30; five first reads were not green and each was re-run:check:comment-mask-adoptionexit 1 (a real finding: the absence walk had a private comment stripper; fixed in2d3b20f5by importingscripts/js-comment-mask.mjs, then exit 0);check-plugin-teardown-shape --self-testexit 3 (shallow clone; fetched its pinned fixture commit, then exit 0);check:exported-any,check:skill-examplesexit 3 andcheck:generatedexit 1 (read a specdistolder than its inputs whiletest:repowas regenerating; after a spec rebuild all exit 0).dispatch-gates --ran: 108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN.check:cross-package-test-inputs,check:authorable-surface,check-adr-0087-registration --base origin/mainexit 0 at2d3b20f5;check-changeset-no-major --eventwith this title and body,check-adr-0087-registrationandcheck-empty-changesetexit 0 atd217dd5f("LEVEL AXIS: … clause-②no (narrowing)and metadata-protocolnarrowed, and no package … is gradedpatch").node scripts/check-platform-checklist.mjs --changed-from origin/main: OK, 23 anchors judged, 0 lost.d217dd5f·os-verify-lock: VERDICT command-exit 0(fullturbo run build --concurrency=2120/120, thenpnpm --filter @objectstack/dogfood test, no shard, no path; 47m35s on a shared box) · Test Files 414 passed | 3 skipped (417) · Tests 3427 passed | 28 skipped (3455) · no failures.Acceptance notes
assertPasswordResetEndsSessionsif unreachable; it is reachable (above), so it was kept and now refusestruetoo. That makes the plugin-auth half a further narrowing of fix(plugin-auth)!: a completed password reset ends the account's other sessions; a reset request answers the same way for any address without waiting on its mail #29827's released "trueis accepted", stated in the changeset.auth-manager-auth-instance.ts": there is none. TherevokeSessionsOnPasswordReset: truethere is better-auth's own option and stays; the absence walk excludes that file with the reason written beside it.docs/HARDENING.mdhad two references to the behaviour (row 263 and the step-3 comment), not three; both updated.EmailAndPasswordConfig(auth configuration is not a walked metadata type), socheck:livenesshas nothing to re-grade.Human Minimum
None: the seat runs the contract review and lands it.
Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzgjTaf6BB6T3vxdisU