docs: record P1 local acceptance and external gates

This commit is contained in:
2026-09-25 16:39:20 +08:00
parent cc105550b0
commit 0ea1639cbc
16 changed files with 271 additions and 69 deletions
+10 -10
View File
@@ -101,8 +101,8 @@
- 外呼号码白名单:`15003164745`、`15830461047`。
- 所有 SIP 线路仅允许在此列表范围内发起外呼;不在列表内的号码必须拒绝。该列表仅用于已授权的 Mock/明确安排的测试;不得因写入此处而自动发起真实呼叫,原始号码保持不变。
- **当前已实现且有本地边界测试的门禁**仍为 Asia/Shanghai 每日 `09:00`(含)至 `20:00`(不含);窗口外 Dispatcher/Agent 必须 fail-closed,禁止等待、自动延迟、重试或换线。mock 测试可注入时间验证边界,不能用 mock 结果宣称 real 放行。下述非生产和真实验证条款中的固定时间同样是**当前运行限制**,不得因目标变更擅自放开。
- **用户确认的后续目标**:由任务配置的可外呼时段与所选 SIP 线路的可外呼时段共同决定是否可发起,二者须同时允许,取代固定全局 `09:00`–`20:00`。任务按周一至周日分别配置多个允许时段,可选排除多个指定日期;任务和线路时段/排除日期统一按 Asia/Shanghai 判断,排除日期全天不允许外呼。精确字段、跨日/边界、版本及排队消息跨窗口的期限和回执尚无冻结合同;先按 `docs/architecture/Dispatcher有界接纳与控制通道改造计划_v0.1.md` 完成契约、D/A最后门禁与验收更新。完成之前仍按现行固定门禁拒绝窗口外外呼,任务或线路时段也不能授权真实试拨;缺失/不确定时段拒绝,不等下一窗口、自动重试或静默换线。
- **现行真实/旧路径运行限制**仍为 Asia/Shanghai 每日 `09:00`(含)至 `20:00`(不含);窗口外 Dispatcher/Agent 必须 fail-closed,禁止等待、自动延迟、重试或换线。下述非生产和真实验证条款中的固定时间仍是**当前真实运行限制**。新增的 V3 执行入口只启用隔离 Mock,mixed/real 启动即拒绝;Mock 测试不能宣称 real 放行。
- **F04 项目内 Mock 已实现、外部未签收的后续目标**:仅 Dispatcher 在持久接纳与实际发出呼出指令前判断任务×已选 SIP 线路的允许时段、白名单、命令/授权/租户额度期限及任务/AI 较小通话时限;Agent 只校验会话及 Dispatcher 签发期限,不重算外呼业务策略。任务按周一至周日分别配置多个允许时段,可排除多个指定日期;线路只配置每周允许时段,**没有线路排除日期**。均按 Asia/Shanghai 判断,跨日拆分、左闭右开,任务排除日期全天拒绝;缺失/不确定时段拒绝并持久记录,不等下一窗口、不自动重试或换线。任务和线路时段**尚不能授权真实试拨**;F08 最终结果/额度收口、F09 完整验收及真实合同/部署授权分别未完成,不能据此放开固定真实门禁。
- 主叫标识保留原值(包括 `BD`),不能按纯数字手机号清洗,也不能直接当成 Digest 认证用户名;具体 From/PAI 等字段映射仍需确认。
- 业务原始被叫号码保持不变;使用该线路时按其规则构造 `7089<被叫号码>`,避免重复添加或把该前缀带到其他供应商线路。
- 传输协议、IP/Digest 鉴权、是否注册及并发限制仍需供应商确认;当前供应商已反馈需使用 PCMA,Asterisk 配置以 `allow=alaw` 表示,仍需真实线路验证。
@@ -155,7 +155,7 @@
- **现行合同(尚未切换)**:Dispatcher按MQ任务agent_version_id,经RabbitMQ专用Topic向SaaS取得不可变AI配置/授权,响应回原Dispatcher;校验租户/源Schema/不可变摘要/能力并持久绑定后向Agent交付执行快照。过去已废弃的独立AI版本GET不复活;新获批准的方向是**任务配置只读接口内含智能体**,须另冻合同而非旧GET兼容层。Agent不直连SaaS,不从CLI/env/源码常量或SDK默认覆盖AI业务值,不新增任务MQ模式字段或调参后台。
- 已有model/prompt/voice/speed/ASR输入与识别/temperature/max_tokens/timeout及对话控制必须实际传入SDK或控制器;热词/VAD/top_p/音量/阶段时限等所需扩展先在上游补GAP-09,再生成校验。严格additionalProperties不放宽,不借metadata/raw_request透传。
- **现行 AI 合同**:SaaS新版本供新任务引用,无需改代码/重启D/A;在途/原排队任务固定快照,同版本异内容拒绝。缓存按租户+版本隔离,断SaaS无有效授权缓存拒新准入;显式0/false与未提供保真,并发通话不得共享可变SDK参数。
- **用户批准的后续只读配置方向(替代前述MQ配置目标)**:拟定 SaaS 只读路径 `/internal/v1/dispatcher/sip`、`/internal/v1/dispatcher/task/:task_id`,以及下一轮归属任务发现 `/internal/v1/dispatcher/tasks`;新增第四条拟定 `/internal/v1/dispatcher/tenant/:tenant_id/quota`(项目路径草案)在取得任务后按 tenant_id 读取分给该D的租户总份额;前两条返回获 management 批准的 SIP 全量和含智能体获授权不可变版本的任务配置;D 用 `X-DISPATCHER-id`/`X-DISPATCHER-SECRET-KEY` 按需获取,不将凭据写入源码、样例或日志。SIP 在启动/重启先取全量、核验 Agent/Asterisk 已加载再读执行队列;任务按租户+任务缓存约60秒,活跃且有待接纳执行时到期主动刷新;已接纳/运行中执行固定原快照,未接纳允许最多约一分钟配置生效延迟。不采用 ETag/304,缓存到期重新读取完整 200 响应;过期或 HTTP 不可用时拒绝新执行,**不使用过期缓存或MQ配置回退**;停/暂停控制不随配置缓存延迟;**现行** opt-out 仍以 MQ 即时事件通知,下一版仅在最终 `call.result` 中通知 SaaS,业务签收前不可冒称两者等价。任务终结后仅清配置缓存,不删执行恢复/幂等/outbox。SIP 变更须关执行准入、排空/对账、确认加载后恢复;不能保证一分钟内完成发布。旧 `call.execute` 固定引用/排队 AI 固定规则须经新合同定义重绑定与修改-准入竞态;未发布新版前不可变现行语义。
- **本轮用户授权的项目内配置目标(现行外部 v1 不变)**:除任务发现已按 `docs/thirds/v0.2.md` §2.5 切换外,以 `docs/thirds/第三方对接事件与请求消费顺序_v0.1.md` 为其余本地契约;F01/F07 Schema、正反例、来源/hash 与 Mock C 通过后可继续本地实现,不等待外部签收/连通。实际 SaaS 字段兼容性仍未验证。拟定 SaaS 只读路径 `/internal/v1/dispatcher/sip`、`/internal/v1/dispatcher/task/:task_id`,以及下一轮归属任务发现 `/internal/v1/dispatcher/tasks`;新增第四条拟定 `/internal/v1/dispatcher/tenant/:tenant_id/quota`(项目路径草案)在取得任务后按 tenant_id 读取分给该D的租户总份额;前两条返回获 management 批准的 SIP 全量和含智能体获授权不可变版本的任务配置;D 用 `X-DISPATCHER-id`/`X-DISPATCHER-SECRET-KEY` 按需获取,不将凭据写入源码、样例或日志。SIP 在启动/重启先取全量、核验 Agent/Asterisk 已加载再读执行队列;任务按租户+任务缓存约60秒,活跃且有待接纳执行时到期主动刷新;已接纳/运行中执行固定原快照,未接纳允许最多约一分钟配置生效延迟。不采用 ETag/304,缓存到期重新读取完整 200 响应;过期或 HTTP 不可用时拒绝新执行,**不使用过期缓存或MQ配置回退**;停/暂停控制不随配置缓存延迟;**现行** opt-out 仍以 MQ 即时事件通知,下一版仅在最终 `call.result` 中通知 SaaS,业务签收前不可冒称两者等价。任务终结后仅清配置缓存,不删执行恢复/幂等/outbox。SIP 变更须关执行准入、排空/对账、确认加载后恢复;不能保证一分钟内完成发布。旧 `call.execute` 固定引用/排队 AI 固定规则须经新合同定义重绑定与修改-准入竞态;未发布新版前不可变现行语义。
- 凭据/供应商端点来自受控引用且有授权/出口校验,不能因可调参数绕过安全硬限额或启用不安全重试。OpenAI默认自动重试显式关闭;日志只留脱敏版本/摘要/有效参数,不打印prompt/变量/密钥。
- 静态发布只约束SIP/节点制品,不将AI配置硬编码;GAP-08/09及SDK参数PoC为P1门禁,验证入口见验收§5.1(现有E/L项子场景,不新增虚假通过数)。
@@ -164,23 +164,23 @@
- 本项目设计/运行/验收文档只在自身 `docs/` 维护。上游共享接口有唯一权威来源;导入带版本、来源和哈希的不可变契约包,再生成类型/校验,不维护重复手写 Schema。
- 新内部消息/许可/fencing 协议需先获批;不擅自改变 SaaS 路径、字段、状态、路由或控制语义。
- **现行已发布合同(新版本生效前必须遵守)**:SaaS↔Dispatcher的全部交互唯一经RabbitMQ专用Topic订阅,双方无HTTP请求/回调/兼容通道或故障回退,包括执行、控制、查询、整体补传、AI配置/授权及recording.uploaded上传事实通知;上传不申请SaaS会话或等待verified/OSS ID回复。OSS配置/TOKEN不来自SaaS:Agent领取及显式重申请TOKEN只经D↔A Unary;本规则不禁止Agent→OSS、ARI、AI供应商HTTP(S)或gRPC的HTTP/2。
- **用户已批准的新目标,尚非现行合同或运行能力**:任务(含智能体)、SIP、任务发现和按 tenant_id 的租户额度走四条只读 HTTP;呼叫、控制及其必要回执/单份最终结果走 MQ,取消对外业务查询/补传和分散通话事件。不提供配置HTTP→MQ回退,也不恢复其它业务HTTP通道。SaaS须分发 management 已批准的唯一 SIP 版本,management 仍为唯一编辑/审批面。当前严格 MQ Schema、HTTP 路径、身份/版本/缓存契约及验收在 W01 新版本冻结并完成切换前不可擅改或声称已实现。
- 新HTTP配置字段阶段草案见 `docs/contracts/config-read-fields-v0.1-proposal.md` 及同目录 `config-read-v0.1.schema.json`/mock示例;截图只证实UI含义,英文响应键为项目自定义,绝非SaaS现网接口已确认字段。用户新增任务排除日期、线路时段等未见截图项按项目需求设计;SIP传输/鉴权/注册及额度未知不能猜默认值。草案校验不代表SaaS/management签收或W01接口就绪;真实响应、审批来源、摘要和Agent/Asterisk实际加载仍须验证。当前唯一权威运行契约不因草案变化。
- **本轮项目内目标(尚未替换真实外部运行)**:任务(含智能体)、SIP、任务发现和按 tenant_id 的租户额度走四条只读 HTTP;呼叫、控制及其必要回执/单份最终结果走 MQ,取消对外业务查询/补传和分散通话事件。不提供配置HTTP→MQ回退,也不恢复其它业务HTTP通道。SaaS须分发 management 已批准的唯一 SIP 版本,management 仍为唯一编辑/审批面。该本地语义按第三方契约及版本化 Schema/示例/hash 冻结,Mock C 是本地门禁,不等待外部签收;真实切换仍需另行授权。
- 新HTTP配置字段阶段草案见 `docs/contracts/config-read-fields-v0.1-proposal.md` 及同目录 `config-read-v0.1.schema.json`/mock示例;截图只证实UI含义,英文响应键为项目自定义,绝非SaaS现网接口已确认字段。用户新增任务排除日期、线路时段等未见截图项按项目需求设计;SIP传输/鉴权/注册及额度未知不能猜默认值。Schema/Mock 校验可满足项目内 C,但不代表 SaaS/management 已发布或真实兼容;真实响应、审批来源、摘要和 Agent/Asterisk 实际加载仍须单独验证。当前唯一权威运行契约不因草案变化。
- **每个Dispatcher必须有独立、全局唯一且不重复的ID及独立接收Topic/队列**;指定D的任务/现行MQ配置结果/上传结果不能由其它D抢收,也不能广播后仅靠正文过滤;新目标只读HTTP配置由该D UUID+SECRETKEY获取且须核对任务归属。身份与tenant/Agent/Cell ID、dispatcher_epoch分开;现行合同保留租户独立队列及原值tenant_key,完整新路由长度预算须重验。具体ID生成/持久化、Topic/绑定、消息字段/关联/错误/期限须随W01新版本冻结,不凭本文给旧严格Schema添加字段。
- **用户确认的下一轮任务队列所有权硬边界(未实施,见 `docs/plan-config-read-v0.1.md` §1/§3.4/§4):任务队列及 RabbitMQ 绑定只能由 SaaS 创建、维护、退役;Dispatcher 只消费,不能自行声明/建队、绑定或删除任务队列。** SaaS 需先确保持久任务/控制队列及绑定就绪,再向按归属 D+任务 ID 定位的任务队列发布 persistent 命令;D 离线时已存在的任务队列可积压,缺队列而未入队的原消息由 SaaS 保留并在就绪后按同一身份重发,不能称作 D 重启自动补收。新增按 D 身份授权的 `tasks` 全量清单用于 D 重启恢复,运行中拟每 30 秒 `GET /internal/v1/dispatcher/tasks?after=<cursor>` 拉取该 D 新增/更新/撤销,`cursor` 为 SaaS 变更水位而非最大任务 ID,失效则重新取一致全量快照;轮询间隔不等于端到端发现时限,也不能替代 MQ 即时控制;pause保留积压、resume重新核验后继续消费原积压,过期不延长;stop持久生效后未接纳旧消息**静默消费/ACK,不拨号、不回逐条结果**,不能直接删队列;控制本身和已有在途通话仍有处理/最终结果,本地错误与计数不静默。同一 D 下任务响应须带 tenant_id/原值 tenant_key,按 tenant_id 读取的租户额度在各任务间共用,0停新准入、缺失/过期失败拒新;降额不强挂或清未知占用,跨 D 仍须权威份额;任务数与队列资源有上限。现行代码启动时按 `--tenant-key` 自行声明租户队列,与新目标不符,F07 严格合同、F08 实现及 F09 验收前不得声称动态租户/任务队列已可用。
- **本轮项目内任务队列所有权硬边界(规则见第三方契约 §1.2 和 `mq-topology-v0.1-proposal.json`;F03 本地实现状态见本条末尾):任务队列及 RabbitMQ 绑定只能由 SaaS 创建、维护、退役;Dispatcher 只消费,不能自行声明/建队、绑定或删除任务队列;F03 本地 v0.2 运行代码已实现此边界,外部兼容性未验证。** SaaS 需先确保持久任务/控制队列及绑定就绪,再向按归属 D+任务 ID 定位的任务队列发布 persistent 命令;D 离线时已存在的任务队列可积压,缺队列而未入队的原消息由 SaaS 保留并在就绪后按同一身份重发,不能称作 D 重启自动补收。新增按 D 身份授权的 `tasks` 全量清单用于 D 重启恢复,运行中拟每 30 秒 `GET /internal/v1/dispatcher/tasks?after=<cursor>` 拉取该 D 新增/更新/撤销,`cursor` 为 SaaS 变更水位而非最大任务 ID,失效则重新取一致全量快照;轮询间隔不等于端到端发现时限,也不能替代 MQ 即时控制;pause保留积压、resume重新核验后继续消费原积压,过期不延长;stop持久生效后未接纳旧消息**静默消费/ACK,不拨号、不回逐条结果**,不能直接删队列;控制本身和已有在途通话仍有处理/最终结果,本地错误与计数不静默。同一 D 下任务响应须带 tenant_id/原值 tenant_key,按 tenant_id 读取的租户额度在各任务间共用,0停新准入、缺失/过期失败拒新;降额不强挂或清未知占用,跨 D 仍须权威份额;任务数与队列资源有上限。F03 本地实现已移除 `--tenant-key` 和自建租户队列路径,按任务发现只被动消费 SaaS 预建任务/控制队列;本地 RabbitMQ 无 `configure` 权限测试通过。当前任务发现每 D 单次完整响应最多 256 条,不分页、不在正文返回队列地址;按 D+task ID 推导队列,空变更 `next_cursor` 等于请求 `after`,410 过期重拉全量,`removed` 撤销归属。发现失败持久关新准入并停任务消费,不停 MQ 控制;完整响应、归属和游标须原子持久化。旧 v0.1 分页证据仅保留历史,v0.2 本地证据见 `docs/evidence/f03-local-task-discovery-v0.2.md`;均不证明外部 SaaS/management 兼容、多 D 业务运行或生产可用。
- **OSS相关配置存于Dispatcher配置文件,Agent向Dispatcher领取临时上传TOKEN后直传OSS,不保存长期凭据;SaaS不再下发OSS配置/TOKEN。** D复用官方SDK提供受限TOKEN/目标信息,配置缺失/无效明确失败;不在样例、源码、日志或证据中保存实际密钥/完整TOKEN。过期只允许A显式向D重新申请,不自动续期或向SaaS申请TOKEN;精确配置格式/TOKEN形态/UploadGrant映射另行核验,不猜字段。
- **用户已修订目标:上传仅负责Agent直传及D可靠通知MQ,SaaS后续处理不属本项目职责。** D保留签发能力、不转发文件;R13持久保存事实与recording.uploaded outbox,消息为persistent、进入指定durable队列/绑定、mandatory无return且publisher confirm成功后才记交付完成。只写本地outbox不算入队;不申请SaaS会话、不等待verified/OSS ID、不新增VERIFYING、不伪造SaaS结果。
- **本轮项目内目标:上传仅负责 Agent 直传及 D 可靠通知 MQ,SaaS 后续处理不属本项目职责;外部 v2 的现行上传事实不因本地目标自动改变。** D保留签发能力、不转发文件;R13持久保存事实与recording.uploaded outbox,消息为persistent、进入指定durable队列/绑定、mandatory无return且publisher confirm成功后才记交付完成。只写本地outbox不算入队;不申请SaaS会话、不等待verified/OSS ID、不新增VERIFYING、不伪造SaaS结果。
- 新版recording.uploaded取代本项目recording.ready,字段为call_id/recording_id/upload_id/bucket/object_key/format/channels/sample_rate_hz/duration_ms/size_bytes/checksum_sha256;不含TOKEN/密钥/签名URL。MQ失败/确认丢失/重启只恢复原消息身份的交付,不重新PUT或新建资产。现行AI/控制等必要请求响应不受此收缩影响;新目标AI改用任务只读HTTP获取,控制仍走MQ。
- 前一轮MQ-only文档纠正已结束;以下是**现行 MQ-only v1 合同的历史基线**,不得误当作新只读配置HTTP方案的限制或验收通过:现行机器依据见 `contracts/upstream/v1/mq-topology.json`、`mq.schema.json` 和 `event-payloads.schema.json`,第三方顺序说明见 `docs/thirds/第三方对接事件与请求消费顺序_v0.1.md`:纯Topic精确绑定、拒绝独立通配词段、tenant_key预算196 UTF-8字节、稳定UUID v4、旧版严格JSON配置及15分钟SDK预签名PUT;新目标保留业务MQ及OSS规则,仅替换配置获取,须另发版本化合同,不原地复用旧Schema。当前仅保留现有代码需要的契约包;未发生契约迁移前不新增契约,旧快照不作为当前工作树输入。新版须完成Schema/正反例/哈希验证后发布,不把方案确认当实现完成。历史 W01/W02/W04/W05/W07/W08/W11/W12/W13/W14 范围见归档 `docs/archive/plan-0918.md` §8.2;本轮新配置/控制路径须按 `docs/plan-config-read-v0.1.md` §4–§8 重新验证。全局唯一D身份/专用Topic及本地D1/D2隔离fixture为当前合同要求,不授权双D业务运行、HA或共享额度。
- **现行已发布合同**中的实时文字事件名为transcript.updated,不新增call.transcript别名;OSS文本归档不能冒充当前实时文字/opt-out,缺少专用资产授权接口时明确未启用,不能伪装recording.ready。
- **待 review/待 SaaS 与业务签收的下一轮方向**见 `docs/plan-config-read-v0.1.md` §3.5/§4:SaaS↔D 移除对外查询/补传命令及分散通话/转写/拒联/录音事件,保留呼叫/控制命令和必要命令回执;录音仍上传 OSS,D 在上传成功后只回传一份含最终转写、拒联事实与 OSS 路径的 `call.result` 草案。用户已确认不要求 SaaS 在通话结束前收到实时文字或拒联,但原验收与跨任务/跨 D 拦截能力将变化,必须单独修订并签收;未产生录音的无应答/忙线等按not_created直接最终收口;录音失败/超时unavailable的有限期限仍待冻结。通话确认终结释放执行资源后释放占用,不等待OSS或MQ结果确认;未知仍占用。现行 Schema/代码未改、此草案不得冒称已上线,且本轮只改文档供用户 review。
- **用户已批准的项目内精简契约目标**见 `docs/plan-config-read-v0.1.md` §3.5/§4 和第三方对接契约:SaaS↔D 移除对外查询/补传命令及分散通话/转写/拒联/录音事件,保留呼叫/控制命令和必要回执;录音仍上传 OSS,D 每通话只回传一份含最终转写、拒联事实与 OSS 路径的 `call.result`。本地实现以 F01/F07 Schema、正反例、来源/hash 和 Mock C 为门禁,无需额外外部/业务签收;本地验收更新后不要求通话前实时文字或拒联。无录音按 `not_created` 收口;预期录音明确失败立即 `unavailable`,未知最迟于 `call.ended_at + 15m` 收口。通话确认终结释放执行资源后释放占用,不等待 OSS 或 MQ confirm;未知仍占用。现行外部 Schema/代码保持原样,真实兼容性未验证。
- 现有MQ信封command_type/command_id、event_type/aggregate_*与正文已对齐;事件payload专属约束尚需补齐,不把通用object校验当完整验收。字段索引只读生成,不手改成第二套Schema。
- `tenant_key` 原值一对一绑定,不清洗、编码或截断;旧布局224个UTF-8字节预算不能在加入D身份后直接照搬,W01须冻结并验证完整routing key/queue预算及分隔符/通配符边界;超限停止发布并保留源任务。
- 持久 inbox 后 ACK;状态与 outbox 同事务;confirm 不等于 SaaS 应用收讫。重复投递、未知执行和恢复不能触发重复拨号。
- 配额覆盖所有 Cell/实例及未知占用;租约过期不自动释放不明通话。**现行 v2** 控制 CAS 为 expected_task_revision,pause 与 stop 的 drain/hangup 区分;paused 可按新授权恢复,stopped 不可恢复。现行整体补传仅 call_id/source_command_id;下一版对外查询/补传目标取消但严格合同/代码未改。最后发起许可、权限和屏障须故障注入。
- **用户确认的下一版精简契约目标(待 SaaS/业务签收,不能混写现行 v2)**:`call.execute.payload` 仅 `task_id/callee`,外呼信封身份仍用于防止重拨;路由/主叫/智能体版本与 `ring_timeout_ms/max_call_duration_ms` 均从已批准的任务配置取得并持久绑定。`task.control` 的 pause/resume/stop 均不带 `command_id` 或 `expected_task_revision`,不设计控制去重,但 D 必须回 task/action/status 处理结果;乱序、控制重投/回执丢失与 stop 后 resume 的判定是 F07 签收阻塞项,不偷偷重引入 CAS/去重掩盖。任务配置新增明确 `caller_profile_id`,按allowed_trunk_ids顺序选首个时段/额度/加载/主叫均匹配的线路,选后固定、不自动换线重拨;有效通话上限取任务与已授权AI两者较小值。旧running配置/发现不得解除已持久的paused/stopped,resume须强制最新任务核验,stopped同ID不可逆。拟定配置响应不再包含 `agent.content_sha256`;SIP 快照/录音 checksum 为另有用途的字段,不误删。参见 `docs/plan-config-read-v0.1.md` 和 `docs/thirds/第三方对接事件与请求消费顺序_v0.1.md`。
- **用户已批准的项目内精简契约目标(不能混写现行 v2;外部签收不阻塞本地 C)**:`call.execute.payload` 仅 `task_id/callee`,外呼信封身份仍用于防止重拨;路由/主叫/智能体版本与 `ring_timeout_ms/max_call_duration_ms` 均从已批准的任务配置取得并持久绑定。`task.control` 的 pause/resume/stop 均不带 `command_id` 或 `expected_task_revision`,不设计控制去重,但 D 必须回 task/action/status 处理结果;乱序、控制重投/回执丢失与 stop 后 resume 的本地判定写入第三方契约并由 Mock 测试;外部签收不是本地阻塞项,不偷偷重引入 CAS/去重掩盖。任务配置新增明确 `caller_profile_id`,按allowed_trunk_ids顺序选首个时段/额度/加载/主叫均匹配的线路,选后固定、不自动换线重拨;有效通话上限取任务与已授权AI两者较小值。旧running配置/发现不得解除已持久的paused/stopped,resume须强制最新任务核验,stopped同ID不可逆。拟定配置响应不再包含 `agent.content_sha256`;SIP 快照/录音 checksum 为另有用途的字段,不误删。参见 `docs/plan-config-read-v0.1.md` 和 `docs/thirds/第三方对接事件与请求消费顺序_v0.1.md`。
- management是SIP配置唯一编辑/审批面。**现行 P1**通过批准的版本化静态制品和受控部署入口交付,D核验目标/准入屏障,Agent加载并报告;不要求在线发布控制面。静态交接合同须批准,旧直接写Agent面不能同时启用;成功必须证明精确快照已被Asterisk加载。
- **用户批准的后续 SIP 配置获取目标(取代此前MQ全量+增量目标)**:management 仍是 SIP 唯一编辑/审批方,SaaS 仅经只读 HTTP 提供该 D 资源分区的获批全量快照;新加入/重启 D 先查询、核验所属 Agent/Asterisk 实际加载的版本/摘要后才消费执行队列。后续约每60秒核对版本,有变化就关执行准入、排空旧活动通话并确认新版已加载;控制MQ照常处理。SaaS/management 分属不同配置系统时须证明SaaS分发的是同一份获批制品;不引入共享业务DB或MQ配置回退。此路径及多 D 不属现行 P1 已实现能力,需新只读HTTP/管理交接合同、资源配额份额和恢复/边界验收,详见 `docs/architecture/Dispatcher有界接纳与控制通道改造计划_v0.1.md` §3.2。
- **本轮项目内 SIP 配置读取目标(现行外部运行未切换)**:management 仍是 SIP 唯一编辑/审批方,SaaS 仅经只读 HTTP 提供该 D 资源分区的获批全量快照;新加入/重启 D 先查询、核验所属 Agent/Asterisk 实际加载的版本/摘要后才消费执行队列。后续约每60秒核对版本,有变化就关执行准入、排空旧活动通话并确认新版已加载;控制MQ照常处理。SaaS/management 分属不同配置系统时须证明SaaS分发的是同一份获批制品;不引入共享业务DB或MQ配置回退。本地 SIP 读取路径按本轮第三方契约、计划和 Mock C 实施;当前运行代码尚未切换。真实 SaaS/management 兼容性及多 D 资源配额份额仍未验证、不属本轮验收,详见 `docs/architecture/Dispatcher有界接纳与控制通道改造计划_v0.1.md` §3.2。
## 安全和真实验证
+11 -11
View File
@@ -1,8 +1,8 @@
# go-sip
面向生产的Go SIP调度与执行项目:同一module/二进制通过Cobra提供 `dispatcher`、`agent` 两个业务子命令,分阶段替换Python,保留既有业务语义。现行 SaaS↔Dispatcher 合同为 MQ-only;新增两只读 HTTP 配置接口仅是本轮待冻结的目标。
Go SIP 调度与执行项目:同一 module/二进制通过 Cobra 提供 `dispatcher`、`agent` 两个业务子命令,分阶段替换 Python。现行**外部** SaaS↔Dispatcher 合同仍为 MQ-only;本轮**项目内本地 Mock** 目标为四条只读 HTTP GET、v3 MQ 及单份最终结果,其中只有任务发现改按 [`docs/thirds/v0.2.md`](docs/thirds/v0.2.md) §2.5,其他本地 SaaS 协议仍按 v0.1。项目内提案和 Mock 不代表 SaaS/management 已签收或上线。
> **当前状态:本轮单节点/单Dispatcher/单Agent/单Cell/单租户的 MQ-only 本地范围已完成。** 控制、AI配置/授权、查询/补传、上传通知及恢复均有 loopback RabbitMQ 证据;业务源码覆盖率为65.8%。真实SaaS/MQ、供应商/ECS、生产切换及第二Cell/第二租户仍属第二阶段;当前开发主机缺少 Asterisk/tcpdump,部署 preflight 已按要求 fail-closed,未冒充 mixed/real 通过。
> **当前状态:F00–F04、F08、F09 与项目内 Mock C 已完成本地适用验证;F06 的本地制品/拒绝门禁通过,真实部署和切换未执行。** P1 业务模块覆盖率均 ≥65%(MQ 须带 RabbitMQ 集成测试);CLI 和历史包未达线单独列为技术债,不宣称全仓达标。默认 Mock 不生成真实录音,上传成功/失败分支由隔离测试注入合成事实。真实 SaaS/MQ、供应商/ECS、Asterisk 媒体、生产切换及第二 Cell/第二租户仍属未验证或后续阶段;缺少 Asterisk/tcpdump 时部署 preflight 按要求 fail-closed,未冒充 mixed/real 通过。
> 本项目已独立拆仓运营;源码、配置、依赖、迁移、测试、部署和文档均在此目录内维护。远程仓库为 `git.ipao.vip/rogee/go-sip`,本地 Git 默认分支为 `main`;真实外呼仍受逐次授权、capture-first、白名单和时间门禁约束。
## 已确认的范围
@@ -12,21 +12,21 @@
- 内部 **Unary gRPC**;Dispatcher配置Agent Endpoint列表,Agent业务启动只需Dispatcher Endpoint,证书/监听/本地资源通过部署提供。
- 所有Agent共用mTLS证书、Dispatcher身份独立;需Endpoint主动激活/受限节点会话,不能把群组证书当单节点身份。
- 本次以稳定、快速单节点上线为目标:**1 个 Agent、1 套 Asterisk、1 个单活 Dispatcher、单 Cell、至少 3 家独立 SIP trunk 的契约/协议 fixture、1 个内测商务租户**;双节点、第二 Cell、双租户延期第二阶段。
- SIP 使用管理平台批准的不可变静态快照;维护窗口发布、排空和实际加载确认保留,在线动态发布/回滚编排暂缓。外呼仅允许 Asia/Shanghai 每日 `09:00`(含)至 `20:00`(不含),窗口外 Dispatcher/Agent fail-closed;按供应商授权登记单 Cell/出口 fixture。
- SIP 使用管理平台批准的不可变静态快照;维护窗口发布、排空和实际加载确认保留,在线动态发布/回滚编排暂缓。**现行真实路径**仍受 Asia/Shanghai 每日 `09:00`(含)至 `20:00`(不含)及白名单门禁约束;任务×线路时段与任务排除日期仅在本地 Mock V3 验证,不放开真实试拨。
- 有适用开源库/官方 SDK 时强制复用,不从零手写 SIP/ARI、RTP、MQ、数据库、OSS 或 AI 协议客户端;选库失败先报告阻塞,不静默转自研。
- 重写完整 Agent:调度控制面、Cell 外呼执行、ARI/RTP/录音、AI 流式适配、Cell 配置接收。
- 分阶段迁移,最终构建、测试和运行不依赖 Python、父仓库目录或其它业务项目内部代码。
- Asterisk 继续负责 SIP;独立 SIP 管理平台继续拥有配置管理权,均不纳入重写。
- **现行已发布的 SaaS↔Dispatcher 合同**:全部请求、响应和事件只走 RabbitMQ,双方无 HTTP;每个 D 有全局唯一 ID 及独立接收 Topic/队列。新目标只让获批 SIP 全量配置及任务(内含智能体)经两条只读 HTTP 接口获取,现行执行/控制/查询/结果/上传事实仍走 MQ;下一版另拟移除对外查询/补传及拆分通话事件,仅以一份含 OSS 路径的最终结果回传。两项目标均待合同签收、代码和验收切换,现行规则仍有效。P1 不扩为多 D 协调/HA。
- **外部运行基线与本地目标分开**:当前真实 SaaS↔Dispatcher 仍按已发布 MQ-only 合同工作。P1 本地目标按项目内契约采用四条只读 HTTP GET(SIP 全量、单任务含智能体、归属任务清单、按 `tenant_id` 取额度)和 v3 MQ 命令/控制/回执/唯一最终 `call.result`,移除对外 query/replay 及拆分通话事件。F01/F07 Schema、正反例、来源/hash 与 Mock C 已通过,F02–F04/F08/F09 本地验证见下文;外部兼容仍未验证,不扩为多 D 协调/HA。
- **OSS配置存于Dispatcher配置文件,Agent向Dispatcher领取临时上传TOKEN后直传OSS,不保存长期凭据。** SaaS不下发OSS配置/TOKEN;D保留SDK签发能力,不转发文件。上传不申请SaaS会话、不等待verified/OSS ID或业务处理回复;D仅在原上传事实可靠进入指定持久MQ队列后记录交付完成。实时文字仍为 `transcript.updated`,归档不能替代实时事件。
- 本次必须支持 **ASR-only** 与 **ASR + LLM + TTS** 两种模式;本阶段已完成本地/协议隔离双模式验收。百炼/火山ASR、OpenAI兼容LLM、火山TTS的真实供应商能力和生产参数联调仍标第二阶段/未启用,不能将 Mock 写成真实供应商通过;禁止复用旧LLM/TTS。
- **现行 AI 配置**按任务 agent_version_id 经 MQ 向 SaaS 取得并持久绑定;过去独立的 AI GET 已废弃。**新目标**将获授权的智能体配置放入任务只读 HTTP 响应,不复活旧 GET 或保留 MQ 配置回退。 模型、提示词、音色/语速、识别、超时/打断等参数不写死;新版本用于新任务,无需重启,在途通话固定快照。静态SIP发布不代表AI配置静态硬编码。
- 本轮验收范围收敛为单节点、单 Agent、单 Cell、单租户;保留 tenant_key 原值、独立队列、复合幂等和有界窗口。双节点、第二 Cell、双租户公平/背压/恢复不在本轮开发或验收范围,作为后续阶段。
- 上传链路仅包含R12临时授权、Agent直传、R13报告及 `recording.uploaded` 可靠入队;SaaS后续资产处理不属于本项目。真实SaaS/MQ联调仍第二阶段,不新增生产授权。
- 当前外部上传合同仍是 R12 临时授权、Agent 直传、R13 报告及 `recording.uploaded` 可靠入队。P1 本地目标改为每通话一份最终 `call.result`(含 `uploaded/unavailable/not_created`),不再发送拆分录音/实时文字/拒联事件;15 分钟收口、8 MiB 消息体上限及超限 outbox 阻塞已通过本地 Mock 验证,但未完成真实媒体、OSS 或 SaaS 应用签收。
## 当前本地实现
MQ身份隔离、严格配置文件、执行接收/回执、查询/补传、任务控制、AI配置/授权及上传通知已有 loopback RabbitMQ 本地往返;旧HTTP业务入口已删除。最终质量检查通过,部署 preflight 在缺失 Asterisk/tcpdump 时明确失败并保留日志;这不等同真实SaaS、供应商或生产验收。阶段证据见 `docs/evidence/20260922-mq-only-local-final.md`。
旧 MQ-only 基线的身份隔离、配置、回执、控制、AI授权及上传通知有 loopback RabbitMQ 本地证据,见 `docs/evidence/20260922-mq-only-local-final.md`;该证据不代签新目标。本轮项目内结果分别见 [F03 v0.2](docs/evidence/f03-local-task-discovery-v0.2.md)、[F04](docs/evidence/f04-local-dial-policy-v0.1.md)、[F08](docs/evidence/f08-local-final-result-v0.1.md)、[F09](docs/evidence/f09-local-acceptance-v0.2.md) 和 [F06 本地发布阻断](docs/evidence/f06-local-release-gates-v0.2.md)。部署 preflight 在缺失 Asterisk/tcpdump 时明确失败并保留日志;本地测试不等同真实 SaaS、供应商或生产验收。
- `contracts/upstream/` 嵌入项目内自包含 W01 基线,记录父源 commit、dirty 继承和 SHA-256;运行时代码读取该包,不读取父目录。
- `internal/store/` 提供 SQLite inbox、任务、租户/跨 Cell 配额、控制 CAS、replay 和 outbox 事务。
@@ -35,10 +35,10 @@ MQ身份隔离、严格配置文件、执行接收/回执、查询/补传、任
- 旧 `internal/control/` HTTP业务实现、启动入口、CLI参数及环境配置已删除,不保留兼容通道。
- `internal/rpc/` 提供 `agent.v1` Unary handlers、mTLS TLS1.3 配置、会话世代/fencing、CAS、permit/fact/upload metadata 边界;Agent CLI 可选启动受证书保护的 gRPC listener,并校验静态 Cell 制品与 AI 授权边界。`internal/calllog/` 提供按手机号 HMAC 关联、掩码和 allow-list JSONL 外呼业务日志,不写原始号码、凭据、音频、转写或 prompt。
- `internal/health/` 使用 gopsutil 采样主机/进程资源;媒体端口和 AI 配额未接入时明确报告 unknown。`internal/mq/` 默认 bounded prefetch=1、per-tenant DLQ 和 publisher confirm;`make mq-integration-local` 只启动 disposable RabbitMQ。
- `make acceptance-local` 会校验固定契约、跑 race/vet/build、执行 mock Agent/Dispatcher smoke,并确认 real mode 无凭据时拒绝;Alibaba OSS 实际授权测试由 `AGENT_CALL_OSS_INTEGRATION=1` 门控,使用受控环境变量,不把凭据写入仓库;`make release` 生成带 dirty-source/哈希的 local-development manifest;证据见 [`docs/evidence/20260918-local-development.json`](docs/evidence/20260918-local-development.json)。
- `make acceptance-local` 校验当前项目内合同、格式、race/vet/build 和受限 RabbitMQ 本地矩阵;不访问真实服务。`make release-check-local` 构建并校验一次性本地制品与归档包、v0.1 业务/v0.2 发现/v3 队列草案和 Proto 的版本/hash,验证已有制品、打包目录、归档及校验文件均不会被覆盖,且 CLI 旧开关不存在。`make release` 仅允许在 `dist/` 下指定**尚不存在**的目标目录,记录 dirty-source 与版本/hash,始终标记 `production_approval=false`;两者均不部署或拨号。真实 OSS 授权测试仍须另用受控环境显式开启,不能从 Mock 或上述制品检查推定通过。
- 直接依赖版本/本地许可证清单见 [`docs/evidence/20260918-dependencies.md`](docs/evidence/20260918-dependencies.md);门禁结论见 [`docs/evidence/20260918-acceptance-matrix.md`](docs/evidence/20260918-acceptance-matrix.md);SIP 注册/认证/From/PAI/前缀/选路对比见 [`docs/evidence/20260919-sip-routing-implementation-comparison.md`](docs/evidence/20260919-sip-routing-implementation-comparison.md),手机号业务日志边界见 [`docs/evidence/20260919-phone-call-business-log.md`](docs/evidence/20260919-phone-call-business-log.md)。
这些证据只签收旧基线的单节点/单Cell/单租户P1适用范围,不覆盖本次MQ-only修订,也不等于真实供应商、生产SaaS/MQ receipt、生产切换或第二阶段拓扑通过。范围和延期项见 [`docs/evidence/20260920-scope-amendment.md`](docs/evidence/20260920-scope-amendment.md)。
以上 20260918–20260922 的历史证据只覆盖旧基线;本轮新合同、Mock C 和 F03–F09/F06 的本地证据单独列在前文。两组证据都不等于真实供应商、生产 SaaS/MQ receipt、实际切换或第二阶段拓扑通过。范围和延期项见 [`docs/evidence/20260920-scope-amendment.md`](docs/evidence/20260920-scope-amendment.md)。
## 显式重新申请上传授权
@@ -58,11 +58,11 @@ sip-go-agent agent upload-retry --spool /path/to/agent-spool \
| 文档 | 内容 |
| --- | --- |
| [本轮配置读取与有界外呼计划](docs/plan-config-read-v0.1.md) | 首读入口:F00–F06、C/L/M 与 I/M/G 门禁、本轮单写/验证状态;两只读接口仍是草案 |
| [本轮配置读取与有界外呼计划](docs/plan-config-read-v0.1.md) | 首读入口:F00–F09、C/L/M 与 I/M/G 门禁、单写/验证状态;四条只读 GET 的外部合同仍待签收 |
| [旧 plan-0918 归档原文](docs/archive/plan-0918.md) | W00–W16 历史状态及旧 MQ-only 阶段证据;非本轮执行入口 |
| [Go重写方案 v0.3](docs/architecture/Go重写方案_v0.3.md) | 本次单节点/单 Cell/单租户目标、至少3 SIP fixture、双AI模式、P0/P1/第二阶段分期 |
| [通信与事件数据交互 v0.1](docs/contracts/通信与事件数据交互_v0.1.md) | 首发Unary、静态SIP与双模式、§6.1–6.3 SaaS配置来源/参数矩阵/安全边界 |
| [SaaS↔Dispatcher 第三方对接顺序](docs/thirds/第三方对接事件与请求消费顺序_v0.1.md) | 配置获取、现行 MQ 命令/回执与拟定单份最终通话结果;取消查询/补传和分散事件仍待签收,替代原六份已删除说明 |
| [SaaS↔Dispatcher 第三方对接顺序](docs/thirds/第三方对接事件与请求消费顺序_v0.1.md)、[任务发现 v0.2](docs/thirds/v0.2.md) | 仅任务发现按 v0.2 §2.5 本地修订,其余项目内 SaaS 协议仍用 v0.1;外部合同/取消查询补传仍待签收 |
| [现行 MQ 机器契约](contracts/upstream/v1/mq.schema.json)、[Agent Proto](proto/agent/v1/agent.proto) | 现行消息和Unary字段权威;HTTP配置仅有项目提案 |
| [OpenAPI与MQ字段索引 v0.1](docs/references/OpenAPI与MQ字段索引_v0.1.md) | 旧源只读提取42个HTTP操作/115个组件及哈希;不代表新MQ-only入口,不手改生成物 |
| [deploys:物理机 systemd 发布包](deploys/README.md) | 锁定 Debian 13/Go 1.27.1/发布版本,构建并上传不依赖 Docker 的 Agent/Dispatcher 安装包 |
@@ -81,7 +81,7 @@ sip-go-agent agent upload-retry --spool /path/to/agent-spool \
## 旧阶段基线与本轮入口
以下为旧阶段的原有分期记录;两只读 HTTP 配置接口、有界消费和时段改造的新依赖、状态与门禁以[本轮计划](docs/plan-config-read-v0.1.md)为准。
以下为旧阶段的原有分期记录;四条只读 HTTP GET(配置、任务发现、租户额度)、有界消费和时段改造的新依赖、状态与门禁以[本轮计划](docs/plan-config-read-v0.1.md)为准。
1. **P0:先完成MQ-only新合同(GAP-10)。** 冻结D唯一ID/生命周期、独立Topic/队列/绑定、全部请求响应/关联/错误/期限及Unary异步衔接;旧包原样保留。其余已有基线按受影响范围重新验证,包括8种事件payload、双AI模式(GAP-08)、SaaS任务AI配置读取/调参(GAP-09)、首发Unary职责、单 Cell 静态快照来源/加载回执、D配置文件/临时上传TOKEN及上传通知可靠入队和至少3家SIP trunk的配置/协议 fixture。只核验实际采用的SDK;火山TTS参数覆盖、精确版本/许可证未核验前不宣布锁库,不等待未来动态发布/文本OSS归档合同。
2. **P1:本次单节点内测上线。** 完成单 Cell、单租户、双模式、幂等/控制/配额/恢复/录音安全的本地/隔离闭环;真实 ECS、真实外呼、生产 SaaS/MQ 联调不作为本阶段前置。
+11 -7
View File
@@ -6,7 +6,11 @@
本文件是 [Go 重写方案](../architecture/Go重写方案_v0.3.md) 的执行清单,**不是验收通过报告**。项目内已具备 Go 可执行程序、W01/W02 契约与本地测试入口;下文仍将本地通过、协议 Mock、真实集成和生产签收严格分开。
**本轮MQ-only修订重新打开受影响验收**:SaaS↔D所有请求/响应/事件只能走RabbitMQ,每个D全局唯一ID、独立接收Topic/队列。旧HTTP/仅租户路由/本地OSS验证证据不能覆盖新基线,现行 MQ-only 旧版验证事实见[归档计划§8.2](../archive/plan-0918.md)和[现行 MQ 机器契约](../../contracts/upstream/v1/mq.schema.json);第三方时序说明见[事件顺序](../thirds/第三方对接事件与请求消费顺序_v0.1.md),用户新批准的两只读 HTTP 配置接口仍须按[新计划](../plan-config-read-v0.1.md)另验。新增子场景纳入既有88项,不新增虚假通过数;本轮仅改文档,未执行这些运行测试。
**现行外部 MQ-only 合同仍有效;本文件不是验收通过报告。** 现行外部事实见[归档计划§8.2](../archive/plan-0918.md)和[现行 MQ 机器契约](../../contracts/upstream/v1/mq.schema.json)。本轮 P1 本地目标按[配置读取计划](../plan-config-read-v0.1.md)实施四条只读 GET、v3 MQ 与最终 `call.result`;除任务发现已按 [v0.2 §2.5](../thirds/v0.2.md) 修订外,其余项目内合同沿用[第三方对接 v0.1](../thirds/第三方对接事件与请求消费顺序_v0.1.md)。F01/F07 严格 Schema、正反例、来源/hash 和 Mock C 是本地门禁,不等待外部签收/连通;C 后按 F02→F03→F04→F08→F09 重验适用的 88 项子场景,不新增虚假通过数。旧 HTTP/租户路由/OSS 证据不能覆盖新基线;真实 SaaS、management、云、供应商和生产切换仍分别未验证。
任务发现的单次完整响应修订见 [v0.2 项目内合同](../thirds/v0.2.md)及[严格 Schema](../contracts/task-discovery-v0.2-proposal.schema.json):每 D ≤256、响应不带 queue 地址、空变更保留游标、`removed` 退役和 410 后全量恢复已通过 [F03 本地 Mock 验证](../evidence/f03-local-task-discovery-v0.2.md),F09 完整矩阵仍待重验。原 v0.1 分页 F03 证据保留为历史,不能计入新版通过;真实 SaaS 兼容性仍未验证。
[F04 本地 Mock 证据](../evidence/f04-local-dial-policy-v0.1.md)仅证明 Dispatcher 的任务×已选线路时段与最后拨号屏障、持久一次性决定及 Mock Agent 指令期限检查;Agent 不重算业务策略,也没有真实 SIP 发起。F08 最终结果/额度释放和 F09 全矩阵未完成;现行真实路径的固定 `09:00`–`20:00` 门禁未放宽,V3 mixed/real 启动拒绝。
每个阶段分别报告:通过、失败、未执行、被外部条件阻塞,以及“本阶段不适用/延后”。延后不得计入通过率或当成实现。旧Python证据不计Go通过数;管理平台、SIP Mock、供应商和Go Agent分别签收。
@@ -18,16 +22,16 @@
| --- | --- | --- |
| P1-01 独立制品/契约 | 单module/Cobra双命令独立构建;格式化/vet/race/构建及实际依赖门禁通过,使用入口与引用Schema可追踪;GAP的P1部分关闭 | C01–C11、L01–L08、A01/A02 |
| P1-02 单节点就绪 | 恰有1 Agent/1 Asterisk、1单活D;单 Cell 完成绑定/版本/健康/准入与隔离链路验证;失联或未授权时停止新接单,单 Cell 仅接满足授权与全局额度的新执行 | A03–A11/A18/A20、S07/S10;单 Cell/固定出口 fixture |
| 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-03 至少3家SIP | 至少3个不同供应商独立trunk;登记单 Cell/出口授权、主叫/前缀/codec/额度,使用配置/路由/协议 fixture 覆盖;未知/未授权组合拒绝;本地 F04 Mock 由 Dispatcher 按 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而不重发执行;Agent直传字节一致,D将recording.uploaded可靠交付指定持久队列;不等待SaaS会话/verified/OSS ID,通知恢复不重传文件或重拨 | S01–S04/S16/S23、E08/E12/E17–E19、A07/A15/A16 |
| P1-06 结果与资产 | 区分现行外部 v2 与本地目标 v3;本地仅验证四 GET、必要命令/控制回执及单份最终 `call.result`,不恢复 query/replay/实时文字/opt-out/拆分录音事件;Agent直传及持久通知恢复不得重传文件或重拨;超限按 8 MiB 规则阻塞,不截断 | F01/F07 Schema/Mock C、F08/F09 本地适用场景;现有 S/E/A 证据仅作旧基线 |
| 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-08 故障与控制 | 重投同一 call.execute 只产生一次实际发起;覆盖§4.1崩溃窗口、MQ/Unary/ARI断连及D/A重启,未知不重拨/不释放;本地 pause/resume/stop 按 F07 顺序/状态/ACK/退役规则通过,不引入旧 v2 控制 CAS;任务发现单次完整快照/增量、≤256条、拒绝分页/queue 字段、空变更、removed、410 全量恢复与无建队权限均须重新测试;F04 本地 Mock 最后许可及持久签发/拒绝决定按任务×已选线路时段拒绝窗口外发起、不等待或自动重试;真实路径固定 `09:00`–`20:00` 仍须独立验证 | F03/F04/F09 本地适用场景;旧 S/E/A 证据不替代新控制合同 |
| P1-09 静态配置/稳定性 | 管理批准制品经维护窗口加载;错误版本/哈希、未排空/未确认、部分失败均不恢复相关准入;两种模式按§9.1受限隔离负载持续稳定测试,告警/录音/资源无未解释泄漏 | E04/E05/E13、A09–A14的P1部分、S27;单 Cell profile与原始计数器 |
| P1-10 本地恢复/授权 | 单节点唯一写入与唯一调度所有权、备份/维护更新/恢复演练通过;契约/fixture 签收白名单、时段、限额/预算与风险;明确D单点和未上传资产丢盘边界。生产切换与真实授权延期第二阶段 | §7/§8、A06/A17–A20;运维步骤与隔离签收记录 |
| P1-10 本地恢复/授权 | 单节点唯一写入与唯一调度所有权、备份/维护更新/恢复演练通过;项目内契约/fixture/Mock C 留证,白名单、时段、限额/预算与风险边界明确;D单点和未上传资产丢盘边界明确。生产切换与真实授权另行申请 | §7/§8、A06/A17–A20;本地运维步骤与隔离证据 |
两模式均需在本地/协议隔离环境验证,所有纳入本轮的供应商配置 fixture 和单 Cell 组合分别留存闭环证据;稳定性长跑按批准的隔离负载矩阵覆盖全部 fixture。SIP 外呼时间窗口使用注入时钟覆盖 08:59:59、09:00:00、19:59:59、20:00:00 四个边界,窗口外必须在最后许可前失败且不产生 originate。真实供应商、ECS、生产 SaaS/MQ receipt 和生产切换如实标记为第二阶段,不能把 fixture 写成真实联调完成。
两种 AI 模式均需在本地/协议隔离环境验证,所有纳入本轮的供应商配置 fixture 和单 Cell 组合分别留存闭环证据;稳定性长跑按批准的隔离负载矩阵覆盖全部 fixture。F04 本地 Mock 按 Asia/Shanghai 的任务×线路时段、跨日、任务排除日期及左闭右开边界注入时钟;拒绝后持久记录且不产生 Mock Agent originate。现行真实路径另须覆盖固定窗口 08:59:59、09:00:00、19:59:59、20:00:00 四个边界,窗口外必须在最后许可前失败且不产生真实 originate;Mock 不代替真实门禁。真实供应商、ECS、生产 SaaS/MQ receipt 和生产切换如实标记为第二阶段,不能把 fixture 写成真实联调完成。
### 1.2 88项基线的阶段适用规则
@@ -246,7 +250,7 @@ P1运行不依赖旧Python Cell;迁移验证如需临时桥接而旧端不满
6. P1受限稳定性与已授权费用;P2公平调度;后续完整1000路/N+1分别签收。
7. P1维护切换、人工恢复、备份演练;在线发布/HA切换待相应功能启用另验。
本轮仅能检查第 1 类中的文档结构与一致性,不能把尚未生成的 Go 测试/服务列为“通过”。
本轮需按配置读取计划完成项目内 F01/F07、Mock C、F02–F09;每项只在产生可复核的本地测试/服务证据后记“通过”。任何本地结果都不替代真实 SaaS/management 或生产验收。
开发前的交付/解锁证据见 [G0开发准备与契约冻结方案](../architecture/G0开发准备与契约冻结提案_v0.1.md) §2/§5/§8。D01–D10方案已获用户确认;项目内 W01 版本和 W02 Proto 已交付,但外部权威签收、D10正式PoC和真实供应商仍未完成;十项交付门禁不增加本方案88项运行验收数量。§5.5内部profile已确认为隔离PoC初始值,未实测、不是生产SLA,也不覆盖下节上游/供应商基线。P1必须实施当前租户及单 Cell 原子配额,P2/第二阶段再扩展多租户公平、跨 Cell 和并行竞争验收。
@@ -1,6 +1,8 @@
# Dispatcher 有界接纳与控制通道改造计划 v0.1
**下一版设计草案,未实施、未获 SaaS 签收。** 唯一计划/台账是[plan-config-read-v0.1](../plan-config-read-v0.1.md);对外结构见[第三方对接](../thirds/第三方对接事件与请求消费顺序_v0.1.md)。本文同步替代此前的 ETag/304、控制 CAS、暂停丢弃旧积压和逐条停止回执方案,不修改现行 `contracts/upstream/v1/`。
**项目内 P1 设计计划,尚未实施;本地 F01/F07 Schema、正反例、来源/hash 与 Mock C 是实施门禁,不要求 SaaS 外部签收。** 唯一计划/台账是[plan-config-read-v0.1](../plan-config-read-v0.1.md);项目内对接结构见[第三方对接契约](../thirds/第三方对接事件与请求消费顺序_v0.1.md)。本文同步替代此前的 ETag/304、控制 CAS、暂停丢弃旧积压和逐条停止回执方案,不修改现行 `contracts/upstream/v1/`;外部兼容性未验证。
路径来源:`/internal/v1/dispatcher/sip`、`/internal/v1/dispatcher/task/:task_id`、`/internal/v1/dispatcher/tasks`(含 `?after=<cursor>`)为用户给定路径;`/internal/v1/dispatcher/tenant/:tenant_id/quota` 与任务路由字段 `route_policy_id`、`caller_profile_id`、`allowed_trunk_ids` 为项目内目标,按第三方对接契约和 F01/F07 Schema 实施;不代表 SaaS 现网字段,真实兼容性另记。
## 1. 目标与范围
@@ -15,7 +17,7 @@ SaaS 先建持久任务/控制队列和精确绑定,再发布 persistent 消
- `/internal/v1/dispatcher/sip`:本 D 获批 SIP 全量,变更关准入、排空/核验实际加载。
- `/internal/v1/dispatcher/task/:task_id`:任务及AI、tenant_id、tenant_key、明确主叫、候选线路优先级、两项任务超时。
- `/internal/v1/dispatcher/tasks`:启动一致全量/水位,运行每30秒 `?after=<cursor>` 获取变更;cursor非最大任务ID,旧ID变化也返回。
- `/internal/v1/dispatcher/tenant/:tenant_id/quota`:**新增路径/字段待签收**;拿到任务后取该租户分给本 D 的额度、版本、截止时间。额度0不接纳,缺失/过期失败不猜默认值。
- `/internal/v1/dispatcher/tenant/:tenant_id/quota`:本地项目定义路径;拿到任务后取该租户分给本 D 的额度、版本、截止时间。额度0不接纳,缺失/过期失败不猜默认值;真实 SaaS 路径未验证。
SIP/任务完整200响应最多缓存约60秒;租户额度也最多约60秒且不超过有效截止时间。不使用 ETag/304。同租户任务复用一份额度与占用,降额不强挂、不清未知;占用低于新限额后才接新。任务时限和AI授权对话时限取较小值;路由按任务获批候选顺序选首个可用且支持明确主叫的线路,拨号后不得自动换线重试。
@@ -28,23 +30,23 @@ SIP/任务完整200响应最多缓存约60秒;租户额度也最多约60秒且
| running、有名额 | 有界接收并原子预留;满额不继续搬队列 | 依持久快照执行 | 接纳/拒绝回执,发生执行后最终结果 |
| pause | 关闭准入并暂停消费,已交付未接纳消息退回原队列,不ACK丢弃 | 按drain/hangup | 暂停控制回执;不把暂停积压当作逐条拒绝 |
| resume | 强制读取最新任务,running且授权/额度/时段有效才恢复原队列;过期消息拒绝,未过期正常接纳 | 不改旧快照 | 恢复控制回执;无需SaaS重发原积压 |
| stop | 先持久停止屏障,再小批量消费并ACK,**无拨号、无逐条回执、无最终结果** | 按drain/hangup处理,保留真实执行结果 | stop控制本身有回执;静默只针对尚未接纳消息 |
| stop | 按第三方契约 §0 持久 stopped 屏障,退回已预取未接纳消息并以 `basic.get` 静默 ACK 积压至空;**无拨号、无逐条回执、无最终结果** | 按drain/hangup处理,保留真实执行结果 | 队列无未 ACK 且为空后回 stopped/applied;SaaS 收到后才退役队列 |
本地 stopped 不可逆,同任务ID不得再resume;paused只有有效resume可解锁。tasks增量/全量和配置缓存不能用旧running解除屏障,低版本不能覆盖高版本;冲突/HTTP错误关新准入。SaaS先持久修改权威任务状态再发控制;无编号控制不设计去重/请求CAS,重投可能重复回执,不能声称exactly-once。乱序/权威状态不符时明确失败并保持关准入,不能根据旧resume自动重开;控制恢复需要新的有效resume核验。
本地 stopped 不可逆,同任务ID不得再resume;paused只有有效resume可解锁。tasks增量/全量和配置缓存不能用旧running解除屏障,低版本不能覆盖高版本;冲突/HTTP错误关新准入。SaaS先持久修改权威任务状态再按每任务顺序发控制;无编号控制不设计去重/请求CAS,重投可能重复回执,不能声称 exactly-once。乱序/权威状态不符时明确失败并保持关准入,不能根据旧 resume 自动重开;恢复需要新鲜任务 GET 确认 running。具体状态和错误按第三方契约 §0/§3。
D重启恢复屏障再应用SaaS全量,取更严格状态。stop排空不依赖通话配额/AI配置可用性:额度0、配置过期仍可ACK;ACK丢失重投再次静默处理。静默仍须保留脱敏计数/错误/恢复事实,不能删除其他任务或忽略已在途执行。SaaS在停止新发布、队列积压和未ACK清零、未知执行核清并完成签收的退役核验后才删队列;队列空不是通话结束证明。
D重启恢复屏障再应用SaaS全量,取更严格状态。stop排空不依赖通话配额/AI配置可用性:额度0、配置过期仍可ACK;ACK丢失重投再次静默处理。静默仍须保留脱敏计数/错误/恢复事实,不能删除其他任务或忽略已在途执行。SaaS停止发布后,D 结清已预取投递并 `basic.get` 至空;仅在无未 ACK 且收到 stopped/applied 回执后,SaaS 才能删除队列/绑定并随后发 `removed`。队列空不是通话结束证明。
## 4. 期限、占用与结果
30秒轮询不是端到端发现保证,首条命令还需分页/配置/额度准备;有效期由SaaS覆盖其可接受排队时间,不使用30秒默认值。离线/暂停不延长not_after;过期/窗口外非stopped消息明确拒绝,不等待次日。stopped积压即使过期仍静默ACK。
只有核实通话终结并释放执行资源才释放通话额度,未知不释放;**不等待录音上传和最终消息确认**。录音上传成功后单份call.result带OSS路径;正常未产生录音(忙线/无应答)以not_created即时终结,不申请上传;应有录音但失败用unavailable,上传失败/超时须签收有界终结期限。未接纳且stopped静默处理没有call.result,其他未接纳拒绝只有回执,不造通话。最终文字大小及无截断策略需F07签收,MQ重投同一事实身份不重拨/重PUT。
只有核实通话终结并释放执行资源才释放通话额度,未知不释放;**不等待录音上传和最终消息确认**。录音上传成功后单份 call.result 带 OSS 路径;正常未产生录音(忙线/无应答)以 `not_created` 即时终结,不申请上传;授权/PUT 明确失败立即以 `unavailable` 收口,仍无确定结果最迟于 `call.ended_at + 15m` 收口。未接纳且 stopped 静默处理没有 call.result,其他未接纳拒绝只有回执,不造通话。每条 JSON 消息体上限 8,388,608 bytes;超限持久阻塞 outbox,不截断/拆分/丢弃;MQ 重投同一事实身份不重拨/重 PUT。
取消对外查询/补传与拆分实时文字/拒联/录音事件,不取消内部恢复、日志和外呼幂等。最终通知延迟对拒联业务的影响必须获业务签收。
取消对外查询/补传与拆分实时文字/拒联/录音事件,不取消内部恢复、日志和外呼幂等;本轮用户已批准仅最终反馈,真实 SaaS 消费兼容性另记。
## 5. 开发先后与验收
不再维护另一套 B 工作包。使用总计划:F01(HTTP/额度)与F07(MQ/发现/状态/结果)并行冻结并联合C → F02配置读取 → F03仅此处的消费/屏障 → F04最后许可/时段 → F08结果集成与切换 → F09矩阵验证 → F06发布。F05多D另授权,避免F01/F07互等和F03/F08重复实现。
不再维护另一套 B 工作包。使用总计划:F01(HTTP/额度)与F07(MQ/发现/状态/结果)并行冻结 → 项目内 Mock C → F02配置读取 → F03仅此处的消费/屏障 → F04最后许可/时段 → F08结果集成与本地发布准备 → F09矩阵验证 → F06发布。C 不等待外部签收;F05多D另授权,避免F01/F07互等和F03/F08重复实现。
必须覆盖总计划§5:同租户多任务原子上限/降额/未知、pause积压100条并在resume继续、stop积压100条后originate/outbox均0而已有2通结果保留、重启/ACK丢失、旧running/迟到resume不解锁、任务ID小而版本新、分页/游标失效、D无建队权限、离线期限、无录音、确认终结即释放额度而OSS延迟、完整最终转写超限。
@@ -2,16 +2,16 @@
## 1. 状态、权限与使用方式
**状态:D01–D10原方向已确认,旧W01契约和W02 Proto已有项目内证据;本轮用户确认SaaS↔Dispatcher全MQ,受影响G0的项目内部分已按[归档旧计划§1.2/§8.2](../archive/plan-0918.md)重新验证;本轮两只读 HTTP 配置目标见[新计划](../plan-config-read-v0.1.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方向被本修订替代,不把旧证据覆盖到新设计。
**状态:D01–D10原方向已确认,旧W01契约和W02 Proto已有项目内证据;现行 SaaS↔Dispatcher MQ-only 合同及受影响G0的项目内检查仍是当前外部运行基线;本轮四条只读 HTTP GET(SIP、单任务、任务发现、租户额度)和单份最终 `call.result` 的项目内契约见[新计划](../plan-config-read-v0.1.md),完成 F01/F07 Schema、正反例、来源/hash 与 Mock C 后可继续本地实现,不等待外部签收或连通。** 现行真实 SaaS 与 D 之间仍遵循已发布 MQ 合同;每个 D 具备全局唯一 ID 和独立接收 Topic/队列。V1 Schema、拓扑和关联已有本地包及 RabbitMQ 证据,外部发布/签收另计。OSS 补充确认:配置存于 D 配置文件,A 向 D 领取固定 15 分钟临时上传 TOKEN 后直传;SaaS 不再提供 OSS 配置/TOKEN,上传完成以 `recording.uploaded` 可靠入队为界,不等待 SaaS verified 或 OSS ID,D 不转发文件。本地 C 不宣称 G0、外部兼容、生产验收或上线授权。原 HTTP 方向被本修订替代,不把旧证据覆盖到新设计。
本项目已创建并验证自己的 Go module、W01 bundle、W02 Proto/stubs、RPC/mTLS 和本地 Mock 测试;未修改父项目权威来源、字段索引或生成产物,未访问真实供应商或创建云资源。文件名保留“提案”以保持链接稳定,不代表还需重复审批已确认方向。
当前结论:可以继续开展 P0 契约补充、隔离 PoC 和项目内实现;不得宣称 G0 通过或把项目内基线冒充外部权威,也不得启动未获授权的真实供应商/云/拨号。架构、首发范围和供应商方向沿用已有决定,不重新选型。
- 外部字段/路径/状态仍以带来源哈希的上游主契约为准。本文件记录已确认的变更方向和验收要求,**不能被代码当 Schema 使用**。后续由上游维护者修改唯一来源并发布,再导入生成;本文件保留为决策历史,不继续手工维护字段副本。
- 对现行真实 SaaS v1,字段/路径/状态仍以带来源哈希的上游主契约为准。本轮本地目标的规范来源是第三方对接文档,`docs/contracts` 中版本化 Schema、正反例和拓扑为其机器校验件并记录来源/hash;该本地合同不冒称外部发布。本文件仍是 G0 决策历史,不替代上述两类规范。
- 内部机制、字段方向及兼容策略已确认,正式 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。
- 以上为 G0 原阶段决策记录:现行 v1 下控制/查询/补传/配置授权/上传协调走专用 MQ。与本轮四条只读 GET、单份最终 `call.result` 目标相关的本地配置及事件边界,以[新计划](../plan-config-read-v0.1.md)和第三方对接契约为准;新本地代码通过 C 前,旧代码/真实外部运行仍按现行 v1。无需外部签收即可完成本地 C;真实切换另行授权与验证。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. 方案确认记录与开发边界
+6 -5
View File
@@ -1,6 +1,6 @@
# 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个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及关键可靠性;动态治理和规模化另立项。文件名保留,当前仍只有文档,不代表实现或验收通过。配置读取及最终反馈改造以[本轮计划](../plan-config-read-v0.1.md)为准:目标为 SIP、单任务、任务发现、租户额度四条只读 GET,并以一份最终 `call.result` 替代实时转写、即时拒联和拆分录音事件。F01/F07 联合 C 与业务签收前,本文记录的 MQ-only 配置和既有事件仍是运行合同;不能据新提案声称新路径/事件已生效。
## 1. 状态、范围与决策
@@ -111,19 +111,20 @@ Dispatcher配置包含MQ/SaaS受控引用、SQLite路径、两个Agent Endpoint
### 3.2 权威来源与独立拆仓
**本轮用户已确认SaaS↔Dispatcher全MQ:双方不再有任何HTTP请求/回调;每个D有全局唯一ID和独立接收Topic/队列。** 执行、控制、查询、补传、AI配置/授权及上传完成事实均经MQ;上传不申请SaaS会话、不等待verified/OSS ID,详见[现行 MQ Schema](../../contracts/upstream/v1/mq.schema.json)、[第三方事件顺序](../thirds/第三方对接事件与请求消费顺序_v0.1.md)与[归档旧计划§1.2/§8.2](../archive/plan-0918.md);这是现行已发布 MQ-only 合同的历史依据,后续仅配置改为两只读 HTTP 接口的目标见[新计划](../plan-config-read-v0.1.md),尚未成为运行事实。旧HTTP和旧租户路由已删除;新Schema/拓扑/身份生命周期/关联已在项目内唯一 V1 包和本地证据中冻结,旧源包及哈希不改。
**外部当前基线**仍是已发布的 SaaS↔Dispatcher MQ-only v2:真实运行中的执行、控制、查询、补传、AI配置/授权及上传事实沿用[现行 MQ Schema](../../contracts/upstream/v1/mq.schema.json)及[归档旧计划§1.2/§8.2](../archive/plan-0918.md),不擅改上游包。**本轮 P1 本地目标**按[新计划](../plan-config-read-v0.1.md)和[第三方对接项目契约](../thirds/第三方对接事件与请求消费顺序_v0.1.md)使用四条只读 HTTP GET、v3 命令/控制及单份最终 `call.result`;F01/F07 Schema、正反例、来源/hash 和 Mock C 通过后可继续本地实现,不等待外部签收或连通。它尚未成为真实运行事实,不能与 MQ v2 拼成线上协议,也不代表 G0/生产切换。
现有上游权威是《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。
- 对现行外部协议,仍将已发布版本作为**版本化、只读包**导入本项目 `contracts/`,记录源仓库、源版本/提交和文件 SHA-256;工作区修改不能冒充上游发布产物。
- 对本轮用户指定的项目内目标,以第三方对接文档为规范来源,`docs/contracts` 中版本化 JSON Schema、正反例与 MQ 拓扑为机器校验件,并在本地 manifest 中记录来源和 SHA-256;不宣称这些字段是 SaaS 已发布响应。
- 类型和校验代码按各自版本化契约生成/验证;现行外部上游包与本地目标包分离,不互相覆盖或维护隐式兼容层。
- 同步工具在开发阶段获取发布包;普通构建、测试和发行包运行不得读取父目录。
- 新增调度器—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或复制其业务后端。生成/校验仅覆盖使用入口及其引用闭包,来源包与只读字段索引保持完整。
- 42操作/115组件是上游目录,不是本项目首发工作量。现行外部 v2 仍按其已发布语义运行;本地 P1 目标则使用已冻结的四条只读 GET、呼叫/控制必要 MQ 回执与单份最终结果,删除旧 query/replay 和拆分实时文字/拒联/录音事件,不实现管理平台30个API或复制其业务后端。真实 SaaS 兼容性另记未验证。
- 当前AI Schema强制要求llm/prompt/tts/asr/conversation,缺少明确的ASR-only表达。GAP-08须先在上游批准模式/缺省/文字与错误语义;不在MQ添加临时mode字段或绕过required校验。
## 4. Go 1.27.1 的采用策略
@@ -1,5 +1,7 @@
# RabbitMQ local integration evidence — 2026-09-18
> Historical record: this verifies the former tenant-queue `--consume --tenant-key` path, which F03 removed. It is not current runtime or protocol evidence; current local V3 evidence is [here](f03-local-task-runtime-v0.1.md). No external broker compatibility is implied.
## Scope
This is an isolated local Docker broker test only. It is not a SaaS broker,
@@ -1,5 +1,7 @@
# MQ-only 本地最终回归证据
> 历史记录:本文记录上一阶段的 MQ-only/tenant-queue 实现与测试,不是当前 F03 接入协议或验收结论。旧 `mq-only-acceptance-local.sh`、Dispatcher tenant-queue consumer 与旧 control worker 已移除;当前本地 F03 证据见 [f03-local-task-runtime-v0.1.md](f03-local-task-runtime-v0.1.md)。本文中的旧查询/补传/事件结果不得作为新契约或外部 SaaS 兼容依据。
本记录只证明当前独立项目的单节点、单 Dispatcher、单 Agent、单 Cell、单租户本地/隔离范围;不代表真实 SaaS、真实供应商、云主机、生产 broker 或生产切换通过。
## 代码与契约
@@ -0,0 +1,33 @@
# F02 本地配置与租户额度读取证据
## 范围
本证据只覆盖单 Dispatcher、单 Agent、单 Asterisk、单 Cell、单租户的项目内 v0.1 实现与本地 Mock。它不代表 SaaS/management 外部签收、真实 Agent/Asterisk 已加载、真实部署或真实线路验收。
## 实现事实
- 配置客户端读取并严格校验 SIP、任务发现页、任务配置、租户额度四条只读 GET;核对 Dispatcher、任务、tenant_id、原值 tenant_key 与 SaaS 所有的队列路由身份。游标过期时可请求完整发现快照首响应;多页聚合、持久水位与重启恢复由 F03 完成。
- Dispatcher 仅在四个完整响应都验证通过且 SIP applied-status verifier 通过后发布配置快照。每个响应原始 JSON、版本、内容 SHA-256、获取时间和失效时间写入 SQLite;版本回退或同版本内容变化拒绝。内存快照按 tenant_id/task_id 索引;SIP/发现快照在任务间共享,租户额度在同租户任务间共享。失败或过期不使用旧缓存准入。
- tenant_id 与 tenant_key 在本 Dispatcher 内一对一绑定。租户/任务额度更新与完整响应持久化同事务;额度降级不清理已有 reserved/unknown 占用。
- 接纳 `call.execute` 时,tenant 与 task 两个额度范围、任务行、执行配置原始快照、reservation、inbox、command receipt 和 outbox 在一个 SQLite 事务内提交。额度不足时持久化 `quota_exceeded` 拒绝,不创建任务/占用;额度未配置或事务失败时拒绝 ACK。相同 command_id 重投复用原 receipt,不重复占额。
- 新配置读取必须通过 Agent applied-config 核验:部署指定的 Agent/Cell 身份需匹配;`applied_configs` 恰有一条 `kind="sip"`,且 `state="applied"`、十进制 revision、SHA-256 与配置响应中的获批 SIP artifact 精确一致,并有正的 `observed_at_unix_ms`。缺失、重复或不匹配均 fail-closed。此处是项目内 Agent 协议规则;Mock status 不证明真实 Asterisk 已加载。
- 配置刷新持写锁,命令接纳持读锁;刷新失败清空可用于新接纳的内存快照,避免旧配置与新版本竞态。
## 验证结果
- `python3 scripts/validate-local-contracts.py`:四个本地 Schema、正反例和 19 个 manifest SHA-256 项通过。
- `go test ./internal/configread ./internal/contract ./internal/store ./internal/dispatcher -count=1`:通过。
- `go test -race ./internal/configread ./internal/contract ./internal/store ./internal/dispatcher -count=1`:通过。
- `go test ./... -count=1`:通过。
- `go vet ./...`:通过。
- `go build ./...`:通过。
- `go test -cover ./internal/configread ./internal/contract ./internal/store ./internal/dispatcher -count=1`:覆盖率依次为 75.0%、73.3%、65.1%、71.4%,F02 涉及包均不低于 65%。
- 当前 `go test -cover ./...` 总覆盖率为 49.5%;全项目 65% 门禁仍由 F09 验证,不能将 F02 包覆盖率当作全项目通过。
Mock 覆盖配置断连、过期、身份/路由不匹配、版本冲突、额度降级、同租户多任务共享额度、并发额度竞争、刷新与接纳竞态,以及 Agent applied-config 缺失/重复/不匹配。
## 未验证与后续边界
- 未连接真实 SaaS、management、云、供应商或 SIP 线路;没有真实拨号、真实 Agent status 或真实 Asterisk 加载证据。
- F02 组件及本地测试已交付,但 v3 队列消费/应用启动集成仍属于 F03;当前旧 MQ v2 主入口尚未切换。
- F09 其他工作包、真实部署/切换和生产验收未由本证据覆盖。
@@ -0,0 +1,22 @@
# F03 v0.2 单次任务发现:本地 Mock 验证
范围:单 Dispatcher、单租户、预建任务队列;仅本地 Mock HTTP 与隔离 RabbitMQ。`docs/evidence/f03-local-task-runtime-v0.1.md` 中的分页发现结果只作历史,不用于本版签收。`contracts/upstream/v1/` 外部运行基线未改,真实 SaaS/management、云、SIP 线路、拨号和生产均未验证。
## 合同与实际改动
- `docs/thirds/v0.2.md` §2.5、`docs/contracts/task-discovery-v0.2-proposal.schema.json`、六份 v0.2 正反例/错误例和 `docs/contracts/local-contract-manifest-v0.2.json`:完整快照或增量每次只返回一个响应,最多 256 项;不含 `queue`、分页 token、`snapshot_id`;空变更 `next_cursor` 等于请求 `after`,`removed` 撤销归属,410 `cursor_expired` 重取全量。v0.1 Schema 和示例留在文档校验中供历史核对,但运行时代码只加载 v0.2 任务发现 Schema。
- `internal/configread/client.go`:验证完整响应、Dispatcher/租户绑定、重复 task ID、256 项界限和空变更游标。410 仅在游标过期时拉取一次全量;其他失败不推进游标,不把 HTTP 失败误当无变更。
- `internal/store/local_v01_discovery.go`、`internal/store/migrations/015_local_v02_task_discovery.sql`:同一事务写入完整响应及 SHA-256、任务归属和游标;主动任务总量超过 256、版本回退或身份冲突时事务回滚。队列名称/路由按 D+task ID 在本地推导;仅保留任务归属及未决执行,旧分页游标/页存储被移除,启动后须重新获取完整快照。SaaS 仍独占队列创建、绑定和退役。
- `internal/dispatcher/task_runtime_v3.go`:发现失败即持久关闭新准入并停止任务消费;成功获取完整响应后才重新开放并核对消费者。控制通道不因任务发现失败而关闭。暂停保留积压、恢复原队列、停止先持久屏障后静默 ACK 的既有行为不变。
## 本地验证事实
- `python3 scripts/validate-local-contracts.py`:旧 v0.1 历史合同继续可校验;v0.2 草案 26 个正向验证、4 个负向验证、12 个 SHA-256 文件通过。Schema 拒绝旧 `queue`/分页字段与 257 项响应。
- `go test ./... -count=1` 与 `./scripts/acceptance-local.sh` 通过:包含格式检查、`go mod verify`、Schema/hash 校验、`go test -race ./... -count=1`、`go vet ./...`、`go build ./...` 和本地 RabbitMQ 集成。RabbitMQ 使用独立预建队列账户和 Dispatcher 账户;Dispatcher `configure=^$`,未声明、绑定或删除队列。Mock 使用 v0.2 发现响应;此结果不是 SaaS 应用收讫或真实队列授权验收。
- `internal/configread/discovery_v02_test.go`:单次全量/增量、空变更游标、410 全量恢复、拒绝旧 queue/page 字段及 256/257 边界。
- `internal/store/local_v01_discovery_test.go`、`internal/dispatcher/task_runtime_v3_test.go`:完整响应/归属/游标同事务持久化、超限回滚、失效期间拒新、任务消费停止、恢复后重开、410 全量快照退役旧任务;任务队列名称由约定规则推导。
- 本地非集成覆盖率:`internal/configread` 74.9%、`internal/dispatcher` 66.0%、`internal/store` 66.0%;`internal/mq` 单独不带 RabbitMQ 集成时仅 1.7%,不能声称所有模块已满足 65%。F09 的完整矩阵与覆盖率门禁仍需另行执行。
## 未完成边界
F04 的任务/线路时段与最后拨号门禁、F08 最终结果/切换、F09 全矩阵及真实 SaaS 兼容性均未由本证据签收。Mock 只能证明项目内定义与本地实现一致,不能授权任何真实呼叫、上传或云操作。
@@ -0,0 +1,23 @@
# F03 本地任务队列、发现与控制证据
## 范围与结论
F03 在单 Dispatcher、单 Agent、单 Cell、单租户的本地隔离环境通过。SaaS/management、真实云主机、供应商、SIP 拨号、生产部署与切换均未连接或验证;项目内 Mock 结果不代表外部兼容或生产验收。`contracts/upstream/v1/` 仍是外部运行基线。
## 实现事实
- Dispatcher 使用 MQ v3 运行时。任务队列和控制队列由 SaaS 预建;Dispatcher 只被动验证/消费和停止消费,不声明、绑定或删除这些队列。RabbitMQ 集成测试将 provisioner 与 Dispatcher 分成两个身份;Dispatcher 的 `configure` 权限正则为 `^$`,直接 `QueueDeclare` 被拒绝,而被动连接、消费、排空和结果发布通过。CLI 不再提供 `--tenant-key`/`--consume` 自建租户队列路径,旧 tenant-queue consumer 与旧 per-call control worker 已移除。
- 任务发现读取完整分页结果;不透明游标/页 token 校验连续性,完整发现窗口及任务归属在同一事务持久化后才推进水位。无游标时取完整快照,客户端检测到游标/页状态失效时走完整快照恢复。
- 启动和轮询仅在任务归属、running 状态、有效配置快照均满足时订阅相同的 SaaS 预建任务队列。pause 先持久关闭接纳并停止消费者,保留积压;resume 重新读取权威 running 状态后恢复消费原队列;stop 先持久终态屏障并停止消费者,再读取权威 stopped 状态。
- stop 将权威状态/版本先持久化但不写回执,随后被动排空并 ACK 未接纳积压,最后才事务写入 control receipt/outbox。排空失败时无回执,重复控制可继续排空并在成功后写入原任务结果,不产生未接纳命令回执或拨号。
## 验证记录
`./scripts/acceptance-local.sh` 通过。该入口检查格式、契约哈希与 Schema 正反例、`go mod verify`、`go test -race ./... -count=1`、`go vet ./...`、构建,并运行本地 RabbitMQ 集成测试。
- 本地契约:4 个 Schema 有效;正例数量为 config-read 17、task-discovery 13、command-next 6、call-result 4;四类负例均拒绝;19 个 manifest 文件 SHA-256 校验通过。
- 覆盖率:`go test ./internal/configread ./internal/dispatcher ./internal/store -cover -count=1` 分别为 76.8%、66.2%、65.3%。带本地 RabbitMQ 的 `-race -cover -tags=integration` 测试中,`internal/dispatcher` 为 66.4%,`internal/mq` 为 55.5%。项目 F09 的完整覆盖率门禁仍待 F09 统一验收,不在此处冒称已通过。
- RabbitMQ 使用本机已缓存的 `rabbitmq:4.1-management-alpine` 镜像和临时 loopback 容器;集成 fixture 由独立 Mock SaaS 身份预建并清理队列。无 `configure` 权限的 Dispatcher 身份不能声明队列,但 V3 MQ 与 Dispatcher 端到端测试均通过。脚本在镜像未缓存时 fail-closed,不隐式拉取镜像。
- 控制测试覆盖暂停保留积压、权威状态后恢复、已停止任务不能恢复、停止排空前不写回执、排空失败后重试,以及无归属任务的拒绝回执。发现/运行时测试覆盖完整快照/游标持久化、队列接纳资格和运行时缺配置失败关闭。
本证据只证明本地项目内契约与隔离 Mock。未访问真实 SaaS/management、外部 MQ、云、供应商或 SIP 线路;未发起真实呼叫、上传或切换。
@@ -0,0 +1,27 @@
# F04 本地 Mock:外呼时段与最后拨号屏障
状态:**仅本地 Mock 的 F04 门禁已验证**。单节点、单 Dispatcher、单 Agent、单 Cell、单租户;没有接入真实 SaaS、management、SIP 线路、云资源或生产环境,也没有发起真实呼叫。F08 最终结果、额度释放与上传/MQ 收口及 F09 全矩阵仍待验收。
## 本地规则与执行边界
- Dispatcher 在命令持久接纳时,以及向 Agent 发出指令前,分别检查原始被叫白名单、任务授权和状态、Asia/Shanghai 的任务×**已绑定线路**周时段、任务排除日期、任务/AI 较小通话时限、命令与授权及租户额度有效期。时段左闭右开;跨日拆分按当地日期判断;排除日期只属于任务。接纳后固定原配置快照、主叫与所选线路,不因新配置、时钟变化、暂停/停止、线路变化而自动换线、等待或重拨。
- `local_v02_origination_decisions` 对每个执行持久写入唯一 `issued` 或 `refused`;拒绝后即使时钟回退或 MQ 重投也不能重新获准。Agent 调用前窗口再次关闭时,不调用 Agent,已签发身份仍不得重拨,留待 F08 结果/占用对账。
- SaaS 独占队列;任务消费者先持久接纳,再查原命令的待执行记录并执行 Dispatcher 最后门禁。成功或失败均核对持久决定;已决定的重投直接 ACK,不把 Agent 失败当作 MQ 自动重试信号。控制消费者与任务消费者独立。
- 新的 `agent-authorized-origination.v0.1` Unary gRPC 只在隔离 Mock 中启用。Dispatcher 下发身份、线路、主叫、时限及最早截止时刻;Agent 只核验会话、指令完整性及 Dispatcher 签发期限,持久记录未知/终态,不重新判断白名单、业务时段或选择线路。Mock 适配器明确不发 SIP;Dispatcher V3 的 mixed/real 模式在启动时拒绝,不能将此合同当作真实拨号许可。现行真实路径的固定 `09:00`–`20:00` 限制未放宽。
## 验证事实
| 项目 | 结果 |
| --- | --- |
| `internal/callwindow/policy_test.go` | 任务和线路交集、左右边界、跨日、任务排除日期、空起止时间 |
| `internal/dispatcher/dial_policy_test.go`、`local_v01_test.go`、`local_origination_quota_test.go` | 白名单、固定线路与主叫、任务/AI 时限、命令/额度到期、绑定快照不受缓存更新影响、控制与持久决定竞态 |
| `internal/dispatcher/task_queue_v3_origination_test.go`、`local_origination_rpc_test.go` | 消费后 Mock Agent 执行一次;窗口外不调用;已签发/已拒绝命令重投均不触发二次呼出 |
| `internal/store/local_origination_test.go` | 原命令与绑定快照的哈希/身份、暂停屏障、唯一签发或拒绝决定、原始消息重复投递与额度占用均失败关闭 |
| `internal/rpc/authorized_execution_test.go` | 非 Mock 拒绝、签发期限、相同身份重投、内容冲突、Agent 重启及未知结果不重复调用 |
| `./scripts/check-proto.sh`、`./scripts/acceptance-local.sh` | 2026-09-24 本地通过;后者含格式、合同/例子哈希、`go vet ./...`、`go test -race ./...`、构建和本地 RabbitMQ `no configure` 权限测试。RabbitMQ 使用本地缓存镜像,不访问真实 SaaS。 |
普通测试覆盖率(`go test -cover`):`internal/callwindow` 84.4%、`internal/dispatcher` 68.3%、`internal/store` 66.7%、`internal/rpc` 69.7%;本地 RabbitMQ 集成测试中 `internal/dispatcher` 68.4%、`internal/mq` 55.5%。**F09 的全部适用模块覆盖率≥65% 尚未达到,不以 F04 测试代替 F09 验收。**
## 未签收与后续
F08 尚须把 Mock 通话终态与单份 `call.result`、持久 outbox、额度释放和录音上传事实按既定合同接通,并证明断电/重复通知不重拨或重 PUT;此前 Agent 的 Mock 终态不等于 SaaS 已处理的结果。F09 须重跑发现 v0.2 与单节点适用场景全矩阵并解决覆盖率缺口。F06 真实环境、真实线路、供应商、SaaS/management 联调与生产切换均无本轮授权,**未执行、未验证**。
@@ -0,0 +1,24 @@
# F08 本地 Mock:唯一最终结果与恢复证据
范围:单节点、单 Dispatcher、单 Agent、单 Cell、单租户的项目内隔离 Mock。除任务发现按 v0.2 外,其余本地 SaaS 对接协议仍按 v0.1;新增录音失败事实仅在 Agent↔Dispatcher 的 Mock 路径使用。未访问真实 SaaS、management、OSS、SIP、云或生产环境;不修改 `contracts/upstream/v1/`。
## 已验证的本地行为
| 场景 | 本地结论与证据 |
| --- | --- |
| 通话确认终结与额度 | `local_call_terminals` 保留已确认终态并立即释放执行额度,不等待 OSS/MQ;通话未知继续占额。`internal/store/local_call_terminal_test.go`、`internal/dispatcher/local_mock_pending_result_test.go`。 |
| 唯一结果与无录音 | 已接纳但未拨号的拒绝/期限用执行关联 ID 收口,不伪称 SIP dialog;无应答/忙线等录音为 `not_created`。已终结录音等待完成、明确失败或 15 分钟期限,结果不可变、只入队一个 `call.result`。`internal/dispatcher/local_mock_refusal_pending_test.go`、`local_mock_dispatcher_deadline_test.go`、`local_mock_pending_result_test.go`。 |
| 上传成功与明确失败 | 录音 manifest、授权上传 ID、checksum、完成事实必须与唯一执行绑定;结果持久化后不再签新 PUT 授权。`uploaded` 与 `unavailable`(授权/PUT 明确失败、期限)均在隔离 Mock 测试中用合成录音事实验证;未知 PUT(传输异常、HTTP 408/429/5xx)不伪报失败、不重 PUT。`internal/agent/upload_failure_report_test.go`、`internal/store/local_v3_upload_state_test.go`、`internal/rpc/dispatcher_upload_local_v3_test.go`。 |
| Agent 失败事实与重启 | 已知失败先写 Agent 文件日志;经原 AgentControl listener 的 `ReportExecutionEvent`、当前会话元数据上报。模拟 Dispatcher 已持久签收但回执丢失,Agent 重启/boot 更新/超过录音期限后仍重送原 fact ID、原摘要与观察时间;Dispatcher 幂等,事实仅一份,不发第二次 PUT 或呼叫。无当前激活会话则保留事实而不冒用旧会话。`internal/dispatcher/local_mock_pending_result_test.go`、`internal/agent/upload_failure_report_test.go`、`cmd/sip-go-agent/upload_failure_recovery_test.go`。 |
| 可靠 MQ 与受控边界 | 最终消息经 outbox 保留原 event ID,publisher confirm、mandatory、durable 路由确认后才将相应上传标记完成;confirm 不等于 SaaS 已处理。MQ 失败/确认丢失重发原消息,不重新 originate/PUT;超过 8,388,608 字节持久阻塞并记录 ID、字节数与 SHA-256,不截断或拆分。RabbitMQ 隔离集成验证了单份 `call.result`、重启补投及无 `configure` 权限的发布路径。V3 Mock 拒绝旧拆分执行事件;V3 mixed/real 启动拒绝,现行真实 `09:00`–`20:00` 门禁未放宽。`internal/dispatcher/outbox_payload_limit_test.go`、`local_v01_integration_test.go`、`internal/rpc/dispatcher_events_local_v3_test.go`、`scripts/mq-integration-local.sh`。 |
新增内部失败事实的字段、4 份正反例、独立 SHA-256 清单和 Agent 嵌入 Schema 的一致性见 `docs/contracts/local-mock-recording-failure-v0.1.md`、同目录 Schema/manifest 与 `scripts/validate-local-contracts.py`。不将此事实当成 SaaS v0.1 扩展,也不启用 mixed/real。
## 验证记录
- `go test ./... -count=1`:通过。
- `./scripts/check-proto.sh`、`./scripts/check-contracts.sh`:通过,包含新事实正反例、来源/hash 与嵌入 Schema 一致性。
- `./scripts/acceptance-local.sh`:通过,内含 gofmt、`go mod verify`、`go test -race ./... -count=1`、`go vet ./...`、构建,以及本地 RabbitMQ 有界权限集成测试。
- 本次普通测试覆盖率:`internal/agent` 71.1%、`internal/dispatcher` 69.7%、`internal/rpc` 69.9%、`internal/store` 67.1%、`internal/contract` 74.0%;`internal/mq` 普通测试仅 1.7%,主要逻辑需带 RabbitMQ 的集成测试覆盖,**F09 的 ≥65% 门禁尚未签收**。
默认 Mock originator 只模拟无应答,不生成真实录音;本地上传成功/失败场景由测试注入严格绑定的合成 manifest/回执,并非真实录音文件、OSS PUT 或真实供应商闭环。录音事实已就绪时的结果/失败恢复可验证,不据此宣称实际媒体采集或真实 SaaS 应用签收已完成。F09 完整本地矩阵、覆盖率及本地发布准备仍另行验证;F06 真实部署/切换没有授权,未执行。
@@ -0,0 +1,24 @@
# F09 P1 本地 Mock 验收(任务发现 v0.2)
仅验收单节点、单 Dispatcher、单 Agent、单 Cell、单租户的本地隔离 Mock。任务发现采用 `docs/thirds/v0.2.md` §2.5;其余 SaaS 项目内协议仍为 v0.1。未修改 `contracts/upstream/v1/`,旧分页发现不运行、不回退。此处的通过不代表 SaaS/management、真实 OSS/SIP、Asterisk 媒体、云部署或生产切换通过。
## 计划 §5 本轮适用矩阵
| 适用场景 | 本地验证结果 |
| --- | --- |
| 发现版本与恢复 | 单次完整响应最多 256 项、无分页及队列地址/key;空变更保持请求水位,`removed` 退役,410 `cursor_expired` 重新取得全量并原子持久化;超限/未知字段拒绝,不截断、不启用旧分页兼容。合同正反例、来源及 SHA-256、Mock SaaS 流程见 [`f03-local-task-discovery-v0.2.md`](f03-local-task-discovery-v0.2.md),`internal/configread/discovery_v02_test.go`、`internal/store/local_v01_discovery_test.go`。 |
| 只读配置、归属与额度 | D 身份/租户/任务及版本、SIP 快照已加载校验,HTTP 错误与缓存过期关闭新准入;按归属 D 及 tenant_id 取得共享额度,缺失/0/降额/未知占用不擅自发额或释放。`internal/dispatcher/local_config_read_test.go`、`local_v01_config_test.go`、`local_origination_quota_test.go` 及 F02 证据。 |
| 最后拨号判断 | 任务×已选线路的上海时区窗口、任务排除日期、号码白名单、有效期及较小时限在接纳和发起前检查;不等下一窗口、不换线重拨;mock 授权仅 Mock Agent 使用,mixed/real 不启用新授权。见 [`f04-local-dial-policy-v0.1.md`](f04-local-dial-policy-v0.1.md)、`internal/dispatcher/dial_policy_test.go`、`local_v01_test.go`。 |
| 队列暂停、恢复及停止 | RabbitMQ 隔离测试中 100 条积压经 pause 保留、resume 从原队列继续;stop 静默 ACK 另 100 条未接纳命令,不产生逐条结果。两条已发起的 Mock 执行在 stop 前已将 Agent 归属与一次性拨号决定**同一 SQLite 事务**持久化,故 stop 对两条都发 hangup;已在途通话仍各自产生最终结果、释放额度,不额外拨号。见 `internal/mq/amqp_v3_integration_test.go`、`internal/dispatcher/task_queue_v3_stop_active_test.go`、`task_control_v3_test.go`。此前测试发现 V3 缺失持久 Agent 归属,导致两条在途执行被 stop 控制漏掉;先有失败测试,再修复并复测。 |
| 队列所有权、队列满、SQLite 写入失败 | Dispatcher 的受限 RabbitMQ 账号无 `configure` 权限,仅消费 SaaS 预建任务/控制队列;队列缺失不自行声明。SaaS Mock 建 `x-max-length=1`、`reject-publish` 队列,第二条 persistent 命令获拒绝,原第一条保留并由 D 消费,未入队的消息不虚称 D 自动补收。SQLite 写入故障以隔离测试触发器模拟满盘类写入错误:准入事务无残留任务/额度、错误非永久命令错误,恢复后沿原命令身份接纳;MQ 瞬态 handler 错误 NACK/requeue 原消息,恢复后 ACK。**未物理填满磁盘**。见 `internal/mq/amqp_v3_queue_full_integration_test.go`、`amqp_v3_integration_test.go`、`internal/dispatcher/local_sqlite_full_test.go`。 |
| 结果、上传及故障恢复 | 确认终态后独立释放额度;`uploaded`/`unavailable`/`not_created` 只出一份最终 `call.result`。明确录音失败事实、未知 PUT、15 分钟期限、断回执/重启、MQ confirm 丢失和超长正文均保留原身份,不重拨或重新 PUT;confirm 不是 SaaS 应用签收。见 [`f08-local-final-result-v0.1.md`](f08-local-final-result-v0.1.md) 与本轮受限 RabbitMQ 集成。默认 Mock 无真实录音,录音分支由严格绑定的合成事实注入。 |
## 执行与覆盖率
- `./scripts/acceptance-local.sh`:通过;内含格式、Proto/合同及 hash 校验、`go mod verify`、`go test -race ./... -count=1`、`go vet ./...`、构建和无 `configure` 权限的本地 RabbitMQ 集成测试。
- `go test -cover -count=1`:本轮 P1 业务包 `agent` 71.1%、`callwindow` 84.4%、`configread` 74.9%、`contract` 74.0%、`dispatcher` 70.4%、`rpc` 69.9%、`store` 67.2%、`tenant` 85.4%。`mq` 必须带 RabbitMQ integration tag 执行,`./scripts/mq-integration-local.sh` 中为 66.4%。均达到用户确认的**本轮 P1 业务模块 ≥65%**口径。
- 独立列明未达线包:`cmd/sip-go-agent` 29.6%、历史 `internal/callruntime` 61.7%;MQ 普通测试不带 integration tag 时仅 1.7%。这些数字**不计入本轮 P1 业务模块门槛**,不宣称 CLI 或全仓库达到 65%,后续补测仍是技术债。
## 未验证及门禁
真实 SaaS/management 合同、ECS/OSS/SIP 供应商、Asterisk 媒体、第二节点/Cell/租户、容量及切换均未验证或未获授权。F06 只核对本地制品、合同/队列版本和 fail-closed 发布准备;不得因上述本地 Mock 结果启动真实部署、拨号或生产切换。
+30
View File
@@ -0,0 +1,30 @@
# 本地 Mock SaaS C 门禁证据(v0.1)
## 范围
本证据只证明项目内 F01/F07 契约与本地 Mock 的端到端交互。未访问真实 SaaS、management、云资源、供应商或 SIP 线路;未发起通话、未上传录音,也未验证外部兼容性。`contracts/upstream/v1/` 仍是当前外部运行基线。本地通过不代表外部签收或生产验收。
## Mock 交互
`TestLocalDispatcherMockSaaSEndToEndWithOutboxRecovery` 使用本地 HTTP Mock、临时 SQLite 和本地 RabbitMQ:
1. Dispatcher 使用 `X-DISPATCHER-id` 与 `X-DISPATCHER-SECRET-KEY` 请求四个只读资源:
- `GET /internal/v1/dispatcher/sip`
- `GET /internal/v1/dispatcher/tasks`
- `GET /internal/v1/dispatcher/task/task-mock`
- `GET /internal/v1/dispatcher/tenant/tenant-id-mock/quota`
2. SaaS Mock 预先创建并绑定其所有的 task/result 队列。测试发布 persistent `call.execute`;Dispatcher 消费后持久化命令与 `command.result` outbox。
3. 测试暂时删除 SaaS-owned result queue,确认发布失败并保留 outbox;恢复队列后,Dispatcher 重试并将相同回执送达 Mock。
4. 测试使用本地示例构造 synthetic `call.result`,并核对它引用已接纳的 `command_id`、`execution_id`、task 和租户;Mock 收到完整事件。重复相同事件幂等,执行 ID 不匹配时拒绝。
当前 C 切片只验证配置读取、命令收件/回执及最终结果的持久化交付;不会启动真实执行,也不调用 Asterisk。`call.result` 中的转写和 OSS 对象均为示例数据,不对应真实录音或上传。`pause/resume/stop` 的项目内 Schema 与正反例由合同验证器检查;其运行时控制和队列排空属于后续 F03,不计入本次运行时 E2E。
## 契约与验证结果
- `python3 scripts/validate-local-contracts.py`:4 个项目内 Schema 有效;正例分别为 config-read 17、task-discovery 13、command-next 6、call-result 4;每类 Schema 的负例均按预期拒绝;source 与 18 项产物共 19 个 SHA-256 文件通过。
- `bash scripts/check-contracts.sh`:上游 v1 契约基线、项目内 Schema/示例及 Go 合同测试通过。
- `go test ./internal/configread ./internal/dispatcher ./internal/store ./internal/mq`:通过,覆盖配置读取失败清除旧快照、命令过期/白名单拒绝/窗口拒绝、重复命令和最终结果关联。
- `bash scripts/mq-integration-local.sh`:通过;本地 RabbitMQ 集成测试覆盖 Mock 消息发布、result queue 暂不可用后的 outbox 恢复以及最终事件交付。
- `git diff --check`:通过。
`go vet`、`go test -race`、完整构建、模块覆盖率及 F02–F09 的验收仍待后续工作包验证。本证据不宣称这些检查已通过。
+33 -25
View File
@@ -1,6 +1,6 @@
# Go SIP Agent:配置读取与有界外呼迭代计划 v0.1
**当前迭代唯一执行入口。状态:项目提案;新 HTTP/MQ 合同、实现及验收均未完成。** 现行 `contracts/upstream/v1/` 和代码仍是旧基线,不将本文当作上线授权。分项设计:[有界消费与控制通道](architecture/Dispatcher有界接纳与控制通道改造计划_v0.1.md);对外结构:[第三方对接](thirds/第三方对接事件与请求消费顺序_v0.1.md);配置字段:[字段提案](contracts/config-read-fields-v0.1-proposal.md)、[草案 Schema](contracts/config-read-v0.1.schema.json)。分项材料须与本文一起更新,不能继续使用旧 ETag/304、控制 CAS 或即时反馈目标。
**当前迭代唯一执行入口。状态:用户已授权 P1 本地迭代;项目内契约冻结与 Mock C、实现及本地验收进行中。** 现行 `contracts/upstream/v1/` 和代码仍是旧外部基线;本文不授权真实 SaaS、生产部署、拨号或切换。分项设计:[有界消费与控制通道](architecture/Dispatcher有界接纳与控制通道改造计划_v0.1.md);原本地 SaaS 结构契约:[第三方对接 v0.1](thirds/第三方对接事件与请求消费顺序_v0.1.md);任务发现单次响应的[修订草案 v0.2](thirds/v0.2.md)及[独立 Schema](contracts/task-discovery-v0.2-proposal.schema.json)不回写 v0.1 历史证据,待新合同及 F03 实现验收后切换。机器校验件:[Schema/正反例](contracts/)。分项材料须与本文一起更新,不能继续使用旧 ETag/304、控制 CAS 或即时反馈目标。
## 1. 范围和完成标准
@@ -13,13 +13,13 @@
| `/internal/v1/dispatcher/sip` | 用户指定路径;本 D 获管理面批准的 SIP 全量。 |
| `/internal/v1/dispatcher/task/:task_id` | 用户指定路径;任务归属、路由/主叫、智能体、时段及两项任务级超时。 |
| `/internal/v1/dispatcher/tasks`(可 `?after=<cursor>`) | 用户指定路径;启动一致全量快照,运行中每 30 秒按变更游标发现任务。 |
| `/internal/v1/dispatcher/tenant/:tenant_id/quota` | **新增路径/字段为项目草案**:取得任务后按其 `tenant_id` 读取该租户分配给本 D 的并发额度;实际路径须 SaaS 签收。 |
| `/internal/v1/dispatcher/tenant/:tenant_id/quota` | 本地项目定义路径:取得任务后按其 `tenant_id` 读取该租户分配给本 D 的并发额度;真实 SaaS 路径兼容性未验证。 |
**任务队列及精确绑定只由 SaaS 创建、维护和退役;D 只消费,不建队、不绑定、不删除。** SaaS 先确认持久队列 ready 再发布 persistent 消息,mandatory 无 return 且 confirm 成功才记入队。D 离线时消息可积压;无队列时未成功入队的原消息由 SaaS 保留,就绪后按原身份重新发布,不能靠 D 重启倒灌。任务队列按 D+任务 ID 定位;租户身份保留原值用于核验和额度汇总。
**用户明确的消费规则:pause 保留积压,resume 消费原队列积压;stop 持久生效后静默消费并 ACK 所有未接纳积压,不拨号,不向 SaaS 发布这些消息的回执或通话结果。** 静默只限定已停止任务的未接纳命令:停止控制本身仍有处理回执;已接纳/在途通话按 drain/hangup 处理并保留其最终结果。D 的错误日志、停止状态和计数不静默,ACK 丢失后重投仍不拨号、不补发被抑制结果。
完成分三层:**C**:F01/F07 正式来源、版本、严格 Schema、正反例与验收约定签收;**L**:单 D 本地隔离闭环和故障矩阵通过;**M**:多 D 独占资源/份额为另行授权的后续。项目字段草案、本地 Mock 和旧测试不能代签 C 或真实供应商结果。
完成分三层:**C**:F01/F07 项目内来源、版本、严格 Schema、正反例、SHA-256 对齐并通过 Mock SaaS 端到端验证;不等待外部签收;**L**:单 D 本地隔离闭环和故障矩阵通过;**M**:多 D 独占资源/份额为另行授权的后续。Mock C 不代表真实 SaaS 或供应商验收。
## 2. 权威资料和读法
@@ -31,9 +31,10 @@
- management 是 SIP 唯一编辑/审批面,SaaS 只分发获批完整版本。SIP 变更先关准入、排空/对账,再核验实际加载;发现变化约 60 秒不等于加载完成约 60 秒。供应商 transport/auth/registration 不明不得给 real 猜默认值。
- 新 `call.execute.payload` 仅 `task_id/callee`;外呼信封仍有 `command_id` 防重复拨号。任务必须返回 `tenant_id/tenant_key`,D 核对消息与任务归属后再按 `tenant_id` 取额度,身份错配/缺失即拒新准入。任务配置不返回 `agent.content_sha256`;版本不可变,同版本异内容拒绝,已有执行持久快照不漂移。
- **最小路由约定(项目草案,F01 签收):**`allowed_trunk_ids` 按列表顺序作为候选优先级,选首条满足时段、已加载状态、供应商/线路额度且支持任务 `caller_profile_id` 的线路;无满足项不接纳。`caller_profile_id` 是新增任务级明确引用,必须命中选中线路的获批主叫,不默认取第一个主叫。`route_policy_id` 只标识这份任务路由,不依赖另一个未定义的查询接口。选定线路/主叫后持久绑定;拨号失败或未知不自动换线重拨。
- **最小路由约定(本地项目契约,F01 Schema/Mock 验证):**`allowed_trunk_ids` 按列表顺序作为候选优先级,选首条满足时段、已加载状态、供应商/线路额度且支持任务 `caller_profile_id` 的线路;无满足项不接纳。`caller_profile_id` 是新增任务级明确引用,必须命中选中线路的获批主叫,不默认取第一个主叫。`route_policy_id` 只标识这份任务路由,不依赖另一个未定义的查询接口。选定线路/主叫后持久绑定;拨号失败或未知不自动换线重拨。
- `ring_timeout_ms` 是任务级振铃上限;通话有效上限是任务 `max_call_duration_ms` 与已授权 AI `conversation.max_duration_ms` 的**较小值**,控制器和执行侧均使用同一值。两值不相同必须记录来源/有效值,不改写智能体原快照或悄悄放宽任何上限。没有逐呼 `variables` 的新合同不提供联系人变量来源;需要未提供变量的提示词不得以空字符串代替继续执行,须在 F01 明确静态提示词范围或补齐获批来源。
- SIP/任务配置约 60 秒缓存到期重取完整 `200`,不使用 ETag/304;过期/失败拒绝新准入,不影响既有执行快照。配置内 `status=running` **不是恢复许可**,状态按 §3.3 独立处理。
- F02 项目内 Agent 状态校验:使用受部署配置约束的 `agent_id/cell_id` 查询 Agent status;`applied_configs` 必须恰有一条 `kind="sip"`,且 `state="applied"`、十进制 `revision` 与 `config_sha256` 分别精确匹配获批 SIP artifact revision/SHA-256、`observed_at_unix_ms>0`,才可读取新配置/准入。缺失、重复、身份不符或匹配失败均 fail-closed。此为本地 Agent 协议规则,不是 SaaS/management 外部确认;Mock 状态不能证明真实 Asterisk 已加载。
### 3.2 租户额度
@@ -53,34 +54,35 @@ SaaS 先持久修改权威任务状态,再向独立 D 控制队列发布 `task
### 3.4 任务发现、发布与期限
D 启动/重启用 `/internal/v1/dispatcher/tasks` 拉一致全量快照和水位,运行中每 30 秒 `?after=<cursor>` 拉本 D 新增/更新/撤销。cursor 是变更水位,不是最大 task_id;完整分页连贯、持久应用成功才推进。游标失效、缺页、快照不一致时关闭受影响任务新准入并重拉全量。发现清单/任务响应必须提供 tenant_id/tenant_key 以便取额度。无变化不撤销消费关系。
D 启动/重启用 `/internal/v1/dispatcher/tasks` 拉一致全量快照和水位,运行中每 30 秒 `?after=<cursor>` 拉本 D 新增/更新/撤销。cursor 是不透明变更水位,不是最大 task_id。新版单次返回完整快照或完整变更集,最多 256 条且不分页;响应不带每个任务的 queue/key,D 按已约定的 D+task ID 命名规则推导并只消费 SaaS 预建队列。全量 `cursor`、增量 `next_cursor` 仅在完整校验和原子持久化后推进;空变更的 `next_cursor` 与请求 `after` 相同。`removed` 表示完成退役握手后的撤销归属,不等同于 stop;HTTP 410 `cursor_expired`(含增量超限)关闭新准入并重拉全量,禁止截断或跳过变更。发现清单/任务响应必须提供 tenant_id/tenant_key 以便取额度。无变化不撤销消费关系。
**队列 ready 不等于 D 已开始消费。** 首条任务可能等待轮询、分页、配置/额度读取和可用名额;30 秒是轮询间隔,不是端到端发现 SLA。SaaS 设置的 `not_after` 应覆盖允许的排队和准备时间,不能用固定 30 秒示例承诺离线恢复必拨。D 重启仅恢复原消息:未过期且有效的才接纳;过期明确拒绝(已 stopped 则静默 ACK),绝不顺延期限/自动造新 command_id 重拨。若要求更短首呼时限,必须在 F07 另定就绪通知协议,不能假设已存在。
**队列 ready 不等于 D 已开始消费。** 首条任务可能等待轮询、配置/额度读取和可用名额;30 秒是轮询间隔,不是端到端发现 SLA。SaaS 设置的 `not_after` 应覆盖允许的排队和准备时间,不能用固定 30 秒示例承诺离线恢复必拨。D 重启仅恢复原消息:未过期且有效的才接纳;过期明确拒绝(已 stopped 则静默 ACK),绝不顺延期限/自动造新 command_id 重拨。若要求更短首呼时限,必须在 F07 另定就绪通知协议,不能假设已存在。
SaaS 清单须保留已 stopped 但未排空任务;停止新发布、积压/未 ACK 排空、无未知执行并完成双方约定的退役核验后,才撤销任务归属/删除队列。该核验不靠逐条停止回执,队列空不代表无在途通话;撤销握手待 F07 冻结。D 不创建/删除队列;任务 ID 唯一性、key 长度/字符、队列总数上限一并冻结。
SaaS 清单须保留已 stopped 但未排空任务;撤销握手按第三方契约执行:SaaS 停止发布,D 持久 stop 屏障、退回/排空未接纳积压、确认无未 ACK 且队列为空后回执,SaaS 收到后删除队列并发送 removed。D 不创建/删除队列;task_id 为 `[A-Za-z0-9_-]{1,128}`,每 D 最多 256 个未退役任务队列,routing key/queue name 上限见 MQ topology。
### 3.5 最终结果与无录音
保留呼叫/控制回执及一种最终 `call.result`,不保留对外 query/replay、实时文字/拒联/录音拆分事件。用户已选择仅最终反馈;取消即时拒联的产品风险与现有验收冲突须正式业务签收。最终结果的身份在本地持久化,MQ 重投仍是同一份事实,不重新拨号或上传。
保留呼叫/控制回执及一种最终 `call.result`,不保留对外 query/replay、实时文字/拒联/录音拆分事件。用户已批准仅最终反馈,本轮实现与验收按此更新;真实 SaaS 消费兼容性另行验证。最终结果的身份在本地持久化,MQ 重投仍是同一份事实,不重新拨号或上传。
- 已产生录音:上传 OSS 成功后 `recording.status=uploaded`、携带路径;上传永久失败/到有限期限后 `unavailable`、明确错误与 null 路径。具体期限是 F07 阻塞项,不无限等待。
- 已产生录音:上传 OSS 成功后 `recording.status=uploaded`、携带路径;上传永久失败/到有限期限后 `unavailable`、明确错误与 null 路径。明确上传/授权失败立即以 unavailable 收口,无确定结果最晚于 `call.ended_at + 15m` 收口。
- 通话已尝试但忙线/无应答等**未产生录音**:终结已确认后即可发唯一结果,`recording.status=not_created`、身份/路径等资产值为 null,原因如 `no_answer`;不申请上传、不假称上传失败。录音本应生成却失败用 `unavailable` 和明确阶段原因,不伪装正常无录音。
- 未接纳且被停止屏障吞掉:无命令回执、无最终结果;其他未接纳拒绝只有命令回执,不能伪造通话。已接纳取消/失败/挂断仍按其事实形成最终结果。
- `outcome` 反映通话本身,录音错误写 recording 不改成呼叫失败。通话额度释放与上传/通知独立;日志只保留脱敏事实,不输出完整音频/文本/密钥。
- 最终文字合并后的消息大小与现行 MQ 上限可能冲突,F07 必须冻结可容纳的正文上限和明确失败策略,不截断完整转写、不拆成用户已移除的实时事件来掩盖。
- 最终文字合并后的消息大小与现行 MQ 上限可能冲突,项目内 JSON 正文上限为 8,388,608 bytes,超限保留 outbox 并显式阻塞,不截断完整转写、不拆成用户已移除的实时事件来掩盖。
## 4. 依赖顺序与工作包
**执行顺序:F00 → F01(HTTP/配置/额度)与 F07(MQ/发现/控制/结果)并行冻结 → 联合一致性签收 C → F02 → F03 → F04 → F08 → F09 → F06。F05 是另获授权的后续。** F01 不再要求先完成 F07 的 MQ 改写;二者共同签收后才能进入新路径实现。F03 单独负责新队列消费/状态屏障,F08 只集成结果交付和受控切换,不重复开发同一消费路径。
**执行顺序:F00 → F01(HTTP/配置/额度)与 F07(MQ/发现/控制/结果)并行冻结 → 项目内 Mock C → F02 → F03 → F04 → F08 → F09 → F06。F05 是另获授权的后续。** F01/F07 可按各自边界并行;严格 Schema、正反例与来源/hash 对齐,并由 Mock SaaS 端到端通过后即为本地 C,不等待外部签收/连通。F03 单独负责新队列消费/状态屏障,F08 只集成结果交付和受控切换,不重复开发同一消费路径。
| 工作包 | 输入、责任与交付 | 门禁 |
| --- | --- | --- |
| F00 草案整理 | 集成负责人同步本文、分项设计、对接文档、配置 Schema/示例;标明用户给定路径与项目新增额度/路由字段。 | 正反例可验证,不冒充 SaaS 现网。 |
| F01 HTTP 与配置/额度合同 | SaaS/management 签收四条 GET、两 Header、身份映射、任务主叫/候选优先级、两时限较小值、授权/缓存/配置不可变及租户额度份额/版本/有效期。 | 严格 Schema、来源/版本/哈希、正反例;静态提示词或变量来源明确。外部未签收则 blocked。 |
| F07 MQ、发现及控制/结果合同 | 与 F01 并行:SaaS 签收任务发现分页/水位、先建后发、队列生命周期、精简 call.execute、无编号控制、pause保留/resume续消费/stop静默排空、状态优先级、回执/最终结果及无录音;联合核验 HTTP 与 MQ 相同身份/版本。 | 新版本权威包及业务签收 C;撤销旧 query/replay/配置MQ/拆分事件;未决结果期限/消息大小/退役握手全部冻结。 |
| F02 配置与额度读取(C 后) | SaaS 提供四接口;D 读取并校验 SIP 实际加载、任务租户映射、按 tenant_id 的额度,完整 200 缓存;有期限拒绝与持久快照。 | 断 SaaS、降额、同租户多任务、过期/错身份、新旧配置竞态不超额/不漂移。 |
| F03 消费/发现/控制(F02 后) | D 移除新协议 `--tenant-key` 自建租户队列路径,按 SaaS 已建任务队列消费;发现游标/暂停恢复/停止静默ACK/独立控制与原子额度;未接纳仍留 MQ。 | §5 的队列/控制矩阵通过;D 无建队权限仍可工作,stop静默但可排查。 |
| F04 时段及最后拨号屏障(F03 后) | 任务×线路时段、排除日期、白名单、有效期和已绑定时限在 D/执行侧一致,最后许可与控制串行判定。 | 窗口边界/跨日/更新/暂停竞态无错误拨号,旧固定窗只在合同/验收批准后替换。 |
| F01 HTTP 与配置/额度合同 | 按第三方文档冻结四条 GET、两 Header、身份映射、任务主叫/候选优先级、两时限较小值、授权/缓存/配置不可变及租户额度份额/版本/有效期。 | 项目内严格 Schema、来源/版本/哈希、正反例;静态提示词或变量来源明确;Mock 验证,不等待外部签收。 |
| F07 MQ、发现及控制/结果合同 | 历史 F07 先冻结 v0.1 分页发现;仅任务发现随后由 F03 v0.2 单次响应修订取代。其余 SaaS 独占队列生命周期、v3 routing、精简 call.execute、无编号控制、pause/resume/stop、回执/唯一最终结果、录音边界及消息大小/期限维持原合同。 | 项目内严格 Schema、拓扑、正反例、来源/hash;与 F01 一致并由 Mock 端到端验证后 C 通过。移除旧 query/replay/配置 MQ/拆分事件;外部兼容另记未验证。 |
| F02 配置与额度读取(C 后) | 接入四条已冻结 GET(本地先由 Mock 提供);D 读取并校验 SIP 实际加载、任务租户映射、按 tenant_id 的额度,完整 200 缓存;有期限拒绝与持久快照。 | 断服务、降额、同租户多任务、过期/错身份、新旧配置竞态不超额/不漂移;真实 SaaS 未联调不冒称通过。 |
| F03 消费/发现/控制(F02 后) | D 移除旧 `--tenant-key` 自建租户队列路径,按 SaaS 已建任务队列消费;发现游标/暂停恢复/停止静默ACK/独立控制与原子额度;未接纳仍留 MQ。旧分页发现结果仅属历史。 | §5 的队列/控制矩阵通过;D 无建队权限仍可工作,stop静默但可排查。 |
| F03 v0.2 任务发现修订(F04 前) | 每 D 单次完整响应≤256,正文无 queue/key;失效游标 410 恢复全量,空变更原游标,退役 `removed`;完整响应、任务归属、游标原子持久化,发现失败关准入/停任务消费但不中断控制。 | v0.2 Schema/示例/hash 与 Mock/本地 RabbitMQ 再验;只运行新协议,不把旧 F03 证据当作新版通过。 |
| F04 时段及最后拨号屏障(F03 后) | 仅 Dispatcher 在持久接纳与发出呼出指令前复核任务×已绑定线路时段、任务排除日期、白名单、有效期及任务/AI 时限,持久化唯一签发/拒绝决定;Agent 只核验会话及 Dispatcher 签发期限。 | 本地 Mock 验证窗口边界/跨日/更新/暂停竞态不拨号、不等待或自动重试;真实路径固定时段只在外部合同、F09/F06 验收及授权后替换。 |
| F08 结果与受控切换(F04 后) | 组装 uploaded/unavailable/not_created 的最终结果与既有命令/控制回执;通话释放独立于上传;旧新版本互斥,恢复原身份 outbox。 | 本地全流程无重复拨号/PUT,停止积压不产生回传,已有执行结果不丢失。 |
| F09 本地验收(F08 后) | 集成负责人运行 §5 全矩阵;单 D 单租户多任务为 L,扩展 fixture 标为非 P1。 | gofmt、`go vet ./...`、`go test -race ./...`、`go build ./...`、本模块覆盖率≥65%,脱敏证据。 |
| F06 发布/切换(F09 后) | 单写 §8 状态,核对合同/制品/队列实际版本;真实 SaaS/OSS/云/线路另外授权。 | 旧本地证据不代签;回退不能并行旧新写入/额度或引起重拨。 |
@@ -98,29 +100,35 @@ SaaS 清单须保留已 stopped 但未排空任务;停止新发布、积压/
| stop 静默 | 未接纳积压100条、在途2通、stop;ACK丢失/重启/额度0 | 100条最终ACK,originate=0、新回执/最终结果outbox=0;本地计数可核查。stop控制有回执,2通按策略终结并各有最终结果;其他任务不受影响。 |
| 状态优先级 | pause/stop后收到旧running快照或低版本tasks;旧resume迟到/控制重复/HTTP断连 | 不自动恢复;resume未获最新running/未过屏障即明确失败;stopped不可逆。控制不按消息身份去重,不保证只回一次;回执不明不当成功。 |
| 新任务/离线期限 | 刚过轮询点新建队列;离线超过not_after;首条期限不足准备时间 | 消息先入已建队列;恢复只消费仍有效者,过期不拨不顺延,不自动新建外呼命令;30秒只是发请求周期。 |
| 发现/退役 | 较小ID任务变更、分页缺页/重复、游标失效、SaaS创建前误发、stopped仍有积压 | 不漏旧任务变更;重拉一致全量不越过未应用页;未入队原消息由SaaS保留;stopped保留清单直到静默排空,无未ACK/未知执行再退役。 |
| 发现/退役 | 较小ID任务变更、空变更、重复/超过256条、游标失效、removed 退役、SaaS创建前误发、stopped仍有积压 | 不漏旧任务变更;不接收分页或响应内队列字段,不截断超限结果,失效后重拉全量且只在原子持久化后推进游标;未入队原消息由SaaS保留;stopped保留清单直到静默排空,无未ACK/未知执行再退役。 |
| 队列权限/配额 | D禁止create/bind/delete、队列满、SQLite满盘、租户满额 | D只消费,不补建;SaaS保留未成功发布消息;事务失败不ACK已接纳;不会搬全部积压进SQLite,不阻塞控制/stop清理。 |
| 无录音/失败 | 无应答/忙线且无录音;应有录音却失败;OSS超时;停止未接纳命令 | 正常无录音not_created且不等待上传;故障unavailable有阶段原因并在签收期限内最终收口;停止未接纳无回传;不能伪造call_id/成功路径。 |
| 释放与结果 | 已确认结束但上传慢、MQ confirm丢失或重启、完整转写超过预算 | 通话额度已释放、未知仍占额;原结果身份重投不重拨/PUT;按签收的大小/失败合同处理,不静默截断或假成功。 |
| 无录音/失败 | 无应答/忙线且无录音;应有录音却失败;OSS超时;停止未接纳命令 | 正常无录音not_created且不等待上传;明确授权/PUT失败立即unavailable,未知最晚15分钟收口;停止未接纳无回传;不能伪造call_id/成功路径。 |
| 释放与结果 | 已确认结束但上传慢、MQ confirm丢失或重启、完整转写超过8 MiB | 通话额度已释放、未知仍占额;原结果身份重投不重拨/PUT;超限 outbox 显式阻塞,不截断/拆分/丢弃。 |
| 时间及真实边界 | 任务/线路交集、空日、排除日、左闭右开、最后许可前控制到达 | 不跨窗或被暂停/停止后新拨,不等待次日自动执行;Mock时钟不等于real放行。 |
## 6. 写入与实施边界
项目设计在自身 docs;现行 Schema 保持不动,新合同先签收再导入唯一版本化来源,不手工维护两套发布 Schema。本文/分项/guide/字段提案必须同步;源码、截图、真实凭据、无关父仓库不改动。TDD 先失败后实施,复用现有库,禁止隐藏错误、HTTP/MQ配置回退、重复外呼。大改另开分支,未获得子 Agent 授权由当前负责人执行。
项目设计与本地目标契约在自身 docs 维护。`contracts/upstream/v1` 保持现行外部运行基线,不擅改;原 v0.1 本地合同与 F03 证据保留为历史;`docs/thirds/v0.2.md` §2.5 中任务发现单次响应已作为**本地运行修订合同**完成实现和 Mock 校验,独立 v0.2 Schema、正反例和来源哈希见新版 F03 证据;这不代表真实 SaaS 已发布或签收。其他业务合同沿用已有本地 v0.1 机器校验件,须记录规范文件与机器文件 SHA-256。该本地契约不冒称 SaaS/management 已发布,外部签收和连通不阻塞本地 C。本文/分项/guide/字段提案必须同步;源码、截图、真实凭据、无关父仓库不改动。TDD 先失败后实施,复用现有库,禁止隐藏错误、HTTP/MQ配置回退、重复外呼。大改另开分支,未获得子 Agent 授权由当前负责人执行。
## 7. I/M/G 前置
**I**:用户方向确认不等于 SaaS/management 权威签收。四接口、租户份额、路由/控制优先级、新消息 Schema、结果失败期限及退役握手未完成联合 C 前不实现新生产路径。**M**:旧 W 的通过不能代替新 F02–F09;仅本地隔离验证不代表真线路/OSS通过。**G**:部署版本、网络/ECS只读核验、SHA-256、systemd enabled+active、Asterisk/ARI/PJSIP、媒体监听和非生产诊断门禁全部保留;实际云/生产/拨号另获授权。
**I**:本地实施按本文件与第三方对接文档冻结的项目内契约;F01/F07 的严格 Schema、正反例、来源/hash 对齐以及 Mock SaaS 端到端 C 通过后即可进入 F02,不等待 SaaS/management 外部签收或连通。**M**:旧 W 的通过不能代替新 F02–F09;仅本地隔离验证不代表真线路/OSS通过。**G**:部署版本、网络/ECS只读核验、SHA-256、systemd enabled+active、Asterisk/ARI/PJSIP、媒体监听和非生产诊断门禁全部保留;实际云/生产/拨号另获授权。
## 8. 当前事实总台账
| 项目 | 当前事实 | 下一门禁 |
| --- | --- | --- |
| 历史计划 | [plan-0918](archive/plan-0918.md)保留原文,只作历史。 | 不回写旧W台账。 |
| F00 | 用户确认按tenant_id另取租户额度、pause保留/resume续消费、stop静默排空;相关文档/项目 Schema 已修订,非现网合同。 | 正反例与文档一致性检查、外部签收。 |
| F01/F07 | 三条原路径/Header用户已指定;新增租户额度路径/字段、主叫/候选规则、控制优先级和无录音结构为本轮项目提案。 | 两合同包并行冻结后联合 C,不互设循环依赖。 |
| F02–F04/F08 | 新路径未实现;现行代码仍是旧 MQ-only/静态租户队列等基线。 | C 通过,逐包 TDD/故障注入。 |
| F09/F06 | 新验收及切换未执行,旧测试不代签。 | L证据与授权,真实联调另行安排。 |
| F00 | 已对齐计划、分项设计、第三方对接、字段提案、架构/验收入口;三条用户给定路径与新增租户额度路径、项目路由字段来源均有标识,均属项目内目标,非现网合同。 | Draft 2020-12 本地校验:4 个有效响应示例通过,1 个额外字段非法样例按预期拒绝;文档链接检查通过。下一门禁为 F01/F07 项目内 Schema/正反例/来源哈希冻结及 Mock C。 |
| F01 | 四条只读 GET 的身份/归属、路由/主叫、缓存/版本、额度/有效期已按第三方对接文档冻结为项目内规则;`resource_not_found`/租户额度等 HTTP 错误映射有正向 fixture。`contracts/upstream/v1` 仍是现行外部基线,真实兼容性未验证。 | `python3 scripts/validate-local-contracts.py` 通过:4 个本地 Schema、17 个配置读取正向例、HTTP 状态映射及负例;manifest SHA-256 校验通过。C 通过见下行;不代表真实 SaaS 兼容性。 |
| F07 | 项目内任务发现/队列所有权与生命周期、MQ v3 控制/回执、单份 `call.result`、大小/期限/退役规则已按第三方文档冻结;本地正反例已校验。`contracts/upstream/v1` 仍是现行外部基线,真实兼容性未验证。 | `python3 scripts/validate-local-contracts.py` 通过:发现 13 个正例、命令 6 个、结果 4 个,各 Schema 负例均拒绝;source 与 18 项产物共 19 个 SHA-256 文件通过。F03 运行时本地交付见下行;外部兼容性仍未验证。 |
| C | 本地 Mock SaaS 通过四条只读 GET、`call.execute`、`command.result` 与 synthetic `call.result` 的 Dispatcher 端到端交互;缺少 result queue 时 outbox 保留并在恢复后交付。 | 证据:[`local-mock-saas-c-v0.1.md`](evidence/local-mock-saas-c-v0.1.md)。契约验证、包测试及 `bash scripts/mq-integration-local.sh` 通过。仅属项目内 Mock;没有真实执行、拨号、录音上传或外部验收。 |
| F02 | 本地配置与额度读取模块完成:四条 GET 严格校验,四份完整响应按版本/hash/有效期持久化,任务/租户身份绑定,task+tenant 配额与 call.execute reservation 原子接纳;SIP Agent applied-config 核验、断连/过期/不匹配 fail-closed、同租户多任务和配置竞态均有 Mock 覆盖。F02 涉及四包覆盖率为 65.1%–75.0%。 | 证据:[`f02-local-config-read-v0.1.md`](evidence/f02-local-config-read-v0.1.md)。包测试、race、`go test ./...`、vet、build 和契约校验通过。F09 按用户确认的本轮 P1 业务模块 ≥65% 口径另验;CLI/历史包低覆盖率单列;F03 应用启动/消费现有本地证据见下行,真实 SaaS/Asterisk 加载仍未验证。 |
| F03(历史分页证据) | 单 D/单租户 v3 运行时曾在本地交付:只被动消费 SaaS 预建队列,持久发现游标/任务归属,暂停保留积压,恢复使用原队列,停止先持久屏障与权威状态、后静默排空并 ACK,排空完成后才写控制回执。RabbitMQ ACL Mock 证明 Dispatcher 无 `configure` 权限仍可消费、排空和发布;旧 tenant-queue consumer、旧 per-call control worker、CLI 旧消费参数及 MQ-only v1 acceptance runner 已移除。 | 证据:[`f03-local-task-runtime-v0.1.md`](evidence/f03-local-task-runtime-v0.1.md)。本地 acceptance、Race、vet、build、契约与无 `configure` 权限的 RabbitMQ 集成测试通过;`internal/dispatcher` 覆盖率 66.2%。仅是本地隔离验证,外部 SaaS/management、云、线路和生产均未验证;F09 新版本地矩阵见下行,历史分页证据不代签。 |\n| F03 v0.2 修订 | 本地运行已采用单次完整发现:每 D 最多 256 个归属任务、响应不含队列地址;队列名按 D+task ID 推导且只能被动消费 SaaS 预建队列。空变更保持 `next_cursor`,过期游标 HTTP 410 重拉全量,`removed` 退役,超额拒绝且不推进游标;发现故障关闭新准入/停任务消费但保留 MQ 控制。旧 F03 v0.1 证据只作历史。 | 证据:[`f03-local-task-discovery-v0.2.md`](evidence/f03-local-task-discovery-v0.2.md)。新版 Schema/正反例/hash 与本地 Mock、race、vet、构建和无 `configure` 的 RabbitMQ 测试通过;F09 本地全矩阵/覆盖率见下行,真实 SaaS/management/拨号/生产未验证。 |
| F04 | 本地隔离 Mock 已将任务×已选线路时段、任务排除日期、白名单/有效期、绑定快照及任务/AI 较小通话时限接入接纳与发起前门禁;执行身份只有一次持久 `issued`/`refused` 决定,队列消费后由 D 经版本化 Unary 向 Mock Agent 下发,Agent 不重算业务策略、不发送 SIP。V3 mixed/real 启动拒绝,原真实固定门禁未放宽。 | 证据:[`f04-local-dial-policy-v0.1.md`](evidence/f04-local-dial-policy-v0.1.md)。Proto/合同、本地 Mock、race、vet、build、RabbitMQ 无 `configure` 权限及 `scripts/acceptance-local.sh` 通过;外部合同/真实拨号未验证。F08 后续本地结果见 [`f08-local-final-result-v0.1.md`](evidence/f08-local-final-result-v0.1.md);F09 本地矩阵与覆盖率见 [`f09-local-acceptance-v0.2.md`](evidence/f09-local-acceptance-v0.2.md)。 |
| F08 | 单份最终 `call.result`、确认终态后独立释放额度、uploaded/unavailable/not_created、本地 Agent→D 录音失败事实与原身份 outbox 已完成隔离 Mock 验证;V3 Mock 不并行写旧分散事件,V3 mixed/real 启动拒绝。证据:[`f08-local-final-result-v0.1.md`](evidence/f08-local-final-result-v0.1.md)。 | `scripts/acceptance-local.sh`(含 race、vet、构建及受限 RabbitMQ)通过;Mock 录音成功/失败由合成事实注入,默认 Mock 只模拟无应答,无实际媒体/OSS PUT,publisher confirm 不等于 SaaS 应用签收。F09 本地验证见 [`f09-local-acceptance-v0.2.md`](evidence/f09-local-acceptance-v0.2.md);F06 真实部署/切换仍未获授权。 |
| F09 | 任务发现 v0.2 的 ≤256 单次完整返回、空变更/removed/410 恢复及计划 §5 单节点/单租户适用矩阵均已重新进行本地 Mock 验证;stop 前两条已发起执行的 Agent 归属与一次性拨号决定现由同一事务持久化,stop 可对两条发 hangup 且旧执行仍返回结果。证据:[`f09-local-acceptance-v0.2.md`](evidence/f09-local-acceptance-v0.2.md)。 | `scripts/acceptance-local.sh`(格式/合同/Proto/hash、race、vet、构建及受限 RabbitMQ)通过;本轮 P1 业务模块均 ≥65%,MQ 仅带 integration tag 时为 66.4%。CLI 29.6% 和历史 callruntime 61.7% 未达线,按用户确认口径单列技术债,**不声称全仓达标**。队列满真实 RabbitMQ Mock 拒绝;SQLite 满盘仅作写入错误注入,未物理填盘。外部/真实验收未完成。 |
| F06 | 本地发布准备与切换阻断已核对:Go 1.27.1 一次性制品/校验和、v0.1 其他协议+v0.2 任务发现+项目 v3 MQ 队列及 Proto manifest 的版本/hash 写入制品清单;已有 release/package 输出及归档拒绝覆盖,mixed/real V3 执行在触碰 DB/MQ 前 fail-closed,旧消费 CLI 和旧分散事件不能并行写入新路径。证据:[`f06-local-release-gates-v0.2.md`](evidence/f06-local-release-gates-v0.2.md)。 | `make release-check-local` 与本地 acceptance 通过;来源 dirty,制品仅 local-development、`production_approval=false`。未获真实部署/拨号/切换授权,旧 Python 资源移交、ECS/systemd/Asterisk 实际加载和生产签收均**未执行**;本地阻断不代替真实切换。 |
| F05 | 多D/第二租户不在本轮运行范围。 | 另获阶段与额度/资源授权。 |
## 9. 协作与版本记录