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
Sign-in lockout through doors other than the auth settings save: deleting the last SSO provider row, or a redeploy that drops the last provider, while email and password is off #30300
Filing gate: ② a sibling of #30202 (p1, whole-tenant lockout), found by its dev (PR #30298, os-dev-report 6650530417, out_of_scope class b) and outside that card's door. Read, not measured — the first act is to measure it. ⛔ Not a claim. Triage sets the grade.
Reader: domain:server, plugin-auth (the sign-in method floor PR #30298 adds, sign-in-method-floor.ts) and the doors that remove a method without a settings save.
Dedupe: last sso provider delete lockout, redeploy drops provider lockout → none open. New.
The doors
PR #30298 refuses an auth settings save that would leave no sign-in method. The same end state is reachable without that save, while email and password is off:
Deleting (or disabling) the last sys_sso_provider row through the data API or Setup.
A redeploy whose stack config no longer declares the last provider (or drops plugins.phoneNumber), so the running manager serves no method at the next boot.
Wanted
Measure both on a booted deployment with email and password off and one other method.
Door 2: a boot that would serve no sign-in method says so loudly (and names OS_AUTH_EMAIL_PASSWORD_ENABLED=true as the recovery); decide refuse vs warn by posture, as the data-key boot does.
The card says it was read, not measured, so measure first. Fix site: plugin-auth's sign-in-method-floor.ts, applied at every door that removes a method.
Claim: PM loop round 2
Session: session_01Utp6CbPxYq1wQmUNWvMZkC (seat) · dev = project card thread session_01Utp6CadeYZeJahkVWxXfYx
Account: os-bill
Branch: claude/project-thread-xt31nw
Worktree: objectstack-issue-30300
Domain: domain:server
Seat: domain:server#3
File surface: packages/plugins/plugin-auth (the sign-in method floor sign-in-method-floor.ts that PR #30298 adds, reused, and the doors that remove a method without a settings save: the sys_sso_provider delete/disable path, the boot-time manager that serves no method after a redeploy), their tests, one changeset; measurement first on main, code only after PR #30298 merges — stop on breach; explain in the report.
Container & model: M, mode:thread, model: default judgment tier (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: xhigh (p1, M)
Clause-②: no — the dev re-measures; a refused delete of the last sign-in method narrows nothing an author writes
Metadata-protocol: none
Responsibility: the sign-in method floor is enforced only on the auth settings save (reading, from the #30202 dev, PR #30298 class b) | none: a deployment with email and password off and one other method | every tenant of such a deployment; read, not measured — the first act measures it Thread-read: 6650883232 Premise: holds Serial constraints cleared: serial behind seat 2 PR #30298 (#30202, sign-in-method-floor.ts, in rework after contract review): no code before it merges; seat 2 handed this card to seat 3 (06:41Z); no other in-flight claim on plugin-auth sign-in floor.`
{
"issue": 30300,
"status": "blocked",
"branch": "claude/project-thread-xt31nw",
"pr": null,
"session": "session_01Utp6CadeYZeJahkVWxXfYx",
"premise_still_valid": true,
"summary": "Measurement only, as the Claim orders; no code, no commit, no PR. Both doors reproduce on main bfca0efb on a real showcase boot (pnpm dev, SqlDriver better-sqlite3), with email and password off and exactly one other way in. Door 1: the Setup action 'Delete SSO Provider' (POST /api/v1/auth/sso/delete-provider) deletes the last sys_sso_provider row with 200; /auth/config then serves no method and, after sign-out, every sign-in answers 400 or 404. The data API is not a door: DELETE /api/v1/data/sys_sso_provider/:id answers 405 OBJECT_API_METHOD_NOT_ALLOWED. Door 2: a redeploy that drops the boot-declared Google provider (GOOGLE_CLIENT_ID/SECRET) boots to 'Server is ready' with no line about sign-in, and serves no method; OS_AUTH_EMAIL_PASSWORD_ENABLED=true and a restart recover it. Blocked on the Claim's serial constraint: code waits for PR #30298 (sign-in-method-floor.ts) to merge, then reuses its floor and message family on main.",
"measurements": {
"door_1_last_sso_provider_deleted": [
"Boot: pnpm dev -- --fresh, OS_SSO_ENABLED=true. Signed in as the seeded platform admin (email and password on).",
"One SAML identity provider registered through POST /api/v1/auth/sso/register (platform-admin gated) -> 200; GET /api/v1/data/sys_sso_provider -> 1 row (acme-saml); /auth/config features.sso true. Setup's own Register SAML Provider bridge refused every input on this vendor version: see out_of_scope_findings.",
"PUT /api/settings/auth {email_password_enabled:false} -> 200 (main has no floor; with PR #30298 this save is also accepted, because SSO plus a provider row counts as a way in). /auth/config: emailPassword.enabled false, socialProviders [], features.sso true.",
"Data API: DELETE /api/v1/data/sys_sso_provider/<id> -> 405 OBJECT_API_METHOD_NOT_ALLOWED (allowed: get, list, aggregate, history, search, export); the row stays. Not a door.",
"Disable: sys_sso_provider has no enabled or disabled field, so there is no disable door; the only other vendor mutation, POST /sso/update-provider, rewrites a provider and removes none (not measured).",
"Setup's Delete SSO Provider action: POST /api/v1/auth/sso/delete-provider {providerId:'acme-saml'} -> 200 {success:true}; the row is gone (records []). /auth/config: emailPassword.enabled false, socialProviders [], features.sso false, phoneNumber false, which is the login page's 'No sign-in method is available' state (LoginForm.tsx noSignInMethod).",
"After sign-out: POST /sign-in/email -> 400 EMAIL_PASSWORD_DISABLED; POST /sign-in/sso by email (someone@acme.test) and by providerId -> 404 'No provider found for the issuer'. Nobody can sign in, the administrator included.",
"Server log: no line about the delete or the lockout. Read, not measured: the vendor's delete (@better-auth/sso 1.7.12 deleteSSOProvider) also removes every account row linked to that providerId, in the same transaction."
],
"door_2_redeploy_drops_last_provider": [
"Boot A: pnpm dev with a persistent OS_HOME and OS_DATABASE_URL, GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET set (Google declared at boot). PUT /api/settings/auth {email_password_enabled:false} -> 200. /auth/config: emailPassword.enabled false, socialProviders [google]. POST /sign-in/social {provider:'google'} -> 200 with the Google authorize URL. Google is the one way in.",
"Boot B, the redeploy: the same database, GOOGLE_CLIENT_* unset. The boot prints 'Server is ready' and no sign-in line at warn or error; the dev banner still prints the dev admin credentials, which cannot sign in. /auth/config: emailPassword.enabled false, socialProviders [], features.sso false, phoneNumber false. POST /sign-in/email -> 400 EMAIL_PASSWORD_DISABLED; POST /sign-in/social google -> 404 PROVIDER_NOT_FOUND; POST /sign-in/sso -> 404. The only log lines are better-auth's own, per failed request.",
"Boot C, recovery: the same database plus OS_AUTH_EMAIL_PASSWORD_ENABLED=true. /auth/config emailPassword.enabled true; POST /sign-in/email -> 200 with a session.",
"Measured once with Google declared by env. Dropping plugins.phoneNumber, a stack-config social provider, or plugins.sso / OS_SSO_ENABLED is the same mechanism (the manager is built from the boot config, and the stored email_password_enabled false applies on top). Read, not measured."
],
"transcripts": "Request and response lines, session tokens redacted, with the scripts that produced them: /mnt/project-files/server-seat3/30300/ in the project folder (door1-final.out, door2-a.out, door2-b.out, door2-c.out; door1-run1.out holds the Setup SAML bridge refusal)."
},
"fix_sites_read_for_the_code_round": [
"Door 1: plugin-auth already intercepts POST /sso/delete-provider in its better-auth before-hook (auth-manager-before-hook.ts, the ADR-0069 D3 held-caller branch), before the vendor deletes anything. The refusal can be made there: email and password off, the provider being deleted is the last sys_sso_provider row, and the floor's otherSignInMethods finds nothing besides enterprise SSO. The vendor's own guardProviderMutation option is a second seam, but it requires an adapter with native transactions and async-context support (assertProviderMutationGuardCapabilities), which the ObjectQL adapter is not known to provide; not measured.",
"Door 2: the kernel:ready site where boot-sign-in-reachability.ts already reports no_sign_in_account_at_boot reads the live manager and the store. A boot whose manager serves no method can be judged there with the same otherSignInMethods."
],
"tests": "None; measurement only. Evidence is the real-boot transcripts above. The workspace was built with turbo run build --filter=!@objectstack/docs (119 tasks, exit 0) before the boots.",
"gates": "Not run: no diff.",
"dogfood_battery": "n/a: no diff.",
"mcp_calls": "4, all reads: issue_read get #30300, issue_read get_comments #30300, pull_request_read get #30298, pull_request_read get_files #30298. No MCP write.",
"api_writes": "1: post-stamped, this comment (POST /repos/objectstack-ai/objectstack/issues/30300/comments) through the relay. No git push.",
"open_questions": [
{
"question": "Door 2: refuse the boot by posture (NODE_ENV=production refuses, as LocalCryptoProvider refuses to mint a data key in production), or log at error in every posture?",
"options": [
"A: error level in every posture, naming OS_AUTH_EMAIL_PASSWORD_ENABLED=true as the recovery; the boot proceeds",
"B: production refuses the boot, naming the same recovery; development and test log at error"
],
"recommendation": "A, because the floor's list of ways in does not count ssoOnlyMode alone, which a platform-SSO tenant boots with (boot-sign-in-reachability.ts, the delegated-path section), so B could stop a tenant that works today; and a refusal offers no recovery beyond the same variable. B fits only once every delegated path is counted."
}
],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: Setup -> SSO Providers -> Register SAML Provider (POST /api/v1/auth/admin/sso/register-saml) answers 400 SAML_REGISTER_FAILED '[body.samlConfig.idpMetadata] idpMetadata.entityID is required when IdP metadata XML is not provided' for a complete form, on main bfca0efb with @better-auth/sso 1.7.12: runRegisterSamlProviderFromForm (plugin-auth register-sso-provider.ts) builds samlConfig with no idpMetadata, and the vendor schema requires idpMetadata.entityID or metadata XML. The same provider registers through the vendor's /sso/register when idpMetadata.entityID is sent · evidence: /mnt/project-files/server-seat3/30300/door1-run1.out · dedupe words: register saml provider idpMetadata, SAML_REGISTER_FAILED entityID, saml bridge better-auth 1.7",
"carrier: seat · noted, not filed"
]
}
{
"issue": 30300,
"status": "done",
"branch": "claude/project-thread-xt31nw",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30449",
"session": "session_01Utp6CadeYZeJahkVWxXfYx",
"premise_still_valid": true,
"summary": "Option A as ruled, on main after #30298 (870ebce3, then merged with main 7d7babb1). Door 1: plugin-auth's global before-hook refuses POST /sso/delete-provider when email and password is off, enterprise SSO is on, no other way in counts (otherSignInMethods without enterprise SSO) and the provider is the last sys_sso_provider row. It answers 409 DELETE_RESTRICTED with lastSignInMethod enterprise_sso and localized copy (en/zh-CN/ja-JP/es-ES), or 503 SERVICE_UNAVAILABLE when the provider list cannot be read; the judge is wired by AuthPlugin through an @internal AuthManager.setSsoProviderDeleteFloor. Door 2: at the kernel:ready site of no_sign_in_account_at_boot, after the auth settings are applied, a boot that serves no sign-in method logs no_sign_in_method_at_boot at error in every posture and never refuses the boot (positive claim only; refusal named in the PR body as a later option, not done). One builder (AuthPlugin.signInMethodFloorDeps) feeds the settings save, the delete and the boot report; self-hosting.mdx gains 'No sign-in method left'.",
"tests": "Pins in the floor's shared harness packages/plugins/plugin-auth/src/sign-in-method-floor.test.ts as top-level describes (ruling 6583487846): 18 new, 35 in the file, 35/35. Door 1 and door 2 run on a real ObjectKernel + ObjectQLPlugin (in-memory SQLite) + SettingsServicePlugin + AuthPlugin (sso) with a real sign-up and cookie, through the real delete endpoint, each with controls (another provider left, email and password on, unknown provider 404, no session 401; Google declared, env recovery over stack config). Judge/report faults pinned against the floor functions (503, silence on unreadable table, seven pass cases, error/warn sink). Ablations via scripts/ablation-replace.mjs, each landed (anchor 1->0, blob changed) and restored clean: A1 before-hook call removed -> 2 red (expected 200 to be 409); A2 boot report call removed -> 2 red; A3 unknown read as present -> 1 red; A4 report speaks unless present -> 1 red; A5 plugin does not wire the judge -> 2 red ({success:true}: expected 200 to be 409). A1/A2 re-run on the final shared file. Final head 385ecdcc: pnpm --filter @objectstack/plugin-auth test VERDICT command-exit 0, Test Files 224/224, Tests 3967 passed 10 skipped; typecheck VERDICT 0 (check:test-typecheck OK, ledger unchanged); pnpm lint VERDICT 0. Real boot re-measure (pnpm dev): door 1 last delete 409 en + zh-CN, row kept, control delete 200, SSO sign-in still routes; door 2 redeploy logs the ERROR line and reaches Server is ready, recovery boot logs none and sign-in/email 200. Transcripts: /mnt/project-files/server-seat3/30300/remeasure-pr-head/.",
"gates": "dispatch-gates --commands derived 95 on 385ecdcc; all 95 exit 0 (codes recorded before any pipe); --ran: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN. Pre-merge head e5f6926f: check:liveness exit 1 (main's red, fixed by #30368, green after the merge); check-plugin-teardown-shape --self-test exit 3 (pinned fixture commit absent in the shallow clone; passed after git fetch --depth=1 of it). CI check-runs on #30449: pending at report time.",
"dogfood_battery": "385ecdcc · os-verify-lock: VERDICT command-exit 0 (held 33m24s) · Test Files 419 passed, 3 skipped (422) · Tests 3492 passed, 36 skipped (3528) · no failure",
"mcp_calls": "3 this round, all reads: issue_read get_comments #30300, pull_request_read get #30368, pull_request_read get #30376 (the measurement round's 4 reads are in 6652003986); plus subscribe_pr_activity #30449, a session subscription, not a GitHub write. No MCP write.",
"api_writes": "3: git push to claude/project-thread-xt31nw (4 commits incl. the main merge); relay pr_create -> #30449 (draft, run 42021957727, 12456 bytes read back identical, no assignee, no reviewer); post-stamped, this comment.",
"open_questions": [
{
"question": "Clause-②: the Claim wrote 'no — the dev re-measures'. Measured: POST /sso/delete-provider on the last provider answered 200 before and 409 now, an accept-set narrowing at a published endpoint (the kind #30298 declared yes (narrowing)); the widening half is one @internal setter on the exported AuthManager. Declared 'yes (narrowing)' with '!' in the PR body and changeset.",
"options": [
"A: keep yes (narrowing) as declared; the seat runs CONTRACT_REVIEW_TIER",
"B: re-declare no; the seat edits the PR body and changeset line"
],
"recommendation": "A, because CONTRIBUTING item 5 says yes when a change narrows what a published package accepts, and #30298 set the precedent for the same family."
}
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: seat · Setup Register SAML Provider bridge 400 already filed by the seat as #30352; no new finding"
]
}
Claim amendment · 2026-11-05T12:03Z · declaration and file surface for PR #30449.
Clause-②: yes (narrowing) — POST /api/v1/auth/sso/delete-provider on the last enterprise SSO provider, with email and password off and no other counted sign-in method, answered 200 and now answers 409 DELETE_RESTRICTED (503 when the provider list cannot be read); the exported AuthManager gains the @internal setter setSsoProviderDeleteFloor(), which @internal does not trim from the published declarations.
Metadata-protocol: none
Changed: the claim's Clause-②: no — the dev re-measures was the seat's reading before the measurement; the dev re-measured 200 → 409 at a published endpoint, and the contract review of record on the PR (CONTRACT_REVIEW_TIER, PASS on 385ecdcc) confirms yes (narrowing). BREAKING: patch with the BREAKING banner and one ADR-0087 marker; landing waits for the maintainer's word.
Added to the surface: packages/plugins/plugin-auth/src/auth-manager.ts, auth-manager-before-hook.ts, auth-manager-auth-instance.ts, auth-plugin.ts (kernel:ready boot report), content/docs/deployment/self-hosting.mdx.
Unchanged: branch claude/project-thread-xt31nw; seat ruling option A (the boot report never refuses the boot).
ACCEPT → PR #30449 comment 6657794372 (seat record 6658022337) · 2026-11-05T12:30Z · maintainer's word for the BREAKING landing taken 12:28Z; ready + auto-merge armed.
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-05T05:53Z⛔ Not a claim.
plugin-auth'ssign-in-method-floor.ts, applied at every door that removes a method.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC
Claim: PM loop round 2
Session:
session_01Utp6CbPxYq1wQmUNWvMZkC(seat) · dev = project card threadsession_01Utp6CadeYZeJahkVWxXfYxAccount:
os-billBranch:
claude/project-thread-xt31nwWorktree:
objectstack-issue-30300Domain:
domain:serverSeat:
domain:server#3File surface:
packages/plugins/plugin-auth(the sign-in method floorsign-in-method-floor.tsthat PR #30298 adds, reused, and the doors that remove a method without a settings save: thesys_sso_providerdelete/disable path, the boot-time manager that serves no method after a redeploy), their tests, one changeset; measurement first onmain, code only after PR #30298 merges — stop on breach; explain in the report.Container & model:
M,mode:thread,model: default judgment tier (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable),effort: xhigh (p1, M)Clause-②: no — the dev re-measures; a refused delete of the last sign-in method narrows nothing an author writes
Metadata-protocol: none
Responsibility:
the sign-in method floor is enforced only on the auth settings save (reading, from the #30202 dev, PR #30298 class b) | none: a deployment with email and password off and one other method | every tenant of such a deployment; read, not measured — the first act measures it Thread-read: 6650883232 Premise: holds Serial constraints cleared:serial behind seat 2 PR #30298 (#30202, sign-in-method-floor.ts, in rework after contract review): no code before it merges; seat 2 handed this card to seat 3 (06:41Z); no other in-flight claim on plugin-auth sign-in floor.`objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-bill · session_01Utp6CadeYZeJahkVWxXfYx
os-dev-report
{ "issue": 30300, "status": "blocked", "branch": "claude/project-thread-xt31nw", "pr": null, "session": "session_01Utp6CadeYZeJahkVWxXfYx", "premise_still_valid": true, "summary": "Measurement only, as the Claim orders; no code, no commit, no PR. Both doors reproduce on main bfca0efb on a real showcase boot (pnpm dev, SqlDriver better-sqlite3), with email and password off and exactly one other way in. Door 1: the Setup action 'Delete SSO Provider' (POST /api/v1/auth/sso/delete-provider) deletes the last sys_sso_provider row with 200; /auth/config then serves no method and, after sign-out, every sign-in answers 400 or 404. The data API is not a door: DELETE /api/v1/data/sys_sso_provider/:id answers 405 OBJECT_API_METHOD_NOT_ALLOWED. Door 2: a redeploy that drops the boot-declared Google provider (GOOGLE_CLIENT_ID/SECRET) boots to 'Server is ready' with no line about sign-in, and serves no method; OS_AUTH_EMAIL_PASSWORD_ENABLED=true and a restart recover it. Blocked on the Claim's serial constraint: code waits for PR #30298 (sign-in-method-floor.ts) to merge, then reuses its floor and message family on main.", "measurements": { "door_1_last_sso_provider_deleted": [ "Boot: pnpm dev -- --fresh, OS_SSO_ENABLED=true. Signed in as the seeded platform admin (email and password on).", "One SAML identity provider registered through POST /api/v1/auth/sso/register (platform-admin gated) -> 200; GET /api/v1/data/sys_sso_provider -> 1 row (acme-saml); /auth/config features.sso true. Setup's own Register SAML Provider bridge refused every input on this vendor version: see out_of_scope_findings.", "PUT /api/settings/auth {email_password_enabled:false} -> 200 (main has no floor; with PR #30298 this save is also accepted, because SSO plus a provider row counts as a way in). /auth/config: emailPassword.enabled false, socialProviders [], features.sso true.", "Data API: DELETE /api/v1/data/sys_sso_provider/<id> -> 405 OBJECT_API_METHOD_NOT_ALLOWED (allowed: get, list, aggregate, history, search, export); the row stays. Not a door.", "Disable: sys_sso_provider has no enabled or disabled field, so there is no disable door; the only other vendor mutation, POST /sso/update-provider, rewrites a provider and removes none (not measured).", "Setup's Delete SSO Provider action: POST /api/v1/auth/sso/delete-provider {providerId:'acme-saml'} -> 200 {success:true}; the row is gone (records []). /auth/config: emailPassword.enabled false, socialProviders [], features.sso false, phoneNumber false, which is the login page's 'No sign-in method is available' state (LoginForm.tsx noSignInMethod).", "After sign-out: POST /sign-in/email -> 400 EMAIL_PASSWORD_DISABLED; POST /sign-in/sso by email (someone@acme.test) and by providerId -> 404 'No provider found for the issuer'. Nobody can sign in, the administrator included.", "Server log: no line about the delete or the lockout. Read, not measured: the vendor's delete (@better-auth/sso 1.7.12 deleteSSOProvider) also removes every account row linked to that providerId, in the same transaction." ], "door_2_redeploy_drops_last_provider": [ "Boot A: pnpm dev with a persistent OS_HOME and OS_DATABASE_URL, GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET set (Google declared at boot). PUT /api/settings/auth {email_password_enabled:false} -> 200. /auth/config: emailPassword.enabled false, socialProviders [google]. POST /sign-in/social {provider:'google'} -> 200 with the Google authorize URL. Google is the one way in.", "Boot B, the redeploy: the same database, GOOGLE_CLIENT_* unset. The boot prints 'Server is ready' and no sign-in line at warn or error; the dev banner still prints the dev admin credentials, which cannot sign in. /auth/config: emailPassword.enabled false, socialProviders [], features.sso false, phoneNumber false. POST /sign-in/email -> 400 EMAIL_PASSWORD_DISABLED; POST /sign-in/social google -> 404 PROVIDER_NOT_FOUND; POST /sign-in/sso -> 404. The only log lines are better-auth's own, per failed request.", "Boot C, recovery: the same database plus OS_AUTH_EMAIL_PASSWORD_ENABLED=true. /auth/config emailPassword.enabled true; POST /sign-in/email -> 200 with a session.", "Measured once with Google declared by env. Dropping plugins.phoneNumber, a stack-config social provider, or plugins.sso / OS_SSO_ENABLED is the same mechanism (the manager is built from the boot config, and the stored email_password_enabled false applies on top). Read, not measured." ], "transcripts": "Request and response lines, session tokens redacted, with the scripts that produced them: /mnt/project-files/server-seat3/30300/ in the project folder (door1-final.out, door2-a.out, door2-b.out, door2-c.out; door1-run1.out holds the Setup SAML bridge refusal)." }, "fix_sites_read_for_the_code_round": [ "Door 1: plugin-auth already intercepts POST /sso/delete-provider in its better-auth before-hook (auth-manager-before-hook.ts, the ADR-0069 D3 held-caller branch), before the vendor deletes anything. The refusal can be made there: email and password off, the provider being deleted is the last sys_sso_provider row, and the floor's otherSignInMethods finds nothing besides enterprise SSO. The vendor's own guardProviderMutation option is a second seam, but it requires an adapter with native transactions and async-context support (assertProviderMutationGuardCapabilities), which the ObjectQL adapter is not known to provide; not measured.", "Door 2: the kernel:ready site where boot-sign-in-reachability.ts already reports no_sign_in_account_at_boot reads the live manager and the store. A boot whose manager serves no method can be judged there with the same otherSignInMethods." ], "tests": "None; measurement only. Evidence is the real-boot transcripts above. The workspace was built with turbo run build --filter=!@objectstack/docs (119 tasks, exit 0) before the boots.", "gates": "Not run: no diff.", "dogfood_battery": "n/a: no diff.", "mcp_calls": "4, all reads: issue_read get #30300, issue_read get_comments #30300, pull_request_read get #30298, pull_request_read get_files #30298. No MCP write.", "api_writes": "1: post-stamped, this comment (POST /repos/objectstack-ai/objectstack/issues/30300/comments) through the relay. No git push.", "open_questions": [ { "question": "Door 2: refuse the boot by posture (NODE_ENV=production refuses, as LocalCryptoProvider refuses to mint a data key in production), or log at error in every posture?", "options": [ "A: error level in every posture, naming OS_AUTH_EMAIL_PASSWORD_ENABLED=true as the recovery; the boot proceeds", "B: production refuses the boot, naming the same recovery; development and test log at error" ], "recommendation": "A, because the floor's list of ways in does not count ssoOnlyMode alone, which a platform-SSO tenant boots with (boot-sign-in-reachability.ts, the delegated-path section), so B could stop a tenant that works today; and a refusal offers no recovery beyond the same variable. B fits only once every delegated path is counted." } ], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: Setup -> SSO Providers -> Register SAML Provider (POST /api/v1/auth/admin/sso/register-saml) answers 400 SAML_REGISTER_FAILED '[body.samlConfig.idpMetadata] idpMetadata.entityID is required when IdP metadata XML is not provided' for a complete form, on main bfca0efb with @better-auth/sso 1.7.12: runRegisterSamlProviderFromForm (plugin-auth register-sso-provider.ts) builds samlConfig with no idpMetadata, and the vendor schema requires idpMetadata.entityID or metadata XML. The same provider registers through the vendor's /sso/register when idpMetadata.entityID is sent · evidence: /mnt/project-files/server-seat3/30300/door1-run1.out · dedupe words: register saml provider idpMetadata, SAML_REGISTER_FAILED entityID, saml bridge better-auth 1.7", "carrier: seat · noted, not filed" ] }Report taken 2026-11-05T07:07Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-bill · session_01Utp6CadeYZeJahkVWxXfYx
os-dev-report
{ "issue": 30300, "status": "done", "branch": "claude/project-thread-xt31nw", "pr": "https://github.com/objectstack-ai/objectstack/pull/30449", "session": "session_01Utp6CadeYZeJahkVWxXfYx", "premise_still_valid": true, "summary": "Option A as ruled, on main after #30298 (870ebce3, then merged with main 7d7babb1). Door 1: plugin-auth's global before-hook refuses POST /sso/delete-provider when email and password is off, enterprise SSO is on, no other way in counts (otherSignInMethods without enterprise SSO) and the provider is the last sys_sso_provider row. It answers 409 DELETE_RESTRICTED with lastSignInMethod enterprise_sso and localized copy (en/zh-CN/ja-JP/es-ES), or 503 SERVICE_UNAVAILABLE when the provider list cannot be read; the judge is wired by AuthPlugin through an @internal AuthManager.setSsoProviderDeleteFloor. Door 2: at the kernel:ready site of no_sign_in_account_at_boot, after the auth settings are applied, a boot that serves no sign-in method logs no_sign_in_method_at_boot at error in every posture and never refuses the boot (positive claim only; refusal named in the PR body as a later option, not done). One builder (AuthPlugin.signInMethodFloorDeps) feeds the settings save, the delete and the boot report; self-hosting.mdx gains 'No sign-in method left'.", "tests": "Pins in the floor's shared harness packages/plugins/plugin-auth/src/sign-in-method-floor.test.ts as top-level describes (ruling 6583487846): 18 new, 35 in the file, 35/35. Door 1 and door 2 run on a real ObjectKernel + ObjectQLPlugin (in-memory SQLite) + SettingsServicePlugin + AuthPlugin (sso) with a real sign-up and cookie, through the real delete endpoint, each with controls (another provider left, email and password on, unknown provider 404, no session 401; Google declared, env recovery over stack config). Judge/report faults pinned against the floor functions (503, silence on unreadable table, seven pass cases, error/warn sink). Ablations via scripts/ablation-replace.mjs, each landed (anchor 1->0, blob changed) and restored clean: A1 before-hook call removed -> 2 red (expected 200 to be 409); A2 boot report call removed -> 2 red; A3 unknown read as present -> 1 red; A4 report speaks unless present -> 1 red; A5 plugin does not wire the judge -> 2 red ({success:true}: expected 200 to be 409). A1/A2 re-run on the final shared file. Final head 385ecdcc: pnpm --filter @objectstack/plugin-auth test VERDICT command-exit 0, Test Files 224/224, Tests 3967 passed 10 skipped; typecheck VERDICT 0 (check:test-typecheck OK, ledger unchanged); pnpm lint VERDICT 0. Real boot re-measure (pnpm dev): door 1 last delete 409 en + zh-CN, row kept, control delete 200, SSO sign-in still routes; door 2 redeploy logs the ERROR line and reaches Server is ready, recovery boot logs none and sign-in/email 200. Transcripts: /mnt/project-files/server-seat3/30300/remeasure-pr-head/.", "gates": "dispatch-gates --commands derived 95 on 385ecdcc; all 95 exit 0 (codes recorded before any pipe); --ran: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN. Pre-merge head e5f6926f: check:liveness exit 1 (main's red, fixed by #30368, green after the merge); check-plugin-teardown-shape --self-test exit 3 (pinned fixture commit absent in the shallow clone; passed after git fetch --depth=1 of it). CI check-runs on #30449: pending at report time.", "dogfood_battery": "385ecdcc · os-verify-lock: VERDICT command-exit 0 (held 33m24s) · Test Files 419 passed, 3 skipped (422) · Tests 3492 passed, 36 skipped (3528) · no failure", "mcp_calls": "3 this round, all reads: issue_read get_comments #30300, pull_request_read get #30368, pull_request_read get #30376 (the measurement round's 4 reads are in 6652003986); plus subscribe_pr_activity #30449, a session subscription, not a GitHub write. No MCP write.", "api_writes": "3: git push to claude/project-thread-xt31nw (4 commits incl. the main merge); relay pr_create -> #30449 (draft, run 42021957727, 12456 bytes read back identical, no assignee, no reviewer); post-stamped, this comment.", "open_questions": [ { "question": "Clause-②: the Claim wrote 'no — the dev re-measures'. Measured: POST /sso/delete-provider on the last provider answered 200 before and 409 now, an accept-set narrowing at a published endpoint (the kind #30298 declared yes (narrowing)); the widening half is one @internal setter on the exported AuthManager. Declared 'yes (narrowing)' with '!' in the PR body and changeset.", "options": [ "A: keep yes (narrowing) as declared; the seat runs CONTRACT_REVIEW_TIER", "B: re-declare no; the seat edits the PR body and changeset line" ], "recommendation": "A, because CONTRIBUTING item 5 says yes when a change narrows what a published package accepts, and #30298 set the precedent for the same family." } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: seat · Setup Register SAML Provider bridge 400 already filed by the seat as #30352; no new finding" ] }Report taken 2026-11-05T11:49Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC
Claim amendment · 2026-11-05T12:03Z · declaration and file surface for PR #30449.
Clause-②: yes (narrowing) —
POST /api/v1/auth/sso/delete-provideron the last enterprise SSO provider, with email and password off and no other counted sign-in method, answered200and now answers409 DELETE_RESTRICTED(503when the provider list cannot be read); the exportedAuthManagergains the@internalsettersetSsoProviderDeleteFloor(), which@internaldoes not trim from the published declarations.Metadata-protocol: none
Clause-②: no — the dev re-measureswas the seat's reading before the measurement; the dev re-measured200→409at a published endpoint, and the contract review of record on the PR (CONTRACT_REVIEW_TIER, PASS on385ecdcc) confirmsyes (narrowing). BREAKING:patchwith the BREAKING banner and one ADR-0087 marker; landing waits for the maintainer's word.packages/plugins/plugin-auth/src/auth-manager.ts,auth-manager-before-hook.ts,auth-manager-auth-instance.ts,auth-plugin.ts(kernel:readyboot report),content/docs/deployment/self-hosting.mdx.claude/project-thread-xt31nw; seat ruling option A (the boot report never refuses the boot).objectstack-fleet commentedon Nov 5, 2026
domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC
ACCEPT → PR #30449 comment 6657794372 (seat record 6658022337) · 2026-11-05T12:30Z · maintainer's word for the BREAKING landing taken 12:28Z; ready + auto-merge armed.