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

174 lines
62 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 项目内实施证据
> 本文记录当时的实施过程。当前运行期间不再定时轮询任务列表,也不再有 `DiscoveryFollower`;新建任务用 `task.control` 的 `start`、暂停后修改用 `resume` 重读任务配置,不重读 SIP;启动时读取一次全局 AI 服务商列表,运行中的 start/resume 沿用该快照,变更须重启。SIP 仅在启动和 `sip.config` 通知待处理时拉取全量;确认 Agent 已加载且旧呼叫结束后,更新本地任务的 SIP 绑定,不重读任务列表。现行规则以 [`docs/thirds/saas-dispatcher.md`](../thirds/saas-dispatcher.md) 为准。
## 改动前基线
- 基线提交:`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)
> 以下为 P05 当时的火山 TTS 历史验收记录,不再代表当前 TTS 实现。现行合同已改为任务明确授权的百炼 `qwen3-tts-flash`/`Cherry`/`Chinese`:本地 Mock 已核验生成请求、签名音频引用下载、16 kHz PCM16 转换、一次请求与失败不重试;未发起真实百炼 TTS 或真实通话,旧凭据不构成新任务授权。当前依据以 [`SaaS↔Dispatcher 规范`](../thirds/saas-dispatcher.md) 及合同为准。
- 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 已通过含任务的本机隔离正向联测;失败和 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 实际传输:Agent `RecordingClient` 每次读取并克隆当前会话元数据,经受控 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 实现、代次标记和契约尚待清理,不能将此批视为 P07 完成。
- SQLite 代码归属:当前 Store 所需的 SQLite 驱动和 Dispatcher 身份/命令冲突错误已迁到当前入口;随后删除旧 Store 的源码、旧测试和旧迁移 SQL 文件。当前 Store 的独立建表、拒绝旧布局及外来 Dispatcher 抢占测试继续通过;这些操作仅移除仓库内过时源码,不读取、改写或清理现存 SQLite、spool 或 outbox 业务数据。
- MQ 旧任务队列分支:旧 Store 的任务队列引用删除后,移除 `V3Broker` 和三份只验证旧拓扑的代码/测试;共享结果队列和无配置权限下的投递仍由当前 MQ 隔离测试验证。真实 SaaS/MQ 接收与应用收讫仍未验证。
- MQ 旧声明拓扑分支:移除旧 `Broker`、旧租户 Topic 路由/队列声明及其测试;独立保留当前消费端的消息处理器、永久错误分类与 Dispatcher UUID v4 校验,补错误解包和规范身份的回归测试。当前 Broker 仅被动核对 SaaS 预建拓扑;隔离 MQ 测试通过不代表真实 SaaS 应用收讫。
- Store 名称收敛:移除现行 Store 的自有 `Current*` 类型、错误和 `OpenCurrent` 入口,保留单一 `Store`/`Open`;20 份 Go 源码与测试文件改为不带代次的路径,原现行任务/录音/结果测试仍执行。只重命名源码与调用,不修改现有 SQLite 表、记录或恢复数据。
- MQ/租户路由名称收敛:现行 MQ 和租户路由的自有 `Current*` 代码标识改为唯一的 `Broker`/`Open`、`Route` 及控制/任务/结果路由入口;七份 Go 文件改为无代次路径。固定 Topic/队列的 `.v1` 名称和值保持原样,隔离 MQ 的精确路由和不声明拓扑检查继续执行。
- HTTP 配置读取旧入口:移除旧字符串租户的三资源拼接读取、单独任务状态读取及旧 snapshot/changes 发现分支和专属测试;保留统一的凭据头、HTTP/JSON 错误处理和当前五类只读读取。当前任务/发现的 503、身份错配及分页测试继续执行,不从旧配置回退。
- HTTP 配置读取名称收敛:四份现行读取/发现 Go 文件改为 `snapshots`/`discovery` 的无代次路径,公开快照与 `ReadSIP`/`ReadTask`/`ReadTasks`/`ReadAllTasks` 只保留一套入口。原始 AI JSON、租户数字身份、五类 HTTP 路径及断连拒绝行为未改变;相关调用方和隔离测试同步更新。
- Dispatcher 名称收敛:现行 `Current*` 调度、控制、发现、线路选择及执行类型/错误改为 `Runtime`、`Bootstrap`、`DiscoveryFollower` 等唯一代码入口;18 份 Go 源码/测试文件移除代次路径,相关 Agent 控制及调用方同步更新。任务归属、固定 SIP 快照、白名单/时段、额度和结果防重规则不变,现行隔离执行测试继续通过。
- Agent 根命令不可达分支:删除旧执行模型的本地录音上传、手工重试、后台通知恢复和旧 Mock originate 代码及专属测试;根命令仍只注册获批的 Mock Agent/Dispatcher。当前录音 RPC、单次 PUT、失败保留和结果恢复另由现行隔离测试覆盖,删除旧命令不改变现有业务数据。
- Agent RPC 客户端入口:删除没有现行调用者的旧 `Client`/`DialFromFiles` 包装及仅针对该包装的测试;当前命令仍通过部署提供的双向 TLS 配置建立 gRPC 连接,原证书信任、SAN 和握手回归测试保持不变。旧服务端业务 RPC 与 Proto 仍待后续清理。
- Agent 会话持久边界:将会话代际使用的私有原子落盘函数从旧执行日志模块移入独立会话日志文件;原有目录同步、写失败报错和重启代际栅栏测试继续通过。旧执行日志及业务 RPC 尚未删除;此次只移动源码,不修改现存 Agent 恢复文件。
- Agent 旧执行状态屏障:批准的 Mock Agent 启动时若发现旧执行日志,立即拒绝启动并明确要求人工处置;隔离测试先复现了原先允许启动的问题,再证明旧日志字节不变、新会话状态未写入。此屏障不迁移、不清理存量文件;没有检查或处置真实环境中的旧记录。
- Agent Proto 唯一服务面:新增服务方法集合测试先确认旧 RPC 仍可被服务描述符发现,随后将 `AgentControlService` 收敛为状态/激活、获批执行与控制、加载版本及录音/结果八个方法;重新生成 Go 类型、核验七项来源哈希,并同步改写当前 Proto 错误与恢复说明。旧消息定义、旧服务端实现及专属测试尚待清理,不能把本批当作 P07 完成或真实 Agent 联调。
- Agent 旧失败事实分支:旧 Mock 上传失败事实通过已退役的 `ReportExecutionEvent` RPC 回报,现删除该代码及专属测试。现行录音失败、未知 PUT、重启恢复和最终结果仍由 `recording_delivery*` 隔离测试覆盖;不复活额外通话事件或把未知上传当作成功。
- Agent 旧业务 RPC 处理器:先以服务结构测试复现旧 `GetBootstrap` 等十个方法仍存在,再删除旧执行、许可、控制、查询、事件和上传处理器及仅依赖旧服务面的专属测试;原混合测试保留会话代际、证书指纹、状态和真实 gRPC 激活。另将旧执行日志写入失败保护迁至现行获批执行测试:日志目录不可写时两次相同请求均不得接受或发起呼叫,修复目录后只发起一次,结果未知时拒绝重拨。`go test ./...`、`go test -race ./...`、`go vet ./...`、`go build ./...`、Proto 来源/hash、当前合同及临时 RabbitMQ/HTTPS/双向 TLS 隔离链路均通过;旧执行日志结构、未使用的 Proto 消息与其他引用仍须继续清理,未接触现存业务数据或真实外部服务。
- Agent 旧执行日志根因:新增「首次激活→同一路径重启」测试,复现现行入口曾在初次启动自动写出废弃的 `.executions` 文件,下一次启动又将它识别为旧未交付状态并拒绝服务。移除旧日志写入、回放状态和闲置执行配置,只保留当前 `.approved` 日志、会话代际和旧文件存在时拒绝启动的保护;回归确认首次启动与重启不会产生新旧执行日志。已有 `.executions` 一律保留并失败关闭,不自动清理或猜测其业务内容;当前测试只使用临时目录。
- Agent 旧静态制品入口:现行命令从未提供 `StaticArtifactRaw/Expected`,旧激活分支及只服务于旧 `static-cell-artifact-v0.2` Schema 的手写解析器无法证明 Asterisk 实际加载。结构测试先复现残留,再移除旧 RPC 参数、解析器与专属测试;保留当前隔离 Mock 的 `LoadedSIP` revision 回报及 Dispatcher SIP 版本准入校验。此变更不等于真实 Agent/Asterisk 已加载或管理平台已审批,历史 Schema/来源事实另行辨析。
- Agent 旧 spool 准入边界:隔离测试先复现现行 Agent 对同一恢复根目录中旧 `.uploads`、`.upload-locks` 与逐执行 `state.json` 均会照常启动;现于创建会话和媒体状态前只读检查这些遗留标记,发现时明确拒绝启动并保留原文件。测试核实没有写新会话、没有修改标记;仅对配置的恢复根目录生效,不替代现存数据的人工核查或处置。
- Agent 旧 spool 代码:旧执行状态、实时事件、上传尝试、失败事实及上传锁只由旧模块彼此调用,没有现行命令或录音恢复调用;删除专属实现与测试。录音恢复仍复用的原子写、目录同步、文件名校验等小函数移至 `internal/agent/file_state.go`,通过现行录音重试/未知结果测试确认恢复能力保持。删除源码不删除任何磁盘 spool;旧文件在当前恢复根目录触发上述拒绝启动,不能视作已经补传或处置。
- Agent Proto 旧消息:先用描述符结构测试复现 `ConfigReference` 等废弃定义仍可被发现,再按当前八个服务方法及现行 Go 引用追踪字段依赖;删除无调用者的 26 个旧消息和 3 个旧枚举,不改现行请求/响应的字段号。使用本地 Buf 重新生成 Go 类型,核对七项来源/hash;现行录音客户端、获批执行、控制及会话测试保持通过。固定 `agent.v1` 仍是有效的内部协议值;这不构成真实 Agent/Asterisk 或外部 SaaS 验收。
- 隔离 MQ 校验脚本:审计发现脚本因测试文件改名仍使用旧筛选式,`internal/mq` 和 `internal/dispatcher` 输出 `no tests to run` 但退出成功;先复现两个空匹配,再改为现行测试名并要求三项均明确 `PASS`,跳过或空匹配均失败。隔离 RabbitMQ 实跑 `TestBrokerSharedResultQueueAndNoConfigure`、`TestRuntimeIsolatedControlBacklogExecuteAndSharedResult` 与 `TestCurrentDispatcherCommandStartsWithIsolatedMQHTTPAndAgent` 全部通过;这只证明本机隔离链路,不代替真实 MQ/SaaS 应用收讫。
- 旧 ARI/媒体执行包:`internal/callruntime` 仅实现旧 `ai.Snapshot` 的独立 `Run` 入口,无当前 Agent/Dispatcher 调用者;删除该包及仅针对该路径的测试。现行获批执行、录音恢复和完整 AI Mock 测试继续覆盖唯一现行入口;删除死代码不表示真实 Asterisk/ARI 或线路已验证,历史验收报告保留当时包名与覆盖率事实。
- 旧通话流程入口:旧 `Execute`/`ExecuteWithCapture` 使用可缺省的旧 AI 快照,现已无 Agent 调用者;删除入口及专属测试,保留同一 `executeFlow` 媒体顺序实现。先在当前 `ExecuteApproved` 的测试中补齐首次媒体等待上限、三轮对话、开场发送失败不伪造播放、拒联后不发送回答与 ASR-only 实际采集区间,再迁移共用测试夹具;现行获批通话测试和全仓测试通过。未声称真实媒体链路通过。
- 旧 AI 配置与供应商链路:旧 `Snapshot`/独立 MQ 配置响应/独立授权、旧可变 Mock 与旧供应商流水线已无现行执行者;删除专属实现和测试。当前获批执行仍使用原值 AI 模式及每通话不可变参数;将仍被当前路径使用的 `Mode` 与 `TurnResult` 移至 `internal/ai/turn.go`。先在当前 SDK 隔离测试补齐 LLM 失败与空回答候选不自动重试,再删除旧 SDK 专属测试;ASR-only、完整 AI、关键词、时限和媒体的当前测试继续通过。旧供应商测试的历史通过不能代替真实供应商联调。
- 旧项目内合同装载:测试先复现 `contracts.Files` 将 v0.1–v0.4 的历史本地 Schema 仍嵌入现行二进制;现在仅嵌入获批当前 local Schema/示例和原样上游 v1 来源,移除可从代码访问旧 local 的 `ReadLocal`、旧命令/事件/配置验证层及专属测试,将并发 Schema 缓存保留于当前验证器。项目历史 Schema 文件与外部上游来源仍原样留在仓库,没有改写来源哈希;历史文件不再构成运行时回退。当前合同正反例、上游来源读取和全仓 Go 测试通过,未代表外部 SaaS 已签收。
- 旧 Dispatcher 本地配置解析:旧 `LoadDispatcherFile` 独占读取上游 v1 `dispatcher-config.schema.json`,没有现行 Dispatcher CLI 调用者;删除实现与旧文件专属测试。现行入口仍从部署环境独立读取 Dispatcher 身份、SaaS 只读配置端点及受控 OSS 配置,并保留当前校验和错误报告。历史上游 Schema 未改,删除旧入口不代表真实配置来源已联调。
- 本地发布与合同脚本:先运行发布检查复现旧校验器因受保护第三方文档的历史 SHA-256 不匹配而中断,未覆盖用户修改、未改写历史 manifest/hash;改由当前合同正反例验证作准入,单独保留上游 v1 文件 SHA-256 的历史来源核对,删除会重新生成/校验过时代次的两个脚本。发布清单只记录当前 local 合同、MQ 拓扑、Proto 及历史来源 manifest 的实际哈希,不再宣称旧 v0.1/v0.3 草案是现行。发布检查先因旧清单格式失败,改完后仍因旧 CLI 错误文案失败;以当前 `dispatcher --mode mixed` 实测确认其在创建 SQLite 前拒绝,再修正检查文本。最终本地发布及打包、防覆盖、mixed/real 资源前拒绝和当前合同/队列检查均通过;仅生成并删除自有临时产物,未部署、未拨号、未处理现存业务数据。
- 旧全局环境配置:旧 `Config`/`FromEnv` 同时配置 Mock、mixed/real、旧 Dispatcher SQLite 和 OSS 环境凭据,已无现行业务调用;删除实现和专属测试,保留当前分角色环境及批准文件/受控引用读取。把「旧 OSS 环境变量不可覆盖配置文件与受控凭据」迁至现行 `LoadCurrentOSSConfig` 隔离测试;原旧结构的 mTLS 专属测试删除,现行 Dispatcher/Agent mTLS 校验测试仍在。全仓 Go 测试通过;不更改实际部署配置或现存数据。
- Agent 端点样例:包内 `deploys/config/agent-endpoints.example.json` 原指向非本机地址,无法通过唯一现行 Mock Dispatcher 的本地端点校验;新增读取实际打包样例的测试先复现失败,再将地址改为显式 loopback 端口。Agent/Cell 标识与证书 SNI 仍为部署占位值,须由受控环境提供匹配证书;样例通过只证明隔离 Mock 可读取,不授权真实 Agent/线路。
- 当前本地验收入口:旧 `scripts/mq-integration-local.sh` 的 V3/旧 Dispatcher 筛选式均匹配零项测试,但原脚本仍可能返回成功;先以 `go test -list` 复现空匹配,随后删除旧入口并令 `scripts/acceptance-local.sh`、`Makefile` 共用要求三项明确 PASS 的当前隔离 MQ 脚本。首次运行发现 `internal/store/store.go` 三处匿名结构体缩进未格式化,已仅执行格式化;再次运行当前验收入口的合同来源、完整 race 测试、静态检查、构建及三项真实隔离 MQ 测试均通过。完成提示明确非生产主机诊断和真实外部仍未验证,不把历史 F09 验收数写成本轮通过。
- 历史部署 JSON:部署目录仍放置旧 Dispatcher 文件配置与两份自行编号的静态 Cell 样例,其中审批标记不能证明现行管理平台审批。部署布局测试先复现三份旧样例仍在当前路径,再将原始字节移至 `docs/archive/deployment-examples/`,逐份核验迁移前后 SHA-256 相同并记录于该目录 README;当前 `deploys/config/` 仅保留已由现行 Mock 端点读取测试验证的 Agent 样例。安装包从未包含这三份旧文件,真实 Agent/Asterisk 加载和审批仍未验证。
- 当前文档单入口:`AGENTS.md`、根 `README.md` 和 `docs/README.md` 不再把旧 MQ-only、F 工作包或 v0.4 发现写作当前执行依据,改指唯一项目内通信说明、机器合同与新的 `docs/plan-saas-dispatcher.md` 状态入口;物理部署说明标明当前制品只支持隔离 Mock、不批准真实安装。原 AGENTS 原字节归档到 `docs/archive/AGENTS-before-current-contract.md`(SHA-256 `8996ce2db909508172f59f39e780bdab2f48b2eba1acc3590dcffc50f8b2f6f0`)。版本化计划 `docs/plan-saas-dispatcher-v05-v0.1.md` 已被当前 `contracts/local/manifest.json` 固定原路径及 SHA-256 `666f39e56ea9f4b55661efcac82edd6f9729848e2d60e5f24cdf5aa3ac97ee87`,因此保留原字节作为合同**历史来源例外**,另建不带代次的当前计划索引,而不伪造来源或另立运行合同。四份当前根文档的相对链接、两份原字节 SHA-256、`go test ./contracts ./internal/config`、`make check`(含 race/vet/build 与三项真实隔离 MQ PASS)及 `make release-check-local` 均已核对通过;当前待办仍有 AI 自有名称及历史夹具残留分类,本批不宣布 P07 完成。真实 SaaS、Asterisk/ECS、OSS、AI 与非生产主机诊断均未运行。
- AI 自有代次命名清理:新增 `TestAIBindingsUseDomainNames`,先在旧 `CurrentBound`/`BindCurrent` 和 `current*.go` 上得到预期失败;将获批配置绑定、ASR/LLM/TTS/对话参数和单通话控制统一改为业务名 `Binding`、`ASRConfig`/`LLMConfig`/`TTSConfig`/`ConversationConfig`、`Bind`、`Call`/`NewCall`,对应 `internal/ai/` 的源码和测试文件不再带 `current` 前缀,调用方与测试同步改名,业务行为未增减。命名测试、四个受影响模块的 `go test -race ... -count=1`、全仓 `make check`(含三项真实隔离 MQ PASS)及 `make release-check-local` 通过,旧 AI 标识和文件名在活动 Go 源中已无匹配。旧 `contracts/local/v0.1–v0.4` 文件与未使用的 `internal/testfixture` 仍需按真实来源及调用情况分类,不能凭命名删除合同来源或现存数据;此批不宣称 P07 完成。
- 旧本地 Schema/示例归档:新增 `TestCurrentContractDirectoryExcludesHistoricalGenerations`,先在 `contracts/local/v0.1–v0.4` 上预期失败;将共 17 个历史 JSON 文件逐字节移到 `docs/archive/contracts-local-history/v0.1–v0.4`,逐文件 SHA-256 与移动前一致、Git 基线为 `91d23a1`。`contracts/local/` 现在只保留当前无代次合同与示例,`contracts.Files` 原有嵌入规则未扩大;运行时拒绝旧本地合同的现有负例与目录检查均通过。`docs/contracts/local-contract-manifest-v0.6.json` 引用的是另一路径的历史原件,没有改写;本次也没有触碰上游来源、历史计划 hash 或现存 SQLite/恢复文件。历史文件不作为真实外部验收或配置回退。旧未使用夹具和测试对旧配置示例的直接依赖仍待移除,此批不宣布 P07 完成。
- 普通时段测试不再读取旧方案示例:`TestEvaluateApprovedTaskAndLineConfigShape` 改用 `contracts.Files` 嵌入的当前 task/SIP 正例;TDD 先验证旧 `trunk_details` 在当前 `trunks` 响应下产生预期失败,再绑定当前字段,周一 `09:30` 的允许终点为 `20:00`。`contracts.ReadCurrent` 只允许顶层 Schema/拓扑而**不**允许例子,这是原有严格边界,不为测试扩张;测试只读嵌入的当前正例。删除无人引用、带旧 `schema_version` 和 `agent_version_id` 字段的 `internal/testfixture/execute.go`,不清理数据、日志或上游历史原件。受影响模块 `go test -race`、全仓 `make check`(含三项实际 PASS 的隔离 MQ 测试)、`make release-check-local` 通过。本批仍需完成全仓残留/例外总账与 P08,外部验收未运行。
- 未接入运行路径的旧通话日志包:全仓活动 Go 代码中没有 `internal/calllog` 调用,独立 `internal/calllog/log.go`/单元测试仍包含旧 `schema_version`、旧任务条目等数据模型;删除该死代码而不是留下看似可用的第二条事实通道。Agent/Dispatcher 的现行失败关键路径仍有 `cmd/sip-go-agent/main.go` 的 `slog.Error` 与 `internal/rpc/` 的脱敏执行、录音授予、结束和结果提交日志,删包不删除任何已存在的日志/录音/恢复文件。`go test -race ./...`、`make check`(含三项真实隔离 MQ PASS)与 `make release-check-local` 均通过;不将旧日志格式留作兼容层。旧 AI 部署夹具与自有 `Current` 入口名称仍需另行审计,P07 尚未收口。
- 退役 AI 部署夹具:`TestDeploymentHasOneCanonicalDirectory` 先在旧 `deploys/test/ai-dental-meiba-v1.json` 上预期失败;逐字节移至 `docs/archive/deployment-examples/ai-dental-meiba-v1.json`,迁移前后 SHA-256 均为 `ae6f94045719e838c943e6c5ff9170ebc7fedb5991958cc64bbbffc229ba07b4`。运行入口、安装包和脚本无调用者,`deploys/test/README.md` 改指归档,历史证据中仍记录的原路径是**当时运行的输入事实**而不是现在的可执行路径;归档 README 列出原路径与当前位置,未改写旧证据 JSON。本机布局测试、当前文档链接/哈希、`make check`(含三项真实隔离 MQ PASS)与 `make release-check-local` 全部通过;不能因此宣称旧配置或真实供应商有效。P07 尚待自有 `Current` 命名及最终残留分类。
- 唯一业务入口与部署配置的自有 `Current` 代次命名:新增 `TestBusinessCommandsUseDomainNames` 与 `TestDeploymentConfigUsesDomainNames`,先在旧 `current_*.go` 与 `newCurrentAgentCommand`/`LoadCurrentOSSConfig` 等名称上预期失败;12 个业务命令及测试文件、4 个部署配置及测试文件改为 Agent、Dispatcher、Mock Endpoint 和 OSS 的职责名称,6 个构造/读取函数及调用方同步更名。`scripts/check-current-mq-mock.sh` 的第三项必跑集成测试筛选式同步改为现行测试名,错误提示也不再使用“current Agent”指代 Mock 模式。Agent/Dispatcher 行为无变化;两个命名测试、受影响模块 `go test -race -count=1`、全仓 `make check`(三项 MQ 集成测试实际 `PASS`)及 `make release-check-local` 均通过。`contracts.ReadCurrent`/`contract.ValidateCurrent` 表示**当前合同校验与历史源区分**、`SessionRegistry.CurrentMeta` 表示**当前有效会话**,不是并行代次业务入口,暂按语义例外保留,后续总账复核。P07 尚未提交最终残留总账,不代签 P08 或真实外部验收。
## 验收台账
P01–P08 的项目内隔离证据见上及下文;P07 唯一当前入口、全仓残留/合法例外及来源/hash 的逐项结果见 [`saas-dispatcher-p07-audit.md`](saas-dispatcher-p07-audit.md)。A01–A12、K01–K16、业务覆盖率、故障/重启和诊断门禁的最终对照见 [`saas-dispatcher-p08-acceptance.md`](saas-dispatcher-p08-acceptance.md)。不得用本地 Mock 冒充真实 SaaS、management、MQ、OSS、AI、Asterisk、SIP、ECS 或生产签收。
## P08 本地验收进度
- 十分钟 Agent 会话续期:审查发现 Dispatcher 之前只在启动时激活一次,约十分钟后 Agent 和 Dispatcher 均拒绝过期会话,长时间运行时无法再执行/上报。新增从真实会话到期时刻计算的提前五分钟续期;只允许同一个已审批 Agent boot、Cell 与 Dispatcher epoch 生成更高代际,激活响应、到期前/状态探测后的有效期及报告中的代际均须复核。续期失败立即关闭新准入、取消服务并以 Agent ID/代际的脱敏错误说明原因,不接纳未知新 boot、不自动重拨。TDD 先复现没有 `Renew`/循环、状态探测期间过期仍被重新激活,再以 bufconn 真实双端会话、可控时钟及重复 race 测试证明原会话过期后新会话仍可操作、旧代际被拒绝、换 boot/过期/失败不续;正常取消不再发起激活。`go test -race ./internal/dispatcher ./cmd/sip-go-agent -count=1`、`make check`(三项隔离 MQ 明确 PASS)、`make coverage`(全部非生成手写代码语句覆盖率 **71.8%**)、`make release-check-local` 通过。录音上报与续期切换竞争的故障证据和非生产诊断脚本门禁仍待审查,不将此条视为 P08 完成或真实主机验证。
- 非生产呼叫诊断**前置门禁**:审查发现原脚本允许绕过 Asia/Shanghai 真实窗口、将 Asterisk `enabled/active` 状态和已安装制品 SHA-256 错误用 `|| true` 吞掉,也未检查 ARI module/HTTP `/ari/` 与所选 PJSIP endpoint。新增 `internal/config/nonprod_call_gate_test.go`:先以纯本机假工具复现缺失门禁,再覆盖时段 `08:59`/`09:00`/`19:59`/`20:00`、错误时钟、无效环境、systemd 两种失败、ARI 模块/HTTP route、缺失 endpoint/哈希,12 个故障/边界均在**假 `runuser` 被调用之前**拒绝;时间门禁在准备证据前及实际执行前各查一次,时段左闭右开,状态和 SHA 失败均显式错误。`bash -n deploys/test/nonprod-call-evidence.sh` 与定向 `go test -race ./internal/config` 通过。这是隔离负例,既无真实主机状态,也没有发起 SIP 呼叫;拨号后证据收集的故障处置及部署/SSH/磁盘诊断仍待核验,不冒充完整 P08 签收。
- 非生产证据**结束收集门禁**:原脚本在 `EXIT` 收尾时使用 `|| true` 掩盖抓包 SHA-256 等必需事实缺失,隔离 `--preflight-only` 试验先复现“合成抓包 hash 失败却返回成功”;新增 `TestNonprodPreflightRejectsIncompleteCapturedEvidence`,使用本机假 Asterisk/tcpdump/systemctl/sha256sum 和临时目录,仅写合成抓包、**不调用假拨号命令**。收尾现在保存原调用失败码,并单独核验 PJSIP logger 关闭、结束时间、抓包及录音 hash、Asterisk journal、SIP 摘要和受限目录归属;任一失败留 `diagnostic-errors.txt` 的脱敏原因,原本成功的前置检查须返回失败。`bash -n` 和定向 `go test -race ./internal/config` 经预期红→绿;受限临时目录保留了失败时的合成 pcap 与 hash 错误事实。尚未有真实主机、真实 RTP、SSH/EIP 与供应商证据,绝不据此声称非生产现场验证已通过。
- 会话续期与录音事实切换:`internal/dispatcher/agent_inbound_session_test.go` 先复现缺少相邻代际可识别拒绝,再保持原 `store.ErrCommandConflict` 栅栏,仅在同 Agent/Cell/boot/epoch 且有效的相邻代际附加显式转场错误;`internal/rpc/recording_server_flow_test.go` 先确认三项原事实以前均误归类为永久 `PermissionDenied`,再要求 mTLS 指纹已验证时才返回专用 `Unauthenticated`,其余拒绝维持原义。Agent 的新测试先复现无重报和无时限,再覆盖原 `RequestRecordingUpload`、`ReportCallEnded`、`ReportCallResult` 的源事件、operation/idempotency、资产与结果内容不变;六秒/调用方更早期限、换 Agent/Cell/boot/epoch、错误码不符、二次报告结果未知均失败关闭,绝不重拨或重传未知 OSS PUT。实际本机 mTLS bufconn 连续三次切换均在 `internal/rpc/recording_transport_test.go` 完成,原结果 outbox 身份保持;`internal/session/transition.go` 统一跨模块错误标记和消息,`proto/ERRORS.md` 与 `proto/manifest.json` 更新且 Proto RPC 不变。会话切换耗尽仍明确报错,不冒充已交付或跨 boot 自动恢复。
- P08 隔离交付对照:`internal/mq/broker_integration_test.go` 在临时 RabbitMQ 中验证关闭连接拒绝宣称交付、显式重开后原待发事件进入共享队列,三项必跑集成测试均实际 PASS;`internal/agent/recording_retry_test.go` 补足 8/16 分钟测试,使 1/2/4/8/16/32/60 分钟与 48 小时待人工边界有明确证据。`docs/evidence/saas-dispatcher-p08-acceptance.md` 逐项列 P01–P08、A01–A12、K01–K16、五条录音路径和失败/重启对照;53 个命名测试与 21 个文件引用存在。`make check`、`make coverage`(手写业务语句覆盖率 **72.0%**)和 `make release-check-local` 本地通过;活动目录意外代次路径和退役符号复扫均为 0,发布批准仍为 false。以上只核验本地 Mock 和诊断假工具:未运行真实 SaaS/management/MQ 应用收讫、OSS/AI 供应商、Asterisk/SIP/ECS、非生产现场拨号前抓包或生产切换。