Promote VedAstro to gated adapter candidate

This commit is contained in:
732642856
2026-06-27 13:10:32 +08:00
parent bc173ba9ee
commit 835c517a48
5 changed files with 94 additions and 13 deletions
@@ -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.
@@ -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.
+10 -9
View File
@@ -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.",
],
}
+18 -1
View File
@@ -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.",
}
@@ -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"]