6Deterministic RiskClass scorer (M8.0 WU-9b; LD-1, §5.3/§5.6).
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).
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.
29- :func:`assign_risk_class` — capability × target → ``RiskClass`` (deterministic).
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.
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)
48 L2 — a fail-closed critical assignment (invalid capability / metadata)
49 L3 — per-assignment rung detail
52from __future__
import annotations
54from typing
import get_args
56from oc_diagnostics
import _dbg
57from option_c_agent_contracts
import CapabilityClass, RiskClass
61_CAPABILITIES: frozenset[str] = frozenset(get_args(CapabilityClass))
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,
74 """Return the deterministic :data:`RiskClass` for a proposed operation (LD-1).
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).
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.
94 ``False`` ⇒ missing/invalid TOOL_METADATA ⇒ fail closed to ``"critical"``.
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)
107 if cap
in (
"read",
"list")
and target_path
and is_protected_read_path(target_path):
109 if cap
in (
"write",
"persist",
"exec")
and target_path
and is_protected_write_path(target_path):
113 if cap
in (
"write",
"persist")
and (importable_under_enforcing
or human_confirm_target):
115 if cap ==
"exec" and not templated:
117 if cap
in (
"egress_fetch",
"egress_provider"):
118 return "medium" if host_trusted
is True else "high"
121 if cap ==
"exec" and templated:
123 if cap
in (
"write",
"persist"):
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)