diff --git a/app/core/config.py b/app/core/config.py index 8c79b69..126ea18 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -56,7 +56,15 @@ class Config: # omnibioai-billing are then not visible. V1_REDIS_URL = os.getenv("V1_REDIS_URL", "") V1_RATE_LIMIT_PER_MINUTE = int(os.getenv("V1_RATE_LIMIT_PER_MINUTE", "60")) - V1_IDEMPOTENCY_TTL = int(os.getenv("V1_IDEMPOTENCY_TTL", "86400")) + # M18 (design audit gap #11): this cached replay body is the real + # answer text/citations for /v1/literature/answers, not just + # metadata -- one of only two places that text survives anywhere in + # this system at all (the other is omnibioai-rag's own query + # cache). The design doc's 30-day question/answer deletion policy + # is enforced here as a hard ceiling on this already-short-lived + # cache's TTL, not a separate deletion job -- there is no durable + # store for one to run against. + V1_IDEMPOTENCY_TTL = min(int(os.getenv("V1_IDEMPOTENCY_TTL", "86400")), 30 * 24 * 60 * 60) # Design audit gap #7 ("concurrent-answer limits are absent"): the # most expensive /v1 call (it invokes an LLM, up to RAG's own # 300-second timeout) is capped on how many of a single key's or diff --git a/tests/test_config.py b/tests/test_config.py index f10aa45..c785867 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -41,6 +41,30 @@ def test_config_reads_iam_url_from_env(monkeypatch): importlib.reload(cfg_module) +def test_v1_idempotency_ttl_is_capped_at_30_days_even_if_configured_higher(monkeypatch): + """M18 (design audit gap #11): the idempotency-replay cache holds + the real answer text/citations, so its TTL is the de facto + enforcement of the 30-day deletion policy -- a misconfigured env + var must not be able to silently violate that.""" + monkeypatch.setenv("V1_IDEMPOTENCY_TTL", str(60 * 24 * 60 * 60)) # 60 days + import app.core.config as cfg_module + + importlib.reload(cfg_module) + assert cfg_module.Config.V1_IDEMPOTENCY_TTL == 30 * 24 * 60 * 60 + monkeypatch.delenv("V1_IDEMPOTENCY_TTL", raising=False) + importlib.reload(cfg_module) + + +def test_v1_idempotency_ttl_below_the_30_day_cap_is_unaffected(monkeypatch): + monkeypatch.setenv("V1_IDEMPOTENCY_TTL", "60") + import app.core.config as cfg_module + + importlib.reload(cfg_module) + assert cfg_module.Config.V1_IDEMPOTENCY_TTL == 60 + monkeypatch.delenv("V1_IDEMPOTENCY_TTL", raising=False) + importlib.reload(cfg_module) + + def test_config_defaults_are_set(): """IAM_URL/REDIS_URL/JWT_SECRET all have non-empty defaults, so the gateway can start with no environment variables configured."""