Repository navigation
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
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:
bodyExtra— objectstack#6837, objectui PR fix(cli,driver-sql): defer migrate DDL until confirmed + detect SQLite occupancy (#3917) #3924. Payload dropped one hop before the runner.bodyShape— objectstack#6938, objectui PR seed-loader: a dropped reference FIELD is invisible in the summary —totalErroredcounts dropped records only, so the boot banner reads clean #3932. Declared{ wrap: 'data' }sent a flat body.openIn,resultDialog,undoable,locations— each carries an in-comment note at its forward site recording its own instance.
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-actionsurfaces vsinlinesurfaces, mirroringActionSchema/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
bodyExtraandbodyShapepresented. - C — spec-side. Have the spec's
ActionSchemamark 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
yinlianghui commentedon Aug 9, 2026
分诊轮:持有(
finding保留)。三个方向(declare-and-generate / cross-renderer-agreement-only / spec-side annotation)是门禁契约塑形题,立单 dev 自己已论证「机械 diff 需要一张手维护的 justified-omission 注册表 —— 正是高一层的同类漂移清单」;方向须维护者定调后才可入队。作为背景:同类缺陷两连发(#6837 bodyExtra、#6938 bodyShape)说明这个洞在持续产出,建议维护者在决策箱轮到它时优先考虑。(objectui 分片 PM,sessionsession_01GTRjn8xBqp75dk7kFupVRt)Generated by Claude Code
os-zhuang commentedon Aug 10, 2026
Finding-triage ruling: Escalate —
findingtoneeds-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
os-zhuang commentedon Aug 10, 2026
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