Skip to content

Commit c8664f5

Browse files
couragehongclaude
andcommitted
fix(service): base64-encode envector cipher before Vault.DecryptScores
The Vault gRPC `DecryptScoresRequest.encrypted_blob_b64` field is a proto3 `string` (valid-UTF-8 only). envector's `Score` returns raw cipher bytes, not a base64 string — so casting `string(blobs[0])` and passing it through trips proto3 string-validation: grpc: error while marshaling: string field contains invalid UTF-8 The bug surfaced as silent zero-result recall (fail point hidden inside the recall pipeline; envector cluster row count rises after capture but recall returns 0 hits) and an "invalid UTF-8" warning during the capture novelty check. Two call sites, both fixed identically: recall.go:163 (RecallService.searchSingle) capture.go:276 (CaptureService novelty-check decrypt) In both, encode the cipher with `base64.StdEncoding.EncodeToString` before passing it as the `EncryptedBlobB64` argument. The field name itself — `...B64` — was the contract; v0.4's port omitted the encoding step on both paths. Test plan --------- - /rune:capture an entry, /rune:recall a related query → top hit returned with score > 0.5 (verified end-to-end on local stack). - Korean-only capture no longer warns "invalid UTF-8" during novelty check. - envector cluster row count + capture_log.jsonl entry both increment per capture (already confirmed pre-fix; unchanged here). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent de17146 commit c8664f5

2 files changed

Lines changed: 14 additions & 3 deletions

File tree

internal/service/capture.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package service
1010

1111
import (
1212
"context"
13+
"encoding/base64"
1314
"encoding/json"
1415
"errors"
1516
"fmt"
@@ -273,7 +274,11 @@ func (s *CaptureService) runNoveltyCheck(ctx context.Context, embeddingText stri
273274
return &domain.NoveltyInfo{Score: 1.0, Class: "novel"}, nil, nil
274275
}
275276

276-
entries, err := s.Vault.DecryptScores(ctx, string(blobs[0]), 3)
277+
// Vault.DecryptScores's `EncryptedBlobB64` is a proto3 string field —
278+
// envector's raw cipher bytes must be base64-encoded before sending.
279+
// Mirrors recall.searchSingle.
280+
encryptedBlobB64 := base64.StdEncoding.EncodeToString(blobs[0])
281+
entries, err := s.Vault.DecryptScores(ctx, encryptedBlobB64, 3)
277282
if err != nil || len(entries) == 0 {
278283
slog.Warn("novelty check: decrypt failed (non-fatal)", "err", err)
279284
return &domain.NoveltyInfo{Score: 1.0, Class: "novel"}, nil, nil

internal/service/recall.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,14 @@ func (s *RecallService) searchSingle(ctx context.Context, vec []float32, topk in
159159
return nil, nil
160160
}
161161

162-
// Vault decrypt scores
163-
entries, err := s.Vault.DecryptScores(ctx, string(blobs[0]), topk)
162+
// Vault decrypt scores. The Vault RPC field is `EncryptedBlobB64`
163+
// (proto3 `string`, valid-UTF-8 only) — envector returns raw cipher
164+
// bytes, so we must base64-encode before sending. A direct
165+
// `string(blobs[0])` cast pushes random cipher bytes through the
166+
// proto3 string-validation path and trips
167+
// "grpc: error while marshaling: string field contains invalid UTF-8".
168+
encryptedBlobB64 := base64.StdEncoding.EncodeToString(blobs[0])
169+
entries, err := s.Vault.DecryptScores(ctx, encryptedBlobB64, topk)
164170
if err != nil {
165171
return nil, fmt.Errorf("vault decrypt scores: %w", err)
166172
}

0 commit comments

Comments
 (0)