Skip to content

fix(objectql): enforce validation rules, requiredWhen and option visibleWhen on multi-row updates#3160

Merged
os-zhuang merged 2 commits into
mainfrom
claude/validation-multi-row-updates-kqxgg4
Jul 18, 2026
Merged

fix(objectql): enforce validation rules, requiredWhen and option visibleWhen on multi-row updates#3160
os-zhuang merged 2 commits into
mainfrom
claude/validation-multi-row-updates-kqxgg4

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Summary

Closes #3106.

evaluateValidationRules() is the single gate for object-level validation rules (script, state_machine, format, cross_field, json_schema, conditional), field-level requiredWhen, and per-option visibleWhen authorization. It ran on insert and single-id update but was never called in the bulk branch of engine.update (options.multidriver.updateMany) — every one of those checks was a silent no-op there, with only a narrow warning that skipped format/json_schema-only schemas entirely.

Per the decision on the issue, this is the full enforcement option:

  • The multi branch now reads the row-scoped match set once — the same middleware-composed AST the write binds, with the fetch shared with the 安全:readonlyWhen 服务端剥离仅覆盖单条 update 路径,多行 updateMany 不强制(条件只读可被批量绕过) #3042 readonlyWhen bulk strip (needsPriorRecord subsumes hasReadonlyWhenInPayload, so there is never a second query).
  • The stripped payload is evaluated against each matched row's prior state, mirroring the single-id branch's ordering (strips first, then rules). Any error-severity violation throws ValidationError — annotated with the failing record id — before updateMany writes anything (all-or-nothing, like the strip's locked-in-any-row rule).
  • Schemas with no prior-dependent rules (format / json_schema / non-prior conditional only) are evaluated once against the payload with no fetch.
  • Rule-free schemas hit the evaluator's early return: the internal multi: true callers (messaging outboxes, settings rows) see zero behavior change and no extra query — covered by a regression test.

Deliberate behavior change: bulk writes that previously slipped past declared rules now throw. Neither REST nor GraphQL surfaces multi, so this is unreachable from untrusted external callers; in-process callers with rule-bearing schemas are the affected surface.

Also fixes the two doc comments that overstated coverage (rule-validator.ts "no silent no-ops", validation.zod.ts "nothing is a silent no-op") — both now describe the per-row bulk enforcement and name the remaining events: ['delete'] gap, which stays split out per the issue decision — and refreshes the AGENTS.md PD #10 example that described the pre-fix state.

Changes

  • packages/objectql/src/engine.ts — per-row rule evaluation in the multi branch; single shared prior-row fetch; obsolete warning removed; ValidationError imported for the row-annotated rethrow.
  • packages/objectql/src/validation/rule-validator.ts, packages/spec/src/data/validation.zod.ts — doc comments corrected (no logic change).
  • AGENTS.md — PD chore: version packages #10 example updated to past tense with the same "check the call site" moral.
  • .changeset/bulk-update-validation-rules.md — patch changeset for @objectstack/objectql + @objectstack/spec.

Tests

Verified: objectql suite 67 files / 917 tests green, spec suite 255 files / 6895 tests green, objectql build green, eslint clean on changed files.

Known gaps (out of scope, tracked separately)

🤖 Generated with Claude Code

https://claude.ai/code/session_01VCUSMJBsX14C3RQdWFw7N7


Generated by Claude Code

…bleWhen on multi-row updates (#3106)

The bulk branch of engine.update (options.multi → driver.updateMany) never
called evaluateValidationRules, so every object-level rule (script,
state_machine, format, cross_field, json_schema, conditional), field-level
requiredWhen, and per-option visibleWhen check was a silent no-op there —
the only signal was a warning that skipped format/json_schema-only schemas
entirely.

The engine now reads the row-scoped match set once (the same middleware-
composed AST the write binds, shared with the #3042 readonlyWhen strip) and
evaluates the stripped payload against each matched row's prior state; any
error-severity violation throws ValidationError (annotated with the failing
record id) before updateMany writes anything. Schemas needing no prior state
are evaluated once against the payload with no fetch; rule-free schemas
(messaging outboxes, settings rows) hit the evaluator's early return and are
unaffected.

Deliberate behavior change: bulk writes that previously slipped past
declared rules now throw.

Also corrects the two doc comments that overstated coverage
(rule-validator.ts, validation.zod.ts) — both now name the remaining
events:['delete'] gap, which is tracked separately — and refreshes the
AGENTS.md PD #10 example that described the pre-fix state.

Closes #3106

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

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 18, 2026 3:17am

Request Review

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

107 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/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.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/cli.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/validating-metadata.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)
  • 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 packages/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/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.

…c comment

The validation.zod.ts module doc comment feeds the generated
content/docs/references/data/validation.mdx; regenerate so `check:docs`
passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCUSMJBsX14C3RQdWFw7N7
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.

Validation rules, requiredWhen and option visibleWhen are silently skipped on multi-row updates (options.multi)

2 participants