fix(consult): race a bounded VedAstro gateway on the chat path
Foreground still skips the heavy overview stack, but overlaps a timed gateway so official evidence can merge when it finishes in budget. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4506,3 +4506,19 @@
|
||||
- 相关记录:BUG-279、BUG-287、BUG-298
|
||||
- 复发自:BUG-279(算了却不交给模型)
|
||||
- 修复版本:待提交
|
||||
|
||||
## BUG-301 | 网页咨询前台把 VedAstro 整段跳过,审计只能诚实 blocked
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-08-19
|
||||
- 最近更新:2026-08-19
|
||||
- 影响面:普通咨询 `POST /api/consult`、`execute_consultation_workflow` foreground、`vedastro_gateway`、模型包 `vedastro_cross_check`
|
||||
- 用户现象:技法表里 VedAstro 云状态一直是阻塞,即使官方网关和网络可用。
|
||||
- 触发条件:聊天前台 `foreground: true` → `defer_optional_external_evidence: true`。
|
||||
- 根因:BUG-161 为避免 overview/full snapshot/range scan/gateway 串行超时,前台直接跳过 gateway 并写死 `foreground_optional_evidence_deferred`。本地层恢复后(BUG-300)仍把可选官方交叉验证当成“本轮不算”。
|
||||
- 修复:前台仍跳过 VedAstro 主入口 overview 与 range scan。gateway 与本地计算并行,子进程预算约 8 秒,本地完成后最多再等 1.5 秒。赶上且有 official raw 则合并为 `official_verified` 并投影日月升一星座;超时或失败保持 `official_blocked`,本地回答不失败。MEVG / PyJHora / jyotishganit 仍 blocked。不提高 `AGENT_TIMEOUT_MS`。
|
||||
- 验证:`tests/test_api_server_security.py` 前台赶上合并与超时降级;`tests/test_consultation_consumer_context.py` 压缩包去掉经纬度;`frontend/tests/consultation-context.test.ts`、`consultation-spectrum-parity.test.ts`。
|
||||
- 防复发:前台不得再同步串联 overview+snapshot+range scan;赶不上不得把未交付官方层标成 executed;压缩交叉验证不得带出生钟点或坐标。
|
||||
- 相关记录:BUG-161、BUG-159、BUG-300
|
||||
- 复发自:BUG-161(超时防护把可选官方层整段关掉)
|
||||
- 修复版本:待提交
|
||||
|
||||
@@ -67,6 +67,8 @@ const NOTE_LABELS: Readonly<Record<string, string>> = {
|
||||
official_raw_archive_not_auditable_for_this_run: "官方原始档案本轮不可审计",
|
||||
official_raw_archive_is_auditable: "官方原始档案可审计",
|
||||
official_cloud_evidence_available: "官方云证据可用",
|
||||
foreground_optional_evidence_timeout: "前台官方云证据超时,本轮未赶上",
|
||||
gateway_invocation_error: "官方云网关调用失败",
|
||||
claims_capped_until_pyjhora_jhora_jyotishganit_are_invoked_for_this_run: "结论上限受外部引擎调用限制",
|
||||
three_engine_runtime_closure_available: "三引擎运行时已闭环",
|
||||
complete_packet_required_for_high_confidence: "高置信度需完整证据包",
|
||||
|
||||
@@ -270,6 +270,7 @@ const validationKeys = new Set([
|
||||
"confidencecap", "summary", "conclusion", "publiccontextonly", "similarpubliccases",
|
||||
"highsimilaritypublicreferencesavailable", "requesteduncovereddomains", "timingstate",
|
||||
"partialmatch", "narayanastatus", "transitstatus", "productiontuningallowed", "nomajorityvote",
|
||||
"vedastrocrosscheck", "officialclosurestate", "officialclosurereason", "natal", "ascendant",
|
||||
]);
|
||||
|
||||
const domainDetailsKeys = new Set([
|
||||
@@ -465,6 +466,7 @@ function projectValidationEvidence(context: ReturnType<typeof toAgentConsultatio
|
||||
technique_truth: context.evidence_contract.technique_truth,
|
||||
reference_transparency: context.reference_transparency,
|
||||
shadbala_boundary: context.local_layers.shadbala_boundary,
|
||||
vedastro_cross_check: context.vedastro_cross_check,
|
||||
}, validationKeys, { allowAstrologyEntityKeys: true });
|
||||
const output = record(validation);
|
||||
const audit = projectTechniqueAudit(context.evidence_contract.technique_audit_table);
|
||||
@@ -487,6 +489,7 @@ const SHARED_MUST_USE_LAYERS = [
|
||||
"Arudha / UL / A10",
|
||||
"Western natal (tropical)",
|
||||
"Transits / Sade Sati",
|
||||
"VedAstro Cloud State",
|
||||
] as const;
|
||||
|
||||
const DOMAIN_MUST_USE_LAYERS: Readonly<Record<string, readonly string[]>> = {
|
||||
@@ -648,6 +651,7 @@ export function toAgentConsultationContext(data: JsonRecord) {
|
||||
range_boundary_contexts: record(data.range_boundary_contexts),
|
||||
thematic_evidence: selectedTheme,
|
||||
vedastro_gateway: record(data.vedastro_gateway),
|
||||
vedastro_cross_check: record(consumerContext.vedastro_cross_check),
|
||||
external_engine_evidence: {
|
||||
runtime_truth: record(data.runtime_truth), numerical_parity: record(data.external_parity_gate),
|
||||
real_case_calibration: record(data.real_case_calibration),
|
||||
|
||||
@@ -157,6 +157,7 @@ test("projects only bounded server-selected evidence to the model", () => {
|
||||
{ technique: "Narayana Dasha", status: "executed", system: "jyotish" },
|
||||
{ technique: "Functional Benefic/Malefic", status: "executed", key_functional_benefics: ["Jupiter"] },
|
||||
{ technique: "Western natal (tropical)", status: "executed", system: "western" },
|
||||
{ technique: "VedAstro Cloud State", status: "executed", boundary: "official_cloud_evidence_available" },
|
||||
{ technique: "MEVG / Global Web Evidence", status: "blocked", boundary: "caps_claims_until_global_web_evidence_runs" },
|
||||
],
|
||||
varga_spectrum: {
|
||||
@@ -169,9 +170,18 @@ test("projects only bounded server-selected evidence to the model", () => {
|
||||
natal: { sun: "Aries", moon: "Taurus", ascendant: "Leo" },
|
||||
techniques: { transits: { status: "executed" } },
|
||||
},
|
||||
vedastro_cross_check: {
|
||||
status: "executed",
|
||||
official_closure_state: "official_verified",
|
||||
natal: { sun: "Leo", moon: "Cancer", ascendant: "Scorpio" },
|
||||
},
|
||||
},
|
||||
reference_transparency: { similar_public_cases: { status: "public_context_only" } },
|
||||
rectification: { summary: "none" },
|
||||
vedastro_gateway: {
|
||||
official_closure_state: "official_verified",
|
||||
official_raw_response: { lat: 25.03, lon: 121.56, hour: 3, minute: 4 },
|
||||
},
|
||||
});
|
||||
const output = toModelOutput(context);
|
||||
const careerPlan = createConsultationPlan({ userIntent: "事业如何", theme: "career" });
|
||||
@@ -231,6 +241,9 @@ test("projects only bounded server-selected evidence to the model", () => {
|
||||
assert.equal(Array.isArray(mustUse), true);
|
||||
assert.equal((mustUse as string[]).includes("Yogas"), true);
|
||||
assert.equal((mustUse as string[]).includes("Arudha / UL / A10"), true);
|
||||
assert.equal((mustUse as string[]).includes("VedAstro Cloud State"), true);
|
||||
assert.equal((mustUse as string[]).includes("MEVG / Global Web Evidence"), false);
|
||||
assert.equal(serialized.includes("Scorpio"), true);
|
||||
assert.equal(serialized.includes("official_verified"), true);
|
||||
assert.equal(serialized.includes("https://"), false);
|
||||
});
|
||||
|
||||
@@ -21,6 +21,8 @@ test("web consult compute and projection keep the live skill's locally computed
|
||||
}
|
||||
assert.match(attach, /_compact_yoga_entry/);
|
||||
assert.match(attach, /_compact_chara_dasha/);
|
||||
assert.match(attach, /_join_foreground_vedastro/);
|
||||
assert.match(attach, /skip_vedastro_main_entry_overview': defer_optional_external_evidence/);
|
||||
assert.doesNotMatch(attach, /defer_optional_external_evidence = False/);
|
||||
|
||||
assert.match(workflow, /yogas: context\.local_layers\.yogas/);
|
||||
|
||||
@@ -22,7 +22,7 @@ import threading
|
||||
import time
|
||||
import tempfile
|
||||
import uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeoutError
|
||||
from datetime import datetime, timedelta
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
@@ -105,6 +105,11 @@ _ASYNC_JOB_EXECUTOR = ThreadPoolExecutor(
|
||||
max_workers=_ASYNC_JOB_WORKERS,
|
||||
thread_name_prefix='jyotish-job',
|
||||
)
|
||||
_FOREGROUND_VEDASTRO_WORKERS = max(int(os.environ.get('JYOTISH_FOREGROUND_VEDASTRO_WORKERS', '2')), 1)
|
||||
_FOREGROUND_VEDASTRO_EXECUTOR = ThreadPoolExecutor(
|
||||
max_workers=_FOREGROUND_VEDASTRO_WORKERS,
|
||||
thread_name_prefix='jyotish-vedastro-fg',
|
||||
)
|
||||
_ASYNC_JOB_CAPACITY = threading.BoundedSemaphore(_ASYNC_JOB_WORKERS + _ASYNC_JOB_QUEUE_SIZE)
|
||||
_RATE_LIMIT_LOCK = threading.Lock()
|
||||
_RATE_LIMIT_BUCKETS: dict[str, tuple[float, int]] = {}
|
||||
@@ -1586,6 +1591,123 @@ def _build_consumer_context(
|
||||
}
|
||||
|
||||
|
||||
_VEDASTRO_NATAL_SIGN_KEYS = ('sun', 'moon', 'ascendant')
|
||||
_VEDASTRO_COMPACT_DENY_KEYS = {
|
||||
'lat', 'lon', 'lng', 'latitude', 'longitude', 'hour', 'minute', 'second',
|
||||
'date', 'time', 'birth', 'tz', 'timezone', 'city', 'address', 'reported_time',
|
||||
}
|
||||
|
||||
|
||||
def _foreground_vedastro_budget_seconds() -> float:
|
||||
raw = str(os.environ.get('JYOTISH_FOREGROUND_VEDASTRO_BUDGET_SECONDS', '8')).strip()
|
||||
try:
|
||||
return min(max(float(raw), 2.0), 12.0)
|
||||
except ValueError:
|
||||
return 8.0
|
||||
|
||||
|
||||
def _foreground_vedastro_join_seconds() -> float:
|
||||
raw = str(os.environ.get('JYOTISH_FOREGROUND_VEDASTRO_JOIN_SECONDS', '1.5')).strip()
|
||||
try:
|
||||
return min(max(float(raw), 0.0), 3.0)
|
||||
except ValueError:
|
||||
return 1.5
|
||||
|
||||
|
||||
def _vedastro_natal_signs(raw) -> dict | None:
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
natal = raw.get('natal') if isinstance(raw.get('natal'), dict) else raw
|
||||
if not isinstance(natal, dict):
|
||||
return None
|
||||
compact = {}
|
||||
sign_names = set(SIGNS)
|
||||
for key in _VEDASTRO_NATAL_SIGN_KEYS:
|
||||
value = natal.get(key) or natal.get(key.title()) or natal.get(key.capitalize())
|
||||
sign = None
|
||||
if isinstance(value, str) and value in sign_names:
|
||||
sign = value
|
||||
elif isinstance(value, dict):
|
||||
candidate = value.get('sign') or value.get('rasi')
|
||||
if isinstance(candidate, str) and candidate in sign_names:
|
||||
sign = candidate
|
||||
if sign:
|
||||
compact[key] = sign
|
||||
return compact or None
|
||||
|
||||
|
||||
def _compact_vedastro_cross_check(vedastro_official: dict, vedastro_gateway: dict | None = None) -> dict:
|
||||
official = vedastro_official if isinstance(vedastro_official, dict) else {}
|
||||
gateway = vedastro_gateway if isinstance(vedastro_gateway, dict) else {}
|
||||
state = str(
|
||||
official.get('official_closure_state')
|
||||
or gateway.get('official_closure_state')
|
||||
or official.get('status')
|
||||
or gateway.get('status')
|
||||
or 'official_blocked'
|
||||
)
|
||||
reason = official.get('official_closure_reason') or gateway.get('official_closure_reason')
|
||||
compact = {
|
||||
'status': 'executed' if state == 'official_verified' else 'blocked',
|
||||
'official_closure_state': state,
|
||||
}
|
||||
if reason:
|
||||
compact['official_closure_reason'] = str(reason)
|
||||
raw = (
|
||||
official.get('raw_response')
|
||||
or official.get('official_raw_response')
|
||||
or gateway.get('official_raw_response')
|
||||
or gateway.get('raw_response')
|
||||
)
|
||||
natal = _vedastro_natal_signs(raw)
|
||||
if natal:
|
||||
compact['natal'] = natal
|
||||
for denied in _VEDASTRO_COMPACT_DENY_KEYS:
|
||||
compact.pop(denied, None)
|
||||
if isinstance(compact.get('natal'), dict):
|
||||
compact['natal'].pop(denied, None)
|
||||
return compact
|
||||
|
||||
|
||||
def _blocked_foreground_vedastro(*, reason: str, error_type: str | None = None) -> dict:
|
||||
packet = {
|
||||
'scope': 'vedastro_gateway_run',
|
||||
'status': 'official_blocked',
|
||||
'official_closure_state': 'official_blocked',
|
||||
'official_closure_reason': reason,
|
||||
}
|
||||
if error_type:
|
||||
packet['error_type'] = error_type
|
||||
return packet
|
||||
|
||||
|
||||
def _run_foreground_vedastro_gateway(handler, body: dict) -> dict:
|
||||
try:
|
||||
with temporary_timeout_seconds(_foreground_vedastro_budget_seconds()):
|
||||
result = handler._compute_vedastro_gateway_run(body)
|
||||
except Exception as exc:
|
||||
return _blocked_foreground_vedastro(
|
||||
reason='gateway_invocation_error',
|
||||
error_type=type(exc).__name__,
|
||||
)
|
||||
return result if isinstance(result, dict) else _blocked_foreground_vedastro(reason='gateway_invocation_error')
|
||||
|
||||
|
||||
def _join_foreground_vedastro(future, *, timeout: float) -> dict:
|
||||
if future is None:
|
||||
return _blocked_foreground_vedastro(reason='foreground_optional_evidence_timeout')
|
||||
try:
|
||||
result = future.result(timeout=timeout)
|
||||
except FuturesTimeoutError:
|
||||
return _blocked_foreground_vedastro(reason='foreground_optional_evidence_timeout')
|
||||
except Exception as exc:
|
||||
return _blocked_foreground_vedastro(
|
||||
reason='gateway_invocation_error',
|
||||
error_type=type(exc).__name__,
|
||||
)
|
||||
return result if isinstance(result, dict) else _blocked_foreground_vedastro(reason='gateway_invocation_error')
|
||||
|
||||
|
||||
def execute_consultation_workflow(
|
||||
handler,
|
||||
*,
|
||||
@@ -1709,6 +1831,13 @@ def execute_consultation_workflow(
|
||||
rectification = {}
|
||||
muhurta_panchanga = {}
|
||||
computed_chart = bool(chart)
|
||||
foreground_vedastro_future = None
|
||||
if defer_optional_external_evidence:
|
||||
# Overlap a bounded official gateway with local compute. Main-entry
|
||||
# overview / range scan stay skipped so foreground cannot replay BUG-161.
|
||||
foreground_vedastro_future = _FOREGROUND_VEDASTRO_EXECUTOR.submit(
|
||||
_run_foreground_vedastro_gateway, handler, dict(body),
|
||||
)
|
||||
|
||||
for step in runtime_planner.get('sync_steps', []):
|
||||
if step == 'run_prashna':
|
||||
@@ -1790,12 +1919,10 @@ def execute_consultation_workflow(
|
||||
|
||||
vedastro_gateway = rectification.get('vedastro_gateway') if isinstance(rectification, dict) else None
|
||||
if defer_optional_external_evidence:
|
||||
vedastro_gateway = {
|
||||
'scope': 'vedastro_gateway_run',
|
||||
'status': 'local_fallback',
|
||||
'official_closure_state': 'official_blocked',
|
||||
'official_closure_reason': 'foreground_optional_evidence_deferred',
|
||||
}
|
||||
vedastro_gateway = _join_foreground_vedastro(
|
||||
foreground_vedastro_future,
|
||||
timeout=_foreground_vedastro_join_seconds(),
|
||||
)
|
||||
elif not isinstance(vedastro_gateway, dict):
|
||||
try:
|
||||
vedastro_gateway = handler._compute_vedastro_gateway_run(body)
|
||||
@@ -1884,6 +2011,14 @@ def execute_consultation_workflow(
|
||||
quality_gate=runtime_evidence_log.get('quality_gate') if isinstance(runtime_evidence_log.get('quality_gate'), dict) else {},
|
||||
entry_mode=str(entry_mode or 'direct_chart'),
|
||||
)
|
||||
consumer_context['vedastro_cross_check'] = _compact_vedastro_cross_check(
|
||||
vedastro_official,
|
||||
vedastro_gateway if isinstance(vedastro_gateway, dict) else {},
|
||||
)
|
||||
consumer_context['vedastro_cross_check'] = _compact_vedastro_cross_check(
|
||||
vedastro_official,
|
||||
vedastro_gateway if isinstance(vedastro_gateway, dict) else {},
|
||||
)
|
||||
|
||||
result = {
|
||||
'success': True,
|
||||
|
||||
@@ -2395,7 +2395,7 @@ def test_consultation_workflow_uses_unified_orchestrator_contract(monkeypatch) -
|
||||
assert result['reference_transparency']['similar_public_cases']['does_not_predict_user_outcome'] is True
|
||||
|
||||
|
||||
def test_consultation_foreground_defers_optional_vedastro_calls(monkeypatch) -> None:
|
||||
def test_consultation_foreground_skips_vedastro_main_entry_overview(monkeypatch) -> None:
|
||||
handler = _handler()
|
||||
seen = {}
|
||||
fake_chart = {
|
||||
@@ -2432,7 +2432,17 @@ def test_consultation_foreground_defers_optional_vedastro_calls(monkeypatch) ->
|
||||
monkeypatch.setattr(
|
||||
handler,
|
||||
'_compute_vedastro_gateway_run',
|
||||
lambda body: pytest.fail('foreground consultation must not call VedAstro gateway'),
|
||||
lambda body: {
|
||||
'scope': 'vedastro_gateway_run',
|
||||
'status': 'official_verified',
|
||||
'official_closure_state': 'official_verified',
|
||||
'official_raw_response': {
|
||||
'request_id': 'fg-ok',
|
||||
'natal': {'sun': 'Leo', 'moon': 'Taurus', 'ascendant': 'Cancer'},
|
||||
'lat': 25.03,
|
||||
'lon': 121.56,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
result = handler._compute_consultation_workflow({
|
||||
@@ -2452,10 +2462,95 @@ def test_consultation_foreground_defers_optional_vedastro_calls(monkeypatch) ->
|
||||
|
||||
assert seen['chart_body']['skip_vedastro_main_entry_overview'] is True
|
||||
assert result['success'] is True
|
||||
assert result['vedastro_gateway']['status'] == 'local_fallback'
|
||||
assert result['vedastro_gateway']['official_closure_reason'] == 'foreground_optional_evidence_deferred'
|
||||
assert result['vedastro_gateway']['official_closure_state'] == 'official_verified'
|
||||
assert result['vedastro_official']['official_closure_state'] == 'official_verified'
|
||||
assert result['consumer_context']['vedastro_cross_check']['status'] == 'executed'
|
||||
assert result['consumer_context']['vedastro_cross_check']['natal'] == {
|
||||
'sun': 'Leo',
|
||||
'moon': 'Taurus',
|
||||
'ascendant': 'Cancer',
|
||||
}
|
||||
assert 'lat' not in result['consumer_context']['vedastro_cross_check']
|
||||
assert 'lon' not in result['consumer_context']['vedastro_cross_check']
|
||||
assert result['consumer_context']['answer_policy']['can_answer_direction'] is True
|
||||
assert result['consumer_context']['answer_policy']['provider_unavailable_is_fatal'] is False
|
||||
audit = {
|
||||
row['technique']: row['status']
|
||||
for row in result['consumer_context']['technique_audit_table']
|
||||
if isinstance(row, dict)
|
||||
}
|
||||
assert audit.get('VedAstro Cloud State') == 'executed'
|
||||
assert audit.get('MEVG / Global Web Evidence') == 'blocked'
|
||||
|
||||
|
||||
def test_consultation_foreground_times_out_vedastro_without_failing_local(monkeypatch) -> None:
|
||||
handler = _handler()
|
||||
fake_chart = {
|
||||
'success': True,
|
||||
'birth_info': {'date': '1997-08-08', 'time': '05:00', 'tz': 8},
|
||||
'ascendant': {'lon': 92.0, 'sign': 'Cancer', 'sign_idx': 3},
|
||||
'planets': _sample_planets(),
|
||||
'dasha': {'periods': [{'lord': 'Sun', 'start': '2026-01-01', 'end': '2027-01-01'}]},
|
||||
'modules': {
|
||||
'varga_full': {'D9': {}, 'D10': {}},
|
||||
'arudha_padas': {'A10': {}, 'UL': {}},
|
||||
'narayana_dasha': {'periods': []},
|
||||
'ashtakavarga': {'sav': []},
|
||||
'kp_cusps': {'houses': []},
|
||||
},
|
||||
'special_lagnas': {'precision': 'sunrise_correct'},
|
||||
}
|
||||
|
||||
monkeypatch.setenv('JYOTISH_FOREGROUND_VEDASTRO_JOIN_SECONDS', '0.05')
|
||||
monkeypatch.setenv('JYOTISH_FOREGROUND_VEDASTRO_BUDGET_SECONDS', '2')
|
||||
monkeypatch.setattr(handler, '_compute_chart', lambda body: fake_chart)
|
||||
monkeypatch.setattr(handler, '_compute_rectification_gate', lambda body: {
|
||||
'success': True,
|
||||
'summary': {'recommended_events': [], 'warned': [], 'disabled': []},
|
||||
})
|
||||
monkeypatch.setattr(handler, '_compute_thematic_report', lambda body: {
|
||||
'success': True,
|
||||
'endpoint': 'thematic_report',
|
||||
'themes': {},
|
||||
})
|
||||
|
||||
def slow_gateway(body):
|
||||
time.sleep(0.4)
|
||||
return {
|
||||
'scope': 'vedastro_gateway_run',
|
||||
'status': 'official_verified',
|
||||
'official_closure_state': 'official_verified',
|
||||
'official_raw_response': {'request_id': 'too-late'},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(handler, '_compute_vedastro_gateway_run', slow_gateway)
|
||||
|
||||
result = handler._compute_consultation_workflow({
|
||||
'entry_mode': 'direct_chart',
|
||||
'question': '应期与阶段问题:深入看今日',
|
||||
'theme': ['career'],
|
||||
'year': 1997,
|
||||
'month': 8,
|
||||
'day': 8,
|
||||
'hour': 5,
|
||||
'minute': 0,
|
||||
'lat': 36.420487,
|
||||
'lon': 114.209936,
|
||||
'tz': 8,
|
||||
'defer_optional_external_evidence': True,
|
||||
})
|
||||
|
||||
assert result['success'] is True
|
||||
assert result['vedastro_gateway']['official_closure_reason'] == 'foreground_optional_evidence_timeout'
|
||||
assert result['consumer_context']['vedastro_cross_check']['status'] == 'blocked'
|
||||
assert result['consumer_context']['answer_policy']['can_answer_direction'] is True
|
||||
audit = {
|
||||
row['technique']: row['status']
|
||||
for row in result['consumer_context']['technique_audit_table']
|
||||
if isinstance(row, dict)
|
||||
}
|
||||
assert audit.get('VedAstro Cloud State') == 'blocked'
|
||||
assert audit.get('MEVG / Global Web Evidence') == 'blocked'
|
||||
|
||||
|
||||
def test_consultation_workflow_accepts_western_oracle_payload(monkeypatch) -> None:
|
||||
|
||||
@@ -20,6 +20,7 @@ from jyotish_api_server import ( # noqa: E402
|
||||
JyotishAPIHandler,
|
||||
_attach_local_consultation_layers,
|
||||
_build_consumer_context,
|
||||
_compact_vedastro_cross_check,
|
||||
)
|
||||
from unified_consultation_orchestrator import UnifiedConsultationOrchestrator # noqa: E402
|
||||
|
||||
@@ -700,3 +701,35 @@ def test_failed_vedastro_raw_packet_is_not_marked_as_used() -> None:
|
||||
)
|
||||
|
||||
assert packet['sections']['vedastro_official_raw_response']['status'] == 'received_unverified'
|
||||
|
||||
|
||||
def test_compact_vedastro_cross_check_keeps_signs_and_drops_coordinates() -> None:
|
||||
compact = _compact_vedastro_cross_check(
|
||||
{'official_closure_state': 'official_verified', 'status': 'ok'},
|
||||
{
|
||||
'official_raw_response': {
|
||||
'natal': {'sun': 'Leo', 'moon': 'Taurus', 'ascendant': 'Cancer'},
|
||||
'lat': 25.03,
|
||||
'lon': 121.56,
|
||||
'hour': 3,
|
||||
'minute': 4,
|
||||
},
|
||||
},
|
||||
)
|
||||
assert compact['status'] == 'executed'
|
||||
assert compact['natal'] == {'sun': 'Leo', 'moon': 'Taurus', 'ascendant': 'Cancer'}
|
||||
assert 'lat' not in compact
|
||||
assert 'lon' not in compact
|
||||
assert 'hour' not in compact
|
||||
|
||||
|
||||
def test_compact_vedastro_cross_check_marks_timeout_blocked() -> None:
|
||||
compact = _compact_vedastro_cross_check(
|
||||
{},
|
||||
{
|
||||
'official_closure_state': 'official_blocked',
|
||||
'official_closure_reason': 'foreground_optional_evidence_timeout',
|
||||
},
|
||||
)
|
||||
assert compact['status'] == 'blocked'
|
||||
assert compact['official_closure_reason'] == 'foreground_optional_evidence_timeout'
|
||||
|
||||
@@ -53,6 +53,16 @@ def _stub_consultation_runtime(monkeypatch, handler: JyotishAPIHandler) -> None:
|
||||
"_compute_muhurta_panchanga",
|
||||
lambda body: {"status": "ok", "scope": "muhurta_panchanga"},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
handler,
|
||||
"_compute_vedastro_gateway_run",
|
||||
lambda body: {
|
||||
"scope": "vedastro_gateway_run",
|
||||
"status": "official_blocked",
|
||||
"official_closure_state": "official_blocked",
|
||||
"official_closure_reason": "test_stub",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("domain", CANONICAL_DOMAINS)
|
||||
|
||||
Reference in New Issue
Block a user