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

20 KiB
Raw Blame History

P08 项目内验收对照

本表保留 P08 当时的本地测试结果、来源路径与哈希;后续整理未改写验收事实。原计划现位于 docs/archive/sources/plan-saas-dispatcher-v05-v0.1.md,完成阶段计划位于 docs/archive/plan-saas-dispatcher-completed.md;当前唯一规范见 ../thirds/saas-dispatcher.md。

**当前补充(不改写 P08 历史结果):**现行运行期间不再定时轮询任务列表,删除了 DiscoveryFollower 及其两个测试;新建任务的 start 和暂停后修改的 resume 均重读任务配置,但不重新拉取/核验 SIP;SIP 仅在启动及通知待处理时独立全量读取,Agent 加载核验和本地任务绑定不依赖重读任务列表;全局 AI 服务商列表在 Dispatcher 每次启动时读取一次,start/resume 仅使用该次快照,服务商变更须重启。TestStartReadsNewTaskWithoutAgentControl、TestResumeRefreshesModifiedTaskRevision、TestStartDoesNotReopenPausedOrStoppedTasks 与隔离 RabbitMQ 的 TestRuntimeIsolatedControlBacklogExecuteAndSharedResult 已通过;本次 PATH=/tmp/sip-go-agent-tools/bin:$PATH make check(含 Proto 重新生成、隔离 RabbitMQ 测试)、go test -race ./...、go vet ./... 和构建通过,业务覆盖率 72.0%;此前含隔离 RabbitMQ 的 Dispatcher 测试覆盖率 75.0%(此轮未重算单包覆盖率)。

本记录按 docs/plan-saas-dispatcher-v05-v0.1.md §3.1、§6 核对 P01–P08、K01–K16 和 A01–A12。范围仅限单节点、单 Dispatcher、单 Agent、单 Cell、单租户的隔离 Mock。 本机证书、RabbitMQ 容器和 OSS/AI 模拟服务不代表 SaaS 应用收讫、供应商验收或真实拨号。原计划是当时的来源事实,不是第二份运行合同;当时的状态入口 docs/plan-saas-dispatcher.md 已归档。

可复现的本地门禁与来源

命令或来源 本地结果和边界
PATH=/tmp/sip-go-agent-tools/bin:$PATH make check 通过:格式、当前合同正反例、Proto 重新生成及来源/hash、go vet ./...、go test -race ./...、构建、本地验收;三项必需隔离 RabbitMQ 测试均实际 PASS,不是零项筛选。
bash scripts/check-current-mq-mock.sh TestBrokerSharedResultQueueAndNoConfigure、TestRuntimeIsolatedControlBacklogExecuteAndSharedResult、TestDispatcherCommandStartsWithIsolatedMQHTTPAndAgent 三项实际 PASS;使用临时隔离 RabbitMQ,不连接真实 SaaS。
make coverage Go 1.27.1;手写业务代码语句覆盖率 72.0%,排除生成目录 gen/,未排除本轮修改的手写代码;超过 65% 门槛。
PATH=/tmp/sip-go-agent-tools/bin:$PATH make release-check-local 通过本地制品/契约门禁;production_approval=false,未启动生产服务。
git diff --check 通过;仅作差异格式检查,不代表真实通信通过。
docs/plan-saas-dispatcher-v05-v0.1.md 固定来源 SHA-256 666f39e56ea9f4b55661efcac82edd6f9729848e2d60e5f24cdf5aa3ac97ee87;contracts/local/manifest.json 固定其来源、34 个合同 JSON 与对应哈希,清单自身 SHA-256 668aea5a742d2bf9bed4685c06f8dd95099d5d29a6bc2f32c2211bbc21197b40。
proto/manifest.json Proto 服务和生成物一致;错误说明更新后 proto/ERRORS.md 为 5108 字节、SHA-256 024f53a02c01151f7223ad49ad2d32c73d7701497dfd032bed0de820d72c7dc4,未更改 RPC 方法或 SaaS Schema。

上述门禁记录的是本地运行结果;新增的验收文件和门禁记录提交前再次执行,最终结果以本文件末尾复核为准。目标分支为 feat/saas-dispatcher-contract。已有 SQLite、Agent 恢复文件、spool 和 outbox 均未自动迁移或清理;两份使用者修改的 docs/thirds/ 文档不属于本批改动。

P01–P08

阶段 本项目可核验证据 状态
P01 契约 contracts/local/ 当前 Schema、MQ 拓扑、正反例与 manifest.json;contracts/current_contract_test.go、contracts/current_topology_test.go。 本地通过;真实 SaaS 字段未签收。
P02 单入口/Proto cmd/sip-go-agent/main.go 唯一 Mock 入口;proto/agent/agent.proto 八个方法;scripts/check-proto.sh 重新生成和核验。 本地通过。
P03 只读配置 internal/configread/snapshots_test.go、discovery_test.go、internal/ai/binding_test.go:任务、发现、SIP、额度、供应商及失效边界。 本地通过;真实 HTTP 未联调。
P04 MQ/SQLite internal/mq/broker_integration_test.go、internal/dispatcher/runtime_integration_test.go、internal/store/result_test.go:队列归属、实际路由、持久状态与唯一结果。 本地通过;实际 SaaS 应用收讫未验证。
P05 AI/媒体 internal/ai/binding_test.go、keyword_hangup_test.go、internal/rpc/approved_full_ai_integration_test.go:ASR-only/full-AI 与失败关闭。 本地通过;真实 AI/SIP/RTP 未验。
P06 录音/结果 internal/agent/recording_delivery_test.go、recording_retry_test.go、internal/rpc/recording_transport_test.go,见下方五条路径。 本地通过;真实 OSS 未验。
P07 唯一当前入口 docs/evidence/saas-dispatcher-p07-audit.md 的旧→新映射、历史例外与复扫;旧路径已删或按原字节归档。 本地通过;不处置现存数据。
P08 对照验收 本文件 A01–A12、K01–K16 与故障/重启对照;53 个命名测试引用和 21 个源文件引用均存在;本地构建、覆盖率和诊断门禁的隔离负例。 本地证据通过;真实现场门禁仍待授权核验。

A01–A12 逐项对照

检查 本地依据及结论 不能据此宣称的事项
A01 契约一致性 contracts/local/config-read.schema.json、task-discovery.schema.json、mq.schema.json、mq-topology.json 及 34 个示例/Schema 文件受清单约束;TestCurrentContractExamples 对合法/非法实例实际执行,TestCurrentContractTopology 固定通信形态。五类 HTTP、命令/回执、共享最终结果和 provider/agent 字段仅按本项目已确认版本核验。 真实 SaaS 的响应字段或审批来源已兼容。
A02 无代次名称 docs/evidence/saas-dispatcher-p07-audit.md 逐项记录旧→新、已删除/归档和合法固定 v1、业务 revision、历史事实;本轮新增 internal/session/ 不引入自有运行代次。本轮复扫 cmd internal gen proto contracts/local scripts deploys 的活动路径,意外 _v2/_v3、v0.x 路径 0 项;扫描 cmd internal scripts deploys 的废弃入口符号 0 项,保留 P07 已列的固定协议与历史例外。 把历史文档里的代次事实误当成现行运行入口。
A03 固定通信名称 TestCurrentContractTopology、TestBrokerSharedResultQueueAndNoConfigure 和根命令隔离集成测试核对实际 exchange/queue/routing key、共享结果接收及 /internal/v1/dispatcher/...;没有旧拓扑回退。 不同真实 SaaS 部署的路由已联调。
A04 引用及生成一致性 make check 执行当前合同及 Proto 清单/来源 SHA-256、重新生成、构建;P07 审计核对当前/历史引用和归档。固定历史来源哈希与本地清单各自独立。 从源代码仓库测试推断真实 management 已发布相同配置。
A05 配置读取与失效 TestCurrentContractExamples 覆盖字符串 tenant_id、旧结构字段和新增未知字段反例;TestReadTaskDoesNotReuseConfigAfterHTTPFailure、TestReadTaskRejectsMismatchedOwnerAndMalformedSIP、TestBindCurrentFullAIUsesApprovedSDKFields、TestBindCurrentRejectsUnauthorizedProviderBeforeCall 等核对 0/false/null、授权绑定、无缓存回退和虚构 credential 的 SDK 参数。 真实凭据或供应商 SDK 已端到端联调。
A06 队列所有权与路由 上述三项隔离 MQ 测试实际 PASS;无 configure 权限、被动检查、D1/D2 路由和共享 SaaS 队列实际收件。TestBrokerSharedResultQueueAndNoConfigure 覆盖缺/错绑定、mandatory return、关闭连接拒发后显式重开并收取原待发消息;TestExecuteTimeoutAndMQFailureDoNotRedial 注入发布确认丢失,保持原状态、不重拨。 不能把 confirm 当 SaaS 处理回执,不能把显式重开写成自动热重连;没有注入真实 RabbitMQ 确认帧丢失。D1/D2 仅夹具。
A07 控制与发现竞态 TestReadAllTasksContinuesAfterShortPageUntilEmptyPage、TestDiscoveryFollowerFailedPageKeepsCursorAndClosesAdmission、TestControlPauseDefaultHangupAckAfterAgentDispatch、TestControlStopCannotResumeOrDispatchPending、TestSIPNotificationPersistsBarrierAndWaitsForLoadedFullSnapshot:游标、持久屏障、旧 running 及停用不可恢复。 Agent/Asterisk 实际加载的 revision 已在真实环境核验。
A08 执行与 AI 行为 TestExecuteRejectsInvalidCalleeWithoutStoppingTask、TestExecuteWaitsForRulesThenDispatchesOriginalIdentity、TestExecuteTimeoutAndMQFailureDoNotRedial、TestSelectTrunkWhitelistScheduleAndCaller、TestBindCurrentASROnlyDoesNotBindOtherProviders、TestKeywordHangupOnlyFinalUserLiteralAndOnlyOnce;重复与未知执行不重拨,只有用户最终 ASR 命中关键词。 Mock 时段/门禁不授权任何真实号码拨号;真实 SIP/AI 仍未验证。
A09 结果与录音 下方五路径和额外的恢复/窗口测试;TestFinalResultNeedsConfirmedEndAndRemainsExactlyOne、TestFinalResultPayloadNoAnswerHasNoFabricatedTranscriptOrAsset。48 小时到期保留待人工、不发假结果。 模拟 HTTPS 对象接收不等于供应商 OSS 成功。
A10 数据保护与恢复 TestRecordingRecoveryDiscoversAndResumesAfterProcessRestart、TestRecordingRecoveryUnknownPUTNeverRetriesAfterRestart、TestFinalResultOutboxFailureRollsBackAndCanResume、TestFinalResultRestartResumesSameOutboxBody;会话代际竞态测试见下表。旧 SQLite 与旧 Agent 状态只读失败关闭,未自行清理。 未保存恢复双文件时进程崩溃不保证零丢失;真实主机重启未验。
A11 测试与构建 TDD:新增会话/录音测试先红后绿;上述 make check、72.0% 覆盖率及本地发布检查通过。TestNonprodCallEvidenceFailsBeforeDialWithoutRequiredGates、TestNonprodPreflightRejectsIncompleteCapturedEvidence 以临时假工具验证缺资源、缺诊断/抓包事实即失败关闭。 假工具不能替代实际 ECS、Asterisk/systemd、ARI/PJSIP、拨号前 SIP/RTP 抓包及收尾哈希。未经另行授权不执行这些现场动作。
A12 交付边界 验收时的状态入口为 docs/plan-saas-dispatcher.md,业务规范为 docs/thirds/saas-dispatcher.md;前者现已归档,本表仍分开本地证据和外部缺口。production_approval=false;已有业务数据未自动处置。 不宣称第二 Dispatcher、第二 Cell/租户、多机、容量、真实线路或生产通过。

A09 五条录音路径及错误边界

路径 隔离测试与可观察结果
录音生成并上传成功 TestRecordingDeliveryDirectPUTThenOneResultWithoutBusinessFiles:一次模拟 HTTPS PUT,仅录音字节;不先写再删业务录音/结果文件;确认通话结束和上传后才给唯一结果。
OSS 明确失败 TestRecordingDeliveryFailedPUTPersistsOriginalAndNeverRetriesImplicitly、TestRecordingDeliveryRestartRetriesOriginalAndReportsConfirmedPUTOnly:原录音/结果双文件、原对象与消息身份保留,重启后显式恢复,不自动重传不确定 PUT。
无录音/无应答 TestRecordingDeliveryNoRecordingReportsOnlyAfterConfirmedEnd、TestFinalResultPayloadNoAnswerHasNoFabricatedTranscriptOrAsset:确认结束后直接回报,空录音,不编造转写/OSS 路径。
预期录音生成失败 TestRecordingDeliveryCaptureFailureDescribesFailureWithoutInventingUpload、TestApprovedRecordedMockCallGenerationFailureReportsEmptyRecording:真实通话结果、空录音并说明失败;不当作 OSS 失败重试。
失败且无法保存双文件 TestRecordingDeliveryRecoveryDiskFailureNeverReportsUploaded:明确报错,无完整副本不发最终结果,不伪称已开始可靠重试。

补充:TestRecordingRecoveryBackoffAndResultRetryDoNotRepeatSuccessfulPUT 核对 1/2/4/8/16/32/60 分钟及上限;TestRecordingRecoveryExpiresAfterExactly48HoursWithoutResult 以双文件保存后时刻为起点,到期留原文件待人工;TestRecordingDeliveryConfirmedPUTReportRetryDoesNotRepeatPUT 确认 MQ 失败不触发第二次成功 PUT。未知 PUT 由 TestRecordingDeliveryUnknownPUTKeepsManualBarrierAcrossRestart 和 TestRecordingRecoveryPersistsInFlightBeforeUnknownPUT 禁止盲重试。

K01–K16 对照

决定 对应实现/测试及本地结论
K01 contracts/local/mq-topology.json、TestCurrentContractTopology、TestBrokerSharedResultQueueAndNoConfigure:任务/控制队列 SaaS 预建,D 无 configure,共享结果队列实际接收。
K02 TestCurrentContractExamples:tenant_id 数字,字符串及旧 schema_version 反例拒绝;内部事件/幂等身份保留。
K03 TestReadAllTasksContinuesAfterShortPageUntilEmptyPage、TestDiscoveryFollowerPersistsPageBeforeAdvancingToTerminalEmpty:非空短页继续、含游标空页结束,逐页持久化后前进;出错关准入。
K04 TestControlStopCannotResumeOrDispatchPending、TestStoppedTaskSilentlyAcknowledgesOldExecuteWithoutRecreatingInbox:同 task_id 的 stop 不可恢复;pause/resume 另有新鲜核验,不做任务删除或跨 D 迁移。
K05 TestControlPauseDefaultHangupAckAfterAgentDispatch、TestControlAgentFailureKeepsBarrierWithoutSuccessAck:缺省挂断,成功回执仅表示已交 Agent;未调度不回成功。
K06 TestExecuteWaitsForRulesThenDispatchesOriginalIdentity、TestFinalResultNeedsConfirmedEndAndRemainsExactlyOne:dispatched 与已接通分离;外部 task_id/callee 关联,内部仍保存每次事实身份。
K07 TestSIPNotificationPersistsBarrierAndWaitsForLoadedFullSnapshot、TestReadSIPWithoutTasks:全量 SIP revision 与通知对应,变更期间关准入;实际 Asterisk 加载仍待现场证明。
K08 TestBindCurrentFullAIUsesApprovedSDKFields、TestBindCurrentRejectsUnauthorizedProviderBeforeCall:虚构 credential 作为获批 provider 参数传入,不发明 ref,不记录真实密钥。
K09 TestKeywordHangupOnlyFinalUserLiteralAndOnlyOnce、TestKeywordHangupConcurrentDuplicateFinalTriggersOnce:仅用户侧最终 ASR 文本的字面关键词生效,重复通知一次。
K10 A09 五路径及 TestRecordingRecoveryBackoffAndResultRetryDoNotRepeatSuccessfulPUT、TestRecordingRecoveryExpiresAfterExactly48HoursWithoutResult:明确失败才持久化双文件,48 小时原起点/间隔/待人工边界。
K11 TestFinalResultPayloadRejectsMissingOrInventedCallFacts、TestFinalResultPayloadNoAnswerHasNoFabricatedTranscriptOrAsset:无真实 SIP 码保持 null 和文字原因,不伪造本地数字码。
K12 TestExecuteWaitsForRulesThenDispatchesOriginalIdentity、TestSelectTrunkWhitelistScheduleAndCaller:规则不满足等待并重新校验,不自动取消人工 pause/stop,不转为伪失败。
K13 TestExecuteRejectsInvalidCalleeWithoutStoppingTask:单号不合规只拒绝该执行,不出 dispatched/最终结果,不暂停整个任务。
K14 TestFinalResultPayloadNoAnswerHasNoFabricatedTranscriptOrAsset、TestFinalResultNeedsConfirmedEndAndRemainsExactlyOne:已确认 answered/no_answer/failed 与未知占用分离,不按 OSS 失败改写通话事实。
K15 TestRecordingDeliveryCaptureFailureDescribesFailureWithoutInventingUpload:录音生成失败仍给真实通话结果、recording={}、文字说明。
K16 TestRecordingDeliveryRecoveryDiskFailureNeverReportsUploaded:双文件保存失败不发结果、不编造可恢复状态。

故障、重启与会话竞态

注入/重启边界 观测证据 剩余限制
HTTP 页/配置网络失败或过期 TestReadTaskDoesNotReuseConfigAfterHTTPFailure、TestReadTasksFailsOnUnavailableRefreshWithoutStaleFallback、TestDiscoveryFollowerFailedPageKeepsCursorAndClosesAdmission。 本地假 HTTP 服务。
MQ 无绑定、不可路由、关闭后重开、确认结果不明 TestBrokerSharedResultQueueAndNoConfigure 实际隔离收件;TestExecuteTimeoutAndMQFailureDoNotRedial 的发布确认丢失注入;TestFinalResultRestartResumesSameOutboxBody 保留原 outbox 消息身份。 未注入真实 SaaS/RabbitMQ 生产网络的确认帧丢失;confirm 不是应用收讫。
Agent 执行/会话代际切换 TestInboundAgentFactRequiresCurrentUnexpiredActivatedSession、TestRecordingServerClassifiesOnlyVerifiedSessionCutover、TestRecordingFactsCrossRealMutualTLSUnaryTransportWithoutExternalOSS、TestRecordingClientDoesNotRetryUnverifiedOrUnknownOutcomes、TestRecordingClientStopsWhenSecondOutcomeIsUnknown、TestRecordingClientSessionCutoverReReportIsBounded。只在同 Agent/Cell/boot/epoch、相邻有效代际且明确拒绝时,在六秒或调用方更早期限内重报原三类事实;未知结果立即停止。 窗口耗尽明确失败,不称已交付;换 boot/身份或真正过期不能凭此自动恢复。未做真实多主机重启。
OSS 明确失败、未知 PUT、写盘错误、48 小时耗尽 A09 测试分别验证原对象/进度、重启、未知不重传、失败不伪造结果和待人工。 成功上传不写业务恢复文件;在未完成失败落盘前崩溃,不保证恢复。
非生产诊断/现场证据缺失 TestNonprodCallEvidenceFailsBeforeDialWithoutRequiredGates、TestNonprodPreflightRejectsIncompleteCapturedEvidence 假工具负例在缺时段、systemd、ARI/PJSIP、安装包哈希或抓包哈希时阻止验收。 未运行真实主机 capture-first 诊断、SIP/RTP/录音/ASR 现场采集。

**待真实环境完成,不构成本地失败的伪证:**真实 SaaS/management 配置签收与审批版本同源;真实 RabbitMQ ACL/TLS、队列所有权、应用收讫和结果消费;Agent/Asterisk 实际加载、ECS/systemd/ARI/PJSIP 状态、媒体/抓包;真实 OSS/AI/SIP 供应商;多节点/容量和生产切换。没有云资源操作、真实号码拨打或现存数据清理的授权与执行。任何缺少现场抓包、状态快照或权限的非生产验证均须失败关闭,不能把隔离 Mock、制品 hash 或三项本地 MQ PASS 写作真实验收。

最终复核

  • PATH=/tmp/sip-go-agent-tools/bin:$PATH make check:PASS,包含重新生成/核验 Proto、当前合同正反例和来源 hash、格式、go vet ./...、go test -race ./...、构建及三项实际 PASS 的隔离 MQ 用例;新增显式重连及全部会话/录音测试已计入。
  • make coverage:PASS;Go 1.27.1,排除生成文件后的手写代码语句覆盖率 72.0%。
  • PATH=/tmp/sip-go-agent-tools/bin:$PATH make release-check-local:PASS;本地制品 SHA-256 00b283d422a27766f1d4763ee193d705857274a5ec9f3c4dba699594536ff30a;清单报告 production_approval=false、source_dirty=true。这不是生产包签收,也未执行部署或拨号。
  • 活动路径/退役符号复扫均为 0;53 个测试符号、21 个源文件引用及当前/索引/证据文档 54 条本地链接均存在。来源计划、当前合同与 Proto 错误说明的哈希见首表;两份使用者修改的第三方文档保持原状且未被纳入本轮变更。