Skip to content

Commit 912496d

Browse files
os-zhuangclaude
andauthored
fix(types,core): the *Validation five derive from spec 17, and the engine stops disagreeing with the server (#3103) (#3107)
Five rule types in `data-protocol.ts` were hand-written interfaces labelled `(ObjectStack Spec v2.0.1)` while the installed spec was 17.0.0-rc.0. Nothing bound them to the spec, so fifteen majors of drift accumulated with `tsc` silent and the comment still vouching for it — the planted-premise failure class objectstack#4115 was filed for. They are now `z.input` derivations of the spec's schemas. `z.input`, not `z.infer`: objectui consumes AUTHORED metadata off `/meta`, before the spec's defaults and ExpressionInput canonicalization run, so `condition` really is `string | {dialect, source}` and `active`/`events`/`priority` really can be absent. What the copies had drifted into, and what each cost the user — the engine is a client PRE-CHECK of rules `objectql/src/validation/rule-validator.ts` enforces, so every one of these is a disagreement with the authority: - Predicate POLARITY was inverted. The server violates a rule when the predicate is TRUE; the engine violated it when FALSE. Every spec-authored `script`/`cross_field` rule got the opposite verdict. - `conditional` read `condition` + `rules[]` where the spec says `when` + `then`/`otherwise` → silent no-op, and `otherwise` never ran at all. - `format` read `pattern` where the spec says `regex` → `undefined.test()` threw into a catch that reported a VIOLATION, blocking writes the server accepts. - Envelope-valued conditions hit `expression.trim()`, threw, and read as "passes" → silent no-op. - An absent `active` disabled the rule; an absent `events` threw. Both arrive absent because the spec defaults them at parse time. - `initialStates` (objectstack#3165) and `priority` were missing entirely; `events: 'delete'` was still advertised after objectstack#3184 retired it. `unique`/`async`/`range` have no spec counterpart — the spec removed the first two deliberately (uniqueness → a unique index, since SELECT-then-INSERT is racy; async → the form layer) — and are now @deprecated, with a test proving the spec's union rejects them, so the comment is load-bearing rather than decorative. Gates, each mutation-tested: the compile-time pins in `validation-rule-spec-parity.test.ts` (live because tsconfig.test.json compiles it, #3009) go TS1360 when a derivation is reverted, and the engine suite goes red for polarity, envelope coercion, `otherwise`, `initialStates` and the spec defaults. `ConditionalValidation.then/otherwise` carry one pinned divergence — the spec's published types erase them to `unknown` (objectstack#4171) — with an inverted pin that fails the day upstream fixes it. Canonicity now rides on the import and the gate, not the comment: `grep "Spec v2.0.1" packages/types/src/data-protocol.ts` is empty, and objectstack#4115's ledger drops 120 -> 115. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent de2e748 commit 912496d

9 files changed

Lines changed: 1594 additions & 712 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
"@object-ui/types": minor
3+
"@object-ui/core": minor
4+
---
5+
6+
feat(types,core): the `*Validation` rule types derive from spec 17, and the engine agrees with the server — objectstack#4115
7+
8+
The five spec-named rule variants in `data-protocol.ts` were hand-written
9+
interfaces, each labelled `(ObjectStack Spec v2.0.1)` while the installed spec
10+
was `17.0.0-rc.0`. Nothing bound them to the spec, so fifteen majors of drift
11+
accumulated with `tsc` silent throughout and the comment still vouching for it.
12+
They are now `z.input` derivations of `ScriptValidationSchema` /
13+
`StateMachineValidationSchema` / `CrossFieldValidationSchema` /
14+
`ConditionalValidationSchema` / `FormatValidationSchema`, and canonicity is
15+
carried by that binding plus a parity gate rather than by a comment (#3017).
16+
17+
`z.input`, not `z.infer`, because objectui consumes **authored** metadata as it
18+
arrives over `/meta` — before the spec applies its defaults and canonicalizes
19+
expressions. That is the shape actually in the JSON.
20+
21+
**Breaking, in the shape of the rule types** (minor per this repo's version
22+
policy — see AGENTS.md §9):
23+
24+
| | was | is |
25+
|---|---|---|
26+
| `ConditionalValidation` | `condition` + `rules[]` | `when` + `then` / `otherwise` |
27+
| `FormatValidation` | `pattern` + `flags`, 8 named formats | `regex`, the 4 formats the server implements |
28+
| `Script`/`CrossField` `condition` | `string` | `string \| { dialect, source }` |
29+
| `StateMachineValidation` || gains `initialStates` (objectstack#3165) |
30+
| `BaseValidation` | no `priority`, `events` included `'delete'` | gains `priority`; `'delete'` retired (objectstack#3184) |
31+
32+
`UniquenessValidation` / `AsyncValidation` / `RangeValidation` are now
33+
`@deprecated`. They have no spec counterpart — the spec removed the first two
34+
deliberately (uniqueness → a unique index, since SELECT-then-INSERT is racy;
35+
async → the form layer) — and the spec's `ValidationRuleSchema` rejects all
36+
three, so no rule in those shapes can ride in `ObjectSchema.validations`.
37+
38+
**`ObjectValidationEngine` now agrees with `objectql`'s rule-validator.** It is a
39+
client PRE-CHECK of rules the server enforces, so every disagreement cost the
40+
user something real. Fixed:
41+
42+
- **Polarity was inverted.** The server violates a rule when the predicate is
43+
TRUE; the engine violated it when the predicate was FALSE. Every
44+
spec-authored `script` / `cross_field` rule produced the opposite verdict.
45+
- **Envelope conditions were a silent no-op.** `{ dialect, source }` reached
46+
`expression.trim()`, threw, was caught, and read as "passes".
47+
- **`conditional` was a silent no-op**, reading `rule.condition` / `rule.rules`
48+
where the spec says `when` / `then`; `otherwise` was never evaluated at all.
49+
- **`format` produced FALSE REJECTIONS** — it read `rule.pattern`, and
50+
`undefined.test(...)` threw into a catch that reported a violation, blocking
51+
writes the server accepts.
52+
- **An absent `active` disabled the rule** and an absent `events` threw; both
53+
arrive absent from `/meta` because the spec defaults them at parse time.
54+
- `priority` now orders execution; `initialStates` is enforced on insert;
55+
`format`/`state_machine` only fire when the write touches the field; a broken
56+
predicate or an uncompilable `regex` fails OPEN with a warning; and a rule type
57+
the engine cannot evaluate (the spec's `json_schema`) warns instead of
58+
reporting the record as valid.
59+
60+
The default `SimpleExpressionEvaluator` is not CEL and never was; it now binds
61+
both the spec's `record.x` scope and objectui's historical bare `x`, and
62+
documents that richer predicates need a CEL-backed evaluator. `validateRecord`'s
63+
`event` parameter no longer accepts `'delete'`.
64+
65+
Gates: `packages/types/src/__tests__/validation-rule-spec-parity.test.ts` (key
66+
sets, wire shapes, the pinned `then`/`otherwise` divergence with an inverted pin
67+
that fails when objectstack#4171 is fixed upstream) and the rewritten engine
68+
suite. objectstack#4115's ledger drops 120 → 115.

0 commit comments

Comments
 (0)