From a3d2e0acf972541db3b592c4f98448c88fe9e1c0 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Thu, 2 Jul 2026 15:09:22 +0800 Subject: [PATCH] Enforce interpretation source content contracts --- jyotish-app/ai-chat.js | 11 + mcp_server.py | 140 +++++++++++- progress.md | 9 + scripts/jyotish_api_server.py | 66 ++++++ scripts/jyotish_engine.py | 44 ++++ tests/test_cli_smoke.py | 19 ++ tests/test_frontend_productization.py | 3 + ...st_interpretation_source_inventory_gate.py | 8 + ...terpretation_source_next_phase_contract.py | 201 ++++++++++++++++++ 9 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 tests/test_interpretation_source_next_phase_contract.py diff --git a/jyotish-app/ai-chat.js b/jyotish-app/ai-chat.js index 8f53bbc7..895d5195 100644 --- a/jyotish-app/ai-chat.js +++ b/jyotish-app/ai-chat.js @@ -313,6 +313,7 @@ function buildChartContext(cd, guidedTopicContext = null) { const primaryRoute = officialSnapshot.strict_workflow_primary_route || Object.keys(strictContracts)[0] || ''; const topReaderContract = primaryRoute ? (strictContracts[primaryRoute] || {}) : {}; const adjudicationStages = topReaderContract.adjudication_stages || {}; + const predictionBoundaryContract = topReaderContract.prediction_boundary_contract || cd.ai_prompt_pack.evidence_snapshot.prediction_boundary_contract || {}; const multiReferenceSummary = topReaderContract.multi_reference_reading_summary || {}; const techniqueAuditSummary = topReaderContract.technique_audit_summary || {}; const runtimePlannerBoundary = runtimePlanner && typeof runtimePlanner === 'object' && Object.keys(runtimePlanner).length @@ -341,6 +342,14 @@ function buildChartContext(cd, guidedTopicContext = null) { `main_conflicts=${(topReaderContract.main_conflicts || []).map(item => item?.type).filter(Boolean).join('/') || 'none'}`, ].join('\n') : ''; + const predictionBoundary = predictionBoundaryContract && typeof predictionBoundaryContract === 'object' && Object.keys(predictionBoundaryContract).length + ? [ + '【Prediction Boundary Contract】', + `source_refs=${(predictionBoundaryContract.source_refs || []).join(' / ') || '-'}`, + `promise=${predictionBoundaryContract.promise?.status || adjudicationStages.promise?.status || 'missing'} · activation=${predictionBoundaryContract.activation?.status || adjudicationStages.activation?.status || 'missing'} · manifestation=${predictionBoundaryContract.manifestation?.status || adjudicationStages.manifestation?.status || 'missing'} · label=${predictionBoundaryContract.label?.value || topReaderContract.dominant_label || '-'}`, + `mevg=${predictionBoundaryContract.confidence_boundary?.mevg_status || 'blocked'} · real_case=${predictionBoundaryContract.confidence_boundary?.real_case_calibration_status || 'blocked'} · policy=${predictionBoundaryContract.confidence_boundary?.unverified_claim_policy || 'downgrade_or_block'}`, + ].join('\n') + : ''; const vedastroBoundary = vedastroOverview && typeof vedastroOverview === 'object' ? [ '【VedAstro Overview Boundary】', @@ -370,6 +379,8 @@ function buildChartContext(cd, guidedTopicContext = null) { '', topReaderBoundary, '', + predictionBoundary, + '', guidedTopicBoundary, '', vedastroBoundary, diff --git a/mcp_server.py b/mcp_server.py index ca7e0298..d6894b30 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -128,6 +128,37 @@ CORE_RULE_SOURCE_REFS = [ "references/transit-multi-reference-guide.md", ] +PROMOTE_BATCH2_TOPIC_SOURCE_REFS = [ + "references/vimshottari_dasha_guide.md", + "references/pratyantar-calculation-guide.md", + "references/divisional-chart-deep-reading.md", + "references/shadbala-complete-methodology.md", + "references/ashtakavarga-complete-system.md", + "references/tajika-yoga-complete-guide.md", + "references/jaimini-complete-system.md", + "references/kp-astrology-complete-system.md", + "references/argala-complete-guide.md", + "references/badhaka-obstacle-planet-guide.md", + "references/condition-dasha-complete.md", +] + +REFERENCE_ONLY_CONFLICT_SOURCE_REFS = [ + "references/dasa-convergence-methodology.md", + "references/multi-dasha-convergence-protocol.md", + "references/yoga-strength-scoring-system.md", +] + +BLOCKED_NON_RUNTIME_SOURCE_REFS = [ + "references/varga-system-quick-reference.md", + "references/yoga-list-chinese.md", + "references/analysis-full-reading-v4.0.md", + "references/analysis-full-reading-v1.8-review.md", + "references/audit-skill-full-test-2026-05-04.md", + "references/feature-gap-matrix-2026.md", + "references/kp-practical-event-timing.md", + "references/consultation-case-library.md", +] + def _build_interpretation_source_inventory(source_refs: List[str]) -> Dict[str, Any]: primary_truth = [ @@ -166,6 +197,8 @@ def _build_interpretation_source_inventory(source_refs: List[str]) -> Dict[str, "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/p1_p12.md", "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/house_framework.md", *CORE_RULE_SOURCE_REFS, + *PROMOTE_BATCH2_TOPIC_SOURCE_REFS, + *REFERENCE_ONLY_CONFLICT_SOURCE_REFS, *frontend_interpretation, *qa_governance, *reader_validation, @@ -223,6 +256,28 @@ def _build_interpretation_source_inventory(source_refs: List[str]) -> Dict[str, "missing_refs": [path for path in CORE_RULE_SOURCE_REFS if not _repo_relative_exists(path)], "boundary": "First promoted core references; visible to strict workflows but still subject to conflict arbitration.", }, + "promote_batch2_topic_sources": { + "status": "available", + "promotion_status": "reference_layer_candidate", + "promotion_batch": "priority1_batch1_remaining_promote_11", + "source_refs": _existing_paths(PROMOTE_BATCH2_TOPIC_SOURCE_REFS), + "missing_refs": [path for path in PROMOTE_BATCH2_TOPIC_SOURCE_REFS if not _repo_relative_exists(path)], + "boundary": "Topic-specific promoted references; visible as supporting layers, not all-at-once primary truth.", + }, + "reference_only_conflict_sources": { + "status": "available", + "promotion_status": "reference_only", + "source_refs": _existing_paths(REFERENCE_ONLY_CONFLICT_SOURCE_REFS), + "missing_refs": [path for path in REFERENCE_ONLY_CONFLICT_SOURCE_REFS if not _repo_relative_exists(path)], + "boundary": "Useful conflict/reference material; must not outrank primary rule sources or tested contracts.", + }, + "blocked_non_runtime_sources": { + "status": "blocked", + "promotion_status": "not_truth_source", + "source_refs": _existing_paths(BLOCKED_NON_RUNTIME_SOURCE_REFS), + "missing_refs": [path for path in BLOCKED_NON_RUNTIME_SOURCE_REFS if not _repo_relative_exists(path)], + "boundary": "Duplicate, obsolete, or quarantined files; never expose through runtime source_refs.", + }, "quarantined_drafts": { "status": "quarantined", "promotion_status": "not_truth_source", @@ -233,8 +288,10 @@ def _build_interpretation_source_inventory(source_refs: List[str]) -> Dict[str, } missing_refs = [path for layer in layers.values() for path in layer["missing_refs"]] quarantined_refs = set(layers["quarantined_drafts"]["source_refs"]) + blocked_non_runtime_refs = set(layers["blocked_non_runtime_sources"]["source_refs"]) promoted_quarantined = sorted(quarantined_refs.intersection(source_refs)) - status = "used" if not missing_refs and not promoted_quarantined else "partial" + promoted_blocked_non_runtime = sorted(blocked_non_runtime_refs.intersection(source_refs)) + status = "used" if not missing_refs and not promoted_quarantined and not promoted_blocked_non_runtime else "partial" return { "status": status, "source": "repo_interpretation_source_inventory_v1", @@ -243,11 +300,14 @@ def _build_interpretation_source_inventory(source_refs: List[str]) -> Dict[str, "primary_truth_count": len(layers["primary_truth"]["source_refs"]), "reference_layer_count": len(_existing_paths(reference_layer)), "quarantined_draft_count": len(layers["quarantined_drafts"]["source_refs"]), + "blocked_non_runtime_count": len(layers["blocked_non_runtime_sources"]["source_refs"]), "missing_ref_count": len(missing_refs), "promoted_quarantined_count": len(promoted_quarantined), + "promoted_blocked_non_runtime_count": len(promoted_blocked_non_runtime), }, "missing_refs": missing_refs, "promoted_quarantined_refs": promoted_quarantined, + "promoted_blocked_non_runtime_refs": promoted_blocked_non_runtime, "boundary": "Inventory is explicit and conservative; local drafts are indexed but not treated as truth sources.", } @@ -267,6 +327,9 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: mevg_path = "references/mandatory-verification-gate-protocol.md" real_case_checklist_path = "references/real-reading-quality-checklist.md" core_rule_paths = CORE_RULE_SOURCE_REFS + promote_batch2_paths = PROMOTE_BATCH2_TOPIC_SOURCE_REFS + reference_only_paths = REFERENCE_ONLY_CONFLICT_SOURCE_REFS + blocked_non_runtime_paths = BLOCKED_NON_RUNTIME_SOURCE_REFS frontend_interpretation_paths = [ "jyotish-app/interpretation.js", "jyotish-app/analysis-deep.js", @@ -308,6 +371,8 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: mevg_path, real_case_checklist_path, *core_rule_paths, + *promote_batch2_paths, + *reference_only_paths, *frontend_interpretation_paths, *planet_house_paths, qa_rules_path, @@ -337,6 +402,8 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: "mevg_mandatory_external_verification", "real_case_quality_checklist", "priority1_batch1_core_rule_sources", + "priority1_batch1_promote_topic_sources", + "reference_only_conflict_sources", "qa_governance_rules", "reader_validation_rules", "frontend_interpretation_layer", @@ -354,6 +421,25 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: "promotion_batch": "priority1_batch1_core5", "boundary": "Audit-promoted core references; use as visible rule sources with conflict arbitration.", }, + "promote_batch2_topic_layer": { + "status": "available" if all(_repo_relative_exists(path) for path in promote_batch2_paths) else "partial", + "source_refs": promote_batch2_paths, + "promotion_status": "reference_layer_candidate", + "promotion_batch": "priority1_batch1_remaining_promote_11", + "boundary": "Topic-specific promoted references; wire by domain and do not flatten into primary truth.", + }, + "reference_only_conflict_layer": { + "status": "available" if all(_repo_relative_exists(path) for path in reference_only_paths) else "partial", + "source_refs": reference_only_paths, + "promotion_status": "reference_only", + "boundary": "Reference-only sources can explain conflicts but cannot override primary rule sources.", + }, + "blocked_non_runtime_layer": { + "status": "blocked", + "source_refs": blocked_non_runtime_paths, + "promotion_status": "not_truth_source", + "boundary": "Duplicate, obsolete, and quarantined files are deliberately excluded from runtime source_refs.", + }, "frontend_interpretation_layer": { "status": "available" if all(_repo_relative_exists(path) for path in frontend_interpretation_paths) else "partial", "source_refs": frontend_interpretation_paths, @@ -2553,6 +2639,16 @@ def _build_technique_audit_summary(route: str, strict: Dict[str, Any]) -> Dict[s if isinstance(interpretation_source_pack.get("core_rule_source_layer"), dict) else [] ), + "promote_batch2_source_refs": ( + interpretation_source_pack.get("promote_batch2_topic_layer", {}).get("source_refs") + if isinstance(interpretation_source_pack.get("promote_batch2_topic_layer"), dict) + else [] + ), + "reference_only_source_refs": ( + interpretation_source_pack.get("reference_only_conflict_layer", {}).get("source_refs") + if isinstance(interpretation_source_pack.get("reference_only_conflict_layer"), dict) + else [] + ), "missing_refs": interpretation_source_pack.get("missing_refs") or [], "effect_on_confidence": ( "uses existing BPHS/Raman/frontend/template source layers; missing refs lower confidence" @@ -2646,6 +2742,47 @@ def _build_adjudication_stages(route: str, present: Dict[str, Any], event_judgem } +def _build_prediction_boundary_contract(route: str, strict: Dict[str, Any]) -> Dict[str, Any]: + stages = strict.get("adjudication_stages") if isinstance(strict.get("adjudication_stages"), dict) else {} + audit = strict.get("technique_audit_summary") if isinstance(strict.get("technique_audit_summary"), dict) else {} + mevg = audit.get("mevg_global_web_evidence") if isinstance(audit.get("mevg_global_web_evidence"), dict) else {} + real_case = audit.get("real_case_calibration") if isinstance(audit.get("real_case_calibration"), dict) else {} + interpretation_source_pack = ( + audit.get("interpretation_source_pack") + if isinstance(audit.get("interpretation_source_pack"), dict) + else {} + ) + return { + "route": route, + "status": "used", + "source_refs": interpretation_source_pack.get("core_rule_source_refs") or CORE_RULE_SOURCE_REFS, + "event_judgment_skeleton": { + "status": "used", + "required_sections": ["promise", "activation", "manifestation", "label"], + "source_ref": "references/event_judgment_skeleton.md", + }, + "promise": stages.get("promise") or {}, + "activation": stages.get("activation") or {}, + "manifestation": stages.get("manifestation") or {}, + "label": stages.get("label") or {}, + "confidence_boundary": { + "status": "used", + "source_ref": "references/prediction-boundary-protocol.md", + "confidence_cap": strict.get("confidence_cap"), + "blocked": bool(strict.get("blocked")), + "mevg_status": mevg.get("status") or "blocked", + "real_case_calibration_status": real_case.get("status") or "blocked", + "unverified_claim_policy": "downgrade_or_block", + "precision_policy": "timing_window_not_guaranteed_event_form", + }, + "modifier_rule_sources": { + "dignity": "references/planetary-dignity-complete-reference.md", + "planetary_conditions": "references/retrograde-combustion-war-guide.md", + "transit": "references/transit-multi-reference-guide.md", + }, + } + + def _build_multi_reference_reading_summary(route: str, present: Dict[str, Any], strict: Dict[str, Any]) -> Dict[str, Any]: return { "root_frame": _summary_root_frame(route, present), @@ -2671,6 +2808,7 @@ def _attach_top_reader_contract(route: str, strict: Dict[str, Any]) -> Dict[str, event_judgement = {} strict["technique_audit_summary"] = _build_technique_audit_summary(route, strict) strict["adjudication_stages"] = _build_adjudication_stages(route, present, event_judgement) + strict["prediction_boundary_contract"] = _build_prediction_boundary_contract(route, strict) strict["multi_reference_reading_summary"] = _build_multi_reference_reading_summary(route, present, strict) strict["official_day_signal_summary"] = _build_official_day_signal_summary(present.get("external_activation")) strict["monthly_adjudication_summary"] = _build_monthly_adjudication_summary(route, strict) diff --git a/progress.md b/progress.md index eea9e1c8..1b104d2d 100644 --- a/progress.md +++ b/progress.md @@ -768,3 +768,12 @@ - `technique_audit_summary.interpretation_source_pack` 现在直接暴露 `core_rule_source_refs`,career / relationship / finance strict workflow 都能看到同一组核心源头。 - 新增 `tests/test_interpretation_source_core5_strict_visibility.py`;同时加固 `tests/test_interpretation_source_inventory_gate.py` 对核心层的断言。 - 边界仍保留:核心 5 已进入调用链可见性层,但具体解释仍需冲突仲裁、MEVG、真实案例校准和领域分盘/大运证据。 + +## 2026-07-02T15:42:00+08:00 - 核心 5 内容级调用与第二批资料层闭环 + +- 核心 5 不再只是可见:career / relationship / finance strict workflow 现在生成 `prediction_boundary_contract`,强制绑定 `promise -> activation -> manifestation -> label`、MEVG blocked、Real Case Calibration blocked 与核心 5 source refs。 +- 第二批 promote 11 个专题源头已接成 `promote_batch2_topic_layer`:Vimshottari、Pratyantar、分盘深读、Shadbala、Ashtakavarga、Tajika、Jaimini、KP、Argala、Badhaka、Condition Dasha。 +- `reference-only` 3 个源头已接成 `reference_only_conflict_layer`,只允许作为冲突/参考材料:`dasa-convergence-methodology`、`multi-dasha-convergence-protocol`、`yoga-strength-scoring-system`。 +- duplicate / obsolete / quarantine 8 个文件已列入 `blocked_non_runtime_layer`,并由 inventory gate 检查不进入 runtime source refs;重点包括 `kp-practical-event-timing.md` 与 `consultation-case-library.md`。 +- Prompt Pack / API fallback / AI Chat 已同步 `prediction_boundary_contract`、核心 5、第二批 11、reference-only 3;AI Chat 上下文新增 `【Prediction Boundary Contract】` 段落。 +- 真实 full-reading 回归确认:REDACTED_DATE REDACTED_TIME REDACTED_PLACE矿区样例中 relationship / career / finance 三条 strict contract 均带 prediction boundary,且 MEVG 与真实案例校准仍为 blocked。 diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index 9383750b..e1d280ed 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -3901,6 +3901,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): '不要仅凭单一配置下结论;核心判断至少交叉 D1、D9、Dasha、Shadbala/Ashtakavarga 或 Transit 中的两个证据层。', '必须显式标注置信度和边界:Dasha/PDF 起点差异、Shadbala 外部绝对值 oracle 尚未完成时,不得声称已经完全校准。', 'VedAstro 官方全量快照是第一原始证据层;若该层 blocked,必须把本地结果标记为 fallback。', + '必须按 promise → activation → manifestation → label 输出;每个判断都要说明属于承诺、激活、落地形式还是标签层。', + '未完成 MEVG / Real Case Calibration 时必须降级或标 blocked,不得把内部一致性写成已验证结论。', ] oracle_progress = { 'scope': 'external_oracle_evidence_validation', @@ -3938,6 +3940,53 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): 'shadbala_ranking': top_strength, }, 'functional_benefic_malefic': functional_layer, + 'interpretation_source_pack': { + 'status': 'fallback_prompt_pack_only', + 'source': 'api_chart_prompt_pack_fallback', + 'core_rule_source_refs': [ + 'references/prediction-boundary-protocol.md', + 'references/event_judgment_skeleton.md', + 'references/planetary-dignity-complete-reference.md', + 'references/retrograde-combustion-war-guide.md', + 'references/transit-multi-reference-guide.md', + ], + 'promote_batch2_source_refs': [ + 'references/vimshottari_dasha_guide.md', + 'references/pratyantar-calculation-guide.md', + 'references/divisional-chart-deep-reading.md', + 'references/shadbala-complete-methodology.md', + 'references/ashtakavarga-complete-system.md', + 'references/tajika-yoga-complete-guide.md', + 'references/jaimini-complete-system.md', + 'references/kp-astrology-complete-system.md', + 'references/argala-complete-guide.md', + 'references/badhaka-obstacle-planet-guide.md', + 'references/condition-dasha-complete.md', + ], + 'reference_only_source_refs': [ + 'references/dasa-convergence-methodology.md', + 'references/multi-dasha-convergence-protocol.md', + 'references/yoga-strength-scoring-system.md', + ], + }, + 'prediction_boundary_contract': { + 'status': 'fallback_prompt_pack_only', + 'source_refs': [ + 'references/prediction-boundary-protocol.md', + 'references/event_judgment_skeleton.md', + 'references/planetary-dignity-complete-reference.md', + 'references/retrograde-combustion-war-guide.md', + 'references/transit-multi-reference-guide.md', + ], + 'event_judgment_skeleton': { + 'required_sections': ['promise', 'activation', 'manifestation', 'label'], + }, + 'confidence_boundary': { + 'mevg_status': 'blocked', + 'real_case_calibration_status': 'blocked', + 'unverified_claim_policy': 'downgrade_or_block', + }, + }, 'vedastro_official_full_snapshot': vedastro_official_full_snapshot, 'vedastro_overview': vedastro_overview, 'guided_topics': guided_topics, @@ -3952,7 +4001,24 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): 'references/ai-reading-workflow-prompt.md', 'references/comprehensive-reading-workflow.md', 'references/prediction-boundary-protocol.md', + 'references/event_judgment_skeleton.md', + 'references/planetary-dignity-complete-reference.md', + 'references/retrograde-combustion-war-guide.md', + 'references/transit-multi-reference-guide.md', + 'references/vimshottari_dasha_guide.md', + 'references/pratyantar-calculation-guide.md', + 'references/divisional-chart-deep-reading.md', + 'references/shadbala-complete-methodology.md', + 'references/ashtakavarga-complete-system.md', + 'references/tajika-yoga-complete-guide.md', + 'references/jaimini-complete-system.md', + 'references/kp-astrology-complete-system.md', + 'references/argala-complete-guide.md', + 'references/badhaka-obstacle-planet-guide.md', + 'references/condition-dasha-complete.md', 'references/dasa-convergence-methodology.md', + 'references/multi-dasha-convergence-protocol.md', + 'references/yoga-strength-scoring-system.md', 'references/shadbala-interpretation-methodology.md', 'references/navamsa-d9-interpretation-template.md', ], diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 2f949b05..08244258 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -1432,6 +1432,7 @@ def _compact_strict_workflow_contract(strict): 'conflicts': strict.get('conflicts') or [], 'technique_audit_summary': strict.get('technique_audit_summary') or {}, 'adjudication_stages': strict.get('adjudication_stages') or {}, + 'prediction_boundary_contract': strict.get('prediction_boundary_contract') or {}, 'multi_reference_reading_summary': strict.get('multi_reference_reading_summary') or {}, 'monthly_adjudication_summary': strict.get('monthly_adjudication_summary') or {}, 'official_day_signal_summary': strict.get('official_day_signal_summary') or {}, @@ -1558,6 +1559,21 @@ def _build_ai_prompt_pack(report): vedastro_official_full_snapshot = _build_vedastro_official_full_snapshot_payload(modules) strict_workflow_contracts = _extract_strict_workflow_contracts(modules) strict_workflow_primary_route, primary_strict_contract = _preferred_strict_workflow_contract(strict_workflow_contracts) + primary_prediction_boundary_contract = ( + primary_strict_contract.get('prediction_boundary_contract') + if isinstance(primary_strict_contract, dict) + else {} + ) + primary_audit = ( + primary_strict_contract.get('technique_audit_summary') + if isinstance(primary_strict_contract, dict) + else {} + ) + interpretation_source_audit = ( + primary_audit.get('interpretation_source_pack') + if isinstance(primary_audit, dict) and isinstance(primary_audit.get('interpretation_source_pack'), dict) + else {} + ) guided_topics = modules.get('guided_topics') if isinstance(modules.get('guided_topics'), list) else build_guided_topics(report) capability_evidence_pool = build_capability_evidence_pool_summary() @@ -1630,6 +1646,15 @@ def _build_ai_prompt_pack(report): }, 'oracle_progress': oracle_progress, 'functional_benefic_malefic': functional_layer, + 'interpretation_source_pack': { + 'status': interpretation_source_audit.get('status') or 'blocked', + 'source': interpretation_source_audit.get('source') or 'repo_existing_interpretation_sources', + 'core_rule_source_refs': interpretation_source_audit.get('core_rule_source_refs') or [], + 'promote_batch2_source_refs': interpretation_source_audit.get('promote_batch2_source_refs') or [], + 'reference_only_source_refs': interpretation_source_audit.get('reference_only_source_refs') or [], + 'missing_refs': interpretation_source_audit.get('missing_refs') or [], + }, + 'prediction_boundary_contract': primary_prediction_boundary_contract or {}, 'strict_workflow_primary_route': strict_workflow_primary_route, 'strict_workflow_routes_available': list(strict_workflow_contracts.keys()), 'strict_workflow_contracts': strict_workflow_contracts, @@ -1660,6 +1685,8 @@ def _build_ai_prompt_pack(report): "若 evidence_snapshot.vedastro_overview.status 为 ok,请把它作为用户可见外部概览证据明确写出,但不要把 overview-only 结果误当作长周期精扫结论。", "VedAstro 官方全量快照是第一原始证据层;若 evidence_snapshot.vedastro_official_full_snapshot.status 不是 ok/partial,必须说明官方全量资料 blocked,并把本地结果标记为 fallback。", "若 evidence_snapshot.capability_evidence_pool 存在,请把 89 项视为后台备选证据池;不要把所有能力条目平铺成结论,也不要让 audit_only/alias 条目影响占星判断。", + "必须按 promise → activation → manifestation → label 输出;每个判断都要说明属于承诺、激活、落地形式还是标签层。", + "未完成 MEVG / Real Case Calibration 时必须降级或标 blocked,不得把内部一致性写成已验证结论。", ] return { @@ -1672,7 +1699,24 @@ def _build_ai_prompt_pack(report): 'references/ai-reading-workflow-prompt.md', 'references/comprehensive-reading-workflow.md', 'references/prediction-boundary-protocol.md', + 'references/event_judgment_skeleton.md', + 'references/planetary-dignity-complete-reference.md', + 'references/retrograde-combustion-war-guide.md', + 'references/transit-multi-reference-guide.md', + 'references/vimshottari_dasha_guide.md', + 'references/pratyantar-calculation-guide.md', + 'references/divisional-chart-deep-reading.md', + 'references/shadbala-complete-methodology.md', + 'references/ashtakavarga-complete-system.md', + 'references/tajika-yoga-complete-guide.md', + 'references/jaimini-complete-system.md', + 'references/kp-astrology-complete-system.md', + 'references/argala-complete-guide.md', + 'references/badhaka-obstacle-planet-guide.md', + 'references/condition-dasha-complete.md', 'references/dasa-convergence-methodology.md', + 'references/multi-dasha-convergence-protocol.md', + 'references/yoga-strength-scoring-system.md', 'references/shadbala-interpretation-methodology.md', 'references/navamsa-d9-interpretation-template.md', 'references/interpretation_template_registry.json', diff --git a/tests/test_cli_smoke.py b/tests/test_cli_smoke.py index ad4c6fb7..411a2ecf 100644 --- a/tests/test_cli_smoke.py +++ b/tests/test_cli_smoke.py @@ -286,6 +286,24 @@ def test_full_reading_reports_ayanamsa_metadata_and_ai_prompt_pack() -> None: assert any(row["technique"] == "MEVG / Global Web Evidence" for row in audit_table) assert any(row["technique"] == "Real Case Calibration" for row in audit_table) assert any(row["technique"] == "Interpretation Source Pack" for row in audit_table) + interpretation_source_pack = prompt_pack["evidence_snapshot"]["interpretation_source_pack"] + assert interpretation_source_pack["core_rule_source_refs"] == [ + "references/prediction-boundary-protocol.md", + "references/event_judgment_skeleton.md", + "references/planetary-dignity-complete-reference.md", + "references/retrograde-combustion-war-guide.md", + "references/transit-multi-reference-guide.md", + ] + assert "references/vimshottari_dasha_guide.md" in interpretation_source_pack["promote_batch2_source_refs"] + assert "references/yoga-strength-scoring-system.md" in interpretation_source_pack["reference_only_source_refs"] + prediction_boundary = prompt_pack["evidence_snapshot"]["prediction_boundary_contract"] + assert prediction_boundary["confidence_boundary"]["unverified_claim_policy"] == "downgrade_or_block" + assert prediction_boundary["event_judgment_skeleton"]["required_sections"] == [ + "promise", + "activation", + "manifestation", + "label", + ] functional_rows = [row for row in audit_table if row["technique"] == "Functional Benefic/Malefic"] assert functional_rows assert functional_rows[0]["status"] == "used" @@ -359,6 +377,7 @@ def test_full_reading_reports_ayanamsa_metadata_and_ai_prompt_pack() -> None: assert "finance" in vedastro_official_snapshot["strict_workflow_contracts"] career_contract = vedastro_official_snapshot["strict_workflow_contracts"]["career"] assert "adjudication_stages" in career_contract + assert "prediction_boundary_contract" in career_contract assert "multi_reference_reading_summary" in career_contract assert "modifier_frame" in career_contract["multi_reference_reading_summary"] assert vedastro_official_snapshot["strict_workflow_primary_route"] in {"relationship", "career", "finance", None} diff --git a/tests/test_frontend_productization.py b/tests/test_frontend_productization.py index 2ad9fe15..97d355e9 100644 --- a/tests/test_frontend_productization.py +++ b/tests/test_frontend_productization.py @@ -332,6 +332,9 @@ def test_frontend_consumes_top_reader_contract_in_prompt_pack_and_ai_chat() -> N assert "Top-reader adjudication" in main assert "multi_reference_reading_summary" in ai_chat assert "adjudication_stages" in ai_chat + assert "prediction_boundary_contract" in ai_chat + assert "【Prediction Boundary Contract】" in ai_chat + assert "unverified_claim_policy" in ai_chat assert "technique_audit_summary" in ai_chat assert "【Top Reader Contract】" in ai_chat assert ".ai-prompt-pack-contract-card" in style diff --git a/tests/test_interpretation_source_inventory_gate.py b/tests/test_interpretation_source_inventory_gate.py index c4ccdf9e..ee456dfc 100644 --- a/tests/test_interpretation_source_inventory_gate.py +++ b/tests/test_interpretation_source_inventory_gate.py @@ -51,6 +51,14 @@ def test_interpretation_source_inventory_gate_reports_layered_source_pack() -> N "references/retrograde-combustion-war-guide.md", "references/transit-multi-reference-guide.md", ] + assert layers["promote_batch2_topic_sources"]["promotion_batch"] == "priority1_batch1_remaining_promote_11" + assert "references/vimshottari_dasha_guide.md" in layers["promote_batch2_topic_sources"]["source_refs"] + assert "references/condition-dasha-complete.md" in layers["promote_batch2_topic_sources"]["source_refs"] + assert layers["reference_only_conflict_sources"]["promotion_status"] == "reference_only" + assert "references/multi-dasha-convergence-protocol.md" in layers["reference_only_conflict_sources"]["source_refs"] + assert layers["blocked_non_runtime_sources"]["promotion_status"] == "not_truth_source" + assert "references/kp-practical-event-timing.md" in layers["blocked_non_runtime_sources"]["source_refs"] + assert report["summary"]["blocked_non_runtime_count"] >= 8 draft_refs = layers["quarantined_drafts"]["source_refs"] source_refs = set(report["runtime_source_refs"]) diff --git a/tests/test_interpretation_source_next_phase_contract.py b/tests/test_interpretation_source_next_phase_contract.py new file mode 100644 index 00000000..4d568291 --- /dev/null +++ b/tests/test_interpretation_source_next_phase_contract.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +"""Regression tests for source-pack content contracts beyond visibility.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +from mcp_server import _collect_strict_evidence, _existing_interpretation_source_pack + + +ROOT = Path(__file__).resolve().parents[1] + +CORE5 = [ + "references/prediction-boundary-protocol.md", + "references/event_judgment_skeleton.md", + "references/planetary-dignity-complete-reference.md", + "references/retrograde-combustion-war-guide.md", + "references/transit-multi-reference-guide.md", +] + +PROMOTE_BATCH2 = [ + "references/vimshottari_dasha_guide.md", + "references/pratyantar-calculation-guide.md", + "references/divisional-chart-deep-reading.md", + "references/shadbala-complete-methodology.md", + "references/ashtakavarga-complete-system.md", + "references/tajika-yoga-complete-guide.md", + "references/jaimini-complete-system.md", + "references/kp-astrology-complete-system.md", + "references/argala-complete-guide.md", + "references/badhaka-obstacle-planet-guide.md", + "references/condition-dasha-complete.md", +] + +REFERENCE_ONLY = [ + "references/dasa-convergence-methodology.md", + "references/multi-dasha-convergence-protocol.md", + "references/yoga-strength-scoring-system.md", +] + +NON_RUNTIME = [ + "references/varga-system-quick-reference.md", + "references/yoga-list-chinese.md", + "references/analysis-full-reading-v4.0.md", + "references/analysis-full-reading-v1.8-review.md", + "references/audit-skill-full-test-2026-05-04.md", + "references/feature-gap-matrix-2026.md", + "references/kp-practical-event-timing.md", + "references/consultation-case-library.md", +] + + +def _base_modules() -> dict: + return { + "modules": { + "chart": { + "ascendant": {"sign": "Leo"}, + "planets": { + "Sun": {"status": "中性(Neutral)"}, + "Moon": {"status": "中性(Neutral)"}, + "Mercury": {"status": "中性(Neutral)"}, + "Venus": {"status": "中性(Neutral)"}, + "Jupiter": {"status": "中性(Neutral)"}, + "Saturn": {"status": "中性(Neutral)"}, + }, + }, + "varga_full": { + "D10_Dasamsa": {"summary": "career varga present"}, + "D9_Navamsha": {"summary": "relationship varga present"}, + "D2_Hora": {"summary": "wealth varga present"}, + "D11_Rudramsha": {"summary": "gains varga present"}, + }, + "special_lagnas": { + "A10_Karma_Pada": {"sign": "Capricorn", "lord": "Saturn"}, + "Upapada_Lagna": {"sign": "Libra", "lord": "Venus"}, + }, + "jaimini": { + "karakas": { + "Amatyakaraka": {"planet": "Mercury"}, + "Atmakaraka": {"planet": "Sun"}, + "Darakaraka": {"planet": "Venus"}, + }, + "karakamsha": {"karakamsha_sign": "Leo", "karakamsha_lord": "Sun"}, + }, + "dasha": {"current_dasha": {"mahadasha": "Mercury", "antardasha": "Sun"}}, + "narayana_dasha": {"current_dasha": {"sign": "Capricorn", "lord": "Saturn"}}, + "dasa_convergence": { + "domain_activations": { + "career_status": {"convergence_level": "L2", "probability": "35-50%"}, + "marriage_relationship": {"convergence_level": "L2", "probability": "35-50%"}, + "wealth_income": {"convergence_level": "L2", "probability": "35-50%"}, + } + }, + } + } + + +def _run_engine(*args: str) -> dict: + completed = subprocess.run( + [sys.executable, "scripts/jyotish_engine.py", *args], + cwd=ROOT, + check=False, + text=True, + capture_output=True, + timeout=180, + ) + assert completed.returncode == 0, completed.stderr[-2000:] or completed.stdout[-2000:] + return json.loads(completed.stdout) + + +def test_core5_sources_drive_prediction_boundary_contract_for_all_strict_routes() -> None: + for route in ["career", "relationship", "finance"]: + strict = _collect_strict_evidence(route, _base_modules()) + contract = strict["prediction_boundary_contract"] + + assert contract["source_refs"] == CORE5 + assert contract["event_judgment_skeleton"]["required_sections"] == [ + "promise", + "activation", + "manifestation", + "label", + ] + assert contract["promise"]["status"] == strict["adjudication_stages"]["promise"]["status"] + assert contract["activation"]["status"] == strict["adjudication_stages"]["activation"]["status"] + assert contract["manifestation"]["status"] == strict["adjudication_stages"]["manifestation"]["status"] + assert contract["confidence_boundary"]["mevg_status"] == "blocked" + assert contract["confidence_boundary"]["real_case_calibration_status"] == "blocked" + assert contract["confidence_boundary"]["unverified_claim_policy"] == "downgrade_or_block" + + +def test_batch2_and_reference_only_layers_are_wired_without_polluting_truth_sources() -> None: + source_pack = _existing_interpretation_source_pack() + + assert source_pack["promote_batch2_topic_layer"]["status"] == "available" + assert source_pack["promote_batch2_topic_layer"]["source_refs"] == PROMOTE_BATCH2 + assert source_pack["reference_only_conflict_layer"]["status"] == "available" + assert source_pack["reference_only_conflict_layer"]["source_refs"] == REFERENCE_ONLY + assert source_pack["reference_only_conflict_layer"]["promotion_status"] == "reference_only" + + runtime_refs = set(source_pack["source_refs"]) + for path in PROMOTE_BATCH2 + REFERENCE_ONLY: + assert path in runtime_refs + for path in NON_RUNTIME: + assert path not in runtime_refs + + inventory = source_pack["interpretation_source_inventory"] + assert inventory["layers"]["promote_batch2_topic_sources"]["source_refs"] == PROMOTE_BATCH2 + assert inventory["layers"]["reference_only_conflict_sources"]["source_refs"] == REFERENCE_ONLY + assert inventory["summary"]["blocked_non_runtime_count"] >= len(NON_RUNTIME) + + +def test_prompt_pack_and_real_reading_regression_expose_content_contracts() -> None: + result = _run_engine( + "full-reading", + "--year", + "REDACTED_YEAR", + "--month", + "4", + "--day", + "17", + "--hour", + "14", + "--minute", + "49", + "--lat", + "36.466667", + "--lon", + "114.2", + "--tz", + "8", + "--today", + "2026-07-02", + "--transit-date", + "2026-07-02", + ) + prompt_pack = result["ai_prompt_pack"] + snapshot = prompt_pack["evidence_snapshot"] + + assert snapshot["prediction_boundary_contract"]["source_refs"] == CORE5 + assert snapshot["interpretation_source_pack"]["core_rule_source_refs"] == CORE5 + assert snapshot["interpretation_source_pack"]["promote_batch2_source_refs"] == PROMOTE_BATCH2 + assert snapshot["interpretation_source_pack"]["reference_only_source_refs"] == REFERENCE_ONLY + assert "必须按 promise → activation → manifestation → label 输出" in prompt_pack["prompt_zh"] + assert "未完成 MEVG / Real Case Calibration 时必须降级或标 blocked" in prompt_pack["prompt_zh"] + + docs = prompt_pack["retrieval_plan"]["local_reference_docs"] + for path in CORE5 + PROMOTE_BATCH2 + REFERENCE_ONLY: + assert path in docs + for path in NON_RUNTIME: + assert path not in docs + + for route in ["career", "relationship", "finance"]: + contract = snapshot["strict_workflow_contracts"][route] + assert contract["prediction_boundary_contract"]["source_refs"] == CORE5 + audit = contract["technique_audit_summary"] + assert audit["mevg_global_web_evidence"]["status"] == "blocked" + assert audit["real_case_calibration"]["status"] == "blocked" + assert audit["interpretation_source_pack"]["core_rule_source_refs"] == CORE5