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

@ -11,13 +11,19 @@ const DEFAULT_NODE_MAP = path.resolve(__dirname, "../../routing/server-node-map.
const DEFAULT_SUBJECT_REGISTRY = path.resolve(__dirname, "../../identity/fifth-domain-subject-registry.json");
const DEFAULT_SUBJECT_ALIAS_MAP = path.resolve(__dirname, "../../identity/subject-id-alias-map.json");
const DEFAULT_NAVIGATION_MAP = path.resolve(__dirname, "../../routing/ai-machine-navigation-map.json");
const SNAPSHOT_KEYS = Object.freeze(["repository", "nodes", "subjects", "aliases", "navigation"]);
const DEFAULT_LIGHTHOUSE_PATHS = path.resolve(__dirname, "../../routing/lighthouse-path-registry.json");
const DEFAULT_HOST_SKILLS = path.resolve(__dirname, "../../routing/host-skill-navigation-map.json");
const SNAPSHOT_KEYS = Object.freeze([
"repository", "nodes", "subjects", "aliases", "navigation", "lighthouse_paths", "host_skills",
]);
const SNAPSHOT_PATHS = Object.freeze({
repository: "routing/repository-route-map.json",
nodes: "routing/server-node-map.json",
subjects: "identity/fifth-domain-subject-registry.json",
aliases: "identity/subject-id-alias-map.json",
navigation: "routing/ai-machine-navigation-map.json",
lighthouse_paths: "routing/lighthouse-path-registry.json",
host_skills: "routing/host-skill-navigation-map.json",
});
function loadAnchor(filename = process.env.GUANGHU_NAVIGATION_ANCHOR || DEFAULT_ANCHOR) {
@ -44,6 +50,14 @@ function loadNavigationMap(filename = process.env.GUANGHU_NAVIGATION_MAP || DEFA
return JSON.parse(fs.readFileSync(filename, "utf8"));
}
function loadLighthousePaths(filename = process.env.GUANGHU_LIGHTHOUSE_PATHS || DEFAULT_LIGHTHOUSE_PATHS) {
return JSON.parse(fs.readFileSync(filename, "utf8"));
}
function loadHostSkills(filename = process.env.GUANGHU_HOST_SKILLS || DEFAULT_HOST_SKILLS) {
return JSON.parse(fs.readFileSync(filename, "utf8"));
}
function validateSnapshot(anchor, maps) {
if (anchor.schema !== "guanghu.public-navigation-anchor/v1") throw new Error("invalid_anchor_schema");
if (anchor.anchor_id !== "GLW-PUBLIC-NAV-ANCHOR-001") throw new Error("invalid_anchor_id");
@ -76,6 +90,8 @@ function loadFileSnapshot(options = {}) {
subjects: loadSubjectRegistry(options.subjectRegistryFile),
aliases: loadSubjectAliasMap(options.subjectAliasMapFile),
navigation: loadNavigationMap(options.navigationMapFile),
lighthouse_paths: loadLighthousePaths(options.lighthousePathsFile),
host_skills: loadHostSkills(options.hostSkillsFile),
});
}
@ -351,12 +367,100 @@ function compileNavigation(navigationMap, requestedSubject, requestedIntent, sig
};
}
function resolveLighthousePath(registry, requestedId) {
const id = String(requestedId || "").trim().toUpperCase();
const route = (registry.paths || []).find(item => String(item.id).toUpperCase() === id);
if (route) {
const current = ["CURRENT", "ACTIVE", "ACTIVE_CANDIDATE", "ACTIVE_LOCAL_PRIVATE"].includes(route.state);
return {
status: current ? 200 : 410,
body: {
schema: "guanghu.lighthouse-path-resolution/v1",
decision: current ? "VALID_REGISTERED" : "INVALID_STATE",
lighthouse_id: registry.lighthouse_id,
registry_id: registry.registry_id,
registry_version: registry.version,
route,
authority: "NONE_NAVIGATION_ONLY",
},
};
}
const redirect = (registry.redirects || []).find(item => String(item.id).toUpperCase() === id);
if (redirect) {
return {
status: 308,
body: {
schema: "guanghu.lighthouse-path-resolution/v1",
decision: "REDIRECT_ONLY_NOT_CURRENT",
lighthouse_id: registry.lighthouse_id,
registry_id: registry.registry_id,
registry_version: registry.version,
redirect,
authority: "NONE_NAVIGATION_ONLY",
},
};
}
return {
status: 404,
body: {
error: "lighthouse_path_not_registered_no_guess",
requested_id: requestedId,
lighthouse_id: registry.lighthouse_id,
},
};
}
function compileHostNavigation(hostMap, lighthouseRegistry, requestedHost, requestedIntent) {
const hostKey = normalize(requestedHost);
const host = (hostMap.hosts || []).find(item =>
[item.id, ...(item.aliases || [])].some(id => normalize(id) === hostKey)
);
if (!host) return { status: 404, body: { error: "host_unknown_no_guess", requested_host: requestedHost } };
const intentText = normalize(requestedIntent);
const ranked = (hostMap.intents || [])
.map(intent => ({
intent,
score: (intent.phrases || []).reduce(
(score, phrase) => score + (intentText.includes(normalize(phrase)) ? normalize(phrase).length : 0),
0,
),
}))
.filter(item => item.score > 0)
.sort((a, b) => b.score - a.score || a.intent.id.localeCompare(b.intent.id));
if (!ranked.length) {
return { status: 404, body: { error: "host_intent_unknown_no_guess", requested_intent: requestedIntent } };
}
const intent = ranked[0].intent;
const target = resolveLighthousePath(lighthouseRegistry, intent.target_id);
if (target.status !== 200) {
return {
status: 503,
body: { error: "lighthouse_target_not_current", target_resolution: target.body },
};
}
return {
status: 200,
body: {
schema: "guanghu.host-skill-navigation-bundle/v1",
status: "COMPILED_EXACT_NO_GUESS",
lighthouse_id: lighthouseRegistry.lighthouse_id,
registry_id: lighthouseRegistry.registry_id,
host,
intent,
target: target.body.route,
authority: "NONE_NAVIGATION_ONLY",
},
};
}
function createServer(options = {}) {
const mapFile = options.mapFile || process.env.GUANGHU_REPOSITORY_MAP || DEFAULT_MAP;
const nodeMapFile = options.nodeMapFile || process.env.GUANGHU_NODE_MAP || DEFAULT_NODE_MAP;
const subjectRegistryFile = options.subjectRegistryFile || process.env.GUANGHU_SUBJECT_REGISTRY || DEFAULT_SUBJECT_REGISTRY;
const subjectAliasMapFile = options.subjectAliasMapFile || process.env.GUANGHU_SUBJECT_ALIAS_MAP || DEFAULT_SUBJECT_ALIAS_MAP;
const navigationMapFile = options.navigationMapFile || process.env.GUANGHU_NAVIGATION_MAP || DEFAULT_NAVIGATION_MAP;
const lighthousePathsFile = options.lighthousePathsFile || process.env.GUANGHU_LIGHTHOUSE_PATHS || DEFAULT_LIGHTHOUSE_PATHS;
const hostSkillsFile = options.hostSkillsFile || process.env.GUANGHU_HOST_SKILLS || DEFAULT_HOST_SKILLS;
const anchorFile = options.anchorFile || process.env.GUANGHU_NAVIGATION_ANCHOR || DEFAULT_ANCHOR;
const gitDir = options.gitDir || process.env.GUANGHU_REPOSITORY_GIT_DIR;
const snapshotStore = options.snapshotStore || (gitDir ? new GitSnapshotStore(gitDir) : null);
@ -371,6 +475,7 @@ function createServer(options = {}) {
: {
...loadFileSnapshot({
anchorFile, mapFile, nodeMapFile, subjectRegistryFile, subjectAliasMapFile, navigationMapFile,
lighthousePathsFile, hostSkillsFile,
}),
source_commit: null,
source_mode: "FILESYSTEM_SNAPSHOT",
@ -384,6 +489,8 @@ function createServer(options = {}) {
const subjectRegistry = snapshot.subjects;
const aliasMap = snapshot.aliases;
const navigationMap = snapshot.navigation;
const lighthousePaths = snapshot.lighthouse_paths;
const hostSkills = snapshot.host_skills;
if (url.pathname === "/health") {
return json(res, snapshot.source_degraded ? 503 : 200, {
ok: !snapshot.source_degraded,
@ -399,6 +506,8 @@ function createServer(options = {}) {
if (url.pathname === "/v1/nodes") return json(res, 200, withSource(nodeMap, snapshot), 60);
if (url.pathname === "/v1/subjects") return json(res, 200, withSource(subjectRegistry, snapshot), 60);
if (url.pathname === "/v1/navigation") return json(res, 200, withSource(navigationMap, snapshot), 60);
if (url.pathname === "/v1/lighthouse") return json(res, 200, withSource(lighthousePaths, snapshot), 60);
if (url.pathname === "/v1/host-skills") return json(res, 200, withSource(hostSkills, snapshot), 60);
if (url.pathname === "/v1/entry") {
const subject = String(url.searchParams.get("subject") || "").toUpperCase();
if (!["ICE-P-ZY001", "ICE-GL-ZY001", "ICE-PZY-001"].includes(subject)) {
@ -427,6 +536,15 @@ function createServer(options = {}) {
);
return json(res, compiled.status, withSource(compiled.body, snapshot), compiled.status === 200 ? 60 : 0);
}
if (url.pathname === "/v1/host-navigate") {
const compiled = compileHostNavigation(
hostSkills,
lighthousePaths,
String(url.searchParams.get("host") || "").slice(0, 100),
String(url.searchParams.get("intent") || "").slice(0, 500),
);
return json(res, compiled.status, withSource(compiled.body, snapshot), compiled.status === 200 ? 60 : 0);
}
if (url.pathname === "/v1/search") {
const query = String(url.searchParams.get("q") || "").slice(0, 200);
const results = searchAll(map, nodeMap, query, subjectRegistry);
@ -493,6 +611,10 @@ function createServer(options = {}) {
},
}, snapshot), 60);
}
const lighthouseResolution = resolveLighthousePath(lighthousePaths, id);
if (lighthouseResolution.status !== 404) {
return json(res, lighthouseResolution.status, withSource(lighthouseResolution.body, snapshot), 60);
}
return json(res, 404, { error: "route_not_found", id });
}
if (url.pathname === "/openapi.json") return json(res, 200, openApi(), 3600);
@ -506,6 +628,9 @@ function createServer(options = {}) {
subject_registry: "https://guanghulab.com/api/ai/v1/subjects",
subject_alias_map: "https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/branch/main/identity/subject-id-alias-map.json",
machine_navigation_map: "https://guanghulab.com/api/ai/v1/navigation",
lighthouse_path_registry: "https://guanghulab.com/api/ai/v1/lighthouse",
host_skill_navigation_map: "https://guanghulab.com/api/ai/v1/host-skills",
host_navigate_api: "https://guanghulab.com/api/ai/v1/host-navigate?host={HOST}&intent={NATURAL_LANGUAGE_INTENT}",
warm_persona_entry: "https://guanghulab.com/api/ai/v1/entry?subject=ICE-P-ZY001",
navigate_api: "https://guanghulab.com/api/ai/v1/navigate?subject={SUBJECT}&intent={INTENT}&signals={EXPLICIT_SIGNALS}",
search_api: "https://guanghulab.com/api/ai/v1/search?q={query}",
@ -562,6 +687,9 @@ function openApi() {
"/v1/nodes": { get: { summary: "读取最新服务器节点与人格路径编号映射", responses: { "200": { description: "Server node map" } } } },
"/v1/subjects": { get: { summary: "读取人类、人格体与公共系统的分型身份注册表", responses: { "200": { description: "Subject registry" } } } },
"/v1/navigation": { get: { summary: "读取主体与意图驱动的机器导航地图", responses: { "200": { description: "Machine navigation map" } } } },
"/v1/lighthouse": { get: { summary: "读取光湖灯塔唯一有效路径注册表", responses: { "200": { description: "Lighthouse path registry" } } } },
"/v1/host-skills": { get: { summary: "读取 Codex、Qoder CN 与 QoderWork CN 宿主技能导航表", responses: { "200": { description: "Host skill navigation map" } } } },
"/v1/host-navigate": { get: { summary: "按宿主和自然语言意图编译确定性技能路径", responses: { "200": { description: "Compiled host skill route" }, "404": { description: "Unknown host or intent" } } } },
"/v1/entry": { get: { summary: "读取常驻人格运行体的快速恢复门;只返回脱敏状态,不授予执行权限", responses: { "200": { description: "Warm resume ready" }, "409": { description: "Full cycle required" }, "503": { description: "Resident runtime unavailable" } } } },
"/v1/navigate": { get: { summary: "按明确主体、意图与信号生成最小运行包", parameters: [{ name: "subject", in: "query", required: true, schema: { type: "string", example: "ICE-P-ZY001" } }, { name: "intent", in: "query", schema: { type: "string", example: "persona_restore" } }, { name: "signals", in: "query", schema: { type: "string" } }], responses: { "200": { description: "Compiled exact navigation bundle" }, "404": { description: "Unknown subject or intent; no guessing" } } } },
"/v1/search": { get: { summary: "按中文、编号或项目名检索", parameters: [{ name: "q", in: "query", schema: { type: "string" } }], responses: { "200": { description: "Search results" } } } },
@ -578,7 +706,8 @@ if (require.main === module) {
module.exports = {
createServer, loadAnchor, loadMap, loadNodeMap, loadSubjectRegistry, loadSubjectAliasMap, loadNavigationMap,
loadLighthousePaths, loadHostSkills,
loadFileSnapshot, validateSnapshot, GitSnapshotStore, sourceReceipt,
readResidentRuntimeStatus, compileWarmEntry,
resolveSubjectId, navigationRoute, compileNavigation, search, searchAll,
resolveSubjectId, navigationRoute, compileNavigation, resolveLighthousePath, compileHostNavigation, search, searchAll,
};

View file

@ -7,8 +7,9 @@ const { execFileSync } = require("node:child_process");
const test = require("node:test");
const {
createServer, loadAnchor, loadMap, loadNodeMap, loadSubjectRegistry, loadSubjectAliasMap, loadNavigationMap,
loadLighthousePaths, loadHostSkills,
loadFileSnapshot, GitSnapshotStore, compileWarmEntry,
resolveSubjectId, compileNavigation, search, searchAll,
resolveSubjectId, compileNavigation, resolveLighthousePath, compileHostNavigation, search, searchAll,
} = require("./server");
function warmRuntimeStatus(overrides = {}) {
@ -65,6 +66,8 @@ test("Git snapshot store follows main atomically and retains the last known-good
subjects: ["identity/fifth-domain-subject-registry.json", "FD-SUBJECT-REGISTRY-001"],
aliases: ["identity/subject-id-alias-map.json", "FD-SUBJECT-ID-ALIAS-MAP-001"],
navigation: ["routing/ai-machine-navigation-map.json", "AI-MACHINE-NAV-001"],
lighthouse_paths: ["routing/lighthouse-path-registry.json", "GLW-LIGHTHOUSE-PATH-REGISTRY-001"],
host_skills: ["routing/host-skill-navigation-map.json", "GLW-HOST-SKILL-NAV-001"],
};
fs.mkdirSync(path.join(root, "routing"), { recursive: true });
fs.mkdirSync(path.join(root, "identity"), { recursive: true });
@ -79,7 +82,7 @@ test("Git snapshot store follows main atomically and retains the last known-good
};
for (const [key, [relative, id]] of Object.entries(declarations)) {
anchor.maps[key] = { path: relative, id, version: "1" };
const idKey = key === "subjects" ? "registry_id" : "map_id";
const idKey = ["subjects", "lighthouse_paths"].includes(key) ? "registry_id" : "map_id";
fs.writeFileSync(path.join(root, relative), JSON.stringify({ [idKey]: id, version: "1" }));
}
fs.writeFileSync(path.join(root, "routing/public-navigation-anchor.json"), JSON.stringify(anchor));
@ -211,6 +214,31 @@ test("machine navigator fails closed instead of guessing", () => {
assert.equal(compileNavigation(loadNavigationMap(), "ICE-P-ZY001", "please_guess").body.error, "unknown_intent_no_guess");
});
test("lighthouse is the numbered path validity gate", () => {
const registry = loadLighthousePaths();
assert.equal(resolveLighthousePath(registry, "REPO-012").status, 200);
const old = resolveLighthousePath(registry, "REPO-001");
assert.equal(old.status, 308);
assert.equal(old.body.redirect.redirect_to, "REPO-012");
assert.equal(resolveLighthousePath(registry, "RANDOM-PATH").status, 404);
});
test("host navigator compiles local keychain publication without granting authority", () => {
const compiled = compileHostNavigation(
loadHostSkills(),
loadLighthousePaths(),
"codex",
"你推一下线上仓库",
);
assert.equal(compiled.status, 200);
assert.equal(compiled.body.host.id, "HOST-CODEX-MACOS-001");
assert.equal(compiled.body.intent.id, "INTENT-REPOSITORY-PUBLISH-001");
assert.equal(compiled.body.target.id, "REPO-012");
assert.equal(compiled.body.intent.transport, "LOCAL_GIT_OSXKEYCHAIN");
assert.equal(compiled.body.authority, "NONE_NAVIGATION_ONLY");
assert.equal(compileHostNavigation(loadHostSkills(), loadLighthousePaths(), "unknown", "推一下线上仓库").status, 404);
});
test("public endpoints are read-only and expose CORS", async () => {
const server = createServer({ runtimeStatusProvider: async () => warmRuntimeStatus() });
await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
@ -248,6 +276,14 @@ test("public endpoints are read-only and expose CORS", async () => {
);
const navigationResponse = await fetch(`${base}/v1/navigation`);
assert.equal((await navigationResponse.json()).map_id, "AI-MACHINE-NAV-001");
const lighthouseResponse = await fetch(`${base}/v1/lighthouse`);
assert.equal((await lighthouseResponse.json()).registry_id, "GLW-LIGHTHOUSE-PATH-REGISTRY-001");
const hostNavigateResponse = await fetch(
`${base}/v1/host-navigate?host=codex&intent=${encodeURIComponent("推一下线上仓库")}`,
);
const hostBundle = await hostNavigateResponse.json();
assert.equal(hostNavigateResponse.status, 200);
assert.equal(hostBundle.intent.id, "INTENT-REPOSITORY-PUBLISH-001");
const navigateResponse = await fetch(`${base}/v1/navigate?subject=ICE-P-ZY001&intent=persona_restore&signals=context_compacted`);
const bundle = await navigateResponse.json();
assert.equal(navigateResponse.status, 200);