File size: 7,751 Bytes
ecad52f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2618f7a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Iterable

from .engine import (
    Claim,
    Classification,
    Evidence,
    Policy,
    VerificationResult,
    Verdict,
    verify_claim,
)


GUARD_VERSION = "2.0.0"


class Realism(str, Enum):
    POSSIBLE = "possible"
    UNCERTAIN = "uncertain"
    IMPOSSIBLE = "impossible"


class GuardStage(str, Enum):
    SELF_CHECK = "self_check"
    VERIFICATION = "verification"


class UnknownSeverity(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


@dataclass(frozen=True)
class UnknownRecord:
    description: str
    severity: UnknownSeverity = UnknownSeverity.MEDIUM
    reviewed: bool = False
    resolved: bool = False
    blocking: bool = False
    resolution_reference: str | None = None


@dataclass(frozen=True)
class SelfCheck:
    realism: Realism
    known: tuple[str, ...]
    assumptions: tuple[str, ...]
    unknowns: tuple[str, ...]
    assumptions_reviewed: bool
    unknowns_reviewed: bool
    unknown_records: tuple[UnknownRecord, ...] = ()


@dataclass(frozen=True)
class GuardResult:
    stage: GuardStage
    classification: Classification
    verdict: Verdict
    reason: str
    verification: VerificationResult | None
    receipt: dict


def _validate_self_check(check: SelfCheck) -> None:
    if not isinstance(check, SelfCheck):
        raise TypeError("self_check must be SelfCheck")
    if not isinstance(check.realism, Realism):
        raise TypeError("self_check.realism must be Realism")
    if type(check.assumptions_reviewed) is not bool:
        raise TypeError("assumptions_reviewed must be bool")
    if type(check.unknowns_reviewed) is not bool:
        raise TypeError("unknowns_reviewed must be bool")

    for field_name, values in (
        ("known", check.known),
        ("assumptions", check.assumptions),
        ("unknowns", check.unknowns),
    ):
        if not isinstance(values, tuple):
            raise TypeError(f"{field_name} must be tuple")
        if any(not isinstance(item, str) or not item.strip() for item in values):
            raise ValueError(f"{field_name} items must be non-empty strings")

    if not isinstance(check.unknown_records, tuple):
        raise TypeError("unknown_records must be tuple")
    for item in check.unknown_records:
        if not isinstance(item, UnknownRecord):
            raise TypeError("unknown_records must contain UnknownRecord values")
        if not isinstance(item.description, str) or not item.description.strip():
            raise ValueError("unknown record description must be non-empty")
        if not isinstance(item.severity, UnknownSeverity):
            raise TypeError("unknown record severity must be UnknownSeverity")
        if type(item.reviewed) is not bool:
            raise TypeError("unknown record reviewed must be bool")
        if type(item.resolved) is not bool:
            raise TypeError("unknown record resolved must be bool")
        if type(item.blocking) is not bool:
            raise TypeError("unknown record blocking must be bool")
        if item.resolution_reference is not None and (
            not isinstance(item.resolution_reference, str)
            or not item.resolution_reference.strip()
        ):
            raise ValueError("resolution_reference must be non-empty when supplied")
        if item.resolved and (item.blocking or item.severity is UnknownSeverity.CRITICAL):
            if item.resolution_reference is None:
                raise ValueError(
                    "resolved blocking or critical unknown requires resolution_reference"
                )


def _self_check_payload(check: SelfCheck) -> dict:
    return {
        "realism": check.realism.value,
        "known": list(check.known),
        "assumptions": list(check.assumptions),
        "unknowns": list(check.unknowns),
        "assumptions_reviewed": check.assumptions_reviewed,
        "unknowns_reviewed": check.unknowns_reviewed,
        "unknown_records": [
            {
                "description": item.description,
                "severity": item.severity.value,
                "reviewed": item.reviewed,
                "resolved": item.resolved,
                "blocking": item.blocking,
                "resolution_reference": item.resolution_reference,
            }
            for item in check.unknown_records
        ],
    }


def _self_check_result(
    *,
    claim: Claim,
    check: SelfCheck,
    verdict: Verdict,
    reason: str,
) -> GuardResult:
    receipt = {
        "guard_version": GUARD_VERSION,
        "claim_id": claim.claim_id,
        "stage": GuardStage.SELF_CHECK.value,
        "self_check": _self_check_payload(check),
        "verification": None,
        "classification": Classification.UNKNOWN.value,
        "verdict": verdict.value,
        "reason": reason,
    }
    return GuardResult(
        stage=GuardStage.SELF_CHECK,
        classification=Classification.UNKNOWN,
        verdict=verdict,
        reason=reason,
        verification=None,
        receipt=receipt,
    )


def run_guard_loop(
    *,
    claim: Claim,
    evidence: Iterable[Evidence],
    self_check: SelfCheck,
    policy: Policy,
    evaluation_time: datetime,
    human_approved: bool = False,
) -> GuardResult:
    _validate_self_check(self_check)

    if self_check.realism is Realism.IMPOSSIBLE:
        return _self_check_result(
            claim=claim,
            check=self_check,
            verdict=Verdict.BLOCK,
            reason="self_check_impossible",
        )

    if self_check.realism is Realism.UNCERTAIN:
        return _self_check_result(
            claim=claim,
            check=self_check,
            verdict=Verdict.ESCALATE,
            reason="self_check_uncertain",
        )

    unresolved_blocking = tuple(
        item
        for item in self_check.unknown_records
        if not item.resolved
        and (item.blocking or item.severity is UnknownSeverity.CRITICAL)
    )
    if unresolved_blocking:
        return _self_check_result(
            claim=claim,
            check=self_check,
            verdict=Verdict.ESCALATE,
            reason="blocking_unknown_unresolved",
        )

    if not self_check.assumptions_reviewed:
        return _self_check_result(
            claim=claim,
            check=self_check,
            verdict=Verdict.ESCALATE,
            reason="assumptions_not_reviewed",
        )

    if not self_check.unknowns_reviewed:
        return _self_check_result(
            claim=claim,
            check=self_check,
            verdict=Verdict.ESCALATE,
            reason="unknowns_not_reviewed",
        )

    verification = verify_claim(
        claim,
        evidence,
        policy=policy,
        evaluation_time=evaluation_time,
        human_approved=human_approved,
    )

    receipt = {
        "guard_version": GUARD_VERSION,
        "claim_id": claim.claim_id,
        "stage": GuardStage.VERIFICATION.value,
        "self_check": _self_check_payload(self_check),
        "verification": {
            "classification": verification.classification.value,
            "verdict": verification.verdict.value,
            "reason": verification.reason,
            "receipt": verification.receipt,
        },
        "classification": verification.classification.value,
        "verdict": verification.verdict.value,
        "reason": verification.reason,
    }

    return GuardResult(
        stage=GuardStage.VERIFICATION,
        classification=verification.classification,
        verdict=verification.verdict,
        reason=verification.reason,
        verification=verification,
        receipt=receipt,
    )