Skip to content

Offboarding: banning a user says nothing about their open work, and there is no way to hand it over #30205

Open

Description

@objectstack-fleet

epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

From a real-user walkthrough of Setup, filed on the maintainer's word: 「以上都同意,立卡」 (2026-11-05). Measured on main b7075d9908, with the console in dev mode, unless a line says otherwise. Test app scaffolded with create-objectstack@18.3.0.

The maintainer agreed the seat's recommendation, so this is not a decision card.

What a customer sees

An employee leaves. An administrator bans the account. The tickets they own and the tickets assigned to them stay where they are, nobody is told, and customers' work stalls. The administrator has to find and reassign each one.

Measured

  • The ban dialog says only 「封禁后会立即登出,且在解除封禁前无法再次登录」.
  • After the ban: 3 tickets still owned, 3 still assigned.
  • The Console's person picker excludes the banned user (banned != true). POST /api/v1/data/asset_repair_ticket with assignee set to the banned user still answers 201.

Expected

  • The ban dialog states how many records the person owns and has assigned, across objects the administrator can read.
  • It offers one optional step to transfer them to someone, in bulk. It does not block the ban, because a ban can be urgent.
  • A new assignment to a banned user is refused at the server, not only hidden in the picker.

Open for the dev to report if it proves expensive: which objects count, given that "assignee" is an app's own user field and not a platform column.

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · domain:server · pm:queue · 2026-11-05T02:58Z

    ⛔ Not a claim.

    • Read fresh. Banning a user says nothing about their open work, and there is no way to hand it over. The card says the maintainer agreed the seat's recommendation. An offboarding gap, so p2.
    • Platform first, so domain:server (the reassignment). The Setup flow is the domain:ui half.

    Generated by Claude Code

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

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

    Claim: PM loop round 2
    Session: session_01Utp6CbPxYq1wQmUNWvMZkC (seat) · dev = project card thread session_01Utp6CZavYYLh5k5r6jhgCF
    Account: os-bill
    Branch: claude/project-thread-dvfr9q
    Worktree: objectstack-issue-30205
    Domain: domain:server
    Seat: domain:server#3
    File surface: the ban / user-admin server path, the data-layer check of a user-lookup write, a read endpoint for per-user counts, tests, changesets, content/docs/** if a page documents banning; ui/** out (the Setup dialog is the ui half); seat 2's in-flight plugin-auth files (auth-plugin.ts bindAuthSettings/applySettings, sign-in-method-floor.ts, allowed-ip-ranges.ts, organization-mfa-enrollment-guard.ts, auth-manager.ts) and service-settings/** only after the seats agree an order; the dev's step-1 plan refines this before code — 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: high (M)
    Clause-②: no — expected; a refusal of assigning to a banned user may make it no (narrowing), measured by the dev and reviewed at contract tier if so
    Metadata-protocol: none
    Responsibility: domain:server seat 3; server half of the maintainer-agreed offboarding card; the Setup dialog is domain:ui
    Thread-read: 6648459408
    Premise: holds
    Serial constraints cleared: none

  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-bill · session_01Utp6CZavYYLh5k5r6jhgCF
    os-dev-report

    {
      "issue": 30205,
      "status": "done",
      "branch": "claude/project-thread-dvfr9q",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30546",
      "session": "session_01Utp6CZavYYLh5k5r6jhgCF",
      "premise_still_valid": true,
      "summary": "Server half of #30205, all three bullets, draft PR #30546 at head 6d576db52 (29 files, +1473/-13). (1) The write-time reference check refuses a caller-supplied user value whose ban is in force with the new FieldErrorCode reference_user_banned, read elevated like the existence probe; a value the row already stores is not judged. (2) GET /api/v1/security/users/:id/assigned-work counts per object and user field under the caller's context. (3) POST .../assigned-work/transfer moves up to 500 rows per call as ordinary updates under the caller's context, reporting per-field failures and what remains; a banned, unknown or identical target is refused before any write. The rules live in @objectstack/core (rest cannot import objectql at runtime); engine.ts changes only in the reference-check host literal (4 lines). After merging main, the branch reuses main's isUserBanInForce / USER_BAN_FIELDS (#30287) instead of its own copy, so the PR body's sentence 'objectql takes isUserBanInForce from the same file' should now read 'from core's security/user-ban.ts (#30287)'. That is the one body edit for the seat to make.",
      "tests": "objectql engine-banned-user-reference 9/9; core security 64 files / 1232; rest 393 / 7650 (353 skipped); client admin-surfaces 10/10; dogfood authz-conformance + showcase-anonymous-deny-surfaces 139/139 (all on the merged head 30927981 or later; objectql re-run on 6d576db52). Real boot before/after on the showcase (owner_id of showcase_announcement): a new assignment to a banned user went 201 -> 400 reference_user_banned; count and transfer went 404 -> 200 (count 2, transfer updated 1, failed 1 RECORD_LOCKED, remaining 1); transfer to a banned target 404 -> 400. 'Before' = ban push and route registration ablated and rebuilt, dist preflight absent then present after restore. Four ablations via ablation-replace.mjs wrap mode, each red and restored: ban push (4/9 red), count filter (count test red), banned-target guard (2 red), rest failure describer (hook-refusal ledger row red on every section-6 shape).",
      "gates": "dispatch-gates --commands derived 128 families on head 6e44006f9; all 128 ran and --ran reconciles 128/128 at exit 0 (8 were red on first run and were fixed: ADR-0087 marker vs a FROM -> TO line, engine-double-contract, objectql-double-limit, route-envelope, where-matcher, query-options-erasure, spec check:generated heap OOM, plugin-teardown-shape self-test needing its pinned commit fetched). After the main merge, re-ran nul-bytes, adr-0087, engine-double-contract, where-matcher, route-envelope, spec check:liveness and error-code-casing (red once on the new literal; marked adr0112-ok D6, field-addressed) at exit 0. Not re-run after the merge: the full 128 set. CI check-runs not yet read.",
      "dogfood_battery": "6e44006f9 · os-verify-lock VERDICT command-exit 0 · Test Files 419 passed / 3 skipped (422) · Tests 3496 passed / 36 skipped (3532) · no failures. The earlier full run on 13e254df had 11 red, all in authz-conformance.test.ts (the new REST family unclassified), fixed by the anonymous-deny-user-work matrix row and its booted proof. Not re-run in full after the main merge; the authz files were (139/139).",
      "mcp_calls": "4 — mcp__github__issue_read x3, mcp__github__pull_request_read x1; no write tool",
      "api_writes": "11 — git push x9 to claude/project-thread-dvfr9q; POST /repos/objectstack-ai/objectstack/dispatches x1 (fleet-write pr_create, read back identical, no assignees); this comment via post-stamped.mjs x1",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

    ACCEPT (server half) → PR #30546 comment 6666852255 · 2026-11-05T19:11Z · ready + auto-merge (BREAKING narrowing, not governed, 1521 lines; contract review 6662784185 FAIL, delta 6665247637 PASS at a5de9d1e; dogfood battery pinned to the head). Part of #30205: this card stays open for its ui half, which returns to the ui lane after the merge.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

    Landing (server half)

    Probe Reading
    PR #30546 merged 2026-11-05T19:41Z, merge commit 2046d21b5
    Head reviewed a5de9d1e; contract review 6662784185 (FAIL), delta 6665247637 PASS, seat record 6666852255; dogfood battery pinned to the head
    Card stays open (Part of #30205); the server half is done
    Wrongly closed cards none
    Follow-ups none filed

    Release: ui half

    The server half is on main:

    • a non-system write that newly assigns a user whose ban is in force is refused 400 VALIDATION_FAILED / reference_user_banned;
    • the assigned-work count and handover routes ship in packages/rest/src/user-work-routes.ts, with their client methods in @objectstack/client.

    What remains is the ui half: the ban dialog shows the user's assigned-work counts and offers the handover. This card goes back to the queue for the ui lane: pm:dispatched and the assignee are removed, domain:server becomes domain:ui, and pm:queue is added. The server lane holds nothing further on it.

    Resume-from: none (the ui half starts from main at 2046d21b5).

    Landed · 2026-11-05T19:42Z.

  7. added
    domain:uiDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 5, 2026
  8. 9 remaining items

  9. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

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

    {
      "issue": 30205,
      "status": "done",
      "branch": "claude/project-thread-4vnhy6",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30652",
      "session": "session_01L6bhgV7XEtNA8u7WA2q1XZ",
      "premise_still_valid": true,
      "summary": "The ui half of #30205, all three asks, draft PR #30652 at head bfe597f0 (Fixes #30205, the server half having landed in PR #30546). The sys_user ban_user dialog gains a host-composed companion (ParamDialogState.companion, chosen by the pure resolveParamDialogCompanion from object + action name + row id, called by both param handlers, no metadata or spec key) that shows the user's count from security.assignedWork.count (total, owned = owner_id rows, assigned = every other user field, per object and field; truncated, unreadable and non-transferable said as the server answers), and an optional hand-over: a user picker (banned users and the user being banned left out) and one security.assignedWork.transfer call per click, whose report shows moved, failed with each reason, and remaining, with a next-batch button and a recount. Confirm stays the ban and is never gated. 19 console.assignedWork keys in all ten packs. The owned figure is 0 on objects whose owner_id is the platform-injected one until #30646 (the seat's card) lands; the PR says so in Acceptance notes. The PR must land after PR #30640: the eager i18n-locale-en pack grows 374 B gz and is over its ceiling until then.",
      "tests": "New: actionDialogCompanion.test.ts 5/5, useConsoleActionRuntime.banCompanion-30205 2/2, RecordDetailView.banCompanion-30205 2/2, ActionParamDialog.banAssignedWork-30205 12/12 (complete, partial with failures and next batch, banned-target refusal, other refusal, Confirm clickable and resolving while a hand-over is in flight, refused count, truncated/unreadable, nothing held). Moved pins: RecordDetailView.paramRuntimeParity-6431 DIALOG_READS gains companion; untranslated-identity-4376 LEGITIMATE_IDENTITIES gains the two format-string keys. Broad run on bbee3036: i18n + ActionParamDialog* + RecordDetailView* + useConsoleActionRuntime* + app-shell __tests__ = 266 files, 2928 passed, 13 skipped, 1 failed (the partial case, fixed in 3e306eff; that file 12/12 after). app-shell type-check exit 0 on bbee3036. Ablations via ablation-replace.mjs on committed 975b53ff, each restored (blob == HEAD, git diff HEAD empty): useConsoleActionRuntime companion call replaced by undefined: 1 failed / 2; same in RecordDetailView: 1 failed / 2; the panel render guarded off in ActionParamDialog: 10 failed / 12. Live showcase (main 14c67d44 before, branch after, en-US and zh-CN, 1440 2x): before = description + ban reason only; after = counts (7 held: owns 3, assigned 4), one hand-over moved 5 (0 failed, 0 remaining) and the target's own count then answered them, Confirm banned the user (banned true), no page error.",
      "gates": "dispatch-gates --commands: 35 families, same list at fb246487 and bfe597f0; all 35 exit 0 on bfe597f0 (incl. pnpm --dir ui exec vitest run scripts/ 170 files / 4970 tests); --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Outside the list on bfe597f0: check:i18n-keys, check:i18n-dead-keys, check:eager-locale-catalogues, check:new-line-citations exit 0; check:eager-closure exit 1 (i18n-locale-en 60,325 gz vs 60,000 ceiling; 59,951 with main's en.ts, same tree otherwise), expected until PR #30640 is merged in. eslint --no-inline-config on the new sources: 0 problems. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "3 — mcp__github__issue_read x2 (issue 30205 get, comments), mcp__github__pull_request_read x1 (PR 30640 get); no write tool",
      "api_writes": "8 — git push x6 to claude/project-thread-4vnhy6; POST /repos/objectstack-ai/objectstack/dispatches x1 (fleet-write pr_create, read back identical, no assignees, no labels); this comment via post-stamped.mjs x1",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: #30646 · the assigned-work count and transfer skip the platform-injected owner_id (system: true), so owned records on most objects are neither counted nor moved; reported to the seat during the run, filed by the seat"
      ]
    }

    Generated by Claude Code

  10. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU

    Release: 2026-11-06T03:54Z. The domain:ui#4 claim 6667820256 (ui half) ends unlanded. On the maintainer's 「所有车道当前任务处理完,合并后就下班」 (2026-11-06T01:17Z), every ui seat signs off once its own work lands, domain:ui#1 included, so seat 4 goes offline. The card goes to pm:blocked on PR #30640, and the assignee is cleared.
    Blocked-by: #30640
    Resume-from: this comment, for the next domain:ui seat, once PR #30640 merges and the card is back at pm:queue. domain:ui#1 names PR #30640 as its stay-behind.

    State left behind:


    Generated by Claude Code

  11. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 6, 2026
  12. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: unlock · pm:blocked → pm:queue · 2026-11-06T05:46Z

    ⛔ Not a claim.


    Generated by Claude Code

  13. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  14. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-tesla · session_01L9qibxW6qraU2Z3mwupUGp

    Claim: PM loop round 2 · 2026-11-06T05:57Z · takeover of PR #30652 (ui half)
    Session: session_01L9qibxW6qraU2Z3mwupUGp
    Account: os-tesla (the card's assignee)
    Branch: the new card thread's designated claude/project-thread-* branch, cut from claude/project-thread-4vnhy6 @ 6491bc3d, named in a claim amendment once the thread reports it. The old branch belongs to an account out of tokens; a cloud thread can push only its own branch, so the thread opens a superseding PR and the seat closes #30652 with a pointer.
    Worktree: objectstack-issue-30205
    Domain: domain:ui
    Seat: domain:ui#3 (split with domain:ui#2, mailbox #30817 6677724447 / 6677742320), on Tesla 「ui 车道跟进一下 3小时内没更新的pr和已派发的issue,接手处理。」 and 「目前ui车道都在你这里,前任没token了」 (2026-11-06T05:20Z, project "Objectstack security ui")
    File surface: PR #30652's 24 files as reviewed (ban dialog: assigned-work counts and transfer, console.assignedWork.* keys), plus any key move into the lazy marketplace namespace the merge needs. ⛔ No ceiling raise on i18n-locale-en. Stop on breach and explain in the report.
    Container & model: M, mode:thread, model: default (opus), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6677973028
    Premise: holds — Release 6676437604: the PR's only red was check:eager-closure (i18n-locale-en 60,325 gz vs 60,000), the budget fix PR #30640 merged as d996c3817e; three review rounds closed (os-dev-report 6669385351).
    Serial constraints cleared: #30206 (PR #30552, also this seat) touches console Home; #30511 (PR #30595) and #30325 (PR #30545) with domain:ui#2 touch ui/packages/i18n packs: shared language-pack files are ordinary concurrency, the later lander merges main.
    Resume-from: claude/project-thread-4vnhy6@6491bc3d

  15. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-tesla · session_01L9qibxW6qraU2Z3mwupUGp

    席位核对 · Amendment · 2026-11-06T05:58Z · claim 6678119224 branch named

    Branch: claude/project-thread-hackce (the card thread's designated branch; session session_01L9qibzvSNwtTjiRhhyeVt8), cut from claude/project-thread-4vnhy6 @ 6491bc3d, then origin/main merged. Everything else in claim 6678119224 stands.

  16. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-tesla · session_01L9qibzvSNwtTjiRhhyeVt8
    os-dev-report

    {
      "issue": 30205,
      "status": "done",
      "branch": "claude/project-thread-hackce",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30855",
      "session": "session_01L9qibzvSNwtTjiRhhyeVt8",
      "premise_still_valid": true,
      "summary": "Takeover of the ui half (Release 6676437604): branch cut from claude/project-thread-4vnhy6 at 6491bc3d, origin/main fd749a45e (incl. PR #30640 d996c3817e) merged cleanly at 58719174dd; superseding draft PR #30855 (Fixes #30205, Supersedes #30652), same 24 files +1342/-0. No new code: the branch's 19 console.assignedWork keys are not marketplace-page strings, so nothing moved into the lazy marketplace namespace. check:eager-closure now passes: i18n-locale-en 57,396 gz vs 60,000 ceiling (57,026 with main's en.ts on the same tree, +370 B); ceiling untouched. The seat closes #30652 with a pointer.",
      "tests": "On 58719174dd: pnpm --dir ui exec vitest run packages/i18n/ + app-shell ActionParamDialog* / RecordDetailView* / useConsoleActionRuntime* / actionDialogCompanion / src/__tests__/ / openPathObjectReaders-12811 / console/marketplace/ = 322 files, 3450 passed, 13 skipped, 16 failed, all 16 in spec-symbol-parity.test.ts (its probe found no @objectstack/spec .d.ts because the dep build ran with OS_SKIP_DTS=1); after a declaration build of @objectstack/spec that file 41/41. pnpm --filter @object-ui/app-shell run type-check (src + tests, after a dts build of @object-ui/app-shell^...): VERDICT command-exit 0. Ablations not re-run (from #30652 on 975b53ff); main changed only RecordDetailView.tsx among the ablated files, and the suites they turned red are green on this head.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 58719174dd: 35 families (same list as #30652); all 35 exit 0, incl. pnpm --dir ui exec vitest run scripts/ (170 files passed, 2 skipped; 4979 tests). --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Outside the list: pnpm --dir ui check:eager-closure exit 0 (i18n-locale-en headroom 2.5 KB), check:i18n-keys, check:i18n-dead-keys, check:eager-locale-catalogues, check:new-line-citations exit 0. CI check-runs: not yet read at report time.",
      "line_budget": "n/a (no skills/** in the diff); 1342 changed lines, under the 3000 human-merge threshold",
      "files_changed": 24,
      "deviations": "The ablations were not re-run on the merged head (carried from #30652). The dependency build used OS_SKIP_DTS=1, which reds spec-symbol-parity until @objectstack/spec has a declaration build; re-run green after one.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "4 — mcp__github__issue_read x2 (issue 30205 get, comments), mcp__github__pull_request_read x1 (PR 30652 get), ToolSearch load x1 not GitHub; 3 GitHub calls, no write tool",
      "api_writes": "3 — git push x1 to claude/project-thread-hackce; POST /repos/objectstack-ai/objectstack/dispatches x1 (fleet-write pr_create, read back identical, no assignees, no labels); this comment via post-stamped.mjs x1",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": ["carrier: #30646 · assigned-work count and transfer skip the platform-injected owner_id, so owned reads 0 on most objects; carried from #30652, said in Acceptance notes"]
    }
  17. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-tesla · session_01L9qibxW6qraU2Z3mwupUGp

    席位核对 · Amendment 2 · 2026-11-06T06:48Z · claim 6678119224 Clause-② corrected · PR #30855

    Clause-②: yes (widening)

    The superseding PR is #30855 (old #30652 closed with a pointer, 6678992636). The claim read no, as the previous holder's did: the 19 new console.assignedWork.* keys ship in all ten published @object-ui/i18n packs, and a published pack publishes every key, so the PR widens the public face. Owed: the lane seat's TIER_DEFAULT review of the declaration before the queue. Everything else in claim 6678119224 stands.

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

Metadata

Metadata

Assignees

Labels

domain:uiDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions