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,343 @@
# Dispatcher / Agent 通信与事件数据交互 v0.1
## 1. 范围、权威与状态
本文件保留现有外部命令/事件与内部职责目录,并明确本轮适用范围:**P1为1 Agent/1 Asterisk/单 Cell、至少3家SIP trunk 的契约与协议 fixture、单租户、静态配置、ASR-only与完整AI双模式;真实 SaaS/MQ 联调、双节点、第二 Cell、第二租户和生产切换延期第二阶段。** 全量目录不等于本轮全部实现;当前只交付设计,运行通过记录另见验收证据。
- **本轮用户已确认SaaS↔Dispatcher全部交互只经RabbitMQ专用Topic,禁止双方HTTP。每个D有全局唯一ID及独立接收Topic/队列,不能共享队列抢收指定D的消息。** 精确身份/拓扑/消息/关联待新版合同冻结,见[MQ-only契约](saas-dispatcher.md)与[计划§1.2/§8.2](../plan-0918.md)。P1仍单活D;D1/D2仅用于本地路由隔离fixture,不开发多D协调。
- 旧外部业务字段以《SaaS交互_OpenAPI与MQ契约规划_v0.1.md》正文v1.0及固定包记录为语义来源;其中HTTP传输和旧租户路由已被MQ-only修订替代。旧OpenAPI/哈希只作对照,不手改源包或只读索引,不把中文MQ语义当已发布字段。
- [OpenAPI与MQ字段索引](../references/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;本项目只保证recording.uploaded可靠入队,不等待SaaS会话、verified或OSS ID。文本继续实时MQ回传,OSS只作归档。
- 准确的文字事件名是 **`transcript.updated`**;`call.transcript` 是之前讨论中的泛称,不是合法event_type,不新增该别名。
- 首发AI范围已确认:**百炼/火山ASR、OpenAI兼容LLM、火山TTS**。业务控制参数由Dispatcher按任务版本向SaaS获取,Agent按执行快照使用;不得从源码常量、本地业务配置或SDK默认值形成第二配置源。具体模型/协议/额度仍须批准和PoC。
### 1.1 已对齐部分与仍待补齐的约束
此前逐字段及SHA-256复核确认,**旧MQ信封与对应版本正文对齐**;这不代表新增D身份/专用Topic及全MQ请求响应已纳入该版Schema。以下为旧外部源的对齐/缺口记录,项目内旧补齐成果以固定包为准;本轮新差异另见GAP-10。
| 项 | 当前正文/Schema一致内容 | 仍须验证 |
| --- | --- | --- |
| 命令类别/ID | command_type / command_id | 拒绝旧type/message_id作为替代,保护作用域幂等 |
| 事件类别 | event_type,枚举覆盖8种事件 | 不接受call.transcript等不存在的别名 |
| 聚合版本 | aggregate_type / aggregate_id / aggregate_version | 按实体域合并,不能降为全局event_version |
| 时间 | issued_at / not_after、occurred_at | 区分授权、发生和接收时间,验证截止 |
| 命令payload | required已含task_revision等正文12字段 | 类型、长度、白名单及跨字段业务规则 |
| 事件payload | 目前仍主要是通用object | 8种专属payload、条件必填/状态/失败分支需在上游唯一源补齐 |
**GAP-01只指事件专属payload和未机读化业务约束的覆盖不足,不指信封字段漂移。** P0补齐并跑正反例。本轮不覆盖父项目已有文件;不能把仅通过通用object校验当完整事件验收。
### 1.2 分期与本轮传输修订
- P1保留call.execute、控制/查询/整体补传/录音协调的既有业务语义及8种业务事件;旧7条HTTP路径全部废弃为接入方式,对应请求响应改经MQ。整体补传恢复原结果,不重新投call.execute执行。
- 42操作/115组件仅为上游目录;不把管理平台30操作移入Dispatcher。按实际入口及引用闭包生成校验,来源包/只读索引仍完整留存,不通过删Schema缩小范围。
- 单租户仅指启用策略:保留tenant_key精确路由、租户独立队列/复合幂等键、有界窗口和单 Cell 全局配额;双租户公平、第二 Cell 汇总和多Dispatcher协调另立第二阶段。
- P1不新增任务MQ模式字段或临时接口。MQ-only所需的控制/查询/配置/上传请求响应必须经GAP-10正式发布,不能伪装为现有8类事件或宽松透传。ASR-only表达沿GAP-08;R04/R06在线改配延后,但最后许可、控制、静态维护屏障和持久恢复不能延后。
## 2. 角色、传输和可靠性边界
| 通道 | 发送方 → 接收方 | 内容 | 接受/交付的含义 |
| --- | --- | --- | --- |
| RabbitMQ执行/控制/查询/补传 | SaaS → MQ → 指定D专用Topic;响应经MQ回SaaS | call.execute及既有业务语义 | 校验目标/租户/原请求,持久受理和outbox后才ACK;accepted不等于applied |
| 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成功后由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推送暂缓 |
Agent不持MQ/SaaS管理凭据、不直接消费SaaS队列,不新增公开HTTP拨号/结果回调。普通Unary同样复用HTTP/2连接,不能按每通电话新建连接。
## 3. 标识和版本不可混用
| 标识/版本 | 范围与用途 |
| --- | --- |
| Dispatcher逻辑ID(精确字段待冻结) | 全局唯一、独立接收Topic;与tenant/Agent/Cell ID及dispatcher_epoch分开。身份持久化/重复拒绝及请求响应关联须新合同冻结 |
| tenant_id / tenant_key | 前者可信归属,后者原值绑定;D隔离不替代租户隔离。旧224字节预算不能直接套新拓扑,完整长度/通配符边界重新验证,超限保留任务停发,不清洗/编码/截断 |
| command_id | 租户作用域业务幂等身份;旧HTTP Idempotency-Key语义须映射到获批MQ合同,不照搬header或猜字段 |
| execution_id | 租户作用域授权执行,换command_id不得重复拨号 |
| task_id / task_item_id / task_revision | 任务、成员和控制版本;与软件/配置版本无关 |
| call_id / attempt_id | 一次逻辑通话与具体拨号尝试;只有持久化意图后才产生call。P1不启用自动FALLBACK;未来启用仍属原执行并计CPS |
| event_id / aggregate_* | SaaS MQ inbox和对应实体/状态域版本;由Dispatcher持久事务分配/递增 |
| turn_id / segment_id / revision | 文字片段与最终稿替换语义,不以消息到达时间判断新旧 |
| 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 |
| desired/applied revision、config_sha256 | 发布意图与实际加载事实;相同版本异哈希冲突,不能以文件已写代替applied |
内部关联字段最终名称/格式在Proto冻结时确定。现有外部信封/配置优先以**原版本JSON字节+schema引用/摘要**嵌入内部消息并按源Schema校验,避免在Proto再手写一份业务Schema;不得经Struct/float转换破坏大整数、空值或哈希语义。
## 4. 现有call.execute完整业务入口
旧routing key为`agent-call.tenant.{tenant_key}.call.execute`、direct exchange,**仅作旧实现对照,不用于新接入**。新版必须定向指定D专用Topic/队列并保留租户隔离,精确命名在GAP-10冻结。下表保留旧业务字段语义,不代表已包含D身份/关联。
| 外壳字段 | 语义 |
| --- | --- |
| schema_version | 已支持契约版本;不支持明确拒绝 |
| command_type | 固定call.execute |
| command_id、tenant_id、tenant_key、trace_id | 幂等、归属、路由和追踪 |
| issued_at、not_after | 签发和每次新发起截止;不能以重投延期 |
| payload | 下表;不接受任意SIP/AI URL、凭据或主叫注入 |
| payload字段 | 约束 |
| --- | --- |
| execution_id、task_id、task_item_id | 原业务归属及执行身份 |
| task_revision | 必须等于当前已生效、允许运行的控制版本 |
| callee | 原始号码,不提前拼线路前缀;服务端白名单另验 |
| route_policy_id、caller_profile_id | 服务端已配置并授权的策略/主叫引用 |
| agent_version_id | 当前租户可信、不可变AI快照 |
| variables | 白名单、类型、长度约束;非代码或任意URL |
| ring_timeout_ms、max_call_duration_ms | 不超过服务端/供应商上限 |
Dispatcher先验身份/Schema/关联,再识别历史幂等事实;新执行才检查当前时效/控制/配置/资源。固定admission_deadline,准入失败有界终结,不依赖资源释放才扫描。实际发起前再次检查所有租约/控制/截止;意图已落地但是否发出未知时reconciling,不回退成无call_id的拒绝,也不重拨。
P1从管理批准的静态route_policy/caller_profile选择供应商trunk与获授权Cell,不在通话过程中改绑或自动跨供应商重拨。每家使用原始callee应用自身规则。AI模式从该租户不可变agent_version_id读取:ASR-only只占ASR资源且不调用LLM/TTS;完整模式同时校验三类AI额度,不能静默降级。当前Schema缺少明确模式表达,批准GAP-08前不伪造空LLM/TTS配置。
## 5. 8种SaaS业务事件全集
### 5.1 通用外壳
每种事件必有:`schema_version`、`event_id`、`event_type`、`tenant_id`、`tenant_key`、`trace_id`、`occurred_at`、`aggregate_type`、`aggregate_id`、`aggregate_version`、`payload`。
旧routing key为`agent-call.{event_type}`;新版须冻结来源D及业务归属的表达。SaaS按`(tenant_id,event_id)`去重,inbox与业务更新同事务,成功后ACK。Dispatcher保存版本化快照及outbox,重发保留event_id/内容,不因RPC重报创建第二个业务事件。本文不会把示例里的可选字段擅自升级成机器required;尚缺的payload Schema见GAP-01。
| event_type | 事实来源 / 发布者 | 必需或条件业务字段 | 时点与合并 |
| --- | --- | --- | --- |
| command.result | Dispatcher自身受理/控制/执行汇总 → Dispatcher | command_id、command_type、status、reason_code;适用的task/execution/call关联;execute含等待/准入字段;control含requested/applied_task_revision | command聚合;accepted/waiting不代表已拨;MQ受理响应不代表applied;重试不重复递增revision |
| 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.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及时持久禁发并处理关联任务屏障,不等挂断;不自造关键词判定 |
中文描述但尚无精确JSON键/类型的字段(例如部分终止原因、attempt汇总、失败细分)必须在GAP-01中按上游定义补全后生成;不得由Go实现自行发明。表中源于既有样例的具体键须通过正文/机读联合验收。
### 5.2 文字与资产语义
- 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/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交互目录
旧7条业务HTTP路径及AI GET均不再作为目标接入。其业务语义由新版MQ合同承接;旧[字段索引](../references/OpenAPI与MQ字段索引_v0.1.md)只作只读对照,不手改生成物。下表中文名称不是已获批消息枚举,详见[SaaS↔D契约§5–§6](saas-dispatcher.md)。
| 业务语义 | MQ请求/响应方向 | 保留的约束 |
| --- | --- | --- |
| 任务控制 | SaaS→指定D;D→SaaS | expected_task_revision CAS,pause/resume/stop与drain/hangup;accepted不等于applied |
| 命令查询 | SaaS→指定D;D→SaaS | 原command及等待/执行事实,结果关联原查询 |
| 通话查询 | SaaS→指定D;D→SaaS | 原call/attempt及独立资产状态,不按当前配置补历史 |
| call整体补传 | SaaS→指定D;D→SaaS | 固定截止点/原事件ID和版本,不支持局部筛选,不重拨 |
| source-command整体补传 | SaaS→指定D;D→SaaS | 尚无call也可补传结果,不重发执行命令、不递归自身结果 |
| 上传完成事实 | 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状态码。
OSS配置/TOKEN来源不属于上述SaaS MQ目录:OSS配置存于D配置文件,A经Unary向D领取/显式重新申请临时TOKEN,配置缺失/无效明确失败。AI配置管理仍归SaaS,管理平台30操作不移入D;Cell静态制品唯一写入和屏障不变,内部Unary及Agent→OSS直传不受SaaS↔D禁HTTP规则影响。
### 6.1 SaaS任务配置 → Dispatcher → Agent(P1必需)
“任务配置”沿用MQ任务的 `agent_version_id` 及获批 `variables` 引用:**D经MQ向SaaS请求不可变配置及有效授权,SaaS经原D专用Topic响应**。旧AI版本GET已废弃为D接入方式,不开发HTTP client或猜测task-config路径。AI配置编辑仍归SaaS;GAP-09冻结版本/授权与受控引用语义,GAP-10冻结MQ消息/关联/期限。
1. D先验MQ可信租户/版本/幂等;历史执行走原事实恢复,不因调参重新执行。新执行在发起前取得对应租户版本,不把模型/音色/timeout等直接加入call.execute。
2. D验证源Schema、不可变内容摘要、租户授权、两种模式和SDK能力,解析该版本批准的provider_ref/credential_ref。SaaS返回的受控供应商配置提供API种类/协议版本、端点、region/资源标识等;缺合同标blocked,不在源码中按供应商名称拼端点或填示例resource_id。
3. 缓存键至少绑定租户和agent_version_id;同版本异内容拒绝并告警。按批准的撤销/新鲜度策略使用已验证缓存;SaaS不可达且无仍有效的已授权快照则暂停/拒绝新准入,遵守原admission_deadline,不用默认模型、其它租户缓存或无限期旧配置顶替。MQ请求重投/响应恢复沿原请求关联且有界,不持SQLite事务等待网络;不回退HTTP。
4. D将该执行最终有效配置、源版本/摘要及SDK能力匹配绑定到原execution。沿获批R07传送原Schema JSON/摘要或已确认缓存引用;引用缺失可经R03受控获取,**不依赖延后的R04热更新**。A二次校验并报告实际使用版本/摘要,错版本不进入最后许可。
5. Agent从会话局部只读快照生成SDK请求和本地控制器参数;可复用连接/Transport,不修改所有通话共用的model/voice/temperature等全局对象。首发有明确的百炼/火山ASR薄适配,选择一次固定到执行,不建插件、自动AI fallback或同通话动态换供应商。
6. 在已支持并获授权的模型/参数范围内,调参在SaaS发布新AI版本,由新任务显式引用后生效,无需改Go代码、重建镜像或重启D/A。排队旧任务/在途通话固定原版本,不能读取“latest”热改;授权撤销/stop仍按控制协议收敛,不以快照固定为由忽略撤销。
**静态发布仅指SIP/节点部署配置,不意味着AI业务参数写死。** 配置/授权同样必须MQ-only;实际音频仍A↔供应商,SaaS/D不代理音频流。
### 6.2 参数覆盖与缺口(需求索引,不是新Schema)
现有严格对象 `additionalProperties: false` 必须保留。下表“已有”是当前源字段;“待补”是P0向上游提交的语义需求,**不是可直接发送的JSON键/已获批枚举**。每个启用参数须在批准的SDK/模型能力矩阵中有单位、范围、缺省、对应请求字段或本地控制点及测试证据。
| 控制范围 | 当前源契约已有 | 首发需补齐/确认的可调能力 |
| --- | --- | --- |
| 通用供应商/模式 | 各AI的provider_ref、credential_ref、model;agent_version_id | GAP-08两模式;GAP-09受控端点/API版本、模型能力、火山app/resource/cluster及认证类型引用的来源/授权,不把密钥作为普通配置值 |
| ASR输入与结果 | asr.language/interim/timeout_ms/input(encoding/sample_rate_hz/channels/sample_width_bytes) | 模型必填或明确缺省、实时中间稿/最终稿行为、发送帧时长/块大小及结束规则;不能沿用示例16kHz覆盖源配置 |
| ASR识别调试 | 当前未定义这些专属字段 | 所选协议支持的热词/词表引用、标点、ITN/文本规范化、语气词/顺滑控制、语种提示、VAD/端点检测/尾部静音阈值;百炼与火山分别映射,不假设同名同义 |
| LLM模型/采样 | llm.model/temperature/max_tokens/timeout_ms | top_p、stop序列、上下文轮数/Token预算;供应商确有需求和支持时补penalty/seed/推理控制,不能把任意extra JSON透传。max_tokens与实际API输出/推理Token语义须匹配 |
| 提示词与变量 | prompt.text/allowed_variables/max_bytes;MQ variables | 渲染失败/缺变量/超限明确拒绝,不执行模板代码;上下文截取策略可审计,不把正文或变量值写入普通日志 |
| TTS声音与音频 | tts.model/voice/speed/timeout_ms/format(encoding/sample_rate_hz/channels) | 所选火山协议支持的音量/增益、音调,以及确有需求的情感/风格;语速/音量单位和范围显式映射,不靠示例speaker/resource_id默认值 |
| 对话/打断 | conversation.opening/allow_interrupt/silence_timeout_ms/max_duration_ms/max_turns | 本地与服务端VAD职责、触发打断的最短语音/防抖等适用阈值;只由可信配置控制,关闭打断也必须保留stop/hangup权限 |
| 分句/缓存/时序 | conversation.sentence_max_chars/max_pending_audio_chunks;各AI.timeout_ms | 分句等待、音频缓存按时长/字节的上限、连接/首结果或Token/首音频/流空闲/总时限的作用域;SDK超时与本地看门狗不能互相覆盖 |
首发必须把已有字段和实际选定模型支持、商务调试需要的上述扩展接通;不为不存在的模型能力造兼容层。未支持参数要在发布/准入时报明确错误,不能“接受但忽略”;必要参数缺SDK支持则对应能力blocked(见组件清单§4.3)。
### 6.3 默认值、适配与安全边界
- SaaS发布不可变版本时按获批Schema/供应商能力物化默认值并留来源;JSON Schema的default只是注解,不能假设校验器会自动填。过渡中缺必需有效值则拒绝,不从Go常量、env、SDK默认或示例请求补业务值。协议固定常量及部署安全上限不属于可任意调参范围。
- 区分未提供、null、显式0/false/空列表;例如temperature=0、interim=false、allow_interrupt=false不能被Go零值/omitempty或SDK设置器吞掉。条件必填与缺省由上游定义,不能因ASR-only跳过整个配置校验。
- SDK请求字段/单位按批准映射转换,保留requested/effective的脱敏差异;越界、冲突、不支持报可定位字段原因,不静默截断/钳制。总通话时限不得超过MQ命令、授权及平台上限,组合规则G0冻结;任务不能通过调大timeout突破stop/许可/资源硬屏障。
- provider端点只能来自可信SaaS配置并命中受控host/协议/端口及出口策略,禁任意重定向/内网探测;localhost或IP直连例外须显式管理批准。credential_ref按租户/供应商/执行授权解析,由受控Secret/短期授权交付;Agent不持SaaS管理凭据,不把密钥、prompt、完整请求/对话记入日志。
- 供应商差异只通过上游批准的有类型、有限范围配置扩展表达;不得借metadata、variables、自由headers或raw_request字典绕过严格Schema。部署只提供身份/网络/硬限额/Secret,不覆写业务模型/音色/语速等值。
- 默认禁用可能重复计费/播放的AI SDK自动重试(OpenAI显式WithMaxRetries(0));未来开启须有批准的副作用/幂等语义,不因SaaS配置了retry就无限重发。已出流/已取消/结果未知不可重放旧生成。
- 调试证据仅含获授权的租户/执行关联、配置版本/摘要、SDK及协议版本、参数名/脱敏有效值和拒绝原因;不新增公共调参API。普通日志不打印prompt/变量/密钥,敏感缓存按独立持久化权限和保留策略管理。
## 7. 内部gRPC公共规则(草案)
以下为早期内部方法职责草案,不是当前Proto字段权威;W02已交付的Proto/handler事实见[Dispatcher↔Agent契约](dispatcher-agent.md)。本轮MQ异步协调仍需核验,不因已有Unary就宣称端到端完成,也不据本文新增SaaS接口。
- 采用官方grpc-go与protobuf,全部Unary;两个角色都可作为受控gRPC客户端/服务端,共用HTTP/2连接池。
- 方向认证:Agent只接受受信Dispatcher角色的管理调用;Dispatcher只接受Agent群组证书和有效节点会话。共享证书只证明群组,不证明agent_id。
- 元数据至少表达协议版本、request/trace关联、已绑定Agent/Cell/boot/会话代次、操作幂等标识、截止时间;具体字段冻结,不把敏感token放业务payload或日志。
- 外部租户身份从Dispatcher已受理执行绑定,Agent报告不能切换tenant/call/asset归属。动态地址只能从预配置Endpoint列表获得,不执行Agent自报URL,防SSRF/错误绑定。
- 请求幂等键必须包含操作和目标;同键同内容返回原结果,同键异内容冲突。RPC超时只表示结果未知,不能自动重拨;查询/恢复保持原标识。
- Execute/Control/Apply返回accepted只表本地接收/持久文件记录;真正applied/终态通过回报或查询确认。业务“不能执行”与gRPC传输错误分开。
- 重要执行/资产事实先写Agent文件,Dispatcher将事实去重、业务更新与MQ outbox同事务持久后才返回成功。回包丢失重报原fact,不新增MQ事件;该内部确认不是SaaS应用收讫。
- 状态采样可以覆盖旧快照,最终文字、opt-out、终态、资产事件不可静默丢弃。有界重试/背压,接近文件容量阈值停新准入而非丢事实。
- 事实的source_sequence/boot用于关联和诊断,不能用Agent全局最大序列丢掉其它通话或迟到资产。认证的当前会话与事实发生时的boot分开;合法历史文件经当前会话上报仍需按原执行对账,不能仅因旧boot丢弃。
- Proto留存已发布字段号,不复用删除字段;兼容范围、未知枚举/能力降级和gRPC最大消息/超时/并发在G0 profile冻结,不以4MiB库默认值代替契约。
### 7.1 错误分类与重试(内部草案)
| gRPC状态/业务情况 | 调用方动作 |
| --- | --- |
| UNAUTHENTICATED / PERMISSION_DENIED | 不重试成其它身份,更新批准凭据/会话或隔离并告警;不能据此释放未知通话 |
| INVALID_ARGUMENT | 拒绝坏字段/Schema/尺寸,不自动改写为另一任务 |
| FAILED_PRECONDITION | 未引导、版本/配置/资产契约未就绪等先修条件;文本归档缺接口不能fallback录音 |
| ABORTED / ALREADY_EXISTS | CAS/归属冲突或同幂等键异内容,查询原决定,不换ID绕过 |
| RESOURCE_EXHAUSTED | 按受控退避/原截止反压;先确认是否已accepted,不重复占额度 |
| UNAVAILABLE / DEADLINE_EXCEEDED | 结果可能已发生;查询/对账,仅对获批幂等操作重试 |
| CANCELLED | 取消本次RPC等待不等于停止已获授权的通话;业务停止走明确控制命令 |
| NOT_FOUND | 查询目标未找到,不足以证明从未拨过/未上传;结合中央事实和持久文件判断 |
传输错误码不直接映射SaaS业务reason_code,后者仍由正文契约及事实决定;已接受长任务通过后续报告完成,不持有一个长时间阻塞RPC。
## 8. Unary职责目录与首发子集(内部草案)
字段组均为数据需求,不是已批准的字段编号。对已有HTTP/MQ结构用源契约引用,不重写第二套结构。
**P1保留R01–R03、R05、R07–R13的实际职责;R04/R06在线改配延后。** 方法数不作为交付指标,G0可批准合并简单职责,但不能省略许可/控制/查询/事实持久确认/录音交接,也不先生成未用服务空壳。R03用于初次绑定后的策略/版本引用;静态制品由受控部署入口提供,不依赖R04/R06才能启动。
| ID / 方法职责 | 方向 | 请求数据 | 响应与副作用 |
| --- | --- | --- | --- |
| R01 GetAgentStatus | D→A | 已配置Endpoint目标、请求关联;激活前只允许Dispatcher身份做受限探测 | 返回boot/版本/能力/采样/加载快照;不会发起电话、不回凭据 |
| R02 ActivateAgent | D→A | D确定的agent/cell绑定、boot、会话代次、受限会话凭据/期限、协议选择 | A校验目标/本地既有归属,保存会话;D持久绑定后才可取敏感配置;冲突或旧boot隔离 |
| R03 GetBootstrap | A→D | 群组mTLS+激活后节点会话、已知运行配置版本 | 未激活只返回pending/非敏感兼容信息;已激活返回本Agent运行策略、SIP/AI期望版本索引、OSS策略引用,不返回全局凭据库 |
| R04 ApplyRuntimeConfig(后续) | D→A | 不可变运行配置版本/摘要/前置版本、适用Agent/boot/会话;受限凭据引用或密封交付 | 校验、原子应用、报告结果;配置不合法/依赖不可用则not-ready,不覆盖最后已确认可用版本 |
| R05 SetAdmissionState | D→A | 受影响trunk/资源范围、发布/维护屏障标识、关闭/开放条件、目标代次 | 关闭新准入并报告许可/预留/拨号/振铃/已接通/未知占用;开放需D确认,不是SaaS任务控制事件 |
| R06 ApplyTrunkConfig(后续) | D→A | Publication原结构:mode/cell_id/trunk_id/revision/expected_local_revision/config/config_sha256,加已冻结内部屏障关联 | SDK/生成器校验和实际加载;沿用Acknowledgement/State语义,未知不假applied;缺屏障拒绝破坏性reload |
| R07 Execute | D→A | 正文call.execute原版本JSON、已持久call/attempt/通道关联、唯一归属/资源许可、路由及配置版本引用 | 接受/拒绝;不等待整通话;同execution不重拨,ARI响应丢失转对账 |
| R08 GetExecutionPermit | A→D | 原执行/attempt、配置/控制/会话版本及许可关联 | D核验控制/时效/完整配额后给有界最后许可或拒绝;所有发出许可纳入pause/发布屏障,不能重复退款/发额度 |
| 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业务会话;原请求重放返回原授权及原期限,新显式请求才可重新签发。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”或“静态配置”都不等于无需业务屏障。
### 8.1 内部事实与外部事件映射(草案)
下表列全R11/R13需要承载的事实种类;名称只是草案标签,不新增RabbitMQ event_type。Agent报告事实,Dispatcher裁决全局状态/版本。
| 类别 | 来源/入口 | 必须关联的数据 | Dispatcher输出 |
| --- | --- | --- | --- |
| 执行接收/拒绝 | R07结果及R11 | 原命令/执行/意图/Agent/boot、是否已持久接收、拒绝原因 | 更新内部投递状态;按命令状态机发command.result,意图已建立的失败不伪造无call拒绝 |
| 通话阶段观测 | ARI→R11 | call/attempt/通道、线路/Cell/出口快照、观测阶段/时间及证据 | call.status;不能仅凭本地计时报告dialing/answered |
| 通话终态 | ARI/执行器→R11 | 原执行/通话/attempt、终止来源/原因/时长、未决资产 | 对账后call.finished及对应command.result,后处理不阻塞终态 |
| 最终/中间文字与播放 | ASR/AI→R11 | turn/segment/revision/text/final/播放证据及时间范围 | transcript.updated;不把生成当已播放 |
| 文字失败 | R11 | 通话、受影响段/原因/可恢复性 | transcript.failed |
| 拒绝再联系 | 获批判定→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、对象位置和通知事实 | recording.uploaded/failed;成功以可靠MQ入队为准,文本归档扩展受GAP-02限制 |
健康采样走R01,不把每次心跳作为持久业务MQ事件。节点移除要先保留受控只收尾状态直到原执行/资产对账完成;强制移除需显式人工恢复路径,不能一删Endpoint就丢弃待交付事实。
## 9. Agent状态数据字典(内部草案)
通过R01周期查询;样本时刻、接收时刻、采样窗口及缺失原因都记录。指标缺失是unknown,不填0。gRPC health SERVING只表示服务能响应,不能替代可拨号判断。下表为能力目录:P1只冻结身份/boot、协议、准入所需CPU/内存/FD/媒体/spool资源、静态applied版本、线路与本模式AI健康;完整IO/负载历史、staged发布态及大清单分页按需后续建设,不能让非准入遥测缺失阻塞整个首发。
| 数据组 | 字段需求/语义 | 调度用途 |
| --- | --- | --- |
| 身份与会话 | 预配置agent_id/cell_id、boot_id、会话代次、启动时间、最后采样序列 | 拒绝错节点/旧boot/乱序覆盖;发现重复实例时隔离新准入 |
| 软件 | 二进制版本/构建提交、gRPC协议/功能能力、配置Schema/生成器版本、Asterisk版本及镜像标识(可核验时) | 不兼容禁止调度;同项目更新不要求两角色同时瞬间升级 |
| 整机 | OS/架构、CPU核数/使用率、load1/5/15、内存可用/进程RSS、FD使用/上限、磁盘与spool容量/IO | 拒绝过载而非越配;区分宿主机、容器/cgroup和进程口径 |
| 媒体资源 | 媒体端口总量/已分配/可用、已知通话阶段数、RTP丢包/抖动/包率、ARI连接与事件滞后 | 资源不足/证据过期则跳过,不能把CPU空闲当可无限拨号 |
| 配置 | 每provider/trunk的desired、staged、applied版本/摘要、加载确认时间、状态/错误/发布屏障 | 匹配本次路由的精确已加载版本;pending/failed/漂移不接新任务 |
| SIP线路 | 受配的provider_id/trunk_id、transport/codec能力、注册是否适用及状态、受控健康观测、出口/白名单核验状态 | “支持协议”与“已配置且获授权供应商”分开;不把不需注册线路当注册失败 |
| 凭据 | 仅引用/版本/可用性/到期信息,不返回密钥正文 | 过期/不可解析阻止新准入并告警 |
| AI与资产 | 可信agent_version缓存可用性、ASR/LLM/TTS依赖健康;待上传条数/字节/最老年龄、最近失败/重试 | 按模式必需资源与spool门禁;未启用能力明确标注,不把Mock变real |
| 就绪 | registering/bootstrapping/ready/draining/degraded/offline/quarantined等内部状态及原因 | 最终是否调度由D以权威配额、最后许可及新鲜状态综合决定 |
使用gopsutil/标准库/ARI SDK,不自写/proc解析器;不读取不必要的用户环境、设备序列号或完整配置秘密。P1仅回传有界静态供应商清单/必要状态,不先建分页服务。新鲜度使用D接收时间,Agent壁钟仅作观测,偏差按既有profile保护;ASR-only不会因未用的LLM/TTS离线变not-ready,完整模式不可缺任何必要依赖。
## 10. 文件、事件和OSS交付
### 10.1 Agent文件最小集合
每个执行/资产有受控目录与元信息文件、文字追加文件、音频临时/封口文件、待回报事实及上传进度。字段记录原tenant/execution/call/attempt关联、内容摘要、是否封口/验证/已被D持久接收、下一步恢复动作;目录名不得直接拼任意tenant_key/外部路径。
关键元信息先同步到盘后原子替换,文件单写者、追加记录尾部可识别,不把Flush当Sync;不创建Agent SQLite、通用数据库或自造消息中间件。重启扫描恢复原事实/通知,不重跑originate,也不自动重新PUT;失败或过期上传必须显式重新申请。录音用现成Asterisk/音频能力,不手写WAV头。
### 10.2 录音时序
1. 接通开始流式记录实际双向音频;实时文字同时走R11,不等资产封口。
2. 完成/取消时正确封口;故障时保留完整段并明确不完整状态。
3. A调用R12向D领取临时上传TOKEN;D按自身OSS配置文件提供固定15分钟的受限授权/目标。配置缺失/无效明确失败,保留原文件;不向SaaS取配置/TOKEN,也不申请上传会话或资产登记。
4. A按指定目标/headers直传OSS,不持长期凭据;TOKEN失效仅显式向D重新申请,对象ID/内容绑定不变,不自动续期/重传。
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 文本归档
P1保留本地文字恢复文件和实时transcript.updated;文本OSS归档延后,是额外资产,不替代实时文字和opt-out。
需要SaaS另定义文本归档授权/complete/引用:资产类型、ID、JSONL或其它格式、编码、内容清单/哈希、segment版本、完整性/失败、保留和查询权限。**现有recording接口没有这些定义,不能用wav/recording_id伪装。** 未冻结前明确“文本OSS归档未启用”,不影响已批准的文字MQ链路;也不宣称该需求已实现。
## 11. 断连、重复与乱序处理
| 故障 | 必须执行 | 禁止行为 |
| --- | --- | --- |
| Execute超时/响应丢失 | 原ID查D事实及Asterisk/文件,必要时reconciling;SDK不能无条件重试副作用 | 换execution/attempt/Cell重新拨号 |
| ReportEvent回包丢失 | A重报同fact;D关联原event_id及版本 | 新建一个语义重复MQ事件 |
| D不可达 | A停新执行,已有获授权通话按原策略继续、文本/录音/结果落盘;恢复补报 | 因断RPC就伪造call.finished或直接释放未知占用 |
| A新boot/失联 | D保留未知占用,重新绑定前对账、核验配置/许可;不自动迁移活动通话 | 看到新boot的0通话就清旧配额 |
| 配置部分成功 | 阻塞受影响资源、保留真实installed快照、对账/回滚也要确认 | 只改desired/active指针就重新ready |
| OSS成功但complete/回报丢失 | 原资产/会话幂等恢复;防旧签名覆盖verified对象 | 新建另一份资产或重拨 |
| D恢复较旧SQLite备份 | 停新准入、恢复唯一所有权、比对A/MQ/资产事实再开放 | 恢复过期许可/遗漏幂等水位后直接运行 |
| 永久丢盘 | 报明确资产/文字失败与告警,仍对账SIP副作用 | 假称文件可恢复或用合成内容替代 |
## 12. P1静态发布与后续在线发布
P1数据流:管理平台审批不可变制品 → 核验来源/版本/哈希及单 Cell 授权 fixture → D/R05关闭受影响资源新准入、收敛许可/占用 → 维护窗口由受控部署入口原子交付/加载或重启 → R01/R10及Asterisk实际加载证据核验 → D恢复满足条件的资源准入。初装也先核验再ready;有未知占用不得跳过屏障,失败/人工恢复均重新核验,不靠旧active指针自动开放。
`Publication`和`Acknowledgement/State`见字段索引。GAP-03只先批准P1静态制品/加载事实所需适配:精确cell/trunk、revision、config_sha256、来源和唯一写入路径;不私改现有Schema、不假称在线API已支持。凭据仍用credential_ref受控解析,不能塞入MQ。
未来在线发布才增加D持久发布编排、R04/R06推送、全目标回执/动态新增移除/自动回滚。P1静态节点清单的人工变更和凭据轮换仍须维护/排空/重激活;稳定SIP配置不逐呼改写。配置变更不自动授权真实测试呼叫或额外注册探测,管理平台30个业务API不搬入Dispatcher。
## 13. 缺口与冻结责任
[G0开发准备与契约冻结方案](../architecture/G0开发准备与契约冻结提案_v0.1.md) D01–D10方向及模式/许可/恢复机制已获用户确认;下表仍跟踪尚未交付的源字段/合同和验证,不再表示已确认方向待用户审批。该文档不是第二套Schema,权威源发布并验证后才关闭相应GAP。
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约束及上传事实通知;文本归档仍缺合同 | 配置/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身份、自动节点会话、重放隔离及全组轮换/撤销演练;不能宣称节点级证书隔离 |
| GAP-06 | 首发资源保护、模式能力、单 Cell 授权/限额与维护窗口 | P1登记受限profile及基础恢复条件;缺必需能力拒绝,低负载不越额;SIP 外呼增加 Asia/Shanghai `09:00`–`20:00` 时间门禁。复杂评分/滚动升级后续 |
| GAP-07 | 单活D故障/人工恢复目标与永久资产损失 | P1核验唯一所有权、SQLite备份/恢复/对账、文件损失和RPO/RTO;跨机自动HA后续,不新增PG/NFS共享 |
| GAP-08 | 当前AI Schema强制llm/prompt/tts/asr/conversation且无明确ASR-only表达 | P0由上游批准两模式选择/缺省、条件必填、资源/超时及文字播放/失败语义并生成校验;不增临时MQ字段、不伪造LLM/TTS配置,两种真实模式都通过才可P1签收 |
| GAP-09 | SaaS任务AI配置的读取归属/授权、provider_ref解析、调试参数和有效快照尚未完全机读化 | AI配置/授权由SaaS经MQ响应原D,保留租户/不可变版本绑定,旧AI GET不再作为接入;在唯一源补§6.2实际参数、单位/默认/范围/能力、缓存撤销及摘要/Unary交接规则;锁定百炼/火山ASR、OpenAI兼容LLM、火山TTS参数映射PoC。不加临时路径或任意透传,SDK缺字段先补库/替代 |
**GAP-10(本轮新增,已确认方向、未冻结机读合同)**:全MQ请求响应、D全局唯一身份及生命周期、独立Topic/队列/绑定、租户/目标/来源/原请求关联、完整路由预算/通配符边界、错误/期限/重复/迟到/重启恢复,以及R12/R13有界异步衔接。由W01/W02发布新版Schema/拓扑/正反例并核验后解除;不得修改旧不可变包或把图中的中文名称当新枚举。GAP-10是当前P1门禁,不新增88项编号,细则纳入既有C/S/E/L子场景。
具体验收见 [验证与切换验收](../acceptance/验证与切换验收_v0.3.md) §1.1–§1.2。GAP-01/05/07/08/09及GAP-02/03/04/06的P1部分均为首发门禁;延后部分只在相应功能启用前冻结,不能记为通过。“文档齐全”不等于契约已获批。