bdo-toolkit 1.0.0__py3-none-any.whl

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 (48) hide show
  1. bdo_toolkit/__init__.py +87 -0
  2. bdo_toolkit/_async_sessions.py +651 -0
  3. bdo_toolkit/_capture_backend.py +194 -0
  4. bdo_toolkit/_capture_options.py +68 -0
  5. bdo_toolkit/_capture_runtime.py +626 -0
  6. bdo_toolkit/_deposit_origin.py +1599 -0
  7. bdo_toolkit/_engine.py +327 -0
  8. bdo_toolkit/_framing.py +904 -0
  9. bdo_toolkit/_profile_runtime.py +157 -0
  10. bdo_toolkit/_protocol.py +386 -0
  11. bdo_toolkit/_reassembly.py +654 -0
  12. bdo_toolkit/_specs.py +285 -0
  13. bdo_toolkit/_storage_destination_validation.py +167 -0
  14. bdo_toolkit/_storage_hydration.py +241 -0
  15. bdo_toolkit/_version.py +3 -0
  16. bdo_toolkit/calibration.py +3223 -0
  17. bdo_toolkit/capture.py +1713 -0
  18. bdo_toolkit/character_state.py +3506 -0
  19. bdo_toolkit/cli.py +948 -0
  20. bdo_toolkit/diagnostics.py +51 -0
  21. bdo_toolkit/events.py +214 -0
  22. bdo_toolkit/filters.py +105 -0
  23. bdo_toolkit/item_state.py +48 -0
  24. bdo_toolkit/origin_learning.py +779 -0
  25. bdo_toolkit/profiles.py +370 -0
  26. bdo_toolkit/py.typed +1 -0
  27. bdo_toolkit/remote_profiles.py +358 -0
  28. bdo_toolkit/solare/__init__.py +50 -0
  29. bdo_toolkit/solare/_constants.py +94 -0
  30. bdo_toolkit/solare/_detail_learning.py +1437 -0
  31. bdo_toolkit/solare/_details.py +796 -0
  32. bdo_toolkit/solare/_discovery.py +1212 -0
  33. bdo_toolkit/solare/_live_tracker.py +472 -0
  34. bdo_toolkit/solare/_replay_capture.py +182 -0
  35. bdo_toolkit/solare/_result.py +441 -0
  36. bdo_toolkit/solare/_scanner.py +203 -0
  37. bdo_toolkit/solare/_validation.py +11 -0
  38. bdo_toolkit/solare/async_session.py +444 -0
  39. bdo_toolkit/solare/models.py +806 -0
  40. bdo_toolkit/solare/replay.py +62 -0
  41. bdo_toolkit/solare/session.py +1051 -0
  42. bdo_toolkit/writers.py +30 -0
  43. bdo_toolkit-1.0.0.dist-info/METADATA +143 -0
  44. bdo_toolkit-1.0.0.dist-info/RECORD +48 -0
  45. bdo_toolkit-1.0.0.dist-info/WHEEL +5 -0
  46. bdo_toolkit-1.0.0.dist-info/entry_points.txt +2 -0
  47. bdo_toolkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  48. bdo_toolkit-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1599 @@
1
+ """Fail-closed worker/manual classification for storage events.
2
+
3
+ Manual deposits carry a calibrated source-stack decrement immediately before
4
+ the storage delta. Worker deposits carry two ordered companion frames within a
5
+ bounded lookahead window that repeat the same high-entropy token from the
6
+ delta's pre-record prefix. The relationship survived a patch that changed
7
+ every involved opcode, length, and token offset, so classification does not
8
+ trust raw opcode values.
9
+
10
+ Missing or conflicting evidence leaves ``source=None`` on an already-live
11
+ ``storage_delta``. A neutral ``storage_record`` is promoted to a live delta
12
+ only when the same independent evidence proves a manual or worker mutation;
13
+ otherwise it remains neutral. Storage context bytes identify the endpoint,
14
+ not whether player inventory or worker production supplied the item.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import dataclasses
20
+ from collections import deque
21
+ from dataclasses import dataclass, field
22
+ from threading import RLock
23
+ from typing import Callable, Iterable, Optional
24
+
25
+ from ._protocol import MAX_TARGET_MESSAGE_LENGTH, BDOFrame, FlowKey, PacketContext
26
+ from .events import BDOEvent
27
+ from .origin_learning import (
28
+ CompanionObservation,
29
+ TOKEN_WIDTH,
30
+ discover_companion_observation,
31
+ )
32
+ from .profiles import OriginCompanionFamily
33
+
34
+ ORIGIN_WORKER = "worker"
35
+ ORIGIN_MANUAL = "manual"
36
+ ORIGIN_UNKNOWN = "unknown"
37
+ SOURCE_WORKER_PRODUCTION = "Worker Production"
38
+ SOURCE_PLAYER_INVENTORY = "Player Inventory"
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class DecrementSpec:
43
+ """One source-stack-decrement shape to test candidates against."""
44
+
45
+ opcode: int
46
+ min_message_length: int
47
+ quantity_offset: int
48
+ source_instance_offset: Optional[int] = None
49
+ repeat_stride: Optional[int] = None
50
+
51
+ def __post_init__(self) -> None:
52
+ if (
53
+ isinstance(self.opcode, bool)
54
+ or not isinstance(self.opcode, int)
55
+ or not 0 <= self.opcode <= 0xFFFF
56
+ ):
57
+ raise ValueError("decrement opcode must be a uint16")
58
+ if (
59
+ isinstance(self.min_message_length, bool)
60
+ or not isinstance(self.min_message_length, int)
61
+ or self.min_message_length < 5
62
+ ):
63
+ raise ValueError("decrement minimum message length must be at least 5")
64
+ if (
65
+ isinstance(self.quantity_offset, bool)
66
+ or not isinstance(self.quantity_offset, int)
67
+ or not 0 <= self.quantity_offset <= self.min_message_length - 4
68
+ ):
69
+ raise ValueError("decrement quantity offset must fit its minimum shape")
70
+ if self.source_instance_offset is not None and (
71
+ isinstance(self.source_instance_offset, bool)
72
+ or not isinstance(self.source_instance_offset, int)
73
+ or self.source_instance_offset < 0
74
+ or self.source_instance_offset + 8 > self.min_message_length
75
+ ):
76
+ raise ValueError(
77
+ "decrement source instance offset must fit its minimum shape"
78
+ )
79
+ if self.repeat_stride is not None and (
80
+ isinstance(self.repeat_stride, bool)
81
+ or not isinstance(self.repeat_stride, int)
82
+ or self.repeat_stride <= 0
83
+ ):
84
+ raise ValueError("decrement repeat stride must be positive or None")
85
+ if self.repeat_stride is not None:
86
+ prefix_length = self.min_message_length - self.repeat_stride
87
+ if (
88
+ prefix_length < 5
89
+ or self.quantity_offset < prefix_length
90
+ or (
91
+ self.source_instance_offset is not None
92
+ and self.source_instance_offset < prefix_length
93
+ )
94
+ ):
95
+ raise ValueError(
96
+ "decrement repeat stride must place repeated fields "
97
+ "after the frame prefix"
98
+ )
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class _ManualDecrementMatch:
103
+ """Strength and anchored geometry of one manual-deposit signal."""
104
+
105
+ opcode: int
106
+ message_length: int
107
+ quantity_offset: int
108
+ source_instance_offset: Optional[int]
109
+ match_kind: str
110
+ confidence: str
111
+ instance_matches_destination: Optional[bool]
112
+
113
+ def to_dict(self) -> dict[str, object]:
114
+ output: dict[str, object] = {
115
+ "opcode": f"0x{self.opcode:04X}",
116
+ "message_length": self.message_length,
117
+ "quantity_offset": self.quantity_offset,
118
+ "match_kind": self.match_kind,
119
+ "confidence": self.confidence,
120
+ }
121
+ if self.source_instance_offset is not None:
122
+ output["source_instance_offset"] = self.source_instance_offset
123
+ if self.instance_matches_destination is not None:
124
+ output["instance_matches_destination"] = (
125
+ self.instance_matches_destination
126
+ )
127
+ return output
128
+
129
+
130
+ @dataclass
131
+ class _PendingDeposit:
132
+ event: BDOEvent
133
+ flow: FlowKey
134
+ stream_sequence: Optional[int]
135
+ timestamp: float
136
+ matching_decrement: bool
137
+ events: tuple[BDOEvent, ...] = ()
138
+ matching_decrement_record_indexes: tuple[int, ...] = ()
139
+ manual_decrement_matches: tuple[
140
+ tuple[int, _ManualDecrementMatch], ...
141
+ ] = ()
142
+ frames_after: int = 0
143
+ end_sequence: Optional[int] = None
144
+ companion_observation: Optional[CompanionObservation] = None
145
+ delta_message: Optional[bytes] = None
146
+ delta_prefix_end: Optional[int] = None
147
+ candidate_observations: dict[
148
+ tuple[int, int, int, int, int], CompanionObservation
149
+ ] = field(default_factory=dict)
150
+ awaiting_storage_boundaries: frozenset[int] = frozenset()
151
+ finalized: bool = False
152
+
153
+
154
+ @dataclass(frozen=True)
155
+ class _StreamSpan:
156
+ start: int
157
+ data: bytes
158
+
159
+ @property
160
+ def end(self) -> int:
161
+ return self.start + len(self.data)
162
+
163
+
164
+ @dataclass(frozen=True)
165
+ class _CompanionScan:
166
+ observations: tuple[CompanionObservation, ...]
167
+ complete: bool
168
+ immediate_family_keys: frozenset[tuple[int, int, int, int, int]] = frozenset()
169
+ awaiting_storage_boundaries: frozenset[int] = frozenset()
170
+
171
+
172
+ @dataclass
173
+ class _StagedNeutralBatch:
174
+ """Records from one unfamiliar-mode wrapper awaiting atomic routing."""
175
+
176
+ expected_count: int
177
+ entries: dict[int, tuple[BDOEvent, Optional[bytes]]] = field(
178
+ default_factory=dict
179
+ )
180
+ invalid: bool = False
181
+
182
+
183
+ class DepositOriginTracker:
184
+ """Correlate live-or-neutral storage records with origin evidence."""
185
+
186
+ # Worker companions can be interleaved with ordinary inventory and
187
+ # storage traffic. Keep a modestly wider message horizon than the original
188
+ # eight-frame window, while the independent time and pending-count bounds
189
+ # below prevent unbounded retention or scanning.
190
+ LOOKAHEAD_FRAMES = 32
191
+ MAX_PENDING_OPERATIONS_PER_FLOW = 64
192
+ MAX_PENDING_OPERATIONS_TOTAL = 4096
193
+ STALE_SECONDS = 2.0
194
+ BACKWARD_WINDOW = 16
195
+ STREAM_SPAN_HISTORY_LIMIT = 64
196
+ MANUAL_LOOKBACK_FRAMES = 2
197
+ OBSERVATION_HISTORY_LIMIT = 4096
198
+ COMPANION_PAIR_HISTORY_LIMIT = 4096
199
+ RECORD_BOUNDARY_HISTORY_LIMIT = 4096
200
+ RUNTIME_CONFIRMED_FAMILY_LIMIT = 4096
201
+ FAMILY_CONFIRMATION_OBSERVATIONS = 2
202
+
203
+ def __init__(
204
+ self,
205
+ *,
206
+ decrement_specs: Iterable[DecrementSpec],
207
+ emit: Callable[[BDOEvent], None],
208
+ origin_observer: Optional[Callable[[CompanionObservation], object]] = None,
209
+ known_companion_families: Iterable[OriginCompanionFamily] = (),
210
+ storage_delta_opcodes: Iterable[int] = (),
211
+ ) -> None:
212
+ known_families = tuple(known_companion_families)
213
+ self._decrement_specs: dict[int, list[DecrementSpec]] = {}
214
+ for spec in decrement_specs:
215
+ matches = self._decrement_specs.setdefault(spec.opcode, [])
216
+ if spec not in matches:
217
+ matches.append(spec)
218
+ self._emit_callback = emit
219
+ self._origin_observer = origin_observer
220
+ # Live capture mutates correlation state from its decoder worker while
221
+ # the application consumer can call ``flush_stale``. Keep every state
222
+ # transition under one lock and queue callbacks in that same order.
223
+ # Exactly one caller owns outbox dispatch at a time, but callbacks run
224
+ # without *any* tracker lock held: a callback may need a session lock
225
+ # while another thread holding that session lock enters the tracker.
226
+ self._state_lock = RLock()
227
+ self._outbox: deque[tuple[str, object]] = deque()
228
+ self._dispatching = False
229
+ self._known_companion_families = {
230
+ family.family_key for family in known_families
231
+ }
232
+ self._confirmed_companion_families = set(self._known_companion_families)
233
+ self._family_confirmation = {
234
+ family_key: "profile" for family_key in self._known_companion_families
235
+ }
236
+ self._runtime_confirmed_family_order: deque[
237
+ tuple[int, int, int, int, int]
238
+ ] = deque()
239
+ self._family_chains: dict[
240
+ tuple[int, int, int, int, int],
241
+ set[tuple[FlowKey, Optional[int]]],
242
+ ] = {}
243
+ self._family_chain_order: deque[
244
+ tuple[
245
+ tuple[int, int, int, int, int],
246
+ tuple[FlowKey, Optional[int]],
247
+ ]
248
+ ] = deque()
249
+ self._storage_delta_opcodes: set[int] = {
250
+ family.delta_opcode for family in known_families
251
+ }
252
+ self._storage_delta_opcodes.update(storage_delta_opcodes)
253
+ self._recent: dict[FlowKey, deque[BDOFrame]] = {}
254
+ self._stream_spans: dict[FlowKey, deque[_StreamSpan]] = {}
255
+ self._pending: list[_PendingDeposit] = []
256
+ self._staged_neutral_batches: dict[
257
+ tuple[FlowKey, Optional[int], Optional[int], Optional[int], float],
258
+ _StagedNeutralBatch,
259
+ ] = {}
260
+ self._observed_chains: set[
261
+ tuple[
262
+ FlowKey,
263
+ Optional[int],
264
+ tuple[int, int, int, int, int],
265
+ ]
266
+ ] = set()
267
+ self._observed_chain_order: deque[
268
+ tuple[
269
+ FlowKey,
270
+ Optional[int],
271
+ tuple[int, int, int, int, int],
272
+ ]
273
+ ] = deque()
274
+ self._contested_companion_pairs: set[
275
+ tuple[FlowKey, int, int]
276
+ ] = set()
277
+ self._contested_companion_pair_order: deque[
278
+ tuple[FlowKey, int, int]
279
+ ] = deque()
280
+ self._companion_contest_overflow_flows: set[FlowKey] = set()
281
+ self._first_record_boundaries: dict[tuple[FlowKey, int], int] = {}
282
+ self._first_record_boundary_order: deque[tuple[FlowKey, int]] = deque()
283
+
284
+ # --- frame stream ---
285
+
286
+ def observe_stream(self, data: bytes, context: PacketContext) -> None:
287
+ """Observe reassembled bytes as one serialized tracker operation."""
288
+ with self._state_lock:
289
+ self._observe_stream_locked(data, context)
290
+ self._drain_outbox()
291
+
292
+ def _observe_stream_locked(self, data: bytes, context: PacketContext) -> None:
293
+ """Retain raw reassembled bytes for alignment-independent correlation.
294
+
295
+ The generic frame tap can begin in the middle of an application frame
296
+ when live capture starts on an established TCP connection. Target
297
+ opcodes can still resynchronize in that situation, so worker evidence
298
+ must not depend exclusively on the generic tap finding a boundary.
299
+ """
300
+ if not data or context.stream_start is None:
301
+ return
302
+ span = _StreamSpan(context.stream_start, bytes(data))
303
+ window = self._stream_spans.setdefault(
304
+ context.flow,
305
+ deque(maxlen=self.STREAM_SPAN_HISTORY_LIMIT),
306
+ )
307
+ window.append(span)
308
+
309
+ if not self._pending:
310
+ return
311
+ still: list[_PendingDeposit] = []
312
+ for pending in self._pending:
313
+ if pending.finalized:
314
+ continue
315
+ if pending.flow != context.flow:
316
+ still.append(pending)
317
+ continue
318
+ if pending.end_sequence is not None and span.end <= pending.end_sequence:
319
+ still.append(pending)
320
+ continue
321
+ if context.timestamp - pending.timestamp > self.STALE_SECONDS:
322
+ self._close_pending(pending)
323
+ continue
324
+ if not self._resolve_companions(pending):
325
+ still.append(pending)
326
+ self._pending = still
327
+
328
+ def observe_frame(self, frame: BDOFrame) -> None:
329
+ """Observe one generic frame as one serialized tracker operation."""
330
+ with self._state_lock:
331
+ self._observe_frame_locked(frame)
332
+ self._drain_outbox()
333
+
334
+ def _observe_frame_locked(self, frame: BDOFrame) -> None:
335
+ window = self._recent.setdefault(
336
+ frame.context.flow,
337
+ deque(maxlen=self.BACKWARD_WINDOW),
338
+ )
339
+ window.append(frame)
340
+ if not self._pending:
341
+ return
342
+
343
+ still: list[_PendingDeposit] = []
344
+ for pending in self._pending:
345
+ if pending.finalized:
346
+ continue
347
+ if pending.flow != frame.context.flow:
348
+ if frame.context.timestamp - pending.timestamp > self.STALE_SECONDS:
349
+ self._close_pending(pending)
350
+ else:
351
+ still.append(pending)
352
+ continue
353
+ if not self._frame_is_after(frame, pending):
354
+ still.append(pending)
355
+ continue
356
+ if frame.context.timestamp - pending.timestamp > self.STALE_SECONDS:
357
+ self._close_pending(pending)
358
+ continue
359
+ if self._resolve_companions(pending):
360
+ continue
361
+ pending.frames_after += 1
362
+ if (
363
+ not pending.awaiting_storage_boundaries
364
+ and pending.frames_after >= self.LOOKAHEAD_FRAMES
365
+ ):
366
+ self._close_pending(pending)
367
+ else:
368
+ still.append(pending)
369
+ self._pending = still
370
+
371
+ @staticmethod
372
+ def _frame_is_after(frame: BDOFrame, pending: _PendingDeposit) -> bool:
373
+ if pending.stream_sequence is None or frame.stream_sequence is None:
374
+ return True
375
+ return frame.stream_sequence > pending.stream_sequence
376
+
377
+ # --- event stream ---
378
+
379
+ def register(
380
+ self,
381
+ event: BDOEvent,
382
+ raw_message: Optional[bytes] = None,
383
+ ) -> None:
384
+ """Defer one live-or-neutral storage event until evidence resolves."""
385
+ with self._state_lock:
386
+ self._register_locked(event, raw_message)
387
+ self._drain_outbox()
388
+
389
+ def _register_locked(
390
+ self,
391
+ event: BDOEvent,
392
+ raw_message: Optional[bytes] = None,
393
+ ) -> None:
394
+ flow = FlowKey(
395
+ source_ip=event.flow.source_ip,
396
+ source_port=event.flow.source_port,
397
+ destination_ip=event.flow.destination_ip,
398
+ destination_port=event.flow.destination_port,
399
+ )
400
+ raw_sequence = event.extra.get("stream_sequence")
401
+ stream_sequence = raw_sequence if isinstance(raw_sequence, int) else None
402
+
403
+ if self._stage_neutral_batch(
404
+ event,
405
+ raw_message,
406
+ flow=flow,
407
+ stream_sequence=stream_sequence,
408
+ ):
409
+ return
410
+ self._register_group(
411
+ (event,),
412
+ raw_message,
413
+ flow=flow,
414
+ stream_sequence=stream_sequence,
415
+ )
416
+
417
+ def _stage_neutral_batch(
418
+ self,
419
+ event: BDOEvent,
420
+ raw_message: Optional[bytes],
421
+ *,
422
+ flow: FlowKey,
423
+ stream_sequence: Optional[int],
424
+ ) -> bool:
425
+ """Buffer unknown-operation multi-record wrappers as one decision."""
426
+ record_index = event.record_index
427
+ record_count = event.record_count
428
+ if (
429
+ event.event_type != "storage_record"
430
+ or isinstance(record_index, bool)
431
+ or not isinstance(record_index, int)
432
+ or isinstance(record_count, bool)
433
+ or not isinstance(record_count, int)
434
+ or record_count <= 1
435
+ or not 1 <= record_index <= record_count
436
+ ):
437
+ return False
438
+
439
+ key = (
440
+ flow,
441
+ stream_sequence,
442
+ event.opcode,
443
+ event.message_length,
444
+ event.timestamp,
445
+ )
446
+ staged = self._staged_neutral_batches.get(key)
447
+ if staged is None:
448
+ staged = _StagedNeutralBatch(expected_count=record_count)
449
+ self._staged_neutral_batches[key] = staged
450
+ elif staged.expected_count != record_count:
451
+ staged.invalid = True
452
+
453
+ previous = staged.entries.get(record_index)
454
+ if previous is not None:
455
+ if previous[0] != event or previous[1] != raw_message:
456
+ staged.invalid = True
457
+ else:
458
+ staged.entries[record_index] = (event, raw_message)
459
+
460
+ if len(staged.entries) < staged.expected_count:
461
+ return True
462
+
463
+ self._staged_neutral_batches.pop(key, None)
464
+ expected_indexes = set(range(1, staged.expected_count + 1))
465
+ if staged.invalid or set(staged.entries) != expected_indexes:
466
+ self._emit_neutral_entries(staged)
467
+ return True
468
+
469
+ ordered = tuple(staged.entries[index] for index in sorted(staged.entries))
470
+ messages = {entry[1] for entry in ordered if entry[1] is not None}
471
+ if len(messages) > 1:
472
+ self._emit_neutral_entries(staged)
473
+ return True
474
+ group_raw = next(iter(messages), None)
475
+ self._register_group(
476
+ tuple(entry[0] for entry in ordered),
477
+ group_raw,
478
+ flow=flow,
479
+ stream_sequence=stream_sequence,
480
+ )
481
+ return True
482
+
483
+ def _emit_neutral_entries(self, staged: _StagedNeutralBatch) -> None:
484
+ for record_index in sorted(staged.entries):
485
+ self._queue_emit(staged.entries[record_index][0])
486
+
487
+ def _register_group(
488
+ self,
489
+ events: tuple[BDOEvent, ...],
490
+ raw_message: Optional[bytes],
491
+ *,
492
+ flow: FlowKey,
493
+ stream_sequence: Optional[int],
494
+ ) -> None:
495
+ """Register one raw storage message, emitting all records atomically."""
496
+ event = events[0]
497
+ if event.opcode is not None:
498
+ self._storage_delta_opcodes.add(event.opcode)
499
+ end_sequence = None
500
+ if stream_sequence is not None and event.message_length is not None:
501
+ end_sequence = stream_sequence + event.message_length
502
+ delta_message = None
503
+ if raw_message is not None and event.message_length is not None:
504
+ candidate = bytes(raw_message)
505
+ if (
506
+ len(candidate) == event.message_length
507
+ and len(candidate) >= 5
508
+ and int.from_bytes(candidate[0:2], "little") == len(candidate)
509
+ ):
510
+ delta_message = candidate
511
+ record_boundaries = [
512
+ candidate.record_offset
513
+ for candidate in events
514
+ if isinstance(candidate.record_offset, int)
515
+ and not isinstance(candidate.record_offset, bool)
516
+ and candidate.message_length is not None
517
+ and 5 + 8 <= candidate.record_offset <= candidate.message_length
518
+ ]
519
+ delta_prefix_end = min(record_boundaries) if record_boundaries else None
520
+ if delta_prefix_end is not None and stream_sequence is not None:
521
+ boundary_key = (flow, stream_sequence)
522
+ previous = self._first_record_boundaries.get(boundary_key)
523
+ if previous is None:
524
+ if (
525
+ len(self._first_record_boundary_order)
526
+ >= self.RECORD_BOUNDARY_HISTORY_LIMIT
527
+ ):
528
+ expired = self._first_record_boundary_order.popleft()
529
+ self._first_record_boundaries.pop(expired, None)
530
+ self._first_record_boundary_order.append(boundary_key)
531
+ self._first_record_boundaries[boundary_key] = delta_prefix_end
532
+ else:
533
+ delta_prefix_end = min(previous, delta_prefix_end)
534
+ self._first_record_boundaries[boundary_key] = delta_prefix_end
535
+ # The raw stream/frame observers run before target records are
536
+ # decoded. An older operation may therefore have crossed this
537
+ # wrapper without yet knowing where its transaction prefix ends.
538
+ # Retry it now that the authoritative first-record boundary is
539
+ # available; never substitute the entire record body as a prefix.
540
+ self._retry_boundary_waiters(flow, stream_sequence)
541
+ manual_matches: list[tuple[int, _ManualDecrementMatch]] = []
542
+ for index, candidate_event in enumerate(events, 1):
543
+ match = self._matching_decrement(
544
+ flow,
545
+ stream_sequence,
546
+ candidate_event,
547
+ )
548
+ if match is not None:
549
+ manual_matches.append(
550
+ (candidate_event.record_index or index, match)
551
+ )
552
+ matching_indexes = tuple(index for index, _match in manual_matches)
553
+ pending = _PendingDeposit(
554
+ event=event,
555
+ flow=flow,
556
+ stream_sequence=stream_sequence,
557
+ timestamp=event.timestamp,
558
+ matching_decrement=bool(matching_indexes),
559
+ events=events,
560
+ matching_decrement_record_indexes=matching_indexes,
561
+ manual_decrement_matches=tuple(manual_matches),
562
+ end_sequence=end_sequence,
563
+ delta_message=delta_message,
564
+ delta_prefix_end=delta_prefix_end,
565
+ )
566
+ if self._resolve_companions(pending):
567
+ return
568
+
569
+ # The frame tap runs before event decoding. Credit already observed
570
+ # frames when the full TCP segment contained the delta and lookahead.
571
+ for frame in self._recent.get(flow, ()):
572
+ if self._frame_is_after(frame, pending):
573
+ pending.frames_after += 1
574
+ if (
575
+ pending.frames_after >= self.LOOKAHEAD_FRAMES
576
+ and not pending.awaiting_storage_boundaries
577
+ ):
578
+ self._close_pending(pending)
579
+ else:
580
+ self._append_pending(pending)
581
+
582
+ def _retry_boundary_waiters(
583
+ self,
584
+ flow: FlowKey,
585
+ stream_sequence: int,
586
+ ) -> None:
587
+ """Retry older operations deferred on one crossed storage wrapper."""
588
+
589
+ still: list[_PendingDeposit] = []
590
+ for pending in self._pending:
591
+ if pending.finalized:
592
+ continue
593
+ if (
594
+ pending.flow != flow
595
+ or stream_sequence not in pending.awaiting_storage_boundaries
596
+ ):
597
+ still.append(pending)
598
+ continue
599
+ if self._resolve_companions(pending):
600
+ continue
601
+ if (
602
+ pending.frames_after >= self.LOOKAHEAD_FRAMES
603
+ and not pending.awaiting_storage_boundaries
604
+ ):
605
+ self._close_pending(pending)
606
+ continue
607
+ still.append(pending)
608
+ self._pending = [entry for entry in still if not entry.finalized]
609
+
610
+ def _append_pending(self, pending: _PendingDeposit) -> None:
611
+ """Retain one unresolved operation under a hard operation bound."""
612
+
613
+ self._pending = [entry for entry in self._pending if not entry.finalized]
614
+ pending_key = self._pending_operation_key(pending)
615
+ operation_keys = self._pending_operation_keys()
616
+ flow_operation_keys = {
617
+ key for key in operation_keys if key[0] == pending.flow
618
+ }
619
+ while pending_key not in operation_keys and (
620
+ len(flow_operation_keys) >= self.MAX_PENDING_OPERATIONS_PER_FLOW
621
+ or len(operation_keys) >= self.MAX_PENDING_OPERATIONS_TOTAL
622
+ ):
623
+ if len(flow_operation_keys) >= self.MAX_PENDING_OPERATIONS_PER_FLOW:
624
+ oldest = next(
625
+ entry for entry in self._pending if entry.flow == pending.flow
626
+ )
627
+ else:
628
+ oldest = self._pending[0]
629
+ oldest_key = self._pending_operation_key(oldest)
630
+ oldest_group = [
631
+ entry
632
+ for entry in self._pending
633
+ if self._pending_operation_key(entry) == oldest_key
634
+ ]
635
+ self._pending = [
636
+ entry
637
+ for entry in self._pending
638
+ if self._pending_operation_key(entry) != oldest_key
639
+ ]
640
+ for oldest in oldest_group:
641
+ self._evict_pending_fail_closed(oldest)
642
+ self._pending = [
643
+ entry for entry in self._pending if not entry.finalized
644
+ ]
645
+ operation_keys = self._pending_operation_keys()
646
+ flow_operation_keys = {
647
+ key for key in operation_keys if key[0] == pending.flow
648
+ }
649
+ self._pending.append(pending)
650
+
651
+ def _evict_pending_fail_closed(self, pending: _PendingDeposit) -> None:
652
+ """Finalize under resource pressure without trusting partial chains."""
653
+
654
+ if pending.finalized:
655
+ return
656
+ pending.candidate_observations.clear()
657
+ pending.companion_observation = None
658
+ pending.awaiting_storage_boundaries = frozenset()
659
+ self._finalize(pending)
660
+
661
+ def _pending_operation_keys(
662
+ self,
663
+ ) -> set[tuple[FlowKey, Optional[int], Optional[int], Optional[int], float]]:
664
+ return {self._pending_operation_key(entry) for entry in self._pending}
665
+
666
+ @staticmethod
667
+ def _pending_operation_key(
668
+ pending: _PendingDeposit,
669
+ ) -> tuple[FlowKey, Optional[int], Optional[int], Optional[int], float]:
670
+ return (
671
+ pending.flow,
672
+ pending.stream_sequence,
673
+ pending.event.opcode,
674
+ pending.event.message_length,
675
+ pending.timestamp,
676
+ )
677
+
678
+ # --- anchored structural companion scan ---
679
+
680
+ def _read_span(self, flow: FlowKey, start: int, length: int) -> Optional[bytes]:
681
+ """Read stream bytes ``[start, start + length)`` from frame spans."""
682
+ if length <= 0:
683
+ return None
684
+ output = bytearray()
685
+ position = start
686
+ remaining = length
687
+ while remaining > 0:
688
+ source_start = None
689
+ source_data = None
690
+ # Prefer raw reassembled spans. Iterate newest-first so an overlap
691
+ # or standalone retransmission cannot shadow newer contiguous data.
692
+ for span in reversed(self._stream_spans.get(flow, ())):
693
+ offset = position - span.start
694
+ if 0 <= offset < len(span.data):
695
+ source_start = span.start
696
+ source_data = span.data
697
+ break
698
+ if source_data is None:
699
+ # Unit-level callers and older integrations may only provide
700
+ # generic frames; retain that compatible fallback.
701
+ for frame in reversed(self._recent.get(flow, ())):
702
+ if frame.stream_sequence is None:
703
+ continue
704
+ offset = position - frame.stream_sequence
705
+ if 0 <= offset < len(frame.message):
706
+ source_start = frame.stream_sequence
707
+ source_data = frame.message
708
+ break
709
+ if source_data is None or source_start is None:
710
+ return None
711
+ offset = position - source_start
712
+ chunk = source_data[offset : offset + remaining]
713
+ if not chunk:
714
+ return None
715
+ output += chunk
716
+ position += len(chunk)
717
+ remaining -= len(chunk)
718
+ return bytes(output)
719
+
720
+ def _scan_companions_after(
721
+ self,
722
+ pending: _PendingDeposit,
723
+ ) -> Optional[_CompanionScan]:
724
+ """Scan a bounded message window, skipping unrelated message families."""
725
+ if (
726
+ pending.end_sequence is None
727
+ or pending.stream_sequence is None
728
+ or pending.event.message_length is None
729
+ or pending.delta_prefix_end is None
730
+ ):
731
+ return None
732
+ delta_message = pending.delta_message or self._read_span(
733
+ pending.flow, pending.stream_sequence, pending.event.message_length
734
+ )
735
+ if delta_message is None:
736
+ return None
737
+
738
+ position = pending.end_sequence
739
+ following: list[tuple[int, bytes]] = []
740
+ crossed_storage_messages: list[tuple[int, bytes]] = []
741
+ awaiting_storage_boundaries: set[int] = set()
742
+ observations: dict[
743
+ tuple[int, int, int, int, int], CompanionObservation
744
+ ] = {}
745
+ immediate: set[tuple[int, int, int, int, int]] = set()
746
+ for message_index in range(self.LOOKAHEAD_FRAMES):
747
+ header = self._read_span(pending.flow, position, 5)
748
+ if header is None:
749
+ return _CompanionScan(
750
+ tuple(observations.values()),
751
+ False,
752
+ frozenset(immediate),
753
+ frozenset(awaiting_storage_boundaries),
754
+ )
755
+ length = int.from_bytes(header[0:2], "little")
756
+ if not 5 <= length <= MAX_TARGET_MESSAGE_LENGTH:
757
+ return _CompanionScan(
758
+ tuple(observations.values()),
759
+ True,
760
+ frozenset(immediate),
761
+ frozenset(awaiting_storage_boundaries),
762
+ )
763
+ message = self._read_span(pending.flow, position, length)
764
+ if message is None:
765
+ return _CompanionScan(
766
+ tuple(observations.values()),
767
+ False,
768
+ frozenset(immediate),
769
+ frozenset(awaiting_storage_boundaries),
770
+ )
771
+ opcode = int.from_bytes(message[3:5], "little")
772
+ if opcode in self._storage_delta_opcodes:
773
+ # A manual or independent storage action can be serialized
774
+ # between a worker delta and its companion pair. Do not use a
775
+ # storage wrapper as a companion, but do continue scanning:
776
+ # the high-entropy token below owns the eventual pair. If the
777
+ # intervening wrapper repeats that same token, ownership is
778
+ # ambiguous and the older candidate fails closed.
779
+ crossed_storage_messages.append((position, message))
780
+ position += length
781
+ continue
782
+ for first_index, (first_sequence, first_message) in enumerate(following):
783
+ observation = discover_companion_observation(
784
+ delta_message=delta_message,
785
+ first_message=first_message,
786
+ second_message=message,
787
+ timestamp=pending.timestamp,
788
+ flow=pending.flow,
789
+ stream_sequence=pending.stream_sequence,
790
+ delta_prefix_end=pending.delta_prefix_end,
791
+ )
792
+ if observation is None:
793
+ continue
794
+ missing_boundaries = {
795
+ sequence
796
+ for sequence, _message in crossed_storage_messages
797
+ if (pending.flow, sequence)
798
+ not in self._first_record_boundaries
799
+ }
800
+ if missing_boundaries:
801
+ # Target decoding will register these authoritative
802
+ # boundaries later in the same scanner pass. Defer this
803
+ # ownership decision instead of searching record bodies.
804
+ awaiting_storage_boundaries.update(missing_boundaries)
805
+ continue
806
+ bounded_storage_messages = (
807
+ (
808
+ sequence,
809
+ storage_message,
810
+ self._first_record_boundaries[(pending.flow, sequence)],
811
+ )
812
+ for sequence, storage_message in crossed_storage_messages
813
+ )
814
+ if not self._observation_has_unique_pending_owner(
815
+ pending,
816
+ observation,
817
+ bounded_storage_messages,
818
+ first_message=first_message,
819
+ second_message=message,
820
+ pair_key=(pending.flow, first_sequence, position),
821
+ ):
822
+ continue
823
+ observations.setdefault(observation.family_key, observation)
824
+ if (
825
+ not crossed_storage_messages
826
+ and first_index == 0
827
+ and message_index == 1
828
+ ):
829
+ immediate.add(observation.family_key)
830
+ following.append((position, message))
831
+ position += length
832
+
833
+ return _CompanionScan(
834
+ tuple(observations.values()),
835
+ True,
836
+ frozenset(immediate),
837
+ frozenset(awaiting_storage_boundaries),
838
+ )
839
+
840
+ def _observation_has_unique_pending_owner(
841
+ self,
842
+ pending: _PendingDeposit,
843
+ observation: CompanionObservation,
844
+ crossed_storage_messages: Iterable[tuple[int, bytes, int]],
845
+ *,
846
+ first_message: bytes,
847
+ second_message: bytes,
848
+ pair_key: tuple[FlowKey, int, int],
849
+ ) -> bool:
850
+ """Reject a shared token claimed by another overlapping storage op.
851
+
852
+ Companion discovery already proves that the token occurs in this
853
+ pending delta and both companion messages. Crossing a storage wrapper
854
+ is safe only when that wrapper does not repeat the same token and no
855
+ other active pending delta prefix claims it. This keeps overlapping
856
+ operations separable without borrowing a later worker's companions.
857
+ """
858
+
859
+ if (
860
+ pending.flow in self._companion_contest_overflow_flows
861
+ or pair_key in self._contested_companion_pairs
862
+ ):
863
+ return False
864
+
865
+ delta_message = pending.delta_message
866
+ if delta_message is None:
867
+ if (
868
+ pending.stream_sequence is None
869
+ or pending.event.message_length is None
870
+ ):
871
+ return False
872
+ delta_message = self._read_span(
873
+ pending.flow,
874
+ pending.stream_sequence,
875
+ pending.event.message_length,
876
+ )
877
+ if delta_message is None:
878
+ return False
879
+ token_offset = observation.token_offsets[0]
880
+ token = delta_message[token_offset : token_offset + TOKEN_WIDTH]
881
+ if len(token) != TOKEN_WIDTH:
882
+ return False
883
+
884
+ competing = False
885
+ for _sequence, message, boundary in crossed_storage_messages:
886
+ if self._message_claims_companion_pair(
887
+ message,
888
+ boundary,
889
+ pending,
890
+ first_message,
891
+ second_message,
892
+ ):
893
+ competing = True
894
+ break
895
+
896
+ for other in self._pending:
897
+ if other is pending or other.finalized or other.flow != pending.flow:
898
+ continue
899
+ if (
900
+ pending.stream_sequence is not None
901
+ and other.stream_sequence == pending.stream_sequence
902
+ and other.event.opcode == pending.event.opcode
903
+ and other.event.message_length == pending.event.message_length
904
+ and other.timestamp == pending.timestamp
905
+ ):
906
+ # Multiple decoded records from one raw storage wrapper share
907
+ # one transaction token and are one claimant, not competing
908
+ # operations. Neutral batches are grouped earlier; retain the
909
+ # same rule for already-live multi-record wrappers.
910
+ continue
911
+ other_message = other.delta_message
912
+ if other_message is None:
913
+ if (
914
+ other.stream_sequence is None
915
+ or other.event.message_length is None
916
+ ):
917
+ continue
918
+ other_message = self._read_span(
919
+ other.flow,
920
+ other.stream_sequence,
921
+ other.event.message_length,
922
+ )
923
+ if other_message is None or other.delta_prefix_end is None:
924
+ continue
925
+ prefix_end = min(other.delta_prefix_end, len(other_message))
926
+ if self._message_claims_companion_pair(
927
+ other_message,
928
+ prefix_end,
929
+ other,
930
+ first_message,
931
+ second_message,
932
+ ):
933
+ competing = True
934
+ break
935
+ if competing:
936
+ self._mark_companion_pair_contested(pair_key)
937
+ return False
938
+ return True
939
+
940
+ @staticmethod
941
+ def _message_claims_companion_pair(
942
+ message: bytes,
943
+ prefix_end: int,
944
+ pending: _PendingDeposit,
945
+ first_message: bytes,
946
+ second_message: bytes,
947
+ ) -> bool:
948
+ if prefix_end < 5 + TOKEN_WIDTH:
949
+ return False
950
+ return (
951
+ discover_companion_observation(
952
+ delta_message=message,
953
+ first_message=first_message,
954
+ second_message=second_message,
955
+ timestamp=pending.timestamp,
956
+ flow=pending.flow,
957
+ stream_sequence=pending.stream_sequence,
958
+ delta_prefix_end=min(prefix_end, len(message)),
959
+ )
960
+ is not None
961
+ )
962
+
963
+ def _mark_companion_pair_contested(
964
+ self,
965
+ pair_key: tuple[FlowKey, int, int],
966
+ ) -> None:
967
+ flow = pair_key[0]
968
+ if (
969
+ flow in self._companion_contest_overflow_flows
970
+ or pair_key in self._contested_companion_pairs
971
+ ):
972
+ return
973
+ if self.COMPANION_PAIR_HISTORY_LIMIT <= 0:
974
+ self._suppress_companion_candidates_for_flow(flow)
975
+ return
976
+ if (
977
+ len(self._contested_companion_pair_order)
978
+ >= self.COMPANION_PAIR_HISTORY_LIMIT
979
+ ):
980
+ expired = self._contested_companion_pair_order.popleft()
981
+ self._contested_companion_pairs.discard(expired)
982
+ # Never let bounded-history pressure turn a previously contested
983
+ # pair back into acceptable evidence. Conservatively suppress
984
+ # worker-chain attribution on that flow until it closes; manual
985
+ # decrement evidence and neutral delivery remain available.
986
+ self._suppress_companion_candidates_for_flow(expired[0])
987
+ if flow in self._companion_contest_overflow_flows:
988
+ return
989
+ self._contested_companion_pairs.add(pair_key)
990
+ self._contested_companion_pair_order.append(pair_key)
991
+
992
+ def _suppress_companion_candidates_for_flow(self, flow: FlowKey) -> None:
993
+ self._companion_contest_overflow_flows.add(flow)
994
+ for pending in self._pending:
995
+ if pending.flow != flow or pending.finalized:
996
+ continue
997
+ pending.candidate_observations.clear()
998
+ pending.companion_observation = None
999
+
1000
+ def _resolve_companions(self, pending: _PendingDeposit) -> bool:
1001
+ if pending.finalized:
1002
+ return True
1003
+ if self._select_confirmed_observation(pending):
1004
+ self._finalize(pending)
1005
+ return True
1006
+ result = self._scan_companions_after(pending)
1007
+ if result is None:
1008
+ return False
1009
+ pending.awaiting_storage_boundaries = (
1010
+ result.awaiting_storage_boundaries
1011
+ )
1012
+ for observation in result.observations:
1013
+ pending.candidate_observations.setdefault(
1014
+ observation.family_key,
1015
+ observation,
1016
+ )
1017
+ self._record_family_observation(observation)
1018
+ if pending.finalized:
1019
+ return True
1020
+ if self._select_confirmed_observation(pending):
1021
+ self._finalize(pending)
1022
+ return True
1023
+
1024
+ # An adjacent, unique pair is strong enough to bootstrap a family
1025
+ # without a separately promoted profile. Delayed/ambiguous pairs wait
1026
+ # for the bounded window to close or for repeated-family confirmation.
1027
+ immediate = result.immediate_family_keys.intersection(
1028
+ pending.candidate_observations
1029
+ )
1030
+ if len(immediate) == 1 and len(pending.candidate_observations) == 1:
1031
+ self._confirm_family(next(iter(immediate)), "adjacent-structural-chain")
1032
+ if not pending.finalized:
1033
+ self._select_confirmed_observation(pending)
1034
+ self._finalize(pending)
1035
+ return True
1036
+
1037
+ if pending.awaiting_storage_boundaries:
1038
+ return False
1039
+ if result.complete:
1040
+ self._close_pending(pending)
1041
+ return pending.finalized
1042
+ return False
1043
+
1044
+ def _record_family_observation(
1045
+ self,
1046
+ observation: CompanionObservation,
1047
+ ) -> None:
1048
+ chain_key = (observation.flow, observation.stream_sequence)
1049
+ if observation.family_key in self._confirmed_companion_families:
1050
+ self._notify_origin_observer(observation)
1051
+ return
1052
+ chains = self._family_chains.get(observation.family_key)
1053
+ is_new = chains is None or chain_key not in chains
1054
+ if is_new:
1055
+ if len(self._family_chain_order) >= self.OBSERVATION_HISTORY_LIMIT:
1056
+ expired_family, expired_chain = self._family_chain_order.popleft()
1057
+ expired_chains = self._family_chains.get(expired_family)
1058
+ if expired_chains is not None:
1059
+ expired_chains.discard(expired_chain)
1060
+ if not expired_chains:
1061
+ self._family_chains.pop(expired_family, None)
1062
+ chains = self._family_chains.setdefault(observation.family_key, set())
1063
+ chains.add(chain_key)
1064
+ self._family_chain_order.append((observation.family_key, chain_key))
1065
+ self._notify_origin_observer(observation)
1066
+ assert chains is not None
1067
+ if len(chains) >= self.FAMILY_CONFIRMATION_OBSERVATIONS:
1068
+ self._confirm_family(observation.family_key, "repeated-structural-chain")
1069
+
1070
+ def _confirm_family(
1071
+ self,
1072
+ family_key: tuple[int, int, int, int, int],
1073
+ reason: str,
1074
+ ) -> None:
1075
+ if family_key in self._confirmed_companion_families:
1076
+ return
1077
+ self._confirmed_companion_families.add(family_key)
1078
+ self._family_confirmation[family_key] = reason
1079
+ if family_key not in self._known_companion_families:
1080
+ if (
1081
+ len(self._runtime_confirmed_family_order)
1082
+ >= self.RUNTIME_CONFIRMED_FAMILY_LIMIT
1083
+ ):
1084
+ expired = self._runtime_confirmed_family_order.popleft()
1085
+ self._confirmed_companion_families.discard(expired)
1086
+ self._family_confirmation.pop(expired, None)
1087
+ self._runtime_confirmed_family_order.append(family_key)
1088
+ self._family_chains.pop(family_key, None)
1089
+ # A later independent chain can resolve an earlier ambiguous one.
1090
+ for pending in self._pending:
1091
+ if pending.finalized or family_key not in pending.candidate_observations:
1092
+ continue
1093
+ if self._select_confirmed_observation(pending):
1094
+ self._finalize(pending)
1095
+
1096
+ def _select_confirmed_observation(self, pending: _PendingDeposit) -> bool:
1097
+ if pending.flow in self._companion_contest_overflow_flows:
1098
+ pending.candidate_observations.clear()
1099
+ pending.companion_observation = None
1100
+ return False
1101
+ eligible = [
1102
+ key
1103
+ for key in pending.candidate_observations
1104
+ if key in self._confirmed_companion_families
1105
+ ]
1106
+ if not eligible:
1107
+ return False
1108
+ # An explicitly promoted profile wins if an ambiguous window happens
1109
+ # to contain more than one confirmed candidate family.
1110
+ eligible.sort(
1111
+ key=lambda key: (key not in self._known_companion_families, key)
1112
+ )
1113
+ pending.companion_observation = pending.candidate_observations[eligible[0]]
1114
+ return True
1115
+
1116
+ def _close_pending(self, pending: _PendingDeposit) -> None:
1117
+ """Close a complete/stale window, auto-confirming only if unambiguous."""
1118
+ if pending.finalized:
1119
+ return
1120
+ if self._select_confirmed_observation(pending):
1121
+ self._finalize(pending)
1122
+ return
1123
+ if len(pending.candidate_observations) == 1:
1124
+ family_key = next(iter(pending.candidate_observations))
1125
+ self._confirm_family(family_key, "unambiguous-bounded-window")
1126
+ if not pending.finalized:
1127
+ self._select_confirmed_observation(pending)
1128
+ self._finalize(pending)
1129
+
1130
+ def _notify_origin_observer(self, observation: CompanionObservation) -> None:
1131
+ # A multi-record storage frame registers several events but represents
1132
+ # one independent companion-family observation.
1133
+ key = (observation.flow, observation.stream_sequence, observation.family_key)
1134
+ if key in self._observed_chains:
1135
+ return
1136
+ if len(self._observed_chain_order) >= self.OBSERVATION_HISTORY_LIMIT:
1137
+ expired = self._observed_chain_order.popleft()
1138
+ self._observed_chains.discard(expired)
1139
+ self._observed_chains.add(key)
1140
+ self._observed_chain_order.append(key)
1141
+ if self._origin_observer is not None:
1142
+ self._outbox.append(("observer", observation))
1143
+
1144
+ # --- calibrated manual-decrement signal ---
1145
+
1146
+ def _matching_decrement(
1147
+ self,
1148
+ flow: FlowKey,
1149
+ stream_sequence: Optional[int],
1150
+ event: BDOEvent,
1151
+ ) -> Optional[_ManualDecrementMatch]:
1152
+ quantity_bytes = event.quantity.to_bytes(4, "little")
1153
+ destination_instance = self._event_storage_instance(event)
1154
+ eligible = [
1155
+ frame
1156
+ for frame in self._recent.get(flow, ())
1157
+ if not (
1158
+ stream_sequence is not None
1159
+ and frame.stream_sequence is not None
1160
+ and frame.stream_sequence >= stream_sequence
1161
+ )
1162
+ ][-self.MANUAL_LOOKBACK_FRAMES :]
1163
+ matches: list[_ManualDecrementMatch] = []
1164
+ for frame in eligible:
1165
+ for spec in self._decrement_specs.get(frame.opcode, ()):
1166
+ # Profile lengths are calibrated single-record minima. Batch
1167
+ # decrements append more records to the same frame.
1168
+ if len(frame.message) < spec.min_message_length:
1169
+ continue
1170
+ inferred_repeat_stride: Optional[int] = None
1171
+ if spec.repeat_stride is not None:
1172
+ extra_length = len(frame.message) - spec.min_message_length
1173
+ if extra_length % spec.repeat_stride:
1174
+ continue
1175
+ record_deltas: Iterable[int] = range(
1176
+ 0,
1177
+ extra_length + 1,
1178
+ spec.repeat_stride,
1179
+ )
1180
+ elif (
1181
+ spec.source_instance_offset is not None
1182
+ and destination_instance is not None
1183
+ and isinstance(event.record_index, int)
1184
+ and not isinstance(event.record_index, bool)
1185
+ and isinstance(event.record_count, int)
1186
+ and not isinstance(event.record_count, bool)
1187
+ and event.record_count > 1
1188
+ and 1 <= event.record_index <= event.record_count
1189
+ ):
1190
+ # Older calibrated profiles predate repeat_stride, but
1191
+ # their multi-record captures still expose enough
1192
+ # geometry to recover it safely: the captured decrement
1193
+ # and storage batch cardinalities align, while exact
1194
+ # source/destination instance equality validates every
1195
+ # inferred record after the first. This never expands
1196
+ # quantity-only matching.
1197
+ extra_length = len(frame.message) - spec.min_message_length
1198
+ divisor = event.record_count - 1
1199
+ if extra_length <= 0 or extra_length % divisor:
1200
+ record_deltas = (0,)
1201
+ else:
1202
+ inferred_stride = extra_length // divisor
1203
+ prefix_length = (
1204
+ spec.min_message_length - inferred_stride
1205
+ )
1206
+ if (
1207
+ inferred_stride <= 0
1208
+ or prefix_length < 5
1209
+ or spec.quantity_offset < prefix_length
1210
+ or spec.source_instance_offset < prefix_length
1211
+ ):
1212
+ record_deltas = (0,)
1213
+ else:
1214
+ inferred_repeat_stride = inferred_stride
1215
+ record_deltas = range(
1216
+ 0,
1217
+ extra_length + 1,
1218
+ inferred_stride,
1219
+ )
1220
+ else:
1221
+ record_deltas = (0,)
1222
+
1223
+ for delta in record_deltas:
1224
+ quantity_offset = spec.quantity_offset + delta
1225
+ quantity_end = quantity_offset + 4
1226
+ if (
1227
+ quantity_end > len(frame.message)
1228
+ or frame.message[quantity_offset:quantity_end]
1229
+ != quantity_bytes
1230
+ ):
1231
+ continue
1232
+
1233
+ source_offset = spec.source_instance_offset
1234
+ if source_offset is None:
1235
+ matches.append(
1236
+ _ManualDecrementMatch(
1237
+ opcode=frame.opcode,
1238
+ message_length=len(frame.message),
1239
+ quantity_offset=quantity_offset,
1240
+ source_instance_offset=None,
1241
+ match_kind="quantity-only",
1242
+ confidence="heuristic",
1243
+ instance_matches_destination=None,
1244
+ )
1245
+ )
1246
+ continue
1247
+
1248
+ source_offset += delta
1249
+ source_end = source_offset + 8
1250
+ if source_end > len(frame.message):
1251
+ continue
1252
+ source_instance = bytes(
1253
+ frame.message[source_offset:source_end]
1254
+ )
1255
+ if not self._nonempty_source_instance(source_instance):
1256
+ # A declared instance field that is empty invalidates
1257
+ # this candidate. Do not fall back to a common
1258
+ # quantity elsewhere in the frame.
1259
+ continue
1260
+
1261
+ exact = (
1262
+ destination_instance is not None
1263
+ and source_instance == destination_instance
1264
+ )
1265
+ if not exact:
1266
+ if inferred_repeat_stride is not None and delta:
1267
+ # The profile did not declare this repeated field
1268
+ # location. Exact identity is the guard that makes
1269
+ # the inferred nonzero record offset trustworthy.
1270
+ continue
1271
+ # A partial stack move can allocate a different
1272
+ # destination instance: the controlled legacy
1273
+ # new_potato_1_1_1 capture proves that mismatch is not
1274
+ # contradictory. Retain it as anchored structural
1275
+ # evidence, but require entropy in both halves. Exact
1276
+ # cross-frame equality needs no such heuristic guard.
1277
+ if not self._structural_source_instance(
1278
+ source_instance
1279
+ ):
1280
+ continue
1281
+ matches.append(
1282
+ _ManualDecrementMatch(
1283
+ opcode=frame.opcode,
1284
+ message_length=len(frame.message),
1285
+ quantity_offset=quantity_offset,
1286
+ source_instance_offset=source_offset,
1287
+ match_kind=(
1288
+ "instance-and-quantity"
1289
+ if exact
1290
+ else "anchored-instance-and-quantity"
1291
+ ),
1292
+ confidence="observed" if exact else "structural",
1293
+ instance_matches_destination=(
1294
+ exact if destination_instance is not None else None
1295
+ ),
1296
+ )
1297
+ )
1298
+
1299
+ if not matches:
1300
+ return None
1301
+ rank = {"observed": 0, "structural": 1, "heuristic": 2}
1302
+ matches.sort(key=lambda match: rank[match.confidence])
1303
+ return matches[0]
1304
+
1305
+ @staticmethod
1306
+ def _event_storage_instance(event: BDOEvent) -> Optional[bytes]:
1307
+ value = event.storage_instance
1308
+ if not isinstance(value, str) or not value.startswith("0x"):
1309
+ return None
1310
+ try:
1311
+ decoded = bytes.fromhex(value[2:])
1312
+ except ValueError:
1313
+ return None
1314
+ return decoded if len(decoded) == 8 else None
1315
+
1316
+ @staticmethod
1317
+ def _nonempty_source_instance(value: bytes) -> bool:
1318
+ return len(value) == 8 and value not in (b"\x00" * 8, b"\xff" * 8)
1319
+
1320
+ @classmethod
1321
+ def _structural_source_instance(cls, value: bytes) -> bool:
1322
+ if not cls._nonempty_source_instance(value):
1323
+ return False
1324
+ empty_halves = {b"\x00" * 4, b"\xff" * 4}
1325
+ return value[:4] not in empty_halves and value[4:] not in empty_halves
1326
+
1327
+ # --- flushing ---
1328
+
1329
+ def flush_stale(self, now: float) -> None:
1330
+ """Finalize expired evidence without racing producer-side mutation."""
1331
+ with self._state_lock:
1332
+ self._flush_stale_locked(now)
1333
+ self._drain_outbox()
1334
+
1335
+ def close_flow(self, flow: FlowKey) -> None:
1336
+ """Finalize one closed TCP flow and release all flow-keyed history."""
1337
+
1338
+ with self._state_lock:
1339
+ staged_keys = [
1340
+ key for key in self._staged_neutral_batches if key[0] == flow
1341
+ ]
1342
+ for key in staged_keys:
1343
+ self._emit_neutral_entries(self._staged_neutral_batches.pop(key))
1344
+
1345
+ for pending in tuple(self._pending):
1346
+ if pending.flow == flow and not pending.finalized:
1347
+ self._close_pending(pending)
1348
+ self._pending = [
1349
+ pending
1350
+ for pending in self._pending
1351
+ if pending.flow != flow and not pending.finalized
1352
+ ]
1353
+ self._purge_flow_history_locked(flow)
1354
+ self._drain_outbox()
1355
+
1356
+ def _purge_flow_history_locked(self, flow: FlowKey) -> None:
1357
+ self._recent.pop(flow, None)
1358
+ self._stream_spans.pop(flow, None)
1359
+
1360
+ self._family_chain_order = deque(
1361
+ (family, chain)
1362
+ for family, chain in self._family_chain_order
1363
+ if chain[0] != flow
1364
+ )
1365
+ for family, chains in tuple(self._family_chains.items()):
1366
+ remaining = {chain for chain in chains if chain[0] != flow}
1367
+ if remaining:
1368
+ self._family_chains[family] = remaining
1369
+ else:
1370
+ self._family_chains.pop(family, None)
1371
+
1372
+ self._observed_chain_order = deque(
1373
+ key for key in self._observed_chain_order if key[0] != flow
1374
+ )
1375
+ self._observed_chains = {
1376
+ key for key in self._observed_chains if key[0] != flow
1377
+ }
1378
+
1379
+ self._contested_companion_pair_order = deque(
1380
+ key for key in self._contested_companion_pair_order if key[0] != flow
1381
+ )
1382
+ self._contested_companion_pairs = {
1383
+ key for key in self._contested_companion_pairs if key[0] != flow
1384
+ }
1385
+ self._companion_contest_overflow_flows.discard(flow)
1386
+
1387
+ self._first_record_boundary_order = deque(
1388
+ key for key in self._first_record_boundary_order if key[0] != flow
1389
+ )
1390
+ self._first_record_boundaries = {
1391
+ key: value
1392
+ for key, value in self._first_record_boundaries.items()
1393
+ if key[0] != flow
1394
+ }
1395
+
1396
+ def _flush_stale_locked(self, now: float) -> None:
1397
+ stale_batches = [
1398
+ key
1399
+ for key, staged in self._staged_neutral_batches.items()
1400
+ if staged.entries
1401
+ and now
1402
+ - min(entry[0].timestamp for entry in staged.entries.values())
1403
+ > self.STALE_SECONDS
1404
+ ]
1405
+ for key in stale_batches:
1406
+ self._emit_neutral_entries(self._staged_neutral_batches.pop(key))
1407
+
1408
+ still: list[_PendingDeposit] = []
1409
+ for pending in self._pending:
1410
+ if now - pending.timestamp > self.STALE_SECONDS:
1411
+ self._close_pending(pending)
1412
+ else:
1413
+ still.append(pending)
1414
+ self._pending = still
1415
+
1416
+ def finalize_all(self) -> None:
1417
+ """Finalize every pending decision and dispatch it in tracker order."""
1418
+ with self._state_lock:
1419
+ self._finalize_all_locked()
1420
+ self._drain_outbox()
1421
+
1422
+ def _finalize_all_locked(self) -> None:
1423
+ staged, self._staged_neutral_batches = self._staged_neutral_batches, {}
1424
+ for batch in staged.values():
1425
+ self._emit_neutral_entries(batch)
1426
+
1427
+ pending, self._pending = self._pending, []
1428
+ for deposit in pending:
1429
+ self._close_pending(deposit)
1430
+ self._recent.clear()
1431
+ self._stream_spans.clear()
1432
+ self._family_chains.clear()
1433
+ self._family_chain_order.clear()
1434
+ self._observed_chains.clear()
1435
+ self._observed_chain_order.clear()
1436
+ self._contested_companion_pairs.clear()
1437
+ self._contested_companion_pair_order.clear()
1438
+ self._companion_contest_overflow_flows.clear()
1439
+ self._first_record_boundaries.clear()
1440
+ self._first_record_boundary_order.clear()
1441
+
1442
+ def _finalize(self, pending: _PendingDeposit) -> None:
1443
+ if pending.finalized:
1444
+ return
1445
+ pending.finalized = True
1446
+ companions = pending.companion_observation is not None
1447
+ # The shared-token chain remains stronger than any backward manual
1448
+ # signal, including an exact instance match, because it proves the
1449
+ # worker-specific three-frame relation for this operation.
1450
+ if companions:
1451
+ origin = ORIGIN_WORKER
1452
+ elif pending.matching_decrement:
1453
+ origin = ORIGIN_MANUAL
1454
+ else:
1455
+ origin = ORIGIN_UNKNOWN
1456
+
1457
+ events = pending.events or (pending.event,)
1458
+ was_neutral = pending.event.event_type == "storage_record"
1459
+ if was_neutral and origin == ORIGIN_UNKNOWN:
1460
+ # An unfamiliar wrapper mode is not a deposit merely because the
1461
+ # target opcode and item fields decoded. Preserve the original
1462
+ # fail-closed event exactly when neither independent live signal
1463
+ # was present.
1464
+ for event in events:
1465
+ self._queue_emit(event)
1466
+ return
1467
+
1468
+ evidence: dict[str, object] = {
1469
+ "worker_companions": companions,
1470
+ "matching_decrement": pending.matching_decrement,
1471
+ }
1472
+ if pending.manual_decrement_matches:
1473
+ if len(pending.manual_decrement_matches) > 1:
1474
+ evidence["manual_decrement_matches"] = tuple(
1475
+ {
1476
+ "record_index": candidate_index,
1477
+ **candidate.to_dict(),
1478
+ }
1479
+ for candidate_index, candidate in pending.manual_decrement_matches
1480
+ )
1481
+ if pending.companion_observation is not None:
1482
+ companion_evidence = pending.companion_observation.to_dict()
1483
+ companion_evidence["known_family"] = (
1484
+ pending.companion_observation.family_key
1485
+ in self._known_companion_families
1486
+ )
1487
+ companion_evidence["confirmed_family"] = (
1488
+ pending.companion_observation.family_key
1489
+ in self._confirmed_companion_families
1490
+ )
1491
+ companion_evidence["confirmation"] = self._family_confirmation.get(
1492
+ pending.companion_observation.family_key
1493
+ )
1494
+ evidence["companion_chain"] = companion_evidence
1495
+ if (
1496
+ was_neutral
1497
+ and len(events) > 1
1498
+ and pending.matching_decrement_record_indexes
1499
+ ):
1500
+ evidence["matching_decrement_record_indexes"] = (
1501
+ pending.matching_decrement_record_indexes
1502
+ )
1503
+
1504
+ for original in events:
1505
+ event_evidence = dict(evidence)
1506
+ if pending.manual_decrement_matches:
1507
+ rank = {"observed": 0, "structural": 1, "heuristic": 2}
1508
+ matching_this_record = tuple(
1509
+ candidate
1510
+ for candidate in pending.manual_decrement_matches
1511
+ if candidate[0] == original.record_index
1512
+ )
1513
+ selection_pool = (
1514
+ matching_this_record
1515
+ if matching_this_record
1516
+ else pending.manual_decrement_matches
1517
+ )
1518
+ record_index, selected_manual = min(
1519
+ selection_pool,
1520
+ key=lambda item: rank[item[1].confidence],
1521
+ )
1522
+ selected_payload = selected_manual.to_dict()
1523
+ selected_payload["record_index"] = record_index
1524
+ event_evidence["manual_decrement"] = selected_payload
1525
+ extra = {
1526
+ **original.extra,
1527
+ "deposit_origin_evidence": event_evidence,
1528
+ }
1529
+ if was_neutral:
1530
+ # A calibrated source decrement or confirmed worker chain
1531
+ # proves that this unfamiliar wire mode is nevertheless a
1532
+ # live mutation. Preserve that inference in additive audit
1533
+ # metadata and promote before EventFilter is applied.
1534
+ extra["storage_operation_evidence"] = {
1535
+ "wire_operation": "unknown",
1536
+ "inferred_operation": "live",
1537
+ "signal": (
1538
+ "worker_companions" if companions else "matching_decrement"
1539
+ ),
1540
+ }
1541
+
1542
+ event = dataclasses.replace(
1543
+ original,
1544
+ event_type=("storage_delta" if was_neutral else original.event_type),
1545
+ source=(
1546
+ SOURCE_WORKER_PRODUCTION
1547
+ if origin == ORIGIN_WORKER
1548
+ else SOURCE_PLAYER_INVENTORY
1549
+ if origin == ORIGIN_MANUAL
1550
+ else None
1551
+ ),
1552
+ extra=extra,
1553
+ )
1554
+ self._queue_emit(event)
1555
+
1556
+ def _queue_emit(self, event: BDOEvent) -> None:
1557
+ """Queue an application event while tracker state is locked."""
1558
+ self._outbox.append(("emit", event))
1559
+
1560
+ def _drain_outbox(self) -> None:
1561
+ """Dispatch callbacks in mutation order without holding tracker locks.
1562
+
1563
+ A concurrent or re-entrant caller only appends to the protected outbox;
1564
+ the active dispatcher observes that append before relinquishing
1565
+ ownership. Claiming and relinquishing ownership while ``_state_lock``
1566
+ is held avoids the empty-outbox handoff race.
1567
+ """
1568
+
1569
+ with self._state_lock:
1570
+ if self._dispatching:
1571
+ return
1572
+ self._dispatching = True
1573
+
1574
+ try:
1575
+ while True:
1576
+ with self._state_lock:
1577
+ if not self._outbox:
1578
+ self._dispatching = False
1579
+ return
1580
+ kind, payload = self._outbox.popleft()
1581
+
1582
+ # Do not move either callback under ``_state_lock`` or add a
1583
+ # dispatch lock around it. Live delivery takes a session lock
1584
+ # that can be held by a concurrent ``flush_stale`` caller.
1585
+ if kind == "emit":
1586
+ assert isinstance(payload, BDOEvent)
1587
+ self._emit_callback(payload)
1588
+ else:
1589
+ assert kind == "observer"
1590
+ assert isinstance(payload, CompanionObservation)
1591
+ observer = self._origin_observer
1592
+ if observer is not None:
1593
+ observer(payload)
1594
+ except BaseException:
1595
+ # Preserve callback exception propagation while allowing cleanup
1596
+ # or a later tracker operation to resume any queued deliveries.
1597
+ with self._state_lock:
1598
+ self._dispatching = False
1599
+ raise