v5.2.7
Data integrity
-
Resolved
@computedand@relationshipvalues were written into durable records, and the affected rows then became unreadable and undeletable (#2368). A record resolved from a cache source carries the record prototype, whose response projection (toJSON) surfaces scalar@computedvalues — and@enumerablerelationships since 5.1.0. msgpackr consults an instance'stoJSONwhen encoding it, so the durable encode ran the response projection and wrote the resolved values as stored fields. Materializing such a record then assigns the stored value back through the resolver accessor, with two distinct outcomes:- a computed attribute has no setter, so every read, query, invalidate and delete on that record threw
attribute.set is not a function— reachable in 5.2.0 through 5.2.6; - a relationship setter dereferences the stale value, so a dangling foreign key crashed the same way (since 5.1.0), and a scalar collision silently destroyed the foreign key.
The write side now enforces the invariant at the layers that own it.
recordUpdaterprojects the record — and the audit entry's own record, gated so message and publish payloads stay verbatim — to its stored fields before anything durable is written, dropping any name a resolver owns whether it arrived through the response projection, from a source, or in a peer payload. A source that returns a related object instead of its foreign key still has the key derived through the writable resolver's setter before the name is dropped.structonis pinned to 1.1.0, which writes own properties only, matching msgpackr's object writers and closing the prototype-walk route for every struct encode.The read side makes the records that affected releases already wrote recoverable: the four paths that promote a plain decode to a record instance now skip resolver-owned names instead of assigning them through the accessors, and the accessor itself drops such an assignment (warning once per table) rather than calling an absent setter. A schema reload clears
attribute.setalongsideattribute.resolve, so changing an attribute from@relationshipto@computedcannot retain a stale setter. If you are on 5.1.x–5.2.6 and have hitattribute.set is not a function, upgrading is what makes those rows readable and deletable again. - a computed attribute has no setter, so every read, query, invalidate and delete on that record threw
-
A prototype-chain walk could copy inherited properties into a durable record (#2368).
assignStoredFieldsusedfor..in, so an inherited enumerable — including anything on a pollutedObject.prototype— could reach a materialized and then durable record on exactly the paths that its ownObject.assignfallback andstoredFieldsOnlyalready restrict to own properties. It now reads own keys only.
Schemas
@computed(from:)expressions failed to compile in an inline-loaded schema (#2360, landed via #2368).vm.Scriptrequires a string filename and inline schemas were loaded with a null one, so any computed expression in such a schema threw. Reported and fixed by @kylebernhardy.
Behavior changes worth knowing on upgrade
- Assigning to a
@computedattribute now throws a client error naming the attribute, instead of aTypeErrorfrom the missing setter. - Harper does not set msgpackr 2.1.0's
useToJSONopt-out. That is deliberate: an encoder-wide opt-out would also silence the legitimatetoJSONof nested values, so the projection is enforced at the layers that write durable records rather than in the encoder.
Also in this release
- CI: the bot caller workflows are synced with
main, and the always-on review arm no longer cancelsready_for_reviewruns (#2331, #2358).resources/DESIGN.mdrecords the durable-vs-response projection convention behind the fix above.
Full Changelog: v5.2.6...v5.2.7