Skip to content

Organization record: turning on "Require MFA" shows the grace period twice, and the console's line says "7 days" even when Setup set another number #30565

Closed

Description

@objectstack-fleet

domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

Filing gate: ① a product defect with a named location, measured by the #30411 dev over HTTP (showcase --fresh, 11-05 ~12:05Z) and confirmed by the seat reading the cited code on main after PR #30529 merged (e8e924f07). ⛔ Not a claim. Triage sets the grade. Suggested grade: p2 (an administrator can read a wrong number of days).
Dedupe: organizationMfaSavedNotice, nowRequired grace. Nearest are #30295 (closed, it added the console line) and #30411 (closed by PR #30529, it added the server notice). New.

What happens

Since PR #30529, the server answers an accepted require_mfa turn-on with the record save's notice, naming the deployment's real MFA grace period (measured: "7 days", then "3 days" after PUT /api/settings/auth).

The console then adds its own line on top: ui/packages/app-shell/src/console/AppContent.tsx handleRecordFormSuccess (~L1061) joins claimed.notice and organizationMfaSavedNotice(...) with a newline. That console line, console.organizationMfa.nowRequired in ui/packages/i18n/src/locales/*.ts, hard-codes "7 days unless changed in Setup → Authentication".

So the administrator sees the grace period twice. When Setup set 3 days, one line says 3 and the other says 7.

Expected

When the save answer carries the server's notice, the toast shows that notice once and drops the console's own grace-period sentence (or keeps only the part the server notice does not say). With no server notice (an older server), the console line stays as it is. Pin both cases in organizationMfaNotices-30295.test.tsx or beside it.

Reader: domain:ui (console). No server change is needed. Links: #30411, PR #30529, #30295.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:uiDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU

    Claim: PM loop round 7 · 2026-11-05T16:29Z
    Session: session_01L6bhgVfPeFZkN1FP93imKU (mode:subagent: the dev shares the seat's session; its identity is the branch)
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-30565-mfa-notice-once
    Worktree: objectstack-issue-30565
    Domain: domain:ui
    Seat: domain:ui#4 (by the ui seats' last-digit split)
    File surface: ui/packages/app-shell/src/console/AppContent.tsx (handleRecordFormSuccess, the join of claimed.notice and organizationMfaSavedNotice), ui/packages/app-shell/src/console/organizationMfaNotices.ts, ui/packages/app-shell/src/console/__tests__/organizationMfaNotices-30295.test.tsx or a test beside it; the console.organizationMfa.* keys in ui/packages/i18n/src/locales/*.ts only if a sentence must change, then in every shipped locale; one root .changeset/ file. ⛔ packages/** (the server notice is PR #30529's, landed)
    Container & model: S, mode:subagent, model: default (dispatch-gates --tier at f5aaa0bc: no path-derived mandate; os-dev's pinned floor is opus), effort: medium
    Clause-②: no: a console toast join
    Metadata-protocol: none
    Responsibility: an accepted require_mfa turn-on toasts the server's notice AND the console's console.organizationMfa.nowRequired line, which hard-codes "7 days" | since PR #30529 the save answer's notice names the deployment's real grace period, who must enroll and that enrolled members are unaffected (packages/plugins/plugin-auth/src/organization-mfa-enrollment-guard.ts ORGANIZATION_MFA_REQUIRED_NOTICE_COPY) | administrators who turn on Require MFA on an organization
    Thread-read: none
    Premise: holds, read at origin/main f5aaa0bc: AppContent.tsx:1070 builds notice: claimed?.notice ? ${claimed.notice}\n${mfaNotice} : mfaNotice, and the server copy says everything the console line says. The dev re-measures the doubled toast before editing and keeps the console line for an answer with no server notice (an older server).
    Serial constraints cleared: no open PR touches these files (read across the 40 open PRs just now).


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU
    os-dev-report

    {
      "issue": 30565,
      "status": "done",
      "branch": "claude/issue-30565-mfa-notice-once",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30592",
      "session": "session_01L6bhgVfPeFZkN1FP93imKU",
      "premise_still_valid": true,
      "summary": "Premise re-measured on origin/main 27b0bc79: AppContent's join, run verbatim through toastRecordSave with the en catalogue and the server's 3-day notice, gives one toast whose description has 2 lines, one saying 3 days and one saying 7 days. Fix: organizationMfaSavedNotice takes an optional answerNotice (the claim's notice) and returns undefined when that notice is the server's MFA notice; AppContent passes claimed?.notice, and the join below is unchanged, so the toast carries the server notice once. Rule: the notice contains the server copy's opening sentence in one of its four languages (en, zh-CN, ja-JP, es-ES; the N-day and 0-day sentences share it). A presence-only check is wrong because the answer carries ONE notice, last call wins, and the MFA guard sets its notice at beforeInsert/beforeUpdate priority 15, so a later hook or a flow save_notice replaces it, and presence would then drop the only MFA sentence. When the opening is not recognized, both lines show, never neither. No notice: the console line, as before. No i18n change.",
      "tests": "New top-level describe in ui/packages/app-shell/src/console/__tests__/organizationMfaNotices-30295.test.tsx (7 cases: 5 server-notice fixtures en 3d / en 0d / zh-CN 3d / ja-JP 0d / es-ES 3d, each checked byte-identical to ORGANIZATION_MFA_REQUIRED_NOTICE_COPY[locale](days) evaluated from the server source at this head; no notice keeps the console line; another hook's notice keeps the console line). On base: 5 failed / 9 passed. On head 8aa05756: pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/console/__tests__/organizationMfaNotices-30295.test.tsx: Test Files 1 passed (1), Tests 14 passed (14). Under the verify lock: packages/app-shell/src/console/__tests__/ Test Files 75 passed (75), Tests 616 passed (616), VERDICT command-exit 0; the 28 other test files that import AppContent: Test Files 28 passed (28), Tests 259 passed (259), VERDICT command-exit 0. Ablation 1 (scripts/ablation-replace.mjs, delete the isServerMfaNotice guard line, anchor x1 to x0, blob 63b87c8c to c82a26b0): 5 failed / 9 passed, the five server-notice cases red; restored, blob 63b87c8c equals HEAD, git diff HEAD empty. Ablation 2 (guard replaced by a presence check): 1 failed / 13 passed, the other-hook-notice case red; restored, blob equals HEAD, git diff HEAD empty. No dist involved: the test imports the module by relative path.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on head 8aa05756 derived 35; all 35 run, each exit 0: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0; node scripts/check-adr-0087-registration.mjs --self-test :: exit 0; node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0; node scripts/check-closing-keyword-parity.mjs :: exit 0; node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0; node scripts/check-comment-mask-corpus.mjs :: exit 0; node scripts/check-empty-changeset.mjs --base origin/main :: exit 0; node scripts/check-empty-changeset.mjs --self-test :: exit 0; node scripts/check-sdui-manifest.mjs :: exit 0; node scripts/check-sdui-manifest.mjs --self-test :: exit 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0; node scripts/release-pending-publish.mjs --self-test :: exit 0; pnpm --dir ui exec vitest run scripts/ :: exit 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0; pnpm check:changeset-gate-self-tests :: exit 0; pnpm check:closed-ceremony-text :: exit 0; pnpm check:cross-package-test-inputs :: exit 0; pnpm check:dispatcher-error-vocabulary :: exit 0; pnpm check:driver-memory-census :: exit 0; pnpm check:engine-double-contract :: exit 0; pnpm check:gitlink-declared :: exit 0; pnpm check:nul-bytes :: exit 0; pnpm check:published-files :: exit 0; pnpm check:query-options-erasure :: exit 0; pnpm check:refd-timer-probe :: exit 0; pnpm check:registry-log-declared :: exit 0; pnpm check:rest-log-spy-declared :: exit 0; pnpm check:skill-identifier-liveness :: exit 0; pnpm check:test-source-alias :: exit 0; pnpm check:tier-file-adoption :: exit 0; pnpm check:type-check-coverage :: exit 0; pnpm check:type-check-debt :: exit 0; pnpm check:undeclared-dep-imports :: exit 0; pnpm check:watch-hint-literal :: exit 0; pnpm check:where-matcher :: exit 0. --ran reconciliation :: exit 0, 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Also: pnpm --dir ui run check:spec-symbols :: exit 0; ui check-new-cross-file-line-citations :: exit 0 (0 new); ui check-control-bytes :: exit 0; turbo run type-check --filter=@object-ui/app-shell under the lock :: VERDICT command-exit 0 (cache miss on app-shell, tsc --noEmit and tsc -p tsconfig.test.json); eslint on the 3 changed source/test files :: exit 0, 0 errors (the warnings are on AppContent.tsx lines this PR does not change). CI on PR #30592 at report time: 17 check-runs in_progress, 3 skipped, none concluded.",
      "line_budget": "+102 / -1 over 4 files: .changeset +12; AppContent.tsx +3 / -0; organizationMfaNotices.ts +36 / -1; organizationMfaNotices-30295.test.tsx +51. No line-ratchet gate reads these files.",
      "files_changed": [
        ".changeset/30565-organization-mfa-notice-once.md",
        "ui/packages/app-shell/src/console/AppContent.tsx",
        "ui/packages/app-shell/src/console/organizationMfaNotices.ts",
        "ui/packages/app-shell/src/console/__tests__/organizationMfaNotices-30295.test.tsx"
      ],
      "deviations": [
        "The pins are a top-level describe in the existing organizationMfaNotices-30295.test.tsx, not a new -30565 file: AGENTS.md puts a new pin for a component with a shared harness file into that file (ruling record 6583487846). The brief allowed either.",
        "The premise probe was a throwaway test file in the worktree, deleted before the commit (its reading is in the PR body).",
        "test:repo (vitest repo project) not run locally; the diff does not touch it and the derived list does not name it; CI Test Core runs it.",
        "Full pnpm test of @object-ui/app-shell (2205 test files) not run locally; ran console/__tests__ (75 files) plus the 28 other files that import AppContent, the reach of the one module changed. CI Test UI runs the rest."
      ],
      "dogfood_battery": "n/a — no ! in the PR title and Clause-②: no, no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — both through the fleet-write relay as objectstack-fleet[bot]: pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 42070647669, read-back 10631 bytes identical, PR #30592) and this os-dev-report comment (post-stamped.mjs). 0 direct REST writes. git push x2 (empty branch probe, then 8aa05756) is not a REST write.",
      "open_questions": [
        {
          "question": "Pin the AppContent wiring line (answerNotice: claimed?.notice) on a mounted AppContent?",
          "options": [
            "A: add a top-level describe to AppContent.recordSaveOneToast-27999.test.tsx (the shared record-save toast harness), which means widening the claim's file surface",
            "B: leave it pinned at the function level as now"
          ],
          "recommendation": "A as a follow-up round if the seat widens the surface: removing that argument today reds nothing. The decision itself is pinned and ablated."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — the server's MFA notice and refusal copy (plugin-auth organization-mfa-enrollment-guard.ts) is written in 4 languages; the console ships 10. de/fr/ko/pt/ru/ar administrators now read only the English server notice where they used to read it followed by the console's localized (7-day) line. Adding those six languages to ORGANIZATION_MFA_REQUIRED_NOTICE_COPY is the server lane's. Dedupe words: ORGANIZATION_MFA_REQUIRED_NOTICE_COPY locales, copyLocaleFor English fallback, MFA notice language.",
        "carrier: none · noted, not filed — the four opening sentences are a second copy of server prose in the console, and no gate checks the two against each other (a ui test cannot read packages/** without a cross-package-test-inputs declaration). If the wording changes, the toast falls back to both lines, never neither. A structured code on the write answer's notice would remove the copy, but that is a protocol change."
      ]
    }
  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU
    os-dev-report

    {
      "issue": 30565,
      "status": "done",
      "round": 2,
      "branch": "claude/issue-30565-mfa-notice-once",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30592",
      "head": "9dee7aa4d0 (fix 8aa05756 unchanged; 9dee7aa4 adds only the mount-level pin)",
      "session": "session_01L6bhgVfPeFZkN1FP93imKU",
      "premise_still_valid": true,
      "summary": "Round 2, the seat's order: the AppContent wiring (answerNotice: claimed?.notice) is now pinned on a mounted AppContent. A new top-level describe in ui/packages/app-shell/src/console/__tests__/AppContent.recordSaveOneToast-27999.test.tsx uses the file's own saveFrom harness. The harness gains a sys_organization object, a findOne on its adapter stub (stored require_mfa false), and saveFrom now checks the overlay edits the object the write names (event.resource; every existing case names showcase_task, so they assert the same value). An edit of org_1 saves require_mfa true: with the server's English 3-day notice on the answer, ONE success toast whose description is that notice alone; CONTROL no notice: the console line; CONTROL another hook's notice: that notice, then the console line.",
      "tests": "On 9dee7aa4: the two files (27999 harness + organizationMfaNotices-30295): Test Files 2 passed (2), Tests 26 passed (26). packages/app-shell/src/console/__tests__/ under the verify lock: Test Files 75 passed (75), Tests 619 passed (619), VERDICT command-exit 0. Ablation 3 (scripts/ablation-replace.mjs, delete the answerNotice: claimed?.notice line in AppContent.tsx, anchor x1 to x0, blob b8669035 to adc800b5): 1 failed / 11 passed, the server-notice case red, both CONTROLs and the nine earlier cases green; restored, blob b8669035 equals HEAD, git diff HEAD empty.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 9dee7aa4 (5 paths) derives the same 35 commands as on 8aa05756: no new family. All 35 re-run on 9dee7aa4, each exit 0; --ran reconciliation :: exit 0, 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. turbo run type-check --filter=@object-ui/app-shell under the lock :: VERDICT command-exit 0 (cache miss on app-shell, both tsc programs, test files compiled). eslint on the 27999 harness :: exit 0, 0 problems. CI not waited on.",
      "line_budget": "round 2: +65 / -1 in AppContent.recordSaveOneToast-27999.test.tsx; PR total +167 / -2 over 5 files.",
      "files_changed": [
        "ui/packages/app-shell/src/console/__tests__/AppContent.recordSaveOneToast-27999.test.tsx"
      ],
      "deviations": [
        "PR body NOT patched by the dev: .claude/agents/os-dev.md says the dev writes the PR body once, at pr_create, never PATCHes it, and names the change in the report for the seat to write; on a conflict with a dispatch order that file wins. The patched body is ready and dry-run validated (trailer-signed issue_patch on #30592, GET confirms a pull request): it adds the mount pin and ablation 3 to Tests and Verification, moves the head to 9dee7aa4, rewrites the stale mount-level acceptance note, re-lists the gates on 9dee7aa4, and drops the session footer (edit side: the platform appends its bare footer); its last line is the signed trailer. The seat sends it with one dispatch.mjs issue_patch call.",
        "The worktree had been removed after round 1; it was re-created on the same branch at 8aa05756 (equal to origin) before this round."
      ],
      "dogfood_battery": "n/a — no ! in the PR title and Clause-②: no",
      "mcp_calls": "0",
      "api_writes": "1 this round — this comment through post-stamped (fleet-write relay). git push of 9dee7aa4 is not a REST write. The PR body issue_patch was dry-run only, nothing sent.",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "unchanged from round 1"
      ]
    }
  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU

    Landing record · 2026-11-05T17:52Z

    PR #30592 merged 2026-11-05T17:51Z as 5b748dfede (seat ACCEPT 6664869362 on head 9dee7aa4, TIER_DEFAULT review in it), Fixes #30565.

    probe reading
    git grep -n "const SERVER_MFA_NOTICE_OPENINGS" origin/main -- ui/packages/app-shell/src/console/organizationMfaNotices.ts :70 (the console tells the server's MFA notice by its opening sentence, in the four languages the server writes it in)
    git grep -n "answerNotice: claimed?.notice" origin/main -- ui/packages/app-shell/src/console/AppContent.tsx :1067 (an accepted turn-on toasts the server's notice once; the console's "7 days" line only when the answer carries none)
    card state closed (completed) by the merge

    State change: pm:dispatched removed; nothing remains on this card. The server copy is written in four languages (CopyLocale in plugin-auth), so an administrator in de, fr, ko, pt, ru or ar now reads the English server notice alone: that is the server half of ruling F, carried by a note on #30512, not filed here.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions