> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwedai.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Verification Diagnostics

> The 3-layer QWED DiagnosticResult model: agent-safe, developer, and proof diagnostics for structured verification output, debugging, and audit logs.

## Overview

> [!NOTE]
>
> **Introduced in v5.2.0.** The `DiagnosticResult` model is an additive contract — no existing engine return types are changed. Engines migrate incrementally.

QWED verification engines historically returned ad-hoc `Dict[str, Any]` results with no consistent structure. Three incompatible `VerificationResult` dataclasses existed. Some engines returned `(bool, str)` tuples. Verification diagnostics were not separable by audience — agents saw internal detection patterns, developers couldn't reliably find expected vs actual values, and auditors had no proof artifact references.

**v5.2.0** introduces a unified `DiagnosticResult` type with three disclosure layers, each targeted at a specific audience.

## The 3-layer model

```mermaid theme={null}
flowchart TB
    R[DiagnosticResult] --> L1["Layer 1 — agent_message\n(agent-safe, no internals)"]
    R --> L2["Layer 2 — developer_fields\n(structured evidence)"]
    R --> L3["Layer 3 — proof_ref\n(sha256 hash of proof artifact)"]

    L1 --> A[Agents / models]
    L2 --> D[Application developers]
    L3 --> AU[Auditors / operators]

    classDef agent fill:#ecfeff,stroke:#06b6d4,color:#155e75;
    classDef dev fill:#f0fdf4,stroke:#22c55e,color:#166534;
    classDef proof fill:#fef2f2,stroke:#ef4444,color:#991b1b;
    class L1 agent;
    class L2 dev;
    class L3 proof;
```

### Layer 1 — Agent-Safe Diagnostics

**Field:** `agent_message: str`

Agent/model-facing summary. Allows agents to correct failures without exposing verification internals.

**Allowed:**

* "Missing required field: customer\_id"
* "Verification failed — claim not supported"
* "Could not deterministically verify"

**Forbidden:**

* Detection signatures
* Rule IDs
* Internal regex patterns
* Prompt injection indicators
* Security bypass guidance
* Verification implementation details

### Layer 2 — Developer Diagnostics

**Field:** `developer_fields: dict`

Application-developer-facing structured evidence. Includes `constraint_id`, `expected`/`actual` values, `advisory_checks`, `methods_used`, and engine-specific evidence.

```python theme={null}
developer_fields = {
    "constraint_id": "math_verifier.irr_non_convergent",
    "iterations": 100,
    "final_npv": "0.7",
    "converged": False,
    "advisory_checks": [
        {
            "name": "llm_fallback",
            "advisory_only": True,
            "constraint_id": "fact_verifier.llm_advisory_only",
            "details": {"llm_verdict": "SUPPORTED", "llm_confidence": 0.65},
        },
    ],
}
```

### Layer 3 — Proof Diagnostics

**Field:** `proof_ref: Optional[str]`

Cryptographic hash (`sha256:...`) of retained proof artifact. Present only when `status == VERIFIED` and proof was established. `None` for `UNVERIFIABLE` / `BLOCKED`.

**This is the authority bit.** It says the verdict was decided, not that the claim holds: a claim proven false or code proven unsafe is `VERIFIED` with a `proof_ref` too. Downstream gates admit through `admission_decision()`, which also requires `developer_fields.is_valid`:

```python theme={null}
from qwed_new.core.diagnostics import AdmissionDecision, admission_decision

if admission_decision(result) is AdmissionDecision.ADMIT:
    # Proven and valid: admissible for control flow
    admit()
else:
    # Proven false or unsafe, unverifiable, or blocked: reject for control flow
    block()
```

## Status taxonomy

Three states only — no proliferation.

| Status | Meaning | `proof_ref` | Control flow |
| - | - | - | - |
| `VERIFIED` | Claim deterministically proven | Required (non-empty) | May admit |
| `UNVERIFIABLE` | Claim could not be proven | `None` | Must reject |
| `BLOCKED` | Verification could not be attempted | `None` | Must reject |

Richer distinctions (ambiguity, insufficient evidence, non-convergence, provider drift) live in `developer_fields.constraint_id`, not in status values. This keeps the taxonomy small while preserving diagnostic richness.

> [!WARNING]
>
> **No `HEURISTIC`, `AMBIGUOUS`, or `CORRECTION_NEEDED` statuses.** Ambiguity IS unverifiability — the distinction is structured in `constraint_id`, not in the status string.

**Proven false is `VERIFIED`.** A claim the engine disproves (for example the math claim `2+2=5`) is a decided verdict. It is `VERIFIED` with `developer_fields.is_valid: false`, carries a `proof_ref`, and admission is `BLOCKED` (`DENY`). `UNVERIFIABLE` means the engine could neither prove nor disprove the claim. The math `VerificationEngine` no longer emits the legacy `CORRECTION_NEEDED` status. It returns `DISPROVEN` for a decided mismatch.

## Key invariants

### VERIFIED requires proof

`__post_init__` raises `ValueError` if `status == VERIFIED` and `proof_ref` is `None` or empty. "VERIFIED without proof" is impossible to construct — not a caller convention, a type-level invariant.

```python theme={null}
# This raises ValueError
DiagnosticResult(
    status=DiagnosticStatus.VERIFIED,
    agent_message="ok",
    developer_fields={},
    proof_ref=None,  # ← impossible
)
```

### Non-VERIFIED rejects proof

The inverse is also enforced — `UNVERIFIABLE` and `BLOCKED` must have `proof_ref = None`. A non-pass state with a proof hash is a contract violation.

### Frozen dataclasses

Both `DiagnosticResult` and `AdvisoryCheck` are `frozen=True`. Post-construction mutation of `proof_ref` or `status` is blocked — prevents bypassing the authority contract.

```python theme={null}
r = DiagnosticResult.unverifiable("no", {})
r.proof_ref = "sha256:fake"  # ← raises FrozenInstanceError
```

### Advisory checks never influence verdicts

`AdvisoryCheck` represents non-proof-bearing analysis (LLM fallback, NLI entailment, VLM interpretation, heuristic consistency checks). It populates `developer_fields.advisory_checks` with `advisory_only=True` enforced via `__post_init__`.

Advisory checks **never** set `status` or `proof_ref`. This structurally enforces the constraint: *diagnostics must never originate from model reasoning, confidence, or self-assessment.*

## Usage

### Constructing results

```python theme={null}
from qwed_new.core.diagnostics import DiagnosticResult, DiagnosticStatus, AdvisoryCheck

# VERIFIED with proof
result = DiagnosticResult.verified(
    agent_message="Claim verified — unique mode match",
    developer_fields={
        "statistic": "mode",
        "calculated_value": 1,
        "claimed_value": 1,
        "modes": [1],
        "modes_count": 1,
        "constraint_id": "math_verifier.mode_unique_match",
    },
    evidence={"calculated": 1, "claimed": 1, "modes": [1]},
)
# result.proof_ref = "sha256:abc123..."
# result.is_authoritative = True

# UNVERIFIABLE — cannot prove
result = DiagnosticResult.unverifiable(
    agent_message="Statistical claim inconclusive — dataset has multiple modes",
    developer_fields={
        "modes": [1, 2],
        "modes_count": 2,
        "tie_detected": True,
        "constraint_id": "math_verifier.mode_ambiguous_tie",
    },
)
# result.proof_ref = None
# result.is_authoritative = False

# BLOCKED — cannot attempt
result = DiagnosticResult.blocked(
    agent_message="Logic verification blocked — variable declarations missing",
    developer_fields={
        "missing_declarations": ["x"],
        "constraint_id": "logic_verifier.explicit_declarations_required",
    },
)
# result.proof_ref = None

# VERIFIED, proven false — a counter-proof
result = DiagnosticResult.disproven(
    agent_message="Claimed value is proven false",
    developer_fields={"calculated_value": 4, "claimed_value": 5},
    evidence={"expression": "2+2", "calculated_value": 4, "claimed_value": 5},
)
# result.status = VERIFIED
# result.developer_fields["is_valid"] = False
# result.is_authoritative = True
```

`disproven()` builds a `VERIFIED` result and forces `developer_fields.is_valid` to `False`, so `admission_decision()` returns `BLOCKED`.

### Advisory checks

```python theme={null}
# LLM fallback as advisory — never verdict-deciding
result = DiagnosticResult.unverifiable(
    agent_message="Claim could not be deterministically verified",
    developer_fields={
        "deterministic_verdict": "INSUFFICIENT_EVIDENCE",
        "advisory_checks": [
            AdvisoryCheck(
                name="llm_fallback",
                advisory_only=True,
                constraint_id="fact_verifier.llm_advisory_only",
                details={"llm_verdict": "SUPPORTED", "llm_confidence": 0.65},
            ),
        ],
    },
)
```

### Downstream gate

```python theme={null}
from qwed_new.core.diagnostics import AdmissionDecision, admission_decision

def release_gate(result: DiagnosticResult) -> bool:
    """Admit only proven-valid results."""
    if admission_decision(result) is not AdmissionDecision.ADMIT:
        log_block(result.constraint_id, result.agent_message)
        return False
    return True
```

Don't gate on `is_authoritative` alone. A proven-false or proven-unsafe result is authoritative too, because it carries a counter-proof.

### Serialization

```python theme={null}
# to_dict — JSON-safe, AdvisoryCheck instances serialized
d = result.to_dict()
# {"status": "VERIFIED", "agent_message": "...", "developer_fields": {...}, "proof_ref": "sha256:...", "is_authoritative": True}

# from_dict — deserialize with validation
result = DiagnosticResult.from_dict(d)
```

### Migrating legacy engines

`from_legacy_dict()` converts ad-hoc engine dicts to `DiagnosticResult` for fail-closed states:

```python theme={null}
# Legacy engine returns dict
legacy = {"is_correct": False, "status": "INCONCLUSIVE", "calculated_value": 3}

# Migrate
result = DiagnosticResult.from_legacy_dict(legacy, engine="math")
# DiagnosticResult(status=UNVERIFIABLE, proof_ref=None, ...)
```

> [!WARNING]
>
> `from_legacy_dict` **raises** for legacy `VERIFIED` and `DISPROVEN` results. Both are decided verdicts that need a `proof_ref`, and the legacy dict doesn't carry the proof artifacts. Use `DiagnosticResult.verified()` or `DiagnosticResult.disproven()` with explicit evidence instead.

## API reference

### `DiagnosticStatus`

```python theme={null}
class DiagnosticStatus(str, Enum):
    VERIFIED = "VERIFIED"
    UNVERIFIABLE = "UNVERIFIABLE"
    BLOCKED = "BLOCKED"
```

### `DiagnosticResult`

| Field | Type | Description |
| - | - | - |
| `status` | `DiagnosticStatus` | Tri-state verdict |
| `agent_message` | `str` | Layer 1 — agent-safe summary |
| `developer_fields` | `dict` | Layer 2 — structured evidence |
| `proof_ref` | `Optional[str]` | Layer 3 — sha256 hash of proof artifact |

| Property | Type | Description |
| - | - | - |
| `is_verified` | `bool` | True only when status is VERIFIED |
| `is_authoritative` | `bool` | True when proof\_ref is not None (authority bit) |
| `is_fail_closed` | `bool` | True when status is UNVERIFIABLE or BLOCKED |
| `constraint_id` | `Optional[str]` | Primary constraint identifier from developer\_fields |
| `advisory_checks` | `List[AdvisoryCheck]` | Deserialized advisory checks |

| Method | Description |
| - | - |
| `to_dict()` | Serialize to JSON-safe dict |
| `from_dict(data)` | Deserialize from dict |
| `verified(agent_message, developer_fields, evidence)` | Construct VERIFIED with computed proof\_ref |
| `disproven(agent_message, developer_fields, evidence)` | Construct VERIFIED for a claim proven false, with `is_valid` forced to `False` |
| `unverifiable(agent_message, developer_fields)` | Construct UNVERIFIABLE |
| `blocked(agent_message, developer_fields)` | Construct BLOCKED |
| `from_legacy_dict(data, engine)` | Migrate ad-hoc engine dict |

### `AdvisoryCheck`

| Field | Type | Description |
| - | - | - |
| `name` | `str` | Check name (e.g. "llm\_fallback") |
| `advisory_only` | `bool` | Always True — structurally enforced |
| `constraint_id` | `Optional[str]` | Constraint identifier |
| `details` | `dict` | Check-specific details |

### `compute_proof_ref(evidence)`

```python theme={null}
def compute_proof_ref(evidence: Dict[str, Any]) -> str:
    """Deterministic sha256 hash of JSON-serialized evidence."""
    # Returns "sha256:abcdef..."
```

Evidence must be JSON-serializable. Non-serializable values raise `ValueError` (fail-closed) — callers must pre-convert.

## Constraints

> [!WARNING]
>
> **Non-negotiable constraints (per #204):**
>
> 1. Diagnostics are NOT explainability — no confidence scores, no chain-of-thought, no model reasoning
> 2. All diagnostic fields must originate from verification results, constraints, rule evaluation, schema validation, or proof systems
> 3. Agent-safe diagnostics must never expose detection logic, rule IDs, regex patterns, or security bypass guidance
> 4. Existing fail-closed behavior must not be weakened

## Migration path

The `DiagnosticResult` contract is additive. Existing engines continue to work with their ad-hoc return types. Migration is incremental:

1. **v5.2.0** (this release) — contract established, 83 tests
2. **Engine conformance** — each engine adopts `DiagnosticResult` in a separate PR
3. **Full migration** — ad-hoc dicts and `VerificationResult` dataclasses replaced

### Engines being migrated

| Engine | Issue | Diagnostic focus |
| - | - | - |
| MathVerifier (mode) | [#129](https://github.com/QWED-AI/qwed-verification/issues/129) | Layer 2 + 3 |
| MathVerifier (eigenvalues) | [#130](https://github.com/QWED-AI/qwed-verification/issues/130) | Layer 2 + 3 |
| MathVerifier (IRR) | [#131](https://github.com/QWED-AI/qwed-verification/issues/131) | Layer 3 |
| FactVerifier (LLM fallback) | [#133](https://github.com/QWED-AI/qwed-verification/issues/133) | Layer 1 + 2 + 3 |
| ImageVerifier (VLM fallback) | [#134](https://github.com/QWED-AI/qwed-verification/issues/134) | Layer 1 + 3 |
| LogicVerifier (symbol inference) | [#162](https://github.com/QWED-AI/qwed-verification/issues/162) | Layer 2 + 3 |
| GraphFactVerifier (NLI) | [#163](https://github.com/QWED-AI/qwed-verification/issues/163) | Layer 2 + 3 |
| ReasoningVerifier (no proof) | [#164](https://github.com/QWED-AI/qwed-verification/issues/164) | Layer 3 |
| FactVerifier (provider drift) | [#190](https://github.com/QWED-AI/qwed-verification/issues/190) | Layer 3 |
| SecureCodeExecutor | [#205](https://github.com/QWED-AI/qwed-verification/issues/205) | Layer 1 + 2 |

## Related

> [card] [Attestations](https://docs.qwedai.tech/advanced/attestations.md)
>
> Cryptographic proof artifacts and JWT signing for verification results.

> [card] [Architecture overview](https://docs.qwedai.tech/architecture.md)
>
> High-level QWED architecture and verification lifecycle.

> [card] [Determinism levels](https://docs.qwedai.tech/advanced/determinism-guarantee.md)
>
> How QWED enforces deterministic verification outcomes.

> [card] [Compliance](https://docs.qwedai.tech/advanced/compliance.md)
>
> Signed, hash-chained audit trail, security activity summary and GDPR data export.

> [card] [QWED Legal diagnostics](https://docs.qwedai.tech/legal/overview.md)
>
> Per-guard `VERIFIED` / `UNVERIFIABLE` / `BLOCKED` mapping and `proof_ref` semantics for the legal guards.


## Related topics

- [QWED architecture: translation, verification, guards](https://docs.qwedai.tech/architecture.md)
- [Verification engine tiers: Proof, Policy, Advisory](https://docs.qwedai.tech/engines/overview.md)
- [QWED Legal: deterministic verification guards for legal AI](https://docs.qwedai.tech/legal/overview.md)
- [Logic engine — Z3 SAT/SMT verification with QWED-Logic DSL](https://docs.qwedai.tech/engines/logic.md)
- [Changelog](https://docs.qwedai.tech/changelog.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.