Skip to content

feat(spec)!: retire emailAndPassword.revokeSessionsOnPasswordReset — a completed password reset always ends every session the account holds - #29964

Draft
objectstack-fleet[bot] wants to merge 7 commits into
mainfrom
claude/project-thread-dpz9e7
Draft

objectstack-fleet[bot] wants to merge 7 commits into
mainfrom
claude/project-thread-dpz9e7

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #29909

Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — git grep -n revokeSessionsOnPasswordReset -- skills has 0 hits at the base and at the head; no published skill teaches the key.

维护者速读

  • 改了什么:认证配置里的 emailAndPassword.revokeSessionsOnPasswordReset 退役为墓碑键:写它(无论 true 还是 false)在 tsc、schema 解析和 plugin-auth 启动/配置补丁入口都会被拒,提示「删掉这个键」。重置密码后吊销该账号全部会话的行为不变。
  • 为什么改:按卡上裁决「一律吊销,不加开关」与「第三批 同意」。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 落地后这个键只剩一个合法值,按 ADR-0049 enforce-or-remove 退役。
  • 风险与回滚:仓内没有任何生产者写这个键(只有测试写过 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.revokeSessionsOnPasswordReset becomes retiredKey(REVOKE_SESSIONS_ON_PASSWORD_RESET_RETIRED). The prescription reads: "emailAndPassword.revokeSessionsOnPasswordReset was 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/system so plugin-auth answers with the same text.
  • The comment above the key (the spec: duration-shaped number keys carry their unit in describe prose only — two ttl keys with different units in one block, bare timeout keys, unit-less tenant timeouts #14478 ruling B externalVocabulary example) no longer cites this key; it now lists the live siblings plugin-auth still forwards by name (disableSignUp, requireEmailVerification, minPasswordLength, maxPasswordLength, autoSignIn).
  • ADR-0087. RETIRED_KEYS_BY_MAJOR[18] gains system/EmailAndPasswordConfig:revokeSessionsOnPasswordReset; a D3 semantic entry email-and-password-revoke-sessions-on-reset-retired and its STEP18_RATIONALE fragment (order 118). No D2 conversion: the key lives in host plugin configuration, not in a stack collection or a stored row (the AccessControlConfig.maxAge precedent). The prescription therefore carries no os migrate meta sentence.
  • @objectstack/plugin-auth. assertPasswordResetEndsSessions is kept and re-aimed, not deleted: it now refuses the retired key at ANY value (it refused only false), throwing [auth] + the spec prescription, at construction and on applyConfigPatch. auth-manager-auth-instance.ts still sets better-auth's own revokeSessionsOnPasswordReset: true: that line is the mechanism of the rule, not a forward of the spec key (comment updated to say so).
  • Docs. docs/HARDENING.md (the revocation row and the step-3 check) and content/docs/permissions/authentication.mdx now say the behaviour is unconditional and the key is retired. Regenerated: authorable-surface/system.json ([RETIRED] row), api-surface/system.json and export-origins/system.json (the new export), content/docs/references/system/auth-config.mdx.
  • Changeset .changeset/29909-revoke-sessions-on-password-reset-retired.md: @objectstack/spec minor, @objectstack/plugin-auth minor, marker registered 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 as 3a856f68):

  • git grep -n revokeSessionsOnPasswordReset -- packages docs skills content examples ui: the schema key, the two writers of true the card names (auth-manager.test.ts:2060, auth-config.test.ts:299), the plugin guard and its tests, the better-auth option at auth-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 released CHANGELOG.md history.
  • assertPasswordResetEndsSessions is NOT unreachable after the tombstone (the order's condition for deleting it fails): AuthManager never parses its configuration through AuthConfigSchema (plugin-auth has no parse of it; one-time-token-handoff.ts says so), so an untyped host or an applyConfigPatch caller still reaches it. Re-aimed instead, as spec seat 2 agreed in thread.
  • Overlap: GitHub search of open PRs for revokeSessionsOnPasswordReset, EmailAndPasswordConfig, assertPasswordResetEndsSessions and "password reset": 0 hits each. That search reads PR text, not file lists.

Tests

  • New packages/spec/src/system/auth-revoke-sessions-on-password-reset-retirement.test.ts (repo project, listed in vitest.repo-tests.json): the prescription text; refusal of true, false, 'false', 0, null, {} at the key with code invalid_type, path and exact message; the nested path through AuthConfigSchema; CONTROL without the key and with undefined; the tsc channel (@ts-expect-error); the walked shape keeps the key; registration (retired-key row, D3 entry with no conversionIds, no D2 names the key); a tree-scoped absence walk over the declared @objectstack/spec radius 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.
  • plugin-auth password-reset-ends-sessions.test.ts: refusal at construction for false, true, 'false', 0, null; on a patch for false and true with the standing configuration kept; the message equals the spec prescription; CONTROL: absent, undefined and no block pass. auth-manager.test.ts: the forwarding case no longer writes the key and asserts better-auth still receives revokeSessionsOnPasswordReset: true.
Ablation (tombstone refusal)

Mutation through scripts/ablation-replace.mjs on packages/spec/src/system/auth-config.zod.ts at 6c320a30: anchor revokeSessionsOnPasswordReset: retiredKey(REVOKE_SESSIONS_ON_PASSWORD_RESET_RETIRED), → revokeSessionsOnPasswordReset: z.boolean().optional() /* ablated-29909 */, (anchor x1 → x0, replacement x0 → x1, blob 6336afb7 → 3666d8f9). No build leg: the pin imports ./auth-config.zod from source, not through a dist/.

  • Retirement pin: 9 failed | 6 passed (15). Red: the six value refusals, the nested AuthConfigSchema refusal, the tsc channel (the parse no longer throws), and the absence walk (the ablated declaration is itself a key-position write without retiredKey().
  • Restored by the tool: blob after restore 6336afb7 == HEAD, git diff HEAD empty.

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 build exit 0; check:generated --fix regenerated exactly 3 (api-surface, export-origins, docs), then ✓ All 14 generated artifacts are up to date — re-measured after merging origin/main at d217dd5f (rebuilt first).
  • pnpm --filter @objectstack/spec typecheck exit 0 (test layer: 52 files / 240 errors / 131 pinned signatures held, unchanged) at 2d3b20f5.
  • pnpm --filter @objectstack/spec test:repo: Test Files 90 passed (90), Tests 1539 passed at 6c320a30; the retirement file again 15/15 at 2d3b20f5.
  • spec --project local targeted (auth-config.test.ts, src/migrations, retired-key-migrate-sentence.test.ts, step18-rationale-merge.test.ts): 6 files, 255 passed.
  • plugin-auth: closure build, typecheck exit 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.
  • Full turbo run build --concurrency=2: 119/119 successful.
  • node scripts/pm/dispatch-gates.mjs --commands: 108 families (same list re-derived at d217dd5f). Run serially at 6c320a30; five first reads were not green and each was re-run: check:comment-mask-adoption exit 1 (a real finding: the absence walk had a private comment stripper; fixed in 2d3b20f5 by importing scripts/js-comment-mask.mjs, then exit 0); check-plugin-teardown-shape --self-test exit 3 (shallow clone; fetched its pinned fixture commit, then exit 0); check:exported-any, check:skill-examples exit 3 and check:generated exit 1 (read a spec dist older than its inputs while test:repo was 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/main exit 0 at 2d3b20f5; check-changeset-no-major --event with this title and body, check-adr-0087-registration and check-empty-changeset exit 0 at d217dd5f ("LEVEL AXIS: … clause-② no (narrowing) and metadata-protocol narrowed, and no package … is graded patch").
  • node scripts/check-platform-checklist.mjs --changed-from origin/main: OK, 23 anchors judged, 0 lost.
  • Dogfood battery at the final head: d217dd5f · os-verify-lock: VERDICT command-exit 0 (full turbo run build --concurrency=2 120/120, then pnpm --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

  • The order said to delete assertPasswordResetEndsSessions if unreachable; it is reachable (above), so it was kept and now refuses true too. 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 "true is accepted", stated in the changeset.
  • The order named "any forward of the key in auth-manager-auth-instance.ts": there is none. The revokeSessionsOnPasswordReset: true there is better-auth's own option and stays; the absence walk excludes that file with the reason written beside it.
  • docs/HARDENING.md had two references to the behaviour (row 263 and the step-3 comment), not three; both updated.
  • No liveness ledger row exists for EmailAndPasswordConfig (auth configuration is not a walked metadata type), so check:liveness has 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

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>
…n the absence walk

Claude-Session: https://claude.ai/code/session_01WGufzgjTaf6BB6T3vxdisU
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:system tests tooling labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

8 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 6 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/authorable-surface/system.json, packages/spec/export-origins/system.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 6 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/authorable-surface/system.json, packages/spec/export-origins/system.json, …) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 174 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 18112bfa776b896f705c182f07b8baeff45957a7 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 2f4a07b474a7f44af86d8f16187a0cba442d68a9 — the merge of head 510b8ea1ec92520df9a460bcd5f78f78ffb4560f into base 18112bfa776b896f705c182f07b8baeff45957a7, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d217dd5fe74218dab0cfed08785926057935c90f
Local-runs: none
Scope: packages/spec/src/migrations/entries/retired-keys/18.system__EmailAndPasswordConfig__revokeSessionsOnPasswordReset.ts, packages/spec/src/migrations/entries/semantic/18.email-and-password-revoke-sessions-on-reset-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/system/auth-config.zod.ts + packages/spec/src/shared/retired-key.ts, packages/spec/src/migrations/entries/README.md, packages/spec/scripts/step18-rationale-merge.test.ts, packages/spec/src/system/auth-revoke-sessions-on-password-reset-retirement.test.ts, packages/spec/src/system/auth-config.test.ts, packages/spec/authorable-surface/system.json, packages/spec/api-surface/system.json, packages/spec/export-origins/system.json, packages/plugins/plugin-auth/src/password-reset-ends-sessions.ts, packages/plugins/plugin-auth/src/password-reset-ends-sessions.test.ts, packages/plugins/plugin-auth/src/auth-manager.ts, packages/plugins/plugin-auth/src/auth-manager-auth-instance.ts, packages/plugins/plugin-auth/src/auth-manager.test.ts, .changeset/29909-revoke-sessions-on-password-reset-retired.md, scripts/check-changeset-no-major.mjs, scripts/check-adr-0087-registration.mjs, .github/workflows/ci.yml

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 origin/main...d217dd5fe7 (19 files, +451/-74, five commits incl. the origin/main merge); the check-runs on the head (two readings); the dev report JSON. The seat's in-thread orders (keep the better-auth option; re-aim assertPasswordResetEndsSessions instead of deleting it; no D2) were judged as rulings the tree must satisfy, not as findings.

① Derived judgments

Accept-set changes the diff implies, each judged against the tree:

  1. EmailAndPasswordConfigSchema.revokeSessionsOnPasswordReset goes from z.boolean().optional() to retiredKey(REVOKE_SESSIONS_ON_PASSWORD_RESET_RETIRED) (auth-config.zod.ts:342). Per shared/retired-key.ts that is a z.never with the guidance as its error map, made .optional() and described [REMOVED] ...: absence and undefined pass, every other value is refused with the prescription on both channels (tsc via the never-typed input, the parse via the error map). RIGHT: this is exactly the removal the ruling 「一律吊销,不加开关」 and the card's "retire the key with a tombstone" ask for, and nothing else in the schema moves.
  2. AuthConfigSchema inherits the refusal at emailAndPassword.revokeSessionsOnPasswordReset (the nested path pin in the new test). RIGHT, derived, no extra code.
  3. New public export REVOKE_SESSIONS_ON_PASSWORD_RESET_RETIRED from @objectstack/spec/system (system/index.ts:73 re-exports auth-config.zod); api-surface/system.json and export-origins/system.json carry the one new row. RIGHT: a widening of the export surface only, by one string constant, so that plugin-auth and the schema say the same prescription; it is not a widening of what a configuration may hold, so Clause-②: no (narrowing) is not contradicted (the narrowing face judges the accept set).
  4. authorable-surface/system.json row becomes system/EmailAndPasswordConfig:revokeSessionsOnPasswordReset [RETIRED]; the authorable-surface.base.json row stays unannotated, which is correct, that file is the pinned anchor at baseRev, not the live surface (its own description says so, and AccessControlConfig:maxAge shows the same pair of readings). RIGHT.
  5. RETIRED_KEYS_BY_MAJOR[18] gains system/EmailAndPasswordConfig:revokeSessionsOnPasswordReset via the entries file; the filename 18.system__EmailAndPasswordConfig__revokeSessionsOnPasswordReset.ts is the README's derivation of the id (/ and : to __), the value matches the authorable-surface row minus [RETIRED], the spelling gate (b) in build-schemas reads. RIGHT.
  6. D3 semantic entry email-and-password-revoke-sessions-on-reset-retired under entries/semantic/18.*.ts, no conversionIds, non-empty reason and acceptanceCriteria; STEP18_RATIONALE fragment with the same id, order: 118 (one more than the highest existing, 117), inserted where the id sorts (between element-text-variant-heading-subheading-retired and email-template-category-is-system-retired), which is what packages/spec/scripts/step18-rationale-merge.test.ts pins (sorted unique kebab ids, positive integer order, NEXT_ORDER = max+1). RIGHT.
  7. No D2 conversion. Judged against the tree rather than the PR body: AuthConfigSchema is consumed by nothing in packages/spec/src outside its own file (grep), stack.zod.ts has no key holding it, and plugin-auth's AuthManagerOptions, which extends a partial of AuthConfig (auth-manager.ts:669), is the only consumer, i.e. the configuration a host hands the plugin in code. Nothing os migrate meta could rewrite exists, so the AccessControlConfig.maxAge / EnhancedApiError.fieldErrors precedent (D3 only) applies, and the prescription rightly carries no os migrate meta sentence (the class pin retired-key-migrate-sentence.test.ts judges only prescriptions that name the command). RIGHT, and it matches the seat's ruling.
  8. plugin-auth: assertPasswordResetEndsSessions now takes unknown and refuses the key at any defined value (!== undefined) with [auth] + the spec prescription; it ran before and runs now at the constructor (auth-manager.ts:1613) and on the merged block in applyConfigPatch (auth-manager.ts:2090). The premise that it is reachable holds: no file in plugin-auth parses through AuthConfigSchema (grep: only three comments mention it; one-time-token-handoff.ts:198 states the manager receives plain objects). The function is not exported from the package index, so its signature change is not a public-surface change. RIGHT, and it is the seat's re-aim order, not the card's delete order, carried out.
  9. auth-manager-auth-instance.ts:392 keeps better-auth's own revokeSessionsOnPasswordReset: true, set unconditionally and never read from this.config, so the behaviour the ruling fixes is unchanged; auth-manager.test.ts:2074 now pins that better-auth still receives true with no host key. RIGHT, per the seat's ruling.
  10. The externalVocabulary example comment (auth-config.zod.ts:321-326) no longer cites the retired key and names five live siblings forwarded by name; checked against auth-manager-auth-instance.ts:357-389, all five are forwarded under their own name (disableSignUp and requireEmailVerification as resolved values). RIGHT. Note, not a finding: enabled and resetPasswordTokenExpiresIn are forwarded by name too and are not in the list; the list is illustrative and the old comment omitted them as well.
  11. Docs: HARDENING.md (two sites, both updated), content/docs/permissions/authentication.mdx:313 (a producer the card did not list, now says the key is retired and refused at any value), generated content/docs/references/system/auth-config.mdx (two [REMOVED] rows, the generator's tombstone rendering). content/docs/releases/ untouched, as required. No skill teaches the key (0 hits in skills/ at base and head). RIGHT.
  12. Re-check git grep -n revokeSessionsOnPasswordReset -- packages docs skills content at the head: every remaining hit is the tombstone, the exported prescription, the plugin guard and its tests, the better-auth option, generated references, the registry entries, the retirement pin, the anchor base.json, and released CHANGELOGs. No producer writes the key. Card acceptance "no producer anywhere writes the key" holds.
  13. Liveness: no packages/spec/liveness/*.json row names EmailAndPasswordConfig (grep over packages/), so there is no ledger row to re-grade; the Spec property liveness check-run on the head concluded success. RIGHT.

Dogfood pinned to the head (the narrowing face's order): the CI Dogfood Regression Gate check-run on this head concluded success, but that is the aggregator over a matrix that ci.yml:2395 skips on pull_request by design (github.event_name != 'pull_request'), so CI has NOT run the battery on this head. The only dogfood reading pinned to d217dd5f is the dev's os-verify-lock: VERDICT command-exit 0 (414 files / 3427 tests, 47m35s). It names this head, and the chronology fits (head committed 17:21:51Z, report stamped 18:15Z, a 47m35s run in between). This review is read-only and cannot re-measure it; the seat accepts it as the dev's pinned claim, which is what the order asked for, and a push run on main will run the matrix.

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 level

Changeset .changeset/29909-revoke-sessions-on-password-reset-retired.md: @objectstack/spec: minor, @objectstack/plugin-auth: minor, BREAKING banner, FROM to TO remedy ("Delete the key; nothing replaces it"), marker adr-0087: registered email-and-password-revoke-sessions-on-reset-retired. Judged against the tree: the id resolves to the new D3 entry at the head and is new in this diff, which is what check-adr-0087-registration.mjs requires of registered; check-changeset-no-major.mjs grades a declared (narrowing) arm as a BREAKING change that ships minor during the launch window (line 1839), so minor on both packages is the grade the gate expects, and major would be refused. The precedent #29827 changeset graded plugin-auth patch for its narrower narrowing; minor here is the conservative side and is what the grader names. Clause-②: no (narrowing) and Metadata-protocol: narrowed on the PR body and the changeset agree with each other and with the diff: the accept set of a published schema key shrinks to absence, no package publishes anything new except the one prescription constant, which exists to carry the narrowing's own message. Level matches the diff.

Clause-②: no (narrowing). What is taken away, measured at the consumer: (a) a typed or parsed auth configuration may no longer carry emailAndPassword.revokeSessionsOnPasswordReset at any value; (b) plugin-auth no longer accepts true at the key (it accepted true and refused false since #29827). Both are the key itself leaving, which is what 「一律吊销,不加开关」 and the card's "retire the key" remove, and nothing beyond it: the better-auth option stays set, every reset door still ends every session, the revocation trail is unchanged, an absent key is accepted as before. Nothing is widened.

③ Boundary flags

  • Dev report open_questions: []. Nothing to answer.
  • Dev report out_of_scope_findings: the overlap check read open PR text only, not file lists. Judged here: the four hits files are new or touched only by this PR; registry.ts.template is the one shared file, and the fragment is inserted in its own sort gap, which is the merge-safe shape the template's header prescribes. Not a blocker; noted for the seat's land-time rebase.
  • Dev flag "assertPasswordResetEndsSessions is NOT unreachable" (falsifies the card's delete clause): confirmed against the tree (① item 8). The seat's re-aim ruling is what the tree implements. Answered.
  • Dev flag "no forward of the spec key in auth-manager-auth-instance.ts": confirmed; the literal at :392 is written, never read from the host config, and the absence walk excludes that one file with the reason beside it. Answered.
  • Dev flag "HARDENING.md had two references, not three": confirmed by the diff (row 263 and the step-3 comment). Answered.
  • Dev flag "no liveness ledger row": confirmed (① item 13). Answered.
  • Card acceptance "check:authorable-surface, check:generated and the liveness ledger are green": liveness concluded green on the head; authorable-surface and generated artifacts are the dev's local readings (all 14 up to date after the origin/main merge) and ride in the pending Lint / Build families. Escalated to the pending check-runs, not to a person.
  • Nothing in this PR touches a Tier H surface; the Governed Surface Queue Guard concluded success.

④ Design judgment

Per hits file, each tried against the code it cites and the scenario it is accepted against:

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; was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove); a dash clause saying why; "Delete the key" plus the mechanism now in force; no migrate sentence because no conversion covers it) and carries no tracker number (the pin asserts this). Exporting the prescription so that the plugin's refusal is byte-identical is the right call: a host that never parses through the schema would otherwise meet a second wording. Tried to break it: an explicit undefined passes both the schema and the guard (consistent, and the pin's CONTROL covers it); null, 0, 'false', {} are refused by both (pinned on both sides); the walked shape keeps the key so the [RETIRED] row and gate (c)'s ageing clock work. The externalVocabulary comment now makes a true claim. Holds. No finding.

packages/spec/src/migrations/entries/retired-keys/18.system__EmailAndPasswordConfig__revokeSessionsOnPasswordReset.ts: against entries/README.md and gate (b). Filename derived from the id, export const entry with the exact defKey:name string, the comment above it is what lands in the generated table and says why the key went and where its D3 lives. Holds. No finding.

packages/spec/src/migrations/entries/semantic/18.email-and-password-revoke-sessions-on-reset-retired.ts: against SemanticMigration and the upgrade-guide reader. Id matches the filename, the changeset marker and the rationale fragment; replacement tells the author what to do and what now holds; reason names the one judgment no tool can make (a deployment-maintained writer of the key); acceptanceCriteria is verifiable (boots without the key; a second-device session answers 401 after a reset). No conversionIds, correctly, and the pin asserts no D2 names the key. Should-fix (low): surface reads auth emailAndPassword.revokeSessionsOnPasswordReset — whether …, a space where the author path has a dot and a trailing explanatory clause; the sibling 18.audience-posture-default-invite-only.ts spells its surface as the plain dotted path (system.AuthConfig.audience) and the skill says to write the surface the way an author writes the key. Nothing matches on it, so this is readability of the printed guide only; spell it auth.emailAndPassword.revokeSessionsOnPasswordReset and move the gloss into reason if wanted. Not a FAIL.

packages/spec/src/migrations/registry.ts.template: against step18-rationale-merge.test.ts and joinRationale. One fragment, id equal to the D3 id, order: 118 = highest + 1, inserted at the id's sort position, text with no leading or trailing space and sentences that read correctly joined after its neighbours (it opens with "It also retires …", the house form). Holds. No finding.

Narrowing itself against the published spec on main: on main the key is z.boolean().optional() and plugin-auth accepts true. At the head the key is unwritable and the plugin refuses it at any value. The capability taken away is the declaration of a setting that had one legal value, which is what the ruling removes; the behaviour (always revoke) is untouched, and the spec's own refusal is made real at the one door the schema cannot reach (an unparsed host object), with the same text. Nothing the ruling keeps is removed.

Must-fix: none.
Should-fix: the D3 surface spelling (above).

Implemented-by: claude/project-thread-dpz9e7
Reviewed-by: session_01WGufzhJG3tKnksHVa1krBz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

CI note — Test UI red on d217dd5f is a job timeout, not a test failure. Test UI (14/16) (job 126116978945) spent 15 minutes in actions/checkout, from 18:22 to 18:37. Its tests then finished green at 18:52:52: 412 files passed, 1 skipped; 4092 tests passed, 35 skipped. The runner cancelled the job three seconds later, at its timeout, and the Test UI aggregator reports that cancel as failure. Every other check-run on the head is green. The relay re-runs only failure runs, not cancelled ones, so the executor will push a fresh head: a main merge plus the dogfood battery re-pinned to it.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 38faba5a6070b5bccd419d6327ef406d1de80dc9
Local-runs: none
Scope: packages/spec/src/migrations/entries/retired-keys/18.system__EmailAndPasswordConfig__revokeSessionsOnPasswordReset.ts, packages/spec/src/migrations/entries/semantic/18.email-and-password-revoke-sessions-on-reset-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/system/auth-config.zod.ts + packages/spec/scripts/step18-rationale-merge.test.ts, packages/spec/authorable-surface/system.json, packages/spec/authorable-surface.base.json, packages/spec/api-surface/system.json, packages/spec/export-origins/system.json, .changeset/29909-revoke-sessions-on-password-reset-retired.md, .changeset/29872-export-carries-manifest-object-extensions.md, .changeset/29899-publish-sheet-picklist-options.md, .changeset/29916-connection-error-no-invented-503.md, .changeset/delegated-admin-unreadable-row.md

Delta review. The full record on d217dd5fe74218dab0cfed08785926057935c90f (comment 6612114259, VERDICT PASS) carries over for everything this merge does not touch; this record judges only what the new head changes relative to that one. The new head is one commit, Merge remote-tracking branch 'origin/main' into claude/project-thread-dpz9e7, with parents d217dd5fe7 (the reviewed head) and 3b8c724cf2 (origin/main at the time of the merge, and still origin/main at this reading). No other commit sits between the two heads on the branch (first-parent walk).

Inputs read: the merge commit's parents and first-parent log; git diff d217dd5fe7 38faba5a60 -- PR-owned files (empty); the net diff origin/main...d217dd5fe7 against origin/main...38faba5a60 (same 19 files, +451/-74, and the two patches hash identical, sha256 ab858522...); the five main commits the merge brought in (#29948, #29950, #29940, #29949, #29947; 26 files, +1641/-111) and their file list against the scope and cited files; git grep -n revokeSessionsOnPasswordReset 38faba5a60 -- packages docs skills content against the same grep at d217dd5fe7; the step-18 ledgers and registry.ts.template at the new head; the four baselines at the new head; main's four new changesets; the PR record via REST (head sha, mergeable, body lines); the check-runs on the new head (one reading). The dev's dogfood claim on this head (415 files passed / 3 skipped, 3432 tests passed / 28 skipped, no failures, under the repo lock) is taken as the dev's claim, not re-measured.

① Derived judgments

  1. The merge is clean and adds nothing of its own. git diff d217dd5fe7 38faba5a60 restricted to the 19 PR-owned files is empty, and the PR's own patch (merge-base..d217dd5fe7) and the new net patch (origin/main..38faba5a60) hash identical. So every accept-set judgment in the full record (items 1 to 13) is a judgment of the same bytes and carries over unchanged. RIGHT.
  2. Nothing main brought in interacts with the retired key. The 26 files main changed are under packages/objectql, packages/metadata-protocol, packages/runtime, packages/plugins/plugin-security, ui/packages/app-shell, ui/packages/data-objectstack, scripts/pm, .claude/skills/pm-dispatch and four new changesets; none is under packages/spec, packages/plugins/plugin-auth, docs/, content/docs, or skills/. The grep of the key at the new head returns the same 59 hits as at the old head, every one of them the tombstone, the exported prescription, the plugin guard and its tests, the better-auth literal, generated references, the registry entries, the retirement pin, the anchor base.json, and released CHANGELOG text. No producer writes the key; card acceptance still holds. RIGHT.
  3. Step-18 migration ledgers: main added no file under packages/spec/src/migrations/ (entries or template), so the PR's 18.system__EmailAndPasswordConfig__revokeSessionsOnPasswordReset.ts retired-keys entry and 18.email-and-password-revoke-sessions-on-reset-retired.ts semantic entry have no sibling collision (544 semantic and 294 retired-keys files at the new head, the PR's ids unique among them). In registry.ts.template the STEP18_RATIONALE fragment keeps order: 118 (one occurrence, still highest + 1 since main added none) at its sorted gap, and the id list has no duplicate. Note, not a finding: many lower order numbers repeat (56, 60, 62, ... 112) on origin/main already; step18-rationale-merge.test.ts pins ids strictly unique and sorted, and order only as a positive integer with equal orders rendering in key order, so repeats are the test's designed shape for two retirements cut from one base, and they predate this PR. RIGHT.
  4. Authorable-surface and export baselines: main changed nothing under packages/spec/authorable-surface/, authorable-surface.base.json, api-surface/, export-origins/ or liveness/. At the new head the [RETIRED] row (authorable-surface/system.json:378), the REVOKE_SESSIONS_ON_PASSWORD_RESET_RETIRED (const) row (api-surface/system.json:559) and its origin (export-origins/system.json:537) are intact, and the anchor base.json row stays unannotated as the full record judged correct. The os-regen-merge script had nothing to regenerate for this PR's surfaces, which agrees with the empty PR-owned diff. RIGHT.
  5. ADR-0087 registration: main's four new changesets carry Clause-②: yes (widening) (runtime: a package export omits the objectExtensions of a package installed through install-local, so an exported .custom pair loses its extension fields on the second export hop #29872, @objectstack/spec not among its packages for this key) and three Clause-②: no; none carries an adr-0087: registered marker naming this PR's id, and at the new head the id email-and-password-revoke-sessions-on-reset-retired appears only in the PR's own files. The PR changeset is byte-identical to the one judged. Check Changeset, pending at the full record, has now concluded success on this head. RIGHT.
  6. PR record via REST: head sha is 38faba5a6070b5bccd419d6327ef406d1de80dc9, base main, mergeable true, body still carries Clause-②: no (narrowing) and Metadata-protocol: narrowed, agreeing with the unchanged changeset and diff. The PR is still in draft, which is the seat's land-time matter, not a contract finding.

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 level

Unchanged from the full record: the changeset, the PR body's Clause-②: no (narrowing) and Metadata-protocol: narrowed, and the diff they describe are byte-identical at the new head. @objectstack/spec: minor, @objectstack/plugin-auth: minor with the BREAKING banner remains the grade check-changeset-no-major.mjs names for a declared narrowing during the launch window, and Check Changeset has now concluded success on this head. Nothing main brought in touches either package's surface for this key. Level matches the diff.

③ Boundary flags

  • The one open boundary item in the full record (overlap on registry.ts.template noted for the seat's land-time rebase) is resolved by this head: the merge brought in no change to the template, so the fragment sits in its own gap with nothing beside it. Answered.
  • Dev report open_questions: [] carries over; the merge raises none.
  • The dev's dogfood reading on this head (415 files / 3432 tests, no failures, under the repo lock) is the only battery pinned to 38faba5a, since CI skips the matrix on pull_request; it is accepted as the dev's pinned claim, as the full record accepted the earlier one.
  • Nothing main brought in touches a Tier H surface relevant to this PR; the Governed Surface Queue Guard concluded success on the new head.

④ Design judgment

The four hits files are byte-identical between d217dd5fe7 and 38faba5a60, and the code each cites (shared/retired-key.ts, entries/README.md, step18-rationale-merge.test.ts, plugin-auth's guard and instance) is byte-identical too, since main changed none of it. The full record's per-file judgments and its narrowing-against-main judgment therefore carry over without re-trial: on main the key is still z.boolean().optional() and plugin-auth still accepts true; at the new head the key is unwritable and refused at any value; the better-auth option stays set; nothing the ruling keeps is removed.

Must-fix: none.
Should-fix: carried over unchanged, the D3 surface spelling in 18.email-and-password-revoke-sessions-on-reset-retired.ts:12 (auth emailAndPassword.revokeSessionsOnPasswordReset — ... rather than the plain dotted author path); readability of the printed guide only, nothing matches on it.

Implemented-by: claude/project-thread-dpz9e7
Reviewed-by: session_01WGufzhJG3tKnksHVa1krBz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 510b8ea1ec92520df9a460bcd5f78f78ffb4560f
Local-runs: none
Scope: packages/spec/src/migrations/entries/retired-keys/18.system__EmailAndPasswordConfig__revokeSessionsOnPasswordReset.ts, packages/spec/src/migrations/entries/semantic/18.email-and-password-revoke-sessions-on-reset-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/system/auth-config.zod.ts + packages/spec/src/migrations/entries/semantic/18.picklist-binding-unknown-refused.ts, packages/spec/src/migrations/entries/README.md, packages/spec/scripts/step18-rationale-merge.test.ts, packages/spec/authorable-surface/system.json, packages/spec/authorable-surface.base.json, packages/spec/api-surface/system.json, packages/spec/export-origins/system.json, .changeset/29898-picklist-binding-unknown-refused.md

Second delta review. The full record on d217dd5fe7 (comment 6612114259, PASS) and the delta on 38faba5a60 (comment 6613896572, PASS) carry over for everything this head does not change. The new head is one commit, Merge remote-tracking branch 'origin/main' into claude/project-thread-dpz9e7, parents 38faba5a60 (the reviewed head) and 18112bfa77 (origin/main); nothing else sits between them on the branch (first-parent walk). It was pushed because CI shards timed out in checkout, not for a code change.

Inputs read: the merge commit's parents and first-parent log; git diff 3b8c724c..38faba5a6 against git diff 18112bfa..510b8ea1ec (byte-identical); git diff 38faba5a6 510b8ea1ec against git diff 3b8c724c 18112bfa (same sha256, so the merge adds nothing of its own); the merge diff restricted to the 19 PR-owned files (empty); main's six commits (#29956, #29955, #29966, #29958, #29967, #29965; 39 files) and their file list against the scope and baselines; main's new step-18 entry and the PR's two step-18 entries at the new head; registry.ts.template at the new head; the step-18 test's constraints; the four baselines; git grep -n revokeSessionsOnPasswordReset at both heads; the PR record via REST; the check-runs on the new head (one reading). The dev's dogfood claim on this head (415 files passed / 3 skipped, 3432 tests passed / 28 skipped, no failures) is taken as the dev's claim, not re-measured.

① Derived judgments

  1. The PR patch is unchanged: 3b8c724c..38faba5a6 and 18112bfa..510b8ea1ec are byte-identical, and the merge diff on the 19 PR-owned files is empty. Every judgment in the two earlier records is a judgment of the same bytes and carries over. RIGHT.
  2. Main's only file under packages/spec/src/ is the new semantic entry 18.picklist-binding-unknown-refused.ts (fix(metadata-protocol)!: the metadata save and publish doors refuse a select field bound to a picklist no loaded package declares #29956). It is a SemanticMigration with id picklist-binding-unknown-refused, a different file name and id from the PR's email-and-password-revoke-sessions-on-reset-retired; the directory is the index and order derives from the id (entries/README.md), so the two coexist without a merge point. 545 semantic and 294 retired-keys files at the new head, no duplicate id among the step-18 semantic entries. Main added no retired-keys entry, so the generated RETIRED_KEYS_BY_MAJOR[18] row the PR's entry feeds gains nothing beside it. RIGHT.
  3. registry.ts.template is untouched by main (empty diff on both sides of the merge). The PR's STEP18_RATIONALE fragment keeps order: 118, still the highest and unique; no duplicate ids in the template. Main's picklist entry added no rationale fragment, so no gap collision. RIGHT.
  4. The four baselines (authorable-surface/, authorable-surface.base.json, api-surface/, export-origins/) are untouched by main; at the new head the [RETIRED] row (authorable-surface/system.json:378), the base.json anchor row (:5598), the api-surface const row and its export origin are intact as the full record judged them. RIGHT.
  5. ADR-0087: main's changeset for fix(metadata-protocol)!: the metadata save and publish doors refuse a select field bound to a picklist no loaded package declares #29956 carries Clause-②: no (narrowing), Metadata-protocol: narrowed and adr-0087: registered picklist-binding-unknown-refused, a different id from this PR's; main's other four changesets are Clause-②: no. No marker names this PR's id, and the PR's own changeset is byte-identical to the one judged. RIGHT.
  6. git grep -n revokeSessionsOnPasswordReset 510b8ea1ec -- packages docs skills content returns the same 59 hits as at 38faba5a6, line for line; no new producer of the key. Card acceptance still holds. RIGHT.
  7. PR record via REST: head 510b8ea1ec..., base main, mergeable true, body still Clause-②: no (narrowing) and Metadata-protocol: narrowed. Still draft, which is the seat's land-time matter.

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 level

Unchanged: @objectstack/spec: minor, @objectstack/plugin-auth: minor with the BREAKING banner for a declared narrowing, as the changeset, PR body and diff all still say; Check Changeset is green on this head. Main's #29956 grades its own packages (metadata-protocol, spec, cli minor) for its own narrowing and does not touch this key. Level matches the diff.

③ Boundary flags

  • No overlap: main touched none of the PR's 19 files, the template, the baselines, plugin-auth, docs, skills or content. Nothing open.
  • Dev report open_questions: [] carries over; the merge raises none.
  • The dev's dogfood reading on this head is the only full battery pinned to it (CI skips the matrix on pull_request); accepted as the dev's pinned claim, as before.
  • Governed Surface Queue Guard concluded success on the new head.

④ Design judgment

The four hits files and the code they cite (shared/retired-key.ts, entries/README.md, step18-rationale-merge.test.ts, plugin-auth's guard and instance) are byte-identical between 38faba5a60 and 510b8ea1ec; main changed none of them. The earlier per-file judgments and the narrowing-against-main judgment carry over without re-trial: on main the key is still z.boolean().optional(); at the new head it is unwritable and refused at any value, the better-auth option stays set, and nothing the ruling keeps is removed.

Must-fix: none.
Should-fix: carried over unchanged, the D3 surface spelling in 18.email-and-password-revoke-sessions-on-reset-retired.ts:12 (auth emailAndPassword.revokeSessionsOnPasswordReset — ... rather than the plain dotted author path); readability only, nothing matches on it.

Implemented-by: claude/project-thread-dpz9e7
Reviewed-by: session_01WGufzhJG3tKnksHVa1krBz

VERDICT: PASS

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(system): retire EmailAndPasswordConfig.revokeSessionsOnPasswordReset — a completed password reset always ends the account's sessions

1 participant