feat(runtime)!: action ctx.session 双发 positions(权威)+ roles(弃用别名) (#5613) - #5991
Conversation
buildActionSession() 现同时输出 `positions`(ADR-0090 D3 权威拼法)与 `roles`(同值弃用别名),弃用窗口由 ADR-0087 语义迁移 `action-session-roles-to-positions` 声明;并修正 docblock 中「mirroring hook ctx.session」的失实自述(hook 侧该键已于 #5050 退役),改为如实指向 ActionSessionSchema 与迁移条目。 - 翻转 action-session-shape-contract.test.ts 的预埋键集断言,并断言双发同值; - 新增真实 dispatch 验证(http-dispatcher.test.ts),按迁移条目的验收口径; - ScriptContext.session 由 `unknown` 收窄为 ScriptSession = ActionSession | HookContext['session'] 的联合(两个真实生产者形状),不收窄为 ActionSession 单型——该 seam 确实承载 hook session。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
CI 的 check:role-word 是 shrink-only 棘轮:skills/objectstack-ui/SKILL.md 的保留词计数由 2 涨到 5(弃用别名示例 + 迁移条目 id + 反例代码各一)。 按 ADR-0090 D3 的本意,技能文档只教权威拼法 `ctx.session.positions`, 别名与迁移处方留在其本来的渠道(changeset + protocol-upgrade-guide, 均不在该棘轮扫描面内),正文以 `action-session-*-to-positions` 指路。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
…ion-session-positions-rename
|
范围外发现(已单独立单,未在本 PR 修)
CI(head Generated by Claude Code Generated by Claude Code |
Fixes #5613
#5613 phase 2 的 runtime 半边(spec 半边为 #5779 / PR #5849,已在主干
d4e080937)。维护者裁定「C 骨架 + A 语义,contract-first 两阶段」:契约先立、生产者随后。本 PR 就是那个「随后」。前提复核(实测,非照抄 issue)
以最新
origin/main(5582e1821)为基线逐条实测:packages/spec/src/ui/action-params.zod.ts的ActionSessionSchema已含positions(权威键)+ 弃用别名对,ADR-0087 语义迁移条目action-session-*-to-positions已在packages/spec/src/migrations/registry.ts、spec-changes.json、docs/protocol-upgrade-guide.md三处就位 —— 契约面确实已解锁;buildActionSession()现址packages/runtime/src/action-execution.ts:768(issue 正文的 689-695 行号已漂),仍只发别名键,值取自ec.positions;docstring 仍自称 "mirroring the hookctx.sessionshape (Unify the developer-facing org identifier: hooks exposesession.tenantIdwhile RLS/seed/columns useorganizationId(addorganizationIdas the blessed name) #3280)";packages/runtime/src/domains/actions.ts:333、action-execution.ts:999(均只是session: buildActionSession(deps, ec),形状变化直接流过,无需改动);toEqual断言)仍在packages/runtime/src/action-session-shape-contract.test.ts。前提成立。
改了什么
1.
buildActionSession()双发(packages/runtime/src/action-execution.ts)一个数组、两个键名,由构造保证同值(不是两次可能漂开的读取)。「非空才写」语义原样保留:无 positions(或
positions非数组)时两个键都不出现 ——'positions' in ctx.session与别名键的in判定同真同假;无身份信封时整个 session 仍是undefined而非{}(#3712)。2. 两句错注释修正
ctx.sessionshape」自 [spec] 退役 HookContext session.roles —— #4839 双删后零消费方零生产方(ADR-0049) #5050 起就是错的(hook 侧同名键已退役,且 hook session 本就是另一套键集、另一个生产者buildSession())—— 删除该自述,改为如实说明:形状由ActionSessionSchema声明、本函数是其唯一生产者、一致性由 pin 测试守;positions/ 别名 / 迁移条目 / 移除路径 Unify the developer-facing org identifier: hooks exposesession.tenantIdwhile RLS/seed/columns useorganizationId(addorganizationIdas the blessed name) #3280→Remove the deprecated hook/actionctx.session.tenantIdalias in the next major (converge onorganizationId) #3290)+ 「两个拼法都不是授权输入」的警示。3. 翻转预埋断言 + 承载双发同值(
action-session-shape-contract.test.ts)键集断言翻为含
positions的新形态,并把窗口期的承重事实钉住:两个键都对着ec.positions断言(而不是互相比对),所以「其中一个改从别处取值」也过不了;新增一条「别名绝不单独出现」,是窗口关闭那天删别名的方向性保险。4. 真实 dispatch 验证(
http-dispatcher.test.ts)迁移条目的验收口径写明「Verify against a real dispatch, not a fixture」,故补了一条走
dispatcher.handleActions的用例:持有 positions 的调用者发起 action,断言 body 实际收到的ctx.session.positions,以及同值的别名。5.
ScriptContext.session收窄 —— 收的是联合,不是ActionSession单型issue 第 4 条要求用 spec 的
ActionSession收窄。实测后按「缩回最小面并说明」处理:这个 seam 确实同时承载 hook session 与 action session(body-runner.ts的buildSandboxContext/buildActionSandboxContext两个写入方),把它声明成ActionSession单型,正是本 issue 要消灭的「一个键名两种现实」的同型错误 —— 只是换到类型面上。因此新增并导出:session?: unknown→session?: ScriptSession。#5697 当初留unknown的理由(收窄会逼 seam 的每个消费者去判别 body 种类)这次是实测而非再假设:两个写入方从any赋值,唯一读取方quickjs-runner的setObjectJson收unknown,全仓(objectstack / objectui / cloud)ScriptContext无 runtime 包外引用。所以今天没有任何站点需要判别,而联合类型正是让将来需要判别的站点被 tsc 抓住。收窄面止于 runtime 包内。6. changeset(major)+ 文档
.changeset/action-session-positions-runtime-dual-emit.md,@objectstack/runtime: major,正文带 FROM → TO 迁移处方(改读positions;别名在窗口内仍解析,窗口关闭按 #3280→#3290 路径移除;不要把…includes('admin')的访问判断改名成positions.includes('admin')—— 那是把缺陷改名而不是把读改对),并把ScriptContext.session的收窄作为第二处 breaking 明写。文档面实测:全仓没有任何 hooks/actions 文档在教 action 侧的旧拼法(
skills/objectstack-data讲的是 hook 侧已退役的那个键,content/docs/references/ui/action-params.mdx是 #5849 已重生的生成物)。唯一真正教 action bodyctx的手写面是skills/objectstack-ui/SKILL.md,在其「Action body context (ctx)」小节补了ctx.session.positions的权威读法 + 「不是授权输入」的警示 + 指向升级指南的迁移指路。验证
反向验证 —— 方向事先预测,结果与预测一致。 预测:这是最常见的「红」向,而非 #5046 式的「诊断变多」或 #5009 式的「反转」—— 因为断言的是产出对象的精确键集,而被撤掉的正是产出该键的那条肢,缺席可直接观测(不存在「计数归零导致断言因空而绿」的陷阱)。把双发撤回只留别名后实测:
真实 dispatch 用例同向变红:
expected undefined to deeply equal [ 'sales_rep', 'org_admin' ](http-dispatcher.test.ts:3659)。随后已还原。正向(均在共享 verify 锁内、
--max-old-space-size=4096):pnpm --filter @objectstack/runtime test(--maxWorkers=2):Test Files 102 passed (102) / Tests 1476 passed (1476);turbo run test --concurrency=2(全仓):135 successful, 135 total;turbo run typecheck --concurrency=2(全仓):125 successful, 125 total —— 收窄没有溢出 runtime 包;turbo run build --filter='!@objectstack/docs' --concurrency=2:71 successful, 71 total;pnpm lint(eslint --no-inline-config,全仓):无输出即绿;lint job 的 24 个check:*逐条本地跑过,全 OK;check:skill-docs/check:skill-refs/check:skill-examples/check:docs/check:spec-changes/check:upgrade-guide:全部 in sync;node scripts/check-nul-bytes.mjs:OK (scanned 5738 tracked text file(s))。⛔ 未动
packages/spec,未动content/docs/releases/。窗口关闭时要做什么(留给后来者)
移除别名的那一次改动,应当同时:producer 去掉别名键、
action-session-shape-contract.test.ts的键集期望与「双发同值」断言随之收缩、http-dispatcher.test.ts的别名断言删除、spec 侧按迁移条目处置(条目已明说窗口期内不立 tombstone)。技能文档无需改动 —— 它从一开始就只教权威拼法。🤖 Generated with Claude Code
https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ