﻿diff --git a/.env.example b/.env.example
index 92e7d1e..1e99052 100644
--- a/.env.example
+++ b/.env.example
@@ -36,6 +36,8 @@ AI_API_BASE=
 AI_API_KEY=
 AI_MODEL=stub-telegram-assistant
 AI_TIMEOUT_SECONDS=20
+AI_CUSTOMER_PERSONA_MODE=operator_humanlike
+AI_DISCLOSURE_MODE=hidden
 AI_TELEGRAM_ENABLED=0
 AI_TELEGRAM_ALWAYS_REPLY=0
 AI_TELEGRAM_MAX_CONTEXT_MESSAGES=20
@@ -48,6 +50,7 @@ AI_WHATSAPP_MAX_KB_RESULTS=3
 AI_WHATSAPP_CONFIDENCE_HANDOFF_THRESHOLD=0.65
 AI_VOICE_RUNTIME_SERVICE_URL=http://localhost:8018
 AI_VOICE_ENABLED=0
+AI_VOICE_POLICY_MODE=llm_guarded
 AI_VOICE_QUEUE_CONFIG_JSON={}
 AI_VOICE_ASR_PROVIDER=openai
 AI_VOICE_TTS_PROVIDER=openai
diff --git a/.env.production.template b/.env.production.template
index b65f60d..7f32934 100644
--- a/.env.production.template
+++ b/.env.production.template
@@ -32,6 +32,8 @@ AI_API_BASE=https://<llm-host>/v1
 AI_API_KEY=<llm-api-key>
 AI_MODEL=<llm-model>
 AI_TIMEOUT_SECONDS=20
+AI_CUSTOMER_PERSONA_MODE=operator_humanlike
+AI_DISCLOSURE_MODE=hidden
 AI_TELEGRAM_ENABLED=0
 AI_TELEGRAM_ALWAYS_REPLY=0
 AI_TELEGRAM_MAX_CONTEXT_MESSAGES=20
@@ -43,6 +45,7 @@ AI_WHATSAPP_MAX_CONTEXT_MESSAGES=20
 AI_WHATSAPP_MAX_KB_RESULTS=3
 AI_WHATSAPP_CONFIDENCE_HANDOFF_THRESHOLD=0.65
 AI_VOICE_ENABLED=0
+AI_VOICE_POLICY_MODE=llm_guarded
 AI_VOICE_QUEUE_CONFIG_JSON={"voice_lab_ai":{"mode":"ai_first","agent_profile":"voice_support","handoff_queue_code":"voice_lab","language":"ru"}}
 AI_VOICE_ASR_PROVIDER=openai
 AI_VOICE_TTS_PROVIDER=openai
diff --git a/docs/acceptance/.gitignore b/docs/acceptance/.gitignore
deleted file mode 100644
index 12bd819..0000000
--- a/docs/acceptance/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-track9/[0-9]*/
-track9_1/[0-9]*/
-track10/[0-9]*/
-track11/[0-9]*/
diff --git a/docs/acceptance/track10/README.md b/docs/acceptance/track10/README.md
deleted file mode 100644
index b6e8840..0000000
--- a/docs/acceptance/track10/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Track 10 Acceptance Evidence
-
-Store Track 10 reliability evidence per run:
-
-- `docs/acceptance/track10/<timestamp>/...`
-
-Recommended contents:
-- `voice-latency-latest.json`
-- `command-output.txt`
-- `track10-acceptance.md`
-
-Reference runbook:
-- `docs/runbooks/track10-live-voice-reliability.md`
diff --git a/docs/acceptance/track10/track10-acceptance.md b/docs/acceptance/track10/track10-acceptance.md
deleted file mode 100644
index 396a89f..0000000
--- a/docs/acceptance/track10/track10-acceptance.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# Track 10 Acceptance (Latest)
-
-## Environment
-
-- Date: `2026-03-05`
-- Gateway URL: `http://127.0.0.1:8080`
-- Database URL (masked): `sqlite:///.data_local/mvp_cc.db`
-- Evidence folder: `docs/acceptance/track10/20260305_163330`
-
-## Validation command
-
-```powershell
-python scripts\track10_voice_latency_report.py `
-  --database-url sqlite:///.data_local/mvp_cc.db `
-  --since-minutes 90 `
-  --breach-mode direct `
-  --json-out docs/acceptance/track10/20260305_163330/voice-latency-latest.json
-```
-
-## Result summary
-
-- total calls: `10`
-- active_no_end: `0`
-- ended_no_recording_upload: `0`
-- ended_direct_calls: `4`
-- ended_reconciled_calls: `6`
-- p95 started->ended: `1686.35s`
-- p95 ended->recording upload: `455.25s`
-- p95 direct started->ended: `33.0s`
-- p95 direct ended->recording upload: `2.85s`
-
-## Decision
-
-- [x] GO (with exception)
-- [ ] NO-GO
-
-## Notes
-
-- Direct path remains stable (`active_no_end=0`, `ended_no_recording_upload=0`).
-- Threshold exception recorded: direct `started->ended` is `33s` vs target `30s`.
-- Detailed run: `docs/acceptance/track10/20260305_163330/track10-acceptance.md`
diff --git a/docs/acceptance/track11/README.md b/docs/acceptance/track11/README.md
deleted file mode 100644
index 0862f76..0000000
--- a/docs/acceptance/track11/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Track 11 Acceptance Evidence
-
-Stable summary:
-
-- [track11-acceptance.md](/e:/Zhan/docs/acceptance/track11/track11-acceptance.md)
-
-Store Track 11 QA evidence per run:
-
-- `docs/acceptance/track11/<timestamp>/track11-acceptance.md`
-- `docs/acceptance/track11/<timestamp>/operator-live-calls.json`
-- `docs/acceptance/track11/<timestamp>/bridge-actions.json`
-- `docs/acceptance/track11/<timestamp>/playback-proof.json`
-
-Reference runbook:
-- `docs/runbooks/track11-live-operator-call-control.md`
diff --git a/docs/acceptance/track11/track11-acceptance.md b/docs/acceptance/track11/track11-acceptance.md
deleted file mode 100644
index 7ad1401..0000000
--- a/docs/acceptance/track11/track11-acceptance.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Track 11 Acceptance (Latest)
-
-## Environment
-
-- Date: `2026-03-07`
-- Base URL: `http://localhost:8080`
-- Database URL (masked): `sqlite:///.data_local/mvp_cc.db`
-- Evidence package: [20260307_104748](/e:/Zhan/docs/acceptance/track11/20260307_104748)
-
-## Executed scenario
-
-1. Operator received a live inbound call in `/operator`.
-2. Operator claimed the call.
-3. Operator completed a real hangup path and recording playback was validated.
-4. Operator completed a blind transfer flow from the live-call block.
-
-## Validation summary
-
-- live inbound visibility: `yes`
-- claim persisted: `yes`
-- hangup persisted: `yes`
-- transfer persisted: `yes`
-- `call.connected` present: `yes`
-- `call.transferred` present: `yes`
-- recording playback: `PASS`
-
-## Result
-
-- GO / NO-GO: `GO`
-- Track 11 status: `Accepted`
-
-## Notes
-
-- Detailed package: `docs/acceptance/track11/20260307_104748/track11-acceptance.md`
-- Track 11 keeps browser UI control and external softphone media as the accepted baseline.
diff --git a/docs/acceptance/track11/track11-acceptance.template.md b/docs/acceptance/track11/track11-acceptance.template.md
deleted file mode 100644
index a44d5bc..0000000
--- a/docs/acceptance/track11/track11-acceptance.template.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# Track 11 Acceptance - <timestamp>
-
-## Environment
-
-- Base URL: `<gateway_base_url>`
-- Database: `<database_url>`
-- Asterisk VM: `<asterisk_host>`
-- Operator account: `<operator_user>`
-- Operator extension: `<operator_extension>`
-
-## Executed scenario
-
-1. Caller `1001` called `7000`.
-2. Operator opened `/operator` -> `╨Ц╨╕╨▓╤Л╨╡ ╨╖╨▓╨╛╨╜╨║╨╕`.
-3. Operator clicked `╨Я╤А╨╕╨╜╤П╤В╤М ╨▓ ╤А╨░╨▒╨╛╤В╤Г`.
-4. Voice conversation in `MicroSIP/Zoiper` was confirmed.
-5. Operator executed one or both actions:
-   - [ ] `╨Ч╨░╨▓╨╡╤А╤И╨╕╤В╤М`
-   - [ ] `╨Я╨╡╤А╨╡╨┤╨░╤В╤М` (blind transfer)
-6. Recording was checked in `/supervisor`.
-
-## API and DB evidence
-
-- Live call list saved: `operator-live-calls.json`
-- Action log saved: `bridge-actions.json`
-- Playback proof saved: `playback-proof.json`
-
-## Mandatory checks
-
-- [ ] `call.started` exists (`payload.source=asterisk`)
-- [ ] `call.connected` exists
-- [ ] `call.ended` exists
-- [ ] `recording.ready` exists
-- [ ] optional `call.transferred` exists when transfer path was tested
-- [ ] no unresolved failed call-control actions in the acceptance window
-
-## Decision
-
-- GO / NO-GO: `<decision>`
-- Notes:
-  - `<notes>`
diff --git a/docs/acceptance/track12/20260307_161629/bridge-actions.json b/docs/acceptance/track12/20260307_161629/bridge-actions.json
deleted file mode 100644
index 3289c98..0000000
--- a/docs/acceptance/track12/20260307_161629/bridge-actions.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
-  "latest_call": "1772881979.108",
-  "latest_call_actions": [],
-  "reference_recent_ok_actions": [
-    {
-      "call_id": "1772881673.105",
-      "action_type": "hangup",
-      "actor_user": "operator",
-      "result_status": "ok",
-      "created_at": "2026-03-07T11:08:42+00:00"
-    },
-    {
-      "call_id": "1772881673.105",
-      "action_type": "claim",
-      "actor_user": "operator",
-      "result_status": "ok",
-      "created_at": "2026-03-07T11:08:16+00:00"
-    },
-    {
-      "call_id": "1772876380.93",
-      "action_type": "hangup",
-      "actor_user": "operator",
-      "result_status": "ok",
-      "created_at": "2026-03-07T09:40:02+00:00"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/docs/acceptance/track12/20260307_161629/operator-live-calls-empty.json b/docs/acceptance/track12/20260307_161629/operator-live-calls-empty.json
deleted file mode 100644
index 0637a08..0000000
--- a/docs/acceptance/track12/20260307_161629/operator-live-calls-empty.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
\ No newline at end of file
diff --git a/docs/acceptance/track12/20260307_161629/operator-live-calls.json b/docs/acceptance/track12/20260307_161629/operator-live-calls.json
deleted file mode 100644
index 16517d8..0000000
--- a/docs/acceptance/track12/20260307_161629/operator-live-calls.json
+++ /dev/null
@@ -1,25 +0,0 @@
-[
-  {
-    "call_id": "1772881979.108",
-    "caller_name": "1001",
-    "caller_number": "1001",
-    "channel_name": "PJSIP/2001-00000047",
-    "claimed_at": null,
-    "claimed_by_user": null,
-    "connected_at": "2026-03-07T11:13:05+00:00",
-    "ended_at": null,
-    "hangup_cause": "unknown",
-    "has_recording": false,
-    "interaction_id": "int_0349796d1e",
-    "last_transition_at": "2026-03-07T11:13:26+00:00",
-    "operator_extension": "2001",
-    "queue_code": "voice_lab",
-    "queue_id": "que_23fb0ad07c",
-    "started_at": "2026-03-07T11:13:01+00:00",
-    "status": "active",
-    "telephony_status": "connected",
-    "terminal_action": null,
-    "terminal_target": null,
-    "updated_at": "2026-03-07T11:13:26+00:00"
-  }
-]
\ No newline at end of file
diff --git a/docs/acceptance/track12/20260307_161629/operator-recent-calls.json b/docs/acceptance/track12/20260307_161629/operator-recent-calls.json
deleted file mode 100644
index 0637a08..0000000
--- a/docs/acceptance/track12/20260307_161629/operator-recent-calls.json
+++ /dev/null
@@ -1 +0,0 @@
-[]
\ No newline at end of file
diff --git a/docs/acceptance/track12/20260307_161629/playback-proof.json b/docs/acceptance/track12/20260307_161629/playback-proof.json
deleted file mode 100644
index 9d48bf9..0000000
--- a/docs/acceptance/track12/20260307_161629/playback-proof.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "call_id": "1772881979.108",
-  "interaction_id": "int_0349796d1e",
-  "recording_id": "rec_733ef323b1",
-  "url": "http://127.0.0.1:8080/proxy/recording/recordings/rec_733ef323b1/content?disposition=inline",
-  "status_code": 200,
-  "content_type": "audio/wav",
-  "content_length": 457324,
-  "file_name": "20260307-161300-1772881979.108.wav"
-}
\ No newline at end of file
diff --git a/docs/acceptance/track12/20260307_161629/track12-acceptance.md b/docs/acceptance/track12/20260307_161629/track12-acceptance.md
deleted file mode 100644
index b52681b..0000000
--- a/docs/acceptance/track12/20260307_161629/track12-acceptance.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# Track 12 Acceptance - 20260307_161629
-
-## Environment
-
-- Base URL: `http://localhost:8080`
-- Database: `sqlite:///./.data_local/mvp_cc.db`
-- Asterisk host: `92.38.48.166`
-- Operator user: `operator`
-- Operator extension: `2001`
-
-## Acceptance Basis
-
-Track 12 is a UX/state hardening layer on top of accepted Track 11.  
-This package closes the Track 12 delta and keeps Track 11 control-path proof as the inherited baseline.
-
-Reference control-path package:
-- [track11-acceptance.md](/e:/Zhan/docs/acceptance/track11/20260307_104748/track11-acceptance.md)
-
-Track 12 delta call:
-- `call_id = 1772881979.108`
-- `interaction_id = int_0349796d1e`
-- `recording_id = rec_733ef323b1`
-
-## Executed Scenarios
-
-- [x] Happy path inherited from accepted Track 11 package
-- [x] Transfer path inherited from accepted Track 11 package
-- [x] Passive path: answer in SIP, no browser action, call remains in `live-calls` until real hangup
-- [x] No-fake-cards path: `╨Ц╨╕╨▓╤Л╨╡ ╨╖╨▓╨╛╨╜╨║╨╕` uses bridge data only and does not show `interaction:int_*`
-
-## Delta Evidence
-
-- Active live call snapshot: [operator-live-calls.json](/e:/Zhan/docs/acceptance/track12/20260307_161629/operator-live-calls.json)
-- Recent call snapshot: [operator-recent-calls.json](/e:/Zhan/docs/acceptance/track12/20260307_161629/operator-recent-calls.json)
-- Empty live snapshot after call end: [operator-live-calls-empty.json](/e:/Zhan/docs/acceptance/track12/20260307_161629/operator-live-calls-empty.json)
-- Action log summary: [bridge-actions.json](/e:/Zhan/docs/acceptance/track12/20260307_161629/bridge-actions.json)
-- Playback proof: [playback-proof.json](/e:/Zhan/docs/acceptance/track12/20260307_161629/playback-proof.json)
-- Live monitor trace: [track12-monitor.log](/e:/Zhan/docs/acceptance/track12/20260307_161629/track12-monitor.log)
-
-## What The Monitor Proved
-
-For call `1772881979.108`:
-
-1. At `2026-03-07T11:13:01+00:00` the call entered `/asterisk/live-calls` as:
-   - `status = active`
-   - `telephony_status = ringing`
-2. At `2026-03-07T11:13:05+00:00` it stayed in `/asterisk/live-calls` and moved to:
-   - `telephony_status = connected`
-   - `operator_extension = 2001`
-3. At `2026-03-07T11:13:26+00:00` a synthetic reconciled `call.ended` was emitted, but the call still remained in `/asterisk/live-calls`.
-4. Only at the real final hangup (`2026-03-07T11:13:30+00:00`) the call left `/asterisk/live-calls` and moved to `/asterisk/recent-calls`.
-5. `recording.ready` was emitted and the recording was imported successfully.
-
-This closes the earlier defect where the card could disappear from the operator flow on the reconciled service event instead of the real final hangup.
-
-## Mandatory Checks
-
-- [x] `GET /proxy/asterisk-bridge/asterisk/live-calls` returns only active calls
-- [x] `GET /proxy/asterisk-bridge/asterisk/recent-calls` returns only recent ended calls
-- [x] passive SIP answer does not force the card out of `╨Р╨║╤В╨╕╨▓╨╜╤Л╨╡ ╨╖╨▓╨╛╨╜╨║╨╕` before real hangup
-- [x] final hangup moves the card to `╨в╨╛╨╗╤М╨║╨╛ ╤З╤В╨╛ ╨╖╨░╨▓╨╡╤А╤И╤С╨╜╨╜╤Л╨╡`
-- [x] no `interaction:int_*` fallback cards are used for `╨Ц╨╕╨▓╤Л╨╡ ╨╖╨▓╨╛╨╜╨║╨╕`
-- [x] recording is available for the monitored call
-- [x] Track 11 `claim / hangup / blind-transfer` control path remains covered by the accepted Track 11 package
-
-## Notes
-
-- The initial runtime regression after a local restart was caused by `scripts/local_stack.py` not loading `.env.production`; this is fixed and the launcher now auto-loads `.env.production`, `.env.local`, and `.env`.
-- The current passive-path monitored call ended without browser actions, so `bridge-actions.json` contains no action rows for `1772881979.108`. This is expected for the passive-path check.
-- Control-path actions (`claim`, `hangup`, `blind-transfer`) remain validated by the accepted Track 11 package and were not redefined by Track 12.
-
-## Decision
-
-- GO / NO-GO: `GO`
-- Track 12 status: `Accepted`
diff --git a/docs/acceptance/track12/20260307_161629/track12-monitor.log b/docs/acceptance/track12/20260307_161629/track12-monitor.log
deleted file mode 100644
index c364db0..0000000
--- a/docs/acceptance/track12/20260307_161629/track12-monitor.log
+++ /dev/null
@@ -1,21 +0,0 @@
-[2026-03-07T16:10:31+05:00] monitor {"status": "started", "window_seconds": 180}
-[2026-03-07T16:10:31+05:00] auth {"role": "operator", "status": "ok", "user": "operator"}
-[2026-03-07T16:10:31+05:00] live-calls []
-[2026-03-07T16:10:31+05:00] recent-calls [{"call_id": "1772881673.105", "caller_name": "1001", "caller_number": "1001", "channel_name": "PJSIP/2001-00000045", "claimed_at": "2026-03-07T11:08:26+00:00", "claimed_by_user": "operator", "connected_at": "2026-03-07T11:07:57+00:00", "ended_at": "2026-03-07T11:08:53+00:00", "hangup_cause": "16", "has_recording": true, "interaction_id": "int_052586cdaa", "last_transition_at": "2026-03-07T11:08:53+00:00", "operator_extension": "2001", "queue_code": "voice_lab", "queue_id": "que_23fb0ad07c", "started_at": "2026-03-07T11:07:55+00:00", "status": "ended", "telephony_status": "ended", "terminal_action": "hangup", "terminal_target": null, "updated_at": "2026-03-07T11:08:53+00:00"}]
-[2026-03-07T16:10:31+05:00] voice-events [{"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_9245c8bb30", "event_type": "recording.ready", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "file_name": "20260307-160753-1772881673.105.wav", "linked_id": "1772881673.105", "mime_type": "audio/wav", "remote_path": "/var/spool/asterisk/monitor/mvpcc/20260307-160753-1772881673.105.wav", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_3e2c4eedc7", "event_type": "call.ended", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "hangup_cause": "16", "linked_id": "1772881673.105", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:20+00:00", "event_id": "vev_dbf1ba4399", "event_type": "call.ended", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": null, "hangup_cause": "unknown", "linked_id": "1772881673.105", "reconciled": true, "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:07:57+00:00", "event_id": "vev_8be5ab34b9", "event_type": "call.connected", "interaction_id": "int_052586cdaa", "payload": {"channel_name": "PJSIP/2001-00000045", "claimed_by_user": null, "linked_id": "1772881673.105", "operator_extension": "2001", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:07:55+00:00", "event_id": "vev_2b41d093c7", "event_type": "call.started", "interaction_id": "int_052586cdaa", "payload": {"caller_name": "1001", "caller_number": "1001", "context": "from-softphones", "direction": "inbound", "extension": "7000", "linked_id": "1772881673.105", "source": "asterisk"}}, {"call_id": "1772876380.93", "created_at": "2026-03-07T09:40:12+00:00", "event_id": "vev_4c44dd5888", "event_type": "recording.ready", "interaction_id": "int_69443612e1", "payload": {"duration_seconds": 31, "file_name": "20260307-143940-1772876380.93.wav", "linked_id": "1772876380.93", "mime_type": "audio/wav", "remote_path": "/var/spool/asterisk/monitor/mvpcc/20260307-143940-1772876380.93.wav", "source": "asterisk"}}]
-[2026-03-07T16:10:31+05:00] call-links [{"call_id": "1772881673.105", "claimed_by_user": "operator", "connected_at": "2026-03-07T11:07:57+00:00", "ended_at": "2026-03-07T11:08:53+00:00", "interaction_id": "int_052586cdaa", "operator_extension": "2001", "started_at": "2026-03-07T11:07:55+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772876380.93", "claimed_by_user": null, "connected_at": "2026-03-07T09:39:49+00:00", "ended_at": "2026-03-07T09:40:12+00:00", "interaction_id": "int_69443612e1", "operator_extension": "2001", "started_at": "2026-03-07T09:39:42+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772875171.90", "claimed_by_user": null, "connected_at": "2026-03-07T09:19:36+00:00", "ended_at": "2026-03-07T09:19:39+00:00", "interaction_id": "int_726c36d95d", "operator_extension": "2001", "started_at": "2026-03-07T09:19:33+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772874280.87", "claimed_by_user": null, "connected_at": "2026-03-07T09:04:47+00:00", "ended_at": "2026-03-07T09:04:54+00:00", "interaction_id": "int_6ef8abe6ec", "operator_extension": "2001", "started_at": "2026-03-07T09:04:42+00:00", "status": "ended", "telephony_status": "ended"}]
-[2026-03-07T16:10:53+05:00] recent-calls []
-[2026-03-07T16:13:01+05:00] live-calls [{"call_id": "1772881979.108", "caller_name": "1001", "caller_number": "1001", "channel_name": "PJSIP/1001-00000046", "claimed_at": null, "claimed_by_user": null, "connected_at": null, "ended_at": null, "hangup_cause": null, "has_recording": false, "interaction_id": "int_0349796d1e", "last_transition_at": "2026-03-07T11:13:01+00:00", "operator_extension": null, "queue_code": "voice_lab", "queue_id": "que_23fb0ad07c", "started_at": "2026-03-07T11:13:01+00:00", "status": "active", "telephony_status": "ringing", "terminal_action": null, "terminal_target": null, "updated_at": "2026-03-07T11:13:01+00:00"}]
-[2026-03-07T16:13:01+05:00] voice-events [{"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:01+00:00", "event_id": "vev_8ece03ec07", "event_type": "call.started", "interaction_id": "int_0349796d1e", "payload": {"caller_name": "1001", "caller_number": "1001", "context": "from-softphones", "direction": "inbound", "extension": "7000", "linked_id": "1772881979.108", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_9245c8bb30", "event_type": "recording.ready", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "file_name": "20260307-160753-1772881673.105.wav", "linked_id": "1772881673.105", "mime_type": "audio/wav", "remote_path": "/var/spool/asterisk/monitor/mvpcc/20260307-160753-1772881673.105.wav", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_3e2c4eedc7", "event_type": "call.ended", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "hangup_cause": "16", "linked_id": "1772881673.105", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:20+00:00", "event_id": "vev_dbf1ba4399", "event_type": "call.ended", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": null, "hangup_cause": "unknown", "linked_id": "1772881673.105", "reconciled": true, "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:07:57+00:00", "event_id": "vev_8be5ab34b9", "event_type": "call.connected", "interaction_id": "int_052586cdaa", "payload": {"channel_name": "PJSIP/2001-00000045", "claimed_by_user": null, "linked_id": "1772881673.105", "operator_extension": "2001", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:07:55+00:00", "event_id": "vev_2b41d093c7", "event_type": "call.started", "interaction_id": "int_052586cdaa", "payload": {"caller_name": "1001", "caller_number": "1001", "context": "from-softphones", "direction": "inbound", "extension": "7000", "linked_id": "1772881673.105", "source": "asterisk"}}]
-[2026-03-07T16:13:01+05:00] call-links [{"call_id": "1772881979.108", "claimed_by_user": null, "connected_at": null, "ended_at": null, "interaction_id": "int_0349796d1e", "operator_extension": null, "started_at": "2026-03-07T11:13:01+00:00", "status": "active", "telephony_status": "ringing"}, {"call_id": "1772881673.105", "claimed_by_user": "operator", "connected_at": "2026-03-07T11:07:57+00:00", "ended_at": "2026-03-07T11:08:53+00:00", "interaction_id": "int_052586cdaa", "operator_extension": "2001", "started_at": "2026-03-07T11:07:55+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772876380.93", "claimed_by_user": null, "connected_at": "2026-03-07T09:39:49+00:00", "ended_at": "2026-03-07T09:40:12+00:00", "interaction_id": "int_69443612e1", "operator_extension": "2001", "started_at": "2026-03-07T09:39:42+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772875171.90", "claimed_by_user": null, "connected_at": "2026-03-07T09:19:36+00:00", "ended_at": "2026-03-07T09:19:39+00:00", "interaction_id": "int_726c36d95d", "operator_extension": "2001", "started_at": "2026-03-07T09:19:33+00:00", "status": "ended", "telephony_status": "ended"}]
-[2026-03-07T16:13:06+05:00] live-calls [{"call_id": "1772881979.108", "caller_name": "1001", "caller_number": "1001", "channel_name": "PJSIP/2001-00000047", "claimed_at": null, "claimed_by_user": null, "connected_at": "2026-03-07T11:13:05+00:00", "ended_at": null, "hangup_cause": null, "has_recording": false, "interaction_id": "int_0349796d1e", "last_transition_at": "2026-03-07T11:13:05+00:00", "operator_extension": "2001", "queue_code": "voice_lab", "queue_id": "que_23fb0ad07c", "started_at": "2026-03-07T11:13:01+00:00", "status": "active", "telephony_status": "connected", "terminal_action": null, "terminal_target": null, "updated_at": "2026-03-07T11:13:05+00:00"}]
-[2026-03-07T16:13:06+05:00] voice-events [{"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:05+00:00", "event_id": "vev_e3bec0b0e6", "event_type": "call.connected", "interaction_id": "int_0349796d1e", "payload": {"channel_name": "PJSIP/2001-00000047", "claimed_by_user": null, "linked_id": "1772881979.108", "operator_extension": "2001", "source": "asterisk"}}, {"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:01+00:00", "event_id": "vev_8ece03ec07", "event_type": "call.started", "interaction_id": "int_0349796d1e", "payload": {"caller_name": "1001", "caller_number": "1001", "context": "from-softphones", "direction": "inbound", "extension": "7000", "linked_id": "1772881979.108", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_9245c8bb30", "event_type": "recording.ready", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "file_name": "20260307-160753-1772881673.105.wav", "linked_id": "1772881673.105", "mime_type": "audio/wav", "remote_path": "/var/spool/asterisk/monitor/mvpcc/20260307-160753-1772881673.105.wav", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_3e2c4eedc7", "event_type": "call.ended", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "hangup_cause": "16", "linked_id": "1772881673.105", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:20+00:00", "event_id": "vev_dbf1ba4399", "event_type": "call.ended", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": null, "hangup_cause": "unknown", "linked_id": "1772881673.105", "reconciled": true, "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:07:57+00:00", "event_id": "vev_8be5ab34b9", "event_type": "call.connected", "interaction_id": "int_052586cdaa", "payload": {"channel_name": "PJSIP/2001-00000045", "claimed_by_user": null, "linked_id": "1772881673.105", "operator_extension": "2001", "source": "asterisk"}}]
-[2026-03-07T16:13:06+05:00] call-links [{"call_id": "1772881979.108", "claimed_by_user": null, "connected_at": "2026-03-07T11:13:05+00:00", "ended_at": null, "interaction_id": "int_0349796d1e", "operator_extension": "2001", "started_at": "2026-03-07T11:13:01+00:00", "status": "active", "telephony_status": "connected"}, {"call_id": "1772881673.105", "claimed_by_user": "operator", "connected_at": "2026-03-07T11:07:57+00:00", "ended_at": "2026-03-07T11:08:53+00:00", "interaction_id": "int_052586cdaa", "operator_extension": "2001", "started_at": "2026-03-07T11:07:55+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772876380.93", "claimed_by_user": null, "connected_at": "2026-03-07T09:39:49+00:00", "ended_at": "2026-03-07T09:40:12+00:00", "interaction_id": "int_69443612e1", "operator_extension": "2001", "started_at": "2026-03-07T09:39:42+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772875171.90", "claimed_by_user": null, "connected_at": "2026-03-07T09:19:36+00:00", "ended_at": "2026-03-07T09:19:39+00:00", "interaction_id": "int_726c36d95d", "operator_extension": "2001", "started_at": "2026-03-07T09:19:33+00:00", "status": "ended", "telephony_status": "ended"}]
-[2026-03-07T16:13:27+05:00] live-calls [{"call_id": "1772881979.108", "caller_name": "1001", "caller_number": "1001", "channel_name": "PJSIP/2001-00000047", "claimed_at": null, "claimed_by_user": null, "connected_at": "2026-03-07T11:13:05+00:00", "ended_at": null, "hangup_cause": "unknown", "has_recording": false, "interaction_id": "int_0349796d1e", "last_transition_at": "2026-03-07T11:13:26+00:00", "operator_extension": "2001", "queue_code": "voice_lab", "queue_id": "que_23fb0ad07c", "started_at": "2026-03-07T11:13:01+00:00", "status": "active", "telephony_status": "connected", "terminal_action": null, "terminal_target": null, "updated_at": "2026-03-07T11:13:26+00:00"}]
-[2026-03-07T16:13:27+05:00] voice-events [{"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:26+00:00", "event_id": "vev_9a03fc9ff8", "event_type": "call.ended", "interaction_id": "int_0349796d1e", "payload": {"duration_seconds": null, "hangup_cause": "unknown", "linked_id": "1772881979.108", "reconciled": true, "source": "asterisk"}}, {"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:05+00:00", "event_id": "vev_e3bec0b0e6", "event_type": "call.connected", "interaction_id": "int_0349796d1e", "payload": {"channel_name": "PJSIP/2001-00000047", "claimed_by_user": null, "linked_id": "1772881979.108", "operator_extension": "2001", "source": "asterisk"}}, {"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:01+00:00", "event_id": "vev_8ece03ec07", "event_type": "call.started", "interaction_id": "int_0349796d1e", "payload": {"caller_name": "1001", "caller_number": "1001", "context": "from-softphones", "direction": "inbound", "extension": "7000", "linked_id": "1772881979.108", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_9245c8bb30", "event_type": "recording.ready", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "file_name": "20260307-160753-1772881673.105.wav", "linked_id": "1772881673.105", "mime_type": "audio/wav", "remote_path": "/var/spool/asterisk/monitor/mvpcc/20260307-160753-1772881673.105.wav", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_3e2c4eedc7", "event_type": "call.ended", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "hangup_cause": "16", "linked_id": "1772881673.105", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:20+00:00", "event_id": "vev_dbf1ba4399", "event_type": "call.ended", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": null, "hangup_cause": "unknown", "linked_id": "1772881673.105", "reconciled": true, "source": "asterisk"}}]
-[2026-03-07T16:13:30+05:00] live-calls []
-[2026-03-07T16:13:30+05:00] recent-calls [{"call_id": "1772881979.108", "caller_name": "1001", "caller_number": "1001", "channel_name": "PJSIP/2001-00000047", "claimed_at": null, "claimed_by_user": null, "connected_at": "2026-03-07T11:13:05+00:00", "ended_at": "2026-03-07T11:13:30+00:00", "hangup_cause": "16", "has_recording": false, "interaction_id": "int_0349796d1e", "last_transition_at": "2026-03-07T11:13:30+00:00", "operator_extension": "2001", "queue_code": "voice_lab", "queue_id": "que_23fb0ad07c", "started_at": "2026-03-07T11:13:01+00:00", "status": "ended", "telephony_status": "ended", "terminal_action": "ended", "terminal_target": null, "updated_at": "2026-03-07T11:13:30+00:00"}]
-[2026-03-07T16:13:30+05:00] voice-events [{"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:30+00:00", "event_id": "vev_42cd34ba3b", "event_type": "recording.ready", "interaction_id": "int_0349796d1e", "payload": {"duration_seconds": 28, "file_name": "20260307-161300-1772881979.108.wav", "linked_id": "1772881979.108", "mime_type": "audio/wav", "remote_path": "/var/spool/asterisk/monitor/mvpcc/20260307-161300-1772881979.108.wav", "source": "asterisk"}}, {"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:30+00:00", "event_id": "vev_dcf3378912", "event_type": "call.ended", "interaction_id": "int_0349796d1e", "payload": {"duration_seconds": 28, "hangup_cause": "16", "linked_id": "1772881979.108", "source": "asterisk"}}, {"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:26+00:00", "event_id": "vev_9a03fc9ff8", "event_type": "call.ended", "interaction_id": "int_0349796d1e", "payload": {"duration_seconds": null, "hangup_cause": "unknown", "linked_id": "1772881979.108", "reconciled": true, "source": "asterisk"}}, {"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:05+00:00", "event_id": "vev_e3bec0b0e6", "event_type": "call.connected", "interaction_id": "int_0349796d1e", "payload": {"channel_name": "PJSIP/2001-00000047", "claimed_by_user": null, "linked_id": "1772881979.108", "operator_extension": "2001", "source": "asterisk"}}, {"call_id": "1772881979.108", "created_at": "2026-03-07T11:13:01+00:00", "event_id": "vev_8ece03ec07", "event_type": "call.started", "interaction_id": "int_0349796d1e", "payload": {"caller_name": "1001", "caller_number": "1001", "context": "from-softphones", "direction": "inbound", "extension": "7000", "linked_id": "1772881979.108", "source": "asterisk"}}, {"call_id": "1772881673.105", "created_at": "2026-03-07T11:08:53+00:00", "event_id": "vev_9245c8bb30", "event_type": "recording.ready", "interaction_id": "int_052586cdaa", "payload": {"duration_seconds": 58, "file_name": "20260307-160753-1772881673.105.wav", "linked_id": "1772881673.105", "mime_type": "audio/wav", "remote_path": "/var/spool/asterisk/monitor/mvpcc/20260307-160753-1772881673.105.wav", "source": "asterisk"}}]
-[2026-03-07T16:13:30+05:00] call-links [{"call_id": "1772881979.108", "claimed_by_user": null, "connected_at": "2026-03-07T11:13:05+00:00", "ended_at": "2026-03-07T11:13:30+00:00", "interaction_id": "int_0349796d1e", "operator_extension": "2001", "started_at": "2026-03-07T11:13:01+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772881673.105", "claimed_by_user": "operator", "connected_at": "2026-03-07T11:07:57+00:00", "ended_at": "2026-03-07T11:08:53+00:00", "interaction_id": "int_052586cdaa", "operator_extension": "2001", "started_at": "2026-03-07T11:07:55+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772876380.93", "claimed_by_user": null, "connected_at": "2026-03-07T09:39:49+00:00", "ended_at": "2026-03-07T09:40:12+00:00", "interaction_id": "int_69443612e1", "operator_extension": "2001", "started_at": "2026-03-07T09:39:42+00:00", "status": "ended", "telephony_status": "ended"}, {"call_id": "1772875171.90", "claimed_by_user": null, "connected_at": "2026-03-07T09:19:36+00:00", "ended_at": "2026-03-07T09:19:39+00:00", "interaction_id": "int_726c36d95d", "operator_extension": "2001", "started_at": "2026-03-07T09:19:33+00:00", "status": "ended", "telephony_status": "ended"}]
-[2026-03-07T16:13:31+05:00] recent-calls [{"call_id": "1772881979.108", "caller_name": "1001", "caller_number": "1001", "channel_name": "PJSIP/2001-00000047", "claimed_at": null, "claimed_by_user": null, "connected_at": "2026-03-07T11:13:05+00:00", "ended_at": "2026-03-07T11:13:30+00:00", "hangup_cause": "16", "has_recording": true, "interaction_id": "int_0349796d1e", "last_transition_at": "2026-03-07T11:13:30+00:00", "operator_extension": "2001", "queue_code": "voice_lab", "queue_id": "que_23fb0ad07c", "started_at": "2026-03-07T11:13:01+00:00", "status": "ended", "telephony_status": "ended", "terminal_action": "ended", "terminal_target": null, "updated_at": "2026-03-07T11:13:30+00:00"}]
-[2026-03-07T16:13:32+05:00] monitor {"status": "finished"}
\ No newline at end of file
diff --git a/docs/acceptance/track12/track12-acceptance.template.md b/docs/acceptance/track12/track12-acceptance.template.md
deleted file mode 100644
index a5eb685..0000000
--- a/docs/acceptance/track12/track12-acceptance.template.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Track 12 Acceptance - <timestamp>
-
-## Environment
-
-- Base URL: `<gateway_base_url>`
-- Database: `<database_url>`
-- Asterisk host: `<asterisk_host>`
-- Operator user: `<operator_user>`
-- Operator extension: `<operator_extension>`
-
-## Executed scenarios
-
-- [ ] Happy path: answer in SIP -> `╨Я╤А╨╕╨╜╤П╤В╤М ╨▓ ╤А╨░╨▒╨╛╤В╤Г` -> `╨Ч╨░╨▓╨╡╤А╤И╨╕╤В╤М`
-- [ ] Transfer path: answer in SIP -> `╨Я╤А╨╕╨╜╤П╤В╤М ╨▓ ╤А╨░╨▒╨╛╤В╤Г` -> `╨Я╨╡╤А╨╡╨┤╨░╤В╤М`
-- [ ] Passive path: answer in SIP, no browser action, call stays active until real hangup
-- [ ] No-fake-cards path: `╨Ц╨╕╨▓╤Л╨╡ ╨╖╨▓╨╛╨╜╨║╨╕` does not show `interaction:int_*`
-
-## Evidence
-
-- Active live call snapshot: `operator-live-calls.json`
-- Recent call snapshot: `operator-recent-calls.json`
-- Action log: `bridge-actions.json`
-- Playback proof: `playback-proof.json`
-
-## Mandatory checks
-
-- [ ] `GET /proxy/asterisk-bridge/asterisk/live-calls` returns only active calls
-- [ ] `GET /proxy/asterisk-bridge/asterisk/recent-calls` returns only recent ended/transferred calls
-- [ ] `claim` keeps the card in `╨Р╨║╤В╨╕╨▓╨╜╤Л╨╡ ╨╖╨▓╨╛╨╜╨║╨╕`
-- [ ] `hangup` moves the card to `╨в╨╛╨╗╤М╨║╨╛ ╤З╤В╨╛ ╨╖╨░╨▓╨╡╤А╤И╤С╨╜╨╜╤Л╨╡`
-- [ ] `blind transfer` moves the card to `╨в╨╛╨╗╤М╨║╨╛ ╤З╤В╨╛ ╨╖╨░╨▓╨╡╤А╤И╤С╨╜╨╜╤Л╨╡`
-- [ ] recording is available in `/supervisor`
-
-## Decision
-
-- GO / NO-GO: `<decision>`
-- Notes:
-  - `<notes>`
diff --git a/docs/acceptance/track14/track14-acceptance.template.md b/docs/acceptance/track14/track14-acceptance.template.md
deleted file mode 100644
index d64cd08..0000000
--- a/docs/acceptance/track14/track14-acceptance.template.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Track 14 Acceptance
-
-## Environment
-
-- Browser: `<chrome|edge version>`
-- Gateway base URL: `<url>`
-- Asterisk host: `<host>`
-- WSS URL: `<wss-url>`
-- Operator app user: `<user>`
-- Browser SIP auth user: `<sip-username>`
-
-## Scenario
-
-1. Open `/operator` in Chrome or Edge.
-2. Connect browser softphone.
-3. Place one inbound call `1001 -> 7000`.
-4. Answer in the browser.
-5. Confirm auto-claim or perform manual `╨Я╤А╨╕╨╜╤П╤В╤М ╨▓ ╤А╨░╨▒╨╛╤В╤Г`.
-6. Hang up through the browser path.
-7. Confirm the call moves to recent and the recording is playable in `/supervisor`.
-
-## Evidence
-
-- Browser softphone registration state screenshot
-- Operator live-call screenshot while ringing or connected
-- Browser answer or hangup action proof
-- Bridge actions snapshot
-- Recent-calls snapshot
-- Recording playback proof
-
-## Result
-
-- `GO` or `NO-GO`
-- Notes:
-  - browser registration
-  - answer behavior
-  - auto-claim result
-  - recording result
diff --git a/docs/acceptance/track15/track15-acceptance.template.md b/docs/acceptance/track15/track15-acceptance.template.md
deleted file mode 100644
index 9b3a2a9..0000000
--- a/docs/acceptance/track15/track15-acceptance.template.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# Track 15 Acceptance
-
-## Environment
-
-- Base URL:
-- Telegram bot username:
-- Queue ID:
-- Operator user:
-- Date/time:
-
-## Scenario
-
-1. Send the first Telegram message to the bot.
-2. Open `/operator` -> `Telegram`.
-3. Confirm a new thread appears and links to a Telegram interaction.
-4. Claim the thread.
-5. Send a reply from the operator page.
-6. Send another inbound message from the same Telegram chat.
-7. Confirm the same thread and same interaction are reused.
-8. Close the thread.
-9. Send one more inbound message and confirm reactivation to `new`.
-
-## Expected results
-
-- thread created on first inbound
-- same thread reused on repeated inbound from the same `chat_id`
-- operator reply persisted and delivered
-- closed interaction reactivated on new inbound
-- unsupported content, if tested, is shown as a system placeholder
-
-## Evidence
-
-- thread payload:
-- messages payload:
-- linked interaction payload:
-- reply proof:
-- reactivation proof:
-
-## Result
-
-- PASS / FAIL
diff --git a/docs/acceptance/track9/README.md b/docs/acceptance/track9/README.md
deleted file mode 100644
index 7681e48..0000000
--- a/docs/acceptance/track9/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Track 9 Acceptance Evidence
-
-Use this folder for formal QA-lab acceptance artifacts for Asterisk integration.
-
-Recommended structure:
-
-- `docs/acceptance/track9/<timestamp>/...` for each run
-- include:
-  - bridge status snapshots
-  - smoke/check outputs
-  - sampled DB evidence
-  - final acceptance note
-
-Generate a baseline package with:
-
-```powershell
-python scripts\track9_collect_evidence.py --base-url http://localhost:8080 --database-url <database-url> --run-checks --require-recording
-```
-
-When `--run-checks` is used, the package now includes:
-- `preflight_output.txt`
-- `smoke_output.txt`
-- `track9_check_output.txt`
-
-For production cutover tracking (Track 9.2), start from:
-
-- [track9-cutover-sheet.template.md](/e:/Zhan/docs/acceptance/track9/track9-cutover-sheet.template.md)
diff --git a/docs/acceptance/track9/track9-acceptance.md b/docs/acceptance/track9/track9-acceptance.md
deleted file mode 100644
index 859d6ad..0000000
--- a/docs/acceptance/track9/track9-acceptance.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# Track 9 Acceptance (Latest QA Run)
-
-## Environment
-
-- Date: `2026-03-05`
-- Host: local QA lab (`127.0.0.1`)
-- Gateway URL: `http://127.0.0.1:8080`
-- Asterisk VM host: `92.38.48.166`
-- Database URL (masked): `sqlite:///.data_local/mvp_cc.db`
-- Evidence package: [20260305_160002](/e:/Zhan/docs/acceptance/track9/20260305_160002)
-
-## Executed scenario
-
-1. Softphone `1001` called `7000`.
-2. Bridge reported AMI connected.
-3. `call.started` received from Asterisk source.
-4. `call.ended` received.
-5. `recording.ready` processed and recording uploaded.
-6. Recording playback validated in supervisor shell.
-
-## Validation commands
-
-```powershell
-python scripts\track9_preflight.py --base-url <gateway> --check-sftp
-python scripts\asterisk_lab_smoke.py --base-url <gateway> --database-url <db> --require-recording
-python scripts\track9_check.py --base-url <gateway> --database-url <db> --require-recording
-python scripts\track9_collect_evidence.py --base-url <gateway> --database-url <db> --run-checks --require-recording
-```
-
-## Result summary
-
-- AMI connected: `yes`
-- call.started present: `yes`
-- call.ended present: `yes`
-- call links present: `yes`
-- recording imported: `yes`
-- failed bridge events: `0`
-- smoke: `PASS`
-- track9_check: `PASS`
-- preflight: `PASS`
-
-## Risks / open items
-
-- Asterisk dialplan still emits some duplicate/late lifecycle signals in lab runs; bridge reconciliation masks this and keeps platform data consistent.
-- For production cutover, keep strict auth and rollback window from Track 9.2 sheet.
-
-## Decision
-
-- [x] GO
-- [ ] NO-GO
diff --git a/docs/acceptance/track9/track9-cutover-sheet.template.md b/docs/acceptance/track9/track9-cutover-sheet.template.md
deleted file mode 100644
index 06c37a9..0000000
--- a/docs/acceptance/track9/track9-cutover-sheet.template.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# Track 9.2 Cutover Sheet (Template)
-
-## Window
-
-- Date:
-- Start time:
-- End time:
-- Change ticket:
-
-## Owners
-
-- K8s owner:
-- Voice/Asterisk owner:
-- Application owner:
-- On-call:
-
-## Environment contract
-
-- `KUBE_CONTEXT`:
-- `NAMESPACE`:
-- `RELEASE`:
-- `GATEWAY_BASE_URL`:
-- `DATABASE_URL`:
-- `IMAGE_TAG`:
-- `ASTERISK_AMI_HOST`:
-- `ASTERISK_SFTP_HOST`:
-- `QUEUE_ID (voice_lab)`:
-
-## Entry checks (T-1 day)
-
-- [ ] QA baseline accepted:
-  - `docs/acceptance/track9/track9-acceptance.md`
-- [ ] Production queue map validated (`voice_lab -> queue_id` exists)
-- [ ] AMI port reachable (`5038/tcp`)
-- [ ] SFTP port reachable (`22/tcp`)
-- [ ] PostgreSQL confirmed for target env
-
-## Dry-run results (T-4h to T-1h)
-
-- [ ] `helm lint` PASS
-- [ ] `helm template` PASS
-- [ ] strict env keys found in rendered manifest
-- Artifact folder:
-
-## Execution (T0)
-
-- Helm command:
-- Start time:
-- End time:
-- Result:
-
-Rollout statuses:
-
-- [ ] `api-gateway` ready
-- [ ] `asterisk-bridge-service` ready
-- [ ] `voice-adapter-service` ready
-- [ ] `recording-service` ready
-
-## Post-cutover checks
-
-- [ ] `track9_preflight --check-sftp --require-strict-service-auth` PASS
-- [ ] Real call completed (`1001 -> 7000`)
-- [ ] `asterisk_lab_smoke --require-recording` PASS
-- [ ] `track9_check --require-recording` PASS
-- [ ] Supervisor playback confirmed (`HTTP 200`, `audio/*`)
-
-## Evidence
-
-- Evidence folder:
-- `track9-acceptance.md`:
-- `playback-proof.md`:
-- `playback-proof.json` (if used):
-
-## Decision
-
-- [ ] GO
-- [ ] NO-GO
-
-Reason:
-
-## Rollback section (fill if NO-GO)
-
-- Trigger:
-- Rollback command:
-- Rollback completion time:
-- Post-rollback preflight result:
-- Incident / RCA ticket:
diff --git a/docs/acceptance/track9_1/README.md b/docs/acceptance/track9_1/README.md
deleted file mode 100644
index 2241116..0000000
--- a/docs/acceptance/track9_1/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Track 9.1 Acceptance (Latest Strict Auth Baseline)
-
-## Summary
-
-Track 9.1 captures the strict service-token hardening pass for the Asterisk bridge flow.
-
-- strict auth profile: `ALLOW_LEGACY_HEADER_AUTH=0`
-- bridge auth mode: `ASTERISK_BRIDGE_AUTH_MODE=bearer`
-- trusted service subjects enforced for voice ingest and recording import
-
-## Latest retained evidence package
-
-- [20260305_042801](/e:/Zhan/docs/acceptance/track9_1/20260305_042801)
-
-## Notes
-
-- Earlier timestamp bundles were reduced during repository cleanup.
-- Use the retained package for low-level QA artifacts and Track 9.1 reference data.
diff --git a/services/ai_orchestrator_service/app.py b/services/ai_orchestrator_service/app.py
index 891669e..848ab51 100644
--- a/services/ai_orchestrator_service/app.py
+++ b/services/ai_orchestrator_service/app.py
@@ -79,6 +79,7 @@ from services.shared.sql_models import (
     VoiceAISessionRow,
 )
 from services.ai_orchestrator_service import voice as voice_flows
+from services.ai_orchestrator_service import operator_persona as persona
 from services.ai_orchestrator_service.voice_name_config import (
     load_voice_name_collection_config,
     save_voice_name_collection_config,
@@ -2316,15 +2317,10 @@ def _openai_prompt(
         }
         for article in kb_results
     ]
-    system_prompt = (
-        f"You are the company's AI assistant for {channel_label}. "
-        "Always disclose you are an AI assistant in the first meaningful reply. "
-        "Use only provided business context, KB snippets, and interaction state. "
-        "Never invent order statuses, tariffs, discounts, deadlines, or actions that are not in context. "
-        "If confidence is low or a human is needed, set needs_handoff=true and do not bluff. "
-        "Return only a JSON object with keys: language, intent, reply_text, confidence, needs_handoff, "
-        "handoff_reason, case_action, kb_refs. case_action must be one of none, close, escalate, keep_open. "
-        f"Prefer {'Kazakh' if language == 'kz' else 'Russian'} for the reply."
+    system_prompt = persona.operator_system_prompt(
+        language=language,
+        channel_label=channel_label,
+        is_voice=False,
     )
     user_prompt = {
         "customer": customer_summary,
@@ -2348,17 +2344,7 @@ def _openai_prompt(
     ]
 
 
-def _openai_compatible_decision(
-    *,
-    customer: Customer | None,
-    interaction: Interaction,
-    thread: Any,
-    messages: list[Any],
-    kb_results: list[KBArticleRow],
-    language: str,
-    channel_label: str = "Telegram",
-    channel_key: str = "telegram",
-) -> dict[str, Any]:
+def _request_structured_model_decision(messages: list[dict[str, str]]) -> dict[str, Any]:
     if not _ai_api_base() or not _ai_api_key():
         raise RuntimeError("AI_API_BASE / AI_API_KEY are required for openai_compatible provider")
     started = time.perf_counter()
@@ -2366,16 +2352,7 @@ def _openai_compatible_decision(
         "model": _ai_model(),
         "temperature": 0.2,
         "response_format": {"type": "json_object"},
-        "messages": _openai_prompt(
-            customer=customer,
-            interaction=interaction,
-            thread=thread,
-            messages=messages,
-            kb_results=kb_results,
-            language=language,
-            channel_label=channel_label,
-            channel_key=channel_key,
-        ),
+        "messages": messages,
     }
     with httpx.Client(timeout=_ai_timeout_seconds()) as client:
         response = client.post(
@@ -2395,6 +2372,31 @@ def _openai_compatible_decision(
     return decision
 
 
+def _openai_compatible_decision(
+    *,
+    customer: Customer | None,
+    interaction: Interaction,
+    thread: Any,
+    messages: list[Any],
+    kb_results: list[KBArticleRow],
+    language: str,
+    channel_label: str = "Telegram",
+    channel_key: str = "telegram",
+) -> dict[str, Any]:
+    return _request_structured_model_decision(
+        _openai_prompt(
+            customer=customer,
+            interaction=interaction,
+            thread=thread,
+            messages=messages,
+            kb_results=kb_results,
+            language=language,
+            channel_label=channel_label,
+            channel_key=channel_key,
+        )
+    )
+
+
 def _sanitize_decision(raw: dict[str, Any], *, fallback_language: str) -> dict[str, Any]:
     decision = {
         "language": str(raw.get("language") or fallback_language or "ru"),
@@ -2442,6 +2444,90 @@ def _always_reply_fallback(last_user_text: str, language: str) -> str:
     )
 
 
+def _stub_decision(
+    *,
+    customer: Customer | None,
+    interaction: Interaction,
+    last_user_message: TelegramMessageRow,
+    kb_results: list[KBArticleRow],
+    language: str,
+) -> dict[str, Any]:
+    text = last_user_message.text
+    if _looks_like_human_request(text):
+        return {
+            "language": language,
+            "intent": "handoff_request",
+            "reply_text": "",
+            "confidence": 0.2,
+            "needs_handoff": True,
+            "handoff_reason": "╨Ъ╨╗╨╕╨╡╨╜╤В ╨╖╨░╨┐╤А╨╛╤Б╨╕╨╗ ╨╢╨╕╨▓╨╛╨│╨╛ ╨╛╨┐╨╡╤А╨░╤В╨╛╤А╨░.",
+            "case_action": "keep_open",
+            "kb_refs": [],
+        }
+    if _is_sensitive_request(text):
+        return {
+            "language": language,
+            "intent": "sensitive_request",
+            "reply_text": "",
+            "confidence": 0.25,
+            "needs_handoff": True,
+            "handoff_reason": "╨Э╤Г╨╢╨╡╨╜ ╤З╨╡╨╗╨╛╨▓╨╡╨║: ╨╖╨░╨┐╤А╨╛╤Б ╨╖╨░╤В╤А╨░╨│╨╕╨▓╨░╨╡╤В ╤З╤Г╨▓╤Б╤В╨▓╨╕╤В╨╡╨╗╤М╨╜╤Г╤О ╤В╨╡╨╝╤Г ╨╕╨╗╨╕ ╨┤╨╡╨╣╤Б╤В╨▓╨╕╨╡ ╨▓╨╜╨╡ ╨┤╨╛╤Б╤В╤Г╨┐╨╜╨╛╨│╨╛ ╨║╨╛╨╜╤В╨╡╨║╤Б╤В╨░.",
+            "case_action": "keep_open",
+            "kb_refs": [],
+        }
+    if _looks_like_resolution_confirmation(text):
+        return {
+            "language": language,
+            "intent": "resolution_confirmed",
+            "reply_text": persona.text_resolution_reply(language),
+            "confidence": 0.9,
+            "needs_handoff": False,
+            "handoff_reason": None,
+            "case_action": "close",
+            "kb_refs": [],
+        }
+    if kb_results:
+        best = kb_results[0]
+        snippet = _article_snippet(best)
+        return {
+            "language": language,
+            "intent": "kb_answer",
+            "reply_text": (
+                f"╥Ъ╤Л╤Б╥Ы╨░╤И╨░ ╨░╨╣╤В╨░╨╣╤Л╨╜: {snippet}"
+                if language == "kz"
+                else f"╨Ъ╨╛╤А╨╛╤В╨║╨╛ ╨┐╨╛╨┤╤Б╨║╨░╨╢╤Г: {snippet}"
+            ),
+            "confidence": 0.84,
+            "needs_handoff": False,
+            "handoff_reason": None,
+            "case_action": "keep_open",
+            "kb_refs": [best.article_id],
+        }
+    return {
+        "language": language,
+        "intent": "clarification",
+        "reply_text": (
+            "╨Э╨░╥Ы╤В╤Л╤А╨░╥Ы ╨░╨╣╤В╤Л╥г╤Л╨╖╤И╤Л: ╨╜╨╡╨╜╤Ц ╤В╨╡╨║╤Б╨╡╤А╤Г ╨╜╨╡╨╝╨╡╤Б╨╡ ╨╜╨░╥Ы╤В╤Л╨╗╨░╤Г ╨║╨╡╤А╨╡╨║?"
+            if language == "kz"
+            else "╨г╤В╨╛╤З╨╜╨╕╤В╨╡, ╨┐╨╛╨╢╨░╨╗╤Г╨╣╤Б╤В╨░, ╤З╤В╨╛ ╨╕╨╝╨╡╨╜╨╜╨╛ ╨╜╤Г╨╢╨╜╨╛ ╨┐╤А╨╛╨▓╨╡╤А╨╕╤В╤М ╨╕╨╗╨╕ ╨┐╨╛╨┤╤Б╨║╨░╨╖╨░╤В╤М."
+        ),
+        "confidence": 0.45,
+        "needs_handoff": False,
+        "handoff_reason": None,
+        "case_action": "keep_open",
+        "kb_refs": [],
+    }
+
+
+def _always_reply_fallback(last_user_text: str, language: str) -> str:
+    normalized = str(last_user_text or "").strip().lower()
+    greeting_tokens = ("╨┐╤А╨╕╨▓╨╡╤В", "╨╖╨┤╤А╨░╨▓╤Б╤В╨▓╤Г╨╣╤В╨╡", "╨┤╨╛╨▒╤А╤Л╨╣", "╤Б╨░╨╗╨╡╨╝", "╤Б╙Щ", "╤Б╙Щ╨╗╨╡╨╝", "hello", "hi")
+    return persona.human_fallback_reply(
+        language,
+        is_greeting=any(token in normalized for token in greeting_tokens),
+    )
+
+
 def _apply_always_reply_mode(
     decision: dict[str, Any],
     *,
diff --git a/services/ai_orchestrator_service/voice.py b/services/ai_orchestrator_service/voice.py
index 571d87e..bc3e254 100644
--- a/services/ai_orchestrator_service/voice.py
+++ b/services/ai_orchestrator_service/voice.py
@@ -22,6 +22,7 @@ from services.shared.sql_models import (
     VoiceAISessionRow,
     VoiceTranscriptSegmentRow,
 )
+from services.ai_orchestrator_service import operator_persona as persona
 from services.ai_orchestrator_service.voice_name_config import (
     load_effective_voice_name_collection_config,
     voice_name_collection_confirmation_greeting,
@@ -71,6 +72,18 @@ def _voice_handoff_reply(language: str) -> str:
     )
 
 
+def _voice_disclosure_prefix(language: str) -> str:
+    return persona.voice_disclosure_prefix(language)
+
+
+def _voice_greeting(language: str) -> str:
+    return persona.voice_greeting(language)
+
+
+def _voice_handoff_reply(language: str) -> str:
+    return persona.voice_handoff_reply(language)
+
+
 def _normalize_phone(value: str | None) -> str | None:
     raw = str(value or "").strip()
     if not raw:
@@ -1037,6 +1050,132 @@ def _ensure_voice_ai_session(
     return ai_session, True
 
 
+def _voice_policy_mode() -> str:
+    return persona.voice_policy_mode()
+
+
+def _voice_llm_prompt_messages(
+    *,
+    language: str,
+    customer: Customer | None,
+    interaction: Interaction,
+    transcript_text: str,
+    transcript_window: list[VoiceTranscriptSegmentRow],
+    kb_results: list[Any],
+    name_value: str | None,
+    name_status: str | None,
+) -> list[dict[str, str]]:
+    app = _app()
+    history = [
+        {
+            "speaker": segment.speaker,
+            "text": segment.text,
+            "sequence_no": segment.sequence_no,
+            "source_type": segment.source_type,
+            "interrupted": bool(segment.barge_in_interrupted),
+            "created_at": segment.created_at,
+        }
+        for segment in transcript_window[-6:]
+    ]
+    kb_context = [
+        {
+            "article_id": article.article_id,
+            "title": article.title,
+            "snippet": app._article_snippet(article, limit=240),
+        }
+        for article in kb_results[:3]
+    ]
+    payload = {
+        "customer": {
+            "customer_id": customer.customer_id if customer else interaction.customer_id,
+            "display_name": customer.display_name if customer else None,
+            "name_status": name_status,
+            "name_value": name_value,
+            "channel": "voice",
+        },
+        "interaction": {
+            "interaction_id": interaction.interaction_id,
+            "status": interaction.status,
+            "queue_id": interaction.queue_id,
+            "subject": interaction.subject,
+        },
+        "voice_turn": {
+            "last_user_text": transcript_text,
+            "language": language,
+        },
+        "kb_results": kb_context,
+        "history": history,
+    }
+    return [
+        {
+            "role": "system",
+            "content": persona.operator_system_prompt(
+                language=language,
+                channel_label="Voice",
+                is_voice=True,
+            ),
+        },
+        {"role": "user", "content": json.dumps(payload, ensure_ascii=False)},
+    ]
+
+
+def _voice_llm_decision(
+    *,
+    language: str,
+    customer: Customer | None,
+    interaction: Interaction,
+    transcript_text: str,
+    transcript_window: list[VoiceTranscriptSegmentRow],
+    kb_results: list[Any],
+    name_value: str | None,
+    name_status: str | None,
+) -> dict[str, Any] | None:
+    app = _app()
+    if _voice_policy_mode() != "llm_guarded":
+        return None
+    if app._ai_provider() != "openai_compatible":
+        return None
+    try:
+        raw = app._request_structured_model_decision(
+            _voice_llm_prompt_messages(
+                language=language,
+                customer=customer,
+                interaction=interaction,
+                transcript_text=transcript_text,
+                transcript_window=transcript_window,
+                kb_results=kb_results,
+                name_value=name_value,
+                name_status=name_status,
+            )
+        )
+    except Exception:
+        return None
+    decision = app._sanitize_decision(raw, fallback_language=language)
+    if not str(decision.get("reply_text") or "").strip():
+        decision["reply_text"] = _voice_generic_prompt(language)
+    if decision.get("needs_handoff") and not decision.get("handoff_reason"):
+        decision["handoff_reason"] = "╨в╤А╨╡╨▒╤Г╨╡╤В╤Б╤П ╤Г╤З╨░╤Б╤В╨╕╨╡ ╨╢╨╕╨▓╨╛╨│╨╛ ╨╛╨┐╨╡╤А╨░╤В╨╛╤А╨░."
+    if decision.get("needs_handoff") and not str(decision.get("reply_text") or "").strip():
+        decision["reply_text"] = _voice_handoff_reply(language)
+    return {
+        "language": decision["language"],
+        "intent": decision["intent"],
+        "reply_text": str(decision["reply_text"] or "").strip(),
+        "confidence": float(decision["confidence"] or 0.0),
+        "needs_handoff": bool(decision["needs_handoff"]),
+        "handoff_reason": decision["handoff_reason"],
+        "case_action": decision["case_action"],
+        "kb_refs": decision["kb_refs"],
+        "summary_text": (
+            str(decision.get("reply_text") or "").strip()
+            or str(decision.get("handoff_reason") or "").strip()
+            or "AI ╨┐╨╛╨┤╨│╨╛╤В╨╛╨▓╨╕╨╗ ╨╛╤В╨▓╨╡╤В ╨╛╨┐╨╡╤А╨░╤В╨╛╤А╤Б╨║╨╕╨╝ ╤Б╤В╨╕╨╗╨╡╨╝."
+        ),
+        "model": decision["_model"],
+        "latency_ms": decision["_latency_ms"],
+    }
+
+
 def _voice_decision_legacy(
     *,
     language: str,
@@ -1118,6 +1257,8 @@ def _voice_decision(
     transcript_window: list[VoiceTranscriptSegmentRow],
     kb_results: list[Any],
     disclosure_required: bool,
+    customer_name_value: str | None = None,
+    customer_name_status: str | None = None,
 ) -> dict[str, Any]:
     app = _app()
     normalized = str(transcript_text or "").strip()
@@ -1209,6 +1350,114 @@ def _voice_decision(
     }
 
 
+def _voice_decision(
+    *,
+    language: str,
+    customer: Customer | None,
+    interaction: Interaction,
+    transcript_text: str,
+    transcript_window: list[VoiceTranscriptSegmentRow],
+    kb_results: list[Any],
+    disclosure_required: bool,
+    customer_name_value: str | None = None,
+    customer_name_status: str | None = None,
+) -> dict[str, Any]:
+    app = _app()
+    normalized = str(transcript_text or "").strip()
+    lower_text = normalized.lower()
+    caller_texts = _voice_recent_caller_texts(transcript_window)
+    model = app._ai_model()
+
+    if app._looks_like_human_request(lower_text) or app._is_sensitive_request(lower_text):
+        return {
+            "language": language,
+            "intent": "handoff_request",
+            "reply_text": _voice_handoff_reply(language),
+            "confidence": 0.25,
+            "needs_handoff": True,
+            "handoff_reason": "╨Ч╨░╨┐╤А╨╛╤Б ╤В╤А╨╡╨▒╤Г╨╡╤В ╤Г╤З╨░╤Б╤В╨╕╤П ╨╢╨╕╨▓╨╛╨│╨╛ ╨╛╨┐╨╡╤А╨░╤В╨╛╤А╨░.",
+            "case_action": "keep_open",
+            "kb_refs": [],
+            "summary_text": "AI ╤Б╨╛╨▒╤А╨░╨╗ ╨┐╨╡╤А╨▓╨╕╤З╨╜╤Л╨╣ ╨║╨╛╨╜╤В╨╡╨║╤Б╤В ╨╕ ╨╖╨░╨┐╤А╨╛╤Б╨╕╨╗ ╨╢╨╕╨▓╨╛╨│╨╛ ╨╛╨┐╨╡╤А╨░╤В╨╛╤А╨░.",
+            "model": model,
+            "latency_ms": 1,
+        }
+
+    clarification_count = _voice_recent_clarification_count(transcript_window)
+    repeated_reply_count = _voice_repeated_assistant_reply_count(transcript_window)
+    recent_caller_window = caller_texts[-3:]
+    low_signal_count = sum(1 for text in recent_caller_window if _voice_is_low_signal_caller_text(text))
+    caller_confused = _voice_is_confused_caller_text(normalized)
+    if clarification_count >= 4 or (
+        clarification_count >= 3 and (caller_confused or low_signal_count >= 2 or repeated_reply_count >= 2)
+    ):
+        reply_text, handoff_reason, summary_text = _voice_loop_handoff(language)
+        return {
+            "language": language,
+            "intent": "handoff_request",
+            "reply_text": reply_text,
+            "confidence": 0.34,
+            "needs_handoff": True,
+            "handoff_reason": handoff_reason,
+            "case_action": "keep_open",
+            "kb_refs": [],
+            "summary_text": summary_text,
+            "model": model,
+            "latency_ms": 1,
+        }
+
+    llm_decision = _voice_llm_decision(
+        language=language,
+        customer=customer,
+        interaction=interaction,
+        transcript_text=transcript_text,
+        transcript_window=transcript_window,
+        kb_results=kb_results,
+        name_value=customer_name_value or (customer.display_name if customer else None),
+        name_status=customer_name_status,
+    )
+    if llm_decision is not None:
+        return llm_decision
+
+    if kb_results:
+        article = kb_results[0]
+        snippet = app._article_snippet(article, limit=220)
+        return {
+            "language": language,
+            "intent": "kb_answer",
+            "reply_text": (
+                f"╥Ъ╤Л╤Б╥Ы╨░╤И╨░ ╨░╨╣╤В╨░╨╣╤Л╨╜: {snippet}"
+                if language == "kz"
+                else f"╨Ъ╨╛╤А╨╛╤В╨║╨╛ ╨┐╨╛╨┤╤Б╨║╨░╨╢╤Г: {snippet}"
+            ),
+            "confidence": 0.78,
+            "needs_handoff": False,
+            "handoff_reason": None,
+            "case_action": "keep_open",
+            "kb_refs": [article.article_id],
+            "summary_text": "AI ╨┤╨░╨╗ ╨╛╤В╨▓╨╡╤В ╨┐╨╛ ╨┤╨╛╤Б╤В╤Г╨┐╨╜╨╛╨╝╤Г ╨║╨╛╨╜╤В╨╡╨║╤Б╤В╤Г.",
+            "model": "voice_policy_fallback",
+            "latency_ms": 1,
+        }
+
+    reply_text = _voice_confusion_prompt(language, caller_texts) if caller_confused else (
+        _voice_topic_prompt(language, caller_texts) or _voice_generic_prompt(language)
+    )
+    return {
+        "language": language,
+        "intent": "clarification",
+        "reply_text": reply_text,
+        "confidence": 0.62,
+        "needs_handoff": False,
+        "handoff_reason": None,
+        "case_action": "keep_open",
+        "kb_refs": [],
+        "summary_text": "AI ╤Г╤В╨╛╤З╨╜╤П╨╡╤В ╤Ж╨╡╨╗╤М ╨╖╨▓╨╛╨╜╨║╨░ ╨╕ ╤Б╨╛╨▒╨╕╤А╨░╨╡╤В ╨║╨╛╨╜╤В╨╡╨║╤Б╤В.",
+        "model": "voice_policy_fallback",
+        "latency_ms": 1,
+    }
+
+
 def start_voice_session(session_id: str, payload: VoiceAIStartIn) -> VoiceAIStartOut:
     session = get_session()
     try:
@@ -1685,6 +1934,8 @@ def turn_voice_session(session_id: str, payload: VoiceAITurnIn) -> VoiceAITurnDe
             transcript_window=transcript_window,
             kb_results=kb_results,
             disclosure_required=disclosure_required,
+            customer_name_value=name_update["value"],
+            customer_name_status=name_update["status"],
         )
         decision_metadata = _voice_name_metadata(
             language=voice_session.voice_start_language or ai_session.language or "ru",
diff --git a/services/ai_orchestrator_service/voice_name_config.py b/services/ai_orchestrator_service/voice_name_config.py
index 413c718..dfc13fb 100644
--- a/services/ai_orchestrator_service/voice_name_config.py
+++ b/services/ai_orchestrator_service/voice_name_config.py
@@ -50,6 +50,34 @@ def voice_name_collection_default_config() -> VoiceNameCollectionConfig:
     )
 
 
+def voice_name_collection_default_config() -> VoiceNameCollectionConfig:
+    return VoiceNameCollectionConfig(
+        enabled=True,
+        texts=VoiceNameCollectionTextsConfig(
+            ru=VoiceNameCollectionLanguageTexts(
+                start_prompt="╨Ч╨┤╤А╨░╨▓╤Б╤В╨▓╤Г╨╣╤В╨╡. ╨Э╨░╨╖╨╛╨▓╨╕╤В╨╡, ╨┐╨╛╨╢╨░╨╗╤Г╨╣╤Б╤В╨░, ╨▓╨░╤И╨╡ ╨╕╨╝╤П.",
+                personalized_greeting_template=(
+                    "╨Ч╨┤╤А╨░╨▓╤Б╤В╨▓╤Г╨╣╤В╨╡, {name}. ╨Ъ╨╛╤А╨╛╤В╨║╨╛ ╤А╨░╤Б╤Б╨║╨░╨╢╨╕╤В╨╡, ╨┐╨╛╨╢╨░╨╗╤Г╨╣╤Б╤В╨░, ╤З╨╡╨╝ ╨┐╨╛╨╝╨╛╤З╤М."
+                ),
+                confirmation_greeting_template=(
+                    "╨Х╤Б╨╗╨╕ ╤П ╨┐╤А╨░╨▓╨╕╨╗╤М╨╜╨╛ ╤А╨░╤Б╤Б╨╗╤Л╤И╨░╨╗, ╨▓╨░╤Б ╨╖╨╛╨▓╤Г╤В {name}? ╨Ш ╤З╨╡╨╝ ╨┐╨╛╨╝╨╛╤З╤М?"
+                ),
+                inline_followup_prompt="╨Ш ╨╡╤Й╤С ╨┐╨╛╨┤╤Б╨║╨░╨╢╨╕╤В╨╡, ╨║╨░╨║ ╨╝╨╜╨╡ ╨║ ╨▓╨░╨╝ ╨╛╨▒╤А╨░╤Й╨░╤В╤М╤Б╤П?",
+            ),
+            kz=VoiceNameCollectionLanguageTexts(
+                start_prompt="╨б╙Щ╨╗╨╡╨╝╨╡╤В╤Б╤Ц╨╖ ╨▒╨╡. ╨Р╤В╤Л╥г╤Л╨╖╨┤╤Л ╨░╤В╨░╨╣╤Б╤Л╨╖ ╨▒╨░?",
+                personalized_greeting_template=(
+                    "╨б╙Щ╨╗╨╡╨╝╨╡╤В╤Б╤Ц╨╖ ╨▒╨╡, {name}. ╥Ъ╤Л╤Б╥Ы╨░╤И╨░ ╨░╨╣╤В╤Л╥г╤Л╨╖╤И╤Л, ╨╜╨╡ ╨║╙й╨╝╨╡╨║╤В╨╡╤Б╤Г ╨║╨╡╤А╨╡╨║."
+                ),
+                confirmation_greeting_template=(
+                    "╨Х╨│╨╡╤А ╨┤╥▒╤А╤Л╤Б ╨╡╤Б╤В╤Ц╤Б╨╡╨╝, ╨░╤В╤Л╥г╤Л╨╖ {name}? ╥Ъ╨░╨╗╨░╨╣ ╨║╙й╨╝╨╡╨║╤В╨╡╤Б╨╡╨╝╤Ц╨╜?"
+                ),
+                inline_followup_prompt="╨в╨░╥У╤Л ╨▒╤Ц╤А ╨╜╨░╥Ы╤В╤Л╨╗╨░╤Б╨░╨╝, ╤Б╤Ц╨╖╨│╨╡ ╥Ы╨░╨╗╨░╨╣ ╥Ы╨░╤А╨░╨╣╨╝╤Л╨╜?",
+            ),
+        ),
+    )
+
+
 def _settings_row(session) -> VoiceNameCollectionSettingsRow | None:
     return session.execute(
         select(VoiceNameCollectionSettingsRow).where(
diff --git a/services/ai_voice_runtime_service/app.py b/services/ai_voice_runtime_service/app.py
index 260344d..c238f4c 100644
--- a/services/ai_voice_runtime_service/app.py
+++ b/services/ai_voice_runtime_service/app.py
@@ -211,6 +211,14 @@ def _default_voice_greeting(language: str | None, *, agent_profile: str = "voice
     )
 
 
+def _default_voice_greeting(language: str | None, *, agent_profile: str = "voice_support") -> str:
+    if str(agent_profile or "").strip() == "voice_start":
+        return _voice_start_name_prompt(language)
+    if str(language or "").strip() == "kz":
+        return "╨б╙Щ╨╗╨╡╨╝╨╡╤В╤Б╤Ц╨╖ ╨▒╨╡. ╨б╥▒╤А╨░╥У╤Л╥г╤Л╨╖╨┤╤Л ╨░╨╣╤В╤Л╥г╤Л╨╖╤И╤Л, ╨║╙й╨╝╨╡╨║╤В╨╡╤Б╤Г╨│╨╡ ╤В╤Л╤А╤Л╤Б╨░╨╝╤Л╨╜."
+    return "╨Ч╨┤╤А╨░╨▓╤Б╤В╨▓╤Г╨╣╤В╨╡. ╨Я╨╛╨┤╤Б╨║╨░╨╢╨╕╤В╨╡, ╨┐╨╛╨╢╨░╨╗╤Г╨╣╤Б╤В╨░, ╤З╨╡╨╝ ╨┐╨╛╨╝╨╛╤З╤М."
+
+
 _ASR_PROVIDER = build_asr_provider(_asr_provider_name())
 _TTS_PROVIDER = build_tts_provider(_tts_provider_name())
 
diff --git a/tests/test_ai_orchestrator_service.py b/tests/test_ai_orchestrator_service.py
index 0f25929..972343e 100644
--- a/tests/test_ai_orchestrator_service.py
+++ b/tests/test_ai_orchestrator_service.py
@@ -1116,6 +1116,39 @@ def test_voice_module_import_helper_returns_app_module():
     assert hasattr(loaded, "_interaction_request")
 
 
+def test_text_openai_prompt_uses_operator_persona_without_ai_disclosure():
+    messages = ai_module._openai_prompt(
+        customer=None,
+        interaction=SimpleNamespace(
+            interaction_id="int_text_prompt",
+            status="new",
+            queue_id="que_text",
+            subject="Need help",
+            customer_id="cust_text",
+        ),
+        thread=SimpleNamespace(thread_id="thr_text", chat_id="chat_text", display_name="Customer"),
+        messages=[
+            SimpleNamespace(
+                author_type="customer",
+                author_id="cust_text",
+                direction="inbound",
+                text="╨е╨╛╤З╤Г ╤Г╨╖╨╜╨░╤В╤М ╤В╨░╤А╨╕╤Д",
+                created_at=utc_now_iso(),
+            )
+        ],
+        kb_results=[],
+        language="ru",
+        channel_label="Telegram",
+        channel_key="telegram",
+    )
+
+    system_prompt = messages[0]["content"]
+    assert "AI assistant" not in system_prompt
+    assert "Always disclose" not in system_prompt
+    assert "human operator" in system_prompt
+    assert "Do not mention a knowledge base" in system_prompt
+
+
 def test_voice_decision_reuses_recent_topic_instead_of_repeating_same_prompt():
     transcript_window = [
         SimpleNamespace(speaker="assistant", text=voice_module._voice_greeting("ru"), sequence_no=1),
@@ -1222,6 +1255,51 @@ def test_voice_kb_search_matches_relaxed_phrase_and_returns_kb_answer():
     assert "relaxbasicx" in decision["reply_text"].lower()
 
 
+def test_voice_llm_guarded_decision_uses_operator_style_without_ai_or_kb(monkeypatch):
+    monkeypatch.setenv("AI_VOICE_POLICY_MODE", "llm_guarded")
+    monkeypatch.setattr(ai_module, "_ai_provider", lambda: "openai_compatible")
+    captured: dict[str, object] = {}
+
+    def _fake_structured(messages):
+        captured["messages"] = messages
+        return {
+            "language": "ru",
+            "intent": "kb_answer",
+            "reply_text": "╨б╨╡╨╣╤З╨░╤Б ╨┐╨╛╨┤╤Б╨║╨░╨╢╤Г: ╤Д╨╕╨╗╨╕╨░╨╗ ╨▓ ╨Р╨╗╨╝╨░╤В╤Л ╤А╨░╨▒╨╛╤В╨░╨╡╤В ╤Б 9:00 ╨┤╨╛ 18:00 ╨┐╨╛ ╨▒╤Г╨┤╨╜╤П╨╝.",
+            "confidence": 0.88,
+            "needs_handoff": False,
+            "handoff_reason": None,
+            "case_action": "keep_open",
+            "kb_refs": ["kba_voice_1"],
+            "_model": "gpt-test",
+            "_latency_ms": 42,
+            "_finish_reason": "stop",
+        }
+
+    monkeypatch.setattr(ai_module, "_request_structured_model_decision", _fake_structured)
+
+    kb_article = SimpleNamespace(article_id="kba_voice_1", title="╨У╤А╨░╤Д╨╕╨║ ╤А╨░╨▒╨╛╤В╤Л", body="╨С╤Г╨┤╨╜╨╕ 9:00-18:00")
+    decision = voice_module._voice_decision(
+        language="ru",
+        customer=SimpleNamespace(customer_id="cus_voice_1", display_name="╨Р╨╣╨┤╨╛╤Б"),
+        interaction=SimpleNamespace(interaction_id="int_voice_llm", status="new", queue_id="que_voice", subject="hours"),
+        transcript_text="╨Ъ╨░╨║ ╤А╨░╨▒╨╛╤В╨░╨╡╤В ╤Д╨╕╨╗╨╕╨░╨╗ ╨▓ ╨Р╨╗╨╝╨░╤В╤Л?",
+        transcript_window=[SimpleNamespace(speaker="caller", text="╨Ъ╨░╨║ ╤А╨░╨▒╨╛╤В╨░╨╡╤В ╤Д╨╕╨╗╨╕╨░╨╗ ╨▓ ╨Р╨╗╨╝╨░╤В╤Л?", sequence_no=1, source_type="voice_asr", barge_in_interrupted=False, created_at=utc_now_iso())],
+        kb_results=[kb_article],
+        disclosure_required=False,
+        customer_name_value="╨Р╨╣╨┤╨╛╤Б",
+        customer_name_status="name_obtained",
+    )
+
+    assert decision["intent"] == "kb_answer"
+    assert decision["model"] == "gpt-test"
+    assert "ai" not in decision["reply_text"].lower()
+    assert "╨▒╨░╨╖╨░ ╨╖╨╜╨░╨╜╨╕╨╣" not in decision["reply_text"].lower()
+    system_prompt = captured["messages"][0]["content"]
+    assert "human operator" in system_prompt
+    assert "Do not say or imply that you are an AI" in system_prompt
+
+
 def test_ai_enqueue_creates_outbound_ai_reply_and_delivery_flow(monkeypatch):
     monkeypatch.setenv("AI_TELEGRAM_ENABLED", "1")
     monkeypatch.setenv("AI_PROVIDER", "stub")
diff --git a/tests/test_ai_voice_runtime_service.py b/tests/test_ai_voice_runtime_service.py
index 2a4e836..bceb96a 100644
--- a/tests/test_ai_voice_runtime_service.py
+++ b/tests/test_ai_voice_runtime_service.py
@@ -105,6 +105,95 @@ def test_create_voice_ai_session_records_greeting_without_blocking_on_orchestrat
         raise AssertionError("background voice-session start did not finish in time")
 
 
+def test_create_voice_ai_session_records_greeting_without_blocking_on_orchestrator(monkeypatch):
+    allow_start = threading.Event()
+
+    def _fake_orchestrator_request(method: str, path: str, *, payload=None, timeout=10.0):
+        assert method == "POST"
+        assert path.endswith("/start")
+        assert allow_start.wait(timeout=2.0)
+        return {
+            "session_id": "ais_voice_demo",
+            "language": "ru",
+            "greeting_text": "╨Ч╨┤╤А╨░╨▓╤Б╤В╨▓╤Г╨╣╤В╨╡. ╨Я╨╛╨┤╤Б╨║╨░╨╢╨╕╤В╨╡, ╨┐╨╛╨╢╨░╨╗╤Г╨╣╤Б╤В╨░, ╤З╨╡╨╝ ╨┐╨╛╨╝╨╛╤З╤М.",
+            "disclosure_required": True,
+        }
+
+    monkeypatch.setattr(runtime_module, "_orchestrator_request", _fake_orchestrator_request)
+
+    client = TestClient(runtime_module.app)
+    response_holder: dict[str, object] = {}
+
+    def _request() -> None:
+        response_holder["response"] = client.post(
+            "/internal/voice-ai/sessions",
+            headers=_admin_headers(),
+            json={
+                "call_id": "call_voice_runtime_1",
+                "linked_id": "linked_voice_runtime_1",
+                "interaction_id": "int_voice_runtime_1",
+                "queue_id": "que_voice_runtime_1",
+                "caller_number": "+77010000031",
+                "caller_name": "Runtime Caller",
+                "agent_profile": "voice_support",
+                "language_hint": "ru",
+                "handoff_queue_id": "que_voice_runtime_1",
+                "metadata": {"queue_code": "voice_lab"},
+            },
+        )
+
+    request_thread = threading.Thread(target=_request, daemon=True)
+    request_thread.start()
+    request_thread.join(timeout=0.3)
+    assert not request_thread.is_alive()
+
+    response = response_holder["response"]
+    assert response.status_code == 200
+    payload = response.json()
+    assert payload["ai_session_id"] is None
+    assert payload["status"] == "greeting"
+
+    session = get_session()
+    try:
+        voice_session = session.execute(
+            select(VoiceAISessionRow).where(VoiceAISessionRow.call_id == "call_voice_runtime_1")
+        ).scalar_one()
+        assert voice_session.status == "greeting"
+        assert voice_session.disclosure_played_at is None
+        assert voice_session.last_ai_reply_at is None
+        greeting_segment = session.execute(
+            select(VoiceTranscriptSegmentRow)
+            .where(VoiceTranscriptSegmentRow.session_id == voice_session.session_id)
+            .where(VoiceTranscriptSegmentRow.speaker == "assistant")
+        ).scalar_one()
+        assert "ai" not in greeting_segment.text.lower()
+        assert greeting_segment.is_final is False
+    finally:
+        session.close()
+
+    allow_start.set()
+    deadline = time.time() + 2.0
+    while time.time() < deadline:
+        session = get_session()
+        try:
+            voice_session = session.execute(
+                select(VoiceAISessionRow).where(VoiceAISessionRow.call_id == "call_voice_runtime_1")
+            ).scalar_one()
+            if voice_session.ai_session_id == "ais_voice_demo":
+                greetings = session.execute(
+                    select(VoiceTranscriptSegmentRow)
+                    .where(VoiceTranscriptSegmentRow.session_id == voice_session.session_id)
+                    .where(VoiceTranscriptSegmentRow.speaker == "assistant")
+                ).scalars().all()
+                assert len(greetings) == 1
+                break
+        finally:
+            session.close()
+        time.sleep(0.05)
+    else:
+        raise AssertionError("background voice-session start did not finish in time")
+
+
 def test_mark_reply_delivered_finalizes_only_matching_pending_assistant_segment():
     now = utc_now_iso()
     session = get_session()
