Purpose
-------
Safety Gate (Layer 2.5) for the ``oct-mcp`` six-layer pipeline.
Enforces the Human-in-the-Loop (HITL) consent protocol for all
write/destructive tool calls, and applies dry-run defaults.
Responsibilities
----------------
- Define :class:`SafetyDecision` — the structured result of a safety
check (proceed, requires_confirmation, dry_run, reason).
- Define :class:`SafetyGate` with a single :meth:`SafetyGate.check`
entry point consumed by :func:`oct.mcp.tools._run_tool_write`.
- Define :func:`_format_confirmation_request` which produces the
structured ``[oct-mcp requires_confirmation] …`` response string
returned to the AI host when confirmation is needed.
HITL Protocol (D-5B-1 — stateless)
-----------------------------------
No server-side pending state.
- **First call** (``confirm=False``, default): safety gate returns
``proceed=False``. The caller returns a
``[oct-mcp requires_confirmation] …`` string to the AI host; no
execution occurs.
- **Second call** (``confirm=True``): safety gate returns
``proceed=True``; full execution proceeds.
The AI host re-submits the identical call with ``confirm=True`` after
the user approves in the host UI. There is no replay window to exploit
(T-08) because each call is evaluated independently.
Dry-run rules (D-5B-2, D-5B-5)
--------------------------------
1. Non-destructive tools → always ``proceed=True``, ``dry_run=False``.
2. Destructive, ``confirm=False`` → ``proceed=False``,
``requires_confirmation=True``.
3. ``oct_format``, ``confirm=True``, ``apply=False`` → ``proceed=True``,
``dry_run=True``.
4. ``oct_format``, ``confirm=True``, ``apply=True`` → ``proceed=True``,
``dry_run=False``.
5. Other destructive, ``confirm=True`` → ``proceed=True``,
``dry_run=False``.
(Tools with their own ``dry_run`` field honour that field directly
— the executor reads it from the validated args dict.)
Diagnostics
-----------
Domain: MCP
Levels:
L2 — lifecycle: safety decision
L4 — deep trace: field values
Contracts
---------
- This module does not import ``click``, the MCP SDK, or any
``oct.mcp.executor`` / ``oct.mcp.tools`` module (no cycles).
- :meth:`SafetyGate.check` never raises for valid input.
- The confirmation request string always starts with the sentinel
``[oct-mcp requires_confirmation]`` so callers can detect it via
simple prefix matching.