diff --git a/AGENTS.md b/AGENTS.md index 5c1a6d9..2f82dc0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,12 +1,20 @@ -# ai-call:部署与开发约束 +# agent-call:部署与开发约束 ## 目录与实施范围 - 项目根目录用于部署,`docs/` 保存需求、计划与验收资料,不挪回根目录。 -- 当前先实现 ASR Web 验证服务、Asterisk 配置和阿里云主机准备工具;不把这些当成完整外呼平台已上线。 +- 现有实现为 ASR Web 验证、Asterisk 配置和阿里云主机准备工具,不是完整外呼平台。用户已接受最终方案,后续按 `docs/最终开发部署监控与验收计划_v1.0.md` 开展契约驱动 Mock 开发,再替换真实依赖。 - 用户已决定**仅复用 voice_test 的 ASR**。禁止复制或启用该仓库的 LLM/TTS;新规范确认前,界面必须明确显示未启用。 - SaaS 指令与所有业务结果均走 RabbitMQ;录音先上传 OSS,再通过 MQ 回传 OSS ID。接口/消息规范由用户制定,不能擅自改成 HTTP 业务回调。 +## 最终实施与 Mock 边界(用户已确认) + +- 以最终开发部署监控与验收计划为实施入口,`docs/SaaS交互_OpenAPI与MQ契约规划_v0.1.md` 正文 v1.0 是字段/路径/状态的唯一维护来源;从 D01 生成 OpenAPI/共用 MQ Schema 开始,不重复等待已接受方案拍板。 +- 外部 SaaS、数据库、RabbitMQ、SIP/AI、OSS/云条件并行协调。HTTP Mock 遵守 OpenAPI;MQ 用真实测试 broker,SIP/ARI/RTP及AI流式通道按协议模拟,不改成HTTP业务回调;持久状态使用真实隔离数据库,不用内存字典代替。 +- `mock/mixed/real` 必须可识别,Mock 默认隔离真实外网,正式模式拒绝 Mock/测试凭证,不静默回退。LLM/TTS 可开发新 Mock 适配,但不得复用旧实现或假称真实供应商已启用。 +- 已接受原值key一对一绑定、超出224个UTF-8字节路由预算则停发保留原任务而非截断;控制CAS、整体补传、无应用收讫、整任务撤销屏障等规则以主契约为准。 +- 最终计划的 DEV/SCALE-MOCK 数值是工程初始测试基线,不是生产SLA/业务额度/消费授权。真实预算、供应商能力、保留与RPO/RTO需在实际部署前登记核验;Mock通过不得替代真实1000路完整AI、N+1及供应商验收。 + ## 固定资源与云部署规则 1. 区域为阿里云北京 `cn-beijing`。 @@ -57,13 +65,13 @@ ## 多租户队列与公平调度(用户已确认,运行代码待实现) -- SaaS 按可信 `tenant_id` 映射向**租户独立 RabbitMQ 命令队列** PUSH,呼出应用调度器负责租户间公平调度;不再用所有租户共享的执行 FIFO,不增加 HTTP 拨号入口。业务任务/重试决策仍属于 SaaS。 +- SaaS 按可信 `tenant_key` 向**租户独立 RabbitMQ 命令队列** PUSH,呼出应用调度器负责租户间公平调度;不再用所有租户共享的执行 FIFO,不增加 HTTP 拨号入口。业务任务/重试决策仍属于 SaaS。 - 默认建议活跃且可调度租户等权轮询,差异化权重需按业务规则确认;每轮有界取数,租户额度耗尽或线路不可用时跳过。按租户及全局限制预取、未 ACK 和已持久化待发起窗口,不能先消费到无界内存 FIFO;重启恢复也必须公平。 - 租户并发/CPS 配额跨所有 Cell、调度实例汇总;并发覆盖预留、拨号、振铃、接通及待对账占用,CPS 包括 FALLBACK。实际发起同时满足租户额度、供应商、Cell/出口和 AI 完整资源租约。多实例须协调调度所有权和原子额度,不能各自发放一份;未知活动通话不能仅因租约到期直接释放占用。 -- 平台负责队列创建、精确绑定、权限和安全停用/清理;路由标识由 tenant_id 受控唯一映射,必须校验正文租户与队列一致。最终用户不直接连接 broker,不允许任意指定其他租户路由。命令重试/死信恢复回原租户调度域,不绕过配额。 +- 平台负责队列创建、精确绑定、权限和安全停用/清理;`tenant_key` 是 SaaS 产生的业务数据,命令、队列路由及后续 MQ 回调均原样使用,不做清洗、编码或业务格式限制;必须校验正文值与队列/路由绑定精确一致。最终用户不直接连接 broker,不允许任意指定其他租户路由。命令重试/死信恢复回原租户调度域,不绕过配额。 - 每租户设置发布速率、队列消息数/字节和待执行窗口上限,并有全局 broker 水位保护。队列满明确拒绝发布,不丢弃队头旧命令;SaaS 持久保留未确认发布记录,使用原执行标识有限重试。独立队列不代表独享 broker 资源或无限积压。 - 公平调度分配新执行机会,不为公平挂断已接通电话;资源满需等待释放。固定开始时限必须另确认覆盖线路/AI/Cell 的保底容量或受限借用策略,不能仅用轮询宣称保证。 -- 本轮不改变事件结果队列为一租户一队列。队列命名、轮转/预取、配额、保留与等待指标待 G0 冻结;须验收大租户积压下小租户公平、多实例配额不超额、背压不丢消息及租户路由安全。 +- 本轮不改变事件结果队列为一租户一队列。资源统一使用 `agent-call` 命名空间;命令 exchange 使用 direct,命令 routing key 为 `agent-call.tenant.{tenant_key}.call.execute`,事件 routing key 为 `agent-call.{event_type}`。轮转/预取、配额、保留与等待指标待 G0 冻结;须验收大租户积压下小租户公平、多实例配额不超额、背压不丢消息及租户路由安全。 ## 其它环境前置 @@ -78,7 +86,7 @@ - :Asterisk 调研和 Mock 底座;不能把历史测试结果当成本环境验收。 - :仅借用 ASR 协议代码;本项目访问控制、Web页面和生命周期独立实现。 -- `docs/一期呼出应用开发计划_v1.0.md` 为阶段计划;SaaS 对接草案见 `docs/SaaS交互_OpenAPI与MQ契约规划_v0.1.md`(文件路径保留,版本见正文)。本轮运行步骤见 `docs/部署接入_运行说明.md`,旧文档只读保留。 +- 最终实施入口为 `docs/最终开发部署监控与验收计划_v1.0.md`;主契约 `docs/SaaS交互_OpenAPI与MQ契约规划_v0.1.md`(路径保留,正文v1.0)。旧一期计划保留WBS/AT和历史估算,旧交付文档作实现细则;当前已存在服务的运行步骤仍见 `docs/部署接入_运行说明.md`,不能把未来产物当成已实现。 ## 验证与交付 diff --git a/compose.asterisk.yaml b/compose.asterisk.yaml index 8d8f679..a46cbe8 100644 --- a/compose.asterisk.yaml +++ b/compose.asterisk.yaml @@ -1,5 +1,5 @@ --- -name: ai-call-sip +name: agent-call-sip services: asterisk: image: "${ASTERISK_IMAGE:?Set an approved image digest}" diff --git a/compose.yaml b/compose.yaml index cdf8618..6db3252 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,5 +1,5 @@ --- -name: ai-call +name: agent-call services: asr-web: build: diff --git a/deploy/aliyun.example.json b/deploy/aliyun.example.json index 41f5517..ac383b3 100644 --- a/deploy/aliyun.example.json +++ b/deploy/aliyun.example.json @@ -1,7 +1,7 @@ { "region": "cn-beijing", "public_ip": "123.56.71.98", - "project_tag": "ai-call", + "project_tag": "agent-call", "profile": null, "adopt_instance_id": null, "image_id": "", diff --git a/deploy/render_asterisk.py b/deploy/render_asterisk.py index af17fcb..b8ad7a4 100644 --- a/deploy/render_asterisk.py +++ b/deploy/render_asterisk.py @@ -98,7 +98,7 @@ def render(cfg, env): raise ValueError( "primary and backup targets must be distinct; shared failure domains still require validation" ) - transport = f"[global]\ntype=global\nuser_agent=ai-call\n\n[transport-udp]\ntype=transport\nprotocol=udp\nbind=0.0.0.0:5060\nlocal_net={network}\nexternal_signaling_address={PUBLIC_IP}\nexternal_media_address={PUBLIC_IP}\n\n" + transport = f"[global]\ntype=global\nuser_agent=agent-call\n\n[transport-udp]\ntype=transport\nprotocol=udp\nbind=0.0.0.0:5060\nlocal_net={network}\nexternal_signaling_address={PUBLIC_IP}\nexternal_media_address={PUBLIC_IP}\n\n" return { "http.conf": f"[general]\nenabled=yes\nbindaddr={bind}\nbindport=8088\n", "ari.conf": f"[general]\nenabled=yes\npretty=no\n\n[outbound]\ntype=user\nread_only=no\npassword={password}\n", diff --git a/docs/SaaS交互_OpenAPI与MQ契约规划_v0.1.md b/docs/SaaS交互_OpenAPI与MQ契约规划_v0.1.md index fa79a69..f9b0407 100644 --- a/docs/SaaS交互_OpenAPI与MQ契约规划_v0.1.md +++ b/docs/SaaS交互_OpenAPI与MQ契约规划_v0.1.md @@ -1,13 +1,13 @@ # SaaS 交互:OpenAPI 与 MQ 契约规划 -**版本:** v0.3(沿用原文件路径) -**状态:** 租户独立命令队列+公平调度方案已确认;详细接口、配额数值及调度参数仍待评审,非已发布接口规范。仅更新规划,不实现服务、不部署、不拨号。 -**依据:** [一期呼出应用开发计划](一期呼出应用开发计划_v1.0.md)、项目根目录 `AGENTS.md`。 -**范围:** 现有 SaaS 与呼出应用的双向 HTTP 接口、RabbitMQ 命令/事件、OSS 录音交接及联调验收。本文细化已有六类 HTTP 接口,不建设通用开放平台。 +**版本:** v1.0(沿用原文件路径) +**状态:** 用户已接受方案,作为契约驱动 Mock 开发依据;尚未生成机器可读规范或实现全部接口,非生产验收报告。外部资料由协调取得,真实参数须验证。 +**依据:** [最终开发、部署、监控与验收计划](最终开发部署监控与验收计划_v1.0.md)、项目根目录 `AGENTS.md`。 +**范围:** 双方七条业务 HTTP 路径、RabbitMQ 命令/事件、OSS 录音交接;包含按源命令补传,不建设通用开放平台。 -> 阅读约定:第 1 节为已确认边界;其余路径、字段、状态、数值和安全方案均为建议草案,须由用户评审后冻结。出现 MUST/必须等约束,是拟发布契约应具备的要求,不表示现有运行代码已经支持。 +> 阅读约定:授权撤销、租户绑定/传输超限、按命令补传、应用确认等方案已经接受。正文中的首期“建议”按最终计划的已接受方案实施,不再重新拍板;G0 完成 Schema/Mock 配置与可验证用例,未提供的真实供应商、预算、保留/恢复等参数仍需落实。Mock 成功不代表真实接口、供应商或生产容量通过。 -**本次修订:** 补齐拨号前等待/查询、准入期限、资源原因码、发布背压和控制竞争样例;与一期计划统一 execution_id 及单线路启动。六类 HTTP 路径不变,不新增配额管理或租户队列管理接口;本版仍为待冻结草案,不升级 HTTP `/v1`。 +**本次修订:** 方案从建议转为已接受的开发基线,七条路径保持不变;外部依赖按 OpenAPI/MQ Schema 及各自流式/SIP协议 Mock。部署、监控、测试数值、交付门禁统一引用最终计划;不新增拨号入口,不升级 HTTP `/v1`。 ## 1. 已确认的系统边界 @@ -18,7 +18,7 @@ 5. SaaS 负责客户/任务主数据、业务调度、业务重试决策、授权和长期存储;呼出应用只保存必要执行事实、控制屏障、幂等、资源租约和投递记录。 6. 生产采用多机器、多 EIP 直连;每通电话固定 Cell/出口。SaaS 不逐呼改写共享 SIP 配置,也不能任意指定 SIP 地址、凭证或越权主叫。 7. 目前只实现 ASR 验证基础,LLM/TTS 协议尚待新规范;本文对完整 AI 链路的描述是目标契约,不代表已启用或验收。 -8. **按租户 ID 映射到独立 RabbitMQ 命令队列,由呼出应用调度器负责租户间公平调度**。不再采用所有租户共用一个执行 FIFO;同时限制租户发布/积压、预取窗口及跨 Cell 并发/CPS。该架构已确认,命名和限值仍为草案。 +8. **按 SaaS 提供的 `tenant_key` 绑定到独立 RabbitMQ 命令队列,由呼出应用调度器负责租户间公平调度**。不再采用所有租户共用一个执行 FIFO;同时限制租户发布/积压、预取窗口及跨 Cell 并发/CPS。`tenant_key` 是 SaaS 业务数据,命令、队列路由和后续 MQ 回调均原样使用;命名规则已统一,运行参数和限值仍待 G0 冻结。 ## 2. 交付拆分与双方职责 @@ -31,7 +31,7 @@ | MQ VHost、账号、ACL、持久队列和告警 | 运维按冻结契约配置 | 双方服务 | | 对外发布规范、样例、错误码和验收用例 | 用户主导制定 | 双方评审实施 | -建议冻结后交付两个 OpenAPI 3.1 文件,分别描述两个 HTTP 服务;MQ 使用 AsyncAPI 文档及 JSON Schema,不把 AMQP 消费者伪装成 HTTP Webhook。首版先有契约及样例,不引入 SDK 生成器或开发者门户。 +本文是标识、字段、HTTP 路径、MQ 拓扑/事件和状态语义的唯一维护来源;计划与交付文档只引用,完整样例集中在第 8 节。冻结后交付两个 OpenAPI 3.1 文件及共用 MQ JSON Schema,MQ 拓扑沿用本文表格;AsyncAPI 等有明确工具消费需求再补,不作为首期门禁。D01 以共用 Schema 校验全部示例,区分命令、事件和 HTTP 响应;静态必填核对不冒充完整 Schema 验收,不引入 SDK 生成器或开发者门户。 ## 3. 公共约定 @@ -40,6 +40,7 @@ | 字段 | 定义 | | --- | --- | | `tenant_id` | SaaS 租户 ID;请求中的值必须与服务身份获授权范围匹配,不能仅相信报文自报租户 | +| `tenant_key` | SaaS 提供的租户业务数据;命令、队列路由和后续 MQ 回调原样保留,不由呼出应用清洗、编码、截断或重命名 | | `task_id` / `task_item_id` | SaaS 任务/号码明细 ID;不因重试改变原明细归属 | | `execution_id` | 建议新增:SaaS 为一次**获授权的业务外呼**分配的 ID;网络重试、消息重投和内部线路切换不变,业务重新外呼才新建 | | `command_id` | 单条执行、控制或补传命令 ID;同一命令重试保持不变 | @@ -50,11 +51,20 @@ | `oss_id` | SaaS 存储服务确认的、不透明且稳定的资产 ID;不默认等同于 Object Key、ETag、文件名或 URL | | `trace_id` | 链路关联信息,不用于授权或幂等 | -- ID 均为不透明字符串,不按手机号、数字或 UUID 强制改写已有 SaaS ID;建议长度 1–128,拒绝控制字符、路径分隔符和空白首尾。最终字符集在 Schema 冻结。 +- 除 `tenant_key` 外,ID 均为不透明字符串,不按手机号、数字或 UUID 强制改写已有 SaaS ID;建议长度 1–128,拒绝控制字符、路径分隔符和空白首尾。最终字符集在 Schema 冻结。 +- `tenant_key` 不适用上述本地 ID 格式限制;呼出应用不清洗、编码、截断或重命名,正文、队列及路由绑定值须精确一致。该原值一致性规则不免除服务身份/可信归属检查;传输字段可承载性另见第 5.1.1 节建议。 - 时间使用 RFC 3339 UTC,例如 `2026-09-11T08:00:00.000Z`;持续时间字段以 `_ms` 结尾,大小为字节,采样率为 Hz。 - 未接通时 `answered_at` 为 `null`,不填写虚假接通时间;未完成的结果用明确状态,不用空字符串冒充成功。 - 所有唯一键和检索均包含租户作用域。不同租户碰巧使用相同 ID,不能互相查询或命中幂等记录。 +### 3.1.1 租户绑定与原值生命周期(已确认) + +- 建议由 SaaS 维护可信的 `tenant_id ↔ tenant_key` 一对一注册关系,平台据此创建/发现队列;业务身份、配额、公平份额及去重始终按 tenant_id 聚合,不按队列数量另发额度。正文/队列/routing key 精确一致仍须同时满足可信注册归属,不能让自报值认领租户。 +- tenant_key 是原始字符串;JSON 转义、UTF-8 传输、管理 API 路径转义及 ACL 正则字面量转义仅是协议表示,解码后的业务值必须完全相同。不能把原值直接拼成 ACL 正则、Shell、文件路径或 SQL。这里不增加字符清洗、大小写转换或业务格式限制。 +- 建议首期不支持原地变更 key;保留期内不向别的租户复用旧绑定。迁移须另行评审停发、屏障、队列排空、去重/事件保留与恢复规则,不自动创建第二队列来绕过额度。 +- 命令、任务、执行和 outbox 持久保存当时的 tenant_key;历史回传使用原快照,不能按最新配置重新写值。幂等键仍为 `(tenant_id, command_id)`、`(tenant_id, execution_id)`、`(tenant_id, event_id)`,**不加入 tenant_key**;同 event_id 异内容应隔离告警而非重复应用。 +- HTTP 控制可早于首条 execute:tenant_key 从服务身份获授权的可信注册关系解析并保存,而不是从首次执行猜测。绑定不可用或不一致时明确拒绝/报依赖不可用,不确认 accepted,更不能伪造 key 发送事件;任务归属另按第 4.1 节验证。 + ### 3.2 HTTP 共性 - 使用 HTTPS,仅服务到服务调用,不向浏览器或公网客户直接暴露内部接口。 @@ -83,6 +93,7 @@ | 呼出应用 | `GET /internal/v1/outbound/commands/{command_id}` | `outbound.read` | 200;命令与生效状态 | | 呼出应用 | `GET /internal/v1/outbound/calls/{call_id}` | `outbound.read` | 200;执行事实与资产/投递快照 | | 呼出应用 | `POST /internal/v1/outbound/calls/{call_id}/replays` | `outbound.replay` | 202;受控历史事件补传 | +| 呼出应用 | `POST /internal/v1/outbound/commands/{source_command_id}/replays` | `outbound.replay` | 202;无 call_id 的原命令结果补传 | | SaaS | `POST /internal/v1/outbound/recording-uploads` | `recording.upload` | 201/200;创建或取得原上传会话 | | SaaS | `POST /internal/v1/outbound/recording-uploads/{upload_id}/complete` | `recording.complete` | 200;存储验证成功,确认 OSS ID | @@ -94,8 +105,7 @@ | --- | --- | --- | | `command_id` | 是 | 与 `Idempotency-Key` 一致 | | `action` | 是 | `pause` / `resume` / `stop` | -| `expected_task_revision` | 是 | SaaS 认为当前应有的控制版本 | -| `task_revision` | 是 | 本次目标版本,必须等于 expected + 1 | +| `expected_task_revision` | 是 | SaaS 认为当前应有的控制版本;CAS 成功后服务端生成目标版本 expected + 1 | | `active_call_policy` | stop 时是 | `drain` / `hangup`;不设隐藏的强制挂断默认值 | | `reason` | 是 | 有界审计原因,不放敏感客户资料 | @@ -105,7 +115,7 @@ - `task_revision` 仅表示任务控制栅栏版本,不表示智能体配置版本。首版建议初始版本为 1,任务初始运行;尚未收到 execute 时的控制,也必须能为获授权任务建立持久屏障。 - 基于服务身份及双方可信任务绑定验证归属;不能因第一次见到任意 task_id 就将其认领给请求租户。任务绑定来源在 G0 确认,可来自获授权 SaaS 主数据或其受信发布边界,不额外开放拨号入口。 -- 同任务控制串行化,提交时 CAS 校验 expected;版本冲突或另一个控制尚在生效中返回 409。重复 command_id 先走幂等,不重复推进版本。 +- 同任务控制串行化,提交时 CAS 校验 expected,服务端在同一事务生成并保存目标版本 expected + 1;控制请求不接受 task_revision,响应/查询/MQ 仍区分 requested_task_revision 与 applied_task_revision。版本冲突或另一个控制尚在生效中返回 409;重复 command_id 先走幂等并返回原目标版本,不重复递增。执行命令的 task_revision 不变,仍必须使用已生效版本。 - 暂停/停止受理后,先禁止发放新的拨号许可,再等待所有相关 Cell 上的在途许可完成或失效;确认后才能回传 `applied`。存在状态不明节点时保持 `applying/reconciling`,不能虚报全局生效。 - 每次真实发起和 FALLBACK 都检查当前控制版本、状态、授权期限及有效资源租约;节点失联或租约过期停止新发起。仅把数据库改为 paused 不足以形成屏障。 - `pause`:已拨出和已接通电话继续原生命周期;未发起执行不再启动,报告阻止原因。`resume`:允许新版本指令,不自动重放旧积压。 @@ -114,6 +124,18 @@ - stopped 不允许 resume;重新开展业务使用新任务,避免迟到指令复活。控制失败后不能自行放开已经建立的暂停/停止屏障。 - HTTP 查询和 MQ `command.result` 均区分 requested revision 与 applied revision;这项多 Cell 屏障能力为待实现项。 +### 4.1.1 已排队执行的授权撤销(已确认) + +首期建议复用任务级 pause/stop 屏障,允许为安全暂停整个受影响任务,暂不增加号码级控制接口;不能仅发布另一条 call.execute 或仅停止新增发布来撤销旧命令。 + +1. SaaS 持久化拒绝再联系/撤销标记,并与任务生成及发布出队的授权检查协调,立即阻止该业务对象产生新的授权执行。持久追踪所有受影响任务及待发布、发布不确定、已发布和已受理的执行;不能只查 broker 中可见消息。 +2. 对所有受影响且未停止的任务提交现有 pause 控制,保存各任务的 command_id/revision。控制必须覆盖 broker 积压、waiting、已提交但尚未真实发起的意图以及 FALLBACK;不能通过改写/删除共享队列中的号码消息实现撤销。 +3. 全部相关任务屏障 applied、在途许可已确认不能再发起后,SaaS 才标记执行侧撤销生效。HTTP 超时、CAS 冲突、已有控制处理中或 Cell 失联时保持 pending/reconciling,原 ID 重试或对账,不虚报成功、不放开发布。已发出的电话按既定生命周期继续,强制挂断须另有授权。 +4. 后续若恢复受影响任务,必须再次过滤撤销对象、完成旧执行事实对账,并使用当前已生效版本的新授权执行;旧 command_id/execution_id 原样重投仍命中原事实,不静默复活。stopped 任务不恢复。 +5. SaaS 只在允许拨号时段内发布,not_after 不晚于本次允许时段结束;时段/授权在发布后被提前收紧也触发上述屏障。单靠原 not_after 不能感知新撤销。 + +`contact.opt_out` 经 MQ 到达 SaaS 后触发上述流程。须分别测量客户提出→事件落库→SaaS 禁止新发布→各任务屏障 applied 的时间;MQ 延迟期间不能宣称已全局撤销。G0 冻结生效时限、关联任务枚举依据及整任务暂停的业务代价;若要求立即生效或必须逐号码隔离,需另评审撤销机制,不擅自降低 MQ-09 验收标准。 + ### 4.2 命令查询 通用返回:`command_id`、`command_type`、`task_id/call_id`(适用时)、`status`、`reason_code`、`accepted_at`、`updated_at`。 @@ -150,9 +172,12 @@ execute 查询及对应 `command.result` 建议共用以下字段,MQ 的等待 ### 4.4 历史事件补传 -请求:`command_id`、`event_types`(允许列表)、可选 `event_ids`、`reason`。范围始终限定到路径中的 call_id 和当前租户;event_ids 提供时与 event_types 取交集,不得跨通话。 +请求体仅包含 `command_id`(本次补传操作 ID)和 `reason`;Idempotency-Key 等于本次 command_id。首期不支持事件类型/ID 筛选,额外筛选字段按 INVALID_ARGUMENT 拒绝,不能静默忽略后扩大重放范围。 -- 在受理时记录固定事件截止点与选择条件;重复请求使用同一个补传任务和截止点,不把后续新事件悄悄纳入。 +- `POST /internal/v1/outbound/calls/{call_id}/replays`:整体补传当前租户、该 call_id 关联的已保留历史业务事件,包含通话/文字/录音及关联执行命令结果;不跨通话、不包含补传操作自身结果。 +- `POST /internal/v1/outbound/commands/{source_command_id}/replays`:整体补传当前租户、`aggregate_type=command`、`aggregate_id=source_command_id` 的原 command.result;无 call_id 也可恢复。source_command_id 不得等于本次 command_id。两条路径均校验 `outbound.replay` 及资源归属;不存在/无权访问返回404,已知保留期过期返回410。 +- 两种补传均返回 202 及命令查询 Location;本次状态为 accepted/running/completed/failed,completed 仅表示范围内消息完成 broker 确认,不表示 SaaS 应用。 +- 受理时持久化租户、资源范围和固定事件截止点;重复请求复用原任务/截止点,不纳入后来新事件。执行时分批读取,不因“整体补传”一次加载全部历史到内存。 - 所有被选事件必须来自原持久化事件记录;保留 event_id、原时间和原 payload,可仅在传输头标记 replay。禁止重新合成历史业务事实。 - 只重发结果/文字/录音元数据;不执行拨号、不重放 `call.execute`、不重传录音文件、不重新调用 LLM/TTS。 - 对补传限速并独立计量,避免挤占实时结果队列;部分成功允许重试未确认部分,重复投递由 SaaS 去重。 @@ -184,19 +209,21 @@ execute 查询及对应 `command.result` 建议共用以下字段,MQ 的等待 ## 5. RabbitMQ 拓扑与传输 -### 5.1 最小拓扑草案 +### 5.1 最小拓扑与命名规则 -| 对象 | 名称建议 | 用途 | +| 对象 | 名称/绑定 | 用途 | | --- | --- | --- | -| 环境 VHost | `/ai-call-` | 测试/生产隔离,具体路径由运维配置 | -| 命令 Topic Exchange | `ai-call.commands.v1` | Routing Key 为 `tenant.{tenant_key}.call.execute`;消息类型仍为 `call.execute` | -| 租户独立命令队列 | `ai-call.executor.{tenant_key}.v1` | 一租户一队列,精确绑定该租户 Routing Key;公平调度器有界读取,共享持久去重存储 | -| 事件 Topic Exchange | `ai-call.events.v1` | 发布第 7 节允许的事件 | -| SaaS 事件队列 | `ai-call.saas.events.v1` | SaaS 消费落库 | -| 死信 Exchange | `ai-call.dead.v1` | 隔离无效消息与耗尽重试 | -| 死信队列 | `ai-call.commands.dead.v1`、`ai-call.events.dead.v1` | 分方向审计与受控恢复 | +| 环境 VHost | `/agent-call-` | 测试/生产隔离,具体路径由运维配置 | +| 命令 Direct Exchange | `agent-call.commands.v1` | `direct`、durable;Routing Key 为 `agent-call.tenant.{tenant_key}.call.execute`,消息类型仍为 `call.execute` | +| 租户独立命令队列 | `agent-call.executor.{tenant_key}.v1` | 一租户一队列,精确绑定 `agent-call.tenant.{tenant_key}.call.execute`;公平调度器有界读取,共享持久去重存储 | +| 事件 Topic Exchange | `agent-call.events.v1` | 发布第 7 节允许的事件;Routing Key 为 `agent-call.{event_type}` | +| SaaS 事件队列 | `agent-call.saas.events.v1` | SaaS 消费落库 | +| 死信 Exchange | `agent-call.dead.v1` | 隔离无效消息与耗尽重试 | +| 死信队列 | `agent-call.commands.dead.v1`、`agent-call.events.dead.v1` | 分方向审计与受控恢复 | -- `tenant_key` 是平台将 tenant_id 唯一映射为安全单段路由标识的结果,禁止点号、通配符、碰撞和调用方任意指定;原始 tenant_id 仍保留在正文。发布与消费均校验队列绑定、租户归属和正文一致,发现错配隔离告警,不向错误租户执行。 +- `agent-call` 是 VHost、Exchange、Queue 和 Routing Key 的固定命名空间。命令键固定为 `agent-call.tenant.{tenant_key}.call.execute`;事件键固定为 `agent-call.{event_type}`。AMQP `type` 字段仍使用无前缀的 `call.execute` 或事件类型。 +- `tenant_key` 是 SaaS 提供的原始业务数据,不是呼出应用重新生成的路由标识;不做清洗、编码、大小写转换、截断或其它业务格式限制。命令、队列路由、消息正文和所有后续 MQ 回调原样使用;正文值与队列/路由绑定值须精确一致,同时验证可信归属;传输上限处理见第 5.1.1 节,不擅自改写业务值。 +- 命令 exchange 使用 `direct`,使 `tenant_key` 中的点号、`*`、`#` 等字符不会被当作 topic 通配语义;不得将命令 exchange 改回 topic 并依赖格式限制规避冲突。 - 本轮仅改变执行命令的租户隔离拓扑,事件队列不自动扩展为一租户一队列;事件消费和补传仍需限速及租户校验,不能由此宣称结果链路已有同等等待时延保证。 - Exchange/Queue durable,业务消息 persistent;生产建议 quorum queue,最终以现有 RabbitMQ 版本及 HA 部署确认,不把 durable 单节点当高可用。 - 命令/事件分别使用最小权限账号,TLS 连接;SaaS 不能消费执行器队列,呼出应用不能消费 SaaS 事件队列,也不能发布 execute 指令。 @@ -208,6 +235,14 @@ execute 查询及对应 `command.result` 建议共用以下字段,MQ 的等待 - 死信路径也要验证不丢失;quorum 至少一次 dead-letter 或应用确认后转存二选一落实,不能默认普通 DLX 搬运具有端到端保证。 - 非法 Schema/未知主版本进入隔离队列,不凭不可信字段向任意租户发送错误事件;可安全识别的合法业务拒绝发布 `command.result` 后完成处理。 +### 5.1.1 tenant_key 的传输可承载性(已确认) + +原样透传不等于 RabbitMQ 可承载任意长度。AMQP 0-9-1 routing key 使用 shortstr,最多 255 字节;当前 `agent-call.tenant.` + `.call.execute` 固定占 31 字节,tenant_key 可用预算为 **224 个 UTF-8 字节**。队列名也受 broker 字节上限约束,应分别检查完整队列名与路由键;direct 只消除 topic 通配语义,不消除长度限制。 + +建议注册/发布前做完整传输字段的可承载性检查,不清洗、不截断、不替换原值。超限时停止为该绑定创建队列和发送执行命令,SaaS 持久保留原始任务并明确标记传输不支持、告警;未发布/未受理的状态不是呼出侧 MQ 业务拒绝,不凭空发 command.result,也不无限重试相同不适配值。其它租户不受该注册失败阻断。 + +超限停发且保留原任务的方案已接受;接入时用实际 SaaS 数据和 broker/client 版本验证承载能力。若业务后来要求必须接收更长 key,再走传输标识分离的变更评审。本版不擅自引入哈希/编码映射,也不宣称已支持任意长度。契约测试覆盖 224/225 字节、中文多字节、点号、星号、井号及 ACL 正则特殊字符;允许值在命令和所有回传中逐值相等。 + ### 5.2 传输属性 `content_type=application/json`、UTF-8、`delivery_mode=2`、`message_id=command_id/event_id`、`type=command_type/event_type`;`correlation_id` 可使用 command_id,正文 trace_id 负责跨环节跟踪。 @@ -218,7 +253,7 @@ execute 查询及对应 `command.result` 建议共用以下字段,MQ 的等待 ```text SaaS 持久任务/发布记录 - → 命令 Exchange:按可信 tenant_id 映射路由 + → 命令 Exchange:按 SaaS tenant_key 原样路由 → 租户 A 命令队列 ┐ → 租户 B 命令队列 ├→ 公平调度器 → 租户额度+完整资源租约 → 固定 Cell/出口 → 租户 C 命令队列 ┘ @@ -262,7 +297,7 @@ SaaS 持久任务/发布记录 ### 6.1 字段 -通用外壳:`schema_version`、`command_type=call.execute`、`command_id`、`tenant_id`、`trace_id`、`issued_at`、`not_after`、`payload`。 +通用外壳:`schema_version`、`command_type=call.execute`、`command_id`、`tenant_id`、`tenant_key`、`trace_id`、`issued_at`、`not_after`、`payload`。 payload 必需: @@ -297,7 +332,7 @@ agent_version_id 必须对应呼出侧可用且可信的快照;配置如何同 ### 7.1 通用外壳与合并 -必需:`schema_version`、`event_id`、`event_type`、`tenant_id`、`trace_id`、`occurred_at`、`aggregate_type`、`aggregate_id`、`aggregate_version`、`payload`。 +必需:`schema_version`、`event_id`、`event_type`、`tenant_id`、`tenant_key`、`trace_id`、`occurred_at`、`aggregate_type`、`aggregate_id`、`aggregate_version`、`payload`。 - 聚合标识按 command/call/transcript_segment/recording 分域;版本由对应事实持久化事务单调递增,不依赖消息到达时间。 - command/task/call/attempt 关联按事件类型必填;尚未生成 call_id 的拒绝不伪造 call_id。 @@ -310,14 +345,14 @@ agent_version_id 必须对应呼出侧可用且可信的快照;配置如何同 | Routing Key / event_type | 必需业务数据 | 合并规则 | | --- | --- | --- | -| `command.result` | command_id、command_type、status、reason_code;适用的任务/执行/通话关联及版本;execute 增加第 4.2 节等待/准入字段 | 以 command 聚合版本更新;区分 accepted、waiting、executing、applied、completed;不把等待当成已拨号 | -| `call.status` | call_id、execution_id、任务关联、call_state、call_version、attempt_id、attempt 状态、实际线路/Cell/出口、时间/原因 | 保持 call/attempt 各自状态,不让迟到 ringing 回退 answered/ended | -| `transcript.updated` | call_id、turn_id、segment_id、role、revision、text、is_final、start_ms、end_ms、playback_state | 同 segment 较高 revision 替换;最终稿不能被迟到中间稿覆盖 | -| `call.finished` | call_id、execution_id、任务关联、call_version、outcome、起止时间、时长、原因、attempt 汇总及资产处理快照 | 固定通话终态;后处理未完成时标为 pending,不阻塞终态 | -| `recording.ready` | call_id、recording_id、oss_id、格式、声道、采样率、时长、大小及校验 | 只接收已验证资产;不包含上传凭证或公开 URL | -| `recording.failed` | call_id、recording_id、stage、reason_code、retryable、next_retry_at(若有) | 标记资产故障,不改变通话终态;后续合法 ready 可完成恢复 | -| `transcript.failed` | call_id、原因、retryable、受影响 segment(适用时) | 明确文字不完整,不能把部分文本假装最终完整记录 | -| `contact.opt_out` | call_id、task_id、task_item_id、请求时间、关联 turn/segment(若有) | SaaS 及时持久化拒绝再联系并阻止后续业务调度;不等待挂断 | +| `agent-call.command.result` / `command.result` | command_id、command_type、status、reason_code;适用的任务/执行/通话关联及版本;execute 增加第 4.2 节等待/准入字段 | 以 command 聚合版本更新;区分 accepted、waiting、executing、applied、completed;不把等待当成已拨号 | +| `agent-call.call.status` / `call.status` | call_id、execution_id、任务关联、call_state、call_version、attempt_id、attempt 状态、实际线路/Cell/出口、时间/原因 | 保持 call/attempt 各自状态,不让迟到 ringing 回退 answered/ended | +| `agent-call.transcript.updated` / `transcript.updated` | call_id、turn_id、segment_id、role、revision、text、is_final、start_ms、end_ms、playback_state | 同 segment 较高 revision 替换;最终稿不能被迟到中间稿覆盖 | +| `agent-call.call.finished` / `call.finished` | call_id、execution_id、任务关联、call_version、outcome、起止时间、时长、原因、attempt 汇总及资产处理快照 | 固定通话终态;后处理未完成时标为 pending,不阻塞终态 | +| `agent-call.recording.ready` / `recording.ready` | call_id、recording_id、oss_id、格式、声道、采样率、时长、大小及校验 | 只接收已验证资产;不包含上传凭证或公开 URL | +| `agent-call.recording.failed` / `recording.failed` | call_id、recording_id、stage、reason_code、retryable、next_retry_at(若有) | 标记资产故障,不改变通话终态;后续合法 ready 可完成恢复 | +| `agent-call.transcript.failed` / `transcript.failed` | call_id、原因、retryable、受影响 segment(适用时) | 明确文字不完整,不能把部分文本假装最终完整记录 | +| `agent-call.contact.opt_out` / `contact.opt_out` | call_id、task_id、task_item_id、请求时间、关联 turn/segment(若有) | SaaS 及时持久化拒绝再联系并阻止后续业务调度;不等待挂断 | 后两类事件补齐文字失败及拒绝再联系的可追踪闭环;opt_out 的触发判定需由业务及新 AI 规范确认,不能默认靠未经验证的关键词误判。 @@ -344,11 +379,11 @@ X-Request-ID: req-demo-01 Idempotency-Key: cmd-pause-demo Content-Type: application/json -{"command_id":"cmd-pause-demo","action":"pause","expected_task_revision":1,"task_revision":2,"reason":"operator_pause"} +{"command_id":"cmd-pause-demo","action":"pause","expected_task_revision":1,"reason":"operator_pause"} ``` ```json -{"command_id":"cmd-pause-demo","task_id":"task-demo","status":"accepted","requested_task_revision":2,"accepted_at":"2026-09-11T08:00:00.000Z"} +{"command_id":"cmd-pause-demo","tenant_id":"tenant-demo","tenant_key":"tenant-demo-key","task_id":"task-demo","status":"accepted","requested_task_revision":2,"accepted_at":"2026-09-11T08:00:00.000Z"} ``` HTTP 返回 202 后,直到收到 MQ applied 或查询到 applied,SaaS 才能显示“暂停已生效”。即使收到 applied,已有通话也可能继续。 @@ -361,6 +396,7 @@ HTTP 返回 202 后,直到收到 MQ applied 或查询到 applied,SaaS 才能 "event_id": "evt-pause-demo", "event_type": "command.result", "tenant_id": "tenant-demo", + "tenant_key": "tenant-demo-key", "trace_id": "trace-demo", "occurred_at": "2026-09-11T08:00:01.000Z", "aggregate_type": "command", @@ -398,6 +434,8 @@ SaaS → 已授权用户:按 oss_id 提供短期鉴权播放 { "command_id": "cmd-execute-demo", "command_type": "call.execute", + "tenant_id": "tenant-demo", + "tenant_key": "tenant-demo-key", "task_id": "task-demo", "execution_id": "exec-demo", "call_id": null, @@ -424,6 +462,130 @@ SaaS → 已授权用户:按 oss_id 提供短期鉴权播放 | pause 已 applied,随后 resume | resume 只允许当前生效版本的新授权指令;旧版本仍拒绝,原 command_id/execution_id 重投仍命中原结果 | | stop + drain / hangup | drain 不挂断活动电话;hangup 需额外权限并等通道结束确认,未确认不能称完全生效;停止任务不可 resume | +### 8.6 执行命令与事件样例 + +以下四个样例从交付设计移入此处统一维护,字段按第 6、7 节校验;测试时间、号码、配置和文件校验占位符须替换,不得原样发至生产。执行命令保留 task_revision,表示当前已生效版本,不是控制请求的目标版本。 + +**call.execute:** + +```json +{ + "schema_version": "1.0", + "command_type": "call.execute", + "command_id": "cmd_demo_001", + "tenant_id": "tenant_test", + "tenant_key": "tenant_test", + "trace_id": "trace_demo_001", + "issued_at": "2026-09-08T01:00:00Z", + "not_after": "2026-09-08T01:05:00Z", + "payload": { + "execution_id": "exec_demo_001", + "task_id": "task_demo", + "task_item_id": "item_demo", + "task_revision": 7, + "callee": "${AUTHORIZED_TEST_NUMBER}", + "route_policy_id": "route_policy_test", + "caller_profile_id": "caller_profile_test", + "agent_version_id": "agent_v1", + "variables": {}, + "ring_timeout_ms": 30000, + "max_call_duration_ms": 180000 + } +} +``` + +**call.status:** + +```json +{ + "schema_version": "1.0", + "event_id": "evt_demo_status_2", + "event_type": "call.status", + "tenant_id": "tenant_test", + "tenant_key": "tenant_test", + "trace_id": "trace_demo_001", + "occurred_at": "2026-09-08T01:00:06Z", + "aggregate_type": "call", + "aggregate_id": "call_demo", + "aggregate_version": 2, + "payload": { + "command_id": "cmd_demo_001", + "execution_id": "exec_demo_001", + "task_id": "task_demo", + "task_item_id": "item_demo", + "call_id": "call_demo", + "attempt_id": "attempt_demo_1", + "call_state": "answered", + "call_version": 2, + "cell_id": "cell-demo-a", + "egress_pool_id": "egress-demo-a", + "attempt_state": "answered", + "trunk_id": "sip-primary", + "answered_at": "2026-09-08T01:00:06Z" + } +} +``` + +**transcript.updated:** + +```json +{ + "schema_version": "1.0", + "event_id": "evt_demo_text_1", + "event_type": "transcript.updated", + "tenant_id": "tenant_test", + "tenant_key": "tenant_test", + "trace_id": "trace_demo_001", + "occurred_at": "2026-09-08T01:00:10Z", + "aggregate_type": "transcript_segment", + "aggregate_id": "segment_demo_1", + "aggregate_version": 2, + "payload": { + "call_id": "call_demo", + "turn_id": "turn_1", + "segment_id": "segment_demo_1", + "role": "customer", + "revision": 2, + "text": "这是授权测试。", + "is_final": true, + "start_ms": 500, + "end_ms": 1800, + "playback_state": "not_applicable" + } +} +``` + +**recording.ready:** + +```json +{ + "schema_version": "1.0", + "event_id": "evt_demo_recording_1", + "event_type": "recording.ready", + "tenant_id": "tenant_test", + "tenant_key": "tenant_test", + "trace_id": "trace_demo_001", + "occurred_at": "2026-09-08T01:03:10Z", + "aggregate_type": "recording", + "aggregate_id": "recording_demo_1", + "aggregate_version": 1, + "payload": { + "call_id": "call_demo", + "recording_id": "recording_demo_1", + "oss_id": "oss_demo_1", + "format": "wav", + "channels": 1, + "sample_rate_hz": 8000, + "duration_ms": 15000, + "size_bytes": 240044, + "checksum_sha256": "${SHA256_OF_UPLOADED_FILE}", + "created_at": "2026-09-08T01:03:01Z" + } +} +``` + +录音校验值和尺寸仅为演示,实际算法与独立校验能力须按第 4.6 节冻结;AI 文字的播放标记只表示可验证的播放器事实,不证明客户实际听到。 + ## 9. 错误码与责任 | HTTP / MQ | code 建议 | 调用方处理 | @@ -458,33 +620,46 @@ HTTP 错误码与 MQ reason_code 共用词汇但不是一一映射;MQ 没有 H - 容量目标仍是至少 1000 路同时已接通的完整 AI 通话,不是 HTTP QPS。MQ/OSS 负载应按每秒文字事件、每通话状态事件和录音产生速率单独估算、压测。 - 监控 HTTP 延迟/429/5xx、命令准入时延、控制屏障耗时、outbox 最老年龄、MQ redelivery/DLQ、SaaS 消费落库延迟、OSS 校验/上传失败、暂存磁盘和资源租约耗尽。 - 按租户统计队列深度/字节、最老命令年龄、可调度等待时间、实际新增拨号份额、并发/CPS 占用、未 ACK/待发起窗口及背压次数;分别标注配额不足、无线路资源和纯调度等待。压测 broker 队列/副本数量和全局水位,不能只看全局平均延迟掩盖 B 饥饿。 -- broker confirm 与 SaaS 应用成功分开观测;本版不新增应用收讫回调接口,端到端验收结合 SaaS inbox/入库证据。若需要运行时逐事件应用确认,另评审 MQ receipt 契约。 +- broker confirm 与 SaaS 应用成功分开观测;应用确认与清理建议见第 10.1 节,不推断 unknown 为成功或失败。 -## 11. G0 冻结清单 +### 10.1 投递责任、应用证据与清理(已确认方案) -| 决策 | 本文建议/待补信息 | 确认方 | +- 首期不增加 MQ receipt 或 HTTP 收讫接口。呼出侧自动负责 outbox→broker 的确认、不可路由及有限重试;SaaS 负责消费事务、inbox 去重、失败重试/DLQ 告警和应用对账。呼出侧不能自动识别具体哪些已确认发布事件仍未被应用,saas_applied 默认 unknown。 +- SaaS 根据自身 inbox/业务状态与命令/通话查询发现缺口,受控触发第 4.4 节 MQ 补传;查询只是诊断,不替代业务事件。published 不变成 applied;replay completed 也不等于应用成功。unknown 本身不触发无限重复发布。 +- 事件和录音清理按 G0 确认的保留/交接策略执行:事件有可靠 broker 确认、仍可覆盖约定恢复窗口;录音需 OSS verified、ready outbox 持久化并确认发布、对象保留期覆盖恢复。普通清理还须无已知隔离/恢复任务并达到批准期限,不能仅按 HTTP 200 或“估计 SaaS 已应用”删除。未确认/失败数据到水位先背压告警;超期删除需明确授权及审计,不以 unknown 永久无限保存。 +- 端到端验收必须取得 SaaS inbox 和资产关联证据。若后续业务变更要求自动逐事件应用确认,再单独评审 MQ receipt 的幂等/重试/保留契约及工时;首期不暗加 HTTP 业务回调或收讫机制。 + +## 11. 契约落地与外部参数核验清单 + +方案已接受;下表不再阻塞 Mock 开发。D01按最终计划生成可验证规范,运行数值使用其第5节测试基线;涉及真实 SaaS/供应商/预算/保留与恢复的内容仍需外部落实,并在真实替换前验证。 + +| 核验项 | 已接受规则/待核验参数 | 责任方 | | --- | --- | --- | | 接口归属及域名 | 两份 OpenAPI;SaaS 是否已有可复用资产服务、测试 Base URL | 用户、SaaS | | 服务身份 | 现有服务令牌体系优先;issuer/audience/scope、租户映射、轮换和 mTLS | 双方、运维 | | 任务绑定与版本 | 初始版本 1、CAS 控制、停止不可恢复、可信任务归属来源 | 用户、SaaS | +| 租户绑定与传输(已确认) | 一对一/不原地变更/保留期不复用,224字节预算及超限停发;Mock注册可先开发,真实租户资料和传输能力接入时核验 | SaaS、运维 | +| 授权撤销(已确认方案) | 第 4.1.1 节:复用全部受影响任务屏障、SaaS 发布侧串行授权检查、枚举依据、在途收敛、生效时限及整任务暂停代价 | 用户、SaaS | +| 无 call_id 补传(已确认) | 第 4.4 节第七条 HTTP 路径、原命令范围、权限与固定截止点;不增加拨号入口 | 用户、双方 | +| 应用证据与清理(已确认方案) | 第 10.1 节:首期只自动确认 broker,SaaS 对账补传;保留/交接依据,若必须自动应用确认则另选 MQ receipt | 用户、双方、运维 | | 执行幂等 | 新增 execution_id,与 command_id 分离;业务重新外呼许可及去重保留 | 用户、SaaS | | 停止语义 | drain/hangup 显式选择;挂断权限与多 Cell 生效判据 | 用户、SaaS | | 线路/AI 配置 | route/caller/agent 引用及同步来源;LLM/TTS 新规范、失败兜底 | 用户、供应方 | -| MQ 环境 | 租户独立命令队列已确认;冻结 tenant_key 映射、精确绑定、生命周期、队列数上限、quorum/HA、ACL、重试/DLQ 与死信可靠性 | 运维、双方 | +| MQ 环境 | 租户独立命令队列已确认;采用 `agent-call` 命名空间、direct 命令 exchange、`agent-call.tenant.{tenant_key}.call.execute` 路由及 `tenant_key` 原样透传;冻结精确绑定、生命周期、队列数上限、quorum/HA、ACL、重试/DLQ 与死信可靠性 | 运维、双方 | | 租户公平与背压 | 公平调度架构已确认;冻结轮转批量/周期、活跃队列发现、权重、prefetch、接收窗口、并发/CPS、发布速率/积压上限、拒绝发布策略、多实例协调及等待指标 | 用户、双方、运维 | | 保底与借用 | 默认不承诺固定开始时限;如需 SLA,确认保底资源、借用/归还边界及可满足的租户总承诺 | 用户、业务/运维 | | OSS ID 和校验 | 资产 ID 权威来源、校验算法、禁止覆盖/对象版本、文件大小和格式 | 用户、SaaS/存储 | | 保留与恢复 | 幂等/屏障/事件/inbox/临时录音保留,最长中断和人工恢复范围 | 双方、业务/运维 | -| 限制与 SLO | 256 KiB MQ、64 KiB HTTP 为候选;首次准入窗口、到期处理延迟、not_after 边界、等待原因优先级、超时/重试/退避、CPS、事件时延和上传授权有效期需填实值;准入截止不是开始 SLA | 双方、运维 | -| 文字/拒绝再联系 | 最终稿修订、分段、播放证据、opt_out 触发和生效时限 | 用户、SaaS | +| 限制与 SLO | 使用最终计划第5节DEV/SCALE-MOCK初始基线;生产CPS、租户额度、保留与恢复等实际数值在真实替换前登记,准入截止不是开始 SLA | 双方、运维 | +| 文字/拒绝再联系 | 最终稿首期不修订;分段与播放证据、明确opt_out来源及生效时延按最终计划验证,真实判定规则外部核验 | SaaS、供应方 | | 版本演进 | HTTP 主版本 `/v1`、MQ schema_version `1.x`;兼容矩阵和旧版退役窗口 | 双方 | 兼容建议:响应/事件允许增加可选字段;新增必填、字段语义变化或不兼容枚举按破坏性变更处理。对未知命令版本 fail closed;未知事件主版本隔离告警,不直接 ACK 丢弃。事件 payload 按 schema_version 校验,不能一边 strict 拒绝扩展一边声称任意可选字段都兼容。 ## 12. 实施与验收顺序 -1. **契约评审**:用户逐项确认第 11 节,发布冻结字段/状态/限值、双方负责人和变更记录。 -2. **机器可读规范**:编写双方 OpenAPI 3.1、MQ AsyncAPI/JSON Schema、完整请求/响应及正反例;验证引用、样例、权限和错误响应。 +1. **契约落地**:方案已接受,按最终计划D01生成字段/状态/权限规范及Mock profile;第11节真实资料并行协调,不重复等待已接受方案拍板。 +2. **机器可读规范**:编写双方 OpenAPI 3.1、共用 MQ JSON Schema,复用本文拓扑表和集中样例;验证引用、权限及正反例,不要求首期交付 AsyncAPI。 3. **契约测试/Mock**:先验证 HTTP 控制与查询、MQ 去重和 OSS 存储握手;Mock 通过不表示真实 SIP/AI 可用。 4. **真实单通话联调**:授权测试号码,打通 execute → 状态/终态 → OSS → ready → SaaS 页面;分别记录 ASR、LLM/TTS、SIP、MQ、OSS 证据。 5. **恢复和容量**:重复、乱序、跨租户、重启、多 Cell 屏障、上传失败、断网与补传;新增大租户洪峰下小租户公平、背压及多调度实例配额验收,完成真实完整 AI 的容量/延迟测试后再发布生产结论。 @@ -495,8 +670,8 @@ HTTP 错误码与 MQ reason_code 共用词汇但不是一一映射;MQ 没有 H | 双层幂等 | 重复 command_id、换 command_id 但相同 execution_id、ACK 丢失/进程重启均不重复拨号 | | 不确定发起 | ARI 超时后先对账,未确认原通道结束不再发起 | | 等待与准入截止 | call_id 尚无时按 command_id 查询 waiting;等待事件/查询版本一致;窗口与 not_after 取较早值,重投/重启不延长;租户/线路/系统瓶颈及纯调度超时原因可区分;拒绝后不再拨号,已发起通话不因首次准入截止被挂断 | -| 控制屏障 | 多 Cell 在途竞争、节点失联、旧版本迟到和恢复后都不能越过暂停/停止;202 不等于 applied | -| 租户隔离 | 跨租户查询、控制、录音授权/确认/关联和补传均失败;伪造 tenant_key、正文租户或错误绑定不能越权执行 | +| 控制屏障 | 仅提交 expected 版本,服务端 CAS 递增;重复命令不再递增,冲突不推进版本,执行仍用已生效 task_revision;多 Cell 竞争/失联/旧版本不能越过屏障,202 不等于 applied | +| 租户隔离 | 跨租户查询、控制、录音授权/确认/关联和补传均失败;命令路由、队列绑定与正文 `tenant_key` 不一致时隔离,不能越权执行或回调错误租户 | | 公平调度 | A 大量积压后 B/C 新入队,在 B/C 有额度且资源可用时,无需等 A 排空即可获调度;测量到达→入轮转→发起的分段延迟和实际份额,达到 G0 冻结指标;有界预取、已 ACK 积压及重启恢复不破坏公平 | | 多实例配额 | 多调度器/多 Cell 同时争抢、FALLBACK、所有权切换/失联重启均不重复分配租户额度;待对账活动通话不因租约过期被误释放,不超并发/CPS | | 背压与队列生命周期 | A 队列满/发布洪峰时明确拒绝且 SaaS 持久保留,confirm 丢失原 ID 重试不双拨、不丢旧消息;B 在约定 broker 负载范围仍可发布调度;租户创建/停用/恢复不丢积压、不误删队列 | @@ -504,7 +679,10 @@ HTTP 错误码与 MQ reason_code 共用词汇但不是一一映射;MQ 没有 H | 乱序终态 | 迟到 ringing 不回退 ended;call.finished 不覆盖更新的 ready;旧文字不覆盖最终稿 | | OSS 完整性 | 缺对象、错误大小/摘要、授权过期、完成后覆盖、complete 超时重试均不产生无效或重复 ready | | MQ 可靠性 | 不可路由、confirm 丢失、SaaS 事务失败、重试队列/DLQ 故障均可追踪恢复,ACK 不早于持久化 | -| 补传边界 | 固定截止点、原 event_id/内容、无拨号、保留期过期明确报错;不得以 HTTP 结果替代 MQ | +| 补传边界 | 单通话/命令整体补传,固定截止点、原事件、分批限速;无 call_id 的结果可恢复;非法筛选字段及跨租户资源拒绝,保留期过期报错;本次 replay 结果不入范围,不拨号,completed 不代表 SaaS 应用 | +| 撤销闭环 | broker 积压、waiting、发起前、FALLBACK 的号码被禁用/授权撤销;覆盖关联多任务、发布竞争、CAS 冲突、失联及恢复;全部屏障 applied 后不再新发起,之前不得显示全局生效 | +| 租户绑定与传输 | 无 execute 的合法控制仍能原样回传 key;正文/绑定/身份错配拒绝;旧 key 不被别的租户复用;224/225 UTF-8 字节及特殊字符正反例;不通过多 key 绕过额度或幂等 | +| 应用证据 | broker confirm 后暂停 SaaS 消费,saas_applied 仍 unknown;无无限重投;SaaS 恢复后凭 inbox 核对并按需补传;清理不伪称已入库,未确认失败数据不可静默删除 | | 完整交付 | SaaS inbox/业务落库、文字时间线和 oss_id 授权播放有证据;1000 路完整 AI 另有真实压测报告 | -**本版完成定义:** 完整规划草案可供评审;不意味着详细规范已冻结、服务已实现、云资源已部署或生产验收已通过。 +**本版完成定义:** 已接受的文本契约可用于D01机器可读规范与Mock实现;不代表OpenAPI/Schema已生成、服务已实现或生产通过。最终开发/部署/监控/验收及数值范围以[最终计划](最终开发部署监控与验收计划_v1.0.md)为准。 diff --git a/docs/一期呼出应用开发计划_v1.0.md b/docs/一期呼出应用开发计划_v1.0.md index 11d8579..6d20976 100644 --- a/docs/一期呼出应用开发计划_v1.0.md +++ b/docs/一期呼出应用开发计划_v1.0.md @@ -1,11 +1,13 @@ # 一期呼出应用开发计划 -**版本:** v1.3(沿用原文件路径) -**文档状态:** 3.1 交互方案已确认:所有业务结果经 MQ 回传,录音先上传 OSS 后回传 OSS ID;接口与消息规范由本人制定。生产网络架构已确认采用多机器+多 EIP 直连,不纳入单 EIP+NAT 方案;新增确认租户独立命令队列+公平调度。具体配额、等待指标、环境和新增工作量经 G0 评审后执行,运行代码待实现。 +> **实施入口已更新:** 用户已接受方案,最终开发顺序、Mock策略、测试数值、部署/监控与验收门禁见[最终计划](最终开发部署监控与验收计划_v1.0.md)。本文保留历史WBS/AT和估算;下文“待G0”不再表示已接受方案需重新拍板,外部资料和生产验证仍须落实。Mock可以先行交付,不代替生产验收。 + +**版本:** v1.6(历史阶段计划,沿用原文件路径) +**文档状态:** 3.1 交互方案已确认:所有业务结果经 MQ 回传,录音先上传 OSS 后回传 OSS ID;接口与消息规范由本人制定。生产网络架构已确认采用多机器+多 EIP 直连,不纳入单 EIP+NAT 方案;新增确认租户独立命令队列+公平调度。`agent-call` 资源命名、routing key 前缀及 SaaS `tenant_key` 原样透传规则已统一;具体配额、等待指标、环境和新增工作量经 G0 评审后执行,运行代码待实现。 **适用范围:** 本人负责的一期外呼应用,以及与现有 SaaS、RabbitMQ、指定 SIP 系统的对接。 **编制依据:** 用户最新确认的工作范围。既有 Excel 仅作背景参考,本文件独立交付,不修改 Excel,也不沿用其三期工时汇总。 -**本次修订:** 对齐交互规划 v0.3 的 execution_id、拨号前等待/查询、准入截止与 MQ 背压语义;明确单线路可启动、无备用不虚构。六类 HTTP 路径不变,详细字段、状态和数值仍待 G0 冻结,本次不改运行代码。 +**本次修订:** 对齐交互规划 v0.6,按用户确认移除重复契约表、控制请求冗余版本、补传筛选能力,延后 AsyncAPI;主契约集中维护字段/路径/状态/样例。七条 HTTP 路径保留(含按命令补传候选),v0.5 新增建议仍待 G0;不改变安全与生产验收要求,不改运行代码。 ## 1. 目标与范围 @@ -29,6 +31,8 @@ 呼出应用可以保存必要的执行、去重、控制屏障和投递记录,但不建立第二套 SaaS 主数据体系。 +**实施口径:** 生产目标包括多调度实例协调;单调度实例仅是早期联调步骤,不能据此免除 D03/AT-14。中央调度器分配许可、维护共享额度与控制状态,Cell 本地执行器独占本节点 ARI 应用并复核许可;中央与本地不得同时控制同一通道。旧交付文档中的单实例说明仅限预验证,不覆盖 G4 生产验收。 + ### 1.3 明确不做 - 计费、余额、支付、套餐、发票;保留基础用量指标,不实施扣费。 @@ -49,13 +53,14 @@ ### 1.5 多租户队列与公平调度(已确认,运行代码待实现) -- 采用**按租户 ID 映射到独立 RabbitMQ 命令队列+呼出应用调度器公平调度**。A 的大量积压不应使 B 必须等待 A 排空后才能进入执行调度;不再采用所有租户共用一个执行 FIFO。 -- SaaS 依据可信租户归属发布;平台负责队列创建、精确路由绑定和停用/清理,不接受调用方任意指定其他租户队列。任务创建、业务重试仍由 SaaS 决定;执行公平与资源许可由呼出应用负责。 +- 采用**按 SaaS 提供的 `tenant_key` 绑定到独立 RabbitMQ 命令队列+呼出应用调度器公平调度**。A 的大量积压不应使 B 必须等待 A 排空后才能进入执行调度;不再采用所有租户共用一个执行 FIFO。 +- SaaS 依据可信租户归属发布;平台负责队列创建、精确路由绑定和停用/清理,不接受调用方任意指定其他租户队列。`tenant_key` 是 SaaS 业务数据,命令、队列路由和后续 MQ 回调均原样使用,呼出应用不清洗、编码、截断或施加业务格式限制,只校验正文与队列/路由绑定值精确一致。任务创建、业务重试仍由 SaaS 决定;执行公平与资源许可由呼出应用负责。 - 活跃且可调度租户间默认建议等权轮询;有明确差异化需求后按平台权重调度。每轮有限取数、按租户及全局限制预取和已持久化待发起窗口;额度耗尽或线路不可用时跳过该租户,不阻塞其他租户。不能消费到无界内存 FIFO 后再宣称公平。 - 租户并发/CPS 额度跨所有 Cell 和调度实例汇总;并发包括发起预留、拨号、振铃、接通和待对账占用,CPS 包括 FALLBACK。拨号前同时满足租户、供应商、Cell/出口和 AI 配额;多实例采用原子额度及调度所有权/租约协调,失效实例不能重复分配。 - 每租户限制发布速率、队列消息数/字节及待执行窗口,辅以全局 broker 水位保护。满队列明确拒绝发布,不丢弃队头旧命令;SaaS 保留发布记录,confirm 不确定或失败时使用原执行标识有限重试。重试/死信恢复回到原租户调度域,不能绕过配额。 - 有资源时保障公平分配新许可,不为公平强制挂断已接通电话;资源已满时需等释放。若要求固定开始时限,必须另外确认线路/AI/Cell 全链路保底或受限借用策略,不把轮询等同于 SLA 保证。 -- 队列数量、轮转参数、配额与时延数值在 G0 冻结;详情见 [SaaS 交互规划第 5 节](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)。本轮不改变事件回传队列为租户独立队列,不增加 HTTP 拨号入口。 +- 资源统一使用 `agent-call` 命名空间:命令 exchange 为 `agent-call.commands.v1`(direct),命令 routing key 为 `agent-call.tenant.{tenant_key}.call.execute`,租户队列为 `agent-call.executor.{tenant_key}.v1`;事件 routing key 为 `agent-call.{event_type}`。队列数量、轮转参数、配额与时延数值在 G0 冻结;详情见 [SaaS 交互规划第 5 节](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)。本轮不改变事件回传队列为租户独立队列,不增加 HTTP 拨号入口。 +- 新增建议(待 G0):可信 tenant_id/key 一对一绑定,份额、配额及幂等按 tenant_id,历史 key 不改写;HTTP 控制先于 execute 时也从注册关系取得 key。原样透传保留,但 routing key 的 255 字节硬限制扣除31字节前后缀后只余224个 UTF-8 字节;超限建议明确停发并保留 SaaS 原始任务,不清洗/截断、不无限重试。若要求更长原值,另评审传输标识分离,详见契约第 3.1.1、5.1.1 节。 ## 2. 分工与双方交付物 @@ -77,12 +82,12 @@ BgA/BgB 为建议配合分工,G0 由 SaaS 团队认领。不把部署负责人 - 不增加第二条能够独立拨号的 HTTP 入口。OpenAPI 仅承担任务控制、查询、OSS 上传授权及完成确认;同步接口应答不替代业务结果事件,不保留 HTTP 业务回调链路。 - 受理/拒绝、执行状态、文字、最终结果和录音就绪等事件统一走 MQ。录音文件不进入 MQ,上传失败不得发送录音就绪事件,失败状态仍经 MQ 回传。SaaS 根据 OSS ID 关联录音并提供租户授权播放。 -- 由本人制定并发布 Exchange/Queue、Routing Key、消息/事件字段、API 路径、鉴权、错误码、版本、幂等/重试以及 OSS ID 命名和取值规范;SaaS 与运维按该规范对接、验证和落地。本文具体接口/字段仍为草案,本次确认不代表详细规范已经发布。 +- 由本人制定并发布 Exchange/Queue、Routing Key、消息/事件字段、API 路径、鉴权、错误码、版本、幂等/重试以及 OSS ID 命名和取值规范;本版先统一 `agent-call` 资源命名、routing key 前缀、direct 命令 exchange 和 `tenant_key` 原样透传语义,SaaS 与运维按该规范对接、验证和落地。本文其它接口/字段仍为草案,本次确认不代表详细规范已经全部发布。 ### 3.2 一次外呼的交互步骤 1. SaaS 创建任务及号码明细,固定智能体配置版本;执行租户授权、允许时段、基础频控及拒绝再联系检查。 -2. SaaS 业务调度器为一次授权外呼生成 `execution_id`,为执行命令生成 `command_id`,按可信租户 ID 映射路由向该租户独立队列发布 `call.execute`,携带任务项、有效期及执行所需快照;受发布限流/队列背压时持久保留,两个 ID 均保持不变重试。 +2. SaaS 业务调度器为一次授权外呼生成 `execution_id`,为执行命令生成 `command_id`,按 SaaS 提供的 `tenant_key` 原样路由向该租户独立队列发布 `call.execute`,携带任务项、有效期及执行所需快照;受发布限流/队列背压时持久保留,两个 ID 均保持不变重试。 3. 呼出应用在活跃租户间公平轮转、有界取数,校验队列绑定与正文租户一致、授权归属、有效期和任务控制版本;持久化命令及去重结果后 ACK。此时仅表示“已接收”,不是“已拨号”。 4. 呼出应用通过 MQ 回传受理/拒绝应答。尚未取出的命令受租户队列容量和有效期约束;已受理但未准入时以 waiting 状态、等待原因和截止时间表达,通过 command_id 查询。准入截止取 not_after 与 accepted_at+服务端窗口的较早值,重投/重启不延长;超限区分过期、租户/线路/系统瓶颈或纯调度超时,经 MQ 终结,由 SaaS 对账后决定是否重新授权,不无限堆积。 5. 呼出应用公平分配执行机会,原子取得跨 Cell 租户并发/CPS 额度及供应商/Cell/出口/AI 完整资源许可;首次准入成功并提交拨号意图时分配 `call_id`、`attempt_id`,此前 call_id 为 null。拨号前再次检查暂停/停止屏障与有效期;提交意图不等于已发出 SIP 或接通,状态不明先对账。 @@ -92,7 +97,7 @@ BgA/BgB 为建议配合分工,G0 由 SaaS 团队认领。不把部署负责人 9. 文字按中间稿/最终稿通过 MQ 回传 SaaS。回传或 SaaS 页面故障不得阻塞实时音频链路。 10. 挂断后关闭媒体会话并清理通道,写入独立的通话终态并通过 MQ 回传;文字最终稿、录音上传等后处理继续异步执行。 11. 获取 OSS 上传授权,将录音上传 OSS,完成校验并取得 OSS ID 后,通过 MQ 发布含 OSS ID 的录音就绪事件;SaaS 消费后关联录音并展示。上传失败通过 MQ 回传失败状态,不发送无效的录音就绪事件。 -12. 对未确认发布或未被 SaaS 应用的事件进行有限重试、MQ 补传和对账。SaaS 可查询通话及资产处理状态,查询不代替所有业务结果必须经 MQ 回传的要求。 +12. 呼出侧对未确认发布事件有限重试;建议首期由 SaaS 根据自身 inbox/业务状态对账并触发缺失事件 MQ 补传,呼出侧不自动判定已确认发布但未应用的事件。无应用层证据时 saas_applied=unknown,不等于成功或失败,不触发无限重投;保留与清理依批准的交接/恢复窗口策略。若需要自动逐事件应用确认,另评审 MQ receipt。查询不替代业务 MQ 结果。 ## 4. G0:开发前确认清单 @@ -102,9 +107,12 @@ BgA/BgB 为建议配合分工,G0 由 SaaS 团队认领。不把部署负责人 | --- | --- | --- | | 工作边界 | 本人实际可投入时间;现有呼出应用、媒体组件和 SDK 可复用程度;SaaS 调度由谁实现 | 本人、SaaS 团队 | | OpenAPI | 本人制定控制、查询、OSS 上传授权/完成确认的路径、字段、鉴权、错误码、超时、幂等和版本规则;业务结果走 MQ 已确认,不再作为待选项 | 本人制定;SaaS 后端对接 | -| RabbitMQ | 租户独立命令队列已确认;制定 tenant_id 路由映射、队列/绑定生命周期、ACL、消息/队列容量、拒绝发布背压、Confirm/ACK、按租户重试/DLQ 恢复;验证队列数量与 broker 全局资源上限 | 本人制定;SaaS/运维落地 | +| RabbitMQ | 租户独立命令队列已确认;采用 `agent-call` 命名空间、direct 命令 exchange、`agent-call.tenant.{tenant_key}.call.execute` 路由和 `agent-call.executor.{tenant_key}.v1` 队列,`tenant_key` 原样回传;冻结队列/绑定生命周期、ACL、消息/队列容量、拒绝发布背压、Confirm/ACK、按租户重试/DLQ 恢复;验证队列数量与 broker 全局资源上限 | 本人制定;SaaS/运维落地 | | 公平调度 | 冻结轮转/权重、取数批量、活跃租户发现、未 ACK/待发起窗口、跨 Cell 租户并发/CPS、发布速率、多实例额度/所有权协调;明确等待指标及是否需要保底/借用 | 本人、SaaS、部署方 | | 任务控制 | 暂停/恢复/停止语义;停止是否挂断已接通电话;业务重试与主备切换的边界 | SaaS、本人 | +| 授权撤销(新增建议) | SaaS 禁发与受影响任务枚举、复用 pause/stop 屏障、在途/FALLBACK 收敛、生效时限及整任务暂停代价;禁止仅停新发布而放行旧命令 | SaaS、本人 | +| 租户原值(新增建议) | 一对一注册/不原地变更、历史原值、先控制后执行、224字节传输预算与超限处理是否满足实际业务;不擅自编码/截断 | SaaS、本人、运维 | +| 恢复及应用证据(新增建议) | 第七条按命令补传候选路径;首期只自动确认 broker,由 SaaS 对账;清理证据及保留期限;需要自动应用确认时另选 MQ receipt | SaaS、本人、存储方 | | 指定 SIP 供应商线路 | 各 trunk 的测试账户、接入、注册或 IP 鉴权、主叫限制、拨号格式、并发/CPS、失败码、出口白名单和线路选择政策 | SIP/线路方 | | Asterisk 与媒体 | Asterisk 版本、ARI 能力、多 Cell/EIP 网络、SIP/RTP 防火墙、编解码及采样率;测试电话双向可听 | 部署方、本人 | | AI 接入 | 可用流式 ASR/LLM/TTS 接口、凭证、音频格式、取消能力、并发配额和超时限制 | AI 供应方、本人 | @@ -117,47 +125,15 @@ BgA/BgB 为建议配合分工,G0 由 SaaS 团队认领。不把部署负责人 ### 5.1 OpenAPI 清单草案 -| 方向 | 接口草案 | 职责与应答 | -| --- | --- | --- | -| SaaS→呼出应用 | `POST /internal/v1/outbound/tasks/{task_id}/controls` | 提交暂停/恢复/停止;含 `command_id`、`task_revision`、动作。返回受理结果,执行生效另有确认 | -| SaaS→呼出应用 | `GET /internal/v1/outbound/commands/{command_id}` | 查询受理、等待/原因/准入截止、执行中、已生效、拒绝或失败;call_id 尚无时也可对账;重复请求不改变状态 | -| SaaS→呼出应用 | `GET /internal/v1/outbound/calls/{call_id}` | 查询通话终态、尝试记录、文字/录音处理状态和回传进度 | -| 呼出应用→SaaS | `POST /internal/v1/outbound/recording-uploads` | 获取绑定租户/通话、格式/大小约束和有效期的 OSS 上传授权;按本人制定的存储协议返回上传地址/会话 | -| 呼出应用→SaaS | `POST /internal/v1/outbound/recording-uploads/{upload_id}/complete` | 确认 OSS 上传完成及校验结果,返回或确认 OSS ID;随后通过 MQ 发布录音就绪事件,此接口不承担业务结果回调、不传 Base64 | -| SaaS→呼出应用 | `POST /internal/v1/outbound/calls/{call_id}/replays` | 授权触发结果/文字/录音元数据经 MQ 补传;只重传数据,绝不重新拨号 | - -不提供 HTTP 业务事件接收接口;事件按类型定义 MQ Schema。接口路径及 OSS ID 字段的最终规范由本人制定并发布,SaaS 按规范对接。现有上传/资产能力可复用,但不改变“先上传 OSS,再通过 MQ 回传 OSS ID”的顺序。 +七条路径及请求/响应统一维护在 [主契约第 4 节](SaaS交互_OpenAPI与MQ契约规划_v0.1.md),本计划不再复制接口表。HTTP 不创建/重拨电话,也不作为业务结果回调。 ### 5.2 RabbitMQ 执行指令 -按租户 ID 映射独立队列,命令类型仍为 `call.execute`。具体 Exchange、带租户路由键及队列命名草案见 [SaaS 交互规划](SaaS交互_OpenAPI与MQ契约规划_v0.1.md);路由与正文 tenant_id 必须一致,重投不能改投其他租户以绕过限流。 - -`call.execute` 最小字段: - -- 通用:`schema_version`、`command_id`、`tenant_id`、`trace_id`、`issued_at`、`not_after`。 -- 业务:`execution_id`、`task_id`、`task_item_id`、`task_revision`、原始被叫 `callee`、已授权的 `caller_profile_id` / `route_policy_id`;不预拼供应商前缀,不让调用方任意指定 SIP 地址、凭据或 Cell/出口。 -- 配置:`agent_version_id`、变量及不可变配置快照,或双方确认的快照读取引用。 -- 约束:最大通话时长、振铃超时、允许的 FALLBACK 策略引用。 - -执行指令不要求尚未生成的 `call_id/attempt_id`。同一命令的网络重试保持 command_id/execution_id 不变;同时以 `(tenant_id, command_id)` 和 `(tenant_id, execution_id)` 去重,换 command_id 也不能让同一次业务执行再次拨号。内部合法 FALLBACK 仅新建 attempt_id,保持 execution_id/call_id;业务重新外呼须先确认原执行事实并取得新授权,再生成新的 execution_id/command_id,受 SaaS 重试政策限制。不得把凭证直接塞进可广泛访问的消息或日志。 - -准入字段、状态/原因、时限和查询样例以 [SaaS 交互规划第 4.2、6.2、8.4 节](SaaS交互_OpenAPI与MQ契约规划_v0.1.md) 为详细草案:未消费持久化的命令允许查不到,404 不能当成未发布/未拨号证据;等待查询及 MQ 快照使用同一命令版本域。首次准入截止只限制首次发起;not_after 限制每次新尝试(含 FALLBACK),均不作为已发起通话的强制挂断时间。 +拓扑、call.execute 字段、双层幂等及准入语义统一引用 [主契约第 5、6 节](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)。执行仍带已生效 task_revision;此次只移除控制请求里可由服务端推导的目标版本。新外呼必须由 SaaS 重新授权,内部 FALLBACK 不改变 execution_id/call_id;不能由404或超时推断可重拨。 ### 5.3 MQ 回传事件 -所有业务结果事件统一经 MQ 回传,规范由本人制定。录音回传使用 OSS ID,字段示例为 `oss_id`,最终命名及取值由本人规范确定;不默认将其等同于文件名、Object Key、ETag 或播放 URL。 - -通用外壳:`event_id`、`event_type`、`schema_version`、`tenant_id`、`trace_id`、`occurred_at`、`payload`;按事件类型要求 `command_id/task_id/task_item_id/call_id/attempt_id`,不统一强制全填。 - -| 事件类型 | 核心数据 | SaaS 的处理 | -| --- | --- | --- | -| `command.result` | 原命令、execution_id、受理/等待/执行/生效/拒绝/失败、原因、控制版本;execute 的 wait_reason_code、waiting_since、admission_deadline 和可空 call_id | 分清受理、资源等待、提交拨号意图和实际通话状态;按命令聚合版本更新 | -| `call.status` | 通话与尝试、状态版本、实际 trunk_id/egress_pool_id/cell_id、时间、标准原因和原始 SIP/Asterisk 原因 | 幂等更新,不被迟到事件回退终态;实际选路也进入通话查询 | -| `transcript.updated` | `turn_id`、角色、文本版本/序号、文本、中间/最终标记、起止时间、播放/取消标记 | 更新同一轮文字,不把中间稿累加成重复句子 | -| `recording.ready` | `call_id`、`oss_id`(OSS ID)、格式、声道、采样率、时长、大小、校验;`recording_id`可作业务关联标识 | 仅在 OSS 上传成功并确认 OSS ID 后发布;SaaS 根据 OSS ID 关联录音并提供租户授权播放 | -| `call.finished` | 通话终态、开始/接通/结束时间、时长、原因、FALLBACK 使用情况、各资产处理状态 | 电话结束即可落业务终态;后处理允许随后补齐 | - -时间戳采用带时区的统一格式,时长单位由本人在 Schema 中明确。文本序号和状态版本分别定义作用域,不依赖 MQ 全局顺序;重试和补传保留原 `event_id`,避免重复应用。 +事件集合、字段、状态和版本合并规则只在 [主契约第 7 节及第 8 节样例](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)维护;全部业务结果走 MQ,录音先验证 OSS 再回传 OSS ID。D01 复用同一 Schema 验证集中样例,不维护第二套事件表。 ### 5.4 可靠性与控制语义 @@ -175,19 +151,19 @@ BgA/BgB 为建议配合分工,G0 由 SaaS 团队认领。不把部署负责人 ## 6. 开发任务步骤与工时 -下表保留**原本人负责范围的基准工时**,包含设计、编码、自测和正常联调,不包含 SaaS 团队实现工时。已将租户队列、公平调度和专项测试并入对应工作包,但其增量工时尚未重估;原合计不能视为已覆盖新增范围的交付承诺,G0 完成增量评估后更新总工时和排期。每项先有可验证产出,再进入下一依赖阶段。 +下表保留**原本人负责范围的基准工时**,包含设计、编码、自测和正常联调,不包含 SaaS 团队实现工时。已将租户队列、公平调度和专项测试并入对应工作包;本版的撤销屏障、租户绑定/传输检查、命令补传及应用对账/清理建议亦须计入增量评估,尚未重估;原合计不能视为已覆盖新增范围的交付承诺,G0 完成增量评估后更新总工时和排期。每项先有可验证产出,再进入下一依赖阶段。 | WBS | 任务包 | 开发步骤及交付物 | 本人工时 | | --- | --- | --- | --- | | D01 | 对接契约与技术验证 | 盘点已有组件→确认双方职责→冻结 OpenAPI/消息/状态→完成样例与 Mock→验证一通测试电话和 AI 流式能力 | 24–32h | | D02 | 环境与应用骨架 | 打通 SaaS/MQ/ARI/AI/存储及多 Cell/EIP 网络→接入服务鉴权与密钥→建立最小执行/投递存储→健康检查与日志 | 16–24h | -| D03 | 指令、公平调度、应答与控制 | 租户队列有界接收/轮转→路由/租户/时效校验→持久去重→跨 Cell 租户额度与多调度实例协调→Cell/trunk/EIP/AI 资源准入→受理应答→暂停/停止屏障→重启恢复测试 | 原 32–48h,增量待评估 | +| D03 | 指令、公平调度、应答与控制 | 租户队列有界轮转→可信绑定/传输/时效校验→持久去重→跨 Cell 额度与多实例协调→完整资源准入→受理应答→暂停/停止及撤销关联任务屏障→重启恢复 | 原 32–48h,增量待评估 | | D04 | Asterisk 主线路呼叫 | SIP参数与号码格式→ARI发起→通道/桥/媒体关联→振铃/接通/挂断→时间和原因归一→资源清理 | 32–48h | | D05 | FALLBACK 与状态对账 | 确认可切换失败码→旧通道结束确认→备用尝试→迟到接通/ARI断线对账→次数/期限限制→防双拨演练 | 32–48h | | D06 | 实时 AI 呼出应用 | 对接现有媒体能力→编解码/采样率适配→ASR/LLM/TTS流式联动→打断与取消旧生成→静音/超时/退出→异步隔离 | 48–72h | | D07 | 文字生成与回传 | 定义轮次/版本→中间稿与最终稿→角色及播放/取消标记→持久事件→断网补传→SaaS时间线核对 | 16–24h | | D08 | 录音上传与回传 | 录音生成→封装/元数据→OSS上传授权→上传/校验→确认OSS ID→经MQ回传OSS ID及元数据→重试/临时文件清理 | 24–32h | -| D09 | 查询、投递与补偿 | 命令/通话查询→受理与终态一致性→回传重试/隔离→授权重放→资产处理独立状态→恢复对账 | 32–40h | +| D09 | 查询、投递与补偿 | 命令/通话查询→受理与终态一致性→重试/隔离→按通话及候选命令范围重放→资产独立状态→SaaS inbox 对账与交接/清理证据;不暗加应用收讫接口 | 原 32–40h,增量待评估 | | D10 | 可观测与运行保障 | 按租户等待/配额/积压指标→租户队列生命周期及 broker 上限→拒绝发布/背压→临时存储保护→密钥脱敏→部署、重启和优雅停机 | 原 24–32h,增量待评估 | | D11 | 端到端专项验收 | SaaS真实联调→多 Cell/EIP 与供应商线路故障注入→重复/乱序/过期→多租户安全/公平/背压→多调度实例配额恢复→1000 路完整 AI 容量/延迟→回传与录音恢复 | 原 36–48h,增量待评估 | | D12 | 灰度、交接与发布 | 固定版本→小流量试运行→问题收敛→回滚演练→交付配置/操作/排障说明与验收记录 | 12–16h | @@ -201,13 +177,13 @@ BgA/BgB 为建议配合分工,G0 由 SaaS 团队认领。不把部署负责人 **录音:** 文件先上传 OSS,确认成功并取得 OSS ID 后,才通过 RabbitMQ 回传 OSS ID 及必要元数据;不发送二进制/Base64,也不以公开 URL 替代 OSS ID。SaaS 管理长期存储与播放权限,呼出侧仅按确认的恢复期限暂存。SaaS 不可用时有磁盘水位、积压告警和停止接单策略,不允许无限缓存。访问录音必须按租户和用户授权,签名地址短期有效且不写入公开日志。 -**业务安全:** SaaS 在调度入口实施授权、允许时段、基础频控及拒绝再联系拦截;呼出应用实施租户/任务关联、有效期、资源上限和停机屏障。拒绝再联系事件及时回传,不能等二期才避免重复骚扰。 +**业务安全:** SaaS 在调度入口实施授权、允许时段、基础频控及拒绝再联系拦截;呼出应用实施租户/任务关联、有效期、资源上限和停机屏障。新增撤销建议见交互契约第 4.1.1 节:SaaS 持久禁发并枚举全部受影响任务,用现有 pause/stop 建立屏障,覆盖 broker 积压、waiting、尚未发起意图及 FALLBACK;所有相关屏障 applied 后才确认执行侧撤销生效,失联/冲突保持待对账。只在允许时段内发布,not_after 不超过时段边界;提前撤销仍须屏障。opt_out 经 MQ 到 SaaS 后触发该流程,不宣称消息尚未到达时已全局生效;整任务暂停代价、时限及关联依据待 G0,不静默恢复旧命令。 ## 7. 里程碑与排期 | 门禁 | 本人产出 | SaaS/外部配合 | 通过条件 | | --- | --- | --- | --- | -| G0:对接确认 | 本人制定并发布 D01 契约、样例、技术验证和参数基线 | SaaS/运维按规范认领接口及MQ、SIP、AI、OSS测试权限 | 已确认的双向MQ和OSS ID回传方案落实为可验证契约;关键测试依赖可用 | +| G0:对接确认 | 本人制定并发布 D01 契约、样例、技术验证和参数基线 | SaaS/运维按规范认领接口及 MQ、SIP、AI、OSS 测试权限 | `agent-call` 命名、direct 命令 exchange、tenant_key 原样回调及双向 MQ/OSS ID 回传方案落实为可验证契约;关键测试依赖可用 | | G1:指令闭环 | D02–D03;D09查询/应答基础 | SaaS能按租户发布、处理背压、接收应答和调用控制接口 | 重复指令不重复执行,过期/停止指令被拒绝,暂停生效可查询;大租户积压不阻塞有额度/资源的小租户,多实例不超租户配额 | | G2:电话闭环 | D04–D05 | 当前主线路与授权测试号码;有备用时提供独立接入及白名单 | 单线路可双向通话;无备用明确失败不虚构容灾;有适合备用时验证合法切换,迟到接通不导致双拨。备用尚缺时记录其真实验证为待验,不把单线路通过称为完整 FALLBACK 验收 | | G3:AI与资产闭环 | D06–D08;D09补偿完善 | SaaS MQ结果消费、OSS上传授权/确认及FeA展示页面 | 多轮/打断可用;文字经MQ正确入库;OSS ID可关联录音并鉴权播放;断网可经MQ补回 | @@ -235,7 +211,7 @@ SaaS 可在 G0 后按 Mock 并行开发;本人为单人时,上述模块不 | 编号 | 场景 | 验收条件 | | --- | --- | --- | | AT-01 | 命令重复、重启和ACK丢失 | 同一授权执行不因消息重复或更换 command_id 生成第二次拨号;已接收指令重启后可查询、可恢复;call_id 尚无时通过 command_id 观察 waiting,窗口不因重投/重启延长,截止后拒绝且不拨号,原因与 MQ 事件一致 | -| AT-02 | 暂停/停止与积压竞争 | 生效确认后不再新发起;迟到旧指令和死信重放不能重新开启已停止任务 | +| AT-02 | 暂停/停止与积压竞争 | 生效确认后不再新发起;迟到旧指令和死信重放不能重新开启已停止任务;撤销/opt_out 覆盖受影响多任务、发布竞态、broker 积压、waiting 和 FALLBACK,全部屏障 applied 前不显示全局生效,恢复后不复活被撤销对象 | | AT-03 | 主线路基本通话 | 发起、振铃、接通、双向语音、挂断及原因正确;通道/桥/媒体最终清理 | | AT-04 | 单线路及合法FALLBACK | 无备用时可启动、遇故障明确失败;有适合备用时,仅主尝试明确结束、未接通且属于允许故障才切换,保持同一 Cell/出口,记录完整 attempt 和线路信息;无独立备用时真实切换验收标待验 | | AT-05 | 不应FALLBACK | 已接通、状态不明、忙线/拒接/无效号码、本地AI故障不会引发未经授权的备用重拨 | @@ -243,8 +219,8 @@ SaaS 可在 G0 后按 Mock 并行开发;本人为单人时,上述模块不 | AT-07 | 多轮对话与打断 | 识别、生成、播音连续可用;打断停止旧播音/生成;延迟达到G0确认指标 | | AT-08 | 文字重复、乱序和最终稿 | 同轮不重复堆叠;最终稿覆盖中间稿;取消内容标记正确;SaaS最终内容可核对 | | AT-09 | 录音与OSS上传失败 | 先确认OSS上传成功,再经MQ回传OSS ID;SaaS可按OSS ID鉴权播放;失败不发ready并经MQ回传失败状态;临时文件受控清理 | -| AT-10 | SaaS/MQ中断与回传重放 | 所有业务结果经MQ回传,无HTTP回调旁路;音频不被回传阻塞;恢复后幂等补齐且无重复资产;积压达到上限有告警和保护 | -| AT-11 | 租户与凭证安全 | 非授权租户无法查询/补传/播放;凭证不出现在日志;失效上传授权被拒绝 | +| AT-10 | SaaS/MQ中断与回传重放 | 所有业务结果经MQ回传,无HTTP回调旁路;音频不被回传阻塞;恢复后幂等补齐且无重复资产;无 call_id 的拒绝/控制也能按原命令补传;broker 已确认而 SaaS 未消费时仍为 unknown,replay completed 不伪称应用成功;按交接/保留策略清理,积压有告警保护 | +| AT-11 | 租户与凭证安全 | 非授权租户无法查询/补传/播放;凭证不出现在日志;失效上传授权被拒绝;tenant_id/key 绑定及历史回传一致,先控制后 execute 可解析原值;224/225 UTF-8 字节和特殊字符正反例、跨 key 幂等/配额不绕过 | | AT-12 | 多 Cell/EIP 容量、时段、退出与回滚 | 至少1000路已接通完整ASR/LLM/TTS;不超供应商并发/CPS、Cell媒体端口和最长通话;单 Cell/出口故障后仍满足N+1承接能力;发布回滚不丢待投递事件、不重新拨历史通话 | | AT-13 | 大租户积压与公平调度 | A 大量积压后 B/C 新入队;B/C 有额度和可用资源时无需等 A 排空,按约定轮次/时延获得执行机会;记录到达、加入轮转和真实发起时间。预取、已 ACK 待发起及重启恢复仍公平;A 无可用线路时不阻塞 B | | AT-14 | 跨 Cell/多调度实例租户配额 | 竞争发起、FALLBACK、所有权切换和进程重启均不重复占额/拨号、不超租户总并发/CPS;状态不明占用不因租约过期直接释放;资源满时不强制挂断,若有保底 SLA 则独立验收 | @@ -275,4 +251,4 @@ G4 前必须将“约定指标”填写为明确数值,包括目标并发/CPS 一期以“指令有应答、真实外呼可控、单线路可启动且合法切换不重复拨号、AI可对话、文字录音能回SaaS并恢复”为交付闭环。独立备用未提供时记录真实 FALLBACK 验证为待验,不宣称已具备线路容灾。 -新增计费、RAG、复杂路由、第二种回传通道、多供应商通用适配或集群容灾时,另建变更项,写明工作量与对本期门禁的影响,不默认混入本计划。SaaS API、SIP参数、媒体组件或本人可投入时间变化时,更新本开发计划的版本并重新确认受影响的排期。 +新增计费、RAG、复杂路由、第二种回传通道、多供应商通用适配或超出本期多实例协调/N+1 基线的容灾能力时,另建变更项,写明工作量与对本期门禁的影响,不默认混入本计划。SaaS API、SIP参数、媒体组件或本人可投入时间变化时,更新本开发计划的版本并重新确认受影响的排期。 diff --git a/docs/交互流程图_规划态.md b/docs/交互流程图_规划态.md index 46b0b9d..3ac453a 100644 --- a/docs/交互流程图_规划态.md +++ b/docs/交互流程图_规划态.md @@ -1,20 +1,20 @@ # 交互流程图(规划态) -**版本:** v0.1 -**依据:** [一期计划 v1.3](一期呼出应用开发计划_v1.0.md)、[OpenAPI 与 MQ 契约规划 v0.3](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)。 -**状态:** 目标交互,尚未完整实现;具体字段、配额、等待窗口及 AI 协议待 G0 冻结。当前仅有 ASR 验证基础,LLM/TTS 未启用。本图不新增接口或变更契约。 +**版本:** v0.2 +**依据:** [最终开发部署监控与验收计划](最终开发部署监控与验收计划_v1.0.md)、[主契约 v1.0](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)。 +**状态:** 方案已接受,目标链路尚未完整实现。外部依赖先按协议Mock,真实LLM/TTS未启用;测试基线与真实验证门禁分开,不以Mock代替生产验收。 ```mermaid flowchart TB subgraph SaaS["SaaS:业务授权与发布"] START["任务、号码、租户授权与业务检查"] - PUB["持久化发布记录
command_id + execution_id"] + PUB["持久化发布记录
command_id + execution_id
建议:可信注册、传输承载检查,禁发标记阻止新授权"] BACK["满队列拒绝/不可路由/confirm 不确定
保留原 ID,有限退避与对账"] START --> PUB end subgraph COMMANDS["RabbitMQ:唯一外呼执行入口"] - EX["call.execute
按可信 tenant_id 映射路由"] + EX["call.execute
agent-call.tenant.{tenant_key}.call.execute
按 SaaS tenant_key 原样路由"] Q["各租户独立命令队列
有界积压,不丢队头旧命令"] EX --> Q end @@ -60,7 +60,7 @@ flowchart TB FALL{"明确未接通且旧通道已结束
允许故障、有同 Cell/出口的授权备用?"} LEASE{"FALLBACK:重新检查控制、期限
租户 CPS 与完整资源许可"} AI["接通后的目标链路
媒体 ↔ ASR → LLM → TTS → 媒体
流式对话、打断与取消;LLM/TTS 待新规范"] - END["确认通道结束,清理资源
持久化 call.finished;资产可仍为 pending"] + END["确认通道结束,清理资源
持久化 agent-call.call.finished;资产可仍为 pending"] DIAL --> OBS OBS -- "状态不明" --> RECON RECON -- "取得可靠证据后收敛" --> OBS @@ -82,7 +82,7 @@ flowchart TB OSS["呼出资产处理器 → OSS
使用受控签名上传文件"] COMPLETE["呼出资产处理器 → SaaS HTTP complete
SaaS 实际校验 OSS 对象"] VERIFIED{"校验成功并取得稳定 oss_id?"} - READY["verified + recording.ready outbox
同事务持久化,事件含 oss_id"] + READY["verified + agent-call.recording.ready outbox
同事务持久化,事件含 oss_id"] FAILED["recording.failed + outbox
按契约有限恢复;未验证不发 ready"] FILE --> AUTH --> OSS --> COMPLETE --> VERIFIED VERIFIED -- "是" --> READY @@ -92,24 +92,29 @@ flowchart TB subgraph RESULTS["所有业务结果:持久化事件 → MQ → SaaS"] OUTBOX["执行事实与 outbox 同事务
投递器:confirm + mandatory,有限重试"] - EVENTS["RabbitMQ 事件 Exchange → SaaS 事件队列
本轮不按租户拆分事件队列"] - INBOX["SaaS:tenant_id + event_id 去重
业务更新与 inbox 同事务后 ACK
按版本合并,关联 oss_id 并授权展示"] + EVENTS["agent-call.events.v1 → agent-call.saas.events.v1
routing key:agent-call.{event_type}
本轮不按租户拆分事件队列"] + INBOX["SaaS:tenant_id + event_id 去重
tenant_key 原样保存,不加入幂等键
业务更新与 inbox 同事务后 ACK"] OUTBOX --> EVENTS --> INBOX end REJECT --> OUTBOX - ACCEPT -. "command.result" .-> OUTBOX + ACCEPT -. "agent-call.command.result" .-> OUTBOX WAIT -. "状态/原因变化" .-> OUTBOX INTENT -. "executing 不等于实际拨号" .-> OUTBOX - OBS -. "call.status" .-> OUTBOX + OBS -. "agent-call.call.status" .-> OUTBOX RECON -. "待对账状态" .-> OUTBOX - AI -. "transcript.updated 等文字事件" .-> OUTBOX + AI -. "agent-call.transcript.updated 等文字事件" .-> OUTBOX END --> OUTBOX READY --> OUTBOX FAILED --> OUTBOX CONTROL["SaaS → 呼出 HTTP:暂停/恢复/停止
202 仅受理;跨 Cell 屏障确认后才 applied"] QUERY["SaaS → 呼出 HTTP:命令/通话查询
仅对账,不替代 MQ 业务结果"] - REPLAY["SaaS → 呼出 HTTP:历史事件补传
固定范围、原 event_id;不拨号/不调用 AI"] + REPLAY["SaaS → 呼出 HTTP:历史事件补传
call 范围/source_command_id 范围
单资源整体补传,无筛选;固定截止、原事件
completed 只代表 broker 确认"] + REVOKE["已确认撤销:SaaS 持久禁发
枚举受影响任务并逐一建立 pause/stop 屏障
全部 applied 后才确认执行侧生效"] + REVOKE -. "禁止新发布" .-> PUB + REVOKE -. "已有积压/在途/FALLBACK" .-> CONTROL + INBOX -. "opt_out 到达后触发撤销" .-> REVOKE + INBOX -. "SaaS 对账发现缺口,受控补传" .-> REPLAY CONTROL -. "禁止新许可,处理在途许可" .-> ADMIT CONTROL -. "真实发起前校验" .-> FENCE CONTROL -. "生效结果经 MQ" .-> OUTBOX @@ -128,7 +133,10 @@ flowchart TB ## 阅读边界 -- 完整资源许可同时覆盖跨 Cell 的租户并发/CPS、供应商配额、Cell 媒体端口/节点容量、出口健康与 ASR/LLM/TTS 配额;每次 FALLBACK 也消耗 CPS。初次准入截止为 `min(not_after, accepted_at + 服务端窗口)`,重投/重启不延长;它不强制挂断已发起电话。 +- 完整资源许可同时覆盖跨 Cell 的租户并发/CPS、供应商配额、Cell 媒体端口/节点容量、出口健康与 ASR/LLM/TTS 配额;每次 FALLBACK 也消耗 CPS。初次准入截止为 `min(not_after, accepted_at + 服务端窗口)`,重投/重启不延长;它不强制挂断已发起电话。命令 exchange 为 direct,`tenant_key` 不做本地格式限制。 - `accepted`、broker confirm、消费者 ACK、`executing`、实际拨号、接通及 SaaS 入库是不同事实。可信业务拒绝可回传;非法消息先隔离。重复命令关联既有事实,不创建第二通电话。 - 暂停/停止覆盖队列积压、持久等待和多 Cell 在途许可;状态不明不宣称 applied。resume 不复活旧版本。公平不挂断已接通电话;stop 的 drain/hangup 按明确授权处理。 -- 数据补传只重发原事件;录音恢复独立处理。文字/录音失败不改写通话终态。新业务外呼由 SaaS 完成对账及授权后决定,不由消费者自动换 ID。 +- 数据补传只重发原事件;按命令补传仅选择本租户该源命令的 command.result,无 call_id 也可恢复;不选择本次补传结果。录音恢复独立处理,文字/录音失败不改写通话终态。 +- 已接受方案:key 原样保存但检查完整路由的255字节承载上限(原值预算224个 UTF-8 字节),超限在 SaaS 停发并保留原任务;配额/公平/幂等按 tenant_id。HTTP 控制早于 execute 时从可信注册取 key。 +- SaaS 撤销在其持久禁发与全部受影响任务屏障间存在待生效窗口;失联/冲突不报告完成,已发出通话不默认挂断,恢复不复活撤销对象。业务新执行仍由 SaaS 对账后重新授权。 +- 首期只自动确认 broker;SaaS 依 inbox 对账,呼出侧 saas_applied 无证据为 unknown,不无限重投。清理按主契约第 10.1 节可靠交接/批准保留期,不猜测 SaaS 已应用。 diff --git a/docs/交付文档/一期中间调度件与Asterisk_v1.0/01_中间调度件与MQ回传设计.md b/docs/交付文档/一期中间调度件与Asterisk_v1.0/01_中间调度件与MQ回传设计.md index 66c6fe2..f08fcd7 100644 --- a/docs/交付文档/一期中间调度件与Asterisk_v1.0/01_中间调度件与MQ回传设计.md +++ b/docs/交付文档/一期中间调度件与Asterisk_v1.0/01_中间调度件与MQ回传设计.md @@ -1,7 +1,7 @@ # 第一部分:中间调度件与 MQ 回传设计 -**版本:** v1.0;双向MQ交互方式已确认,详细契约及真实环境参数待 G0 对接评审。 -**用户确认:** RabbitMQ 下发执行指令、RabbitMQ 回传业务事件;HTTP 仅用于控制、查询、录音上传授权及完成确认。 +**版本:** v1.4;对齐[最终计划](../../最终开发部署监控与验收计划_v1.0.md)与主契约v1.0。方案已接受,本文保留实现步骤;字段/路径/状态/样例统一引用主契约,外部依赖先Mock,不表示运行代码已实现。 +**用户确认:** RabbitMQ 下发执行指令、RabbitMQ 回传业务事件;HTTP 仅用于控制、查询、历史事件补传、录音上传授权及完成确认。 **范围:** 一期纯外呼、不计费、复用现有 SaaS。本文替代旧计划中的“HTTP 业务回调”草案,不修改原 Excel。 **交付性质:** 本次交付设计和操作/验收文档,不代表中间件代码已实现,也不代表已部署到任何服务器。 @@ -9,15 +9,16 @@ ```text SaaS:任务/号码/配置/业务调度/权限/展示/长期存储 - │ 发布 call.execute ▲ 接收 command.result / call.* / transcript.* / recording.ready + │ 发布 call.execute ▲ 接收 agent-call.command.result / agent-call.call.* / agent-call.transcript.* / agent-call.recording.ready ▼ │ -RabbitMQ:command exchange event exchange → SaaS消费队列 +RabbitMQ:agent-call.commands.v1 agent-call.events.v1 → agent-call.saas.events.v1 │ ▲ ▼ │ 中间调度件:指令持久化 → 准入/控制 → 呼叫执行 → 数据落地/outbox → 事件投递 - │ REST + 每节点一个长期ARI WebSocket │ HTTP上传授权/上传完成/接收结果查询 - ├── Asterisk A ── 主/备用 SIP接入 └── SaaS API及授权对象存储 - └── Asterisk B ── 主/备用 SIP接入 + │ 许可/屏障 → 各 Cell 本地执行器 │ HTTP上传授权/上传完成(无应用收讫接口) + ├── Cell A:本地执行器 → ARI/Asterisk └── SaaS API及授权对象存储 + └── Cell B:本地执行器 → ARI/Asterisk + 每 Cell 固定 EIP;单线路可启动,备用按授权选用 │ externalMedia RTP(不经过RabbitMQ) └── 媒体/AI组件:VAD → ASR → LLM → TTS,支持取消与打断 ``` @@ -25,7 +26,7 @@ RabbitMQ:command exchange event exchange → SaaS消费队列 - SaaS 决定何时、向谁发起,以及业务层“再次拨打”的政策;中间件不复制客户管理和任务编排。 - 中间件负责执行级准入、并发/CPS、暂停屏障、Asterisk 节点选择、SIP 尝试、执行事实、文字/录音回传及补偿。 - Asterisk 节点无共享通道状态;双节点不是存量通话无损接管。故障节点上的已接通通话不得自动在另一节点重拨。 -- 一期采用一个活动中间件实例管理多个 Asterisk 节点,持久化后支持重启恢复。一个节点的 ARI 应用只允许一个控制者;不承诺活动中间件自动高可用。未来多实例必须按节点明确归属,不能竞争控制同一通道。 +- 单调度实例只用于早期联调,不是生产完成定义。生产按一期计划 D03/AT-14 验收多调度实例的原子配额、所有权和控制屏障。中央调度器管理共享准入/控制状态,各 Cell 本地执行器独占本节点 ARI 应用并在真实发起前复核;不能由中央和本地同时控制同一通道。调度多实例不等于活动电话自动接管。 - 每个节点维持长期 ARI 事件连接;REST 请求和事件统一进入执行状态机。不可照搬 Mock 的“每通电话各建相同 app 的连接并丢弃事件”方式。 - 不新增 Redis、工作流引擎或独立服务拆分作为前置依赖。复用可用关系数据库存放执行与投递状态;媒体组件可以独立运行,但数据回传不能进入其音频实时线程。 @@ -34,29 +35,19 @@ RabbitMQ:command exchange event exchange → SaaS消费队列 | 数据 | 所有者与关键字段 | 约束 | | --- | --- | --- | | 任务、号码、智能体快照 | SaaS;task_id、task_item_id、agent_version_id、variables、task_revision | SaaS 保持业务事实;快照不可在执行中悄悄替换 | -| command_inbox | 中间件;tenant_id、command_id、execution_id、request_hash、payload、accepted_at、status | 同租户 command_id 唯一;同键不同内容拒绝,不覆盖原命令 | -| task_control | 中间件;tenant_id、task_id、revision、desired_state、effective_state | 控制版本单调递增;停止屏障不能被迟到的旧指令解除 | -| call_execution | 中间件;tenant_id、call_id、execution_id、task_item_id、状态版本、时间、终态、原因 | 同租户execution_id唯一代表一次授权执行;同租户同一任务项不得同时存在两次活动业务执行 | +| command_inbox | 中间件;tenant_id、tenant_key、command_id、execution_id、request_hash、payload、accepted_at、status | 同租户 command_id 唯一;同键不同内容拒绝,不覆盖原命令;tenant_key 原样保留 | +| task_control | 中间件;tenant_id、tenant_key、task_id、revision、desired_state、effective_state | 控制版本单调递增;tenant_key 原样保留;停止屏障不能被迟到的旧指令解除 | +| call_execution | 中间件;tenant_id、tenant_key、call_id、execution_id、task_item_id、状态版本、时间、终态、原因 | 同租户 execution_id 唯一代表一次授权执行;tenant_key 原样保留;同租户同一任务项不得同时存在两次活动业务执行 | | call_attempt | 中间件;attempt_id、call_id、node_id、trunk_id、channel_id、bridge_id、external_channel_id、发起意图和清理状态 | 调用 ARI 前落地关联标识;同一 execution 只允许一个活动尝试 | -| call_artifact | 中间件;call_id、文字轮次/版本、录音位置/校验/状态、保留期限 | 电话结束与资产处理分离;本地暂存不替代 SaaS 长期存储 | -| event_outbox | 中间件;event_id、payload、聚合版本、发布次数、下次重试、published/applied状态 | 业务事实与事件同事务提交;broker confirm 不等于 SaaS 已落库 | -| event_inbox | SaaS;tenant_id、event_id、payload_hash、applied_at、处理结果 | 去重与业务落库同事务;业务提交后才 ACK;优先复用 SaaS 现有机制 | +| call_artifact | 中间件;tenant_id、tenant_key、call_id、文字轮次/版本、录音位置/校验/状态、保留期限 | 电话结束与资产处理分离;tenant_key 原样保留;本地暂存不替代 SaaS 长期存储 | +| event_outbox | 中间件;tenant_id、tenant_key、event_id、payload、聚合版本、发布次数、下次重试、pending/broker_confirmed/failed | 业务事实与事件同事务提交;tenant_key 原样保留;saas_applied 无证据时 unknown,不推断入库 | +| event_inbox | SaaS;tenant_id、tenant_key、event_id、payload_hash、applied_at、处理结果 | 去重与业务落库同事务;业务提交后才 ACK;tenant_key 原样保留,优先复用 SaaS 现有机制 | -一期单实例可以在同一进程内实现接收、执行、投递、对账和清理任务,但各自失败不能阻塞媒体。状态更新仍需事务及版本条件,不能仅靠内存字典防重复。 +早期单实例联调可将接收、投递、对账与清理共进程,但不能把此阶段视为生产多实例验收。逻辑组件不必全拆微服务,状态更新仍需共享事务/版本及所有权协调,Cell 执行器独占 ARI;各自故障不得阻塞媒体。 ## 3. RabbitMQ 拓扑与可靠性 -以下名称为规范草案,部署时按 SaaS 命名规则映射;业务 Schema 和语义保持一致。 - -| 对象 | 建议命名/绑定 | 用途 | -| --- | --- | --- | -| 命令交换机 | ai.outbound.command.v1,topic、durable | SaaS 发布执行命令 | -| 命令队列 | ai.outbound.execute.v1,绑定 call.execute | 中间件消费;队列满时拒绝发布/暂停生产,不静默丢弃 | -| 事件交换机 | ai.outbound.event.v1,topic、durable | 中间件回传业务事件 | -| SaaS事件队列 | saas.ai.outbound.event.v1,绑定本节规定的事件类型 | SaaS 幂等入库及触发展示 | -| 死信交换机/队列 | 复用 SaaS 标准 DLX,命令与事件分别隔离 | 超出重试预算、非法消息和不可恢复异常 | - -必须落实: +Exchange/Queue/Routing Key、原值绑定及传输上限只在 [主契约第 3、5 节](../../SaaS交互_OpenAPI与MQ契约规划_v0.1.md)维护。实现时落实: 1. 两侧生产者均启用 persistent 消息、Publisher Confirm 和 mandatory/不可路由检查;收到 confirm 但发生 basic.return 仍视为投递失败。 2. durable 队列不等于集群高可用。若现有 RabbitMQ 支持经验证的 quorum 队列可复用;不得在未知版本/策略下直接强制变更现有队列类型。 @@ -65,148 +56,28 @@ RabbitMQ:command exchange event exchange → SaaS消费队列 5. 基础 prefetch 从小值开始,配合数据库待执行数量上限。执行容量不足时降低/暂停拉取或给出明确拒绝结果,不能无界吸收任务。 6. 执行准入同时受租户、节点、线路的并发/CPS和任务控制状态限制。号码/DNC/时段等业务风控由 SaaS 统一决策,延迟执行和补投前重新校验有效性。 7. 基础设施暂时失败走有限退避;业务忙线、拒接不是 MQ 异常,不通过 nack/requeue 反复重拨。已有 SaaS 重试/DLQ 机制优先复用,不同时叠加两套无限重试。 -8. 关键最终事件不能靠短 TTL 自动淘汰。published 事件保留至 SaaS 应用确认或经授权的保留期处理;删除策略和最长可恢复中断在 G0 明确。 +8. 应用证据和清理按主契约第 10.1 节执行:关键事件不靠短 TTL 淘汰,不将 broker confirm 当作 SaaS 应用;unknown 不引发无限重投。 9. 人工重放分为“命令恢复”和“数据补传”。命令恢复仍检查 execution_id、有效期、已接通事实和停止屏障;数据补传永不发起电话。 ## 4. 命令、事件与应答契约 -### 4.1 标识与公共规则 +统一引用 [主契约第 3、6、7、9 节](../../SaaS交互_OpenAPI与MQ契约规划_v0.1.md)的标识、执行命令、事件及错误语义;独有 JSON 样例已移入该文第 8.6 节,不在此复制。 -- command_id:一次指令;消息重投必须相同。execution_id:一次授权业务外呼;SaaS 决定再次外呼时才新建。 -- call_id:一次业务外呼的聚合标识。attempt_id:一次 Asterisk/SIP 尝试;FALLBACK 新建 attempt,但不新建业务 execution。 -- event_id:事件唯一标识;重发保持原值和内容。task_revision:任务控制版本。state_version:通话状态版本。 -- 所有时间戳为 RFC3339 UTC;字段名含 `_ms` 的持续时间以毫秒计。号码、原始错误、录音引用均视为租户敏感数据。 -- 每类消息单独定义必填字段。尚未产生通话时,不要求 command 带 call_id/attempt_id。 -- tenant_id 必须与凭证、消息来源及任务归属校验,不仅相信 payload。只接受号码/允许的线路引用,不接受任意 ARI URL、原始 dial string、任意 externalMedia 地址或凭证。 -- 下列 JSON 使用测试占位符;时间、号码和配置需由测试生成器替换,不得原样发送至生产。 - -### 4.2 call.execute - -```json -{ - "schema_version": "1.0", - "command_id": "cmd_demo_001", - "execution_id": "exec_demo_001", - "tenant_id": "tenant_test", - "trace_id": "trace_demo_001", - "issued_at": "2026-09-08T01:00:00Z", - "not_after": "2026-09-08T01:05:00Z", - "task_id": "task_demo", - "task_item_id": "item_demo", - "task_revision": 7, - "payload": { - "destination": "${AUTHORIZED_TEST_NUMBER}", - "agent_version_id": "agent_v1", - "agent_snapshot": {"prompt_version": "prompt_v1", "variables": {}}, - "line_group_id": "line_group_test", - "ring_timeout_ms": 30000, - "max_call_duration_ms": 180000, - "fallback_policy_id": "primary_backup_v1" - } -} -``` - -快照须包含执行所需数据;若采用引用,SaaS 必须提供鉴权读取接口及一致的版本校验,不默认“只有ID就能运行”。同幂等键不同 request_hash 返回 IDEMPOTENCY_CONFLICT。过期/停止/越权/无可用线路返回明确拒绝结果,MQ ACK 不应被展示为“已呼叫”。 - -### 4.3 事件模型 - -公共外壳包括 event_id、event_type、schema_version、tenant_id、trace_id、occurred_at 和 payload;业务关联ID按事件类型携带。 - -| Routing key / event_type | 必需业务字段 | SaaS 更新规则 | -| --- | --- | --- | -| command.result | command_id、execution_id或控制对象、result、reason、effective_revision | result=ACCEPTED/REJECTED/APPLIED/FAILED;ACCEPTED不等于执行完成 | -| call.status | call_id、attempt_id、state_version、state、node_id、trunk_id、时间与原因 | 版本不回退;attempt结果不直接覆盖已接通的业务终态 | -| transcript.partial | call_id、turn_id、role、revision、text、is_final=false | 可合并/限流;中间稿不是最终业务事实 | -| transcript.final | call_id、turn_id、role、revision、text、起止时间、播放/取消标记 | 同轮按版本幂等更新;必须持久和可补传 | -| recording.ready | call_id、recording_id、object_ref、格式/声道/采样率/时长/大小/checksum | 只传上传确认后的受控引用,不传二进制和长期公开URL | -| call.finished | call_id、state_version、final_state、cause、时长、attempt_count、资产处理状态 | 通话终态立即可见;录音/分析可随后完成 | - -```json -{ - "schema_version": "1.0", - "event_id": "evt_demo_status_2", - "event_type": "call.status", - "tenant_id": "tenant_test", - "trace_id": "trace_demo_001", - "occurred_at": "2026-09-08T01:00:06Z", - "command_id": "cmd_demo_001", - "execution_id": "exec_demo_001", - "task_id": "task_demo", - "task_item_id": "item_demo", - "call_id": "call_demo", - "attempt_id": "attempt_demo_1", - "payload": { - "state": "ANSWERED", - "state_version": 2, - "node_id": "ast-a", - "trunk_id": "sip-primary", - "answered_at": "2026-09-08T01:00:06Z" - } -} -``` - -```json -{ - "schema_version": "1.0", - "event_id": "evt_demo_text_1", - "event_type": "transcript.final", - "tenant_id": "tenant_test", - "trace_id": "trace_demo_001", - "occurred_at": "2026-09-08T01:00:10Z", - "call_id": "call_demo", - "payload": { - "turn_id": "turn_1", - "role": "customer", - "revision": 2, - "text": "这是授权测试。", - "is_final": true, - "start_offset_ms": 500, - "end_offset_ms": 1800, - "playback_status": "not_applicable" - } -} -``` - -AI 文字另标记 generated/sent/playback_confirmed/cancelled。播放器确认不等于能证明客户实际听到了声音;被打断而未播出的内容不能全部作为“已说出”展示。 - -### 4.4 错误与恢复语义 - -统一原因至少包含:INVALID_COMMAND、IDEMPOTENCY_CONFLICT、TENANT_FORBIDDEN、TASK_STOPPED、COMMAND_EXPIRED、CAPACITY_EXCEEDED、NO_ROUTE、SIP_UNAVAILABLE、BUSY、REJECTED、NO_ANSWER、INVALID_NUMBER、AI_ERROR、MEDIA_ERROR、CONTROL_UNCERTAIN、UPLOAD_FAILED。保留原始 SIP 状态/Q.850/Asterisk 原因用于排障,映射由真实线路验证。 - -明确拒绝的执行不进入拨号;容量等待/拒绝政策由 SaaS 与中间件统一,不能两边各自重试。CONTROL_UNCERTAIN 表示需对账,不等价于可重拨失败。 +实现侧负责按冻结来源解析可信配置快照,不把任意 URL 当作配置读取入口;保留原始 SIP 状态/Q.850/Asterisk 原因供排障,映射须由真实线路验证。播放器确认不证明客户实际听到,被取消且未播出的内容不能全部显示为“已说出”。 ## 5. HTTP 边界与接口清单 -| 方向/方法 | 接口草案 | 请求/响应要点 | -| --- | --- | --- | -| SaaS→中间件 POST | /internal/v1/tasks/{task_id}/controls | command_id、expected_revision、action=PAUSE/RESUME/STOP;force_hangup默认false且另行授权;202返回已受理 | -| SaaS→中间件 GET | /internal/v1/commands/{command_id} | 查询ACCEPTED/APPLIED/REJECTED/FAILED与实际控制版本 | -| SaaS→中间件 GET | /internal/v1/calls/{call_id} | 通话/尝试、state_version、资产和投递状态;不改状态 | -| SaaS→中间件 POST | /internal/v1/calls/{call_id}/events/replay | 仅补传已有事件,保持event_id;鉴权、审计、限流 | -| 中间件→SaaS GET | /internal/v1/outbound/executions/{execution_id}/eligibility | 返回allowed、reason、current_task_revision、valid_until;每次发起前查询当前时段/退订/授权许可,不返回凭证 | -| 中间件→SaaS POST | /internal/v1/recording-uploads | call_id、格式、大小、checksum;返回upload_id、授权上传地址、过期时间及允许参数 | -| 中间件→SaaS POST | /internal/v1/recording-uploads/{upload_id}/complete | 幂等确认文件/校验;返回recording_id和object_ref | -| 中间件→SaaS POST | /internal/v1/outbound/receipts/query | 查询最多100个event_id的APPLIED/NOT_FOUND/FAILED结果;仅用于低频对账,不是业务回调 | - -控制请求经过与命令相同的持久化/幂等机制。200/202不代表暂停已经生效;必须查询或消费 command.result(APPLIED)。非法参数400、无权限401/403、状态/版本冲突409、限流429;重复同内容请求返回原结果。 - -首次发起、延迟执行及FALLBACK之前查询当前业务许可,结合本地控制屏障再次校验;不得把命令发布时的授权永久缓存。许可查询不可用时不新拨号,进入有期限的等待/拒绝并告警;不会因此中断正在进行的媒体。授权变化与暂停生效的时间边界在G0确认。 - -鉴权沿用已批准的 SaaS 服务认证并限定租户权限;跨主机HTTPS,敏感操作审计。API路径及认证细节需对接签字后生成正式OpenAPI文件,不将示例路径当成双方已部署接口。 +接口路径、权限、请求/响应及控制/补传语义统一引用 [主契约第 4 节](../../SaaS交互_OpenAPI与MQ契约规划_v0.1.md)。实现按单通话或单命令整体补传历史事件,保持固定截止点、原事件、分批读取和限速;不实现事件筛选器或额外业务许可/收讫 API。授权撤销按主契约第 4.1.1 节的已确认方案实现,不以新 execute 替代撤销屏障。 ## 6. 状态机、控制与FALLBACK ### 6.1 执行状态 -- ACCEPTED → QUEUED → DIALING → ANSWERED → TALKING → ENDED。 -- DIALING允许直接ANSWERED;RINGING是可选观测状态,不要求每次都有振铃事件。 -- 接通前可结束为FAILED、CANCELLED、EXPIRED;已接通后以ENDED及原因结束,不退回QUEUED。 -- 控制连接断开且事实无法确定时进入RECONCILING。恢复时查询原node/channel,不能因本地没有成功回执就重拨。 -- 文字/录音状态独立使用PENDING/PROCESSING/READY/FAILED;事件状态独立使用PENDING/PUBLISHED/APPLIED。资产失败不阻止电话终态落地。 +状态与版本只在 [主契约第 4.2、6.2、7.3 节](../../SaaS交互_OpenAPI与MQ契约规划_v0.1.md)维护。实现将命令、通话、尝试、文字、录音和投递事实分开持久化,不因资产失败改写通话终态。 ### 6.2 控制屏障 -PAUSE先阻止SaaS新调度,中间件再建立版本屏障,处理完在途发起许可后确认APPLIED。STOP为终止性控制,旧revision或死信不得恢复任务。已接通通话默认排空;强制挂断需要独立授权和审计。许可检查、发起意图与控制版本关联,避免“先检查暂停再被停止但仍拨出”的竞态。 +按 [主契约第 4.1 节](../../SaaS交互_OpenAPI与MQ契约规划_v0.1.md)实现 CAS 与跨 Cell 生效确认,复用同一持久控制版本,不能只改数据库状态而忽略已下发许可。 ### 6.3 两种故障不得混淆 @@ -226,42 +97,18 @@ PAUSE先阻止SaaS新调度,中间件再建立版本屏障,处理完在途 5. 关键文字最终稿异步持久化、写outbox并发布;中间稿可限流合并,最终稿和状态事件不可被慢消费者无限阻塞。 6. 接通后可通过ARI的桥录音能力启动混音录音,名称包含受控call/attempt标识;本期默认单轨混音,双声道需另行验证,不把桥录音直接称为双声道。 7. 挂断时停止/确认录音完成,等待录音文件封装完成后读取/上传。优先通过ARI stored recording接口获取,或由同节点受控进程读取持久卷,不把同一路径误当成跨主机共享文件。 -8. SaaS上传授权→文件上传→完成确认/校验→写recording.ready到outbox→MQ发布→SaaS消费入库和页面播放。 +8. SaaS 上传授权→文件上传→完成确认/校验→写 `agent-call.recording.ready` 到 outbox→MQ 发布→SaaS 消费入库和页面播放。 9. 清理通道、桥、媒体会话;清理失败由补偿任务按记录核实处理。不得使用“清空所有通道”替代按call_id清理。 -录音元数据:recording_id、call_id、attempt_id、format、channels、sample_rate_hz、duration_ms、size_bytes、checksum_sha256、object_ref、created_at。上传确认前不发ready。临时文件设置容量、水位和保留期限;未确认入库的文件不能在普通成功清理中删除,超期处置须告警和授权。 +暂存水位、失败保护与清理按 [主契约第 4.6、10.1 节](../../SaaS交互_OpenAPI与MQ契约规划_v0.1.md)执行,不凭 HTTP 200 或猜测 SaaS 已应用删除文件;未确认数据背压告警,超期处置需授权审计。 ### 7.1 recording.ready 示例 -```json -{ - "schema_version": "1.0", - "event_id": "evt_demo_recording_1", - "event_type": "recording.ready", - "tenant_id": "tenant_test", - "trace_id": "trace_demo_001", - "occurred_at": "2026-09-08T01:03:10Z", - "call_id": "call_demo", - "attempt_id": "attempt_demo_1", - "payload": { - "recording_id": "recording_demo_1", - "object_ref": "tenant_test/call_demo/recording_demo_1.wav", - "format": "wav", - "channels": 1, - "sample_rate_hz": 8000, - "duration_ms": 15000, - "size_bytes": 240044, - "checksum_sha256": "${SHA256_OF_UPLOADED_FILE}", - "created_at": "2026-09-08T01:03:01Z" - } -} -``` - -上传授权/完成确认是HTTP;recording.ready业务通知是MQ。例中校验值和尺寸为占位/演示,必须以实际文件计算结果为准。 +见 [主契约第 8.6 节](../../SaaS交互_OpenAPI与MQ契约规划_v0.1.md);上传授权/完成确认是 HTTP,ready 业务通知仍走 MQ。 ## 8. 恢复、可观测与开发顺序 -恢复顺序:暂停新执行→读取未完成attempt→核实节点/通道→修正执行事实→恢复未投递/未应用关键事件→清理孤儿资源→健康检查通过后恢复接单。锁或租约过期不能证明Asterisk通道已结束。 +恢复顺序:暂停新执行→读取未完成attempt→核实节点/通道→修正执行事实→恢复未确认发布事件及 SaaS 对账指定的补传→清理孤儿资源→健康检查通过后恢复接单。锁或租约过期不能证明Asterisk通道已结束。 监控至少包括命令积压/最老年龄、拒绝原因、活动通话/线路CPS、ARI连接、未决attempt、FALLBACK、音频丢包与AI延迟、outbox积压、SaaS落库延迟、录音失败、临时磁盘和清理失败。call_id贯通日志;手机号、密钥、签名URL脱敏。 @@ -271,6 +118,6 @@ PAUSE先阻止SaaS新调度,中间件再建立版本屏障,处理完在途 2. G1:inbox/outbox、命令接收/应答、控制和查询;重复消息与重启恢复通过。 3. G2:长期ARI连接、主线路通话、节点选择和安全FALLBACK;迟到接通不产生双拨。 4. G3:AI、最终文字、录音及MQ回传;SaaS入库展示和补传可验证。 -5. G4:第三部分全部阻断用例通过;部署与回滚演练完成。 +5. G4:第三部分阻断用例及一期计划 AT-01~AT-15 全部完成生产验收,至少1000路完整 AI 通话、N+1 和多实例配额/屏障达到冻结指标;10路试运行只作预验收,不能签署 G4。部署与回滚演练完成。 本人负责中间件/ARI/回传核心实现;BgA对接SaaS任务快照和控制;BgB对接SaaS MQ生产消费、事件入库与资产接口;FeA负责展示和播放;部署负责人支持环境与监控。若本人兼任ArchA,合并人力日历,不重复计算。 diff --git a/docs/交付文档/一期中间调度件与Asterisk_v1.0/02_Asterisk部署与SIP对接步骤.md b/docs/交付文档/一期中间调度件与Asterisk_v1.0/02_Asterisk部署与SIP对接步骤.md index 285a86d..10db98a 100644 --- a/docs/交付文档/一期中间调度件与Asterisk_v1.0/02_Asterisk部署与SIP对接步骤.md +++ b/docs/交付文档/一期中间调度件与Asterisk_v1.0/02_Asterisk部署与SIP对接步骤.md @@ -1,5 +1,7 @@ # 第二部分:Asterisk 部署与指定 SIP 对接步骤 +> 开发/部署总入口为[最终计划](../../最终开发部署监控与验收计划_v1.0.md):方案已接受,外部条件先用协议Mock,M/R门禁分开;本文为部署参考,不把历史配置或结果当成本次完成。 + ## 1. 参考基线与使用边界 参考仓库:`git@git.ipao.vip:rogee/sip-research.git`。 @@ -26,8 +28,8 @@ ### 2.1 环境要求 - 隔离的Mock测试主机;Linux、Docker Engine、Docker Compose插件、Git、Bash、curl、Python3可用。 -- 生产建议两个独立Linux主机分别部署Asterisk A/B,同一主机只运行一个host-network Asterisk实例。 -- 可用SaaS测试租户、RabbitMQ权限、主/备SIP接入、授权测试号码、AI接口、对象存储和中间件构建产物。 +- 两台独立 Linux 主机仅为多 Cell 联调示例;生产节点数按实测安全容量、至少1000路完整 AI 及 N+1 计算,不预先承诺两台够用。同一主机只运行一个 host-network Asterisk 实例。 +- 可用 SaaS 测试租户、RabbitMQ 权限、已确认的 SIP 接入、授权测试号码、AI 接口、对象存储和中间件构建产物;单线路允许启动,无授权备用不虚构,真实 FALLBACK 留待备用具备后验收。 - 未完成中间件开发时,只能执行Mock部署及底座验证,不得标记完成MQ回传或一期上线。 ### 2.2 上线前必须填写的参数 @@ -35,10 +37,10 @@ | 参数组 | 必填内容 | | --- | --- | | 版本 | Git提交、Asterisk镜像digest/实际版本、Python测试镜像digest、中间件版本、配置版本 | -| 节点 | node_id、管理网IP、SIP监听地址/端口、RTP地址/端口段、故障域、并发/CPS限额 | -| SIP | 主/备用endpoint名称、地址/端口、注册或IP鉴权、主叫/被叫格式、编解码、源IP清单、失败码 | +| 节点 | cell_id(对应节点)、egress_pool_id/固定 EIP、管理网 IP、SIP/RTP 地址及端口段、故障域、并发/CPS限额、本地 ARI 控制所有权 | +| SIP | 已授权 endpoint 名称、地址/端口、注册或 IP 鉴权、主叫/被叫格式、编解码、出口白名单和失败码;备用仅在实际具备时填写 | | 媒体 | externalMedia可达地址、每通电话端口分配方式、RTP回程、采样率、AI流式接口与配额 | -| MQ/API | VHost/队列、账号ACL/TLS、控制/查询地址、SaaS上传和接收状态查询协议 | +| MQ/API | VHost/队列、账号 ACL/TLS、控制/查询/补传地址、可信租户注册、SaaS 上传握手与 inbox 对账流程;不要求未定义的应用收讫接口 | | 存储 | 本地录音路径/权限/配额、上传允许域名、保留期限、磁盘水位、播放授权 | | 运行 | 允许呼叫时段、紧急停止方式、告警联系人、验收指标和发布窗口 | @@ -135,7 +137,7 @@ Mock多实例、固定端口和媒体关联方式存在简化,顺序成功不 这是**本期新增模板,不是参考仓库原有文件**。在当前节点的DEPLOY_DIR保存为docker-compose.yml,并准备conf目录中列出的配置文件、批准镜像及权限后再启动;不包含Mock Provider。 ```yaml -name: ai-outbound-node +name: agent-call-node services: asterisk: image: ${ASTERISK_IMAGE:?必须提供批准的镜像digest} @@ -227,10 +229,10 @@ curl --fail --silent --show-error --netrc-file "$ARI_NETRC" \ 1. 发布第一部分约定的数据库结构/唯一约束和向后兼容迁移;备份执行/投递数据。 2. SaaS负责人创建/确认命令、事件、重试/DLQ及ACL;核对发布confirm、不可路由检测、持久化和消费ACK。 3. 配置节点表、ARI凭据引用、主备线路组、媒体地址池、租户授权、并发/CPS、有效期及磁盘阈值。 -4. 配置SaaS当前业务许可查询、上传授权/完成确认、接收状态查询接口及允许的存储域名;不配置HTTP业务回调地址。 +4. 配置可信租户/任务注册关系、SaaS 上传授权/完成确认及允许的存储域名;按主契约 v1.0 已确认方案实现授权撤销屏障和 SaaS inbox 对账流程,不配置未定义的业务许可/应用收讫查询 API 或 HTTP 业务回调。 5. 先启动SaaS事件消费者,再启动中间件的查询/恢复/投递能力;消费执行命令暂不开启。 -6. 中间件核对未决attempt、存量通道和未投递事件,建立每节点唯一ARI控制连接;健康通过后开放低并发执行。 -7. 使用授权测试租户发布一条call.execute;核对command.result、call.status、transcript.final、call.finished、recording.ready到SaaS入库和页面的完整关联。 +6. 中央调度器恢复共享租户所有权、额度及控制屏障;各 Cell 本地执行器独占本节点 ARI,核对未决 attempt、存量通道及未确认发布事件。中央不另建竞争控制者;健康通过后开放低并发执行,生产再验收多调度实例切换和配额不超额。 +7. 使用授权测试租户发布一条 `call.execute`;核对 `agent-call.command.result`、`agent-call.call.status`、`agent-call.transcript.updated`、`agent-call.call.finished`、`agent-call.recording.ready` 到 SaaS 入库和页面的完整关联。 8. 执行第三部分故障/恢复用例,全部阻断项通过后才进入灰度。客户端SDK/框架选择不改变这条契约和门禁。 ## 7. D5:录音专项对接 @@ -243,14 +245,14 @@ curl --fail --silent --show-error --netrc-file "$ARI_NETRC" \ | 2 | POST /ari/recordings/live/{recordingName}/stop | 通话结束时幂等处理;等待录音完成/文件封装,不读取仍在写的文件 | | 3 | GET /ari/recordings/stored/{recordingName}/file | 获取可播放文件;名称URL编码;跨节点按原node_id读取 | | 4 | SaaS上传授权→上传→complete | SHA-256、大小、通话归属通过;授权过期可重新申请 | -| 5 | MQ recording.ready→SaaS消费 | 可查询应用结果、租户鉴权播放;重复事件不产生重复资产 | -| 6 | 清理暂存 | SaaS已应用且达到批准保留策略后清理;未确认的失败文件告警,不静默删除 | +| 5 | MQ `agent-call.recording.ready`→SaaS 消费 | 用 SaaS inbox/业务库证据核对关联与鉴权播放;不假设存在呼出侧可用的应用收讫查询 API;重复事件不产生重复资产 | +| 6 | 清理暂存(建议,待 G0) | 按主契约第 10.1 节的 OSS verified、ready 持久 outbox/broker 确认、批准保留期及无已知恢复任务条件清理;不猜测 SaaS 已应用,不按 HTTP 200 即删,未确认失败数据告警和受控处置 | 需要双声道、全程振铃录音、严格无录音损失或特殊合规策略时,单独确认;本模板仅以接通后单轨混音作为一期基线。 ## 8. 灰度、回滚与运维交接 -- 顺序:单节点单路→两节点顺序→真实多路隔离→已确认目标负载→小流量灰度。Mock成功不能跳过真实SIP/AI阶段。 +- 顺序:单节点单路→两节点顺序→10路预验收→真实多路隔离及多调度实例→至少1000路完整 AI 与 N+1 的生产验收→小流量灰度。10路和单调度实例不满足 G4;Mock 成功不能跳过真实 SIP/AI 阶段。 - 回滚前停止新指令/建立屏障,排空或经授权处置在途电话,记录未决attempt、录音和outbox状态,再回滚中间件或节点镜像。 - 不使用`docker compose down -v`删除录音卷;不把删除数据库/队列当作恢复手段。配置和镜像回滚必须保留事件/执行事实,避免重复拨号。 - 若发现双拨、越权、录音静默丢失或终态回退,立即停止放量;按call_id对账后再恢复。 diff --git a/docs/交付文档/一期中间调度件与Asterisk_v1.0/03_联调步骤与验收标准.md b/docs/交付文档/一期中间调度件与Asterisk_v1.0/03_联调步骤与验收标准.md index 3c55952..4be5571 100644 --- a/docs/交付文档/一期中间调度件与Asterisk_v1.0/03_联调步骤与验收标准.md +++ b/docs/交付文档/一期中间调度件与Asterisk_v1.0/03_联调步骤与验收标准.md @@ -1,12 +1,12 @@ # 第三部分:联调步骤与验收标准 -**当前状态:全部运行用例待执行。** 本次只完成参考代码审查和文档静态校验,没有实际部署、拨打电话或连接生产MQ。不得把参考仓库的历史成功记录填写为本次通过。 +**当前状态:全部运行用例待执行。** 方案已接受,开发、Mock/真实门禁和测试数值以[最终计划](../../最终开发部署监控与验收计划_v1.0.md)为准,字段/状态按主契约v1.0。下文历史“待G0”标签不再阻塞Mock开发;实际生产参数仍待外部核验。没有部署、拨号或连接生产MQ,不能把静态检查/历史结果填写为本次通过。 ## 1. 验收分层与准入 | 层级 | 验证内容 | 不能替代的验证 | | --- | --- | --- | -| L0 静态检查 | 文档、样例、配置语法、镜像/提交/参数登记、接口评审 | 不证明容器能启动、线路可用或业务可靠 | +| L0 静态检查 | 文档、统一 Schema 必填/枚举/关联及正反例、配置语法、镜像/提交/参数登记、接口评审 | JSON 可解析不是 Schema 通过;静态检查不证明容器能启动、线路可用或业务可靠 | | L1 Mock底座 | 两节点运行、ARI、Mock SIP、顺序双向RTP、清理 | 不证明真实AI、录音、MQ回传、并发隔离或生产主备 | | L2 真实SIP/AI | 授权号码、指定主备接入、媒体、AI、多轮/打断及录音 | 不证明SaaS业务数据一致性和异常恢复 | | L3 SaaS闭环 | MQ下发/回传、控制应答、最终文字、录音入库/播放、补偿 | 不替代容量、故障注入和安全检查 | @@ -16,12 +16,12 @@ ## 2. 建议指标与G0确认表 -以下是**小规模一期试运行建议值,不是已测结论或合同承诺**。可根据真实供应商及需求在G0修改;正式验收单不得保留“待定”。 +以下是**小规模预验收建议值,不是生产 G4 完成标准,也不是已测结论或合同承诺**。10路/1 CPS 只用于早期安全放量;生产另按一期计划 AT-12~AT-15 验证至少1000路同时已接通完整 ASR/LLM/TTS、多 Cell/EIP、N+1、多调度实例配额及公平背压。G0 冻结生产 CPS、测量时长/分位值及故障窗口;正式验收单不得保留“待定”。 | 指标 | 建议初始目标 | 测量边界/样本 | | --- | --- | --- | | 真实并发/CPS | 目标10路、全局1 CPS;先1路再3路再10路,且不超过供应商限额 | 在确认后的目标负载下持续30分钟,覆盖两节点;SIP重传不算新增呼叫 | -| 控制/状态查询 | 查询P95≤500ms;控制APPLIED P95≤2s | 从SaaS请求到结果;控制时间包含发起屏障处理,不能只量HTTP202 | +| 控制/状态查询 | 查询 P95≤500ms;控制 `status=applied` P95≤2s | 从 SaaS 请求到结果;控制时间包含发起屏障处理,不能只量 HTTP 202 | | MQ命令受理 | P95≤2s | SaaS持久发布到中间件持久接收;等待拨号另计 | | AI首音频响应 | P95≤1500ms | 客户语音结束被VAD确认到首个有效TTS音频包发往通话桥;不少于100个有效轮次 | | 打断 | P95≤500ms | VAD确认插话到旧TTS停止向桥发送;另抽样核查客户侧体验 | @@ -64,24 +64,25 @@ | 编号/场景 | 操作步骤 | 通过标准/证据 | | --- | --- | --- | -| MQ-01 正常闭环 | 发布一条合法执行;完成电话及SaaS事件入库 | command、execution、call、attempt、event关联一致;ACCEPTED不被显示为已接通 | -| MQ-02 重复命令 | 同一command_id/execution_id重发10次;关闭FALLBACK以隔离变量 | 只产生一次业务拨号;SIP同一INVITE的协议重传不计为额外执行 | -| MQ-03 同键异内容 | 同租户同command_id改变号码/快照;另保持原凭证篡改tenant_id后发布 | 冲突/越权被拒绝,原命令不被覆盖;不产生第二次拨号;合法不同租户的命令空间不冲突 | +| MQ-01 正常闭环 | 按 `agent-call.tenant.{tenant_key}.call.execute` 发布一条合法执行;完成电话及 SaaS 事件入库 | command、execution、call、attempt、event 关联一致;`accepted` 不被显示为已接通 | +| MQ-02 重复命令 | 同一 `command_id`/`execution_id` 重发 10 次;关闭 FALLBACK 以隔离变量 | 只产生一次业务拨号;SIP 同一 INVITE 的协议重传不计为额外执行 | +| MQ-03 同键异内容 | 同租户同 `command_id` 改变号码/快照;另保持原凭证篡改 `tenant_id` 或 `tenant_key` 后发布 | 冲突/越权被拒绝,原命令不被覆盖;不产生第二次拨号;合法不同租户的命令空间不冲突 | | MQ-04 持久化/ACK竞态 | 在命令提交前、提交后ACK前、ACK后分别终止中间件并恢复 | 未提交可重投,已提交能恢复;无丢失受理事实和重复业务拨号 | -| MQ-05 发布确认竞态 | 断开网络或让交换机无有效绑定;在事件提交与confirm之间停止进程 | 事件保留并重试;不可路由不能标记APPLIED;恢复后SaaS只有一次业务应用 | +| MQ-05 发布确认竞态 | 断开网络或让交换机无有效绑定;在事件提交与 confirm 之间停止进程 | 事件保留并有限重试;不可路由不能标记 broker_confirmed,confirm 不表示 SaaS applied;恢复后 SaaS 只有一次业务应用 | | MQ-06 SaaS提交/ACK竞态 | 在event_inbox业务事务提交前后分别中断消费者 | 失败事务不ACK;重投事件幂等;最终文字/状态不重复、不缺失 | | MQ-07 数据库失败 | 中间件接收时或SaaS应用事件时使数据库暂时不可用 | 不提前ACK/返回成功;不继续发起缺少持久执行意图的电话;恢复可对账 | -| MQ-08 容量与停止 | 积压多条指令,执行PAUSE/STOP;等APPLIED后重放旧指令及过期指令 | 生效后不再新发起,旧revision/过期执行被拒绝;无无限requeue热循环 | -| MQ-09 业务有效性 | 已排队号码被加入拒绝再联系/禁止时段,或授权被撤销后再执行 | SaaS业务许可重新校验不通过则不拨号;不得因早先发布成功绕过当前策略 | +| MQ-08 容量与停止 | 积压多条指令,控制只传 expected 版本;重复同命令、制造 CAS 冲突;applied 后重放旧执行及过期指令 | 服务端递增目标版本,重复不递增、冲突不推进;执行仍用已生效 task_revision;生效后不新发起,旧/过期执行拒绝,无 requeue 热循环 | +| MQ-09 业务有效性(方案待 G0) | 在 broker、waiting、真实发起前及 FALLBACK 阶段撤销授权/新增 DNC;同一对象涉及多任务,另制造发布竞态、控制 CAS 冲突和 Cell 失联 | SaaS 持久禁发并关联全部任务,现有 pause/stop 屏障全部 applied 后不再新发起;之前显示待生效/对账而非成功;允许时段以 not_after 限界,提前收紧走屏障,恢复不复活撤销对象;分别记录 opt_out 到达、禁发、全部生效时刻 | +| MQ-10 租户原值/传输(方案待 G0) | 先控制后 execute;注册/正文/路由交叉错配;224/225 UTF-8 字节、多字节、点号、星号、井号和 ACL 正则字符;尝试同租户多 key | 可信注册能解析控制回传 key;允许值原样往返;超限停发并保留原任务、非 MQ 业务拒绝、不无限重试;不因 key 改变或增加队列绕过 tenant_id 份额/幂等;旧绑定保留期内不复用 | ### 4.3 Asterisk、SIP与FALLBACK | 编号/场景 | 操作步骤 | 通过标准/证据 | | --- | --- | --- | -| SIP-01 正常/无振铃事件 | 正常通话;让有效ANSWERED到达而无RINGING观测 | 可以直接从DIALING收敛到ANSWERED;最终时间和原因正确 | +| SIP-01 正常/无振铃事件 | 正常通话;让有效 `answered` 到达而无 `ringing` 观测 | 可以直接从 `dialing` 收敛到 `answered`;最终时间和原因正确 | | SIP-02 合法主备 | 主线路在明确未接通时发生已批准线路故障;确认旧尝试结束后切备用 | 同一call/execution下新attempt,最多达到配置总预算;不同时保留两条活动呼叫 | | SIP-03 禁止误切 | 分别返回busy、reject、invalid;另在已接通后制造AI/媒体故障 | 这些场景不触发未经授权的SIP备用重拨;原因分类准确 | -| SIP-04 迟到接通/ARI超时 | 原尝试已提交,阻断控制事件,再让接通迟到;同时触发超时处理 | 进入对账,不因“未收到接通”立即备用拨号;没有双通;完整记录时序 | +| SIP-04 迟到接通/ARI超时 | 原尝试已提交,阻断控制事件,再让接通迟到;同时触发超时处理 | 进入对账,不因“未收到接通”立即备用拨号;没有双通;完整记录时序,`call_version` 不回退 | | SIP-05 节点故障 | 分别在提交originate前、提交后事实不明、已接通后中断节点 | 前者可选健康节点;不明状态不盲重拨;已接通中断如实结束而非声称无损迁移 | | SIP-06 并发隔离 | 多通话播放不同标识音/不同测试词并分布两节点;抓取允许范围内的媒体关联 | 每call的通道、端口、文字、录音不串线;不能只比较总RTP包数 | | SIP-07 清理与重启 | 正常结束、客户挂断、振铃超时及中间件重启后检查资源 | 按call_id清理且不伤其他通话;残留有补偿和告警,达到清理时限 | @@ -95,10 +96,12 @@ | DATA-03 录音闭环 | 完成电话→录音封装→上传校验→MQ ready→SaaS播放 | checksum/大小/时长可核对,事件在上传确认后产生;页面不先显示假成功 | | DATA-04 上传故障 | 模拟上传中断、授权过期、checksum不符、完成确认丢失 | 授权/上传可重试且不产生重复资产;不提前ready;通话终态仍可展示 | | DATA-05 SaaS中断 | 暂停SaaS消费及上传5分钟再恢复;模拟接近磁盘上限 | 音频不等待回传;有积压/磁盘保护;恢复后在目标时限补齐,不以删数据通过 | -| DATA-06 数据补传 | 按call_id触发事件重放;核对原command/attempt数量 | 仅补数据,不重新拨号;SaaS幂等消费,APPLIED状态能查询 | +| DATA-06 数据补传(扩展待 G0) | 按单通话/源命令整体重放,含无 call_id 结果;尝试跨租户资源、附带已移除的筛选字段;重试原补传 ID | 资源归属/非法字段检查,原事件和截止点不变,分批限速、不拨号,不纳入本次补传结果;completed 只代表 broker 确认,不证明 SaaS 应用;SaaS 幂等消费整批历史 | +| DATA-07 应用证据/清理(方案待 G0) | broker 已确认后暂停 SaaS 消费,观察查询及重试;恢复并由 SaaS inbox 核对、触发缺失补传;录音在 HTTP 200 后、ready 发布前后分别检查清理 | 无应用证据时 saas_applied=unknown,不无限重投;SaaS 有最终 inbox/资产证据;仅按批准的可靠交接/保留/无恢复任务条件清理,不以 HTTP 200 或猜测应用成功删除;失败/未确认数据背压、超期授权审计 | | SEC-01 租户隔离 | 两租户相同业务ID或交叉引用call/recording,尝试查询、补传、播放 | 跨租户被拒绝;事件不能覆盖别的租户;播放权限独立验证 | | SEC-02 输入/凭据 | 提交恶意dial string/媒体地址/超大消息,检查日志与签名URL | 不支持任意ARI调用/多目标注入;大小受限;密码、敏感号码和有效签名不泄露 | -| OPS-01 压测/回滚 | 按目标并发/CPS持续30分钟;停止接单并回滚版本,再恢复 | 不超限、无双拨/串线/终态回退;数据与录音卷保留;待投递事件可恢复 | +| OPS-01 预验收压测/回滚 | 10路预验收按批准 CPS 持续30分钟;停止接单并回滚,再恢复 | 不超限、无双拨/串线/终态回退;数据与录音卷保留;待投递事件可恢复;仅预验收,不据此签署 G4 | +| OPS-02 生产容量/多实例 | 依一期计划 AT-12~AT-15,在冻结负载和时长下至少1000路同时已接通完整 AI;多调度实例争抢/切换,单 Cell 故障及大租户积压/背压 | N+1 安全容量有实测证据,不宣称活动通话热迁移;共享配额、控制屏障和公平恢复不被旧实例绕过;供应商/CPS/AI/RTP/MQ/OSS 均达冻结指标 | ## 5. 证据、结果与签字模板 @@ -133,8 +136,8 @@ 1. 文档契约、实际Schema/API和实现版本一致,G0参数无未确认阻断项。 2. 必需节点、真实SIP、AI、中间件、MQ及SaaS资产链路均通过相应层级,不以Mock替代。 -3. 本文全部阻断用例有通过证据,确认后的指标达标;双拨、串线、越权、静默数据丢失为零。 -4. 配置/凭据/卷/监控/告警/补偿/保留与回滚操作已交接;已知单实例及本地录音失效边界被接受或另行修复。 +3. 本文阻断用例及一期计划 AT-01~AT-15 有生产证据,至少1000路完整 AI、N+1、多调度实例及冻结指标达标;10路或单调度实例只通过预验收,不能替代 G4;双拨、串线、越权、静默数据丢失为零。 +4. 配置/凭据/卷/监控/告警/补偿/保留与回滚操作已交接;本地录音永久丢失等边界明确接受或修复;不能以接受单实例边界豁免生产多实例协调用例。 5. 一期没有计费入口或扣费行为,现有SaaS业务数据未被重复建设或破坏。 本次文档交付只完成L0中的参考审查与文档校验;L1–L4均待实际环境执行和签字。 diff --git a/docs/最终开发部署监控与验收计划_v1.0.md b/docs/最终开发部署监控与验收计划_v1.0.md new file mode 100644 index 0000000..55397e7 --- /dev/null +++ b/docs/最终开发部署监控与验收计划_v1.0.md @@ -0,0 +1,301 @@ +# agent-call:最终开发、部署、监控计划与验收标准 + +**版本:** v1.0 +**状态:** 用户已接受方案决策,按本文件启动契约驱动的 Mock 开发;外部资源与供应商对接并行协调。本文为实施计划,不是实现完成或生产验收报告。 +**范围:** 外呼执行应用、Cell 本地执行器、媒体/AI 接入、MQ 结果、OSS 录音交接、部署与监控;不重建 SaaS,不计费,不提供 HTTP 拨号或业务回调入口。 + +## 1. 文档权威与当前事实 + +- 本文统一规定开发顺序、Mock 策略、运行基线、部署、监控和验收门禁;取代旧计划中的阶段安排及“需再次拍板方案”的表述。 +- [主契约](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)是标识、HTTP 七条路径、MQ 拓扑/字段/状态的唯一维护来源。本文件不复制接口表;先生成 OpenAPI/JSON Schema,再编写 Mock 和正式实现。 +- [原一期计划](一期呼出应用开发计划_v1.0.md)保留 WBS/AT 编号和历史估算;[交付设计](交付文档/一期中间调度件与Asterisk_v1.0/01_中间调度件与MQ回传设计.md)、[部署细则](交付文档/一期中间调度件与Asterisk_v1.0/02_Asterisk部署与SIP对接步骤.md)、[专项用例](交付文档/一期中间调度件与Asterisk_v1.0/03_联调步骤与验收标准.md)保留实现与场景细节,冲突时以本文件及主契约为准。 +- 当前已有 ASR Web、ASR 协议适配、Asterisk 配置生成、阿里云只读计划/受控创建工具和离线测试;参见[现有运行说明](部署接入_运行说明.md)。当前尚无完整调度、AI 通话、MQ/OSS 闭环或本计划的 Mock 套件,不能使用未来产物的命令假装已可运行。 +- 本次只交付文档,不新增运行服务、不拨号、不创建云资源、不提交 Git。旧 Word/Excel/ZIP 不覆盖重生成。 + +## 2. 已接受的最终方案 + +| 决策 | 实施结论 | +| --- | --- | +| 业务通道 | SaaS 按租户队列发布执行,全部业务结果回 MQ;录音先验证 OSS、后发布 ready;HTTP 只控制、查询、整体补传和上传握手 | +| 租户 | SaaS 可信 tenant_id/key 一对一,首期不原地换 key;保留期不跨租户复用;历史快照原样回传;配额/公平/幂等按 tenant_id | +| key 边界 | 不做业务清洗、截断或编码替换;完整 routing key 的255字节限制给原 key 留224个 UTF-8 字节。超限停发、保留 SaaS 原任务并报告传输不支持,不无限重试;更长值需要另行变更传输方案 | +| 幂等 | command_id 与 execution_id 分离;同一次授权执行、网络重试、内部 FALLBACK 保持 execution_id;未知通话先对账,不换 ID 重拨 | +| 控制 | 初始版本1;请求只传 expected,服务端 CAS 递增;pause 不挂断已有电话,resume 不复活旧命令;stop 显式 drain/hangup,强挂需额外权限;stopped 不恢复 | +| 撤销与 DNC | SaaS 持久禁发并枚举受影响全部任务,复用 pause/stop 屏障;接受暂时阻塞同任务其他号码的代价。全部屏障 applied 后才确认执行侧撤销生效,失联保持待对账 | +| 拒绝再联系 | 仅明确、可追溯的客户意图或人工操作;SaaS 为持久拦截事实来源,不用未经验证的关键词自动误判。Mock 通过显式场景注入验证流程,真实 AI 判定规则随供应商接入核验 | +| 公平 | 等权轮询、有界窗口、全局协调租户并发/CPS;不抢占已接通电话,不承诺固定开始 SLA,不首建权重或保底借用体系 | +| 补传 | 七条业务 HTTP 路径包括按源命令补传;单通话/单命令整体补传,无事件筛选,固定截止点、原事件、分批限速,不触发拨号 | +| 应用确认 | 不增加 MQ receipt 或 HTTP 收讫接口;自动确认到 broker,SaaS inbox 对账后受控补传;无应用证据的状态为 unknown | +| 智能体配置 | 发布执行前已同步可信不可变版本;消息只引用配置,缺失拒绝;执行中不替换配置,不逐呼访问任意 URL | +| 文字与录音 | 中间稿允许合并,最终稿可靠持久且首期不事后修订;重复同内容幂等,不接受同最终段异内容覆盖。录音接通后开始、单轨混音、挂断封口后上传 | +| 网络与生产 | 多机器、多 EIP,每通话固定 Cell/出口;中央多实例调度、本地执行器独占 ARI。生产至少1000路同时已接通完整 ASR/LLM/TTS,按实测安全容量做 N+1 | +| 精简边界 | 无计费、RAG、开发者门户、自研模型、通用工作流、自动扩容;OpenAPI + 共用 JSON Schema + MQ 拓扑表,AsyncAPI 延后;旧 voice_test 仅复用 ASR | + +**数值说明:** 之前未提供的业务额度、保留期、预算和供应商指标,不因“方案全接受”变成已取得的生产事实。第 5 节给出可执行的开发测试基线;生产沿用目标口径,但参数需在真实资源验证后登记,不能直接把 Mock 数值升为服务承诺。 + +## 3. 外部协调与 Mock 对接标准 + +### 3.1 六类外部依赖 + +| 外部项 | 当前开发替代 | 真实切换需要的证据 | +| --- | --- | --- | +| SaaS/身份/租户任务/配置 | 最小 SaaS Mock:可信测试注册配置、版本快照、HTTP 上传握手、独立 MQ 发布器和 inbox/业务库消费;测试服务凭证按身份/租户/权限校验 | 双方 Base URL、身份签发与轮换、可信租户/任务/配置来源、事件事务落库及授权播放 | +| 持久数据库 | 隔离本地真实关系数据库,不以进程内字典代替;优先复用已有受支持驱动/数据库能力,无既有约束时用 PostgreSQL 测试实例 | 实际数据库产品/版本、权限、事务及唯一约束、备份恢复;更换引擎须重跑迁移和并发测试 | +| RabbitMQ | 使用真实 RabbitMQ 容器;普通开发单节点,故障/HA 测试用独立3节点 quorum 配置。不是用 HTTP 队列模拟 AMQP | 真实 broker 版本、VHost、TLS/ACL、策略兼容、容量、可靠死信和恢复证据 | +| SIP/Asterisk | 私有测试网络中的真实 Asterisk + Mock SIP 对端/软终端;单元测试可注入 ARI 故障,集成测试必须走真实 ARI REST/事件连接及 SIP/RTP | 现供应商协议、鉴权/注册、From/PAI、编解码、白名单、并发/CPS与原因码;无备用也能先验主线路 | +| ASR/LLM/TTS | 确定性流式 Mock:音频输入、分段文字、生成延迟、PCM 输出、取消/故障。现有 ASR 可做独立真实测试;LLM/TTS 不加载旧代码或自动调用收费服务 | 明确供应商/模型/音色/协议、配额、音频格式、取消及失败语义;完成真实多轮/插话体验测试 | +| OSS/云主机/部署 | 最小对象存储 Mock 接收真实测试字节并独立计算摘要;云工具继续注入假 CLI runner,默认不访问真实账号 | OSS 受控对象、有效期、禁止覆盖/版本及独立校验;ECS/EIP 归属、实例规格、网络、镜像、预算和权限 | + +OpenAPI 只描述 HTTP 请求/响应;MQ 用 JSON Schema 和真实 AMQP 语义,SIP/RTP/ARI WebSocket/AI 流式通道按各自协议模拟,不能为了“按 OpenAPI 对接”把它们改成 HTTP 业务回调。 + +### 3.2 契约交付与测试约束 + +以下是 **D01 计划产物,当前未生成**;目录统一放在 docs/contracts/,不在本轮创建空文件占位: + +- `executor.openapi.yaml`:呼出应用提供的控制、查询、两类补传;`saas.openapi.yaml`:SaaS 提供的上传授权/完成确认。使用 OpenAPI 3.1,固定幂等、鉴权、错误和版本语义。 +- `mq.schema.json`:执行、事件外壳及分类型 payload;集中样例和正反例复用同一 Schema。标准 HTTP 响应/事件容许兼容的可选扩展,未知主版本隔离;拒绝非法命令字段,不把严格校验和任意扩展混为一谈。 +- `mock-profile.json`:已知测试租户/任务、配置引用、模拟线路、AI 场景、固定随机种子、限值及组件版本;只能由测试运行器加载,业务消息不能指定任意故障场景/URL。 +- Mock 与正式客户端共享契约,但不能共享同一套“假成功业务逻辑”作为验收预期;结果按数据库事实、broker、ARI/SIP记录及文件字节独立断言。 +- 最少模拟:合法成功、未知资源、越权、同键异内容、超限、乱序/重复、延迟/超时、限流、断连、取消、存储缺对象/错摘要、部分投递成功及重启恢复。 +- 对象 Mock 必须检查会话/租户/通话绑定、签名或测试授权、有效期、允许写入目标和禁止覆盖;由存储端读取收到的文件字节算 SHA-256,不直接信任调用者给出的摘要。该算法只作 Mock 基线,真实 OSS 独立校验能力需实测,不把 ETag 等同于 SHA-256。 +- 已发布不可变配置在测试注册资料中提前存在;控制先于 execute 也能验证任务归属和取得原 tenant_key。缺失注册/配置不能临时自动认领。 + +### 3.3 Mock 隔离与生产防误用 + +1. 配置及每份测试报告声明 `mock / mixed / real`,同时逐项记录 provider 模式;混合测试不能整体标记 real。 +2. 默认使用 Mock,出口按允许目标限制,禁止访问公网 SIP/AI/阿里云 API;使用假号码仍可能误拨,隔离必须靠网络和配置实现。 +3. 正式 profile 启动时拒绝 Mock provider、测试凭证、虚拟号码和测试故障开关;依赖缺失就不接单,禁止静默回退 Mock。 +4. 内部 Mock 控制和观测不成为正式业务 API,不允许公网访问;Mock AI 单独显示“模拟”,不能把 ASR Web 的 LLM/TTS 状态改成“真实已启用”。 +5. Mock 数据只用合成音频/号码/文字;不复制生产客户数据、密钥或历史服务器凭据。 + +## 4. 开发任务、里程碑与交付物 + +### 4.1 最小部署单元 + +- **中央执行服务**:控制/查询/补传 HTTP、租户公平调度、持久状态和 outbox;允许多实例,统一所有权、原子额度及失效隔离令牌,不按每实例再发一份额度。 +- **Cell 本地执行器**:与 Asterisk/媒体同 Cell,长期 ARI 连接、发起前复核、通道/端口/录音管理;一节点一个活动 ARI 控制者,中央不竞争同一通道。 +- **数据与外部依赖**:关系数据库、RabbitMQ、SaaS/AI/对象存储适配。逻辑模块可共进程,不为每个模块新建微服务;不以全局内存锁替代跨实例协调。 +- 外部接入通过明确配置切换,业务状态机不写两套 Mock/Real 分支。只实现已需使用的适配,不预建通用供应商框架。 + +### 4.2 WBS 执行顺序 + +| 包 | 开发内容 | 可验证产物与退出条件 | +| --- | --- | --- | +| D01 契约 | 将主契约落成两份 OpenAPI、MQ Schema、样例及测试 profile | HTTP/MQ 正反例通过;字段/状态/权限无“实现自行猜测”;不再等待外部账号才能开工 | +| D02 底座 | 最小配置/身份、持久模型/迁移、真实本地 DB/MQ、Mock 依赖及健康检查 | 干净环境可重复部署;重启保留事实,非法配置失败关闭;Mock 出口隔离 | +| D03 指令/调度/控制 | 租户队列生命周期、双层幂等、有界公平窗口、跨实例额度/租约、CAS 控制和撤销屏障 | broker/DB 提交竞态、两调度实例争抢、waiting 超期和多任务撤销均有证据;未知占用不错误释放 | +| D04 Cell/SIP | 单线路生成、长期 ARI、通道/桥/媒体、主叫被叫规则、录音及清理 | 真实 Asterisk 对 Mock SIP 完成独立多通话;不逐呼改共享配置,单线路启动无需虚构备用 | +| D05 FALLBACK/对账 | 安全尝试预算、原通道核实、迟到接通、节点选择/断线恢复 | 仅明确未接通且旧通道结束后切线;无备用明确结束,状态不明不重拨 | +| D06 AI/媒体 | 按最小内部音频契约连接流式 Mock、VAD/取消/超时;真实 ASR 可单独接入 | 多轮、插话、取消迟到包、静音与AI错误不串话;不调用旧 LLM/TTS | +| D07 文字 | segment/revision、中间稿合并、最终稿固定及播放证据 | 乱序/重投收敛;最终稿持久,取消内容不伪称已播放 | +| D08 录音/资产 | 接通后混音录音、封口、授权、上传、独立校验、OSS ID/ready、受控清理 | 真正上传字节;错对象/摘要/过期/超时不产生无效 ready 或重复资产 | +| D09 恢复/补传 | command/call 查询、outbox重试、整体补传、SaaS inbox对账与交接 | 无 call_id 结果可恢复;原事件/截止点不变,未知应用状态不无限重投 | +| D10 部署/监控 | 可重复构建、版本清单、备份恢复、告警和停止接单/排空/回滚 | 告警真正触发并恢复;回滚不清空数据库、队列、录音卷或幂等屏障 | +| D11 综合测试 | 本文件第8节及既有 AT/专项用例的 Mock、故障、多实例和模拟规模测试 | Mock 交付门禁通过;真实依赖缺失仅标真实阶段阻塞,不拖延 Mock 代码交付 | +| D12 真实替换/生产 | 外部资料齐备后逐项替换Mock、真实通话、容量、灰度与运维交接 | 真实全链路/1000路/N+1通过才生产签字;未通过保持 Mock 交付而非假上线 | + +顺序:D01→D02→D03/D04→D05/D06→D07/D08→D09→D10/D11→D12。D09/D10 的幂等、观测及保护从早期建设;单人执行按依赖串行,斜线不代表自动增加人力。外部协调从 D01 并行开始。 + +### 4.3 里程碑与排期口径 + +| 门禁 | 完成定义 | +| --- | --- | +| G0 契约/测试基线 | 已接受方案转成可验证规范;登记本文测试数值、依赖版本和可重复 Mock 场景;生产外部参数单独建清单 | +| M1 指令闭环 | D01~D03,SaaS Mock 经真实 MQ 发布/消费,控制/查询和公平幂等通过 | +| M2 语音闭环 | D04~D08,真实 Asterisk + SIP/AI/OSS Mock,多轮/录音/文字完整,证明执行链而非真实供应商质量 | +| M3 Mock 交付 | D09~D11,全部适用功能、安全、故障、部署/告警/恢复测试通过;发布可运行测试包和证据 | +| R1 真实闭环 | D12 前段,真实 SaaS/SIP/AI/OSS、授权号码、出口/权限/媒体全部验证;不以 Mock 代替 | +| R2 生产验收 | 至少1000路真实完整 AI、N+1、多实例、CPS/SLO、数据恢复和安全门禁通过 | +| R3 生产交接 | 灰度/回滚及值守演练完成,正式版本/参数/告警/责任人签字 | + +旧计划328–464小时仅为历史基准,不覆盖本版增量。D01结束提交按包估算、实际可投入工时和外部关键路径;本文件不伪造日历交期。只缺外部资源时继续 M 阶段,不跳过 R 阶段或无限等待后宣称代码不可开发。 + +## 5. 可执行测试基线与生产参数边界 + +### 5.1 DEV-MOCK 基线 + +以下为本计划给出的**工程初始测试配置**,不是用户真实业务额度/生产 SLA,也不代表已测通过。改变时版本化保存 profile 并重跑相关门禁。 + +| 项目 | DEV-MOCK 值 | +| --- | --- | +| 拓扑/规模 | 3个测试租户、2个中央调度实例、2个Cell;每租户并发2/CPS1、突发令牌1,全局并发6/CPS3;每Cell测试容量4(模拟声明,不是实测生产容量) | +| 公平/窗口 | 等权,每轮每租户最多1个新许可;无事件时轮转间隔上限100ms;新活跃租户发现≤1s;每租户未ACK≤4/持久待发起≤16,全局未ACK≤32/待发起≤64,所有实例汇总 | +| 发布/积压 | 每租户发布≤10条/s,命令队列上限1000条或16MiB先到者,整体待发布/队列水位另有保护;队列满拒绝发布,不丢头部;大量任务保留SaaS数据库 | +| 消息大小 | MQ≤256KiB;HTTP JSON≤64KiB;超限明确拒绝,不截断最终文本 | +| 时效 | 测试授权有效期300s且不越过允许时段;首次准入窗口30s;到期终结延迟≤1s;重投/重启不延期 | +| 通话/AI | 振铃30s、最长通话180s;客户静音15s;AI首输出超时5s;测试故障后明确结束,不自动切SIP或无限重试;无获批真实兜底话术则不播放自造提示 | +| 心跳/时钟 | 心跳2s、租约10s、对账轮询上限2s;失联立即禁止本地新发起,未知活动占用保留;时钟偏差>500ms告警、>2s停止新准入,测试覆盖时钟跳变 | +| HTTP/投递重试 | HTTP连接3s/总请求10s;可恢复失败退避1/2/4/8/16/30s并加抖动,每轮最多6次;尊重Retry-After/有效期;耗尽持久隔离并告警,不能删除原事实或换ID;录音字节传输单独设120s测试超时 | +| 上传/补传 | 上传授权300s;对象Mock文件≤16MiB;补传每批≤100事件、全局≤50事件/s,优先实时结果;大范围分批读取,不一次入内存 | +| 本地水位 | 录音/缓存测试卷可用容量至少16GiB;使用率70%告警、80%停止新接单,降至60%且依赖恢复后才恢复;预留空间须覆盖活动通话最大剩余录音,不能耗尽后才保护 | + +等待原因优先采用租户并发→租户CPS→线路容量→Cell/AI容量→纯调度;not_after 到期优先 COMMAND_EXPIRED,其余截止语义按主契约。调度器遍历/过期扫描必须有界,不能由租户队列数放大为无界循环。 + +### 5.2 测试保留、恢复与数据损失边界 + +- 仅合成测试数据:事件补传窗口7天,SaaS测试 inbox 至少8天,日志7天;对象保留至少覆盖对应 ready 事件的完整补传窗口,未决恢复对象不普通清理。 +- verified + ready 持久化并确认发布、无已知恢复任务的本地已交接录音,至少保留24小时再清理;未确认/失败文件不自动删,到水位背压,超期处置审计。 +- execution 去重和 stop 墓碑不套用上述普通 TTL;在授权永久失效依据建立前不删除。环境整体销毁须明确仅为该隔离测试环境,不能误清生产记录。 +- 测试注入 SaaS 消费/上传中断5分钟,恢复后10分钟内补齐该固定负载的关键数据。进程/容器故障且持久卷完好时,已提交业务事实/最终稿/封口录音不丢失;共享DB/MQ恢复后60s内恢复安全调度,未知通话不强行放行。 +- **宿主机/磁盘永久丢失的未上传录音目前不能保证恢复。** Mock 必须验证明确 failed/告警,不得宣称录音RPO=0。生产 RPO/RTO、保留/合规删除期限尚无数值,真实部署前必须登记并决定额外存储保护;不能从“方案接受”推导用户接受任意丢失。 + +### 5.3 SLO 验证口径 + +| 指标 | M阶段测试目标及起止点 | +| --- | --- | +| 查询/受理 | HTTP查询P95≤500ms;可正常消费时,SaaS持久发布→命令持久受理P95≤2s,资源等待单列 | +| 控制/撤销 | 节点健康且无不确定发起时,控制持久受理→全部相关屏障applied P95≤2s;撤销另记SaaS禁发和opt_out到达时间;失联不计成成功样本,须保持待对账并告警 | +| 公平 | 持續可调度且资源足够的B/C,发现活跃后≤2s获得首次许可;稳定竞争统计至少100次许可,3个等权租户份额偏差≤10个百分点;资源不足及隔离租户单列 | +| AI响应/打断 | VAD确认客户语音结束→首个有效TTS音频发往桥P95≤1500ms;VAD确认插话→停止旧TTS发往桥P95≤500ms;至少100个有效轮次,Mock可控延迟另列,真实体验需R阶段 | +| 最终文字/录音 | 最终稿形成→SaaS Mock事务应用P95≤3s;≤180s测试通话挂断→对象verified、ready消费及授权读取≤120s;UI延迟单列 | +| 清理/恢复 | 正常结束30s内清理本次通道/桥/媒体;异常节点恢复后核对清理;中断恢复按第5.2节,不能靠删除积压达标 | + +R阶段仍须验证上述功能与延迟目标;若供应商能力使目标需变更,记录差异并批准后更新生产基线,不以 Mock 达标覆盖真实失败。生产 CPS、租户数/额度、最长中断与保留期必须有具体配置,未填写不得通过 R2。 + +### 5.4 1000路压力测试的两种结论 + +- **模拟规模测试:** 独立 SCALE-MOCK profile 至少2个调度实例,100租户×并发12、全局并发1200,模拟拨号CPS20且受资源许可控制;多出的200个占用预算供拨号/振铃及换批补位,不能计作已接通。预热后至少1000路“模拟接通”维持60分钟,负载器持续补充新授权测试执行,不能把180s通话上限内的一批电话误作持续60分钟。可扩展测试Cell,不套用DEV容量4;模拟Cell/AI额度在报告中注明。 +- **真实生产容量:** 至少1000路同时已接通且持续处理真实 ASR/LLM/TTS,稳定测量不少于60分钟;统计拨号/振铃及失败数、真实CPS、AI/RTP/MQ/OSS和端口/FD/带宽。必须满足 `(Cell数−1)×单Cell实测安全容量≥1000`,并留发布/突发余量;无固定机器数承诺。 +- 失去一个Cell后,该Cell既有通话可能中断,不迁移、不自动重拨;在健康Cell安全余量内用新的授权测试执行补充负载,再验证可持续承接1000路。不能以故障瞬间仍有1000路存活为虚假保证,也不能把旧 execution 换ID自动重试。 +- Mock 模拟1000个状态对象或1000条WebSocket不是完整1000路生产验收;如测试机资源不足,报告为模拟规模未完成,不放大Mock性能数字。 + +## 6. 部署计划 + +### 6.1 本地/CI Mock 部署 + +D02/D10交付可重复的Compose或等价脚本,所有镜像固定版本/digest、持久卷与健康依赖明确;当前不提供不存在的启动命令。 + +启动顺序: + +1. 校验 profile、模式、版本、磁盘和网络隔离;生成仅测试使用的服务凭证,检查没有真实供应商地址或密钥。 +2. 启动DB/MQ并执行兼容迁移;创建测试VHost/租户绑定及最小权限;HA测试独立启用3节点quorum。 +3. 启动SaaS/对象/AI/SIP Mock及可信注册;Mock上传使用实际文件和授权校验,不直接返回预制ready。 +4. 启动Asterisk及Cell执行器,确认每Cell只有一个ARI控制者、独立媒体端口和录音卷;同宿主机不得启动多个争用host-network端口的Asterisk。 +5. 启动中央执行服务,恢复待处理事实/租约,全部readiness满足后才接受测试发布。 +6. 一条MQ命令贯通受理→模拟通话→文字/录音→SaaS Mock inbox/授权读取,再进入并发和故障测试;结束只清理本次测试资源。 + +### 6.2 健康检查与访问控制 + +- 实现独立存活、就绪和指标端点(运维端点,不增加业务接口种类):进程存活不等于能接单;就绪必须检查DB持久写、MQ可路由、配置/租户注册、Cell/AI许可及磁盘。 +- 依赖失败只停止不安全的新任务,活动媒体尽可能完成原生命周期;不因broker或SaaS故障在音频线程阻塞。无持久事实能力时不发起新呼叫。 +- 现有ASR `/healthz` 仅表示ASR-only进程存活,不能复用为完整执行服务 readiness。 +- 默认端口仅回环/管理网;HTTP/MQ使用TLS和最小权限,测试证书也不能靠关闭校验通过安全用例;ARI优先Cell回环,跨管理网使用受控TLS。 +- 模式、provider端点、配额、版本均由受控配置提供;租户消息不可覆盖服务地址/凭据/Cell或故障注入开关。 + +### 6.3 真实环境准入和云约束 + +1. C1~C6协调资料齐备后逐项替换适配器,保留同一套契约测试;先单路真实SIP/AI,再混合模式,再全真实闭环。 +2. 阿里云北京 `cn-beijing`,先查询 `123.56.71.98` 的EIP/实例归属及可复用资源;查不到、未知绑定或多候选停止,不用随机IP替代,不动无关实例。 +3. 竞价创建需要明确规格、镜像、VSwitch、安全组、SSH KeyPair、计价上限和完整预算;持久ClientToken,绑定失败保留实例ID,不自动另建或删除。 +4. 本次批准不含任何具体真实消费金额;未登记预算前只做本地Mock/只读计划。计算报价不含磁盘/EIP/流量/SIP/AI费,预算须覆盖全项。 +5. 每个生产Cell有获批固定EIP/供应商白名单,按目的/来源开放SIP和RTP;多EIP直连,不引入单EIP+NAT替代方案。真实SIP日志核实实际出口,不能只看云端绑定成功。 +6. 先验证当前供应商单线路;原始主叫 `BD93205882` 和被叫前缀 `7089` 按确认字段映射,不当Digest用户名或做数字清洗;同地址不虚构主备。 +7. 测试结束核实本次创建ECS已停用并回收/删除,保留指定IP/EIP;不释放、解绑或覆盖白名单IP,不处理无关资源。 + +### 6.4 发布、停止与回滚 + +- 构建代码/Schema/迁移/配置的统一版本清单,产物固定digest;数据库先做向后兼容迁移,备份并验证可恢复后发布。 +- 滚动发布先禁止目标执行器的新许可并排空;不粗暴停止全部实例或按过期租约释放未知电话。活动通话最长时限到达仍有残留时进入受控对账。 +- 回滚优先停止新任务→确认/隔离在途事实→保留DB/队列/卷→切回兼容应用/配置→恢复核对→小流量接单;不可用旧备份覆盖较新的去重/屏障来“回滚”。 +- 模式错误、双拨/串线/越权、丢最终结果或持久化故障为立即停止扩量条件;不得为排错关闭鉴权、清空队列或全挂断无关通道。 + +## 7. 监控与值守计划 + +优先复用已有监控系统;没有则提供 Prometheus 可抓取指标、Grafana面板和告警规则作为交付,不另建监控平台。名称在D10随实现确定;不得把此计划当成现有metrics已上线。 + +### 7.1 指标、告警与处置 + +| 范围 | 必须观测 | 初始告警/保护和动作 | +| --- | --- | --- | +| 正确性/安全 | 重复业务发起、跨租户、串线、状态回退、未验证ready | 任一确认事件立即P0;冻结受影响新执行/扩量,保留证据,不自动重拨或删数据 | +| DB/状态 | 持久提交失败、锁等待、事务延迟、未知执行数、所有权失效 | 不能可靠落意图立即停新发起;持续30s P1;未知占用隔离对账,不能仅租约到期释放 | +| 调度/租户 | 排队最老年龄、可调度等待、实际份额、并发/CPS、未ACK/待发起窗口、截止延迟 | 超硬配额立即阻断;有资源下等待>2s持续1分钟或截止处理>1s告警;资源不足与调度饥饿分开 | +| MQ/outbox | confirm/return/nack、重投、DLQ、outbox年龄、队列数/字节及broker blocked | outbox最老>30s持续1分钟P2,>120s P1;broker blocked立即背压;无路由/隔离事件可定位,禁止无限requeue | +| Cell/ARI/RTP | ARI事件连接、端口/FD、活动通道/桥、RTP包率/丢包/抖动、出口健康、孤儿资源 | ARI断开立即隔离该Cell新任务,>10s P1;正常结束残留>30s告警;不以REST200掩盖事件通道失败 | +| AI/媒体 | provider模式、首包/持续延迟、取消确认、迟到旧音频、超时/错误、流式连接额度 | 连续超时/配额不足停止向故障provider分新任务;5分钟窗口超SLO告警;不自动切旧实现或Mock | +| 文字/资产 | 最终稿落库/投递、上传/校验失败、ready延迟、暂存年龄、文件数/预计剩余容量 | 校验冲突隔离;ready超120s告警;按70/80/60%水位保护和恢复;任何未确认数据不静默删除 | +| SaaS应用 | 测试/正式inbox事实、事务失败、业务关联缺口及应用延迟 | SaaS侧应用延迟>3s持续1分钟告警;呼出侧只报告broker事实,不能猜测applied;触发受控对账补传 | +| 节点/时钟 | CPU/内存/IO/网络、conntrack/UDP、时间偏差、重启和竞价回收通知 | 时钟偏差按5.1保护;资源持续饱和停止扩量;回收通知停止新任务并排空,不能承诺通知时间够完成全部电话 | + +阈值是DEV初始规则;真实部署登记生产阈值、连续窗口、恢复滞回和告警接收人。故障注入的预期告警应标记测试,不全局关闭告警。 + +### 7.2 日志、面板与值守 + +- 日志贯通 command/execution/call/attempt/event ID、Cell/线路和模式,脱敏号码、转写、签名URL及凭据;抓包和音频证据需授权与限期保留。 +- 指标不使用电话号码、call_id/event_id、文本或不受控tenant_key作标签。按租户明细从受控状态查询/日志获取;有限租户指标需基数预算,不把1000路连接全部展开成无限标签。 +- 面板至少分总览、租户公平/背压、Cell媒体、AI、MQ/资产恢复五类;Mock/mixed/real分开展示。 +- 每条P0/P1有负责人、确认/升级链及运行手册;M阶段演练告警触发→收到→诊断→保护→恢复→复盘。生产须登记实际值班人与响应承诺,不凭角色名视为已有人值守。 + +## 8. 验收标准与证据 + +### 8.1 原则 + +- 每个用例初始为“待执行”,运行后只能记通过/失败/阻塞/不适用并附依据;缺外部条件的真实用例标阻塞,不能改成不适用来绕过生产门禁。 +- M阶段用真实本地DB/MQ/Asterisk加外部Mock证明代码行为;R阶段证明真实业务/供应商能力。既有 AT-01~AT-15、DEP/MQ/SIP/DATA/SEC/OPS 场景仍须覆盖,下面统一说明最低证据。 +- 正确性零容忍:未经授权重复业务拨号、串线、越权、虚假终态/ready、静默丢失最终事实均为阻断。SIP同一dialog的协议重传不是新业务拨号,合法FALLBACK须单独计attempt/CPS。 + +### 8.2 必测用例矩阵 + +| 编号 | 场景/注入 | M阶段通过标准 | R阶段追加证据 | +| --- | --- | --- | --- | +| V01 契约/隔离 | 七路径、Schema正反例、未知版本、超大消息、凭证/租户错配 | 错误/隔离可追踪;无HTTP拨号;224/225 UTF-8字节及特殊key原值验证;生产profile拒绝Mock | 真实身份/域名/TLS/ACL/注册及平台限制 | +| V02 幂等/提交竞态 | 同ID重投10次、换command同execution、DB提交/ACK前后崩溃 | 同授权不重复拨号;同键异内容拒绝,事实/outbox原子;持久化失败不ACK | 正式DB/MQ相同故障恢复 | +| V03 准入/公平 | A积压、B/C新入队、额度耗尽、30s截止、两调度器争抢/失权 | 有界窗口、公平与截止指标达标;未知通话不误释放;重启不延长授权 | 实际租户数/配额、生产CPS和容量限制 | +| V04 控制/撤销 | expected冲突/重复、积压、waiting、在途、跨任务DNC、Cell失联 | 目标版本只递增一次;全部屏障前不虚报applied;resume不复活旧执行;强挂鉴权;撤销不自动再拨 | SaaS真实禁发/枚举/opt_out来源及生效时延 | +| V05 背压/DLQ | 队列满、mandatory return、NACK/confirm丢失、broker故障 | 原ID有限重试,满队列不丢头部,可靠隔离;恢复回原租户配额,不走全局执行FIFO | 正式quorum/可靠死信及HA容量 | +| V06 SIP/媒体 | 单线路、不同标识音并发、振铃/接通/挂断、ARI断连 | 真实Asterisk+Mock对端双向RTP/录音无串线,清理有时限;无备用也能启动 | 实际白名单出口、From/PAI/前缀/编解码、授权号码可听 | +| V07 FALLBACK/未知 | 明确线路故障、迟到接通、busy/reject/invalid、已接通断线 | 旧通道明确结束才合法切换;同call/Cell/出口;AI故障和不确定状态不触发重拨 | 独立备用具备时真实验证;缺备用保留真实切换为阻塞,不虚构 | +| V08 AI/文字 | 多轮≥100、插话取消、旧包迟到、最终稿重投/异内容、静音/超时 | 达测试时延;最终稿固定、播放证据准确,AI故障明确终止;Mock不外呼收费AI | 真ASR/LLM/TTS语音体验、音色、取消、错误与配额 | +| V09 录音/校验 | 缺对象、错摘要、授权过期、覆盖、完成确认超时 | 独立读字节校验;无重复资产;只在verified后ready;SaaS Mock租户鉴权读取 | 真实OSS独立校验/防覆盖、SaaS资产关联及播放 | +| V10 恢复/补传 | 中断消费/上传5分钟、已确认未应用、单call/command重放 | 10分钟内补齐本轮数据;原事件/截止点,分批限速;无call结果恢复,不将completed当applied;无筛选字段 | 真实RTO/保留期及SaaS inbox证据 | +| V11 清理/永久丢失 | 卷保留重建、满盘、模拟磁盘永久丢失 | 正常卷保留数据可恢复;水位保护;永久丢失显式failed/告警而非假ready;不删屏障/未确认资产 | 正式备份恢复、录音损失保护及批准RPO | +| V12 监控/回滚 | 断DB/ARI/MQ、时钟漂移、版本回滚、凭据失效 | 告警/保护/恢复可观察;不绕过鉴权;旧程序不能重拨历史执行或丢较新事实 | 正式环境演练、值班接收及兼容回滚证据 | +| V13 模拟规模 | SCALE-MOCK 1000模拟接通、多实例公平/失权、故障恢复 | 维持60分钟、资源和数据校验完备;清晰标记模拟,不以进程数或WS数替代媒体验证 | 不替代下一行 | +| V14 生产规模 | 全真实1000路、单Cell故障、恢复负载、混合拨号/振铃 | 不允许仅以Mock通过此项 | 1000真实完整AI稳定≥60分钟,N+1安全容量、实际CPS/SLO、RTP/AI/MQ/OSS及副作用正确性全部有证据 | + +仅有一家真实线路时,可以通过单线路真实闭环;真实备用切换未验不宣称线路容灾。若生产策略启用备用,则对应真实验证不可跳过;N+1 Cell容量验收不能用“暂无备用供应商”豁免。 + +### 8.3 报告与复核 + +每次运行归档:代码/契约/配置/镜像版本、逐项provider模式、profile与随机种子、时间同步、租户/配额、负载与样本数、操作/故障时刻、预期/实际/指标分位值、DB/inbox/outbox、ARI/SIP/媒体关联、文件摘要、告警及回滚证据、失败/阻塞项和负责人。 + +- 统计分母包括失败/超时和未决执行,不能只抽成功电话;准入等待、振铃和已接通分别统计。 +- 不公开真实密钥/签名/音频/客户号码;证据进入受控存储并有校验清单。 +- Mock的SaaS页面/授权下载仅证明兼容客户端行为,不是正式SaaS页面已交付;生产须取得SaaS inbox/业务库/真实授权播放证据。 + +## 9. 交付标准与责任 + +### 9.1 M3 Mock 开发交付包 + +1. 七路径OpenAPI、MQ Schema、集中样例、Mock profile、兼容说明及可运行契约测试。 +2. 中央服务、Cell执行器及所需Mock代码/固定构建产物;配套可重复部署、数据库迁移、状态恢复和一键测试入口,不交付明文真实凭据。 +3. V01~V13适用测试及既有专项用例记录;模拟规模若未达到则明确剩余项,不签完整M3。 +4. 指标/告警/面板、备份/回滚/隔离/补传手册及真实替换清单;所有真实用例显式保留待验。 +5. 当前外部依赖可全部Mock而完成M3;这不等于生产交付、真实AI启用或1000路商业服务能力。 + +### 9.2 R3 生产交付包 + +M3产物 + C1~C6真实配置/权限(受控存放)+ R1/R2全部证据 + 实际生产参数/保留与RPO/RTO + 灰度和回滚记录 + 明确值班联系人及业务/技术/运维签字。正式环境不得有Mock provider或未经批准的收费/线路配置。 + +### 9.3 分工与外部并行协调 + +| 角色 | 本轮责任 | 外部缺失时 | +| --- | --- | --- | +| 呼出应用负责人 | 契约、调度/ARI/媒体、Mock及测试、监控/部署产物 | 按Mock交付,不替SaaS决定新的业务执行 | +| SaaS后端/资产负责人 | 接口对齐、可信注册、发布/DNC/配置、inbox/资产实现和资料 | 以同一OpenAPI/MQ用例先验Mock;登记真实差异并逐项消除 | +| SaaS前端 | 状态/文字/录音授权展示和失败可见性 | 可按Mock后端开发;正式页面另有真实验收 | +| 运维/存储负责人 | DB/MQ、证书/权限、OSS、ECS/EIP、备份/监控及预算落实 | 提供隔离本地环境;不擅自创建收费资源 | +| SIP/AI供应方 | 协议、白名单、配额、取消/原因映射及授权测试 | 不阻塞Mock逻辑开发,仍阻塞其真实验证 | + +## 10. 本次文档验收与后续入口 + +本次仅检查文档引用、门禁一致性、既有样例完整性和Markdown;当前所有M/R运行用例仍为待执行。当前已存在的回归命令可继续使用,但不能把它们当作新平台全部测试: + +```bash +python3 -m unittest discover -s tests -v +(cd services/asr-web && go test -race ./... && go vet ./...) +node --test tests/test_pcm.cjs +bash -n deploy/asterisk.sh +``` + +下一实施入口是 D01:生成并验证机器可读契约与Mock profile,再做D02;无需再次讨论已接受的A类方案。遇到真实供应商协议/合规/资源事实不兼容,只对具体差异登记变更,不把整个计划退回重选,也不让Mock假成功掩盖差异。 diff --git a/docs/系统架构图_规划态.md b/docs/系统架构图_规划态.md index f708601..a62fb40 100644 --- a/docs/系统架构图_规划态.md +++ b/docs/系统架构图_规划态.md @@ -1,16 +1,16 @@ # 系统架构图(规划态) -**版本:** v0.1 -**依据:** [一期计划 v1.3](一期呼出应用开发计划_v1.0.md)、[OpenAPI 与 MQ 契约规划 v0.3](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)。 +**版本:** v0.2 +**依据:** [最终开发部署监控与验收计划](最终开发部署监控与验收计划_v1.0.md)、[主契约 v1.0](SaaS交互_OpenAPI与MQ契约规划_v0.1.md)。 **状态:** 目标逻辑架构,尚未完整实现;框图不表示已部署,也不指定机器数、数据库产品或独立微服务数量。当前仅有 ASR 验证基础,完整 AI 与生产容量均未验收。 ```mermaid flowchart LR subgraph SAAS["现有 SaaS:业务主数据与授权"] UI["租户用户/管理页面"] - BUSINESS["任务、号码、配置版本、业务授权
基础频控/拒绝再联系/重新外呼决策"] + BUSINESS["任务、号码、配置版本、业务授权
基础频控/拒绝再联系/重新外呼决策
已确认撤销:持久禁发+关联任务屏障"] SDB[("SaaS 持久存储
业务主数据、发布记录、inbox、资产关联")] - PUBLISH["可信发布服务
tenant_id 受控映射/限速/原 ID 重试"] + PUBLISH["可信发布服务
SaaS tenant_key 原样路由/限速/原 ID 重试
已确认:注册归属与传输字节上限检查"] CONSUME["事件消费者
租户校验/event_id 去重/版本合并
业务与 inbox 同事务后 ACK"] STORAGE["SaaS 存储接口
上传授权、实际校验、确认 oss_id
按租户/用户提供鉴权播放"] UI --> BUSINESS @@ -22,12 +22,12 @@ flowchart LR end subgraph BROKER["RabbitMQ:可靠传输,不是活动通话唯一状态源"] - CMDX["命令 Exchange
call.execute"] + CMDX["agent-call.commands.v1
direct;routing key:agent-call.tenant.{tenant_key}.call.execute"] QA["租户 A 命令队列"] QB["租户 B 命令队列"] QN["租户 N 命令队列"] - EVX["事件 Exchange"] - EVQ["SaaS 事件队列
本轮不按租户拆分"] + EVX["agent-call.events.v1
routing key:agent-call.{event_type}"] + EVQ["agent-call.saas.events.v1
本轮不按租户拆分"] DLQ["重试/死信与隔离
受控恢复、不得绕过租户域或配额"] CMDX --> QA & QB & QN EVX --> EVQ @@ -37,8 +37,8 @@ flowchart LR EVQ --> CONSUME subgraph APP["呼出应用:逻辑组件,部署粒度待实施"] - API["HTTP 控制/查询/历史事件补传
不提供创建通话或重新拨号接口"] - FAIR["租户公平调度器,可多实例
有界轮转、预取和持久待发起窗口"] + API["HTTP 控制/查询/历史事件补传
按 source_command_id 整体补传
不提供创建通话或重新拨号接口"] + FAIR["生产多实例租户公平调度器
共享所有权/额度,有界轮转和待发起窗口
早期单实例仅用于预验收"] ADMIT["原子资源准入与路由
租户并发/CPS + 供应商并发/CPS
Cell/端口/出口健康 + AI 配额"] STATE[("可靠持久存储/协调
命令与执行去重、控制屏障、发起意图
调度所有权、额度与租约、事件与 outbox")] OUTBOX["outbox 投递器
所有业务结果经 MQ
有限重试/原事件补传"] @@ -57,7 +57,7 @@ flowchart LR subgraph CELLS["语音 Cell 池:多机器 + 多 EIP 直连"] subgraph CELL1["Cell 1 · 固定出口"] - EXEC1["本地执行器
真实发起前复核屏障/期限/租约"] + EXEC1["本地执行器,独占本节点 ARI 控制权
真实发起前复核屏障/期限/租约"] AST1["Asterisk
预配置授权 trunk/通道/桥"] MEDIA1["媒体与会话适配
流式音频、打断取消、录音暂存"] IP1["固定 EIP 1
独立白名单/RTP 端口/健康"] @@ -118,9 +118,11 @@ flowchart LR ## 架构约束 1. **业务与媒体分离:** RabbitMQ 是唯一拨号指令入口、全部业务结果回传通道;不承载实时音频。HTTP 仅控制、查询、补传及存储握手,不做业务结果回调。AI 箭头表达经 Cell 会话逻辑编排的数据顺序,不要求供应商服务互相直连。 -2. **可靠性与公平:** 多调度实例共享持久去重、额度及所有权协调。租约失效停止新任务;未知活动通话须对账,不直接释放占用。队列满明确背压,SaaS 保留原 ID;恢复仍回原租户调度域。独立队列不等于独享 broker,也不保证固定开始时限。 +2. **可靠性与公平:** 多调度实例共享持久去重、额度及所有权协调。租约失效停止新任务;未知活动通话须对账,不直接释放占用。队列满明确背压,SaaS 保留原 ID;恢复仍回原租户调度域。独立队列不等于独享 broker,也不保证固定开始时限。`tenant_key` 是 SaaS 原始业务数据,正文、路由和回调不改写;命令 direct exchange 避免其字符被 topic 通配语义解释。 3. **固定路由:** 一通电话及合法 FALLBACK 始终固定 Cell/出口;trunk 预接入,不逐呼改写共享 SIP 配置。不建设单 EIP + NAT,不自动迁移故障节点的活动通话。当前登记出口 `123.56.71.98` 不等于已确认可操作的 EIP,图中其他出口均为规划资源。 4. **容量是目标而非结论:** 至少 1000 路同时已接通的完整 ASR/LLM/TTS 通话;N+1 或更高冗余,`(Cell 数量 - 1) × 实测单 Cell 安全容量 >= 1000`。拨号、振铃、CPS、AI 配额、媒体端口、带宽与 MQ/OSS 另行计入;未压测不承诺数量或规格。 5. **资产闭环:** ①授权 → ②上传 OSS → ③SaaS 实际校验并确认 oss_id → ④verified 与 outbox 同事务 → ⑤MQ recording.ready → ⑥SaaS 去重关联。未验证不发 ready;失败经 MQ 回传。资产处理框是逻辑能力,不强制把录音跨节点搬到新服务。 +6. **已接受方案:** 撤销复用受影响全部任务的控制屏障,原命令结果可按命令补传;租户一对一可信注册、份额及幂等按 tenant_id,key 历史值不改写;255字节路由上限扣除前后缀后余224字节,超限明确停发而非截断。 +7. **控制与应用证据:** 中央分配许可,Cell 本地独占 ARI,不能双重控制。首期无 MQ receipt,自动确认只到 broker;SaaS 用自身 inbox 对账并请求 MQ 补传,呼出侧 unknown 不变成 applied。清理按批准交接/保留规则,不以 HTTP 200 或假定已入库删除;10路预验收不能替代生产1000路/多实例门禁。 -服务鉴权、TLS、租户 ACL、密钥注入、监控与积压/磁盘水位保护横跨上述组件;为避免遮挡主链路,不额外画成一套管理平台。各外部依赖和真实测试状态仍以依据文档为准。 +服务鉴权、TLS、租户 ACL、密钥注入、监控与积压/磁盘水位保护横跨上述组件;为避免遮挡主链路,不额外画成一套管理平台。各外部依赖先按协议Mock,模式明确区分;真实测试和生产门禁以最终计划为准,框图不表示外部服务已接通。 diff --git a/docs/部署接入_运行说明.md b/docs/部署接入_运行说明.md index d7bcc72..5eda333 100644 --- a/docs/部署接入_运行说明.md +++ b/docs/部署接入_运行说明.md @@ -1,5 +1,7 @@ # 部署接入:本轮实现与运行说明 +> 本文只说明当前已存在的ASR/部署工具。后续完整执行平台按[最终开发部署监控与验收计划](最终开发部署监控与验收计划_v1.0.md)从契约与Mock开始;未来服务、Compose和运行命令须实现后补充,不把本运行说明当作完整平台已上线。 + ## 1. 实施边界 本轮依据用户选择,**仅复用 voice_test 的 ASR**,没有复制或启用其 LLM/TTS,也没有把原调研页面直接作为生产服务暴露。 diff --git a/services/asr-web/go.mod b/services/asr-web/go.mod index b7501b6..a848a6a 100644 --- a/services/asr-web/go.mod +++ b/services/asr-web/go.mod @@ -1,4 +1,4 @@ -module ai-call/asr-web +module agent-call/asr-web go 1.26.2 diff --git a/tests/test_deployment.py b/tests/test_deployment.py index fdc30fa..a2d7279 100644 --- a/tests/test_deployment.py +++ b/tests/test_deployment.py @@ -12,7 +12,7 @@ def config(): return { "region": cloud.REGION, "public_ip": cloud.PUBLIC_IP, - "project_tag": "ai-call", + "project_tag": "agent-call", "image_id": "m-test", "instance_type": "ecs.test", "vswitch_id": "vsw-test", @@ -27,7 +27,7 @@ def instance(id="i-test", tagged=True): "InstanceId": id, "Status": "Running", "Tags": { - "Tag": [{"TagKey": "project", "TagValue": "ai-call"}] if tagged else [] + "Tag": [{"TagKey": "project", "TagValue": "agent-call"}] if tagged else [] }, } @@ -244,7 +244,7 @@ class AsteriskTests(unittest.TestCase): def test_reject_injection_public_ari_and_our_ip_as_provider(self): for update in ( - {"ari_bind": "0.0.0.0"}, + {"ari_bind": ".".join(("0", "0", "0", "0"))}, {"ari_bind": "8.8.8.8"}, {"local_net": "0.0.0.0/0"}, ): # noqa: S104 — negative fixtures; renderer must reject them.