docs: organize top-level documentation

This commit is contained in:
2026-09-23 09:24:14 +08:00
parent 42d59c71a7
commit 7fd594a2a4
11 changed files with 50 additions and 50 deletions
@@ -0,0 +1,254 @@
# G0 开发准备与契约冻结提案 v0.1
## 1. 状态、权限与使用方式
**状态:D01–D10原方向已确认,旧W01契约和W02 Proto已有项目内证据;本轮用户确认SaaS↔Dispatcher全MQ,受影响G0的项目内部分已按[计划§1.2/§8.2](../plan-0918.md)重新验证。** SaaS与D之间所有请求、响应和事件禁止HTTP,每个D具备全局唯一ID和独立接收Topic/队列;V1 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 测试;未修改父项目权威来源、字段索引或生成产物,未访问真实供应商或创建云资源。文件名保留“提案”以保持链接稳定,不代表还需重复审批已确认方向。
当前结论:可以继续开展 P0 契约补充、隔离 PoC 和项目内实现;不得宣称 G0 通过或把项目内基线冒充外部权威,也不得启动未获授权的真实供应商/云/拨号。架构、首发范围和供应商方向沿用已有决定,不重新选型。
- 外部字段/路径/状态仍以带来源哈希的上游主契约为准。本文件记录已确认的变更方向和验收要求,**不能被代码当 Schema 使用**。后续由上游维护者修改唯一来源并发布,再导入生成;本文件保留为决策历史,不继续手工维护字段副本。
- 内部机制、字段方向及兼容策略已确认,正式 Proto 的类型/编号/错误映射等仍需定稿与校验。本轮不生成 `.proto`,也不以临时 JSON 绕过契约;未提供的字段细节、实际预算和技术验证不能视为一并通过。
- 角色简称:S=SaaS/上游契约维护方,M=管理平台维护方,D/A=Dispatcher/Agent 实现方,O=部署/安全/测试负责人。角色不代表已指定人员或已签收;实际姓名、版本、日期和证据须在批准记录补全。
- 保留既有业务语义和8类业务事件,但原7条SaaS HTTP路径与AI GET全部废弃为接入方式;控制/查询/补传/配置授权/上传协调都改为专用MQ请求响应。D↔A仍Unary,A→OSS仍直传,不新增任务mode字段或内部MQ。
- 依据:[重写方案](Go重写方案_v0.3.md)、[通信设计](../contracts/通信与事件数据交互_v0.1.md)、[验收方案](../acceptance/验证与切换验收_v0.3.md)、[只读字段索引](../references/OpenAPI与MQ字段索引_v0.1.md)、[组件清单](../dependencies/开源组件选型与复用清单_v0.2.md)。索引不是独立契约包。
## 2. 方案确认记录与开发边界
D01–D10 的共同状态为 **用户已确认方案/源发布与验证未完成**,D07 明确为 Agent 从 Dispatcher 取配置后直连 OSS 上传。确认人及依据为 §1 的本轮用户指令;实现/安全签收责任人和产物版本仍待实际登记。不得再次把已确认方向标成“待用户审批”,也不得因此关闭 GAP。编号保留用于跟踪交付证据,不增加原 88 项运行验收数量。
| 确认项 | 对应缺口/交付内容 | 提交与签收角色 | 最小退出证据 | 未满足时阻塞 |
| --- | --- | --- | --- | --- |
| D01 | §3 八种事件的 payload、条件约束与同域版本 | S 提交,D/A 联合验证 | 上游发布版本、Schema、每种事件正反例、跨语言校验/摘要适用规则 | 业务结果序列化与消费闭环冻结;不阻塞只读工具 PoC |
| D02 | §4.1 双 AI 模式及旧版本兼容(GAP-08) | S 提交,D/A 签收 | 两分支 Schema、旧完整模式兼容样本、模式资源/行为断言 | 两模式执行配置实现与 P1 双模式签收 |
| D03 | §4.2 SaaS MQ配置/授权、参数、受控引用(GAP-09/10) | S/O 提交,D/A 签收 | 请求响应/原D关联、租户授权/撤销合同、摘要金样、参数到SDK映射PoC | 配置缓存/下发和对应 AI 参数能力承诺 |
| 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合同,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 原始记录 | 未验证组件进入正式实现/发布依赖 |
### 2.1 允许先做、不能先做
允许先做:在另行安排开发时,用成熟库开展 SQLite 事务/恢复、AMQP 确认、ARI/媒体、Schema 工具链、SDK 参数映射和 mTLS 原理 PoC;本地资源必须隔离真实外网,不使用生产凭据。PoC 不依赖猜测的业务字段,也不以临时协议投入业务运行。
不能先做:实现者自行给 MQ 补字段、用宽松 object 校验生产事件、凭本地 CLI/env 选择 AI 模式、先写临时许可协议后让上游兼容、把供应商 SDK 示例成功当作参数/取消/收费行为验收。
可按依赖逐项解锁开发,不要求所有真实供应商测试先完成;但完整 G0 须有 P1 合同和本地关键 PoC 签收,P1 交付仍须独立完成真实验证。真实凭据、外呼、云资源或费用均另行授权。
### 2.2 P1/P2 配额澄清(既有要求,不是新提案)
P1 必须实施当前真实租户的限额,并在单活 D 的同一 SQLite 事务域汇总两个 Cell 的租户/全局/供应商/Cell/按模式 AI 并发及 CPS;保留租户复合键、有界窗口、未知占用、停止屏障。不能把“只有一个真实租户”解释为免除租户隔离或额度执行。
P2 增加多个同时活跃租户的等权轮询、额度不足跳过、公平恢复、全链路背压和公平性验收;不是首次实现原子配额。P1 可用两个模拟租户测隔离,但不因此开放第二真实租户。
### 2.3 本轮MQ-only补充(已确认方向,消息细节待冻结)
本轮补充纳入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的职责保留,项目完成边界为recording.uploaded可靠入队,不等待SaaS对象处理。
## 3. D01:外部事件 Schema 补齐方案(已确认方向)
### 3.1 上游修改方法
由上游将现有 `event_type` 分支与专属 `payload` 约束关联,保留原信封和严格未知字段策略。复用上游已有 Call、Attempt、TranscriptSegment、Recording、CommandResult 等实际组件;组件名称以源发布物为准,不在本项目重复定义。现有字段可复用不等于可以省略事件条件约束。
源发布前须给每种事件补齐必填/可选、类型/枚举、字节限制、可空规则、时间单位和条件组合。下表是**已确认的约束及复用方向**,不是已发布完整字段表;缺少源字段的地方由 S 明确扩展细节,不能凭表生成未经权威源定义的 JSON。
| 事件 | payload 方向与必须冻结的条件 | 最少反例 |
| --- | --- | --- |
| `command.result` | 复用原命令结果快照;按 command_type/status 限定 execution/call/control revision 及 reason 的适用性;发起前拒绝/超期没有 call_id;executing 不代表已发 SIP,控制 applied 必须有全目标屏障事实 | 无 call 拒绝却造 call_id;仅 accepted 就标控制 applied;CAS 失败返回成功 |
| `call.status` | 复用同 call 下的通话/attempt 状态快照,关联租户、原命令/执行及当前 attempt;线路/Cell/出口来自实际获批路由;阶段时间依据观测而非纯本地计时 | answered 无接通事实;换租户/attempt;用整体版本丢掉独立域事件 |
| `call.finished` | 复用通话终态和 attempt 结论;开始/接通/结束及持续时间保留原单位与未接通规则;允许录音/文字仍在后处理,不覆盖资产域 | 未接通却生成接通时长;通话结束强行把未交接录音设 ready |
| `transcript.updated` | 复用段/轮次/revision/text/final、说话方和真实播放证据;中间稿/最终稿规则与同段 final 同内容幂等、异内容冲突机读化;完整文本不截断 | 迟到中间稿覆盖 final;仅生成 TTS 就标已播放;ASR-only 伪造助手播放 |
| `transcript.failed` | 引用原 call 和受影响文字范围、源定义的失败阶段/原因/恢复性;无 segment 时如何表达整流失败由上游明确;不虚构空 final | 无关联对象;失败后静默删除已持久最终稿;用 recording.failed 代替 |
| `recording.uploaded` | 报告原录音/上传事实、对象位置和文件元信息;不带OSS ID、上传会话、长期凭据或公开URL | 事实缺字段、大小/摘要不符、跨bucket/object绑定;把SaaS处理结果伪装上传事实 |
| `recording.failed` | 复用原录音身份、失败原因与可恢复性;明确上传超时、校验失败和永久丢失的区分;仍保存后续对账/补传所需关联 | 失败产生新 recording_id 逃避幂等;永久丢失报 ready |
| `contact.opt_out` | 复用原租户/task/member/call 关联与拒绝时间/证据引用;最小必要信息;ASR-only 同样具备经批准的判定与通知流程 | 未授权模型判定;跨成员关联;等待录音上传才发 opt-out |
### 3.2 顺序、重复与测试
- D 对同一稳定事实和内容摘要幂等;同事实 ID 异内容必须冲突。MQ event_id/aggregate 字段继续由 D 的权威事务生成,不能由 Agent 自报最大版本。
- 版本按源规定的聚合实体和状态域处理,独立文字段/录音不被其它域的大版本覆盖;重放使用原事件身份,publish confirm 不等于 SaaS 应用收讫。
- 每种事件至少保留一个合法样本和一个非法样本;另测缺必填、未知字段、错误事件分支、跨租户/归属、空值、边界尺寸、乱序与重复。Schema 能验证形状,数据库归属、控制屏障和 verified 来源还必须运行时验证。
- 上游发布包含上述样本与预期结果的版本;本项目导入原样例运行 Go 校验,不手写一组与上游互不校验的“同名 Schema”。
## 4. D02/D03:双模式与 AI 配置方案
### 4.1 两模式与兼容(用户已确认,待 S 发布)
在**不可变 AI 版本对象**新增 `mode`,枚举为 `asr_only` / `asr_llm_tts`;不放入 `call.execute`。模式随 agent_version_id 固定,不能通过运行时参数覆盖;当前上游 Schema 尚未更新。
| 情况 | 已确认解释与条件 |
| --- | --- |
| 已有发布版本未带 mode | 仅在符合原完整 AI Schema 时按 `asr_llm_tts` 执行;不回写原对象、不注入默认值后重算原摘要。该兼容规则须进发布版本说明 |
| 新发布版本 | 发布入口要求显式 mode;读取旧版本与创建新版本可有不同校验上下文,不能拿兼容读取规则默许新建省略 mode |
| `asr_llm_tts` | 保持原 llm/prompt/tts/asr/conversation 条件和完整对话能力,不因模式引入放宽原必填 |
| `asr_only` | 要求原身份/不可变标记、asr 和适用的 conversation 控制;llm/prompt/tts 必须缺省而非空对象或 null;不得建立 LLM/TTS 会话或占用其专属额度 |
| ASR-only 的 conversation | 仅要求 silence_timeout_ms/max_duration_ms;opening/allow_interrupt/max_turns/sentence_max_chars/max_pending_audio_chunks 禁止提供,避免对无播放/生成场景产生假控制。若业务需识别段数量上限,应另提明确字段,不套用对话轮数 |
| 缺必需能力/未知 mode | 拒绝新准入,不自动切换成另一模式,不静默关闭不支持参数 |
ASR-only 静音以实际输入活动及获批 VAD 策略判定,最长时限从源契约约定的接通时点起算;静音到期按获批结束策略关闭,不自动播放提示。最终文字、录音、文字失败、opt-out 和控制屏障仍适用。opt-out 判定复用独立批准规则;禁止为此暗中开启 LLM,规则缺失时该能力门禁仍阻塞。
模式分支由上游使用源 Schema 机制生成,保持 additionalProperties 严格策略。当前 Schema 无法表达上述 ASR-only,上游发布前不能提交假的完整配置“先跑起来”。
### 4.2 读取、授权与快照(方向已确认,待 S/O 补齐部署与合同细节)
1. D按原任务租户/agent_version_id经MQ请求SaaS不可变配置和授权,SaaS经原D专用Topic响应;校验请求关联、来源/目标与可信租户,不能用响应覆盖归属。无权、找不到、不可用分别表达,精确MQ消息及错误待GAP-10冻结;旧AI GET已废弃,不开发HTTP client或第二个task-config路径。
2. 在首次成功准备执行配置时,将 `(原值 tenant_key, agent_version_id, 源 Schema 版本, 原内容摘要)` 与执行绑定并持久化。尚未取得有效配置的排队项不能准入;已有有效绑定的排队/在途项不因“最新版本”改变配置。新任务须显式引用新 agent_version_id。
3. 摘要采用上游已有算法及其精确输入边界,导入跨语言金样:键顺序、Unicode、数值、缺省/null/0/false 均须验证;本项目不另选 canonical 算法。若上游未发布明确算法,保持该项阻塞,不把普通 json.Marshal 字节当权威摘要。
4. 模式/参数归一化结果作为独立有效快照,关联原摘要和适配器版本,不修改原不可变内容。日志仅留版本、摘要及允许的脱敏参数,不留 prompt/variables/密钥。
5. P1 **不凭离线缓存授予新的发起许可**:D 可缓存不可变内容,但最终许可前须有本次执行的有效授权检查;SaaS 不可达且无上游已批准有效期的授权记录则拒新准入。已确认撤销阻止新许可,已发许可按 §5 有界收敛,在途终止仍走批准控制,不擅自挂断;撤销传播时效等源细节仍需冻结。
6. 如需要离线准入,上游须另外发布租户绑定、有效期、撤销传播和缓存可用性规则;未批准前不从本地 TTL 或 ETag 推导业务授权。每次权威检查使用新版获批MQ请求响应,不走HTTP或自造授权接口。
7. provider_ref/credential_ref 由 O 管理的受控注册表解析,包含供应商、允许 Endpoint、API/资源版本、凭据种类、租户作用域及出口/能力限制;AI 业务参数不写入该注册表。Agent 不直连 SaaS,也不接收全局凭据库。
8. D 向被绑定 Agent 交付本执行最小权限配置/凭据;会话元数据、令牌和敏感值不进日志。配置哈希与原 JSON 校验、有效参数映射和权限检查都成功才准入。并发 SDK 客户端不得共享可变的 per-call 参数。
### 4.3 参数差异表(方向已确认,待进入上游,不是本地 Schema)
现有 model/prompt/voice/speed、ASR 输入/语言/interim、temperature/max_tokens/timeout_ms 和 conversation 字段必须原义传入 SDK/控制器;不能因 SDK 有默认值就省略映射。已有 speed 范围等不在本文件重定义。
| 待源发布的扩展名 | 已确认语义/单位/缺省方向 | 能力与边界要求 |
| --- | --- | --- |
| `asr.hotwords` | 有界非空词字符串列表;未提供表示不启用用户热词 | S 冻结项数/UTF-8 字节上限和必要的词权重模型;仅适配 SDK 真正支持的表达,不能塞 raw_request |
| `asr.vad` | enabled(bool)及获批的语音开始/结束静音阈值(ms);false 与未提供不同 | 明确供应商 VAD 或本地控制器职责,不能双重处理互相截断;阈值范围需供应商证据 |
| `llm.top_p` | number,0 < top_p ≤ 1;未提供按版本化能力表明确解释 | 与 temperature 的同时使用/互斥规则逐供应商批准,不擅自丢弃一个参数 |
| `tts.volume` | 对外统一 0–1 线性音量;显式 0 表示静音,不是默认音量 | 须验证 SDK 原生单位及转换是否保真;只能离散或无等价语义时先提交变更确认,不能先四舍五入上线 |
| 阶段时限 | 分 connect/first_output/idle/total,均整数 ms;与现有 timeout_ms 的优先级须源合同定义 | 网络连接由适配器、流空闲/总时限由控制器共同执行;缺省使用已批准模式 profile 的明确值,不静默沿用 SDK 默认 |
批准参数必须附:必填条件、合法范围、缺省来源、显式零/false 处理、SDK 精确字段/转换、供应商不支持时的拒绝行为和正反例。上表未给齐的供应商范围属于待取证项,不允许本地臆定;metadata 不承担执行参数透传。OpenAI 兼容 SDK 自动重试显式关闭;取消不能触发旧音频重播或二次收费请求。
## 5. D04/D05:首发 Unary 与最后许可方案
### 5.1 公共关联和幂等(语义已确认,待正式 Proto 定稿)
保持通信设计 R01–R03/R05/R07–R13 职责,不增加在线发布 R04/R06 空壳。公共字段按已确认方向分层,精确 wire 定义随 Proto 定稿:
| 层 | 字段方向/规则 |
| --- | --- |
| 传输/诊断 | protocol_version、request_id、trace_id;RPC deadline 只限制一次等待,不是业务授权/通话期限;重试可换 request_id,不能换业务幂等键 |
| 目标/授权 | dispatcher_epoch、agent_id、cell_id、boot_id、session_generation;节点会话凭据仅安全元数据传输;目标均来自 D 的受控绑定 |
| 持久幂等 | operation_id + operation_type + target;内容摘要不可变,同键异内容冲突;业务归属键始终包含原值 tenant_key 与源 execution/call/attempt 标识 |
| 配置/控制 | AI 原版本/摘要、SIP applied revision/hash、task_revision、admission_generation 与 resource_reservation_id;缺失/过期/不匹配拒绝 |
| 事实提交 | fact_id、fact_digest、observed_at、发生时 boot/source_sequence;当前认证会话与历史事实 boot 分离,D 事务持久接收后才确认 |
错误映射沿用通信设计 §7.1。accepted 仅表示持久接收,applied 需真实屏障/加载/控制证据;NOT_FOUND、超时、CANCELLED、Asterisk 当前通道列表为空均不是“从未拨过”证明。
### 5.2 共用证书下的激活、代次和撤销
- D 从预配置 Endpoint 列表发 R01 受限探测,校验 TLS SAN/SNI、Agent 群组证书及部署期预期目标,再发 R02 绑定 agent/cell/boot。A 只接受独立 Dispatcher 身份;不能执行 A 自报 URL。
- D的全局唯一逻辑ID及专用Topic不等于运行代次,身份保持/恢复及重复拒绝随GAP-10冻结;不能以新epoch清除原消息归属。D 每次取得唯一运行所有权后建立新的 dispatcher_epoch;A 每次启动产生新的 boot_id,旧 boot 的执行许可一律无效,但旧执行/资产占用不清零。generation 在所属 epoch 内单调;epoch 不能按 UUID 大小排序。
- R02 采用持久 activation operation_id:D 先保存 pending 绑定,再由 A 持久接受,D 收到/查询到相同绑定后标 active;回复丢失重查原操作。active 前 A 不获得执行/敏感配置权限。会话令牌用成熟安全随机源,D 仅保存必要校验材料,禁止自研密码协议。
- 激活冲突、旧 epoch、重放、错误 boot、未完成绑定都 fail closed。新 epoch 激活必须在旧 D 已停用/网络隔离/身份撤销可证明后进行;备份恢复不能仅修改一个 generation 数字声称隔离旧进程。
- 共用私钥泄露可能冒充整组 Agent,Endpoint 和会话只能降低误绑定/重放风险,不形成节点级密码学隔离。撤销/轮换针对全组;先关准入、收敛许可并处理在途,再替换证书和会话。不能关闭主机名验证或以风险声明替代演练。
### 5.3 最后许可的持久顺序(已确认安全边界)
内部阶段为 prepared → permit_granted → dispatching → observed/unknown → terminal;这些不是新增 MQ status。资源预留、文件记录、D 账本和 Asterisk 事实分别承担职责,不宣称跨 SQLite/文件/ARI 原子事务。
1. D 事务建立原执行/attempt 的发起意图及完整资源预留、唯一 Agent/Cell 路由和配置绑定,持久后才 R07。A 校验后先持久执行文件,R07 重复只能返回原决定,不能发起通话。
2. A 准备发起时,以原 attempt 和唯一 permit 请求 R08。D 在一个短 SQLite 事务内校验当前任务控制、准入代次、会话/健康/版本、授权时段/截止、供应商矩阵及全部额度,再记录唯一 permit_id 和有效期;不跨网络持有 SQLite 写事务。
3. 同 permit 操作重试返回原许可/原期限,不延长授权、不另计一份并发。CPS 在最后许可发放时不可重复扣减地记入限速窗口;许可未使用也不在窗口内提前退 CPS。但许可速率不能直接当真实发起速率:不同许可可能因传输/调度延迟集中提交。P1对每个共享资源采用保守窗口,在任意“源CPS统计窗口+最大许可TTL+时钟安全余量”内发放的许可不超过原窗口预算,同时保留原突发上限;不得各Cell自行发满一份。该办法降低可用吞吐,须由O按供应商实际CPS口径批准,并用两Cell延迟/集中提交负例验证,不能擅自缩短窗口换吞吐。并发释放另依实际可证明终态判断,不能将两者混为退款。
4. A 在统一的**本地发起/控制串行区**检查 boot/session、控制/准入代次、实际加载快照和许可期限,持久记录 dispatching 及确定的 ARI 通道关联后,才允许至多一次 originate 提交。R05/R09 关闭准入也进入该串行区;检查与提交之间不能让控制悄悄穿过。
5. 串行区不能无限等 ARI:网络等待有界,超时/进程中断即保留 unknown;已经提交或可能提交的请求不以同 ID 再次 originate。ARI 客户端对该操作禁止透明自动重试;确定通道 ID 用于关联对账,不单独构成 exactly-once 证明。
6. A 用本机单调时钟对一次 R08 请求建立有效期上界:计时从请求发出起,不从响应收到起;同请求重试不重置起点。D 返回剩余有效期不得超过原许可期限/业务截止。绝对授权截止还须检查批准的时钟偏差安全余量;RPC 晚到、时钟异常、失联或本地代次变化拒绝发起。
7. boot 重启不恢复旧单调计时或旧许可;dispatching/unknown 先查本地文件、D 意图和 Asterisk/其它获批证据,不自行重新拨号。prepared 未越过发起点也必须得到 D 对旧许可收敛的明确决定,不能自动续期。
8. R11 提交稳定事实:D 将去重、状态/配额和 MQ outbox 放同一事务,commit 后才成功响应。成功包丢失 A 重报原 fact;A 不得因缺确认删除唯一事实,也不得换 fact_id 制造新结果。
这套顺序选择“宁可保留不确定占用,也不重拨”,不是承诺网络分区下无需对账的 exactly-once。许可控制的本地界线是首次提交 ARI,不是对 Asterisk 发出线上 INVITE 的跨进程原子提交;已经在途的 ARI 请求必须计入未知占用和控制屏障,不能仅等 TTL 就报告无发起可能。许可过期只禁止新的 ARI 提交,不证明过去没有提交,不能自动释放未知占用。CPS还须观测 Asterisk 实际发起时间;若其排队能使供应商实际口径超额,须先用适用的 Asterisk 原生限速/准入能力及成熟 SDK 验证解决,否则阻塞 D04,不以许可窗口通过冒充实际CPS通过。
### 5.4 pause/stop/维护屏障
D 先 CAS 持久化控制及关闭新许可,再向所有相关 Cell 下发同一控制操作和目标 revision。A 持久关闭门闩、使未使用旧许可失效,报告已提交/可能提交/振铃/接通/未知占用。串行区内抢先提交的发起属于屏障前执行,必须计入控制范围,不能漏掉。
- pause/drain:不新增发起,屏障前已拨出/振铃/接通沿原生命周期继续;暂停后的恢复须新的合法控制/授权,不复活旧许可。
- stop/drain:同样阻止新发起,已提交执行按源 drain 规则结束;stopped 不可恢复。stop/hangup 必须逐项执行获授权挂断并核对终态,超时不当成功。
- 维护/SIP 静态改配:关闭受影响资源,旧许可失效且未知占用完成对账,按维护要求排空后才加载。不能拿任务 pause 的完成条件代替配置安全排空。
- applied 只有在所有必需目标的对应语义均有证据时成立。失联 A/仍可能在途的 ARI 请求保持 applying/reconciling;D 自己等到 TTL 不替代 A 的收敛证据。
### 5.5 恢复/故障矩阵与内部 PoC 初始 profile
| 注入位置 | 必须保留与允许的动作 | 禁止 |
| --- | --- | --- |
| D commit 前后崩溃、R07 回复丢失 | 原执行/预留/操作恢复并查询原结果 | 改 execution_id 再投;重新发一份额度 |
| R08 已提交但回包丢失 | 查/重取原许可和原期限;过期进入收敛 | 新 permit 无限延寿;先退并发再重拨 |
| A 写 dispatching 后、ARI 提交前后崩溃 | 按原通道/意图对账;不能证明则 unknown | “通道没找到”即重发 originate |
| 控制与最终检查/ARI 提交交错 | 同串行区判定先后,D 等所有目标屏障事实 | 仅 CAS 成功就发 applied |
| A 重启/D epoch 更新/时钟跳变 | 废弃旧发起权限,保留旧通话/资产归属 | 新 boot 清空占用;旧事实按旧 boot 丢弃 |
| R11 commit 后回复丢失 | 重报同 fact 同摘要,返回原持久结果 | 二次 outbox 事件、跨状态域版本覆盖 |
| SQLite 备份恢复、旧 D 仍可能存在 | 隔离旧 D 后受控激活;无证据停止新准入 | 两份 SQLite 并行发许可;NFS 共享文件充当 HA |
以下为**用户已确认、尚未实测的内部隔离 PoC 初始值**,不覆盖验收 §9 的上游基线或真实供应商预算:最终许可 TTL 上限 1000ms、R08 单次 deadline 500ms;一般查询/事实提交单次 deadline 3s;接收控制单次 deadline 1s(不是 applied 完成时限)。重试不得越原业务/许可截止,退避继承已批准 profile;不能证明 TTL/网络/时钟余量时拒绝发起。
每条内部 Unary 编码后上限 512KiB,许可/控制请求上限 64KiB;不传音频/录音字节,不无限批量事实。外部MQ仍按256KiB源上限及新版分消息约束验证,旧HTTP JSON 64KiB仅保留为历史来源,不用于恢复SaaS↔D HTTP;内部较大上限不能绕过外部上限;超限拒绝/告警而非截断。协议初版仅支持同一批准版本和显式能力集,未知枚举/关键字段拒绝准入;后续兼容范围须有升级测试再扩展,不承诺自动滚动升级。
## 6. D06–D08:静态交接、录音与运行恢复
### 6.1 P1 静态制品交接(流程已确认,待正式制品合同)
M 是唯一编辑/审批面;制品外层记录 source_release、source_digest、approval_reference、精确 cell/trunk 目标、源 Publication 的 revision/config_sha256 和协议兼容范围。SIP 配置体复用源结构,外层包装不得冒充已有 API 已支持的新字段。
交接顺序:M 批准 → O 核验来源和两个 Cell×至少三供应商授权矩阵 → D 持久关闭资源准入/R05 收敛 → O 经唯一受控入口原子部署/加载 → A/R01 报实际快照与脱敏 Asterisk 加载证据 → D 核验后逐资源开放。审批记录必须证明旧直写入口已关闭。
加载证据至少关联制品版本/哈希、目标、Agent boot、Asterisk 镜像/配置版本、实际加载时间及 SDK/CLI 观测摘要;文件写入成功或 reload 返回 0 不足以证明加载。失败保留关闭准入,人工恢复旧制品同样重新核验;不自动拨电话验证,也不自动启用未获白名单授权的另一出口。
### 6.2 P1 录音配置与 Agent 直连 OSS 上传(用户已确认)
**上传数据面固定为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与期限。recording.uploaded仅报告原call/recording/upload标识、对象位置、格式、时长、完整文件大小及SHA-256;MQ中不传D配置文件、长期凭据、TOKEN或签名URL。SaaS后续处理不属于本项目,不以其消费或对象登记作为完成前置。
1. A封口并持久元信息,经R12向D领取固定15分钟的临时TOKEN,D按自身配置复用SDK提供。同一请求重放返回原授权及原到期时间,不能借重试自动续期;不向SaaS申请会话或资产登记。
2. A 直接把文件内容上传至指定 OSS,不把文件发给 D,也不通过内部 gRPC 传录音字节。D 失联时已有仍有效授权可继续上传;TOKEN过期或缺失则保留原文件,D恢复后由A显式向D重新申请,不自动续期,不向SaaS申请TOKEN,不改用A本地长期密钥。
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:原上传事实已持久保存、recording.uploaded已可靠入队、无恢复任务且满足保留;MQ confirm 不等于 SaaS 应用收讫,不额外等待不存在的应用 ACK。文本 OSS 归档继续受 GAP-02 延后约束,实时 transcript.updated/opt-out 不等待 OSS。
### 6.3 运行 profile 与单活恢复登记
审批登记须包含:profile_id/版本/源哈希/覆盖理由、1 D+2 A 的精确资源绑定、租户/供应商/Cell/模式额度、队列与待发起窗口、心跳/许可/时钟余量、磁盘/文件阈值、保留/备份、真实测试号码/线路/费用上限、维护窗口、RPO/RTO 和签收人。
沿用验收 §9/§9.1 的适用基线(例如心跳 2s、租约 10s、失联停新发起、磁盘 70%告警/80%停新准入/60%恢复),拓扑差异只用有批准记录的覆盖;不把 DEV 全局 6 或后续 1000 路当 P1 真实授权。未取得实际预算/供应商上限时填写 blocked,不填虚构数字。
单活采用本机部署监督+成熟 OS 排他锁防止同机双启动,SQLite 保持本地盘,在线备份走成熟驱动/SQLite 支持的备份能力,包含 WAL 一致性;这些不构成跨机隔离。人工迁移/恢复先证明旧 D 停止与发起权限隔离,再恢复库、激活新 epoch、核对两 A 的文件/Asterisk/未决资产和额度,最后开准入。卷丢失时按实际事实损失签收,永久未上传录音不得承诺 RPO=0。
## 7. D09:独立只读契约包与可重复生成方案
后续进入开发时,在本项目导入一个只读版本目录 `contracts/upstream/<release>/`(此目录本轮未创建),完整包含需要的 Schema/OpenAPI/示例及其引用闭包;禁止外部 `$ref` 在构建时联网或越出包目录。本项目自己的批准 Proto 来源单独维护并记录版本,不手写外部业务结构替代源类型。
清单记录:发布 ID、上游仓库/不可变 commit 或发布地址、文件路径与 SHA-256、批准引用、生成器版本/校验参数、源 Schema 方言和许可证。发布方的来源校验与内容哈希都要验证;自报哈希不等于可信来源。
已确认的后续流水线(本轮未执行):
1. 验证来源和全文件哈希,拒绝缺失/额外未声明/路径越界/引用不闭合的文件;导入过程不能覆盖旧已发布版本。
2. 用锁定的成熟生成器从源生成 Go 类型和只读字段索引,并使用锁定的成熟 Schema 校验器显式执行运行时校验;类型生成不等于校验。生成器不支持的关键约束应报阻塞,不手改生成代码绕过。
3. 对上游合法/非法样例执行一致性测试;同输入重建结果必须确定,生成结果差异须在 CI 失败。源升级经过兼容测试而非静默替换。
4. 将项目复制到无父目录、无外部网络依赖的隔离工作区,用已批准依赖缓存完成生成/构建/测试;普通运行不读取父文档目录。维护者导入可以显式取上游发布物,普通构建不可以偷偷读父目录。
5. 字段索引同步由同一源版本生成,本文件提案不进入生成输入。本轮保留现有索引,避免手工改成第二套 Schema。
## 8. D10:最小 PoC 顺序与证据
复用[组件清单](../dependencies/开源组件选型与复用清单_v0.2.md)现有候选,不新增协议栈。本轮不声称已下载或验证任一精确版本。每个采用项记录 module/tag/commit、源码/依赖哈希、Go 1.27.1 构建、许可证/NOTICE、传递依赖/漏洞处置和适用协议版本。
| 顺序/已有验收域 | 本地最小检查与通过条件 | 失败处理 |
| --- | --- | --- |
| 1.Schema/生成工具 | 真实上游 3.1/2020-12 特性、严格对象、条件分支、缺省/0/false、引用闭包和中文原值在 Go 中与金样一致;不只编译生成类型 | 换成熟生成/校验工具或修上游;不放宽 Schema |
| 2.SQLite+Agent 文件/恢复 | 单 Cell 额度事务、inbox/outbox crash 点、WAL 备份还原;文件使用成熟 FS 原语验证落盘/原子替换/损坏隔离和重启重放 | 驱动不满足先替代;不改成内存字典或 Agent 业务数据库;跨 Cell 额度延期第二阶段 |
| 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从配置文件提供固定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. 确认结果、后续交付与完成判定
D01–D10 的建议已按 §1 用户指令确认,D07 明确为 Agent 取 Dispatcher 配置后直连 OSS 上传。不再重复要求用户审批相同方向。后续优先完成 D02 的上游双模式 Schema 和 D04/D05 的正式 Proto/会话合同,同时由 S 补 D01/D03、M/O 补静态交接、上传会话细节和实际 profile;选型 PoC 与只读包工具验证可以并行准备。
逐项跟踪格式:`确认项 → 用户已确认语义/例外 → 待交付字段/产物 → 上游发布版本/哈希或内部协议版本 → 实施/签收责任人及日期 → PoC/验收证据 → 未决风险`。缺产物/责任人/证据时标为“方案已确认,交付/验证未完成”,不能退回“待用户审批”或冒充“已冻结/通过”;对未给出的字段细节、真实额度/费用以及偏离已确认方案的变更另行确认。
**本轮完成标准:**确认状态、Agent→OSS直传及相关验收要求在文档间一致,父项目权威源未改;不是十项门禁全部通过。后续获得修改上游/创建代码的执行授权再导入发布物、生成 Proto/类型和执行隔离 PoC;本轮不扩大到这些操作。真实云、供应商消费和拨号仍另获明确授权,P1/P2/后续功能分别验收。
+434
View File
@@ -0,0 +1,434 @@
# SIP Go Agent 重写方案 v0.3
本版按本次讨论收敛为**稳定、快速单节点内测上线**:1个Agent/1套Asterisk/1个单活Dispatcher、单 Cell、单租户、静态发布、ASR-only与ASR+LLM+TTS双模式。至少3家SIP供应商保留为 trunk 配置与协议 fixture 覆盖;双节点、第二 Cell、双租户、真实 SaaS/MQ 联调和生产 ECS 延期第二阶段。保留Cobra双命令、Dispatcher SQLite、Agent文件、Unary gRPC及关键可靠性;动态治理和规模化另立项。文件名保留,当前仍只有文档,不代表实现或验收通过。
## 1. 状态、范围与决策
**状态:设计草案,不代表实现、切换或生产验收完成。**
用户已确认:
1. 使用 Go 1.27.1,完整重写 Agent,但分阶段替换。
2. 范围包含调度、Cell 执行、ARI/RTP/录音、AI 流式适配、Cell 配置接收;不是只做一层 Go 代理。
3. `go-sip/` 是独立 Git 仓库,自己的文档也全部放在其 `docs/` 中。
4. 不重写 Asterisk,不合并独立管理平台,不改变 SaaS 的 MQ 业务边界。
5. SIP 和其它组件有适配需求的现成开源库/官方 SDK 时必须复用,绝不从零手写替代协议栈或客户端;库不满足时先证明缺口、选择替代或向上游修复,不能自行转为手写实现。
6. 同一Go module、同一版本/二进制,使用Cobra的 **`agent`、`dispatcher` 两个业务子命令**,分进程部署,方便统一更新而不混用权限。
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领取固定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. 固定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不接收或转发录音字节;成功上传后由D可靠发布recording.uploaded;不申请SaaS会话,不等待verified或OSS ID,SaaS后续处理不属于本项目。每次TOKEN有效15分钟;过期或失败保留源文件,仅由调用方显式向D重新申请,不向SaaS取TOKEN,不自动续期或重试。此直传不改变Agent不直连SaaS的边界。
### 1.1 本次P1目标与完成标准
| 目标 | 完成判定 |
| --- | --- |
| 单节点/单 Cell/单租户 | 1个Agent/1套Asterisk/1个单活Dispatcher;单 Cell 静态制品、单租户准入和本地持久恢复闭环;不要求第二节点或第二 Cell |
| 至少3家SIP供应商 | 每家独立trunk/主叫/前缀/codec/额度的契约、路由和协议 fixture 校验;真实供应商成功外呼延期第二阶段 |
| 双AI模式 | 两种模式均在本地/隔离环境从命令到通话模拟、实时文字、录音和结果闭环;ASR-only不调用/占用LLM/TTS,真实新供应商联调延期 |
| 单租户、静态配置 | 只启用一个可信租户;使用管理批准的不可变单 Cell 快照,初装及维护变更均核验实际加载,错版本/哈希不接单 |
| 可靠性基础 | 同执行不重复拨号、未知不重拨、控制屏障/持久恢复/录音交接安全;租户独立队列、复合幂等键和单 Cell 调度边界留在本轮 |
| 契约与独立交付 | SaaS/MQ 交互按版本化契约、Schema、fixture 和本地状态机验收;可独立检出、构建、测试、打包,无父仓库/Python运行依赖 |
验收按[验收方案§1.1–§1.2](../acceptance/验证与切换验收_v0.3.md)执行。P1通过只表示当前单节点/单 Cell/单租户本地范围通过;不代表真实 SaaS/MQ receipt、真实供应商/ECS、双节点、第二 Cell、双租户、1000路/N+1或生产切换通过。
### 1.2 明确不做
- 不自研 SIP 协议栈,不逐通电话重写 `pjsip.conf` 或重载 Asterisk。
- 不建设第二套 SIP 管理后台,不共享管理平台或 SaaS 的业务数据库。
- 不引入自研调度框架、插件系统、服务网格、事件溯源平台或默认 Kubernetes。
- 不复用 `voice_test` 的 LLM/TTS;验证前仍明确Mock/未启用,但真实双模式是P1退出门禁,不是可删减项。
- P1不做在线动态配置编排、自动跨供应商FALLBACK、活动通话迁移、多Dispatcher HA、权重借用、AI插件/多供应商编排、文本OSS归档、1000路/N+1验收。
- 本轮不开发或验收双租户、公平调度和第二 Cell 汇总配额;保留 tenant_key、独立队列、复合幂等、单租户配额和控制屏障。多租户/跨 Cell 能力作为后续阶段,不能在本轮状态中标为已通过。
- 不把整份 Python 源码机械翻译为 Go,也不为使用新语言特性而增加抽象。
## 2. 已有实现与迁移依据
以下路径仅记录现有实现的来源,**不是新项目的运行依赖**:
| 现有模块 | 已观察到的职责 | 新项目处理方式 |
| --- | --- | --- |
| `agent_call/core.py` | 持久状态、租约、租户轮询、控制、MQ inbox/outbox、上传与补传恢复 | 拆出调度控制面,重建自有持久化边界 |
| `agent_call/real_cell.py` | ARI、RTP、通话资源、路由验证、执行账本、未知执行对账 | 迁移语义到Agent文件恢复与Dispatcher权威账本;不照搬Agent SQLite,不确定不重拨 |
| `agent_call/cell_agent.py` | 配置版本/哈希校验、落盘、Asterisk 应用和状态回执 | 迁移为 Cell 的受控配置接收能力 |
| `agent_call/ai_runtime.py` | ASR/LLM/TTS 流式编排及取消 | 按新项目的音频与供应商契约重新划界 |
| `services/asr-web` | 已有 Go ASR 协议实现 | 复用其已验证行为/测试;先选匹配供应商协议的现成 SDK,不照搬或重写二进制协议,不依赖原服务目录 |
| `management/` | 独立 Go 管理 API 和前端 | 保持外部系统,只通过批准的版本化接口集成 |
当前 Python 调度每轮涉及租约、指令接收、控制、调度、outbox 等多个职责;真实 Cell 已有本地执行账本。**行为和测试可以复用,进程内锁、线程组织以及调用方/执行方混在一个服务中的边界不能照搬。** 尤其不得让生产 Agent 同时扮演 SaaS 事件消费者或写 SaaS 数据库;这些能力只能存在于独立测试端。
## 3. 独立项目与契约管理
### 3.1 目录规划
下面是逐阶段建立的目标布局,不是本轮已经生成的文件列表:
```text
go-sip/
├── AGENTS.md
├── README.md
├── go.mod / go.sum # 独立模块;P1 再创建
├── cmd/sip-go-agent/ # 唯一main,Cobra根命令
├── internal/
│ ├── cli/ # agent / dispatcher 两个子命令
│ ├── contract/ # 从批准契约生成的类型/校验入口
│ ├── rpc/ # 生成的Unary gRPC桩与薄适配
│ ├── dispatcher/ # MQ、租户感知调度/配额、SQLite/outbox;P2补公平
│ ├── agent/ # 通话执行、文件恢复、配置加载、状态采集
│ ├── media/ # 现成RTP/音频能力、录音与打断
│ ├── ai/ # 现成供应商SDK薄适配
│ └── store/ # 仅Dispatcher的SQLite事务/迁移
├── migrations/ # 仅Dispatcher业务数据库迁移
├── config/ # 无密钥的配置样例
├── contracts/ # 带来源与校验和的只读契约发布包
├── tests/ # 项目内夹具、协议 Mock、集成与故障注入
├── deploys/ # 统一发布包、配置、systemd 与 Asterisk 部署入口
└── docs/ # 本项目方案、运行、验收和发布文档
```
采用单Go module、同一发布版本/镜像、少量具体类型。使用`github.com/spf13/cobra`,不手写命令解析或引入第二个业务main。显式业务子命令只有`agent`、`dispatcher`;help/version是框架能力,不再提供scheduler/cell两套别名以免运维混淆。只在真实边界建立小接口,不建通用调度/插件框架。
规划用法(当前本地制品已提供 `agent`/`dispatcher`;生产参数仍须受控注入):
```text
sip-go-agent dispatcher --config <受控配置文件>
sip-go-agent agent --dispatcher-endpoint <Dispatcher管理网地址>
```
Dispatcher配置包含MQ/SaaS受控引用、SQLite路径、两个Agent Endpoint→agent_id/cell_id绑定及准入策略;Agent证书/信任根、监听地址、ARI访问、持久目录及批准的静态快照由部署提供。**只需Endpoint指业务启动不再手填SIP/OSS配置,不代表无需凭据、持久盘、Asterisk或静态制品。** 静态制品不是允许运维另写业务Schema;不在CLI传密钥,不让agent初始化MQ/业务DB或dispatcher打开RTP端口。
两角色同一制品便于更新,不等于必须同时重启;P1采用受控维护窗口,不承诺混合版本无损滚动更新,见§5.7。
### 3.2 权威来源与独立拆仓
**本轮用户已确认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/拓扑/身份生命周期/关联已在项目内唯一 V1 包和本地证据中冻结,旧源包及哈希不改。
现有上游权威是《SaaS交互_OpenAPI与MQ契约规划_v0.1.md》(正文 v1.0)及经核验的发布产物。现有产物包括 `mq.schema.json`、`executor.openapi.yaml`、`cell-agent.openapi.yaml`、AI 配置及 SIP 管理相关契约。文件存在不代表完整覆盖:P0 必须逐条核对正文、Schema、状态语义和实现差异。
- 将经批准的协议作为**版本化、只读发布包**导入本项目 `contracts/`,记录源仓库、源版本/提交、文件 SHA-256 和生成器版本;工作区未提交修改不能冒充某个提交的发布产物。
- 类型和校验代码从该发布包生成;禁止手写另一套字段、枚举或业务 Schema。
- 同步工具在开发阶段获取发布包;普通构建、测试和发行包运行不得读取父目录。
- 新增调度器—Cell 的消息、fencing、回执或对账字段,先形成契约变更提案并获批;本方案不擅自定字段或 MQ 路由。
- 本项目自己的设计、运行、验收文档只在此处维护;上游共享契约仍只有一个编辑源。
- 本项目已独立初始化 Git,并按 `git.ipao.vip/rogee/go-sip` 维护;不创建 submodule 或改变父仓库跟踪关系。项目许可证和第三方 NOTICE 在对外发布前确认。
- [通信与事件数据交互](../contracts/通信与事件数据交互_v0.1.md)覆盖执行命令、8类业务事件、全MQ请求响应目录及13个内部Unary职责草案;[字段索引](../references/OpenAPI与MQ字段索引_v0.1.md)完整提取42个HTTP操作/115个命名组件。
- 已逐字段/哈希确认MQ信封command_type/command_id、event_type/aggregate_*及task_revision与正文一致;但事件payload目前仅通用object,专属约束仍需上游补齐(GAP-01)。不能以旧测试或通用object校验冒充完整验收。文字事件准确名称为transcript.updated。
- 42操作/115组件是上游目录,不是本项目首发工作量。P1保留既有业务语义/8种业务事件,但7条HTTP交互及AI GET改为获批MQ请求响应;消费实际所需AI配置和静态制品;不实现管理平台30个API或复制其业务后端。生成/校验仅覆盖使用入口及其引用闭包,来源包与只读字段索引保持完整。
- 当前AI Schema强制要求llm/prompt/tts/asr/conversation,缺少明确的ASR-only表达。GAP-08须先在上游批准模式/缺省/文字与错误语义;不在MQ添加临时mode字段或绕过required校验。
## 4. Go 1.27.1 的采用策略
已用本机 `GOTOOLCHAIN=local go version` 核验 `go1.27.1 linux/amd64`,并核对官方 Go 1.27 发布说明与本地 `go doc`。以下区分“新版本能力”和“已有成熟能力”,不把所有 API 都说成 1.27 新增。
| 能力 | 使用位置与收益 | 边界 |
| --- | --- | --- |
| Go 1.27 `encoding/json/v2` | 新 JSON 协议边界的候选实现,减少兼容包依赖 | 先过黄金报文与哈希兼容测试;不直接替换全部旧编码行为 |
| Go 1.27 `uuid` | 新项目自生成且契约允许的 UUID | 不改既有命令/事件/执行标识格式,更不改原值 `tenant_key` |
| Go 1.27 goroutine leak profile | 长时间通话、取消/断线循环后的泄漏诊断 | 仅管理网/本地诊断,不公开 pprof |
| Go 1.27 `testing/synctest.Sleep`、`httptest.NewTestServer` | 测试定时、取消、重试与 HTTP 协作 | 真实 socket、broker、DB 的故障仍用真实集成测试,不能只靠虚拟时钟 |
| 已有 `context`、`sync.WaitGroup.Go`、`log/slog`、`net/http`、`pprof` | 生命周期、结构化日志、内部 HTTP 和诊断 | `WaitGroup.Go` 不负责错误传播;任务必须返回/上报错误并正确退出,不把它当 panic 隔离 |
| 泛型与语言新能力 | 简单类型复用 | 没有实际重复就不用,不建立通用调度 DSL |
| Go 1.27 实验性 SIMD | 不采用 | 当前重点是 I/O 编排,不增加实验开关或平台约束 |
特别注意:JSON 新版本在重复键、大小写、空集合、数字、字段省略和对象输出顺序等方面可能与旧行为不同。`content_sha256`、签名或配置摘要必须遵守上游的**确切规范化规则**,不能把某次 `Marshal` 输出或“排序一下”当成通用 canonical JSON;Python/Go 黄金样本不一致就阻塞发布。非法/歧义 JSON 应按获批契约拒绝,不为兼容而接受歧义。
P1 建模块时拟使用 `go 1.27.1` 并锁定 CI/构建镜像及 digest;`go` 指令是最低工具链要求,不是精确锁定。CI 校验实际版本并使用 `GOTOOLCHAIN=local`,不靠运行时自动下载工具链。未来补丁升级走独立验证,不连带修改管理平台或其它服务的版本基线。
### 4.1 开源库优先是实施硬约束
标准库/现有服务原生能力能覆盖的先用它们;其余使用经评估的现成开源库。自有代码只承担契约绑定、状态机、权限、配额、事务和必要的薄适配,不能以“薄适配”为名重写 SIP/ARI、RTP 编解包、G.711、WebSocket、AMQP、数据库驱动、OSS 签名、供应商 SDK 或 Schema 解析器。
候选、许可证、上游证据与风险集中维护在 [开源组件清单](../dependencies/开源组件选型与复用清单_v0.2.md)。使用Cobra、官方grpc-go/protobuf、gopsutil、Asterisk/ARI、Pion、amqp091-go;modernc SQLite用于Dispatcher,pgx退出本项目选型,Agent不引数据库驱动业务能力。ASR/LLM/TTS按批准供应商选SDK。不将所有候选一并引入,不用sipgo/diago另起SIP栈。
P0 必须以锁定 tag/commit 的 PoC 验证 Go 1.27.1、Asterisk 镜像、API/音频格式和现有契约,检查许可证全文、传递依赖、安全通告及 SDK 自动重试;失败则阻塞该组件,不默许改为手写协议。只有业务接口确有必要才抽象,不额外引入 Web 框架、ORM、Redis 锁或认证服务。
当前契约为 OpenAPI 3.1 / JSON Schema 2020-12。生成/校验库必须实测支持这一方言及原契约;不能把开发分支能力当已发布能力,不能为了生成器方便手工降级或复制 Schema。依赖锁版、许可清单、漏洞处置与复用审查是 P1/发布门禁,验收见 L01–L08。
## 5. 目标运行架构
```text
SaaS ── RabbitMQ:D专用Topic/队列 ──> dispatcher(全局唯一ID,单活SQLite)
SaaS <── RabbitMQ:SaaS专用订阅 ───── dispatcher inbox/outbox
执行/控制/查询/补传/AI配置授权/上传协调全部经MQ;无双方HTTP
管理平台 ── 批准静态快照 ──> 受控部署入口(维护窗口)
│
dispatcher ── Unary gRPC ──────┴─ agent-1 ↔ Asterisk-1 ↔ 获授权的SIP trunk
(mTLS) ├─ 两种AI模式、文字/录音/恢复文件
└─ D配置文件 → D临时TOKEN → Agent直传OSS
```
内部不增加MQ或双向流;MQ-only不禁止Unary的HTTP/2、ARI/供应商HTTP(S)或Agent→OSS PUT。Agent不拿broker/SaaS管理凭据,业务事件统一由D发布。P1只运行一个D/Agent/Cell/Asterisk,至少3家独立trunk保留;D1/D2路由隔离仅作本地消息fixture,不扩为多D业务协调、第二Cell或HA。D与Cell同机仍分进程/目录/资源,该节点故障会同时影响中央调度。
### 5.1 Dispatcher:唯一业务状态与调度权威
持久化命令、执行归属、租户/供应商/Cell/按模式AI全局配额、控制CAS、许可、静态目标版本/加载事实、事实去重、资产交接和outbox。单个活动Dispatcher负责决策;同机竞争实例可做所有权/恢复测试,但不以两台各有SQLite的Dispatcher各自发额度。
SQLite业务状态留在Dispatcher,管理平台数据库保持独立。首版不承诺跨机自动热备:备份/恢复时必须停止旧所有者、对账,再开放新准入,禁止NFS共享DB、双活复制或把MQ当数据库。Dispatcher故障暂停新拨,已授权通话在Agent继续,结果落文件待补。
每天2万次:24小时平均约0.23次/s、8小时约0.69次/s;假设每通100条业务写入,8小时平均约69条/s,只是量级估算不是基准测试。若是每Agent2万,中央总量按Agent数汇总;验收要测峰值CPS、并发、写放大、保留和故障恢复,不据日均承诺1000路容量。
### 5.2 Agent:无业务数据库的执行端
不自行分配全局额度、不消费SaaS MQ、不承担另一份任务主库。通过Unary执行授权、控制和配置,在本Cell维护ARI/RTP/AI会话,流式落文字/录音及必要manifest,回报真实事实/上传结果。
每通话固定在同一Cell/出口;不因Dispatcher短暂不可达自动挂断,也不把Agent重启说成通话无损续接。每通话一个生命周期所有者,媒体/AI工作有界可取消,不创建每包goroutine;共享ARI事件按通话分派,控制不能静默丢失。
### 5.3 持久化与恢复(SQLite/文件方向已确认)
| 数据 | 落点 | 边界 |
| --- | --- | --- |
| 全局任务/配额/归属/控制、事件/静态配置事实/资产、inbox/outbox | Dispatcher独立SQLite | WAL、可靠Sync策略、短事务/有界写队列、唯一约束与条件更新;提交后MQ ACK,不跨网络持事务 |
| 活跃通话上下文 | Agent内存+Asterisk真实通道 | 不是任意实例可替换的无状态HTTP请求;未知执行须对账 |
| 执行关联/配置加载/待回报事实/上传进度 | Agent受控文件/manifest | 原子替换、关键事实同步落盘、单写者与尾部截断识别;不用Agent SQLite,也不自造通用数据库 |
| 文字/录音 | Agent持久spool;P1录音→OSS,文字OSS归档后续 | 实时文字先回报;文件封口、摘要校验、恢复与保留条件完整 |
推荐SQLite WAL+synchronous=FULL作为关键事实持久性基线,具体驱动/文件系统/锁/忙等待在P0验证;记录不能按RTP包或每token逐条写中央库。单文件仍只有一个writer,长期读事务/checkpoint/磁盘空间必须监控。备份用SQLite一致性备份能力,不在运行中只拷db忽略WAL。
恢复旧备份可能丢失已ACK的事实/许可/版本;先关准入,对比Agent文件、Asterisk、MQ与资产水位,不恢复过期许可或盲重拨。去重/stop墓碑不套普通日志TTL。持久卷完好崩溃、旧备份、满盘/只读、永久丢盘分开验收;永久丢失未上传资产不能承诺RPO=0。
### 5.4 Unary通信与最小启动
Dispatcher受控配置列出Agent Endpoint及agent_id/cell_id绑定,监听地址必须管理网可达;不信任Agent提交的任意回拨地址。Agent先加载受控证书、本地监听/ARI及部署交付的静态制品,以Dispatcher Endpoint请求引导;不由用户再手填SIP/OSS/AI业务配置。
初次调用若未绑定只返回pending/非敏感能力;Dispatcher主动探测**预配置Endpoint**,核对boot/协议,再向该端点激活受限节点会话。Agent得到会话后获取自身运行策略、SIP/AI版本索引和OSS策略引用,加载并报告ready。任何身份/版本/凭据缺失都只允许诊断,不接真实任务。
普通Unary复用HTTP/2连接;Execute只返回accepted/rejected,不等待整通话;状态/最终文字/上传结果独立回报。SDK重连不证明业务未执行,回应丢失只按原标识查询/重报,不盲目再originate。Proto方法/字段号、截止与大小见交互草案,P0冻结后生成。
### 5.5 共享mTLS的权限边界
用户选择所有Agent共用证书,便于扩展;证书/私钥通过Secret/受控文件挂载,不写进镜像/仓库/参数。Dispatcher使用独立受信身份,Agent管理RPC不能接受仅有Agent群组身份的对端。
共享证书只认证群组:禁止凭自报agent_id/源IP直接领取另一节点的配置或执行。采用预配置Endpoint主动激活和自动签发的受限节点会话,将agent/cell/boot/代次/操作范围绑定;无需用户逐节点填写运行token,但具体认证中间件、期限、重放/撤销机制须PoC,不能自创密码算法。
必须验证SAN/SNI/信任链,不设InsecureSkipVerify;共享证书要覆盖合法管理域名/受控SAN,扩容不能靠关闭主机名校验。管理网隔离/DNS控制/最小端口仍必要。任何Agent私钥泄露影响整组,需全组轮换/撤销;会话授权只能减少误绑定,不等价独立证书的节点隔离。把这一已知风险留在发布签收中。
### 5.6 健康准入与静态候选选择
Dispatcher定期调用GetAgentStatus,复用既有2s心跳/10s失联基线;以本机接收时间判新鲜,保留Agent采样窗口/序列/boot,过期/缺失为unknown而非0。gRPC SERVING只说明能应答,不说明可拨号。
P1状态包括软件/构建/Proto/Asterisk版本,准入必需的CPU/内存/FD/磁盘/spool、媒体端口、ARI及已知/未知占用,本模式AI健康,静态授权trunk的codec/注册适用性/desired与applied版本/哈希。完整load/IO历史、staged发布态和高级评分后续再做。用gopsutil/ARI SDK采集,区分host/container/process,不自写/proc解析。
P1选择顺序为:可信绑定与协议兼容→静态route_policy/caller_profile及授权矩阵匹配→版本/出口/新鲜健康/未排空→租户/供应商/Cell/本模式AI配额→在可行Agent内按固定轮询分配新执行。已建立执行意图后固定归属,不因健康变化改投另一Cell;未发起的新执行可选同策略下其它获授权健康Cell。CPU低不能越额度,新boot/0活动不能清中央未知占用。不做价格/权重优化、自动跨供应商FALLBACK或活动通话迁移。
CPU/内存/FD等首发必要保护阈值和恢复条件在profile登记;spool沿用70/80/60%规则。供应商“支持能力”“已配置”“已加载”“真实验证”分别展示,未验证线路不报ready。P1只传有界静态清单及必要状态,不先建全量遥测/供应商分页平台。
### 5.7 一个制品、维护窗口更新
Cobra两命令共用release版本、依赖锁、镜像和协议包;每个进程只能启动所选角色,凭据/目录/监听权限分离。更新前检查Dispatcher/Agent协议兼容范围与配置Schema,不兼容的节点拒绝新准入,不尝试猜测字段。
P1允许维护窗口暂停新任务:关闭受影响资源准入、排空/对账、备份、更新、重新激活、核验实际配置和资源后恢复。当前只验收单 Cell;第二 Cell、多节点并行和在线滚动编排属于后续阶段。Dispatcher保持唯一所有者,迁移/恢复先验证;Agent不执行远程任意shell或自下载二进制。
## 6. 核心可靠性设计
### 6.1 消息接入与 ACK
以下MQ消费/发布和业务数据库事务由D承担,Agent事实经Unary进入中央事务。每个D有全局唯一逻辑ID、独立接收Topic/队列/绑定,不能共队列竞争消费指定D的消息或广播后过滤;身份不是dispatcher_epoch。原仅按tenant路由的direct拓扑不再用于新接入。精确命名、SaaS订阅、ID生命周期与请求/响应/来源关联随W01新版本冻结,不能直接给旧Schema加字段。
1. 以可信绑定校验目标D、原租户/请求和版本;tenant_key仍原值保留、租户队列仍独立。加入D身份后须重算完整routing key/queue长度并测分隔符/通配符边界,不能套用旧224字节预算;超限保留源任务停发,不清洗、编码或截断。
2. 先验证认证、租户归属和基本 Schema,再识别已持久化的历史幂等事实,最后才对新执行检查当前控制状态、不可变 agent 版本和资源准入。重投不能因当前配置/任务状态变化而改写原决定;权限检查不能因命中历史而绕过。外部命令不能注入 SIP 地址、主叫、密钥或任意 AI URL。
3. inbox 与可恢复待执行状态在同一事务持久化后,才 ACK broker;无效消息按既有拒绝/死信契约可靠处理,不能先 ACK 后记原因。临时额度不足进入受限持久等待,不丢弃合法任务。
4. 传输消息 ID 与业务标识的映射遵守契约;命令、执行、事件的唯一键分别为 `(tenant_id, command_id)`、`(tenant_id, execution_id)`、`(tenant_id, event_id)`,禁止只按 execution ID 跨租户去重。同键异载荷按契约冲突处理;换 command ID 不能绕过同一执行去重。
5. 业务 outbox 与产生它的状态变更同事务提交;MQ confirm 后只标记运输层发布成功。confirm 丢失允许以原 `event_id` 重发,不创造第二个业务结果。
6. broker 持久化、publisher confirm、mandatory/不可路由处理、手动 ACK 和重试退避均必须真实验证。事件confirm不是SaaS业务应用收讫,不自行增加application receipt协议。全MQ控制/查询/配置/上传仍需明确业务响应,精确合同须冻结;重复/迟到/超时沿原关联恢复,不回退HTTP或换D重拨。
启动须核验 broker 的 exchange 类型、精确绑定、持久化、ACL、队列上限和拒绝发布策略;配置漂移即不 ready。处理 mandatory return、confirm 丢失、blocked 和全局水位;满队列拒绝新发布,不丢队头,SaaS 保留原任务。死信/重试恢复回原租户配额,租户停用和清理先完成未决任务/消息/资产对账。现成 AMQP 库不等于已实现这些业务保证;连接恢复封装也需过 S04/S05/S20–S22。
MQ 的 exactly-once 拨号不是可承诺特性。采取持久幂等、授权屏障和**不确定就不重拨**的策略,并把未知执行放入可对账状态。
### 6.2 多租户分期与资源预留
**P1现在做骨架和安全,不开放多租户运营:**
- tenant_key原值贯穿租户独立队列、持久记录、事件与受控诊断;保留tenant_id复合幂等键,不用default tenant或共享总FIFO。
- 调度按可信的活跃租户集合组织,当前启用列表只含一个租户;其它租户不能因发来消息就自动启用。每租户/全局prefetch、未ACK、持久待执行及内存窗口有界,额度不足时不绕过限制。
- 一次预留原子校验当前租户并发/CPS、供应商并发/CPS、单 Cell/媒体端口、出口及本模式AI额度:ASR-only只需ASR;完整模式必须同时具备ASR/LLM/TTS,不以缺少LLM/TTS阻断合法ASR-only,也不静默降级完整模式。
- 单 Cell 额度在单活Dispatcher SQLite短事务中校验/更新,唯一约束/CAS防竞争;并发覆盖预留/拨号/振铃/接通/未知,未知是否发起不错误退还CPS或释放并发占用。
- 所有权代次/fencing、时效和最后发起许可在本轮实现并故障注入;Cell每次originate前必须核验,pause/stop/维护屏障覆盖已发许可。
- 本轮只验证一个可信租户;不开发双租户公平、第二 Cell 汇总或多 Dispatcher 协调。未来阶段再增加多活跃租户轮询、背压、轮转恢复和跨 Cell 汇总配额。
P2/后续多租户能力不作为本轮退出门禁。未来若启用获批FALLBACK,仍计原执行attempt/CPS,不能获得免费额度。
### 6.3 执行、结果与不确定窗口
```text
Dispatcher事务:控制/归属校验 → 预留资源 → 持久执行意图
→ Unary Execute → Agent保存原执行/通道关联文件并返回accepted
→ Unary最后许可(含 Asia/Shanghai 09:00–20:00 时间门禁) → ARI originate → 跟踪实际通话事实
→ Agent文件持久待回报 → Unary Report → Dispatcher事务状态/outbox
→ SaaS MQ事件 + 全局对账/资源释放
```
SIP 外呼时间窗口为 Asia/Shanghai 每日 `[09:00, 20:00)`;窗口外在最后许可和 Agent 执行入口均 fail-closed,不等待、自动延迟、重试或换线。上图是内部处理顺序,不新增对外状态枚举。对外状态必须从批准契约生成。
- 中央幂等/固定归属、Agent恢复文件和Asterisk关联共同防重;任何SQLite/文件提交都无法与外部originate做原子事务,Agent无SQLite不等于可跳过对账。
- 在 originate 超时、进程退出或网络中断时,利用稳定执行/通道标识、ARI 状态和账本对账;无法证明未拨或已结束则保持未知占用,不自动另起一通。
- Caller/callee 由 Cell 受信配置映射;切换线路必须从原始被叫重新构造,禁止串用上一线路前缀。
- 用户业务重试仍由SaaS决定。P1静态策略不启用自动跨供应商FALLBACK;新执行的选路从原始号码开始,失败如实返回。若输入要求未支持策略,按批准的能力/拒绝语义处理,不能静默改写;未来扩展仍不得重拨已接通或未知执行。
- 控制、记录、录音、结果分别持久化重试;OSS 或 MQ 故障不能导致已完成通话再次执行。
### 6.4 控制屏障
沿用主契约的租户作用域 `command_id` 幂等、`expected_task_revision` CAS;区分 `requested_task_revision` 与 `applied_task_revision`,同 command 重试不能再次递增版本。
- 控制请求经目标D专用Topic接收,D持久后经MQ返回accepted;相关Cell、在途许可和要求的挂断均确认后,才能经MQ报applied。失联/结果未知继续 applying/reconciling 并告警,不伪造成功。
- `pause` 停止新发起,已拨出(包括拨号/振铃)和已接通电话继续原生命周期;收敛的是尚可发起的在途许可,不以暂停为由取消已拨出通道。`stop` 必须显式指定 `active_call_policy=drain|hangup`:drain 让已有电话自然结束;hangup 还需 `outbound.hangup` 权限并等待挂断确认。
- 撤销号码先由 SaaS 禁发并对相关旧任务建立整体 pause 屏障,不能用临时删 broker 消息代替。SaaS 过滤撤销对象、对账旧执行后,paused 任务可以按当前已生效 revision 恢复;剩余对象用新 command/execution 授权,旧命令重投不能复活。
- 只有 stopped 任务不可 resume。是否改用新任务由 SaaS 决定,Agent 不能强制把 pause 改成不可恢复的 stop。
### 6.5 整体补传
SaaS经目标D专用Topic,仅按一个 `call_id` 或 `source_command_id` 发起整体结果补传,不增加 task/execution 范围或事件/turn/资产筛选。受理时固定截止点,使用原 event ID/内容/版本,分批、限速并优先实时结果;source command 尚无 call 也应能恢复其结果。区分不存在与保留过期(精确MQ错误码待冻结),响应同样经MQ;不得重发call.execute作为补传,补传自身command.result不得循环纳入;completed 只表示该固定集合发布完成,不代表 SaaS 已应用。
### 6.6 最终文字、事件与 DNC
Dispatcher对Agent稳定事实去重后,按 command/call/transcript_segment/recording 的实体与状态域事务持久化聚合版本,payload 是该域的快照;测试中的独立 SaaS 按同域合并。不能用全局最大版本丢掉低版本但独立的录音/attempt,也不能用 call.finished 覆盖 recording.uploaded。最终文字不会被迟到中间稿覆盖;当前基线同段 final 同内容幂等、异内容冲突,未来允许修订须经 G0 更新契约。文字失败必须出 transcript.failed,通话结束不等待所有后处理。
客户实际说话与 AI 生成/发送/播放证据严格区分,旧轮次片段取消后不能冒充已播放。contact.opt_out 按获批业务判定及时发布,不等挂断;SaaS 持久禁发并枚举相关任务完成屏障。Agent 不能用未经确认的关键词替代业务判定。
## 7. 媒体、录音与 AI
### 7.1 ARI/RTP
- Asterisk 继续实现 SIP、注册和编解码协商;Go 使用现成 ARI SDK 管理通话/桥/ExternalMedia/录音/事件,不手写 ARI 客户端。SDK 缺口优先用已有 API 组合或修复上游,不另起 SIP 栈。
- 固定通话到 Cell/出口,验证受信路由和配置版本。白名单原始号码仅为 `15003164745`、`15830461047`;写入配置不代表授权发起测试。
- 已知供应商 PCMA 作为待真机复验的基线,不把一个供应商的前缀/主叫或媒体源带到其它线路。
- RTP/RTCP 编解包使用 Pion 等现成库;在 SDK 边界校验长度、扩展、padding、SSRC、序号/时间戳回绕、源地址及负载类型。抖动/乱序/节拍也先评估现成组件,不复制协议解析器。
- 转码/重采样优先使用 Asterisk 现有能力;确需 Go 侧 G.711 时评估 `zaf/g711`,不手写 A-law/μ-law 算法或线性插值重采样。明确 PCM 端序、采样率、声道、帧时长和缓冲策略,不能硬编码“所有链路都是 20ms”。
- 每 Cell 独立分配媒体端口;端口池、FD、包率与带宽均计入容量,不能用现有 `10000–10800` 范围宣称保证 1000 路。
- early media 与接通分开统计;按最终计划从接通开始记录双向音频,不能把 183 媒体到达当成 answered。
- 固化既有问题的等价回归:ExternalMedia 就绪/本地端口、桥成员未齐、RTP 源/SSRC、双向标识音、末帧与录音封口。沿用 MixMonitor 时停止并确认封口后才读文件;替代实现须证明相同行为,不接受空 WAV 或合成音冒充录音。
### 7.2 AI 流与打断
本次必须支持两种模式,均由批准的不可变 `agent_version_id` 配置选择,任务不携带供应商密钥或临时模式字段:
| 模式(概念名,非新协议枚举) | 执行与准入 | 验收边界 |
| --- | --- | --- |
| ASR-only | 采集来电音频、识别、实时文字、录音及结果;不建立LLM/TTS会话、不预留其额度、不生成虚假播放事实 | 即使LLM/TTS未配置或不可用也可独立运行;仍受ASR/媒体/控制/录音约束 |
| ASR + LLM + TTS | 完整对话、生成与播放、取消/打断;三类AI配额同时满足 | 使用新批准SDK/模型,真实闭环;依赖不足拒新任务,不静默退成ASR-only |
当前AI Schema强制要求llm/prompt/tts/asr/conversation,没有明确的ASR-only模式/禁用规则。GAP-08须由上游唯一源补齐模式选择、缺省与模式相关的字段/超时/事件语义,包含ASR-only无agent播放片段时的合法表示;禁止伪造LLM/TTS配置或放宽源Schema。本轮用户已确认百炼/火山两种ASR、OpenAI兼容LLM、火山TTS;模型/voice/API协议仍按SaaS配置和能力PoC冻结。每个适配只选必要的一套SDK,不建插件、自动AI切换或多供应商编排。
- ASR 先用真实匹配的现成 SDK:DashScope 的 Paraformer/FunASR 与阿里 NLS 不能混同,火山 SAUC 与经典接口须分别核对。现有 Go ASR 的协议行为/测试是兼容性基线,不因已有手写代码就跳过 SDK 调研。选库、访问控制、生命周期与重连验收归本项目。
- LLM/TTS新规范、供应商、模型、音频、取消/打断、限额及费用核验属于P0/P1必需门禁。使用官方/适用开源SDK薄适配,禁止自写已有客户端或复用旧实现;此前仅Mock/未启用且P1保持blocked,不能将完整模式移到未来。SDK重试不得重复收费生成、播放或拨号。
- 通话 context 管全生命周期,每轮回答使用子 context 和代次;用户打断时取消生成并清理待播音频,过期分片不能继续播放。
- 区分生成、排队、发送和实际播放,不能把 TTS 已产生字节当作用户已听到。
- 媒体/识别/生成队列按音频时长和字节数限额,溢出策略显式验收;不得无限积压或静默丢弃关键控制。
- 持续采集 ASR 首包/最终结果、LLM 首 token、TTS 首音频、端到端响应和打断清空延迟;换 Go 不会消除供应商网络与推理延迟。
### 7.2.1 AI配置来源与调参边界(本次必需)
- 配置流固定为:MQ任务引用agent_version_id → **D经MQ请求SaaS不可变配置/授权,原D专用Topic收响应** → 校验租户/关联/源Schema/摘要/能力并持久绑定 → R07执行快照/获批缓存引用 → Agent会话局部SDK请求与控制器。旧AI GET已废弃;Agent不查询SaaS,不从CLI/env另填参数。
- 初选组件为百炼devinyf/dashscopego、火山ASR/TTS共用GizClaw/doubao-speech-go、LLM官方openai-go;均需实际协议与参数PoC,尤其火山TTS不能忽略语速/音量等字段。基础库及未完成的锁版门禁见 [组件清单§1.3/§4.3](../dependencies/开源组件选型与复用清单_v0.2.md)。
- 模型、提示词/变量、语言/中间稿、音色/语速、音频格式、temperature/Token上限、超时、开场白、打断、静音/轮数/时长、分句/缓存等已有参数必须从SaaS贯穿到执行;热词/VAD/top_p/音量/阶段超时等实际所需扩展先由上游补GAP-09。精确字段及缺省/单位/能力规则仅在源契约维护,需求索引见 [交互§6.1–§6.3](../contracts/通信与事件数据交互_v0.1.md)。
- 在已支持并获授权的能力范围内,SaaS发布新不可变版本,新任务显式引用即可调参,无需改代码/重建/重启;在途与原排队任务固定原快照,未知执行不因版本变化重拨。SIP静态发布不限制这条AI配置读取链路,也不需要建设R04通用热更新平台。
- 无有效授权缓存、版本冲突、未支持参数、模式/SDK能力不匹配均在准入前明确失败;不改用SDK默认值或伪造配置。显式0/false/未提供必须区分,并发两Agent不能共享可变参数。凭据/端点来自受控引用,业务参数不能突破安全硬限额或启用不安全SDK重试。
- 本轮确认配置来源/需求,不代表SaaS已有全部参数或SDK已暴露全部控制。GAP-08/09批准前不手改只读字段索引、不添加MQ临时字段/metadata后门;首发参数必须以SDK请求/本地控制器实测证明有效。
### 7.3 录音交接
**OSS配置存于D配置文件;A经R12向D领取临时上传TOKEN后直传OSS,不保存长期凭据。** D依据自身配置复用官方SDK提供原执行/对象所需TOKEN、目标、headers与期限;配置缺失/无效明确失败,不向SaaS获取配置/TOKEN,不用A本地bucket/长期AK兜底。TOKEN过期只接受A显式向D重新申请,配置格式及TOKEN/UploadGrant映射须核验,不猜字段,样例/日志不写真实凭据。
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% 且依赖恢复后才恢复;为活动通话预留剩余录音空间。原上传事实已持久保存、recording.uploaded已可靠入队且无已知恢复任务的本地已交接录音,测试至少保留 24h;未确认/失败文件不自动删。生产保留另行批准,不能在 confirm 后无条件删原始资产。
授权缺失、失效或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范围。
## 8. 静态配置发布与授权
P1不实现在线动态发布平台。管理平台仍是唯一编辑/审批面,输出带来源/版本/哈希、目标Cell/trunk和credential_ref的不可变快照;由项目自有受控部署入口交付。GAP-03先批准最小静态交接合同,不另建CRUD后台、不手写第二套Schema、不依赖管理源码运行。Dispatcher记录期望版本/准入,Agent验证并报告Asterisk实际加载;旧直写通道不得并行。
- 维护流程:关闭受影响资源新准入→收敛最后许可与预留→排空/对账拨号、振铃、已接通及未知占用→备份/原子替换批准制品→加载或受控重启→核验实际版本/哈希/线路能力→恢复准入。初装同样必须先验证才ready。
- 固定两个Cell及供应商授权矩阵;同版本同哈希幂等、异哈希/旧版本冲突按合同拒绝。失败保持not-ready/待对账;人工恢复旧制品也须完成加载确认,不靠切active指针假成功。
- 部分节点加载成功时只允许已满足完整准入条件的资源接新执行;存在共享冲突则一并阻塞。不能以“静态配置”为由跳过租户/供应商总额度或旧许可屏障。
- 只接受获批快照和凭据引用,不执行任意脚本/路径,不从MQ覆写SIP地址、主叫或供应商凭据;不逐呼reload,不虚构备用地址。
- 运行态验证Endpoint/boot/授权代次及采样新鲜度;实时结果保留实际attempt、线路/Cell/出口历史事实。管理聚合统计与在线发布回滚属于后续集成,不影响事实留存。
未来需要在线发布时,再冻结完整目标集合/发布意图/CAS/回执及R04/R06,验收新增/移除节点、在线停用和回滚编排。P1不为这些功能建立空框架;安全排空与人工恢复纳入本次交付,当前尚未实现。
## 9. 运行、监控与安全
### 9.1 生命周期
SIGTERM 先撤销 ready/停止新预留和新拨号,再有界排空、提交状态并关闭连接。长通话的维护排空与截止行为需配置并验收;到时仍有通话不得默默丢状态或由新实例重拨。
调度器失去租约即停止新决策;Cell 失去授权/心跳时停止接新任务,已有通话按本地策略继续并保留结果。就绪、存活与外部依赖状态分开:数据库不可用不能报告可调度,第三方缺失可以启动诊断/草稿能力,但不能宣称真实可用。
### 9.2 可观测性
- 使用 `slog`;允许记录受控的 trace/command/execution 关联信息,但不输出密钥、ARI 密码、完整音频、完整对话或用户号码。
- P1指标覆盖等待/额度不足、inbox/outbox年龄、confirm延迟、未知执行、两个Cell心跳/配置、媒体/AI延迟、spool/OSS积压与恢复告警;公平份额/租户饥饿指标在P2补齐,不先建复杂监控平台。
- 不把任意 `tenant_key`、execution ID、手机号作为无限基数指标标签;租户详细诊断通过受控查询提供。
- pprof/泄漏诊断只在受控管理网暴露。先 profile,再考虑调 GC、PGO、对象池或零拷贝;不用未经测量的“高性能优化”。
### 9.3 部署边界
独立构建非 root 镜像和部署入口,初期用 Compose/受控 systemd 即可。是否采用 CGO/SQLite 驱动会影响静态链接和交叉编译,必须按实际构建验证,不能因为用了 Go 就承诺完全静态二进制。
镜像/依赖锁定版本及digest;Dispatcher才持MQ/SaaS/中央库权限,Agent才持本地ARI/必要供应商运行权限。两角色不共享工作目录或全部Secret,共用二进制不等于共用运行权限。mock/mixed/real 可识别:Mock 默认限制真实外网,real 拒绝 Mock 端点、测试凭据和未授权路由,不静默回退。
独立开发与 CI 不依赖云资源、真实电话或其它业务测试栈。外部 Mock 服务按需使用固定镜像与版本化协议,不读取父目录源码。生产仍使用多机器、多 EIP 直连;不引入单 EIP+NAT 方案。沿用阿里云北京、指定 IP、竞价与清理授权约束,方案不产生任何云消费权限。
## 10. 分阶段目标与交付顺序
本节替代旧P0–P5全量重写计划;**P1现在表示本次单节点/单 Cell/单租户本地首发,第二阶段再做真实 SaaS/MQ 联调及多节点/多租户扩展**,不是完整生产控制面。旧12–22人周估算不再用于本次排期;不承诺未经SDK/契约核验的缩减百分比或工期。
| 阶段 | 交付范围 | 退出门禁 |
| --- | --- | --- |
| P0 首发契约/PoC | 8种事件、双AI模式与SaaS配置/参数GAP-08/09、首发Unary/最后许可、单 Cell 静态交接、录音授权、共享证书、至少3家trunk的契约/协议 fixture | 契约正反例、模式/参数/权限和本地隔离 PoC 通过;不以宽松Schema、假配置或手写协议绕过 |
| P1a 独立基础与调度骨架 | Cobra/单module、SQLite/文件、MQ inbox/outbox、单租户独立队列/有界窗口、单 Cell 原子配额、Unary身份/健康 | 独立构建、契约/权限/存储/恢复和单租户隔离通过 |
| P1b 单 Cell 执行 | 1 Agent+1 Asterisk、至少3独立trunk配置、静态快照、原始号码选路、最后许可/控制/未知对账 | 协议 Mock/mixed、重复拨号防护、维护时停止新准入通过;真实供应商/ECS 延期 |
| P1c 双AI与资产闭环 | 百炼/火山ASR、OpenAI兼容LLM、火山TTS,双模式/取消/实时文字/录音;D读取契约 fixture 配置并逐执行传参 | 两ASR适配及两模式通过本地/协议 Mock,调参无需重启且参数实际生效;真实供应商联调延期 |
| P1d 本地内测 | 受限隔离负载、故障注入、短期稳定性、维护恢复、告警与运维步骤 | 当前单节点门禁通过;不要求真实 ECS、双节点、第二 Cell、双租户或1000路/N+1 |
| 第二阶段 | 真实 SaaS/MQ、真实供应商/ECS、双节点/第二 Cell、第二租户公平及生产切换 | 另行授权、另行验收,不阻塞本轮本地 P1 |
| 后续按需立项 | 动态发布/回滚、自动SIP FALLBACK、AI多供应商编排、文本OSS归档、多D协调/HA、权重借用、1000路/N+1 | 各自批准契约、资源/预算与专项验收;不阻塞已满足范围的P1/P2 |
P1a–P1d为同一首发的有序工作包,不是各自可替代完整首发的产品。可并行协调供应商/契约,但真实双模式未通过就不能宣布P1完成。P1只要求登记负载内的稳定性和性能,无须先完成Python/Go性能对照项目;规模化收益对比留待后续相同环境专项测试。
## 11. 迁移与切换原则
1. 先做离线黄金样本和非执行影子调度;影子输入来自显式复制/回放,不能竞争消费旧生产队列、预留生产额度或真正拨号。
2. P1不以旧Python Cell作首发运行依赖;若迁移验证确需临时对接,必须有批准协议并完整支持新许可/控制/幂等,否则停留在Mock,不写HTTP拨号代理凑过渡。
3. 优先使用空闲/专用 Cell 做小范围验证。只有能证明全局共享额度和控制屏障协调正确时,才允许 Python/Go 在互斥资源分区中同时运行;否则采用维护窗口完整排空后切换。
4. 切换单元包含租户调度归属、Cell/路由、共享配额及未决任务,不是改一个进程地址。任何相同资源不得由两套 Agent 同时写入/发放许可。
5. 排空旧端、处理 broker 未 ACK、保存控制版本/幂等 ID/待执行/未知执行/outbox/录音进度,再切换所有权。数据库迁移显式、可审计,不长期双写同一业务库。
6. Go 接手新执行后,不能让 Python 忘记这些执行直接恢复消费。回滚先停止新准入、排空并对账;状态无法兼容回迁就暂停并前向修复,不能靠退镜像冒险重拨。
详细演练、量化profile及新增架构门禁见 [验证与切换验收](../acceptance/验证与切换验收_v0.3.md)。
## 12. G0 待冻结清单
| 项目 | 本方案建议 | 未确认时的边界 |
| --- | --- | --- |
| 持久化/恢复细则 | 已确定Dispatcher SQLite、Agent文件;冻结驱动/Sync/checkpoint/备份、保留和RPO/RTO | 不引PG/Agent DB/NFS共享,不拿普通文件复制冒充一致性备份 |
| 调度可用性 | 首版唯一活动Dispatcher,登记维护/故障恢复窗口与fencing | 不宣称跨机自动热备、active-active或零切换延迟 |
| 内部Unary合同 | P1冻结R01–R03、R05、R07–R13必需职责;方法可经批准合并,R04/R06在线改配后续冻结 | 最后许可/查询/事实/上传不裁掉,不手写另一套Schema或新增SaaS入口 |
| 启动/安全/版本 | 共享Agent证书、独立D身份;细化自动节点会话/轮换/重放、监听与兼容矩阵 | 不把共享证书当节点身份,不无凭据下发SIP/OSS配置 |
| 上游缺口 | P1关闭GAP-01、GAP-02录音、GAP-03静态交接、GAP-08双模式、GAP-09 SaaS任务AI配置/有效参数;文本归档/在线发布后续 | 按唯一源修复;明确阻塞阶段,不要求未来接口先落地 |
| 健康准入 | 单 Cell 样本口径/新鲜度、基本资源保护、固定候选选择、供应商和已加载版本 | 不建复杂评分,不用低CPU越额度或新boot清旧未知通话 |
| 运行参数 | 登记P1单租户单 Cell 受限profile及供应商授权 fixture;继承安全/恢复阈值,第二阶段再验公平与SCALE | 拓扑覆盖需版本化批准,不把测试基线当生产SLA或消费授权 |
| 开源依赖 | 锁定实际可用 module/tag/commit、许可证、漏洞处置及兼容 PoC | 无现成库匹配时先报告阻塞,不私自手写协议替代 |
| AI/音频能力 | 已选百炼/火山ASR、OpenAI兼容LLM、火山TTS;D从SaaS读取任务版本,冻结参数/默认/能力/实际SDK映射 | 未核验仍未启用/阻塞P1;无参数的SDK不能靠硬编码或静默忽略凑过 |
| 发布和停用 | P1静态制品版本/哈希/目标、维护排空和加载确认,批准唯一写入路径 | 无在线编排不等于无控制屏障,未知阻塞、不假报applied |
| 成本与保留 | OSS/录音/日志/审计/RPO/RTO、供应商额度 | 不承诺无限保留和自动灾难接管 |
| 真实验收 | 供应商能力、白名单、出口、时间窗口、费用授权 | 不自动拨号、开云主机或扩大网络放行 |
## 13. 参考与证据边界
- 已核验本地工具链:`go1.27.1 linux/amd64`;本地 API:`go doc uuid`、`go doc encoding/json/v2`、`go doc testing/synctest.Sleep`、`go doc net/http/httptest.NewTestServer`、`go doc sync.WaitGroup.Go`、`go doc runtime/pprof`。
- [Go 1.27 官方发布说明](https://go.dev/doc/go1.27)。Go 特性的可用性不等于已验证其在本项目中的收益。
- 上游实施依据:《最终开发部署监控与验收计划_v1.0.md》《SaaS对接与任务调度专项验收计划_v1.0.md》《SaaS交互_OpenAPI与MQ契约规划_v0.1.md》及对应机器可读契约。这里只记录来源名称,独立项目运行不回读这些父目录文件。
- 本文没有新的压测数据、生产容量证明或供应商验收结论;本轮文档检查不能替代实现验收。