fix: unify and auto-refresh public navigation entry
This commit is contained in:
parent
2ba64391ea
commit
ea41e5a4fc
9 changed files with 573 additions and 61 deletions
|
|
@ -43,8 +43,8 @@ LL-CMPN-0001
|
||||||
| **小湖灯仓库** / **第五域** / **现行主** | `REPO-012` → `https://guanghulab.com/code/bingshuo/guanghu-ice-heart` → `FD-WORLD-TREE-001` | 第五代当前主入口 |
|
| **小湖灯仓库** / **第五域** / **现行主** | `REPO-012` → `https://guanghulab.com/code/bingshuo/guanghu-ice-heart` → `FD-WORLD-TREE-001` | 第五代当前主入口 |
|
||||||
| **光湖OS** / **AI原生操作系统** / **广播塔** / **TCS母语** / **HLDP编程语言** / **人格体Agent** / **模块书包** / **运行轨道** / **原生协议** / **协议注册** | `GLS-0247` → `gls/GLS-PROTOCOL-REGISTRY.json` → `gls/protocols/INDEX.hdlp` | 第五域原生 OS 架构与协议栈;只在 REPO-012 登记,不等于服务器部署 |
|
| **光湖OS** / **AI原生操作系统** / **广播塔** / **TCS母语** / **HLDP编程语言** / **人格体Agent** / **模块书包** / **运行轨道** / **原生协议** / **协议注册** | `GLS-0247` → `gls/GLS-PROTOCOL-REGISTRY.json` → `gls/protocols/INDEX.hdlp` | 第五域原生 OS 架构与协议栈;只在 REPO-012 登记,不等于服务器部署 |
|
||||||
| **企业四域分别干嘛** / **五域人格化** / **零感域两个人** / **活人格操作系统** / **国家灯塔升级** / **分布式能力** / **能力到数据** / **教育行业工作台蓝图** | `GLS-0248` → `routing/five-domain-living-persona-os-map.json` → `ZY-BIDIRECTIONAL-COGNITION-019` | 五域责任主体结构、九层OS、国家依法请求接口、通用能力不碰私密数据和P0—P10工程路线 |
|
| **企业四域分别干嘛** / **五域人格化** / **零感域两个人** / **活人格操作系统** / **国家灯塔升级** / **分布式能力** / **能力到数据** / **教育行业工作台蓝图** | `GLS-0248` → `routing/five-domain-living-persona-os-map.json` → `ZY-BIDIRECTIONAL-COGNITION-019` | 五域责任主体结构、九层OS、国家依法请求接口、通用能力不碰私密数据和P0—P10工程路线 |
|
||||||
| **仓库编号地图** / **仓库路由** / **找代码仓库** / **AI API检索** | `GH-CODE-CHANNEL-001` → `routing/code-channel-canonical-map.json` → `https://guanghulab.com/code/api/v1` | 当前机器权威入口;旧图只读 |
|
| **仓库编号地图** / **仓库路由** / **找代码仓库** / **AI API检索** | `GLW-PUBLIC-NAV-ANCHOR-001` → `routing/public-navigation-anchor.json` → `GH-CODE-CHANNEL-001` | 本地、代码频道、公共API先取同一锚点;旧图只读 |
|
||||||
| **服务器编号地图** / **服务器节点编号** / **京东节点映射** | `FD-NODE-MAP-001` → `routing/server-node-map.json` → `https://guanghulab.com/api/ai/v1/nodes` | 先解析节点,再读导航地图 |
|
| **服务器编号地图** / **服务器节点编号** / **京东节点映射** | `GLW-PUBLIC-NAV-ANCHOR-001` → `FD-NODE-MAP-001` → `routing/server-node-map.json` | 先解析唯一锚点,再解析节点并读导航地图 |
|
||||||
| **老仓库** / **历史档案** / **留痕区** | `routing/repository-route-map.json` 历史只读段 | 不推送 |
|
| **老仓库** / **历史档案** / **留痕区** | `routing/repository-route-map.json` 历史只读段 | 不推送 |
|
||||||
| **零件仓库** / **Tolaria完整源码** | REPO-004 历史只读证据;当前不得推送 | 件 / 源 |
|
| **零件仓库** / **Tolaria完整源码** | REPO-004 历史只读证据;当前不得推送 | 件 / 源 |
|
||||||
| **协作记录** / **多人格体协作** | REPO-012 → `eternal-lake-heart/heartbeat-core/LL-MULTI-PERSONA-COLLAB-REGISTRY-20260715.hdlp` | 协 |
|
| **协作记录** / **多人格体协作** | REPO-012 → `eternal-lake-heart/heartbeat-core/LL-MULTI-PERSONA-COLLAB-REGISTRY-20260715.hdlp` | 协 |
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@
|
||||||
如果用户说“搜索国内域名链接、光湖语言世界、第五域、用 API 检索”,直接读取:
|
如果用户说“搜索国内域名链接、光湖语言世界、第五域、用 API 检索”,直接读取:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
https://guanghulab.com/code/
|
https://guanghulab.com/api/ai/v1/anchor
|
||||||
https://guanghulab.com/code/api/v1/repos/search
|
仓库内 routing/public-navigation-anchor.json
|
||||||
仓库内 routing/code-channel-canonical-map.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`GLW-PUBLIC-NAV-ANCHOR-001` 是本地、代码频道和公共导航的唯一当前锚点。锚点再把同一
|
||||||
|
REPO-012 `main` 提交中的代码频道、编号、节点、主体和机器导航地图接成一个快照。
|
||||||
|
|
||||||
检索中文关键词:
|
检索中文关键词:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
@ -24,10 +26,15 @@ GET https://guanghulab.com/api/ai/v1/resolve?id=REPO-012
|
||||||
让导航器返回最小运行包:
|
让导航器返回最小运行包:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
GET https://guanghulab.com/api/ai/v1/entry?subject=ICE-P-ZY001
|
||||||
GET https://guanghulab.com/api/ai/v1/navigate?subject=ICE-P-ZY001&intent=persona_restore
|
GET https://guanghulab.com/api/ai/v1/navigate?subject=ICE-P-ZY001&intent=persona_restore
|
||||||
GET https://guanghulab.com/api/ai/v1/navigate?subject=ICE-P-ZY001&intent=persona_restore&signals=context_compacted
|
GET https://guanghulab.com/api/ai/v1/navigate?subject=ICE-P-ZY001&intent=persona_restore&signals=context_compacted
|
||||||
```
|
```
|
||||||
|
|
||||||
|
先调用 `/v1/entry`。只有它返回 `WARM_RESUME_READY` 且八项门均为 100,才复用京东
|
||||||
|
常驻运行体并只提交本轮新事件;返回 `FULL_CYCLE_REQUIRED` 或不可达时,才重新运行完整
|
||||||
|
模型认知闭环。不得每次会话都无条件从零加载和重复启动。
|
||||||
|
|
||||||
导航器不解释自然语言关键词。未知主体、未知意图或地图完整性失败时必须停止,不得猜测。
|
导航器不解释自然语言关键词。未知主体、未知意图或地图完整性失败时必须停止,不得猜测。
|
||||||
返回的路径只有发现和读取能力,不代表仓库写入、服务器执行、服务健康或人格体已经运行。
|
返回的路径只有发现和读取能力,不代表仓库写入、服务器执行、服务健康或人格体已经运行。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"schema": "guanghu.existing-service-update-request/v1",
|
||||||
|
"request_id": "AI-DISCOVERY-CANONICAL-AUTO-SNAPSHOT-20260806",
|
||||||
|
"target_node": "JD-FD-PRIMARY",
|
||||||
|
"status": "SERVICE_UPDATE_PACKAGE_READY · DEPLOYMENT_PENDING",
|
||||||
|
"purpose": {
|
||||||
|
"what": "把公共导航改为从REPO-012 main的同一提交自动读取唯一导航锚点与整套只读地图快照。",
|
||||||
|
"why": "当前公共服务使用/opt/guanghu/ai-discovery中的人工复制件,代码频道main更新后不会同步,已经出现在线2026-08-04.2落后于仓库2026-08-05.1。",
|
||||||
|
"success": "本地、代码频道和公共API都解析GLW-PUBLIC-NAV-ANCHOR-001;公共响应回执同一source_commit;后续纯地图更新无需复制文件或重启服务。",
|
||||||
|
"out_of_scope": [
|
||||||
|
"不从push执行仓库脚本",
|
||||||
|
"不自动部署服务代码、systemd单元、迁移或操作系统",
|
||||||
|
"不扩大京东服务器权限",
|
||||||
|
"不把仓库发布解释为人格体在线或光湖OS启动"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"module": {
|
||||||
|
"unit": "guanghu-ai-discovery.service",
|
||||||
|
"run_user": "guanghu",
|
||||||
|
"install_root": "/opt/guanghu/ai-discovery"
|
||||||
|
},
|
||||||
|
"service_update": {
|
||||||
|
"kind": "existing-systemd-service",
|
||||||
|
"require_existing_unit": true,
|
||||||
|
"required_existing_files": [
|
||||||
|
"server.js",
|
||||||
|
"repository-route-map.json",
|
||||||
|
"server-node-map.json",
|
||||||
|
"fifth-domain-subject-registry.json",
|
||||||
|
"subject-id-alias-map.json",
|
||||||
|
"ai-machine-navigation-map.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit_source": "server-tools/ai-discovery-gateway/guanghu-ai-discovery.service",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"source": "server-tools/ai-discovery-gateway/server.js",
|
||||||
|
"destination": "server.js",
|
||||||
|
"mode": "0644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "routing/public-navigation-anchor.json",
|
||||||
|
"destination": "public-navigation-anchor.json",
|
||||||
|
"mode": "0644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "routing/repository-route-map.json",
|
||||||
|
"destination": "repository-route-map.json",
|
||||||
|
"mode": "0644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "routing/server-node-map.json",
|
||||||
|
"destination": "server-node-map.json",
|
||||||
|
"mode": "0644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "identity/fifth-domain-subject-registry.json",
|
||||||
|
"destination": "fifth-domain-subject-registry.json",
|
||||||
|
"mode": "0644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "identity/subject-id-alias-map.json",
|
||||||
|
"destination": "subject-id-alias-map.json",
|
||||||
|
"mode": "0644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "routing/ai-machine-navigation-map.json",
|
||||||
|
"destination": "ai-machine-navigation-map.json",
|
||||||
|
"mode": "0644"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"runtime_check": {
|
||||||
|
"url": "http://127.0.0.1:3922/health",
|
||||||
|
"expected": {
|
||||||
|
"ok": true,
|
||||||
|
"service": "guanghu-ai-discovery",
|
||||||
|
"mode": "read-only",
|
||||||
|
"navigation_source": {
|
||||||
|
"anchor_id": "GLW-PUBLIC-NAV-ANCHOR-001",
|
||||||
|
"source_mode": "REPO-012_MAIN_GIT_SNAPSHOT",
|
||||||
|
"source_degraded": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acceptance_checks": [
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:3922/v1/anchor",
|
||||||
|
"expected": {
|
||||||
|
"anchor_id": "GLW-PUBLIC-NAV-ANCHOR-001",
|
||||||
|
"repository_id": "REPO-012",
|
||||||
|
"branch": "main"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:3922/v1/navigation",
|
||||||
|
"expected": {
|
||||||
|
"map_id": "AI-MACHINE-NAV-001",
|
||||||
|
"version": "2026-08-05.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:3922/v1/entry?subject=ICE-P-ZY001",
|
||||||
|
"expected": {
|
||||||
|
"status": "WARM_RESUME_READY",
|
||||||
|
"subject": "ICE-P-ZY001",
|
||||||
|
"runtime_id": "ZY-TCS-BRAIN-RUNTIME-0001",
|
||||||
|
"authority": "NONE_NAVIGATION_AND_RUNTIME_STATUS_ONLY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:3922/v1/navigate?subject=ICE-P-ZY001&intent=persona_restore",
|
||||||
|
"expected": {
|
||||||
|
"status": "COMPILED_EXACT_NO_GUESS",
|
||||||
|
"canonical_subject": "ICE-P-ZY001",
|
||||||
|
"authority": "NONE_NAVIGATION_ONLY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source_consistency_check": {
|
||||||
|
"repository": "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/guanghu-ice-heart.git",
|
||||||
|
"branch": "refs/heads/main",
|
||||||
|
"require_every_response_source_commit_equals_branch_head": true,
|
||||||
|
"require_maps_loaded_from_one_commit": true
|
||||||
|
},
|
||||||
|
"rollback": {
|
||||||
|
"automatic_on_failure": true,
|
||||||
|
"scope": "restore every declared destination and the previous systemd unit, then restart the previous service",
|
||||||
|
"receipt_required": true
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"public_read_only": true,
|
||||||
|
"allowlisted_json_only": true,
|
||||||
|
"repository_scripts_executed": false,
|
||||||
|
"restart_on_map_update": false,
|
||||||
|
"last_known_good_fallback": true,
|
||||||
|
"degraded_health_on_invalid_head": true,
|
||||||
|
"no_secrets": true
|
||||||
|
}
|
||||||
|
}
|
||||||
52
routing/public-navigation-anchor.json
Normal file
52
routing/public-navigation-anchor.json
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"schema": "guanghu.public-navigation-anchor/v1",
|
||||||
|
"anchor_id": "GLW-PUBLIC-NAV-ANCHOR-001",
|
||||||
|
"version": "2026-08-06.1",
|
||||||
|
"state": "CURRENT_CANONICAL",
|
||||||
|
"repository_id": "REPO-012",
|
||||||
|
"branch": "main",
|
||||||
|
"public_entry": "https://guanghulab.com/api/ai/v1/anchor",
|
||||||
|
"code_entry": "https://guanghulab.com/code/bingshuo/guanghu-ice-heart",
|
||||||
|
"local_entry": "routing/public-navigation-anchor.json",
|
||||||
|
"entry_path": {
|
||||||
|
"path_id": "LL-CMPN-0001",
|
||||||
|
"world_node_id": "SYS-GLW-0001",
|
||||||
|
"human_anchor": "TCS-0002∞",
|
||||||
|
"persona_id": "ICE-P-ZY001",
|
||||||
|
"runtime_id": "ZY-TCS-BRAIN-RUNTIME-0001"
|
||||||
|
},
|
||||||
|
"maps": {
|
||||||
|
"repository": {
|
||||||
|
"path": "routing/repository-route-map.json",
|
||||||
|
"id": "FD-REPO-MAP-001",
|
||||||
|
"version": "2026-08-04.4"
|
||||||
|
},
|
||||||
|
"nodes": {
|
||||||
|
"path": "routing/server-node-map.json",
|
||||||
|
"id": "FD-NODE-MAP-001",
|
||||||
|
"version": "2026-08-03.1"
|
||||||
|
},
|
||||||
|
"subjects": {
|
||||||
|
"path": "identity/fifth-domain-subject-registry.json",
|
||||||
|
"id": "FD-SUBJECT-REGISTRY-001"
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"path": "identity/subject-id-alias-map.json",
|
||||||
|
"id": "FD-SUBJECT-ID-ALIAS-MAP-001"
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"path": "routing/ai-machine-navigation-map.json",
|
||||||
|
"id": "AI-MACHINE-NAV-001",
|
||||||
|
"version": "2026-08-05.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update_contract": {
|
||||||
|
"source": "the exact accepted REPO-012 main commit",
|
||||||
|
"mode": "read-only allowlisted JSON snapshot",
|
||||||
|
"atomicity": "resolve one commit then load every declared map from that commit",
|
||||||
|
"invalid_update": "retain the last known-good in-memory snapshot and report degraded source health",
|
||||||
|
"code_execution_from_push": false,
|
||||||
|
"service_restart_on_map_update": false
|
||||||
|
},
|
||||||
|
"rule": "本地、代码频道与公共导航只解析本锚点;锚点中的全部地图必须来自同一个REPO-012 main提交。旧路径只能跳转到本锚点,不能各自声明当前真值。"
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,17 @@
|
||||||
国内主节点在回环端口 `3922` 提供只读 API,广州备案前门通过专用 SSH
|
国内主节点在回环端口 `3922` 提供只读 API,广州备案前门通过专用 SSH
|
||||||
隧道发布为 `https://guanghulab.com/api/ai/`。
|
隧道发布为 `https://guanghulab.com/api/ai/`。
|
||||||
|
|
||||||
仓库权威数据来自 `routing/repository-route-map.json`。AI 应先读取
|
唯一公共导航锚点是 `routing/public-navigation-anchor.json`,在线地址为
|
||||||
`/api/ai/v1/repositories`,再按 `REPO-xxx` 解析国内主路径;新加坡地址只作
|
`/api/ai/v1/anchor`。本地、代码频道和公共 API 都先解析该锚点,再从同一个
|
||||||
历史备用,不参与默认路由。
|
REPO-012 `main` 提交读取仓库、节点、主体、别名和机器导航地图。
|
||||||
|
|
||||||
|
公共服务只对锚点声明的 JSON 白名单执行只读加载,不运行推送提交里的脚本,也不因
|
||||||
|
地图更新重启服务。每次请求先锁定一个提交,再从该提交读取完整快照;新提交若破坏
|
||||||
|
锚点或地图一致性,服务保留内存中的上一份已验证快照,并在健康接口报告降级。
|
||||||
|
|
||||||
|
`/api/ai/v1/entry?subject=ICE-P-ZY001` 只读回环查询京东常驻控制器并返回脱敏的快速
|
||||||
|
恢复门。常驻运行体、完成回执和八项边界门都有效时返回 `WARM_RESUME_READY`,调用方
|
||||||
|
只提交本轮新事件;否则要求完整闭环。它不代理写请求,也不授予服务器执行权限。
|
||||||
|
|
||||||
服务器与人格路径编号来自 `routing/server-node-map.json`。AI 读取
|
服务器与人格路径编号来自 `routing/server-node-map.json`。AI 读取
|
||||||
`/api/ai/v1/nodes` 后,可以通过 `/api/ai/v1/resolve?id=JD-FD-PRIMARY` 或
|
`/api/ai/v1/nodes` 后,可以通过 `/api/ai/v1/resolve?id=JD-FD-PRIMARY` 或
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ Environment=GUANGHU_NODE_MAP=/opt/guanghu/ai-discovery/server-node-map.json
|
||||||
Environment=GUANGHU_SUBJECT_REGISTRY=/opt/guanghu/ai-discovery/fifth-domain-subject-registry.json
|
Environment=GUANGHU_SUBJECT_REGISTRY=/opt/guanghu/ai-discovery/fifth-domain-subject-registry.json
|
||||||
Environment=GUANGHU_SUBJECT_ALIAS_MAP=/opt/guanghu/ai-discovery/subject-id-alias-map.json
|
Environment=GUANGHU_SUBJECT_ALIAS_MAP=/opt/guanghu/ai-discovery/subject-id-alias-map.json
|
||||||
Environment=GUANGHU_NAVIGATION_MAP=/opt/guanghu/ai-discovery/ai-machine-navigation-map.json
|
Environment=GUANGHU_NAVIGATION_MAP=/opt/guanghu/ai-discovery/ai-machine-navigation-map.json
|
||||||
|
Environment=GUANGHU_NAVIGATION_ANCHOR=/opt/guanghu/ai-discovery/public-navigation-anchor.json
|
||||||
|
Environment=GUANGHU_REPOSITORY_GIT_DIR=/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/guanghu-ice-heart.git
|
||||||
ExecStart=/usr/bin/node /opt/guanghu/ai-discovery/server.js
|
ExecStart=/usr/bin/node /opt/guanghu/ai-discovery/server.js
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
|
@ -23,6 +25,7 @@ PrivateTmp=true
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
ReadOnlyPaths=/opt/guanghu/ai-discovery
|
ReadOnlyPaths=/opt/guanghu/ai-discovery
|
||||||
|
ReadOnlyPaths=/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/guanghu-ice-heart.git
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,26 @@
|
||||||
const fs = require("node:fs");
|
const fs = require("node:fs");
|
||||||
const http = require("node:http");
|
const http = require("node:http");
|
||||||
const path = require("node:path");
|
const path = require("node:path");
|
||||||
|
const { execFileSync } = require("node:child_process");
|
||||||
|
|
||||||
|
const DEFAULT_ANCHOR = path.resolve(__dirname, "../../routing/public-navigation-anchor.json");
|
||||||
const DEFAULT_MAP = path.resolve(__dirname, "../../routing/repository-route-map.json");
|
const DEFAULT_MAP = path.resolve(__dirname, "../../routing/repository-route-map.json");
|
||||||
const DEFAULT_NODE_MAP = path.resolve(__dirname, "../../routing/server-node-map.json");
|
const DEFAULT_NODE_MAP = path.resolve(__dirname, "../../routing/server-node-map.json");
|
||||||
const DEFAULT_SUBJECT_REGISTRY = path.resolve(__dirname, "../../identity/fifth-domain-subject-registry.json");
|
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_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 DEFAULT_NAVIGATION_MAP = path.resolve(__dirname, "../../routing/ai-machine-navigation-map.json");
|
||||||
|
const SNAPSHOT_KEYS = Object.freeze(["repository", "nodes", "subjects", "aliases", "navigation"]);
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadAnchor(filename = process.env.GUANGHU_NAVIGATION_ANCHOR || DEFAULT_ANCHOR) {
|
||||||
|
return JSON.parse(fs.readFileSync(filename, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
function loadMap(filename = process.env.GUANGHU_REPOSITORY_MAP || DEFAULT_MAP) {
|
function loadMap(filename = process.env.GUANGHU_REPOSITORY_MAP || DEFAULT_MAP) {
|
||||||
return JSON.parse(fs.readFileSync(filename, "utf8"));
|
return JSON.parse(fs.readFileSync(filename, "utf8"));
|
||||||
|
|
@ -30,6 +44,164 @@ function loadNavigationMap(filename = process.env.GUANGHU_NAVIGATION_MAP || DEFA
|
||||||
return JSON.parse(fs.readFileSync(filename, "utf8"));
|
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");
|
||||||
|
if (anchor.repository_id !== "REPO-012" || anchor.branch !== "main") throw new Error("invalid_anchor_source");
|
||||||
|
for (const key of SNAPSHOT_KEYS) {
|
||||||
|
const declaration = anchor.maps?.[key];
|
||||||
|
const map = maps[key];
|
||||||
|
if (!declaration || !map) throw new Error(`missing_snapshot_map:${key}`);
|
||||||
|
if (declaration.path !== SNAPSHOT_PATHS[key]) {
|
||||||
|
throw new Error(`invalid_snapshot_path:${key}`);
|
||||||
|
}
|
||||||
|
if (declaration.id && ![map.map_id, map.registry_id].includes(declaration.id)) {
|
||||||
|
throw new Error(`snapshot_map_id_mismatch:${key}`);
|
||||||
|
}
|
||||||
|
if (declaration.version && map.version !== declaration.version) {
|
||||||
|
throw new Error(`snapshot_map_version_mismatch:${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (anchor.entry_path?.path_id !== "LL-CMPN-0001" || anchor.entry_path?.world_node_id !== "SYS-GLW-0001") {
|
||||||
|
throw new Error("invalid_language_world_entry");
|
||||||
|
}
|
||||||
|
return { anchor, ...maps };
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFileSnapshot(options = {}) {
|
||||||
|
const anchor = loadAnchor(options.anchorFile);
|
||||||
|
return validateSnapshot(anchor, {
|
||||||
|
repository: loadMap(options.mapFile),
|
||||||
|
nodes: loadNodeMap(options.nodeMapFile),
|
||||||
|
subjects: loadSubjectRegistry(options.subjectRegistryFile),
|
||||||
|
aliases: loadSubjectAliasMap(options.subjectAliasMapFile),
|
||||||
|
navigation: loadNavigationMap(options.navigationMapFile),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class GitSnapshotStore {
|
||||||
|
constructor(gitDir, ref = "refs/heads/main") {
|
||||||
|
this.gitDir = gitDir;
|
||||||
|
this.ref = ref;
|
||||||
|
this.lastKnownGood = null;
|
||||||
|
this.lastError = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
git(args) {
|
||||||
|
return execFileSync("/usr/bin/git", [`--git-dir=${this.gitDir}`, ...args], {
|
||||||
|
encoding: "utf8",
|
||||||
|
maxBuffer: 4 * 1024 * 1024,
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
readJson(commit, relativePath) {
|
||||||
|
return JSON.parse(this.git(["show", `${commit}:${relativePath}`]));
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
const commit = this.git(["rev-parse", "--verify", `${this.ref}^{commit}`]).trim();
|
||||||
|
if (this.lastKnownGood?.source_commit === commit) return this.lastKnownGood;
|
||||||
|
const anchor = this.readJson(commit, "routing/public-navigation-anchor.json");
|
||||||
|
const maps = {};
|
||||||
|
for (const key of SNAPSHOT_KEYS) {
|
||||||
|
if (anchor.maps?.[key]?.path !== SNAPSHOT_PATHS[key]) throw new Error(`invalid_snapshot_path:${key}`);
|
||||||
|
maps[key] = this.readJson(commit, SNAPSHOT_PATHS[key]);
|
||||||
|
}
|
||||||
|
this.lastKnownGood = {
|
||||||
|
...validateSnapshot(anchor, maps),
|
||||||
|
source_commit: commit,
|
||||||
|
source_mode: "REPO-012_MAIN_GIT_SNAPSHOT",
|
||||||
|
source_degraded: false,
|
||||||
|
};
|
||||||
|
this.lastError = null;
|
||||||
|
return this.lastKnownGood;
|
||||||
|
} catch (error) {
|
||||||
|
this.lastError = String(error.message || error);
|
||||||
|
if (this.lastKnownGood) {
|
||||||
|
return { ...this.lastKnownGood, source_degraded: true, source_error: this.lastError };
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourceReceipt(snapshot) {
|
||||||
|
return {
|
||||||
|
anchor_id: snapshot.anchor.anchor_id,
|
||||||
|
anchor_version: snapshot.anchor.version,
|
||||||
|
source_commit: snapshot.source_commit || null,
|
||||||
|
source_mode: snapshot.source_mode || "FILESYSTEM_SNAPSHOT",
|
||||||
|
source_degraded: Boolean(snapshot.source_degraded),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function withSource(body, snapshot) {
|
||||||
|
return { ...body, navigation_source: sourceReceipt(snapshot) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function readResidentRuntimeStatus(options = {}) {
|
||||||
|
const host = options.host || "127.0.0.1";
|
||||||
|
const port = Number(options.port || process.env.GUANGHU_PERSONA_CONTROLLER_PORT || 3930);
|
||||||
|
const timeoutMs = Number(options.timeoutMs || 1500);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = http.get({ host, port, path: "/v1/status", timeout: timeoutMs }, response => {
|
||||||
|
const chunks = [];
|
||||||
|
let size = 0;
|
||||||
|
response.on("data", chunk => {
|
||||||
|
size += chunk.length;
|
||||||
|
if (size > 64 * 1024) {
|
||||||
|
request.destroy(new Error("runtime_status_too_large"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
response.on("end", () => {
|
||||||
|
if (response.statusCode !== 200) return reject(new Error(`runtime_status_http_${response.statusCode}`));
|
||||||
|
try { resolve(JSON.parse(Buffer.concat(chunks).toString("utf8"))); }
|
||||||
|
catch { reject(new Error("runtime_status_invalid_json")); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
request.on("timeout", () => request.destroy(new Error("runtime_status_timeout")));
|
||||||
|
request.on("error", reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileWarmEntry(status) {
|
||||||
|
const gates = [
|
||||||
|
"language_world_entry_bound",
|
||||||
|
"chu_he_han_jie_boundary_bound",
|
||||||
|
"persona_cycle_language_world_entry_bound",
|
||||||
|
"persona_cycle_chu_he_han_jie_boundary_bound",
|
||||||
|
"persona_brain_runtime_exists",
|
||||||
|
"living_ai_system_controller_running",
|
||||||
|
"machine_navigation_bound",
|
||||||
|
"language_world_route_first",
|
||||||
|
];
|
||||||
|
const identityValid =
|
||||||
|
status?.node === "JD-FD-PRIMARY" &&
|
||||||
|
status?.persona_id === "ICE-P-ZY001" &&
|
||||||
|
status?.runtime_id === "ZY-TCS-BRAIN-RUNTIME-0001";
|
||||||
|
const gatesValid = gates.every(key => status?.[key] === 100);
|
||||||
|
const warm = identityValid && gatesValid && status.phase === "RUNNING_COMPANION" && status.completed_cycles >= 1;
|
||||||
|
return {
|
||||||
|
schema: "guanghu.persona-warm-entry/v1",
|
||||||
|
status: warm ? "WARM_RESUME_READY" : "FULL_CYCLE_REQUIRED",
|
||||||
|
subject: "ICE-P-ZY001",
|
||||||
|
runtime_id: "ZY-TCS-BRAIN-RUNTIME-0001",
|
||||||
|
node: status?.node || "JD-FD-PRIMARY",
|
||||||
|
phase: status?.phase || "UNAVAILABLE",
|
||||||
|
completed_cycles: Number(status?.completed_cycles || 0),
|
||||||
|
receipt_id: status?.receipt_id || null,
|
||||||
|
gates: Object.fromEntries(gates.map(key => [key, status?.[key] === 100 ? 100 : 0])),
|
||||||
|
start_rule: warm
|
||||||
|
? "Reuse the resident verified companion state; submit only the new bounded event."
|
||||||
|
: "Run one complete model-backed persona cycle before claiming persona runtime.",
|
||||||
|
authority: "NONE_NAVIGATION_AND_RUNTIME_STATUS_ONLY",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function resolveSubjectId(aliasMap, requestedId) {
|
function resolveSubjectId(aliasMap, requestedId) {
|
||||||
const requested = String(requestedId || "").trim();
|
const requested = String(requestedId || "").trim();
|
||||||
const key = requested.toUpperCase();
|
const key = requested.toUpperCase();
|
||||||
|
|
@ -185,48 +357,78 @@ function createServer(options = {}) {
|
||||||
const subjectRegistryFile = options.subjectRegistryFile || process.env.GUANGHU_SUBJECT_REGISTRY || DEFAULT_SUBJECT_REGISTRY;
|
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 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 navigationMapFile = options.navigationMapFile || process.env.GUANGHU_NAVIGATION_MAP || DEFAULT_NAVIGATION_MAP;
|
||||||
return http.createServer((req, res) => {
|
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);
|
||||||
|
const runtimeStatusProvider = options.runtimeStatusProvider || readResidentRuntimeStatus;
|
||||||
|
return http.createServer(async (req, res) => {
|
||||||
const url = new URL(req.url, "http://localhost");
|
const url = new URL(req.url, "http://localhost");
|
||||||
if (req.method !== "GET") return json(res, 405, { error: "method_not_allowed" });
|
if (req.method !== "GET") return json(res, 405, { error: "method_not_allowed" });
|
||||||
if (url.pathname === "/health") return json(res, 200, { ok: true, service: "guanghu-ai-discovery", mode: "read-only" });
|
let snapshot;
|
||||||
|
try {
|
||||||
let map;
|
snapshot = snapshotStore
|
||||||
try { map = loadMap(mapFile); } catch { return json(res, 503, { error: "route_map_unavailable" }); }
|
? snapshotStore.get()
|
||||||
|
: {
|
||||||
|
...loadFileSnapshot({
|
||||||
|
anchorFile, mapFile, nodeMapFile, subjectRegistryFile, subjectAliasMapFile, navigationMapFile,
|
||||||
|
}),
|
||||||
|
source_commit: null,
|
||||||
|
source_mode: "FILESYSTEM_SNAPSHOT",
|
||||||
|
source_degraded: false,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return json(res, 503, { error: "navigation_snapshot_unavailable" });
|
||||||
|
}
|
||||||
|
const map = snapshot.repository;
|
||||||
|
const nodeMap = snapshot.nodes;
|
||||||
|
const subjectRegistry = snapshot.subjects;
|
||||||
|
const aliasMap = snapshot.aliases;
|
||||||
|
const navigationMap = snapshot.navigation;
|
||||||
|
if (url.pathname === "/health") {
|
||||||
|
return json(res, snapshot.source_degraded ? 503 : 200, {
|
||||||
|
ok: !snapshot.source_degraded,
|
||||||
|
service: "guanghu-ai-discovery",
|
||||||
|
mode: "read-only",
|
||||||
|
navigation_source: sourceReceipt(snapshot),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (url.pathname === "/" || url.pathname === "/index.html") return html(res, entryPage(map));
|
if (url.pathname === "/" || url.pathname === "/index.html") return html(res, entryPage(map));
|
||||||
if (url.pathname === "/v1/repositories" || url.pathname === "/v1/manifest") return json(res, 200, map, 300);
|
if (url.pathname === "/v1/anchor") return json(res, 200, withSource(snapshot.anchor, snapshot), 60);
|
||||||
if (url.pathname === "/v1/nodes") {
|
if (url.pathname === "/v1/repositories" || url.pathname === "/v1/manifest") return json(res, 200, withSource(map, snapshot), 60);
|
||||||
try { return json(res, 200, loadNodeMap(nodeMapFile), 300); }
|
if (url.pathname === "/v1/nodes") return json(res, 200, withSource(nodeMap, snapshot), 60);
|
||||||
catch { return json(res, 503, { error: "node_map_unavailable" }); }
|
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/subjects") {
|
if (url.pathname === "/v1/entry") {
|
||||||
try { return json(res, 200, loadSubjectRegistry(subjectRegistryFile), 300); }
|
const subject = String(url.searchParams.get("subject") || "").toUpperCase();
|
||||||
catch { return json(res, 503, { error: "subject_registry_unavailable" }); }
|
if (!["ICE-P-ZY001", "ICE-GL-ZY001", "ICE-PZY-001"].includes(subject)) {
|
||||||
}
|
return json(res, 404, withSource({ error: "unknown_subject_no_guess", requested_subject: subject }, snapshot));
|
||||||
if (url.pathname === "/v1/navigation") {
|
}
|
||||||
try { return json(res, 200, loadNavigationMap(navigationMapFile), 300); }
|
try {
|
||||||
catch { return json(res, 503, { error: "navigation_map_unavailable" }); }
|
const entry = compileWarmEntry(await runtimeStatusProvider());
|
||||||
|
return json(res, entry.status === "WARM_RESUME_READY" ? 200 : 409, withSource(entry, snapshot));
|
||||||
|
} catch (error) {
|
||||||
|
return json(res, 503, withSource({
|
||||||
|
schema: "guanghu.persona-warm-entry/v1",
|
||||||
|
status: "RUNTIME_STATUS_UNAVAILABLE",
|
||||||
|
subject: "ICE-P-ZY001",
|
||||||
|
error: "resident_controller_unavailable",
|
||||||
|
start_rule: "Do not claim warm resume; use the full verified cycle or inspect the resident controller.",
|
||||||
|
authority: "NONE_NAVIGATION_AND_RUNTIME_STATUS_ONLY",
|
||||||
|
}, snapshot));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (url.pathname === "/v1/navigate") {
|
if (url.pathname === "/v1/navigate") {
|
||||||
let navigationMap;
|
|
||||||
try { navigationMap = loadNavigationMap(navigationMapFile); }
|
|
||||||
catch { return json(res, 503, { error: "navigation_map_unavailable" }); }
|
|
||||||
const compiled = compileNavigation(
|
const compiled = compileNavigation(
|
||||||
navigationMap,
|
navigationMap,
|
||||||
String(url.searchParams.get("subject") || "").slice(0, 100),
|
String(url.searchParams.get("subject") || "").slice(0, 100),
|
||||||
String(url.searchParams.get("intent") || "").slice(0, 100),
|
String(url.searchParams.get("intent") || "").slice(0, 100),
|
||||||
String(url.searchParams.get("signals") || "").slice(0, 500),
|
String(url.searchParams.get("signals") || "").slice(0, 500),
|
||||||
);
|
);
|
||||||
return json(res, compiled.status, compiled.body, compiled.status === 200 ? 60 : 0);
|
return json(res, compiled.status, withSource(compiled.body, snapshot), compiled.status === 200 ? 60 : 0);
|
||||||
}
|
}
|
||||||
if (url.pathname === "/v1/search") {
|
if (url.pathname === "/v1/search") {
|
||||||
const query = String(url.searchParams.get("q") || "").slice(0, 200);
|
const query = String(url.searchParams.get("q") || "").slice(0, 200);
|
||||||
let nodeMap;
|
|
||||||
let subjectRegistry;
|
|
||||||
try { nodeMap = loadNodeMap(nodeMapFile); }
|
|
||||||
catch { return json(res, 503, { error: "node_map_unavailable" }); }
|
|
||||||
try { subjectRegistry = loadSubjectRegistry(subjectRegistryFile); }
|
|
||||||
catch { return json(res, 503, { error: "subject_registry_unavailable" }); }
|
|
||||||
const results = searchAll(map, nodeMap, query, subjectRegistry);
|
const results = searchAll(map, nodeMap, query, subjectRegistry);
|
||||||
return json(res, 200, {
|
return json(res, 200, {
|
||||||
schema: "guanghu.ai-search-response/v1",
|
schema: "guanghu.ai-search-response/v1",
|
||||||
|
|
@ -235,46 +437,35 @@ function createServer(options = {}) {
|
||||||
map_version: map.version,
|
map_version: map.version,
|
||||||
count: results.length,
|
count: results.length,
|
||||||
results,
|
results,
|
||||||
|
navigation_source: sourceReceipt(snapshot),
|
||||||
}, 60);
|
}, 60);
|
||||||
}
|
}
|
||||||
if (url.pathname === "/v1/resolve") {
|
if (url.pathname === "/v1/resolve") {
|
||||||
const id = String(url.searchParams.get("id") || "").toUpperCase();
|
const id = String(url.searchParams.get("id") || "").toUpperCase();
|
||||||
const repository = map.repositories.find(item => item.code === id || item.slug.toUpperCase() === id);
|
const repository = map.repositories.find(item => item.code === id || item.slug.toUpperCase() === id);
|
||||||
if (repository) return json(res, 200, repository, 300);
|
if (repository) return json(res, 200, withSource(repository, snapshot), 60);
|
||||||
let nodeMap;
|
|
||||||
try { nodeMap = loadNodeMap(nodeMapFile); }
|
|
||||||
catch { return json(res, 503, { error: "node_map_unavailable" }); }
|
|
||||||
const node = nodeMap.nodes.find(item => item.node_id.toUpperCase() === id);
|
const node = nodeMap.nodes.find(item => item.node_id.toUpperCase() === id);
|
||||||
if (node) return json(res, 200, node, 300);
|
if (node) return json(res, 200, withSource(node, snapshot), 60);
|
||||||
const personaRoute = nodeMap.persona_routes.find(item => item.route_id.toUpperCase() === id);
|
const personaRoute = nodeMap.persona_routes.find(item => item.route_id.toUpperCase() === id);
|
||||||
if (personaRoute) return json(res, 200, personaRoute, 300);
|
if (personaRoute) return json(res, 200, withSource(personaRoute, snapshot), 60);
|
||||||
|
|
||||||
let navigationMap = null;
|
|
||||||
try { navigationMap = loadNavigationMap(navigationMapFile); } catch { /* Legacy routes remain available. */ }
|
|
||||||
if (navigationMap) {
|
if (navigationMap) {
|
||||||
if (navigationMap.map_id.toUpperCase() === id) return json(res, 200, navigationMap, 300);
|
if (snapshot.anchor.anchor_id.toUpperCase() === id) return json(res, 200, withSource(snapshot.anchor, snapshot), 60);
|
||||||
|
if (navigationMap.map_id.toUpperCase() === id) return json(res, 200, withSource(navigationMap, snapshot), 60);
|
||||||
const machineRoute = navigationRoute(navigationMap, id);
|
const machineRoute = navigationRoute(navigationMap, id);
|
||||||
if (machineRoute) {
|
if (machineRoute) {
|
||||||
return json(res, 200, {
|
return json(res, 200, withSource({
|
||||||
schema: "guanghu.ai-machine-route-resolution/v1",
|
schema: "guanghu.ai-machine-route-resolution/v1",
|
||||||
status: "RESOLVED",
|
status: "RESOLVED",
|
||||||
map_id: navigationMap.map_id,
|
map_id: navigationMap.map_id,
|
||||||
...machineRoute,
|
...machineRoute,
|
||||||
}, 300);
|
}, snapshot), 60);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let aliasMap;
|
|
||||||
let subjectRegistry;
|
|
||||||
try {
|
|
||||||
aliasMap = loadSubjectAliasMap(subjectAliasMapFile);
|
|
||||||
subjectRegistry = loadSubjectRegistry(subjectRegistryFile);
|
|
||||||
} catch {
|
|
||||||
return json(res, 503, { error: "subject_identity_map_unavailable" });
|
|
||||||
}
|
|
||||||
const exactSubject = subjectRegistry.subjects.find(item => item.id.toUpperCase() === id);
|
const exactSubject = subjectRegistry.subjects.find(item => item.id.toUpperCase() === id);
|
||||||
if (exactSubject) {
|
if (exactSubject) {
|
||||||
return json(res, 200, {
|
return json(res, 200, withSource({
|
||||||
schema: "guanghu.subject-resolution/v1",
|
schema: "guanghu.subject-resolution/v1",
|
||||||
status: "RESOLVED",
|
status: "RESOLVED",
|
||||||
requested_id: id,
|
requested_id: id,
|
||||||
|
|
@ -282,7 +473,7 @@ function createServer(options = {}) {
|
||||||
redirected: false,
|
redirected: false,
|
||||||
subject_kind: exactSubject.subject_kind,
|
subject_kind: exactSubject.subject_kind,
|
||||||
subject: exactSubject,
|
subject: exactSubject,
|
||||||
}, 300);
|
}, snapshot), 60);
|
||||||
}
|
}
|
||||||
const identity = resolveSubjectId(aliasMap, id);
|
const identity = resolveSubjectId(aliasMap, id);
|
||||||
if (identity.status === "CONFLICT_REJECTED") {
|
if (identity.status === "CONFLICT_REJECTED") {
|
||||||
|
|
@ -291,7 +482,7 @@ function createServer(options = {}) {
|
||||||
if (identity.canonical_id) {
|
if (identity.canonical_id) {
|
||||||
const subject = subjectRegistry.subjects.find(item => item.id.toUpperCase() === identity.canonical_id.toUpperCase());
|
const subject = subjectRegistry.subjects.find(item => item.id.toUpperCase() === identity.canonical_id.toUpperCase());
|
||||||
if (!subject) return json(res, 503, { error: "canonical_subject_missing", ...identity });
|
if (!subject) return json(res, 503, { error: "canonical_subject_missing", ...identity });
|
||||||
return json(res, 200, {
|
return json(res, 200, withSource({
|
||||||
schema: "guanghu.subject-resolution/v1",
|
schema: "guanghu.subject-resolution/v1",
|
||||||
...identity,
|
...identity,
|
||||||
subject,
|
subject,
|
||||||
|
|
@ -300,7 +491,7 @@ function createServer(options = {}) {
|
||||||
path: identity.current_path,
|
path: identity.current_path,
|
||||||
rule: "Canonicalize the subject id before navigation; legacy ids grant no authority.",
|
rule: "Canonicalize the subject id before navigation; legacy ids grant no authority.",
|
||||||
},
|
},
|
||||||
}, 300);
|
}, snapshot), 60);
|
||||||
}
|
}
|
||||||
return json(res, 404, { error: "route_not_found", id });
|
return json(res, 404, { error: "route_not_found", id });
|
||||||
}
|
}
|
||||||
|
|
@ -308,17 +499,20 @@ function createServer(options = {}) {
|
||||||
if (url.pathname === "/well-known") return json(res, 200, {
|
if (url.pathname === "/well-known") return json(res, 200, {
|
||||||
schema: "guanghu.ai-discovery/v1",
|
schema: "guanghu.ai-discovery/v1",
|
||||||
name: "光湖语言世界 · 第五域",
|
name: "光湖语言世界 · 第五域",
|
||||||
|
canonical_anchor: "https://guanghulab.com/api/ai/v1/anchor",
|
||||||
canonical_repository: map.repositories[0].primary.url,
|
canonical_repository: map.repositories[0].primary.url,
|
||||||
repository_map: map.canonical_api,
|
repository_map: map.canonical_api,
|
||||||
server_node_map: "https://guanghulab.com/api/ai/v1/nodes",
|
server_node_map: "https://guanghulab.com/api/ai/v1/nodes",
|
||||||
subject_registry: "https://guanghulab.com/api/ai/v1/subjects",
|
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",
|
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",
|
machine_navigation_map: "https://guanghulab.com/api/ai/v1/navigation",
|
||||||
|
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}",
|
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}",
|
search_api: "https://guanghulab.com/api/ai/v1/search?q={query}",
|
||||||
resolve_api: "https://guanghulab.com/api/ai/v1/resolve?id={NUMBER}",
|
resolve_api: "https://guanghulab.com/api/ai/v1/resolve?id={NUMBER}",
|
||||||
openapi: "https://guanghulab.com/api/ai/openapi.json",
|
openapi: "https://guanghulab.com/api/ai/openapi.json",
|
||||||
access: "public-read-only",
|
access: "public-read-only",
|
||||||
|
navigation_source: sourceReceipt(snapshot),
|
||||||
write_authorization: {
|
write_authorization: {
|
||||||
mode: "public-no-authority-workorder-then-owner-email-approval",
|
mode: "public-no-authority-workorder-then-owner-email-approval",
|
||||||
capabilities: "https://guanghulab.com/authz/api/public/capabilities",
|
capabilities: "https://guanghulab.com/authz/api/public/capabilities",
|
||||||
|
|
@ -363,10 +557,12 @@ function openApi() {
|
||||||
info: { title: "光湖语言世界 · 第五域 AI Discovery API", version: "1.1.0" },
|
info: { title: "光湖语言世界 · 第五域 AI Discovery API", version: "1.1.0" },
|
||||||
servers: [{ url: "https://guanghulab.com/api/ai" }],
|
servers: [{ url: "https://guanghulab.com/api/ai" }],
|
||||||
paths: {
|
paths: {
|
||||||
|
"/v1/anchor": { get: { summary: "读取唯一公共导航锚点和当前REPO-012提交", responses: { "200": { description: "Canonical navigation anchor" } } } },
|
||||||
"/v1/repositories": { get: { summary: "读取最新仓库编号路径映射", responses: { "200": { description: "Repository route map" } } } },
|
"/v1/repositories": { get: { summary: "读取最新仓库编号路径映射", responses: { "200": { description: "Repository route map" } } } },
|
||||||
"/v1/nodes": { get: { summary: "读取最新服务器节点与人格路径编号映射", responses: { "200": { description: "Server node map" } } } },
|
"/v1/nodes": { get: { summary: "读取最新服务器节点与人格路径编号映射", responses: { "200": { description: "Server node map" } } } },
|
||||||
"/v1/subjects": { get: { summary: "读取人类、人格体与公共系统的分型身份注册表", responses: { "200": { description: "Subject registry" } } } },
|
"/v1/subjects": { get: { summary: "读取人类、人格体与公共系统的分型身份注册表", responses: { "200": { description: "Subject registry" } } } },
|
||||||
"/v1/navigation": { get: { summary: "读取主体与意图驱动的机器导航地图", responses: { "200": { description: "Machine navigation map" } } } },
|
"/v1/navigation": { get: { summary: "读取主体与意图驱动的机器导航地图", responses: { "200": { description: "Machine navigation map" } } } },
|
||||||
|
"/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/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" } } } },
|
"/v1/search": { get: { summary: "按中文、编号或项目名检索", parameters: [{ name: "q", in: "query", schema: { type: "string" } }], responses: { "200": { description: "Search results" } } } },
|
||||||
"/v1/resolve": { get: { summary: "解析仓库、服务器节点或人格路径编号", parameters: [{ name: "id", in: "query", required: true, schema: { type: "string", example: "ZY-OPS-LOOP-001" } }], responses: { "200": { description: "Resolved numbered route" }, "404": { description: "Unknown route" } } } }
|
"/v1/resolve": { get: { summary: "解析仓库、服务器节点或人格路径编号", parameters: [{ name: "id", in: "query", required: true, schema: { type: "string", example: "ZY-OPS-LOOP-001" } }], responses: { "200": { description: "Resolved numbered route" }, "404": { description: "Unknown route" } } } }
|
||||||
|
|
@ -381,6 +577,8 @@ if (require.main === module) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createServer, loadMap, loadNodeMap, loadSubjectRegistry, loadSubjectAliasMap, loadNavigationMap,
|
createServer, loadAnchor, loadMap, loadNodeMap, loadSubjectRegistry, loadSubjectAliasMap, loadNavigationMap,
|
||||||
|
loadFileSnapshot, validateSnapshot, GitSnapshotStore, sourceReceipt,
|
||||||
|
readResidentRuntimeStatus, compileWarmEntry,
|
||||||
resolveSubjectId, navigationRoute, compileNavigation, search, searchAll,
|
resolveSubjectId, navigationRoute, compileNavigation, search, searchAll,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,107 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
const assert = require("node:assert/strict");
|
const assert = require("node:assert/strict");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const os = require("node:os");
|
||||||
|
const path = require("node:path");
|
||||||
|
const { execFileSync } = require("node:child_process");
|
||||||
const test = require("node:test");
|
const test = require("node:test");
|
||||||
const {
|
const {
|
||||||
createServer, loadMap, loadNodeMap, loadSubjectRegistry, loadSubjectAliasMap, loadNavigationMap,
|
createServer, loadAnchor, loadMap, loadNodeMap, loadSubjectRegistry, loadSubjectAliasMap, loadNavigationMap,
|
||||||
|
loadFileSnapshot, GitSnapshotStore, compileWarmEntry,
|
||||||
resolveSubjectId, compileNavigation, search, searchAll,
|
resolveSubjectId, compileNavigation, search, searchAll,
|
||||||
} = require("./server");
|
} = require("./server");
|
||||||
|
|
||||||
|
function warmRuntimeStatus(overrides = {}) {
|
||||||
|
return {
|
||||||
|
node: "JD-FD-PRIMARY",
|
||||||
|
persona_id: "ICE-P-ZY001",
|
||||||
|
runtime_id: "ZY-TCS-BRAIN-RUNTIME-0001",
|
||||||
|
phase: "RUNNING_COMPANION",
|
||||||
|
completed_cycles: 1,
|
||||||
|
receipt_id: "ZY-BRAIN-RUNTIME-TEST",
|
||||||
|
language_world_entry_bound: 100,
|
||||||
|
chu_he_han_jie_boundary_bound: 100,
|
||||||
|
persona_cycle_language_world_entry_bound: 100,
|
||||||
|
persona_cycle_chu_he_han_jie_boundary_bound: 100,
|
||||||
|
persona_brain_runtime_exists: 100,
|
||||||
|
living_ai_system_controller_running: 100,
|
||||||
|
machine_navigation_bound: 100,
|
||||||
|
language_world_route_first: 100,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test("warm entry reuses only a fully verified resident companion", () => {
|
||||||
|
assert.equal(compileWarmEntry(warmRuntimeStatus()).status, "WARM_RESUME_READY");
|
||||||
|
assert.equal(
|
||||||
|
compileWarmEntry(warmRuntimeStatus({ persona_brain_runtime_exists: 0 })).status,
|
||||||
|
"FULL_CYCLE_REQUIRED",
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
compileWarmEntry(warmRuntimeStatus({ phase: "RUNNING_CYCLE" })).status,
|
||||||
|
"FULL_CYCLE_REQUIRED",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("canonical anchor binds every local map to one language-world entry", () => {
|
||||||
|
const snapshot = loadFileSnapshot();
|
||||||
|
assert.equal(snapshot.anchor.anchor_id, "GLW-PUBLIC-NAV-ANCHOR-001");
|
||||||
|
assert.equal(snapshot.anchor.entry_path.path_id, "LL-CMPN-0001");
|
||||||
|
assert.equal(snapshot.anchor.entry_path.world_node_id, "SYS-GLW-0001");
|
||||||
|
assert.equal(snapshot.anchor.entry_path.persona_id, "ICE-P-ZY001");
|
||||||
|
assert.equal(snapshot.anchor.entry_path.runtime_id, "ZY-TCS-BRAIN-RUNTIME-0001");
|
||||||
|
assert.equal(snapshot.navigation.map_id, loadAnchor().maps.navigation.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Git snapshot store follows main atomically and retains the last known-good snapshot", () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "guanghu-nav-"));
|
||||||
|
try {
|
||||||
|
execFileSync("git", ["init", "-q", "-b", "main", root]);
|
||||||
|
execFileSync("git", ["-C", root, "config", "user.name", "test"]);
|
||||||
|
execFileSync("git", ["-C", root, "config", "user.email", "test@example.invalid"]);
|
||||||
|
const declarations = {
|
||||||
|
repository: ["routing/repository-route-map.json", "FD-REPO-MAP-001"],
|
||||||
|
nodes: ["routing/server-node-map.json", "FD-NODE-MAP-001"],
|
||||||
|
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"],
|
||||||
|
};
|
||||||
|
fs.mkdirSync(path.join(root, "routing"), { recursive: true });
|
||||||
|
fs.mkdirSync(path.join(root, "identity"), { recursive: true });
|
||||||
|
const anchor = {
|
||||||
|
schema: "guanghu.public-navigation-anchor/v1",
|
||||||
|
anchor_id: "GLW-PUBLIC-NAV-ANCHOR-001",
|
||||||
|
version: "test.1",
|
||||||
|
repository_id: "REPO-012",
|
||||||
|
branch: "main",
|
||||||
|
entry_path: { path_id: "LL-CMPN-0001", world_node_id: "SYS-GLW-0001" },
|
||||||
|
maps: {},
|
||||||
|
};
|
||||||
|
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";
|
||||||
|
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));
|
||||||
|
execFileSync("git", ["-C", root, "add", "."]);
|
||||||
|
execFileSync("git", ["-C", root, "commit", "-qm", "valid snapshot"]);
|
||||||
|
const store = new GitSnapshotStore(path.join(root, ".git"));
|
||||||
|
const first = store.get();
|
||||||
|
assert.equal(first.anchor.version, "test.1");
|
||||||
|
assert.equal(first.source_degraded, false);
|
||||||
|
anchor.maps.navigation.version = "2";
|
||||||
|
fs.writeFileSync(path.join(root, "routing/public-navigation-anchor.json"), JSON.stringify(anchor));
|
||||||
|
execFileSync("git", ["-C", root, "add", "."]);
|
||||||
|
execFileSync("git", ["-C", root, "commit", "-qm", "invalid snapshot"]);
|
||||||
|
const fallback = store.get();
|
||||||
|
assert.equal(fallback.source_commit, first.source_commit);
|
||||||
|
assert.equal(fallback.source_degraded, true);
|
||||||
|
assert.match(fallback.source_error, /snapshot_map_version_mismatch:navigation/);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("repository map exposes only the three current code-channel repositories", () => {
|
test("repository map exposes only the three current code-channel repositories", () => {
|
||||||
const map = loadMap();
|
const map = loadMap();
|
||||||
const expectedCodes = ["REPO-012", "REPO-014", "REPO-015"];
|
const expectedCodes = ["REPO-012", "REPO-014", "REPO-015"];
|
||||||
|
|
@ -108,7 +204,7 @@ test("machine navigator fails closed instead of guessing", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("public endpoints are read-only and expose CORS", async () => {
|
test("public endpoints are read-only and expose CORS", async () => {
|
||||||
const server = createServer();
|
const server = createServer({ runtimeStatusProvider: async () => warmRuntimeStatus() });
|
||||||
await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
|
await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
|
||||||
const base = `http://127.0.0.1:${server.address().port}`;
|
const base = `http://127.0.0.1:${server.address().port}`;
|
||||||
try {
|
try {
|
||||||
|
|
@ -151,6 +247,13 @@ test("public endpoints are read-only and expose CORS", async () => {
|
||||||
assert.equal(bundle.runtime_entry.id, "ZY-TCS-BRAIN-RUNTIME-0001");
|
assert.equal(bundle.runtime_entry.id, "ZY-TCS-BRAIN-RUNTIME-0001");
|
||||||
assert.equal((await fetch(`${base}/v1/navigate?subject=unknown&intent=persona_restore`)).status, 404);
|
assert.equal((await fetch(`${base}/v1/navigate?subject=unknown&intent=persona_restore`)).status, 404);
|
||||||
assert.equal((await fetch(`${base}/v1/navigate?subject=ICE-P-ZY001&intent=guess`)).status, 404);
|
assert.equal((await fetch(`${base}/v1/navigate?subject=ICE-P-ZY001&intent=guess`)).status, 404);
|
||||||
|
const entryResponse = await fetch(`${base}/v1/entry?subject=ICE-P-ZY001`);
|
||||||
|
const entry = await entryResponse.json();
|
||||||
|
assert.equal(entryResponse.status, 200);
|
||||||
|
assert.equal(entry.status, "WARM_RESUME_READY");
|
||||||
|
assert.equal(entry.receipt_id, "ZY-BRAIN-RUNTIME-TEST");
|
||||||
|
assert.equal(entry.navigation_source.anchor_id, "GLW-PUBLIC-NAV-ANCHOR-001");
|
||||||
|
assert.equal((await fetch(`${base}/v1/entry?subject=unknown`)).status, 404);
|
||||||
const humanResponse = await fetch(`${base}/v1/resolve?id=${encodeURIComponent("ICE-GL∞")}`);
|
const humanResponse = await fetch(`${base}/v1/resolve?id=${encodeURIComponent("ICE-GL∞")}`);
|
||||||
const humanResolution = await humanResponse.json();
|
const humanResolution = await humanResponse.json();
|
||||||
assert.equal(humanResponse.status, 200);
|
assert.equal(humanResponse.status, 200);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"schema": "GLW-WORLD-ROUTER-1.1",
|
"schema": "GLW-WORLD-ROUTER-1.1",
|
||||||
|
"canonical_navigation_anchor": "routing/public-navigation-anchor.json",
|
||||||
|
"canonical_navigation_anchor_id": "GLW-PUBLIC-NAV-ANCHOR-001",
|
||||||
"canonical_source": "BROADCAST-TOWER.hdlp",
|
"canonical_source": "BROADCAST-TOWER.hdlp",
|
||||||
"entry_repository": "REPO-012",
|
"entry_repository": "REPO-012",
|
||||||
"world_tree_map": {
|
"world_tree_map": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue