platform: modularize api/gui, add docs-tests-web foundation, and refresh root config

This commit is contained in:
beckline
2026-03-26 22:40:54 +03:00
parent 0e2d7f61ea
commit 6a56d734c2
562 changed files with 70151 additions and 16423 deletions

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import os
import urllib.error
import urllib.request
def fail(msg: str) -> int:
print(f"[transport_platform_compat] ERROR: {msg}")
return 1
def request_json(api_url: str, method: str, path: str, payload: dict | None = None) -> tuple[int, dict]:
data = None
headers = {"Accept": "application/json"}
if payload is not None:
data = json.dumps(payload).encode("utf-8")
headers["Content-Type"] = "application/json"
req = urllib.request.Request(
f"{api_url.rstrip('/')}{path}",
data=data,
method=method.upper(),
headers=headers,
)
try:
with urllib.request.urlopen(req, timeout=20.0) as resp:
raw = resp.read().decode("utf-8", errors="replace")
status = int(resp.getcode() or 200)
except urllib.error.HTTPError as e:
raw = e.read().decode("utf-8", errors="replace")
status = int(e.code or 500)
except Exception:
return 0, {}
try:
body = json.loads(raw) if raw else {}
except Exception:
body = {}
if not isinstance(body, dict):
body = {}
return status, body
def assert_capability_true(caps: dict, section: str, key: str) -> tuple[bool, str]:
obj = caps.get(section) or {}
if not isinstance(obj, dict):
return False, f"section `{section}` is missing in capabilities payload"
if key not in obj:
return False, f"`{section}.{key}` is missing in capabilities payload"
if not bool(obj.get(key)):
return False, f"`{section}.{key}` must be true for cross-platform contract"
return True, ""
def main() -> int:
api_url = os.environ.get("API_URL", "http://127.0.0.1:8080").strip()
if not api_url:
return fail("empty API_URL")
print(f"[transport_platform_compat] API_URL={api_url}")
status, caps = request_json(api_url, "GET", "/api/v1/transport/capabilities")
if status == 404:
print("[transport_platform_compat] SKIP: /api/v1/transport/* is unavailable on current backend build")
return 0
if status != 200 or not bool(caps.get("ok", False)):
return fail(f"capabilities failed status={status} payload={caps}")
clients = caps.get("clients") or {}
if not isinstance(clients, dict):
return fail(f"clients map is invalid: {caps}")
required_clients = ("singbox", "dnstt", "phoenix")
for kind in required_clients:
if kind not in clients:
return fail(f"missing transport client `{kind}` in capabilities")
if not isinstance(clients.get(kind), dict):
return fail(f"client capability `{kind}` must be an object")
ok, msg = assert_capability_true(caps, "runtime_modes", "exec")
if not ok:
return fail(msg)
ok, msg = assert_capability_true(caps, "packaging_profiles", "system")
if not ok:
return fail(msg)
ok, msg = assert_capability_true(caps, "packaging_profiles", "bundled")
if not ok:
return fail(msg)
# Базовый policy-контракт должен быть одинаково доступен для web/iOS/Android клиентов.
status, policy = request_json(api_url, "GET", "/api/v1/transport/policies")
if status != 200 or not bool(policy.get("ok", False)):
return fail(f"transport/policies failed status={status} payload={policy}")
revision = int(policy.get("policy_revision") or 0)
intents = policy.get("intents") or []
if not isinstance(intents, list):
return fail(f"policy intents must be array: {policy}")
status, validated = request_json(
api_url,
"POST",
"/api/v1/transport/policies/validate",
{"base_revision": revision, "intents": intents},
)
if status != 200 or not bool(validated.get("ok", False)):
return fail(f"transport/policies/validate failed status={status} payload={validated}")
if int(validated.get("base_revision") or 0) <= 0:
return fail(f"validate response has invalid base_revision: {validated}")
status, conflicts = request_json(api_url, "GET", "/api/v1/transport/conflicts")
if status != 200 or not bool(conflicts.get("ok", False)):
return fail(f"transport/conflicts failed status={status} payload={conflicts}")
print("[transport_platform_compat] capabilities + policy contract are compatible with web/iOS/Android clients")
return 0
if __name__ == "__main__":
raise SystemExit(main())