diff --git a/docs/research/pre_work_error_ledger.md b/docs/research/pre_work_error_ledger.md index 93ffc8c2..77f3f432 100644 --- a/docs/research/pre_work_error_ledger.md +++ b/docs/research/pre_work_error_ledger.md @@ -61,6 +61,7 @@ For large architecture or release work, also read: | ERR-028 | Active birth-time rectification can stop at question generation and never narrow candidate clusters from user answers. | mitigated 2026-07-09 | `active_rectification_questions.score_answers()` must turn A/B/C/D answers into cluster rankings, next-round questions, and an explicit boundary that final rectification still needs candidate chart differences. | | ERR-029 | Basic git and premium cloud-drive skill packages can blur contents, privacy exclusions, and external-engine promises. | mitigated 2026-07-09 | `scripts/skill_release_manifest.py` must define edition contents, excluded private material, acceptance commands, and external-engine runtime boundaries before packaging. | | ERR-030 | Release packaging can misread non-ASCII tracked filenames when parsing quoted `git ls-files` output. | mitigated 2026-07-09 | Package builders must use `git ls-files -z` and decode NUL-separated paths before writing zip archives. | +| ERR-031 | Premium skill zip can ship without user install prompts or replay schemas, leaving users and future oracle imports without a contract. | mitigated 2026-07-09 | `skill_release_package.py` must inject `INSTALL.md` and `USER_PROMPTS.md`; replay contracts must live in `references/real_case_calibration/` and `references/oracle/`. | ## Fragment Sweep Command Set diff --git a/references/oracle/three_engine_parity_replay.schema.json b/references/oracle/three_engine_parity_replay.schema.json new file mode 100644 index 00000000..03f76815 --- /dev/null +++ b/references/oracle/three_engine_parity_replay.schema.json @@ -0,0 +1,115 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Three Engine Same-Chart Parity Replay", + "type": "object", + "required": [ + "case_id", + "birth_data_policy", + "engines", + "comparison_rows", + "status" + ], + "properties": { + "case_id": { + "type": "string" + }, + "birth_data_policy": { + "type": "string", + "enum": [ + "public_case_only", + "anonymized_private_case", + "operator_local_only" + ] + }, + "engines": { + "type": "object", + "properties": { + "VedAstro": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "official_raw_response_path": { + "type": "string" + }, + "artifact_hash": { + "type": "string" + } + } + }, + "PyJHora_JHora": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "raw_output_path": { + "type": "string" + }, + "settings": { + "type": "object" + } + } + }, + "jyotishganit": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "raw_output_path": { + "type": "string" + } + } + } + } + }, + "comparison_rows": { + "type": "array", + "items": { + "type": "object", + "required": [ + "section", + "field", + "local_value", + "oracle_values", + "status" + ], + "properties": { + "section": { + "type": "string" + }, + "field": { + "type": "string" + }, + "local_value": {}, + "oracle_values": { + "type": "object" + }, + "status": { + "type": "string", + "enum": [ + "match", + "mismatch", + "blocked", + "not_comparable" + ] + } + } + } + }, + "status": { + "type": "string", + "enum": [ + "not_started", + "blocked", + "partial", + "complete" + ] + }, + "blocked_reason": { + "type": "string" + } + } +} diff --git a/references/real_case_calibration/catalog.schema.json b/references/real_case_calibration/catalog.schema.json new file mode 100644 index 00000000..cc579bda --- /dev/null +++ b/references/real_case_calibration/catalog.schema.json @@ -0,0 +1,140 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Real Case Calibration Catalog", + "type": "object", + "required": [ + "case_id", + "source", + "chart_signature", + "event_outcomes", + "similarity", + "replay" + ], + "properties": { + "case_id": { + "type": "string" + }, + "source": { + "type": "object", + "required": [ + "url", + "source_grade", + "license_or_quote_boundary" + ], + "properties": { + "url": { + "type": "string" + }, + "source_grade": { + "type": "string", + "enum": [ + "primary", + "verified_secondary", + "forum_claim", + "unverified" + ] + }, + "license_or_quote_boundary": { + "type": "string" + } + } + }, + "chart_signature": { + "type": "object", + "properties": { + "lagna": { + "type": "string" + }, + "moon_sign": { + "type": "string" + }, + "d9_lagna": { + "type": "string" + }, + "ul": { + "type": "string" + }, + "a7": { + "type": "string" + }, + "a10": { + "type": "string" + }, + "notable_yogas": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "event_outcomes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "event_type", + "event_date", + "outcome" + ], + "properties": { + "event_type": { + "type": "string" + }, + "event_date": { + "type": "string" + }, + "outcome": { + "type": "string" + }, + "source_excerpt_note": { + "type": "string" + } + } + } + }, + "similarity": { + "type": "object", + "properties": { + "score": { + "type": "number" + }, + "matching_factors": { + "type": "array", + "items": { + "type": "string" + } + }, + "dissimilar_factors": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "replay": { + "type": "object", + "properties": { + "outcome_replay_status": { + "type": "string", + "enum": [ + "not_started", + "blocked", + "partial", + "complete" + ] + }, + "conflict_notes": { + "type": "array", + "items": { + "type": "string" + } + }, + "do_not_use_for_prediction": { + "type": "boolean" + } + } + } + } +} diff --git a/scripts/skill_release_package.py b/scripts/skill_release_package.py index 96c67ada..bc8c3a81 100644 --- a/scripts/skill_release_package.py +++ b/scripts/skill_release_package.py @@ -21,6 +21,34 @@ except ModuleNotFoundError: # pragma: no cover - direct script execution ROOT = Path(__file__).resolve().parents[1] SKIP_PREFIXES = ("scratch/", "references/open_source_sources/", ".git/", "__pycache__/") SKIP_NAMES = {".env", ".env.local"} +GENERATED_DOCS = { + "INSTALL.md": """# Jyotish Skill Install + +1. Unzip this package into a local folder. +2. Run `python3 scripts/public_release_privacy_scan.py`. +3. Run `python3 scripts/user_invocation_acceptance_check.py`. +4. If the check passes, load this folder as a Codex skill/plugin or call `mcp_server.py`. +5. VedAstro cloud evidence is optional. Without `official_raw_response`, reports must say `official_blocked` or `local_fallback`. + +Do not add private birth data, API keys, or desktop oracle screenshots to this package. +""", + "USER_PROMPTS.md": """# User Prompts + +## User Has No Question + +请根据我的出生信息先运行统一主链,生成 evidence_packet、guided_topics 和 Technique Audit Table。 +不要反问我想看什么;请先给出 3-5 个最值得继续看的方向,并附可直接复制的问题。 + +## High-Rigor Reading + +请使用 strict_workflow,并在输出中标明 VedAstro / PyJHora-JHora / jyotishganit / Real Case Calibration 的状态。 +如果没有 VedAstro official_raw_response,请标记 official_blocked 或 local_fallback。 + +## Birth-Time Rectification + +请使用主动问询式校时:先生成候选时间扫描和选择题,我只回答 A/B/C/D。 +""", +} def _git_files() -> list[str]: @@ -59,6 +87,7 @@ def build_package_plan(edition: str = "premium_cloud_drive") -> dict[str, Any]: "privacy_scan_status": privacy["status"], "file_count": len(files), "files": files, + "generated_files": sorted(GENERATED_DOCS), "boundary": "Dry-run plan only; use --write-zip to create a local zip, then upload manually if desired.", } @@ -71,6 +100,8 @@ def write_zip(edition: str, output: Path) -> dict[str, Any]: with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED) as archive: for rel in plan["files"]: archive.write(ROOT / rel, rel) + for rel, text in GENERATED_DOCS.items(): + archive.writestr(rel, text) return {**plan, "mode": "write_zip", "zip_path": str(output)} diff --git a/tests/test_calibration_replay_schemas.py b/tests/test_calibration_replay_schemas.py new file mode 100644 index 00000000..a078315f --- /dev/null +++ b/tests/test_calibration_replay_schemas.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import json +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def _load(path: str) -> dict: + return json.loads((ROOT / path).read_text(encoding="utf-8")) + + +def test_real_case_calibration_schema_defines_replay_contract() -> None: + schema = _load("references/real_case_calibration/catalog.schema.json") + props = schema["properties"] + + assert schema["title"] == "Real Case Calibration Catalog" + assert {"case_id", "source", "chart_signature", "event_outcomes", "similarity", "replay"} <= set(props) + assert props["source"]["properties"]["source_grade"]["enum"] == ["primary", "verified_secondary", "forum_claim", "unverified"] + assert "outcome_replay_status" in props["replay"]["properties"] + assert "do_not_use_for_prediction" in props["replay"]["properties"] + + +def test_three_engine_parity_replay_schema_defines_raw_oracle_slots() -> None: + schema = _load("references/oracle/three_engine_parity_replay.schema.json") + engines = schema["properties"]["engines"]["properties"] + + assert schema["title"] == "Three Engine Same-Chart Parity Replay" + assert {"VedAstro", "PyJHora_JHora", "jyotishganit"} <= set(engines) + assert "official_raw_response_path" in engines["VedAstro"]["properties"] + assert "raw_output_path" in engines["PyJHora_JHora"]["properties"] + assert "comparison_rows" in schema["properties"] + assert "blocked_reason" in schema["properties"] diff --git a/tests/test_skill_release_package.py b/tests/test_skill_release_package.py index 59df2390..ed6582e7 100644 --- a/tests/test_skill_release_package.py +++ b/tests/test_skill_release_package.py @@ -29,4 +29,12 @@ def test_skill_release_package_can_write_zip(tmp_path) -> None: names = set(archive.namelist()) assert "SKILL.md" in names assert "scripts/skill_release_manifest.py" in names + assert "INSTALL.md" in names + assert "USER_PROMPTS.md" in names assert ".env.local" not in names + with zipfile.ZipFile(target) as archive: + install = archive.read("INSTALL.md").decode("utf-8") + prompts = archive.read("USER_PROMPTS.md").decode("utf-8") + assert "python3 scripts/user_invocation_acceptance_check.py" in install + assert "guided_topics" in prompts + assert "official_blocked" in prompts