feat(gateway): D3 审查读侧/revert 端点 + 语音用户可见面(L45)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

(a) 记忆入库前审查的两条 admin 端点(主人判「有必要就做」):
  GET /api/v1/admin/memories/reviews?limit= (留痕读侧,不含记忆正文)
  POST /api/v1/admin/memories/{id}/revert (回滚软合并,落审计 memory.review.revert)
  两条 writeAuth(裁定 DECISIONS #10:留痕属私有面,不给 viewer)
(b) 语音用户可见面:DTO voice(含服务端派生 url)+ GET /api/v1/media/{name}
  (200/206/304/416/HEAD、Accept-Ranges、非法名/穿越统一 404 media_not_found)
  + documents.Store.SaveAudio(内容寻址、$DATA_DIR 0600)+ history voice 列读写与引用计数差动

缺口(如实):message_persisted/握手回放仍不带 voice(文件面外,需给 history.Message 加字段);
AMR 需一行 VoiceTranscoder 注入(DECISIONS #11 已排)。4 个变异自检(含一条自认的等价变异)。
This commit is contained in:
AskaEth committed 2026-10-06 14:12:27 +08:00
1 parent 4a937bd269
commit 1420e8aaa6
15 files changed
+2202 -5

No files matched your search

+20
View File
@@ -93,6 +93,13 @@ func (s *Server) registerUserRoutes(
// 「随机名即凭据」的免鉴权读法,也不会放开内联服务 HTML/SVG 的口子。
mux.Handle("POST /api/v1/documents", writeAuth(http.HandlerFunc(s.handleDocumentUpload)))
mux.Handle("GET /api/v1/documents/{name}", writeAuth(http.HandlerFunc(s.handleDocumentFile)))
// 语音副本回放(design/24 §10.11;契约 v0.67 登记、本片落地):与文档读侧**同一
// 道门**(owner|admin,必须 Bearer)—— 语音是私密内容,刻意**不**走图片那套
// 「随机名即凭据」的免鉴权读法(客户端带 token 取字节 → `blob:` 播放)。音频副本
// 与文档共用 `<DataDir>/uploads/documents` 目录,但下发类型恒为 `audio/*` 且内联,
// 理由见 media.go。
mux.Handle("GET /api/v1/media/{name}", writeAuth(http.HandlerFunc(s.handleMediaFile)))
}
// registerHistoryRoutes 挂载持久化聊天历史表面(design/16)
@@ -214,6 +221,16 @@ func (s *Server) registerAdminRoutes(
// 语义、同样**不审计**。结果只活在进程内存(TTL 30 分钟),进程重启后旧 runId
// 如实 404。
mux.Handle("GET /api/v1/admin/memories/tidy/dry-run/{runId}", writeAuth(http.HandlerFunc(s.adminMemoryTidyDryRunStatus)))
// 入库前审查(D3)的管理面(design/23 §15 D3-8「面板可查」+ §8「回滚」;切片 L45):
// - GET .../memories/reviews —— 审查留痕列表(谁被审 / 四态 / 依据 / 合并去向 /
// 是否降级 / 时间),**不含记忆正文**。只读、不审计,走 writeAuth(owner|admin):
// 留痕带 itemId/personaId/projectId/sessionId,属私有记忆面,与同族的
// GET /admin/memories 同档,**不给 viewer**(design/23 §11 的既有取向 + design/11 §7
// 对 viewer 的定义是「只读监控投影」)。
// - POST .../memories/{id}/revert —— 撤销一次软合并(无损可逆,行永不物理删除)。
// 它是**写**,因此落审计(动作名 memory.review.revert,见 admin_memory_review.go)。
mux.Handle("GET /api/v1/admin/memories/reviews", writeAuth(http.HandlerFunc(s.adminMemoryReviews)))
mux.Handle("POST /api/v1/admin/memories/{id}/revert", writeAuth(http.HandlerFunc(s.adminMemoryRevert)))
mux.Handle("GET /api/v1/admin/observability/audit", writeAuth(http.HandlerFunc(s.adminObservabilityAudit)))
mux.Handle("GET /api/v1/admin/sessions/{id}/export", writeAuth(http.HandlerFunc(s.adminExportSession)))
mux.Handle("POST /api/v1/admin/rag/query", writeAuth(http.HandlerFunc(s.adminSearchRag)))
@@ -325,6 +342,9 @@ func (s *Server) withMiddleware(mux http.Handler) http.Handler {
// 文档路径的同类防护(design/24 §3.2「路径穿越」):`..` 形态必须在 ServeMux
// 清理路径并重定向之前被拦下,否则穿越请求会变成一个非文档路径的重定向。
h = guardDocumentPaths(h)
// 语音副本的同类防护(design/24 §10.11):三条读侧路(files/documents/media)
// 各自的名字形态与错误码,因此各有一道门。
h = guardMediaPaths(h)
if s.frontend != nil {
h = s.spa(h)
// 安全入口(裁决 1.6 / 16.4.7,design/07 §0.2):管理面板前端按 .env 的