@solidjs/signals 2.0.0-rc.3 → 2.0.0-rc.5

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 (71) hide show
  1. package/dist/dev.js +2494 -271
  2. package/dist/node.cjs +3691 -1595
  3. package/dist/prod/affects.js +13 -12
  4. package/dist/prod/boundaries.js +43 -35
  5. package/dist/prod/core/action.js +3 -3
  6. package/dist/prod/core/async.js +137 -106
  7. package/dist/prod/core/constants.js +55 -1
  8. package/dist/prod/core/core.js +354 -247
  9. package/dist/prod/core/effect.js +47 -50
  10. package/dist/prod/core/error.js +13 -1
  11. package/dist/prod/core/external.js +4 -4
  12. package/dist/prod/core/graph.js +88 -52
  13. package/dist/prod/core/heap.js +38 -38
  14. package/dist/prod/core/lanes.js +34 -34
  15. package/dist/prod/core/optimistic.js +61 -58
  16. package/dist/prod/core/owner.js +38 -38
  17. package/dist/prod/core/scheduler.js +401 -174
  18. package/dist/prod/core/verdict.js +132 -65
  19. package/dist/prod/index.js +7 -3
  20. package/dist/prod/map.js +106 -106
  21. package/dist/prod/signals.js +253 -25
  22. package/dist/prod/store/index.js +2 -0
  23. package/dist/prod/store/next/optimistic.js +314 -121
  24. package/dist/prod/store/next/patch-hooks.js +13 -0
  25. package/dist/prod/store/next/patch.js +614 -0
  26. package/dist/prod/store/next/projection.js +23 -19
  27. package/dist/prod/store/next/reconcile.js +307 -120
  28. package/dist/prod/store/next/store.js +440 -117
  29. package/dist/prod/store/next/target.js +13 -4
  30. package/dist/prod/store/store.js +5 -5
  31. package/dist/types/core/async.d.ts +2 -0
  32. package/dist/types/core/attribution.d.ts +9 -4
  33. package/dist/types/core/constants.d.ts +54 -0
  34. package/dist/types/core/core.d.ts +34 -21
  35. package/dist/types/core/dev.d.ts +8 -0
  36. package/dist/types/core/error.d.ts +9 -0
  37. package/dist/types/core/graph.d.ts +22 -0
  38. package/dist/types/core/index.d.ts +2 -2
  39. package/dist/types/core/scheduler.d.ts +46 -0
  40. package/dist/types/core/types.d.ts +12 -0
  41. package/dist/types/index.d.ts +3 -3
  42. package/dist/types/signals.d.ts +108 -0
  43. package/dist/types/store/index.d.ts +2 -0
  44. package/dist/types/store/next/optimistic.d.ts +13 -10
  45. package/dist/types/store/next/patch-hooks.d.ts +41 -0
  46. package/dist/types/store/next/patch.d.ts +91 -0
  47. package/dist/types/store/next/projection.d.ts +1 -1
  48. package/dist/types/store/next/reconcile.d.ts +14 -0
  49. package/dist/types/store/next/store.d.ts +52 -2
  50. package/dist/types/store/next/target.d.ts +73 -8
  51. package/dist/types-cjs/core/async.d.cts +2 -0
  52. package/dist/types-cjs/core/attribution.d.cts +9 -4
  53. package/dist/types-cjs/core/constants.d.cts +54 -0
  54. package/dist/types-cjs/core/core.d.cts +34 -21
  55. package/dist/types-cjs/core/dev.d.cts +8 -0
  56. package/dist/types-cjs/core/error.d.cts +9 -0
  57. package/dist/types-cjs/core/graph.d.cts +22 -0
  58. package/dist/types-cjs/core/index.d.cts +2 -2
  59. package/dist/types-cjs/core/scheduler.d.cts +46 -0
  60. package/dist/types-cjs/core/types.d.cts +12 -0
  61. package/dist/types-cjs/index.d.cts +3 -3
  62. package/dist/types-cjs/signals.d.cts +108 -0
  63. package/dist/types-cjs/store/index.d.cts +2 -0
  64. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  65. package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
  66. package/dist/types-cjs/store/next/patch.d.cts +91 -0
  67. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  68. package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
  69. package/dist/types-cjs/store/next/store.d.cts +52 -2
  70. package/dist/types-cjs/store/next/target.d.cts +73 -8
  71. package/package.json +2 -2
@@ -1,14 +1,16 @@
1
- import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN } from "../../core/constants.js";
1
+ import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
2
2
 
3
- import { setSignal, isEqual, read, pendingCheckActive, readNodeFast, READ_SLOW, signal, ext } from "../../core/core.js";
3
+ import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, context } from "../../core/core.js";
4
4
 
5
- import { projectionWriteActive, setProjectionWriteActive, schedule, setStoreCommitHook } from "../../core/scheduler.js";
5
+ import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
6
6
 
7
7
  import { getObserver, getOwner } from "../../core/owner.js";
8
8
 
9
9
  import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
10
10
 
11
- import { storeNextLookup, optHooks, ownedRaw, devAssertNeverUserMutation } from "./target.js";
11
+ import { storeNextLookup, optHooks, ownedRaw, markDescendants, devAssertNeverUserMutation } from "./target.js";
12
+
13
+ import { patchHooks, rowHooks } from "./patch-hooks.js";
12
14
 
13
15
  /**
14
16
  * Store rewrite — increment 2: plain deep stores with pending-backing writes.
@@ -38,8 +40,13 @@ import { storeNextLookup, optHooks, ownedRaw, devAssertNeverUserMutation } from
38
40
  * headroom for future fields. The prototype is reset to `Object.prototype`
39
41
  * so proxy-forwarded semantics (getPrototypeOf, constructor) are exactly a
40
42
  * plain object's. Array targets keep the bare-`[]` path — they must carry
41
- * the array exotic class for `Array.isArray(proxy)`, and arrays store named
42
- * fields off-object where this cliff does not apply. */
43
+ * the array exotic class for `Array.isArray(proxy)`.
44
+ *
45
+ * ARRAY SHAPE RULE: arrays normalize their named properties to dictionary
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. */
43
50
  function TargetShape() {
44
51
  this.v = undefined;
45
52
  this.ch = undefined;
@@ -60,11 +67,24 @@ function TargetShape() {
60
67
  this.s = undefined;
61
68
  this.ovl = undefined;
62
69
  this.del = undefined;
63
- this.wk = undefined;
70
+ this.pc = undefined;
71
+ this.hv = undefined;
72
+ this.ht = undefined;
64
73
  }
65
74
 
66
75
  TargetShape.prototype = Object.prototype;
67
76
 
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
+
68
88
  function createTarget(e, t, n, r = t?.fam ?? null) {
69
89
  // The proxy target carries the array exotic class when the value is an
70
90
  // array, so Array.isArray(proxy) is true; the fields live on it directly.
@@ -81,6 +101,7 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
81
101
  i.h = null;
82
102
  i.k = null;
83
103
  i.dk = null;
104
+ i.pc = null;
84
105
  i.u = t;
85
106
  i.pk = n;
86
107
  i.px = null;
@@ -93,7 +114,8 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
93
114
  i.s = false;
94
115
  i.ovl = false;
95
116
  i.del = null;
96
- i.wk = null;
117
+ i.hv = null;
118
+ i.ht = null;
97
119
  i.px = new Proxy(i, traps);
98
120
  // Legacy interop: shared machinery (affects walks, wrap dedupe) reads the
99
121
  // proxy off looked-up targets as a field.
@@ -171,7 +193,7 @@ function getNode(e, t, n) {
171
193
  // Optimistic families: arm the override slot — setSignal routes armed
172
194
  // nodes through the core engine (lanes, ownership, reverts all native).
173
195
  if (e.fam?.opt) {
174
- ext(o).De = NOT_PENDING;
196
+ ext(o).Oe = NOT_PENDING;
175
197
  o.T |= CONFIG_OPTIMISTIC;
176
198
  }
177
199
  // A node born inside a live mark's identity scope inherits the mark
@@ -204,7 +226,7 @@ function getHasNode(e, t, n) {
204
226
  }, e.fam?.node ?? undefined);
205
227
  o.T |= CONFIG_OWNED_WRITE;
206
228
  if (e.fam?.opt) {
207
- ext(o).De = NOT_PENDING;
229
+ ext(o).Oe = NOT_PENDING;
208
230
  o.T |= CONFIG_OPTIMISTIC;
209
231
  }
210
232
  if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
@@ -225,7 +247,7 @@ function getKeySetNode(e) {
225
247
  }, e.fam?.node ?? undefined);
226
248
  n.T |= CONFIG_OWNED_WRITE;
227
249
  if (e.fam?.opt) {
228
- ext(n).De = NOT_PENDING;
250
+ ext(n).Oe = NOT_PENDING;
229
251
  n.T |= CONFIG_OPTIMISTIC;
230
252
  }
231
253
  e.k = t;
@@ -245,7 +267,7 @@ function getDeepNode(e) {
245
267
  }, e.fam?.node ?? undefined);
246
268
  n.T |= CONFIG_OWNED_WRITE;
247
269
  if (e.fam?.opt) {
248
- ext(n).De = NOT_PENDING;
270
+ ext(n).Oe = NOT_PENDING;
249
271
  n.T |= CONFIG_OPTIMISTIC;
250
272
  }
251
273
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
@@ -260,14 +282,6 @@ function getDeepNode(e) {
260
282
  if (e.dk !== null) setSignal(e.dk, 1);
261
283
  }
262
284
 
263
- function markDescendants(e) {
264
- let t = e;
265
- while (t && !t.d) {
266
- t.d = true;
267
- t = t.u;
268
- }
269
- }
270
-
271
285
  // ---------------------------------------------------------------------------
272
286
  // pending backing + fold (the single mutation point)
273
287
  /** target → committed backing at batch start (the fold diff's old side). */ const foldOlds = new Map;
@@ -291,6 +305,12 @@ function cloneRaw(e, t) {
291
305
  return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
292
306
  }
293
307
 
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);
312
+ }
313
+
294
314
  /** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
295
315
  * returns true when the container is plain data (overlay-safe). */ function scanAccessorsOnce(e) {
296
316
  const t = e.v;
@@ -331,6 +351,21 @@ function cloneRaw(e, t) {
331
351
 
332
352
  function ensurePB(e) {
333
353
  let t = e.pb;
354
+ // Truth-staged backing hand-off (#3164 fold): a TENTATIVE draft opening on
355
+ // a target whose pending backing is truth-staged (a landing folded into a
356
+ // retaining transaction — it carries a foldBatches stamp) must not share
357
+ // the container. Tentative writes would pollute staged truth, and the
358
+ // tentative discard (notifyOptimisticWrites nulls pb) would destroy the
359
+ // landing. Park the staged backing and open a fresh draft seeded from the
360
+ // optimistic view below; the tentative discard restores it. The
361
+ // tentativePBs guard scopes this to draft OPEN: the draft's own backing
362
+ // (foldBatches-stamped by its first write when an action's transition is
363
+ // ambient) must not be parked by its own later writes.
364
+ if (t !== null && !tentativePBs.has(t) && e.fam?.opt === true && !projectionWriteActive && !getWriteOverride() && foldBatches.has(e)) {
365
+ stagedTruthPB.set(e, t);
366
+ t = e.pb = null;
367
+ }
368
+ if (activeTransition !== null) foldBatches.set(e, activeTransition);
334
369
  if (t === null) {
335
370
  // Prototype-chain overlay (#3044): plain-data non-array containers
336
371
  // outside projection/optimistic families open drafts in O(1) — own keys
@@ -348,18 +383,19 @@ function ensurePB(e) {
348
383
  // seed from committed truth — seeding overrides there would fold a lane
349
384
  // value into the committed home ("authority wins at reveal" would break).
350
385
  if (e.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
386
+ tentativePBs.add(t);
351
387
  const n = e.n;
352
388
  if (n !== null) {
353
389
  for (const e of Reflect.ownKeys(n)) {
354
390
  const r = n[e];
355
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.De);
391
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Oe);
356
392
  }
357
393
  }
358
394
  const r = e.h;
359
395
  if (r !== null) {
360
396
  for (const e of Reflect.ownKeys(r)) {
361
397
  const n = r[e];
362
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.De)) delete t[e];
398
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.Oe)) delete t[e];
363
399
  }
364
400
  }
365
401
  }
@@ -370,6 +406,25 @@ function ensurePB(e) {
370
406
  return t;
371
407
  }
372
408
 
409
+ /** Sentinel holder for `t.ht`: a latest()-pull staged this adoption outside
410
+ * any transition — the hold lasts until the fold commit (drainFolds). */ const PLAIN_HOLD = Symbol("plainHold");
411
+
412
+ /** True while a latest() read is pulling the projection computed up to date
413
+ * (see the get trap): adoptions landing during the pull are speculative
414
+ * against the un-flushed batch and stage a held view. (Not injectable — the
415
+ * derived createStore overload retains projection machinery in every store
416
+ * bundle, see treeshake.test.ts.) */ let latestPullActive = false;
417
+
418
+ /** Resolve the held committed view (#3074): answers the masked old backing
419
+ * while the hold is live, and lazily clears a hold whose transition has
420
+ * committed (transitions merge — resolve through currentTransition, same as
421
+ * foldHeld's node stamps). */ function heldMaskView(e) {
422
+ const t = e.ht;
423
+ if (t === null) return null;
424
+ if (t !== PLAIN_HOLD && currentTransition(t)?.sn === true) return e.ht = e.hv = null;
425
+ return e.hv;
426
+ }
427
+
373
428
  /**
374
429
  * Adoption (2026-08-16c): the incoming object becomes the committed backing
375
430
  * IMMEDIATELY — reconcile is eagerly visible to every reader (shipped
@@ -385,6 +440,20 @@ function ensurePB(e) {
385
440
  queueFold(e);
386
441
  // records the pre-batch old before we swap
387
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
+ }
388
457
  }
389
458
  e.pb = null;
390
459
  // Overlay and accessor-scan state describe the OUTGOING backing — a
@@ -396,28 +465,58 @@ function ensurePB(e) {
396
465
  // draft rescans once (#3044 audit follow-up).
397
466
  e.ovl = false;
398
467
  e.del = null;
399
- e.wk = null;
400
- // adoption supersedes any staged trap writes
401
- e.sc = false;
468
+ e.sc = false;
402
469
  e.a = false;
403
- e.v = t;
470
+ if (e.pc !== null) e.pc.wk = null;
471
+ // adoption supersedes staged trap writes
472
+ e.v = t;
404
473
  e.ch = t[$TARGET] !== undefined;
405
474
  (e.fam?.map ?? storeNextLookup).set(t, e);
406
475
  }
407
476
 
477
+ /** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
478
+ * array length write implicitly deleted indices) — consumers full-scan. */ const WK_ALL = new Set;
479
+
480
+ const plainProto = e => {
481
+ const t = Object.getPrototypeOf(e);
482
+ return t === Object.prototype || t === Array.prototype || t === null;
483
+ };
484
+
408
485
  function queueFold(e) {
409
486
  if (foldOlds.has(e)) return;
410
- if (foldOlds.size === 0) {
411
- if (!hookInstalled) {
412
- hookInstalled = true;
413
- setStoreCommitHook(drainFolds);
414
- }
487
+ if (!hookInstalled) {
488
+ hookInstalled = true;
489
+ setStoreCommitHook(drainFolds);
490
+ }
491
+ // Always arm — "map non-empty ⇒ drain scheduled" is NOT an invariant: a
492
+ // held re-queue, or an incomplete-transition flush (which skips
493
+ // commitPendingNodes entirely), leaves entries behind after `scheduled`
494
+ // was consumed. A size-gated arm then strands every LATER fold — queued
495
+ // silently, never drained, committed base frozen at stale state while its
496
+ // nodes commit (#3089). schedule() early-returns when already armed.
415
497
  schedule();
416
- // once per batch — drain clears the map
417
- }
418
498
  foldOlds.set(e, e.v);
419
499
  }
420
500
 
501
+ /** Fold write-attribution (#3089): a draft written while a transition is
502
+ * active belongs to that transition — its fold must not commit before the
503
+ * transition settles. Observed keys already defer through the held check in
504
+ * drainFolds (their nodes carry _pendingValue); this write-time stamp is the
505
+ * equivalent hold for UNOBSERVED keys, which have no node to consult.
506
+ * Refreshed on every write; resolved through currentTransition at drain
507
+ * (transitions merge — same rule as heldMaskView). */ const foldBatches = new WeakMap;
508
+
509
+ /** Parked truth-staged pending backings (#3164 fold): a tentative draft that
510
+ * opens while a folded landing's backing is live moves the staged container
511
+ * here (see ensurePB); the tentative discard in notifyOptimisticWrites
512
+ * restores it in place of the usual null. */ const stagedTruthPB = new WeakMap;
513
+
514
+ /** Backings opened by TENTATIVE drafts (optimistic user setters): ensurePB's
515
+ * truth-park must not fire against the draft's own container on its second
516
+ * and later writes (the first write stamps foldBatches whenever an action's
517
+ * transition is ambient). Entries die with their draft — tentative backings
518
+ * are consumed at setter exit. */ const tentativePBs = new WeakSet;
519
+
421
520
  /** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
422
521
  if (ownedRaw.has(e.v)) return;
423
522
  const t = cloneRaw(e.v, e);
@@ -437,29 +536,52 @@ function drainFolds() {
437
536
  const e = [ ...foldOlds ];
438
537
  foldOlds.clear();
439
538
  for (const [t, n] of e) {
539
+ // A latest()-pull staging holds only until the fold commit: this flush
540
+ // is committing the batch the pull ran ahead of. Transition holds stay —
541
+ // they clear when their transition is done (heldMaskView).
542
+ 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;
440
548
  if (t.pb !== null) {
549
+ // #3089: a fold written under a still-running transition defers to
550
+ // that transition's settle (the write-time stamp covers unobserved
551
+ // keys; observed keys also hit the pending-node held check below).
552
+ const e = foldBatches.get(t);
553
+ if (e !== undefined) {
554
+ if (currentTransition(e).sn === false) {
555
+ foldOlds.set(t, n);
556
+ continue;
557
+ }
558
+ foldBatches.delete(t);
559
+ }
441
560
  // Setter path: nodes were setSignal'd at setter exit (write-time
442
561
  // notification — transitions/holds ride core machinery). Commit the
443
562
  // backing only for keys whose nodes have committed; a still-pending
444
563
  // node (transition-held) re-queues the target for the settling flush.
445
- let e = false;
446
- const r = t.pb;
447
- const i = t.n;
448
- if (i !== null) {
564
+ let r = false;
565
+ const i = t.pb;
566
+ const o = t.n;
567
+ if (o !== null) {
449
568
  // Only written keys can hold (their nodes took the setSignal); the
450
569
  // wk bound keeps this O(written) — see notifyWrites. Same fallback
451
570
  // rules as the notify (WK_ALL / accessors / non-plain prototypes).
452
- const n = t.wk;
453
- const o = n === null || n === WK_ALL || t.a === true || !plainProto(t.ovl ? t.v : r) ? Reflect.ownKeys(i) : n;
454
- for (const t of o) {
455
- const n = i[t];
456
- if (n !== undefined && n.Pe !== NOT_PENDING) {
457
- e = true;
571
+ const e = t.pc !== null ? t.pc.wk : null;
572
+ const n = e === null || e === WK_ALL || t.a === true ||
573
+ // Overlay pbs chain to the COMMITTED object (#3044) — plainness is
574
+ // the committed container's prototype, not the overlay's.
575
+ !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
576
+ for (const e of n) {
577
+ const t = o[e];
578
+ if (t !== undefined && t.Pe !== NOT_PENDING) {
579
+ r = true;
458
580
  break;
459
581
  }
460
582
  }
461
583
  }
462
- if (e) {
584
+ if (r) {
463
585
  foldOlds.set(t, n);
464
586
  // re-queue: commit happens when the hold settles
465
587
  continue;
@@ -474,30 +596,68 @@ function drainFolds() {
474
596
  // — the never-mutate-user-data contract holds.
475
597
  privatizeCommitted(t);
476
598
  const e = t.v;
477
- for (const t of Reflect.ownKeys(r)) {
478
- const n = Object.getOwnPropertyDescriptor(r, t);
599
+ for (const t of Reflect.ownKeys(i)) {
600
+ const n = Object.getOwnPropertyDescriptor(i, t);
479
601
  if (n.get || n.set || !n.enumerable || !n.writable || !n.configurable) Object.defineProperty(e, t, n); else e[t] = n.value;
480
602
  }
481
603
  if (t.del !== null) {
482
604
  for (const n of t.del) delete e[n];
483
605
  t.del = null;
484
606
  }
485
- (t.fam?.map ?? storeNextLookup).delete(r);
607
+ (t.fam?.map ?? storeNextLookup).delete(i);
486
608
  t.pb = null;
487
609
  t.ovl = false;
488
- t.wk = null;
610
+ if (t.pc !== null) t.pc.wk = null;
489
611
  // written-keys window closes with the fold commit
490
612
  } else {
491
- t.v = r;
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
+ t.v = i;
492
627
  t.ch = false;
493
628
  // pb is always a plain clone
494
629
  t.pb = null;
495
- t.wk = null;
630
+ if (t.pc !== null) t.pc.wk = null;
496
631
  // written-keys window closes with the fold commit
497
632
  }
498
633
  }
499
- if (t.v === n) continue;
500
- // adopted then re-adopted back, or no-op
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);
659
+ }
660
+ }
501
661
  // Path copying (CAS: see the eager-fold twin above).
502
662
  if (t.u && t.u.v[t.pk] === n) {
503
663
  privatizeCommitted(t.u);
@@ -518,19 +678,7 @@ function drainFolds() {
518
678
  * isPending, affects, and lane machinery ride the core natively (§3's
519
679
  * "pending home = the node when a node exists"). Unobserved keys stay in the
520
680
  * pending backing and fold directly at commit.
521
- */
522
- /** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
523
- * array length write implicitly deleted indices) — consumers full-scan. */ const WK_ALL = new Set;
524
-
525
- /** Plain-prototype check for the written-keys bound: prototype getters on
526
- * class instances can derive from ANY field, so only plain-data containers
527
- * may bound the notify to written keys. Overlay pbs chain to the COMMITTED
528
- * object (#3044), so overlay plainness is judged on the committed proto. */ const plainProto = e => {
529
- const t = Object.getPrototypeOf(e);
530
- return t === Object.prototype || t === Array.prototype || t === null;
531
- };
532
-
533
- function notifyWrites(e) {
681
+ */ function notifyWrites(e) {
534
682
  let t = e.pb;
535
683
  if (t === null) return;
536
684
  // Optimistic channel: user writes on an optimistic family become node-level
@@ -567,9 +715,16 @@ function notifyWrites(e) {
567
715
  // not a full scan). Falls back to the full node scan when the bound can't
568
716
  // hold: no trap granularity (wk null), an array length write (WK_ALL —
569
717
  // implicit index deletes), accessors on the record (t.a — a getter node's
570
- // value can change when ANY key is written), or a non-plain prototype.
571
- const i = e.wk;
572
- const o = i === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : t) ? null : i;
718
+ // value can change when ANY key is written), or a non-plain prototype
719
+ // (class instances: prototype getters derive from arbitrary fields).
720
+ const i = e.pc !== null ? e.pc.wk : null;
721
+ // Overlay pbs chain to the COMMITTED object (#3044): a prototype-overlay
722
+ // draft is plain data on its own layer, but its getPrototypeOf is the
723
+ // committed container — judge plainness by the COMMITTED prototype or the
724
+ // bound never engages for overlay writes (every plain-object setter batch
725
+ // would full-scan: the exact selection-map workload wk exists for; jf
726
+ // `select` regressed 2x on this).
727
+ const o = i === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : t) ? null : i;
573
728
  if (r !== null) {
574
729
  const i = o ?? Reflect.ownKeys(r);
575
730
  for (const o of i) {
@@ -600,12 +755,16 @@ function notifyWrites(e) {
600
755
  }
601
756
  const f = e.h;
602
757
  if (f !== null) {
603
- for (const n of Reflect.ownKeys(f)) setSignal(f[n], n in t && !(e.del !== null && e.del.has(n)));
758
+ const n = o ?? Reflect.ownKeys(f);
759
+ for (const r of n) {
760
+ const n = f[r];
761
+ if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
762
+ }
604
763
  }
605
764
  // Deep-witness (dk): setter writes must notify a deep() subscriber even on
606
- // keys with no node. O(pb keys) equality only when a witness exists.
765
+ // keys with no node. O(written/pb keys) equality only when a witness exists.
607
766
  if (e.dk !== null) {
608
- if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of Reflect.ownKeys(t)) {
767
+ if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of o ?? Reflect.ownKeys(t)) {
609
768
  const i = t[r];
610
769
  const o = n[r];
611
770
  if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
@@ -632,6 +791,12 @@ function notifyWrites(e) {
632
791
  }
633
792
  if (r) setSignal(e.k, e => e + 1);
634
793
  }
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);
635
800
  // Projection backing folds split by channel (two pinned contracts):
636
801
  // - sync-derive drafts (recompute body): NEVER eager — a downstream async
637
802
  // hold can form LATER in the same flush and the leaf must stay at stale
@@ -642,12 +807,19 @@ function notifyWrites(e) {
642
807
  // IMMEDIATE — landed truth shows to untracked readers even while a
643
808
  // downstream consumer's own async still holds the effect-level reveal
644
809
  // (spec-async "verdicts never inherit consumers' in-flight state").
645
- if (e.fam !== null && e.pb !== null && getWriteOverride()) {
810
+ // EXCEPT under an active transaction (#3164 fold): a landing riding a
811
+ // retaining transaction (the optimistic module's aroundWrite binds it)
812
+ // stages instead — ensurePB stamped foldBatches, so the backing commits
813
+ // with the transaction and the reveal is atomic at settle. The pinned
814
+ // immediate-commit contract is stated over the no-transaction microtask
815
+ // posture, which `activeTransition === null` is exactly.
816
+ if (e.fam !== null && e.pb !== null && getWriteOverride() && activeTransition === null) {
817
+ // Landed truth (post-await write-override): immediately visible to every
818
+ // reader — any staged held view is superseded.
819
+ if (e.ht !== null) e.ht = e.hv = null;
646
820
  const n = e.v;
647
821
  e.pb = null;
648
- e.wk = null;
649
- // written-keys window closes with the eager fold
650
- e.v = t;
822
+ e.v = t;
651
823
  e.ch = false;
652
824
  if (e.u && e.u.v[e.pk] === n) {
653
825
  privatizeCommitted(e.u);
@@ -710,8 +882,8 @@ i = true) {
710
882
  return;
711
883
  }
712
884
  const f = i?.value;
713
- const u = o?.value;
714
- if (!isEqual(f, u) && !targetsEqual(f, u)) setSignal(e, typeof u === "function" ? () => u : u);
885
+ const l = o?.value;
886
+ if (!isEqual(f, l) && !targetsEqual(f, l)) setSignal(e, typeof l === "function" ? () => l : l);
715
887
  } else {
716
888
  const i = n[t];
717
889
  const o = r[t];
@@ -836,10 +1008,21 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
836
1008
  * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
837
1009
  const e = getOwner();
838
1010
  if (e === null) return false;
839
- const t = e.Ct ? e.Ot : e;
1011
+ const t = e.Gt ? e.Dt : e;
840
1012
  return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
841
1013
  }
842
1014
 
1015
+ /** CHILDREN_FORBIDDEN execution scope (createTrackedEffect / onSettled
1016
+ * callbacks). Distinct from context-free: these scopes get committed
1017
+ * visibility even against a projection's authoritative-elect pending
1018
+ * backing (#3082) — parity with signals, where core read() serves
1019
+ * committed to them regardless of staged writes. */ function inForbiddenScope() {
1020
+ const e = getOwner();
1021
+ if (e === null) return false;
1022
+ const t = e.Gt ? e.Dt : e;
1023
+ return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
1024
+ }
1025
+
843
1026
  /** A pending fold is transition-held when any written node's parked value is
844
1027
  * stamped by a live transition (a plain batch parking — the lazy-recompute
845
1028
  * read case — has no transition stamp and serves fresh). */ function foldHeld(e) {
@@ -847,26 +1030,57 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
847
1030
  if (t === null) return false;
848
1031
  for (const e of Reflect.ownKeys(t)) {
849
1032
  const n = t[e];
850
- if (n.Pe !== NOT_PENDING && n._e != null && n._e.an !== true) return true;
1033
+ if (n.Pe !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
851
1034
  }
852
1035
  return false;
853
1036
  }
854
1037
 
855
1038
  function readSource(e) {
1039
+ // Held view first (#3074): an adoption staged under a live hold serves the
1040
+ // pre-hold committed backing to committed-visibility readers. Speculative
1041
+ // readers — drafts, write-override, owner-context computeds recomputing
1042
+ // inside the transaction, and latest() reads — see the adopted backing.
1043
+ if (e.ht !== null && !latestReadActive && !inDraft(e) && !getWriteOverride() && !inOwnerContext()) {
1044
+ const t = heldMaskView(e);
1045
+ if (t !== null) return t;
1046
+ }
856
1047
  // Signal-parity visibility (core read(): owner-context reads serve
857
1048
  // _pendingValue, context-free reads serve committed — effects recompute
858
1049
  // BEFORE commitPendingNodes in the flush, so the pending view must be
859
1050
  // servable). Drafts (setter window OR projection write-override) and
860
1051
  // owner-context reads see the pending backing; context-free reads see
861
1052
  // committed. Node reads apply the same rule, so both homes agree.
862
- if (e.pb !== null && (inDraft(e) || getWriteOverride() || inOwnerContext() ||
1053
+ if (e.pb !== null && (inDraft(e) || getWriteOverride() ||
1054
+ // Owner-context readers see the pending backing — EXCEPT held truth
1055
+ // on an optimistic family (#3164 fold): a live pb on an opt family
1056
+ // outside the draft/write-override windows is a staged landing
1057
+ // (tentative drafts never outlive their setter), and only the
1058
+ // authoritative postures and latest() see it (the backing-level twin
1059
+ // of core read()'s A17-for-held-truth arm; ordinary readers keep
1060
+ // committed until the transaction's reveal).
1061
+ inOwnerContext() && !heldTruthMasked(e) ||
863
1062
  // A projection's pending backing is authoritative-elect: serve it to
864
1063
  // context-free readers too UNLESS a transition is holding the node
865
- // commits (downstream async hold — stale committed is the contract).
866
- e.fam !== null && !foldHeld(e))) return e.pb;
1064
+ // commits (downstream async hold — stale committed is the contract)
1065
+ // or the reader is a CHILDREN_FORBIDDEN scope, which never observes
1066
+ // its own unsettled write (#3082, signal parity per #3006).
1067
+ e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope())) return e.pb;
867
1068
  return e.v;
868
1069
  }
869
1070
 
1071
+ /** #3164 fold: HELD truth on an optimistic family — a pending backing
1072
+ * stamped by a live transition that retains optimism — is masked from
1073
+ * ordinary readers (they keep committed until the transaction's reveal);
1074
+ * the authoritative postures and latest() tunnel through. Un-stamped
1075
+ * backings and optimism-free transitions keep ordinary mid-batch/
1076
+ * speculation visibility. */ function heldTruthMasked(e) {
1077
+ if (e.fam?.opt !== true || latestReadActive || authoritativeServe()) return false;
1078
+ const t = foldBatches.get(e);
1079
+ // opt families are only created by createOptimisticStore, whose module
1080
+ // install populates optHooks — the assertion holds by construction.
1081
+ return t !== undefined && optHooks.retainsOptimism(t);
1082
+ }
1083
+
870
1084
  const hasOwn = Object.prototype.hasOwnProperty;
871
1085
 
872
1086
  // Allocation-free own-accessor probe (replaces eager descriptor scans — the
@@ -896,7 +1110,27 @@ function isOwnAccessor(e, t) {
896
1110
 
897
1111
  /** Active optimistic override on an armed node (armed slot idles at
898
1112
  * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
899
- return e.o?.De !== undefined && e.o?.De !== NOT_PENDING;
1113
+ return e.o?.Oe !== undefined && e.o?.Oe !== NOT_PENDING;
1114
+ }
1115
+
1116
+ /** The reading computation is until()'s authoritative-view predicate — same
1117
+ * source of truth as core read()'s A17 carve-out (`context`, which persists
1118
+ * under untrack). optimisticView()'s composition gate consults exactly this:
1119
+ * write-side machinery (patch emission, tentative re-application) must keep
1120
+ * composing even when it runs inside an authoritative-write bracket. */ function authoritativeRead() {
1121
+ const e = context;
1122
+ return e !== null && (e.T & CONFIG_AUTHORITATIVE_READ) !== 0;
1123
+ }
1124
+
1125
+ /** Serve-side authoritative gate: until()'s predicate PLUS truth authors —
1126
+ * the projection derive's draft (wrapDraft trap brackets, runAuthoritative;
1127
+ * the same posture pair ensurePB classifies drafts by). A source computing
1128
+ * the next truth must never read its callers' tentative overlays: a derive
1129
+ * continuation's `store.push` computing its index from an action's
1130
+ * optimistic row landed truth in the wrong slot and corrupted committed
1131
+ * state (#3108). Trap-level overlay serves gate on this so values, length,
1132
+ * membership, and keys leave the authoritative view together. */ function authoritativeServe() {
1133
+ return projectionWriteActive || getWriteOverride() || authoritativeRead();
900
1134
  }
901
1135
 
902
1136
  /** Context-aware node view for reads outside tracking: active override >
@@ -906,7 +1140,19 @@ function isOwnAccessor(e, t) {
906
1140
  * reader that forced it (backing commits eagerly; node values fold at flush).
907
1141
  * FORCE sentinels never surface (they only bump subscribers of accessor
908
1142
  * keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
909
- const n = hasActiveOverride(e) ? unwrapOverride(e.o?.De) : e.Pe !== NOT_PENDING && inOwnerContext() ? e.Pe : t;
1143
+ // latest() sees the in-flight parked value like an owner-context reader
1144
+ // does (#3075) — signal/memo parity for store-node-backed keys.
1145
+ // Authoritative-view reads (until()'s predicate) skip the override arm
1146
+ // only: staged pending values are authoritative, overrides are the
1147
+ // caller's optimism.
1148
+ const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Oe) : e.Pe !== NOT_PENDING && (latestReadActive ||
1149
+ // Owner-context pending visibility — except HELD truth (#3164,
1150
+ // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1151
+ // confirming truth), which only authoritative/latest readers
1152
+ // see (core read()'s A17-for-held-truth twin; ordinary readers
1153
+ // keep committed until the transaction's reveal — latest() is
1154
+ // exempted by the leading arm above).
1155
+ (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Pe : t;
910
1156
  return n === FORCE ? t : n;
911
1157
  }
912
1158
 
@@ -931,15 +1177,20 @@ function isOwnAccessor(e, t) {
931
1177
  read(getNode(e, t, n));
932
1178
  }
933
1179
  }
934
- return optHooks.optimisticView(e, r).length;
1180
+ // Truth authors read the backing's own length — an optimistic row from
1181
+ // the caller's transaction must not shift where the author's next write
1182
+ // lands (#3108).
1183
+ return (authoritativeServe() ? r : optHooks.optimisticView(e, r)).length;
935
1184
  }
936
1185
  if (inDraft(e)) {
937
1186
  // Optimistic drafts before their first write have no pending backing yet;
938
1187
  // reads must still see the live optimistic view (compose, not clobber —
939
1188
  // #2951). Once ensurePB runs, the seeded clone carries the view.
940
- if (e.fam?.opt && e.pb === null) {
1189
+ // AUTHORITATIVE drafts (projection derive) never overlay ensurePB's
1190
+ // seeding rule, applied to the read side (#3108).
1191
+ if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
941
1192
  const n = e.n?.[t];
942
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
1193
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
943
1194
  }
944
1195
  } else {
945
1196
  if (i !== undefined) {
@@ -995,6 +1246,27 @@ function isOwnAccessor(e, t) {
995
1246
  if (t != null && t.S & (STATUS_UNINITIALIZED | STATUS_ERROR)) read(t);
996
1247
  }
997
1248
 
1249
+ /** latest() pull (#3075): bring the projection computed up to date so the
1250
+ * read serves the IN-FLIGHT derivation — signal/memo parity, where core
1251
+ * read() routes latest() through a companion that recomputes speculatively.
1252
+ * The latest flag is suspended for the recompute (the derive's own reads
1253
+ * are normal reads), and latestPullActive marks any adoption it commits as
1254
+ * staged (see adoptPB) — the speculative swap must not leak to
1255
+ * committed-visibility readers before the flush. */ function pullProjectionForLatest(e) {
1256
+ const t = e.fam.node;
1257
+ if (t == null) return;
1258
+ const n = latestReadActive;
1259
+ setLatestReadActive(false);
1260
+ const r = latestPullActive;
1261
+ latestPullActive = true;
1262
+ try {
1263
+ prepareComputed(t, true);
1264
+ } finally {
1265
+ latestPullActive = r;
1266
+ setLatestReadActive(n);
1267
+ }
1268
+ }
1269
+
998
1270
  const traps = {
999
1271
  get(e, t, n) {
1000
1272
  // One typeof gates every brand-symbol compare off the hot string path
@@ -1022,6 +1294,10 @@ const traps = {
1022
1294
  }
1023
1295
  if (pendingCheckActive) witnessAffectsMark(e, t);
1024
1296
  if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1297
+ // latest() pull (#3075): store traps never reach core read() without an
1298
+ // observer, so bring the projection computed up to date here — signal/
1299
+ // memo parity for latest() reads through a projection.
1300
+ if (e.fam !== null && latestReadActive && !inDraft(e) && !getWriteOverride()) pullProjectionForLatest(e);
1025
1301
  const r = readSource(e);
1026
1302
  // Overlay delete (#3044): a prototype overlay cannot shadow a delete, so
1027
1303
  // deleted keys are tracked aside and read as absent in the pending view.
@@ -1097,9 +1373,12 @@ const traps = {
1097
1373
  if (e.s) return serveShallow(e, t, r);
1098
1374
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1099
1375
  }
1100
- } else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null) {
1376
+ } else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
1377
+ // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1378
+ // the caller's optimism is not truth (has-trap twin below).
1379
+ !authoritativeServe()) {
1101
1380
  const n = e.n?.[t];
1102
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
1381
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
1103
1382
  }
1104
1383
  if (e.s) return serveShallow(e, t, f);
1105
1384
  return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
@@ -1119,15 +1398,17 @@ const traps = {
1119
1398
  if (!inDraft(e)) {
1120
1399
  if (getObserver() !== null) {
1121
1400
  const n = getHasNode(e, t, r);
1122
- const i = read(n);
1401
+ // Authoritative-view readers get the right answer for free: core read()
1402
+ // skips the override arm for them, so nv is authoritative presence.
1403
+ const i = read(n);
1123
1404
  if (hasActiveOverride(n)) r = !!i;
1124
- } else {
1405
+ } else if (!authoritativeServe()) {
1125
1406
  const n = e.h?.[t];
1126
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
1407
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
1127
1408
  }
1128
- } else if (e.fam?.opt && e.pb === null) {
1409
+ } else if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1129
1410
  const n = e.h?.[t];
1130
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
1411
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
1131
1412
  }
1132
1413
  return r;
1133
1414
  },
@@ -1150,14 +1431,15 @@ const traps = {
1150
1431
  // Optimistic membership overlay: presence-node overrides add/remove keys
1151
1432
  // (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
1152
1433
  // Draft reads before the first write overlay too (pb, once created, is
1153
- // seeded with the view).
1154
- if (e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
1434
+ // seeded with the view). Authoritative-view reads (until()'s predicate,
1435
+ // truth-author drafts) skip the overlay.
1436
+ if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
1155
1437
  let t = null;
1156
1438
  for (const r of Reflect.ownKeys(e.h)) {
1157
1439
  const i = e.h[r];
1158
1440
  if (!hasActiveOverride(i)) continue;
1159
1441
  t ??= new Set(n);
1160
- if (unwrapOverride(i.o?.De)) t.add(r); else t.delete(r);
1442
+ if (unwrapOverride(i.o?.Oe)) t.add(r); else t.delete(r);
1161
1443
  }
1162
1444
  if (t !== null) return [ ...t ];
1163
1445
  }
@@ -1172,10 +1454,10 @@ const traps = {
1172
1454
  if (e.del !== null && e.del.has(t)) return undefined;
1173
1455
  if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
1174
1456
  }
1175
- if (e.fam?.opt && !inDraft(e)) {
1457
+ if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1176
1458
  const n = e.h?.[t];
1177
1459
  if (n !== undefined && hasActiveOverride(n)) {
1178
- if (!unwrapOverride(n.o?.De)) return undefined;
1460
+ if (!unwrapOverride(n.o?.Oe)) return undefined;
1179
1461
  // opt delete
1180
1462
  if (r === undefined) {
1181
1463
  const n = e.n?.[t];
@@ -1216,13 +1498,14 @@ const traps = {
1216
1498
  // Array length writes implicitly delete indices — the written-keys bound
1217
1499
  // can't see them, so poison to the full scan for this batch. Index
1218
1500
  // writes implicitly GROW length, so arrays always record it alongside.
1219
- if (Array.isArray(f)) {
1220
- if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1221
- const n = e.wk ??= new Set;
1222
- n.add(t);
1223
- n.add("length");
1501
+ const l = pcOf(e);
1502
+ if (Array.isArray(f)) {
1503
+ if (t === "length") l.wk = WK_ALL; else if (l.wk !== WK_ALL) {
1504
+ const e = l.wk ??= new Set;
1505
+ e.add(t);
1506
+ e.add("length");
1224
1507
  }
1225
- } else if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1508
+ } else if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
1226
1509
  // Own data keys literally named "prototype"/"constructor" land as data —
1227
1510
  // defineProperty sidesteps a proto-chain setter named the same.
1228
1511
  if (UNSAFE_KEYS.has(t)) {
@@ -1260,7 +1543,14 @@ const traps = {
1260
1543
  const i = !r && getWriteOverride();
1261
1544
  if (!r && !i) return true;
1262
1545
  if (t === "__proto__") return true;
1263
- if (n.get || n.set) e.a = true;
1546
+ if (n.get || n.set) {
1547
+ e.a = true;
1548
+ // Accessor demotion (re-audit blocker 3): a record that acquires an
1549
+ // accessor after patch registration stops being patchable — pull its
1550
+ // patches and re-drive them as tracked effect fallbacks. Hooks are
1551
+ // installed whenever pc.p exists (registration installs them).
1552
+ if (e.pc !== null && e.pc.p !== null) patchHooks.demoteToEffects(e);
1553
+ }
1264
1554
  // Unwrap before ensurePB (see the set trap: self-reference materializes).
1265
1555
  if ("value" in n) n = {
1266
1556
  ...n,
@@ -1268,7 +1558,8 @@ const traps = {
1268
1558
  };
1269
1559
  const o = ensurePB(e);
1270
1560
  pendingNotify.add(e);
1271
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1561
+ const f = pcOf(e);
1562
+ if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
1272
1563
  Object.defineProperty(o, t, n);
1273
1564
  if (e.del !== null) e.del.delete(t);
1274
1565
  if (i) notifyWrites(e);
@@ -1280,7 +1571,8 @@ const traps = {
1280
1571
  if (!n && !r) return true;
1281
1572
  const i = ensurePB(e);
1282
1573
  pendingNotify.add(e);
1283
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1574
+ const o = pcOf(e);
1575
+ if (o.wk !== WK_ALL) (o.wk ??= new Set).add(t);
1284
1576
  delete i[t];
1285
1577
  // A prototype overlay cannot shadow a delete of a committed key —
1286
1578
  // record it aside (#3044); reads/has/ownKeys/commit consult the set.
@@ -1344,8 +1636,39 @@ function createStoreNext(e, t = false) {
1344
1636
  return [ n, setter ];
1345
1637
  }
1346
1638
 
1639
+ /** True when `proxy` is a SHALLOW store (children served verbatim, slots
1640
+ * replaced by reference — #2932). The list driver uses this to choose the
1641
+ * slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
1642
+ const t = e?.[$TARGET];
1643
+ return t !== undefined && t.s === true;
1644
+ }
1645
+
1646
+ /** True when `proxy` belongs to a projection/optimistic FAMILY. The list
1647
+ * driver must DECLINE family arrays (external audit finding): family
1648
+ * structural changes never emit row/slot ops (the setter channel is
1649
+ * fam-gated; optimistic writes ride node overrides), and the proxy identity
1650
+ * is stable so the each-watch cannot catch the change either — an engaged
1651
+ * list would freeze on optimistic/projection structural updates. Record-
1652
+ * level family patches are unaffected (they have their own emission). */ function storeHasFamily(e) {
1653
+ const t = e?.[$TARGET];
1654
+ return t !== undefined && t.fam !== null;
1655
+ }
1656
+
1657
+ /** True when `proxy` belongs to an OPTIMISTIC family specifically. The list
1658
+ * driver declines these (audit finding, narrowed): optimistic user writes
1659
+ * ride node-level overrides — they never enter the reconcile walk, so no
1660
+ * row/slot ops are emitted and an engaged list would freeze on optimistic
1661
+ * structural changes. PROJECTION (non-optimistic) families are drivable:
1662
+ * their recomputes go through the reconcile walk, whose emissions are
1663
+ * transition-stamped in the apply queue like any other (equivalence-matrix
1664
+ * gated). Re-admitting optimistic families requires a lane-timed structural
1665
+ * emission mirroring emitPatchOptimistic, plus revert resync. */ function storeHasOptimisticFamily(e) {
1666
+ const t = e?.[$TARGET];
1667
+ return t !== undefined && t.fam?.opt === true;
1668
+ }
1669
+
1347
1670
  /** Tracking deep snapshot (`deep()` for next targets): subscribes to the
1348
- * key-set and every property node at every reachable level, then returns the
1671
+ * key-set and deep-witness node at every reachable level, then returns the
1349
1672
  * plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
1350
1673
  const t = e?.[$TARGET];
1351
1674
  if (t === undefined || t.px !== e) return e;
@@ -1465,11 +1788,11 @@ function snapshotWalk(e, t, n) {
1465
1788
  Object.defineProperty(i, o, f);
1466
1789
  continue;
1467
1790
  }
1468
- const u = f.value;
1469
- const l = u !== null && typeof u === "object" ? snapshotWalk(u, t, n) : u;
1470
- if (f.enumerable && f.writable && f.configurable) i[o] = l; else Object.defineProperty(i, o, {
1791
+ const l = f.value;
1792
+ const u = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
1793
+ if (f.enumerable && f.writable && f.configurable) i[o] = u; else Object.defineProperty(i, o, {
1471
1794
  ...f,
1472
- value: l
1795
+ value: u
1473
1796
  });
1474
1797
  }
1475
1798
  if (e && i.length !== r.length) i.length = r.length;
@@ -1484,16 +1807,16 @@ function snapshotWalk(e, t, n) {
1484
1807
  if (!i || i.get || i.set) continue;
1485
1808
  const o = i.value;
1486
1809
  if (o === null || typeof o !== "object") continue;
1487
- const u = snapshotWalk(o, t, n);
1488
- if (u !== o) {
1810
+ const l = snapshotWalk(o, t, n);
1811
+ if (l !== o) {
1489
1812
  if (f === null) {
1490
1813
  f = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
1491
1814
  t.set(r, f);
1492
1815
  }
1493
- f[e] = u;
1816
+ f[e] = l;
1494
1817
  }
1495
1818
  }
1496
1819
  return f ?? r;
1497
1820
  }
1498
1821
 
1499
- export { adoptPB, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, storeSetterNext, targetsEqual, unwrapValue, wrapNext };
1822
+ 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 };