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
Organization require-MFA can be turned on with two-factor off, and after the grace period everyone is locked out with no way to enroll #30201
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 is told to harden accounts, ticks 强制多因素认证 on the organization, and sees it saved. Nothing changes that day. When the grace period ends, every member, the owner included, is sent to 设置双重验证, enters their password, and reads Two-factor enable failed (404). There is no way forward, and no one inside the company can undo it, because Setup is behind the same gate.
Measured
GET /api/v1/auth/config: features.twoFactor: false, the default.
Edit organization, tick the box, save: toast 「组织已更新」, sys_organization.require_mfa = true, no warning.
Inside the grace period: reads answer 200, and mfa_required_at is stamped.
With mfa_required_at moved 8 days back in the test database to simulate the 7-day grace: reads answer 403 MFA_REQUIRED for a member and for the owner, and POST /api/v1/auth/two-factor/enable answers 404.
Control: Setup → Settings → Authentication → 要求多因素认证, the global switch, says it also turns two-factor on. After it, features.twoFactor is true and a member enrolled and verified a TOTP code. So only the organization field reaches the dead end.
Whether members are reminded during the grace period was not measured specifically; none was seen.
Expected
Turning the organization field on also makes enrollment available, the same as the global switch. If that cannot be done, the save is refused and says why.
The save states who is affected and when the block starts.
The enrollment screen's refusal is localized and says what to do.
Read fresh. An organization's require-MFA can be turned on while two-factor is off (features.twoFactor: false). After the grace period, every member, the owner included, is sent to a two-factor setup that cannot be completed, and nobody can recover from inside the product. A whole-tenant lockout from one Setup toggle, so p1.
Fix site: plugin-auth, refusing the save (or the enforcement) while two-factor is off. The Setup wording is the domain:ui half.
Claim: PM loop round 3
Session: session_01Utp6Cbvcmv8YWFhFJwzjKL (seat) · dev thread session_01Utp6CYRZxPEjGeTbGfW4YC
Account: os-bill
Branch: claude/project-thread-mjj479 (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30201
Domain: domain:server
Seat: domain:server#2
File surface: packages/plugins/plugin-auth/src/ (the session-policy MFA gate, the org write path or the twoFactor enablement, tests), possibly packages/platform-objects/src/identity/sys-organization.object.ts (help text only), one changeset .changeset/30201-org-mfa-needs-enrollment.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 per-user MFA gate (auth-manager-password-session-policy.ts) enforces sys_organization.require_mfa while the twoFactor plugin is off | the global mfa_required switch already pairs enforcement with enrollment (auth-plugin.ts, #20412) | every member and owner of an org that ticks 强制多因素认证 on a deployment with two-factor off, after the grace period
Thread-read: 6648424134
Premise: holds — at 5fa9375e the global switch applies { mfaRequired: true, plugins: { twoFactor: true } } as one refusable patch, while the org field is read by the gate (refreshOrgMfaCacheIfStale, resolveUserAuthGates) with no enrollment pairing; the 404 on /two-factor/enable is the card's measurement, re-measured by the dev
Serial constraints cleared: #30202 (this seat) edits auth-plugin.ts at the email_password_enabled apply; 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 #30005/#30006, #30093/#30095, #30215/#30217, #30000, #30052, #30123, #30117, #30228, #29848, #30204 read by title/surface)
Order: project file server-seat2/dispatch-30201.md, delivered as the card thread's opening message.
{
"issue": 30201,
"status": "done",
"branch": "claude/project-thread-mjj479",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30293",
"session": "session_01Utp6CYRZxPEjGeTbGfW4YC",
"head": "8c4430f5fdf9d92204dd4f13a523f5e95d15b55a",
"premise_still_valid": true,
"summary": "Route (b): a new beforeInsert/beforeUpdate hook on sys_organization (priority 15, after the ADR-0092 D2 guard) refuses any write whose payload turns require_mfa on while two-factor is off, 400 VALIDATION_FAILED with one fields[] entry require_mfa/invalid_value saying why and the two remedies; judged for every context and on the payload (by-id, multi-row, insert), with two-factor read per write from the live AuthManager. Route (a) was measured and not taken: the twoFactor switch is process config (settings pass + OS_AUTH_TWO_FACTOR, re-applied at boot), so enabling it from an org row would not survive a restart or reach another node, and disabling it when the last org clears the column would drop enrolled factors. Gate fail-safe: resolveUserAuthGates holds a user for MFA only while two-factor is on, so no MFA_REQUIRED and no mfa_required_at stamp otherwise (one warning per process names the remedy); rows already stored require_mfa = true lock nobody out. resolveTwoFactorEnabled is now the one decision point for the plugin list, features.twoFactor, the gate and the refusal.",
"tests": "New organization-mfa-enrollment-guard.test.ts 25/25 (rule on a capture engine; AuthPlugin kernel:ready registration; real SQLite ObjectQL with SysOrganization + D2 guard: admin by-id refused and row unchanged, admitted after two-factor on, system write / multi-row update / insert refused, other writes pass; end to end on real better-auth: two-factor off -> POST /two-factor/enable 404 and session authGate not MFA_REQUIRED, resolveUserAuthGates [], warning once, no restamp; two-factor on -> MFA_REQUIRED and enable 200 with totpURI). auth-manager.test.ts: 4 new cases (no hold/no stamp/warn once; OS_AUTH_TWO_FACTOR=false outranks plugins.twoFactor and features.twoFactor false; control; per-org no block). On 8c4430f5 under os-verify-lock: pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 -> Test Files 221 passed (221), Tests 3905 passed | 10 skipped, VERDICT command-exit 0; plugin-auth typecheck VERDICT command-exit 0; verify held-session-impersonation-target + vendor-admin-doors + platform-admin -> 3 files, 49 passed, VERDICT 0. Ablations on 9b256807 via scripts/ablation-replace.mjs WRAP, each restored blob == HEAD: A guard throw deleted -> 5 red; B gate fail-safe neutralized -> 4 red incl. e2e \"expected 'MFA_REQUIRED' not to be 'MFA_REQUIRED'\"; C registration deleted -> registration pin red; D env precedence dropped -> env-pin red. Full output: project file server-seat2/30201/ablations-ran.txt.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 8c4430f5: 64 derived, exit codes recorded before any pipe. 62 exit 0 on the first pass; 2 exit 3 (prerequisite), each re-run once met: pnpm check:dual-build-cjs-loads exit 0 after building the 7 packages it named without dist/; pnpm check:type-check-debt exit 0 under the lock (first pass queued behind the dogfood hold). --ran over the final record: 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. pnpm lint exit 0. CI on the PR: pending at posting time.",
"dogfood_battery": "8c4430f5 · VERDICT command-exit 1 (held the lock 2328s) · 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 … copies into its shipping package's pair' (clone of member_default -> 422 INVALID_METADATA, grants on sys_comment not defined). Not this change: it arrived with the main merge from #30222; with default-permission-sets.ts put back to before that commit and plugin-security rebuilt, the file is 11/11 green on this head (restore proven blob == HEAD). main has since reverted #30222 (#30262, main red #30255).",
"line_budget": "704 changed lines (+692 / -12) over 10 files, under the 3000-line human-merge threshold (dispatch-gates change-set reading)",
"files_changed": [
".changeset/30201-org-mfa-needs-enrollment.md",
"packages/plugins/plugin-auth/src/auth-manager-password-session-policy.ts",
"packages/plugins/plugin-auth/src/auth-manager-plugin-list.ts",
"packages/plugins/plugin-auth/src/auth-manager.test.ts",
"packages/plugins/plugin-auth/src/auth-manager.ts",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/plugins/plugin-auth/src/organization-mfa-enrollment-guard.test.ts",
"packages/plugins/plugin-auth/src/organization-mfa-enrollment-guard.ts",
"packages/verify/src/held-session-impersonation-target.pin.test.ts",
"packages/verify/src/held-session-vendor-admin-doors.pin.test.ts"
],
"deviations": [
"Clause-② declared 'no (narrowing)', not the Claim's 'no': a require_mfa = true an administrator could save before is refused while two-factor is off. Owes the CONTRACT_REVIEW_TIER review; the changeset carries the BREAKING banner, the '!' summary and the ADR-0087 not-required (no-migration-prescription) marker.",
"File surface: two packages/verify held-session pin tests outside the declared surface, one config key each (plugins: { twoFactor: true } beside mfaRequired), because the gate fail-safe no longer holds a user nobody can enroll and those pins set mfaRequired without twoFactor.",
"Skills-docs follow-up owed (content/docs/permissions/authentication.mdx, the mfa_required row); not edited, outside the file surface.",
"The PM-suggested route (a) was not taken; measured reason in summary.",
"dispatch-gates --commands ran without paths (change set derived from git), per os-dev.",
"The dogfood and verify closure builds ran unlocked (nohup, no locked run was queued in this single-agent container); the dist top-up build ran under the lock.",
"check-plugin-teardown-shape --self-test fixture commit fetched with git fetch --depth=1 of that one commit.",
"One push was refused locally by check:commit-card-trailers (a model name in the co-author trailer); the two unpublished commits were re-created with identical trees and the model-free pair; nothing published was rewritten.",
"main moved 20 commits after the pre-PR merge (incl. the #30222 revert), none in plugin-auth or packages/verify; not re-merged before opening, the seat re-pulls before landing.",
"Write budget 6, not 3: git push x4 (WIP pushes per the order: 004729a0, 1aa1a1d0, 4a878a7e, 8c4430f5), relay pr_create, this comment."
],
"write_budget": "6 — git push x4, relay pr_create #30293, this comment via post-stamped",
"mcp_calls": "3 — mcp__github__issue_read x3 (get, get_comments x2); no GitHub MCP write",
"api_writes": "2 — fleet-write pr_create #30293 (run 41972763469, 14017 bytes sent, 14017 stored, identical; assignees none, draft); this comment via post-stamped",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: Setup organization edit form (Require Multi-Factor Auth) and the enrollment screen · the domain:ui half: Setup copy saying who is affected and when the block starts, and a localized enrollment refusal saying what to do; until then the form shows the server's English refusal text · carrier: seat (domain:ui, lane off duty) · dedupe: require_mfa setup copy, two-factor enrollment refusal, organization MFA toggle",
"carrier: seat · content/docs/permissions/authentication.mdx mfa_required row says require_mfa is settable per organization with no condition (Skills-docs follow-up)",
"carrier: #30255 · dogfood permission-set-clone-door red from #30222, already reverted by #30262 · noted, not filed"
]
}
Claim correction (supersedes the Clause-② line and widens the file surface of claim 6648643507; every other line stands)
Clause-②: no (narrowing) — measured by the dev: route (a) cannot hold (the twoFactor switch is process configuration; an org row turning it on survives neither a restart nor another node), so route (b): an organization write of require_mfa = true while two-factor is off is refused (400 VALIDATION_FAILED), a value an admin could save before. Plus the gate fail-safe: with two-factor off the MFA gate neither blocks nor stamps mfa_required_at, so rows already stored true lock no one out. Changeset carries the BREAKING banner and its ADR-0087 marker. Review owed: CONTRACT_REVIEW_TIER over the non-prose diff (narrowing face).
File surface (added): packages/verify/src/held-session-impersonation-target.pin.test.ts, packages/verify/src/held-session-vendor-admin-doors.pin.test.ts (one config key plugins.twoFactor: true each, so the held-session pins still exercise an enforcing gate after the fail-safe). Accepted by the seat, subject to the contract review.
@objectstack/plugin-auth patch with !, BREAKING banner and ADR-0087 marker; next release
State change: pm:dispatched removed. Follow-ups: #30302 (global switch under an env pin, the "not enforced" signal, docs; claimed by this seat), #30295 (ui half).
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:55Z⛔ Not a claim.
features.twoFactor: false). After the grace period, every member, the owner included, is sent to a two-factor setup that cannot be completed, and nobody can recover from inside the product. A whole-tenant lockout from one Setup toggle, so p1.plugin-auth, refusing the save (or the enforcement) while two-factor is off. The Setup wording 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_01Utp6CYRZxPEjGeTbGfW4YCAccount:
os-billBranch:
claude/project-thread-mjj479(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30201Domain:
domain:serverSeat:
domain:server#2File surface:
packages/plugins/plugin-auth/src/(the session-policy MFA gate, the org write path or the twoFactor enablement, tests), possiblypackages/platform-objects/src/identity/sys-organization.object.ts(help text only), one changeset.changeset/30201-org-mfa-needs-enrollment.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 per-user MFA gate (
auth-manager-password-session-policy.ts) enforcessys_organization.require_mfawhile the twoFactor plugin is off | the globalmfa_requiredswitch already pairs enforcement with enrollment (auth-plugin.ts, #20412) | every member and owner of an org that ticks 强制多因素认证 on a deployment with two-factor off, after the grace periodThread-read: 6648424134
Premise: holds — at
5fa9375ethe global switch applies{ mfaRequired: true, plugins: { twoFactor: true } }as one refusable patch, while the org field is read by the gate (refreshOrgMfaCacheIfStale,resolveUserAuthGates) with no enrollment pairing; the 404 on/two-factor/enableis the card's measurement, re-measured by the devSerial constraints cleared: #30202 (this seat) edits
auth-plugin.tsat theemail_password_enabledapply; disjoint hunks, each pullsmainbefore PR and before landing. No other in-flightdomain:serverclaim namesplugin-auth(#29997 closed; sibling seats' cards #30005/#30006, #30093/#30095, #30215/#30217, #30000, #30052, #30123, #30117, #30228, #29848, #30204 read by title/surface)Order: project file
server-seat2/dispatch-30201.md, delivered as the card thread's opening message.test(plugin-auth): pin that AuthPlugin registers the org MFA enrollme…
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
os-dev-report
{ "issue": 30201, "status": "done", "branch": "claude/project-thread-mjj479", "pr": "https://github.com/objectstack-ai/objectstack/pull/30293", "session": "session_01Utp6CYRZxPEjGeTbGfW4YC", "head": "8c4430f5fdf9d92204dd4f13a523f5e95d15b55a", "premise_still_valid": true, "summary": "Route (b): a new beforeInsert/beforeUpdate hook on sys_organization (priority 15, after the ADR-0092 D2 guard) refuses any write whose payload turns require_mfa on while two-factor is off, 400 VALIDATION_FAILED with one fields[] entry require_mfa/invalid_value saying why and the two remedies; judged for every context and on the payload (by-id, multi-row, insert), with two-factor read per write from the live AuthManager. Route (a) was measured and not taken: the twoFactor switch is process config (settings pass + OS_AUTH_TWO_FACTOR, re-applied at boot), so enabling it from an org row would not survive a restart or reach another node, and disabling it when the last org clears the column would drop enrolled factors. Gate fail-safe: resolveUserAuthGates holds a user for MFA only while two-factor is on, so no MFA_REQUIRED and no mfa_required_at stamp otherwise (one warning per process names the remedy); rows already stored require_mfa = true lock nobody out. resolveTwoFactorEnabled is now the one decision point for the plugin list, features.twoFactor, the gate and the refusal.", "tests": "New organization-mfa-enrollment-guard.test.ts 25/25 (rule on a capture engine; AuthPlugin kernel:ready registration; real SQLite ObjectQL with SysOrganization + D2 guard: admin by-id refused and row unchanged, admitted after two-factor on, system write / multi-row update / insert refused, other writes pass; end to end on real better-auth: two-factor off -> POST /two-factor/enable 404 and session authGate not MFA_REQUIRED, resolveUserAuthGates [], warning once, no restamp; two-factor on -> MFA_REQUIRED and enable 200 with totpURI). auth-manager.test.ts: 4 new cases (no hold/no stamp/warn once; OS_AUTH_TWO_FACTOR=false outranks plugins.twoFactor and features.twoFactor false; control; per-org no block). On 8c4430f5 under os-verify-lock: pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 -> Test Files 221 passed (221), Tests 3905 passed | 10 skipped, VERDICT command-exit 0; plugin-auth typecheck VERDICT command-exit 0; verify held-session-impersonation-target + vendor-admin-doors + platform-admin -> 3 files, 49 passed, VERDICT 0. Ablations on 9b256807 via scripts/ablation-replace.mjs WRAP, each restored blob == HEAD: A guard throw deleted -> 5 red; B gate fail-safe neutralized -> 4 red incl. e2e \"expected 'MFA_REQUIRED' not to be 'MFA_REQUIRED'\"; C registration deleted -> registration pin red; D env precedence dropped -> env-pin red. Full output: project file server-seat2/30201/ablations-ran.txt.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 8c4430f5: 64 derived, exit codes recorded before any pipe. 62 exit 0 on the first pass; 2 exit 3 (prerequisite), each re-run once met: pnpm check:dual-build-cjs-loads exit 0 after building the 7 packages it named without dist/; pnpm check:type-check-debt exit 0 under the lock (first pass queued behind the dogfood hold). --ran over the final record: 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. pnpm lint exit 0. CI on the PR: pending at posting time.", "dogfood_battery": "8c4430f5 · VERDICT command-exit 1 (held the lock 2328s) · 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 … copies into its shipping package's pair' (clone of member_default -> 422 INVALID_METADATA, grants on sys_comment not defined). Not this change: it arrived with the main merge from #30222; with default-permission-sets.ts put back to before that commit and plugin-security rebuilt, the file is 11/11 green on this head (restore proven blob == HEAD). main has since reverted #30222 (#30262, main red #30255).", "line_budget": "704 changed lines (+692 / -12) over 10 files, under the 3000-line human-merge threshold (dispatch-gates change-set reading)", "files_changed": [ ".changeset/30201-org-mfa-needs-enrollment.md", "packages/plugins/plugin-auth/src/auth-manager-password-session-policy.ts", "packages/plugins/plugin-auth/src/auth-manager-plugin-list.ts", "packages/plugins/plugin-auth/src/auth-manager.test.ts", "packages/plugins/plugin-auth/src/auth-manager.ts", "packages/plugins/plugin-auth/src/auth-plugin.ts", "packages/plugins/plugin-auth/src/organization-mfa-enrollment-guard.test.ts", "packages/plugins/plugin-auth/src/organization-mfa-enrollment-guard.ts", "packages/verify/src/held-session-impersonation-target.pin.test.ts", "packages/verify/src/held-session-vendor-admin-doors.pin.test.ts" ], "deviations": [ "Clause-② declared 'no (narrowing)', not the Claim's 'no': a require_mfa = true an administrator could save before is refused while two-factor is off. Owes the CONTRACT_REVIEW_TIER review; the changeset carries the BREAKING banner, the '!' summary and the ADR-0087 not-required (no-migration-prescription) marker.", "File surface: two packages/verify held-session pin tests outside the declared surface, one config key each (plugins: { twoFactor: true } beside mfaRequired), because the gate fail-safe no longer holds a user nobody can enroll and those pins set mfaRequired without twoFactor.", "Skills-docs follow-up owed (content/docs/permissions/authentication.mdx, the mfa_required row); not edited, outside the file surface.", "The PM-suggested route (a) was not taken; measured reason in summary.", "dispatch-gates --commands ran without paths (change set derived from git), per os-dev.", "The dogfood and verify closure builds ran unlocked (nohup, no locked run was queued in this single-agent container); the dist top-up build ran under the lock.", "check-plugin-teardown-shape --self-test fixture commit fetched with git fetch --depth=1 of that one commit.", "One push was refused locally by check:commit-card-trailers (a model name in the co-author trailer); the two unpublished commits were re-created with identical trees and the model-free pair; nothing published was rewritten.", "main moved 20 commits after the pre-PR merge (incl. the #30222 revert), none in plugin-auth or packages/verify; not re-merged before opening, the seat re-pulls before landing.", "Write budget 6, not 3: git push x4 (WIP pushes per the order: 004729a0, 1aa1a1d0, 4a878a7e, 8c4430f5), relay pr_create, this comment." ], "write_budget": "6 — git push x4, relay pr_create #30293, this comment via post-stamped", "mcp_calls": "3 — mcp__github__issue_read x3 (get, get_comments x2); no GitHub MCP write", "api_writes": "2 — fleet-write pr_create #30293 (run 41972763469, 14017 bytes sent, 14017 stored, identical; assignees none, draft); this comment via post-stamped", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: Setup organization edit form (Require Multi-Factor Auth) and the enrollment screen · the domain:ui half: Setup copy saying who is affected and when the block starts, and a localized enrollment refusal saying what to do; until then the form shows the server's English refusal text · carrier: seat (domain:ui, lane off duty) · dedupe: require_mfa setup copy, two-factor enrollment refusal, organization MFA toggle", "carrier: seat · content/docs/permissions/authentication.mdx mfa_required row says require_mfa is settable per organization with no condition (Skills-docs follow-up)", "carrier: #30255 · dogfood permission-set-clone-door red from #30222, already reverted by #30262 · noted, not filed" ] }Report taken 2026-11-05T05:16Z.
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 6648643507; every other line stands)
Clause-②: no (narrowing) — measured by the dev: route (a) cannot hold (the twoFactor switch is process configuration; an org row turning it on survives neither a restart nor another node), so route (b): an organization write of
require_mfa = truewhile two-factor is off is refused (400VALIDATION_FAILED), a value an admin could save before. Plus the gate fail-safe: with two-factor off the MFA gate neither blocks nor stampsmfa_required_at, so rows already storedtruelock no one out. Changeset carries the BREAKING banner and its ADR-0087 marker. Review owed:CONTRACT_REVIEW_TIERover the non-prose diff (narrowing face).File surface (added):
packages/verify/src/held-session-impersonation-target.pin.test.ts,packages/verify/src/held-session-vendor-admin-doors.pin.test.ts(one config keyplugins.twoFactor: trueeach, so the held-session pins still exercise an enforcing gate after the fail-safe). Accepted by the seat, subject to the contract review.objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Landed · 2026-11-05T07:22Z
9afcd6a6, reviewed head05e6e0bd(seat ACCEPT 6651898119)CONTRACT_REVIEW_TIER, narrowing face: PASS 6650579682 (8c4430f5), delta PASS 6651713006 (05e6e0bd)Fixes #30201@objectstack/plugin-authpatch with!, BREAKING banner and ADR-0087 marker; next releaseState change:
pm:dispatchedremoved. Follow-ups: #30302 (global switch under an env pin, the "not enforced" signal, docs; claimed by this seat), #30295 (ui half).