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
Forgot password says the reset link was sent on a deployment with no email service #30107
Found in a real-user walkthrough of the development flow, asked for by the maintainer: 「你站在真实用户实际使用的角度完整的走一遍具体的开发流程,再看一下还有什么问题」 (2026-11-04).
What a customer sees
A fresh self-hosted deployment has no email service until someone configures one. A member who forgets their password enters their address and reads 「我们已将密码重置链接发送至 colleague@example.test,请检查您的收件箱。」 No mail is sent, and they wait for a message that never comes. The invitation path on the same server tells the truth: 「此服务器未配置邮件,因此没有发出任何邮件。」
Measured
Step
Result
Where
Submit the forgot password form with no email service
The success screen above
Published 18.3.0, os start on a fresh database
POST /api/v1/auth/request-password-reset
200, {"status":true,"message":"If this email exists in our system, check your email for the reset link"}
main63b86b385d
Server side with no transport
Throws Password-reset email could not be sent…, so it reaches the log only
Names no email delivery state, so the page cannot tell
main63b86b385d
The console on main was not run in this check, because the review worktree has no console build. The form and the string were read at that commit.
Expected
On a deployment with no email transport the page says the reset cannot be sent here and to ask an administrator, who can already set a password from Setup → Users. That statement is about the deployment and not about the address, so the no-account-existence-oracle rule at ui/packages/auth/src/ForgotPasswordForm.tsx:221 still holds: known and unknown addresses read the same.
With a transport configured the behavior stays as it is. The English default also says "Check your email", so the fix covers every locale.
Read fresh. On a deployment with no email transport, forgot-password still says the reset link was sent. The server only logs the failure, and /auth/config exposes no delivery state the page could read. p2: a misleading message, and an admin can already set a password.
Platform first, so domain:server: expose the deployment's email-delivery state. The page text (ui/packages/i18n) is the domain:ui half. The statement is about the deployment, not the address, so the no-account-existence-oracle rule still holds. The PR says so.
Claim: PM loop round 4
Session: session_01Utp6Cbvcmv8YWFhFJwzjKL (seat) · dev thread session_01Utp6CbSX6BdpVNjS79LB5A
Account: os-bill
Branch: claude/project-thread-xry5up (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30107
Domain: domain:server
Seat: domain:server#2
File surface: packages/plugins/plugin-auth/src/ (the password-reset route's answer or getPublicConfig, tests), possibly packages/spec/src/api/auth-endpoints.zod.ts + its test (one optional member, only if the /auth/config carrier is chosen), one changeset .changeset/30107-reset-without-email.md. ⛔ ui/**, apps/**, skills/**, docs/adr/**, content/docs/**, .claude/**. (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default tier (opus) — dispatch-gates --tier: no path-derived mandate, effort: high
Clause-②: yes (widening) expected — a new optional published member telling the page whether mail leaves this deployment; no if the carrier turns out unpublished; the dev re-declares on measurement
Metadata-protocol: none
Responsibility: POST /api/v1/auth/request-password-reset answers the same success with no email transport, and /auth/config names no delivery state, so the page says the link was sent | the console copy (domain:ui half, filed by the seat) | every member who forgets their password on a deployment with no email service
Thread-read: 6633840753
Premise: holds — at cfb602c4 the no-transport reset throws Password-reset email could not be sent… (auth-manager-auth-instance.ts ~:470) into the log only; AuthManager.isEmailDeliveryLive() already decides delivery for invite-member's outcome (invitation-delivery-outcome.ts)
Serial constraints cleared: this seat's #30201 (PR #30293), #30202 (PR #30298) and #30203 (PR #30309) edit auth-plugin.tsapplySettings and the settings write predicates; this card keeps out of both and pulls main before PR and before landing. Sibling seats: seat 3 #30300 (sign-in lockout via provider delete, after PR #30298), seat 4 #30304 (objectql registry) — disjoint paths.
Order: project file server-seat2/dispatch-30107.md, delivered as the card thread's opening message.
{
"issue": 30107,
"status": "done",
"branch": "claude/project-thread-xry5up",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30365",
"session": "session_01Utp6CbSX6BdpVNjS79LB5A",
"premise_still_valid": true,
"summary": "GET /api/v1/auth/config now serves features.emailDelivery from AuthManager.isEmailDeliveryLive() (the wired email service's own isConfigured(), read per request): false with no email service and with the stock log transport, true with a configured one. Carrier (a) chosen by measurement: features.phoneNumberOtp is the exact precedent on the same forgot-password form, the page reads the config before submit, and the console's forgotPassword() discards the reset answer, so an answer member (b) would need more console plumbing. The reset answer is unchanged and identical for known and unknown addresses in all three transport states (pinned). Spec: AuthFeaturesConfigSchema member + PUBLIC_AUTH_FEATURES entry (login, opt-in, exempt), which also adds requiresFeature: 'emailDelivery'.",
"tests": "head f78457d8: spec test 813 files / 25579 passed | 1 todo; plugin-auth test 220 files / 3879 passed | 10 skipped; platform-objects feature-gate-guard 112 passed; spec + plugin-auth typecheck exit 0. New pins: 3 cases in plugin-auth src/reset-request-mail-after-response.test.ts (no service / log transport / live; known == unknown answer each). Ablation A (served isEmailServiceAvailable()): 1 failed | 11 passed, the log-transport case 'expected true to be false'. Ablation B (member deleted): 5 failed | 12 passed over the harness + registry drift guard. Both restored, blob 5bc1cba772b0 == HEAD blob; tests import ./auth-manager.js from source, no dist step.",
"gates": "dispatch-gates --commands derived 106 at f78457d8; --ran: 106 accounted, 105 run, 1 NOT-MEASURED (check:dual-build-cjs-loads exit 3, needs whole-workspace build; CI). check-plugin-teardown-shape --self-test exit 0 after git fetch 621a4876; check:skill-examples exit 0 after building client + client-react. pnpm lint exit 0. check:generated --fix regenerated content/docs/references (auth-endpoints, action). PR check-runs: in_progress at report time.",
"dogfood_battery": "n/a — no ! in the PR title and the Clause-② arm is (widening)",
"line_budget": "11 files, +101/-6 vs origin/main: plugin-auth auth-manager.ts +11/-1, reset harness test +46/-1, auth-manager.test.ts +2; spec zod +5, registry +13, tests +5/-1 and +1; generated authorable-surface/api.json +1, reference pages +5/-3; changeset +13",
"files_changed": [
".changeset/30107-reset-without-email.md",
"packages/plugins/plugin-auth/src/auth-manager.ts",
"packages/plugins/plugin-auth/src/auth-manager.test.ts",
"packages/plugins/plugin-auth/src/reset-request-mail-after-response.test.ts",
"packages/spec/src/api/auth-endpoints.zod.ts",
"packages/spec/src/api/auth-endpoints.test.ts",
"packages/spec/src/kernel/public-auth-features.ts",
"packages/spec/src/kernel/public-auth-features.test.ts",
"packages/spec/authorable-surface/api.json",
"content/docs/references/api/auth-endpoints.mdx",
"content/docs/references/ui/action.mdx"
],
"deviations": [
"Metadata-protocol declared added, not the Claim's none: the registry entry widens requiresFeature's enum (an accepted authorable value) and the PR moves packages/spec/authorable-surface/api.json, which the level axis refuses under none (none-baseline-moved). Changeset: @objectstack/spec minor, @objectstack/plugin-auth patch.",
"Outside the order's file surface, all forced: packages/spec/src/kernel/public-auth-features.ts (+ its test), required by the plugin-auth drift guard for every served boolean flag; packages/spec/authorable-surface/api.json and content/docs/references/** (two pages), generator output from the spec build and check:generated --fix, not hand edits.",
"Merge commit f78457d8 (origin/main into the branch) carries git's default message."
],
"mcp_calls": "0",
"api_writes": "2 — relay pr_create (repository_dispatch, run 41991762696, PR 30365); this post-stamped comment on 30107. Plus git push (not REST).",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: public door — the console forgot-password page (ui/packages/auth/src/ForgotPasswordForm.tsx successDescription, ui/packages/i18n) still says the reset link was sent on a deployment with no mail; it should read features.emailDelivery like phoneNumberOtp and, when not true, say a link cannot be emailed here and to ask an administrator, same for every address · evidence: card 30107 measured table · dedupe words: forgot password, emailDelivery, successDescription, reset link sent, no email service · carrier: seat (domain:ui half, lane off duty)",
"class: none · POST /magic-link/send answers success with no mail, but magic link is unadvertised and has no console UI; callers can read features.emailDelivery · carrier: none, noted in Acceptance notes",
"class: none · visitor verification-email resend already refuses 400 EMAIL_SERVICE_REQUIRED without a live transport; invite-member already answers outcome link_only · carrier: none"
]
}
Claim correction (supersedes the Metadata-protocol line and widens the file surface of claim 6651778079; every other line stands)
Metadata-protocol: added — measured by the dev (report 6652972753): the new flag joins PUBLIC_AUTH_FEATURES, so requiresFeature on an action accepts one more authorable value ('emailDelivery'), and packages/spec/authorable-surface/api.json moves; none is refused by the level axis. Changeset minor on @objectstack/spec, patch on @objectstack/plugin-auth. Review owed: CONTRACT_REVIEW_TIER (schema face).
File surface (added): packages/spec/src/kernel/public-auth-features.ts + its test (the plugin-auth drift guard requires every public auth flag to be classified there); generated packages/spec/authorable-surface/api.json, content/docs/references/api/auth-endpoints.mdx and content/docs/references/ui/action.mdx (generator output from check:generated --fix, not hand edits). Accepted by the seat, subject to the contract review. The ui half is filed as #30366.
Activity
objectstack-fleet commentedon Nov 4, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:server·pm:queue· 2026-11-04T14:54Z⛔ Not a claim.
/auth/configexposes no delivery state the page could read. p2: a misleading message, and an admin can already set a password.domain:server: expose the deployment's email-delivery state. The page text (ui/packages/i18n) is thedomain:uihalf. The statement is about the deployment, not the address, so the no-account-existence-oracle rule still holds. The PR says so.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Claim: PM loop round 4
Session:
session_01Utp6Cbvcmv8YWFhFJwzjKL(seat) · dev threadsession_01Utp6CbSX6BdpVNjS79LB5AAccount:
os-billBranch:
claude/project-thread-xry5up(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30107Domain:
domain:serverSeat:
domain:server#2File surface:
packages/plugins/plugin-auth/src/(the password-reset route's answer orgetPublicConfig, tests), possiblypackages/spec/src/api/auth-endpoints.zod.ts+ its test (one optional member, only if the/auth/configcarrier is chosen), one changeset.changeset/30107-reset-without-email.md. ⛔ui/**,apps/**,skills/**,docs/adr/**,content/docs/**,.claude/**. (stop on breach; explain in the report)Container & model:
M,mode:thread,model: default tier (opus) — dispatch-gates --tier: no path-derived mandate,effort: highClause-②: yes (widening) expected — a new optional published member telling the page whether mail leaves this deployment;
noif the carrier turns out unpublished; the dev re-declares on measurementMetadata-protocol: none
Responsibility:
POST /api/v1/auth/request-password-resetanswers the same success with no email transport, and/auth/confignames no delivery state, so the page says the link was sent | the console copy (domain:uihalf, filed by the seat) | every member who forgets their password on a deployment with no email serviceThread-read: 6633840753
Premise: holds — at
cfb602c4the no-transport reset throwsPassword-reset email could not be sent…(auth-manager-auth-instance.ts~:470) into the log only;AuthManager.isEmailDeliveryLive()already decides delivery for invite-member'soutcome(invitation-delivery-outcome.ts)Serial constraints cleared: this seat's #30201 (PR #30293), #30202 (PR #30298) and #30203 (PR #30309) edit
auth-plugin.tsapplySettingsand the settings write predicates; this card keeps out of both and pullsmainbefore PR and before landing. Sibling seats: seat 3 #30300 (sign-in lockout via provider delete, after PR #30298), seat 4 #30304 (objectql registry) — disjoint paths.Order: project file
server-seat2/dispatch-30107.md, delivered as the card thread's opening message.objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6CbSX6BdpVNjS79LB5A
os-dev-report
{ "issue": 30107, "status": "done", "branch": "claude/project-thread-xry5up", "pr": "https://github.com/objectstack-ai/objectstack/pull/30365", "session": "session_01Utp6CbSX6BdpVNjS79LB5A", "premise_still_valid": true, "summary": "GET /api/v1/auth/config now serves features.emailDelivery from AuthManager.isEmailDeliveryLive() (the wired email service's own isConfigured(), read per request): false with no email service and with the stock log transport, true with a configured one. Carrier (a) chosen by measurement: features.phoneNumberOtp is the exact precedent on the same forgot-password form, the page reads the config before submit, and the console's forgotPassword() discards the reset answer, so an answer member (b) would need more console plumbing. The reset answer is unchanged and identical for known and unknown addresses in all three transport states (pinned). Spec: AuthFeaturesConfigSchema member + PUBLIC_AUTH_FEATURES entry (login, opt-in, exempt), which also adds requiresFeature: 'emailDelivery'.", "tests": "head f78457d8: spec test 813 files / 25579 passed | 1 todo; plugin-auth test 220 files / 3879 passed | 10 skipped; platform-objects feature-gate-guard 112 passed; spec + plugin-auth typecheck exit 0. New pins: 3 cases in plugin-auth src/reset-request-mail-after-response.test.ts (no service / log transport / live; known == unknown answer each). Ablation A (served isEmailServiceAvailable()): 1 failed | 11 passed, the log-transport case 'expected true to be false'. Ablation B (member deleted): 5 failed | 12 passed over the harness + registry drift guard. Both restored, blob 5bc1cba772b0 == HEAD blob; tests import ./auth-manager.js from source, no dist step.", "gates": "dispatch-gates --commands derived 106 at f78457d8; --ran: 106 accounted, 105 run, 1 NOT-MEASURED (check:dual-build-cjs-loads exit 3, needs whole-workspace build; CI). check-plugin-teardown-shape --self-test exit 0 after git fetch 621a4876; check:skill-examples exit 0 after building client + client-react. pnpm lint exit 0. check:generated --fix regenerated content/docs/references (auth-endpoints, action). PR check-runs: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and the Clause-② arm is (widening)", "line_budget": "11 files, +101/-6 vs origin/main: plugin-auth auth-manager.ts +11/-1, reset harness test +46/-1, auth-manager.test.ts +2; spec zod +5, registry +13, tests +5/-1 and +1; generated authorable-surface/api.json +1, reference pages +5/-3; changeset +13", "files_changed": [ ".changeset/30107-reset-without-email.md", "packages/plugins/plugin-auth/src/auth-manager.ts", "packages/plugins/plugin-auth/src/auth-manager.test.ts", "packages/plugins/plugin-auth/src/reset-request-mail-after-response.test.ts", "packages/spec/src/api/auth-endpoints.zod.ts", "packages/spec/src/api/auth-endpoints.test.ts", "packages/spec/src/kernel/public-auth-features.ts", "packages/spec/src/kernel/public-auth-features.test.ts", "packages/spec/authorable-surface/api.json", "content/docs/references/api/auth-endpoints.mdx", "content/docs/references/ui/action.mdx" ], "deviations": [ "Metadata-protocol declared added, not the Claim's none: the registry entry widens requiresFeature's enum (an accepted authorable value) and the PR moves packages/spec/authorable-surface/api.json, which the level axis refuses under none (none-baseline-moved). Changeset: @objectstack/spec minor, @objectstack/plugin-auth patch.", "Outside the order's file surface, all forced: packages/spec/src/kernel/public-auth-features.ts (+ its test), required by the plugin-auth drift guard for every served boolean flag; packages/spec/authorable-surface/api.json and content/docs/references/** (two pages), generator output from the spec build and check:generated --fix, not hand edits.", "Merge commit f78457d8 (origin/main into the branch) carries git's default message." ], "mcp_calls": "0", "api_writes": "2 — relay pr_create (repository_dispatch, run 41991762696, PR 30365); this post-stamped comment on 30107. Plus git push (not REST).", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: public door — the console forgot-password page (ui/packages/auth/src/ForgotPasswordForm.tsx successDescription, ui/packages/i18n) still says the reset link was sent on a deployment with no mail; it should read features.emailDelivery like phoneNumberOtp and, when not true, say a link cannot be emailed here and to ask an administrator, same for every address · evidence: card 30107 measured table · dedupe words: forgot password, emailDelivery, successDescription, reset link sent, no email service · carrier: seat (domain:ui half, lane off duty)", "class: none · POST /magic-link/send answers success with no mail, but magic link is unadvertised and has no console UI; callers can read features.emailDelivery · carrier: none, noted in Acceptance notes", "class: none · visitor verification-email resend already refuses 400 EMAIL_SERVICE_REQUIRED without a live transport; invite-member already answers outcome link_only · carrier: none" ] }Report taken 2026-11-05T08:05Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Claim correction (supersedes the Metadata-protocol line and widens the file surface of claim 6651778079; every other line stands)
Metadata-protocol: added — measured by the dev (report 6652972753): the new flag joins
PUBLIC_AUTH_FEATURES, sorequiresFeatureon an action accepts one more authorable value ('emailDelivery'), andpackages/spec/authorable-surface/api.jsonmoves;noneis refused by the level axis. Changesetminoron@objectstack/spec,patchon@objectstack/plugin-auth. Review owed:CONTRACT_REVIEW_TIER(schema face).File surface (added):
packages/spec/src/kernel/public-auth-features.ts+ its test (the plugin-auth drift guard requires every public auth flag to be classified there); generatedpackages/spec/authorable-surface/api.json,content/docs/references/api/auth-endpoints.mdxandcontent/docs/references/ui/action.mdx(generator output fromcheck:generated --fix, not hand edits). Accepted by the seat, subject to the contract review. The ui half is filed as #30366.