Implement native AIS inventory reconciliation (#63)
All checks were successful
Native code generation / deterministic (push) Successful in 13m18s
Imaging and meshing gate / native (push) Successful in 4m12s
Native Rust workspace compile / compile (push) Successful in 4m12s

This commit is contained in:
2026-08-10 06:41:29 +00:00
parent 61d6721287
commit d5c318d280
16 changed files with 1896 additions and 161 deletions

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Audit issue 63's native AISv3 capability and reconciliation 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" / "inventory_ais.rs"
HTTP = ROOT / "crates" / "libremetaverse" / "src" / "caps_http.rs"
INVENTORY = ROOT / "crates" / "libremetaverse" / "src" / "inventory.rs"
NATIVE_TESTS = ROOT / "crates" / "libremetaverse" / "src" / "inventory_ais_internal_semantics.rs"
COMPAT_TESTS = ROOT / "tests" / "compat" / "tests" / "inventory_ais_semantics.rs"
DOC = ROOT / "docs" / "inventory.md"
MAPPING = ROOT / "api" / "RUST-MAPPING.tsv"
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")
def require_markers(path: Path, markers: tuple[str, ...]) -> None:
text = path.read_text()
missing = [marker for marker in markers if marker not in text]
if missing:
raise SystemExit(f"{path.name}: audit evidence missing: " + ", ".join(missing))
def main() -> None:
native_types = generate_api_shims.NATIVE_TYPES | generate_api_shims.NATIVE_DECLARATIONS
for type_id in ("T:LibreMetaverse.AISResponseMeta", "T:LibreMetaverse.InventoryAISClient"):
if type_id not in native_types:
raise SystemExit(f"issue 63 native type missing: {type_id}")
mapped = generate_api_shims.NATIVE_MEMBER_BODIES
required = set()
for row in MAPPING.read_text().splitlines()[1:]:
doc_id = row.split("\t", 1)[0]
if doc_id.startswith(("M:LibreMetaverse.InventoryAISClient.", "P:LibreMetaverse.InventoryAISClient.")):
required.add(doc_id)
missing = sorted(required - mapped.keys())
if missing:
raise SystemExit("issue 63 native members missing: " + ", ".join(missing))
for member in (
"P:LibreMetaverse.GridClient.AisClient",
"P:LibreMetaverse.GridClient.AisClient#set",
):
if member not in mapped:
raise SystemExit(f"issue 63 GridClient member missing: {member}")
if STUB_RE.search(SOURCE.read_text()):
raise SystemExit("issue 63 owned Rust stubs remain in inventory_ais.rs")
require_markers(
SOURCE,
(
'"COPY"',
'"Destination"',
'"application/llsd+xml"',
"MAX_FOLDER_DEPTH_REQUEST",
"MAX_AIS_OBJECTS",
"Error::Cancelled",
"parse_meta_map",
"native_reconcile_ais",
),
)
require_markers(HTTP, ("send_custom", "send_with_headers"))
require_markers(INVENTORY, ("native_apply_ais_batch", "detached candidate"))
require_markers(
NATIVE_TESTS,
(
"records_exact_ais_verbs_paths_headers_and_llsd_payloads",
"malformed_and_cancelled_responses_leave_store_unchanged",
"partially_valid_response_is_rejected_before_any_reconciliation",
"response_metadata_reconciles_removals_and_folder_versions_together",
),
)
require_markers(
COMPAT_TESTS,
(
"parse_links_object_asset_results_in_attachment_type_and_parses_fields",
"parse_embedded_combines_items_and_links",
"move_category_links_returns_completed_future",
),
)
require_markers(
DOC,
(
"Inventory API v3",
"InventoryAPIv3",
"LibraryAPIv3",
"Destination",
"atomically",
"UDP fallback",
),
)
print(
"issue 63 audit: AIS endpoints, LLSD parsing, COPY headers, bounded atomic "
"reconciliation, documentation, and focused tests are present"
)
if __name__ == "__main__":
main()

View File

@@ -38,6 +38,7 @@ TARGETS = {
# implementations. The generated module keeps catalog markers and re-exports
# the hand-written type so coverage remains deterministic.
NATIVE_TYPES = {
"T:LibreMetaverse.AISResponseMeta": "crate::inventory_ais::AISResponseMeta",
"T:LibreMetaverse.AccountLevelBenefits": "crate::login::AccountLevelBenefits",
"T:LibreMetaverse.AgentDisplayName": "crate::message_decoder::AgentDisplayName",
"T:LibreMetaverse.AgentDataReplyEventArgs": "crate::agent_messages::AgentDataReplyEventArgs",
@@ -262,6 +263,7 @@ NATIVE_DECLARATIONS = {
"T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager",
"T:LibreMetaverse.GridClient": "crate::client_core::GridClient",
"T:LibreMetaverse.InventoryManager": "crate::inventory_manager::InventoryManager",
"T:LibreMetaverse.InventoryAISClient": "crate::inventory_ais::InventoryAISClient",
"T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager",
"T:LibreMetaverse.Settings": "crate::client_core::Settings",
"T:LibreMetaverse.Simulator": "crate::network_manager::Simulator",
@@ -337,6 +339,10 @@ NATIVE_MEMBER_BODIES = {
"self.native_inventory().unwrap_or_else(|_| panic!(\"failed to construct InventoryManager\"))",
"P:LibreMetaverse.GridClient.Inventory#set":
"self.native_set_inventory(value)",
"P:LibreMetaverse.GridClient.AisClient":
"self.native_ais_client().unwrap_or_else(|_| panic!(\"failed to construct InventoryAISClient\"))",
"P:LibreMetaverse.GridClient.AisClient#set":
"self.native_set_ais_client(value)",
"P:LibreMetaverse.GridClient.Network":
"self.native_network().unwrap_or_else(|_| panic!(\"failed to construct NetworkManager\"))",
"P:LibreMetaverse.GridClient.Network#set":
@@ -363,6 +369,81 @@ NATIVE_MEMBER_BODIES = {
"crate::inventory_manager::InventoryManager::native_magic_id()",
"M:LibreMetaverse.InventoryManager.#ctor(LibreMetaverse.GridClient)":
"crate::inventory_manager::InventoryManager::native_new(client)",
"F:LibreMetaverse.InventoryAISClient.INVENTORY_CAP_NAME":
"crate::inventory_ais::InventoryAISClient::INVENTORY_CAP_NAME",
"F:LibreMetaverse.InventoryAISClient.LIBRARY_CAP_NAME":
"crate::inventory_ais::InventoryAISClient::LIBRARY_CAP_NAME",
"M:LibreMetaverse.InventoryAISClient.#ctor(LibreMetaverse.GridClient)":
"crate::inventory_ais::InventoryAISClient::native_new(client)",
"P:LibreMetaverse.InventoryAISClient.IsAvailable": "self.native_is_available()",
"M:LibreMetaverse.InventoryAISClient.CreateInventoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Boolean,System.Threading.CancellationToken)":
"self.native_create_inventory(parent_uuid, new_inventory, create_link, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.SlamFolderAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
"self.native_slam_folder(folder_uuid, new_inventory, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.RemoveCategoryAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_remove_category(category_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.RemoveItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_remove_item(item_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.CopyLibraryCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)":
"self.native_copy_library_category(source_uuid, dest_uuid, copy_subfolders, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.PurgeDescendentsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_purge_descendents(category_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.UpdateCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
"self.native_update_category(category_uuid, updates, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.UpdateItemAsync(LibreMetaverse.UUID,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
"self.native_update_item(item_uuid, updates, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.CopyCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)":
"self.native_copy_category(source_uuid, dest_uuid, simulate, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.MoveCategoryAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_move_category(source_uuid, dest_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.PutCategoryChildrenAsync(System.String,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
"self.native_put_category_children(category, children_payload, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.CopyCategoryChildrenAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_copy_category_children(category, dest_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.MoveCategoryChildrenAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_move_category_children(category, dest_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.DeleteCategoryChildrenAsync(System.String,System.Threading.CancellationToken)":
"self.native_delete_category_children(category, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.PutCategoryLinksAsync(System.String,LibreMetaverse.StructuredData.OSD,System.Threading.CancellationToken)":
"self.native_put_category_links(category, links_payload, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.CopyCategoryLinksAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_copy_category_links(category, dest_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.MoveCategoryLinksAsync(System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_move_category_links(category, dest_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.DeleteCategoryLinksAsync(System.String,System.Threading.CancellationToken)":
"self.native_delete_category_links(category, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_copy_item(item_uuid, dest_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.MoveItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_move_item(item_uuid, dest_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.GetCategoryAsync(System.String,System.Threading.CancellationToken)":
"self.native_get_category(category, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.GetCategoryChildrenAsync(System.String,System.Int32,System.Boolean,System.Threading.CancellationToken)":
"self.native_get_category_children(category, depth, recursive, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.FetchCategoryCategoriesAsync(LibreMetaverse.UUID,System.Boolean,System.Boolean,System.Int32,System.Threading.CancellationToken)":
"self.native_fetch_category_categories(cat_id, use_inventory_cap, recursive, depth, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.FetchCategorySubsetAsync(LibreMetaverse.UUID,System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Boolean,System.Boolean,System.Int32,System.Threading.CancellationToken)":
"self.native_fetch_category_subset(cat_id, specific_children, use_inventory_cap, recursive, depth, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.GetCategoryLinksAsync(System.String,System.Threading.CancellationToken)":
"self.native_get_category_links(category, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.FetchItemAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)":
"self.native_fetch_item(item_uuid, cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.FetchCOFAsync(System.Threading.CancellationToken)":
"self.native_fetch_cof(cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.FetchOrphansAsync(System.Threading.CancellationToken)":
"self.native_fetch_orphans(cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.EmptyTrashAsync(System.Threading.CancellationToken)":
"self.native_empty_trash(cancellation_token).await",
"M:LibreMetaverse.InventoryAISClient.ParseItemsFromEmbedded(LibreMetaverse.StructuredData.OSDMap)":
"self.native_parse_items_from_embedded(response)",
"M:LibreMetaverse.InventoryAISClient.ParseFoldersFromEmbedded(LibreMetaverse.StructuredData.OSDMap)":
"self.native_parse_folders_from_embedded(response)",
"M:LibreMetaverse.InventoryAISClient.ParseLinksFromEmbedded(LibreMetaverse.StructuredData.OSDMap)":
"self.native_parse_links_from_embedded(response)",
"M:LibreMetaverse.InventoryAISClient.ParseEmbedded(LibreMetaverse.StructuredData.OSDMap,System.Collections.Generic.List{LibreMetaverse.InventoryFolder}@,System.Collections.Generic.List{LibreMetaverse.InventoryItem}@,System.Collections.Generic.List{LibreMetaverse.InventoryItem}@)":
"self.native_parse_embedded(response, folders, items, links)",
"M:LibreMetaverse.InventoryAISClient.ParseAISResponseMeta(LibreMetaverse.StructuredData.OSDMap)":
"crate::inventory_ais::InventoryAISClient::native_parse_ais_response_meta(response)",
"M:LibreMetaverse.InventoryManager.CreateInventoryItem(LibreMetaverse.InventoryType,LibreMetaverse.UUID)":
"crate::inventory_manager::InventoryManager::native_create_inventory_item(type_, id)",
"M:LibreMetaverse.InventoryManager.CopyItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Threading.CancellationToken)":

View File

@@ -994,6 +994,7 @@ def validate_generated_shims() -> None:
"crate::gesture::",
"crate::json_codec::",
"crate::inventory::",
"crate::inventory_ais::",
"crate::inventory_manager::",
"crate::message_codec::",
"crate::message_decoder::",