docs: scope the reference-idempotence promise to what holds - #13
Merged
Conversation
The Semantics list contradicted itself in consecutive lines: one bullet said replaced values count as unchanged only when they are the very same reference, the next promised that re-applying any valid delta returns the previous result by reference, so a store could drop duplicate frames with one equality check. Only the first is unconditional. Websocket reducers are the headline use case and they decode every frame afresh, and a replacement is unchanged only under Object.is. Ordinary JSON parsing satisfies that for primitives but allocates a fresh object or array every time, so a frame carrying an unkeyed array, a non-plain object or an object-valued `replace` subtree yields an equal result with a new reference. A `replace` path holding a string or a number costs nothing, which is why the line falls at what decoding allocates rather than at replacement. The claim also lived as the justification inside the law that appears to prove it, and as a constraint in AGENTS.md, which is where a future writer would go to restate it. All three now say the same thing, and a law pins both sides of the boundary through an unkeyed array and a configured replace path, so neither route can start comparing by value unnoticed. No behavior change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bullet above the reference-idempotence one said replaced values count as unchanged only when they are the very same reference. That is the consequence, not the rule: equality is Object.is, so an equal primitive at a replace path is unchanged without any shared reference. Both bullets now rest on the same premise. Found while sweeping the rest of the file for claims stronger than the code delivers, which is #14. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Item 2 of the four flagged design choices. The verdict there was keep the behavior, fix the claim — so this is documentation, one test comment, and one law. No behavior change.
The defect
The Semantics list contradicted itself in consecutive bullets:
Only the first is unconditional. The second holds for the same delta object — but websocket reducers are the headline use case in the intro, and they decode every frame afresh.
Where the line actually falls
A replacement is unchanged only under
Object.is. Ordinary JSON parsing satisfies that for primitives and never for objects or arrays. Measured:replacepath → string / number / nullreplacepath → object / arrayAll are still
toEqual; only the reference churns.My first attempt drew the line at "carries a replacement," which review caught as wrong — a
replacepath holding a primitive costs nothing. The line falls at what decoding allocates.Changed
Three sites, because the claim lived in three places and the third is the one that would regenerate it:
Object.isrule, then the ordinary-JSON consequence, then the dedup guidance scoped to when it actually works.Plus a law,
a re-decoded frame keeps the reference only for Object.is-equal replacements, pinning three cases: merged values re-decoded aretoBe; an unkeyed array and a configuredreplacepath arenot.toBebuttoEqual; a primitive at areplacepath istoBe. Both replacement routes are covered so neither can start comparing by value unnoticed.Review
Two rounds with GPT-5.6-sol. Round one caught the "carries a replacement" overcorrection; round two grounded the rule in
Object.israther than allocation, added the object-valuedreplacecase, and replaced a cast of invalid data intoDelta<Shape>with a dedicated typed merger.118 tests,
vp run checkgreen.Summary by cubic
Scopes the reference-idempotence claim to what actually holds and pins the boundary with a law; no behavior change. Prevents overstating dedup-by-reference for re-decoded frames (e.g., websocket reducers).
Object.isas the equality rule for wholesale replacements, so primitive replacements are unchanged without shared refs; scopes idempotence to re-applying the same delta object and notes re-decoded frames keep refs only when all replacements areObject.is-equal (primitives, not objects/arrays). Dedup by a single equality check works only while no replacements are objects/arrays.toBeafter re-decode; unkeyed arrays and object-valuedreplacepaths arenot.toBebuttoEqual; primitivereplaceremainstoBe.Written for commit 1ff0ce5. Summary will update on new commits.