Files
go-sip/docs/evidence/saas-dispatcher-implementation.md
T

91 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 通过;此预检尚未接入主 CLI,不能当作 P02/P03 完成。
- 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 参数中的显式 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` 命令尚未调用该辅助,不能声称启动已切换。
- `store.OpenCurrent` 新建数字租户 SQLite 状态;旧表、旧版当前布局、残缺布局均在写入前拒绝并保留原记录;不执行旧数据迁移或自动清理。启动时完整发现同一快照一次提交,分页增量逐页持久提交后才推进**内存** cursor;失败关闭准入,重启重新取完整快照。HTTP 的旧 running 不能解除 MQ 暂停/终止,同 revision 异内容及跨任务 SIP/租户额度冲突拒绝。
- `CurrentBootstrap` 先关闭准入,核验 SIP 全量与 Agent/Asterisk 已加载 revision、读取任务和 provider/额度,再排空 MQ 控制积压,最后依据已验证 SIP revision 开准入;有更新的持久 SIP 通知时保持关闭但控制与结果处理仍可继续。`CurrentDiscoveryFollower` 逐页绑定任务快照;HTTP 错误、失效或授权不一致只失败,不回退旧读取。**目前只在隔离运行组件中调用,尚未接入 `cmd/sip-go-agent/main.go`;provider 向真实 Agent 交付及真实加载尚待 P05/P07。**
- 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 为注入的**假外呼**,主 CLI 仍旧;真实 SaaS/RabbitMQ、供应商、线路、录音与 `call.result` 均**未验收**,分别留 P05–P08。
## 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 的持久控制仍为权威,不能据此称真实通话已受控。
- 新增内部任务级 `ApplyApprovedTaskControl` Proto、生成物及 Dispatcher `ApprovedOriginator.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 授权及回报尚未接线。
- Dispatcher 的无录音最终结果隔离组件:`CurrentStore.RecordCallResult` 仅在确认通话结束后,按持久任务快照校验任务、被叫、主叫和已选线路,并以源执行事件固定生成唯一最终结果身份;消息通过严格 MQ Schema 校验后与 outbox 在同一事务写入。同内容重投/重启只恢复原消息,冲突结果和 SQLite 写入失败均不会产生第二份结果。这里只验证隔离组件,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 写入失败保留原占用。并发竞争及结束后立即生成唯一最终结果有单元测试;主入口真实 Agent 会话注入与通话执行仍未接线。
- Dispatcher 录音事实 Unary RPC 隔离服务:`RequestRecordingUpload`、`ReportCallEnded`、`ReportCallResult` 均要求已配置本 D、核验 mTLS 指纹及当前 Agent 会话、数字租户和已保留的执行;复用官方 SDK 仅对原始录音签发固定 15 分钟授权,显式重申请仍用相同 bucket/object_key。结束事实可先于外呼响应而持久化原回执;录音结果核对 D 已存目标和 Agent 报告的成功 PUT,再与唯一结果 outbox 同事务提交。Mock 覆盖会话/租户拒绝、同资产重申请、上传前结果拒绝、坏 JSON、已上传与无录音结果及重复回报。隔离测试还通过本地双向 TLS 的 gRPC 实际传输:Agent `RecordingClient` 每次读取并克隆当前会话元数据,经受控 D 客户端领取授权、上报结束和唯一结果;未配置客户端明确拒绝。其分层测试不包含 OSS PUT;下述本地隔离链路另验证直传,主入口批准执行的媒体录音仍未接线。
- 内存录音隔离组件:`RecordingSession` 仅复制共享通话流程实际读到和成功发送的 16-kHz PCM16,`EncodeMonoWAV` 直接在内存生成有界单声道 WAV;空音频、奇数字节、超过上限及未成功发送的音频都不能伪造成可上传录音。单元与 race 测试未产生业务文件。批准执行入口尚未接入该组件,且 Mock 中观测到的帧不等于真实 Asterisk 通话的全量媒体验收。
- Agent 录音交付隔离组件:`RecordingDelivery` 先确认结束,再依照录音是否实际生成分别上报唯一空录音结果或请求原授权并直传内存 WAV;录音生成失败保留通话真实结果、空录音对象及明确原因,不虚构上传事实。隔离测试通过本地 HTTP PUT 和假 Dispatcher RPC 覆盖成功无业务文件、OSS 明确失败后私有文件保存、恢复写入失败、未知 PUT 隔离、重启重领原目标、上传已确认后只重发原结果。再次调用不会隐式重新 PUT;正常已确认上传但尚未被 D 持久收讫的跨进程间隙仍受 K16 边界约束。此处未连接主入口批准执行媒体或 MQ;下述隔离链路另测本地真正的 D gRPC/SQLite。
- 最终结果隔离组件:`ApprovedExecution` 已保留获批任务的 `caller_profile_id`;`FinalResultPayload` 只从批准的任务身份、确认时间及实际收到的用户媒体窗口生成当前唯一结果;最终 ASR 文本与字面关键词拒联写入完整转写,未播放的助手候选内容不冒充转写,未知 SIP 响应码保留 `null`,无录音保留 `{}`。单元测试直接核验当前 MQ Schema、字段缺失/乱序时窗与无应答负例;开场白发送失败不得计入已播放音频。此组件尚未接入主入口,也未证明真实 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`,但还没有从主进程完成 D→A 执行、Agent→D 录音与 MQ 出站的端到端联测;现有 OSS/RPC 测试均为隔离替身,不能代表真实线路或供应商通过。
- 已验证:`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`。主 Agent 入口已组装隔离合成媒体和录音交付,但尚未通过主进程 D↔A 会话/执行/录音、实际上传事实与最终结果交付、MQ 发布及重启恢复的整体联测,不能宣称 P06 通过。
## 验收台账
A01–A12 的行为验证及 K01–K16 的运行时验证待 P03–P08 逐项填充;不得用本地 Mock 冒充外部签收。