mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 08:15:29 +00:00
- Add vault_documents.chat_id + composite index (migration 000056) - Filter vault_search by chat_id when team.workspace_scope=isolated - Stamp chat_id on AfterWrite/AfterWriteMedia for isolated teams - Deny cross-chat vault_read in isolated teams (M2 fix) - RunContext.TeamIsolated flag resolved once per run - Fallback WorkspaceChatID → ChatID in loop_context for entry points that don't set WorkspaceChatID explicitly (WS direct, HTTP, cron) Fixes cross-chat doc leak where agent in chat A could see vault docs from chat B within the same isolated team.
90 lines
2.5 KiB
Go
90 lines
2.5 KiB
Go
package pg
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// vaultDocRow is an sqlx scan struct for vault_documents SELECT queries.
|
|
// IDs are scanned as uuid.UUID then converted to string for VaultDocument.
|
|
// Metadata is scanned as raw JSON then unmarshalled post-scan.
|
|
type vaultDocRow struct {
|
|
ID uuid.UUID `db:"id"`
|
|
TenantID uuid.UUID `db:"tenant_id"`
|
|
AgentID *uuid.UUID `db:"agent_id"`
|
|
TeamID *uuid.UUID `db:"team_id"`
|
|
ChatID *string `db:"chat_id"`
|
|
Scope string `db:"scope"`
|
|
CustomScope *string `db:"custom_scope"`
|
|
Path string `db:"path"`
|
|
PathBasename string `db:"path_basename"`
|
|
Title string `db:"title"`
|
|
DocType string `db:"doc_type"`
|
|
ContentHash string `db:"content_hash"`
|
|
Summary string `db:"summary"`
|
|
MetaJSON []byte `db:"metadata"`
|
|
CreatedAt time.Time `db:"created_at"`
|
|
UpdatedAt time.Time `db:"updated_at"`
|
|
}
|
|
|
|
// toVaultDocument converts a vaultDocRow to store.VaultDocument.
|
|
func (r *vaultDocRow) toVaultDocument() store.VaultDocument {
|
|
doc := store.VaultDocument{
|
|
ID: r.ID.String(),
|
|
TenantID: r.TenantID.String(),
|
|
Scope: r.Scope,
|
|
CustomScope: r.CustomScope,
|
|
Path: r.Path,
|
|
PathBasename: r.PathBasename,
|
|
Title: r.Title,
|
|
DocType: r.DocType,
|
|
ContentHash: r.ContentHash,
|
|
Summary: r.Summary,
|
|
CreatedAt: r.CreatedAt,
|
|
UpdatedAt: r.UpdatedAt,
|
|
}
|
|
if r.AgentID != nil {
|
|
s := r.AgentID.String()
|
|
doc.AgentID = &s
|
|
}
|
|
if r.TeamID != nil {
|
|
s := r.TeamID.String()
|
|
doc.TeamID = &s
|
|
}
|
|
if r.ChatID != nil {
|
|
s := *r.ChatID
|
|
doc.ChatID = &s
|
|
}
|
|
if len(r.MetaJSON) > 0 {
|
|
json.Unmarshal(r.MetaJSON, &doc.Metadata) //nolint:errcheck
|
|
}
|
|
return doc
|
|
}
|
|
|
|
// vaultDocRowsToDocs converts a slice of vaultDocRow to store.VaultDocument.
|
|
func vaultDocRowsToDocs(rows []vaultDocRow) []store.VaultDocument {
|
|
docs := make([]store.VaultDocument, len(rows))
|
|
for i := range rows {
|
|
docs[i] = rows[i].toVaultDocument()
|
|
}
|
|
return docs
|
|
}
|
|
|
|
// vaultSearchRow extends vaultDocRow with a computed score column for search queries.
|
|
type vaultSearchRow struct {
|
|
vaultDocRow
|
|
Score float64 `db:"score"`
|
|
}
|
|
|
|
// toVaultSearchResult converts a vaultSearchRow to store.VaultSearchResult.
|
|
func (r *vaultSearchRow) toVaultSearchResult(source string) store.VaultSearchResult {
|
|
return store.VaultSearchResult{
|
|
Document: r.vaultDocRow.toVaultDocument(),
|
|
Score: r.Score,
|
|
Source: source,
|
|
}
|
|
}
|