@solidjs/signals 2.0.0-rc.6 → 2.0.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1758 -6621
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/boundaries.js +3 -1
  40. package/dist/prod/core/action.js +52 -32
  41. package/dist/prod/core/async.js +177 -128
  42. package/dist/prod/core/attribution-hooks.js +3 -0
  43. package/dist/prod/core/constants.js +32 -1
  44. package/dist/prod/core/context.js +10 -16
  45. package/dist/prod/core/core.js +505 -286
  46. package/dist/prod/core/dev.js +2 -0
  47. package/dist/prod/core/effect.js +72 -43
  48. package/dist/prod/core/external.js +2 -2
  49. package/dist/prod/core/graph.js +40 -36
  50. package/dist/prod/core/heap.js +50 -51
  51. package/dist/prod/core/lanes.js +43 -22
  52. package/dist/prod/core/optimistic.js +224 -80
  53. package/dist/prod/core/owner.js +43 -39
  54. package/dist/prod/core/scheduler.js +347 -232
  55. package/dist/prod/core/verdict.js +71 -74
  56. package/dist/prod/index.js +9 -5
  57. package/dist/prod/map.js +311 -150
  58. package/dist/prod/signals.js +52 -38
  59. package/dist/prod/store/next/optimistic.js +127 -131
  60. package/dist/prod/store/next/projection.js +3 -3
  61. package/dist/prod/store/next/reconcile.js +149 -294
  62. package/dist/prod/store/next/store.js +694 -380
  63. package/dist/prod/store/next/target.js +32 -10
  64. package/dist/prod/store/store.js +12 -14
  65. package/dist/prod/store/utils.js +36 -27
  66. package/dist/types/attribution.d.ts +15 -0
  67. package/dist/types/attribution.prod.d.ts +13 -0
  68. package/dist/types/core/async.d.ts +1 -1
  69. package/dist/types/core/attribution-hooks.d.ts +183 -6
  70. package/dist/types/core/attribution.d.ts +435 -24
  71. package/dist/types/core/constants.d.ts +31 -0
  72. package/dist/types/core/core.d.ts +19 -3
  73. package/dist/types/core/dev.d.ts +165 -26
  74. package/dist/types/core/effect.d.ts +8 -1
  75. package/dist/types/core/heap.d.ts +5 -3
  76. package/dist/types/core/index.d.ts +2 -1
  77. package/dist/types/core/invariants.d.ts +1 -1
  78. package/dist/types/core/lanes.d.ts +17 -0
  79. package/dist/types/core/scheduler.d.ts +69 -4
  80. package/dist/types/core/types.d.ts +29 -9
  81. package/dist/types/index.d.ts +9 -2
  82. package/dist/types/map.d.ts +2 -0
  83. package/dist/types/signals.d.ts +10 -0
  84. package/dist/types/store/index.d.ts +4 -6
  85. package/dist/types/store/next/optimistic.d.ts +4 -2
  86. package/dist/types/store/next/reconcile.d.ts +5 -12
  87. package/dist/types/store/next/store.d.ts +8 -9
  88. package/dist/types/store/next/target.d.ts +56 -55
  89. package/dist/types/store/store.d.ts +14 -9
  90. package/dist/types/store/utils.d.ts +7 -0
  91. package/package.json +21 -16
  92. package/dist/node.cjs +0 -11121
  93. package/dist/prod/store/next/patch-hooks.js +0 -13
  94. package/dist/prod/store/next/patch.js +0 -614
  95. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  96. package/dist/types/store/next/patch.d.ts +0 -91
  97. package/dist/types-cjs/affects.d.cts +0 -47
  98. package/dist/types-cjs/boundaries.d.cts +0 -175
  99. package/dist/types-cjs/core/action.d.cts +0 -64
  100. package/dist/types-cjs/core/async.d.cts +0 -23
  101. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
  102. package/dist/types-cjs/core/attribution.d.cts +0 -239
  103. package/dist/types-cjs/core/core.d.cts +0 -176
  104. package/dist/types-cjs/core/dev.d.cts +0 -95
  105. package/dist/types-cjs/core/effect.d.cts +0 -30
  106. package/dist/types-cjs/core/error.d.cts +0 -56
  107. package/dist/types-cjs/core/external.d.cts +0 -15
  108. package/dist/types-cjs/core/graph.d.cts +0 -28
  109. package/dist/types-cjs/core/heap.d.cts +0 -22
  110. package/dist/types-cjs/core/index.d.cts +0 -13
  111. package/dist/types-cjs/core/invariants.d.cts +0 -59
  112. package/dist/types-cjs/core/lanes.d.cts +0 -44
  113. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  114. package/dist/types-cjs/core/owner.d.cts +0 -124
  115. package/dist/types-cjs/core/scheduler.d.cts +0 -231
  116. package/dist/types-cjs/core/types.d.cts +0 -204
  117. package/dist/types-cjs/core/verdict.d.cts +0 -2
  118. package/dist/types-cjs/index.d.cts +0 -10
  119. package/dist/types-cjs/map.d.cts +0 -69
  120. package/dist/types-cjs/package.json +0 -3
  121. package/dist/types-cjs/store/index.d.cts +0 -21
  122. package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
  123. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  124. package/dist/types-cjs/store/next/patch.d.cts +0 -91
  125. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  126. package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
  127. package/dist/types-cjs/store/next/store.d.cts +0 -127
  128. package/dist/types-cjs/store/next/target.d.cts +0 -192
  129. package/dist/types-cjs/store/store.d.cts +0 -138
  130. package/dist/types-cjs/store/storePath.d.cts +0 -58
  131. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,16 +1,16 @@
1
- import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
1
+ import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_HELD_TRUTH, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
2
2
 
3
- import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, context } from "../../core/core.js";
3
+ import { setSlotUnobserved, setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, unlinkFirewallChild, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
4
4
 
5
5
  import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
6
6
 
7
+ import "../../core/invariants.js";
8
+
7
9
  import { getObserver, getOwner } from "../../core/owner.js";
8
10
 
9
11
  import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
10
12
 
11
- import { storeNextLookup, optHooks, ownedRaw, markDescendants, devAssertNeverUserMutation } from "./target.js";
12
-
13
- import { patchHooks, rowHooks } from "./patch-hooks.js";
13
+ import { lookupTarget, optHooks, storeNextLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
14
14
 
15
15
  /**
16
16
  * Store rewrite — increment 2: plain deep stores with pending-backing writes.
@@ -44,9 +44,9 @@ import { patchHooks, rowHooks } from "./patch-hooks.js";
44
44
  *
45
45
  * ARRAY SHAPE RULE: arrays normalize their named properties to dictionary
46
46
  * mode as the count grows (V8 13.x: counts ≡ 0 mod 3 from 18 up), so the
47
- * target's named field count is capped at 20 — write-side patch-channel
48
- * state lives inside the single `pc` extension (see target.ts), never as
49
- * new named fields here. */
47
+ * target's named field count is capped at 20 — any future write-side state
48
+ * beyond `wk` must ride an extension object (see target.ts), never new
49
+ * named fields here. */
50
50
  function TargetShape() {
51
51
  this.v = undefined;
52
52
  this.ch = undefined;
@@ -61,30 +61,20 @@ function TargetShape() {
61
61
  this.d = undefined;
62
62
  this.a = undefined;
63
63
  this.sc = undefined;
64
+ this.kc = undefined;
64
65
  this.nc = undefined;
65
- this.adopted = undefined;
66
+ this.ab = undefined;
66
67
  this.fam = undefined;
67
68
  this.s = undefined;
68
69
  this.ovl = undefined;
69
70
  this.del = undefined;
70
- this.pc = undefined;
71
+ this.wk = undefined;
71
72
  this.hv = undefined;
72
73
  this.ht = undefined;
73
74
  }
74
75
 
75
76
  TargetShape.prototype = Object.prototype;
76
77
 
77
- /** Lazily allocate the patch-channel extension (one literal shape). */ function pcOf(e) {
78
- return e.pc ?? (e.pc = {
79
- sp: null,
80
- p: null,
81
- ro: null,
82
- wk: null,
83
- qa: null,
84
- qe: null
85
- });
86
- }
87
-
88
78
  function createTarget(e, t, n, r = t?.fam ?? null) {
89
79
  // The proxy target carries the array exotic class when the value is an
90
80
  // array, so Array.isArray(proxy) is true; the fields live on it directly.
@@ -101,15 +91,16 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
101
91
  i.h = null;
102
92
  i.k = null;
103
93
  i.dk = null;
104
- i.pc = null;
94
+ i.wk = null;
105
95
  i.u = t;
106
96
  i.pk = n;
107
97
  i.px = null;
108
98
  i.d = false;
109
99
  i.a = false;
110
- i.sc = false;
100
+ i.sc = 0;
101
+ i.kc = 0;
111
102
  i.nc = 0;
112
- i.adopted = false;
103
+ i.ab = null;
113
104
  i.fam = r;
114
105
  i.s = false;
115
106
  i.ovl = false;
@@ -128,7 +119,7 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
128
119
  // markRaw'd values never wrap through ANY store (R42; sticky raw-marking
129
120
  // is one half of the never-both-wrapped-and-raw invariant, RUL-12).
130
121
  if (rawValuesUsed && isRawValue(e)) return e;
131
- const i = (r?.map ?? storeNextLookup).get(e);
122
+ const i = lookupTarget(e, r);
132
123
  if (i !== undefined) return i.px;
133
124
  const o = e[$TARGET];
134
125
  if (o !== undefined && o.px === e) {
@@ -155,62 +146,119 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
155
146
 
156
147
  // ---------------------------------------------------------------------------
157
148
  // nodes: pure subscription points (values used only for equality gating)
158
- function getNode(e, t, n) {
159
- const r = e.n ??= Object.create(null);
160
- let i = r[t];
161
- if (i === undefined) {
162
- const o = i = signal(n, {
163
- // Logical-slot equality: values resolving to the same child target
164
- // are the same slot (privatization/adoption swap raw identity without
165
- // changing the logical value — only changed leaves notify, R9).
166
- equals: (t, n) => isEqual(t, n) || sameLogicalSlot(e, t, n),
167
- unobserved() {
168
- // A live affects() mark keeps the node addressable (sweep parity).
169
- if (o.o?.t) return;
170
- if (e.n && e.n[t] === o) {
171
- delete e.n[t];
172
- e.nc--;
173
- }
174
- }
175
- },
176
- // Projection nodes carry the projection computed as their firewall:
177
- // reads through them link the derive's status/lifecycle (§7b).
178
- e.fam?.node ?? undefined);
179
- // Store nodes are ownedWrite: the setter carries the owned-scope write
180
- // guard; node-level setSignals are internal notification machinery.
181
- o.T |= CONFIG_OWNED_WRITE;
182
- // Accessor-ness resolved ONCE per node (no per-object descriptor scan):
183
- // accessor keys serve through Reflect.get with the proxy receiver.
184
- o.acc = isOwnAccessor(e.pb ?? e.v, t);
185
- // Wrap cache: the proxy last served for this key and the raw it wrapped.
186
- // Raw-as-truth stores raw in nodes, so every object read needs a wrapper;
187
- // one pointer compare (pxv === value) replaces the per-read WeakMap
188
- // lookup in wrapNext the dominant read-path cost vs legacy, whose
189
- // nodes stored pre-wrapped values. A replaced child fails the compare
190
- // and re-wraps; at most one stale proxy is pinned until the next read.
191
- o.px = undefined;
192
- o.pxv = undefined;
149
+ // Shared slot-node equality (create-floor diet): ONE function for every
150
+ // store node `this` is the node (method-call convention at every _equals
151
+ // site), `_host` is the baked-in target backref. Logical-slot equality:
152
+ // values resolving to the same child target are the same slot
153
+ // (privatization/adoption swap raw identity without changing the logical
154
+ // value only changed leaves notify, R9).
155
+ const slotNodeEquals = function(e, t) {
156
+ return isEqual(e, t) || sameLogicalSlot(this.Ut, e, t);
157
+ };
158
+
159
+ // Shared slot-node unobserved handler (create-floor diet): registered once;
160
+ // the core sweep dispatches CONFIG_SLOT_NODE nodes here instead of holding a
161
+ // per-node closure in a per-node NodeExtension.
162
+ setSlotUnobserved(e => {
163
+ // A live affects() mark keeps the node addressable (sweep parity).
164
+ if (e.o?.t) return;
165
+ const t = e.Ut;
166
+ const n = e.xt;
167
+ if (t.n && t.n[n] === e) {
168
+ delete t.n[n];
169
+ t.nc--;
170
+ // Projection leaves also leave the firewall child chain (#3351): a
171
+ // dropped node is unreachable through the store — a fresh read makes a
172
+ // fresh node — so the chain would only retain it and its last value.
173
+ unlinkFirewallChild(e);
174
+ }
175
+ });
176
+
177
+ function getNode(e, t, n,
178
+ // First-read dedupe (create-floor slice 2): the get trap probes
179
+ // accessor-ness right before creating the node pass the verdict through
180
+ // so creation skips the second descriptor scan. -1 = unknown (other
181
+ // callers), 0/1 = probed.
182
+ r = -1) {
183
+ const i = e.n ??= Object.create(null);
184
+ let o = i[t];
185
+ if (o === undefined) {
186
+ // Born holding (#3330 store twin): a key first read while a live
187
+ // transaction holds an ADOPTION on this target (adoptPB's `ht`) is
188
+ // created as if it had existed when the hold was notified — the adopted
189
+ // value already swapped into `v`, so committed is the held view
190
+ // `hv[key]` and the adopted `v[key]` is staged under the transaction
191
+ // (stageHeldKey). Its held-adoption notification (stageHeldAdoptions)
192
+ // ran before the node existed and the drain has nothing left to say.
193
+ // Without this the node was born from whichever view its first reader
194
+ // saw and never learned the other. (The held-FOLD twin — a setter's
195
+ // write to an unobserved key landing only in the pending backing — is
196
+ // #3336's, and lands with #3337.)
197
+ const l = heldAdoptionTransition(e);
198
+ if (l !== null) n = e.hv[t];
199
+ // Create-floor diet: slotSignal bakes the whole node into one literal —
200
+ // no options object, no equals/unobserved closures, no NodeExtension,
201
+ // no post-construction expandos (acc + the wrap cache px/pxv are
202
+ // pre-shaped fields: the proxy last served for this key and the raw it
203
+ // wrapped — one pointer compare replaces the per-read WeakMap lookup in
204
+ // wrapNext). ownedWrite rides the literal's config: the setter carries
205
+ // the owned-scope write guard; node-level setSignals are internal
206
+ // notification machinery. Projection nodes carry the projection
207
+ // computed as their firewall: reads through them link the derive's
208
+ // status/lifecycle (§7b).
209
+ const f = o = slotSignal(n, slotNodeEquals, e, t,
210
+ // Accessor-ness resolved ONCE per node (no per-object descriptor
211
+ // scan on reads): accessor keys serve through Reflect.get with the
212
+ // proxy receiver.
213
+ r === -1 ? isOwnAccessor(e.pb ?? e.v, t) : r === 1, e.fam?.node ?? undefined);
193
214
  // Optimistic families: arm the override slot — setSignal routes armed
194
215
  // nodes through the core engine (lanes, ownership, reverts all native).
195
216
  if (e.fam?.opt) {
196
- ext(o).Pe = NOT_PENDING;
197
- o.T |= CONFIG_OPTIMISTIC;
217
+ ext(f).be = NOT_PENDING;
218
+ f.T |= CONFIG_OPTIMISTIC;
198
219
  }
199
220
  // A node born inside a live mark's identity scope inherits the mark
200
221
  // (the declaration walk could only cover nodes existing then).
201
- if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
202
- r[t] = i;
222
+ if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
223
+ if (l !== null) stageHeldKey(f, e.v[t], l);
224
+ i[t] = o;
203
225
  e.nc++;
204
226
  markDescendants(e);
205
227
  }
206
- return i;
228
+ return o;
229
+ }
230
+
231
+ /** The live transaction holding an adoption on `target` (adoptPB's `ht`;
232
+ * a latest()-pull PLAIN_HOLD is not a transaction), else null. */ function heldAdoptionTransition(e) {
233
+ if (e.ht === null || e.ht === PLAIN_HOLD || heldMaskView(e) === null) return null;
234
+ const t = currentTransition(e.ht);
235
+ return t.ft === false ? t : null;
236
+ }
237
+
238
+ /**
239
+ * Materialization under a hold. A node created by a first tracked read while
240
+ * a transaction holds this target must carry the same state a node that
241
+ * existed when the hold was notified carries — a transaction-stamped
242
+ * `_pendingValue` that core read() serves by ITS rules (a stale render reader
243
+ * of a foreign transaction's write sees committed, an owner-context reader
244
+ * inside the transaction sees the write) — or the reader's answer depends on
245
+ * whether some OTHER reader had materialized the key before the hold. Stage
246
+ * `nv` (the held value for the key — see getNode for which view it comes
247
+ * from) as the holding transaction's write — directly, not through
248
+ * setSignal: it is not a new write (it staged with the adoption's batch) and
249
+ * it walks no subscriber (the node has none yet). Transition-stamped now, as
250
+ * `runFolded` does — no parked-flush pass will stamp it.
251
+ */ function stageHeldKey(e, t, n) {
252
+ if (slotNodeEquals.call(e, e.me, t)) return;
253
+ e.Ge = t;
254
+ e.ge = n;
255
+ n.$t.push(e);
207
256
  }
208
257
 
209
258
  function sameLogicalSlot(e, t, n) {
210
259
  if (t === null || typeof t !== "object" || n === null || typeof n !== "object") return false;
211
- const r = e.fam?.map ?? storeNextLookup;
212
- const i = r.get(t);
213
- return i !== undefined && i === r.get(n);
260
+ const r = lookupTarget(t, e.fam);
261
+ return r !== undefined && r === lookupTarget(n, e.fam);
214
262
  }
215
263
 
216
264
  function getHasNode(e, t, n) {
@@ -221,12 +269,15 @@ function getHasNode(e, t, n) {
221
269
  equals: isEqual,
222
270
  unobserved() {
223
271
  if (o.o?.t) return;
224
- if (e.h && e.h[t] === o) delete e.h[t];
272
+ if (e.h && e.h[t] === o) {
273
+ delete e.h[t];
274
+ unlinkFirewallChild(o);
275
+ }
225
276
  }
226
277
  }, e.fam?.node ?? undefined);
227
278
  o.T |= CONFIG_OWNED_WRITE;
228
279
  if (e.fam?.opt) {
229
- ext(o).Pe = NOT_PENDING;
280
+ ext(o).be = NOT_PENDING;
230
281
  o.T |= CONFIG_OPTIMISTIC;
231
282
  }
232
283
  if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
@@ -242,12 +293,15 @@ function getKeySetNode(e) {
242
293
  const n = t = signal(0, {
243
294
  equals: false,
244
295
  unobserved() {
245
- if (e.k === n) e.k = null;
296
+ if (e.k === n) {
297
+ e.k = null;
298
+ unlinkFirewallChild(n);
299
+ }
246
300
  }
247
301
  }, e.fam?.node ?? undefined);
248
302
  n.T |= CONFIG_OWNED_WRITE;
249
303
  if (e.fam?.opt) {
250
- ext(n).Pe = NOT_PENDING;
304
+ ext(n).be = NOT_PENDING;
251
305
  n.T |= CONFIG_OPTIMISTIC;
252
306
  }
253
307
  e.k = t;
@@ -262,12 +316,15 @@ function getDeepNode(e) {
262
316
  const n = t = signal(0, {
263
317
  equals: false,
264
318
  unobserved() {
265
- if (e.dk === n) e.dk = null;
319
+ if (e.dk === n) {
320
+ e.dk = null;
321
+ unlinkFirewallChild(n);
322
+ }
266
323
  }
267
324
  }, e.fam?.node ?? undefined);
268
325
  n.T |= CONFIG_OWNED_WRITE;
269
326
  if (e.fam?.opt) {
270
- ext(n).Pe = NOT_PENDING;
327
+ ext(n).be = NOT_PENDING;
271
328
  n.T |= CONFIG_OPTIMISTIC;
272
329
  }
273
330
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
@@ -288,43 +345,74 @@ function getDeepNode(e) {
288
345
 
289
346
  let hookInstalled = false;
290
347
 
291
- function cloneRaw(e, t) {
348
+ /** Shallow-clone `source` as a backing OWNED by `t` (stamped `$OWNER`, see
349
+ * target.ts). Callers always pass their own committed backing as `source`. */ function cloneRaw(e, t) {
350
+ // Plain-data fast path (#3360): a scanned `Object.prototype` container
351
+ // whose own keys are all enumerable data clones by spread — the same
352
+ // result as the descriptor walk below (own enumerable string+symbol keys,
353
+ // normalized writable+configurable), at ~1/50th the cost.
354
+ t.sc || scanAccessorsOnce(t);
355
+ if (t.sc === 2) {
356
+ const n = {
357
+ ...e
358
+ };
359
+ n[$OWNER] = t;
360
+ return n;
361
+ }
292
362
  // Descriptor-preserving shallow clone (R29: installed getters stay live;
293
363
  // ruled 2026-08-17: frozen sources clone unfrozen — theirs stays frozen).
294
364
  // Data descriptors normalize to writable+configurable (the clone is OURS to
295
365
  // mutate — R51's "source-non-configurable is writable through the store");
296
366
  // enumerability and accessors are preserved. The scan doubles as the
297
367
  // accessor-flag detector (free — we're enumerating descriptors anyway).
298
- const n = Object.getOwnPropertyDescriptors(e);
368
+ const n = Object.getOwnPropertyDescriptors(e);
299
369
  for (const r of Reflect.ownKeys(n)) {
300
370
  const i = n[r];
301
371
  if (r === "length" && Array.isArray(e)) continue;
302
372
  i.configurable = true;
303
- if (!i.get && !i.set) i.writable = true; else if (t) t.a = true;
373
+ if (!i.get && !i.set) i.writable = true; else t.a = true;
304
374
  }
305
- return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
375
+ const r = Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
376
+ r[$OWNER] = t;
377
+ return r;
306
378
  }
307
379
 
308
- /** Scanned plainness for patch admission (patchableRaw): runs the one-time
309
- * accessor scan if it hasn't happened yet — the sticky `a` flag alone is not
310
- * trustworthy before a scan (it starts false and is discovered lazily). */ function targetIsPlain(e) {
311
- return e.sc ? !e.a : scanAccessorsOnce(e);
380
+ /** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
381
+ * writable, configurable, no accessor) is a bare assignment — the common case
382
+ * and the cheap one; anything else goes through defineProperty so accessors
383
+ * and attribute flags survive the copy. */ function copyOwn(e, t, n) {
384
+ const r = Object.getOwnPropertyDescriptor(t, n);
385
+ if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(e, n, r); else e[n] = r.value;
312
386
  }
313
387
 
314
388
  /** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
315
- * returns true when the container is plain data (overlay-safe). */ function scanAccessorsOnce(e) {
389
+ * returns true when the container is plain data (overlay-safe). Also grades
390
+ * the container for the plain-data fast paths (`sc` = 2): `Object.prototype`
391
+ * and every own key an enumerable data property — what a spread copies
392
+ * exactly and what a bare assignment lands exactly. */ function scanAccessorsOnce(e) {
316
393
  const t = e.v;
317
- for (const n of Reflect.ownKeys(t)) {
394
+ const n = Reflect.ownKeys(t);
395
+ let r = Object.getPrototypeOf(t) === Object.prototype;
396
+ for (const i of n) {
318
397
  // Own keys shadow prototype accessors, so the lookups are exact here.
319
- if (lookupGetter.call(t, n) !== undefined || lookupSetter.call(t, n) !== undefined) {
398
+ if (lookupGetter.call(t, i) !== undefined || lookupSetter.call(t, i) !== undefined) {
320
399
  e.a = true;
400
+ r = false;
321
401
  break;
322
402
  }
403
+ if (r && !propertyIsEnumerable.call(t, i)) r = false;
323
404
  }
324
- e.sc = true;
405
+ e.sc = r ? 2 : 1;
406
+ e.kc = n.length;
325
407
  return !e.a;
326
408
  }
327
409
 
410
+ /** Own-key count above which a draft opens as a prototype overlay rather
411
+ * than a clone (#3360). Below it a spread clone of a plain container is
412
+ * cheaper than the overlay's `Object.create` (V8 converts the committed
413
+ * backing into a prototype, and every later flatten writes into that
414
+ * prototype); above it the clone's O(keys) copy dominates (#3044). */ const OVERLAY_MIN_KEYS = 32;
415
+
328
416
  /** Downgrade a prototype-overlay pending backing to the clone path: builds
329
417
  * the real container (committed + overlay writes − deletes) that fold will
330
418
  * SWAP in as the committed backing, exactly as if the draft had started on
@@ -333,18 +421,11 @@ function cloneRaw(e, t) {
333
421
  if (!e.ovl) return;
334
422
  const t = e.pb;
335
423
  const n = cloneRaw(e.v, e);
336
- for (const e of Reflect.ownKeys(t)) {
337
- const r = Object.getOwnPropertyDescriptor(t, e);
338
- if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(n, e, r); else n[e] = r.value;
339
- }
424
+ for (const e of Reflect.ownKeys(t)) copyOwn(n, t, e);
340
425
  if (e.del !== null) {
341
426
  for (const t of e.del) delete n[t];
342
427
  e.del = null;
343
428
  }
344
- const r = e.fam?.map ?? storeNextLookup;
345
- r.delete(t);
346
- ownedRaw.add(n);
347
- r.set(n, e);
348
429
  e.pb = n;
349
430
  e.ovl = false;
350
431
  }
@@ -367,15 +448,32 @@ function ensurePB(e) {
367
448
  }
368
449
  if (activeTransition !== null) foldBatches.set(e, activeTransition);
369
450
  if (t === null) {
370
- // Prototype-chain overlay (#3044): plain-data non-array containers
371
- // outside projection/optimistic families open drafts in O(1) — own keys
372
- // are the writes, reads fall through to committed. Everything else
373
- // (arrays: splice/length semantics; families: seeding/revert machinery;
374
- // accessor containers: live getters) keeps the descriptor clone.
375
- if (e.fam === null && !Array.isArray(e.v) && (e.sc ? !e.a : scanAccessorsOnce(e))) {
376
- t = e.pb = Object.create(e.v);
451
+ // Prototype-chain overlay (#3044): plain-data non-array containers open
452
+ // drafts in O(1) — own keys are the writes, reads fall through to
453
+ // committed. Projection and derived-store families take it too (#3352:
454
+ // a derive touching one root key of a wide keyed record paid an
455
+ // O(keys) clone per recompute). Everything else keeps the descriptor
456
+ // clone: arrays (splice/length semantics), OPTIMISTIC families (the
457
+ // view-seeding below writes and deletes on the draft container, and the
458
+ // tentative discard/truth-park hand whole backings around), chained
459
+ // backings (the committed layer is another store's proxy — an overlay
460
+ // would route every read-through into its traps), accessor containers
461
+ // (live getters).
462
+ // The overlay pays off only for a WIDE container over an OWNED committed
463
+ // backing (#3360). Narrow containers clone cheaper than they overlay (a
464
+ // spread is a fast-path copy; `Object.create` turns the backing into a
465
+ // V8 prototype and every later flatten writes into that prototype). An
466
+ // unowned backing has to be privatized (cloned) at commit anyway, so an
467
+ // overlay there would cost the create PLUS the clone PLUS a per-key
468
+ // copy — the clone path does the one clone and swaps it in. The first
469
+ // write on a fresh store is exactly that case.
470
+ const n = e.v;
471
+ if (!e.fam?.opt && !e.ch && !Array.isArray(n) && (e.sc !== 0 ? !e.a : scanAccessorsOnce(e)) && e.kc > OVERLAY_MIN_KEYS && isOwned(n)) {
472
+ // Inherits `v`'s $OWNER stamp — the overlay resolves and reads as
473
+ // owned without a registration of its own.
474
+ t = e.pb = Object.create(n);
377
475
  e.ovl = true;
378
- } else t = e.pb = cloneRaw(e.v, e);
476
+ } else t = e.pb = cloneRaw(n, e);
379
477
  // Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
380
478
  // (committed + active node overrides), so follow-up writes compose on
381
479
  // optimism instead of clobbering from base (#2951's compose half).
@@ -388,19 +486,17 @@ function ensurePB(e) {
388
486
  if (n !== null) {
389
487
  for (const e of Reflect.ownKeys(n)) {
390
488
  const r = n[e];
391
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Pe);
489
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.be);
392
490
  }
393
491
  }
394
492
  const r = e.h;
395
493
  if (r !== null) {
396
494
  for (const e of Reflect.ownKeys(r)) {
397
495
  const n = r[e];
398
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.Pe)) delete t[e];
496
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.be)) delete t[e];
399
497
  }
400
498
  }
401
499
  }
402
- ownedRaw.add(t);
403
- (e.fam?.map ?? storeNextLookup).set(t, e);
404
500
  queueFold(e);
405
501
  }
406
502
  return t;
@@ -421,7 +517,7 @@ function ensurePB(e) {
421
517
  * foldHeld's node stamps). */ function heldMaskView(e) {
422
518
  const t = e.ht;
423
519
  if (t === null) return null;
424
- if (t !== PLAIN_HOLD && currentTransition(t)?.sn === true) return e.ht = e.hv = null;
520
+ if (t !== PLAIN_HOLD && currentTransition(t)?.ft === true) return e.ht = e.hv = null;
425
521
  return e.hv;
426
522
  }
427
523
 
@@ -439,21 +535,38 @@ function ensurePB(e) {
439
535
  if (!n) {
440
536
  queueFold(e);
441
537
  // records the pre-batch old before we swap
442
- e.adopted = true;
443
- // #3074/#3075: a projection recompute deriving from uncommitted inputs
444
- // swaps the backing SPECULATIVELY — committed-visibility readers must
445
- // keep the pre-hold view until the hold resolves (a source held by a
446
- // live transition, or a latest()-pull ahead of the flush). Post-await
447
- // landings (write-override) stay immediately visible landed truth —
448
- // and clear any hold; optimistic families ride the lane machinery.
449
- if (e.fam?.opt !== true) {
450
- if (getWriteOverride()) {
451
- e.ht = e.hv = null;
452
- } else if (activeTransition !== null || latestPullActive) {
453
- if (heldMaskView(e) === null) e.hv = e.v;
454
- e.ht = activeTransition ?? PLAIN_HOLD;
455
- }
456
- }
538
+ // Diff base = the view the nodes were last told (#3296). A draft's
539
+ // setter-exit notifications already moved them to its pending backing,
540
+ // so a later adoption diffs against THAT against committed, a key the
541
+ // draft changed and the adoption restores would never re-notify. An
542
+ // adoption with no draft leaves nodes where they were: keep an existing
543
+ // base, else the pre-batch committed (foldOlds' entry itself stays the
544
+ // committed identity for the path-copy CAS). Eager callers read t.pb
545
+ // directly; this hand-off exists because pb is gone by drain time.
546
+ if (e.pb !== null) {
547
+ if (e.ovl) materializePB(e);
548
+ e.ab = e.pb;
549
+ } else e.ab ??= foldOlds.get(e);
550
+ }
551
+ // #3074/#3075: a projection recompute deriving from uncommitted inputs
552
+ // swaps the backing SPECULATIVELY — committed-visibility readers must
553
+ // keep the pre-hold view until the hold resolves (a source held by a
554
+ // live transition, or a latest()-pull ahead of the flush). Post-await
555
+ // landings (write-override) stay immediately visible — landed truth —
556
+ // and clear any hold. Optimistic families hold too (#3330 store twin):
557
+ // their tentative edits ride the lane machinery, but a sync derive
558
+ // adopting under a transaction is held TRUTH like any projection's —
559
+ // unheld, handlers read it early and an optimistic write equal to it
560
+ // compared as a no-op against the swapped-in backing. A plain store's
561
+ // reconcile inside an action holds the same way: its nodes stage under the
562
+ // transaction (the inline notify), and the backing must not show handlers
563
+ // and stale readers what the tracked read masks (signal parity, #3336).
564
+ if (getWriteOverride()) {
565
+ e.ht = e.hv = null;
566
+ } else if (activeTransition !== null || !n && latestPullActive) {
567
+ if (heldMaskView(e) === null) e.hv = e.v;
568
+ e.ht = activeTransition ?? PLAIN_HOLD;
569
+ if (!n && activeTransition !== null) heldAdoptions.add(e);
457
570
  }
458
571
  e.pb = null;
459
572
  // Overlay and accessor-scan state describe the OUTGOING backing — a
@@ -465,13 +578,17 @@ function ensurePB(e) {
465
578
  // draft rescans once (#3044 audit follow-up).
466
579
  e.ovl = false;
467
580
  e.del = null;
468
- e.sc = false;
581
+ e.sc = 0;
469
582
  e.a = false;
470
- if (e.pc !== null) e.pc.wk = null;
583
+ e.wk = null;
471
584
  // adoption supersedes staged trap writes
472
585
  e.v = t;
473
586
  e.ch = t[$TARGET] !== undefined;
474
- (e.fam?.map ?? storeNextLookup).set(t, e);
587
+ // An adoptee we own within this family (a draft aliasing one of the
588
+ // family's own backings) re-stamps to its new owner — the stamp is the
589
+ // family's registration for it; everything else registers in the map.
590
+ const r = t[$OWNER];
591
+ if (r !== undefined && r.fam === e.fam) t[$OWNER] = e; else (e.fam?.map ?? storeNextLookup).set(t, e);
475
592
  }
476
593
 
477
594
  /** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
@@ -517,20 +634,78 @@ function queueFold(e) {
517
634
  * transition is ambient). Entries die with their draft — tentative backings
518
635
  * are consumed at setter exit. */ const tentativePBs = new WeakSet;
519
636
 
637
+ /** A draft read composes the live optimistic view until the draft has opened
638
+ * its OWN view-seeded backing (ensurePB seeds that clone from the view and
639
+ * registers it in tentativePBs; from then on reads must see the draft's
640
+ * writes, not the overrides they superseded). A pending backing that exists
641
+ * for any other reason is not that clone — a truth landing staged into a
642
+ * retaining transaction (#3164 fold) is authoritative truth WITHOUT the
643
+ * live overrides. ensurePB parks such a backing on the draft's first WRITE
644
+ * and reseeds from the view, but the reads that precede that write went to
645
+ * the staged truth: `votes++` read base, wrote base+1, and the override it
646
+ * emitted landed on the value already displayed — a second in-flight
647
+ * increment made after a sibling's landing was invisible (#2951's compose
648
+ * half, one landing later). */ function draftSeesOverrides(e) {
649
+ return e.pb === null || !tentativePBs.has(e.pb);
650
+ }
651
+
520
652
  /** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
521
- if (ownedRaw.has(e.v)) return;
522
- const t = cloneRaw(e.v, e);
523
- ownedRaw.add(t);
524
- storeNextLookup.set(t, e);
525
- e.v = t;
653
+ if (isOwned(e.v)) return;
654
+ const t = e.v;
655
+ const n = cloneRaw(t, e);
656
+ e.v = n;
526
657
  e.ch = false;
527
658
  if (e.u) {
528
659
  privatizeCommitted(e.u);
529
660
  devAssertNeverUserMutation(e.u.v);
530
- e.u.v[e.pk] = e.v;
661
+ // CAS, same as drainFolds' path copy: re-point the parent slot only
662
+ // while it still holds the raw we cloned. A parent that folded EARLIER
663
+ // in this drain may have replaced or deleted this slot (the same batch
664
+ // wrote the child and then `parent.row = fresh` / `parent.length = 0`)
665
+ // — an unconditional write resurrected the dropped child over it.
666
+ const n = e.u.v;
667
+ const r = parentSlotKey(e, t);
668
+ if (n[r] === t) n[r] = e.v;
531
669
  }
532
670
  }
533
671
 
672
+ /** Resolve the slot this child currently occupies in its parent's committed
673
+ * backing (#3282). `pk` is stamped at wrap time and arrays MOVE: a reverse/
674
+ * unshift/splice relocates the raw, and a fold that re-points the wrap-time
675
+ * slot writes the clone over whichever row lives there now. Objects never
676
+ * move keys, so the stamp is authoritative; for arrays, verify and re-locate
677
+ * by identity when stale (fold-time only — never on a read path). */ function parentSlotKey(e, t) {
678
+ const n = e.pk;
679
+ const r = e.u.v;
680
+ if (r[n] === t || !Array.isArray(r)) return n;
681
+ const i = r.indexOf(t);
682
+ if (i === -1) return n;
683
+ e.pk = i;
684
+ return i;
685
+ }
686
+
687
+ /** Overlay commit (#3044): apply the batch's writes onto an OWNED committed
688
+ * backing in place — O(written), not O(container). Unowned backings
689
+ * privatize first (clone once, parents re-slotted) — the never-mutate-user-
690
+ * data contract holds. Shared by the deferred fold (drainFolds) and the
691
+ * projection landing's immediate commit (notifyWrites). */ function flattenOverlay(e, t) {
692
+ privatizeCommitted(e);
693
+ const n = e.v;
694
+ // Plain-data grade (sc 2): every own key on the overlay is an enumerable
695
+ // writable data slot (set-trap writes; a non-plain defineProperty
696
+ // downgrades the grade) — a bare assignment lands it without the
697
+ // descriptor round trip.
698
+ for (const r of Reflect.ownKeys(t)) e.sc === 2 ? n[r] = t[r] : copyOwn(n, t, r);
699
+ if (e.del !== null) {
700
+ for (const t of e.del) delete n[t];
701
+ e.del = null;
702
+ }
703
+ e.pb = null;
704
+ e.ovl = false;
705
+ e.wk = null;
706
+ // written-keys window closes with the commit
707
+ }
708
+
534
709
  function drainFolds() {
535
710
  if (foldOlds.size === 0) return;
536
711
  const e = [ ...foldOlds ];
@@ -540,18 +715,13 @@ function drainFolds() {
540
715
  // is committing the batch the pull ran ahead of. Transition holds stay —
541
716
  // they clear when their transition is done (heldMaskView).
542
717
  if (t.ht === PLAIN_HOLD) t.ht = t.hv = null;
543
- // Eager (write-override) family folds swap pb -> v at notifyWrites'
544
- // tail: by the time this drain runs they carry no pb, and their
545
- // structural ops must emit at the fold-commit site below (the clone
546
- // branch never sees them). Re-audit blocker 4.
547
- const e = t.pb === null;
548
718
  if (t.pb !== null) {
549
719
  // #3089: a fold written under a still-running transition defers to
550
720
  // that transition's settle (the write-time stamp covers unobserved
551
721
  // keys; observed keys also hit the pending-node held check below).
552
722
  const e = foldBatches.get(t);
553
723
  if (e !== undefined) {
554
- if (currentTransition(e).sn === false) {
724
+ if (currentTransition(e).ft === false) {
555
725
  foldOlds.set(t, n);
556
726
  continue;
557
727
  }
@@ -568,14 +738,14 @@ function drainFolds() {
568
738
  // Only written keys can hold (their nodes took the setSignal); the
569
739
  // wk bound keeps this O(written) — see notifyWrites. Same fallback
570
740
  // rules as the notify (WK_ALL / accessors / non-plain prototypes).
571
- const e = t.pc !== null ? t.pc.wk : null;
741
+ const e = t.wk;
572
742
  const n = e === null || e === WK_ALL || t.a === true ||
573
743
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is
574
744
  // the committed container's prototype, not the overlay's.
575
745
  !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
576
746
  for (const e of n) {
577
747
  const t = o[e];
578
- if (t !== undefined && t.Re !== NOT_PENDING) {
748
+ if (t !== undefined && t.Ge !== NOT_PENDING) {
579
749
  r = true;
580
750
  break;
581
751
  }
@@ -587,87 +757,80 @@ function drainFolds() {
587
757
  continue;
588
758
  }
589
759
  if (t.ovl) {
590
- // Overlay flatten (#3044): apply this batch's writes onto an OWNED
591
- // committed backing in place O(written), not O(container). The
592
- // backing keeps its identity, so the `t.v === old` gate below skips
593
- // path copying (the parent slot already points here) and the
594
- // adopted-notify (setter notifications happened at write time).
595
- // Unowned backings privatize first (clone once, parents re-slotted)
596
- // the never-mutate-user-data contract holds.
760
+ // Overlay flatten (#3044): the backing keeps its identity, so the
761
+ // `t.v === old` gate below skips path copying (the parent slot
762
+ // already points here) and the adopted-notify (setter notifications
763
+ // happened at write time).
764
+ flattenOverlay(t, i);
765
+ } else if (t.v !== n) {
766
+ // Privatized mid-batch (#3271): an earlier fold in this drain
767
+ // path-copied THROUGH this target — privatizeCommitted cloned the
768
+ // committed backing, re-pointed the parent slot at the clone, and
769
+ // stitched the descendant's fold into it. The draft's pb predates
770
+ // that: swapping it in would clobber the descendant's fold, and the
771
+ // parent CAS below (still comparing against `old`) would fail and
772
+ // orphan this fold entirely — a projection draft writing descendant-
773
+ // then-ancestor silently lost the ancestor write. Merge the batch's
774
+ // writes onto the current container instead (the parent slot already
775
+ // points at it). privatize first: an adopt-then-write batch can land
776
+ // here with an unowned adoptee as t.v.
597
777
  privatizeCommitted(t);
598
778
  const e = t.v;
599
- for (const t of Reflect.ownKeys(i)) {
600
- const n = Object.getOwnPropertyDescriptor(i, t);
601
- if (n.get || n.set || !n.enumerable || !n.writable || !n.configurable) Object.defineProperty(e, t, n); else e[t] = n.value;
602
- }
603
- if (t.del !== null) {
604
- for (const n of t.del) delete e[n];
605
- t.del = null;
779
+ const r = t.wk;
780
+ if (r !== null && r !== WK_ALL) {
781
+ // The trap records every write/delete key apply exactly those.
782
+ for (const t of r) {
783
+ if (hasOwn.call(i, t)) copyOwn(e, i, t); else delete e[t];
784
+ }
785
+ } else {
786
+ // Array length write poisoned the bound (WK_ALL) — value-diff
787
+ // against the pre-batch old. Slots the draft never touched hold
788
+ // the same raw reference in both, so descendant folds stay put.
789
+ // Not copyOwn: the plain-value write is GATED on "the draft changed
790
+ // this slot" — an untouched slot in pb holds the pre-batch reference,
791
+ // and writing it back would clobber a descendant fold stitched into v.
792
+ for (const t of Reflect.ownKeys(i)) {
793
+ const r = Object.getOwnPropertyDescriptor(i, t);
794
+ if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(e, t, r); else if (r.value !== n[t] || !hasOwn.call(n, t)) e[t] = r.value;
795
+ }
796
+ for (const t of Reflect.ownKeys(n)) {
797
+ if (!hasOwn.call(i, t)) delete e[t];
798
+ }
606
799
  }
607
- (t.fam?.map ?? storeNextLookup).delete(i);
608
800
  t.pb = null;
609
- t.ovl = false;
610
- if (t.pc !== null) t.pc.wk = null;
801
+ t.wk = null;
611
802
  // written-keys window closes with the fold commit
612
803
  } else {
613
- // Setter-channel structural ops: a fold that changes an array's shape
614
- // (push/splice/permutation through the setter — the reconcile walk
615
- // never queues here) is a structural visibility transition for any
616
- // registered list driver. Identity-keyed; aligned folds emit nothing.
617
- // Family targets defer to their own adoption emission (fam reconcile).
618
- // Arrays always fold on this clone branch (overlay is non-array only).
619
- // Family setter drafts (writable projection push/splice through the
620
- // masked setter) fold on this branch too and the fold IS their
621
- // visibility moment — emit unless the structure already rode another
622
- // channel: adoption folds (reconcile walk emitted ops) and
623
- // optimistic families (lane-timed override channel). Re-audit
624
- // blocker 4.
625
- if (t.pc !== null && t.pc.ro !== null && !t.adopted && t.fam?.opt !== true && Array.isArray(i) && Array.isArray(t.v)) rowHooks.emitSetterRowOps(t, t.v, i);
626
804
  t.v = i;
627
805
  t.ch = false;
628
806
  // pb is always a plain clone
629
807
  t.pb = null;
630
- if (t.pc !== null) t.pc.wk = null;
808
+ t.wk = null;
631
809
  // written-keys window closes with the fold commit
632
810
  }
633
811
  }
634
- if (t.v === n) {
635
- // A no-op adoption (A -> B -> A before flush) still consumed its walk:
636
- // clear the flag or every later setter row-op gate (!t.adopted) stays
637
- // failed and a driven family list freezes (re-audit 5, P1-1).
638
- t.adopted = false;
639
- continue;
640
- }
641
- // Patch channel (fold-commit site): family targets emit HERE — the fold
642
- // IS their visibility moment (held folds re-queued above emit when they
643
- // actually commit) and so do PLAIN fold-adopted targets (setter-
644
- // returned root replacements, chained-store swaps: adoptions WITHOUT a
645
- // reconcile walk, so no walk-site emission ever happened — re-audit 2,
646
- // P1-2). Plain eager targets emitted at their walk/setter sites already.
647
- if (t.pc !== null && (t.fam !== null || t.adopted)) {
648
- // Structural ops for folds whose structure rode no other channel:
649
- // eager-folded family SETTER drafts (write-override swaps pb -> v at
650
- // notifyWrites' tail — the clone branch never sees them; adoption
651
- // folds re-emitting would double the walk's ops) and PLAIN fold
652
- // adoptions (no walk at all). Optimistic families ride the override
653
- // channel (lane-timed ops + revert RESYNC) — never re-emit here.
654
- if (t.pc.ro !== null && t.fam?.opt !== true && (t.fam !== null ? e && !t.adopted : t.adopted) && Array.isArray(t.v) && Array.isArray(n)) rowHooks.emitSetterRowOps(t, n, t.v);
655
- if (t.pc.p !== null) {
656
- // Accessor demotion at the fold-commit seam is DEV-ONLY (see the
657
- // reconcile seam note: prod never pays per-adoption scans).
658
- patchHooks.emitPatchLocal(t, t.v, n);
812
+ const e = t.ab;
813
+ t.ab = null;
814
+ if (t.v !== n) {
815
+ // Path copying (CAS: see the eager-fold twin above). Slot resolved by
816
+ // identity (#3282): an array move relocated the raw, so the wrap-time
817
+ // pk may point at a sibling — a raw-slot CAS there both failed to
818
+ // re-point AND (via privatizeCommitted's unguarded write) clobbered
819
+ // the sibling.
820
+ if (t.u) {
821
+ const e = parentSlotKey(t, n);
822
+ if (t.u.v[e] === n) {
823
+ privatizeCommitted(t.u);
824
+ devAssertNeverUserMutation(t.u.v);
825
+ t.u.v[e] = t.v;
826
+ }
659
827
  }
660
828
  }
661
- // Path copying (CAS: see the eager-fold twin above).
662
- if (t.u && t.u.v[t.pk] === n) {
663
- privatizeCommitted(t.u);
664
- devAssertNeverUserMutation(t.u.v);
665
- t.u.v[t.pk] = t.v;
666
- }
667
- if (t.adopted) {
668
- t.adopted = false;
669
- notifyFold(t, n, t.v);
670
- }
829
+ // Adoption notify against the base the nodes were last told (#3296). A
830
+ // no-op adoption (A -> B -> A before flush, no draft) has base === v and
831
+ // nothing to say; a draft superseded by an adoption back to the SAME raw
832
+ // still has (base = pending backing) !== v and must notify.
833
+ if (e !== null && e !== t.v) notifyFold(t, e, t.v);
671
834
  }
672
835
  }
673
836
 
@@ -717,7 +880,7 @@ function drainFolds() {
717
880
  // implicit index deletes), accessors on the record (t.a — a getter node's
718
881
  // value can change when ANY key is written), or a non-plain prototype
719
882
  // (class instances: prototype getters derive from arbitrary fields).
720
- const i = e.pc !== null ? e.pc.wk : null;
883
+ const i = e.wk;
721
884
  // Overlay pbs chain to the COMMITTED object (#3044): a prototype-overlay
722
885
  // draft is plain data on its own layer, but its getPrototypeOf is the
723
886
  // committed container — judge plainness by the COMMITTED prototype or the
@@ -765,6 +928,7 @@ function drainFolds() {
765
928
  // keys with no node. O(written/pb keys) equality only when a witness exists.
766
929
  if (e.dk !== null) {
767
930
  if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of o ?? Reflect.ownKeys(t)) {
931
+ if (r === $OWNER) continue;
768
932
  const i = t[r];
769
933
  const o = n[r];
770
934
  if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
@@ -791,12 +955,6 @@ function drainFolds() {
791
955
  }
792
956
  if (r) setSignal(e.k, e => e + 1);
793
957
  }
794
- // Patch channel (setter site): a committed write transitions this record —
795
- // queue its patches and bubble to ancestors (targeted nested writes must
796
- // reach the row patch, §4b). One number compare when no patches exist.
797
- // Family targets skip this site: their visibility moment is the FOLD
798
- // commit (drainFolds emits), not the recompute/draft write.
799
- if (e.fam === null && patchHooks !== null && patchHooks.hasPatches()) patchHooks.emitPatch(e, t, n);
800
958
  // Projection backing folds split by channel (two pinned contracts):
801
959
  // - sync-derive drafts (recompute body): NEVER eager — a downstream async
802
960
  // hold can form LATER in the same flush and the leaf must stay at stale
@@ -817,14 +975,22 @@ function drainFolds() {
817
975
  // Landed truth (post-await write-override): immediately visible to every
818
976
  // reader — any staged held view is superseded.
819
977
  if (e.ht !== null) e.ht = e.hv = null;
978
+ // Overlay landing (#3352): flatten in place — identity-stable, and
979
+ // privatizeCommitted re-slots the parent itself when it has to clone an
980
+ // unowned seed, so no path copy is needed.
981
+ if (e.ovl) return flattenOverlay(e, t);
820
982
  const n = e.v;
821
983
  e.pb = null;
822
984
  e.v = t;
823
985
  e.ch = false;
824
- if (e.u && e.u.v[e.pk] === n) {
825
- privatizeCommitted(e.u);
826
- devAssertNeverUserMutation(e.u.v);
827
- e.u.v[e.pk] = t;
986
+ if (e.u) {
987
+ // Identity-resolved slot (#3282) — see drainFolds' path-copy twin.
988
+ const r = parentSlotKey(e, n);
989
+ if (e.u.v[r] === n) {
990
+ privatizeCommitted(e.u);
991
+ devAssertNeverUserMutation(e.u.v);
992
+ e.u.v[r] = t;
993
+ }
828
994
  }
829
995
  }
830
996
  }
@@ -833,9 +999,9 @@ const FORCE = Symbol();
833
999
 
834
1000
  /** Same logical slot: both values resolve to one (re-pointed) child target —
835
1001
  * adoption preserved identity, so the slot did not change (R9). */ function targetsEqual(e, t) {
836
- if (e === null || typeof e !== "object") return false;
837
- const n = storeNextLookup.get(e);
838
- return n !== undefined && n === storeNextLookup.get(t);
1002
+ if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return false;
1003
+ const n = lookupTarget(e, null);
1004
+ return n !== undefined && n === lookupTarget(t, null);
839
1005
  }
840
1006
 
841
1007
  function arrayStructureChanged(e, t) {
@@ -850,8 +1016,9 @@ function arrayStructureChanged(e, t) {
850
1016
 
851
1017
  function membershipChanged(e, t) {
852
1018
  const n = Reflect.ownKeys(t);
853
- if (Reflect.ownKeys(e).length !== n.length) return true;
854
- for (const t of n) if (!(t in e)) return true;
1019
+ // The $OWNER stamp is not membership: an owned side counts one key more.
1020
+ if (Reflect.ownKeys(e).length - +isOwned(e) !== n.length - +isOwned(t)) return true;
1021
+ for (const t of n) if (t !== $OWNER && !(t in e)) return true;
855
1022
  return false;
856
1023
  }
857
1024
 
@@ -999,6 +1166,34 @@ function inDraft(e) {
999
1166
 
1000
1167
  /** Targets written during the current (outermost) setter — notified at exit. */ const pendingNotify = new Set;
1001
1168
 
1169
+ /** Targets adopted under a live transaction this setter (adoptPB set a
1170
+ * transaction hold) — their nodes are notified at the outermost exit. */ const heldAdoptions = new Set;
1171
+
1172
+ /**
1173
+ * Write-time notification for a transaction-held adoption (#3330 store twin;
1174
+ * the setter path's notifyWrites twin for adoptions). A projection's fold
1175
+ * normally notifies its nodes at the drain — and the drain of a batch parked
1176
+ * in a live transaction is the transaction's COMMIT, so the nodes took the
1177
+ * adopted values as fresh writes at commit time: every subscriber was
1178
+ * re-marked and re-ran against a frame the lane had already published (a
1179
+ * third `v=1 d=2`), where a signal's write had staged at write time and
1180
+ * promoted silently. Staging here, inside the transaction's batch, makes the
1181
+ * two paths one: the nodes carry transition-stamped `_pendingValue`s, their
1182
+ * subscribers recompute in this flush and park with the transaction, and the
1183
+ * commit promotes without re-notifying. `ab` moves to the adopted backing —
1184
+ * the view the nodes were last told (#3296) — so the drain has nothing left
1185
+ * to say and only path-copies.
1186
+ */ function stageHeldAdoptions() {
1187
+ const e = [ ...heldAdoptions ];
1188
+ heldAdoptions.clear();
1189
+ for (const t of e) {
1190
+ const e = t.ab;
1191
+ if (e === null || e === t.v) continue;
1192
+ notifyFold(t, e, t.v);
1193
+ t.ab = t.v;
1194
+ }
1195
+ }
1196
+
1002
1197
  const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1003
1198
 
1004
1199
  /** Mirror of core read()'s context rule: the OWNER context (not the tracking
@@ -1008,7 +1203,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1008
1203
  * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
1009
1204
  const e = getOwner();
1010
1205
  if (e === null) return false;
1011
- const t = e.Gt ? e.Dt : e;
1206
+ const t = e.gt ? e.bt : e;
1012
1207
  return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
1013
1208
  }
1014
1209
 
@@ -1019,7 +1214,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1019
1214
  * committed to them regardless of staged writes. */ function inForbiddenScope() {
1020
1215
  const e = getOwner();
1021
1216
  if (e === null) return false;
1022
- const t = e.Gt ? e.Dt : e;
1217
+ const t = e.gt ? e.bt : e;
1023
1218
  return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
1024
1219
  }
1025
1220
 
@@ -1030,7 +1225,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1030
1225
  if (t === null) return false;
1031
1226
  for (const e of Reflect.ownKeys(t)) {
1032
1227
  const n = t[e];
1033
- if (n.Re !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1228
+ if (n.Ge !== NOT_PENDING && n.ge != null && n.ge.ft !== true) return true;
1034
1229
  }
1035
1230
  return false;
1036
1231
  }
@@ -1102,6 +1297,8 @@ const lookupGetter = Object.prototype.__lookupGetter__;
1102
1297
 
1103
1298
  const lookupSetter = Object.prototype.__lookupSetter__;
1104
1299
 
1300
+ const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
1301
+
1105
1302
  function isOwnAccessor(e, t) {
1106
1303
  return hasOwn.call(e, t) && (lookupGetter.call(e, t) !== undefined || lookupSetter.call(e, t) !== undefined);
1107
1304
  }
@@ -1121,7 +1318,7 @@ function isOwnAccessor(e, t) {
1121
1318
 
1122
1319
  /** Active optimistic override on an armed node (armed slot idles at
1123
1320
  * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
1124
- return e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING;
1321
+ return e.o?.be !== undefined && e.o?.be !== NOT_PENDING;
1125
1322
  }
1126
1323
 
1127
1324
  /** The reading computation is until()'s authoritative-view predicate — same
@@ -1156,30 +1353,53 @@ function isOwnAccessor(e, t) {
1156
1353
  // Authoritative-view reads (until()'s predicate) skip the override arm
1157
1354
  // only: staged pending values are authoritative, overrides are the
1158
1355
  // caller's optimism.
1159
- const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Pe) : e.Re !== NOT_PENDING && (latestReadActive ||
1356
+ const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.be) : e.Ge !== NOT_PENDING && (latestReadActive ||
1160
1357
  // Owner-context pending visibility — except HELD truth (#3164,
1161
1358
  // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1162
1359
  // confirming truth), which only authoritative/latest readers
1163
1360
  // see (core read()'s A17-for-held-truth twin; ordinary readers
1164
1361
  // keep committed until the transaction's reveal — latest() is
1165
1362
  // exempted by the leading arm above).
1166
- (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Re : t;
1363
+ (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Ge : t;
1167
1364
  return n === FORCE ? t : n;
1168
1365
  }
1169
1366
 
1367
+ /** §7b: a chained target's child found as a RAW — from its pending backing
1368
+ * (a cloneRaw of the inner proxy, whose descriptors yield the inner store's
1369
+ * raws) or from the deep() walk's descriptor read through the chain — must
1370
+ * resolve to the inner family's proxy for that object before this family
1371
+ * wraps it, so the overlay serves the same chained targets as the settled
1372
+ * state. Otherwise every row re-wrapped as a fresh non-chained target keyed
1373
+ * by the raw: identities churned for the life of an optimistic action and
1374
+ * snapped back at settle, and writes landed on those orphans while deep()'s
1375
+ * witnesses sat on the chained targets (#3323). The inner family owns the
1376
+ * raw iff it has served it or its backing holds that exact object at `key`;
1377
+ * anything else is a draft's own replacement object — view-owned, correctly
1378
+ * non-chained — and is returned as is. Recurses down further chains. */ function resolveChainedRaw(e, t, n) {
1379
+ const r = e.v[$TARGET];
1380
+ if (r.ch) {
1381
+ const e = resolveChainedRaw(r, t, n);
1382
+ return e === n ? n : wrapNext(e, r, t);
1383
+ }
1384
+ const i = lookupTarget(n, r.fam);
1385
+ if (i !== undefined) return i.px;
1386
+ if ((r.v[t] === n || r.pb?.[t] === n) && isWrappable(n)) return wrapNext(n, r, t);
1387
+ return n;
1388
+ }
1389
+
1170
1390
  /** Serve an own data key: node-first when a node exists (pending visibility,
1171
1391
  * holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
1172
1392
  * backing IS another store's proxy) serve the read-through value — the outer
1173
1393
  * node is linked only for adoption-swap notification, its value never
1174
- * shadows the live chain. */ function serveDataKey(e, t, n, r, i) {
1175
- const o = e.ch && r === e.v;
1176
- let l = n;
1394
+ * shadows the live chain. */ function serveDataKey(e, t, n, r, i, o = -1) {
1395
+ const l = e.ch && r === e.v;
1396
+ let f = n;
1177
1397
  // §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
1178
1398
  // (backing ± presence overrides) for both length and indices makes torn
1179
1399
  // iteration impossible (a length node's value rides different visibility
1180
1400
  // rails than index overrides mid-settle). The node still carries
1181
1401
  // subscriptions; its value is never served here.
1182
- if (t === "length" && e.fam?.opt === true && !o && Array.isArray(r)) {
1402
+ if (t === "length" && e.fam?.opt === true && !l && Array.isArray(r)) {
1183
1403
  if (!inDraft(e)) {
1184
1404
  const r = e.n?.length;
1185
1405
  if (r !== undefined) {
@@ -1199,9 +1419,9 @@ function isOwnAccessor(e, t) {
1199
1419
  // #2951). Once ensurePB runs, the seeded clone carries the view.
1200
1420
  // AUTHORITATIVE drafts (projection derive) never overlay — ensurePB's
1201
1421
  // seeding rule, applied to the read side (#3108).
1202
- if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1422
+ if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1203
1423
  const n = e.n?.[t];
1204
- if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1424
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.be);
1205
1425
  }
1206
1426
  } else {
1207
1427
  if (i !== undefined) {
@@ -1213,28 +1433,31 @@ function isOwnAccessor(e, t) {
1213
1433
  // parity): READ_SLOW = a global read window or non-plain node.
1214
1434
  let e = readNodeFast(i);
1215
1435
  if (e === READ_SLOW) e = read(i);
1216
- if (!o || hasActiveOverride(i)) l = e === FORCE ? n : e;
1217
- } else if (!o || hasActiveOverride(i)) {
1218
- l = nodeValue(i, n);
1436
+ if (!l || hasActiveOverride(i)) f = e === FORCE ? n : e;
1437
+ } else if (!l || hasActiveOverride(i)) {
1438
+ f = nodeValue(i, n);
1219
1439
  }
1220
1440
  } else if (getObserver() !== null) {
1221
- read(getNode(e, t, n));
1441
+ // First tracked read: create + link, and let the wrap-cache branch
1442
+ // below populate px/pxv so read #2 skips wrapNext (slice 2).
1443
+ read(i = getNode(e, t, n, o));
1222
1444
  }
1223
1445
  }
1224
1446
  // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
1225
- if (e.s) return serveShallow(e, t, l);
1447
+ if (e.s) return serveShallow(e, t, f);
1448
+ if (e.ch && !l && f !== null && typeof f === "object" && f[$TARGET] === undefined) f = resolveChainedRaw(e, t, f);
1226
1449
  if (i !== undefined) {
1227
1450
  // Wrap cache (see getNode): only wrappables are ever cached, so a hit
1228
1451
  // skips isWrappable too — pointer-compare replaces both checks.
1229
- if (i.pxv === l && l !== undefined) return draftServe(e, i.px);
1230
- if (!isWrappable(l)) return l;
1231
- const n = wrapNext(l, e, t);
1452
+ if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
1453
+ if (!isWrappable(f)) return f;
1454
+ const n = wrapNext(f, e, t);
1232
1455
  i.px = n;
1233
- i.pxv = l;
1456
+ i.pxv = f;
1234
1457
  return draftServe(e, n);
1235
1458
  }
1236
- if (!isWrappable(l)) return l;
1237
- return draftServe(e, wrapNext(l, e, t));
1459
+ if (!isWrappable(f)) return f;
1460
+ return draftServe(e, wrapNext(f, e, t));
1238
1461
  }
1239
1462
 
1240
1463
  /** §6c store-wide status gate for reads that DON'T flow through a node:
@@ -1285,6 +1508,8 @@ const traps = {
1285
1508
  if (typeof t !== "string") {
1286
1509
  if (t === $TARGET) return e;
1287
1510
  if (t === $PROXY) return n;
1511
+ if (t === $OWNER) return undefined;
1512
+ // ownership stamp: never a user key
1288
1513
  // refresh()/isPending resolve the projection computed through $REFRESH.
1289
1514
  if (t === $REFRESH) return e.fam?.node ?? undefined;
1290
1515
  if (t === $TRACK) {
@@ -1320,8 +1545,11 @@ const traps = {
1320
1545
  // tracked read of a present data key — the dbmon/uibench effect re-read
1321
1546
  // shape. Skips serveDataKey's frame, the FORCE compare (only accessor
1322
1547
  // keys ever hold the sentinel), and isWrappable for primitives.
1323
- if (e.ch === false && writeScopes === null) {
1324
- const n = e.n?.[t];
1548
+ // ONE node-map lookup serves this block and the accessor probe below
1549
+ // (nothing between them creates nodes).
1550
+ const i = e.n?.[t];
1551
+ if (e.ch === false && writeScopes === null) {
1552
+ const n = i;
1325
1553
  if (n !== undefined && n.acc !== true && getObserver() !== null) {
1326
1554
  let r = readNodeFast(n);
1327
1555
  if (r === READ_SLOW) r = read(n);
@@ -1349,14 +1577,22 @@ const traps = {
1349
1577
  // absent-key/accessor subscriptions for every store read during any
1350
1578
  // derive, leaving nested projections permanently dependency-less when
1351
1579
  // their sources hadn't materialized yet (#3037).
1352
- const i = e.n?.[t];
1580
+ // First-read dedupe (create-floor slice 2): remember the probe verdict —
1581
+ // node creation downstream reuses it instead of re-scanning the
1582
+ // descriptor, but only when the probed object IS the one getNode would
1583
+ // scan (pb ?? v).
1584
+ let o = -1;
1353
1585
  {
1354
- const o = i !== undefined ? i.acc === true : !inDraft(e) && getObserver() !== null && isOwnAccessor(r, t);
1355
- if (o) {
1356
- if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined));
1357
- const o = Reflect.get(r, t, n);
1358
- if (e.s) return serveShallow(e, t, o);
1359
- return isWrappable(o) ? draftServe(e, wrapNext(o, e, t)) : o;
1586
+ let l;
1587
+ if (i !== undefined) l = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
1588
+ l = isOwnAccessor(r, t);
1589
+ if (r === (e.pb ?? e.v)) o = l ? 1 : 0;
1590
+ } else l = false;
1591
+ if (l) {
1592
+ if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, o));
1593
+ const l = Reflect.get(r, t, n);
1594
+ if (e.s) return serveShallow(e, t, l);
1595
+ return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1360
1596
  }
1361
1597
  }
1362
1598
  // Plain-data fast path: no descriptor allocation per read.
@@ -1366,40 +1602,41 @@ const traps = {
1366
1602
  // backings chain to the committed backing, so "own in the view" means
1367
1603
  // own on either layer (ownInView) — a genuine prototype method is one
1368
1604
  // that is own on NEITHER.
1369
- const o = e.ovl && r === e.pb;
1370
- if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
1371
- let l = r[t];
1372
- if (l === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
1605
+ const l = e.ovl && r === e.pb;
1606
+ if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t))) return undefined;
1607
+ let f = r[t];
1608
+ if (f === undefined ? !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t)) : false) {
1373
1609
  // Inherited: prototype getters/methods run with the proxy receiver.
1374
- l = Reflect.get(r, t, n);
1375
- if (typeof l === "function") return l;
1610
+ f = Reflect.get(r, t, n);
1611
+ if (typeof f === "function") return f;
1376
1612
  // proto methods untracked
1377
1613
  // Reading a currently-absent own key subscribes to it (R12) — for any
1378
1614
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1379
- if (l === undefined && !inDraft(e)) {
1380
- if (getObserver() !== null) read(getNode(e, t, undefined));
1615
+ if (f === undefined && !inDraft(e)) {
1616
+ if (getObserver() !== null) read(getNode(e, t, undefined, o));
1381
1617
  const n = e.n?.[t];
1382
1618
  if (n) {
1383
1619
  const r = nodeValue(n, undefined);
1384
1620
  if (e.s) return serveShallow(e, t, r);
1385
1621
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1386
1622
  }
1387
- } else if (l === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
1623
+ } else if (f === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
1388
1624
  // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1389
1625
  // the caller's optimism is not truth (has-trap twin below).
1390
1626
  !authoritativeServe()) {
1391
1627
  const n = e.n?.[t];
1392
- if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1628
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.be);
1393
1629
  }
1394
- if (e.s) return serveShallow(e, t, l);
1395
- return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1630
+ if (e.s) return serveShallow(e, t, f);
1631
+ return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
1396
1632
  }
1397
- if (typeof l === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return l;
1633
+ if (typeof f === "function" && !hasOwn.call(r, t) && !(l && hasOwn.call(e.v, t))) return f;
1398
1634
  // proto method
1399
- return serveDataKey(e, t, l, r, i);
1635
+ return serveDataKey(e, t, f, r, i, o);
1400
1636
  },
1401
1637
  has(e, t) {
1402
1638
  if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
1639
+ if (t === $OWNER) return false;
1403
1640
  if (pendingCheckActive) witnessAffectsMark(e, t);
1404
1641
  if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1405
1642
  const n = readSource(e);
@@ -1415,11 +1652,11 @@ const traps = {
1415
1652
  if (hasActiveOverride(n)) r = !!i;
1416
1653
  } else if (!authoritativeServe()) {
1417
1654
  const n = e.h?.[t];
1418
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1655
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.be);
1419
1656
  }
1420
- } else if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1657
+ } else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1421
1658
  const n = e.h?.[t];
1422
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1659
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.be);
1423
1660
  }
1424
1661
  return r;
1425
1662
  },
@@ -1427,66 +1664,17 @@ const traps = {
1427
1664
  if (pendingCheckActive) witnessAffectsMark(e);
1428
1665
  if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1429
1666
  if (!inDraft(e) && getObserver() !== null) read(getKeySetNode(e));
1430
- const t = readSource(e);
1431
- let n;
1432
- if (e.ovl && t === e.pb) {
1433
- // Overlay merge (#3044): committed keys in their order, then this
1434
- // batch's NEW keys, minus deletes.
1435
- n = Reflect.ownKeys(e.v);
1436
- const r = e.del;
1437
- if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
1438
- for (const r of Reflect.ownKeys(t)) {
1439
- if (!hasOwn.call(e.v, r)) n.push(r);
1440
- }
1441
- } else n = Reflect.ownKeys(t);
1442
- // Optimistic membership overlay: presence-node overrides add/remove keys
1443
- // (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
1444
- // Draft reads before the first write overlay too (pb, once created, is
1445
- // seeded with the view). Authoritative-view reads (until()'s predicate,
1446
- // truth-author drafts) skip the overlay.
1447
- if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
1448
- let t = null;
1449
- for (const r of Reflect.ownKeys(e.h)) {
1450
- const i = e.h[r];
1451
- if (!hasActiveOverride(i)) continue;
1452
- t ??= new Set(n);
1453
- if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
1454
- }
1455
- if (t !== null) return [ ...t ];
1456
- }
1457
- return n;
1667
+ return visibleKeys(e, readSource(e));
1458
1668
  },
1459
1669
  getOwnPropertyDescriptor(e, t) {
1460
- const n = readSource(e);
1461
- let r = Object.getOwnPropertyDescriptor(n, t);
1462
- // Overlay (#3044): unwritten keys live on the committed backing;
1463
- // deleted keys are absent from the pending view.
1464
- if (e.ovl && n === e.pb) {
1465
- if (e.del !== null && e.del.has(t)) return undefined;
1466
- if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
1467
- }
1468
- if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1469
- const n = e.h?.[t];
1470
- if (n !== undefined && hasActiveOverride(n)) {
1471
- if (!unwrapOverride(n.o?.Pe)) return undefined;
1472
- // opt delete
1473
- if (r === undefined) {
1474
- const n = e.n?.[t];
1475
- return {
1476
- value: n !== undefined ? nodeValue(n, undefined) : undefined,
1477
- writable: true,
1478
- enumerable: true,
1479
- configurable: true
1480
- };
1481
- }
1482
- }
1483
- }
1484
- if (r === undefined) return undefined;
1670
+ if (t === $OWNER) return undefined;
1671
+ const n = visibleDescriptor(e, readSource(e), t);
1672
+ if (n === undefined) return undefined;
1485
1673
  // Array targets carry a real non-configurable `length` the proxy
1486
1674
  // invariant forces us to report faithfully; everything else reports
1487
1675
  // configurable via target indirection (core R51).
1488
- if (!(t === "length" && Array.isArray(e))) r.configurable = true;
1489
- return r;
1676
+ if (!(t === "length" && Array.isArray(e))) n.configurable = true;
1677
+ return n;
1490
1678
  },
1491
1679
  set(e, t, n) {
1492
1680
  // Writes require the target's draft scope OR the projection write
@@ -1509,14 +1697,20 @@ const traps = {
1509
1697
  // Array length writes implicitly delete indices — the written-keys bound
1510
1698
  // can't see them, so poison to the full scan for this batch. Index
1511
1699
  // writes implicitly GROW length, so arrays always record it alongside.
1512
- const f = pcOf(e);
1513
- if (Array.isArray(l)) {
1514
- if (t === "length") f.wk = WK_ALL; else if (f.wk !== WK_ALL) {
1515
- const e = f.wk ??= new Set;
1516
- e.add(t);
1517
- e.add("length");
1700
+ if (Array.isArray(l)) {
1701
+ if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1702
+ const n = e.wk ??= new Set;
1703
+ n.add(t);
1704
+ n.add("length");
1518
1705
  }
1519
- } else if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
1706
+ } else {
1707
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1708
+ // Live own-key estimate for the overlay/clone choice (#3360): `in`
1709
+ // sees through an overlay to the committed keys, so this counts keys
1710
+ // NEW to the container. Deletes are not un-counted (a stale high
1711
+ // count only picks the overlay a little early).
1712
+ if (!(t in l)) e.kc++;
1713
+ }
1520
1714
  // Own data keys literally named "prototype"/"constructor" land as data —
1521
1715
  // defineProperty sidesteps a proto-chain setter named the same.
1522
1716
  if (UNSAFE_KEYS.has(t)) {
@@ -1531,8 +1725,10 @@ const traps = {
1531
1725
  }
1532
1726
  // Overlay first-write DEFINES the own key: assignment through the proto
1533
1727
  // chain would reject on a non-writable committed property (the clone
1534
- // path normalized descriptors for exactly this — R51 parity).
1535
- if (e.ovl && !hasOwn.call(l, t)) {
1728
+ // path normalized descriptors for exactly this — R51 parity). A
1729
+ // plain-data-graded backing (sc 2, owned: every slot writable) takes the
1730
+ // bare assignment — it lands as an own key on the overlay all the same.
1731
+ if (e.ovl && e.sc !== 2 && !hasOwn.call(l, t)) {
1536
1732
  Object.defineProperty(l, t, {
1537
1733
  value: o,
1538
1734
  writable: true,
@@ -1554,23 +1750,19 @@ const traps = {
1554
1750
  const i = !r && getWriteOverride();
1555
1751
  if (!r && !i) return true;
1556
1752
  if (t === "__proto__") return true;
1557
- if (n.get || n.set) {
1558
- e.a = true;
1559
- // Accessor demotion (re-audit blocker 3): a record that acquires an
1560
- // accessor after patch registration stops being patchable — pull its
1561
- // patches and re-drive them as tracked effect fallbacks. Hooks are
1562
- // installed whenever pc.p exists (registration installs them).
1563
- if (e.pc !== null && e.pc.p !== null) patchHooks.demoteToEffects(e);
1564
- }
1753
+ if (n.get || n.set) e.a = true;
1565
1754
  // Unwrap before ensurePB (see the set trap: self-reference materializes).
1566
1755
  if ("value" in n) n = {
1567
1756
  ...n,
1568
1757
  value: unwrapValue(n.value)
1569
1758
  };
1570
1759
  const o = ensurePB(e);
1760
+ // A non-default data descriptor (or an accessor) leaves the plain-data
1761
+ // grade: the key reaches the committed backing as defined, so the spread
1762
+ // clone and bare-assignment paths no longer describe it.
1763
+ if (e.a || !(n.enumerable && n.writable && n.configurable)) e.sc = 1;
1571
1764
  pendingNotify.add(e);
1572
- const l = pcOf(e);
1573
- if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
1765
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1574
1766
  Object.defineProperty(o, t, n);
1575
1767
  if (e.del !== null) e.del.delete(t);
1576
1768
  if (i) notifyWrites(e);
@@ -1582,8 +1774,7 @@ const traps = {
1582
1774
  if (!n && !r) return true;
1583
1775
  const i = ensurePB(e);
1584
1776
  pendingNotify.add(e);
1585
- const o = pcOf(e);
1586
- if (o.wk !== WK_ALL) (o.wk ??= new Set).add(t);
1777
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1587
1778
  delete i[t];
1588
1779
  // A prototype overlay cannot shadow a delete of a committed key —
1589
1780
  // record it aside (#3044); reads/has/ownKeys/commit consult the set.
@@ -1631,6 +1822,7 @@ const traps = {
1631
1822
  adoptPB(r, unwrapValue(o));
1632
1823
  }
1633
1824
  }
1825
+ if (writing === 0 && heldAdoptions.size) stageHeldAdoptions();
1634
1826
  }
1635
1827
 
1636
1828
  // Affects integration: the legacy affects machinery reads next targets
@@ -1647,6 +1839,10 @@ function createStoreNext(e, t = false) {
1647
1839
  return [ n, setter ];
1648
1840
  }
1649
1841
 
1842
+ // ---------------------------------------------------------------------------
1843
+ // snapshot (next targets): the backing IS the plain raw graph — zero copy.
1844
+ // Sees pending (R27) by reading pb. Chained/owned-copy caching lands with the
1845
+ // utilities increment; this covers the createStore-suite contract.
1650
1846
  /** True when `proxy` is a SHALLOW store (children served verbatim, slots
1651
1847
  * replaced by reference — #2932). The list driver uses this to choose the
1652
1848
  * slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
@@ -1678,6 +1874,78 @@ function createStoreNext(e, t = false) {
1678
1874
  return t !== undefined && t.fam?.opt === true;
1679
1875
  }
1680
1876
 
1877
+ // ---------------------------------------------------------------------------
1878
+ // visibility: what a reader sees on a record — ONE rule for the ownKeys /
1879
+ // getOwnPropertyDescriptor traps and the deep() walk (#3323). The walk used
1880
+ // to re-derive the trap rules over raw backings and missed each new one in
1881
+ // turn (the #3044 overlay merge, then #3323's chain, then optimistic presence
1882
+ // and value overrides); sharing the body makes parity structural.
1883
+ /** Keys visible on `target` served from `src` (= readSource(target)): the
1884
+ * pending-overlay merge (#3044) and, on optimistic families, presence-node
1885
+ * overrides (§6, FINDING-2's fix). Draft reads before the first write overlay
1886
+ * too (pb, once created, is seeded with the view). Authoritative-view reads
1887
+ * (until()'s predicate, truth-author drafts) skip the overlay. */ function visibleKeys(e, t) {
1888
+ let n;
1889
+ if (e.ovl && t === e.pb) {
1890
+ // Overlay merge (#3044): committed keys in their order, then this
1891
+ // batch's NEW keys, minus deletes.
1892
+ n = Reflect.ownKeys(e.v);
1893
+ const r = e.del;
1894
+ if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
1895
+ for (const r of Reflect.ownKeys(t)) {
1896
+ if (!hasOwn.call(e.v, r)) n.push(r);
1897
+ }
1898
+ } else n = Reflect.ownKeys(t);
1899
+ // Drop the $OWNER stamp (owned backings carry it as an own enumerable
1900
+ // symbol). Symbols enumerate last, so the scan stops at the first string.
1901
+ for (let e = n.length - 1; e >= 0 && typeof n[e] === "symbol"; e--) {
1902
+ if (n[e] === $OWNER) {
1903
+ n.splice(e, 1);
1904
+ break;
1905
+ }
1906
+ }
1907
+ if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
1908
+ let t = null;
1909
+ for (const r of Reflect.ownKeys(e.h)) {
1910
+ const i = e.h[r];
1911
+ if (!hasActiveOverride(i)) continue;
1912
+ t ??= new Set(n);
1913
+ if (unwrapOverride(i.o?.be)) t.add(r); else t.delete(r);
1914
+ }
1915
+ if (t !== null) return [ ...t ];
1916
+ }
1917
+ return n;
1918
+ }
1919
+
1920
+ /** The data/accessor descriptor visible for `key` on `target` served from
1921
+ * `src`: overlay (#3044 — unwritten keys live on the committed backing,
1922
+ * deleted keys are absent) and optimistic presence overrides (an opt delete
1923
+ * hides the key; an opt add synthesizes a data descriptor from the value
1924
+ * node). `configurable` is the trap's concern (proxy invariant). */ function visibleDescriptor(e, t, n) {
1925
+ let r = Object.getOwnPropertyDescriptor(t, n);
1926
+ if (e.ovl && t === e.pb) {
1927
+ if (e.del !== null && e.del.has(n)) return undefined;
1928
+ if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, n);
1929
+ }
1930
+ if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1931
+ const t = e.h?.[n];
1932
+ if (t !== undefined && hasActiveOverride(t)) {
1933
+ if (!unwrapOverride(t.o?.be)) return undefined;
1934
+ // opt delete
1935
+ if (r === undefined) {
1936
+ const t = e.n?.[n];
1937
+ return {
1938
+ value: t !== undefined ? nodeValue(t, undefined) : undefined,
1939
+ writable: true,
1940
+ enumerable: true,
1941
+ configurable: true
1942
+ };
1943
+ }
1944
+ }
1945
+ }
1946
+ return r;
1947
+ }
1948
+
1681
1949
  /** Tracking deep snapshot (`deep()` for next targets): subscribes to the
1682
1950
  * key-set and deep-witness node at every reachable level, then returns the
1683
1951
  * plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
@@ -1689,34 +1957,69 @@ function createStoreNext(e, t = false) {
1689
1957
  // in per-key nodes, and it walks TARGETS directly — no per-child proxy
1690
1958
  // round-trip (wrapNext → proxy → $TARGET trap) on the re-walk every
1691
1959
  // effect run performs.
1692
- const walkT = e => {
1960
+ // Resolve `child` (a raw or a stored proxy found under `t`) to the target
1961
+ // that `t`'s family serves for it — created on first visit, as the get trap
1962
+ // would. Undefined = leaf (unwrappable or raw-marked).
1963
+ const childTarget = (e, t, n) => {
1964
+ let r = lookupTarget(t, e.fam);
1965
+ if (r === undefined) {
1966
+ if (!isWrappable(t)) return undefined;
1967
+ wrapNext(t, e, n);
1968
+ // Stored proxies (chained slots) that this family passes through
1969
+ // resolve to their own target.
1970
+ r = lookupTarget(t, e.fam) ?? t[$TARGET];
1971
+ }
1972
+ return r;
1973
+ };
1974
+ const walkT = e => {
1693
1975
  const t = readSource(e);
1694
1976
  if (n.has(t)) return;
1695
1977
  n.add(t);
1696
1978
  read(getKeySetNode(e));
1697
1979
  read(getDeepNode(e));
1698
- const r = e.fam?.map ?? storeNextLookup;
1699
- for (const n of Reflect.ownKeys(t)) {
1700
- const i = Object.getOwnPropertyDescriptor(t, n);
1980
+ // Chained backing (§7b): the committed backing IS another store's proxy.
1981
+ // Writes to the inner store bump the INNER record's witnesses; this
1982
+ // target's k/dk hear only its own family's writes (optimistic overrides).
1983
+ // Read through the whole chain — the $TRACK trap's rule (#2864 / R21)
1984
+ // applied to deep() (#3323). From `t.v`, not `src`: a pending backing is a
1985
+ // raw clone and would hide the chain. Non-chained targets pay one flag.
1986
+ for (let t = e; t.ch; ) {
1987
+ t = t.v[$TARGET];
1988
+ read(getKeySetNode(t));
1989
+ read(getDeepNode(t));
1990
+ }
1991
+ // Keys and children exactly as the traps serve them — the overlay merge
1992
+ // (#3044/#3283: a bare ownKeys over a pending backing mid-flush dropped
1993
+ // every untouched child from the re-subscribing effect's dependencies)
1994
+ // and optimistic presence overrides (a row added under a held action
1995
+ // lives in `h`/`n`, not the committed backing; the walk never reached its
1996
+ // record, so deep() was deaf to every write on it until settle).
1997
+ const r = e.fam?.opt === true;
1998
+ for (const n of visibleKeys(e, t)) {
1999
+ const i = visibleDescriptor(e, t, n);
1701
2000
  if (i === undefined) continue;
1702
2001
  if (i.get || i.set) {
1703
2002
  e.a = true;
1704
2003
  continue;
1705
2004
  // accessors track through their own reads when invoked
1706
2005
  }
1707
- const o = i.value;
2006
+ let o = i.value;
2007
+ // An active value override on an optimistic node shadows the backing's
2008
+ // child (an optimistic replacement `d[i] = {...}`) — follow what reads
2009
+ // serve, as nodeValue's leading arm does.
2010
+ if (r) {
2011
+ const t = e.n?.[n];
2012
+ if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) o = unwrapOverride(t.o.be);
2013
+ }
1708
2014
  if (o === null || typeof o !== "object") continue;
1709
- // Stored proxies (chained slots) resolve through their own target;
1710
- // raw children through the family map, created on first visit.
1711
- let l = o[$TARGET] ?? r.get(o);
1712
- if (l === undefined) {
1713
- if (!isWrappable(o)) continue;
1714
- wrapNext(o, e, n);
1715
- l = r.get(o);
1716
- if (l === undefined) continue;
2015
+ // Through a chain the descriptor yields the INNERMOST raw: resolve it to
2016
+ // the inner family's proxy so this level wraps the chained `view[i]` the
2017
+ // get trap serves, never a fresh non-chained wrapper of the base raw.
2018
+ if (e.ch && o[$TARGET] === undefined) o = resolveChainedRaw(e, n, o);
2019
+ const l = childTarget(e, o, n);
2020
+ if (l === undefined) continue;
1717
2021
  // raw-marked: leaf by contract
1718
- }
1719
- walkT(l);
2022
+ walkT(l);
1720
2023
  }
1721
2024
  };
1722
2025
  walkT(t);
@@ -1746,24 +2049,35 @@ function snapshotWalk(e, t, n) {
1746
2049
  // encountered and compose their views over the resolved base, innermost
1747
2050
  // outward.
1748
2051
  let i = null;
1749
- for (;;) {
1750
- let e = r?.[$TARGET]?.v !== undefined ? r[$TARGET] : undefined;
1751
- if (e === undefined && n !== null) e = n.map.get(r);
1752
- if (e === undefined) e = storeNextLookup.get(r);
1753
- if (e === undefined) break;
1754
- if (e.fam !== null) n = e.fam;
1755
- if (e.fam?.opt === true) (i ??= []).push(e);
2052
+ for (let e = true; ;e = false) {
2053
+ const t = r?.[$TARGET]?.v !== undefined;
2054
+ let o = t ? r[$TARGET] : undefined;
2055
+ if (o === undefined && n !== null) o = lookupTarget(r, n);
2056
+ if (o === undefined) o = lookupTarget(r, null);
2057
+ if (o === undefined) break;
2058
+ // Entering a level from a RAW below a chained family: the raw resolves to
2059
+ // the INNER store's target, but this family's wrapper for it — keyed by
2060
+ // the inner proxy, §7b — is where the outer overrides live. Start from
2061
+ // the wrapper so they compose (#3323: a nested optimistic write on a view
2062
+ // row was served by reads but missing from deep()/snapshot()). Entry only:
2063
+ // the descent then runs wrapper → inner → raw and terminates.
2064
+ if (e && !t && n !== null && o.fam !== n) {
2065
+ const e = n.map.get(o.px);
2066
+ if (e !== undefined) o = e;
2067
+ }
2068
+ if (o.fam !== null) n = o.fam;
2069
+ if (o.fam?.opt === true) (i ??= []).push(o);
1756
2070
  // The shared visibility decision (#3147): the speculative peek serves
1757
2071
  // pending staging, but a HELD landing is masked to committed exactly as
1758
2072
  // it is for per-key readers — the two families must answer alike while
1759
2073
  // a transaction holds store landings.
1760
- const t = pendingBackingVisible(e, true);
2074
+ const l = pendingBackingVisible(o, true);
1761
2075
  // Snapshot runs mid-flush (tracked memos execute before commit), so a
1762
2076
  // pending prototype overlay must present as a REAL merged container.
1763
- if (t && e.ovl) materializePB(e);
1764
- const o = t ? e.pb : e.v;
1765
- if (o === r) break;
1766
- r = o;
2077
+ if (l && o.ovl) materializePB(o);
2078
+ const f = l ? o.pb : o.v;
2079
+ if (f === r) break;
2080
+ r = f;
1767
2081
  }
1768
2082
  if (!isWrappable(r)) return r;
1769
2083
  // Optimistic families: compose the visible view; a composed view is a fresh
@@ -1778,7 +2092,7 @@ function snapshotWalk(e, t, n) {
1778
2092
  const l = o ? [] : Object.create(Object.getPrototypeOf(e));
1779
2093
  t.set(r, l);
1780
2094
  for (const r of Reflect.ownKeys(e)) {
1781
- if (o && r === "length") continue;
2095
+ if (o && r === "length" || r === $OWNER) continue;
1782
2096
  const i = e[r];
1783
2097
  l[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1784
2098
  }
@@ -1792,12 +2106,12 @@ function snapshotWalk(e, t, n) {
1792
2106
  // subtrees "unmodified relative to source"): non-enumerable symbols are
1793
2107
  // excluded (recon-snap R29), and the copy registers BEFORE descent so
1794
2108
  // cycles keep identity (FINDING-3).
1795
- if (ownedRaw.has(r)) {
2109
+ if (isOwned(r)) {
1796
2110
  const e = Array.isArray(r);
1797
2111
  const i = e ? [] : Object.create(Object.getPrototypeOf(r));
1798
2112
  t.set(r, i);
1799
2113
  for (const o of Reflect.ownKeys(r)) {
1800
- if (e && o === "length") continue;
2114
+ if (e && o === "length" || o === $OWNER) continue;
1801
2115
  const l = Object.getOwnPropertyDescriptor(r, o);
1802
2116
  if (typeof o === "symbol" && !l.enumerable) continue;
1803
2117
  if (l.get || l.set) {
@@ -1805,10 +2119,10 @@ function snapshotWalk(e, t, n) {
1805
2119
  continue;
1806
2120
  }
1807
2121
  const f = l.value;
1808
- const u = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
1809
- if (l.enumerable && l.writable && l.configurable) i[o] = u; else Object.defineProperty(i, o, {
2122
+ const s = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
2123
+ if (l.enumerable && l.writable && l.configurable) i[o] = s; else Object.defineProperty(i, o, {
1810
2124
  ...l,
1811
- value: u
2125
+ value: s
1812
2126
  });
1813
2127
  }
1814
2128
  if (e && i.length !== r.length) i.length = r.length;
@@ -1835,4 +2149,4 @@ function snapshotWalk(e, t, n) {
1835
2149
  return l ?? r;
1836
2150
  }
1837
2151
 
1838
- export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, pcOf, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetIsPlain, targetsEqual, unwrapValue, wrapNext };
2152
+ export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, heldMaskView, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, wrapNext };