@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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/android/build.gradle +24 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/unrulysystems/nativemotion/GestureExclusionModule.kt +55 -0
- package/android/src/main/java/com/unrulysystems/nativemotion/NativeMotionPackage.kt +27 -0
- package/package.json +57 -0
- package/react-native.config.js +14 -0
- package/src/native/driver/instantWindow.ts +31 -0
- package/src/native/driver/strictModeReplay.ts +20 -0
- package/src/native/driver/uiLayoutEngine.ts +845 -0
- package/src/native/driver/uiLayoutGraph.ts +338 -0
- package/src/native/driver/uiValueChannel.ts +4640 -0
- package/src/native/driver/workletDriver.ts +3860 -0
- package/src/native/motion/AnimatePresence.tsx +1605 -0
- package/src/native/motion/LayoutGroup.tsx +163 -0
- package/src/native/motion/MotionConfig.tsx +400 -0
- package/src/native/motion/MotionRoot.tsx +250 -0
- package/src/native/motion/MotionView.tsx +6761 -0
- package/src/native/motion/addScaleCorrector.ts +97 -0
- package/src/native/motion/colorEndpointUiFeed.ts +32 -0
- package/src/native/motion/colorProjection.ts +39 -0
- package/src/native/motion/composeTransform.ts +271 -0
- package/src/native/motion/constraintMeasure.ts +157 -0
- package/src/native/motion/deferredPendingSnapshots.ts +25 -0
- package/src/native/motion/discreteProjection.ts +36 -0
- package/src/native/motion/dragAncestorPanContext.ts +28 -0
- package/src/native/motion/dragControls.ts +152 -0
- package/src/native/motion/dragGestureWiring.ts +1168 -0
- package/src/native/motion/dragHandoffBinding.ts +179 -0
- package/src/native/motion/dragHubStream.ts +756 -0
- package/src/native/motion/dragPropagationLock.ts +57 -0
- package/src/native/motion/driverValueChannel.ts +3288 -0
- package/src/native/motion/externalDragDriver.ts +214 -0
- package/src/native/motion/frameData.ts +31 -0
- package/src/native/motion/gestureBinding.ts +107 -0
- package/src/native/motion/gestureStateGate.ts +563 -0
- package/src/native/motion/gestureStateResolver.ts +286 -0
- package/src/native/motion/identityValueChannelControllerAdapter.ts +2043 -0
- package/src/native/motion/identityValueChannelControllerReconciliation.ts +176 -0
- package/src/native/motion/identityValueChannelLaneMarker.ts +14 -0
- package/src/native/motion/imperativeAnimate.ts +816 -0
- package/src/native/motion/keyframeTiming.ts +7 -0
- package/src/native/motion/layoutIdBinding.ts +261 -0
- package/src/native/motion/layoutIdFlightConfig.ts +51 -0
- package/src/native/motion/layoutProjection.ts +89 -0
- package/src/native/motion/layoutScroll.ts +204 -0
- package/src/native/motion/layoutTransition.ts +1302 -0
- package/src/native/motion/lengthLayoutContext.tsx +100 -0
- package/src/native/motion/lengthLayoutHost.ts +44 -0
- package/src/native/motion/mappedKeys.ts +184 -0
- package/src/native/motion/motionViewController.ts +3180 -0
- package/src/native/motion/nativeHostMarker.ts +22 -0
- package/src/native/motion/panSessionWiring.ts +127 -0
- package/src/native/motion/pathTransition.ts +101 -0
- package/src/native/motion/popLayout.ts +176 -0
- package/src/native/motion/presenceBinding.ts +382 -0
- package/src/native/motion/scaleCorrectorRegistry.ts +123 -0
- package/src/native/motion/scrollValues.ts +260 -0
- package/src/native/motion/serializablePayload.ts +151 -0
- package/src/native/motion/severity.ts +16 -0
- package/src/native/motion/shippedSurface.ts +843 -0
- package/src/native/motion/staticLengthGate.ts +86 -0
- package/src/native/motion/styleBaseGate.ts +72 -0
- package/src/native/motion/styleValueBinding.ts +657 -0
- package/src/native/motion/systemGestureExclusion.ts +61 -0
- package/src/native/motion/tapGestureWiring.ts +215 -0
- package/src/native/motion/transformOrder.ts +38 -0
- package/src/native/motion/transformStringBinding.ts +200 -0
- package/src/native/motion/transformTemplateGate.ts +45 -0
- package/src/native/motion/transitionGate.ts +435 -0
- package/src/native/motion/useAnimate.ts +52 -0
- package/src/native/motion/useCycle.ts +44 -0
- package/src/native/motion/useInstantTransition.ts +68 -0
- package/src/native/motion/useReducedMotion.ts +58 -0
- package/src/native/motion/useScroll.ts +162 -0
- package/src/native/motion/useViewportScroll.ts +29 -0
- package/src/native/motion/valueChannel.ts +7121 -0
- package/src/native/motion/valueHooks.ts +901 -0
- package/src/native/motion/variantChildRegistry.ts +67 -0
- package/src/native/motion/variantContext.tsx +172 -0
- package/src/native/motion/variantProps.ts +598 -0
- package/src/native.ts +159 -0
- package/src/verification/harnessMetrics.ts +78 -0
- package/src/verification/probe/LayoutIdentityWorkletProbe.tsx +251 -0
- package/src/verification/probe/WorkletParityProbe.tsx +162 -0
- package/src/verification/probe/layoutIdentityProbeEngine.ts +429 -0
- package/src/verification/screens/ArcPathChecksScreen.tsx +273 -0
- package/src/verification/screens/BooleanAnimateChecksScreen.tsx +358 -0
- package/src/verification/screens/ChoreographyGalleryScreen.tsx +1010 -0
- package/src/verification/screens/ColorBindingChecksScreen.tsx +568 -0
- package/src/verification/screens/CompletionChecksScreen.tsx +294 -0
- package/src/verification/screens/ConformanceScreen.tsx +573 -0
- package/src/verification/screens/ContentionProbeScreen.tsx +224 -0
- package/src/verification/screens/DriverSmokeScreen.tsx +99 -0
- package/src/verification/screens/DurationOnlyTweenChecksScreen.tsx +294 -0
- package/src/verification/screens/DynamicDragConfigChecksScreen.tsx +237 -0
- package/src/verification/screens/FrameDataChecksScreen.tsx +252 -0
- package/src/verification/screens/GestureChecksScreen.tsx +685 -0
- package/src/verification/screens/InstantTransitionChecksScreen.tsx +839 -0
- package/src/verification/screens/LayoutAnimationStartChecksScreen.tsx +648 -0
- package/src/verification/screens/LayoutChecksScreen.tsx +822 -0
- package/src/verification/screens/LayoutCommitSpikeScreen.tsx +142 -0
- package/src/verification/screens/MotionViewChecksScreen.tsx +930 -0
- package/src/verification/screens/PresenceChecksScreen.tsx +614 -0
- package/src/verification/screens/ReducedMotionChecksScreen.tsx +614 -0
- package/src/verification/screens/RestThresholdChecksScreen.tsx +634 -0
- package/src/verification/screens/ScaleCorrectorChecksScreen.tsx +425 -0
- package/src/verification/screens/SharedLayoutContinuityChecksScreen.tsx +338 -0
- package/src/verification/screens/SharedLayoutCrossfadeChecksScreen.tsx +425 -0
- package/src/verification/screens/TransitionDefaultSelectionChecksScreen.tsx +514 -0
- package/src/verification/screens/ViewportScrollAliasChecksScreen.tsx +303 -0
- package/src/verification/screens/conformanceBanner.ts +21 -0
- package/src/verification/screens/proofConsoleTap.ts +18 -0
- package/src/verification.ts +47 -0
- package/src/web.ts +98 -0
|
@@ -0,0 +1,4640 @@
|
|
|
1
|
+
// Private UI-global value-channel coordinator (REQ-DRIVER-034).
|
|
2
|
+
// Creation-ordered callback/admission records, one timestamp-global begin/invoke/revoke/commit
|
|
3
|
+
// frame transaction, stable callback registration, runner admission/reselection, cold-work relay
|
|
4
|
+
// through preallocated canonical records, private dependency events, sink fanout, and the manual
|
|
5
|
+
// frame group. Every kind-keyed dispatch refuses unrecognized kinds with a typed invariant error
|
|
6
|
+
// and leaves state untouched on refusal.
|
|
7
|
+
|
|
8
|
+
import type { FrameInfo } from 'react-native-reanimated'
|
|
9
|
+
import type { MotionValue } from '@unrulysystems/native-motion-core'
|
|
10
|
+
import type {
|
|
11
|
+
DestinationCommitAcknowledgement,
|
|
12
|
+
ImmutableDestinationCommit,
|
|
13
|
+
NormalizedRetainedWorkEntry,
|
|
14
|
+
ValueHandoffDestinationLedgerState,
|
|
15
|
+
} from '../motion/driverValueChannel'
|
|
16
|
+
import type { ChannelMailboxEntry } from '../motion/valueChannel'
|
|
17
|
+
import type {
|
|
18
|
+
MotionValueDriverPort,
|
|
19
|
+
PrimitiveNumericHistory,
|
|
20
|
+
TransportedExecutionGeneratorSample,
|
|
21
|
+
} from '@unrulysystems/native-motion-core/internal-driver'
|
|
22
|
+
|
|
23
|
+
export type UiCoordinatorFrameInfo = Pick<
|
|
24
|
+
FrameInfo,
|
|
25
|
+
'timestamp' | 'timeSincePreviousFrame' | 'timeSinceFirstFrame'
|
|
26
|
+
>
|
|
27
|
+
|
|
28
|
+
export type UiCallbackLeaseState =
|
|
29
|
+
| 'REGISTERED_INACTIVE'
|
|
30
|
+
| 'ACTIVATION_REQUESTED'
|
|
31
|
+
| 'ADMITTED'
|
|
32
|
+
| 'RUNNER_CANDIDATE'
|
|
33
|
+
| 'RETIRING'
|
|
34
|
+
| 'REVOKED'
|
|
35
|
+
|
|
36
|
+
export interface UiCallbackRegistrationState {
|
|
37
|
+
readonly driverId: number
|
|
38
|
+
readonly callbackIdentityId: number
|
|
39
|
+
readonly callbackRegistrationGeneration: number
|
|
40
|
+
readonly callbackLeaseEpoch: number
|
|
41
|
+
readonly state: UiCallbackLeaseState
|
|
42
|
+
readonly admittedFromTimestampMs: number | null
|
|
43
|
+
readonly lastInvocationTimestampMs: number | null
|
|
44
|
+
readonly observerRevision: number
|
|
45
|
+
readonly activatorRegistered: boolean
|
|
46
|
+
readonly reanimatedCallbackActive: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type UiRelayWorkClass =
|
|
50
|
+
| 'channel-ordinary-first-sample'
|
|
51
|
+
| 'private-ordinary-first-sample'
|
|
52
|
+
| 'instant-target'
|
|
53
|
+
| 'instant-completion'
|
|
54
|
+
| 'equal-target-reassertion'
|
|
55
|
+
| 'direct-write'
|
|
56
|
+
| 'idle-write'
|
|
57
|
+
| 'transition-end-carrier'
|
|
58
|
+
| 'exit-carrier'
|
|
59
|
+
| 'packet-b-carrier'
|
|
60
|
+
|
|
61
|
+
export type UiColdWorkRelayLeaseState =
|
|
62
|
+
| 'ARMED'
|
|
63
|
+
| 'RELAYED_BY_INCUMBENT'
|
|
64
|
+
| 'CLAIMED_BY_TARGET'
|
|
65
|
+
| 'RELEASED'
|
|
66
|
+
|
|
67
|
+
export type UiColdRelayGeneratorExecutionEntry = Extract<
|
|
68
|
+
NormalizedRetainedWorkEntry,
|
|
69
|
+
{ readonly kind: 'generator-execution' }
|
|
70
|
+
>
|
|
71
|
+
|
|
72
|
+
export type UiColdRelayUpdateTaskEntry<
|
|
73
|
+
TaskClass extends Extract<
|
|
74
|
+
NormalizedRetainedWorkEntry,
|
|
75
|
+
{ readonly kind: 'update-task' }
|
|
76
|
+
>['taskClass'],
|
|
77
|
+
> = Extract<NormalizedRetainedWorkEntry, { readonly kind: 'update-task' }> & {
|
|
78
|
+
readonly taskClass: TaskClass
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type UiColdRelayDirectWriteEntry = Extract<
|
|
82
|
+
NormalizedRetainedWorkEntry,
|
|
83
|
+
{ readonly kind: 'direct-write' }
|
|
84
|
+
>
|
|
85
|
+
|
|
86
|
+
export type UiColdRelayCompletionEntry = Extract<
|
|
87
|
+
NormalizedRetainedWorkEntry,
|
|
88
|
+
{ readonly kind: 'value-completion' }
|
|
89
|
+
>
|
|
90
|
+
|
|
91
|
+
export type UiColdRelayCarrierEntry<
|
|
92
|
+
TaskClass extends Extract<
|
|
93
|
+
NormalizedRetainedWorkEntry,
|
|
94
|
+
{ readonly kind: 'carrier-task' }
|
|
95
|
+
>['taskClass'],
|
|
96
|
+
> = Extract<NormalizedRetainedWorkEntry, { readonly kind: 'carrier-task' }> & {
|
|
97
|
+
readonly taskClass: TaskClass
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface UiColdRelayEnvelopeWatermarks {
|
|
101
|
+
readonly mailboxDrainId: number
|
|
102
|
+
readonly drainedThroughAuthoritativeSequence: number
|
|
103
|
+
readonly deliveredWriteWatermark: number
|
|
104
|
+
readonly deliveredEventWatermark: number
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type UiColdRelayWriteResultSlot =
|
|
108
|
+
| {
|
|
109
|
+
readonly kind: 'GENERATOR_SAMPLE_WRITE'
|
|
110
|
+
readonly resultSlotId: number
|
|
111
|
+
readonly entry: UiColdRelayGeneratorExecutionEntry
|
|
112
|
+
}
|
|
113
|
+
| {
|
|
114
|
+
readonly kind: 'UPDATE_TASK_WRITE'
|
|
115
|
+
readonly resultSlotId: number
|
|
116
|
+
readonly entry: UiColdRelayUpdateTaskEntry<'instant' | 'equal' | 'idle-direct-write'>
|
|
117
|
+
}
|
|
118
|
+
| {
|
|
119
|
+
readonly kind: 'DIRECT_WRITE'
|
|
120
|
+
readonly resultSlotId: number
|
|
121
|
+
readonly entry: UiColdRelayDirectWriteEntry
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type UiColdRelayCallbackResultSlot =
|
|
125
|
+
| {
|
|
126
|
+
readonly kind: 'CHANNEL_TRANSITION_ON_UPDATE'
|
|
127
|
+
readonly resultSlotId: number
|
|
128
|
+
readonly callbackId: number
|
|
129
|
+
readonly eventSequence: number
|
|
130
|
+
}
|
|
131
|
+
| {
|
|
132
|
+
readonly kind: 'TASK_CALLBACK'
|
|
133
|
+
readonly resultSlotId: number
|
|
134
|
+
readonly taskId: number
|
|
135
|
+
readonly callback: UiColdRelayUpdateTaskEntry<
|
|
136
|
+
'instant' | 'equal' | 'idle-direct-write'
|
|
137
|
+
>['callbacks'][number]
|
|
138
|
+
}
|
|
139
|
+
| {
|
|
140
|
+
readonly kind: 'VALUE_COMPLETION_CALLBACKS'
|
|
141
|
+
readonly resultSlotId: number
|
|
142
|
+
readonly completionSequence: number
|
|
143
|
+
readonly transitionOnCompleteCallbackId: number | null
|
|
144
|
+
readonly lifecycleCallbackIds: readonly number[]
|
|
145
|
+
readonly publicStartPromiseId: number | null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface UiColdRelayCarrierResultSlot<
|
|
149
|
+
TaskClass extends 'transition-end-carrier' | 'exit-carrier' | 'packet-b-carrier' =
|
|
150
|
+
| 'transition-end-carrier'
|
|
151
|
+
| 'exit-carrier'
|
|
152
|
+
| 'packet-b-carrier',
|
|
153
|
+
> {
|
|
154
|
+
readonly kind: 'CARRIER'
|
|
155
|
+
readonly resultSlotId: number
|
|
156
|
+
readonly entry: UiColdRelayCarrierEntry<TaskClass>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type UiColdRelayHostCommitResultSlot = Extract<
|
|
160
|
+
ChannelMailboxEntry,
|
|
161
|
+
{ readonly kind: 'ELEMENT_HOST_COMMIT' }
|
|
162
|
+
>
|
|
163
|
+
|
|
164
|
+
interface UiColdRelayDeliverySlotsCommon<Class extends UiRelayWorkClass> {
|
|
165
|
+
readonly workClass: Class
|
|
166
|
+
readonly envelope: UiColdRelayEnvelopeWatermarks
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type UiColdRelayDeliverySlots =
|
|
170
|
+
| (UiColdRelayDeliverySlotsCommon<'channel-ordinary-first-sample'> & {
|
|
171
|
+
readonly writeSlots: readonly [
|
|
172
|
+
Extract<UiColdRelayWriteResultSlot, { readonly kind: 'GENERATOR_SAMPLE_WRITE' }>,
|
|
173
|
+
]
|
|
174
|
+
readonly callbackSlots: readonly Extract<
|
|
175
|
+
UiColdRelayCallbackResultSlot,
|
|
176
|
+
{ readonly kind: 'CHANNEL_TRANSITION_ON_UPDATE' }
|
|
177
|
+
>[]
|
|
178
|
+
readonly carrierSlots: readonly []
|
|
179
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
180
|
+
})
|
|
181
|
+
| (UiColdRelayDeliverySlotsCommon<'private-ordinary-first-sample'> & {
|
|
182
|
+
readonly writeSlots: readonly [
|
|
183
|
+
Extract<UiColdRelayWriteResultSlot, { readonly kind: 'GENERATOR_SAMPLE_WRITE' }>,
|
|
184
|
+
]
|
|
185
|
+
readonly callbackSlots: readonly []
|
|
186
|
+
readonly carrierSlots: readonly []
|
|
187
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
188
|
+
})
|
|
189
|
+
| (UiColdRelayDeliverySlotsCommon<'instant-target'> & {
|
|
190
|
+
readonly writeSlots: readonly [
|
|
191
|
+
Extract<UiColdRelayWriteResultSlot, { readonly kind: 'UPDATE_TASK_WRITE' }> & {
|
|
192
|
+
readonly entry: UiColdRelayUpdateTaskEntry<'instant'>
|
|
193
|
+
},
|
|
194
|
+
]
|
|
195
|
+
readonly callbackSlots: readonly Extract<
|
|
196
|
+
UiColdRelayCallbackResultSlot,
|
|
197
|
+
{ readonly kind: 'TASK_CALLBACK' }
|
|
198
|
+
>[]
|
|
199
|
+
readonly carrierSlots: readonly []
|
|
200
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
201
|
+
})
|
|
202
|
+
| (UiColdRelayDeliverySlotsCommon<'instant-completion'> & {
|
|
203
|
+
readonly writeSlots: readonly []
|
|
204
|
+
readonly callbackSlots: readonly [
|
|
205
|
+
Extract<UiColdRelayCallbackResultSlot, { readonly kind: 'VALUE_COMPLETION_CALLBACKS' }>,
|
|
206
|
+
]
|
|
207
|
+
readonly carrierSlots: readonly []
|
|
208
|
+
readonly hostSlots: readonly []
|
|
209
|
+
})
|
|
210
|
+
| (UiColdRelayDeliverySlotsCommon<'equal-target-reassertion'> & {
|
|
211
|
+
readonly writeSlots: readonly [
|
|
212
|
+
Extract<UiColdRelayWriteResultSlot, { readonly kind: 'UPDATE_TASK_WRITE' }> & {
|
|
213
|
+
readonly entry: UiColdRelayUpdateTaskEntry<'equal'>
|
|
214
|
+
},
|
|
215
|
+
]
|
|
216
|
+
readonly callbackSlots: readonly Extract<
|
|
217
|
+
UiColdRelayCallbackResultSlot,
|
|
218
|
+
{ readonly kind: 'TASK_CALLBACK' }
|
|
219
|
+
>[]
|
|
220
|
+
readonly carrierSlots: readonly []
|
|
221
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
222
|
+
})
|
|
223
|
+
| (UiColdRelayDeliverySlotsCommon<'direct-write'> & {
|
|
224
|
+
readonly writeSlots: readonly [
|
|
225
|
+
Extract<UiColdRelayWriteResultSlot, { readonly kind: 'DIRECT_WRITE' }>,
|
|
226
|
+
]
|
|
227
|
+
readonly callbackSlots: readonly []
|
|
228
|
+
readonly carrierSlots: readonly []
|
|
229
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
230
|
+
})
|
|
231
|
+
| (UiColdRelayDeliverySlotsCommon<'idle-write'> & {
|
|
232
|
+
readonly writeSlots: readonly [
|
|
233
|
+
Extract<UiColdRelayWriteResultSlot, { readonly kind: 'UPDATE_TASK_WRITE' }> & {
|
|
234
|
+
readonly entry: UiColdRelayUpdateTaskEntry<'idle-direct-write'>
|
|
235
|
+
},
|
|
236
|
+
]
|
|
237
|
+
readonly callbackSlots: readonly Extract<
|
|
238
|
+
UiColdRelayCallbackResultSlot,
|
|
239
|
+
{ readonly kind: 'TASK_CALLBACK' }
|
|
240
|
+
>[]
|
|
241
|
+
readonly carrierSlots: readonly []
|
|
242
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
243
|
+
})
|
|
244
|
+
| (UiColdRelayDeliverySlotsCommon<'transition-end-carrier'> & {
|
|
245
|
+
readonly writeSlots: readonly []
|
|
246
|
+
readonly callbackSlots: readonly []
|
|
247
|
+
readonly carrierSlots: readonly [UiColdRelayCarrierResultSlot<'transition-end-carrier'>]
|
|
248
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
249
|
+
})
|
|
250
|
+
| (UiColdRelayDeliverySlotsCommon<'exit-carrier'> & {
|
|
251
|
+
readonly writeSlots: readonly []
|
|
252
|
+
readonly callbackSlots: readonly []
|
|
253
|
+
readonly carrierSlots: readonly [UiColdRelayCarrierResultSlot<'exit-carrier'>]
|
|
254
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
255
|
+
})
|
|
256
|
+
| (UiColdRelayDeliverySlotsCommon<'packet-b-carrier'> & {
|
|
257
|
+
readonly writeSlots: readonly []
|
|
258
|
+
readonly callbackSlots: readonly []
|
|
259
|
+
readonly carrierSlots: readonly [UiColdRelayCarrierResultSlot<'packet-b-carrier'>]
|
|
260
|
+
readonly hostSlots: readonly [UiColdRelayHostCommitResultSlot]
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
interface UiColdRelayCommandAuthorityCommon<Class extends UiRelayWorkClass> {
|
|
264
|
+
readonly workClass: Class
|
|
265
|
+
readonly destinationRecordId: number
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
type UiColdRelayCommandAuthority =
|
|
269
|
+
| (UiColdRelayCommandAuthorityCommon<'channel-ordinary-first-sample'> & {
|
|
270
|
+
readonly owner: { readonly kind: 'PUBLIC_IDENTITY'; readonly channelId: number }
|
|
271
|
+
readonly executionId: number
|
|
272
|
+
})
|
|
273
|
+
| (UiColdRelayCommandAuthorityCommon<'private-ordinary-first-sample'> & {
|
|
274
|
+
readonly owner: { readonly kind: 'PRIVATE_CONTROL'; readonly controlId: number }
|
|
275
|
+
readonly executionId: number
|
|
276
|
+
})
|
|
277
|
+
| (UiColdRelayCommandAuthorityCommon<'instant-target'> & {
|
|
278
|
+
readonly taskId: number
|
|
279
|
+
readonly taskSequence: number
|
|
280
|
+
})
|
|
281
|
+
| (UiColdRelayCommandAuthorityCommon<'instant-completion'> & {
|
|
282
|
+
readonly completionSequence: number
|
|
283
|
+
readonly completionControllerId: number
|
|
284
|
+
})
|
|
285
|
+
| (UiColdRelayCommandAuthorityCommon<'equal-target-reassertion'> & {
|
|
286
|
+
readonly taskId: number
|
|
287
|
+
readonly taskSequence: number
|
|
288
|
+
})
|
|
289
|
+
| (UiColdRelayCommandAuthorityCommon<'direct-write'> & {
|
|
290
|
+
readonly directOperationId: number
|
|
291
|
+
readonly directOperationEpoch: number
|
|
292
|
+
})
|
|
293
|
+
| (UiColdRelayCommandAuthorityCommon<'idle-write'> & {
|
|
294
|
+
readonly taskId: number
|
|
295
|
+
readonly taskSequence: number
|
|
296
|
+
})
|
|
297
|
+
| (UiColdRelayCommandAuthorityCommon<'transition-end-carrier'> & {
|
|
298
|
+
readonly carrierControllerId: number
|
|
299
|
+
readonly taskId: number
|
|
300
|
+
})
|
|
301
|
+
| (UiColdRelayCommandAuthorityCommon<'exit-carrier'> & {
|
|
302
|
+
readonly carrierControllerId: number
|
|
303
|
+
readonly taskId: number
|
|
304
|
+
})
|
|
305
|
+
| (UiColdRelayCommandAuthorityCommon<'packet-b-carrier'> & {
|
|
306
|
+
readonly carrierControllerId: number
|
|
307
|
+
readonly taskId: number
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
declare const uiColdRelayCanonicalRecordBrand: unique symbol
|
|
311
|
+
|
|
312
|
+
export interface UiColdRelayCanonicalRecord {
|
|
313
|
+
readonly [uiColdRelayCanonicalRecordBrand]: true
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export interface UiColdWorkRelayValueBearingEntry {
|
|
317
|
+
readonly kind: 'COMPLETE_TRANSITION_ON_UPDATE'
|
|
318
|
+
readonly channelId: number
|
|
319
|
+
readonly targetCallbackIdentityId: number
|
|
320
|
+
readonly transitionCallbackId: number
|
|
321
|
+
readonly valueWriteEntryId: number
|
|
322
|
+
readonly transitionCallbackEntryId: number
|
|
323
|
+
readonly executionId: number
|
|
324
|
+
readonly directOperationEpoch: number
|
|
325
|
+
readonly executionLease: number
|
|
326
|
+
readonly authoritativeSequence: number
|
|
327
|
+
readonly writeSequence: number
|
|
328
|
+
readonly eventSequence: number
|
|
329
|
+
readonly candidateHistory: PrimitiveNumericHistory
|
|
330
|
+
readonly completion: TransportedExecutionGeneratorSample['completion']
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export type UiColdWorkRelayValueWriteMailboxEntry = Extract<
|
|
334
|
+
ChannelMailboxEntry,
|
|
335
|
+
{ readonly kind: 'VALUE_WRITE' }
|
|
336
|
+
> & {
|
|
337
|
+
readonly sample: TransportedExecutionGeneratorSample
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export type UiColdWorkRelayTransitionCallbackMailboxEntry = Extract<
|
|
341
|
+
ChannelMailboxEntry,
|
|
342
|
+
{ readonly kind: 'TRANSITION_CALLBACK' }
|
|
343
|
+
> & {
|
|
344
|
+
readonly event: Extract<
|
|
345
|
+
ChannelMailboxEntry,
|
|
346
|
+
{ readonly kind: 'TRANSITION_CALLBACK' }
|
|
347
|
+
>['event'] & { readonly kind: 'transition-on-update' }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface UiColdWorkRelayMailboxPrefix {
|
|
351
|
+
readonly mailboxOrigin: 'UI'
|
|
352
|
+
readonly mailboxDrainId: number
|
|
353
|
+
readonly stagedEntries: readonly [
|
|
354
|
+
UiColdWorkRelayValueWriteMailboxEntry,
|
|
355
|
+
UiColdWorkRelayTransitionCallbackMailboxEntry,
|
|
356
|
+
]
|
|
357
|
+
readonly sealed: false
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export interface UiColdWorkRelayStagedValueBearingPrefix {
|
|
361
|
+
readonly mailboxDrainId: number
|
|
362
|
+
readonly valueWriteEntryId: number
|
|
363
|
+
readonly transitionCallbackEntryId: number
|
|
364
|
+
readonly transitionCallbackId: number
|
|
365
|
+
readonly authoritativeSequence: number
|
|
366
|
+
readonly writeSequence: number
|
|
367
|
+
readonly eventSequence: number
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Canonical public-identity authority retained by the coordinator, never reconstructed by relay. */
|
|
371
|
+
export interface UiPublicIdentityChannelRecord {
|
|
372
|
+
readonly channelId: number
|
|
373
|
+
readonly motionValue: MotionValue<number>
|
|
374
|
+
readonly driverPort: MotionValueDriverPort<number>
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
interface UiGeneratorExecutionRecordCommon<Class extends UiRelayWorkClass> {
|
|
378
|
+
readonly workClass: Class
|
|
379
|
+
readonly executionRecordId: number
|
|
380
|
+
readonly entry: UiColdRelayGeneratorExecutionEntry
|
|
381
|
+
readonly valueBearingEntry: UiColdWorkRelayValueBearingEntry | null
|
|
382
|
+
readonly writeSlot: Extract<
|
|
383
|
+
UiColdRelayWriteResultSlot,
|
|
384
|
+
{ readonly kind: 'GENERATOR_SAMPLE_WRITE' }
|
|
385
|
+
>
|
|
386
|
+
readonly callbackSlots: readonly Extract<
|
|
387
|
+
UiColdRelayCallbackResultSlot,
|
|
388
|
+
{ readonly kind: 'CHANNEL_TRANSITION_ON_UPDATE' }
|
|
389
|
+
>[]
|
|
390
|
+
readonly mailboxDrainId: number
|
|
391
|
+
readonly hostSinkRecordIds: readonly number[]
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export type UiGeneratorExecutionRecord =
|
|
395
|
+
| (UiGeneratorExecutionRecordCommon<'channel-ordinary-first-sample'> & {
|
|
396
|
+
readonly owner: { readonly kind: 'PUBLIC_IDENTITY'; readonly channelId: number }
|
|
397
|
+
readonly publicIdentityChannel: UiPublicIdentityChannelRecord
|
|
398
|
+
readonly valueBearingEntry: UiColdWorkRelayValueBearingEntry
|
|
399
|
+
})
|
|
400
|
+
| (UiGeneratorExecutionRecordCommon<'private-ordinary-first-sample'> & {
|
|
401
|
+
readonly owner: { readonly kind: 'PRIVATE_CONTROL'; readonly controlId: number }
|
|
402
|
+
readonly publicIdentityChannel: null
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
type UiDueWorkTaskClass = Extract<
|
|
406
|
+
NormalizedRetainedWorkEntry,
|
|
407
|
+
{ readonly kind: 'update-task' }
|
|
408
|
+
>['taskClass']
|
|
409
|
+
|
|
410
|
+
interface UiDueWorkTaskRecordCommon<
|
|
411
|
+
Class extends UiRelayWorkClass,
|
|
412
|
+
TaskClass extends UiDueWorkTaskClass,
|
|
413
|
+
> {
|
|
414
|
+
readonly workClass: Class
|
|
415
|
+
readonly taskRecordId: number
|
|
416
|
+
readonly entry: UiColdRelayUpdateTaskEntry<TaskClass>
|
|
417
|
+
readonly writeSlot: Extract<
|
|
418
|
+
UiColdRelayWriteResultSlot,
|
|
419
|
+
{ readonly kind: 'UPDATE_TASK_WRITE' }
|
|
420
|
+
> & { readonly entry: UiColdRelayUpdateTaskEntry<TaskClass> }
|
|
421
|
+
readonly callbackSlots: readonly Extract<
|
|
422
|
+
UiColdRelayCallbackResultSlot,
|
|
423
|
+
{ readonly kind: 'TASK_CALLBACK' }
|
|
424
|
+
>[]
|
|
425
|
+
readonly mailboxDrainId: number
|
|
426
|
+
readonly hostSinkRecordIds: readonly number[]
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export type UiDueWorkTaskRecord =
|
|
430
|
+
| UiDueWorkTaskRecordCommon<'instant-target', 'instant'>
|
|
431
|
+
| UiDueWorkTaskRecordCommon<'equal-target-reassertion', 'equal'>
|
|
432
|
+
| UiDueWorkTaskRecordCommon<'idle-write', 'idle-direct-write'>
|
|
433
|
+
|
|
434
|
+
export interface UiValueCompletionControllerRecord {
|
|
435
|
+
readonly workClass: 'instant-completion'
|
|
436
|
+
readonly completionControllerId: number
|
|
437
|
+
readonly entry: UiColdRelayCompletionEntry
|
|
438
|
+
readonly callbackSlot: Extract<
|
|
439
|
+
UiColdRelayCallbackResultSlot,
|
|
440
|
+
{ readonly kind: 'VALUE_COMPLETION_CALLBACKS' }
|
|
441
|
+
>
|
|
442
|
+
readonly mailboxDrainId: number
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export interface UiDirectOperationRecord {
|
|
446
|
+
readonly workClass: 'direct-write'
|
|
447
|
+
readonly directOperationId: number
|
|
448
|
+
readonly directOperationEpoch: number
|
|
449
|
+
readonly entry: UiColdRelayDirectWriteEntry
|
|
450
|
+
readonly writeSlot: Extract<UiColdRelayWriteResultSlot, { readonly kind: 'DIRECT_WRITE' }>
|
|
451
|
+
readonly mailboxDrainId: number
|
|
452
|
+
readonly hostSinkRecordIds: readonly number[]
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
interface UiCarrierControllerRecordCommon<
|
|
456
|
+
Class extends 'transition-end-carrier' | 'exit-carrier' | 'packet-b-carrier' =
|
|
457
|
+
| 'transition-end-carrier'
|
|
458
|
+
| 'exit-carrier'
|
|
459
|
+
| 'packet-b-carrier',
|
|
460
|
+
> {
|
|
461
|
+
readonly workClass: Class
|
|
462
|
+
readonly carrierControllerId: number
|
|
463
|
+
readonly entry: UiColdRelayCarrierEntry<Class>
|
|
464
|
+
readonly carrierSlot: UiColdRelayCarrierResultSlot<Class>
|
|
465
|
+
readonly mailboxDrainId: number
|
|
466
|
+
readonly hostSinkRecordIds: readonly number[]
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export type UiCarrierControllerRecord = {
|
|
470
|
+
readonly [Class in
|
|
471
|
+
| 'transition-end-carrier'
|
|
472
|
+
| 'exit-carrier'
|
|
473
|
+
| 'packet-b-carrier']: UiCarrierControllerRecordCommon<Class>
|
|
474
|
+
}['transition-end-carrier' | 'exit-carrier' | 'packet-b-carrier']
|
|
475
|
+
|
|
476
|
+
export interface UiMailboxTurnRecord {
|
|
477
|
+
readonly mailboxDrainId: number
|
|
478
|
+
readonly envelope: UiColdRelayEnvelopeWatermarks
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export interface UiHostSinkRecord {
|
|
482
|
+
readonly hostSinkRecordId: number
|
|
483
|
+
readonly slot: UiColdRelayHostCommitResultSlot
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
type UiColdRelayPrimaryRecordByClass = {
|
|
487
|
+
readonly 'channel-ordinary-first-sample': Extract<
|
|
488
|
+
UiGeneratorExecutionRecord,
|
|
489
|
+
{ readonly workClass: 'channel-ordinary-first-sample' }
|
|
490
|
+
>
|
|
491
|
+
readonly 'private-ordinary-first-sample': Extract<
|
|
492
|
+
UiGeneratorExecutionRecord,
|
|
493
|
+
{ readonly workClass: 'private-ordinary-first-sample' }
|
|
494
|
+
>
|
|
495
|
+
readonly 'instant-target': Extract<UiDueWorkTaskRecord, { readonly workClass: 'instant-target' }>
|
|
496
|
+
readonly 'instant-completion': UiValueCompletionControllerRecord
|
|
497
|
+
readonly 'equal-target-reassertion': Extract<
|
|
498
|
+
UiDueWorkTaskRecord,
|
|
499
|
+
{ readonly workClass: 'equal-target-reassertion' }
|
|
500
|
+
>
|
|
501
|
+
readonly 'direct-write': UiDirectOperationRecord
|
|
502
|
+
readonly 'idle-write': Extract<UiDueWorkTaskRecord, { readonly workClass: 'idle-write' }>
|
|
503
|
+
readonly 'transition-end-carrier': Extract<
|
|
504
|
+
UiCarrierControllerRecord,
|
|
505
|
+
{ readonly workClass: 'transition-end-carrier' }
|
|
506
|
+
>
|
|
507
|
+
readonly 'exit-carrier': Extract<
|
|
508
|
+
UiCarrierControllerRecord,
|
|
509
|
+
{ readonly workClass: 'exit-carrier' }
|
|
510
|
+
>
|
|
511
|
+
readonly 'packet-b-carrier': Extract<
|
|
512
|
+
UiCarrierControllerRecord,
|
|
513
|
+
{ readonly workClass: 'packet-b-carrier' }
|
|
514
|
+
>
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
type UiColdRelayCallbackRegistrationsByClass = {
|
|
518
|
+
readonly 'channel-ordinary-first-sample': readonly [UiCallbackRegistrationState]
|
|
519
|
+
readonly 'private-ordinary-first-sample': readonly []
|
|
520
|
+
readonly 'instant-target': readonly [UiCallbackRegistrationState]
|
|
521
|
+
readonly 'instant-completion': readonly [UiCallbackRegistrationState]
|
|
522
|
+
readonly 'equal-target-reassertion': readonly [UiCallbackRegistrationState]
|
|
523
|
+
readonly 'direct-write': readonly []
|
|
524
|
+
readonly 'idle-write': readonly []
|
|
525
|
+
readonly 'transition-end-carrier': readonly []
|
|
526
|
+
readonly 'exit-carrier': readonly []
|
|
527
|
+
readonly 'packet-b-carrier': readonly []
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
type UiColdRelayHostSinkRecordsByClass = {
|
|
531
|
+
readonly 'channel-ordinary-first-sample': readonly [UiHostSinkRecord]
|
|
532
|
+
readonly 'private-ordinary-first-sample': readonly [UiHostSinkRecord]
|
|
533
|
+
readonly 'instant-target': readonly [UiHostSinkRecord]
|
|
534
|
+
readonly 'instant-completion': readonly []
|
|
535
|
+
readonly 'equal-target-reassertion': readonly [UiHostSinkRecord]
|
|
536
|
+
readonly 'direct-write': readonly [UiHostSinkRecord]
|
|
537
|
+
readonly 'idle-write': readonly [UiHostSinkRecord]
|
|
538
|
+
readonly 'transition-end-carrier': readonly [UiHostSinkRecord]
|
|
539
|
+
readonly 'exit-carrier': readonly [UiHostSinkRecord]
|
|
540
|
+
readonly 'packet-b-carrier': readonly [UiHostSinkRecord]
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const uiColdRelaySourceRecordsBrand = Symbol('UiColdRelaySourceRecords')
|
|
544
|
+
|
|
545
|
+
export type UiColdRelaySourceRecords = {
|
|
546
|
+
readonly [Class in UiRelayWorkClass]: {
|
|
547
|
+
readonly [uiColdRelaySourceRecordsBrand]: true
|
|
548
|
+
readonly workClass: Class
|
|
549
|
+
readonly primaryRecord: UiColdRelayPrimaryRecordByClass[Class]
|
|
550
|
+
readonly callbackRegistrations: UiColdRelayCallbackRegistrationsByClass[Class]
|
|
551
|
+
readonly mailboxRecord: UiMailboxTurnRecord
|
|
552
|
+
readonly hostSinkRecords: UiColdRelayHostSinkRecordsByClass[Class]
|
|
553
|
+
}
|
|
554
|
+
}[UiRelayWorkClass]
|
|
555
|
+
|
|
556
|
+
export type UiColdRelayCommandProductionRequest =
|
|
557
|
+
| {
|
|
558
|
+
readonly kind: 'START_PUBLIC_IDENTITY_EXECUTION'
|
|
559
|
+
readonly destinationRecordId: number
|
|
560
|
+
readonly channelId: number
|
|
561
|
+
readonly executionId: number
|
|
562
|
+
}
|
|
563
|
+
| {
|
|
564
|
+
readonly kind: 'START_PRIVATE_CONTROL_EXECUTION'
|
|
565
|
+
readonly destinationRecordId: number
|
|
566
|
+
readonly controlId: number
|
|
567
|
+
readonly executionId: number
|
|
568
|
+
}
|
|
569
|
+
| {
|
|
570
|
+
readonly kind: 'QUEUE_INSTANT_TARGET'
|
|
571
|
+
readonly destinationRecordId: number
|
|
572
|
+
readonly taskId: number
|
|
573
|
+
readonly taskSequence: number
|
|
574
|
+
}
|
|
575
|
+
| {
|
|
576
|
+
readonly kind: 'ARM_INSTANT_COMPLETION'
|
|
577
|
+
readonly destinationRecordId: number
|
|
578
|
+
readonly completionSequence: number
|
|
579
|
+
readonly completionControllerId: number
|
|
580
|
+
}
|
|
581
|
+
| {
|
|
582
|
+
readonly kind: 'QUEUE_EQUAL_TARGET_REASSERTION'
|
|
583
|
+
readonly destinationRecordId: number
|
|
584
|
+
readonly taskId: number
|
|
585
|
+
readonly taskSequence: number
|
|
586
|
+
}
|
|
587
|
+
| {
|
|
588
|
+
readonly kind: 'APPLY_DIRECT_WRITE'
|
|
589
|
+
readonly destinationRecordId: number
|
|
590
|
+
readonly directOperationId: number
|
|
591
|
+
readonly directOperationEpoch: number
|
|
592
|
+
}
|
|
593
|
+
| {
|
|
594
|
+
readonly kind: 'QUEUE_IDLE_WRITE'
|
|
595
|
+
readonly destinationRecordId: number
|
|
596
|
+
readonly taskId: number
|
|
597
|
+
readonly taskSequence: number
|
|
598
|
+
}
|
|
599
|
+
| {
|
|
600
|
+
readonly kind:
|
|
601
|
+
| 'QUEUE_TRANSITION_END_CARRIER'
|
|
602
|
+
| 'QUEUE_EXIT_CARRIER'
|
|
603
|
+
| 'QUEUE_PACKET_B_CARRIER'
|
|
604
|
+
readonly destinationRecordId: number
|
|
605
|
+
readonly carrierControllerId: number
|
|
606
|
+
readonly taskId: number
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const uiColdRelayProducerReceiptBrand = Symbol('UiColdRelayProducerReceipt')
|
|
610
|
+
|
|
611
|
+
export interface UiColdRelayCommandProductionTransition {
|
|
612
|
+
readonly [uiColdRelayProducerReceiptBrand]: true
|
|
613
|
+
readonly state: UiValueChannelCoordinatorState
|
|
614
|
+
readonly commandAuthority: UiColdRelayCommandAuthority
|
|
615
|
+
readonly sourceRecords: UiColdRelaySourceRecords
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
interface UiColdRelayCanonicalRecordObservationCommon<Class extends UiRelayWorkClass> {
|
|
619
|
+
readonly recordId: number
|
|
620
|
+
readonly authority: Extract<UiColdRelayCommandAuthority, { readonly workClass: Class }>
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
type UiColdRelayFilledCanonicalRecordObservation = {
|
|
624
|
+
readonly [Class in UiRelayWorkClass]: UiColdRelayCanonicalRecordObservationCommon<Class> & {
|
|
625
|
+
readonly fillState: 'FILLED_FROM_LIVE_RECORDS'
|
|
626
|
+
readonly sourceRecords: Extract<UiColdRelaySourceRecords, { readonly workClass: Class }>
|
|
627
|
+
readonly deliverySlots: Extract<UiColdRelayDeliverySlots, { readonly workClass: Class }>
|
|
628
|
+
}
|
|
629
|
+
}[UiRelayWorkClass]
|
|
630
|
+
|
|
631
|
+
export type UiColdRelayCanonicalRecordObservation =
|
|
632
|
+
| ({
|
|
633
|
+
readonly fillState: 'EMPTY'
|
|
634
|
+
readonly sourceRecords: null
|
|
635
|
+
readonly deliverySlots: null
|
|
636
|
+
} & {
|
|
637
|
+
readonly [Class in UiRelayWorkClass]: UiColdRelayCanonicalRecordObservationCommon<Class>
|
|
638
|
+
}[UiRelayWorkClass])
|
|
639
|
+
| UiColdRelayFilledCanonicalRecordObservation
|
|
640
|
+
|
|
641
|
+
export type UiColdRelayEffect =
|
|
642
|
+
| {
|
|
643
|
+
readonly kind: 'allocate-empty-canonical-cold-relay-record'
|
|
644
|
+
readonly relayId: number
|
|
645
|
+
readonly canonicalRecord: UiColdRelayCanonicalRecord
|
|
646
|
+
}
|
|
647
|
+
| {
|
|
648
|
+
readonly kind: 'fill-canonical-cold-relay-record-from-live-records'
|
|
649
|
+
readonly relayId: number
|
|
650
|
+
readonly canonicalRecord: UiColdRelayCanonicalRecord
|
|
651
|
+
readonly sourceRecords: UiColdRelaySourceRecords
|
|
652
|
+
}
|
|
653
|
+
| {
|
|
654
|
+
readonly kind: 'apply-canonical-cold-relay-record'
|
|
655
|
+
readonly relayId: number
|
|
656
|
+
readonly targetDriverId: number
|
|
657
|
+
readonly canonicalRecord: UiColdRelayCanonicalRecord
|
|
658
|
+
}
|
|
659
|
+
| {
|
|
660
|
+
readonly kind: 'arm-cold-relay-lease-at-command-edge'
|
|
661
|
+
readonly relayId: number
|
|
662
|
+
readonly targetDriverId: number
|
|
663
|
+
readonly targetCallbackRegistrationGeneration: number
|
|
664
|
+
readonly relayLeaseEpoch: number
|
|
665
|
+
readonly commandReceipt: UiColdRelayCommandProductionTransition
|
|
666
|
+
readonly canonicalRecord: UiColdRelayCanonicalRecord
|
|
667
|
+
}
|
|
668
|
+
| {
|
|
669
|
+
readonly kind: 'activate-cold-relay-target-after-command-arm'
|
|
670
|
+
readonly relayId: number
|
|
671
|
+
readonly targetDriverId: number
|
|
672
|
+
readonly targetCallbackRegistrationGeneration: number
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export interface UiColdWorkRelayState {
|
|
676
|
+
readonly relayId: number
|
|
677
|
+
readonly targetDriverId: number
|
|
678
|
+
readonly commandReceipt: UiColdRelayCommandProductionTransition
|
|
679
|
+
readonly canonicalRecord: UiColdRelayCanonicalRecord
|
|
680
|
+
readonly commandSequence: number
|
|
681
|
+
readonly targetCallbackRegistrationGeneration: number
|
|
682
|
+
readonly relayLeaseEpoch: number
|
|
683
|
+
readonly leaseState: UiColdWorkRelayLeaseState
|
|
684
|
+
readonly lifetime: 'ONE_SHOT' | 'ONGOING_EXECUTION'
|
|
685
|
+
readonly armedAtTimestampMs: number | null
|
|
686
|
+
readonly lastRelayedStepTimestampMs: number | null
|
|
687
|
+
readonly claimedAtTimestampMs: number | null
|
|
688
|
+
readonly claimedByDriverId: number | null
|
|
689
|
+
readonly pendingValueBearingEntry: UiColdWorkRelayValueBearingEntry | null
|
|
690
|
+
readonly stagedValueBearingPrefix: UiColdWorkRelayStagedValueBearingPrefix | null
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export interface UiRetainedFrameCallbackActivatorRegistry {
|
|
694
|
+
hasStableActivator(driverId: number, callbackRegistrationGeneration: number): boolean
|
|
695
|
+
setActive(driverId: number, callbackRegistrationGeneration: number, active: boolean): void
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export interface UiRunnerAdmissionRequestState {
|
|
699
|
+
readonly requestedRunnerLeaseEpoch: number
|
|
700
|
+
readonly candidateDriverId: number
|
|
701
|
+
readonly callbackRegistrationGeneration: number
|
|
702
|
+
readonly admissionToken: number
|
|
703
|
+
readonly absoluteDeadlineMs: number
|
|
704
|
+
readonly actualInvocationObserved: boolean
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export interface UiIdentityFrameState {
|
|
708
|
+
readonly channelId: number
|
|
709
|
+
readonly lastAbsoluteTimestampMs: number | null
|
|
710
|
+
readonly stepCount: number
|
|
711
|
+
readonly hostCommitCount: number
|
|
712
|
+
readonly lastCommittedValueSequence: number
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export interface UiOwnedIdentitySinkCommitState {
|
|
716
|
+
readonly sinkId: number
|
|
717
|
+
readonly driverId: number
|
|
718
|
+
readonly handle: number
|
|
719
|
+
readonly key: string
|
|
720
|
+
readonly bindingGeneration: number
|
|
721
|
+
readonly lastCommittedValue: number
|
|
722
|
+
readonly lastCommittedWriteSequence: number
|
|
723
|
+
readonly commitCount: number
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export interface UiOwnedIdentitySampleFanoutState {
|
|
727
|
+
readonly channelId: number
|
|
728
|
+
readonly ownership: 'UI'
|
|
729
|
+
readonly lastAcceptedAbsoluteTimestampMs: number | null
|
|
730
|
+
readonly sinks: readonly UiOwnedIdentitySinkCommitState[]
|
|
731
|
+
readonly uiSinkCommitCount: number
|
|
732
|
+
readonly jsToUiWriteLaneInvocationCount: number
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
export interface UiOwnedIdentitySampleFanoutRequest {
|
|
736
|
+
readonly source: 'UI_OWNED_IDENTITY_SAMPLE'
|
|
737
|
+
readonly channelId: number
|
|
738
|
+
readonly executionId: number
|
|
739
|
+
readonly value: number
|
|
740
|
+
readonly writeSequence: number
|
|
741
|
+
readonly absoluteTimestampMs: number
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export type UiOwnedIdentitySampleFanoutEffect =
|
|
745
|
+
| {
|
|
746
|
+
readonly kind: 'adopt-ui-owned-identity-sample'
|
|
747
|
+
readonly channelId: number
|
|
748
|
+
readonly executionId: number
|
|
749
|
+
readonly value: number
|
|
750
|
+
readonly writeSequence: number
|
|
751
|
+
readonly absoluteTimestampMs: number
|
|
752
|
+
}
|
|
753
|
+
| {
|
|
754
|
+
readonly kind: 'fanout-ui-owned-identity-sample-to-sink'
|
|
755
|
+
readonly sinkId: number
|
|
756
|
+
readonly driverId: number
|
|
757
|
+
readonly handle: number
|
|
758
|
+
readonly key: string
|
|
759
|
+
readonly bindingGeneration: number
|
|
760
|
+
readonly value: number
|
|
761
|
+
readonly writeSequence: number
|
|
762
|
+
}
|
|
763
|
+
| {
|
|
764
|
+
readonly kind: 'commit-ui-owned-identity-sinks-once'
|
|
765
|
+
readonly channelId: number
|
|
766
|
+
readonly sinkIds: readonly number[]
|
|
767
|
+
readonly absoluteTimestampMs: number
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export interface UiOwnedIdentitySampleFanoutObservation {
|
|
771
|
+
readonly channelId: number
|
|
772
|
+
readonly sinkCount: number
|
|
773
|
+
readonly committedSinkCount: number
|
|
774
|
+
readonly committedSinkIds: readonly number[]
|
|
775
|
+
readonly uiSinkCommitCount: number
|
|
776
|
+
readonly jsToUiWriteLaneInvocationCount: number
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export interface UiOwnedIdentitySampleFanoutTransition {
|
|
780
|
+
readonly state: UiOwnedIdentitySampleFanoutState
|
|
781
|
+
readonly observation: UiOwnedIdentitySampleFanoutObservation
|
|
782
|
+
readonly orderedEffects: readonly UiOwnedIdentitySampleFanoutEffect[]
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
interface UiFrameTransactionCommon {
|
|
786
|
+
readonly lastCommittedTimestampMs: number | null
|
|
787
|
+
readonly committedFrameCount: number
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export type UiFrameTransactionState =
|
|
791
|
+
| (UiFrameTransactionCommon & { readonly kind: 'CLOSED' })
|
|
792
|
+
| (UiFrameTransactionCommon & {
|
|
793
|
+
readonly kind: 'OPEN'
|
|
794
|
+
readonly mode: 'LIVE' | 'MANUAL'
|
|
795
|
+
readonly absoluteTimestampMs: number
|
|
796
|
+
readonly mailboxDrainId: number
|
|
797
|
+
readonly expectedAttendeeDriverIds: readonly number[]
|
|
798
|
+
readonly visitedAttendeeDriverIds: readonly number[]
|
|
799
|
+
readonly revokedAttendeeDriverIds: readonly number[]
|
|
800
|
+
readonly steppedChannelIds: readonly number[]
|
|
801
|
+
readonly frozenInvocationOrder: readonly number[]
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
export interface UiManualDestinationCommitSlot extends ImmutableDestinationCommit {
|
|
805
|
+
readonly commitSlotId: number
|
|
806
|
+
readonly acknowledgementSlotId: number
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
export interface UiDestinationCommitAuthority {
|
|
810
|
+
readonly channelId: number
|
|
811
|
+
readonly sourceRecordId: number
|
|
812
|
+
readonly destinationRecordId: number
|
|
813
|
+
readonly direction: 'UI_TO_JS' | 'JS_TO_UI'
|
|
814
|
+
readonly transferLease: number
|
|
815
|
+
readonly releaseFenceSequence: number
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
export interface UiPreparedDestinationRecordState extends UiDestinationCommitAuthority {
|
|
819
|
+
readonly adoptedCommitSlotId: number | null
|
|
820
|
+
readonly appliedThroughAuthoritativeSequence: number
|
|
821
|
+
readonly ledgers: ValueHandoffDestinationLedgerState
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
export type UiDestinationCommitRefusalReason =
|
|
825
|
+
| 'envelope-not-open'
|
|
826
|
+
| 'missing-prepared-destination-record'
|
|
827
|
+
| 'missing-acknowledgement-slot'
|
|
828
|
+
| 'wrong-channel'
|
|
829
|
+
| 'wrong-source-record'
|
|
830
|
+
| 'wrong-destination-record'
|
|
831
|
+
| 'wrong-direction'
|
|
832
|
+
| 'wrong-transfer-lease'
|
|
833
|
+
| 'wrong-release-fence'
|
|
834
|
+
| 'ahead-of-sent-watermark'
|
|
835
|
+
| 'sent-watermark-regressed'
|
|
836
|
+
| 'prefix-not-contiguous'
|
|
837
|
+
|
|
838
|
+
export type UiDestinationCommitCanonicalRefusalReason = Exclude<
|
|
839
|
+
UiDestinationCommitRefusalReason,
|
|
840
|
+
'envelope-not-open' | 'missing-acknowledgement-slot'
|
|
841
|
+
>
|
|
842
|
+
|
|
843
|
+
export interface UiDestinationCommitCanonicalValidationRequest {
|
|
844
|
+
readonly commit: ImmutableDestinationCommit
|
|
845
|
+
readonly preparedDestinationRecords: readonly UiPreparedDestinationRecordState[]
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export type UiDestinationCommitCanonicalValidationOutcome =
|
|
849
|
+
| {
|
|
850
|
+
readonly kind: 'VALID'
|
|
851
|
+
readonly authority: UiPreparedDestinationRecordState
|
|
852
|
+
}
|
|
853
|
+
| {
|
|
854
|
+
readonly kind: 'REFUSED'
|
|
855
|
+
readonly authority: UiPreparedDestinationRecordState | null
|
|
856
|
+
readonly reason: UiDestinationCommitCanonicalRefusalReason
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const uiDestinationCommitCanonicalValidatorCapabilityBrand = Symbol(
|
|
860
|
+
'UiDestinationCommitCanonicalValidatorCapability',
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
export interface UiDestinationCommitCanonicalValidatorCapability {
|
|
864
|
+
readonly [uiDestinationCommitCanonicalValidatorCapabilityBrand]: true
|
|
865
|
+
readonly validate: typeof validateUiDestinationCommitWithCanonicalAuthority
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
export type UiDestinationCommitCanonicalValidationEffect =
|
|
869
|
+
| {
|
|
870
|
+
readonly kind: 'validate-ui-destination-commit-with-canonical-authority'
|
|
871
|
+
readonly root: 'LIVE'
|
|
872
|
+
readonly commitSlotId: number
|
|
873
|
+
readonly authority: UiPreparedDestinationRecordState
|
|
874
|
+
readonly validator: UiDestinationCommitCanonicalValidatorCapability
|
|
875
|
+
}
|
|
876
|
+
| {
|
|
877
|
+
readonly kind: 'validate-ui-destination-commit-with-canonical-authority'
|
|
878
|
+
readonly root: 'MANUAL'
|
|
879
|
+
readonly groupId: number
|
|
880
|
+
readonly commitSlotId: number
|
|
881
|
+
readonly authority: UiPreparedDestinationRecordState
|
|
882
|
+
readonly validator: UiDestinationCommitCanonicalValidatorCapability
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
export type UiDestinationCommitRefusalReportEffect =
|
|
886
|
+
| {
|
|
887
|
+
readonly kind: 'report-ui-destination-commit-refusal'
|
|
888
|
+
readonly root: 'LIVE'
|
|
889
|
+
readonly commitSlotId: number
|
|
890
|
+
readonly authority: UiPreparedDestinationRecordState | null
|
|
891
|
+
readonly reason: UiDestinationCommitRefusalReason
|
|
892
|
+
}
|
|
893
|
+
| {
|
|
894
|
+
readonly kind: 'report-ui-destination-commit-refusal'
|
|
895
|
+
readonly root: 'MANUAL'
|
|
896
|
+
readonly groupId: number
|
|
897
|
+
readonly commitSlotId: number
|
|
898
|
+
readonly authority: UiPreparedDestinationRecordState | null
|
|
899
|
+
readonly reason: UiDestinationCommitRefusalReason
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
export interface UiDestinationCommitAcknowledgementSlotState {
|
|
903
|
+
readonly acknowledgementSlotId: number
|
|
904
|
+
readonly commitSlotId: number
|
|
905
|
+
readonly state: 'EMPTY' | 'FILLED' | 'STAGED_IN_CURRENT_ENVELOPE'
|
|
906
|
+
readonly acknowledgement: DestinationCommitAcknowledgement | null
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export interface ManualFrameGroupState {
|
|
910
|
+
readonly groupId: number
|
|
911
|
+
readonly currentAbsoluteTimestampMs: number
|
|
912
|
+
readonly participantDriverIds: readonly number[]
|
|
913
|
+
readonly callbackRegistrationGenerations: readonly number[]
|
|
914
|
+
readonly pendingDestinationCommits: readonly UiManualDestinationCommitSlot[]
|
|
915
|
+
readonly consumedCommitSlotIds: readonly number[]
|
|
916
|
+
readonly armedForExplicitStep: boolean
|
|
917
|
+
readonly explicitStepCount: number
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
export interface UiValueChannelCoordinatorState {
|
|
921
|
+
readonly runnerDriverId: number | null
|
|
922
|
+
readonly runnerLeaseEpoch: number
|
|
923
|
+
readonly runnerAdmissionRequest: UiRunnerAdmissionRequestState | null
|
|
924
|
+
readonly nextRunnerAdmissionToken: number
|
|
925
|
+
readonly nextMailboxDrainId: number
|
|
926
|
+
readonly publicIdentityChannels: readonly UiPublicIdentityChannelRecord[]
|
|
927
|
+
readonly generatorExecutions: readonly UiGeneratorExecutionRecord[]
|
|
928
|
+
readonly dueWorkTasks: readonly UiDueWorkTaskRecord[]
|
|
929
|
+
readonly valueCompletionControllers: readonly UiValueCompletionControllerRecord[]
|
|
930
|
+
readonly directOperations: readonly UiDirectOperationRecord[]
|
|
931
|
+
readonly carrierControllers: readonly UiCarrierControllerRecord[]
|
|
932
|
+
readonly mailboxTurns: readonly UiMailboxTurnRecord[]
|
|
933
|
+
readonly hostSinks: readonly UiHostSinkRecord[]
|
|
934
|
+
readonly preparedDestinationRecords: readonly UiPreparedDestinationRecordState[]
|
|
935
|
+
readonly destinationCommitAcknowledgementSlots: readonly UiDestinationCommitAcknowledgementSlotState[]
|
|
936
|
+
readonly callbacks: readonly UiCallbackRegistrationState[]
|
|
937
|
+
readonly relays: readonly UiColdWorkRelayState[]
|
|
938
|
+
readonly identities: readonly UiIdentityFrameState[]
|
|
939
|
+
readonly transaction: UiFrameTransactionState
|
|
940
|
+
readonly manualGroups: readonly ManualFrameGroupState[]
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export interface UiValueChannelObservation {
|
|
944
|
+
readonly runnerDriverId: number | null
|
|
945
|
+
readonly runnerLeaseEpoch: number
|
|
946
|
+
readonly callbackCount: number
|
|
947
|
+
readonly callbackIdentityIds: readonly number[]
|
|
948
|
+
readonly callbackRegistrationGenerations: readonly number[]
|
|
949
|
+
readonly admittedDriverIds: readonly number[]
|
|
950
|
+
readonly relayCount: number
|
|
951
|
+
readonly unclaimedRelayCount: number
|
|
952
|
+
readonly identityCount: number
|
|
953
|
+
readonly totalIdentityStepCount: number
|
|
954
|
+
readonly totalHostCommitCount: number
|
|
955
|
+
readonly transactionKind: UiFrameTransactionState['kind']
|
|
956
|
+
readonly openTimestampMs: number | null
|
|
957
|
+
readonly expectedAttendeeCount: number
|
|
958
|
+
readonly visitedAttendeeCount: number
|
|
959
|
+
readonly revokedAttendeeCount: number
|
|
960
|
+
readonly lastCommittedTimestampMs: number | null
|
|
961
|
+
readonly committedFrameCount: number
|
|
962
|
+
readonly manualGroupCount: number
|
|
963
|
+
readonly armedManualGroupIds: readonly number[]
|
|
964
|
+
readonly pendingManualDestinationCommitCount: number
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
export type UiCoordinatorEvent =
|
|
968
|
+
| {
|
|
969
|
+
readonly kind: 'arm-cold-work-relay-at-command-edge'
|
|
970
|
+
readonly relayId: number
|
|
971
|
+
readonly targetDriverId: number
|
|
972
|
+
readonly commandSequence: number
|
|
973
|
+
readonly targetCallbackRegistrationGeneration: number
|
|
974
|
+
readonly relayLeaseEpoch: number
|
|
975
|
+
readonly lifetime: 'ONE_SHOT' | 'ONGOING_EXECUTION'
|
|
976
|
+
readonly armedAtTimestampMs: number
|
|
977
|
+
readonly commandReceipt: UiColdRelayCommandProductionTransition
|
|
978
|
+
}
|
|
979
|
+
| {
|
|
980
|
+
readonly kind: 'refresh-callback-observer'
|
|
981
|
+
readonly driverId: number
|
|
982
|
+
readonly callbackRegistrationGeneration: number
|
|
983
|
+
readonly observerRevision: number
|
|
984
|
+
}
|
|
985
|
+
| {
|
|
986
|
+
readonly kind: 'request-callback-activation'
|
|
987
|
+
readonly driverId: number
|
|
988
|
+
readonly callbackRegistrationGeneration: number
|
|
989
|
+
readonly requestedAtTimestampMs: number
|
|
990
|
+
readonly requestedRunnerLeaseEpoch: number
|
|
991
|
+
readonly admissionToken: number
|
|
992
|
+
readonly absoluteDeadlineMs: number
|
|
993
|
+
}
|
|
994
|
+
| {
|
|
995
|
+
readonly kind: 'invoke-live-callback'
|
|
996
|
+
readonly driverId: number
|
|
997
|
+
readonly callbackRegistrationGeneration: number
|
|
998
|
+
readonly callbackLeaseEpoch: number
|
|
999
|
+
readonly frame: UiCoordinatorFrameInfo
|
|
1000
|
+
}
|
|
1001
|
+
| {
|
|
1002
|
+
readonly kind: 'acknowledge-callback-admission'
|
|
1003
|
+
readonly candidateDriverId: number
|
|
1004
|
+
readonly callbackRegistrationGeneration: number
|
|
1005
|
+
readonly requestedRunnerLeaseEpoch: number
|
|
1006
|
+
readonly admissionToken: number
|
|
1007
|
+
}
|
|
1008
|
+
| {
|
|
1009
|
+
readonly kind: 'runner-admission-timeout'
|
|
1010
|
+
readonly candidateDriverId: number
|
|
1011
|
+
readonly callbackRegistrationGeneration: number
|
|
1012
|
+
readonly requestedRunnerLeaseEpoch: number
|
|
1013
|
+
readonly admissionToken: number
|
|
1014
|
+
readonly nowMs: number
|
|
1015
|
+
}
|
|
1016
|
+
| {
|
|
1017
|
+
readonly kind: 'retire-callback'
|
|
1018
|
+
readonly driverId: number
|
|
1019
|
+
readonly callbackRegistrationGeneration: number
|
|
1020
|
+
}
|
|
1021
|
+
| {
|
|
1022
|
+
readonly kind: 'dispose-callback'
|
|
1023
|
+
readonly driverId: number
|
|
1024
|
+
readonly callbackRegistrationGeneration: number
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
export interface UiManualFrameCrossing {
|
|
1028
|
+
readonly groupId: number
|
|
1029
|
+
readonly absoluteTimestampMs: number
|
|
1030
|
+
readonly orderedAttendeeDriverIds: readonly number[]
|
|
1031
|
+
readonly callbackRegistrationGenerations: readonly number[]
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
export interface UiManualParticipantInvocation {
|
|
1035
|
+
readonly groupId: number
|
|
1036
|
+
readonly absoluteTimestampMs: number
|
|
1037
|
+
readonly driverId: number
|
|
1038
|
+
readonly callbackRegistrationGeneration: number
|
|
1039
|
+
readonly invocationIndex: number
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
export interface UiManualParticipantRevocation {
|
|
1043
|
+
readonly groupId: number
|
|
1044
|
+
readonly absoluteTimestampMs: number
|
|
1045
|
+
readonly driverId: number
|
|
1046
|
+
readonly callbackRegistrationGeneration: number
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
export interface UiManualFrameCommitRequest {
|
|
1050
|
+
readonly groupId: number
|
|
1051
|
+
readonly absoluteTimestampMs: number
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export interface UiLiveDestinationCommitSlot extends ImmutableDestinationCommit {
|
|
1055
|
+
readonly commitSlotId: number
|
|
1056
|
+
readonly acknowledgementSlotId: number
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
export interface UiLiveSiblingEmission {
|
|
1060
|
+
readonly emissionSequence: number
|
|
1061
|
+
readonly emissionKind: 'FRAME_WRITE' | 'LIFECYCLE' | 'TARGET_BATCH_DEPENDENCY' | 'HOST'
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
export type UiLiveDestinationEnvelopeEntry =
|
|
1065
|
+
| {
|
|
1066
|
+
readonly kind: 'DESTINATION_COMMIT_ACKNOWLEDGEMENT'
|
|
1067
|
+
readonly acknowledgementSlotId: number
|
|
1068
|
+
readonly drainedThroughAuthoritativeSequence: number
|
|
1069
|
+
}
|
|
1070
|
+
| ({ readonly kind: 'SIBLING_EMISSION' } & UiLiveSiblingEmission)
|
|
1071
|
+
|
|
1072
|
+
export interface UiLiveDestinationEnvelopeState {
|
|
1073
|
+
readonly mailboxDrainId: number
|
|
1074
|
+
readonly state: 'OPEN' | 'SEALED'
|
|
1075
|
+
readonly entries: readonly UiLiveDestinationEnvelopeEntry[]
|
|
1076
|
+
readonly sealCount: number
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export interface UiLiveDestinationCommitConsumeState {
|
|
1080
|
+
readonly coordinator: UiValueChannelCoordinatorState
|
|
1081
|
+
readonly envelope: UiLiveDestinationEnvelopeState
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
export interface UiLiveDestinationCommitConsumeRequest {
|
|
1085
|
+
readonly driverId: number
|
|
1086
|
+
readonly callbackRegistrationGeneration: number
|
|
1087
|
+
readonly absoluteTimestampMs: number
|
|
1088
|
+
readonly commit: UiLiveDestinationCommitSlot
|
|
1089
|
+
readonly siblingEmissions: readonly UiLiveSiblingEmission[]
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
export type UiLiveDestinationCommitRefusalReason = UiDestinationCommitRefusalReason
|
|
1093
|
+
|
|
1094
|
+
export type UiLiveDestinationCommitConsumeOutcome =
|
|
1095
|
+
| {
|
|
1096
|
+
readonly kind: 'APPLIED'
|
|
1097
|
+
readonly commitSlotId: number
|
|
1098
|
+
readonly acknowledgement: DestinationCommitAcknowledgement
|
|
1099
|
+
}
|
|
1100
|
+
| {
|
|
1101
|
+
readonly kind: 'DUPLICATE_REACKNOWLEDGED'
|
|
1102
|
+
readonly commitSlotId: number
|
|
1103
|
+
readonly acknowledgement: DestinationCommitAcknowledgement
|
|
1104
|
+
}
|
|
1105
|
+
| {
|
|
1106
|
+
readonly kind: 'REFUSED'
|
|
1107
|
+
readonly commitSlotId: number
|
|
1108
|
+
readonly reason: UiLiveDestinationCommitRefusalReason
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
export type UiLiveDestinationCommitEffect =
|
|
1112
|
+
| {
|
|
1113
|
+
readonly kind: 'lookup-live-prepared-destination-record'
|
|
1114
|
+
readonly destinationRecordId: number
|
|
1115
|
+
}
|
|
1116
|
+
| UiDestinationCommitCanonicalValidationEffect
|
|
1117
|
+
| {
|
|
1118
|
+
readonly kind: 'adopt-live-immutable-destination-commit'
|
|
1119
|
+
readonly commitSlotId: number
|
|
1120
|
+
}
|
|
1121
|
+
| {
|
|
1122
|
+
readonly kind: 'apply-live-included-post-fence-ledger-entry-in-place'
|
|
1123
|
+
readonly commitSlotId: number
|
|
1124
|
+
readonly prefixIndex: number
|
|
1125
|
+
readonly destinationSlot: NormalizedRetainedWorkEntry['destinationSlot']
|
|
1126
|
+
readonly entry: NormalizedRetainedWorkEntry
|
|
1127
|
+
}
|
|
1128
|
+
| {
|
|
1129
|
+
readonly kind: 'fill-live-destination-acknowledgement-slot'
|
|
1130
|
+
readonly commitSlotId: number
|
|
1131
|
+
readonly acknowledgementSlotId: number
|
|
1132
|
+
}
|
|
1133
|
+
| {
|
|
1134
|
+
readonly kind: 'stage-live-destination-acknowledgement-in-current-envelope'
|
|
1135
|
+
readonly acknowledgementSlotId: number
|
|
1136
|
+
readonly mailboxDrainId: number
|
|
1137
|
+
}
|
|
1138
|
+
| UiDestinationCommitRefusalReportEffect
|
|
1139
|
+
| ({ readonly kind: 'emit-live-ui-sibling' } & UiLiveSiblingEmission)
|
|
1140
|
+
| {
|
|
1141
|
+
readonly kind: 'seal-live-ui-envelope-once'
|
|
1142
|
+
readonly mailboxDrainId: number
|
|
1143
|
+
readonly entryCount: number
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
export interface UiLiveDestinationCommitTransition {
|
|
1147
|
+
readonly state: UiLiveDestinationCommitConsumeState
|
|
1148
|
+
readonly outcome: UiLiveDestinationCommitConsumeOutcome
|
|
1149
|
+
readonly orderedEffects: readonly UiLiveDestinationCommitEffect[]
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
interface UiSharedManualIdentityTransferGapCommon {
|
|
1153
|
+
readonly groupId: number
|
|
1154
|
+
readonly channelId: number
|
|
1155
|
+
readonly absoluteTimestampMs: number
|
|
1156
|
+
readonly orderedAttendeeDriverIds: readonly [number, number]
|
|
1157
|
+
readonly callbackRegistrationGenerations: readonly [number, number]
|
|
1158
|
+
readonly transferLease: number
|
|
1159
|
+
readonly sourceRecordId: number
|
|
1160
|
+
readonly destinationRecordId: number
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
export type UiSharedManualIdentityTransferGapRequest =
|
|
1164
|
+
| (UiSharedManualIdentityTransferGapCommon & {
|
|
1165
|
+
readonly direction: 'UI_TO_JS'
|
|
1166
|
+
readonly ownershipAtBegin: 'UI_RELEASE_PENDING_TO_JS'
|
|
1167
|
+
readonly ownershipAtCommit: 'UI_RELEASED_TO_JS'
|
|
1168
|
+
})
|
|
1169
|
+
| (UiSharedManualIdentityTransferGapCommon & {
|
|
1170
|
+
readonly direction: 'JS_TO_UI'
|
|
1171
|
+
readonly ownershipAtBegin: 'JS_RELEASE_PENDING_TO_UI'
|
|
1172
|
+
readonly ownershipAtCommit: 'JS_RELEASED_TO_UI'
|
|
1173
|
+
})
|
|
1174
|
+
|
|
1175
|
+
export type UiSharedManualIdentityTransferGapEffect =
|
|
1176
|
+
| {
|
|
1177
|
+
readonly kind: 'begin-shared-manual-transfer-transaction'
|
|
1178
|
+
readonly groupId: number
|
|
1179
|
+
readonly channelId: number
|
|
1180
|
+
readonly absoluteTimestampMs: number
|
|
1181
|
+
readonly orderedAttendeeDriverIds: readonly [number, number]
|
|
1182
|
+
readonly callbackRegistrationGenerations: readonly [number, number]
|
|
1183
|
+
}
|
|
1184
|
+
| {
|
|
1185
|
+
readonly kind: 'invoke-shared-manual-transfer-participant'
|
|
1186
|
+
readonly groupId: number
|
|
1187
|
+
readonly driverId: number
|
|
1188
|
+
readonly callbackRegistrationGeneration: number
|
|
1189
|
+
readonly invocationIndex: 0 | 1
|
|
1190
|
+
readonly absoluteTimestampMs: number
|
|
1191
|
+
}
|
|
1192
|
+
| {
|
|
1193
|
+
readonly kind: 'bridge-shared-identity-transfer-gap'
|
|
1194
|
+
readonly channelId: number
|
|
1195
|
+
readonly direction: 'UI_TO_JS' | 'JS_TO_UI'
|
|
1196
|
+
readonly transferLease: number
|
|
1197
|
+
readonly sourceRecordId: number
|
|
1198
|
+
readonly destinationRecordId: number
|
|
1199
|
+
readonly absoluteTimestampMs: number
|
|
1200
|
+
}
|
|
1201
|
+
| {
|
|
1202
|
+
readonly kind: 'step-shared-manual-transfer-identity-once'
|
|
1203
|
+
readonly channelId: number
|
|
1204
|
+
readonly absoluteTimestampMs: number
|
|
1205
|
+
}
|
|
1206
|
+
| {
|
|
1207
|
+
readonly kind: 'commit-shared-manual-transfer-transaction-once'
|
|
1208
|
+
readonly groupId: number
|
|
1209
|
+
readonly absoluteTimestampMs: number
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
export interface UiSharedManualIdentityTransferGapTransition {
|
|
1213
|
+
readonly state: UiValueChannelCoordinatorState
|
|
1214
|
+
readonly observation: UiValueChannelObservation
|
|
1215
|
+
readonly orderedEffects: readonly UiSharedManualIdentityTransferGapEffect[]
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
export interface ManualFrameGroupStepRequest {
|
|
1219
|
+
readonly groupId: number
|
|
1220
|
+
readonly elapsedMs: number
|
|
1221
|
+
readonly invocationOrder?: readonly number[]
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
export interface LegacyManualDriverStepRequest {
|
|
1225
|
+
readonly groupId: number
|
|
1226
|
+
readonly driverId: number
|
|
1227
|
+
readonly elapsedMs: number
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
export type UiValueChannelEffect =
|
|
1231
|
+
| UiColdRelayEffect
|
|
1232
|
+
| UiDestinationCommitCanonicalValidationEffect
|
|
1233
|
+
| UiDestinationCommitRefusalReportEffect
|
|
1234
|
+
| {
|
|
1235
|
+
readonly kind: 'retain-stable-callback-registration'
|
|
1236
|
+
readonly driverId: number
|
|
1237
|
+
readonly callbackIdentityId: number
|
|
1238
|
+
readonly callbackRegistrationGeneration: number
|
|
1239
|
+
}
|
|
1240
|
+
| {
|
|
1241
|
+
readonly kind: 'defer-callback-admission-to-next-timestamp'
|
|
1242
|
+
readonly driverId: number
|
|
1243
|
+
readonly afterTimestampMs: number
|
|
1244
|
+
}
|
|
1245
|
+
| {
|
|
1246
|
+
readonly kind: 'admit-callback-lease'
|
|
1247
|
+
readonly driverId: number
|
|
1248
|
+
readonly callbackLeaseEpoch: number
|
|
1249
|
+
readonly absoluteTimestampMs: number
|
|
1250
|
+
}
|
|
1251
|
+
| {
|
|
1252
|
+
readonly kind: 'publish-actual-callback-invocation-admission'
|
|
1253
|
+
readonly driverId: number
|
|
1254
|
+
readonly callbackRegistrationGeneration: number
|
|
1255
|
+
readonly callbackLeaseEpoch: number
|
|
1256
|
+
}
|
|
1257
|
+
| {
|
|
1258
|
+
readonly kind: 'start-runner-admission-timer'
|
|
1259
|
+
readonly requestedRunnerLeaseEpoch: number
|
|
1260
|
+
readonly candidateDriverId: number
|
|
1261
|
+
readonly callbackRegistrationGeneration: number
|
|
1262
|
+
readonly admissionToken: number
|
|
1263
|
+
readonly absoluteDeadlineMs: number
|
|
1264
|
+
}
|
|
1265
|
+
| { readonly kind: 'cancel-runner-admission-timer'; readonly admissionToken: number }
|
|
1266
|
+
| {
|
|
1267
|
+
readonly kind: 'transfer-runner-lease-after-admission-acknowledgement'
|
|
1268
|
+
readonly fromDriverId: number | null
|
|
1269
|
+
readonly toDriverId: number
|
|
1270
|
+
readonly runnerLeaseEpoch: number
|
|
1271
|
+
}
|
|
1272
|
+
| {
|
|
1273
|
+
readonly kind: 'invalidate-runner-admission-candidate'
|
|
1274
|
+
readonly driverId: number
|
|
1275
|
+
readonly admissionToken: number
|
|
1276
|
+
}
|
|
1277
|
+
| {
|
|
1278
|
+
readonly kind: 'select-next-live-runner-candidate'
|
|
1279
|
+
readonly excludedDriverIds: readonly number[]
|
|
1280
|
+
readonly selectedDriverId: number
|
|
1281
|
+
}
|
|
1282
|
+
| {
|
|
1283
|
+
readonly kind: 'open-frame-transaction'
|
|
1284
|
+
readonly mode: 'LIVE' | 'MANUAL'
|
|
1285
|
+
readonly absoluteTimestampMs: number
|
|
1286
|
+
}
|
|
1287
|
+
| {
|
|
1288
|
+
readonly kind: 'claim-cold-work-relay'
|
|
1289
|
+
readonly relayId: number
|
|
1290
|
+
readonly driverId: number
|
|
1291
|
+
}
|
|
1292
|
+
| {
|
|
1293
|
+
readonly kind: 'peek-relayed-driver-state'
|
|
1294
|
+
readonly relayId: number
|
|
1295
|
+
readonly targetDriverId: number
|
|
1296
|
+
}
|
|
1297
|
+
| {
|
|
1298
|
+
readonly kind: 'step-cold-work-relay'
|
|
1299
|
+
readonly relayId: number
|
|
1300
|
+
readonly targetDriverId: number
|
|
1301
|
+
readonly absoluteTimestampMs: number
|
|
1302
|
+
}
|
|
1303
|
+
| {
|
|
1304
|
+
readonly kind: 'form-cold-work-relay-value-bearing-mailbox-prefix'
|
|
1305
|
+
readonly relayId: number
|
|
1306
|
+
readonly canonicalRecord: UiColdRelayCanonicalRecord
|
|
1307
|
+
readonly sourceEntry: UiColdWorkRelayValueBearingEntry
|
|
1308
|
+
readonly openTurn: UiColdWorkRelayMailboxPrefix
|
|
1309
|
+
}
|
|
1310
|
+
| { readonly kind: 'retain-ongoing-relay-until-target-claim'; readonly relayId: number }
|
|
1311
|
+
| { readonly kind: 'release-claimed-relay'; readonly relayId: number }
|
|
1312
|
+
| {
|
|
1313
|
+
readonly kind: 'step-shared-identity-once'
|
|
1314
|
+
readonly channelId: number
|
|
1315
|
+
readonly absoluteTimestampMs: number
|
|
1316
|
+
readonly deltaMs: number
|
|
1317
|
+
}
|
|
1318
|
+
| { readonly kind: 'record-attendance'; readonly driverId: number }
|
|
1319
|
+
| { readonly kind: 'record-synthetic-revocation-attendance'; readonly driverId: number }
|
|
1320
|
+
| { readonly kind: 'retire-callback-effective-next-timestamp'; readonly driverId: number }
|
|
1321
|
+
| { readonly kind: 'delete-disposed-callback-after-attendance'; readonly driverId: number }
|
|
1322
|
+
| { readonly kind: 'commit-frame-once'; readonly absoluteTimestampMs: number }
|
|
1323
|
+
| {
|
|
1324
|
+
readonly kind: 'advance-manual-group-clock-once'
|
|
1325
|
+
readonly groupId: number
|
|
1326
|
+
readonly absoluteTimestampMs: number
|
|
1327
|
+
}
|
|
1328
|
+
| {
|
|
1329
|
+
readonly kind: 'freeze-manual-attendee-batch'
|
|
1330
|
+
readonly groupId: number
|
|
1331
|
+
readonly orderedAttendeeDriverIds: readonly number[]
|
|
1332
|
+
}
|
|
1333
|
+
| {
|
|
1334
|
+
readonly kind: 'arm-manual-group-for-destination-commit'
|
|
1335
|
+
readonly groupId: number
|
|
1336
|
+
readonly commitSlotId: number
|
|
1337
|
+
}
|
|
1338
|
+
| {
|
|
1339
|
+
readonly kind: 'consume-manual-destination-commit-on-explicit-step'
|
|
1340
|
+
readonly groupId: number
|
|
1341
|
+
readonly commitSlotId: number
|
|
1342
|
+
}
|
|
1343
|
+
| {
|
|
1344
|
+
readonly kind: 'lookup-prepared-destination-record'
|
|
1345
|
+
readonly groupId: number
|
|
1346
|
+
readonly destinationRecordId: number
|
|
1347
|
+
}
|
|
1348
|
+
| {
|
|
1349
|
+
readonly kind: 'adopt-immutable-destination-commit'
|
|
1350
|
+
readonly groupId: number
|
|
1351
|
+
readonly commitSlotId: number
|
|
1352
|
+
}
|
|
1353
|
+
| {
|
|
1354
|
+
readonly kind: 'apply-included-post-fence-ledger-entry-in-place'
|
|
1355
|
+
readonly groupId: number
|
|
1356
|
+
readonly commitSlotId: number
|
|
1357
|
+
readonly prefixIndex: number
|
|
1358
|
+
readonly destinationSlot: NormalizedRetainedWorkEntry['destinationSlot']
|
|
1359
|
+
readonly entry: NormalizedRetainedWorkEntry
|
|
1360
|
+
}
|
|
1361
|
+
| {
|
|
1362
|
+
readonly kind: 'fill-prefilled-destination-acknowledgement-slot'
|
|
1363
|
+
readonly groupId: number
|
|
1364
|
+
readonly commitSlotId: number
|
|
1365
|
+
readonly acknowledgementSlotId: number
|
|
1366
|
+
}
|
|
1367
|
+
| {
|
|
1368
|
+
readonly kind: 'stage-destination-acknowledgement-in-current-envelope'
|
|
1369
|
+
readonly groupId: number
|
|
1370
|
+
readonly acknowledgementSlotId: number
|
|
1371
|
+
readonly mailboxDrainId: number
|
|
1372
|
+
}
|
|
1373
|
+
| {
|
|
1374
|
+
readonly kind: 'delegate-legacy-step-to-private-single-member-group'
|
|
1375
|
+
readonly groupId: number
|
|
1376
|
+
readonly driverId: number
|
|
1377
|
+
}
|
|
1378
|
+
| { readonly kind: 'already-committed-no-op'; readonly absoluteTimestampMs: number }
|
|
1379
|
+
|
|
1380
|
+
export type UiManualFrameRefusal =
|
|
1381
|
+
| {
|
|
1382
|
+
readonly kind: 'MISSING_ATTENDEES'
|
|
1383
|
+
readonly missingDriverIds: readonly number[]
|
|
1384
|
+
}
|
|
1385
|
+
| {
|
|
1386
|
+
readonly kind: 'LATE_ATTENDANCE'
|
|
1387
|
+
readonly driverId: number
|
|
1388
|
+
readonly alreadyCommittedTimestampMs: number
|
|
1389
|
+
}
|
|
1390
|
+
| {
|
|
1391
|
+
readonly kind: 'MULTI_MEMBER_GROUP_REQUIRES_GROUP_STEP'
|
|
1392
|
+
readonly groupId: number
|
|
1393
|
+
readonly participantCount: number
|
|
1394
|
+
}
|
|
1395
|
+
| {
|
|
1396
|
+
readonly kind: 'DESTINATION_COMMIT_REFUSED'
|
|
1397
|
+
readonly groupId: number
|
|
1398
|
+
readonly commitSlotId: number
|
|
1399
|
+
readonly authority: UiPreparedDestinationRecordState | null
|
|
1400
|
+
readonly reason: UiDestinationCommitRefusalReason
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
export interface UiValueChannelTransition {
|
|
1404
|
+
readonly state: UiValueChannelCoordinatorState
|
|
1405
|
+
readonly observation: UiValueChannelObservation
|
|
1406
|
+
readonly orderedEffects: readonly UiValueChannelEffect[]
|
|
1407
|
+
readonly refusal: UiManualFrameRefusal | null
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
export type UiPrivateTargetBatchDependencyEventKind =
|
|
1411
|
+
| 'RAW_RESOLVED'
|
|
1412
|
+
| 'VALUE_ACKNOWLEDGED'
|
|
1413
|
+
| 'PERMANENTLY_UNRESOLVED'
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* UI-owned private controls never borrow a public MotionValue identity outbox. The owning adapter
|
|
1417
|
+
* keeps the primitive sequence needed to prefill one dependency event into the transaction that is
|
|
1418
|
+
* already open for the live or manual frame root.
|
|
1419
|
+
*/
|
|
1420
|
+
export interface UiPrivateTargetBatchAdapterState {
|
|
1421
|
+
readonly controlId: number
|
|
1422
|
+
readonly batchId: number
|
|
1423
|
+
readonly dependencyId: number
|
|
1424
|
+
readonly nextDependencyEventSequence: number
|
|
1425
|
+
readonly stagedDependencyEventCount: number
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
export interface UiPrivateTargetBatchDependencyEventRequest {
|
|
1429
|
+
readonly controlId: number
|
|
1430
|
+
readonly batchId: number
|
|
1431
|
+
readonly dependencyId: number
|
|
1432
|
+
readonly kind: UiPrivateTargetBatchDependencyEventKind
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
export interface UiPrefilledPrivateTargetBatchDependencyEvent {
|
|
1436
|
+
readonly kind: 'TARGET_BATCH_DEPENDENCY_EVENT'
|
|
1437
|
+
readonly batchId: number
|
|
1438
|
+
readonly dependencyId: number
|
|
1439
|
+
readonly dependencyEventKind: UiPrivateTargetBatchDependencyEventKind
|
|
1440
|
+
readonly eventSequence: number
|
|
1441
|
+
readonly ownerControlId: number
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
export type UiPrivateTargetBatchDependencyEventEffect =
|
|
1445
|
+
| {
|
|
1446
|
+
readonly kind: 'PREFILL_PRIVATE_TARGET_BATCH_DEPENDENCY_EVENT'
|
|
1447
|
+
readonly transactionMode: 'LIVE' | 'MANUAL'
|
|
1448
|
+
readonly event: UiPrefilledPrivateTargetBatchDependencyEvent
|
|
1449
|
+
}
|
|
1450
|
+
| {
|
|
1451
|
+
readonly kind: 'ADVANCE_PRIVATE_TARGET_BATCH_EVENT_SEQUENCE'
|
|
1452
|
+
readonly controlId: number
|
|
1453
|
+
readonly eventSequence: number
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
export interface UiPrivateTargetBatchDependencyEventTransition {
|
|
1457
|
+
readonly adapter: UiPrivateTargetBatchAdapterState
|
|
1458
|
+
readonly orderedEffects: readonly UiPrivateTargetBatchDependencyEventEffect[]
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
export type UiRunnerAdmissionCandidateKind =
|
|
1462
|
+
| 'RUNNER_CANDIDATE'
|
|
1463
|
+
| 'ADMITTED'
|
|
1464
|
+
| 'EXPIRED'
|
|
1465
|
+
| 'DISPOSED'
|
|
1466
|
+
|
|
1467
|
+
export interface UiRunnerAdmissionCandidateState {
|
|
1468
|
+
readonly driverId: number
|
|
1469
|
+
readonly callbackRegistrationGeneration: number
|
|
1470
|
+
readonly callbackLeaseEpoch: number
|
|
1471
|
+
readonly requestedAtTimestampMs: number
|
|
1472
|
+
readonly absoluteAdmissionDeadlineMs: number
|
|
1473
|
+
readonly state: UiRunnerAdmissionCandidateKind
|
|
1474
|
+
readonly invocationObserved: boolean
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
export interface UiRunnerRetainedPassiveSinkState {
|
|
1478
|
+
readonly sinkId: number
|
|
1479
|
+
readonly driverId: number
|
|
1480
|
+
readonly handleId: number
|
|
1481
|
+
readonly bindingGeneration: number
|
|
1482
|
+
readonly key: string
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
export interface UiRunnerInstalledPreArmControlSlotState {
|
|
1486
|
+
readonly controlId: number
|
|
1487
|
+
readonly executionId: number
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
export interface UiRunnerRetainedCarrierReferenceState {
|
|
1491
|
+
readonly batchId: number
|
|
1492
|
+
readonly carrierId: number
|
|
1493
|
+
readonly identityId: number
|
|
1494
|
+
readonly authoredIndex: number
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
export interface UiRunnerRetainedChannelState {
|
|
1498
|
+
readonly channelId: number
|
|
1499
|
+
readonly passiveSinks: readonly UiRunnerRetainedPassiveSinkState[]
|
|
1500
|
+
readonly installedPreArmControlSlot: UiRunnerInstalledPreArmControlSlotState | null
|
|
1501
|
+
readonly physicalExecutions: readonly Extract<
|
|
1502
|
+
NormalizedRetainedWorkEntry,
|
|
1503
|
+
{ readonly kind: 'generator-execution' }
|
|
1504
|
+
>[]
|
|
1505
|
+
readonly dueWork: readonly Extract<
|
|
1506
|
+
NormalizedRetainedWorkEntry,
|
|
1507
|
+
{ readonly kind: 'update-task' | 'carrier-task' | 'direct-write' }
|
|
1508
|
+
>[]
|
|
1509
|
+
readonly valueCompletions: readonly Extract<
|
|
1510
|
+
NormalizedRetainedWorkEntry,
|
|
1511
|
+
{ readonly kind: 'value-completion' }
|
|
1512
|
+
>[]
|
|
1513
|
+
readonly batchDependencyEvents: readonly Extract<
|
|
1514
|
+
NormalizedRetainedWorkEntry,
|
|
1515
|
+
{ readonly kind: 'target-batch-dependency-event' }
|
|
1516
|
+
>[]
|
|
1517
|
+
readonly carrierReferences: readonly UiRunnerRetainedCarrierReferenceState[]
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
export interface UiRunnerRetainedWorkRuntime {
|
|
1521
|
+
reportFinalRunnerTimeoutRefusal(channelIds: readonly number[]): void
|
|
1522
|
+
stopInstalledPreArmSlot(channelId: number, slot: UiRunnerInstalledPreArmControlSlotState): void
|
|
1523
|
+
transferRetainedChannelWork(channel: UiRunnerRetainedChannelState): void
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
export interface UiRunnerAdmissionState {
|
|
1527
|
+
readonly runnerDriverId: number | null
|
|
1528
|
+
readonly runnerLeaseEpoch: number
|
|
1529
|
+
readonly candidates: readonly UiRunnerAdmissionCandidateState[]
|
|
1530
|
+
readonly retainedChannels: readonly UiRunnerRetainedChannelState[]
|
|
1531
|
+
readonly timeoutRefusalCount: number
|
|
1532
|
+
readonly retainedWorkTransferRequestCount: number
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
export type UiRunnerAdmissionEvent =
|
|
1536
|
+
| {
|
|
1537
|
+
readonly kind: 'runner-candidate-timeout'
|
|
1538
|
+
readonly driverId: number
|
|
1539
|
+
readonly callbackRegistrationGeneration: number
|
|
1540
|
+
readonly callbackLeaseEpoch: number
|
|
1541
|
+
readonly observedAtTimestampMs: number
|
|
1542
|
+
}
|
|
1543
|
+
| {
|
|
1544
|
+
readonly kind: 'invoke-runner-candidate'
|
|
1545
|
+
readonly driverId: number
|
|
1546
|
+
readonly callbackRegistrationGeneration: number
|
|
1547
|
+
readonly callbackLeaseEpoch: number
|
|
1548
|
+
readonly observedAtTimestampMs: number
|
|
1549
|
+
}
|
|
1550
|
+
| {
|
|
1551
|
+
readonly kind: 'dispose-runner-candidate'
|
|
1552
|
+
readonly driverId: number
|
|
1553
|
+
readonly callbackRegistrationGeneration: number
|
|
1554
|
+
readonly callbackLeaseEpoch: number
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
export type UiRunnerAdmissionEffect =
|
|
1558
|
+
| { readonly kind: 'EXPIRE_RUNNER_CANDIDATE'; readonly driverId: number }
|
|
1559
|
+
| { readonly kind: 'DISPOSE_RUNNER_CANDIDATE'; readonly driverId: number }
|
|
1560
|
+
| {
|
|
1561
|
+
readonly kind: 'SELECT_SUCCESSOR_RUNNER_CANDIDATE'
|
|
1562
|
+
readonly driverId: number
|
|
1563
|
+
readonly callbackLeaseEpoch: number
|
|
1564
|
+
}
|
|
1565
|
+
| {
|
|
1566
|
+
readonly kind: 'ADMIT_INVOKED_RUNNER_CANDIDATE'
|
|
1567
|
+
readonly driverId: number
|
|
1568
|
+
readonly callbackLeaseEpoch: number
|
|
1569
|
+
}
|
|
1570
|
+
| { readonly kind: 'IGNORE_STALE_RUNNER_EVENT'; readonly driverId: number }
|
|
1571
|
+
| {
|
|
1572
|
+
readonly kind: 'REPORT_FINAL_RUNNER_TIMEOUT_REFUSAL'
|
|
1573
|
+
readonly channelIds: readonly number[]
|
|
1574
|
+
}
|
|
1575
|
+
| {
|
|
1576
|
+
readonly kind: 'STOP_INSTALLED_PRE_ARM_SLOT_ONLY'
|
|
1577
|
+
readonly channelId: number
|
|
1578
|
+
readonly slot: UiRunnerInstalledPreArmControlSlotState
|
|
1579
|
+
}
|
|
1580
|
+
| {
|
|
1581
|
+
readonly kind: 'TRANSFER_RETAINED_CHANNEL_WORK'
|
|
1582
|
+
readonly channel: UiRunnerRetainedChannelState
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
export interface UiRunnerAdmissionTransition {
|
|
1586
|
+
readonly state: UiRunnerAdmissionState
|
|
1587
|
+
readonly orderedEffects: readonly UiRunnerAdmissionEffect[]
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
export interface UiRecursionExecutionSeed {
|
|
1591
|
+
readonly executionId: number
|
|
1592
|
+
readonly targetValue: number
|
|
1593
|
+
readonly firstSampleValue: number
|
|
1594
|
+
readonly commandSequence: number
|
|
1595
|
+
readonly completionSequence: number
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
export type UiRecursionExecutionPhase = 'RUNNING' | 'STOPPED' | 'COMPLETED'
|
|
1599
|
+
|
|
1600
|
+
export interface UiRecursionExecutionState extends UiRecursionExecutionSeed {
|
|
1601
|
+
readonly updateSequence: number
|
|
1602
|
+
readonly stopCount: number
|
|
1603
|
+
readonly phase: UiRecursionExecutionPhase
|
|
1604
|
+
readonly orphaned: boolean
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
export interface UiQueuedInstantCompletionRecursionEntry {
|
|
1608
|
+
readonly completionId: number
|
|
1609
|
+
readonly completionSequence: number
|
|
1610
|
+
readonly callbackSuccessor: UiRecursionExecutionSeed
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
export interface UiGuardedCancelListenerState {
|
|
1614
|
+
readonly listenerId: number
|
|
1615
|
+
readonly hasStartedSuccessor: boolean
|
|
1616
|
+
readonly successor: UiRecursionExecutionSeed
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* Primitive-only UI-channel state for the pin's synchronous listener-recursion seam. No callback,
|
|
1621
|
+
* host object, MotionValue, generator, or promise crosses this reducer boundary.
|
|
1622
|
+
*/
|
|
1623
|
+
export interface UiValueChannelRecursionState {
|
|
1624
|
+
readonly channelId: number
|
|
1625
|
+
readonly currentValue: number
|
|
1626
|
+
readonly publicControlSlotExecutionId: number | null
|
|
1627
|
+
readonly executions: readonly UiRecursionExecutionState[]
|
|
1628
|
+
readonly queuedInstantCompletions: readonly UiQueuedInstantCompletionRecursionEntry[]
|
|
1629
|
+
readonly guardedCancelListeners: readonly UiGuardedCancelListenerState[]
|
|
1630
|
+
readonly nextUpdateSequence: number
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
export type UiValueChannelRecursionEvent =
|
|
1634
|
+
| { readonly kind: 'drain-queued-instant-completions' }
|
|
1635
|
+
| { readonly kind: 'apply-recursive-outer-stop' }
|
|
1636
|
+
| { readonly kind: 'apply-recursive-outer-jump'; readonly value: number }
|
|
1637
|
+
| {
|
|
1638
|
+
readonly kind: 'apply-recursive-interrupting-start'
|
|
1639
|
+
readonly successor: UiRecursionExecutionSeed
|
|
1640
|
+
}
|
|
1641
|
+
| {
|
|
1642
|
+
readonly kind: 'sample-runnable-recursion-executions'
|
|
1643
|
+
readonly absoluteTimestampMs: number
|
|
1644
|
+
}
|
|
1645
|
+
| {
|
|
1646
|
+
readonly kind: 'install-recursion-successor'
|
|
1647
|
+
readonly successor: UiRecursionExecutionSeed
|
|
1648
|
+
}
|
|
1649
|
+
| {
|
|
1650
|
+
readonly kind: 'complete-recursion-execution'
|
|
1651
|
+
readonly executionId: number
|
|
1652
|
+
readonly completionSequence: number
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
export type UiValueChannelRecursionEffect =
|
|
1656
|
+
| {
|
|
1657
|
+
readonly kind: 'DELIVER_QUEUED_INSTANT_COMPLETION'
|
|
1658
|
+
readonly completionId: number
|
|
1659
|
+
readonly completionSequence: number
|
|
1660
|
+
}
|
|
1661
|
+
| {
|
|
1662
|
+
readonly kind: 'DELIVER_GUARDED_CANCEL_LISTENER'
|
|
1663
|
+
readonly listenerId: number
|
|
1664
|
+
readonly cancelDeliveryOrdinal: number
|
|
1665
|
+
}
|
|
1666
|
+
| {
|
|
1667
|
+
readonly kind: 'ISSUE_REENTRANT_START_COMMAND'
|
|
1668
|
+
readonly executionId: number
|
|
1669
|
+
readonly commandSequence: number
|
|
1670
|
+
readonly source: 'INSTANT_COMPLETION' | 'GUARDED_CANCEL_LISTENER'
|
|
1671
|
+
readonly sourceId: number
|
|
1672
|
+
}
|
|
1673
|
+
| {
|
|
1674
|
+
readonly kind: 'STOP_EXECUTION_IDEMPOTENT'
|
|
1675
|
+
readonly executionId: number
|
|
1676
|
+
readonly stopOrdinal: number
|
|
1677
|
+
readonly source: 'OUTER_OPERATION' | 'REENTRANT_START'
|
|
1678
|
+
}
|
|
1679
|
+
| {
|
|
1680
|
+
readonly kind: 'INSTALL_RECURSION_EXECUTION'
|
|
1681
|
+
readonly executionId: number
|
|
1682
|
+
readonly commandSequence: number
|
|
1683
|
+
readonly updateSequence: number
|
|
1684
|
+
readonly role:
|
|
1685
|
+
| 'COMPLETION_SUCCESSOR'
|
|
1686
|
+
| 'CANCEL_LISTENER_SUCCESSOR'
|
|
1687
|
+
| 'INTERRUPTING_SUCCESSOR'
|
|
1688
|
+
| 'LATER_LIVE_SUCCESSOR'
|
|
1689
|
+
}
|
|
1690
|
+
| {
|
|
1691
|
+
readonly kind: 'CLEAR_PUBLIC_CONTROL_SLOT_TOKEN_BLIND'
|
|
1692
|
+
readonly source: 'INSTANT_COMPLETION' | 'STOP_CONTINUATION' | 'VALUE_COMPLETION'
|
|
1693
|
+
readonly sourceId: number
|
|
1694
|
+
readonly clearedExecutionId: number | null
|
|
1695
|
+
}
|
|
1696
|
+
| { readonly kind: 'APPLY_OUTER_JUMP'; readonly value: number }
|
|
1697
|
+
| {
|
|
1698
|
+
readonly kind: 'SAMPLE_RECURSION_EXECUTION'
|
|
1699
|
+
readonly executionId: number
|
|
1700
|
+
readonly updateSequence: number
|
|
1701
|
+
readonly value: number
|
|
1702
|
+
readonly absoluteTimestampMs: number
|
|
1703
|
+
readonly orphaned: boolean
|
|
1704
|
+
}
|
|
1705
|
+
| {
|
|
1706
|
+
readonly kind: 'COMPLETE_RECURSION_EXECUTION'
|
|
1707
|
+
readonly executionId: number
|
|
1708
|
+
readonly completionSequence: number
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
export interface UiValueChannelRecursionTransition {
|
|
1712
|
+
readonly state: UiValueChannelRecursionState
|
|
1713
|
+
readonly orderedEffects: readonly UiValueChannelRecursionEffect[]
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
export type UiValueChannelOperation =
|
|
1717
|
+
| UiCoordinatorEvent['kind']
|
|
1718
|
+
| 'ui-begin-manual-frame'
|
|
1719
|
+
| 'ui-invoke-manual-participant'
|
|
1720
|
+
| 'ui-revoke-manual-participant'
|
|
1721
|
+
| 'ui-commit-manual-frame'
|
|
1722
|
+
| 'arm-manual-destination-commit'
|
|
1723
|
+
| 'step-manual-frame-group'
|
|
1724
|
+
| 'consume-live-ui-destination-commit'
|
|
1725
|
+
| 'produce-cold-relay-command-from-canonical-state'
|
|
1726
|
+
| 'validate-ui-destination-commit-with-canonical-authority'
|
|
1727
|
+
| 'observe-cold-relay-canonical-record'
|
|
1728
|
+
| 'step-shared-manual-identity-transfer-gap'
|
|
1729
|
+
| 'fanout-ui-owned-identity-sample'
|
|
1730
|
+
| 'step-legacy-manual-driver'
|
|
1731
|
+
| 'stage-private-target-batch-dependency-event'
|
|
1732
|
+
| UiRunnerAdmissionEvent['kind']
|
|
1733
|
+
| UiValueChannelRecursionEvent['kind']
|
|
1734
|
+
|
|
1735
|
+
export const UI_VALUE_CHANNEL_UNIMPLEMENTED =
|
|
1736
|
+
'NATIVE_MOTION_UI_VALUE_CHANNEL_UNIMPLEMENTED' as const
|
|
1737
|
+
|
|
1738
|
+
export class UiValueChannelUnimplementedError extends Error {
|
|
1739
|
+
readonly code = UI_VALUE_CHANNEL_UNIMPLEMENTED
|
|
1740
|
+
readonly operation: UiValueChannelOperation
|
|
1741
|
+
|
|
1742
|
+
constructor(operation: UiValueChannelOperation) {
|
|
1743
|
+
super(
|
|
1744
|
+
`native-motion: UI value-channel operation '${operation}' is unimplemented ` +
|
|
1745
|
+
'(REQ-DRIVER-034)',
|
|
1746
|
+
)
|
|
1747
|
+
this.name = 'UiValueChannelUnimplementedError'
|
|
1748
|
+
this.operation = operation
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
export function observeUiValueChannel(
|
|
1753
|
+
state: UiValueChannelCoordinatorState,
|
|
1754
|
+
): UiValueChannelObservation {
|
|
1755
|
+
let totalIdentityStepCount = 0
|
|
1756
|
+
let totalHostCommitCount = 0
|
|
1757
|
+
for (const identity of state.identities) {
|
|
1758
|
+
totalIdentityStepCount += identity.stepCount
|
|
1759
|
+
totalHostCommitCount += identity.hostCommitCount
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
let unclaimedRelayCount = 0
|
|
1763
|
+
for (const relay of state.relays) {
|
|
1764
|
+
if (relay.claimedAtTimestampMs === null) unclaimedRelayCount += 1
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
const admittedDriverIds: number[] = []
|
|
1768
|
+
for (const callback of state.callbacks) {
|
|
1769
|
+
if (callback.state === 'ADMITTED') admittedDriverIds.push(callback.driverId)
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
const armedManualGroupIds: number[] = []
|
|
1773
|
+
let pendingManualDestinationCommitCount = 0
|
|
1774
|
+
for (const group of state.manualGroups) {
|
|
1775
|
+
if (group.armedForExplicitStep) armedManualGroupIds.push(group.groupId)
|
|
1776
|
+
pendingManualDestinationCommitCount += group.pendingDestinationCommits.length
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
const transaction = state.transaction
|
|
1780
|
+
return {
|
|
1781
|
+
runnerDriverId: state.runnerDriverId,
|
|
1782
|
+
runnerLeaseEpoch: state.runnerLeaseEpoch,
|
|
1783
|
+
callbackCount: state.callbacks.length,
|
|
1784
|
+
callbackIdentityIds: state.callbacks.map((callback) => callback.callbackIdentityId),
|
|
1785
|
+
callbackRegistrationGenerations: state.callbacks.map(
|
|
1786
|
+
(callback) => callback.callbackRegistrationGeneration,
|
|
1787
|
+
),
|
|
1788
|
+
admittedDriverIds,
|
|
1789
|
+
relayCount: state.relays.length,
|
|
1790
|
+
unclaimedRelayCount,
|
|
1791
|
+
identityCount: state.identities.length,
|
|
1792
|
+
totalIdentityStepCount,
|
|
1793
|
+
totalHostCommitCount,
|
|
1794
|
+
transactionKind: transaction.kind,
|
|
1795
|
+
openTimestampMs: transaction.kind === 'OPEN' ? transaction.absoluteTimestampMs : null,
|
|
1796
|
+
expectedAttendeeCount:
|
|
1797
|
+
transaction.kind === 'OPEN' ? transaction.expectedAttendeeDriverIds.length : 0,
|
|
1798
|
+
visitedAttendeeCount:
|
|
1799
|
+
transaction.kind === 'OPEN' ? transaction.visitedAttendeeDriverIds.length : 0,
|
|
1800
|
+
revokedAttendeeCount:
|
|
1801
|
+
transaction.kind === 'OPEN' ? transaction.revokedAttendeeDriverIds.length : 0,
|
|
1802
|
+
lastCommittedTimestampMs: transaction.lastCommittedTimestampMs,
|
|
1803
|
+
committedFrameCount: transaction.committedFrameCount,
|
|
1804
|
+
manualGroupCount: state.manualGroups.length,
|
|
1805
|
+
armedManualGroupIds,
|
|
1806
|
+
pendingManualDestinationCommitCount,
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
export const UI_VALUE_CHANNEL_INVARIANT = 'NATIVE_MOTION_UI_VALUE_CHANNEL_INVARIANT' as const
|
|
1811
|
+
|
|
1812
|
+
export type UiValueChannelInvariant =
|
|
1813
|
+
| 'coordinator-event-kind-unrecognized'
|
|
1814
|
+
| 'runner-admission-event-kind-unrecognized'
|
|
1815
|
+
| 'recursion-event-kind-unrecognized'
|
|
1816
|
+
| 'cold-relay-command-kind-unrecognized'
|
|
1817
|
+
| 'cold-relay-source-record-missing'
|
|
1818
|
+
| 'cold-relay-mailbox-record-missing'
|
|
1819
|
+
| 'cold-relay-host-sink-missing'
|
|
1820
|
+
| 'cold-relay-target-callback-missing'
|
|
1821
|
+
| 'cold-relay-canonical-record-unknown'
|
|
1822
|
+
| 'callback-registration-missing'
|
|
1823
|
+
| 'callback-registration-conflict'
|
|
1824
|
+
| 'runner-admission-request-conflict'
|
|
1825
|
+
| 'manual-frame-group-missing'
|
|
1826
|
+
| 'frame-transaction-conflict'
|
|
1827
|
+
| 'frame-attendance-conflict'
|
|
1828
|
+
| 'owned-identity-channel-conflict'
|
|
1829
|
+
| 'private-target-batch-adapter-conflict'
|
|
1830
|
+
| 'private-target-batch-transaction-closed'
|
|
1831
|
+
| 'shared-transfer-identity-missing'
|
|
1832
|
+
|
|
1833
|
+
/** Typed fail-closed refusal for malformed, conflicting, or out-of-sequence UI-channel input. */
|
|
1834
|
+
export class UiValueChannelInvariantError extends Error {
|
|
1835
|
+
readonly code = UI_VALUE_CHANNEL_INVARIANT
|
|
1836
|
+
readonly invariant: UiValueChannelInvariant
|
|
1837
|
+
readonly operation: UiValueChannelOperation | null
|
|
1838
|
+
|
|
1839
|
+
constructor(invariant: UiValueChannelInvariant, operation?: UiValueChannelOperation) {
|
|
1840
|
+
super(
|
|
1841
|
+
`native-motion: UI value-channel invariant '${invariant}' was violated` +
|
|
1842
|
+
(operation === undefined ? '' : ` by operation '${operation}'`) +
|
|
1843
|
+
' (REQ-DRIVER-034)',
|
|
1844
|
+
)
|
|
1845
|
+
this.name = 'UiValueChannelInvariantError'
|
|
1846
|
+
this.invariant = invariant
|
|
1847
|
+
this.operation = operation ?? null
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
/** Bounded window a runner candidate has to produce its first actual invocation (two frames). */
|
|
1852
|
+
const UI_RUNNER_ADMISSION_WINDOW_MS = 32
|
|
1853
|
+
|
|
1854
|
+
// ——— Canonical cold-relay record store ———
|
|
1855
|
+
// The canonical record is a frozen brand-only handle; its fill state lives in this coordinator-
|
|
1856
|
+
// private store so the frozen record can be observed before and after its single command-edge
|
|
1857
|
+
// fill without ever being mutated itself.
|
|
1858
|
+
|
|
1859
|
+
interface UiColdRelayCanonicalRecordEntry {
|
|
1860
|
+
readonly recordId: number
|
|
1861
|
+
readonly authority: UiColdRelayCommandAuthority
|
|
1862
|
+
fillState: 'EMPTY' | 'FILLED_FROM_LIVE_RECORDS'
|
|
1863
|
+
sourceRecords: UiColdRelaySourceRecords | null
|
|
1864
|
+
deliverySlots: UiColdRelayDeliverySlots | null
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
const uiColdRelayCanonicalRecordIds = new WeakMap<UiColdRelayCanonicalRecord, number>()
|
|
1868
|
+
const uiColdRelayCanonicalRecordEntries = new Map<number, UiColdRelayCanonicalRecordEntry>()
|
|
1869
|
+
let nextUiColdRelayCanonicalRecordId = 1
|
|
1870
|
+
|
|
1871
|
+
function requireCanonicalRecordEntry(
|
|
1872
|
+
record: UiColdRelayCanonicalRecord,
|
|
1873
|
+
operation: UiValueChannelOperation,
|
|
1874
|
+
): UiColdRelayCanonicalRecordEntry {
|
|
1875
|
+
const recordId = uiColdRelayCanonicalRecordIds.get(record)
|
|
1876
|
+
const entry = recordId === undefined ? undefined : uiColdRelayCanonicalRecordEntries.get(recordId)
|
|
1877
|
+
if (recordId === undefined || entry === undefined) {
|
|
1878
|
+
throw new UiValueChannelInvariantError('cold-relay-canonical-record-unknown', operation)
|
|
1879
|
+
}
|
|
1880
|
+
return entry
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
function allocateCanonicalRecord(
|
|
1884
|
+
authority: UiColdRelayCommandAuthority,
|
|
1885
|
+
): UiColdRelayCanonicalRecord {
|
|
1886
|
+
const record = Object.freeze({}) as UiColdRelayCanonicalRecord
|
|
1887
|
+
const recordId = nextUiColdRelayCanonicalRecordId
|
|
1888
|
+
nextUiColdRelayCanonicalRecordId += 1
|
|
1889
|
+
uiColdRelayCanonicalRecordIds.set(record, recordId)
|
|
1890
|
+
uiColdRelayCanonicalRecordEntries.set(recordId, {
|
|
1891
|
+
recordId,
|
|
1892
|
+
authority,
|
|
1893
|
+
fillState: 'EMPTY',
|
|
1894
|
+
sourceRecords: null,
|
|
1895
|
+
deliverySlots: null,
|
|
1896
|
+
})
|
|
1897
|
+
return record
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
export function observeUiColdRelayCanonicalRecord(
|
|
1901
|
+
record: UiColdRelayCanonicalRecord,
|
|
1902
|
+
): UiColdRelayCanonicalRecordObservation {
|
|
1903
|
+
const entry = requireCanonicalRecordEntry(record, 'observe-cold-relay-canonical-record')
|
|
1904
|
+
if (entry.fillState === 'EMPTY') {
|
|
1905
|
+
return Object.freeze({
|
|
1906
|
+
recordId: entry.recordId,
|
|
1907
|
+
authority: entry.authority,
|
|
1908
|
+
fillState: 'EMPTY',
|
|
1909
|
+
sourceRecords: null,
|
|
1910
|
+
deliverySlots: null,
|
|
1911
|
+
}) as UiColdRelayCanonicalRecordObservation
|
|
1912
|
+
}
|
|
1913
|
+
const sourceRecords = entry.sourceRecords
|
|
1914
|
+
const deliverySlots = entry.deliverySlots
|
|
1915
|
+
if (sourceRecords === null || deliverySlots === null) {
|
|
1916
|
+
throw new UiValueChannelInvariantError(
|
|
1917
|
+
'cold-relay-canonical-record-unknown',
|
|
1918
|
+
'observe-cold-relay-canonical-record',
|
|
1919
|
+
)
|
|
1920
|
+
}
|
|
1921
|
+
return Object.freeze({
|
|
1922
|
+
recordId: entry.recordId,
|
|
1923
|
+
authority: entry.authority,
|
|
1924
|
+
fillState: 'FILLED_FROM_LIVE_RECORDS',
|
|
1925
|
+
sourceRecords,
|
|
1926
|
+
deliverySlots,
|
|
1927
|
+
}) as UiColdRelayCanonicalRecordObservation
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// ——— Cold-relay command production from canonical coordinator records ———
|
|
1931
|
+
|
|
1932
|
+
function requireMailboxRecord(
|
|
1933
|
+
state: UiValueChannelCoordinatorState,
|
|
1934
|
+
mailboxDrainId: number,
|
|
1935
|
+
): UiMailboxTurnRecord {
|
|
1936
|
+
const record = state.mailboxTurns.find((candidate) => candidate.mailboxDrainId === mailboxDrainId)
|
|
1937
|
+
if (record === undefined) {
|
|
1938
|
+
throw new UiValueChannelInvariantError(
|
|
1939
|
+
'cold-relay-mailbox-record-missing',
|
|
1940
|
+
'produce-cold-relay-command-from-canonical-state',
|
|
1941
|
+
)
|
|
1942
|
+
}
|
|
1943
|
+
return record
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
function requireHostSinkRecords(
|
|
1947
|
+
state: UiValueChannelCoordinatorState,
|
|
1948
|
+
hostSinkRecordIds: readonly number[],
|
|
1949
|
+
): readonly [UiHostSinkRecord] {
|
|
1950
|
+
const records = hostSinkRecordIds.map((hostSinkRecordId) => {
|
|
1951
|
+
const record = state.hostSinks.find(
|
|
1952
|
+
(candidate) => candidate.hostSinkRecordId === hostSinkRecordId,
|
|
1953
|
+
)
|
|
1954
|
+
if (record === undefined) {
|
|
1955
|
+
throw new UiValueChannelInvariantError(
|
|
1956
|
+
'cold-relay-host-sink-missing',
|
|
1957
|
+
'produce-cold-relay-command-from-canonical-state',
|
|
1958
|
+
)
|
|
1959
|
+
}
|
|
1960
|
+
return record
|
|
1961
|
+
})
|
|
1962
|
+
const first = records[0]
|
|
1963
|
+
if (first === undefined || records.length !== 1) {
|
|
1964
|
+
throw new UiValueChannelInvariantError(
|
|
1965
|
+
'cold-relay-host-sink-missing',
|
|
1966
|
+
'produce-cold-relay-command-from-canonical-state',
|
|
1967
|
+
)
|
|
1968
|
+
}
|
|
1969
|
+
return [first]
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
/** The relay target is the one registered callback that has not yet been admitted to a frame. */
|
|
1973
|
+
function requireColdTargetCallback(
|
|
1974
|
+
state: UiValueChannelCoordinatorState,
|
|
1975
|
+
): UiCallbackRegistrationState {
|
|
1976
|
+
const target = state.callbacks.find(
|
|
1977
|
+
(candidate) =>
|
|
1978
|
+
candidate.state === 'REGISTERED_INACTIVE' ||
|
|
1979
|
+
candidate.state === 'ACTIVATION_REQUESTED' ||
|
|
1980
|
+
candidate.state === 'RUNNER_CANDIDATE',
|
|
1981
|
+
)
|
|
1982
|
+
if (target === undefined) {
|
|
1983
|
+
throw new UiValueChannelInvariantError(
|
|
1984
|
+
'cold-relay-target-callback-missing',
|
|
1985
|
+
'produce-cold-relay-command-from-canonical-state',
|
|
1986
|
+
)
|
|
1987
|
+
}
|
|
1988
|
+
return target
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
function missingColdRelaySourceRecord(): never {
|
|
1992
|
+
throw new UiValueChannelInvariantError(
|
|
1993
|
+
'cold-relay-source-record-missing',
|
|
1994
|
+
'produce-cold-relay-command-from-canonical-state',
|
|
1995
|
+
)
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
export function produceUiColdRelayCommandFromCanonicalState(
|
|
1999
|
+
state: UiValueChannelCoordinatorState,
|
|
2000
|
+
request: UiColdRelayCommandProductionRequest,
|
|
2001
|
+
): UiColdRelayCommandProductionTransition {
|
|
2002
|
+
switch (request.kind) {
|
|
2003
|
+
case 'START_PUBLIC_IDENTITY_EXECUTION': {
|
|
2004
|
+
const primaryRecord = state.generatorExecutions.find(
|
|
2005
|
+
(
|
|
2006
|
+
candidate,
|
|
2007
|
+
): candidate is Extract<
|
|
2008
|
+
UiGeneratorExecutionRecord,
|
|
2009
|
+
{ readonly workClass: 'channel-ordinary-first-sample' }
|
|
2010
|
+
> =>
|
|
2011
|
+
candidate.workClass === 'channel-ordinary-first-sample' &&
|
|
2012
|
+
candidate.owner.kind === 'PUBLIC_IDENTITY' &&
|
|
2013
|
+
candidate.owner.channelId === request.channelId &&
|
|
2014
|
+
candidate.entry.executionId === request.executionId,
|
|
2015
|
+
)
|
|
2016
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2017
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2018
|
+
workClass: 'channel-ordinary-first-sample',
|
|
2019
|
+
destinationRecordId: request.destinationRecordId,
|
|
2020
|
+
owner: { kind: 'PUBLIC_IDENTITY', channelId: request.channelId },
|
|
2021
|
+
executionId: request.executionId,
|
|
2022
|
+
}
|
|
2023
|
+
const sourceRecords = Object.freeze({
|
|
2024
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2025
|
+
workClass: 'channel-ordinary-first-sample' as const,
|
|
2026
|
+
primaryRecord,
|
|
2027
|
+
callbackRegistrations: [requireColdTargetCallback(state)] as const,
|
|
2028
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2029
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2030
|
+
})
|
|
2031
|
+
return {
|
|
2032
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2033
|
+
state,
|
|
2034
|
+
commandAuthority,
|
|
2035
|
+
sourceRecords,
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
case 'START_PRIVATE_CONTROL_EXECUTION': {
|
|
2039
|
+
const primaryRecord = state.generatorExecutions.find(
|
|
2040
|
+
(
|
|
2041
|
+
candidate,
|
|
2042
|
+
): candidate is Extract<
|
|
2043
|
+
UiGeneratorExecutionRecord,
|
|
2044
|
+
{ readonly workClass: 'private-ordinary-first-sample' }
|
|
2045
|
+
> =>
|
|
2046
|
+
candidate.workClass === 'private-ordinary-first-sample' &&
|
|
2047
|
+
candidate.owner.kind === 'PRIVATE_CONTROL' &&
|
|
2048
|
+
candidate.owner.controlId === request.controlId &&
|
|
2049
|
+
candidate.entry.executionId === request.executionId,
|
|
2050
|
+
)
|
|
2051
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2052
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2053
|
+
workClass: 'private-ordinary-first-sample',
|
|
2054
|
+
destinationRecordId: request.destinationRecordId,
|
|
2055
|
+
owner: { kind: 'PRIVATE_CONTROL', controlId: request.controlId },
|
|
2056
|
+
executionId: request.executionId,
|
|
2057
|
+
}
|
|
2058
|
+
const sourceRecords = Object.freeze({
|
|
2059
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2060
|
+
workClass: 'private-ordinary-first-sample' as const,
|
|
2061
|
+
primaryRecord,
|
|
2062
|
+
callbackRegistrations: [] as const,
|
|
2063
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2064
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2065
|
+
})
|
|
2066
|
+
return {
|
|
2067
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2068
|
+
state,
|
|
2069
|
+
commandAuthority,
|
|
2070
|
+
sourceRecords,
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
case 'QUEUE_INSTANT_TARGET': {
|
|
2074
|
+
const primaryRecord = state.dueWorkTasks.find(
|
|
2075
|
+
(
|
|
2076
|
+
candidate,
|
|
2077
|
+
): candidate is Extract<UiDueWorkTaskRecord, { readonly workClass: 'instant-target' }> =>
|
|
2078
|
+
candidate.workClass === 'instant-target' &&
|
|
2079
|
+
candidate.entry.taskId === request.taskId &&
|
|
2080
|
+
candidate.entry.taskSequence === request.taskSequence,
|
|
2081
|
+
)
|
|
2082
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2083
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2084
|
+
workClass: 'instant-target',
|
|
2085
|
+
destinationRecordId: request.destinationRecordId,
|
|
2086
|
+
taskId: request.taskId,
|
|
2087
|
+
taskSequence: request.taskSequence,
|
|
2088
|
+
}
|
|
2089
|
+
const sourceRecords = Object.freeze({
|
|
2090
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2091
|
+
workClass: 'instant-target' as const,
|
|
2092
|
+
primaryRecord,
|
|
2093
|
+
callbackRegistrations: [requireColdTargetCallback(state)] as const,
|
|
2094
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2095
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2096
|
+
})
|
|
2097
|
+
return {
|
|
2098
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2099
|
+
state,
|
|
2100
|
+
commandAuthority,
|
|
2101
|
+
sourceRecords,
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
case 'QUEUE_EQUAL_TARGET_REASSERTION': {
|
|
2105
|
+
const primaryRecord = state.dueWorkTasks.find(
|
|
2106
|
+
(
|
|
2107
|
+
candidate,
|
|
2108
|
+
): candidate is Extract<
|
|
2109
|
+
UiDueWorkTaskRecord,
|
|
2110
|
+
{ readonly workClass: 'equal-target-reassertion' }
|
|
2111
|
+
> =>
|
|
2112
|
+
candidate.workClass === 'equal-target-reassertion' &&
|
|
2113
|
+
candidate.entry.taskId === request.taskId &&
|
|
2114
|
+
candidate.entry.taskSequence === request.taskSequence,
|
|
2115
|
+
)
|
|
2116
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2117
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2118
|
+
workClass: 'equal-target-reassertion',
|
|
2119
|
+
destinationRecordId: request.destinationRecordId,
|
|
2120
|
+
taskId: request.taskId,
|
|
2121
|
+
taskSequence: request.taskSequence,
|
|
2122
|
+
}
|
|
2123
|
+
const sourceRecords = Object.freeze({
|
|
2124
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2125
|
+
workClass: 'equal-target-reassertion' as const,
|
|
2126
|
+
primaryRecord,
|
|
2127
|
+
callbackRegistrations: [requireColdTargetCallback(state)] as const,
|
|
2128
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2129
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2130
|
+
})
|
|
2131
|
+
return {
|
|
2132
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2133
|
+
state,
|
|
2134
|
+
commandAuthority,
|
|
2135
|
+
sourceRecords,
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
case 'QUEUE_IDLE_WRITE': {
|
|
2139
|
+
const primaryRecord = state.dueWorkTasks.find(
|
|
2140
|
+
(
|
|
2141
|
+
candidate,
|
|
2142
|
+
): candidate is Extract<UiDueWorkTaskRecord, { readonly workClass: 'idle-write' }> =>
|
|
2143
|
+
candidate.workClass === 'idle-write' &&
|
|
2144
|
+
candidate.entry.taskId === request.taskId &&
|
|
2145
|
+
candidate.entry.taskSequence === request.taskSequence,
|
|
2146
|
+
)
|
|
2147
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2148
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2149
|
+
workClass: 'idle-write',
|
|
2150
|
+
destinationRecordId: request.destinationRecordId,
|
|
2151
|
+
taskId: request.taskId,
|
|
2152
|
+
taskSequence: request.taskSequence,
|
|
2153
|
+
}
|
|
2154
|
+
const sourceRecords = Object.freeze({
|
|
2155
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2156
|
+
workClass: 'idle-write' as const,
|
|
2157
|
+
primaryRecord,
|
|
2158
|
+
callbackRegistrations: [] as const,
|
|
2159
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2160
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2161
|
+
})
|
|
2162
|
+
return {
|
|
2163
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2164
|
+
state,
|
|
2165
|
+
commandAuthority,
|
|
2166
|
+
sourceRecords,
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
case 'ARM_INSTANT_COMPLETION': {
|
|
2170
|
+
const primaryRecord = state.valueCompletionControllers.find(
|
|
2171
|
+
(candidate) =>
|
|
2172
|
+
candidate.completionControllerId === request.completionControllerId &&
|
|
2173
|
+
candidate.entry.completionSequence === request.completionSequence,
|
|
2174
|
+
)
|
|
2175
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2176
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2177
|
+
workClass: 'instant-completion',
|
|
2178
|
+
destinationRecordId: request.destinationRecordId,
|
|
2179
|
+
completionSequence: request.completionSequence,
|
|
2180
|
+
completionControllerId: request.completionControllerId,
|
|
2181
|
+
}
|
|
2182
|
+
const sourceRecords = Object.freeze({
|
|
2183
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2184
|
+
workClass: 'instant-completion' as const,
|
|
2185
|
+
primaryRecord,
|
|
2186
|
+
callbackRegistrations: [requireColdTargetCallback(state)] as const,
|
|
2187
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2188
|
+
hostSinkRecords: [] as const,
|
|
2189
|
+
})
|
|
2190
|
+
return {
|
|
2191
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2192
|
+
state,
|
|
2193
|
+
commandAuthority,
|
|
2194
|
+
sourceRecords,
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
case 'APPLY_DIRECT_WRITE': {
|
|
2198
|
+
const primaryRecord = state.directOperations.find(
|
|
2199
|
+
(candidate) =>
|
|
2200
|
+
candidate.directOperationId === request.directOperationId &&
|
|
2201
|
+
candidate.directOperationEpoch === request.directOperationEpoch,
|
|
2202
|
+
)
|
|
2203
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2204
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2205
|
+
workClass: 'direct-write',
|
|
2206
|
+
destinationRecordId: request.destinationRecordId,
|
|
2207
|
+
directOperationId: request.directOperationId,
|
|
2208
|
+
directOperationEpoch: request.directOperationEpoch,
|
|
2209
|
+
}
|
|
2210
|
+
const sourceRecords = Object.freeze({
|
|
2211
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2212
|
+
workClass: 'direct-write' as const,
|
|
2213
|
+
primaryRecord,
|
|
2214
|
+
callbackRegistrations: [] as const,
|
|
2215
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2216
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2217
|
+
})
|
|
2218
|
+
return {
|
|
2219
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2220
|
+
state,
|
|
2221
|
+
commandAuthority,
|
|
2222
|
+
sourceRecords,
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
case 'QUEUE_TRANSITION_END_CARRIER': {
|
|
2226
|
+
const primaryRecord = state.carrierControllers.find(
|
|
2227
|
+
(
|
|
2228
|
+
candidate,
|
|
2229
|
+
): candidate is Extract<
|
|
2230
|
+
UiCarrierControllerRecord,
|
|
2231
|
+
{ readonly workClass: 'transition-end-carrier' }
|
|
2232
|
+
> =>
|
|
2233
|
+
candidate.workClass === 'transition-end-carrier' &&
|
|
2234
|
+
candidate.carrierControllerId === request.carrierControllerId &&
|
|
2235
|
+
candidate.entry.taskId === request.taskId,
|
|
2236
|
+
)
|
|
2237
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2238
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2239
|
+
workClass: 'transition-end-carrier',
|
|
2240
|
+
destinationRecordId: request.destinationRecordId,
|
|
2241
|
+
carrierControllerId: request.carrierControllerId,
|
|
2242
|
+
taskId: request.taskId,
|
|
2243
|
+
}
|
|
2244
|
+
const sourceRecords = Object.freeze({
|
|
2245
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2246
|
+
workClass: 'transition-end-carrier' as const,
|
|
2247
|
+
primaryRecord,
|
|
2248
|
+
callbackRegistrations: [] as const,
|
|
2249
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2250
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2251
|
+
})
|
|
2252
|
+
return {
|
|
2253
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2254
|
+
state,
|
|
2255
|
+
commandAuthority,
|
|
2256
|
+
sourceRecords,
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
case 'QUEUE_EXIT_CARRIER': {
|
|
2260
|
+
const primaryRecord = state.carrierControllers.find(
|
|
2261
|
+
(
|
|
2262
|
+
candidate,
|
|
2263
|
+
): candidate is Extract<
|
|
2264
|
+
UiCarrierControllerRecord,
|
|
2265
|
+
{ readonly workClass: 'exit-carrier' }
|
|
2266
|
+
> =>
|
|
2267
|
+
candidate.workClass === 'exit-carrier' &&
|
|
2268
|
+
candidate.carrierControllerId === request.carrierControllerId &&
|
|
2269
|
+
candidate.entry.taskId === request.taskId,
|
|
2270
|
+
)
|
|
2271
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2272
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2273
|
+
workClass: 'exit-carrier',
|
|
2274
|
+
destinationRecordId: request.destinationRecordId,
|
|
2275
|
+
carrierControllerId: request.carrierControllerId,
|
|
2276
|
+
taskId: request.taskId,
|
|
2277
|
+
}
|
|
2278
|
+
const sourceRecords = Object.freeze({
|
|
2279
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2280
|
+
workClass: 'exit-carrier' as const,
|
|
2281
|
+
primaryRecord,
|
|
2282
|
+
callbackRegistrations: [] as const,
|
|
2283
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2284
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2285
|
+
})
|
|
2286
|
+
return {
|
|
2287
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2288
|
+
state,
|
|
2289
|
+
commandAuthority,
|
|
2290
|
+
sourceRecords,
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
case 'QUEUE_PACKET_B_CARRIER': {
|
|
2294
|
+
const primaryRecord = state.carrierControllers.find(
|
|
2295
|
+
(
|
|
2296
|
+
candidate,
|
|
2297
|
+
): candidate is Extract<
|
|
2298
|
+
UiCarrierControllerRecord,
|
|
2299
|
+
{ readonly workClass: 'packet-b-carrier' }
|
|
2300
|
+
> =>
|
|
2301
|
+
candidate.workClass === 'packet-b-carrier' &&
|
|
2302
|
+
candidate.carrierControllerId === request.carrierControllerId &&
|
|
2303
|
+
candidate.entry.taskId === request.taskId,
|
|
2304
|
+
)
|
|
2305
|
+
if (primaryRecord === undefined) return missingColdRelaySourceRecord()
|
|
2306
|
+
const commandAuthority: UiColdRelayCommandAuthority = {
|
|
2307
|
+
workClass: 'packet-b-carrier',
|
|
2308
|
+
destinationRecordId: request.destinationRecordId,
|
|
2309
|
+
carrierControllerId: request.carrierControllerId,
|
|
2310
|
+
taskId: request.taskId,
|
|
2311
|
+
}
|
|
2312
|
+
const sourceRecords = Object.freeze({
|
|
2313
|
+
[uiColdRelaySourceRecordsBrand]: true as const,
|
|
2314
|
+
workClass: 'packet-b-carrier' as const,
|
|
2315
|
+
primaryRecord,
|
|
2316
|
+
callbackRegistrations: [] as const,
|
|
2317
|
+
mailboxRecord: requireMailboxRecord(state, primaryRecord.mailboxDrainId),
|
|
2318
|
+
hostSinkRecords: requireHostSinkRecords(state, primaryRecord.hostSinkRecordIds),
|
|
2319
|
+
})
|
|
2320
|
+
return {
|
|
2321
|
+
[uiColdRelayProducerReceiptBrand]: true,
|
|
2322
|
+
state,
|
|
2323
|
+
commandAuthority,
|
|
2324
|
+
sourceRecords,
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
default: {
|
|
2328
|
+
const exhaustive: never = request
|
|
2329
|
+
void exhaustive
|
|
2330
|
+
throw new UiValueChannelInvariantError('cold-relay-command-kind-unrecognized')
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// ——— Shared UI destination COMMIT canonical validator ———
|
|
2336
|
+
|
|
2337
|
+
export function validateUiDestinationCommitWithCanonicalAuthority(
|
|
2338
|
+
request: UiDestinationCommitCanonicalValidationRequest,
|
|
2339
|
+
): UiDestinationCommitCanonicalValidationOutcome {
|
|
2340
|
+
const { commit, preparedDestinationRecords } = request
|
|
2341
|
+
// The runtime keys prepared records by its own destination record id; a commit naming an
|
|
2342
|
+
// unknown destination id for a known channel still resolves the channel's authority so the
|
|
2343
|
+
// exact mismatched tuple field is diagnosed instead of a bare missing record.
|
|
2344
|
+
const record =
|
|
2345
|
+
preparedDestinationRecords.find(
|
|
2346
|
+
(candidate) => candidate.destinationRecordId === commit.destinationRecordId,
|
|
2347
|
+
) ?? preparedDestinationRecords.find((candidate) => candidate.channelId === commit.channelId)
|
|
2348
|
+
if (record === undefined) {
|
|
2349
|
+
return { kind: 'REFUSED', authority: null, reason: 'missing-prepared-destination-record' }
|
|
2350
|
+
}
|
|
2351
|
+
const reason = validateUiCommitAgainstPreparedRecord(record, commit)
|
|
2352
|
+
if (reason !== null) {
|
|
2353
|
+
return { kind: 'REFUSED', authority: record, reason }
|
|
2354
|
+
}
|
|
2355
|
+
return { kind: 'VALID', authority: record }
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
function validateUiCommitAgainstPreparedRecord(
|
|
2359
|
+
record: UiPreparedDestinationRecordState,
|
|
2360
|
+
commit: ImmutableDestinationCommit,
|
|
2361
|
+
): UiDestinationCommitCanonicalRefusalReason | null {
|
|
2362
|
+
if (commit.channelId !== record.channelId) return 'wrong-channel'
|
|
2363
|
+
if (commit.sourceRecordId !== record.sourceRecordId) return 'wrong-source-record'
|
|
2364
|
+
if (commit.destinationRecordId !== record.destinationRecordId) return 'wrong-destination-record'
|
|
2365
|
+
// The UI destination root only ever consumes JS→UI commits; a self-consistent UI→JS authority
|
|
2366
|
+
// is as refused here as a tuple mismatch.
|
|
2367
|
+
if (commit.direction !== record.direction || commit.direction !== 'JS_TO_UI') {
|
|
2368
|
+
return 'wrong-direction'
|
|
2369
|
+
}
|
|
2370
|
+
if (commit.transferLease !== record.transferLease) return 'wrong-transfer-lease'
|
|
2371
|
+
if (commit.releaseFenceSequence !== record.releaseFenceSequence) return 'wrong-release-fence'
|
|
2372
|
+
if (
|
|
2373
|
+
commit.includedPostFencePrefix.some(
|
|
2374
|
+
(entry) => entry.authoritativeSequence > commit.sentThroughAuthoritativeSequence,
|
|
2375
|
+
)
|
|
2376
|
+
) {
|
|
2377
|
+
return 'ahead-of-sent-watermark'
|
|
2378
|
+
}
|
|
2379
|
+
if (commit.sentThroughAuthoritativeSequence < record.appliedThroughAuthoritativeSequence) {
|
|
2380
|
+
return 'sent-watermark-regressed'
|
|
2381
|
+
}
|
|
2382
|
+
const newEntries = commit.includedPostFencePrefix.filter(
|
|
2383
|
+
(entry) => entry.authoritativeSequence > record.appliedThroughAuthoritativeSequence,
|
|
2384
|
+
)
|
|
2385
|
+
for (let index = 0; index < newEntries.length; index += 1) {
|
|
2386
|
+
const entry = newEntries[index]
|
|
2387
|
+
if (entry === undefined) break
|
|
2388
|
+
if (entry.authoritativeSequence !== record.appliedThroughAuthoritativeSequence + index + 1) {
|
|
2389
|
+
return 'prefix-not-contiguous'
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
return null
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
export const uiDestinationCommitCanonicalValidator: UiDestinationCommitCanonicalValidatorCapability =
|
|
2396
|
+
Object.freeze({
|
|
2397
|
+
[uiDestinationCommitCanonicalValidatorCapabilityBrand]: true as const,
|
|
2398
|
+
validate: validateUiDestinationCommitWithCanonicalAuthority,
|
|
2399
|
+
})
|
|
2400
|
+
|
|
2401
|
+
// ——— Destination ledger application (in-place at the commit consumption edge) ———
|
|
2402
|
+
|
|
2403
|
+
function createUiLedgerSlot(
|
|
2404
|
+
entry: NormalizedRetainedWorkEntry,
|
|
2405
|
+
): ValueHandoffDestinationLedgerState['slots'][number] {
|
|
2406
|
+
return {
|
|
2407
|
+
...entry.destinationSlot,
|
|
2408
|
+
entries: [entry],
|
|
2409
|
+
} as ValueHandoffDestinationLedgerState['slots'][number]
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
function appendUiLedgerEntry(
|
|
2413
|
+
slot: ValueHandoffDestinationLedgerState['slots'][number],
|
|
2414
|
+
entry: NormalizedRetainedWorkEntry,
|
|
2415
|
+
): ValueHandoffDestinationLedgerState['slots'][number] {
|
|
2416
|
+
if (slot.ledger !== entry.destinationSlot.ledger) return slot
|
|
2417
|
+
return {
|
|
2418
|
+
...slot,
|
|
2419
|
+
entries: [...slot.entries, entry],
|
|
2420
|
+
} as ValueHandoffDestinationLedgerState['slots'][number]
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
function applyEntriesToUiLedgers(
|
|
2424
|
+
ledgers: ValueHandoffDestinationLedgerState,
|
|
2425
|
+
entries: readonly NormalizedRetainedWorkEntry[],
|
|
2426
|
+
): ValueHandoffDestinationLedgerState {
|
|
2427
|
+
let slots = ledgers.slots
|
|
2428
|
+
for (const entry of entries) {
|
|
2429
|
+
const index = slots.findIndex(
|
|
2430
|
+
(slot) =>
|
|
2431
|
+
slot.ledger === entry.destinationSlot.ledger &&
|
|
2432
|
+
slot.slotId === entry.destinationSlot.slotId,
|
|
2433
|
+
)
|
|
2434
|
+
if (index < 0) {
|
|
2435
|
+
slots = [...slots, createUiLedgerSlot(entry)]
|
|
2436
|
+
continue
|
|
2437
|
+
}
|
|
2438
|
+
const slot = slots[index]
|
|
2439
|
+
if (slot === undefined) continue
|
|
2440
|
+
slots = [...slots.slice(0, index), appendUiLedgerEntry(slot, entry), ...slots.slice(index + 1)]
|
|
2441
|
+
}
|
|
2442
|
+
return { slots }
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
// ——— Coordinator transition helpers ———
|
|
2446
|
+
|
|
2447
|
+
function uiTransition(
|
|
2448
|
+
state: UiValueChannelCoordinatorState,
|
|
2449
|
+
orderedEffects: readonly UiValueChannelEffect[],
|
|
2450
|
+
refusal: UiManualFrameRefusal | null = null,
|
|
2451
|
+
): UiValueChannelTransition {
|
|
2452
|
+
return { state, observation: observeUiValueChannel(state), orderedEffects, refusal }
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
function requireCallback(
|
|
2456
|
+
state: UiValueChannelCoordinatorState,
|
|
2457
|
+
driverId: number,
|
|
2458
|
+
operation: UiValueChannelOperation,
|
|
2459
|
+
): UiCallbackRegistrationState {
|
|
2460
|
+
const callback = state.callbacks.find((candidate) => candidate.driverId === driverId)
|
|
2461
|
+
if (callback === undefined) {
|
|
2462
|
+
throw new UiValueChannelInvariantError('callback-registration-missing', operation)
|
|
2463
|
+
}
|
|
2464
|
+
return callback
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
function replaceCallback(
|
|
2468
|
+
state: UiValueChannelCoordinatorState,
|
|
2469
|
+
replacement: UiCallbackRegistrationState,
|
|
2470
|
+
): UiValueChannelCoordinatorState {
|
|
2471
|
+
return {
|
|
2472
|
+
...state,
|
|
2473
|
+
callbacks: state.callbacks.map((candidate) =>
|
|
2474
|
+
candidate.driverId === replacement.driverId ? replacement : candidate,
|
|
2475
|
+
),
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
function requireManualGroup(
|
|
2480
|
+
state: UiValueChannelCoordinatorState,
|
|
2481
|
+
groupId: number,
|
|
2482
|
+
operation: UiValueChannelOperation,
|
|
2483
|
+
): ManualFrameGroupState {
|
|
2484
|
+
const group = state.manualGroups.find((candidate) => candidate.groupId === groupId)
|
|
2485
|
+
if (group === undefined) {
|
|
2486
|
+
throw new UiValueChannelInvariantError('manual-frame-group-missing', operation)
|
|
2487
|
+
}
|
|
2488
|
+
return group
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
function replaceManualGroup(
|
|
2492
|
+
state: UiValueChannelCoordinatorState,
|
|
2493
|
+
replacement: ManualFrameGroupState,
|
|
2494
|
+
): UiValueChannelCoordinatorState {
|
|
2495
|
+
return {
|
|
2496
|
+
...state,
|
|
2497
|
+
manualGroups: state.manualGroups.map((candidate) =>
|
|
2498
|
+
candidate.groupId === replacement.groupId ? replacement : candidate,
|
|
2499
|
+
),
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
// ——— Frame transaction stepping ———
|
|
2504
|
+
|
|
2505
|
+
function stepIdentitiesOnce(
|
|
2506
|
+
state: UiValueChannelCoordinatorState,
|
|
2507
|
+
absoluteTimestampMs: number,
|
|
2508
|
+
effects: UiValueChannelEffect[],
|
|
2509
|
+
): UiValueChannelCoordinatorState {
|
|
2510
|
+
const steppedChannelIds: number[] = []
|
|
2511
|
+
const identities = state.identities.map((identity) => {
|
|
2512
|
+
steppedChannelIds.push(identity.channelId)
|
|
2513
|
+
const deltaMs =
|
|
2514
|
+
identity.lastAbsoluteTimestampMs === null
|
|
2515
|
+
? 0
|
|
2516
|
+
: absoluteTimestampMs - identity.lastAbsoluteTimestampMs
|
|
2517
|
+
effects.push({
|
|
2518
|
+
kind: 'step-shared-identity-once',
|
|
2519
|
+
channelId: identity.channelId,
|
|
2520
|
+
absoluteTimestampMs,
|
|
2521
|
+
deltaMs,
|
|
2522
|
+
})
|
|
2523
|
+
return {
|
|
2524
|
+
...identity,
|
|
2525
|
+
lastAbsoluteTimestampMs: absoluteTimestampMs,
|
|
2526
|
+
stepCount: identity.stepCount + 1,
|
|
2527
|
+
}
|
|
2528
|
+
})
|
|
2529
|
+
const transaction = state.transaction
|
|
2530
|
+
if (transaction.kind !== 'OPEN') {
|
|
2531
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', 'invoke-live-callback')
|
|
2532
|
+
}
|
|
2533
|
+
return {
|
|
2534
|
+
...state,
|
|
2535
|
+
identities,
|
|
2536
|
+
transaction: { ...transaction, steppedChannelIds },
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
function recordFrameAttendance(
|
|
2541
|
+
state: UiValueChannelCoordinatorState,
|
|
2542
|
+
driverId: number,
|
|
2543
|
+
effects: UiValueChannelEffect[],
|
|
2544
|
+
): UiValueChannelCoordinatorState {
|
|
2545
|
+
const transaction = state.transaction
|
|
2546
|
+
if (transaction.kind !== 'OPEN') {
|
|
2547
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', 'invoke-live-callback')
|
|
2548
|
+
}
|
|
2549
|
+
effects.push({ kind: 'record-attendance', driverId })
|
|
2550
|
+
let next: UiValueChannelCoordinatorState = {
|
|
2551
|
+
...state,
|
|
2552
|
+
transaction: {
|
|
2553
|
+
...transaction,
|
|
2554
|
+
visitedAttendeeDriverIds: [...transaction.visitedAttendeeDriverIds, driverId],
|
|
2555
|
+
},
|
|
2556
|
+
callbacks: state.callbacks.map((candidate) =>
|
|
2557
|
+
candidate.driverId === driverId
|
|
2558
|
+
? { ...candidate, lastInvocationTimestampMs: transaction.absoluteTimestampMs }
|
|
2559
|
+
: candidate,
|
|
2560
|
+
),
|
|
2561
|
+
}
|
|
2562
|
+
if (transaction.steppedChannelIds.length === 0) {
|
|
2563
|
+
next = stepIdentitiesOnce(next, transaction.absoluteTimestampMs, effects)
|
|
2564
|
+
}
|
|
2565
|
+
return next
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
function commitFrameIfAttendanceComplete(
|
|
2569
|
+
state: UiValueChannelCoordinatorState,
|
|
2570
|
+
effects: UiValueChannelEffect[],
|
|
2571
|
+
): UiValueChannelCoordinatorState {
|
|
2572
|
+
const transaction = state.transaction
|
|
2573
|
+
if (transaction.kind !== 'OPEN') return state
|
|
2574
|
+
const complete = transaction.expectedAttendeeDriverIds.every(
|
|
2575
|
+
(driverId) =>
|
|
2576
|
+
transaction.visitedAttendeeDriverIds.includes(driverId) ||
|
|
2577
|
+
transaction.revokedAttendeeDriverIds.includes(driverId),
|
|
2578
|
+
)
|
|
2579
|
+
if (!complete) return state
|
|
2580
|
+
effects.push({
|
|
2581
|
+
kind: 'commit-frame-once',
|
|
2582
|
+
absoluteTimestampMs: transaction.absoluteTimestampMs,
|
|
2583
|
+
})
|
|
2584
|
+
return {
|
|
2585
|
+
...state,
|
|
2586
|
+
identities: state.identities.map((identity) => ({
|
|
2587
|
+
...identity,
|
|
2588
|
+
hostCommitCount: identity.hostCommitCount + 1,
|
|
2589
|
+
})),
|
|
2590
|
+
transaction: {
|
|
2591
|
+
kind: 'CLOSED',
|
|
2592
|
+
lastCommittedTimestampMs: transaction.absoluteTimestampMs,
|
|
2593
|
+
committedFrameCount: transaction.committedFrameCount + 1,
|
|
2594
|
+
},
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
// ——— Cold-work relay stepping ———
|
|
2599
|
+
|
|
2600
|
+
function freezeSlotArrayInPlace<Slot>(slots: readonly Slot[]): readonly Slot[] {
|
|
2601
|
+
return Object.freeze(slots)
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
function buildDeliverySlots(sourceRecords: UiColdRelaySourceRecords): UiColdRelayDeliverySlots {
|
|
2605
|
+
const envelope = sourceRecords.mailboxRecord.envelope
|
|
2606
|
+
const hostSlots = Object.freeze(
|
|
2607
|
+
sourceRecords.hostSinkRecords.map((record) => record.slot),
|
|
2608
|
+
) as readonly [UiColdRelayHostCommitResultSlot]
|
|
2609
|
+
switch (sourceRecords.workClass) {
|
|
2610
|
+
case 'channel-ordinary-first-sample':
|
|
2611
|
+
case 'private-ordinary-first-sample': {
|
|
2612
|
+
const primary = sourceRecords.primaryRecord
|
|
2613
|
+
return Object.freeze({
|
|
2614
|
+
workClass: sourceRecords.workClass,
|
|
2615
|
+
envelope,
|
|
2616
|
+
writeSlots: Object.freeze([primary.writeSlot] as const),
|
|
2617
|
+
callbackSlots: freezeSlotArrayInPlace(primary.callbackSlots),
|
|
2618
|
+
carrierSlots: Object.freeze([] as const),
|
|
2619
|
+
hostSlots,
|
|
2620
|
+
}) as UiColdRelayDeliverySlots
|
|
2621
|
+
}
|
|
2622
|
+
case 'instant-target':
|
|
2623
|
+
case 'equal-target-reassertion':
|
|
2624
|
+
case 'idle-write': {
|
|
2625
|
+
const primary = sourceRecords.primaryRecord
|
|
2626
|
+
// The preallocated task callback buffer is compacted in place at this single command-edge
|
|
2627
|
+
// fill: instant/equal deliveries keep only the tokenless transition-on-update slots (the
|
|
2628
|
+
// animation-complete delivery belongs to the value-completion controller), while an idle
|
|
2629
|
+
// direct write delivers no task callbacks at all.
|
|
2630
|
+
const callbackSlots = primary.callbackSlots as Extract<
|
|
2631
|
+
UiColdRelayCallbackResultSlot,
|
|
2632
|
+
{ readonly kind: 'TASK_CALLBACK' }
|
|
2633
|
+
>[]
|
|
2634
|
+
let retainedCount = 0
|
|
2635
|
+
if (sourceRecords.workClass !== 'idle-write') {
|
|
2636
|
+
for (const slot of callbackSlots) {
|
|
2637
|
+
if (slot.callback.callbackKind !== 'transition-on-update') continue
|
|
2638
|
+
callbackSlots[retainedCount] = slot
|
|
2639
|
+
retainedCount += 1
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
callbackSlots.length = retainedCount
|
|
2643
|
+
return Object.freeze({
|
|
2644
|
+
workClass: sourceRecords.workClass,
|
|
2645
|
+
envelope,
|
|
2646
|
+
writeSlots: Object.freeze([primary.writeSlot] as const),
|
|
2647
|
+
callbackSlots: Object.freeze(callbackSlots),
|
|
2648
|
+
carrierSlots: Object.freeze([] as const),
|
|
2649
|
+
hostSlots,
|
|
2650
|
+
}) as UiColdRelayDeliverySlots
|
|
2651
|
+
}
|
|
2652
|
+
case 'instant-completion': {
|
|
2653
|
+
const primary = sourceRecords.primaryRecord
|
|
2654
|
+
return Object.freeze({
|
|
2655
|
+
workClass: 'instant-completion' as const,
|
|
2656
|
+
envelope,
|
|
2657
|
+
writeSlots: Object.freeze([] as const),
|
|
2658
|
+
callbackSlots: Object.freeze([primary.callbackSlot] as const),
|
|
2659
|
+
carrierSlots: Object.freeze([] as const),
|
|
2660
|
+
hostSlots: Object.freeze([] as const),
|
|
2661
|
+
}) as UiColdRelayDeliverySlots
|
|
2662
|
+
}
|
|
2663
|
+
case 'direct-write': {
|
|
2664
|
+
const primary = sourceRecords.primaryRecord
|
|
2665
|
+
return Object.freeze({
|
|
2666
|
+
workClass: 'direct-write' as const,
|
|
2667
|
+
envelope,
|
|
2668
|
+
writeSlots: Object.freeze([primary.writeSlot] as const),
|
|
2669
|
+
callbackSlots: Object.freeze([] as const),
|
|
2670
|
+
carrierSlots: Object.freeze([] as const),
|
|
2671
|
+
hostSlots,
|
|
2672
|
+
}) as UiColdRelayDeliverySlots
|
|
2673
|
+
}
|
|
2674
|
+
case 'transition-end-carrier':
|
|
2675
|
+
case 'exit-carrier':
|
|
2676
|
+
case 'packet-b-carrier': {
|
|
2677
|
+
const primary = sourceRecords.primaryRecord
|
|
2678
|
+
return Object.freeze({
|
|
2679
|
+
workClass: sourceRecords.workClass,
|
|
2680
|
+
envelope,
|
|
2681
|
+
writeSlots: Object.freeze([] as const),
|
|
2682
|
+
callbackSlots: Object.freeze([] as const),
|
|
2683
|
+
carrierSlots: Object.freeze([primary.carrierSlot] as const),
|
|
2684
|
+
hostSlots,
|
|
2685
|
+
}) as UiColdRelayDeliverySlots
|
|
2686
|
+
}
|
|
2687
|
+
default: {
|
|
2688
|
+
const exhaustive: never = sourceRecords
|
|
2689
|
+
void exhaustive
|
|
2690
|
+
throw new UiValueChannelInvariantError('cold-relay-command-kind-unrecognized')
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
interface UiRelayStepResult {
|
|
2696
|
+
readonly state: UiValueChannelCoordinatorState
|
|
2697
|
+
readonly relay: UiColdWorkRelayState
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
/** Fill and apply the canonical record exactly once, forming the value-bearing prefix with it. */
|
|
2701
|
+
function fillAndApplyCanonicalRelayRecord(
|
|
2702
|
+
state: UiValueChannelCoordinatorState,
|
|
2703
|
+
relay: UiColdWorkRelayState,
|
|
2704
|
+
effects: UiValueChannelEffect[],
|
|
2705
|
+
): UiRelayStepResult {
|
|
2706
|
+
const entry = requireCanonicalRecordEntry(relay.canonicalRecord, 'invoke-live-callback')
|
|
2707
|
+
let nextRelay = relay
|
|
2708
|
+
if (entry.fillState === 'EMPTY') {
|
|
2709
|
+
const sourceRecords = relay.commandReceipt.sourceRecords
|
|
2710
|
+
entry.sourceRecords = sourceRecords
|
|
2711
|
+
entry.deliverySlots = buildDeliverySlots(sourceRecords)
|
|
2712
|
+
entry.fillState = 'FILLED_FROM_LIVE_RECORDS'
|
|
2713
|
+
effects.push({
|
|
2714
|
+
kind: 'fill-canonical-cold-relay-record-from-live-records',
|
|
2715
|
+
relayId: relay.relayId,
|
|
2716
|
+
canonicalRecord: relay.canonicalRecord,
|
|
2717
|
+
sourceRecords,
|
|
2718
|
+
})
|
|
2719
|
+
effects.push({
|
|
2720
|
+
kind: 'apply-canonical-cold-relay-record',
|
|
2721
|
+
relayId: relay.relayId,
|
|
2722
|
+
targetDriverId: relay.targetDriverId,
|
|
2723
|
+
canonicalRecord: relay.canonicalRecord,
|
|
2724
|
+
})
|
|
2725
|
+
const primaryRecord = sourceRecords.primaryRecord
|
|
2726
|
+
if (sourceRecords.workClass === 'channel-ordinary-first-sample') {
|
|
2727
|
+
const valueBearingEntry = (
|
|
2728
|
+
primaryRecord as Extract<
|
|
2729
|
+
UiGeneratorExecutionRecord,
|
|
2730
|
+
{ readonly workClass: 'channel-ordinary-first-sample' }
|
|
2731
|
+
>
|
|
2732
|
+
).valueBearingEntry
|
|
2733
|
+
nextRelay = { ...nextRelay, pendingValueBearingEntry: valueBearingEntry }
|
|
2734
|
+
const mailboxDrainId = state.nextMailboxDrainId
|
|
2735
|
+
const valueWriteEntry = Object.freeze({
|
|
2736
|
+
kind: 'VALUE_WRITE' as const,
|
|
2737
|
+
entryId: valueBearingEntry.valueWriteEntryId,
|
|
2738
|
+
index: 0,
|
|
2739
|
+
channelId: valueBearingEntry.channelId,
|
|
2740
|
+
sample: Object.freeze({
|
|
2741
|
+
kind: 'generator-sample' as const,
|
|
2742
|
+
origin: 'ui-frame' as const,
|
|
2743
|
+
channelId: valueBearingEntry.channelId,
|
|
2744
|
+
authoritativeSequence: valueBearingEntry.authoritativeSequence,
|
|
2745
|
+
executionId: valueBearingEntry.executionId,
|
|
2746
|
+
directOperationEpoch: valueBearingEntry.directOperationEpoch,
|
|
2747
|
+
executionLease: valueBearingEntry.executionLease,
|
|
2748
|
+
writeSequence: valueBearingEntry.writeSequence,
|
|
2749
|
+
history: valueBearingEntry.candidateHistory,
|
|
2750
|
+
completion: valueBearingEntry.completion,
|
|
2751
|
+
}),
|
|
2752
|
+
})
|
|
2753
|
+
const transitionCallbackEntry = Object.freeze({
|
|
2754
|
+
kind: 'TRANSITION_CALLBACK' as const,
|
|
2755
|
+
entryId: valueBearingEntry.transitionCallbackEntryId,
|
|
2756
|
+
index: 1,
|
|
2757
|
+
channelId: valueBearingEntry.channelId,
|
|
2758
|
+
callbackId: valueBearingEntry.transitionCallbackId,
|
|
2759
|
+
event: Object.freeze({
|
|
2760
|
+
kind: 'transition-on-update' as const,
|
|
2761
|
+
channelId: valueBearingEntry.channelId,
|
|
2762
|
+
executionId: valueBearingEntry.executionId,
|
|
2763
|
+
directOperationEpoch: valueBearingEntry.directOperationEpoch,
|
|
2764
|
+
executionLease: valueBearingEntry.executionLease,
|
|
2765
|
+
writeSequence: valueBearingEntry.writeSequence,
|
|
2766
|
+
eventSequence: valueBearingEntry.eventSequence,
|
|
2767
|
+
history: valueBearingEntry.candidateHistory,
|
|
2768
|
+
}),
|
|
2769
|
+
})
|
|
2770
|
+
const openTurn = Object.freeze({
|
|
2771
|
+
mailboxOrigin: 'UI' as const,
|
|
2772
|
+
mailboxDrainId,
|
|
2773
|
+
stagedEntries: Object.freeze([valueWriteEntry, transitionCallbackEntry] as const),
|
|
2774
|
+
sealed: false as const,
|
|
2775
|
+
})
|
|
2776
|
+
effects.push({
|
|
2777
|
+
kind: 'form-cold-work-relay-value-bearing-mailbox-prefix',
|
|
2778
|
+
relayId: relay.relayId,
|
|
2779
|
+
canonicalRecord: relay.canonicalRecord,
|
|
2780
|
+
sourceEntry: valueBearingEntry,
|
|
2781
|
+
openTurn,
|
|
2782
|
+
})
|
|
2783
|
+
nextRelay = {
|
|
2784
|
+
...nextRelay,
|
|
2785
|
+
pendingValueBearingEntry: null,
|
|
2786
|
+
stagedValueBearingPrefix: {
|
|
2787
|
+
mailboxDrainId,
|
|
2788
|
+
valueWriteEntryId: valueBearingEntry.valueWriteEntryId,
|
|
2789
|
+
transitionCallbackEntryId: valueBearingEntry.transitionCallbackEntryId,
|
|
2790
|
+
transitionCallbackId: valueBearingEntry.transitionCallbackId,
|
|
2791
|
+
authoritativeSequence: valueBearingEntry.authoritativeSequence,
|
|
2792
|
+
writeSequence: valueBearingEntry.writeSequence,
|
|
2793
|
+
eventSequence: valueBearingEntry.eventSequence,
|
|
2794
|
+
},
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
return { state, relay: nextRelay }
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
function retainRelay(
|
|
2802
|
+
state: UiValueChannelCoordinatorState,
|
|
2803
|
+
relay: UiColdWorkRelayState,
|
|
2804
|
+
): UiValueChannelCoordinatorState {
|
|
2805
|
+
return {
|
|
2806
|
+
...state,
|
|
2807
|
+
relays: state.relays.map((candidate) =>
|
|
2808
|
+
candidate.relayId === relay.relayId ? relay : candidate,
|
|
2809
|
+
),
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
/** A relay is claimable by its target while armed, or while an ongoing execution awaits claim. */
|
|
2814
|
+
function relayClaimableByTarget(relay: UiColdWorkRelayState): boolean {
|
|
2815
|
+
if (relay.leaseState === 'ARMED') return true
|
|
2816
|
+
return relay.leaseState === 'RELAYED_BY_INCUMBENT' && relay.lifetime === 'ONGOING_EXECUTION'
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
/** A relay is steppable by an admitted incumbent while armed or while it retains an ongoing one. */
|
|
2820
|
+
function relaySteppableByIncumbent(relay: UiColdWorkRelayState): boolean {
|
|
2821
|
+
if (relay.leaseState === 'ARMED') return true
|
|
2822
|
+
return relay.leaseState === 'RELAYED_BY_INCUMBENT' && relay.lifetime === 'ONGOING_EXECUTION'
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
function claimColdWorkRelay(
|
|
2826
|
+
state: UiValueChannelCoordinatorState,
|
|
2827
|
+
relay: UiColdWorkRelayState,
|
|
2828
|
+
driverId: number,
|
|
2829
|
+
absoluteTimestampMs: number,
|
|
2830
|
+
effects: UiValueChannelEffect[],
|
|
2831
|
+
): UiValueChannelCoordinatorState {
|
|
2832
|
+
effects.push({ kind: 'claim-cold-work-relay', relayId: relay.relayId, driverId })
|
|
2833
|
+
let next: UiValueChannelCoordinatorState = state
|
|
2834
|
+
let stepped = relay
|
|
2835
|
+
if (relay.leaseState === 'ARMED') {
|
|
2836
|
+
effects.push({
|
|
2837
|
+
kind: 'step-cold-work-relay',
|
|
2838
|
+
relayId: relay.relayId,
|
|
2839
|
+
targetDriverId: relay.targetDriverId,
|
|
2840
|
+
absoluteTimestampMs,
|
|
2841
|
+
})
|
|
2842
|
+
const result = fillAndApplyCanonicalRelayRecord(next, relay, effects)
|
|
2843
|
+
next = result.state
|
|
2844
|
+
stepped = result.relay
|
|
2845
|
+
}
|
|
2846
|
+
const claimed: UiColdWorkRelayState = {
|
|
2847
|
+
...stepped,
|
|
2848
|
+
leaseState: relay.lifetime === 'ONGOING_EXECUTION' ? 'RELEASED' : 'CLAIMED_BY_TARGET',
|
|
2849
|
+
claimedAtTimestampMs: absoluteTimestampMs,
|
|
2850
|
+
claimedByDriverId: driverId,
|
|
2851
|
+
lastRelayedStepTimestampMs:
|
|
2852
|
+
relay.leaseState === 'ARMED' ? absoluteTimestampMs : relay.lastRelayedStepTimestampMs,
|
|
2853
|
+
}
|
|
2854
|
+
if (relay.lifetime === 'ONGOING_EXECUTION') {
|
|
2855
|
+
effects.push({ kind: 'release-claimed-relay', relayId: relay.relayId })
|
|
2856
|
+
}
|
|
2857
|
+
return retainRelay(next, claimed)
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
function stepColdWorkRelayAsIncumbent(
|
|
2861
|
+
state: UiValueChannelCoordinatorState,
|
|
2862
|
+
relay: UiColdWorkRelayState,
|
|
2863
|
+
absoluteTimestampMs: number,
|
|
2864
|
+
effects: UiValueChannelEffect[],
|
|
2865
|
+
): UiValueChannelCoordinatorState {
|
|
2866
|
+
effects.push({
|
|
2867
|
+
kind: 'peek-relayed-driver-state',
|
|
2868
|
+
relayId: relay.relayId,
|
|
2869
|
+
targetDriverId: relay.targetDriverId,
|
|
2870
|
+
})
|
|
2871
|
+
effects.push({
|
|
2872
|
+
kind: 'step-cold-work-relay',
|
|
2873
|
+
relayId: relay.relayId,
|
|
2874
|
+
targetDriverId: relay.targetDriverId,
|
|
2875
|
+
absoluteTimestampMs,
|
|
2876
|
+
})
|
|
2877
|
+
const result = fillAndApplyCanonicalRelayRecord(state, relay, effects)
|
|
2878
|
+
if (relay.lifetime === 'ONGOING_EXECUTION') {
|
|
2879
|
+
effects.push({ kind: 'retain-ongoing-relay-until-target-claim', relayId: relay.relayId })
|
|
2880
|
+
}
|
|
2881
|
+
return retainRelay(result.state, {
|
|
2882
|
+
...result.relay,
|
|
2883
|
+
leaseState: 'RELAYED_BY_INCUMBENT',
|
|
2884
|
+
lastRelayedStepTimestampMs: absoluteTimestampMs,
|
|
2885
|
+
})
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// ——— Live callback invocation ———
|
|
2889
|
+
|
|
2890
|
+
function invokeLiveCallback(
|
|
2891
|
+
state: UiValueChannelCoordinatorState,
|
|
2892
|
+
event: Extract<UiCoordinatorEvent, { readonly kind: 'invoke-live-callback' }>,
|
|
2893
|
+
): UiValueChannelTransition {
|
|
2894
|
+
const operation: UiValueChannelOperation = 'invoke-live-callback'
|
|
2895
|
+
const callback = requireCallback(state, event.driverId, operation)
|
|
2896
|
+
if (
|
|
2897
|
+
callback.callbackRegistrationGeneration !== event.callbackRegistrationGeneration ||
|
|
2898
|
+
callback.callbackLeaseEpoch !== event.callbackLeaseEpoch
|
|
2899
|
+
) {
|
|
2900
|
+
throw new UiValueChannelInvariantError('callback-registration-conflict', operation)
|
|
2901
|
+
}
|
|
2902
|
+
if (callback.state === 'RETIRING' || callback.state === 'REVOKED') {
|
|
2903
|
+
throw new UiValueChannelInvariantError('callback-registration-conflict', operation)
|
|
2904
|
+
}
|
|
2905
|
+
const absoluteTimestampMs = event.frame.timestamp
|
|
2906
|
+
const effects: UiValueChannelEffect[] = []
|
|
2907
|
+
let next = state
|
|
2908
|
+
|
|
2909
|
+
// A pending runner candidate publishes admission only on its first actual invocation; the
|
|
2910
|
+
// runner lease itself transfers only after the later acknowledgement.
|
|
2911
|
+
const admissionRequest = next.runnerAdmissionRequest
|
|
2912
|
+
if (
|
|
2913
|
+
admissionRequest !== null &&
|
|
2914
|
+
admissionRequest.candidateDriverId === event.driverId &&
|
|
2915
|
+
admissionRequest.callbackRegistrationGeneration === event.callbackRegistrationGeneration &&
|
|
2916
|
+
!admissionRequest.actualInvocationObserved
|
|
2917
|
+
) {
|
|
2918
|
+
effects.push({
|
|
2919
|
+
kind: 'publish-actual-callback-invocation-admission',
|
|
2920
|
+
driverId: event.driverId,
|
|
2921
|
+
callbackRegistrationGeneration: event.callbackRegistrationGeneration,
|
|
2922
|
+
callbackLeaseEpoch: event.callbackLeaseEpoch,
|
|
2923
|
+
})
|
|
2924
|
+
next = {
|
|
2925
|
+
...next,
|
|
2926
|
+
runnerAdmissionRequest: { ...admissionRequest, actualInvocationObserved: true },
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
const transaction = next.transaction
|
|
2931
|
+
if (transaction.kind === 'OPEN') {
|
|
2932
|
+
if (transaction.absoluteTimestampMs !== absoluteTimestampMs) {
|
|
2933
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', operation)
|
|
2934
|
+
}
|
|
2935
|
+
if (
|
|
2936
|
+
transaction.visitedAttendeeDriverIds.includes(event.driverId) ||
|
|
2937
|
+
transaction.revokedAttendeeDriverIds.includes(event.driverId)
|
|
2938
|
+
) {
|
|
2939
|
+
throw new UiValueChannelInvariantError('frame-attendance-conflict', operation)
|
|
2940
|
+
}
|
|
2941
|
+
next = recordFrameAttendance(next, event.driverId, effects)
|
|
2942
|
+
next = commitFrameIfAttendanceComplete(next, effects)
|
|
2943
|
+
return uiTransition(next, effects)
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
const lastCommittedTimestampMs = transaction.lastCommittedTimestampMs
|
|
2947
|
+
if (lastCommittedTimestampMs !== null && absoluteTimestampMs <= lastCommittedTimestampMs) {
|
|
2948
|
+
if (callback.state === 'ADMITTED') {
|
|
2949
|
+
effects.push({ kind: 'already-committed-no-op', absoluteTimestampMs })
|
|
2950
|
+
} else {
|
|
2951
|
+
effects.push({
|
|
2952
|
+
kind: 'defer-callback-admission-to-next-timestamp',
|
|
2953
|
+
driverId: event.driverId,
|
|
2954
|
+
afterTimestampMs: absoluteTimestampMs,
|
|
2955
|
+
})
|
|
2956
|
+
}
|
|
2957
|
+
return uiTransition(next, effects)
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// A cold target claims its own relay at a timestamp that is not already committed and performs
|
|
2961
|
+
// the relayed command-edge work in this same invocation.
|
|
2962
|
+
for (const relay of next.relays) {
|
|
2963
|
+
if (relay.targetDriverId !== event.driverId || !relayClaimableByTarget(relay)) continue
|
|
2964
|
+
next = claimColdWorkRelay(next, relay, event.driverId, absoluteTimestampMs, effects)
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
let admittedCallback = callback
|
|
2968
|
+
if (callback.state !== 'ADMITTED') {
|
|
2969
|
+
effects.push({
|
|
2970
|
+
kind: 'admit-callback-lease',
|
|
2971
|
+
driverId: event.driverId,
|
|
2972
|
+
callbackLeaseEpoch: event.callbackLeaseEpoch,
|
|
2973
|
+
absoluteTimestampMs,
|
|
2974
|
+
})
|
|
2975
|
+
admittedCallback = {
|
|
2976
|
+
...callback,
|
|
2977
|
+
state: 'ADMITTED',
|
|
2978
|
+
admittedFromTimestampMs: absoluteTimestampMs,
|
|
2979
|
+
reanimatedCallbackActive: true,
|
|
2980
|
+
}
|
|
2981
|
+
next = replaceCallback(next, admittedCallback)
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
// The attendee batch freezes when the transaction opens: admitted, active callbacks that have
|
|
2985
|
+
// already produced an invocation (plus the invoking driver itself).
|
|
2986
|
+
const expectedAttendeeDriverIds = next.callbacks
|
|
2987
|
+
.filter(
|
|
2988
|
+
(candidate) =>
|
|
2989
|
+
candidate.state === 'ADMITTED' &&
|
|
2990
|
+
candidate.reanimatedCallbackActive &&
|
|
2991
|
+
(candidate.lastInvocationTimestampMs !== null || candidate.driverId === event.driverId),
|
|
2992
|
+
)
|
|
2993
|
+
.map((candidate) => candidate.driverId)
|
|
2994
|
+
next = {
|
|
2995
|
+
...next,
|
|
2996
|
+
transaction: {
|
|
2997
|
+
kind: 'OPEN',
|
|
2998
|
+
mode: 'LIVE',
|
|
2999
|
+
absoluteTimestampMs,
|
|
3000
|
+
mailboxDrainId: next.nextMailboxDrainId,
|
|
3001
|
+
expectedAttendeeDriverIds,
|
|
3002
|
+
visitedAttendeeDriverIds: [],
|
|
3003
|
+
revokedAttendeeDriverIds: [],
|
|
3004
|
+
steppedChannelIds: [],
|
|
3005
|
+
frozenInvocationOrder: expectedAttendeeDriverIds,
|
|
3006
|
+
lastCommittedTimestampMs,
|
|
3007
|
+
committedFrameCount: transaction.committedFrameCount,
|
|
3008
|
+
},
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
// An admitted incumbent relays every armed/ongoing cold relay targeted at other drivers before
|
|
3012
|
+
// its own attendance and commit, so no pinned next-update task waits an extra frame.
|
|
3013
|
+
for (const relay of next.relays) {
|
|
3014
|
+
if (relay.targetDriverId === event.driverId || !relaySteppableByIncumbent(relay)) continue
|
|
3015
|
+
next = stepColdWorkRelayAsIncumbent(next, relay, absoluteTimestampMs, effects)
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
next = recordFrameAttendance(next, event.driverId, effects)
|
|
3019
|
+
next = commitFrameIfAttendanceComplete(next, effects)
|
|
3020
|
+
return uiTransition(next, effects)
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
// ——— Coordinator reducer ———
|
|
3024
|
+
|
|
3025
|
+
export function advanceUiCoordinator(
|
|
3026
|
+
state: UiValueChannelCoordinatorState,
|
|
3027
|
+
event: UiCoordinatorEvent,
|
|
3028
|
+
activators: UiRetainedFrameCallbackActivatorRegistry,
|
|
3029
|
+
): UiValueChannelTransition {
|
|
3030
|
+
switch (event.kind) {
|
|
3031
|
+
case 'arm-cold-work-relay-at-command-edge': {
|
|
3032
|
+
const canonicalRecord = allocateCanonicalRecord(event.commandReceipt.commandAuthority)
|
|
3033
|
+
activators.setActive(event.targetDriverId, event.targetCallbackRegistrationGeneration, true)
|
|
3034
|
+
const relay: UiColdWorkRelayState = {
|
|
3035
|
+
relayId: event.relayId,
|
|
3036
|
+
targetDriverId: event.targetDriverId,
|
|
3037
|
+
commandReceipt: event.commandReceipt,
|
|
3038
|
+
canonicalRecord,
|
|
3039
|
+
commandSequence: event.commandSequence,
|
|
3040
|
+
targetCallbackRegistrationGeneration: event.targetCallbackRegistrationGeneration,
|
|
3041
|
+
relayLeaseEpoch: event.relayLeaseEpoch,
|
|
3042
|
+
leaseState: 'ARMED',
|
|
3043
|
+
lifetime: event.lifetime,
|
|
3044
|
+
armedAtTimestampMs: event.armedAtTimestampMs,
|
|
3045
|
+
lastRelayedStepTimestampMs: null,
|
|
3046
|
+
claimedAtTimestampMs: null,
|
|
3047
|
+
claimedByDriverId: null,
|
|
3048
|
+
pendingValueBearingEntry: null,
|
|
3049
|
+
stagedValueBearingPrefix: null,
|
|
3050
|
+
}
|
|
3051
|
+
return uiTransition({ ...state, relays: [...state.relays, relay] }, [
|
|
3052
|
+
{
|
|
3053
|
+
kind: 'allocate-empty-canonical-cold-relay-record',
|
|
3054
|
+
relayId: event.relayId,
|
|
3055
|
+
canonicalRecord,
|
|
3056
|
+
},
|
|
3057
|
+
{
|
|
3058
|
+
kind: 'arm-cold-relay-lease-at-command-edge',
|
|
3059
|
+
relayId: event.relayId,
|
|
3060
|
+
targetDriverId: event.targetDriverId,
|
|
3061
|
+
targetCallbackRegistrationGeneration: event.targetCallbackRegistrationGeneration,
|
|
3062
|
+
relayLeaseEpoch: event.relayLeaseEpoch,
|
|
3063
|
+
commandReceipt: event.commandReceipt,
|
|
3064
|
+
canonicalRecord,
|
|
3065
|
+
},
|
|
3066
|
+
{
|
|
3067
|
+
kind: 'activate-cold-relay-target-after-command-arm',
|
|
3068
|
+
relayId: event.relayId,
|
|
3069
|
+
targetDriverId: event.targetDriverId,
|
|
3070
|
+
targetCallbackRegistrationGeneration: event.targetCallbackRegistrationGeneration,
|
|
3071
|
+
},
|
|
3072
|
+
])
|
|
3073
|
+
}
|
|
3074
|
+
case 'refresh-callback-observer': {
|
|
3075
|
+
const callback = requireCallback(state, event.driverId, event.kind)
|
|
3076
|
+
if (callback.callbackRegistrationGeneration !== event.callbackRegistrationGeneration) {
|
|
3077
|
+
throw new UiValueChannelInvariantError('callback-registration-conflict', event.kind)
|
|
3078
|
+
}
|
|
3079
|
+
const refreshed: UiCallbackRegistrationState = {
|
|
3080
|
+
...callback,
|
|
3081
|
+
observerRevision: event.observerRevision,
|
|
3082
|
+
}
|
|
3083
|
+
return uiTransition(replaceCallback(state, refreshed), [
|
|
3084
|
+
{
|
|
3085
|
+
kind: 'retain-stable-callback-registration',
|
|
3086
|
+
driverId: event.driverId,
|
|
3087
|
+
callbackIdentityId: callback.callbackIdentityId,
|
|
3088
|
+
callbackRegistrationGeneration: event.callbackRegistrationGeneration,
|
|
3089
|
+
},
|
|
3090
|
+
])
|
|
3091
|
+
}
|
|
3092
|
+
case 'request-callback-activation': {
|
|
3093
|
+
const callback = requireCallback(state, event.driverId, event.kind)
|
|
3094
|
+
if (callback.callbackRegistrationGeneration !== event.callbackRegistrationGeneration) {
|
|
3095
|
+
throw new UiValueChannelInvariantError('callback-registration-conflict', event.kind)
|
|
3096
|
+
}
|
|
3097
|
+
activators.setActive(event.driverId, event.callbackRegistrationGeneration, true)
|
|
3098
|
+
const requested: UiCallbackRegistrationState = {
|
|
3099
|
+
...callback,
|
|
3100
|
+
state: 'ACTIVATION_REQUESTED',
|
|
3101
|
+
reanimatedCallbackActive: true,
|
|
3102
|
+
}
|
|
3103
|
+
const next: UiValueChannelCoordinatorState = {
|
|
3104
|
+
...replaceCallback(state, requested),
|
|
3105
|
+
runnerAdmissionRequest: {
|
|
3106
|
+
requestedRunnerLeaseEpoch: event.requestedRunnerLeaseEpoch,
|
|
3107
|
+
candidateDriverId: event.driverId,
|
|
3108
|
+
callbackRegistrationGeneration: event.callbackRegistrationGeneration,
|
|
3109
|
+
admissionToken: event.admissionToken,
|
|
3110
|
+
absoluteDeadlineMs: event.absoluteDeadlineMs,
|
|
3111
|
+
actualInvocationObserved: false,
|
|
3112
|
+
},
|
|
3113
|
+
nextRunnerAdmissionToken: Math.max(
|
|
3114
|
+
state.nextRunnerAdmissionToken,
|
|
3115
|
+
event.admissionToken + 1,
|
|
3116
|
+
),
|
|
3117
|
+
}
|
|
3118
|
+
return uiTransition(next, [
|
|
3119
|
+
{
|
|
3120
|
+
kind: 'start-runner-admission-timer',
|
|
3121
|
+
requestedRunnerLeaseEpoch: event.requestedRunnerLeaseEpoch,
|
|
3122
|
+
candidateDriverId: event.driverId,
|
|
3123
|
+
callbackRegistrationGeneration: event.callbackRegistrationGeneration,
|
|
3124
|
+
admissionToken: event.admissionToken,
|
|
3125
|
+
absoluteDeadlineMs: event.absoluteDeadlineMs,
|
|
3126
|
+
},
|
|
3127
|
+
])
|
|
3128
|
+
}
|
|
3129
|
+
case 'invoke-live-callback':
|
|
3130
|
+
return invokeLiveCallback(state, event)
|
|
3131
|
+
case 'acknowledge-callback-admission': {
|
|
3132
|
+
const request = state.runnerAdmissionRequest
|
|
3133
|
+
if (
|
|
3134
|
+
request === null ||
|
|
3135
|
+
request.admissionToken !== event.admissionToken ||
|
|
3136
|
+
request.candidateDriverId !== event.candidateDriverId ||
|
|
3137
|
+
request.callbackRegistrationGeneration !== event.callbackRegistrationGeneration ||
|
|
3138
|
+
request.requestedRunnerLeaseEpoch !== event.requestedRunnerLeaseEpoch ||
|
|
3139
|
+
!request.actualInvocationObserved
|
|
3140
|
+
) {
|
|
3141
|
+
throw new UiValueChannelInvariantError('runner-admission-request-conflict', event.kind)
|
|
3142
|
+
}
|
|
3143
|
+
const effects: UiValueChannelEffect[] = [
|
|
3144
|
+
{ kind: 'cancel-runner-admission-timer', admissionToken: event.admissionToken },
|
|
3145
|
+
{
|
|
3146
|
+
kind: 'transfer-runner-lease-after-admission-acknowledgement',
|
|
3147
|
+
fromDriverId: state.runnerDriverId,
|
|
3148
|
+
toDriverId: event.candidateDriverId,
|
|
3149
|
+
runnerLeaseEpoch: request.requestedRunnerLeaseEpoch,
|
|
3150
|
+
},
|
|
3151
|
+
]
|
|
3152
|
+
let callbacks = state.callbacks
|
|
3153
|
+
const previousRunner = state.callbacks.find(
|
|
3154
|
+
(candidate) => candidate.driverId === state.runnerDriverId,
|
|
3155
|
+
)
|
|
3156
|
+
if (previousRunner !== undefined && previousRunner.driverId !== event.candidateDriverId) {
|
|
3157
|
+
activators.setActive(
|
|
3158
|
+
previousRunner.driverId,
|
|
3159
|
+
previousRunner.callbackRegistrationGeneration,
|
|
3160
|
+
false,
|
|
3161
|
+
)
|
|
3162
|
+
callbacks = callbacks.map((candidate) =>
|
|
3163
|
+
candidate.driverId === previousRunner.driverId
|
|
3164
|
+
? { ...candidate, reanimatedCallbackActive: false }
|
|
3165
|
+
: candidate,
|
|
3166
|
+
)
|
|
3167
|
+
}
|
|
3168
|
+
callbacks = callbacks.map((candidate) =>
|
|
3169
|
+
candidate.driverId === event.candidateDriverId
|
|
3170
|
+
? { ...candidate, state: 'ADMITTED', reanimatedCallbackActive: true }
|
|
3171
|
+
: candidate,
|
|
3172
|
+
)
|
|
3173
|
+
return uiTransition(
|
|
3174
|
+
{
|
|
3175
|
+
...state,
|
|
3176
|
+
callbacks,
|
|
3177
|
+
runnerDriverId: event.candidateDriverId,
|
|
3178
|
+
runnerLeaseEpoch: request.requestedRunnerLeaseEpoch,
|
|
3179
|
+
runnerAdmissionRequest: null,
|
|
3180
|
+
},
|
|
3181
|
+
effects,
|
|
3182
|
+
)
|
|
3183
|
+
}
|
|
3184
|
+
case 'runner-admission-timeout': {
|
|
3185
|
+
const request = state.runnerAdmissionRequest
|
|
3186
|
+
if (
|
|
3187
|
+
request === null ||
|
|
3188
|
+
request.admissionToken !== event.admissionToken ||
|
|
3189
|
+
request.candidateDriverId !== event.candidateDriverId ||
|
|
3190
|
+
request.callbackRegistrationGeneration !== event.callbackRegistrationGeneration ||
|
|
3191
|
+
request.requestedRunnerLeaseEpoch !== event.requestedRunnerLeaseEpoch
|
|
3192
|
+
) {
|
|
3193
|
+
throw new UiValueChannelInvariantError('runner-admission-request-conflict', event.kind)
|
|
3194
|
+
}
|
|
3195
|
+
const effects: UiValueChannelEffect[] = [
|
|
3196
|
+
{ kind: 'cancel-runner-admission-timer', admissionToken: event.admissionToken },
|
|
3197
|
+
{
|
|
3198
|
+
kind: 'invalidate-runner-admission-candidate',
|
|
3199
|
+
driverId: event.candidateDriverId,
|
|
3200
|
+
admissionToken: event.admissionToken,
|
|
3201
|
+
},
|
|
3202
|
+
]
|
|
3203
|
+
activators.setActive(event.candidateDriverId, event.callbackRegistrationGeneration, false)
|
|
3204
|
+
const callbacksAfterInvalidation = state.callbacks.map((candidate) =>
|
|
3205
|
+
candidate.driverId === event.candidateDriverId
|
|
3206
|
+
? { ...candidate, state: 'REGISTERED_INACTIVE' as const, reanimatedCallbackActive: false }
|
|
3207
|
+
: candidate,
|
|
3208
|
+
)
|
|
3209
|
+
const successor = callbacksAfterInvalidation.find(
|
|
3210
|
+
(candidate) =>
|
|
3211
|
+
candidate.driverId !== event.candidateDriverId &&
|
|
3212
|
+
candidate.state === 'REGISTERED_INACTIVE',
|
|
3213
|
+
)
|
|
3214
|
+
if (successor === undefined) {
|
|
3215
|
+
return uiTransition(
|
|
3216
|
+
{ ...state, callbacks: callbacksAfterInvalidation, runnerAdmissionRequest: null },
|
|
3217
|
+
effects,
|
|
3218
|
+
)
|
|
3219
|
+
}
|
|
3220
|
+
activators.setActive(successor.driverId, successor.callbackRegistrationGeneration, true)
|
|
3221
|
+
const admissionToken = state.nextRunnerAdmissionToken
|
|
3222
|
+
const absoluteDeadlineMs = event.nowMs + UI_RUNNER_ADMISSION_WINDOW_MS
|
|
3223
|
+
effects.push({
|
|
3224
|
+
kind: 'select-next-live-runner-candidate',
|
|
3225
|
+
excludedDriverIds: [event.candidateDriverId],
|
|
3226
|
+
selectedDriverId: successor.driverId,
|
|
3227
|
+
})
|
|
3228
|
+
effects.push({
|
|
3229
|
+
kind: 'start-runner-admission-timer',
|
|
3230
|
+
requestedRunnerLeaseEpoch: request.requestedRunnerLeaseEpoch,
|
|
3231
|
+
candidateDriverId: successor.driverId,
|
|
3232
|
+
callbackRegistrationGeneration: successor.callbackRegistrationGeneration,
|
|
3233
|
+
admissionToken,
|
|
3234
|
+
absoluteDeadlineMs,
|
|
3235
|
+
})
|
|
3236
|
+
return uiTransition(
|
|
3237
|
+
{
|
|
3238
|
+
...state,
|
|
3239
|
+
callbacks: callbacksAfterInvalidation.map((candidate) =>
|
|
3240
|
+
candidate.driverId === successor.driverId
|
|
3241
|
+
? {
|
|
3242
|
+
...candidate,
|
|
3243
|
+
state: 'ACTIVATION_REQUESTED' as const,
|
|
3244
|
+
reanimatedCallbackActive: true,
|
|
3245
|
+
}
|
|
3246
|
+
: candidate,
|
|
3247
|
+
),
|
|
3248
|
+
runnerAdmissionRequest: {
|
|
3249
|
+
requestedRunnerLeaseEpoch: request.requestedRunnerLeaseEpoch,
|
|
3250
|
+
candidateDriverId: successor.driverId,
|
|
3251
|
+
callbackRegistrationGeneration: successor.callbackRegistrationGeneration,
|
|
3252
|
+
admissionToken,
|
|
3253
|
+
absoluteDeadlineMs,
|
|
3254
|
+
actualInvocationObserved: false,
|
|
3255
|
+
},
|
|
3256
|
+
nextRunnerAdmissionToken: admissionToken + 1,
|
|
3257
|
+
},
|
|
3258
|
+
effects,
|
|
3259
|
+
)
|
|
3260
|
+
}
|
|
3261
|
+
case 'retire-callback': {
|
|
3262
|
+
const callback = requireCallback(state, event.driverId, event.kind)
|
|
3263
|
+
if (callback.callbackRegistrationGeneration !== event.callbackRegistrationGeneration) {
|
|
3264
|
+
throw new UiValueChannelInvariantError('callback-registration-conflict', event.kind)
|
|
3265
|
+
}
|
|
3266
|
+
const retiring: UiCallbackRegistrationState = { ...callback, state: 'RETIRING' }
|
|
3267
|
+
return uiTransition(replaceCallback(state, retiring), [
|
|
3268
|
+
{ kind: 'retire-callback-effective-next-timestamp', driverId: event.driverId },
|
|
3269
|
+
])
|
|
3270
|
+
}
|
|
3271
|
+
case 'dispose-callback': {
|
|
3272
|
+
const callback = requireCallback(state, event.driverId, event.kind)
|
|
3273
|
+
if (callback.callbackRegistrationGeneration !== event.callbackRegistrationGeneration) {
|
|
3274
|
+
throw new UiValueChannelInvariantError('callback-registration-conflict', event.kind)
|
|
3275
|
+
}
|
|
3276
|
+
const effects: UiValueChannelEffect[] = []
|
|
3277
|
+
let next = state
|
|
3278
|
+
const transaction = state.transaction
|
|
3279
|
+
if (
|
|
3280
|
+
transaction.kind === 'OPEN' &&
|
|
3281
|
+
transaction.expectedAttendeeDriverIds.includes(event.driverId) &&
|
|
3282
|
+
!transaction.visitedAttendeeDriverIds.includes(event.driverId) &&
|
|
3283
|
+
!transaction.revokedAttendeeDriverIds.includes(event.driverId)
|
|
3284
|
+
) {
|
|
3285
|
+
effects.push({ kind: 'record-synthetic-revocation-attendance', driverId: event.driverId })
|
|
3286
|
+
next = {
|
|
3287
|
+
...next,
|
|
3288
|
+
transaction: {
|
|
3289
|
+
...transaction,
|
|
3290
|
+
revokedAttendeeDriverIds: [...transaction.revokedAttendeeDriverIds, event.driverId],
|
|
3291
|
+
},
|
|
3292
|
+
}
|
|
3293
|
+
next = commitFrameIfAttendanceComplete(next, effects)
|
|
3294
|
+
}
|
|
3295
|
+
effects.push({ kind: 'delete-disposed-callback-after-attendance', driverId: event.driverId })
|
|
3296
|
+
next = {
|
|
3297
|
+
...next,
|
|
3298
|
+
callbacks: next.callbacks.filter((candidate) => candidate.driverId !== event.driverId),
|
|
3299
|
+
}
|
|
3300
|
+
return uiTransition(next, effects)
|
|
3301
|
+
}
|
|
3302
|
+
default: {
|
|
3303
|
+
const exhaustive: never = event
|
|
3304
|
+
void exhaustive
|
|
3305
|
+
throw new UiValueChannelInvariantError('coordinator-event-kind-unrecognized')
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
// ——— Manual frame transaction ———
|
|
3311
|
+
|
|
3312
|
+
export function uiBeginManualFrame(
|
|
3313
|
+
state: UiValueChannelCoordinatorState,
|
|
3314
|
+
crossing: UiManualFrameCrossing,
|
|
3315
|
+
): UiValueChannelTransition {
|
|
3316
|
+
const transaction = state.transaction
|
|
3317
|
+
if (transaction.kind === 'OPEN') {
|
|
3318
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', 'ui-begin-manual-frame')
|
|
3319
|
+
}
|
|
3320
|
+
const lastCommittedTimestampMs = transaction.lastCommittedTimestampMs
|
|
3321
|
+
if (
|
|
3322
|
+
lastCommittedTimestampMs !== null &&
|
|
3323
|
+
crossing.absoluteTimestampMs <= lastCommittedTimestampMs
|
|
3324
|
+
) {
|
|
3325
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', 'ui-begin-manual-frame')
|
|
3326
|
+
}
|
|
3327
|
+
return uiTransition(
|
|
3328
|
+
{
|
|
3329
|
+
...state,
|
|
3330
|
+
transaction: {
|
|
3331
|
+
kind: 'OPEN',
|
|
3332
|
+
mode: 'MANUAL',
|
|
3333
|
+
absoluteTimestampMs: crossing.absoluteTimestampMs,
|
|
3334
|
+
mailboxDrainId: state.nextMailboxDrainId,
|
|
3335
|
+
expectedAttendeeDriverIds: crossing.orderedAttendeeDriverIds,
|
|
3336
|
+
visitedAttendeeDriverIds: [],
|
|
3337
|
+
revokedAttendeeDriverIds: [],
|
|
3338
|
+
steppedChannelIds: [],
|
|
3339
|
+
frozenInvocationOrder: crossing.orderedAttendeeDriverIds,
|
|
3340
|
+
lastCommittedTimestampMs,
|
|
3341
|
+
committedFrameCount: transaction.committedFrameCount,
|
|
3342
|
+
},
|
|
3343
|
+
},
|
|
3344
|
+
[
|
|
3345
|
+
{
|
|
3346
|
+
kind: 'open-frame-transaction',
|
|
3347
|
+
mode: 'MANUAL',
|
|
3348
|
+
absoluteTimestampMs: crossing.absoluteTimestampMs,
|
|
3349
|
+
},
|
|
3350
|
+
],
|
|
3351
|
+
)
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
export function uiInvokeManualParticipant(
|
|
3355
|
+
state: UiValueChannelCoordinatorState,
|
|
3356
|
+
invocation: UiManualParticipantInvocation,
|
|
3357
|
+
): UiValueChannelTransition {
|
|
3358
|
+
const transaction = state.transaction
|
|
3359
|
+
if (transaction.kind !== 'OPEN') {
|
|
3360
|
+
if (
|
|
3361
|
+
transaction.lastCommittedTimestampMs !== null &&
|
|
3362
|
+
invocation.absoluteTimestampMs <= transaction.lastCommittedTimestampMs
|
|
3363
|
+
) {
|
|
3364
|
+
return uiTransition(state, [], {
|
|
3365
|
+
kind: 'LATE_ATTENDANCE',
|
|
3366
|
+
driverId: invocation.driverId,
|
|
3367
|
+
alreadyCommittedTimestampMs: transaction.lastCommittedTimestampMs,
|
|
3368
|
+
})
|
|
3369
|
+
}
|
|
3370
|
+
throw new UiValueChannelInvariantError(
|
|
3371
|
+
'frame-transaction-conflict',
|
|
3372
|
+
'ui-invoke-manual-participant',
|
|
3373
|
+
)
|
|
3374
|
+
}
|
|
3375
|
+
if (
|
|
3376
|
+
transaction.mode !== 'MANUAL' ||
|
|
3377
|
+
transaction.absoluteTimestampMs !== invocation.absoluteTimestampMs
|
|
3378
|
+
) {
|
|
3379
|
+
throw new UiValueChannelInvariantError(
|
|
3380
|
+
'frame-transaction-conflict',
|
|
3381
|
+
'ui-invoke-manual-participant',
|
|
3382
|
+
)
|
|
3383
|
+
}
|
|
3384
|
+
if (!transaction.expectedAttendeeDriverIds.includes(invocation.driverId)) {
|
|
3385
|
+
throw new UiValueChannelInvariantError(
|
|
3386
|
+
'frame-attendance-conflict',
|
|
3387
|
+
'ui-invoke-manual-participant',
|
|
3388
|
+
)
|
|
3389
|
+
}
|
|
3390
|
+
if (
|
|
3391
|
+
transaction.visitedAttendeeDriverIds.includes(invocation.driverId) ||
|
|
3392
|
+
transaction.revokedAttendeeDriverIds.includes(invocation.driverId)
|
|
3393
|
+
) {
|
|
3394
|
+
throw new UiValueChannelInvariantError(
|
|
3395
|
+
'frame-attendance-conflict',
|
|
3396
|
+
'ui-invoke-manual-participant',
|
|
3397
|
+
)
|
|
3398
|
+
}
|
|
3399
|
+
const effects: UiValueChannelEffect[] = []
|
|
3400
|
+
let next = recordFrameAttendance(state, invocation.driverId, effects)
|
|
3401
|
+
next = commitFrameIfAttendanceComplete(next, effects)
|
|
3402
|
+
return uiTransition(next, effects)
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
export function uiRevokeManualParticipant(
|
|
3406
|
+
state: UiValueChannelCoordinatorState,
|
|
3407
|
+
revocation: UiManualParticipantRevocation,
|
|
3408
|
+
): UiValueChannelTransition {
|
|
3409
|
+
const transaction = state.transaction
|
|
3410
|
+
if (
|
|
3411
|
+
transaction.kind !== 'OPEN' ||
|
|
3412
|
+
transaction.mode !== 'MANUAL' ||
|
|
3413
|
+
transaction.absoluteTimestampMs !== revocation.absoluteTimestampMs
|
|
3414
|
+
) {
|
|
3415
|
+
throw new UiValueChannelInvariantError(
|
|
3416
|
+
'frame-transaction-conflict',
|
|
3417
|
+
'ui-revoke-manual-participant',
|
|
3418
|
+
)
|
|
3419
|
+
}
|
|
3420
|
+
if (!transaction.expectedAttendeeDriverIds.includes(revocation.driverId)) {
|
|
3421
|
+
throw new UiValueChannelInvariantError(
|
|
3422
|
+
'frame-attendance-conflict',
|
|
3423
|
+
'ui-revoke-manual-participant',
|
|
3424
|
+
)
|
|
3425
|
+
}
|
|
3426
|
+
if (
|
|
3427
|
+
transaction.visitedAttendeeDriverIds.includes(revocation.driverId) ||
|
|
3428
|
+
transaction.revokedAttendeeDriverIds.includes(revocation.driverId)
|
|
3429
|
+
) {
|
|
3430
|
+
throw new UiValueChannelInvariantError(
|
|
3431
|
+
'frame-attendance-conflict',
|
|
3432
|
+
'ui-revoke-manual-participant',
|
|
3433
|
+
)
|
|
3434
|
+
}
|
|
3435
|
+
const effects: UiValueChannelEffect[] = [
|
|
3436
|
+
{ kind: 'record-synthetic-revocation-attendance', driverId: revocation.driverId },
|
|
3437
|
+
]
|
|
3438
|
+
let next: UiValueChannelCoordinatorState = {
|
|
3439
|
+
...state,
|
|
3440
|
+
transaction: {
|
|
3441
|
+
...transaction,
|
|
3442
|
+
revokedAttendeeDriverIds: [...transaction.revokedAttendeeDriverIds, revocation.driverId],
|
|
3443
|
+
},
|
|
3444
|
+
}
|
|
3445
|
+
next = commitFrameIfAttendanceComplete(next, effects)
|
|
3446
|
+
return uiTransition(next, effects)
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
export function uiCommitManualFrame(
|
|
3450
|
+
state: UiValueChannelCoordinatorState,
|
|
3451
|
+
request: UiManualFrameCommitRequest,
|
|
3452
|
+
): UiValueChannelTransition {
|
|
3453
|
+
const transaction = state.transaction
|
|
3454
|
+
if (transaction.kind !== 'OPEN') {
|
|
3455
|
+
if (transaction.lastCommittedTimestampMs === request.absoluteTimestampMs) {
|
|
3456
|
+
return uiTransition(state, [
|
|
3457
|
+
{ kind: 'already-committed-no-op', absoluteTimestampMs: request.absoluteTimestampMs },
|
|
3458
|
+
])
|
|
3459
|
+
}
|
|
3460
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', 'ui-commit-manual-frame')
|
|
3461
|
+
}
|
|
3462
|
+
if (
|
|
3463
|
+
transaction.mode !== 'MANUAL' ||
|
|
3464
|
+
transaction.absoluteTimestampMs !== request.absoluteTimestampMs
|
|
3465
|
+
) {
|
|
3466
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', 'ui-commit-manual-frame')
|
|
3467
|
+
}
|
|
3468
|
+
const missingDriverIds = transaction.expectedAttendeeDriverIds.filter(
|
|
3469
|
+
(driverId) =>
|
|
3470
|
+
!transaction.visitedAttendeeDriverIds.includes(driverId) &&
|
|
3471
|
+
!transaction.revokedAttendeeDriverIds.includes(driverId),
|
|
3472
|
+
)
|
|
3473
|
+
if (missingDriverIds.length > 0) {
|
|
3474
|
+
return uiTransition(state, [], { kind: 'MISSING_ATTENDEES', missingDriverIds })
|
|
3475
|
+
}
|
|
3476
|
+
const effects: UiValueChannelEffect[] = []
|
|
3477
|
+
const next = commitFrameIfAttendanceComplete(state, effects)
|
|
3478
|
+
return uiTransition(next, effects)
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
export function armManualDestinationCommit(
|
|
3482
|
+
state: UiValueChannelCoordinatorState,
|
|
3483
|
+
groupId: number,
|
|
3484
|
+
commit: UiManualDestinationCommitSlot,
|
|
3485
|
+
): UiValueChannelTransition {
|
|
3486
|
+
const group = requireManualGroup(state, groupId, 'arm-manual-destination-commit')
|
|
3487
|
+
return uiTransition(
|
|
3488
|
+
replaceManualGroup(state, {
|
|
3489
|
+
...group,
|
|
3490
|
+
pendingDestinationCommits: [...group.pendingDestinationCommits, commit],
|
|
3491
|
+
armedForExplicitStep: true,
|
|
3492
|
+
}),
|
|
3493
|
+
[
|
|
3494
|
+
{
|
|
3495
|
+
kind: 'arm-manual-group-for-destination-commit',
|
|
3496
|
+
groupId,
|
|
3497
|
+
commitSlotId: commit.commitSlotId,
|
|
3498
|
+
},
|
|
3499
|
+
],
|
|
3500
|
+
)
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
interface UiManualCommitConsumption {
|
|
3504
|
+
readonly state: UiValueChannelCoordinatorState
|
|
3505
|
+
readonly group: ManualFrameGroupState
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
function consumeManualDestinationCommit(
|
|
3509
|
+
state: UiValueChannelCoordinatorState,
|
|
3510
|
+
group: ManualFrameGroupState,
|
|
3511
|
+
commit: UiManualDestinationCommitSlot,
|
|
3512
|
+
authority: UiPreparedDestinationRecordState,
|
|
3513
|
+
validator: UiDestinationCommitCanonicalValidatorCapability,
|
|
3514
|
+
effects: UiValueChannelEffect[],
|
|
3515
|
+
): UiManualCommitConsumption {
|
|
3516
|
+
effects.push({
|
|
3517
|
+
kind: 'lookup-prepared-destination-record',
|
|
3518
|
+
groupId: group.groupId,
|
|
3519
|
+
destinationRecordId: commit.destinationRecordId,
|
|
3520
|
+
})
|
|
3521
|
+
effects.push({
|
|
3522
|
+
kind: 'validate-ui-destination-commit-with-canonical-authority',
|
|
3523
|
+
root: 'MANUAL',
|
|
3524
|
+
groupId: group.groupId,
|
|
3525
|
+
commitSlotId: commit.commitSlotId,
|
|
3526
|
+
authority,
|
|
3527
|
+
validator,
|
|
3528
|
+
})
|
|
3529
|
+
const newEntries = commit.includedPostFencePrefix.filter(
|
|
3530
|
+
(entry) => entry.authoritativeSequence > authority.appliedThroughAuthoritativeSequence,
|
|
3531
|
+
)
|
|
3532
|
+
const appliedThroughAuthoritativeSequence =
|
|
3533
|
+
newEntries.at(-1)?.authoritativeSequence ?? authority.appliedThroughAuthoritativeSequence
|
|
3534
|
+
let next = state
|
|
3535
|
+
if (newEntries.length > 0) {
|
|
3536
|
+
effects.push({
|
|
3537
|
+
kind: 'adopt-immutable-destination-commit',
|
|
3538
|
+
groupId: group.groupId,
|
|
3539
|
+
commitSlotId: commit.commitSlotId,
|
|
3540
|
+
})
|
|
3541
|
+
for (const entry of newEntries) {
|
|
3542
|
+
effects.push({
|
|
3543
|
+
kind: 'apply-included-post-fence-ledger-entry-in-place',
|
|
3544
|
+
groupId: group.groupId,
|
|
3545
|
+
commitSlotId: commit.commitSlotId,
|
|
3546
|
+
prefixIndex: commit.includedPostFencePrefix.indexOf(entry),
|
|
3547
|
+
destinationSlot: entry.destinationSlot,
|
|
3548
|
+
entry,
|
|
3549
|
+
})
|
|
3550
|
+
}
|
|
3551
|
+
next = {
|
|
3552
|
+
...next,
|
|
3553
|
+
preparedDestinationRecords: next.preparedDestinationRecords.map((candidate) =>
|
|
3554
|
+
candidate === authority
|
|
3555
|
+
? {
|
|
3556
|
+
...candidate,
|
|
3557
|
+
adoptedCommitSlotId: commit.commitSlotId,
|
|
3558
|
+
appliedThroughAuthoritativeSequence,
|
|
3559
|
+
ledgers: applyEntriesToUiLedgers(candidate.ledgers, newEntries),
|
|
3560
|
+
}
|
|
3561
|
+
: candidate,
|
|
3562
|
+
),
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
const acknowledgement: DestinationCommitAcknowledgement = {
|
|
3566
|
+
kind: 'DESTINATION_COMMIT_ACKNOWLEDGEMENT',
|
|
3567
|
+
channelId: commit.channelId,
|
|
3568
|
+
sourceRecordId: commit.sourceRecordId,
|
|
3569
|
+
destinationRecordId: commit.destinationRecordId,
|
|
3570
|
+
direction: commit.direction,
|
|
3571
|
+
transferLease: commit.transferLease,
|
|
3572
|
+
releaseFenceSequence: commit.releaseFenceSequence,
|
|
3573
|
+
drainedThroughAuthoritativeSequence: appliedThroughAuthoritativeSequence,
|
|
3574
|
+
}
|
|
3575
|
+
effects.push({
|
|
3576
|
+
kind: 'fill-prefilled-destination-acknowledgement-slot',
|
|
3577
|
+
groupId: group.groupId,
|
|
3578
|
+
commitSlotId: commit.commitSlotId,
|
|
3579
|
+
acknowledgementSlotId: commit.acknowledgementSlotId,
|
|
3580
|
+
})
|
|
3581
|
+
effects.push({
|
|
3582
|
+
kind: 'stage-destination-acknowledgement-in-current-envelope',
|
|
3583
|
+
groupId: group.groupId,
|
|
3584
|
+
acknowledgementSlotId: commit.acknowledgementSlotId,
|
|
3585
|
+
mailboxDrainId: next.nextMailboxDrainId,
|
|
3586
|
+
})
|
|
3587
|
+
next = {
|
|
3588
|
+
...next,
|
|
3589
|
+
destinationCommitAcknowledgementSlots: next.destinationCommitAcknowledgementSlots.map(
|
|
3590
|
+
(candidate) =>
|
|
3591
|
+
candidate.acknowledgementSlotId === commit.acknowledgementSlotId
|
|
3592
|
+
? { ...candidate, state: 'STAGED_IN_CURRENT_ENVELOPE', acknowledgement }
|
|
3593
|
+
: candidate,
|
|
3594
|
+
),
|
|
3595
|
+
}
|
|
3596
|
+
effects.push({
|
|
3597
|
+
kind: 'consume-manual-destination-commit-on-explicit-step',
|
|
3598
|
+
groupId: group.groupId,
|
|
3599
|
+
commitSlotId: commit.commitSlotId,
|
|
3600
|
+
})
|
|
3601
|
+
const pendingDestinationCommits = group.pendingDestinationCommits.filter(
|
|
3602
|
+
(candidate) => candidate.commitSlotId !== commit.commitSlotId,
|
|
3603
|
+
)
|
|
3604
|
+
return {
|
|
3605
|
+
state: next,
|
|
3606
|
+
group: {
|
|
3607
|
+
...group,
|
|
3608
|
+
pendingDestinationCommits,
|
|
3609
|
+
consumedCommitSlotIds: [...group.consumedCommitSlotIds, commit.commitSlotId],
|
|
3610
|
+
armedForExplicitStep: pendingDestinationCommits.length > 0,
|
|
3611
|
+
},
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
export function stepManualFrameGroup(
|
|
3616
|
+
state: UiValueChannelCoordinatorState,
|
|
3617
|
+
request: ManualFrameGroupStepRequest,
|
|
3618
|
+
validator: UiDestinationCommitCanonicalValidatorCapability,
|
|
3619
|
+
): UiValueChannelTransition {
|
|
3620
|
+
const group = requireManualGroup(state, request.groupId, 'step-manual-frame-group')
|
|
3621
|
+
if (state.transaction.kind === 'OPEN') {
|
|
3622
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', 'step-manual-frame-group')
|
|
3623
|
+
}
|
|
3624
|
+
const pendingCommit = group.pendingDestinationCommits[0]
|
|
3625
|
+
let authority: UiPreparedDestinationRecordState | null = null
|
|
3626
|
+
if (pendingCommit !== undefined) {
|
|
3627
|
+
const outcome = validator.validate({
|
|
3628
|
+
commit: pendingCommit,
|
|
3629
|
+
preparedDestinationRecords: state.preparedDestinationRecords,
|
|
3630
|
+
})
|
|
3631
|
+
if (outcome.kind === 'REFUSED') {
|
|
3632
|
+
return uiTransition(
|
|
3633
|
+
state,
|
|
3634
|
+
[
|
|
3635
|
+
{
|
|
3636
|
+
kind: 'report-ui-destination-commit-refusal',
|
|
3637
|
+
root: 'MANUAL',
|
|
3638
|
+
groupId: request.groupId,
|
|
3639
|
+
commitSlotId: pendingCommit.commitSlotId,
|
|
3640
|
+
authority: outcome.authority,
|
|
3641
|
+
reason: outcome.reason,
|
|
3642
|
+
},
|
|
3643
|
+
],
|
|
3644
|
+
{
|
|
3645
|
+
kind: 'DESTINATION_COMMIT_REFUSED',
|
|
3646
|
+
groupId: request.groupId,
|
|
3647
|
+
commitSlotId: pendingCommit.commitSlotId,
|
|
3648
|
+
authority: outcome.authority,
|
|
3649
|
+
reason: outcome.reason,
|
|
3650
|
+
},
|
|
3651
|
+
)
|
|
3652
|
+
}
|
|
3653
|
+
authority = outcome.authority
|
|
3654
|
+
const acknowledgementSlot = state.destinationCommitAcknowledgementSlots.find(
|
|
3655
|
+
(candidate) => candidate.acknowledgementSlotId === pendingCommit.acknowledgementSlotId,
|
|
3656
|
+
)
|
|
3657
|
+
if (acknowledgementSlot === undefined) {
|
|
3658
|
+
return uiTransition(
|
|
3659
|
+
state,
|
|
3660
|
+
[
|
|
3661
|
+
{
|
|
3662
|
+
kind: 'report-ui-destination-commit-refusal',
|
|
3663
|
+
root: 'MANUAL',
|
|
3664
|
+
groupId: request.groupId,
|
|
3665
|
+
commitSlotId: pendingCommit.commitSlotId,
|
|
3666
|
+
authority,
|
|
3667
|
+
reason: 'missing-acknowledgement-slot',
|
|
3668
|
+
},
|
|
3669
|
+
],
|
|
3670
|
+
{
|
|
3671
|
+
kind: 'DESTINATION_COMMIT_REFUSED',
|
|
3672
|
+
groupId: request.groupId,
|
|
3673
|
+
commitSlotId: pendingCommit.commitSlotId,
|
|
3674
|
+
authority,
|
|
3675
|
+
reason: 'missing-acknowledgement-slot',
|
|
3676
|
+
},
|
|
3677
|
+
)
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
const absoluteTimestampMs = group.currentAbsoluteTimestampMs + request.elapsedMs
|
|
3682
|
+
const orderedAttendeeDriverIds = request.invocationOrder ?? group.participantDriverIds
|
|
3683
|
+
const effects: UiValueChannelEffect[] = [
|
|
3684
|
+
{ kind: 'advance-manual-group-clock-once', groupId: request.groupId, absoluteTimestampMs },
|
|
3685
|
+
{
|
|
3686
|
+
kind: 'freeze-manual-attendee-batch',
|
|
3687
|
+
groupId: request.groupId,
|
|
3688
|
+
orderedAttendeeDriverIds,
|
|
3689
|
+
},
|
|
3690
|
+
]
|
|
3691
|
+
let next = state
|
|
3692
|
+
let steppedGroup = group
|
|
3693
|
+
if (pendingCommit !== undefined && authority !== null) {
|
|
3694
|
+
const consumption = consumeManualDestinationCommit(
|
|
3695
|
+
next,
|
|
3696
|
+
steppedGroup,
|
|
3697
|
+
pendingCommit,
|
|
3698
|
+
authority,
|
|
3699
|
+
validator,
|
|
3700
|
+
effects,
|
|
3701
|
+
)
|
|
3702
|
+
next = consumption.state
|
|
3703
|
+
steppedGroup = consumption.group
|
|
3704
|
+
}
|
|
3705
|
+
const identities = next.identities.map((identity) => {
|
|
3706
|
+
const deltaMs =
|
|
3707
|
+
identity.lastAbsoluteTimestampMs === null
|
|
3708
|
+
? 0
|
|
3709
|
+
: absoluteTimestampMs - identity.lastAbsoluteTimestampMs
|
|
3710
|
+
effects.push({
|
|
3711
|
+
kind: 'step-shared-identity-once',
|
|
3712
|
+
channelId: identity.channelId,
|
|
3713
|
+
absoluteTimestampMs,
|
|
3714
|
+
deltaMs,
|
|
3715
|
+
})
|
|
3716
|
+
return {
|
|
3717
|
+
...identity,
|
|
3718
|
+
lastAbsoluteTimestampMs: absoluteTimestampMs,
|
|
3719
|
+
stepCount: identity.stepCount + 1,
|
|
3720
|
+
hostCommitCount: identity.hostCommitCount + 1,
|
|
3721
|
+
}
|
|
3722
|
+
})
|
|
3723
|
+
effects.push({ kind: 'commit-frame-once', absoluteTimestampMs })
|
|
3724
|
+
const transaction = next.transaction
|
|
3725
|
+
if (transaction.kind !== 'CLOSED') {
|
|
3726
|
+
throw new UiValueChannelInvariantError('frame-transaction-conflict', 'step-manual-frame-group')
|
|
3727
|
+
}
|
|
3728
|
+
next = {
|
|
3729
|
+
...next,
|
|
3730
|
+
identities,
|
|
3731
|
+
transaction: {
|
|
3732
|
+
kind: 'CLOSED',
|
|
3733
|
+
lastCommittedTimestampMs: absoluteTimestampMs,
|
|
3734
|
+
committedFrameCount: transaction.committedFrameCount + 1,
|
|
3735
|
+
},
|
|
3736
|
+
}
|
|
3737
|
+
next = replaceManualGroup(next, {
|
|
3738
|
+
...steppedGroup,
|
|
3739
|
+
currentAbsoluteTimestampMs: absoluteTimestampMs,
|
|
3740
|
+
explicitStepCount: steppedGroup.explicitStepCount + 1,
|
|
3741
|
+
})
|
|
3742
|
+
return uiTransition(next, effects)
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
// ——— Live UI destination COMMIT consumption ———
|
|
3746
|
+
|
|
3747
|
+
function refuseLiveUiDestinationCommit(
|
|
3748
|
+
state: UiLiveDestinationCommitConsumeState,
|
|
3749
|
+
commitSlotId: number,
|
|
3750
|
+
authority: UiPreparedDestinationRecordState | null,
|
|
3751
|
+
reason: UiLiveDestinationCommitRefusalReason,
|
|
3752
|
+
): UiLiveDestinationCommitTransition {
|
|
3753
|
+
return {
|
|
3754
|
+
state,
|
|
3755
|
+
outcome: { kind: 'REFUSED', commitSlotId, reason },
|
|
3756
|
+
orderedEffects: [
|
|
3757
|
+
{
|
|
3758
|
+
kind: 'report-ui-destination-commit-refusal',
|
|
3759
|
+
root: 'LIVE',
|
|
3760
|
+
commitSlotId,
|
|
3761
|
+
authority,
|
|
3762
|
+
reason,
|
|
3763
|
+
},
|
|
3764
|
+
],
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
export function consumeLiveUiDestinationCommit(
|
|
3769
|
+
state: UiLiveDestinationCommitConsumeState,
|
|
3770
|
+
request: UiLiveDestinationCommitConsumeRequest,
|
|
3771
|
+
validator: UiDestinationCommitCanonicalValidatorCapability,
|
|
3772
|
+
): UiLiveDestinationCommitTransition {
|
|
3773
|
+
const envelope = state.envelope
|
|
3774
|
+
const commit = request.commit
|
|
3775
|
+
if (envelope.state !== 'OPEN') {
|
|
3776
|
+
return refuseLiveUiDestinationCommit(state, commit.commitSlotId, null, 'envelope-not-open')
|
|
3777
|
+
}
|
|
3778
|
+
const outcome = validator.validate({
|
|
3779
|
+
commit,
|
|
3780
|
+
preparedDestinationRecords: state.coordinator.preparedDestinationRecords,
|
|
3781
|
+
})
|
|
3782
|
+
if (outcome.kind === 'REFUSED') {
|
|
3783
|
+
return refuseLiveUiDestinationCommit(
|
|
3784
|
+
state,
|
|
3785
|
+
commit.commitSlotId,
|
|
3786
|
+
outcome.authority,
|
|
3787
|
+
outcome.reason,
|
|
3788
|
+
)
|
|
3789
|
+
}
|
|
3790
|
+
const authority = outcome.authority
|
|
3791
|
+
const acknowledgementSlot = state.coordinator.destinationCommitAcknowledgementSlots.find(
|
|
3792
|
+
(candidate) => candidate.acknowledgementSlotId === commit.acknowledgementSlotId,
|
|
3793
|
+
)
|
|
3794
|
+
if (acknowledgementSlot === undefined) {
|
|
3795
|
+
return refuseLiveUiDestinationCommit(
|
|
3796
|
+
state,
|
|
3797
|
+
commit.commitSlotId,
|
|
3798
|
+
authority,
|
|
3799
|
+
'missing-acknowledgement-slot',
|
|
3800
|
+
)
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
const effects: UiLiveDestinationCommitEffect[] = [
|
|
3804
|
+
{
|
|
3805
|
+
kind: 'lookup-live-prepared-destination-record',
|
|
3806
|
+
destinationRecordId: commit.destinationRecordId,
|
|
3807
|
+
},
|
|
3808
|
+
{
|
|
3809
|
+
kind: 'validate-ui-destination-commit-with-canonical-authority',
|
|
3810
|
+
root: 'LIVE',
|
|
3811
|
+
commitSlotId: commit.commitSlotId,
|
|
3812
|
+
authority,
|
|
3813
|
+
validator,
|
|
3814
|
+
},
|
|
3815
|
+
]
|
|
3816
|
+
const newEntries = commit.includedPostFencePrefix.filter(
|
|
3817
|
+
(entry) => entry.authoritativeSequence > authority.appliedThroughAuthoritativeSequence,
|
|
3818
|
+
)
|
|
3819
|
+
const appliedThroughAuthoritativeSequence =
|
|
3820
|
+
newEntries.at(-1)?.authoritativeSequence ?? authority.appliedThroughAuthoritativeSequence
|
|
3821
|
+
const duplicate = newEntries.length === 0
|
|
3822
|
+
let preparedDestinationRecords = state.coordinator.preparedDestinationRecords
|
|
3823
|
+
if (!duplicate) {
|
|
3824
|
+
effects.push({
|
|
3825
|
+
kind: 'adopt-live-immutable-destination-commit',
|
|
3826
|
+
commitSlotId: commit.commitSlotId,
|
|
3827
|
+
})
|
|
3828
|
+
for (const entry of newEntries) {
|
|
3829
|
+
effects.push({
|
|
3830
|
+
kind: 'apply-live-included-post-fence-ledger-entry-in-place',
|
|
3831
|
+
commitSlotId: commit.commitSlotId,
|
|
3832
|
+
prefixIndex: commit.includedPostFencePrefix.indexOf(entry),
|
|
3833
|
+
destinationSlot: entry.destinationSlot,
|
|
3834
|
+
entry,
|
|
3835
|
+
})
|
|
3836
|
+
}
|
|
3837
|
+
preparedDestinationRecords = preparedDestinationRecords.map((candidate) =>
|
|
3838
|
+
candidate === authority
|
|
3839
|
+
? {
|
|
3840
|
+
...candidate,
|
|
3841
|
+
adoptedCommitSlotId: commit.commitSlotId,
|
|
3842
|
+
appliedThroughAuthoritativeSequence,
|
|
3843
|
+
ledgers: applyEntriesToUiLedgers(candidate.ledgers, newEntries),
|
|
3844
|
+
}
|
|
3845
|
+
: candidate,
|
|
3846
|
+
)
|
|
3847
|
+
}
|
|
3848
|
+
const acknowledgement: DestinationCommitAcknowledgement = {
|
|
3849
|
+
kind: 'DESTINATION_COMMIT_ACKNOWLEDGEMENT',
|
|
3850
|
+
channelId: commit.channelId,
|
|
3851
|
+
sourceRecordId: commit.sourceRecordId,
|
|
3852
|
+
destinationRecordId: commit.destinationRecordId,
|
|
3853
|
+
direction: commit.direction,
|
|
3854
|
+
transferLease: commit.transferLease,
|
|
3855
|
+
releaseFenceSequence: commit.releaseFenceSequence,
|
|
3856
|
+
drainedThroughAuthoritativeSequence: appliedThroughAuthoritativeSequence,
|
|
3857
|
+
}
|
|
3858
|
+
effects.push({
|
|
3859
|
+
kind: 'fill-live-destination-acknowledgement-slot',
|
|
3860
|
+
commitSlotId: commit.commitSlotId,
|
|
3861
|
+
acknowledgementSlotId: commit.acknowledgementSlotId,
|
|
3862
|
+
})
|
|
3863
|
+
effects.push({
|
|
3864
|
+
kind: 'stage-live-destination-acknowledgement-in-current-envelope',
|
|
3865
|
+
acknowledgementSlotId: commit.acknowledgementSlotId,
|
|
3866
|
+
mailboxDrainId: envelope.mailboxDrainId,
|
|
3867
|
+
})
|
|
3868
|
+
const entries: UiLiveDestinationEnvelopeEntry[] = [
|
|
3869
|
+
{
|
|
3870
|
+
kind: 'DESTINATION_COMMIT_ACKNOWLEDGEMENT',
|
|
3871
|
+
acknowledgementSlotId: commit.acknowledgementSlotId,
|
|
3872
|
+
drainedThroughAuthoritativeSequence: appliedThroughAuthoritativeSequence,
|
|
3873
|
+
},
|
|
3874
|
+
]
|
|
3875
|
+
for (const sibling of request.siblingEmissions) {
|
|
3876
|
+
effects.push({
|
|
3877
|
+
kind: 'emit-live-ui-sibling',
|
|
3878
|
+
emissionSequence: sibling.emissionSequence,
|
|
3879
|
+
emissionKind: sibling.emissionKind,
|
|
3880
|
+
})
|
|
3881
|
+
entries.push({
|
|
3882
|
+
kind: 'SIBLING_EMISSION',
|
|
3883
|
+
emissionSequence: sibling.emissionSequence,
|
|
3884
|
+
emissionKind: sibling.emissionKind,
|
|
3885
|
+
})
|
|
3886
|
+
}
|
|
3887
|
+
effects.push({
|
|
3888
|
+
kind: 'seal-live-ui-envelope-once',
|
|
3889
|
+
mailboxDrainId: envelope.mailboxDrainId,
|
|
3890
|
+
entryCount: entries.length,
|
|
3891
|
+
})
|
|
3892
|
+
const sealedEnvelope: UiLiveDestinationEnvelopeState = {
|
|
3893
|
+
...envelope,
|
|
3894
|
+
state: 'SEALED',
|
|
3895
|
+
entries,
|
|
3896
|
+
sealCount: envelope.sealCount + 1,
|
|
3897
|
+
}
|
|
3898
|
+
return {
|
|
3899
|
+
state: {
|
|
3900
|
+
coordinator: {
|
|
3901
|
+
...state.coordinator,
|
|
3902
|
+
preparedDestinationRecords,
|
|
3903
|
+
destinationCommitAcknowledgementSlots:
|
|
3904
|
+
state.coordinator.destinationCommitAcknowledgementSlots.map((candidate) =>
|
|
3905
|
+
candidate.acknowledgementSlotId === commit.acknowledgementSlotId
|
|
3906
|
+
? { ...candidate, state: 'STAGED_IN_CURRENT_ENVELOPE', acknowledgement }
|
|
3907
|
+
: candidate,
|
|
3908
|
+
),
|
|
3909
|
+
},
|
|
3910
|
+
envelope: sealedEnvelope,
|
|
3911
|
+
},
|
|
3912
|
+
outcome: duplicate
|
|
3913
|
+
? { kind: 'DUPLICATE_REACKNOWLEDGED', commitSlotId: commit.commitSlotId, acknowledgement }
|
|
3914
|
+
: { kind: 'APPLIED', commitSlotId: commit.commitSlotId, acknowledgement },
|
|
3915
|
+
orderedEffects: effects,
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
// ——— Shared manual identity transfer gap ———
|
|
3920
|
+
|
|
3921
|
+
export function stepSharedManualIdentityTransferGap(
|
|
3922
|
+
state: UiValueChannelCoordinatorState,
|
|
3923
|
+
request: UiSharedManualIdentityTransferGapRequest,
|
|
3924
|
+
): UiSharedManualIdentityTransferGapTransition {
|
|
3925
|
+
const identity = state.identities.find((candidate) => candidate.channelId === request.channelId)
|
|
3926
|
+
if (identity === undefined) {
|
|
3927
|
+
throw new UiValueChannelInvariantError(
|
|
3928
|
+
'shared-transfer-identity-missing',
|
|
3929
|
+
'step-shared-manual-identity-transfer-gap',
|
|
3930
|
+
)
|
|
3931
|
+
}
|
|
3932
|
+
const firstDriverId = request.orderedAttendeeDriverIds[0]
|
|
3933
|
+
const secondDriverId = request.orderedAttendeeDriverIds[1]
|
|
3934
|
+
const firstGeneration = request.callbackRegistrationGenerations[0]
|
|
3935
|
+
const secondGeneration = request.callbackRegistrationGenerations[1]
|
|
3936
|
+
const orderedEffects: UiSharedManualIdentityTransferGapEffect[] = [
|
|
3937
|
+
{
|
|
3938
|
+
kind: 'begin-shared-manual-transfer-transaction',
|
|
3939
|
+
groupId: request.groupId,
|
|
3940
|
+
channelId: request.channelId,
|
|
3941
|
+
absoluteTimestampMs: request.absoluteTimestampMs,
|
|
3942
|
+
orderedAttendeeDriverIds: request.orderedAttendeeDriverIds,
|
|
3943
|
+
callbackRegistrationGenerations: request.callbackRegistrationGenerations,
|
|
3944
|
+
},
|
|
3945
|
+
{
|
|
3946
|
+
kind: 'invoke-shared-manual-transfer-participant',
|
|
3947
|
+
groupId: request.groupId,
|
|
3948
|
+
driverId: firstDriverId,
|
|
3949
|
+
callbackRegistrationGeneration: firstGeneration,
|
|
3950
|
+
invocationIndex: 0,
|
|
3951
|
+
absoluteTimestampMs: request.absoluteTimestampMs,
|
|
3952
|
+
},
|
|
3953
|
+
{
|
|
3954
|
+
kind: 'bridge-shared-identity-transfer-gap',
|
|
3955
|
+
channelId: request.channelId,
|
|
3956
|
+
direction: request.direction,
|
|
3957
|
+
transferLease: request.transferLease,
|
|
3958
|
+
sourceRecordId: request.sourceRecordId,
|
|
3959
|
+
destinationRecordId: request.destinationRecordId,
|
|
3960
|
+
absoluteTimestampMs: request.absoluteTimestampMs,
|
|
3961
|
+
},
|
|
3962
|
+
{
|
|
3963
|
+
kind: 'invoke-shared-manual-transfer-participant',
|
|
3964
|
+
groupId: request.groupId,
|
|
3965
|
+
driverId: secondDriverId,
|
|
3966
|
+
callbackRegistrationGeneration: secondGeneration,
|
|
3967
|
+
invocationIndex: 1,
|
|
3968
|
+
absoluteTimestampMs: request.absoluteTimestampMs,
|
|
3969
|
+
},
|
|
3970
|
+
{
|
|
3971
|
+
kind: 'step-shared-manual-transfer-identity-once',
|
|
3972
|
+
channelId: request.channelId,
|
|
3973
|
+
absoluteTimestampMs: request.absoluteTimestampMs,
|
|
3974
|
+
},
|
|
3975
|
+
{
|
|
3976
|
+
kind: 'commit-shared-manual-transfer-transaction-once',
|
|
3977
|
+
groupId: request.groupId,
|
|
3978
|
+
absoluteTimestampMs: request.absoluteTimestampMs,
|
|
3979
|
+
},
|
|
3980
|
+
]
|
|
3981
|
+
const transaction = state.transaction
|
|
3982
|
+
const next: UiValueChannelCoordinatorState = {
|
|
3983
|
+
...state,
|
|
3984
|
+
identities: state.identities.map((candidate) =>
|
|
3985
|
+
candidate.channelId === request.channelId
|
|
3986
|
+
? {
|
|
3987
|
+
...candidate,
|
|
3988
|
+
lastAbsoluteTimestampMs: request.absoluteTimestampMs,
|
|
3989
|
+
stepCount: candidate.stepCount + 1,
|
|
3990
|
+
hostCommitCount: candidate.hostCommitCount + 1,
|
|
3991
|
+
}
|
|
3992
|
+
: candidate,
|
|
3993
|
+
),
|
|
3994
|
+
transaction: {
|
|
3995
|
+
kind: 'CLOSED',
|
|
3996
|
+
lastCommittedTimestampMs: request.absoluteTimestampMs,
|
|
3997
|
+
committedFrameCount: transaction.committedFrameCount + 1,
|
|
3998
|
+
},
|
|
3999
|
+
}
|
|
4000
|
+
return { state: next, observation: observeUiValueChannel(next), orderedEffects }
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
// ——— UI-owned identity sink fanout ———
|
|
4004
|
+
|
|
4005
|
+
export function fanoutUiOwnedIdentitySample(
|
|
4006
|
+
state: UiOwnedIdentitySampleFanoutState,
|
|
4007
|
+
request: UiOwnedIdentitySampleFanoutRequest,
|
|
4008
|
+
): UiOwnedIdentitySampleFanoutTransition {
|
|
4009
|
+
if (request.channelId !== state.channelId) {
|
|
4010
|
+
throw new UiValueChannelInvariantError(
|
|
4011
|
+
'owned-identity-channel-conflict',
|
|
4012
|
+
'fanout-ui-owned-identity-sample',
|
|
4013
|
+
)
|
|
4014
|
+
}
|
|
4015
|
+
const orderedEffects: UiOwnedIdentitySampleFanoutEffect[] = [
|
|
4016
|
+
{
|
|
4017
|
+
kind: 'adopt-ui-owned-identity-sample',
|
|
4018
|
+
channelId: request.channelId,
|
|
4019
|
+
executionId: request.executionId,
|
|
4020
|
+
value: request.value,
|
|
4021
|
+
writeSequence: request.writeSequence,
|
|
4022
|
+
absoluteTimestampMs: request.absoluteTimestampMs,
|
|
4023
|
+
},
|
|
4024
|
+
]
|
|
4025
|
+
const sinks = state.sinks.map((sink) => {
|
|
4026
|
+
orderedEffects.push({
|
|
4027
|
+
kind: 'fanout-ui-owned-identity-sample-to-sink',
|
|
4028
|
+
sinkId: sink.sinkId,
|
|
4029
|
+
driverId: sink.driverId,
|
|
4030
|
+
handle: sink.handle,
|
|
4031
|
+
key: sink.key,
|
|
4032
|
+
bindingGeneration: sink.bindingGeneration,
|
|
4033
|
+
value: request.value,
|
|
4034
|
+
writeSequence: request.writeSequence,
|
|
4035
|
+
})
|
|
4036
|
+
return {
|
|
4037
|
+
...sink,
|
|
4038
|
+
lastCommittedValue: request.value,
|
|
4039
|
+
lastCommittedWriteSequence: request.writeSequence,
|
|
4040
|
+
commitCount: sink.commitCount + 1,
|
|
4041
|
+
}
|
|
4042
|
+
})
|
|
4043
|
+
orderedEffects.push({
|
|
4044
|
+
kind: 'commit-ui-owned-identity-sinks-once',
|
|
4045
|
+
channelId: request.channelId,
|
|
4046
|
+
sinkIds: sinks.map((sink) => sink.sinkId),
|
|
4047
|
+
absoluteTimestampMs: request.absoluteTimestampMs,
|
|
4048
|
+
})
|
|
4049
|
+
const next: UiOwnedIdentitySampleFanoutState = {
|
|
4050
|
+
...state,
|
|
4051
|
+
lastAcceptedAbsoluteTimestampMs: request.absoluteTimestampMs,
|
|
4052
|
+
sinks,
|
|
4053
|
+
uiSinkCommitCount: state.uiSinkCommitCount + 1,
|
|
4054
|
+
}
|
|
4055
|
+
return {
|
|
4056
|
+
state: next,
|
|
4057
|
+
observation: {
|
|
4058
|
+
channelId: next.channelId,
|
|
4059
|
+
sinkCount: next.sinks.length,
|
|
4060
|
+
committedSinkCount: next.sinks.length,
|
|
4061
|
+
committedSinkIds: next.sinks.map((sink) => sink.sinkId),
|
|
4062
|
+
uiSinkCommitCount: next.uiSinkCommitCount,
|
|
4063
|
+
jsToUiWriteLaneInvocationCount: next.jsToUiWriteLaneInvocationCount,
|
|
4064
|
+
},
|
|
4065
|
+
orderedEffects,
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
// ——— Legacy manual driver.step compatibility ———
|
|
4070
|
+
|
|
4071
|
+
export function stepLegacyManualDriver(
|
|
4072
|
+
state: UiValueChannelCoordinatorState,
|
|
4073
|
+
request: LegacyManualDriverStepRequest,
|
|
4074
|
+
): UiValueChannelTransition {
|
|
4075
|
+
const group = requireManualGroup(state, request.groupId, 'step-legacy-manual-driver')
|
|
4076
|
+
if (
|
|
4077
|
+
group.participantDriverIds.length !== 1 ||
|
|
4078
|
+
group.participantDriverIds[0] !== request.driverId
|
|
4079
|
+
) {
|
|
4080
|
+
return uiTransition(state, [], {
|
|
4081
|
+
kind: 'MULTI_MEMBER_GROUP_REQUIRES_GROUP_STEP',
|
|
4082
|
+
groupId: request.groupId,
|
|
4083
|
+
participantCount: group.participantDriverIds.length,
|
|
4084
|
+
})
|
|
4085
|
+
}
|
|
4086
|
+
const absoluteTimestampMs = group.currentAbsoluteTimestampMs + request.elapsedMs
|
|
4087
|
+
const effects: UiValueChannelEffect[] = [
|
|
4088
|
+
{
|
|
4089
|
+
kind: 'delegate-legacy-step-to-private-single-member-group',
|
|
4090
|
+
groupId: request.groupId,
|
|
4091
|
+
driverId: request.driverId,
|
|
4092
|
+
},
|
|
4093
|
+
{ kind: 'advance-manual-group-clock-once', groupId: request.groupId, absoluteTimestampMs },
|
|
4094
|
+
{
|
|
4095
|
+
kind: 'freeze-manual-attendee-batch',
|
|
4096
|
+
groupId: request.groupId,
|
|
4097
|
+
orderedAttendeeDriverIds: [request.driverId],
|
|
4098
|
+
},
|
|
4099
|
+
]
|
|
4100
|
+
const identities = state.identities.map((identity) => {
|
|
4101
|
+
const deltaMs =
|
|
4102
|
+
identity.lastAbsoluteTimestampMs === null
|
|
4103
|
+
? 0
|
|
4104
|
+
: absoluteTimestampMs - identity.lastAbsoluteTimestampMs
|
|
4105
|
+
effects.push({
|
|
4106
|
+
kind: 'step-shared-identity-once',
|
|
4107
|
+
channelId: identity.channelId,
|
|
4108
|
+
absoluteTimestampMs,
|
|
4109
|
+
deltaMs,
|
|
4110
|
+
})
|
|
4111
|
+
return {
|
|
4112
|
+
...identity,
|
|
4113
|
+
lastAbsoluteTimestampMs: absoluteTimestampMs,
|
|
4114
|
+
stepCount: identity.stepCount + 1,
|
|
4115
|
+
hostCommitCount: identity.hostCommitCount + 1,
|
|
4116
|
+
}
|
|
4117
|
+
})
|
|
4118
|
+
effects.push({ kind: 'commit-frame-once', absoluteTimestampMs })
|
|
4119
|
+
const transaction = state.transaction
|
|
4120
|
+
if (transaction.kind !== 'CLOSED') {
|
|
4121
|
+
throw new UiValueChannelInvariantError(
|
|
4122
|
+
'frame-transaction-conflict',
|
|
4123
|
+
'step-legacy-manual-driver',
|
|
4124
|
+
)
|
|
4125
|
+
}
|
|
4126
|
+
const next = replaceManualGroup(
|
|
4127
|
+
{
|
|
4128
|
+
...state,
|
|
4129
|
+
identities,
|
|
4130
|
+
transaction: {
|
|
4131
|
+
kind: 'CLOSED',
|
|
4132
|
+
lastCommittedTimestampMs: absoluteTimestampMs,
|
|
4133
|
+
committedFrameCount: transaction.committedFrameCount + 1,
|
|
4134
|
+
},
|
|
4135
|
+
},
|
|
4136
|
+
{
|
|
4137
|
+
...group,
|
|
4138
|
+
currentAbsoluteTimestampMs: absoluteTimestampMs,
|
|
4139
|
+
explicitStepCount: group.explicitStepCount + 1,
|
|
4140
|
+
},
|
|
4141
|
+
)
|
|
4142
|
+
return uiTransition(next, effects)
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
// ——— UI-owned private target-batch dependency events ———
|
|
4146
|
+
|
|
4147
|
+
export function stageUiPrivateTargetBatchDependencyEvent(
|
|
4148
|
+
coordinator: UiValueChannelCoordinatorState,
|
|
4149
|
+
adapter: UiPrivateTargetBatchAdapterState,
|
|
4150
|
+
request: UiPrivateTargetBatchDependencyEventRequest,
|
|
4151
|
+
): UiPrivateTargetBatchDependencyEventTransition {
|
|
4152
|
+
if (
|
|
4153
|
+
request.controlId !== adapter.controlId ||
|
|
4154
|
+
request.batchId !== adapter.batchId ||
|
|
4155
|
+
request.dependencyId !== adapter.dependencyId
|
|
4156
|
+
) {
|
|
4157
|
+
throw new UiValueChannelInvariantError(
|
|
4158
|
+
'private-target-batch-adapter-conflict',
|
|
4159
|
+
'stage-private-target-batch-dependency-event',
|
|
4160
|
+
)
|
|
4161
|
+
}
|
|
4162
|
+
const transaction = coordinator.transaction
|
|
4163
|
+
if (transaction.kind !== 'OPEN') {
|
|
4164
|
+
throw new UiValueChannelInvariantError(
|
|
4165
|
+
'private-target-batch-transaction-closed',
|
|
4166
|
+
'stage-private-target-batch-dependency-event',
|
|
4167
|
+
)
|
|
4168
|
+
}
|
|
4169
|
+
const eventSequence = adapter.nextDependencyEventSequence
|
|
4170
|
+
return {
|
|
4171
|
+
adapter: {
|
|
4172
|
+
...adapter,
|
|
4173
|
+
nextDependencyEventSequence: eventSequence + 1,
|
|
4174
|
+
stagedDependencyEventCount: adapter.stagedDependencyEventCount + 1,
|
|
4175
|
+
},
|
|
4176
|
+
orderedEffects: [
|
|
4177
|
+
{
|
|
4178
|
+
kind: 'PREFILL_PRIVATE_TARGET_BATCH_DEPENDENCY_EVENT',
|
|
4179
|
+
transactionMode: transaction.mode,
|
|
4180
|
+
event: {
|
|
4181
|
+
kind: 'TARGET_BATCH_DEPENDENCY_EVENT',
|
|
4182
|
+
batchId: adapter.batchId,
|
|
4183
|
+
dependencyId: adapter.dependencyId,
|
|
4184
|
+
dependencyEventKind: request.kind,
|
|
4185
|
+
eventSequence,
|
|
4186
|
+
ownerControlId: adapter.controlId,
|
|
4187
|
+
},
|
|
4188
|
+
},
|
|
4189
|
+
{
|
|
4190
|
+
kind: 'ADVANCE_PRIVATE_TARGET_BATCH_EVENT_SEQUENCE',
|
|
4191
|
+
controlId: adapter.controlId,
|
|
4192
|
+
eventSequence,
|
|
4193
|
+
},
|
|
4194
|
+
],
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
// ——— Runner admission timeout and reselection ———
|
|
4199
|
+
|
|
4200
|
+
function staleRunnerEvent(
|
|
4201
|
+
state: UiRunnerAdmissionState,
|
|
4202
|
+
driverId: number,
|
|
4203
|
+
): UiRunnerAdmissionTransition {
|
|
4204
|
+
return { state, orderedEffects: [{ kind: 'IGNORE_STALE_RUNNER_EVENT', driverId }] }
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
function requireCurrentRunnerCandidate(
|
|
4208
|
+
state: UiRunnerAdmissionState,
|
|
4209
|
+
event: UiRunnerAdmissionEvent,
|
|
4210
|
+
): UiRunnerAdmissionCandidateState | null {
|
|
4211
|
+
const candidate = state.candidates.find(
|
|
4212
|
+
(registration) => registration.driverId === event.driverId,
|
|
4213
|
+
)
|
|
4214
|
+
if (
|
|
4215
|
+
candidate === undefined ||
|
|
4216
|
+
candidate.state !== 'RUNNER_CANDIDATE' ||
|
|
4217
|
+
candidate.callbackRegistrationGeneration !== event.callbackRegistrationGeneration ||
|
|
4218
|
+
candidate.callbackLeaseEpoch !== event.callbackLeaseEpoch
|
|
4219
|
+
) {
|
|
4220
|
+
return null
|
|
4221
|
+
}
|
|
4222
|
+
return candidate
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
function transferRetainedChannels(
|
|
4226
|
+
state: UiRunnerAdmissionState,
|
|
4227
|
+
effects: UiRunnerAdmissionEffect[],
|
|
4228
|
+
retainedWorkRuntime: UiRunnerRetainedWorkRuntime,
|
|
4229
|
+
): UiRunnerAdmissionState {
|
|
4230
|
+
let retainedWorkTransferRequestCount = state.retainedWorkTransferRequestCount
|
|
4231
|
+
for (const channel of state.retainedChannels) {
|
|
4232
|
+
retainedWorkRuntime.transferRetainedChannelWork(channel)
|
|
4233
|
+
effects.push({ kind: 'TRANSFER_RETAINED_CHANNEL_WORK', channel })
|
|
4234
|
+
retainedWorkTransferRequestCount += 1
|
|
4235
|
+
}
|
|
4236
|
+
return { ...state, retainedWorkTransferRequestCount }
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
export function advanceUiRunnerAdmission(
|
|
4240
|
+
state: UiRunnerAdmissionState,
|
|
4241
|
+
event: UiRunnerAdmissionEvent,
|
|
4242
|
+
activators: UiRetainedFrameCallbackActivatorRegistry,
|
|
4243
|
+
retainedWorkRuntime: UiRunnerRetainedWorkRuntime,
|
|
4244
|
+
): UiRunnerAdmissionTransition {
|
|
4245
|
+
switch (event.kind) {
|
|
4246
|
+
case 'runner-candidate-timeout': {
|
|
4247
|
+
const candidate = requireCurrentRunnerCandidate(state, event)
|
|
4248
|
+
if (candidate === null) return staleRunnerEvent(state, event.driverId)
|
|
4249
|
+
const effects: UiRunnerAdmissionEffect[] = [
|
|
4250
|
+
{ kind: 'EXPIRE_RUNNER_CANDIDATE', driverId: event.driverId },
|
|
4251
|
+
]
|
|
4252
|
+
activators.setActive(event.driverId, event.callbackRegistrationGeneration, false)
|
|
4253
|
+
const candidates = state.candidates.map((registration) =>
|
|
4254
|
+
registration.driverId === event.driverId
|
|
4255
|
+
? { ...registration, state: 'EXPIRED' as const }
|
|
4256
|
+
: registration,
|
|
4257
|
+
)
|
|
4258
|
+
const successor = candidates.find(
|
|
4259
|
+
(registration) =>
|
|
4260
|
+
registration.driverId !== event.driverId && registration.state === 'RUNNER_CANDIDATE',
|
|
4261
|
+
)
|
|
4262
|
+
if (successor !== undefined) {
|
|
4263
|
+
activators.setActive(successor.driverId, successor.callbackRegistrationGeneration, true)
|
|
4264
|
+
effects.push({
|
|
4265
|
+
kind: 'SELECT_SUCCESSOR_RUNNER_CANDIDATE',
|
|
4266
|
+
driverId: successor.driverId,
|
|
4267
|
+
callbackLeaseEpoch: successor.callbackLeaseEpoch,
|
|
4268
|
+
})
|
|
4269
|
+
return {
|
|
4270
|
+
state: {
|
|
4271
|
+
...state,
|
|
4272
|
+
candidates,
|
|
4273
|
+
runnerDriverId: successor.driverId,
|
|
4274
|
+
runnerLeaseEpoch: successor.callbackLeaseEpoch,
|
|
4275
|
+
},
|
|
4276
|
+
orderedEffects: effects,
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
const channelIds = state.retainedChannels.map((channel) => channel.channelId)
|
|
4280
|
+
retainedWorkRuntime.reportFinalRunnerTimeoutRefusal(channelIds)
|
|
4281
|
+
effects.push({ kind: 'REPORT_FINAL_RUNNER_TIMEOUT_REFUSAL', channelIds })
|
|
4282
|
+
for (const channel of state.retainedChannels) {
|
|
4283
|
+
if (channel.installedPreArmControlSlot !== null) {
|
|
4284
|
+
retainedWorkRuntime.stopInstalledPreArmSlot(
|
|
4285
|
+
channel.channelId,
|
|
4286
|
+
channel.installedPreArmControlSlot,
|
|
4287
|
+
)
|
|
4288
|
+
effects.push({
|
|
4289
|
+
kind: 'STOP_INSTALLED_PRE_ARM_SLOT_ONLY',
|
|
4290
|
+
channelId: channel.channelId,
|
|
4291
|
+
slot: channel.installedPreArmControlSlot,
|
|
4292
|
+
})
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
const transferred = transferRetainedChannels(
|
|
4296
|
+
{
|
|
4297
|
+
...state,
|
|
4298
|
+
candidates,
|
|
4299
|
+
runnerDriverId: null,
|
|
4300
|
+
runnerLeaseEpoch: 0,
|
|
4301
|
+
timeoutRefusalCount: state.timeoutRefusalCount + 1,
|
|
4302
|
+
},
|
|
4303
|
+
effects,
|
|
4304
|
+
retainedWorkRuntime,
|
|
4305
|
+
)
|
|
4306
|
+
return { state: transferred, orderedEffects: effects }
|
|
4307
|
+
}
|
|
4308
|
+
case 'invoke-runner-candidate': {
|
|
4309
|
+
const candidate = requireCurrentRunnerCandidate(state, event)
|
|
4310
|
+
if (candidate === null) return staleRunnerEvent(state, event.driverId)
|
|
4311
|
+
const effects: UiRunnerAdmissionEffect[] = [
|
|
4312
|
+
{
|
|
4313
|
+
kind: 'ADMIT_INVOKED_RUNNER_CANDIDATE',
|
|
4314
|
+
driverId: event.driverId,
|
|
4315
|
+
callbackLeaseEpoch: candidate.callbackLeaseEpoch,
|
|
4316
|
+
},
|
|
4317
|
+
]
|
|
4318
|
+
const admitted: UiRunnerAdmissionState = {
|
|
4319
|
+
...state,
|
|
4320
|
+
candidates: state.candidates.map((registration) =>
|
|
4321
|
+
registration.driverId === event.driverId
|
|
4322
|
+
? { ...registration, state: 'ADMITTED' as const, invocationObserved: true }
|
|
4323
|
+
: registration,
|
|
4324
|
+
),
|
|
4325
|
+
runnerDriverId: event.driverId,
|
|
4326
|
+
runnerLeaseEpoch: candidate.callbackLeaseEpoch,
|
|
4327
|
+
}
|
|
4328
|
+
const transferred = transferRetainedChannels(admitted, effects, retainedWorkRuntime)
|
|
4329
|
+
return { state: transferred, orderedEffects: effects }
|
|
4330
|
+
}
|
|
4331
|
+
case 'dispose-runner-candidate': {
|
|
4332
|
+
const candidate = requireCurrentRunnerCandidate(state, event)
|
|
4333
|
+
if (candidate === null) return staleRunnerEvent(state, event.driverId)
|
|
4334
|
+
const effects: UiRunnerAdmissionEffect[] = [
|
|
4335
|
+
{ kind: 'DISPOSE_RUNNER_CANDIDATE', driverId: event.driverId },
|
|
4336
|
+
]
|
|
4337
|
+
activators.setActive(event.driverId, event.callbackRegistrationGeneration, false)
|
|
4338
|
+
const candidates = state.candidates.map((registration) =>
|
|
4339
|
+
registration.driverId === event.driverId
|
|
4340
|
+
? { ...registration, state: 'DISPOSED' as const }
|
|
4341
|
+
: registration,
|
|
4342
|
+
)
|
|
4343
|
+
const successor = candidates.find(
|
|
4344
|
+
(registration) =>
|
|
4345
|
+
registration.driverId !== event.driverId && registration.state === 'RUNNER_CANDIDATE',
|
|
4346
|
+
)
|
|
4347
|
+
if (successor !== undefined) {
|
|
4348
|
+
activators.setActive(successor.driverId, successor.callbackRegistrationGeneration, true)
|
|
4349
|
+
effects.push({
|
|
4350
|
+
kind: 'SELECT_SUCCESSOR_RUNNER_CANDIDATE',
|
|
4351
|
+
driverId: successor.driverId,
|
|
4352
|
+
callbackLeaseEpoch: successor.callbackLeaseEpoch,
|
|
4353
|
+
})
|
|
4354
|
+
return {
|
|
4355
|
+
state: {
|
|
4356
|
+
...state,
|
|
4357
|
+
candidates,
|
|
4358
|
+
runnerDriverId: successor.driverId,
|
|
4359
|
+
runnerLeaseEpoch: successor.callbackLeaseEpoch,
|
|
4360
|
+
},
|
|
4361
|
+
orderedEffects: effects,
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
return {
|
|
4365
|
+
state: { ...state, candidates, runnerDriverId: null, runnerLeaseEpoch: 0 },
|
|
4366
|
+
orderedEffects: effects,
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
default: {
|
|
4370
|
+
const exhaustive: never = event
|
|
4371
|
+
void exhaustive
|
|
4372
|
+
throw new UiValueChannelInvariantError('runner-admission-event-kind-unrecognized')
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4377
|
+
// ——— Primitive listener-recursion reducer ———
|
|
4378
|
+
|
|
4379
|
+
/** Mutable working copy of one execution record; returned to the immutable state at the end. */
|
|
4380
|
+
interface UiRecursionWorkingExecution {
|
|
4381
|
+
executionId: number
|
|
4382
|
+
targetValue: number
|
|
4383
|
+
firstSampleValue: number
|
|
4384
|
+
commandSequence: number
|
|
4385
|
+
completionSequence: number
|
|
4386
|
+
updateSequence: number
|
|
4387
|
+
stopCount: number
|
|
4388
|
+
phase: UiRecursionExecutionPhase
|
|
4389
|
+
orphaned: boolean
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
interface UiRecursionWorkingCancelListener {
|
|
4393
|
+
listenerId: number
|
|
4394
|
+
hasStartedSuccessor: boolean
|
|
4395
|
+
successor: UiRecursionExecutionSeed
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
interface UiRecursionWorkingState {
|
|
4399
|
+
channelId: number
|
|
4400
|
+
currentValue: number
|
|
4401
|
+
publicControlSlotExecutionId: number | null
|
|
4402
|
+
executions: UiRecursionWorkingExecution[]
|
|
4403
|
+
queuedInstantCompletions: readonly UiQueuedInstantCompletionRecursionEntry[]
|
|
4404
|
+
guardedCancelListeners: UiRecursionWorkingCancelListener[]
|
|
4405
|
+
nextUpdateSequence: number
|
|
4406
|
+
cancelDeliveryOrdinal: number
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
function installRecursionSuccessor(
|
|
4410
|
+
working: UiRecursionWorkingState,
|
|
4411
|
+
successor: UiRecursionExecutionSeed,
|
|
4412
|
+
role: Extract<
|
|
4413
|
+
UiValueChannelRecursionEffect,
|
|
4414
|
+
{ readonly kind: 'INSTALL_RECURSION_EXECUTION' }
|
|
4415
|
+
>['role'],
|
|
4416
|
+
effects: UiValueChannelRecursionEffect[],
|
|
4417
|
+
): void {
|
|
4418
|
+
if (working.publicControlSlotExecutionId !== null) {
|
|
4419
|
+
stopRecursionControl(working, working.publicControlSlotExecutionId, 'REENTRANT_START', effects)
|
|
4420
|
+
}
|
|
4421
|
+
const updateSequence = working.nextUpdateSequence
|
|
4422
|
+
working.nextUpdateSequence += 1
|
|
4423
|
+
effects.push({
|
|
4424
|
+
kind: 'INSTALL_RECURSION_EXECUTION',
|
|
4425
|
+
executionId: successor.executionId,
|
|
4426
|
+
commandSequence: successor.commandSequence,
|
|
4427
|
+
updateSequence,
|
|
4428
|
+
role,
|
|
4429
|
+
})
|
|
4430
|
+
working.executions.push({
|
|
4431
|
+
...successor,
|
|
4432
|
+
updateSequence,
|
|
4433
|
+
stopCount: 0,
|
|
4434
|
+
phase: 'RUNNING',
|
|
4435
|
+
orphaned: false,
|
|
4436
|
+
})
|
|
4437
|
+
working.publicControlSlotExecutionId = successor.executionId
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
function clearRecursionPublicSlotTokenBlind(
|
|
4441
|
+
working: UiRecursionWorkingState,
|
|
4442
|
+
source: 'INSTANT_COMPLETION' | 'STOP_CONTINUATION' | 'VALUE_COMPLETION',
|
|
4443
|
+
sourceId: number,
|
|
4444
|
+
effects: UiValueChannelRecursionEffect[],
|
|
4445
|
+
): void {
|
|
4446
|
+
const clearedExecutionId = working.publicControlSlotExecutionId
|
|
4447
|
+
effects.push({
|
|
4448
|
+
kind: 'CLEAR_PUBLIC_CONTROL_SLOT_TOKEN_BLIND',
|
|
4449
|
+
source,
|
|
4450
|
+
sourceId,
|
|
4451
|
+
clearedExecutionId,
|
|
4452
|
+
})
|
|
4453
|
+
if (clearedExecutionId !== null) {
|
|
4454
|
+
const cleared = working.executions.find(
|
|
4455
|
+
(candidate) => candidate.executionId === clearedExecutionId,
|
|
4456
|
+
)
|
|
4457
|
+
if (cleared !== undefined) cleared.orphaned = true
|
|
4458
|
+
}
|
|
4459
|
+
working.publicControlSlotExecutionId = null
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4462
|
+
function stopRecursionControl(
|
|
4463
|
+
working: UiRecursionWorkingState,
|
|
4464
|
+
controlExecutionId: number,
|
|
4465
|
+
source: 'OUTER_OPERATION' | 'REENTRANT_START',
|
|
4466
|
+
effects: UiValueChannelRecursionEffect[],
|
|
4467
|
+
): void {
|
|
4468
|
+
const control = working.executions.find(
|
|
4469
|
+
(candidate) => candidate.executionId === controlExecutionId,
|
|
4470
|
+
)
|
|
4471
|
+
if (control !== undefined) {
|
|
4472
|
+
control.stopCount += 1
|
|
4473
|
+
control.phase = 'STOPPED'
|
|
4474
|
+
effects.push({
|
|
4475
|
+
kind: 'STOP_EXECUTION_IDEMPOTENT',
|
|
4476
|
+
executionId: controlExecutionId,
|
|
4477
|
+
stopOrdinal: control.stopCount,
|
|
4478
|
+
source,
|
|
4479
|
+
})
|
|
4480
|
+
}
|
|
4481
|
+
for (const listener of working.guardedCancelListeners) {
|
|
4482
|
+
if (listener.hasStartedSuccessor) continue
|
|
4483
|
+
listener.hasStartedSuccessor = true
|
|
4484
|
+
working.cancelDeliveryOrdinal += 1
|
|
4485
|
+
effects.push({
|
|
4486
|
+
kind: 'DELIVER_GUARDED_CANCEL_LISTENER',
|
|
4487
|
+
listenerId: listener.listenerId,
|
|
4488
|
+
cancelDeliveryOrdinal: working.cancelDeliveryOrdinal,
|
|
4489
|
+
})
|
|
4490
|
+
effects.push({
|
|
4491
|
+
kind: 'ISSUE_REENTRANT_START_COMMAND',
|
|
4492
|
+
executionId: listener.successor.executionId,
|
|
4493
|
+
commandSequence: listener.successor.commandSequence,
|
|
4494
|
+
source: 'GUARDED_CANCEL_LISTENER',
|
|
4495
|
+
sourceId: listener.listenerId,
|
|
4496
|
+
})
|
|
4497
|
+
installRecursionSuccessor(working, listener.successor, 'CANCEL_LISTENER_SUCCESSOR', effects)
|
|
4498
|
+
}
|
|
4499
|
+
clearRecursionPublicSlotTokenBlind(working, 'STOP_CONTINUATION', controlExecutionId, effects)
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
export function advanceUiValueChannelRecursion(
|
|
4503
|
+
state: UiValueChannelRecursionState,
|
|
4504
|
+
event: UiValueChannelRecursionEvent,
|
|
4505
|
+
): UiValueChannelRecursionTransition {
|
|
4506
|
+
const working: UiRecursionWorkingState = {
|
|
4507
|
+
channelId: state.channelId,
|
|
4508
|
+
currentValue: state.currentValue,
|
|
4509
|
+
publicControlSlotExecutionId: state.publicControlSlotExecutionId,
|
|
4510
|
+
executions: state.executions.map((execution) => ({ ...execution })),
|
|
4511
|
+
queuedInstantCompletions: state.queuedInstantCompletions,
|
|
4512
|
+
guardedCancelListeners: state.guardedCancelListeners.map((listener) => ({ ...listener })),
|
|
4513
|
+
nextUpdateSequence: state.nextUpdateSequence,
|
|
4514
|
+
cancelDeliveryOrdinal: 0,
|
|
4515
|
+
}
|
|
4516
|
+
const effects: UiValueChannelRecursionEffect[] = []
|
|
4517
|
+
switch (event.kind) {
|
|
4518
|
+
case 'drain-queued-instant-completions': {
|
|
4519
|
+
for (const queued of working.queuedInstantCompletions) {
|
|
4520
|
+
effects.push({
|
|
4521
|
+
kind: 'DELIVER_QUEUED_INSTANT_COMPLETION',
|
|
4522
|
+
completionId: queued.completionId,
|
|
4523
|
+
completionSequence: queued.completionSequence,
|
|
4524
|
+
})
|
|
4525
|
+
effects.push({
|
|
4526
|
+
kind: 'ISSUE_REENTRANT_START_COMMAND',
|
|
4527
|
+
executionId: queued.callbackSuccessor.executionId,
|
|
4528
|
+
commandSequence: queued.callbackSuccessor.commandSequence,
|
|
4529
|
+
source: 'INSTANT_COMPLETION',
|
|
4530
|
+
sourceId: queued.completionId,
|
|
4531
|
+
})
|
|
4532
|
+
installRecursionSuccessor(
|
|
4533
|
+
working,
|
|
4534
|
+
queued.callbackSuccessor,
|
|
4535
|
+
'COMPLETION_SUCCESSOR',
|
|
4536
|
+
effects,
|
|
4537
|
+
)
|
|
4538
|
+
clearRecursionPublicSlotTokenBlind(
|
|
4539
|
+
working,
|
|
4540
|
+
'INSTANT_COMPLETION',
|
|
4541
|
+
queued.completionId,
|
|
4542
|
+
effects,
|
|
4543
|
+
)
|
|
4544
|
+
}
|
|
4545
|
+
working.queuedInstantCompletions = []
|
|
4546
|
+
break
|
|
4547
|
+
}
|
|
4548
|
+
case 'apply-recursive-outer-stop': {
|
|
4549
|
+
if (working.publicControlSlotExecutionId !== null) {
|
|
4550
|
+
stopRecursionControl(
|
|
4551
|
+
working,
|
|
4552
|
+
working.publicControlSlotExecutionId,
|
|
4553
|
+
'OUTER_OPERATION',
|
|
4554
|
+
effects,
|
|
4555
|
+
)
|
|
4556
|
+
}
|
|
4557
|
+
break
|
|
4558
|
+
}
|
|
4559
|
+
case 'apply-recursive-outer-jump': {
|
|
4560
|
+
if (working.publicControlSlotExecutionId !== null) {
|
|
4561
|
+
stopRecursionControl(
|
|
4562
|
+
working,
|
|
4563
|
+
working.publicControlSlotExecutionId,
|
|
4564
|
+
'OUTER_OPERATION',
|
|
4565
|
+
effects,
|
|
4566
|
+
)
|
|
4567
|
+
}
|
|
4568
|
+
effects.push({ kind: 'APPLY_OUTER_JUMP', value: event.value })
|
|
4569
|
+
working.currentValue = event.value
|
|
4570
|
+
break
|
|
4571
|
+
}
|
|
4572
|
+
case 'apply-recursive-interrupting-start': {
|
|
4573
|
+
if (working.publicControlSlotExecutionId !== null) {
|
|
4574
|
+
stopRecursionControl(
|
|
4575
|
+
working,
|
|
4576
|
+
working.publicControlSlotExecutionId,
|
|
4577
|
+
'OUTER_OPERATION',
|
|
4578
|
+
effects,
|
|
4579
|
+
)
|
|
4580
|
+
}
|
|
4581
|
+
installRecursionSuccessor(working, event.successor, 'INTERRUPTING_SUCCESSOR', effects)
|
|
4582
|
+
break
|
|
4583
|
+
}
|
|
4584
|
+
case 'install-recursion-successor': {
|
|
4585
|
+
installRecursionSuccessor(working, event.successor, 'LATER_LIVE_SUCCESSOR', effects)
|
|
4586
|
+
break
|
|
4587
|
+
}
|
|
4588
|
+
case 'sample-runnable-recursion-executions': {
|
|
4589
|
+
for (const execution of working.executions) {
|
|
4590
|
+
if (execution.phase !== 'RUNNING') continue
|
|
4591
|
+
effects.push({
|
|
4592
|
+
kind: 'SAMPLE_RECURSION_EXECUTION',
|
|
4593
|
+
executionId: execution.executionId,
|
|
4594
|
+
updateSequence: execution.updateSequence,
|
|
4595
|
+
value: execution.firstSampleValue,
|
|
4596
|
+
absoluteTimestampMs: event.absoluteTimestampMs,
|
|
4597
|
+
orphaned: execution.orphaned,
|
|
4598
|
+
})
|
|
4599
|
+
working.currentValue = execution.firstSampleValue
|
|
4600
|
+
}
|
|
4601
|
+
break
|
|
4602
|
+
}
|
|
4603
|
+
case 'complete-recursion-execution': {
|
|
4604
|
+
const execution = working.executions.find(
|
|
4605
|
+
(candidate) => candidate.executionId === event.executionId,
|
|
4606
|
+
)
|
|
4607
|
+
if (execution === undefined) {
|
|
4608
|
+
throw new UiValueChannelInvariantError(
|
|
4609
|
+
'recursion-event-kind-unrecognized',
|
|
4610
|
+
'complete-recursion-execution',
|
|
4611
|
+
)
|
|
4612
|
+
}
|
|
4613
|
+
execution.phase = 'COMPLETED'
|
|
4614
|
+
effects.push({
|
|
4615
|
+
kind: 'COMPLETE_RECURSION_EXECUTION',
|
|
4616
|
+
executionId: event.executionId,
|
|
4617
|
+
completionSequence: event.completionSequence,
|
|
4618
|
+
})
|
|
4619
|
+
clearRecursionPublicSlotTokenBlind(working, 'VALUE_COMPLETION', event.executionId, effects)
|
|
4620
|
+
break
|
|
4621
|
+
}
|
|
4622
|
+
default: {
|
|
4623
|
+
const exhaustive: never = event
|
|
4624
|
+
void exhaustive
|
|
4625
|
+
throw new UiValueChannelInvariantError('recursion-event-kind-unrecognized')
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
return {
|
|
4629
|
+
state: {
|
|
4630
|
+
channelId: working.channelId,
|
|
4631
|
+
currentValue: working.currentValue,
|
|
4632
|
+
publicControlSlotExecutionId: working.publicControlSlotExecutionId,
|
|
4633
|
+
executions: working.executions,
|
|
4634
|
+
queuedInstantCompletions: working.queuedInstantCompletions,
|
|
4635
|
+
guardedCancelListeners: working.guardedCancelListeners,
|
|
4636
|
+
nextUpdateSequence: working.nextUpdateSequence,
|
|
4637
|
+
},
|
|
4638
|
+
orderedEffects: effects,
|
|
4639
|
+
}
|
|
4640
|
+
}
|