Skip to content

feat(lint): flag an action nobody placed — ADR-0078 Phase 3 action-locations - #4501

Merged
os-zhuang merged 1 commit into
mainfrom
claude/bulk-action-aggregated-call-j95epo
Aug 1, 2026
Merged

feat(lint): flag an action nobody placed — ADR-0078 Phase 3 action-locations#4501
os-zhuang merged 1 commit into
mainfrom
claude/bulk-action-aggregated-call-j95epo

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

objectui 侧配套:objectstack-ai/objectui#3154(移除同两个退役 key 的授权控件)。

新规则 action-no-placement(advisory)

一个动作既没声明 locations、也没有任何列表视图按名字放置它,就渲染不到任何表面 —— 它能解析、能发布、在 Setup 里能看到,而没有任何用户点得到它。

ADR-0078 开篇就点名了这个形态(「a summary with no summaryOperations; an action with no locations; … Each parses, "renders", reports success — and does nothing」),Phase 3 要的正是这条规则。ADR 设想的共享完整性谓词从未被建造(其 Status 行自陈),所以本次按 Phase 3 自己的说法「one verified shape at a time」独立落地。

为什么现在才可验证:objectui#3142 之前,action:bar 和记录头部会把未声明的动作照样渲染出来 —— 这个形态在纸面上看着 inert,实际不是。四个渲染器统一到一条放置规则之后(objectui 17.1),它才真正可测量地 inert。

两处刻意不报

  • locations: [] —— 文档化的 headless 动作(actions.mdx 的「Headless actions: declare it, then hide it」:保留能力门、参数契约、审计轨迹,只是不占 UI 表面)。ADR-0110 D3 又拒绝未声明的 handler,所以 headless 声明是暴露这类动作的唯一合法写法,报它等于和那条 ADR 打架。规则因此区分「明确说了哪都不放([])」与「根本没说(key 缺失)」,只报后者。
  • 被视图按名字放置的动作 —— bulkActionsbulkActionDefs(含 execution: 'aggregate' 的定义,这类动作按构造就没有单记录位置)、rowActions,覆盖三层列表视图:views[i].listviews[i].listViews.<key>,以及对象内嵌objects[i].listViews.<key>(对象没有顶层 list,漏掉这层会误报对象自己视图放置的动作)。

严重度

warning / tier: 'advisory',与 validateSemanticRoleslintLivenessProperties 同类 —— 另一个已安装包里的视图可能才是放置方。ADR-0078 §1 也规定「error 只用于完全 inert,degrade 的用 warning」,而这里存在真实的跨包漏报面。

一并修掉的两处

  • metadata-protocol 的动作表单 schema 不再声明 shortcut / bulkEnabled。两者都是 spec 17 的 retiredKey() 墓碑,而这份 schema 正是 Studio 设计器渲染回退表单的依据 —— 继续声明等于给作者两个只能产出存不下去的草稿的输入框(objectui#3154 删的是与之配对的专用控件)。
  • content/docs/ui/actions.mdx 的一句话过宽:它断言"在 widget 里命名动作不绕过位置过滤",而 showcase 自己的 task.view.ts 写着 bulk 场景"命名它就是全部声明"。两者其实都对 —— 选中条是唯一的例外(这正是 action.bulkEnabled 墓碑的原话所规定的)。现在文档说准了,不再与自己的示例互相矛盾。

测试与验证

  • 新增 15 项规则测试:三层视图放置的豁免、[] 与缺失的区分、对象内嵌动作、无名动作跳过,以及模板要求的 floor(clean / {} / null)。
  • 接线守卫 17 项通过(authoring-rule-wiring.test.ts 会读规则源码校验 advisory 声明是否自洽、source 路径是否存在、是否被命令文件直接 import)。
  • packages/lint 744 项、packages/cli 689 项全通过;全量构建通过。
  • 零误报实测:对 app-showcase / app-crm / app-todoobjectstack lint,该规则均无输出。

🤖 Generated with Claude Code

https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE


Generated by Claude Code

New advisory rule `action-no-placement`: an action that declares no
`locations` and that no list view places by name renders on NO surface — it
parses, publishes, shows up in Setup, and no user can ever click it. ADR-0078
names this shape in its opening paragraph and Phase 3 asks for exactly this
rule; the shared completeness predicate it envisioned was never built, so this
lands standalone, one verified shape at a time.

What made it verifiable now: objectui#3142 collapsed four disagreeing
renderers onto one placement predicate. Before that, action:bar and the record
header rendered an UNdeclared action anyway, so the shape only looked inert on
paper. As of objectui 17.1 it is measurably inert.

Two deliberate non-findings:

- `locations: []` — the documented headless action (callable over REST / MCP /
  AI, no UI surface). ADR-0110 D3 refuses an undeclared handler, so this is
  the only legal way to expose one; flagging it would fight that ADR. The rule
  distinguishes "nowhere, deliberately" (`[]`) from an unstated placement (key
  absent) and reports only the latter.
- Actions a view places by NAME — bulkActions, bulkActionDefs (including
  `execution: 'aggregate'` defs, whose whole point is an action with no
  single-record home) and rowActions, across all three list-view tiers:
  views[i].list, views[i].listViews.<key>, and the object-embedded
  objects[i].listViews.<key>.

Advisory, never fatal: a view in another installed package may be the one
placing the action — the same reason validateSemanticRoles and
lintLivenessProperties warn rather than gate. Verified zero findings against
app-showcase, app-crm and app-todo.

Also in this change:
- metadata-protocol's action form schema stops declaring `shortcut` /
  `bulkEnabled`. Both are spec-17 retiredKey() tombstones, and this schema is
  what the Studio designer renders its fallback form from — advertising them
  handed authors two inputs that could only ever produce an unsaveable draft
  (objectui#3145 removed the matching dedicated controls).
- content/docs/ui/actions.mdx said naming an action in a widget never bypasses
  location filtering. The selection bar IS the exception, which the showcase's
  own task.view.ts comment already stated — the two now agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 1, 2026 12:36pm

Request Review

@github-actions github-actions Bot added the size/m label Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/lint, @objectstack/metadata-protocol.

24 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, @objectstack/lint)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 1, 2026
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 1, 2026
Merged via the queue into main with commit 0800433 Aug 1, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/bulk-action-aggregated-call-j95epo branch August 1, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants