Skip to content

fix(field): fold deprecated conditionalRequired into requiredWhen and drop the alias (#3754) - #3764

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

fix(field): fold deprecated conditionalRequired into requiredWhen and drop the alias (#3754)#3764
os-zhuang merged 2 commits into
mainfrom
claude/action-execute-target-precedence-bjsja4

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3754. Follows #3742 — same bug class, one field over, found while verifying that fix.

The shape

requiredWhen is canonical, conditionalRequired is its documented deprecated alias. Unlike action.execute, FieldSchema had no canonicalization at all — no lowering, no dropping. Both keys stayed live in the parsed output, so the precedence was re-implemented per consumer instead of being decided once in the contract. That is exactly the condition that produced #3713.

And the alias surviving parse was test-pinned, including one case named literally:

it('requiredWhen and its alias conditionalRequired can coexist', …)

— the inverse of the contract #3742 had just established one field over.

Honest framing: this is hardening, not a live-bug fix

Every reader I could inspect already prefers the canonical key:

Reader Behavior
packages/objectql/src/validation/rule-validator.ts requiredWhen ?? conditionalRequired — canonical first ✅
packages/lint/src/validate-expressions.ts iterates both keys to validate expressions — order-independent ✅
Studio object designer (#2571) rewrites the alias on first edit — UI-only, so code-authored metadata never passes through it
objectui client-side required rendering not verifiable from this repo (separate repo)

So no button is currently misbehaving. The problem is that nothing in the contract made the server right — the one surface most likely to have picked alias-first order is the same one that did in #3713, and it's the one I can't inspect from here. Folding once, in the schema, removes the chance rather than a defect. I'd rather state that plainly than dress this up as a bug fix.

What changed

FieldSchema gains a transform that lowers conditionalRequired into requiredWhen and drops the alias; requiredWhen wins when both are declared. Same shape as action.executetarget (#3742) and agent.knowledge.topicssources (#1891).

objectql's fallback is deliberately kept. evaluateValidationRules is also handed raw, unparsed field definitions — its own test suite does exactly that (rule-validator.test.ts, the conditionalRequired case) — so the alias-reading path still has real inputs. I commented it in place so it doesn't read as dead code, and noted that the order must stay canonical-first. This is the same defence-in-depth reasoning used for objectui in #3742, and it's why I did not follow step 3 of my own suggestion in #3754.

Compatibility

Authoring is unchanged. conditionalRequired is still accepted on input, still lowered, 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 parsedField.conditionalRequired → TO parsedField.requiredWhen
  • One-line fix: field.conditionalRequired || field.requiredWhen becomes field.requiredWhen

z.infer<typeof FieldSchema> no longer carries the alias, so a stale reader fails to compile rather than silently reading undefined.

One naming note: I added FieldParseInput (z.input<typeof FieldSchema>) for the author-facing shape. FieldInput was already taken by an unrelated factory-helper type (Omit<Partial<Field>, 'type'>) further down the same file — worth knowing before reaching for that name.

Risk that needed checking

This is the first transform on FieldSchema, which turns it from a ZodObject into a wrapped schema. Two things that would have broken:

  • Schema composition.shape / .extend / .partial / .omit / .pick don't exist on a transform-wrapped schema. Swept the repo: no such use of FieldSchema. ✅
  • The liveness gate's schema walker — if it couldn't unwrap the wrapper, every field property would go UNCLASSIFIED and the gate would fail. It unwraps generically (as it already must for action). ✅ Gate passes.

Verification

Suite Result
@objectstack/spec 258 files / 6719 tests ✅
@objectstack/objectql 75 files / 1119 tests ✅
@objectstack/runtime 46 files / 655 tests ✅
@objectstack/metadata 13 files / 276 tests ✅
@objectstack/lint 32 files / 471 tests ✅
pnpm build 71/71 tasks ✅ (incl. showcase artifact)
check:liveness ✅ all classified; the new evidence string resolves cleanly against the gate's path check
check:docs ✅ 250 generated files in sync

Follow-ups


Generated by Claude Code

… and drop the alias (#3754)

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. That is exactly the condition that produced #3713, where the spec
kept `target` while objectui's ActionRunner preferred the alias and one button
ran two different scripts, client vs server.

Worse, the alias surviving parse was test-pinned — including a case literally
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, not just a bare
`FieldSchema.parse()`.

This fixes no live bug: every reader we can see already prefers the canonical key
(`rule-validator.ts` reads `requiredWhen ?? conditionalRequired`). The point is
that 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 (see its own `conditionalRequired` test), which
still carry the alias. Commented in place so it doesn't read as dead code.

Authoring is unchanged — the alias is still accepted on input, still lowered,
still documented. 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 — distinct from the pre-existing
`FieldInput` factory type (`Partial<Field>`), which is unrelated.

Verified: spec 6719, objectql 1119, runtime 655, metadata 276, lint 471,
`pnpm build` 71/71, 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 2:42am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/m labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

108 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 @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @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/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 @objectstack/objectql, 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 packages/objectql, @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/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • 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/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/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via 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/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • 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/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @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/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • 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/objectql, @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/objectql, @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/spec)
  • content/docs/releases/v9.mdx (via @objectstack/objectql, @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.

CI's api-surface gate (run inside the "TypeScript Type Check" job) flagged the
new export: "0 breaking (removed/narrowed), 1 added". The addition is intentional
— `FieldParseInput` = `z.input<typeof FieldSchema>` names the author-facing shape
that still accepts the deprecated `conditionalRequired` alias, mirroring the
existing `CurrencyConfigInput` / `DataQualityRulesInput` in the same file and
`ActionInput` on the action schema. Regenerated the snapshot per the gate's own
instruction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDwRktLBFwgtV5Svxm4LJm
@os-zhuang
os-zhuang marked this pull request as ready for review July 28, 2026 02:58
@os-zhuang
os-zhuang merged commit f5a2320 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:58
os-zhuang added a commit that referenced this pull request Jul 28, 2026
…#3743) (#3854)

#3838 introduced `lintDeprecatedAliases` — the pre-parse pass that reports an
alias the parse itself is about to consume — with one rule, for
`action.execute`. #3743 predicted the pass would earn its keep beyond that rule.
It does: `execute` was never special. The spec has exactly THREE transforms that
fold an alias into its canonical key and then drop it from the parsed output,
and all three share one failure mode — declare both slots with different values
and one is discarded with no signal, invisible to every downstream check because
the parse already erased the evidence.

Two more rules, same shape, same advisory severity, same two surfaces:

  field-requiredwhen-conditionalrequired-conflict — `FieldSchema` folds
  `conditionalRequired` into `requiredWhen` (#3754/#3764); the discarded
  predicate never gates the field. Covers fields on objects AND on object
  extensions, and compares the predicate TEXT so a bare string and the
  `{ dialect, source }` envelope it lowers into read as the same predicate.

  agent-knowledge-sources-topics-conflict — `AIKnowledgeSchema` folds
  `knowledge.topics` into `knowledge.sources` (#1878/#1891); the discarded list
  names RAG sources the agent never recruits from. Compares by SET, so the same
  sources in a different order stay quiet.

The three findings now share one builder, so they phrase the common half
identically (which slot wins, that the alias is dropped, delete it) while each
still names what its own discarded value would have done. No CLI change: both
call sites already loop over whatever the pass returns.

Also corrects `content/docs/ai/agents.mdx`, which documented `knowledge` as
`{ topics, indexes }` and used `topics` in all three examples — teaching the
deprecated alias as the canonical key, and contradicting
`skills/objectstack-ai/SKILL.md`, which already had it right.

Follow-ups filed: #3855 (removal schedule for all three aliases), #3856
(objectui's now-dead `execute || target` fallback).
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:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] field.conditionalRequired is the same alias-drift shape #3713 just fixed — and the spec pins the alias to SURVIVE parse

2 participants