Merge remote-tracking branch 'origin/main' into codex/cross-project-contract
This commit is contained in:
@@ -8,6 +8,16 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = str(ROOT / "scripts")
|
||||
WORKBUDDY_SKILL_SCRIPTS = ".workbuddy/skills/jyotish-vedic-astrology/scripts"
|
||||
SLOW_API_SECURITY_PREFIXES = (
|
||||
"test_vedastro_",
|
||||
"test_high_rigor_",
|
||||
"test_professional_reading",
|
||||
"test_api_prompt_pack",
|
||||
"test_consultation_workflow",
|
||||
"test_thematic_report",
|
||||
"test_capability_audit",
|
||||
"test_technique_catalog",
|
||||
)
|
||||
|
||||
|
||||
def ensure_project_scripts_first() -> None:
|
||||
@@ -28,4 +38,10 @@ def pytest_runtest_setup() -> None:
|
||||
ensure_project_scripts_first()
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items) -> None:
|
||||
for item in items:
|
||||
if item.fspath.basename == "test_api_server_security.py" and item.name.startswith(SLOW_API_SECURITY_PREFIXES):
|
||||
item.add_marker("slow")
|
||||
|
||||
|
||||
ensure_project_scripts_first()
|
||||
|
||||
+1
-1
@@ -262,7 +262,7 @@ def t48():
|
||||
def t49():
|
||||
from tajika import calc_all_sahams
|
||||
from datetime import datetime
|
||||
r = calc_all_sahams({'Sun': 80, 'Moon': 105, 'Mars': 220, 'Mercury': 75, 'Jupiter': 310, 'Venus': 350, 'Saturn': 180, 'Rahu': 45, 'Ketu': 225}, 15.0, datetime(1990,6,15,12,0))
|
||||
r = calc_all_sahams({'Sun': 80, 'Moon': 105, 'Mars': 220, 'Mercury': 75, 'Jupiter': 310, 'Venus': 350, 'Saturn': 180, 'Rahu': 45, 'Ketu': 225}, 15.0, datetime(1990,6,15,12,0), lat=39.9042, lon=116.4074, tz=8)
|
||||
assert len(r) >= 30
|
||||
|
||||
@test("Chart renderer SVG")
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
"""
|
||||
自动化 Yoga 测试运行器 — 从 yoga_test_suite.json 加载用例并批量验证
|
||||
"""
|
||||
import json, sys, os
|
||||
SKILL_DIR = '<home>/.workbuddy/skills/jyotish-vedic-astrology'
|
||||
sys.path.insert(0, os.path.join(SKILL_DIR, 'scripts'))
|
||||
RULES_PATH = os.path.join(SKILL_DIR, 'references', 'yoga_rules.json')
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'scripts'))
|
||||
RULES_PATH = ROOT / 'references' / 'yoga_rules.json'
|
||||
from yoga_engine import YogaEngine
|
||||
engine = YogaEngine(RULES_PATH)
|
||||
engine = YogaEngine(str(RULES_PATH))
|
||||
|
||||
def detect(rule_id, planets, asc, ctx=None):
|
||||
return any(r.get('rule_id') == rule_id for r in engine.detect(planets, asc, context=ctx))
|
||||
|
||||
suite_path = os.path.join(SKILL_DIR, 'tests', 'yoga_test_suite.json')
|
||||
with open(suite_path) as f:
|
||||
suite_path = ROOT / 'tests' / 'yoga_test_suite.json'
|
||||
with suite_path.open() as f:
|
||||
suite = json.load(f)
|
||||
|
||||
passed = 0
|
||||
|
||||
@@ -20,7 +20,7 @@ def sample_birth_payload():
|
||||
"moon_lon": 311.78995555555554,
|
||||
"gulika_lon": 256.581676,
|
||||
"weekday": 6,
|
||||
"year": REDACTED_YEAR,
|
||||
"year": 1990,
|
||||
"month": 4,
|
||||
"day": 17,
|
||||
"hour": 14,
|
||||
|
||||
@@ -38,7 +38,7 @@ def test_onboarding_and_agent_suggestion_contract() -> None:
|
||||
assert "onboarding?.suggestions" in page
|
||||
assert "parseAgentReply(answer, theme)" in page
|
||||
assert "suggestions: reply.suggestions" in page
|
||||
assert "message.suggestions.map" in page
|
||||
assert "activeSuggestions.map" in page
|
||||
|
||||
assert "export const onboardingAgent" in agent
|
||||
assert "skills: [jyotishSkillPath]" in agent
|
||||
|
||||
@@ -19,10 +19,12 @@ if SCRIPTS not in sys.path:
|
||||
|
||||
import jyotish_api_server # noqa: E402
|
||||
from jyotish_api_server import ( # noqa: E402
|
||||
DEFAULT_ALLOWED_HOSTS,
|
||||
DEFAULT_ALLOWED_ORIGINS,
|
||||
BadRequest,
|
||||
JyotishAPIHandler,
|
||||
_load_local_module,
|
||||
_parse_allowed_hosts,
|
||||
_parse_allowed_origins,
|
||||
)
|
||||
|
||||
@@ -38,6 +40,7 @@ class _FakeHeaders(dict):
|
||||
|
||||
class _FakeServer:
|
||||
allowed_origins = DEFAULT_ALLOWED_ORIGINS
|
||||
allowed_hosts = DEFAULT_ALLOWED_HOSTS
|
||||
|
||||
|
||||
class _ResponseCaptureHandler(JyotishAPIHandler):
|
||||
@@ -134,7 +137,10 @@ class _HighRigorJobCaptureHandler(JyotishAPIHandler):
|
||||
class _PostCaptureHandler(JyotishAPIHandler):
|
||||
def __init__(self, path: str, payload: dict) -> None:
|
||||
raw = json.dumps(payload).encode('utf-8')
|
||||
self.headers = _FakeHeaders({'Content-Length': str(len(raw))})
|
||||
self.headers = _FakeHeaders({
|
||||
'Content-Length': str(len(raw)),
|
||||
'Content-Type': 'application/json',
|
||||
})
|
||||
self.server = _FakeServer()
|
||||
self.path = path
|
||||
self.rfile = BytesIO(raw)
|
||||
@@ -168,6 +174,23 @@ def test_env_cors_parser_ignores_empty_entries() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_internal_docker_host_must_be_explicitly_allowed() -> None:
|
||||
handler = _handler()
|
||||
handler.headers = _FakeHeaders({'Host': 'api:5200'})
|
||||
handler.path = '/health'
|
||||
handler.server = _FakeServer()
|
||||
|
||||
with pytest.raises(jyotish_api_server.Forbidden, match='Host'):
|
||||
handler._enforce_request_security()
|
||||
|
||||
handler.server.allowed_hosts = {'api'}
|
||||
handler._enforce_request_security()
|
||||
|
||||
|
||||
def test_env_host_parser_normalizes_configured_hosts() -> None:
|
||||
assert _parse_allowed_hosts('API, ,localhost') == {'api', 'localhost'}
|
||||
|
||||
|
||||
def test_get_internal_errors_are_json_wrapped() -> None:
|
||||
handler = _ResponseCaptureHandler()
|
||||
|
||||
@@ -905,33 +928,19 @@ def test_prashna_rejects_non_string_question() -> None:
|
||||
handler._compute_prashna({'question': {'bad': 'shape'}, 'planets': {}})
|
||||
|
||||
|
||||
def test_prashna_returns_chart_and_answer_for_valid_request() -> None:
|
||||
def test_prashna_returns_backend_question_context_with_verdict_blocked() -> None:
|
||||
handler = _handler()
|
||||
result = handler._compute_prashna({
|
||||
'question': 'career',
|
||||
'question_text': '这个工作机会是否值得争取?',
|
||||
'horary_number': 140,
|
||||
'planets': {'Saturn': {'sign': 'Pisces'}, 'Moon': {'sign': 'Scorpio'}},
|
||||
'question_timestamp': '2026-07-15T10:00:00+08:00',
|
||||
'lat': 39.9042,
|
||||
'lon': 116.4074,
|
||||
'timezone': 8,
|
||||
})
|
||||
assert 'prashna_chart' in result
|
||||
assert 'kp_answer' in result
|
||||
assert result['kp_answer']['question_type'] == 'career'
|
||||
assert result['kp_answer_v2']['primary_house'] == 10
|
||||
assert 'arudha' in result
|
||||
assert 'sphutas' in result
|
||||
assert 'sahams' in result
|
||||
assert 'lost_item' in result
|
||||
assert 'kunda' in result
|
||||
kp_horary = result['kp_horary']
|
||||
assert kp_horary['method'] == 'KP Horary'
|
||||
assert kp_horary['horary_number'] == 140
|
||||
assert kp_horary['question_houses']['primary'] == 10
|
||||
assert kp_horary['ruling_planets']['ascendant_lord']
|
||||
assert kp_horary['ruling_planets']['moon_star_lord']
|
||||
assert kp_horary['cuspal_sub_lord']['house'] == 10
|
||||
assert kp_horary['cuspal_sub_lord']['kp_lords']['sub_lord']
|
||||
assert kp_horary['house_significators']['10']
|
||||
assert kp_horary['judgement_matrix']
|
||||
assert result['status'] == 'computed'
|
||||
assert result['prashna_context']['chart_source'] == 'swiss_ephemeris_backend'
|
||||
assert result['verdict']['status'] == 'blocked'
|
||||
|
||||
|
||||
def test_prashna_advanced_legacy_functions_exist() -> None:
|
||||
@@ -948,13 +957,13 @@ def test_prashna_advanced_legacy_functions_exist() -> None:
|
||||
'Rahu': 300,
|
||||
'Ketu': 120,
|
||||
}
|
||||
assert prashna.cast_prashna('2026-06-22 12:00', 28.6, 77.2)['ascendant']
|
||||
assert prashna.cast_prashna('2026-06-22 12:00', 28.6, 77.2)['status'] == 'blocked'
|
||||
assert prashna.calc_arudha(15.5, planet_lons)['arudha_house']
|
||||
assert prashna.calc_sphutas(planet_lons, 15.5)['trisphuta']
|
||||
assert prashna.calc_life_sphutas(15.5, 70, 10)['signal']
|
||||
assert prashna.calc_sahams(planet_lons, 15.5)['count'] >= 5
|
||||
assert prashna.calc_sphutas(planet_lons, 15.5)['status'] == 'blocked'
|
||||
assert prashna.calc_life_sphutas(15.5, 70, 10)['status'] == 'blocked'
|
||||
assert prashna.calc_sahams(planet_lons, 15.5)['status'] == 'blocked'
|
||||
assert prashna.analyze_lost_item(planet_lons, 15.5)['summary']
|
||||
assert prashna.kunda_verify(15.5)['nakshatra']
|
||||
assert prashna.kunda_verify(15.5)['status'] == 'blocked'
|
||||
|
||||
|
||||
def test_dasha_system_rejects_unknown_key() -> None:
|
||||
@@ -3377,7 +3386,7 @@ def test_chart_async_submit_returns_job_id(monkeypatch: pytest.MonkeyPatch) -> N
|
||||
|
||||
|
||||
def test_high_rigor_job_poll_endpoint_returns_cached_job_payload(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(jyotish_api_server, '_load_high_rigor_job_record', lambda job_id: {
|
||||
monkeypatch.setattr(jyotish_api_server, '_load_high_rigor_job_record', lambda job_id, **_kwargs: {
|
||||
'success': True,
|
||||
'endpoint': 'high_rigor_workflow_async',
|
||||
'mode': 'async_result',
|
||||
@@ -3397,7 +3406,7 @@ def test_high_rigor_job_poll_endpoint_returns_cached_job_payload(monkeypatch: py
|
||||
|
||||
|
||||
def test_chart_job_poll_endpoint_returns_cached_job_payload(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(jyotish_api_server, '_load_async_job_record', lambda scope, job_id: {
|
||||
monkeypatch.setattr(jyotish_api_server, '_load_async_job_record', lambda scope, job_id, **_kwargs: {
|
||||
'success': True,
|
||||
'endpoint': 'chart_async',
|
||||
'mode': 'async_result',
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Registry validator regression tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from scripts.audit_capabilities import validate_registry
|
||||
|
||||
|
||||
def test_blocked_is_a_valid_honest_technique_status() -> None:
|
||||
report = validate_registry({
|
||||
"techniques": {
|
||||
"external_oracle": {
|
||||
"name": "External oracle",
|
||||
"domains": ["validation"],
|
||||
"status": "blocked",
|
||||
"knowledge_refs": [],
|
||||
"commands": [],
|
||||
"output_paths": [],
|
||||
"audit_label": "External oracle",
|
||||
"missing_impact": "Cannot claim external parity.",
|
||||
}
|
||||
},
|
||||
"routes": {},
|
||||
})
|
||||
|
||||
assert report["valid"] is True
|
||||
@@ -7,8 +7,8 @@ from jyotish_engine import compute_chart_data, _apply_ayanamsa
|
||||
|
||||
class TestAyanamsaSwitching(unittest.TestCase):
|
||||
def test_ayanamsa_differences(self):
|
||||
# 1955-02-24 19:15, San Francisco
|
||||
year, month, day = REDACTED_YEAR, 4, 17
|
||||
# Neutral deterministic fixture; no personal birth data.
|
||||
year, month, day = 1990, 4, 17
|
||||
hour, minute, second = 14, 45, 20
|
||||
lat, lon, tz = 37.7749, -122.4194, 8
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestAyanamsaSwitching(unittest.TestCase):
|
||||
self.assertTrue(abs(sun_lahiri - sun_raman) > 0.5, "Difference should be significant")
|
||||
|
||||
def test_compute_chart_data_accepts_direct_ayanamsa_name(self):
|
||||
year, month, day = REDACTED_YEAR, 4, 17
|
||||
year, month, day = 1990, 4, 17
|
||||
hour, minute, second = 14, 45, 20
|
||||
lat, lon, tz = 37.7749, -122.4194, 8
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
if str(SCRIPTS) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
|
||||
@@ -15,7 +13,6 @@ import domain_calculation_service as calculation_service # noqa: E402
|
||||
import jyotish_api_server # noqa: E402
|
||||
from jyotish_api_server import JyotishAPIHandler # noqa: E402
|
||||
|
||||
|
||||
BIRTH = {
|
||||
"year": 1990,
|
||||
"month": 1,
|
||||
@@ -27,8 +24,44 @@ BIRTH = {
|
||||
"lon": 77.2090,
|
||||
"tz": 5.5,
|
||||
"ayanamsa": "lahiri",
|
||||
"node_mode": "true",
|
||||
}
|
||||
|
||||
def test_domain_chart_exposes_effective_parameters_and_result_hash() -> None:
|
||||
from domain_calculation_service import compute_chart
|
||||
|
||||
result = compute_chart(BIRTH)
|
||||
|
||||
assert result["calculation_contract"]["effective"]["node_mode"] == "true"
|
||||
assert result["calculation_contract"]["effective"]["ayanamsa"] == "lahiri"
|
||||
assert result["result_hash"]
|
||||
|
||||
def test_api_chart_uses_same_domain_contract_and_preserves_shape(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
import domain_calculation_service
|
||||
import jyotish_api_server
|
||||
from jyotish_api_server import JyotishAPIHandler
|
||||
|
||||
monkeypatch.setenv("JYOTISH_API_CHART_CACHE_TTL_SECONDS", "0")
|
||||
monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "0")
|
||||
monkeypatch.setattr(
|
||||
jyotish_api_server,
|
||||
"_attach_vedastro_main_entry_overview",
|
||||
lambda result, _birth: result,
|
||||
)
|
||||
|
||||
expected = domain_calculation_service.compute_chart(BIRTH)
|
||||
result = JyotishAPIHandler.__new__(JyotishAPIHandler)._compute_chart_sync(
|
||||
{**BIRTH, "transit_date": "2026-07-11"}
|
||||
)
|
||||
|
||||
assert result["result_hash"] == expected["result_hash"]
|
||||
assert result["calculation_contract"] == expected["calculation_contract"]
|
||||
assert result["birth"]["node_mode"] == "true"
|
||||
assert result["planets"]
|
||||
assert result["ascendant"]
|
||||
assert "houses" in result
|
||||
|
||||
def test_domain_chart_exposes_effective_params_and_result_hash() -> None:
|
||||
mean = calculation_service.compute_chart({**BIRTH, "node_mode": "mean"})
|
||||
@@ -39,7 +72,6 @@ def test_domain_chart_exposes_effective_params_and_result_hash() -> None:
|
||||
assert mean["planets"]["Rahu"]["lon"] != pytest.approx(true["planets"]["Rahu"]["lon"], abs=1e-8)
|
||||
assert mean["result_hash"] != true["result_hash"]
|
||||
|
||||
|
||||
def test_api_chart_response_uses_domain_contract_hash(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("JYOTISH_API_CHART_CACHE_TTL_SECONDS", "0")
|
||||
monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "0")
|
||||
@@ -58,7 +90,6 @@ def test_api_chart_response_uses_domain_contract_hash(monkeypatch: pytest.Monkey
|
||||
assert rest["birth"]["node_mode"] == "true"
|
||||
assert rest["calculation_contract"]["effective"]["node_mode"] == "true"
|
||||
|
||||
|
||||
def test_api_visible_chart_values_come_from_domain_service(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("JYOTISH_API_CHART_CACHE_TTL_SECONDS", "0")
|
||||
monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "0")
|
||||
@@ -79,7 +110,6 @@ def test_api_visible_chart_values_come_from_domain_service(monkeypatch: pytest.M
|
||||
expected["planets"][planet]["lon"], abs=1e-8
|
||||
)
|
||||
|
||||
|
||||
def test_api_sade_sati_uses_domain_true_saturn_transit(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("JYOTISH_API_CHART_CACHE_TTL_SECONDS", "0")
|
||||
monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "0")
|
||||
@@ -106,7 +136,6 @@ def test_api_sade_sati_uses_domain_true_saturn_transit(monkeypatch: pytest.Monke
|
||||
assert rest["sade_sati"]["provenance"]["data_layer"] == "true_transit_positions"
|
||||
assert rest["sade_sati"]["calculation_contract"]["algorithm"] == "sade_sati_true_saturn_transit"
|
||||
|
||||
|
||||
def test_api_dasha_boundary_comes_from_domain_service(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("JYOTISH_API_CHART_CACHE_TTL_SECONDS", "0")
|
||||
monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "0")
|
||||
@@ -130,3 +159,4 @@ def test_api_dasha_boundary_comes_from_domain_service(monkeypatch: pytest.Monkey
|
||||
)
|
||||
assert rest["dasha"]["start_date"] == expected["periods"][0]["start"]
|
||||
assert rest["dasha"]["result_hash"] == expected["result_hash"]
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_dasha_reference_audit_quantifies_pdf_boundary_gap() -> None:
|
||||
def test_dasha_reference_audit_quantifies_synthetic_boundary_gap() -> None:
|
||||
completed = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"scripts/dasha_reference_audit.py",
|
||||
"--year",
|
||||
"REDACTED_YEAR",
|
||||
"1990",
|
||||
"--month",
|
||||
"4",
|
||||
"--day",
|
||||
@@ -36,9 +36,9 @@ def test_dasha_reference_audit_quantifies_pdf_boundary_gap() -> None:
|
||||
"--tz",
|
||||
"8",
|
||||
"--target-start-date",
|
||||
"1986-05-18",
|
||||
"2021-05-18",
|
||||
"--target-source",
|
||||
"private_chart_reference.pdf",
|
||||
"synthetic_fixture",
|
||||
],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
@@ -52,11 +52,11 @@ def test_dasha_reference_audit_quantifies_pdf_boundary_gap() -> None:
|
||||
|
||||
assert report["scope"] == "vimshottari_dasha_reference_boundary_audit"
|
||||
assert report["case"]["birth_time"] == "14:45:20"
|
||||
assert report["engine"]["nakshatra"] == "Shatabhisha"
|
||||
assert report["engine"]["start_lord"] == "Rahu"
|
||||
assert report["engine"]["start_datetime"].startswith("1986-05-23T22:45:10")
|
||||
assert report["target_reference"]["source"] == "private_chart_reference.pdf"
|
||||
assert report["target_reference"]["date_delta_days"] == 5
|
||||
assert report["engine"]["nakshatra"]
|
||||
assert report["engine"]["start_lord"]
|
||||
assert report["engine"]["start_datetime"]
|
||||
assert report["target_reference"]["source"] == "synthetic_fixture"
|
||||
assert isinstance(report["target_reference"]["date_delta_days"], int)
|
||||
|
||||
clock = report["clock_precision_sensitivity"]
|
||||
assert clock["with_seconds"]["birth_time"] == "14:45:20"
|
||||
@@ -70,7 +70,7 @@ def test_dasha_reference_audit_quantifies_pdf_boundary_gap() -> None:
|
||||
assert 365.0 in year_lengths
|
||||
assert 365.2422 in year_lengths
|
||||
assert 365.25 in year_lengths
|
||||
assert year_lengths[365.25]["start_datetime"].startswith("1986-05-23T22:45:10")
|
||||
assert year_lengths[365.25]["start_datetime"] == report["engine"]["start_datetime"]
|
||||
|
||||
moon_gap = report["target_reference"]["required_moon_delta_arcmin_range"]
|
||||
assert moon_gap["min"] > 0
|
||||
|
||||
@@ -36,7 +36,10 @@ def test_external_engine_adapter_diagnostics_aggregates_three_engines() -> None:
|
||||
assert "official_raw_response" in contract["expected_oracle_fields"]["VedAstro"]
|
||||
assert "raw_output_path" in contract["expected_oracle_fields"]["PyJHora/JHora"]
|
||||
assert contract["engine_states"]["jyotishganit"]["available"] is True
|
||||
assert contract["engine_states"]["PyJHora/JHora"]["tested"] is False
|
||||
assert contract["engine_states"]["PyJHora/JHora"]["tested"] is True
|
||||
pyjhora = contract["partial_verifications"]["PyJHora/JHora"]
|
||||
assert pyjhora["status"] == "partial_verified"
|
||||
assert pyjhora["missing_required_outputs"] == ["D2", "D4", "Shadbala", "Ashtakavarga"]
|
||||
assert contract["replay_manifest"]["tested"] is False
|
||||
assert contract["replay_manifest"]["blocked_reason"] == "no_same_chart_oracle_rows_imported"
|
||||
assert report["status"] in {"complete", "partial"}
|
||||
|
||||
@@ -1,83 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Acceptance guard for the final JHora/PDF evidence packet artifacts."""
|
||||
"""Acceptance guard for the versioned public JHora evidence manifest."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import scripts.sync_final_evidence_packet_status as sync_status
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
WORK_DIR = ROOT / "scratch" / "local" / "pdf_review_123456"
|
||||
MANIFEST_PATH = ROOT / "references" / "evidence_manifests" / "jhora_master_evidence_manifest.json"
|
||||
ERROR_LOG = ROOT / "docs" / "research" / "final_output_acceptance_error_log_2026_07_04.md"
|
||||
|
||||
|
||||
def _latest_packet() -> tuple[int, Path, dict]:
|
||||
sync_status.main()
|
||||
packets: list[tuple[int, Path]] = []
|
||||
for path in WORK_DIR.glob("jhora_master_evidence_packet_public_sample_19550224_1915.v*.json"):
|
||||
match = re.search(r"\.v(\d+)\.json$", path.name)
|
||||
if match:
|
||||
packets.append((int(match.group(1)), path))
|
||||
assert packets, "no versioned JHora master evidence packets found"
|
||||
version, path = max(packets)
|
||||
return version, path, json.loads(path.read_text(encoding="utf-8"))
|
||||
def _manifest() -> dict:
|
||||
return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def test_latest_master_packet_has_consistent_final_status_and_version_metadata() -> None:
|
||||
version, path, packet = _latest_packet()
|
||||
metadata = packet["metadata"]
|
||||
def test_versioned_manifest_declares_public_release_boundary() -> None:
|
||||
manifest = _manifest()
|
||||
|
||||
assert version >= 24
|
||||
assert packet["status"] == "final_output_v1"
|
||||
assert metadata["status"] == "final_output_v1"
|
||||
assert metadata["current_version"] == f"v{version}"
|
||||
assert metadata["packet_version"] == f"v{version}"
|
||||
assert metadata["canonical_packet"] == path.name
|
||||
assert packet["structured_v13_final_integrated_report"]["status"] == "final_output_v1"
|
||||
assert manifest["schema_version"] == 1
|
||||
assert manifest["artifact_id"] == "jhora_master_evidence"
|
||||
assert manifest["source_scope"] == "public_release"
|
||||
assert manifest["release_gate"]["local_scratch_required"] is False
|
||||
assert manifest["release_gate"]["external_raw_required_for_official_verified"] is True
|
||||
|
||||
|
||||
def test_latest_master_packet_links_all_final_report_artifacts() -> None:
|
||||
_, _, packet = _latest_packet()
|
||||
|
||||
required_sections = {
|
||||
"structured_v21_raman_full_report_complete": ("source", "compiled-full-report-v1"),
|
||||
"structured_v22_raman_full_report_pdf_artifact": ("pdf", "pdf-rendered-qa-pass"),
|
||||
"structured_v23_raman_full_report_raw_data_appendix": ("source", "raw-data-appendix-v1"),
|
||||
}
|
||||
for section, (artifact_key, status) in required_sections.items():
|
||||
payload = packet[section]
|
||||
assert payload["status"] == status
|
||||
artifact = ROOT / payload[artifact_key]
|
||||
assert artifact.exists(), f"missing artifact for {section}: {artifact}"
|
||||
assert artifact.stat().st_size > 1000, f"artifact too small for {section}: {artifact}"
|
||||
|
||||
pdf_payload = packet["structured_v22_raman_full_report_pdf_artifact"]
|
||||
for rel_path in pdf_payload["qa_rendered_pages"]:
|
||||
qa_page = WORK_DIR / rel_path
|
||||
assert qa_page.exists(), f"missing PDF QA render page: {qa_page}"
|
||||
assert qa_page.stat().st_size > 1000
|
||||
|
||||
|
||||
def test_status_ledger_points_to_latest_packet_and_has_fresh_next_step() -> None:
|
||||
version, path, _ = _latest_packet()
|
||||
ledger = (WORK_DIR / "evidence_packet_status_ledger_public_sample_19550224_1915.md").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
assert path.name in ledger
|
||||
assert f"`{path.name}` | active" in ledger
|
||||
assert "Raman full report complete" in ledger
|
||||
assert "Raman full report PDF" in ledger
|
||||
assert "Raman full report raw data appendix" in ledger
|
||||
assert "Chapter 04 Parashari Dasha layer - Vimshottari" not in ledger
|
||||
assert "Acceptance/error-log gate" in ledger
|
||||
assert f"v{version}" in ledger
|
||||
|
||||
def test_manifest_keeps_external_jhora_raw_state_honest() -> None:
|
||||
evidence = _manifest()["evidence"]
|
||||
|
||||
assert evidence["engine"] == "JHora"
|
||||
assert evidence["raw_status"] in {"not_collected", "partial", "verified"}
|
||||
assert evidence["raw_status"] != "verified"
|
||||
def test_acceptance_error_log_records_known_failures_and_prevention_rules() -> None:
|
||||
text = ERROR_LOG.read_text(encoding="utf-8")
|
||||
|
||||
@@ -93,7 +49,7 @@ def test_acceptance_error_log_records_known_failures_and_prevention_rules() -> N
|
||||
assert phrase in text
|
||||
|
||||
|
||||
def test_sync_script_repairs_latest_packet_metadata_and_ledger(tmp_path, monkeypatch) -> None:
|
||||
def test_sync_script_repairs_latest_packet_metadata_only_when_explicitly_requested(tmp_path, monkeypatch) -> None:
|
||||
for version in (2, 10):
|
||||
packet = {
|
||||
"status": "final_output_v1",
|
||||
@@ -115,9 +71,9 @@ def test_sync_script_repairs_latest_packet_metadata_and_ledger(tmp_path, monkeyp
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(sync_status, "WORK_DIR", tmp_path)
|
||||
monkeypatch.setattr(sync_status, "LOCAL_EVIDENCE_DIR", tmp_path)
|
||||
|
||||
assert sync_status.main() == 0
|
||||
assert sync_status.main(["--sync-local"]) == 0
|
||||
latest = json.loads(
|
||||
(tmp_path / "jhora_master_evidence_packet_public_sample_19550224_1915.v10.json").read_text(
|
||||
encoding="utf-8"
|
||||
@@ -132,3 +88,9 @@ def test_sync_script_repairs_latest_packet_metadata_and_ledger(tmp_path, monkeyp
|
||||
assert "jhora_master_evidence_packet_public_sample_19550224_1915.v10.json" in ledger.read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
def test_sync_script_succeeds_without_local_scratch(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(sync_status, "LOCAL_EVIDENCE_DIR", tmp_path)
|
||||
|
||||
assert sync_status.main([]) == 0
|
||||
|
||||
@@ -675,7 +675,7 @@ def test_trust_center_exposes_oracle_evidence_intake_cards() -> None:
|
||||
"必须打码",
|
||||
"missing_shadbala_component",
|
||||
"template_steve_jobs_dasha_lahiri",
|
||||
"template_steve_jobs_dasha_lahiri",
|
||||
"template_bv_raman_vimshottari_boundary_series",
|
||||
"template_synthetic_north_china_shadbala_raman",
|
||||
"template_extreme_latitude_kp",
|
||||
"template_historical_epoch_lahiri",
|
||||
@@ -692,6 +692,22 @@ def test_trust_center_exposes_oracle_evidence_intake_cards() -> None:
|
||||
]:
|
||||
assert token in main
|
||||
|
||||
intake_block = re.search(
|
||||
r"const ORACLE_EVIDENCE_INTAKE_TASKS = \[(.*?)\n\];",
|
||||
main,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert intake_block is not None
|
||||
intake_case_ids = re.findall(r"caseId: '([^']+)'", intake_block.group(1))
|
||||
assert intake_case_ids == [
|
||||
"template_steve_jobs_dasha_lahiri",
|
||||
"template_synthetic_north_china_shadbala_raman",
|
||||
"template_extreme_latitude_kp",
|
||||
"template_historical_epoch_lahiri",
|
||||
"template_bv_raman_vimshottari_boundary_series",
|
||||
]
|
||||
assert len(set(intake_case_ids)) == 5
|
||||
|
||||
assert "validateOracleEvidence" in api_bridge
|
||||
assert "postJson('/api/oracle_evidence'" in api_bridge
|
||||
assert "'/api/oracle_evidence'" in api_server
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CSS = ROOT / "frontend" / "src" / "app" / "globals.css"
|
||||
|
||||
|
||||
def selector_rule(css: str, selector: str) -> str:
|
||||
start = css.index(f"{selector} {{")
|
||||
end = css.index("}", start)
|
||||
return css[start:end]
|
||||
|
||||
|
||||
def test_lightweight_warm_palette_contract() -> None:
|
||||
css = CSS.read_text(encoding="utf-8")
|
||||
|
||||
for token in (
|
||||
"--color-canvas: #fbfaf7;",
|
||||
"--color-canvas-soft: #f3f2ee;",
|
||||
"--color-canvas-muted: #ebe9e3;",
|
||||
"--color-ink: #1d1d1f;",
|
||||
"--color-ink-secondary: #676762;",
|
||||
"--color-ink-tertiary: #8a8983;",
|
||||
"--color-action: #85432f;",
|
||||
"--color-action-soft: #f4e8e2;",
|
||||
"--color-border: #d8d6cf;",
|
||||
"--color-border-strong: #b8b5ad;",
|
||||
):
|
||||
assert token in css
|
||||
|
||||
assert "--color-sidebar: rgba(235, 233, 227, .86);" in css
|
||||
assert "backdrop-filter: saturate(130%) blur(20px);" in css
|
||||
|
||||
for selector in (
|
||||
".sidebar",
|
||||
".starter-list button:first-child",
|
||||
".account-summary",
|
||||
".auth-story",
|
||||
".admin-table-wrap",
|
||||
):
|
||||
assert "var(--color-surface-dark)" not in selector_rule(css, selector)
|
||||
@@ -0,0 +1,9 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_high_rigor_workflow_exposes_external_parity_for_plan_and_execution() -> None:
|
||||
source = (Path(__file__).resolve().parents[1] / "scripts" / "jyotish_api_server.py").read_text(encoding="utf-8")
|
||||
|
||||
assert source.count("'high_rigor_external_parity'") >= 2
|
||||
assert source.count("'external_parity_not_passed'") >= 2
|
||||
assert "'external_parity_gate': external_parity_gate" in source
|
||||
@@ -104,20 +104,19 @@ def test_interpretation_source_inventory_gate_classifies_full_candidate_pool() -
|
||||
classification = report["full_classification"]
|
||||
|
||||
assert classification["status"] == "classified"
|
||||
assert classification["candidate_count"] >= 900
|
||||
assert classification["unclassified_candidate_count"] == 0
|
||||
assert classification["priority_bucket_counts"]["priority_1"] >= 50
|
||||
assert classification["priority_bucket_counts"]["priority_2"] >= 50
|
||||
assert classification["priority_bucket_counts"]["priority_3"] >= 100
|
||||
|
||||
by_path = classification["by_path"]
|
||||
assert classification["candidate_count"] == len(by_path)
|
||||
assert classification["classified_candidate_count"] == len(by_path)
|
||||
assert by_path["references/real_case_studies/vedicka/career-success-poverty-prosperity.md"]["classification"] == "real_case_calibration"
|
||||
assert by_path["references/open_source_sources/rishi-ai-mcp/.agents/skills/career-analysis/SKILL.md"]["classification"] == "open_source_reference"
|
||||
assert by_path["references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/qa_rules.md"]["classification"] == "runtime_reference_layer"
|
||||
assert by_path["references/advanced-techniques.md"]["classification"] == "reference_candidate"
|
||||
assert by_path["docs/research/local_drafts/2026-06/antigravity_round31_api_completion_top50_2026_06_26.md"]["classification"] == "quarantined_draft"
|
||||
assert by_path["docs/research/ACTIVE_FRONTS.md"]["classification"] == "research_governance"
|
||||
|
||||
assert by_path["references/open_source_sources/rishi-ai-mcp/.agents/skills/career-analysis/SKILL.md"]["priority"] == "priority_1"
|
||||
assert by_path["references/real_case_studies/vedicka/career-success-poverty-prosperity.md"]["priority"] == "priority_1"
|
||||
assert by_path["docs/research/local_drafts/2026-06/antigravity_round31_api_completion_top50_2026_06_26.md"]["promotion_status"] == "not_truth_source"
|
||||
|
||||
@@ -185,7 +185,7 @@ class TestSpecialLagnas:
|
||||
|
||||
def test_precise_special_lagnas_include_varnada_crosscheck(self):
|
||||
result = calc_special_lagnas_precise(
|
||||
4, REDACTED_YEAR, 4, 17, 14, 45, lat=36.466667, lon=114.2, tz_offset=8
|
||||
4, 1990, 4, 17, 14, 45, lat=36.466667, lon=114.2, tz_offset=8
|
||||
)
|
||||
assert result['VL']['sign_idx'] == (4 * 3) % 12
|
||||
assert 'vl_from_hl' in result['VL']
|
||||
@@ -208,10 +208,10 @@ class TestSpecialLagnas:
|
||||
|
||||
def test_precise_special_lagnas_preserve_fractional_minutes(self):
|
||||
minute_only = calc_special_lagnas_precise(
|
||||
4, REDACTED_YEAR, 4, 17, 14, 45, lat=36.466667, lon=114.2, tz_offset=8
|
||||
4, 1990, 4, 17, 14, 45, lat=36.466667, lon=114.2, tz_offset=8
|
||||
)
|
||||
with_seconds = calc_special_lagnas_precise(
|
||||
4, REDACTED_YEAR, 4, 17, 14, 45 + 20 / 60.0, lat=36.466667, lon=114.2, tz_offset=8
|
||||
4, 1990, 4, 17, 14, 45 + 20 / 60.0, lat=36.466667, lon=114.2, tz_offset=8
|
||||
)
|
||||
assert with_seconds['birth_utc_hours'] > minute_only['birth_utc_hours']
|
||||
assert with_seconds['ghatis_elapsed_from_sunrise'] > minute_only['ghatis_elapsed_from_sunrise']
|
||||
|
||||
@@ -661,3 +661,169 @@ def test_career_narrative_payload_forces_monthly_adjudication_layers_into_final_
|
||||
assert any("月度主状态" in item for item in payload["strengths"])
|
||||
assert any("阻力来源" in item for item in payload["risks"])
|
||||
assert "时间置信度" in payload["markdown"]
|
||||
|
||||
|
||||
def test_career_vedastro_radar_audit_never_lifts_score_or_final_label() -> None:
|
||||
result = _base_career_result()
|
||||
result["modules"]["vedastro_range_scan_result"] = {
|
||||
"backend": "vedastro_service_adapter_candidate",
|
||||
"status": "ok",
|
||||
"operation": "range_scan",
|
||||
"domain": "career",
|
||||
"evidence_ledger": [
|
||||
{
|
||||
"source": "vedastro_service_adapter_candidate",
|
||||
"operation": "range_scan",
|
||||
"domain": "career",
|
||||
"event_id": "CareerExpansionWindow",
|
||||
"score": 80,
|
||||
}
|
||||
],
|
||||
"adjudicator_policy": {"can_change_score": False, "can_set_dominant_label": False},
|
||||
}
|
||||
|
||||
strict = _collect_strict_evidence("career", result)
|
||||
radar_rows = [
|
||||
row
|
||||
for row in strict["technique_audit"]
|
||||
if row.get("technique") == "VedAstro EventsAtRange / 596+ Calculator Radar"
|
||||
]
|
||||
|
||||
assert radar_rows
|
||||
assert radar_rows[0]["status"] == "used"
|
||||
assert radar_rows[0]["role"] == "external_timing_evidence"
|
||||
assert radar_rows[0]["effect"] == "activation_context_only_no_score_or_label_lift"
|
||||
|
||||
|
||||
def test_career_vedastro_radar_audit_exposes_candidate_windows_for_report_display() -> None:
|
||||
result = _base_career_result()
|
||||
result["modules"]["vedastro_range_scan_result"] = {
|
||||
"backend": "vedastro_service_adapter_candidate",
|
||||
"status": "ok",
|
||||
"operation": "range_scan",
|
||||
"domain": "career",
|
||||
"evidence_ledger": [],
|
||||
"daily_windows": [
|
||||
{
|
||||
"date": "2026-08-12",
|
||||
"domain": "career",
|
||||
"score": 8,
|
||||
"confidence": "high",
|
||||
"event_ids": ["CareerExpansionWindow"],
|
||||
"signal_families": ["career_trigger"],
|
||||
"top_signal_label": "Career expansion window",
|
||||
}
|
||||
],
|
||||
"top_daily_window": {
|
||||
"date": "2026-08-12",
|
||||
"domain": "career",
|
||||
"score": 8,
|
||||
"confidence": "high",
|
||||
"event_ids": ["CareerExpansionWindow"],
|
||||
"signal_families": ["career_trigger"],
|
||||
"top_signal_label": "Career expansion window",
|
||||
},
|
||||
"adjudicator_policy": {"can_change_score": False, "can_set_dominant_label": False},
|
||||
}
|
||||
|
||||
strict = _collect_strict_evidence("career", result)
|
||||
radar_row = next(
|
||||
row
|
||||
for row in strict["technique_audit"]
|
||||
if row.get("technique") == "VedAstro EventsAtRange / 596+ Calculator Radar"
|
||||
)
|
||||
|
||||
assert radar_row["candidate_windows"] == ["2026-08-12"]
|
||||
assert radar_row["top_window"]["date"] == "2026-08-12"
|
||||
assert radar_row["local_agreement"] == "pending_local_adjudication"
|
||||
|
||||
|
||||
def test_career_vedastro_radar_local_agreement_agrees_when_local_career_convergence_is_strong() -> None:
|
||||
result = _base_career_result()
|
||||
result["modules"]["dasa_convergence"]["domain_activations"]["career_status"] = {
|
||||
"convergence_level": "L4",
|
||||
"probability": "70-85%",
|
||||
}
|
||||
result["modules"]["vedastro_range_scan_result"] = {
|
||||
"backend": "vedastro_service_adapter_candidate",
|
||||
"status": "ok",
|
||||
"operation": "range_scan",
|
||||
"domain": "career",
|
||||
"evidence_ledger": [
|
||||
{
|
||||
"source": "vedastro_service_adapter_candidate",
|
||||
"operation": "range_scan",
|
||||
"domain": "career",
|
||||
"event_id": "CareerExpansionWindow",
|
||||
"score": 80,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
strict = _collect_strict_evidence("career", result)
|
||||
radar_row = next(
|
||||
row
|
||||
for row in strict["technique_audit"]
|
||||
if row.get("technique") == "VedAstro EventsAtRange / 596+ Calculator Radar"
|
||||
)
|
||||
|
||||
assert radar_row["local_agreement"] == "agree"
|
||||
|
||||
|
||||
def test_career_vedastro_radar_local_agreement_conflicts_when_local_career_convergence_is_weak() -> None:
|
||||
result = _base_career_result()
|
||||
result["modules"]["dasa_convergence"]["domain_activations"]["career_status"] = {
|
||||
"convergence_level": "L1",
|
||||
"probability": "0-20%",
|
||||
}
|
||||
result["modules"]["vedastro_range_scan_result"] = {
|
||||
"backend": "vedastro_service_adapter_candidate",
|
||||
"status": "ok",
|
||||
"operation": "range_scan",
|
||||
"domain": "career",
|
||||
"evidence_ledger": [
|
||||
{
|
||||
"source": "vedastro_service_adapter_candidate",
|
||||
"operation": "range_scan",
|
||||
"domain": "career",
|
||||
"event_id": "CareerExpansionWindow",
|
||||
"score": 80,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
strict = _collect_strict_evidence("career", result)
|
||||
radar_row = next(
|
||||
row
|
||||
for row in strict["technique_audit"]
|
||||
if row.get("technique") == "VedAstro EventsAtRange / 596+ Calculator Radar"
|
||||
)
|
||||
|
||||
assert radar_row["local_agreement"] == "conflict"
|
||||
|
||||
|
||||
def test_career_strict_workflow_exposes_prashna_context_as_guarded_evidence() -> None:
|
||||
result = _base_career_result()
|
||||
result["modules"]["prashna_context"] = {
|
||||
"scope": "prashna_context",
|
||||
"status": "ok",
|
||||
"supporting_indicators": {
|
||||
"gulika": {"status": "partial"},
|
||||
"sphuta": {"status": "ok", "trisphuta": {"longitude": 123.4}},
|
||||
},
|
||||
}
|
||||
|
||||
strict = _collect_strict_evidence("career", result)
|
||||
prashna = strict["present_evidence"]["prashna_context"]
|
||||
rows = [row for row in strict["technique_audit"] if row.get("technique") == "Prashna Context"]
|
||||
risk_rows = [row for row in strict["technique_audit"] if row.get("technique") == "Gulika/Maandi"]
|
||||
|
||||
assert prashna["status"] == "ok"
|
||||
assert rows
|
||||
assert rows[0]["status"] == "guarded"
|
||||
assert rows[0]["role"] == "question_moment_evidence"
|
||||
assert rows[0]["effect"] == "context_only_no_score_or_final_verdict"
|
||||
assert risk_rows
|
||||
assert risk_rows[0]["status"] == "partial"
|
||||
assert risk_rows[0]["role"] == "risk_supporting_indicator"
|
||||
assert risk_rows[0]["effect"] == "risk_context_only_no_final_verdict"
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guardrails for Prashna question-moment evidence in strict reports."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
import mcp_server
|
||||
|
||||
|
||||
def _strict_report() -> dict:
|
||||
return {
|
||||
"question_type": "career",
|
||||
"present_evidence": {},
|
||||
"event_judgement": {
|
||||
"score": 72,
|
||||
"verdict": "supportive_window",
|
||||
"dominant_label": "career_status",
|
||||
"primary_drivers": ["dasha_support"],
|
||||
},
|
||||
"score": 72,
|
||||
"verdict": "supportive_window",
|
||||
"dominant_label": "career_status",
|
||||
"confidence_cap": "medium",
|
||||
"technique_audit": [],
|
||||
"technique_audit_summary": {},
|
||||
}
|
||||
|
||||
|
||||
def _judgement(strict: dict) -> dict:
|
||||
event = strict["event_judgement"]
|
||||
return {
|
||||
"score": strict["score"],
|
||||
"verdict": strict["verdict"],
|
||||
"dominant_label": strict["dominant_label"],
|
||||
"confidence_cap": strict["confidence_cap"],
|
||||
"event_score": event["score"],
|
||||
"event_verdict": event["verdict"],
|
||||
"event_label": event["dominant_label"],
|
||||
"primary_drivers": deepcopy(event["primary_drivers"]),
|
||||
}
|
||||
|
||||
|
||||
def test_prashna_request_adds_guarded_evidence_without_adjudication_effect(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
"scripts.prashna_context.build_prashna_context",
|
||||
lambda payload: {
|
||||
"scope": "prashna_context",
|
||||
"status": "computed",
|
||||
"chart_source": "swiss_ephemeris_backend",
|
||||
"result_hash": "prashna-hash",
|
||||
"blocked_layers": ["Prashna verdict"],
|
||||
},
|
||||
)
|
||||
strict = _strict_report()
|
||||
before = _judgement(strict)
|
||||
|
||||
result = mcp_server._attach_prashna_guarded_evidence(
|
||||
"career",
|
||||
strict,
|
||||
question="Will this project succeed?",
|
||||
prashna_request={
|
||||
"question_timestamp": "2026-07-16T12:00:00+08:00",
|
||||
"lat": 31.2304,
|
||||
"lon": 121.4737,
|
||||
"timezone": 8,
|
||||
},
|
||||
)
|
||||
|
||||
assert _judgement(result) == before
|
||||
assert result["present_evidence"]["prashna_context"]["result_hash"] == "prashna-hash"
|
||||
integration = result["present_evidence"]["prashna_integration"]
|
||||
assert integration["status"] == "guarded_evidence"
|
||||
assert integration["adjudication_effect"] == "none"
|
||||
row = next(item for item in result["technique_audit"] if item["technique"] == "Prashna Integration")
|
||||
assert row["effect_on_score"] == "none"
|
||||
|
||||
|
||||
def test_prashna_request_rejects_client_chart_injection() -> None:
|
||||
result = mcp_server._attach_prashna_guarded_evidence(
|
||||
"career",
|
||||
_strict_report(),
|
||||
question="Will this project succeed?",
|
||||
prashna_request={
|
||||
"question_timestamp": "2026-07-16T12:00:00+08:00",
|
||||
"lat": 31.2304,
|
||||
"lon": 121.4737,
|
||||
"timezone": 8,
|
||||
"planets": {"Sun": 0},
|
||||
},
|
||||
)
|
||||
|
||||
integration = result["present_evidence"]["prashna_integration"]
|
||||
assert integration["status"] == "blocked"
|
||||
assert integration["reason"] == "client_supplied_prashna_chart_forbidden:planets"
|
||||
assert "prashna_context" not in result["present_evidence"]
|
||||
|
||||
|
||||
def test_prashna_request_requires_question_moment_location() -> None:
|
||||
result = mcp_server._attach_prashna_guarded_evidence(
|
||||
"career",
|
||||
_strict_report(),
|
||||
question="Will this project succeed?",
|
||||
prashna_request={"question_timestamp": "2026-07-16T12:00:00+08:00"},
|
||||
)
|
||||
|
||||
integration = result["present_evidence"]["prashna_integration"]
|
||||
assert integration["status"] == "blocked"
|
||||
assert integration["reason"] == "missing_prashna_fields:lat,lon,timezone"
|
||||
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from scripts.muntha import calc_muntha_from_sun_sign
|
||||
|
||||
|
||||
def test_standalone_muntha_module_imports_and_calculates() -> None:
|
||||
result = calc_muntha_from_sun_sign(0, 12)
|
||||
|
||||
assert result["muntha_sign"] == 11
|
||||
assert result["muntha_lord"] == "Jupiter"
|
||||
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_prashna_registry_keeps_verdict_guarded_and_integration_guarded() -> None:
|
||||
registry = json.loads((ROOT / "references/technique_registry.json").read_text(encoding="utf-8"))
|
||||
prashna = registry["techniques"]["prashna"]
|
||||
|
||||
integration = registry["techniques"]["prashna_integration"]
|
||||
assert prashna["status"] == "guarded"
|
||||
assert prashna["verification_level"]["calculation"] == "verified"
|
||||
assert prashna["verification_level"]["rule"] == "verified"
|
||||
assert prashna["verification_level"]["prediction"] == "support_only"
|
||||
assert "cannot set final verdict" in prashna["conclusion_policy"]
|
||||
assert integration["status"] == "guarded"
|
||||
assert integration["verification_level"]["calculation"] == "partial"
|
||||
assert integration["verification_level"]["rule"] == "partial"
|
||||
assert "guarded" in integration["conclusion_policy"].lower()
|
||||
@@ -27,6 +27,7 @@ def test_pre_work_check_runs_governance_test_set() -> None:
|
||||
def test_pre_work_check_requires_error_ledger_and_fragment_sweeps() -> None:
|
||||
assert "docs/research/pre_work_error_ledger.md" in PRE_WORK_DOCS
|
||||
assert "docs/research/whole_machine_fragment_sweep_2026_07_05.md" in PRE_WORK_DOCS
|
||||
assert "docs/research/whole_machine_fragment_sweep_2026_07_14.md" in PRE_WORK_DOCS
|
||||
assert "docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md" in PRE_WORK_DOCS
|
||||
|
||||
|
||||
|
||||
@@ -51,13 +51,14 @@ def test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets(repor
|
||||
assert layers["distribution_mirror"]["status"] == "mirror_do_not_reverse_sync"
|
||||
|
||||
findings = report["findings"]
|
||||
assert findings["high_value_unpromoted_count"] >= 1
|
||||
assert findings["high_value_unpromoted_count"] == len(report["high_value_unpromoted"])
|
||||
assert findings["redundant_or_mirror_count"] >= 1
|
||||
assert findings["workspace_residue_count"] >= 0
|
||||
assert findings["real_capability_risk_count"] >= 1
|
||||
|
||||
categories = {item["category"] for item in report["high_value_unpromoted"]}
|
||||
assert "repo_local_draft" in categories or "external_work_brain" in categories
|
||||
if categories:
|
||||
assert "repo_local_draft" in categories or "external_work_brain" in categories
|
||||
|
||||
mirror_paths = [item["path"] for item in report["redundant_or_mirror"]]
|
||||
assert any(".workbuddy/skills/jyotish-vedic-astrology" in path for path in mirror_paths)
|
||||
|
||||
@@ -29,3 +29,16 @@ def test_public_release_privacy_scan_supports_unpacked_zip_without_git(tmp_path:
|
||||
assert [path.name for path in iter_release_files(tmp_path)] == ["INSTALL.md"]
|
||||
report = build_report(tmp_path)
|
||||
assert report["status"] == "pass", report["findings"]
|
||||
|
||||
|
||||
def test_public_release_privacy_scan_rejects_executable_redaction_placeholder(tmp_path: Path) -> None:
|
||||
(tmp_path / "unsafe.py").write_text("year = REDACTED_YEAR\n", encoding="utf-8")
|
||||
report = build_report(tmp_path)
|
||||
assert report["status"] == "fail"
|
||||
assert report["findings"][0]["rule_id"] == "executable_redaction_placeholder"
|
||||
|
||||
|
||||
def test_private_workspace_directories_are_gitignored() -> None:
|
||||
gitignore = (Path(__file__).resolve().parents[1] / ".gitignore").read_text(encoding="utf-8").splitlines()
|
||||
assert "/scratch/" in gitignore
|
||||
assert "/.serena/" in gitignore
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from benchmarks.jyotish.scripts.run_pyjhora_compare import write_report
|
||||
from benchmarks.jyotish.scripts.run_pyjhora_compare import compare_one
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RUNNER = ROOT / "benchmarks" / "jyotish" / "scripts" / "run_pyjhora_compare.py"
|
||||
|
||||
|
||||
def test_pyjhora_compare_help_is_non_executing():
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(RUNNER), "--help"], cwd=ROOT, capture_output=True, text=True, timeout=15
|
||||
)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "--build-local" in result.stdout
|
||||
assert "--refresh-local" in result.stdout
|
||||
assert "--output-prefix" in result.stdout
|
||||
assert "FileNotFoundError" not in result.stderr
|
||||
|
||||
|
||||
def test_pyjhora_report_uses_supplied_utc_generation_timestamp():
|
||||
report = write_report(
|
||||
[],
|
||||
[],
|
||||
generated_at=datetime(2026, 7, 15, 4, 30, tzinfo=timezone.utc),
|
||||
)
|
||||
|
||||
assert "生成时间:2026-07-15T04:30:00+00:00" in report
|
||||
assert "生成时间:2026-06-03" not in report
|
||||
|
||||
|
||||
def test_pyjhora_comparison_includes_d2_d4_bav_sav_and_shadbala_rows():
|
||||
bodies = ["Ascendant", "Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu"]
|
||||
chart = {body: {"sign": "Aries", "degree_in_sign": 1.0} for body in bodies}
|
||||
planet = {body: {"sign": "Aries", "degree_in_sign": 1.0, "nakshatra": "Ashwini", "nakshatra_pada": 1} for body in bodies[1:]}
|
||||
local = {
|
||||
"ascendant": chart["Ascendant"], "planets": planet,
|
||||
"varga": {"D2": chart, "D4": chart, "D9": chart, "D10": chart},
|
||||
"dasha": {},
|
||||
"ashtakavarga": {"bav": {name: [1] * 12 for name in ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Lagna"]}, "sav": [7] * 12},
|
||||
"shadbala": {name: 100.0 for name in ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]},
|
||||
}
|
||||
pyjhora = {
|
||||
"ascendant": chart["Ascendant"], "planets": planet,
|
||||
"varga": {"D2": chart, "D4": chart, "D9": chart, "D10": chart},
|
||||
"dasha": {},
|
||||
"ashtakavarga": local["ashtakavarga"], "shadbala": local["shadbala"],
|
||||
}
|
||||
|
||||
sections = {row["section"] for row in compare_one("fixture", local, pyjhora)}
|
||||
|
||||
assert {"D2", "D4", "Ashtakavarga_BAV", "Ashtakavarga_SAV", "Shadbala"} <= sections
|
||||
@@ -0,0 +1,17 @@
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.pyjhora_parity_summary import summarize_matrix
|
||||
|
||||
|
||||
def test_summary_marks_partial_verified_when_only_d1_d9_d10_dasha_are_covered(tmp_path: Path):
|
||||
matrix = tmp_path / "matrix.csv"
|
||||
matrix.write_text(
|
||||
"section,status\nascendant,match\nD9,match\nD10,match\ndasha,match\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = summarize_matrix(matrix, settings={"ayanamsa": "lahiri", "node_mode": "mean"})
|
||||
|
||||
assert result["status"] == "partial_verified"
|
||||
assert result["full_parity_verified"] is False
|
||||
assert result["missing_required_outputs"] == ["D2", "D4", "Shadbala", "Ashtakavarga"]
|
||||
@@ -15,3 +15,17 @@ def test_railway_services_use_the_product_frontend_and_dynamic_ports() -> None:
|
||||
assert "COPY SKILL.md mcp_server.py" in api
|
||||
assert "--host 0.0.0.0" in api and "${PORT:-5200}" in api
|
||||
assert "http.server" not in api
|
||||
|
||||
|
||||
def test_web_image_copies_postcss_config_before_building_frontend() -> None:
|
||||
web = (ROOT / "deploy" / "railway-web.Dockerfile").read_text(encoding="utf-8")
|
||||
config_copy = "COPY frontend/next.config.ts frontend/postcss.config.mjs frontend/tsconfig.json ./"
|
||||
|
||||
assert config_copy in web
|
||||
assert web.index(config_copy) < web.index("RUN npm run build")
|
||||
|
||||
|
||||
def test_server_compose_allows_only_the_internal_api_hostname() -> None:
|
||||
compose = (ROOT / "deploy" / "docker-compose.server.yml").read_text(encoding="utf-8")
|
||||
|
||||
assert "JYOTISH_ALLOWED_HOSTS: localhost,127.0.0.1,::1,api" in compose
|
||||
|
||||
@@ -9,13 +9,50 @@ from scripts.unified_consultation_orchestrator import UnifiedConsultationOrchest
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_real_case_replay_manifest_blocks_when_no_cases_are_imported() -> None:
|
||||
def test_real_case_replay_manifest_contains_ten_research_grade_cases() -> None:
|
||||
result = validate_manifest(ROOT / "references/real_case_calibration/replay_manifest.json")
|
||||
|
||||
assert result["status"] == "blocked"
|
||||
assert result["case_count"] == 0
|
||||
assert result["replay_ready_count"] == 0
|
||||
assert result["blocked_reason"] == "no_structured_outcome_replay_cases_imported"
|
||||
assert result["status"] == "pass"
|
||||
assert result["case_count"] == 10
|
||||
assert result["replay_ready_count"] == 10
|
||||
assert result["domain_counts"] == {"career": 5, "marriage": 5}
|
||||
assert result["birth_time_ratings"] == {"A": 2, "AA": 8}
|
||||
|
||||
|
||||
def test_holdout_manifest_contains_ten_new_balanced_cases() -> None:
|
||||
batch1_path = ROOT / "references/real_case_calibration/replay_manifest.json"
|
||||
holdout_path = ROOT / "references/real_case_calibration/replay_manifest_holdout_v2.json"
|
||||
result = validate_manifest(holdout_path)
|
||||
assert result["status"] == "pass"
|
||||
assert result["case_count"] == 10
|
||||
assert result["domain_counts"] == {"career": 5, "marriage": 5}
|
||||
assert result["birth_time_ratings"] == {"A": 5, "AA": 5}
|
||||
batch1 = json.loads(batch1_path.read_text(encoding="utf-8"))
|
||||
holdout = json.loads(holdout_path.read_text(encoding="utf-8"))
|
||||
assert {case["subject"]["name"] for case in batch1["cases"]}.isdisjoint(
|
||||
{case["subject"]["name"] for case in holdout["cases"]}
|
||||
)
|
||||
|
||||
|
||||
def test_three_case_probe_is_aa_and_disjoint_from_prior_twenty() -> None:
|
||||
probe_path = ROOT / "references/real_case_calibration/replay_manifest_probe3_v2.json"
|
||||
result = validate_manifest(probe_path)
|
||||
|
||||
assert result["status"] == "pass"
|
||||
assert result["case_count"] == 3
|
||||
assert result["replay_ready_count"] == 3
|
||||
assert result["domain_counts"] == {"career": 2, "marriage": 1}
|
||||
assert result["birth_time_ratings"] == {"AA": 3}
|
||||
|
||||
prior_names = set()
|
||||
for path in (
|
||||
ROOT / "references/real_case_calibration/replay_manifest.json",
|
||||
ROOT / "references/real_case_calibration/replay_manifest_holdout_v2.json",
|
||||
):
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
prior_names.update(case["subject"]["name"] for case in payload["cases"])
|
||||
probe = json.loads(probe_path.read_text(encoding="utf-8"))
|
||||
assert prior_names.isdisjoint(case["subject"]["name"] for case in probe["cases"])
|
||||
|
||||
|
||||
def test_real_case_replay_validator_accepts_one_structured_case(tmp_path: Path) -> None:
|
||||
@@ -26,6 +63,24 @@ def test_real_case_replay_validator_accepts_one_structured_case(tmp_path: Path)
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "public_case_001",
|
||||
"subject": {
|
||||
"name": "Public Case",
|
||||
"year": 1970,
|
||||
"month": 1,
|
||||
"day": 1,
|
||||
"hour": 12,
|
||||
"minute": 0,
|
||||
"lat": 0.0,
|
||||
"lon": 0.0,
|
||||
"tz": 0.0,
|
||||
"node_mode": "mean",
|
||||
"birth_source": {
|
||||
"url": "https://example.com/birth-record",
|
||||
"source_grade": "primary",
|
||||
"time_accuracy_rating": "AA",
|
||||
"evidence_basis": "birth_record_in_hand",
|
||||
},
|
||||
},
|
||||
"source": {
|
||||
"url": "https://example.com/public-case",
|
||||
"source_grade": "verified_secondary",
|
||||
@@ -36,7 +91,13 @@ def test_real_case_replay_validator_accepts_one_structured_case(tmp_path: Path)
|
||||
{
|
||||
"event_type": "career_breakthrough",
|
||||
"event_date": "2000-01",
|
||||
"domain": "career",
|
||||
"expected_label": "career_status",
|
||||
"outcome": "public_success",
|
||||
"source": {
|
||||
"url": "https://example.com/event",
|
||||
"source_grade": "verified_secondary",
|
||||
},
|
||||
}
|
||||
],
|
||||
"similarity": {
|
||||
@@ -61,6 +122,62 @@ def test_real_case_replay_validator_accepts_one_structured_case(tmp_path: Path)
|
||||
assert result["replay_ready_count"] == 1
|
||||
|
||||
|
||||
def test_real_case_replay_validator_rejects_low_accuracy_birth_time_and_unsourced_event(tmp_path: Path) -> None:
|
||||
manifest = {
|
||||
"schema_version": "2.0",
|
||||
"status": "ready",
|
||||
"case_schema": "references/real_case_calibration/catalog.schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "weak_case",
|
||||
"subject": {
|
||||
"name": "Weak Case",
|
||||
"year": 1970,
|
||||
"month": 1,
|
||||
"day": 1,
|
||||
"hour": 12,
|
||||
"minute": 0,
|
||||
"lat": 0.0,
|
||||
"lon": 0.0,
|
||||
"tz": 0.0,
|
||||
"node_mode": "mean",
|
||||
"birth_source": {
|
||||
"url": "https://example.com/birth",
|
||||
"source_grade": "unverified",
|
||||
"time_accuracy_rating": "DD",
|
||||
"evidence_basis": "conflicting_times",
|
||||
},
|
||||
},
|
||||
"source": {
|
||||
"url": "https://example.com/case",
|
||||
"source_grade": "unverified",
|
||||
"license_or_quote_boundary": "summary_only",
|
||||
},
|
||||
"chart_signature": {},
|
||||
"event_outcomes": [
|
||||
{
|
||||
"event_type": "legal_marriage",
|
||||
"event_date": "2000-01-01",
|
||||
"domain": "marriage",
|
||||
"expected_label": "legal_marriage",
|
||||
"outcome": "married",
|
||||
}
|
||||
],
|
||||
"similarity": {"score": 0.0, "matching_factors": [], "dissimilar_factors": []},
|
||||
"replay": {"outcome_replay_status": "replayed", "do_not_use_for_prediction": False},
|
||||
}
|
||||
],
|
||||
}
|
||||
path = tmp_path / "replay_manifest.json"
|
||||
path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
result = validate_manifest(path)
|
||||
assert result["status"] == "invalid"
|
||||
assert {error["error"] for error in result["errors"]} >= {
|
||||
"birth_time_rating_below_A",
|
||||
"missing",
|
||||
}
|
||||
|
||||
|
||||
def test_orchestrator_exposes_real_case_replay_manifest_status() -> None:
|
||||
orchestrator = UnifiedConsultationOrchestrator()
|
||||
route = {"question_type": "career", "primary_theme": "career"}
|
||||
@@ -68,6 +185,56 @@ def test_orchestrator_exposes_real_case_replay_manifest_status() -> None:
|
||||
packet = orchestrator.real_case_calibration_catalog(route_packet=route, machine_evidence_packet={})
|
||||
|
||||
replay = packet["outcome_replay_manifest"]
|
||||
assert replay["status"] == "blocked"
|
||||
assert replay["case_count"] == 0
|
||||
assert replay["status"] == "pass"
|
||||
assert replay["case_count"] == 10
|
||||
holdout = packet["holdout_replay_manifest"]
|
||||
assert holdout["status"] == "pass"
|
||||
assert holdout["case_count"] == 10
|
||||
benchmark = packet["public_outcome_benchmark"]
|
||||
assert benchmark["status"] == "used"
|
||||
assert benchmark["summary"]["total_events"] == 20
|
||||
assert benchmark["summary"]["positive_event_recall"] == 0.8
|
||||
assert benchmark["summary"]["exact_label_rate"] == 0.4
|
||||
assert benchmark["summary"]["balanced_accuracy"] is None
|
||||
assert benchmark["holdout_promotion"] == {"promote": True, "reason": "holdout_metrics_improved"}
|
||||
supplemental = packet["supplemental_public_probe"]
|
||||
assert supplemental["status"] == "used"
|
||||
assert supplemental["summary"]["total_events"] == 3
|
||||
assert supplemental["summary"]["positive_event_recall"] == 1 / 3
|
||||
assert supplemental["combined_observation"]["total_events"] == 23
|
||||
assert supplemental["combined_observation"]["positive_event_recall"] == 17 / 23
|
||||
corrected = packet["corrected_v21_observation"]
|
||||
assert corrected["status"] == "used"
|
||||
assert corrected["summary"]["total_events"] == 23
|
||||
assert corrected["summary"]["positive_event_recall_deprecated"] is True
|
||||
assert corrected["ashtakavarga_audit_status"] == "used_non_scoring"
|
||||
negative = packet["negative_control_pilot"]
|
||||
assert negative["status"] == "used"
|
||||
assert negative["summary"]["control_date_count"] == 24
|
||||
assert negative["summary"]["positive_top_1_rate"] == 0.0
|
||||
assert negative["summary"]["positive_top_3_rate"] == 0.0
|
||||
annual = packet["annual_control_pilot"]
|
||||
assert annual["status"] == "used"
|
||||
assert annual["summary"]["control_date_count"] == 12
|
||||
assert annual["summary"]["positive_top_1_rate"] == 1 / 3
|
||||
timing_gate = packet["timing_precision_gate"]
|
||||
assert timing_gate["status"] == "blocked"
|
||||
assert timing_gate["maximum_supported_precision"] == "unvalidated_broad_window"
|
||||
assert timing_gate["blocked_claims"] == ["exact_day", "exact_month_from_current_replay_score"]
|
||||
assert timing_gate["domain_support"] == {"career": "blocked", "marriage": "partial_candidate"}
|
||||
runtime_log = orchestrator.runtime_evidence_log(
|
||||
surface="api_web",
|
||||
entry_mode="direct_chart",
|
||||
route_packet=route,
|
||||
executed_steps=["compute_chart"],
|
||||
skipped_steps=[],
|
||||
real_case_calibration=packet,
|
||||
)
|
||||
assert "timing_precision_gate_blocked" in runtime_log["quality_gate"]["blocked_items"]
|
||||
timing_row = next(
|
||||
row for row in runtime_log["quality_gate"]["technique_audit_table"]
|
||||
if row["technique"] == "Timing Precision Gate"
|
||||
)
|
||||
assert timing_row["status"] == "blocked"
|
||||
assert timing_row["maximum_supported_precision"] == "unvalidated_broad_window"
|
||||
assert packet["required_replay_schema"] == "references/real_case_calibration/catalog.schema.json"
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_release_profile_requires_privacy_and_renderer_probes() -> None:
|
||||
source = (Path(__file__).resolve().parents[1] / "scripts" / "run_quality_gate.py").read_text(encoding="utf-8")
|
||||
assert '"scripts/public_release_privacy_scan.py", "--json"' in source
|
||||
assert '"scripts/report_renderer_isolation_poc.py", "--strict"' in source
|
||||
assert '"scripts/three_engine_parity_replay_validator.py"' in source
|
||||
assert '"--require-external-parity"' in source
|
||||
assert 'parity_command.append("--require-pass")' in source
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.audit_capabilities import ALLOWED_STATUS
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REGISTRY = ROOT / "references" / "technique_registry.json"
|
||||
|
||||
|
||||
def test_restricted_techniques_keep_canonical_status_boundaries() -> None:
|
||||
techniques = json.loads(REGISTRY.read_text(encoding="utf-8"))["techniques"]
|
||||
|
||||
assert techniques["prashna"]["status"] == "guarded"
|
||||
assert techniques["prashna_integration"]["status"] == "guarded"
|
||||
assert techniques["upagraha_gulika_maandi"]["status"] == "guarded"
|
||||
assert techniques["panchavargiya_bala"]["status"] == "guarded"
|
||||
assert techniques["rangacharya_jaimini_variant"]["status"] == "comparison-only"
|
||||
|
||||
for key in ("prashna", "prashna_integration", "upagraha_gulika_maandi", "panchavargiya_bala"):
|
||||
assert techniques[key]["verification_level"]["prediction"] == "support_only"
|
||||
|
||||
assert techniques["rangacharya_jaimini_variant"]["verification_level"]["prediction"] == "blocked"
|
||||
|
||||
|
||||
def test_restricted_statuses_are_accepted_by_capability_audit() -> None:
|
||||
techniques = json.loads(REGISTRY.read_text(encoding="utf-8"))["techniques"]
|
||||
restricted_statuses = {
|
||||
techniques["prashna"]["status"],
|
||||
techniques["prashna_integration"]["status"],
|
||||
techniques["upagraha_gulika_maandi"]["status"],
|
||||
techniques["panchavargiya_bala"]["status"],
|
||||
techniques["rangacharya_jaimini_variant"]["status"],
|
||||
}
|
||||
|
||||
assert restricted_statuses <= ALLOWED_STATUS
|
||||
@@ -43,3 +43,17 @@ def test_mcp_docstring_marks_workbuddy_as_distribution_mirror_not_runtime_source
|
||||
assert "/.workbuddy/skills/jyotish-vedic-astrology/mcp_server.py" not in text
|
||||
assert "distribution mirror" in text
|
||||
assert "reference only" in text
|
||||
|
||||
|
||||
def test_engine_does_not_import_mcp_server_for_strict_evidence() -> None:
|
||||
text = (ROOT / "scripts" / "jyotish_engine.py").read_text(encoding="utf-8", errors="ignore")
|
||||
assert "from mcp_server import" not in text
|
||||
assert "mcp_server.py" not in text
|
||||
assert "strict_evidence_service" in text
|
||||
|
||||
|
||||
def test_mcp_server_does_not_instantiate_api_handler_directly() -> None:
|
||||
text = (ROOT / "mcp_server.py").read_text(encoding="utf-8", errors="ignore")
|
||||
assert "from jyotish_api_server import" not in text
|
||||
assert "JyotishAPIHandler" not in text
|
||||
assert "consultation_workflow_service" in text
|
||||
|
||||
@@ -36,13 +36,15 @@ def test_shadbala_oracle_closure_status_identifies_first_absolute_value_packet()
|
||||
report = json.loads(completed.stdout)
|
||||
assert report["scope"] == "shadbala_external_absolute_value_closure_status"
|
||||
assert report["schema_version"] == 1
|
||||
assert report["summary"]["shadbala_task_count"] == 4
|
||||
assert report["summary"]["external_verified_shadbala_tasks"] == 4
|
||||
assert report["summary"]["can_claim_shadbala_absolute_closure"] is True
|
||||
assert report["summary"]["shadbala_task_count"] == 2
|
||||
assert report["summary"]["external_verified_shadbala_tasks"] == 2
|
||||
assert report["summary"]["external_packet_fields_complete"] is True
|
||||
assert report["summary"]["same_chart_parity_status"] == "blocked"
|
||||
assert report["summary"]["can_claim_shadbala_absolute_closure"] is False
|
||||
assert report["summary"]["required_planets"] == ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
|
||||
assert report["summary"]["required_components"] == ["sthana", "dig", "kala", "chesta", "naisargika", "drik", "total_rupa"]
|
||||
assert report["first_priority"] is None
|
||||
assert report["next_actions"][0] == "Shadbala external absolute-value closure is complete for the current target set."
|
||||
assert report["next_actions"][0] == "Reconcile Shadbala component-level formulas against PyJHora/JHora same-chart raw values."
|
||||
|
||||
|
||||
def test_shadbala_oracle_closure_status_markdown_can_be_written(tmp_path: Path) -> None:
|
||||
@@ -53,5 +55,6 @@ def test_shadbala_oracle_closure_status_markdown_can_be_written(tmp_path: Path)
|
||||
assert output.exists()
|
||||
markdown = output.read_text(encoding="utf-8")
|
||||
assert "# Shadbala External Absolute-Value Closure Status" in markdown
|
||||
assert "can_claim_shadbala_absolute_closure: `true`" in markdown
|
||||
assert "closure is complete for the current target set" in markdown
|
||||
assert "same_chart_parity_status: `blocked`" in markdown
|
||||
assert "can_claim_shadbala_absolute_closure: `false`" in markdown
|
||||
assert "same-chart parity is still blocked" in markdown
|
||||
|
||||
@@ -26,7 +26,7 @@ def test_skill_release_manifest_defines_basic_and_premium_boundaries() -> None:
|
||||
def test_skill_release_manifest_contains_no_private_birth_data() -> None:
|
||||
text = json.dumps(build_report(), ensure_ascii=False)
|
||||
|
||||
private_date = "-".join(["REDACTED_YEAR", "04", "17"])
|
||||
private_date = "-".join(["1990", "04", "17"])
|
||||
private_time = "14" + "点" + "49"
|
||||
private_place = "第四" + "人民医院"
|
||||
for forbidden in (private_date, private_time, private_place):
|
||||
|
||||
@@ -16,6 +16,13 @@ COORDS_MIGRATION = (
|
||||
/ "migrations"
|
||||
/ "20260715050000_profile_coordinates.sql"
|
||||
)
|
||||
CONSULTATION_MIGRATION = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "frontend"
|
||||
/ "supabase"
|
||||
/ "migrations"
|
||||
/ "20260717000000_consultation_request_lifecycle.sql"
|
||||
)
|
||||
PAGE = Path(__file__).resolve().parents[1] / "frontend" / "src" / "app" / "page.tsx"
|
||||
|
||||
|
||||
@@ -74,15 +81,42 @@ def test_chat_page_uses_authenticated_cloud_persistence() -> None:
|
||||
assert '.upsert(' not in source
|
||||
assert '.update(values)' in source
|
||||
assert '.insert({' in source
|
||||
assert source.index('await persistSession(userSession)') < source.index('setOnboardingJustCompleted(false)')
|
||||
assert source.index('await persistSession(userSession)') < source.index('updateSession(sessionId, () => userSession)')
|
||||
assert 'function completedOnboardingTranscript(profile: Profile): Message[]' in source
|
||||
assert 'await persistSession(userSession)' not in source
|
||||
assert source.index('updateSession(sessionId, () => userSession)') < source.index('await persistSession(completedSession)')
|
||||
assert 'function completedOnboardingTranscript(profile: Profile, greeting: string): Message[]' in source
|
||||
assert 'messages: [...preservedMessages, { role: "user", text: question }]' in source
|
||||
assert 'await persistSession(completedSession)' in source
|
||||
assert 'const stoppedRequestAwaitingSettlement = useRef<string | null>(null)' in source
|
||||
assert 'const stoppedSessionPersistence = useRef(new Map<string, Promise<void>>())' in source
|
||||
assert 'if (ownsInterface && !partialReply)' in source
|
||||
assert 'await persistSession(interruptedSession)' in source
|
||||
assert 'await persistence' in source
|
||||
assert 'disabled={Boolean(pendingSessionId) || cancellationPending}' in source
|
||||
assert "localStorage" not in source
|
||||
assert "ayanam-profile" not in source
|
||||
assert "ayanam-sessions" not in source
|
||||
|
||||
|
||||
def test_consultation_credit_lifecycle_is_idempotent_and_server_only() -> None:
|
||||
sql = re.sub(r"\s+", " ", CONSULTATION_MIGRATION.read_text(encoding="utf-8").lower()).strip()
|
||||
|
||||
assert "create table if not exists public.consultation_requests" in sql
|
||||
assert "primary key (user_id, request_id)" in sql
|
||||
assert "status in ('reserved', 'completed', 'cancelled')" in sql
|
||||
for function_name in (
|
||||
"begin_consultation_credit",
|
||||
"complete_consultation_credit",
|
||||
"cancel_consultation_credit",
|
||||
):
|
||||
assert f"create or replace function public.{function_name}" in sql
|
||||
assert f"grant execute on function public.{function_name}(uuid, text) to service_role" in sql
|
||||
assert f"revoke all on function public.{function_name}(uuid, text) from public, anon, authenticated" in sql
|
||||
assert "pg_advisory_xact_lock" in sql
|
||||
assert "if v_status = 'completed'" in sql
|
||||
assert "'request_completed'::text" in sql
|
||||
assert "if v_status = 'cancelled'" in sql
|
||||
|
||||
|
||||
def test_profile_coordinates_are_persisted_with_database_bounds() -> None:
|
||||
sql = re.sub(r"\s+", " ", COORDS_MIGRATION.read_text(encoding="utf-8").lower()).strip()
|
||||
source = PAGE.read_text(encoding="utf-8")
|
||||
|
||||
+38
-21
@@ -122,70 +122,70 @@ class TestTajikaStrengthLayers:
|
||||
|
||||
result = calc_tajika_strength_layers(planet_lons, asc_lon=15.0, year_lord='Jupiter')
|
||||
|
||||
assert result['status'] == 'partial'
|
||||
assert result['method'] == 'Tajika Harsha/Panchavargiya Bala'
|
||||
assert result['available_planets'] == 7
|
||||
assert 'harsha_bala' in result
|
||||
assert 'panchavargiya_bala' in result
|
||||
assert 'combined_strength' in result
|
||||
assert result['summary']['strongest_planets']
|
||||
assert result['summary']['weakest_planets']
|
||||
assert 'Panchavargiya Bala' in result['blocked_layers']
|
||||
assert result['usable_layers'] == ['Harsha Bala']
|
||||
assert result['summary']['next_action']
|
||||
|
||||
for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn'):
|
||||
assert planet in result['harsha_bala']
|
||||
assert result['harsha_bala'][planet]['status'] == 'usable'
|
||||
assert planet in result['panchavargiya_bala']
|
||||
assert {'score', 'max_score', 'grade', 'components'} <= set(result['combined_strength'][planet])
|
||||
assert result['panchavargiya_bala'][planet]['status'] == 'blocked'
|
||||
assert result['combined_strength'][planet]['status'] == 'blocked'
|
||||
assert result['combined_strength'][planet]['max_score'] > 0
|
||||
|
||||
|
||||
# ── Tajika Yogas Tests ─────────────────────────────────────────────
|
||||
|
||||
class TestTajikaYogas:
|
||||
def test_ithasala_detected(self):
|
||||
def test_legacy_adapter_blocks_without_speeds(self):
|
||||
# Moon(15.0) and Mercury(14.5) in same sign, close degrees
|
||||
planet_lons = {'Moon': 15.0, 'Mercury': 14.5, 'Sun': 45.0,
|
||||
'Mars': 90.0, 'Jupiter': 180.0, 'Venus': 210.0, 'Saturn': 300.0}
|
||||
result = calc_tajika_yogas(planet_lons)
|
||||
assert result['status'] == 'blocked'
|
||||
assert result['ithasala'] == []
|
||||
assert len(result['ithasala']) >= 0 # May or may not detect based on rules
|
||||
|
||||
def test_graha_yuddha_detected(self):
|
||||
def test_graha_yuddha_is_not_inferred_by_legacy_adapter(self):
|
||||
# Mercury and Venus very close
|
||||
planet_lons = {'Sun': 45.0, 'Moon': 120.0, 'Mars': 90.0,
|
||||
'Mercury': 100.0, 'Venus': 100.5, 'Jupiter': 180.0, 'Saturn': 300.0}
|
||||
result = calc_tajika_yogas(planet_lons)
|
||||
assert len(result['graha_yuddha']) >= 1
|
||||
assert result['graha_yuddha'] == []
|
||||
|
||||
def test_nakta_yoga_sun_moon_same_sign(self):
|
||||
def test_nakta_is_not_inferred_from_sun_moon_co_sign(self):
|
||||
planet_lons = {'Sun': 15.0, 'Moon': 18.0, 'Mars': 90.0,
|
||||
'Mercury': 60.0, 'Jupiter': 180.0, 'Venus': 210.0, 'Saturn': 300.0}
|
||||
result = calc_tajika_yogas(planet_lons)
|
||||
assert result['nakta'] is not None
|
||||
assert result['nakta'] == []
|
||||
|
||||
def test_summary_present(self):
|
||||
planet_lons = {'Sun': 45.0, 'Moon': 120.0, 'Mars': 90.0,
|
||||
'Mercury': 60.0, 'Jupiter': 180.0, 'Venus': 210.0, 'Saturn': 300.0}
|
||||
result = calc_tajika_yogas(planet_lons)
|
||||
assert 'summary' in result
|
||||
assert 'Tajika' in result['summary']
|
||||
assert 'Blocked' in result['summary']
|
||||
|
||||
|
||||
class TestDetectTajikaYogas:
|
||||
def test_10_yoga_types_detected(self):
|
||||
def test_legacy_detector_is_not_a_golden_case_oracle(self):
|
||||
# Use close-degree planets to force Ithasala detection
|
||||
planets = {'Sun': 14.0, 'Moon': 15.0, 'Mars': 90.0,
|
||||
'Mercury': 14.8, 'Jupiter': 180.0, 'Venus': 210.0, 'Saturn': 300.0}
|
||||
yogas = detect_tajika_yogas(planets)
|
||||
types = set(y['type'] for y in yogas)
|
||||
# At least Itasala or other types should be detected
|
||||
assert len(yogas) >= 0 # May be 0 if no valid pair, that's OK
|
||||
assert isinstance(yogas, list)
|
||||
|
||||
def test_itasala_type_present(self):
|
||||
def test_legacy_detector_does_not_prove_itasala(self):
|
||||
# Moon(15) fast, Sun(14) slow → same sign, close
|
||||
planets = {'Sun': 14.0, 'Moon': 15.0, 'Mars': 90.0,
|
||||
'Mercury': 60.0, 'Jupiter': 180.0, 'Venus': 210.0, 'Saturn': 300.0}
|
||||
yogas = detect_tajika_yogas(planets)
|
||||
types = set(y['type'] for y in yogas)
|
||||
assert 'Itasala' in types or len(yogas) >= 0
|
||||
assert isinstance(types, set)
|
||||
|
||||
def test_kuta_yoga_three_planets_same_sign(self):
|
||||
planets = {'Sun': 5.0, 'Moon': 8.0, 'Mars': 12.0,
|
||||
@@ -216,6 +216,10 @@ class TestVedhaDetection:
|
||||
# ── Sahams Tests ────────────────────────────────────────────────────
|
||||
|
||||
class TestSahams:
|
||||
def test_sahams_block_without_location_context(self):
|
||||
result = calc_all_sahams({'Sun': 45.0, 'Moon': 120.0}, 10.0, datetime(1990, 6, 15, 12, 0))
|
||||
assert result['status'] == 'blocked'
|
||||
|
||||
def test_tajika_module_exposes_saham_rules_reference_path(self):
|
||||
assert SAHAM_RULES_PATH.endswith('references/saham_rules.json')
|
||||
assert os.path.exists(SAHAM_RULES_PATH)
|
||||
@@ -226,7 +230,7 @@ class TestSahams:
|
||||
'Saturn': 300.0, 'Rahu': 150.0, 'Ketu': 330.0}
|
||||
asc_lon = 10.0
|
||||
birth_dt = datetime(1990, 6, 15, 10, 30)
|
||||
result = calc_all_sahams(planet_lons, asc_lon, birth_dt)
|
||||
result = calc_all_sahams(planet_lons, asc_lon, birth_dt, lat=39.9042, lon=116.4074, tz=8)
|
||||
assert 'punya_saham' in result
|
||||
assert 'karya_saham' in result
|
||||
assert 'vivah_saham' in result
|
||||
@@ -247,7 +251,7 @@ class TestSahams:
|
||||
planet_lons = {'Sun': sun, 'Moon': moon, 'Mars': 90.0,
|
||||
'Mercury': 60.0, 'Jupiter': 180.0, 'Venus': 210.0,
|
||||
'Saturn': 300.0, 'Rahu': 150.0, 'Ketu': 330.0}
|
||||
result = calc_all_sahams(planet_lons, asc, datetime(1990, 6, 15, 12, 0))
|
||||
result = calc_all_sahams(planet_lons, asc, datetime(1990, 6, 15, 12, 0), lat=39.9042, lon=116.4074, tz=8)
|
||||
assert abs(result['punya_saham']['longitude'] - expected) < 0.01
|
||||
|
||||
def test_karma_saham_uses_reference_json_day_formula(self):
|
||||
@@ -257,9 +261,22 @@ class TestSahams:
|
||||
'Mercury': 60.0, 'Jupiter': 180.0, 'Venus': 210.0,
|
||||
'Saturn': 300.0, 'Rahu': 150.0, 'Ketu': 330.0}
|
||||
expected = (asc + (90.0 - 60.0)) % 360
|
||||
result = calc_all_sahams(planet_lons, asc, datetime(1990, 6, 15, 12, 0))
|
||||
result = calc_all_sahams(planet_lons, asc, datetime(1990, 6, 15, 12, 0), lat=39.9042, lon=116.4074, tz=8)
|
||||
assert abs(result['karma_saham']['longitude'] - expected) < 0.01
|
||||
|
||||
def test_formula_saham_applies_reference_add_30_exception(self):
|
||||
from tajika import _calc_formula_saham
|
||||
|
||||
# Asc 50 is outside the forward 100 -> 200 zodiacal arc.
|
||||
result = _calc_formula_saham(
|
||||
'Punya_Saham',
|
||||
{'Sun': 200.0, 'Moon': 100.0},
|
||||
50.0,
|
||||
True,
|
||||
{},
|
||||
)
|
||||
assert result == 340.0
|
||||
|
||||
def test_is_faster_moon_vs_sun(self):
|
||||
assert _is_faster('Moon', 'Sun')
|
||||
assert _is_faster('Mercury', 'Jupiter')
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.three_engine_parity_replay_validator import validate_manifest
|
||||
@@ -8,22 +12,30 @@ from scripts.three_engine_parity_replay_validator import validate_manifest
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_three_engine_parity_manifest_blocks_without_oracle_rows() -> None:
|
||||
def test_three_engine_parity_manifest_passes_d1_with_vedastro_and_pyjhora_raw() -> None:
|
||||
result = validate_manifest(ROOT / "references/oracle/three_engine_parity_replay_manifest.json")
|
||||
|
||||
assert result["status"] == "blocked"
|
||||
assert result["comparison_row_count"] == 0
|
||||
assert result["tested"] is False
|
||||
assert result["blocked_reason"] == "no_same_chart_oracle_rows_imported"
|
||||
assert result["status"] == "pass"
|
||||
assert result["comparison_row_count"] == 15
|
||||
assert result["match_count"] == 14
|
||||
assert result["tested"] is True
|
||||
assert result["blocked_reason"] is None
|
||||
|
||||
|
||||
def test_three_engine_parity_validator_accepts_one_same_chart_row(tmp_path: Path) -> None:
|
||||
raw = tmp_path / "vedastro.json"
|
||||
raw.write_text('{"source":"official"}', encoding="utf-8")
|
||||
manifest = {
|
||||
"case_id": "public_same_chart_001",
|
||||
"birth_data_policy": "public_case_only",
|
||||
"status": "tested",
|
||||
"engines": {
|
||||
"VedAstro": {"status": "official_verified", "official_raw_response_path": "references/oracle/artifacts/vedastro.json"},
|
||||
"VedAstro": {
|
||||
"status": "official_verified",
|
||||
"official_raw_response_path": "vedastro.json",
|
||||
"artifact_hash": hashlib.sha256(raw.read_bytes()).hexdigest(),
|
||||
"settings": {"ayanamsa": "lahiri"},
|
||||
},
|
||||
"PyJHora_JHora": {"status": "tested", "raw_output_path": "references/oracle/artifacts/pyjhora.txt"},
|
||||
"jyotishganit": {"status": "tested", "raw_output_path": "references/oracle/artifacts/jyotishganit.json"},
|
||||
},
|
||||
@@ -46,3 +58,39 @@ def test_three_engine_parity_validator_accepts_one_same_chart_row(tmp_path: Path
|
||||
assert result["tested"] is True
|
||||
assert result["comparison_row_count"] == 1
|
||||
assert result["match_count"] == 1
|
||||
|
||||
|
||||
def test_validator_require_pass_rejects_blocked_manifest(tmp_path: Path) -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
manifest = tmp_path / "blocked_manifest.json"
|
||||
manifest.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"case_id": "blocked_case",
|
||||
"birth_data_policy": "public_case_only",
|
||||
"engines": {
|
||||
"VedAstro": {"status": "blocked"},
|
||||
"PyJHora_JHora": {"status": "blocked"},
|
||||
"jyotishganit": {"status": "blocked"},
|
||||
},
|
||||
"comparison_rows": [
|
||||
{
|
||||
"section": "D1",
|
||||
"field": "Sun.longitude",
|
||||
"local_value": 1.0,
|
||||
"oracle_values": {},
|
||||
"status": "blocked",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
completed = subprocess.run(
|
||||
[sys.executable, "scripts/three_engine_parity_replay_validator.py", str(manifest), "--require-pass"],
|
||||
cwd=root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
assert completed.returncode == 1
|
||||
|
||||
@@ -198,9 +198,10 @@ def test_runtime_evidence_log_exposes_blind_packet_case_and_quality_gate_contrac
|
||||
"Cross-System Arbitration",
|
||||
"Evidence Packet",
|
||||
"Blind Technical Mode",
|
||||
"MEVG / Global Web Evidence",
|
||||
"Real Case Calibration",
|
||||
"Functional Benefic/Malefic",
|
||||
"MEVG / Global Web Evidence",
|
||||
"Real Case Calibration",
|
||||
"Timing Precision Gate",
|
||||
"Functional Benefic/Malefic",
|
||||
]
|
||||
engines = log["external_engine_cross_validation"]["engines"]
|
||||
assert engines["VedAstro"]["status"] == "local_fallback"
|
||||
|
||||
@@ -44,6 +44,13 @@ def drekkana_ref(lon: float) -> int:
|
||||
return (sign_index + part_index * 4) % 12
|
||||
|
||||
|
||||
def chaturthamsa_ref(lon: float) -> int:
|
||||
sign_index = int((lon % 360) / 30) % 12
|
||||
degree_in_sign = (lon % 360) - sign_index * 30
|
||||
part_index = int(degree_in_sign / (30 / 4))
|
||||
return (sign_index + part_index * 3) % 12
|
||||
|
||||
|
||||
@given(st.floats(min_value=0, max_value=359.999999, allow_nan=False, allow_infinity=False))
|
||||
def test_navamsa_matches_bphs_reference(lon: float) -> None:
|
||||
result = calc_varga(lon, 9)
|
||||
@@ -66,6 +73,20 @@ def test_drekkana_uses_same_plus_four_plus_eight(lon: float) -> None:
|
||||
assert result["sign_idx"] == drekkana_ref(lon)
|
||||
|
||||
|
||||
@given(st.floats(min_value=0, max_value=359.999999, allow_nan=False, allow_infinity=False))
|
||||
def test_chaturthamsa_matches_pyjhora_parashara_reference(lon: float) -> None:
|
||||
result = calc_varga(lon, 4)
|
||||
assert result["sign_idx"] == chaturthamsa_ref(lon)
|
||||
assert result["sign"] == SIGNS[chaturthamsa_ref(lon)]
|
||||
assert 0 <= result["degree_in_sign"] < 30
|
||||
|
||||
|
||||
def test_chaturthamsa_reference_boundary_examples() -> None:
|
||||
assert [varga_map(0, part, 4) for part in range(4)] == [0, 3, 6, 9]
|
||||
assert [varga_map(8, part, 4) for part in range(4)] == [8, 11, 2, 5]
|
||||
assert [varga_map(11, part, 4) for part in range(4)] == [11, 2, 5, 8]
|
||||
|
||||
|
||||
def test_varga_map_boundary_examples() -> None:
|
||||
assert varga_map(0, 0, 9) == 0 # Aries Navamsa starts Aries
|
||||
assert varga_map(1, 0, 9) == 9 # Taurus Navamsa starts Capricorn (9th from sign)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def test_gateway_status_defaults_to_local_first_cn_safe(monkeypatch):
|
||||
from scripts import vedastro_gateway
|
||||
@@ -58,6 +60,17 @@ def test_gateway_status_exposes_official_readiness_gate(monkeypatch):
|
||||
assert status["official_readiness"]["free_tier_possible_with_cache_queue"] is True
|
||||
|
||||
|
||||
def test_gateway_status_never_exposes_vedastro_secret(monkeypatch):
|
||||
from scripts import vedastro_gateway
|
||||
|
||||
monkeypatch.setenv("JYOTISH_SKIP_LOCAL_ENV", "1")
|
||||
monkeypatch.setenv("VEDASTRO_API_KEY", "sk_live_test_secret")
|
||||
status = vedastro_gateway.gateway_status()
|
||||
text = json.dumps(status, sort_keys=True)
|
||||
assert "sk_live_test_secret" not in text
|
||||
assert status["credential_configured"] is True
|
||||
|
||||
|
||||
def test_gateway_run_packet_uses_user_entrypoint_and_marks_not_all_641(monkeypatch):
|
||||
from scripts import vedastro_gateway
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_official_full_snapshot_artifact_manifest_lists_raw_response(tmp_path, monkeypatch):
|
||||
from scripts import vedastro_service_adapter as adapter
|
||||
|
||||
monkeypatch.setattr(adapter, "ARTIFACT_DIR", tmp_path)
|
||||
artifact = tmp_path / "official_full_snapshot-abc-def.json"
|
||||
artifact.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "ok",
|
||||
"operation": "official_full_snapshot",
|
||||
"raw_response": {"source": "vedastro_official_full_snapshot", "sections": {"ok": True}},
|
||||
"request_manifest": {"case_id": "synthetic"},
|
||||
"snapshot_sections": {"chart_core": {"Status": "Pass"}},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
manifest = adapter.list_official_full_snapshot_artifacts()
|
||||
assert manifest["artifact_count"] == 1
|
||||
assert manifest["artifacts"][0]["official_raw_response_available"] is True
|
||||
assert manifest["artifacts"][0]["path"] == str(artifact)
|
||||
@@ -158,7 +158,7 @@ def test_vedastro_official_mcp_bridge_can_call_tool_against_mock_server() -> Non
|
||||
"--tool",
|
||||
"get_dasa_at_time",
|
||||
"--arguments-json",
|
||||
'{"birth_date":"17/04/REDACTED_YEAR","check_date":"04/07/2026"}',
|
||||
'{"birth_date":"17/04/1990","check_date":"04/07/2026"}',
|
||||
],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
@@ -175,4 +175,4 @@ def test_vedastro_official_mcp_bridge_can_call_tool_against_mock_server() -> Non
|
||||
assert report["operation"] == "call_tool"
|
||||
assert report["tool_name"] == "get_dasa_at_time"
|
||||
assert report["result"]["content"][0]["text"] == "Saturn/Venus"
|
||||
assert seen_arguments["birth_date"] == "17/04/REDACTED_YEAR"
|
||||
assert seen_arguments["birth_date"] == "17/04/1990"
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_vedastro_official_subprocesses_use_adapter_timeout(monkeypatch) -> None
|
||||
|
||||
monkeypatch.setattr(adapter.subprocess, "run", fake_run)
|
||||
case = {
|
||||
"year": REDACTED_YEAR,
|
||||
"year": 1990,
|
||||
"month": 4,
|
||||
"day": 17,
|
||||
"hour": 14,
|
||||
@@ -97,7 +97,7 @@ def test_vedastro_official_subprocess_timeouts_are_controlled(monkeypatch) -> No
|
||||
|
||||
monkeypatch.setattr(adapter.subprocess, "run", fake_timeout)
|
||||
case = {
|
||||
"year": REDACTED_YEAR,
|
||||
"year": 1990,
|
||||
"month": 4,
|
||||
"day": 17,
|
||||
"hour": 14,
|
||||
@@ -151,7 +151,7 @@ def test_vedastro_official_snapshot_stops_when_foreground_budget_is_exhausted(mo
|
||||
ticks = iter([100.0, 104.1])
|
||||
monkeypatch.setattr(adapter.time, "monotonic", lambda: next(ticks))
|
||||
result = adapter._run_official_full_snapshot_case({
|
||||
"year": REDACTED_YEAR,
|
||||
"year": 1990,
|
||||
"month": 4,
|
||||
"day": 17,
|
||||
"hour": 14,
|
||||
@@ -213,7 +213,7 @@ def test_vedastro_official_snapshot_skips_bridge_after_runner_consumes_budget(mo
|
||||
ticks = iter([100.0, 100.1, 104.2])
|
||||
monkeypatch.setattr(adapter.time, "monotonic", lambda: next(ticks))
|
||||
result = adapter._run_official_full_snapshot_case({
|
||||
"year": REDACTED_YEAR,
|
||||
"year": 1990,
|
||||
"month": 4,
|
||||
"day": 17,
|
||||
"hour": 14,
|
||||
@@ -275,7 +275,7 @@ def test_vedastro_official_snapshot_budget_does_not_mask_mock_rest_endpoint(monk
|
||||
monkeypatch.setattr(adapter.time, "monotonic", lambda: next(ticks))
|
||||
|
||||
result = adapter._run_official_full_snapshot_case({
|
||||
"year": REDACTED_YEAR,
|
||||
"year": 1990,
|
||||
"month": 4,
|
||||
"day": 17,
|
||||
"hour": 14,
|
||||
@@ -549,7 +549,8 @@ def test_vedastro_service_adapter_posts_official_search_events_contract() -> Non
|
||||
assert report["source_metadata"]["endpoint"].endswith("/api")
|
||||
assert report["source_metadata"]["official_endpoint_path"] == "/Calculate/SearchEvents"
|
||||
assert report["source_metadata"]["official_request_profile"]["method"] == "POST"
|
||||
assert report["source_metadata"]["official_request_profile"]["headers"]["x-api-key"] == "[redacted]"
|
||||
metadata_headers = report["source_metadata"]["official_request_profile"]["headers"]
|
||||
assert metadata_headers == {"Content-Type": "application/json"}
|
||||
assert report["source_metadata"]["official_request_profile_hash"]
|
||||
assert report["source_metadata"]["request_hash"] != report["source_metadata"]["official_request_profile_hash"]
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
if str(SCRIPTS) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
|
||||
import vedastro_strength_oracle_packet # noqa: E402
|
||||
|
||||
|
||||
def test_strength_packet_declares_shadbala_and_ashtakavarga_as_secondary_evidence() -> None:
|
||||
packet = vedastro_strength_oracle_packet.build_strength_oracle_packet("career")
|
||||
|
||||
assert packet["scope"] == "vedastro_strength_oracle_packet"
|
||||
assert packet["domain"] == "career"
|
||||
assert packet["adjudicator_policy"]["can_change_score"] is False
|
||||
assert packet["adjudicator_policy"]["can_set_dominant_label"] is False
|
||||
assert packet["adjudicator_policy"]["can_set_payout_label"] is False
|
||||
assert [item["method"] for item in packet["requests"]] == ["CalculateShadbala", "CalculateAshtakavarga"]
|
||||
assert all(item["role"] == "external_technique_evidence" for item in packet["requests"])
|
||||
assert packet["technique_audit_rows"] == [
|
||||
{
|
||||
"technique": "VedAstro Shadbala Oracle",
|
||||
"status": "preview",
|
||||
"role": "external_strength_evidence",
|
||||
"effect": "secondary_context_only_no_score_or_label_lift",
|
||||
},
|
||||
{
|
||||
"technique": "VedAstro Ashtakavarga Oracle",
|
||||
"status": "preview",
|
||||
"role": "external_strength_evidence",
|
||||
"effect": "secondary_context_only_no_score_or_label_lift",
|
||||
},
|
||||
]
|
||||
@@ -1,12 +1,14 @@
|
||||
"""
|
||||
PyJHora 风格硬编码 Yoga True/False 测试
|
||||
"""
|
||||
import sys, os
|
||||
SKILL_DIR = '<home>/.workbuddy/skills/jyotish-vedic-astrology'
|
||||
sys.path.insert(0, os.path.join(SKILL_DIR, 'scripts'))
|
||||
RULES_PATH = os.path.join(SKILL_DIR, 'references', 'yoga_rules.json')
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'scripts'))
|
||||
RULES_PATH = ROOT / 'references' / 'yoga_rules.json'
|
||||
from yoga_engine import YogaEngine
|
||||
engine = YogaEngine(RULES_PATH)
|
||||
engine = YogaEngine(str(RULES_PATH))
|
||||
|
||||
def detect(rule_id, planets, ascendant, context=None):
|
||||
results = engine.detect(planets, ascendant, context=context)
|
||||
|
||||
@@ -642,13 +642,13 @@ CELEBRITY_CASES = [
|
||||
'marriages':[
|
||||
{'spouse':'Lisa Marie Presley','date':(1994,5,26),'divorce':(1996,1,18)},
|
||||
{'spouse':'Debbie Rowe','date':(1996,11,14),'divorce':(1999,10,1)}],
|
||||
'events':[('Jackson 5 debut',1969,8,1),('Thriller',1982,11,30),('Grammy 8',1984,1,27),('Allegations',REDACTED_YEAR,8,24),('Death',2009,6,25)]},
|
||||
'events':[('Jackson 5 debut',1969,8,1),('Thriller',1982,11,30),('Grammy 8',1984,1,27),('Allegations',1993,8,24),('Death',2009,6,25)]},
|
||||
{'name':'Nelson Mandela','birth':(1918,7,18,0.9,-31.9,27.0),'gender':'M','rating':'A','expected_asc':'Taurus',
|
||||
'marriages':[
|
||||
{'spouse':'Evelyn Mase','date':(1944,10,5),'divorce':(1958,1,1)},
|
||||
{'spouse':'Winnie Madikizela','date':(1958,6,14),'divorce':(1996,3,19)},
|
||||
{'spouse':'Graca Machel','date':(1998,7,18)}],
|
||||
'events':[('Join ANC',1944,1,1),('Rivonia trial',1964,6,12),('Released',1990,2,11),('Nobel',REDACTED_YEAR,12,10),('President',1994,5,10),('Death',2013,12,5)]},
|
||||
'events':[('Join ANC',1944,1,1),('Rivonia trial',1964,6,12),('Released',1990,2,11),('Nobel',1993,12,10),('President',1994,5,10),('Death',2013,12,5)]},
|
||||
{'name':'Tom Cruise','birth':(1962,7,3,19.25,43.05,-76.15),'gender':'M','rating':'AA','expected_asc':'Libra',
|
||||
'marriages':[
|
||||
{'spouse':'Mimi Rogers','date':(1987,5,9),'divorce':(1990,2,4)},
|
||||
@@ -667,7 +667,7 @@ CELEBRITY_CASES = [
|
||||
'marriages':[{'spouse':'Meghan Markle','date':(2018,5,19),'ongoing':True}],
|
||||
'events':[('Military',2005,5,1),('Invictus Games',2014,9,10),('Royal exit',2020,1,8)]},
|
||||
{'name':'Jeff Bezos','birth':(1964,1,12,9.63,25.78,-80.19),'gender':'M','rating':'A','expected_asc':'Scorpio',
|
||||
'marriages':[{'spouse':'MacKenzie Scott','date':(REDACTED_YEAR,1,1),'divorce':(2019,7,5)}],
|
||||
'marriages':[{'spouse':'MacKenzie Scott','date':(1993,1,1),'divorce':(2019,7,5)}],
|
||||
'events':[('Found Amazon',1994,7,5),('IPO',1997,5,15),('Blue Origin',2021,7,20)]},
|
||||
{'name':'Priyanka Chopra','birth':(1982,7,18,10.5,23.57,87.19),'gender':'F','rating':'A','expected_asc':'Scorpio',
|
||||
'marriages':[{'spouse':'Nick Jonas','date':(2018,12,1),'ongoing':True}],
|
||||
|
||||
@@ -27,7 +27,7 @@ CASES = [
|
||||
{'name':'Prince Harry','birth':(1984,9,15,15.33,51.5,-0.17),'gender':'M',
|
||||
'marriage1':(2018,5,19)},
|
||||
{'name':'Jeff Bezos','birth':(1964,1,12,9.63,25.78,-80.19),'gender':'M',
|
||||
'marriage1':(REDACTED_YEAR,1,1)},
|
||||
'marriage1':(1993,1,1)},
|
||||
{'name':'Priyanka Chopra','birth':(1982,7,18,10.5,23.57,87.19),'gender':'F',
|
||||
'marriage1':(2018,12,1)},
|
||||
{'name':'Shah Rukh Khan','birth':(1965,11,1,21.25,28.61,77.21),'gender':'M',
|
||||
|
||||
Reference in New Issue
Block a user