feat(ai): Finalize voice operator persona and voice name flow

This commit is contained in:
Yera All
2026-04-06 23:19:24 +05:00
parent cfee88cdb1
commit 1497bb0feb
31 changed files with 2175 additions and 638 deletions
-4
View File
@@ -1,4 +0,0 @@
track9/[0-9]*/
track9_1/[0-9]*/
track10/[0-9]*/
track11/[0-9]*/
-13
View File
@@ -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`
@@ -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`
-15
View File
@@ -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`
@@ -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.
@@ -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>`
@@ -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"
}
]
}
@@ -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"
}
]
@@ -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"
}
@@ -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`
@@ -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"}
@@ -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>`
@@ -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
@@ -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
-27
View File
@@ -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)
@@ -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
@@ -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:
-18
View File
@@ -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.