Skip to content

[runtime] ctx.user 的 roles 别名(值是 positions)没有关闭日期 —— #5613 给 ctx.session 装了迁移窗口,同名同值的 ctx.user 面仍是无限期别名(observation) #6011

Closed

Description

@baozhoutao

在做 #5613(action body ctx.session 的 roles → positions 更名,PR #5991)时顺带测到的同族面,不在该 PR 范围内,按 Prime Directive #10 单独记录。

事实

packages/runtime/src/security/actor-user.ts 的 ActorUser 同时声明两个键,值完全相同:

  • 第 95-97 行:positions: string[] —— /** ADR-0090 position names held by the caller (canonical; EvalUser.positions). */
  • 第 98 行:roles: string[] —— /** Legacy alias of positions (pre-ADR-0090 spelling, kept for the REST/AI shapes). */

构造处(约 208 行)是一次赋值两次:

positions: core.positions,
roles: core.positions,

这个形状被 packages/runtime/src/action-ctx-user-shape.test.ts 明确钉住(the ADR-0090 position aliases stay in lockstep),并在 http-dispatcher.test.ts 的 action body 用例里断言过。所以它是有意的、被测试保护的、正在被消费的别名 —— 不是漏网的死代码。

为什么仍然值得记一笔

它与 #5613 修掉的那个面是同一个形状,唯独少了后者刚装上的那一半:

ctx.session(#5613 后) ctx.user(现状)
权威键 positions positions
旧拼法 存在,已标 deprecated 存在,注释写 "Legacy alias"
ADR-0087 迁移条目 有(action-session-*-to-positions) 无
关闭日期 / 移除路径 有(#3280 → #3290 那条路) 无
契约声明 ActionSessionSchema(spec) 仅 runtime 的 TS interface

ADR-0090 D3 把该拼法列为保留禁用词,而 check:role-word 只扫 content/docs 与 skills,扫不到 .ts;packages/lint 的 security-role-word 只判作者写的元数据,也判不到运行时构造的 ctx.user。所以这个别名今天没有任何闸门在看,也没有任何东西约定它何时结束 —— 「两个拼法长期共存」正是 #5613 的裁定里被认定为缺陷而非终点的那个状态。

为什么标 finding 而不是缺陷

今天没有用户会撞上:两个键恒等,读哪个都对。它是方向问题,不是故障 —— 因此按观察类记录,不挂 pm:queue,由 PM 分诊定级(严重度在立单时判断本来就不可靠,#1004 / #897 两个方向都出过错)。

可能的处置(供分诊,不预判)

  1. 照 [runtime] action body 的 ctx.session 仍在生产 roles(值是 ec.positions)—— 自称「mirroring hook ctx.session」,而 hook 侧该键已按 ADR-0049 退役 #5613 的模子办:给 ctx.user 面立 spec 契约(它今天连 schema 都没有,只有 runtime 的 interface),把 roles 标 deprecated,加 ADR-0087 语义迁移条目和关闭日期;
  2. 直接退役:先测消费者(REST / AI 路由 / objectui / cloud)是否真的还在读旧拼法,若无 → ADR-0049 enforce-or-remove;
  3. 明确保留:若 REST/AI 的对外契约确实需要这个拼法,把「为什么它豁免 ADR-0090 D3」写进 ADR,而不是留在一行代码注释里(Prime Directive [WIP] Add Chinese version of the documentation #13:代码里找不到的决定,就是会被推翻的决定)。

现场

查重:按 actor-user.ts、ctx.user roles、ADR-0090 D3 alias 三组关键词搜过 open issues,未见同题(唯一命中的 #5521 是同一接缝的另一个字段 ScriptContext.user 的类型问题,已单独记录)。


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    Contributor

    roles 别名现在就关闭

  2. claude commented on Aug 6, 2026

    @claude
    Contributor

    分诊登记:维护者已就地裁决,本座位仅做落点核实与路由差异记录 —— ⛔ 未改动任何标签

    一、裁决已落地(评论区裁决,记录在案)

    维护者 2026-08-06 14:49:30Z:「roles 别名现在就关闭」,并于 14:50Z 就地改标:摘 finding,加 priority:p0 + pm:queue + target:v17(域标签先打 domain:spec、8 秒后改为 domain:services)。

    即:立单人列出的三条处置里,裁的是方向 2「直接退役」,且不接受方向 3「明确保留」。本座位不重判 —— 裁决落在评论区,分诊照办。

    二、落点核实(origin/main 44106d9,正文所述逐条命中)

    面 文件 现状
    声明 packages/runtime/src/security/actor-user.ts:95-98 positions: string[] 与 roles: string[] 并存,后者注释 "Legacy alias of positions"
    构造 同文件 :207-208 positions: core.positions, / roles: core.positions, —— 一次赋值两次,两键恒等
    钉子 packages/runtime/src/action-ctx-user-shape.test.ts the ADR-0090 position aliases stay in lockstep —— 退役时这条钉子必须同批翻转,否则它会把退役判红
    迁移台账 packages/spec/src/migrations/registry.ts ⚠️ 本面无条目。同族的 session 侧三条都在(data.hookContext.session.roles :1870、ui.actionSession.roles :1907、CEL/formula: current_user.roles :180)—— 这正是正文那张对照表里 ctx.user 行「无 ADR-0087 条目」的实证

    三、⚠️ 路由差异 —— 交维护者确认,本座位不自行改写

    domain:services 与落点不符,按 SKILL「Domain lanes」域表:

    • packages/runtime 属 domain:cli 车道(该行范围:cli / runtime / verify / qa / types / rest / mcp / observability / client / client-react / cloud-connection / create-objectstack / adapters/* / plugin-hono-server / plugin-dev);
    • domain:services 的范围是 packages/services/* / connectors/* / triggers/* / plugin-approvals / plugin-webhooks / plugin-email / plugin-reports / embedder-openai / knowledge-* —— 不含 packages/runtime,本单的三个文件面无一落在其中。

    本座位保留 domain:services 原样:SKILL step 2 明写「A routing label a human already set is respected as-is」,⛔ 分诊座位不改写人工设置的路由标签。但按双射协议如实记录后果:这是一条 priority:p0 + target:v17 的单,而它现在停在一个范围内不含其任何文件的车道上 —— services 座位认领它即越界(违反一座一主),cli 座位读不到它(不在自己车道),最可能的结果是无人认领。请维护者确认下面二选一(任一都只需改一个标签):

    1. domain:services → domain:cli(与域表一致,单一车道即可完成 runtime 侧退役);
    2. 维持 domain:services,但按 rule 4 的跨域例外路径指定认领方并申报文件面。

    补注:14:50Z 那次 domain:spec → domain:services 的 8 秒改标,从时序看像是即时更正;若原意其实是「spec 契约半边」,见下条。

    四、退役范围提示:这是跨域的,建议 contract-first 拆分

    方向 2 的完整落地不止 runtime 一处:

    • runtime 半边(domain:cli):删 roles 键、翻 action-ctx-user-shape.test.ts 的 lockstep 钉子、核实并迁移消费方;
    • spec 半边(domain:spec,「shared contract surfaces have one owner」):补 ADR-0087 语义迁移条目(否则这次退役在台账上不可见,与同族 session 侧三条不对称),且正文已指出 ctx.user 面今天连 spec schema 都没有,只有 runtime 的 TS interface —— 「退役一个只存在于 TS interface 里的键」要不要顺带补契约,归 spec 座位判。

    按 SKILL rule 2,建议由本座位下一轮做 contract-first 拆分(父单 + 两条 sub-issue + Blocked-by:,spec 先行)。本轮不擅自拆 —— 拆分会移动一条维护者刚亲手定级的 p0 的形状,先请示再动。

    消费方预扫(为方向 2 的第一步「测消费者是否真在读旧拼法」提前取数,origin/main 全仓非测试 .ts):裸 .roles 命中集中在 spec 的迁移台账散文 与 plugin-approvals / plugin-auth 的历史注释,packages/runtime/src/action-execution.ts:763 另有一处注释说明 hook session 与 action ctx 是不同键集 —— 未见活体生产代码读 ActorUser.roles。⚠️ 这是预扫不是结论:REST / AI 路由、objectui、cloud 三处消费面尚未逐一核,认领方须自行复核后再删(注释说该别名是 "kept for the REST/AI shapes")。

    五、finding 计数说明

    维护者已摘 finding,故本单不计入本轮发现分诊轮的处理数,仅作主分诊登记。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. claude commented on Aug 6, 2026

    @claude
    Contributor

    路由更正已执行:domain:services → domain:cli(经维护者指示「按照你的建议调整 domain」)

    • 改动:摘 domain:services,加 domain:cli。其余标签一律未动 —— pm:queue / priority:p0 / target:v17 保持维护者 14:50Z 设定的原样;assignee 仍为空(分诊座位不认领)。
    • 依据:SKILL「Domain lanes」域表 —— packages/runtime 属 domain:cli 行(cli / runtime / verify / qa / types / rest / mcp / observability / client / client-react / cloud-connection / create-objectstack / adapters/* / plugin-hono-server / plugin-dev)。本单落点三面全在 packages/runtime/:src/security/actor-user.ts:95-98(声明)、:207-208(构造)、src/action-ctx-user-shape.test.ts(lockstep 钉子)。
    • 标签写后已复读确认(本仓 Auto Label bot 的整组 PUT 会用旧快照冲掉刚打的标签,devx 车道 08-06 实测)。

    ⚠️ 仍待维护者定夺的一项(本轮未动,不因本次改标而自动成立):上一条评论第四节提出的 contract-first 拆分。退役的 spec 半边(补 ADR-0087 语义迁移条目;ctx.user 面今天连 spec schema 都没有,只有 runtime 的 TS interface)按「shared contract surfaces have one owner」归 domain:spec 座位,与 runtime 半边不同车道。⛔ 分诊座位不擅自拆一条维护者已亲手定级的 p0 —— 请示后再动。

    两种走法,任一都行,只是要有个决定:

    1. 不拆:cli 座位一并做掉,其中 packages/spec/src/migrations/registry.ts 的条目按跨域例外路径处理(由本座位指定 cli 认领 + 该认领评论申报完整文件面 + 跑定向在飞检查);
    2. 拆:父单 + 两条 sub-issue(domain:spec 先行 + domain:cli 带 Blocked-by:),由本座位下一轮执行。

    在收到指示前,#6011 现状即可派:cli 座位可按本单已有的落点与消费方预扫直接认领 runtime 半边;spec 台账条目届时按上面二选一补。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions