35 KiB
SaaS↔Dispatcher 项目内实施证据
改动前基线
- 基线提交:
f5c2d6a92036a579e0b070beb1d9381b0976f81e;执行分支:feat/saas-dispatcher-contract;开始前已拉取并核对origin/main,无待同步提交;Go 1.27.1。 - 原工作树已有用户修改:
docs/thirds/v0.4.md、docs/thirds/第三方对接事件与请求消费顺序_v0.1.md;未跟踪:docs/plan-saas-dispatcher-v05-v0.1.md、docs/thirds/v0.5-proposal.md、docs/.plan-saas-dispatcher-v05-v0.1.md.swp。这些内容不得作为已提交基线,不能静默删除、暂存或覆盖;历史来源在改名时逐一核验。 - 全仓路径初筛发现 182 个代次式候选文件(包括历史证据/第三方合法版本,不是 182 个可删除目标);源码、契约与脚本约 119 个候选。去后缀会发生实存碰撞:
internal/mq/amqp_v3.go→internal/mq/amqp.go;历史证据文件也有碰撞,历史证据不能按文件名自动改写。 - 尚未验收源码、契约、Proto 及数据兼容;此处只记录实施前状态,不构成 P01–P08 通过证明。
路径/符号映射与冲突处置(实施时逐项勾稽)
| 旧路径/符号 | 目标职责与处置 |
|---|---|
internal/mq/amqp.go 的旧声明/租户队列 Broker 与 internal/mq/amqp_v3.go 的预建队列 Broker |
保留预建队列单一路径,合并必要发布/错误处理后归入 internal/mq/amqp.go 的 Broker;删除旧运行入口,不能直接覆盖。 |
internal/configread/discovery_v04.go |
internal/configread/discovery.go,重做新 cursor 语义,删除旧 snapshot/watermark 路径。 |
internal/dispatcher/task_queue_v3.go / task_control_v3.go / task_runtime_v3.go |
task_queue.go / task_control.go / task_runtime.go,类型按职责命名,不保留 V3/LocalV01 别名。 |
internal/dispatcher/call_result_v01.go / local_v01.go |
call_result.go / local.go;只留已确认的对外事件。 |
proto/agent/v1/agent.proto / gen/agent/v1/ |
proto/agent/agent.proto / gen/agent/,同步生成配置、Go 导入与服务名,重新生成而非手改生成物。 |
contracts/local/v0.x/、docs/contracts/*-v0.x* 与按代次加载/校验器 |
各领域只有一份无代次当前 Schema、示例及来源/hash;历史来源只能归档,不参与运行校验。 |
internal/store/migrations/*_local_v0x_*.sql |
保留有意义的迁移序号并更换实现代次名;先验证现存数据库的迁移跟踪/未交付状态,不能以改名重跑破坏数据。 |
docs/plan-saas-dispatcher-v05-v0.1.md / docs/thirds/v0.5-proposal.md |
分别成为 docs/plan-saas-dispatcher.md / docs/thirds/saas-dispatcher.md;既有未跟踪内容须先核对,链接/哈希同步更新。 |
P01:项目内当前通信契约
- 新增
contracts/local/config-read.schema.json、task-discovery.schema.json、mq.schema.json、mq-topology.json与 30 份正反例,manifest 覆盖 34 个当前 JSON 文件;contracts.ReadCurrent只接受这五个当前文件,无旧目录回退。 docs/thirds/saas-dispatcher.md记录五类 HTTP、入站/出站事件、队列归属/共享结果队列及 K01–K16 的业务边界;数字 tenant_id、原值 credential、ASR-only、revision、空录音、真实 outcome/reason_code、48h 失败留待人工等均有字段/行为约定。机器校验拒绝字符串租户、旧 schema_version/credential_ref/call_id、未知 recording 状态、非法控制 policy。- 测试:
bash scripts/check-current-contracts.sh、go test ./internal/contract ./contracts -count=1、git diff --check均通过;当前正例未出现旧配置或通话身份字段。来源/内容 SHA-256 在 manifest 中可重算。这是当前合同文件的本地校验,不代表 P03–P08 运行行为、真实 SaaS 或旧合同清理通过。
P02:Proto/生成物分批改造(未整体签收)
-
proto/agent/v1/agent.proto→proto/agent/agent.proto,包agent;gen/agent/v1/→gen/agent/,源码 79 处导入/标识机械更新;旧生成路径已删除。已有RequestMeta.protocol_version=agent.v1是有意义的线协议元数据,没有为了去名改动字段或重编号。 -
原 Buf STANDARD 的
PACKAGE_VERSION_SUFFIX与已批准的无代次内部包名冲突;buf.yaml仅对这一条规则作例外,其余 STANDARD 规则保持。隔离安装 Buf v1.50.0 / protoc-gen-go v1.36.12 / protoc-gen-go-grpc v1.5.1 于/tmp/sip-go-agent-tools/bin,未改应用依赖。 -
PATH=/tmp/sip-go-agent-tools/bin:$PATH sh scripts/check-proto.sh通过(lint/build/generate/新包测试/7 文件清单 hash);git diff --check、go test ./internal/rpc ./internal/agent ./internal/dispatcher ./cmd/sip-go-agent -count=1通过。其他旧实现/契约入口、文件名碰撞和迁移数据安全仍待 P02/P07,不能据此称全仓已无代次或整体完成。 -
新增无实现代次字段的 Dispatcher 环境预检:必须显式给出规范 UUID v4 身份、只读 HTTP 地址及密钥、RabbitMQ 地址和 SQLite 路径;当前 Mock 仅接受本机 HTTP/MQ 目标,mixed/real 在任何资源操作前拒绝。预检不打开数据库或网络,缺失值不继承旧默认配置,错误不打印凭据。
go test ./internal/config -run '^TestLoadDispatcherEnvironment' -count=1通过;该预检已接入根dispatcher命令;隔离启动不代表 P02/P03 整体验收。 -
Dispatcher 另有纯运行环境预检:唯一 Agent 端点清单和 OSS 配置文件须提供路径,本机 gRPC 监听及双向 TLS 文件/Agent 证书指纹须显式给出;缺失、非法指纹或非本机监听明确拒绝且不回显值。该检查不读取配置文件、不连接 Agent、不打开 SQLite/MQ;
go test ./internal/config -run '^TestLoadDispatcherRuntimeEnvironment' -count=1通过。该检查现由根dispatcher命令调用;本机 Mock 不能作为真实 Agent 加载或 OSS 签发的验收。 -
D 的部署文件读取已独立于旧代次 Schema:Agent 清单仅接受一个本机端点;OSS 配置必须匹配本 D 身份、指定本机 HTTPS 目标、15 分钟授权与显式资产上限,凭据只经受控环境变量引用。超大文件、重复/未知字段、多个 Agent、非本机目标、缺失凭据和错误 D 归属均拒绝;JSON 唯一键及凭据引用基础函数已移出旧配置文件。
go test ./internal/config -count=1通过。此处仅验证部署数据,不代表 Agent 实际加载、官方 SDK 已签发目标,主dispatcher命令现已读取这些部署文件;仅隔离 Mock 完成启动,未验证真实目标。 -
独立的隔离 D 命令函数现已组装严格部署预检、当前 HTTP 读取、
store.OpenCurrent、被动核验 SaaS 预建拓扑的mq.OpenCurrent、D 身份绑定的 Agent 会话、批准执行/控制、实际加载版本检查、CurrentRuntime和录音双向 TLS RPC;启动开库后先持久关闭新准入,不清理旧状态或擅自建队。负例测试确认 mixed/real、旧--config与非本机 Agent/错误 OSS 归属在打开 SQLite 前拒绝。根dispatcher命令已切换;隔离脚本用临时本机 RabbitMQ、Mock HTTP 和本机双向 TLS 模拟 Agent 服务验证启动、读取与取消退出,未覆盖有任务的主进程执行、录音、MQ 最终结果或外部验收。 -
D 的 Agent 会话激活现在必须显式携带规范 UUID v4 的本 D 身份,并写入 Agent 授权绑定;预配置了对应 D 身份的隔离 Agent 会拒绝错误归属,不能再靠 D 自报空身份放行。
go test ./internal/dispatcher -count=1通过;主dispatcher命令已通过本机模拟 Agent 的探测与激活;D↔A 执行仍未在主进程联测。 -
D 的已授权调用元数据现在只从该 Agent 最新且未过期的会话产生,每次生成不同操作身份;未注册、会话缺失或过期均明确拒绝。
TestAgentCoordinatorProbesBeforeActivation覆盖这些边界,不能代替实际 D 主进程的执行与录音联测。 -
Agent 增加独立的无代次环境预检:Agent/Cell 与预授权 D 的身份、会话与私有恢复路径、隔离 Mock 场景和已加载 SIP 测试事实、D gRPC 目标及 mTLS 文件/指纹均须显式提供;仅允许本机监听和本机 D 端点,mixed/real 在访问文件或网络前拒绝。缺失项不继承旧
FromEnv默认值,凭据/地址不回显;go test ./internal/config -run '^TestLoadAgentEnvironment' -count=1通过。此检查已接入根命令可达的唯一 Agent Mock 入口;本机双向 TLS 监听可由受信 D 证书探测,另一张同 CA 证书被指纹门禁拒绝。本地测试用批准的 D 身份激活会话,错误 D UUID 即使携带受信证书也在写入会话日志前拒绝;尚未执行呼叫、验证 Agent→D 录音或真实 Asterisk 加载。 -
会话栅栏修复:Agent 重启后
session_generation=0由持久化代际高水位递增,旧显式代际仍拒绝,代际耗尽明确阻断;D 每次新激活产生独立操作身份,同 boot/epoch 的下一次激活不再误回放旧会话。隔离重启/重复激活测试通过,未作为主 Dispatcher 进程重启或真实部署证据。
P03:HTTP 读取分批改造(未整体签收)
contract.ValidateCurrent与configread按当前 Schema 读取 SIP、provider、task、quota 和 cursor 任务发现;严格检查数字 tenant_id、本 D 归属及不可变配置。provider 凭据原值保留在持久快照并经受信 Agent RPC 交付,不写入日志或证据;Agent 参数中的显式 0/false 保真;无旧 Schema/旧配置回退。- CLI 组装辅助
dispatcherConfigurationClient将严格 Mock 环境预检与当前 HTTP Client 绑定;本机 HTTP 隔离测试实际读取 SIP,核对固定/internal/v1/dispatcher/sip、D 身份/密钥 Header、revision 与不打开 SQLite;旧DISPATCHER_CONFIG_READ_BASE_URL不可充当缺失的当前地址。go test ./cmd/sip-go-agent -run '^TestDispatcherConfigurationClient' -count=1通过;主dispatcher命令已通过该辅助读取本机 Mock;真实 SaaS 的授权和连通尚未验证。 store.OpenCurrent新建数字租户 SQLite 状态;旧表、旧版当前布局、残缺布局均在写入前拒绝并保留原记录;不执行旧数据迁移或自动清理。启动时完整发现同一快照一次提交,分页增量逐页持久提交后才推进内存 cursor;失败关闭准入,重启重新取完整快照。HTTP 的旧 running 不能解除 MQ 暂停/终止,同 revision 异内容及跨任务 SIP/租户额度冲突拒绝。CurrentBootstrap先关闭准入,核验 SIP 全量与 Agent/Asterisk 已加载 revision、读取任务和 provider/额度,再排空 MQ 控制积压,最后依据已验证 SIP revision 开准入;有更新的持久 SIP 通知时保持关闭但控制与结果处理仍可继续。CurrentDiscoveryFollower逐页绑定任务快照;HTTP 错误、失效或授权不一致只失败,不回退旧读取。根dispatcher命令已接入该启动链路;本机隔离测试实际读取 SIP、分页发现、任务、provider 与租户额度,核验模拟 Agent 已加载 revision 后订阅 SaaS 预建任务队列。主进程实际呼叫、录音与结果交付,以及真实 Agent/Asterisk 加载仍未验证。- HTTP 失败回归覆盖 SIP、provider、task、tenant quota 四个读取端点:已有成功响应后收到 503 必须失败且保留 HTTP 状态与资源归属,不复用旧响应或泄漏受控 Header。任务发现同样在后续 503 时拒绝旧页;
TestReadCurrentTasksFailsOnUnavailableRefreshWithoutStaleFallback先暴露缺少错误归属,修复后保留HTTPError及task discovery上下文。根命令的预建队列、完整 HTTP 读取与模拟 Agent 激活由bash scripts/check-current-mq-mock.sh隔离验证。 - TDD 与回归:
go test ./internal/configread ./internal/tenant ./internal/store ./internal/dispatcher -count=1、bash scripts/check-current-contracts.sh、已提交a0118e3的干净归档测试通过;旧布局行/表原样保留由TestCurrentStoreRefusesPreviousCurrentLayoutBeforeModifyingDatabase覆盖。
P04:隔离 MQ、控制与外呼接纳(仅项目内 Mock)
- 固定
v1精确路由与 SaaS 共享结果队列;各 D 控制/任务队列均由 SaaS 预建,D 仅被动检查和消费。隔离 RabbitMQ 实测无configure权限、D1/D2 不串收、shared queue 实际收讫、断绑后 mandatory 失败不算交付;畸形控制积压拒绝并阻止启动准入。 - 执行消息先写持久 inbox 才 ACK;白名单号码格式错误单条拒绝,不暂停其它任务;任务/线路规则不满足时原执行身份留在 SQLite、该任务后续积压留在 SaaS 队列,窗口开放后重新核验才发出 Mock 指令。发指令前两次时窗/SIP/准入检查与持久额度占用;未知 Agent RPC 保持未知占用、不自动重拨。结果 outbox 只在 mandatory/return/confirm 成功后标记已入队,不宣称 SaaS 已处理。
- pause/stop/resume 控制先持久挡住该任务新呼叫,Agent 确认收到指令后在同一事务提交应用状态和回执;省略策略默认 hangup,重复控制仍执行、重复回执复用事件身份,stopped 同 ID 不可恢复,未接纳旧外呼静默 ACK。SIP 通知先持久关闭准入,旧/未知通话未确认终结、SaaS 新版尚未分发或 Agent/Asterisk 未加载时不重开;等待期间控制和 outbox 仍可处理。
- TDD 与隔离验证:
go test ./internal/store ./internal/dispatcher ./internal/mq -count=1、bash scripts/check-current-mq-mock.sh、git diff --check通过;Mock 覆盖任务积压、恢复、控制、重复投递、发布失败、SIP revision 栅栏与格式错误。当前 Agent side effect 为注入的假外呼;根 D/Agent 命令均已切换为 Mock-only,但含任务的主进程执行、录音及call.result未贯通。真实 SaaS/RabbitMQ、供应商与线路均未验收。
P05:Agent 快照与 SDK 隔离链路(项目内组件通过,主入口待 P07)
- Dispatcher 在启动、增量发现、恢复任务、SIP 更新及发指令前对完整 AI/provider 快照执行能力校验;不支持的任务关闭准入而不占额度或呼叫。已按用户确认保留现行 TTS Schema:火山 TTS V2 SDK 无法表达的 PCMA、0.5–2 倍以外或整数 speech_rate 无法精确表达的速度均明确拒绝,不静默修改配置;不支持的插话配置同样拒绝。
ExecuteApproved只在隔离 Mock 中签发:携带任务原始 JSON、引用的 provider 明文凭据、SIP revision、选定路由/主叫/原始被叫、独立的拨号期限及最大通话时限;任务+provider 原始字节+SIP revision 计算绑定摘要。Agent 对照已激活的 D 会话身份、绑定摘要、SDK 能力及实际观察到的 SIP 加载结果;发出 Mock 指令前仅持久保存摘要和未知占用,失败/重复/重启不自动重拨,文件中不留明文凭据。当前LoadedSIP为可注入的隔离 Mock 观察器,不等于真实 Asterisk 已加载核验。- ASR-only 不启动 LLM/TTS;full-AI 使用冻结的 model、显式 temperature=0/max_tokens、TTS voice/format/speech_rate 与 provider 原值凭据。真实 ASR SDK WebSocket 发出的音频输入/识别配置、LLM 与 TTS SDK 向隔离 HTTP Mock 发出的实际参数均已捕获核对;已配置的开场白由 TTS 单次合成,失败不自动重播且未完成时不进入对话轮次;空开场白不调用 TTS。关键词仅匹配用户侧最终 ASR,失败/未知挂断不自动重试。
ApprovedOriginator经生成的 Unary gRPC Stub 交付完整快照,SIP 全量 revision 不吻合即拒绝。 - 对话控制隔离验证:共享
callflow不再根据转写或回复内的硬编码词推断拒联,仅处理明确的关键词/拒联事实;ASR-only 不播报开场或 TTS 回复。配置的首语音/静默期限、整通话期限与最大轮数交给媒体控制器;回复按 Unicode 字符数分片,火山 TTS 的缓存音频块数超限明确失败而不重试;不支持的插话配置在准入前拒绝。该控制器尚未接入新主 CLI,不能宣称真实通话媒体已验证。 - Agent 隔离媒体入口
rpc.RunApprovedCall仅接收签发的执行快照、媒体会话、挂断动作和显式每通话 AI pipeline;缺失或 typed nil 适配器在读媒体前拒绝,无隐式 SDK/Mock 回退。测试分别由已绑定快照构建 SDK pipeline 与注入受控合成脚本的ApprovedMockPipeline。签发通话期限与 AI 总期限均约束整通电话;ASR-only 不合成开场,full-AI 开场完成才采集语音。父级期限不会被“未检测到语音”掩盖,空媒体 Mock 等到上下文真正结束。当前媒体只处理 16 kHz PCM16,SDK 虽接受、但媒体不能正确处理的 24 kHz ASR/TTS 配置在 Dispatcher 与 Agent 绑定时明确拒绝,不静默播放或识别错速音频。该入口尚未连接主 CLI,也未用于真实拨号。 - 隔离 Mock AI 适配器仅消费显式冻结的模拟媒体与最终识别脚本,不手写或替代供应商协议;缺失/多余轮次、未批准的开场或助理音频明确拒绝。ASR-only 不执行开场与回复;full-AI 开场只允许一次,用户最终识别的已配置字面关键词先挂断、绝不播放该轮候选回复,未配置关键词不推断拒联。任务未配置最大轮数时严格使用共享通话流程的一轮边界,负数仍拒绝。Mock 输出不代表真实 ASR/LLM/TTS 已通过。
- 新增 Agent 任务级在途通话栅栏
TaskCalls:按 D/数字租户/任务隔离;暂停或停止先拒新通话,hangup 请求取消、drain 不挂断,两者均等待已登记通话结束才确认;等待超时仍保留关闭栅栏,停止后不能恢复,同一控制重复执行不另设去重。隔离测试验证两任务互不干扰、重复结束不破坏状态。该栅栏已由根命令的隔离合成 runner 绑定到同一个控制器;尚未进行主进程执行+控制的完整联测,真实媒体入口未启用,Dispatcher 的持久控制仍为权威,不能据此称真实通话已受控。 - 新增内部任务级
ApplyApprovedTaskControlProto、生成物及 DispatcherApprovedOriginator.SendControl:D 每次发送前从已激活会话领取元数据,只生成独立 RPC 追踪 ID,不从 SaaS 控制虚构 command_id、幂等键或 revision;Agent 校验活跃 D 会话、数字租户、任务和 pause/stop 的 hangup/drain 政策,在 Mock 中等待已登记活动通话结束后才确认,超时保留本地停止栅栏。隔离测试覆盖拒绝伪 D/旧会话、非法字段与政策、无适配器、未知 RPC 结果不自动重试及重复控制的显式重送;本地双向 TLS gRPC 还验证了受信 D 证书经真实生成 Stub 传送控制后,先挂断再等已登记通话结束才确认,不受信的客户端证书与旧会话均拒绝且不能重新准入。隔离合成 runner 已接入新主 CLI,主进程 D 控制联测及真实媒体仍未验证,不能据此称真实通话控制已验收。 ApprovedCallWorker与NewApprovedAgentServer强制把已授权执行和控制 RPC 绑定同一个任务栅栏、显式 Mock 模式及 Agent 进程期限;每通话先在无拨号/无业务写入前准备并核验签发 AI、合成媒体和交付目标,准备失败即明确拒绝且不发完成事实;签发拨号时限在异步工人发出接受回执前重验,超时或任务关闭同样在无拨号时明确拒绝;启动请求结束不取消在途通话,任务 hangup 等待 runner 明确结束后才完成控制。隔离测试覆盖先执行回执后合成挂断、进程关闭、失败观察、重复身份防重及缺失/竞争适配器拒绝;未知执行不自动重拨。这里证明隔离组件的调度与取消顺序;新 Agent 主 CLI 已组装合成 runner、录音交付与失败观察,但尚未完成主进程 D 会话/录音交付、真实媒体或出站 MQ 的端到端联测。- Agent 主命令现使用严格有界的隔离 Mock 场景及已加载 SIP 测试事实读取器:合成 PCM16、脚本、录音预期、结果事实、WAV 上限及 SIP revision 均须显式给出;缺失/未知字段、额外 JSON、非法 base64 或不合理 revision 拒绝,错误不回显场景内容。隔离加载事实不是 Asterisk 加载证据,也不是 SaaS 智能体值或真实音频验收。
- 根命令已将
newCurrentAgentServer接入唯一可达的agent子命令:显式 Mock 身份、每通话合成脚本、同一控制/执行栅栏及 Agent→D 会话元数据/录音交付已组装;缺失/typed-nil D 客户端、不私有的恢复目录及竞争配置拒绝。共用 Mock 上传客户端仅能连接本机且拒绝外部跳转;脚本、SIP 加载事实和证书指纹复制为启动快照。隔离命令测试实际启动本机双向 TLS gRPC 监听,受信 D 证书读取 Agent 状态,同 CA 未列入指纹的证书被拒绝;--call-once已不再由根命令接受。尚未在主进程验证 Agent→D 的真实会话激活/录音执行或部署诊断,更非真实 Asterisk、线路和供应商通过。 - 验证:
PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-current-contracts.sh、PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-proto.sh、go test ./... -count=1、go test -race ./internal/ai ./internal/callflow ./internal/configread ./internal/dispatcher ./internal/rpc -count=1、go vet ./...、go build ./...、git diff --check均通过。 - 后续边界: 录音直传/OSS 失败恢复及最终结果属 P06;新主 CLI 接线、真实媒体完整联动及旧路径清理属 P07;隔离端到端和 A01–A12 属 P08。真实 Agent/Asterisk、SaaS、MQ、AI 供应商联调未开展,不能由 Mock 结果代签。
P06:录音/OSS/最终结果(进行中,未签收)
- 已新增 Agent 内存录音单次 PUT 组件
UploadClient.UploadBytes:复用受限授权、大小和 SHA-256 核对,成功路径不写临时录音或通话结果文件。OSS 明确拒绝保留状态码且不自动重试;传输结果不明时返回专用错误、停止自动重试并隐藏带签名的 URL。空授权头拒绝而非使服务崩溃。这里只验证组件,尚未连接实际录音或最终结果。 - 内部 OSS 授权增加 Dispatcher 原始 bucket,官方 SDK 签发时返回获批 bucket;Agent 收到缺少 bucket 的授权会在 PUT 前拒绝,失败恢复的显式重申请若返回了不同 bucket,也在再次 PUT 前拒绝。两条红灯测试证明先前会错误上传;修复后全包测试及 Agent/RPC/OSS race 测试通过。这里只核验本地授权载体,不代表新主入口已完成签发或真实 OSS 已验证。
- Agent 失败恢复隔离组件:仅在 OSS PUT 明确失败后,把原 bucket/object_key 对应的录音和通话信息两文件写入私有目录并同步落盘;双文件缺失或损坏明确报错、不伪造结果。完成保存后固定 48 小时窗口,按 1 分钟递增至最长 1 小时重试;到期保留原文件。PUT 前持久写入 in-flight,结果不明或进程重启不会二次 PUT;确认上传后先持久记录成功,再经注入的 Mock 回报通话结果,回报失败/重启仅重发原结果。启动扫描识别遗漏文件并提供不暴露原路径的稳定摘要;根 Dispatcher 已通过含任务的本机隔离正向联测;失败和 48 小时边界分别由本段的分层故障测试核验。
- Dispatcher 的无录音最终结果隔离组件:
CurrentStore.RecordCallResult仅在确认通话结束后,按持久任务快照校验任务、被叫、主叫和已选线路,并以源执行事件固定生成唯一最终结果身份;消息通过严格 MQ Schema 校验后与 outbox 在同一事务写入。同内容重投/重启只恢复原消息,冲突结果和 SQLite 写入失败均不会产生第二份结果。此条事务测试为隔离组件;下述根命令隔离联测已连通批准的 Mock Agent 回报,但不代表外部应用收讫。 - Dispatcher 的原始 OSS 目标及已上传结果隔离组件:新 SQLite 布局把一次通话的 upload_id、bucket、object_key、录音格式/时长/大小和 SHA-256 唯一绑定到已保留的执行;不保存临时 URL 或 TOKEN。旧布局拒绝启动并原样保留待交付 outbox,不自动迁移或清理。已签发录音目标不能通过空录音结果绕过上传;已有空录音最终结果不能再签发录音目标。
RecordUploadedCallResult仅接受与持久绑定完全一致的录音事实及 Agent 所报告的成功 PUT 状态,录音确认与唯一最终结果 outbox 同事务提交;丢失回报或 MQ 投递时重用原消息,已确认后拒绝再次签发 PUT 授权。Mock 证明的是本地状态约束,不是独立 OSS 校验或真实 Agent 身份验证。 - Dispatcher 的终结与外呼回执顺序竞争隔离修复:原流程在 Agent 接受执行的 RPC 返回后才写入外呼回执,快速结束或 RPC 超时可能先到;现以一次 SQLite 事务在确认通话已结束后补齐原回执并释放占用,未知执行仅在确认结束后释放。迟到的执行响应、超时和重复结束不会产生第二份回执;注入 outbox 写入失败保留原占用。并发竞争及结束后立即生成唯一最终结果有单元测试;根 Dispatcher 的 Agent 会话、合成 Mock 呼叫、上传事实和 MQ 最终结果现已在本机隔离联测;根命令遭遇 MQ 持续故障后的完整重启恢复仍待 P08 注入验证。
- Dispatcher 录音事实 Unary RPC 隔离服务:
RequestRecordingUpload、ReportCallEnded、ReportCallResult均要求已配置本 D、核验 mTLS 指纹及当前 Agent 会话、数字租户和已保留的执行;复用官方 SDK 仅对原始录音签发固定 15 分钟授权,显式重申请仍用相同 bucket/object_key。结束事实可先于外呼响应而持久化原回执;录音结果核对 D 已存目标和 Agent 报告的成功 PUT,再与唯一结果 outbox 同事务提交。Mock 覆盖会话/租户拒绝、同资产重申请、上传前结果拒绝、坏 JSON、已上传与无录音结果及重复回报。隔离测试还通过本地双向 TLS 的 gRPC 实际传输:AgentRecordingClient每次读取并克隆当前会话元数据,经受控 D 客户端领取授权、上报结束和唯一结果;未配置客户端明确拒绝。其分层测试不包含 OSS PUT;下述本地隔离链路另验证直传,根 Dispatcher 与批准的 Mock Agent 服务已在本机双向 TLS 链路完成合成媒体录音、录音 Unary、单次 OSS PUT 和共享 MQ 最终结果;真实 Agent CLI 与外部服务未在该测试中启动。 - 内存录音隔离组件:
RecordingSession仅复制共享通话流程实际读到和成功发送的 16-kHz PCM16,EncodeMonoWAV直接在内存生成有界单声道 WAV;空音频、奇数字节、超过上限及未成功发送的音频都不能伪造成可上传录音。单元与 race 测试未产生业务文件。批准执行的合成 Mock runner 已接入该组件;Mock 中观测到的帧不等于真实 Asterisk 通话的全量媒体验收。 - Agent 录音交付隔离组件:
RecordingDelivery先确认结束,再依照录音是否实际生成分别上报唯一空录音结果或请求原授权并直传内存 WAV;录音生成失败保留通话真实结果、空录音对象及明确原因,不虚构上传事实。隔离测试通过本地 HTTP PUT 和假 Dispatcher RPC 覆盖成功无业务文件、OSS 明确失败后私有文件保存、恢复写入失败、未知 PUT 隔离、重启重领原目标、上传已确认后只重发原结果。再次调用不会隐式重新 PUT;正常已确认上传但尚未被 D 持久收讫的跨进程间隙仍受 K16 边界约束。此条失败/恢复测试是分层证据;根 Dispatcher 通过临时 RabbitMQ、批准 Agent 服务与本地 HTTPS OSS 的正向联测另见下述记录。 - 最终结果隔离组件:
ApprovedExecution已保留获批任务的caller_profile_id;FinalResultPayload只从批准的任务身份、确认时间及实际收到的用户媒体窗口生成当前唯一结果;最终 ASR 文本与字面关键词拒联写入完整转写,未播放的助手候选内容不冒充转写,未知 SIP 响应码保留null,无录音保留{}。单元测试直接核验当前 MQ Schema、字段缺失/乱序时窗与无应答负例;开场白发送失败不得计入已播放音频。此组件已接入 Agent 合成 runner,但未证明真实 RTP 转写时间精度。 - 本地隔离链路:
RunApprovedCall显式使用合成ApprovedMockPipeline,让共享批准通话流程按 ASR-only 限制消费实际读出的 PCM Mock 媒体帧;RecordingSession生成内存 WAV,FinalResultPayload仅用最终模拟识别及实测采集时窗构造结果。Agent 经双向 TLS gRPC 向 Dispatcher 确认结束、领取原始资产签名授权,再对本地 HTTPS OSS Mock 单次 PUT;Dispatcher 将原上传事实与唯一最终结果 outbox 同事务提交。测试确认一次 PUT、一次结果、无业务文件,使用官方 SDK 生成的路径;Mock ASR 与 OSS 服务不验证真实供应商协议或签名。这不是主入口执行、RabbitMQ 投递或外部验收。 ApprovedRecordedMockCall将显式批准的 ASR-only AI 快照、合成 PCM 媒体、每通话独立脚本、实际采集的有界 WAV、用户最终识别时窗和RecordingDelivery组合到一个可供 Agent 工人调用的 Mock runner;隔离测试验证先结束事实、一次本地 OSS PUT、唯一最终结果及正常路径无业务文件。无实际读入媒体时明确报告失败并以空录音、生成失败原因收口,不伪造上传或转写;缺失每通话交付器、交付器与签发 D/数字租户/事件不一致、私有恢复目录并非0700或模拟脚本不满足已批准 AI 时,均在发外呼接受回执前拒绝。此 runner 已接到隔离 Mock 主 CLI 的ApprovedCallWorker,该合成 runner 已在根 Dispatcher 驱动的本机隔离流中完成 D→A 执行、Agent→D 录音、HTTPS OSS 单次 PUT 和 MQ 出站;这不代表真实线路或供应商通过。- 根 Dispatcher 正向隔离链路:
bash scripts/check-current-mq-mock.sh启用临时 RabbitMQ、五类本机 Mock HTTP 资源和预建任务队列;根 Dispatcher 激活实际批准的 Mock Agent 服务并处理一条call.execute。Agent 以合成 16-kHz PCM 生成 WAV,经双向 TLS 领取授权后单次 PUT 至本机 HTTPS OSS;共享结果队列收到answered/uploaded的唯一最终事实,WAV 字节数和 SHA-256 与 PUT 一致,正常路径私有恢复目录没有业务文件。Agent 使用与根命令相同的服务组装函数,但该测试未启动 Agent CLI;原 SQLite 关闭重启并重投同一指令后恢复消费,OSS PUT 仍为一次;MQ 确认不是 SaaS 应用收讫,根命令持续 MQ 故障及真实外部仍待验证。 - 分层故障与恢复:Agent 测试覆盖空录音、录音生成失败、OSS 明确失败后私有落盘、落盘失败保留原音频、未知 PUT 停止自动重传、确认上传后仅重试回报、以及到期恰好 48 小时保留供人工而不 PUT;Dispatcher/SQLite 测试覆盖录音事实与唯一结果 outbox 的事务回滚、重启后保留原授权目标及同一出站身份;隔离 RabbitMQ 测试证明无路由时 mandatory/confirm 不谎报送达。根命令另在原 SQLite/Agent 会话上重启并重投相同
call.execute,确认没有第二次 PUT。分层测试不等于真实 SaaS 应用收讫或长时 MQ 故障后的主入口完整恢复。 - 本机 HTTPS Mock 信任:Agent 上传及 Dispatcher 配置读取使用现有 mTLS CA 信任束校验证书,不放宽主机名或证书验证,仍拒绝非本机目标及跨域重定向。测试确认已批准的本机 HTTPS 证书可访问,未知证书与无效信任束均失败;未使用
InsecureSkipVerify。这不是对真实 OSS 的验证。 - 已验证:
go test ./... -count=1、go test -race ./... -count=1、go vet ./...、go build ./...、PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-current-contracts.sh、PATH=/tmp/sip-go-agent-tools/bin:$PATH bash scripts/check-proto.sh、git diff --check。P06 的本机隔离正向、失败注入和重启防重复场景已分别通过;根命令在 MQ 长时故障后的完整恢复仍需 P08 验证,真实外部服务、SaaS 应用收讫与线路均未验收,不冒充生产通过。
P07 单一入口清理(执行中)
- 数据归属根因:现行 SQLite 的
CloseAdmission原本只按传入的 Dispatcher ID 插入或更新状态;把已有未投递 outbox 的 D1 数据库交给 D2 启动时,会错误建立第二个归属状态。新测试先证实该问题,再要求同一事务内检查既有归属,冲突时返回身份错误且保留原归属和未确认 outbox;本轮不迁移、不删除任何既有数据库。旧入口及名称残留仍须分批清理,不能因该测试通过便宣称 P07 完成。 - CLI 去旧入口:
main.go先由 AST 回归测试证实还有 14 个与进程入口无关的函数,随后收敛为main与唯一newRootCommand;未注册的旧 Agent/Dispatcher 入口及旧上传重试命令直接移除,根命令仍仅提供当前 Agent/Dispatcher 两个显式角色。根角色/废弃参数、非 Mock 预检已有现行命令测试覆盖;原 SQLite 身份/未投递回执保护已由现行 Store 回归接管,线路前缀与 ASR-only 的行为由现行策略及 AI 测试覆盖。不迁移或删除任何实际 SQLite、spool 或 outbox。其余旧模块、契约、引用及代次命名尚待清查,不能将本批当作 P07 整体完成。 - RPC 信任边界:录音服务原先复用旧 Dispatcher server 的可选 peer 校验函数;新
peer_test.go先验证当前录音必须具备经 mTLS 验证的证书和预授权指纹,随后移入无可选关闭分支的独立校验函数。旧 Dispatcher 上传、拆分事件服务及只验证旧链路的测试已经删除;现行录音的双向 TLS 传输、共享最终结果及失败恢复测试保持通过。其余旧 Agent/Dispatcher 路径尚待清查,不将此批视为 P07 完成。 - Dispatcher 旧任务消费分支:删除
task_runtime_v3、task_queue_v3、task_control_v3及其专属测试和仅依赖这些旧测试夹具的旧 Mock 恢复测试;当前CurrentRuntime、CurrentBootstrap、CurrentDiscoveryFollower和根命令隔离测试仍独立通过。 - Dispatcher 旧本地执行与控制分支:删除
local_v01、旧 Mock 呼叫/上传/恢复、旧路由与时间策略、旧Dispatcher/lease 实现及依赖它们的测试;现行命令、任务消费、录音/结果链路的定向测试继续通过。 - Agent 会话共用边界:保留当前根命令使用的
AgentCoordinator注册、状态探测、激活、最新会话授权和过期拒绝;移除仅供旧执行链路使用的ExecuteRaw、旧控制、查询补偿与旧 SIP applied-config 核验及其测试。当前 SIP 门禁仍由现行加载回报与隔离测试校验,不冒充 Asterisk 实际加载;旧 MQ/Store 和其余契约尚待清理,不能将此批视为 P07 完成。 - SQLite 代码归属:当前 Store 所需的 SQLite 驱动和 Dispatcher 身份/命令冲突错误已迁到当前入口;随后删除旧 Store 的源码、旧测试和旧迁移 SQL 文件。当前 Store 的独立建表、拒绝旧布局及外来 Dispatcher 抢占测试继续通过;这些操作仅移除仓库内过时源码,不读取、改写或清理现存 SQLite、spool 或 outbox 业务数据。
验收台账
P01–P06 的项目内隔离证据见上;P07 全仓命名与唯一入口尚在清理,A01–A12 和 K01–K16 的最终对照仍待 P08。不得用本地 Mock 冒充外部签收。