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.
This commit is contained in:
63
.claude/rules/config-startup.md
Normal file
63
.claude/rules/config-startup.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
description: pydantic-settings nested sub-configs + get_settings() singleton, SecretStr discipline, startup refusals, .env hygiene
|
||||
paths:
|
||||
- "config.py"
|
||||
- "main.py"
|
||||
- ".env*"
|
||||
---
|
||||
|
||||
# Config & startup
|
||||
|
||||
Config is `Settings` in [config.py](../../config.py): a root `BaseSettings` with
|
||||
**nested sub-config models**, each carrying its own `env_prefix`. Read it through
|
||||
the `get_settings()` cached singleton.
|
||||
|
||||
- **`get_settings()` is the only accessor.** It memoises a single `Settings()`.
|
||||
Don't construct `Settings()` elsewhere, and don't reach for `os.environ.get`
|
||||
for Hold Slayer config — the whole point of the sub-config layout is that every
|
||||
knob has one typed home.
|
||||
|
||||
- **Sub-configs own their prefixes.** `SIP_TRUNK_*` → `SIPTrunkSettings`, `LLM_*`
|
||||
→ `LLMSettings`, `TTS_*` → `TTSSettings`, `RECEPTIONIST_*` →
|
||||
`ReceptionistSettings`, `CASDOOR_*` → `CasdoorSettings`, `CLASSIFIER_*`,
|
||||
`SPEACHES_*`, `GATEWAY_SIP_*`. Root vars (`DATABASE_URL`, `HOST`, `PORT`,
|
||||
`MAX_CONCURRENT_CALLS`, `USE_MOCK_SIP`, `NOTIFY_SMS_NUMBER`, `DEBUG`,
|
||||
`LOG_LEVEL`, and the auth cross-cutters `OWNER_NAME` + `PUBLIC_BASE_URL`) are
|
||||
unprefixed on the root model. A new knob goes in the sub-config it belongs to;
|
||||
a genuinely new subsystem gets its own sub-config + prefix, not flat root vars.
|
||||
- `OWNER_NAME` (the owner's Casdoor username) and `PUBLIC_BASE_URL` (OAuth
|
||||
discovery base) live on the root, not under `CASDOOR_`, because they cross-cut
|
||||
every surface — like `DATABASE_URL`. The Casdoor *connection* knobs
|
||||
(`enabled`/`endpoint`/`client_id`/`client_secret`/`org_name`/`app_name`) live
|
||||
under `CASDOOR_`.
|
||||
- `HoldSlayerSettings` uses `env_prefix_allow_empty=True` with explicit
|
||||
`validation_alias`es (`DEFAULT_TRANSFER_DEVICE`, `MAX_HOLD_TIME`,
|
||||
`HOLD_CHECK_INTERVAL`) — i.e. those three are read *unprefixed* by design.
|
||||
Follow that pattern only if you deliberately want an unprefixed name.
|
||||
|
||||
- **Secrets are `SecretStr`.** `casdoor.client_secret`, `sip_trunk.password`,
|
||||
`llm.api_key`, `tts.api_key`. Keep new secrets as `SecretStr`; call
|
||||
`.get_secret_value()` only at the point of use (outbound header, SDK
|
||||
construction) — never store the bare string, never log it.
|
||||
|
||||
- **Startup refuses bad configs loudly, then exits.** In [main.py](../../main.py):
|
||||
- `_check_startup_config` exits if `DATABASE_URL` is unset; if `CASDOOR_ENABLED`
|
||||
is true but any of `CASDOOR_ENDPOINT`/`CLIENT_ID`/`CLIENT_SECRET`/`OWNER_NAME`
|
||||
is missing; or if `CASDOOR_ENABLED` is false while `HOST` is off-loopback
|
||||
(dev-owner mode would be open to the network). See the
|
||||
[auth-surfaces rule](auth-surfaces.md).
|
||||
- `_handle_db_error` turns raw asyncpg failures into human-readable guidance
|
||||
(wrong password, missing DB, connection refused, bad hostname) and `sys.exit(1)`.
|
||||
- SIP engine build failure and mock-vs-real are surfaced, not swallowed.
|
||||
**Keep the pattern: a misconfiguration stops the service with a message a human
|
||||
can act on — never a silent degrade or a stack trace with no guidance.**
|
||||
|
||||
- **`use_mock_sip` is opt-in for a reason.** An unconfigured trunk without
|
||||
`USE_MOCK_SIP=true` must fail startup rather than boot a gateway that silently
|
||||
can't place real calls. Don't default it to `True`.
|
||||
|
||||
- **`.env` hygiene:** `.env` is gitignored and holds real secrets — never commit
|
||||
it, never treat the checked-out `.env` as a template. Only `.env.example`
|
||||
(placeholders) is committed, and it must stay in sync with the models here and
|
||||
the README config table. Every new var lands in all three: model,
|
||||
`.env.example`, README.
|
||||
Reference in New Issue
Block a user