| """ |
| Safety rewriter — keeps generated text inside the active policy profile. |
| |
| Phase 1 implementation: deterministic redaction + soft-rewrite for |
| flagged substrings. LLM phase will call an instructed rewriter |
| behind the same ``rewrite_for_safety`` signature. |
| |
| Used by: |
| - script.generator after narration is written, to catch drift |
| - runtime (batch 6) free-input echo path |
| - publish (batch 8) pre-publish lint |
| """ |
| from __future__ import annotations |
|
|
| import re |
| from dataclasses import dataclass |
| from typing import List, Tuple |
|
|
| from ..policy.profiles import PolicyProfile |
|
|
|
|
| @dataclass(frozen=True) |
| class RewriteResult: |
| """Outcome of a safety rewrite pass.""" |
|
|
| text: str |
| changed: bool |
| removed_terms: List[str] |
| hints: List[str] |
|
|
|
|
| |
| |
| _UNIVERSAL_REDACT: List[Tuple[re.Pattern[str], str]] = [ |
| (re.compile(r"\b(child|minor|under\s*1[0-7])\b", re.I), "[redacted-age]"), |
| (re.compile(r"\b(kill|stab|rape)\b", re.I), "[redacted-violence]"), |
| ] |
|
|
|
|
| def rewrite_for_safety( |
| text: str, profile: PolicyProfile, |
| ) -> RewriteResult: |
| """Apply universal redactions + profile-specific softening.""" |
| if not text: |
| return RewriteResult(text="", changed=False, removed_terms=[], hints=[]) |
|
|
| out = text |
| removed: List[str] = [] |
|
|
| for pattern, replacement in _UNIVERSAL_REDACT: |
| new = pattern.sub(replacement, out) |
| if new != out: |
| removed.append(pattern.pattern) |
| out = new |
|
|
| |
| |
| if profile.allowed_intents and "explicit_request" not in profile.allowed_intents: |
| explicit = re.compile( |
| r"\b(pussy|cock|dick|nude|naked)\b", re.I, |
| ) |
| new = explicit.sub("[redacted-explicit]", out) |
| if new != out: |
| removed.append("explicit_out_of_profile") |
| out = new |
|
|
| hints: List[str] = [] |
| if removed: |
| hints.append(f"rewrote {len(removed)} problematic pattern(s)") |
|
|
| return RewriteResult( |
| text=out, |
| changed=out != text, |
| removed_terms=removed, |
| hints=hints, |
| ) |
|
|