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.
46 lines
2.3 KiB
Markdown
46 lines
2.3 KiB
Markdown
---
|
||
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.
|