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.
- bdo_toolkit/__init__.py +87 -0
- bdo_toolkit/_async_sessions.py +651 -0
- bdo_toolkit/_capture_backend.py +194 -0
- bdo_toolkit/_capture_options.py +68 -0
- bdo_toolkit/_capture_runtime.py +626 -0
- bdo_toolkit/_deposit_origin.py +1599 -0
- bdo_toolkit/_engine.py +327 -0
- bdo_toolkit/_framing.py +904 -0
- bdo_toolkit/_profile_runtime.py +157 -0
- bdo_toolkit/_protocol.py +386 -0
- bdo_toolkit/_reassembly.py +654 -0
- bdo_toolkit/_specs.py +285 -0
- bdo_toolkit/_storage_destination_validation.py +167 -0
- bdo_toolkit/_storage_hydration.py +241 -0
- bdo_toolkit/_version.py +3 -0
- bdo_toolkit/calibration.py +3223 -0
- bdo_toolkit/capture.py +1713 -0
- bdo_toolkit/character_state.py +3506 -0
- bdo_toolkit/cli.py +948 -0
- bdo_toolkit/diagnostics.py +51 -0
- bdo_toolkit/events.py +214 -0
- bdo_toolkit/filters.py +105 -0
- bdo_toolkit/item_state.py +48 -0
- bdo_toolkit/origin_learning.py +779 -0
- bdo_toolkit/profiles.py +370 -0
- bdo_toolkit/py.typed +1 -0
- bdo_toolkit/remote_profiles.py +358 -0
- bdo_toolkit/solare/__init__.py +50 -0
- bdo_toolkit/solare/_constants.py +94 -0
- bdo_toolkit/solare/_detail_learning.py +1437 -0
- bdo_toolkit/solare/_details.py +796 -0
- bdo_toolkit/solare/_discovery.py +1212 -0
- bdo_toolkit/solare/_live_tracker.py +472 -0
- bdo_toolkit/solare/_replay_capture.py +182 -0
- bdo_toolkit/solare/_result.py +441 -0
- bdo_toolkit/solare/_scanner.py +203 -0
- bdo_toolkit/solare/_validation.py +11 -0
- bdo_toolkit/solare/async_session.py +444 -0
- bdo_toolkit/solare/models.py +806 -0
- bdo_toolkit/solare/replay.py +62 -0
- bdo_toolkit/solare/session.py +1051 -0
- bdo_toolkit/writers.py +30 -0
- bdo_toolkit-1.0.0.dist-info/METADATA +143 -0
- bdo_toolkit-1.0.0.dist-info/RECORD +48 -0
- bdo_toolkit-1.0.0.dist-info/WHEEL +5 -0
- bdo_toolkit-1.0.0.dist-info/entry_points.txt +2 -0
- bdo_toolkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- bdo_toolkit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Out-of-band protocol compatibility diagnostics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict, dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class DecoderDiagnostic:
|
|
11
|
+
"""One deduplicated decoder-compatibility warning.
|
|
12
|
+
|
|
13
|
+
Diagnostics are deliberately not :class:`BDOEvent` objects and therefore
|
|
14
|
+
bypass event filters. A storage-ID-filtered production app can learn that
|
|
15
|
+
its profile stopped resolving destinations even when no matching event can
|
|
16
|
+
be delivered.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
code: str
|
|
20
|
+
message: str
|
|
21
|
+
severity: str = "warning"
|
|
22
|
+
subsystem: str = "storage"
|
|
23
|
+
opcode: Optional[int] = None
|
|
24
|
+
requested_storage_ids: tuple[int, ...] = ()
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict[str, object]:
|
|
27
|
+
return asdict(self)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class DecoderHealth:
|
|
32
|
+
"""Snapshot of storage-decoder compatibility, separate from packet loss."""
|
|
33
|
+
|
|
34
|
+
storage_status: str = "not_observed"
|
|
35
|
+
storage_messages_observed: int = 0
|
|
36
|
+
storage_messages_decoded: int = 0
|
|
37
|
+
storage_records_decoded: int = 0
|
|
38
|
+
storage_destination_failures: int = 0
|
|
39
|
+
storage_geometry_failures: int = 0
|
|
40
|
+
diagnostics_emitted: int = 0
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def storage_is_compatible(self) -> Optional[bool]:
|
|
44
|
+
if self.storage_status == "not_observed":
|
|
45
|
+
return None
|
|
46
|
+
return self.storage_status == "compatible"
|
|
47
|
+
|
|
48
|
+
def to_dict(self) -> dict[str, object]:
|
|
49
|
+
output = asdict(self)
|
|
50
|
+
output["storage_is_compatible"] = self.storage_is_compatible
|
|
51
|
+
return output
|
bdo_toolkit/events.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Stable event objects emitted by the toolkit.
|
|
2
|
+
|
|
3
|
+
The packet protocol is still moving, so these models intentionally keep an
|
|
4
|
+
``extra`` mapping for newly discovered fields that should not require a schema
|
|
5
|
+
break on day one.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import dataclasses
|
|
11
|
+
import datetime as dt
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from types import MappingProxyType
|
|
14
|
+
from typing import Any, Mapping, Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _freeze_value(value: Any) -> Any:
|
|
18
|
+
if isinstance(value, Mapping):
|
|
19
|
+
return MappingProxyType(
|
|
20
|
+
{key: _freeze_value(item) for key, item in value.items()}
|
|
21
|
+
)
|
|
22
|
+
if isinstance(value, (list, tuple)):
|
|
23
|
+
return tuple(_freeze_value(item) for item in value)
|
|
24
|
+
if isinstance(value, (set, frozenset)):
|
|
25
|
+
return frozenset(_freeze_value(item) for item in value)
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _thaw_value(value: Any) -> Any:
|
|
30
|
+
if isinstance(value, Mapping):
|
|
31
|
+
return {key: _thaw_value(item) for key, item in value.items()}
|
|
32
|
+
if isinstance(value, tuple):
|
|
33
|
+
return [_thaw_value(item) for item in value]
|
|
34
|
+
if isinstance(value, frozenset):
|
|
35
|
+
return [_thaw_value(item) for item in value]
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _hashable_value(value: Any) -> Any:
|
|
40
|
+
if isinstance(value, Mapping):
|
|
41
|
+
return frozenset(
|
|
42
|
+
(key, _hashable_value(item)) for key, item in value.items()
|
|
43
|
+
)
|
|
44
|
+
if isinstance(value, (tuple, list)):
|
|
45
|
+
return tuple(_hashable_value(item) for item in value)
|
|
46
|
+
if isinstance(value, (set, frozenset)):
|
|
47
|
+
return frozenset(_hashable_value(item) for item in value)
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class Flow:
|
|
53
|
+
"""Network flow for a server-to-client packet stream."""
|
|
54
|
+
|
|
55
|
+
source_ip: str
|
|
56
|
+
source_port: int
|
|
57
|
+
destination_ip: str
|
|
58
|
+
destination_port: int
|
|
59
|
+
|
|
60
|
+
def __str__(self) -> str:
|
|
61
|
+
return (
|
|
62
|
+
f"{self.source_ip}:{self.source_port} -> "
|
|
63
|
+
f"{self.destination_ip}:{self.destination_port}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> dict[str, object]:
|
|
67
|
+
return dataclasses.asdict(self)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class BDOEvent:
|
|
72
|
+
"""One decoded gameplay-relevant packet event."""
|
|
73
|
+
|
|
74
|
+
event_type: str
|
|
75
|
+
timestamp: float
|
|
76
|
+
flow: Flow
|
|
77
|
+
item_id: int
|
|
78
|
+
quantity: int
|
|
79
|
+
# Known semantic origin, producing system, or event context. Storage
|
|
80
|
+
# endpoint identity belongs exclusively to storage_id/name fields.
|
|
81
|
+
source: Optional[str] = None
|
|
82
|
+
raw_context: Optional[str] = None
|
|
83
|
+
opcode: Optional[int] = None
|
|
84
|
+
message_length: Optional[int] = None
|
|
85
|
+
base_item_id: Optional[int] = None
|
|
86
|
+
enhancement_level: Optional[int] = None
|
|
87
|
+
enhancement: Optional[str] = None
|
|
88
|
+
inventory_slot: Optional[int] = None
|
|
89
|
+
item_instance: Optional[str] = None
|
|
90
|
+
# Opaque per-record instance observed to remain identical while matching
|
|
91
|
+
# item/quantity records entered different town storages. It is not the
|
|
92
|
+
# destination identity; ``storage_id`` carries that key.
|
|
93
|
+
storage_instance: Optional[str] = None
|
|
94
|
+
record_index: Optional[int] = None
|
|
95
|
+
record_count: Optional[int] = None
|
|
96
|
+
record_offset: Optional[int] = None
|
|
97
|
+
confidence: Optional[str] = None
|
|
98
|
+
extra: Mapping[str, Any] = field(default_factory=dict)
|
|
99
|
+
storage_id: Optional[int] = None
|
|
100
|
+
storage_name: Optional[str] = None
|
|
101
|
+
storage_name_confidence: Optional[str] = None
|
|
102
|
+
# Internal TCP connection-lifetime identity. This must not affect the
|
|
103
|
+
# public event schema, equality, or hashing; stateful classifiers use it to
|
|
104
|
+
# keep reconnects that reuse one four-tuple isolated.
|
|
105
|
+
_flow_generation: int = field(
|
|
106
|
+
default=0,
|
|
107
|
+
kw_only=True,
|
|
108
|
+
repr=False,
|
|
109
|
+
compare=False,
|
|
110
|
+
hash=False,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def __post_init__(self) -> None:
|
|
114
|
+
object.__setattr__(self, "extra", _freeze_value(self.extra))
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def timestamp_text(self) -> str:
|
|
118
|
+
return dt.datetime.fromtimestamp(self.timestamp).strftime("%H:%M:%S.%f")[:-3]
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def timestamp_iso(self) -> str:
|
|
122
|
+
"""Deterministic UTC ISO-8601 timestamp for serialization."""
|
|
123
|
+
return (
|
|
124
|
+
dt.datetime.fromtimestamp(self.timestamp, tz=dt.timezone.utc)
|
|
125
|
+
.isoformat(timespec="milliseconds")
|
|
126
|
+
.replace("+00:00", "Z")
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def to_dict(self) -> dict[str, Any]:
|
|
130
|
+
output: dict[str, Any] = {
|
|
131
|
+
"event_type": self.event_type,
|
|
132
|
+
"timestamp": self.timestamp,
|
|
133
|
+
"timestamp_iso": self.timestamp_iso,
|
|
134
|
+
"flow": self.flow.to_dict(),
|
|
135
|
+
"item_id": self.item_id,
|
|
136
|
+
"quantity": self.quantity,
|
|
137
|
+
}
|
|
138
|
+
optional = {
|
|
139
|
+
"source": self.source,
|
|
140
|
+
"raw_context": self.raw_context,
|
|
141
|
+
"opcode": f"0x{self.opcode:04X}" if self.opcode is not None else None,
|
|
142
|
+
"message_length": self.message_length,
|
|
143
|
+
"base_item_id": self.base_item_id,
|
|
144
|
+
"enhancement_level": self.enhancement_level,
|
|
145
|
+
"enhancement": self.enhancement,
|
|
146
|
+
"inventory_slot": (
|
|
147
|
+
f"0x{self.inventory_slot:02x}"
|
|
148
|
+
if self.inventory_slot is not None
|
|
149
|
+
else None
|
|
150
|
+
),
|
|
151
|
+
"item_instance": self.item_instance,
|
|
152
|
+
"storage_instance": self.storage_instance,
|
|
153
|
+
"storage_id": self.storage_id,
|
|
154
|
+
"storage_name": self.storage_name,
|
|
155
|
+
"storage_name_confidence": self.storage_name_confidence,
|
|
156
|
+
"record_index": self.record_index,
|
|
157
|
+
"record_count": self.record_count,
|
|
158
|
+
"record_offset": self.record_offset,
|
|
159
|
+
"confidence": self.confidence,
|
|
160
|
+
}
|
|
161
|
+
for key, value in optional.items():
|
|
162
|
+
if value is not None:
|
|
163
|
+
output[key] = value
|
|
164
|
+
if self.extra:
|
|
165
|
+
output["extra"] = _thaw_value(self.extra)
|
|
166
|
+
return output
|
|
167
|
+
|
|
168
|
+
def __hash__(self) -> int:
|
|
169
|
+
values = tuple(
|
|
170
|
+
getattr(self, field_info.name)
|
|
171
|
+
for field_info in dataclasses.fields(self)
|
|
172
|
+
if field_info.name != "extra"
|
|
173
|
+
and field_info.compare
|
|
174
|
+
and field_info.hash is not False
|
|
175
|
+
)
|
|
176
|
+
return hash((values, _hashable_value(self.extra)))
|
|
177
|
+
|
|
178
|
+
def format_human(self) -> str:
|
|
179
|
+
parts = [f"[{self.timestamp_text}]", self.event_type.upper()]
|
|
180
|
+
is_storage = self.event_type in {
|
|
181
|
+
"storage_delta",
|
|
182
|
+
"storage_snapshot",
|
|
183
|
+
"storage_record",
|
|
184
|
+
}
|
|
185
|
+
if self.source is not None:
|
|
186
|
+
parts.append(f"source={self.source!r}")
|
|
187
|
+
if is_storage:
|
|
188
|
+
if self.storage_name is not None:
|
|
189
|
+
parts.append(f"destination={self.storage_name!r}")
|
|
190
|
+
elif self.storage_id is not None:
|
|
191
|
+
parts.append(
|
|
192
|
+
f"destination='UNKNOWN_STORAGE(0x{self.storage_id:08x})'"
|
|
193
|
+
)
|
|
194
|
+
parts.extend((f"item_id={self.item_id}", f"quantity={self.quantity}"))
|
|
195
|
+
if self.base_item_id is not None:
|
|
196
|
+
parts.append(f"base_item_id={self.base_item_id}")
|
|
197
|
+
if self.enhancement_level is not None:
|
|
198
|
+
parts.append(f"enhancement_level={self.enhancement_level}")
|
|
199
|
+
if self.enhancement is not None:
|
|
200
|
+
parts.append(f"enhancement={self.enhancement}")
|
|
201
|
+
if self.record_index is not None and self.record_count is not None:
|
|
202
|
+
parts.append(f"record_index={self.record_index}/{self.record_count}")
|
|
203
|
+
if self.item_instance is not None:
|
|
204
|
+
parts.append(f"item_instance={self.item_instance}")
|
|
205
|
+
if self.storage_instance is not None:
|
|
206
|
+
parts.append(f"storage_instance={self.storage_instance}")
|
|
207
|
+
if self.storage_id is not None:
|
|
208
|
+
parts.append(f"storage_id=0x{self.storage_id:08x}")
|
|
209
|
+
if self.storage_name_confidence is not None:
|
|
210
|
+
parts.append(
|
|
211
|
+
f"storage_name_confidence={self.storage_name_confidence}"
|
|
212
|
+
)
|
|
213
|
+
return " ".join(parts)
|
|
214
|
+
|
bdo_toolkit/filters.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Composable event filters for app-facing streams."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Iterable, Optional, TypeVar
|
|
7
|
+
|
|
8
|
+
from .events import BDOEvent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_ACTIVITY_EVENT_TYPES = frozenset(
|
|
15
|
+
{"loot_preview", "item_received", "storage_delta"}
|
|
16
|
+
)
|
|
17
|
+
_SNAPSHOT_RECORD_EVENT_TYPES = frozenset(
|
|
18
|
+
{"inventory_snapshot", "storage_snapshot"}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _freeze(values: Optional[Iterable[T]]) -> Optional[frozenset[T]]:
|
|
23
|
+
if values is None:
|
|
24
|
+
return None
|
|
25
|
+
if isinstance(values, (str, bytes)):
|
|
26
|
+
raise TypeError("filter values must be an iterable of values, not a string")
|
|
27
|
+
return frozenset(values)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, init=False)
|
|
31
|
+
class EventFilter:
|
|
32
|
+
"""Filter completed events before yielding them to an application.
|
|
33
|
+
|
|
34
|
+
Constructing ``EventFilter()`` directly remains an unconstrained filter.
|
|
35
|
+
The named presets make common delivery policies explicit without changing
|
|
36
|
+
the meaning of any caller-supplied filter.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
event_types: Optional[frozenset[str]] = None
|
|
40
|
+
# Exact semantic origin, producing-system, or event-context labels.
|
|
41
|
+
sources: Optional[frozenset[str]] = None
|
|
42
|
+
# Authoritative numeric storage destination identities.
|
|
43
|
+
storage_ids: Optional[frozenset[int]] = None
|
|
44
|
+
item_ids: Optional[frozenset[int]] = None
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
*,
|
|
49
|
+
event_types: Optional[Iterable[str]] = None,
|
|
50
|
+
sources: Optional[Iterable[str]] = None,
|
|
51
|
+
storage_ids: Optional[Iterable[int]] = None,
|
|
52
|
+
item_ids: Optional[Iterable[int]] = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
# Normalize at the public constructor boundary so callers can use
|
|
55
|
+
# ordinary sets/lists while the frozen object remains truly immutable.
|
|
56
|
+
object.__setattr__(self, "event_types", _freeze(event_types))
|
|
57
|
+
object.__setattr__(self, "sources", _freeze(sources))
|
|
58
|
+
object.__setattr__(self, "storage_ids", _freeze(storage_ids))
|
|
59
|
+
object.__setattr__(self, "item_ids", _freeze(item_ids))
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_values(
|
|
63
|
+
cls,
|
|
64
|
+
*,
|
|
65
|
+
event_types: Optional[Iterable[str]] = None,
|
|
66
|
+
sources: Optional[Iterable[str]] = None,
|
|
67
|
+
storage_ids: Optional[Iterable[int]] = None,
|
|
68
|
+
item_ids: Optional[Iterable[int]] = None,
|
|
69
|
+
) -> "EventFilter":
|
|
70
|
+
return cls(
|
|
71
|
+
event_types=event_types,
|
|
72
|
+
sources=sources,
|
|
73
|
+
storage_ids=storage_ids,
|
|
74
|
+
item_ids=item_ids,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def activity(cls) -> "EventFilter":
|
|
79
|
+
"""Ordinary live events, excluding hydration and neutral diagnostics."""
|
|
80
|
+
|
|
81
|
+
return cls(event_types=_ACTIVITY_EVENT_TYPES)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def snapshot_records(cls) -> "EventFilter":
|
|
85
|
+
"""Per-record inventory and storage character-load hydration."""
|
|
86
|
+
|
|
87
|
+
return cls(event_types=_SNAPSHOT_RECORD_EVENT_TYPES)
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def all(cls) -> "EventFilter":
|
|
91
|
+
"""Every completed decoded event, equivalent to ``EventFilter()``."""
|
|
92
|
+
|
|
93
|
+
return cls()
|
|
94
|
+
|
|
95
|
+
def allows(self, event: BDOEvent) -> bool:
|
|
96
|
+
if self.event_types is not None and event.event_type not in self.event_types:
|
|
97
|
+
return False
|
|
98
|
+
if self.sources is not None and event.source not in self.sources:
|
|
99
|
+
return False
|
|
100
|
+
if self.storage_ids is not None and event.storage_id not in self.storage_ids:
|
|
101
|
+
return False
|
|
102
|
+
if self.item_ids is not None and event.item_id not in self.item_ids:
|
|
103
|
+
return False
|
|
104
|
+
return True
|
|
105
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Canonical experimental facade for aggregate inventory and storage state.
|
|
2
|
+
|
|
3
|
+
The implementation remains in :mod:`bdo_toolkit.character_state` so existing
|
|
4
|
+
imports continue to work. These aliases provide item-domain naming without a
|
|
5
|
+
wholesale move or package-root stability promise.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .character_state import (
|
|
11
|
+
CharacterLoadSession,
|
|
12
|
+
CharacterStateSnapshot as ItemStateSnapshot,
|
|
13
|
+
InventoryContainerSummary,
|
|
14
|
+
InventoryHydrationDiagnostics,
|
|
15
|
+
InventorySnapshotSummary,
|
|
16
|
+
ItemStateCaptureLimitError,
|
|
17
|
+
ItemStateCaptureLimits,
|
|
18
|
+
ItemStateCoverage,
|
|
19
|
+
ItemStateDiagnostics,
|
|
20
|
+
ItemStateProvenance,
|
|
21
|
+
SnapshotItem,
|
|
22
|
+
StorageContents,
|
|
23
|
+
StorageDestinationDiagnostics,
|
|
24
|
+
StorageHydrationDiagnostics,
|
|
25
|
+
StorageSnapshotSummary,
|
|
26
|
+
analyze_character_load_pcap as analyze_item_state_pcap,
|
|
27
|
+
format_character_state as format_item_state,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"CharacterLoadSession",
|
|
32
|
+
"InventoryContainerSummary",
|
|
33
|
+
"InventoryHydrationDiagnostics",
|
|
34
|
+
"InventorySnapshotSummary",
|
|
35
|
+
"ItemStateCaptureLimitError",
|
|
36
|
+
"ItemStateCaptureLimits",
|
|
37
|
+
"ItemStateCoverage",
|
|
38
|
+
"ItemStateDiagnostics",
|
|
39
|
+
"ItemStateProvenance",
|
|
40
|
+
"ItemStateSnapshot",
|
|
41
|
+
"SnapshotItem",
|
|
42
|
+
"StorageContents",
|
|
43
|
+
"StorageDestinationDiagnostics",
|
|
44
|
+
"StorageHydrationDiagnostics",
|
|
45
|
+
"StorageSnapshotSummary",
|
|
46
|
+
"analyze_item_state_pcap",
|
|
47
|
+
"format_item_state",
|
|
48
|
+
]
|