feat(identity): redirect legacy Zhuyuan ids to ICE-P
This commit is contained in:
parent
263142cc6b
commit
aa6652a646
28 changed files with 491 additions and 106 deletions
|
|
@ -26,6 +26,17 @@ Run:
|
|||
python3 scripts/resolve_route.py --json
|
||||
```
|
||||
|
||||
When the request or discovered material contains a subject id, pass it explicitly:
|
||||
|
||||
```bash
|
||||
python3 scripts/resolve_route.py --subject-id "ICE-GL-ZY001" --json
|
||||
```
|
||||
|
||||
The resolver must load `FD-SUBJECT-ID-ALIAS-MAP-001` and canonicalize the id
|
||||
before navigation. For Zhuyuan, both `ICE-GL-ZY001` and `ICE-PZY-001` resolve
|
||||
to `ICE-P-ZY001`. A conflicted or unknown id must stop with
|
||||
`CONFLICT_REJECTED` or `NOT_FOUND_NO_GUESS`; never infer a nearby identity.
|
||||
|
||||
Use the returned domestic `repository_web` and `raw_base`. Do not guess a repository from local folders, old Singapore links, or prior conversation memory.
|
||||
|
||||
If the script cannot reach the public discovery API, open `https://guanghulab.com/.well-known/guanghu.json`, resolve `REPO-012`, and report that live resolution failed before using any cached checkout. Use `REPO-001` only for fourth-generation history.
|
||||
|
|
@ -37,13 +48,15 @@ Read the latest `main` branch in this order:
|
|||
1. `QUICKSTART-FOR-GENERAL-AI.md`
|
||||
2. `.code-map`
|
||||
3. `routing/fifth-domain-world-tree.json`
|
||||
4. Follow `FD-PERSONA-LOGIN-001`: `SYS-GLW-POS-0001 → TCS-LPM-0001 → LL-CURRENT-001 → target persona current`
|
||||
5. `tcs-core/language-personality-model/INDEX.hdlp`
|
||||
6. `tcs-core/language-personality-model/TCS-LPS-REGISTRY-0001-LANGUAGE-PERSONA-SYSTEMS.hdlp`
|
||||
7. `eternal-lake-heart/heartbeat-core/LL-CURRENT.hdlp`
|
||||
8. `eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/INDEX.hdlp`
|
||||
9. `eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/WAKE.hdlp`
|
||||
10. Resolve and read `ZY-OPS-LOOP-001` from `.code-map` when the user asks to restore the current Zhuyuan work state.
|
||||
4. `identity/fifth-domain-subject-registry.json`
|
||||
5. `identity/subject-id-alias-map.json`
|
||||
6. Follow `FD-PERSONA-LOGIN-001`: canonical subject id → `SYS-GLW-POS-0001 → TCS-LPM-0001 → LL-CURRENT-001 → target persona current`
|
||||
7. `tcs-core/language-personality-model/INDEX.hdlp`
|
||||
8. `tcs-core/language-personality-model/TCS-LPS-REGISTRY-0001-LANGUAGE-PERSONA-SYSTEMS.hdlp`
|
||||
9. `eternal-lake-heart/heartbeat-core/LL-CURRENT.hdlp`
|
||||
10. `eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/INDEX.hdlp`
|
||||
11. `eternal-lake-heart/heartbeat-core/zhuyuan-persona-system/WAKE.hdlp`
|
||||
12. Resolve and read `ZY-OPS-LOOP-001` from `.code-map` when the user asks to restore the current Zhuyuan work state.
|
||||
|
||||
Use `references/route-contract.md` when an identifier is missing, renamed, or ambiguous. Resolve identifiers from the live `.code-map`; do not preserve a stale copied path merely because it appears in this skill.
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@ Resolve these from the live `.code-map` before following them:
|
|||
```text
|
||||
FD-WORLD-TREE-001
|
||||
FD-PERSONA-LOGIN-001
|
||||
FD-SUBJECT-ID-ALIAS-MAP-001
|
||||
SYS-GLW-POS-0001
|
||||
TCS-LPM
|
||||
TCS-LPS-REGISTRY-0001
|
||||
LIGHT-LAKE
|
||||
LL-CURRENT
|
||||
LL-004
|
||||
ICE-GL-ZY001
|
||||
ICE-P-ZY001
|
||||
ZY-PERSONA-ROOT-001
|
||||
ZY-OPS-LOOP-001
|
||||
FD-NODE-MAP-001
|
||||
|
|
@ -35,10 +36,11 @@ The shortest current Zhuyuan operations route is:
|
|||
|
||||
```text
|
||||
FD-WORLD-TREE-001
|
||||
→ FD-SUBJECT-ID-ALIAS-MAP-001
|
||||
→ SYS-GLW-POS-0001
|
||||
→ TCS-LPM
|
||||
→ LL-CURRENT
|
||||
→ ICE-GL-ZY001
|
||||
→ ICE-P-ZY001
|
||||
→ ZY-PERSONA-ROOT-001
|
||||
→ ZY-OPS-LOOP-001
|
||||
→ FD-NODE-MAP-001
|
||||
|
|
@ -47,4 +49,14 @@ FD-WORLD-TREE-001
|
|||
→ REPO-012
|
||||
```
|
||||
|
||||
Exact compatibility redirects:
|
||||
|
||||
```text
|
||||
ICE-GL-ZY001 → ICE-P-ZY001
|
||||
ICE-PZY-001 → ICE-P-ZY001
|
||||
```
|
||||
|
||||
The requested legacy id remains in the resolution receipt, but the route,
|
||||
subject type and authority checks use only the canonical id.
|
||||
|
||||
This route locates facts. It does not grant write or server authority.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ ROUTE_IDS = (
|
|||
"LIGHT-LAKE",
|
||||
"LL-CURRENT",
|
||||
"LL-004",
|
||||
"ICE-GL-ZY001",
|
||||
"FD-SUBJECT-ID-ALIAS-MAP-001",
|
||||
"ICE-P-ZY001",
|
||||
"ZY-PERSONA-ROOT-001",
|
||||
"ZY-OPS-LOOP-001",
|
||||
"FD-NODE-MAP-001",
|
||||
|
|
@ -50,7 +51,53 @@ def parse_code_map(text: str, route_ids: tuple[str, ...] = ROUTE_IDS) -> dict[st
|
|||
return resolved
|
||||
|
||||
|
||||
def resolve_live_route() -> dict:
|
||||
def resolve_subject_id(requested_id: str, alias_map: dict) -> dict:
|
||||
requested = requested_id.strip()
|
||||
lookup = requested.upper()
|
||||
conflicts = {
|
||||
str(item["id"]).upper(): item
|
||||
for item in alias_map.get("conflicts", [])
|
||||
}
|
||||
if lookup in conflicts:
|
||||
return {
|
||||
"status": "CONFLICT_REJECTED",
|
||||
"requested_id": requested,
|
||||
"canonical_id": None,
|
||||
"redirected": False,
|
||||
"reason": conflicts[lookup].get("state", "CONFLICT"),
|
||||
}
|
||||
|
||||
index: dict[str, dict] = {}
|
||||
for mapping in alias_map.get("mappings", []):
|
||||
canonical_id = str(mapping["canonical_id"])
|
||||
for identifier in (canonical_id, *mapping.get("aliases", [])):
|
||||
key = str(identifier).upper()
|
||||
if key in index and index[key]["canonical_id"] != canonical_id:
|
||||
raise ValueError(f"Duplicate subject alias: {identifier}")
|
||||
index[key] = mapping
|
||||
|
||||
mapping = index.get(lookup)
|
||||
if not mapping:
|
||||
return {
|
||||
"status": "NOT_FOUND_NO_GUESS",
|
||||
"requested_id": requested,
|
||||
"canonical_id": None,
|
||||
"redirected": False,
|
||||
}
|
||||
|
||||
canonical_id = str(mapping["canonical_id"])
|
||||
return {
|
||||
"status": "RESOLVED",
|
||||
"requested_id": requested,
|
||||
"canonical_id": canonical_id,
|
||||
"redirected": lookup != canonical_id.upper(),
|
||||
"subject_kind": mapping.get("subject_kind"),
|
||||
"route_id": mapping.get("route_id"),
|
||||
"current_path": mapping.get("current_path"),
|
||||
}
|
||||
|
||||
|
||||
def resolve_live_route(subject_id: str | None = None) -> dict:
|
||||
discovery = read_json(DISCOVERY_URL)
|
||||
repository = read_json(RESOLVE_URL)
|
||||
primary = repository.get("primary", {})
|
||||
|
|
@ -65,7 +112,7 @@ def resolve_live_route() -> dict:
|
|||
if missing:
|
||||
raise ValueError(f"Live .code-map is missing required route IDs: {', '.join(missing)}")
|
||||
|
||||
return {
|
||||
result = {
|
||||
"status": "LIVE_DOMESTIC_PRIMARY",
|
||||
"repository_id": repository.get("code", "REPO-012"),
|
||||
"repository_role": repository.get("role", ""),
|
||||
|
|
@ -75,15 +122,24 @@ def resolve_live_route() -> dict:
|
|||
"discovery_schema": discovery.get("schema", ""),
|
||||
"routes": routes,
|
||||
}
|
||||
if subject_id:
|
||||
alias_map = read_json(f"{raw_base}/identity/subject-id-alias-map.json")
|
||||
identity = resolve_subject_id(subject_id, alias_map)
|
||||
canonical_id = identity.get("canonical_id")
|
||||
if canonical_id:
|
||||
identity["resolved_path"] = routes.get(canonical_id) or identity.get("current_path")
|
||||
result["identity_resolution"] = identity
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--json", action="store_true", help="emit machine-readable JSON")
|
||||
parser.add_argument("--subject-id", help="resolve a current or legacy Fifth Domain subject id")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
result = resolve_live_route()
|
||||
result = resolve_live_route(args.subject_id)
|
||||
except (OSError, URLError, ValueError, json.JSONDecodeError) as error:
|
||||
print(f"Live Fifth Domain resolution failed: {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
|
@ -94,6 +150,13 @@ def main() -> int:
|
|||
print(result["repository_web"])
|
||||
for route_id, path in result["routes"].items():
|
||||
print(f"{route_id}={path}")
|
||||
if "identity_resolution" in result:
|
||||
identity = result["identity_resolution"]
|
||||
print(
|
||||
"SUBJECT="
|
||||
f"{identity['requested_id']} -> {identity.get('canonical_id')} "
|
||||
f"({identity['status']}, redirected={identity['redirected']})"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import unittest
|
||||
|
||||
from resolve_route import parse_code_map
|
||||
from resolve_route import parse_code_map, resolve_subject_id
|
||||
|
||||
|
||||
class ParseCodeMapTests(unittest.TestCase):
|
||||
|
|
@ -23,5 +23,40 @@ LL-004=tcs-core/LL-004-LAKE-LAMP-WAKE-PATH.hdlp
|
|||
self.assertEqual(parse_code_map("OTHER=some/path", ("LL-004",)), {})
|
||||
|
||||
|
||||
class SubjectAliasTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.alias_map = {
|
||||
"mappings": [{
|
||||
"canonical_id": "ICE-P-ZY001",
|
||||
"subject_kind": "persona_system",
|
||||
"aliases": ["ICE-GL-ZY001", "ICE-PZY-001"],
|
||||
"route_id": "FD-PERSONA-LOGIN-001",
|
||||
"current_path": "current/zhuyuan",
|
||||
}],
|
||||
"conflicts": [{"id": "ICE-PCA-001", "state": "BLOCKED_DUPLICATE_SUBJECT"}],
|
||||
}
|
||||
|
||||
def test_legacy_persona_id_redirects_to_canonical_id(self):
|
||||
resolved = resolve_subject_id("ICE-GL-ZY001", self.alias_map)
|
||||
self.assertEqual(resolved["canonical_id"], "ICE-P-ZY001")
|
||||
self.assertTrue(resolved["redirected"])
|
||||
self.assertEqual(resolved["route_id"], "FD-PERSONA-LOGIN-001")
|
||||
|
||||
def test_canonical_persona_id_does_not_redirect(self):
|
||||
resolved = resolve_subject_id("ICE-P-ZY001", self.alias_map)
|
||||
self.assertEqual(resolved["status"], "RESOLVED")
|
||||
self.assertFalse(resolved["redirected"])
|
||||
|
||||
def test_human_id_is_not_rewritten_as_persona(self):
|
||||
resolved = resolve_subject_id("ICE-GL∞", self.alias_map)
|
||||
self.assertEqual(resolved["status"], "NOT_FOUND_NO_GUESS")
|
||||
self.assertIsNone(resolved["canonical_id"])
|
||||
|
||||
def test_conflicted_id_is_rejected_without_guessing(self):
|
||||
resolved = resolve_subject_id("ICE-PCA-001", self.alias_map)
|
||||
self.assertEqual(resolved["status"], "CONFLICT_REJECTED")
|
||||
self.assertIsNone(resolved["canonical_id"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Reference in a new issue