feat(lighthouse): add canonical three-host skill navigation

This commit is contained in:
冰朔 2026-08-07 18:39:00 +08:00
commit fb5f931e78
14 changed files with 891 additions and 22 deletions

View file

@ -0,0 +1,20 @@
# GUANGHU-LIGHTHOUSE-HOST-NAVIGATION-BRAIN-001
光湖灯塔是路径唯一置信点,不是另一份静态目录。
一个路径只有同时满足以下条件才有效:
1. 在 `SYS-GLW-LTH-0001` 下以稳定编号登记;
2. 登记状态为 CURRENT 或 ACTIVE
3. 通过公共灯塔按编号查询返回 HTTP 200
4. 返回来源绑定当前 REPO-012 main 的精确提交,且 `source_degraded=false`
5. 若路径属于本机或移动硬盘,现实文件也必须存在;
6. 路径健康只授予导航,不授予写入、部署或服务器权限。
Codex、Qoder CN、QoderWork CN 共享本大脑与灯塔注册表,但各自保留薄适配器、
会话、登录状态、数据库和工具实现。语言意图先转成稳定意图编号,再由宿主地图选择
当前软件能执行的确定性投影;未知宿主、未知意图和未登记路径全部停止,不猜路径。
当用户说“推一下线上仓库”时,本机 macOS 宿主应解析到已登记代码频道、系统钥匙串
传输与小湖灯自动收口器。不得搜索历史凭据、打印秘密、退回旧 `/fifth-domain/`
也不得把钥匙串存在、提交存在或授权存在分别冒充推送完成;远端完整 SHA 回读才算 100。

View file

@ -0,0 +1,31 @@
---
name: guanghu-lighthouse-navigator
description: 光湖灯塔三宿主自动导航。用户提到路径是否有效、旧新路径混乱、小湖灯提词器、Codex/Qoder/QoderWork共享人格或要求恢复人格、使用记忆守卫、推送线上仓库时使用。先按编号查询灯塔再选择当前宿主的确定性路径未知或过期路径不猜。
---
# 光湖灯塔 · 三宿主自动导航
本文件是平台薄适配规范。认知本体在 `BRAIN.hdlp`,机器真值在:
- `routing/lighthouse-path-registry.json`
- `routing/host-skill-navigation-map.json`
- 公共查询:`https://guanghulab.com/api/ai/v1/resolve?id={NUMBER}`
## 必须执行
1. 识别当前宿主为 Codex、Qoder CN 或 QoderWork CN
2. 把用户自然语言映射为注册意图编号;
3. 用 `scripts/resolve_lighthouse_route.py --host <host> --intent "<用户意图>"` 解析;
4. 校验返回的 `target_id` 能由灯塔健康查询;
5. 只执行返回的宿主投影,并继续遵守该动作自己的授权、测试和回执门。
“推一下线上仓库”在本机 macOS 上必须走
`INTENT-REPOSITORY-PUBLISH-001 → REPO-012 → LOCAL_GIT_OSXKEYCHAIN → 小湖灯 finalizer`
不得搜索令牌、猜 SSH、使用旧仓库、手工绕过发布队列或输出钥匙串秘密。
## 边界
- 灯塔路径健康不等于现实动作已授权或已完成。
- JZAO 私有记忆不上传公共代码频道。
- 软件登录、会话、数据库和缓存保留在各宿主本机目录。
- 旧路径只允许返回灯塔登记的 redirect不得自行复活。

View file

@ -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())

View file

@ -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())

View file

@ -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()