Audit lifecycle
How POST /api/v1/audit/conversation turns a request into an attested evidence envelope, the only endpoint that reaches a live provider.
Audit lifecycle
The audit endpoint is the seam between the outside world and the Evidence Engine. It accepts a task, a list of instructions and the input text to audit, resolves a provider, invokes it, and returns the provider's structured verdict together with the evidence envelope built around it.
src/vailidator/api/routes.py · _route_conversation_audit, audit_conversation_endpointflowchart TD A["POST /api/v1/audit/conversationAudit lifecycle, provider resolution (two paths) then evidence assembly. Source: api/routes.py lines 624–700.
ConversationAuditRequest(task, instructions, input)"] --> B["_route_conversation_audit(body)"] B --> C["config = ProviderConfig.from_env()"] C --> D{"VERIFAIER_DEFAULT_PROVIDER set
and non-empty?"} D -->|yes: router path| E["ProviderRouter(config, provider_mode=live)
.route(task, instructions, input)"] D -->|no: legacy path| F{"VERIFAIER_ENABLE_GEMINI
in {0,false,no,off}?"} F -->|yes| G["router.route_forced('mock')"] F -->|no| H["ProviderRouter(enable_gemini=True, mode=live)
.route_forced('gemini')"] E --> I["RoutingResult"] G --> I H --> I I --> J["_evidence_engine.build(routed, operation=task)"] J --> K["EvidenceEnvelope (+ Receipt + Provenance)"] K --> L["ConversationAuditResponse
provider result + routing + envelope + receipt + provenance"]
Two resolution paths
Provider resolution has two modes, chosen entirely by environment configuration. This preserves the original single-provider contract while enabling the full router:
| Condition | Behavior |
|---|---|
VERIFAIER_DEFAULT_PROVIDER set & non-empty | Router path. ProviderRouter.route() walks the primary → VERIFAIER_PROVIDER_FALLBACKS → mock chain in live mode. See provider routing. |
unset, VERIFAIER_ENABLE_GEMINI ∈ {0,false,no,off} | Legacy path. route_forced("mock"): deterministic offline result. |
| unset, otherwise | Legacy path. Gemini forced live via route_forced("gemini"); the proxy self-falls-back when the key is absent or upstream is unavailable. |
Both paths return a RoutingResult: a ProviderResult plus safe routing metadata. The endpoint never sees an API key: adapters read keys from the environment at call time, and keys never appear in the request, the logs, or the response.
From result to envelope
The handler passes the RoutingResult to _evidence_engine.build(routed, operation=body.task). That single call assembles the Evidence Envelope, its Receipt and its Provenance, all sharing one timestamp. The response is then projected from the envelope:
ConversationAuditResponse( **provider["result"], # risk, drift, inconsistency, hallucination_risk, explanation, confidence, evidence provider = provider["provider"], mode = provider["mode"], status = provider["status"], latency_ms = provider["latency_ms"], routing = envelope.routing, # safe routing metadata envelope = envelope.identifiers(), receipt = envelope.receipt, provenance = envelope.provenance, )
The provider verdict fields (risk, drift, inconsistency, hallucination_risk, explanation, confidence, evidence) are the historical contract; routing, envelope, receipt and provenance are additive: older clients ignore them. This is the additive-response invariant in practice.
The broader audit family
Two sibling endpoints share the "audit" name but a different pipeline, they run the deterministic validation engine, not the provider router:
POST /dev/audit: audits AI-generated code by comparing declared intent (extract_intent) with code reality (analyze_diff) viarun_audit; deterministic checks override the LLM assessment (the "Golden Rule").POST /sentinel/verify: the browser extension's hallucination check; runs the validation engine and maps confidence/verifiability to anACCEPT / REVIEW / REJECTverdict. See Extension ↔ Runtime.
fallback_used, skipped_providers). Failure is data, not an exception. See failure handling.