Repository navigation
Patch add operations on BigInt attributes add instead of overwriting, and object sub-types validate the sub-object - #3577
Merged
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request updates the validation logic in Table.ts to ensure that top-level patch operations (such as { __op__: 'add', value }) are preserved during validation rather than being replaced by their coerced scalar values, while still coercing their operands in place. It also refactors object sub-type validation to correctly handle computed properties and pending changes, and adds comprehensive unit tests to verify these behaviors. Feedback on the changes suggests using object spread when copying a frozen patch operation envelope to preserve any existing custom properties or metadata.
…date sub-objects on the sub-object
Table.validate replaced a patch's `{ __op__: 'add', value }` envelope with its
coerced operand, so a BigInt counter patched with a number or string delta was
overwritten instead of incremented. The operand is now validated and coerced
inside the envelope, add operations are limited to numeric properties with a
non-null operand, and a frozen envelope is replaced rather than mutated.
Also: coerced falsy values (0n) are written back in sub-objects and arrays;
computed/relationship checks of a sub-type read the sub-object (Object.hasOwn)
instead of the outer record; an array or primitive for an object sub-type is
one type error and no longer falls through into the child checks.
Dispatch-Task: harper-validate-patch-envelope-bigint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ones, with numeric operands The commit applies `__op__` envelopes on top-level properties only, so one nested in a sub-object or array element is now a type error instead of being stored literally. Unknown operation names and non-numeric operands on untyped or Any properties are rejected before the commit. A computed property assigned on a tracked sub-object (held in its pending changes) is rejected too. Dispatch-Task: harper-validate-patch-envelope-bigint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…etChanges only when callable Dispatch-Task: harper-validate-patch-envelope-bigint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dispatch-Task: harper-validate-patch-envelope-bigint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dispatch-Task: harper-validate-patch-envelope-bigint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…der untyped fields are data Dispatch-Task: harper-validate-patch-envelope-bigint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…neighbour to meet the line budget Dispatch-Task: pr-maint-25376f6ce7c0222ac51338fa18370905 Co-Authored-By: Gemini <noreply@google.com>
kriszyp
force-pushed
the
fix/validate-patch-envelope-coercion
branch
from
October 27, 2026 00:21
9056b77 to
685fbc9
Compare
kriszyp
marked this pull request as ready for review
October 27, 2026 14:44
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.
⊙ Problem
A patch like
{ balance: { __op__: 'add', value: 25 } }on aBigIntattribute overwrites the stored value with25ninstead of adding to it.Table.validatewrote the coerced operand over the whole add operation. Object sub-type validation had three more bugs:0nwas dropped;TypeError.🤔 Is the requirement right?
Yes. The task asked that a null add on an object sub-type "not throw". It now fails with a 400
ValidationError, not aTypeError: an add on an object has no meaning.💡 Solution
BigInt). Anything else is a 400 before commit.0ncoercions are kept (sub-objects, arrays). Computed checks read the sub-object, and an array or primitive for an object sub-type is onetypeerror.🤔 Unmeasured probe on untyped PATCH fields
A plain object value (not an operation) on an untyped or
Anyattribute now costs one__op__read per PATCH. Typed primitives no longer read it. Not benchmarked: the shared bench lock was busy all session.⚖️ Alternatives
crdt.addValues) — an add onto an absent field would store a number in aBigIntcolumn.🤔 Operations are checked only at the top level
Planning review (
Framing-Verdict: better-alternative-exists) suggested checking envelopes in the recursivevalidateValue. Adopted, then narrowed to the top-level loop: the commit applies__op__only there, so a nested envelope is now a type error.🔧 Changes
resources/Table.ts— the top-level operation check, sub-type traversal fixes,!== undefinedwrite-backs,OPERATION_TYPES, and thecrdt.operationsregistry import.resources/DESIGN.md,DESIGN.md— the invariant and its index line.unitTests/resources/validation.test.js— 8 new cases (BigInt add, rejected operations, sub-type computed checks).✅ Verification
validation.test.json RocksDB and LMDB: 11/11 pass (5 new cases fail against baseTable.ts).test:unit:resources: 4905 passing.subscribeReadGateCallerImpact(fixed on main in8bf7d1e06) andsubscribeExpiredCurrentpass alone.test:unit:maintimed out on a saturated box after 1158 passing, 0 failing; CI covers it andtest:integration:all.PR QA: Do concurrent REST PATCH add operations on a BigInt attribute sum exactly across HTTP workers? — queued @ 9056b77, qa-pr-harper-3577-9056b77
Docs: not needed — numeric
__op__patches already add per the documented CRDT semantics; this stops a wrong overwrite.🤖 Generated with Claude Code — Claude Opus 5.5
Release target: main — a validation bug fix; no customer need for a backport is known
Related PRs: #3430 overlaps, 76 others independent
Complexity: complicated
Deferred findings
b96bf5be08b4228eminor correctness —resources/Table.ts— Untyped/Any add with a finite fractional operand onto a stored bigint throws RangeError from BigInt() at commit — filed Patch add with a fractional operand onto a bigint in an untyped attribute fails at commit with a 500 #356804b5b64a15c43b6eminor correctness —resources/Table.ts— Fractional or non-integer-string operand/value on a BigInt attribute throws raw RangeError/SyntaxError (500) instead of ValidationError — NOT FILEDb3e9df0ae7f12605nit correctness —resources/Table.ts— Coerced write-back into a caller-frozen sub-object or array throws TypeError (now also for 0n) — filed Table.validate coercion throws a TypeError on a caller-frozen sub-object or array #35737b87e54a2676d57fnit correctness —resources/Table.ts— Top-level computed-property check uses only own properties, unlike the new sub-object getChanges check — NOT FILEDOrigin — the dispatch brief this PR was written from
Table.validate / validateValue (resources/Table.ts, called from _writeUpdate) has four defects: (A) for a patch op envelope ({op, value}) the coerced scalar is written back over the envelope (BigInt counters lose the add), and a null-op on a nested sub-type dereferences null; (B)
if (updated)truthiness writebacks drop 0n; (C) the nested computed/relationship check uses record.hasOwnProperty; (D) arrays are accepted for object sub-types and addError falls through into the child loop. When patch is true and the value is an op envelope, coerce .value in place and return undefined (skip further validation if the unwrapped value is null); reject arrays for object sub-types and return instead of iterating; use Object.hasOwn(value, name); change both writebacks to !== undefined. Related only: #1372.Acceptance
Tests on rocksdb and lmdb (the QA-1949 suite shape): BigInt addTo/subtractFrom patch on a BigInt attribute applies the delta; 0n persists; array for an object sub-type is rejected with one error; null-op on nested sub-type does not throw; Int-delta patches unchanged. Show each new test failing on origin/main sources (forced rebuild) before the fix.
Dispatch: task
harper-validate-patch-envelope-bigint· queued by finding-triage/wave-40 · ran by claude/opus/high · worker kzyp-xps-1Review-Coverage: authored=gemini; ran=cursor-composer,codex,cursor-grok,claude; late=cursor-grok:adjudicated; adjudicated=domain; declined=muse; rounds=8; full=2 @ 685fbc9
Review-Attention: study ~20m (critical: Table.ts; decisions: budget-via-reflow, reject-string-operands-untyped, top-level-only-operations, operand-validity-split, strict-object-subtype-arrays) @ 685fbc9