Part of objectstack-ai/hotcrm#673(下游症状:活动时间线 sys_activity.actor_name 渲染为裸 user id)。Filed unassigned by the hotcrm PM loop;证据由该单的实施 agent 在 17.0.0-rc.2 安装包上核实。
The defect
The REST action dispatcher builds the body's user object with the name key declared and delivered wrong — it is assigned the raw user id:
@objectstack/runtime dist (17.0.0-rc.2) REST action path, dist/index.js:5397-5399: { id: ec.userId, name: ec.userId, … }
- MCP dispatch path disagrees in the same file (dist/index.js:1776):
ec.userName ?? ec.userDisplayName ?? ec.userId — but nothing in the installed packages ever assigns ec.userName or ec.userDisplayName (grep across runtime / account / objectql / metadata / spec / plugin-auth finds read sites only, dist/index.js:1776 and :5264), so this path also lands on the id.
- The AI-route builder disagrees with both: dist/index.js:5264 exposes
displayName, not name.
buildActionSandboxContext (dist/index.js:1289-1305) passes the user object through verbatim — the sandbox is not where the name is lost.
Why this is worse than a missing key
ctx.user.name reads as a plausible string; no ?? fallback chain can detect it is the wrong value. This is exactly the failure mode declared = enforced exists to prevent, and it is silent by construction. App code that trusts the declared key writes opaque ids into user-facing surfaces (hotcrm's activity timeline did, for every activity action).
The platform has the correct value available at dispatch time: sys_user.name is the platform's own profile display-name column (plugin-auth SYS_USER_PROFILE_EDIT_FIELDS = {name, image}; the dev admin is seeded with name "Dev Admin").
Suggested fix
Resolve the display name once at dispatch and populate ctx.user.name consistently on all three paths (REST, MCP, AI-route). Downstream app-side workarounds (hotcrm now resolves via a sys_user read inside the action body, with a comment naming this issue's condition) can then be deleted — the hotcrm workaround deliberately trusts ctx.user.name the moment it differs from the id, so it self-retires when this lands.
Acceptance
- An action body dispatched over REST reads
ctx.user.name as the acting user's display name (not the id);
- MCP and AI-route paths agree on the same user shape;
- a pin test covers the REST path (the one that was hardcoded).
Part of objectstack-ai/hotcrm#673(下游症状:活动时间线
sys_activity.actor_name渲染为裸 user id)。Filed unassigned by the hotcrm PM loop;证据由该单的实施 agent 在17.0.0-rc.2安装包上核实。The defect
The REST action dispatcher builds the body's user object with the
namekey declared and delivered wrong — it is assigned the raw user id:@objectstack/runtimedist (17.0.0-rc.2) REST action path, dist/index.js:5397-5399:{ id: ec.userId, name: ec.userId, … }ec.userName ?? ec.userDisplayName ?? ec.userId— but nothing in the installed packages ever assignsec.userNameorec.userDisplayName(grep across runtime / account / objectql / metadata / spec / plugin-auth finds read sites only, dist/index.js:1776 and :5264), so this path also lands on the id.displayName, notname.buildActionSandboxContext(dist/index.js:1289-1305) passes the user object through verbatim — the sandbox is not where the name is lost.Why this is worse than a missing key
ctx.user.namereads as a plausible string; no??fallback chain can detect it is the wrong value. This is exactly the failure mode declared = enforced exists to prevent, and it is silent by construction. App code that trusts the declared key writes opaque ids into user-facing surfaces (hotcrm's activity timeline did, for every activity action).The platform has the correct value available at dispatch time:
sys_user.nameis the platform's own profile display-name column (plugin-authSYS_USER_PROFILE_EDIT_FIELDS = {name, image}; the dev admin is seeded with name "Dev Admin").Suggested fix
Resolve the display name once at dispatch and populate
ctx.user.nameconsistently on all three paths (REST, MCP, AI-route). Downstream app-side workarounds (hotcrm now resolves via asys_userread inside the action body, with a comment naming this issue's condition) can then be deleted — the hotcrm workaround deliberately trustsctx.user.namethe moment it differs from the id, so it self-retires when this lands.Acceptance
ctx.user.nameas the acting user's display name (not the id);