--- status: historical created: 2026-08-18 archived: 2026-08-18 reason: "Stage-C evidence: the design pass's server DEMAND-WALK lens (d) — the verified K×N duplication, the walk's read anatomy (~7 read activities per property; value-only changes re-fire it), the redesign (a) one node per (scope-name, root id) + (c′) the structural walk, the union-log fixes (Q3), the cost estimate, pins T1–T9 with mutations, rulings R1–R5, refutation conditions; read against fb2292a24; input to the reconciled design (docs/plans/server-execution-v2/stage-c-design.md)." --- I have what I need. Below is my report. # Lens (d): the server-side demand WALK × roots-per-change Worktree `/Users/berni/labs-worktrees/design-c` @ fb2292a24. All line numbers are from that tree. Read-only; nothing edited. ## 0. Verification ledger for the convener's eight readings **1. VERIFIED.** `#loadDemandedStructure` is `space-server.ts:2202–2494`; one walk effect node per DEMAND KEY (`this.#demandSinks.set(key, this.#installDemandWalk(runtime, root))`, :2462–2465, inside the `for (const [key, root] of rootByKey)` loop :2326). `keyOf` :2220–2239 yields `space\0` for space roots and `\0` (e.g. `user:alice\0`) for scoped ones. `#installDemandWalk` :2502–2532: link `{space, id, scope: root.scope ?? "space", path: []}` (:2506–2511), body `JSON.stringify(runtime.getCellFromLink(link).withTx(tx).get())` in a swallow-all try (:2512–2519), registered `isEffect: true` with `observationIdentity.pieceRootId = root.id` (:2524–2531). **2. VERIFIED, and it is real K×N duplication.** `rootByKey` is per demand key (first row per key, :2243–2246); every key gets its own sink (:2326–2465, no dedup by id); `#demandersFor` (:1222–1253) matches by `keyRoot = key.slice(indexOf("\0")+1)` OR the resolved piece root, and unions pairs across ALL matching keys (:1225–1234). So `user:alice\0X` and `user:bob\0X` are two walk nodes with the identical link (`scope:"user", id:X` — the scope NAME, not the instance) and identical `pieceRootId`, each fanned over {alice-s1, alice-s2, bob-s1}. Both narrow on their probe run (the very first read is a `user`-scoped address → `recordReadScope("user")`, ext-tx :974–986), so each runs per principal: 2 nodes × 2 (or 3 if a session hop) runs where 1 × 2 (3) suffices. Session-scoped roots: 3 keys × 3. Since walkers of the same id compute the identical thing (their product is a subscription; see Q1), merging is a pure dedup — no ruling needed. Caveat for the arithmetic: the win only exists for scoped keys. From the given 585–634 rows / 232–249 keys ≈ 2.5 rows/key: all-space would be 3.0 rows/key, so the mix is roughly ~170 space keys + ~50 user keys (~25 ids) + ~20 session keys (~7 ids) → (a) alone removes ~15–20% of walk NODES; it is not the main lever. **3. VERIFIED.** `run.ts:585–604` runs instances serially (`await runOnce(...)` :600), then `:605–618` resubscribes ONCE with `fanOutUnionLog(fanOut)` if `ran || fanOut.instances.size > 0`. `fan-out.ts:333–344` concatenates every instance's last log (`reads.push(...record.log.reads)`). Each stored instance log is the RAW `txToReactivityLog(tx)` (`run.ts:810`, stored via `fanOutRunFinished` :824–828 → `record.log = outcome.log`, fan-out.ts:320) — never compacted, and the tx's `#readActivities` are never deduped (`v2-transaction.ts:1400–1413` pushes one activity per read call). So the union carries N copies of the space prefix AND each copy is itself ~5–10× redundant (see item 8). `sortAndCompactPaths` (`reactive-dependencies.ts:40–80`) then collapses the N space copies (no `scopeKey` → `instanceOf = "space"`, :52–53) and keeps instance-keyed reads apart. **4. VERIFIED.** `invalidation.ts:262–285` (`markInvalid` → `dirtyFanOutForCause` for a cause, `dirtyFanOutAll` for none unless `"keep"`); `fan-out.ts:220–235`: `causeKey === undefined || "space"` → all; else instances whose identity chain contains the key. So any space-doc change under a root re-runs all N walk instances. **5. VERIFIED.** `registration.ts:238–319` → `setSchedulerDependencies` (`dependency-updates.ts:11–35`, `sortAndCompactPaths` on the whole union ×2 :26–27) → `updateDependents` (`facade.ts:1768–1781`; the telemetry payload at :1773–1780 maps over ALL reads eagerly and `telemetry.submit` always allocates an event, `telemetry.ts:285–287`) → `updateDependentEdgesForLog` (`dependency-graph.ts:447–472`, via `collectReverseDependenciesForLog` → `forEachOverlappingWriter` `scheduling-writes.ts:218–259`, one name-keyed index lookup + overlap test per read) → `applyActionReadDelta` (`trigger-index.ts:367–408`: four `addressesToPathByEntity` passes, per-entity `pathsEqual` skip :439). Nothing is incremental except the per-entity path-set equality skip and the writer-set diff. 67 ms per resubscribe at 67K reads is consistent with this. **6. VERIFIED, with one important refinement (below).** Pull mode: `work-oracle.ts:93–106` `isRunnableSchedulingSeed = isInvalidOrNeverRan && (isLiveAction || pending) && …`. Liveness: `dependency-graph.ts:61–68` (`isDemandRoot || liveRefs > 0`; roots = effects/provisional/materializer :52–59); `liveRefs` are maintained from `reverseDependencies` edges (`registerDependentEdgeImpl` :484–523 — `writerRecord.liveRefs++` when the dependent is live, then `grantDemandFrom` propagates upstream :225–245). Edges come from (i) the reader's resubscribe (`updateDependentEdgesForLog`, name-keyed writer index) and (ii) the WRITER's registration (`registerDependentsForWriterSurface` :525–541, called from `registration.ts:181` `registerWriterDependents` — readers already in the trigger index on the writer's declared surface get the edge). I found NO path where a query-result-proxy read runs a computed synchronously: the `get` trap (`query-result-proxy.ts:362–653`) only does `resolveLink` + `readValueOrThrow` + proxy construction; the only side effects are async load kicks (`link-resolution.ts:413–430`, `v2-transaction.ts:2639–2659`). The never-ran / newly-linked case: the walk's read of a NOT-YET-EXISTING doc IS logged (`v2-transaction.ts:1400–1413` records the activity before existence is checked; `readOrThrow` maps NotFound to `undefined`, ext-tx :1737–1748), so `D/[]` (shallow) and `D/["/","link@1"]` (recursive probe) enter the trigger index and the dependency log; the writer's declared surface at `D/[]` overlaps (`arraysOverlap([], …)` is true) → edge → live, either at the walk's resubscribe or at the writer's registration. When D lands, the walk is (correctly, structurally) re-triggered: `shallowEqual(undefined, obj)` → `valueEqual` false (`reactive-dependencies.ts:334–338`) and the probe's reachability flips (:217–224). **Refinement:** the walk's RUN is indeed not what pulls; but TODAY the walk is still RE-RUN by value-only changes, because JSON.stringify over the proxy issues a nonRecursive (SHAPE) read at every scalar LEAF path (`createViewProxy` :256 then :280–291) and `shallowEqual` compares an opaque leaf BY VALUE (:334–338), and issues a RECURSIVE read of every ARRAY (`length` trap :369–372, also `Symbol.iterator`/array methods :390–393, :439–450). So "value-only changes leave its log unchanged" is true of the log and false of the trigger — see Q2(c) for why the gate must live in the walk's read CLASS, not the trigger index. **7. VERIFIED.** The stamped instance identity reaches the storage tx at `wave.ts:278–279` (`tx.tx.scopeKeyIdentity = context.scopeKeyIdentity`, from `#stampRun` `space-server.ts:1092–1158` ← `stampServerRun` `run.ts:485–494`); a scoped read resolves the demander's instance in `loadRoot` (`v2-transaction.ts:2623–2627` `replica.getDocument(id, scope, identity)`), the logged address carries `scopeKey` (`#instanceOf` :2442–2450, applied :2463–2470), and every read moves the tx ratchet by scope NAME (`prepareRead` → `recordReadScope`, ext-tx :974–986; read at commit kickoff `run.ts:824–828`). Redirect following is `resolveLink` (`link-resolution.ts:208–456`; hop kind `overwrite:"redirect"` :64–67), invoked per property from the proxy (:248) and at the root by `validateAndTransform` (`schema.ts:1166`, and the schemaless branch hands back a proxy :1213–1221). **The log is NOT one recursive read per doc.** Per traversed property JSON.stringify costs: `getOwnPropertyDescriptor` trap (SHAPE read of the parent :754–757 + `createViewProxy(child)` :781–790) and `get` trap (`createViewProxy(child)` again :643–652 — the cache check :351–359 comes AFTER `resolveLink` :248 and the SHAPE read :256, so a hit still pays them); each `createViewProxy(child)` = sigil probe read at `[…child,"/","link@1"]` (`link-resolution.ts:245–251`, recursive, kept for reactivity), usually a second parent-probe read (:287–293), and the SHAPE read (:256). Plus per object a `toJSON` probe (JSON.stringify's `GetV(value,"toJSON")` reaches :639–652 and builds a child view for `[…,"toJSON"]`) and `ownKeys` (:689–693). ≈7 read activities per property, ≈4 per object, one recursive whole-array read per array. That is why a few-thousand-property subtree yields ~20K raw activities per instance and 67K in a 3-instance union, and why the trigger set is effectively "any change anywhere under any reachable doc". **8. VERIFIED (read, not profiled).** Per property, no single expensive op; it is the multiplicity: ~7 `tx.read`s (each: `prepareRead`, `editable`, `branch`, `document` (`#lastDocument` fast path misses when alternating docs), `readAttestation` path walk, `freezeReadValue`, `PathKeyMap.set`, activity push, `invalidateReactivityLog`), 2 `resolveLink`s, 2 `mergeCfcLabelViews`/`cfcLabelViewForDereferenceTraces` (with a `dereferenceTraces.slice`), 2 `JSON.stringify` cache keys (:87–98), 1 `Proxy` allocation, `Object.isFrozen`, and `runtime.readTx(tx)` calls. 20–250 ms per run for hundreds-to-thousands of properties is what this predicts. ## Q1. Why one change re-walks every root for every demander; what is per-(demander, root) The walk COMPUTES nothing and WRITES nothing. Its entire product is its READ LOG, which serves: (i) liveness — edges to every writer of every doc it read (name-keyed writer index, `scheduling-writes.ts:43–58`) hold the subtree demanded; (ii) its own re-trigger set; (iii) ratchet discovery (`getNarrowestReadScope`); (iv) as a side effect, instance-named load kicks for absent scoped docs (`v2-transaction.ts:2639–2659`, `link-resolution.ts:413–430`). Identity-independence of the prefix is a storage fact: the tx identity is consulted ONLY for `scope !== "space"` reads (`loadRoot` :2624–2627; `#instanceOf` returns undefined for space :2446); a space doc has one instance. So from the root down to the first `user`/`session` link (a redirect or a scoped data link), every instance run reads the same addresses and sees the same values; the runs diverge only at scoped addresses (per-instance docs, logged with `scopeKey`). Hence per-(demander, root) work is exactly the scoped SUFFIX: the reads below redirects. Liveness is node-level (C11b; writer index name-keyed), so for liveness one instance's traversal of a doc suffices for all — per-demander walks matter only for REACHABILITY differences (Alice's `ifElse` branch links to a computed Bob's doesn't; the (f-walk) pin `executor-fan-out.test.ts:677–795`) and for B7's per-instance dirtiness of the walk itself. Why one change re-walks everything today: (1) a space cause dirties all instances (item 4) — correct for structure, wasteful for values; (2) the walk's read class makes VALUE changes fire it (item 6 refinement); (3) K walk nodes per scoped id (item 2); (4) each fire pays a full N-instance re-traversal plus an O(union) resubscribe. The register already names (2)+(4) as accepted cost: `verification-coverage.md` residual "(ix) the walk re-fires per changed doc it read (an effect; N × walk per changed root — the design's stated cost)" (~:1247–1249). ## Q2. Smallest sound redesign **Recommend (a) + (c′), where (c′) is "structural walk" — a purpose-built traversal replacing `JSON.stringify(cell.get())` — with (b)/(d)/(e) deferred.** All of it lives in `space-server.ts` (+ two ON-only lines in `fan-out.ts`/`run.ts` for Q3); it keeps per-demander instances, B7, the ratchet, C11b, and W's rule untouched. **(a) One walk node per (scope-name, root id).** Key the sink map by `${root.scope ?? "space"}\0${id}` (a refcount over the demand keys that name it: install on the first, cancel on the last), keeping `#demandersByKey`/`#pieceRootByDemandKey` per demand key exactly as today (arrivals, retirement, `#demandersFor` unchanged). Node count per root id becomes the number of DISTINCT DEMANDED SCOPE NAMES of that id (≤3), independent of demander count. Keep space vs user vs session distinct: a `user`-scoped demand names the user instance doc, which can exist without a space→user redirect (the register's "legacy plain values" flag), so a single space-link walk would not reach it. Nothing is lost: today's K nodes already fan over the identical union (`#demandersFor([id])`). **(c′) Structural walk — the gate is the read CLASS, not the trigger index.** Operationally in this codebase, "structural" = the three change kinds `determineTriggeredActions` (`reactive-dependencies.ts:115–249`) already distinguishes without any new code: (i) a container's own-key set / array length changed — a nonRecursive read AT the container path, `shallowEqual` :323–332 (plain containers by key set; arrays also by length); (ii) the value at a LINK position changed (appear/retarget/vanish) — the recursive `whole` read `resolveLink` issues at a found link (`link-resolution.ts:261–263`, :299) and `shallowEqual`'s link arm (:319–321, `isPrimitiveCellLink`); (iii) reachability depth at a visited path changed (scalar↔container, doc appear/vanish, scalar→link) — the recursive sigil PROBE read at `[…p,"/","link@1"]` (`link-resolution.ts:245–251`), decided by the `beforeCanReach/afterCanReach/lastObject` logic (:194–224). Link recognition: `linkPayloadAtProbe`/`linkProbeSubPath` (data-model cell-rep), `parseLink` (link-utils), redirect marker `overwrite: "redirect"` (:64–67, :259). The walk therefore issues, per traversed doc instance: the root probe + (if a link) the whole read via `resolveLink` (keeps hop semantics, redirect following, ratchet, sync kicks, cycle guard); ONE nonRecursive read per container path (`readValueOrThrow(container, {nonRecursive:true})` — you get the frozen subtree value in hand and inspect children in memory); and ONE recursive probe path per LEAF, `[…leaf,"/","link@1"]`, registered in a batch with `trackReadPaths` (`ext-tx:1708–1728` → `v2-transaction.ts:1471–1517`, no load, no freeze). NEVER a read at a scalar leaf's own path, NEVER a recursive read of an array. Add a per-run visited set of `(space, scopeKey, id, path)` so link cycles terminate cleanly instead of today's throw-and-swallow (`space-server.ts:2515–2518`, which leaves a PARTIAL log). Then, by construction: value-only changes do not fire the walk; a new/retargeted/removed link, a member added/removed (arrays: growth/shrink), a reachable doc appearing/vanishing, or a scalar becoming a container all do; per-instance keyed causes still dirty only that demander (B7, item 4); the ratchet still moves (scoped reads happen); liveness is preserved because every visited path carries a recursive probe (overlaps any writer surface at or above it: `arraysOverlap` prefix, `scheduling-writes.ts:168–185`) and every container carries a shallow read (overlaps a writer at the container or its direct child, `nonRecursiveReadMayOverlapWrite` :260–266). Unchanged pre-existing gap, not filled: a writer whose declared surface lies two or more levels below the deepest visited container of an existing doc is not pulled live by the walk — identical to today's `JSON.stringify` walk (its `toJSON`/parent probes have the same reach), and one-run-late soundness (§3b) covers it once anything writes there. Cost per property drops from ~7 reads + 2 resolveLinks + proxy machinery to ~1 activity; logs shrink ~5–7×. Why NOT a trigger-index gate: it would be a generic-path change (`trigger-index.ts`/`invalidation.ts`, OFF-visible), need a per-action opt-in, and re-derive from `change.before/after` what `determineTriggeredActions` already decides from the read class. Why not an ambient read-meta trick on the existing proxy walk: leaf vs container is only known after the read, the proxy's array `length` recursive reads would remain, and `query-result-proxy.ts` is generic. **(b) share the space prefix across instances in a pass** — sound (the prefix is identity-independent, Q1) but needs cross-run state inside the action (a pass id / cause set is not handed to `Action(tx)`) and log synthesis; after (c′) it only halves the residual structural re-walk of N instances. Defer; the natural follow-on is (b′) INCREMENTAL structural walk: re-traverse only from the run's causes (`tx.getCfcState().triggerReads`, fed by `run.ts:470–472`) and re-register the untouched remainder from a per-instance structural cache via `trackReadPaths`. **(d)** probe walk + per-slot narrowed walks: dynamic per-slot effect nodes with their own lifecycle; not smallest; only if (c′)'s residual matters. **(e)** replica/scheduler-maintained link closure: a new subsystem duplicating what the reactivity log + trigger index already provide, and it changes the D11 "discovery by running" posture from a run's log to a maintained index (with per-demander projections); too big for this stage — the structural walk gets ~90% of the value with zero generic change. ## Q3. The 67K-read union It is recomputed from scratch per PASS of the node (`fan-out.ts:333–344` concat → `dependency-updates.ts:26–27` sort/compact ×2 → four `addressesToPathByEntity` maps → writer scan → per-entity path-set diff), on RAW uncompacted per-instance logs. Smallest changes, all ON-only (fan-out state exists only when `serverRunDemandersFor` returns principals, `run.ts:434–439`): 1. Compact each instance log ONCE at record time (`fanOutRunFinished`, `fan-out.ts:320`: store `sortAndCompactPaths(reads)` / `(shallowReads, false)`). Compaction is a prefix-minimal closure, so compact(compact(A)∪compact(B)) = compact(A∪B). Removes the intra-instance ×5–10 redundancy; with (c′) the raw log is nearly minimal anyway. 2. `run.ts:605`: resubscribe only if `ran || pruned` — make `pruneFanOutInstances` (`fan-out.ts:262–274`) return whether it removed anything (departed instances' reads must still leave the union). Today "keep"-style re-arms (arrival re-arm `facade.ts:845–866`, actor preflight :940–966) that find nothing to run still pay a full resubscribe. 3. Optional: keep the previous compacted log per instance and skip the resubscribe when every ran instance's compacted log equals its previous one and nothing was pruned (O(n) compare vs sort+4 maps+writer scan). Sound because writer arrivals/departures are already edged at THEIR registration/unsubscription (`dependency-graph.ts:525–541`, `registration.ts:416–436`) — verify with `SCHEDULER_LIVENESS_EQUIVALENCE=1` (`dependency-graph.ts:98–129`). Flag R5 below because it touches C11b's "one subscription" bookkeeping for fanned-out COMPUTEDS too. 4. Telemetry payload laziness (`facade.ts:1773–1780`) — GENERIC path, OFF runs it; behavior-preserving; flag as such (Q7). The #5569-style incremental liveness (`liveRefs`, `livenessWork`, `docs/history/development/performance/2026-08-scheduler-liveness-maintenance.md`) already covers the EDGE side; the remaining O(union) is the log→index projection, and (c′)+1+2 shrink its input ~10×, which is enough. ## Q4. What the specs REQUIRE vs what the implementation does beyond `scopes.md §2` mechanism sentence (:161–197) requires only that a principal's broad demand be demand for THAT principal's instances of every node narrowing beneath it, i.e. registry rows carry the pair on space roots and instance sets = discovered scope × demanders. It names no walk; the redesign satisfies it unchanged. `protocol.md §4` (:649–682) requires W to cover every DEMANDED INSTANCE; a value-only change re-derives instances through the trigger index + liveness (walk not involved) — unchanged. `serving-loop.md §1` :53–62 ("Demand is value-granular client pull … the demand WALK (the live reader per demanded root that pulls the value's subtree) runs once per demanding pair, each run following THAT demander's redirects") is a DESCRIPTION of the landed stage-B mechanism written into a normative section (commit 755569f4a); the ruled content is "whose demand a broad subscription is", not "the walk runs per pair per change". §3b :388–398 (B7 union subscription) is untouched. Amended §1 sentence the redesign needs (owner wording): "…and the demand WALK — the live reader per demanded root whose STANDING SUBSCRIPTION (its last run's structural read set: container shapes, link positions, per-path link probes) holds the value's subtree live for its demanders — runs per demanding pair on first demand and again only when the link structure reachable from the root changed for that pair (a link appearing, retargeting or vanishing; a container gaining or losing members; a reachable doc appearing or vanishing), each run following THAT demander's redirects. Value-only changes beneath a walked root re-derive the demanded instances through the ordinary trigger index and liveness edges without a walk run; the walk computes and writes nothing." Also restate register residual (ix). ## Q5. Estimated cost after the redesign (chat: 8 events, 3 instances, ~240 keys) Anchors (given): today ~30 walk nodes fire per event wave, ~72 runs @ ~48 ms, ~33 resubscribes @ ~67 ms — walks+resubscribes = 96% of a 2.6–3.6 s settle. After (a): −15–20% nodes (scoped-key dedup; ~0 if the keys were all space keys — check the scope mix first). After (c′): a message append is STRUCTURAL for every root whose subtree contains a container that grew (the messages array, derived/filtered arrays, the rendered VDOM list) and NOT for value-only outputs (counts, per-user drafts, edited text): I estimate 5–10 firing nodes × ~2.4 instances ≈ 12–24 runs, each 3–10 ms (≈1 activity/property, no proxies) ≈ 40–240 ms; resubscribes 5–10 × 10–20 ms (logs 5–7× smaller, per-instance compaction) ≈ 50–200 ms; total ≈ 0.1–0.45 s per event wave vs ~2.5–3.5 s. Cross-user step 2.4–7 s → ~0.3–0.8 s (settle + the non-walk ~0.1–0.15 s + push + client apply): sub-second at the median, plausible; the tail depends on what else drove the 7 s events (exhausted waves cascading — `wavesBudgetExhausted 66–90` in the register). ON floor: the 100 ms deadline is a CAP not a wait (`#waveCycle` races `idle()`/frames vs the deadline and breaks as soon as `isIdle()`, `space-server.ts:2664–2711`; `DEFAULT_FLUSH_DEADLINE_MS` :193), so floor ≈ append RTT + drain/handler/derivations + commit + push + apply ≈ tens of ms on localhost for a value-only step. Honestly: beating OFF's 4–42 ms client-local step needs the walk fully off the append path (b′ incremental: O(new element)) AND wave commit+push cheaper than a client-local recompute — on a two-user localhost chat that is not credible; the honest bar is "sub-second and within a small constant of OFF", which (a)+(c′) reach. ## Q6. Invariants to pin red-first (mutation named) - T1 value-only change under a walked root → downstream computed's instances re-derive and W advances with ZERO new `demand-walk:` trace entries. Kills: M1 revert to `JSON.stringify(cell.get())` (leaf/array reads → walk runs); M2 walk logs no container/probe reads (computed never live → no re-derive). - T2 a new link (or scalar→link) at a visited path IS walked; the linked subtree becomes live for each demander (per-user: under each instance). Kills: M3 drop leaf probes; M4 drop the whole read at link positions (retarget missed). - T3 array growth is structural: an appended element carrying a link becomes live. Kills: M5 skip container SHAPE reads for arrays. - T4 a per-user structural change re-walks only that demander's instance(s) (trace `instanceKey`). Kills: M6 log scoped reads without `scopeKey` / dirty all. - T5 walk node count per (scope-name, id) = 1 with two principals watching a user-scoped root; walk runs per pass = N, not K×N. Kills: M7 per-demand-key sinks. - T6 union coverage after a skipped/partial pass: after a pass that ran only Bob (or nothing), a change under Alice's instance-only path still dirties the walk; after a prune with no run, the departed instance's reads leave. Kills: M8 skip resubscribe unconditionally when `!ran`; M8′ drop per-instance log retention. - T7 doc-appears: a computed's absent output doc is live via the walk's absent read; when it lands the walk re-runs once and traverses it. Kills: M9 don't log absent-doc reads. - T8 resubscribe count telemetry (`scheduler.dependencies.update`) — performed iff ran or pruned. Kills: guard removed. - T9 OFF-arm: no `demand-walk:` node ever exists off the serving posture (structural: `#installDemandWalk` is SpaceServer-private; `installSealDestination` throws OFF, `runtime.ts:1953–1959`); OFF runner suite byte-identical. Note: `executor-fan-out.test.ts:731–734`'s comment ("her walk instance … must observe her draft") becomes a stale mechanism claim (the draft is value-only); the assertion itself stays green (both keys appear at the flag flip, which is structural for Alice's `view` link and re-derives Bob's instance in the same pass). ## Q7. OFF-arm implications None by construction for (a) and (c′): both live in `space-server.ts` and use existing tx primitives (`resolveLink`, `readValueOrThrow`, `trackReadPaths`, `tx.read`); the trigger machinery is unchanged. Q3 items 1–3 live in `fan-out.ts` / the `fanOut !== undefined` branch of `run.ts` (unreachable OFF: `serverRunDemandersFor` → `#serverRunDemanderResolver?.()` undefined, `runtime.ts:2007–2011`). Two generic-path items to keep OUT of this stage or land separately with the OFF suite as the gate: the eager telemetry map (`facade.ts:1773–1780`) and the proxy's read-before-cache order / `toJSON` probe (`query-result-proxy.ts:246–256, 351–359, 639–652`) — behavior-preserving but OFF-visible code paths. ## Q8. Owner rulings — flag, don't fill - R1 Lazy per-demander projection: NO. There is no server-side "demander's first read" — the watch IS the demand — so laziness = removing the pair from D, and W (`protocol.md §4` :668–677) would advance over a demanded-but-never-walked instance. Options: (A) eager per-demander structural suffix (recommended; cheap after (c′)); (B) lazy + redefine "demanded instance" (rejected); (C) probe eager, siblings on first push (= B). - R2 Wording sign-off: the walk becomes a STRUCTURAL SUBSCRIPTION, not a "value pull" — amend serving-loop §1 (Q4) and restate register (ix). - R3 Whether the pre-existing "writer surface strictly below a visited leaf" reach gap is accepted as unchanged (it is not introduced by (c′)). - R4 Whether Q3 item 3 (skip resubscribe on identical logs) may apply to fanned-out COMPUTEDS as well as the walk (touches C11b's one-subscription bookkeeping; I believe safe, verified only by reading the edge maintenance). - R5 (a)'s node key: per (scope-name, id) as recommended, vs one node per id reading all demanded scope names (needs a re-arm when a new scope name arrives for a known id) — implementation choice, cheap to rule. ## Q9. What would refute this - If, after (a)+(c′), walk runs per event wave fall to ≈ (#roots whose subtree grew) × N and the settle stays ~2.6 s, the term was elsewhere (wave commit/frames/`server.idle()`, derivation fan-out, structure-load pass) and the 96% attribution is wrong. - If T1 fails — a value-only change under a walked root leaves the downstream computed stale or W stalls — then something in liveness or loading depends on the walk's RUN (my `isRunnableSchedulingSeed`/`liveRefs` reading, or an instance-load kick only the walk performs); the structural gate is unsound and the fallback is "one recursive read at `[]` per traversed doc" (trivially sound superset, tiny logs, still re-fires on values but ~10× cheaper per run/resubscribe). - If the per-run ms does not drop with the structural traversal, the cost is in the tx read layer per activity (`readAttestation`/freeze/validate), not the proxy — attack via `trackReadPaths` batching / storage layer. - If the demand-key scope mix is all-space, (a) is inert (expected: rows/keys ≈ 2.5 says it is not). ## Tuning-class (not design), clearly labeled - `#demandersFor` full-scan of all keys per action run, twice (`run.ts:429`, and per loop iteration :586) — index rootId→pairs. Micro at 240 keys. - Raw `#readActivities` never deduped (`v2-transaction.ts:1400–1413`) → per-instance compaction (Q3.1). - `updateDependents` eager telemetry payload (`facade.ts:1773–1780`) — generic. - Proxy `createViewProxy` reads before its cache check; JSON.stringify's `toJSON` probe per object; `getOwnPropertyDescriptor`+`get` double child-view per property (`query-result-proxy.ts:246–256, 351–359, 639–652, 731–790`) — generic, OFF-visible; moot for the walk once (c′) lands. - Walk cycle handling today throws at depth 100 and swallows, leaving a partial log (`space-server.ts:2515–2518`) — the structural walk's visited set fixes it. ## Verified by reading vs taken on faith Verified by reading: everything cited above with file:line, including the K×N duplication, the read-log anatomy per property, the value-vs-structure trigger semantics in `determineTriggeredActions`/`shallowEqual`, the two liveness edge paths, the identity seam, the ratchet, the wave deadline as a cap. Taken on faith: the instrumented numbers (573/263/67K/48 ms/96%), the ~30-nodes-per-event and 5–10-structural-nodes estimates in Q5 (inferred, not measured), that keyed notification addresses arrive with `scopeKey` on the serving replica (stage A; I read the differential/`entityKey` side, not the wire), and that pattern computeds are registered with declared write surfaces (required for the name-keyed writer index; `registration.ts:178–184` shows an undeclared, log-less computation only ever runs as `pending`).