@mindot/will 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +777 -0
  3. package/dist/index.d.ts +8144 -0
  4. package/dist/index.js +26231 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +73 -0
  7. package/src/cognition/agency/access.grants.ts +59 -0
  8. package/src/cognition/agency/competence.codec.ts +89 -0
  9. package/src/cognition/agency/engines/action.selector.ts +462 -0
  10. package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
  11. package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
  12. package/src/cognition/agency/engines/instruction.intake.ts +54 -0
  13. package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
  14. package/src/cognition/agency/engines/reafference.engine.ts +241 -0
  15. package/src/cognition/agency/execution.primitives.ts +118 -0
  16. package/src/cognition/agency/index.ts +58 -0
  17. package/src/cognition/agency/proactive.communicator.ts +274 -0
  18. package/src/cognition/agency/reconcile.learning.ts +82 -0
  19. package/src/cognition/agency/schemas/external.ts +59 -0
  20. package/src/cognition/agency/schemas/innate.ts +111 -0
  21. package/src/cognition/agency/schemas/repertoire.ts +246 -0
  22. package/src/cognition/agency/selection.scoring.ts +191 -0
  23. package/src/cognition/agency/types.ts +165 -0
  24. package/src/cognition/bus.ts +391 -0
  25. package/src/cognition/completion.inbox.ts +86 -0
  26. package/src/cognition/config.mirror.entities.ts +475 -0
  27. package/src/cognition/conversation.memory.ts +81 -0
  28. package/src/cognition/event.log.ts +116 -0
  29. package/src/cognition/event.schemas.ts +568 -0
  30. package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
  31. package/src/cognition/faculties/affective.blender.ts +562 -0
  32. package/src/cognition/faculties/attachment.evaluator.ts +432 -0
  33. package/src/cognition/faculties/attention.allocator.ts +422 -0
  34. package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
  35. package/src/cognition/faculties/bias.detector.ts +363 -0
  36. package/src/cognition/faculties/circadian.oscillator.ts +294 -0
  37. package/src/cognition/faculties/confidence.calibrator.ts +324 -0
  38. package/src/cognition/faculties/dream.simulator.ts +251 -0
  39. package/src/cognition/faculties/empathy.simulator.ts +253 -0
  40. package/src/cognition/faculties/energy.regulator.ts +320 -0
  41. package/src/cognition/faculties/episodic.consolidator.ts +728 -0
  42. package/src/cognition/faculties/executive.engine/commands.ts +459 -0
  43. package/src/cognition/faculties/executive.engine/config.ts +46 -0
  44. package/src/cognition/faculties/executive.engine/context.ts +605 -0
  45. package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
  46. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
  47. package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
  48. package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
  49. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
  50. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
  51. package/src/cognition/faculties/executive.engine/facet.ts +628 -0
  52. package/src/cognition/faculties/executive.engine/gating.ts +222 -0
  53. package/src/cognition/faculties/executive.engine/index.ts +6 -0
  54. package/src/cognition/faculties/executive.engine/messages.ts +102 -0
  55. package/src/cognition/faculties/executive.engine/parser.ts +380 -0
  56. package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
  57. package/src/cognition/faculties/executive.engine/types.ts +323 -0
  58. package/src/cognition/faculties/exteroception.ts +338 -0
  59. package/src/cognition/faculties/forgetting.curve.ts +202 -0
  60. package/src/cognition/faculties/frustration.evaluator.ts +280 -0
  61. package/src/cognition/faculties/goal.manager.ts +1008 -0
  62. package/src/cognition/faculties/inhibition.controller.ts +351 -0
  63. package/src/cognition/faculties/interoception.ts +417 -0
  64. package/src/cognition/faculties/introspection.engine.ts +246 -0
  65. package/src/cognition/faculties/known.entity.tracker.ts +439 -0
  66. package/src/cognition/faculties/loss.evaluator.ts +224 -0
  67. package/src/cognition/faculties/moral.evaluator.ts +437 -0
  68. package/src/cognition/faculties/novelty.detector.ts +254 -0
  69. package/src/cognition/faculties/persona.consolidator.ts +845 -0
  70. package/src/cognition/faculties/planning.engine/engine.ts +859 -0
  71. package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
  72. package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
  73. package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
  74. package/src/cognition/faculties/planning.engine/types.ts +143 -0
  75. package/src/cognition/faculties/reputation.tracker.ts +294 -0
  76. package/src/cognition/faculties/reward.evaluator.ts +304 -0
  77. package/src/cognition/faculties/self.model.updater.ts +677 -0
  78. package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
  79. package/src/cognition/faculties/semantic.engine/index.ts +6 -0
  80. package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
  81. package/src/cognition/faculties/semantic.engine/types.ts +53 -0
  82. package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
  83. package/src/cognition/faculties/social.perception.ts +333 -0
  84. package/src/cognition/faculties/spaced.repetition.ts +605 -0
  85. package/src/cognition/faculties/stress.regulator.ts +375 -0
  86. package/src/cognition/faculties/task.switcher.ts +239 -0
  87. package/src/cognition/faculties/theory.of.mind.ts +333 -0
  88. package/src/cognition/faculties/threat.evaluator.ts +312 -0
  89. package/src/cognition/faculties/working.memory.ts +406 -0
  90. package/src/cognition/generative.model.ts +344 -0
  91. package/src/cognition/heartbeat.ts +70 -0
  92. package/src/cognition/index.ts +291 -0
  93. package/src/cognition/instruction.handler.ts +146 -0
  94. package/src/cognition/memory/index.ts +35 -0
  95. package/src/cognition/memory/vector.adapter.ts +316 -0
  96. package/src/cognition/memory/vector.content.ts +47 -0
  97. package/src/cognition/memory/vector.embedder.ts +212 -0
  98. package/src/cognition/memory/vector.index.ts +459 -0
  99. package/src/cognition/memory/vector.types.ts +65 -0
  100. package/src/cognition/orchestrator.ts +142 -0
  101. package/src/cognition/persona.prior.ts +265 -0
  102. package/src/cognition/schema.registry.ts +145 -0
  103. package/src/cognition/senses/audition.engine/engine.ts +976 -0
  104. package/src/cognition/senses/audition.engine/salience.ts +59 -0
  105. package/src/cognition/senses/base.sense.engine.ts +117 -0
  106. package/src/cognition/senses/gustation.engine.ts +24 -0
  107. package/src/cognition/senses/index.ts +172 -0
  108. package/src/cognition/senses/olfaction.engine.ts +24 -0
  109. package/src/cognition/senses/somatosensation.engine.ts +23 -0
  110. package/src/cognition/senses/vision.engine.ts +23 -0
  111. package/src/cognition/types.ts +63 -0
  112. package/src/cognition/utilities/token.tracker.ts +492 -0
  113. package/src/core/abstracts.ts +60 -0
  114. package/src/core/async.engine.ts +539 -0
  115. package/src/core/clock.ts +196 -0
  116. package/src/core/completion.recorder.ts +122 -0
  117. package/src/core/conflict.detector.ts +140 -0
  118. package/src/core/distributed.ts +626 -0
  119. package/src/core/event.bus.ts +292 -0
  120. package/src/core/inbound.recorder.ts +100 -0
  121. package/src/core/index.ts +167 -0
  122. package/src/core/logger.ts +85 -0
  123. package/src/core/metrics.ts +107 -0
  124. package/src/core/orchestrator.ts +625 -0
  125. package/src/core/replay.ts +589 -0
  126. package/src/core/scenario.ts +120 -0
  127. package/src/core/serialization.ts +448 -0
  128. package/src/core/simulation.ts +197 -0
  129. package/src/core/snapshot.manager.ts +293 -0
  130. package/src/core/state.manager.ts +283 -0
  131. package/src/core/types.ts +274 -0
  132. package/src/core/utils.ts +82 -0
  133. package/src/core/wall.clock.ts +23 -0
  134. package/src/extensions/livestream.ext.ts +570 -0
  135. package/src/extensions/time.ext.ts +339 -0
  136. package/src/index.ts +91 -0
  137. package/src/llm/gate.ts +140 -0
  138. package/src/llm/index.ts +716 -0
  139. package/src/llm/summarizer.ts +170 -0
  140. package/src/llm/wire.contracts.ts +67 -0
  141. package/src/pma/eval.ts +651 -0
  142. package/src/pma/index.ts +1082 -0
  143. package/src/profiles/built-in.ts +7 -0
  144. package/src/profiles/companion.ts +28 -0
  145. package/src/profiles/company-brain.ts +54 -0
  146. package/src/profiles/customer-service.ts +30 -0
  147. package/src/profiles/game-npc.ts +20 -0
  148. package/src/profiles/index.ts +42 -0
  149. package/src/profiles/smart-home.ts +34 -0
  150. package/src/runners/coherence.runner.ts +49 -0
  151. package/src/runners/outreach.runner.ts +158 -0
  152. package/src/runners/social.runner.ts +182 -0
  153. package/src/runners/thin-shim.runner.ts +195 -0
  154. package/src/sdk/will.ts +334 -0
  155. package/src/stem/assembly.audit.ts +88 -0
  156. package/src/stem/distribution.ts +203 -0
  157. package/src/stem/guards/identity.coherence.ts +152 -0
  158. package/src/stem/guards/identity.guard.ts +227 -0
  159. package/src/stem/index.ts +1145 -0
  160. package/src/stem/mind.ts +1090 -0
  161. package/src/stem/tracts/ack.reconciler.ts +55 -0
  162. package/src/stem/tracts/biography.writer.ts +190 -0
  163. package/src/stem/tracts/effector.controller.ts +143 -0
  164. package/src/stem/tracts/health.reporter.ts +113 -0
  165. package/src/stem/tracts/inbound.queue.ts +54 -0
  166. package/src/stem/tracts/outbox.controller.ts +167 -0
  167. package/src/stem/tracts/outbox.writer.ts +133 -0
  168. package/src/stem/tracts/pma.controller.ts +109 -0
  169. package/src/stem/tracts/replay.controller.ts +177 -0
  170. package/src/stem/tracts/sensory.controller.ts +168 -0
  171. package/src/stem/tracts/session.logger.ts +157 -0
  172. package/src/stem/tracts/transport/index.ts +38 -0
  173. package/src/stem/tracts/transport/loopback.transport.ts +86 -0
  174. package/src/stem/tracts/transport/socketio.transport.ts +200 -0
  175. package/src/stem/tracts/transport/stream.transport.ts +152 -0
  176. package/src/stem/tracts/transport/types.ts +193 -0
  177. package/src/stem/tracts/transport.controller.ts +380 -0
  178. package/src/types.ts +116 -0
@@ -0,0 +1,626 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/core/distributed.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * Distributed simulation support for multi-node execution.
7
+ * Supports partitioning by entity type, geographic region, or custom sharding.
8
+ */
9
+
10
+ import type { StateManager } from '#core/state.manager'
11
+ import type { SimulationState, SimulationContext, SimulationEntity, Tick, Timestamp, SimulationEvent, RestoreOptions, StateCommands } from '#core/types'
12
+ import { createContext } from '#core/utils'
13
+ import { wallClock } from '#core/wall.clock'
14
+
15
+ export type ShardStrategy = 'by-entity-type' | 'by-entity-id-hash' | 'by-region' | 'custom'
16
+ export type ConsistencyLevel = 'strong' | 'eventual' | 'causal'
17
+ export type DeliveryGuarantee = 'at-most-once' | 'at-least-once' | 'exactly-once'
18
+
19
+ export interface ShardConfig {
20
+ index: number
21
+ total: number
22
+ strategy: ShardStrategy
23
+ entityTypes?: string[] // for by-entity-type strategy
24
+ regionBounds?: { minX: number; maxX: number; minY: number; maxY: number } // for by-region
25
+ }
26
+
27
+ export interface DistributedNodeConfig {
28
+ nodeId: string
29
+ shard: ShardConfig
30
+ coordinatorUrl?: string
31
+ heartbeatIntervalMs?: number
32
+ syncIntervalMs?: number
33
+ consistencyLevel?: ConsistencyLevel
34
+ }
35
+
36
+ export interface ShardAssignment {
37
+ nodeId: string
38
+ shard: ShardConfig
39
+ status: 'active' | 'standby' | 'failed'
40
+ lastHeartbeat: Timestamp
41
+ }
42
+
43
+ export interface DistributedEvent<T = unknown> extends SimulationEvent<T, {
44
+ originatingNode: string
45
+ shardIndex: number
46
+ isCrossShard: boolean
47
+ targetShard?: number
48
+ }> {}
49
+
50
+ export interface RemoteNode {
51
+ nodeId: string
52
+ shard: ShardConfig
53
+ send( event: SimulationEvent, context: SimulationContext ): Promise<void>
54
+ query( request: CrossShardQuery ): Promise<CrossShardResult>
55
+ }
56
+
57
+ export interface CrossShardQuery {
58
+ type: 'get-entity' | 'get-entities-by-type' | 'get-metric'
59
+ entityId?: string
60
+ entityType?: string
61
+ metricKey?: string
62
+ shardTarget?: number
63
+ }
64
+
65
+ export interface CrossShardResult {
66
+ success: boolean
67
+ data: unknown
68
+ fromNode: string
69
+ timestamp: Timestamp
70
+ }
71
+
72
+ export interface PartitionRouter {
73
+ getShardForEntity( entity: SimulationEntity, totalShards: number ): number
74
+ getShardForMetric( metricKey: string, totalShards: number ): number
75
+ isLocalEntity( entityId: string, shardConfig: ShardConfig ): boolean
76
+ }
77
+
78
+ export class ConsistentHashRouter implements PartitionRouter {
79
+ private _virtualNodes: number
80
+
81
+ constructor( virtualNodes: number = 150 ){
82
+ this._virtualNodes = virtualNodes
83
+ }
84
+
85
+ getShardForEntity( entity: SimulationEntity, totalShards: number ): number {
86
+ const key = `${entity.type}:${entity.id}`
87
+ return this._hashToShard( key, totalShards )
88
+ }
89
+
90
+ getShardForMetric( metricKey: string, totalShards: number ): number {
91
+ return this._hashToShard(`metric:${metricKey}`, totalShards )
92
+ }
93
+
94
+ isLocalEntity( entityId: string, shardConfig: ShardConfig ): boolean {
95
+ const
96
+ hash = this._hash( entityId ),
97
+ shardIndex = hash % shardConfig.total
98
+
99
+ return shardIndex === shardConfig.index
100
+ }
101
+
102
+ private _hashToShard( key: string, totalShards: number ): number {
103
+ return this._hash( key ) % totalShards
104
+ }
105
+
106
+ /**
107
+ * Use FNV-1a hash for better distribution.
108
+ * Replaces the weak djb2 variant.
109
+ */
110
+ private _hash( key: string ): number {
111
+ let hash = 2166136261 // FNV offset basis
112
+ for( let i = 0; i < key.length; i++ ) {
113
+ hash ^= key.charCodeAt( i )
114
+ hash = Math.imul( hash, 16777619 ) // FNV prime
115
+ }
116
+ return hash >>> 0
117
+ }
118
+ }
119
+
120
+ export class DefaultPartitionRouter implements PartitionRouter {
121
+ private _strategy: ShardStrategy
122
+ private _regionSize: number
123
+
124
+ constructor( strategy: ShardStrategy = 'by-entity-id-hash', regionSize: number = 100 ){
125
+ this._strategy = strategy
126
+ this._regionSize = regionSize
127
+ }
128
+
129
+ getShardForEntity( entity: SimulationEntity, totalShards: number ): number {
130
+ switch( this._strategy ){
131
+ case 'by-entity-type': return this._hash( entity.type ) % totalShards
132
+ case 'by-entity-id-hash': return this._hash( entity.id ) % totalShards
133
+ case 'by-region': {
134
+ const coords = entity.metadata?.position as { x: number; y: number } | undefined
135
+ if( !coords ) return 0
136
+
137
+ const
138
+ regionX = Math.floor( coords.x / this._regionSize ),
139
+ regionY = Math.floor( coords.y / this._regionSize )
140
+
141
+ return ( regionX * 31 + regionY ) % totalShards
142
+ }
143
+ default: return this._hash( entity.id ) % totalShards
144
+ }
145
+ }
146
+
147
+ getShardForMetric( metricKey: string, totalShards: number ): number {
148
+ return this._hash( metricKey ) % totalShards
149
+ }
150
+
151
+ isLocalEntity( entityId: string, shardConfig: ShardConfig ): boolean {
152
+ const
153
+ hash = this._hash( entityId ),
154
+ assignedShard = hash % shardConfig.total
155
+
156
+ return assignedShard === shardConfig.index
157
+ }
158
+
159
+ private _hash( key: string ): number {
160
+ let hash = 5381
161
+ for( let i = 0; i < key.length; i++ )
162
+ hash = ( ( hash << 5 ) + hash ) + key.charCodeAt( i )
163
+
164
+ return Math.abs( hash )
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Transport interface with timeout, retry, circuit breaker
170
+ * semantics, and delivery guarantees.
171
+ */
172
+ export interface CrossShardTransport {
173
+ send( targetNode: string, event: DistributedEvent, context: SimulationContext ): Promise<void>
174
+ broadcast( event: DistributedEvent, context: SimulationContext ): Promise<void>
175
+ query( targetNode: string, query: CrossShardQuery, timeoutMs?: number ): Promise<CrossShardResult>
176
+ registerHandler( handler: CrossShardHandler ): void
177
+
178
+ /** Configure delivery guarantee for subsequent sends. */
179
+ setDeliveryGuarantee( guarantee: DeliveryGuarantee ): void
180
+
181
+ /** Configure retry policy. */
182
+ setRetryPolicy( maxRetries: number, backoffMs: number ): void
183
+
184
+ /** Returns circuit breaker status for a node. */
185
+ isCircuitOpen( nodeId: string ): boolean
186
+
187
+ /** Reset circuit breaker for a node. */
188
+ resetCircuit( nodeId: string ): void
189
+ }
190
+
191
+ export type CrossShardHandler = ( event: DistributedEvent, context: SimulationContext ) => Promise<void>
192
+
193
+ export class LocalTransport implements CrossShardTransport {
194
+ private _nodes: Map<string, LocalTransportNode> = new Map()
195
+ private _handlers: Set<CrossShardHandler> = new Set()
196
+ private _deliveryGuarantee: DeliveryGuarantee = 'at-least-once'
197
+ private _maxRetries = 3
198
+ private _backoffMs = 100
199
+ private _circuitBreakers: Map<string, { failures: number; open: boolean; lastFailure: number }> = new Map()
200
+
201
+ registerNode( nodeId: string, transport: LocalTransportNode ): void {
202
+ this._nodes.set( nodeId, transport )
203
+ }
204
+
205
+ async send( targetNode: string, event: DistributedEvent, context: SimulationContext ): Promise<void> {
206
+ const node = this._nodes.get( targetNode )
207
+ if( !node )
208
+ throw new Error(`Node not found: ${targetNode}`)
209
+
210
+ // Circuit breaker check
211
+ if( this.isCircuitOpen( targetNode ) )
212
+ throw new Error(`Circuit breaker open for node: ${targetNode}`)
213
+
214
+ // Retry logic
215
+ let lastError: Error | null = null
216
+ for( let attempt = 0; attempt <= this._maxRetries; attempt++ ){
217
+ try {
218
+ await node.receive( event, context )
219
+ this._recordSuccess( targetNode )
220
+ return
221
+ }
222
+ catch( error ){
223
+ lastError = error as Error
224
+ this._recordFailure( targetNode )
225
+ if( attempt < this._maxRetries )
226
+ await this._delay( this._backoffMs * Math.pow( 2, attempt ) )
227
+ }
228
+ }
229
+
230
+ if( this._deliveryGuarantee === 'at-least-once')
231
+ throw lastError ?? new Error(`Failed to send to ${targetNode} after ${this._maxRetries} retries`)
232
+
233
+ // at-most-once: silently drop after retries exhausted
234
+ }
235
+
236
+ async broadcast( event: DistributedEvent, context: SimulationContext ): Promise<void> {
237
+ await Promise.all( Array.from( this._nodes.values() ).map( node => node.receive( event, context ) ) )
238
+ }
239
+
240
+ async query( targetNode: string, query: CrossShardQuery, timeoutMs: number = 5000 ): Promise<CrossShardResult> {
241
+ const node = this._nodes.get( targetNode )
242
+ if( !node )
243
+ return { success: false, data: null, fromNode: targetNode, timestamp: wallClock() }
244
+
245
+ // Timeout support
246
+ const result = await Promise.race([
247
+ node.handleQuery( query ),
248
+ new Promise<CrossShardResult>( ( _, reject ) =>
249
+ setTimeout( () => reject( new Error(`Query timeout to ${targetNode}`) ), timeoutMs )
250
+ )
251
+ ])
252
+
253
+ return result as CrossShardResult
254
+ }
255
+
256
+ registerHandler( handler: CrossShardHandler ): void {
257
+ this._handlers.add( handler )
258
+ }
259
+
260
+ // ── Configuration setters ──────────────────────────────
261
+
262
+ setDeliveryGuarantee( guarantee: DeliveryGuarantee ): void {
263
+ this._deliveryGuarantee = guarantee
264
+ }
265
+
266
+ setRetryPolicy( maxRetries: number, backoffMs: number ): void {
267
+ this._maxRetries = maxRetries
268
+ this._backoffMs = backoffMs
269
+ }
270
+
271
+ isCircuitOpen( nodeId: string ): boolean {
272
+ return this._circuitBreakers.get( nodeId )?.open ?? false
273
+ }
274
+
275
+ resetCircuit( nodeId: string ): void {
276
+ this._circuitBreakers.set( nodeId, { failures: 0, open: false, lastFailure: 0 } )
277
+ }
278
+
279
+ private _recordSuccess( nodeId: string ): void {
280
+ this._circuitBreakers.set( nodeId, { failures: 0, open: false, lastFailure: 0 } )
281
+ }
282
+
283
+ private _recordFailure( nodeId: string ): void {
284
+ const current = this._circuitBreakers.get( nodeId ) ?? { failures: 0, open: false, lastFailure: 0 }
285
+ current.failures++
286
+ current.lastFailure = wallClock()
287
+
288
+ // Open circuit after 5 consecutive failures
289
+ if( current.failures >= 5 )
290
+ current.open = true
291
+
292
+ this._circuitBreakers.set( nodeId, current )
293
+ }
294
+
295
+ private async _delay( ms: number ): Promise<void> {
296
+ return new Promise( resolve => setTimeout( resolve, ms ) )
297
+ }
298
+
299
+ async _dispatch( event: DistributedEvent, context: SimulationContext ): Promise<void> {
300
+ for( const handler of this._handlers )
301
+ await handler( event, context )
302
+ }
303
+ }
304
+
305
+ export interface LocalTransportNode {
306
+ receive( event: DistributedEvent, context: SimulationContext ): Promise<void>
307
+ handleQuery( query: CrossShardQuery ): Promise<CrossShardResult>
308
+ }
309
+
310
+ export class DistributedStateManager implements StateManager, LocalTransportNode {
311
+ private _localState: StateManager
312
+ private _router: PartitionRouter
313
+ private _shardConfig: ShardConfig
314
+ private _transport: LocalTransport
315
+ private _nodeId: string
316
+ private _pendingCrossShardEvents: DistributedEvent[] = []
317
+ private _syncInterval: NodeJS.Timeout | null = null
318
+
319
+ constructor(
320
+ localState: StateManager,
321
+ router: PartitionRouter,
322
+ shardConfig: ShardConfig,
323
+ transport: LocalTransport,
324
+ nodeId: string,
325
+ syncIntervalMs: number = 1000
326
+ ){
327
+ this._localState = localState
328
+ this._router = router
329
+ this._shardConfig = shardConfig
330
+ this._transport = transport
331
+ this._nodeId = nodeId
332
+
333
+ if( syncIntervalMs > 0 )
334
+ this._syncInterval = setInterval( () => this._sync(), syncIntervalMs )
335
+ }
336
+
337
+ get currentTick(): Tick { return this._localState.currentTick }
338
+ get currentTime(): Timestamp { return this._localState.currentTime }
339
+
340
+ updateClock( tick: Tick, time: Timestamp ): void {
341
+ this._localState.updateClock( tick, time )
342
+ }
343
+
344
+ getEntity<T extends SimulationEntity>( id: string ): T | undefined {
345
+ if( this._router.isLocalEntity( id, this._shardConfig ) )
346
+ return this._localState.getEntity<T>( id )
347
+
348
+ return undefined
349
+ }
350
+
351
+ /**
352
+ * Async cross-shard entity access.
353
+ * Returns undefined for entities not found on the target shard.
354
+ */
355
+ async getEntityAsync<T extends SimulationEntity>( id: string ): Promise<T | undefined> {
356
+ if( this._router.isLocalEntity( id, this._shardConfig ) )
357
+ return this._localState.getEntity<T>( id )
358
+
359
+ // Route to the appropriate shard
360
+ const dummyEntity: SimulationEntity = { id, type: '', createdAt: 0, updatedAt: 0 }
361
+ const targetShard = this._router.getShardForEntity( dummyEntity, this._shardConfig.total )
362
+ const targetNode = `node-${targetShard}`
363
+
364
+ try {
365
+ const result = await this._transport.query( targetNode, { type: 'get-entity', entityId: id })
366
+ return result.success ? ( result.data as T ) : undefined
367
+ }
368
+ catch { return undefined }
369
+ }
370
+
371
+ setEntity( entity: SimulationEntity ): void {
372
+ const targetShard = this._router.getShardForEntity( entity, this._shardConfig.total )
373
+
374
+ if( targetShard === this._shardConfig.index )
375
+ this._localState.setEntity( entity )
376
+
377
+ else this._pendingCrossShardEvents.push({
378
+ id: crypto.randomUUID(), // determinism-ok: cross-shard coordination id at the cross-node boundary (already wallClock-stamped)
379
+ type: 'shard.entity.set',
380
+ timestamp: wallClock(),
381
+ tick: this.currentTick,
382
+ source: this._nodeId,
383
+ payload: entity,
384
+ metadata: {
385
+ originatingNode: this._nodeId,
386
+ shardIndex: this._shardConfig.index,
387
+ isCrossShard: true,
388
+ targetShard
389
+ }
390
+ })
391
+ }
392
+
393
+ deleteEntity( id: string ): boolean {
394
+ if( this._router.isLocalEntity( id, this._shardConfig ) )
395
+ return this._localState.deleteEntity( id )
396
+
397
+ return false
398
+ }
399
+
400
+ getAllEntities(): IterableIterator<SimulationEntity> {
401
+ return this._localState.getAllEntities()
402
+ }
403
+
404
+ getEntitiesByType( type: string ): SimulationEntity[] {
405
+ return this._localState.getEntitiesByType( type )
406
+ }
407
+
408
+ getMetric( key: string ): number | undefined {
409
+ const targetShard = this._router.getShardForMetric( key, this._shardConfig.total )
410
+ if( targetShard === this._shardConfig.index )
411
+ return this._localState.getMetric( key )
412
+
413
+ return undefined
414
+ }
415
+
416
+ setMetric( key: string, value: number ): void {
417
+ const targetShard = this._router.getShardForMetric( key, this._shardConfig.total )
418
+
419
+ if( targetShard === this._shardConfig.index )
420
+ this._localState.setMetric( key, value )
421
+
422
+ else this._pendingCrossShardEvents.push({
423
+ id: crypto.randomUUID(), // determinism-ok: cross-shard coordination id at the cross-node boundary (already wallClock-stamped)
424
+ type: 'shard.metric.set',
425
+ timestamp: wallClock(),
426
+ tick: this.currentTick,
427
+ source: this._nodeId,
428
+ payload: { key, value },
429
+ metadata: {
430
+ originatingNode: this._nodeId,
431
+ shardIndex: this._shardConfig.index,
432
+ isCrossShard: true,
433
+ targetShard
434
+ }
435
+ })
436
+ }
437
+
438
+ incrementMetric( key: string, delta: number = 1 ): number {
439
+ const targetShard = this._router.getShardForMetric( key, this._shardConfig.total )
440
+
441
+ if( targetShard === this._shardConfig.index )
442
+ return this._localState.incrementMetric( key, delta )
443
+
444
+ this._pendingCrossShardEvents.push({
445
+ id: crypto.randomUUID(), // determinism-ok: cross-shard coordination id at the cross-node boundary (already wallClock-stamped)
446
+ type: 'shard.metric.increment',
447
+ timestamp: wallClock(),
448
+ tick: this.currentTick,
449
+ source: this._nodeId,
450
+ payload: { key, delta },
451
+ metadata: {
452
+ originatingNode: this._nodeId,
453
+ shardIndex: this._shardConfig.index,
454
+ isCrossShard: true,
455
+ targetShard
456
+ }
457
+ })
458
+
459
+ const current = this._localState.getMetric( key ) ?? 0
460
+ return current + delta
461
+ }
462
+
463
+ snapshot(): SimulationState {
464
+ return this._localState.snapshot()
465
+ }
466
+
467
+ restore( snapshot: SimulationState, options?: RestoreOptions ): void {
468
+ this._localState.restore( snapshot, options )
469
+ }
470
+
471
+ applyCommands( commands: StateCommands ): void {
472
+ this._localState.applyCommands( commands )
473
+ }
474
+
475
+ clear(): void {
476
+ this._localState.clear()
477
+ }
478
+
479
+ async receive( event: DistributedEvent, _context: SimulationContext ): Promise<void> {
480
+ switch( event.type ) {
481
+ case 'shard.entity.set':
482
+ this._localState.setEntity( event.payload as SimulationEntity )
483
+ break
484
+
485
+ case 'shard.metric.set':
486
+ const { key, value } = event.payload as { key: string; value: number }
487
+ this._localState.setMetric( key, value )
488
+ break
489
+
490
+ case 'shard.metric.increment':
491
+ const { key: incKey, delta } = event.payload as { key: string; delta: number }
492
+ this._localState.incrementMetric( incKey, delta )
493
+ break
494
+
495
+ default:
496
+ break
497
+ }
498
+ }
499
+
500
+ async handleQuery( query: CrossShardQuery ): Promise<CrossShardResult> {
501
+ switch( query.type ){
502
+ case 'get-entity':
503
+ if( !query.entityId )
504
+ return { success: false, data: null, fromNode: this._nodeId, timestamp: wallClock() }
505
+
506
+ return {
507
+ success: true,
508
+ data: this._localState.getEntity( query.entityId ),
509
+ fromNode: this._nodeId,
510
+ timestamp: wallClock()
511
+ }
512
+
513
+ case 'get-entities-by-type':
514
+ if( !query.entityType )
515
+ return { success: false, data: null, fromNode: this._nodeId, timestamp: wallClock() }
516
+
517
+ return {
518
+ success: true,
519
+ data: this._localState.getEntitiesByType( query.entityType ),
520
+ fromNode: this._nodeId,
521
+ timestamp: wallClock()
522
+ }
523
+
524
+ case 'get-metric':
525
+ if( !query.metricKey )
526
+ return { success: false, data: null, fromNode: this._nodeId, timestamp: wallClock() }
527
+
528
+ return {
529
+ success: true,
530
+ data: this._localState.getMetric( query.metricKey ),
531
+ fromNode: this._nodeId,
532
+ timestamp: wallClock()
533
+ }
534
+
535
+ default:
536
+ return { success: false, data: null, fromNode: this._nodeId, timestamp: wallClock() }
537
+ }
538
+ }
539
+
540
+ flushPendingEvents(): DistributedEvent[] {
541
+ return this._pendingCrossShardEvents.splice( 0 )
542
+ }
543
+
544
+ private async _sync(): Promise<void> {
545
+ const events = this.flushPendingEvents()
546
+ for( const event of events ) {
547
+ const targetShard = event.metadata?.targetShard
548
+
549
+ if( targetShard !== undefined && targetShard !== this._shardConfig.index ){
550
+ const targetNode = `node-${targetShard}`
551
+ await this._transport.send( targetNode, event, createContext('distributed', this._nodeId ) )
552
+ }
553
+ }
554
+ }
555
+
556
+ destroy(): void {
557
+ this._syncInterval && clearInterval( this._syncInterval )
558
+ }
559
+ }
560
+
561
+ export class DistributedOrchestrator {
562
+ private _nodes: Map<string, DistributedNode> = new Map()
563
+ private _router: PartitionRouter
564
+ private _transport: LocalTransport
565
+ private _coordinatorId: string | null = null
566
+
567
+ constructor( router: PartitionRouter = new DefaultPartitionRouter() ){
568
+ this._router = router
569
+ this._transport = new LocalTransport()
570
+ }
571
+
572
+ addNode( config: DistributedNodeConfig, localState: StateManager ): DistributedNode {
573
+ const distributedState = new DistributedStateManager(
574
+ localState,
575
+ this._router,
576
+ config.shard,
577
+ this._transport,
578
+ config.nodeId,
579
+ config.syncIntervalMs
580
+ )
581
+
582
+ const node: DistributedNode = {
583
+ nodeId: config.nodeId,
584
+ shard: config.shard,
585
+ stateManager: distributedState,
586
+ transport: this._transport,
587
+ status: 'active',
588
+ }
589
+
590
+ this._nodes.set( config.nodeId, node )
591
+ this._transport.registerNode( config.nodeId, distributedState )
592
+
593
+ if( config.coordinatorUrl && !this._coordinatorId )
594
+ this._coordinatorId = config.nodeId
595
+
596
+ return node
597
+ }
598
+
599
+ getNode( nodeId: string ): DistributedNode | undefined {
600
+ return this._nodes.get( nodeId )
601
+ }
602
+
603
+ getAllNodes(): DistributedNode[] {
604
+ return Array.from( this._nodes.values() )
605
+ }
606
+
607
+ async broadcast( event: DistributedEvent, context: SimulationContext ): Promise<void> {
608
+ await this._transport.broadcast( event, context )
609
+ }
610
+
611
+ async sendToShard( shardIndex: number, event: DistributedEvent, context: SimulationContext ): Promise<void> {
612
+ const node = Array.from( this._nodes.values() ).find( n => n.shard.index === shardIndex )
613
+ if( !node )
614
+ throw new Error(`No node found for shard ${shardIndex}`)
615
+
616
+ await this._transport.send( node.nodeId, event, context )
617
+ }
618
+ }
619
+
620
+ export interface DistributedNode {
621
+ nodeId: string
622
+ shard: ShardConfig
623
+ stateManager: DistributedStateManager
624
+ transport: CrossShardTransport
625
+ status: 'active' | 'standby' | 'failed'
626
+ }