Guidance 已有可运行的内容 Store、助手 Adapter 和双 ORM 基础,但商业 GA 必须证明 Draft 不泄漏、发布不中断线上指南、旧 Prompt 可撤销、跨模块会话创建可恢复、NDJSON 在错误/取消/超时下有稳定合同。
1. NDJSON 正常流
手写端点从 Mediator 获得 IAsyncEnumerable<string> 并包装进 NdjsonTextStreamResult,把每个 chunk 序列化为类型化 {"kind":"data","text":"..."} 帧,每行 Flush,最后写 {"kind":"done"}。失败时恰好产生一个终态 {"kind":"error",...} 帧。响应设置:
application/x-ndjson; charset=utf-8;Cache-Control: no-cache;X-Accel-Buffering: no;userPolicy;DisableRequestTimeout。
POST /api/v1/guidance/sessions/guidance-100-hash/messages/streamContent-Type: application/jsonAccept: application/x-ndjsonAuthorization: Bearer <token>
{ "content": "如何筛选未开票记录?", "modelId": "managed-model-id"}2. 当前错误帧不统一
空消息、助手关闭或 Session 不存在时,Application/Adapter 返回字符串 [ERROR: description];Host 把它当普通 text frame。Host 捕获未处理异常时改写 {"error":"Guidance stream response failed."}。正常结束才有 done。
客户端必须猜测 text 是否以 [ERROR: 开头;错误没有稳定 code/retryable/correlation,也可能没有 done。GA 应使用版本化 union frame。
{"type":"meta","version":1,"correlationId":"corr-1"}{"type":"delta","sequence":1,"text":"先打开筛选条件。"}{"type":"error","code":"Guidance.Assistant.SessionNotFound","retryable":false}{"type":"done","finishReason":"error"}这是目标协议,不是当前输出。
3. 取消与超时
Host 使用 RequestAborted 发送 Query,并对枚举调用 WithCancellation;OperationCanceledException 被视为客户端断开。底层 Adapter 又把 token 传给 AIManage Stream Handler。
但端点禁用标准请求超时,没有替代最大持续时间、最大 idle、token/bytes/chunk 或 tenant concurrency。恶意/失控 Provider 可长期占用连接和计费资源。
4. 当前自动化证据
| 测试面 | 已覆盖 |
|---|---|
| Content Store | 四级选择、角色/语言、列表/删除、损坏状态 |
| Assistant Application | Prompt 内容、稳定 SessionId、空消息 |
| Assistant Adapter | disabled/workspace fail-closed、Session 聚合恢复 |
| Architecture | Layer 1 无 AI、统一聚合、9 生成路由、流 Host 纯度 |
| Integration | Content 与 Session SqlSugar/EF Core parity、迁移 |
未覆盖真实 HTTP 权限、平台 Draft 泄漏、发布并发、会话成功路径/竞态/补偿、旧 Prompt 撤销、AIManage owner 策略、真实 NDJSON 错误、Provider 故障、容量和恢复。
5. 最小回归组合
# 复用一个过滤表达式,确保三类证据覆盖同一模块边界。GUIDANCE_FILTER="FullyQualifiedName~Guidance"
# Guidance 应用、Store 与 Adapter 测试。dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \ --filter "$GUIDANCE_FILTER"
# 模块边界、路由和持久化形态。dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \ --filter "$GUIDANCE_FILTER"
# 双 ORM 内容与 Session parity。dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj \ --filter "$GUIDANCE_FILTER"发布证据还必须通过真实 API Host 和实际支持数据库,不能只用内存 EntitySet/NSubstitute。
6. 内容 HTTP 矩阵
测试匿名、普通 Published 读者、Draft 管理员、平台管理员、跨租户 ID、角色不匹配、平台 Draft、未知语言、重复发布、并发 Revise/Publish 和删除竞态。每个拒绝都查询数据库证明没有副作用。
var published = await api.GetContextualAsync(route, cancellationToken);await admin.ReviseDraftAsync(published.ContentId, newBody, published.ETag, cancellationToken);
// Draft 修改期间,终端用户继续得到旧 PublishedRevision。var stillLive = await api.GetContextualAsync(route, cancellationToken);stillLive.RevisionId.ShouldBe(published.RevisionId);stillLive.BodyMarkdown.ShouldBe(published.BodyMarkdown);当前 DTO 没有 ETag/RevisionId;这是 GA 目标测试。
7. Session 故障注入
在 FindSession、CreateConversation、Session Add、唯一提交、首问 Send、响应前依次注入失败。断言没有孤儿或孤儿可恢复;同 IdempotencyKey 重放返回同一 Session;并发 Start 不创建多个 Conversation。
var request = NewStartRequest(idempotencyKey: "guide-session-2026-42");
// 两个请求同时跨越 AIManage 与 Guidance 持久化边界。var results = await Task.WhenAll( client.StartAsync(request, cancellationToken), client.StartAsync(request, cancellationToken));
results[0].SessionId.ShouldBe(results[1].SessionId);await evidence.AssertSingleConversationAndSessionAsync(request, cancellationToken);8. Prompt 与授权回归
先启动包含受限指南的 Session,再撤销角色、删除/修订指南、关闭 Feature 或切换 Workspace,确认后续 Send/Stream 拒绝或刷新 Prompt。不能只检查新的 Start,因为风险来自复用旧 Session。
测试 Prompt 不含原始 TenantId/UserId、不记录正文;Secret/PII detector 命中时按政策拒绝或脱敏。
9. NDJSON 合同测试
使用真实 HTTP 流分别覆盖 meta/delta/done、校验 error、Session NotFound、Provider error、Host exception、取消、代理缓冲、慢消费者、断线和字符边界。解析器按行增量读取,不能假设一个网络 chunk 等于一条 JSON。
每个流只允许一个 done;error 后必须 done(error) 或连接合同明确终止。取消不写虚假成功。
10. Markdown 安全测试
覆盖 raw HTML、script、javascript/data URL、事件属性、SVG、远程像素、超大图片、Unicode 混淆、嵌套列表和代码块。管理预览与终端显示必须用同一 sanitizer policy,并用浏览器测试证明无 XSS。
同一正文进入 Prompt 时另测 prompt injection、Secret 和 PII;浏览器 sanitizer 无法保护模型路径。
11. 容量基线
至少测每租户 100/1万/100万指南、候选重复 1/10/100、正文 1KB/100KB/1MB、用户 1/100/1000 并发流、Prompt 16K 字符、长响应与慢消费者。记录 P50/P95/P99、数据库扫描、首字节、tokens、bytes、连接时长、内存和成本。
FindBestMatch 当前把候选全部 List 到内存再排序;候选自然键不唯一时数据增长会放大。应评估数据库 rank 查询或预计算 Published projection。
12. 指标与告警
- content create/revise/publish/delete/conflict;
- contextual hit/miss by rank、language、audience;
- platform-draft access denial;
- session start/reuse/refresh/expire/orphan;
- conversation/session compensation;
- prompt chars/policy denial,不记录正文;
- stream first-byte/duration/chunks/bytes/cancel/error;
- tokens/cost/quota/provider failure;
- outbox/event lag 与清理积压。
告警重点是 Draft 泄漏、发布后无线上版本、孤儿 Conversation、旧 Prompt 继续使用、流无终态、超时/成本激增。
13. 备份与恢复
备份 GuidanceContent 当前/历史/发布指针、Session 映射、相关 AIManage Conversation 引用和 Outbox。恢复后验证平台 null Tenant 语义、Audience、Published 指针、软删除、Session owner、Prompt revision 和孤儿映射。
若 AIManage 与 Guidance 恢复到不同时间点,需要 reconciliation job:关闭指向不存在 Conversation 的 Session,并按策略归档无 Session 的 Guidance Conversation。
14. GA 阻断项
- 平台 Draft 可能经 Detail/List 暴露;
- Published 同行修订导致线上内容消失且无历史;
- 自然键、发布唯一与并发控制不足;
- Markdown 渲染安全合同缺失;
- 助手没有独立 Permission/Feature/Quota,UserId 可为 0;
- Prompt 外发原始身份/角色且无敏感策略;
- 复用 Session 不刷新指南/角色/权限;
- Conversation/Session 创建无幂等、原子或补偿证据;
- 直接 AIManage Handler 可能绕过 owner Pipeline 政策;
- NDJSON 错误帧不稳定;
- DisableRequestTimeout 没有资源上限替代;
- HTTP、并发、故障、隐私、容量与恢复证据不足。
15. 扫尾命令
# P0 后应归零或只保留登记兼容入口。rg -n "UserId\?\.ToString.*\?\? \"0\"|DisableRequestTimeout|\[ERROR:" \ src/Platform/Guidance src/Hosts/BitzOrcas.Api/Endpoints/GuidanceEndpointGroup.cs -g '*.cs'
# 目标发布与会话治理能力。rg -n "PublishedRevision|IdempotencyKey|PromptHash|ExpiresAt|AssistantUse" \ src/Platform/Guidance -g '*.cs'Guidance 总览 · 内容生命周期 · 助手安全