feat(governance): pin selective upstream import contract

This commit is contained in:
Jesse_Chen
2026-08-15 12:13:09 +08:00
parent 0fd111d16b
commit 8abd248f49
11 changed files with 1053 additions and 51 deletions
@@ -0,0 +1,34 @@
# PR-0 Upstream Governance Baseline — 2026-08-14
## Scope
Read-only baseline before implementing PR-0 from `Jyotisha_Coding_Agent_Execution_and_Test_Plan_2026-08-14.md`. This PR is limited to upstream provenance, one-way selective-import policy, deterministic manifests, and regression guards. It does not modify consultation, rectification, report runtime, authentication, billing, database, deployment, production, or DNS behavior.
## Repository baseline
- Commercial repository: `root/Jyotisha`
- Branch: `codex/pr0-upstream-governance`
- Base and refreshed `origin/staging`: `0fd111d16b45796086a6c1d0945dbd3de6755d8a`
- Worktree at baseline: clean
- Baseline recorded at: `2026-08-14T02:43:14Z`
## Research input identity
- Repository: `https://github.com/732642856/yinduzhanxing`
- Commit: `5db72537741fcedaa7b5498502d4a31b0f9fc147`
- Git tree object: `16935cb68a6fa1ef72661cfca4650a42c60e9b2c`
- ZIP SHA-256: `07d6b71af311160c544433eb9d51beb5b0a5e4bd0afabd4a7d43d3edb573518d`
- Importer-compatible source tree SHA-256: `18e3122ef73c2776a950bfec128efeb09ac0524ec1d2179390260166b87ca814`
- Root research `SKILL.md` SHA-256: `ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5`
- ZIP versus Git commit comparison: 2,901 files; zero missing, extra, or byte differences
## Baseline checks
| Command | Result | Duration | Notes |
|---|---:|---:|---|
| `.venv/bin/python scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` | pass | about 17s | Python 3.12.12; fragment scan, external adapters, remote visibility, and focused tests passed |
| `.venv/bin/python -m pytest -q tests/test_import_yinduzhanxing.py tests/test_report_orchestrator_reader_contract.py` | 15 passed | 1.68s | Existing importer and report provenance baseline |
## Protected surfaces
PR-0 must not modify the commercial root `SKILL.md`, the birth-time rectification Skill, frontend production code, authentication, identity, billing, payment, subscriptions, entitlements, database migrations/RLS/RPC, deployment workflows, production, or DNS.
@@ -13,9 +13,11 @@
"properties": {
"schema_version": {"const": 1},
"source_repository": {"type": "string", "minLength": 1},
"source_repository_url": {"type": "string", "pattern": "^https://github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"},
"source_commit": {"type": "string", "pattern": "^(unknown|[0-9a-f]{40})$"},
"source_tree_hash": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
"source_mode": {"enum": ["git", "snapshot"]},
"source_skill_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$", "description": "SHA-256 of the research source root SKILL.md."},
"source_mode": {"enum": ["git", "archive", "snapshot"]},
"target_repository": {"type": "string", "minLength": 1},
"target_base_commit": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
"policy_version": {"const": 2},
@@ -36,6 +38,15 @@
},
"operator_review_required": {"type": "boolean"}
},
"allOf": [
{
"if": {"properties": {"source_mode": {"enum": ["git", "archive"]}}},
"then": {
"required": ["source_repository_url", "source_skill_sha256"],
"properties": {"source_commit": {"pattern": "^[0-9a-f]{40}$"}}
}
}
],
"$defs": {
"file": {
"type": "object",
@@ -0,0 +1,80 @@
{
"generated_at": "2026-08-14T01:13:57Z",
"mirror_files": [
{
"license": "MIT",
"source": "SKILL.md",
"source_sha256": "ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5",
"status": "applied",
"target": "references/upstream/yinduzhanxing/SKILL.md",
"target_sha256_after": "ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5",
"target_sha256_before": "1be8beafbd5f4b6df7f95afec39e87033cd70ef6bf582fe7d9f1eb62fb5d00fb"
}
],
"operator_review_required": true,
"policy_version": 2,
"privacy_scan": {
"rejections": [],
"scanned_files": 1,
"status": "pass"
},
"protected_rejections": [],
"schema_version": 1,
"semantic_merge_files": [
{
"diff_summary": "manual semantic merge required: +42/-31 lines",
"path": "SKILL.md",
"source_sha256": "ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5",
"status": "review_required",
"target_sha256": "b0377ac7952f8a8d9433522c50837967939e5f91fbed5042467278520379da49"
},
{
"diff_summary": "manual semantic merge required: +65/-53 lines",
"path": "AGENTS.md",
"source_sha256": "a1ff71472c6312000b8aef3f099d5499c0b27d1fd4f61594ef06a56c48533dd2",
"status": "review_required",
"target_sha256": "d8cf104ff60e627ffaf75031237a37b022b54f7145cafdffb3eacdc634f998f9"
},
{
"diff_summary": "manual semantic merge required: +13/-1 lines",
"path": "references/strict-workflow-router.md",
"source_sha256": "afb83e0b1b290166cf7ecdcabcae8221d94b14138abc223e9cf57d27596f82d4",
"status": "review_required",
"target_sha256": "2dbab0179c6001c3c8b51e425af323f214e5e912afe7840821f6c38c3546c905"
},
{
"diff_summary": "manual semantic merge required: +207/-202 lines",
"path": "scripts/unified_consultation_orchestrator.py",
"source_sha256": "f48ad61455ee344ffc53cda225198511979eea05a4be2ac8ef16ea56f40a4d1a",
"status": "review_required",
"target_sha256": "da87697fb8d9bcafa98b2ba0cf091df38769f936a030b8095288f4c99a5dd928"
},
{
"diff_summary": "manual semantic merge required: +2/-11 lines",
"path": "scripts/report_orchestrator.py",
"source_sha256": "cd67d23cf9df68d200167ac25af8473bfabb1a344a5f0a1e1254de14ba426daf",
"status": "review_required",
"target_sha256": "5fbc2c8df6772597295ae80a5e68709a996ddb9b35724f0820f17f7cb028a201"
},
{
"diff_summary": "manual semantic merge required: +5082/-1638 lines",
"path": "scripts/jyotish_api_server.py",
"source_sha256": "d522d82a453645770df25b63f6ba77e75de4c694b9b6580a0d88b931a2988314",
"status": "review_required",
"target_sha256": "5c2f4de25547a98d16f6f8e84c129f525a1be6359c3a5f53fa6fd2a631411aee"
}
],
"source_commit": "5db72537741fcedaa7b5498502d4a31b0f9fc147",
"source_mode": "archive",
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_skill_sha256": "ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5",
"source_tree_hash": "18e3122ef73c2776a950bfec128efeb09ac0524ec1d2179390260166b87ca814",
"target_base_commit": "0fd111d16b45796086a6c1d0945dbd3de6755d8a",
"target_repository": "root/Jyotisha",
"tests_run": [
"tests/test_import_yinduzhanxing.py",
"tests/test_report_orchestrator_reader_contract.py",
"tests/test_upstream_import_plan.py"
]
}
@@ -2,6 +2,7 @@
"schema_version": 2,
"direction": "research_to_commercial_only",
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"target_repository": "root/Jyotisha",
"reverse_sync": "forbidden",
"modes": {
@@ -21,6 +22,9 @@
"scripts/jyotish_api_server.py"
],
"protected": [
"skills/jyotish-birth-time-rectification/**",
"skills/jyotish-vedic-astrology/**",
"SKILL.md",
"frontend/**",
"deploy/**",
".gitea/**",
@@ -47,6 +51,23 @@
"Only explicit mirror mappings may be copied byte-for-byte.",
"Semantic-merge paths are review inputs and are never overwritten by the importer.",
"Commercial product, identity, billing, database and deployment surfaces are protected.",
"There is no commercial-to-research mode or gate in schema v2."
]
"There is no commercial-to-research mode or gate in schema v2.",
"The only byte-for-byte mirror is upstream SKILL.md into references/upstream/yinduzhanxing/SKILL.md.",
"Commercial root and birth-time-rectification Skills are protected targets and may only be reviewed through separate semantic work.",
"The importer enforces the built-in minimum protected pattern set even when an alternate policy file is supplied.",
"Unbound non-Git snapshots are legacy records; new archive imports fail closed without commit and expected tree SHA-256.",
"A Git source must be a clean repository-root checkout whose origin matches source_repository; Git checkouts cannot be relabeled as archive sources.",
"Manifest generation requires a clean target repository root and is deterministic for fixed source, target base commit, policy and CLI identity inputs.",
"Manifest output must be JSON, cannot overlap the source tree, and may enter the target only through references/cross_project_contract/imports."
],
"source_modes": {
"git": "Use the exact clean repository-root HEAD commit. Explicit commit and expected tree pins, when used, must be supplied together and match.",
"archive": "Requires both an explicit 40-hex source commit and expected importer-compatible source-tree SHA-256; absence or mismatch is rejected.",
"snapshot": "Legacy manifest records only. The v2 importer does not emit new unbound snapshots."
},
"determinism": {
"tree_hash": "SHA-256 over sorted relative path, NUL, file SHA-256 bytes, NUL; .git is excluded and source symlinks are rejected.",
"generated_at": "Git source commit time for git mode; target base commit time for archive mode. Wall-clock time is forbidden.",
"json": "UTF-8, sorted keys, two-space indentation, trailing newline."
}
}
@@ -120,6 +120,7 @@ adapter available 解释为已完成 VedAstro、PyJHora/JHora 或 jyotishganit r
3. **阶段二**:意图识别 → 路由目标宫位(无明确意图→Level 2综合解盘)
4. **阶段二点五**:若 `full-reading` 或网页/API 返回 `ai_prompt_pack`,必须优先读取 `prompt_zh``evidence_snapshot``retrieval_plan` 作为 AI/RAG 主上下文;若没有该字段,再退回传统 JSON 摘要。
4.1 **VedAstro 官方优先级**:用户给出生信息后,网页、Skill、MCP 都必须默认走同一条数据优先级:`VedAstro official snapshot -> local supplemental modules -> local fallback only when official blocked`。用户不需要主动要求“调用 VedAstro”。若 `evidence_snapshot.vedastro_official_full_snapshot.status``ok/partial` 且官方 chart 可用,D1/分盘/官方返回的原始字段以 VedAstro 为主;本地引擎只做补充、交叉检查或官方 blocked 时 fallback。
4.1.1 **Formal Varga 官方证据审计**MCP `strict_workflow` 需要补充 VedAstro 官方形式分盘证据时,可显式传 `official_formal_varga_audit=True`;返回的 `vedastro_official_formal_varga_audit` 只能进入 Evidence Ledger / Technique Audit Table,作为 `evidence_only`,不得宣称本地 Varga 公式真值闭环、不得用官方响应静默覆盖本地算法、不得写入或展示任何 API key。
4. **阶段三**:静态分析10步(宫位→承诺→Yoga→Argala→逆行→NK→Shadbala→AV→Ketu→分盘)
5. **阶段四**:动态推运7步(Dasha→五系统Convergence→Transit→Double Transit→Jaimini→KP→Varshaphala
6. **阶段五**:应期输出(五层验证→时间窗口→Actionable Output+案例检索)
@@ -0,0 +1,233 @@
{
"schema_version": 1,
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_commit": "5db72537741fcedaa7b5498502d4a31b0f9fc147",
"source_tree_sha256": "18e3122ef73c2776a950bfec128efeb09ac0524ec1d2179390260166b87ca814",
"boundary": "PR-0 records selective semantic decisions only. The importer mirrors only the pinned upstream root SKILL.md under references/upstream; none of these research scripts is copied into the commercial runtime by this PR.",
"entries": [
{
"source": "scripts/evidence_maturity.py",
"category": "evidence_assertion_contracts",
"action": "adapt",
"reason": "Reuse the explicit calculated/profile_validated/external_parity/predictively_validated vocabulary and visible partial/blocked states without treating a research receipt as commercial execution proof.",
"targets": [
"scripts/strict_evidence_service.py",
"scripts/high_rigor_closure_gate.py"
],
"tests": [
"tests/test_high_rigor_closure_gate.py",
"tests/test_upstream_import_plan.py"
]
},
{
"source": "scripts/assertion_policy.py",
"category": "evidence_assertion_contracts",
"action": "adapt",
"reason": "Adapt assertion-strength labels so blocked and parameter-sensitive claims remain explicit; server-owned evidence, state and scoring continue to decide what may be asserted.",
"targets": [
"scripts/rectification_policy.py",
"scripts/report_orchestrator.py"
],
"tests": [
"tests/test_rectification_technique_contract.py",
"tests/test_report_orchestrator_reader_contract.py",
"tests/test_upstream_import_plan.py"
]
},
{
"source": "scripts/evidence_labeled_reporting.py",
"category": "evidence_assertion_contracts",
"action": "adapt",
"reason": "Adapt evidence labels and limitation rendering while preserving the commercial reader contract and preventing computed_pending_validation material from being presented as externally verified truth.",
"targets": [
"scripts/report_builder.py",
"scripts/report_orchestrator.py"
],
"tests": [
"tests/test_report_orchestrator_reader_contract.py",
"tests/test_upstream_import_plan.py"
]
},
{
"source": "scripts/calculation_profile_contract.py",
"category": "calculation_contracts",
"action": "adapt",
"reason": "Adapt deterministic calculation-profile identity and privacy-safe hashing to the commercial producer instead of creating a second calculation chain or importing research dependency paths.",
"targets": [
"scripts/domain_calculation_service.py",
"scripts/rectification_three_engine_packet.py"
],
"tests": [
"tests/test_commercial_domain_calculation_contract.py",
"tests/test_rectification_three_engine_packet.py",
"tests/test_upstream_import_plan.py"
]
},
{
"source": "scripts/report_pack_contract.py",
"category": "report_domain_profile_contracts",
"action": "adapt",
"reason": "Adapt typed report-pack sections, provenance and limitations into the existing commercial report contract without replacing its routes, authorization or reader-facing conclusions.",
"targets": [
"scripts/personal_report_contract.py",
"scripts/report_orchestrator.py"
],
"tests": [
"tests/test_personal_report_contract.py",
"tests/test_report_orchestrator_reader_contract.py",
"tests/test_upstream_import_plan.py"
]
},
{
"source": "scripts/domain_profile_builder.py",
"category": "report_domain_profile_contracts",
"action": "adapt",
"reason": "Adapt domain-profile normalization behind the existing commercial domain service; migration, family and annual route selection remains owned by the commercial orchestrator.",
"targets": [
"scripts/domain_calculation_service.py",
"scripts/unified_consultation_orchestrator.py"
],
"tests": [
"tests/test_commercial_domain_calculation_contract.py",
"tests/test_unified_consultation_orchestrator.py",
"tests/test_upstream_import_plan.py"
]
},
{
"source": "scripts/unified_calculation_archive.py",
"category": "research_oracle_assets",
"action": "reference_only",
"reason": "Retain the research archive shape and PL9 observations as pinned reference evidence only. It must not replace the commercial calculation archive, satisfy required rectification layers, or become runtime truth.",
"targets": [
"scripts/high_rigor_closure_gate.py",
"scripts/unified_consultation_orchestrator.py"
],
"tests": [
"tests/test_high_rigor_closure_gate.py",
"tests/test_upstream_import_plan.py"
]
},
{
"source": "scripts/specialized_evidence_packets.py",
"category": "research_oracle_assets",
"action": "adapt",
"reason": "Adapt the raw Jaimini, KP and Tajika packet boundaries as evidence-only structures; each system keeps its own commercial validation gate and cannot vote itself into a deterministic outcome.",
"targets": [
"scripts/high_rigor_closure_gate.py",
"scripts/rectification_three_engine_packet.py"
],
"tests": [
"tests/test_high_rigor_closure_gate.py",
"tests/test_rectification_three_engine_packet.py",
"tests/test_upstream_import_plan.py"
]
},
{
"source": "scripts/timing_evidence_packets.py",
"category": "research_oracle_assets",
"action": "adapt",
"reason": "Adapt raw Dasha/transit packet labeling while retaining complete commercial candidate sensitivity and stability diagnostics; computed timing windows do not establish an event outcome or exact birth minute.",
"targets": [
"scripts/candidate_time_sensitivity_scan.py",
"scripts/high_rigor_closure_gate.py"
],
"tests": [
"tests/test_candidate_time_sensitivity_scan.py",
"tests/test_high_rigor_closure_gate.py",
"tests/test_upstream_import_plan.py"
]
}
],
"formal_varga_policy": {
"action": "reference_only",
"reason": "The upstream Formal Varga official-response audit may appear only as evidence_only in Evidence Ledger or Technique Audit output. It cannot claim local-formula parity, satisfy required layers, overwrite local calculations, or expose an API key.",
"targets": [
"references/upstream/yinduzhanxing/SKILL.md",
"scripts/high_rigor_closure_gate.py",
"scripts/report_orchestrator.py"
],
"tests": [
"tests/test_high_rigor_closure_gate.py",
"tests/test_report_orchestrator_reader_contract.py",
"tests/test_upstream_import_plan.py"
]
},
"rejected_regressions": [
{
"id": "fixed_event_domain_count_threshold",
"action": "reject",
"reason": "A fixed count of 3 events across 2 domains is not sufficient evidence for exact-minute confirmation and cannot bypass server-owned evidence completeness, state or scoring policy.",
"commercial_owner": "scripts/rectification_technique_contract.py",
"tests": [
"tests/test_rectification_technique_contract.py",
"tests/test_upstream_import_plan.py"
]
},
{
"id": "sparse_start_mid_end_rectification_scan",
"action": "reject",
"reason": "Sampling only range start, midpoint and end is not a complete sensitivity or stability gate; the commercial candidate scan and required diagnostic layers remain authoritative.",
"commercial_owner": "scripts/candidate_time_sensitivity_scan.py",
"tests": [
"tests/test_candidate_time_sensitivity_scan.py",
"tests/test_upstream_import_plan.py"
]
},
{
"id": "weaken_three_engine_gate",
"action": "reject",
"reason": "Research prose or a single official response cannot weaken the commercial three-engine evidence gate or convert blocked parity into executed truth.",
"commercial_owner": "scripts/rectification_three_engine_packet.py",
"tests": [
"tests/test_rectification_three_engine_packet.py",
"tests/test_upstream_import_plan.py"
]
},
{
"id": "weaken_required_layers",
"action": "reject",
"reason": "Missing required divisional, timing, strength or functional-benefic layers remain visible and blocked; upstream packets cannot silently satisfy them.",
"commercial_owner": "scripts/high_rigor_closure_gate.py",
"tests": [
"tests/test_high_rigor_closure_gate.py",
"tests/test_upstream_import_plan.py"
]
},
{
"id": "weaken_stability_gate",
"action": "reject",
"reason": "Candidate range or exact-minute claims must retain the commercial stability and sensitivity gates; narrative confidence is not stability evidence.",
"commercial_owner": "scripts/candidate_time_sensitivity_scan.py",
"tests": [
"tests/test_candidate_time_sensitivity_scan.py",
"tests/test_upstream_import_plan.py"
]
},
{
"id": "remove_commercial_routes",
"action": "reject",
"reason": "Existing migration, family and annual Web routes, their fallbacks and user-visible recovery remain commercial-runtime-owned and cannot be deleted or replaced by upstream routing.",
"commercial_owner": "scripts/unified_consultation_orchestrator.py",
"preserved_routes": [
"migration",
"family",
"annual"
],
"tests": [
"tests/test_unified_consultation_orchestrator.py",
"tests/test_upstream_import_plan.py"
]
}
],
"protected_surfaces": [
"SKILL.md",
"skills/jyotish-vedic-astrology/**",
"skills/jyotish-birth-time-rectification/**",
"frontend/**",
"deploy/**",
"authentication",
"billing",
"database"
]
}
@@ -1,11 +1,18 @@
{
"schema_version": 1,
"source_repository": "732642856/yinduzhanxing",
"source_commit": null,
"source_mode": "snapshot",
"source_tree_sha256": "9034e1967032d09c7fbae83fc2205f7e75e8ad482c5f9eba1bf309fe30aef5bb",
"skill_sha256": "1be8beafbd5f4b6df7f95afec39e87033cd70ef6bf582fe7d9f1eb62fb5d00fb",
"archive_file_count": 2901,
"archive_sha256": "07d6b71af311160c544433eb9d51beb5b0a5e4bd0afabd4a7d43d3edb573518d",
"boundary": "Archive content was verified byte-for-byte against all 2,901 non-.git files at the bound commit. Only root SKILL.md is mirrored; commercial and rectification Skills remain protected. skill_sha256 is retained as a legacy alias of source_skill_sha256.",
"import_policy_version": 2,
"imported_at": "2026-08-14T03:16:04Z",
"license": "MIT",
"imported_at": "2026-08-06T00:00:00Z",
"boundary": "The provided source had no usable Git metadata. This manifest does not claim parity with a GitHub commit."
"schema_version": 1,
"skill_sha256": "ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5",
"source_commit": "5db72537741fcedaa7b5498502d4a31b0f9fc147",
"source_committed_at": "2026-08-13T18:03:53Z",
"source_git_tree": "16935cb68a6fa1ef72661cfca4650a42c60e9b2c",
"source_mode": "archive",
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_skill_sha256": "ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5",
"source_tree_sha256": "18e3122ef73c2776a950bfec128efeb09ac0524ec1d2179390260166b87ca814"
}
+268 -22
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env python3
"""Safely import an explicit research allowlist into the commercial repository.
The importer is deliberately one-way and offline. It never writes semantic-merge
or protected paths, and defaults to a dry run. A source without usable Git
metadata is recorded as an auditable snapshot with ``source_commit=unknown``.
The importer is deliberately one-way and offline. It never writes semantic-merge
or protected paths, and defaults to a dry run. Non-Git archives are accepted
only when an operator binds them to an explicit commit and expected source-tree
SHA-256; missing or mismatched provenance fails closed.
"""
from __future__ import annotations
@@ -20,6 +21,7 @@ import tempfile
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
from typing import Any
from urllib.parse import urlsplit
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_POLICY = ROOT / "references/cross_project_contract/sync_policy.v2.json"
@@ -27,6 +29,29 @@ EXIT_OK = 0
EXIT_SEMANTIC_REVIEW = 2
EXIT_POLICY_REJECTED = 3
EXIT_VALIDATION_FAILED = 4
COMMIT_HEX_LENGTH = 40
SHA256_HEX_LENGTH = 64
IMPORT_RECORD_DIR = ROOT / "references/cross_project_contract/imports"
REQUIRED_PROTECTED_PATTERNS = frozenset({
"skills/jyotish-birth-time-rectification/**",
"skills/jyotish-vedic-astrology/**",
"SKILL.md",
"frontend/**",
"deploy/**",
".gitea/**",
".github/**",
"frontend/db/**",
"frontend/supabase/**",
"references/oracle/commercial_skill_truth_overlay.v1.json",
"**/.env",
"**/.env.*",
"**/*payment*",
"**/*billing*",
"**/*subscription*",
"**/*entitlement*",
"**/*admin*",
"**/*service_role*",
})
class ImportRejected(ValueError):
@@ -54,8 +79,14 @@ def _safe_relative(value: str) -> str:
def _resolved_file(root: Path, relative: str, *, must_exist: bool) -> Path:
path = root / _safe_relative(relative)
root_resolved = root.resolve()
safe_relative = _safe_relative(relative)
path = root_resolved / safe_relative
current = root_resolved
for part in PurePosixPath(safe_relative).parts:
current = current / part
if current.is_symlink():
raise ImportRejected(f"symlink_component_rejected:{relative}")
if must_exist and not path.exists():
return path
resolved = path.resolve(strict=must_exist)
@@ -81,12 +112,20 @@ def load_policy(path: Path) -> dict[str, Any]:
raise ImportRejected("reverse_sync_must_be_forbidden")
if "commercial_to_research" in json.dumps(policy, sort_keys=True):
raise ImportRejected("reverse_direction_is_not_expressible")
expected_identity = _repository_identity(str(policy.get("source_repository_url", "")))
if expected_identity is None:
raise ImportRejected("source_repository_url_invalid")
if expected_identity[1] != str(policy.get("source_repository", "")).strip("/").casefold():
raise ImportRejected("source_repository_url_slug_mismatch")
modes = policy.get("modes")
if not isinstance(modes, dict) or set(modes) != {"mirror", "semantic_merge", "protected"}:
raise ImportRejected("policy_modes_invalid")
if not isinstance(modes["mirror"], list) or not isinstance(modes["semantic_merge"], list):
raise ImportRejected("policy_allowlists_invalid")
protected = [_safe_relative(value) if "*" not in value else value.replace("\\", "/") for value in modes["protected"]]
missing_protected = sorted(REQUIRED_PROTECTED_PATTERNS - set(protected))
if missing_protected:
raise ImportRejected(f"required_protected_patterns_missing:{','.join(missing_protected)}")
for mapping in modes["mirror"]:
if not isinstance(mapping, dict) or not {"source", "target", "license"} <= set(mapping):
raise ImportRejected("mirror_mapping_invalid")
@@ -108,12 +147,71 @@ def _git_output(root: Path, *args: str) -> str | None:
return None
def source_identity(source: Path) -> tuple[str, str]:
def _normalized_hex(value: str | None, *, length: int, label: str) -> str | None:
if value is None:
return None
normalized = value.strip().lower()
if len(normalized) != length or any(ch not in "0123456789abcdef" for ch in normalized):
raise ImportRejected(f"{label}_invalid")
return normalized
def source_identity(
source: Path,
*,
source_mode: str = "auto",
source_commit: str | None = None,
expected_tree_sha256: str | None = None,
actual_tree_sha256: str | None = None,
) -> tuple[str, str]:
if source_mode not in {"auto", "git", "archive"}:
raise ImportRejected("source_mode_must_be_auto_git_or_archive")
if source_mode != "archive" and (source_commit is None) != (expected_tree_sha256 is None):
raise ImportRejected("source_commit_and_expected_tree_sha256_must_be_provided_together")
explicit_commit = _normalized_hex(
source_commit, length=COMMIT_HEX_LENGTH, label="source_commit"
)
expected_tree = _normalized_hex(
expected_tree_sha256, length=SHA256_HEX_LENGTH, label="expected_tree_sha256"
)
actual_tree = actual_tree_sha256 or source_tree_hash(source)
inside = _git_output(source, "rev-parse", "--show-toplevel")
commit = _git_output(source, "rev-parse", "HEAD") if inside and Path(inside).resolve() == source.resolve() else None
if commit and len(commit) == 40 and all(ch in "0123456789abcdef" for ch in commit.lower()):
return "git", commit.lower()
return "snapshot", "unknown"
detected_commit = (
_git_output(source, "rev-parse", "HEAD")
if inside and Path(inside).resolve() == source.resolve()
else None
)
detected_commit = _normalized_hex(
detected_commit, length=COMMIT_HEX_LENGTH, label="detected_source_commit"
)
if source_mode == "archive" and detected_commit:
raise ImportRejected("archive_source_must_not_be_git_checkout")
if source_mode != "archive" and detected_commit:
status = _git_output(source, "status", "--porcelain=v1", "--untracked-files=all")
ignored = _git_output(source, "ls-files", "--others", "--ignored", "--exclude-standard")
if status is None or ignored is None:
raise ImportRejected("git_source_status_unavailable")
if status or ignored:
raise ImportRejected("git_source_must_be_clean")
if explicit_commit and explicit_commit != detected_commit:
raise ImportRejected("source_commit_mismatch")
if expected_tree and expected_tree != actual_tree:
raise ImportRejected("source_tree_sha256_mismatch")
return "git", detected_commit
if source_mode == "git":
raise ImportRejected("git_source_metadata_unavailable")
if explicit_commit is None:
raise ImportRejected("archive_source_commit_required")
if expected_tree is None:
raise ImportRejected("archive_expected_tree_sha256_required")
if expected_tree != actual_tree:
raise ImportRejected("source_tree_sha256_mismatch")
return "archive", explicit_commit
def _normalized_remote(root: Path) -> str | None:
@@ -124,6 +222,54 @@ def _normalized_remote(root: Path) -> str | None:
return value.rstrip("/")
def _repository_identity(value: str) -> tuple[str, str] | None:
value = value.strip().removesuffix(".git").rstrip("/")
if not value:
return None
if "://" in value:
parsed = urlsplit(value)
host = (parsed.hostname or "").casefold()
path = parsed.path
elif ":" in value and "@" in value.split(":", 1)[0]:
host = value.split("@", 1)[1].split(":", 1)[0].casefold()
path = value.split(":", 1)[1]
else:
return None
parts = [part for part in path.split("/") if part]
if not host or len(parts) < 2:
return None
return host, "/".join(parts).casefold()
def _remote_repository_identity(root: Path) -> tuple[str, str] | None:
remote = _git_output(root, "remote", "get-url", "origin")
return _repository_identity(remote or "")
def validate_git_source_repository(source: Path, expected_repository: str, expected_url: str) -> None:
expected = expected_repository.strip().removesuffix(".git").strip("/").casefold()
expected_identity = _repository_identity(expected_url)
actual = _remote_repository_identity(source)
if actual is None:
raise ImportRejected("git_source_origin_unavailable")
if expected_identity is None or expected_identity[1] != expected:
raise ImportRejected("source_repository_url_invalid")
if actual != expected_identity:
raise ImportRejected(f"git_source_repository_mismatch:{actual[0]}/{actual[1]}")
def validate_clean_target(target: Path) -> None:
top_level = _git_output(target, "rev-parse", "--show-toplevel")
if not top_level or Path(top_level).resolve() != target.resolve():
raise ImportRejected("target_must_be_git_repository_root")
status = _git_output(target, "status", "--porcelain=v1", "--untracked-files=all")
ignored = _git_output(target, "ls-files", "--others", "--ignored", "--exclude-standard")
if status is None or ignored is None:
raise ImportRejected("target_status_unavailable")
if status or ignored:
raise ImportRejected("target_must_be_clean")
def validate_roots(source: Path, target: Path) -> None:
source = source.expanduser().resolve(strict=True)
target = target.expanduser().resolve(strict=True)
@@ -141,7 +287,11 @@ def source_tree_hash(source: Path) -> str:
digest = hashlib.sha256()
for path in sorted(source.rglob("*"), key=lambda item: item.relative_to(source).as_posix()):
relative = path.relative_to(source).as_posix()
if ".git" in PurePosixPath(relative).parts or path.is_symlink() or not path.is_file():
if ".git" in PurePosixPath(relative).parts:
continue
if path.is_symlink():
raise ImportRejected(f"source_tree_symlink_rejected:{relative}")
if not path.is_file():
continue
digest.update(relative.encode("utf-8"))
digest.update(b"\0")
@@ -150,6 +300,37 @@ def source_tree_hash(source: Path) -> str:
return digest.hexdigest()
def _normalized_commit_timestamp(value: str | None) -> str | None:
if not value:
return None
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError:
return None
if parsed.tzinfo is None:
return None
return parsed.astimezone(UTC).isoformat(timespec="seconds").replace("+00:00", "Z")
def deterministic_generated_at(
source: Path,
*,
source_mode: str,
source_commit: str,
target: Path,
target_commit: str,
) -> str:
timestamp = None
if source_mode == "git":
timestamp = _git_output(source, "show", "-s", "--format=%cI", source_commit)
if timestamp is None:
timestamp = _git_output(target, "show", "-s", "--format=%cI", target_commit)
normalized = _normalized_commit_timestamp(timestamp)
if normalized is None:
raise ImportRejected("deterministic_generated_at_unavailable")
return normalized
def _privacy_rejection(path: Path, relative: str, privacy: dict[str, Any]) -> str | None:
lowered = relative.casefold()
basenames = {str(value).casefold() for value in privacy.get("forbidden_basenames", [])}
@@ -189,17 +370,50 @@ def build_manifest(
policy_path: Path = DEFAULT_POLICY,
target: Path = ROOT,
apply: bool = False,
source_mode: str = "auto",
source_commit: str | None = None,
expected_tree_sha256: str | None = None,
tests_run: list[str] | None = None,
) -> dict[str, Any]:
source = source.expanduser().resolve(strict=True)
target = target.expanduser().resolve(strict=True)
validate_roots(source, target)
policy = load_policy(policy_path)
validate_roots(source, target)
validate_clean_target(target)
protected = list(policy["modes"]["protected"])
privacy = policy.get("privacy") if isinstance(policy.get("privacy"), dict) else {}
source_mode, source_commit = source_identity(source)
target_commit = _git_output(target, "rev-parse", "HEAD")
if not target_commit or len(target_commit) != 40:
actual_tree_sha256 = source_tree_hash(source)
resolved_source_mode, resolved_source_commit = source_identity(
source,
source_mode=source_mode,
source_commit=source_commit,
expected_tree_sha256=expected_tree_sha256,
actual_tree_sha256=actual_tree_sha256,
)
if resolved_source_mode == "git":
validate_git_source_repository(
source,
str(policy["source_repository"]),
str(policy["source_repository_url"]),
)
source_skill_file = _resolved_file(source, "SKILL.md", must_exist=True)
if not source_skill_file.exists():
raise ImportRejected("root_skill_missing")
source_skill_sha256 = sha256_file(source_skill_file)
target_commit = _normalized_hex(
_git_output(target, "rev-parse", "HEAD"),
length=COMMIT_HEX_LENGTH,
label="target_base_commit",
)
if target_commit is None:
raise ImportRejected("target_base_commit_unavailable")
generated_at = deterministic_generated_at(
source,
source_mode=resolved_source_mode,
source_commit=resolved_source_commit,
target=target,
target_commit=target_commit,
)
mirror_files: list[dict[str, Any]] = []
privacy_rejections: list[str] = []
@@ -291,22 +505,41 @@ def build_manifest(
return {
"schema_version": 1,
"source_repository": policy["source_repository"],
"source_commit": source_commit,
"source_tree_hash": source_tree_hash(source),
"source_mode": source_mode,
"source_repository_url": policy["source_repository_url"],
"source_commit": resolved_source_commit,
"source_tree_hash": actual_tree_sha256,
"source_skill_sha256": source_skill_sha256,
"source_mode": resolved_source_mode,
"target_repository": policy["target_repository"],
"target_base_commit": target_commit.lower(),
"target_base_commit": target_commit,
"policy_version": 2,
"generated_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
"generated_at": generated_at,
"mirror_files": mirror_files,
"semantic_merge_files": semantic_files,
"protected_rejections": [],
"tests_run": [],
"tests_run": sorted(set(tests_run or [])),
"privacy_scan": {"status": "pass", "scanned_files": len(mirror_files), "rejections": []},
"operator_review_required": any(row["status"] in {"review_required", "target_missing"} for row in semantic_files),
}
def validate_output_path(path: Path, *, target: Path = ROOT, source: Path | None = None) -> Path:
"""Allow manifests outside the target or inside the dedicated import-record directory only."""
resolved = path.expanduser().resolve(strict=False)
target_resolved = target.expanduser().resolve(strict=True)
if source is not None:
source_resolved = source.expanduser().resolve(strict=True)
if resolved == source_resolved or source_resolved in resolved.parents:
raise ImportRejected("manifest_output_must_not_overlap_source")
if resolved == target_resolved or target_resolved in resolved.parents:
import_root = (target_resolved / IMPORT_RECORD_DIR.relative_to(ROOT)).resolve(strict=False)
if import_root not in resolved.parents:
raise ImportRejected("manifest_output_must_be_external_or_import_record_json")
if resolved.suffix.casefold() != ".json":
raise ImportRejected("manifest_output_must_be_json")
return resolved
def write_json_atomic(path: Path, value: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
data = (json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n").encode("utf-8")
@@ -326,14 +559,27 @@ def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--source", type=Path, required=True)
parser.add_argument("--policy", type=Path, default=DEFAULT_POLICY)
parser.add_argument("--source-mode", choices=("auto", "git", "archive"), default="auto")
parser.add_argument("--source-commit")
parser.add_argument("--expected-tree-sha256")
parser.add_argument("--test-run", action="append", default=[], help="Test path recorded in the manifest; repeatable.")
mode = parser.add_mutually_exclusive_group()
mode.add_argument("--dry-run", action="store_true")
mode.add_argument("--apply", action="store_true")
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args(argv)
try:
manifest = build_manifest(args.source, policy_path=args.policy, apply=args.apply)
write_json_atomic(args.output, manifest)
output = validate_output_path(args.output, source=args.source)
manifest = build_manifest(
args.source,
policy_path=args.policy,
apply=args.apply,
source_mode=args.source_mode,
source_commit=args.source_commit,
expected_tree_sha256=args.expected_tree_sha256,
tests_run=args.test_run,
)
write_json_atomic(output, manifest)
except ImportRejected as error:
print(json.dumps({"status": "rejected", "reason": str(error)}, ensure_ascii=False), file=sys.stderr)
return EXIT_POLICY_REJECTED
+224 -15
View File
@@ -16,6 +16,7 @@ if str(SCRIPTS) not in sys.path:
import import_yinduzhanxing as importer # noqa: E402
POLICY = ROOT / "references/cross_project_contract/sync_policy.v2.json"
ARCHIVE_COMMIT = "5db72537741fcedaa7b5498502d4a31b0f9fc147"
def _git(root: Path, *args: str) -> str:
@@ -47,10 +48,31 @@ def _source(tmp_path: Path) -> Path:
return source
def _policy(tmp_path: Path, *, mirror: list[dict[str, str]] | None = None) -> Path:
def _git_source(tmp_path: Path) -> Path:
source = _source(tmp_path)
subprocess.run(["git", "init", "-q", str(source)], check=True)
subprocess.run(["git", "-C", str(source), "config", "user.email", "tests@example.invalid"], check=True)
subprocess.run(["git", "-C", str(source), "config", "user.name", "Tests"], check=True)
subprocess.run(
["git", "-C", str(source), "remote", "add", "origin", "https://github.com/732642856/yinduzhanxing.git"],
check=True,
)
subprocess.run(["git", "-C", str(source), "add", "."], check=True)
subprocess.run(["git", "-C", str(source), "commit", "-qm", "research base"], check=True)
return source
def _policy(
tmp_path: Path,
*,
mirror: list[dict[str, str]] | None = None,
protected: list[str] | None = None,
) -> Path:
value = json.loads(POLICY.read_text(encoding="utf-8"))
if mirror is not None:
value["modes"]["mirror"] = mirror
if protected is not None:
value["modes"]["protected"] = protected
path = tmp_path / "policy.json"
path.write_text(json.dumps(value), encoding="utf-8")
return path
@@ -60,37 +82,186 @@ def test_v2_policy_is_strictly_one_way_and_has_no_reverse_gate() -> None:
policy = importer.load_policy(POLICY)
assert policy["direction"] == "research_to_commercial_only"
assert policy["reverse_sync"] == "forbidden"
assert policy["source_repository_url"] == "https://github.com/732642856/yinduzhanxing"
assert "commercial_to_research" not in json.dumps(policy, sort_keys=True)
assert policy["modes"]["protected"]
def test_dry_run_writes_no_target_file_and_snapshot_commit_is_unknown(tmp_path: Path) -> None:
def test_policy_cannot_remove_mandatory_commercial_protections(tmp_path: Path) -> None:
weakened = sorted(importer.REQUIRED_PROTECTED_PATTERNS - {"frontend/**"})
policy = _policy(tmp_path, protected=weakened)
with pytest.raises(importer.ImportRejected, match="required_protected_patterns_missing:frontend/\\*\\*"):
importer.load_policy(policy)
def _archive_kwargs(source: Path) -> dict[str, str]:
return {
"source_mode": "archive",
"source_commit": ARCHIVE_COMMIT,
"expected_tree_sha256": importer.source_tree_hash(source),
}
def test_dry_run_writes_no_target_file_and_records_bound_archive_identity(tmp_path: Path) -> None:
source, target = _source(tmp_path), _target(tmp_path)
before = _git(target, "status", "--porcelain=v1")
manifest = importer.build_manifest(source, policy_path=POLICY, target=target)
manifest = importer.build_manifest(source, policy_path=POLICY, target=target, **_archive_kwargs(source))
after = _git(target, "status", "--porcelain=v1")
assert before == after == ""
assert manifest["source_mode"] == "snapshot"
assert manifest["source_commit"] == "unknown"
assert manifest["source_mode"] == "archive"
assert manifest["source_commit"] == ARCHIVE_COMMIT
assert manifest["source_tree_hash"] == importer.source_tree_hash(source)
assert manifest["mirror_files"][0]["status"] == "new"
assert not (target / "references/upstream/yinduzhanxing/SKILL.md").exists()
@pytest.mark.parametrize(
("source_commit", "expected_tree_sha256", "error"),
[
(ARCHIVE_COMMIT, None, "archive_expected_tree_sha256_required"),
(None, "0" * 64, "archive_source_commit_required"),
(ARCHIVE_COMMIT, "0" * 64, "source_tree_sha256_mismatch"),
],
)
def test_archive_identity_requires_commit_and_matching_tree_hash(
tmp_path: Path, source_commit: str | None, expected_tree_sha256: str | None, error: str
) -> None:
source, target = _source(tmp_path), _target(tmp_path)
with pytest.raises(importer.ImportRejected, match=error):
importer.build_manifest(
source,
policy_path=POLICY,
target=target,
source_mode="archive",
source_commit=source_commit,
expected_tree_sha256=expected_tree_sha256,
)
@pytest.mark.parametrize(
("source_commit", "expected_tree_sha256"),
[(ARCHIVE_COMMIT, None), (None, "0" * 64)],
)
def test_explicit_provenance_pins_must_be_provided_together_for_git_sources(
tmp_path: Path, source_commit: str | None, expected_tree_sha256: str | None
) -> None:
source, target = _git_source(tmp_path), _target(tmp_path)
with pytest.raises(importer.ImportRejected, match="source_commit_and_expected_tree_sha256_must_be_provided_together"):
importer.build_manifest(
source,
policy_path=POLICY,
target=target,
source_mode="git",
source_commit=source_commit,
expected_tree_sha256=expected_tree_sha256,
)
def test_unbound_non_git_source_no_longer_emits_unknown_snapshot(tmp_path: Path) -> None:
source, target = _source(tmp_path), _target(tmp_path)
with pytest.raises(importer.ImportRejected, match="archive_source_commit_required"):
importer.build_manifest(source, policy_path=POLICY, target=target)
def test_git_source_must_be_clean_and_is_bound_to_head(tmp_path: Path) -> None:
source, target = _git_source(tmp_path), _target(tmp_path)
manifest = importer.build_manifest(source, policy_path=POLICY, target=target)
assert manifest["source_mode"] == "git"
assert manifest["source_commit"] == _git(source, "rev-parse", "HEAD")
(source / "SKILL.md").write_text("# Dirty tracked research skill\n", encoding="utf-8")
with pytest.raises(importer.ImportRejected, match="git_source_must_be_clean"):
importer.build_manifest(source, policy_path=POLICY, target=target)
subprocess.run(["git", "-C", str(source), "restore", "SKILL.md"], check=True)
(source / "untracked.txt").write_text("not committed\n", encoding="utf-8")
with pytest.raises(importer.ImportRejected, match="git_source_must_be_clean"):
importer.build_manifest(source, policy_path=POLICY, target=target)
def test_git_source_cannot_be_forced_through_archive_mode(tmp_path: Path) -> None:
source, target = _git_source(tmp_path), _target(tmp_path)
with pytest.raises(importer.ImportRejected, match="archive_source_must_not_be_git_checkout"):
importer.build_manifest(source, policy_path=POLICY, target=target, **_archive_kwargs(source))
def test_git_source_origin_must_match_policy_repository(tmp_path: Path) -> None:
source, target = _git_source(tmp_path), _target(tmp_path)
subprocess.run(
["git", "-C", str(source), "remote", "set-url", "origin", "https://github.com/example/not-the-upstream.git"],
check=True,
)
with pytest.raises(importer.ImportRejected, match="git_source_repository_mismatch"):
importer.build_manifest(source, policy_path=POLICY, target=target)
subprocess.run(
["git", "-C", str(source), "remote", "set-url", "origin", "https://evil.example/732642856/yinduzhanxing.git"],
check=True,
)
with pytest.raises(importer.ImportRejected, match="git_source_repository_mismatch"):
importer.build_manifest(source, policy_path=POLICY, target=target)
subprocess.run(
["git", "-C", str(source), "remote", "set-url", "origin", "https://github.com/attacker/732642856/yinduzhanxing.git"],
check=True,
)
with pytest.raises(importer.ImportRejected, match="git_source_repository_mismatch"):
importer.build_manifest(source, policy_path=POLICY, target=target)
def test_manifest_output_cannot_overwrite_commercial_repository_files(tmp_path: Path) -> None:
protected = ROOT / "SKILL.md"
before = protected.read_bytes()
with pytest.raises(importer.ImportRejected, match="manifest_output_must_be_external_or_import_record_json"):
importer.validate_output_path(protected)
assert protected.read_bytes() == before
external = tmp_path / "manifest.json"
assert importer.validate_output_path(external) == external.resolve()
internal = ROOT / "references/cross_project_contract/imports/test-output.json"
assert importer.validate_output_path(internal) == internal.resolve()
source, target = _source(tmp_path), _target(tmp_path)
with pytest.raises(importer.ImportRejected, match="manifest_output_must_not_overlap_source"):
importer.validate_output_path(source / "manifest.json", target=target, source=source)
with pytest.raises(importer.ImportRejected, match="manifest_output_must_be_json"):
importer.validate_output_path(tmp_path / "manifest.txt", target=target, source=source)
def test_fixed_archive_inputs_build_byte_equivalent_manifests(tmp_path: Path) -> None:
source, target = _source(tmp_path), _target(tmp_path)
kwargs = _archive_kwargs(source)
recorded_tests = ["tests/test_upstream_import_plan.py", "tests/test_import_yinduzhanxing.py", "tests/test_import_yinduzhanxing.py"]
first = importer.build_manifest(source, policy_path=POLICY, target=target, tests_run=recorded_tests, **kwargs)
second = importer.build_manifest(source, policy_path=POLICY, target=target, tests_run=list(reversed(recorded_tests)), **kwargs)
assert first == second
assert json.dumps(first, ensure_ascii=False, sort_keys=True) == json.dumps(second, ensure_ascii=False, sort_keys=True)
assert first["source_skill_sha256"] == hashlib.sha256((source / "SKILL.md").read_bytes()).hexdigest()
assert first["tests_run"] == ["tests/test_import_yinduzhanxing.py", "tests/test_upstream_import_plan.py"]
def test_apply_handles_new_update_and_unchanged_with_recomputable_hashes(tmp_path: Path) -> None:
source, target = _source(tmp_path), _target(tmp_path)
destination = target / "references/upstream/yinduzhanxing/SKILL.md"
first = importer.build_manifest(source, policy_path=POLICY, target=target, apply=True)
first = importer.build_manifest(source, policy_path=POLICY, target=target, apply=True, **_archive_kwargs(source))
assert first["mirror_files"][0]["status"] == "applied"
assert destination.read_bytes() == (source / "SKILL.md").read_bytes()
unchanged = importer.build_manifest(source, policy_path=POLICY, target=target)
subprocess.run(["git", "-C", str(target), "add", "references/upstream/yinduzhanxing/SKILL.md"], check=True)
subprocess.run(["git", "-C", str(target), "commit", "-qm", "record mirrored skill"], check=True)
unchanged = importer.build_manifest(source, policy_path=POLICY, target=target, **_archive_kwargs(source))
assert unchanged["mirror_files"][0]["status"] == "unchanged"
assert unchanged["mirror_files"][0]["source_sha256"] == hashlib.sha256(destination.read_bytes()).hexdigest()
(source / "SKILL.md").write_text("# Updated research skill\n", encoding="utf-8")
update = importer.build_manifest(source, policy_path=POLICY, target=target)
update = importer.build_manifest(source, policy_path=POLICY, target=target, **_archive_kwargs(source))
assert update["mirror_files"][0]["status"] == "update"
assert update["mirror_files"][0]["target_sha256_before"] != update["mirror_files"][0]["target_sha256_after"]
@@ -98,9 +269,11 @@ def test_apply_handles_new_update_and_unchanged_with_recomputable_hashes(tmp_pat
def test_semantic_files_never_overwrite_target(tmp_path: Path) -> None:
source, target = _source(tmp_path), _target(tmp_path)
(target / "SKILL.md").write_text("commercial skill\n", encoding="utf-8")
subprocess.run(["git", "-C", str(target), "add", "SKILL.md"], check=True)
subprocess.run(["git", "-C", str(target), "commit", "-qm", "add commercial skill"], check=True)
before = (target / "SKILL.md").read_bytes()
manifest = importer.build_manifest(source, policy_path=POLICY, target=target, apply=True)
manifest = importer.build_manifest(source, policy_path=POLICY, target=target, apply=True, **_archive_kwargs(source))
assert (target / "SKILL.md").read_bytes() == before
row = next(item for item in manifest["semantic_merge_files"] if item["path"] == "SKILL.md")
@@ -112,16 +285,31 @@ def test_protected_mapping_is_rejected_instead_of_silently_skipped(tmp_path: Pat
source, target = _source(tmp_path), _target(tmp_path)
policy = _policy(tmp_path, mirror=[{"source": "SKILL.md", "target": "frontend/src/stolen.ts", "license": "MIT"}])
with pytest.raises(importer.ImportRejected, match="protected_rejected"):
importer.build_manifest(source, policy_path=policy, target=target, apply=True)
importer.build_manifest(source, policy_path=policy, target=target, apply=True, **_archive_kwargs(source))
assert not (target / "frontend/src/stolen.ts").exists()
@pytest.mark.parametrize(
"target_path",
[
"SKILL.md",
"skills/jyotish-vedic-astrology/SKILL.md",
"skills/jyotish-birth-time-rectification/SKILL.md",
"skills/jyotish-birth-time-rectification/references/private.md",
],
)
def test_commercial_skill_surfaces_cannot_be_mirror_targets(tmp_path: Path, target_path: str) -> None:
policy = _policy(tmp_path, mirror=[{"source": "SKILL.md", "target": target_path, "license": "MIT"}])
with pytest.raises(importer.ImportRejected, match="protected_rejected"):
importer.load_policy(policy)
@pytest.mark.parametrize("path", ["../escape", "/absolute/path", "safe/../../escape"])
def test_path_traversal_and_absolute_paths_are_rejected(tmp_path: Path, path: str) -> None:
source, target = _source(tmp_path), _target(tmp_path)
policy = _policy(tmp_path, mirror=[{"source": "SKILL.md", "target": path, "license": "MIT"}])
with pytest.raises(importer.ImportRejected, match="unsafe_path"):
importer.build_manifest(source, policy_path=policy, target=target)
importer.build_manifest(source, policy_path=policy, target=target, **_archive_kwargs(source))
def test_symlink_escape_is_rejected(tmp_path: Path) -> None:
@@ -130,8 +318,29 @@ def test_symlink_escape_is_rejected(tmp_path: Path) -> None:
outside.write_text("outside\n", encoding="utf-8")
(source / "linked.md").symlink_to(outside)
policy = _policy(tmp_path, mirror=[{"source": "linked.md", "target": "references/upstream/linked.md", "license": "MIT"}])
with pytest.raises(importer.ImportRejected, match="symlink_escape|non_regular_or_symlink"):
importer.build_manifest(source, policy_path=policy, target=target)
with pytest.raises(importer.ImportRejected, match="source_tree_symlink_rejected|symlink_escape|non_regular_or_symlink"):
importer.build_manifest(source, policy_path=policy, target=target, **_archive_kwargs(source))
def test_target_parent_symlink_cannot_redirect_mirror_into_protected_surface(tmp_path: Path) -> None:
source, target = _source(tmp_path), _target(tmp_path)
(target / "frontend").mkdir()
(target / "frontend/.keep").write_text("protected\n", encoding="utf-8")
(target / "references/upstream").mkdir(parents=True)
(target / "references/upstream/yinduzhanxing").symlink_to("../../frontend")
subprocess.run(["git", "-C", str(target), "add", "frontend", "references/upstream/yinduzhanxing"], check=True)
subprocess.run(["git", "-C", str(target), "commit", "-qm", "add redirecting symlink"], check=True)
with pytest.raises(importer.ImportRejected, match="symlink_component_rejected"):
importer.build_manifest(source, policy_path=POLICY, target=target, apply=True, **_archive_kwargs(source))
assert not (target / "frontend/SKILL.md").exists()
def test_target_worktree_must_be_clean_for_deterministic_manifest(tmp_path: Path) -> None:
source, target = _source(tmp_path), _target(tmp_path)
(target / "untracked.txt").write_text("dirty target\n", encoding="utf-8")
with pytest.raises(importer.ImportRejected, match="target_must_be_clean"):
importer.build_manifest(source, policy_path=POLICY, target=target, **_archive_kwargs(source))
def test_sensitive_source_is_rejected(tmp_path: Path) -> None:
@@ -139,7 +348,7 @@ def test_sensitive_source_is_rejected(tmp_path: Path) -> None:
(source / ".env").write_text("OPENAI_API_KEY=not-a-real-key\n", encoding="utf-8")
policy = _policy(tmp_path, mirror=[{"source": ".env", "target": "references/upstream/env.txt", "license": "MIT"}])
with pytest.raises(importer.ImportRejected, match="sensitive_filename|sensitive_content"):
importer.build_manifest(source, policy_path=policy, target=target)
importer.build_manifest(source, policy_path=policy, target=target, **_archive_kwargs(source))
def test_overlapping_source_and_target_are_rejected(tmp_path: Path) -> None:
@@ -169,6 +378,6 @@ def test_apply_rolls_back_earlier_files_when_later_replace_fails(tmp_path: Path,
monkeypatch.setattr(importer.os, "replace", fail_second)
with pytest.raises(OSError, match="simulated"):
importer.build_manifest(source, policy_path=policy, target=target, apply=True)
importer.build_manifest(source, policy_path=policy, target=target, apply=True, **_archive_kwargs(source))
assert not (target / "references/upstream/one.md").exists()
assert not (target / "references/upstream/two.md").exists()
@@ -3,6 +3,7 @@ from __future__ import annotations
import hashlib
import json
import re
from pathlib import Path
from scripts.report_orchestrator import render_reader_report
@@ -42,9 +43,11 @@ def test_upstream_skill_snapshot_matches_manifest_and_commercial_root_remains_ro
root_skill = (ROOT / "SKILL.md").read_text(encoding="utf-8")
linked_skill = ROOT / "skills/jyotish-vedic-astrology/SKILL.md"
assert hashlib.sha256(snapshot.read_bytes()).hexdigest() == manifest["skill_sha256"]
assert manifest["source_mode"] == "snapshot"
assert manifest["source_commit"] is None
snapshot_hash = hashlib.sha256(snapshot.read_bytes()).hexdigest()
assert snapshot_hash == manifest.get("source_skill_sha256", manifest["skill_sha256"])
assert manifest["source_mode"] == "archive"
assert re.fullmatch(r"[0-9a-f]{40}", manifest["source_commit"])
assert re.fullmatch(r"[0-9a-f]{64}", manifest["source_tree_sha256"])
assert linked_skill.resolve() == (ROOT / "SKILL.md").resolve()
assert "商业运行时路由(最高优先级)" in root_skill
assert "references/upstream/yinduzhanxing/SKILL.md" in root_skill
+157
View File
@@ -0,0 +1,157 @@
"""PR-0 selective upstream import-plan and provenance contracts."""
from __future__ import annotations
import hashlib
import json
import re
from pathlib import Path
from jsonschema import Draft202012Validator, FormatChecker
ROOT = Path(__file__).resolve().parents[1]
UPSTREAM = ROOT / "references/upstream/yinduzhanxing"
PLAN = UPSTREAM / "import-plan.json"
SOURCE_MANIFEST = UPSTREAM / "source-manifest.json"
IMPORT_SCHEMA = ROOT / "references/cross_project_contract/import_manifest.schema.json"
LEGACY_SNAPSHOT = ROOT / "references/cross_project_contract/imports/snapshot-9034e1967032d09c.json"
PINNED_IMPORT_RECORD = ROOT / "references/cross_project_contract/imports/commit-5db72537741fcedaa7b5498502d4a31b0f9fc147.json"
EXPECTED_SOURCES = {
"scripts/evidence_maturity.py",
"scripts/assertion_policy.py",
"scripts/evidence_labeled_reporting.py",
"scripts/calculation_profile_contract.py",
"scripts/report_pack_contract.py",
"scripts/domain_profile_builder.py",
"scripts/unified_calculation_archive.py",
"scripts/specialized_evidence_packets.py",
"scripts/timing_evidence_packets.py",
}
EXPECTED_CATEGORIES = {
"calculation_contracts",
"evidence_assertion_contracts",
"report_domain_profile_contracts",
"research_oracle_assets",
}
EXPECTED_REJECTIONS = {
"fixed_event_domain_count_threshold",
"sparse_start_mid_end_rectification_scan",
"weaken_three_engine_gate",
"weaken_required_layers",
"weaken_stability_gate",
"remove_commercial_routes",
}
EXPECTED_UPSTREAM_IDENTITY = {
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_commit": "5db72537741fcedaa7b5498502d4a31b0f9fc147",
"source_tree_sha256": "18e3122ef73c2776a950bfec128efeb09ac0524ec1d2179390260166b87ca814",
}
EXPECTED_SOURCE_GIT_TREE = "16935cb68a6fa1ef72661cfca4650a42c60e9b2c"
EXPECTED_ARCHIVE_SHA256 = "07d6b71af311160c544433eb9d51beb5b0a5e4bd0afabd4a7d43d3edb573518d"
EXPECTED_ARCHIVE_FILE_COUNT = 2901
EXPECTED_SOURCE_SKILL_SHA256 = "ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5"
def _load(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def test_import_plan_records_all_nine_upstream_sources_and_semantic_destinations() -> None:
plan = _load(PLAN)
entries = plan["entries"]
assert len(entries) == 9
assert {entry["source"] for entry in entries} == EXPECTED_SOURCES
assert {entry["category"] for entry in entries} == EXPECTED_CATEGORIES
assert [entry["action"] for entry in entries].count("adapt") == 8
assert [entry["action"] for entry in entries].count("reference_only") == 1
for entry in entries:
assert set(("source", "category", "action", "reason", "targets", "tests")) <= set(entry)
assert entry["source"].startswith("scripts/")
assert entry["reason"]
assert entry["targets"] and all(target.startswith(("scripts/", "references/")) for target in entry["targets"])
assert entry["tests"] and all(test.startswith("tests/") for test in entry["tests"])
archive = next(entry for entry in entries if entry["source"] == "scripts/unified_calculation_archive.py")
assert archive["action"] == "reference_only"
assert "runtime truth" in archive["reason"]
assert "none of these research scripts is copied" in plan["boundary"]
def test_rejected_regressions_protect_server_truth_gates_and_commercial_routes() -> None:
plan = _load(PLAN)
rejections = plan["rejected_regressions"]
assert {item["id"] for item in rejections} == EXPECTED_REJECTIONS
assert all(item["action"] == "reject" for item in rejections)
by_id = {item["id"]: item for item in rejections}
fixed_count = by_id["fixed_event_domain_count_threshold"]["reason"].lower()
assert "3 events" in fixed_count and "2 domains" in fixed_count
assert "not sufficient" in fixed_count and "cannot bypass" in fixed_count
sparse_scan = by_id["sparse_start_mid_end_rectification_scan"]["reason"].lower()
assert all(token in sparse_scan for token in ("start", "midpoint", "end"))
assert "not a complete" in sparse_scan
for rejection_id in (
"weaken_three_engine_gate",
"weaken_required_layers",
"weaken_stability_gate",
):
assert by_id[rejection_id]["action"] == "reject"
assert by_id[rejection_id]["commercial_owner"].startswith("scripts/")
routes = by_id["remove_commercial_routes"]
assert routes["preserved_routes"] == ["migration", "family", "annual"]
assert "commercial-runtime-owned" in routes["reason"]
def test_formal_varga_is_evidence_only_and_cannot_close_local_truth() -> None:
policy = _load(PLAN)["formal_varga_policy"]
assert policy["action"] == "reference_only"
reason = policy["reason"].lower()
for token in ("evidence_only", "cannot claim", "cannot", "api key"):
assert token in reason
assert "local-formula parity" in reason
assert "required layers" in reason
def test_source_manifest_pins_valid_archive_commit_tree_and_root_skill_hash() -> None:
manifest = _load(SOURCE_MANIFEST)
plan = _load(PLAN)
assert manifest["source_mode"] == "archive"
assert manifest["import_policy_version"] == 2
assert manifest["imported_at"] != manifest["source_committed_at"]
assert {key: manifest[key] for key in EXPECTED_UPSTREAM_IDENTITY} == EXPECTED_UPSTREAM_IDENTITY
assert {key: plan[key] for key in EXPECTED_UPSTREAM_IDENTITY} == EXPECTED_UPSTREAM_IDENTITY
assert manifest["source_git_tree"] == EXPECTED_SOURCE_GIT_TREE
assert manifest["archive_sha256"] == EXPECTED_ARCHIVE_SHA256
assert manifest["archive_file_count"] == EXPECTED_ARCHIVE_FILE_COUNT
assert manifest["source_committed_at"] == "2026-08-13T18:03:53Z"
assert re.fullmatch(r"[0-9a-f]{40}", manifest["source_commit"])
assert re.fullmatch(r"[0-9a-f]{64}", manifest["source_tree_sha256"])
source_skill_sha256 = manifest.get("source_skill_sha256", manifest["skill_sha256"])
assert re.fullmatch(r"[0-9a-f]{64}", source_skill_sha256)
snapshot_hash = hashlib.sha256((UPSTREAM / "SKILL.md").read_bytes()).hexdigest()
assert source_skill_sha256 == snapshot_hash == EXPECTED_SOURCE_SKILL_SHA256
assert manifest["skill_sha256"] == manifest["source_skill_sha256"]
def test_import_manifest_schema_accepts_archive_and_legacy_snapshot_records() -> None:
schema = _load(IMPORT_SCHEMA)
validator = Draft202012Validator(schema, format_checker=FormatChecker())
legacy = _load(LEGACY_SNAPSHOT)
pinned = _load(PINNED_IMPORT_RECORD)
validator.validate(legacy)
validator.validate(pinned)
invalid_git = dict(pinned, source_mode="git", source_commit="unknown")
assert list(validator.iter_errors(invalid_git))
assert "archive" in schema["properties"]["source_mode"]["enum"]
source_manifest = _load(SOURCE_MANIFEST)
plan = _load(PLAN)
assert pinned["source_repository"] == source_manifest["source_repository"] == plan["source_repository"]
assert pinned["source_repository_url"] == source_manifest["source_repository_url"] == plan["source_repository_url"] == EXPECTED_UPSTREAM_IDENTITY["source_repository_url"]
assert pinned["source_commit"] == source_manifest["source_commit"] == plan["source_commit"]
assert pinned["source_tree_hash"] == source_manifest["source_tree_sha256"] == plan["source_tree_sha256"]
assert pinned["source_skill_sha256"] == source_manifest["source_skill_sha256"] == EXPECTED_SOURCE_SKILL_SHA256