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:
@@ -28,6 +28,32 @@ TECHNIQUE_TRUTH_IDS = (
|
||||
"tajika_yogas",
|
||||
"conception_chart",
|
||||
"relationship_combinations",
|
||||
"ashtottari_dasha",
|
||||
"classical_astrology_core",
|
||||
"classical_event_chart",
|
||||
"classical_real_case_calibration",
|
||||
"classical_usable_loop_audit",
|
||||
"western_draconic_chart",
|
||||
"western_profection",
|
||||
"western_relocation_chart",
|
||||
"western_solar_arc_directions",
|
||||
"western_zodiacal_release",
|
||||
"ziwei_app_js",
|
||||
"ziwei_bio_core_js",
|
||||
"ziwei_bridge_script_pack",
|
||||
"ziwei_character_bio_enhanced_js",
|
||||
"ziwei_character_bio_generator_js",
|
||||
"ziwei_complete_js",
|
||||
"ziwei_doushu_bridge",
|
||||
"ziwei_face_writing_bundle",
|
||||
"ziwei_i18n_bridge_bundle",
|
||||
"ziwei_i18n_complete_js",
|
||||
"ziwei_mainline_packet",
|
||||
"ziwei_patterns_js",
|
||||
"ziwei_personal_report_export",
|
||||
"ziwei_psychology_js",
|
||||
"ziwei_runtime_bridge_scripts",
|
||||
"ziwei_star_details_js",
|
||||
)
|
||||
_BLOCKED_STATUSES = {"blocked", "research_only_blocked"}
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ def build_report() -> dict:
|
||||
if official_ready
|
||||
else "official_snapshot_budget_exhausted_or_endpoint_blocked"
|
||||
),
|
||||
"official_transport": "rest",
|
||||
"official_calculate_endpoint": "https://api.vedastro.org/api/Calculate",
|
||||
"rate_limit_per_minute": 5,
|
||||
"mcp_bridge_role": "protocol_probe_only",
|
||||
"official_closure_plan": {
|
||||
"required_env": {
|
||||
"VEDASTRO_API_ENDPOINT": endpoint or "https://api.vedastro.org/api",
|
||||
@@ -77,9 +81,10 @@ def build_report() -> dict:
|
||||
"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.",
|
||||
"free_tier_policy": "Official REST Calculate is limited to 5 requests/minute. 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.",
|
||||
"default_path": "REST bridge scripts/vedastro_rest_bridge.py against /api/Calculate. Official MCP tools/call is protocol-probe only.",
|
||||
},
|
||||
"next_step": (
|
||||
"Run full-reading or strict_workflow; verify vedastro_official.status is ok/partial."
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
This artifact turns the parity matrix plus live official catalog snapshot into
|
||||
an execution-friendly view:
|
||||
|
||||
- what should go through official MCP
|
||||
- what should go through the official REST Calculate bridge (default)
|
||||
- what should go through the official Python bridge
|
||||
- what should go through the REST adapter
|
||||
- what should stay local because the repo is already stronger there
|
||||
- official MCP remains protocol-probe only after tools/call regression
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -27,9 +27,9 @@ DEFAULT_MARKDOWN_PATH = ROOT / "docs" / "research" / "vedastro_fast_path_checkli
|
||||
CATALOG_PATH = ROOT / "scratch" / "local" / "vedastro_adapter" / "method_catalog_snapshot.json"
|
||||
|
||||
LANE_TITLES = {
|
||||
"official_mcp": "Direct Official MCP",
|
||||
"official_mcp": "Official MCP protocol probe only",
|
||||
"official_python_bridge": "Official Python Bridge",
|
||||
"rest_adapter": "REST Adapter",
|
||||
"rest_adapter": "Official REST Calculate bridge",
|
||||
"local_native_preferred": "Local Native Preferred",
|
||||
"hybrid_router": "Hybrid Router",
|
||||
"external_evidence_only": "External Evidence Only",
|
||||
|
||||
@@ -16,6 +16,9 @@ BACKEND_PRIORITY = ["self_host", "official", "cache", "queue", "local_fallback"]
|
||||
BOUNDARY_TEXT = "Users never call VedAstro directly; backend gateway owns cache, queue, and fallback."
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OFFICIAL_ENDPOINT = "https://api.vedastro.org/api"
|
||||
OFFICIAL_CALCULATE_ENDPOINT = "https://api.vedastro.org/api/Calculate"
|
||||
REST_RATE_LIMIT_PER_MINUTE = 5
|
||||
MCP_BRIDGE_ROLE = "protocol_probe_only"
|
||||
|
||||
|
||||
def _bool_env(name: str) -> bool:
|
||||
@@ -229,16 +232,42 @@ def run_gateway_job(job_id: str) -> dict[str, Any] | None:
|
||||
return complete_gateway_job(job_id, result)
|
||||
|
||||
|
||||
def probe_official_rest_health() -> dict[str, Any]:
|
||||
"""Health of the default official path: REST ``/api/Calculate`` Status Pass."""
|
||||
base = {
|
||||
"transport": "rest",
|
||||
"endpoint": OFFICIAL_CALCULATE_ENDPOINT,
|
||||
"rate_limit_per_minute": REST_RATE_LIMIT_PER_MINUTE,
|
||||
"mcp_bridge_role": MCP_BRIDGE_ROLE,
|
||||
}
|
||||
if os.environ.get("JYOTISH_SKIP_LOCAL_ENV", "").strip().lower() in {"1", "true", "yes", "on"}:
|
||||
return {**base, "status": "not_probed", "reason": "jyotish_skip_local_env"}
|
||||
if not _official_network_enabled():
|
||||
return {**base, "status": "official_blocked", "reason": "network_disabled"}
|
||||
try:
|
||||
from scripts.vedastro_rest_bridge import probe_calculate_health
|
||||
except ModuleNotFoundError: # pragma: no cover - script execution
|
||||
from vedastro_rest_bridge import probe_calculate_health
|
||||
result = probe_calculate_health()
|
||||
return {**base, **result}
|
||||
|
||||
|
||||
def gateway_status() -> dict[str, Any]:
|
||||
from scripts.diagnose_vedastro_mode import build_report as build_vedastro_mode_report
|
||||
|
||||
readiness = build_vedastro_mode_report()
|
||||
config = build_gateway_config()
|
||||
rest_health = probe_official_rest_health()
|
||||
return {
|
||||
"scope": "vedastro_gateway",
|
||||
"mode": config["mode"],
|
||||
"backend_priority": BACKEND_PRIORITY,
|
||||
"active_backend": _active_backend(config),
|
||||
"official_transport": "rest",
|
||||
"official_calculate_endpoint": OFFICIAL_CALCULATE_ENDPOINT,
|
||||
"rate_limit_per_minute": REST_RATE_LIMIT_PER_MINUTE,
|
||||
"mcp_bridge_role": MCP_BRIDGE_ROLE,
|
||||
"official_calculate_health": rest_health,
|
||||
"self_host_configured": config["self_host_endpoint_configured"],
|
||||
"official_configured": config["official_endpoint_configured"],
|
||||
"credential_configured": bool(os.environ.get("VEDASTRO_API_KEY", "").strip()),
|
||||
@@ -251,6 +280,8 @@ def gateway_status() -> dict[str, Any]:
|
||||
"readiness_blockers": list(readiness.get("readiness_blockers") or []),
|
||||
"free_tier_possible_with_cache_queue": bool(readiness.get("free_tier_possible_with_cache_queue")),
|
||||
"official_closure_plan": readiness.get("official_closure_plan") or {},
|
||||
"official_transport": "rest",
|
||||
"rate_limit_per_minute": REST_RATE_LIMIT_PER_MINUTE,
|
||||
},
|
||||
"direct_browser_access_allowed": False,
|
||||
"frontend_secret_safe": True,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Thin bridge to the official public VedAstro MCP endpoint.
|
||||
"""Protocol-probe-only bridge to the official public VedAstro MCP endpoint.
|
||||
|
||||
This bridge is intentionally narrow. It proves that the official public MCP
|
||||
surface is reachable and callable from the local workspace without letting
|
||||
external responses silently override local adjudication.
|
||||
The 2026-09-01 probe found ``tools/call`` returning "Invalid or Outdated Call".
|
||||
Default official comparison now goes through ``scripts/vedastro_rest_bridge.py``.
|
||||
This module stays for initialize/tools-list protocol checks and must not be
|
||||
treated as the production comparison path.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -189,16 +189,17 @@ VEDASTRO_CAPABILITY_SEEDS: list[dict[str, Any]] = [
|
||||
"jyotish_api_server.py",
|
||||
"strict workflows",
|
||||
"vedastro_service_adapter.py",
|
||||
"vedastro_rest_bridge.py",
|
||||
"vedastro_official_mcp_bridge.py",
|
||||
],
|
||||
"can_call_vedastro": True,
|
||||
"recommended_path": "hybrid_local_plus_vedastro",
|
||||
"fastest_path_lane": "official_mcp",
|
||||
"fastest_path_lane": "rest_adapter",
|
||||
"priority": "P0",
|
||||
"license_boundary": "external_service_or_local_native",
|
||||
"adjudicator_use": "primary",
|
||||
"gap_notes": "Local API/MCP surfaces exist and the official public MCP bridge is live; REST adapter official endpoint smoke still depends on configured endpoint-backed execution.",
|
||||
"route_notes": "If official MCP is available, that is the fastest direct agent path; otherwise fall back to local REST adapter.",
|
||||
"gap_notes": "Official MCP tools/call remains Invalid or Outdated Call; default external path is the REST Calculate bridge. MCP bridge is protocol-probe only. match/synastry stays official_blocked.",
|
||||
"route_notes": "Default official comparison uses scripts/vedastro_rest_bridge.py (5 req/min). Keep vedastro_official_mcp_bridge.py for protocol probing only.",
|
||||
},
|
||||
{
|
||||
"vedastro_capability": "Numerology / Non-Jyotish Tools",
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python3
|
||||
"""VedAstro official REST bridge.
|
||||
|
||||
The official MCP ``tools/call`` resolver returned "Invalid or Outdated Call"
|
||||
in the 2026-09-01 probe. This bridge calls ``https://api.vedastro.org/api/Calculate``
|
||||
directly for external comparison data.
|
||||
|
||||
Birth arguments default to a fictional smoke chart (1990-01-01 Beijing).
|
||||
They are never a real person. The match/synastry subcommand is not implemented
|
||||
and remains ``official_blocked``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
API = "https://api.vedastro.org/api/Calculate"
|
||||
RATE_LIMIT_PER_MINUTE = 5
|
||||
# Fictional smoke only. Do not replace with a real birth record.
|
||||
SMOKE_DEFAULT = dict(
|
||||
name="Beijing, China",
|
||||
lat=39.9042,
|
||||
lon=116.4074,
|
||||
birth="12:00 01/01/1990 +08:00",
|
||||
ayanamsa="LAHIRI",
|
||||
)
|
||||
|
||||
_RATE_LOCK = threading.Lock()
|
||||
_RATE_WINDOW = 0.0
|
||||
_RATE_COUNT = 0
|
||||
|
||||
|
||||
class RestRateLimited(RuntimeError):
|
||||
"""Raised when the local 5/min guard trips before a network call."""
|
||||
|
||||
|
||||
def _time(birth: str, name: str, lat: float, lon: float) -> dict[str, Any]:
|
||||
return {"StdTime": birth, "Location": {"Name": name, "Longitude": lon, "Latitude": lat}}
|
||||
|
||||
|
||||
def _date_to_vedastro_std(value: str) -> str:
|
||||
parts = value.split()
|
||||
if len(parts) != 3:
|
||||
raise ValueError("time must use 'YYYY-MM-DD HH:MM +08:00' format")
|
||||
date_part, time_part, tz = parts
|
||||
y, m, d = date_part.split("-")
|
||||
return f"{time_part} {d}/{m}/{y} {tz}"
|
||||
|
||||
|
||||
def consume_rate_token(*, now: float | None = None) -> None:
|
||||
global _RATE_WINDOW, _RATE_COUNT
|
||||
current = time.time() if now is None else now
|
||||
with _RATE_LOCK:
|
||||
if current - _RATE_WINDOW >= 60:
|
||||
_RATE_WINDOW = current
|
||||
_RATE_COUNT = 0
|
||||
if _RATE_COUNT >= RATE_LIMIT_PER_MINUTE:
|
||||
raise RestRateLimited("vedastro_rest_rate_limited")
|
||||
_RATE_COUNT += 1
|
||||
|
||||
|
||||
def reset_rate_limiter() -> None:
|
||||
global _RATE_WINDOW, _RATE_COUNT
|
||||
with _RATE_LOCK:
|
||||
_RATE_WINDOW = 0.0
|
||||
_RATE_COUNT = 0
|
||||
|
||||
|
||||
def build_horoscope_payload(args: argparse.Namespace) -> dict[str, Any]:
|
||||
return {
|
||||
"Ayanamsa": args.ayanamsa,
|
||||
"BirthTime": _time(args.birth, args.name, args.lat, args.lon),
|
||||
"SortByWeight": False,
|
||||
}
|
||||
|
||||
|
||||
def build_dasa_payload(args: argparse.Namespace) -> dict[str, Any]:
|
||||
return {
|
||||
"Ayanamsa": args.ayanamsa,
|
||||
"BirthTime": _time(args.birth, args.name, args.lat, args.lon),
|
||||
"StartTime": _time(_date_to_vedastro_std(args.start), args.name, args.lat, args.lon),
|
||||
"EndTime": _time(_date_to_vedastro_std(args.end), args.name, args.lat, args.lon),
|
||||
"Levels": 3,
|
||||
"PrecisionHours": 100,
|
||||
}
|
||||
|
||||
|
||||
def match_not_implemented() -> dict[str, Any]:
|
||||
return {
|
||||
"Status": "official_blocked",
|
||||
"official_closure_state": "official_blocked",
|
||||
"official_closure_reason": "match_subcommand_not_implemented",
|
||||
"boundary": "VedAstro REST match/synastry is not implemented; do not fabricate a comparison.",
|
||||
}
|
||||
|
||||
|
||||
def call(
|
||||
method: str,
|
||||
payload: dict[str, Any],
|
||||
out: str | None = None,
|
||||
*,
|
||||
opener: Any | None = None,
|
||||
timeout: float = 90,
|
||||
skip_rate_limit: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
if not skip_rate_limit:
|
||||
consume_rate_token()
|
||||
req = urllib.request.Request(
|
||||
f"{API}/{method}",
|
||||
data=json.dumps(payload).encode(),
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
if opener is None:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
else:
|
||||
with opener(req, timeout=timeout) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
except urllib.error.HTTPError as error:
|
||||
if error.code == 429:
|
||||
return {
|
||||
"Status": "official_blocked",
|
||||
"official_closure_state": "official_blocked",
|
||||
"official_closure_reason": "rate_limited",
|
||||
"http_status": 429,
|
||||
}
|
||||
raise
|
||||
if out:
|
||||
Path(out).write_text(json.dumps(data, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
|
||||
print(f"[已落盘] {out}")
|
||||
return data
|
||||
|
||||
|
||||
def probe_calculate_health(*, opener: Any | None = None, timeout: float = 8) -> dict[str, Any]:
|
||||
"""POST a fictional smoke horoscope and report whether Status is Pass."""
|
||||
args = argparse.Namespace(**SMOKE_DEFAULT)
|
||||
try:
|
||||
payload = build_horoscope_payload(args)
|
||||
data = call("HoroscopePredictions", payload, opener=opener, timeout=timeout)
|
||||
except RestRateLimited:
|
||||
return {
|
||||
"status": "official_blocked",
|
||||
"reason": "rate_limited",
|
||||
"endpoint": API,
|
||||
"transport": "rest",
|
||||
}
|
||||
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, OSError) as error:
|
||||
return {
|
||||
"status": "official_blocked",
|
||||
"reason": type(error).__name__,
|
||||
"endpoint": API,
|
||||
"transport": "rest",
|
||||
}
|
||||
status = str(data.get("Status") or "")
|
||||
if status.lower() == "pass":
|
||||
return {"status": "official_verified", "reason": "status_pass", "endpoint": API, "transport": "rest"}
|
||||
if str(data.get("official_closure_reason") or "") == "rate_limited":
|
||||
return {"status": "official_blocked", "reason": "rate_limited", "endpoint": API, "transport": "rest"}
|
||||
return {
|
||||
"status": "official_blocked",
|
||||
"reason": f"status_{status or 'missing'}",
|
||||
"endpoint": API,
|
||||
"transport": "rest",
|
||||
}
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="VedAstro official REST bridge (fictional smoke defaults)")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
for command in ("horoscope", "dasa"):
|
||||
item = sub.add_parser(command)
|
||||
item.add_argument("--birth", default=SMOKE_DEFAULT["birth"], help="VedAstro StdTime; default is fictional smoke")
|
||||
item.add_argument("--name", default=SMOKE_DEFAULT["name"])
|
||||
item.add_argument("--lat", type=float, default=SMOKE_DEFAULT["lat"])
|
||||
item.add_argument("--lon", type=float, default=SMOKE_DEFAULT["lon"])
|
||||
item.add_argument("--ayanamsa", default=SMOKE_DEFAULT["ayanamsa"])
|
||||
item.add_argument("--out")
|
||||
sub._name_parser_map["dasa"].add_argument("--start", default="2026-01-01 00:00 +08:00")
|
||||
sub._name_parser_map["dasa"].add_argument("--end", default="2032-12-31 00:00 +08:00")
|
||||
match = sub.add_parser("match", help="Not implemented; remains official_blocked")
|
||||
match.add_argument("--out")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
if args.cmd == "match":
|
||||
result = match_not_implemented()
|
||||
if args.out:
|
||||
Path(args.out).write_text(json.dumps(result, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
return 0
|
||||
if args.cmd == "horoscope":
|
||||
result = call("HoroscopePredictions", build_horoscope_payload(args), args.out)
|
||||
elif args.cmd == "dasa":
|
||||
result = call("DasaAtRange", build_dasa_payload(args), args.out)
|
||||
else:
|
||||
sys.exit("未知子命令")
|
||||
if not args.out:
|
||||
print(json.dumps(result, ensure_ascii=False)[:2000])
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("PYTHONDONTWRITEBYTECODE", "1")
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user