Skip to content

fix(action): one precedence for target vs deprecated execute — lower the alias, then drop it (#3713) - #3742

Merged
os-zhuang merged 1 commit into
mainfrom
claude/action-execute-target-precedence-bjsja4
Jul 28, 2026
Merged

fix(action): one precedence for target vs deprecated execute — lower the alias, then drop it (#3713)#3742
os-zhuang merged 1 commit into
mainfrom
claude/action-execute-target-precedence-bjsja4

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3713.

The divergence

execute is the deprecated alias of target. Three readers resolved "the author declared both" in two opposite directions#3713 found two of them; the third turned up while verifying:

Reader Preferred Recorded in #3713?
ActionSchema transform — packages/spec/src/ui/action.zod.ts target
objectui ActionRunner.executeScriptexecute || target execute
CLI compile step — lowerCallables (packages/cli/src/utils/lower-callables.ts) execute new

So defineAction({ type: 'script', target: 'preferredHandler', execute: 'legacyHandler' }) ran preferredHandler server-side and legacyHandler client-side — two different scripts for one button, silently.

The CLI half was the nastiest of the three: with a function inlined in both slots it bundled the execute one and then overwrote action.target with that ref, so the function the author actually declared on target was silently dropped from the build.

What changed

target wins everywhere, and the transform drops the alias from its output — the issue's second (stronger) option: the conflict becomes unrepresentable rather than merely agreed-upon. Same "canonical wins, alias disappears" shape as agent.knowledge.topicssources (#1891), which asserts 'topics' in parsed === false.

Two findings from verification that make dropping clearly the right call:

  • The server runtime never read execute at all. isHeadlessInvokableAction gates on target || body and the dispatch candidate chain probes target/name (packages/runtime/src/action-execution.ts). Authoring execute worked solely because the transform lowered it — so target was already the de-facto canonical slot server-side, and dropping the alias costs the server nothing.
  • It fixes unpatched renderers by construction. An execute || target reader falls through to target once execute is absent, so the spec-side fix alone resolves the divergence for any client receiving parsed metadata — the objectui change is defence-in-depth for paths that bypass parse (cached payloads, raw metadata-fs reads), not a prerequisite.

Compatibility

Authoring is unchanged. execute is still accepted on input (ActionInput = z.input), still lowered to target, still listed in the reference docs and JSON Schema. Nothing to migrate in app metadata.

Consumers of the parsed metadata must read the canonical slot:

  • FROM parsedAction.execute → TO parsedAction.target
  • One-line fix: action.execute || action.target becomes action.target

z.infer<typeof ActionSchema> no longer carries execute, so a stale reader fails to compile rather than silently reading undefined. Swept this repo: no consumer reads execute off a parsed action, and no example/skill/platform-object authors it.

Files

  • packages/spec/src/ui/action.zod.ts — transform lowers and drops; .describe() + TSDoc updated
  • packages/cli/src/utils/lower-callables.ts — probe target first, drop the alias after binding
  • packages/spec/src/ui/action.test.ts — the drop is pinned ('execute' in result === false, key list, JSON round-trip, and execute-only still lowering for a target-required type)
  • packages/cli/src/utils/lower-callables.test.tsnew; 5 cases covering both-callable precedence, alias drop, execute-only back-compat, string/string left to the spec, and object-nested actions
  • packages/spec/liveness/action.json + liveness/README.md — divergence recorded as resolved; evidence corrected to the parse-time lowering (and now resolves cleanly against the gate's path check)
  • content/docs/protocol/objectui/actions.mdx + regenerated content/docs/references/ui/action.mdx

Verification

Suite Result
@objectstack/spec 258 files / 6711 tests ✅
@objectstack/cli 64 files / 652 tests ✅ (5 new)
@objectstack/runtime 46 files / 655 tests ✅
@objectstack/lint 32 files / 467 tests ✅
pnpm build 71/71 tasks ✅ (incl. showcase artifact)
check:liveness ✅ all classified
check:docs / check:skill-docs ✅ in sync

A concurrent pnpm test run also flagged driver-mongodb, driver-sql, plugin-hono-server, service-analytics, service-automation, service-package and metadata-protocol; each passes standalone (verified for metadata-protocol and lint) — resource contention from running 98 vitest tasks in parallel in the container, unrelated to this change.

Follow-ups

  • objectui (separate repo, separate PR): flip ActionRunner.ts executeScript to const script = action.target || action.execute;. Not a prerequisite — see "fixes unpatched renderers" above — but it closes the parse-bypassing paths.
  • Filed separately: an author-facing warning when both slots are declared. Discarding execute is now documented, tested and consistent, but still silent, and the compile-time lints all run post-parse (compile.ts lints result.data) where the alias is already gone — surfacing it needs a pre-parse hook, which is a bigger change than this fix warrants (Prime Directive chore: version packages #10).

Generated by Claude Code

…wer the alias, then drop it (#3713)

`execute` is the deprecated alias of `target`, and three readers resolved
"the author declared both" in TWO OPPOSITE directions:

  - `ActionSchema`'s transform (spec)      → kept `target`
  - objectui `ActionRunner.executeScript`  → `execute || target`
  - CLI compile step (`lowerCallables`)    → preferred a fn on `execute`

So an action declaring both ran `preferredHandler` server-side and
`legacyHandler` client-side — two different scripts for one button, silently,
with no error anywhere.

`target` now wins everywhere, and the transform DROPS the alias from its
output, so the conflict is unrepresentable rather than merely agreed-upon —
the same "canonical wins, alias disappears" shape as `agent.knowledge.topics`
→ `sources` (#1891). Note the server runtime never read `execute` at all
(`isHeadlessInvokableAction` gates on `target || body`; the dispatch candidate
chain probes `target`/`name`), so authoring it worked solely because it was
lowered at parse time — dropping it costs the server nothing and takes the
ambiguity off the wire. It also fixes unpatched renderers by construction: an
`execute || target` reader falls through to `target` once the alias is gone.

The CLI had the same bug in compile-time form, which #3713 did not record: with
a function inlined in both slots it bundled the `execute` one and then
overwrote `action.target` with that ref, silently discarding the function the
author declared on `target`. It now probes `target` first and drops the alias.

Authoring is unchanged — `execute` is still accepted on input (`ActionInput`),
still lowered, still documented. Consumers of the PARSED metadata must read
`target`; `z.infer<typeof ActionSchema>` no longer carries `execute`, so a
stale reader fails to compile instead of silently reading `undefined`.

Also updates the liveness ledger entry (the divergence it recorded is now
resolved, and the cited evidence is the lowering, not a second field reader).

Verified: spec 6711 tests, cli 652 (5 new), runtime 655, lint 467, plus the
liveness and generated-docs gates.

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

vercel Bot commented Jul 28, 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 Jul 28, 2026 1:16am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 28, 2026 02:07
@os-zhuang
os-zhuang merged commit 08b5a3d into main Jul 28, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/action-execute-target-precedence-bjsja4 branch July 28, 2026 02:07
os-zhuang added a commit that referenced this pull request Jul 28, 2026
… and drop the alias (#3754) (#3764)

Second instance of the alias-drift shape #3742 fixed for `action.execute`, found
while verifying that fix.

`requiredWhen` is canonical and `conditionalRequired` is its documented deprecated
alias, but `FieldSchema` had NO canonicalization at all: both keys stayed live in
the parsed output, leaving every consumer to re-implement the precedence — the
condition that produced #3713. The alias surviving parse was even test-pinned,
including a case named "requiredWhen and its alias conditionalRequired can
coexist", the inverse of the contract #3742 had just established one field over.

`FieldSchema` now lowers the alias into `requiredWhen` at parse time and removes it
from the output; canonical wins when both are declared. The pinning tests are
inverted, and a new case asserts the alias is gone from a field parsed through
`ObjectSchema` — the path a renderer actually receives.

This fixes no live bug: every reader we can see already prefers the canonical key
(`rule-validator.ts` reads `requiredWhen ?? conditionalRequired`). Nothing in the
contract MADE that right — this is hardening, removing the chance rather than a
defect.

objectql's fallback is deliberately kept: `evaluateValidationRules` is also handed
raw, unparsed field definitions, which still carry the alias.

Authoring is unchanged. Consumers of the parsed metadata must read `requiredWhen`;
`z.infer<typeof FieldSchema>` no longer carries the alias, so a stale reader fails
to compile instead of silently reading `undefined`. Adds `FieldParseInput`
(`z.input`) for the author-facing shape — `FieldInput` was already taken by an
unrelated `Partial<Field>` factory type in the same file.

Closes #3754.
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 protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] action execute vs target: spec and objectui resolve the precedence in OPPOSITE directions

2 participants