--- a/src/fixplesk/__init__.py
+++ b/src/fixplesk/__init__.py
@@ -2,4 +2,4 @@
 from .models import Event, Plan, Snapshot
 
 __all__ = ["Event", "Plan", "Snapshot", "__version__"]
-__version__ = "0.5.0"
+__version__ = "0.6.0"
--- a/src/fixplesk/cli.py
+++ b/src/fixplesk/cli.py
@@ -20,7 +20,7 @@
 from .errors import AuthorizationError, ConfigurationError, FixpleskError
 from .executor import Executor
 from .graph import correlation_graph, to_dot
-from .llm import LLMEngine
+from .llm import LLMEngine, provider_info
 from .models import DiagnosticReport, ExecutionReport, Plan, ReadCheck, RuleSet, Snapshot, Step
 from .explain import explain_plan, render_explanation
 from .privacy import private_payload
@@ -194,6 +194,8 @@
     preview = commands.add_parser("llm-preview", help="Pokaż zminimalizowany payload i raport prywatności; bez sieci")
     preview.add_argument("--snapshot", type=Path, required=True)
     output(preview)
+    info = commands.add_parser("llm-info", help="Dostawca, endpoint i polityka LLM; bez sieci i odczytu klucza")
+    output(info)
     recovery = commands.add_parser("recovery", help="Stan przerwanego planu i jawne uzgodnienie przez operatora; bez automatycznego resume")
     recovery.add_argument("action", choices=["status", "reconcile"])
     recovery.add_argument("--digest", required=True)
@@ -325,6 +327,8 @@
         report = diagnose(read_model(args.snapshot, Snapshot), rules_path=args.rules)
         graph = correlation_graph(report)
         emit(to_dot(graph) if args.format == "dot" else graph, args.output)
+    elif cmd == "llm-info":
+        emit(provider_info(load_config(args.config).llm), args.output)
     elif cmd == "llm-preview":
         snapshot = read_model(args.snapshot, Snapshot)
         cfg = load_config(args.config) if args.config else None
@@ -335,7 +339,7 @@
         report = diagnose(snapshot)
         llm_cfg = cfg.llm if cfg else LLMConfig()
         payload, boundary = private_payload(report.snapshot, report.findings, [], llm_cfg)
-        emit({"payload": payload, "privacy": boundary.report(llm_cfg.privacy_mode), "sent": False}, args.output)
+        emit({"payload": payload, "privacy": boundary.report(llm_cfg.privacy_mode), "provider_policy": provider_info(llm_cfg), "sent": False}, args.output)
     elif cmd == "twin-export":
         from .twin import snapshot_fact
         emit(snapshot_fact(read_model(args.snapshot, Snapshot), args.instance_id), args.output)
@@ -501,6 +505,7 @@
                   "executables": {key: {"path": path, "local_executable": os.access(path, os.X_OK)} for key, path in command_paths.items()},
                   "environment_present": {key: bool(os.getenv(key)) for key in sorted(env_names)},
                   "llm_profile_configured": bool(config.llm.profiles.get(config.llm.profile)),
+                  "llm": provider_info(config.llm),
                   "notice": "Nie wykonano połączeń ani komend. Obecność pliku/klucza nie potwierdza zgodności serwera."}, args.output)
         elif cmd == "export-plesk":
             report = read_model(args.report, DiagnosticReport)
--- a/src/fixplesk/config.py
+++ b/src/fixplesk/config.py
@@ -151,19 +151,65 @@
         return self
 
 
+# Audited model identifiers on 2026-09-16; aliases are not immutable weight pins.
+DEEPSEEK_MODELS = ("deepseek-flash", "deepseek-v4-pro", "deepseek-v4-flash")
+
+
 class LLMConfig(Model):
-    backend: Literal["litellm", "openrouter"] = "litellm"
-    api_key_env: str = "OPENROUTER_API_KEY"
-    profile: str = "fast"
+    backend: Literal["litellm", "openrouter", "deepseek"] = "litellm"
+    api_key_env: str = Field(default="OPENROUTER_API_KEY", pattern=r"^[A-Z_][A-Z0-9_]{0,127}$")
+    profile: str = Field(default="fast", min_length=1, max_length=64)
     profiles: dict[str, list[str]] = Field(default_factory=lambda: {"fast": [], "accurate": []})
     max_input_chars: int = Field(default=40_000, ge=2000, le=200_000)
-    max_output_tokens: int = Field(default=2048, ge=128, le=8192)
+    max_output_tokens: int = Field(default=2048, ge=128, le=65_536)
     max_attempts: int = Field(default=2, ge=1, le=5)
-    timeout: int = Field(default=60, ge=1, le=180)
+    timeout: int = Field(default=60, ge=1, le=600)
     include_log_samples: bool = False
     language: Literal["pl", "en"] = "pl"
     privacy_mode: Literal["strict", "redacted"] = "strict"
-    require_zdr: Literal[True] = True
+    require_zdr: bool = Field(default=True, strict=True)
+    # Direct APIs do not inherit OpenRouter's provider-routing policy.
+    accept_provider_data_policy: bool = Field(default=False, strict=True)
+    thinking: Literal["enabled", "disabled"] = "enabled"
+    reasoning_effort: Literal["low", "high", "max"] = "high"
+    max_response_bytes: int = Field(default=1_048_576, ge=4096, le=4_194_304)
+
+    @model_validator(mode="before")
+    @classmethod
+    def backend_defaults(cls, value):
+        if isinstance(value, dict) and value.get("backend") == "deepseek":
+            value = dict(value)
+            # Only the omitted values get defaults. Never silently change consent.
+            value.setdefault("api_key_env", "DEEPSEEK_API_KEY")
+            value.setdefault("profiles", {"fast": ["deepseek-flash"], "accurate": ["deepseek-v4-pro"]})
+            value.setdefault("max_output_tokens", 16_384)
+            value.setdefault("timeout", 180)
+            value.setdefault("max_attempts", 1)
+        return value
+
+    @model_validator(mode="after")
+    def provider_contract(self) -> LLMConfig:
+        if len(self.profiles) > 32 or any(len(v) > 16 for v in self.profiles.values()):
+            raise ValueError("Zbyt wiele profili/modeli LLM")
+        if self.backend == "deepseek":
+            if self.require_zdr or not self.accept_provider_data_policy:
+                raise ValueError("DeepSeek direct nie wymusza ZDR. Wymaga świadomego require_zdr=false "
+                                 "i accept_provider_data_policy=true; inaczej zachowaj OpenRouter ZDR/offline.")
+            if self.privacy_mode != "strict" or self.include_log_samples:
+                raise ValueError("DeepSeek direct wymaga privacy_mode='strict' i include_log_samples=false")
+            if self.api_key_env == "OPENROUTER_API_KEY":
+                raise ValueError("Nie wysyłaj klucza OpenRouter do DeepSeek; użyj DEEPSEEK_API_KEY")
+            if any(m not in DEEPSEEK_MODELS for models in self.profiles.values() for m in models):
+                raise ValueError("Nieobsługiwany model DeepSeek; wybierz deepseek-flash lub deepseek-v4-pro. "
+                                 "deepseek-v4-flash to starszy alias, nie pin wersji.")
+        else:
+            if not self.require_zdr or self.accept_provider_data_policy:
+                raise ValueError("Backend OpenRouter/LiteLLM wymaga require_zdr=true; nie zmienia polityki retencji")
+            if self.api_key_env == "DEEPSEEK_API_KEY":
+                raise ValueError("Klucz DeepSeek wymaga backend='deepseek', nie OpenRouter")
+            if self.max_output_tokens > 8192 or self.timeout > 180:
+                raise ValueError("Dla istniejących backendów limit wynosi 8192 tokeny i 180 sekund")
+        return self
 
 
 class Config(Model):
--- a/src/fixplesk/llm.py
+++ b/src/fixplesk/llm.py
@@ -8,6 +8,7 @@
 from pydantic import Field, ValidationError
 
 from .config import LLMConfig
+from . import deepseek
 from .dsl import load_rules
 from .errors import AuthorizationError, CapabilityError, RuleError, TransportError
 from .models import Finding, Model, RuleSet, Snapshot
@@ -34,11 +35,13 @@
 class LLMEngine:
     def __init__(self, config: LLMConfig, registry: ToolRegistry | None = None,
                  completion: Callable[..., dict[str, Any]] | None = None):
-        self.config = config
+        self.config = LLMConfig.model_validate(config.model_dump())
         self.registry = registry or default_registry()
         self.completion = completion or self._completion
 
     def _completion(self, *, model: str, messages: list[dict[str, str]]) -> dict[str, Any]:
+        if self.config.backend == "deepseek":
+            return deepseek.complete(self.config, model=model, messages=messages)
         key = os.getenv(self.config.api_key_env)
         if not key:
             raise CapabilityError(f"Brak {self.config.api_key_env}; ustaw klucz tylko w środowisku")
@@ -66,10 +69,12 @@
     def analyze(self, snapshot: Snapshot, findings: list[Finding], *, allow_remote: bool = False,
                 knowledge: list[dict[str, Any]] | None = None) -> dict[str, Any]:
         if not allow_remote:
-            raise AuthorizationError("LLM wymaga jawnej zgody --allow-remote na wysłanie podsumowania do OpenRouter")
+            raise AuthorizationError("LLM wymaga jawnej zgody --allow-remote na wysłanie podsumowania do skonfigurowanego dostawcy")
+        # Nested dict/list mutations must not bypass the provider/privacy contract.
+        self.config = LLMConfig.model_validate(self.config.model_dump())
         models = self.config.profiles.get(self.config.profile, [])
         if not models:
-            raise CapabilityError("Profil LLM nie ma modeli; uzupełnij llm.profiles aktualnym ID z OpenRouter")
+            raise CapabilityError("Profil LLM nie ma modeli; uzupełnij llm.profiles ID właściwym dla wybranego dostawcy")
         payload, boundary = private_payload(snapshot, findings, knowledge or [], self.config)
         schema = Advice.model_json_schema()
         available = [item for item in self.registry.list() if item["available"]]
@@ -82,6 +87,7 @@
                        "Pseudonimy nie są adresami ani nazwami do wykonania. Stosuj szablony {{domain.name}}, nie dosłowne pseudonimy. "
                        "Cytuj wyłącznie ID źródeł z danych. Gdy nie ma dowodów, zaproponuj pytania/sprawdzenia. "
                        "Warunki DSL: all/any/not i dotted paths; operatory eq,ne,in,contains,exists,version. "
+                       'PRZYKŁAD JSON: {"summary":"Potrzebne dalsze dowody.","rules":{"rules":[]}} '
                        f"SCHEMAT: {json.dumps(schema, ensure_ascii=False)} "
                        f"NAZWANE NARZĘDZIA: {json.dumps([{'name':x['name'],'parameters':x['parameters']} for x in available], ensure_ascii=False)}")
         while len(instruction) + len(json.dumps(payload, ensure_ascii=False)) > self.config.max_input_chars:
@@ -104,11 +110,15 @@
                 choice = response["choices"][0]
                 if choice.get("finish_reason") == "length":
                     raise RuleError("LLM przekroczył limit odpowiedzi")
+                if self.config.backend == "deepseek" and choice.get("finish_reason") != "stop":
+                    raise RuleError("DeepSeek nie zakończył poprawnie odpowiedzi; częściowa porada odrzucona")
                 message = choice["message"]
+                if message.get("refusal"):
+                    raise RuleError("Dostawca odmówił odpowiedzi")
                 if message.get("tool_calls") or message.get("function_call"):
                     raise RuleError("LLM zwrócił zabronione wywołanie narzędzia")
                 content = message.get("content")
-                if not isinstance(content, str) or len(content.encode()) > 128_000:
+                if not isinstance(content, str) or not content.strip() or len(content.encode()) > 128_000:
                     raise RuleError("Niepoprawny rozmiar odpowiedzi LLM")
                 advice = Advice.model_validate_json(content)
                 rules = load_rules(text=advice.rules.model_dump_json(), validate_tool=self.registry.validate)
@@ -122,18 +132,51 @@
                 if cited - known:
                     raise RuleError("LLM wymyślił identyfikatory źródeł")
                 data_usage = response.get("usage", {}) or {}
-                usage = {key: data_usage.get(key) for key in ("prompt_tokens", "completion_tokens", "total_tokens")
-                         if isinstance(data_usage.get(key), int)}
+                usage = {key: data_usage.get(key) for key in ("prompt_tokens", "completion_tokens", "total_tokens", "prompt_cache_hit_tokens",
+                                                             "prompt_cache_miss_tokens", "reasoning_tokens")
+                         if type(data_usage.get(key)) is int and data_usage[key] >= 0}
                 cost = data_usage.get("cost")
                 return {"advisory_only": True, "model": model, "backend": self.config.backend,
                         "attempts": attempts, "input_chars": len(messages[1]["content"]),
                         "system_chars": len(instruction), "usage": usage,
                         "cost_usd": float(cost) if isinstance(cost, (float, int)) else None,
-                        "privacy": privacy_report,
+                        "privacy": privacy_report, "provider_policy": provider_info(self.config),
+                        "returned_model": response.get("model") if self.config.backend == "deepseek" else None,
                         "advice": redact(advice.model_dump(mode="json")),
                         "warnings": ["Brak ceny oznacza nieznany koszt, nie koszt zerowy. Nieudane próby też mogą być płatne.",
-                                     "Propozycje LLM nie zostały dodane do aktywnych reguł ani wykonane."] + errors}
+                                     "Propozycje LLM nie zostały dodane do aktywnych reguł ani wykonane."]
+                                    + provider_info(self.config)["warnings"] + errors}
             except Exception as exc:
                 # Do not send SDK errors (may include credentials/logs) to the next model.
-                errors.append(f"{model}: {type(exc).__name__}")
+                label = f"{type(exc).__name__}/HTTP_{exc.status_code}/{exc.code}" if isinstance(exc, deepseek.DeepSeekAPIError) else type(exc).__name__
+                errors.append(f"{model}: {label}")
+                # Failed network calls may already have consumed tokens. Do not
+                # silently change model/provider on HTTP, timeout or missing keys.
+                if self.config.backend == "deepseek" and isinstance(exc, (TransportError, CapabilityError)):
+                    break
         raise TransportError("Żaden model nie zwrócił poprawnej odpowiedzi: " + "; ".join(errors))
+
+
+def provider_info(config: LLMConfig) -> dict[str, Any]:
+    """Static non-secret metadata. No model check, credentials or HTTP request."""
+    is_deepseek = config.backend == "deepseek"
+    models = list(dict.fromkeys(config.profiles.get(config.profile, [])))
+    warnings = []
+    if is_deepseek:
+        warnings.append("DeepSeek direct: fixplesk nie wymusza ZDR ani nie poświadcza retencji dostawcy. "
+                        "Zgoda na jego politykę nie oznacza anonimizacji nieodwracalnej.")
+        if "deepseek-v4-flash" in models:
+            warnings.append("deepseek-v4-flash jest starszym aliasem kierowanym według dokumentacji do V4.1 Flash; nie jest pinem V4.")
+    return {"provider": "deepseek" if is_deepseek else "openrouter", "backend": config.backend,
+            "endpoint": deepseek.ENDPOINT if is_deepseek else "https://openrouter.ai/api/v1/chat/completions",
+            "api_key_env": config.api_key_env, "profile": config.profile, "models": models,
+            "remote_verified": False, "zdr_routing_requested": not is_deepseek,
+            "provider_retention_verified": False,
+            "accept_provider_data_policy": config.accept_provider_data_policy,
+            "privacy_mode": config.privacy_mode,
+            "thinking": config.thinking if is_deepseek else None,
+            "reasoning_effort": config.reasoning_effort if is_deepseek and config.thinking == "enabled" else None,
+            "max_output_tokens": config.max_output_tokens, "max_attempts": config.max_attempts,
+            "timeout_seconds": config.timeout, "cross_provider_fallback": False,
+            "contract_checked_at": deepseek.CONTRACT_CHECKED_AT if is_deepseek else None,
+            "warnings": warnings}
--- a/tests/test_cli_reports.py
+++ b/tests/test_cli_reports.py
@@ -50,7 +50,8 @@
 def test_module_entrypoint():
     env = {**os.environ, "PYTHONPATH": str(Path(__file__).resolve().parents[1] / "src")}
     result = subprocess.run([sys.executable, "-m", "fixplesk", "--version"], text=True, capture_output=True, env=env)
-    assert result.returncode == 0 and "0.5.0" in result.stdout
+    from fixplesk import __version__
+    assert result.returncode == 0 and result.stdout.strip() == f"fixplesk {__version__}"
 
 
 def test_reports_escape_untrusted_text():
--- a/tests/test_new_contracts.py
+++ b/tests/test_new_contracts.py
@@ -134,7 +134,9 @@
 def test_python_package_renamed():
     import importlib.util
     import fixplesk
-    assert fixplesk.__version__ == "0.5.0"
+    import tomllib
+    metadata = tomllib.loads((Path(__file__).resolve().parents[1] / "pyproject.toml").read_text())
+    assert fixplesk.__version__ == metadata["project"]["version"]
     assert importlib.util.find_spec("pleskme") is None
 
 
--- /dev/null
+++ b/src/fixplesk/deepseek.py
@@ -0,0 +1,103 @@
+"""Native DeepSeek Chat Completions. Fixed host, no SDK, tools or silent routing.
+
+Contract checked against official documentation on 2026-09-16.
+This adapter does not claim to enforce the provider's retention policy.
+"""
+from __future__ import annotations
+
+import json
+import os
+import re
+from typing import Any
+
+from .config import DEEPSEEK_MODELS, LLMConfig
+from .errors import CapabilityError, TransportError
+from .transport import HTTP
+
+ENDPOINT = "https://api.deepseek.com/chat/completions"
+CONTRACT_CHECKED_AT = "2026-09-16"
+
+
+class DeepSeekAPIError(TransportError):
+    """Sanitized HTTP status. Never preserve a remote body or credentials."""
+    def __init__(self, status_code: int):
+        self.status_code = status_code
+        self.code = {
+            400: "invalid_request", 401: "authentication", 402: "insufficient_balance",
+            403: "forbidden", 404: "model_or_endpoint_unavailable", 422: "invalid_parameters",
+            429: "rate_limit", 500: "server_error", 502: "gateway_error",
+            503: "overloaded", 504: "gateway_timeout",
+        }.get(status_code, "redirect_denied" if 300 <= status_code < 400 else "http_error")
+        super().__init__(f"DeepSeek: HTTP {status_code} ({self.code}); bez automatycznego ponowienia")
+
+
+def request_body(config: LLMConfig, model: str, messages: list[dict[str, str]]) -> dict[str, Any]:
+    """No I/O. Revalidate nested mutable configuration before building a request."""
+    config = LLMConfig.model_validate(config.model_dump())
+    if config.backend != "deepseek" or model not in DEEPSEEK_MODELS:
+        raise CapabilityError("Model nie pasuje do adaptera DeepSeek")
+    if not messages or any(m.get("role") not in {"system", "user"} or
+                           not isinstance(m.get("content"), str) for m in messages):
+        raise CapabilityError("DeepSeek przyjmuje wyłącznie tekstowy kontekst system/user")
+    if sum(len(m["content"]) for m in messages) > config.max_input_chars:
+        raise CapabilityError("Przekroczono budżet wejścia DeepSeek")
+    body: dict[str, Any] = {
+        "model": model,
+        "messages": [{"role": m["role"], "content": m["content"]} for m in messages],
+        "response_format": {"type": "json_object"},
+        "max_tokens": config.max_output_tokens,
+        "thinking": {"type": config.thinking},
+        "stream": False,
+    }
+    if config.thinking == "enabled":
+        body["reasoning_effort"] = config.reasoning_effort
+    # No tools, arbitrary extra_body, provider-routing fields, sampling knobs,
+    # or assistant reasoning from a previous request.
+    return body
+
+
+def complete(config: LLMConfig, *, model: str, messages: list[dict[str, str]]) -> dict[str, Any]:
+    """One POST only. Upper layers own privacy construction and explicit consent."""
+    body = request_body(config, model, messages)
+    key = os.getenv(config.api_key_env)
+    if not key:
+        raise CapabilityError(f"Brak {config.api_key_env}; klucz ustaw tylko w środowisku")
+    # Reject invalid headers before a client is created. Do not echo the value.
+    if not re.fullmatch(r"[!-~]{1,4096}", key):
+        raise CapabilityError("Nieprawidłowy format klucza API DeepSeek")
+    with HTTP(timeout=config.timeout, max_bytes=config.max_response_bytes) as http:
+        status, raw, _ = http.request(
+            "POST", ENDPOINT, read_only=False, attempts=1, allow_error_status=True,
+            headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
+            json=body,
+        )
+    if status != 200:
+        raise DeepSeekAPIError(status)
+    try:
+        result = json.loads(raw)
+    except (ValueError, UnicodeDecodeError):
+        raise TransportError("DeepSeek nie zwrócił poprawnego JSON") from None
+    if not isinstance(result, dict) or not isinstance(result.get("choices"), list) or len(result["choices"]) != 1:
+        raise TransportError("Nieobsługiwana struktura odpowiedzi DeepSeek")
+    choice = result["choices"][0]
+    if not isinstance(choice, dict) or not isinstance(choice.get("message"), dict):
+        raise TransportError("Brak wiadomości w odpowiedzi DeepSeek")
+    # Deliberately drop reasoning_content, headers, IDs and all unknown metadata.
+    message = choice["message"]
+    clean: dict[str, Any] = {
+        "choices": [{"finish_reason": choice.get("finish_reason"),
+                     "message": {k: message[k] for k in ("content", "tool_calls", "function_call", "refusal") if k in message}}],
+    }
+    usage = result.get("usage")
+    if isinstance(usage, dict):
+        clean["usage"] = {k: v for k, v in usage.items() if k in {
+            "prompt_tokens", "completion_tokens", "total_tokens", "prompt_cache_hit_tokens", "prompt_cache_miss_tokens"
+        } and type(v) is int and v >= 0}
+        details = usage.get("completion_tokens_details")
+        if isinstance(details, dict) and type(details.get("reasoning_tokens")) is int and details["reasoning_tokens"] >= 0:
+            clean["usage"]["reasoning_tokens"] = details["reasoning_tokens"]
+    # The returned identifier is diagnostic metadata, not an attestation of weights.
+    returned = result.get("model")
+    if isinstance(returned, str) and re.fullmatch(r"deepseek-[a-zA-Z0-9_.-]{1,96}", returned):
+        clean["model"] = returned
+    return clean
--- /dev/null
+++ b/tests/test_deepseek.py
@@ -0,0 +1,370 @@
+"""DeepSeek direct contract tests. MockTransport only; no real credentials/network."""
+from __future__ import annotations
+
+import json
+import os
+import socket
+import sys
+from pathlib import Path
+
+import httpx
+import pytest
+from pydantic import ValidationError
+
+from fixplesk import deepseek
+from fixplesk.cli import demo_snapshot, main
+from fixplesk.config import Config, LLMConfig
+from fixplesk.errors import AuthorizationError, CapabilityError, PrivacyError, TransportError
+from fixplesk.llm import LLMEngine, provider_info
+from fixplesk.models import Event, Snapshot
+from fixplesk.transport import HTTP
+
+
+def cfg(**kwargs):
+    return LLMConfig(**{"backend": "deepseek", "require_zdr": False,
+                        "accept_provider_data_policy": True, **kwargs})
+
+
+def write_config(path, config):
+    # The application intentionally accepts trusted TOML, not an arbitrary JSON config.
+    target = config.targets[0]
+    text = f'''schema_version = 1
+state_dir = {json.dumps(str(config.state_dir))}
+[principal]
+id = "operator"
+role = "admin"
+tenant_id = "admin"
+[[targets]]
+domain = {json.dumps(target.domain)}
+tenant_id = {json.dumps(target.tenant_id)}
+subscription_id = {json.dumps(target.subscription_id)}
+[llm]
+backend = "deepseek"
+require_zdr = false
+accept_provider_data_policy = true
+'''
+    path.write_text(text)
+    path.chmod(0o600)
+
+
+def response(content=None, finish="stop", **message):
+    return {"model": "deepseek-v4-pro-0813", "choices": [{"finish_reason": finish,
+        "message": {"content": content if content is not None else json.dumps({
+            "summary": "Potrzebne dodatkowe dowody.", "rules": {"rules": []}}), **message}}],
+        "usage": {"prompt_tokens": 100, "completion_tokens": 20, "total_tokens": 120,
+                  "prompt_cache_hit_tokens": 50, "prompt_cache_miss_tokens": 50,
+                  "completion_tokens_details": {"reasoning_tokens": 10}, "unknown": "secret"}}
+
+
+@pytest.fixture
+def snapshot():
+    return Snapshot(domain="private-customer.example", tenant_id="tenant-SECRET-123", wordpress={
+        "core_version": "6.8.3", "DB_PASSWORD": "DB-SECRET-987", "users": [{"email": "jan@private-customer.example"}]},
+        logs={"php": ["password=DB-SECRET-987 client 192.0.2.77"]}, events=[
+            Event(id="private", pattern="PHP_FATAL", message="jan@private-customer.example", source="php")])
+
+
+@pytest.fixture
+def network(monkeypatch):
+    calls = []
+    replies = []
+    def handler(request):
+        calls.append(request)
+        item = replies.pop(0) if replies else httpx.Response(200, json=response())
+        if isinstance(item, Exception):
+            raise item
+        return item
+    client = httpx.Client(transport=httpx.MockTransport(handler), follow_redirects=False, trust_env=False)
+    monkeypatch.setenv("DEEPSEEK_API_KEY", "TEST-ONLY-DEEPSEEK-KEY")
+    monkeypatch.setenv("OPENROUTER_API_KEY", "MUST-NOT-LEAVE-THIS-PROCESS")
+    monkeypatch.setattr(deepseek, "HTTP", lambda **kw: HTTP(client=client, **kw))
+    yield calls, replies
+    client.close()
+
+
+def test_provider_defaults():
+    c = cfg()
+    assert c.api_key_env == "DEEPSEEK_API_KEY"
+    assert c.profiles == {"fast": ["deepseek-flash"], "accurate": ["deepseek-v4-pro"]}
+    assert c.max_attempts == 1 and c.max_output_tokens == 16384 and c.timeout == 180
+    assert LLMConfig().require_zdr is True
+
+
+@pytest.mark.parametrize("values", [
+    {"backend": "deepseek"},
+    {"backend": "deepseek", "require_zdr": False},
+    {"backend": "deepseek", "accept_provider_data_policy": True},
+    {"backend": "deepseek", "require_zdr": True, "accept_provider_data_policy": True},
+    {"require_zdr": False}, {"backend": "openrouter", "require_zdr": False},
+    {"backend": "litellm", "accept_provider_data_policy": True},
+    {"backend": "openrouter", "api_key_env": "DEEPSEEK_API_KEY"},
+])
+def test_retention_never_silently_downgraded(values):
+    with pytest.raises(ValidationError):
+        LLMConfig(**values)
+
+
+@pytest.mark.parametrize("values", [
+    {"privacy_mode": "redacted"}, {"include_log_samples": True},
+    {"require_zdr": "false"}, {"accept_provider_data_policy": "yes"},
+    {"api_key_env": "OPENROUTER_API_KEY"}, {"api_key_env": "bad name"},
+    {"api_key_env": "KEY\nAuthorization"}, {"base_url": "https://evil.example"},
+    {"extra_body": {"provider": "evil"}}, {"thinking": "auto"},
+    {"reasoning_effort": "ultra"}, {"max_output_tokens": 65537},
+    {"timeout": 601}, {"max_response_bytes": 4194305},
+    {"profiles": {"fast": ["deepseek-chat"]}},
+    {"profiles": {"fast": ["deepseek-reasoner"]}},
+    {"profiles": {"fast": ["deepseek-v4.1-flash"]}},
+    {"profiles": {"fast": ["deepseek/deepseek-flash"]}},
+    {"profiles": {"fast": ["openrouter/deepseek/model"]}},
+    {"profiles": {"fast": ["https://evil.example/?key=x"]}},
+])
+def test_bad_direct_config_rejected(values):
+    with pytest.raises(ValidationError):
+        cfg(**values)
+
+
+@pytest.mark.parametrize("model", ["deepseek-flash", "deepseek-v4-pro", "deepseek-v4-flash"])
+@pytest.mark.parametrize("effort", ["low", "high", "max"])
+def test_actual_http_dispatch(network, snapshot, model, effort):
+    calls, _ = network
+    result = LLMEngine(cfg(profiles={"fast": [model]}, reasoning_effort=effort)).analyze(snapshot, [], allow_remote=True)
+    request = calls[0]
+    assert request.method == "POST" and str(request.url) == deepseek.ENDPOINT
+    assert request.headers["Authorization"] == "Bearer TEST-ONLY-DEEPSEEK-KEY"
+    body = json.loads(request.content)
+    assert set(body) == {"model", "messages", "response_format", "max_tokens", "thinking", "reasoning_effort", "stream"}
+    assert body["model"] == model and body["thinking"] == {"type": "enabled"}
+    assert body["reasoning_effort"] == effort and body["stream"] is False
+    assert body["response_format"] == {"type": "json_object"}
+    assert result["advisory_only"] and result["cost_usd"] is None
+    assert result["usage"]["reasoning_tokens"] == 10
+    assert result["returned_model"] == "deepseek-v4-pro-0813"
+    assert "TEST-ONLY" not in json.dumps(result)
+    assert "MUST-NOT-LEAVE" not in request.content.decode()
+    assert result["provider_policy"]["zdr_routing_requested"] is False
+
+
+def test_non_thinking_omits_effort(network, snapshot):
+    calls, _ = network
+    LLMEngine(cfg(thinking="disabled")).analyze(snapshot, [], allow_remote=True)
+    body = json.loads(calls[0].content)
+    assert body["thinking"] == {"type": "disabled"}
+    assert "reasoning_effort" not in body and "temperature" not in body
+
+
+def test_strict_privacy_at_real_http_boundary(network, snapshot):
+    calls, _ = network
+    result = LLMEngine(cfg()).analyze(snapshot, [], allow_remote=True)
+    raw = calls[0].content.decode()
+    for secret in (snapshot.domain, snapshot.tenant_id, "DB-SECRET-987", "192.0.2.77", "jan@"):
+        assert secret not in raw
+    assert result["privacy"]["mode"] == "strict"
+    assert "provider" not in json.loads(raw)
+
+
+def test_remote_consent_before_network(network, snapshot):
+    with pytest.raises(AuthorizationError):
+        LLMEngine(cfg()).analyze(snapshot, [])
+    assert not network[0]
+
+
+def test_privacy_failure_no_fallback(network, snapshot, monkeypatch):
+    def deny(*a, **k):
+        raise PrivacyError("Denied")
+    monkeypatch.setattr("fixplesk.llm.private_payload", deny)
+    with pytest.raises(PrivacyError):
+        LLMEngine(cfg()).analyze(snapshot, [], allow_remote=True)
+    assert not network[0]
+
+
+def test_nested_mutation_revalidated(network, snapshot):
+    engine = LLMEngine(cfg())
+    engine.config.profiles["fast"].append("openrouter/not/allowed")
+    with pytest.raises(ValidationError):
+        engine.analyze(snapshot, [], allow_remote=True)
+    assert not network[0]
+
+
+@pytest.mark.parametrize("status", [301, 302, 307, 400, 401, 402, 403, 404, 422, 429, 500, 502, 503, 504])
+def test_http_error_body_not_logged_or_retried(network, snapshot, status):
+    calls, replies = network
+    replies.append(httpx.Response(status, headers={"Location": "https://evil.example"}, text="API-KEY-AND-CUSTOMER-SECRET"))
+    with pytest.raises(TransportError) as exc:
+        LLMEngine(cfg(max_attempts=2, profiles={"fast": ["deepseek-flash", "deepseek-v4-pro"]})).analyze(snapshot, [], allow_remote=True)
+    assert len(calls) == 1 and f"HTTP_{status}" in str(exc.value)
+    assert "CUSTOMER-SECRET" not in str(exc.value) and "TEST-ONLY" not in str(exc.value)
+
+
+def test_timeout_not_retried(network, snapshot):
+    network[1].append(httpx.ReadTimeout("DO-NOT-PRINT-SECRET"))
+    with pytest.raises(TransportError) as exc:
+        LLMEngine(cfg(max_attempts=2, profiles={"fast": ["deepseek-flash", "deepseek-v4-pro"]})).analyze(snapshot, [], allow_remote=True)
+    assert len(network[0]) == 1 and "DO-NOT-PRINT" not in str(exc.value)
+
+
+@pytest.mark.parametrize("finish", ["length", "content_filter", "tool_calls", "insufficient_system_resource", None])
+def test_incomplete_or_refused_finish_rejected(network, snapshot, finish):
+    network[1].append(httpx.Response(200, json=response(finish=finish)))
+    with pytest.raises(TransportError):
+        LLMEngine(cfg()).analyze(snapshot, [], allow_remote=True)
+
+
+@pytest.mark.parametrize("reply", [
+    response(content=""), response(content="not JSON"), response(content="{\"summary\":"),
+    response(content=json.dumps({"summary": "x", "sources": ["invented.source"]})),
+    response(tool_calls=[{"function": {"name": "shell"}}]),
+    response(function_call={"name": "exec"}), response(refusal="policy refusal"),
+    response(content=json.dumps({"summary": "x", "execute": "rm -rf /"})),
+])
+def test_invalid_advice_no_execution(network, snapshot, reply):
+    network[1].append(httpx.Response(200, json=reply))
+    with pytest.raises(TransportError):
+        LLMEngine(cfg()).analyze(snapshot, [], allow_remote=True)
+
+
+def test_explicit_advice_fallback_same_private_payload(network, snapshot):
+    network[1].append(httpx.Response(200, json=response(content="")))
+    result = LLMEngine(cfg(max_attempts=2, profiles={"fast": ["deepseek-flash", "deepseek-v4-pro"]})).analyze(snapshot, [], allow_remote=True)
+    assert result["attempts"] == 2 and result["model"] == "deepseek-v4-pro"
+    assert json.loads(network[0][0].content)["messages"] == json.loads(network[0][1].content)["messages"]
+
+
+def test_reasoning_text_not_returned_or_persisted(network, snapshot):
+    network[1].append(httpx.Response(200, json=response(reasoning_content="DO-NOT-LOG-REASONING")))
+    result = LLMEngine(cfg()).analyze(snapshot, [], allow_remote=True)
+    assert "DO-NOT-LOG" not in json.dumps(result)
+    assert "reasoning_content" not in json.dumps(result)
+
+
+def test_raw_adapter_strips_reasoning(network):
+    network[1].append(httpx.Response(200, json=response(reasoning_content="private reasoning")))
+    result = deepseek.complete(cfg(), model="deepseek-flash", messages=[{"role": "user", "content": "json"}])
+    assert "private reasoning" not in str(result) and "unknown" not in result["usage"]
+
+
+def test_size_bound(network, snapshot):
+    network[1].append(httpx.Response(200, text=" " * 5000))
+    with pytest.raises(TransportError):
+        LLMEngine(cfg(max_response_bytes=4096)).analyze(snapshot, [], allow_remote=True)
+    assert len(network[0]) == 1
+
+
+@pytest.mark.parametrize("payload", [[], {}, {"choices": []}, {"choices": [1]}, {"choices": [{"message": None}]}, {"choices": [1, 2]}])
+def test_invalid_envelope(network, payload):
+    network[1].append(httpx.Response(200, json=payload))
+    with pytest.raises(TransportError):
+        deepseek.complete(cfg(), model="deepseek-flash", messages=[{"role": "user", "content": "json"}])
+
+
+def test_missing_or_invalid_key_no_connection(network, snapshot, monkeypatch):
+    for key in (None, "", "key\nheader", "key with space", "ą-key"):
+        if key is None:
+            monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
+        else:
+            monkeypatch.setenv("DEEPSEEK_API_KEY", key)
+        with pytest.raises(TransportError):
+            LLMEngine(cfg()).analyze(snapshot, [], allow_remote=True)
+    assert not network[0]
+
+
+def test_static_metadata_no_key_read_or_network(monkeypatch):
+    def forbidden(*a, **kw):
+        raise AssertionError("I/O forbidden")
+    monkeypatch.setattr(socket, "create_connection", forbidden)
+    monkeypatch.setattr(os, "getenv", forbidden)
+    info = provider_info(cfg())
+    assert info["remote_verified"] is False and info["provider"] == "deepseek"
+    assert info["contract_checked_at"] == "2026-09-16"
+
+
+def test_legacy_alias_warning():
+    info = provider_info(cfg(profiles={"fast": ["deepseek-v4-flash"]}))
+    assert any("aliasem" in x for x in info["warnings"])
+
+
+def test_without_litellm_dependency(network, snapshot, monkeypatch):
+    monkeypatch.setitem(sys.modules, "litellm", None)
+    assert LLMEngine(cfg()).analyze(snapshot, [], allow_remote=True)["backend"] == "deepseek"
+
+
+def test_input_budget_and_message_contract():
+    with pytest.raises(CapabilityError):
+        deepseek.request_body(cfg(max_input_chars=2000), "deepseek-flash", [{"role": "user", "content": "x" * 2001}])
+    with pytest.raises(CapabilityError):
+        deepseek.request_body(cfg(), "deepseek-flash", [{"role": "assistant", "content": "json"}])
+    with pytest.raises(CapabilityError):
+        deepseek.request_body(cfg(), "openrouter/evil/model", [{"role": "user", "content": "json"}])
+
+
+def test_cli_preserves_local_report_on_api_failure(network, tmp_path, config, snapshot):
+    config.llm = cfg()
+    target = config.targets[0]
+    snapshot.domain = target.domain
+    snapshot.tenant_id = target.tenant_id
+    snapshot.subscription_id = target.subscription_id
+    path = tmp_path / "config.json"
+    write_config(path, config)
+    snap = tmp_path / "snapshot.json"; snap.write_text(snapshot.model_dump_json())
+    report = tmp_path / "report.json"
+    network[1].append(httpx.Response(401, text="PRIVATE"))
+    assert main(["--config", str(path), "diagnose", "--snapshot", str(snap), "--llm", "--allow-remote", "--output", str(report)]) == 3
+    value = json.loads(report.read_text())
+    assert value["llm"]["status"] == "unavailable" and "findings" in value
+    assert "PRIVATE" not in report.read_text()
+
+
+def test_cli_info_and_preview_are_offline(network, tmp_path, config):
+    config.llm = cfg()
+    snap = demo_snapshot()
+    target = config.targets[0]
+    snap.domain, snap.tenant_id, snap.subscription_id = target.domain, target.tenant_id, target.subscription_id
+    path = tmp_path / "config.json";write_config(path, config)
+    sp = tmp_path / "snapshot.json";sp.write_text(snap.model_dump_json())
+    out = tmp_path / "preview.json"
+    assert main(["--config", str(path), "llm-info", "--output", str(out)]) == 0
+    assert json.loads(out.read_text())["provider"] == "deepseek"
+    assert main(["--config", str(path), "llm-preview", "--snapshot", str(sp), "--output", str(out)]) == 0
+    value = json.loads(out.read_text())
+    assert value["sent"] is False and value["provider_policy"]["provider"] == "deepseek"
+    assert not network[0]
+
+
+def test_documented_example_configs():
+    import tomllib
+    root = Path(__file__).resolve().parents[1]
+    full = Config.model_validate(tomllib.loads((root / "examples/deepseek/config.toml").read_text()))
+    section = LLMConfig.model_validate(tomllib.loads((root / "examples/deepseek/llm-section.toml").read_text())["llm"])
+    assert full.llm == section and full.llm.backend == "deepseek"
+    example = Snapshot.model_validate_json((root / "examples/deepseek/snapshot.json").read_text())
+    target = full.target(example.domain)
+    assert (example.tenant_id, example.subscription_id) == (target.tenant_id, target.subscription_id)
+
+
+def test_http_client_security_flags(snapshot, monkeypatch):
+    real = httpx.Client
+    seen = []
+    def factory(**kwargs):
+        seen.append(kwargs)
+        assert kwargs["verify"] is True
+        assert kwargs["trust_env"] is False and kwargs["follow_redirects"] is False
+        return real(transport=httpx.MockTransport(lambda req: httpx.Response(200, json=response())), **kwargs)
+    monkeypatch.setattr(httpx, "Client", factory)
+    monkeypatch.setenv("DEEPSEEK_API_KEY", "TEST-ONLY-KEY")
+    assert LLMEngine(cfg()).analyze(snapshot, [], allow_remote=True)["advisory_only"]
+    assert len(seen) == 1
+
+
+def test_invalid_json_transport_not_logged(network):
+    network[1].append(httpx.Response(200, content=b"PRIVATE-DATA-not-json"))
+    with pytest.raises(TransportError) as exc:
+        deepseek.complete(cfg(), model="deepseek-flash", messages=[{"role": "user", "content": "json"}])
+    assert "PRIVATE" not in str(exc.value)
+
+
+def test_provider_model_metadata_is_not_free_text(network):
+    value = response();value["model"] = "https://private.example?password=secret"
+    value["usage"] = {"prompt_tokens": True, "completion_tokens": -1, "total_tokens": "private"}
+    network[1].append(httpx.Response(200, json=value))
+    result = deepseek.complete(cfg(), model="deepseek-flash", messages=[{"role": "user", "content": "json"}])
+    assert "model" not in result and result["usage"] == {}
