|
| 1 | +--- |
| 2 | +'@objectstack/objectql': minor |
| 3 | +--- |
| 4 | + |
| 5 | +fix(objectql)!: a system write's readonly value is judged for its shape — a seed's `'yesterday'` on a readonly datetime is refused with the sentence any other field gets, never stored (#21663) |
| 6 | + |
| 7 | +**BREAKING** — a write that keeps a readonly value now has that value's SHAPE |
| 8 | +checked. The static readonly strip still exempts a system write (seed replay, |
| 9 | +migration, `isSystem` plugin code, a `before*` hook's stamp) and still drops a |
| 10 | +non-system caller's readonly value; what changed is that the value the |
| 11 | +exemption keeps is no longer stored unjudged. Before, the record validator |
| 12 | +skipped every readonly field, so under `isSystem` a malformed readonly value |
| 13 | +reached the driver verbatim — a seed's `run_at: 'yesterday'` on a readonly |
| 14 | +`datetime`, an unresolved `cel` envelope from a seeder that skips its |
| 15 | +resolution, an authored `created_at` the seed now keeps — while the same value |
| 16 | +on a non-readonly field was refused. |
| 17 | + |
| 18 | +Now it is refused the same way: `VALIDATION_FAILED` (400 at an HTTP boundary), |
| 19 | +the same field code and the same sentence a non-readonly field gets |
| 20 | +(`Run At must be a valid datetime (ISO-8601)`), and a seed counts the row as a |
| 21 | +seed error. This holds on insert, on the dry run (`ObjectQL.validate`), and on |
| 22 | +both update paths, where the readonly values left after the strip are judged. |
| 23 | + |
| 24 | +Which checks a readonly value reaches — its type's shape, never a constraint: |
| 25 | + |
| 26 | +- refused: a `date` / `datetime` / `time` the platform does not read, a |
| 27 | + non-number on a number-typed field, a non-boolean on a boolean, a non-array on |
| 28 | + a multi-value field, a filter-operator object, and an ADR-0104 reference / |
| 29 | + media / structured-JSON shape under the object's own posture (warn-first, as |
| 30 | + on any other field, until the deployment's evidence enforces it); |
| 31 | +- NOT checked, exactly as before: option membership, `maxLength` / |
| 32 | + `minLength`, `valueDomain`, `min` / `max` / `scale` / `precision`, the email / |
| 33 | + url / phone formats, and `required`. Option membership stays out on purpose: |
| 34 | + `sys_activity.type` is a readonly `select` whose options are the built-in set |
| 35 | + of an open vocabulary, and an author-contributed value there is stored. |
| 36 | + |
| 37 | +A numeric string on a readonly number field is now written as its number, and a |
| 38 | +lone scalar on a readonly multi-value field as a one-member list, as on any |
| 39 | +other field — the door reads the value the same way it judges it. |
| 40 | + |
| 41 | +**What moves for consumers.** A seed, migration or `isSystem` write that puts a |
| 42 | +malformed value in a readonly field — or a hook that stamps one — is refused |
| 43 | +where it was stored. Fix the value at its producer: write an ISO-8601 instant |
| 44 | +(or a `Date`) into a readonly `datetime`, resolve a `cel` value before the |
| 45 | +write, and stamp numbers and booleans as such. Rows already stored are never |
| 46 | +re-read or rewritten. `validateRecord`, as exported, is unchanged: the readonly |
| 47 | +scope is the engine write path's own. |
| 48 | + |
| 49 | +Clause-②: no (narrowing) |
| 50 | + |
| 51 | +<!-- adr-0087: not-required (no-migration-prescription) a write-time refusal of a malformed value in a readonly field, judged by the same per-type shape checks a non-readonly field already gets. No authorable key, spelling, export or stored shape moves: the field schema is unchanged, the published validateRecord signature is unchanged, no stored row is read or rewritten, and which value a producer meant to write is not something a ledger entry can rewrite. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers this behaviour (not already-registered); and the change is a write-path verdict, not a declaration (not runtime-interface-only or type-surface-only). --> |
0 commit comments