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
Authentication settings: turning off email and password with no other sign-in method saves without warning, and everyone is locked out #30202
From a real-user walkthrough of Setup, filed on the maintainer's word: 「以上都同意,立卡」 (2026-11-05). Measured on mainb7075d9908, with the console in dev mode, unless a line says otherwise. Test app scaffolded with [email protected].
What a customer sees
An administrator plans to move to single sign-on and turns off 启用邮箱/密码登录 before SSO is configured. It saves. As soon as people sign out, no one can sign back in, the administrator included, and the fix needs an environment variable or a database edit.
Measured
Setup → Settings → Authentication, switch off, 保存更改: toast 「设置已保存」, no warning or confirmation. socialProviders is empty and SSO is off.
POST /api/v1/auth/sign-in/email: 400 EMAIL_PASSWORD_DISABLED.
GET /api/v1/auth/config: emailPassword.enabled: false.
The save is refused while it would leave no sign-in method, and names what to configure first. When email and password really are off, the login page does not offer the form.
Read fresh. Turning off email and password sign-in with no other sign-in method configured saves with no warning. Once people sign out, nobody can sign back in, the administrator included, and recovery needs an environment change. A whole-tenant lockout from one Setup toggle, so p1.
Fix site: the auth settings save door (plugin-auth) refuses a configuration with no working sign-in method. The Setup confirmation is the domain:ui half.
Claim: PM loop round 3
Session: session_01Utp6Cbvcmv8YWFhFJwzjKL (seat) · dev thread session_01Utp6CXhZcHuALPAjkDCiiT
Account: os-bill
Branch: claude/project-thread-bg54o3 (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30202
Domain: domain:server
Seat: domain:server#2
File surface: packages/plugins/plugin-auth/src/ (the auth settings apply / save door, tests), possibly manifest.ts (help text), one changeset .changeset/30202-keep-a-sign-in-method.md. ⛔ packages/spec/**, ui/**, apps/**, skills/**, docs/adr/**, .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: xhigh (p1)
Clause-②: no — a refused save / gate fail-safe on an existing door; the dev re-declares yes for a new published code, export or settings key, or no (narrowing) if a value an admin could save before is now refused (then CONTRACT_REVIEW_TIER review)
Metadata-protocol: none
Responsibility: the auth settings apply (auth-plugin.ts, email_password_enabled) persists email/password off with no other sign-in method | no other door checks that a sign-in method remains | every user of the deployment, the administrator included, once they sign out
Thread-read: 6648434001
Premise: holds — at 5fa9375eemail_password_enabled is applied through emailAndPasswordField(...) with no check of social providers or SSO; the same apply already has a refusable path (mfa_required) and a refusal-isolation test
Serial constraints cleared: #30201 (this seat) may edit auth-plugin.ts near mfa_required and the session-policy gate; disjoint hunks, each pulls main before PR and before landing. No other in-flight domain:server claim names plugin-auth (#29997 closed; sibling seats' cards read by title/surface)
Order: project file server-seat2/dispatch-30202.md, delivered as the card thread's opening message.
{
"issue": 30202,
"status": "done",
"branch": "claude/project-thread-bg54o3",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30298",
"session": "session_01Utp6CXhZcHuALPAjkDCiiT",
"premise_still_valid": true,
"summary": "Saving the auth settings now refuses, at the settings save door and before anything is written, turning email and password off while no other way in works, and (with email and password already off) the Google change that removes the last provider. It is one write predicate (SettingsService.registerWritePredicate, the ADR-0144 D4 seam) registered from AuthPlugin.bindAuthSettings on auth.email_password_enabled and auth.google_enabled / google_client_id / google_client_secret, answering the existing 400 SETTINGS_VALIDATION invalid_value field error with constraint.lastSignInMethod and a message in en / zh-CN / ja-JP / es-ES naming what to set up first. 'Another way in' = a social or OIDC provider in force; enterprise SSO switched on AND a sys_sso_provider row; phone-number sign-in; a declared hostSignInHandoff (ssoOnlyMode alone, magic link and passkeys do not count). Recovery for an already locked deployment is the existing OS_AUTH_EMAIL_PASSWORD_ENABLED=true override plus a restart, named in the changeset and pinned; no new variable.",
"tests": "sign-in-method-floor.test.ts (new, 11 cases, real SettingsService + AuthPlugin + AuthManager): 11/11, re-run on 249ec4c4 (main 4be97342 merged). Whole plugin-auth suite (vitest --maxWorkers=2): Test Files 221 passed, Tests 3887 passed / 10 skipped, on deadfe9f and on 1474c9ec (main 959c26be merged). plugin-auth typecheck (both tsconfigs + check:test-typecheck): exit 0 on the merged head after rebuilding @objectstack/core's dist (stale soleWriter d.ts from main's DispatchLoop change). pnpm lint: exit 0. Ablations through scripts/ablation-replace.mjs, each anchor 1 -> 0 and restored to the HEAD blob with git diff HEAD empty (tests import src; service-settings aliased to src, so no dist leg): A email/password refusal returns nothing -> 3 red; B Google refusal returns nothing -> 1 red; C SSO switch counts without a provider row -> 1 red.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands: 72 derived families on db95d0dc, each exit code recorded before any pipe (/mnt/project-files/server-seat2/gates-30202-ran.txt): 72 exit 0; --ran: 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN. First pass had two exit 3 for prerequisites (check:dual-build-cjs-loads: seven packages without dist, built; check-plugin-teardown-shape --self-test: git fetch of 621a4876), both then 0. The 18 families the changeset path derives re-run on deadfe9f (changeset-only commit): all 0. On the merged head the derived list is the same 72 (sorted diff empty); check:engine-double-contract re-run: 0. CI check-runs on PR #30298 (head 249ec4c4): not yet read at report time; the seat thread hears when they settle.",
"dogfood_battery": "db95d0dc · os-verify-lock VERDICT command-exit 1 (held 1933s) · Test Files 418 passed / 1 failed / 3 skipped (422) · Tests 3491 passed / 1 failed / 36 skipped (3528) · failure: permission-set-clone-door.dogfood.test.ts > 'a platform set (the lock-and-clone rule's commonest source) copies into its shipping package's pair', 422 INVALID_METADATA on member_default's sys_comment / sys_activity grants. Control: registerSignInMethodFloor( neutralized (--hold, plugin-auth rebuilt, ablation-dist-preflight marker present in 2 built files) -> same file red the same way (1 failed / 10 passed); restored, rebuilt, --absent confirmed. It is main red #30255 (from #30222), reverted on main by #30262; after merging 4be97342 that file passes (11/11, VERDICT command-exit 0). Full battery not re-run after the merges.",
"line_budget": "5 files, +641 / -0 against origin/main: sign-in-method-floor.ts +262, sign-in-method-floor.test.ts +333, auth-plugin.ts +22, .changeset/30202-keep-a-sign-in-method.md +19, scripts/engine-double-contract.pinned.json +5",
"files_changed": [
"packages/plugins/plugin-auth/src/sign-in-method-floor.ts",
"packages/plugins/plugin-auth/src/sign-in-method-floor.test.ts",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
".changeset/30202-keep-a-sign-in-method.md",
"scripts/engine-double-contract.pinned.json"
],
"deviations": [
"scripts/engine-double-contract.pinned.json is outside the claimed file surface: one ledger row for the new test's findOne double, written by check-engine-double-contract.mjs --write, which the gate requires for a new test file with an engine double.",
"The refusal message names what to configure, not the recovery variable: it speaks to the administrator in Setup, who is still signed in. OS_AUTH_EMAIL_PASSWORD_ENABLED is named in the changeset and the PR body.",
"The harness's assign / review-request step was skipped, as the order says.",
"After the changeset-only commit deadfe9f, only the 18 changeset-derived families were re-run, not all 72."
],
"mcp_calls": "5 — all reads: issue_read get #30202, issue_read get_comments #30202 (x2), issue_read get #29159, pull_request_read get #30298 (draft true, no assignees, body as sent). No MCP write.",
"api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls) and post-stamped (POST /repos/objectstack-ai/objectstack/issues/30202/comments); plus git push (not REST). Write budget: 3 of 3.",
"open_questions": [
{
"question": "A provider filled in and email and password turned off in the same save is refused (the predicate judges one key against the running config). Is two saves acceptable?",
"options": [
"A keep two saves; the message says to save the other method first",
"B judge the batch as a whole, which needs the batch on SettingsWritePredicateInput (a service-settings contract change, its own card)"
],
"recommendation": "A, because it refuses nothing that leaves a way in once the method is saved, and B widens a shared contract beyond this card."
}
],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: Setup → Settings → Authentication shows no confirmation before turning off the last sign-in method, and shows the refusal only as the field error the console extractor reads; the card also saw the login page draw the email/password form under os dev with better-auth's English 'Email and password is not enabled' (LoginForm.tsx:351 has the no-method state at 797f9257, so what remains is what the console dev server draws there; not reproduced) · evidence: card #30202 body, ui/packages/auth/src/LoginForm.tsx · dedupe words: login page no sign-in method, setup auth confirmation, email password disabled form",
"carrier: seat files to domain:ui (lane off duty) · noted, not filed",
"class: b · reach: other doors to the same lockout that are not settings saves: deleting the last sys_sso_provider row, or a redeploy that drops the last stack-config provider or plugins.phoneNumber, while email and password is off · evidence: read, not measured · dedupe words: last sso provider delete lockout, redeploy drops provider lockout",
"carrier: seat · noted, not filed"
]
}
Claim correction (supersedes the Clause-② line and widens the file surface of claim 6648660209; every other line stands)
Clause-②: no (narrowing) — measured by the dev: the auth settings save door refuses, as a whole batch (400 SETTINGS_VALIDATION), a save that turns off the last sign-in method (email and password with no other method, or the last Google provider while email and password are off), a value an admin could save before. Recovery for a deployment already locked: the existing OS_AUTH_EMAIL_PASSWORD_ENABLED=true and a restart. Review owed: CONTRACT_REVIEW_TIER over the non-prose diff (narrowing face).
File surface (added): scripts/engine-double-contract.pinned.json (one row the gate requires for the new module). Accepted by the seat, subject to the contract review.
{
"issue": 30202,
"status": "done",
"branch": "claude/project-thread-bg54o3",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30298",
"session": "session_01Utp6CXhZcHuALPAjkDCiiT",
"premise_still_valid": true,
"summary": "Rework after contract review 6650756385 (seat ruling B, same PR). service-settings: SettingsWritePredicateInput gains optional batch (every key the write sets, frozen, null = clear), built once in refuseByWritePredicates and passed to every predicate. plugin-auth: the sign-in floor judges the state after the whole save, so { email_password_enabled: false, google_enabled: false } (or the Google secret cleared) on email+password+Google is refused at both keys, and Google filled in, or switched on over a stored pair, in the same save as email and password off is accepted. Should-fix: a social or OIDC provider counts only when both clientId and clientSecret are non-blank in the boot configuration it came from (providerCredentialIndex); Google added from the settings has a pair by construction. The one-key two-save limitation of the first head is gone.",
"tests": "sign-in-method-floor.test.ts 17/17 (6 new: both-keys refusal x2 batches, Google filled in the same save, switch over a stored pair, half a pair refused, blank-secret stack provider, providerCredentialIndex). settings-write-predicate.test.ts + localization-currency-lock.test.ts 33/33 (2 new: batch handed frozen with a clear as null; expectTypeOf on SettingsWritePredicateInput['batch'] imported from ./index.js). Whole packages under the lock: plugin-auth 221 files, 3893 passed / 10 skipped; service-settings 67 files, 1153 passed. typecheck (service-settings, plugin-auth incl. check:test-typecheck) exit 0; built dist/index.d.ts of service-settings carries batch?: Readonly<Record<string, unknown>>. pnpm lint exit 0. Ablations, each anchor 1 -> 0 and restored to the HEAD blob: D no batch passed -> ss 1 red + auth 3 red; E floor ignores batch -> 3 red; F provider counts without creds -> 1 red; G unset key not read as stored -> 2 red; A email refusal void -> 6 red; B Google refusal void -> 2 red; C SSO switch without row -> 1 red.",
"gates": "dispatch-gates --commands on 0b89bdf1: 73 derived (72 + check:package-naming from the service-settings path), run after a full turbo build, exit codes recorded before any pipe (/mnt/project-files/server-seat2/gates-30202-ran-v2.txt): 73 exit 0; --ran: 73 derived, 73 run, 0 NOT-MEASURED, 0 UNRUN. CI check-runs on the new head: not yet read at report time.",
"dogfood_battery": "0b89bdf1 · os-verify-lock VERDICT command-exit 1 (held 2157s, gate loop running beside it) · Test Files 418 passed / 1 failed / 3 skipped (422) · Tests 3488 passed / 4 failed / 36 skipped (3528) · failures: package-commit-revert-path-package.dogfood.test.ts #29390 and #29414 x3, each 'Test timed out in 5000ms'; the file alone under the lock on the same head: 12/12, VERDICT command-exit 0.",
"line_budget": "8 files, +886 / -3 against origin/main: sign-in-method-floor.ts +376, sign-in-method-floor.test.ts +418, auth-plugin.ts +28, settings-service.ts +8/-1, settings-service.types.ts +9, settings-write-predicate.test.ts +20/-2, changeset +22, scripts/engine-double-contract.pinned.json +5",
"files_changed": [
"packages/plugins/plugin-auth/src/sign-in-method-floor.ts",
"packages/plugins/plugin-auth/src/sign-in-method-floor.test.ts",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/services/service-settings/src/settings-service.ts",
"packages/services/service-settings/src/settings-service.types.ts",
"packages/services/service-settings/src/settings-write-predicate.test.ts",
".changeset/30202-keep-a-sign-in-method.md",
"scripts/engine-double-contract.pinned.json"
],
"deviations": [
"packages/services/service-settings/** is outside the dispatch's file surface; the seat's ruling B asked for it.",
"scripts/engine-double-contract.pinned.json: one ledger row (accepted by the seat).",
"The PR body is not rewritten (a dev writes it once, at pr_create). The full corrected body is at /mnt/project-files/server-seat2/pr-30202-body-v2.md for the seat to write: Clause-② yes (narrowing), 维护者速读, What changes, Tests, Acceptance notes and Verification all change.",
"Pushed on main 977be380; main has 3 commits since that touch none of the changed paths (trial merge clean), not merged again.",
"The harness's assign / review-request step was skipped, as the order says."
],
"mcp_calls": "5 in this thread, all reads, none since the first report: issue_read get #30202, issue_read get_comments #30202 (x2), issue_read get #29159, pull_request_read get #30298. No MCP write.",
"api_writes": "this round 1 — post-stamped (POST /repos/objectstack-ai/objectstack/issues/30202/comments), plus git push (not REST). Whole card: relay pr_create and 2 report comments, plus git pushes.",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: Setup → Settings → Authentication shows no confirmation before turning off the last sign-in method, and the refusal only as the field error; the login page under os dev was seen drawing the email/password form with better-auth's English error (not reproduced) · evidence: card #30202, ui/packages/auth/src/LoginForm.tsx · dedupe words: login page no sign-in method, setup auth confirmation",
"carrier: seat (filing) · noted, not filed",
"class: b · reach: lockout doors that are not settings saves: deleting the last sys_sso_provider row, a redeploy dropping the last provider or plugins.phoneNumber, while email and password is off · evidence: read, not measured · dedupe words: last sso provider delete lockout",
"carrier: seat (filing) · noted, not filed"
]
}
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p1 ·
domain:server·pm:queue· 2026-11-05T02:56Z⛔ Not a claim.
plugin-auth) refuses a configuration with no working sign-in method. The Setup confirmation is thedomain:uihalf.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Claim: PM loop round 3
Session:
session_01Utp6Cbvcmv8YWFhFJwzjKL(seat) · dev threadsession_01Utp6CXhZcHuALPAjkDCiiTAccount:
os-billBranch:
claude/project-thread-bg54o3(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30202Domain:
domain:serverSeat:
domain:server#2File surface:
packages/plugins/plugin-auth/src/(the auth settings apply / save door, tests), possiblymanifest.ts(help text), one changeset.changeset/30202-keep-a-sign-in-method.md. ⛔packages/spec/**,ui/**,apps/**,skills/**,docs/adr/**,.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: xhigh(p1)Clause-②: no — a refused save / gate fail-safe on an existing door; the dev re-declares
yesfor a new published code, export or settings key, orno (narrowing)if a value an admin could save before is now refused (thenCONTRACT_REVIEW_TIERreview)Metadata-protocol: none
Responsibility: the auth settings apply (
auth-plugin.ts,email_password_enabled) persists email/password off with no other sign-in method | no other door checks that a sign-in method remains | every user of the deployment, the administrator included, once they sign outThread-read: 6648434001
Premise: holds — at
5fa9375eemail_password_enabledis applied throughemailAndPasswordField(...)with no check of social providers or SSO; the same apply already has arefusablepath (mfa_required) and a refusal-isolation testSerial constraints cleared: #30201 (this seat) may edit
auth-plugin.tsnearmfa_requiredand the session-policy gate; disjoint hunks, each pullsmainbefore PR and before landing. No other in-flightdomain:serverclaim namesplugin-auth(#29997 closed; sibling seats' cards read by title/surface)Order: project file
server-seat2/dispatch-30202.md, delivered as the card thread's opening message.objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6CXhZcHuALPAjkDCiiT
os-dev-report
{ "issue": 30202, "status": "done", "branch": "claude/project-thread-bg54o3", "pr": "https://github.com/objectstack-ai/objectstack/pull/30298", "session": "session_01Utp6CXhZcHuALPAjkDCiiT", "premise_still_valid": true, "summary": "Saving the auth settings now refuses, at the settings save door and before anything is written, turning email and password off while no other way in works, and (with email and password already off) the Google change that removes the last provider. It is one write predicate (SettingsService.registerWritePredicate, the ADR-0144 D4 seam) registered from AuthPlugin.bindAuthSettings on auth.email_password_enabled and auth.google_enabled / google_client_id / google_client_secret, answering the existing 400 SETTINGS_VALIDATION invalid_value field error with constraint.lastSignInMethod and a message in en / zh-CN / ja-JP / es-ES naming what to set up first. 'Another way in' = a social or OIDC provider in force; enterprise SSO switched on AND a sys_sso_provider row; phone-number sign-in; a declared hostSignInHandoff (ssoOnlyMode alone, magic link and passkeys do not count). Recovery for an already locked deployment is the existing OS_AUTH_EMAIL_PASSWORD_ENABLED=true override plus a restart, named in the changeset and pinned; no new variable.", "tests": "sign-in-method-floor.test.ts (new, 11 cases, real SettingsService + AuthPlugin + AuthManager): 11/11, re-run on 249ec4c4 (main 4be97342 merged). Whole plugin-auth suite (vitest --maxWorkers=2): Test Files 221 passed, Tests 3887 passed / 10 skipped, on deadfe9f and on 1474c9ec (main 959c26be merged). plugin-auth typecheck (both tsconfigs + check:test-typecheck): exit 0 on the merged head after rebuilding @objectstack/core's dist (stale soleWriter d.ts from main's DispatchLoop change). pnpm lint: exit 0. Ablations through scripts/ablation-replace.mjs, each anchor 1 -> 0 and restored to the HEAD blob with git diff HEAD empty (tests import src; service-settings aliased to src, so no dist leg): A email/password refusal returns nothing -> 3 red; B Google refusal returns nothing -> 1 red; C SSO switch counts without a provider row -> 1 red.", "gates": "node scripts/pm/dispatch-gates.mjs --commands: 72 derived families on db95d0dc, each exit code recorded before any pipe (/mnt/project-files/server-seat2/gates-30202-ran.txt): 72 exit 0; --ran: 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN. First pass had two exit 3 for prerequisites (check:dual-build-cjs-loads: seven packages without dist, built; check-plugin-teardown-shape --self-test: git fetch of 621a4876), both then 0. The 18 families the changeset path derives re-run on deadfe9f (changeset-only commit): all 0. On the merged head the derived list is the same 72 (sorted diff empty); check:engine-double-contract re-run: 0. CI check-runs on PR #30298 (head 249ec4c4): not yet read at report time; the seat thread hears when they settle.", "dogfood_battery": "db95d0dc · os-verify-lock VERDICT command-exit 1 (held 1933s) · Test Files 418 passed / 1 failed / 3 skipped (422) · Tests 3491 passed / 1 failed / 36 skipped (3528) · failure: permission-set-clone-door.dogfood.test.ts > 'a platform set (the lock-and-clone rule's commonest source) copies into its shipping package's pair', 422 INVALID_METADATA on member_default's sys_comment / sys_activity grants. Control: registerSignInMethodFloor( neutralized (--hold, plugin-auth rebuilt, ablation-dist-preflight marker present in 2 built files) -> same file red the same way (1 failed / 10 passed); restored, rebuilt, --absent confirmed. It is main red #30255 (from #30222), reverted on main by #30262; after merging 4be97342 that file passes (11/11, VERDICT command-exit 0). Full battery not re-run after the merges.", "line_budget": "5 files, +641 / -0 against origin/main: sign-in-method-floor.ts +262, sign-in-method-floor.test.ts +333, auth-plugin.ts +22, .changeset/30202-keep-a-sign-in-method.md +19, scripts/engine-double-contract.pinned.json +5", "files_changed": [ "packages/plugins/plugin-auth/src/sign-in-method-floor.ts", "packages/plugins/plugin-auth/src/sign-in-method-floor.test.ts", "packages/plugins/plugin-auth/src/auth-plugin.ts", ".changeset/30202-keep-a-sign-in-method.md", "scripts/engine-double-contract.pinned.json" ], "deviations": [ "scripts/engine-double-contract.pinned.json is outside the claimed file surface: one ledger row for the new test's findOne double, written by check-engine-double-contract.mjs --write, which the gate requires for a new test file with an engine double.", "The refusal message names what to configure, not the recovery variable: it speaks to the administrator in Setup, who is still signed in. OS_AUTH_EMAIL_PASSWORD_ENABLED is named in the changeset and the PR body.", "The harness's assign / review-request step was skipped, as the order says.", "After the changeset-only commit deadfe9f, only the 18 changeset-derived families were re-run, not all 72." ], "mcp_calls": "5 — all reads: issue_read get #30202, issue_read get_comments #30202 (x2), issue_read get #29159, pull_request_read get #30298 (draft true, no assignees, body as sent). No MCP write.", "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls) and post-stamped (POST /repos/objectstack-ai/objectstack/issues/30202/comments); plus git push (not REST). Write budget: 3 of 3.", "open_questions": [ { "question": "A provider filled in and email and password turned off in the same save is refused (the predicate judges one key against the running config). Is two saves acceptable?", "options": [ "A keep two saves; the message says to save the other method first", "B judge the batch as a whole, which needs the batch on SettingsWritePredicateInput (a service-settings contract change, its own card)" ], "recommendation": "A, because it refuses nothing that leaves a way in once the method is saved, and B widens a shared contract beyond this card." } ], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: Setup → Settings → Authentication shows no confirmation before turning off the last sign-in method, and shows the refusal only as the field error the console extractor reads; the card also saw the login page draw the email/password form under os dev with better-auth's English 'Email and password is not enabled' (LoginForm.tsx:351 has the no-method state at 797f9257, so what remains is what the console dev server draws there; not reproduced) · evidence: card #30202 body, ui/packages/auth/src/LoginForm.tsx · dedupe words: login page no sign-in method, setup auth confirmation, email password disabled form", "carrier: seat files to domain:ui (lane off duty) · noted, not filed", "class: b · reach: other doors to the same lockout that are not settings saves: deleting the last sys_sso_provider row, or a redeploy that drops the last stack-config provider or plugins.phoneNumber, while email and password is off · evidence: read, not measured · dedupe words: last sso provider delete lockout, redeploy drops provider lockout", "carrier: seat · noted, not filed" ] }Report taken 2026-11-05T05:28Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Claim correction (supersedes the Clause-② line and widens the file surface of claim 6648660209; every other line stands)
Clause-②: no (narrowing) — measured by the dev: the auth settings save door refuses, as a whole batch (400
SETTINGS_VALIDATION), a save that turns off the last sign-in method (email and password with no other method, or the last Google provider while email and password are off), a value an admin could save before. Recovery for a deployment already locked: the existingOS_AUTH_EMAIL_PASSWORD_ENABLED=trueand a restart. Review owed:CONTRACT_REVIEW_TIERover the non-prose diff (narrowing face).File surface (added):
scripts/engine-double-contract.pinned.json(one row the gate requires for the new module). Accepted by the seat, subject to the contract review.objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6CXhZcHuALPAjkDCiiT
os-dev-report
{ "issue": 30202, "status": "done", "branch": "claude/project-thread-bg54o3", "pr": "https://github.com/objectstack-ai/objectstack/pull/30298", "session": "session_01Utp6CXhZcHuALPAjkDCiiT", "premise_still_valid": true, "summary": "Rework after contract review 6650756385 (seat ruling B, same PR). service-settings: SettingsWritePredicateInput gains optional batch (every key the write sets, frozen, null = clear), built once in refuseByWritePredicates and passed to every predicate. plugin-auth: the sign-in floor judges the state after the whole save, so { email_password_enabled: false, google_enabled: false } (or the Google secret cleared) on email+password+Google is refused at both keys, and Google filled in, or switched on over a stored pair, in the same save as email and password off is accepted. Should-fix: a social or OIDC provider counts only when both clientId and clientSecret are non-blank in the boot configuration it came from (providerCredentialIndex); Google added from the settings has a pair by construction. The one-key two-save limitation of the first head is gone.", "tests": "sign-in-method-floor.test.ts 17/17 (6 new: both-keys refusal x2 batches, Google filled in the same save, switch over a stored pair, half a pair refused, blank-secret stack provider, providerCredentialIndex). settings-write-predicate.test.ts + localization-currency-lock.test.ts 33/33 (2 new: batch handed frozen with a clear as null; expectTypeOf on SettingsWritePredicateInput['batch'] imported from ./index.js). Whole packages under the lock: plugin-auth 221 files, 3893 passed / 10 skipped; service-settings 67 files, 1153 passed. typecheck (service-settings, plugin-auth incl. check:test-typecheck) exit 0; built dist/index.d.ts of service-settings carries batch?: Readonly<Record<string, unknown>>. pnpm lint exit 0. Ablations, each anchor 1 -> 0 and restored to the HEAD blob: D no batch passed -> ss 1 red + auth 3 red; E floor ignores batch -> 3 red; F provider counts without creds -> 1 red; G unset key not read as stored -> 2 red; A email refusal void -> 6 red; B Google refusal void -> 2 red; C SSO switch without row -> 1 red.", "gates": "dispatch-gates --commands on 0b89bdf1: 73 derived (72 + check:package-naming from the service-settings path), run after a full turbo build, exit codes recorded before any pipe (/mnt/project-files/server-seat2/gates-30202-ran-v2.txt): 73 exit 0; --ran: 73 derived, 73 run, 0 NOT-MEASURED, 0 UNRUN. CI check-runs on the new head: not yet read at report time.", "dogfood_battery": "0b89bdf1 · os-verify-lock VERDICT command-exit 1 (held 2157s, gate loop running beside it) · Test Files 418 passed / 1 failed / 3 skipped (422) · Tests 3488 passed / 4 failed / 36 skipped (3528) · failures: package-commit-revert-path-package.dogfood.test.ts #29390 and #29414 x3, each 'Test timed out in 5000ms'; the file alone under the lock on the same head: 12/12, VERDICT command-exit 0.", "line_budget": "8 files, +886 / -3 against origin/main: sign-in-method-floor.ts +376, sign-in-method-floor.test.ts +418, auth-plugin.ts +28, settings-service.ts +8/-1, settings-service.types.ts +9, settings-write-predicate.test.ts +20/-2, changeset +22, scripts/engine-double-contract.pinned.json +5", "files_changed": [ "packages/plugins/plugin-auth/src/sign-in-method-floor.ts", "packages/plugins/plugin-auth/src/sign-in-method-floor.test.ts", "packages/plugins/plugin-auth/src/auth-plugin.ts", "packages/services/service-settings/src/settings-service.ts", "packages/services/service-settings/src/settings-service.types.ts", "packages/services/service-settings/src/settings-write-predicate.test.ts", ".changeset/30202-keep-a-sign-in-method.md", "scripts/engine-double-contract.pinned.json" ], "deviations": [ "packages/services/service-settings/** is outside the dispatch's file surface; the seat's ruling B asked for it.", "scripts/engine-double-contract.pinned.json: one ledger row (accepted by the seat).", "The PR body is not rewritten (a dev writes it once, at pr_create). The full corrected body is at /mnt/project-files/server-seat2/pr-30202-body-v2.md for the seat to write: Clause-② yes (narrowing), 维护者速读, What changes, Tests, Acceptance notes and Verification all change.", "Pushed on main 977be380; main has 3 commits since that touch none of the changed paths (trial merge clean), not merged again.", "The harness's assign / review-request step was skipped, as the order says." ], "mcp_calls": "5 in this thread, all reads, none since the first report: issue_read get #30202, issue_read get_comments #30202 (x2), issue_read get #29159, pull_request_read get #30298. No MCP write.", "api_writes": "this round 1 — post-stamped (POST /repos/objectstack-ai/objectstack/issues/30202/comments), plus git push (not REST). Whole card: relay pr_create and 2 report comments, plus git pushes.", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: Setup → Settings → Authentication shows no confirmation before turning off the last sign-in method, and the refusal only as the field error; the login page under os dev was seen drawing the email/password form with better-auth's English error (not reproduced) · evidence: card #30202, ui/packages/auth/src/LoginForm.tsx · dedupe words: login page no sign-in method, setup auth confirmation", "carrier: seat (filing) · noted, not filed", "class: b · reach: lockout doors that are not settings saves: deleting the last sys_sso_provider row, a redeploy dropping the last provider or plugins.phoneNumber, while email and password is off · evidence: read, not measured · dedupe words: last sso provider delete lockout", "carrier: seat (filing) · noted, not filed" ] }Report taken 2026-11-05T07:08Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Landed · 2026-11-05T08:46Z
870ebce3, reviewed headba1977cb(seat ACCEPT 6652576035)CONTRACT_REVIEW_TIER, narrowing face: FAIL 6650756385 (249ec4c4, per-key judging), delta PASS 6652218570 (0b89bdf1);0b89bdf1 → ba1977cbseat-read in the ACCEPTquery/searchFields, #30367), not this PR's; the PR merged on a later queue buildFixes #30202@objectstack/plugin-authand@objectstack/service-settingspatch,Clause-②: yes (narrowing), BREAKING banner and ADR-0087 marker; next releaseState change:
pm:dispatchedremoved. Unblocks #30300 (seat 3) and, after #30302 lands, #30312.