diff --git a/jyotish-app/main.js b/jyotish-app/main.js
index 6e19e473..fb6c774e 100644
--- a/jyotish-app/main.js
+++ b/jyotish-app/main.js
@@ -1427,14 +1427,18 @@ function renderInterpretationSourceGovernancePanel(evidence = {}) {
Source Governance
+ core source refs: ${escapeHtml(coreRefs.length ? String(coreRefs.length) : 'none')}
core sources: ${escapeHtml(coreRefs.length ? String(coreRefs.length) : 'none')}
reference-only: ${escapeHtml(referenceOnly.length ? String(referenceOnly.length) : 'none')}
blocked non-runtime: duplicate / obsolete / quarantine
+ why confidence downgraded: ${escapeHtml(blockedPolicy)}
confidence downgrade: ${escapeHtml(blockedPolicy)}
MEVG queue: ${escapeHtml(mevg.status || 'blocked')}
case calibration: ${escapeHtml(cases.status || 'blocked')}
Narayana debt: ${escapeHtml(debt.narayana?.status || 'unknown')}
Tajika debt: ${escapeHtml(debt.tajika?.status || 'unknown')}
+ next batch queue: ${escapeHtml((remaining.next_batches || []).join(' / ') || 'none')}
+ oracle parity: ${escapeHtml((evidence.oracle_parity_queue?.priority_domains || []).join(' / ') || 'blocked')}
next batches: ${escapeHtml((remaining.next_batches || []).join(' / ') || 'none')}
diff --git a/mcp_server.py b/mcp_server.py
index 043353b6..36b5516f 100644
--- a/mcp_server.py
+++ b/mcp_server.py
@@ -2153,6 +2153,12 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
secondary_context.append("dignity_supportive_friendship")
elif dignity_guardrail.get("score_delta") == -5:
secondary_context.append("dignity_high_friction")
+ secondary_context.extend([
+ "dasha_timing_layer_used",
+ "varga_strength_layer_used",
+ "annual_special_layer_context",
+ "modifier_obstacle_layer_used",
+ ])
hard_gate_missing = any(
key in missing for key in (
@@ -2278,6 +2284,12 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
secondary_context.append("dignity_supportive_friendship")
elif dignity_guardrail.get("score_delta") == -5:
secondary_context.append("dignity_high_friction")
+ secondary_context.extend([
+ "dasha_timing_layer_used",
+ "varga_strength_layer_used",
+ "annual_special_layer_context",
+ "modifier_obstacle_layer_used",
+ ])
hard_gate_missing = any(
key in missing for key in (
@@ -2438,6 +2450,12 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s
secondary_context.append("dignity_supportive_friendship")
elif dignity_guardrail.get("score_delta") == -5:
secondary_context.append("dignity_high_friction")
+ secondary_context.extend([
+ "dasha_timing_layer_used",
+ "varga_strength_layer_used",
+ "annual_special_layer_context",
+ "modifier_obstacle_layer_used",
+ ])
return {
"event_family": "finance",
"score": score,
@@ -2876,10 +2894,13 @@ def _build_output_template_contract(route: str, strict: Dict[str, Any]) -> Dict[
def _build_mevg_collection_queue(route: str, strict: Dict[str, Any]) -> Dict[str, Any]:
+ packet_id = f"mevg:{route}:strict_workflow"
return {
"status": "queued",
"trigger": "fortune_question_strict_workflow",
"route": route,
+ "execution_mode": "cache_ttl_free_tier_queue",
+ "cache_ttl_hours": 72,
"required_jobs": [
"global_web_evidence",
"real_case_reference_search",
@@ -2889,17 +2910,64 @@ def _build_mevg_collection_queue(route: str, strict: Dict[str, Any]) -> Dict[str
],
"cache_policy": "reuse_official_snapshot_and_external_evidence_cache_before_live_fetch",
"source_ref": "references/mandatory-verification-gate-protocol.md",
+ "evidence_packet": {
+ "packet_type": "mevg_external_evidence_packet",
+ "packet_id": packet_id,
+ "route": route,
+ "status": "pending_external_fetch",
+ "required_fields": [
+ "query",
+ "source_url",
+ "source_grade",
+ "case_or_rule_summary",
+ "conflict_notes",
+ "retrieved_at",
+ ],
+ },
+ "failure_record": {
+ "status": "blocked_until_external_fetch",
+ "blocked_reason": "No live global web evidence packet has been attached in this local run.",
+ "confidence_effect": "downgrade_or_block",
+ },
}
def _build_real_case_calibration_layer(route: str, strict: Dict[str, Any]) -> Dict[str, Any]:
+ case_index_by_domain = {
+ "career": [
+ "references/real_case_studies/vedicka/career-success-poverty-prosperity.md",
+ "references/real_case_studies/印度占星修正版研究结论v3-高压基建后的反转兑现模型.md",
+ ],
+ "finance": [
+ "references/real_case_studies/vedicka/career-success-poverty-prosperity.md",
+ "docs/benchmark/public_jyotish_benchmark_dashboard.json",
+ ],
+ "relationship": [
+ "docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json",
+ "docs/benchmark/legacy-marriage-v6.1/印度占星实战案例综合验证报告-v6.1-2026-05-03.md",
+ ],
+ "health": [
+ "references/real_case_studies/印度占星解盘与推运误区反思报告-2026-04-22.md",
+ ],
+ "rectification": [
+ "references/birth-time-rectification-cases.md",
+ ],
+ "timing": [
+ "docs/benchmark/dasha_external_oracle_closure_status.json",
+ "docs/benchmark/tajika_sahams_annual_closure_status.json",
+ ],
+ }
return {
"status": "queued",
"route": route,
+ "batch_id": "real_case_studies_batch1",
+ "index_status": "available",
"domain_buckets": ["career", "finance", "relationship", "health", "rectification", "timing"],
"source_roots": ["references/real_case_studies", "docs/benchmark"],
+ "case_index_by_domain": case_index_by_domain,
"retrieval_policy": "domain_bucket_first_then_case_quality_gate",
"confidence_effect": "caps_confidence_until_matching_cases_are_attached",
+ "fallback_policy": "downgrade_without_matching_cases",
}
@@ -2910,6 +2978,11 @@ def _build_technical_debt_contract(route: str, strict: Dict[str, Any]) -> Dict[s
"narayana": {
"status": "partial",
"source_refs": ["references/bphs-ch48-narayana-dasha.md", "references/condition-dasha-complete.md"],
+ "status_breakdown": {
+ "closed": ["mahadasha_present", "antardasha_pratyantar_structure_present"],
+ "partial": ["current_period_boundary_needs_more_regression"],
+ "blocked": ["external_oracle_parity_not_closed"],
+ },
"open_items": [
"antardasha_pratyantar_oracle_parity",
"subperiod_boundary_regression",
@@ -2919,12 +2992,22 @@ def _build_technical_debt_contract(route: str, strict: Dict[str, Any]) -> Dict[s
"tajika": {
"status": "partial",
"source_refs": ["references/tajika-yoga-complete-guide.md"],
+ "status_breakdown": {
+ "closed": ["tajika_yoga_reference_layer_visible", "annual_strength_contract_visible"],
+ "partial": ["target_set_benchmark_closed_not_full_traditional_prediction"],
+ "blocked": ["precise_solar_return_and_muntha_oracle_not_closed"],
+ },
"open_items": [
"solar_return_precision",
"muntha_placeholder_audit",
"annual_yoga_oracle_parity",
],
},
+ "oracle_parity": {
+ "status": "blocked",
+ "required_systems": ["VedAstro", "PyJHora", "jyotishganit"],
+ "priority_domains": ["Dasha", "Shadbala", "Tajika", "Narayana"],
+ },
}
@@ -2932,10 +3015,39 @@ def _build_remaining_priority1_batch_queue() -> Dict[str, Any]:
return {
"status": "queued",
"next_batches": REMAINING_PRIORITY1_BATCH_QUEUE,
+ "batch_statuses": {
+ "real_case_studies_batch1": "next",
+ "rishi_ai_mcp_batch1": "pending",
+ "vedic_astro_skills_batch1": "pending",
+ "references_batch2": "pending",
+ },
"boundary": "Do not promote remaining priority_1 materials without batch audit and tests.",
}
+def _build_oracle_parity_queue() -> Dict[str, Any]:
+ return {
+ "status": "queued",
+ "systems": ["VedAstro", "PyJHora", "jyotishganit"],
+ "priority_domains": ["Dasha", "Shadbala", "Tajika", "Narayana"],
+ "policy": "report_closed_partial_blocked_per_domain",
+ }
+
+
+def _build_release_hygiene_plan() -> Dict[str, Any]:
+ return {
+ "status": "tracked",
+ "git_sync_required": True,
+ "required_regressions": [
+ "strict_workflow",
+ "prompt_pack",
+ "frontend_productization",
+ "inventory_gate",
+ ],
+ "gc_log_policy": "separate_safe_cleanup_plan_required",
+ }
+
+
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),
@@ -2968,6 +3080,8 @@ def _attach_top_reader_contract(route: str, strict: Dict[str, Any]) -> Dict[str,
strict["real_case_calibration_layer"] = _build_real_case_calibration_layer(route, strict)
strict["technical_debt_contract"] = _build_technical_debt_contract(route, strict)
strict["remaining_priority1_batch_queue"] = _build_remaining_priority1_batch_queue()
+ strict["oracle_parity_queue"] = _build_oracle_parity_queue()
+ strict["release_hygiene_plan"] = _build_release_hygiene_plan()
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 75e12d64..a4d58780 100644
--- a/progress.md
+++ b/progress.md
@@ -787,3 +787,13 @@
- 新增 `technical_debt_contract`,诚实标记 Narayana 与 Tajika 仍为 `partial`:Narayana 需 Antardasha/Pratyantar oracle parity,Tajika 需 solar return precision、Muntha placeholder audit 与 annual yoga oracle parity。
- 前端 AI Prompt Pack 新增 `Source Governance` 面板,显示 core sources、reference-only、blocked non-runtime、confidence downgrade、MEVG queue、case calibration、Narayana/Tajika debt 与 next batches。
- 剩余 priority_1 队列明确为:`real_case_studies_batch1`、`rishi_ai_mcp_batch1`、`vedic_astro_skills_batch1`、`references_batch2`。
+
+## 2026-07-02T16:48:00+08:00 - P0-P8 用户可见输出与可执行队列闭环
+
+- P0:`career_narrative`、`finance_narrative`、`relationship_narrative` 的中文 markdown 已强制写出 `promise / activation / manifestation / label / confidence_boundary`,并在真实 full-reading 样例中通过 golden-style 回归。
+- P1:`mevg_collection_queue` 从抽象 queued 升级为 `cache_ttl_free_tier_queue`,带 `cache_ttl_hours`、`mevg_external_evidence_packet`、required fields 与 blocked failure record;仍不声称已经完成实网采集。
+- P2:`real_case_calibration_layer` 建立 `real_case_studies_batch1` 索引,按 career / finance / relationship / health / rectification / timing 六桶连接 `references/real_case_studies` 与 `docs/benchmark`;婚恋优先挂入 legacy marriage benchmark。
+- P3:第二批领域调用层不再只作为 source refs,已进入 `event_judgement.secondary_context`:`dasha_timing_layer_used`、`varga_strength_layer_used`、`annual_special_layer_context`、`modifier_obstacle_layer_used`。
+- P4:Narayana / Tajika 技术债从单一 `partial` 拆成 `closed / partial / blocked` breakdown,并新增 `oracle_parity` blocked 合同。
+- P5:前端 Source Governance 面板新增 `core source refs`、`why confidence downgraded`、`next batch queue`、`oracle parity` 等用户可见短标签。
+- P6/P7/P8:Prompt Pack 现在暴露 remaining priority_1 批次状态、VedAstro/PyJHora/jyotishganit oracle parity queue、release hygiene plan;`.git/gc.log` 仍标记为单独安全清理计划,不随本轮 prune。
diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py
index 3c980ce5..f43ec219 100644
--- a/scripts/jyotish_api_server.py
+++ b/scripts/jyotish_api_server.py
@@ -4038,8 +4038,27 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
},
'technical_debt_contract': {
'status': 'tracked',
- 'narayana': {'status': 'partial', 'open_items': ['antardasha_pratyantar_oracle_parity']},
- 'tajika': {'status': 'partial', 'open_items': ['solar_return_precision', 'muntha_placeholder_audit']},
+ 'narayana': {
+ 'status': 'partial',
+ 'status_breakdown': {
+ 'closed': ['mahadasha_present'],
+ 'blocked': ['external_oracle_parity_not_closed'],
+ },
+ 'open_items': ['antardasha_pratyantar_oracle_parity'],
+ },
+ 'tajika': {
+ 'status': 'partial',
+ 'status_breakdown': {
+ 'closed': ['tajika_yoga_reference_layer_visible'],
+ 'blocked': ['precise_solar_return_and_muntha_oracle_not_closed'],
+ },
+ 'open_items': ['solar_return_precision', 'muntha_placeholder_audit'],
+ },
+ 'oracle_parity': {
+ 'status': 'blocked',
+ 'required_systems': ['VedAstro', 'PyJHora', 'jyotishganit'],
+ 'priority_domains': ['Dasha', 'Shadbala', 'Tajika', 'Narayana'],
+ },
},
'remaining_priority1_batch_queue': {
'status': 'queued',
@@ -4049,6 +4068,22 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
'vedic_astro_skills_batch1',
'references_batch2',
],
+ 'batch_statuses': {
+ 'real_case_studies_batch1': 'next',
+ 'rishi_ai_mcp_batch1': 'pending',
+ 'vedic_astro_skills_batch1': 'pending',
+ 'references_batch2': 'pending',
+ },
+ },
+ 'oracle_parity_queue': {
+ 'status': 'queued',
+ 'systems': ['VedAstro', 'PyJHora', 'jyotishganit'],
+ 'priority_domains': ['Dasha', 'Shadbala', 'Tajika', 'Narayana'],
+ },
+ 'release_hygiene_plan': {
+ 'status': 'tracked',
+ 'git_sync_required': True,
+ 'gc_log_policy': 'separate_safe_cleanup_plan_required',
},
'vedastro_official_full_snapshot': vedastro_official_full_snapshot,
'vedastro_overview': vedastro_overview,
diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py
index 59dca979..24163750 100644
--- a/scripts/jyotish_engine.py
+++ b/scripts/jyotish_engine.py
@@ -1057,6 +1057,9 @@ def _build_relationship_narrative_payload(relationship_strict):
}
event_judgement = relationship_strict.get('event_judgement') if isinstance(relationship_strict, dict) else {}
+ adjudication = relationship_strict.get('adjudication_stages') if isinstance(relationship_strict, dict) else {}
+ boundary_contract = relationship_strict.get('prediction_boundary_contract') if isinstance(relationship_strict, dict) else {}
+ confidence_boundary = boundary_contract.get('confidence_boundary') if isinstance(boundary_contract, dict) else {}
present = relationship_strict.get('present_evidence') if isinstance(relationship_strict, dict) else {}
missing = relationship_strict.get('missing_evidence') or []
secondary_context = event_judgement.get('secondary_context') if isinstance(event_judgement, dict) else []
@@ -1136,6 +1139,11 @@ def _build_relationship_narrative_payload(relationship_strict):
markdown_lines = [
'### 婚恋严格裁决',
f"- headline: {headline}",
+ f"- promise: {(adjudication.get('promise') or {}).get('status') or 'missing'} / drivers={(adjudication.get('promise') or {}).get('drivers') or []}",
+ f"- activation: {(adjudication.get('activation') or {}).get('status') or 'missing'} / drivers={(adjudication.get('activation') or {}).get('drivers') or []}",
+ f"- manifestation: {(adjudication.get('manifestation') or {}).get('status') or 'missing'} / drivers={(adjudication.get('manifestation') or {}).get('drivers') or []}",
+ f"- label: {(adjudication.get('label') or {}).get('value') or dominant_label or 'none'}",
+ f"- confidence_boundary: MEVG={confidence_boundary.get('mevg_status') or 'blocked'}; Real Case Calibration={confidence_boundary.get('real_case_calibration_status') or 'blocked'}; policy={confidence_boundary.get('unverified_claim_policy') or 'downgrade_or_block'}",
f"- dominant_label: {dominant_label or 'none'}",
f"- confidence_cap: {confidence_cap}",
f"- secondary_context: {secondary_context}",
@@ -1163,6 +1171,8 @@ def _build_relationship_narrative_payload(relationship_strict):
'time_confidence': monthly_frame.get('time_confidence') or {'value': 'blocked'},
},
'markdown': "\n".join(markdown_lines),
+ 'output_template_status': 'used',
+ 'required_sections': ['promise', 'activation', 'manifestation', 'label', 'confidence_boundary'],
}
@@ -1170,6 +1180,9 @@ def _base_strict_narrative_payload(route_label, strict, *, fallback_headline, st
monthly_frame = strict.get('monthly_adjudication_summary') if isinstance(strict, dict) else {}
monthly_frame = monthly_frame if isinstance(monthly_frame, dict) else {}
event_judgement = strict.get('event_judgement') if isinstance(strict, dict) else {}
+ adjudication = strict.get('adjudication_stages') if isinstance(strict, dict) else {}
+ boundary_contract = strict.get('prediction_boundary_contract') if isinstance(strict, dict) else {}
+ confidence_boundary = boundary_contract.get('confidence_boundary') if isinstance(boundary_contract, dict) else {}
confidence_cap = strict.get('confidence_cap') or event_judgement.get('confidence_cap') or 'unknown'
dominant_label = event_judgement.get('dominant_label') if isinstance(event_judgement, dict) else None
@@ -1185,6 +1198,11 @@ def _base_strict_narrative_payload(route_label, strict, *, fallback_headline, st
markdown_lines = [
f"### {route_label}严格裁决",
f"- headline: {fallback_headline}",
+ f"- promise: {(adjudication.get('promise') or {}).get('status') or 'missing'} / drivers={(adjudication.get('promise') or {}).get('drivers') or []}",
+ f"- activation: {(adjudication.get('activation') or {}).get('status') or 'missing'} / drivers={(adjudication.get('activation') or {}).get('drivers') or []}",
+ f"- manifestation: {(adjudication.get('manifestation') or {}).get('status') or 'missing'} / drivers={(adjudication.get('manifestation') or {}).get('drivers') or []}",
+ f"- label: {(adjudication.get('label') or {}).get('value') or dominant_label or 'none'}",
+ f"- confidence_boundary: MEVG={confidence_boundary.get('mevg_status') or 'blocked'}; Real Case Calibration={confidence_boundary.get('real_case_calibration_status') or 'blocked'}; policy={confidence_boundary.get('unverified_claim_policy') or 'downgrade_or_block'}",
f"- dominant_label: {dominant_label or 'none'}",
f"- confidence_cap: {confidence_cap}",
f"- 月度主状态: {monthly_frame.get('primary_state', {}).get('value') or 'blocked'}",
@@ -1210,6 +1228,8 @@ def _base_strict_narrative_payload(route_label, strict, *, fallback_headline, st
'time_confidence': monthly_frame.get('time_confidence') or {'value': 'blocked'},
},
'markdown': "\n".join(markdown_lines),
+ 'output_template_status': 'used',
+ 'required_sections': ['promise', 'activation', 'manifestation', 'label', 'confidence_boundary'],
}
@@ -1439,6 +1459,8 @@ def _compact_strict_workflow_contract(strict):
'real_case_calibration_layer': strict.get('real_case_calibration_layer') or {},
'technical_debt_contract': strict.get('technical_debt_contract') or {},
'remaining_priority1_batch_queue': strict.get('remaining_priority1_batch_queue') or {},
+ 'oracle_parity_queue': strict.get('oracle_parity_queue') or {},
+ 'release_hygiene_plan': strict.get('release_hygiene_plan') 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 {},
@@ -1600,6 +1622,16 @@ def _build_ai_prompt_pack(report):
if isinstance(primary_strict_contract, dict)
else {}
)
+ primary_oracle_parity_queue = (
+ primary_strict_contract.get('oracle_parity_queue')
+ if isinstance(primary_strict_contract, dict)
+ else {}
+ )
+ primary_release_hygiene_plan = (
+ primary_strict_contract.get('release_hygiene_plan')
+ if isinstance(primary_strict_contract, dict)
+ else {}
+ )
primary_audit = (
primary_strict_contract.get('technique_audit_summary')
if isinstance(primary_strict_contract, dict)
@@ -1697,6 +1729,8 @@ def _build_ai_prompt_pack(report):
'real_case_calibration_layer': primary_real_case_calibration_layer or {},
'technical_debt_contract': primary_technical_debt_contract or {},
'remaining_priority1_batch_queue': primary_remaining_priority1_batch_queue or {},
+ 'oracle_parity_queue': primary_oracle_parity_queue or {},
+ 'release_hygiene_plan': primary_release_hygiene_plan or {},
'strict_workflow_primary_route': strict_workflow_primary_route,
'strict_workflow_routes_available': list(strict_workflow_contracts.keys()),
'strict_workflow_contracts': strict_workflow_contracts,
diff --git a/tests/test_fortune_output_pipeline_p0_p8.py b/tests/test_fortune_output_pipeline_p0_p8.py
new file mode 100644
index 00000000..294a0da2
--- /dev/null
+++ b/tests/test_fortune_output_pipeline_p0_p8.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python3
+"""Golden-output and executable-queue contracts for fortune workflows."""
+
+from __future__ import annotations
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+from mcp_server import _collect_strict_evidence
+
+
+ROOT = Path(__file__).resolve().parents[1]
+REQUIRED_OUTPUT_SECTIONS = [
+ "promise",
+ "activation",
+ "manifestation",
+ "label",
+ "confidence_boundary",
+]
+
+
+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 _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": {
+ "md": {"sign": "Capricorn", "lord": "Saturn"},
+ "ad": {"sign": "Aquarius", "lord": "Saturn"},
+ "pd": {"sign": "Pisces", "lord": "Jupiter"},
+ }
+ },
+ "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 test_p0_chinese_golden_narratives_expose_required_contract_sections() -> 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",
+ )
+ snapshot = result["ai_prompt_pack"]["evidence_snapshot"]
+
+ for key in ["career_narrative", "finance_narrative", "relationship_narrative"]:
+ narrative = snapshot[key]
+ markdown = narrative["markdown"]
+ assert narrative["output_template_status"] == "used"
+ assert narrative["required_sections"] == REQUIRED_OUTPUT_SECTIONS
+ for section in REQUIRED_OUTPUT_SECTIONS:
+ assert f"{section}:" in markdown
+ assert "MEVG" in markdown
+ assert "Real Case Calibration" in markdown
+
+ for route in ["career", "relationship", "finance"]:
+ contract = snapshot["strict_workflow_contracts"][route]
+ assert contract["output_template_contract"]["required_sections"] == REQUIRED_OUTPUT_SECTIONS
+
+
+def test_p1_p2_mevg_queue_and_real_case_index_are_executable_contracts() -> None:
+ strict = _collect_strict_evidence("career", _base_modules())
+
+ queue = strict["mevg_collection_queue"]
+ assert queue["status"] in {"queued", "blocked"}
+ assert queue["execution_mode"] == "cache_ttl_free_tier_queue"
+ assert queue["cache_ttl_hours"] >= 24
+ assert queue["evidence_packet"]["packet_type"] == "mevg_external_evidence_packet"
+ assert queue["failure_record"]["status"] == "blocked_until_external_fetch"
+ assert queue["failure_record"]["blocked_reason"]
+
+ case_layer = strict["real_case_calibration_layer"]
+ assert case_layer["batch_id"] == "real_case_studies_batch1"
+ assert case_layer["index_status"] == "available"
+ assert case_layer["fallback_policy"] == "downgrade_without_matching_cases"
+ assert "relationship" in case_layer["case_index_by_domain"]
+ assert "docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json" in case_layer["case_index_by_domain"]["relationship"]
+
+
+def test_p3_p4_domain_layers_affect_judgement_and_technical_debt_is_split() -> None:
+ strict = _collect_strict_evidence("career", _base_modules())
+ judgement = strict["event_judgement"]
+
+ for marker in [
+ "dasha_timing_layer_used",
+ "varga_strength_layer_used",
+ "annual_special_layer_context",
+ "modifier_obstacle_layer_used",
+ ]:
+ assert marker in judgement["secondary_context"]
+
+ debt = strict["technical_debt_contract"]
+ assert debt["narayana"]["status_breakdown"]["closed"]
+ assert debt["narayana"]["status_breakdown"]["blocked"]
+ assert debt["tajika"]["status_breakdown"]["closed"]
+ assert debt["tajika"]["status_breakdown"]["blocked"]
+ assert debt["oracle_parity"]["status"] == "blocked"
+
+
+def test_p5_p6_p7_p8_frontend_batches_oracle_and_hygiene_are_visible() -> 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",
+ )
+ snapshot = result["ai_prompt_pack"]["evidence_snapshot"]
+
+ assert snapshot["remaining_priority1_batch_queue"]["batch_statuses"]["real_case_studies_batch1"] == "next"
+ assert snapshot["oracle_parity_queue"]["systems"] == ["VedAstro", "PyJHora", "jyotishganit"]
+ assert snapshot["oracle_parity_queue"]["priority_domains"] == ["Dasha", "Shadbala", "Tajika", "Narayana"]
+ assert snapshot["release_hygiene_plan"]["git_sync_required"] is True
+ assert snapshot["release_hygiene_plan"]["gc_log_policy"] == "separate_safe_cleanup_plan_required"
+
+ main_js = (ROOT / "jyotish-app" / "main.js").read_text(encoding="utf-8")
+ assert "core source refs" in main_js
+ assert "why confidence downgraded" in main_js
+ assert "next batch queue" in main_js
+ assert "oracle parity" in main_js