feat(governance): pin selective upstream import contract
This commit is contained in:
@@ -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",
|
||||
|
||||
+80
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user