feat(upstream): snapshot a6f47abd, REST VedAstro path, and 116-technique truth layer

Advance the one-way import to git commit a6f47abd with consultation keypath golden, switch official VedAstro comparison to the REST Calculate bridge, and receive the 25 new registry entries behind research_only_blocked.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-03 17:35:26 +08:00
parent 01a4536333
commit 45d132588f
1184 changed files with 464058 additions and 70 deletions
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -688,7 +688,8 @@ def t99():
@test("Package version is consistent")
def t100():
from jyotish_vedic import __version__
assert __version__ == '6.9.14'
# 原值=6.9.14;新值=6.9.15。原因=任务 1 新增 skill 版本目录。
assert __version__ == '6.9.15'
# ── v6.9.11 Precision gate tests ──
@test("Transit uses Swiss Ephemeris")
+3 -1
View File
@@ -1622,7 +1622,9 @@ def test_capability_audit_scans_registry_and_local_sources() -> None:
assert ux['summary']['excellent'] == summary['productized']
assert ux['summary']['usable'] == 0
assert ux['summary']['thin'] == summary['api_backed'] + summary['engine_or_full_reading']
assert ux['summary']['not_user_ready'] == 0
# 原值=not_user_ready == 0。新值=等于 registry_only。
# 原因=任务 1 新增 25 项中网页未执行的条目保持 registry_only,覆盖层 research_only_blocked。
assert ux['summary']['not_user_ready'] == summary['registry_only']
assert all(0 <= row['ux_score'] <= 6 for row in ux['rows'])
assert all('ux_next_action' in row for row in ux['next_queue'])
ux_by_id = {row['id']: row for row in ux['rows']}
+9 -2
View File
@@ -62,12 +62,19 @@ def test_evidence_pool_summary_routes_few_primary_items_and_many_support_items()
summary = build_capability_evidence_pool_summary()
assert summary["scope"] == "backend_capability_evidence_pool"
assert summary["total_entries"] == 91
# 原值=91;新值=116。原因=任务 1 接收上游 25 项注册表内容。
assert summary["total_entries"] == 116
assert summary["ordinary_user_policy"].startswith("Users see topic-level")
assert summary["evidence_role_counts"]["primary"] >= 8
assert summary["evidence_role_counts"]["secondary"] > summary["evidence_role_counts"]["primary"]
assert summary["evidence_role_counts"]["audit_only"] >= 3
assert summary["comparison_only_entries"] == ["rangacharya_jaimini_variant"]
# 原值=仅 rangacharya_jaimini_variant;新值=加上 3 个上游 ziwei comparison-only。
assert summary["comparison_only_entries"] == [
"rangacharya_jaimini_variant",
"ziwei_bridge_script_pack",
"ziwei_doushu_bridge",
"ziwei_runtime_bridge_scripts",
]
assert summary["conclusion_policy"]["comparison_only_entries_cannot_affect_astrological_conclusions"] is True
assert summary["prediction_verification_counts"]["not_claimed"] > 0
assert summary["conclusion_policy"]["primary_chain_required"] is True
+2 -1
View File
@@ -409,7 +409,8 @@ def test_full_reading_reports_ayanamsa_metadata_and_ai_prompt_pack() -> None:
assert "domain_statuses" in vedastro_rows[0]["note"]
capability_pool = prompt_pack["evidence_snapshot"]["capability_evidence_pool"]
assert capability_pool["scope"] == "backend_capability_evidence_pool"
assert capability_pool["total_entries"] == 91
# 原值=91;新值=116。原因=任务 1 接收上游 25 项注册表。
assert capability_pool["total_entries"] == 116
assert capability_pool["conclusion_policy"]["all_89_entries_must_not_be_flattened_into_conclusions"] is True
assert "后台备选证据池" in prompt_pack["prompt_zh"]
+10 -4
View File
@@ -38,12 +38,18 @@ def test_truth_overlay_cannot_bypass_server_answer_contract() -> None:
truth = result["technique_truth"]
policy = result["answer_policy"]
overlay = load_commercial_skill_truth()
assert truth["status"] == "restricted"
assert set(truth["blocked_techniques"]) == {
"sahams",
"sphuta_trisphuta_family",
"conception_chart",
# 原值=仅 sahams / sphuta_trisphuta_family / conception_chart。
# 新值=再加上 25 项网页路径未执行的上游新技法(research_only_blocked)。
# 原因=任务 1 按决策记录把未执行项写入覆盖层,不得进入确定性结论。
overlay_blocked = {
item["technique_id"]
for item in overlay["techniques"]
if item["status"] in {"blocked", "research_only_blocked"}
}
assert {"sahams", "sphuta_trisphuta_family", "conception_chart"} <= overlay_blocked
assert set(truth["blocked_techniques"]) == overlay_blocked
assert set(policy["deterministic_claims_forbidden_for"]) == set(TECHNIQUE_TRUTH_IDS)
assert policy["can_answer_precise_timing"] is True
evidence = result["commercial_evidence_status"]
+310
View File
@@ -0,0 +1,310 @@
"""Consultation and chart API contract golden: key paths and types only.
Frozen fixtures record the baseline response shape. After later engine merges,
a fresh capture must be a key-path superset with matching types. Values are
never stored. Smoke birth data is fictional (1990-01-01 Beijing).
"""
from __future__ import annotations
import json
import os
import socket
import sys
import threading
import urllib.error
import urllib.request
from http.server import ThreadingHTTPServer
from pathlib import Path
from typing import Any
import pytest
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
GOLDEN = ROOT / "tests" / "golden" / "consultation_contract_keypaths_v1.json"
CAPTURE_ENV = "JYOTISH_CAPTURE_CONSULTATION_GOLDEN"
# Fictional smoke chart used by existing golden_cases.json. Not a real person.
SMOKE_BIRTH = {
"year": 1990,
"month": 1,
"day": 1,
"hour": 12,
"minute": 0,
"lat": 39.9,
"lon": 116.4,
"tz": 8,
"city": "Beijing",
"note": "fictional_smoke",
}
THEMES = ("career", "marriage", "wealth", "timing", "health")
FORBIDDEN_PRIVACY_MARKERS = ("1993-04-17", "17/04/1993", "14:49")
_ROUTE_LAYERS = {
"career": ["D1", "D10", "10th house/lord", "A10", "AmK", "Vimshottari", "Narayana", "Transit"],
"marriage": ["D1", "D9", "7th house/lord", "Venus/Jupiter", "DK", "UL", "A7", "Vimshottari", "Narayana", "Transit"],
"wealth": ["D1", "D2", "D11", "2nd/11th/9th/5th houses", "Wealth Yogas", "Ashtakavarga", "Dasha"],
"timing": ["Vimshottari", "Narayana", "Transit", "Varga", "negative holdout gate"],
"health": ["D1", "D6", "D8", "6th/8th houses", "Dasha", "non-medical boundary"],
}
_ROUTE_CLAIMS = {
"career": "career_direction_and_broad_timing_only",
"marriage": "relationship_pattern_and_broad_window_only",
"wealth": "wealth_structure_not_financial_advice",
"timing": "candidate_day_month_window_only_until_holdout_passes",
"health": "wellbeing_pressure_patterns_not_medical_diagnosis",
}
def json_type(value: Any) -> str:
if value is None:
return "null"
if isinstance(value, bool):
return "boolean"
if isinstance(value, (int, float)):
return "number"
if isinstance(value, str):
return "string"
if isinstance(value, list):
return "array"
if isinstance(value, dict):
return "object"
return type(value).__name__
def flatten_keypaths(value: Any, prefix: str = "", *, depth: int = 0, max_depth: int = 2) -> dict[str, str]:
"""Record dotted key paths and JSON types, two object levels deep.
Arrays contribute their type and the first element's type plus one child
level. This keeps the golden small while locking frontend-consumed keys.
"""
out: dict[str, str] = {}
if prefix:
out[prefix] = json_type(value)
if depth >= max_depth:
return out
if isinstance(value, dict):
for key, child in sorted(value.items(), key=lambda item: str(item[0])):
path = f"{prefix}.{key}" if prefix else str(key)
out.update(flatten_keypaths(child, path, depth=depth + 1, max_depth=max_depth))
elif isinstance(value, list) and value:
out.update(flatten_keypaths(value[0], f"{prefix}[0]", depth=depth + 1, max_depth=max_depth))
return out
def _workflow_body(theme: str, *, skip_full_reading: bool) -> dict[str, Any]:
timing_horizon = "next_12_months" if theme in {"timing", "annual"} else None
return {
**{key: SMOKE_BIRTH[key] for key in ("year", "month", "day", "hour", "minute", "lat", "lon", "tz", "city")},
"question": f"请用虚构资料计算 {theme} 主题",
"question_text": f"请用虚构资料计算 {theme} 主题",
"theme": [theme],
"entry_mode": "direct_chart",
"plan_version": "consultation-plan-v2",
"strict_workflow_route": theme,
"required_layers": list(_ROUTE_LAYERS[theme]),
"claim_boundary": _ROUTE_CLAIMS[theme],
"plan_depth": "standard",
"requested_domains": [theme],
"timing_horizon": timing_horizon,
"precision_boundary": "server_evidence_required",
"required_evidence_categories": ["natal_foundation", "domain", "timing", "validation"],
"defer_optional_external_evidence": True,
"skip_full_reading_for_thematic": skip_full_reading,
}
class _ApiThread(threading.Thread):
def __init__(self, server: ThreadingHTTPServer) -> None:
super().__init__(daemon=True)
self.server = server
def run(self) -> None:
self.server.serve_forever()
def _start_local_api() -> tuple[ThreadingHTTPServer, str]:
from scripts.jyotish_api_server import DEFAULT_ALLOWED_HOSTS, DEFAULT_ALLOWED_ORIGINS, JyotishAPIHandler
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 0))
port = sock.getsockname()[1]
sock.close()
server = ThreadingHTTPServer(("127.0.0.1", port), JyotishAPIHandler)
server.daemon_threads = True
origin = f"http://127.0.0.1:{port}"
server.allowed_origins = set(DEFAULT_ALLOWED_ORIGINS) | {origin}
server.allowed_hosts = set(DEFAULT_ALLOWED_HOSTS)
thread = _ApiThread(server)
thread.start()
return server, origin
def _post(base: str, path: str, body: dict[str, Any]) -> dict[str, Any]:
payload = json.dumps(body).encode("utf-8")
request = urllib.request.Request(
f"{base}{path}",
data=payload,
method="POST",
headers={
"Content-Type": "application/json",
"Origin": base,
"Host": base.split("://", 1)[1],
},
)
try:
with urllib.request.urlopen(request, timeout=180) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as error:
detail = error.read().decode("utf-8", errors="replace")
raise AssertionError(f"{path} HTTP {error.code}: {detail[:500]}") from error
def capture_contract_shapes(base: str) -> dict[str, dict[str, str]]:
cases: dict[str, dict[str, str]] = {}
for theme in THEMES:
for skip in (True, False):
name = f"consultation_workflow.{theme}.skip_full_reading_{str(skip).lower()}"
body = _workflow_body(theme, skip_full_reading=skip)
cases[name] = flatten_keypaths(_post(base, "/api/consultation_workflow", body))
chart = _post(base, "/api/chart", {**SMOKE_BIRTH, "ayanamsa": "raman", "node_mode": "mean"})
cases["chart"] = flatten_keypaths(chart)
planets = chart.get("planets") if isinstance(chart.get("planets"), dict) else {}
ascendant = chart.get("ascendant") if isinstance(chart.get("ascendant"), dict) else {}
varga = _post(
base,
"/api/varga_full",
{
**{key: SMOKE_BIRTH[key] for key in ("year", "month", "day", "hour", "minute", "lat", "lon", "tz")},
"planets": planets,
"ascendant": ascendant,
"divisions": ["D9", "D10"],
},
)
cases["varga_full"] = flatten_keypaths(varga)
daily = _post(
base,
"/api/daily_guidance",
{
**{key: SMOKE_BIRTH[key] for key in ("year", "month", "day", "hour", "minute", "lat", "lon", "tz")},
"date": "2026-09-03",
"today": "2026-09-03",
"reference_date": "2026-09-03",
"transit_date": "2026-09-03",
"ayanamsa": "raman",
"node_mode": "mean",
},
)
cases["daily_guidance"] = flatten_keypaths(daily)
moon = planets.get("Moon") if isinstance(planets.get("Moon"), dict) else {}
moon_lon = moon.get("lon", 0)
synastry = _post(base, "/api/synastry", {"male_moon": moon_lon, "female_moon": (float(moon_lon) + 30) % 360})
cases["synastry"] = flatten_keypaths(synastry)
return cases
def write_golden(cases: dict[str, dict[str, str]]) -> None:
GOLDEN.parent.mkdir(parents=True, exist_ok=True)
document = {
"schema_version": 1,
"kind": "keypath_types_only",
"max_path_segments": 2,
"baseline_commit": "58cae371bbcb43ac9d4dfe8a66c78be46c88a1ee",
"smoke_birth": SMOKE_BIRTH,
"cases": cases,
}
GOLDEN.write_text(json.dumps(document, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
def load_golden() -> dict[str, Any]:
return json.loads(GOLDEN.read_text(encoding="utf-8"))
def assert_superset(frozen: dict[str, str], live: dict[str, str], *, case: str) -> None:
missing = sorted(path for path in frozen if path not in live)
type_mismatch = sorted(
f"{path}: frozen={frozen[path]} live={live[path]}"
for path in frozen
if path in live and frozen[path] != live[path]
)
assert not missing, f"{case} lost key paths: {missing[:20]}"
assert not type_mismatch, f"{case} type changes: {type_mismatch[:20]}"
def test_golden_fixture_is_fictional_and_value_free() -> None:
assert GOLDEN.exists(), "run capture first (JYOTISH_CAPTURE_CONSULTATION_GOLDEN=1)"
raw = GOLDEN.read_text(encoding="utf-8")
for marker in FORBIDDEN_PRIVACY_MARKERS:
assert marker not in raw
document = load_golden()
assert document["kind"] == "keypath_types_only"
assert document["smoke_birth"]["note"] == "fictional_smoke"
assert document["smoke_birth"]["year"] == 1990
for case, paths in document["cases"].items():
assert paths, case
assert all(kind in {"null", "boolean", "number", "string", "array", "object"} for kind in paths.values()), case
@pytest.mark.slow
def test_live_http_capture_is_keypath_superset() -> None:
if os.environ.get("JYOTISH_LIVE_CONTRACT_GOLDEN", "").strip().lower() not in {"1", "true", "yes"}:
pytest.skip("set JYOTISH_LIVE_CONTRACT_GOLDEN=1 to recapture over HTTP (~1 min after API import)")
os.environ.setdefault("JYOTISH_SKIP_LOCAL_ENV", "1")
os.environ["VEDASTRO_ENABLE_NETWORK"] = "0"
os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
frozen = load_golden()["cases"]
server, base = _start_local_api()
try:
live = capture_contract_shapes(base)
finally:
server.shutdown()
server.server_close()
if os.environ.get(CAPTURE_ENV, "").strip() in {"1", "true", "yes"}:
write_golden(live)
frozen = live
assert set(frozen) <= set(live)
for case, paths in frozen.items():
assert_superset(paths, live[case], case=case)
def test_golden_covers_required_frontend_surfaces() -> None:
cases = load_golden()["cases"]
required = {
"consultation_workflow.career.skip_full_reading_true",
"consultation_workflow.career.skip_full_reading_false",
"consultation_workflow.marriage.skip_full_reading_true",
"consultation_workflow.marriage.skip_full_reading_false",
"consultation_workflow.wealth.skip_full_reading_true",
"consultation_workflow.wealth.skip_full_reading_false",
"consultation_workflow.timing.skip_full_reading_true",
"consultation_workflow.timing.skip_full_reading_false",
"consultation_workflow.health.skip_full_reading_true",
"consultation_workflow.health.skip_full_reading_false",
"chart",
"varga_full",
"daily_guidance",
"synastry",
}
assert required <= set(cases)
for name in required:
assert "success" in cases[name] or name == "synastry"
if __name__ == "__main__":
os.environ.setdefault("JYOTISH_SKIP_LOCAL_ENV", "1")
os.environ["VEDASTRO_ENABLE_NETWORK"] = "0"
server, base = _start_local_api()
try:
cases = capture_contract_shapes(base)
finally:
server.shutdown()
server.server_close()
write_golden(cases)
print(f"wrote {GOLDEN} cases={len(cases)}")
@@ -0,0 +1,37 @@
"""Git-mode import record for upstream a6f47abd. Does not replace the 5db72537 archive pin test."""
from __future__ import annotations
import hashlib
import json
import re
from pathlib import Path
from jsonschema import Draft202012Validator, FormatChecker
ROOT = Path(__file__).resolve().parents[1]
RECORD = ROOT / "references/cross_project_contract/imports/commit-a6f47abd2c9b7c6baa911ea00c971e1ec902380c.json"
SOURCE_MANIFEST = ROOT / "references/upstream/yinduzhanxing/source-manifest.json"
SKILL_SNAPSHOT = ROOT / "references/upstream/yinduzhanxing/SKILL.md"
SCHEMA = ROOT / "references/cross_project_contract/import_manifest.schema.json"
COMMIT = "a6f47abd2c9b7c6baa911ea00c971e1ec902380c"
def test_git_import_record_matches_mirrored_skill_and_manifest() -> None:
record = json.loads(RECORD.read_text(encoding="utf-8"))
manifest = json.loads(SOURCE_MANIFEST.read_text(encoding="utf-8"))
schema = json.loads(SCHEMA.read_text(encoding="utf-8"))
Draft202012Validator(schema, format_checker=FormatChecker()).validate(record)
assert record["source_mode"] == "git"
assert record["source_commit"] == COMMIT
assert re.fullmatch(r"[0-9a-f]{40}", record["source_commit"])
assert re.fullmatch(r"[0-9a-f]{64}", record["source_tree_hash"])
assert record["source_tree_hash"] == manifest["source_tree_sha256"]
assert record["source_skill_sha256"] == manifest["source_skill_sha256"]
snapshot_hash = hashlib.sha256(SKILL_SNAPSHOT.read_bytes()).hexdigest()
assert snapshot_hash == record["source_skill_sha256"] == record["mirror_files"][0]["target_sha256_after"]
assert record["mirror_files"][0]["status"] == "applied"
assert manifest["source_mode"] == "git"
assert manifest["source_commit"] == COMMIT
assert all(row["status"] == "review_required" for row in record["semantic_merge_files"])
+19 -16
View File
@@ -44,13 +44,13 @@ EXPECTED_REJECTIONS = {
EXPECTED_UPSTREAM_IDENTITY = {
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_commit": "5db72537741fcedaa7b5498502d4a31b0f9fc147",
"source_tree_sha256": "18e3122ef73c2776a950bfec128efeb09ac0524ec1d2179390260166b87ca814",
"source_commit": "a6f47abd2c9b7c6baa911ea00c971e1ec902380c",
"source_tree_sha256": "790fd5c05ffd6c1a17c6347259143d3c174e0e425246ea2207a8405f7bf6eaed",
}
EXPECTED_SOURCE_GIT_TREE = "16935cb68a6fa1ef72661cfca4650a42c60e9b2c"
EXPECTED_ARCHIVE_SHA256 = "07d6b71af311160c544433eb9d51beb5b0a5e4bd0afabd4a7d43d3edb573518d"
EXPECTED_ARCHIVE_FILE_COUNT = 2901
EXPECTED_SOURCE_SKILL_SHA256 = "ef453dd8dd4a9da72b56010ec33bb7dab57fd72333d8785cac0986d5456e17c5"
EXPECTED_SOURCE_GIT_TREE = "650551ddcaddce0dacc76360efca425158ebb8d4"
EXPECTED_SOURCE_SKILL_SHA256 = "2cf15abe82e80fd1eba1fb232bc31b15a6998cafd586bbd281e76a7522b744be"
LEGACY_ARCHIVE_COMMIT = "5db72537741fcedaa7b5498502d4a31b0f9fc147"
CURRENT_IMPORT_RECORD = ROOT / "references/cross_project_contract/imports/commit-a6f47abd2c9b7c6baa911ea00c971e1ec902380c.json"
def _load(path: Path) -> dict:
@@ -117,18 +117,18 @@ def test_formal_varga_is_evidence_only_and_cannot_close_local_truth() -> None:
assert "required layers" in reason
def test_source_manifest_pins_valid_archive_commit_tree_and_root_skill_hash() -> None:
def test_source_manifest_pins_valid_git_commit_tree_and_root_skill_hash() -> None:
manifest = _load(SOURCE_MANIFEST)
plan = _load(PLAN)
assert manifest["source_mode"] == "archive"
assert manifest["source_mode"] == "git"
assert manifest["import_policy_version"] == 2
assert manifest["imported_at"] != manifest["source_committed_at"]
assert {key: manifest[key] for key in EXPECTED_UPSTREAM_IDENTITY} == EXPECTED_UPSTREAM_IDENTITY
assert {key: plan[key] for key in EXPECTED_UPSTREAM_IDENTITY} == EXPECTED_UPSTREAM_IDENTITY
assert manifest["source_git_tree"] == EXPECTED_SOURCE_GIT_TREE
assert manifest["archive_sha256"] == EXPECTED_ARCHIVE_SHA256
assert manifest["archive_file_count"] == EXPECTED_ARCHIVE_FILE_COUNT
assert manifest["source_committed_at"] == "2026-08-13T18:03:53Z"
assert "archive_sha256" not in manifest
assert "archive_file_count" not in manifest
assert manifest["source_committed_at"] == "2026-09-03T05:33:11Z"
assert re.fullmatch(r"[0-9a-f]{40}", manifest["source_commit"])
assert re.fullmatch(r"[0-9a-f]{64}", manifest["source_tree_sha256"])
source_skill_sha256 = manifest.get("source_skill_sha256", manifest["skill_sha256"])
@@ -143,18 +143,21 @@ def test_import_manifest_schema_accepts_archive_and_legacy_snapshot_records() ->
validator = Draft202012Validator(schema, format_checker=FormatChecker())
legacy = _load(LEGACY_SNAPSHOT)
pinned = _load(PINNED_IMPORT_RECORD)
current = _load(CURRENT_IMPORT_RECORD)
validator.validate(legacy)
validator.validate(pinned)
validator.validate(current)
invalid_git = dict(pinned, source_mode="git", source_commit="unknown")
assert list(validator.iter_errors(invalid_git))
assert "archive" in schema["properties"]["source_mode"]["enum"]
source_manifest = _load(SOURCE_MANIFEST)
plan = _load(PLAN)
assert pinned["source_repository"] == source_manifest["source_repository"] == plan["source_repository"]
assert pinned["source_repository_url"] == source_manifest["source_repository_url"] == plan["source_repository_url"] == EXPECTED_UPSTREAM_IDENTITY["source_repository_url"]
assert pinned["source_commit"] == source_manifest["source_commit"] == plan["source_commit"]
assert pinned["source_tree_hash"] == source_manifest["source_tree_sha256"] == plan["source_tree_sha256"]
assert pinned["source_skill_sha256"] == source_manifest["source_skill_sha256"] == EXPECTED_SOURCE_SKILL_SHA256
assert pinned["source_commit"] == LEGACY_ARCHIVE_COMMIT
assert current["source_repository"] == source_manifest["source_repository"] == plan["source_repository"]
assert current["source_repository_url"] == source_manifest["source_repository_url"] == plan["source_repository_url"] == EXPECTED_UPSTREAM_IDENTITY["source_repository_url"]
assert current["source_commit"] == source_manifest["source_commit"] == plan["source_commit"]
assert current["source_tree_hash"] == source_manifest["source_tree_sha256"] == plan["source_tree_sha256"]
assert current["source_skill_sha256"] == source_manifest["source_skill_sha256"] == EXPECTED_SOURCE_SKILL_SHA256
def test_raman_2026_08_20_packets_are_adapt_only_and_exclude_blocked_batches() -> None:
+1 -1
View File
@@ -20,7 +20,7 @@ def test_checklist_routes_high_value_capabilities_to_expected_lanes():
def capabilities(lane: str) -> set[str]:
return {item["capability"] for item in checklist["lanes"][lane]}
assert "MCP / API Surface" in capabilities("official_mcp")
assert "MCP / API Surface" in capabilities("rest_adapter")
assert "Shadbala" in capabilities("official_python_bridge")
assert "EventsAtRange / Life Event Graph" in capabilities("rest_adapter")
assert "D1-D60 Divisional Charts" in capabilities("local_native_preferred")
+6
View File
@@ -23,6 +23,12 @@ def test_gateway_status_defaults_to_official_first_cn_safe(monkeypatch):
assert status["backend_priority"] == ["self_host", "official", "cache", "queue", "local_fallback"]
assert status["active_backend"] == "official"
assert status["official_readiness"]["official_ready"] is True
assert status["official_transport"] == "rest"
assert status["official_calculate_endpoint"] == "https://api.vedastro.org/api/Calculate"
assert status["rate_limit_per_minute"] == 5
assert status["mcp_bridge_role"] == "protocol_probe_only"
assert status["official_calculate_health"]["status"] == "not_probed"
assert status["official_calculate_health"]["transport"] == "rest"
assert status["boundary"] == "Users never call VedAstro directly; backend gateway owns cache, queue, and fallback."
+1 -1
View File
@@ -122,7 +122,7 @@ def test_matrix_declares_fastest_path_lane_for_high_value_rows():
assert _row(matrix, "EventsAtRange / Life Event Graph")["fastest_path_lane"] == "rest_adapter"
assert _row(matrix, "Shadbala")["fastest_path_lane"] == "official_python_bridge"
assert _row(matrix, "MCP / API Surface")["fastest_path_lane"] == "official_mcp"
assert _row(matrix, "MCP / API Surface")["fastest_path_lane"] == "rest_adapter"
assert _row(matrix, "D1-D60 Divisional Charts")["fastest_path_lane"] == "local_native_preferred"
+111
View File
@@ -0,0 +1,111 @@
import argparse
import http.client
import urllib.error
from io import BytesIO
import pytest
from scripts.vedastro_rest_bridge import (
SMOKE_DEFAULT,
_date_to_vedastro_std,
build_dasa_payload,
build_horoscope_payload,
build_parser,
call,
match_not_implemented,
probe_calculate_health,
reset_rate_limiter,
)
def _args(**overrides):
values = {
"birth": SMOKE_DEFAULT["birth"],
"name": SMOKE_DEFAULT["name"],
"lat": SMOKE_DEFAULT["lat"],
"lon": SMOKE_DEFAULT["lon"],
"ayanamsa": SMOKE_DEFAULT["ayanamsa"],
"start": "2026-01-01 00:00 +08:00",
"end": "2026-03-31 00:00 +08:00",
}
values.update(overrides)
return argparse.Namespace(**values)
def test_default_horoscope_payload_uses_fictional_smoke_without_network():
payload = build_horoscope_payload(_args())
assert payload == {
"Ayanamsa": "LAHIRI",
"BirthTime": {
"StdTime": "12:00 01/01/1990 +08:00",
"Location": {"Name": "Beijing, China", "Longitude": 116.4074, "Latitude": 39.9042},
},
"SortByWeight": False,
}
assert "1993" not in payload["BirthTime"]["StdTime"]
assert "14:49" not in payload["BirthTime"]["StdTime"]
def test_dasa_payload_formats_vedastro_range_times_without_network():
payload = build_dasa_payload(_args())
assert payload["Ayanamsa"] == "LAHIRI"
assert payload["StartTime"]["StdTime"] == "00:00 01/01/2026 +08:00"
assert payload["EndTime"]["StdTime"] == "00:00 31/03/2026 +08:00"
assert payload["Levels"] == 3
assert payload["PrecisionHours"] == 100
def test_date_parser_rejects_ambiguous_times():
with pytest.raises(ValueError):
_date_to_vedastro_std("2026-01-01")
def test_parser_match_is_registered_as_official_blocked():
parser = build_parser()
args = parser.parse_args(["match"])
assert args.cmd == "match"
blocked = match_not_implemented()
assert blocked["official_closure_state"] == "official_blocked"
assert blocked["official_closure_reason"] == "match_subcommand_not_implemented"
class _Handle(BytesIO):
def __enter__(self):
return self
def __exit__(self, *args):
return False
def test_recorded_horoscope_status_pass_does_not_hit_network():
reset_rate_limiter()
recorded = {"Status": "Pass", "Payload": {"source": "recorded_smoke"}}
def fake_opener(_req, timeout=90):
return _Handle(b'{"Status": "Pass", "Payload": {"source": "recorded_smoke"}}')
result = call("HoroscopePredictions", build_horoscope_payload(_args()), opener=fake_opener)
assert result == recorded
def test_probe_maps_recorded_pass_and_rate_limit_without_network():
reset_rate_limiter()
health = probe_calculate_health(opener=lambda _req, timeout=8: _Handle(b'{"Status": "Pass"}'))
assert health["status"] == "official_verified"
assert health["transport"] == "rest"
def limited(_req, timeout=8):
raise urllib.error.HTTPError(
"https://api.vedastro.org/api/Calculate/HoroscopePredictions",
429,
"rate",
hdrs=http.client.HTTPMessage(),
fp=BytesIO(b""),
)
reset_rate_limiter()
blocked = probe_calculate_health(opener=limited)
assert blocked["status"] == "official_blocked"
assert blocked["reason"] == "rate_limited"