fix(objectql): enforce validation rules, requiredWhen and option visibleWhen on multi-row updates#3160
Merged
Merged
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…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
os-zhuang
marked this pull request as ready for review
July 18, 2026 03:12
This was referenced Jul 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #3106.
evaluateValidationRules()is the single gate for object-level validation rules (script,state_machine,format,cross_field,json_schema,conditional), field-levelrequiredWhen, and per-optionvisibleWhenauthorization. It ran on insert and single-id update but was never called in the bulk branch ofengine.update(options.multi→driver.updateMany) — every one of those checks was a silent no-op there, with only a narrow warning that skippedformat/json_schema-only schemas entirely.Per the decision on the issue, this is the full enforcement option:
readonlyWhenbulk strip (needsPriorRecordsubsumeshasReadonlyWhenInPayload, so there is never a second query).ValidationError— annotated with the failing record id — beforeupdateManywrites anything (all-or-nothing, like the strip's locked-in-any-row rule).format/json_schema/ non-priorconditionalonly) are evaluated once against the payload with no fetch.multi: truecallers (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 remainingevents: ['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;ValidationErrorimported 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
engine.test.ts— new "Bulk update validation enforcement (Validation rules, requiredWhen and option visibleWhen are silently skipped on multi-row updates (options.multi) #3106)" describe: format rule rejects with no fetch / passes when valid;state_machineevaluated per row (one illegal row rejects the batch, message carries the record id); rule-free schema does no fetch (outbox regression guard);requiredWhenand optionvisibleWhenenforced per row; strip + validation share exactly onedriver.find.plugin.integration.test.ts— new describe mirroring the 安全:readonlyWhen 服务端剥离仅覆盖单条 update 路径,多行 updateMany 不强制(条件只读可被批量绕过) #3042 bulk harness through a real kernel: format reject (nothing written, no fetch), state_machine reject/accept pair, single shared fetch.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)
events: ['delete']are still never evaluated (engine.deletedoesn't call the evaluator) — named explicitly in both corrected doc comments, per the issue's "split out" decision.🤖 Generated with Claude Code
https://claude.ai/code/session_01VCUSMJBsX14C3RQdWFw7N7
Generated by Claude Code