Repository navigation
Fix Date chaining and preserve validation across table re-declarations - #3594
Conversation
There was a problem hiding this comment.
Code Review
This pull request enhances schema evolution and re-declaration handling in Harper. It ensures that when a table schema is re-declared, stale accessors from the previous declaration (such as relationships, computed properties, and timestamps) are retired and deleted from the prototype to prevent them from shadowing newly declared fields. Additionally, it refactors types.date to support chaining modifiers like .indexed, .nullable, .createdTime, and .updatedTime in any order, and updates timestamp assignment during transactions to use the attribute object directly to avoid race conditions during schema reloads. Comprehensive unit, integration, and type tests have been added to validate these changes. There are no review comments, and I have no additional feedback to provide.
a0b0dc9 to
d9ad0c5
Compare
91efa6a to
17832a3
Compare
…d attribute accessors on schema re-declaration types.date.indexed.createdTime (and .nullable/.primaryKey before a timestamp, or .createdTime.updatedTime) evaluated to undefined, so defineTable crashed: the flag getters rebuilt through a builder without the Date timestamp getters. makeField now owns each kind's full flag surface, so every getter rebuilds a complete field; DateField types every flag as a DateField. Re-declaring a table (defineTable, a GraphQL reload, drop_attribute) left the previous declaration's accessors installed. A removed relationship or @computed kept resolving on decoded records and shadowed a now-stored field of the same name; a removed typed attribute's setter kept rejecting writes; hasRelationships never went back to false. updatedAttributes() now deletes the previous resolvedAttributeNamesList names from the struct prototype and resets hasRelationships, and assignTrackedAccessors deletes own isAttribute accessors the new attribute set no longer declares. Adds the QA-1951 regression suite (6 of 7 fail on main, both engines) plus chain-order, typed-struct round-trip, computed-to-stored, retained-relationship and permission coverage, and type tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: harper-definetable-date-modifiers-accessors Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
…stamp fixture, sharper tests - assignTrackedAccessors keys its descriptor map on a null prototype, so an attribute named __proto__ cannot re-parent it and hide removed accessors from the cleanup. - The type fixture puts date.createdTime.indexed.updatedTime on its own table: a table keeps one created and one updated slot, so it would have shadowed the fixture's other timestamps. - The typed-struct reload test also reads a record written before the reload and checks the struct prototype descriptor; the retained-relationship test checks Updatable.prototype directly. - Trimmed comments that restated code; shortened the DESIGN.md note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: harper-definetable-date-modifiers-accessors Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
…'s narration Updatable is never instantiated (update() returns the table instance), so the QA arm-4 titles and the registration test's prototype helper were checking the table prototype under another name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: harper-definetable-date-modifiers-accessors Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
The slots were resolved once when the table class was built, so a re-declaration that added a @createdTime/@updatedTime field left it unstamped until restart, and one that removed the field kept writing it. updatedAttributes() now rebinds both beside primaryKeyAttribute and refreshes the static copies deleteRecordsBefore reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: harper-definetable-date-modifiers-accessors Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: harper-definetable-date-modifiers-accessors Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
…de against Now that a schema reload rebinds createdTimeProperty, a fill that decided to stamp before a reload and committed after it read the new (possibly undefined) slot. The fill now carries the attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: harper-definetable-date-modifiers-accessors Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
…eping resources/DESIGN.md's line count resources/DESIGN.md is already over its 1000-line budget on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: harper-definetable-date-modifiers-accessors Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: late-review-kriszyp_harper_3594-cc66360be478-7c917eff Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
…ments for the next reader Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: late-review-kriszyp_harper_3594-d9ad0c563568-92aad3f5 Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Validate own fields before tracked writes can be skipped, without revalidating untouched legacy data. Compile expressions before mutating live derived state, stage reload bindings, and overwrite retained resolver getters while retiring only absent names. Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Co-Authored-By: GPT-5 Codex <noreply@openai.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
…ure comment at #3584 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: late-review-kriszyp_harper_3594-17832a3ba55a-0fe469dd
A tracked update that also carried an own instance field validated and stamped a copy, so a put-only source got the record without this write's updated time, and copying a value back to a non-writable own field threw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: late-review-kriszyp_harper_3594-17832a3ba55a-0fe469dd
…of storing the raw value Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: late-review-kriszyp_harper_3594-17832a3ba55a-0fe469dd
…ot read as a conversion Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Dispatch-Task: late-review-kriszyp_harper_3594-17832a3ba55a-0fe469dd
Co-Authored-By: Gemini <noreply@google.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Skip applyOwnChanges when collapseChanges returns instanceChanges unchanged, keeping the default tracked write path free of an extra Object.keys(this) allocation. Move the updatedAttributes re-declaration note to Conventions and update the collapseChanges design note for loadAsInstance !== false and applyOwnChanges write-back. Co-Authored-By: Gemini <noreply@google.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
Pass the already-enumerated ownKeys array from Table._writeUpdate into collapseChanges and applyOwnChanges, and gate applyOwnChanges on ownKeys.length > 0 so reading or updating nested objects on an instance without own properties never calls Object.keys(this) a second time. Also update the Schema re-declaration bullet in resources/DESIGN.md to note that every computed expression is precompiled and link harper#3584. Co-Authored-By: Gemini <noreply@google.com> Dispatch-Task: pr-maint-0931d2030635b6e0ba4bcb08171b3e4e
0af466f to
c914e37
Compare
… isolation (#3808) * Add integration anchors for sealed re-declaration and local-principal isolation Promotes two security-lane regression specs. sealed-redeclare-tracked-update (#3594): a sealed table refuses an undeclared own property assigned on a tracked Table.update() instance, across a two-worker re-declaration. Rejected arms now assert detail[0].code === 'unknown_property' and an unchanged updatedAt. local-principal-isolation (#3755): nothing from a non-loopback source becomes the `local` principal. The listener address is HARPER_TEST_NON_LOOPBACK_ADDRESS or the first external IPv4 interface (suite skipped when there is none), ports are allocated per run, the scoped-token mint must succeed, the dead `name === 'local'` clause is gone, and a new check covers a principal named `local` without the _localPrincipal marker. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Dispatch-Task: harper-promote-sealed-update-and-local-principal-anchors * Tighten the promoted anchors after review Bound network waits, fail the sealed setup when a worker does not converge, keep tokens and cookies out of the logs, use a plain role for the look-alike users so a local-principal substitution cannot hide behind super_user, and drop the dead MQTT poll. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Dispatch-Task: harper-promote-sealed-update-and-local-principal-anchors * Let held sealed-update transactions outlive the default open-time limit The gate budget is 60 s; the HTTP idle timeout and the 30 s default maxTransactionOpenTime sat inside it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Dispatch-Task: harper-promote-sealed-update-and-local-principal-anchors * Settle held requests on setup failure and dropped responses Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Dispatch-Task: harper-promote-sealed-update-and-local-principal-anchors * Wait for the stored session rows in the local-principal suite Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Dispatch-Task: harper-promote-sealed-update-and-local-principal-anchors --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Fix Date chaining, retire obsolete schema accessors, and preserve write validation and live bindings across reloads.
⊙ Problem
Date chains lost timestamp getters. Re-declaration retained stale accessors and timestamp slots, exposed sealed-write bypasses, and cleared live bindings when expression compilation failed.
🤔 Bounded reload contract
Accessor retirement must preserve sealed-write admission and live bindings after a rejected expression reload; durable catalog rollback remains a follow-up.
💡 Solution
Keep Date builders complete, reconcile generated accessors with the current schema, include own fields in the validated local mutation delta, and prepare expression resolvers and derived bindings before publishing them.
⚖️ Alternatives
🤔 Validate edits rather than the merged record
Merged-record validation rejects untouched legacy data. Combine tracked and own-field edits into one validated delta when
loadAsInstance !== false, preserving unsealed own-only edits; non-object incremental roots (null, primitives, arrays, binary) fail root validation with 400 rather than being silently dropped.🤔 Failed reload scope
Precompile expressions before touching live bindings; catalog rollback (#3781) and generated resolver ownership (#3782) remain deferred. Overwrite retained getters and delete absent names.
🤔 Reload and per-write cost
Deleting retired accessors leaves prototypes in V8 fast mode (
%HasFastProperties).Object.keys(this)runs once perloadAsInstance !== falsetracked update (tracked in #3785); Date descriptors are built once per declaration.🤔 Edge inputs kept as on main
A caller-sealed patch plus an own field throws, as at the updated-time stamp. An own field defined with
Object.definePropertyunder a timestamp name still overrides the stamp. A source-fill stub reads the live created-time attribute (#3616).🤔 Existing Date policies
Date policies (#3584, #3713), builder serialization (#3784) and fixture cleanup (#3596) stay separate.
🔧 Changes
resources/defineTable.ts: complete Date construction and fluent DateField types preserve every chain.resources/Table.ts: own-field mutation capture, root admission before delta construction, expression preparation, derived binding publication, absent-only retirement, and source-fill created-time capture. Look hardest at validation before no-change skipping and compilation before live mutation.resources/tracked.ts: null-prototype descriptor map and retirement of obsolete generated accessors preserve application-defined members.resources/DESIGN.md: enforced reload and validation contract.Validation order
Own-key enumeration avoids the inherited schema walk. Own fields merge into the change set itself, so coercion and timestamp stamps reach the instance and a put-only source; a read-only own field that needs converting rejects the write. Both initialized and lazy updates require capture.
Table.ts:5621
Product and architecture tour
Sealed tracked writes
Which edits does the commit validator inspect?
Expression preparation and live publication
What remains usable when a raw expression is invalid?
Prepare before publishing
Compilation errors leave the previous live derived bindings and generated getters available.
flowchart TD A[Raw table declaration] --> B[Compile all expressions] B -->|Syntax error| C[Throw with old live bindings] B -->|Success| D[Assemble next resolver map and slots] D --> E[Publish derived bindings] E --> F[Overwrite retained getters] F --> G[Delete retired names]Complete Date builders
What can a component declare after another modifier?
Example: Indexed creation timestamp
createdAt: types.date.indexed.createdTimeretains both index and server-managed metadata. Each modifier returns a fresh complete Date builder.Outcome: The table registers and stamps a Date; changing one chain leaves its source unchanged.
✅ Verification
17832a3,01f458eand8c92da6.Full-gate limits and isolated checks
llama3.2alias (404), whilellama3.2:3bexists (200); no backend code changed.17832a3and this head; 33 attributes, 16 rows, audit off, 500 warmup and 2,500 timed writes per mode. Lazy/initialized/own-field median times were 102.2→112.0µs (+9.6%), 161.4→172.4µs (+6.8%) and 167.5→163.6µs (−2.4%), with overlapping ranges. Separate 1,500-write heap samples changed approximately +2.3%, +1.3%, +1.0%. The modest measured cost is accepted for matching the validated and persisted delta; narrowing to empty changes would reintroduce the initialized-update bypass. This synthetic single-process RocksDB check excludes native allocation and production concurrency.test:unit:main:core,:components,:server, three resource shards and all integration matrix shards cover the full gates in separate processes.PR QA: Do tracked handles enforce sealed-field retirement across HTTP-worker reloads? — passed @ 2e73efb, qa-pr-harper-3594-2e73efb
All 14 arms and a cold rerun passed on two HTTP workers; pre-fix controls failed. Later commit 002b0cc only condenses DESIGN notes.
Docs: none needed — these repairs enforce existing schema and sealed-table contracts.
🤖 Generated by Claude (Opus), maintained by OpenAI Codex; posted via @kriszyp.
Release target: v5.4 — older release lines only on demonstrated customer need
Related PRs: #3586 overlaps, #3610 overlaps, #3653 overlaps, #3655 overlaps, 3 others independent
Framing-Verdict: chosen-approach-sound
Complexity: complicated
Tracked follow-ups
Deferred findings
f3c239815d68ff76minor data-integrity —resources/databases.ts— Failed schema binding leaves catalog and version ahead of live bindings — filed Reject raw table declarations atomically when computed binding fails #3781bf10b0cc9768fd8aminor correctness —resources/defineTable.ts— Recursive enumerable field modifiers overflow serialization — filed Define finite serialization for fluent schema field builders #37848dece860c91d5dcbminor correctness —resources/Table.ts— Reload retains obsolete schema-generated computed resolvers — filed Retire schema-generated computed resolvers when an expression is removed #378211c203b4f8d371efminor correctness —resources/Table.ts— Date primary key that is also a timestamp: getNewId returns a number and the primary-key correction overwrites the Date stamp — filed A Date primary key marked createdTime gets a numeric id and no created time #371325e65aa81a684ee4minor correctness —resources/defineTable.ts— date.createdTime.updatedTime binds both timestamp slots to one field; created-time retention overwrites the updated stamp so it never advanc — filed A field with both timestamp flags never updates, and a table stamps only one created and one updated field #3584ce464ff0a127b5banit performance —resources/tracked.ts— retire loop scans every own prototype key per reload — filed Remove the never-instantiated Updatable class from makeTable #35859d62bb87ee1920c1nit performance —resources/tracked.ts— collapseChanges enumerates Object.keys(target) on every loadAsInstance!==false tracked write — NOT FILED6c66820b53f50fb5nit documentation —resources/DESIGN.md— document dual-timestamp stamping next to the source-fill note once A field with both timestamp flags never updates, and a table stamps only one created and one updated field #3584 lands — NOT FILEDb5cd68d7e588d613nit performance —resources/Table.ts— collapseChanges enumerates Object.keys(target) on every loadAsInstance!==false tracked write — NOT FILED7413c79f06cb1466nit data-integrity —resources/Table.ts— record-root shape check skipped for context.source writes — NOT FILEDLate Grok findings
4 surviving (highest minor) — Grok reviewed cc66360, ruled against 886b844
resources/defineTable.ts:96—date.createdTime.updatedTimesets both flags on one attribute, but a table has one created-time slot and one updated-time slot (resources/Table.ts:1538-1539). On every write after the first, the updated-time stamp atresources/Table.ts:5130is overwritten with the original value at `resources/unitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:63— added comments still narrate the steps or restate test names (qa-1951…test.js:1-2, 63, 198, 262, 308, 318, 366);resources/Table.ts:9044repeats the note atresources/DESIGN.md:289resources/DESIGN.md:289— the note names its enforcing test, so under the rule atDESIGN.md:1it should shrink to one line naming that enforcement. It is still three sentences and links no issue or PRresources/tracked.ts:225— the retire loop scans every own prototype key on each schema declaration. This runs only at declaration time (resources/Table.ts:9037-9038), not per request, so the cost does not matterNot standing: 1 merged, 3 resolved. Grok ran 20m (20m from launch to report); the fold adjudication (
cursor-grok@cc66360be478-7c917eff, claude) took 2m.Origin — the dispatch brief this PR was written from
defineTable (resources/defineTable.ts) Date modifier getters (.indexed, .nullable, .primaryKey) return plain makeField, dropping .createdTime/.updatedTime when chained first (types.date.indexed.createdTime crashes). Also on schema evolution: updatedAttributes() never resets hasRelationships and never deletes old accessors from structPrototype; assignTrackedAccessors (resources/tracked.ts) never deletes removed isAttribute accessors. Make every Date modifier return makeDateField and fix the DateField interface; reset hasRelationships and delete the previous resolvedAttributeNamesList names from structPrototype; in assignTrackedAccessors delete own prototype properties whose getter is marked isAttribute and are absent from the new descriptor set. Include the QA regression suite unitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js (7 tests: fails 6/7 without the fix, 7/7 with it) in this PR.
Acceptance
The 7-test QA-1951 suite passes (and 6/7 fail on origin/main); existing defineTable tests green. Show each new test failing on origin/main sources (forced rebuild) before the fix.
Dispatch: task
harper-definetable-date-modifiers-accessors· queued by finding-triage/wave-40 · ran by claude/opus/xhigh · worker kzyp-xps-1Late Grok findings
5 surviving (highest minor) — Grok reviewed d9ad0c5
resources/defineTable.ts:96—date.createdTime.updatedTime(and the other orders) is accepted, butTable.ts:1688-1689binds both timestamp slots to the same attribute. On an update,Table.ts:5491stamps the updated time and thenTable.ts:5504-5506writes the original created time over it, so the field never changes afterresources/Table.ts:5491— a Date field marked both primary key and timestamp (date.primaryKey.createdTime, now pinned valid atunitTests/resources/defineTable-registration.test.js:300):create({})gets a numeric id fromgetNewId(Table.ts:3104skips the UUID branch forDate,Table.ts:3202returns a number). TheunitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:8— Arms 1–3 repeatdefineTable-registration.test.jscases (Arm 1 passes on main). Only Arm 4 adds coverage of its own.resources/DESIGN.md:275— two problems: (1) the timestamp-slot rebind is folded into the RecordObject-prototype bullet, where a reader looking for it won't search; (2) it cites#3594, which can't be checked because the branch has no PR yet.resources/Table.ts:11724— added comments speak to the diff rather than to the next reader:Table.ts:11724("the attribute, not a flag:") andunitTests/types/defineTable.type-test.ts:125(a fixture label that says the field "stands alone" but not which slot wins at runtime).Not standing: 2 merged. Grok ran 13m (13m from launch to report); the fold adjudication (
cursor-grok@d9ad0c563568-92aad3f5, claude) took 1m.Late Grok findings
1 surviving (highest nit) — Grok reviewed 91efa6a
resources/Table.ts:12472— a source fill picks the created-time attribute once, atresources/Table.ts:11725-11726. If the table is re-declared before that fill commits, and that re-declaration moves created-time to a different attribute, then on a sharded table with secondary indexes where this node does not own the record,Not standing: none. Grok ran 15m (15m from launch to report); the fold adjudication (
cursor-grok@91efa6afcba0-84444436, claude) took 1m.Late Grok findings
7 surviving (highest major) — Grok reviewed 17832a3
resources/Table.ts:10423— Reload deletes and recreates retained resolver getters; their lookup-performance impact remains unmeasuredresources/tracked.ts:225— After dropping a sealed-table field, assigning it alongside a declared-field edit persists an unknown property without validationresources/Table.ts:10195— A rawtable()reload whose computed-expression compilation throws before timestamp and relationship attributes leaves subsequent writes unstamped and relationship-selection checks disabledresources/defineTable.ts:94— Every Date modifier constructs additional descriptor objects during schema declarationunitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:90— Date and relationship fixtures duplicate registration-suite coverage, requiring parallel maintenance; consolidate while retaining unique tracked-update casesunitTests/types/defineTable.type-test.ts:125— The added comment narrates runtime timestamp behavior that this type test does not exerciseunitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:2— The new assert import omits the requirednode:prefixNot standing: none. Grok ran 15m (15m from launch to report); the fold adjudication (
cursor-grok@17832a3ba55a-0fe469dd, codex) took 4m.Late Grok findings
9 surviving (highest major) — Grok reviewed 8c92da6
resources/Table.ts:5523— Every local incremental instance commit allocates an own-key array, including writes without own fields; this path has no measurementresources/Table.ts:5526— An incremental update with a Buffer root followed byrecord.extra = 1converts the root into indexed properties before validation, allowing an invalid record mutationresources/databases.ts:5531— A schema-binding exception leaves persisted catalog changes and the incremented version ahead of live bindingsresources/Table.ts:10398— Reloading a schema-generated computed field without its previous function or expression leaves the old resolver callable, producing stale valuesresources/defineTable.ts:84— Serializing a Date builder exhausts the stack through recursive enumerable modifiers; both reported serialization examples already fail on the base branchresources/defineTable.ts:94— Every Date modifier step installs two additional getter descriptors during schema constructionunitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:90— Overlapping Date and accessor-retirement fixtures require parallel maintenance alongside the registration suiteunitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:2— The new suite imports a Node builtin without the requirednode:prefixunitTests/types/defineTable.type-test.ts:125— The added comment describes runtime timestamp precedence beside assertions that establish only typesNot standing: none. Grok ran 23m (23m from launch to report); the fold adjudication (
cursor-grok@8c92da6e48f1-9681b034, codex) took 3m.Late Grok findings
7 surviving (highest minor) — Grok reviewed 43a5440, ruled against 525d09a
resources/Table.ts:5552— Every local incremental instance write, including a plainTable.patch(id, { label })with no own fields, now runsObject.keys(this). The probe measured a median of 0.539 µs on HEAD vs 0.440 µs on base for the validation block.resources/Table.ts:5580— An own field that carries the created-time or updated-time attribute's name is copied over the server stamp at commit, becauseresources/tracked.ts:490-497copies own properties after the change set. Also, the change set is left half-merged when the update is rejected.resources/Table.ts:5583— An unchanged non-writable own field holdingNaNfails the!==check,Reflect.setreturns false, and the update is wrongly rejected with a 400.resources/Table.ts:5557— A sealed but not frozen patch object is reused as the change set, so adding a captured own field throwsTypeErrorand aborts the write.resources/Table.ts:12788—residencyStubreads the livecreatedTimePropertywhile the source fill pinnedcreatedTimeToAssign(resources/Table.ts:12040-12042). If a schema reload lands between the fill and a non-resident commit, the stub is stored without the pinned created-time stamp.resources/Table.ts:10449—assignTrackedAccessors(Updatable, this, true)reinstalls accessors and runs the retirement scan on every reload, for a class that is never instantiated. The cost is per reload only.resources/Table.ts:5555— New comments atresources/Table.ts:5555-5556,:5581,:12040andunitTests/types/defineTable.type-test.ts:125mostly narrate the code.Not standing: 1 separate-issue. Grok ran 15m (15m from launch to report); the fold adjudication (
cursor-grok@43a5440d371c-1e418129, claude) took 1m.Review-Coverage: authored=gemini; ran=cursor-composer,codex,cursor-grok,claude; late=cursor-grok:adjudicated; adjudicated=domain; declined=muse; rounds=27; full=8 @ c914e37
Review-Attention: deep ~35m (critical: Table.ts, tracked.ts; decisions: readonly-own-field-rejects, v2-instance-own-props-excluded, null-incremental-root-400, applyownchanges-rederives-merged-keys, reload-atomicity-in-memory-only, do-less-alternative, release-target-vs-evidence) @ c914e37