Observation-class finding, recorded while implementing #4633 (PR #6532), so the trade it makes is on the record rather than discovered later.
What changed
Before PR #6532 the import runner's required pre-check was gated on !runAutomations, with this reason:
Skipped when automations run: a beforeInsert hook may populate a required field, so we defer to the engine's own validation rather than false-reject here.
The gate was not applied to the bound checks #3956 added — those ran on every dry run regardless — so the two families were already asymmetric.
PR #6532 routes the whole dry run through DataProtocol.validateData and applies no runAutomations gate. validateData runs no hooks (a documented, deliberate boundary of #6037: firing user-authored hooks inside a preview is the retired validateOnly defect respelled). So on an object whose beforeInsert hook derives a required business field, a dry run with automations on can now report required for a row the real write would create.
Why it was done this way
runAutomations has defaulted to true since #2922. Gating the validateData call on !runAutomations would leave the DEFAULT dry run with no validation at all — strictly worse than both the pre-#6532 state and the state #4633 set out to fix. The trade taken was: close the false all-clear on every dry run, and accept the narrow hook-derived false alarm that #6037 already documents as its boundary.
Scope of the exposure
Narrow, and worth measuring before acting:
- audit and ownership stamps (
created_by, owner_id, organization_id, …) are system/readonly and are skipped by validation anyway, so they cannot produce this;
- what remains is a hook deriving a business field that its own object also declares
required with no defaultValue.
No example of that shape was found in examples/app-crm or examples/app-showcase while implementing #6532, but the search was not exhaustive.
Possible dispositions
Recording, not recommending — the measurement above is what a triage round needs first.
Observation-class finding, recorded while implementing #4633 (PR #6532), so the trade it makes is on the record rather than discovered later.
What changed
Before PR #6532 the import runner's required pre-check was gated on
!runAutomations, with this reason:The gate was not applied to the bound checks #3956 added — those ran on every dry run regardless — so the two families were already asymmetric.
PR #6532 routes the whole dry run through
DataProtocol.validateDataand applies norunAutomationsgate.validateDataruns no hooks (a documented, deliberate boundary of #6037: firing user-authored hooks inside a preview is the retiredvalidateOnlydefect respelled). So on an object whosebeforeInserthook derives a required business field, a dry run with automations on can now reportrequiredfor a row the real write would create.Why it was done this way
runAutomationshas defaulted totruesince #2922. Gating thevalidateDatacall on!runAutomationswould leave the DEFAULT dry run with no validation at all — strictly worse than both the pre-#6532 state and the state #4633 set out to fix. The trade taken was: close the false all-clear on every dry run, and accept the narrow hook-derived false alarm that #6037 already documents as its boundary.Scope of the exposure
Narrow, and worth measuring before acting:
created_by,owner_id,organization_id, …) aresystem/readonlyand are skipped by validation anyway, so they cannot produce this;requiredwith nodefaultValue.No example of that shape was found in
examples/app-crmorexamples/app-showcasewhile implementing #6532, but the search was not exhaustive.Possible dispositions
runAutomationsis true — the row report already has awarnings[]channel as of PR fix(rest,objectql): the import dry run asks the engine for its verdict instead of predicting it (#4633) #6532, so the shape exists. Costs: two verdict classes for one finding, which is the vocabulary split [spec] DataProtocol 新增 validate-only 数据校验操作 —— #4633 裁 D 的契约半边(spec 先行) #6037 explicitly avoided for postures.validateDataa way to run a declared-safe subset of derivations. Large, and needs its own decision about what "safe" means.Recording, not recommending — the measurement above is what a triage round needs first.