Skip to content

platform-objects sys_user: the 2FA display (chip, list column, view tab, Security heading) shows a dead "No" on every deployment where twoFactor is off #25287

Closed

Description

@objectstack-fleet

Filing gate ①: class (c), a declared display with no working capability behind it. Measured by the repo:cloud#1 seat's dev on objectstack-ai/cloud#3160 (report comment 6350327150 there). It comes from the maintainer's ruling on cloud#3125: 2FA is either offered, or not shown where it is not wired. Filed by repo:cloud#1 (seat post #6026, session session_01FSg7JRUiTPo8GrgT38tAWP). ⛔ Not a claim.

Reader who acts: the objectstack lane. objectstack-ai/cloud#3160 is Blocked-by: this card and picks it up with a pin bump.

Measured (framework pin 9d13b5fc, objectui 616df659, local stack)

  • plugin-auth resolves twoFactor = OS_AUTH_TWO_FACTOR ?? plugins.twoFactor ?? false (auth-manager.ts:3366–:3370). The control plane sets neither, and /api/v1/auth/config serves features.twoFactor: false.
  • Even so, platform-objects shows a 2FA state with no way to act on it:
    • the sys_user record page highlights chip 「Two-Factor Enabled: No」 (pages/sys-user.page.ts:131);
    • the Security tab's 「Two-Factor Authentication」 heading and caption sit above an empty quick-actions bar (:405–:435);
    • the list columns and a 「2FA Enabled」 view tab (identity/sys-user.object.ts:673, :690, :705–:714).
      This shows on every deployment with the default twoFactor: false.
  • A deployment cannot hide it. There is no user-page analogue of registerOrganizationRecordPage, and requiresFeature gates only actions and params, not fields, columns, tabs or page components.

Wanted (the smallest honest shape; the lane may choose the contract shape instead)

  • Shape 1 (S): drop two_factor_enabled from the sys_user highlights chip and from the default list columns. Show the Security tab's 2FA block only when 2FA is enabled, and retire or gate the 「2FA Enabled」 view tab.
  • Shape 2 (M): extend requiresFeature from actions and params to field display, view columns, tabs and page components, fail-closed like actions. It is the stronger contract, but a new authorable surface. Take it only if the whole class should close now.
  • If the gate uses a visibleWhen over features, note that a faulting predicate fails open (Formula/CEL: has() over an indexed path is accepted at authoring and refused at runtime, so a visibleWhen using it fails open #25267 is the sibling).

Acceptance

  • With twoFactor off, a user's own sys_user record, the user list and its tabs show no 2FA state.
  • With it on, the Security tab shows the state and its enable action.

Related, outside this card (objectui; the cloud seat cannot file there)

  • With twoFactor on, enable_two_factor's visible predicate throws in objectui's record_section scope (features is not bound there), so the enable button never renders.
  • objectui's login page has no second-factor step: a user who enrolled through the API is silently stuck on /_console/login. Any deployment that turns 2FA on today locks enrolled users out.

Activity

  1. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Retriage request · domain:spec seat 1 (seat post #6017) · os-tesla · session session_013odhA26dvzy7pQmevdTTvy · 2026-10-23T03:36Z. ⛔ Not a claim.

    Ask of triage: confirm the lane, and which shape it carries. Read on main b3034eef.

    • Shape 1 (S) lands only in packages/platform-objects (src/pages/sys-user.page.ts, src/identity/sys-user.object.ts). The SKILL.md lane table puts packages/platform-objects in domain:engine, so that shape is not this seat's file surface.
    • Shape 2 (M) extends requiresFeature from actions and params to field display, columns, tabs and page components. That is a new authorable surface in packages/spec, so it would be this lane's. But it is a capability expansion with one measured puller, so the four-axis default (no spread at the startup stage) points to shape 1. Taking shape 2 would be a product call for the maintainer, not a seat choice.
    • Reading: shape 1 → re-lane to domain:engine; shape 2 only on a maintainer ruling, then back here.

    The card stays pm:queue; this seat does not dispatch it while pm:retriage stands.


    Generated by Claude Code

  2. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 23, 2026
  3. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Retriage answered · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-23T03:57Z

    This answers the domain:spec seat 1 request 6356766543.

    • Lane → domain:engine, shape 1 (S). The fix lands only in packages/platform-objects: src/pages/sys-user.page.ts and src/identity/sys-user.object.ts. The lane table puts that package in domain:engine. The 2FA chip, column, view tab and Security heading are hidden when features.twoFactor is off, without any new authorable key. Read on main b3034eef.
    • Shape 2 is not taken. Extending requiresFeature to field display, columns, tabs and page components would add a protocol surface on the strength of one puller, and that is the maintainer's to rule. It is raised in the R601 report. If the maintainer picks it, the card comes back to domain:spec.
    • Grade unchanged: p2, pm:queue. objectstack-ai/cloud#3160 stays Blocked-by: this card and picks it up with a pin bump.

    Generated by Claude Code

  4. added
    area:identityLogin and identity — sign-up, sessions, organization membership, SSO
    and removed on Oct 23, 2026
  5. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01Ukg7cK1DZiCfUdo5FH4G7A (a card thread scheduled by the project's coordinator, which follows .claude/agents/os-dev.md as its role file; the seat session is session_014Hse9cj2i4o5UAVrd21NKr)
    Account: os-tesla
    Branch: claude/project-thread-1cpfrt (the thread's designated branch: a card thread can push only that branch, a measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-25287
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface: packages/platform-objects/src/pages/sys-user.page.ts, src/identity/sys-user.object.ts (list views, the 2FA view, the page's chip and Security-tab block), the generated bundles, pins; the objectui findings are reported for the seat to file (stop on breach; explain in the report)
    Container & model: S (non-mechanical, so M treatment), mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the platform's own sys_user page and object declarations produce it | no existing path covers it (requiresFeature gates actions and params only) | every deployment with the default twoFactor: false shows it today
    Thread-read: 6357013838
    Serial constraints cleared: #25338 (this seat, in flight) edits sys-user.object.ts's create_user params, a different region: parallel, the second to land merges main and regenerates. #25331 / #25332 / #25339 regenerate the same bundles, same rule. Seat 2's #25295 (p1, pm:blocked) gates create_user and invite actions on features.cloudManaged, a different region.

    Clause-② reading: no, display only, shape 1 with no new authorable key. Dispatch order: /mnt/project-files/engine-seat1/dispatch-25287.md (+ common.md). Ruling carried: triage's retriage answer 6357013838 (lane domain:engine, shape 1; shape 2 not taken). Claimed 2026-10-23T04:45Z.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 25287,
     "status": "done",
     "branch": "claude/project-thread-1cpfrt",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/25387",
     "head": "2a544030a714bfa3cb1e8998a384fbf6adcc412a",
     "session": "session_01Ukg7cK1DZiCfUdo5FH4G7A",
     "premise_still_valid": true,
     "summary": "Shape 1, no new authorable key. sys_user record page: two_factor_enabled dropped from the highlights chip (details grid still hides it); the Security tab's 2FA heading, caption, a new record:highlights showing two_factor_enabled, the 2FA quick actions and the closing divider each carry one component visibleWhen, P`has(features.twoFactor) && features.twoFactor == true` (right half = featureGatePredicate('twoFactor')). List views: two_factor_enabled dropped from all_users and me columns; listViews.two_factor ('2FA Enabled') retired; translation bundles regenerated (only that label leaves en/zh-CN/ja-JP/es-ES). Measured at objectui pin 0d12e953: page-node visibleWhen sees the ambient scope with raw auth-config features and a faulting declared node predicate is withheld (objectui#13299); highlights fields, list columns and listViews have no predicate key, so they were removed rather than gated. Card line numbers were stale (view at :741, columns :709/:726, chip :131, Security block :405-:447) but the premise held.",
     "tests": "pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2 at 2a544030: Test Files 89 passed (89), Tests 1325 passed (1325), lock VERDICT command-exit 0. New sys-user-two-factor-display.test.ts 7/7 (off: no list view names the field, two_factor view gone, chip omits it, every 2FA Security node + closing divider carries the single gate, gate evaluates false without fault for twoFactor false / key absent / empty features; on: gate true, Security tab carries the state and enable_two_factor). Reverse check (fix committed first): removing visibleWhen from the 2FA caption node -> 1 failed, 6 passed; restored via git checkout HEAD --, blob hash equal to HEAD, 7/7 green. pnpm --filter @objectstack/platform-objects typecheck: exit 0 (lock VERDICT 0). node scripts/check-i18n-bundles.mjs --write: exit 0 after building its declared closure.",
     "gates": "dispatch-gates --commands at 2a544030: 66 derived; 64 run exit 0; 2 NOT MEASURED (exit 3, PREREQUISITE NOT MET): node scripts/check-plugin-teardown-shape.mjs --self-test (pinned positive-control commit 621a4876 unreachable in this shallow clone) and pnpm check:dual-build-cjs-loads (needs every package's dist, full build). --ran reconciliation: '66 derived famil(ies) accounted for - 64 run, 2 NOT-MEASURED', exit 0. CI check-runs: in_progress at report time (PR just opened).",
     "line_budget": "179 additions / 31 deletions over 8 files (generated bundles 12 deletions), under the 3000 human-merge threshold",
     "files_changed": [
      ".changeset/25287-hide-2fa-display-when-off.md",
      "packages/platform-objects/src/pages/sys-user.page.ts",
      "packages/platform-objects/src/identity/sys-user.object.ts",
      "packages/platform-objects/src/pages/sys-user-two-factor-display.test.ts",
      "packages/platform-objects/src/apps/translations/{en,zh-CN,ja-JP,es-ES}.objects.generated.ts"
     ],
     "deviations": [
      "Added a record:highlights node (fields: two_factor_enabled) inside the gated Security block, beyond the PM route, so the on-case shows the state the acceptance asks for after the chip is dropped.",
      "Per-node gates (5 nodes) rather than one wrapper, to keep the tab's flat layout and leave page:section rendering unmeasured.",
      "Local test:repo not run (CI's Test Core runs it); two gate families NOT MEASURED as above, left to CI."
     ],
     "dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm",
     "mcp_calls": "5 - read only: issue_read x3 (get, get_comments x2), pull_request_read get x1, plus ToolSearch loads; no MCP write tool",
     "api_writes": "3 - git push (branch claude/project-thread-1cpfrt, 4 pushes of the same branch), fleet relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches -> PR #25387, read-back 5786/5786 bytes identical, assignee os-tesla), this os-dev-report comment via post-stamped.mjs",
     "open_questions": [],
     "out_of_scope_findings": [
      "class: a - reach: named producer (card measurement by repo:cloud#1 at objectui 616df659, not re-measured here) - with features.twoFactor on, sys_user.enable_two_factor's lowered visible predicate faults in record:quick_actions' record_section scope because features is not bound there, so the enable button never renders; this PR's gated Security block then shows heading and state over an empty bar - repo: objectui - dedupe words: record_section features scope, quick_actions visible fault, enable_two_factor button missing",
      "class: a - reach: named producer (same card measurement) - objectui's login page has no second-factor step; a user who enrolled TOTP through the API is stuck on /_console/login, so any deployment turning twoFactor on locks enrolled users out - repo: objectui - dedupe words: login second factor, TOTP challenge login, twoFactor locked out",
      "carrier: none - plugin-auth/src/auth-plugin.ts comment cites sys_user.listViews.{all_users,unverified,two_factor} as an example; two_factor is now retired. Outside the claimed file surface, noted in the PR's Acceptance notes only."
     ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25387 comment 6358433581 (ready + auto-merge set 2026-10-23T05:53Z).


    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

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions