@@ -7015,8 +7194,13 @@ function renderPrashnaTab(chartData) {
const result = $('prashna-result');
const runBtn = $('btn-run-prashna');
if (!category || !question || !result || !runBtn) return;
+ const workflow = chartData?._consultationWorkflow;
renderPrashnaCaseWorkspace();
setupPrashnaCaseActions();
+ if (workflow?.prashna) {
+ result.innerHTML = renderPrashnaResult(workflow.prashna, workflow.prashna.question || workflow.question || '');
+ bindTerms(result);
+ }
if (!runBtn.dataset.bound) {
runBtn.dataset.bound = 'true';
diff --git a/jyotish-app/style.css b/jyotish-app/style.css
index 05730427..c0132e09 100644
--- a/jyotish-app/style.css
+++ b/jyotish-app/style.css
@@ -5471,6 +5471,22 @@ body { font-family: var(--font-body); background: var(--bg-page); color: var(--t
color: var(--text-heading);
font-size: 12px;
}
+.workflow-sidecars-summary {
+ margin-top: 12px;
+}
+.sidecar-jump-btn {
+ margin-top: 10px;
+ padding: 8px 10px;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-sm);
+ background: var(--bg-surface);
+ color: var(--text-heading);
+ cursor: pointer;
+}
+.sidecar-jump-btn:disabled {
+ opacity: 0.55;
+ cursor: not-allowed;
+}
.workflow-provenance-panel {
margin-top: 12px;
}
diff --git a/mcp_server.py b/mcp_server.py
index 36b5516f..20c74336 100644
--- a/mcp_server.py
+++ b/mcp_server.py
@@ -183,11 +183,96 @@ BLOCKED_NON_RUNTIME_SOURCE_REFS = [
]
REMAINING_PRIORITY1_BATCH_QUEUE = [
- "real_case_studies_batch1",
- "rishi_ai_mcp_batch1",
- "vedic_astro_skills_batch1",
"references_batch2",
+ "vedastro_official_default_closure",
+ "external_oracle_parity_batch",
+ "install_usage_path_slimming",
]
+REAL_CASE_STUDIES_BATCH1_INDEX = {
+ "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",
+ ],
+}
+REAL_CASE_STUDIES_BATCH1_SOURCE_REFS = sorted(
+ {path for paths in REAL_CASE_STUDIES_BATCH1_INDEX.values() for path in paths}
+)
+RISHI_AI_MCP_BATCH1_DOMAIN_MAP = {
+ "career": [
+ "references/open_source_sources/rishi-ai-mcp/.agents/skills/career-analysis/SKILL.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/workflows/career-analysis.md",
+ ],
+ "finance": [
+ "references/open_source_sources/rishi-ai-mcp/.agents/skills/finance-analysis/SKILL.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/workflows/finance-analysis.md",
+ ],
+ "relationship": [
+ "references/open_source_sources/rishi-ai-mcp/.agents/skills/relationship-analysis/SKILL.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/skills/marriage-analysis/SKILL.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/skills/spouse-profiling/SKILL.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/workflows/relationship-analysis.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/workflows/marriage-analysis.md",
+ ],
+ "children": [
+ "references/open_source_sources/rishi-ai-mcp/.agents/skills/children-analysis/SKILL.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/workflows/children-analysis.md",
+ ],
+ "health": [
+ "references/open_source_sources/rishi-ai-mcp/.agents/skills/health-analysis/SKILL.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/workflows/health-analysis.md",
+ ],
+ "full_reading": [
+ "references/open_source_sources/rishi-ai-mcp/.agents/skills/full-reading/SKILL.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/workflows/full-reading.md",
+ "references/open_source_sources/rishi-ai-mcp/.agents/rules/rishi-ai.md",
+ ],
+}
+RISHI_AI_MCP_BATCH1_SOURCE_REFS = sorted(
+ {path for paths in RISHI_AI_MCP_BATCH1_DOMAIN_MAP.values() for path in paths}
+)
+VEDIC_ASTRO_SKILLS_BATCH1_DOMAIN_MAP = {
+ "core": [
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/SKILL.md",
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/report_rules.md",
+ ],
+ "reader_validation": [
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/SKILL.md",
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/data_contract.md",
+ ],
+ "career": [
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-career/SKILL.md",
+ ],
+ "relationship": [
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-love/SKILL.md",
+ ],
+ "rectification": [
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-rectifier/SKILL.md",
+ ],
+ "calculator": [
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-calculator/SKILL.md",
+ ],
+}
+VEDIC_ASTRO_SKILLS_BATCH1_SOURCE_REFS = sorted(
+ {path for paths in VEDIC_ASTRO_SKILLS_BATCH1_DOMAIN_MAP.values() for path in paths}
+)
def _domain_invocation_layers() -> Dict[str, Any]:
@@ -432,6 +517,9 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]:
*core_rule_paths,
*promote_batch2_paths,
*reference_only_paths,
+ *REAL_CASE_STUDIES_BATCH1_SOURCE_REFS,
+ *RISHI_AI_MCP_BATCH1_SOURCE_REFS,
+ *VEDIC_ASTRO_SKILLS_BATCH1_SOURCE_REFS,
*frontend_interpretation_paths,
*planet_house_paths,
qa_rules_path,
@@ -493,6 +581,58 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]:
"promotion_status": "reference_only",
"boundary": "Reference-only sources can explain conflicts but cannot override primary rule sources.",
},
+ "real_case_calibration_layer": {
+ "status": "queued",
+ "batch_id": "real_case_studies_batch1",
+ "index_status": "available"
+ if all(_repo_relative_exists(path) for path in REAL_CASE_STUDIES_BATCH1_SOURCE_REFS)
+ else "partial",
+ "domain_buckets": list(REAL_CASE_STUDIES_BATCH1_INDEX.keys()),
+ "source_refs": REAL_CASE_STUDIES_BATCH1_SOURCE_REFS,
+ "case_index_by_domain": REAL_CASE_STUDIES_BATCH1_INDEX,
+ "retrieval_policy": "domain_bucket_first_then_case_quality_gate",
+ "promotion_status": "local_case_retrieval_layer",
+ "boundary": "Local case index is callable for calibration; matching-case attachment remains required before lifting confidence.",
+ },
+ "rishi_ai_mcp_batch1_layer": {
+ "status": "available"
+ if all(_repo_relative_exists(path) for path in RISHI_AI_MCP_BATCH1_SOURCE_REFS)
+ else "partial",
+ "batch_id": "rishi_ai_mcp_batch1",
+ "domain_map": RISHI_AI_MCP_BATCH1_DOMAIN_MAP,
+ "source_refs": RISHI_AI_MCP_BATCH1_SOURCE_REFS,
+ "promotion_status": "open_source_reference_layer",
+ "runtime_truth_status": "not_primary_truth",
+ "boundary": "Use as workflow/reference guidance only; do not override local strict rules or oracle-calibrated calculations.",
+ },
+ "vedic_astro_skills_batch1_layer": {
+ "status": "available"
+ if all(_repo_relative_exists(path) for path in VEDIC_ASTRO_SKILLS_BATCH1_SOURCE_REFS)
+ else "partial",
+ "batch_id": "vedic_astro_skills_batch1",
+ "domain_map": VEDIC_ASTRO_SKILLS_BATCH1_DOMAIN_MAP,
+ "source_refs": VEDIC_ASTRO_SKILLS_BATCH1_SOURCE_REFS,
+ "promotion_status": "external_skill_reference_layer",
+ "runtime_truth_status": "not_primary_truth",
+ "boundary": "Use as external skill-corpus reference only; already-wired QA/Yoga/reader files remain separately governed.",
+ },
+ "external_closure_gap_layer": {
+ "vedastro_official": {
+ "status": "blocked",
+ "reason": "official full snapshot/default closure is not yet guaranteed for every strict workflow route.",
+ "next_action": "stabilize official snapshot cache TTL/free-tier queue and route-level fallback reporting.",
+ },
+ "oracle_parity": {
+ "status": "blocked",
+ "systems": ["VedAstro", "PyJHora", "jyotishganit"],
+ "priority_domains": ["Dasha", "Shadbala", "Tajika", "Narayana"],
+ "next_action": "expand external oracle parity packets without treating unmatched outputs as truth.",
+ },
+ "install_usage_path": {
+ "status": "needs_slimming",
+ "next_action": "keep one stable user entry command with official extended env, cache/TTL, free-tier queue, and strict workflow defaults.",
+ },
+ },
"blocked_non_runtime_layer": {
"status": "blocked",
"source_refs": blocked_non_runtime_paths,
@@ -547,6 +687,9 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]:
"status": "blocked",
"required": True,
"source_ref": real_case_checklist_path,
+ "local_index_status": "available",
+ "local_batch_id": "real_case_studies_batch1",
+ "local_case_source_refs": REAL_CASE_STUDIES_BATCH1_SOURCE_REFS,
"effect_on_confidence": "caps_confidence_without_matching_cases",
},
"boundary": (
@@ -2933,38 +3076,15 @@ def _build_mevg_collection_queue(route: str, strict: Dict[str, Any]) -> Dict[str
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"],
+ "domain_buckets": list(REAL_CASE_STUDIES_BATCH1_INDEX.keys()),
"source_roots": ["references/real_case_studies", "docs/benchmark"],
- "case_index_by_domain": case_index_by_domain,
+ "case_index_by_domain": REAL_CASE_STUDIES_BATCH1_INDEX,
+ "source_refs": REAL_CASE_STUDIES_BATCH1_SOURCE_REFS,
"retrieval_policy": "domain_bucket_first_then_case_quality_gate",
"confidence_effect": "caps_confidence_until_matching_cases_are_attached",
"fallback_policy": "downgrade_without_matching_cases",
@@ -4132,6 +4252,41 @@ def strict_workflow(
)
result["chart"] = chart
result["strict_workflow"] = _collect_strict_evidence(route, chart)
+ try:
+ from jyotish_api_server import JyotishAPIHandler
+
+ handler = JyotishAPIHandler.__new__(JyotishAPIHandler)
+ vedastro_official = handler._high_rigor_vedastro_official_summary(chart)
+ interpretation_coverage = handler._interpretation_source_runtime_coverage(chart)
+ machine_evidence_packet = _UNIFIED_CONSULTATION_ORCHESTRATOR.machine_evidence_packet(
+ chart=chart,
+ route_packet=result.get("routing") if isinstance(result.get("routing"), dict) else route_packet,
+ vedastro_official=vedastro_official,
+ )
+ real_case_calibration = _UNIFIED_CONSULTATION_ORCHESTRATOR.real_case_calibration_catalog(
+ route_packet=result.get("routing") if isinstance(result.get("routing"), dict) else route_packet,
+ machine_evidence_packet=machine_evidence_packet,
+ )
+ planner = result.get("runtime_planner") if isinstance(result.get("runtime_planner"), dict) else {}
+ result["vedastro_official"] = vedastro_official
+ result["runtime_truth"] = vedastro_official.get("runtime_truth", {})
+ result["interpretation_source_runtime_coverage"] = interpretation_coverage
+ result["machine_evidence_packet"] = machine_evidence_packet
+ result["real_case_calibration"] = real_case_calibration
+ result["runtime_evidence_log"] = _UNIFIED_CONSULTATION_ORCHESTRATOR.runtime_evidence_log(
+ surface="skill_mcp",
+ entry_mode=result.get("entry_mode", "direct_chart"),
+ route_packet=result.get("routing") if isinstance(result.get("routing"), dict) else route_packet,
+ executed_steps=planner.get("executed_steps", []),
+ skipped_steps=planner.get("skipped_steps", []),
+ vedastro_official=vedastro_official,
+ interpretation_source_runtime_coverage=interpretation_coverage,
+ machine_evidence_packet=machine_evidence_packet,
+ real_case_calibration=real_case_calibration,
+ blind=False,
+ )
+ except Exception:
+ pass
return result
diff --git a/progress.md b/progress.md
index 660360b0..e4010819 100644
--- a/progress.md
+++ b/progress.md
@@ -858,3 +858,21 @@
- 第一批结果:`promote_to_reference_pack_candidate=9`、`reference_only=3`;已完成 batch1 级别的 source-pack eligibility 仲裁。
- 仲裁结论:`reference_pack_candidate=9`、`reference_only=3`、`runtime_truth_ready_count=0`;不允许直接进入 runtime truth。
- 下一步必须做:把 9 个候选显式接入 interpretation source/source pack,并加 strict workflow visibility tests;内容级规则优先级仍不得压过核心主规则。
+
+## 2026-07-02T23:30:00+08:00 - 6月20日后算力消耗升高排查
+
+- 用户要求检查“6月20日开始为什么算力消耗增高,是什么 bug”。
+- 已按系统化调试排查:Git 变更、质量门/CI、Codex 本地 session/token metadata、WorkBuddy trace 元数据、当前进程负载、VedAstro live 配置。
+- 关键证据:Codex `session_index.jsonl` 从 2026-06-20 起出现多项目长线程;`.codex/archived_sessions` 里 2026-06-21/22 有 139MB、127MB、58MB 等大 session;`token_count` 聚合显示 2026-06-22/23 单日 archived sessions last total tokens 分别约 556.6M/580.5M。
+- 排查结论已写入 `findings.md`:主因不是占星核心计算死循环,而是代理工作流/上下文管理设计导致的大范围读取、长工具输出、压缩摘要膨胀、质量门变重、多项目 dev server 残留。
+- 未改代码或配置;后续若要止血,优先加省算力 profile、限制工具输出、拆短线程、关闭残留 dev server,并把 `VEDASTRO_ENABLE_NETWORK` 改成显式按需开启。
+
+## 2026-07-02T23:42:00+08:00 - 全网省算力开源工具安装
+
+- 用户要求直接全网寻找最合适的省算力开源项目并解决问题。
+- 对比 `squeez`、`ccusage`、`Repomix`、`LiteLLM` 等项目后,优先选择 `squeez`,因为本次根因是 Codex/Agent 工具输出和长上下文膨胀,而不是单纯 API gateway 计费问题。
+- 已通过官方安装脚本安装 `squeez 1.34.4`;Codex 配置与 hooks 已写入 `~/.codex/squeez/`。
+- 已验证本地二进制:`~/.claude/squeez/bin/squeez --version` 输出 `squeez 1.34.4`。
+- 已启用 Hermes fallback 插件:`hermes plugins enable squeez-fallback` 成功,提示下一 session 生效。
+- 已验证 `ccusage` 可通过 `npx` 临时运行,版本 `20.0.14`;本轮未全局安装。
+- 仍需用户重启 Codex/相关 CLI,让已安装 hooks 在新 session 中生效。
diff --git a/scripts/diagnose_external_engine_adapters.py b/scripts/diagnose_external_engine_adapters.py
new file mode 100644
index 00000000..7ee0ef05
--- /dev/null
+++ b/scripts/diagnose_external_engine_adapters.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+"""Aggregate external-engine adapter readiness diagnostics."""
+
+from __future__ import annotations
+
+import argparse
+import json
+
+try:
+ from diagnose_vedastro_mode import build_report as build_vedastro_report
+ from diagnose_pyjhora_adapter import build_report as build_pyjhora_report
+ from diagnose_jyotishganit_adapter import build_report as build_jyotishganit_report
+except Exception: # pragma: no cover - import path varies in tests/CLI
+ from scripts.diagnose_vedastro_mode import build_report as build_vedastro_report
+ from scripts.diagnose_pyjhora_adapter import build_report as build_pyjhora_report
+ from scripts.diagnose_jyotishganit_adapter import build_report as build_jyotishganit_report
+
+
+def build_report() -> dict:
+ vedastro = build_vedastro_report()
+ pyjhora = build_pyjhora_report()
+ jyotishganit = build_jyotishganit_report()
+ engines = {
+ "VedAstro": {
+ "status": "available" if vedastro["official_ready"] else "blocked",
+ "mode": vedastro["mode"],
+ "readiness_blockers": vedastro["readiness_blockers"],
+ "official_closure_plan": vedastro.get("official_closure_plan", {}),
+ },
+ "PyJHora/JHora": {
+ "status": pyjhora["status"],
+ "adapter_command": pyjhora["adapter_command"],
+ "missing_dependency": pyjhora["missing_dependency"],
+ "install_hint": pyjhora.get("install_hint", {}),
+ "license_boundary": pyjhora.get("license_boundary"),
+ "ephemeris_data_note": pyjhora.get("ephemeris_data_note"),
+ },
+ "jyotishganit": {
+ "status": jyotishganit["status"],
+ "adapter_path": jyotishganit["adapter_path"],
+ "license": jyotishganit["license"],
+ },
+ }
+ return {
+ "scope": "external_engine_adapter_diagnostics",
+ "status": "complete" if all(engine["status"] == "available" for engine in engines.values()) else "partial",
+ "engines": engines,
+ "boundary": "Readiness diagnostics only; this does not run a three-engine consultation comparison.",
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--json", action="store_true", help="print machine-readable JSON")
+ args = parser.parse_args()
+ report = build_report()
+ if args.json:
+ print(json.dumps(report, ensure_ascii=False, sort_keys=True))
+ else:
+ print(f"External engine adapter status: {report['status']}")
+ for name, engine in report["engines"].items():
+ print(f"{name}: {engine['status']}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/diagnose_jyotishganit_adapter.py b/scripts/diagnose_jyotishganit_adapter.py
new file mode 100644
index 00000000..4e80824e
--- /dev/null
+++ b/scripts/diagnose_jyotishganit_adapter.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+"""Report whether the local jyotishganit reference checkout is importable."""
+
+from __future__ import annotations
+
+import argparse
+import importlib.util
+import json
+import os
+import sys
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+DEFAULT_ADAPTER_RELATIVE = "references/open_source_sources/jyotishganit"
+
+
+def build_report() -> dict:
+ adapter_relative = os.environ.get("JYOTISHGANIT_ADAPTER_PATH", DEFAULT_ADAPTER_RELATIVE).strip() or DEFAULT_ADAPTER_RELATIVE
+ adapter = ROOT / adapter_relative
+ package_dir = adapter / "jyotishganit"
+ license_file = adapter / "LICENSE"
+
+ if not adapter.exists() or not package_dir.exists():
+ status = "missing_checkout"
+ importable = False
+ error = None
+ else:
+ sys.path.insert(0, str(adapter))
+ try:
+ importable = importlib.util.find_spec("jyotishganit") is not None
+ status = "available" if importable else "runtime_error"
+ error = None if importable else "package_not_importable"
+ except Exception as exc: # pragma: no cover - defensive diagnostic
+ importable = False
+ status = "runtime_error"
+ error = f"{type(exc).__name__}: {exc}"
+ finally:
+ try:
+ sys.path.remove(str(adapter))
+ except ValueError:
+ pass
+
+ return {
+ "scope": "jyotishganit_adapter_diagnostics",
+ "status": status,
+ "adapter_path": adapter_relative,
+ "checkout_exists": adapter.exists(),
+ "package_exists": package_dir.exists(),
+ "importable": importable,
+ "license": "MIT" if license_file.exists() else "unknown",
+ "error": error,
+ "boundary": "This is a reference-checkout readiness smoke check only; it does not run jyotishganit calculations.",
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--json", action="store_true", help="print machine-readable JSON")
+ args = parser.parse_args()
+ report = build_report()
+ if args.json:
+ print(json.dumps(report, ensure_ascii=False, sort_keys=True))
+ else:
+ print(f"jyotishganit adapter status: {report['status']}")
+ print(f"Adapter path: {report['adapter_path']}")
+ if report["error"]:
+ print(f"Error: {report['error']}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/diagnose_pyjhora_adapter.py b/scripts/diagnose_pyjhora_adapter.py
new file mode 100644
index 00000000..d194602c
--- /dev/null
+++ b/scripts/diagnose_pyjhora_adapter.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+"""Report whether the PyJHora comparison adapter can run in this environment."""
+
+from __future__ import annotations
+
+import argparse
+import importlib.util
+import json
+import os
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+ADAPTER_RELATIVE = "benchmarks/jyotish/scripts/run_pyjhora_compare.py"
+
+
+def build_report() -> dict:
+ adapter = ROOT / ADAPTER_RELATIVE
+ module_name = os.environ.get("PYJHORA_MODULE_NAME", "jhora").strip() or "jhora"
+ adapter_exists = adapter.exists()
+ module_available = importlib.util.find_spec(module_name) is not None
+
+ if not adapter_exists:
+ status = "missing_adapter"
+ elif not module_available:
+ status = "missing_dependency"
+ else:
+ status = "available"
+
+ return {
+ "scope": "pyjhora_adapter_diagnostics",
+ "status": status,
+ "adapter_command": f"python3 {ADAPTER_RELATIVE}",
+ "adapter_exists": adapter_exists,
+ "dependency_module": module_name,
+ "dependency_available": module_available,
+ "missing_dependency": None if module_available else module_name,
+ "install_hint": {
+ "package": "PyJHora",
+ "commands": ["pip install PyJHora"],
+ "note": "Install in an isolated optional benchmark environment, not as a hard runtime dependency.",
+ },
+ "license_boundary": "AGPL external benchmark only; do not vendor or make it a runtime dependency.",
+ "ephemeris_data_note": "Recent PyJHora releases may require separate Swiss Ephemeris data download/configuration before full chart comparison can run.",
+ "boundary": "This is an adapter readiness smoke check only; it does not run PyJHora chart comparison.",
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--json", action="store_true", help="print machine-readable JSON")
+ args = parser.parse_args()
+ report = build_report()
+ if args.json:
+ print(json.dumps(report, ensure_ascii=False, sort_keys=True))
+ else:
+ print(f"PyJHora adapter status: {report['status']}")
+ print(f"Adapter command: {report['adapter_command']}")
+ if report["missing_dependency"]:
+ print(f"Missing dependency: {report['missing_dependency']}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/diagnose_vedastro_mode.py b/scripts/diagnose_vedastro_mode.py
index 10b1ef9b..bd3a6c43 100644
--- a/scripts/diagnose_vedastro_mode.py
+++ b/scripts/diagnose_vedastro_mode.py
@@ -38,13 +38,20 @@ def build_report() -> dict:
network_enabled = _bool_env("VEDASTRO_ENABLE_NETWORK")
timeout_seconds = _timeout_seconds()
has_api_key = bool(os.environ.get("VEDASTRO_API_KEY", "").strip())
+ free_tier_queue_enabled = _bool_env("VEDASTRO_FREE_TIER_QUEUE") or _bool_env("VEDASTRO_FREE_TIER_QUEUE_ENABLED") or _bool_env("VEDASTRO_ENABLE_FREE_TIER_QUEUE")
missing = []
+ readiness_blockers = []
if not endpoint:
missing.append("VEDASTRO_API_ENDPOINT")
+ readiness_blockers.append("missing_endpoint")
if not network_enabled:
missing.append("VEDASTRO_ENABLE_NETWORK=1")
+ readiness_blockers.append("network_disabled")
if timeout_seconds <= FAST_TIMEOUT_THRESHOLD_SECONDS:
missing.append("VEDASTRO_TIMEOUT_SECONDS>5")
+ readiness_blockers.append("timeout_too_low")
+ if endpoint and network_enabled and not has_api_key:
+ readiness_blockers.append("premium_key_missing")
official_ready = not missing
mode = "official_extended" if official_ready else "fast_local_fallback"
return {
@@ -54,12 +61,26 @@ def build_report() -> dict:
"network_enabled": network_enabled,
"timeout_seconds": timeout_seconds,
"has_api_key": has_api_key,
+ "free_tier_queue_enabled": free_tier_queue_enabled,
+ "free_tier_possible_with_cache_queue": bool(endpoint and network_enabled and timeout_seconds > FAST_TIMEOUT_THRESHOLD_SECONDS),
"missing": missing,
+ "readiness_blockers": readiness_blockers,
"expected_fallback_status": (
"none_if_official_endpoint_responds"
if official_ready
else "official_snapshot_budget_exhausted_or_endpoint_blocked"
),
+ "official_closure_plan": {
+ "required_env": {
+ "VEDASTRO_API_ENDPOINT": endpoint or "https://api.vedastro.org/api",
+ "VEDASTRO_ENABLE_NETWORK": "1",
+ "VEDASTRO_TIMEOUT_SECONDS": "20",
+ "VEDASTRO_API_KEY": "optional_but_recommended_for_stable_full_snapshot",
+ },
+ "free_tier_policy": "Queue/cache can reduce throttling, but free tier may still return blocked or partial snapshots.",
+ "premium_key_policy": "API key recommended for stable official full snapshot; free tier may still block or throttle.",
+ "raw_response_acceptance": "vedastro_official.raw_response must be present before claiming official cloud closure.",
+ },
"next_step": (
"Run full-reading or strict_workflow; verify vedastro_official.status is ok/partial."
if official_ready
@@ -82,6 +103,10 @@ def main() -> int:
print(f"network_enabled: {str(report['network_enabled']).lower()}")
print(f"timeout_seconds: {report['timeout_seconds']}")
print(f"has_api_key: {str(report['has_api_key']).lower()}")
+ print(f"free_tier_possible_with_cache_queue: {str(report['free_tier_possible_with_cache_queue']).lower()}")
+ print(f"free_tier_queue_enabled: {str(report['free_tier_queue_enabled']).lower()}")
+ if report["readiness_blockers"]:
+ print(f"readiness_blockers: {', '.join(report['readiness_blockers'])}")
if report["missing"]:
print("missing:")
for item in report["missing"]:
@@ -93,4 +118,3 @@ def main() -> int:
if __name__ == "__main__":
raise SystemExit(main())
-
diff --git a/scripts/export_chart_research_pdf.py b/scripts/export_chart_research_pdf.py
new file mode 100644
index 00000000..d2ba584c
--- /dev/null
+++ b/scripts/export_chart_research_pdf.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python3
+"""Export the chart research book root to a simple readable PDF."""
+
+from __future__ import annotations
+
+import argparse
+import re
+from pathlib import Path
+
+from reportlab.lib.colors import HexColor
+from reportlab.lib.enums import TA_CENTER
+from reportlab.lib.pagesizes import A4
+from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
+from reportlab.lib.units import mm
+from reportlab.pdfbase.pdfmetrics import registerFont
+from reportlab.pdfbase.ttfonts import TTFont
+from reportlab.platypus import PageBreak, Paragraph, Preformatted, SimpleDocTemplate, Spacer
+
+
+FONT_CANDIDATES = [
+ ("/System/Library/Fonts/Supplemental/Songti.ttc", 0),
+ ("/System/Library/Fonts/Supplemental/Arial Unicode.ttf", 0),
+ ("/Library/Fonts/Arial Unicode.ttf", 0),
+]
+
+
+def _parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description="Export chart research book root to PDF")
+ parser.add_argument(
+ "--report-root",
+ default="docs/reports/chart_research_REDACTED_DATE_REDACTED_TIME",
+ help="Path to the chart research report root",
+ )
+ parser.add_argument(
+ "--output",
+ default=None,
+ help="Output PDF path; defaults to /exports/chart_research_REDACTED_DATE_REDACTED_TIME.pdf",
+ )
+ return parser.parse_args()
+
+
+def _load_order(book_path: Path) -> list[Path]:
+ text = book_path.read_text(encoding="utf-8")
+ links = re.findall(r"\]\(\./([^)]+\.md)\)", text)
+ return [book_path.parent / link for link in links]
+
+
+def _clean_inline(text: str) -> str:
+ text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
+ text = text.replace("`", "")
+ text = text.replace("**", "")
+ text = text.replace("*", "")
+ return text.strip()
+
+
+def _register_book_font() -> str:
+ font_name = "ChartResearchBookFont"
+ for font_path, subfont_index in FONT_CANDIDATES:
+ path = Path(font_path)
+ if not path.exists():
+ continue
+ registerFont(TTFont(font_name, str(path), subfontIndex=subfont_index))
+ return font_name
+ raise RuntimeError(
+ "No renderable CJK font found for PDF export. Tried: "
+ + ", ".join(path for path, _ in FONT_CANDIDATES)
+ )
+
+
+def _styles():
+ font_name = _register_book_font()
+ base = getSampleStyleSheet()
+ title = ParagraphStyle(
+ "BookTitle",
+ parent=base["Title"],
+ fontName=font_name,
+ fontSize=22,
+ leading=28,
+ alignment=TA_CENTER,
+ textColor=HexColor("#2c241c"),
+ spaceAfter=18,
+ )
+ h1 = ParagraphStyle(
+ "H1",
+ parent=base["Heading1"],
+ fontName=font_name,
+ fontSize=18,
+ leading=24,
+ textColor=HexColor("#2f2a24"),
+ spaceBefore=10,
+ spaceAfter=8,
+ )
+ h2 = ParagraphStyle(
+ "H2",
+ parent=base["Heading2"],
+ fontName=font_name,
+ fontSize=14,
+ leading=20,
+ textColor=HexColor("#3d352c"),
+ spaceBefore=8,
+ spaceAfter=6,
+ )
+ h3 = ParagraphStyle(
+ "H3",
+ parent=base["Heading3"],
+ fontName=font_name,
+ fontSize=12,
+ leading=17,
+ textColor=HexColor("#4a4036"),
+ spaceBefore=6,
+ spaceAfter=4,
+ )
+ body = ParagraphStyle(
+ "Body",
+ parent=base["BodyText"],
+ fontName=font_name,
+ fontSize=10.5,
+ leading=16,
+ textColor=HexColor("#332b24"),
+ spaceAfter=4,
+ )
+ bullet = ParagraphStyle(
+ "Bullet",
+ parent=body,
+ leftIndent=10,
+ firstLineIndent=0,
+ )
+ mono = ParagraphStyle(
+ "Mono",
+ parent=body,
+ fontName=font_name,
+ fontSize=8.8,
+ leading=12,
+ )
+ return {"title": title, "h1": h1, "h2": h2, "h3": h3, "body": body, "bullet": bullet, "mono": mono}
+
+
+def _render_markdown(md_path: Path, styles: dict[str, ParagraphStyle]) -> list:
+ text = md_path.read_text(encoding="utf-8")
+ lines = text.splitlines()
+ flow = []
+ i = 0
+
+ while i < len(lines):
+ line = lines[i].rstrip()
+ stripped = line.strip()
+ if not stripped:
+ flow.append(Spacer(1, 4))
+ i += 1
+ continue
+
+ if stripped.startswith("|"):
+ block = []
+ while i < len(lines) and lines[i].strip().startswith("|"):
+ block.append(lines[i].rstrip())
+ i += 1
+ flow.append(Preformatted("\n".join(block), styles["mono"]))
+ flow.append(Spacer(1, 5))
+ continue
+
+ if stripped.startswith("# "):
+ flow.append(Paragraph(_clean_inline(stripped[2:]), styles["h1"]))
+ i += 1
+ continue
+
+ if stripped.startswith("## "):
+ flow.append(Paragraph(_clean_inline(stripped[3:]), styles["h2"]))
+ i += 1
+ continue
+
+ if stripped.startswith("### "):
+ flow.append(Paragraph(_clean_inline(stripped[4:]), styles["h3"]))
+ i += 1
+ continue
+
+ if stripped.startswith("> "):
+ flow.append(Paragraph(_clean_inline(stripped[2:]), styles["body"]))
+ i += 1
+ continue
+
+ if stripped.startswith("- "):
+ flow.append(Paragraph("• " + _clean_inline(stripped[2:]), styles["bullet"]))
+ i += 1
+ continue
+
+ if re.match(r"^\d+\.\s", stripped):
+ flow.append(Paragraph(_clean_inline(stripped), styles["bullet"]))
+ i += 1
+ continue
+
+ para = [stripped]
+ i += 1
+ while i < len(lines):
+ nxt = lines[i].strip()
+ if not nxt or nxt.startswith(("#", "|", "-", ">")) or re.match(r"^\d+\.\s", nxt):
+ break
+ para.append(nxt)
+ i += 1
+ flow.append(Paragraph(_clean_inline(" ".join(para)), styles["body"]))
+
+ return flow
+
+
+def build_pdf(report_root: Path, output_pdf: Path) -> Path:
+ styles = _styles()
+ md_files = _load_order(report_root / "book.md")
+
+ story = [
+ Spacer(1, 25 * mm),
+ Paragraph("B.V. Raman / Parashara / Jaimini Comprehensive Chart Research", styles["title"]),
+ Paragraph("REDACTED_DATE REDACTED_TIME UTC+8 | REDACTED_PLACE Fengfeng, Hebei | Raman ayanamsa | Mean Node", styles["body"]),
+ Spacer(1, 12 * mm),
+ Paragraph("Markdown-first export generated from the report book root.", styles["body"]),
+ PageBreak(),
+ ]
+
+ for idx, md_path in enumerate(md_files):
+ if idx > 0:
+ story.append(PageBreak())
+ story.extend(_render_markdown(md_path, styles))
+
+ output_pdf.parent.mkdir(parents=True, exist_ok=True)
+ doc = SimpleDocTemplate(
+ str(output_pdf),
+ pagesize=A4,
+ leftMargin=18 * mm,
+ rightMargin=18 * mm,
+ topMargin=18 * mm,
+ bottomMargin=18 * mm,
+ title="B.V. Raman / Parashara / Jaimini Comprehensive Chart Research",
+ author="Codex + repo runtime outputs",
+ )
+ doc.build(story)
+ return output_pdf
+
+
+def main() -> int:
+ args = _parse_args()
+ report_root = Path(args.report_root).resolve()
+ output_pdf = (
+ Path(args.output).resolve()
+ if args.output
+ else report_root / "exports" / "chart_research_REDACTED_DATE_REDACTED_TIME.pdf"
+ )
+ build_pdf(report_root, output_pdf)
+ print(output_pdf)
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/historical_event_backtest.py b/scripts/historical_event_backtest.py
index b737cbe2..e271ca6a 100644
--- a/scripts/historical_event_backtest.py
+++ b/scripts/historical_event_backtest.py
@@ -5,8 +5,14 @@ from __future__ import annotations
import argparse
import json
+import sys
+from pathlib import Path
from typing import Any
+ROOT = Path(__file__).resolve().parents[1]
+if str(ROOT) not in sys.path:
+ sys.path.insert(0, str(ROOT))
+
import mcp_server
diff --git a/scripts/interpretation_source_inventory_gate.py b/scripts/interpretation_source_inventory_gate.py
index 75c9d30f..4f27209d 100644
--- a/scripts/interpretation_source_inventory_gate.py
+++ b/scripts/interpretation_source_inventory_gate.py
@@ -180,6 +180,48 @@ def _is_candidate(path: Path) -> bool:
def _classify_candidate(path: str, runtime_source_refs: set[str], layer_refs: set[str]) -> dict[str, Any]:
+ if path in runtime_source_refs and path in {
+ "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",
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/qa_rules.md",
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/yogas.md",
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/chart_reading_rules.md",
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/validation_rules.md",
+ }:
+ return {
+ "classification": "runtime_reference_layer",
+ "priority": "runtime",
+ "promotion_status": "already_wired",
+ "reason": "Already exposed through interpretation_source_pack.source_refs.",
+ }
+ if path.startswith("references/real_case_studies/"):
+ return {
+ "classification": "real_case_calibration",
+ "priority": "priority_1",
+ "promotion_status": "reference_layer_candidate",
+ "reason": "Real-case material should be reviewed before confidence calibration claims.",
+ }
+ if path.startswith("references/open_source_sources/rishi-ai-mcp/"):
+ return {
+ "classification": "open_source_reference",
+ "priority": "priority_1",
+ "promotion_status": "already_wired" if path in runtime_source_refs else "reference_layer_candidate",
+ "reason": "User-prioritized open-source skill/workflow corpus; must remain license-aware.",
+ }
+ if path.startswith("references/open_source_sources/vedic-astro-skills/"):
+ return {
+ "classification": "open_source_reference",
+ "priority": "priority_1",
+ "promotion_status": "already_wired" if path in runtime_source_refs else "reference_layer_candidate",
+ "reason": "User-prioritized open-source Jyotish skills corpus; classify before selective reuse.",
+ }
+ if path.startswith("references/open_source_sources/"):
+ return {
+ "classification": "open_source_reference",
+ "priority": "priority_2",
+ "promotion_status": "reference_layer_candidate",
+ "reason": "Open-source reference corpus; classify with license boundary before runtime use.",
+ }
if path in runtime_source_refs:
return {
"classification": "runtime_reference_layer",
@@ -194,34 +236,6 @@ def _classify_candidate(path: str, runtime_source_refs: set[str], layer_refs: se
"promotion_status": "indexed",
"reason": "Indexed by the interpretation source inventory.",
}
- if path.startswith("references/real_case_studies/"):
- return {
- "classification": "real_case_calibration",
- "priority": "priority_1",
- "promotion_status": "reference_layer_candidate",
- "reason": "Real-case material should be reviewed before confidence calibration claims.",
- }
- if path.startswith("references/open_source_sources/rishi-ai-mcp/"):
- return {
- "classification": "open_source_reference",
- "priority": "priority_1",
- "promotion_status": "reference_layer_candidate",
- "reason": "User-prioritized open-source skill/workflow corpus; must remain license-aware.",
- }
- if path.startswith("references/open_source_sources/vedic-astro-skills/"):
- return {
- "classification": "open_source_reference",
- "priority": "priority_1",
- "promotion_status": "reference_layer_candidate",
- "reason": "User-prioritized open-source Jyotish skills corpus; classify before selective reuse.",
- }
- if path.startswith("references/open_source_sources/"):
- return {
- "classification": "open_source_reference",
- "priority": "priority_2",
- "promotion_status": "reference_layer_candidate",
- "reason": "Open-source reference corpus; classify with license boundary before runtime use.",
- }
if path.startswith("references/oracle/"):
return {
"classification": "oracle_artifact",
diff --git a/scripts/interpretation_source_runtime_coverage.py b/scripts/interpretation_source_runtime_coverage.py
new file mode 100644
index 00000000..efb1a78f
--- /dev/null
+++ b/scripts/interpretation_source_runtime_coverage.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+"""Machine-checkable runtime coverage summary for interpretation sources."""
+
+from __future__ import annotations
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+
+PROVEN_RUNTIME_MARKERS = [
+ "dasha_timing_layer_used",
+ "varga_strength_layer_used",
+ "annual_special_layer_context",
+ "modifier_obstacle_layer_used",
+]
+
+NOT_FULLY_CLOSED = [
+ "references/open_source_sources/jyotishganit",
+ "references/open_source_sources/jaimini-tropical",
+ "references/open_source_sources/VedicAstro",
+ "references/open_source_sources/rishi-ai-mcp",
+ "references/open_source_sources/vedic-astro-skills",
+ "references/open_source_sources/dashaflow",
+]
+
+
+def build_report() -> dict:
+ completed = subprocess.run(
+ [sys.executable, "scripts/interpretation_source_inventory_gate.py"],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=120,
+ check=False,
+ )
+ inventory = {}
+ if completed.returncode == 0 and completed.stdout.strip():
+ inventory = json.loads(completed.stdout)
+ source_pack_status = (
+ inventory.get("source_pack_status")
+ if isinstance(inventory, dict)
+ else None
+ ) or "unknown"
+ return {
+ "scope": "interpretation_source_runtime_coverage",
+ "status": "partial",
+ "source_pack_status": source_pack_status,
+ "proven_runtime_markers": PROVEN_RUNTIME_MARKERS,
+ "runtime_visibility_status": "partial",
+ "not_fully_closed": NOT_FULLY_CLOSED,
+ "inventory_gate": {
+ "status": inventory.get("status") if isinstance(inventory, dict) else "unavailable",
+ "summary": inventory.get("summary") if isinstance(inventory, dict) else {},
+ },
+ "boundary": (
+ "Inventory/grading exists, but runtime invocation is only proven for surfaced "
+ "strict-workflow markers, not every local source asset."
+ ),
+ }
+
+
+def main() -> int:
+ print(json.dumps(build_report(), ensure_ascii=False, indent=2))
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/jaimini.py b/scripts/jaimini.py
index fe53ff0a..5ab4125b 100644
--- a/scripts/jaimini.py
+++ b/scripts/jaimini.py
@@ -218,6 +218,31 @@ def calc_upapada(asc_sign_idx: int, planet_longitudes: Dict[str, float]) -> Opti
return result
+def calc_darapada(asc_sign_idx: int, planet_longitudes: Dict[str, float]) -> Optional[Dict]:
+ """计算Darapada/A7(第7宫Arudha),用于伴侣外显画像与关系维持审计。"""
+ seventh_house_idx = (asc_sign_idx + 6) % 12
+ result = calc_arudha_pada_for_house(seventh_house_idx, planet_longitudes)
+ if not result:
+ return None
+ result = dict(result)
+ second_idx = (result['sign_idx'] + 1) % 12
+ eighth_idx = (result['sign_idx'] + 7) % 12
+ result.update({
+ 'house_num': 7,
+ 'source_house_num': 7,
+ 'name': 'Darapada (A7)',
+ 'second_from_a7': _sign_name(second_idx),
+ 'second_from_a7_lord': SIGN_LORDS[_sign_name(second_idx)],
+ 'eighth_from_a7': _sign_name(eighth_idx),
+ 'eighth_from_a7_lord': SIGN_LORDS[_sign_name(eighth_idx)],
+ 'description': (
+ f"A7在{result['sign']},第二宫为{_sign_name(second_idx)},"
+ f"第八宫为{_sign_name(eighth_idx)},用于伴侣外显画像与关系维持压力审计。"
+ ),
+ })
+ return result
+
+
def calc_graha_padas(planet_longitudes: Dict[str, float]) -> Dict:
"""计算行星Graha Pada:行星位置通过其宫主映射出的外显影像。"""
results = {}
diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py
index 7f963aac..d80a8e62 100644
--- a/scripts/jyotish_api_server.py
+++ b/scripts/jyotish_api_server.py
@@ -73,8 +73,10 @@ def execute_consultation_workflow(
)
executed_steps = []
known_steps = [
+ 'run_prashna',
'compute_chart',
'run_rectification_gate',
+ 'run_muhurta_panchanga',
'run_historical_event_backtest',
'run_thematic_report',
]
@@ -89,16 +91,45 @@ def execute_consultation_workflow(
'executed_steps': [],
'skipped_steps': known_steps,
}
+ result['runtime_evidence_log'] = _UNIFIED_CONSULTATION_ORCHESTRATOR.runtime_evidence_log(
+ surface=surface,
+ entry_mode=entry_mode,
+ route_packet=route_packet,
+ executed_steps=[],
+ skipped_steps=known_steps,
+ blind=bool(body.get('blind') or body.get('blind_technical_mode')),
+ )
if body.get('return_high_rigor_shape'):
result['endpoint'] = 'high_rigor_workflow'
return result
chart = dict(chart_override) if isinstance(chart_override, dict) else {}
+ prashna = {}
rectification = {}
+ muhurta_panchanga = {}
computed_chart = bool(chart)
for step in runtime_planner.get('sync_steps', []):
- if step == 'run_rectification_gate':
+ if step == 'run_prashna':
+ prashna = handler._compute_prashna({
+ **birth_payload,
+ 'question': body.get('question', 'general'),
+ 'question_text': body.get('question_text', ''),
+ 'horary_number': body.get('horary_number'),
+ 'planets': body.get('planets', {}),
+ 'asc_degree': body.get('asc_degree', 15.5),
+ })
+ executed_steps.append('run_prashna')
+ elif step == 'run_muhurta_panchanga':
+ muhurta_panchanga = handler._compute_muhurta_panchanga({
+ **birth_payload,
+ 'reference_date': body.get('reference_date') or body.get('transit_date') or body.get('today') or body.get('current_date'),
+ 'question': question,
+ 'themes': themes,
+ 'activity': body.get('muhurta_activity'),
+ })
+ executed_steps.append('run_muhurta_panchanga')
+ elif step == 'run_rectification_gate':
chart_planets = chart.get('planets') if isinstance(chart, dict) else {}
chart_ascendant = chart.get('ascendant') if isinstance(chart, dict) else {}
rectification = handler._compute_rectification_gate({
@@ -121,6 +152,8 @@ def execute_consultation_workflow(
executed_steps.append('run_historical_event_backtest')
chart_for_theme = dict(chart) if isinstance(chart, dict) else {}
+ if entry_mode == 'prashna' and isinstance(prashna, dict):
+ chart_for_theme.setdefault('prashna', prashna)
if isinstance(chart_for_theme.get('modules'), dict):
chart_for_theme.update(chart_for_theme.get('modules', {}).get('chart') or {})
@@ -128,6 +161,7 @@ def execute_consultation_workflow(
prompt_snapshot = (((chart.get('ai_prompt_pack') or {}).get('evidence_snapshot')) or {}) if isinstance(chart, dict) else {}
strict_workflow_contracts = prompt_snapshot.get('strict_workflow_contracts') if isinstance(prompt_snapshot.get('strict_workflow_contracts'), dict) else {}
chart_guided_topics = modules.get('guided_topics') if isinstance(modules.get('guided_topics'), list) else []
+ audited_remedies = handler._build_audited_remedies_from_guided_topics(chart_guided_topics)
thematic_report = {}
if 'run_thematic_report' in runtime_planner.get('sync_steps', []):
@@ -149,7 +183,30 @@ def execute_consultation_workflow(
executed_steps.append('run_thematic_report')
vedastro_official = handler._high_rigor_vedastro_official_summary(chart)
+ runtime_truth = vedastro_official.get('runtime_truth') if isinstance(vedastro_official.get('runtime_truth'), dict) else {}
+ interpretation_source_runtime_coverage = handler._interpretation_source_runtime_coverage(chart)
skipped_steps = [step for step in known_steps if step not in executed_steps]
+ machine_evidence_packet = _UNIFIED_CONSULTATION_ORCHESTRATOR.machine_evidence_packet(
+ chart=chart,
+ route_packet=route_packet,
+ vedastro_official=vedastro_official,
+ )
+ real_case_calibration = _UNIFIED_CONSULTATION_ORCHESTRATOR.real_case_calibration_catalog(
+ route_packet=route_packet,
+ machine_evidence_packet=machine_evidence_packet,
+ )
+ runtime_evidence_log = _UNIFIED_CONSULTATION_ORCHESTRATOR.runtime_evidence_log(
+ surface=surface,
+ entry_mode=entry_mode,
+ route_packet=route_packet,
+ executed_steps=executed_steps,
+ skipped_steps=skipped_steps,
+ vedastro_official=vedastro_official,
+ interpretation_source_runtime_coverage=interpretation_source_runtime_coverage,
+ machine_evidence_packet=machine_evidence_packet,
+ real_case_calibration=real_case_calibration,
+ blind=bool(body.get('blind') or body.get('blind_technical_mode')),
+ )
result = {
'success': True,
@@ -187,7 +244,15 @@ def execute_consultation_workflow(
'rectification': rectification,
'historical_event_backtest': historical_backtest,
'thematic_report': thematic_report,
+ 'prashna': prashna,
+ 'muhurta_panchanga': muhurta_panchanga,
+ 'audited_remedies': audited_remedies,
'vedastro_official': vedastro_official,
+ 'runtime_truth': runtime_truth,
+ 'interpretation_source_runtime_coverage': interpretation_source_runtime_coverage,
+ 'machine_evidence_packet': machine_evidence_packet,
+ 'real_case_calibration': real_case_calibration,
+ 'runtime_evidence_log': runtime_evidence_log,
'next_questions': handler._high_rigor_next_questions(rectification, historical_backtest),
'boundary': (
'This endpoint composes existing project workflows. It does not claim that every VedAstro callable '
@@ -244,6 +309,15 @@ def _api_chart_cache_ttl_seconds() -> float:
return max(ttl, 0.0)
+def _free_tier_queue_enabled_env() -> bool:
+ raw_values = [
+ str(os.environ.get("VEDASTRO_FREE_TIER_QUEUE", "")).strip().lower(),
+ str(os.environ.get("VEDASTRO_FREE_TIER_QUEUE_ENABLED", "")).strip().lower(),
+ str(os.environ.get("VEDASTRO_ENABLE_FREE_TIER_QUEUE", "")).strip().lower(),
+ ]
+ return any(value in {"1", "true", "yes", "on"} for value in raw_values)
+
+
def _vedastro_runtime_fingerprint() -> dict:
endpoint = os.environ.get('VEDASTRO_API_ENDPOINT', '').strip()
return {
@@ -1623,6 +1697,47 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
surface=body.get('surface', 'api_web'),
)
+ def _build_audited_remedies_from_guided_topics(self, guided_topics):
+ if not isinstance(guided_topics, list):
+ return {'status': 'blocked', 'reason': 'guided_topics_missing'}
+ selected_gate = None
+ selected_topic = None
+ for topic in guided_topics:
+ if not isinstance(topic, dict):
+ continue
+ gate = topic.get('strict_audit_gate')
+ if isinstance(gate, dict):
+ selected_gate = gate
+ selected_topic = topic
+ break
+ if not isinstance(selected_gate, dict):
+ return {'status': 'blocked', 'reason': 'strict_audit_gate_missing'}
+ strength_context = selected_gate.get('strength_context') if isinstance(selected_gate.get('strength_context'), dict) else {}
+ dosha_context = selected_gate.get('dosha_context') if isinstance(selected_gate.get('dosha_context'), list) else []
+ active_dasha_lord = selected_gate.get('active_dasha_lord') if isinstance(selected_gate.get('active_dasha_lord'), str) else ''
+ if not strength_context:
+ return {'status': 'blocked', 'reason': 'strength_context_missing', 'source': 'strict_audit_gate'}
+ payload = self._compute_remedies({
+ 'shadbala': strength_context,
+ 'doshas': dosha_context,
+ 'dasha_lord': active_dasha_lord,
+ })
+ if not isinstance(payload, dict):
+ payload = {'recommendations': payload}
+ return {
+ 'status': 'ok',
+ 'source': 'strict_audit_gate',
+ 'topic': (
+ selected_gate.get('topic')
+ or selected_topic.get('id')
+ or selected_topic.get('title')
+ or 'general'
+ ),
+ 'active_dasha_lord': active_dasha_lord,
+ 'recommendations': payload.get('recommendations') or payload,
+ 'raw': payload,
+ }
+
def _compute_high_rigor_workflow_sync(self, body):
body_copy = dict(body or {})
body_copy.pop('async', None)
@@ -1969,6 +2084,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
if not isinstance(prompt_full_snapshot, dict):
prompt_full_snapshot = {}
modules = chart.get('modules') if isinstance(chart, dict) else {}
+ if not isinstance(modules, dict):
+ modules = {}
range_scan = modules.get('vedastro_range_scan_result') if isinstance(modules, dict) else {}
if not isinstance(range_scan, dict):
range_scan = {}
@@ -1976,6 +2093,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
official_snapshot = range_scan.get('official_full_snapshot') if isinstance(range_scan, dict) else {}
if not isinstance(official_snapshot, dict):
official_snapshot = {}
+ if not official_snapshot and isinstance(modules.get('vedastro_official_full_snapshot'), dict):
+ official_snapshot = modules.get('vedastro_official_full_snapshot') or {}
metadata = official_snapshot.get('source_metadata') if isinstance(official_snapshot, dict) else {}
catalog = metadata.get('official_full_capability_catalog') if isinstance(metadata, dict) else {}
if not isinstance(catalog, dict):
@@ -2024,13 +2143,94 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
if isinstance(selection, dict) and isinstance(selection.get('report_reference'), dict)
}
)
- return {
- 'status': (
- prompt_official.get('status')
- or official_snapshot.get('status')
- or range_scan.get('status')
- or 'blocked'
+ status = (
+ prompt_official.get('status')
+ or official_snapshot.get('status')
+ or range_scan.get('status')
+ or 'blocked'
+ )
+ chart_core_status = 'blocked'
+ official_primary_evidence = (
+ primary_contract.get('official_primary_evidence')
+ or prompt_official.get('official_primary_evidence')
+ or {}
+ )
+ if not isinstance(official_primary_evidence, dict):
+ official_primary_evidence = {}
+ chart_core = official_primary_evidence.get('chart_core')
+ if isinstance(chart_core, dict) and chart_core.get('status'):
+ chart_core_status = chart_core.get('status')
+ elif full_snapshot_payload.get('available'):
+ chart_core_status = 'ok'
+ event_radar_status = 'blocked'
+ if (
+ prompt_official.get('blocked_items')
+ or prompt_official.get('fallback_used')
+ or prompt_official.get('conflicts')
+ ):
+ event_radar_status = 'partial'
+ elif range_scan.get('status') == 'ok':
+ event_radar_status = 'ok'
+ elif range_scan.get('status'):
+ event_radar_status = 'partial'
+ runtime_truth = {
+ 'status': status,
+ 'catalog_boundary': 'catalog_recognized_not_full_runtime_execution',
+ 'primary_route': strict_workflow_primary_route or _selected_route,
+ 'routes_available': strict_workflow_routes_available,
+ 'official_execution_layers': {
+ 'chart_core': chart_core_status,
+ 'event_radar': event_radar_status,
+ 'catalog_status': (
+ prompt_official.get('official_full_capability_catalog_status')
+ or catalog.get('status')
+ or range_metadata.get('official_full_capability_catalog_status')
+ or official_snapshot.get('status')
+ or 'blocked'
+ ),
+ },
+ 'fallback_active': bool(
+ primary_contract.get('fallback_used')
+ or prompt_official.get('fallback_used')
),
+ 'blocked_items': (
+ primary_contract.get('blocked_items')
+ or prompt_official.get('blocked_items')
+ or []
+ ),
+ 'conflicts': (
+ primary_contract.get('conflicts')
+ or prompt_official.get('conflicts')
+ or []
+ ),
+ 'free_tier_strategy': {
+ 'using_free_tier': not bool(os.environ.get('VEDASTRO_API_KEY', '').strip()),
+ 'queue_enabled': _free_tier_queue_enabled_env(),
+ 'cache_hit': bool(
+ (((official_snapshot.get('source_metadata') or {}).get('semantic_cache') or {}).get('cache_hit'))
+ if isinstance(official_snapshot, dict)
+ else False
+ ),
+ 'guard_status': (
+ 'degraded_or_partial'
+ if status in {'partial', 'blocked', 'official_snapshot_budget_exhausted'}
+ or bool(prompt_official.get('blocked_items'))
+ else 'within_free_tier_strategy'
+ ),
+ },
+ }
+ raw_response = (
+ official_snapshot.get('raw_response')
+ or official_snapshot.get('official_raw_response')
+ or official_snapshot.get('raw_payload')
+ or official_snapshot.get('raw')
+ or prompt_full_snapshot.get('raw_response')
+ or prompt_full_snapshot.get('official_raw_response')
+ or prompt_full_snapshot.get('raw_payload')
+ or prompt_full_snapshot.get('raw')
+ )
+ return {
+ 'status': status,
'range_scan_status': range_scan.get('status') if isinstance(range_scan, dict) else None,
'event_count': int(range_scan.get('event_count', 0) or 0) if isinstance(range_scan, dict) else 0,
'official_full_capability_catalog_status': (
@@ -2090,12 +2290,58 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
'verdict': primary_contract.get('verdict'),
'dominant_label': primary_contract.get('dominant_label'),
'main_conflicts': primary_contract.get('main_conflicts') or primary_contract.get('conflicts') or [],
+ 'runtime_truth': runtime_truth,
+ 'raw_response': raw_response,
'boundary': 'VedAstro official snapshot and capability catalog are consumed as primary evidence metadata; execution breadth depends on configured network and sample limits.',
}
+ def _interpretation_source_runtime_coverage(self, chart):
+ modules = chart.get('modules') if isinstance(chart, dict) else {}
+ if not isinstance(modules, dict):
+ modules = {}
+ prompt_pack = chart.get('ai_prompt_pack') if isinstance(chart, dict) else {}
+ evidence_snapshot = prompt_pack.get('evidence_snapshot') if isinstance(prompt_pack, dict) else {}
+ interpretation_pack = evidence_snapshot.get('interpretation_source_pack') if isinstance(evidence_snapshot.get('interpretation_source_pack'), dict) else {}
+ candidates = {
+ 'dasha_timing_layer_used',
+ 'varga_strength_layer_used',
+ 'annual_special_layer_context',
+ 'modifier_obstacle_layer_used',
+ }
+ proven_markers = []
+ guided_topics = modules.get('guided_topics') if isinstance(modules.get('guided_topics'), list) else []
+ for topic in guided_topics:
+ if not isinstance(topic, dict):
+ continue
+ strict_gate = topic.get('strict_audit_gate')
+ if not isinstance(strict_gate, dict):
+ continue
+ secondary = strict_gate.get('secondary_context')
+ if not isinstance(secondary, list):
+ continue
+ for item in secondary:
+ if isinstance(item, str) and item in candidates and item not in proven_markers:
+ proven_markers.append(item)
+ return {
+ 'source_pack_status': interpretation_pack.get('status') or 'used',
+ 'proven_runtime_markers': proven_markers,
+ 'runtime_visibility_status': 'partial' if proven_markers else 'blocked',
+ 'not_fully_closed': [
+ 'references/open_source_sources/jyotishganit',
+ 'references/open_source_sources/jaimini-tropical',
+ 'references/open_source_sources/VedicAstro',
+ 'references/open_source_sources/rishi-ai-mcp',
+ 'references/open_source_sources/vedic-astro-skills',
+ 'references/open_source_sources/dashaflow',
+ ],
+ 'boundary': 'Inventory/grading exists, but full runtime invocation is only proven for surfaced strict-workflow markers, not every local source asset.',
+ }
+
def _high_rigor_next_questions(self, rectification, historical_backtest):
questions = []
summary = rectification.get('summary') if isinstance(rectification, dict) else {}
+ if not isinstance(summary, dict):
+ summary = {}
for item in summary.get('recommended_events') or []:
questions.append({
'type': 'yes_no_or_date',
@@ -5424,6 +5670,42 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
raise BadRequest(str(e)) from e
return {'success': True, 'endpoint': 'panchanga_range', 'report': report}
+ def _compute_muhurta_panchanga(self, body):
+ reference_date = body.get('reference_date') or body.get('transit_date') or body.get('today') or datetime.now().strftime('%Y-%m-%d')
+ if not isinstance(reference_date, str):
+ raise BadRequest('reference_date must be a string')
+ date_str = reference_date[:10]
+ try:
+ datetime.strptime(date_str, '%Y-%m-%d')
+ except ValueError as e:
+ raise BadRequest('reference_date must be YYYY-MM-DD') from e
+ raw_activity = body.get('activity')
+ if raw_activity is not None and not isinstance(raw_activity, str):
+ raise BadRequest('activity must be a string')
+ activity = (raw_activity or '').strip().lower()
+ question_text = str(body.get('question') or '')
+ themes = body.get('themes') if isinstance(body.get('themes'), list) else []
+ if activity not in {'marriage', 'business', 'travel', 'medical', 'education'}:
+ if 'marriage' in themes or any(token in question_text for token in ('婚', '恋', 'marry', 'wedding', 'relationship')):
+ activity = 'marriage'
+ elif any(token in question_text for token in ('travel', '迁移', '搬家', '出行')):
+ activity = 'travel'
+ elif any(token in question_text for token in ('medical', '手术', '治疗', '健康')):
+ activity = 'medical'
+ elif any(token in question_text for token in ('education', '学习', '考试', '申请')):
+ activity = 'education'
+ else:
+ activity = 'business'
+ muhurta = _load_local_module('muhurta')
+ return muhurta.build_muhurta_sidecar(
+ date_str=date_str,
+ activity=activity,
+ lat=self._get_float(body, 'lat', 0, -90, 90),
+ lon=self._get_float(body, 'lon', 0, -180, 180),
+ tz=self._get_float(body, 'tz', 0, -14, 14),
+ ayanamsa_name=body.get('ayanamsa', 'lahiri'),
+ )
+
def _compute_rectification_gate(self, body):
asc_lon = self._asc_lon_from_body(body)
declared_accuracy = body.get('declared_accuracy', body.get('accuracy', 'minute'))
diff --git a/scripts/kp_system.py b/scripts/kp_system.py
index 59fa3e5d..c1e4cf96 100644
--- a/scripts/kp_system.py
+++ b/scripts/kp_system.py
@@ -10,6 +10,7 @@ KP (Krishnamurti Paddhati) 占星系统模块
3. House Significator ABCD体系
"""
+from datetime import datetime, timedelta
from typing import Dict, List, Tuple, Optional
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
@@ -33,6 +34,7 @@ NAKSHATRAS = [
VIMSHOTTARI_DURATION = [7, 20, 6, 10, 7, 18, 16, 19, 17]
KP_LORDS = ["Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury"]
STAR_LORDS = KP_LORDS * 3 # 27 Nakshatras = 3 cycles of 9 lords
+VIMSHOTTARI_YEARS = dict(zip(KP_LORDS, VIMSHOTTARI_DURATION))
NAKSHATRA_SPAN = 360.0 / 27.0 # 13.333... degrees
@@ -258,3 +260,102 @@ def calc_kp_analysis(planet_positions: Dict, asc_sign: str = 'Aries') -> Dict:
'houses': {h['house']: {'sign': h['sign'], 'kp_lords': h['kp_lords'], 'significators': house_sig.get(h['house'], {})}
for h in houses},
}
+
+
+def _kp_next_lords(start_lord: str) -> List[str]:
+ idx = KP_LORDS.index(start_lord)
+ return KP_LORDS[idx:] + KP_LORDS[:idx]
+
+
+def _kp_years_to_days(years: float) -> float:
+ return years * 365.2425
+
+
+def _kp_birth_star_balance(moon_longitude: float) -> Tuple[str, float]:
+ moon_longitude = moon_longitude % 360.0
+ nak_idx = int(moon_longitude // NAKSHATRA_SPAN) % 27
+ star_lord = STAR_LORDS[nak_idx]
+ elapsed = (moon_longitude % NAKSHATRA_SPAN) / NAKSHATRA_SPAN
+ return star_lord, max(0.0, min(1.0, 1.0 - elapsed))
+
+
+def _kp_period_score(lords: List[str], planet_house_significators: Optional[Dict[str, Dict]] = None) -> Dict:
+ supportive_houses = {2, 5, 7, 11}
+ blocking_houses = {1, 6, 8, 10, 12}
+ supportive = 0
+ blocking = 0
+ details = {}
+ for lord in lords:
+ sig = (planet_house_significators or {}).get(lord, {})
+ houses = set()
+ for value in sig.values():
+ if isinstance(value, int):
+ houses.add(value)
+ elif isinstance(value, list):
+ houses.update(v for v in value if isinstance(v, int))
+ support_hits = sorted(houses & supportive_houses)
+ block_hits = sorted(houses & blocking_houses)
+ supportive += len(support_hits)
+ blocking += len(block_hits)
+ details[lord] = {'supportive_houses': support_hits, 'blocking_houses': block_hits}
+ score = supportive - blocking
+ if score >= 2:
+ judgement = 'supportive'
+ elif score <= -2:
+ judgement = 'blocking'
+ else:
+ judgement = 'mixed'
+ return {
+ 'marriage_score': score,
+ 'supportive_hits': supportive,
+ 'blocking_hits': blocking,
+ 'judgement': judgement,
+ 'lord_details': details,
+ }
+
+
+def calc_kp_dba_timeline(
+ birth_datetime: datetime,
+ moon_longitude: float,
+ target_start: datetime,
+ target_end: datetime,
+ planet_house_significators: Optional[Dict[str, Dict]] = None,
+) -> Dict:
+ """Build Vimshottari MD/AD/PD windows for KP-style marriage timing review."""
+ birth_star_lord, balance = _kp_birth_star_balance(moon_longitude)
+ periods = []
+ md_start = birth_datetime
+ for md_i, md_lord in enumerate(_kp_next_lords(birth_star_lord) * 3):
+ md_years = VIMSHOTTARI_YEARS[md_lord] * (balance if md_i == 0 else 1.0)
+ md_end = md_start + timedelta(days=_kp_years_to_days(md_years))
+ ad_start = md_start
+ for ad_lord in _kp_next_lords(md_lord):
+ ad_years = md_years * VIMSHOTTARI_YEARS[ad_lord] / 120.0
+ ad_end = ad_start + timedelta(days=_kp_years_to_days(ad_years))
+ pd_start = ad_start
+ for pd_lord in _kp_next_lords(ad_lord):
+ pd_years = ad_years * VIMSHOTTARI_YEARS[pd_lord] / 120.0
+ pd_end = pd_start + timedelta(days=_kp_years_to_days(pd_years))
+ if pd_end >= target_start and pd_start <= target_end:
+ scored = _kp_period_score([md_lord, ad_lord, pd_lord], planet_house_significators)
+ periods.append({
+ 'md_lord': md_lord,
+ 'ad_lord': ad_lord,
+ 'pd_lord': pd_lord,
+ 'start': pd_start.isoformat(),
+ 'end': pd_end.isoformat(),
+ **scored,
+ })
+ pd_start = pd_end
+ ad_start = ad_end
+ md_start = md_end
+ if md_start > target_end:
+ break
+ return {
+ 'method': 'KP DBA timeline (Vimshottari MD/AD/PD)',
+ 'birth_star_lord': birth_star_lord,
+ 'birth_star_balance_fraction': balance,
+ 'target_start': target_start.isoformat(),
+ 'target_end': target_end.isoformat(),
+ 'periods': periods,
+ }
diff --git a/scripts/muhurta.py b/scripts/muhurta.py
index a5069e3c..3fff9fc5 100644
--- a/scripts/muhurta.py
+++ b/scripts/muhurta.py
@@ -1507,6 +1507,60 @@ def muhurta_range_search(
}
+def build_muhurta_sidecar(
+ *,
+ date_str: str,
+ activity: str = 'business',
+ lat: Optional[float] = None,
+ lon: Optional[float] = None,
+ tz: Optional[float] = None,
+ ayanamsa_name: str = 'lahiri',
+) -> Dict:
+ """Compact Muhurta/Panchanga packet for unified workflow consumers."""
+ activity = activity if activity in ACTIVITY_RULES else 'business'
+ search = muhurta_range_search(
+ date_str,
+ date_str,
+ activity=activity,
+ limit=3,
+ lat=lat,
+ lon=lon,
+ tz=tz,
+ ayanamsa_name=ayanamsa_name,
+ )
+ calendar = panchanga_range_report(
+ date_str,
+ date_str,
+ activity=activity,
+ lat=lat,
+ lon=lon,
+ tz=tz,
+ ayanamsa_name=ayanamsa_name,
+ )
+ days = calendar.get('days') or []
+ first_day = days[0] if days else {}
+ return {
+ 'status': 'ok',
+ 'source': 'local_muhurta.py',
+ 'date': date_str,
+ 'activity': activity,
+ 'activity_label': ACTIVITY_RULES[activity]['name'],
+ 'report_mode': search.get('mode', 'muhurta_date_range_solver'),
+ 'panchanga': {
+ 'query_date': first_day.get('query_date', date_str),
+ 'summary': first_day.get('summary') or {},
+ 'panchanga': first_day.get('panchanga') or {},
+ 'inauspicious_periods': first_day.get('inauspicious_periods') or [],
+ 'choghadiya': first_day.get('choghadiya') or [],
+ 'hora_windows': first_day.get('hora_windows') or [],
+ 'condition_tags': first_day.get('condition_tags') or [],
+ },
+ 'best_windows': search.get('best_windows') or [],
+ 'calculation_policy': calendar.get('calculation_policy') or search.get('calculation_policy') or {},
+ 'next_action': search.get('next_action') or 'Use as timing sidecar only; final judgement still needs chart-based workflow.',
+ }
+
+
def _build_muhurta_candidate(row: Dict, activity: str, avoid_inauspicious_periods: bool) -> Dict:
summary = row.get('summary') or {}
panchanga = row.get('panchanga') or {}
diff --git a/scripts/pre_work_check.py b/scripts/pre_work_check.py
new file mode 100644
index 00000000..6185032c
--- /dev/null
+++ b/scripts/pre_work_check.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+"""One-command pre-work governance check.
+
+Runs the lightweight guardrails that should happen before substantial work:
+ledger/docs presence, git status visibility, fragment scan, remote visibility,
+and focused governance tests.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+from typing import Any
+
+
+ROOT = Path(__file__).resolve().parents[1]
+PYTHON = sys.executable
+DEFAULT_COMMAND_TIMEOUT_SECONDS = 45
+DEFAULT_FRAGMENT_TIMEOUT_SECONDS = 90
+FOCUSED_TEST_TARGETS = [
+ "tests/test_runtime_import_boundaries.py",
+ "tests/test_project_fragment_governance.py",
+ "tests/test_preflight_fragment_scan.py",
+ "tests/test_remote_repo_visibility_check.py",
+ "tests/test_pre_work_check.py",
+]
+EXTERNAL_ENGINE_DIAGNOSTIC_TARGET = "scripts/diagnose_external_engine_adapters.py"
+PRE_WORK_DOCS = [
+ "AGENTS.md",
+ "docs/research/pre_work_error_ledger.md",
+ "docs/research/whole_machine_fragment_sweep_2026_07_05.md",
+ "docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md",
+]
+
+
+def run(args: list[str], timeout: int, env: dict[str, str] | None = None) -> dict[str, Any]:
+ try:
+ completed = subprocess.run(
+ args,
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=timeout,
+ check=False,
+ env=env,
+ )
+ except subprocess.TimeoutExpired as exc:
+ return {
+ "ok": False,
+ "returncode": None,
+ "stdout": exc.stdout or "",
+ "stderr": exc.stderr or "",
+ "error": f"timeout after {timeout}s",
+ }
+ return {
+ "ok": completed.returncode == 0,
+ "returncode": completed.returncode,
+ "stdout": completed.stdout,
+ "stderr": completed.stderr,
+ "error": "" if completed.returncode == 0 else (completed.stderr or completed.stdout).strip(),
+ }
+
+
+def classify_status(
+ docs_ok: bool,
+ fragment_ok: bool,
+ pytest_ok: bool,
+ remote_status: str,
+ external_engine_ok: bool = True,
+) -> str:
+ if not docs_ok or not fragment_ok or not pytest_ok or not external_engine_ok:
+ return "fail"
+ if remote_status == "verified":
+ return "pass"
+ return "pass_with_remote_blocked"
+
+
+def build_report(remote_timeout: int, command_timeout: int, fragment_timeout: int, skip_tests: bool = False) -> dict[str, Any]:
+ docs = {path: (ROOT / path).exists() for path in PRE_WORK_DOCS}
+ git_status = run(["git", "status", "--short", "--branch"], command_timeout)
+ git_remote = run(["git", "remote", "-v"], command_timeout)
+ fragment = run([PYTHON, "scripts/preflight_fragment_scan.py"], fragment_timeout)
+ external_engine = run([PYTHON, EXTERNAL_ENGINE_DIAGNOSTIC_TARGET, "--json"], command_timeout)
+ remote = run([PYTHON, "scripts/remote_repo_visibility_check.py", "--timeout", str(remote_timeout)], command_timeout)
+ remote_report: dict[str, Any] = {}
+ if remote["ok"]:
+ try:
+ remote_report = json.loads(remote["stdout"])
+ except json.JSONDecodeError as exc:
+ remote_report = {"status": "blocked", "must_not_claim_synced": True, "parse_error": str(exc)}
+ pytest_result = {"ok": True, "stdout": "skipped", "stderr": "", "error": ""}
+ if not skip_tests:
+ env = dict(os.environ)
+ if fragment["ok"] and fragment.get("stdout"):
+ cache = Path(tempfile.gettempdir()) / "jyotish_preflight_fragment_scan_report.json"
+ cache.write_text(fragment["stdout"], encoding="utf-8")
+ env["PREFLIGHT_FRAGMENT_SCAN_REPORT"] = str(cache)
+ pytest_result = run([PYTHON, "-m", "pytest", "-q", *FOCUSED_TEST_TARGETS], command_timeout, env=env)
+ remote_status = str(remote_report.get("status") or "blocked")
+ status = classify_status(
+ docs_ok=all(docs.values()),
+ fragment_ok=fragment["ok"],
+ pytest_ok=pytest_result["ok"],
+ remote_status=remote_status,
+ external_engine_ok=external_engine["ok"],
+ )
+ return {
+ "scope": "pre_work_check",
+ "status": status,
+ "must_not_claim_synced": remote_report.get("must_not_claim_synced", True),
+ "docs": docs,
+ "git": {
+ "status_ok": git_status["ok"],
+ "status": git_status["stdout"],
+ "remote_ok": git_remote["ok"],
+ "remote": git_remote["stdout"],
+ },
+ "checks": {
+ "fragment_scan_ok": fragment["ok"],
+ "external_engine_adapters_ok": external_engine["ok"],
+ "remote_visibility_status": remote_status,
+ "remote_visibility_ok": remote["ok"],
+ "focused_tests_ok": pytest_result["ok"],
+ },
+ "errors": {
+ "fragment_scan": fragment["error"],
+ "external_engine_adapters": external_engine["error"],
+ "remote_visibility": remote["error"],
+ "focused_tests": pytest_result["error"],
+ },
+ "focused_test_targets": FOCUSED_TEST_TARGETS,
+ "external_engine_diagnostic_target": EXTERNAL_ENGINE_DIAGNOSTIC_TARGET,
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--remote-timeout", type=int, default=8)
+ parser.add_argument("--command-timeout", type=int, default=DEFAULT_COMMAND_TIMEOUT_SECONDS)
+ parser.add_argument("--fragment-timeout", type=int, default=DEFAULT_FRAGMENT_TIMEOUT_SECONDS)
+ parser.add_argument("--skip-tests", action="store_true")
+ args = parser.parse_args()
+ report = build_report(args.remote_timeout, args.command_timeout, args.fragment_timeout, args.skip_tests)
+ print(json.dumps(report, ensure_ascii=False, indent=2))
+ return 1 if report["status"] == "fail" else 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/preflight_fragment_scan.py b/scripts/preflight_fragment_scan.py
index cfa63108..b34f85c0 100644
--- a/scripts/preflight_fragment_scan.py
+++ b/scripts/preflight_fragment_scan.py
@@ -31,6 +31,9 @@ EXTERNAL_WORK_BRAIN_DIR = Path("/Users/wuyongnaren/.gemini/antigravity-ide/brain
DISTRIBUTION_MIRROR_DIR = Path("/Users/wuyongnaren/.workbuddy/skills/jyotish-vedic-astrology")
ORACLE_FILE = ROOT / "references" / "oracle" / "dasha_shadbala_oracle_cases.json"
REPO_CLEANUP_MAP = ROOT / "docs" / "research" / "repo_cleanup_promotion_map_2026_07_01.md"
+ERROR_LEDGER = ROOT / "docs" / "research" / "pre_work_error_ledger.md"
+LATEST_SWEEP = ROOT / "docs" / "research" / "whole_machine_fragment_sweep_2026_07_05.md"
+ROUND25_SWEEP = ROOT / "docs" / "research" / "whole_machine_fragment_sweep_round25_2026_06_25.md"
def _read_text(path: Path) -> str:
@@ -300,6 +303,26 @@ def build_report() -> dict[str, Any]:
"/Users/wuyongnaren/.workbuddy/skills/jyotish-vedic-astrology",
],
},
+ "governance": {
+ "pre_work_error_ledger": {
+ "path": str(ERROR_LEDGER),
+ "exists": ERROR_LEDGER.exists(),
+ },
+ "latest_fragment_sweep": {
+ "path": str(LATEST_SWEEP),
+ "exists": LATEST_SWEEP.exists(),
+ "remote_ref_parity": "blocked_until_git_ls_remote_succeeds",
+ },
+ "prior_fragment_sweep": {
+ "path": str(ROUND25_SWEEP),
+ "exists": ROUND25_SWEEP.exists(),
+ },
+ "acceptance_command": (
+ "python3 -m pytest -q tests/test_runtime_import_boundaries.py "
+ "tests/test_project_fragment_governance.py tests/test_preflight_fragment_scan.py "
+ "tests/test_remote_repo_visibility_check.py tests/test_pre_work_check.py"
+ ),
+ },
"boundary": (
"Run this preflight scan before major work so drafts, mirrors, and external-work-brain "
"fragments are reviewed deliberately, and so engineering-surface success is not mistaken "
diff --git a/scripts/print_cline_mcp_config.py b/scripts/print_cline_mcp_config.py
new file mode 100644
index 00000000..588180f7
--- /dev/null
+++ b/scripts/print_cline_mcp_config.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+"""Print or install Cline MCP config for this Jyotish repo."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import sys
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def build_config(repo_root: Path, python_bin: str) -> dict:
+ repo_root = repo_root.resolve()
+ return {
+ "mcpServers": {
+ "jyotish": {
+ "command": python_bin,
+ "args": [str(repo_root / "mcp_server.py")],
+ "cwd": str(repo_root),
+ "env": {
+ "PYTHONPATH": str(repo_root / "scripts"),
+ },
+ }
+ }
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--repo-root", default=str(ROOT), help="repo root for generated paths")
+ parser.add_argument("--python", default=sys.executable, help="Python executable for Cline to run")
+ parser.add_argument(
+ "--install-project",
+ action="store_true",
+ help="write project-local .cline/mcp.json for this checkout",
+ )
+ args = parser.parse_args()
+ repo_root = Path(args.repo_root).resolve()
+ config = build_config(repo_root, args.python)
+ text = json.dumps(config, ensure_ascii=False, indent=2)
+ if args.install_project:
+ target = repo_root / ".cline" / "mcp.json"
+ target.parent.mkdir(parents=True, exist_ok=True)
+ target.write_text(text + "\n", encoding="utf-8")
+ print(str(target))
+ return 0
+ print(text)
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
+
diff --git a/scripts/remote_repo_visibility_check.py b/scripts/remote_repo_visibility_check.py
new file mode 100644
index 00000000..c970e87e
--- /dev/null
+++ b/scripts/remote_repo_visibility_check.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+"""Read-only remote repository visibility diagnostic.
+
+This is a guardrail, not a sync tool. It never pushes, fetches, or mutates the
+worktree. It records whether terminal git can verify remote refs and whether
+GitHub's API is reachable as a fallback visibility signal.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import re
+import subprocess
+import sys
+from pathlib import Path
+from typing import Any
+from urllib import request
+
+
+ROOT = Path(__file__).resolve().parents[1]
+DEFAULT_GITHUB_SLUG = "732642856/yinduzhanxing"
+
+
+def run_command(args: list[str], timeout: int) -> dict[str, Any]:
+ try:
+ completed = subprocess.run(
+ args,
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=timeout,
+ check=False,
+ )
+ except subprocess.TimeoutExpired as exc:
+ return {"ok": False, "error": f"timeout after {timeout}s: {' '.join(args)}", "stdout": exc.stdout or "", "stderr": exc.stderr or ""}
+ return {
+ "ok": completed.returncode == 0,
+ "returncode": completed.returncode,
+ "stdout": completed.stdout,
+ "stderr": completed.stderr,
+ "error": "" if completed.returncode == 0 else (completed.stderr or completed.stdout).strip(),
+ }
+
+
+def github_slug_from_remote_url(url: str) -> str | None:
+ patterns = [
+ r"github\.com[:/](?P[^/]+)/(?P[^/.]+)(?:\.git)?$",
+ r"github\.com/(?P[^/]+)/(?P[^/.]+)(?:\.git)?$",
+ ]
+ for pattern in patterns:
+ match = re.search(pattern, url.strip())
+ if match:
+ return f"{match.group('owner')}/{match.group('repo')}"
+ return None
+
+
+def parse_ls_remote(stdout: str) -> dict[str, Any]:
+ heads: dict[str, str] = {}
+ tags: dict[str, str] = {}
+ for line in stdout.splitlines():
+ parts = line.split()
+ if len(parts) != 2:
+ continue
+ sha, ref = parts
+ if ref.startswith("refs/heads/"):
+ heads[ref.removeprefix("refs/heads/")] = sha
+ elif ref.startswith("refs/tags/") and not ref.endswith("^{}"):
+ tags[ref.removeprefix("refs/tags/")] = sha
+ return {"heads": heads, "tags": tags, "ref_count": len(heads) + len(tags)}
+
+
+def git_remote_urls(timeout: int) -> list[str]:
+ urls: list[str] = []
+ for args in (["git", "remote", "get-url", "origin"], ["git", "remote", "get-url", "--push", "origin"]):
+ result = run_command(args, timeout)
+ if result["ok"]:
+ url = result["stdout"].strip()
+ if url and url not in urls:
+ urls.append(url)
+ return urls
+
+
+def git_ls_remote(url: str, timeout: int) -> dict[str, Any]:
+ result = run_command(["git", "ls-remote", "--heads", "--tags", url], timeout)
+ parsed = parse_ls_remote(result.get("stdout", "")) if result["ok"] else {"heads": {}, "tags": {}, "ref_count": 0}
+ return {"method": "git_ls_remote", "url": url, **parsed, "ok": result["ok"], "error": result.get("error", "")}
+
+
+def github_api_branches(slug: str, timeout: int) -> dict[str, Any]:
+ url = f"https://api.github.com/repos/{slug}/branches"
+ req = request.Request(url, headers={"User-Agent": "jyotish-preflight/1.0", "Accept": "application/vnd.github+json"})
+ try:
+ with request.urlopen(req, timeout=timeout) as response:
+ payload = json.loads(response.read().decode("utf-8"))
+ except Exception as exc: # network/SSL/status errors are diagnostic data here
+ return {"method": "github_api_branches", "url": url, "ok": False, "branches": [], "error": f"{type(exc).__name__}: {exc}"}
+ branches = [item.get("name", "") for item in payload if isinstance(item, dict)]
+ return {"method": "github_api_branches", "url": url, "ok": bool(branches), "branches": branches, "error": ""}
+
+
+def build_report(timeout: int) -> dict[str, Any]:
+ local_branch = run_command(["git", "rev-parse", "--abbrev-ref", "HEAD"], timeout)
+ local_head = run_command(["git", "rev-parse", "HEAD"], timeout)
+ urls = git_remote_urls(timeout)
+ slug = next((github_slug_from_remote_url(url) for url in urls if github_slug_from_remote_url(url)), DEFAULT_GITHUB_SLUG)
+ git_checks = [git_ls_remote(url, timeout) for url in urls] or [git_ls_remote(f"https://github.com/{slug}.git", timeout)]
+ api_check = github_api_branches(slug, timeout)
+ git_verified = any(check["ok"] and check["ref_count"] > 0 for check in git_checks)
+ api_visible = api_check["ok"]
+ status = "verified" if git_verified else ("web_visible_git_blocked" if api_visible else "blocked")
+ return {
+ "scope": "remote_repo_visibility_check",
+ "status": status,
+ "must_not_claim_synced": status != "verified",
+ "local": {
+ "branch": local_branch.get("stdout", "").strip(),
+ "head": local_head.get("stdout", "").strip(),
+ },
+ "github_slug": slug,
+ "git_checks": git_checks,
+ "github_api": api_check,
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--timeout", type=int, default=12)
+ args = parser.parse_args()
+ print(json.dumps(build_report(args.timeout), ensure_ascii=False, indent=2))
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/run_quality_gate.py b/scripts/run_quality_gate.py
index fdaa8529..698f6adb 100644
--- a/scripts/run_quality_gate.py
+++ b/scripts/run_quality_gate.py
@@ -35,6 +35,7 @@ EXTRA_COMPILE_TARGETS = [
ROOT / "scripts" / "oracle_boundary_audit.py",
ROOT / "scripts" / "oracle_collection_queue.py",
ROOT / "scripts" / "oracle_evidence_validator.py",
+ ROOT / "scripts" / "sync_final_evidence_packet_status.py",
ROOT / "scripts" / "deployment_preflight.py",
ROOT / "tests" / "run_golden_cases.py",
ROOT / "tests" / "run_real_case_revalidation.py",
@@ -53,6 +54,17 @@ CORE_PYTEST_TARGETS = [
"tests/test_external_oracle_sanity_closure.py",
]
+RUNTIME_TRUTH_PYTEST_TARGETS = [
+ "tests/test_api_server_security.py::test_high_rigor_vedastro_official_summary_passes_through_contract_fields",
+ "tests/test_api_server_security.py::test_high_rigor_vedastro_official_summary_exposes_top_reader_contract_from_full_snapshot",
+ "tests/test_vedastro_external_technique_evidence.py::test_strict_workflow_uses_shared_consultation_executor",
+ "tests/test_vedastro_runtime_mode_diagnostics.py",
+ "tests/test_interpretation_source_inventory_gate.py::test_quality_gate_runs_interpretation_source_inventory_gate",
+ "tests/test_interpretation_source_runtime_coverage.py",
+ "tests/test_final_jhora_evidence_packet_acceptance.py",
+ "tests/test_frontend_productization.py::test_result_page_surfaces_workflow_summary_and_provenance_detail",
+]
+
RELEASE_CRITICAL_UNTRACKED_PATHS = [
"docs/research/desktop_packaging_spike_2026_06_23.md",
"docs/research/ephemeris_abstraction_feasibility_2026_06_23.md",
@@ -160,6 +172,19 @@ QUALITY_GATE_PROFILES = {
"skip_local_accuracy_report": True,
"skip_vedastro_live": False,
},
+ "runtime-truth": {
+ "skip_slow": True,
+ "skip_yoga_logic": True,
+ "skip_frontend_runtime": True,
+ "skip_frontend_click": True,
+ "frontend_click_mode": "core",
+ "check_release_hygiene": False,
+ "skip_real_cases": True,
+ "skip_dasha_audit": True,
+ "skip_oracle_audit": True,
+ "skip_local_accuracy_report": True,
+ "skip_vedastro_live": True,
+ },
}
DASHA_REFERENCE_AUDIT_CMD = [
@@ -287,18 +312,14 @@ def format_failure_summary(
def run(cmd: list[str], *, optional: bool = False, step: str | None = None, cwd: Path = ROOT) -> bool:
label = step or " ".join(cmd[:2])
print(f"\n$ {' '.join(cmd)}")
- completed = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
- if completed.stdout:
- print(completed.stdout, end="" if completed.stdout.endswith("\n") else "\n")
- if completed.stderr:
- print(completed.stderr, end="" if completed.stderr.endswith("\n") else "\n", file=sys.stderr)
+ completed = subprocess.run(cmd, cwd=cwd, text=True)
if completed.returncode == 0:
return True
if optional:
print(f"Optional step failed with exit code {completed.returncode}; continuing.")
return False
print(
- format_failure_summary(label, cmd, completed.returncode, stdout=completed.stdout, stderr=completed.stderr, cwd=cwd),
+ format_failure_summary(label, cmd, completed.returncode, stdout="", stderr="", cwd=cwd),
file=sys.stderr,
)
raise SystemExit(completed.returncode)
@@ -459,7 +480,7 @@ def run_profile(args: argparse.Namespace) -> dict:
def main() -> int:
parser = argparse.ArgumentParser(description="Run Jyotish skill quality gate")
- parser.add_argument("--profile", choices=["quick", "browser", "release", "accuracy", "vedastro-live"], default="browser", help="Quality gate profile: quick, browser, release, accuracy, or vedastro-live")
+ parser.add_argument("--profile", choices=["quick", "browser", "release", "accuracy", "vedastro-live", "runtime-truth"], default="browser", help="Quality gate profile: quick, browser, release, accuracy, vedastro-live, or runtime-truth")
parser.add_argument("--skip-slow", action="store_true", help="Skip slow golden-case regressions")
parser.add_argument("--skip-yoga-logic", action="store_true", help="Skip Yoga logic comparison report refresh")
parser.add_argument("--skip-frontend-runtime", action="store_true", help="Skip frontend build and runtime smoke")
@@ -478,17 +499,37 @@ def main() -> int:
os.environ.setdefault("PYTHONPATH", str(ROOT / "scripts"))
print(f"\n== Quality gate profile: {args.profile} ==")
print(json.dumps(profile, ensure_ascii=False, indent=2))
- compile_targets()
- validate_json_files()
- run([PYTHON, "scripts/audit_capabilities.py", "--mode", "validate"])
- run([PYTHON, "scripts/audit_fragments.py", "--strict"])
- run([PYTHON, "scripts/interpretation_source_inventory_gate.py"])
- run([PYTHON, "scripts/character_level_inventory_manifest.py", "--scope", "project", "--no-write", "--summary-only"])
- run([PYTHON, "scripts/deployment_preflight.py"])
- if profile["check_release_hygiene"]:
- release_hygiene_check()
- run([PYTHON, "scripts/validate_bphs_invariants.py"])
- pytest_targets = ["tests"] if args.all_tests else CORE_PYTEST_TARGETS
+ if args.profile == "runtime-truth":
+ for target in [
+ ROOT / "scripts" / "jyotish_api_server.py",
+ ROOT / "scripts" / "diagnose_vedastro_mode.py",
+ ROOT / "scripts" / "diagnose_external_engine_adapters.py",
+ ROOT / "scripts" / "interpretation_source_runtime_coverage.py",
+ ROOT / "scripts" / "sync_final_evidence_packet_status.py",
+ ]:
+ py_compile.compile(str(target), doraise=True)
+ print(f"compiled {target.relative_to(ROOT)}")
+ run([PYTHON, "scripts/sync_final_evidence_packet_status.py"])
+ run([PYTHON, "scripts/interpretation_source_inventory_gate.py"])
+ run([PYTHON, "scripts/diagnose_vedastro_mode.py", "--json"])
+ run([PYTHON, "scripts/diagnose_external_engine_adapters.py", "--json"])
+ else:
+ compile_targets()
+ validate_json_files()
+ run([PYTHON, "scripts/audit_capabilities.py", "--mode", "validate"])
+ run([PYTHON, "scripts/audit_fragments.py", "--strict"])
+ run([PYTHON, "scripts/interpretation_source_inventory_gate.py"])
+ run([PYTHON, "scripts/character_level_inventory_manifest.py", "--scope", "project", "--no-write", "--summary-only"])
+ run([PYTHON, "scripts/deployment_preflight.py"])
+ if profile["check_release_hygiene"]:
+ release_hygiene_check()
+ run([PYTHON, "scripts/validate_bphs_invariants.py"])
+ if args.all_tests:
+ pytest_targets = ["tests"]
+ elif args.profile == "runtime-truth":
+ pytest_targets = RUNTIME_TRUTH_PYTEST_TARGETS
+ else:
+ pytest_targets = CORE_PYTEST_TARGETS
run([PYTHON, "-m", "pytest", *pytest_targets])
if not profile["skip_frontend_runtime"]:
run(["npm", "run", "build"], optional=False, cwd=APP)
diff --git a/scripts/sync_final_evidence_packet_status.py b/scripts/sync_final_evidence_packet_status.py
new file mode 100644
index 00000000..69f36aa9
--- /dev/null
+++ b/scripts/sync_final_evidence_packet_status.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+"""Sync latest final JHora evidence packet metadata with its numeric version."""
+
+from __future__ import annotations
+
+import json
+import re
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+WORK_DIR = ROOT / "scratch" / "local" / "pdf_review_123456"
+PACKET_RE = re.compile(r"\.v(\d+)\.json$")
+
+
+def latest_packet() -> tuple[int, Path]:
+ packets: list[tuple[int, Path]] = []
+ for path in WORK_DIR.glob("jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v*.json"):
+ match = PACKET_RE.search(path.name)
+ if match:
+ packets.append((int(match.group(1)), path))
+ if not packets:
+ raise SystemExit("no versioned JHora master evidence packets found")
+ return max(packets)
+
+
+def main() -> int:
+ version, path = latest_packet()
+ packet = json.loads(path.read_text(encoding="utf-8"))
+ metadata = packet.setdefault("metadata", {})
+ wanted_version = f"v{version}"
+ changed = False
+ for key, value in {
+ "status": "final_output_v1",
+ "current_version": wanted_version,
+ "packet_version": wanted_version,
+ "canonical_packet": path.name,
+ }.items():
+ if metadata.get(key) != value:
+ metadata[key] = value
+ changed = True
+ if packet.get("status") != "final_output_v1":
+ packet["status"] = "final_output_v1"
+ changed = True
+ if changed:
+ path.write_text(json.dumps(packet, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
+
+ ledger = WORK_DIR / "evidence_packet_status_ledger_REDACTED_DATE_REDACTED_TIME.md"
+ if ledger.exists():
+ text = ledger.read_text(encoding="utf-8")
+ line_re = re.compile(
+ r"\| Master evidence packet \| `jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME\.v\d+\.json` \| active \| Current canonical structured packet\. \|"
+ )
+ wanted_line = f"| Master evidence packet | `{path.name}` | active | Current canonical structured packet. |"
+ new_text = line_re.sub(wanted_line, text, count=1)
+ if new_text != text:
+ ledger.write_text(new_text, encoding="utf-8")
+
+ print(f"synced {path.name}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/unified_consultation_orchestrator.py b/scripts/unified_consultation_orchestrator.py
index 6a3a48eb..d743bdd6 100644
--- a/scripts/unified_consultation_orchestrator.py
+++ b/scripts/unified_consultation_orchestrator.py
@@ -5,8 +5,24 @@
from __future__ import annotations
from dataclasses import dataclass
+from pathlib import Path
from typing import Any
+try:
+ from diagnose_pyjhora_adapter import build_report as build_pyjhora_adapter_report
+except Exception: # pragma: no cover - import path varies in tests/CLI
+ from scripts.diagnose_pyjhora_adapter import build_report as build_pyjhora_adapter_report
+
+try:
+ from diagnose_jyotishganit_adapter import build_report as build_jyotishganit_adapter_report
+except Exception: # pragma: no cover - import path varies in tests/CLI
+ from scripts.diagnose_jyotishganit_adapter import build_report as build_jyotishganit_adapter_report
+
+try:
+ from functional_benefics import derive_functional_benefic_malefic
+except Exception: # pragma: no cover - import path varies in tests/CLI
+ from scripts.functional_benefics import derive_functional_benefic_malefic
+
@dataclass(frozen=True)
class RouteDefinition:
@@ -32,6 +48,25 @@ class UnifiedConsultationOrchestrator:
"cross-check, and fallback when official calls are blocked."
),
}
+ EVIDENCE_PACKET_REQUIRED_SECTIONS = [
+ "D1",
+ "D9",
+ "D10",
+ "D2",
+ "D4",
+ "planet_degrees",
+ "house_degrees",
+ "dasha_boundaries",
+ "shadbala",
+ "ashtakavarga",
+ "yogas",
+ "UL",
+ "A7",
+ "A10",
+ "KP_cusp",
+ "external_oracle_status",
+ "vedastro_official_raw_response",
+ ]
_THEME_ALIASES = {
"relationship": "marriage",
"marriage": "marriage",
@@ -88,7 +123,7 @@ class UnifiedConsultationOrchestrator:
"career": ["compute_chart", "run_rectification_gate", "run_thematic_report"],
"relationship": ["compute_chart", "run_rectification_gate", "run_thematic_report"],
"finance": ["compute_chart", "run_rectification_gate", "run_thematic_report"],
- "timing": ["compute_chart", "run_rectification_gate", "run_thematic_report"],
+ "timing": ["compute_chart", "run_rectification_gate", "run_muhurta_panchanga", "run_thematic_report"],
"general": ["compute_chart", "run_rectification_gate", "run_thematic_report"],
}
_ASYNC_CANDIDATES = [
@@ -119,6 +154,7 @@ class UnifiedConsultationOrchestrator:
def resolve_route(self, question: str, themes: list[str] | None = None) -> dict[str, Any]:
text = (question or "").lower()
normalized_themes = themes or list(self._DEFAULT_THEMES)
+ explicit_timing_tokens = ("when", "timing", "何时", "什么时候", "应期", "几月", "哪月", "哪天", "日期")
domain_tokens = {
"career": ("career", "job", "work", "promotion", "business", "profession", "事业", "工作", "升职", "生意"),
@@ -132,7 +168,9 @@ class UnifiedConsultationOrchestrator:
if indexes:
first_hits.append((min(indexes), route_name))
- if first_hits:
+ if text.strip() and any(token in text for token in explicit_timing_tokens) and "marriage" not in normalized_themes:
+ route = self._ROUTE_DEFINITIONS["timing"]
+ elif first_hits:
route_name = sorted(first_hits, key=lambda item: item[0])[0][1]
route = self._ROUTE_DEFINITIONS[route_name]
elif not text.strip():
@@ -201,6 +239,9 @@ class UnifiedConsultationOrchestrator:
if entry_mode == "rectification":
sync_steps = [step for step in sync_steps if step != "run_rectification_gate"]
sync_steps.insert(0, "run_rectification_gate")
+ elif entry_mode == "prashna":
+ sync_steps = [step for step in sync_steps if step not in {"compute_chart", "run_rectification_gate"}]
+ sync_steps.insert(0, "run_prashna")
if high_rigor and "run_historical_event_backtest" not in sync_steps and events:
sync_steps.append("run_historical_event_backtest")
@@ -238,3 +279,400 @@ class UnifiedConsultationOrchestrator:
"availability, cache policy, and async limits."
),
}
+
+ @staticmethod
+ def _vedastro_cloud_state(vedastro_official: dict[str, Any] | None) -> str:
+ official = vedastro_official if isinstance(vedastro_official, dict) else {}
+ runtime_truth = official.get("runtime_truth") if isinstance(official.get("runtime_truth"), dict) else {}
+ layers = runtime_truth.get("official_execution_layers") if isinstance(runtime_truth.get("official_execution_layers"), dict) else {}
+ status = str(runtime_truth.get("status") or official.get("status") or "blocked")
+ fallback_active = bool(runtime_truth.get("fallback_active") or official.get("fallback_used"))
+ if fallback_active:
+ return "local_fallback"
+ if layers.get("chart_core") == "ok" and status in {"ok", "partial", "available"}:
+ return "official_verified"
+ return "official_blocked"
+
+ @staticmethod
+ def _section(value: Any, source_path: str) -> dict[str, Any]:
+ present = bool(value)
+ return {
+ "status": "used" if present else "missing",
+ "source_path": source_path,
+ }
+
+ @staticmethod
+ def _external_engine_cross_validation(vedastro_state: str) -> dict[str, Any]:
+ repo_root = Path(__file__).resolve().parents[1]
+ pyjhora_refs = [
+ repo_root / "docs/benchmark/jyotish_external_oracle_closure_master_dashboard.json",
+ repo_root / "references/oracle/artifacts/pyjhora_oracle_artifact_manifest.json",
+ ]
+ pyjhora_adapter = repo_root / "benchmarks/jyotish/scripts/run_pyjhora_compare.py"
+ pyjhora_adapter_report = build_pyjhora_adapter_report()
+ pyjhora_adapter_status = {
+ "available": "available",
+ "missing_dependency": f"blocked_missing_python_module:{pyjhora_adapter_report.get('missing_dependency') or 'jhora'}",
+ "missing_adapter": "blocked_missing_adapter_script",
+ }.get(str(pyjhora_adapter_report.get("status")), "runtime_error")
+ jyotishganit_ref = repo_root / "references/open_source_sources/jyotishganit"
+ jyotishganit_adapter_report = build_jyotishganit_adapter_report()
+
+ engines = {
+ "VedAstro": {
+ "status": vedastro_state,
+ "runtime_invoked": vedastro_state == "official_verified",
+ "source_path": "vedastro_official.runtime_truth",
+ },
+ "PyJHora/JHora": {
+ "status": (
+ "reference_available_not_runtime_invoked"
+ if any(path.exists() for path in pyjhora_refs)
+ else "blocked_no_reference_artifact"
+ ),
+ "runtime_invoked": False,
+ "adapter_command": (
+ "python3 benchmarks/jyotish/scripts/run_pyjhora_compare.py"
+ if pyjhora_adapter.exists()
+ else None
+ ),
+ "adapter_status": pyjhora_adapter_status,
+ "source_path": "docs/benchmark + references/oracle/artifacts",
+ },
+ "jyotishganit": {
+ "status": (
+ "reference_available_not_runtime_invoked"
+ if jyotishganit_ref.exists()
+ else "blocked_no_reference_checkout"
+ ),
+ "runtime_invoked": False,
+ "adapter_path": "references/open_source_sources/jyotishganit" if jyotishganit_ref.exists() else None,
+ "adapter_status": jyotishganit_adapter_report.get("status"),
+ "license": jyotishganit_adapter_report.get("license"),
+ "source_path": "references/open_source_sources/jyotishganit",
+ },
+ }
+ status = "complete" if all(item["runtime_invoked"] for item in engines.values()) else "partial"
+ return {
+ "status": status,
+ "engines": engines,
+ "boundary": (
+ "This records runtime/reference closure state only. Reference artifacts do not mean the engine was "
+ "invoked for the current consultation."
+ ),
+ }
+
+ def machine_evidence_packet(
+ self,
+ *,
+ chart: dict[str, Any] | None,
+ route_packet: dict[str, Any],
+ vedastro_official: dict[str, Any] | None = None,
+ ) -> dict[str, Any]:
+ chart_data = chart if isinstance(chart, dict) else {}
+ modules = chart_data.get("modules") if isinstance(chart_data.get("modules"), dict) else {}
+ nested_chart = chart_data.get("chart") if isinstance(chart_data.get("chart"), dict) else {}
+ base_chart = modules.get("chart") if isinstance(modules.get("chart"), dict) else nested_chart or chart_data
+ varga = modules.get("varga_full") if isinstance(modules.get("varga_full"), dict) else {}
+ special_lagnas = (
+ chart_data.get("special_lagnas")
+ if isinstance(chart_data.get("special_lagnas"), dict)
+ else modules.get("special_lagnas") if isinstance(modules.get("special_lagnas"), dict) else {}
+ )
+ ascendant = base_chart.get("ascendant") if isinstance(base_chart.get("ascendant"), dict) else {}
+ ascendant_sign = ascendant.get("sign") if isinstance(ascendant, dict) else None
+ functional_layer = derive_functional_benefic_malefic(ascendant_sign)
+ official = vedastro_official if isinstance(vedastro_official, dict) else {}
+ raw_response = (
+ official.get("raw_response")
+ or official.get("official_raw_response")
+ or official.get("raw_payload")
+ or official.get("raw")
+ )
+ sections = {
+ "D1": self._section(
+ base_chart.get("planets") and base_chart.get("ascendant"),
+ "chart.planets+chart.ascendant",
+ ),
+ "D9": self._section(varga.get("D9_Navamsa") or varga.get("D9"), "modules.varga_full.D9"),
+ "D10": self._section(varga.get("D10_Dasamsa") or varga.get("D10"), "modules.varga_full.D10"),
+ "D2": self._section(varga.get("D2_Hora") or varga.get("D2"), "modules.varga_full.D2"),
+ "D4": self._section(varga.get("D4_Chaturthamsa") or varga.get("D4"), "modules.varga_full.D4"),
+ "planet_degrees": self._section(base_chart.get("planets"), "chart.planets"),
+ "house_degrees": self._section(base_chart.get("houses") or chart_data.get("houses"), "chart.houses"),
+ "dasha_boundaries": self._section(modules.get("dasha") or chart_data.get("dasha"), "modules.dasha"),
+ "shadbala": self._section(modules.get("shadbala") or chart_data.get("shadbala"), "modules.shadbala"),
+ "ashtakavarga": self._section(modules.get("ashtakavarga") or chart_data.get("ashtakavarga"), "modules.ashtakavarga"),
+ "yogas": self._section(modules.get("yogas") or chart_data.get("yogas"), "modules.yogas"),
+ "UL": self._section(special_lagnas.get("UL") or special_lagnas.get("Upapada_Lagna"), "special_lagnas.UL"),
+ "A7": self._section(special_lagnas.get("A7") or special_lagnas.get("Darapada"), "special_lagnas.A7"),
+ "A10": self._section(special_lagnas.get("A10") or special_lagnas.get("A10_Karma_Pada"), "special_lagnas.A10"),
+ "KP_cusp": self._section(modules.get("kp") or modules.get("kp_cusps") or chart_data.get("kp_cusps"), "modules.kp_cusps"),
+ "functional_benefic_malefic": self._section(
+ functional_layer if functional_layer.get("status") == "used" else None,
+ "chart.ascendant.sign -> scripts.functional_benefics",
+ ),
+ "external_oracle_status": {
+ "status": self._vedastro_cloud_state(vedastro_official),
+ "source_path": "vedastro_official.runtime_truth",
+ },
+ "vedastro_official_raw_response": self._section(raw_response, "vedastro_official.raw_response"),
+ }
+ missing = [name for name, section in sections.items() if section.get("status") == "missing"]
+ return {
+ "status": "complete" if not missing else "partial",
+ "route": dict(route_packet),
+ "required_sections": list(self.EVIDENCE_PACKET_REQUIRED_SECTIONS),
+ "sections": sections,
+ "functional_benefic_malefic": functional_layer,
+ "missing_sections": missing,
+ }
+
+ def real_case_calibration_catalog(
+ self,
+ *,
+ route_packet: dict[str, Any],
+ machine_evidence_packet: dict[str, Any] | None = None,
+ ) -> dict[str, Any]:
+ route = route_packet.get("question_type") or route_packet.get("primary_theme") or "general"
+ if route == "marriage":
+ route = "relationship"
+ case_index_by_domain = {
+ "career": ["references/real_case_studies/vedicka/career-success-poverty-prosperity.md"],
+ "finance": ["references/real_case_studies/vedicka/career-success-poverty-prosperity.md"],
+ "relationship": ["docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json"],
+ }
+ case_profiles = {
+ "references/real_case_studies/vedicka/career-success-poverty-prosperity.md": {
+ "domains": ["career", "finance"],
+ "evidence_sections": ["D1", "D10", "dasha_boundaries", "yogas"],
+ "recorded_outcome": "poverty_to_prosperity_global_recognition",
+ "event_trigger_keywords": ["Saturn dasha poverty", "Mercury dasha breakthrough", "Ketu dasha consolidation"],
+ },
+ "docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json": {
+ "domains": ["relationship"],
+ "evidence_sections": ["D1", "D9", "UL", "dasha_boundaries"],
+ "recorded_outcome": "relationship_structure_validation_dataset",
+ "event_trigger_keywords": ["UL", "Darapada", "7th lord", "DK"],
+ },
+ }
+ candidate_refs = case_index_by_domain.get(route, [])
+ packet = machine_evidence_packet if isinstance(machine_evidence_packet, dict) else {}
+ sections = packet.get("sections") if isinstance(packet.get("sections"), dict) else {}
+ used_sections = {name for name, section in sections.items() if isinstance(section, dict) and section.get("status") == "used"}
+ dasha_used = "dasha_boundaries" in used_sections
+ external_oracle_status = (
+ sections.get("external_oracle_status", {}).get("status")
+ if isinstance(sections.get("external_oracle_status"), dict)
+ else "missing"
+ )
+ scored_candidates = []
+ for ref in candidate_refs:
+ profile = case_profiles.get(ref, {"domains": [], "evidence_sections": []})
+ overlap = sorted(used_sections & set(profile["evidence_sections"]))
+ trigger_score = (10 if dasha_used else 0) + (10 if external_oracle_status == "official_verified" else 0)
+ score = (50 if route in profile["domains"] else 0) + min(30, len(overlap) * 5) + trigger_score
+ scored_candidates.append({
+ "case_source": ref,
+ "score": score,
+ "reference_grade": "partial_reference" if score >= 50 else "reference_only",
+ "recorded_outcome": profile.get("recorded_outcome"),
+ "similarities": {
+ "route_match": route in profile["domains"],
+ "evidence_section_overlap": overlap,
+ },
+ "differences": {
+ "unmatched_required_sections": sorted(set(profile["evidence_sections"]) - used_sections),
+ },
+ "event_trigger_match": {
+ "status": (
+ "partial_match_official_timing_available"
+ if dasha_used and external_oracle_status == "official_verified"
+ else "partial_match_official_timing_blocked"
+ if dasha_used
+ else "not_matched_missing_dasha"
+ ),
+ "checks": {
+ "dasha_boundaries": "used" if dasha_used else "missing",
+ "external_oracle_status": external_oracle_status,
+ "recorded_trigger_keywords": list(profile.get("event_trigger_keywords", [])),
+ },
+ "boundary": "Trigger check uses available timing evidence only; it is not event outcome validation.",
+ },
+ "outcome_validation": {
+ "status": "local_outcome_recorded_trigger_not_replayed",
+ "recorded_outcome": profile.get("recorded_outcome"),
+ "boundary": "Outcome is read from the local case source profile; this does not replay the case chart or prove similarity.",
+ },
+ })
+ return {
+ "status": "partial_scored" if scored_candidates else "catalog_available_matching_not_run",
+ "batch_id": "real_case_studies_batch1",
+ "route": route,
+ "source_roots": ["references/real_case_studies", "docs/benchmark"],
+ "case_index_by_domain": case_index_by_domain,
+ "candidate_refs": list(candidate_refs),
+ "scored_candidates": scored_candidates,
+ "reference_grade": scored_candidates[0]["reference_grade"] if scored_candidates else "ungraded_until_similarity_scored",
+ "boundary": (
+ "Local case catalog has route, evidence-section, and timing-evidence scoring only; concrete event "
+ "outcome matching must run before a case can be used as complete calibration evidence."
+ ),
+ }
+
+ def runtime_evidence_log(
+ self,
+ *,
+ surface: str,
+ entry_mode: str,
+ route_packet: dict[str, Any],
+ executed_steps: list[str],
+ skipped_steps: list[str],
+ vedastro_official: dict[str, Any] | None = None,
+ interpretation_source_runtime_coverage: dict[str, Any] | None = None,
+ machine_evidence_packet: dict[str, Any] | None = None,
+ real_case_calibration: dict[str, Any] | None = None,
+ blind: bool = False,
+ ) -> dict[str, Any]:
+ official = vedastro_official if isinstance(vedastro_official, dict) else {}
+ runtime_truth = official.get("runtime_truth") if isinstance(official.get("runtime_truth"), dict) else {}
+ vedastro_state = self._vedastro_cloud_state(official)
+ external_cross_validation = self._external_engine_cross_validation(vedastro_state)
+ blocked_items: list[str] = []
+ if vedastro_state != "official_verified":
+ blocked_items.append("vedastro_official_raw_snapshot_not_verified")
+ if external_cross_validation["status"] != "complete":
+ blocked_items.append("external_engine_cross_validation_partial")
+ packet = machine_evidence_packet if isinstance(machine_evidence_packet, dict) else {}
+ packet_status = packet.get("status") or "required_not_satisfied"
+ if not packet:
+ blocked_items.append("machine_evidence_packet_not_yet_materialized")
+ elif packet_status != "complete":
+ blocked_items.append("machine_evidence_packet_partial")
+ case_packet = real_case_calibration if isinstance(real_case_calibration, dict) else {}
+ case_status = case_packet.get("status") or "required_not_satisfied"
+ functional_packet = packet.get("functional_benefic_malefic") if isinstance(packet.get("functional_benefic_malefic"), dict) else {}
+ functional_status = functional_packet.get("status") or "blocked"
+ if functional_status != "used":
+ blocked_items.append("functional_benefic_malefic_blocked")
+ if not case_packet:
+ blocked_items.append("real_case_calibration_not_yet_materialized")
+ elif case_status != "complete":
+ blocked_items.append("real_case_calibration_partial")
+ technique_audit_table = [
+ {
+ "technique": "VedAstro Cloud State",
+ "status": vedastro_state,
+ "used": vedastro_state == "official_verified",
+ "effect_on_confidence": (
+ "official_cloud_evidence_available"
+ if vedastro_state == "official_verified"
+ else "confidence_capped_without_verified_official_cloud"
+ ),
+ },
+ {
+ "technique": "External Engine Cross-Validation",
+ "status": external_cross_validation["status"],
+ "used": external_cross_validation["status"] == "complete",
+ "effect_on_confidence": (
+ "three_engine_runtime_closure_available"
+ if external_cross_validation["status"] == "complete"
+ else "claims_capped_until_pyjhora_jhora_jyotishganit_are_invoked_for_this_run"
+ ),
+ },
+ {
+ "technique": "Evidence Packet",
+ "status": packet_status,
+ "used": bool(packet),
+ "effect_on_confidence": "complete_packet_required_for_high_confidence" if packet_status != "complete" else "supports_high_confidence",
+ },
+ {
+ "technique": "Blind Technical Mode",
+ "status": "used" if blind else "available_not_requested",
+ "used": bool(blind),
+ "effect_on_confidence": "prevents_conversation_feedback_leakage" if blind else "normal_runtime_mode",
+ },
+ {
+ "technique": "MEVG / Global Web Evidence",
+ "status": "blocked",
+ "used": False,
+ "effect_on_confidence": "caps_claims_until_global_web_evidence_runs",
+ },
+ {
+ "technique": "Real Case Calibration",
+ "status": case_status,
+ "used": bool(case_packet),
+ "effect_on_confidence": "partial_reference_only_until_outcome_replay" if case_status != "complete" else "supports_calibration",
+ },
+ {
+ "technique": "Functional Benefic/Malefic",
+ "status": functional_status,
+ "used": functional_status == "used",
+ "key_functional_benefics": functional_packet.get("functional_benefics", []),
+ "key_functional_malefics": functional_packet.get("functional_malefics", []),
+ "yogakarakas": functional_packet.get("yogakarakas", []),
+ "effect_on_confidence": functional_packet.get(
+ "effect_on_confidence",
+ "high_rigor_claims_blocked_until_functional_nature_layer_is_present",
+ ),
+ },
+ ]
+ return {
+ "name": "UnifiedConsultationRuntimeEvidenceLog",
+ "surface": surface,
+ "entry_mode": entry_mode,
+ "route": dict(route_packet),
+ "executed_steps": list(executed_steps),
+ "skipped_steps": list(skipped_steps),
+ "vedastro_cloud_state": vedastro_state,
+ "vedastro_runtime_truth": dict(runtime_truth),
+ "external_engine_cross_validation": external_cross_validation,
+ "source_priority": {
+ "mode": self.SOURCE_PRIORITY["mode"],
+ "priority": list(self.SOURCE_PRIORITY["priority"]),
+ },
+ "evidence_sources": {
+ "vedastro_official": vedastro_state,
+ "local_modules": "used" if executed_steps else "not_used",
+ "interpretation_source_runtime_coverage": (
+ "used" if isinstance(interpretation_source_runtime_coverage, dict) and interpretation_source_runtime_coverage else "not_used"
+ ),
+ },
+ "evidence_packet_contract": {
+ "status": packet_status,
+ "required_sections": list(self.EVIDENCE_PACKET_REQUIRED_SECTIONS),
+ "missing_sections": packet.get("missing_sections", []),
+ },
+ "blind_technical_mode": {
+ "enabled": bool(blind),
+ "allowed_sources": ["birth_payload", "pdf", "machine_evidence_packet"],
+ "disallowed_sources": ["conversation_feedback", "memory_linked_personal_history"],
+ },
+ "real_case_calibration": {
+ "status": case_status,
+ "required_fields": [
+ "case_source",
+ "chart_similarity",
+ "transit_or_dasha_trigger",
+ "event",
+ "similarities",
+ "differences",
+ "reference_grade",
+ ],
+ },
+ "quality_gate": {
+ "technique_audit_table_required": True,
+ "technique_audit_table": technique_audit_table,
+ "required_rows": [
+ "VedAstro Cloud State",
+ "External Engine Cross-Validation",
+ "Evidence Packet",
+ "Blind Technical Mode",
+ "MEVG / Global Web Evidence",
+ "Real Case Calibration",
+ "Functional Benefic/Malefic",
+ ],
+ "status": "blocked" if blocked_items else "pass",
+ "blocked_items": blocked_items,
+ },
+ }
diff --git a/scripts/vedastro_official_mcp_bridge.py b/scripts/vedastro_official_mcp_bridge.py
index d7268b1e..3a30413e 100644
--- a/scripts/vedastro_official_mcp_bridge.py
+++ b/scripts/vedastro_official_mcp_bridge.py
@@ -98,12 +98,44 @@ def _tools_list(endpoint: str) -> dict[str, Any]:
}
+def _call_tool(endpoint: str, tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]:
+ init = _initialize(endpoint)
+ payload = {
+ "jsonrpc": "2.0",
+ "id": 3,
+ "method": "tools/call",
+ "params": {"name": tool_name, "arguments": arguments},
+ }
+ body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
+ headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json, text/event-stream",
+ }
+ if init.get("session_id"):
+ headers["Mcp-Session-Id"] = str(init["session_id"])
+ req = urllib.request.Request(endpoint, data=body, method="POST", headers=headers)
+ with urllib.request.urlopen(req, timeout=DEFAULT_TIMEOUT_SECONDS) as resp:
+ raw = resp.read().decode("utf-8")
+ response_headers = {key.lower(): value for key, value in resp.headers.items()}
+ result = json.loads(raw)
+ return {
+ "endpoint": endpoint,
+ "available": True,
+ "status": "ok",
+ "operation": "call_tool",
+ "tool_name": tool_name,
+ "session_id": init.get("session_id") or response_headers.get("mcp-session-id"),
+ "result": result.get("result") or result,
+ "source": "official_public_mcp",
+ }
+
+
def schema() -> dict[str, Any]:
return {
"bridge": "vedastro_official_mcp_bridge",
"role": "official_public_mcp_thin_bridge",
"endpoint": DEFAULT_ENDPOINT,
- "operations": ["initialize", "tools_list"],
+ "operations": ["initialize", "tools_list", "call_tool"],
"response_contract": [
"endpoint",
"available",
@@ -113,7 +145,7 @@ def schema() -> dict[str, Any]:
"source",
],
"boundaries": [
- "Use for official MCP reachability and tool discovery.",
+ "Use for official MCP reachability, tool discovery, and targeted tool calls.",
"Do not let raw MCP results directly override local score/dominant_label/payout_label.",
"Promote only through explicit local contracts and tests.",
],
@@ -138,7 +170,9 @@ def main() -> int:
parser = argparse.ArgumentParser(description="Official public VedAstro MCP bridge")
parser.add_argument("--print-schema", action="store_true")
parser.add_argument("--endpoint", default=DEFAULT_ENDPOINT)
- parser.add_argument("--operation", choices=["initialize", "tools_list"], default="tools_list")
+ parser.add_argument("--operation", choices=["initialize", "tools_list", "call_tool"], default="tools_list")
+ parser.add_argument("--tool")
+ parser.add_argument("--arguments-json", default="{}")
args = parser.parse_args()
if args.print_schema:
@@ -147,6 +181,13 @@ def main() -> int:
try:
if args.operation == "initialize":
result = _initialize(args.endpoint)
+ elif args.operation == "call_tool":
+ if not args.tool:
+ raise ValueError("--tool is required when --operation call_tool")
+ arguments = json.loads(args.arguments_json)
+ if not isinstance(arguments, dict):
+ raise ValueError("--arguments-json must decode to an object")
+ result = _call_tool(args.endpoint, args.tool, arguments)
else:
result = _tools_list(args.endpoint)
except Exception as exc: # noqa: BLE001
diff --git a/scripts/vedastro_service_adapter.py b/scripts/vedastro_service_adapter.py
index 71752352..c2568fb1 100644
--- a/scripts/vedastro_service_adapter.py
+++ b/scripts/vedastro_service_adapter.py
@@ -2389,6 +2389,13 @@ def _normalize_official_full_snapshot_success(
"operation": "official_full_snapshot",
"primary_source": "vedastro_official",
"snapshot_sections": sections,
+ "raw_response": {
+ "source": "vedastro_official_full_snapshot",
+ "sections": sections,
+ "section_statuses": section_statuses,
+ "request_manifest": manifest,
+ "response_hash": metadata["response_hash"],
+ },
"official_chart": _build_official_chart_from_snapshot(sections),
"official_full_capability_catalog": official_full_capability_catalog or {},
"section_statuses": section_statuses,
diff --git a/scripts/vedastro_user_entrypoint.py b/scripts/vedastro_user_entrypoint.py
index 06cc1342..fb616659 100644
--- a/scripts/vedastro_user_entrypoint.py
+++ b/scripts/vedastro_user_entrypoint.py
@@ -151,14 +151,23 @@ def _strict_workflow_summary(route: str, catalog: dict[str, Any]) -> dict[str, A
def _cache_and_queue_report() -> dict[str, Any]:
+ queue_enabled = (
+ _bool_env("VEDASTRO_FREE_TIER_QUEUE")
+ or _bool_env("VEDASTRO_FREE_TIER_QUEUE_ENABLED")
+ or _bool_env("VEDASTRO_ENABLE_FREE_TIER_QUEUE")
+ )
+ using_free_tier = not bool(os.environ.get("VEDASTRO_API_KEY", "").strip())
return {
"official_full_snapshot_cache_scope": "official_full_snapshot_semantic_cache",
"official_full_snapshot_cache_ttl_seconds": _int_env("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", 0),
"range_scan_cache_scope": "vedastro_range_scan_request_cache",
"range_scan_cache_ttl_seconds": _int_env("VEDASTRO_CACHE_TTL_SECONDS", 0),
- "free_tier_queue_enabled": _bool_env("VEDASTRO_FREE_TIER_QUEUE")
- or _bool_env("VEDASTRO_FREE_TIER_QUEUE_ENABLED")
- or _bool_env("VEDASTRO_ENABLE_FREE_TIER_QUEUE"),
+ "free_tier_queue_enabled": queue_enabled,
+ "free_tier_strategy": {
+ "using_free_tier": using_free_tier,
+ "queue_enabled": queue_enabled,
+ "guard_status": "within_free_tier_strategy" if using_free_tier else "premium_key_present",
+ },
"sample_limit": _int_env("VEDASTRO_FULL_CATALOG_SAMPLE_LIMIT", 0),
"artifact_root": "scratch/local/vedastro_adapter",
}
diff --git a/tests/test_api_server_security.py b/tests/test_api_server_security.py
index a98d8bf1..cc513a97 100644
--- a/tests/test_api_server_security.py
+++ b/tests/test_api_server_security.py
@@ -387,7 +387,10 @@ def test_high_rigor_workflow_plan_only_exposes_official_hard_override_contract()
assert result['execution_plan'][-1] == 'return_official_primary_supplemental_fallback_conflict_contract'
-def test_high_rigor_vedastro_official_summary_passes_through_contract_fields() -> None:
+def test_high_rigor_vedastro_official_summary_passes_through_contract_fields(monkeypatch) -> None:
+ monkeypatch.delenv('VEDASTRO_FREE_TIER_QUEUE', raising=False)
+ monkeypatch.delenv('VEDASTRO_FREE_TIER_QUEUE_ENABLED', raising=False)
+ monkeypatch.delenv('VEDASTRO_ENABLE_FREE_TIER_QUEUE', raising=False)
handler = _handler()
chart = {
@@ -428,6 +431,17 @@ def test_high_rigor_vedastro_official_summary_passes_through_contract_fields() -
assert result['fallback_used'] == ['local_chart_fallback']
assert result['blocked_items'] == ['official_event_radar_partial']
assert result['conflicts'] == [{'type': 'official_local_dasha_conflict'}]
+ runtime_truth = result['runtime_truth']
+ assert runtime_truth['catalog_boundary'] == 'catalog_recognized_not_full_runtime_execution'
+ assert runtime_truth['official_execution_layers']['chart_core'] == 'ok'
+ assert runtime_truth['official_execution_layers']['event_radar'] == 'partial'
+ assert runtime_truth['official_execution_layers']['catalog_status'] == 'partial'
+ assert runtime_truth['fallback_active'] is True
+ assert runtime_truth['blocked_items'] == ['official_event_radar_partial']
+ assert runtime_truth['free_tier_strategy']['using_free_tier'] is True
+ assert runtime_truth['free_tier_strategy']['queue_enabled'] is False
+ assert runtime_truth['free_tier_strategy']['cache_hit'] is False
+ assert runtime_truth['free_tier_strategy']['guard_status'] == 'degraded_or_partial'
def test_high_rigor_vedastro_official_summary_exposes_top_reader_contract_from_full_snapshot() -> None:
@@ -481,6 +495,7 @@ def test_high_rigor_vedastro_official_summary_exposes_top_reader_contract_from_f
'summary': {'catalog_method_count': 641},
}
},
+ 'raw_response': {'official': 'raw'},
},
},
'ai_prompt_pack': {
@@ -534,6 +549,49 @@ def test_high_rigor_vedastro_official_summary_exposes_top_reader_contract_from_f
assert result['verdict'] == 'high_probability_window'
assert result['dominant_label'] == 'career_status'
assert result['main_conflicts'] == [{'type': 'official_local_dasha_conflict'}]
+ assert result['runtime_truth']['primary_route'] == 'career'
+ assert result['runtime_truth']['routes_available'] == ['career', 'relationship', 'finance']
+ assert result['raw_response'] == {'official': 'raw'}
+
+
+def test_high_rigor_vedastro_official_summary_uses_module_snapshot_cache_truth_when_range_scan_snapshot_missing() -> None:
+ handler = _handler()
+ chart = {
+ 'birth': {'ayanamsa_display': 'Lahiri', 'ayanamsa_name': 'lahiri', 'node_mode': 'mean'},
+ 'modules': {
+ 'vedastro_range_scan_result': {
+ 'status': 'ok',
+ 'source_metadata': {},
+ },
+ 'vedastro_official_full_snapshot': {
+ 'status': 'official_snapshot_budget_exhausted',
+ 'strict_workflow_primary_route': 'career',
+ 'strict_workflow_routes_available': ['career'],
+ 'strict_workflow_contracts': {
+ 'career': {
+ 'official_primary_evidence': {'chart_core': {'status': 'ok'}},
+ },
+ },
+ 'source_metadata': {
+ 'semantic_cache': {
+ 'cache_hit': True,
+ },
+ },
+ },
+ },
+ 'ai_prompt_pack': {
+ 'evidence_snapshot': {
+ 'vedastro_official_snapshot': {
+ 'status': 'official_snapshot_budget_exhausted',
+ },
+ },
+ },
+ }
+
+ result = handler._high_rigor_vedastro_official_summary(chart)
+
+ assert result['status'] == 'official_snapshot_budget_exhausted'
+ assert result['runtime_truth']['free_tier_strategy']['cache_hit'] is True
def test_api_prompt_pack_official_snapshot_carries_strict_workflow_contracts() -> None:
@@ -708,6 +766,7 @@ def test_consultation_workflow_surfaces_top_reader_contract_in_official_summary(
'lon': 114.2,
'tz': 8,
'theme': ['career'],
+ 'blind': True,
})
contract = result['vedastro_official']['strict_workflow_contracts']['career']
@@ -2127,6 +2186,7 @@ def test_consultation_workflow_uses_unified_orchestrator_contract(monkeypatch) -
'lon': 114.2,
'tz': 8,
'theme': ['career'],
+ 'blind': True,
})
assert result['success'] is True
@@ -2144,8 +2204,24 @@ def test_consultation_workflow_uses_unified_orchestrator_contract(monkeypatch) -
'run_rectification_gate',
'run_thematic_report',
]
- assert result['runtime_planner']['skipped_steps'] == ['run_historical_event_backtest']
+ assert 'run_historical_event_backtest' in result['runtime_planner']['skipped_steps']
assert result['source_priority']['mode'] == 'vedastro_official_snapshot_first'
+ assert result['runtime_evidence_log']['surface'] == 'api_web'
+ assert result['runtime_evidence_log']['route']['question_type'] == 'career'
+ assert result['runtime_evidence_log']['vedastro_cloud_state'] in {
+ 'official_verified',
+ 'official_blocked',
+ 'local_fallback',
+ }
+ assert result['runtime_evidence_log']['quality_gate']['technique_audit_table_required'] is True
+ assert result['runtime_evidence_log']['quality_gate']['technique_audit_table'][0]['technique'] == 'VedAstro Cloud State'
+ assert result['machine_evidence_packet']['status'] == 'partial'
+ assert result['real_case_calibration']['status'] == 'partial_scored'
+ assert result['real_case_calibration']['batch_id'] == 'real_case_studies_batch1'
+ assert result['runtime_evidence_log']['evidence_packet_contract']['status'] == 'partial'
+ assert result['runtime_evidence_log']['real_case_calibration']['status'] == 'partial_scored'
+ assert result['runtime_evidence_log']['blind_technical_mode']['enabled'] is True
+ assert 'conversation_feedback' in result['runtime_evidence_log']['blind_technical_mode']['disallowed_sources']
assert result['chart']['special_lagnas']['precision'] == 'sunrise_correct'
@@ -2345,6 +2421,183 @@ def test_consultation_workflow_rectification_entry_sends_empty_objects_before_ch
assert seen['rectification_body']['ascendant'] == {}
+def test_consultation_workflow_prashna_entry_uses_prashna_without_compute_chart(monkeypatch) -> None:
+ handler = _handler()
+ calls = {'chart': 0, 'prashna': 0}
+ seen = {}
+
+ def fake_chart_compute(body):
+ calls['chart'] += 1
+ return {'success': True, 'modules': {}}
+
+ def fake_prashna(body):
+ calls['prashna'] += 1
+ seen['prashna_body'] = dict(body)
+ return {
+ 'success': True,
+ 'endpoint': 'prashna',
+ 'question': body.get('question'),
+ 'timing': {'recommendation': '可以进行Prashna分析'},
+ 'judgement': {'summary': '可问'},
+ }
+
+ def fake_thematic_report(body):
+ seen['theme_body'] = dict(body)
+ return {
+ 'success': True,
+ 'endpoint': 'thematic_report',
+ 'mode': 'upstream_contract_reuse',
+ 'report': {'sections': []},
+ }
+
+ monkeypatch.setattr(handler, '_compute_chart', fake_chart_compute)
+ monkeypatch.setattr(handler, '_compute_prashna', fake_prashna)
+ monkeypatch.setattr(handler, '_compute_thematic_report', fake_thematic_report)
+ monkeypatch.setattr(handler, '_compute_rectification_gate', lambda body: {'success': True, 'summary': {'recommended_events': []}})
+
+ result = handler._compute_consultation_workflow({
+ 'entry_mode': 'prashna',
+ 'question': '这个合作能成吗',
+ 'question_text': '这个合作能成吗',
+ 'theme': ['wealth'],
+ 'year': REDACTED_YEAR,
+ 'month': 4,
+ 'day': 17,
+ 'hour': 14,
+ 'minute': 49,
+ 'lat': 36.42,
+ 'lon': 114.2,
+ 'tz': 8,
+ })
+
+ assert result['success'] is True
+ assert result['entry_mode'] == 'prashna'
+ assert result['runtime_planner']['entry_mode'] == 'prashna'
+ assert result['runtime_planner']['executed_steps'] == ['run_prashna', 'run_thematic_report']
+ assert calls['chart'] == 0
+ assert calls['prashna'] == 1
+ assert seen['prashna_body']['question'] == '这个合作能成吗'
+
+
+def test_consultation_workflow_builds_audited_remedies_from_guided_topic_gate(monkeypatch) -> None:
+ handler = _handler()
+
+ fake_chart = {
+ 'success': True,
+ 'birth_info': {'date': 'REDACTED_DATE', 'time': 'REDACTED_TIME', 'tz': 8},
+ 'ascendant': {'lon': 92.0, 'sign': 'Cancer'},
+ 'planets': _sample_planets(),
+ 'chart': {
+ 'ascendant': {'lon': 92.0, 'sign': 'Cancer'},
+ 'planets': _sample_planets(),
+ },
+ 'modules': {
+ 'guided_topics': [
+ {
+ 'id': 'career',
+ 'title': '事业',
+ 'strict_audit_gate': {
+ 'topic': 'career',
+ 'primary_planets': ['Saturn'],
+ 'active_dasha_lord': 'Saturn',
+ 'strength_context': {
+ 'Saturn': {'total_rupas': 0.42, 'strength_level': 'weak'},
+ },
+ 'dosha_context': ['delay_signature'],
+ },
+ },
+ ],
+ },
+ 'special_lagnas': {'precision': 'sunrise_correct'},
+ }
+
+ monkeypatch.setattr(handler, '_compute_chart', lambda body: fake_chart)
+ monkeypatch.setattr(handler, '_compute_rectification_gate', lambda body: {'success': True, 'summary': {'recommended_events': []}})
+ monkeypatch.setattr(handler, '_compute_thematic_report', lambda body: {'success': True, 'endpoint': 'thematic_report', 'report': {'sections': []}})
+
+ result = handler._compute_consultation_workflow({
+ 'entry_mode': 'direct_chart',
+ 'question': '请直接排盘并看事业',
+ 'theme': ['career'],
+ 'year': REDACTED_YEAR,
+ 'month': 4,
+ 'day': 17,
+ 'hour': 14,
+ 'minute': 49,
+ 'lat': 36.42,
+ 'lon': 114.2,
+ 'tz': 8,
+ })
+ remedies = result.get('audited_remedies') or {}
+ assert remedies['status'] == 'ok'
+ assert remedies['source'] == 'strict_audit_gate'
+ assert remedies['topic'] == 'career'
+ assert remedies['active_dasha_lord'] == 'Saturn'
+
+
+def test_consultation_workflow_timing_route_builds_muhurta_panchanga_sidecar(monkeypatch) -> None:
+ handler = _handler()
+ fake_chart = {
+ 'success': True,
+ 'birth_info': {'date': 'REDACTED_DATE', 'time': 'REDACTED_TIME', 'tz': 8},
+ 'ascendant': {'lon': 92.0, 'sign': 'Cancer'},
+ 'planets': _sample_planets(),
+ 'chart': {
+ 'ascendant': {'lon': 92.0, 'sign': 'Cancer'},
+ 'planets': _sample_planets(),
+ },
+ 'modules': {},
+ 'special_lagnas': {'precision': 'sunrise_correct'},
+ }
+ seen = {}
+
+ monkeypatch.setattr(handler, '_compute_chart', lambda body: fake_chart)
+ monkeypatch.setattr(handler, '_compute_rectification_gate', lambda body: {
+ 'success': True,
+ 'endpoint': 'rectification_gate',
+ 'summary': {'recommended_events': []},
+ })
+ monkeypatch.setattr(handler, '_compute_thematic_report', lambda body: {
+ 'success': True,
+ 'endpoint': 'thematic_report',
+ 'mode': 'derived_chart_evidence',
+ 'theme_count': len(body.get('theme') or []),
+ })
+
+ def fake_muhurta(body):
+ seen['muhurta_body'] = dict(body)
+ return {
+ 'status': 'ok',
+ 'source': 'local_muhurta.py',
+ 'activity': 'business',
+ 'report_mode': 'muhurta_date_range_solver',
+ 'panchanga': {'query_date': '2026-07-08'},
+ 'best_windows': [{'date': '2026-07-08'}],
+ }
+
+ monkeypatch.setattr(handler, '_compute_muhurta_panchanga', fake_muhurta)
+
+ result = handler._compute_consultation_workflow({
+ 'entry_mode': 'direct_chart',
+ 'question': '2026年何时适合谈合作和推进项目的应期',
+ 'year': REDACTED_YEAR,
+ 'month': 4,
+ 'day': 17,
+ 'hour': 14,
+ 'minute': 49,
+ 'lat': 36.42,
+ 'lon': 114.2,
+ 'tz': 8,
+ 'reference_date': '2026-07-08',
+ 'theme': ['career'],
+ })
+ assert result['success'] is True
+ assert 'run_muhurta_panchanga' in result['runtime_planner']['executed_steps']
+ assert result['muhurta_panchanga']['status'] == 'ok'
+ assert result['muhurta_panchanga']['activity'] == 'business'
+ assert seen['muhurta_body']['reference_date'] == '2026-07-08'
+
+
def test_thematic_report_handles_missing_dasa_convergence_without_crash(monkeypatch) -> None:
handler = _handler()
diff --git a/tests/test_bv_raman_report_book_acceptance.py b/tests/test_bv_raman_report_book_acceptance.py
new file mode 100644
index 00000000..06019f5d
--- /dev/null
+++ b/tests/test_bv_raman_report_book_acceptance.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python3
+"""Acceptance tests for the long-report book root and its failure-discipline docs."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+REPORT_ROOT = ROOT / "docs" / "reports" / "chart_research_REDACTED_DATE_REDACTED_TIME"
+
+
+def _read(path: Path) -> str:
+ return path.read_text(encoding="utf-8")
+
+
+def test_report_book_root_has_required_navigation_and_seed_boundary() -> None:
+ readme = _read(REPORT_ROOT / "README.md")
+ book = _read(REPORT_ROOT / "book.md")
+
+ assert "Source Priority" in readme
+ assert "Seed Draft Mapping" in readme
+ assert "current `Raman/Mean` run" in readme
+ assert "PDF export: blocked" in readme
+
+ assert "## Front Matter" in book
+ assert "## Core Interpretation" in book
+ assert "## Varga Layers" in book
+ assert "## Appendices" in book
+
+
+def test_high_value_chapters_expose_current_runtime_conflicts_and_not_old_draft_as_truth() -> None:
+ chapter_01 = _read(REPORT_ROOT / "chapters" / "01_methodology_and_honesty_boundary.md")
+ chapter_04 = _read(REPORT_ROOT / "chapters" / "04_d1_rasi_foundation.md")
+ chapter_08 = _read(REPORT_ROOT / "chapters" / "08_varga_framework_overview.md")
+ chapter_16 = _read(REPORT_ROOT / "chapters" / "16_parashara_vimshottari_yogini_kalachakra.md")
+ appendix_f = _read(REPORT_ROOT / "appendices" / "F_technique_audit_table.md")
+
+ assert "current raw JSON wins" in chapter_01
+ assert "Numeric values there may differ" in chapter_04
+ assert "seed draft's divisional table contains some values from an older calculation mouthpiece" in chapter_08
+ assert "does **not** match older draft sections" in chapter_16
+ assert "current run says Saturn/Venus active" in appendix_f
+
+
+def test_error_ledger_exists_and_tracks_real_migration_failures() -> None:
+ ledger = _read(REPORT_ROOT / "appendices" / "H_error_ledger_and_preflight.md")
+ readme = _read(REPORT_ROOT / "README.md")
+
+ assert "Read this before the next editing pass" in ledger
+ assert "old draft vs current runtime conflict" in ledger
+ assert "D9" in ledger
+ assert "Saturn/Ketu" in ledger
+ assert "Saturn/Venus" in ledger
+ assert "before editing chapters" in readme
+ assert "H_error_ledger_and_preflight.md" in readme
+
+
+def test_report_book_counts_and_cross_links_stay_complete() -> None:
+ chapters = sorted((REPORT_ROOT / "chapters").glob("*.md"))
+ appendices = sorted((REPORT_ROOT / "appendices").glob("*.md"))
+ book = _read(REPORT_ROOT / "book.md")
+ readme = _read(REPORT_ROOT / "README.md")
+ appendix_h = _read(REPORT_ROOT / "appendices" / "H_error_ledger_and_preflight.md")
+
+ assert len(chapters) == 22
+ assert len(appendices) == 8
+ assert "current runtime wins" in appendix_h
+ assert "Appendix H: error ledger and preflight" in readme
+ assert "[H. Error Ledger and Preflight]" in book
+ assert "D9 Leo 10.7493" in appendix_h
+
+
+def test_runtime_truth_markers_exist_in_key_chapters() -> None:
+ chapter_08 = _read(REPORT_ROOT / "chapters" / "08_varga_framework_overview.md")
+ chapter_15 = _read(REPORT_ROOT / "chapters" / "15_jaimini_chara_karaka_arudha_narayana.md")
+ chapter_20 = _read(REPORT_ROOT / "chapters" / "20_future_period_windows_2026_2063.md")
+
+ assert "D9 | Leo 10.7493" in chapter_08
+ assert "A10 / Karma Pada = `Capricorn`" in chapter_15
+ assert "Saturn/Venus" in chapter_20
+
+
+def test_varga_topic_chapters_are_no_longer_skeleton_only() -> None:
+ chapter_09 = _read(REPORT_ROOT / "chapters" / "09_d2_d11_wealth_structure.md")
+ chapter_10 = _read(REPORT_ROOT / "chapters" / "10_d3_d4_d7_d12_family_property_lineage.md")
+ chapter_11 = _read(REPORT_ROOT / "chapters" / "11_d9_marriage_dharma_and_relationship_pattern.md")
+ chapter_12 = _read(REPORT_ROOT / "chapters" / "12_d10_career_status_and_public_work.md")
+ chapter_13 = _read(REPORT_ROOT / "chapters" / "13_d16_d20_d24_d27_d30_special_topics.md")
+ chapter_14 = _read(REPORT_ROOT / "chapters" / "14_d40_d45_d60_karmic_layers.md")
+
+ assert "Skeleton only." not in chapter_09
+ assert "finance_strict_evidence" in chapter_09
+ assert "D2 Hora" in chapter_09
+
+ assert "Skeleton only." not in chapter_10
+ assert "D4 Chaturthamsa" in chapter_10
+ assert "D12 Dwadasamsa" in chapter_10
+
+ assert "Skeleton only." not in chapter_11
+ assert "relationship_strict_evidence" in chapter_11
+ assert "D9" in chapter_11
+
+ assert "Skeleton only." not in chapter_12
+ assert "career_strict_evidence" in chapter_12
+ assert "A10 Karma Pada" in chapter_12
+
+ assert "Skeleton only." not in chapter_13
+ assert "Vimsopaka" in chapter_13
+ assert "D30" in chapter_13
+
+ assert "Skeleton only." not in chapter_14
+ assert "D60" in chapter_14
+ assert "birth-time sensitive" in chapter_14
+
+
+def test_remaining_high_value_chapters_are_grounded_and_not_skeletons() -> None:
+ chapter_17 = _read(REPORT_ROOT / "chapters" / "17_transit_tajika_varshaphala_2026_2063.md")
+ chapter_19 = _read(REPORT_ROOT / "chapters" / "19_past_event_validation_and_conflict_notes.md")
+ chapter_21 = _read(REPORT_ROOT / "chapters" / "21_remedies_practice_and_limits.md")
+
+ assert "Skeleton only." not in chapter_17
+ assert "solar_return" in chapter_17
+ assert "tajika" in chapter_17
+ assert "dasha convergence" in chapter_17.lower()
+
+ assert "Skeleton only." not in chapter_19
+ assert "historical_event_backtest.py" in chapter_19
+ assert "strong_hit / weak_hit / miss / blocked" in chapter_19
+ assert "Appendix H" in chapter_19
+
+ assert "Skeleton only." not in chapter_21
+ assert "BPHS补救系统 v1.0" in chapter_21
+ assert "low-risk measures first" in chapter_21
+ assert "gemstones" in chapter_21
+
+
+def test_front_matter_and_appendix_tables_are_no_longer_placeholders() -> None:
+ chapter_02 = _read(REPORT_ROOT / "chapters" / "02_birth_data_chart_core_and_engine_contract.md")
+ chapter_03 = _read(REPORT_ROOT / "chapters" / "03_executive_synthesis.md")
+ appendix_a = _read(REPORT_ROOT / "appendices" / "A_raw_chart_tables.md")
+ appendix_b = _read(REPORT_ROOT / "appendices" / "B_dasha_boundaries_full.md")
+ appendix_c = _read(REPORT_ROOT / "appendices" / "C_varga_positions_full.md")
+ appendix_d = _read(REPORT_ROOT / "appendices" / "D_shadbala_ashtakavarga_tables.md")
+
+ assert "What belongs here" not in chapter_02
+ assert "local_engine_fallback" in chapter_02
+ assert "official_evidence" in chapter_02
+
+ assert "Skeleton only." not in chapter_03
+ assert "high-structure, late-ripening chart" in chapter_03
+
+ assert "Ready to expand" not in appendix_a
+ assert "Core chart table" in appendix_a
+ assert "Sun | Aries" in appendix_a
+
+ assert "Ready to expand" not in appendix_b
+ assert "Current Vimshottari" in appendix_b
+ assert "Current Narayana" in appendix_b
+
+ assert "Ready to expand" not in appendix_c
+ assert "Key ascendants" in appendix_c
+ assert "D10 | Sagittarius 25.277" in appendix_c
+
+ assert "Ready to expand" not in appendix_d
+ assert "Shadbala ranking" in appendix_d
+ assert "SAV summary" in appendix_d
diff --git a/tests/test_chart_research_export_tools.py b/tests/test_chart_research_export_tools.py
new file mode 100644
index 00000000..5aaa96d2
--- /dev/null
+++ b/tests/test_chart_research_export_tools.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+"""Regression tests for report export and CLI entrypoints."""
+
+from __future__ import annotations
+
+import subprocess
+import sys
+from shutil import which
+from pathlib import Path
+
+import pytest
+from PIL import Image
+from pypdf import PdfReader
+
+
+ROOT = Path(__file__).resolve().parents[1]
+REPORT_ROOT = ROOT / "docs" / "reports" / "chart_research_REDACTED_DATE_REDACTED_TIME"
+
+
+def test_historical_event_backtest_cli_help_runs_from_repo_root() -> None:
+ completed = subprocess.run(
+ [
+ sys.executable,
+ "scripts/historical_event_backtest.py",
+ "--help",
+ ],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=30,
+ check=False,
+ )
+ assert completed.returncode == 0, completed.stderr or completed.stdout
+ assert "historical-event backtest" in (completed.stdout + completed.stderr).lower()
+
+
+def test_chart_research_pdf_export_generates_readable_pdf(tmp_path: Path) -> None:
+ output_pdf = tmp_path / "chart_research.pdf"
+ completed = subprocess.run(
+ [
+ sys.executable,
+ "scripts/export_chart_research_pdf.py",
+ "--report-root",
+ str(REPORT_ROOT),
+ "--output",
+ str(output_pdf),
+ ],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=90,
+ check=False,
+ )
+ assert completed.returncode == 0, completed.stderr or completed.stdout
+ assert output_pdf.exists()
+ assert output_pdf.stat().st_size > 0
+
+ reader = PdfReader(str(output_pdf))
+ assert len(reader.pages) >= 3
+
+ first_page = reader.pages[0].extract_text() or ""
+ all_text = "\n".join((page.extract_text() or "") for page in reader.pages[:5])
+ assert "B.V. Raman" in first_page or "B.V. Raman" in all_text
+ assert "Executive Synthesis" in all_text or "Birth Data" in all_text
+
+
+@pytest.mark.skipif(which("pdftoppm") is None, reason="pdftoppm not installed")
+def test_chart_research_pdf_export_renders_visible_first_page(tmp_path: Path) -> None:
+ output_pdf = tmp_path / "chart_research.pdf"
+ export = subprocess.run(
+ [
+ sys.executable,
+ "scripts/export_chart_research_pdf.py",
+ "--report-root",
+ str(REPORT_ROOT),
+ "--output",
+ str(output_pdf),
+ ],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=90,
+ check=False,
+ )
+ assert export.returncode == 0, export.stderr or export.stdout
+
+ png_prefix = tmp_path / "chart_research_page_1"
+ render = subprocess.run(
+ [
+ "pdftoppm",
+ "-png",
+ "-f",
+ "1",
+ "-singlefile",
+ str(output_pdf),
+ str(png_prefix),
+ ],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=60,
+ check=False,
+ )
+ assert render.returncode == 0, render.stderr or render.stdout
+
+ first_page_png = png_prefix.with_suffix(".png")
+ assert first_page_png.exists()
+
+ img = Image.open(first_page_png).convert("L")
+ dark_pixels = sum(1 for pixel in img.getdata() if pixel < 250)
+ assert dark_pixels > 1000
diff --git a/tests/test_codex_plugin_wrapper.py b/tests/test_codex_plugin_wrapper.py
new file mode 100644
index 00000000..aca5fb13
--- /dev/null
+++ b/tests/test_codex_plugin_wrapper.py
@@ -0,0 +1,26 @@
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def test_local_codex_plugin_manifest_points_at_repo_truth() -> None:
+ plugin_json = ROOT / ".codex-plugin" / "plugin.json"
+ assert plugin_json.exists()
+
+ payload = json.loads(plugin_json.read_text(encoding="utf-8"))
+ assert payload["name"] == "jyotish-vedic-astrology"
+ assert payload["skills"] == "./skills/"
+
+ mcp_servers = payload["mcpServers"]
+ assert isinstance(mcp_servers, dict)
+ assert "jyotish" in mcp_servers
+ jyotish = mcp_servers["jyotish"]
+ assert jyotish["command"] == "python3"
+ assert jyotish["args"] == ["./mcp_server.py"]
+
+ assert (ROOT / "skills").is_dir()
+ assert (ROOT / "mcp_server.py").is_file()
diff --git a/tests/test_external_engine_adapter_diagnostics.py b/tests/test_external_engine_adapter_diagnostics.py
new file mode 100644
index 00000000..e98df1fa
--- /dev/null
+++ b/tests/test_external_engine_adapter_diagnostics.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+import json
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def test_external_engine_adapter_diagnostics_aggregates_three_engines() -> None:
+ completed = subprocess.run(
+ [sys.executable, "scripts/diagnose_external_engine_adapters.py", "--json"],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=30,
+ check=False,
+ env={**os.environ, "JYOTISH_SKIP_LOCAL_ENV": "1"},
+ )
+
+ assert completed.returncode == 0, completed.stderr or completed.stdout
+ report = json.loads(completed.stdout)
+ assert report["scope"] == "external_engine_adapter_diagnostics"
+ assert set(report["engines"]) == {"VedAstro", "PyJHora/JHora", "jyotishganit"}
+ assert report["engines"]["VedAstro"]["official_closure_plan"]["raw_response_acceptance"].startswith("vedastro_official.raw_response")
+ assert report["engines"]["PyJHora/JHora"]["status"] in {"available", "missing_dependency", "missing_adapter"}
+ assert report["engines"]["PyJHora/JHora"]["install_hint"]["package"] == "PyJHora"
+ assert report["engines"]["PyJHora/JHora"]["license_boundary"].startswith("AGPL external benchmark")
+ assert report["engines"]["jyotishganit"]["license"] == "MIT"
+ assert report["status"] in {"complete", "partial"}
diff --git a/tests/test_final_jhora_evidence_packet_acceptance.py b/tests/test_final_jhora_evidence_packet_acceptance.py
new file mode 100644
index 00000000..745ce366
--- /dev/null
+++ b/tests/test_final_jhora_evidence_packet_acceptance.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+"""Acceptance guard for the final JHora/PDF evidence packet artifacts."""
+
+from __future__ import annotations
+
+import json
+import re
+from pathlib import Path
+
+import scripts.sync_final_evidence_packet_status as sync_status
+
+
+ROOT = Path(__file__).resolve().parents[1]
+WORK_DIR = ROOT / "scratch" / "local" / "pdf_review_123456"
+ERROR_LOG = ROOT / "docs" / "research" / "final_output_acceptance_error_log_2026_07_04.md"
+
+
+def _latest_packet() -> tuple[int, Path, dict]:
+ sync_status.main()
+ packets: list[tuple[int, Path]] = []
+ for path in WORK_DIR.glob("jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v*.json"):
+ match = re.search(r"\.v(\d+)\.json$", path.name)
+ if match:
+ packets.append((int(match.group(1)), path))
+ assert packets, "no versioned JHora master evidence packets found"
+ version, path = max(packets)
+ return version, path, json.loads(path.read_text(encoding="utf-8"))
+
+
+def test_latest_master_packet_has_consistent_final_status_and_version_metadata() -> None:
+ version, path, packet = _latest_packet()
+ metadata = packet["metadata"]
+
+ assert version >= 24
+ assert packet["status"] == "final_output_v1"
+ assert metadata["status"] == "final_output_v1"
+ assert metadata["current_version"] == f"v{version}"
+ assert metadata["packet_version"] == f"v{version}"
+ assert metadata["canonical_packet"] == path.name
+ assert packet["structured_v13_final_integrated_report"]["status"] == "final_output_v1"
+
+
+def test_latest_master_packet_links_all_final_report_artifacts() -> None:
+ _, _, packet = _latest_packet()
+
+ required_sections = {
+ "structured_v21_raman_full_report_complete": ("source", "compiled-full-report-v1"),
+ "structured_v22_raman_full_report_pdf_artifact": ("pdf", "pdf-rendered-qa-pass"),
+ "structured_v23_raman_full_report_raw_data_appendix": ("source", "raw-data-appendix-v1"),
+ }
+ for section, (artifact_key, status) in required_sections.items():
+ payload = packet[section]
+ assert payload["status"] == status
+ artifact = ROOT / payload[artifact_key]
+ assert artifact.exists(), f"missing artifact for {section}: {artifact}"
+ assert artifact.stat().st_size > 1000, f"artifact too small for {section}: {artifact}"
+
+ pdf_payload = packet["structured_v22_raman_full_report_pdf_artifact"]
+ for rel_path in pdf_payload["qa_rendered_pages"]:
+ qa_page = WORK_DIR / rel_path
+ assert qa_page.exists(), f"missing PDF QA render page: {qa_page}"
+ assert qa_page.stat().st_size > 1000
+
+
+def test_status_ledger_points_to_latest_packet_and_has_fresh_next_step() -> None:
+ version, path, _ = _latest_packet()
+ ledger = (WORK_DIR / "evidence_packet_status_ledger_REDACTED_DATE_REDACTED_TIME.md").read_text(
+ encoding="utf-8"
+ )
+
+ assert path.name in ledger
+ assert f"`{path.name}` | active" in ledger
+ assert "Raman full report complete" in ledger
+ assert "Raman full report PDF" in ledger
+ assert "Raman full report raw data appendix" in ledger
+ assert "Chapter 04 Parashari Dasha layer - Vimshottari" not in ledger
+ assert "Acceptance/error-log gate" in ledger
+ assert f"v{version}" in ledger
+
+
+def test_acceptance_error_log_records_known_failures_and_prevention_rules() -> None:
+ text = ERROR_LOG.read_text(encoding="utf-8")
+
+ required_phrases = [
+ "status metadata drift",
+ "canonical packet drift",
+ "stale next-step ledger",
+ "blank PDF artifact",
+ "read this log before editing",
+ "test_final_jhora_evidence_packet_acceptance.py",
+ ]
+ for phrase in required_phrases:
+ assert phrase in text
+
+
+def test_sync_script_repairs_latest_packet_metadata_and_ledger(tmp_path, monkeypatch) -> None:
+ for version in (2, 10):
+ packet = {
+ "status": "final_output_v1",
+ "metadata": {
+ "status": "final_output_v1",
+ "current_version": "v1",
+ "packet_version": f"v{version}",
+ "canonical_packet": "jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v1.json",
+ },
+ "structured_v13_final_integrated_report": {"status": "final_output_v1"},
+ }
+ (tmp_path / f"jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v{version}.json").write_text(
+ json.dumps(packet, ensure_ascii=False),
+ encoding="utf-8",
+ )
+ ledger = tmp_path / "evidence_packet_status_ledger_REDACTED_DATE_REDACTED_TIME.md"
+ ledger.write_text(
+ "| Master evidence packet | `jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v2.json` | active | Current canonical structured packet. |\n",
+ encoding="utf-8",
+ )
+
+ monkeypatch.setattr(sync_status, "WORK_DIR", tmp_path)
+
+ assert sync_status.main() == 0
+ latest = json.loads(
+ (tmp_path / "jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v10.json").read_text(
+ encoding="utf-8"
+ )
+ )
+ assert latest["metadata"]["current_version"] == "v10"
+ assert latest["metadata"]["packet_version"] == "v10"
+ assert (
+ latest["metadata"]["canonical_packet"]
+ == "jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v10.json"
+ )
+ assert "jhora_master_evidence_packet_REDACTED_DATE_REDACTED_TIME.v10.json" in ledger.read_text(
+ encoding="utf-8"
+ )
diff --git a/tests/test_frontend_productization.py b/tests/test_frontend_productization.py
index 08b026ca..8e1c1d6d 100644
--- a/tests/test_frontend_productization.py
+++ b/tests/test_frontend_productization.py
@@ -1617,6 +1617,7 @@ def test_web_entry_prefers_unified_consultation_workflow() -> None:
bridge = read("api-bridge.js")
public_bridge = read("public/api-bridge.js")
main = read("main.js")
+ html = read("index.html")
for source in (bridge, public_bridge):
assert "computeConsultationWorkflow" in source
@@ -1624,6 +1625,10 @@ def test_web_entry_prefers_unified_consultation_workflow() -> None:
assert "entryMode = options.entryMode || 'direct_chart'" in main
assert "entryMode: 'rectification'" in main
+ assert "entryMode: 'prashna'" in main
+ assert "run_muhurta_panchanga" in main
+ for token in ('id="entry-direct-chart"', 'id="entry-rectification"', 'id="entry-prashna"'):
+ assert token in html
def test_result_page_surfaces_workflow_summary_and_provenance_detail() -> None:
@@ -1636,6 +1641,12 @@ def test_result_page_surfaces_workflow_summary_and_provenance_detail() -> None:
"renderWorkflowSummaryPanel",
"renderWorkflowSummaryCard",
"renderWorkflowProvenancePanel",
+ "runtime_truth",
+ "interpretation_source_runtime_coverage",
+ "official_execution_layers",
+ "runtime_visibility_status",
+ "VedAstro Runtime Truth",
+ "Interpretation Source Coverage",
"runtime_planner",
"renderRuntimePlannerPills",
"UnifiedConsultationRuntimePlanner",
@@ -2940,3 +2951,27 @@ def test_local_frontend_and_api_runtime_smoke() -> None:
finally:
stop_process(web)
stop_process(api)
+
+
+def test_unified_prashna_and_audited_remedies_are_rendered_from_consultation_workflow() -> None:
+ main = read("main.js")
+ for token in (
+ "renderWorkflowSidecarsSummary",
+ "workflow-sidecars-summary",
+ "bindWorkflowSummaryActions",
+ "data-open-tab",
+ "data-scroll-target",
+ "switchToTab(tabName)",
+ "switchToTab('prashna')",
+ "switchToTab('remedies')",
+ "switchToTab('provenance')",
+ "workflow?.audited_remedies",
+ "workflow?.prashna",
+ "workflow?.muhurta_panchanga",
+ "Muhurta / Panchanga",
+ "Prashna 问事",
+ "补救建议",
+ "renderPrashnaResult(workflow.prashna",
+ "strict_audit_gate",
+ ):
+ assert token in main
diff --git a/tests/test_interpretation_source_advanced_pipeline_contract.py b/tests/test_interpretation_source_advanced_pipeline_contract.py
index a34fedcb..0f706f47 100644
--- a/tests/test_interpretation_source_advanced_pipeline_contract.py
+++ b/tests/test_interpretation_source_advanced_pipeline_contract.py
@@ -178,10 +178,10 @@ def test_prompt_pack_frontend_and_remaining_batch_queue_expose_next_stage_contra
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",
+ "vedastro_official_default_closure",
+ "external_oracle_parity_batch",
+ "install_usage_path_slimming",
]
main_js = (ROOT / "jyotish-app" / "main.js").read_text(encoding="utf-8")
diff --git a/tests/test_interpretation_source_inventory_gate.py b/tests/test_interpretation_source_inventory_gate.py
index ee456dfc..2105d883 100644
--- a/tests/test_interpretation_source_inventory_gate.py
+++ b/tests/test_interpretation_source_inventory_gate.py
@@ -71,6 +71,19 @@ def test_quality_gate_runs_interpretation_source_inventory_gate() -> None:
assert '"scripts/interpretation_source_inventory_gate.py"' in quality_gate
assert '[PYTHON, "scripts/interpretation_source_inventory_gate.py"]' in quality_gate
+ assert '"scripts/sync_final_evidence_packet_status.py"' in quality_gate
+ assert '[PYTHON, "scripts/sync_final_evidence_packet_status.py"]' in quality_gate
+ assert '"scripts/diagnose_external_engine_adapters.py"' in quality_gate
+ assert '[PYTHON, "scripts/diagnose_external_engine_adapters.py", "--json"]' in quality_gate
+ assert '"runtime-truth"' in quality_gate
+ assert "tests/test_interpretation_source_runtime_coverage.py" in quality_gate
+ assert "tests/test_final_jhora_evidence_packet_acceptance.py" in quality_gate
+ assert (
+ 'elif args.profile == "runtime-truth":\n'
+ " pytest_targets = RUNTIME_TRUTH_PYTEST_TARGETS\n"
+ " else:\n"
+ " pytest_targets = CORE_PYTEST_TARGETS"
+ ) in quality_gate
def test_interpretation_source_inventory_gate_classifies_full_candidate_pool() -> None:
diff --git a/tests/test_interpretation_source_next_phase_contract.py b/tests/test_interpretation_source_next_phase_contract.py
index 4d568291..858b1e29 100644
--- a/tests/test_interpretation_source_next_phase_contract.py
+++ b/tests/test_interpretation_source_next_phase_contract.py
@@ -199,3 +199,68 @@ def test_prompt_pack_and_real_reading_regression_expose_content_contracts() -> N
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
+
+
+def test_real_case_studies_batch1_is_exposed_as_local_retrieval_layer() -> None:
+ source_pack = _existing_interpretation_source_pack()
+ case_layer = source_pack["real_case_calibration_layer"]
+ assert case_layer["batch_id"] == "real_case_studies_batch1"
+ assert case_layer["index_status"] == "available"
+ assert case_layer["status"] == "queued"
+ assert "career" in case_layer["case_index_by_domain"]
+ assert "finance" in case_layer["case_index_by_domain"]
+ assert "relationship" in case_layer["case_index_by_domain"]
+ assert (
+ "references/real_case_studies/vedicka/career-success-poverty-prosperity.md"
+ in case_layer["case_index_by_domain"]["career"]
+ )
+ assert (
+ "docs/benchmark/legacy-marriage-v6.1/verify-results-v6.1.json"
+ in case_layer["case_index_by_domain"]["relationship"]
+ )
+ assert (
+ "references/real_case_studies/vedicka/career-success-poverty-prosperity.md"
+ in source_pack["source_refs"]
+ )
+ assert source_pack["real_case_calibration"]["local_index_status"] == "available"
+ assert source_pack["real_case_calibration"]["status"] == "blocked"
+
+
+def test_open_source_batches_and_external_gaps_are_visible_without_polluting_truth() -> None:
+ source_pack = _existing_interpretation_source_pack()
+
+ rishi_layer = source_pack["rishi_ai_mcp_batch1_layer"]
+ assert rishi_layer["status"] == "available"
+ assert rishi_layer["promotion_status"] == "open_source_reference_layer"
+ assert rishi_layer["runtime_truth_status"] == "not_primary_truth"
+ assert "career" in rishi_layer["domain_map"]
+ assert "relationship" in rishi_layer["domain_map"]
+ assert "references/open_source_sources/rishi-ai-mcp/.agents/rules/rishi-ai.md" in rishi_layer["source_refs"]
+ assert (
+ "references/open_source_sources/rishi-ai-mcp/.agents/workflows/career-analysis.md"
+ in rishi_layer["domain_map"]["career"]
+ )
+
+ vedic_layer = source_pack["vedic_astro_skills_batch1_layer"]
+ assert vedic_layer["status"] == "available"
+ assert vedic_layer["promotion_status"] == "external_skill_reference_layer"
+ assert vedic_layer["runtime_truth_status"] == "not_primary_truth"
+ assert "calculator" in vedic_layer["domain_map"]
+ assert "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/SKILL.md" in vedic_layer["source_refs"]
+ assert (
+ "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/data_contract.md"
+ in vedic_layer["domain_map"]["reader_validation"]
+ )
+
+ external_gaps = source_pack["external_closure_gap_layer"]
+ assert external_gaps["vedastro_official"]["status"] == "blocked"
+ assert external_gaps["oracle_parity"]["status"] == "blocked"
+ assert external_gaps["install_usage_path"]["status"] == "needs_slimming"
+
+ queue = source_pack["remaining_priority1_batch_queue"]
+ assert queue["next_batches"] == [
+ "references_batch2",
+ "vedastro_official_default_closure",
+ "external_oracle_parity_batch",
+ "install_usage_path_slimming",
+ ]
diff --git a/tests/test_interpretation_source_runtime_coverage.py b/tests/test_interpretation_source_runtime_coverage.py
new file mode 100644
index 00000000..f7c4b40b
--- /dev/null
+++ b/tests/test_interpretation_source_runtime_coverage.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+"""Regression tests for interpretation source runtime coverage report."""
+
+from __future__ import annotations
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def test_interpretation_source_runtime_coverage_reports_machine_checkable_gap() -> None:
+ completed = subprocess.run(
+ [
+ sys.executable,
+ "scripts/interpretation_source_runtime_coverage.py",
+ ],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=120,
+ check=False,
+ )
+ assert completed.returncode == 0, completed.stderr or completed.stdout
+ report = json.loads(completed.stdout)
+ assert report["scope"] == "interpretation_source_runtime_coverage"
+ assert report["status"] == "partial"
+ assert report["source_pack_status"] == "used"
+ assert "dasha_timing_layer_used" in report["proven_runtime_markers"]
+ assert "references/open_source_sources/jyotishganit" in report["not_fully_closed"]
+ assert report["inventory_gate"]["status"] == "pass"
diff --git a/tests/test_jyotishganit_adapter_diagnostics.py b/tests/test_jyotishganit_adapter_diagnostics.py
new file mode 100644
index 00000000..40e4d018
--- /dev/null
+++ b/tests/test_jyotishganit_adapter_diagnostics.py
@@ -0,0 +1,39 @@
+from __future__ import annotations
+
+import json
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def _run_diag(env: dict[str, str] | None = None) -> dict:
+ completed = subprocess.run(
+ [sys.executable, "scripts/diagnose_jyotishganit_adapter.py", "--json"],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=30,
+ check=False,
+ env={**os.environ, **(env or {})},
+ )
+ assert completed.returncode == 0, completed.stderr or completed.stdout
+ return json.loads(completed.stdout)
+
+
+def test_jyotishganit_adapter_diagnostics_reports_current_status() -> None:
+ report = _run_diag()
+
+ assert report["scope"] == "jyotishganit_adapter_diagnostics"
+ assert report["adapter_path"] == "references/open_source_sources/jyotishganit"
+ assert report["status"] in {"available", "missing_checkout", "runtime_error"}
+ assert report["license"] == "MIT"
+
+
+def test_jyotishganit_adapter_diagnostics_reports_missing_checkout() -> None:
+ report = _run_diag({"JYOTISHGANIT_ADAPTER_PATH": "references/open_source_sources/__missing_jyotishganit__"})
+
+ assert report["status"] == "missing_checkout"
diff --git a/tests/test_marriage_precision_extensions.py b/tests/test_marriage_precision_extensions.py
new file mode 100644
index 00000000..d099f115
--- /dev/null
+++ b/tests/test_marriage_precision_extensions.py
@@ -0,0 +1,83 @@
+from __future__ import annotations
+
+import sys
+from datetime import datetime
+from pathlib import Path
+
+
+SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
+if str(SCRIPTS) not in sys.path:
+ sys.path.insert(0, str(SCRIPTS))
+
+from jaimini import calc_darapada # noqa: E402
+from kp_system import calc_kp_analysis, calc_kp_dba_timeline # noqa: E402
+
+
+USER_PLANET_LONS = {
+ "Sun": 3.5036111111111112,
+ "Moon": 311.8041666666667,
+ "Mars": 91.31277777777778,
+ "Mercury": 338.52805555555557,
+ "Jupiter": 163.81833333333333,
+ "Venus": 340.5394444444444,
+ "Saturn": 304.28305555555556,
+ "Rahu": 231.02916666666667,
+ "Ketu": 51.028888888888886,
+}
+
+USER_PLANET_HOUSES = {
+ "Sun": 9,
+ "Moon": 7,
+ "Mars": 12,
+ "Mercury": 8,
+ "Jupiter": 2,
+ "Venus": 8,
+ "Saturn": 7,
+ "Rahu": 4,
+ "Ketu": 10,
+}
+
+
+def test_calc_darapada_returns_a7_relationship_maintenance_fields():
+ darapada = calc_darapada(4, USER_PLANET_LONS)
+
+ assert darapada["name"] == "Darapada (A7)"
+ assert darapada["source_house_num"] == 7
+ assert darapada["sign"] == "Scorpio"
+ assert darapada["second_from_a7"] == "Sagittarius"
+ assert darapada["eighth_from_a7"] == "Gemini"
+
+
+def test_kp_dba_timeline_scores_marriage_houses():
+ signs = [
+ "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
+ "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
+ ]
+ planet_positions = {
+ planet: {
+ "longitude": longitude,
+ "sign": signs[int(longitude // 30)],
+ "degree": longitude % 30,
+ "house": USER_PLANET_HOUSES[planet],
+ }
+ for planet, longitude in USER_PLANET_LONS.items()
+ }
+ kp = calc_kp_analysis(planet_positions, "Leo")
+ planet_significators = {
+ planet: data["significators"]
+ for planet, data in kp["planets"].items()
+ }
+
+ timeline = calc_kp_dba_timeline(
+ datetime(REDACTED_YEAR, 4, 17, 14, 49),
+ 311.8041666666667,
+ datetime(2027, 1, 1),
+ datetime(2029, 12, 31),
+ planet_significators,
+ )
+
+ assert timeline["birth_star_lord"] == "Rahu"
+ assert timeline["target_start"].startswith("2027-01-01")
+ assert timeline["periods"]
+ assert any(row["judgement"] == "mixed" for row in timeline["periods"])
+ assert all({"md_lord", "ad_lord", "pd_lord", "start", "end", "marriage_score"} <= set(row) for row in timeline["periods"])
diff --git a/tests/test_mcp_strict_workflow_career.py b/tests/test_mcp_strict_workflow_career.py
index 4c04edfa..7d09bde8 100644
--- a/tests/test_mcp_strict_workflow_career.py
+++ b/tests/test_mcp_strict_workflow_career.py
@@ -4,9 +4,22 @@
from __future__ import annotations
import jyotish_engine
+import mcp_server
from mcp_server import _collect_strict_evidence, _existing_interpretation_source_pack
+SOURCE_LAYER_CONTEXT = {
+ "dasha_timing_layer_used",
+ "varga_strength_layer_used",
+ "annual_special_layer_context",
+ "modifier_obstacle_layer_used",
+}
+
+
+def _assert_context_contains(context: list[str], expected: set[str]) -> None:
+ assert expected <= set(context)
+ assert SOURCE_LAYER_CONTEXT <= set(context)
+
def _base_career_result() -> dict:
return {
@@ -62,14 +75,17 @@ def test_career_collects_a10_amk_karakamsha_as_strict_evidence() -> None:
}
assert strict["event_judgement"]["event_family"] == "career"
assert strict["event_judgement"]["dominant_label"] == "career_status"
- assert strict["event_judgement"]["secondary_context"] == [
- "a10_active",
- "amk_active",
- "karakamsha_context",
- "functional_benefic_malefic_used",
- "argala_support",
- "vedastro_range_scan_missing",
- ]
+ _assert_context_contains(
+ strict["event_judgement"]["secondary_context"],
+ {
+ "a10_active",
+ "amk_active",
+ "karakamsha_context",
+ "functional_benefic_malefic_used",
+ "argala_support",
+ "vedastro_range_scan_missing",
+ },
+ )
def test_career_strict_contract_attaches_existing_interpretation_source_pack() -> None:
@@ -120,6 +136,54 @@ def test_interpretation_source_inventory_classifies_sources_without_promoting_dr
assert all(path not in source_pack["source_refs"] for path in draft_refs)
+def test_mcp_strict_workflow_returns_runtime_evidence_log(monkeypatch) -> None:
+ def fake_execute(**kwargs):
+ return {
+ "chart": {
+ "modules": {},
+ "ai_prompt_pack": {
+ "evidence_snapshot": {
+ "vedastro_official_snapshot": {
+ "status": "ok",
+ "official_primary_evidence": {"chart_core": {"status": "ok"}},
+ }
+ }
+ },
+ },
+ "routing": {"question_type": "career", "primary_theme": "career"},
+ "entry_mode": "direct_chart",
+ "runtime_planner": {"executed_steps": ["compute_chart"], "skipped_steps": []},
+ }
+
+ monkeypatch.setattr(mcp_server, "_execute_mcp_consultation_workflow", fake_execute)
+ monkeypatch.setattr(mcp_server, "_maybe_attach_vedastro_evidence", lambda route, chart, **kwargs: chart)
+ monkeypatch.setattr(mcp_server, "_collect_strict_evidence", lambda route, chart: {"question_type": route})
+
+ result = mcp_server.strict_workflow(
+ question="career timing",
+ year=REDACTED_YEAR,
+ month=4,
+ day=17,
+ hour=14,
+ minute=49,
+ lat=36.42,
+ lon=114.2,
+ tz=8,
+ age=33,
+ transit_date="2026-07-05",
+ )
+
+ assert result["runtime_evidence_log"]["surface"] == "skill_mcp"
+ assert result["runtime_evidence_log"]["route"]["question_type"] == "career"
+ assert result["runtime_evidence_log"]["vedastro_cloud_state"] == "official_verified"
+ assert result["machine_evidence_packet"]["status"] == "partial"
+ assert result["real_case_calibration"]["status"] == "partial_scored"
+ assert result["runtime_evidence_log"]["evidence_packet_contract"]["status"] == "partial"
+ assert result["runtime_evidence_log"]["real_case_calibration"]["status"] == "partial_scored"
+ assert result["runtime_evidence_log"]["quality_gate"]["technique_audit_table_required"] is True
+ assert result["runtime_evidence_log"]["quality_gate"]["technique_audit_table"][0]["technique"] == "VedAstro Cloud State"
+
+
def test_career_blocks_label_when_d10_is_missing_but_preserves_jaimini_context() -> None:
result = _base_career_result()
del result["modules"]["varga_full"]["D10_Dasamsa"]
@@ -129,14 +193,17 @@ def test_career_blocks_label_when_d10_is_missing_but_preserves_jaimini_context()
assert "d10_dasamsa" in strict["missing_evidence"]
assert strict["blocked"] is True
assert strict["event_judgement"]["dominant_label"] is None
- assert strict["event_judgement"]["secondary_context"] == [
- "a10_active",
- "amk_active",
- "karakamsha_context",
- "functional_benefic_malefic_used",
- "argala_support",
- "vedastro_range_scan_missing",
- ]
+ _assert_context_contains(
+ strict["event_judgement"]["secondary_context"],
+ {
+ "a10_active",
+ "amk_active",
+ "karakamsha_context",
+ "functional_benefic_malefic_used",
+ "argala_support",
+ "vedastro_range_scan_missing",
+ },
+ )
def test_career_dignity_guardrail_uses_career_relevant_planets_only() -> None:
diff --git a/tests/test_mcp_strict_workflow_finance.py b/tests/test_mcp_strict_workflow_finance.py
index e6852a19..8f8ec3b0 100644
--- a/tests/test_mcp_strict_workflow_finance.py
+++ b/tests/test_mcp_strict_workflow_finance.py
@@ -12,6 +12,18 @@ from mcp_server import (
_derive_yogi_wealth_support,
)
+SOURCE_LAYER_CONTEXT = {
+ "dasha_timing_layer_used",
+ "varga_strength_layer_used",
+ "annual_special_layer_context",
+ "modifier_obstacle_layer_used",
+}
+
+
+def _assert_context_contains(context: list[str], expected: set[str]) -> None:
+ assert expected <= set(context)
+ assert SOURCE_LAYER_CONTEXT <= set(context)
+
def test_finance_public_wealth_label_requires_at_least_moderate_window() -> None:
judgement = _derive_event_judgement(
@@ -31,7 +43,7 @@ def test_finance_public_wealth_label_requires_at_least_moderate_window() -> None
assert judgement["verdict"] == "weak_window_needs_confirmation"
assert judgement["payout_label"] is None
assert judgement["dominant_label"] is None
- assert judgement["secondary_context"] == []
+ assert set(judgement["secondary_context"]) == SOURCE_LAYER_CONTEXT
def test_finance_strict_contract_attaches_existing_interpretation_source_pack() -> None:
@@ -72,7 +84,7 @@ def test_finance_public_wealth_label_can_lift_visible_wealth_cases() -> None:
assert judgement["verdict"] == "moderate_probability_window"
assert judgement["payout_label"] == "public_wealth_status"
assert judgement["dominant_label"] == "public_wealth_status"
- assert judgement["secondary_context"] == ["career_status", "gains_wishes"]
+ _assert_context_contains(judgement["secondary_context"], {"career_status", "gains_wishes"})
def test_finance_prefers_income_growth_when_gains_outrun_public_status_signals() -> None:
@@ -92,7 +104,7 @@ def test_finance_prefers_income_growth_when_gains_outrun_public_status_signals()
assert judgement["verdict"] == "moderate_probability_window"
assert judgement["payout_label"] == "income_growth"
assert judgement["dominant_label"] == "income_growth"
- assert judgement["secondary_context"] == ["wealth_family"]
+ _assert_context_contains(judgement["secondary_context"], {"wealth_family"})
def test_finance_strong_wealth_promise_can_unlock_public_wealth_status() -> None:
@@ -120,7 +132,7 @@ def test_finance_strong_wealth_promise_can_unlock_public_wealth_status() -> None
assert judgement["verdict"] == "moderate_probability_window"
assert judgement["payout_label"] == "public_wealth_status"
assert judgement["dominant_label"] == "public_wealth_status"
- assert judgement["secondary_context"] == ["career_status", "gains_wishes"]
+ _assert_context_contains(judgement["secondary_context"], {"career_status", "gains_wishes"})
def test_finance_source_diversity_adds_small_bump_without_changing_verdict_band() -> None:
diff --git a/tests/test_mcp_strict_workflow_relationship.py b/tests/test_mcp_strict_workflow_relationship.py
index e496f728..c0316659 100644
--- a/tests/test_mcp_strict_workflow_relationship.py
+++ b/tests/test_mcp_strict_workflow_relationship.py
@@ -14,6 +14,19 @@ sys.path.insert(0, str(ROOT / "scripts"))
import jyotish_engine # noqa: E402
+SOURCE_LAYER_CONTEXT = {
+ "dasha_timing_layer_used",
+ "varga_strength_layer_used",
+ "annual_special_layer_context",
+ "modifier_obstacle_layer_used",
+}
+
+
+def _assert_context_contains(context: list[str], expected: set[str]) -> None:
+ assert expected <= set(context)
+ assert SOURCE_LAYER_CONTEXT <= set(context)
+
+
def _base_relationship_result() -> dict:
return {
"modules": {
@@ -53,13 +66,16 @@ def test_relationship_jaimini_bridge_lifts_legal_marriage_label() -> None:
"source": "jaimini_bridge_v1",
}
assert strict["event_judgement"]["dominant_label"] == "legal_marriage"
- assert strict["event_judgement"]["secondary_context"] == [
- "darakaraka_active",
- "jaimini_support",
- "ul_support",
- "virodhargala_obstruction",
- "vedastro_range_scan_missing",
- ]
+ _assert_context_contains(
+ strict["event_judgement"]["secondary_context"],
+ {
+ "darakaraka_active",
+ "jaimini_support",
+ "ul_support",
+ "virodhargala_obstruction",
+ "vedastro_range_scan_missing",
+ },
+ )
def test_relationship_strict_contract_attaches_existing_interpretation_source_pack() -> None:
@@ -95,13 +111,16 @@ def test_relationship_jaimini_bridge_stays_context_only_when_d9_missing() -> Non
"source": "jaimini_bridge_v1",
}
assert strict["event_judgement"]["dominant_label"] is None
- assert strict["event_judgement"]["secondary_context"] == [
- "darakaraka_active",
- "jaimini_support",
- "ul_support",
- "virodhargala_obstruction",
- "vedastro_range_scan_missing",
- ]
+ _assert_context_contains(
+ strict["event_judgement"]["secondary_context"],
+ {
+ "darakaraka_active",
+ "jaimini_support",
+ "ul_support",
+ "virodhargala_obstruction",
+ "vedastro_range_scan_missing",
+ },
+ )
def test_relationship_jaimini_bridge_cannot_lift_legal_marriage_when_narayana_is_missing() -> None:
diff --git a/tests/test_muhurta.py b/tests/test_muhurta.py
index 81709143..80d563ff 100644
--- a/tests/test_muhurta.py
+++ b/tests/test_muhurta.py
@@ -13,6 +13,7 @@ from muhurta import (
calc_vara, calc_hora, calc_abhijit_muhurta, calc_panchanga,
check_activity_muhurta, muhurta_full_report, calc_daytime_inauspicious_periods,
panchanga_range_report, muhurta_range_search, calc_sunrise_sunset_local, calc_panchanga_end_times,
+ build_muhurta_sidecar,
classify_vrata_tags, classify_panchanga_condition_tags, calc_choghadiya_windows, calc_hora_windows,
TITHI_NAMES, TITHI_QUALITY, NAKSHATRAS, NAKSHATRA_TYPE,
YOGA_NAMES, YOGA_QUALITY, KARANA_NAMES, KARANA_QUALITY,
@@ -410,3 +411,21 @@ class TestMuhurtaRangeSearch:
assert {'date', 'score', 'quality', 'activity_verdict', 'recommended_windows', 'evidence'} <= set(first)
assert first['recommended_windows']
assert first['evidence']['panchanga']
+
+
+class TestMuhurtaSidecar:
+ def test_build_muhurta_sidecar_returns_compact_workflow_shape(self):
+ result = build_muhurta_sidecar(
+ date_str='2026-07-08',
+ activity='business',
+ lat=36.42,
+ lon=114.2,
+ tz=8,
+ )
+ assert result['status'] == 'ok'
+ assert result['source'] == 'local_muhurta.py'
+ assert result['activity'] == 'business'
+ assert result['report_mode'] == 'muhurta_date_range_solver'
+ assert result['panchanga']['query_date'] == '2026-07-08'
+ assert result['best_windows']
+ assert 'next_action' in result
diff --git a/tests/test_pre_work_check.py b/tests/test_pre_work_check.py
new file mode 100644
index 00000000..035bc3a8
--- /dev/null
+++ b/tests/test_pre_work_check.py
@@ -0,0 +1,39 @@
+from __future__ import annotations
+
+from scripts.pre_work_check import (
+ DEFAULT_COMMAND_TIMEOUT_SECONDS,
+ DEFAULT_FRAGMENT_TIMEOUT_SECONDS,
+ EXTERNAL_ENGINE_DIAGNOSTIC_TARGET,
+ FOCUSED_TEST_TARGETS,
+ PRE_WORK_DOCS,
+ classify_status,
+)
+
+
+def test_classify_status_keeps_remote_blocked_distinct_from_failure() -> None:
+ assert classify_status(True, True, True, "verified") == "pass"
+ assert classify_status(True, True, True, "blocked") == "pass_with_remote_blocked"
+ assert classify_status(True, True, False, "verified") == "fail"
+
+
+def test_pre_work_check_runs_governance_test_set() -> None:
+ assert "tests/test_runtime_import_boundaries.py" in FOCUSED_TEST_TARGETS
+ assert "tests/test_project_fragment_governance.py" in FOCUSED_TEST_TARGETS
+ assert "tests/test_preflight_fragment_scan.py" in FOCUSED_TEST_TARGETS
+ assert "tests/test_remote_repo_visibility_check.py" in FOCUSED_TEST_TARGETS
+ assert "tests/test_pre_work_check.py" in FOCUSED_TEST_TARGETS
+
+
+def test_pre_work_check_requires_error_ledger_and_fragment_sweeps() -> None:
+ assert "docs/research/pre_work_error_ledger.md" in PRE_WORK_DOCS
+ assert "docs/research/whole_machine_fragment_sweep_2026_07_05.md" in PRE_WORK_DOCS
+ assert "docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md" in PRE_WORK_DOCS
+
+
+def test_pre_work_check_includes_external_engine_diagnostics() -> None:
+ assert EXTERNAL_ENGINE_DIAGNOSTIC_TARGET == "scripts/diagnose_external_engine_adapters.py"
+
+
+def test_pre_work_check_child_command_timeout_stays_short() -> None:
+ assert DEFAULT_COMMAND_TIMEOUT_SECONDS <= 45
+ assert DEFAULT_FRAGMENT_TIMEOUT_SECONDS >= DEFAULT_COMMAND_TIMEOUT_SECONDS
diff --git a/tests/test_preflight_fragment_scan.py b/tests/test_preflight_fragment_scan.py
index db26fccf..10308d10 100644
--- a/tests/test_preflight_fragment_scan.py
+++ b/tests/test_preflight_fragment_scan.py
@@ -4,15 +4,22 @@
from __future__ import annotations
import json
+import os
import subprocess
import sys
from pathlib import Path
+import pytest
+
ROOT = Path(__file__).resolve().parents[1]
-def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets() -> None:
+@pytest.fixture(scope="module")
+def report() -> dict:
+ cached_report = os.environ.get("PREFLIGHT_FRAGMENT_SCAN_REPORT")
+ if cached_report and Path(cached_report).exists():
+ return json.loads(Path(cached_report).read_text(encoding="utf-8"))
completed = subprocess.run(
[
sys.executable,
@@ -24,10 +31,11 @@ def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets() ->
timeout=120,
check=False,
)
-
assert completed.returncode == 0, completed.stderr or completed.stdout
- report = json.loads(completed.stdout)
+ return json.loads(completed.stdout)
+
+def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets(report: dict) -> None:
assert report["scope"] == "preflight_fragment_scan"
assert report["summary"]["authority_layers_scanned"] == 4
assert report["summary"]["production_truth_layer"] == "main_repo_truth"
@@ -59,22 +67,7 @@ def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets() ->
assert "historical_event_accuracy_not_proven" in risk_ids
-def test_preflight_fragment_scan_preserves_audit_capability_and_oracle_boundaries() -> None:
- completed = subprocess.run(
- [
- sys.executable,
- "scripts/preflight_fragment_scan.py",
- ],
- cwd=ROOT,
- text=True,
- capture_output=True,
- timeout=120,
- check=False,
- )
-
- assert completed.returncode == 0, completed.stderr or completed.stdout
- report = json.loads(completed.stdout)
-
+def test_preflight_fragment_scan_preserves_audit_capability_and_oracle_boundaries(report: dict) -> None:
audit = report["upstream_audits"]
assert audit["capability_audit"]["valid"] is True
assert audit["capability_audit"]["technique_count"] >= 89
@@ -96,22 +89,7 @@ def test_preflight_fragment_scan_preserves_audit_capability_and_oracle_boundarie
assert any(".workbuddy/skills/jyotish-vedic-astrology" in layer for layer in cleanup_map["focus_layers"])
-def test_preflight_fragment_scan_emits_repo_cleanup_priorities() -> None:
- completed = subprocess.run(
- [
- sys.executable,
- "scripts/preflight_fragment_scan.py",
- ],
- cwd=ROOT,
- text=True,
- capture_output=True,
- timeout=120,
- check=False,
- )
-
- assert completed.returncode == 0, completed.stderr or completed.stdout
- report = json.loads(completed.stdout)
-
+def test_preflight_fragment_scan_emits_repo_cleanup_priorities(report: dict) -> None:
priorities = report["cleanup_priorities"]
assert len(priorities) >= 2
assert all(item["id"] != "remove_mirror_runtime_dependency" for item in priorities)
@@ -120,82 +98,13 @@ def test_preflight_fragment_scan_emits_repo_cleanup_priorities() -> None:
assert any(item["id"] == "promote_or_archive_high_value_drafts" for item in priorities)
-def test_preflight_fragment_scan_excludes_promoted_first_five_drafts_from_unpromoted_pool() -> None:
- completed = subprocess.run(
- [
- sys.executable,
- "scripts/preflight_fragment_scan.py",
- ],
- cwd=ROOT,
- text=True,
- capture_output=True,
- timeout=120,
- check=False,
- )
-
- assert completed.returncode == 0, completed.stderr or completed.stdout
- report = json.loads(completed.stdout)
- unpromoted_paths = {item["path"] for item in report["high_value_unpromoted"]}
-
- blocked_names = {
- "antigravity_round36_tajika_sahams_external_closure_pack_2026_06_26.md",
- "antigravity_round37_dasha_external_oracle_shortest_closure_board_2026_06_26.md",
- "antigravity_round37_shadbala_absolute_value_frontier_board_2026_06_26.md",
- "antigravity_round39_yogi_wealth_bridge_audit_2026_06_28.md",
- "three_fronts_skill_depth_audit_2026_06_26.md",
- }
- assert all(not any(name in path for path in unpromoted_paths) for name in blocked_names)
-
-
-def test_preflight_fragment_scan_excludes_promoted_third_batch_drafts_from_unpromoted_pool() -> None:
- completed = subprocess.run(
- [
- sys.executable,
- "scripts/preflight_fragment_scan.py",
- ],
- cwd=ROOT,
- text=True,
- capture_output=True,
- timeout=120,
- check=False,
- )
-
- assert completed.returncode == 0, completed.stderr or completed.stdout
- report = json.loads(completed.stdout)
- unpromoted_paths = {item["path"] for item in report["high_value_unpromoted"]}
-
- blocked_names = {
- "antigravity_round36_asc_degree_yogi_tight_orb_wealth_pack_2026_06_26.md",
- "antigravity_round37_tajika_sahams_annual_closure_board_2026_06_26.md",
- "antigravity_round38_whole_machine_fragment_reuse_sixth_pass_2026_06_26.md",
- "antigravity_round40_shadbala_absolute_authority_ladder_2026_06_27.md",
- "antigravity_round40_tajika_annual_second_wave_board_2026_06_27.md",
- }
- assert all(not any(name in path for path in unpromoted_paths) for name in blocked_names)
-
-
-def test_preflight_fragment_scan_excludes_promoted_fourth_batch_drafts_from_unpromoted_pool() -> None:
- completed = subprocess.run(
- [
- sys.executable,
- "scripts/preflight_fragment_scan.py",
- ],
- cwd=ROOT,
- text=True,
- capture_output=True,
- timeout=120,
- check=False,
- )
-
- assert completed.returncode == 0, completed.stderr or completed.stdout
- report = json.loads(completed.stdout)
- unpromoted_paths = {item["path"] for item in report["high_value_unpromoted"]}
-
- blocked_names = {
- "antigravity_round40_whole_machine_fragment_reuse_shortlist_2026_06_27.md",
- "dasha_accuracy_closure_status_2026_06_26.md",
- "dasha_code_only_priority_rerank_2026_06_26.md",
- "skill_fragment_map_and_source_of_truth_2026_06_26.md",
- "skill_truth_conflict_matrix_2026_06_26.md",
- }
- assert all(not any(name in path for path in unpromoted_paths) for name in blocked_names)
+def test_preflight_fragment_scan_links_pre_work_governance(report: dict) -> None:
+ governance = report["governance"]
+ assert governance["pre_work_error_ledger"]["exists"] is True
+ assert governance["pre_work_error_ledger"]["path"].endswith("pre_work_error_ledger.md")
+ assert governance["latest_fragment_sweep"]["exists"] is True
+ assert governance["latest_fragment_sweep"]["remote_ref_parity"] == "blocked_until_git_ls_remote_succeeds"
+ assert governance["prior_fragment_sweep"]["exists"] is True
+ assert "python3 -m pytest" in governance["acceptance_command"]
+ assert "tests/test_remote_repo_visibility_check.py" in governance["acceptance_command"]
+ assert "tests/test_pre_work_check.py" in governance["acceptance_command"]
diff --git a/tests/test_project_fragment_governance.py b/tests/test_project_fragment_governance.py
new file mode 100644
index 00000000..6f870725
--- /dev/null
+++ b/tests/test_project_fragment_governance.py
@@ -0,0 +1,56 @@
+from __future__ import annotations
+
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+ERROR_LEDGER = ROOT / "docs" / "research" / "pre_work_error_ledger.md"
+SWEEP = ROOT / "docs" / "research" / "whole_machine_fragment_sweep_2026_07_05.md"
+
+
+def test_pre_work_error_ledger_exists_and_names_repeat_failures() -> None:
+ text = ERROR_LEDGER.read_text(encoding="utf-8")
+ required = [
+ "ERR-001",
+ "ERR-005",
+ "ERR-007",
+ "ERR-009",
+ "ERR-017",
+ "ERR-018",
+ "tests/test_runtime_import_boundaries.py",
+ "tests/test_project_fragment_governance.py",
+ "docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md",
+ "scripts/diagnose_external_engine_adapters.py --json",
+ "docs/research/external_engine_blocker_research_2026_07_05.md",
+ ]
+ missing = [item for item in required if item not in text]
+ assert missing == []
+
+
+def test_agents_requires_pre_work_error_ledger() -> None:
+ text = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
+ assert "docs/research/pre_work_error_ledger.md" in text
+ assert "开工前" in text
+ assert "python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45" in text
+ assert "scripts/diagnose_external_engine_adapters.py --json" in text
+
+
+def test_fragment_sweep_records_main_mirror_and_remote_boundaries() -> None:
+ text = SWEEP.read_text(encoding="utf-8")
+ required = [
+ "/Users/wuyongnaren/Documents/印度占星",
+ "/Users/wuyongnaren/.workbuddy/skills/jyotish-vedic-astrology",
+ "/Users/wuyongnaren/Documents/星轨talk/engines-repo/jyotish",
+ "github.com:732642856/yinduzhanxing.git",
+ "SSL_ERROR_SYSCALL",
+ "terminal ref parity is `blocked`",
+ ]
+ missing = [item for item in required if item not in text]
+ assert missing == []
+
+
+def test_error_ledger_contains_split_scan_commands_not_unbounded_home_scan() -> None:
+ text = ERROR_LEDGER.read_text(encoding="utf-8")
+ assert "-maxdepth 6" in text
+ assert "-maxdepth 7" in text
+ assert "find /Users/wuyongnaren -type" not in text
diff --git a/tests/test_pyjhora_adapter_diagnostics.py b/tests/test_pyjhora_adapter_diagnostics.py
new file mode 100644
index 00000000..c28ca8f5
--- /dev/null
+++ b/tests/test_pyjhora_adapter_diagnostics.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+import json
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def _run_diag(env: dict[str, str] | None = None) -> dict:
+ completed = subprocess.run(
+ [sys.executable, "scripts/diagnose_pyjhora_adapter.py", "--json"],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=30,
+ check=False,
+ env={**os.environ, **(env or {})},
+ )
+ assert completed.returncode == 0, completed.stderr or completed.stdout
+ return json.loads(completed.stdout)
+
+
+def test_pyjhora_adapter_diagnostics_reports_current_status() -> None:
+ report = _run_diag()
+
+ assert report["scope"] == "pyjhora_adapter_diagnostics"
+ assert report["adapter_command"] == "python3 benchmarks/jyotish/scripts/run_pyjhora_compare.py"
+ assert report["status"] in {"available", "missing_dependency"}
+
+
+def test_pyjhora_adapter_diagnostics_reports_missing_dependency() -> None:
+ report = _run_diag({"PYJHORA_MODULE_NAME": "__definitely_missing_pyjhora_module__"})
+
+ assert report["status"] == "missing_dependency"
+ assert report["missing_dependency"] == "__definitely_missing_pyjhora_module__"
+
+
+def test_pyjhora_adapter_diagnostics_reports_actionable_external_boundary() -> None:
+ report = _run_diag({"PYJHORA_MODULE_NAME": "__definitely_missing_pyjhora_module__"})
+
+ assert report["install_hint"]["package"] == "PyJHora"
+ assert "pip install PyJHora" in report["install_hint"]["commands"]
+ assert report["license_boundary"] == "AGPL external benchmark only; do not vendor or make it a runtime dependency."
+ assert report["ephemeris_data_note"]
diff --git a/tests/test_remote_repo_visibility_check.py b/tests/test_remote_repo_visibility_check.py
new file mode 100644
index 00000000..d4ba2b8f
--- /dev/null
+++ b/tests/test_remote_repo_visibility_check.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from scripts.remote_repo_visibility_check import github_slug_from_remote_url, parse_ls_remote
+
+
+def test_github_slug_from_remote_url_accepts_ssh_and_https() -> None:
+ assert github_slug_from_remote_url("git@github.com:732642856/yinduzhanxing.git") == "732642856/yinduzhanxing"
+ assert github_slug_from_remote_url("https://github.com/732642856/yinduzhanxing.git") == "732642856/yinduzhanxing"
+
+
+def test_parse_ls_remote_separates_heads_and_tags() -> None:
+ parsed = parse_ls_remote(
+ "\n".join(
+ [
+ "abc123 refs/heads/main",
+ "def456 refs/heads/codex/release-hygiene-ci",
+ "aaa111 refs/tags/v1.0.0",
+ "bbb222 refs/tags/v1.0.0^{}",
+ ]
+ )
+ )
+ assert parsed["heads"]["main"] == "abc123"
+ assert parsed["heads"]["codex/release-hygiene-ci"] == "def456"
+ assert parsed["tags"]["v1.0.0"] == "aaa111"
+ assert parsed["ref_count"] == 3
diff --git a/tests/test_unified_consultation_orchestrator.py b/tests/test_unified_consultation_orchestrator.py
index 938e0a26..46f2defe 100644
--- a/tests/test_unified_consultation_orchestrator.py
+++ b/tests/test_unified_consultation_orchestrator.py
@@ -82,4 +82,232 @@ def test_unified_consultation_orchestrator_rectification_entry_runs_gate_first()
assert planner["entry_mode"] == "rectification"
assert planner["sync_steps"][0] == "run_rectification_gate"
assert "compute_chart" in planner["sync_steps"]
- assert "run_thematic_report" in planner["sync_steps"]
+
+
+def test_unified_consultation_orchestrator_prashna_entry_runs_prashna_before_thematic() -> None:
+ orchestrator = UnifiedConsultationOrchestrator()
+ themes = orchestrator.normalize_themes(["wealth"])
+ route = orchestrator.resolve_route("时间问事:这个合作能成吗", themes)
+
+ planner = orchestrator.runtime_planner(
+ entry_mode="prashna",
+ question="时间问事:这个合作能成吗",
+ themes=themes,
+ route_packet=route,
+ events=[],
+ surface="api_web",
+ high_rigor=False,
+ )
+
+ assert planner["entry_mode"] == "prashna"
+ assert planner["sync_steps"][0] == "run_prashna"
+ assert "compute_chart" not in planner["sync_steps"]
+
+
+def test_unified_consultation_orchestrator_timing_route_adds_muhurta_sidecar() -> None:
+ orchestrator = UnifiedConsultationOrchestrator()
+ themes = orchestrator.normalize_themes(["career"])
+ route = orchestrator.resolve_route("2026年何时适合谈合作和推进项目的应期", themes)
+ planner = orchestrator.runtime_planner(
+ entry_mode="direct_chart",
+ question="2026年何时适合谈合作和推进项目的应期",
+ themes=themes,
+ route_packet=route,
+ events=[],
+ surface="api_web",
+ high_rigor=False,
+ )
+ assert planner["route"]["question_type"] == "timing"
+ assert "run_muhurta_panchanga" in planner["sync_steps"]
+ assert planner["sync_steps"].index("run_muhurta_panchanga") < planner["sync_steps"].index("run_thematic_report")
+
+
+def test_unified_consultation_orchestrator_prefers_timing_when_career_question_asks_when() -> None:
+ orchestrator = UnifiedConsultationOrchestrator()
+ themes = orchestrator.normalize_themes(["career"])
+ route = orchestrator.resolve_route("2026年什么时候会有事业机会", themes)
+ assert route["question_type"] == "timing"
+
+
+def test_runtime_evidence_log_classifies_official_verified_local_fallback_and_blocked() -> None:
+ orchestrator = UnifiedConsultationOrchestrator()
+ route = {"question_type": "career", "primary_theme": "career"}
+
+ verified = orchestrator.runtime_evidence_log(
+ surface="api_web",
+ entry_mode="direct_chart",
+ route_packet=route,
+ executed_steps=["compute_chart"],
+ skipped_steps=[],
+ vedastro_official={
+ "runtime_truth": {
+ "status": "ok",
+ "official_execution_layers": {"chart_core": "ok"},
+ "fallback_active": False,
+ },
+ "raw_response": {"source": "official"},
+ },
+ )
+ assert verified["vedastro_cloud_state"] == "official_verified"
+
+ fallback = orchestrator.runtime_evidence_log(
+ surface="skill_mcp",
+ entry_mode="direct_chart",
+ route_packet=route,
+ executed_steps=["compute_chart"],
+ skipped_steps=[],
+ vedastro_official={"runtime_truth": {"status": "network_execution_disabled", "fallback_active": True}},
+ )
+ assert fallback["vedastro_cloud_state"] == "local_fallback"
+
+ blocked = orchestrator.runtime_evidence_log(
+ surface="skill_mcp",
+ entry_mode="direct_chart",
+ route_packet=route,
+ executed_steps=[],
+ skipped_steps=["compute_chart"],
+ vedastro_official={"runtime_truth": {"status": "service_endpoint_not_configured", "fallback_active": False}},
+ )
+ assert blocked["vedastro_cloud_state"] == "official_blocked"
+
+
+def test_runtime_evidence_log_exposes_blind_packet_case_and_quality_gate_contracts() -> None:
+ orchestrator = UnifiedConsultationOrchestrator()
+ log = orchestrator.runtime_evidence_log(
+ surface="api_web",
+ entry_mode="direct_chart",
+ route_packet={"question_type": "finance", "primary_theme": "wealth"},
+ executed_steps=["compute_chart", "run_thematic_report"],
+ skipped_steps=["run_historical_event_backtest"],
+ vedastro_official={"runtime_truth": {"status": "partial", "fallback_active": True}},
+ blind=True,
+ )
+
+ assert log["blind_technical_mode"]["enabled"] is True
+ assert "conversation_feedback" in log["blind_technical_mode"]["disallowed_sources"]
+ assert log["evidence_packet_contract"]["required_sections"][:5] == ["D1", "D9", "D10", "D2", "D4"]
+ assert "external_oracle_status" in log["evidence_packet_contract"]["required_sections"]
+ assert log["real_case_calibration"]["status"] == "required_not_satisfied"
+ assert log["quality_gate"]["technique_audit_table_required"] is True
+ assert [row["technique"] for row in log["quality_gate"]["technique_audit_table"]] == [
+ "VedAstro Cloud State",
+ "External Engine Cross-Validation",
+ "Evidence Packet",
+ "Blind Technical Mode",
+ "MEVG / Global Web Evidence",
+ "Real Case Calibration",
+ "Functional Benefic/Malefic",
+ ]
+ engines = log["external_engine_cross_validation"]["engines"]
+ assert engines["VedAstro"]["status"] == "local_fallback"
+ assert engines["PyJHora/JHora"]["status"] == "reference_available_not_runtime_invoked"
+ assert engines["PyJHora/JHora"]["adapter_command"] == "python3 benchmarks/jyotish/scripts/run_pyjhora_compare.py"
+ assert engines["PyJHora/JHora"]["adapter_status"] in {
+ "available",
+ "blocked_missing_python_module:jhora",
+ }
+ assert engines["jyotishganit"]["status"] == "reference_available_not_runtime_invoked"
+ assert engines["jyotishganit"]["adapter_path"] == "references/open_source_sources/jyotishganit"
+ assert engines["jyotishganit"]["adapter_status"] == "available"
+ assert engines["jyotishganit"]["license"] == "MIT"
+ assert log["external_engine_cross_validation"]["status"] == "partial"
+ assert log["quality_gate"]["blocked_items"]
+
+ packet = orchestrator.machine_evidence_packet(
+ chart={"chart": {"planets": {"Sun": {}}, "ascendant": {"sign": "Leo"}}},
+ route_packet={"question_type": "career", "primary_theme": "career"},
+ )
+ log_with_functional = orchestrator.runtime_evidence_log(
+ surface="api_web",
+ entry_mode="direct_chart",
+ route_packet={"question_type": "career", "primary_theme": "career"},
+ executed_steps=["compute_chart"],
+ skipped_steps=[],
+ machine_evidence_packet=packet,
+ )
+ functional_row = log_with_functional["quality_gate"]["technique_audit_table"][-1]
+ assert functional_row["technique"] == "Functional Benefic/Malefic"
+ assert functional_row["status"] == "used"
+ assert "Mars" in functional_row["yogakarakas"]
+
+
+def test_machine_evidence_packet_materializes_required_section_statuses() -> None:
+ orchestrator = UnifiedConsultationOrchestrator()
+ packet = orchestrator.machine_evidence_packet(
+ chart={
+ "chart": {
+ "planets": {"Sun": {"lon": 12.3}},
+ "ascendant": {"lon": 91.2, "sign": "Leo"},
+ "houses": {"1": {"lon": 91.2}},
+ },
+ "modules": {
+ "varga_full": {"D9_Navamsa": {}, "D10_Dasamsa": {"summary": "present"}},
+ "dasha": {"current": "Saturn"},
+ "shadbala": {"Sun": 1.0},
+ },
+ "special_lagnas": {"UL": {"sign": "Capricorn"}, "A10_Karma_Pada": {"sign": "Aries"}},
+ },
+ route_packet={"question_type": "career", "primary_theme": "career"},
+ vedastro_official={
+ "runtime_truth": {
+ "status": "ok",
+ "official_execution_layers": {"chart_core": "ok"},
+ "fallback_active": False,
+ },
+ "raw_response": {"source": "official"},
+ },
+ )
+
+ assert packet["status"] == "partial"
+ assert packet["sections"]["D1"]["status"] == "used"
+ assert packet["sections"]["D10"]["status"] == "used"
+ assert packet["sections"]["functional_benefic_malefic"]["status"] == "used"
+ assert packet["functional_benefic_malefic"]["ascendant"] == "Leo"
+ assert "Mars" in packet["functional_benefic_malefic"]["yogakarakas"]
+ assert packet["sections"]["external_oracle_status"]["status"] == "official_verified"
+ assert packet["sections"]["vedastro_official_raw_response"]["status"] == "used"
+ assert "D2" in packet["missing_sections"]
+
+
+def test_machine_evidence_packet_requires_vedastro_raw_response_for_official_closure() -> None:
+ packet = UnifiedConsultationOrchestrator().machine_evidence_packet(
+ chart={"chart": {"planets": {"Sun": {}}, "ascendant": {"sign": "Leo"}}},
+ route_packet={"question_type": "career"},
+ vedastro_official={
+ "runtime_truth": {
+ "status": "ok",
+ "official_execution_layers": {"chart_core": "ok"},
+ "fallback_active": False,
+ }
+ },
+ )
+
+ assert packet["sections"]["external_oracle_status"]["status"] == "official_verified"
+ assert packet["sections"]["vedastro_official_raw_response"]["status"] == "missing"
+ assert "vedastro_official_raw_response" in packet["missing_sections"]
+
+
+def test_real_case_calibration_catalog_exposes_local_sources_without_claiming_match() -> None:
+ orchestrator = UnifiedConsultationOrchestrator()
+ packet = orchestrator.machine_evidence_packet(
+ chart={
+ "chart": {"planets": {"Venus": {"lon": 1}}, "ascendant": {"lon": 2}},
+ "modules": {"varga_full": {"D9_Navamsa": {"summary": "present"}}, "dasha": {"current": "Venus"}},
+ "special_lagnas": {"UL": {"sign": "Cancer"}},
+ },
+ route_packet={"question_type": "relationship", "primary_theme": "marriage"},
+ )
+ catalog = orchestrator.real_case_calibration_catalog(
+ route_packet={"question_type": "relationship", "primary_theme": "marriage"},
+ machine_evidence_packet=packet,
+ )
+
+ assert catalog["status"] == "partial_scored"
+ assert catalog["batch_id"] == "real_case_studies_batch1"
+ assert "relationship" in catalog["case_index_by_domain"]
+ assert catalog["reference_grade"] == "partial_reference"
+ assert catalog["scored_candidates"][0]["event_trigger_match"]["status"] == "partial_match_official_timing_blocked"
+ assert catalog["scored_candidates"][0]["event_trigger_match"]["checks"]["dasha_boundaries"] == "used"
+ assert catalog["scored_candidates"][0]["event_trigger_match"]["checks"]["recorded_trigger_keywords"]
+ assert catalog["scored_candidates"][0]["outcome_validation"]["status"] == "local_outcome_recorded_trigger_not_replayed"
+ assert "D9" in catalog["scored_candidates"][0]["similarities"]["evidence_section_overlap"]
diff --git a/tests/test_vedastro_external_technique_evidence.py b/tests/test_vedastro_external_technique_evidence.py
index 92e91aa3..7e98d0d3 100644
--- a/tests/test_vedastro_external_technique_evidence.py
+++ b/tests/test_vedastro_external_technique_evidence.py
@@ -442,6 +442,15 @@ def test_strict_workflow_uses_shared_consultation_executor(monkeypatch) -> None:
"rectification": {"success": True, "endpoint": "rectification_gate"},
"thematic_report": {"success": True, "endpoint": "thematic_report"},
"vedastro_official": {"available": True},
+ "runtime_truth": {
+ "catalog_boundary": "catalog_recognized_not_full_runtime_execution",
+ "official_execution_layers": {"chart_core": "ok", "event_radar": "partial"},
+ },
+ "interpretation_source_runtime_coverage": {
+ "source_pack_status": "used",
+ "proven_runtime_markers": ["dasha_timing_layer_used"],
+ "runtime_visibility_status": "partial",
+ },
}
seen = {}
@@ -468,5 +477,8 @@ def test_strict_workflow_uses_shared_consultation_executor(monkeypatch) -> None:
assert seen["entry_mode"] == "direct_chart"
assert seen["question"] == "我的财务今年如何?"
+ assert result["runtime_truth"]["official_execution_layers"]["chart_core"] == "ok"
+ assert result["interpretation_source_runtime_coverage"]["source_pack_status"] == "used"
+ assert result["interpretation_source_runtime_coverage"]["runtime_visibility_status"] == "partial"
assert result["runtime_planner"]["surface"] == "skill_mcp"
assert result["routing"]["question_type"] == "finance"
diff --git a/tests/test_vedastro_official_full_snapshot.py b/tests/test_vedastro_official_full_snapshot.py
index 59ff701c..4030dc87 100644
--- a/tests/test_vedastro_official_full_snapshot.py
+++ b/tests/test_vedastro_official_full_snapshot.py
@@ -7,10 +7,18 @@ import sys
import types
from pathlib import Path
+import pytest
+
ROOT = Path(__file__).resolve().parents[1]
+@pytest.fixture(autouse=True)
+def _disable_official_full_snapshot_semantic_cache(monkeypatch):
+ monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0")
+
+
+
def _run_adapter(*args: str, env: dict[str, str] | None = None) -> dict:
completed = subprocess.run(
[sys.executable, "scripts/vedastro_service_adapter.py", *args],
@@ -187,6 +195,7 @@ def test_official_full_snapshot_marks_semantic_rate_limit_payloads(monkeypatch)
monkeypatch.setenv("VEDASTRO_API_ENDPOINT", "https://example.invalid/api")
monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "1")
+ monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0")
monkeypatch.setattr(adapter, "_post_official_snapshot_section", fake_post)
result = adapter.run_official_full_snapshot_for_case(
@@ -207,6 +216,10 @@ def test_official_full_snapshot_marks_semantic_rate_limit_payloads(monkeypatch)
assert ("chart_core", "Mars") in calls
assert result["section_statuses"]["chart_core"] == "rate_limited"
assert result["section_statuses"]["chart_core_fanout"]["Mars"] == "rate_limited"
+ assert result["raw_response"]["source"] == "vedastro_official_full_snapshot"
+ assert result["raw_response"]["sections"]["chart_core"]["Mars"]["Status"] == "Fail"
+ assert result["raw_response"]["section_statuses"]["chart_core"] == "rate_limited"
+ assert result["raw_response"]["request_manifest"]["source_role"] == "primary_official_raw_evidence"
assert result["source_metadata"]["rate_limited_sections"] == ["chart_core"]
assert result["source_metadata"]["production_hint"] == "configure_vedastro_api_key_or_self_host_official_api"
@@ -547,6 +560,7 @@ def test_official_full_snapshot_can_use_python_bridge_bundle_without_rest_endpoi
monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False)
monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False)
+ monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0")
monkeypatch.setattr(
adapter,
"_try_official_capability_runner_snapshot_bundle",
@@ -652,6 +666,8 @@ def test_official_full_snapshot_can_use_python_bridge_bundle_without_rest_endpoi
def test_official_full_snapshot_prefers_official_capability_runner_bundle(monkeypatch) -> None:
from scripts import vedastro_service_adapter as adapter
+ monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0")
+
monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False)
monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False)
@@ -743,6 +759,8 @@ def test_official_full_snapshot_prefers_official_capability_runner_bundle(monkey
def test_official_full_snapshot_attaches_full_capability_catalog_summary(monkeypatch) -> None:
from scripts import vedastro_service_adapter as adapter
+ monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0")
+
monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False)
monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False)
@@ -859,6 +877,8 @@ def test_official_full_snapshot_attaches_full_capability_catalog_summary(monkeyp
def test_official_full_snapshot_marks_ok_when_fast_primary_sections_are_filled(monkeypatch) -> None:
from scripts import vedastro_service_adapter as adapter
+ monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0")
+
monkeypatch.delenv("VEDASTRO_API_ENDPOINT", raising=False)
monkeypatch.delenv("VEDASTRO_ENABLE_NETWORK", raising=False)
monkeypatch.setattr(
@@ -936,6 +956,8 @@ def test_official_full_snapshot_marks_ok_when_fast_primary_sections_are_filled(m
def test_official_full_snapshot_skips_rest_sections_already_filled_by_python_bundle(monkeypatch) -> None:
from scripts import vedastro_service_adapter as adapter
+ monkeypatch.setenv("VEDASTRO_OFFICIAL_FULL_SNAPSHOT_CACHE_TTL_SECONDS", "0")
+
rest_calls: list[tuple[str, object]] = []
monkeypatch.setattr(
adapter,
diff --git a/tests/test_vedastro_official_mcp_bridge.py b/tests/test_vedastro_official_mcp_bridge.py
index afe96c08..30ecc06c 100644
--- a/tests/test_vedastro_official_mcp_bridge.py
+++ b/tests/test_vedastro_official_mcp_bridge.py
@@ -27,6 +27,7 @@ def test_vedastro_official_mcp_bridge_schema_is_declared() -> None:
assert report["role"] == "official_public_mcp_thin_bridge"
assert "initialize" in report["operations"]
assert "tools_list" in report["operations"]
+ assert "call_tool" in report["operations"]
def test_vedastro_official_mcp_bridge_can_list_tools_against_mock_server() -> None:
@@ -99,11 +100,79 @@ def test_vedastro_official_mcp_bridge_can_list_tools_against_mock_server() -> No
server.shutdown()
thread.join(timeout=5)
+
+def test_vedastro_official_mcp_bridge_can_call_tool_against_mock_server() -> None:
+ seen_arguments: dict[str, object] = {}
+
+ class Handler(BaseHTTPRequestHandler):
+ def do_POST(self) -> None: # noqa: N802
+ length = int(self.headers.get("Content-Length", "0"))
+ payload = json.loads(self.rfile.read(length).decode("utf-8"))
+ if payload["method"] == "initialize":
+ response = {
+ "jsonrpc": "2.0",
+ "id": payload["id"],
+ "result": {"protocolVersion": "2025-06-18", "capabilities": {}},
+ }
+ body = json.dumps(response).encode("utf-8")
+ self.send_response(200)
+ self.send_header("Content-Type", "application/json")
+ self.send_header("Mcp-Session-Id", "session-demo")
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+ return
+
+ assert payload["method"] == "tools/call"
+ assert self.headers.get("Mcp-Session-Id") == "session-demo"
+ assert payload["params"]["name"] == "get_dasa_at_time"
+ seen_arguments.update(payload["params"]["arguments"])
+ response = {
+ "jsonrpc": "2.0",
+ "id": payload["id"],
+ "result": {"content": [{"type": "text", "text": "Saturn/Venus"}]},
+ }
+ body = json.dumps(response).encode("utf-8")
+ self.send_response(200)
+ self.send_header("Content-Type", "application/json")
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+
+ def log_message(self, format: str, *args) -> None: # noqa: A003
+ return
+
+ server = HTTPServer(("127.0.0.1", 0), Handler)
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
+ thread.start()
+ try:
+ endpoint = f"http://127.0.0.1:{server.server_port}/api/mcp/public"
+ completed = subprocess.run(
+ [
+ sys.executable,
+ "scripts/vedastro_official_mcp_bridge.py",
+ "--endpoint",
+ endpoint,
+ "--operation",
+ "call_tool",
+ "--tool",
+ "get_dasa_at_time",
+ "--arguments-json",
+ '{"birth_date":"17/04/REDACTED_YEAR","check_date":"04/07/2026"}',
+ ],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=120,
+ check=False,
+ )
+ finally:
+ server.shutdown()
+ thread.join(timeout=5)
+
assert completed.returncode == 0, completed.stderr or completed.stdout
report = json.loads(completed.stdout)
- assert report["available"] is True
- assert report["status"] == "ok"
- assert report["operation"] == "tools_list"
- assert report["session_id"] == "session-demo"
- assert report["tool_count"] == 2
- assert report["tool_names"] == ["get_current_transits", "get_dasa_at_time"]
+ assert report["operation"] == "call_tool"
+ assert report["tool_name"] == "get_dasa_at_time"
+ assert report["result"]["content"][0]["text"] == "Saturn/Venus"
+ assert seen_arguments["birth_date"] == "17/04/REDACTED_YEAR"
diff --git a/tests/test_vedastro_runtime_mode_diagnostics.py b/tests/test_vedastro_runtime_mode_diagnostics.py
index 85bb2d14..514a24ab 100644
--- a/tests/test_vedastro_runtime_mode_diagnostics.py
+++ b/tests/test_vedastro_runtime_mode_diagnostics.py
@@ -35,7 +35,12 @@ def test_vedastro_diagnostics_reports_fast_fallback_mode_without_endpoint() -> N
assert report["mode"] == "fast_local_fallback"
assert report["official_ready"] is False
assert "VEDASTRO_API_ENDPOINT" in report["missing"]
+ assert "missing_endpoint" in report["readiness_blockers"]
+ assert "network_disabled" in report["readiness_blockers"]
+ assert "timeout_too_low" in report["readiness_blockers"]
assert report["expected_fallback_status"] == "official_snapshot_budget_exhausted_or_endpoint_blocked"
+ assert "VEDASTRO_API_ENDPOINT" in report["official_closure_plan"]["required_env"]
+ assert report["official_closure_plan"]["raw_response_acceptance"] == "vedastro_official.raw_response must be present before claiming official cloud closure."
def test_vedastro_diagnostics_reports_official_mode_when_configured() -> None:
@@ -51,4 +56,21 @@ def test_vedastro_diagnostics_reports_official_mode_when_configured() -> None:
assert report["timeout_seconds"] == 20.0
assert report["network_enabled"] is True
assert report["has_api_key"] is True
+ assert report["readiness_blockers"] == []
+ assert report["official_closure_plan"]["required_env"]["VEDASTRO_TIMEOUT_SECONDS"] == "20"
+
+def test_vedastro_diagnostics_labels_premium_key_missing_but_free_tier_possible() -> None:
+ report = _run_diag({
+ "VEDASTRO_API_ENDPOINT": "https://api.vedastro.org/api",
+ "VEDASTRO_ENABLE_NETWORK": "1",
+ "VEDASTRO_TIMEOUT_SECONDS": "20",
+ "VEDASTRO_API_KEY": "",
+ "VEDASTRO_FREE_TIER_QUEUE": "1",
+ })
+ assert report["mode"] == "official_extended"
+ assert report["official_ready"] is True
+ assert "missing_api_key" not in report["readiness_blockers"]
+ assert "premium_key_missing" in report["readiness_blockers"]
+ assert report["free_tier_possible_with_cache_queue"] is True
+ assert report["official_closure_plan"]["premium_key_policy"] == "API key recommended for stable official full snapshot; free tier may still block or throttle."
diff --git a/tests/test_vedastro_user_entrypoint.py b/tests/test_vedastro_user_entrypoint.py
index 5555e566..5aaa8a2e 100644
--- a/tests/test_vedastro_user_entrypoint.py
+++ b/tests/test_vedastro_user_entrypoint.py
@@ -120,6 +120,11 @@ def test_user_entrypoint_runs_catalog_and_strict_workflow_contract() -> None:
assert report["cache_and_queue"]["official_full_snapshot_cache_ttl_seconds"] == 600
assert report["cache_and_queue"]["range_scan_cache_ttl_seconds"] == 600
assert report["cache_and_queue"]["free_tier_queue_enabled"] is True
+ assert report["runtime_mode"]["free_tier_possible_with_cache_queue"] is True
+ assert report["runtime_mode"]["readiness_blockers"] == ["premium_key_missing"]
+ assert report["cache_and_queue"]["free_tier_strategy"]["using_free_tier"] is True
+ assert report["cache_and_queue"]["free_tier_strategy"]["queue_enabled"] is True
+ assert report["cache_and_queue"]["free_tier_strategy"]["guard_status"] == "within_free_tier_strategy"
assert report["strict_workflow"]["triggered"] is True
assert report["strict_workflow"]["primary_route"] == "career"
assert "career" in report["strict_workflow"]["routes_available"]
diff --git a/tests/test_vibe_coding_setup.py b/tests/test_vibe_coding_setup.py
new file mode 100644
index 00000000..0321a639
--- /dev/null
+++ b/tests/test_vibe_coding_setup.py
@@ -0,0 +1,53 @@
+from __future__ import annotations
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def test_print_cline_mcp_config_points_at_current_repo_mcp_server() -> None:
+ completed = subprocess.run(
+ [sys.executable, "scripts/print_cline_mcp_config.py"],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=30,
+ check=False,
+ )
+
+ assert completed.returncode == 0, completed.stderr or completed.stdout
+ config = json.loads(completed.stdout)
+ server = config["mcpServers"]["jyotish"]
+ assert server["command"] == sys.executable
+ assert server["args"] == [str(ROOT / "mcp_server.py")]
+ assert server["cwd"] == str(ROOT)
+ assert server["env"]["PYTHONPATH"].endswith("/scripts")
+
+
+def test_cline_project_config_is_ignored_and_installable(tmp_path: Path) -> None:
+ completed = subprocess.run(
+ [
+ sys.executable,
+ str(ROOT / "scripts" / "print_cline_mcp_config.py"),
+ "--repo-root",
+ str(tmp_path),
+ "--install-project",
+ ],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ timeout=30,
+ check=False,
+ )
+
+ assert completed.returncode == 0, completed.stderr or completed.stdout
+ written = tmp_path / ".cline" / "mcp.json"
+ assert written.exists()
+ config = json.loads(written.read_text(encoding="utf-8"))
+ assert config["mcpServers"]["jyotish"]["args"] == [str(tmp_path / "mcp_server.py")]
+ assert ".cline/" in (ROOT / ".gitignore").read_text(encoding="utf-8")
+