# AI Call Center / Umid / Umidjon — whole-project production audit

**Audit date:** 2026-08-11 (Asia/Tashkent)  
**Disposition:** advisory and read-only; no code, configuration, services, calls, databases, credentials, or production data were changed  
**Current production snapshot:** `D:\Pystart\codex\.codex_tmp\ai_call_audit_20260811\live`  
**Historical/local project:** `D:\YangiAsrDialer` — useful as deployment history, but not a trustworthy current source of truth

## Executive verdict

The system contains thoughtful production engineering—20 ms audio pacing, company-specific knowledge slicing, tool allowlists, durable local lead files, modality-aware usage accounting, reconnect/watchdog logic, and operational recording delivery. It is not a toy.

However, it is not yet safe to call the AI quality-controlled or release-controlled. The largest risks are:

1. **A reachable outbound scenario can quote a generic Grand Ta'lim course price of 95,000 so'm, although 95,000 is valid only for arithmetic and the active catalog lists most language courses at 340,000–545,000+ so'm.**
2. **The main inbound AI receptionist has recordings and health monitoring, but no dependable automated semantic/Uzbek-quality evaluation loop.** Bad prices, unnatural Uzbek, lost interruptions, or bad lead fields can persist undetected.
3. **Gemini connection establishment has no end-to-end deadline.** The project's own health code records a realized 35-hour incident in which all six workers wedged and 186 calls received silence.
4. **The model is ordered to treat a contradictory, partly unverified, partly expired knowledge base as complete and to answer confidently.** This converts data uncertainty into confident misinformation.
5. **Lead/CRM persistence and recording correlation have race and durability gaps.** A GT CRM failure can be marked successful; farewell can outrun lead persistence; recordings can be matched by approximate time rather than a stable Asterisk ID.
6. **The static prompt is both expensive and cognitively noisy.** The 30-day production ledger records 327 million input-text tokens, zero cached tokens, and sharply compounding cost on long calls.
7. **PII handling, service privilege, local secret ACLs, source control, dependency reproducibility, test coverage, and deployment rollback are below production-grade.**
8. **Historical local scripts contain credential-shaped literal assignments.** If any remains active, it is an immediate rotation/revocation issue.

### Scorecard

| Area | Assessment | Reason |
|---|---:|---|
| Architecture | 6/10 | Sensible components and several durable paths; source-of-truth and cross-component contracts are weak |
| Reliability | 4/10 | A silent 35-hour outage already occurred; deadlines, recovery, correlation, and outcome outboxes are incomplete |
| Uzbek conversation quality | 5/10, **not certified** | Good intent and cultural guidance, but contradictory rules, half-duplex speech loss, and no current native-audio release gate |
| Factual grounding | 3/10 | Contradictory facts, stale promotions, unresolved values, and an instruction to answer confidently anyway |
| Lead/CRM integrity | 5/10 | Strong caller-ID preference and atomic notes, but model-controlled fields and terminal-state races remain |
| Token/cost management | 5/10 | Good per-company slicing and token ledger; large re-billed context, no compression, incomplete caps, and QA accounting gaps |
| Security/privacy | 3/10 | Excessive root privilege, broad local secret ACLs, world-readable transcript/ledger artifacts, PII logs, and risky lookup authorization |
| QA/observability | 4/10 | Health/cost/recording visibility exists; the actual inbound AI's factual and conversational behavior is not continuously scored |
| Release engineering | 2/10 | No Git metadata, no reproducible Python manifest, stale README, almost no isolated automated tests, and manual deployment history |

The scores are a prioritization aid, not a compliance certification.

## Scope and method

The audit used the maximum available parallel specialist capacity—three specialist auditors plus the primary reviewer—with repeated independent passes for architecture/reliability, Uzbek quality and grounding, token cost/performance, and security/privacy. This environment supports four concurrent agents including the primary reviewer, so it was not possible to run hundreds simultaneously. Findings were then cross-checked against:

- the read-only live code and knowledge snapshot;
- current service/runtime metadata and file permissions, without retrieving secret values;
- current production cost-ledger aggregates and sender logs;
- the local imported project and deployment history;
- current official Gemini 3.1 Live pricing, migration notes, and cost-management guidance.

No test call, load test, deployment, restart, database mutation, secret rotation, or configuration edit was performed because this is production.

## Priority findings

### F-01 — P0 immediate: generic `c_lead` can quote the wrong 95,000 so'm price

**Evidence.** The active scenario tells the model to determine the prospect's language/level and then say “oyiga 95 ming” at [ya_gateway.py:422](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:422). The active Grand Ta'lim catalog lists common group-course prices around 340,000–545,000 so'm and individual courses up to 3,000,000 so'm at [knowledge_c.txt:64](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/knowledge_c.txt:64). The 95,000 figure applies to mathematics/arithmetic at [knowledge_c.txt:85](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/knowledge_c.txt:85), not a generic language-course lead.

The scenario is reachable for any outbound call context carrying `scenario="c_lead"`; the imported dashboard exposes it for single and batch use. Inbound routing uses `c_inbound`/`cc_inbound`, so this is **not** an inbound defect. Recent automatic or manual use was not proven from the snapshot.

**Impact.** Direct loss of trust, false advertising risk, angry prospects, and bad conversion. It also teaches the QA system the wrong behavior if the judge inherits inconsistent grounding.

**Safe action.** Quarantine the exact-price sentence from the outbound scenario until a business owner validates a course × branch × modality × age price catalog. Do not substitute another global price. Ask the missing dimensions before quoting.

### F-02 — P0 quality-control gap: inbound AI conversations are not semantically evaluated

Inbound calls are recorded, delivered to Telegram, represented in `qa.db`, and watched for worker health. That is useful operational coverage. It is not conversational QA.

`qa_worker` explicitly records and skips every AudioSocket AI call at [qa_worker.py:646](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/qa_worker.py:646). The analytics queries explicitly filter to `is_ai_call=0` at [qa_analytics.py:94](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/qa_analytics.py:94). The separate auto-transcriber recognizes only outbound recording filenames at [auto_transcriber.py:49](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/auto_transcriber.py:49).

The runtime archive contained 4,014 historical transcript/critique JSON files, but the newest was 2026-07-22 while production code/knowledge changed in August. Those files cannot certify the current inbound operator.

**Impact.** There is no reliable current feedback loop for factual accuracy, native Uzbek, interruption recovery, repetition, lead accuracy, or wrong-branch routing. Prompt regressions can remain invisible until a person complains.

**Safe action.** Add a shadow, cost-capped sample of existing inbound recordings. Do not post automated judgments to staff until the judge has been calibrated against native reviewers and one authoritative fact manifest.

### F-03 — P0 reliability: Gemini connect/reconnect can wedge workers before the call deadline starts

Initial connection and reconnect have no explicit async deadline at [ya_gateway.py:1240](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1240) and [ya_gateway.py:1310](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1310). `MAX_CALL_SEC` begins only after the connection and task setup at [ya_gateway.py:1561](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1561). The health module documents the realized failure: all six workers wedged for 35 hours and 186 calls heard silence at [gw_health.py:6](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/gw_health.py:6).

Only the initial header bytes have a timeout; frame bodies, connection establishment, and some fallback writes are outside a true end-to-end call deadline.

**Safe action.** Measure current connect p99, then canary explicit initial/reconnect deadlines on one worker with the existing inbound fallback and outbound failure-receipt path. Also shorten the health “alive” freshness window: the current 120-minute criterion can delay healing a new/partial wedge.

### F-04 — P0/P1 grounding: the AI is required to be confident in a non-authoritative KB

The system prompt declares the KB “to'liq va ISHONCHLI” and forbids hedging at [ya_gateway.py:222](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:222). Yet the current files contain:

- 11 `TEKSHIRISH KERAK` markers in `knowledge_c.txt`, 12 in `knowledge_cc.txt`, and one in `knowledge_m.txt`;
- wrong or ambiguous QA grounding for online course prices and Namangan;
- 2007 founding vs 2008 operation without explicit semantic labels;
- expired May/June promotions embedded as timeless prose;
- internally inconsistent schedules such as “ertalab 17:30”;
- school enrollment timing stated both as the end of June and by June 14–15.

The QA truth file claims broad ~370,000 so'm online pricing and Namangan availability at [qa_grounding.txt:23](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/qa_grounding.txt:23), conflicting with the model-visible catalog at [knowledge_c.txt:43](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/knowledge_c.txt:43) and [knowledge_c.txt:68](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/knowledge_c.txt:68). A judge can therefore reward a false answer.

**Safe action.** Create a signed/versioned fact manifest with owner, source, organization, branch, service, modality, age group, exact value, `valid_from`, `valid_to`, and status (`verified`, `unknown`, `prohibited_to_quote`). Generate model KBs, scenario facts, and QA grounding from it. Quarantine unresolved facts rather than forcing a confident guess.

### F-05 — P1 business integrity: GT CRM failures can be permanently treated as success

GT CRM exceptions can emerge as `{"error": ...}` without `ok:false` at [ya_gateway.py:785](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:785) and [ya_gateway.py:867](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:867). The dispatcher marks a result failed only when `result.get("ok") is False` at [ya_gateway.py:1386](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1386). A response containing only `error` is therefore treated as written. There is no durable CRM-outcome outbox after the call.

**Impact.** Lost follow-up status, misleading operational reports, and prospects silently dropped from campaigns.

**Safe action.** Define success as `ok is True` plus the expected remote identifier/status. Persist every intended result to a durable outbox before the remote write; record retries and terminal failure separately. Test only with mocked CRM responses before a canary.

### S-01 — P0 conditional: embedded credential literals in historical local scripts

Without displaying or testing any value, the audit confirmed credential-assignment lines containing non-placeholder-looking literals in the historical local project:

- Telegram bot assignment at [missed_calls_to_telegram.sh:7](/D:/YangiAsrDialer/missed_calls_to_telegram.sh:7);
- Gemini/API assignments at [analyze_call.py:8](/D:/YangiAsrDialer/analyze_call.py:8), [analyze_batch.py:20](/D:/YangiAsrDialer/analyze_batch.py:20), [analyze_campaign.py:16](/D:/YangiAsrDialer/analyze_campaign.py:16), [synth_faq.py:17](/D:/YangiAsrDialer/synth_faq.py:17), and [synth_faq_v2.py:21](/D:/YangiAsrDialer/synth_faq_v2.py:21).

These files are historical/local, so the audit does **not** claim the credentials are current, externally exposed, or still valid. The project has no Git metadata, so repository-history exposure could not be assessed.

**Safe action.** Privately inventory each credential fingerprint and provider—not the raw value—then revoke/rotate any active key or bot token. Replace literal assignments with the established protected configuration path. Search backups and any real repository history before considering the incident closed. Do not paste credentials into tickets, Telegram, or this report.

### F-06 — P1 privacy: broad transcript/ledger access, indefinite retention, and PII logging

Read-only runtime inspection found:

- `/opt/ya-gateway/auto_transcripts` mode `0755`, with 4,014 transcript/critique JSON files mode `0644`, retained since 2026-06-08;
- `/opt/ya-gateway/inbound_messages` mode `0755`, with 4,463 files; contents are `0600`, but filenames include phone numbers and directory listing reveals them;
- `/var/lib/ai-recordings-tg/gemini_cost.db`, `sent.db`, `inflight.db`, and `leads.db` mode `0644`; `qa.db` is correctly `0600`;
- gateway logs full model tool arguments and full lead objects—including names, phones, and messages—at [ya_gateway.py:759](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:759) and [ya_gateway.py:843](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:843);
- recording jobs embed caller and lead details but are created with ordinary `open("w")` under the process umask at [ya_gateway.py:719](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:719).

On the local Windows project, `secrets.ini` inherits **Modify** for Authenticated Users and **ReadAndExecute** for BUILTIN Users. The remote helper passes the SSH password via PuTTY's `-pw` argument at [run.ps1:19](/D:/YangiAsrDialer/_audit/run.ps1:19), making it briefly visible in process arguments. Secret values were not copied into this report.

**Safe action.** Define approved retention periods and legal/consent basis; move phone numbers out of filenames; restrict directories and DBs to the service group; redact/hash phones and suppress raw lead/tool logs; use a credential method that does not expose a password in argv; narrow the Windows ACL, then rotate credentials because previous local access cannot be disproved. Preserve an access-controlled backup and test restoration before deleting retained data.

No end-to-end subject index currently maps one caller across CRM ID, phone(s), AudioSocket/CDR identifiers, recording, lead revisions, transcript, QA row, cost event, logs, and Telegram message IDs. Any retention or deletion design must cover all of those copies. Recording/transcription/third-party-processing notice and consent requirements should be reviewed with the organization's policy/legal owner; this audit makes no legal conclusion.

### F-07 — P1 privacy/authorization: inbound caller can induce lookup of another plausible phone

`lookup_applicant` accepts a model-supplied phone at [ya_gateway.py:529](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:529), calls `/api/applicants?telefon=...`, and returns the first full record at [ya_gateway.py:672](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:672). Inbound callers have the tool. The dispatcher replaces empty, short, or obviously fabricated placeholder numbers, but preserves a plausible different 9-digit number at [ya_gateway.py:1391](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1391).

**Impact.** A caller may cause another applicant's record to be sent into the model context without identity/authorization verification. Returning the entire record also violates data minimization.

**Safe action.** Bind default lookup to the server-observed caller ID and return only fields needed for the conversation. Put “lookup another number” behind a separate, explicit flow with dictated-number readback and an approved verification rule. Never rely on the model to authorize access.

### F-08 — P1 conversation quality: half-duplex drops every caller interruption

Caller audio is deliberately discarded while the AI is speaking at [ya_gateway.py:1247](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1247) and [ya_gateway.py:1273](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1273). This protects against echo, but it also drops real “yo'q”, “to'xtang”, names, digits, and corrections.

**Impact.** Umid/Umida appears inattentive, repeats questions, records wrong names or branches, and lengthens calls.

**Safe action.** Do not simply remove the guard. Build staff/synthetic tests with echo, then trial server interruption events plus immediate output-buffer clearing on one isolated line. Until then, shorten turns and avoid a pause after every sentence.

### F-09 — P1 lead integrity: farewell, persistence, and corrections are not a gateway-enforced state machine

There are strong controls: caller ID is preferred, common placeholders are filtered, the local note is created with `O_EXCL` and `0600`, and Telegram corrections can edit the prior message. But the gateway has no authoritative `lead_saved`/`name_confirmed` terminal state. Farewell transcription can set `ending` at [ya_gateway.py:1353](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1353); the first-completed task cancels the others, while a `take_message` call may still be persisting. The model-supplied name is stored directly at [ya_gateway.py:843](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:843). Corrections create another local JSON even when the Telegram message is edited.

Historical June rechecks found clearly spoken names lost, wrong, or empty in 32% of the inspected sample, including one caller recorded as “Umidjon.” That is historical evidence, not a current defect rate; the current code still lacks a self-name guard and deterministic finalization invariant.

**Safe action.** Use one stable `lead_id` per call; atomically set `lead_saved` immediately after local persistence; queue Telegram afterward; reject exact operator/self names and placeholders without inventing a replacement; store `caller_phone` and `callback_phone` separately; and guarantee every inbound call finishes as truthful `captured` or `uncaptured`.

### F-10 — P1 cost and model attention: very large static prompts are re-billed each turn

Before dynamic call data and tool schemas, the approximate prompt sizes are:

| Route | Approximate static characters |
|---|---:|
| `m` | 20,883 |
| `u` | 40,871 |
| `c` | 53,784 |
| `cc` / full fallback | 80,612 |

Unknown/missing scenario codes silently fall back to the full knowledge base at [ya_gateway.py:318](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:318). The Live API reprocesses active context each turn, and the code accumulates the reported input totals at [ya_gateway.py:68](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:68). There is no context-window compression in the Live config at [ya_gateway.py:1185](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1185).

The selected model officially does not support explicit context caching. Do not design savings around caching.

**Safe action.** First canonicalize/deduplicate owner-approved facts and fail closed on unknown routing. Then canary context-window compression with a trigger above the observed first-turn static baseline and enough recent history to retain names, numbers, objections, and commitments. A retrieval tool should not be the default replacement because synchronous tools can add latency and another full-context generation.

### F-11 — P1 vendor compatibility: current Gemini 3.1 event semantics need an immediate canary check

Google's current migration notes say a server event may contain multiple content parts and clients must process all of them. The gateway prefers `response.data` and only walks `model_turn.parts` when it is absent at [ya_gateway.py:1424](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1424), which may omit a part depending on SDK semantics.

The same notes say `send_client_content` is for initial-history seeding with `history_config.initial_history_in_client_content`, while realtime text updates should use realtime input. The config has no `history_config`, yet greeting and watchdog nudge use `send_client_content` at [ya_gateway.py:1261](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1261) and [ya_gateway.py:1535](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1535).

This is a **compatibility risk requiring SDK-level confirmation**, not proof that audio is currently dropped. Validate the installed `google-genai 2.8.0` behavior offline and on one canary worker before changing production. See [Google's Gemini 3.1 Live model/migration notes](https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-live-preview).

### F-12 — P1 latency: synchronous function calls can create 10–45 seconds of silence

Tool execution is moved off the event loop, which protects other calls, but Gemini 3.1 function calling is synchronous: this caller waits for the response. Applicant lookup can wait 45 seconds before local fallback at [ya_gateway.py:571](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:571); Telegram lead delivery can wait 10 seconds after the durable write at [ya_gateway.py:710](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:710). `tool_busy` suppresses the dead-air watchdog at [ya_gateway.py:1505](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1505).

**Safe action.** Use a measured 3–5 second lookup deadline then the existing local index; return immediately after atomic local lead persistence; deliver Telegram asynchronously with retries; keep longer CRM retries after farewell rather than making the caller wait.

### F-13 — P1 budget controls: total cap is incomplete and inbound refusal is unsafe

`over_cap()` reads the central Gemini ledger at [gemini_meter.py:197](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/gemini_meter.py:197), while QA usage is stored separately at [qa_worker.py:516](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/qa_worker.py:516). The advertised cap therefore excludes QA. DB errors fail open. Multiple simultaneous calls have no reservation and can all pass a completed-spend check before overshooting.

The default is alert-only, but if `cap_action=refuse` is enabled, the inbound handler hangs up before the normal fallback, notification, final event, and recording job at [ya_gateway.py:1211](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1211). The actual production value was intentionally not copied from secrets/configuration.

**Safe action.** Reconcile all Live/transcriber/QA spend into one cap, reserve estimated concurrent-call cost, alert on ledger failure, and make refusal direction-aware: defer outbound; for inbound, play the fallback and create a callback record.

### F-14 — P1 workflow durability: text/audio split, approximate correlation, and at-most-once loss window

The gateway sends lead text to one configured chat at [ya_gateway.py:120](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:120), while the sender routes the Yangi Asr DID's audio to another group at [ai_recordings_to_telegram.py:70](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ai_recordings_to_telegram.py:70). It carries an old-chat reply ID but sets `allow_sending_without_reply=true` at [ai_recordings_to_telegram.py:476](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ai_recordings_to_telegram.py:476). August logs showed 293 successful sends, including 113 Yangi Asr sends, and no HTTP 400/permanent-failure/retry-drop entries. The observed problem is split, usually unthreaded/context-poor work—not proven audio loss.

Structurally, however, the sender marks a UID inflight before upload and can treat uncertain failures as terminal. Recording matching uses caller and nearest CDR timestamp within ±90 seconds rather than a stable Asterisk `uniqueid` at [ai_recordings_to_telegram.py:262](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ai_recordings_to_telegram.py:262). Closely spaced redials can attach the wrong recording. Completion job names have only second timestamp plus process ID, allowing same-worker same-second overwrite at [ya_gateway.py:752](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:752).

**Safe action.** Carry one stable call/lead/Asterisk ID through gateway, CDR, CRM, spool, Telegram, and metrics; use an idempotent retry state machine; reconcile text/audio routing; prove recovery by fault-injection tests.

### F-15 — P1/P2 event-loop and capacity risks

CRM tools are thread-offloaded, but budget checks, meter writes, credit alerts, and some failure notifications are synchronous inside the async call handler at [ya_gateway.py:1217](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1217) and [ya_gateway.py:1614](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1614). SQLite waits or Telegram timeouts can stall sibling calls on the same worker. The listener also has no explicit admission limit and uses an unbounded output queue.

**Safe action.** Queue/thread-offload accounting and alerts, bound per-worker sessions/queues, reserve capacity for fallback, and alert on saturation. Preserve the current six-worker `reuse_port` design until measured load tests on an isolated environment justify a change.

### F-16 — P1/P2 QA/transcriber accounting defects

- `QA_STORE_TRANSCRIPT=0` does not alter the actual prompt. The active builder always asks for a full transcript at [qa_prompt.py:6](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/qa_prompt.py:6), so the system can pay for text it later discards.
- QA parses JSON before returning usage; parse failures can trigger up to five retries without ledgering already billed attempts at [qa_worker.py:352](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/qa_worker.py:352) and [qa_worker.py:682](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/qa_worker.py:682).
- The auto-transcriber has no daily cap or steady-state sampling strategy and skips backlog on restart.
- Live input/output transcriptions incur an additional text-output surcharge, but the current ledger cannot separate that component. They also support farewell logic, so disabling them blindly is unsafe.

**Safe action.** Make the transcript flag modify the actual schema/prompt; ledger every API attempt before parsing; use stable stratified sampling for ordinary calls while retaining 100% of failures, complaints, new prompt/model/voice canaries, unusually long/high-cost calls, and weekly minimums per organization/scenario.

### F-17 — P1 Uzbek naturalness rules conflict or encourage scripted speech

Examples:

- “Uzbek only” at [ya_gateway.py:179](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:179) conflicts with Russian adaptation in [operator_guide.txt:18](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/operator_guide.txt:18).
- The prompt mandates very slow speech and a pause after every sentence at [ya_gateway.py:206](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:206), which can sound robotic and amplifies the half-duplex problem.
- `aka/opa` guidance guesses from gender rather than known age; `inshaAllah/barakalla` is proactively encouraged rather than mirrored from the caller.
- “AI” is forced as “ey-ay”; a more TTS-stable disclosure may be “sun'iy intellekt yordamchisiman.”
- Mixed Latin/Cyrillic contamination appears in active prompt/tool text.

**Safe policy.** Start in Uzbek; mirror Russian only when the caller uses or requests it; default to neutral `siz`; use kinship/religious expressions only when the caller's register supports them; keep turns short; adapt pace to the caller; test audio, not merely transcripts.

### F-18 — P1 release engineering and least privilege are below production grade

`D:\YangiAsrDialer` has no Git metadata. The README still describes a spreadsheet/DTMF demo rather than the current Gemini Live system. There is no root-level reproducible Python dependency manifest or lock, no CI, and almost no isolated regression coverage outside audit scripts. Live and local gateway hashes differ; several live components do not have a reliable local source-of-truth history. Deployment/restart scripts and historical “selftests” can touch real Asterisk calls; one historical incident left two zombie self-test channels consuming CPU for 7.5 days.

All six gateway services were observed active/enabled and running as root. No `User`, `Group`, `NoNewPrivileges`, `ProtectSystem`, `ProtectHome`, or `PrivateTmp` restriction was observed. `gateway.ini` and its backups are correctly `0600 root:root`, but a compromise of the network-facing root service has full host impact.

**Safe action.** First place the exact live snapshot and deployment manifests under private version control without secrets or PII; add hash/version metadata to every call; build mocked unit/integration tests; create a transactional backup/canary/rollback deployment; then reduce service privileges and add systemd sandboxing one restriction at a time in staging/canary. Do not harden all directives simultaneously on production.

### F-19 — P2 prompt injection from CRM data is only superficially sanitized

Outbound record fields are inserted into the system prompt. `_san()` collapses whitespace and truncates text at [ya_gateway.py:1013](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1013), but does not enforce the expected character/type format or neutralize section-like instruction text. Since registration/CRM fields can originate from users, a crafted name or status could steer the model.

**Safe action.** Validate each field against its domain (name, registration type, ISO date), keep untrusted values in a clearly typed data object rather than instruction prose, and add prompt-injection regression cases. Do not rely on delimiters alone.

### F-20 — P2 ledger price is stale

The meter prices Gemini 3.1 Live text output at $6/M at [gemini_meter.py:36](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/gemini_meter.py:36); Google's current rate is $4.50/M. The other configured Live modality rates match. This overstates only the text-output component; input text and output audio dominate more of the bill. Raw modality counts are retained, so history can be recomputed. See [official Gemini pricing](https://ai.google.dev/gemini-api/docs/pricing).

### F-21 — P2 privacy trade-off: external-provider retention settings are not governed in the project

The gateway enables Gemini session resumption at [ya_gateway.py:1193](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/ya_gateway.py:1193). Google's current documentation says this can retain Live conversation state, including audio and text, for up to 24 hours; achieving provider-side zero data retention requires avoiding session resumption. QA also uploads recordings above its inline threshold through the Files API at [qa_worker.py:352](/D:/Pystart/codex/.codex_tmp/ai_call_audit_20260811/live/qa_worker.py:352) and does not explicitly delete the uploaded object; Google documents automatic expiry after 48 hours and supports immediate deletion. See [Gemini ZDR guidance](https://ai.google.dev/gemini-api/docs/zdr?hl=en) and [Files API retention/deletion](https://ai.google.dev/gemini-api/docs/files).

Do not disable resumption immediately: it is part of call recovery. First establish the organization's retention requirement and verify paid-tier, abuse-logging, data-sharing/ZDR, Files cleanup, and DPA settings. Then compare recovery and privacy in a controlled canary. Telegram group membership, former-employee removal, forwarding, bot scope, and auto-delete settings also require an owner review outside this codebase.

## Current token/cost baseline

The following are read-only aggregates from the current production `gemini_cost.db` for the most recent 30 days. Dollar values are the local ledger's estimates; they slightly overstate the text-output component because of F-20.

| Segment | Calls/events | Ledger cost | Average/call | Avg duration | Avg summed prompt tokens/call |
|---|---:|---:|---:|---:|---:|
| Live total | 2,845 | $323.02 | $0.1135 | 63.3 s | 121,692 |
| Inbound | 2,136 | $281.48 | $0.1318 | 69.5 s | 142,256 |
| Outbound | 709 | $41.53 | $0.0586 | 44.7 s | 59,740 |

Additional facts:

- input text: approximately **326.8 million** tokens;
- input audio: approximately **15.7 million** tokens;
- cached tokens: **0**;
- 31 calls cost at least $0.50, totaling $20.86 and averaging 262.5 seconds;
- `cc_inbound` averaged roughly 248k prompt tokens at 5 turns, 479k at 10 turns, 654k at 14 turns, and 892k at 20 turns;
- spend by prompt-token band: `<50k` $25.94, `50–100k` $36.67, `100–200k` $100.21, `200–400k` $114.98, `400k+` $45.21.

This confirms that repeated static input—not audio alone—is the primary optimization target. Google documents that Live bills all tokens in the active context on each turn, including accumulated history, and recommends context-window compression for long sessions. Transcription text carries an extra output-token surcharge. See [Google's Live API cost guidance](https://ai.google.dev/gemini-api/docs/live-api/best-practices).

## Cost optimization without losing speaking quality

Use this order; every behavior-changing item requires an offline/staff test, a 5% canary, and an explicit rollback gate.

1. **Fix measurement, not behavior:** correct pricing; store prompt/KB/hash/model/voice/SDK, per-generation modality tokens, connect/turn/tool latency, fallback/nudge/reconnect/compression flags, and lead/CRM delivery state.
2. **Fix facts and deduplicate:** one owner-approved manifest should generate KB slices, scenario facts, and QA truth. Removing repeated/conflicting content can reduce cost and improve accuracy simultaneously. Do not delete facts merely because they appear duplicate; compare fact IDs and regression cases.
3. **Fail closed on unknown scenario routing:** do not silently load the full 80k-character context.
4. **Canary context-window compression on calls with at least four turns:** choose a trigger above the route's measured first-turn baseline, not the generic vendor example. Roll back on forgotten names/numbers, repeated questions, or contradictory commitments.
5. **Shorten synchronous tools and queue notifications:** lower dead air and avoid the extra turns callers use to repeat themselves.
6. **Fix QA output and retry accounting; sample ordinary transcriber traffic:** preserve 100% anomaly and release-canary coverage.
7. **Test a pre-recorded generic inbound greeting:** historical assumptions suggest roughly $0.015 text-input saving on a `u` call and $0.027 on a `cc` call by removing one full-context generation, plus faster pickup. These are estimates, not current guaranteed savings; voice continuity and caller-overlap must pass a blind audio test.
8. **A/B transcription modes only after deterministic terminal handling exists:** keep caller-goodbye detection intact until an alternative is proven.
9. **Barge-in last:** it may improve naturalness and shorten calls, but has the highest echo/interruption engineering risk.

Do **not** start with a cheaper model, blanket prompt deletion, unsupported Live caching, global VAD reduction, disabling QA/transcription, or a synchronous retrieval tool. Those moves have a higher chance of making Umid/Umida cheaper but dumber.

## Native-Uzbek release gate

Build the test set from the approved fact manifest and expected structured lead records—not from current `qa_grounding.txt` and not solely from an LLM judge.

### Minimum coverage

- all organizations/routes: `u`, `c`, `m`, `cc`, inbound and relevant outbound scenarios;
- prices by language, branch, online/offline, child/adult, group/individual;
- Tashkent, Fergana/Andijon, Samarqand/Bukhara, Surxondaryo/Qashqadaryo, and Karakalpak-influenced Uzbek speakers;
- Russian/Uzbek code-switching, colloquial contractions, elderly/young callers, parents, unknown gender;
- rare/multiword names, name corrections, refused name, parent/child distinction;
- caller-ID, dictated callback, withheld number, branch absent/correction;
- quiet/noisy audio, short answers, silence, repeated question, fast goodbye;
- interruptions during long AI speech, price/name/branch corrections;
- Telegram/CRM/filesystem/Gemini timeout and cancellation fault sequences.

Recommended corpus: 120 scripted conversations repeated three times, 200 consented native-speaker audio clips from at least eight speakers, 40 deterministic event-order replays, and 20 mocked fault-injection cases, with 20% held out from prompt tuning.

### Hard blockers

- wrong organization, sensitive price/date/branch fact, invented identity/phone/applicant ID, wrong-branch route: **zero observed**;
- usable callback retained when caller ID or a confirmed dictated number exists: **100%**;
- each inbound call ends as truthful `captured` or `uncaptured`: **100%**;
- duplicate corrected lead or false spoken “saved” after persistence failure: **zero**;
- deterministic event/fault suite: **100% pass**.

### Statistical gates

- saved-name precision ≥99%; clear-and-confirmed name recall ≥97%; correction accuracy ≥99%;
- branch precision 100%, recall ≥95%; interest-summary accuracy ≥95%;
- known question answered before lead pitch ≥95%; duplicate/near-duplicate turns ≤1%;
- no unexplained silence over five seconds; response p95 after caller stops ≤3 seconds;
- native reviewer naturalness mean ≥4.2/5, lower confidence bound ≥4.0;
- name/number intelligibility from audio ≥98%;
- prompt tokens and cost per successfully captured lead do not regress more than 5% unless an approved quality gain explains it.

Use three blinded native-Uzbek reviewers with an anchored rubric; compare names/numbers against source annotations rather than ASR; freeze Asia/Tashkent time for date tests; record prompt/KB/fact/test/model/voice hashes; and adjudicate low reviewer agreement. LLM judgment should be secondary and calibrated.

## Remediation roadmap

### First 0–48 hours: contain the highest-risk paths

1. Freeze nonessential prompt/KB/deployment changes until hashes and owners are recorded.
2. Owner-review and quarantine the generic `c_lead` 95k sentence and stale 95k language-course text; do not invent a replacement price.
3. Privately verify whether historical embedded credentials are still active; revoke/rotate active ones without exposing their values.
4. Verify current `cap_action`, installed SDK behavior, recent `c_lead` traffic, and whether any current inbound quality sampler exists; these checks are read-only.
5. Restrict PII directory/DB access and Windows secret ACL through a backed-up, staged permissions change; plan broader secret rotation.
6. Add alerts/reporting for GT CRM errors, uncaptured inbound calls, missing recording linkage, full-KB fallback, and connection timeouts before changing call behavior.

### Days 3–7: make facts, lead state, and failures deterministic

1. Create the versioned fact manifest and regenerate KB/QA inputs from it.
2. Implement mocked regression tests and the initial native-Uzbek/fact suite.
3. Add connection/reconnect/end-to-end deadlines on one canary worker.
4. Correct GT success semantics and add a durable CRM outbox.
5. Split local lead commit from Telegram delivery; add stable `lead_id`, `lead_saved`, revision, captured/uncaptured terminal status, and separate phone sources.
6. Add shadow inbound AI QA with strict cost/sample caps.

### Weeks 2–4: reduce cost and blast radius safely

1. Canonicalize/deduplicate KBs only after fact-owner approval and regression diffs.
2. Canary context compression, pre-recorded inbound greeting, and tool deadlines independently.
3. Reconcile text/audio routing and carry stable call IDs through CDR, recording, CRM, Telegram, and metrics.
4. Establish private Git source of truth, dependency lock, CI, immutable deployment manifest, backup/rollback, and one-worker canary.
5. Move services from root and add systemd restrictions incrementally.
6. Apply retention, redaction, access logging, backup/restore testing, and periodic credential rotation.
7. Only then trial full barge-in/duplex behavior with echo and interruption tests.

## Strengths to preserve

- exact 20 ms audio pacing and 16 kHz input;
- company-specific knowledge slicing;
- per-mode tool allowlists;
- trusted server-side applicant ID for outbound result writes;
- real caller-ID preference and placeholder filters;
- atomic `0600` inbound note creation;
- most CRM/tool work already off the audio loop;
- usage accumulation with raw modality counts;
- VAD, reconnect, session-resumption handle, duration cap, and dead-air watchdog;
- recording/CDR settle logic, ffmpeg timeout, file locking, and atomic job publication;
- durable human-operator QA queue with caps/retries;
- existing cost/balance/fallback and independent health probes.

These should be protected by regression tests rather than rewritten wholesale.

## Limitations and unverified items

- No current production audio was played or sent to external evaluators, so current prosody/pronunciation/naturalness is **not certified**.
- Historical transcripts and June rechecks are evidence of prior weaknesses, not current defect rates.
- Recent `c_lead` usage was not proven; the defect is confirmed reachable.
- Secret values, current balance, and private configuration contents were not copied into the audit.
- Credential-shaped historical literals were not tested against providers, so active/revoked status remains unknown.
- No live call, destructive failure test, load test, service restart, restore drill, or real CRM/Telegram write was attempted.
- Legal consent/retention requirements, backup encryption, restore success, current dialplan, and full invoice-versus-meter reconciliation still require owner-approved follow-up.

## Recommended decision

Do not launch a broad prompt rewrite or model downgrade. First contain the wrong-price path, establish a canonical fact source and shadow inbound QA, add connection deadlines, make lead/CRM outcomes deterministic, and close the most obvious PII/privilege gaps. Once those foundations exist, the project can safely optimize context and turn-taking without making the AI operator cheaper but less capable.
