"""Immutable package and policy contract for jyotish-personal-report.""" from __future__ import annotations import hashlib import json from pathlib import Path ROOT = Path(__file__).resolve().parents[1] PACKAGE_ROOT = ROOT / "skills/jyotish-personal-report" VERSION_ROOT = PACKAGE_ROOT / "versions/1.0.0" REGISTRY = ROOT / "skills/skill-package-registry.json" HASH_DOMAIN = b"jyotisha-skill-package-v1\0" def _update_length_prefixed(digest, label: str, value: bytes) -> None: digest.update(f"{label}:{len(value)}\0".encode()) digest.update(value) digest.update(b"\0") def _package_sha256(package_directory: Path) -> str: digest = hashlib.sha256() digest.update(HASH_DOMAIN) files = sorted( (path for path in package_directory.rglob("*") if path.is_file()), key=lambda path: path.relative_to(package_directory).as_posix(), ) assert files for path in files: assert not path.is_symlink() relative = path.relative_to(package_directory).as_posix().encode("utf-8") executable = b"1" if path.stat().st_mode & 0o111 else b"0" _update_length_prefixed(digest, "path", relative) _update_length_prefixed(digest, "executable", executable) _update_length_prefixed(digest, "bytes", path.read_bytes()) return digest.hexdigest() def test_personal_report_skill_is_versioned_and_registry_hash_matches_package_bytes() -> None: registry = json.loads(REGISTRY.read_text(encoding="utf-8")) matches = [ package for package in registry["packages"] if package["name"] == "jyotish-personal-report" and package["status"] == "active" ] assert matches == [{ "name": "jyotish-personal-report", "version": "1.0.0", "sha256": _package_sha256(VERSION_ROOT), "sourceCommit": None, "packagePath": "skills/jyotish-personal-report/versions/1.0.0", "status": "active", }] assert (PACKAGE_ROOT / "SKILL.md").read_bytes() == (VERSION_ROOT / "SKILL.md").read_bytes() assert (PACKAGE_ROOT / "references/report-contract.md").read_bytes() == ( VERSION_ROOT / "references/report-contract.md" ).read_bytes() assert not (VERSION_ROOT / "scripts").exists() def test_personal_report_skill_encodes_evidence_only_two_stage_and_safety_boundaries() -> None: skill = (VERSION_ROOT / "SKILL.md").read_text(encoding="utf-8") contract = (VERSION_ROOT / "references/report-contract.md").read_text(encoding="utf-8") combined = f"{skill}\n{contract}" for required in ( "ReportEvidenceBundleV2", "claimCards", "blockedSections", "planner → writer", "concise", "standard", "deep", "research", "requestedThemes", "聊天历史", "工具轨迹", "内部文件路径", "禁止 HTML", "不得计算、外推、缩窄、扩展或编造日期", "禁止医疗诊断", "禁止确定性财务承诺", ): assert required in combined assert "每个 requested theme" in skill assert "恰好" in skill assert "不得从宫位、星座、度数、分盘或 Dasha 自行推导含义" in skill assert "research` 不代表可以联网或补证" in skill