Skip to content

Fix Date chaining and preserve validation across table re-declarations - #3594

Merged
kriszyp merged 21 commits into
mainfrom
fix/definetable-date-chaining-stale-accessors
Oct 30, 2026
Merged

kriszyp merged 21 commits into
mainfrom
fix/definetable-date-chaining-stale-accessors

Conversation

@kriszyp

@kriszyp kriszyp commented Oct 26, 2026 •

Copy link
Copy Markdown
Member
Kris on a ladder pulling dusty disconnected levers off a workshop control panel into a scrap bin, Utah arches outside

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 per loadAsInstance !== false tracked 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.defineProperty under 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

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

write.captureChanges?.()
assertValidRecordRootBeforeCombining(recordUpdate)
if (tracksInstance) {
  ownKeys = collapseOwn ? Object.keys(this) : []
  recordUpdate = collapseChanges(this, instanceChanges, ownKeys)
  ownMerged = ownKeys.length > 0 && recordUpdate !== instanceChanges
}
if (fullUpdate || (tracksInstance ? recordUpdate !== undefined : recordUpdate && hasChanges(recordUpdate))) {
  this.validate(recordUpdate, !fullUpdate)
  if (ownMerged) applyOwnChanges(this, recordUpdate, ownKeys)
}

Product and architecture tour

Sealed tracked writes

Which edits does the commit validator inspect?

Before After
A retired own field plus a declared edit could persist an unknown value. Validation rejects the transaction; unrelated edits preserve untouched stored legacy fields.

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]
Loading

Complete Date builders

What can a component declare after another modifier?

Example: Indexed creation timestamp

createdAt: types.date.indexed.createdTime retains 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

  • Write admission and reload recovery cover real transactions/storage; pre-fix failures reproduce on 17832a3, 01f458e and 8c92da6.
  • Registration, QA-1951, tracked, schema, source-fill, caching and write/transaction suites: 161 passing on RocksDB; 112 on LMDB. Type fixtures, build, lint and TypeScript checks pass.
Full-gate limits and isolated checks
  • Core: 1,646 recorded passing checks before timeout, seven deployment-test failures reported. All six named cases, including the associated cleanup, pass in isolation.
  • Resources: 5,129 recorded passing checks before timeout, one source-apply snapshot-retry failure. That case and the subscription-resume case at the cutoff pass in isolation.
  • Integration: timeout with one TTL clean-window premise failure and four local Ollama generation failures. The complete TTL file passes 5/5 alone, with all 10 windows exact. Ollama requests the absent llama3.2 alias (404), while llama3.2:3b exists (200); no backend code changed.
  • Performance diagnostic: four paired runs against incoming 17832a3 and 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.
  • Downstream Next.js Node24 initially timed out waiting for a cross-node cached nonce (42 passed, 1 failed, 5 not run). The targeted retry passed; Nodes20/22 passed initially. The cause was not established, and no source change was made for this observation.
  • CI mapping: 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

Late Grok findings

4 surviving (highest minor) — Grok reviewed cc66360, ruled against 886b844
  • minor — resources/defineTable.ts:96 — date.createdTime.updatedTime sets 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 at resources/Table.ts:5130 is overwritten with the original value at `resources/
  • nit — 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:9044 repeats the note at resources/DESIGN.md:289
  • nit — resources/DESIGN.md:289 — the note names its enforcing test, so under the rule at DESIGN.md:1 it should shrink to one line naming that enforcement. It is still three sentences and links no issue or PR
  • nit — resources/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 matter

Not 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-1

Late Grok findings

5 surviving (highest minor) — Grok reviewed d9ad0c5
  • minor — resources/defineTable.ts:96 — date.createdTime.updatedTime (and the other orders) is accepted, but Table.ts:1688-1689 binds both timestamp slots to the same attribute. On an update, Table.ts:5491 stamps the updated time and then Table.ts:5504-5506 writes the original created time over it, so the field never changes after
  • minor — resources/Table.ts:5491 — a Date field marked both primary key and timestamp (date.primaryKey.createdTime, now pinned valid at unitTests/resources/defineTable-registration.test.js:300): create({}) gets a numeric id from getNewId (Table.ts:3104 skips the UUID branch for Date, Table.ts:3202 returns a number). The
  • nit — unitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:8 — Arms 1–3 repeat defineTable-registration.test.js cases (Arm 1 passes on main). Only Arm 4 adds coverage of its own.
  • nit — 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.
  • nit — resources/Table.ts:11724 — added comments speak to the diff rather than to the next reader: Table.ts:11724 ("the attribute, not a flag:") and unitTests/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
  • nit — resources/Table.ts:12472 — a source fill picks the created-time attribute once, at resources/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
  • major — resources/Table.ts:10423 — Reload deletes and recreates retained resolver getters; their lookup-performance impact remains unmeasured
  • major — resources/tracked.ts:225 — After dropping a sealed-table field, assigning it alongside a declared-field edit persists an unknown property without validation
  • major — resources/Table.ts:10195 — A raw table() reload whose computed-expression compilation throws before timestamp and relationship attributes leaves subsequent writes unstamped and relationship-selection checks disabled
  • nit — resources/defineTable.ts:94 — Every Date modifier constructs additional descriptor objects during schema declaration
  • nit — unitTests/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 cases
  • nit — unitTests/types/defineTable.type-test.ts:125 — The added comment narrates runtime timestamp behavior that this type test does not exercise
  • nit — unitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:2 — The new assert import omits the required node: prefix

Not 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
  • major — resources/Table.ts:5523 — Every local incremental instance commit allocates an own-key array, including writes without own fields; this path has no measurement
  • major — resources/Table.ts:5526 — An incremental update with a Buffer root followed by record.extra = 1 converts the root into indexed properties before validation, allowing an invalid record mutation
  • minor — resources/databases.ts:5531 — A schema-binding exception leaves persisted catalog changes and the incremented version ahead of live bindings
  • minor — resources/Table.ts:10398 — Reloading a schema-generated computed field without its previous function or expression leaves the old resolver callable, producing stale values
  • minor — resources/defineTable.ts:84 — Serializing a Date builder exhausts the stack through recursive enumerable modifiers; both reported serialization examples already fail on the base branch
  • nit — resources/defineTable.ts:94 — Every Date modifier step installs two additional getter descriptors during schema construction
  • nit — unitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:90 — Overlapping Date and accessor-retirement fixtures require parallel maintenance alongside the registration suite
  • nit — unitTests/resources/qa-1951-define-table-evolution-and-date-chaining.test.js:2 — The new suite imports a Node builtin without the required node: prefix
  • nit — unitTests/types/defineTable.type-test.ts:125 — The added comment describes runtime timestamp precedence beside assertions that establish only types

Not 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
  • minor — resources/Table.ts:5552 — Every local incremental instance write, including a plain Table.patch(id, { label }) with no own fields, now runs Object.keys(this). The probe measured a median of 0.539 µs on HEAD vs 0.440 µs on base for the validation block.
  • minor — 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, because resources/tracked.ts:490-497 copies own properties after the change set. Also, the change set is left half-merged when the update is rejected.
  • minor — resources/Table.ts:5583 — An unchanged non-writable own field holding NaN fails the !== check, Reflect.set returns false, and the update is wrongly rejected with a 400.
  • minor — resources/Table.ts:5557 — A sealed but not frozen patch object is reused as the change set, so adding a captured own field throws TypeError and aborts the write.
  • minor — resources/Table.ts:12788 — residencyStub reads the live createdTimeProperty while the source fill pinned createdTimeToAssign (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.
  • nit — 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.
  • nit — resources/Table.ts:5555 — New comments at resources/Table.ts:5555-5556, :5581, :12040 and unitTests/types/defineTable.type-test.ts:125 mostly 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

@kriszyp kriszyp added this to the v5.4 milestone Oct 26, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@kriszyp kriszyp closed this Oct 27, 2026
@kriszyp kriszyp reopened this Oct 27, 2026
@kriszyp kriszyp closed this Oct 27, 2026
@kriszyp kriszyp reopened this Oct 27, 2026
@kriszyp
kriszyp marked this pull request as ready for review October 27, 2026 14:58
@kriszyp
kriszyp force-pushed the fix/definetable-date-chaining-stale-accessors branch 2 times, most recently from a0b0dc9 to d9ad0c5 Compare October 28, 2026 13:46
@kriszyp
kriszyp force-pushed the fix/definetable-date-chaining-stale-accessors branch from 91efa6a to 17832a3 Compare October 29, 2026 13:42
@kriszyp
kriszyp marked this pull request as draft October 29, 2026 20:28
@kriszyp kriszyp changed the title Fix types.date.indexed.createdTime, and drop the old schema's accessors and timestamp fields when a table is re-declared Fix Date chaining and preserve validation across table re-declarations Oct 29, 2026
kriszyp and others added 7 commits October 29, 2026 22:21
…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
kriszyp and others added 14 commits October 29, 2026 22:21
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
@kriszyp
kriszyp force-pushed the fix/definetable-date-chaining-stale-accessors branch from 0af466f to c914e37 Compare October 30, 2026 05:34
@kriszyp
kriszyp marked this pull request as ready for review October 30, 2026 13:46
@kriszyp
kriszyp merged commit 50d6b7e into main Oct 30, 2026
74 checks passed
@kriszyp
kriszyp deleted the fix/definetable-date-chaining-stale-accessors branch October 30, 2026 13:46
kriszyp added a commit that referenced this pull request Oct 30, 2026
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant