149 lines
6.5 KiB
Python
149 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Audit issue 80's native RLV callback and service orchestration boundary."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import generate_api_shims
|
|
import generate_surface
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SOURCE = ROOT / "crates" / "libremetaverse-rlv" / "src" / "service.rs"
|
|
STATE = ROOT / "crates" / "libremetaverse-rlv" / "src" / "state.rs"
|
|
GENERATED = ROOT / "crates" / "libremetaverse-rlv" / "src" / "generated.rs"
|
|
SUPPORT = ROOT / "tests" / "compat" / "src" / "rlv_support.rs"
|
|
QUERY_TESTS = ROOT / "tests" / "compat" / "tests" / "rlv_query_basics_semantics.rs"
|
|
ACTION_TESTS = ROOT / "tests" / "compat" / "tests" / "rlv_attach_commands_semantics.rs"
|
|
NOTIFY_TESTS = ROOT / "tests" / "compat" / "tests" / "rlv_notify_semantics.rs"
|
|
DOC = ROOT / "crates" / "libremetaverse-rlv" / "README.md"
|
|
WORKFLOW = ROOT / ".gitea" / "workflows" / "ci.yml"
|
|
CATALOG = ROOT / "api" / "public-api.json"
|
|
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")
|
|
|
|
TYPES = {
|
|
"T:LibreMetaverse.RLV.AttachmentRequest": "crate::service::AttachmentRequest",
|
|
"T:LibreMetaverse.RLV.RlvActionCallbacksDefault":
|
|
"crate::service::RlvActionCallbacksDefault",
|
|
"T:LibreMetaverse.RLV.RlvCallbacksDefault": "crate::service::RlvCallbacksDefault",
|
|
"T:LibreMetaverse.RLV.RlvCommandProcessor": "crate::service::RlvCommandProcessor",
|
|
"T:LibreMetaverse.RLV.RlvService": "crate::service::RlvService",
|
|
}
|
|
|
|
|
|
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 catalog_members() -> set[str]:
|
|
catalog = json.loads(CATALOG.read_text())
|
|
assembly = next(
|
|
value for value in catalog["assemblies"]
|
|
if value["identity"]["name"] == "LibreMetaverse.RLV"
|
|
)
|
|
return {
|
|
member["doc_id"]
|
|
for api_type in assembly["types"]
|
|
if api_type["doc_id"] in TYPES
|
|
for member in api_type["members"]
|
|
}
|
|
|
|
|
|
def generated_type_block(text: str, doc_id: str) -> str:
|
|
marker = f"/// C# type: `{doc_id}`."
|
|
start = text.find(marker)
|
|
if start < 0:
|
|
raise SystemExit(f"generated declaration is missing for {doc_id}")
|
|
next_type = text.find("/// C# type:", start + len(marker))
|
|
return text[start:] if next_type < 0 else text[start:next_type]
|
|
|
|
|
|
def main() -> None:
|
|
for api_type, declaration in TYPES.items():
|
|
if generate_api_shims.NATIVE_TYPES.get(api_type) != declaration:
|
|
raise SystemExit(f"issue 80 native type mapping is missing for {api_type}")
|
|
members = catalog_members()
|
|
if len(members) != 53:
|
|
raise SystemExit(f"issue 80 expected 53 mapped members, found {len(members)}")
|
|
|
|
source = SOURCE.read_text()
|
|
if STUB_RE.search(source):
|
|
raise SystemExit("issue 80 owned Rust stubs remain in service.rs")
|
|
if re.search(r"\b(?:Mutex|RwLock|read_lock|write_lock)\b", source):
|
|
raise SystemExit("service.rs must not acquire internal locks around host callbacks")
|
|
generated = GENERATED.read_text()
|
|
for api_type in TYPES:
|
|
if STUB_RE.search(generated_type_block(generated, api_type)):
|
|
raise SystemExit(f"issue 80 owned generated stubs remain for {api_type}")
|
|
if "pub trait IRlvActionCallbacks: Send + Sync" not in generated:
|
|
raise SystemExit("action callbacks are not thread-safe")
|
|
if "pub trait IRlvQueryCallbacks: Send + Sync" not in generated:
|
|
raise SystemExit("query callbacks are not thread-safe")
|
|
|
|
catalog = json.loads((ROOT / "tests" / "upstream-tests.json").read_text())
|
|
rlv_cases = [
|
|
case for case in catalog["tests"]
|
|
if case.get("rust_file", "").startswith("tests/compat/tests/rlv_")
|
|
]
|
|
if len(rlv_cases) != 616 or len({case["rust_file"] for case in rlv_cases}) != 24:
|
|
raise SystemExit("issue 80 RLV parity catalog is not the complete 616-case slice")
|
|
markers = generate_surface.scan_parity_markers(ROOT)
|
|
evidence_keys = ("status", "rust_file", "rust_line", "rust_test", "rust_body_sha256")
|
|
for case in rlv_cases:
|
|
marker = markers.get(case["id"], [])
|
|
if len(marker) != 1 or any(marker[0][key] != case[key] for key in evidence_keys):
|
|
raise SystemExit(f"issue 80 parity metadata is stale for {case['id']}")
|
|
|
|
require_markers(SOURCE, (
|
|
"MAX_RLV_MESSAGE_BYTES", "MAX_RLV_COMMANDS", "pub struct AttachmentRequest",
|
|
"pub struct RlvActionCallbacksDefault", "pub struct RlvCallbacksDefault",
|
|
"pub struct RlvCommandProcessor", "pub struct RlvService",
|
|
"process_instant_message", "process_message", "process_restriction",
|
|
"send_restriction_notification", "send_notification", "refresh_inventory",
|
|
"report_inventory_offer_accepted", "report_item_attached",
|
|
"report_send_public_message", "report_sit", "throw_if_cancellation_requested",
|
|
"default_callbacks_observe_cancellation_without_side_effects",
|
|
"attachment_request_hash_is_stable_and_value_based",
|
|
))
|
|
require_markers(STATE, (
|
|
"set_action_callbacks", "notify_removed_restriction",
|
|
"let removed = {", "let callbacks = read_lock", ".send_reply(",
|
|
))
|
|
require_markers(SUPPORT, (
|
|
"struct RecordingActions", "struct RecordingQueries", "struct RlvHarness",
|
|
"inventory_map.clone()", "RlvService::new",
|
|
))
|
|
require_markers(QUERY_TESTS, (
|
|
"manual_blacklist_all", "manual_version", "get_environment_known_setting",
|
|
"get_status_all_senders", "get_current_sit_id",
|
|
))
|
|
require_markers(ACTION_TESTS, (
|
|
"attach_this_by_id", "attach_all_this_recursive",
|
|
"attach_over_or_replace_uses_position_from_folder_name",
|
|
))
|
|
require_markers(NOTIFY_TESTS, (
|
|
"notify_every_restriction", "notify_multiple_channels_filtered",
|
|
"notify_inventory_offer", "notify_sit_and_stand_legal",
|
|
))
|
|
require_markers(DOC, (
|
|
"Callback and service contract", "Send + Sync",
|
|
"after releasing every manager lock", "completed inventory and agent",
|
|
"616", "ubuntu-latest",
|
|
))
|
|
require_markers(WORKFLOW, ("required-gate",))
|
|
print(
|
|
"issue 80 audit: native callbacks, typed actions and queries, bounded service "
|
|
"dispatch, inventory/agent adapters, cancellation, post-lock notifications, "
|
|
"recording-fake workflows, compatibility evidence, and docs are present"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|