mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 08:52:45 +00:00
179 lines
5.5 KiB
Python
179 lines
5.5 KiB
Python
"""Verify the frozen R220 API route/facade contract."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import inspect
|
|
import json
|
|
import sys
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from scripts.contract_digest import stable_text_digest, write_text_lf # noqa: E402
|
|
|
|
CONTRACT_PATH = ROOT / "tests" / "api_route_contract_r220.json"
|
|
|
|
|
|
class _NamedHandler:
|
|
def __init__(self, name: str) -> None:
|
|
self.__name__ = name
|
|
|
|
|
|
class _AttributeHandlers:
|
|
def __getattr__(self, name: str) -> _NamedHandler:
|
|
return _NamedHandler(name)
|
|
|
|
|
|
def _handler_map() -> defaultdict[str, _NamedHandler]:
|
|
return defaultdict(lambda: _NamedHandler("unknown"))
|
|
|
|
|
|
def _normalize_specs(specs: Any) -> list[dict[str, str]]:
|
|
return [
|
|
{
|
|
"method": spec.method,
|
|
"path": spec.path,
|
|
"handler": getattr(spec.handler, "__name__", type(spec.handler).__name__),
|
|
}
|
|
for spec in specs
|
|
]
|
|
|
|
|
|
def _metadata(handler: Any) -> dict[str, Any]:
|
|
from services.endpoint_manifest import get_metadata
|
|
|
|
meta = get_metadata(handler)
|
|
if meta is None:
|
|
raise RuntimeError(f"missing endpoint metadata for {handler.__name__}")
|
|
return {
|
|
"auth": meta.auth_tier.value,
|
|
"risk": meta.risk_tier.value,
|
|
"plane": meta.route_plane.value if meta.route_plane else None,
|
|
"summary": meta.summary,
|
|
"description": meta.description,
|
|
"audit": meta.audit_action,
|
|
"scopes": list(meta.required_scopes),
|
|
}
|
|
|
|
|
|
def build_contract() -> dict[str, Any]:
|
|
from api import routes
|
|
from api.route_registrars import (
|
|
build_assist_route_specs,
|
|
build_connector_installation_route_specs,
|
|
build_core_route_specs,
|
|
build_pack_route_specs,
|
|
)
|
|
|
|
core_handlers = _handler_map()
|
|
core_keys = inspect.getsource(build_core_route_specs)
|
|
for key in {part.split('"', 1)[0] for part in core_keys.split('handlers["')[1:]}:
|
|
core_handlers[key] = _NamedHandler(key)
|
|
|
|
connector_handlers = _handler_map()
|
|
connector_keys = inspect.getsource(build_connector_installation_route_specs)
|
|
for key in {
|
|
part.split('"', 1)[0] for part in connector_keys.split('handlers["')[1:]
|
|
}:
|
|
connector_handlers[key] = _NamedHandler(key)
|
|
|
|
packs = _AttributeHandlers()
|
|
assist = _AttributeHandlers()
|
|
families: dict[str, list[dict[str, str]]] = {}
|
|
for prefix in ("/openclaw", "/moltbot"):
|
|
families[f"core:{prefix}"] = _normalize_specs(
|
|
build_core_route_specs(prefix, core_handlers)
|
|
)
|
|
families[f"assist:{prefix}"] = _normalize_specs(
|
|
build_assist_route_specs(prefix, assist)
|
|
)
|
|
families[f"connector_installations:{prefix}"] = _normalize_specs(
|
|
build_connector_installation_route_specs(prefix, connector_handlers)
|
|
)
|
|
families[f"packs:{prefix}"] = _normalize_specs(
|
|
build_pack_route_specs(prefix, packs)
|
|
)
|
|
|
|
facade_names = (
|
|
"health_handler",
|
|
"_ensure_observability_deps_ready",
|
|
"logs_tail_handler",
|
|
"jobs_handler",
|
|
"_emit_jobs_list_audit",
|
|
"trace_handler",
|
|
"register_dual_route",
|
|
"_resolve_mae_profile",
|
|
"_run_mae_startup_gate",
|
|
"register_routes",
|
|
)
|
|
facade = {
|
|
name: str(inspect.signature(getattr(routes, name))) for name in facade_names
|
|
}
|
|
metadata = {
|
|
name: _metadata(getattr(routes, name))
|
|
for name in (
|
|
"health_handler",
|
|
"logs_tail_handler",
|
|
"jobs_handler",
|
|
"trace_handler",
|
|
)
|
|
}
|
|
return {
|
|
"schema_version": 1,
|
|
"registration_order": [
|
|
"startup_profile_gate",
|
|
"core:/openclaw",
|
|
"core:/moltbot",
|
|
"assist:/openclaw",
|
|
"assist:/moltbot",
|
|
"connector_installations:/openclaw",
|
|
"connector_installations:/moltbot",
|
|
"bridge",
|
|
"mae_posture_gate",
|
|
"packs:/openclaw",
|
|
"packs:/moltbot",
|
|
],
|
|
"feature_conditions": {
|
|
"assist": "assist is truthy",
|
|
"connector_installations": "list handler is truthy",
|
|
"bridge": "server.app exists and BRIDGE module is enabled",
|
|
"packs": "optional pack imports succeed",
|
|
},
|
|
"direct_alias_rule": "each registered path also attempts path and /api+path",
|
|
"legacy_rule": "moltbot handlers retain telemetry and deprecation headers",
|
|
"families": families,
|
|
"facade_signatures": facade,
|
|
"facade_metadata": metadata,
|
|
"openapi_sha256": stable_text_digest(ROOT / "docs" / "openapi.yaml"),
|
|
}
|
|
|
|
|
|
def _canonical_json(value: Any) -> str:
|
|
return json.dumps(value, indent=2, sort_keys=True, ensure_ascii=False) + "\n"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--write-baseline", action="store_true")
|
|
args = parser.parse_args()
|
|
actual = build_contract()
|
|
if args.write_baseline:
|
|
write_text_lf(CONTRACT_PATH, _canonical_json(actual))
|
|
print(f"API-ROUTE-CONTRACT-WRITTEN: {CONTRACT_PATH}")
|
|
return 0
|
|
expected = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))
|
|
if actual != expected:
|
|
print("API-ROUTE-CONTRACT-FAIL: frozen route/facade contract drifted")
|
|
return 1
|
|
print("API-ROUTE-CONTRACT-PASS")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|