refactor(store/pg): error-propagate parseUUID in vault_links

All 16 sites migrated. Covers BatchCreateLinks (2 sites in loop with
fail-fast per Q7), CreateLink, DeleteLink, GetOutLinks, GetOutLinksBatch,
GetBacklinks, DeleteDocLinks, DeleteDocLinksByType, DeleteDocLinksByTypes.

BatchCreateLinks previously silently dropped bad UUIDs via loop-over-
validated-docs; post-P4 the parse layer rejects the entire batch on
first bad doc_id — a documented contract change per Q7.

Phase 4 Step 4b.7 of agent identity hardening (TD-1).
This commit is contained in:
viettranx committed 2026-04-11 21:22:23 +07:00
1 parent 2aa3ca31ae
commit 5ff29d051d
1 file changed
+74 -21
+74 -21
View File
@@ -80,11 +80,19 @@ func (s *PGVaultStore) CreateLinks(ctx context.Context, links []store.VaultLink)
ph := make([]string, 0, len(chunk))
now := time.Now().UTC()
for i, l := range chunk {
fromID, err := parseUUID(l.FromDocID)
if err != nil {
return fmt.Errorf("vault batch create links: from_doc_id: %w", err)
}
toID, err := parseUUID(l.ToDocID)
if err != nil {
return fmt.Errorf("vault batch create links: to_doc_id: %w", err)
}
b := i * paramsPerRow
ph = append(ph, fmt.Sprintf("($%d,$%d,$%d,$%d,$%d,$%d)", b+1, b+2, b+3, b+4, b+5, b+6))
args = append(args,
uuid.Must(uuid.NewV7()),
mustParseUUID(l.FromDocID), mustParseUUID(l.ToDocID),
fromID, toID,
l.LinkType, l.Context, now,
)
}
@@ -101,13 +109,19 @@ func (s *PGVaultStore) CreateLinks(ctx context.Context, links []store.VaultLink)
// CreateLink inserts a vault link, updating context on conflict.
// Validates same-tenant + same-team boundary before insert.
func (s *PGVaultStore) CreateLink(ctx context.Context, link *store.VaultLink) error {
fromID := mustParseUUID(link.FromDocID)
toID := mustParseUUID(link.ToDocID)
fromID, err := parseUUID(link.FromDocID)
if err != nil {
return fmt.Errorf("vault create link: from: %w", err)
}
toID, err := parseUUID(link.ToDocID)
if err != nil {
return fmt.Errorf("vault create link: to: %w", err)
}
// Verify both docs exist and belong to same tenant + team boundary.
var fromTenant, toTenant uuid.UUID
var fromTeamID, toTeamID *uuid.UUID
err := s.db.QueryRowContext(ctx,
err = s.db.QueryRowContext(ctx,
`SELECT tenant_id, team_id FROM vault_documents WHERE id = $1`, fromID,
).Scan(&fromTenant, &fromTeamID)
if err != nil {
@@ -149,9 +163,15 @@ func (s *PGVaultStore) CreateLink(ctx context.Context, link *store.VaultLink) er
// DeleteLink removes a vault link by ID, scoped by tenant via JOIN.
func (s *PGVaultStore) DeleteLink(ctx context.Context, tenantID, id string) error {
uid := mustParseUUID(id)
tid := mustParseUUID(tenantID)
_, err := s.db.ExecContext(ctx, `
uid, err := parseUUID(id)
if err != nil {
return fmt.Errorf("vault delete link: id: %w", err)
}
tid, err := parseUUID(tenantID)
if err != nil {
return fmt.Errorf("vault delete link: tenant: %w", err)
}
_, err = s.db.ExecContext(ctx, `
DELETE FROM vault_links vl
USING vault_documents vd
WHERE vl.id = $1 AND vl.from_doc_id = vd.id AND vd.tenant_id = $2`, uid, tid)
@@ -160,8 +180,14 @@ func (s *PGVaultStore) DeleteLink(ctx context.Context, tenantID, id string) erro
// GetOutLinks returns all links originating from a document, scoped by tenant.
func (s *PGVaultStore) GetOutLinks(ctx context.Context, tenantID, docID string) ([]store.VaultLink, error) {
uid := mustParseUUID(docID)
tid := mustParseUUID(tenantID)
uid, err := parseUUID(docID)
if err != nil {
return nil, fmt.Errorf("vault get out links: doc: %w", err)
}
tid, err := parseUUID(tenantID)
if err != nil {
return nil, fmt.Errorf("vault get out links: tenant: %w", err)
}
rows, err := s.db.QueryContext(ctx, `
SELECT vl.id, vl.from_doc_id, vl.to_doc_id, vl.link_type, vl.context, vl.created_at
FROM vault_links vl
@@ -180,7 +206,10 @@ func (s *PGVaultStore) GetOutLinksBatch(ctx context.Context, tenantID string, do
if len(docIDs) == 0 {
return nil, nil
}
tid := mustParseUUID(tenantID)
tid, err := parseUUID(tenantID)
if err != nil {
return nil, fmt.Errorf("vault get out links batch: %w", err)
}
rows, err := s.db.QueryContext(ctx, `
SELECT vl.id, vl.from_doc_id, vl.to_doc_id, vl.link_type, vl.context, vl.created_at
FROM vault_links vl
@@ -196,8 +225,14 @@ func (s *PGVaultStore) GetOutLinksBatch(ctx context.Context, tenantID string, do
// GetBacklinks returns enriched backlinks pointing to a document (single JOIN, LIMIT 100).
func (s *PGVaultStore) GetBacklinks(ctx context.Context, tenantID, docID string) ([]store.VaultBacklink, error) {
did := mustParseUUID(docID)
tid := mustParseUUID(tenantID)
did, err := parseUUID(docID)
if err != nil {
return nil, fmt.Errorf("vault backlinks: doc: %w", err)
}
tid, err := parseUUID(tenantID)
if err != nil {
return nil, fmt.Errorf("vault backlinks: tenant: %w", err)
}
rows, err := s.db.QueryContext(ctx, `
SELECT vl.from_doc_id, vl.context, vd.title, vd.path, vd.team_id
FROM vault_links vl
@@ -229,9 +264,15 @@ func (s *PGVaultStore) GetBacklinks(ctx context.Context, tenantID, docID string)
// DeleteDocLinks removes all links from or to a document, scoped by tenant.
func (s *PGVaultStore) DeleteDocLinks(ctx context.Context, tenantID, docID string) error {
uid := mustParseUUID(docID)
tid := mustParseUUID(tenantID)
_, err := s.db.ExecContext(ctx, `
uid, err := parseUUID(docID)
if err != nil {
return fmt.Errorf("vault delete doc links: doc: %w", err)
}
tid, err := parseUUID(tenantID)
if err != nil {
return fmt.Errorf("vault delete doc links: tenant: %w", err)
}
_, err = s.db.ExecContext(ctx, `
DELETE FROM vault_links vl
USING vault_documents vd
WHERE (vl.from_doc_id = $1 OR vl.to_doc_id = $1)
@@ -241,9 +282,15 @@ func (s *PGVaultStore) DeleteDocLinks(ctx context.Context, tenantID, docID strin
// DeleteDocLinksByType removes outbound links of a specific type from a document.
func (s *PGVaultStore) DeleteDocLinksByType(ctx context.Context, tenantID, docID, linkType string) error {
uid := mustParseUUID(docID)
tid := mustParseUUID(tenantID)
_, err := s.db.ExecContext(ctx, `
uid, err := parseUUID(docID)
if err != nil {
return fmt.Errorf("vault delete doc links by type: doc: %w", err)
}
tid, err := parseUUID(tenantID)
if err != nil {
return fmt.Errorf("vault delete doc links by type: tenant: %w", err)
}
_, err = s.db.ExecContext(ctx, `
DELETE FROM vault_links vl
USING vault_documents vd
WHERE vl.from_doc_id = $1
@@ -257,8 +304,14 @@ func (s *PGVaultStore) DeleteDocLinksByTypes(ctx context.Context, tenantID, docI
if len(types) == 0 {
return nil
}
uid := mustParseUUID(docID)
tid := mustParseUUID(tenantID)
uid, err := parseUUID(docID)
if err != nil {
return fmt.Errorf("vault delete doc links by types: doc: %w", err)
}
tid, err := parseUUID(tenantID)
if err != nil {
return fmt.Errorf("vault delete doc links by types: tenant: %w", err)
}
// Build IN clause with positional params: $3, $4, ...
params := []any{uid, tid}
placeholders := make([]string, len(types))
@@ -272,7 +325,7 @@ func (s *PGVaultStore) DeleteDocLinksByTypes(ctx context.Context, tenantID, docI
WHERE vl.from_doc_id = $1
AND vd.id = vl.from_doc_id AND vd.tenant_id = $2
AND vl.link_type IN (%s)`, strings.Join(placeholders, ","))
_, err := s.db.ExecContext(ctx, q, params...)
_, err = s.db.ExecContext(ctx, q, params...)
return err
}