Files
weichen 0634ffb8ee fix(storage): deterministic ID ordering in memory-mode ListProjectRefs
Memory CAS iterated memoryRefs map (random order) while PG path uses
ORDER BY id ASC; asset dedup-hint 'first reference unflagged' logic
depends on stable order. Flaky TestUploadProjectAsset_DedupHint
reproduced 2/20 rounds before fix, green 30/30 after.
2026-09-25 13:17:25 +00:00

69 lines
1.8 KiB
Go

package storage
import (
"context"
"errors"
"fmt"
"io"
"sort"
)
// ListProjectRefs 列出项目下全部 CAS 文件引用(智能体任务上下文资产池)。
func (c *CAS) ListProjectRefs(ctx context.Context, projectID int64) ([]*FileReference, error) {
if projectID <= 0 {
return nil, nil
}
if c.pool != nil {
rows, err := c.pool.Query(ctx, `
SELECT id, project_id, user_id, blob_sha256, file_name, file_category, COALESCE(summary, ''), created_at
FROM file_references
WHERE project_id = $1
ORDER BY id ASC
`, projectID)
if err != nil {
return nil, fmt.Errorf("list project refs: %w", err)
}
defer rows.Close()
var refs []*FileReference
for rows.Next() {
ref := &FileReference{}
if err := rows.Scan(&ref.ID, &ref.ProjectID, &ref.UserID, &ref.BlobSHA256, &ref.FileName, &ref.FileCategory, &ref.Summary, &ref.CreatedAt); err != nil {
continue
}
refs = append(refs, ref)
}
return refs, rows.Err()
}
c.mu.RLock()
defer c.mu.RUnlock()
var refs []*FileReference
for _, ref := range c.memoryRefs {
if ref.ProjectID == projectID {
cpy := *ref
refs = append(refs, &cpy)
}
}
// 与 PG 路径 ORDER BY id ASC 对齐:map 迭代顺序随机,
// 会令 handler 的「首条引用不标 dedup」判定不稳定(偶发测试失败)。
sort.Slice(refs, func(i, j int) bool { return refs[i].ID < refs[j].ID })
return refs, nil
}
// ReadRefBytes 读取文件引用对应的完整二进制内容。
func (c *CAS) ReadRefBytes(ctx context.Context, refID int64) ([]byte, *FileReference, error) {
rc, ref, err := c.OpenFileByRef(ctx, refID)
if err != nil {
return nil, nil, err
}
defer rc.Close()
data, err := io.ReadAll(rc)
if err != nil {
return nil, ref, err
}
if len(data) == 0 {
return nil, ref, errors.New("asset file is empty")
}
return data, ref, nil
}