Skip to content

fix(core): the console runs a stored action that leaves type to its default as a script action - #26964

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-kvrw4t
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-kvrw4t

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #26879

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:控制台动作运行器(@object-ui/core 的 ActionRunner)在选派发路由的唯一一处,把「没写 type」的已存动作读成 spec 的默认值 script,于是它走已注册的 script 派发,打平台动作路由 POST /api/v1/actions/{object}/{action}。
  • 为什么改:spec 的 ActionSchema.type 默认是 script,但控制台跑的是存储里的原样行、不是解析后的行。Studio「新建」骨架和旧版 Studio 存下的无 type 动作,路由被解析成动作自己的名字,匹配不到执行器,被报「not executable」,点了没反应。
  • 风险与回滚:低。只影响「无 type、无 actionType、有名字、名下没注册处理器、也没写运行器自有键(navigate/api/endpoint/onClick/redirect/显式 reload、close)」的动作:原来必然被拒,现在走 script。未知 type、空动作仍拒绝;按名注册的处理器仍优先。回滚即还原 resolveDispatchType 一个函数。
  • 先看:ui/packages/core/src/actions/ActionRunner.ts 的 resolveDispatchType 与 declaresRunnerBehavior(新增)以及 execute 里改用它的那一行;ActionRunner.specDefaultType-26879.test.ts(8 个用例)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/core/src/actions/__tests__/ActionRunner.specDefaultType-26879.test.ts,预期 8 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

  • One place picks the route. execute used to resolve the dispatch key as type || actionType || name. It now calls resolveDispatchType, which keeps the first three legs (an authored type, the action:* block's actionType, the name when a handler is registered under it or the action carries a runner-own key) and, for a named action that has none of those, answers ActionSchema.type's default, script. Nothing unnamed takes the default, so {} still lands in executeActionSchema and is refused.
  • The same key is asked twice, as before: by refusalBeforeInput (the dispatch's early refusal, so a typeless script action meets the script dispatch's row guard) and by dispatchCollected.
  • declaresRunnerBehavior names the runner-own keys once; executeActionSchema's guard now reads it instead of its own copy (same keys: navigate never reaches that guard because dispatchCollected runs it first).
  • Fork taken: the runner, not the serve path. The card offered either the console reading a typeless action as script at route selection, or the serve path returning the parsed row. The runner is in this lane's file surface and covers every unparsed source at once (stored rows from any Studio build or by hand), with no server change. No writer adds type, and no stored row is rewritten.
  • @object-ui/core's exports are unchanged: both helpers and the constant are module-private.
  • One root changeset, @object-ui/core patch.

Tests

New ui/packages/core/src/actions/__tests__/ActionRunner.specDefaultType-26879.test.ts, 8 cases:

  • A typeless { name, body } and a typeless { name, operation: 'update', patch } both POST /api/v1/actions/tasks/NAME through the real createServerActionHandler.
  • A typeless action is asked the script dispatch's refuseBeforeInput.
  • The default is the spec's: the stored row is parsed with @objectstack/spec's ActionSchema, and with a spy handler under every runnable type the typeless action reaches exactly the handler named by the parsed type. If the spec's default ever moves, this case goes red.
  • Not swallowed by the default: an unknown type is refused, {} is refused as "not executable", a handler registered under the action's name runs ahead of script, and a typeless named redirect action still redirects.

The typeless update shape already reached the route through the operation check that runs ahead of type (pinned in ActionRunner.updateOperation.test.ts); it is pinned again here at the new resolution.

Acceptance notes

  • The card's two shapes run on the script route; an unknown type and an empty schema are still refused (cases above).
  • The New skeleton in ObjectActionsPanel.tsx still leaves type off, as its comment intends; with this change that row runs. Its pinned Type control reading "—" until the first write is untouched (outside this claim's file surface).

Verification

All at head e7e7e357, a worktree off origin/main 8074feb6 (main has moved 27 commits since, none in ui/packages/core/; git merge-tree against it is clean).

Tests, ablation and type-check
  • pnpm --dir ui exec vitest run packages/core/src/actions/ under scripts/pm/os-verify-lock.sh: Test Files 45 passed (45), Tests 660 passed (660); the new file alone, verbose: 8/8.
  • Every ui test file that names ActionRunner, ActionProvider, useActionRunner or useConsoleActionRuntime outside packages/core/src/actions/ (383 files: app-shell, plugin-grid, components, react, plugin-detail, plugin-view, console and others), --maxWorkers=2 in four locked batches: 47 + 45 + 145 + 146 files passed, 630 + 452 + 1656 + 1310 tests passed, 0 failed.
  • Ablation through scripts/ablation-replace.mjs on the committed head: return SPEC_DEFAULT_ACTION_TYPE; replaced by return name; (anchor 1 -> 0, blob ecfc5fa6 -> 9f29d4cd). The new file went 3 failed | 5 passed (8): the typeless body action, the early-refusal case and the spec-default case. The typeless update case stays green, as expected: operation is read ahead of type. Restored: blob back to ecfc5fa6 = HEAD, git diff HEAD empty.
  • pnpm exec turbo run type-check --filter=@object-ui/core --concurrency=2 under the lock: VERDICT command-exit 0, 4/4 tasks, core's type-check a cache miss (it runs tsconfig.test.json too, so the new test file is compiled).
Derived dispatch gates (34/34)
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 commands; every one exited 0, the PR-body gates (check-closing-keyword-parity, check-adr-0087-registration, check-changeset-no-major) against this body.
  • pnpm --dir ui exec vitest run scripts/ under the lock: Test Files 178 passed | 2 skipped (180), Tests 5065 passed | 2 skipped (5067).
  • pnpm check:type-check-debt under the lock: 0 raw tsc error(s) total, none above its recorded number.
  • dispatch-gates --ran: 34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED.
  • pnpm check:new-line-citations (from ui/): 0 new citation(s). Control-byte self-scan of the three files: no hit.

No dogfood battery: no ! in the title and no (narrowing) arm.


Generated by Claude Code

…he spec's default script route

`ActionSchema.type` defaults to `script`, but the console runs a stored
row unparsed, so a typeless action resolved its route to its own name,
matched no executor and was refused as "not executable". The runner now
reads a missing `type` as the spec's default at the one place it picks
the dispatch route. An unknown `type`, an empty schema, a handler
registered under the action's name and the runner's own legacy keys
keep their behaviour.

Claude-Session: https://claude.ai/code/session_01QsR2jUfE6TDKDHQvod289V
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · seat domain:ui#4 · session_01QsR2jTfvjNTaR29ehLk26T · 2026-10-27T23:41Z · head e7e7e3572a488018195eb0c77cf3eb3fa6735d76

  1. Shape: draft, base main, Fixes #26879. Clause-②: no and Metadata-protocol: none are in the body and the changeset, and they match the claim (6454575586). Three files, all inside the claimed surface: ui/packages/core/src/actions/ActionRunner.ts, the new ActionRunner.specDefaultType-26879.test.ts, and one @object-ui/core patch changeset. No export added or changed, no writer, no ObjectActionsPanel.tsx, no packages/spec/**, no server package.
  2. Premise, measured: the seat read the diff. The fork is the runner.
    • resolveDispatchType is now the one place execute picks the route. It takes type, then actionType. Failing both, it takes the action's name when a handler is registered under it or when the action declares a runner key of its own (declaresRunnerBehavior). Otherwise a named action takes SPEC_DEFAULT_ACTION_TYPE = 'script', the spec's ActionSchema.type default, applied where the route is read and in no producer.
    • An unknown type stays step 1's answer and is refused, and {} still resolves to '' and is refused (objectui#2960).
    • executeActionSchema's guard now calls the shared predicate. The predicate adds navigate, which is unreachable there: dispatchCollected runs navigate before that fallback, its only caller.
    • The new file pins both typeless shapes and the refusals, and the ablation reddens 3. Every one of the 383 runner-consumer ui test files is green (4048 tests).
  3. Gates, pinned to the head: check-runs?filter=latest 43 success, 9 skipped, 0 failing or pending · check-governed-merges --pr 26964: NOT governed, +258/−9. mergeable_state: clean. origin/main 4b8f8cdc has no resolveDispatchType, so nothing here is already landed.
  4. Accepted deviations: the branch is 27 commits behind main, none in ui/packages/core, and merge-tree is clean, so it was not merged. The thread killed its own stalled single-worker vitest run and re-ran the same 383 files with 2 workers.
  5. Out of scope: none. New's skeleton in ObjectActionsPanel.tsx keeps leaving type off, which is now correct by the spec. No skip, quarantine or raised timeout. Skills-docs: is not owed.
  6. Prose face, checked against the diff: the changeset's "unchanged" bullet matches the four refusal and precedence legs in resolveDispatchType. The doc comments cite console: a stored action without type does not run, though the spec defaults type to script; New's update skeleton and older Studio rows are stored that way #26879, objectui#2960 and [P2] The spec contract stops at authored source — stored metadata is rehydrated unparsed, unconverted, and ungated #3903.

Landing: no contract review is owed (no published schema, no governed text, no exported type, Clause-②: no). The seat marks the PR ready and arms auto-merge. On MERGED, Fixes #26879 closes the card.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 23:42
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 27e4d65 Oct 28, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-kvrw4t branch October 28, 2026 00:22
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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants