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.
os-zhuang added a commit that referenced this pull request Jul 28, 2026
…hem a handler (#3743) (#3838)

#3742 made `target` beat the deprecated `execute` alias everywhere and had the
`ActionSchema` transform DROP the alias from its output, so "two different
scripts for one button" became unrepresentable. What it left behind: an author
who declares both slots with different values still loses one of the two
handlers they wrote, silently. Per Prime Directive #12 that belongs at
authoring time.

New advisory rule `action-target-execute-conflict`: an action declaring both
slots with different values gets a warning naming both handlers, stating that
`target` wins, and giving the one-line fix. Equal values are harmless
duplication and stay quiet. It never fails the build.

The rule has to run PRE-PARSE, because the parse is what consumes the alias.
#3743 proposed the CLI compile pipeline as its home; that window is real but
nearly always empty, because `defineStack` parses inside the author's own
config module — for a `defineStack` app the alias is gone before `os build`
ever loads the module. So the rule lives in `@objectstack/spec`
(`lintDeprecatedAliases`) and is wired into both layers that perform the
discard: `defineStack` warns before parsing (once per distinct conflict), and
`os build`/`os validate` run a pre-parse pass for stacks that skip strict
`defineStack` (plain object export, `strict: false`, inline function handlers).
Each layer reports only its own discards, so one conflict yields one warning.

Behaviour fix in the same contract: #3742 probed for a CALLABLE `target` first,
which left a string `target` beside a function `execute` still resolving the
alias's way — it bound the alias and overwrote the canonical ref the author
wrote. `target` now wins in every string/function combination, matching the
`ActionSchema` transform.

Also documents the rule in the objectui actions reference, which already stated
the precedence while saying nothing about the author finding out.
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…to the protocol-17 conversion layer (#3796) (#3976)

* feat(spec,automation): graduate the seven flow-node config aliases into the protocol-17 conversion layer (#3796)

FlowNodeSchema.config is an unconstrained record, so the executors were the
only statement of which config key is canonical — and seven deprecated
aliases lived there: object→objectName behind the readAliasedConfig shim,
plus six open-coded ?? fallbacks (notify to/subject/body/url, script
functionName/input) with no warning, no ledger, and no retirement path.

All seven graduate into the ADR-0087 D2 conversion layer as protocol-17
live-window entries (flow-node-crud-object-alias,
flow-node-notify-config-aliases, flow-node-script-config-aliases): a stored
flow authored with an alias is rewritten to the canonical key at load —
normalizeStackInput and the AutomationEngine.registerFlow rehydration seam
alike — with a structured ConversionNotice per rewrite. The executors read
canonical keys only, and the emptied readAliasedConfig shim is deleted.

actionUrl (not url) is the deliberate canonical of its pair, resolving the
contradiction where the notify descriptor documented url as canonical while
the executor precedence, tests, and examples all preferred actionUrl: the
whole downstream chain already uses that name (sys_notification.action_url,
the channel contract, the REST read model), and url elsewhere means an HTTP
endpoint to call. The choice is behaviour-preserving.

Also: step-17 migration chain + regenerated spec-changes.json /
protocol-upgrade-guide.md carry the new entries; PD #12 records the endgame;
common-patterns.mdx now authors canonical keys; lint comments updated to
reference the conversions instead of the deleted shim.

Refs #3796, #3713, #3742, #3754, #3795, ADR-0087.

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

* docs(releases): v17 notes cover the seven flow-node config alias graduations (#3796)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
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