feat(spec): ActionSession 补 positions 权威键,roles 降为弃用别名 (#5779) - #5849
Conversation
…as its deprecated alias (#5779) The spec half of #5613 phase 2, under the maintainer's contract-first ruling ("C skeleton + A semantics"). Phase 1 (#5697) declared the action-body `ctx.session` shape exactly as `buildActionSession()` built it and deliberately withheld a `positions` key — "minting one before the migration would ship two live spellings of one value". That prohibition existed to stop a two-spelling window opening without a closing date; this change opens it WITH one, and rewrites the docstring that carried the ban. `positions` is now the canonical key: the ADR-0090 D3 vocabulary the execution context, the sharing service and (since #5605 / PR #5722) the hook `ctx.session` already speak. `roles` becomes a deprecated alias of it, removed after one window on the path `session.tenantId` already walked (#3280 deprecated, #3290 removed in v11). The ADR-0087 semantic migration `action-session-roles-to-positions` is the reader-facing channel. It is a D3 semantic TODO rather than a D2 conversion on two independent grounds: an action `ctx.session` is constructed per dispatch and never persisted, so there is no source for the chain to rewrite; and the only place the key is ever spelled is inside an action body, i.e. free-form author-written code, which is why the ADR-0090 wave delegated `current_user.roles` at step 13 instead of substituting text. The alias is deliberately NOT tombstoned — a tombstone rejects the key, which is the removal a deprecation window exists to defer. Contract leads producer: `buildActionSession()` dual-emitting both keys, and the two wrong sentences already tracked in its docblock, are #5613's runtime half and are not in this change. `packages/runtime` is untouched. Both keys are optional, so the runtime consistency pin (`action-session-shape-contract.test.ts`) is unchanged and stays green — a non-strict parse of a session without `positions` neither gains the key nor rejects the object. Measured before and after: 10/10 passing both times. Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ion-session-positions
… describe text (#5779) `check:org-identifier` is a hard-fail guard, and it skips COMMENT lines but not code — so naming the removed alias in a JSDoc note is documentation, while naming it inside a `.describe()` string or a migration `reason` is a published string the gate rightly refuses. Those strings are not incidental: a `.describe()` lands verbatim in `content/docs/references/`, which is exactly the author-facing prose an AI copies a hook or action body from, and the guard exists because a `session.tenantId` read there resolves to `undefined` (#3290 removed it in v11). Three occurrences reworded to "the v11 session-alias removal path", keeping the #3280 → #3290 anchor that makes the precedent findable: two `.describe()` strings on ActionSession (`positions`, `roles`) and the migration entry's `reason`. The JSDoc mentions are left as they are — the gate permits them by design, and the pre-existing `organizationId` docblock already names the alias the same way. `os-allow-tenant-id` was deliberately NOT used: it is documented for the rare genuine driver-layer read, and spending it on prose would launder a documentation reference past a guard that has zero baselined occurrences. Generated artifacts regenerated from the reworded source; the `#3290` assertion in the description pins still holds. Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D Co-authored-by: Claude <noreply@anthropic.com>
|
org-identifier 措辞修订(
闸门为什么对:它跳过注释行但不跳过代码行——所以 JSDoc 里点名这个已移除别名是「文档」(现存 没有用 合并
合并时 Generated by Claude Code |
…ble-surface) #5861 被合并队列踢出,签名是 `strictness-ledger-doc.test.ts > is checked in current`。 归因:#5849 / #5857 在入队前合入 main(#5857 给 `api/` 新增了一个 z.object 站点), 本分支的 counts.md 是在旧树上渲染的,合并树上不再等于现渲染 —— 单体生成物的串行税 (#5837 正在治的病),不是实现回归。 在合并树上重跑 os-regen:`api/` 站点数 395 → 396(main 的 +1 与本单 `projectionApplied` 嵌套对象的 +1),docs 与 authorable-surface 一并重渲。相对 origin/main 的净增量仍只有 本单四个键。`authorable-surface.base.json` 本轮无重锚漂移(已核对与 main 一致)。 Co-Authored-By: Claude <noreply@anthropic.com>
Fixes #5779
#5613 第二阶段的 spec 半边,依维护者「C 骨架 + A 语义」裁定。⛔
packages/runtime零文件触碰(已实测确认,见下)。前提核验(实测
origin/main@1624f4ad2)issue 前提成立:
ActionSessionSchema在packages/spec/src/ui/action-params.zod.ts:206,roles已标 deprecated,其 docstring 明写「The rename is #5613 phase 2; do NOT add apositionskey here ahead of it」——本单正是那条禁令等的东西,随本 PR 解除并改写。做了什么
positions为权威键(z.array(z.string()).optional())。describe 指向 ADR-0090 D3 /ExecutionContext.positions,并把现rolesdescribe 里「Never gate PRIVILEGE on this array——问 security service(ADR-0095)」的告诫原样带过来:告诫属于值而不属于拼法,所以更名必须原样保留。JSDoc 写明双发窗口语义,以及一句必须说破的话——把roles.includes('admin')改写成positions.includes('admin')是迁移了缺陷,不是迁移了代码。roles改写为弃用别名对:describe 改为「DEPRECATED alias ofpositions」,点名 ADR-0087 条目 id 与 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) #3290session.tenantId别名下线先例路径;@deprecatedJSDoc 保留并更新指向。action-session-roles-to-positions(step17,紧邻 refactor(spec)!: 退役 HookContext.session.roles —— 声明过、被两条死分支读过、从未被生产 (#5050) #5621 落的hook-context-session-roles-retired)。定性为语义迁移而非 D2 数据转换,两条独立理由:action ctx 每次派发现造、从不落库,无存量源可改写(照 [spec] 退役 HookContext session.roles —— #4839 双删后零消费方零生产方(ADR-0049) #5050 / RestServerConfig.openApi31(OpenApi31Extensions / Callback / OpenApiWebhookEvent)declared ≠ enforced:没有任何运行时读取它 —— ADR-0049 enforce-or-remove 候选 #4579 / ADR-0049:两份activationEvents声明四仓零 reader —— declared-but-unenforced,且 studio 侧z.string()零校验 #4657 先例);且这个键唯一被拼写的地方是 action body 里的自由代码,声明式 transform 不能安全改写代码中的标识符——正是 ADR-0090 那一波在 step 13 把current_user.roles交还作者(cel-current-user-roles-to-positions)而非做文本替换的同一理由。条目里另写明故意不立墓碑:retiredKey()是拒收该键,而弃用窗口的全部意义就是让旧拼法继续可用——窗口期立墓碑等于把它要推迟的移除提前执行了。authorable-surface.json+1 键(ui/ActionSession:positions,与预期一致)、spec-changes.json、docs/protocol-upgrade-guide.md、reference 页。@objectstack/specminor,正文是作者视角的迁移处方。兼容性核验(先证红方向,再实测)
预测写在跑之前:本单只加 optional 键,runtime 的
action-session-shape-contract.test.ts(钉parse(built)深等于built+ 键集断言)应当保持绿——加可选键既不能拒收原本合法的对象,也不会在缺该键的输入上把键凭空造出来;而生产方(仍只发roles)根本没动。Test Files 1 passed (1)/Tests 10 passed (10)Test Files 1 passed (1)/Tests 10 passed (10)方向与预测一致。这里不是「先绿后红」那类反向验证,而是一次不动的预测——说明它有意义的,是方向和机制都写在跑之前:若它红了,就意味着契约把生产方带崩了,而弃用窗口存在的全部目的正是不让这件事发生。已在测试块注释里如实标注这一点,没有硬套模板。
spec 侧先证红(把
positions声明删掉后实测):三条按预测转红,已恢复。
钉什么的判据:
ActionSessionSchema刻意非 strict(平台交给 body 的运行时形状),所以safeParse().success在这里毫无价值——未声明的键照样「解析成功」然后被静默剥掉。承重断言因此是「parse 保住了这个键」。这是键可达性问题而非值裁决问题(schema 对positions的值除string[]外不作判断),所以要求更多就是钉了契约没主张的东西。消费半径扫描
ActionSessionSchema的消费方:runtime 的 pin 测试 +action-execution.ts的类型标注 +sandbox/script-runner.ts的注释——均不改。全仓 grepsession.roles的其余命中都在 hook 面(skills/objectstack-data/references/data-hooks.md、plugin-approvals的 #4839 pin、hook.zod/hook.test),是 #5050 已退役的另一张面,与本单无关且未触碰。验证
pnpm --filter @objectstack/spec check:generated✓ All 10 generated artifacts are up to date.pnpm --filter @objectstack/spec typechecktsc --noEmit通过;check:test-typecheck: OKpnpm --filter @objectstack/spec testTest Files 319 passed (319)/Tests 8152 passed (8152)10 passed (10),改动前后各一次node scripts/check-nul-bytes.mjsOK (scanned 5678 tracked text file(s));另对本单所有改动文件做了grep -naP越界自扫,干净两处需要 reviewer 知道的判断
authorable-surface.base.json的重锚漂移已剔除(照check:authorable-surface在--check模式下也会重写authorable-surface.base.json—— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358)。gen:schema会把它重锚到 merge base 并带进 8 个别人已合并的键(discovery 族,SDK 的 client.capabilities 声明为 WellKnownCapabilities,但两个 discovery 生产者填的是互不相交的键集 #5672 在飞面),与本单无关,故 revert。闸门对此非致命并自报:「trails the merge base by 8 key(s) — expected right after a surface change lands」,已实测确认剔除后仍绿。reason必须是单段。首版我在reason里用了段落分隔,生成器把它直接插进嵌套 bullet(- Why not automatic: ${s.reason}),换行会把后续段落顶出列表结构、破坏 markdown 层级。已改回单段,与现存全部条目一致;重新生成后 diff 恰为 3 行(条目 + Why not automatic + Done when)。后续
#5613 的 runtime 半边(
buildActionSession()双发、两句错注释修正、翻action-session-shape-contract.test.ts键集断言、major changeset)本单落地后可派。承重翻转点未动,仍在原处。Generated by Claude Code