Files
HomeAgent/internal/sdk/e2e_mediabytes_test.go
JianFeeeee 9f7d91c219 refactor(memory)!: 删除 CAS 包与第二套召回(块 union 化第四阶段:收尾)
块 union 化前三阶段已让块自带负载、回填存量字节。本提交拆掉 CAS 本身:
`internal/memory/media/`(2100 行)+ `QueryMediaScored`(第二套召回)
+ `media_refs`/`ref_count` 引用账本 + `internal/sdk/media.go`(MediaAPI)
+ `SDKConfig.Media` / `SetMediaStore` 接线。删完 CAS 才真正不存在。

## 为何可删(不是"先删了再说")

- **MediaAPI 在生产早已是死的**:`SDKConfig.Media` 自 86572d13 起从未被设置,
  `sdk.Media()` 恒为 nil,插件 digest 路径一直走"内容寻址库不可用"分支。
  删的是死代码,不是活路径。
- **第二套召回确无调用方**:`QueryMediaScored` 仅被 media 包自身的测试引用,
  跨包引用为 0(块召回走 `RecallBlocks`)。
- **heldMediaDigests 补丁**(crossmodal 靠它防召回"无处可归的媒体")
  已在 86572d13 删除 —— 两套存储才需要的那种补丁,随存储一起没了。

## 替代路径:块引用取代 digest

插件取媒体从 `sdk.Media().Get(digest)` 改为 `sdk.Memory().MediaBytes(ref)`:

- **新增 `MemoryAPI.MediaBytes(ref) (data, mime, ok, err)`**(内置接口,非 pubsdk):
  按块引用(完整块 ID 或唯一前缀)取字节;`ok=false` 而非 error ——
  "这个引用没有媒体"是调用方能处理的常态。
- **seemedia / multimodal**:`digest` 参数语义改为**媒体块引用**,工具描述同步。
  path/url 两条来源不变(生产实际只用这两条)。
- **webui 知识上传**:媒体字节不再 Put 进 CAS 再拿 digest,而是作为
  `KnowledgeMediaRef.Data` 随引用携带,由 knowledge.Write 落进条目目录
  (与 content.md 同生共灭,Remove 的 RemoveAll 天然清掉)。
- **回填命令去依赖**:cmd/homed-media-backfill 不再 import 待删的 media 包,
  改为直接读**冻结的历史布局** blobs/<d[:2]>/<d[2:]> 并自校验 sha256。
  依赖待删代码的迁移工具会在删包那刻失效 —— 迁移必须比被迁移的东西活得久。

## 判据(全部做过变异测试)

- `TestMemoryBytes_ByBlockRef`:完整 ID / 唯一前缀可取回且逐字节一致;
  不存在→ok=false;过短前缀(歧义)→ok=false;无负载块→ok=false;空引用不 panic。
  变异(不校验 ok,把不存在当有)当场被 3 条断言抓住。
- `TestE2EMediaBytesAgainstBackfilledDB`(-tags e2e,读真回填库):
  10/10 块经 MediaBytes 取回的字节与块 data 的 sha256 完全一致。
- `TestKnowledgeUploadedMediaRetrievable` 重写为**条目目录自带字节**判据
  (不再是"向存储按 digest 读回");`TestKnowledgeUploadMediaNeedsNoStore`
  改为"上传无外部存储依赖也必须成功"(旧的 503 语义已随 CAS 消失)。

## 静态确认
`QueryMediaScored` / `internal/memory/media` import / `MediaAPI` /
`sdk.Media()` / `SetMediaStore` / `heldMediaDigests` 全仓均为 0 处
(仅剩 medialoop.go 一句历史注释)。

回归:memory / sdk / agent-core / knowledge / plugin / webui / seemedia /
multimodal 八包全绿;全仓生产编译 GREEN。
2026-10-12 16:52:43 +08:00

60 lines
1.4 KiB
Go
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.

//go:build e2e
package sdk
import (
"crypto/sha256"
"encoding/hex"
"os"
"testing"
"github.com/JianFeeeee/HomeAgent/internal/memory"
)
// 真实数据 e2e:CAS 淘汰后插件经 MediaBytes 取回的字节必须与回填库中
// 块 data 的 sha256 一致(也就是说:插件真拿到了那张图,不是空/错字节)。
func TestE2EMediaBytesAgainstBackfilledDB(t *testing.T) {
dbPath := os.Getenv("E2E_DB")
if dbPath == "" {
t.Skip("E2E_DB 未设置")
}
g, err := memory.NewGraphDB(dbPath)
if err != nil {
t.Fatal(err)
}
defer g.Close()
m := NewGraphMemoryWithPlugin("e2e", g)
ids, err := g.SampleFilledBlockIDs(10)
if err != nil || len(ids) == 0 {
t.Skipf("没有已回填块: %v", err)
}
ok := 0
for _, id := range ids {
want, err := g.BlockData(id)
if err != nil || len(want) == 0 {
t.Errorf("BlockData(%s): %v", id, err)
continue
}
wantSum := sha256.Sum256(want)
data, mime, got, err := m.MediaBytes(id)
if err != nil || !got {
t.Errorf("MediaBytes(%s): ok=%v err=%v", id, got, err)
continue
}
gotSum := sha256.Sum256(data)
if hex.EncodeToString(gotSum[:]) != hex.EncodeToString(wantSum[:]) {
t.Errorf("%s 字节不一致", id)
continue
}
if mime == "" {
t.Errorf("%s MIME 为空", id)
}
ok++
}
t.Logf("MediaBytes e2e:%d/%d 块字节与块 data 完全一致", ok, len(ids))
if ok == 0 {
t.Fatal("一个块都没能通过 MediaBytes 取回")
}
}