# Side-effect-free delivery boundaries

Use this pattern when wiring a generated artifact toward an external channel/API (Telegram, email, social platform, publishing adapter) but the session should not perform the real side effect yet.

## Pattern

1. Write tests against a missing preparation module first, e.g. `from package.telegram.review_sender import prepare_review_delivery`.
2. The RED state should be a missing module/import or missing API, not a real network/API failure.
3. Implement a frozen dataclass that represents the delivery plan, not the delivery action:
   - `text` or payload
   - target/topic/channel metadata
   - `delivery_type`
   - `requires_human_approval=True` when approval is part of the workflow
   - `side_effects=()` to make no-I/O intent explicit
4. Validate safety constraints before any adapter boundary:
   - blank payload rejected
   - platform length/format limits enforced (e.g. Telegram 4096-char text limit)
   - forbidden action language rejected when review and execution must remain separate (e.g. `publish`, `post` in review-only deliveries)
5. Keep real I/O behind a later explicit module/function. Do not call messaging APIs from the preparation function.
6. Verify with both focused tests and a small manual script that feeds the real fixture through the preparation function.

## Example shape

```python
@dataclass(frozen=True)
class ReviewDelivery:
    text: str
    topic: str
    delivery_type: str = "telegram_review"
    requires_human_approval: bool = True
    side_effects: tuple[str, ...] = ()


def prepare_review_delivery(review_text: str, *, topic: str = "review") -> ReviewDelivery:
    text = review_text.strip()
    if not text:
        raise ValueError("review text is required")
    if len(text) > 4096:
        raise ValueError("Telegram message exceeds 4096 characters")
    if "publish" in text.casefold() or "post" in text.casefold():
        raise ValueError("unsafe publishing language is not allowed in review deliveries")
    return ReviewDelivery(text=text, topic=topic)
```

## Why

This creates a testable seam between content/review generation and irreversible or externally visible actions. It lets later integration code consume a boring, validated object instead of smuggling network calls into domain logic.
