90 lines
3.2 KiB
Python
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
|