From 32af80b28ca48d5188d9a06cf95a6d2eb5542634 Mon Sep 17 00:00:00 2001 From: viettranx Date: Sat, 11 Apr 2026 11:37:27 +0700 Subject: [PATCH] refactor(store/pg): propagate error from optAgentUUID wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/store/pg/vault_documents.go | 41 +++++++++++++++---- .../store/pg/vault_documents_enrichment.go | 17 ++++++-- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/internal/store/pg/vault_documents.go b/internal/store/pg/vault_documents.go index 66f7bb92..8267edca 100644 --- a/internal/store/pg/vault_documents.go +++ b/internal/store/pg/vault_documents.go @@ -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) diff --git a/internal/store/pg/vault_documents_enrichment.go b/internal/store/pg/vault_documents_enrichment.go index fbbbf1b0..5d7d9307 100644 --- a/internal/store/pg/vault_documents_enrichment.go +++ b/internal/store/pg/vault_documents_enrichment.go @@ -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)