mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 04:18:44 +00:00
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:
1 parent
2aa3ca31ae
commit
5ff29d051d
1 file changed
+74
-21
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user