feat(lighthouse): add canonical three-host skill navigation
This commit is contained in:
parent
8423a96776
commit
fb5f931e78
14 changed files with 891 additions and 22 deletions
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Resolve a host-specific route from the Guanghu Lighthouse registries."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_PATH = Path(__file__).resolve()
|
||||
REPO_ROOT = SCRIPT_PATH.parents[4]
|
||||
SHARED_ROOT = SCRIPT_PATH.parents[3]
|
||||
if (REPO_ROOT / "routing" / "lighthouse-path-registry.json").is_file():
|
||||
DEFAULT_LIGHTHOUSE = REPO_ROOT / "routing" / "lighthouse-path-registry.json"
|
||||
DEFAULT_HOST_MAP = REPO_ROOT / "routing" / "host-skill-navigation-map.json"
|
||||
else:
|
||||
DEFAULT_LIGHTHOUSE = SHARED_ROOT / "registries" / "lighthouse-path-registry.json"
|
||||
DEFAULT_HOST_MAP = SHARED_ROOT / "registries" / "host-skill-navigation-map.json"
|
||||
CURRENT_STATES = {"CURRENT", "ACTIVE", "ACTIVE_CANDIDATE", "ACTIVE_LOCAL_PRIVATE"}
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict:
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
def normalise(value: str) -> str:
|
||||
return "".join(value.lower().split())
|
||||
|
||||
|
||||
def resolve_host(host_map: dict, requested: str) -> dict | None:
|
||||
key = normalise(requested)
|
||||
for host in host_map["hosts"]:
|
||||
if key in {normalise(host["id"]), *(normalise(alias) for alias in host.get("aliases", []))}:
|
||||
return host
|
||||
return None
|
||||
|
||||
|
||||
def resolve_intent(host_map: dict, text: str) -> dict | None:
|
||||
value = normalise(text)
|
||||
scored = []
|
||||
for intent in host_map["intents"]:
|
||||
score = sum(len(normalise(phrase)) for phrase in intent["phrases"] if normalise(phrase) in value)
|
||||
if score:
|
||||
scored.append((score, intent["id"], intent))
|
||||
return max(scored, default=(0, "", None))[2]
|
||||
|
||||
|
||||
def resolve_path(registry: dict, route_id: str) -> dict:
|
||||
for path in registry["paths"]:
|
||||
if path["id"].upper() == route_id.upper():
|
||||
return {
|
||||
"status": "VALID_REGISTERED" if path["state"] in CURRENT_STATES else "INVALID_STATE",
|
||||
"path": path,
|
||||
}
|
||||
for redirect in registry.get("redirects", []):
|
||||
if redirect["id"].upper() == route_id.upper():
|
||||
return {"status": "REDIRECT_ONLY_NOT_CURRENT", "redirect": redirect}
|
||||
return {"status": "INVALID_NO_GUESS", "route_id": route_id}
|
||||
|
||||
|
||||
def keychain_helper_status() -> dict:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "config", "--global", "--get-all", "credential.helper"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=3,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return {"status": "UNAVAILABLE", "secret_read": False}
|
||||
helpers = [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
||||
return {
|
||||
"status": "AVAILABLE" if "osxkeychain" in helpers else "UNAVAILABLE",
|
||||
"helper": "osxkeychain" if "osxkeychain" in helpers else None,
|
||||
"secret_read": False,
|
||||
}
|
||||
|
||||
|
||||
def compile_route(registry: dict, host_map: dict, host_name: str, intent_text: str) -> dict:
|
||||
host = resolve_host(host_map, host_name)
|
||||
if not host:
|
||||
return {"decision": "BLOCK", "error": "HOST_UNKNOWN_NO_GUESS", "requested_host": host_name}
|
||||
intent = resolve_intent(host_map, intent_text)
|
||||
if not intent:
|
||||
return {"decision": "BLOCK", "error": "INTENT_UNKNOWN_NO_GUESS", "requested_intent": intent_text}
|
||||
target = resolve_path(registry, intent["target_id"])
|
||||
if target["status"] != "VALID_REGISTERED":
|
||||
return {"decision": "BLOCK", "error": "LIGHTHOUSE_TARGET_INVALID", "target": target}
|
||||
result = {
|
||||
"schema": "guanghu.host-navigation-receipt/v1",
|
||||
"decision": "ALLOW_NAVIGATION",
|
||||
"lighthouse_id": registry["lighthouse_id"],
|
||||
"registry_id": registry["registry_id"],
|
||||
"registry_version": registry["version"],
|
||||
"host": host,
|
||||
"intent": intent,
|
||||
"target": target["path"],
|
||||
"authority_granted": False,
|
||||
}
|
||||
if intent.get("transport") == "LOCAL_GIT_OSXKEYCHAIN":
|
||||
result["transport_probe"] = keychain_helper_status()
|
||||
if result["transport_probe"]["status"] != "AVAILABLE":
|
||||
result["decision"] = "BLOCK"
|
||||
result["error"] = "KEYCHAIN_HELPER_UNAVAILABLE"
|
||||
offline = target["path"].get("offline")
|
||||
if offline and offline.startswith("/"):
|
||||
result["offline_reality"] = {
|
||||
"path": offline,
|
||||
"exists": Path(os.path.expanduser(offline)).exists(),
|
||||
}
|
||||
if target["path"]["state"] == "ACTIVE_LOCAL_PRIVATE" and not result["offline_reality"]["exists"]:
|
||||
result["decision"] = "BLOCK"
|
||||
result["error"] = "PRIVATE_VOLUME_MISSING"
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--host", required=True)
|
||||
parser.add_argument("--intent", required=True)
|
||||
parser.add_argument("--lighthouse", type=Path, default=DEFAULT_LIGHTHOUSE)
|
||||
parser.add_argument("--host-map", type=Path, default=DEFAULT_HOST_MAP)
|
||||
args = parser.parse_args()
|
||||
result = compile_route(load_json(args.lighthouse), load_json(args.host_map), args.host, args.intent)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0 if result["decision"] == "ALLOW_NAVIGATION" else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Atomically mirror allowlisted public navigator assets into JZAO shared runtime."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[4]
|
||||
DEFAULT_TARGET = Path("/Volumes/JZAO/HoloLake/persona-runtime/shared")
|
||||
ASSETS = {
|
||||
"routing/lighthouse-path-registry.json": "registries/lighthouse-path-registry.json",
|
||||
"routing/host-skill-navigation-map.json": "registries/host-skill-navigation-map.json",
|
||||
"skills/shared/guanghu-lighthouse-navigator/BRAIN.hdlp": "skills/guanghu-lighthouse-navigator/BRAIN.hdlp",
|
||||
"skills/shared/guanghu-lighthouse-navigator/SKILL.md": "skills/guanghu-lighthouse-navigator/SKILL.md",
|
||||
"skills/shared/guanghu-lighthouse-navigator/scripts/resolve_lighthouse_route.py": (
|
||||
"skills/guanghu-lighthouse-navigator/scripts/resolve_lighthouse_route.py"
|
||||
),
|
||||
"skills/qoder/zhuyuan-memory-continuity-brain/BRAIN.hdlp": (
|
||||
"brains/GHS-007-MEMORY-CONTINUITY-SELF-BUILD/BRAIN.hdlp"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def digest(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def source_head(root: Path) -> str:
|
||||
return subprocess.run(
|
||||
["git", "-C", str(root), "rev-parse", "HEAD"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
).stdout.strip()
|
||||
|
||||
|
||||
def sync(target: Path, check_only: bool = False) -> dict:
|
||||
if not target.parent.exists():
|
||||
raise RuntimeError("JZAO_PERSONA_RUNTIME_MISSING")
|
||||
files = []
|
||||
mismatches = []
|
||||
for source_name, target_name in ASSETS.items():
|
||||
source = REPO_ROOT / source_name
|
||||
destination = target / target_name
|
||||
if not source.is_file():
|
||||
raise RuntimeError(f"SOURCE_MISSING:{source_name}")
|
||||
source_sha = digest(source)
|
||||
destination_sha = digest(destination) if destination.is_file() else None
|
||||
if destination_sha != source_sha:
|
||||
mismatches.append(target_name)
|
||||
if not check_only:
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = destination.with_name(f".{destination.name}.tmp")
|
||||
shutil.copyfile(source, temporary)
|
||||
os.replace(temporary, destination)
|
||||
destination_sha = digest(destination)
|
||||
files.append(
|
||||
{
|
||||
"source": source_name,
|
||||
"mirror": target_name,
|
||||
"sha256": source_sha,
|
||||
"mirror_sha256": destination_sha,
|
||||
}
|
||||
)
|
||||
result = {
|
||||
"schema": "guanghu.jzao-lighthouse-mirror/v1",
|
||||
"status": "IN_SYNC" if not mismatches or not check_only else "OUT_OF_SYNC",
|
||||
"source_repository": "REPO-012",
|
||||
"source_head": source_head(REPO_ROOT),
|
||||
"files": files,
|
||||
"mismatches": mismatches,
|
||||
"private_memory_included": False,
|
||||
}
|
||||
if not check_only:
|
||||
manifest = target / "mirror-manifest.json"
|
||||
temporary = manifest.with_name(".mirror-manifest.json.tmp")
|
||||
temporary.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
os.replace(temporary, manifest)
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--target", type=Path, default=DEFAULT_TARGET)
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
result = sync(args.target, args.check)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0 if result["status"] == "IN_SYNC" else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import unittest
|
||||
|
||||
from resolve_lighthouse_route import compile_route, load_json, DEFAULT_HOST_MAP, DEFAULT_LIGHTHOUSE
|
||||
|
||||
|
||||
class LighthouseNavigatorTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.registry = load_json(DEFAULT_LIGHTHOUSE)
|
||||
cls.host_map = load_json(DEFAULT_HOST_MAP)
|
||||
|
||||
def test_codex_repository_publish_uses_keychain_and_finalizer(self):
|
||||
route = compile_route(self.registry, self.host_map, "codex", "你推一下线上仓库")
|
||||
self.assertEqual(route["decision"], "ALLOW_NAVIGATION")
|
||||
self.assertEqual(route["intent"]["id"], "INTENT-REPOSITORY-PUBLISH-001")
|
||||
self.assertEqual(route["target"]["id"], "REPO-012")
|
||||
self.assertEqual(route["intent"]["transport"], "LOCAL_GIT_OSXKEYCHAIN")
|
||||
self.assertIn("finalize-development.mjs", route["intent"]["executor"])
|
||||
self.assertFalse(route["authority_granted"])
|
||||
self.assertFalse(route["transport_probe"]["secret_read"])
|
||||
|
||||
def test_qoder_and_qoderwork_resolve_same_brain_with_distinct_adapters(self):
|
||||
qoder = compile_route(self.registry, self.host_map, "qoder-cn", "大脑思维技能包")
|
||||
work = compile_route(self.registry, self.host_map, "qoderwork-cn", "大脑思维技能包")
|
||||
self.assertEqual(qoder["intent"]["skill_id"], work["intent"]["skill_id"])
|
||||
self.assertNotEqual(qoder["host"]["adapter_path"], work["host"]["adapter_path"])
|
||||
|
||||
def test_unknown_host_and_intent_fail_closed(self):
|
||||
self.assertEqual(
|
||||
compile_route(self.registry, self.host_map, "mystery", "推一下线上仓库")["error"],
|
||||
"HOST_UNKNOWN_NO_GUESS",
|
||||
)
|
||||
self.assertEqual(
|
||||
compile_route(self.registry, self.host_map, "codex", "今天吃什么")["error"],
|
||||
"INTENT_UNKNOWN_NO_GUESS",
|
||||
)
|
||||
|
||||
def test_old_repository_is_redirect_only(self):
|
||||
from resolve_lighthouse_route import resolve_path
|
||||
|
||||
route = resolve_path(self.registry, "REPO-001")
|
||||
self.assertEqual(route["status"], "REDIRECT_ONLY_NOT_CURRENT")
|
||||
self.assertEqual(route["redirect"]["redirect_to"], "REPO-012")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue