wenay-common2 1.0.73 → 1.0.74

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.
package/doc/ROADMAP.md CHANGED
@@ -1,230 +1,233 @@
1
- # wenay-common2 — Roadmap (open / deferred)
2
-
3
- > Forward-looking backlog of distributed-state / transport features that are **not fully built**.
4
- > Everything here sits on top of the existing seams — `{emit,on}` transport, `exposeStore` /
5
- > `createStoreMirror`, and the replay `seq` / `keyframe` / `frame` contract. None of it requires
6
- > changing the store core; the store stays single-authority (one `seq` sequencer, last-writer-wins
7
- > per path), and these features are layers or adapters above that.
8
- > Status: 🔴 not started · 🟡 partial / ongoing · 🧊 deferred (super-low priority, not forbidden).
9
- >
10
- > Current focus is NOT more transport: see `doc/target/library-uplift-tasks.md` (showcase, SDK facade,
11
- > vertical demo app). Transport items below stay available but rank below everything in that plan.
12
-
13
- ## 0. Distributed Runtime Model
14
-
15
- The strategic direction is not "RPC plus store helpers"; it is a small distributed-state runtime:
16
- application code keeps a stable typed API while lower layers can choose the transport, route, replay
17
- source, and authority model.
18
-
19
- Four concerns must stay explicit:
20
-
21
- - **Transport** - how two endpoints exchange `{emit,on}` messages: socket.io, in-process loopback,
22
- relay, WebRTC/direct channel, or a future adapter. Transport is replaceable infrastructure.
23
- - **Routing** - where a logical call or stream goes: server, peer through relay, peer directly, or a
24
- promoted relay <-> direct path. Routing must be allowed to change without changing the facade API.
25
- - **Authority** - who is allowed to write truth: one server, partitioned peer-owned slices, server
26
- authority with client prediction, or true multi-writer conflict resolution. This is a semantic
27
- decision, not a transport decision.
28
- - **Replay** - how live state survives reconnect, lag, transport swap, and history playback:
29
- `seq` + keyframe/frame + deltas. Replay is the continuity layer that makes route changes boring.
30
-
31
- Design rule: direct peer links and relay hand-offs are optimizations of transport/routing. They must
32
- not silently change auth, ownership, validation, or conflict semantics. If a method is exposed as
33
- `api.hit(playerId)`, it may travel through the server or a direct channel, but the authority rules
34
- behind that method must remain the same.
35
-
36
- This implies a useful split:
37
-
38
- - **API surface** remains facade-shaped and typed.
39
- - **Transport/routing layer** may substitute sockets and promote paths.
40
- - **Replay layer** resumes streams and mirrors from the last known `seq`.
41
- - **Authority layer** decides whether an incoming write is accepted, predicted, reconciled, or merged.
42
-
43
- Critical ordering rule: do **not** start with WebRTC/NAT plumbing or CRDTs. The next useful layer is a
44
- small route/account/policy coordinator with a fake/in-process transport adapter. Direct transport and
45
- multi-writer merge are expensive adapters; they only become safe after the coordinator state machine is
46
- boring and well-tested.
47
-
48
- ### 0.1 Account route coordinator ("wrapper over wrappers") 🟡
49
-
50
- Some deployments need separate client/account identities to communicate directly when policy and
51
- network conditions allow it, while still allowing the server/relay to step back into the path later.
52
- This is not just a socket trick; it is an account-aware routing shell above the existing facade,
53
- mirror, and replay primitives.
54
-
55
- - **Account model:** every participant has its own account/session identity and a scoped facade/store
56
- set. Runtime-account maps are a dynamic keyspace, so they should look like `noStrict(accountMap)`
57
- rather than a fixed schema. Access checks stay in the facade/policy layer; `noStrict` is not an ACL.
58
- - **Wrapper over wrappers:** an app-level coordinator owns the set of per-account clients, exposed
59
- facades, mirrors, replay subscriptions, and route state. "State of other accounts" is represented as
60
- selected `Observe` mirrors/replay/offline resources, ideally started and stopped through
61
- `createStoreManager`, not as a new global store core.
62
- - **Route states:** a pair of accounts may be `relay`, `direct`, `direct+shadowRelay` (audit/observe
63
- copy), `blocked`, or `fallback`. Direct links are optional optimizations, useful for latency or
64
- traffic cost, not a semantic change.
65
- - **Re-interposition:** if NDA/privacy policy, audit, moderation, throttling, reauth, direct-link
66
- failure, or group topology changes require it, the relay must be able to re-enter the data path.
67
- This is the reverse of direct promotion: open the replacement route, resume from the last `seq`,
68
- switch consumers after catch-up, then close or demote the old route.
69
- - **Privacy rule:** direct account links are opt-in and policy-gated. Peers only receive the endpoint
70
- and session material needed for that specific relationship; no implicit broad account discovery.
71
-
72
- Concrete next API shape, not final names:
73
-
74
- ```ts
75
- createRouteCoordinator({
76
- policy,
77
- routes,
78
- resources,
79
- }) -> {
80
- pair(a, b),
81
- state(pair),
82
- promoteDirect(pair, opts?),
83
- reinterposeRelay(pair, reason?),
84
- block(pair, reason?),
85
- fallback(pair, reason?),
86
- onRoute(cb),
87
- }
88
- ```
89
-
90
- Policy must run **before** transport promotion:
91
-
92
- - `canDirect(pair, ctx)` - may these accounts attempt direct at all?
93
- - `mustRelay(pair, ctx)` - force relay path because of NDA/audit/moderation/reauth.
94
- - `mustShadowRelay(pair, ctx)` - allow direct payload path, but keep audit/observe copy.
95
- - `canExposeEndpoint(pair, ctx)` - whether signaling may reveal endpoint/session material.
96
- - `canReinterpose(pair, ctx)` - whether/when relay is allowed or required to step back in.
97
-
98
- Minimum state machine:
99
-
100
- - `relay` -> `direct:connecting` -> `direct` -> `relay:reinterposing` -> `relay`
101
- - `relay` -> `direct:connecting` -> `fallback` -> `relay`
102
- - `direct` -> `direct+shadowRelay`
103
- - any state -> `blocked`
104
-
105
- Required failure modes:
106
-
107
- - direct setup never completes: keep relay active and mark fallback;
108
- - replacement route catches up too slowly: keep old route active and fail the switch;
109
- - policy changes mid-stream: re-interpose relay through replay hand-off;
110
- - account reauth changes facade/ACL: rebuild route policy before accepting new writes;
111
- - endpoint/session material is revoked: close direct and resume relay from `seq`;
112
- - audit/shadow route lags: decide whether to throttle, fallback, or block by policy.
113
-
114
- Acceptance tests for 0.1:
115
-
116
- - policy denial: direct is never attempted;
117
- - direct promotion: old relay stays live until replacement catches up;
118
- - failed direct: old relay continues with no data gap;
119
- - re-interposition: direct -> relay resumes from `seq`;
120
- - `direct+shadowRelay`: direct path is active while relay/audit mirror observes;
121
- - revocation: direct closes and relay resumes without changing facade API;
122
- - account map uses `noStrict`, but all access checks live in policy/facade code;
123
- - `createStoreManager` starts/stops selected per-account mirrors without store-core changes.
124
-
125
- Open questions: whether the relay sees payloads or only coordinates encrypted direct streams;
126
- backpressure across multi-hop and direct paths; group topology beyond a pair of accounts;
127
- `noStrict(accountMap)` / `createStoreManager` lifecycle integration for dynamic peer maps.
128
-
129
- Status: 🟡 partial (2026-07-09, v1.0.67). Core implemented as `Replay.createRouteCoordinator`
130
- (`src/Common/events/route-coordinator.ts`): `RouteConnector` contract (pure transport: open/close/state/
131
- metrics/onFail/capabilities), all five policy hooks, the full state machine above (including
132
- `direct+shadowRelay` audit copy, catch-up timeout, revocation auto-fallback, terminal `blocked`), data
133
- continuity through `replayRouteSubscribe`. Acceptance oracle: `replay/route-coordinator.test.ts` over
134
- fake in-process connectors — policy denial never touches transport, promotion keeps the old relay live,
135
- failed/slow direct falls back gap-free, re-interposition resumes from `seq`, shadow relay observes the
136
- switch window, revocation closes direct without facade changes, block is terminal.
137
- v1.0.68 added step 9: `createSignalHub` (offer/answer/ICE/session/revoke over the EXISTING socket/RPC
138
- control channel; `authorize` = server-side `canExposeEndpoint`), `createWebRtcConnector` /
139
- `acceptWebRtcDirect` (RTCPeerConnection injected as a runtime factory, structural types, no lib.dom),
140
- and `serveReplayChannel`/`channelReplayRemote` (replay wire over any ordered channel — the datachannel
141
- path bypasses the RPC core by design). Oracle `replay/route-webrtc.test.ts` drives promotion, endpoint
142
- denial, session rejection, and server revoke over both an in-proc hub and a real Socket.IO/RPC wire.
143
- Still open: browser/Node WebRTC glue (step 10 — now a one-line `rtc` factory plus media re-emit) and
144
- the account-map lifecycle integration (step 7). Media-side candidates (2026-07-10, after the demo
145
- stand stress test): a real `transport:'webrtc'` media track — SDP over the existing signal hub,
146
- media bypassing the socket relay for call-grade smoothness; and `MediaStreamTrackProcessor` capture
147
- for true 30fps (`grabFrame`'s ~50ms serial latency caps snapshot capture at ~15-20fps).
148
-
149
- Consumption-layer candidates (2026-07-10, author's ask):
150
- - **Call system** — one account calls another, messenger-style: presence (the host already knows
151
- who is connected), ring / accept / decline envelopes over the existing signal hub, then media
152
- over the socket relay (mainstream messengers route calls through their servers too — relay-first
153
- is the privacy default, `promoteDirect` stays the opt-in). Builds entirely on shipped parts:
154
- peer host + signal hub + media lines + `Media.attach*` viewers.
155
- - **Store-descriptor media bridge** ("streaming center") the store carries a small descriptor
156
- (`{kind: 'video', sourceId, state}`), a bridge watches the mirror and auto-attaches/detaches
157
- `Media.attachVideoCanvas`/`attachAudioPlayer` to the matching binary line. Bytes flow only while
158
- subscribed (native Listen-over-RPC semantics). Pairs with the React-hooks direction.
159
- - 🧊 **Zero-parse patch format** (super-far future) Cap'n Proto / FlatBuffers-style layout for
160
- store patches: fields read directly from the received buffer, no parse stage. Only if a real
161
- consumer hits a serialization wall; JSON envelopes are nowhere near the bottleneck today
162
- (measured 2-3ms e2e on the stand).
163
-
164
- ## 1. Connection hand-off relay direct promotion ("port forwarding") 🟡
165
-
166
- A relay/intermediary bootstraps a connection between two parties, then — on a signal — **steps out
167
- of the middle**: both ends open a second, direct socket and migrate the live stream onto it,
168
- bypassing the relay. The same mechanism must work in reverse: the relay can deliberately
169
- **re-interpose** and become the middleman again. Under the hood the socket substitutes itself; data
170
- starts flowing on the new path.
171
-
172
- - Family: NAT hole-punching / WebRTC TURN→direct promotion, expressed over the existing `{emit,on}`
173
- abstraction rather than a specific transport.
174
- - **Design lead (reuse what exists):** the socket swap is a *stream resume*, which the replay layer
175
- already solves. Migrate with `replaySubscribe(..., {since: prev.seq()})` /
176
- `syncStoreReplay(..., {since})` on the new socket — the consumer fold is gap-free by contract, so a
177
- mid-stream transport change is not a special case. The transport carries only `seq`; the semantics
178
- never learn the socket changed.
179
- - **Implemented foundation (2026-07-08):** `Replay.replayRouteSubscribe(...)` and
180
- `Observe.syncStoreReplayRoute(...)` keep the old route alive, subscribe the replacement route,
181
- catch it up from the last delivered `seq`, then close the old route. This covers relay → direct
182
- promotion and direct → relay re-interposition for any ordered `ReplayRemote`; overlap is deduped by
183
- `seq`, and a failed replacement leaves the old route active.
184
- - Open questions: auth continuity across the swap; per-socketKey vs whole-connection hand-off;
185
- policy trigger rules for direct → relay re-interposition beyond explicit calls and `onFail`.
186
- - Status: 🟡 mostly done. Route hand-off/resume: `replay/route-handoff.test.ts`. Route decisions +
187
- state machine: `createRouteCoordinator` (v1.0.67). Signaling + direct endpoint negotiation over the
188
- existing control channel and fallback-if-never-establishes: `createSignalHub` /
189
- `createWebRtcConnector` / `acceptWebRtcDirect` (v1.0.68, `replay/route-webrtc.test.ts`). Remaining:
190
- real NAT/WebRTC runtime glue (injected `rtc` factory) and auth-continuity policy.
191
-
192
- ## 2. Coordinated fan-out send to a large group 🧊
193
-
194
- Synchronized/batched send to a very large audience "at once", as opposed to the current model where
195
- **each connection is paced independently** (per-connection lag gate + `frame` policy).
196
-
197
- - Current reality: pacing is per-recipient by design every client's link is unique. Even in video
198
- fan-out no two receivers drain at the same rate, so the per-connection gate is usually the *correct*
199
- answer. That is why this is shelved.
200
- - When it would matter: true lockstep broadcast (all-or-nothing / same-instant delivery) rare.
201
- - Status: 🧊 deliberately shelved. Revisit only against a concrete lockstep requirement.
202
-
203
- ## 3. Multi-authority stores two truths, one reconciliation (games) 🔴
204
-
205
- Two stores, each **dynamically filled by its own authority ("truth")**, that must agree on a shared
206
- source of truth. Decompose by write topology:
207
-
208
- - **Partitioned authority fits today.** Each peer authoritative over its own slice → two
209
- `exposeStore` / `createStoreMirror` pairs crossed over one duplex `{emit,on}`. No new primitive.
210
- "Confirmation" = the per-direction `seq` ack.
211
- - **Authoritative server + client prediction small layer on top.** Server store is the truth; the
212
- client applies optimistically, then reconciles when the authoritative patch arrives. Build a
213
- `predictedStore` = confirmed mirror + a pending-input list, rebased on each `mirror.each()` /
214
- `changed` tick. Helper factory, not a core change.
215
- - **Symmetric co-write of the same path needs a different model.** LWW-per-path converges but can
216
- silently drop a concurrent write. This is the one case that genuinely needs CRDT/OT. Design lead:
217
- wrap a Yjs/Automerge doc as a `RemoteStore`-shaped source and mirror from it — the store↔transport
218
- decoupling makes this a small adapter, not a rewrite.
219
- - Status: 🧊 deferred, super-low priority. Prediction layer waits for the demo app to demand and shape
220
- it (`doc/target/library-uplift-tasks.md` task 4); CRDT adapter reopens only on a real co-write need.
221
- Partitioned-authority already expressible today.
222
-
223
- ## 4. Data-transfer optimization backlog (ongoing) 🟡
224
-
225
- Open-ended transfer/perf work, especially for backend-heavy models. Never "done".
226
-
227
- - Candidates: tighter `frame` condensation per line; delta/patch minimization; binary framing for hot
228
- paths; batching heuristics beyond the current `pipe` / `space` modes.
229
- - Status: 🧊 deferred, super-low priority; pick items only as real bottlenecks surface in the SDK/demo
230
- consumers, not speculatively.
1
+ # wenay-common2 — Roadmap (open / deferred)
2
+
3
+ > Forward-looking backlog of distributed-state / transport features that are **not fully built**.
4
+ > Everything here sits on top of the existing seams — `{emit,on}` transport, `exposeStore` /
5
+ > `createStoreMirror`, and the replay `seq` / `keyframe` / `frame` contract. None of it requires
6
+ > changing the store core; the store stays single-authority (one `seq` sequencer, last-writer-wins
7
+ > per path), and these features are layers or adapters above that.
8
+ > Status: 🔴 not started · 🟡 partial / ongoing · 🧊 deferred (super-low priority, not forbidden).
9
+ >
10
+ > Current focus is NOT more transport: see `doc/target/library-uplift-tasks.md` (showcase, SDK facade,
11
+ > vertical demo app). Transport items below stay available but rank below everything in that plan.
12
+
13
+ ## 0. Distributed Runtime Model
14
+
15
+ The strategic direction is not "RPC plus store helpers"; it is a small distributed-state runtime:
16
+ application code keeps a stable typed API while lower layers can choose the transport, route, replay
17
+ source, and authority model.
18
+
19
+ Four concerns must stay explicit:
20
+
21
+ - **Transport** - how two endpoints exchange `{emit,on}` messages: socket.io, in-process loopback,
22
+ relay, WebRTC/direct channel, or a future adapter. Transport is replaceable infrastructure.
23
+ - **Routing** - where a logical call or stream goes: server, peer through relay, peer directly, or a
24
+ promoted relay <-> direct path. Routing must be allowed to change without changing the facade API.
25
+ - **Authority** - who is allowed to write truth: one server, partitioned peer-owned slices, server
26
+ authority with client prediction, or true multi-writer conflict resolution. This is a semantic
27
+ decision, not a transport decision.
28
+ - **Replay** - how live state survives reconnect, lag, transport swap, and history playback:
29
+ `seq` + keyframe/frame + deltas. Replay is the continuity layer that makes route changes boring.
30
+
31
+ Design rule: direct peer links and relay hand-offs are optimizations of transport/routing. They must
32
+ not silently change auth, ownership, validation, or conflict semantics. If a method is exposed as
33
+ `api.hit(playerId)`, it may travel through the server or a direct channel, but the authority rules
34
+ behind that method must remain the same.
35
+
36
+ This implies a useful split:
37
+
38
+ - **API surface** remains facade-shaped and typed.
39
+ - **Transport/routing layer** may substitute sockets and promote paths.
40
+ - **Replay layer** resumes streams and mirrors from the last known `seq`.
41
+ - **Authority layer** decides whether an incoming write is accepted, predicted, reconciled, or merged.
42
+
43
+ Critical ordering rule: do **not** start with WebRTC/NAT plumbing or CRDTs. The next useful layer is a
44
+ small route/account/policy coordinator with a fake/in-process transport adapter. Direct transport and
45
+ multi-writer merge are expensive adapters; they only become safe after the coordinator state machine is
46
+ boring and well-tested.
47
+
48
+ ### 0.1 Account route coordinator ("wrapper over wrappers") 🟡
49
+
50
+ Some deployments need separate client/account identities to communicate directly when policy and
51
+ network conditions allow it, while still allowing the server/relay to step back into the path later.
52
+ This is not just a socket trick; it is an account-aware routing shell above the existing facade,
53
+ mirror, and replay primitives.
54
+
55
+ - **Account model:** every participant has its own account/session identity and a scoped facade/store
56
+ set. Runtime-account maps are a dynamic keyspace, so they should look like `noStrict(accountMap)`
57
+ rather than a fixed schema. Access checks stay in the facade/policy layer; `noStrict` is not an ACL.
58
+ - **Wrapper over wrappers:** an app-level coordinator owns the set of per-account clients, exposed
59
+ facades, mirrors, replay subscriptions, and route state. "State of other accounts" is represented as
60
+ selected `Observe` mirrors/replay/offline resources, ideally started and stopped through
61
+ `createStoreManager`, not as a new global store core.
62
+ - **Route states:** a pair of accounts may be `relay`, `direct`, `direct+shadowRelay` (audit/observe
63
+ copy), `blocked`, or `fallback`. Direct links are optional optimizations, useful for latency or
64
+ traffic cost, not a semantic change.
65
+ - **Re-interposition:** if NDA/privacy policy, audit, moderation, throttling, reauth, direct-link
66
+ failure, or group topology changes require it, the relay must be able to re-enter the data path.
67
+ This is the reverse of direct promotion: open the replacement route, resume from the last `seq`,
68
+ switch consumers after catch-up, then close or demote the old route.
69
+ - **Privacy rule:** direct account links are opt-in and policy-gated. Peers only receive the endpoint
70
+ and session material needed for that specific relationship; no implicit broad account discovery.
71
+
72
+ Concrete next API shape, not final names:
73
+
74
+ ```ts
75
+ createRouteCoordinator({
76
+ policy,
77
+ routes,
78
+ resources,
79
+ }) -> {
80
+ pair(a, b),
81
+ state(pair),
82
+ promoteDirect(pair, opts?),
83
+ reinterposeRelay(pair, reason?),
84
+ block(pair, reason?),
85
+ fallback(pair, reason?),
86
+ onRoute(cb),
87
+ }
88
+ ```
89
+
90
+ Policy must run **before** transport promotion:
91
+
92
+ - `canDirect(pair, ctx)` - may these accounts attempt direct at all?
93
+ - `mustRelay(pair, ctx)` - force relay path because of NDA/audit/moderation/reauth.
94
+ - `mustShadowRelay(pair, ctx)` - allow direct payload path, but keep audit/observe copy.
95
+ - `canExposeEndpoint(pair, ctx)` - whether signaling may reveal endpoint/session material.
96
+ - `canReinterpose(pair, ctx)` - whether/when relay is allowed or required to step back in.
97
+
98
+ Minimum state machine:
99
+
100
+ - `relay` -> `direct:connecting` -> `direct` -> `relay:reinterposing` -> `relay`
101
+ - `relay` -> `direct:connecting` -> `fallback` -> `relay`
102
+ - `direct` -> `direct+shadowRelay`
103
+ - any state -> `blocked`
104
+
105
+ Required failure modes:
106
+
107
+ - direct setup never completes: keep relay active and mark fallback;
108
+ - replacement route catches up too slowly: keep old route active and fail the switch;
109
+ - policy changes mid-stream: re-interpose relay through replay hand-off;
110
+ - account reauth changes facade/ACL: rebuild route policy before accepting new writes;
111
+ - endpoint/session material is revoked: close direct and resume relay from `seq`;
112
+ - audit/shadow route lags: decide whether to throttle, fallback, or block by policy.
113
+
114
+ Acceptance tests for 0.1:
115
+
116
+ - policy denial: direct is never attempted;
117
+ - direct promotion: old relay stays live until replacement catches up;
118
+ - failed direct: old relay continues with no data gap;
119
+ - re-interposition: direct -> relay resumes from `seq`;
120
+ - `direct+shadowRelay`: direct path is active while relay/audit mirror observes;
121
+ - revocation: direct closes and relay resumes without changing facade API;
122
+ - account map uses `noStrict`, but all access checks live in policy/facade code;
123
+ - `createStoreManager` starts/stops selected per-account mirrors without store-core changes.
124
+
125
+ Open questions: whether the relay sees payloads or only coordinates encrypted direct streams;
126
+ backpressure across multi-hop and direct paths; group topology beyond a pair of accounts;
127
+ `noStrict(accountMap)` / `createStoreManager` lifecycle integration for dynamic peer maps.
128
+
129
+ Status: 🟡 partial (2026-07-09, v1.0.67). Core implemented as `Replay.createRouteCoordinator`
130
+ (`src/Common/events/route-coordinator.ts`): `RouteConnector` contract (pure transport: open/close/state/
131
+ metrics/onFail/capabilities), all five policy hooks, the full state machine above (including
132
+ `direct+shadowRelay` audit copy, catch-up timeout, revocation auto-fallback, terminal `blocked`), data
133
+ continuity through `replayRouteSubscribe`. Acceptance oracle: `replay/route-coordinator.test.ts` over
134
+ fake in-process connectors — policy denial never touches transport, promotion keeps the old relay live,
135
+ failed/slow direct falls back gap-free, re-interposition resumes from `seq`, shadow relay observes the
136
+ switch window, revocation closes direct without facade changes, block is terminal.
137
+ v1.0.68 added step 9: `createSignalHub` (offer/answer/ICE/session/revoke over the EXISTING socket/RPC
138
+ control channel; `authorize` = server-side `canExposeEndpoint`), `createWebRtcConnector` /
139
+ `acceptWebRtcDirect` (RTCPeerConnection injected as a runtime factory, structural types, no lib.dom),
140
+ and `serveReplayChannel`/`channelReplayRemote` (replay wire over any ordered channel — the datachannel
141
+ path bypasses the RPC core by design). Oracle `replay/route-webrtc.test.ts` drives promotion, endpoint
142
+ denial, session rejection, and server revoke over both an in-proc hub and a real Socket.IO/RPC wire.
143
+ Still open: browser/Node WebRTC glue (step 10 — now a one-line `rtc` factory plus media re-emit) and
144
+ the account-map lifecycle integration (step 7). Media-side candidates (2026-07-10, after the demo
145
+ stand stress test): a real `transport:'webrtc'` media track — SDP over the existing signal hub,
146
+ media bypassing the socket relay for call-grade smoothness; and `MediaStreamTrackProcessor` capture
147
+ for true 30fps (`grabFrame`'s ~50ms serial latency caps snapshot capture at ~15-20fps).
148
+
149
+ Consumption-layer candidates (2026-07-10, author's ask):
150
+ - **Call system** — SHIPPED in v1.0.74: `Peer.createCallManager` (ring/accept/decline/hangup as
151
+ envelopes over the existing signal hub zero new server routes, the host `authorize` hook is the
152
+ single server-side policy point), host `presence` (fragment key, refcounted edges), and
153
+ `Peer.createMediaRelay` (the demo media hub promoted into the library; relay-first stays the
154
+ privacy default, `promoteDirect` the opt-in). Oracle: `replay/peer-call.test.ts`. Consumer step
155
+ shipped too: the demo stand rings/accepts/declines with presence, media attaches only while the
156
+ call is active, and the watch-ACL grant set lives in the host `authorize` hook (verified headless,
157
+ two Chromium tabs, zero page errors).
158
+ - **Store-descriptor media bridge** ("streaming center") the store carries a small descriptor
159
+ (`{kind: 'video', sourceId, state}`), a bridge watches the mirror and auto-attaches/detaches
160
+ `Media.attachVideoCanvas`/`attachAudioPlayer` to the matching binary line. Bytes flow only while
161
+ subscribed (native Listen-over-RPC semantics). Pairs with the React-hooks direction.
162
+ - 🧊 **Zero-parse patch format** (super-far future) — Cap'n Proto / FlatBuffers-style layout for
163
+ store patches: fields read directly from the received buffer, no parse stage. Only if a real
164
+ consumer hits a serialization wall; JSON envelopes are nowhere near the bottleneck today
165
+ (measured 2-3ms e2e on the stand).
166
+
167
+ ## 1. Connection hand-off relay direct promotion ("port forwarding") 🟡
168
+
169
+ A relay/intermediary bootstraps a connection between two parties, then on a signal — **steps out
170
+ of the middle**: both ends open a second, direct socket and migrate the live stream onto it,
171
+ bypassing the relay. The same mechanism must work in reverse: the relay can deliberately
172
+ **re-interpose** and become the middleman again. Under the hood the socket substitutes itself; data
173
+ starts flowing on the new path.
174
+
175
+ - Family: NAT hole-punching / WebRTC TURN→direct promotion, expressed over the existing `{emit,on}`
176
+ abstraction rather than a specific transport.
177
+ - **Design lead (reuse what exists):** the socket swap is a *stream resume*, which the replay layer
178
+ already solves. Migrate with `replaySubscribe(..., {since: prev.seq()})` /
179
+ `syncStoreReplay(..., {since})` on the new socket — the consumer fold is gap-free by contract, so a
180
+ mid-stream transport change is not a special case. The transport carries only `seq`; the semantics
181
+ never learn the socket changed.
182
+ - **Implemented foundation (2026-07-08):** `Replay.replayRouteSubscribe(...)` and
183
+ `Observe.syncStoreReplayRoute(...)` keep the old route alive, subscribe the replacement route,
184
+ catch it up from the last delivered `seq`, then close the old route. This covers relay → direct
185
+ promotion and direct → relay re-interposition for any ordered `ReplayRemote`; overlap is deduped by
186
+ `seq`, and a failed replacement leaves the old route active.
187
+ - Open questions: auth continuity across the swap; per-socketKey vs whole-connection hand-off;
188
+ policy trigger rules for direct → relay re-interposition beyond explicit calls and `onFail`.
189
+ - Status: 🟡 mostly done. Route hand-off/resume: `replay/route-handoff.test.ts`. Route decisions +
190
+ state machine: `createRouteCoordinator` (v1.0.67). Signaling + direct endpoint negotiation over the
191
+ existing control channel and fallback-if-never-establishes: `createSignalHub` /
192
+ `createWebRtcConnector` / `acceptWebRtcDirect` (v1.0.68, `replay/route-webrtc.test.ts`). Remaining:
193
+ real NAT/WebRTC runtime glue (injected `rtc` factory) and auth-continuity policy.
194
+
195
+ ## 2. Coordinated fan-out send to a large group 🧊
196
+
197
+ Synchronized/batched send to a very large audience "at once", as opposed to the current model where
198
+ **each connection is paced independently** (per-connection lag gate + `frame` policy).
199
+
200
+ - Current reality: pacing is per-recipient by design every client's link is unique. Even in video
201
+ fan-out no two receivers drain at the same rate, so the per-connection gate is usually the *correct*
202
+ answer. That is why this is shelved.
203
+ - When it would matter: true lockstep broadcast (all-or-nothing / same-instant delivery) — rare.
204
+ - Status: 🧊 deliberately shelved. Revisit only against a concrete lockstep requirement.
205
+
206
+ ## 3. Multi-authority stores two truths, one reconciliation (games) 🔴
207
+
208
+ Two stores, each **dynamically filled by its own authority ("truth")**, that must agree on a shared
209
+ source of truth. Decompose by write topology:
210
+
211
+ - **Partitioned authority fits today.** Each peer authoritative over its own slice two
212
+ `exposeStore` / `createStoreMirror` pairs crossed over one duplex `{emit,on}`. No new primitive.
213
+ "Confirmation" = the per-direction `seq` ack.
214
+ - **Authoritative server + client prediction small layer on top.** Server store is the truth; the
215
+ client applies optimistically, then reconciles when the authoritative patch arrives. Build a
216
+ `predictedStore` = confirmed mirror + a pending-input list, rebased on each `mirror.each()` /
217
+ `changed` tick. Helper factory, not a core change.
218
+ - **Symmetric co-write of the same path — needs a different model.** LWW-per-path converges but can
219
+ silently drop a concurrent write. This is the one case that genuinely needs CRDT/OT. Design lead:
220
+ wrap a Yjs/Automerge doc as a `RemoteStore`-shaped source and mirror from it the store↔transport
221
+ decoupling makes this a small adapter, not a rewrite.
222
+ - Status: 🧊 deferred, super-low priority. Prediction layer waits for the demo app to demand and shape
223
+ it (`doc/target/library-uplift-tasks.md` task 4); CRDT adapter reopens only on a real co-write need.
224
+ Partitioned-authority already expressible today.
225
+
226
+ ## 4. Data-transfer optimization backlog (ongoing) 🟡
227
+
228
+ Open-ended transfer/perf work, especially for backend-heavy models. Never "done".
229
+
230
+ - Candidates: tighter `frame` condensation per line; delta/patch minimization; binary framing for hot
231
+ paths; batching heuristics beyond the current `pipe` / `space` modes.
232
+ - Status: 🧊 deferred, super-low priority; pick items only as real bottlenecks surface in the SDK/demo
233
+ consumers, not speculatively.
@@ -0,0 +1,12 @@
1
+ # 1.0.74
2
+
3
+ - Call system (`peer-call`, additive) — one account calls another, messenger-style, with ZERO new server routes: `Peer.createCallManager({port: callPortOf(remote), self})` drives ring/accept/decline/hangup envelopes over the EXISTING signal hub (`tSignalType` widened additively; the host `authorize` hook sees call envelopes too — one server-side policy point). Call handle: `state()/reason()/changed/ended/accept()/decline()/hangup()`; offline callee fails FAST (send verdict, no timeout wait); ring timeout expires both sides; default incoming gate auto-declines `'busy'` during a live call (also settles glare); `manager.ready` = subscription registration ack via a self-probe on the same ordered socket.
4
+ - Presence in the peer host (additive fragment key) — `presence: {list(), changes}`: refcounted per account, edges on 0↔1 connection-count transitions only. Subscribe first, then `list()`.
5
+ - Media relay formalized (`Peer.createMediaRelay`) — the demo stand's media hub promoted into the library: per-account named replay lines (`'video'` keep-latest + `current:'last'`, `'audio'` short lossless queue), `[frame, sentAt]` stamps for viewer latency stats, `watch` as a `noStrict` dynamic map (peers-map pattern), eager `publishOf` so watchers resolve the account path the moment the connection is wired. Relay-first is the privacy default; `promoteDirect` stays the opt-in.
6
+ - Media watch ACL (`watchOf(watcher)` + `canWatch(watcher, owner, line)`) — per-connection policy views gate every rpc path resolution and every forwarded live frame/keyframe. Revocation therefore blocks an already-open subscription without trusting the client to detach; `dropAccount(account)` still closes lines loudly and frees the keyspace. The unfiltered `watch` map remains for trusted wiring.
7
+ - Oracle `replay/peer-call.test.ts` — real Socket.IO/RPC wire: presence on/off edges, ring→accept→active, media frames + keep-latest `keyframe()` while active, busy gate, decline, hangup, offline fast-fail, ring timeout with callee cancel.
8
+ - Demo stand: messenger-style calls — presence indicator, call/accept/decline/hangup buttons, peer media viewers attach ONLY while the call is active. A tiny server-owned call lifecycle inside the host `authorize` hook proves ring/accept participants before granting media, so a forged `accept` cannot mint access; decline/hangup/offline revoke it. Verified headless (two Chromium tabs, fake camera): ring→accept→frames→hangup→decline, zero page errors.
9
+ - Peer host: `peers` map auto-creates an EMPTY relay journal on first touch (Proxy over the dynamic keyspace) — a mirror subscribed BEFORE its owner ever connected now waits for the first publish instead of failing loudly (the demo A-tab-loads-first race). Optional `accounts(account)` host hook gates junk keys on public servers.
10
+ - Media viewers: `viewer.off()` now detaches RPC-projected lines too (the deep proxy returns a promise of the subscription handle; local lines keep detaching synchronously — order-sensitive: a callable handle is also thenable).
11
+ - Lifecycle hardening: back-to-back incoming rings atomically reserve the default busy gate; same-account signaling registrations fall back to the previous live port when the newest tab closes; call ids stay distinct across manager restarts/tabs. Regression coverage lives in `replay/peer-call.test.ts`.
12
+ - Docs: brief SDK section "Calls, presence and the media relay"; rare reference for `createCallManager` / `createMediaRelay` / presence / widened `SignalEnvelope`.