Add multi-instance groundwork: blobstore seam for artifacts

Details (original message):
Add multi-instance groundwork: blobstore seam for artifacts, PG LISTEN/NOTIFY event fanout, and per-tenant rate limiting with native Redis backend
This commit is contained in:
weichen
2026-09-30 02:39:50 +00:00
parent 4fdbfbc9a2
commit 29f14ac5f2
16 changed files with 593 additions and 41 deletions
+11 -27
View File
@@ -5,8 +5,6 @@ import (
"database/sql"
"errors"
"fmt"
"os"
"path/filepath"
"time"
core "agent-system-core"
@@ -16,18 +14,6 @@ import (
// 文件写走 临时文件 → rename → 落库:崩溃残留只可能产生无主孤立文件,
// 不产生库里有行、对象缺失的悬空引用。
func (s *Store) writeBlobAtomic(path string, content []byte) error {
tmp := path + ".tmp-" + newID("x")
if err := os.WriteFile(tmp, content, 0o644); err != nil {
return err
}
if err := os.Rename(tmp, path); err != nil {
os.Remove(tmp)
return err
}
return nil
}
func (s *Store) CreateArtifact(a *core.Artifact, content []byte) error {
if a.ID == "" {
a.ID = newID("art")
@@ -35,14 +21,14 @@ func (s *Store) CreateArtifact(a *core.Artifact, content []byte) error {
now := time.Now().UTC()
a.CreatedAt, a.UpdatedAt = now, now
a.Size = int64(len(content))
path := filepath.Join(s.dataDir, a.ID)
if err := s.writeBlobAtomic(path, content); err != nil {
ref, err := s.blobs.Put(a.ID, content)
if err != nil {
return err
}
_, err := s.db.Exec(s.q(`INSERT INTO artifacts(id,project_id,owner,name,kind,mime,size,source_run,content_path,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)`),
a.ID, a.ProjectID, a.Owner, a.Name, a.Kind, a.MIME, a.Size, a.SourceRun, path, nowISO(), nowISO())
_, err = s.db.Exec(s.q(`INSERT INTO artifacts(id,project_id,owner,name,kind,mime,size,source_run,content_path,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)`),
a.ID, a.ProjectID, a.Owner, a.Name, a.Kind, a.MIME, a.Size, a.SourceRun, ref, nowISO(), nowISO())
if err != nil {
os.Remove(path) // 落库失败:回收对象,不留悬空
_ = s.blobs.Delete(ref) // 落库失败:回收对象,不留悬空
return err
}
return nil
@@ -57,7 +43,8 @@ func (s *Store) UpdateArtifactContent(owner, id string, content []byte) error {
if err != nil {
return err
}
if err := s.writeBlobAtomic(path, content); err != nil {
// 同 ID 原子覆写(临时文件+rename);行内引用不变,避免行/对象漂移。
if _, err := s.blobs.Put(id, content); err != nil {
return err
}
res, err := s.db.Exec(s.q(`UPDATE artifacts SET size=?, updated_at=? WHERE id=? AND owner=?`), len(content), nowISO(), id, owner)
@@ -96,15 +83,15 @@ func (s *Store) GetArtifact(owner, id string) (core.Artifact, error) {
}
func (s *Store) ArtifactContent(owner, id string) ([]byte, error) {
var path string
err := s.db.QueryRow(s.q(`SELECT content_path FROM artifacts WHERE id=? AND owner=?`), id, owner).Scan(&path)
var ref string
err := s.db.QueryRow(s.q(`SELECT content_path FROM artifacts WHERE id=? AND owner=?`), id, owner).Scan(&ref)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
if err != nil {
return nil, err
}
return os.ReadFile(path)
return s.blobs.Get(ref)
}
func (s *Store) ListArtifacts(owner, projectID string) ([]core.Artifact, error) {
@@ -140,10 +127,7 @@ func (s *Store) DeleteArtifact(owner, id string) error {
// 联动清理检索索引(知识行 + 向量;失败不阻断删除,残留可由重建索引收敛)。
_ = s.DeleteKnowledgeByRef(owner, id)
_ = s.DeleteKnowledgeVectors(context.Background(), owner, id)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
return err
}
return nil
return s.blobs.Delete(path)
}
// ---- HITL 卡片(resolve 原子化:条件更新消灭先查后改窗口)----