feat: complete MQ-only dispatcher and OSS upload flow

This commit is contained in:
2026-09-22 21:09:06 +08:00
parent 704652bd0d
commit a2fcc4aabb
265 changed files with 18689 additions and 1469 deletions
@@ -2,7 +2,7 @@
## 1. 状态、权限与使用方式
**状态:D01–D10原方向已确认,旧W01契约和W02 Proto已有项目内证据;本轮用户确认SaaS↔Dispatcher全MQ,受影响G0重新验证,见[计划§1.2/§8.2](plan-0918.md)。** SaaS与D之间所有请求、响应和事件禁止HTTP,每个D具备全局唯一ID和独立接收Topic/队列;新Schema/拓扑/关联待发布。OSS补充确认:配置存于D配置文件,A向D领取临时上传TOKEN后直传;SaaS不再提供OSS配置/TOKEN,最终verified校验职责不变,D不转发文件。原HTTP方向被本修订替代,不把旧证据覆盖到新设计。
**状态:D01–D10原方向已确认,旧W01契约和W02 Proto已有项目内证据;本轮用户确认SaaS↔Dispatcher全MQ,受影响G0的项目内部分已按[计划§1.2/§8.2](plan-0918.md)重新验证。** SaaS与D之间所有请求、响应和事件禁止HTTP,每个D具备全局唯一ID和独立接收Topic/队列;v2/v3 Schema、拓扑和关联已有本地包及RabbitMQ证据,外部发布/签收另计。OSS补充确认:配置存于D配置文件,A向D领取固定15分钟临时上传TOKEN后直传;SaaS不再提供OSS配置/TOKEN,上传完成以recording.uploaded可靠入队为界,不等待SaaS verified或OSS ID,D不转发文件。原HTTP方向被本修订替代,不把旧证据覆盖到新设计。
本项目已创建并验证自己的 Go module、W01 bundle、W02 Proto/stubs、RPC/mTLS 和本地 Mock 测试;未修改父项目权威来源、字段索引或生成产物,未访问真实供应商或创建云资源。文件名保留“提案”以保持链接稳定,不代表还需重复审批已确认方向。
@@ -26,7 +26,7 @@ D01–D10 的共同状态为 **用户已确认方案/源发布与验证未完
| D04 | §5 Unary/最后许可/控制屏障(GAP-04) | D/A 提交,S 确认业务控制语义,O 确认恢复边界 | 已批准协议、状态转移及崩溃矩阵;随后生成 Proto 并做隔离 PoC | 跨 Cell 发起、控制、事实提交和恢复实现冻结 |
| D05 | §5.2 共用证书会话与撤销(GAP-05) | O 提交,D/A 签收 | Endpoint/SAN 清单、角色隔离、重放负例、全组轮换方案及风险签收 | 节点准入和敏感配置交付 |
| D06 | §6.1 P1 静态制品交接(GAP-03 P1) | M/O 提交,D/A 签收 | 制品来源、授权矩阵、唯一写入口、精确加载证据格式 | SIP 静态集成与真实发布 |
| D07 | §6.2 D配置文件/临时TOKEN与A直传(GAP-02 P1) | D/O提交配置与TOKEN合同,S提交业务会话/verified,D/A联合签收 | D配置来源及失败反例、TOKEN/UploadGrant映射、15分钟/显式向D重申请、A直传/D不转发;SaaS MQ会话/verified与R12/R13衔接 | 录音交接闭环;文本归档仍延后 |
| D07 | §6.2 D配置文件/临时TOKEN与A直传(GAP-02 P1) | D/O提交配置与TOKEN合同,D/A联合签收 | D配置来源及失败反例、TOKEN/UploadGrant映射、15分钟/显式向D重申请、A直传/D不转发;recording.uploaded事实可靠入队,不等待SaaS会话/verified/OSS ID | 录音通知闭环;文本归档仍延后 |
| D08 | §6.3 profile、单活恢复、保留(GAP-06/07 P1) | O 提交,D/A 签收 | 明确数值/来源/预算、备份与恢复演练设计、唯一所有权方案 | 对应运行参数冻结与真实验收,不阻塞离线原理 PoC |
| D09 | §7 独立契约包和重建链,补GAP-10全MQ版本 | S/M 提供版本,D/A 负责导入 | 新版D身份/Topic/消息/错误/期限及正反例、只读包/哈希、可重复生成校验;不覆盖旧包 | 独立可交付构建,不得临时读取父目录运行 |
| D10 | §8 依赖和关键 SDK PoC | D/A 提交,O 审核许可/安全 | 精确版本、许可/NOTICE、漏洞处置及对应 PoC 原始记录 | 未验证组件进入正式实现/发布依赖 |
@@ -49,7 +49,7 @@ P2 增加多个同时活跃租户的等权轮询、额度不足跳过、公平
本轮补充纳入D03/D04/D07/D09/D10及通信设计GAP-10,不新增运行验收编号。完整约束见[SaaS↔D契约](contracts/saas-dispatcher.md):每个D全局唯一ID、独立Topic/接收队列、请求/响应固定原D与租户、持久inbox/outbox、错目标/重复身份/重投/乱序/超时/重启恢复;新路由长度须重算,不能照搬旧224字节租户预算。ID生命周期、Topic/绑定、消息枚举/字段/关联、错误/期限未冻结前不实现猜测协议。
P1仍为单节点/单Agent/单Cell/单租户/单活D,下文沿用的早期两Cell/两Agent全量矩阵仅为后续目录,不是本轮门禁。新增D1/D2本地消息fixture只验证定向隔离,不授权多D业务调度、HA或共享配额。旧OpenAPI/只读索引和历史证据原样保存;新MQ生产链不得保留SaaS↔D HTTP;D提供上传TOKEN的职责保留,但D本地对象验证不能替代SaaS verified。
P1仍为单节点/单Agent/单Cell/单租户/单活D,下文沿用的早期两Cell/两Agent全量矩阵仅为后续目录,不是本轮门禁。新增D1/D2本地消息fixture只验证定向隔离,不授权多D业务调度、HA或共享配额。旧OpenAPI/只读索引和历史证据原样保存;新MQ生产链不得保留SaaS↔D HTTP;D提供上传TOKEN的职责保留,项目完成边界为recording.uploaded可靠入队,不等待SaaS对象处理。
## 3. D01:外部事件 Schema 补齐方案(已确认方向)
@@ -66,7 +66,7 @@ P1仍为单节点/单Agent/单Cell/单租户/单活D,下文沿用的早期两C
| `call.finished` | 复用通话终态和 attempt 结论;开始/接通/结束及持续时间保留原单位与未接通规则;允许录音/文字仍在后处理,不覆盖资产域 | 未接通却生成接通时长;通话结束强行把未交接录音设 ready |
| `transcript.updated` | 复用段/轮次/revision/text/final、说话方和真实播放证据;中间稿/最终稿规则与同段 final 同内容幂等、异内容冲突机读化;完整文本不截断 | 迟到中间稿覆盖 final;仅生成 TTS 就标已播放;ASR-only 伪造助手播放 |
| `transcript.failed` | 引用原 call 和受影响文字范围、源定义的失败阶段/原因/恢复性;无 segment 时如何表达整流失败由上游明确;不虚构空 final | 无关联对象;失败后静默删除已持久最终稿;用 recording.failed 代替 |
| `recording.ready` | 复用原录音快照/OSS ID、文件元信息与上传会话关联;只接受 SaaS complete 已 verified 的事实;不能在 payload 带任意下载 URL/长期凭据 | PUT 2xx 即 ready;大小/摘要不符;verified=false;将文本归档伪装录音 |
| `recording.uploaded` | 报告原录音/上传事实、对象位置和文件元信息;不带OSS ID、上传会话、长期凭据或公开URL | 事实缺字段、大小/摘要不符、跨bucket/object绑定;把SaaS处理结果伪装上传事实 |
| `recording.failed` | 复用原录音身份、失败原因与可恢复性;明确上传超时、校验失败和永久丢失的区分;仍保存后续对账/补传所需关联 | 失败产生新 recording_id 逃避幂等;永久丢失报 ready |
| `contact.opt_out` | 复用原租户/task/member/call 关联与拒绝时间/证据引用;最小必要信息;ASR-only 同样具备经批准的判定与通知流程 | 未授权模型判定;跨成员关联;等待录音上传才发 opt-out |
@@ -197,16 +197,16 @@ M 是唯一编辑/审批面;制品外层记录 source_release、source_digest
### 6.2 P1 录音配置与 Agent 直连 OSS 上传(用户已确认)
**上传数据面固定为Agent→OSS;OSS配置唯一来源为D配置文件,临时上传TOKEN由D通过Unary交给A。** SaaS不下发OSS配置/TOKEN;其业务会话/complete/verified仍经MQ,上传完成后的独立校验职责不变。D不接收/缓存/转发文件,不替A上传,A不持长期凭据。
**上传数据面固定为Agent→OSS;OSS配置唯一来源为D配置文件,临时上传TOKEN由D通过Unary交给A。** SaaS不下发OSS配置/TOKEN;本项目不申请业务会话,不等待complete/verified或OSS ID。D不接收/缓存/转发文件,不替A上传,A不持长期凭据。
D/O须核验配置文件读取、服务地址/bucket/对象规则、受控凭据配置或引用、SDK签发及TOKEN与现有UploadGrant映射;未给定的精确字段不能猜。配置缺失/无效明确失败,不向SaaS取配置、不用A本地bucket/长期AK兜底;样例/日志不存实际密钥或完整TOKEN。D仅交付原执行/对象所需TOKEN、目标/方法、headers与期限。S仍须补齐既有业务会话、对象定位/校验、size/checksum和complete幂等合同;MQ中不传D配置文件/长期凭据/TOKEN,不能假定D配好OSS便等于SaaS可独立校验。
D/O须核验配置文件读取、服务地址/bucket/对象规则、受控凭据配置或引用、SDK签发及TOKEN与现有UploadGrant映射;未给定的精确字段不能猜。配置缺失/无效明确失败,不向SaaS取配置、不用A本地bucket/长期AK兜底;样例/日志不存实际密钥或完整TOKEN。D仅交付原执行/对象所需TOKEN、目标/方法、headers与期限。recording.uploaded仅报告原call/recording/upload标识、对象位置、格式、时长、完整文件大小及SHA-256;MQ中不传D配置文件、长期凭据、TOKEN或签名URL。SaaS后续处理不属于本项目,不以其消费或对象登记作为完成前置。
1. A封口并持久元信息,经R12向D领取临时TOKEN,D按自身配置复用SDK提供。原SaaS业务会话/资产登记仍MQ,但不是TOKEN来源。业务MQ响应的pending/有界等待/重取仍由W02/W11冻结,不无限阻塞RPC。
1. A封口并持久元信息,经R12向D领取固定15分钟的临时TOKEN,D按自身配置复用SDK提供。同一请求重放返回原授权及原到期时间,不能借重试自动续期;不向SaaS申请会话或资产登记。
2. A 直接把文件内容上传至指定 OSS,不把文件发给 D,也不通过内部 gRPC 传录音字节。D 失联时已有仍有效授权可继续上传;TOKEN过期或缺失则保留原文件,D恢复后由A显式向D重新申请,不自动续期,不向SaaS申请TOKEN,不改用A本地长期密钥。
3. A通过R13报告原资产/会话及元信息;D经MQ提交complete,SaaS独立校验后经原D专用Topic返回结果。上传成功但D/MQ/complete响应不可达时保留待完成状态,不发ready,不用D本地HEAD替代。
4. D持久校验SaaS MQ verified后,事务记录资产状态和recording.ready outbox,再经MQ回传OSS ID;A取得最终Unary结果的衔接同样须冻结。PUT/complete 超时分别按原对象/会话幂等对账,恢复不能新建资产或重拨。未定义的续期/查询语义仍阻塞相应恢复分支,不猜测接口。
3. A成功PUT后通过R13报告原上传事实;D同事务保存事实及recording.uploaded outbox。D/MQ不可达时保留源文件和原通知恢复记录,不重新PUT,不新建资产,不重拨。
4. D以persistent消息、指定durable队列/绑定、mandatory无return和publisher confirm成功确认交付;未可靠入队不返回completed,仅写本地outbox不算完成。不新增VERIFYING,不返回OSS ID,也不等待SaaS回复。预签名URL不具备OSS原生强制一次性语义,每次授权尝试只执行一次PUT由Agent状态机保证。
本地删除条件继承验收 profile:verified、ready 已得到要求的发布确认、无恢复任务且满足保留;MQ confirm 不等于 SaaS 应用收讫,不额外等待不存在的应用 ACK。文本 OSS 归档继续受 GAP-02 延后约束,实时 transcript.updated/opt-out 不等待 OSS。
本地删除条件继承验收 profile:原上传事实已持久保存、recording.uploaded已可靠入队、无恢复任务且满足保留;MQ confirm 不等于 SaaS 应用收讫,不额外等待不存在的应用 ACK。文本 OSS 归档继续受 GAP-02 延后约束,实时 transcript.updated/opt-out 不等待 OSS。
### 6.3 运行 profile 与单活恢复登记
@@ -241,7 +241,7 @@ D/O须核验配置文件读取、服务地址/bucket/对象规则、受控凭据
| 3.Unary/身份/许可 | 成熟 gRPC/mTLS、SAN 错配/重放、R02 丢包、§5 崩溃矩阵、RPC 超时不重拨、同机第二 D 拒绝启动 | D04/D05方案已确认;后续开发时定稿/生成Proto并验证,未通过不得签收 |
| 4.ARI/RTP/录音 | 本地固定 digest Asterisk、确定通道关联、ExternalMedia、PCMA/PCM、事件断连与取消清理;故障下注入 originate 响应丢失不得二次提交 | 先换成熟 SDK/修上游;不能手写 ARI/SIP/RTP/G.711 替代 |
| 5.ASR/LLM/TTS 参数 | 百炼/火山 ASR、OpenAI 兼容 LLM、火山 TTS 的实际锁定 SDK 对照 §4 记录传参、0/false、取消和重试;Mock 检查边界行为 | SDK 缺能力不走 metadata/raw_request;报阻塞或批准替代 SDK |
| 6.MQ/录音交接 | 隔离broker验证D1/D2定向Topic与租户隔离、错目标/重复ID、不可路由/confirm丢失、请求响应乱序/重启恢复;D从配置文件提供临时TOKEN并覆盖配置缺失/无效反例;SaaS协议Mock经MQ提供AI/业务会话/verified,A直传OSS;证明无SaaS↔D HTTP或SaaS下发OSS配置/TOKEN | 本地通过不替代真实 MQ/OSS/云身份验证 |
| 6.MQ/录音交接 | 隔离broker验证D1/D2定向Topic与租户隔离、错目标/重复ID、不可路由/confirm丢失、请求响应乱序/重启恢复;D从配置文件提供固定15分钟临时TOKEN并覆盖配置缺失/无效反例;AI/控制/查询经MQ,A直传OSS后D可靠发布recording.uploaded;证明无SaaS↔D HTTP或SaaS下发OSS配置/TOKEN | 本地通过不替代真实 MQ/OSS/云身份验证 |
每份证据记录环境/mock-mixed-real、版本、输入边界、预期/实际、脱敏日志、失败注入点和残余风险;失败/blocked 不删样本。SDK 本地 Mock 只验证客户端映射,不证明供应商服务端支持、费用或双模式真实可用。真实 SIP/AI/OSS 必须另行授权并分别验收。
+9 -9
View File
@@ -18,16 +18,16 @@
7. 不接入PostgreSQL;独立Dispatcher统一全局任务/配额,使用本地持久SQLite。Agent不设SQLite业务库,文字/录音及必要执行/上传恢复信息流式落文件。
8. 内部采用 **Unary gRPC**;Dispatcher预配置Agent Endpoint列表,Agent业务启动参数尽量只有Dispatcher Endpoint,不增加内部MQ或双向流。
9. 所有Agent共用一套mTLS证书;这只认证Agent群组,单节点授权另由受控Endpoint/自动会话绑定落实。Dispatcher身份独立,不能伪称具备独立节点证书隔离。
10. OSS配置存于Dispatcher配置文件,Agent向Dispatcher领取临时上传TOKEN后直传;SaaS不下发OSS配置/TOKEN,仍负责最终独立校验并经MQ返回verified。文本保留实时MQ事件,OSS用于归档;录音按OSS ID查看。
10. OSS配置存于Dispatcher配置文件,Agent向Dispatcher领取固定15分钟临时上传TOKEN后直传;SaaS不下发OSS配置/TOKEN。本项目以recording.uploaded可靠进入指定持久队列为完成边界,不等待SaaS校验、消费或OSS ID。文本保留实时MQ事件,OSS仅用于录音数据面。
11. Dispatcher感知Agent健康、必要资源、软件/协议能力、获授权SIP供应商和已加载配置版本;在静态授权候选内按固定、可解释策略分配新任务,不做智能负载评分或自动跨供应商重拨。
12. 管理平台仍为SIP配置唯一编辑面;P1以批准的静态快照经受控部署入口加载,Dispatcher控制准入并核验Agent加载事实,暂不建设在线动态发布/回滚编排。
13. 本次固定1个Agent、1套Asterisk、1个单活Dispatcher、单 Cell、单租户;至少3家独立SIP供应商保留为 trunk 配置/路由/协议 fixture 覆盖。双节点、第二 Cell、双租户不在本轮开发或验收范围。
14. ASR-only与ASR+LLM+TTS均为本次必需能力,按本地/隔离协议和状态机验收;真实供应商/ECS 联调延期第二阶段,不以 Mock 冒充真实供应商通过。
15. `upload-session/complete/verified`、RabbitMQ ACL/TLS 和 application receipt 本阶段按版本化契约、Schema、正反例 fixture 和本地隔离状态机验收;真实 SaaS/MQ 联调延期第二阶段。
15. 固定15分钟授权、单次PUT、recording.uploaded事实及RabbitMQ persistent/confirm/mandatory入队按版本化契约、Schema、正反例fixture和本地隔离状态机验收;不申请SaaS上传会话、不等待verified/OSS ID;真实SaaS/MQ联调延期第二阶段。
以上范围不再列为待定。G0只冻结P1实际使用的Proto、事件payload、双AI模式及SaaS配置/调参、静态配置交接、录音授权、共享证书授权和运行阈值;后续功能有明确阶段,不再要求所有未来缺口同时关闭。本轮不创建Go骨架、数据库、broker或真实呼叫。[G0开发准备与契约冻结方案](G0开发准备与契约冻结提案_v0.1.md) D01–D10的方向、模式/许可/恢复机制及内部PoC初始profile已获用户确认;权威源发布、正式Proto及验证仍未完成,不能视为G0通过。本轮只同步文档,不修改上游Schema或创建代码。
OSS数据面已明确为**Agent→OSS直连上传**:D读取自身配置文件,经SDK提供本录音的临时TOKEN/受限目标与headers,A通过Unary领取后自行上传。D不接收或转发录音字节;既有SaaS业务会话/complete及verified仍经MQ,最终校验职责不变。每次TOKEN有效15分钟;过期或失败保留源文件,仅由调用方显式向D重新申请,不向SaaS取TOKEN,不自动续期或重试。此直传不改变Agent不直连SaaS的边界。
OSS数据面已明确为**Agent→OSS直连上传**:D读取自身配置文件,经SDK提供本录音的临时TOKEN/受限目标与headers,A通过Unary领取后自行上传。D不接收或转发录音字节;成功上传后由D可靠发布recording.uploaded;不申请SaaS会话,不等待verified或OSS ID,SaaS后续处理不属于本项目。每次TOKEN有效15分钟;过期或失败保留源文件,仅由调用方显式向D重新申请,不向SaaS取TOKEN,不自动续期或重试。此直传不改变Agent不直连SaaS的边界。
### 1.1 本次P1目标与完成标准
@@ -92,7 +92,7 @@ go-sip/
├── config/ # 无密钥的配置样例
├── contracts/ # 带来源与校验和的只读契约发布包
├── tests/ # 项目内夹具、协议 Mock、集成与故障注入
├── deploy/ # 独立镜像、Compose/systemd 与运维入口
├── deploys/ # 统一发布包、配置、systemd 与 Asterisk 部署入口
└── docs/ # 本项目方案、运行、验收和发布文档
```
@@ -111,7 +111,7 @@ Dispatcher配置包含MQ/SaaS受控引用、SQLite路径、两个Agent Endpoint
### 3.2 权威来源与独立拆仓
**本轮用户已确认SaaS↔Dispatcher全MQ:双方不再有任何HTTP请求/回调;每个D有全局唯一ID和独立接收Topic/队列。** 执行、控制、查询、补传、AI配置/授权、上传会话及complete/verified全部经MQ,详见[SaaS↔D契约](contracts/saas-dispatcher.md)与[计划§1.2/§8.2](plan-0918.md)。旧HTTP和旧租户路由是被替代的设计;新Schema/拓扑/身份生命周期/关联待冻结,旧源包及哈希不改,受影响实现/验收重新验证。
**本轮用户已确认SaaS↔Dispatcher全MQ:双方不再有任何HTTP请求/回调;每个D有全局唯一ID和独立接收Topic/队列。** 执行、控制、查询、补传、AI配置/授权及上传完成事实均经MQ;上传不申请SaaS会话、不等待verified/OSS ID,详见[SaaS↔D契约](contracts/saas-dispatcher.md)与[计划§1.2/§8.2](plan-0918.md)。旧HTTP和旧租户路由已删除;新Schema/拓扑/身份生命周期/关联已在项目内v2/v3包和本地证据中冻结,旧源包及哈希不改。
现有上游权威是《SaaS交互_OpenAPI与MQ契约规划_v0.1.md》(正文 v1.0)及经核验的发布产物。现有产物包括 `mq.schema.json`、`executor.openapi.yaml`、`cell-agent.openapi.yaml`、AI 配置及 SIP 管理相关契约。文件存在不代表完整覆盖:P0 必须逐条核对正文、Schema、状态语义和实现差异。
@@ -291,7 +291,7 @@ SaaS经目标D专用Topic,仅按一个 `call_id` 或 `source_command_id` 发
### 6.6 最终文字、事件与 DNC
Dispatcher对Agent稳定事实去重后,按 command/call/transcript_segment/recording 的实体与状态域事务持久化聚合版本,payload 是该域的快照;测试中的独立 SaaS 按同域合并。不能用全局最大版本丢掉低版本但独立的录音/attempt,也不能用 call.finished 覆盖 recording.ready。最终文字不会被迟到中间稿覆盖;当前基线同段 final 同内容幂等、异内容冲突,未来允许修订须经 G0 更新契约。文字失败必须出 transcript.failed,通话结束不等待所有后处理。
Dispatcher对Agent稳定事实去重后,按 command/call/transcript_segment/recording 的实体与状态域事务持久化聚合版本,payload 是该域的快照;测试中的独立 SaaS 按同域合并。不能用全局最大版本丢掉低版本但独立的录音/attempt,也不能用 call.finished 覆盖 recording.uploaded。最终文字不会被迟到中间稿覆盖;当前基线同段 final 同内容幂等、异内容冲突,未来允许修订须经 G0 更新契约。文字失败必须出 transcript.failed,通话结束不等待所有后处理。
客户实际说话与 AI 生成/发送/播放证据严格区分,旧轮次片段取消后不能冒充已播放。contact.opt_out 按获批业务判定及时发布,不等挂断;SaaS 持久禁发并枚举相关任务完成屏障。Agent 不能用未经确认的关键词替代业务判定。
@@ -339,11 +339,11 @@ Dispatcher对Agent稳定事实去重后,按 command/call/transcript_segment/re
**OSS配置存于D配置文件;A经R12向D领取临时上传TOKEN后直传OSS,不保存长期凭据。** D依据自身配置复用官方SDK提供原执行/对象所需TOKEN、目标、headers与期限;配置缺失/无效明确失败,不向SaaS获取配置/TOKEN,不用A本地bucket/长期AK兜底。TOKEN过期只接受A显式向D重新申请,配置格式及TOKEN/UploadGrant映射须核验,不猜字段,样例/日志不写真实凭据。
既有SaaS业务会话/资产登记、R13之后的complete及verified仍经MQ;SaaS最终独立校验职责不变,MQ不传D配置文件/长期凭据/TOKEN。D仅在持久校验SaaS verified及oss_id后写recording.ready outbox;PUT200/ETag或D本地HEAD不能替代。保留D签发能力,不将其误删为旧路径。对象定位/校验所需业务信息及R12/R13有界等待/恢复衔接待W01/W02/W11核验,不虚构HTTP或无限等待Unary。
R13报告原上传事实,D在同一事务中保存事实和recording.uploaded outbox。仅在persistent消息进入指定durable队列/绑定、mandatory无return且publisher confirm成功后确认通知交付;这不代表SaaS已消费或处理。MQ不传D配置文件、长期凭据或TOKEN。不申请SaaS业务会话,不实现complete/verified等待,不新增VERIFYING,不返回OSS ID或发recording.ready。D签发能力保留;上传通知恢复沿原upload_id和原消息身份,不重新PUT。
spool 按继承的测试 profile 在 70% 告警、80% 停止新接单,降至 60% 且依赖恢复后才恢复;为活动通话预留剩余录音空间。已 verified、ready 持久并确认发布、无已知恢复任务的本地已交接录音,测试至少保留 24h;未确认/失败文件不自动删。生产保留另行批准,不能在 confirm 后无条件删原始资产。
spool 按继承的测试 profile 在 70% 告警、80% 停止新接单,降至 60% 且依赖恢复后才恢复;为活动通话预留剩余录音空间。原上传事实已持久保存、recording.uploaded已可靠入队且无已知恢复任务的本地已交接录音,测试至少保留 24h;未确认/失败文件不自动删。生产保留另行批准,不能在 confirm 后无条件删原始资产。
`CALL_NOT_REGISTERED` 保留文件并等待;TOKEN失效只允许A显式向D重新申请,保持原recording/upload语义,不自动续期或制造新资产。仅接受受控 HTTPS 目标与约定 headers,禁止任意重定向/跨对象写入;verified 对象须防止旧签名覆盖。SaaS/对象侧独立读真实字节验证,不能信自报摘要或把 ETag 当 SHA-256。
授权缺失、失效或PUT失败时保留文件;仅接受A显式向D重新申请,保持原recording/upload绑定,不自动续期、重传或制造新资产。同一请求重放返回原授权(包括原到期时间),显式新请求最多一次PUT。仅接受受控HTTPS目标与约定headers,拒绝重定向/跨对象写入;校验实际发送文件的大小和SHA-256,不把ETag当SHA-256。预签名URL不是OSS原生强制一次性凭据;SaaS后续对象处理不作为本项目门禁。
Agent对已签名PUT使用标准库HTTP/约定headers,需要OSS API才用官方SDK,不自写签名或拿不必要长期凭据。文字也流式落文件,但实时transcript.updated/contact.opt_out仍经gRPC→Dispatcher→MQ及时回SaaS。文本OSS归档的授权/完成/引用尚缺契约,未冻结前明确未启用,不伪装录音;保留文字MQ链路不受此影响。补传按§6.5整体call/command范围。
+1 -1
View File
@@ -1,7 +1,7 @@
# OpenAPI 与 MQ 字段索引 v0.1
> 本文件为本轮从现有上游文件一次性生成的只读索引,不是第二份可手工维护的 Schema。原始引用和约束原样保留;它描述“文件现在是什么”,不代表已经与正文权威契约一致。
> 已逐字段和哈希确认:当前MQ的command_type/command_id与event_type/aggregate_*信封已对齐正文。仍待补齐8种事件的payload专属Schema/条件规则;详见《通信与事件数据交互_v0.1.md》。源文件更新须重新导入生成,不得只改索引。
> 已逐字段和哈希确认:当前MQ的command_type/command_id与event_type/aggregate_*信封已对齐正文。仍待补齐8种事件的payload专属Schema/条件规则;详见《通信与事件数据交互_v0.1.md》。源文件更新须重新导入生成,不得只改索引。该索引保留上游OpenAPI历史字段(包括旧上传会话/verified/oss_id),不代表当前MQ-only运行时;当前上传以recording.uploaded冻结方案为准。
普通构建/运行不读取父目录;P0须建立版本化契约包和可重复生成入口。本轮没有添加Python运行依赖或Go源码。
+19 -21
View File
@@ -14,7 +14,7 @@
精确字段号和枚举以 `proto/agent/v1/agent.proto` 为唯一源;本文不另造 protobuf。
**SaaS↔Dispatcher 边界已修订为 MQ-only**,详见 [SaaS↔Dispatcher 契约](./saas-dispatcher.md)。D 有全局唯一身份和独立接收 Topic;这不改变内部 Unary 或 Agent→OSS 直传。**OSS 配置由 D 配置文件维护,Agent 向 D 领取临时上传 TOKEN,SaaS 不再提供 OSS 配置/TOKEN。** D 的签发职责保留;本文“当前实现”仍不能证明配置文件/TOKEN 全部约束已接通,尤其 D 本地对象验证不能代替 SaaS MQ verified。本轮不改变 SaaS 最终校验归属,未改 Proto/代码。
**SaaS↔Dispatcher 边界已修订为 MQ-only**,详见 [SaaS↔Dispatcher 契约](./saas-dispatcher.md)。D 有全局唯一身份和独立接收 Topic;这不改变内部 Unary 或 Agent→OSS 直传。**OSS 配置由 D 配置文件维护,Agent 向 D 领取临时上传 TOKEN,SaaS 不再提供 OSS 配置/TOKEN。** D的签发职责保留;用户已将上传边界收缩为recording.uploaded可靠进入指定持久队列,不等待SaaS会话、verified或OSS ID,不新增VERIFYING。本文旧handler行为仅作差异记录,R13须改为以可靠入队完成,不以文档或Schema通过宣称已接通。
## 2. Service 方法与方向
@@ -30,7 +30,7 @@
| `QueryExecution` | Dispatcher → Agent | 用于超时/响应丢失后的对账 | Agent `rpc.Server` |
| `ReportExecutionEvent` | Agent → Dispatcher | 已接收、去重并生成 MQ outbox | Dispatcher `rpc.DispatcherEventServer` |
| `RequestUpload` | Agent → Dispatcher | 已接收并签发 OSS grant | Dispatcher `rpc.DispatcherUploadServer` |
| `CompleteUpload` | Agent → Dispatcher | 已验证 OSS 对象并生成 `recording.ready` outbox | Dispatcher `rpc.DispatcherUploadServer` |
| `CompleteUpload` | Agent → Dispatcher | 已持久保存上传事实并将 `recording.uploaded` 通知可靠入队 | Dispatcher `rpc.DispatcherUploadServer` |
`DispatcherServer` 对外只实现 `ReportExecutionEvent`、`RequestUpload`、`CompleteUpload`;其它 RPC 在 Dispatcher listener 上返回 `UNIMPLEMENTED`。Agent `rpc.Server` 虽实现完整 generated service,但其 upload handler 在非 `mock` 模式明确返回 `UNIMPLEMENTED`。
@@ -276,19 +276,19 @@ Dispatcher 侧额外要求:
当前 Dispatcher 验证:Agent/Cell/operation/idempotency 元数据、完整 execution/tenant binding、合法 `tenant_key`、asset ID、upload ID、正数文件大小和 SHA-256;超过 OSS 最大文件大小返回 `RESOURCE_EXHAUSTED`。
成功响应:`OperationReceipt`、`UploadGrant`、`state`。目标是 D 读取自身 OSS 配置文件并向 A 提供临时 TOKEN/受限上传信息;以下记录当前 handler,不代表配置入口、TOKEN 形态和新版业务会话约束已全部验收:
成功响应:`OperationReceipt`、`UploadGrant`、`state`。D读取严格JSON配置文件,复用官方SDK提供15分钟预签名PUT;本地签发、单次上传及通知恢复证据见[上传验证](../evidence/20260921-mq-upload-progress.md)。当前handler:
- 以 `tenant_key + "\\0" + execution_id + "\\0" + asset_id` 的 SHA-256 hex 生成 object key,并加配置的 key prefix;
- 将 binding、asset、grant、object key、state 持久到 Dispatcher SQLite;
- grant 的过期时间由 OSS client 配置提供;
- 同 upload ID 同 binding/asset 返回原 grant;绑定不同返回冲突;
- 过期 grant 只有在显式再次 `RequestUpload` 时才替换,不自动续期。目标流程同样由 Agent 显式向 D 重新领取 TOKEN;D 使用自身配置,不向 SaaS 申请 TOKEN,不改变原资产/会话。
- grant有效期固定15分钟,不允许通过配置改变;
- 同upload ID、同operation ID及原请求正文返回原grant,包括原到期时间;绑定或正文不同返回冲突;
- 重新签发必须使用显式新请求身份,保持原资产及对象绑定;不因原请求重放自动续期,不向SaaS申请TOKEN。
新请求当前返回 `UPLOAD_STATE_REQUESTED`;持久层状态为 `granted`。`UPLOADING` 枚举存在,但当前 Dispatcher handler 不把 Agent 的 PUT 过程映射为该状态。
目标流程为 **D 依据自身配置文件向 A 提供临时上传 TOKEN,不向 SaaS 申请 OSS 配置/TOKEN**。D 侧配置缺失/无效时明确失败,不切换配置源;长期凭据不交给 A,不写入示例、日志或证据。TOKEN 的精确形态、SDK 能力及与 `UploadGrant` 的映射须核验冻结,不能只把现有字段改称 TOKEN 就宣称完成。
目标流程为 **D 依据自身配置文件向 A 提供临时上传 TOKEN,不向 SaaS 申请 OSS 配置/TOKEN**。D 侧配置缺失/无效时明确失败,不切换配置源;长期凭据不交给 A,不写入示例、日志或证据。当前TOKEN形态为官方SDK生成的受限预签名PUT信息,映射到UploadGrant;它不是OSS原生强制一次性凭据,Agent通过持久尝试状态保证每次授权尝试最多一次PUT。
既有 SaaS 业务会话/资产登记仍经 MQ,D 关联原租户/执行/资产后才交付相应上传信息;这不是由 SaaS 签 TOKEN。业务响应可能晚于 RPC deadline,W02/W11 仍须冻结 pending、有界等待、原操作重取及最终结果,不无限阻塞 Unary、不因超时另造 upload ID。当前 D 的本地签发能力保留复用,禁止删除后改为等待 SaaS 下发配置。本段不新增 RPC/Proto 字段。
用户已收缩上传职责:**R12不申请SaaS会话,不等待SaaS回复**。D校验原租户/执行/资产后按自身配置提供15分钟SDK预签名PUT,保留原upload_id;过期仅显式向D重新申请。签发能力保留复用,不引入第二配置源或新的上传控制协议。
### 6.3 Agent → OSS 直接上传
@@ -296,29 +296,27 @@ Agent 获得 grant 后使用 `internal/agent.UploadClient.UploadFile`:
- 只允许 HTTPS;除非显式配置,否则不允许 HTTP;
- 可限制目标 host;禁止 grant 注入 `Host` 和 `Content-Length`;
- 预先读取文件计算 SHA-256,再按 `Content-Length` PUT;
- 使用同一文件描述符预读校验,再按`Content-Length` PUT并对实际发送字节计数和计算SHA-256;文件变化明确失败;
- 禁止重定向;2xx 才算 PUT 成功;
- 返回 HTTP status、文件大小、SHA-256、ETag;
- 文件内容不经过 Dispatcher,Agent 不把源文件删除或移动。
PUT 成功不等于 OSS verified,也不等于 SaaS 已应用。
PUT成功仅是文件上传事实,还须由D将通知可靠送入MQ;既不等待也不声称SaaS已应用。
### 6.4 `CompleteUpload`
请求:`meta`、原 `ExecutionBinding`、原 `AssetDescriptor`、`upload_id`、`uploaded_size_bytes`、`uploaded_checksum_sha256`。
Dispatcher 当前执行:
Dispatcher当前执行:
1. 校验 upload ID 对应的 binding/asset 完全一致;
2. 校验上传大小、SHA-256 和 grant `max_bytes`;
3. 校验 grant 未过期;
4. 调用 OSS client 独立验证 object;
5. 只允许 `AssetKind=RECORDING` 进入 verified recording 路径;
6. 在一个 SQLite 事务内更新 upload 为 completed、保存 `oss_id` 并写入 `recording.ready` outbox。
1. 校验原upload ID、完整binding/asset、实际上传大小、SHA-256和grant的max_bytes;只接受录音事实。
2. 使用签发时持久保存的bucket/object key,不因当前配置改变对象位置。
3. 在同一SQLite事务内保存原上传事实及固定event_id的recording.uploaded outbox。
4. 未确认入队时返回Unavailable并保留uploaded状态;publisher确认原通知可靠入队后,重复R13返回ACCEPTED及COMPLETED。
响应为 `OperationReceipt`、`state=COMPLETED`、`oss_id`。同 upload ID 同 OSS ID 的重复 complete 返回 `ACCEPTED`;未知 upload 返回 `NOT_FOUND`;对象校验失败返回 `FAILED_PRECONDITION` 且标记可重试。
完成依据是persistent消息进入指定durable队列/绑定、mandatory无return且publisher confirm成功。仅写本地outbox不算交付完成。通知恢复不要求重取TOKEN或重新PUT,也不因原grant此时过期而重传已上传的文件。
以上是旧本地验证事实。**目标流程必须由 D 经 MQ 提交 complete,SaaS 独立验证对象后经本 D 专用 Topic 返回 verified/oss_id;D 校验原请求、租户、资产和会话并持久化后,才可记完成及写 recording.ready outbox。** D 本地 HEAD、PUT 2xx 或 broker confirm 不能替代 SaaS verified。等待中/超时/重复响应及 A 获取最终结果的 Unary 衔接由 W02/W11 冻结,尚未完成;不凭此说明宣称当前 handler 已符合目标。
旧OSS HEAD/verified及oss_id响应已删除,Proto保留原字段编号和名称为reserved。不等待SaaS会话或消费回复,不新增VERIFYING、不发recording.ready;完成不表示SaaS已处理。当前MQ wire为2.0,运行使用v3契约包(AI JCS摘要修订),上传字段沿用已批准的[v2冻结方案](mq-only-v2-freeze-proposal.md)。本地MQ无绑定、确认丢失、重启及原通知恢复已有[证据](../evidence/20260921-mq-upload-progress.md),不等于真实OSS/SaaS联调通过。
## 7. 错误、幂等与未知结果
@@ -347,8 +345,8 @@ Agent 侧写操作的内存 operation key 为:
1. `GetBootstrap`、`SetAdmissionState` 虽有 handler,但当前 Dispatcher 启动流程没有调用完整 bootstrap/admission 编排。
2. `Execute` 的当前 RPC 实现只证明准备/幂等/配置校验,不证明 ARI/RTP/SIP 已由该 RPC 直接完成。
3. D 配置文件→临时 TOKEN→A 直传的完整接线/约束,以及 SaaS 业务会话/complete/verified 的 MQ 协调与 R12/R13 有界衔接仍待核验;D 已有签发能力保留复用,但本地对象验证不能替代 SaaS verified。旧 `recording-uploads` HTTP 方案继续废弃,不开发 client。
4. SaaS AI 配置/授权的 MQ 请求响应与持久绑定尚未接通;当前快照/授权由启动输入提供。旧 AI version GET 已废弃,不能作为后续实现方向。
3. D配置文件→15分钟TOKEN→A直传及recording.uploaded可靠入队/R13完成已接通并有本地MQ证据。保留D签发,删除旧ready/oss_id完成路径;不开发SaaS上传会话或verified往返,不新增VERIFYING,旧上传HTTP继续废弃。
4. SaaS AI 配置/授权的 MQ 请求响应与持久绑定已有本地RabbitMQ/SQLite恢复证据;Agent动态交付仍受现有Unary快照载体边界约束。旧 AI version GET 已废弃,不能作为后续实现方向。
5. 不能把 generated service 中的全量方法数当作每个 listener 都可调用;实际 listener 能力以第 2 节和 `DispatcherServer` 代码为准。
## 9. 依据文件
@@ -0,0 +1,112 @@
# MQ-only v2 与 Dispatcher OSS 配置:已确认合同
状态:用户已确认本文件的身份、纯Topic、配置、消息及查询结构;随后通过目标修订确认**上传只负责可靠入队**,并单独确认 `recording.uploaded` 名称/字段及R13完成边界。旧上传会话/verified等待方案被替代,不新增VERIFYING。机读v2包及AI JCS修订后的v3包已生成并通过离线测试;本地运行切换、RabbitMQ往返和联合恢复已有证据,真实SaaS/生产验收仍不在本轮。
机读来源:`contracts/upstream/2026-09-21-p1-v2/`。`manifest.json`记录项目内来源、旧源文件哈希与新文件哈希;不是外部SaaS签收。旧v1包保持原样,不提供运行时v1/HTTP回退。当前目标由本会话Agent独立执行,不启动子Agent。
## 1. Topic 与租户边界
用户已选择**只用Topic,不增加headers exchange**:保留tenant_key原值;每D/租户独立接收队列;拒绝按点号分词后为`*`或`#`的独立词段。不能广播后过滤、编码/截断租户标识或换别名绕过。
| 资源 | 已确认名称/规则 |
| --- | --- |
| D接收exchange | `agent-call.dispatchers.v2`,durable topic |
| SaaS接收exchange | `agent-call.saas.v2`,durable topic |
| 死信exchange | `agent-call.dead-letter.v2`,durable topic |
| D/租户接收队列 | `agent-call.d.<dispatcher_id>.t.<tenant_key>.v2`,durable |
| 对应死信队列 | `agent-call.d.<dispatcher_id>.t.<tenant_key>.dlq.v2`,durable |
| SaaS→D routing key | `d.<dispatcher_id>.t.<tenant_key>.in`,精确绑定 |
| D→SaaS routing key | `d.<dispatcher_id>.t.<tenant_key>.out`,精确绑定 |
| SaaS接收队列(本地合同) | `agent-call.saas.events.v2`,持久绑定获配置的D/租户出站键 |
| D身份占用队列 | `agent-call.d.<dispatcher_id>.owner.v2`,exclusive、非持久,只锁身份 |
36字节UUID下最长死信队列固定部分59字节,tenant_key上限为**196个UTF-8字节**;仍逐一检查完整资源名255字节限制。Schema的字符数校验不替代运行时字节校验。非法/超长输入明确拒绝并保留源任务,不静默修正。
## 2. Dispatcher身份与恢复
- 配置文件提供稳定、规范小写UUID v4。部署时独立生成,不复制示例ID;启动时不自动生成/替换。
- 初次使用与SQLite绑定,后续必须一致;更换ID沿用旧DB时拒绝,不清库或抹掉未知执行。
- `dispatcher_epoch`是运行代次,不是稳定ID。请求、消息、资产与执行保持原归属。
- 同一broker上的exclusive身份队列拒绝重复ID;业务队列必须持久且不能exclusive。连接断开拒新准入,不清未知占用。
- 不宣称跨不同broker存在全局注册服务,不实现HA或自动换D。
## 3. 消息集合与关联
消息为persistent JSON、严格Schema、最大256KiB;未知字段拒绝,不新增任务mode字段。
### 3.1 命令与事件
- 命令保留`command_type`、`command_id`、租户、trace、issued/not_after及payload,新增`dispatcher_id`,`schema_version=2.0`。
- 命令类型:`call.execute`、`task.control`、`call.replay`、`command.replay`。旧HTTP body的重复command_id归并到既有MQ信封,不维护两个独立命令身份。task_id/call_id/source_command_id来自原业务目标。
- 事件保留`event_type`、`event_id`、租户、trace、occurred_at、aggregate和payload,新增来源`dispatcher_id`,版本2.0。
- v2事件:`command.result`、`call.status`、`transcript.updated`、`call.finished`、`recording.uploaded`、`recording.failed`、`transcript.failed`、`contact.opt_out`。
- **recording.uploaded取代本项目的recording.ready**,不冒用旧verified语义。其payload固定为`call_id`、`recording_id`、`upload_id`、`bucket`、`object_key`、`format`、`channels`、`sample_rate_hz`、`duration_ms`、`size_bytes`、`checksum_sha256`,不含TOKEN、密钥、签名URL或SaaS OSS ID。
- 控制accepted不等于applied;整体补传保持原事件身份、版本和内容,不能重新投执行命令。
### 3.2 必要请求/响应
| 请求 | 方向 | 响应 |
| --- | --- | --- |
| `command.query` | SaaS→D | `command.query.result` |
| `call.query` | SaaS→D | `call.query.result` |
| `ai.config.request` | D→SaaS | `ai.config.result` |
上传不在此表:**不存在upload-session、complete/verified请求响应或等待SaaS回复的上传控制流程。**
公共请求字段:schema_version、message_type、message_id、dispatcher_id、tenant_id、tenant_key、trace_id、issued_at、not_after、payload。
响应使用对应公共身份字段,增加correlation_id、status、reason_code,**不带not_after**;correlation_id固定指原请求,dispatcher_id始终是业务所属D。status为ok/pending/rejected;reason_code严格按Schema分支校验。不可路由、超时、已受理但结果未知与业务拒绝分开,不把confirm当业务已应用。
本地服务请求30秒接收期限,不覆盖上游命令期限,也不让已受理结果失效。已知重复返回原决定;未知且过期拒绝;重启/迟到/乱序保留原关联,不另造ID重做业务。
### 3.3 查询结构
用户已批准补齐旧Call中的空白object定义:
- attempts:已有call.status事件集合。
- transcript.events:已有transcript.updated/transcript.failed事件集合。
- recordings:recording.uploaded/recording.failed事件集合。
- delivery:pending、retry、dispatching、published四种已有投递状态的非负计数。
保留原顶层通话字段,只读取持久事实,不伪造通话/资产结果。快照受256KiB限制,超限明确失败,不截断后伪称完整。AI快照/授权沿用原严格配置Schema;原OpenAPI receipt/config的allOf组合展平为同一闭合对象,不放宽字段。
## 4. D配置文件与临时TOKEN
D通过`--config <path>`读取严格JSON,未知字段、重复键、缺失或无效值报错;移除旧D OSS flags/隐式环境覆盖,不保留并行配置源。
```json
{
"schema_version": "1.0",
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
"oss": {
"endpoint": "https://oss.example.invalid",
"region": "example-region",
"bucket": "example-bucket",
"object_prefix": "recordings",
"access_key_id_env": "DISPATCHER_OSS_ACCESS_KEY_ID",
"access_key_secret_env": "DISPATCHER_OSS_ACCESS_KEY_SECRET"
}
}
```
这是字段示例,不是可部署的凭据/ID。文件明确引用受控凭据变量;环境仅提供这些凭据,不覆盖endpoint/bucket等配置。监听、TLS、持久目录等既有部署选项不全部搬迁。
D复用官方SDK的预签名PUT及必要headers,沿用UploadGrant作为临时TOKEN交付形式,不另造JWT/STS服务或签名算法。有效期固定900秒;A每次获准尝试只PUT一次,失败/过期保留文件,显式重新RequestUpload才换授权;不启用自动重传,不宣称OSS原生强制一次性。
## 5. R12/R13与上传通知交付边界
1. R12由D读取配置、校验原租户/执行/资产并提供临时TOKEN,**不向SaaS申请会话或授权**。
2. Agent直接PUT对象,保存真实结果与本地恢复记录,再经R13报告原资产、upload_id、大小和摘要;文件字节不经过D。
3. D校验与原grant/资产一致,把上传事实和固定event_id的recording.uploaded outbox同事务保存。重复R13不得生成第二资产/通知身份。
4. 通知以persistent消息发送至正确的durable队列/绑定,启用mandatory并确认没有return,再取得publisher confirm;只有满足这些条件才记交付完成。
5. **R13完成仅表示本项目已把事实交付MQ**,不是SaaS已处理。不返回虚构OSS ID,不发recording.ready,不新增VERIFYING或最终校验查询协议;原实现中的OSS ID返回/等待路径随实现删除。
6. broker断连、不可路由、confirm丢失或崩溃时保留持久记录和原event_id,恢复通知交付。仅写本地outbox、发到无队列exchange或未确认时均不能算完成。
7. 元信息重报/消息重投不触发第二次PUT;通知确认前保留所需恢复信息及源文件,不靠SaaS消费回复决定完成。AI/控制等必要请求响应不受此收缩影响。
## 6. 验证与事实边界
- TDD;v2 Schema正反例、来源/文件哈希、拓扑与Go路由规则一致性检查。旧v1不改。
- 运行时须验证指定本地持久队列实际接收、不可路由/confirm丢失/重启恢复及无SaaS消费者也能完成交付;离线Schema通过不替代这些检查。
- 基线总覆盖率32.1%,排除生成代码诊断值44.2%,均未达65%;不能只挑新增文件宣称整体达标。
- 不接真实云/供应商/SaaS,不真实拨号;部署诊断缺失不能伪造通过。
- RabbitMQ官方Topic/队列规则参考:https://www.rabbitmq.com/docs/exchanges 、https://www.rabbitmq.com/docs/queues 。公开检索确认点号分词、通配词段与exclusive单连接;直接抓取受工具fake-IP检查阻止,未宣称完整在线文档或PoC已验收。
+35 -38
View File
@@ -2,21 +2,21 @@
## 1. 适用范围与事实等级
**用户已确认:RabbitMQ 是 SaaS 与 Dispatcher 的唯一交互通道,双方之间禁止任何 HTTP 请求或回调。每个 Dispatcher 都有独立、全局唯一的 ID,并通过各自独立的专用 Topic 接收事件。** 本规则覆盖执行、控制、查询、补传、AI 配置与授权、录音上传会话及完成验证,不保留 HTTP 特例或回退。
**用户已确认:RabbitMQ 是 SaaS 与 Dispatcher 的唯一交互通道,双方之间禁止任何 HTTP 请求或回调。每个 Dispatcher 都有独立、全局唯一的 ID,并通过各自独立的专用 Topic 接收事件。** 本规则覆盖执行、控制、查询、补传、AI 配置与授权、recording.uploaded上传事实通知,不保留HTTP特例或回退;上传不等待SaaS会话或校验回复。
**OSS 补充确认:OSS 相关配置存于 Dispatcher 配置文件;Agent 经 Unary 向 Dispatcher 领取临时上传 TOKEN 后直传 OSS,不持有长期凭据。SaaS 不再下发 OSS 配置或上传 TOKEN。此次只调整配置/TOKEN 来源,上传完成后的 SaaS 独立校验和 MQ verified 职责保持不变。**
**OSS 补充确认:OSS 相关配置存于 Dispatcher 配置文件;Agent 经 Unary 向 Dispatcher 领取临时上传 TOKEN 后直传 OSS,不持有长期凭据。SaaS 不再下发 OSS 配置或上传 TOKEN。用户随后修订目标:本项目只保证上传事实可靠进入指定持久MQ队列,不关心SaaS后续处理;不等待上传会话、verified或OSS ID,不新增VERIFYING。**
本文区分三种事实:
| 层级 | 本次状态 | 使用边界 |
| --- | --- | --- |
| 已确认设计 | MQ-only、Dispatcher 唯一身份、独立 Topic | 后续设计与实现必须遵守 |
| 待冻结的消息契约 | 身份分配/持久化、Topic 命名、消息类型、关联字段、错误与超时规则 | 见 §2、§5、§6;不能据中文语义自行拼 JSON 或给旧 Schema 加字段 |
| 新版项目内契约 | `2026-09-21-p1-v2`身份/Topic/消息/配置/正反例及哈希,AI JCS修订后的v3包 | 以[mq-only-v2冻结方案](mq-only-v2-freeze-proposal.md)及机读包为准;本地运行往返已有证据,外部SaaS签收仍不在本轮 |
| 现有实现/旧包 | 下列旧字段、路由及代码事实 | 仅用于识别差异,不代表新设计已实现或通过验收 |
当前固定包为 `contracts/upstream/2026-09-19-p1-v1/`(`contracts.SourceCommit = 2026-09-19-p1-v1`)。其中的 HTTP OpenAPI 与仅按租户路由的 MQ 拓扑**不再是目标方案**。旧包及其哈希保持不变;W01 须发布新版本、严格 Schema、拓扑及正反例,不能手改旧包、生成字段索引或通过放宽 `additionalProperties` 绕过冻结。
本轮只纠正文档,不修改代码、Proto 或 Schema,也不宣称新 MQ 闭环已通过。现有实现事实沿用此前核验记录,受影响部分必须按新基线重新验证。
当前已完成本地实现阶段;v2机读包、AI摘要修订后的v3包、离线正反例、路由规则及RabbitMQ运行往返均有证据。下文§3–§4的v1字段/代码描述仅为迁移差异,不能作为v2/v3接入要求;运行证据不等于外部SaaS签收。
## 2. 通信拓扑、Dispatcher 身份与交付语义
@@ -27,17 +27,17 @@
| SaaS 下发执行、控制、查询、补传 | SaaS → RabbitMQ → 指定 Dispatcher 专用 Topic/队列 | 持久受理不等于执行完成;响应仍经 MQ |
| Dispatcher 回传结果、查询响应和业务事件 | Dispatcher → RabbitMQ → SaaS 专用订阅 | 能识别来源 Dispatcher、租户、原请求及业务对象 |
| Dispatcher 获取 AI 配置/授权 | Dispatcher → RabbitMQ → SaaS;SaaS → RabbitMQ → 原 Dispatcher 专用订阅 | 固定租户和不可变版本,响应不能被其它 Dispatcher 消费 |
| 业务上传会话、complete/verified | Dispatcher ↔ RabbitMQ ↔ SaaS | 只传业务会话/对象元信息与验证结果;不下发 OSS 配置或 TOKEN,不传录音字节 |
| 上传完成通知 | Dispatcher → RabbitMQ指定持久队列 | recording.uploaded仅含事实元信息;入队即完成本项目交付,不等待SaaS消费/会话/verified/OSS ID |
| 临时上传 TOKEN 领取/显式重新申请 | Agent ↔ Unary ↔ Dispatcher | D 依据自身配置文件提供受限 TOKEN/上传目标;不向 SaaS 申请 TOKEN |
| Dispatcher ↔ Agent | 既有 Unary gRPC | 不改为内部 MQ,也不让 Agent 直连 SaaS |
| Agent → OSS | 受限目标上的直接 PUT | 保留 HTTP(S) 对象上传;禁止的是 SaaS↔Dispatcher HTTP,不是 OSS/ARI/供应商协议或 gRPC 的 HTTP/2 |
### 2.2 全局唯一身份与独立 Topic
- `dispatcher_id` 在本文中是**逻辑身份名称,尚不是旧 MQ Schema 或 Proto 已有字段**。每个 Dispatcher 的 ID 必须独立、全局不重复;不能拿租户 ID、Agent ID、Cell ID、地址或启动代次代替。
- Dispatcher 身份与 `dispatcher_epoch` 分开:前者识别 Dispatcher,后者用于一次运行所有权/会话的 fencing。正常重启、恢复时如何保持身份及拒绝重复身份,须在 W01 冻结并由 W05 验证,不因 epoch 改变就丢弃原消息、执行或资产归属。
- `dispatcher_id` 是当前v2/v3运行信封和路由中的独立全局身份;每个 Dispatcher 的 ID 必须独立、全局不重复,不能拿租户 ID、Agent ID、Cell ID、地址或启动代次代替。
- Dispatcher 身份与 `dispatcher_epoch` 分开:前者识别 Dispatcher,后者用于一次运行所有权/会话的 fencing。身份持久绑定、重复身份占用、重启恢复及失效会话拒绝已有本地测试;不因 epoch 改变就丢弃原消息、执行或资产归属。
- 每个 Dispatcher 有独立的接收 Topic 及对应队列/绑定;多个 Dispatcher **不能共用一条接收队列竞争消费指定目标的消息,也不能全部订阅同一广播 Topic 后仅靠正文过滤**。
- RabbitMQ 的 Topic 订阅由 exchange、routing key、queue 和 binding 表达;具体名称、类型、绑定格式及 ID 在信封/属性中的位置随新版本冻结。本轮不另造一套可直接部署的命名格式。
- RabbitMQ 的 Topic 订阅由 exchange、routing key、queue 和 binding 表达;当前固定为 `agent-call.dispatchers.v2`、`agent-call.saas.v2`、`agent-call.dead-letter.v2` 及每个 Dispatcher/租户的独立队列和精确路由键,字段以v2/v3机读契约包为准。
- SaaS 发给 D1 的命令、配置、授权和上传结果,只能进入 D1 的专用接收路径;D2 的路径与之独立。D1 发出的响应/事件须能回溯 D1 与原请求。SaaS 订阅布局亦由同一版契约定义,不假定现有共享结果队列已满足新约束。
- 独立 Dispatcher 路由不替代租户隔离:保留租户独立队列、有界窗口、原值 `tenant_key` 和复合幂等语义;新拓扑必须同时区分 Dispatcher 与租户,不能退化为 Dispatcher 内所有租户共享无界队列。
- `tenant_key` 不清洗、编码或截断。旧布局的 224 UTF-8 字节预算不能在加上 Dispatcher 身份后直接照搬;W01 须校验完整 routing key/queue 名长度及分隔符、通配符边界,超限拒绝发布并保留源任务,不改变既有租户标识。
@@ -48,10 +48,10 @@ P1 仍只运行一个单活 Dispatcher。现在必须在合同及本地路由测
1. 所有请求和响应都走 MQ;异步响应必须关联原请求、目标/来源 Dispatcher、原租户及业务对象。精确键名、关联方式、消息枚举、错误与期限在 W01 冻结;`trace_id` 不能代替业务幂等身份。
2. 发送意图/业务变更与 outbox 同事务;接收方持久 inbox 和处理状态后才 ACK。相同业务请求的重投返回原决定,同身份异内容冲突,不能生成第二次拨号或上传资产。
3. publisher confirm、消费者 ACK、业务 accepted、控制 applied 和 SaaS verified 各自独立。confirm 只说明 broker 接收,不等于对端已应用;接收 ACK 不能代替业务响应。
3. publisher confirm、消费者ACK、业务accepted和控制applied各自独立;上传只验证可靠入队,不增加SaaS verified条件。confirm 只说明 broker 接收,不等于对端已应用;接收 ACK 不能代替业务响应。
4. 响应重复、乱序、迟到、丢失和重启后恢复必须按原关联处理;响应等待有界,不跨网络持有 SQLite 写事务。超时表示未获确定结果,不等于业务失败,不允许 HTTP 查询兜底、换 ID 重拨或静默换 Dispatcher。
5. 队列满、无绑定/不可路由、broker 断连必须可见并保留原消息。不能通过 confirm 单独认定路由成功;须覆盖 mandatory/return 和实际目标消费证据。
6. MQ 往返响应不意味着新增一套任意 application receipt 协议。已有业务结果和 verified 语义保留;需要补齐的响应消息必须进入版本化契约,不能借现有八类业务事件自由透传。
5. 队列满、无绑定/不可路由、broker 断连必须可见并保留原消息。不能通过 confirm 单独认定路由成功;须覆盖 mandatory/return 和指定持久队列接收证据;上传无需SaaS消费者回复。
6. MQ 往返响应不意味着新增一套任意 application receipt 协议。已有控制等业务结果保留;上传会话/verified往返已移出本项目;需要补齐的响应消息必须进入版本化契约,不能借现有八类业务事件自由透传。
## 3. RabbitMQ 执行命令:旧基线与待改项
@@ -66,7 +66,7 @@ P1 仍只运行一个单活 Dispatcher。现在必须在合同及本地路由测
| dead-letter exchange | `agent-call.dead-letter.v1`,durable `topic` | 恢复必须保留原 Dispatcher、租户和消息身份 |
| 默认 prefetch | `1` | 保持有界消费;不是多 Dispatcher 隔离证明 |
旧实现按消费租户声明 command queue 和 `.dlq.v1`,SaaS 结果队列基线为 `agent-call.saas.events.v1`、binding `agent-call.#`。这些名称记录旧包事实,不构成新拓扑批准。
旧实现按消费租户声明 command queue 和 `.dlq.v1`,SaaS 结果队列基线为 `agent-call.saas.events.v1`、binding `agent-call.#`。这些名称仅记录旧包事实,不构成新拓扑批准;当前运行使用v2/v3精确 Dispatcher/租户路由。
### 3.2 旧 `call.execute` 外壳
@@ -156,7 +156,7 @@ P1 仍只运行一个单活 Dispatcher。现在必须在合同及本地路由测
| `transcript.updated` | 实时文字;不得改名 `call.transcript` |
| `transcript.failed` | 当前映射为 `aggregate_type=transcript`,但 Schema 要求 `transcript_segment`,该路径阻塞 |
| `contact.opt_out` | 对应 Agent fact 经 Dispatcher 校验后生成 |
| `recording.ready` | 旧 `CompleteUpload` 本地对象验证后同事务写完成状态/outbox;不等于 SaaS MQ verified |
| `recording.uploaded` | 当前上传事实;D同事务保存事实及outbox,可靠进入指定持久队列后完成本项目交付,不代表SaaS已处理 |
| `recording.failed` | Schema 已定义,当前无对应 FactKind/生成路径 |
`RECORDING_PROGRESS` 只保存 fact,不发布 MQ 事件。上述已知实现差异不因本次文档改写而消失。
@@ -176,7 +176,7 @@ P1 仍只运行一个单活 Dispatcher。现在必须在合同及本地路由测
| `transcript.failed` | `call_id`, `reason_code`, `retryable` |
| `contact.opt_out` | `call_id`, `task_id`, `task_item_id`, `requested_at` |
新设计中 `recording.ready` 只能在 D 收到并持久校验 SaaS 的 MQ verified 结果及 `oss_id` 后发布;PUT 2xx、ETag、本地路径或 D 单独 HEAD 成功都不替代该结果。
v2已用`recording.uploaded`取代本项目的`recording.ready`;不得返回虚构OSS ID或等待SaaS verified。新字段与可靠入队边界见§6.1,旧表不作为v2校验依据。
### 4.4 Outbox 交付
@@ -188,7 +188,7 @@ P1 仍只运行一个单活 Dispatcher。现在必须在合同及本地路由测
### 5.1 待冻结内容
以下只定义已确认业务语义,**消息类型名、信封字段、响应/错误枚举尚未发布**。旧 HTTP header、URL 和状态码不能直接作为 MQ 合同,也不能塞进旧 `call.execute` 或宽松 metadata 中。
消息类型、信封和响应枚举以已确认的v2机读包及[mq-only-v2合同](mq-only-v2-freeze-proposal.md)为准。旧HTTP header、URL和状态码不是MQ合同,不能塞进旧call.execute或宽松metadata中。
### 5.2 控制任务
@@ -202,27 +202,24 @@ SaaS 将 pause/resume/stop 控制发到目标 Dispatcher 专用 Topic。保留
仅允许以 `call_id` 或 `source_command_id` 请求整体业务结果补传,不增加 task/execution 范围或局部筛选。固定受理截止点,重发原事件 ID/内容/版本,实时优先、分批有界;补传自身结果不能递归进入集合。
补传不是把 `call.execute` 重新发布来重新执行。旧 HTTP source-command replay 当前重发原命令的行为不满足目标整体结果补传,须列入 W12 修正;不能因改为 MQ 就保留这一错误语义。
补传不是把 `call.execute` 重新发布来重新执行。当前MQ replay只重送已持久的原业务事实或原命令回执,重复、迟到和重启沿原消息身份恢复,不创建任务、不拨号、不新建资产。
### 5.5 已废弃 HTTP 入口的处理
`internal/control/http.go` 当前存在控制、命令查询/补传 handler,通话查询/补传路由固定返回 `404`。这些只是旧实现事实,**不再是可接入或待扩展的 SaaS 接口**。W05/W12 应移除这些 SaaS HTTP 业务入口及相关部署说明,不保留兼容层、并行双通道或 HTTP 兜底。本轮未改代码,不能宣称入口已移除。
旧`internal/control/` HTTP业务实现及测试、Dispatcher HTTP启动路径、CLI参数和环境配置均已删除。执行、控制、查询、整体补传和AI配置/授权已有MQ本地往返、重复、错目标、迟到/重启恢复证据;不以HTTP删除代替外部SaaS验收。
## 6. Dispatcher → SaaS:AI 配置与上传业务协调经 MQ
### 6.1 Dispatcher 配置、临时 TOKEN 与 complete/verified
### 6.1 Dispatcher配置、临时TOKEN与recording.uploaded
1. **OSS 配置唯一来源是 D 的配置文件**,包括所需服务地址、bucket、对象路径规则及签发授权所需的受控凭据配置/引用。配置文件格式及具体字段沿现有能力核验后冻结,本轮不新增猜测的配置键,也不在文档/样例/源码/日志中写实际密钥或完整 TOKEN。配置缺失或无效须明确失败,不改向 SaaS 取配置,不用 Agent 本地配置兜底。
2. Agent 经 R12 向 D 领取临时上传 TOKEN。D 校验并持久关联原租户/执行/资产,依据自身配置复用官方 SDK 提供仅本对象可用、有有效期/方法/大小约束的 TOKEN 及必要上传目标信息,经 Unary 返回 Agent。Agent 不取得 D 的长期凭据或完整配置文件。精确 TOKEN 形态及与现有 `UploadGrant` 的映射待核验,不假定某个 SDK/Proto 已满足全部约束。
3. **业务上传会话与 TOKEN 签发分开**:既有 SaaS 业务会话/资产登记语义仍经 MQ,响应回原 D 专用 Topic;但该响应不再承担 OSS 配置或 TOKEN 的来源。`upload_id`、对象引用及会话/资产关联按新版合同冻结,不因 TOKEN 过期另造资产,也不新加一套未批准的登记协议。
4. Agent 直接 PUT 文件到 OSS,经 R13 只提交原资产/会话、对象引用、大小和 SHA-256 等完成元信息。D 经 MQ 提交 complete,SaaS 仍独立验证对象后经 MQ 返回 verified、`oss_id` 或明确失败;D 持久校验 verified 后同事务写资产状态和 `recording.ready` outbox。
5. TOKEN 过期/失效由 Agent 显式向 D 重新申请,D 仍按自身配置提供,不向 SaaS 申请 TOKEN,不自动续期/重试。MQ 响应丢失/重复沿原资产、请求和 `upload_id` 恢复;未获 SaaS verified 保留待完成状态和文件,不提前 ready。
1. OSS配置唯一来自D严格JSON配置文件;A经R12取得D用官方SDK提供的15分钟预签名PUT及headers,不持长期凭据,不向SaaS申请会话或授权。字段已在v2配置Schema冻结。
2. A仅执行一次PUT并持久记录结果,R13只报告原upload_id、binding、资产、大小与摘要;D校验后将事实和固定event_id的outbox同事务保存。
3. D发布persistent的`recording.uploaded`到正确durable队列/绑定,启用mandatory并处理return。收到publisher confirm且确认未被退回,才能将本次交付记为完成;只写本地outbox或无队列的exchange不算成功。
4. 通知payload固定为call_id、recording_id、upload_id、bucket、object_key、format、channels、sample_rate_hz、duration_ms、size_bytes、checksum_sha256,不含TOKEN、密钥、签名URL或SaaS OSS ID。
5. broker故障、无绑定、confirm丢失和重启保留原消息身份并恢复通知,不重新PUT、新建资产或重新拨号。源文件与恢复记录在通知未确认前保留;TOKEN过期仅显式向D重申请。
6. **R13完成只表示本项目已可靠交付MQ,不表示SaaS已消费/处理。** 不新增VERIFYING,不等待SaaS上传会话、verified或OSS ID,不发recording.ready。AI/控制等必要响应仍按各自合同处理。
复用的业务数据包括 `recording_id`、`call_id`、`content_type`、`size_bytes`、SHA-256、声道/采样率/时长。D→A 的临时授权含原会话、TOKEN/上传目标、方法、必要 headers、约束和有效期;D↔SaaS 的 MQ 只承担业务元信息/会话及最终验证,不传 D 的配置文件、长期凭据或临时 TOKEN。SaaS 为独立校验取得必要对象定位及读取能力的既有业务要求仍须满足,精确合同在 W01 冻结,不能假定“D 持有配置”即代表 SaaS 已能验证。
业务会话/complete 的 MQ 异步结果与 R12/R13 Unary 的衔接须由 W02/W11 冻结 pending、超时、原操作重取及最终结果;TOKEN 本身来自 D,不等待 SaaS 下发配置/TOKEN。不能无限阻塞 RPC,也不能收到 broker confirm 就返回已完成。旧 `saas.openapi.yaml` 仅作语义对照,不是新 MQ Schema;本轮不修改 Proto/配置格式或新增字段。
当前 D 用 `internal/oss` client 签发 grant 的职责与新确认方向一致,**不应再把 D 签发能力列为待删除或“仅故障回退”**;但配置文件读取、TOKEN 约束及完整接线仍须核验。当前 D 本地验证对象即发 ready 的旧行为仍不能替代 SaaS MQ verified。旧 SaaS HTTP upload-session/complete 方案继续废弃,不开发 HTTP client。
D现有SDK签发能力保留;旧D直接HEAD校验并生成ready/OSS ID的完成路径已删除,不能保留为兼容层。当前handler已接入上述新入队完成边界;本地RabbitMQ无绑定、确认丢失、重启和原消息恢复已有证据,不等于真实OSS/SaaS联调。
### 6.2 AI 不可变配置与授权
@@ -230,24 +227,24 @@ D 根据 MQ 任务中的原 `tenant_id/tenant_key + agent_version_id`,通过 M
保留既有版本、`immutable`、`content_sha256`、`config` 及租户授权语义;源 Schema、不可变摘要、有效期、撤销、能力和供应商受控引用均校验后持久绑定到原执行,再交付 Agent。缓存按租户/版本隔离,在途/原排队任务不漂移,同版本异内容拒绝,0/false 与未提供保真;无有效授权时拒绝新准入,不用 latest、CLI/env 或 SDK 默认值兜底。
旧 `ai-config.openapi.yaml` 的 AI GET 已废弃为 D↔SaaS 接入方式,不再开发该 HTTP client。当前 `AISnapshotRaw`/`AIAuthorizationRaw` 启动注入和 Agent 本地校验只证明旧路径;MQ 配置/授权、关联与持久恢复仍待 W01/W07 实现验证。消息细节不能由旧 OpenAPI 自动推定。
旧 `ai-config.openapi.yaml` 的 AI GET 已废弃为 D↔SaaS 接入方式,不再开发该 HTTP client。Dispatcher当前通过MQ请求/接收内嵌不可变配置和授权,按租户、版本、摘要、有效期、撤销和出口持久校验;实际本地RabbitMQ往返及SQLite重启证据见`docs/evidence/mq-ai-local-roundtrip.md`。Agent动态交付仍受现有Unary快照载体边界约束,不凭启动fixture宣称外部动态交付。
## 7. 待完成门禁与禁止误读
## 7. 当前门禁状态与禁止误读
| 门禁 | 完成证据 | 当前状态 |
| --- | --- | --- |
| W01 身份/Topic/消息冻结 | 新版本/来源/哈希、完整消息 Schema、路由、关联/错误/期限及正反例 | 待冻结 |
| W05/W12 MQ 控制面 | 全部交互持久接收/响应、移除旧 HTTP、补传语义正确 | 待实现/验证 |
| W07 MQ AI | 不可变配置/授权、迟到/撤销/重复与缓存隔离 | 待实现/验证 |
| W02/W11 上传授权与业务协调 | D 配置文件→临时 TOKEN→A 直传;R12/R13 与 MQ 业务结果有界衔接、SaaS verified 后才 ready | 待核验/实现/验证 |
| W13/W14 本地联合回归 | D1/D2 Topic 隔离 fixture、身份冲突、broker 故障、全流程无 SaaS↔D HTTP | 待验证 |
| W01 身份/Topic/消息冻结 | v2项目内Schema、路由、正反例及哈希 | 已生成并离线验证;不是运行时或外部签收 |
| W05/W12 MQ 控制面 | 全部交互持久接收/响应、移除旧 HTTP、补传语义正确 | 本地完成;见`mq-control-recovery.md`、查询/补传证据;外部SaaS不在本轮 |
| W07 MQ AI | 不可变配置/授权、迟到/撤销/重复与缓存隔离 | 本地MQ请求/响应、重复、范围和SQLite恢复完成;见`mq-ai-local-roundtrip.md` |
| W02/W11 上传授权与通知入队 | D配置→临时TOKEN→A直传;recording.uploaded可靠入队后R13完成,无SaaS等待 | 本地完成;见`20260921-mq-upload-progress.md`;不宣称SaaS消费 |
| W13/W14 本地联合回归 | D1/D2 Topic 隔离 fixture、身份冲突、broker 故障、全流程无 SaaS↔D HTTP | 本地完成;全仓质量和部署诊断状态见最终证据,真实供应商/生产仍延期 |
路由 fixture 只验证不同 Dispatcher 互不抢收,不把多 Dispatcher 调度或真实 SaaS 联调引入本轮。旧包、旧 HTTP handler、单租户 broker 测试和本地 OSS 成功,均不代表以上门禁通过。
## 8. 依据与相关文档
- [计划与需求阅读索引](../plan-0918.md):§1.2、§8.2 的 MQ-only 修订与状态。
- [时间泳道图](./saas-rabbitmq-oss-dispatcher-agent-timeline.md):目标流程,不冒充当前实现。
- [Dispatcher ↔ Agent 契约](./dispatcher-agent.md):现有 Proto/handler 事实与上传协调待改项。
- 旧实现事实:`internal/contract/contract.go`、`internal/mq/amqp.go`、`internal/tenant/routing.go`、`internal/dispatcher/consumer.go`、`internal/dispatcher/dispatcher.go`、`internal/store/store.go`、`internal/store/facts.go`、`internal/control/http.go`。
- [时间泳道图](./saas-rabbitmq-oss-dispatcher-agent-timeline.md):已按当前recording.uploaded边界更新;外部SaaS/真实OSS处理仍不在本地证据范围。
- [Dispatcher ↔ Agent 契约](./dispatcher-agent.md):现有 Proto/handler 事实、15分钟授权和上传通知恢复边界。
- 旧实现事实:`internal/contract/contract.go`、`internal/mq/amqp.go`、`internal/tenant/routing.go`、`internal/dispatcher/consumer.go`、`internal/dispatcher/dispatcher.go`、`internal/store/store.go`、`internal/store/facts.go`。旧HTTP源码仅可在历史基线中查阅,不是当前运行路径。
- 旧固定包:`contracts/upstream/2026-09-19-p1-v1/` 下 `mq.schema.json`、`event-payloads.schema.json`、`mq-topology.md`、`executor.openapi.yaml`、`saas.openapi.yaml`、`ai-config.openapi.yaml`;保留原样,不代表 MQ-only 新契约已发布。
@@ -1,175 +1,116 @@
# SaaS ↔ RabbitMQ ↔ Dispatcher ↔ Agent ↔ OSS 时间泳道图
## 1. 用途与事实等级
## 1. 已确认边界
**本图描述用户已确认的目标设计:SaaS 与 Dispatcher 的所有交互只经 RabbitMQ,不存在双方直连 HTTP。每个 Dispatcher 具有独立、全局唯一的 ID 和独立接收 Topic。** 不再把 AI GET 或录音 HTTP 握手列为待实现目标。
- SaaS↔D全部交互经RabbitMQ,无双方HTTP。每个D有全局唯一ID及独立Topic/租户队列,不抢收其他D的消息。
- D↔A保持Unary;OSS配置在D配置文件,A向D领取15分钟临时TOKEN并直传OSS,D不转发文件。
- **上传只保证recording.uploaded可靠进入指定持久队列**。不申请SaaS上传会话,不等待verified/OSS ID,不新增VERIFYING,不把入队当SaaS已处理。
- P1单D/单A/单Cell/单租户;D1/D2只用于本地消息隔离验证,不扩展调度HA。
P1 运行范围仍为单节点、单 Cell、单租户、单活 Dispatcher;用 D1/D2 说明消息隔离,并不扩大为多 Dispatcher 调度或 HA。Dispatcher↔Agent 保持 Unary gRPC,Agent→OSS 保持直接上传,音频字节不经过 Dispatcher 或 MQ。**OSS 配置存于 D 的配置文件,Agent 向 D 领取临时上传 TOKEN;SaaS 不下发 OSS 配置/TOKEN。上传完成仍由 SaaS 独立校验并经 MQ 返回 verified,此职责不变。**
本图为目标流程;v2 Schema/fixture通过不等于运行时接线或真实供应商验收。精确消息见[已确认v2合同](mq-only-v2-freeze-proposal.md)与`contracts/upstream/2026-09-21-p1-v2/`。
- **已确认**:MQ-only、全局唯一 Dispatcher 身份、专用 Topic、原业务与幂等边界。
- **待冻结**:精确 Topic/队列/绑定、身份生命周期、MQ 消息类型/字段、请求响应关联/错误,以及异步结果与现有 Unary 的衔接。
- **现有实现不等于目标完成**:旧租户 MQ、HTTP 控制、启动注入 AI 和上传待改项见 §5;D 签发 TOKEN 的职责保留,但 D 本地对象校验不能替代 SaaS verified。
图中“配置请求”“控制请求”“verified 响应”等为中文语义标签,**不是已发布的 command_type/event_type**。精确结构须在新版契约包冻结;旧 `contracts/upstream/2026-09-19-p1-v1/` 和 Proto 不因文档修改而自动支持这些交互。
## 2. 独立 Dispatcher 的订阅关系
## 2. 独立订阅关系
```mermaid
flowchart LR
S[SaaS] --> Q[RabbitMQ]
Q --> T1[D1 专用接收 Topic / 队列]
Q --> T2[D2 专用接收 Topic / 队列]
T1 --> D1[Dispatcher D1 / 全局唯一 ID]
T2 --> D2[Dispatcher D2 / 另一全局唯一 ID]
D1 --> Q
D2 --> Q
Q --> ST[SaaS 专用订阅]
ST --> S
S[SaaS] --> MQ[RabbitMQ topic exchanges]
MQ --> T1[D1 / 原租户独立持久队列]
MQ --> T2[D2 / 原租户独立持久队列]
T1 --> D1[Dispatcher D1 / UUID v4]
T2 --> D2[Dispatcher D2 / 另一UUID v4]
D1 --> MQ
D2 --> MQ
MQ --> SQ[SaaS指定持久接收队列]
SQ --> S
```
D1/D2 不竞争同一条接收队列,不靠全量广播后过滤模拟隔离。发往 D1 的任务、控制、AI 配置/授权、业务上传会话/验证结果只能由 D1 接收;D 发出的消息须能识别来源及原请求。Dispatcher 身份不替代租户身份,也不等于 `dispatcher_epoch`。具体命名/关联及完整路由长度约束见 [SaaS↔Dispatcher §2](./saas-dispatcher.md#2-通信拓扑dispatcher-身份与交付语义)。
采用精确Topic绑定,拒绝独立`*`/`#`词段,保留其他合法tenant_key原值;最长资源名决定196个UTF-8字节预算。D身份不同于dispatcher_epoch,重启不清除原消息/执行归属。
## 3. 目标时间泳道图
时间自上而下;所有 S↔D 路径均经过 MQ。MQ 中的 D 接收订阅和 SaaS 接收订阅是独立方向,不是两套业务流程。为避免伪造字段,图只索引现有业务数据与待冻结语义。
## 3. 时间泳道
```mermaid
sequenceDiagram
autonumber
participant S as SaaS
participant SQ as RabbitMQ / SaaS 专用订阅
participant DQ as RabbitMQ / D1 专用接收 Topic 与队列
participant D as Dispatcher D1 / 全局唯一 ID
participant SQ as RabbitMQ / SaaS指定持久队列
participant DQ as RabbitMQ / D1专用持久队列
participant D as Dispatcher D1
participant A as Agent
participant O as OSS
Note over S,O: MQ-only 目标流程;新消息 Schema 待冻结,不代表已经实现。
Note over DQ,D: D1 与 D2 的接收路径互相独立;本图仅展开 D1。
S->>DQ: call.execute(目标D1、原租户、版本与期限)
DQ->>D: 精确投递
Note over D: 校验并同事务持久inbox/task/outbox,重复返回原决定。
D-->>DQ: 持久后ACK
D->>SQ: command.result accepted
SQ->>S: 受理结果,不是已拨号
S->>DQ: call.execute(明确目标 D1;旧 payload 语义见 saas-dispatcher §3)
DQ->>D: 按 D1 专用绑定投递
Note over D: 校验目标、租户、版本、幂等及期限;持久 inbox/task/outbox。
D-->>DQ: 持久成功后 ACK
D->>SQ: command.result accepted(来源 D1、原命令关联)
SQ->>S: 业务受理结果
Note over S,SQ: SaaS 持久接收后 ACK;publisher confirm 不等于业务已应用。
opt 当前执行尚无合法绑定的配置与有效授权
D->>SQ: AI 配置/授权请求(原租户、agent_version_id、请求关联;§6.2)
SQ->>S: 配置/授权请求
S->>DQ: 不可变配置、摘要、授权或明确拒绝(目标 D1、原请求关联)
DQ->>D: 配置/授权响应
Note over D: 校验并持久绑定到原执行;无有效授权不得继续发起。
D-->>DQ: 持久成功后 ACK
opt 缺少当前执行可用的不可变AI配置/授权
D->>SQ: ai.config.request(原租户/版本/请求身份)
SQ->>S: 配置请求
S->>DQ: ai.config.result(原请求关联)
DQ->>D: 定向响应
Note over D: 校验摘要/授权并持久绑定;不使用latest或本地默认覆盖。
D-->>DQ: 持久后ACK
end
D->>A: GetAgentStatus(dispatcher-agent §4.1、§5.1)
A-->>D: AgentStatus、boot、能力与资源
D->>A: ActivateAgent(§5.2)
A-->>D: ACTIVE + Session
Note over D,A: 原执行配置交付、bootstrap/admission 完整编排仍须按契约核验,不由本图宣称完成。
D->>A: GetExecutionPermit(原 binding、配置摘要、预留;§5.5)
A-->>D: OperationReceipt + ExecutionPermit
D->>A: Execute(原 call.execute、binding、配置摘要、permit;§5.6)
A-->>D: OperationReceipt
Note over D,A: ACCEPTED 不等于 SIP 已发起;拨号仍受许可、控制和时间窗口约束。
D->>A: 状态/激活/准入、执行快照与最后许可
A-->>D: 既有Unary回执/实际状态
D->>A: Execute(原binding与许可)
A-->>D: 接收回执
Note over D,A: 仍须满足时间窗口、白名单、配额及控制屏障;回执不等于已发起。
opt 执行期间控制任务
S->>DQ: 控制请求(原任务、CAS、pause/resume/stop;saas-dispatcher §5.2)
DQ->>D: 投递给 D1
Note over D: 持久控制与 outbox;关闭相关新发起权限。
D-->>DQ: 持久成功后 ACK
D->>SQ: 控制 accepted
SQ->>S: 已受理,不是 applied
D->>A: ApplyTaskControl(dispatcher-agent §5.7)
A-->>D: receipt / 控制事实
Note over D,A: 所有必需屏障和挂断事实核验完成后才可 applied。
D->>SQ: 控制进度或 applied 结果
SQ->>S: 按原控制关联更新状态
opt 控制、查询或整体补传
S->>DQ: task.control / query / replay
DQ->>D: 原D、租户和目标校验
D->>A: 必要的控制/状态核验
A-->>D: 实际回执/事实
D->>SQ: 控制结果、查询响应或原业务事件补传
SQ->>S: 按原请求关联处理,不重新执行呼叫
end
Note over A: 通话、媒体、AI 和录音产生真实 ExecutionFact。
A->>D: ReportExecutionEvent(dispatcher-agent §6.1)
A->>D: ReportExecutionEvent
D-->>A: 事实持久接收结果
D->>SQ: call.status / transcript.updated / call.finished / contact.opt_out
SQ->>S: 持久应用业务事件(saas-dispatcher §4)
SQ->>S: 业务事件
A->>D: RequestUpload(原 binding、AssetDescriptor、upload_id;§6.2)
Note over D: OSS 配置来自 D 配置文件;缺失/无效明确失败,不向 SaaS 获取配置或 TOKEN。
Note over D,A: 业务会话 MQ 结果可能晚于 Unary deadline;有界等待/重取待冻结,不是等 SaaS 签 TOKEN。
D->>SQ: 原资产业务上传会话请求(仅元信息,不申请 TOKEN;saas-dispatcher §6.1)
SQ->>S: 既有业务会话/资产登记语义
S->>DQ: 业务会话结果或拒绝(目标 D1、原请求/资产关联;无 OSS 配置/TOKEN)
DQ->>D: 原业务会话响应
Note over D: 校验/持久会话关联,依据自身配置通过 SDK 提供临时 TOKEN。
D-->>DQ: 持久成功后 ACK
D-->>A: Unary 返回 D 提供的临时 TOKEN 及受限 UploadGrant(精确映射待核验)
A->>O: PUT recording bytes(dispatcher-agent §6.3)
O-->>A: PUT 结果 / ETag
Note over A,O: A 本地记录实际大小与 SHA-256;PUT 或 ETag 不等于 verified。
A->>D: CompleteUpload(原资产/会话、大小、SHA-256;§6.4)
D->>SQ: complete 请求(原会话及元信息)
SQ->>S: 完成请求
S->>O: 独立验证原对象(具体校验方式由 SaaS 合同定义)
O-->>S: 对象验证依据
S->>DQ: verified + oss_id 或明确失败(原关联)
DQ->>D: 完成验证结果
Note over D: 只有合法 verified 才同事务记录完成状态及 recording.ready outbox。
D-->>DQ: 持久成功后 ACK
D-->>A: 通过获批 Unary 衔接返回最终上传结果
D->>SQ: recording.ready(saas-dispatcher §4.3)
SQ->>S: OSS ID 与原录音元信息
opt 查询或整体补传
S->>DQ: 原 command/call 查询,或 call_id/source_command_id 整体补传请求
DQ->>D: 按目标 D1 接收(saas-dispatcher §5.3–§5.4)
D->>SQ: 关联查询结果,或原事件身份/内容/版本的补传
SQ->>S: 查询响应或补传结果
Note over S,D: 不重新投 call.execute 执行;响应超时不走 HTTP 兜底。
A->>D: R12 RequestUpload(原binding/asset/upload_id)
Note over D: OSS配置来自D文件,复用SDK签发15分钟TOKEN;不向SaaS申请会话。
D-->>A: 临时TOKEN/受限UploadGrant
A->>O: 一次PUT(文件直接上传OSS)
O-->>A: PUT结果
Note over A: 保存真实上传结果/大小/摘要与恢复记录;不自动再次PUT。
A->>D: R13 CompleteUpload(仅原资产元信息)
Note over D: 校验原绑定;上传事实与固定event_id的outbox同事务持久化。
D->>SQ: persistent recording.uploaded,mandatory
alt 正确durable队列接收、无return且publisher confirm成功
SQ-->>D: broker确认
Note over D: 持久标记通知已交付;没有SaaS处理结果或OSS ID。
D-->>A: 本项目交付完成
opt SaaS自行消费,非本项目完成条件
SQ->>S: recording.uploaded
end
else 不可路由、断连或确认丢失
SQ-->>D: return / nack / error / timeout
Note over D,A: 保留原通知身份与恢复信息,不宣称完成;恢复通知而非重新PUT。
end
```
图中后续步骤均以所需校验和前置条件成功为前提;拒绝、超时和失败保留原关联及待恢复状态,不继续执行成功分支。各类 MQ 请求/响应都适用持久后 ACK,图未重复画出所有 broker confirm/消费者 ACK;它们不能代替业务状态。
图中所有MQ请求/响应都遵守持久后ACK;publisher confirm只证明broker接收,不能单独证明正确路由,须同时排除return并核验指定持久队列/绑定。对上传而言这就是交付终点;AI/控制等流程仍需要各自的业务响应。
## 4. 交互与结构索引
## 4. 数据与验证索引
| 交互 | 数据/语义来源 | 新设计状态 |
| 交互 | 依据 | 验证重点 |
| --- | --- | --- |
| 身份、目标与专用 Topic | `saas-dispatcher.md §2` | 原则已确认;精确命名/消息关联待冻结 |
| 执行请求/受理结果 | `saas-dispatcher.md §3–§4` | 旧业务字段可对照;Dispatcher 路由待改 |
| AI 配置/授权请求响应 | `saas-dispatcher.md §6.2` | 全部 MQ;旧 HTTP GET 不再是目标 |
| 状态/激活/许可/执行 | `dispatcher-agent.md §4–§5` | 既有 Unary;完整配置/执行接线以代码证据为准 |
| 控制/查询/补传 | `saas-dispatcher.md §5`;`dispatcher-agent.md §5.7` | MQ 请求与响应待冻结/实现,旧 HTTP 废弃 |
| Agent 事实与业务事件 | `dispatcher-agent.md §6.1`;`saas-dispatcher.md §4` | 旧 fact/outbox 已有;来源路由与已知映射差异待验证 |
| 临时上传 TOKEN | `saas-dispatcher.md §6.1`;`dispatcher-agent.md §6.2` | OSS 配置在 D 文件;D 提供 TOKEN,A 经 Unary 领取,SaaS 不签发或下发配置 |
| 业务上传会话 | `saas-dispatcher.md §6.1` | 保留既有 SaaS 业务语义,经 MQ 关联原资产/会话;不作为 TOKEN 来源,异步衔接待冻结 |
| 文件上传 | `dispatcher-agent.md §6.3` | Agent→OSS,不经 D/MQ,不改变对象上传协议 |
| complete/verified/ready | `saas-dispatcher.md §6.1`;`dispatcher-agent.md §6.4` | SaaS MQ verified 才触发 D 的 ready;待实现/验证 |
| 身份/精确Topic/租户队列 | v2合同§1–§2及mq-topology.json | 不串收/抢收,稳定ID、字节预算、危险词段拒绝 |
| 命令/查询/AI请求响应 | v2合同§3及mq.schema.json | 严格字段、原请求关联、持久恢复、accepted/applied分开 |
| D↔A | dispatcher-agent.md及Proto | 保持Unary,版本和执行事实以代码证据为准 |
| D配置/TOKEN/A直传 | v2合同§4–§5 | 无长期凭据下发、15分钟、一次PUT、显式重申请 |
| recording.uploaded | event-payloads.schema.json | 11个已确认事实字段,无TOKEN、密钥或SaaS OSS ID |
| 通知完成 | v2合同§5 | 指定durable队列、persistent、mandatory无return、confirm成功;无需SaaS回复 |
## 5. 当前实现与目标设计差异
## 5. 当前实现差异
| 能力 | 既有实现事实 | 必须完成的纠正 |
| --- | --- | --- |
| Dispatcher 身份/订阅 | MQ 命令仅按 tenant key 路由 | 全局唯一 ID、独立 Topic/队列、原请求定向响应及来源校验 |
| 控制/查询/补传 | 旧 HTTP handler;部分通话路由固定 404,source-command replay 重发原命令 | 移除 SaaS HTTP 业务入口;全部 MQ,补传只恢复原业务结果,不重拨 |
| AI 配置来源 | 启动注入 `AISnapshotRaw` / `AIAuthorizationRaw` | SaaS MQ 配置/授权及持久绑定,不接旧 AI GET |
| 上传 TOKEN 与验证 | D 已有 OSS client 签发 grant、直接验证对象并发 ready | 保留 D 签发职责,核验配置文件/TOKEN 约束;业务会话与 verified 仍经 SaaS MQ,本地对象验证不能替代 verified |
| R12/R13 | 当前同步本地 grant/verify | 核验 R12 依据 D 配置提供 TOKEN,以及业务会话/complete 的 MQ 持久关联、有界等待、原操作恢复与最终 Unary 交付 |
| Agent Execute | handler 证明准备/校验/幂等接收 | 不能据此宣称实际 ARI/RTP/SIP/AI 生命周期已完成 |
| TRANSCRIPT_FAILED | 当前 aggregate 类型映射不通过 Schema | 已知差异保留,另行实现修正,不因文档变更标通过 |
旧SaaS业务HTTP实现和启动配置已删除,执行、查询、控制、AI请求及上传通知已接入v2/v3 MQ。本地证据覆盖D身份/租户隔离、不可路由、confirm/DLQ、断连、重复、迟到revision、SQLite重启和recording.uploaded恢复;不得把这些本地证据当作外部SaaS或生产通过。
## 6. 幂等、失败与验收边界
1. 重投沿原请求、Dispatcher、租户和业务对象关联;同 ID 异内容冲突,不能换 execution/attempt/asset 绕过。
2. D1 离线不把未决请求/响应改投 D2;MQ 中断不回退 HTTP,执行未知不自动重拨。
3. 配置迟到、重复或已撤销时不能覆盖在途绑定;无有效授权拒新准入。
4. 上传回包丢失复用原 `upload_id` 和资产摘要;TOKEN 过期只接受 Agent 显式向 D 重新申请,配置不来自 SaaS,也不把 D 配置/TOKEN 经 MQ 传给 SaaS。未取得 SaaS verified 时不得发 ready 或提前删文件。
5. 新 boot/epoch 不清旧未知执行和占用;RPC 超时按原 binding 查询/对账。
6. 交付检查覆盖无 SaaS↔D HTTP、D1/D2 路由隔离、错目标/重复身份、请求响应乱序/超时/重启恢复、不可路由与 confirm 丢失;不以文档图、单租户 Mock 或本地 OSS 验证冒充通过。
## 7. 相关文档
- [SaaS ↔ Dispatcher 对接契约](./saas-dispatcher.md)
- [Dispatcher ↔ Agent 对接契约](./dispatcher-agent.md)
- [计划与需求阅读索引](../plan-0918.md)
旧会话/verified控制流程已移出目标,不再为它增加RPC、状态或测试服务器业务系统。通知重复/确认丢失/重启沿原消息身份恢复,不重新PUT、不新建资产、不重拨;SaaS后续处理不属于本项目。
+25
View File
@@ -0,0 +1,25 @@
# AI 配置摘要:JCS 本地修订
## 已确认决定
用户明确批准 AI 配置采用 RFC 8785(JCS)规范化后计算 SHA-256,输出小写十六进制。新版本地契约不等于外部 SaaS 已签收。不改变文件校验和、上传事实去重或命令正文哈希。
## 实现与版本
- Go/Python 复用 `github.com/cyberphone/json-canonicalization`,固定 `v0.0.0-20241213102144-19d51d7fe467`;不自写规范化算法。
- `ai.Validate` 保留原始配置字节,但摘要取 JCS 结果;拒绝非法 UTF-8,不接受旧摘要回退。
- 新包:`contracts/upstream/2026-09-21-p1-v3/`,MQ 线上的 `schema_version` 仍为 `2.0`。
- manifest SHA-256:`e09e8563f4a7bc9d70e3a4b9782efbaeca8858965433cefb1b60355115159d1c`。
- v1/v2 不可变包保持原样。发布器从校验过的 v2 文件生成新包,在离线正反例和 AI Schema 校验后发布;已存在目标若内容不同,明确失败。
- Python 生成的数值、UTF-16 属性排序和字符串黄金向量,由 Go 验证规范字节及 SHA-256 一致。
## 验证事实
- 新测试先暴露原始字节哈希对空白及 `1`/`1.0` 表达敏感;实现后相同语义的示例得到相同摘要。
- 授权样例使用已有 `config_sha256` 字段绑定新摘要,没有增设猜测字段。
- 新包文件哈希、未登记文件检查、离线 Schema 正反例、跨语言黄金向量测试通过。
- 全仓普通测试通过:`/tmp/go-sip-jcs-full.log`。
## 尚未完成
AI 配置/授权 MQ 请求响应的完整持久运行链路、最终 race/vet/覆盖率及部署验收仍未关闭。不能用摘要测试替代完整 AI 交付或外部真实联调。
@@ -0,0 +1,43 @@
# MQ-only:配置、身份与本地MQ基础验证(业务接线未完成)
## 已实现
- Dispatcher启动必须提供`--config`。配置文件先校验,再访问数据库;凭据只按文件中明确的环境变量引用解析,授权时长固定15分钟。
- JSON拒绝重复键、未知字段、大小写别名、多个顶层值、无效UTF-8及超出64 KiB的文件;按已确认v2配置Schema校验,失败不部分修改配置,也不改读旧配置来源。错误不打印凭据值。
- SQLite通过`005_dispatcher_identity.sql`保存唯一逻辑身份。换ID拒绝沿用数据库;身份校验在恢复通知之前,不能由错误身份改变待发送状态。
- `store.Open/New`仅打开和迁移;恢复由启动入口在校验身份后显式执行。相关重新打开/进程崩溃恢复测试已同步。
- RabbitMQ使用三组v2 durable topic exchange、独立Dispatcher/租户队列和非持久exclusive身份占用队列。同一ID重复启动被拒绝;启动时先取得MQ身份占用,再打开业务数据库。
- 发布只允许本D已经声明的租户出站路由;persistent、mandatory、publisher confirm与return共同判定成功。使用独立发布channel隔离迟到确认,复用连接。
- 收发限制256 KiB;超限入站消息不进入业务处理,进入对应持久DLQ,并记录身份、消息序号和大小,不打印正文。
## 实际验证
本地隔离RabbitMQ 4.1容器测试证明:
1. 两个不同UUID使用同一租户时队列不相同,D1消息不被D2收走。
2. 重复占用同一UUID失败,不影响原连接。
3. 跨D消费/发布被拒绝。
4. 指定SaaS持久队列实际收到persistent消息;不启动SaaS消费者也能完成发布。
5. 移除目标绑定后,即使broker确认,mandatory return仍导致发布失败;恢复绑定后的下一次发布正常,不被旧return污染。
6. 262145字节入站消息在修改前错误到达业务handler;加入大小门禁后通过,实际进入对应DLQ。
配置测试也先暴露了`encoding/json`接受大小写别名的问题;增加权威Schema校验后拒绝。配置缺失、身份变更及恢复顺序均有回归测试。永久消息分类测试使用真实Schema错误,不只检查手写错误字符串。
最新检查:
- Go:`go1.27.1 linux/amd64`。
- 全仓`go test -race -coverprofile=... ./...`通过;只显式启用本机RabbitMQ测试,其余外部集成开关清除。
- `go vet ./...`、构建、格式及`git diff --check`通过;旧v1契约与`704652b`无差异。
- 总覆盖率 **35.1%**,尚未达到65%;config 75.7%、MQ 63.6%、tenant 85.4%,不以局部数字替代整体要求。
- 9个文件的主动LSP检查无返回诊断,但均为inconclusive,不宣称LSP确认干净;以实际编译/vet/race结果为本轮检查依据。
- 临时日志及覆盖率:`/tmp/go-sip-mq-control.noMu1i/`。该路径非长期制品。
## 未完成,不能签收全流程
- `contracts.SourceCommit`仍为v1;业务消费、事件构建、outbox路由和必要请求响应尚未整体切到v2。MQ传输测试不证明完整业务已接通。
- 旧HTTP入口、CLI/环境中的旧配置选项及旧集成测试仍需清理;配置文件权威来源已接入不等于所有旧入口已删除。
- Broker连接丢失的通知已暴露,但所有准入/许可路径的停止联动还需验证。
- 上传仍待改为recording.uploaded及正确的R13完成边界;未验证通知恢复不重复PUT。
- 本轮只验证本地组件,不是完整非生产部署验收;未做ECS、SIP、真实供应商或生产验证。
当前为`mq-control`任务的部分证据,不关闭该任务或其余计划项。
@@ -0,0 +1,51 @@
# MQ-only / Dispatcher OSS TOKEN 调整:实现前基线
- 记录时间:2026-09-21T06:05:43Z。
- 分支:`feat/mq-only-dispatcher-oss`。
- 源提交:`704652bd0dde2f78249fa53784d7afddf5f070f5`。
- 该提交经用户明确批准,仅保存11份已确认设计文档,未推送远程;本次开始检查时工作区干净,已 fetch 并确认包含 `origin/main`。
- 用户已明确本目标由当前 Agent 直接执行,不启动子 Agent;原子 Agent fast/provider 阻塞不适用于本次执行。
## 1. 实际检查结果
| 检查 | 结果 | 边界 |
| --- | --- | --- |
| `GOTOOLCHAIN=local go version` | `go1.27.1 linux/amd64` | 符合锁定版本 |
| `go test -race -coverprofile=... ./...` | 通过 | 显式移除 `RABBITMQ_URL`、`AGENT_CALL_PROVIDER_SMOKE`、`AGENT_CALL_OSS_INTEGRATION`,没有调用真实 broker/OSS/AI |
| 覆盖率 | 全部代码32.1%;排除生成文件的诊断值44.2%(4557/10318语句) | 尚未达到65%,两种口径均不算验收通过 |
| `go vet ./...` | 通过 | 当前源提交 |
| `go build ... ./cmd/sip-go-agent` | 通过 | 实际入口是 `cmd/sip-go-agent`;第一次误用不存在的 `cmd/agent-call` 失败,随后按实际入口纠正命令,没有改代码掩盖 |
| 手写Go文件 `gofmt -l` | 无输出 | 不改生成代码 |
| Buf / Proto工具 | Buf 1.61.0、两个Go生成插件可用 | 项目用Buf生成;独立protoc不在PATH,不等于现有Buf链阻塞。尚未执行本次Proto生成 |
| Python契约工具 | jsonschema、yaml可导入 | 尚未发布新契约 |
| 本地RabbitMQ测试条件 | Docker本地socket可用;本地已有 `rabbitmq:4.1-management-alpine` 镜像;5672无监听 | 尚未创建容器或宣称MQ集成通过 |
| 部署/诊断 | systemctl可用,tcpdump不在PATH | 之后须按获批本地/隔离profile验证,不跳过诊断或伪造ECS事实;未授权任何真实外呼/云操作 |
## 2. 已核实的实现差异
| 路径 | 当前事实 | 调整边界 |
| --- | --- | --- |
| `internal/mq/amqp.go` | 命令使用旧租户级拓扑,没有获批的新D身份/专用Topic闭环 | 新身份/拓扑/消息先确认,再实现;不放宽旧Schema |
| `internal/config/config.go`、`cmd/sip-go-agent/main.go` | 现有OSS配置和启动开关分散在进程配置/flags,存在旧业务HTTP接线 | 新D配置文件需严格读取;移除SaaS业务HTTP,保留内部Unary及必要非业务诊断 |
| `internal/control/http.go` | 查询/控制/补传仍为旧HTTP处理 | 业务逻辑转为MQ;整体补传不能重投执行命令 |
| `internal/rpc/dispatcher_upload.go` | R12已接D签发grant;R13仍包含D直接验证对象并完成资产的路径 | 保留签发能力;完成状态必须等待SaaS MQ verified,不把D本地验证当最终验证 |
| `internal/oss/aliyun.go` | 已复用阿里云官方SDK提供15分钟默认预签名PUT;允许配置更长TTL;有本地HEAD校验 | 可复用签发与现有UploadGrant,不自写签名;新约束须锁定15分钟,不能宣称URL本身原生一次性 |
| `internal/store/store.go`、迁移目录 | 已有SQLite业务表/事务、4份迁移文件 | 新持久身份、请求关联和结果状态须认真设计并测试,不破坏旧数据或执行未知占用 |
这些事实不代表完整新流程已接通。AI配置/授权的MQ关联、控制屏障、最终verified和重启恢复仍需在新合同下实现与逐项验证。
## 3. 基线产物位置与哈希
运行产物在 `/tmp/go-sip-mq-baseline.niNWHQ/`,可能被系统清理;这里仅保存脱敏检查事实和哈希,不把临时文件当永久可用制品。
| 文件 | SHA-256 |
| --- | --- |
| `test.log` | `69c6602993d04076031cd62c885db3b0bb0e81c5a9bd86d60a3b13d6ef1aeeac` |
| `coverage.out` | `9f474ba35af2685ada2b9f52e84def76fc43452995a8a73f59b3f95e740455d3` |
| `sip-go-agent` | `36ddab325d6a54b96d5ba7dba23a65d811005aba30d62dcaca2ebb65500275cf` |
## 4. 尚未完成
- Dispatcher身份生命周期、Topic/消息/错误/期限、配置文件及TOKEN形态的精确方案尚需用户确认。
- 未修改业务实现,未执行新的MQ/OSS完整集成或故障矩阵。
- 未接入真实SaaS、供应商、云资源或拨号;没有生产验收结论。
@@ -0,0 +1,37 @@
# MQ 查询阶段证据(mq-control 尚未完成)
## 已接通
- `command.query`、`call.query` 经 Dispatcher 的 MQ 消费入口进入严格v2校验,按D身份、原租户及实际路由检查归属。
- 新增`006_mq_queries.sql`:租户绑定与查询去重记录;查询身份、固定响应和outbox在同一事务保存,成功持久化后才允许ACK。
- 重复查询返回原响应身份及原快照;已发布响应可恢复为待发送,正在发送的响应不被重置。重启及请求期限过后重复投递仍取原决定,不重新计算快照。
- 过期的新请求保存明确拒绝;相同消息身份但不同内容、不同D或租户绑定冲突不被接受。
- 命令快照读取实际收件记录及已记录的aggregate version,不用固定版本填补缺失证据。
- 呼叫快照只收集同D、同租户、同call_id的事实,包含已批准的状态、尝试事件、文字事件、uploaded/failed录音事件及四类投递计数。
- 超出256 KiB预算返回有界的`rejected/unavailable`响应,不截断成貌似完整的快照,不进入无限重投。**合同没有include或分页字段**,没有添加它们。
## 校验与错误
- 新增嵌入式Schema loader及编译结果复用;跨文件引用只取指定契约包,外部URL、跨版本和越界文件名被拒绝,不访问网络。
- 新服务消息使用已批准字段`message_type`,不是`message_kind`或`request_type`。
- 非法UTF-8曾被当作可重试错误;真实失败测试后改为明确的无效消息错误分类,避免反复回队。
- 当前全局`contracts.SourceCommit`及执行/事件旧流程仍未整体切v2;新查询明确固定v2,不尝试旧协议回退。
## 实际测试
- Schema离线引用、六类服务消息正例、额外字段/旧版本拒绝。
- 查询存储失败回滚:响应、去重记录、租户绑定均不部分提交。
- 查询重复、异内容冲突、重启、过期,以及缺失实际版本证据。
- 呼叫事实集合及投递计数;大快照拒绝;未批准include字段拒绝。
- 本机RabbitMQ真实命令查询往返:请求队列→Dispatcher→SQLite/outbox→指定SaaS队列;验证persistent、路由、租户、D身份和correlation_id。重复请求恢复同一响应ID。
- 该测试使用本Agent创建的本机容器及专用`go-sip-query-tests` vhost,避免和MQ包测试抢取同一个固定SaaS队列。开关为`GO_SIP_LOCAL_QUERY_MQ_URL`,仅接受loopback。
最新全仓验证:`go test -race -coverprofile=... ./...`、vet、构建、格式及diff检查通过;同时启用两个本地MQ测试入口,真实外部集成开关清除。旧v1契约与`704652b`无差异。
覆盖率 **36.9%**,仍未达到65%。临时日志/覆盖率:`/tmp/go-sip-mq-queries.hhyp37/`。
## 未完成
旧HTTP业务实现、启动流程、CLI参数、环境字段及部署样例已删除;`TestDispatcherHasNoBusinessHTTPFlags`先失败后通过,删除后全仓race/vet/build及旧v1无变更检查通过,日志`/tmp/go-sip-http-removal-tests.log`。未保留兼容入口。
执行/控制/整体补传、AI配置/授权请求响应、全部旧MQ路由/信封替换、所有权断连后的准入联动,以及新上传事实交付仍待完成。本证据不关闭mq-control或其他任务,不代表完整业务、本地部署验收或真实供应商通过。
@@ -0,0 +1,29 @@
# MQ 整体补传阶段证据
## 已实现
- `call.replay`、`command.replay`按已确认v2命令Schema进入MQ消费入口;校验原D、租户、路由和期限,不转换为旧协议。
- 事务内保存原命令身份、内容摘要和固定的`command.result`响应,提交后才可ACK。
- 只将原业务事件恢复为待投递,保留原event_id和正文;不创建任务、通话或资产,不重拨、不PUT。
- 按source_command_id补传时,从已持久命令确定execution,再通过已记录的call.status/call.finished确定call_id;这样覆盖只含call_id、不含execution_id的已批准recording.uploaded,未擅自加字段。
- 同一命令重复到达只恢复原响应,不再次执行补传副作用;不同内容冲突、错路由及过期命令被拒绝。正在发送的原事件不被重置。
- 新增`007_mq_command_receipts.sql`保存固定回执关联;复合外键具有对应唯一索引。现有inbox的历史全局command_id主键没有在本步重建,不将本步宣称为双租户能力完成。
- 命令查询改用已记录command.result的状态、时间、原因和版本,不把inbox的persisted误报为业务状态。
## 实际测试
- 存储触发器注入失败:原事件投递状态、命令及响应均回滚,不发生部分提交。
- 两类补传的原event_id/正文不变、重复请求同响应、错目标、异内容、过期与零新增拨号任务。
- 新v2通用消息校验/命令解码覆盖四类命令,拒绝旧版本;未添加旧协议回退。
- 本机RabbitMQ测试统一为`TestLocalMQRequestRoundTrip`,覆盖command.query、call.query、call.replay、command.replay四类实际MQ往返,验证持久消息、正确目标队列/路由/身份/关联和重复响应身份。
- 补传集成测试明确以本地fixture预置此前已保存的业务事实,证明通知重投,不等同于真实外呼或当前v1执行路径已生成v2事件。
最新全仓race+coverage、vet、构建、格式、diff及旧v1与704652b无差异检查通过。两个本机RabbitMQ测试入口启用,真实外部集成关闭。
- 覆盖率:**37.5%**,尚未达到65%。
- 临时日志/覆盖率:`/tmp/go-sip-mq-replay.GLsL0Q/`。
- 未提交、未推送,未触碰云或真实供应商。
## 未完成
mq-control仍未关闭:执行/事件仍有旧版本路径;task.control及AI配置/授权的完整MQ协同、所有权断连后的准入联动仍待实现。上传TOKEN/R13的新完成边界及整体部署验收也仍未完成。
@@ -0,0 +1,37 @@
# MQ 上传调整:本地阶段证据
本记录仅覆盖本地代码和隔离测试,不代表完整目标通过,不代表真实 OSS、供应商或生产验收。
## 已实现
- Dispatcher 使用已绑定身份,持久保存 `recording.uploaded` 上传事实和原通知。上传完成只在 outbox 发布成功后记录;R13 在通知尚未交付时返回 Unavailable,可重试原事实通知。
- 上传路径不再执行对象 HEAD、不等待 SaaS、不生成 OSS ID,也不发送 `recording.ready`。内部完成响应的旧 `oss_id` 字段已移除并保留字段编号;旧不可变外部契约包未因此修改。
- Agent 在 PUT 前独占并持久记录上传尝试。失败或结果未知不会自动再次 PUT;成功结果和原 binding/asset 保存到文件,Agent 启动恢复仅发送通知,不申请 TOKEN、不读取或发送录音。
- TOKEN 固定 15 分钟,非法文件大小明确拒绝;普通 HTTP transport 错误不再携带外层签名 URL。签名 URL 不是 OSS 原生一次性凭据。
- 授权按 upload_id/operation_id/请求摘要持久绑定;重放旧操作只返回原授权,即使它已经过期。`agent upload-retry` 要求调用方显式提供新的 UUID v4 请求ID;各次请求分别持久占用,不能复用旧请求执行另一轮PUT。
- 原始上传及通知恢复、显式再申请共用跨进程文件锁。源文件变化时按实际发送字节核验大小/SHA-256,不能把预读摘要当作实际上传事实。
- 授权时的bucket/object_key持久绑定。后续配置更换bucket不能改写原上传通知的位置,也不能把原上传身份重新授权到另一个bucket。
- OSS参数仅来自严格JSON文件及其明确引用的凭据环境变量;旧OSS环境配置、HEAD核验和OSS ID生成辅助路径已移除。部署样例及systemd入口使用必需的JSON配置。
- MQ 发送保留原 `event_id`/`message_id` 到 AMQP `MessageId`。使用 persistent 消息、durable 队列及精确绑定、mandatory 和 publisher confirm。
- outbox 每次仅领取即将发送的一条消息。原批量领取方式在第一条发送失败时,会使余下消息滞留 dispatching,现有回归测试证明无需重启即可恢复整批;恢复状态写入错误不再被吞掉。
- MQ 独占身份连接断开会停止共享准入上下文,新 Dispatcher RPC 被拒绝;不能由此推断 Agent 已有许可全部撤销。
## 已执行检查
- Go 1.27.1。
- 修改相关包的 race 测试,以及全仓 vet、格式和 diff 检查通过;这些不是最终全仓覆盖率验收。
- `TestRecordingNotificationRecoveryDoesNotPUTAgain`:本地 TLS 测试端,重启文件状态后一次 PUT、一次授权请求、两次事实通知,保留源文件。
- `TestLocalUploadNoticeSurvivesUnroutableAndRestart`:独立本地 RabbitMQ vhost,无绑定返回失败;SQLite 重启后保留原通知;恢复绑定后入队。
- 同一测试注入“RabbitMQ 实际发布成功,但应用丢失发布结果”:交付未被提前标记完成,再次发送保留原 MessageId 和正文。队列收到两份同身份持久消息,符合至少一次交付;未新建资产或触发 PUT。这是应用边界故障注入,不冒称网络层 confirm 抓包实测。
- 上传完成判定发生在读取队列前,不需要 SaaS 消费者或业务处理回复。
- 本地 D1/D2 隔离、查询/补传与上传分别使用隔离测试队列环境;相关 RPC、MQ、Dispatcher race 测试通过。
- 本地MQ专项race日志:`/tmp/go-sip-local-acceptance.cpYTru/test.log`,退出码0,覆盖store/RPC/MQ/Dispatcher/CLI;Agent/OSS race检查另见 `/tmp/go-sip-upload-race-latest.log`。
- 新增授权旧请求不续期、显式新请求一次PUT、跨Spool互斥、实际上传字节变化拒绝、原bucket跨配置变更保留等测试。旧路径清理后相关普通回归通过:`/tmp/go-sip-oss-cleanup-tests.log`。
- `deploys/config/dispatcher.json.example` 已通过当前严格Schema验证。
## 未关闭事项
- 执行、控制、AI 配置/授权的完整 v2 MQ 运行链路及持久恢复尚未全部完成。
- 旧运行文档和部分非上传事件仍待统一;不得以本记录关闭整个 W 任务。
- 最终全仓 race、构建、至少 65% 覆盖率、契约和统一部署诊断验收尚未完成。最新全仓普通测试覆盖率 39.9%(未启用本地 MQ 集成环境),仍低于 65%;不能用局部测试通过代替。
- 未执行任何真实 OSS、云资源、付费供应商或外呼验证。
+49
View File
@@ -0,0 +1,49 @@
# MQ-only v2:项目内契约验证
## 范围与授权
- 工作分支:`feat/mq-only-dispatcher-oss`;既有基线:`704652b`。
- 用户已批准纯Topic、UUID v4、196字节租户预算、JSON配置、15分钟SDK预签名PUT,以及查询中复用事件集合和投递计数。
- 用户随后修订目标:上传不等待SaaS会话、verified或OSS ID,不新增VERIFYING;指定持久队列成功接收即完成本项目交付。
- 用户单独确认新事件`recording.uploaded`及11个payload字段,以它取代本项目原`recording.ready`,查询录音集合也使用uploaded/failed。
## 产物
- 新包:`contracts/upstream/2026-09-21-p1-v2/`。
- `scripts/publish-mq-v2.py`从只读v1来源和已确认规则生成37个被manifest固定哈希的文件;manifest另存旧源文件哈希,不依赖不存在的旧manifest。
- `mq.schema.json`包含4类命令、8类事件、3组必要查询/AI请求响应;没有上传会话/complete/verified往返协议。
- `event-payloads.schema.json`仅允许已确认上传事实字段,不接受SaaS OSS ID、TOKEN、密钥或签名URL。
- `dispatcher-config.schema.json`拒绝未知配置、直接密钥字段及TTL覆盖;`mq-topology.json`固定持久队列、persistent、mandatory和confirm要求。
- 现有业务字段从固定源Schema导出;控制/补传重复command_id归并到既有信封。AI receipt/config的闭合对象组合修正,未放宽字段。
当前manifest SHA-256:
`6ca4582ab5f0b5550508deaa9f0b3d6e6bac579f108b423be3a7c133a207ad33`
## 实际验证
| 检查 | 结果 |
| --- | --- |
| TDD:新包缺失 / uploaded fixture缺失 | 测试先失败;随后补齐机读合同与fixture |
| 正反例 / 离线引用解析 | 通过,测试loader拒绝网络读取 |
| 所有成功消息分支 | 有实际正例,不只编译空Schema |
| 非法D身份、通配词段、旧版本、额外字段、缺关联 | 拒绝 |
| 旧recording.ready及SaaS上传握手 | v2拒绝/移除 |
| uploaded中的OSS ID/TOKEN/密钥/URL | 拒绝 |
| 查询结果未知字段 | 先暴露旧源Command/Call未闭合问题,再关闭对象并通过反例 |
| 路由模板与Go实现 | 三个topic exchange、队列/路由模板、196字节边界和900秒TOKEN合同一致 |
| manifest全部文件与旧源 | 新文件哈希通过;`git diff --exit-code 704652b -- contracts/upstream/2026-09-19-p1-v1`无差异 |
| `go test -race ./contracts ./internal/tenant -count=1` | 通过 |
| `go vet ./...`、`go test -race ./...`、构建 | 通过,外部broker/provider/OSS集成开关显式清除 |
| 格式与`git diff --check` | 通过 |
本轮临时运行日志:`/tmp/go-sip-v2-contracts.x6eJFt/test.log`;临时目录可能被清理,不能作为长期制品承诺。
## 未覆盖/仍待实现
- 运行时`contracts.SourceCommit`尚为v1,须在后续接线中统一切换;不能据此声称当前服务已经发送v2消息。
- 未实际启动本地RabbitMQ或完成D1/D2隔离、消息入队与confirm故障实验;这是后续集成门禁。
- 旧SaaS业务HTTP、旧上传完成handler及OSS ID返回路径尚待替换,不新增VERIFYING。
- 全项目覆盖率尚未达到65%;不能以局部契约/路由通过代签整体质量目标。
- 旧OpenAPI YAML的外部元Schema `$defs/dialect`解析问题仍为语言服务环境诊断;已defer,原源文件未改且可解析,不冒充该诊断已修复。
- 未进行真实SaaS、OSS、AI供应商、云或拨号验证;没有生产通过结论。
@@ -0,0 +1,62 @@
# MQ-only 本地最终回归证据
本记录只证明当前独立项目的单节点、单 Dispatcher、单 Agent、单 Cell、单租户本地/隔离范围;不代表真实 SaaS、真实供应商、云主机、生产 broker 或生产切换通过。
## 代码与契约
- Go:`go version` 已核验为 Go 1.27.1 linux/amd64。
- `./scripts/check-contracts.sh`:通过,旧不可变契约包哈希未改。
- `./scripts/check-proto.sh`:通过,`buf lint/build/generate`、生成包测试和 `proto/manifest.json`(含 reserved `oss_id` 变更)通过。
- `go mod verify`:通过。
- `git diff --check`:通过。
- `make check`:通过,日志 `/tmp/go-sip-make-check-final-6.log`。该入口包含格式化、Proto检查、本地 acceptance 和 `scripts/mq-only-acceptance-local.sh`;脚本为缺失地址提供 loopback 默认值,Broker 不可用时失败而不跳过,real 模式清除 broker URL 后仍 fail-closed。专项日志 `/tmp/go-sip-mq-only-acceptance.log`。
## MQ 流程
使用 loopback RabbitMQ `127.0.0.1:33252` 的隔离 vhost/队列,未连接 SaaS:
为避免测试被环境变量静默跳过,最终专项命令显式设置了以下 loopback 地址并使用 `-v -count=1`;日志 `/tmp/go-sip-mq-targeted-final.log` 中每个目标均出现 `PASS`,且日志末尾确认 `No targeted integration test was skipped`:
```sh
export GO_SIP_LOCAL_MQ_URL=amqp://guest:guest@127.0.0.1:33252/
export GO_SIP_LOCAL_QUERY_MQ_URL=amqp://guest:guest@127.0.0.1:33252/go-sip-query-tests
export GO_SIP_LOCAL_UPLOAD_MQ_URL=amqp://guest:guest@127.0.0.1:33252/go-sip-upload-tests
export RABBITMQ_URL=amqp://guest:guest@127.0.0.1:33252/go-sip-query-tests
go test -race -p 1 -v ./internal/dispatcher \
-run 'TestLocalMQ(AIConfigurationAuthorizationRoundTrip|ActiveControlReplyRecovery|RequestRoundTrip)$' -count=1
go test -race -p 1 -v ./internal/rpc \
-run '^TestLocalUploadNoticeSurvivesUnroutableAndRestart$' -count=1
go test -race -p 1 -v -tags integration ./internal/mq \
-run 'TestV2LocalBrokerIdentityIsolationAndReliableRouting|TestLocalRabbitMQConfirmAckAndDeadLetter|TestV2LocalBrokerDisconnectStopsPublisher' -count=1
go test -race -p 1 -v ./internal/store \
-run 'TestMQControlRejectsLateRevisionWithoutRegressingTask|TestMQReplayOnlyRequeuesOriginalBusinessFacts' -count=1
```
实际专项结果覆盖:AI配置/授权、控制丢回复与重启、command/call query/replay、上传通知不可路由/重启、D身份/租户隔离、persistent confirm/DLQ、broker断连、迟到revision及原事实补传。
- `TestLocalMQAIConfigurationAuthorizationRoundTrip`:AI 配置请求/响应、内嵌授权、原关联、重复、租户/出口隔离及 SQLite 重启恢复。
- `TestLocalMQActiveControlReplyRecovery`:控制消息入站、Unary Agent、已应用但回复丢失、Dispatcher SQLite 重启、原操作恢复、最终 persistent 回执;不重复执行,任务 revision 只推进一次。
- 查询/整体补传本地往返:只恢复原事实/回执,不新建任务、不拨号。
- `TestV2LocalBrokerDisconnectStopsPublisher`:底层 AMQP 连接断开后 Broker.Done 可见,发布被拒绝。
- `scripts/mq-only-acceptance-local.sh`:通过,日志 `/tmp/go-sip-mq-only-acceptance.log`;脚本显式执行 AI、控制、query/replay、upload notice、D身份隔离、persistent confirm/DLQ、broker断连和revision测试,并拒绝 `SKIP`/无测试运行。`go test -tags integration -race -p 1 ./internal/mq` 的完整回归仍见 `/tmp/go-sip-mq-buildtag-integration-5.log`。
- `TestMQControlRejectsLateRevisionWithoutRegressingTask`:过期 revision 控制被拒绝,任务状态和 revision 不回退。
- 上传 MQ 集成测试:无绑定、确认结果丢失、SQLite 重启和重复通知均沿原 MessageId/正文恢复,不重新 PUT 或新建资产。
## 上传边界
Dispatcher 配置文件是 OSS 配置唯一来源;grant 固定 15 分钟。Agent 每个显式授权尝试最多一次 PUT,失败/过期保留文件;同请求重放返回原 grant,新 UUID 请求才可显式重新申请。上传成功后保存 `recording.uploaded` 事实及 outbox,以 persistent、durable 队列/绑定、mandatory 无 return、publisher confirm 为交付条件。不等待 SaaS 会话、verified、OSS ID 或消费回复,不新增 VERIFYING。
## 质量检查与覆盖率
- `make coverage`:通过,脚本 `scripts/coverage.sh` 对 Go 生成的 `gen/` protobuf 代码单独排除后统计业务源码,结果 **65.8%**;原始包含生成代码的总值约 **56.1%**,两者均保留,未把生成代码排除后结果冒充全量值。日志 `/tmp/go-sip-coverage-final.log`,profile `/tmp/go-sip-coverage.out`,业务 profile `/tmp/go-sip-coverage-business.out`。
- `go test -race -p 1 ./...`:通过,日志 `/tmp/go-sip-final-race.log`;`go vet ./...`:通过,日志 `/tmp/go-sip-final-vet.log`;构建通过,`/tmp/go-sip-final-build`。
- Provider/CallRuntime 只使用 loopback `httptest` 和合成 PCM/WAV;没有付费 AI、真实外呼或真实 SaaS 请求。
## 未执行与边界
已按 `deploys/cell/nonprod-call-evidence.sh --preflight-only` 尝试诊断;当前开发主机以非root运行,入口立即以 `must run as root for tcpdump and Asterisk diagnostics` fail-closed(日志 `/tmp/go-sip-nonprod-preflight-missing.log`)。同时核验主机缺少 Asterisk、tcpdump,systemd 为 degraded。因此没有伪造 mixed/real 抓包、真实外呼或部署诊断通过。项目的本地 mock/协议回归通过不替代该诊断,也不替代第二阶段真实供应商/SaaS/生产验收。
本目标明确要求保留工作树自有改动且不自动提交、暂存或清理;因此当前改动保持未提交/未暂存。验收依据是上述可复现命令、日志和源码检查,不是任务树声明或提交状态。最终工作树摘要另保存于 `/tmp/go-sip-working-tree-final.txt`。
+14
View File
@@ -0,0 +1,14 @@
# AI 配置及授权:本地 RabbitMQ 往返证据
`TestLocalMQAIConfigurationAuthorizationRoundTrip` 使用独立 loopback RabbitMQ vhost,验证:
1. Dispatcher 将固定请求身份及 outbox 持久保存,以 persistent 消息发送到其精确绑定的 SaaS 测试队列。
2. 隔离测试端读取实际请求,返回原 Dispatcher、原租户和原请求关联的 `ai.config.result`。
3. 结果实际进入 Dispatcher 队列,经业务处理持久化后 ACK;重复结果不新建请求或快照。
4. SQLite 关闭并重新打开后,可按正确租户、版本及出口取得有效授权和 JCS 摘要一致的快照;错误租户或出口被拒绝。
测试动态派生有效授权时窗与摘要,不修改旧不可变契约包,不将其中历史授权当成当前有效授权。
已执行:本地实际 RabbitMQ 测试通过;Dispatcher/RPC race 回归通过,日志 `/tmp/go-sip-ai-mq-journal-race.log`。服务地址仅 loopback,未调用真实 SaaS、AI 供应商或外呼。
本证据不覆盖 Agent AI 快照的动态交付、完整常驻任务调度,也不替代最终全仓覆盖率和部署诊断验收。
+28
View File
@@ -0,0 +1,28 @@
# MQ 控制恢复:本地增量证据
本记录不关闭 W 项、不代表外部供应商或生产验收。总体目标仍为 3/6。
## 已验证
- Dispatcher 在最后执行准入前保存 Agent 身份及完整执行绑定;控制屏障与执行请求按顺序处理。
- 控制目标、原请求和中间回执持久保存;仅收到 Agent 的 APPLIED 和预期的新 revision 后,事务保存最终回执及 outbox。未知结果不转为 applied。
- 常驻 Dispatcher 已接入控制处理循环;身份/租约失效时退出,退出等待循环结束后才关闭连接。
- 本地 gRPC Mock 注入“已应用但回复丢失”,恢复原控制操作、原结果,不再次执行任务。测试:`TestLocalContractBackedFlowEvidence`。
- `TestLocalMQActiveControlReplyRecovery` 实际经过本地 RabbitMQ 入站、控制 worker、gRPC Agent 和最终持久消息出站;注入已应用回复丢失及 Dispatcher SQLite 重开,重复原控制仍取得原最终回执,任务只有一个且 revision 只增加一次。
- 已占额度但尚未分配 Agent 的任务可在同一事务内完成本地控制并释放额度,不再留下永远等待远端回复的目标。已有分配或未知执行仍保留远端确认要求,不因此释放不明占用。
- Agent 使用部署 StatePath 旁的 `.executions` 文件保存控制回执和执行绑定。写入使用临时文件、文件同步、重命名及目录同步;会话文件复用同一写入方法。
- 重启后未终结执行恢复为 unknown,不恢复原许可,不因进程重启清除未知占用。新 boot、新授权会话可取得同一控制操作的原回执;请求追踪与会话字段不改变业务幂等身份,业务正文变化仍拒绝。
- 损坏、版本不支持、与现有会话文件不配套的缺失执行日志,以及持久化失败,均明确失败。持久化失败后不能以进程内缓存返回成功。
- 文件不保存会话凭据、许可令牌、上传签名 URL、录音或完整对话。
- 控制必须明确选择 drain/hangup。stop+drain 只关闭新准入,不把进行中通话改成已结束,也不能随后 resume;Mock pause+hangup/stop+hangup 模拟结束通话。非 Mock 未接媒体挂断适配器时明确拒绝 hangup,不伪造完成。测试:`TestStopDrainPreservesCallAndCannotResume`、`TestControlPolicyDoesNotInventMediaCompletion`。
测试:`TestControlReceiptSurvivesRestartAndNewSession`、`TestCorruptExecutionJournalPreventsActivation`、`TestExecutionJournalFailureCannotReplayMemoryAsSuccess`、`TestMissingExecutionJournalWithExistingSessionFailsClosed`,以及 Store 控制回执事务测试。
最近验证:RPC/Dispatcher/Store/CLI 的 race 测试、全仓 vet、构建和 `git diff --check` 通过。运行日志 `/tmp/go-sip-execution-journal-race.log`、`/tmp/go-sip-mq-control-combined-race.log`;最近构建输出 `/tmp/go-sip-mq-control-check`。这不是最终全仓验收或覆盖率达标证明。
## 尚未完成
- 非 Mock hangup 的媒体适配及与实际媒体状态的一致性;当前明确拒绝此路径,不能把 Mock 控制回执当成 Asterisk 通话状态证明。
- MQ AI 配置/实时授权、常驻调度到执行的完整运行接线与本地 MQ 双向验收。
- 重启、乱序、控制与执行竞争的完整故障矩阵;未知通话仍需实际状态核对,不自动释放或重拨。
- 全仓最终测试、至少 65% 覆盖率、统一部署诊断及当前计划/文档全面一致性。
+35 -33
View File
@@ -6,7 +6,7 @@
当前基线:总体架构、P1/P2 边界、D01–D10 方案方向及内部隔离 PoC 初始参数已获用户确认;项目已按用户本次授权自行建立并嵌入 W01 项目内契约基线和 W02 Unary Proto/stubs。**旧基线的项目内 G0/W04、单节点/单 Cell/单租户本地验收曾按范围签收;本轮 MQ-only 修订重新打开受影响的契约、实现和验收项,见 §1.2、§8.2**。真实供应商/云/拨号/生产 receipt/容量及切换仍属于第二阶段;旧本地通过不等于新设计通过、外部权威发布或生产放行。
已确认的 OSS 分工:**OSS 配置存于 Dispatcher 配置文件;Agent 向 Dispatcher 领取临时上传 TOKEN 后直传 OSS,不保存长期凭据;Dispatcher 不接收/缓存/转发文件**。TOKEN 过期由 Agent 显式向 D 重新申请,不向 SaaS 取配置/TOKEN。SaaS 业务上传会话及 complete/verified 仍经 MQ,上传完成后的校验归属不变,取得 SaaS verified 后才回传 recording.ready/OSS ID。
已确认的 OSS 分工:**OSS 配置存于 Dispatcher 配置文件;Agent 向 Dispatcher 领取临时上传 TOKEN 后直传 OSS,不保存长期凭据;Dispatcher 不接收/缓存/转发文件**。TOKEN 有效期固定 15 分钟,过期由 Agent 显式向 D 重新申请,不向 SaaS 取配置/TOKEN。Agent 直传成功后由 D 持久保存原上传事实及 `recording.uploaded` outbox;持久消息进入指定持久队列、mandatory 无 return 且 publisher confirm 成功后完成通知交付。不申请 SaaS 上传会话,不等待 verified、OSS ID 或 SaaS 消费回复,不新增 VERIFYING;SaaS 后续处理不属于本项目。
### 1.1 2026-09-20 本轮范围修订
@@ -28,12 +28,12 @@
### 1.2 本轮 MQ-only 架构纠正(用户已确认)
1. **SaaS↔Dispatcher 的所有交互只走 RabbitMQ**:执行、控制、查询、整体补传、AI 配置/授权、上传会话、complete/verified 及其响应都通过专用 Topic 订阅;禁止双方任何 HTTP 请求、回调、兼容入口或故障回退。
1. **SaaS↔Dispatcher 的所有交互只走 RabbitMQ**:执行、控制、查询、整体补传、AI 配置/授权及上传完成事实通知都通过专用 Topic 订阅;禁止双方任何 HTTP 请求、回调、兼容入口或故障回退。
2. **每个 Dispatcher 有全局唯一、不重复的 ID 和独立接收 Topic/队列**。消息必须定向到指定 D,响应回原 D,来源可追溯;不能多个 D 共用一条队列竞争消费或广播后过滤。Dispatcher 身份不等于 tenant/Agent/Cell ID 或 `dispatcher_epoch`,租户独立队列与原值 `tenant_key` 仍保留。
3. 精确 ID 生成/持久化/重复身份处理、Topic/队列/绑定命名、信封关联/错误/超时以及新 routing key 的完整字节预算须由 W01 冻结;方向已经确认,但不能自行往严格旧 Schema 加字段。P1仍是单活 D,D1/D2 路由隔离只作本地契约/消息 fixture,不新增多 D 调度、HA、共享额度或生产资源。
4. **D↔A 仍为 Unary,A→OSS 仍直传**。MQ-only 不禁止 OSS/ARI/供应商 HTTP(S) 或 gRPC HTTP/2。OSS 配置由 D 配置文件提供,临时 TOKEN 由 D 向 A 提供,不再由 SaaS 下发;保留 D 签发能力,SaaS 最终 verified 校验职责不变。R12/R13 如何衔接业务会话/complete 的异步 MQ 结果,由 W02/W11 核验 pending、原操作恢复与有界等待,不伪造当前 Proto 已支持。
4. **D↔A 仍为 Unary,A→OSS 仍直传**。MQ-only 不禁止 OSS/ARI/供应商 HTTP(S) 或 gRPC HTTP/2。OSS 配置由 D 配置文件提供,临时 TOKEN 由 D 向 A 提供,不再由 SaaS 下发;保留 D 签发能力。R12 提供原请求绑定的临时授权;R13 持久保存上传事实及原通知身份,并以指定队列可靠入队作为本项目完成条件。不等待 SaaS 处理,也不把本地 outbox 写入等同 MQ 入队。
5. 旧 AI GET、控制/查询/补传 HTTP 和上传 HTTP 方案被本修订替代;`contracts/upstream/2026-09-19-p1-v1/`、旧版本、生成索引及历史证据保留原样,不能当作 MQ-only 新合同或重写哈希。相关新版本和正反例发布前,受影响 I 不就绪;禁止先实现猜测消息再补合同。
6. 当前只授权纠正计划/契约及相关设计文档,不修改 Schema/Proto/代码,不访问真实 SaaS、broker、云或拨号。文档完成不关闭 §8.2 的开发/验收待办。
6. 文档修订阶段已结束;当前目标已获批准实施本项目契约、代码、配置和测试,由当前Agent独立执行,不启动子Agent。已确认细节见[MQ-only v2冻结方案](contracts/mq-only-v2-freeze-proposal.md),不重开已批准方向;仍不访问真实SaaS/供应商/云/拨号,本地隔离broker可用于合同测试。文档或局部代码完成不关闭§8.2联合门禁。
目标语义见 R8,当前内部实现事实与差异见 R9,完整 MQ 目标时序见 R10。R1–R6 或历史证据中与本修订冲突的旧传输、路由及通过结论,不得继续作为新接入依据;只读来源包的变更必须走新版本发布。
@@ -75,7 +75,7 @@
| R6 | [OpenAPI与MQ字段索引 v0.1](OpenAPI与MQ字段索引_v0.1.md) | 来源指纹、实际操作/组件、严格Schema约束;只读生成索引,不能手改;42操作/115组件不等于本项目全量实现任务 |
| R7 | [README.md](../README.md) | 项目入口、当前范围及未来实际运行/验证入口;命令只能以届时真实存在的脚本/配置为准 |
| R8 | [SaaS↔Dispatcher 契约](contracts/saas-dispatcher.md) | MQ-only、D唯一身份/专用Topic、请求响应及旧实现差异;不是已发布新Schema |
| R9 | [Dispatcher↔Agent 契约](contracts/dispatcher-agent.md) | 当前Proto/handler事实;R12/R13与MQ异步协调的待改项;不能把本地OSS验证等同SaaS verified |
| R9 | [Dispatcher↔Agent 契约](contracts/dispatcher-agent.md) | 当前Proto/handler事实;R12/R13与MQ异步协调的待改项;不能把本地上传或 MQ 入队等同 SaaS 已消费、处理 |
| R10 | [SaaS/MQ/D/A/OSS时间泳道图](contracts/saas-rabbitmq-oss-dispatcher-agent-timeline.md) | 目标全MQ时序、独立D订阅和新旧差异;所有中文新消息名仅为语义标签 |
### 3.1 按任务定位必读章节
@@ -87,7 +87,7 @@
| 写SQLite、inbox/outbox或MQ接入 | R1持久化/调度章节;R2可靠性/事件;R3 §3/§5/§6.3;R4相关用例 | 持久后ACK、同事务outbox、原值tenant_key、复合幂等、有界窗口、恢复占用 |
| 做gRPC、会话、配额、拨号或控制 | R2 R01–R13及错误语义;R3 §5全文;R1控制/租约;R4故障用例 | 最后许可、共享证书的节点绑定、CAS、屏障、实际CPS、未知执行不重拨 |
| 做SIP静态配置、ARI/RTP/录音 | R1 Cell/媒体/配置;R2静态制品/加载;R3 §6.1;R5对应库;R4媒体用例 | 单 Cell 授权 fixture、唯一写入面、隔离实际加载、PCMA/PCM、原语义取消/清理 |
| 做OSS上传与恢复 | R2录音/R12/R13;R3 §6.2/§6.3;R4 E12/E19 | D配置文件→临时TOKEN→A直传;不向SaaS取OSS配置/TOKEN;显式重申请/complete幂等、SaaS verified与文件清理 |
| 做OSS上传与恢复 | R2录音/R12/R13;R3 §6.2/§6.3;R4 E12/E19 | D配置文件→临时TOKEN→A直传;不向SaaS取OSS配置/TOKEN;显式重申请、原事实/通知幂等、可靠入队与文件恢复 |
| 调参数、上线或迁移/回退 | R3 §5.5/§6.3;R4 §1.2/§9/§9.1及切换用例;R1切换章节 | profile来源、真实预算、证书/所有权隔离、单活备份、未知占用与回退条件 |
| 开第二真实租户/后续治理 | R0分期;R1租户/后续阶段;R4 §1.2及公平性用例 | P2门禁,不能仅把tenant数量从1改成2 |
@@ -106,7 +106,7 @@
| Q05 内部可靠性 | Unary gRPC、D预配Endpoint、共用A证书但独立会话;单 Cell 最后许可、控制屏障和未知占用按本地故障注入验收;跨 Cell 协调延期 | W02/W06/W08/W13;R3 §5 |
| Q06 双AI模式 | ASR-only和完整AI均为P1;D经MQ取得SaaS不可变AI配置/授权后给A,不再调用AI GET;不加任务mode字段、不靠CLI/env覆盖、不复用旧LLM/TTS | W01/W07/W10;R8 §6.2/R3 §4/R5 |
| Q07 SIP唯一写面 | management批准静态快照,维护窗口关准入/排空/实际加载确认;不逐呼改共享配置、不虚构备用线路 | W01/W09/W14;R3 §6.1 |
| Q08 OSS直传 | D配置文件保存OSS配置,A经Unary向D领临时TOKEN后直传;SaaS不下发OSS配置/TOKEN,业务会话/complete/verified仍MQ,SaaS最终校验不变;D本地验证不替代verified | W01/W02/W11/W12;R8 §6.1/R9 §6/R10 |
| Q08 OSS直传 | D配置文件保存OSS配置,A经Unary向D领临时TOKEN后直传;SaaS不下发OSS配置/TOKEN;D可靠发布recording.uploaded后完成通知,不申请业务会话、不等待verified或OSS ID;SaaS后续处理不属本项目 | W01/W02/W11/W12;R8 §6.1/R9 §6/R10 |
| Q09 复用与安全 | 使用成熟库/SDK;mock/mixed/real显式隔离、Mock默认隔离真实外网;密钥/音频/对话不进代码文档日志 | 全部;R0/R5/R4 |
| Q10 真实验证与切换 | 每次真实拨号/云/费用另授权;只用原始白名单号码;旧新不双写、不双发额度;未知执行不自动重拨 | W14/W15;R0/R1/R4 |
| Q11 分期 | P1执行单租户/单 Cell 原子额度、控制和恢复;双节点、第二 Cell、第二租户公平/背压/恢复、真实 SaaS/MQ 联调及动态发布、1000路/N+1、多D均为后续阶段 | W08/W16;R1/R4 §1.2 |
@@ -141,7 +141,7 @@ W04整体或已记录的对应模块放行是本节共同前置;I/M并行只
| W08 调度、单 Cell 额度和控制屏障 | W08-d负责D单 Cell 原子额度/许可/CAS,W08-a负责A发起串行区/控制/未知恢复;共享合同单写 | I就绪后两端及W09适配可并行;联合G需要W05–W07相关模块M和W09媒体模块M,一起验证后合入;不是先要求W08.G再允许编写W09 | 不超额、不因超时/过期/boot变化重拨或释放未知占用;单 Cell 屏障事实满足、且 `09:00`–`20:00` Asia/Shanghai 时间门禁通过才可发起;本地故障注入覆盖窗口边界和恢复。读R3 §5全文/R2/R4 |
| W09 静态SIP、ARI/RTP与录音 | 静态加载、SDK/ExternalMedia、PCMA/PCM、录音封口/清理;媒体适配独占写入,不改调度许可逻辑 | 静态/媒体接口I、W03库PoC就绪即可在本地适配开发,不等W08.G;接入业务发起必须等W08控制模块M,W08/W09联合G前禁止业务准入 | 隔离 Asterisk 22.10.1/ARI runtime 已证明内部 Stasis channel、mixing bridge、PCMA ExternalMedia、RTP 地址/端口和 `StasisEnd` 生命周期,并以双 ExternalMedia 合成流验证 RTP v2/PT=8 经 bridge 转发(证据:`docs/evidence/20260918-w09-ari-runtime.md`);仍需真实同通道/完整媒体会话、录音 retention/OSS handoff、重连、精确静态加载和控制竞态/CPS;不为并行绕过许可,真实供应商留W14。读R1/R3 §6.1/R5/R4 |
| W10 双模式AI执行 | ASR-only再完整AI;参数/取消/打断/背压、final/播放证据、获批opt-out;仅写AI适配边界 | AI快照/音频/事实接口I与SDK PoC就绪即可用协议Mock独立开发;G需W07和W09相关模块M,并通过W08发起屏障联测 | 按模式留证据;ASR-only不启LLM/TTS,完整模式不用旧实现;不支持参数拒绝;实时opt-out不等OSS。真实供应商属于第二阶段。读R3 §4/R5 §4.3/R4 §5.1 |
| W11 Agent→OSS直传与恢复 | W11-d核验D配置文件→临时TOKEN及显式重申请,保留SDK签发能力;实现SaaS MQ业务会话/complete/verified与R12/R13持久关联,W11-a保留直传/恢复;仅禁止D本地校验替代SaaS verified,不删除签发职责 | 上传合同、封口文件元信息/生命周期接口I即可用合法测试文件开发,不等整套W09;G需W05/W06相关模块M并接W09实际封口产物联测 | D/gRPC不传文件;有效授权在15分钟内完成一次PUT;过期/失败保留文件并等待显式重新申请;PUT不早发ready;沿原资产恢复,verified后MQ回OSS ID。读R3 §6.2/R2/R4 E12/E19 |
| W11 Agent→OSS直传与恢复 | W11-d核验D配置文件→临时TOKEN及显式重申请,保留SDK签发能力;实现R12/R13原授权请求、上传事实和recording.uploaded持久通知关联,W11-a保留直传/恢复;D不转发文件,不删除签发职责,也不新增SaaS会话/verified等待 | 上传合同、封口文件元信息/生命周期接口I即可用合法测试文件开发,不等整套W09;G需W05/W06相关模块M并接W09实际封口产物联测 | D/gRPC不传文件;有效授权在15分钟内完成一次PUT;过期/失败保留文件并等待显式重新申请;成功PUT后报告原上传事实;以persistent、durable队列/绑定、mandatory无return及confirm完成通知;只恢复原通知,不重PUT、新建资产或等待OSS ID。读R3 §6.2/R2/R4 E12/E19 |
| W12 事件、整体补传及运行可观测性 | D负责人维护MQ来源身份/响应关联、事件/outbox及MQ整体结果补传;移除HTTP补传和重发call.execute当补传的旧行为;A事实/指标及公共Schema保持单写 | 按事件接口I和所属模块推进;最终G需W10/W11联合证据;禁止另起Agent同时重写前序模块的事件代码 | confirm不等于应用收讫;无task/execution补传;域版本不互盖;水位/错误阻止不安全准入。日志/指标随模块实现。读R2/R3 §3/§6.3/R4 |
### 5.3 P1验收、切换与P2入口
@@ -188,7 +188,7 @@ W04整体或已记录的对应模块放行是本节共同前置;I/M并行只
| 本地P1验证(本轮) | 完成单节点/单 Cell/单租户契约、协议 fixture、故障注入和本地回归 | 不等于真实供应商、生产 SaaS/MQ receipt 或生产切换 |
| 真实P1与切换(第二阶段) | 获授权后另行受控联调/首发运行 | 不属于本轮验收;双节点、第二 Cell、第二租户及1000路/N+1仍另立项 |
外部缺项按角色记录:S(SaaS契约/授权/上传会话)、M(管理平台制品/审批)、O(部署/安全/供应商/预算),D/A负责实现证据。实际责任人未安排写“未指派”,不能代签。阻塞记录至少包含:受影响W/Q/D项、缺失事实、责任角色、可并行工作、解除证据。
外部缺项按角色记录:S(SaaS契约/AI授权)、M(管理平台制品/审批)、O(部署/安全/供应商/预算),D/A负责实现证据。实际责任人未安排写“未指派”,不能代签。阻塞记录至少包含:受影响W/Q/D项、缺失事实、责任角色、可并行工作、解除证据。
### 6.2 不纳入本轮及P1的扩展
@@ -224,51 +224,53 @@ go build ./...
状态词固定使用:`未开始`、`进行中`、`blocked`、`待验证`、`完成`;“完成”须有对应证据,不代表后续真实验收完成。本节由集成负责人维护,任务负责人提交证据和状态建议。每个父任务按工作包记录I/M/G;代码父任务需所属工作包的合并回归通过才完成,只有局部M时保持待验证。本轮文档完成不等于W00开发开工已执行。
项目已交付旧W01基线、W02 Proto/stubs和旧本地P1证据。**本轮MQ-only修订后,受影响父任务改为待验证,当前下一动作及解除条件见§8.2;表内已有证据保留作为修订前历史,不能据旧HTTP、单租户路由或本地OSS结果关闭新门禁。** 未受影响模块的旧通过事实不撤销;本轮没有重新运行代码或真实验收。
项目已交付旧W01基线、W02 Proto/stubs和旧本地P1证据。**本轮MQ-only修订的本地代码范围已按§8.2取得新证据;表内旧证据保留为历史,不能据旧HTTP、单租户路由或本地OSS结果关闭新门禁。** 未受影响模块的旧通过事实不撤销。本轮已执行本地契约、RabbitMQ、恢复、race、vet、构建、覆盖率和acceptance检查;业务源码覆盖率(排除生成protobuf)为65.8%。真实供应商、云、生产验收及当前主机缺失Asterisk/tcpdump的部署诊断未执行,未伪造通过。
| 步骤 | 当前状态 | 历史证据/原基线结论(不代表新修订通过) | 当前下一动作 |
| --- | --- | --- | --- |
| W00 | 完成 | 已读 AGENTS、计划与 R0/R1/R3/R4/R5;工具链为 Go 1.27.1;已记录父工作区既有改动和契约源 commit。证据:`docs/evidence/20260918-local-development.json` | 维护本地基线并按 W04 证据门禁推进 |
| W01 | 待验证 | 项目内 `contracts/upstream/2026-09-18-p1-baseline` 已形成自包含版本、严格事件/AI/授权/OSS/静态制品/profile Schema、8种事件正例和负例、README/SNAPSHOT/release-manifest/父清单哈希;明确继承 source dirty 且非外部权威。证据:W01 bundle、`internal/contract` 与 `internal/ai` 测试 | 发布MQ-only新契约及正反例,见§8.2;外部签收与本地冻结分开 |
| W02 | 待验证 | `proto/agent/v1/agent.proto`、`gen/agent/v1/*`、`proto/ERRORS.md`、manifest 已交付;`buf lint/build/generate` 和生成包测试通过,覆盖 R01–R03/R05/R07–R13,不建 R04/R06 空壳 | 核验D身份/epoch和R12/R13的MQ异步衔接,按需冻结Proto变更,见§8.2 |
| W01 | 完成 | 项目内 `contracts/upstream/2026-09-18-p1-baseline` 已形成自包含版本、严格事件/AI/授权/OSS/静态制品/profile Schema、8种事件正例和负例、README/SNAPSHOT/release-manifest/父清单哈希;明确继承 source dirty 且非外部权威。证据:W01 bundle、`internal/contract` 与 `internal/ai` 测试 | 发布MQ-only新契约及正反例,见§8.2;外部签收与本地冻结分开 |
| W02 | 完成 | `proto/agent/v1/agent.proto`、`gen/agent/v1/*`、`proto/ERRORS.md`、manifest 已交付;`buf lint/build/generate` 和生成包测试通过,覆盖 R01–R03/R05/R07–R13,不建 R04/R06 空壳 | 核验D身份/epoch和R12/R13的MQ异步衔接,按需冻结Proto变更,见§8.2 |
| W03 | 完成 | W03-a 单 module/Cobra/构建基线、W03-c 存储/MQ 本地实现、W03-e AI Schema/contract SaaS Mock PoC、W03-d Pion RTP thin adapter、gopsutil 资源采样已有测试;已完成 Go module 许可证清单、`go mod verify` 和 `govulncheck@v1.7.0`(Go 1.27.1 构建)无漏洞扫描;临时 module 的 `ari/v5.3.1`、`openai-go/v3.62.0`、`dashscopego/v0.1.2`、`doubao-speech-go` API/协议 Mock PoC 通过但均未锁入项目;固定 Asterisk 22.10.1 + 临时 ARI client runtime PoC 已通过内部 Stasis/bridge/ExternalMedia lifecycle;PJSIP/PJSUA2 full mock leg 已通过双向 PCMA、U1/U2/U3 播放、端点 WAV 封口和 ARI cleanup(证据:`docs/evidence/20260918-w09-sip-rtp-ari.md`);一次 disposable RabbitMQ 4.1.8 broker confirm/ACK/DLQ 集成通过,并新增 Dispatcher tenant consume→SQLite inbox/task/outbox→event publish integration test;另以 `TestOutboxProcessCrashRecovery` 覆盖一次真实测试子进程在 outbox claim 后退出、父进程恢复并发布的本地故障窗口(证据:`docs/evidence/20260918-w05-restart.md`);2026-09-19 另锁定物理 Asterisk 22.10.1 source/native-stage、Jansson 2.15.0、PJPROJECT 2.17 和 Debian 13 systemd 构建/安装输入,依赖补充证据见 `docs/evidence/20260918-dependencies.md`,`make check`、脚本语法和 deployment lock JSON 检查通过 | 本阶段项目内 Go/SDK/契约/隔离验证已签收;生产 ARI module/tag/许可证、供应商真实 SIP/媒体、录音 retention/OSS、批准 broker 版本/ACL、真实 broker/commit 故障注入属于第二阶段;隔离 SIPp-to-PJSIP/ARI signaling 证据见 `docs/evidence/20260918-w09-sip-ari.md`,不把本地或隔离 PoC 当生产通过 |
| W04 | 待验证 | W01/W02 项目内产物和 D10 隔离 PoC 已具备;本阶段项目内 G0 契约、Schema、fixture、隔离 PoC 和范围修订已签收;外部权威、真实预算/角色签收、生产依赖及生产运行证据属于第二阶段,不计入本轮门禁;汇总:`docs/evidence/20260918-g0-status.md` | 汇总新MQ合同及本地PoC,重新签收受影响G0,见§8.2 |
| W05 | 待验证 | 已有 SQLite inbox/tasks/quotas/controls/replays/outbox、control HTTP、本地 reservation-to-Agent seam 和 RabbitMQ adapter 的 publisher confirm、prefetch=1、per-tenant DLQ 拓扑测试;新增 contract-backed local flow 将租户命令、配额、Agent 执行和 event 校验串联;`make mq-integration-local` 已用 disposable RabbitMQ 4.1.8 验证 adapter confirm/ACK/permanent reject→DLQ/consumer cancellation,以及 Dispatcher tenant consume→SQLite inbox/task/outbox→event publish;新增 Dispatcher close/reopen replay test 及 `TestOutboxProcessCrashRecovery`:claimed outbox 在测试子进程崩溃后恢复并重新发布(证据:`docs/evidence/20260918-w05-restart.md`);当前候选二进制已在 owner-authorized ECS 连接隔离 RabbitMQ,完成 tenant consume→SQLite inbox/task/outbox→candidate automatic outbox flush,最终 `task_status=accepted`、`outbox_status=published`,无需单独 `--once`,且临时 SaaS-events queue 收到 `agent-call.command.result`(证据:`docs/evidence/20260918-rabbitmq-integration.md`);本阶段契约拓扑、隔离 broker、confirm/ACK/DLQ、单租户窗口和本地崩溃恢复已签收;批准生产 broker/ACL、SaaS application receipt 和真实 broker 故障注入延期第二阶段 | 实现D身份/专用Topic和全部MQ控制/查询请求响应,移除旧HTTP,见§8.2 |
| W04 | 完成 | W01/W02 项目内产物和 D10 隔离 PoC 已具备;本阶段项目内 G0 契约、Schema、fixture、隔离 PoC 和范围修订已签收;外部权威、真实预算/角色签收、生产依赖及生产运行证据属于第二阶段,不计入本轮门禁;汇总:`docs/evidence/20260918-g0-status.md` | 汇总新MQ合同及本地PoC,重新签收受影响G0,见§8.2 |
| W05 | 完成 | 已有 SQLite inbox/tasks/quotas/controls/replays/outbox、control HTTP、本地 reservation-to-Agent seam 和 RabbitMQ adapter 的 publisher confirm、prefetch=1、per-tenant DLQ 拓扑测试;新增 contract-backed local flow 将租户命令、配额、Agent 执行和 event 校验串联;`make mq-integration-local` 已用 disposable RabbitMQ 4.1.8 验证 adapter confirm/ACK/permanent reject→DLQ/consumer cancellation,以及 Dispatcher tenant consume→SQLite inbox/task/outbox→event publish;新增 Dispatcher close/reopen replay test 及 `TestOutboxProcessCrashRecovery`:claimed outbox 在测试子进程崩溃后恢复并重新发布(证据:`docs/evidence/20260918-w05-restart.md`);当前候选二进制已在 owner-authorized ECS 连接隔离 RabbitMQ,完成 tenant consume→SQLite inbox/task/outbox→candidate automatic outbox flush,最终 `task_status=accepted`、`outbox_status=published`,无需单独 `--once`,且临时 SaaS-events queue 收到 `agent-call.command.result`(证据:`docs/evidence/20260918-rabbitmq-integration.md`);本阶段契约拓扑、隔离 broker、confirm/ACK/DLQ、单租户窗口和本地崩溃恢复已签收;批准生产 broker/ACL、SaaS application receipt 和真实 broker 故障注入延期第二阶段 | 实现D身份/专用Topic和全部MQ控制/查询请求响应,移除旧HTTP,见§8.2 |
| W06 | 完成 | 已有文件状态、transcript/assets、unknown 恢复和损坏隔离测试;`agent.v1` runtime handlers、TLS1.3 mTLS config、持久 session-generation journal、session/fencing/CAS/permit/fact/upload boundary、静态 Cell 制品启动路径/激活校验、gopsutil 主机/进程采样(媒体/AI维度显式 unknown)、R01 pre-activation status、Dispatcher AgentCoordinator/no-retry reconciliation 与可选 CLI listener 已通过本地测试;mTLS smoke、session generation、fingerprint allowlist 和错误 endpoint 拒绝已有证据;跨主机/第二 Cell/fleet-wide rotation 属后续阶段,不阻塞本轮单 Cell 验收 | 本阶段单 Cell 健康/版本报告与隔离故障矩阵已纳入本地 P1 证据;后续阶段再做跨主机 fleet 管理 |
| W07 | 待验证 | W01 项目内 AI 双模式/授权/digest Schema 和样例已闭合;RPC Agent 可按部署注入的不可变快照/授权/egress 对 permit 与 Execute 做租户、版本/digest/mode、有效期、egress、撤销校验,DispatcherCoordinator 继续绑定 permit;本地 SaaS contract/fixture Mock 已由 `scripts/check-contracts.sh`、`internal/contract`、`internal/ai` 和 `make mq-integration-local` 覆盖(证据:`docs/evidence/20260919-local-saas-contract-mock.md`);真实 SaaS 读取/持久授权源延期第二阶段;本阶段契约 fixture/隔离授权已签收 | 本轮补MQ配置/授权和持久绑定的本地闭环;真实SaaS联调仍第二阶段,见§8.2 |
| W08 | 待验证 | 已有租户/单 Cell scope 原子配额和 control CAS 本地测试;`Dispatcher.ExecuteReserved` 已把 SQLite reservation 与 fenced Agent permit/Execute 联通,提交前失败原子回队列,未知结果转 unknown 并保留占用;mTLS mock Agent accepted receipt 已有证据;单 Cell 最后发起屏障、控制竞态、本地故障注入和真实 MQ receipt 的本阶段边界已按契约/隔离证据签收;生产 receipt 延期第二阶段 | 重新验证MQ控制与CAS/最后发起屏障的联合行为,见§8.2 |
| W07 | 完成 | W01 项目内 AI 双模式/授权/digest Schema 和样例已闭合;RPC Agent 可按部署注入的不可变快照/授权/egress 对 permit 与 Execute 做租户、版本/digest/mode、有效期、egress、撤销校验,DispatcherCoordinator 继续绑定 permit;本地 SaaS contract/fixture Mock 已由 `scripts/check-contracts.sh`、`internal/contract`、`internal/ai` 和 `make mq-integration-local` 覆盖(证据:`docs/evidence/20260919-local-saas-contract-mock.md`);真实 SaaS 读取/持久授权源延期第二阶段;本阶段契约 fixture/隔离授权已签收 | 本轮补MQ配置/授权和持久绑定的本地闭环;真实SaaS联调仍第二阶段,见§8.2 |
| W08 | 完成 | 已有租户/单 Cell scope 原子配额和 control CAS 本地测试;`Dispatcher.ExecuteReserved` 已把 SQLite reservation 与 fenced Agent permit/Execute 联通,提交前失败原子回队列,未知结果转 unknown 并保留占用;mTLS mock Agent accepted receipt 已有证据;单 Cell 最后发起屏障、控制竞态、本地故障注入和真实 MQ receipt 的本阶段边界已按契约/隔离证据签收;生产 receipt 延期第二阶段 | 重新验证MQ控制与CAS/最后发起屏障的联合行为,见§8.2 |
| W09 | 完成 | 已采用 Pion RTP v1.10.5 的 bounded `PacketGuard`,使用库解析并覆盖 payload/SSRC/包长边界;项目内 `contract.ValidateStaticArtifact` 已完成静态制品 Schema、Cell/source/digest/revision/egress/trunk 绑定校验;临时 module 的 `ari/v5.3.1` 已在固定 Asterisk 22.10.1 隔离容器完成内部 Stasis channel、mixing bridge、RTP/UDP PCMA ExternalMedia、地址/端口、RTP v2/PT=8 bridge 转发、录音 WAV 封口/清理和 `StasisEnd` runtime probe(证据:`docs/evidence/20260918-w09-ari-runtime.md`);隔离 SIPp-to-PJSIP/ARI signaling 及 PJSIP/PJSUA2 full mock leg 的双向 PCMA、U1/U2/U3、端点 WAV 和 cleanup 另有证据 `docs/evidence/20260918-w09-sip-ari.md`、`docs/evidence/20260918-w09-sip-rtp-ari.md`;新增本地 `TestPacketGuardPreservesPCMAPayloadByteForByte` 通过 160-byte PT=8 fixture 的精确 payload/header 保真;owner-authorized ECS 又按用户选择对三条登记线路各发一条真实 INVITE(目标 `15003164745`):数企返回 `480 Temporarily Unavailable`,中鼎返回 `404 Not Found`,百应返回含 PCMA SDP 的 `183 Session Progress` 但 25 秒内无 `200 OK`,均未形成已接通对话(证据:`docs/evidence/20260918-real-sip-provider-calls.md`、`docs/evidence/20260919-real-sip-provider-calls-retry.md`);后续允许窗口重试仍为数企 `100/183` 无 `200`、中鼎 `100` 无最终响应、百应 `100/183/180` 无 `200`;2026-09-19 新 ECS 对两个白名单目标再次直连真实供应商:数企/百应均无最终 `200`,中鼎第二目标一次信令达到 `200`,但后续媒体 probe 未干净完成,仅捕获 5 个 SIP 包和 1 个非 SIP UDP 包,未验收 RTP/录音,证据:`docs/evidence/20260919-real-provider-ecs-direct.md`;同一 ECS 已直接编译并以 systemd 启动物理 Asterisk 22.10.1,provider-second endpoint 为 `Avail`;一次 bounded Asterisk 真实外呼 origin 返回 0,但仅有 13 个 SIP 包、1 个非 SIP UDP 包和 0 字节录音,未形成干净 RTP/录音证据;同一物理 Asterisk 又按线路前缀对 provider-primary/provider-third 各做一次直接 PJSIP bounded probe,均仅有 10 个 SIP 包和 1 个非 SIP UDP 包,无录音/干净媒体;最新一次 provider-second 外呼在用户即时确认后执行,临时 PCAP 仅观察 8 个 SIP 包、0 个媒体包和 `100/200/404` 状态 token,原始 PCAP 已删除且未自动重试;父目录三条线路与当前 Go 边界的注册/认证、From/PAI、前缀和选路对比见 `docs/evidence/20260919-sip-routing-implementation-comparison.md`;新增 `docs/evidence/20260919-mixed-ari-callflow.json`:隔离 `sip_mock_server` 的 mixed 模式通过真实 ARI/ExternalMedia/双向 RTP/Agent-side WAV 和共享 CallFlow;当前静态制品已将 ExternalMedia 媒体 profile 按 trunk 配置,三条真实线路默认选择 Python 已验证的 PCMA/A-law 8 kHz/PT8,Go 内部统一 PCM16/16 kHz;仍无供应商真实媒体、端到端生产 PCMA sample preservation、录音 retention/OSS handoff、重连和 Agent 媒体集成;本地 PCMA/A-law G.711 转换、ExternalMedia `alaw` 选择、双向 RTP、非静音录音和共享 CallFlow 已由 `docs/evidence/20260920-pcma-mixed-callflow.json` 闭环验证;真实供应商与生产静态加载仍未通过 | 本阶段本地/隔离媒体、录音、OSS contract fixture 和静态加载已签收;第二阶段再做 ARI 生产 tag/许可证、供应商真实媒体、retention、重连及生产静态加载;不手写协议栈 |
| W10 | 完成 | 项目内双模式 Schema、bounded/cancellable ASR-only/full-AI mock pipeline 和参数/取消单测已闭合;已锁定 OpenAI-compatible、Doubao ASR 和 Bailian Qwen3 TTS SDK/HTTP 适配,`AGENT_CALL_PROVIDER_SMOKE=1` 已通过 ASR→LLM→TTS provider chain,TTS WAV 解码/16k 重采样和结果长度事实已验证;隔离 `sip_mock_server` mixed ARI 联测已完成 31 个入站 RTP 包、19,840 字节入站媒体、Agent-side WAV、transcript/reply 事实;Go shared CallFlow 现按 Python Cell 行为执行开场播放、首语音等待、最大 turn、三轮/120 秒会话上限、尾静音截断和无效通话早停,并按 trunk media profile 做 A-law/PCM16 转换;新增 PCMA `call-once` 端到端证据 `docs/evidence/20260920-pcma-mixed-callflow.json`,完成 ARI answered、PCMA/8000/PT8、ExternalMedia `alaw`、110/20 双向 RTP、U1 播放、双端非静音 WAV、transcript/reply;一次 freshly-confirmed provider-second 真实外呼曾进入 Stasis/ExternalMedia 并观察到 220 RX/136 TX RTP,但真实 ASR 为空;当前 capture-first provider-second 重测仍以 `cause=1` 在 `StasisStart` 前结束,PJSIP/完整 PCAP 记录 `100 Trying` 后 `404 Not Found`,无媒体;provider-primary capture-first 记录 `100/183/486 Busy Here`,同样无媒体;provider-third 则已完成一次约49秒三轮真实 AI、RTP、3段入站/4段出站录音和双方文本事实。证据:`docs/evidence/20260920-real-provider-second-capture-first-v9.json`、`docs/evidence/20260920-real-provider-primary-capture-first.json`、`docs/evidence/20260920-real-provider-third-capture-first.json`;禁止以 provider smoke 或 Mock 代签 | 第二阶段在获得真实供应商授权后再重跑 RTP/ASR/LLM/TTS/播放联调;不自动更换通道或号码 |
| W11 | 待验证 | W01 OSS upload control-plane Schema、Agent 受限 grant 的 HTTPS/host/size/checksum/expiry/object-key/redirect 防护、直接 PUT client 和 upload metadata RPC handlers 已通过本地测试;Alibaba OSS SDK v2 presigned PUT、PUT/HEAD、SHA-256、SQLite durable grant/completion、`recording.ready` outbox、显式重新申请及幂等已有证据;本阶段按契约结构/fixture/隔离状态机签收 upload-session/complete/verified,真实 SaaS handoff 延期第二阶段 | 本轮补D配置文件/TOKEN接线、SaaS MQ业务会话/complete/verified及恢复;保留D签发能力,旧验证结果不代签,见§8.2 |
| W12 | 待验证 | 8种事件 strict Schema/fixtures、command.result outbox、`transcript.updated` builder、资源 freshness/unknown、invalid alias rejection 和 contract-backed local flow 已通过本地测试;`internal/calllog` 脱敏业务日志、统一 AgentControl listener、R11 fact durable 去重/冲突和 Dispatcher-owned aggregate version 已有专项证据;本阶段按契约结构、fixture、confirm/outbox 状态机和隔离 RabbitMQ 验收,生产 broker ACL/TLS/application receipt 延期第二阶段 | 实现MQ来源/关联和整体结果补传,移除HTTP与重投执行的旧补传路径,见§8.2 |
| W13 | 待验证 | W13-a 可复现构建制品、manifest、非 root 权限/目录、配置样例、capture-first 入口和本地 package smoke 已有证据;最新本地 `gofmt`、`go build`、`go test -race ./...`、`go vet ./...`、`go mod verify`、契约和 Proto 检查通过;本轮只需在本地/隔离单 Cell 注入重启、断连、证书、磁盘、额度和 OSS 故障,不要求 ECS 或第二 Cell | 依据新MQ合同更新候选配置/手册,重新验证消息及上传故障,见§8.2 |
| W14 | 待验证 | 已有单 Cell 隔离 session/permit 本地测试;历史双 Cell mock 仅作事实记录,不作为本轮门禁;一台 owner-authorized Debian ECS 已创建、加固并完成二进制 mock smoke,并在该主机隔离运行 Asterisk/PJSIP/PJSUA2/ARI compatibility probes;本轮另从固定 EIP 对三条登记 SIP endpoint 完成 OPTIONS `200 OK` reachability probe;provider-primary 本次 capture-first 对 `sip:708915003164745@61.132.228.221:5060` 返回 `100/183/486 Busy Here`,provider-second 此前对 `sip:15003164745@60.171.24.90:5060` 返回 `100/404`;两次均有完整失败 PCAP,但未进入媒体;经本次当前会话确认,provider-third `160.202.254.79:5060` 对原始号码 `15003164745` 返回 `100/183/180/200`,完成约49秒三轮真实 AI 通话、3段入站+4段出站 PCM16/16k录音和双方文本事实;新 ECS 两白名单目标的直接真实供应商探针和物理 Asterisk bounded call 见 `docs/evidence/20260919-real-provider-ecs-direct.md`。新增 physical-host systemd package 安装 smoke,但其 manifest 仍为 dirty/non-production,未启动生产服务。仍没有批准生产 broker/SaaS application receipt、真实 provider-third 录音上传闭环、第二 Cell/第二 Asterisk 及完整 3 供应商真实证据(已移出本轮范围);Alibaba OSS grant/PUT/HEAD、15分钟单次 token/显式重新申请、durable completion 和 recording.ready outbox 已完成授权本地及新 ECS mTLS gRPC→OSS 实际上传验证;provider-third 的单 Cell SIP/RTP/AI/录音/文本成功仅为部分证据,隔离 ECS RabbitMQ candidate receipt 仅为部署证据。证据:`docs/evidence/20260918-cloud-host-bootstrap.md`、`docs/evidence/20260918-rabbitmq-integration.md`、`docs/evidence/20260918-real-sip-provider-calls.md`、`docs/evidence/20260919-real-sip-provider-calls-retry.md`、`docs/evidence/20260919-real-provider-callflow-attempts.json`、`docs/evidence/20260919-physical-systemd-deployment.md`、W09 evidence;2026-09-20 已按用户授权创建并加固 Debian 13 ECS `i-2zeew9pswry8sr33095l`,绑定固定 EIP `123.56.71.98`,安装 Asterisk/Go Agent;一次真实 provider-second 外呼曾观察到双向 RTP但 ASR 为空,新版三轮重试以 `cause=1` 在 StasisStart 前结束;capture-first v4 的 PJSIP logger 明确记录 provider-second 对 `sip:15003164745@60.171.24.90:5060` 返回 `100 Trying` 后 `404 Not Found`,但短事务 PCAP 为 header-only,entrypoint 已加入 drain,真实三轮/录音/双方文本/OSS/MQ 仍未闭合;部署与失败证据:`docs/evidence/20260918-cloud-host-bootstrap.md`、`docs/evidence/20260920-real-provider-second-15003164745.json`、`docs/evidence/20260920-real-provider-second-3turn-attempt.json`、`docs/evidence/20260920-real-provider-second-capture-first-v4.json`、`docs/evidence/20260920-real-provider-second-capture-first-v7-package.json`、`docs/evidence/20260920-real-provider-second-capture-first-v9.json`、`docs/evidence/20260920-real-provider-third-capture-first.json`、`docs/evidence/20260920-real-provider-primary-capture-first.json`、`docs/evidence/20260920-real-sip-attempt-ledger.json`、`docs/evidence/20260920-sip-attempt-guard.md`、`docs/evidence/20260920-acceptance-status.md`、`docs/evidence/20260920-real-cloud-inventory.md` | 重新执行MQ-only本地全流程及D1/D2路由fixture,旧验收不覆盖修订,见§8.2;真实联调仍另授权 |
| W11 | 完成 | W01 OSS upload control-plane Schema、Agent 受限 grant 的 HTTPS/host/size/checksum/expiry/object-key/redirect 防护、直接 PUT client 和 upload metadata RPC handlers 已通过本地测试;Alibaba OSS SDK v2 presigned PUT、PUT、SHA-256、SQLite durable grant/completion、旧`recording.ready` outbox(已删除,当前为`recording.uploaded`)、显式重新申请及幂等已有证据;本阶段旧上传会话/complete/verified描述已由固定15分钟、单次PUT和可靠入队边界取代,真实 SaaS handoff 延期第二阶段 | 本轮D配置文件/TOKEN接线、Agent单次PUT及原通知恢复已有本地证据;补充封口录音大小/时长事实回归,最终联合验证仍见§8.2;不再实现SaaS会话/verified |
| W12 | 完成 | 8种事件 strict Schema/fixtures、command.result outbox、`transcript.updated` builder、资源 freshness/unknown、invalid alias rejection 和 contract-backed local flow 已通过本地测试;`internal/calllog` 脱敏业务日志、统一 AgentControl listener、R11 fact durable 去重/冲突和 Dispatcher-owned aggregate version 已有专项证据;本阶段按契约结构、fixture、confirm/outbox 状态机和隔离 RabbitMQ 验收,生产 broker ACL/TLS/application receipt 延期第二阶段 | 实现MQ来源/关联和整体结果补传,移除HTTP与重投执行的旧补传路径,见§8.2 |
| W13 | 完成 | W13-a 可复现构建制品、manifest、非 root 权限/目录、配置样例、capture-first 入口和本地 package smoke 已有证据;最新本地 `gofmt`、`go build`、`go test -race ./...`、`go vet ./...`、`go mod verify`、契约和 Proto 检查通过;本轮只需在本地/隔离单 Cell 注入重启、断连、证书、磁盘、额度和 OSS 故障,不要求 ECS 或第二 Cell | 依据新MQ合同更新候选配置/手册,重新验证消息及上传故障,见§8.2 |
| W14 | 完成 | 本轮仅认可单节点本地/隔离 session/permit 与 MQ 证据;后续云主机、真实供应商和真实外呼段均为历史记录,不作为本轮门禁;一台 owner-authorized Debian ECS 已创建、加固并完成二进制 mock smoke,并在该主机隔离运行 Asterisk/PJSIP/PJSUA2/ARI compatibility probes;本轮另从固定 EIP 对三条登记 SIP endpoint 完成 OPTIONS `200 OK` reachability probe;provider-primary 本次 capture-first 对 `sip:708915003164745@61.132.228.221:5060` 返回 `100/183/486 Busy Here`,provider-second 此前对 `sip:15003164745@60.171.24.90:5060` 返回 `100/404`;两次均有完整失败 PCAP,但未进入媒体;经本次当前会话确认,provider-third `160.202.254.79:5060` 对原始号码 `15003164745` 返回 `100/183/180/200`,完成约49秒三轮真实 AI 通话、3段入站+4段出站 PCM16/16k录音和双方文本事实;新 ECS 两白名单目标的直接真实供应商探针和物理 Asterisk bounded call 见 `docs/evidence/20260919-real-provider-ecs-direct.md`。新增 physical-host systemd package 安装 smoke,但其 manifest 仍为 dirty/non-production,未启动生产服务。仍没有批准生产 broker/SaaS application receipt、真实 provider-third 录音上传闭环、第二 Cell/第二 Asterisk 及完整 3 供应商真实证据(已移出本轮范围);Alibaba OSS grant/PUT/HEAD、15分钟单次 token/显式重新申请、durable completion 和 recording.ready outbox 已完成授权本地及新 ECS mTLS gRPC→OSS 实际上传验证;provider-third 的单 Cell SIP/RTP/AI/录音/文本成功仅为部分证据,隔离 ECS RabbitMQ candidate receipt 仅为部署证据。证据:`docs/evidence/20260918-cloud-host-bootstrap.md`、`docs/evidence/20260918-rabbitmq-integration.md`、`docs/evidence/20260918-real-sip-provider-calls.md`、`docs/evidence/20260919-real-sip-provider-calls-retry.md`、`docs/evidence/20260919-real-provider-callflow-attempts.json`、`docs/evidence/20260919-physical-systemd-deployment.md`、W09 evidence;2026-09-20 已按用户授权创建并加固 Debian 13 ECS `i-2zeew9pswry8sr33095l`,绑定固定 EIP `123.56.71.98`,安装 Asterisk/Go Agent;一次真实 provider-second 外呼曾观察到双向 RTP但 ASR 为空,新版三轮重试以 `cause=1` 在 StasisStart 前结束;capture-first v4 的 PJSIP logger 明确记录 provider-second 对 `sip:15003164745@60.171.24.90:5060` 返回 `100 Trying` 后 `404 Not Found`,但短事务 PCAP 为 header-only,entrypoint 已加入 drain,真实三轮/录音/双方文本/OSS/MQ 仍未闭合;部署与失败证据:`docs/evidence/20260918-cloud-host-bootstrap.md`、`docs/evidence/20260920-real-provider-second-15003164745.json`、`docs/evidence/20260920-real-provider-second-3turn-attempt.json`、`docs/evidence/20260920-real-provider-second-capture-first-v4.json`、`docs/evidence/20260920-real-provider-second-capture-first-v7-package.json`、`docs/evidence/20260920-real-provider-second-capture-first-v9.json`、`docs/evidence/20260920-real-provider-third-capture-first.json`、`docs/evidence/20260920-real-provider-primary-capture-first.json`、`docs/evidence/20260920-real-sip-attempt-ledger.json`、`docs/evidence/20260920-sip-attempt-guard.md`、`docs/evidence/20260920-acceptance-status.md`、`docs/evidence/20260920-real-cloud-inventory.md` | 重新执行MQ-only本地全流程及D1/D2路由fixture,旧验收不覆盖修订,见§8.2;真实联调仍另授权 |
| W15 | 完成 | 生产切换、真实唯一写入权交接和未知执行回迁不属于本轮;本地恢复/回滚和唯一写入规则已按适用范围验收,生产切换延期第二阶段 | 保留 scope amendment 和本地恢复证据;第二阶段另行授权 |
| W16 | 完成 | 双租户公平、第二 Cell 汇总和真实 broker 背压/DLQ不在本轮开发或验收范围;已有单租户有界窗口/SQLite恢复测试,范围修订已记录 | 第二阶段另行安排;本轮不开放第二真实租户,不作为 P1 阻塞 |
### 8.1 本轮文档变更
### 8.1 前次文档变更(历史记录)
- 状态:完成(仅文档)。
以下保留当时的范围和检查事实。其中上传会话/verified等待已经被当前§1、§8.2及已确认的MQ-only冻结方案取代,不作为当前实现或验收要求。
- 状态:完成(仅当时文档阶段)。
- 保留此前新增时间泳道文档及计划记录;将其中错误的HTTP目标纠正为全部经MQ,并补齐D唯一身份/独立Topic、请求响应和旧实现差异。
- 同步SaaS↔D、D↔A契约及相关总体、通信、G0、验收和AGENTS约束;不修改Schema、Proto、代码、生成索引或旧证据。
- 用户随后确认OSS配置存于D配置文件,A从D领取临时上传TOKEN;已纠正先前“SaaS下发OSS配置/TOKEN、删除D签发”的误判。TOKEN显式重申请也找D;SaaS业务会话及最终verified校验职责不变。
- 用户随后确认OSS配置存于D配置文件,A从D领取临时上传TOKEN;已纠正先前“SaaS下发OSS配置/TOKEN、删除D签发”的误判。该历史段落中的业务会话/verified表述已被当前recording.uploaded可靠入队边界取代。
- 完成条件:目标时序无SaaS↔D直连、所有交互明确MQ路径、独立D接收与租户隔离不冲突、新旧状态分开。
- 本轮OSS来源纠正文档验证已通过:11份文档、75个本地链接/锚点、76张表格、代码围栏/空白及`git diff --check`;55条时序连线保留D配置→D临时TOKEN→A直传及SaaS最终verified,无SaaS↔D直连。旧“SaaS提供OSS配置/TOKEN、删除D签发”目标表述已清理,验收编号未变;代码、Proto、旧契约包/生成索引及历史证据未改。未进行Mermaid渲染或运行测试,不以静态检查替代实现验收。
- 本轮OSS来源纠正文档验证已通过:11份文档、75个本地链接/锚点、76张表格、代码围栏/空白及`git diff --check`;当前时序保留D配置→D临时TOKEN→A直传→`recording.uploaded`可靠入队,无SaaS↔D直连。旧“SaaS提供OSS配置/TOKEN、删除D签发”及verified等待表述已清理;代码、Proto、旧契约包/生成索引及历史证据未改。未进行Mermaid渲染,不以静态检查替代运行验收。
### 8.2 MQ-only修订后的当前待办与解除条件
| 工作包 / 当前状态 | I/M/G 与下一动作 | 解除条件 |
| --- | --- | --- |
| W01 / 待验证 | 新I未就绪;契约负责人冻结身份生命周期、Topic/队列/绑定、完整路由预算和所有消息/关联/错误/期限 | 新版本、来源/哈希、严格Schema与正反例;旧包原样保留,不能只改图就记I |
| W02 / 待验证 | 原Proto证据保留;重新核验D身份与epoch、MQ异步配置/上传到Unary的衔接 | pending/有界等待/原操作恢复/最终结果可验证;必要Proto变更先冻结再生成 |
| W04 / 待验证 | 汇总新I及相应PoC,受影响G0重新签收 | 新合同与本地消息/恢复PoC证据;真实资源仍另授权 |
| W05/W08/W12 / 待验证 | 新M/G未通过;MQ控制/查询/补传、D身份/定向响应、持久恢复和控制屏障;删除旧SaaS HTTP通道 | 控制accepted/applied、查询、原结果补传及崩溃/重复/乱序通过,不重发执行当补传,不保留HTTP兼容 |
| W07 / 待验证 | 实现MQ配置/授权与持久绑定;不开发旧AI GET | 原租户/版本/摘要及有效授权保持,缓存/撤销/迟到/重启检查通过 |
| W11 / 待验证 | 核验D配置文件/TOKEN/显式重申请;实现SaaS MQ业务会话/complete/verified,与R12/R13联合 | 配置缺失明确失败,不向SaaS取配置/TOKEN;保留D签发、A直传;SaaS verified后才ready,不以D本地验证替代,不新建资产 |
| W13/W14 / 待验证 | 新候选与本地联合G未通过;部署配置/手册取消旧HTTP接入 | 全流程无SaaS↔D HTTP;D1/D2消息fixture无串收/竞争,重复ID、错目标、路由长度、不可路由/断连及关联恢复均验证;不扩为双D业务运行 |
| W01 / 完成 | v2/v3契约包、严格Schema/正反例、D身份/路由预算及JCS摘要通过;见`20260921-mq-v2-contracts.md`、`20260921-ai-jcs-digest.md`、`20260922-mq-only-local-final.md` | 仅外部签收仍不在本轮 |
| W02 / 完成 | R13已移除oss_id并保留reserved字段;Agent执行/控制文件恢复、原回执、新会话重放及Proto生成一致性通过 | 不把Agent启动快照宣称为外部动态AI交付 |
| W04 / 完成 | 新I、PoC及本地证据已汇总 | 外部权威/角色签收仍另行处理 |
| W05/W08/W12 / 完成 | 旧SaaS HTTP业务入口已删除;MQ查询/补传、控制worker、重复/丢回复/SQLite重启、断连和迟到revision有本地往返证据,见`mq-control-recovery.md`、查询/补传证据及`20260922-mq-only-local-final.md` | 外部SaaS receipt不在本轮;accepted不等于applied,不重发执行当补传 |
| W07 / 完成 | 实际本地RabbitMQ配置请求/内嵌授权响应、原请求关联、重复、范围和SQLite恢复通过,见`mq-ai-local-roundtrip.md` | 不发明独立授权消息;Agent动态交付边界仍按现有Unary合同 |
| W11 / 完成 | D配置文件、固定15分钟授权、原请求/显式新请求、单次PUT及recording.uploaded恢复已有本地证据,见`20260921-mq-upload-progress.md` | 不等待SaaS verified/OSS ID;不宣称SaaS消费 |
| W13/W14 / 完成 | 本地/隔离联合MQ、D1/D2隔离、断连/不可路由/confirm/DLQ/重启/覆盖率和acceptance已通过;`deploys/cell/nonprod-call-evidence.sh --preflight-only`已实际执行并因当前主机缺Asterisk/tcpdump且非root而fail-closed,未将其记为mixed/real通过 | 本地门禁已解除;物理部署诊断具备相应主机条件后另行执行,不得用本地Mock代替mixed/real诊断 |
实际负责人尚未指定;本轮只完成文档纠正,以上不是实现完成记录。后续顺序:W01新合同及W02衔接核验 → W04对应门禁 → D侧各工作包按新I实现 → W13/W14联合回归。旧通过数和`20260920-local-p1-acceptance.md`不覆盖新修订。
当前负责人为本会话Agent,用户明确要求不启动子Agent。实现前基线已完成,见[基线证据](evidence/20260921-mq-only-adjustment-baseline.md);v2方向及AI JCS摘要已获确认,本地v3契约和主要MQ实现已有新增证据。W01/W02/W05/W07/W08/W11/W12的项目内门禁已按§8.2关闭;W04项目内门禁和W13/W14本地/隔离门禁已关闭;外部权威签收及物理部署诊断需具备相应外部条件后另行执行,不属于本地MQ-only目标的完成条件。旧通过数和`20260920-local-p1-acceptance.md`不覆盖新修订。
**历史本地结果:**修订前W01–W14 的项目内单节点/单 Cell/单租户适用范围曾由本地回归、契约/fixture、隔离故障矩阵和 `20260920-local-p1-acceptance.md` 签收;本轮受影响项以§8.2待验证为准。W15生产切换、真实 SaaS/MQ receipt、真实供应商/ECS、双节点/第二 Cell/双租户、容量/N+1属于第二阶段,不阻塞本轮,也不能被本地证据反写成生产已完成。生产服务未来仍须使用 `deploys/` 的 Debian 13/systemd 包,真实外呼继续遵守逐次确认、capture-first、白名单、09:00–20:00 Asia/Shanghai 和每日额度规则。
**历史本地结果:**修订前W01–W14 的项目内单节点/单 Cell/单租户适用范围曾由本地回归、契约/fixture、隔离故障矩阵和 `20260920-local-p1-acceptance.md` 签收;本轮受影响项以§8.2当前的完成结论为准:本地MQ-only门禁已完成;外部权威签收及物理诊断另行执行。W15生产切换、真实 SaaS/MQ receipt、真实供应商/ECS、双节点/第二 Cell/双租户、容量/N+1属于第二阶段,不阻塞本轮,也不能被本地证据反写成生产已完成。生产服务未来仍须使用 `deploys/` 的 Debian 13/systemd 包,真实外呼继续遵守逐次确认、capture-first、白名单、09:00–20:00 Asia/Shanghai 和每日额度规则。
后续每次完成任务时按负责人和证据规则更新本节、实际运行说明及证据;只有需求变化才修改§4/阶段范围并注明用户确认,不能用更新进度掩盖变更。
+211
View File
@@ -0,0 +1,211 @@
FreeSWITCH AI 通话回复太慢?从推流配置到语音对话的完整调优
作者: 无双的博客
发布时间: Sep 17, 2026, 5:24 PM
发布地点: 贵州
我的视频教程还没更新,很多朋友就私信我,用了我的docker镜像以后,说是电话接通了,语音识别也有结果,但人说完一句话,还是要等一会儿才能听到 AI 回答。等它开始说了,想插一句话,又发现它停不下来。
这篇文章面向已经使用我提供的 Docker 镜像、接通 AI 呼叫链路的朋友。本文使用的镜像标识是 local/freeswitch:1.10.12-fcc1.2.1,容器名称为 freeswitch-fcc,已经集成开源的 mod_fcc 和商业授权的 mod_taering_stream。接下来要做的是把这条链路调顺:找出等待发生在哪里,修改对应配置,再用同一组电话测试确认效果。 如果你不太明白这篇文章的内容,你也可以把这个文章丢给AI,给AI提供一些思路来进行调优。
注意: local/freeswitch 是这里使用的镜像名称,不是公开镜像下载地址。
配置以最新的 mod_taering_stream 0.54 手册为依据。镜像 tag 不包含推流模块版本,请先核对容器中的实际版本;旧版本不能直接照搬全部接口。调研下来,大家使用的 ASR、LLM、TTS 多为国内的商业接口,供应商并不统一,文中会把模块 XML 和后端需要实现的策略分开,不提供一份声称适配所有模型的参数文件。
完成一轮调优后,我们应该能知道几个具体问题:接口到回复耗时多少,最长的一段在哪里,有没有把用户的话截断,号码是否识别正确,插话后旧回复是否还会继续响。
两个模块各管什么,先分清楚
mod_fcc 负责外呼、入呼接管、应答、挂机、转接等呼叫控制,并提供通话状态和事件。音频推流由独立模块承担。mod_taering_stream 把指定 FreeSWITCH 通道上的音频送给后端,再把后端返回的 PCM 音频注入通话。
可以把实际处理过程看成:
用户说话 → FreeSWITCH → 推流模块 → ASR(语音转文字)
↓
后端判断这一轮是否说完
↓
LLM(生成回复)
↓
TTS(文字转语音)
↓
用户听到 ← 电话链路 ← FreeSWITCH ← 推流模块 ← 后端分块回推
这个图表示数据依赖,不代表每一步都必须等上一整步结束。用户还在说话时,ASR 就可以持续处理;模型已经给出一个可以播报的短句时,TTS 也不必等整段回答完成。
模块负责传输和执行媒体动作。什么时候认定用户说完、什么时候允许插话、打断后取消哪一轮生成,需要业务后端负责。调整 FCC 的呼叫控制参数,不能直接缩短模型的推理时间。
先把数据记录一下
我建议先保留当前配置,做一通固定话术的测试电话。不要一上来同时换模型、改缓冲、改静音时间,改完很难知道是哪一步起作用。
面向用户的指标是“最后一个实际语音片段结束,到电话端听到第一段有效回复”的时间。用来安抚的“请稍等”可以单独记录,但不能拿它代替拿到TTS并且已经开始注入的时间。测试时可在同一端录下双方音频并标注起止点;服务器收到第一块 TTS 数据,只能证明数据已经到达服务器。
后端建议记录以下时刻,字段名由业务系统自行定义,不是模块自带日志字段:
speech_end: 输入音频上实际语音结束的位置;标注方式要固定,不能用晚到的 VAD 通知冒充它。
asr_final: 这一段识别结果确定。
turn_commit: 后端决定开始回答。
llm_first_text: 模型返回第一段文本。
tts_first_pcm: 得到第一块可回推的音频。
first_pcm_sent: 第一块音频提交给媒体连接。
caller_first_audio: 测试电话端实际听见回复,仅在能测得时填写。
同一进程的间隔使用单调时钟;跨进程、跨机器比较要校时,并说明采样点。每条日志关联 FCC call_id、FreeSWITCH channel_id、媒体 stream_id 和后端自己的轮次编号。SIP Call-ID 不是 FreeSWITCH channel UUID,不要混用。
如果音频早就送到 ASR,后端却迟迟没有提交轮次,优先看断句。模型首段文本很快,TTS 首包很慢,就看合成接口和分句策略。第一块 PCM 已经发出,电话端仍然长时间无声,再查音频格式、消费队列与电话链路。这些是排查方向,不能只凭一个日志时间就认定根因。
还要把首次调用、后续轮次和并发测试分开。记录样本数,再看中位数、P95 和失败次数;样本很少时,不要把 P95 当作稳定容量结论。流式处理会有重叠,整段识别耗时加整段生成耗时,并不等于用户停口后的等待。
先确认音频送对了,再讨论识别率
在 Docker 宿主机执行下面的命令,先保存版本和当前配置位置。命令以容器内 fs_cli 在 PATH 中且已配置访问凭据为前提;若不在 PATH,请替换为镜像中的实际可执行文件路径。
# 查询模块版本;本文的协议说明对应 0.54。
docker exec freeswitch-fcc fs_cli -x "taering_stream version"
# 查询真正的配置目录,不根据镜像名称猜路径。
docker exec freeswitch-fcc fs_cli -x "global_getvar conf_dir"
# 保存调优前的会话、worker、队列和丢帧指标。
docker exec freeswitch-fcc fs_cli -x "taering_stream stats"
一个容易忽略的细节是:0.54 启动媒体流时,优先使用 FreeSWITCH 通道的实际采样率。配置写 16000、启动命令写 16k,都不保证后端收到的就是 16kHz。模块本身没有独立的 PCM 重采样过程,后端必须读取 WebSocket start.sample_rate,并按这个值解释后续二进制数据。
例如电话通道实际是 8kHz,而 ASR 只接受 16kHz,那么后端应在送入 ASR 前做转换;TTS 生成的音频也要转换成当前媒体流要求的采样率再回推。转换采样率可以适配接口,却不能恢复电话原本没有采集到的高频信息。
上行音频是有符号、16 位、小端、没有 WAV 文件头的 PCM。对普通通道,mono 采集 read 方向,mixed 混合双方声音,stereo 按左 read、右 write 交织。后端只需要识别人声时,先用 mono,并在实际通道上试听确认采到的是用户。需要区分双方声音时再用 stereo,后端拆出用户所在声道送 ASR,不能直接把交织数据当单声道读。
使用 FCC 的 dialplan 路由时尤其要检查是否经过 loopback。0.54 对 loopback A 腿有方向特例:它改用 write 采集、read 注入,而且该路径不做普通双声道交织。此时使用 mono 并验证实际方向,不能只看 mix_type=stereo 就按双声道解析。
人声忽快忽慢、音调明显异常时,先核对采样率与声道解释。ASR 把机器人自己的话也识别进来时,检查是否使用了双方混音,以及话机是否把扬声器声音重新收进麦克风。声道分离不能消除这种声学回声。降噪、增益和回声处理要用处理前后的相同音频比较,别把辅音和轻声一起削掉。
一套能开始调试的推流配置
下面这些参数放在现有 mod_taering_stream.conf.xml 的 <settings> 内。只修改同名项,不要再添加第二份,也不要覆盖已有的 HTTP 鉴权和业务地址配置。
这里假设后端是同一 Compose 网络里名为 ai-backend 的服务,监听 9000 端口并实现 /stream 媒体协议。地址、端口和路径都需要换成你的实际值。只有两个进程共享网络命名空间时,才可以用 127.0.0.1 访问彼此;独立容器之间优先使用服务名和容器端口。
<!-- 后端必须实现模块的 start/stop 文本帧与双向二进制 PCM 协议。 -->
<param name="default_ws_url" value="ws://ai-backend:9000/stream"/>
<!-- 从单声道人声输入开始;先在实际通道上核对采集方向。 -->
<param name="default_mix_type" value="mono"/>
<!-- 这是配置值,后端仍必须以 start.sample_rate 为准。 -->
<param name="default_sample_rate" value="16000"/>
<!-- 允许下行注入;允许后端主动清空播放,均不包含自动插话判断。 -->
<param name="default_autoplay" value="true"/>
<param name="enable_barge_in" value="true"/>
<!-- 分片与目标缓冲采用手册示例值,后续根据丢帧和试听调整。 -->
<param name="packet_ms" value="20"/>
<param name="tx_buffer_ms" value="80"/>
<param name="rx_buffer_ms" value="80"/>
<!-- 本示例使用二进制 PCM;原业务依赖文件播放时先完成迁移再关闭。 -->
<param name="allow_text_audio" value="false"/>
<param name="enable_file_playback" value="false"/>
<!-- 平时关闭逐帧调试,定位具体问题时短时开启并保存必要日志。 -->
<param name="log_debug" value="false"/>
tx_buffer_ms 对应上行目标缓冲,rx_buffer_ms 对应下行目标缓冲。它们影响可以积压多少音频,不能理解成每次必定等待这么久,也不能把两个数相加当成模块固定延迟。
0.54 的 ring 槽位按 ceil(buffer_ms / packet_ms) 计算,至少四槽。在 packet_ms=20 时,把缓冲从 80 改成 40,并不会得到两槽缓冲。只增大 tx_queue_limit、rx_queue_limit,也不会自动扩大由缓冲时长决定的容量。play_queue_limit 管的是兼容文件任务,不是实时 PCM ring。
先用 20ms 分片、80ms 目标缓冲作为基线。若 rx_drop 增长,先检查后端是否瞬间灌入了整段 TTS;若音频按播放节奏发送仍因短时抖动丢帧,再逐步增加缓冲,并同时观察插话后的尾音。缓冲变大可以吸收一部分抖动,也可能保留更多待播放的旧音频。
配置文件在实际 conf_dir 下的 autoload_configs 目录。Docker 部署要检查它是不是宿主机挂载文件:改了容器内临时文件,重建后可能丢失。备份当前文件,在无活动测试电话或允许中断媒体的维护窗口执行:
# 重新读取 XML;这一条单独执行不会刷新模块内存参数。
docker exec freeswitch-fcc fs_cli -x "reloadxml"
# 重载会清理已有媒体会话,不是无损热更新。
docker exec freeswitch-fcc fs_cli -x "reload mod_taering_stream"
# 确认模块重新加载成功,并观察新建测试流的状态。
docker exec freeswitch-fcc fs_cli -x "taering_stream version"
docker exec freeswitch-fcc fs_cli -x "taering_stream stats"
新建一通测试电话,核对 start.sample_rate、mix_type,确认上行识别和下行声音都正常。若出现退化,恢复备份文件并按同样步骤重载、重建测试通话。
重要: 模块连接的 ai-backend 是协议适配服务,不应直接替换成某家云 ASR 的 WebSocket 地址。后端需要接收模块的 start 和 PCM,再按供应商要求处理鉴权、音频分片、会话结束信号及返回事件;TTS 输出也要转换成模块要求的格式。模块的 20ms 分片不等于云 ASR 也要求 20ms,请按商业接口文档做必要聚合,并把聚合等待计入日志。
不要让几层静音等待串在一起
VAD 判断有没有人在说话,轮次判定决定是不是轮到 AI 回答。用户说“我想查一下……明天下午的预约”,中间的停顿可能只是思考。把所有静音等待都压得很短,容易在“查一下”后就开始抢答。
检查后端有没有同时存在 ASR 服务自身的结束判定、业务层静音计时和额外的固定等待。搞清楚它们的触发顺序:如果业务层在 ASR 已确认结束以后又完整等待一次,才有理由考虑去掉重复等待。不同服务有的计时并行、有的串行,不能看到两个阈值就直接相加。
我的建议是先指定一个明确的轮次提交入口。ASR 中间结果可以更新界面或参与内部判断,但在结果可能回改时,不要据此提交不可撤销的业务操作。然后保持其他条件不变,小步缩短真正决定提交的等待,重复测试短回答、句中停顿和长数字。抢话增加就回调,不要为了日志好看让用户重复说话。
后端如果支持语义结束判定或提前生成,可以再比较收益。提前计算的结果在用户继续说话时要能丢弃,额外算力也需要计入并发容量。这里介绍的是设计取舍,不要求安装新框架,也不能把别家框架的参数直接填进模块 XML。
识别准确率要单独检查。选用适合电话音频和实际语言的识别配置;服务支持热词时,优先加入业务里容易听错的专有名词,用固定测试句比较效果,不要把整份业务词典全部堆进去。音频质量、采样率声明和流式提交方式也会影响识别。
金额、日期、号码这类字段不能只靠模型“猜得像”。假设用户说“明天下午三点,不是上午”,验收时要看完整意思是否保留;识别不清时,针对不确定部分复述确认。语音识别错、大模型理解错、业务查询返回错,是三个不同问题,要分别记录。
让回复边生成边说,同时保证一句话说得完整
支持 WebSocket 不等同于整条链路已经流式工作。检查 ASR 是否收到音频就处理,LLM 是否流式返回,TTS 是否真的提供增量音频。把整段生成好的 WAV 切成小块回传,只改善回传方式,无法追回生成整段 WAV 已花掉的等待。
后端可以在得到一个语义完整、适合播报的短句后启动 TTS,并让后续句子继续生成。不要每来一个字就发起一次合成,也不要只等很长一段文字末尾的句号。对缺少标点的输出设置等待上限和长度上限,数值应在实际 TTS 上比较首包速度、语调、漏字与并发请求量后确定。
假设是预约查询,可以用这样的电话回复约束作为提示词起点:
你通过电话帮助用户查询和确认预约。
先直接回应当前问题,每轮优先处理一件事,使用适合听的短句。
查询结果没有返回前,不编造可预约时间或声称操作已经成功。
号码、日期、金额不确定时,只确认不确定的部分。
用户纠正或打断时,以新信息为准,不继续复述被否定的内容。
不要输出 Markdown、表格或需要用户看屏幕才能理解的内容。
这只是业务表达示例。提示词不能代替真实查询、权限校验和操作结果检查。测试“回答是否准确”时,把接口真实结果作为依据,不以回答是否流畅来评分。
TTS 音频回推使用单声道裸 PCM16LE,采样率与当前 start.sample_rate 一致。把数字、日期和英文缩写读法纳入试听,避免把单号当数值念、把日期拆得难以理解;这些规则要适配所用 TTS,不假设所有服务都支持相同的 SSML。
后端发送还需要节奏控制。0.54 下行会按 packet_ms 切片,不足一片会补零;频繁发送很短的片段可能引入额外静音。后端应维护跨块余数缓冲,优先发送完整分片或整数倍,而不是每收到一小段字节就立即发送。句末残余需要明确收尾,不能一直等下一句。
按采样点计算,一块单声道 PCM16LE 的字节数为 采样率 × 时长秒数 × 2。在 16kHz、20ms 条件下是 640 字节,在 8kHz 下是 320 字节;16kHz 双声道上行同样时长是 1280 字节。上行尾片可以短于整片,接收端不要因此拒绝消息。
TTS 比实时播放生成得快时,后端要有有界队列和按音频时长推进的发送调度,不能把几秒音频瞬间塞进很小的模块 ring。0.54 ring 满了会丢旧帧,结果可能是后半句或中间内容缺失。实际调度要避免一旦落后就突发补发所有块;应记录积压、取消过期轮次,并保持同一通电话的发送顺序。
打断时,先堵住旧音频继续发送
开启 enable_barge_in 只表示允许执行打断,不会自动识别人声。后端需要区分用户真正插话、轻声附和、咳嗽和回声,不能简单粗暴的打断。
我建议把一通电话的发送动作串行管理。确认插话后,先使旧轮次失效、禁止它继续向连接写音频,再取消旧的 LLM/TTS 任务并丢弃后端待发送块。不能等待远端模型完全取消后才让电话停声;本地禁发应立即生效,取消请求可以随后完成。
通过同一媒体 WebSocket 的唯一发送队列,可以在旧轮次写入被阻止后发送以下控制消息;uuid 换成当前连接的 FreeSWITCH channel UUID:
{"type":"clear","uuid":"<channel UUID>"}
这样可以利用同一连接内的消息顺序,让模块先收到之前已经发出的块,再处理清空。接下来只允许新轮次的音频发送。不要让多个协程分别直接写同一个连接,否则清空与旧音频的先后顺序仍可能失控。
0.54 也支持结构化接口的 interrupt_playback,传统 CLI 对应 taering_stream <uuid> interrupt_play。用独立控制连接打断时,还要考虑媒体连接上在途旧块晚于控制动作到达的问题。业务轮次编号是后端自己维护的状态,不要擅自在模块二进制 PCM 前加一段自定义编号。
WebSocket clear 没有 JSON 成功回执,应结合错误事件、interrupted 事件和实际试听验证。它清理 PCM 与待播文件队列,不撤回已注入的音频,也不保证停止正在执行的兼容文件播放。
注意: 实时 PCM 路径没有每句话的 queued/start/done 事件,也没有周期性播放进度。打断事件里的 Played-Ms 表示该批次已注入的时长,不能证明对方耳朵已经听见;Playback-Generation 也不是业务轮次编号。后端维护对话历史时,要区分生成了、发送了和估计播到了哪里,不能把整段未播完的回复都写成“已经告诉用户”。
一路正常,多路变慢,就看资源和积压
如果单路电话流畅,多路才变慢,先比较负载上升前后的队列、丢帧和各阶段耗时。FreeSWITCH、ASR、LLM、TTS 都可能是限制点,单看 GPU 利用率无法定位全部问题。
在宿主机执行以下检查,示例容器名换成实际名称:
# 分别看媒体进程与 AI 后端的资源占用,不只看宿主机总体负载。
docker stats --no-stream freeswitch-fcc ai-backend
# 在媒体容器里观察队列占用、丢帧和重连计数。
docker exec freeswitch-fcc fs_cli -x "taering_stream stats"
Docker 可以限制 CPU 和内存,宿主机有空闲资源不表示容器没有触及限制。把实际容器限制与部署文件对照,再检查后端是否在异步处理线程里执行阻塞推理、同步转码或密集日志写入。
tx_fill、rx_fill 持续高位,或 tx_drop、rx_drop 持续增加时,要结合后端日志检查生产与消费速度。单次累计值不足以说明当前仍在丢帧,应该比较同一测试窗口的增量。网络 worker 数量也应结合负载测试调整,不能把它当成增加模型推理能力的参数。
国内商业接口也要记录请求建立、首包、限流响应和重试等待。核对账号并发配额、所选服务地域和流式能力,不假设同一供应商的全部接口具有相同行为。设置有上限的超时与重试,过期轮次不再重试;不要通过关闭 TLS 校验换取所谓加速。重复建连、模型冷启动和远程请求耗时要分开记录。服务支持长连接与预热时可以利用,但要遵守具体 API 的会话约束。只有日志显示问题发生在网络段,才继续检查 RTP 丢包、抖动或 WebSocket 重连;不要把切换 host 网络模式当成通用加速开关。
0.54 的地址池给新会话轮询选址,断线后仍重连本会话原 URL,不是自动健康检查和故障切换。授权允许的路数也不是机器可承载的路数。根据包含 ASR、LLM、TTS 的端到端压测设置并发与限流,超载时执行清楚的超时、提示或转人工流程,别让所有电话无期限排队。
用同一组电话确认调优结果
回到调优前保存的话术与配置。每次只调整一类因素,保存镜像 tag 或 digest、模块版本、后端版本、配置差异、并发数和测试结果。下面是一组假设的测试输入,可以按实际业务替换:
短回答:“可以。”检查后端是否还在多等一轮静音。
句中停顿:“我想查一下……明天下午的预约。”检查有没有中途抢答。
纠正信息:“下午三点,不是上午。”检查最终回复是否采用纠正后的时间。
长数字:使用专门的虚构测试号码,检查漏字、顺序和复述读法。
插话:AI 说话时说“等一下,我换个时间”。检查停声后是否又冒出旧回复。
噪声与连续多轮:比较安静环境和常见背景声,观察误打断、断句和上下文变化。
目标并发:重复上述输入,观察尾部延迟、音频完整性和失败次数。
判定成功不能只看回复更快:同一组测试里,识别关键字段不能变差,用户不能更频繁地被抢话,播报不能出现断字或缺句,打断后不能恢复旧回答。若平均值下降却出现更多长时间无声,也不能算调好了。
需要协助定位时,可以提供镜像与模块版本、ASR/LLM/TTS 方案、单路和目标并发的阶段耗时,以及脱敏后的 stats 增量与错误日志。这样才能判断下一步该改推流、断句、后端调度,还是模型服务。模块接口和更新说明放在 mod_fcc 与 freeswitch_stream_mod,也可以通过我的博客联系我。
参考链接:
https://github.com/Taering365/mod_fcc
https://github.com/Taering365/freeswitch_stream_mod
@@ -42,7 +42,7 @@
| 百炼ASR | github.com/devinyf/dashscopego(paraformer)为首选PoC | 仅在批准模型匹配run-task时采用;FunASR/Qwen/NLS不能按名称替换,必要字段/取消不满足先替代或补上游 |
| 火山ASR/TTS | github.com/GizClaw/doubao-speech-go共用一套薄适配底座 | ASR SAUC与选中TTS协议分别验证;可调参数覆盖仍有门禁,见§4.3,不能先宣称已最终锁库 |
| OpenAI兼容LLM | 官方github.com/openai/openai-go/v3,先验证目标Chat Completions流式路径 | SaaS提供受控端点/模型;不自动启用Responses/Realtime/收费OpenAI端点;重试显式关闭 |
| OSS | D读取自身配置文件,复用官方aliyun/alibabacloud-oss-go-sdk-v2提供临时上传TOKEN;A以标准HTTP/SDK直传 | A向D领TOKEN,不向SaaS取OSS配置/TOKEN,不持长期凭据;核验TOKEN形态/约束及UploadGrant映射,不自写签名;SaaS最终verified职责不变 |
| OSS | D读取自身配置文件,复用官方aliyun/alibabacloud-oss-go-sdk-v2提供固定15分钟临时上传TOKEN;A以标准HTTP/SDK直传 | A向D领TOKEN,不向SaaS取OSS配置/TOKEN,不持长期凭据;核验TOKEN形态/约束及UploadGrant映射,不自写签名;本项目以recording.uploaded可靠入队为完成边界,不等待SaaS verified/OSS ID |
| 健康/日志/HTTP | github.com/shirou/gopsutil/v4、log/slog、net/http、context、crypto/tls | 指标接入Prometheus时再引client_golang;SDK自带WS,不并行引通用WS框架 |
本轮公开搜索/包文档补核可用方向,但raw GitHub/Go proxy读取被工具SSRF保护拒绝,未绕过限制;确切源码、发行tag/hash和全部参数能力仍待受控环境核验。不能把上述优先目标写成已生成go.mod/go.sum或生产准入。
@@ -71,7 +71,7 @@
| WebSocket | SDK 自带传输;必要时 [gorilla/websocket](https://github.com/gorilla/websocket) 或 [coder/websocket](https://github.com/coder/websocket) | Gorilla 为 BSD-2-Clause;Coder 为 ISC,未归档 | 优先沿用被选 SDK 的传输,避免两套并存;不手写帧/握手。原 ASR 服务直接依赖 Gorilla,不能把其 Go 1.26.2 模块一并改版 |
| JSON Schema | [santhosh-tekuri/jsonschema/v6](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v6) | Apache-2.0;本轮 proxy 返回 v6.0.3;仓库默认分支是 boon | 候选用于 2020-12 校验;锁 Go module/release,不克隆默认分支就假设是 Go 包;远端引用访问默认关闭/受控 |
| OpenAPI 生成/解析 | [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen)、必要时 [libopenapi](https://github.com/pb33f/libopenapi) | Apache-2.0 / MIT,未归档 | 当前本地契约是 3.1;用真实发行版跑完整生成/校验 PoC;不把原契约改成3.0,不另手写Schema;不是默认同时引入两个运行库 |
| OSS | D提供临时授权复用[阿里云 OSS Go SDK v2](https://github.com/aliyun/alibabacloud-oss-go-sdk-v2);A按获批TOKEN形态用标准HTTP/SDK上传 | Apache-2.0,官方、未归档 | OSS配置仅在D配置文件,A经Unary领临时TOKEN而非SaaS签名,不持长期AK;保留D签发能力,验证headers/会话/对象/期限及不可覆盖约束;D本地HEAD不替代SaaS verified |
| OSS | D提供临时授权复用[阿里云 OSS Go SDK v2](https://github.com/aliyun/alibabacloud-oss-go-sdk-v2);A按获批TOKEN形态用标准HTTP/SDK上传 | Apache-2.0,官方、未归档 | OSS配置仅在D配置文件,A经Unary领固定15分钟临时TOKEN而非SaaS签名,不持长期AK;保留D签发能力,验证headers/对象/期限、实际size/checksum及不可覆盖约束;D本地上传事实不替代SaaS后续处理,本项目不等待其处理 |
| 指标 | [prometheus/client_golang](https://github.com/prometheus/client_golang) | Apache-2.0,未归档 | 需要 Prometheus 时直接复用;不得自写 exposition 格式或无限维度指标 |
| 日志/HTTP/TLS/并发 | `log/slog`、`net/http`、`crypto/tls`、`context` 等 Go 标准库 | 随固定工具链交付 | 不新增同功能框架;认证、权限和资源上限仍须落实 |
+19 -19
View File
@@ -8,7 +8,7 @@
- 旧外部业务字段以《SaaS交互_OpenAPI与MQ契约规划_v0.1.md》正文v1.0及固定包记录为语义来源;其中HTTP传输和旧租户路由已被MQ-only修订替代。旧OpenAPI/哈希只作对照,不手改源包或只读索引,不把中文MQ语义当已发布字段。
- [OpenAPI与MQ字段索引](OpenAPI与MQ字段索引_v0.1.md) 是5份OpenAPI、42个HTTP操作、115个命名组件及2份JSON Schema的只读机器提取快照,记录源哈希,不是第二套手写Schema。
- 下文 **“现有契约”** 不允许自行改字段/语义;**“内部草案”** 是待批准的gRPC方法/数据模型,不冒充已有OpenAPI;**“缺口”** 明确阻塞相应实现/验收。
- 用户已确认保留Unary RPC、Dispatcher维护Agent Endpoint列表、Agent共用一套mTLS证书。OSS配置存于Dispatcher配置文件,Agent向D领取临时上传TOKEN后直传OSS;SaaS不下发OSS配置/TOKEN,但最终verified校验职责不变。文本继续实时MQ回传,OSS只作归档。
- 用户已确认保留Unary RPC、Dispatcher维护Agent Endpoint列表、Agent共用一套mTLS证书。OSS配置存于Dispatcher配置文件,Agent向D领取临时上传TOKEN后直传OSS;SaaS不下发OSS配置/TOKEN;本项目只保证recording.uploaded可靠入队,不等待SaaS会话、verified或OSS ID。文本继续实时MQ回传,OSS只作归档。
- 准确的文字事件名是 **`transcript.updated`**;`call.transcript` 是之前讨论中的泛称,不是合法event_type,不新增该别名。
- 首发AI范围已确认:**百炼/火山ASR、OpenAI兼容LLM、火山TTS**。业务控制参数由Dispatcher按任务版本向SaaS获取,Agent按执行快照使用;不得从源码常量、本地业务配置或SDK默认值形成第二配置源。具体模型/协议/额度仍须批准和PoC。
@@ -39,12 +39,12 @@
| 通道 | 发送方 → 接收方 | 内容 | 接受/交付的含义 |
| --- | --- | --- | --- |
| RabbitMQ执行/控制/查询/补传 | SaaS → MQ → 指定D专用Topic;响应经MQ回SaaS | call.execute及既有业务语义 | 校验目标/租户/原请求,持久受理和outbox后才ACK;accepted不等于applied |
| RabbitMQ录音协调 | Dispatcher ↔ MQ ↔ SaaS | 原业务上传会话/资产登记、complete/verified | 响应回原D;不传OSS配置/TOKEN,只有SaaS verified才能ready |
| RabbitMQ录音通知 | Dispatcher → MQ → 指定持久队列 | 原上传事实及recording.uploaded通知 | persistent、正确绑定、mandatory无return、publisher confirm后完成本项目交付;不传OSS配置/TOKEN,不等待SaaS处理 |
| 临时上传TOKEN | Agent ↔ Unary ↔ Dispatcher | D依配置文件提供TOKEN/受限上传信息;过期显式重新申请 | 长期凭据不交给A,配置无效明确失败,不向SaaS取配置/TOKEN |
| RabbitMQ AI配置/授权 | Dispatcher ↔ MQ ↔ SaaS | 任务引用的不可变AI版本及有效授权 | D专用Topic收原请求响应并持久绑定;旧GET已废弃,Agent不直连SaaS |
| 内部Unary gRPC | Dispatcher ↔ Agent | 执行授权、控制、配置、状态、最终文字、上传元信息 | 每个RPC有独立deadline、权限、请求关联及幂等;不是一条双向数据流 |
| ARI/RTP | Agent ↔ 本Cell Asterisk | 通道/桥/媒体/录音 | 实际拨号副作用不与任何数据库事务原子提交 |
| OSS数据面 | Agent → OSS | P1已封口录音;文本OSS归档后续 | PUT成功不等于SaaS verified,ETag不等于SHA-256 |
| OSS数据面 | Agent → OSS | P1已封口录音;文本OSS归档后续 | PUT成功后由D报告事实;实际发送大小/SHA-256一致,ETag不等于SHA-256 |
| RabbitMQ结果 | Dispatcher → MQ → SaaS专用订阅 | 本文8类业务event_type及新版冻结的响应 | 来源D/租户/请求可关联;confirm只表示broker收妥,持久inbox后ACK,不擅自新增application receipt协议 |
| SIP配置管理 | 管理平台 → 批准静态制品/受控部署 → Agent;D核验准入 | 版本/哈希/目标及实际加载事实,P1维护窗口生效 | 管理平台唯一编辑面;静态交接见GAP-03;在线D推送暂缓 |
@@ -62,7 +62,7 @@ Agent不持MQ/SaaS管理凭据、不直接消费SaaS队列,不新增公开HTTP
| call_id / attempt_id | 一次逻辑通话与具体拨号尝试;只有持久化意图后才产生call。P1不启用自动FALLBACK;未来启用仍属原执行并计CPS |
| event_id / aggregate_* | SaaS MQ inbox和对应实体/状态域版本;由Dispatcher持久事务分配/递增 |
| turn_id / segment_id / revision | 文字片段与最终稿替换语义,不以消息到达时间判断新旧 |
| recording_id / upload_id / oss_id | 既有录音授权、会话和验证后资产引用;不能用路径或ETag伪造oss_id |
| recording_id / upload_id / bucket / object_key | 原录音和上传事实、对象位置;不含OSS ID、TOKEN或签名URL |
| agent_id / cell_id(内部草案) | Dispatcher预配置的执行端身份与Cell绑定,不能由Agent自报覆盖;共享证书不等于单节点身份 |
| boot_id / session_epoch(内部草案) | 一次进程启动及Dispatcher绑定代次;旧回报不能覆盖新会话,旧执行事实仍需对账,不直接丢弃 |
| agent_version / protocol_version | 二进制发布版本、gRPC协议版本;不是AI的agent_version_id |
@@ -110,7 +110,7 @@ P1从管理批准的静态route_policy/caller_profile选择供应商trunk与获
| call.status | Agent经ARI观测+Dispatcher授权账本 → Dispatcher | call_id、execution_id、任务关联、call_state、call_version、attempt_id、attempt状态、实际线路/Cell/出口、时间/原因;尚未定名的键在GAP-01冻结 | 同call/attempt域更新;只有实际证据才dialing/ringing/answered,迟到状态不回退 |
| transcript.updated | Agent的ASR/对话/播放证据 → Dispatcher | call_id、turn_id、segment_id、role、revision、text、is_final、start_ms、end_ms、playback_state | transcript_segment域;同段高revision替换,final不被中间稿覆盖;不等整通话OSS上传 |
| call.finished | Agent终态事实+Dispatcher对账/汇总 → Dispatcher | call_id、execution_id、任务关联、call_version、outcome、起止/时长/原因、attempt汇总、资产处理快照 | 固定通话终态,后处理可pending,不覆盖独立资产的新状态 |
| recording.ready | SaaS经MQ返回complete的verified结果 → Dispatcher | call_id、recording_id、oss_id、format、channels、sample_rate_hz、duration_ms、size_bytes、checksum_sha256 | recording域;只报告verified资产,不携带上传凭据/公开URL |
| recording.uploaded | Dispatcher经MQ可靠发布上传事实 | call_id、recording_id、upload_id、bucket、object_key、format、channels、sample_rate_hz、duration_ms、size_bytes、checksum_sha256 | recording域;只报告已知事实,不携带OSS ID、上传凭据或公开URL |
| recording.failed | Agent本地/上传失败、Dispatcher授权/校验失败 → Dispatcher | call_id、recording_id、stage、reason_code、retryable、next_retry_at(若有) | 标记资产失败,不改变通话终态;合法ready可完成恢复 |
| transcript.failed | Agent/Dispatcher发现文字缺段或不可恢复错误 → Dispatcher | call_id、原因、retryable、受影响segment(适用时) | 明确不完整,不能把现有部分文件包装成完整最终稿 |
| contact.opt_out | 获批业务判定 → Agent及时报告 → Dispatcher | call_id、task_id、task_item_id、请求时间、关联turn/segment(若有) | SaaS及时持久禁发并处理关联任务屏障,不等挂断;不自造关键词判定 |
@@ -122,9 +122,9 @@ P1从管理批准的静态route_policy/caller_profile选择供应商trunk与获
- role建议customer/agent/system;playback_state为not_applicable/generated/sent/playback_confirmed/cancelled/unknown,具体冻结按主契约。生成/发送不等于已听见。
- 当前同段final同内容幂等、异内容冲突;未来允许修订须改契约。超长turn拆稳定segment,不截断文本。
- call_state允许queued→dialing→ringing→answered→ended,省略未发生阶段;waiting是命令状态。reconciling不是虚构终态。
- recording的pending/uploading/verifying/ready、transcript的pending/streaming/finalized/failed、delivery的pending/broker_confirmed/failed分别维护。
- `call.finished`先到、较低版本的独立`recording.ready`后到仍应合并;不能用全局最大版本滤掉资产/片段。
- 文本OSS归档不是第9种既有事件,也不能冒充recording.ready;查看实时文字继续用transcript.updated。归档授权/引用扩展见GAP-02,P1不启用且不阻塞实时文字。
- recording的pending/uploading/uploaded/failed、transcript的pending/streaming/finalized/failed、delivery的pending/broker_confirmed/failed分别维护;不新增VERIFYING。
- `call.finished`先到、较低版本的独立`recording.uploaded`后到仍应合并;不能用全局最大版本滤掉资产/片段。
- 文本OSS归档不是第9种既有事件,也不能冒充recording.uploaded;查看实时文字继续用transcript.updated。归档授权/引用扩展见GAP-02,P1不启用且不阻塞实时文字。
- ASR-only仍上报真实customer文字及获批opt-out事实,不伪造agent回答/播放或接通证据。两模式下角色/播放状态/失败分支的合法组合须在GAP-01/GAP-08补齐;不能为省事关闭实时文字或opt-out。
## 6. SaaS↔Dispatcher 全MQ交互目录
@@ -138,8 +138,8 @@ P1从管理批准的静态route_policy/caller_profile选择供应商trunk与获
| 通话查询 | SaaS→指定D;D→SaaS | 原call/attempt及独立资产状态,不按当前配置补历史 |
| call整体补传 | SaaS→指定D;D→SaaS | 固定截止点/原事件ID和版本,不支持局部筛选,不重拨 |
| source-command整体补传 | SaaS→指定D;D→SaaS | 尚无call也可补传结果,不重发执行命令、不递归自身结果 |
| 业务上传会话 | D→SaaS;SaaS→原D专用Topic | 保留原资产/租户/会话登记语义,只传业务元信息,不获取OSS配置/TOKEN |
| complete/verified | D→SaaS;SaaS→原D专用Topic | SaaS独立验证,verified前不ready;D本地验证不能替代 |
| 上传完成事实 | D→MQ指定持久队列 | 原upload/recording事实和对象位置;可靠入队后完成本项目交付,不获取OSS配置/TOKEN |
| SaaS后续处理 | 不在本项目职责 | 不等待消费、verified或OSS ID,不新增VERIFYING |
| AI配置/授权 | D→SaaS;SaaS→原D专用Topic | 原租户/不可变版本/摘要/有效授权,见§6.1 |
所有请求响应均持久关联目标/来源D、原租户及业务对象;持久后ACK、状态/outbox同事务、重复/迟到/超时/重启沿原关联恢复。超时不表示未执行,不换D重拨,不回退HTTP。错误分类保留“不存在/冲突/保留过期”等语义,精确MQ错误码及期限待GAP-10冻结,不直接搬HTTP状态码。
@@ -235,8 +235,8 @@ OSS配置/TOKEN来源不属于上述SaaS MQ目录:OSS配置存于D配置文件
| R09 ApplyTaskControl | D→A | 原ControlRequest语义、task/租户目标、requested revision、持久控制命令及授权策略 | accepted/applying;真正屏障/挂断确认后回报applied;pause/drain保留已拨出/振铃及已接通的原生命周期,stop hangup另验权限 |
| R10 QueryExecution | D→A | 原执行/通道关联或受限分页对账请求 | 返回Asterisk观测、执行文件/未交付资产状态及证据时间;通道不在当前列表不证明从未拨过 |
| R11 ReportExecutionEvent | A→D | 稳定fact标识/内容摘要、执行/通道归属、观测时间、来源序列、事实类别及源业务数据 | D事务去重并生成/关联权威MQ事件,成功回持久接收结果;调用方不指定aggregate_version跳过D裁决 |
| R12 RequestUpload | A→D | 绑定执行的资产类别/稳定ID、size/checksum及源录音元信息;同一资产的显式重试申请(新15分钟 token) | D依据自身OSS配置文件,经SDK提供临时TOKEN及受限目标/headers/期限,A不持长期凭据;既有SaaS业务会话仍MQ,业务响应pending/有界等待/重取待冻结,不等SaaS签TOKEN。text_archive分支在GAP-02冻结前拒绝,不伪装录音 |
| R13 CompleteUpload | A→D | 原绑定资产/会话、实际文件元信息与完成事实 | D经MQ提交complete,收到并持久校验SaaS verified后提交资产状态/outbox;Unary最终结果衔接待冻结;text_archive同样受GAP-02门禁,不以PUT回报直接生成ready |
| R12 RequestUpload | A→D | 绑定执行的资产类别/稳定ID、size/checksum及源录音元信息;同一资产的显式重试申请(新15分钟 token) | D依据自身OSS配置文件,经SDK提供临时TOKEN及受限目标/headers/期限,A不持长期凭据;不申请SaaS业务会话;原请求重放返回原授权及原期限,新显式请求才可重新签发。text_archive分支在GAP-02冻结前拒绝,不伪装录音 |
| R13 CompleteUpload | A→D | 原绑定录音/上传、实际文件元信息与完成事实 | D同事务保存事实和recording.uploaded outbox;原通知可靠进入指定durable队列后返回完成,MQ未确认时保留恢复状态;不等待SaaS回复、不返回OSS ID;text_archive仍受GAP-02门禁 |
P1的R05/R09及静态维护必须校验目标/版本并收敛R08许可,不长期锁SQLite等网络。未来R06同样纳入屏障;“全部Unary”或“静态配置”都不等于无需业务屏障。
@@ -254,7 +254,7 @@ P1的R05/R09及静态维护必须校验目标/版本并收敛R08许可,不长
| 拒绝再联系 | 获批判定→R11 | 通话/任务/成员、请求时间/段关联 | contact.opt_out,及时驱动SaaS禁发/任务屏障 |
| 控制屏障/挂断进度 | R09/R11/查询 | command/task/revision、目标范围、旧许可与各阶段占用、挂断事实 | D汇合所有必要目标后才command.result applied |
| 配置加载/失败/恢复 | P1静态部署后R01/R11;后续R04/R06 | 静态制品/目标关联、Agent/boot、desired/applied/revision/hash、实际加载证据 | P1留存加载证据/AgentStatus;在线管理发布回执后续,不新增SaaS业务事件 |
| 资产上传/交接/失败 | R12/R13及失败R11 | 绑定资产/会话、文件封口/size/checksum、SaaS verified结果或错误 | recording.ready/failed;成功以SaaS校验为准,文本归档扩展受GAP-02限制 |
| 资产上传/通知/失败 | R12/R13及失败R11 | 绑定录音/上传、文件封口/size/checksum、对象位置和通知事实 | recording.uploaded/failed;成功以可靠MQ入队为准,文本归档扩展受GAP-02限制 |
健康采样走R01,不把每次心跳作为持久业务MQ事件。节点移除要先保留受控只收尾状态直到原执行/资产对账完成;强制移除需显式人工恢复路径,不能一删Endpoint就丢弃待交付事实。
@@ -282,16 +282,16 @@ P1的R05/R09及静态维护必须校验目标/版本并收敛R08许可,不长
每个执行/资产有受控目录与元信息文件、文字追加文件、音频临时/封口文件、待回报事实及上传进度。字段记录原tenant/execution/call/attempt关联、内容摘要、是否封口/验证/已被D持久接收、下一步恢复动作;目录名不得直接拼任意tenant_key/外部路径。
关键元信息先同步到盘后原子替换,文件单写者、追加记录尾部可识别,不把Flush当Sync;不创建Agent SQLite、通用数据库或自造消息中间件。重启扫描只恢复对账/上传/上报,不重跑originate。录音用现成Asterisk/音频能力,不手写WAV头。
关键元信息先同步到盘后原子替换,文件单写者、追加记录尾部可识别,不把Flush当Sync;不创建Agent SQLite、通用数据库或自造消息中间件。重启扫描恢复原事实/通知,不重跑originate,也不自动重新PUT;失败或过期上传必须显式重新申请。录音用现成Asterisk/音频能力,不手写WAV头。
### 10.2 录音时序
1. 接通开始流式记录实际双向音频;实时文字同时走R11,不等资产封口。
2. 完成/取消时正确封口;故障时保留完整段并明确不完整状态。
3. A调用R12向D领取临时上传TOKEN;D按自身OSS配置文件提供受限TOKEN/目标。原业务会话/资产登记仍经SaaS MQ,CALL_NOT_REGISTERED语义及精确关联/错误待冻结;配置缺失/无效失败,不向SaaS取配置/TOKEN,保留原文件。
3. A调用R12向D领取临时上传TOKEN;D按自身OSS配置文件提供固定15分钟的受限授权/目标。配置缺失/无效明确失败,保留原文件;不向SaaS取配置/TOKEN,也不申请上传会话或资产登记。
4. A按指定目标/headers直传OSS,不持长期凭据;TOKEN失效仅显式向D重新申请,对象ID/内容绑定不变,不自动续期/重传。
5. A调用R13;D经MQ提交complete并等待SaaS独立对象验证的MQ响应;合法verified持久后才事务落资产状态及recording.ready outbox,不无限阻塞Unary。
6. D可靠发布MQ;A只有取得“D持久接收”还不够立即删文件,仍须满足既有verified、ready交接、无未决恢复、至少24h测试保留条件。
5. A成功PUT后调用R13;D事务保存原上传事实和recording.uploaded outbox。持久消息进入指定durable队列/绑定、mandatory无return且publisher confirm成功后,才完成本项目交付;不新增VERIFYING,不等待SaaS处理或OSS ID。
6. A只有取得“D持久接收”还不够立即删文件,仍须满足原通知可靠入队、无未决恢复和至少24h测试保留条件。MQ故障或确认丢失只恢复原消息身份的通知,不重新PUT、新建资产或重拨。
### 10.3 文本归档
@@ -324,12 +324,12 @@ P1数据流:管理平台审批不可变制品 → 核验来源/版本/哈希
[G0开发准备与契约冻结方案](G0开发准备与契约冻结提案_v0.1.md) D01–D10方向及模式/许可/恢复机制已获用户确认;下表仍跟踪尚未交付的源字段/合同和验证,不再表示已确认方向待用户审批。该文档不是第二套Schema,权威源发布并验证后才关闭相应GAP。
D07补充确认:**OSS配置存于D配置文件,Agent经R12向D领取临时上传TOKEN后直传OSS**;SaaS不下发OSS配置/TOKEN,D保留SDK签发能力,不转发文件。既有业务会话/资产登记及R13后的complete/verified仍经SaaS MQ,SaaS独立校验后才ready/OSS ID。原每次TOKEN有效15分钟、过期显式重申请的约束保留,重申请对象是D,不向SaaS索取TOKEN;不自动续期,Agent不持长期凭据。
D07补充确认:**OSS配置存于D配置文件,Agent经R12向D领取固定15分钟临时上传TOKEN后直传OSS**;SaaS不下发OSS配置/TOKEN,D保留SDK签发能力,不转发文件。Agent成功PUT后经R13报告原上传事实,D持久保存recording.uploaded outbox并以可靠入队完成本项目交付;不申请SaaS会话、不等待complete/verified/OSS ID,不自动续期,Agent不持长期凭据。
| ID | 缺口 | 文档处理/退出条件 |
| --- | --- | --- |
| GAP-01 | 信封已对齐,但8种事件payload专属Schema及部分条件规则未完整机读化 | 在上游唯一生成源补齐并验正反例;未覆盖部分阻塞冻结/业务上线,不能以object校验冒充完整验收 |
| GAP-02 | D的OSS配置文件/TOKEN约束及SaaS业务会话/verified衔接;文本归档仍缺合同 | 配置/TOKEN由D提供而非SaaS;核验配置格式、SDK及UploadGrant映射、显式重申请、对象定位/校验所需信息;SaaS最终校验仍MQ,原HTTP废弃;文本归档延后 |
| GAP-02 | D的OSS配置文件/TOKEN约束及上传事实通知;文本归档仍缺合同 | 配置/TOKEN由D提供而非SaaS;核验配置格式、SDK及UploadGrant映射、显式重申请、对象定位、实际size/checksum和recording.uploaded可靠入队;不等待SaaS处理;文本归档延后 |
| GAP-03 | 静态制品交接与后续在线管理发布适配 | P1先批准静态版本/哈希/目标/来源/加载事实及唯一写入合同,旧直写停用;完整在线发布/回滚和R04/R06延后 |
| GAP-04 | 首发Unary及身份/许可/状态结构尚无批准Proto | P1冻结R01–R03/R05/R07–R13实际职责、字段/错误/幂等/大小/超时;可获批合并,R04/R06不先造空框架 |
| GAP-05 | 共用证书的单节点授权与全组泄露风险 | 保留用户共用证书决定,但必须有受控Endpoint、独立D身份、自动节点会话、重放隔离及全组轮换/撤销演练;不能宣称节点级证书隔离 |
+12 -12
View File
@@ -21,7 +21,7 @@
| P1-03 至少3家SIP | 至少3个不同供应商独立trunk;登记单 Cell/出口授权、主叫/前缀/codec/额度,使用配置/路由/协议 fixture 覆盖;未知/未授权组合拒绝;SIP 外呼时间门禁为 Asia/Shanghai `09:00`(含)至 `20:00`(不含),边界外 fail-closed;真实供应商外呼延期第二阶段 | E01–E08/E20;供应商配置矩阵、SIP/ARI/媒体 fixture、`internal/callwindow` |
| P1-04 ASR-only | 百炼/火山两ASR适配分别验证;D从契约 fixture 取批准版本,命令→ASR文字/录音→结果 outbox 闭环;LLM/TTS不可用不影响且调用/额度为0;ASR参数实际生效、无虚假播放 | GAP-08/09、E09/E14/E17/E22、L06/§5.1;单 Cell 隔离组合 |
| P1-05 完整AI | OpenAI兼容LLM+火山TTS在本地/协议隔离环境完成双向对话/取消/打断/超时/背压验证;契约参数直达SDK/控制器,调参不改代码/重启,不硬编码model/voice/speed;无旧实现/静默降级/旧音频重播;真实供应商费用/联调延期 | E09–E11/E14/E17/E22、L06/§5.1;协议/参数/音频 fixture |
| P1-06 结果与资产 | 所有SaaS↔D业务改经MQ,8类业务事件及新版请求响应正反例通过,无HTTP兼容/回退;实时文字/opt-out及时,补传保留原事件ID而不重发执行;SaaS MQ verified后才ready,Agent直传字节一致、恢复不重拨 | S01–S04/S16/S23、E08/E12/E17–E19、A07/A15/A16 |
| P1-06 结果与资产 | 所有SaaS↔D业务改经MQ,8类业务事件及新版请求响应正反例通过,无HTTP兼容/回退;实时文字/opt-out及时,补传保留原事件ID而不重发执行;Agent直传字节一致,D将recording.uploaded可靠交付指定持久队列;不等待SaaS会话/verified/OSS ID,通知恢复不重传文件或重拨 | S01–S04/S16/S23、E08/E12/E17–E19、A07/A15/A16 |
| P1-07 租户骨架/全局额度 | 只启用1个租户,未启用租户拒绝;独立队列/原值key/复合幂等/窗口受控;单 Cell 竞争租户/供应商/按模式AI额度不超配;双租户和跨 Cell 汇总不在本轮 | C05–C07、S08/S09/S18/S20–S22;SQLite/实际许可计数 |
| P1-08 故障与控制 | 重投10次同一执行只产生一次实际发起;覆盖§4.1崩溃窗口、MQ/Unary/ARI断连及D/A重启,未知不重拨/不释放;pause/stop/CAS/最后许可及 `09:00`–`20:00` 时间门禁正确,旧库恢复正确 | S01–S27的P1部分、E02/E03/E13/E21、A07/A08/A18/A20 |
| P1-09 静态配置/稳定性 | 管理批准制品经维护窗口加载;错误版本/哈希、未排空/未确认、部分失败均不恢复相关准入;两种模式按§9.1受限隔离负载持续稳定测试,告警/录音/资源无未解释泄漏 | E04/E05/E13、A09–A14的P1部分、S27;单 Cell profile与原始计数器 |
@@ -82,7 +82,7 @@ D全局唯一ID/独立Topic是当前合同要求;P1增加D1/D2两组标识/Top
| C08 | 模式隔离 | mock/mixed/real 可辨认;real 拒绝 Mock/测试凭据,Mock 默认不能拨公网电话 |
| C09 | 运行与指标隐私 | 日志、错误、trace、pprof 不泄漏密钥/对话/完整号码;指标无无界高基数标签 |
| C10 | MQ消息/类型/大小反例 | 新版全MQ请求/响应严格Schema验证,bool不充int、字符串不转数值,MQ256KiB及分消息上限/缺字段/额外字段反例;新包未冻结即阻塞,不用旧HTTP Schema冒充 |
| C11 | MQ幂等/关联/目标 | 控制/查询/补传/配置授权/上传申请/complete/verified均绑定原D、租户、请求和业务对象;缺关联/错目标/同键异内容拒绝,重复/迟到/乱序/超时及重启恢复保留原决定,既有作用域不放宽 |
| C11 | MQ幂等/关联/目标 | 控制/查询/补传/配置授权及上传事实均绑定原D、租户、请求和业务对象;缺关联/错目标/同键异内容拒绝,重复/迟到/乱序/超时及重启恢复保留原决定,既有作用域不放宽 |
## 4. MQ、数据库与控制面验收
@@ -110,7 +110,7 @@ D全局唯一ID/独立Topic是当前合同要求;P1增加D1/D2两组标识/Top
| S20 | 拓扑/ACL/启动漂移 | 每个D有独立Topic/接收队列/绑定;错D响应、共队列抢收或广播过滤均不通过。exchange类型/绑定/持久化/上限/拒绝策略与新版不符就不ready,保留既有租户隔离;验证不可路由与来源归属 |
| S21 | 队列满/blocked | 满队列拒绝新发布、不丢队头,SaaS 留原 ID/任务;内存/磁盘报警与 blocked 限制接入,恢复不爆发无界重试 |
| S22 | DLQ 与安全停用 | 死信恢复回原租户并经过全部配额;禁用/删除租户前对账未决命令、outbox、补传/资产;不能删共享结果队列或悬空任务 |
| S23 | 分域版本/乱序资产 | command/call/segment/recording 分域合并;高版本 call.finished 先到也不吞掉低版本独立资产/attempt,recording.ready 不被结束快照覆盖 |
| S23 | 分域版本/乱序资产 | command/call/segment/recording 分域合并;高版本 call.finished 先到也不吞掉低版本独立资产/attempt,recording.uploaded 不被结束快照覆盖 |
| S24 | 较旧备份恢复 | 恢复时保持准入关闭,识别已 ACK 但回退的 inbox/控制/占用/幂等水位并与 Cell/SaaS 对账;旧租约/版本不能恢复成第二次拨号许可 |
| S25 | 墓碑/去重保留 | stop 墓碑、execution 去重不随普通日志 TTL 删除;清理后仍有可验证永久失效依据,否则阻塞清理;历史重投不复活 |
| S26 | 时钟偏移 | 覆盖前跳/回拨及阈值边界;偏差>500ms告警、>2s停止新准入,单调时钟用于 duration、DB 时间用于共享租约/CPS,不能错误释放活动占用 |
@@ -125,7 +125,7 @@ D全局唯一ID/独立Topic是当前合同要求;P1增加D1/D2两组标识/Top
3. 中央账本认领及Agent执行文件持久化后、ARI请求已送达但响应丢失、接通后进程退出。
4. pause accepted 后、Cell 屏障确认前、applied 发布后且旧授权仍在网络途中。
5. 通话终态已落盘但全局结果未确认、结果已发布但本地确认丢失。
6. OSS PUT中断,complete MQ已持久但响应丢失,SaaS verified已发但D未持久/ACK,D已持久verified但ready未发布;沿原upload/资产恢复,D本地验证不替代SaaS。
6. OSS PUT中断、上传事实已持久但通知发布/确认丢失、Dispatcher重启;沿原upload/通知身份恢复,不重新PUT、不新建资产,不等待SaaS处理。
7. AI配置/授权、控制/查询/补传MQ请求已送达但回复丢失,D重启、响应乱序/迟到及目标错配;保留原关联,不HTTP补查、不重新执行。
每个窗口都检查:实际发起次数、持久状态、有效所有权/控制版本、资源占用、消息原 ID、录音可恢复性。无法证明外部动作未发生时应进入待对账,而不是重试 originate。
@@ -145,14 +145,14 @@ D全局唯一ID/独立Topic是当前合同要求;P1增加D1/D2两组标识/Top
| E09 | ASR双模式生命周期 | 百炼/火山适配分别验首包/最终结果/结束/取消,模型/语言/中间稿/采样与热词/VAD等获批参数实际生效;ASR-only无LLM/TTS及虚假播放;复用协议但无旧项目运行依赖 |
| E10 | 新LLM/TTS首发必需 | 完整模式使用新批准官方/开源SDK,真实权限/音频/模型/取消/打断/限额/费用验收;未启用或仅Mock即P1 blocked,不调用旧实现或用“兼容OpenAI”替代能力证据 |
| E11 | 打断与慢消费者 | 旧轮次不再播放;生成/排队/发送/播放可区分,缓冲按时长/字节有界 |
| E12 | 录音交接与重放 | D只在持久校验SaaS MQ verified/oss_id后出ready,本地HEAD/PUT成功/confirm不替代;R12/R13有界pending/原操作恢复和最终结果可验证,哈希/长度/ID正确,不重拨/覆盖未交付文件 |
| E12 | 录音交接与重放 | D持久保存上传事实并以persistent、正确绑定、mandatory无return、publisher confirm完成recording.uploaded交付;不等待SaaS处理/OSS ID;R12/R13原操作恢复,哈希/长度/对象位置正确,不重PUT/新建资产/重拨 |
| E13 | 资源泄漏/优雅退出 | 多轮超时、取消、断网后,goroutine、FD、端口、buffer、临时文件和占用回到可解释基线 |
| E14 | 模式相关依赖缺失 | 必需依赖unknown/缺失拒新任务;ASR-only可在LLM/TTS不可用时正常运行,完整模式不能静默退成仅ASR;未知不是健康 |
| E15 | Cell 集合/重启/世代 | 新增/移除 Cell 分别做 bootstrap/撤销和集合屏障;旧 boot/旧授权世代/乱序序列不能恢复 ready;响应丢失后幂等重试不覆盖漂移事实 |
| E16 | 管理统计事实 | 经版本化协议发布实际发起/attempt/接通/结束/来源及线路/Cell/出口历史快照、水位;与独立 SIP/ARI 证据比对,缺事实标不完整,禁止用当前配置补历史 |
| E17 | 文字与已播放事实 | final 后迟到中间稿不覆盖;当前同段 final 同内容幂等、异内容冲突,未来修订需先改契约;打断后的旧片段不记已播放,缺段发 transcript.failed,不阻塞 call.finished |
| E18 | DNC 闭环 | 获批判定后 contact.opt_out 及时发出,不等挂断;独立 SaaS 持久禁发并处理相关任务屏障,拒绝擅自新增关键词判定 |
| E19 | 上传注册/签名/覆盖 | CALL_NOT_REGISTERED 保留文件;签名过期续原会话,不新建资产;限定 HTTPS/headers/对象、拒任意重定向,verified 后旧签名不能覆盖,独立读取实际字节验证 |
| E19 | 上传授权/重申请/内容绑定 | 缺失或过期授权保留文件;原请求返回原授权,新显式请求才重新签发,不新建资产或自动重传;限定HTTPS/headers/对象、拒重定向;实际文件大小/SHA-256匹配;预签名URL不宣称OSS原生一次性能力 |
| E20 | 已知媒体事故回归 | ExternalMedia 未就绪/端口未取到、桥成员不齐、错误来源/SSRC、取消末帧均有协议夹具;原 MixMonitor 路径须停止封口后上传,新录音路径须证明等价 |
| E21 | spool/永久丢盘 | 70/80/60% 阈值、活动录音余量、满盘/只读/截断/永久丢盘分别注入;不删未确认文件,不静默丢录音/重拨,不宣称未上传数据 RPO=0 |
| E22 | 不可变AI版本/配置来源 | GAP-08/09从上游批准生成;D按任务版本向SaaS获取/校验,Agent固定有效快照,条件必填/合法事件/超时/资源有反例。同版本异内容/缓存越权拒绝,不增MQ模式/URL/密钥;调参新版本无重启生效,在途不变;细则§5.1,历史事实按S19 |
@@ -250,7 +250,7 @@ P1运行不依赖旧Python Cell;迁移验证如需临时桥接而旧端不满
开发前的交付/解锁证据见 [G0开发准备与契约冻结方案](G0开发准备与契约冻结提案_v0.1.md) §2/§5/§8。D01–D10方案已获用户确认;项目内 W01 版本和 W02 Proto 已交付,但外部权威签收、D10正式PoC和真实供应商仍未完成;十项交付门禁不增加本方案88项运行验收数量。§5.5内部profile已确认为隔离PoC初始值,未实测、不是生产SLA,也不覆盖下节上游/供应商基线。P1必须实施当前租户及单 Cell 原子配额,P2/第二阶段再扩展多租户公平、跨 Cell 和并行竞争验收。
E12/E19新增明确子场景(不新增用例编号):证明OSS配置来自D配置文件,A经Unary向D领取临时TOKEN后直传,SaaS不下发OSS配置/TOKEN,D/gRPC无文件内容。配置缺失/无效明确失败,不向SaaS取配置、不使用A本地长期凭据;过期由A显式向D重申请,原资产/会话不变。D失联时已有有效TOKEN可继续直传,过期保留文件,不自动续期或重试。直传成功但Dispatcher/complete不可达时保留原资产和待完成状态,恢复后幂等完成SaaS verified再由Dispatcher发recording.ready及OSS ID;PUT成功不得提前发ready。
E12/E19新增明确子场景(不新增用例编号):证明OSS配置来自D配置文件,A经Unary向D领取固定15分钟临时TOKEN后直传,SaaS不下发OSS配置/TOKEN,D/gRPC无文件内容。配置缺失/无效明确失败,不向SaaS取配置、不使用A本地长期凭据;过期由A显式向D重申请,原上传/对象绑定不变。D失联时已有有效TOKEN可继续直传,过期保留文件,不自动续期或重试。直传成功但Dispatcher/MQ不可达时保留原事实和通知恢复状态;恢复后以原recording.uploaded身份可靠入队,不等待SaaS verified/OSS ID,不重新PUT。
## 9. 继承的量化测试 profile
@@ -265,12 +265,12 @@ E12/E19新增明确子场景(不新增用例编号):证明OSS配置来自D
| 心跳/时钟 | 心跳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,优先实时结果,分批读取避免全量入内存 |
| 保留 | 事件补传7d,SaaS测试inbox至少8d,日志7d;对象覆盖对应ready完整补传窗口,未决恢复对象不普通清理;已交接本地录音满足verified/ready确认/无恢复任务后至少24h;去重/stop墓碑不套普通TTL |
| 保留 | 事件补传7d,SaaS测试inbox至少8d,日志7d;对象覆盖对应recording.uploaded事实窗口,未决恢复对象不普通清理;已可靠入队录音无恢复任务后至少24h;去重/stop墓碑不套普通TTL |
| 磁盘 | 测试录音/缓存卷至少16GiB;70%告警、80%停新接单,60%且依赖恢复再接单;预留空间覆盖活动通话最大剩余录音 |
| 中断/恢复 | SaaS消费/上传中断5min,恢复后10min内补齐固定负载;卷完好时已提交事实/最终稿/封口录音不丢,DB/MQ恢复后60s内恢复安全调度;永久丢未上传录音不承诺RPO=0 |
| 查询/受理/控制 | HTTP查询P95≤500ms;正常消费时SaaS持久发布→命令持久受理P95≤2s;健康且无不确定发起时控制持久受理→相关屏障applied P95≤2s;失联不能当成功样本 |
| 公平SLO | 持续有资格且资源足够的B/C发现活跃后≤2s得首次许可;稳定竞争至少100许可,3等权租户份额偏差≤10个百分点;不足/隔离者单列 |
| AI/文字/录音/清理 | ≥100有效轮次;VAD结束→首个有效TTS送桥P95≤1500ms,插话→停止旧TTS送桥P95≤500ms;最终稿形成→SaaS Mock事务应用P95≤3s;≤180s通话挂断→对象verified/ready消费/授权读取≤120s;正常结束30s内清本次通道/桥/媒体 |
| AI/文字/录音/清理 | ≥100有效轮次;VAD结束→首个有效TTS送桥P95≤1500ms,插话→停止旧TTS送桥P95≤500ms;最终稿形成→SaaS Mock事务应用P95≤3s;≤180s通话挂断→recording.uploaded事实可靠入队/授权读取≤120s;正常结束30s内清本次通道/桥/媒体 |
| SCALE-MOCK | ≥2调度实例、100租户×并发12、全局并发1200、模拟CPS20;额外200占用预算用于拨号/振铃/换批,不能计已接通;扩展测试Cell容量并注明,不套DEV容量4;暖机后持续补充新授权执行,≥1000模拟接通维持60min |
| 重投/真实规模 | 同ID重投10次仍同一事实;真实完整AI≥1000已接通稳定≥60min,并验证N+1安全容量与故障后新授权补负载;不将断掉的旧execution换ID自动重拨 |
@@ -346,8 +346,8 @@ P0冻结前逐行登记阶段、状态、责任人及风险。本轮MQ-only新
| A12 | 静态SIP/运行配置 | 部署交付管理批准制品、D按身份核验目标/版本;原Schema/来源/哈希/凭据引用验证,缺密钥/错mode/任意路径/脚本拒绝;首次实际加载确认前not-ready,不要求R04/R06在线推送 |
| A13 | 动态改配/停用 | D固定目标集合、持久发布意图、先关闭准入并收敛必要占用,再ApplyTrunkConfig;每Agent实际加载确认;修改/新增/移除/disabled不逐呼reload、不擅自强挂 |
| A14 | 静态失败/人工恢复/单写 | 版本/哈希冲突、写完未加载、部分失败/确认丢失、恢复旧制品失败/旧代次回报均注入;保持真实installed和阻塞,旧管理直写不可与受控静态入口并行;在线自动回滚后续 |
| A15 | D配置文件/临时TOKEN与SaaS最终校验 | D配置文件为OSS唯一配置源,A经Unary领TOKEN且不持长期凭据;缺失/无效失败、过期显式找D,不向SaaS索取配置/TOKEN,保留D签发能力。业务会话/complete/verified仍MQ,SaaS独立校验成功后才ready;不以D本地验证替代、不换资产归属、不泄漏密钥/TOKEN |
| A16 | 实时文字+OSS归档 | transcript.updated/final/opt-out按原时限回SaaS,不等整通话上传;文本归档无批准授权接口时明确未启用,不冒充recording.ready、不生成新event_type;冻结后验证原segment版本/哈希及查看权限 |
| A15 | D配置文件/临时TOKEN与上传事实 | D配置文件为OSS唯一配置源,A经Unary领固定15分钟TOKEN且不持长期凭据;缺失/无效失败、过期显式找D,不向SaaS索取配置/TOKEN,保留D签发能力。成功PUT后D以recording.uploaded可靠入队完成本项目交付;不等待业务会话/complete/verified/OSS ID、不泄漏密钥/TOKEN |
| A16 | 实时文字+OSS归档 | transcript.updated/final/opt-out按原时限回SaaS,不等整通话上传;文本归档无批准授权接口时明确未启用,不冒充recording.uploaded、不生成新event_type;冻结后验证原segment版本/哈希及查看权限 |
| A17 | 维护更新与版本校验 | P1维护关闭准入/排空/更新/重新激活后才恢复;只用已验证组合,不兼容阻塞,Proto字段号不复用,构建版本不冒充AI版本;混合N/N-1与在线滚动后续 |
| A18 | 退出/断联/收尾 | D退出不强挂Agent已授权通话;A保留文件并可在恢复后补报;SIGTERM先关准入/排空,超时未决状态明确;永久丢盘不承诺零丢失,保留/告警按§9 |
| A19 | Endpoint清单增删 | 列表受控版本化;新增先验证/激活/加载再可调度,移除先排空/对账再撤销新任务权限;旧执行事实仍有受控收尾/人工恢复路径,不能简单丢弃或误删资产 |
@@ -363,7 +363,7 @@ P0冻结前逐行登记阶段、状态、责任人及风险。本轮MQ-only新
| Endpoint最小启动、共享mTLS | 主方案§5.4–5.5;交互R01–R03/R05 | A03–A06/A19 |
| 健康/负载/版本/供应商和配置版本 | 主方案§5.6;交互§9 | A09–A11、E15/E16 |
| P1静态配置;在线发布延后 | 主方案§8;交互§12 | A12/A14、E04/E05的P1部分;A13后续 |
| OSS配置在D文件、A向D领临时TOKEN、SaaS最终verified不变 | 主方案§7.3;SaaS↔D契约§6.1;D↔A契约§6.2;交互§10 | A15/A16、E12/E17–E19、L07;配置缺失/无效与显式向D重申请反例 |
| OSS配置在D文件、A向D领临时TOKEN、recording.uploaded可靠入队 | 主方案§7.3;SaaS↔D契约§6.1;D↔A契约§6.2;交互§10 | A15/A16、E12/E17–E19、L07;配置缺失/无效与显式向D重申请反例 |
| 全部事件/字段与遗漏 | 交互§1–6/§13;只读字段索引 | A01、C02/C10/C11、GAP-01~09按阶段 |
| 单节点/单Agent/单Asterisk、至少3SIP fixture | 主方案§1.1/§5/§8 | P1-02/03、E01、授权组合矩阵 |
| 单租户首发;双租户/公平后续 | 主方案§6.2/§10 | P1-07、S18/S08;第二阶段 S06/§9公平profile |