Skip to content

objectui: no gate diffs the action renderers' forward whitelists against the keys the runtime actually reads — every new spec action key is silently dropped until five lists are edited #6975

Description

@yinlianghui

Carried out of objectstack#6938 (bodyShape), whose body proposed this and whose triage marked it a non-binding candidate for the implementing dev. Filed standalone so the suggestion survives #6938 closing, and deliberately not implemented in objectui PR objectstack-ai/objectui#3932 — the reasons are the substance of this card.

The recurring shape

Every action renderer forwards an explicit whitelist of keys to the ActionRunner. That is a deliberate design, not an oversight: a key no renderer honours must not look wired. Its cost is that a NEW spec key stays invisible until five separate lists are edited, and nothing fails while they are not — the key parses, publishes, and reads as honoured.

Confirmed instances, same face each time:

Four of the six were found by a human reading the lists side by side. There is no gate.

Why PR #3932 did not land the proposed pin

The proposal — diff the renderers' forwarded key sets against the spec keys the runtime actually reads — needs two inputs, and only one of them is mechanically derivable.

Derivable: the forwarded key sets. AST-read the object literal each renderer passes to execute(...). packages/core/src/actions/__tests__/actionKeys.pin.test.ts already does this class of walk (it reads ActionDef's members off the interface with the typescript compiler API), so the technique is established in-repo.

Not derivable: "the keys the runtime reads". The read sites span packages/core (ActionRunner) and packages/app-shell (useConsoleActionRuntime.apiHandler, RecordDetailView.apiHandler), and a git grep 'action\.' over them returns a large set of action.name / action.objectName / action.api / action.method hits with no way to separate "body-path key a renderer must forward" from "mechanic the runner resolves itself". Grepping produces a false-positive list, not a contract.

And the omissions are load-bearing, not noise. recordIdParam is forwarded by none of the five and that is correct — #6938 checked it: the apiHandler reads it only under if (rowRecord && action.recordIdParam), and rowRecord arrives only from the spread-based hosts (DeclaredActionsBar / RelatedRecordActionsBridge / ObjectGrid), which carry recordIdParam too. It is unreachable on the declared renderers, not broken. element:button's omission of bodyShape is likewise correct and deliberate: its list mirrors spec's InlineActionSchema pick list, which does not include the key.

So a mechanical diff needs a hand-maintained justified-omission registry beside it — which is itself the drift-prone list this whole thread is about, one level up. That is a design decision about where the contract for "which keys each renderer surface owes the runtime" should live, not a test to bolt on, and it wants a maintainer's call rather than a dev's guess mid-fix.

Directions worth weighing (not a recommendation to implement as-is)

  • A — declare the surface, generate the lists. Put the per-surface key set in one declared table (declared-action surfaces vs inline surfaces, mirroring ActionSchema / InlineActionSchema), have the renderers forward off it, and let the pin re-derive both halves. Removes the five hand-maintained lists entirely rather than testing them. Highest cost, and the only option that makes the failure structurally impossible instead of merely loud.
  • B — pin cross-renderer agreement only. AST-read the four declared renderers' lists and assert they agree on their shared subset. No omission registry needed (the renderers are compared against each other), and it catches the real recurrence mode of "added to one list, forgotten in three". Does not catch a key missing from all four — which is exactly how bodyExtra and bodyShape presented.
  • C — spec-side. Have the spec's ActionSchema mark which keys are renderer-forwardable and publish that as data the pin consumes. Puts the contract at the producer, where #0.1 says it belongs; needs a spec change and cross-repo sequencing.

B is cheap and partial; A and C address the actual failure but are contract-shaping. #6938's PR deliberately made no guess between them.

Grade

Observation-class: nothing a user hits today that is not already tracked by the per-key cards. This is preventive infrastructure for the next key — finding, no pm:queue, unassigned for triage.

Activity

  1. yinlianghui commented on Aug 9, 2026

    @yinlianghui
    CollaboratorAuthor

    分诊轮:持有(finding 保留)。三个方向(declare-and-generate / cross-renderer-agreement-only / spec-side annotation)是门禁契约塑形题,立单 dev 自己已论证「机械 diff 需要一张手维护的 justified-omission 注册表 —— 正是高一层的同类漂移清单」;方向须维护者定调后才可入队。作为背景:同类缺陷两连发(#6837 bodyExtra、#6938 bodyShape)说明这个洞在持续产出,建议维护者在决策箱轮到它时优先考虑。(objectui 分片 PM,session session_01GTRjn8xBqp75dk7kFupVRt)


    Generated by Claude Code

  2. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Finding-triage ruling: Escalate — finding to needs-user-decision.

    The gap recurred twice in one week (#6837, #6838/#6938): every new spec action key is silently dropped until five hand-maintained renderer whitelists are edited (action-button.tsx:117, action-group.tsx:226, action-icon.tsx:72, action-menu.tsx:171, plus the pin tests), and no gate diffs those whitelists against runtime read sites. The mechanical diff needs a justified-omission registry, and where that contract lives is a maintainer call — the objectui shard PM's own grading suggested decision-box priority.

    Question for the maintainer: pick the direction — A generate the whitelists from a table, B cross-renderer agreement gate, C spec-side annotation — or decline the gate.

    Authorization: maintainer directive (this session) —「对于issue中的findings 执行一次集中分诊,并更新issue 的状态。」Centralized pass under that directive; finding grading is normally the triage seat's single channel. Round: objectstack#4949 finding-triage, 2026-08-10.
    Session: 01JaVVMrSxt7Tgi1uwEuDtH7


    Generated by Claude Code

  3. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Migrated to objectstack-ai/objectui#4050 under #7167 (file-at-destination ruling, maintainer 2026-08-10). Native GitHub issue transfer is not available to this session's credential, so the card was recreated at the destination; this thread stays as the authoritative history and is linked from the new card. Closing as not planned here — moved, not rejected.


    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