Files
Jyotisha/docs/superpowers/plans/2026-07-16-rangacharya-vedastro-phase1.md
T

628 lines
18 KiB
Markdown

# Rangacharya Variant + VedAstro Closure Phase 1 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build the first safe Rangacharya variant slice: source manifest, core variant skeleton, current-vs-variant diff output, non-adjudication guard, and VedAstro raw-closure status hooks.
**Architecture:** Keep current Jaimini unchanged. Add a small `rangacharya` module that consumes the same chart inputs, emits experimental outputs, and is blocked from verdict/timing use by explicit validation status. Add source-manifest governance before importing any screenshot/archive/article rules into runtime.
**Tech Stack:** Python 3 standard library, existing `scripts/jaimini.py`, existing `scripts/jyotish_api_server.py`, existing VedAstro gateway/archive contracts, pytest.
## Global Constraints
- Do not store or echo the VedAstro key.
- Do not copy AGPL PyJHora code.
- Do not use `.workbuddy` as runtime.
- Do not change existing `calc_arudha_padas()` behavior.
- Every new Rangacharya rule starts `transcribed`, `blocked`, or `experimental_not_for_adjudication`.
- Keep unrelated dirty-tree files untouched.
## File Map
- Create `references/rangacharya_source_manifest.json`: source inventory and rule validation states.
- Create `scripts/rangacharya.py`: independent variant functions and diff helpers.
- Create `tests/test_rangacharya_source_manifest.py`: manifest schema/privacy tests.
- Create `tests/test_rangacharya_variant.py`: core variant and non-adjudication tests.
- Modify `scripts/jyotish_api_server.py`: optional `variant=rangacharya|all` in Jaimini endpoint.
- Modify `tests/test_jaimini.py` or create `tests/test_jaimini_rangacharya_api.py`: endpoint contract tests.
- Modify existing VedAstro tests only if needed to expose raw-closure status, not to run real credentials in CI.
---
### Task 1: Source Manifest
**Files:**
- Create: `references/rangacharya_source_manifest.json`
- Create: `tests/test_rangacharya_source_manifest.py`
- [ ] **Step 1: Write failing manifest tests**
Create `tests/test_rangacharya_source_manifest.py`:
```python
import json
from pathlib import Path
MANIFEST = Path("references/rangacharya_source_manifest.json")
def test_manifest_exists_and_has_required_sections():
data = json.loads(MANIFEST.read_text(encoding="utf-8"))
assert data["schema_version"] == 1
assert "sources" in data
assert "rules" in data
assert "validation_ladder" in data
def test_manifest_does_not_contain_secrets():
text = MANIFEST.read_text(encoding="utf-8")
assert "sk_live_" not in text
assert "api_key" not in text.lower()
def test_rules_default_below_adjudication():
data = json.loads(MANIFEST.read_text(encoding="utf-8"))
for rule in data["rules"]:
assert rule["status"] in {
"transcribed",
"source_verified",
"golden_verified",
"engine_cross_checked",
"case_calibrated",
"blocked",
}
assert rule["status"] != "adjudication_enabled"
assert rule["adjudication_enabled"] is False
```
- [ ] **Step 2: Run test, verify failure**
Run:
```bash
python3 -m pytest tests/test_rangacharya_source_manifest.py -q
```
Expected: FAIL because manifest is missing.
- [ ] **Step 3: Add minimal manifest**
Create `references/rangacharya_source_manifest.json` with no secrets:
```json
{
"schema_version": 1,
"created": "2026-07-16",
"sources": [
{
"id": "uploaded_screenshots_20260716",
"kind": "user_uploaded_screenshots",
"paths": [
"/Users/wuyongnaren/Downloads/IMG_3502.PNG",
"/Users/wuyongnaren/Downloads/IMG_3503.PNG",
"/Users/wuyongnaren/Downloads/IMG_3504.PNG",
"/Users/wuyongnaren/Downloads/IMG_3505.PNG",
"/Users/wuyongnaren/Downloads/IMG_3506.PNG",
"/Users/wuyongnaren/Downloads/IMG_3507.PNG"
],
"license": "user_private_reference",
"privacy": "private",
"runtime_use": "reference_only_until_formula_verified"
},
{
"id": "local_article_warehouse_20260716",
"kind": "local_research_archive",
"path": "/Users/wuyongnaren/文件仓库/印度占星文章",
"license": "unknown",
"privacy": "local_research",
"runtime_use": "manifest_only_until_hash_and_license_review"
},
{
"id": "vedastro_official",
"kind": "external_oracle",
"url": "https://github.com/VedAstro/VedAstro",
"license": "MIT",
"privacy": "public",
"runtime_use": "oracle_raw_reference"
},
{
"id": "pyjhora_official",
"kind": "external_oracle",
"url": "https://github.com/naturalstupid/PyJHora",
"license": "AGPL-3.0",
"privacy": "public",
"runtime_use": "isolated_external_process_only"
}
],
"validation_ladder": [
"transcribed",
"source_verified",
"golden_verified",
"engine_cross_checked",
"case_calibrated",
"adjudication_enabled",
"blocked"
],
"rules": [
{
"id": "rangacharya_core_arudha",
"label": "Rangacharya Arudha core counting",
"source_ids": ["uploaded_screenshots_20260716"],
"status": "transcribed",
"adjudication_enabled": false
},
{
"id": "active_effective_lagna",
"label": "Active Lagna and Effective Lagna",
"source_ids": ["uploaded_screenshots_20260716"],
"status": "transcribed",
"adjudication_enabled": false
},
{
"id": "rangacharya_named_yogas",
"label": "Dhana/Nirdhana/Kemadruma and other named yogas",
"source_ids": ["uploaded_screenshots_20260716"],
"status": "blocked",
"adjudication_enabled": false,
"blocked_reason": "needs formula-level source cards before runtime use"
}
]
}
```
- [ ] **Step 4: Run test, verify pass**
Run:
```bash
python3 -m pytest tests/test_rangacharya_source_manifest.py -q
```
- [ ] **Step 5: Commit**
Stage only manifest and its test.
---
### Task 2: Rangacharya Variant Skeleton
**Files:**
- Create: `scripts/rangacharya.py`
- Create: `tests/test_rangacharya_variant.py`
- [ ] **Step 1: Write failing core tests**
Create `tests/test_rangacharya_variant.py`:
```python
from scripts import rangacharya
SAMPLE_LONGS = {
"Sun": 10.0,
"Moon": 45.0,
"Mars": 80.0,
"Mercury": 110.0,
"Jupiter": 145.0,
"Venus": 200.0,
"Saturn": 250.0,
"Rahu": 300.0,
"Ketu": 120.0,
}
def test_variant_result_is_experimental_and_not_for_adjudication():
result = rangacharya.calc_rangacharya_variant(0, SAMPLE_LONGS)
assert result["variant"] == "rangacharya"
assert result["adjudication_enabled"] is False
assert result["status"] == "experimental_not_for_adjudication"
def test_variant_includes_core_sections():
result = rangacharya.calc_rangacharya_variant(0, SAMPLE_LONGS)
assert "source_status" in result
assert "arudha_padas" in result
assert "active_lagna" in result
assert "effective_lagna" in result
def test_diff_marks_algorithm_names():
current = {"AL": {"sign": "Aries"}}
variant = {"arudha_padas": {"AL": {"sign": "Taurus"}}}
diff = rangacharya.diff_current_vs_rangacharya(current, variant)
assert diff["current_algorithm"] == "current_jaimini"
assert diff["variant_algorithm"] == "rangacharya"
assert diff["differences"][0]["key"] == "AL.sign"
```
- [ ] **Step 2: Run test, verify failure**
Run:
```bash
python3 -m pytest tests/test_rangacharya_variant.py -q
```
Expected: FAIL because module is missing.
- [ ] **Step 3: Add minimal module**
Create `scripts/rangacharya.py`:
```python
"""Experimental Rangacharya/Jaimini variant.
All outputs are blocked from adjudication until formula-level validation passes.
"""
from __future__ import annotations
from typing import Any, Dict, Mapping
SIGNS = [
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
]
def _sign_index(longitude: float) -> int:
return int((longitude % 360) // 30)
def _sign_name(index: int) -> str:
return SIGNS[index % 12]
def _placeholder_pada(label: str, asc_sign_idx: int, source_house: int) -> Dict[str, Any]:
sign_idx = (asc_sign_idx + source_house - 1) % 12
return {
"label": label,
"sign": _sign_name(sign_idx),
"sign_index": sign_idx,
"source_house": source_house,
"validation_status": "transcribed",
"adjudication_enabled": False,
"note": "Rangacharya formula pending source-card implementation",
}
def calc_rangacharya_variant(asc_sign_idx: int, planet_longitudes: Mapping[str, float]) -> Dict[str, Any]:
asc_sign_idx %= 12
arudha_padas = {
"AL": _placeholder_pada("AL", asc_sign_idx, 1),
"A7": _placeholder_pada("A7", asc_sign_idx, 7),
"A10": _placeholder_pada("A10", asc_sign_idx, 10),
"UL": _placeholder_pada("UL", asc_sign_idx, 12),
}
return {
"variant": "rangacharya",
"status": "experimental_not_for_adjudication",
"adjudication_enabled": False,
"source_status": "transcribed",
"active_lagna": {
"sign": _sign_name(asc_sign_idx),
"validation_status": "transcribed",
"adjudication_enabled": False,
},
"effective_lagna": {
"sign": _sign_name(asc_sign_idx),
"validation_status": "transcribed",
"adjudication_enabled": False,
},
"arudha_padas": arudha_padas,
"input_planets_present": sorted(planet_longitudes),
}
def _flatten(prefix: str, value: Any) -> Dict[str, Any]:
if not isinstance(value, dict):
return {prefix: value}
rows: Dict[str, Any] = {}
for key, child in value.items():
child_key = f"{prefix}.{key}" if prefix else str(key)
rows.update(_flatten(child_key, child))
return rows
def diff_current_vs_rangacharya(current: Mapping[str, Any], variant: Mapping[str, Any]) -> Dict[str, Any]:
current_flat = _flatten("", dict(current))
variant_flat = _flatten("", dict(variant.get("arudha_padas", variant)))
differences = []
for key in sorted(set(current_flat) | set(variant_flat)):
current_value = current_flat.get(key)
variant_value = variant_flat.get(key)
if current_value != variant_value:
differences.append({"key": key, "current": current_value, "rangacharya": variant_value})
return {
"current_algorithm": "current_jaimini",
"variant_algorithm": "rangacharya",
"adjudication_enabled": False,
"differences": differences,
}
```
- [ ] **Step 4: Run tests**
Run:
```bash
python3 -m pytest tests/test_rangacharya_variant.py -q
```
- [ ] **Step 5: Commit**
Stage only `scripts/rangacharya.py` and `tests/test_rangacharya_variant.py`.
---
### Task 3: API Exposure Without Changing Current Defaults
**Files:**
- Modify: `scripts/jyotish_api_server.py`
- Create: `tests/test_jaimini_rangacharya_api.py`
- [ ] **Step 1: Write endpoint contract tests**
Create `tests/test_jaimini_rangacharya_api.py` using the existing API test helper style in nearby tests. Assert:
```python
def test_jaimini_default_does_not_include_rangacharya(client):
response = client.post("/api/jaimini", json={"ascendant": 0, "planets": []})
data = response.get_json()
assert "rangacharya" not in data["result"]
def test_jaimini_variant_all_includes_current_variant_and_diff(client):
response = client.post("/api/jaimini", json={
"ascendant": 0,
"variant": "all",
"mode": "arudha",
"planets": [
{"name": "Sun", "longitude": 10},
{"name": "Moon", "longitude": 45},
{"name": "Mars", "longitude": 80},
{"name": "Mercury", "longitude": 110},
{"name": "Jupiter", "longitude": 145},
{"name": "Venus", "longitude": 200},
{"name": "Saturn", "longitude": 250}
]
})
data = response.get_json()
assert data["result"]["rangacharya"]["adjudication_enabled"] is False
assert data["result"]["rangacharya_diff"]["adjudication_enabled"] is False
```
If existing test fixtures use a different client factory, copy that local pattern exactly.
- [ ] **Step 2: Run test, verify failure**
Run:
```bash
python3 -m pytest tests/test_jaimini_rangacharya_api.py -q
```
- [ ] **Step 3: Add optional variant handling**
In `scripts/jyotish_api_server.py::_compute_jaimini`:
```python
variant = body.get("variant", "current")
if not isinstance(variant, str):
raise BadRequest("variant must be a string")
variant = variant.strip().lower() or "current"
allowed_variants = {"current", "rangacharya", "all"}
if variant not in allowed_variants:
raise BadRequest(f'variant must be one of: {", ".join(sorted(allowed_variants))}')
```
After current arudha calculation:
```python
if variant in ("rangacharya", "all"):
rangacharya = _load_local_module("rangacharya")
rangacharya_result = rangacharya.calc_rangacharya_variant(asc_sign_idx, planet_lons)
result["rangacharya"] = rangacharya_result
current_arudha = result.get("arudha_padas") or jaimini.calc_arudha_padas(asc_sign_idx, planet_lons)
result["rangacharya_diff"] = rangacharya.diff_current_vs_rangacharya(current_arudha, rangacharya_result)
```
Preserve default `variant=current`, so existing clients do not see new fields.
- [ ] **Step 4: Run focused tests**
Run:
```bash
python3 -m pytest tests/test_jaimini_rangacharya_api.py tests/test_jaimini.py -q
```
- [ ] **Step 5: Commit**
Stage only API/test files touched in this task.
---
### Task 4: Adjudication Guard
**Files:**
- Create: `tests/test_rangacharya_adjudication_guard.py`
- Modify: `scripts/rangacharya.py`
- [ ] **Step 1: Write guard tests**
Create `tests/test_rangacharya_adjudication_guard.py`:
```python
import pytest
from scripts import rangacharya
def test_assert_adjudication_allowed_rejects_default_variant():
result = rangacharya.calc_rangacharya_variant(0, {"Sun": 10.0})
with pytest.raises(rangacharya.RangacharyaValidationError):
rangacharya.assert_adjudication_allowed(result)
def test_validation_summary_lists_blocking_rules():
result = rangacharya.calc_rangacharya_variant(0, {"Sun": 10.0})
summary = rangacharya.validation_summary(result)
assert summary["adjudication_enabled"] is False
assert summary["blocking_statuses"]
```
- [ ] **Step 2: Run test, verify failure**
Run:
```bash
python3 -m pytest tests/test_rangacharya_adjudication_guard.py -q
```
- [ ] **Step 3: Add guard functions**
Add to `scripts/rangacharya.py`:
```python
class RangacharyaValidationError(RuntimeError):
pass
def validation_summary(result: Mapping[str, Any]) -> Dict[str, Any]:
statuses = []
for row in _flatten("", dict(result)).items():
key, value = row
if key.endswith("validation_status"):
statuses.append(str(value))
blocking = sorted({status for status in statuses if status != "adjudication_enabled"})
return {
"adjudication_enabled": bool(result.get("adjudication_enabled")) and not blocking,
"blocking_statuses": blocking,
}
def assert_adjudication_allowed(result: Mapping[str, Any]) -> None:
summary = validation_summary(result)
if not summary["adjudication_enabled"]:
raise RangacharyaValidationError(
"Rangacharya variant is not adjudication-enabled; validation gates are incomplete"
)
```
- [ ] **Step 4: Run tests**
Run:
```bash
python3 -m pytest tests/test_rangacharya_adjudication_guard.py tests/test_rangacharya_variant.py -q
```
- [ ] **Step 5: Commit**
Stage only guard files.
---
### Task 5: VedAstro Closure Status Hook
**Files:**
- Modify: existing VedAstro gateway/status module found by `rg -n "official_closure_state|VedAstro Raw Archive Manifest|gateway_status" scripts tests`
- Modify/Create: focused VedAstro status test
- [ ] **Step 1: Locate existing contract**
Run:
```bash
rg -n "official_closure_state|VedAstro Raw Archive Manifest|gateway_status|official_raw_response" scripts tests
```
Use the current status function rather than adding a parallel gateway.
- [ ] **Step 2: Write failing test for env-only key and blocked raw**
In the existing VedAstro status test file, add a test equivalent to:
```python
def test_vedastro_closure_status_never_exposes_secret(monkeypatch):
monkeypatch.setenv("VEDASTRO_API_KEY", "sk_live_test_secret")
status = gateway_status()
text = json.dumps(status, sort_keys=True)
assert "sk_live_test_secret" not in text
assert status["official_closure_state"] in {
"official_verified",
"official_raw_missing_or_unverified",
"blocked",
"not_configured",
}
```
Adapt imports to the existing module.
- [ ] **Step 3: Implement minimal redaction/status preservation**
If status currently includes env values, replace with booleans:
```python
"credential_configured": bool(os.environ.get("VEDASTRO_API_KEY")),
```
Never include key contents. If no official raw response exists, set:
```python
"official_closure_state": "official_raw_missing_or_unverified"
```
- [ ] **Step 4: Run focused VedAstro tests**
Run:
```bash
python3 -m pytest tests/test_vedastro_service_adapter_executor.py tests/test_vedastro_official_mcp_bridge.py -q
```
If these are too broad, run only the specific test nodes touched and record that full files were not run.
- [ ] **Step 5: Commit**
Stage only VedAstro status/test files touched in this task.
---
### Task 6: Documentation And Planning Sync
**Files:**
- Modify: `.planning/rangacharya_vedastro_design_20260716/task_plan.md`
- Modify: `.planning/rangacharya_vedastro_design_20260716/progress.md`
- Modify: `.planning/rangacharya_vedastro_design_20260716/findings.md` if new facts appear
- [ ] **Step 1: Update planning state**
Mark design/planning complete and implementation pending/executing according to actual state.
- [ ] **Step 2: Run mandatory pre-work and focused tests**
Run:
```bash
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
python3 -m pytest tests/test_rangacharya_source_manifest.py tests/test_rangacharya_variant.py tests/test_rangacharya_adjudication_guard.py tests/test_jaimini_rangacharya_api.py -q
```
- [ ] **Step 3: Final verify no secret leakage**
Run:
```bash
rg -n "sk_live_|VEDASTRO_API_KEY=.*sk_|api_key.*sk_" references scripts tests docs .planning
```
Expected: no real user secret. Existing synthetic examples are acceptable only if already present and clearly fake.
- [ ] **Step 4: Commit or report uncommitted state**
If asked to commit, stage only files from this plan. Otherwise leave changes unstaged and report exact files changed.