oc_chat
Loading...
Searching...
No Matches
risk_class.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/core/risk_class.py
4
5"""
6Deterministic RiskClass scorer (M8.0 WU-9b; LD-1, §5.3/§5.6).
7
8Purpose
9-------
10Layer-1 deterministic assignment of a :data:`RiskClass` (``low<medium<high<critical``)
11to a proposed operation, from its capability verb and target properties. The **max
12rung any clause grants wins**; missing/invalid metadata (or an unknown capability)
13fails closed to ``"critical"`` (the §5.6 sentinel). This is the *assignment* only —
14its emission on a ``permission_decision`` event rides on the M8.2 single-decision
15machinery (the escalate-to-monitor threshold ``risk_class ≥ high`` is M8.5).
16
17The mapping follows LD-1: **critical** = ``authz`` · write/persist/exec on the
18protected-write *hard-deny* tier · read/list on the protected-read class · invalid
19metadata; **high** = a human-confirm-tier protected-write (``human_confirm_target``)
20· importable/in-tree write under ``guarded``/``isolated`` (F62) · un-templated exec ·
21egress to a non-trusted host; **medium** = a plain write/persist · templated exec ·
22egress to an already-trusted host; **low** = a non-protected read/list and inert
23diagnostics. The *predicate* that recognizes a human-confirm-tier path
24(``*settings*.json``/``pyproject.toml``/…) lands with F70 (WU-8 follow-up); this
25scorer takes it as the ``human_confirm_target`` input so the rung is LD-1-complete.
26
27Responsibilities
28----------------
29- :func:`assign_risk_class` — capability × target → ``RiskClass`` (deterministic).
30
31Contracts
32---------
33- Fail-closed: ``metadata_ok=False`` or an out-of-enum capability ⇒ ``"critical"``.
34- Egress with unknown trust (``host_trusted is None``) ⇒ ``"high"`` (not trusted).
35- Pure / side-effect-free; the protected-read/write classes are the WU-2/WU-5
36 predicates so the rung tracks the actual hard-deny tiers.
37
38Dependencies
39------------
40- oc_diagnostics (structured debug logging via _dbg)
41- option_c_agent_contracts (RiskClass / CapabilityClass closed enums — LD-1)
42- oc_chat.tools._sandbox (protected-read and protected-write path predicates)
43
44Diagnostics
45-----------
46Domain: OC_CHAT.CORE
47Levels:
48 L2 — a fail-closed critical assignment (invalid capability / metadata)
49 L3 — per-assignment rung detail
50 L4 — deep tracing
51"""
52from __future__ import annotations
53
54from typing import get_args
55
56from oc_diagnostics import _dbg
57from option_c_agent_contracts import CapabilityClass, RiskClass
58
59from oc_chat.tools._sandbox import is_protected_read_path, is_protected_write_path
60
61_CAPABILITIES: frozenset[str] = frozenset(get_args(CapabilityClass))
62
63
65 capability: str,
66 *,
67 target_path: str | None = None,
68 host_trusted: bool | None = None,
69 templated: bool = False,
70 importable_under_enforcing: bool = False,
71 human_confirm_target: bool = False,
72 metadata_ok: bool = True,
73) -> RiskClass:
74 """Return the deterministic :data:`RiskClass` for a proposed operation (LD-1).
75
76 Parameters
77 ----------
78 capability : str
79 A :data:`CapabilityClass` verb. An out-of-enum value fails closed.
80 target_path : str | None
81 The operation's target (for the protected-read / protected-write checks).
82 host_trusted : bool | None
83 For egress capabilities: whether the destination host is session-trusted.
84 ``None`` (unknown) is treated as not-trusted (fail-closed).
85 templated : bool
86 For ``exec``: whether the argv is a fixed template (vs un-templated shell).
87 importable_under_enforcing : bool
88 For write/persist: whether the target is an importable/in-tree path under a
89 ``guarded``/``isolated`` profile (F62).
90 human_confirm_target : bool
91 For write/persist: whether the target is a human-confirm-tier path
92 (``*settings*.json``/``pyproject.toml``/…). The recognizing predicate is F70.
93 metadata_ok : bool
94 ``False`` ⇒ missing/invalid TOOL_METADATA ⇒ fail closed to ``"critical"``.
95 """
96 func = "assign_risk_class"
97 if not metadata_ok or capability not in _CAPABILITIES:
98 _dbg("OC_CHAT.CORE", func,
99 f"fail-closed critical: metadata_ok={metadata_ok} capability={capability!r}", 2)
100 return "critical"
101
102 cap = capability
103
104 # ── critical (the max rung) ──────────────────────────────────────────────
105 if cap == "authz":
106 return "critical"
107 if cap in ("read", "list") and target_path and is_protected_read_path(target_path):
108 return "critical"
109 if cap in ("write", "persist", "exec") and target_path and is_protected_write_path(target_path):
110 return "critical"
111
112 # ── high ─────────────────────────────────────────────────────────────────
113 if cap in ("write", "persist") and (importable_under_enforcing or human_confirm_target):
114 return "high"
115 if cap == "exec" and not templated:
116 return "high"
117 if cap in ("egress_fetch", "egress_provider"):
118 return "medium" if host_trusted is True else "high"
119
120 # ── medium ───────────────────────────────────────────────────────────────
121 if cap == "exec" and templated:
122 return "medium"
123 if cap in ("write", "persist"):
124 return "medium"
125
126 # ── low (non-protected read/list, inert diagnostics) ─────────────────────
127 return "low"
128
129
130__all__ = ["assign_risk_class"]
RiskClass assign_risk_class(str capability, *, str|None target_path=None, bool|None host_trusted=None, bool templated=False, bool importable_under_enforcing=False, bool human_confirm_target=False, bool metadata_ok=True)
Definition risk_class.py:73