guanghu-ice-heart/skills/codex/guanghu-persona-skill-guard/scripts/resolve_persona_skill.py

206 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""Resolve and enforce registered Guanghu persona skills without granting authority."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
DEFAULT_REGISTRY = Path(__file__).resolve().parents[1] / "references" / "persona-skill-registry.json"
REQUIRED_SKILL_FIELDS = {
"id",
"hldp_skill",
"gls_id",
"title",
"intents",
"preferred_route",
"forbidden_route_markers",
"deprecated_route_markers",
"evidence",
"freshness",
"authorization",
}
def load_registry(path: Path = DEFAULT_REGISTRY) -> dict:
with path.open(encoding="utf-8") as handle:
registry = json.load(handle)
if registry.get("schema") != "guanghu.persona-skill-registry/v1":
raise ValueError("unsupported persona skill registry schema")
if not registry.get("version") or not isinstance(registry.get("skills"), list):
raise ValueError("registry version or skills are missing")
seen_ids: set[str] = set()
seen_stable_numbers: dict[str, str] = {}
for skill in registry["skills"]:
missing = REQUIRED_SKILL_FIELDS.difference(skill)
if missing:
raise ValueError(f"{skill.get('id', '<unknown>')} missing fields: {sorted(missing)}")
skill_id = skill["id"]
if skill_id in seen_ids:
raise ValueError(f"duplicate skill id: {skill_id}")
seen_ids.add(skill_id)
match = re.match(r"^(GHS-\d{3})(?:-|$)", skill_id)
if not match:
raise ValueError(f"invalid stable skill id: {skill_id}")
stable_number = match.group(1)
if stable_number in seen_stable_numbers:
raise ValueError(
"duplicate stable skill number: "
f"{stable_number} used by {seen_stable_numbers[stable_number]} and {skill_id}"
)
seen_stable_numbers[stable_number] = skill_id
return registry
def _normalise(value: str) -> str:
return "".join(value.lower().split())
def _marker_hits(text: str, markers: list[str]) -> list[str]:
normalised = _normalise(text)
return [marker for marker in markers if _normalise(marker) in normalised]
def select_skill(registry: dict, intent: str) -> tuple[dict | None, int]:
normalised = _normalise(intent)
best_skill = None
best_score = 0
for skill in registry["skills"]:
score = sum(max(1, len(_normalise(term))) for term in skill["intents"] if _normalise(term) in normalised)
if score > best_score:
best_skill = skill
best_score = score
return best_skill, best_score
def resolve(registry: dict, intent: str, proposed_route: str = "") -> dict:
global_forbidden = []
global_deprecated = []
global_guard_ids = []
for candidate in registry["skills"]:
if candidate.get("enforcement_scope") != "GLOBAL_PREREQUISITE":
continue
forbidden_hits = _marker_hits(proposed_route, candidate["forbidden_route_markers"])
deprecated_hits = _marker_hits(proposed_route, candidate["deprecated_route_markers"])
if forbidden_hits or deprecated_hits:
global_guard_ids.append(candidate["id"])
global_forbidden.extend(forbidden_hits)
global_deprecated.extend(deprecated_hits)
if global_forbidden or global_deprecated:
return {
"decision": "BLOCK" if global_forbidden else "CORRECT",
"matched_skill": global_guard_ids[0],
"applied_global_guards": global_guard_ids,
"hldp_skill": next(
skill["hldp_skill"]
for skill in registry["skills"]
if skill["id"] == global_guard_ids[0]
),
"gls_id": next(
skill["gls_id"]
for skill in registry["skills"]
if skill["id"] == global_guard_ids[0]
),
"confidence": 1.0,
"preferred_route": next(
skill["preferred_route"]
for skill in registry["skills"]
if skill["id"] == global_guard_ids[0]
),
"forbidden_hits": global_forbidden,
"deprecated_hits": global_deprecated,
"correction": "A global persona-source prerequisite rejected the route before task-specific skill selection.",
"evidence": next(
skill["evidence"]
for skill in registry["skills"]
if skill["id"] == global_guard_ids[0]
),
"freshness": next(
skill["freshness"]
for skill in registry["skills"]
if skill["id"] == global_guard_ids[0]
),
"authorization": next(
skill["authorization"]
for skill in registry["skills"]
if skill["id"] == global_guard_ids[0]
),
"recovery_route": next(
skill.get("recovery_route", [])
for skill in registry["skills"]
if skill["id"] == global_guard_ids[0]
),
"enforcement_level": "PROMOTED_SKILL",
"authority_granted": False,
"registry_version": registry["version"],
}
skill, score = select_skill(registry, intent)
if skill is None:
return {
"decision": "NO_MATCH",
"registry_id": registry["registry_id"],
"registry_version": registry["version"],
"correction": "Resolve the stable number through SYS-GLW-LTH-0001 and current REPO-012 main; do not invent a route.",
"authority_granted": False,
}
forbidden = _marker_hits(proposed_route, skill["forbidden_route_markers"])
deprecated = _marker_hits(proposed_route, skill["deprecated_route_markers"])
if forbidden:
decision = "BLOCK"
correction = "Stop the proposed route and use the registered preferred route after live verification."
elif deprecated:
decision = "CORRECT"
correction = "Replace the deprecated route with the registered preferred route and verify current evidence."
else:
decision = "ALLOW"
correction = "Use the preferred route within the stated authorization boundary."
return {
"decision": decision,
"matched_skill": skill["id"],
"hldp_skill": skill["hldp_skill"],
"gls_id": skill["gls_id"],
"confidence": min(1.0, round(score / 20, 2)),
"preferred_route": skill["preferred_route"],
"forbidden_hits": forbidden,
"deprecated_hits": deprecated,
"correction": correction,
"evidence": skill["evidence"],
"freshness": skill["freshness"],
"authorization": skill["authorization"],
"recovery_route": skill.get("recovery_route", []),
"enforcement_level": skill.get("enforcement_level", "UNSPECIFIED"),
"experience_receipts": skill.get("experience_receipts", []),
"authority_granted": False,
"registry_version": registry["version"],
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--intent", required=True)
parser.add_argument("--proposed-route", default="")
parser.add_argument("--registry", type=Path, default=DEFAULT_REGISTRY)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
try:
result = resolve(load_registry(args.registry), args.intent, args.proposed_route)
except (OSError, ValueError, json.JSONDecodeError) as error:
parser.error(str(error))
if args.json:
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
print(result["decision"])
print(result["correction"])
for step in result.get("preferred_route", []):
print(f"- {step}")
return 0
if __name__ == "__main__":
raise SystemExit(main())