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
20
skills/shared/guanghu-lighthouse-navigator/BRAIN.hdlp
Normal file
20
skills/shared/guanghu-lighthouse-navigator/BRAIN.hdlp
Normal 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。
|
||||
31
skills/shared/guanghu-lighthouse-navigator/SKILL.md
Normal file
31
skills/shared/guanghu-lighthouse-navigator/SKILL.md
Normal 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,不得自行复活。
|
||||
|
|
@ -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