@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,196 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/core/clock.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * Core simulation clock — completely generic.
7
+ * No time-of-day, calendar, or domain-specific logic.
8
+ * Time is just milliseconds since epoch.
9
+ */
10
+
11
+ import type { Duration, Tick, Timestamp } from '#core/types'
12
+ import { wallClock } from '#core/wall.clock'
13
+
14
+ export interface SimulationClock {
15
+ readonly now: Timestamp
16
+ readonly currentTick: Tick
17
+ readonly delta: Duration
18
+ readonly isRunning: boolean
19
+ readonly multiplier: number
20
+
21
+ // Driven externally by the Orchestrator
22
+ tick(): void
23
+
24
+ // Lifecycle
25
+ pause(): void
26
+ resume(): void
27
+ reset(): void
28
+
29
+ // Time manipulation
30
+ setTime( time: Timestamp ): void
31
+ setTick( tick: Tick ): void
32
+ setMultiplier( newMultiplier: number ): void
33
+
34
+ // Helpers
35
+ toSimMs( wallMs: number ): number
36
+ toWallMs( simMs: number ): number
37
+ simSleep( simMs: number ): Promise<void>
38
+ }
39
+
40
+ export interface ClockConfig {
41
+ startTime?: Timestamp
42
+ startTick?: Tick
43
+ timeScale?: number // 1.0 = real-time, 2.0 = 2× real-time
44
+ /** When set, tick() uses this fixed duration (ms) instead of wall time. */
45
+ fixedDeltaMs?: number
46
+ }
47
+
48
+ export class DefaultSimulationClock implements SimulationClock {
49
+ private _now: Timestamp
50
+ private _tick: Tick
51
+ private _delta: Duration = 0
52
+ private _lastTick: Timestamp
53
+ private _isRunning = true
54
+ private _timeScale: number
55
+ private _pausedAt: Timestamp | null = null
56
+ private _fixedDeltaMs: number | null = null
57
+
58
+ // Anchors for accurate now() under dynamic multiplier
59
+ private _startWallMs: Timestamp
60
+ private _startSimMs: Timestamp
61
+
62
+ constructor( config: ClockConfig = {} ){
63
+ this._now = config.startTime ?? wallClock()
64
+ this._tick = config.startTick ?? 0
65
+ this._lastTick = this._now
66
+ this._timeScale = config.timeScale ?? 1.0
67
+ this._fixedDeltaMs = config.fixedDeltaMs ?? null
68
+
69
+ this._startWallMs = wallClock()
70
+ this._startSimMs = this._now
71
+ }
72
+
73
+ // ── Getters ─────────────────────────────────────────────
74
+
75
+ get now(): Timestamp {
76
+ // Deterministic mode: time only advances inside tick(); never read wall
77
+ // time, so SimulationState.time reproduces exactly on replay (R2).
78
+ if( this._fixedDeltaMs !== null ) return this._now
79
+
80
+ if( !this._isRunning ) return this._now
81
+
82
+ const wallElapsed = wallClock() - this._startWallMs
83
+ return this._startSimMs + wallElapsed * this._timeScale
84
+ }
85
+
86
+ get currentTick(): Tick { return this._tick }
87
+ get delta(): Duration { return this._delta }
88
+ get isRunning(): boolean { return this._isRunning }
89
+ get multiplier(): number { return this._timeScale }
90
+
91
+ // ── Core tick ───────────────────────────────────────────
92
+
93
+ /**
94
+ * The Orchestrator calls this once per tick as the single
95
+ * source of tick advancement.
96
+ */
97
+ tick(): void {
98
+ if( !this._isRunning ) return
99
+
100
+ // Deterministic mode: advance by a fixed sim delta, never reading wall
101
+ // time. This is what makes the tick→time mapping reproducible on replay (R2).
102
+ if( this._fixedDeltaMs !== null ){
103
+ this._delta = this._fixedDeltaMs * this._timeScale
104
+ this._now += this._delta
105
+ this._tick += 1
106
+ return
107
+ }
108
+
109
+ const
110
+ realNow = wallClock(),
111
+ realDelta = realNow - this._lastTick,
112
+ scaledDelta = realDelta * this._timeScale
113
+
114
+ this._delta = scaledDelta
115
+ this._now = this.now
116
+ this._tick += 1
117
+ this._lastTick = realNow
118
+ }
119
+
120
+ // ── Lifecycle ───────────────────────────────────────────
121
+
122
+ pause(): void {
123
+ if( !this._isRunning ) return
124
+
125
+ this._isRunning = false
126
+ this._pausedAt = wallClock()
127
+ }
128
+
129
+ resume(): void {
130
+ if( this._isRunning ) return
131
+
132
+ this._isRunning = true
133
+
134
+ if( this._pausedAt !== null ){
135
+ const pauseDuration = wallClock() - this._pausedAt
136
+
137
+ // Shift anchors to compensate for pause — maintains continuous time
138
+ this._startWallMs += pauseDuration
139
+ this._lastTick = wallClock() - ( this._pausedAt - this._lastTick )
140
+ this._pausedAt = null
141
+ }
142
+ }
143
+
144
+ reset(): void {
145
+ const now = wallClock()
146
+
147
+ this._now = now
148
+ this._tick = 0
149
+ this._delta = 0
150
+ this._lastTick = now
151
+ this._isRunning = true
152
+ this._pausedAt = null
153
+ this._startWallMs = now
154
+ this._startSimMs = now
155
+ }
156
+
157
+ // ── Time manipulation ───────────────────────────────────
158
+
159
+ setTime( time: Timestamp ): void {
160
+ this._now = time
161
+ this._startSimMs = time
162
+ this._startWallMs = wallClock()
163
+ this._lastTick = this._startWallMs
164
+ }
165
+
166
+ setTick( tick: Tick ): void {
167
+ this._tick = tick
168
+ }
169
+
170
+ setMultiplier( newMultiplier: number ): void {
171
+ if( newMultiplier <= 0 )
172
+ throw new Error('Multiplier must be positive')
173
+
174
+ // Re-anchor current sim time to prevent jumps
175
+ const simNow = this.now
176
+
177
+ this._startSimMs = simNow
178
+ this._startWallMs = wallClock()
179
+ this._timeScale = newMultiplier
180
+ }
181
+
182
+ // ── Helpers ─────────────────────────────────────────────
183
+
184
+ toSimMs( wallMs: number ): number {
185
+ return wallMs * this._timeScale
186
+ }
187
+
188
+ toWallMs( simMs: number ): number {
189
+ return simMs / this._timeScale
190
+ }
191
+
192
+ async simSleep( simMs: number ): Promise<void> {
193
+ const wallMs = this.toWallMs( simMs )
194
+ return new Promise( resolve => setTimeout( resolve, wallMs ) )
195
+ }
196
+ }
@@ -0,0 +1,122 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/core/completion.recorder.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * LLM completion record/replay seam.
7
+ *
8
+ * The LLM is an external, non-deterministic oracle: to re-run a session
9
+ * deterministically (REORIENT R2) the recorder captures each completion's full
10
+ * input (model, params, prompt) and output, and on replay a *source* re-feeds
11
+ * those recorded completions instead of re-calling the model. This module is
12
+ * both halves of that seam — the capture sink (R2-b) and the re-feed source
13
+ * (R2-c). Both registries are keyed by willId so each Will routes to its own
14
+ * recorder/source (unlike the process-global token tracker — see REORIENT R4).
15
+ */
16
+
17
+ import type { Tick, Timestamp } from '#core/types'
18
+
19
+ export interface LLMCompletionRecord {
20
+ tick: Tick
21
+ willId: string
22
+ provider: string
23
+ model: string
24
+ maxOutputTokens: number
25
+ systemPrompt: string
26
+ userMessage: string
27
+ text: string
28
+ inputTok: number
29
+ outputTok: number
30
+ /** True when produced by the deterministic mock path (no API call). */
31
+ mock: boolean
32
+ latencyMs: number
33
+ timestamp: Timestamp
34
+ }
35
+
36
+ export interface LLMCompletionSink {
37
+ recordCompletion( record: LLMCompletionRecord ): void
38
+ }
39
+
40
+ const _sinks = new Map<string, LLMCompletionSink>()
41
+
42
+ export function setCompletionRecorder( willId: string, sink: LLMCompletionSink ): void {
43
+ _sinks.set( willId, sink )
44
+ }
45
+
46
+ export function clearCompletionRecorder( willId: string ): void {
47
+ _sinks.delete( willId )
48
+ }
49
+
50
+ export function getCompletionRecorder( willId: string ): LLMCompletionSink | undefined {
51
+ return _sinks.get( willId )
52
+ }
53
+
54
+ // ── Replay re-feed source (R2-c) ─────────────────────────────
55
+
56
+ /**
57
+ * Supplies recorded completions back to the LLM director during replay. A
58
+ * registered source short-circuits the live API call: the director returns the
59
+ * recorded output instead of re-invoking the non-deterministic model. Returning
60
+ * `undefined` means "no recorded completion — fall through to a live call"; a
61
+ * strict source (see RecordedCompletionSource) instead throws on a miss so a
62
+ * replay can never silently diverge.
63
+ */
64
+ export interface LLMCompletionSource {
65
+ nextCompletion(
66
+ tick: number,
67
+ systemPrompt: string,
68
+ userMessage: string,
69
+ ): LLMCompletionRecord | undefined
70
+ }
71
+
72
+ /**
73
+ * Strict re-feed source backed by an array of recorded completions. Matches by
74
+ * tick, then by BYTE-IDENTICAL PROMPT within the tick — order-independent.
75
+ *
76
+ * Why prompt-keyed, not FIFO: concurrent LLM chains (the master executive and
77
+ * a deliberation facet) can ISSUE their same-tick calls in either order — that
78
+ * interleaving is scheduler timing, not seed-pinned state, so it may flip
79
+ * between the recording run and the replay run. Positional matching mispairs
80
+ * at the first flip and reports a phantom "prompt diverged". Keying each call
81
+ * to its own record by exact prompt makes the pairing insensitive to issue
82
+ * order while remaining strict: the prompts themselves are pure functions of
83
+ * deterministic state, so a real divergence still has no matching record and
84
+ * throws. Identical duplicate prompts within a tick consume FIFO.
85
+ */
86
+ export class RecordedCompletionSource implements LLMCompletionSource {
87
+ private _byTick = new Map<number, LLMCompletionRecord[]>()
88
+
89
+ constructor( completions: readonly LLMCompletionRecord[] ){
90
+ for( const c of completions ){
91
+ const queue = this._byTick.get( c.tick )
92
+ if( queue ) queue.push( c )
93
+ else this._byTick.set( c.tick, [ c ] )
94
+ }
95
+ }
96
+
97
+ nextCompletion( tick: number, systemPrompt: string, userMessage: string ): LLMCompletionRecord {
98
+ const queue = this._byTick.get( tick )
99
+ if( !queue || queue.length === 0 )
100
+ throw new Error( `replay: no recorded LLM completion for tick ${tick} (recording is incomplete or call timing diverged)` )
101
+
102
+ const idx = queue.findIndex( r => r.systemPrompt === systemPrompt && r.userMessage === userMessage )
103
+ if( idx === -1 )
104
+ throw new Error( `replay: LLM prompt diverged at tick ${tick} — no recorded completion matches this call (upstream non-determinism)` )
105
+
106
+ return queue.splice( idx, 1 )[ 0 ]!
107
+ }
108
+ }
109
+
110
+ const _sources = new Map<string, LLMCompletionSource>()
111
+
112
+ export function setCompletionSource( willId: string, source: LLMCompletionSource ): void {
113
+ _sources.set( willId, source )
114
+ }
115
+
116
+ export function clearCompletionSource( willId: string ): void {
117
+ _sources.delete( willId )
118
+ }
119
+
120
+ export function getCompletionSource( willId: string ): LLMCompletionSource | undefined {
121
+ return _sources.get( willId )
122
+ }
@@ -0,0 +1,140 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/core/conflict.detector.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * Validates async engine reasoning footprints against current state.
7
+ * Detects read-after-write and write-after-write conflicts that occur
8
+ * when an engine reasons on a stale snapshot while the world advances.
9
+ */
10
+
11
+ import type {
12
+ ReasoningFootprint,
13
+ ConflictReport,
14
+ ConflictResolution,
15
+ ConflictStrategy,
16
+ ReadonlySimulationState,
17
+ StateCommands,
18
+ } from '#core/types'
19
+
20
+ export class ConflictDetector {
21
+ /**
22
+ * Check a reasoning footprint against the current state snapshot.
23
+ * Returns a detailed conflict report.
24
+ */
25
+ detect(
26
+ footprint: ReasoningFootprint,
27
+ currentState: ReadonlySimulationState
28
+ ): ConflictReport {
29
+ const
30
+ readConflicts: string[] = [],
31
+ writeConflicts: string[] = []
32
+
33
+ // Read conflicts: entities the engine observed have been modified
34
+ // since the footprint was taken. Compare the entity's last-write tick
35
+ // against `tickObserved` (both are sim ticks). Entities without a tick
36
+ // stamp (e.g. restored from a snapshot) cannot be proven stale → skip.
37
+ for( const id of footprint.entitiesRead ){
38
+ const entity = currentState.entities.get( id )
39
+ const modifiedAtTick = entity?.updatedAtTick
40
+ if( entity && modifiedAtTick !== undefined && modifiedAtTick > footprint.tickObserved )
41
+ readConflicts.push(`Entity '${id}' modified at tick ${modifiedAtTick} (observed at tick ${footprint.tickObserved})`)
42
+ }
43
+
44
+ // Write conflicts: entities the engine wants to modify were modified by others
45
+ for( const id of footprint.entitiesModified ){
46
+ const entity = currentState.entities.get( id )
47
+ const modifiedAtTick = entity?.updatedAtTick
48
+ if( entity && modifiedAtTick !== undefined && modifiedAtTick > footprint.tickObserved )
49
+ writeConflicts.push(`Write conflict on '${id}': modified at tick ${modifiedAtTick} (observed at tick ${footprint.tickObserved})`)
50
+ }
51
+
52
+ return {
53
+ hasConflicts: readConflicts.length > 0 || writeConflicts.length > 0,
54
+ readConflicts,
55
+ writeConflicts,
56
+ detectedAtTick: currentState.tick,
57
+ footprint,
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Resolve conflicts using the specified strategy.
63
+ */
64
+ resolve(
65
+ report: ConflictReport,
66
+ strategy: ConflictStrategy = 'REJECT'
67
+ ): ConflictResolution {
68
+ switch( strategy ){
69
+ case 'FORCE':
70
+ return {
71
+ strategy: 'FORCE',
72
+ resolvedCommands: report.footprint.intendedCommands,
73
+ shouldRerun: false,
74
+ reason: 'FORCE: applied despite conflicts',
75
+ }
76
+
77
+ case 'MERGE':
78
+ return this._merge( report )
79
+
80
+ case 'REJECT':
81
+ default:
82
+ return {
83
+ strategy: 'REJECT',
84
+ resolvedCommands: null,
85
+ shouldRerun: true,
86
+ reason: `REJECT: ${report.readConflicts.length} read conflicts, ${report.writeConflicts.length} write conflicts`,
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Attempt to apply a non-conflicting subset of commands.
93
+ * Only includes entity sets and metric sets that don't touch conflicted entities.
94
+ */
95
+ private _merge( report: ConflictReport ): ConflictResolution {
96
+ if( !report.hasConflicts )
97
+ return {
98
+ strategy: 'MERGE',
99
+ resolvedCommands: report.footprint.intendedCommands,
100
+ shouldRerun: false,
101
+ reason: 'MERGE: no conflicts detected, full commit',
102
+ }
103
+
104
+ const
105
+ footprint = report.footprint,
106
+ original = footprint.intendedCommands,
107
+ conflictedIds = new Set<string>()
108
+
109
+ // Collect all conflicted entity IDs from write conflict messages
110
+ for( const msg of report.writeConflicts ){
111
+ const match = msg.match( /Write conflict on '([^']+)'/ )
112
+ match && conflictedIds.add( match[1]! )
113
+ }
114
+
115
+ const merged: StateCommands = {}
116
+
117
+ // Only include entity sets that don't touch conflicted entities
118
+ if( original.set )
119
+ merged.set = original.set.filter( e => !conflictedIds.has( e.id ) )
120
+
121
+ // Only include deletes that don't touch conflicted entities
122
+ if( original.delete )
123
+ merged.delete = original.delete.filter( id => !conflictedIds.has( id ) )
124
+
125
+ // Metrics are always safe to apply (no entity-level conflicts)
126
+ if( original.metrics )
127
+ merged.metrics = [ ...original.metrics ]
128
+
129
+ const
130
+ appliedCount = ( merged.set?.length ?? 0 ) + ( merged.delete?.length ?? 0 ),
131
+ rejectedCount = conflictedIds.size
132
+
133
+ return {
134
+ strategy: 'MERGE',
135
+ resolvedCommands: merged,
136
+ shouldRerun: rejectedCount > 0,
137
+ reason: `MERGE: applied ${appliedCount} operations, rejected ${rejectedCount} conflicts`,
138
+ }
139
+ }
140
+ }