Files
Jyotisha/tests/test_jyotish_personal_report_skill_package.py
2026-08-15 05:10:37 +08:00

90 lines
3.2 KiB
Python

"""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