fix(consult): stop the consult-gate VedAstro wait and record step timing

Consult turns reuse a same-day official snapshot inside the rectification gate, or record deferred_in_consultation instead of starting another 4-second snapshot. Classification, step 0, step 1, and the time to the first answer character go into the existing usage metadata and a new admin usage column.

BUG-1231, BUG-1232
This commit is contained in:
jesse-ux
2026-10-05 02:33:36 +08:00
parent 4c64733f91
commit 137ed6f408
16 changed files with 1188 additions and 9 deletions
@@ -0,0 +1,199 @@
"""Consult rectification gate reuses or defers the official VedAstro snapshot."""
from __future__ import annotations
import json
import pytest
from scripts.jyotish_api_server import JyotishAPIHandler
from scripts.vedastro_consultation_snapshot import (
consult_rectification_vedastro_gateway,
negative_cache_dir,
snapshot_identity_from_request,
store_negative_snapshot,
)
from scripts.vedastro_snapshot_cache import snapshot_cache_dir, store_snapshot
def _handler() -> JyotishAPIHandler:
return object.__new__(JyotishAPIHandler)
def _planets() -> dict:
return {
"Sun": {"lon": 80.0},
"Moon": {"lon": 123.0},
"Mars": {"lon": 210.0},
"Mercury": {"lon": 75.0},
"Jupiter": {"lon": 15.0},
"Venus": {"lon": 102.0},
"Saturn": {"lon": 330.0},
"Rahu": {"lon": 5.0},
"Ketu": {"lon": 185.0},
}
def _body(**extra) -> dict:
payload = {
"year": 1990,
"month": 1,
"day": 1,
"hour": 5,
"minute": 0,
"second": 0,
"lat": 19.0,
"lon": 72.8,
"tz": 5.5,
"ayanamsa": "raman",
"node_mode": "mean",
"reference_date": "2026-10-05",
"today": "2026-10-05",
"current_date": "2026-10-05",
"planets": _planets(),
"ascendant": {"lon": 15.0},
"declared_accuracy": "minute",
"time_source": "family_clear",
}
payload.update(extra)
return payload
def _verified(request_id: str = "same-day") -> dict:
return {
"scope": "vedastro_gateway_run",
"status": "official_verified",
"official_closure_state": "official_verified",
"official_closure_reason": "official_raw_response_present",
"official_raw_response": {"request_id": request_id, "natal": {"sun": "Leo"}},
}
@pytest.fixture
def caches(tmp_path, monkeypatch):
monkeypatch.setenv("JYOTISH_VEDASTRO_SNAPSHOT_CACHE_DIR", str(tmp_path / "positive"))
monkeypatch.setenv("JYOTISH_VEDASTRO_SNAPSHOT_NEGATIVE_CACHE_DIR", str(tmp_path / "negative"))
return tmp_path
def _forbid_runner(monkeypatch) -> list[str]:
calls: list[str] = []
def runner(*_args, **_kwargs):
calls.append("runner")
raise AssertionError("snapshot runner must not start on the consult gate")
def report(*_args, **_kwargs):
calls.append("report")
raise AssertionError("build_report must not start on the consult gate")
monkeypatch.setattr(
"scripts.vedastro_service_adapter._try_official_capability_runner_snapshot_bundle",
runner,
)
monkeypatch.setattr("scripts.vedastro_user_entrypoint.build_report", report)
return calls
def test_consult_gate_does_not_call_the_snapshot_runner(caches, monkeypatch) -> None:
calls = _forbid_runner(monkeypatch)
handler = _handler()
def live_gateway(_body):
calls.append("gateway")
raise AssertionError("consult gate must not call the live gateway")
handler._compute_vedastro_gateway_run = live_gateway # type: ignore[method-assign]
result = handler._compute_rectification_gate({
**_body(),
"defer_optional_external_evidence": True,
})
gateway = result["vedastro_gateway"]
assert calls == []
assert gateway["official_closure_state"] == "official_blocked"
assert gateway["official_closure_reason"] == "deferred_in_consultation"
assert gateway["status"] != "official_verified"
assert "official_raw_response" not in gateway
assert result["endpoint"] == "rectification_gate"
def test_negative_cache_hits_the_same_utc_day_and_misses_the_next(caches, monkeypatch) -> None:
calls = _forbid_runner(monkeypatch)
stored = store_negative_snapshot(_body(), {
"scope": "vedastro_gateway_run",
"status": "official_blocked",
"official_closure_state": "official_blocked",
"official_closure_reason": "foreground_optional_evidence_timeout",
"email": "hidden@example.com",
}, today="2026-10-05")
assert stored is not None
assert "email" not in json.dumps(stored["gateway"])
same_day = consult_rectification_vedastro_gateway({
**_body(),
"defer_optional_external_evidence": True,
})
assert same_day["official_closure_reason"] == "foreground_optional_evidence_timeout"
before = sorted(path.read_bytes() for path in negative_cache_dir().glob("*.json"))
again = consult_rectification_vedastro_gateway({
**_body(),
"defer_optional_external_evidence": True,
})
after = sorted(path.read_bytes() for path in negative_cache_dir().glob("*.json"))
assert again["official_closure_reason"] == "foreground_optional_evidence_timeout"
assert before == after
next_day = consult_rectification_vedastro_gateway({
**_body(reference_date="2026-10-06", today="2026-10-06", current_date="2026-10-06"),
"defer_optional_external_evidence": True,
})
assert next_day["official_closure_reason"] == "deferred_in_consultation"
assert calls == []
assert negative_cache_dir().resolve() != snapshot_cache_dir().resolve()
def test_same_day_verified_snapshot_is_reused_ahead_of_the_negative_cache(caches, monkeypatch) -> None:
calls = _forbid_runner(monkeypatch)
request = _body()
store_snapshot(request, _verified("cached-official"))
store_negative_snapshot(request, {
"status": "official_blocked",
"official_closure_state": "official_blocked",
"official_closure_reason": "foreground_optional_evidence_timeout",
}, today="2026-10-05")
normalized = {
**request,
"hour": 5.0,
"minute": 0.0,
"defer_optional_external_evidence": True,
"vedastro_snapshot_identity": snapshot_identity_from_request(request),
}
reused = consult_rectification_vedastro_gateway(normalized)
assert reused["official_closure_state"] == "official_verified"
assert reused["official_raw_response"]["request_id"] == "cached-official"
missed = consult_rectification_vedastro_gateway({
**request,
"hour": 5.0,
"defer_optional_external_evidence": True,
})
assert missed["official_closure_reason"] == "deferred_in_consultation"
assert calls == []
def test_rectification_surface_without_the_flag_still_uses_the_live_gateway(caches) -> None:
handler = _handler()
seen: list[dict] = []
def live_gateway(body):
seen.append(dict(body))
return _verified("rectification-surface")
handler._compute_vedastro_gateway_run = live_gateway # type: ignore[method-assign]
result = handler._compute_rectification_gate(_body())
assert len(seen) == 1
assert "defer_optional_external_evidence" not in seen[0]
gateway = result["vedastro_gateway"]
assert gateway["official_closure_state"] == "official_verified"
assert gateway["official_closure_reason"] == "official_raw_response_present"
assert gateway["official_raw_response"]["request_id"] == "rectification-surface"
assert result["endpoint"] == "rectification_gate"
assert result["success"] is True