refactor(store/pg): propagate error from optAgentUUID wrapper

optAgentUUID now returns (*uuid.UUID, error). All three known callers
updated to check the error and wrap with context:

1. vault_documents.go UpsertDocument — also migrates tenant_id and
   team_id to parseUUID in the same hot path.
2. vault_documents.go Search — migrates tenant_id and agent_id.
3. vault_documents_enrichment.go FindSimilarDocs — migrates tenant_id,
   agent_id, and doc_id.

Pre-Phase-4, the wrapper silently converted garbage agent_id strings to
uuid.Nil, letting the downstream INSERT hit a FK violation with cryptic
PG code 23503. Now bad input fails at the caller boundary with a clear
Go error wrapping the raw value.

Phase 4 Step 4e of agent identity hardening (TD-1).
Covers sub-step 4b migrations for vault_documents.go and
vault_documents_enrichment.go as a side effect.
This commit is contained in:
viettranx committed 2026-04-11 21:22:23 +07:00
1 parent 9a8eb396b0
commit 32af80b28c
2 files changed
+45 -13

No files matched your search

+32 -9
View File
@@ -55,18 +55,32 @@ func (s *PGVaultStore) SetEmbeddingProvider(provider store.EmbeddingProvider) {
func (s *PGVaultStore) Close() error { return nil }
// optAgentUUID converts a nullable *string agent_id to *uuid.UUID for SQL.
func optAgentUUID(agentID *string) *uuid.UUID {
// Returns (nil, nil) when the input is nil or empty — a legitimate SQL NULL.
// Returns (nil, error) on a non-empty, non-UUID input — propagating the error
// prevents silent-nil writes that would otherwise corrupt data (pre-Phase-4
// this helper silently returned nil on garbage input).
// See docs/agent-identity-conventions.md (Phase 6).
func optAgentUUID(agentID *string) (*uuid.UUID, error) {
if agentID == nil || *agentID == "" {
return nil
return nil, nil
}
u := mustParseUUID(*agentID)
return &u
u, err := parseUUID(*agentID)
if err != nil {
return nil, err
}
return &u, nil
}
// UpsertDocument inserts or updates a vault document.
func (s *PGVaultStore) UpsertDocument(ctx context.Context, doc *store.VaultDocument) error {
tid := mustParseUUID(doc.TenantID)
aid := optAgentUUID(doc.AgentID)
tid, err := parseUUID(doc.TenantID)
if err != nil {
return fmt.Errorf("vault upsert: tenant: %w", err)
}
aid, err := optAgentUUID(doc.AgentID)
if err != nil {
return fmt.Errorf("vault upsert: agent: %w", err)
}
now := time.Now().UTC()
meta, err := json.Marshal(doc.Metadata)
@@ -91,7 +105,10 @@ func (s *PGVaultStore) UpsertDocument(ctx context.Context, doc *store.VaultDocum
var teamID *uuid.UUID
if doc.TeamID != nil && *doc.TeamID != "" {
t := mustParseUUID(*doc.TeamID)
t, err := parseUUID(*doc.TeamID)
if err != nil {
return fmt.Errorf("vault upsert: team: %w", err)
}
teamID = &t
}
@@ -358,8 +375,14 @@ func (s *PGVaultStore) UpdateHash(ctx context.Context, tenantID, id, newHash str
// Search performs hybrid FTS + vector search on vault_documents.
func (s *PGVaultStore) Search(ctx context.Context, opts store.VaultSearchOptions) ([]store.VaultSearchResult, error) {
tid := mustParseUUID(opts.TenantID)
aid := optAgentUUID(&opts.AgentID) // empty string → nil → no agent filter
tid, err := parseUUID(opts.TenantID)
if err != nil {
return nil, fmt.Errorf("vault search: tenant: %w", err)
}
aid, err := optAgentUUID(&opts.AgentID) // empty string → nil → no agent filter
if err != nil {
return nil, fmt.Errorf("vault search: agent: %w", err)
}
// Build team filter for search sub-queries.
tf := buildSearchTeamFilter(opts.TeamID, opts.TeamIDs)
@@ -46,13 +46,22 @@ func (s *PGVaultStore) UpdateSummaryAndReembed(ctx context.Context, tenantID, do
// Returns top-N neighbors excluding the source doc itself.
// Empty agentID means no agent filter.
func (s *PGVaultStore) FindSimilarDocs(ctx context.Context, tenantID, agentID, docID string, limit int) ([]store.VaultSearchResult, error) {
tid := mustParseUUID(tenantID)
aid := optAgentUUID(&agentID)
did := mustParseUUID(docID)
tid, err := parseUUID(tenantID)
if err != nil {
return nil, fmt.Errorf("find similar: tenant: %w", err)
}
aid, err := optAgentUUID(&agentID)
if err != nil {
return nil, fmt.Errorf("find similar: agent: %w", err)
}
did, err := parseUUID(docID)
if err != nil {
return nil, fmt.Errorf("find similar: doc: %w", err)
}
// Fetch source embedding.
var embStr *string
err := s.db.QueryRowContext(ctx,
err = s.db.QueryRowContext(ctx,
`SELECT embedding::text FROM vault_documents WHERE id = $1 AND tenant_id = $2`,
did, tid,
).Scan(&embStr)