File size: 2,293 Bytes
921d377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
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]  # human-readable diagnostics (debugging aid)


# Fixed substitution list for universally-problematic tokens.
# These always redact regardless of profile.
_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

    # Profile-specific: if the profile's allowed list is narrow,
    # strip any explicit language that slipped in.
    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,
    )