Summary
BaseValidationSchema admits events: ['insert', 'update', 'delete'] (packages/spec/src/data/validation.zod.ts:88), but the rule evaluator's runtime Mode is 'insert' | 'update' (packages/objectql/src/validation/rule-validator.ts:72) and engine.delete never invokes evaluateValidationRules. The events filter (rule-validator.ts:526) can therefore never match 'delete' — a validation rule declaring events: ['delete'] silently never runs.
This is the declared ≠ enforced tail split out of #3106 / PR #3160 (which fixed the multi-row-update call-site gap and left this one explicitly for a follow-up). It was also independently flagged by the June liveness audit.
Evidence
packages/spec/src/data/validation.zod.ts:88 — events: z.array(z.enum(['insert', 'update', 'delete'])).default(['insert', 'update']).
packages/objectql/src/validation/rule-validator.ts:72 — type Mode = 'insert' | 'update'; :526 — const events = r.events ?? ['insert', 'update']; return events.includes(mode); (mode is never 'delete').
docs/audits/2026-06-validationschema-property-liveness.md:10-21 — "🔴 events: ['delete'] is a silent no-op … Remove delete from the events enum (dead)."
- The published skill
skills/objectstack-data/rules/validation.md:268 actively teaches events: ['delete'] ("Only on delete"), so AI-authored metadata following the skill gets a delete guard that never fires.
Blast radius
Zero in practice. No example app, seed, template, fixture, or test declares a validation rule with 'delete' in events — the only repo-wide occurrences are documentation/skill text. Any hypothetical off-spec metadata fails loudly (ZodError at registry.validate / os validate), never silently.
Decision: trim (not enforce)
Remove 'delete' from the events enum (contract-first, AGENTS.md PD #12). Delete-time guards belong in beforeDelete lifecycle hooks, which the skills already teach correctly (skills/objectstack-data/references/data-hooks.md:448-464). Enforcement (wiring the evaluator into engine.delete) was considered and rejected: only script/cross_field would be meaningful on delete, and hooks already cover that need.
Scope
- Narrow the enum + its three hand-written mirrors (
validation.zod.ts:226 type, rule-validator.ts:79 BaseRule, packages/metadata-protocol/src/protocol.ts:231-235 JSON-schema mirror).
- Fix the skill line and the hand-written doc table (
content/docs/data-modeling/validation.mdx:72); regenerate content/docs/references/data/validation.mdx.
- Correct the doc comments that framed the delete gap as a known exclusion.
- Changeset + a negative test asserting
events: ['delete'] is now rejected.
🤖 Generated with Claude Code
Summary
BaseValidationSchemaadmitsevents: ['insert', 'update', 'delete'](packages/spec/src/data/validation.zod.ts:88), but the rule evaluator's runtimeModeis'insert' | 'update'(packages/objectql/src/validation/rule-validator.ts:72) andengine.deletenever invokesevaluateValidationRules. The events filter (rule-validator.ts:526) can therefore never match'delete'— a validation rule declaringevents: ['delete']silently never runs.This is the
declared ≠ enforcedtail split out of #3106 / PR #3160 (which fixed the multi-row-update call-site gap and left this one explicitly for a follow-up). It was also independently flagged by the June liveness audit.Evidence
packages/spec/src/data/validation.zod.ts:88—events: z.array(z.enum(['insert', 'update', 'delete'])).default(['insert', 'update']).packages/objectql/src/validation/rule-validator.ts:72—type Mode = 'insert' | 'update';:526—const events = r.events ?? ['insert', 'update']; return events.includes(mode);(modeis never'delete').docs/audits/2026-06-validationschema-property-liveness.md:10-21— "🔴events: ['delete']is a silent no-op … Removedeletefrom the events enum (dead)."skills/objectstack-data/rules/validation.md:268actively teachesevents: ['delete']("Only on delete"), so AI-authored metadata following the skill gets a delete guard that never fires.Blast radius
Zero in practice. No example app, seed, template, fixture, or test declares a validation rule with
'delete'inevents— the only repo-wide occurrences are documentation/skill text. Any hypothetical off-spec metadata fails loudly (ZodError atregistry.validate/os validate), never silently.Decision: trim (not enforce)
Remove
'delete'from theeventsenum (contract-first, AGENTS.md PD #12). Delete-time guards belong inbeforeDeletelifecycle hooks, which the skills already teach correctly (skills/objectstack-data/references/data-hooks.md:448-464). Enforcement (wiring the evaluator intoengine.delete) was considered and rejected: onlyscript/cross_fieldwould be meaningful on delete, and hooks already cover that need.Scope
validation.zod.ts:226type,rule-validator.ts:79BaseRule,packages/metadata-protocol/src/protocol.ts:231-235JSON-schema mirror).content/docs/data-modeling/validation.mdx:72); regeneratecontent/docs/references/data/validation.mdx.events: ['delete']is now rejected.🤖 Generated with Claude Code