@unrulysystems/native-motion 0.1.0-alpha.0

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 (117) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +55 -0
  4. package/android/build.gradle +24 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/com/unrulysystems/nativemotion/GestureExclusionModule.kt +55 -0
  7. package/android/src/main/java/com/unrulysystems/nativemotion/NativeMotionPackage.kt +27 -0
  8. package/package.json +57 -0
  9. package/react-native.config.js +14 -0
  10. package/src/native/driver/instantWindow.ts +31 -0
  11. package/src/native/driver/strictModeReplay.ts +20 -0
  12. package/src/native/driver/uiLayoutEngine.ts +845 -0
  13. package/src/native/driver/uiLayoutGraph.ts +338 -0
  14. package/src/native/driver/uiValueChannel.ts +4640 -0
  15. package/src/native/driver/workletDriver.ts +3860 -0
  16. package/src/native/motion/AnimatePresence.tsx +1605 -0
  17. package/src/native/motion/LayoutGroup.tsx +163 -0
  18. package/src/native/motion/MotionConfig.tsx +400 -0
  19. package/src/native/motion/MotionRoot.tsx +250 -0
  20. package/src/native/motion/MotionView.tsx +6761 -0
  21. package/src/native/motion/addScaleCorrector.ts +97 -0
  22. package/src/native/motion/colorEndpointUiFeed.ts +32 -0
  23. package/src/native/motion/colorProjection.ts +39 -0
  24. package/src/native/motion/composeTransform.ts +271 -0
  25. package/src/native/motion/constraintMeasure.ts +157 -0
  26. package/src/native/motion/deferredPendingSnapshots.ts +25 -0
  27. package/src/native/motion/discreteProjection.ts +36 -0
  28. package/src/native/motion/dragAncestorPanContext.ts +28 -0
  29. package/src/native/motion/dragControls.ts +152 -0
  30. package/src/native/motion/dragGestureWiring.ts +1168 -0
  31. package/src/native/motion/dragHandoffBinding.ts +179 -0
  32. package/src/native/motion/dragHubStream.ts +756 -0
  33. package/src/native/motion/dragPropagationLock.ts +57 -0
  34. package/src/native/motion/driverValueChannel.ts +3288 -0
  35. package/src/native/motion/externalDragDriver.ts +214 -0
  36. package/src/native/motion/frameData.ts +31 -0
  37. package/src/native/motion/gestureBinding.ts +107 -0
  38. package/src/native/motion/gestureStateGate.ts +563 -0
  39. package/src/native/motion/gestureStateResolver.ts +286 -0
  40. package/src/native/motion/identityValueChannelControllerAdapter.ts +2043 -0
  41. package/src/native/motion/identityValueChannelControllerReconciliation.ts +176 -0
  42. package/src/native/motion/identityValueChannelLaneMarker.ts +14 -0
  43. package/src/native/motion/imperativeAnimate.ts +816 -0
  44. package/src/native/motion/keyframeTiming.ts +7 -0
  45. package/src/native/motion/layoutIdBinding.ts +261 -0
  46. package/src/native/motion/layoutIdFlightConfig.ts +51 -0
  47. package/src/native/motion/layoutProjection.ts +89 -0
  48. package/src/native/motion/layoutScroll.ts +204 -0
  49. package/src/native/motion/layoutTransition.ts +1302 -0
  50. package/src/native/motion/lengthLayoutContext.tsx +100 -0
  51. package/src/native/motion/lengthLayoutHost.ts +44 -0
  52. package/src/native/motion/mappedKeys.ts +184 -0
  53. package/src/native/motion/motionViewController.ts +3180 -0
  54. package/src/native/motion/nativeHostMarker.ts +22 -0
  55. package/src/native/motion/panSessionWiring.ts +127 -0
  56. package/src/native/motion/pathTransition.ts +101 -0
  57. package/src/native/motion/popLayout.ts +176 -0
  58. package/src/native/motion/presenceBinding.ts +382 -0
  59. package/src/native/motion/scaleCorrectorRegistry.ts +123 -0
  60. package/src/native/motion/scrollValues.ts +260 -0
  61. package/src/native/motion/serializablePayload.ts +151 -0
  62. package/src/native/motion/severity.ts +16 -0
  63. package/src/native/motion/shippedSurface.ts +843 -0
  64. package/src/native/motion/staticLengthGate.ts +86 -0
  65. package/src/native/motion/styleBaseGate.ts +72 -0
  66. package/src/native/motion/styleValueBinding.ts +657 -0
  67. package/src/native/motion/systemGestureExclusion.ts +61 -0
  68. package/src/native/motion/tapGestureWiring.ts +215 -0
  69. package/src/native/motion/transformOrder.ts +38 -0
  70. package/src/native/motion/transformStringBinding.ts +200 -0
  71. package/src/native/motion/transformTemplateGate.ts +45 -0
  72. package/src/native/motion/transitionGate.ts +435 -0
  73. package/src/native/motion/useAnimate.ts +52 -0
  74. package/src/native/motion/useCycle.ts +44 -0
  75. package/src/native/motion/useInstantTransition.ts +68 -0
  76. package/src/native/motion/useReducedMotion.ts +58 -0
  77. package/src/native/motion/useScroll.ts +162 -0
  78. package/src/native/motion/useViewportScroll.ts +29 -0
  79. package/src/native/motion/valueChannel.ts +7121 -0
  80. package/src/native/motion/valueHooks.ts +901 -0
  81. package/src/native/motion/variantChildRegistry.ts +67 -0
  82. package/src/native/motion/variantContext.tsx +172 -0
  83. package/src/native/motion/variantProps.ts +598 -0
  84. package/src/native.ts +159 -0
  85. package/src/verification/harnessMetrics.ts +78 -0
  86. package/src/verification/probe/LayoutIdentityWorkletProbe.tsx +251 -0
  87. package/src/verification/probe/WorkletParityProbe.tsx +162 -0
  88. package/src/verification/probe/layoutIdentityProbeEngine.ts +429 -0
  89. package/src/verification/screens/ArcPathChecksScreen.tsx +273 -0
  90. package/src/verification/screens/BooleanAnimateChecksScreen.tsx +358 -0
  91. package/src/verification/screens/ChoreographyGalleryScreen.tsx +1010 -0
  92. package/src/verification/screens/ColorBindingChecksScreen.tsx +568 -0
  93. package/src/verification/screens/CompletionChecksScreen.tsx +294 -0
  94. package/src/verification/screens/ConformanceScreen.tsx +573 -0
  95. package/src/verification/screens/ContentionProbeScreen.tsx +224 -0
  96. package/src/verification/screens/DriverSmokeScreen.tsx +99 -0
  97. package/src/verification/screens/DurationOnlyTweenChecksScreen.tsx +294 -0
  98. package/src/verification/screens/DynamicDragConfigChecksScreen.tsx +237 -0
  99. package/src/verification/screens/FrameDataChecksScreen.tsx +252 -0
  100. package/src/verification/screens/GestureChecksScreen.tsx +685 -0
  101. package/src/verification/screens/InstantTransitionChecksScreen.tsx +839 -0
  102. package/src/verification/screens/LayoutAnimationStartChecksScreen.tsx +648 -0
  103. package/src/verification/screens/LayoutChecksScreen.tsx +822 -0
  104. package/src/verification/screens/LayoutCommitSpikeScreen.tsx +142 -0
  105. package/src/verification/screens/MotionViewChecksScreen.tsx +930 -0
  106. package/src/verification/screens/PresenceChecksScreen.tsx +614 -0
  107. package/src/verification/screens/ReducedMotionChecksScreen.tsx +614 -0
  108. package/src/verification/screens/RestThresholdChecksScreen.tsx +634 -0
  109. package/src/verification/screens/ScaleCorrectorChecksScreen.tsx +425 -0
  110. package/src/verification/screens/SharedLayoutContinuityChecksScreen.tsx +338 -0
  111. package/src/verification/screens/SharedLayoutCrossfadeChecksScreen.tsx +425 -0
  112. package/src/verification/screens/TransitionDefaultSelectionChecksScreen.tsx +514 -0
  113. package/src/verification/screens/ViewportScrollAliasChecksScreen.tsx +303 -0
  114. package/src/verification/screens/conformanceBanner.ts +21 -0
  115. package/src/verification/screens/proofConsoleTap.ts +18 -0
  116. package/src/verification.ts +47 -0
  117. package/src/web.ts +98 -0
@@ -0,0 +1,3288 @@
1
+ // Private JS-side value-channel handoff (REQ-VALUE-014/015, REQ-DRIVER-034): the exact-object
2
+ // identity registry and its retention reconciliation, the serializable handoff protocol machine
3
+ // (freeze → prepare → source-confirmed release/abort → immutable COMMIT → destination
4
+ // acknowledgement), tuple validation at every producer/consumer boundary, the JS destination
5
+ // COMMIT consumer with its preallocated acknowledgement slot, and the bounded post-RELEASE
6
+ // refusal drain. Every transition replaces state immutably; malformed, conflicting, or
7
+ // out-of-sequence input is refused with the file's typed vocabulary and never mutates retained
8
+ // protocol state.
9
+
10
+ import type { MotionValue } from '@unrulysystems/native-motion-core'
11
+ import type {
12
+ MotionValueDriverBindingEligibility,
13
+ MotionValueDriverPort,
14
+ MotionValueDriverPortObservation,
15
+ } from '@unrulysystems/native-motion-core/internal-driver'
16
+
17
+ export type ValueChannelSide = 'UI' | 'JS'
18
+ export type ValueHandoffDirection = 'UI_TO_JS' | 'JS_TO_UI'
19
+
20
+ export type ValueHandoffOwnership =
21
+ | 'IDLE'
22
+ | 'UI_RUNNING'
23
+ | 'UI_FROZEN_TO_JS'
24
+ | 'JS_PREPARED'
25
+ | 'UI_RELEASE_PENDING_TO_JS'
26
+ | 'UI_RELEASED_TO_JS'
27
+ | 'JS_RUNNING'
28
+ | 'JS_FROZEN_TO_UI'
29
+ | 'UI_PREPARED'
30
+ | 'JS_RELEASE_PENDING_TO_UI'
31
+ | 'JS_RELEASED_TO_UI'
32
+ | 'UI_DRAINING'
33
+ | 'JS_DRAINING'
34
+ | 'BOUNDED_REFUSAL_DRAINING'
35
+ | 'TERMINAL_REFUSED'
36
+
37
+ export interface ValueChannelHistoryState {
38
+ readonly currentValue: number
39
+ readonly previousValue: number
40
+ readonly currentTimestampMs: number
41
+ readonly previousTimestampMs: number
42
+ }
43
+
44
+ export interface ValueChannelSequenceState {
45
+ readonly authoritativeSequence: number
46
+ readonly commandSequence: number
47
+ readonly writeSequence: number
48
+ readonly eventSequence: number
49
+ readonly frameSequence: number
50
+ readonly dueWorkSequence: number
51
+ readonly valueCompletionSequence: number
52
+ readonly batchDependencyEventSequence: number
53
+ readonly dependencyOutboxAckSequence: number
54
+ }
55
+
56
+ export type NormalizedValueChannelEasingPlan =
57
+ | { readonly kind: 'LINEAR' }
58
+ | {
59
+ readonly kind: 'CUBIC_BEZIER'
60
+ readonly x1: number
61
+ readonly y1: number
62
+ readonly x2: number
63
+ readonly y2: number
64
+ }
65
+
66
+ export type NormalizedValueChannelGeneratorPlan =
67
+ | {
68
+ readonly kind: 'SPRING'
69
+ readonly from: number
70
+ readonly target: number
71
+ readonly initialVelocity: number
72
+ readonly stiffness: number
73
+ readonly damping: number
74
+ readonly mass: number
75
+ readonly restSpeed: number
76
+ readonly restDelta: number
77
+ readonly delayMs: number
78
+ }
79
+ | {
80
+ readonly kind: 'TIMING'
81
+ readonly keyframes: readonly number[]
82
+ readonly times: readonly number[]
83
+ readonly easing: readonly NormalizedValueChannelEasingPlan[]
84
+ readonly durationMs: number
85
+ readonly delayMs: number
86
+ readonly repeatCount: number
87
+ readonly repeatType: 'loop' | 'reverse' | 'mirror'
88
+ readonly repeatDelayMs: number
89
+ }
90
+ | {
91
+ readonly kind: 'INSTANT'
92
+ readonly target: number
93
+ readonly delayMs: number
94
+ }
95
+ | {
96
+ readonly kind: 'INERTIA'
97
+ readonly from: number
98
+ readonly initialVelocity: number
99
+ readonly power: number
100
+ readonly timeConstant: number
101
+ readonly bounceStiffness: number
102
+ readonly bounceDamping: number
103
+ readonly restDelta: number
104
+ readonly restSpeed: number
105
+ readonly min: number | null
106
+ readonly max: number | null
107
+ }
108
+
109
+ export interface NormalizedValueChannelWritePayload {
110
+ readonly writeSequence: number
111
+ readonly frameSequence: number
112
+ readonly value: number
113
+ readonly velocity: number
114
+ readonly previousValue: number
115
+ readonly previousTimestampMs: number
116
+ readonly currentTimestampMs: number
117
+ }
118
+
119
+ export type NormalizedValueChannelTaskCallbackPayload =
120
+ | {
121
+ readonly callbackKind: 'transition-on-update' | 'transition-on-complete'
122
+ readonly callbackId: number
123
+ readonly eventSequence: number
124
+ readonly callbackDeliverySequence: number
125
+ }
126
+ | {
127
+ readonly callbackKind: 'animation-complete'
128
+ readonly callbackId: number
129
+ readonly eventSequence: number
130
+ readonly callbackDeliverySequence: number
131
+ readonly completionSequence: number
132
+ }
133
+
134
+ export type NormalizedValueChannelCarrierValue =
135
+ | { readonly kind: 'number'; readonly value: number }
136
+ | { readonly kind: 'keyword'; readonly value: string }
137
+
138
+ export type ValueHandoffDestinationLedgerKind =
139
+ | 'PUBLIC_CONTROL'
140
+ | 'RETENTION'
141
+ | 'EXECUTION'
142
+ | 'DUE_WORK'
143
+ | 'COMPLETION'
144
+ | 'DEPENDENCY_EVENT'
145
+ | 'GENERATION'
146
+ | 'SEQUENCE'
147
+
148
+ interface ValueHandoffDestinationLedgerSlot<Ledger extends ValueHandoffDestinationLedgerKind> {
149
+ readonly ledger: Ledger
150
+ readonly slotId: number
151
+ }
152
+
153
+ export interface ValueChannelHandoffResourceState {
154
+ readonly preparedStateRetained: boolean
155
+ readonly preparedCloneRetained: boolean
156
+ readonly transferActivityRetained: boolean
157
+ readonly transferTimerArmed: boolean
158
+ }
159
+
160
+ export interface DriverValueChannelIdentityRecordState {
161
+ readonly channelId: number
162
+ readonly jsRecordId: number
163
+ readonly uiRecordId: number
164
+ readonly creationKind: 'direct-public-finite-number'
165
+ readonly publicReferenceRetained: boolean
166
+ readonly sinkCount: number
167
+ readonly changeObserverCount: number
168
+ readonly publicControlSlotId: number | null
169
+ readonly physicalExecutionIds: readonly number[]
170
+ readonly dueWorkSequences: readonly number[]
171
+ readonly valueCompletionSequences: readonly number[]
172
+ readonly targetBatchDependencyIds: readonly number[]
173
+ readonly dependencyEventOutboxSequences: readonly number[]
174
+ readonly dependencyEventAckWatermark: number
175
+ readonly sharedBatchReferenceCount: number
176
+ readonly pendingFrameActivity: boolean
177
+ readonly currentBindingGeneration: number
178
+ readonly retiredBindingGeneration: number
179
+ readonly directOperationEpoch: number
180
+ readonly executionLeaseEpoch: number
181
+ readonly history: ValueChannelHistoryState
182
+ readonly sequences: ValueChannelSequenceState
183
+ }
184
+
185
+ export interface ValueHandoffPreparedMutableTuple {
186
+ readonly publicControlSlot:
187
+ | { readonly kind: 'empty' }
188
+ | {
189
+ readonly kind: 'installed'
190
+ readonly controlId: number
191
+ readonly executionId: number
192
+ readonly directOperationEpoch: number
193
+ }
194
+ readonly physicalExecutionIds: readonly number[]
195
+ readonly dueWorkSequences: readonly number[]
196
+ readonly valueCompletionSequences: readonly number[]
197
+ readonly targetBatchDependencyIds: readonly number[]
198
+ readonly dependencyEventOutboxSequences: readonly number[]
199
+ readonly currentBindingGeneration: number
200
+ readonly retiredBindingGeneration: number
201
+ readonly directOperationEpoch: number
202
+ readonly dueWorkSequence: number
203
+ readonly valueCompletionSequence: number
204
+ readonly batchDependencyEventSequence: number
205
+ readonly dependencyOutboxAckSequence: number
206
+ readonly authoritativeSequence: number
207
+ }
208
+
209
+ export type NormalizedRetainedWorkEntry =
210
+ | {
211
+ readonly kind: 'public-control-slot'
212
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'PUBLIC_CONTROL'>
213
+ readonly authoritativeSequence: number
214
+ readonly state:
215
+ | { readonly kind: 'EMPTY' }
216
+ | {
217
+ readonly kind: 'INSTALLED'
218
+ readonly controlId: number
219
+ readonly executionId: number
220
+ readonly directOperationEpoch: number
221
+ }
222
+ }
223
+ | {
224
+ readonly kind: 'retention-edge'
225
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'RETENTION'>
226
+ readonly authoritativeSequence: number
227
+ readonly registrationKind: 'SINK' | 'CHANGE_OBSERVER' | 'PUBLIC_REFERENCE'
228
+ readonly registrationId: number
229
+ readonly bindingGeneration: number
230
+ readonly edge: 'ATTACH' | 'DETACH'
231
+ }
232
+ | {
233
+ readonly kind: 'generator-execution'
234
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'EXECUTION'>
235
+ readonly authoritativeSequence: number
236
+ readonly executionId: number
237
+ readonly controlId: number
238
+ readonly directOperationEpoch: number
239
+ readonly executionLease: number
240
+ readonly plan: NormalizedValueChannelGeneratorPlan
241
+ readonly elapsedMs: number
242
+ readonly sample: NormalizedValueChannelWritePayload & { readonly done: boolean }
243
+ readonly phase: 'RUNNING' | 'DONE_DECIDED' | 'STOPPED'
244
+ readonly orphaned: boolean
245
+ }
246
+ | {
247
+ readonly kind: 'update-task'
248
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'DUE_WORK'>
249
+ readonly authoritativeSequence: number
250
+ readonly taskId: number
251
+ readonly taskSequence: number
252
+ readonly taskClass: 'instant' | 'equal' | 'idle-direct-write'
253
+ readonly state: 'QUEUED' | 'WRITE_RESOLVED' | 'CALLBACKS_PENDING'
254
+ readonly write: NormalizedValueChannelWritePayload
255
+ readonly callbacks: readonly NormalizedValueChannelTaskCallbackPayload[]
256
+ }
257
+ | {
258
+ readonly kind: 'carrier-task'
259
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'DUE_WORK'>
260
+ readonly authoritativeSequence: number
261
+ readonly taskId: number
262
+ readonly taskSequence: number
263
+ readonly taskClass: 'transition-end-carrier' | 'exit-carrier' | 'packet-b-carrier'
264
+ readonly carrier: {
265
+ readonly carrierId: number
266
+ readonly identityId: number
267
+ readonly authoredIndex: number
268
+ readonly key: string
269
+ readonly value: NormalizedValueChannelCarrierValue
270
+ }
271
+ }
272
+ | {
273
+ readonly kind: 'direct-write'
274
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'DUE_WORK'>
275
+ readonly authoritativeSequence: number
276
+ readonly operation: 'SET' | 'JUMP'
277
+ readonly commandSequence: number
278
+ readonly directOperationEpoch: number
279
+ readonly write: NormalizedValueChannelWritePayload
280
+ }
281
+ | {
282
+ readonly kind: 'value-completion'
283
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'COMPLETION'>
284
+ readonly authoritativeSequence: number
285
+ readonly executionId: number
286
+ readonly completionSequence: number
287
+ readonly eventSequence: number
288
+ readonly bindingGeneration: number
289
+ readonly state: 'ARMED' | 'RESOLVED' | 'DELIVERED'
290
+ readonly transitionOnCompleteCallbackId: number | null
291
+ readonly lifecycleCallbackIds: readonly number[]
292
+ readonly publicStartPromiseId: number | null
293
+ readonly clearControlIdAtArm: number | null
294
+ }
295
+ | {
296
+ readonly kind: 'target-batch-dependency-event'
297
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'DEPENDENCY_EVENT'>
298
+ readonly authoritativeSequence: number
299
+ readonly batchId: number
300
+ readonly dependencyId: number
301
+ readonly identityId: number
302
+ readonly dependencyEventKind: 'RAW_RESOLVED' | 'VALUE_ACKNOWLEDGED' | 'PERMANENTLY_UNRESOLVED'
303
+ readonly eventSequence: number
304
+ readonly outboxSequence: number
305
+ readonly outboxAcknowledgementWatermark: number
306
+ readonly state: 'UNACKNOWLEDGED' | 'ACKNOWLEDGED'
307
+ }
308
+ | {
309
+ readonly kind: 'binding-generation'
310
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'GENERATION'>
311
+ readonly authoritativeSequence: number
312
+ readonly currentBindingGeneration: number
313
+ readonly retiredBindingGeneration: number
314
+ }
315
+ | {
316
+ readonly kind: 'sequence-state'
317
+ readonly destinationSlot: ValueHandoffDestinationLedgerSlot<'SEQUENCE'>
318
+ readonly authoritativeSequence: number
319
+ readonly sequences: ValueChannelSequenceState
320
+ }
321
+
322
+ export type ValueHandoffDestinationLedgerSlotState =
323
+ | {
324
+ readonly ledger: 'PUBLIC_CONTROL'
325
+ readonly slotId: number
326
+ readonly entries: readonly Extract<
327
+ NormalizedRetainedWorkEntry,
328
+ { readonly kind: 'public-control-slot' }
329
+ >[]
330
+ }
331
+ | {
332
+ readonly ledger: 'RETENTION'
333
+ readonly slotId: number
334
+ readonly entries: readonly Extract<
335
+ NormalizedRetainedWorkEntry,
336
+ { readonly kind: 'retention-edge' }
337
+ >[]
338
+ }
339
+ | {
340
+ readonly ledger: 'EXECUTION'
341
+ readonly slotId: number
342
+ readonly entries: readonly Extract<
343
+ NormalizedRetainedWorkEntry,
344
+ { readonly kind: 'generator-execution' }
345
+ >[]
346
+ }
347
+ | {
348
+ readonly ledger: 'DUE_WORK'
349
+ readonly slotId: number
350
+ readonly entries: readonly Extract<
351
+ NormalizedRetainedWorkEntry,
352
+ { readonly kind: 'update-task' | 'carrier-task' | 'direct-write' }
353
+ >[]
354
+ }
355
+ | {
356
+ readonly ledger: 'COMPLETION'
357
+ readonly slotId: number
358
+ readonly entries: readonly Extract<
359
+ NormalizedRetainedWorkEntry,
360
+ { readonly kind: 'value-completion' }
361
+ >[]
362
+ }
363
+ | {
364
+ readonly ledger: 'DEPENDENCY_EVENT'
365
+ readonly slotId: number
366
+ readonly entries: readonly Extract<
367
+ NormalizedRetainedWorkEntry,
368
+ { readonly kind: 'target-batch-dependency-event' }
369
+ >[]
370
+ }
371
+ | {
372
+ readonly ledger: 'GENERATION'
373
+ readonly slotId: number
374
+ readonly entries: readonly Extract<
375
+ NormalizedRetainedWorkEntry,
376
+ { readonly kind: 'binding-generation' }
377
+ >[]
378
+ }
379
+ | {
380
+ readonly ledger: 'SEQUENCE'
381
+ readonly slotId: number
382
+ readonly entries: readonly Extract<
383
+ NormalizedRetainedWorkEntry,
384
+ { readonly kind: 'sequence-state' }
385
+ >[]
386
+ }
387
+
388
+ export interface ValueHandoffDestinationLedgerState {
389
+ readonly slots: readonly ValueHandoffDestinationLedgerSlotState[]
390
+ }
391
+
392
+ export type ValueHandoffPhase = 'PREPARED' | 'RELEASE_PENDING_PRE_FENCE' | 'RELEASED_POST_FENCE'
393
+
394
+ interface ValueHandoffSequencedPhaseEvent {
395
+ readonly authoritativeSequence: number
396
+ }
397
+
398
+ export type ValueHandoffPhaseMutationEvent =
399
+ | (ValueHandoffSequencedPhaseEvent & {
400
+ readonly kind: 'sink-attach'
401
+ readonly sink: DriverValueChannelSinkRegistration
402
+ })
403
+ | (ValueHandoffSequencedPhaseEvent & {
404
+ readonly kind: 'sink-detach'
405
+ readonly sink: DriverValueChannelSinkRegistration
406
+ })
407
+ | (ValueHandoffSequencedPhaseEvent & {
408
+ readonly kind: 'observer-arm'
409
+ readonly observerId: number
410
+ })
411
+ | (ValueHandoffSequencedPhaseEvent & {
412
+ readonly kind: 'observer-disarm'
413
+ readonly observerId: number
414
+ })
415
+ | (ValueHandoffSequencedPhaseEvent & {
416
+ readonly kind: 'start'
417
+ readonly executionId: number
418
+ readonly controlId: number
419
+ readonly directOperationEpoch: number
420
+ })
421
+ | (ValueHandoffSequencedPhaseEvent & {
422
+ readonly kind: 'retarget'
423
+ readonly previousControlId: number
424
+ readonly executionId: number
425
+ readonly controlId: number
426
+ readonly directOperationEpoch: number
427
+ })
428
+ | (ValueHandoffSequencedPhaseEvent & {
429
+ readonly kind: 'set'
430
+ readonly value: number
431
+ readonly commandSequence: number
432
+ readonly directOperationEpoch: number
433
+ })
434
+ | (ValueHandoffSequencedPhaseEvent & {
435
+ readonly kind: 'jump'
436
+ readonly value: number
437
+ readonly commandSequence: number
438
+ readonly directOperationEpoch: number
439
+ })
440
+ | (ValueHandoffSequencedPhaseEvent & {
441
+ readonly kind: 'stop'
442
+ readonly controlId: number | null
443
+ readonly commandSequence: number
444
+ readonly directOperationEpoch: number
445
+ })
446
+ | (ValueHandoffSequencedPhaseEvent & {
447
+ readonly kind: 'destroy'
448
+ readonly retiredBindingGeneration: number
449
+ })
450
+ | (ValueHandoffSequencedPhaseEvent & {
451
+ readonly kind: 'public-slot'
452
+ readonly controlId: number | null
453
+ readonly executionId: number | null
454
+ readonly directOperationEpoch: number
455
+ })
456
+ | (ValueHandoffSequencedPhaseEvent & {
457
+ readonly kind: 'physical-sample'
458
+ readonly executionId: number
459
+ readonly writeSequence: number
460
+ readonly value: number
461
+ })
462
+ | (ValueHandoffSequencedPhaseEvent & {
463
+ readonly kind: 'physical-done'
464
+ readonly executionId: number
465
+ readonly writeSequence: number
466
+ readonly completionSequence: number
467
+ })
468
+ | (ValueHandoffSequencedPhaseEvent & {
469
+ readonly kind: 'completion-arm'
470
+ readonly executionId: number
471
+ readonly completionSequence: number
472
+ })
473
+ | (ValueHandoffSequencedPhaseEvent & {
474
+ readonly kind: 'completion-resolved'
475
+ readonly executionId: number
476
+ readonly completionSequence: number
477
+ readonly eventSequence: number
478
+ })
479
+ | (ValueHandoffSequencedPhaseEvent & {
480
+ readonly kind: 'batch-dependency'
481
+ readonly batchId: number
482
+ readonly dependencyId: number
483
+ readonly referenceDelta: -1 | 1
484
+ })
485
+ | (ValueHandoffSequencedPhaseEvent & {
486
+ readonly kind: 'batch-outbox'
487
+ readonly batchId: number
488
+ readonly dependencyId: number
489
+ readonly status: 'RAW_RESOLVED' | 'VALUE_ACKNOWLEDGED' | 'PERMANENTLY_UNRESOLVED'
490
+ readonly eventSequence: number
491
+ })
492
+ | (ValueHandoffSequencedPhaseEvent & {
493
+ readonly kind: 'batch-outbox-ack'
494
+ readonly acknowledgedThroughSequence: number
495
+ })
496
+ | (ValueHandoffSequencedPhaseEvent & {
497
+ readonly kind: 'fifo-task'
498
+ readonly taskSequence: number
499
+ readonly taskClass: 'instant' | 'equal' | 'carrier' | 'idle'
500
+ })
501
+ | (ValueHandoffSequencedPhaseEvent & {
502
+ readonly kind: 'binding-generation'
503
+ readonly currentBindingGeneration: number
504
+ readonly retiredBindingGeneration: number
505
+ })
506
+
507
+ export type ValueHandoffPhaseReadEvent =
508
+ | { readonly kind: 'synchronous-snapshot' }
509
+ | { readonly kind: 'synchronous-get' }
510
+ | { readonly kind: 'synchronous-get-velocity' }
511
+
512
+ export type ValueHandoffPhaseEvent = ValueHandoffPhaseMutationEvent | ValueHandoffPhaseReadEvent
513
+
514
+ export type ValueHandoffPhaseEventKind = ValueHandoffPhaseEvent['kind']
515
+ export type ValueHandoffPhaseEventOperation = `phase-event-${ValueHandoffPhaseEventKind}`
516
+
517
+ export interface ValueHandoffPhaseEventEnvelope {
518
+ readonly direction: ValueHandoffDirection
519
+ readonly transferLease: number
520
+ readonly phaseToken: number
521
+ readonly phase: ValueHandoffPhase
522
+ readonly event: ValueHandoffPhaseEvent
523
+ }
524
+
525
+ export type ValueHandoffPhaseReadView =
526
+ | 'AUTHORITATIVE_SOURCE'
527
+ | 'SOURCE_WITH_DIRECT_OPERATION_OVERLAY'
528
+ | 'PREPARED_DESTINATION_WITH_DIRECT_OPERATION_OVERLAY'
529
+
530
+ export type ValueHandoffPhaseEventDisposition =
531
+ | {
532
+ readonly kind: 'ABORT_AND_REPREPARE'
533
+ readonly phase: 'PREPARED' | 'RELEASE_PENDING_PRE_FENCE'
534
+ readonly eventKind: ValueHandoffPhaseMutationEvent['kind']
535
+ readonly status: Extract<ValueHandoffSourceStatus, { readonly kind: 'ABORTED' }>
536
+ }
537
+ | {
538
+ readonly kind: 'APPEND_IMMUTABLE_COMMIT_SUFFIX'
539
+ readonly phase: 'RELEASED_POST_FENCE'
540
+ readonly eventKind: ValueHandoffPhaseMutationEvent['kind']
541
+ readonly commit: ImmutableDestinationCommit
542
+ readonly authoredSuffix: readonly ValueHandoffPhaseMutationEvent[]
543
+ }
544
+ | {
545
+ readonly kind: 'SYNCHRONOUS_READ_WITHOUT_PROTOCOL_MUTATION'
546
+ readonly phase: ValueHandoffPhase
547
+ readonly eventKind: ValueHandoffPhaseReadEvent['kind']
548
+ readonly view: ValueHandoffPhaseReadView
549
+ readonly history: ValueChannelHistoryState
550
+ }
551
+
552
+ export type ValueHandoffProtocolCheckpoint =
553
+ | 'FREEZE'
554
+ | 'PREPARE_READY'
555
+ | 'RELEASE_PENDING_SOURCE_UNKNOWN'
556
+ | 'SOURCE_CONFIRMED_ABORTED'
557
+ | 'SOURCE_CONFIRMED_RELEASED'
558
+ | 'DESTINATION_COMMIT_CONSUMPTION'
559
+ | 'DESTINATION_ACKNOWLEDGED'
560
+
561
+ interface ValueHandoffProtocolRetainedWorkPayload {
562
+ readonly retainedWorkEntry: NormalizedRetainedWorkEntry
563
+ }
564
+
565
+ export type ValueHandoffProtocolMutationEvent =
566
+ | (ValueHandoffPhaseMutationEvent & ValueHandoffProtocolRetainedWorkPayload)
567
+ | (ValueHandoffSequencedPhaseEvent &
568
+ ValueHandoffProtocolRetainedWorkPayload & {
569
+ readonly kind: 'tokenless-task-enqueue'
570
+ readonly taskSequence: number
571
+ readonly taskClass: 'instant' | 'equal' | 'carrier' | 'idle'
572
+ })
573
+ | (ValueHandoffSequencedPhaseEvent &
574
+ ValueHandoffProtocolRetainedWorkPayload & {
575
+ readonly kind: 'lifecycle-enqueue'
576
+ readonly lifecycleSequence: number
577
+ readonly lifecycleKind: 'animationStart' | 'animationCancel' | 'animationComplete'
578
+ readonly bindingGeneration: number
579
+ })
580
+
581
+ export type ValueHandoffProtocolEvent =
582
+ | ValueHandoffProtocolMutationEvent
583
+ | ValueHandoffPhaseReadEvent
584
+
585
+ export type ValueHandoffProtocolEventKind = ValueHandoffProtocolEvent['kind']
586
+ export type ValueHandoffProtocolMutationEventKind = ValueHandoffProtocolMutationEvent['kind']
587
+ export type ValueHandoffProtocolCheckpointOperation =
588
+ `protocol-checkpoint-${ValueHandoffProtocolCheckpoint}-${ValueHandoffProtocolEventKind}`
589
+
590
+ export interface ValueHandoffProtocolCheckpointEnvelope {
591
+ readonly direction: ValueHandoffDirection
592
+ readonly transferLease: number
593
+ readonly phaseToken: number
594
+ readonly checkpoint: ValueHandoffProtocolCheckpoint
595
+ readonly event: ValueHandoffProtocolEvent
596
+ }
597
+
598
+ export type ValueHandoffProtocolMutationHandling =
599
+ | 'INVALIDATE_FREEZE_AND_REPREPARE_SOURCE'
600
+ | 'ABORT_PREPARE_AND_REPREPARE_SOURCE'
601
+ | 'SEQUENCE_WITHOUT_INFERRING_SOURCE_STATUS'
602
+ | 'APPLY_CONFIRMED_ABORT_AND_REPREPARE_SOURCE'
603
+ | 'APPEND_TO_IMMUTABLE_RELEASED_COMMIT'
604
+ | 'APPLY_IN_DESTINATION_COMMIT_PREFIX'
605
+ | 'APPLY_TO_ACKNOWLEDGED_DESTINATION_OR_REVERSE'
606
+
607
+ export type ValueHandoffProtocolReadAuthority =
608
+ | 'AUTHORITATIVE_SOURCE'
609
+ | 'SOURCE_WITH_DIRECT_OPERATION_OVERLAY'
610
+ | 'PREPARED_DESTINATION_WITH_DIRECT_OPERATION_OVERLAY'
611
+ | 'ACKNOWLEDGED_DESTINATION'
612
+
613
+ export type ValueHandoffProtocolRetainedWorkDisposition =
614
+ | {
615
+ readonly kind: 'CARRIED_IN_INVALIDATED_FREEZE_REPREPARE'
616
+ readonly carriedEntry: NormalizedRetainedWorkEntry
617
+ }
618
+ | {
619
+ readonly kind: 'CARRIED_IN_ABORTED_PREPARE_REPREPARE'
620
+ readonly carriedEntry: NormalizedRetainedWorkEntry
621
+ }
622
+ | {
623
+ readonly kind: 'QUEUED_BEHIND_SOURCE_DECISION'
624
+ readonly queuedEntry: NormalizedRetainedWorkEntry
625
+ }
626
+ | {
627
+ readonly kind: 'APPLIED_TO_CONFIRMED_SOURCE_BEFORE_REPREPARE'
628
+ readonly appliedEntry: NormalizedRetainedWorkEntry
629
+ }
630
+ | {
631
+ readonly kind: 'APPENDED_TO_IMMUTABLE_RELEASED_COMMIT'
632
+ readonly appendedEntry: NormalizedRetainedWorkEntry
633
+ }
634
+ | {
635
+ readonly kind: 'APPLIED_TO_PREFILLED_DESTINATION_SLOT'
636
+ readonly destinationSlot: NormalizedRetainedWorkEntry['destinationSlot']
637
+ readonly appliedEntry: NormalizedRetainedWorkEntry
638
+ }
639
+ | {
640
+ readonly kind: 'APPLIED_TO_ACKNOWLEDGED_DESTINATION_OR_REVERSE'
641
+ readonly destinationSlot: NormalizedRetainedWorkEntry['destinationSlot']
642
+ readonly appliedEntry: NormalizedRetainedWorkEntry
643
+ }
644
+
645
+ export type ValueHandoffProtocolCheckpointDisposition =
646
+ | {
647
+ readonly kind: 'MUTATION'
648
+ readonly checkpoint: ValueHandoffProtocolCheckpoint
649
+ readonly eventKind: ValueHandoffProtocolMutationEvent['kind']
650
+ readonly operation: ValueHandoffProtocolMutationEvent
651
+ readonly handling: ValueHandoffProtocolMutationHandling
652
+ readonly retainedWorkDisposition: ValueHandoffProtocolRetainedWorkDisposition
653
+ readonly absoluteTransferDeadlineMs: number
654
+ readonly absoluteNoConsumerDeadlineMs: number
655
+ readonly resources: ValueChannelHandoffResourceState
656
+ }
657
+ | {
658
+ readonly kind: 'SYNCHRONOUS_READ_WITHOUT_PROTOCOL_MUTATION'
659
+ readonly checkpoint: ValueHandoffProtocolCheckpoint
660
+ readonly eventKind: ValueHandoffPhaseReadEvent['kind']
661
+ readonly authority: ValueHandoffProtocolReadAuthority
662
+ readonly history: ValueChannelHistoryState
663
+ }
664
+
665
+ export interface ValueHandoffProtocolCheckpointTransition {
666
+ readonly state: DriverValueChannelState
667
+ readonly observation: DriverValueChannelObservation
668
+ readonly disposition: ValueHandoffProtocolCheckpointDisposition
669
+ readonly orderedEffects: readonly ValueHandoffEffect[]
670
+ }
671
+
672
+ export interface ImmutableDestinationCommit {
673
+ readonly kind: 'DESTINATION_COMMIT'
674
+ readonly channelId: number
675
+ readonly sourceRecordId: number
676
+ readonly destinationRecordId: number
677
+ readonly direction: ValueHandoffDirection
678
+ readonly transferLease: number
679
+ readonly releaseFenceSequence: number
680
+ readonly sentThroughAuthoritativeSequence: number
681
+ readonly includedPostFencePrefix: readonly NormalizedRetainedWorkEntry[]
682
+ }
683
+
684
+ export interface DestinationCommitAcknowledgement {
685
+ readonly kind: 'DESTINATION_COMMIT_ACKNOWLEDGEMENT'
686
+ readonly channelId: number
687
+ readonly sourceRecordId: number
688
+ readonly destinationRecordId: number
689
+ readonly direction: ValueHandoffDirection
690
+ readonly transferLease: number
691
+ readonly releaseFenceSequence: number
692
+ readonly drainedThroughAuthoritativeSequence: number
693
+ }
694
+
695
+ export type JsDestinationRoot =
696
+ | 'js-local-graph-frame'
697
+ | 'js-fallback-frame'
698
+ | 'js-pending-update'
699
+ | 'js-orphan-drain-frame'
700
+
701
+ export type DestinationRuntime =
702
+ | { readonly kind: 'live-ui'; readonly driverId: number }
703
+ | { readonly kind: 'manual-ui'; readonly manualFrameGroupId: number }
704
+ | { readonly kind: 'js'; readonly root: JsDestinationRoot }
705
+
706
+ export interface JsPreparedDestinationRecordState {
707
+ readonly destinationRecordId: number
708
+ readonly channelId: number
709
+ readonly sourceRecordId: number
710
+ readonly direction: ValueHandoffDirection
711
+ readonly transferLease: number
712
+ readonly releaseFenceSequence: number
713
+ readonly adoptedCommitSlotId: number | null
714
+ readonly adoptedSentThroughAuthoritativeSequence: number
715
+ readonly appliedThroughAuthoritativeSequence: number
716
+ readonly ledgers: ValueHandoffDestinationLedgerState
717
+ }
718
+
719
+ export interface JsDestinationCommitAcknowledgementSlotState {
720
+ readonly acknowledgementSlotId: number
721
+ readonly commitSlotId: number
722
+ readonly state: 'EMPTY' | 'FILLED' | 'STAGED_IN_CURRENT_ENVELOPE'
723
+ readonly acknowledgement: DestinationCommitAcknowledgement | null
724
+ }
725
+
726
+ export type JsDestinationCommitEnvelopeEntry =
727
+ | {
728
+ readonly kind: 'DESTINATION_COMMIT_ACKNOWLEDGEMENT'
729
+ readonly acknowledgementSlotId: number
730
+ readonly acknowledgement: DestinationCommitAcknowledgement
731
+ }
732
+ | {
733
+ readonly kind: 'ROOT_SIBLING_DUE_WORK'
734
+ readonly dueWorkSequence: number
735
+ }
736
+
737
+ export interface JsDestinationCommitEnvelopeState {
738
+ readonly root: JsDestinationRoot
739
+ readonly mailboxDrainId: number
740
+ readonly state: 'NOT_BEGUN' | 'OPEN' | 'SEALED'
741
+ readonly beginCount: number
742
+ readonly entries: readonly JsDestinationCommitEnvelopeEntry[]
743
+ readonly sealCount: number
744
+ }
745
+
746
+ export interface JsDestinationCommitConsumeState {
747
+ readonly preparedDestinationRecords: readonly JsPreparedDestinationRecordState[]
748
+ readonly acknowledgementSlots: readonly JsDestinationCommitAcknowledgementSlotState[]
749
+ readonly envelope: JsDestinationCommitEnvelopeState
750
+ }
751
+
752
+ export interface JsDestinationCommitConsumeRequest {
753
+ readonly root: JsDestinationRoot
754
+ readonly commitSlotId: number
755
+ readonly acknowledgementSlotId: number
756
+ readonly commit: ImmutableDestinationCommit
757
+ readonly siblingDueWorkSequences: readonly number[]
758
+ }
759
+
760
+ export type JsDestinationCommitRefusalReason =
761
+ | 'root-mismatch'
762
+ | 'envelope-already-begun'
763
+ | 'missing-prepared-destination-record'
764
+ | 'missing-acknowledgement-slot'
765
+ | 'wrong-channel'
766
+ | 'wrong-source-record'
767
+ | 'wrong-destination-record'
768
+ | 'wrong-direction'
769
+ | 'wrong-transfer-lease'
770
+ | 'wrong-release-fence'
771
+ | 'ahead-of-sent-watermark'
772
+ | 'sent-watermark-regressed'
773
+ | 'prefix-not-contiguous'
774
+
775
+ export type JsDestinationCommitConsumeOutcome =
776
+ | {
777
+ readonly kind: 'APPLIED'
778
+ readonly commitSlotId: number
779
+ readonly acknowledgement: DestinationCommitAcknowledgement
780
+ }
781
+ | {
782
+ readonly kind: 'DUPLICATE_REACKNOWLEDGED'
783
+ readonly commitSlotId: number
784
+ readonly acknowledgement: DestinationCommitAcknowledgement
785
+ }
786
+ | {
787
+ readonly kind: 'REFUSED'
788
+ readonly commitSlotId: number
789
+ readonly reason: JsDestinationCommitRefusalReason
790
+ }
791
+
792
+ export type JsDestinationCommitEffect =
793
+ | { readonly kind: 'begin-js-destination-envelope'; readonly root: JsDestinationRoot }
794
+ | {
795
+ readonly kind: 'lookup-js-prepared-destination-record'
796
+ readonly destinationRecordId: number
797
+ }
798
+ | {
799
+ readonly kind: 'validate-js-immutable-destination-commit'
800
+ readonly commitSlotId: number
801
+ }
802
+ | {
803
+ readonly kind: 'adopt-js-immutable-destination-commit'
804
+ readonly commitSlotId: number
805
+ }
806
+ | {
807
+ readonly kind: 'apply-js-destination-ledger-entry-in-place'
808
+ readonly commitSlotId: number
809
+ readonly prefixIndex: number
810
+ readonly destinationSlot: NormalizedRetainedWorkEntry['destinationSlot']
811
+ readonly entry: NormalizedRetainedWorkEntry
812
+ }
813
+ | {
814
+ readonly kind: 'dedupe-js-destination-commit-application'
815
+ readonly commitSlotId: number
816
+ readonly drainedThroughAuthoritativeSequence: number
817
+ }
818
+ | {
819
+ readonly kind: 'fill-js-destination-acknowledgement-slot'
820
+ readonly commitSlotId: number
821
+ readonly acknowledgementSlotId: number
822
+ }
823
+ | {
824
+ readonly kind: 'stage-js-destination-acknowledgement-in-current-envelope'
825
+ readonly acknowledgementSlotId: number
826
+ readonly mailboxDrainId: number
827
+ }
828
+ | { readonly kind: 'run-js-root-sibling-due-work'; readonly dueWorkSequence: number }
829
+ | {
830
+ readonly kind: 'refuse-malformed-js-destination-commit'
831
+ readonly commitSlotId: number
832
+ readonly reason: JsDestinationCommitRefusalReason
833
+ }
834
+ | {
835
+ readonly kind: 'seal-js-destination-envelope-once'
836
+ readonly mailboxDrainId: number
837
+ readonly entryCount: number
838
+ }
839
+
840
+ export interface JsDestinationCommitTransition {
841
+ readonly state: JsDestinationCommitConsumeState
842
+ readonly outcome: JsDestinationCommitConsumeOutcome
843
+ readonly orderedEffects: readonly JsDestinationCommitEffect[]
844
+ }
845
+
846
+ export interface ValueHandoffProtocolState {
847
+ readonly ownership: ValueHandoffOwnership
848
+ readonly desiredSide: ValueChannelSide | null
849
+ readonly direction: ValueHandoffDirection | null
850
+ readonly transferLease: number
851
+ readonly phaseToken: number
852
+ readonly sourceRecordId: number | null
853
+ readonly destinationRecordId: number | null
854
+ readonly absoluteTransferDeadlineMs: number | null
855
+ readonly absoluteNoConsumerDeadlineMs: number | null
856
+ readonly releaseFenceSequence: number | null
857
+ readonly sentThroughAuthoritativeSequence: number
858
+ readonly drainedThroughAuthoritativeSequence: number
859
+ readonly preparedMutableTuple: ValueHandoffPreparedMutableTuple | null
860
+ readonly destinationRuntime: DestinationRuntime | null
861
+ readonly sourceMutationQueue: readonly NormalizedRetainedWorkEntry[]
862
+ readonly commitQueue: readonly ImmutableDestinationCommit[]
863
+ readonly resources: ValueChannelHandoffResourceState
864
+ }
865
+
866
+ export interface DriverValueChannelState {
867
+ readonly identity: DriverValueChannelIdentityRecordState
868
+ readonly handoff: ValueHandoffProtocolState
869
+ }
870
+
871
+ interface ValueHandoffCommitLossRetainedWorkCommon {
872
+ readonly workSequence: number
873
+ }
874
+
875
+ export type ValueHandoffCommitLossRetainedWork =
876
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
877
+ readonly kind: 'cache-adoption'
878
+ readonly authoritativeSequence: number
879
+ readonly value: number
880
+ })
881
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
882
+ readonly kind: 'transition-on-update'
883
+ readonly callbackId: number
884
+ readonly value: number
885
+ })
886
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
887
+ readonly kind: 'transition-on-complete'
888
+ readonly callbackId: number
889
+ })
890
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
891
+ readonly kind: 'current-animation-complete'
892
+ readonly listenerId: number
893
+ readonly bindingGeneration: number
894
+ })
895
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
896
+ readonly kind: 'token-blind-public-slot-clear'
897
+ readonly initiallyInstalledControlId: number
898
+ readonly installedControlIdAtClear: number | null
899
+ })
900
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
901
+ readonly kind: 'public-start-promise-resolution'
902
+ readonly promiseId: number
903
+ })
904
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
905
+ readonly kind: 'target-batch-dependency-delivery'
906
+ readonly batchId: number
907
+ readonly dependencyId: number
908
+ readonly eventSequence: number
909
+ })
910
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
911
+ readonly kind: 'target-batch-dependency-acknowledgement'
912
+ readonly batchId: number
913
+ readonly dependencyId: number
914
+ readonly acknowledgedEventSequence: number
915
+ })
916
+ | (ValueHandoffCommitLossRetainedWorkCommon & {
917
+ readonly kind: 'settle-release'
918
+ readonly settleEntryId: number
919
+ })
920
+
921
+ export interface ValueHandoffCommitLossCleanupCounts {
922
+ readonly transferTimerCancellationCount: number
923
+ readonly preparedStateReleaseCount: number
924
+ readonly preparedCloneReleaseCount: number
925
+ readonly transferActivityReleaseCount: number
926
+ readonly terminalFinalizationCount: number
927
+ }
928
+
929
+ export interface ValueHandoffCommitLossDrainState {
930
+ readonly channel: DriverValueChannelState
931
+ readonly retainedWork: readonly ValueHandoffCommitLossRetainedWork[]
932
+ readonly drainedWorkSequences: readonly number[]
933
+ readonly retiredListenerIds: readonly number[]
934
+ readonly retiredSinkRegistrationIds: readonly number[]
935
+ readonly cleanupCounts: ValueHandoffCommitLossCleanupCounts
936
+ }
937
+
938
+ export interface ValueHandoffCommitLossDrainObservation {
939
+ readonly remainingRetainedWorkCount: number
940
+ readonly drainedWorkCount: number
941
+ readonly retiredListenerDeliveryCount: number
942
+ readonly retiredSinkPublicationCount: number
943
+ readonly hostOnUpdatePublicationCount: number
944
+ readonly dependencyAcknowledgementCount: number
945
+ readonly settleReleaseCount: number
946
+ readonly destinationRunPermitCount: number
947
+ readonly sourceRevivalCount: number
948
+ readonly reverseTransferCount: number
949
+ readonly cleanupCounts: ValueHandoffCommitLossCleanupCounts
950
+ }
951
+
952
+ export type ValueHandoffCommitLossDrainEffect =
953
+ | {
954
+ readonly kind: 'adopt-bounded-destination-cache'
955
+ readonly workSequence: number
956
+ readonly authoritativeSequence: number
957
+ readonly value: number
958
+ }
959
+ | {
960
+ readonly kind: 'deliver-bounded-transition-on-update'
961
+ readonly workSequence: number
962
+ readonly callbackId: number
963
+ readonly value: number
964
+ }
965
+ | {
966
+ readonly kind: 'deliver-bounded-transition-on-complete'
967
+ readonly workSequence: number
968
+ readonly callbackId: number
969
+ }
970
+ | {
971
+ readonly kind: 'deliver-current-animation-complete'
972
+ readonly workSequence: number
973
+ readonly listenerId: number
974
+ readonly bindingGeneration: number
975
+ }
976
+ | {
977
+ readonly kind: 'clear-public-control-slot-token-blind'
978
+ readonly workSequence: number
979
+ readonly initiallyInstalledControlId: number
980
+ readonly installedControlIdAtClear: number | null
981
+ }
982
+ | {
983
+ readonly kind: 'resolve-public-start-promise'
984
+ readonly workSequence: number
985
+ readonly promiseId: number
986
+ }
987
+ | {
988
+ readonly kind: 'deliver-target-batch-dependency-event'
989
+ readonly workSequence: number
990
+ readonly batchId: number
991
+ readonly dependencyId: number
992
+ readonly eventSequence: number
993
+ }
994
+ | {
995
+ readonly kind: 'acknowledge-target-batch-dependency-event'
996
+ readonly workSequence: number
997
+ readonly batchId: number
998
+ readonly dependencyId: number
999
+ readonly acknowledgedEventSequence: number
1000
+ }
1001
+ | {
1002
+ readonly kind: 'release-settle-entry'
1003
+ readonly workSequence: number
1004
+ readonly settleEntryId: number
1005
+ }
1006
+ | { readonly kind: 'cancel-transfer-timer-once' }
1007
+ | { readonly kind: 'release-prepared-state-once' }
1008
+ | { readonly kind: 'release-prepared-clone-once' }
1009
+ | { readonly kind: 'release-transfer-activity-once' }
1010
+ | { readonly kind: 'finalize-bounded-refusal-without-run-or-reverse-once' }
1011
+
1012
+ export interface ValueHandoffCommitLossDrainTransition {
1013
+ readonly state: ValueHandoffCommitLossDrainState
1014
+ readonly observation: ValueHandoffCommitLossDrainObservation
1015
+ readonly orderedEffects: readonly ValueHandoffCommitLossDrainEffect[]
1016
+ }
1017
+
1018
+ export interface DriverValueChannelSinkRegistration {
1019
+ readonly driverId: number
1020
+ readonly handle: number
1021
+ readonly key: string
1022
+ readonly bindingGeneration: number
1023
+ }
1024
+
1025
+ /**
1026
+ * Exact-object registry record. The public reference and private CORE port are intentionally
1027
+ * available only on this private seam; primitive observation below never exposes either object.
1028
+ * Cross-identity target-batch authority is deliberately absent from this shape.
1029
+ */
1030
+ export interface DriverValueChannelIdentityRegistryRecord<T> {
1031
+ readonly value: MotionValue<T>
1032
+ readonly port: MotionValueDriverPort<T>
1033
+ readonly state: DriverValueChannelState
1034
+ readonly sinks: readonly DriverValueChannelSinkRegistration[]
1035
+ }
1036
+
1037
+ export interface DriverValueChannelResolveBindingRequest<T> {
1038
+ readonly value: MotionValue<T>
1039
+ readonly port: MotionValueDriverPort<T>
1040
+ readonly currentValue: unknown
1041
+ readonly requestedState: DriverValueChannelState
1042
+ readonly sink: DriverValueChannelSinkRegistration
1043
+ }
1044
+
1045
+ export type DriverValueChannelBindingResolution<T> =
1046
+ | {
1047
+ readonly kind: 'eligible'
1048
+ readonly record: DriverValueChannelIdentityRegistryRecord<T>
1049
+ readonly created: boolean
1050
+ readonly attachedSink: DriverValueChannelSinkRegistration
1051
+ }
1052
+ | {
1053
+ readonly kind: 'ineligible'
1054
+ readonly eligibility: Extract<
1055
+ MotionValueDriverBindingEligibility,
1056
+ { readonly kind: 'ineligible' }
1057
+ >
1058
+ }
1059
+
1060
+ export interface DriverValueChannelDetachBindingRequest<T> {
1061
+ readonly value: MotionValue<T>
1062
+ readonly sink: DriverValueChannelSinkRegistration
1063
+ }
1064
+
1065
+ export interface DriverValueChannelRebindRequest<T> {
1066
+ readonly previousValue: MotionValue<T>
1067
+ readonly previousSink: DriverValueChannelSinkRegistration
1068
+ readonly replacement: DriverValueChannelResolveBindingRequest<T>
1069
+ }
1070
+
1071
+ export interface DriverValueChannelRebindResult<T> {
1072
+ readonly previousRecord: DriverValueChannelIdentityRegistryRecord<T>
1073
+ readonly replacement: DriverValueChannelBindingResolution<T>
1074
+ }
1075
+
1076
+ export interface DriverValueChannelRetentionReconciliation<T> {
1077
+ readonly value: MotionValue<T>
1078
+ readonly nextState: DriverValueChannelState
1079
+ }
1080
+
1081
+ export interface DriverValueChannelIdentityRegistryRecordObservation {
1082
+ readonly channelId: number
1083
+ readonly jsRecordId: number
1084
+ readonly uiRecordId: number
1085
+ readonly portChannelId: number
1086
+ readonly creationKind: MotionValueDriverPortObservation['creationKind']
1087
+ readonly initializerProvenance: MotionValueDriverPortObservation['initializerProvenance']
1088
+ readonly sinkCount: number
1089
+ readonly sinks: readonly DriverValueChannelSinkRegistration[]
1090
+ readonly currentBindingGeneration: number
1091
+ readonly retiredBindingGeneration: number
1092
+ readonly retentionReasons: readonly DriverValueChannelRetentionReason[]
1093
+ readonly retained: boolean
1094
+ readonly stronglyRegistered: boolean
1095
+ }
1096
+
1097
+ export interface DriverValueChannelIdentityRegistryObservation {
1098
+ readonly retainedRecordCount: number
1099
+ readonly retainedChannelIds: readonly number[]
1100
+ }
1101
+
1102
+ export interface CreateDriverValueChannelIdentityRegistryOptions<T> {
1103
+ readonly initialRecords?: readonly DriverValueChannelIdentityRegistryRecord<T>[]
1104
+ }
1105
+
1106
+ export interface DriverValueChannelCanonicalOperationObservation<T> {
1107
+ readonly record: DriverValueChannelIdentityRegistryRecord<T>
1108
+ readonly port: MotionValueDriverPortObservation
1109
+ }
1110
+
1111
+ export interface DriverValueChannelIdentityRegistry<T> {
1112
+ observe(): DriverValueChannelIdentityRegistryObservation
1113
+ peekExact(value: MotionValue<T>): DriverValueChannelIdentityRegistryRecordObservation | null
1114
+ peekById(channelId: number): DriverValueChannelIdentityRegistryRecordObservation | null
1115
+ resolveBinding(
1116
+ request: DriverValueChannelResolveBindingRequest<T>,
1117
+ ): DriverValueChannelBindingResolution<T>
1118
+ detachBinding(
1119
+ request: DriverValueChannelDetachBindingRequest<T>,
1120
+ ): DriverValueChannelIdentityRegistryRecord<T> | null
1121
+ rebind(request: DriverValueChannelRebindRequest<T>): DriverValueChannelRebindResult<T>
1122
+ reconcileRetention(
1123
+ reconciliation: DriverValueChannelRetentionReconciliation<T>,
1124
+ ): DriverValueChannelIdentityRegistryObservation
1125
+ snapshotForHandoff(value: MotionValue<T>): DriverValueChannelState
1126
+ observeCanonicalOperation(
1127
+ value: MotionValue<T>,
1128
+ ): DriverValueChannelCanonicalOperationObservation<T>
1129
+ }
1130
+
1131
+ export type DriverValueChannelRetentionReason =
1132
+ | 'public-reference'
1133
+ | 'sink'
1134
+ | 'change-observer'
1135
+ | 'public-control-slot'
1136
+ | 'physical-execution'
1137
+ | 'due-work'
1138
+ | 'value-completion'
1139
+ | 'target-batch-dependency'
1140
+ | 'dependency-event-outbox'
1141
+ | 'shared-batch-reference'
1142
+ | 'pending-frame-activity'
1143
+ | 'prepared-state'
1144
+ | 'prepared-clone'
1145
+ | 'transfer-activity'
1146
+ | 'transfer-timer'
1147
+
1148
+ export interface DriverValueChannelObservation {
1149
+ readonly channelId: number
1150
+ readonly jsRecordId: number
1151
+ readonly uiRecordId: number
1152
+ readonly ownership: ValueHandoffOwnership
1153
+ readonly ownerSide: ValueChannelSide | null
1154
+ readonly desiredSide: ValueChannelSide | null
1155
+ readonly transferLease: number
1156
+ readonly currentBindingGeneration: number
1157
+ readonly retiredBindingGeneration: number
1158
+ readonly publicControlSlotId: number | null
1159
+ readonly physicalExecutionCount: number
1160
+ readonly dueWorkCount: number
1161
+ readonly valueCompletionCount: number
1162
+ readonly targetBatchDependencyCount: number
1163
+ readonly dependencyEventOutboxCount: number
1164
+ readonly dependencyEventAckWatermark: number
1165
+ readonly commitQueueLength: number
1166
+ readonly sourceMutationQueueLength: number
1167
+ readonly sentThroughAuthoritativeSequence: number
1168
+ readonly drainedThroughAuthoritativeSequence: number
1169
+ readonly retentionReasons: readonly DriverValueChannelRetentionReason[]
1170
+ readonly retained: boolean
1171
+ }
1172
+
1173
+ export type DestinationCommitValidation =
1174
+ | { readonly kind: 'accepted'; readonly duplicate: boolean }
1175
+ | {
1176
+ readonly kind: 'rejected'
1177
+ readonly reason:
1178
+ | 'wrong-channel'
1179
+ | 'wrong-source-record'
1180
+ | 'wrong-destination-record'
1181
+ | 'wrong-direction'
1182
+ | 'wrong-transfer-lease'
1183
+ | 'wrong-release-fence'
1184
+ | 'ahead-of-sent-watermark'
1185
+ | 'sent-watermark-regressed'
1186
+ | 'prefix-not-contiguous'
1187
+ }
1188
+
1189
+ export type DestinationCommitAcknowledgementValidation =
1190
+ | {
1191
+ readonly kind: 'accepted'
1192
+ readonly extent: 'duplicate' | 'partial' | 'through-current-tail'
1193
+ }
1194
+ | {
1195
+ readonly kind: 'rejected'
1196
+ readonly reason:
1197
+ | 'wrong-channel'
1198
+ | 'wrong-source-record'
1199
+ | 'wrong-destination-record'
1200
+ | 'wrong-direction'
1201
+ | 'wrong-transfer-lease'
1202
+ | 'wrong-release-fence'
1203
+ | 'before-release-fence-watermark'
1204
+ | 'ahead-of-sent-watermark'
1205
+ }
1206
+
1207
+ export interface ValueHandoffSourceStatusIdentity {
1208
+ readonly channelId: number
1209
+ readonly sourceRecordId: number
1210
+ readonly destinationRecordId: number
1211
+ readonly direction: ValueHandoffDirection
1212
+ readonly transferLease: number
1213
+ }
1214
+
1215
+ export type ValueHandoffSourceStatus = ValueHandoffSourceStatusIdentity &
1216
+ (
1217
+ | {
1218
+ readonly kind: 'ABORTED'
1219
+ readonly authoritativeSequence: number
1220
+ }
1221
+ | {
1222
+ readonly kind: 'RELEASED'
1223
+ readonly releaseFenceSequence: number
1224
+ }
1225
+ )
1226
+
1227
+ export type ValueHandoffSourceStatusRefusalReason =
1228
+ | 'wrong-channel'
1229
+ | 'wrong-source-record'
1230
+ | 'wrong-destination-record'
1231
+ | 'wrong-direction'
1232
+ | 'wrong-transfer-lease'
1233
+
1234
+ export type ValueHandoffSourceStatusValidation =
1235
+ | { readonly kind: 'accepted' }
1236
+ | { readonly kind: 'rejected'; readonly reason: ValueHandoffSourceStatusRefusalReason }
1237
+
1238
+ export type ValueHandoffSourceStatusProductionRequest = ValueHandoffSourceStatusIdentity &
1239
+ (
1240
+ | { readonly outcome: 'ABORTED'; readonly authoritativeSequence: number }
1241
+ | { readonly outcome: 'RELEASED'; readonly releaseFenceSequence: number }
1242
+ )
1243
+
1244
+ export interface ValueHandoffSourceStatusProductionTransition {
1245
+ readonly state: DriverValueChannelState
1246
+ readonly validation: ValueHandoffSourceStatusValidation
1247
+ readonly producedStatus: ValueHandoffSourceStatus | null
1248
+ }
1249
+
1250
+ export type ValueHandoffEvent =
1251
+ | {
1252
+ readonly kind: 'begin-handoff'
1253
+ readonly direction: ValueHandoffDirection
1254
+ readonly destinationRecordId: number
1255
+ readonly destinationRuntime: DestinationRuntime
1256
+ readonly transferLease: number
1257
+ readonly phaseToken: number
1258
+ readonly absoluteTransferDeadlineMs: number
1259
+ readonly absoluteNoConsumerDeadlineMs: number
1260
+ }
1261
+ | {
1262
+ readonly kind: 'destination-ready'
1263
+ readonly direction: ValueHandoffDirection
1264
+ readonly transferLease: number
1265
+ readonly preparedMutableTuple: ValueHandoffPreparedMutableTuple
1266
+ }
1267
+ | {
1268
+ readonly kind: 'source-mutation'
1269
+ readonly entry: NormalizedRetainedWorkEntry
1270
+ }
1271
+ | { readonly kind: 'source-status'; readonly status: ValueHandoffSourceStatus }
1272
+ | {
1273
+ readonly kind: 'enqueue-destination-commit'
1274
+ readonly commit: ImmutableDestinationCommit
1275
+ }
1276
+ | {
1277
+ readonly kind: 'destination-commit-acknowledgement'
1278
+ readonly acknowledgement: DestinationCommitAcknowledgement
1279
+ }
1280
+ | {
1281
+ readonly kind: 'phase-timeout'
1282
+ readonly direction: ValueHandoffDirection
1283
+ readonly transferLease: number
1284
+ readonly phaseToken: number
1285
+ }
1286
+ | {
1287
+ readonly kind: 'bounded-terminal'
1288
+ readonly direction: ValueHandoffDirection
1289
+ readonly transferLease: number
1290
+ readonly phaseToken: number
1291
+ }
1292
+ | {
1293
+ readonly kind: 'finish-bounded-refusal-drain'
1294
+ readonly direction: ValueHandoffDirection
1295
+ readonly transferLease: number
1296
+ }
1297
+
1298
+ export type ValueHandoffEffect =
1299
+ | { readonly kind: 'send-prepare'; readonly direction: ValueHandoffDirection }
1300
+ | { readonly kind: 'invalidate-prepared-clone' }
1301
+ | { readonly kind: 'resume-authoritative-source'; readonly side: ValueChannelSide }
1302
+ | { readonly kind: 'request-source-release-status' }
1303
+ | { readonly kind: 'enqueue-immutable-destination-commit' }
1304
+ | {
1305
+ readonly kind: 'request-autonomous-destination-root'
1306
+ readonly root: 'live-ui-frame' | JsDestinationRoot
1307
+ }
1308
+ | { readonly kind: 'arm-manual-frame-group'; readonly manualFrameGroupId: number }
1309
+ | { readonly kind: 'rebuild-same-commit-with-suffix'; readonly suffixStartSequence: number }
1310
+ | { readonly kind: 'cancel-transfer-timer' }
1311
+ | { readonly kind: 'release-prepared-state' }
1312
+ | { readonly kind: 'release-prepared-clone' }
1313
+ | { readonly kind: 'release-transfer-activity' }
1314
+ | { readonly kind: 'permit-destination-run-or-reverse' }
1315
+ | { readonly kind: 'enter-change-host-silent-drain' }
1316
+ | { readonly kind: 'refuse-live-channel-property' }
1317
+ | { readonly kind: 'finalize-bounded-refusal-without-run-or-reverse' }
1318
+
1319
+ export interface ValueHandoffTransition {
1320
+ readonly state: DriverValueChannelState
1321
+ readonly observation: DriverValueChannelObservation
1322
+ readonly orderedEffects: readonly ValueHandoffEffect[]
1323
+ readonly outcome:
1324
+ | { readonly kind: 'APPLIED' }
1325
+ | { readonly kind: 'IGNORED_STALE_EVENT' }
1326
+ | {
1327
+ readonly kind: 'SOURCE_STATUS_REFUSED'
1328
+ readonly reason: ValueHandoffSourceStatusRefusalReason
1329
+ }
1330
+ }
1331
+
1332
+ export interface ValueHandoffPhaseEventTransition {
1333
+ readonly state: DriverValueChannelState
1334
+ readonly observation: DriverValueChannelObservation
1335
+ readonly disposition: ValueHandoffPhaseEventDisposition
1336
+ readonly orderedEffects: readonly ValueHandoffEffect[]
1337
+ }
1338
+
1339
+ export type DriverValueChannelOperation =
1340
+ | ValueHandoffEvent['kind']
1341
+ | ValueHandoffPhaseEventOperation
1342
+ | ValueHandoffProtocolCheckpointOperation
1343
+ | `consume-js-destination-commit-${JsDestinationRoot}`
1344
+ | 'validate-destination-commit'
1345
+ | 'validate-destination-commit-acknowledgement'
1346
+ | 'produce-source-status'
1347
+ | 'registry-resolve-binding'
1348
+ | 'registry-detach-binding'
1349
+ | 'registry-rebind'
1350
+ | 'registry-reconcile-retention'
1351
+ | 'registry-snapshot-for-handoff'
1352
+ | 'registry-observe-canonical-operation'
1353
+ | 'drain-post-release-commit-loss'
1354
+
1355
+ export const DRIVER_VALUE_CHANNEL_UNIMPLEMENTED =
1356
+ 'NATIVE_MOTION_DRIVER_VALUE_CHANNEL_UNIMPLEMENTED' as const
1357
+
1358
+ export class DriverValueChannelUnimplementedError extends Error {
1359
+ readonly code = DRIVER_VALUE_CHANNEL_UNIMPLEMENTED
1360
+ readonly operation: DriverValueChannelOperation
1361
+
1362
+ constructor(operation: DriverValueChannelOperation) {
1363
+ super(
1364
+ `native-motion: driver value-channel operation '${operation}' is unimplemented ` +
1365
+ '(REQ-VALUE-014/015, REQ-DRIVER-034)',
1366
+ )
1367
+ this.name = 'DriverValueChannelUnimplementedError'
1368
+ this.operation = operation
1369
+ }
1370
+ }
1371
+
1372
+ export const DRIVER_VALUE_CHANNEL_INVARIANT =
1373
+ 'NATIVE_MOTION_DRIVER_VALUE_CHANNEL_INVARIANT' as const
1374
+
1375
+ export type DriverValueChannelInvariant =
1376
+ | 'registry-record-missing'
1377
+ | 'registry-rebind-previous-record-missing'
1378
+ | 'handoff-event-kind-unrecognized'
1379
+ | 'phase-event-kind-unrecognized'
1380
+ | 'phase-event-phase-unrecognized'
1381
+ | 'protocol-checkpoint-unrecognized'
1382
+ | 'protocol-event-kind-unrecognized'
1383
+ | 'commit-loss-work-kind-unrecognized'
1384
+
1385
+ /** Typed fail-closed refusal for malformed, conflicting, or out-of-sequence channel input. */
1386
+ export class DriverValueChannelInvariantError extends Error {
1387
+ readonly code = DRIVER_VALUE_CHANNEL_INVARIANT
1388
+ readonly invariant: DriverValueChannelInvariant
1389
+ readonly operation: DriverValueChannelOperation | null
1390
+
1391
+ constructor(invariant: DriverValueChannelInvariant, operation?: DriverValueChannelOperation) {
1392
+ super(
1393
+ `native-motion: driver value-channel invariant '${invariant}' was violated` +
1394
+ (operation === undefined ? '' : ` by operation '${operation}'`) +
1395
+ ' (REQ-VALUE-014/015, REQ-DRIVER-034)',
1396
+ )
1397
+ this.name = 'DriverValueChannelInvariantError'
1398
+ this.invariant = invariant
1399
+ this.operation = operation ?? null
1400
+ }
1401
+ }
1402
+
1403
+ function ownerSide(
1404
+ ownership: ValueHandoffOwnership,
1405
+ direction: ValueHandoffDirection | null,
1406
+ ): ValueChannelSide | null {
1407
+ switch (ownership) {
1408
+ case 'UI_RUNNING':
1409
+ case 'UI_FROZEN_TO_JS':
1410
+ case 'JS_PREPARED':
1411
+ case 'UI_RELEASE_PENDING_TO_JS':
1412
+ case 'UI_DRAINING':
1413
+ return 'UI'
1414
+ case 'JS_RUNNING':
1415
+ case 'JS_FROZEN_TO_UI':
1416
+ case 'UI_PREPARED':
1417
+ case 'JS_RELEASE_PENDING_TO_UI':
1418
+ case 'JS_DRAINING':
1419
+ return 'JS'
1420
+ case 'BOUNDED_REFUSAL_DRAINING':
1421
+ return direction === 'UI_TO_JS' ? 'JS' : direction === 'JS_TO_UI' ? 'UI' : null
1422
+ case 'UI_RELEASED_TO_JS':
1423
+ case 'JS_RELEASED_TO_UI':
1424
+ case 'IDLE':
1425
+ case 'TERMINAL_REFUSED':
1426
+ return null
1427
+ default: {
1428
+ const exhaustive: never = ownership
1429
+ return exhaustive
1430
+ }
1431
+ }
1432
+ }
1433
+
1434
+ export function observeDriverValueChannel(
1435
+ state: DriverValueChannelState,
1436
+ ): DriverValueChannelObservation {
1437
+ const { handoff, identity } = state
1438
+ const reasons: DriverValueChannelRetentionReason[] = []
1439
+ if (identity.publicReferenceRetained) reasons.push('public-reference')
1440
+ if (identity.sinkCount > 0) reasons.push('sink')
1441
+ if (identity.changeObserverCount > 0) reasons.push('change-observer')
1442
+ if (identity.publicControlSlotId !== null) reasons.push('public-control-slot')
1443
+ if (identity.physicalExecutionIds.length > 0) reasons.push('physical-execution')
1444
+ if (identity.dueWorkSequences.length > 0) reasons.push('due-work')
1445
+ if (identity.valueCompletionSequences.length > 0) reasons.push('value-completion')
1446
+ if (identity.targetBatchDependencyIds.length > 0) reasons.push('target-batch-dependency')
1447
+ if (identity.dependencyEventOutboxSequences.length > 0) {
1448
+ reasons.push('dependency-event-outbox')
1449
+ }
1450
+ if (identity.sharedBatchReferenceCount > 0) reasons.push('shared-batch-reference')
1451
+ if (identity.pendingFrameActivity) reasons.push('pending-frame-activity')
1452
+ if (handoff.resources.preparedStateRetained) reasons.push('prepared-state')
1453
+ if (handoff.resources.preparedCloneRetained) reasons.push('prepared-clone')
1454
+ if (handoff.resources.transferActivityRetained) reasons.push('transfer-activity')
1455
+ if (handoff.resources.transferTimerArmed) reasons.push('transfer-timer')
1456
+
1457
+ return {
1458
+ channelId: identity.channelId,
1459
+ jsRecordId: identity.jsRecordId,
1460
+ uiRecordId: identity.uiRecordId,
1461
+ ownership: handoff.ownership,
1462
+ ownerSide: ownerSide(handoff.ownership, handoff.direction),
1463
+ desiredSide: handoff.desiredSide,
1464
+ transferLease: handoff.transferLease,
1465
+ currentBindingGeneration: identity.currentBindingGeneration,
1466
+ retiredBindingGeneration: identity.retiredBindingGeneration,
1467
+ publicControlSlotId: identity.publicControlSlotId,
1468
+ physicalExecutionCount: identity.physicalExecutionIds.length,
1469
+ dueWorkCount: identity.dueWorkSequences.length,
1470
+ valueCompletionCount: identity.valueCompletionSequences.length,
1471
+ targetBatchDependencyCount: identity.targetBatchDependencyIds.length,
1472
+ dependencyEventOutboxCount: identity.dependencyEventOutboxSequences.length,
1473
+ dependencyEventAckWatermark: identity.dependencyEventAckWatermark,
1474
+ commitQueueLength: handoff.commitQueue.length,
1475
+ sourceMutationQueueLength: handoff.sourceMutationQueue.length,
1476
+ sentThroughAuthoritativeSequence: handoff.sentThroughAuthoritativeSequence,
1477
+ drainedThroughAuthoritativeSequence: handoff.drainedThroughAuthoritativeSequence,
1478
+ retentionReasons: reasons,
1479
+ retained: reasons.length > 0,
1480
+ }
1481
+ }
1482
+
1483
+ function frozenOwnershipFor(direction: ValueHandoffDirection): ValueHandoffOwnership {
1484
+ return direction === 'UI_TO_JS' ? 'UI_FROZEN_TO_JS' : 'JS_FROZEN_TO_UI'
1485
+ }
1486
+
1487
+ function preparedOwnershipFor(direction: ValueHandoffDirection): ValueHandoffOwnership {
1488
+ return direction === 'UI_TO_JS' ? 'JS_PREPARED' : 'UI_PREPARED'
1489
+ }
1490
+
1491
+ function releasePendingOwnershipFor(direction: ValueHandoffDirection): ValueHandoffOwnership {
1492
+ return direction === 'UI_TO_JS' ? 'UI_RELEASE_PENDING_TO_JS' : 'JS_RELEASE_PENDING_TO_UI'
1493
+ }
1494
+
1495
+ function releasedOwnershipFor(direction: ValueHandoffDirection): ValueHandoffOwnership {
1496
+ return direction === 'UI_TO_JS' ? 'UI_RELEASED_TO_JS' : 'JS_RELEASED_TO_UI'
1497
+ }
1498
+
1499
+ function sourceRunningOwnershipFor(direction: ValueHandoffDirection): ValueHandoffOwnership {
1500
+ return direction === 'UI_TO_JS' ? 'UI_RUNNING' : 'JS_RUNNING'
1501
+ }
1502
+
1503
+ function destinationRunningOwnershipFor(direction: ValueHandoffDirection): ValueHandoffOwnership {
1504
+ return direction === 'UI_TO_JS' ? 'JS_RUNNING' : 'UI_RUNNING'
1505
+ }
1506
+
1507
+ function sourceSideFor(direction: ValueHandoffDirection): ValueChannelSide {
1508
+ return direction === 'UI_TO_JS' ? 'UI' : 'JS'
1509
+ }
1510
+
1511
+ /** The one already-declared autonomous destination turn a successful COMMIT enqueue activates. */
1512
+ function destinationActivationEffect(
1513
+ runtime: DestinationRuntime | null,
1514
+ ): ValueHandoffEffect | null {
1515
+ if (runtime === null) return null
1516
+ switch (runtime.kind) {
1517
+ case 'live-ui':
1518
+ return { kind: 'request-autonomous-destination-root', root: 'live-ui-frame' }
1519
+ case 'manual-ui':
1520
+ return { kind: 'arm-manual-frame-group', manualFrameGroupId: runtime.manualFrameGroupId }
1521
+ case 'js':
1522
+ return { kind: 'request-autonomous-destination-root', root: runtime.root }
1523
+ default: {
1524
+ const exhaustive: never = runtime
1525
+ return exhaustive
1526
+ }
1527
+ }
1528
+ }
1529
+
1530
+ function withHandoff(
1531
+ state: DriverValueChannelState,
1532
+ handoff: ValueHandoffProtocolState,
1533
+ ): DriverValueChannelState {
1534
+ return { identity: state.identity, handoff }
1535
+ }
1536
+
1537
+ function appliedTransition(
1538
+ state: DriverValueChannelState,
1539
+ orderedEffects: readonly ValueHandoffEffect[],
1540
+ ): ValueHandoffTransition {
1541
+ return {
1542
+ state,
1543
+ observation: observeDriverValueChannel(state),
1544
+ orderedEffects,
1545
+ outcome: { kind: 'APPLIED' },
1546
+ }
1547
+ }
1548
+
1549
+ function ignoredTransition(state: DriverValueChannelState): ValueHandoffTransition {
1550
+ return {
1551
+ state,
1552
+ observation: observeDriverValueChannel(state),
1553
+ orderedEffects: [],
1554
+ outcome: { kind: 'IGNORED_STALE_EVENT' },
1555
+ }
1556
+ }
1557
+
1558
+ /**
1559
+ * The immutable five-field transfer identity every producer/consumer boundary revalidates; a
1560
+ * mismatch is refused by name and never mutates retained protocol state.
1561
+ */
1562
+ function validateTransferIdentityTuple(
1563
+ state: DriverValueChannelState,
1564
+ candidate: ValueHandoffSourceStatusIdentity,
1565
+ ): ValueHandoffSourceStatusRefusalReason | null {
1566
+ if (candidate.channelId !== state.identity.channelId) return 'wrong-channel'
1567
+ if (candidate.sourceRecordId !== state.handoff.sourceRecordId) return 'wrong-source-record'
1568
+ if (candidate.destinationRecordId !== state.handoff.destinationRecordId) {
1569
+ return 'wrong-destination-record'
1570
+ }
1571
+ if (candidate.direction !== state.handoff.direction) return 'wrong-direction'
1572
+ if (candidate.transferLease !== state.handoff.transferLease) return 'wrong-transfer-lease'
1573
+ return null
1574
+ }
1575
+
1576
+ function releasedResourceState(): ValueChannelHandoffResourceState {
1577
+ return {
1578
+ preparedStateRetained: false,
1579
+ preparedCloneRetained: false,
1580
+ transferActivityRetained: false,
1581
+ transferTimerArmed: false,
1582
+ }
1583
+ }
1584
+
1585
+ function baseDestinationCommit(
1586
+ state: DriverValueChannelState,
1587
+ direction: ValueHandoffDirection,
1588
+ ): ImmutableDestinationCommit {
1589
+ return {
1590
+ kind: 'DESTINATION_COMMIT',
1591
+ channelId: state.identity.channelId,
1592
+ sourceRecordId: state.handoff.sourceRecordId ?? -1,
1593
+ destinationRecordId: state.handoff.destinationRecordId ?? -1,
1594
+ direction,
1595
+ transferLease: state.handoff.transferLease,
1596
+ releaseFenceSequence: state.handoff.releaseFenceSequence ?? -1,
1597
+ sentThroughAuthoritativeSequence: state.handoff.sentThroughAuthoritativeSequence,
1598
+ includedPostFencePrefix: [],
1599
+ }
1600
+ }
1601
+
1602
+ /** Append one retained-work entry to the current immutable COMMIT, creating it when absent. */
1603
+ function appendEntryToCommitQueue(
1604
+ state: DriverValueChannelState,
1605
+ entry: NormalizedRetainedWorkEntry,
1606
+ ): readonly ImmutableDestinationCommit[] {
1607
+ const queue = state.handoff.commitQueue
1608
+ const current = queue.at(-1)
1609
+ if (current === undefined) {
1610
+ const direction = state.handoff.direction
1611
+ if (direction === null) return queue
1612
+ return [
1613
+ {
1614
+ ...baseDestinationCommit(state, direction),
1615
+ sentThroughAuthoritativeSequence: entry.authoritativeSequence,
1616
+ includedPostFencePrefix: [entry],
1617
+ },
1618
+ ]
1619
+ }
1620
+ return [
1621
+ ...queue.slice(0, -1),
1622
+ {
1623
+ ...current,
1624
+ sentThroughAuthoritativeSequence: entry.authoritativeSequence,
1625
+ includedPostFencePrefix: [...current.includedPostFencePrefix, entry],
1626
+ },
1627
+ ]
1628
+ }
1629
+
1630
+ function isProtocolReadEvent(
1631
+ event: ValueHandoffPhaseEvent | ValueHandoffProtocolEvent,
1632
+ ): event is ValueHandoffPhaseReadEvent {
1633
+ return (
1634
+ event.kind === 'synchronous-snapshot' ||
1635
+ event.kind === 'synchronous-get' ||
1636
+ event.kind === 'synchronous-get-velocity'
1637
+ )
1638
+ }
1639
+
1640
+ /**
1641
+ * Fail closed on any phase outside the closed union BEFORE a phase-keyed transition runs; an
1642
+ * untyped caller must never fall into the pre-fence branch by default.
1643
+ */
1644
+ function requireValueHandoffPhase(phase: ValueHandoffPhase): void {
1645
+ switch (phase) {
1646
+ case 'PREPARED':
1647
+ case 'RELEASE_PENDING_PRE_FENCE':
1648
+ case 'RELEASED_POST_FENCE':
1649
+ return
1650
+ default: {
1651
+ const exhaustive: never = phase
1652
+ void exhaustive
1653
+ throw new DriverValueChannelInvariantError('phase-event-phase-unrecognized')
1654
+ }
1655
+ }
1656
+ }
1657
+
1658
+ /** Fail closed on any phase event kind outside the closed union before mutation dispatch. */
1659
+ function requirePhaseEventKind(kind: ValueHandoffPhaseEventKind): void {
1660
+ switch (kind) {
1661
+ case 'sink-attach':
1662
+ case 'sink-detach':
1663
+ case 'observer-arm':
1664
+ case 'observer-disarm':
1665
+ case 'start':
1666
+ case 'retarget':
1667
+ case 'set':
1668
+ case 'jump':
1669
+ case 'stop':
1670
+ case 'destroy':
1671
+ case 'public-slot':
1672
+ case 'physical-sample':
1673
+ case 'physical-done':
1674
+ case 'completion-arm':
1675
+ case 'completion-resolved':
1676
+ case 'batch-dependency':
1677
+ case 'batch-outbox':
1678
+ case 'batch-outbox-ack':
1679
+ case 'fifo-task':
1680
+ case 'binding-generation':
1681
+ case 'synchronous-snapshot':
1682
+ case 'synchronous-get':
1683
+ case 'synchronous-get-velocity':
1684
+ return
1685
+ default: {
1686
+ const exhaustive: never = kind
1687
+ void exhaustive
1688
+ throw new DriverValueChannelInvariantError('phase-event-kind-unrecognized')
1689
+ }
1690
+ }
1691
+ }
1692
+
1693
+ /** Fail closed on any checkpoint outside the closed union before a keyed transition runs. */
1694
+ function requireProtocolCheckpoint(checkpoint: ValueHandoffProtocolCheckpoint): void {
1695
+ switch (checkpoint) {
1696
+ case 'FREEZE':
1697
+ case 'PREPARE_READY':
1698
+ case 'RELEASE_PENDING_SOURCE_UNKNOWN':
1699
+ case 'SOURCE_CONFIRMED_ABORTED':
1700
+ case 'SOURCE_CONFIRMED_RELEASED':
1701
+ case 'DESTINATION_COMMIT_CONSUMPTION':
1702
+ case 'DESTINATION_ACKNOWLEDGED':
1703
+ return
1704
+ default: {
1705
+ const exhaustive: never = checkpoint
1706
+ void exhaustive
1707
+ throw new DriverValueChannelInvariantError('protocol-checkpoint-unrecognized')
1708
+ }
1709
+ }
1710
+ }
1711
+
1712
+ /** Fail closed on any protocol event kind outside the closed union before mutation dispatch. */
1713
+ function requireProtocolEventKind(kind: ValueHandoffProtocolEventKind): void {
1714
+ switch (kind) {
1715
+ case 'sink-attach':
1716
+ case 'sink-detach':
1717
+ case 'observer-arm':
1718
+ case 'observer-disarm':
1719
+ case 'start':
1720
+ case 'retarget':
1721
+ case 'set':
1722
+ case 'jump':
1723
+ case 'stop':
1724
+ case 'destroy':
1725
+ case 'public-slot':
1726
+ case 'physical-sample':
1727
+ case 'physical-done':
1728
+ case 'completion-arm':
1729
+ case 'completion-resolved':
1730
+ case 'batch-dependency':
1731
+ case 'batch-outbox':
1732
+ case 'batch-outbox-ack':
1733
+ case 'fifo-task':
1734
+ case 'binding-generation':
1735
+ case 'tokenless-task-enqueue':
1736
+ case 'lifecycle-enqueue':
1737
+ case 'synchronous-snapshot':
1738
+ case 'synchronous-get':
1739
+ case 'synchronous-get-velocity':
1740
+ return
1741
+ default: {
1742
+ const exhaustive: never = kind
1743
+ void exhaustive
1744
+ throw new DriverValueChannelInvariantError('protocol-event-kind-unrecognized')
1745
+ }
1746
+ }
1747
+ }
1748
+
1749
+ function readViewForPhase(phase: ValueHandoffPhase): ValueHandoffPhaseReadView {
1750
+ switch (phase) {
1751
+ case 'PREPARED':
1752
+ return 'AUTHORITATIVE_SOURCE'
1753
+ case 'RELEASE_PENDING_PRE_FENCE':
1754
+ return 'SOURCE_WITH_DIRECT_OPERATION_OVERLAY'
1755
+ case 'RELEASED_POST_FENCE':
1756
+ return 'PREPARED_DESTINATION_WITH_DIRECT_OPERATION_OVERLAY'
1757
+ default: {
1758
+ const exhaustive: never = phase
1759
+ return exhaustive
1760
+ }
1761
+ }
1762
+ }
1763
+
1764
+ function readAuthorityForCheckpoint(
1765
+ checkpoint: ValueHandoffProtocolCheckpoint,
1766
+ ): ValueHandoffProtocolReadAuthority {
1767
+ switch (checkpoint) {
1768
+ case 'FREEZE':
1769
+ case 'PREPARE_READY':
1770
+ case 'SOURCE_CONFIRMED_ABORTED':
1771
+ return 'AUTHORITATIVE_SOURCE'
1772
+ case 'RELEASE_PENDING_SOURCE_UNKNOWN':
1773
+ return 'SOURCE_WITH_DIRECT_OPERATION_OVERLAY'
1774
+ case 'SOURCE_CONFIRMED_RELEASED':
1775
+ case 'DESTINATION_COMMIT_CONSUMPTION':
1776
+ return 'PREPARED_DESTINATION_WITH_DIRECT_OPERATION_OVERLAY'
1777
+ case 'DESTINATION_ACKNOWLEDGED':
1778
+ return 'ACKNOWLEDGED_DESTINATION'
1779
+ default: {
1780
+ const exhaustive: never = checkpoint
1781
+ return exhaustive
1782
+ }
1783
+ }
1784
+ }
1785
+
1786
+ function mutationHandlingForCheckpoint(
1787
+ checkpoint: ValueHandoffProtocolCheckpoint,
1788
+ ): ValueHandoffProtocolMutationHandling {
1789
+ switch (checkpoint) {
1790
+ case 'FREEZE':
1791
+ return 'INVALIDATE_FREEZE_AND_REPREPARE_SOURCE'
1792
+ case 'PREPARE_READY':
1793
+ return 'ABORT_PREPARE_AND_REPREPARE_SOURCE'
1794
+ case 'RELEASE_PENDING_SOURCE_UNKNOWN':
1795
+ return 'SEQUENCE_WITHOUT_INFERRING_SOURCE_STATUS'
1796
+ case 'SOURCE_CONFIRMED_ABORTED':
1797
+ return 'APPLY_CONFIRMED_ABORT_AND_REPREPARE_SOURCE'
1798
+ case 'SOURCE_CONFIRMED_RELEASED':
1799
+ return 'APPEND_TO_IMMUTABLE_RELEASED_COMMIT'
1800
+ case 'DESTINATION_COMMIT_CONSUMPTION':
1801
+ return 'APPLY_IN_DESTINATION_COMMIT_PREFIX'
1802
+ case 'DESTINATION_ACKNOWLEDGED':
1803
+ return 'APPLY_TO_ACKNOWLEDGED_DESTINATION_OR_REVERSE'
1804
+ default: {
1805
+ const exhaustive: never = checkpoint
1806
+ return exhaustive
1807
+ }
1808
+ }
1809
+ }
1810
+
1811
+ function retainedWorkDispositionFor(
1812
+ checkpoint: ValueHandoffProtocolCheckpoint,
1813
+ event: ValueHandoffProtocolMutationEvent,
1814
+ ): ValueHandoffProtocolRetainedWorkDisposition {
1815
+ switch (checkpoint) {
1816
+ case 'FREEZE':
1817
+ return {
1818
+ kind: 'CARRIED_IN_INVALIDATED_FREEZE_REPREPARE',
1819
+ carriedEntry: event.retainedWorkEntry,
1820
+ }
1821
+ case 'PREPARE_READY':
1822
+ return {
1823
+ kind: 'CARRIED_IN_ABORTED_PREPARE_REPREPARE',
1824
+ carriedEntry: event.retainedWorkEntry,
1825
+ }
1826
+ case 'RELEASE_PENDING_SOURCE_UNKNOWN':
1827
+ return {
1828
+ kind: 'QUEUED_BEHIND_SOURCE_DECISION',
1829
+ queuedEntry: event.retainedWorkEntry,
1830
+ }
1831
+ case 'SOURCE_CONFIRMED_ABORTED':
1832
+ return {
1833
+ kind: 'APPLIED_TO_CONFIRMED_SOURCE_BEFORE_REPREPARE',
1834
+ appliedEntry: event.retainedWorkEntry,
1835
+ }
1836
+ case 'SOURCE_CONFIRMED_RELEASED':
1837
+ return {
1838
+ kind: 'APPENDED_TO_IMMUTABLE_RELEASED_COMMIT',
1839
+ appendedEntry: event.retainedWorkEntry,
1840
+ }
1841
+ case 'DESTINATION_COMMIT_CONSUMPTION':
1842
+ return {
1843
+ kind: 'APPLIED_TO_PREFILLED_DESTINATION_SLOT',
1844
+ destinationSlot: event.retainedWorkEntry.destinationSlot,
1845
+ appliedEntry: event.retainedWorkEntry,
1846
+ }
1847
+ case 'DESTINATION_ACKNOWLEDGED':
1848
+ return {
1849
+ kind: 'APPLIED_TO_ACKNOWLEDGED_DESTINATION_OR_REVERSE',
1850
+ destinationSlot: event.retainedWorkEntry.destinationSlot,
1851
+ appliedEntry: event.retainedWorkEntry,
1852
+ }
1853
+ default: {
1854
+ const exhaustive: never = checkpoint
1855
+ return exhaustive
1856
+ }
1857
+ }
1858
+ }
1859
+
1860
+ /**
1861
+ * Apply one confirmed retained-work entry to the identity record. Sequence-state entries adopt
1862
+ * their exact sequences and advance the dependency-outbox acknowledgement watermark; outbox
1863
+ * dependency events append their event sequence; destroy retires the current binding generation
1864
+ * and clears the current public reference, sinks, and observers (channel-generation retirement,
1865
+ * never public-object death).
1866
+ */
1867
+ function applyProtocolMutationToIdentity(
1868
+ identity: DriverValueChannelIdentityRecordState,
1869
+ event: ValueHandoffProtocolMutationEvent,
1870
+ ): DriverValueChannelIdentityRecordState {
1871
+ const entry = event.retainedWorkEntry
1872
+ switch (entry.kind) {
1873
+ case 'sequence-state':
1874
+ return {
1875
+ ...identity,
1876
+ dependencyEventAckWatermark: entry.sequences.dependencyOutboxAckSequence,
1877
+ sequences: entry.sequences,
1878
+ }
1879
+ case 'binding-generation':
1880
+ if (event.kind === 'destroy') {
1881
+ return {
1882
+ ...identity,
1883
+ publicReferenceRetained: false,
1884
+ sinkCount: 0,
1885
+ changeObserverCount: 0,
1886
+ currentBindingGeneration: entry.currentBindingGeneration,
1887
+ retiredBindingGeneration: entry.retiredBindingGeneration,
1888
+ }
1889
+ }
1890
+ return {
1891
+ ...identity,
1892
+ currentBindingGeneration: entry.currentBindingGeneration,
1893
+ retiredBindingGeneration: entry.retiredBindingGeneration,
1894
+ }
1895
+ case 'target-batch-dependency-event':
1896
+ return {
1897
+ ...identity,
1898
+ dependencyEventOutboxSequences: identity.dependencyEventOutboxSequences.includes(
1899
+ entry.eventSequence,
1900
+ )
1901
+ ? identity.dependencyEventOutboxSequences
1902
+ : [...identity.dependencyEventOutboxSequences, entry.eventSequence],
1903
+ sequences: {
1904
+ ...identity.sequences,
1905
+ batchDependencyEventSequence: entry.eventSequence,
1906
+ },
1907
+ }
1908
+ default:
1909
+ return identity
1910
+ }
1911
+ }
1912
+
1913
+ /** Append one COMMIT entry to its prefilled destination ledger slot, in place by slot identity. */
1914
+ function appendLedgerEntry(
1915
+ slot: ValueHandoffDestinationLedgerSlotState,
1916
+ entry: NormalizedRetainedWorkEntry,
1917
+ ): ValueHandoffDestinationLedgerSlotState {
1918
+ switch (slot.ledger) {
1919
+ case 'PUBLIC_CONTROL':
1920
+ return entry.kind === 'public-control-slot'
1921
+ ? { ...slot, entries: [...slot.entries, entry] }
1922
+ : slot
1923
+ case 'RETENTION':
1924
+ return entry.kind === 'retention-edge' ? { ...slot, entries: [...slot.entries, entry] } : slot
1925
+ case 'EXECUTION':
1926
+ return entry.kind === 'generator-execution'
1927
+ ? { ...slot, entries: [...slot.entries, entry] }
1928
+ : slot
1929
+ case 'DUE_WORK':
1930
+ return entry.kind === 'update-task' ||
1931
+ entry.kind === 'carrier-task' ||
1932
+ entry.kind === 'direct-write'
1933
+ ? { ...slot, entries: [...slot.entries, entry] }
1934
+ : slot
1935
+ case 'COMPLETION':
1936
+ return entry.kind === 'value-completion'
1937
+ ? { ...slot, entries: [...slot.entries, entry] }
1938
+ : slot
1939
+ case 'DEPENDENCY_EVENT':
1940
+ return entry.kind === 'target-batch-dependency-event'
1941
+ ? { ...slot, entries: [...slot.entries, entry] }
1942
+ : slot
1943
+ case 'GENERATION':
1944
+ return entry.kind === 'binding-generation'
1945
+ ? { ...slot, entries: [...slot.entries, entry] }
1946
+ : slot
1947
+ case 'SEQUENCE':
1948
+ return entry.kind === 'sequence-state' ? { ...slot, entries: [...slot.entries, entry] } : slot
1949
+ default: {
1950
+ const exhaustive: never = slot
1951
+ return exhaustive
1952
+ }
1953
+ }
1954
+ }
1955
+
1956
+ /** Grow one destination ledger slot at the consumption edge when the prefix declares a new slot. */
1957
+ function createLedgerSlot(
1958
+ entry: NormalizedRetainedWorkEntry,
1959
+ ): ValueHandoffDestinationLedgerSlotState {
1960
+ switch (entry.kind) {
1961
+ case 'public-control-slot':
1962
+ return { ledger: 'PUBLIC_CONTROL', slotId: entry.destinationSlot.slotId, entries: [entry] }
1963
+ case 'retention-edge':
1964
+ return { ledger: 'RETENTION', slotId: entry.destinationSlot.slotId, entries: [entry] }
1965
+ case 'generator-execution':
1966
+ return { ledger: 'EXECUTION', slotId: entry.destinationSlot.slotId, entries: [entry] }
1967
+ case 'update-task':
1968
+ case 'carrier-task':
1969
+ case 'direct-write':
1970
+ return { ledger: 'DUE_WORK', slotId: entry.destinationSlot.slotId, entries: [entry] }
1971
+ case 'value-completion':
1972
+ return { ledger: 'COMPLETION', slotId: entry.destinationSlot.slotId, entries: [entry] }
1973
+ case 'target-batch-dependency-event':
1974
+ return {
1975
+ ledger: 'DEPENDENCY_EVENT',
1976
+ slotId: entry.destinationSlot.slotId,
1977
+ entries: [entry],
1978
+ }
1979
+ case 'binding-generation':
1980
+ return { ledger: 'GENERATION', slotId: entry.destinationSlot.slotId, entries: [entry] }
1981
+ case 'sequence-state':
1982
+ return { ledger: 'SEQUENCE', slotId: entry.destinationSlot.slotId, entries: [entry] }
1983
+ default: {
1984
+ const exhaustive: never = entry
1985
+ return exhaustive
1986
+ }
1987
+ }
1988
+ }
1989
+
1990
+ function applyEntriesToLedgers(
1991
+ ledgers: ValueHandoffDestinationLedgerState,
1992
+ entries: readonly NormalizedRetainedWorkEntry[],
1993
+ ): ValueHandoffDestinationLedgerState {
1994
+ let slots = ledgers.slots
1995
+ for (const entry of entries) {
1996
+ const index = slots.findIndex(
1997
+ (slot) =>
1998
+ slot.ledger === entry.destinationSlot.ledger &&
1999
+ slot.slotId === entry.destinationSlot.slotId,
2000
+ )
2001
+ if (index < 0) {
2002
+ slots = [...slots, createLedgerSlot(entry)]
2003
+ continue
2004
+ }
2005
+ const slot = slots[index]
2006
+ if (slot === undefined) continue
2007
+ slots = [...slots.slice(0, index), appendLedgerEntry(slot, entry), ...slots.slice(index + 1)]
2008
+ }
2009
+ return { slots }
2010
+ }
2011
+
2012
+ class DriverValueChannelIdentityRegistryImpl<T> implements DriverValueChannelIdentityRegistry<T> {
2013
+ readonly #recordsByExactValue = new WeakMap<
2014
+ MotionValue<T>,
2015
+ DriverValueChannelIdentityRegistryRecord<T>
2016
+ >()
2017
+ readonly #retainedRecordsByChannelId = new Map<
2018
+ number,
2019
+ DriverValueChannelIdentityRegistryRecord<T>
2020
+ >()
2021
+
2022
+ constructor(options: CreateDriverValueChannelIdentityRegistryOptions<T>) {
2023
+ for (const record of options.initialRecords ?? []) {
2024
+ this.#recordsByExactValue.set(record.value, record)
2025
+ if (observeDriverValueChannel(record.state).retained) {
2026
+ this.#retainedRecordsByChannelId.set(record.state.identity.channelId, record)
2027
+ }
2028
+ }
2029
+ }
2030
+
2031
+ observe(): DriverValueChannelIdentityRegistryObservation {
2032
+ const retainedChannelIds = [...this.#retainedRecordsByChannelId.keys()].reduce<number[]>(
2033
+ (ordered, channelId) => {
2034
+ const insertionIndex = ordered.findIndex((existing) => existing > channelId)
2035
+ if (insertionIndex < 0) return [...ordered, channelId]
2036
+ return [...ordered.slice(0, insertionIndex), channelId, ...ordered.slice(insertionIndex)]
2037
+ },
2038
+ [],
2039
+ )
2040
+ return {
2041
+ retainedRecordCount: this.#retainedRecordsByChannelId.size,
2042
+ retainedChannelIds,
2043
+ }
2044
+ }
2045
+
2046
+ peekExact(value: MotionValue<T>): DriverValueChannelIdentityRegistryRecordObservation | null {
2047
+ const record = this.#recordsByExactValue.get(value)
2048
+ return record === undefined ? null : this.#observeRecord(record)
2049
+ }
2050
+
2051
+ peekById(channelId: number): DriverValueChannelIdentityRegistryRecordObservation | null {
2052
+ const record = this.#retainedRecordsByChannelId.get(channelId)
2053
+ return record === undefined ? null : this.#observeRecord(record)
2054
+ }
2055
+
2056
+ resolveBinding(
2057
+ request: DriverValueChannelResolveBindingRequest<T>,
2058
+ ): DriverValueChannelBindingResolution<T> {
2059
+ const existing = this.#recordsByExactValue.get(request.value)
2060
+ if (existing !== undefined) {
2061
+ // Exact-object identity is the law: conflicting caller ports, snapshots, or ids never fork
2062
+ // the canonical record; the binding only appends its sink registration.
2063
+ ;(existing.sinks as DriverValueChannelSinkRegistration[]).push(request.sink)
2064
+ return { kind: 'eligible', record: existing, created: false, attachedSink: request.sink }
2065
+ }
2066
+ const eligibility = request.port.evaluateBindingEligibility(request.currentValue)
2067
+ if (eligibility.kind === 'ineligible') {
2068
+ return { kind: 'ineligible', eligibility }
2069
+ }
2070
+ const record: DriverValueChannelIdentityRegistryRecord<T> = {
2071
+ value: request.value,
2072
+ port: request.port,
2073
+ state: request.requestedState,
2074
+ sinks: [request.sink],
2075
+ }
2076
+ this.#recordsByExactValue.set(request.value, record)
2077
+ if (observeDriverValueChannel(record.state).retained) {
2078
+ this.#retainedRecordsByChannelId.set(record.state.identity.channelId, record)
2079
+ }
2080
+ return { kind: 'eligible', record, created: true, attachedSink: request.sink }
2081
+ }
2082
+
2083
+ detachBinding(
2084
+ request: DriverValueChannelDetachBindingRequest<T>,
2085
+ ): DriverValueChannelIdentityRegistryRecord<T> | null {
2086
+ const record = this.#recordsByExactValue.get(request.value)
2087
+ if (record === undefined) return null
2088
+ const sinks = record.sinks as DriverValueChannelSinkRegistration[]
2089
+ let index = sinks.indexOf(request.sink)
2090
+ if (index < 0) {
2091
+ index = sinks.findIndex(
2092
+ (candidate) =>
2093
+ candidate.driverId === request.sink.driverId &&
2094
+ candidate.handle === request.sink.handle &&
2095
+ candidate.key === request.sink.key &&
2096
+ candidate.bindingGeneration === request.sink.bindingGeneration,
2097
+ )
2098
+ }
2099
+ if (index >= 0) sinks.splice(index, 1)
2100
+ return record
2101
+ }
2102
+
2103
+ rebind(request: DriverValueChannelRebindRequest<T>): DriverValueChannelRebindResult<T> {
2104
+ const previousRecord = this.detachBinding({
2105
+ value: request.previousValue,
2106
+ sink: request.previousSink,
2107
+ })
2108
+ if (previousRecord === null) {
2109
+ throw new DriverValueChannelInvariantError(
2110
+ 'registry-rebind-previous-record-missing',
2111
+ 'registry-rebind',
2112
+ )
2113
+ }
2114
+ return { previousRecord, replacement: this.resolveBinding(request.replacement) }
2115
+ }
2116
+
2117
+ reconcileRetention(
2118
+ reconciliation: DriverValueChannelRetentionReconciliation<T>,
2119
+ ): DriverValueChannelIdentityRegistryObservation {
2120
+ const record = this.#recordsByExactValue.get(reconciliation.value)
2121
+ if (record === undefined) {
2122
+ throw new DriverValueChannelInvariantError(
2123
+ 'registry-record-missing',
2124
+ 'registry-reconcile-retention',
2125
+ )
2126
+ }
2127
+ const nextRecord: DriverValueChannelIdentityRegistryRecord<T> = {
2128
+ value: record.value,
2129
+ port: record.port,
2130
+ state: reconciliation.nextState,
2131
+ sinks: record.sinks,
2132
+ }
2133
+ this.#recordsByExactValue.set(record.value, nextRecord)
2134
+ for (const [channelId, retained] of this.#retainedRecordsByChannelId) {
2135
+ if (retained === record) this.#retainedRecordsByChannelId.delete(channelId)
2136
+ }
2137
+ if (observeDriverValueChannel(nextRecord.state).retained) {
2138
+ this.#retainedRecordsByChannelId.set(nextRecord.state.identity.channelId, nextRecord)
2139
+ }
2140
+ return this.observe()
2141
+ }
2142
+
2143
+ snapshotForHandoff(value: MotionValue<T>): DriverValueChannelState {
2144
+ const record = this.#recordsByExactValue.get(value)
2145
+ if (record === undefined) {
2146
+ throw new DriverValueChannelInvariantError(
2147
+ 'registry-record-missing',
2148
+ 'registry-snapshot-for-handoff',
2149
+ )
2150
+ }
2151
+ // Identity-local state only: the JS-global batch controller is never stored or cloned here.
2152
+ return record.state
2153
+ }
2154
+
2155
+ observeCanonicalOperation(
2156
+ value: MotionValue<T>,
2157
+ ): DriverValueChannelCanonicalOperationObservation<T> {
2158
+ const record = this.#recordsByExactValue.get(value)
2159
+ if (record === undefined) {
2160
+ throw new DriverValueChannelInvariantError(
2161
+ 'registry-record-missing',
2162
+ 'registry-observe-canonical-operation',
2163
+ )
2164
+ }
2165
+ const port = record.port.observe()
2166
+ if (port.currentExecutionId !== null || !record.value.isAnimating()) {
2167
+ return { record, port }
2168
+ }
2169
+ // A public start that bypassed the port is still observable through the canonical record:
2170
+ // report the record's latest retained physical execution identity.
2171
+ const currentExecutionId = record.state.identity.physicalExecutionIds.at(-1) ?? null
2172
+ if (currentExecutionId === null) {
2173
+ return { record, port }
2174
+ }
2175
+ return { record, port: { ...port, currentExecutionId } }
2176
+ }
2177
+
2178
+ #observeRecord(
2179
+ record: DriverValueChannelIdentityRegistryRecord<T>,
2180
+ ): DriverValueChannelIdentityRegistryRecordObservation {
2181
+ const state = observeDriverValueChannel(record.state)
2182
+ const port = record.port.observe()
2183
+ return {
2184
+ channelId: state.channelId,
2185
+ jsRecordId: state.jsRecordId,
2186
+ uiRecordId: state.uiRecordId,
2187
+ portChannelId: port.channelId,
2188
+ creationKind: port.creationKind,
2189
+ initializerProvenance: port.initializerProvenance,
2190
+ sinkCount: record.sinks.length,
2191
+ sinks: record.sinks,
2192
+ currentBindingGeneration: state.currentBindingGeneration,
2193
+ retiredBindingGeneration: state.retiredBindingGeneration,
2194
+ retentionReasons: state.retentionReasons,
2195
+ retained: state.retained,
2196
+ stronglyRegistered: this.#retainedRecordsByChannelId.get(state.channelId) === record,
2197
+ }
2198
+ }
2199
+ }
2200
+
2201
+ export function createDriverValueChannelIdentityRegistry<T>(
2202
+ options: CreateDriverValueChannelIdentityRegistryOptions<T> = {},
2203
+ ): DriverValueChannelIdentityRegistry<T> {
2204
+ return new DriverValueChannelIdentityRegistryImpl(options)
2205
+ }
2206
+
2207
+ export function validateDestinationCommit(
2208
+ state: DriverValueChannelState,
2209
+ commit: ImmutableDestinationCommit,
2210
+ ): DestinationCommitValidation {
2211
+ const refusal = validateTransferIdentityTuple(state, commit)
2212
+ if (refusal !== null) return { kind: 'rejected', reason: refusal }
2213
+ const fence = state.handoff.releaseFenceSequence
2214
+ if (fence === null || commit.releaseFenceSequence !== fence) {
2215
+ return { kind: 'rejected', reason: 'wrong-release-fence' }
2216
+ }
2217
+ if (commit.sentThroughAuthoritativeSequence > state.handoff.sentThroughAuthoritativeSequence) {
2218
+ return { kind: 'rejected', reason: 'ahead-of-sent-watermark' }
2219
+ }
2220
+ if (commit.sentThroughAuthoritativeSequence < state.handoff.sentThroughAuthoritativeSequence) {
2221
+ return { kind: 'rejected', reason: 'sent-watermark-regressed' }
2222
+ }
2223
+ const prefix = commit.includedPostFencePrefix
2224
+ for (let index = 1; index < prefix.length; index += 1) {
2225
+ const previous = prefix[index - 1]
2226
+ const current = prefix[index]
2227
+ if (previous === undefined || current === undefined) break
2228
+ if (current.authoritativeSequence <= previous.authoritativeSequence) {
2229
+ return { kind: 'rejected', reason: 'prefix-not-contiguous' }
2230
+ }
2231
+ }
2232
+ const last = prefix.at(-1)
2233
+ if (last !== undefined && last.authoritativeSequence > commit.sentThroughAuthoritativeSequence) {
2234
+ return { kind: 'rejected', reason: 'prefix-not-contiguous' }
2235
+ }
2236
+ return {
2237
+ kind: 'accepted',
2238
+ duplicate:
2239
+ commit.sentThroughAuthoritativeSequence <= state.handoff.drainedThroughAuthoritativeSequence,
2240
+ }
2241
+ }
2242
+
2243
+ export function validateDestinationCommitAcknowledgement(
2244
+ state: DriverValueChannelState,
2245
+ acknowledgement: DestinationCommitAcknowledgement,
2246
+ ): DestinationCommitAcknowledgementValidation {
2247
+ const refusal = validateTransferIdentityTuple(state, acknowledgement)
2248
+ if (refusal !== null) return { kind: 'rejected', reason: refusal }
2249
+ const fence = state.handoff.releaseFenceSequence
2250
+ if (fence === null || acknowledgement.releaseFenceSequence !== fence) {
2251
+ return { kind: 'rejected', reason: 'wrong-release-fence' }
2252
+ }
2253
+ if (acknowledgement.drainedThroughAuthoritativeSequence < fence) {
2254
+ return { kind: 'rejected', reason: 'before-release-fence-watermark' }
2255
+ }
2256
+ if (
2257
+ acknowledgement.drainedThroughAuthoritativeSequence >
2258
+ state.handoff.sentThroughAuthoritativeSequence
2259
+ ) {
2260
+ return { kind: 'rejected', reason: 'ahead-of-sent-watermark' }
2261
+ }
2262
+ if (
2263
+ acknowledgement.drainedThroughAuthoritativeSequence <=
2264
+ state.handoff.drainedThroughAuthoritativeSequence
2265
+ ) {
2266
+ return { kind: 'accepted', extent: 'duplicate' }
2267
+ }
2268
+ if (
2269
+ acknowledgement.drainedThroughAuthoritativeSequence <
2270
+ state.handoff.sentThroughAuthoritativeSequence
2271
+ ) {
2272
+ return { kind: 'accepted', extent: 'partial' }
2273
+ }
2274
+ return { kind: 'accepted', extent: 'through-current-tail' }
2275
+ }
2276
+
2277
+ export function produceValueHandoffSourceStatus(
2278
+ state: DriverValueChannelState,
2279
+ request: ValueHandoffSourceStatusProductionRequest,
2280
+ ): ValueHandoffSourceStatusProductionTransition {
2281
+ const refusal = validateTransferIdentityTuple(state, request)
2282
+ if (refusal !== null) {
2283
+ return { state, validation: { kind: 'rejected', reason: refusal }, producedStatus: null }
2284
+ }
2285
+ const identity: ValueHandoffSourceStatusIdentity = {
2286
+ channelId: request.channelId,
2287
+ sourceRecordId: request.sourceRecordId,
2288
+ destinationRecordId: request.destinationRecordId,
2289
+ direction: request.direction,
2290
+ transferLease: request.transferLease,
2291
+ }
2292
+ const producedStatus: ValueHandoffSourceStatus =
2293
+ request.outcome === 'ABORTED'
2294
+ ? { ...identity, kind: 'ABORTED', authoritativeSequence: request.authoritativeSequence }
2295
+ : { ...identity, kind: 'RELEASED', releaseFenceSequence: request.releaseFenceSequence }
2296
+ return { state, validation: { kind: 'accepted' }, producedStatus }
2297
+ }
2298
+
2299
+ function beginHandoff(
2300
+ state: DriverValueChannelState,
2301
+ event: Extract<ValueHandoffEvent, { readonly kind: 'begin-handoff' }>,
2302
+ ): ValueHandoffTransition {
2303
+ const direction = event.direction
2304
+ const next = withHandoff(state, {
2305
+ ...state.handoff,
2306
+ ownership: frozenOwnershipFor(direction),
2307
+ direction,
2308
+ transferLease: event.transferLease,
2309
+ phaseToken: event.phaseToken,
2310
+ destinationRecordId: event.destinationRecordId,
2311
+ destinationRuntime: event.destinationRuntime,
2312
+ absoluteTransferDeadlineMs: event.absoluteTransferDeadlineMs,
2313
+ absoluteNoConsumerDeadlineMs: event.absoluteNoConsumerDeadlineMs,
2314
+ releaseFenceSequence: null,
2315
+ preparedMutableTuple: null,
2316
+ sourceMutationQueue: [],
2317
+ commitQueue: [],
2318
+ })
2319
+ return appliedTransition(next, [{ kind: 'send-prepare', direction }])
2320
+ }
2321
+
2322
+ function destinationReady(
2323
+ state: DriverValueChannelState,
2324
+ event: Extract<ValueHandoffEvent, { readonly kind: 'destination-ready' }>,
2325
+ ): ValueHandoffTransition {
2326
+ const handoff = state.handoff
2327
+ if (
2328
+ handoff.direction !== event.direction ||
2329
+ handoff.transferLease !== event.transferLease ||
2330
+ handoff.ownership !== frozenOwnershipFor(event.direction)
2331
+ ) {
2332
+ return ignoredTransition(state)
2333
+ }
2334
+ const next = withHandoff(state, {
2335
+ ...handoff,
2336
+ ownership: preparedOwnershipFor(event.direction),
2337
+ preparedMutableTuple: event.preparedMutableTuple,
2338
+ releaseFenceSequence: null,
2339
+ })
2340
+ return appliedTransition(next, [{ kind: 'request-source-release-status' }])
2341
+ }
2342
+
2343
+ function sourceMutation(
2344
+ state: DriverValueChannelState,
2345
+ event: Extract<ValueHandoffEvent, { readonly kind: 'source-mutation' }>,
2346
+ ): ValueHandoffTransition {
2347
+ const handoff = state.handoff
2348
+ const entry = event.entry
2349
+ switch (handoff.ownership) {
2350
+ case 'UI_FROZEN_TO_JS':
2351
+ case 'JS_FROZEN_TO_UI':
2352
+ case 'JS_PREPARED':
2353
+ case 'UI_PREPARED': {
2354
+ const direction = handoff.direction
2355
+ if (direction === null) return ignoredTransition(state)
2356
+ // Pre-fence: the prepared clone is invalidated and the authoritative source resumes.
2357
+ const next = withHandoff(state, {
2358
+ ...handoff,
2359
+ ownership: sourceRunningOwnershipFor(direction),
2360
+ releaseFenceSequence: null,
2361
+ preparedMutableTuple: null,
2362
+ commitQueue: [],
2363
+ })
2364
+ return appliedTransition(next, [
2365
+ { kind: 'invalidate-prepared-clone' },
2366
+ { kind: 'resume-authoritative-source', side: sourceSideFor(direction) },
2367
+ ])
2368
+ }
2369
+ case 'UI_RELEASE_PENDING_TO_JS':
2370
+ case 'JS_RELEASE_PENDING_TO_UI': {
2371
+ // The source RELEASE/ABORT status is still unknown: sequence, never infer.
2372
+ const next = withHandoff(state, {
2373
+ ...handoff,
2374
+ sourceMutationQueue: [...handoff.sourceMutationQueue, entry],
2375
+ })
2376
+ return appliedTransition(next, [])
2377
+ }
2378
+ case 'UI_RELEASED_TO_JS':
2379
+ case 'JS_RELEASED_TO_UI': {
2380
+ // Post-fence: the fence and lease are immutable; the mutation joins the COMMIT prefix.
2381
+ const next = withHandoff(state, {
2382
+ ...handoff,
2383
+ sentThroughAuthoritativeSequence: entry.authoritativeSequence,
2384
+ commitQueue: appendEntryToCommitQueue(state, entry),
2385
+ })
2386
+ return appliedTransition(next, [])
2387
+ }
2388
+ default:
2389
+ return ignoredTransition(state)
2390
+ }
2391
+ }
2392
+
2393
+ function sourceStatus(
2394
+ state: DriverValueChannelState,
2395
+ event: Extract<ValueHandoffEvent, { readonly kind: 'source-status' }>,
2396
+ ): ValueHandoffTransition {
2397
+ const refusal = validateTransferIdentityTuple(state, event.status)
2398
+ if (refusal !== null) {
2399
+ return {
2400
+ state,
2401
+ observation: observeDriverValueChannel(state),
2402
+ orderedEffects: [],
2403
+ outcome: { kind: 'SOURCE_STATUS_REFUSED', reason: refusal },
2404
+ }
2405
+ }
2406
+ const handoff = state.handoff
2407
+ const status = event.status
2408
+ const direction = status.direction
2409
+ if (handoff.ownership !== releasePendingOwnershipFor(direction)) {
2410
+ return ignoredTransition(state)
2411
+ }
2412
+ if (status.kind === 'ABORTED') {
2413
+ const next = withHandoff(state, {
2414
+ ...handoff,
2415
+ ownership: sourceRunningOwnershipFor(direction),
2416
+ releaseFenceSequence: null,
2417
+ preparedMutableTuple: null,
2418
+ commitQueue: [],
2419
+ })
2420
+ return appliedTransition(next, [
2421
+ { kind: 'invalidate-prepared-clone' },
2422
+ { kind: 'resume-authoritative-source', side: sourceSideFor(direction) },
2423
+ ])
2424
+ }
2425
+ // Source-confirmed RELEASE is the handoff linearization point: mutations queued behind the
2426
+ // source decision drain into the one immutable COMMIT, which is enqueued and activates the
2427
+ // declared destination turn without permitting the destination to run on its own yet.
2428
+ const drainedPrefix = handoff.sourceMutationQueue
2429
+ const sentThrough =
2430
+ drainedPrefix.at(-1)?.authoritativeSequence ?? handoff.sentThroughAuthoritativeSequence
2431
+ const commit: ImmutableDestinationCommit = {
2432
+ kind: 'DESTINATION_COMMIT',
2433
+ channelId: status.channelId,
2434
+ sourceRecordId: status.sourceRecordId,
2435
+ destinationRecordId: status.destinationRecordId,
2436
+ direction,
2437
+ transferLease: status.transferLease,
2438
+ releaseFenceSequence: status.releaseFenceSequence,
2439
+ sentThroughAuthoritativeSequence: sentThrough,
2440
+ includedPostFencePrefix: drainedPrefix,
2441
+ }
2442
+ const activation = destinationActivationEffect(handoff.destinationRuntime)
2443
+ const next = withHandoff(state, {
2444
+ ...handoff,
2445
+ ownership: releasedOwnershipFor(direction),
2446
+ releaseFenceSequence: status.releaseFenceSequence,
2447
+ sentThroughAuthoritativeSequence: sentThrough,
2448
+ sourceMutationQueue: [],
2449
+ commitQueue: [commit],
2450
+ })
2451
+ return appliedTransition(
2452
+ next,
2453
+ activation === null
2454
+ ? [{ kind: 'enqueue-immutable-destination-commit' }]
2455
+ : [{ kind: 'enqueue-immutable-destination-commit' }, activation],
2456
+ )
2457
+ }
2458
+
2459
+ function enqueueDestinationCommit(
2460
+ state: DriverValueChannelState,
2461
+ event: Extract<ValueHandoffEvent, { readonly kind: 'enqueue-destination-commit' }>,
2462
+ ): ValueHandoffTransition {
2463
+ const handoff = state.handoff
2464
+ const activation = destinationActivationEffect(handoff.destinationRuntime)
2465
+ const next = withHandoff(state, {
2466
+ ...handoff,
2467
+ commitQueue: [...handoff.commitQueue, event.commit],
2468
+ })
2469
+ return appliedTransition(
2470
+ next,
2471
+ activation === null
2472
+ ? [{ kind: 'enqueue-immutable-destination-commit' }]
2473
+ : [{ kind: 'enqueue-immutable-destination-commit' }, activation],
2474
+ )
2475
+ }
2476
+
2477
+ function destinationCommitAcknowledgement(
2478
+ state: DriverValueChannelState,
2479
+ event: Extract<ValueHandoffEvent, { readonly kind: 'destination-commit-acknowledgement' }>,
2480
+ ): ValueHandoffTransition {
2481
+ const validation = validateDestinationCommitAcknowledgement(state, event.acknowledgement)
2482
+ if (validation.kind === 'rejected' || validation.extent === 'duplicate') {
2483
+ // Malformed candidates and completed-prefix duplicates are inert: cleanup never repeats.
2484
+ return ignoredTransition(state)
2485
+ }
2486
+ const handoff = state.handoff
2487
+ const acknowledgement = event.acknowledgement
2488
+ if (validation.extent === 'partial') {
2489
+ const current = handoff.commitQueue.at(-1)
2490
+ if (current === undefined) return ignoredTransition(state)
2491
+ // A partial acknowledgement rebuilds and re-enqueues the same immutable COMMIT shape with
2492
+ // only the remaining ordered suffix under the same lease, fence, and activation rule.
2493
+ const suffix = current.includedPostFencePrefix.filter(
2494
+ (entry) => entry.authoritativeSequence > acknowledgement.drainedThroughAuthoritativeSequence,
2495
+ )
2496
+ const firstSuffix = suffix[0]
2497
+ const effects: ValueHandoffEffect[] = [
2498
+ {
2499
+ kind: 'rebuild-same-commit-with-suffix',
2500
+ suffixStartSequence:
2501
+ firstSuffix?.authoritativeSequence ?? acknowledgement.drainedThroughAuthoritativeSequence,
2502
+ },
2503
+ ]
2504
+ const activation = destinationActivationEffect(handoff.destinationRuntime)
2505
+ if (activation !== null) effects.push(activation)
2506
+ const next = withHandoff(state, {
2507
+ ...handoff,
2508
+ drainedThroughAuthoritativeSequence: acknowledgement.drainedThroughAuthoritativeSequence,
2509
+ commitQueue: [
2510
+ ...handoff.commitQueue.slice(0, -1),
2511
+ { ...current, includedPostFencePrefix: suffix },
2512
+ ],
2513
+ })
2514
+ return appliedTransition(next, effects)
2515
+ }
2516
+ // Acknowledged through the current tail: cancel the timer, release every retained protocol
2517
+ // resource exactly once, then permit the destination to run or reverse.
2518
+ const next = withHandoff(state, {
2519
+ ...handoff,
2520
+ ownership: destinationRunningOwnershipFor(acknowledgement.direction),
2521
+ drainedThroughAuthoritativeSequence: acknowledgement.drainedThroughAuthoritativeSequence,
2522
+ commitQueue: [],
2523
+ resources: releasedResourceState(),
2524
+ })
2525
+ return appliedTransition(next, [
2526
+ { kind: 'cancel-transfer-timer' },
2527
+ { kind: 'release-prepared-state' },
2528
+ { kind: 'release-prepared-clone' },
2529
+ { kind: 'release-transfer-activity' },
2530
+ { kind: 'permit-destination-run-or-reverse' },
2531
+ ])
2532
+ }
2533
+
2534
+ function phaseTimeout(
2535
+ state: DriverValueChannelState,
2536
+ event: Extract<ValueHandoffEvent, { readonly kind: 'phase-timeout' }>,
2537
+ ): ValueHandoffTransition {
2538
+ const handoff = state.handoff
2539
+ if (
2540
+ handoff.direction !== event.direction ||
2541
+ handoff.transferLease !== event.transferLease ||
2542
+ handoff.phaseToken !== event.phaseToken
2543
+ ) {
2544
+ // A timeout from a retired lease is stale; the successor lease is never touched.
2545
+ return ignoredTransition(state)
2546
+ }
2547
+ const direction = event.direction
2548
+ switch (handoff.ownership) {
2549
+ case 'UI_FROZEN_TO_JS':
2550
+ case 'JS_FROZEN_TO_UI':
2551
+ case 'JS_PREPARED':
2552
+ case 'UI_PREPARED': {
2553
+ const next = withHandoff(state, {
2554
+ ...handoff,
2555
+ ownership: sourceRunningOwnershipFor(direction),
2556
+ releaseFenceSequence: null,
2557
+ preparedMutableTuple: null,
2558
+ commitQueue: [],
2559
+ })
2560
+ return appliedTransition(next, [
2561
+ { kind: 'invalidate-prepared-clone' },
2562
+ { kind: 'resume-authoritative-source', side: sourceSideFor(direction) },
2563
+ ])
2564
+ }
2565
+ case 'UI_RELEASE_PENDING_TO_JS':
2566
+ case 'JS_RELEASE_PENDING_TO_UI':
2567
+ // The source status is still unknown: retransmit only the query, never guess ownership.
2568
+ return appliedTransition(state, [{ kind: 'request-source-release-status' }])
2569
+ case 'UI_RELEASED_TO_JS':
2570
+ case 'JS_RELEASED_TO_UI': {
2571
+ const activation = destinationActivationEffect(handoff.destinationRuntime)
2572
+ return appliedTransition(
2573
+ state,
2574
+ activation === null
2575
+ ? [{ kind: 'enqueue-immutable-destination-commit' }]
2576
+ : [{ kind: 'enqueue-immutable-destination-commit' }, activation],
2577
+ )
2578
+ }
2579
+ default:
2580
+ return ignoredTransition(state)
2581
+ }
2582
+ }
2583
+
2584
+ function boundedTerminal(
2585
+ state: DriverValueChannelState,
2586
+ event: Extract<ValueHandoffEvent, { readonly kind: 'bounded-terminal' }>,
2587
+ ): ValueHandoffTransition {
2588
+ const handoff = state.handoff
2589
+ if (
2590
+ handoff.direction !== event.direction ||
2591
+ handoff.transferLease !== event.transferLease ||
2592
+ handoff.phaseToken !== event.phaseToken ||
2593
+ handoff.ownership !== releasedOwnershipFor(event.direction)
2594
+ ) {
2595
+ return ignoredTransition(state)
2596
+ }
2597
+ // Expiry after confirmed release enters the bounded-refusal drain with every protocol resource
2598
+ // still retained; cleanup waits for the retained pin-visible work to finish.
2599
+ const next = withHandoff(state, { ...handoff, ownership: 'BOUNDED_REFUSAL_DRAINING' })
2600
+ return appliedTransition(next, [
2601
+ { kind: 'enter-change-host-silent-drain' },
2602
+ { kind: 'refuse-live-channel-property' },
2603
+ ])
2604
+ }
2605
+
2606
+ function finishBoundedRefusalDrain(
2607
+ state: DriverValueChannelState,
2608
+ event: Extract<ValueHandoffEvent, { readonly kind: 'finish-bounded-refusal-drain' }>,
2609
+ ): ValueHandoffTransition {
2610
+ const handoff = state.handoff
2611
+ if (
2612
+ handoff.direction !== event.direction ||
2613
+ handoff.transferLease !== event.transferLease ||
2614
+ handoff.ownership !== 'BOUNDED_REFUSAL_DRAINING'
2615
+ ) {
2616
+ return ignoredTransition(state)
2617
+ }
2618
+ const next = withHandoff(state, {
2619
+ ...handoff,
2620
+ ownership: 'TERMINAL_REFUSED',
2621
+ resources: releasedResourceState(),
2622
+ })
2623
+ return appliedTransition(next, [
2624
+ { kind: 'cancel-transfer-timer' },
2625
+ { kind: 'release-prepared-state' },
2626
+ { kind: 'release-prepared-clone' },
2627
+ { kind: 'release-transfer-activity' },
2628
+ { kind: 'finalize-bounded-refusal-without-run-or-reverse' },
2629
+ ])
2630
+ }
2631
+
2632
+ export function advanceValueHandoff(
2633
+ state: DriverValueChannelState,
2634
+ event: ValueHandoffEvent,
2635
+ now: number,
2636
+ ): ValueHandoffTransition {
2637
+ void now
2638
+ switch (event.kind) {
2639
+ case 'begin-handoff':
2640
+ return beginHandoff(state, event)
2641
+ case 'destination-ready':
2642
+ return destinationReady(state, event)
2643
+ case 'source-mutation':
2644
+ return sourceMutation(state, event)
2645
+ case 'source-status':
2646
+ return sourceStatus(state, event)
2647
+ case 'enqueue-destination-commit':
2648
+ return enqueueDestinationCommit(state, event)
2649
+ case 'destination-commit-acknowledgement':
2650
+ return destinationCommitAcknowledgement(state, event)
2651
+ case 'phase-timeout':
2652
+ return phaseTimeout(state, event)
2653
+ case 'bounded-terminal':
2654
+ return boundedTerminal(state, event)
2655
+ case 'finish-bounded-refusal-drain':
2656
+ return finishBoundedRefusalDrain(state, event)
2657
+ default: {
2658
+ // An untyped/JS caller can reach this with a kind outside the union: fail closed with the
2659
+ // typed invariant (never return the event as a transition — that would corrupt state).
2660
+ const exhaustive: never = event
2661
+ void exhaustive
2662
+ throw new DriverValueChannelInvariantError('handoff-event-kind-unrecognized')
2663
+ }
2664
+ }
2665
+ }
2666
+
2667
+ export function advanceValueHandoffPhaseEvent(
2668
+ state: DriverValueChannelState,
2669
+ envelope: ValueHandoffPhaseEventEnvelope,
2670
+ now: number,
2671
+ ): ValueHandoffPhaseEventTransition {
2672
+ void now
2673
+ const { phase, event } = envelope
2674
+ requireValueHandoffPhase(phase)
2675
+ requirePhaseEventKind(event.kind)
2676
+ if (isProtocolReadEvent(event)) {
2677
+ return {
2678
+ state,
2679
+ observation: observeDriverValueChannel(state),
2680
+ disposition: {
2681
+ kind: 'SYNCHRONOUS_READ_WITHOUT_PROTOCOL_MUTATION',
2682
+ phase,
2683
+ eventKind: event.kind,
2684
+ view: readViewForPhase(phase),
2685
+ history: state.identity.history,
2686
+ },
2687
+ orderedEffects: [],
2688
+ }
2689
+ }
2690
+ if (phase === 'RELEASED_POST_FENCE') {
2691
+ // Post-fence: the fence is immutable; the authored suffix rides the same COMMIT shape.
2692
+ const current = state.handoff.commitQueue.at(-1)
2693
+ const commit: ImmutableDestinationCommit = {
2694
+ kind: 'DESTINATION_COMMIT',
2695
+ channelId: state.identity.channelId,
2696
+ sourceRecordId: state.handoff.sourceRecordId ?? -1,
2697
+ destinationRecordId: state.handoff.destinationRecordId ?? -1,
2698
+ direction: envelope.direction,
2699
+ transferLease: envelope.transferLease,
2700
+ releaseFenceSequence:
2701
+ state.handoff.releaseFenceSequence ?? current?.releaseFenceSequence ?? -1,
2702
+ sentThroughAuthoritativeSequence: event.authoritativeSequence,
2703
+ includedPostFencePrefix: current?.includedPostFencePrefix ?? [],
2704
+ }
2705
+ const next = withHandoff(state, {
2706
+ ...state.handoff,
2707
+ sentThroughAuthoritativeSequence: event.authoritativeSequence,
2708
+ })
2709
+ return {
2710
+ state: next,
2711
+ observation: observeDriverValueChannel(next),
2712
+ disposition: {
2713
+ kind: 'APPEND_IMMUTABLE_COMMIT_SUFFIX',
2714
+ phase,
2715
+ eventKind: event.kind,
2716
+ commit,
2717
+ authoredSuffix: [event],
2718
+ },
2719
+ orderedEffects: [],
2720
+ }
2721
+ }
2722
+ const direction = envelope.direction
2723
+ const status: ValueHandoffSourceStatus = {
2724
+ kind: 'ABORTED',
2725
+ channelId: state.identity.channelId,
2726
+ sourceRecordId: state.handoff.sourceRecordId ?? -1,
2727
+ destinationRecordId: state.handoff.destinationRecordId ?? -1,
2728
+ direction,
2729
+ transferLease: envelope.transferLease,
2730
+ authoritativeSequence: event.authoritativeSequence,
2731
+ }
2732
+ const next = withHandoff(state, {
2733
+ ...state.handoff,
2734
+ ownership: frozenOwnershipFor(direction),
2735
+ releaseFenceSequence: null,
2736
+ preparedMutableTuple: null,
2737
+ commitQueue: [],
2738
+ })
2739
+ return {
2740
+ state: next,
2741
+ observation: observeDriverValueChannel(next),
2742
+ disposition: { kind: 'ABORT_AND_REPREPARE', phase, eventKind: event.kind, status },
2743
+ orderedEffects: [
2744
+ { kind: 'invalidate-prepared-clone' },
2745
+ { kind: 'resume-authoritative-source', side: sourceSideFor(direction) },
2746
+ { kind: 'send-prepare', direction },
2747
+ ],
2748
+ }
2749
+ }
2750
+
2751
+ function applyProtocolCheckpointMutation(
2752
+ state: DriverValueChannelState,
2753
+ envelope: ValueHandoffProtocolCheckpointEnvelope,
2754
+ event: ValueHandoffProtocolMutationEvent,
2755
+ ): {
2756
+ readonly state: DriverValueChannelState
2757
+ readonly orderedEffects: readonly ValueHandoffEffect[]
2758
+ } {
2759
+ const handoff = state.handoff
2760
+ const direction = envelope.direction
2761
+ switch (envelope.checkpoint) {
2762
+ case 'FREEZE':
2763
+ return { state, orderedEffects: [{ kind: 'send-prepare', direction }] }
2764
+ case 'PREPARE_READY': {
2765
+ const next = withHandoff(state, {
2766
+ ...handoff,
2767
+ ownership: frozenOwnershipFor(direction),
2768
+ releaseFenceSequence: null,
2769
+ preparedMutableTuple: null,
2770
+ commitQueue: [],
2771
+ })
2772
+ return {
2773
+ state: next,
2774
+ orderedEffects: [
2775
+ { kind: 'invalidate-prepared-clone' },
2776
+ { kind: 'send-prepare', direction },
2777
+ ],
2778
+ }
2779
+ }
2780
+ case 'RELEASE_PENDING_SOURCE_UNKNOWN': {
2781
+ const next = withHandoff(state, {
2782
+ ...handoff,
2783
+ sourceMutationQueue: [...handoff.sourceMutationQueue, event.retainedWorkEntry],
2784
+ })
2785
+ return { state: next, orderedEffects: [] }
2786
+ }
2787
+ case 'SOURCE_CONFIRMED_ABORTED':
2788
+ return {
2789
+ state: {
2790
+ identity: applyProtocolMutationToIdentity(state.identity, event),
2791
+ handoff,
2792
+ },
2793
+ orderedEffects: [{ kind: 'send-prepare', direction }],
2794
+ }
2795
+ case 'SOURCE_CONFIRMED_RELEASED':
2796
+ case 'DESTINATION_COMMIT_CONSUMPTION': {
2797
+ const next = {
2798
+ identity: applyProtocolMutationToIdentity(state.identity, event),
2799
+ handoff: {
2800
+ ...handoff,
2801
+ sentThroughAuthoritativeSequence: event.retainedWorkEntry.authoritativeSequence,
2802
+ commitQueue: appendEntryToCommitQueue(state, event.retainedWorkEntry),
2803
+ },
2804
+ }
2805
+ return { state: next, orderedEffects: [] }
2806
+ }
2807
+ case 'DESTINATION_ACKNOWLEDGED':
2808
+ return {
2809
+ state: {
2810
+ identity: applyProtocolMutationToIdentity(state.identity, event),
2811
+ handoff,
2812
+ },
2813
+ orderedEffects: [],
2814
+ }
2815
+ default: {
2816
+ const exhaustive: never = envelope.checkpoint
2817
+ return exhaustive
2818
+ }
2819
+ }
2820
+ }
2821
+
2822
+ export function advanceValueHandoffProtocolCheckpoint(
2823
+ state: DriverValueChannelState,
2824
+ envelope: ValueHandoffProtocolCheckpointEnvelope,
2825
+ now: number,
2826
+ ): ValueHandoffProtocolCheckpointTransition {
2827
+ void now
2828
+ const { checkpoint, event } = envelope
2829
+ requireProtocolCheckpoint(checkpoint)
2830
+ requireProtocolEventKind(event.kind)
2831
+ if (isProtocolReadEvent(event)) {
2832
+ return {
2833
+ state,
2834
+ observation: observeDriverValueChannel(state),
2835
+ disposition: {
2836
+ kind: 'SYNCHRONOUS_READ_WITHOUT_PROTOCOL_MUTATION',
2837
+ checkpoint,
2838
+ eventKind: event.kind,
2839
+ authority: readAuthorityForCheckpoint(checkpoint),
2840
+ history: state.identity.history,
2841
+ },
2842
+ orderedEffects: [],
2843
+ }
2844
+ }
2845
+ const mutation = applyProtocolCheckpointMutation(state, envelope, event)
2846
+ return {
2847
+ state: mutation.state,
2848
+ observation: observeDriverValueChannel(mutation.state),
2849
+ disposition: {
2850
+ kind: 'MUTATION',
2851
+ checkpoint,
2852
+ eventKind: event.kind,
2853
+ operation: event,
2854
+ handling: mutationHandlingForCheckpoint(checkpoint),
2855
+ retainedWorkDisposition: retainedWorkDispositionFor(checkpoint, event),
2856
+ absoluteTransferDeadlineMs: state.handoff.absoluteTransferDeadlineMs ?? -1,
2857
+ absoluteNoConsumerDeadlineMs: state.handoff.absoluteNoConsumerDeadlineMs ?? -1,
2858
+ resources: state.handoff.resources,
2859
+ },
2860
+ orderedEffects: mutation.orderedEffects,
2861
+ }
2862
+ }
2863
+
2864
+ function refuseJsDestinationCommit(
2865
+ state: JsDestinationCommitConsumeState,
2866
+ request: JsDestinationCommitConsumeRequest,
2867
+ reason: JsDestinationCommitRefusalReason,
2868
+ priorEffects: readonly JsDestinationCommitEffect[],
2869
+ envelope: JsDestinationCommitEnvelopeState | null,
2870
+ ): JsDestinationCommitTransition {
2871
+ if (envelope === null) {
2872
+ // The envelope never began: refuse without begin/seal or any destination mutation.
2873
+ return {
2874
+ state,
2875
+ outcome: { kind: 'REFUSED', commitSlotId: request.commitSlotId, reason },
2876
+ orderedEffects: [
2877
+ {
2878
+ kind: 'refuse-malformed-js-destination-commit',
2879
+ commitSlotId: request.commitSlotId,
2880
+ reason,
2881
+ },
2882
+ ],
2883
+ }
2884
+ }
2885
+ const sealedEnvelope: JsDestinationCommitEnvelopeState = {
2886
+ ...envelope,
2887
+ state: 'SEALED',
2888
+ sealCount: envelope.sealCount + 1,
2889
+ }
2890
+ return {
2891
+ state: {
2892
+ preparedDestinationRecords: state.preparedDestinationRecords,
2893
+ acknowledgementSlots: state.acknowledgementSlots,
2894
+ envelope: sealedEnvelope,
2895
+ },
2896
+ outcome: { kind: 'REFUSED', commitSlotId: request.commitSlotId, reason },
2897
+ orderedEffects: [
2898
+ ...priorEffects,
2899
+ {
2900
+ kind: 'refuse-malformed-js-destination-commit',
2901
+ commitSlotId: request.commitSlotId,
2902
+ reason,
2903
+ },
2904
+ {
2905
+ kind: 'seal-js-destination-envelope-once',
2906
+ mailboxDrainId: sealedEnvelope.mailboxDrainId,
2907
+ entryCount: 0,
2908
+ },
2909
+ ],
2910
+ }
2911
+ }
2912
+
2913
+ function validateJsCommitAgainstRecord(
2914
+ record: JsPreparedDestinationRecordState,
2915
+ commit: ImmutableDestinationCommit,
2916
+ ): JsDestinationCommitRefusalReason | null {
2917
+ if (commit.channelId !== record.channelId) return 'wrong-channel'
2918
+ if (commit.sourceRecordId !== record.sourceRecordId) return 'wrong-source-record'
2919
+ if (commit.destinationRecordId !== record.destinationRecordId) return 'wrong-destination-record'
2920
+ if (commit.direction !== record.direction) return 'wrong-direction'
2921
+ if (commit.transferLease !== record.transferLease) return 'wrong-transfer-lease'
2922
+ if (commit.releaseFenceSequence !== record.releaseFenceSequence) return 'wrong-release-fence'
2923
+ if (
2924
+ commit.includedPostFencePrefix.some(
2925
+ (entry) => entry.authoritativeSequence > commit.sentThroughAuthoritativeSequence,
2926
+ )
2927
+ ) {
2928
+ return 'ahead-of-sent-watermark'
2929
+ }
2930
+ if (commit.sentThroughAuthoritativeSequence < record.adoptedSentThroughAuthoritativeSequence) {
2931
+ return 'sent-watermark-regressed'
2932
+ }
2933
+ const newEntries = commit.includedPostFencePrefix.filter(
2934
+ (entry) => entry.authoritativeSequence > record.appliedThroughAuthoritativeSequence,
2935
+ )
2936
+ for (let index = 0; index < newEntries.length; index += 1) {
2937
+ const entry = newEntries[index]
2938
+ if (entry === undefined) break
2939
+ if (entry.authoritativeSequence !== record.appliedThroughAuthoritativeSequence + index + 1) {
2940
+ return 'prefix-not-contiguous'
2941
+ }
2942
+ }
2943
+ return null
2944
+ }
2945
+
2946
+ export function consumeJsDestinationCommit(
2947
+ state: JsDestinationCommitConsumeState,
2948
+ request: JsDestinationCommitConsumeRequest,
2949
+ ): JsDestinationCommitTransition {
2950
+ const envelope = state.envelope
2951
+ if (envelope.root !== request.root) {
2952
+ return refuseJsDestinationCommit(state, request, 'root-mismatch', [], null)
2953
+ }
2954
+ if (envelope.state !== 'NOT_BEGUN') {
2955
+ return refuseJsDestinationCommit(state, request, 'envelope-already-begun', [], null)
2956
+ }
2957
+ const commit = request.commit
2958
+ const openEnvelope: JsDestinationCommitEnvelopeState = {
2959
+ ...envelope,
2960
+ state: 'OPEN',
2961
+ beginCount: envelope.beginCount + 1,
2962
+ }
2963
+ const effects: JsDestinationCommitEffect[] = [
2964
+ { kind: 'begin-js-destination-envelope', root: request.root },
2965
+ {
2966
+ kind: 'lookup-js-prepared-destination-record',
2967
+ destinationRecordId: commit.destinationRecordId,
2968
+ },
2969
+ ]
2970
+ // The runtime keys its prepared records by its own destination record id; a commit naming an
2971
+ // unknown destination id for a known channel still resolves the channel's authority so the
2972
+ // validator can diagnose the exact tuple field rather than report a missing record.
2973
+ const record =
2974
+ state.preparedDestinationRecords.find(
2975
+ (candidate) => candidate.destinationRecordId === commit.destinationRecordId,
2976
+ ) ??
2977
+ state.preparedDestinationRecords.find((candidate) => candidate.channelId === commit.channelId)
2978
+ if (record === undefined) {
2979
+ return refuseJsDestinationCommit(
2980
+ state,
2981
+ request,
2982
+ 'missing-prepared-destination-record',
2983
+ effects,
2984
+ openEnvelope,
2985
+ )
2986
+ }
2987
+ effects.push({
2988
+ kind: 'validate-js-immutable-destination-commit',
2989
+ commitSlotId: request.commitSlotId,
2990
+ })
2991
+ const refusal = validateJsCommitAgainstRecord(record, commit)
2992
+ if (refusal !== null) {
2993
+ return refuseJsDestinationCommit(state, request, refusal, effects, openEnvelope)
2994
+ }
2995
+ const acknowledgementSlot = state.acknowledgementSlots.find(
2996
+ (candidate) => candidate.acknowledgementSlotId === request.acknowledgementSlotId,
2997
+ )
2998
+ if (acknowledgementSlot === undefined) {
2999
+ return refuseJsDestinationCommit(
3000
+ state,
3001
+ request,
3002
+ 'missing-acknowledgement-slot',
3003
+ effects,
3004
+ openEnvelope,
3005
+ )
3006
+ }
3007
+
3008
+ // Validation is complete before any entry applies: only the entries beyond the applied
3009
+ // watermark are new; a fully-applied candidate dedupes without a second application.
3010
+ const newEntries = commit.includedPostFencePrefix.filter(
3011
+ (entry) => entry.authoritativeSequence > record.appliedThroughAuthoritativeSequence,
3012
+ )
3013
+ const appliedThrough =
3014
+ newEntries.at(-1)?.authoritativeSequence ?? record.appliedThroughAuthoritativeSequence
3015
+ const acknowledgement: DestinationCommitAcknowledgement = {
3016
+ kind: 'DESTINATION_COMMIT_ACKNOWLEDGEMENT',
3017
+ channelId: commit.channelId,
3018
+ sourceRecordId: commit.sourceRecordId,
3019
+ destinationRecordId: commit.destinationRecordId,
3020
+ direction: commit.direction,
3021
+ transferLease: commit.transferLease,
3022
+ releaseFenceSequence: commit.releaseFenceSequence,
3023
+ drainedThroughAuthoritativeSequence: appliedThrough,
3024
+ }
3025
+ const duplicate = newEntries.length === 0
3026
+ let nextRecord = record
3027
+ if (duplicate) {
3028
+ effects.push({
3029
+ kind: 'dedupe-js-destination-commit-application',
3030
+ commitSlotId: request.commitSlotId,
3031
+ drainedThroughAuthoritativeSequence: record.appliedThroughAuthoritativeSequence,
3032
+ })
3033
+ } else {
3034
+ effects.push({
3035
+ kind: 'adopt-js-immutable-destination-commit',
3036
+ commitSlotId: request.commitSlotId,
3037
+ })
3038
+ for (const entry of newEntries) {
3039
+ effects.push({
3040
+ kind: 'apply-js-destination-ledger-entry-in-place',
3041
+ commitSlotId: request.commitSlotId,
3042
+ prefixIndex: entry.authoritativeSequence - commit.releaseFenceSequence - 1,
3043
+ destinationSlot: entry.destinationSlot,
3044
+ entry,
3045
+ })
3046
+ }
3047
+ nextRecord = {
3048
+ ...record,
3049
+ adoptedCommitSlotId: request.commitSlotId,
3050
+ adoptedSentThroughAuthoritativeSequence: commit.sentThroughAuthoritativeSequence,
3051
+ appliedThroughAuthoritativeSequence: appliedThrough,
3052
+ ledgers: applyEntriesToLedgers(record.ledgers, newEntries),
3053
+ }
3054
+ }
3055
+ effects.push({
3056
+ kind: 'fill-js-destination-acknowledgement-slot',
3057
+ commitSlotId: request.commitSlotId,
3058
+ acknowledgementSlotId: request.acknowledgementSlotId,
3059
+ })
3060
+ effects.push({
3061
+ kind: 'stage-js-destination-acknowledgement-in-current-envelope',
3062
+ acknowledgementSlotId: request.acknowledgementSlotId,
3063
+ mailboxDrainId: openEnvelope.mailboxDrainId,
3064
+ })
3065
+ const entries: JsDestinationCommitEnvelopeEntry[] = [
3066
+ {
3067
+ kind: 'DESTINATION_COMMIT_ACKNOWLEDGEMENT',
3068
+ acknowledgementSlotId: request.acknowledgementSlotId,
3069
+ acknowledgement,
3070
+ },
3071
+ ]
3072
+ for (const dueWorkSequence of request.siblingDueWorkSequences) {
3073
+ effects.push({ kind: 'run-js-root-sibling-due-work', dueWorkSequence })
3074
+ entries.push({ kind: 'ROOT_SIBLING_DUE_WORK', dueWorkSequence })
3075
+ }
3076
+ const sealedEnvelope: JsDestinationCommitEnvelopeState = {
3077
+ ...openEnvelope,
3078
+ state: 'SEALED',
3079
+ entries,
3080
+ sealCount: openEnvelope.sealCount + 1,
3081
+ }
3082
+ effects.push({
3083
+ kind: 'seal-js-destination-envelope-once',
3084
+ mailboxDrainId: sealedEnvelope.mailboxDrainId,
3085
+ entryCount: entries.length,
3086
+ })
3087
+ const filledSlot: JsDestinationCommitAcknowledgementSlotState = {
3088
+ ...acknowledgementSlot,
3089
+ state: 'STAGED_IN_CURRENT_ENVELOPE',
3090
+ acknowledgement,
3091
+ }
3092
+ return {
3093
+ state: {
3094
+ preparedDestinationRecords: duplicate
3095
+ ? state.preparedDestinationRecords
3096
+ : state.preparedDestinationRecords.map((candidate) =>
3097
+ candidate === record ? nextRecord : candidate,
3098
+ ),
3099
+ acknowledgementSlots: state.acknowledgementSlots.map((candidate) =>
3100
+ candidate === acknowledgementSlot ? filledSlot : candidate,
3101
+ ),
3102
+ envelope: sealedEnvelope,
3103
+ },
3104
+ outcome: duplicate
3105
+ ? { kind: 'DUPLICATE_REACKNOWLEDGED', commitSlotId: request.commitSlotId, acknowledgement }
3106
+ : { kind: 'APPLIED', commitSlotId: request.commitSlotId, acknowledgement },
3107
+ orderedEffects: effects,
3108
+ }
3109
+ }
3110
+
3111
+ export function drainPostReleaseCommitLoss(
3112
+ state: ValueHandoffCommitLossDrainState,
3113
+ now: number,
3114
+ ): ValueHandoffCommitLossDrainTransition {
3115
+ void now
3116
+ // The bounded drain is change/host-silent: every retained public obligation still runs in
3117
+ // packet order; retired listeners, retired sinks, and host publication stay silent.
3118
+ const orderedEffects: ValueHandoffCommitLossDrainEffect[] = []
3119
+ const drainedWorkSequences: number[] = []
3120
+ let dependencyAcknowledgementCount = 0
3121
+ let settleReleaseCount = 0
3122
+ for (const work of state.retainedWork) {
3123
+ drainedWorkSequences.push(work.workSequence)
3124
+ switch (work.kind) {
3125
+ case 'cache-adoption':
3126
+ orderedEffects.push({
3127
+ kind: 'adopt-bounded-destination-cache',
3128
+ workSequence: work.workSequence,
3129
+ authoritativeSequence: work.authoritativeSequence,
3130
+ value: work.value,
3131
+ })
3132
+ break
3133
+ case 'transition-on-update':
3134
+ orderedEffects.push({
3135
+ kind: 'deliver-bounded-transition-on-update',
3136
+ workSequence: work.workSequence,
3137
+ callbackId: work.callbackId,
3138
+ value: work.value,
3139
+ })
3140
+ break
3141
+ case 'transition-on-complete':
3142
+ orderedEffects.push({
3143
+ kind: 'deliver-bounded-transition-on-complete',
3144
+ workSequence: work.workSequence,
3145
+ callbackId: work.callbackId,
3146
+ })
3147
+ break
3148
+ case 'current-animation-complete':
3149
+ orderedEffects.push({
3150
+ kind: 'deliver-current-animation-complete',
3151
+ workSequence: work.workSequence,
3152
+ listenerId: work.listenerId,
3153
+ bindingGeneration: work.bindingGeneration,
3154
+ })
3155
+ break
3156
+ case 'token-blind-public-slot-clear':
3157
+ orderedEffects.push({
3158
+ kind: 'clear-public-control-slot-token-blind',
3159
+ workSequence: work.workSequence,
3160
+ initiallyInstalledControlId: work.initiallyInstalledControlId,
3161
+ installedControlIdAtClear: work.installedControlIdAtClear,
3162
+ })
3163
+ break
3164
+ case 'public-start-promise-resolution':
3165
+ orderedEffects.push({
3166
+ kind: 'resolve-public-start-promise',
3167
+ workSequence: work.workSequence,
3168
+ promiseId: work.promiseId,
3169
+ })
3170
+ break
3171
+ case 'target-batch-dependency-delivery':
3172
+ orderedEffects.push({
3173
+ kind: 'deliver-target-batch-dependency-event',
3174
+ workSequence: work.workSequence,
3175
+ batchId: work.batchId,
3176
+ dependencyId: work.dependencyId,
3177
+ eventSequence: work.eventSequence,
3178
+ })
3179
+ break
3180
+ case 'target-batch-dependency-acknowledgement':
3181
+ dependencyAcknowledgementCount += 1
3182
+ orderedEffects.push({
3183
+ kind: 'acknowledge-target-batch-dependency-event',
3184
+ workSequence: work.workSequence,
3185
+ batchId: work.batchId,
3186
+ dependencyId: work.dependencyId,
3187
+ acknowledgedEventSequence: work.acknowledgedEventSequence,
3188
+ })
3189
+ break
3190
+ case 'settle-release':
3191
+ settleReleaseCount += 1
3192
+ orderedEffects.push({
3193
+ kind: 'release-settle-entry',
3194
+ workSequence: work.workSequence,
3195
+ settleEntryId: work.settleEntryId,
3196
+ })
3197
+ break
3198
+ default: {
3199
+ // An untyped caller can reach this with a work kind outside the union: fail closed with
3200
+ // the typed invariant (never return the work entry as a drain transition).
3201
+ const exhaustive: never = work
3202
+ void exhaustive
3203
+ throw new DriverValueChannelInvariantError('commit-loss-work-kind-unrecognized')
3204
+ }
3205
+ }
3206
+ }
3207
+
3208
+ // Only when the due-work set ends does the refusal release its protocol resources, each
3209
+ // exactly once, finalizing without permitting either side to run or reverse.
3210
+ let transferTimerCancellationCount = state.cleanupCounts.transferTimerCancellationCount
3211
+ let preparedStateReleaseCount = state.cleanupCounts.preparedStateReleaseCount
3212
+ let preparedCloneReleaseCount = state.cleanupCounts.preparedCloneReleaseCount
3213
+ let transferActivityReleaseCount = state.cleanupCounts.transferActivityReleaseCount
3214
+ let terminalFinalizationCount = state.cleanupCounts.terminalFinalizationCount
3215
+ let preparedStateRetained = state.channel.handoff.resources.preparedStateRetained
3216
+ let preparedCloneRetained = state.channel.handoff.resources.preparedCloneRetained
3217
+ let transferActivityRetained = state.channel.handoff.resources.transferActivityRetained
3218
+ let transferTimerArmed = state.channel.handoff.resources.transferTimerArmed
3219
+ let ownership = state.channel.handoff.ownership
3220
+ if (transferTimerCancellationCount === 0 && transferTimerArmed) {
3221
+ orderedEffects.push({ kind: 'cancel-transfer-timer-once' })
3222
+ transferTimerCancellationCount = 1
3223
+ transferTimerArmed = false
3224
+ }
3225
+ if (preparedStateReleaseCount === 0 && preparedStateRetained) {
3226
+ orderedEffects.push({ kind: 'release-prepared-state-once' })
3227
+ preparedStateReleaseCount = 1
3228
+ preparedStateRetained = false
3229
+ }
3230
+ if (preparedCloneReleaseCount === 0 && preparedCloneRetained) {
3231
+ orderedEffects.push({ kind: 'release-prepared-clone-once' })
3232
+ preparedCloneReleaseCount = 1
3233
+ preparedCloneRetained = false
3234
+ }
3235
+ if (transferActivityReleaseCount === 0 && transferActivityRetained) {
3236
+ orderedEffects.push({ kind: 'release-transfer-activity-once' })
3237
+ transferActivityReleaseCount = 1
3238
+ transferActivityRetained = false
3239
+ }
3240
+ if (terminalFinalizationCount === 0) {
3241
+ orderedEffects.push({ kind: 'finalize-bounded-refusal-without-run-or-reverse-once' })
3242
+ terminalFinalizationCount = 1
3243
+ ownership = 'TERMINAL_REFUSED'
3244
+ }
3245
+
3246
+ const cleanupCounts: ValueHandoffCommitLossCleanupCounts = {
3247
+ transferTimerCancellationCount,
3248
+ preparedStateReleaseCount,
3249
+ preparedCloneReleaseCount,
3250
+ transferActivityReleaseCount,
3251
+ terminalFinalizationCount,
3252
+ }
3253
+ const resources: ValueChannelHandoffResourceState = {
3254
+ preparedStateRetained,
3255
+ preparedCloneRetained,
3256
+ transferActivityRetained,
3257
+ transferTimerArmed,
3258
+ }
3259
+
3260
+ const nextState: ValueHandoffCommitLossDrainState = {
3261
+ channel: {
3262
+ identity: state.channel.identity,
3263
+ handoff: { ...state.channel.handoff, ownership, resources },
3264
+ },
3265
+ retainedWork: [],
3266
+ drainedWorkSequences: [...state.drainedWorkSequences, ...drainedWorkSequences],
3267
+ retiredListenerIds: state.retiredListenerIds,
3268
+ retiredSinkRegistrationIds: state.retiredSinkRegistrationIds,
3269
+ cleanupCounts,
3270
+ }
3271
+ return {
3272
+ state: nextState,
3273
+ observation: {
3274
+ remainingRetainedWorkCount: nextState.retainedWork.length,
3275
+ drainedWorkCount: drainedWorkSequences.length,
3276
+ retiredListenerDeliveryCount: 0,
3277
+ retiredSinkPublicationCount: 0,
3278
+ hostOnUpdatePublicationCount: 0,
3279
+ dependencyAcknowledgementCount,
3280
+ settleReleaseCount,
3281
+ destinationRunPermitCount: 0,
3282
+ sourceRevivalCount: 0,
3283
+ reverseTransferCount: 0,
3284
+ cleanupCounts,
3285
+ },
3286
+ orderedEffects,
3287
+ }
3288
+ }