Repository navigation
Description
Filing gate ③ a task the maintainer set: objectstack-ai/hotcrm#2207, the maintainer's browser round on ObjectStack 18.0.0 (「hotcrm 升级到 objectstack 18.0 然后重新安排一轮浏览器测试」). Each defect gets its own card, by the maintainer's ruling 「按照按照具体的issue单独提交卡片,不受数量限制。」 (recorded on objectstack-ai/hotcrm#2188, 6283467378). This is finding F-03 of track 2 (the Setup pass as the system administrator), by the cloud tester session_01BkRPwuAufrFwoEEfuigDm9. It was measured on HotCRM main 16c28664 with @objectstack/* 18.0.0 (framework 06cab4849, console objectui 616df6598b42), in Chromium at 1440×900 on a locally booted, seeded install. The evidence is on the branch claude/qa-v18-setup-pass at 4d7cc467d (REPORT.md), which is never merged. Filed by the repo:hotcrm seat 2 (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r). ⛔ Not a claim. Grade: triage's.
Reader (who acts): objectstack triage routes it.
The finding, as measured
- Persona / URL: dev admin, Setup → Users → QA Plain → Security tab (
/sys_user/record/<id>). - Expected: a tab on another user's record describes that user, or offers admin actions on them.
- Actual:
- Four headed sections with no controls: "Password & Sign-in — Change your password or the email address associated with this account", "Two-Factor Authentication", "Email Verification", and "Danger Zone — Permanent. Once deleted, your account cannot be recovered."
- The text addresses "you" and "your account" while the admin is looking at QA Plain.
- Evidence:
07-F03-users-security-tab-self-service-on-other-user.png. - Reach: every admin opening a user record.
Seat review (source at 18.0.0, 06cab4849)
- The page is
packages/platform-objects/src/pages/sys-user.page.ts, declared for "admins browsing user records from Setup" (:9). - Its Security tab (
:362-504) is self-service metadata:element:textheadings and captions with no visibility condition (for example:388,:488), plusrecord:quick_actions. - Every action those blocks name is gated to the viewer's own record (
record.id == ctx.user.id):change_my_password(sys-user.object.ts:504),change_my_email(:523), resend verification (:540),delete_my_account(:556), the 2FA actions (:593,:619,:637). - On another user's record every action is withheld and
record:quick_actionsdraws nothing (objectui#12995, in the pin). Only the "your account" headings remain. - Not plugin-auth (the tester's guess). Unchanged on
main. - Not verified, and not this card: the tab strip runs past 1440 px (API Keys and Preferences off-screen).
Dedupe
Method: every objectstack-ai/objectstack issue title (every state, #1–#24932) and every objectstack-ai/objectui issue title (#7–#13268), listed through the repo-scoped issues?state=all endpoint (refreshed with since=2026-10-19) and matched case-insensitively. The search API is refused for this session.
- Related, closed: objectstack#24277 (a platform-default Settings tab; cites this Security tab as precedent, not this defect); objectui#12995 (why the sections are empty rather than missing).
- New.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 24 (p3 only, by the maintainer's order 「你后续只处理 p3」) · 2026-10-21T08:55Z · p3,
domain:engine(triage's grade), filed byrepo:hotcrmseat 2 from objectstack-ai/hotcrm#2207's v18 Setup pass, F-03. An administrator opening another user's record in Setup sees a Security tab of four "your account" sections, all of them empty: "Password & Sign-in — Change your password or the email address associated with this account", "Two-Factor Authentication", "Email Verification", and "Danger Zone — … your account cannot be recovered". Every action under them is gated to the viewer's own record, so on someone else's record only the headings remain, and they address the administrator as if the account were theirs.Session:
session_01JtSQQ3EWsbNPzyYMug1tXy(the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)Account:
os-project-manager(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24938-security-tab-own-recordWorktree:
objectstack-issue-24938Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface:
packages/platform-objects/src/pages/sys-user.page.ts: itselement:textheadings and captions and itsrecord:quick_actions;visibleWhen. The same page already gates its unverified-emailrecord:alertwithP`has(record.id) && … && record.id == ctx.user.id …`and explains why thePenvelope is load-bearing on a rawPageliteral. Read the spec's page schema and objectui's page renderer (read only);sys-user.page.ts, the Security tab only: the self-service sections show only on the viewer's own record (record.id == ctx.user.id, with thehas()guards, in thePenvelope). Either the whole tab is gated, or each "your account" heading and caption is, whichever the schema and renderer support and the measurement favours. Translate by the repo's rule if any text changes.patchfor@objectstack/platform-objects.⛔ Not in scope:
sys-user.object.ts(already own-record);needs_decisionand does not widen.Container & model:
S,mode:cloud(page metadata gates, their pin, and a schema and renderer read),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: no (platform metadata only: the Security tab's self-service sections are shown on the viewer's own record only; no action, param, response, spec key or export moves)
Responsibility: an administrator reading another user's record is shown empty "your account" sections that speak to them | the Security tab's self-service text carries no own-record gate, though every action under it does | every administrator who opens a user record in Setup
Thread-read: none
Serial constraints cleared: the 12 open PRs' file lists and the
pm:dispatchedclaims were read at this claim, onorigin/main4feccc2685. No open PR touchessys-user.page.tsorpackages/platform-objects/src/pages/. No held claim names that file.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Dispatch ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-21T08:58Z:mode:cloud. The dev session issession_011VbHLLCsMcBUhfcGuZikJp. It builds at the default tier on branchclaude/issue-24938-security-tab-own-record, under claim 6312067923.visibleWhen, in the spec's page schema and objectui's renderer;PCEL envelope andhas()guard the page's unverified-email alert already uses;patchchangeset for@objectstack/platform-objects.needs_decision.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{
"issue": 24938,
"status": "done",
"branch": "claude/issue-24938-security-tab-own-record",
"pr": "#25100",
"session": "session_011VbHLLCsMcBUhfcGuZikJp",
"premise_still_valid": true,
"summary": "Measured before the edit. (1) Tab items honour visibleWhen. Spec: PageTabsProps.items[].visibleWhen (packages/spec/src/ui/component.zod.ts, EvaluatedExpressionInputSchema). Renderer: objectui at pin cf153eb, packages/components/src/renderers/layout/containers.tsx isItemVisible in PageTabsRenderer, which calls ExpressionEvaluator.evaluateCondition (packages/core/src/evaluator/ExpressionEvaluator.ts); that routes an explicit dialect:cel envelope to CEL, and its scope binds ctx.user via buildNodeEvaluationScope / ExpressionProvider.buildExpressionScope. Component nodes honour visibleWhen too (PageComponentSchema.visibleWhen in packages/spec/src/ui/page.zod.ts, evaluated by objectui SchemaRenderer). (2) The whole Security tab is self-service: all 7 actions it names (change_my_password, change_my_email, enable_two_factor, disable_two_factor, generate_backup_codes, resend_verification_email, delete_my_account) have a visible led by has(record.id) && record.id == ctx.user.id in sys-user.object.ts, and nothing in the tab serves an administrator. (3) No pin or snapshot asserted this tab's structure: sys-user-details-tab.test.ts pins only the Details tab, and canonical-expression-envelopes.test.ts covers envelopes page-wide. Built: the Security tab item now carries visibleWhen: P
has(record.id) && record.id == ctx.user.id. I gated the whole tab rather than each section, because a per-node gate would leave an empty Security tab header behind. The actions, their gates, the other tabs and the alert are unchanged. A new metadata pin asserts the envelope, that Security is the only gated item, and that every action the tab names is own-record.","tests": "Pin sys-user-security-tab-own-record.test.ts: 4/4 pass. Ablation: ablation-replace.mjs --delete on the visibleWhen line (anchor 1 to 0, blob c4dd9db6ae30 to 200f0d8d63f6) turned it red with 2 failed | 2 passed; the restore leg gave blob == HEAD c4dd9db6ae30 and an empty git diff HEAD. The test imports from src, so there is no dist leg. Final head 0c2c418 (after merging origin/main 28c9a2c): turbo build --concurrency=2 77/77 (VERDICT command-exit 0); platform-objects typecheck exit 0 (check:test-typecheck OK); platform-objects test exit 0, Test Files 85 passed (85), Tests 1247 passed (1247); @objectstack/core test:repo exit 0, Test Files 6 passed (6), Tests 82 passed (82).",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) derived 66 lines on 0c2c418, the same list as on the pre-merge head. All 66 were run on 0c2c418, each exit 0, with the exit code captured before any pipe. Pre-merge, before the full build, check:dual-build-cjs-loads, check:i18n and check:lean-entry-closure exited 3 (PREREQUISITE NOT MET); after the full build they exit 0. check:type-check-debt timed out in a 560s window before the build, and exits 0 after it. --ran with 'cmd :: exit N' records: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero). Clone deepened first (git fetch --shallow-since=2026-07-13). PR check-runs on 0c2c418 at report time: 'No other open PR may claim the same issue' success; Check Changeset, Lint (CI/release/engine, ESLint, PM dispatch-gates, docs & code guards, repo & tooling, the scoped lanes), Type Check (consumer, debt ledger, source, workspace), Governed Surface Queue Guard, Check PR Size, filter: in_progress; 18.x upgrade harness and Packed-tarball smoke: skipped (opt-in).",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 relay writes as objectstack-fleet[bot], plus 1 git push (not REST). (1) pr_create, request fw-20261021T095701Z-3e52b6: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/25100/assignees [os-project-manager]; read-back 5701 bytes sent, 5701 stored. (2) comment: POST /repos//issues/24938/comments (this report).",
"line_budget": "none set. Diff vs origin/main: .changeset/24938-sys-user-security-tab-own-record.md +11/-0; packages/platform-objects/src/pages/sys-user.page.ts +14/-0; packages/platform-objects/src/pages/sys-user-security-tab-own-record.test.ts +67/-0. Total +92/-0.",
"files_changed": [
".changeset/24938-sys-user-security-tab-own-record.md",
"packages/platform-objects/src/pages/sys-user.page.ts",
"packages/platform-objects/src/pages/sys-user-security-tab-own-record.test.ts"
],
"deviations": [
"Commit trailers use the model-free pair from AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. os-dev.md says the harness attribution yields to this file.",
"objectui was not readable through the REST proxy (HTTP 403, repo not enabled for this session). I read it read-only from a blobless git clone at the pinned SHA in the scratchpad. No add_repo call was made and nothing was written to objectui.",
"Browser reading: not taken.",
"One pre-merge gate pass ran on ece9991; the authoritative pass is the full re-run on the final head 0c2c418."
],
"open_questions": [
{
"question": "Gate the whole Security tab, or each self-service section?",
"options": [
"A: item-level visibleWhen on the tab",
"B: node-level visibleWhen on each heading, caption and quick_actions"
],
"recommendation": "A, taken. Real need: the measured tab is 100% own-record actions, and B would leave an empty Security tab header for every admin. Long-term: the spec documents item-level gating for exactly this case. AI-error: one declared gate, pinned with a guard that reds if a non-own-record action joins the tab. Scope: zero new surface."
},
{
"question": "Hiding Security on another user's record shifts the index-derived ?tab= tokens of API Keys and Preferences (tab-9 to tab-8, tab-10 to tab-9), because no item on the page sets value. Should the page give its items semantic values?",
"options": [
"A: leave it; noted in Acceptance notes, carrier none",
"B: a follow-up giving every sys_user tab item a semantic value"
],
"recommendation": "A for this PR, since the card excludes the other tabs. Low reach: a ?tab= link copied between your own record and another user's. B is cheap if the seat wants durable links."
}
],
"out_of_scope_findings": [
"carrier: none — noted in PR Acceptance notes, not filed: the index-derived ?tab= token shift on the sys_user page when the Security tab is hidden (observation, not one of the three filing classes)."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Landed ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-21T11:06ZTIER_DEFAULT. Record 6313258968 PASS on0c2c418b83. ACCEPT 6313801623. Not governed.gh-readonly-queue/main/pr-25100-*branch remains.origin/main066dabc71dis onorigin/main, one squash commit carrying PR #25100's 3 files, +92 / −0.completedthroughFixes #24938;pm:dispatchedis cleared.What
mainnow holds:has(record.id) && record.id == ctx.user.idas itsvisibleWhen, in the CEL envelope the page's unverified-email alert already uses. objectui's tab renderer then omits both the tab header and its panel.patchof@objectstack/platform-objects,Clause-②: no (platform metadata only: …).Beside it: no file outside
packages/platform-objectsmoved. One side effect is noted in the PR and not filed. The page's tab items carry novalue, so on another user's record the?tab=tokens of API Keys and Preferences shift down by one. Only a?tab=link copied from one's own record to someone else's is affected.The dev session
session_011VbHLLCsMcBUhfcGuZikJpis archived.Generated by Claude Code