Files
hold-slayer/.claude/rules/call-safety.md
Robert Helewka 4a3c14d4af
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 45s
CVE Scan & Docker Build / build-and-push (push) Successful in 1m53s
docs: add Claude AI assistant rules and configuration
Add comprehensive rule documentation for AI-assisted development covering
authentication surfaces, outbound-call safety invariants, and other project
conventions to guide Claude's understanding of critical system behaviors.
2026-07-28 19:01:38 -04:00

46 lines
2.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: emergency-number guard, concurrent-call cap, dial-path discipline — the outbound safety invariants
paths:
- "core/dial_plan.py"
- "core/gateway.py"
- "mcp_server/server.py"
- "api/calls.py"
---
# Outbound-call safety
This is the safety core. A defect here means an unwanted real phone call, a
runaway telephony bill, or — the one that matters most — an AI-initiated
emergency call that should have been impossible.
- **`is_emergency_number()` is the single source of truth for refusal.** It
lives in `core/dial_plan.py`, blocks `911`/`9911`/`112` and their E.164
mappings (`_BLOCKED` = keys values), and normalises the input (strips
spaces, dashes, dots) before comparing. If you learn of another dialled form
that reaches emergency services, add it to `EMERGENCY_NUMBERS` — never work
around the guard.
- **Every outbound path goes through `gateway.make_call`, and the guard is its
first check** — before the concurrency cap, before `create_call`, before any
SIP action. REST `make_call`, MCP `make_call`, receptionist ring-back, and any
transfer to an external number must funnel through it. **Do not introduce a
dial path that reaches `sip_engine.make_call` without passing the guard
first.** If a new feature needs to place a call, it calls `gateway.make_call`.
- **The concurrency cap is spend control, not decoration.** `max_concurrent_calls`
(default 4) is checked in `make_call` after the emergency guard and before
call creation, using `len(call_manager.active_calls)`. Keep the ordering:
refuse-emergency, then cap, then create. Don't move the count to after
creation (it would off-by-one) and don't remove it.
- **Refusals raise `ValueError` at the gateway; surfaces translate it.**
`make_call` raises `ValueError` for both refusals; the MCP tool converts it to
`ToolError`, and the REST layer maps it to a 4xx. Keep refusals as exceptions
from the gateway — a refused call must never look like a placed one.
- **The README's `[!CAUTION]` block is a contract, not decoration.** If you
change refusal behaviour, the README caution and this rule must stay true. A
system that quietly stops refusing emergency numbers is a serious regression
even if every test still passes — add/keep a test that asserts each blocked
form is refused.