{"record":{"id":"2deb37c9d3617eea","repo":"Tencent/WeKnora","slug":"check-forgotten-source-w","errorCode":null,"errorMessage":"check forgotten source: %w","messagePattern":"check forgotten source: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/memory/service.go","lineNumber":337,"sourceCode":"\t}\n\n\t// Something the user deliberately forgot must not come back the next time\n\t// distillation reads the message it came from. Two checks, because the\n\t// re-derived statement is usually worded slightly differently and so does\n\t// not hash the same: the exact fingerprint, and whether the message it came\n\t// from already produced a memory the user rejected.\n\tforgotten, err := s.repo.HasTombstone(ctx, scope, types.MemoryFingerprint(content))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"check forgotten memory: %w\", err)\n\t}\n\tif !forgotten && item.SourceMessageID != \"\" && item.Origin == types.MemoryOriginExtracted {\n\t\t// Only the background path is gated this way. An explicit \"remember\n\t\t// this\" is the user asking again, and must always win.\n\t\tforgotten, err = s.repo.HasTombstoneForMessage(\n\t\t\tctx, scope, item.SourceMessageID, rejectedMessageWindow,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"check forgotten source: %w\", err)\n\t\t}\n\t}\n\tif forgotten {\n\t\tlogger.Infof(ctx, \"memory: skipped a statement the user previously deleted\")\n\t\treturn nil, ErrPreviouslyForgotten\n\t}\n\tif _, err := s.repo.EnsureSubject(ctx, scope); err != nil {\n\t\treturn nil, fmt.Errorf(\"ensure memory subject: %w\", err)\n\t}\n\n\ttopic := types.SanitizeMemoryTopic(item.Topic)\n\tnormalizedKey := types.MemoryItemKey(topic, content)\n\texisting, err := s.repo.FindActiveByKey(ctx, scope, normalizedKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"find conflicting memory: %w\", err)\n\t}\n\tif existing != nil && types.SanitizeMemoryContent(existing.Content) == content {\n\t\t// Same statement about the same topic: nothing changed, so keep the","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/memory/service.go#L319-L355","documentation":"The second tombstone check in write(): when an extracted memory has a SourceMessageID, HasTombstoneForMessage checks whether the user already rejected a memory derived from that message. A repo failure here aborts the write with this wrapped error, preserving the invariant that rejected messages never regenerate memories.","triggerScenarios":"write() called with item.Origin == extracted and non-empty SourceMessageID, and repo.HasTombstoneForMessage returns an error (DB failure, timeout, canceled context).","commonSituations":"DB outage during background distillation; missing index/migration for message-scoped tombstone lookups; slow query timing out under large rejectedMessageWindow scans.","solutions":["Check DB health and slow-query logs for the HasTombstoneForMessage query","Ensure the tombstone-by-message index exists and migrations are current","Retry the write after the transient error — the source messages are re-read later","Tune rejectedMessageWindow if scans are timing out on large windows"],"exampleFix":"null","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isForgottenSourceErr(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"check forgotten source\")\n}","tryCatchPattern":"item, err := memSvc.CreateItem(ctx, scope, item)\nif err != nil {\n\tif isForgottenSourceErr(err) {\n\t\treturn retryWithBackoff(ctx, 3, call) // aborted before write; safe to retry\n\t}\n\treturn err\n}","preventionTips":["Index tombstones by (scope, source_message_id) so the lookup stays fast","Ensure rejectedMessageWindow isn't so large that scans time out","Run migrations before deploying distillation workers","Alert on repeated wrapped DB errors in the memory write path"],"tags":["memory","database","tombstone"],"backgroundTag":"database-query-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}