Repository navigation
Description
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
The signature line above and the Blocked-by: line at the end were added by triage#1 when recording the ruling; the card text below is the filing seat's, unchanged.
Filing category: ② a decision only the maintainer can make.
维护者速读
- 现象:MCP agent 无法把一个人(按姓名或邮箱)解析成环境里的用户 id。
sys_user是系统对象,query_records直接拒绝("is a system object and is not exposed via MCP")。所以 agent 不能往「员工 / 审批人 / 负责人」这类user字段填任何别人,只能等当事人自己建记录、再从owner_id里抄 id。托管环境里 cloud 账号 id 和环境里的用户 id 还不是同一个,agent 手里的 id 也用不上。 - 要你裁:MCP 是否提供「找人」的能力,以及形状。
- 推荐:A:只读的「人员查找」,按姓名/邮箱前缀返回 id + 显示名(受调用者自己的可见范围约束),不开放
sys_user整表。
One-sentence problem
"Create Zhang San's leave balance" or "assign this request to Li Si" cannot be done by an agent, because it has no way to find out who Zhang San is.
Evidence (reach)
Hosted tenant (pnpm dev:stack, Team plan; cloud ff373e02, framework d7c3d44e), MCP OAuth token of the organization owner:
query_records { objectName: 'sys_user', where: { email: '<member email>' } }→Object "sys_user" is a system object and is not exposed via MCP(same for a plain member).create_record rtbw_leave_bal { employee: '<the member's cloud user id>' }→员工: no sys_user record has id "…"— the id the control plane knows is not the environment's user id (the member signed in through OIDC and got an environment userMgyoX3…).- Workaround used in the test: the member created a request themselves; the owner read its
owner_id.
Governing text
packages/mcp/README.md— "sys_*objects hidden by default" (list_objects), "sys_*-object actions are held back fail-closed".docs/NORTH-STAR.md— agents through MCP are one of the two kinds of user; "approvals and automation", "sign-in and identity" are on the road.- Rule 14c (served by
get_authoring_rules) — approvers byfieldname "a user lookup field on the record", which an agent cannot fill for anyone but itself.
Options
| Option | What it does | What the customer sees | Effort |
|---|---|---|---|
| A | A read-only people lookup on the MCP data surface (e.g. find_people { query } → { id, name, email? }), scoped by what the caller may already see of members |
Agents fill person fields and route work by name | mcp seat · 1 PR · no precedent |
| B | Expose sys_user read-only through the record tools under FLS (name/email only) |
Same, through query_records |
mcp + security seats · 1–2 PRs · no precedent |
| C | Keep people invisible to agents; document that person fields are filled by people | Agents leave every person field empty | none |
Business reading: A = agents can say "this is for Zhang San" like a colleague would; B = the same, by opening a system table; C = agents only ever act on their own records.
Recommendation
A. Two years out an agent on a business app routes and assigns work by name (Salesforce/ServiceNow agents resolve users through a scoped lookup, not by opening the user table). A keeps the system object closed and adds one narrow, read-only tool. Confidence gap: whether member email should be visible to every member (privacy) — the tool can return name + id only by default.
Self-check: only ① picks A; ②③④ do not flip it.
After the ruling
A: packages/mcp adds the tool under data:read, scoped through the same identity read the console's people picker uses; a test asserting it never returns a member outside the caller's organization.
os-decision-facets
① 项目长远合理性: A 用一个窄的只读工具补上 agent 的「找人」,不打开 sys_user 整表,特例最小。
② 实际业务拉动: 任何带人员字段的流程(请假余额归属、审批人、负责人分配)今天都卡在这一步(本次实测)。
③ 防 AI 犯错: 现状 agent 只能猜 id 或把字段留空;A 让它拿到真实 id,写错时被响亮拒绝而不是静默留空。
④ 创业阶段不扩散: A 新增一个工具(永久义务);C 零成本但让人员字段对 agent 不可用。
Prior rulings read: "mcp sys_user lookup person user field" → 65 hits (top: #3146, #31350, #24952; none on MCP people lookup); ADR-0101; thread: none
Reader who acts: the packages/mcp seat, after the ruling.
Dedupe: repo:objectstack-ai/objectstack is:issue mcp sys_user lookup person user field → 65 hits, none proposes an MCP people lookup.
Blocked-by: #32442
Activity
objectstack-fleet commentedon Nov 12, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
分诊首触 + 决策分析(fable 档独立评估)· 2026-11-12T16:54Z
维护者速读
find_people窄查找 / B 对sys_user开放query_records/ C 不做。推荐 A。是否已有裁决: 未找到直接裁决。可见范围已由 ADR-0092 D5 定死:
sys_user_org_members是只读的同组织可见范围。前提核实:
sys_*,且 fail-closed(packages/mcp/src/mcp-http-tools.ts:2116-2118、:3195)。sys_user。拉动: 六张卡里最实:HotCRM 有 22 处
sys_user引用,审批和指派都卡在这一步。find_people{query}→{id,name}sys_user开放query_records四轴:
users.lookupByEmail、Linear MCPlist_users、Agentforce 都是窄查找,不是开整表。推荐 A: 默认只返回 id 和显示名;邮箱只支持精确匹配,不支持前缀枚举;结果设上限。置信缺口:成员对同事行的列级可见集合未核实。自检: 只看①选 A;④单看会选 C,不足以翻转。人工底线项:通讯录枚举与邮箱可见性。
os-decision-facets
Prior rulings read: ADR-0092 D5; packages/mcp README (sys_* fail-closed); thread: none
Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Brief-claimed-by: session_01Q4EjjMnyLqJdzuktEmRPXH · batch #1 · 2026-11-13T04:18Z
Decision batch #1 (the MCP agent-capability family, under epic #32897), taken on the maintainer's 「5张一批按总监决裁格式给我」.
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Ruling recorded · #32641 · B · 2026-11-13T04:39Z
Maintainer, verbatim (triage#1 session): 「以上原则同意」, answering decision batch #1 (
32641 B / 32591 A / 32592 C / 32686 A / 32747 A). The batch was analysed independently at the fable tier, with prior rulings checked first; the full analysis was presented in the session.enable.aiReadable, landed by PR feat(spec): enable.aiReadable — a system object's author can open its records to the AI read tools, read-only #32971, under the [Decision] Should an approver be able to see and decide their pending approvals over MCP? #32442 read-half ruling). An agent reads colleagues with the existing query tools and sees exactly what that member sees in the console's person picker: same organization, directory fields only (plugin-security: an org member reading a colleague's sys_user row is served the Admin-group fields (last_login_ip/at, failed_login_count, locked_until, ban_*, phone_number) — and through sys_activity, their sign-in history #21237). No new MCP tool.needs-user-decision→pm:blocked,Blocked-by: #32442(the read half's server PR, so the MCP guard honours the declaration). After it lands, onedomain:serverPR declares the user table. Acceptance through a real MCP client: find a colleague by name and fill a person field; refused on admin fields; refused for another organization's users.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: unblocked →
pm:queue· 2026-11-13T22:59ZBlocked-by: #32442: closed. PR feat(approvals,mcp): an approver's agent lists the approvals waiting on them over MCP #33117 landed the per-object read-only AI exposure (enable.aiReadable, spec spec: a per-object, read-only AI exposure declaration so an agent can read a declared system object through the MCP record tools (#32442 read half) #32865) and its first user,sys_approval_request. The maintainer's ruling B (4:40Z record) exposessys_userread-only to the MCP record tools under field masking (name and email only). That mechanism now exists, so B can be built. Grade unchanged (p2).enable.aiReadableonsys_user; the read stays within the caller's organization and member visibility; field masking keeps only id, name and email (decide whether email is visible to every member, the card's privacy gap); no write opens. Pin a non-member as invisible.objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Ruling recorded · #32641 · the email gap is closed: member email is visible to agents · 2026-11-13T23:05Z
Maintainer, verbatim (triage#1 session): 「成员的邮箱可以对 AI 可见,姓名容易重复吧?」. This answered triage's question about the confidence gap under ruling B: should a member's email be visible to every member's agent?
What it settles for the builder:
sys_useris exposed read-only to the MCP record tools under field masking with id, name and email, as option B states. Email stays in, because names repeat and email is the reliable key to resolve a person.sys_userfield is visible. Pin that a non-member is invisible and that every masked field stays hidden.[-][Decision] How should an MCP agent find a colleague (by name or email) to fill a person field or route work, when sys_user is not exposed over MCP?[/-][+]mcp: an agent finds a colleague through a read-only sys_user (id, name, email) within the caller's organization visibility — ruled B, email visible[/+]objectstack-fleet commentedon Nov 13, 2026
epic:#32897#1 · os-tesla · session_01L9qibzoApSyVPuia3NCrBY
Claim: PM loop round 1 · epic seat
epic:#32897#1(registration 6867120489) · 2026-11-13T23:11ZSession:
session_01L9qibzL1RvtJP7jM7QZJaF(card thread; seatsession_01L9qibzoApSyVPuia3NCrBY)Account:
os-teslaBranch:
claude/project-thread-83mt0o(the card thread's harness-pinned branch; the thread can push only this name)Worktree:
objectstack-issue-32641Domain:
domain:serverSeat:
epic:#32897#1File surface:
packages/platform-objects/src/identity/sys-user.object.ts, the baseline permission file PR #33117 touched (if needed),packages/mcp/src/**(tests; code only if admission needs it; the MCP-served skill textpackages/mcp/src/skill-md.tsfor the ruling-6867150851 sentence),content/docs/ai/connect-mcp.mdx,packages/mcp/README.md, root.changeset/(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate),effort: xhigh(permission boundary)Clause-②: yes (widening) — expected; the dev derives and cites
Metadata-protocol: none
Responsibility: platform code in objectstack (
sys_userinpackages/platform-objects/src/identity/sys-user.object.tsdeclares no AI read exposure); the platform path that already covers it isenable.aiReadable+isAiReadExposed(PR #33117,3eac63a9); reached by every agent filling a person field — measured on a hosted tenant in the card bodyThread-read: 6867150851
Premise: holds (rulings 6844187236 B and 6867150851 email visible;
Blocked-by: #32442closed; mainc04aa295)Serial constraints cleared: none in flight on these files
Order:
/mnt/project-files/epic-32897-1/r2026-11-13/dispatch-32641.mdunder that folder'sFRAME.md. Contract review: this seat, atCONTRACT_REVIEW_TIER.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
epic:#32897#1 · os-tesla · session_01L9qibzoApSyVPuia3NCrBY
Seat note · the agent's
sys_userfield set is put to the maintainer · 2026-11-13T23:17Z⛔ Not a ruling. The card thread measured (static read at
origin/main) that the console person picker reads the whole member-readablesys_userrow: self + org peers, every field except theAdmingroup (identityAdminFieldSecurity('withhold')onmember_default). Declaringenable.aiReadablelands an agent on exactly that set through the existing gates.Two recorded texts differ on the field set: ruling record 6844187236 ("exactly what that member sees in the console's person picker … directory fields only (#21237)") and option B's own text plus triage's 6867150851 ("name/email only", "no other
sys_userfield is visible"). A narrower set needs a new agent-only mask, a privacy boundary this seat does not decide.Generated by Claude Code
objectstack-fleet commentedon Nov 14, 2026
epic:#32897#1 · os-tesla · session_01L9qibzL1RvtJP7jM7QZJaF
os-dev-report
2026-11-14T00:16Z · PR #33180 · head
1e1c1561{ "issue": 32641, "status": "done", "branch": "claude/project-thread-83mt0o", "pr": "https://github.com/objectstack-ai/objectstack/pull/33180", "session": "session_01L9qibzL1RvtJP7jM7QZJaF", "premise_still_valid": true, "summary": "sys_user declares enable.aiReadable: true (the PR #33117 mechanism); no admission or permission code changes. A member's agent reads sys_user with the existing MCP read tools under the gates the console person picker is served through: own row + active-organization members (member_default sys_user_self / sys_user_org_members), every field except the Admin group (identityAdminFieldSecurity withhold), on results and filters. Measured served set for a member: accountable_user_id, banned, created_at, email, email_verified, id, image, locale, manager_id, name, primary_business_unit_id, principal_kind, source, two_factor_enabled, updated_at. query_records' text and the served SKILL.md (plus its byte-identical Console copy) say how to find a colleague for a person field and to tell namesakes apart by email (ruling 6867150851). The field-set question (console set vs id/name/email only) is with the maintainer via the seat (6867361507); this PR builds the console reading and contains no agent-only mask.", "tests": "head 1e1c1561 (after merging main 58443466a). dogfood colleague-lookup-over-mcp (new, 10) + approval-read-over-mcp (pin updated): 2 files / 20 tests passed. platform-objects test 122 files / 1957 passed (incl. new sys-user-ai-read.test.ts, 2). mcp test 64 / 1141 passed (incl. new skill-md describe). typecheck platform-objects 0, mcp 0, dogfood 0 (8 GiB heap; 4 GiB OOM exit 134). Ablations, each committed head, ablation-replace --hold, package rebuilt, dist preflight present before / absent after, restore blob == HEAD and porcelain empty: A1 aiReadable line deleted (= main state) -> declaration pin 1 failed, dogfood 8 failed / 2 passed, reads answer 'Object \"sys_user\" is a system object and is not exposed via MCP'; A2 sys_user_org_members widened to true (x3 anchor, --expect 3) -> 2 failed (outsider appears); A3 member_default keeps the Admin group -> 2 failed (13 group fields served); A4 update_record on readGuard -> 1 failed (update answered a record). Two first tries were void and rerun: A2 anchor ambiguous without --expect (nothing written), A4 comment marker stripped by the bundler (preflight refused, nothing ran).", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 1e1c1561: 100 commands, each run with stdin closed, exit captured before any pipe: 97 exit 0 first pass; check:dual-build-cjs-loads and spec check:skill-examples exit 3 PREREQUISITE NOT MET (packages without dist/) -> built -> exit 0; check-plugin-teardown-shape --self-test exit 3 (pinned fixture commit missing from shallow clone) -> fetched -> exit 0. pnpm lint exit 0. --ran with exit codes: 100 derived, 100 run, 0 NOT-MEASURED (derived zero); pnpm lint outside the derivation. Record: /mnt/project-files/epic-32897-1/r2026-11-13/32641-gates-ran-1e1c1561.list. CI check-runs: in_progress at report time.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "line_budget": "+~420 / -4: 2 new test files, skill text +7 lines in skill-md.ts and its Console mirror, docs +9, changeset; no skills/** touched", "deviations": [ "Outside the claim's file surface, changed as consequences: ui/apps/console/src/pages/developer/objectstack-skill.ts (skill-md.test.ts holds it byte-identical to the served render) and packages/qa/dogfood/test/approval-read-over-mcp.dogfood.test.ts (its list_objects pin said sys_approval_request is the only listed sys_* object).", "New dogfood file packages/qa/dogfood/test/colleague-lookup-over-mcp.dogfood.test.ts (dogfood is the lane's real-client run; the order's step 4 asked for an end-to-end MCP pin).", "PR body attribution second line links this card thread (session-context instruction), not the seat thread FRAME names.", "Dogfood typecheck run at --max-old-space-size=8192: the 4096 default OOMs (exit 134)." ], "files_changed": [ ".changeset/32641-sys-user-ai-readable.md", "content/docs/ai/connect-mcp.mdx", "packages/mcp/README.md", "packages/mcp/src/mcp-http-tools.ts", "packages/mcp/src/skill-md.ts", "packages/mcp/src/skill-md.test.ts", "packages/platform-objects/src/identity/sys-user.object.ts", "packages/platform-objects/src/identity/sys-user-ai-read.test.ts", "packages/qa/dogfood/test/colleague-lookup-over-mcp.dogfood.test.ts", "packages/qa/dogfood/test/approval-read-over-mcp.dogfood.test.ts", "ui/apps/console/src/pages/developer/objectstack-skill.ts" ], "mcp_calls": "4 — reads only: issue_read x3 (get, get_comments x2 on #32641), pull_request_read x1 (get_files #33117); 0 write tools", "api_writes": "2 relay writes — pr_create (#33180, with assignee os-tesla) and this os-dev-report comment via post-stamped", "open_questions": [ { "question": "The agent's sys_user field set: the console member set (15 fields, Admin group withheld, what this PR builds) or id/name/email only?", "options": ["A console set: no new mask; agent = console (ruling record 6844187236 wording)", "B id/name/email only: needs a new agent-only field mask the console does not use (option B text, triage 6867150851 wording)"], "recommendation": "A, because it adds no second security boundary and keeps the agent's answer equal to the HTTP twin; already with the maintainer via the seat (6867361507)" } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: #33156 / #33030 · the stdio bridge admits sys_user through the same isAiReadExposed; its row and field gates are that card's scope and are not pinned here · noted, not filed" ] }objectstack-fleet commentedon Nov 14, 2026
epic:#32897#1 · os-tesla · session_01L9qibzoApSyVPuia3NCrBY
Ruling recorded · #32641 · the agent's
sys_userfield set = the console person picker's set · 2026-11-14T00:49ZMaintainer, verbatim (decision card in this seat's project thread, question 「#32641 让 agent 读同事时,能看到哪些用户字段?」): tapped 「和界面选人一样」 — "agent 看到的同事字段与成员在界面选人时一致(姓名、邮箱、头像、语言、上级等,管理员字段照旧隐藏),不加新规则。" The other option, 「只给 id、姓名、邮箱」 (a new agent-only mask), was not chosen.
This settles the question in seat note 6867361507:
sys_userthrough the MCP record tools is served exactly what the member is served by the console's person picker: their own row and their organization's members, every field except theAdmingroup (identityAdminFieldSecurity('withhold')onmember_default). No agent-only mask.Generated by Claude Code
objectstack-fleet commentedon Nov 14, 2026
epic:#32897#1 · os-tesla · session_01L9qibzoApSyVPuia3NCrBY
Claim amendment to this seat's claim 6867271061 on #32641 · 2026-11-14T00:52Z: the file surface adds two files the card thread touched outside it, both accepted:
ui/apps/console/src/pages/developer/objectstack-skill.ts: the Console "Download skill" copy ofpackages/mcp/src/skill-md.ts, held byte-identical byskill-md.test.ts; the ruling-6867150851 sentence has to land in both or that test is red. Same text, no other change. (domain:uifile;domain:ui#4is this account's seat and has no open PR on it.)packages/qa/dogfood/test/approval-read-over-mcp.dogfood.test.ts: it pins the listedsys_*set, which now includessys_user.The field set was ruled 6868810528 (the console person picker's set); PR #33180 is that shape.
Generated by Claude Code
objectstack-fleet commentedon Nov 14, 2026
epic:#32897#1 · os-tesla · session_01L9qibzoApSyVPuia3NCrBY
Landed · #32641 · 2026-11-14T02:18Z — PR #33180 merged through the queue as
6a36b79c;Fixes #32641closed this card.pm:dispatchedoff in this stroke.Delivered:
sys_userdeclaresenable.aiReadable: true, so a member's agent finds a colleague withquery_records/get_record(by email for exactly one person, by name with email to tell namesakes apart) and is served the console person picker's set: own organization only,Admingroup withheld, every write refused (ruling 6868810528). Left: the stdio host's row and field gates forsys_userbelong to the stdio parity work (PR #33175, merged) and are not pinned by this card's dogfood.Generated by Claude Code