Implement native AgentManager services (#59)
All checks were successful
Native code generation / deterministic (push) Successful in 12m0s
Imaging and meshing gate / native (push) Successful in 4m3s
Native Rust workspace compile / compile (push) Successful in 4m4s

This commit is contained in:
2026-08-09 22:07:51 +00:00
parent c8b2317e13
commit 9c9d5b91f1
16 changed files with 7464 additions and 1531 deletions

View File

@@ -13,6 +13,7 @@ on:
- "tools/check_api_coverage.py"
- "tools/check_codegen_gate.py"
- "tools/check_milestone_08.py"
- "tools/check_milestone_09_issue_59.py"
- "crates/libremetaverse/Cargo.toml"
- "crates/libremetaverse/src/generated.rs"
- "crates/libremetaverse/src/packets.rs"
@@ -37,6 +38,7 @@ on:
- "tools/check_api_coverage.py"
- "tools/check_codegen_gate.py"
- "tools/check_milestone_08.py"
- "tools/check_milestone_09_issue_59.py"
- "crates/libremetaverse/Cargo.toml"
- "crates/libremetaverse/src/generated.rs"
- "crates/libremetaverse/src/packets.rs"
@@ -69,6 +71,7 @@ jobs:
python3 tools/generate_api_shims.py --check
python3 tools/check_api_coverage.py
python3 tools/check_milestone_08.py
python3 tools/check_milestone_09_issue_59.py
cargo check --manifest-path tests/api-compile/Cargo.toml --locked
- name: Test generated wire codecs and translated parity cases
run: |

View File

@@ -25,5 +25,7 @@ jobs:
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
- name: Audit native AgentManager boundary
run: python3 tools/check_milestone_09_issue_59.py
- name: Compile every workspace target with bounded memory
run: cargo check --workspace --all-targets --locked -j 1

View File

@@ -324,3 +324,18 @@ counters and receives real UDP and server-stat updates without exposing packet
payloads or endpoints. The gate asserts token redaction, bounded policies, and
joined CAPS/download/network tasks; `tools/check_milestone_08.py` prevents owned
networking stubs or required audit evidence from regressing.
### Milestone 09
The first world-services slice provides a native non-movement `AgentManager`
over the milestone 08 transport. Chat, instant messages and conferences,
gestures, animation, viewer effects, balance and payments, profiles, notes,
picks, classifieds, display names, language/access/preferences, experiences,
viewer reporting, benefits, attachment resources, mute lists, and nav-mesh
status now use their C#-compatible LLUDP or capability paths. Incoming state is
updated before callbacks, and callbacks run outside synchronization locks.
Gesture assets have a native version-2 parser and executor with bounded HTTP
and UDP download paths. An offline fake grid verifies capability discovery and
the resulting chat, animation, and sound packets. The ownership, cancellation,
limits, and compatibility rules are documented in
[`docs/agent-manager.md`](docs/agent-manager.md).

View File

@@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
| Assembly | Types | Members | Status |
|---|---:|---:|---|
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 79 types / 14,127 members; remaining surface is callable failure-only shims |
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 121 types / 14,470 members; remaining surface is callable failure-only shims |
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain |
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,6 +256,12 @@ impl Caps {
Ok(read(&self.inner.caps).get(&capability).cloned())
}
pub(crate) fn seed_request_finished(&self) -> bool {
mutex(&self.inner.seed_task)
.as_ref()
.is_none_or(|task| task.handle.is_finished())
}
pub fn disconnect(&self, immediate: bool) -> Result<(), Error> {
self.inner.disconnect(immediate);
Ok(())

View File

@@ -117,6 +117,7 @@ struct ClientRuntime {
http_caps_client: Mutex<crate::caps_http::HttpCapsClient>,
caps_rate_limiter: Mutex<crate::caps_http::CapsRateLimiter>,
network_manager: Mutex<std::sync::Weak<crate::network_manager::NetworkManagerInner>>,
agent_manager: Mutex<std::sync::Weak<crate::agent_manager::AgentManagerInner>>,
shutdown_complete: Condvar,
shutdown_wait: Mutex<()>,
}
@@ -144,6 +145,7 @@ impl ClientRuntime {
http_caps_client: Mutex::new(http_caps_client),
caps_rate_limiter: Mutex::new(caps_rate_limiter),
network_manager: Mutex::new(std::sync::Weak::new()),
agent_manager: Mutex::new(std::sync::Weak::new()),
shutdown_complete: Condvar::new(),
shutdown_wait: Mutex::new(()),
}
@@ -242,11 +244,24 @@ impl ClientRuntime {
/// Construction is side-effect free: it creates no runtime, task, socket, or
/// HTTP request. Network-facing services are attached explicitly and are owned
/// until ordered shutdown or drop.
#[derive(Clone)]
pub struct GridClient {
pub(crate) settings: Settings,
pub(crate) time_provider: TimeProvider,
runtime: Arc<ClientRuntime>,
network_manager: Option<crate::NetworkManager>,
agent_manager: Option<Box<crate::agent_manager::AgentManager>>,
}
impl Clone for GridClient {
fn clone(&self) -> Self {
Self {
settings: self.settings.clone(),
time_provider: self.time_provider.clone(),
runtime: Arc::clone(&self.runtime),
network_manager: self.network_manager.clone(),
agent_manager: None,
}
}
}
#[cfg(test)]
@@ -353,6 +368,9 @@ impl GridClient {
}
pub(crate) fn native_network(&self) -> Result<crate::NetworkManager, crate::Error> {
if let Some(manager) = &self.network_manager {
return Ok(manager.clone());
}
let mut cached = self
.runtime
.network_manager
@@ -369,12 +387,44 @@ impl GridClient {
}
#[allow(clippy::needless_pass_by_value)] // The mapped C# property setter owns its value.
pub(crate) fn native_set_network(&self, value: crate::NetworkManager) {
pub(crate) fn native_set_network(&mut self, value: crate::NetworkManager) {
*self
.runtime
.network_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = value.native_inner_weak();
self.network_manager = Some(value);
}
pub(crate) fn native_self(&mut self) -> &mut crate::agent_manager::AgentManager {
if self.agent_manager.is_none() {
let client = Arc::new(self.clone());
let manager = crate::agent_manager::AgentManager::native_new(Some(client))
.unwrap_or_else(|_| panic!("failed to construct AgentManager"));
self.agent_manager = Some(Box::new(manager));
}
self.agent_manager.as_deref_mut().expect("agent manager")
}
pub(crate) fn cached_agent_manager_inner(
&self,
) -> Option<Arc<crate::agent_manager::AgentManagerInner>> {
self.runtime
.agent_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.upgrade()
}
pub(crate) fn cache_agent_manager_inner(
&self,
inner: &Arc<crate::agent_manager::AgentManagerInner>,
) {
*self
.runtime
.agent_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Arc::downgrade(inner);
}
pub(crate) fn native_set_caps_rate_limiter(
@@ -416,7 +466,7 @@ impl fmt::Debug for GridClient {
.field("lifecycle_state", &self.lifecycle_state())
.field("services", &service_names)
.field("time_provider", &self.time_provider)
.finish()
.finish_non_exhaustive()
}
}
@@ -527,6 +577,8 @@ impl GridClientBuilder {
http_caps_client,
caps_rate_limiter,
)),
network_manager: None,
agent_manager: None,
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,375 @@
//! Native gesture asset parsing and step types.
#![allow(clippy::inherent_to_string)] // Public names mirror C# ToString mappings.
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility API.
use crate::{Error, assets::GestureStepType};
use libremetaverse_types::{AssetType, UUID};
use std::fmt::Write as _;
#[derive(Clone, Debug, PartialEq)]
pub enum GestureStep {
Animation(GestureStepAnimation),
Sound(GestureStepSound),
Chat(GestureStepChat),
Wait(GestureStepWait),
EOF(GestureStepEOF),
}
impl GestureStep {
#[must_use]
pub const fn gesture_step_type(&self) -> GestureStepType {
match self {
Self::Animation(_) => GestureStepType::Animation,
Self::Sound(_) => GestureStepType::Sound,
Self::Chat(_) => GestureStepType::Chat,
Self::Wait(_) => GestureStepType::Wait,
Self::EOF(_) => GestureStepType::EOF,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct GestureStepAnimation {
pub animation_start: bool,
pub id: UUID,
pub name: String,
}
impl GestureStepAnimation {
pub fn new() -> Result<Self, Error> {
Ok(Self {
animation_start: true,
..Self::default()
})
}
#[must_use]
pub fn to_string(&self) -> String {
format!(
"{} animation: {}",
if self.animation_start {
"Start"
} else {
"Stop"
},
self.name
)
}
#[must_use]
pub const fn gesture_step_type(&self) -> GestureStepType {
GestureStepType::Animation
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct GestureStepSound {
pub id: UUID,
pub name: String,
}
impl GestureStepSound {
pub fn new() -> Result<Self, Error> {
Ok(Self::default())
}
#[must_use]
pub fn to_string(&self) -> String {
format!("Sound: {}", self.name)
}
#[must_use]
pub const fn gesture_step_type(&self) -> GestureStepType {
GestureStepType::Sound
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct GestureStepChat {
pub text: String,
}
impl GestureStepChat {
pub fn new() -> Result<Self, Error> {
Ok(Self::default())
}
#[must_use]
pub fn to_string(&self) -> String {
format!("Chat: {}", self.text)
}
#[must_use]
pub const fn gesture_step_type(&self) -> GestureStepType {
GestureStepType::Chat
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GestureStepWait {
pub wait_for_animation: bool,
pub wait_for_time: bool,
pub wait_time: f32,
}
impl GestureStepWait {
pub const fn new() -> Result<Self, Error> {
Ok(Self {
wait_for_animation: false,
wait_for_time: false,
wait_time: 0.0,
})
}
#[must_use]
pub fn to_string(&self) -> String {
let mut result = String::from("-- Wait for: ");
if self.wait_for_animation {
result.push_str("(animations to finish) ");
}
if self.wait_for_time {
let _ = write!(result, "(time {:.1}s)", self.wait_time);
}
result
}
#[must_use]
pub const fn gesture_step_type(&self) -> GestureStepType {
GestureStepType::Wait
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct GestureStepEOF;
impl GestureStepEOF {
pub const fn new() -> Result<Self, Error> {
Ok(Self)
}
#[must_use]
pub fn to_string(&self) -> String {
"End of gesture sequence".into()
}
#[must_use]
pub const fn gesture_step_type(&self) -> GestureStepType {
GestureStepType::EOF
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct AssetGesture {
pub replace_with: String,
pub sequence: Vec<GestureStep>,
pub trigger: String,
pub trigger_key: u8,
pub trigger_key_mask: u32,
asset_id: UUID,
asset_data: Vec<u8>,
}
impl AssetGesture {
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self::default())
}
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
Ok(Self {
asset_id,
asset_data,
..Self::default()
})
}
#[must_use]
pub const fn asset_type(&self) -> AssetType {
AssetType::Gesture
}
#[must_use]
pub const fn asset_id(&self) -> UUID {
self.asset_id
}
#[must_use]
pub fn asset_data(&self) -> &[u8] {
&self.asset_data
}
pub fn decode(&mut self) -> Result<bool, Error> {
Ok(self.decode_inner().is_ok())
}
fn decode_inner(&mut self) -> Result<(), Error> {
let text = String::from_utf8(self.asset_data.clone()).map_err(|_| Error::Argument)?;
let mut lines = text.split('\n');
if parse_line::<i32>(&mut lines)? != 2 {
return Err(Error::Argument);
}
self.trigger_key = parse_line(&mut lines)?;
self.trigger_key_mask = parse_line(&mut lines)?;
next_line(&mut lines)?.clone_into(&mut self.trigger);
next_line(&mut lines)?.clone_into(&mut self.replace_with);
let count = parse_line::<i32>(&mut lines)?;
if count < 0 {
return Err(Error::Argument);
}
let mut sequence = Vec::with_capacity(usize::try_from(count).map_err(|_| Error::Argument)?);
for _ in 0..count {
match parse_line::<i32>(&mut lines)? {
0 => {
let name = next_line(&mut lines)?.to_owned();
let id = UUID::new_with_string(
next_line(&mut lines)?.trim_end_matches('\r').into(),
)?;
let flags = parse_line::<i32>(&mut lines)?;
sequence.push(GestureStep::Animation(GestureStepAnimation {
animation_start: flags == 0,
id,
name,
}));
}
1 => {
let name = next_line(&mut lines)?.replace('\r', "");
let id = UUID::new_with_string(
next_line(&mut lines)?.trim_end_matches('\r').into(),
)?;
let _flags = parse_line::<i32>(&mut lines)?;
sequence.push(GestureStep::Sound(GestureStepSound { id, name }));
}
2 => {
let text = next_line(&mut lines)?.to_owned();
let _flags = parse_line::<i32>(&mut lines)?;
sequence.push(GestureStep::Chat(GestureStepChat { text }));
}
3 => {
let wait_time = parse_line::<f32>(&mut lines)?;
let flags = parse_line::<i32>(&mut lines)?;
sequence.push(GestureStep::Wait(GestureStepWait {
wait_for_animation: flags & 0x02 != 0,
wait_for_time: flags & 0x01 != 0,
wait_time,
}));
}
4 => {
sequence.push(GestureStep::EOF(GestureStepEOF));
break;
}
// The reference ignores unknown step discriminants.
_ => {}
}
}
self.sequence = sequence;
Ok(())
}
pub fn encode(&mut self) -> Result<(), Error> {
let mut text = format!(
"2\n{}\n{}\n{}\n{}\n{}\n",
self.trigger_key,
self.trigger_key_mask,
self.trigger,
self.replace_with,
self.sequence.len()
);
for step in &self.sequence {
match step {
GestureStep::Animation(step) => {
let _ = write!(
text,
"0\n{}\n{}\n{}\n",
step.name,
step.id,
i32::from(!step.animation_start)
);
}
GestureStep::Sound(step) => {
let _ = write!(text, "1\n{}\n{}\n0\n", step.name, step.id);
}
GestureStep::Chat(step) => {
let _ = write!(text, "2\n{}\n0\n", step.text);
}
GestureStep::Wait(step) => {
let flags =
i32::from(step.wait_for_time) | (i32::from(step.wait_for_animation) << 1);
let _ = write!(text, "3\n{:.6}\n{}\n", step.wait_time, flags);
}
GestureStep::EOF(_) => {
text.push_str("4\n");
break;
}
}
}
self.asset_data = text.into_bytes();
Ok(())
}
}
fn next_line<'a>(lines: &mut impl Iterator<Item = &'a str>) -> Result<&'a str, Error> {
lines.next().ok_or(Error::Argument)
}
fn parse_line<'a, T: std::str::FromStr>(
lines: &mut impl Iterator<Item = &'a str>,
) -> Result<T, Error> {
next_line(lines)?
.trim_end_matches('\r')
.parse()
.map_err(|_| Error::Argument)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gesture_v2_roundtrip_preserves_all_step_payloads() {
let animation = UUID::random().unwrap();
let sound = UUID::random().unwrap();
let mut gesture = AssetGesture::new_with_constructor().unwrap();
gesture.trigger_key = 7;
gesture.trigger_key_mask = 3;
gesture.trigger = "/wave".into();
gesture.replace_with = "waves".into();
gesture.sequence = vec![
GestureStep::Chat(GestureStepChat {
text: "/5 hello".into(),
}),
GestureStep::Animation(GestureStepAnimation {
animation_start: true,
id: animation,
name: "Wave".into(),
}),
GestureStep::Sound(GestureStepSound {
id: sound,
name: "Bell".into(),
}),
GestureStep::Wait(GestureStepWait {
wait_for_animation: true,
wait_for_time: true,
wait_time: 1.25,
}),
GestureStep::EOF(GestureStepEOF),
];
gesture.encode().unwrap();
let mut decoded =
AssetGesture::new_with_uuid_bytes(UUID::random().unwrap(), gesture.asset_data.clone())
.unwrap();
assert!(decoded.decode().unwrap());
assert_eq!(decoded.trigger_key, gesture.trigger_key);
assert_eq!(decoded.trigger_key_mask, gesture.trigger_key_mask);
assert_eq!(decoded.trigger, gesture.trigger);
assert_eq!(decoded.replace_with, gesture.replace_with);
assert_eq!(decoded.sequence, gesture.sequence);
}
#[test]
fn malformed_gesture_returns_false_without_partial_sequence() {
let mut gesture =
AssetGesture::new_with_uuid_bytes(UUID::zero(), b"1\n0\n".to_vec()).unwrap();
assert!(!gesture.decode().unwrap());
assert!(gesture.sequence.is_empty());
}
}

View File

@@ -4,12 +4,15 @@ extern crate self as libremetaverse;
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
mod attention_catalog;
mod agent_manager;
mod agent_messages;
mod bit_pack;
mod caps;
mod caps_http;
mod client_core;
mod download_manager;
mod event_queue;
mod gesture;
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
mod foliage_catalog;
mod generated;
@@ -154,18 +157,6 @@ impl InventoryAISClient {
}
}
#[allow(dead_code)]
impl AgentManager {
fn extract_experience_permission(
_map: libremetaverse_structured_data::OSDMap,
_experience_id: libremetaverse_types::UUID,
) -> Result<String, Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.AgentManager.ExtractExperiencePermission(LibreMetaverse.StructuredData.OSDMap,LibreMetaverse.UUID)",
)
}
}
#[allow(dead_code, clippy::unused_self)]
impl EstateTools {
fn estate_owner_message_handler(

View File

@@ -295,6 +295,13 @@ pub struct PacketReceivedEventArgs {
simulator: Simulator,
}
#[derive(Clone)]
pub(crate) struct RawPacketReceivedEventArgs {
pub packet_type: PacketType,
pub data: Vec<u8>,
pub simulator: Simulator,
}
impl PacketReceivedEventArgs {
pub fn new(packet: Packet, simulator: Simulator) -> Result<Self, Error> {
Ok(Self { packet, simulator })
@@ -1201,6 +1208,12 @@ impl Simulator {
Arc::clone(&self.data)
}
pub(crate) fn native_caps(&self) -> Option<Caps> {
read(&self.data.caps_state)
.clone()
.map(|inner| Caps::native_from_inner(inner, self.native_clone_without_caps()))
}
pub(crate) fn native_from_weak(data: &Weak<SimulatorData>) -> Option<Self> {
data.upgrade().map(|data| Self { caps: None, data })
}
@@ -1775,6 +1788,7 @@ struct NetworkEvents {
logged_out: EventRegistry<LoggedOutEventArgs>,
login_progress: EventRegistry<LoginProgressEventArgs>,
packet_sent: EventRegistry<PacketSentEventArgs>,
raw_packet_received: EventRegistry<RawPacketReceivedEventArgs>,
sim_changed: EventRegistry<SimChangedEventArgs>,
sim_connected: EventRegistry<SimConnectedEventArgs>,
sim_connecting: EventRegistry<SimConnectingEventArgs>,
@@ -1870,6 +1884,15 @@ impl NetworkManagerInner {
continue;
};
inner.process_internal_packet(&packet, &simulator, raw_data.as_deref());
if let Some(data) = raw_data {
inner.events.raw_packet_received.emit_with(|| {
RawPacketReceivedEventArgs {
packet_type: packet.type_,
data: data.clone(),
simulator: simulator.clone(),
}
});
}
let _ =
inner
.packet_events
@@ -2378,6 +2401,7 @@ impl Drop for NetworkManagerInner {
}
/// Native implementation of C# `NetworkManager`.
#[derive(Clone)]
pub struct NetworkManager {
pub login_response_data: Option<LoginResponseData>,
pub simulators: SimulatorCollection,
@@ -2408,6 +2432,14 @@ impl NetworkManager {
Arc::downgrade(&self.inner)
}
pub(crate) fn native_agent_id(&self) -> UUID {
*read(&self.inner.agent_id)
}
pub(crate) fn native_session_id(&self) -> UUID {
*read(&self.inner.session_id)
}
pub fn native_new(client: GridClient) -> Result<Self, Error> {
let simulators = SimulatorCollection::default();
let packet_events = PacketEventDictionary::new(client.clone())?;
@@ -3176,6 +3208,13 @@ impl NetworkManager {
.subscribe(packet_type, handler, is_async)
}
pub(crate) fn subscribe_raw_packet(
&self,
handler: EventHandler<RawPacketReceivedEventArgs>,
) -> Subscription {
self.inner.events.raw_packet_received.subscribe(handler)
}
pub fn subscribe_caps(
&self,
caps_event: String,

49
docs/agent-manager.md Normal file
View File

@@ -0,0 +1,49 @@
# Agent manager compatibility contract
The native `AgentManager` implements LibreMetaverse's non-movement agent
services over the native network and capability layers. It owns a strong
network-manager reference until `Dispose`, so UDP and event-queue callbacks do
not disappear when the weak `GridClient` service cache is the only other
reference.
## Protocol selection and state
Chat, instant messages, conferences, gesture activation, animation, viewer
effects, balance requests, payments, profile updates, interests, picks,
classifieds, mute changes, and their replies retain the C# LLUDP packet choice
and wire fields. Capability-backed preferences, experiences, display names,
offline messages, attachment resources, viewer benefits, render information,
viewer statistics, postcards, and nav-mesh status use LLSD through the current
simulator's advertised capability. A missing optional capability produces the
same empty/`None` result as the reference; caller cancellation remains a typed
`Error::Cancelled`.
Incoming UDP and capability handlers update synchronized state before taking a
snapshot of the subscriber list. User callbacks therefore run without an
agent-state lock held. Group-chat membership, signaled animations, balance,
identity/group data, mute entries, preferences, benefits, and the last nav-mesh
status are shared between all `GridClient::Self` snapshots.
`Debug` output exposes only message and binary-payload lengths, never chat/IM
text, transaction descriptions, or capability URLs.
Mute lists are downloaded with the legacy RequestXfer/SendXferPacket protocol.
The receiver validates the agent, acknowledges ordered chunks, rejects
oversized or inconsistent transfers, replaces state only after a complete
download, and then raises `MuteListUpdated`.
## Gesture execution
Gesture assets use the C# version-2 text format. `PlayGestureAsync` first uses
`ViewerAsset` and falls back to the LLUDP asset-transfer protocol. It executes
chat-channel prefixes, animation start/stop, sound triggers, and timed waits in
sequence. The reference's animation-completion wait remains a no-op. Downloads
are bounded to 16 MiB, the whole operation is bounded to 30 seconds, and a
cancelled UDP transfer sends an abort packet.
## Verification
Unit tests cover UTF-8 chat splitting, identity and animation state, group-chat
ordering, invitation/display-name events, mute parsing, nav-mesh state, gesture
format round trips, and malformed assets. The offline fake-grid test performs
real seed discovery, serves a gesture through `ViewerAsset`, and observes the
resulting chat, animation, and sound packets on a loopback UDP peer.

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Audit issue 59's native non-movement AgentManager boundary."""
from __future__ import annotations
from pathlib import Path
import re
import generate_api_shims
ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "crates" / "libremetaverse" / "src" / "agent_manager.rs"
GESTURE = ROOT / "crates" / "libremetaverse" / "src" / "gesture.rs"
DOC = ROOT / "docs" / "agent-manager.md"
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")
REQUIRED_TYPES = {
"T:LibreMetaverse.AgentManager",
"T:LibreMetaverse.InstantMessage",
"T:LibreMetaverse.ChatSessionMember",
"T:LibreMetaverse.Assets.AssetGesture",
"T:LibreMetaverse.Assets.GestureStepAnimation",
"T:LibreMetaverse.Assets.GestureStepChat",
"T:LibreMetaverse.Assets.GestureStepEOF",
"T:LibreMetaverse.Assets.GestureStepSound",
"T:LibreMetaverse.Assets.GestureStepWait",
}
REQUIRED_MEMBERS = {
"E:LibreMetaverse.AgentManager.IM",
"E:LibreMetaverse.AgentManager.ChatFromSimulator",
"E:LibreMetaverse.AgentManager.MuteListUpdated",
"E:LibreMetaverse.AgentManager.NavMeshStatusUpdate",
"M:LibreMetaverse.AgentManager.Chat(System.String,System.Int32,LibreMetaverse.ChatType,System.Boolean)",
"M:LibreMetaverse.AgentManager.PlayGestureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.RetrieveInstantMessagesAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.GetAgentPreferencesAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.GetAgentExperiencesAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.GetAttachmentResourcesAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.GetViewerBenefitsAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.RequestMuteList",
"M:LibreMetaverse.AgentManager.RequestNavMeshGenerationStatusAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.RequestNavMeshRebakeAsync(System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.SendViewerStatsAsync(LibreMetaverse.Messages.Linden.ViewerStatsMessage,System.Threading.CancellationToken)",
"M:LibreMetaverse.AgentManager.UpdateProfileAsync(LibreMetaverse.Avatar.AvatarProperties,System.Threading.CancellationToken)",
}
def require_markers(path: Path, markers: tuple[str, ...]) -> None:
source = path.read_text()
missing = [marker for marker in markers if marker not in source]
if missing:
raise SystemExit(f"{path.name}: audit evidence missing: " + ", ".join(missing))
def main() -> None:
native_types = (
generate_api_shims.NATIVE_TYPES.keys()
| generate_api_shims.NATIVE_DECLARATIONS.keys()
)
missing_types = sorted(REQUIRED_TYPES - native_types)
if missing_types:
raise SystemExit("issue 59 native types missing: " + ", ".join(missing_types))
missing_members = sorted(REQUIRED_MEMBERS - generate_api_shims.NATIVE_MEMBER_BODIES.keys())
if missing_members:
raise SystemExit("issue 59 native members missing: " + ", ".join(missing_members))
for path in (SOURCE, GESTURE):
if STUB_RE.search(path.read_text()):
raise SystemExit(f"issue 59 owned Rust stubs remain in {path.name}")
require_markers(
SOURCE,
(
"ChatterBoxSessionAgentListUpdates",
"MuteListUpdate",
"NavMeshStatusUpdate",
"MAX_GESTURE_ASSET_BYTES",
"MAX_MUTE_LIST_BYTES",
"fake_grid_serves_gesture_capability_and_observes_udp_actions",
"instant_message_debug_redacts_message_and_binary_payload",
"callbacks are not invoked",
),
)
require_markers(GESTURE, ("gesture_v2_roundtrip_preserves_all_step_payloads", "decode"))
require_markers(DOC, ("RequestXfer", "ViewerAsset", "callbacks therefore run without"))
print(
"issue 59 audit: native AgentManager mappings, bounded gesture/mute transfers, "
"lock-free callback evidence, documentation, and offline fake-grid coverage are present"
)
if __name__ == "__main__":
main()

View File

@@ -40,6 +40,13 @@ TARGETS = {
NATIVE_TYPES = {
"T:LibreMetaverse.AccountLevelBenefits": "crate::login::AccountLevelBenefits",
"T:LibreMetaverse.AgentDisplayName": "crate::message_decoder::AgentDisplayName",
"T:LibreMetaverse.AgentDataReplyEventArgs": "crate::agent_messages::AgentDataReplyEventArgs",
"T:LibreMetaverse.AgentAccessEventArgs": "crate::agent_messages::AgentAccessEventArgs",
"T:LibreMetaverse.AgentExperiencesEventArgs": "crate::agent_messages::AgentExperiencesEventArgs",
"T:LibreMetaverse.AgentPreferencesEventArgs": "crate::agent_messages::AgentPreferencesEventArgs",
"T:LibreMetaverse.AnimationsChangedEventArgs": "crate::agent_messages::AnimationsChangedEventArgs",
"T:LibreMetaverse.AvatarRenderInfoEventArgs": "crate::agent_messages::AvatarRenderInfoEventArgs",
"T:LibreMetaverse.BalanceEventArgs": "crate::agent_messages::BalanceEventArgs",
"T:LibreMetaverse.CapabilitiesReceivedEventArgs": "crate::caps::CapabilitiesReceivedEventArgs",
"T:LibreMetaverse.Caps": "crate::caps::Caps",
"T:LibreMetaverse.CapsRateLimiter": "crate::caps_http::CapsRateLimiter",
@@ -51,6 +58,40 @@ NATIVE_TYPES = {
"T:LibreMetaverse.Http.EventQueueClient.EventCallback": "crate::event_queue::EventQueueClientEventCallback",
"T:LibreMetaverse.HttpCapsClient": "crate::caps_http::HttpCapsClient",
"T:LibreMetaverse.HttpCapsClient.ProgressReport": "crate::caps_http::HttpCapsClientProgressReport",
"T:LibreMetaverse.InstantMessage": "crate::agent_manager::InstantMessage",
"T:LibreMetaverse.InstantMessageEventArgs": "crate::agent_manager::InstantMessageEventArgs",
"T:LibreMetaverse.ChatEventArgs": "crate::agent_messages::ChatEventArgs",
"T:LibreMetaverse.ChatSessionMember": "crate::agent_messages::ChatSessionMember",
"T:LibreMetaverse.ChatSessionMemberAddedEventArgs": "crate::agent_messages::ChatSessionMemberAddedEventArgs",
"T:LibreMetaverse.ChatSessionMemberLeftEventArgs": "crate::agent_messages::ChatSessionMemberLeftEventArgs",
"T:LibreMetaverse.Assets.AssetGesture": "crate::gesture::AssetGesture",
"T:LibreMetaverse.Assets.GestureStep": "crate::gesture::GestureStep",
"T:LibreMetaverse.Assets.GestureStepAnimation": "crate::gesture::GestureStepAnimation",
"T:LibreMetaverse.Assets.GestureStepChat": "crate::gesture::GestureStepChat",
"T:LibreMetaverse.Assets.GestureStepEOF": "crate::gesture::GestureStepEOF",
"T:LibreMetaverse.Assets.GestureStepSound": "crate::gesture::GestureStepSound",
"T:LibreMetaverse.Assets.GestureStepWait": "crate::gesture::GestureStepWait",
"T:LibreMetaverse.ExperienceInfoEventArgs": "crate::agent_messages::ExperienceInfoEventArgs",
"T:LibreMetaverse.ExperiencePreferencesEventArgs": "crate::agent_messages::ExperiencePreferencesEventArgs",
"T:LibreMetaverse.GroupChatJoinedEventArgs": "crate::agent_messages::GroupChatJoinedEventArgs",
"T:LibreMetaverse.RegionExperiencesEventArgs": "crate::agent_messages::RegionExperiencesEventArgs",
"T:LibreMetaverse.SetDisplayNameReplyEventArgs": "crate::agent_messages::SetDisplayNameReplyEventArgs",
"T:LibreMetaverse.ProductInfoEventArgs": "crate::agent_messages::ProductInfoEventArgs",
"T:LibreMetaverse.ViewerBenefitsEventArgs": "crate::agent_messages::ViewerBenefitsEventArgs",
"T:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage": "crate::agent_manager::ProductInfoRequestMessage",
"T:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo": "crate::agent_manager::ProductInfoRequestMessageProductInfo",
"T:LibreMetaverse.Messages.Linden.ExperienceListMessage": "crate::agent_messages::ExperienceListMessage",
"T:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage": "crate::agent_messages::ExperiencePreferencesMessage",
"T:LibreMetaverse.Messages.Linden.RegionExperiencesMessage": "crate::agent_messages::RegionExperiencesMessage",
"T:LibreMetaverse.Messages.Linden.AgentPreferencesMessage": "crate::agent_messages::AgentPreferencesMessage",
"T:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage": "crate::agent_messages::AvatarRenderInfoMessage",
"T:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo": "crate::agent_messages::AvatarRenderInfoMessageAvatarInfo",
"T:LibreMetaverse.Messages.Linden.ExperienceInfo": "crate::agent_messages::ExperienceInfo",
"T:LibreMetaverse.Messages.Linden.ExperienceInfoMessage": "crate::agent_messages::ExperienceInfoMessage",
"T:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage": "crate::agent_messages::ViewerBenefitsMessage",
"T:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage": "crate::agent_messages::ViewerBenefitsMessageBenefitPackage",
"T:LibreMetaverse.MoneyBalanceReplyEventArgs": "crate::agent_messages::MoneyBalanceReplyEventArgs",
"T:LibreMetaverse.NavMeshStatusUpdateEventArgs": "crate::agent_messages::NavMeshStatusUpdateEventArgs",
"T:LibreMetaverse.LoginCredential": "crate::login::LoginCredential",
"T:LibreMetaverse.LoginException": "crate::login::LoginException",
"T:LibreMetaverse.LoginParams": "crate::login::LoginParams",
@@ -176,6 +217,7 @@ NATIVE_TYPES = {
# below. This is used for static namespace types such as OSDParser: the type is
# hand-written, while its fixed public methods remain generator-audited.
NATIVE_DECLARATIONS = {
"T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager",
"T:LibreMetaverse.GridClient": "crate::client_core::GridClient",
"T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager",
"T:LibreMetaverse.Settings": "crate::client_core::Settings",
@@ -184,6 +226,36 @@ NATIVE_DECLARATIONS = {
}
NATIVE_MEMBER_BODIES = {
"M:LibreMetaverse.Avatar.AvatarProperties.FromOSD(LibreMetaverse.StructuredData.OSD)":
"crate::agent_messages::avatar_properties_from_osd(o)",
"M:LibreMetaverse.Avatar.AvatarProperties.GetOSD":
"crate::agent_messages::avatar_properties_get_osd(self)",
"P:LibreMetaverse.Avatar.AvatarProperties.AllowPublish":
"self.flags.0 & crate::ProfileFlags::ALLOW_PUBLISH.0 != 0",
"P:LibreMetaverse.Avatar.AvatarProperties.AllowPublish#set":
"if value { self.flags.0 |= crate::ProfileFlags::ALLOW_PUBLISH.0 } else { self.flags.0 &= !crate::ProfileFlags::ALLOW_PUBLISH.0 }",
"P:LibreMetaverse.Avatar.AvatarProperties.Identified":
"self.flags.0 & crate::ProfileFlags::IDENTIFIED.0 != 0",
"P:LibreMetaverse.Avatar.AvatarProperties.Identified#set":
"if value { self.flags.0 |= crate::ProfileFlags::IDENTIFIED.0 } else { self.flags.0 &= !crate::ProfileFlags::IDENTIFIED.0 }",
"P:LibreMetaverse.Avatar.AvatarProperties.MaturePublish":
"self.flags.0 & crate::ProfileFlags::MATURE_PUBLISH.0 != 0",
"P:LibreMetaverse.Avatar.AvatarProperties.MaturePublish#set":
"if value { self.flags.0 |= crate::ProfileFlags::MATURE_PUBLISH.0 } else { self.flags.0 &= !crate::ProfileFlags::MATURE_PUBLISH.0 }",
"P:LibreMetaverse.Avatar.AvatarProperties.Online":
"self.flags.0 & crate::ProfileFlags::ONLINE.0 != 0",
"P:LibreMetaverse.Avatar.AvatarProperties.Online#set":
"if value { self.flags.0 |= crate::ProfileFlags::ONLINE.0 } else { self.flags.0 &= !crate::ProfileFlags::ONLINE.0 }",
"P:LibreMetaverse.Avatar.AvatarProperties.Transacted":
"self.flags.0 & crate::ProfileFlags::TRANSACTED.0 != 0",
"P:LibreMetaverse.Avatar.AvatarProperties.Transacted#set":
"if value { self.flags.0 |= crate::ProfileFlags::TRANSACTED.0 } else { self.flags.0 &= !crate::ProfileFlags::TRANSACTED.0 }",
"M:LibreMetaverse.Avatar.Interests.FromOSD(LibreMetaverse.StructuredData.OSD)":
"crate::agent_messages::avatar_interests_from_osd(o)",
"M:LibreMetaverse.Avatar.Interests.GetOSD":
"crate::agent_messages::avatar_interests_get_osd(self)",
"M:LibreMetaverse.TransactionInfo.#ctor":
"Ok(crate::TransactionInfo { amount: 0, dest_id: libremetaverse_types::UUID::zero(), is_dest_group: false, is_source_group: false, item_description: String::new(), source_id: libremetaverse_types::UUID::zero(), transaction_type: 0 })",
"M:LibreMetaverse.AgentSettings.#ctor":
"Ok(crate::client_core::agent_settings_defaults())",
"M:LibreMetaverse.AssetCacheSettings.#ctor":
@@ -222,6 +294,290 @@ NATIVE_MEMBER_BODIES = {
"self.native_network().unwrap_or_else(|_| panic!(\"failed to construct NetworkManager\"))",
"P:LibreMetaverse.GridClient.Network#set":
"self.native_set_network(value)",
"P:LibreMetaverse.GridClient.Self":
"self.native_self()",
"E:LibreMetaverse.AgentManager.IM":
"self.native_subscribe_im(handler)",
"E:LibreMetaverse.AgentManager.AgentDataReply":
"self.native_subscribe_agent_data_reply(handler)",
"E:LibreMetaverse.AgentManager.AnimationsChanged":
"self.native_subscribe_animations_changed(handler)",
"E:LibreMetaverse.AgentManager.ChatFromSimulator":
"self.native_subscribe_chat_from_simulator(handler)",
"E:LibreMetaverse.AgentManager.MoneyBalance":
"self.native_subscribe_money_balance(handler)",
"E:LibreMetaverse.AgentManager.MoneyBalanceReply":
"self.native_subscribe_money_balance_reply(handler)",
"E:LibreMetaverse.AgentManager.AgentExperiencesUpdated":
"self.native_subscribe_agent_experiences_updated(handler)",
"E:LibreMetaverse.AgentManager.ExperiencePreferencesUpdated":
"self.native_subscribe_experience_preferences_updated(handler)",
"E:LibreMetaverse.AgentManager.RegionExperiencesUpdated":
"self.native_subscribe_region_experiences_updated(handler)",
"E:LibreMetaverse.AgentManager.ExperienceInfoReceived":
"self.native_subscribe_experience_info_received(handler)",
"E:LibreMetaverse.AgentManager.AgentPreferencesUpdated":
"self.native_subscribe_agent_preferences_updated(handler)",
"E:LibreMetaverse.AgentManager.AvatarRenderInfoUpdated":
"self.native_subscribe_avatar_render_info_updated(handler)",
"E:LibreMetaverse.AgentManager.ViewerBenefitsUpdated":
"self.native_subscribe_viewer_benefits_updated(handler)",
"E:LibreMetaverse.AgentManager.ProductInfoUpdated":
"self.native_subscribe_product_info_updated(handler)",
"E:LibreMetaverse.AgentManager.MuteListUpdated":
"self.native_subscribe_mute_list_updated(handler)",
"E:LibreMetaverse.AgentManager.GroupChatJoined":
"self.native_subscribe_group_chat_joined(handler)",
"E:LibreMetaverse.AgentManager.ChatSessionMemberAdded":
"self.native_subscribe_chat_session_member_added(handler)",
"E:LibreMetaverse.AgentManager.ChatSessionMemberLeft":
"self.native_subscribe_chat_session_member_left(handler)",
"E:LibreMetaverse.AgentManager.SetDisplayNameReply":
"self.native_subscribe_set_display_name_reply(handler)",
"E:LibreMetaverse.AgentManager.NavMeshStatusUpdate":
"self.native_subscribe_nav_mesh_status_update(handler)",
"M:LibreMetaverse.AgentManager.#ctor(LibreMetaverse.GridClient)":
"crate::agent_manager::AgentManager::native_new(client)",
"M:LibreMetaverse.AgentManager.Dispose":
"self.native_dispose()",
"M:LibreMetaverse.AgentManager.ActivateGesture(LibreMetaverse.UUID,LibreMetaverse.UUID)":
"self.native_activate_gesture(inv_id, asset_id)",
"M:LibreMetaverse.AgentManager.Animate(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.Boolean},System.Boolean)":
"self.native_animate(animations, reliable)",
"M:LibreMetaverse.AgentManager.AnimationStart(LibreMetaverse.UUID,System.Boolean)":
"self.native_animation_start(animation, reliable)",
"M:LibreMetaverse.AgentManager.AnimationStop(LibreMetaverse.UUID,System.Boolean)":
"self.native_animation_stop(animation, reliable)",
"M:LibreMetaverse.AgentManager.BeamEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.Color4,System.Single,LibreMetaverse.UUID)":
"self.native_viewer_effect(source_avatar, target_object, global_offset, color, duration, effect_id, crate::EffectType::Beam, None)",
"M:LibreMetaverse.AgentManager.Chat(System.String,System.Int32,LibreMetaverse.ChatType,System.Boolean)":
"self.native_chat(message, channel, type_, split_large_messages)",
"M:LibreMetaverse.AgentManager.ChatterBoxAcceptInviteAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_chatter_box_accept_invite(session_id, cancellation_token).await",
"M:LibreMetaverse.AgentManager.DeactivateGesture(LibreMetaverse.UUID)":
"self.native_deactivate_gesture(inv_id)",
"M:LibreMetaverse.AgentManager.DeleteClassified(LibreMetaverse.UUID)":
"self.native_delete_classified(classified_id)",
"M:LibreMetaverse.AgentManager.FindExperienceByNameAsync(System.String,System.Int32,System.Int32,System.Threading.CancellationToken)":
"self.native_find_experience_by_name(query, page, page_size, cancellation_token).await",
"M:LibreMetaverse.AgentManager.ForgetExperiencePermissionAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_forget_experience_permission(experience_id, cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetAdminExperiencesAsync(System.Threading.CancellationToken)":
"self.native_get_admin_experiences(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetAgentExperiencePermissionsAsync(System.Threading.CancellationToken)":
"self.native_get_agent_experience_permissions(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetAgentExperiencesAsync(System.Threading.CancellationToken)":
"self.native_get_agent_experiences(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetAgentPreferencesAsync(System.Threading.CancellationToken)":
"self.native_get_agent_preferences(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetAttachmentResourcesAsync(System.Threading.CancellationToken)":
"self.native_get_attachment_resources(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetAvatarRenderInfoAsync(System.Threading.CancellationToken)":
"self.native_get_avatar_render_info(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetCreatorExperiencesAsync(System.Threading.CancellationToken)":
"self.native_get_creator_experiences(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetExperienceInfoAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)":
"self.native_get_experience_info(experience_ids, cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetExperiencePermissionAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_get_experience_permission(experience_id, cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetExperiencePreferencesAsync(System.Threading.CancellationToken)":
"self.native_get_experience_preferences(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetGroupExperiencesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_get_group_experiences(group_id, cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetProductInfoAsync(System.Threading.CancellationToken)":
"self.native_get_product_info(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetRegionExperiencesAsync(System.Threading.CancellationToken)":
"self.native_get_region_experiences(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GetViewerBenefitsAsync(System.Threading.CancellationToken)":
"self.native_get_viewer_benefits(cancellation_token).await",
"M:LibreMetaverse.AgentManager.GiveAvatarMoney(LibreMetaverse.UUID,System.Int32)":
"self.native_give_avatar_money(target, amount, String::new())",
"M:LibreMetaverse.AgentManager.GiveAvatarMoney(LibreMetaverse.UUID,System.Int32,System.String)":
"self.native_give_avatar_money(target, amount, description)",
"M:LibreMetaverse.AgentManager.GiveGroupMoney(LibreMetaverse.UUID,System.Int32)":
"self.native_give_group_money(target, amount, String::new())",
"M:LibreMetaverse.AgentManager.GiveGroupMoney(LibreMetaverse.UUID,System.Int32,System.String)":
"self.native_give_group_money(target, amount, description)",
"M:LibreMetaverse.AgentManager.GiveMoney(LibreMetaverse.UUID,System.Int32,System.String,LibreMetaverse.MoneyTransactionType,LibreMetaverse.TransactionFlags)":
"self.native_give_money(target, amount, description, type_, flags)",
"M:LibreMetaverse.AgentManager.GiveObjectMoney(LibreMetaverse.UUID,System.Int32,System.String)":
"self.native_give_object_money(target, amount, object_name)",
"M:LibreMetaverse.AgentManager.GroupInviteRespond(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)":
"self.native_group_invite_respond(group_id, im_session_id, accept)",
"M:LibreMetaverse.AgentManager.InstantMessage(LibreMetaverse.UUID,System.String)":
"self.native_instant_message(target, message, None)",
"M:LibreMetaverse.AgentManager.InstantMessage(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)":
"self.native_instant_message(target, message, Some(im_session_id))",
"M:LibreMetaverse.AgentManager.InstantMessage(System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,LibreMetaverse.InstantMessageDialog,LibreMetaverse.InstantMessageOnline,LibreMetaverse.Vector3,LibreMetaverse.UUID,System.Byte[])":
"self.native_instant_message_full(from_name, target, message, im_session_id, dialog, offline, position, region_id, binary_bucket)",
"M:LibreMetaverse.AgentManager.InstantMessage(System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID[])":
"self.native_instant_message_conference(from_name, target, message, im_session_id, conference_i_ds)",
"M:LibreMetaverse.AgentManager.InstantMessageGroup(LibreMetaverse.UUID,System.String)":
"self.native_instant_message_group(None, group_id, message)",
"M:LibreMetaverse.AgentManager.InstantMessageGroup(System.String,LibreMetaverse.UUID,System.String)":
"self.native_instant_message_group(Some(from_name), group_id, message)",
"M:LibreMetaverse.AgentManager.IsExperienceAdminAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_is_experience_role(\"IsExperienceAdmin\", experience_id, cancellation_token).await",
"M:LibreMetaverse.AgentManager.IsExperienceContributorAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_is_experience_role(\"IsExperienceContributor\", experience_id, cancellation_token).await",
"M:LibreMetaverse.AgentManager.IsGroupMessage(LibreMetaverse.InstantMessage)":
"Ok(self.native_is_group_message(message))",
"M:LibreMetaverse.AgentManager.LookAtEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.LookAtType,LibreMetaverse.UUID)":
"self.native_look_at_effect(source_avatar, target_object, global_offset, type_, effect_id)",
"M:LibreMetaverse.AgentManager.ModerateChatSessions(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Boolean,System.Threading.CancellationToken)":
"self.native_moderate_chat_sessions(session_id, member_id, key, moderate, cancellation_token)",
"M:LibreMetaverse.AgentManager.PayUploadFee":
"self.native_pay_upload_fee(None, String::new())",
"M:LibreMetaverse.AgentManager.PayUploadFee(System.Int32)":
"self.native_pay_upload_fee(Some(amount), String::new())",
"M:LibreMetaverse.AgentManager.PayUploadFee(System.String)":
"self.native_pay_upload_fee(None, description)",
"M:LibreMetaverse.AgentManager.PickDelete(LibreMetaverse.UUID)":
"self.native_pick_delete(pick_id)",
"M:LibreMetaverse.AgentManager.PickInfoUpdate(LibreMetaverse.UUID,System.Boolean,LibreMetaverse.UUID,System.String,LibreMetaverse.Vector3d,LibreMetaverse.UUID,System.String)":
"self.native_pick_info_update(pick_id, top_pick, parcel_id, name, global_position, texture_id, description)",
"M:LibreMetaverse.AgentManager.PlayGestureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_play_gesture(gesture_id, cancellation_token).await",
"M:LibreMetaverse.AgentManager.PointAtEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.PointAtType,LibreMetaverse.UUID)":
"self.native_point_at_effect(source_avatar, target_object, global_offset, type_, effect_id)",
"M:LibreMetaverse.AgentManager.PostAvatarRenderInfoAsync(System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.ValueTuple{System.Int32,System.Boolean}},System.Threading.CancellationToken)":
"self.native_post_avatar_render_info(agents, cancellation_token).await",
"M:LibreMetaverse.AgentManager.PostAvatarRenderInfoAsync(System.Int32,System.Boolean,System.Threading.CancellationToken)":
"self.native_post_local_avatar_render_info(weight, too_complex, cancellation_token).await",
"M:LibreMetaverse.AgentManager.ReplyToScriptDialog(System.Int32,System.Int32,System.String,LibreMetaverse.UUID)":
"self.native_reply_to_script_dialog(channel, button_index, button_label, object_id)",
"M:LibreMetaverse.AgentManager.RequestBalance":
"self.native_request_balance()",
"M:LibreMetaverse.AgentManager.RequestJoinGroupChat(LibreMetaverse.UUID)":
"self.native_join_group_chat(group_id)",
"M:LibreMetaverse.AgentManager.RequestLeaveGroupChat(LibreMetaverse.UUID)":
"self.native_leave_group_chat(group_id)",
"M:LibreMetaverse.AgentManager.RequestMuteList":
"self.native_request_mute_list()",
"M:LibreMetaverse.AgentManager.RequestNavMeshGenerationStatusAsync(System.Threading.CancellationToken)":
"self.native_request_nav_mesh_generation_status(cancellation_token).await",
"M:LibreMetaverse.AgentManager.RequestNavMeshRebakeAsync(System.Threading.CancellationToken)":
"self.native_request_nav_mesh_rebake(cancellation_token).await",
"M:LibreMetaverse.AgentManager.RemoveMuteListEntry(LibreMetaverse.UUID,System.String)":
"self.native_remove_mute_list_entry(id, name)",
"M:LibreMetaverse.AgentManager.RetrieveInstantMessagesAsync(System.Threading.CancellationToken)":
"self.native_retrieve_instant_messages(cancellation_token).await",
"M:LibreMetaverse.AgentManager.QueryExperiencesOnParcelAsync(System.Int32,System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)":
"self.native_query_experiences_on_parcel(parcel_local_id, experience_ids, cancellation_token).await",
"M:LibreMetaverse.AgentManager.SendPostcardAsync(System.Byte[],System.String,System.String,System.String,System.String,LibreMetaverse.Vector3,System.Threading.CancellationToken)":
"self.native_send_postcard(jpeg_image_data, to_email, from_name, subject, message, global_position, cancellation_token).await",
"M:LibreMetaverse.AgentManager.SendViewerStatsAsync(LibreMetaverse.Messages.Linden.ViewerStatsMessage,System.Threading.CancellationToken)":
"self.native_send_viewer_stats(stats, cancellation_token).await",
"M:LibreMetaverse.AgentManager.SetAgentAccessAsync(System.String,System.Threading.CancellationToken)":
"self.native_set_agent_access_async(access, cancellation_token).await",
"M:LibreMetaverse.AgentManager.SetDisplayNameAsync(System.String,System.String,System.Threading.CancellationToken)":
"self.native_set_display_name(old_name, new_name, cancellation_token).await",
"M:LibreMetaverse.AgentManager.SetExperiencePermissionAsync(LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)":
"self.native_set_experience_permission(experience_id, permission, cancellation_token).await",
"M:LibreMetaverse.AgentManager.SetExperiencePreferencesAsync(LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage,System.Threading.CancellationToken)":
"self.native_set_experience_preferences_async(preferences, cancellation_token).await",
"M:LibreMetaverse.AgentManager.UpdateExperienceAsync(LibreMetaverse.Messages.Linden.ExperienceInfo,System.Threading.CancellationToken)":
"self.native_update_experience(info, cancellation_token).await",
"M:LibreMetaverse.AgentManager.SphereEffect(LibreMetaverse.Vector3d,LibreMetaverse.Color4,System.Single,LibreMetaverse.UUID)":
"self.native_viewer_effect(libremetaverse_types::UUID::zero(), libremetaverse_types::UUID::zero(), global_offset, color, duration, effect_id, crate::EffectType::Sphere, None)",
"M:LibreMetaverse.AgentManager.StartIMConferenceAsync(System.Collections.Generic.List{LibreMetaverse.UUID},LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_start_im_conference(participants, tmp_session_id, cancellation_token).await",
"M:LibreMetaverse.AgentManager.SetHoverHeightAsync(System.Double,System.Threading.CancellationToken)":
"self.native_set_hover_height_async(hover_height, cancellation_token).await",
"M:LibreMetaverse.AgentManager.UpdateAgentLanguageAsync(System.String,System.Boolean,System.Threading.CancellationToken)":
"self.native_update_agent_language(language, is_public, cancellation_token).await",
"M:LibreMetaverse.AgentManager.UpdateClassifiedInfo(LibreMetaverse.UUID,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.UUID,System.Int32,LibreMetaverse.Vector3d,System.String,System.String,System.Boolean)":
"self.native_update_classified_info(classified_id, category, snapshot_id, price, position, name, desc, auto_renew)",
"M:LibreMetaverse.AgentManager.UpdateClassifiedInfo(LibreMetaverse.UUID,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.UUID,System.Int32,System.String,System.String,System.Boolean)":
"self.native_update_classified_info(classified_id, category, snapshot_id, price, libremetaverse_types::Vector3d::zero(), name, desc, auto_renew)",
"M:LibreMetaverse.AgentManager.UpdateInterests(LibreMetaverse.Avatar.Interests)":
"self.native_update_interests(interests)",
"M:LibreMetaverse.AgentManager.UpdateMuteListEntry(LibreMetaverse.MuteType,LibreMetaverse.UUID,System.String)":
"self.native_update_mute_list_entry(type_, id, name, crate::MuteFlags::DEFAULT)",
"M:LibreMetaverse.AgentManager.UpdateMuteListEntry(LibreMetaverse.MuteType,LibreMetaverse.UUID,System.String,LibreMetaverse.MuteFlags)":
"self.native_update_mute_list_entry(type_, id, name, flags)",
"M:LibreMetaverse.AgentManager.UpdateProfile(LibreMetaverse.Avatar.AvatarProperties)":
"self.native_update_profile(profile)",
"M:LibreMetaverse.AgentManager.UpdateProfileAsync(LibreMetaverse.Avatar.AvatarProperties,System.Threading.CancellationToken)":
"self.native_update_profile_async(profile, cancellation_token).await",
"M:LibreMetaverse.AgentManager.UpdateProfileNotes(LibreMetaverse.UUID,System.String)":
"self.native_update_profile_notes(target, notes)",
"M:LibreMetaverse.AgentManager.UpdateProfileNotesAsync(LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)":
"self.native_update_profile_notes_async(target, notes, cancellation_token).await",
"M:LibreMetaverse.AgentManager.UpdateProfileUdp(LibreMetaverse.Avatar.AvatarProperties)":
"self.native_update_profile_udp(profile)",
"P:LibreMetaverse.AgentManager.ActiveGestures":
"self.native_active_gestures()",
"P:LibreMetaverse.AgentManager.AgentExperienceList":
"self.native_agent_experience_list()",
"P:LibreMetaverse.AgentManager.AgentExperienceList#set":
"self.native_set_agent_experience_list(value)",
"P:LibreMetaverse.AgentManager.ExperiencePreferences":
"self.native_experience_preferences()",
"P:LibreMetaverse.AgentManager.ExperiencePreferences#set":
"self.native_set_experience_preferences(value)",
"P:LibreMetaverse.AgentManager.LastRegionExperiences":
"self.native_last_region_experiences()",
"P:LibreMetaverse.AgentManager.LastRegionExperiences#set":
"self.native_set_last_region_experiences(value)",
"P:LibreMetaverse.AgentManager.AvatarRenderInfo":
"self.native_avatar_render_info()",
"P:LibreMetaverse.AgentManager.AvatarRenderInfo#set":
"self.native_set_avatar_render_info(value)",
"P:LibreMetaverse.AgentManager.ViewerBenefits":
"self.native_viewer_benefits()",
"P:LibreMetaverse.AgentManager.ViewerBenefits#set":
"self.native_set_viewer_benefits(value)",
"P:LibreMetaverse.AgentManager.Benefits":
"self.native_benefits()",
"P:LibreMetaverse.AgentManager.Benefits#set":
"self.native_set_benefits(value)",
"P:LibreMetaverse.AgentManager.Name":
"self.native_name()",
"P:LibreMetaverse.AgentManager.ActiveGroup":
"self.native_active_group()",
"P:LibreMetaverse.AgentManager.ActiveGroup#set":
"self.native_set_active_group(value)",
"P:LibreMetaverse.AgentManager.ActiveGroupPowers":
"self.native_active_group_powers()",
"P:LibreMetaverse.AgentManager.ActiveGroupPowers#set":
"self.native_set_active_group_powers(value)",
"P:LibreMetaverse.AgentManager.FirstName":
"self.native_first_name()",
"P:LibreMetaverse.AgentManager.FirstName#set":
"self.native_set_first_name(value)",
"P:LibreMetaverse.AgentManager.LastName":
"self.native_last_name()",
"P:LibreMetaverse.AgentManager.LastName#set":
"self.native_set_last_name(value)",
"P:LibreMetaverse.AgentManager.AgentAccess":
"self.native_agent_access()",
"P:LibreMetaverse.AgentManager.AgentAccess#set":
"self.native_set_agent_access(value)",
"P:LibreMetaverse.AgentManager.HoverHeight":
"self.native_hover_height()",
"P:LibreMetaverse.AgentManager.HoverHeight#set":
"self.native_set_hover_height(value)",
"P:LibreMetaverse.AgentManager.ProductInfo":
"self.native_product_info()",
"P:LibreMetaverse.AgentManager.ProductInfo#set":
"self.native_set_product_info(value)",
"P:LibreMetaverse.AgentManager.Balance":
"self.native_balance()",
"P:LibreMetaverse.AgentManager.Balance#set":
"self.native_set_balance(value)",
"P:LibreMetaverse.AgentManager.LastNavMeshStatus":
"self.native_last_nav_mesh_status()",
"P:LibreMetaverse.AgentManager.LastNavMeshStatus#set":
"self.native_set_last_nav_mesh_status(value)",
"P:LibreMetaverse.AgentManager.AgentID":
"self.native_agent_id()",
"P:LibreMetaverse.AgentManager.AgentID#set":
"self.native_set_agent_id(value)",
"P:LibreMetaverse.AgentManager.SessionID":
"self.native_session_id()",
"P:LibreMetaverse.AgentManager.SessionID#set":
"self.native_set_session_id(value)",
"E:LibreMetaverse.NetworkManager.Disconnected":
"self.native_subscribe_disconnected(handler)",
"E:LibreMetaverse.NetworkManager.EventQueueRunning":

View File

@@ -976,33 +976,41 @@ def validate_generated_shims() -> None:
or "native network implementation" in body
):
continue
if FAKE_BODY.search(body):
raise ValueError(f"generated shim returns a plausible fallback: {path.relative_to(ROOT)}")
if not any(
uses_standardized_or_native_path = any(
marker in body
for marker in (
"unimplemented_api!",
"not_implemented(",
"NotImplemented::new(",
"crate::agent_manager::",
"crate::agent_messages::",
"crate::byte_order::",
"crate::attention_catalog::",
"crate::binary::",
"crate::dispatch::",
"crate::foliage_catalog::",
"crate::genepool_catalog::",
"crate::gesture::",
"crate::json_codec::",
"crate::message_codec::",
"crate::message_decoder::",
"crate::notation::",
"crate::packet_catalog::",
"crate::packet_wire::",
"crate::ProfileFlags::",
"crate::TransactionInfo",
"crate::protobuf::",
"crate::skeleton::",
"crate::visual_catalog::",
"crate::xml_codec::",
"stored_visual_params",
"self.native_",
)
):
)
if uses_standardized_or_native_path:
continue
if FAKE_BODY.search(body):
raise ValueError(f"generated shim returns a plausible fallback: {path.relative_to(ROOT)}")
raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}")