From 0e3adb5a1374cdf0cfbff13cb69635d169698d61 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Thu, 2 Jul 2026 15:40:34 +0800 Subject: [PATCH] Add domain invocation governance contracts --- jyotish-app/main.js | 29 +++ mcp_server.py | 159 +++++++++++++++ progress.md | 10 + scripts/jyotish_api_server.py | 63 ++++++ scripts/jyotish_engine.py | 42 ++++ tests/test_cli_smoke.py | 16 ++ tests/test_frontend_productization.py | 5 + ...ation_source_advanced_pipeline_contract.py | 191 ++++++++++++++++++ 8 files changed, 515 insertions(+) create mode 100644 tests/test_interpretation_source_advanced_pipeline_contract.py diff --git a/jyotish-app/main.js b/jyotish-app/main.js index d5cec00a..6e19e473 100644 --- a/jyotish-app/main.js +++ b/jyotish-app/main.js @@ -1274,6 +1274,7 @@ function renderAIPromptPackPanel(cd) { ` : ''} ${renderStrictWorkflowContractPanel(vedastroOfficial)} + ${renderInterpretationSourceGovernancePanel(evidence)}
@@ -1412,6 +1413,34 @@ function renderStrictWorkflowContractCard(route, contract = {}, isPrimary = fals `; } +function renderInterpretationSourceGovernancePanel(evidence = {}) { + const sourcePack = evidence.interpretation_source_pack || {}; + const boundary = evidence.prediction_boundary_contract || {}; + const mevg = evidence.mevg_collection_queue || {}; + const cases = evidence.real_case_calibration_layer || {}; + const debt = evidence.technical_debt_contract || {}; + const remaining = evidence.remaining_priority1_batch_queue || {}; + const coreRefs = sourcePack.core_rule_source_refs || []; + const referenceOnly = sourcePack.reference_only_source_refs || []; + const blockedPolicy = boundary.confidence_boundary?.unverified_claim_policy || 'downgrade_or_block'; + return ` +
+

Source Governance

+
+ core sources: ${escapeHtml(coreRefs.length ? String(coreRefs.length) : 'none')} + reference-only: ${escapeHtml(referenceOnly.length ? String(referenceOnly.length) : 'none')} + blocked non-runtime: duplicate / obsolete / quarantine + 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 batches: ${escapeHtml((remaining.next_batches || []).join(' / ') || 'none')} +
+
+ `; +} + function renderVedAstroOverviewPromptCard(overview = {}) { if (!overview || typeof overview !== 'object') return ''; const status = overview.status || 'blocked'; diff --git a/mcp_server.py b/mcp_server.py index d6894b30..043353b6 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -142,6 +142,29 @@ PROMOTE_BATCH2_TOPIC_SOURCE_REFS = [ "references/condition-dasha-complete.md", ] +DASHA_TIMING_SOURCE_REFS = [ + "references/vimshottari_dasha_guide.md", + "references/pratyantar-calculation-guide.md", + "references/condition-dasha-complete.md", +] + +VARGA_STRENGTH_SOURCE_REFS = [ + "references/divisional-chart-deep-reading.md", + "references/shadbala-complete-methodology.md", + "references/ashtakavarga-complete-system.md", +] + +ANNUAL_SPECIAL_SOURCE_REFS = [ + "references/tajika-yoga-complete-guide.md", + "references/jaimini-complete-system.md", + "references/kp-astrology-complete-system.md", +] + +MODIFIER_OBSTACLE_SOURCE_REFS = [ + "references/argala-complete-guide.md", + "references/badhaka-obstacle-planet-guide.md", +] + REFERENCE_ONLY_CONFLICT_SOURCE_REFS = [ "references/dasa-convergence-methodology.md", "references/multi-dasha-convergence-protocol.md", @@ -159,6 +182,42 @@ BLOCKED_NON_RUNTIME_SOURCE_REFS = [ "references/consultation-case-library.md", ] +REMAINING_PRIORITY1_BATCH_QUEUE = [ + "real_case_studies_batch1", + "rishi_ai_mcp_batch1", + "vedic_astro_skills_batch1", + "references_batch2", +] + + +def _domain_invocation_layers() -> Dict[str, Any]: + return { + "dasha_timing": { + "status": "available", + "source_refs": DASHA_TIMING_SOURCE_REFS, + "required_in_routes": ["career", "relationship", "finance"], + "contract": "timing must cite Vimshottari/Narayana cross-check and sub-period boundaries when used.", + }, + "varga_strength": { + "status": "available", + "source_refs": VARGA_STRENGTH_SOURCE_REFS, + "required_in_routes": ["career", "relationship", "finance"], + "contract": "domain conclusions must include relevant varga and strength/ashtakavarga boundaries.", + }, + "annual_special": { + "status": "available", + "source_refs": ANNUAL_SPECIAL_SOURCE_REFS, + "required_in_routes": ["career", "relationship", "finance"], + "contract": "annual/special systems are supporting layers until oracle parity is closed.", + }, + "modifier_obstacle": { + "status": "available", + "source_refs": MODIFIER_OBSTACLE_SOURCE_REFS, + "required_in_routes": ["career", "relationship", "finance"], + "contract": "Argala and Badhaka are modifiers/obstacle indicators, not standalone event guarantees.", + }, + } + def _build_interpretation_source_inventory(source_refs: List[str]) -> Dict[str, Any]: primary_truth = [ @@ -440,6 +499,12 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: "promotion_status": "not_truth_source", "boundary": "Duplicate, obsolete, and quarantined files are deliberately excluded from runtime source_refs.", }, + "domain_invocation_layers": _domain_invocation_layers(), + "remaining_priority1_batch_queue": { + "status": "queued", + "next_batches": REMAINING_PRIORITY1_BATCH_QUEUE, + "boundary": "Future batches remain audit-only until classified and tested.", + }, "frontend_interpretation_layer": { "status": "available" if all(_repo_relative_exists(path) for path in frontend_interpretation_paths) else "partial", "source_refs": frontend_interpretation_paths, @@ -2783,6 +2848,94 @@ def _build_prediction_boundary_contract(route: str, strict: Dict[str, Any]) -> D } +def _build_domain_invocation_contract(route: str, strict: Dict[str, Any]) -> Dict[str, Any]: + layers = _domain_invocation_layers() + return { + key: { + **value, + "route": route, + "used": True, + } + for key, value in layers.items() + } + + +def _build_output_template_contract(route: str, strict: Dict[str, Any]) -> Dict[str, Any]: + return { + "status": "used", + "route": route, + "language": "zh", + "required_sections": ["promise", "activation", "manifestation", "label", "confidence_boundary"], + "golden_test_status": "required", + "source_refs": [ + "references/prediction-boundary-protocol.md", + "references/event_judgment_skeleton.md", + ], + "instruction": "Final Chinese fortune output must map claims to promise/activation/manifestation/label and show confidence boundaries.", + } + + +def _build_mevg_collection_queue(route: str, strict: Dict[str, Any]) -> Dict[str, Any]: + return { + "status": "queued", + "trigger": "fortune_question_strict_workflow", + "route": route, + "required_jobs": [ + "global_web_evidence", + "real_case_reference_search", + "source_grading", + "conflict_arbitration", + "unverified_claim_downgrade", + ], + "cache_policy": "reuse_official_snapshot_and_external_evidence_cache_before_live_fetch", + "source_ref": "references/mandatory-verification-gate-protocol.md", + } + + +def _build_real_case_calibration_layer(route: str, strict: Dict[str, Any]) -> Dict[str, Any]: + return { + "status": "queued", + "route": route, + "domain_buckets": ["career", "finance", "relationship", "health", "rectification", "timing"], + "source_roots": ["references/real_case_studies", "docs/benchmark"], + "retrieval_policy": "domain_bucket_first_then_case_quality_gate", + "confidence_effect": "caps_confidence_until_matching_cases_are_attached", + } + + +def _build_technical_debt_contract(route: str, strict: Dict[str, Any]) -> Dict[str, Any]: + return { + "status": "tracked", + "route": route, + "narayana": { + "status": "partial", + "source_refs": ["references/bphs-ch48-narayana-dasha.md", "references/condition-dasha-complete.md"], + "open_items": [ + "antardasha_pratyantar_oracle_parity", + "subperiod_boundary_regression", + "external_engine_crosscheck", + ], + }, + "tajika": { + "status": "partial", + "source_refs": ["references/tajika-yoga-complete-guide.md"], + "open_items": [ + "solar_return_precision", + "muntha_placeholder_audit", + "annual_yoga_oracle_parity", + ], + }, + } + + +def _build_remaining_priority1_batch_queue() -> Dict[str, Any]: + return { + "status": "queued", + "next_batches": REMAINING_PRIORITY1_BATCH_QUEUE, + "boundary": "Do not promote remaining priority_1 materials without batch audit and tests.", + } + + 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), @@ -2809,6 +2962,12 @@ def _attach_top_reader_contract(route: str, strict: Dict[str, Any]) -> Dict[str, 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["domain_invocation_contract"] = _build_domain_invocation_contract(route, strict) + strict["output_template_contract"] = _build_output_template_contract(route, strict) + strict["mevg_collection_queue"] = _build_mevg_collection_queue(route, strict) + 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["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 1b104d2d..75e12d64 100644 --- a/progress.md +++ b/progress.md @@ -777,3 +777,13 @@ - 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。 + +## 2026-07-02T16:12:00+08:00 - 第二批领域调用层与后续队列合同 + +- 第二批 11 个 promote 源头已拆成四个领域调用层:`dasha_timing`、`varga_strength`、`annual_special`、`modifier_obstacle`;career / relationship / finance strict workflow 均暴露 `domain_invocation_contract`。 +- 新增 `output_template_contract`,要求最终中文输出按 `promise / activation / manifestation / label / confidence_boundary` 组织;当前以合同和测试约束为主,后续可继续做 golden narrative snapshot。 +- 新增 `mevg_collection_queue`,fortune strict workflow 会生成外部采集队列合同:global web evidence、real case search、source grading、conflict arbitration、unverified downgrade。 +- 新增 `real_case_calibration_layer`,按 career / finance / relationship / health / rectification / timing 六个桶连接 `references/real_case_studies` 与 `docs/benchmark`。 +- 新增 `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`。 diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index e1d280ed..3c980ce5 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -3987,6 +3987,69 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): 'unverified_claim_policy': 'downgrade_or_block', }, }, + 'domain_invocation_layers': { + 'dasha_timing': { + 'status': 'fallback_prompt_pack_only', + 'source_refs': [ + 'references/vimshottari_dasha_guide.md', + 'references/pratyantar-calculation-guide.md', + 'references/condition-dasha-complete.md', + ], + }, + 'varga_strength': { + 'status': 'fallback_prompt_pack_only', + 'source_refs': [ + 'references/divisional-chart-deep-reading.md', + 'references/shadbala-complete-methodology.md', + 'references/ashtakavarga-complete-system.md', + ], + }, + 'annual_special': { + 'status': 'fallback_prompt_pack_only', + 'source_refs': [ + 'references/tajika-yoga-complete-guide.md', + 'references/jaimini-complete-system.md', + 'references/kp-astrology-complete-system.md', + ], + }, + 'modifier_obstacle': { + 'status': 'fallback_prompt_pack_only', + 'source_refs': [ + 'references/argala-complete-guide.md', + 'references/badhaka-obstacle-planet-guide.md', + ], + }, + }, + 'output_template_contract': { + 'status': 'fallback_prompt_pack_only', + 'language': 'zh', + 'required_sections': ['promise', 'activation', 'manifestation', 'label', 'confidence_boundary'], + 'golden_test_status': 'required', + }, + 'mevg_collection_queue': { + 'status': 'queued', + 'trigger': 'fortune_question_strict_workflow', + 'required_jobs': ['global_web_evidence', 'source_grading', 'conflict_arbitration'], + }, + 'real_case_calibration_layer': { + 'status': 'queued', + 'domain_buckets': ['career', 'finance', 'relationship', 'health', 'rectification', 'timing'], + 'source_roots': ['references/real_case_studies', 'docs/benchmark'], + }, + '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']}, + }, + 'remaining_priority1_batch_queue': { + 'status': 'queued', + 'next_batches': [ + 'real_case_studies_batch1', + 'rishi_ai_mcp_batch1', + 'vedic_astro_skills_batch1', + 'references_batch2', + ], + }, 'vedastro_official_full_snapshot': vedastro_official_full_snapshot, 'vedastro_overview': vedastro_overview, 'guided_topics': guided_topics, diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 08244258..59dca979 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -1433,6 +1433,12 @@ def _compact_strict_workflow_contract(strict): '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 {}, + 'domain_invocation_contract': strict.get('domain_invocation_contract') or {}, + 'output_template_contract': strict.get('output_template_contract') or {}, + 'mevg_collection_queue': strict.get('mevg_collection_queue') or {}, + '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 {}, '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 {}, @@ -1564,6 +1570,36 @@ def _build_ai_prompt_pack(report): if isinstance(primary_strict_contract, dict) else {} ) + primary_domain_invocation_layers = ( + primary_strict_contract.get('domain_invocation_contract') + if isinstance(primary_strict_contract, dict) + else {} + ) + primary_output_template_contract = ( + primary_strict_contract.get('output_template_contract') + if isinstance(primary_strict_contract, dict) + else {} + ) + primary_mevg_collection_queue = ( + primary_strict_contract.get('mevg_collection_queue') + if isinstance(primary_strict_contract, dict) + else {} + ) + primary_real_case_calibration_layer = ( + primary_strict_contract.get('real_case_calibration_layer') + if isinstance(primary_strict_contract, dict) + else {} + ) + primary_technical_debt_contract = ( + primary_strict_contract.get('technical_debt_contract') + if isinstance(primary_strict_contract, dict) + else {} + ) + primary_remaining_priority1_batch_queue = ( + primary_strict_contract.get('remaining_priority1_batch_queue') + if isinstance(primary_strict_contract, dict) + else {} + ) primary_audit = ( primary_strict_contract.get('technique_audit_summary') if isinstance(primary_strict_contract, dict) @@ -1655,6 +1691,12 @@ def _build_ai_prompt_pack(report): 'missing_refs': interpretation_source_audit.get('missing_refs') or [], }, 'prediction_boundary_contract': primary_prediction_boundary_contract or {}, + 'domain_invocation_layers': primary_domain_invocation_layers or {}, + 'output_template_contract': primary_output_template_contract or {}, + 'mevg_collection_queue': primary_mevg_collection_queue or {}, + '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 {}, '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_cli_smoke.py b/tests/test_cli_smoke.py index 411a2ecf..0ea2c7f7 100644 --- a/tests/test_cli_smoke.py +++ b/tests/test_cli_smoke.py @@ -304,6 +304,22 @@ def test_full_reading_reports_ayanamsa_metadata_and_ai_prompt_pack() -> None: "manifestation", "label", ] + domain_layers = prompt_pack["evidence_snapshot"]["domain_invocation_layers"] + assert domain_layers["dasha_timing"]["source_refs"] == [ + "references/vimshottari_dasha_guide.md", + "references/pratyantar-calculation-guide.md", + "references/condition-dasha-complete.md", + ] + assert domain_layers["varga_strength"]["source_refs"] == [ + "references/divisional-chart-deep-reading.md", + "references/shadbala-complete-methodology.md", + "references/ashtakavarga-complete-system.md", + ] + assert prompt_pack["evidence_snapshot"]["output_template_contract"]["required_sections"][-1] == "confidence_boundary" + assert prompt_pack["evidence_snapshot"]["mevg_collection_queue"]["status"] == "queued" + assert prompt_pack["evidence_snapshot"]["real_case_calibration_layer"]["status"] == "queued" + assert prompt_pack["evidence_snapshot"]["technical_debt_contract"]["tajika"]["status"] == "partial" + assert prompt_pack["evidence_snapshot"]["remaining_priority1_batch_queue"]["next_batches"][0] == "real_case_studies_batch1" functional_rows = [row for row in audit_table if row["technique"] == "Functional Benefic/Malefic"] assert functional_rows assert functional_rows[0]["status"] == "used" diff --git a/tests/test_frontend_productization.py b/tests/test_frontend_productization.py index 97d355e9..9ab8139a 100644 --- a/tests/test_frontend_productization.py +++ b/tests/test_frontend_productization.py @@ -337,6 +337,11 @@ def test_frontend_consumes_top_reader_contract_in_prompt_pack_and_ai_chat() -> N assert "unverified_claim_policy" in ai_chat assert "technique_audit_summary" in ai_chat assert "【Top Reader Contract】" in ai_chat + assert "renderInterpretationSourceGovernancePanel" in main + assert "Source Governance" in main + assert "reference-only" in main + assert "blocked non-runtime" in main + assert "confidence downgrade" in main assert ".ai-prompt-pack-contract-card" in style diff --git a/tests/test_interpretation_source_advanced_pipeline_contract.py b/tests/test_interpretation_source_advanced_pipeline_contract.py new file mode 100644 index 00000000..a34fedcb --- /dev/null +++ b/tests/test_interpretation_source_advanced_pipeline_contract.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Contracts for the next interpretation-source pipeline stage.""" + +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] + +DASHA_TIMING = [ + "references/vimshottari_dasha_guide.md", + "references/pratyantar-calculation-guide.md", + "references/condition-dasha-complete.md", +] +VARGA_STRENGTH = [ + "references/divisional-chart-deep-reading.md", + "references/shadbala-complete-methodology.md", + "references/ashtakavarga-complete-system.md", +] +ANNUAL_SPECIAL = [ + "references/tajika-yoga-complete-guide.md", + "references/jaimini-complete-system.md", + "references/kp-astrology-complete-system.md", +] +MODIFIER_OBSTACLE = [ + "references/argala-complete-guide.md", + "references/badhaka-obstacle-planet-guide.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": { + "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 _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_promote_batch2_is_split_into_domain_invocation_layers() -> None: + source_pack = _existing_interpretation_source_pack() + domain_layers = source_pack["domain_invocation_layers"] + + assert domain_layers["dasha_timing"]["source_refs"] == DASHA_TIMING + assert domain_layers["varga_strength"]["source_refs"] == VARGA_STRENGTH + assert domain_layers["annual_special"]["source_refs"] == ANNUAL_SPECIAL + assert domain_layers["modifier_obstacle"]["source_refs"] == MODIFIER_OBSTACLE + assert domain_layers["dasha_timing"]["required_in_routes"] == ["career", "relationship", "finance"] + assert domain_layers["varga_strength"]["required_in_routes"] == ["career", "relationship", "finance"] + + for route in ["career", "relationship", "finance"]: + strict = _collect_strict_evidence(route, _base_modules()) + invocation = strict["domain_invocation_contract"] + assert invocation["dasha_timing"]["source_refs"] == DASHA_TIMING + assert invocation["varga_strength"]["source_refs"] == VARGA_STRENGTH + assert invocation["annual_special"]["source_refs"] == ANNUAL_SPECIAL + assert invocation["modifier_obstacle"]["source_refs"] == MODIFIER_OBSTACLE + + +def test_output_template_mevg_case_and_technical_debt_contracts_are_present() -> None: + strict = _collect_strict_evidence("career", _base_modules()) + + template = strict["output_template_contract"] + assert template["required_sections"] == ["promise", "activation", "manifestation", "label", "confidence_boundary"] + assert template["language"] == "zh" + assert template["golden_test_status"] == "required" + + mevg_queue = strict["mevg_collection_queue"] + assert mevg_queue["status"] == "queued" + assert mevg_queue["trigger"] == "fortune_question_strict_workflow" + assert "global_web_evidence" in mevg_queue["required_jobs"] + assert "source_grading" in mevg_queue["required_jobs"] + assert "conflict_arbitration" in mevg_queue["required_jobs"] + + case_layer = strict["real_case_calibration_layer"] + assert case_layer["status"] == "queued" + assert case_layer["domain_buckets"] == ["career", "finance", "relationship", "health", "rectification", "timing"] + assert case_layer["source_roots"] == ["references/real_case_studies", "docs/benchmark"] + + debt = strict["technical_debt_contract"] + assert debt["narayana"]["status"] == "partial" + assert "antardasha_pratyantar_oracle_parity" in debt["narayana"]["open_items"] + assert debt["tajika"]["status"] == "partial" + assert "solar_return_precision" in debt["tajika"]["open_items"] + assert "muntha_placeholder_audit" in debt["tajika"]["open_items"] + + +def test_prompt_pack_frontend_and_remaining_batch_queue_expose_next_stage_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", + ) + snapshot = result["ai_prompt_pack"]["evidence_snapshot"] + + assert snapshot["domain_invocation_layers"]["dasha_timing"]["source_refs"] == DASHA_TIMING + assert snapshot["output_template_contract"]["required_sections"][-1] == "confidence_boundary" + assert snapshot["mevg_collection_queue"]["status"] == "queued" + assert snapshot["real_case_calibration_layer"]["status"] == "queued" + assert snapshot["technical_debt_contract"]["tajika"]["status"] == "partial" + assert snapshot["remaining_priority1_batch_queue"]["next_batches"] == [ + "real_case_studies_batch1", + "rishi_ai_mcp_batch1", + "vedic_astro_skills_batch1", + "references_batch2", + ] + + main_js = (ROOT / "jyotish-app" / "main.js").read_text(encoding="utf-8") + assert "renderInterpretationSourceGovernancePanel" in main_js + assert "Source Governance" in main_js + assert "reference-only" in main_js + assert "blocked non-runtime" in main_js