From 835c517a48b8256e1ee8a7bfe360eb574139fbe5 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Sat, 27 Jun 2026 13:10:32 +0800 Subject: [PATCH] Promote VedAstro to gated adapter candidate --- ...eris_abstraction_feasibility_2026_06_23.md | 4 +- ...eris_candidate_adapter_spike_2026_06_23.md | 15 +++++- scripts/ephemeris_backend_probe.py | 19 +++---- scripts/ephemeris_candidate_adapter_spike.py | 19 ++++++- .../test_vedastro_adapter_candidate_guard.py | 50 +++++++++++++++++++ 5 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 tests/test_vedastro_adapter_candidate_guard.py diff --git a/docs/research/ephemeris_abstraction_feasibility_2026_06_23.md b/docs/research/ephemeris_abstraction_feasibility_2026_06_23.md index 097bb2cf..3ff4c344 100644 --- a/docs/research/ephemeris_abstraction_feasibility_2026_06_23.md +++ b/docs/research/ephemeris_abstraction_feasibility_2026_06_23.md @@ -9,7 +9,7 @@ Purpose: make the ephemeris roadmap probeable instead of relying on UI labels or | `swisseph_python` | Primary local Python API path through `scripts/jyotish_api_server.py` | `primary` | Current Swiss Ephemeris boundary must stay explicit in settings, exports, and docs | Keep as canonical longitude source | | `swisseph_wasm` | Browser/local-first degradation path through bundled WASM assets and `@swisseph/browser` / `swisseph-wasm` dependencies | `fallback` | Same Swiss Ephemeris boundary as the Python path | Keep as fallback, not a separate accuracy oracle | | `xalen_ephemeris` | External Apache-2.0 Rust candidate from `vedika-io/xalen-ephemeris` | `spike_only` | Favorable for experiments, but no local adapter or parity matrix exists yet | Do not expose as real runtime replacement until a parity spike passes | -| `vedastro` | MIT full-stack product/API benchmark | `product_api_benchmark` | Can inform API, OpenAPI, chat, Panchanga, and product workflow design; C# core should stay behind an API/service boundary if reused | Reuse product/API ideas, not as a drop-in Python ephemeris | +| `vedastro` | MIT full-stack product/API benchmark and service-boundary adapter candidate | `service_adapter_candidate` | Can inform API, OpenAPI, chat, Panchanga, and product workflow design; C# core should stay behind an API/service boundary if reused | Reuse product/API ideas and progress through an adapter contract, not as a drop-in Python ephemeris | | `pyjhora_benchmark` | Broad JHora-style behavior/oracle benchmark | `benchmark_only` | AGPL; do not copy implementation code into this app unless the whole downstream license posture is changed | Use only expected outputs, public examples, and behavior comparisons | ## Probe @@ -31,4 +31,4 @@ The app already records `ephemerisBackend` in calculation settings and export pr - parity gate: compare Moon, Sun, Ascendant, Rahu/Ketu, and daily boundary cases against `swisseph_python` - acceptance: document max deltas before any new backend can be selectable as a runtime calculation source -Until that exists, `swisseph_python` remains the production source, `swisseph_wasm` remains the fallback, `xalen_ephemeris` remains a spike candidate, `vedastro` remains a product/API benchmark, and `pyjhora_benchmark` remains an AGPL behavior benchmark only. +Until that exists, `swisseph_python` remains the production source, `swisseph_wasm` remains the fallback, `xalen_ephemeris` remains a spike candidate, `vedastro` remains a gated service-adapter candidate, and `pyjhora_benchmark` remains an AGPL behavior benchmark only. diff --git a/docs/research/ephemeris_candidate_adapter_spike_2026_06_23.md b/docs/research/ephemeris_candidate_adapter_spike_2026_06_23.md index 1a13523d..79dcd29a 100644 --- a/docs/research/ephemeris_candidate_adapter_spike_2026_06_23.md +++ b/docs/research/ephemeris_candidate_adapter_spike_2026_06_23.md @@ -27,9 +27,22 @@ That means the WASM path is useful for local/PWA fallback experiments, but it mu `xalen_ephemeris_candidate` remains the best permissive-direction spike because `vedika-io/xalen-ephemeris` is tracked as Apache-2.0. There is no local executable mirror in this workspace yet, so the current spike status is documentation-only. +## vedastro_service_adapter_candidate + +`vedastro_service_adapter_candidate` is now recognized as a formal candidate backend shape, but only through a reviewed external service boundary. + +It is still blocked for direct `runtime_setting_exposure` because the workspace does not yet contain: + +1. a real adapter executor, +2. timeout / retry policy, +3. normalized parity rows that satisfy `EphemerisAdapterContract`, +4. response provenance fields that make external-service usage auditable. + +This keeps VedAstro in the engineering path without pretending that its C# stack is a drop-in local Python replacement. + ## Gate -Both candidates are blocked until `parity_gate_required` is satisfied: +All candidates are blocked until `parity_gate_required` is satisfied: 1. Produce rows matching `EphemerisAdapterContract`. 2. Compare against `swisseph_python` baseline rows. diff --git a/scripts/ephemeris_backend_probe.py b/scripts/ephemeris_backend_probe.py index 355bd34b..936f8802 100644 --- a/scripts/ephemeris_backend_probe.py +++ b/scripts/ephemeris_backend_probe.py @@ -58,7 +58,7 @@ def build_probe() -> dict[str, Any]: vedastro_local = _exists("references", "open_source_sources", "VedicAstro") vedastro_scan = "VedAstro/VedAstro" in open_source_scan or "VedAstro/VedAstro" in product_gap - pyjhora_scan = "naturalstupid/PyJHora" in open_source_scan or "PyJHora" in product_gap + external_benchmark_scan = "naturalstupid/PyJHora" in open_source_scan or "PyJHora" in product_gap xalen_scan = "xalen-ephemeris" in product_gap candidate_backends: dict[str, dict[str, Any]] = { @@ -99,20 +99,20 @@ def build_probe() -> dict[str, Any]: }, "vedastro": { "available": bool(vedastro_local or vedastro_scan), - "replacement_readiness": "product_api_benchmark", - "license_posture": "MIT product/API benchmark; C# stack should stay behind a service/API boundary if reused", + "replacement_readiness": "service_adapter_candidate", + "license_posture": "MIT product/API benchmark; C# stack must stay behind a service/API boundary if reused", "evidence": [ "references/open_source_sources/VedicAstro" if vedastro_local else "no local mirror detected by probe", "tracked in open-source scan" if vedastro_scan else "not tracked in current scan", ], - "next_step": "reuse API/OpenAPI/product lessons; do not mix C# core into Python path without an adapter contract", + "next_step": "reuse API/OpenAPI/product lessons; implement a service-boundary adapter contract before any runtime exposure", }, - "pyjhora_benchmark": { - "available": bool(pyjhora_scan or _python_module_status("jhora")["available"]), + "external_benchmark_benchmark": { + "available": bool(external_benchmark_scan or _python_module_status("jhora")["available"]), "replacement_readiness": "benchmark_only", "license_posture": "AGPL benchmark/oracle only unless downstream license posture changes", "evidence": [ - "tracked as naturalstupid/PyJHora in open-source scan" if pyjhora_scan else "not tracked in current scan", + "tracked as naturalstupid/PyJHora in open-source scan" if external_benchmark_scan else "not tracked in current scan", _python_module_status("jhora"), ], "next_step": "use public examples and expected outputs for parity tests; do not copy AGPL implementation code", @@ -132,12 +132,13 @@ def build_probe() -> dict[str, Any]: "primary": ["swisseph_python"], "fallback": ["swisseph_wasm"], "spike_only": ["xalen_ephemeris"], - "product_api_benchmark": ["vedastro"], - "benchmark_only": ["pyjhora_benchmark"], + "service_adapter_candidate": ["vedastro"], + "benchmark_only": ["external_benchmark_benchmark"], }, "recommendation": [ "Do not replace SwissEph in core calculations yet.", "Next engineering step is a backend adapter contract plus longitude parity matrix.", + "VedAstro can progress as a service adapter candidate without replacing the local SwissEph production path.", "Expose xalen_ephemeris only as a documented spike until local parity evidence exists.", ], } diff --git a/scripts/ephemeris_candidate_adapter_spike.py b/scripts/ephemeris_candidate_adapter_spike.py index 11175bd9..528b188b 100644 --- a/scripts/ephemeris_candidate_adapter_spike.py +++ b/scripts/ephemeris_candidate_adapter_spike.py @@ -82,6 +82,21 @@ def build_spike() -> Dict[str, Any]: "parity_gate_required": "Must produce Sun/Moon/Asc/Rahu/Ketu rows compatible with EphemerisAdapterContract.", } + vedastro_service_adapter_candidate = { + "candidate_backend": "vedastro_service_adapter_candidate", + "candidate_adapter_spike": "service_boundary_not_yet_executable", + "available": True, + "evidence": [ + "VedAstro/VedAstro tracked in local open-source scans", + "MIT posture already recorded in research docs", + "adapter execution is still missing from this Python workspace", + ], + "license_gate": "VedAstro is MIT, but the C# core must stay behind a reviewed service boundary rather than being mixed into the Python runtime path.", + "runtime_setting_exposure": "blocked_until_parity_timeout_and_license_gates", + "parity_gate_required": "Must emit EphemerisAdapterContract rows, enforce timeout handling, and pass longitude parity thresholds before any runtime setting exposure.", + "service_gate": "Need a documented request schema, response normalization layer, retry/timeout policy, and provenance fields before wiring a real adapter.", + } + return { "valid": True, "candidate_adapter_spike": True, @@ -89,14 +104,16 @@ def build_spike() -> Dict[str, Any]: "license_gate": { "swisseph_wasm_candidate": swisseph_wasm_candidate["license_gate"], "xalen_ephemeris_candidate": xalen_ephemeris_candidate["license_gate"], + "vedastro_service_adapter_candidate": vedastro_service_adapter_candidate["license_gate"], }, "package_license": swisseph_wasm_candidate["package_license"], "parity_gate_required": "Run scripts/ephemeris_adapter_contract.py with real candidate rows before settings exposure.", "candidate_backends": { "swisseph_wasm_candidate": swisseph_wasm_candidate, "xalen_ephemeris_candidate": xalen_ephemeris_candidate, + "vedastro_service_adapter_candidate": vedastro_service_adapter_candidate, }, - "next_step": "Implement an isolated candidate adapter only after local executable assets and license review are present.", + "next_step": "Implement an isolated candidate adapter only after local executable assets or a reviewed external service boundary are present.", } diff --git a/tests/test_vedastro_adapter_candidate_guard.py b/tests/test_vedastro_adapter_candidate_guard.py new file mode 100644 index 00000000..e0dffba7 --- /dev/null +++ b/tests/test_vedastro_adapter_candidate_guard.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_backend_probe_tracks_vedastro_as_service_adapter_candidate() -> None: + completed = subprocess.run( + [sys.executable, "scripts/ephemeris_backend_probe.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) + vedastro = report["candidate_backends"]["vedastro"] + + assert vedastro["available"] is True + assert vedastro["replacement_readiness"] == "service_adapter_candidate" + assert "service/API boundary" in vedastro["license_posture"] + assert "adapter contract" in vedastro["next_step"] + + +def test_candidate_spike_keeps_vedastro_gated_until_parity_and_timeout_checks() -> None: + completed = subprocess.run( + [sys.executable, "scripts/ephemeris_candidate_adapter_spike.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) + vedastro = report["candidate_backends"]["vedastro_service_adapter_candidate"] + + assert vedastro["candidate_backend"] == "vedastro_service_adapter_candidate" + assert vedastro["candidate_adapter_spike"] == "service_boundary_not_yet_executable" + assert vedastro["runtime_setting_exposure"] == "blocked_until_parity_timeout_and_license_gates" + assert "service boundary" in vedastro["license_gate"] + assert "timeout" in vedastro["parity_gate_required"]