Skip to content

fix: dashboard v1.0.2 — date bugs, analytics stats, relation detection, heartbeat config#22

Merged
thebtf merged 5 commits into
mainfrom
fix/dashboard-v1.0.2
Mar 19, 2026
Merged

fix: dashboard v1.0.2 — date bugs, analytics stats, relation detection, heartbeat config#22
thebtf merged 5 commits into
mainfrom
fix/dashboard-v1.0.2

Conversation

@thebtf
Copy link
Copy Markdown
Owner

@thebtf thebtf commented Mar 19, 2026

Summary

Production bug fixes + relation detection (core knowledge graph feature).

Phase 1: Frontend Fixes

  • safeDateFormat() / safeAbsoluteDate() — handles null dates (no more "Invalid Date")
  • VaultView: correct date formatting + encryption setup helper text
  • AnalyticsView: fixed field mapping (snake_case JSON tags on RetrievalStats)
  • Recent queries: fixed field name mapping (timestamp → last_used)

Phase 2: Relation Detection (CRITICAL)

  • NEW internal/relation/detector.go (290 lines) — async relation detection
  • On every new observation: embed → pgvector similarity search → classify → store relations
  • Heuristics: supersedes (>0.92), fixes (bugfix→discovery), explains, contradicts, evolves_from
  • Conflicts auto-created for supersedes + contradicts
  • Non-blocking: 256-buffer channel, 5s timeout per detection, graceful shutdown
  • Wired into ObservationStore.StoreObservation via interface (no circular deps)

Phase 3: Heartbeat Config

  • OpenClaw plugin: heartbeat.ingest config (default: false)
  • Filters 10 low-value tool patterns (heartbeat, ping, status, noop, etc.)
  • Plugin bumped to 1.3.1

Test plan

  • go build ./cmd/worker/ compiles
  • No "Invalid Date" on vault/analytics pages
  • Analytics stats show non-zero values
  • New observation triggers relation detection (check /api/relations/stats)
  • FalkorDB graph populated after relation detection
  • Heartbeat tools not ingested by default

Summary by CodeRabbit

  • New Features

    • Добавлено асинхронное обнаружение связанных наблюдений и конфликтов.
    • Добавлена фильтрация heartbeat-событий с опцией включения ingestion.
    • Добавлены безопасные форматтеры дат и их использование в аналитике и хранилище.
  • Chores

    • Обновлена версия плагина до 1.3.1.
    • Переименование основного токена: рекомендуется ENGRAM_AUTH_ADMIN_TOKEN; старое имя помечено как устаревшее.
    • Обновлена сериализация статистики поиска (JSON-ключи).

thebtf added 4 commits March 19, 2026 13:57
- Add safeDateFormat() and safeAbsoluteDate() helpers that return "—"
  for null/undefined/invalid date values
- VaultView: use safeAbsoluteDate for credential created_at dates
- AnalyticsView: use safeDateFormat for recent queries and search misses
- Fix fetchRecentSearches to unwrap {queries:[...]} response envelope
  and map Go field names (timestamp->last_used, results->count)
- Add vault encryption helper text when encryption is disabled
Without json tags, Go's encoding/json marshals PascalCase field names
(e.g., TotalRequests) but the dashboard frontend expects snake_case
(e.g., total_requests). Add explicit json struct tags to all fields.
Add relation.Detector that runs asynchronously after observation creation:
- Vector similarity search finds top 20 candidates in same project
- Heuristic classification: supersedes, fixes, explains, contradicts, evolves_from
- Concept overlap (Jaccard similarity) for finer-grained relation typing
- Conflict auto-detection for supersedes and contradicts relations
- Non-blocking queue (256 buffer) with graceful shutdown on context cancellation
- Wired into ObservationStore via RelationDetector interface (avoids circular imports)
- Conditionally enabled only when embedding service and vector client are available
Add heartbeat.ingest config option (default: false) to control whether
heartbeat/keep-alive tool events are sent to engram for ingestion.
Low-value tool names (heartbeat, ping, status, noop, etc.) are now
filtered out in the after-tool-call hook unless explicitly opted in.

Bump openclaw-engram version 1.3.0 → 1.3.1.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 19, 2026

Walkthrough

Добавлена асинхронная система обнаружения связей между наблюдениями на основе векторного поиска: интерфейс RelationDetector и фоновой Detector, интеграция в ObservationStore и воркер. Дополнительно — heartbeat-конфиг и обработка heartbeat-инструментов в плагине, улучшенные форматтеры дат и изменения в UI/API-ответах, мелкие правки пакета.

Changes

Cohort / File(s) Summary
Детектор связей и интеграция в хранилище
internal/relation/detector.go, internal/db/gorm/observation_store.go
Добавлен Detector с очередью, методами Start, Enqueue, Detect; в ObservationStore — интерфейс RelationDetector, поле relationDetector и метод SetRelationDetector. После создания наблюдения вызывается relationDetector.Enqueue(...).
Инициализация в воркере
internal/worker/service.go
При наличии сервиса эмбеддинга и vector client создаётся и регистрируется детектор, запускается в фоне. Обновлён формат RetrievalStats (json-тэги) и сообщение по переменным окружения аутентификации (добавлена депрекейт-заметка).
Плагин: конфиг и хук
plugin/openclaw-engram/src/config.ts, plugin/openclaw-engram/src/hooks/after-tool-call.ts, plugin/openclaw-engram/package.json
Добавлена секция heartbeat.ingest (по умолчанию false). В хуке after-tool-call добавлен allowlist HEARTBEAT_TOOLS и логика пропуска при отключённом ingest. Пакет плагина версия обновлена 1.3.0 → 1.3.1.
UI: утилиты и API
ui/src/utils/formatters.ts, ui/src/utils/api.ts
Добавлены экспортируемые safeDateFormat и safeAbsoluteDate с защитой от некорректных значений. fetchRecentSearches теперь ожидает объект ответа и маппит response.queriesRecentQuery[].
UI: представления
ui/src/views/AnalyticsView.vue, ui/src/views/VaultView.vue
Заменены вызовы форматтеров на новые safeDateFormat/safeAbsoluteDate. В VaultView при отключённом шифровании отображается команда openssl rand -hex 32 и подсказка про ENGRAM_VAULT_KEY.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ObsStore as ObservationStore
    participant Detector as RelationDetector
    participant Queue as RequestQueue
    participant Vector as VectorClient
    participant DB as Database

    Client->>ObsStore: StoreObservation(obs)
    ObsStore->>DB: Create observation
    DB-->>ObsStore: obsID
    ObsStore->>Detector: Enqueue(obsID, project)
    Detector->>Queue: Push request (non-blocking)
    alt queue full
        Queue-->>Detector: Drop + warn
    else queued
        Queue-->>Detector: OK
    end

    Note over Detector,Queue: Фоновый цикл потребляет запросы
    Detector->>Queue: Pop request
    Queue-->>Detector: detectRequest
    Detector->>DB: Load observation by obsID
    DB-->>Detector: observation data
    Detector->>Vector: Similarity search (project filter)
    Vector-->>Detector: candidate IDs + similarities
    Detector->>Detector: classifyRelation / buildSimilarityMap
    Detector->>DB: Store ObservationRelation / Conflict records
    DB-->>Detector: ack
    Detector->>Detector: log completion
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Я в норах векторов прыг-скок бегал,

Связи нашёл, и очередь завёл,
Heartbeat тихо мимо просвистал,
Даты отформатировал, версию поднял.
Пусть код шевелится — я рад и пою!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: frontend date formatting fixes, analytics stats corrections, relation detection feature, and heartbeat configuration. It covers all four major themes of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dashboard-v1.0.2
📝 Coding Plan
  • Generate coding plan for human review comments

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.3)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on enhancing the stability and intelligence of the system by addressing several dashboard-related bugs and introducing a foundational knowledge graph feature. It ensures a more robust user interface experience by fixing date display issues and improving analytics data consistency. Concurrently, it integrates an asynchronous relation detection mechanism to automatically identify connections and conflicts between observations, significantly advancing the system's ability to build a coherent knowledge base. Additionally, it refines data ingestion by allowing users to filter out irrelevant 'heartbeat' events from the OpenClaw plugin, leading to cleaner and more meaningful data.

Highlights

  • Frontend Bug Fixes: Implemented safeDateFormat() and safeAbsoluteDate() utilities to gracefully handle null or invalid date values, preventing 'Invalid Date' displays across the UI. Corrected date formatting in VaultView and AnalyticsView, and fixed field mapping for RetrievalStats (snake_case JSON tags) and recent queries (timestamp to last_used).
  • Asynchronous Relation Detection: Introduced a new internal/relation/detector.go service for async relation and conflict detection. This critical feature processes new observations by embedding them, performing vector similarity searches, classifying relations (e.g., supersedes, fixes, explains, contradicts, evolves_from), and storing the results. It operates non-blockingly with a buffered channel, a 5-second timeout per detection, and graceful shutdown, integrating seamlessly into the ObservationStore.
  • Heartbeat Tool Filtering in OpenClaw Plugin: Added a new heartbeat.ingest configuration option (default: false) to the OpenClaw plugin. This allows filtering out common low-value 'heartbeat' or 'keep-alive' tool patterns (e.g., ping, status, noop) from being ingested, reducing noise in the observation store. The plugin version has been bumped to 1.3.1.
  • Improved Authentication Environment Variable Handling: Updated error messages and added a deprecation warning for the ENGRAM_API_TOKEN environment variable, guiding users to use the new ENGRAM_AUTH_ADMIN_TOKEN for better clarity and future compatibility.
  • Vault Encryption Setup Helper Text: Added helpful instructions within the VaultView to guide users on how to enable vault encryption when it is currently disabled, improving user experience for security configuration.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several valuable fixes and a major new feature for relation detection. The frontend fixes for date formatting and analytics stats are well-implemented. The new heartbeat configuration provides useful filtering for noisy tool events. The core of this PR, the asynchronous relation detection in internal/relation/detector.go, is a significant addition. My review focuses on ensuring its robustness, particularly around graceful shutdown. I've identified one high-severity issue related to context propagation that could impact service shutdown, and I've provided a detailed suggestion to address it. The rest of the changes look solid.

Comment thread internal/relation/detector.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
ui/src/utils/formatters.ts (2)

46-51: Избыточная тернарная операция.

На строке 48 тернарная операция typeof value === 'number' ? value : value всегда возвращает value, что избыточно. Возможно, подразумевалось преобразование Unix timestamp (в секундах) в миллисекунды.

♻️ Предлагаемое исправление
 export function safeDateFormat(value: string | number | null | undefined): string {
   if (value === null || value === undefined || value === '' || value === 0) return '\u2014'
-  const date = new Date(typeof value === 'number' ? value : value)
+  const date = new Date(value)
   if (isNaN(date.getTime())) return '\u2014'
   return formatRelativeTime(date.getTime())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/utils/formatters.ts` around lines 46 - 51, The ternary in
safeDateFormat always returns value; update the date construction to handle
numeric Unix timestamps correctly: when value is a number treat it as seconds
and convert to milliseconds (value * 1000) before passing to new Date, otherwise
pass the string value directly; modify the new Date call inside safeDateFormat
(referencing safeDateFormat and formatRelativeTime) to use this conversion and
remove the redundant ternary.

56-61: Аналогичная избыточная тернарная операция.

Та же проблема на строке 58 — тернарная операция не выполняет никакого преобразования.

♻️ Предлагаемое исправление
 export function safeAbsoluteDate(value: string | number | null | undefined): string {
   if (value === null || value === undefined || value === '' || value === 0) return '\u2014'
-  const date = new Date(typeof value === 'number' ? value : value)
+  const date = new Date(value)
   if (isNaN(date.getTime())) return '\u2014'
   return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/utils/formatters.ts` around lines 56 - 61, The ternary in
safeAbsoluteDate is redundant (new Date(typeof value === 'number' ? value :
value)); update the Date construction to pass value directly (e.g., new
Date(value)) so that the date variable is created correctly without the no-op
ternary. Keep the existing early-return checks and isNaN(date.getTime()) logic
unchanged.
internal/relation/detector.go (2)

328-351: Потенциальная проблема с дубликатами в b.Concepts.

Расчёт Jaccard предполагает, что b.Concepts не содержит дубликатов. Если дубликаты есть, intersection может быть подсчитан неправильно (один элемент из set может быть найден несколько раз).

♻️ Предложение: использовать set для второго массива
 func conceptOverlap(a, b *models.Observation) float64 {
 	if len(a.Concepts) == 0 || len(b.Concepts) == 0 {
 		return 0
 	}

-	set := make(map[string]bool, len(a.Concepts))
+	setA := make(map[string]bool, len(a.Concepts))
 	for _, c := range a.Concepts {
-		set[c] = true
+		setA[c] = true
 	}

+	setB := make(map[string]bool, len(b.Concepts))
+	for _, c := range b.Concepts {
+		setB[c] = true
+	}
+
 	var intersection int
-	for _, c := range b.Concepts {
-		if set[c] {
+	for c := range setB {
+		if setA[c] {
 			intersection++
 		}
 	}

-	union := len(set) + len(b.Concepts) - intersection
+	union := len(setA) + len(setB) - intersection
 	if union == 0 {
 		return 0
 	}
 	return float64(intersection) / float64(union)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/relation/detector.go` around lines 328 - 351, The Jaccard calc in
conceptOverlap can over-count when b.Concepts contains duplicates; change the
logic to treat both a.Concepts and b.Concepts as sets: build setA (existing
map[string]bool) and build setB (map[string]bool) from b.Concepts, compute
intersection by counting keys present in both maps, compute union as
len(setA)+len(setB)-intersection, and return intersection/union; adjust
references in the function conceptOverlap and models.Observation usage
accordingly.

53-70: Отсутствует проверка nil-параметров в конструкторе.

Комментарий предупреждает, что передача nil вызовет панику во время выполнения, но проверка не выполняется. Лучше проваливаться рано с понятным сообщением.

🛡️ Предложение: добавить проверку nil
 func NewDetector(
 	embedSvc *embedding.Service,
 	vectorClient vector.Client,
 	relationStore *gorm.RelationStore,
 	conflictStore *gorm.ConflictStore,
 	observationStore *gorm.ObservationStore,
 ) *Detector {
+	if vectorClient == nil || relationStore == nil || conflictStore == nil || observationStore == nil {
+		panic("relation.NewDetector: required dependency is nil")
+	}
 	return &Detector{
 		embedSvc:         embedSvc,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/relation/detector.go` around lines 53 - 70, The NewDetector
constructor currently accepts required dependencies but does not validate them,
causing later panics; update NewDetector to validate that embedSvc,
vectorClient, relationStore, conflictStore, and observationStore are non-nil and
fail fast with a clear panic or error message naming the offending parameter(s).
Locate the NewDetector function and add nil checks for each parameter (embedSvc,
vectorClient, relationStore, conflictStore, observationStore) and return/raise
an explicit error/panic (e.g., "NewDetector: nil <ParamName>") before
constructing the Detector and creating the queue (queueSize/detectRequest remain
unchanged).
internal/db/gorm/observation_store.go (1)

144-147: Отсутствует синхронизация при установке relationDetector.

Метод SetRelationDetector устанавливает поле, которое читается в StoreObservation. Без синхронизации возможна гонка данных при параллельном доступе.

Однако, если SetRelationDetector вызывается только один раз при инициализации (до обработки запросов), это безопасно. Рекомендуется добавить комментарий, документирующий это ограничение.

📝 Предложение: добавить документацию об ограничении
 // SetRelationDetector sets the async relation detector for post-creation detection.
+// Must be called during initialization, before any calls to StoreObservation.
 func (s *ObservationStore) SetRelationDetector(d RelationDetector) {
 	s.relationDetector = d
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/db/gorm/observation_store.go` around lines 144 - 147,
SetRelationDetector currently writes the relationDetector field that
StoreObservation reads without synchronization; either make access
concurrency-safe or document the init-only restriction. Update
SetRelationDetector and the relationDetector field comment to state that
SetRelationDetector must be called only once during initialization (before any
calls to StoreObservation), or alternatively protect reads/writes using a
mutex/atomic (e.g., add a sync.Mutex on ObservationStore and guard
relationDetector access in SetRelationDetector and StoreObservation); include
references to SetRelationDetector, relationDetector, and StoreObservation so
reviewers can locate the changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/relation/detector.go`:
- Around line 312-318: The current contradiction check compares
newObs.Title.String and candidate.Title.String without verifying
sql.NullString.Valid, causing false positives when one side lacks a title;
update the logic in the contradiction block (where newObs.Type and
candidate.Type are models.ObsTypeDecision and similarity >
contradictSimilarityThreshold) to only consider titles for contradiction if both
newObs.Title.Valid and candidate.Title.Valid are true and the strings differ
(otherwise treat as non-contradictory); leave the return of
models.RelationContradicts and the similarity adjustment unchanged when both
titles are valid and different.
- Around line 43-70: The Detector struct declares and NewDetector accepts
embedSvc *embedding.Service but the field is never used (vectorClient.Query is
passed raw text and pgvector handles embedding), so remove the unused field and
constructor parameter: delete embedSvc from the Detector struct and remove the
embedSvc parameter from NewDetector (and its initialization), then update all
call sites that construct a Detector to stop passing an embedding.Service;
ensure any tests or mocks creating a Detector are adjusted accordingly. If
instead you meant to use explicit embeddings, refactor vectorClient.Query
callers to accept embeddings produced by embedSvc and wire embedSvc into
Detector; otherwise prefer removal to avoid the dead parameter.

---

Nitpick comments:
In `@internal/db/gorm/observation_store.go`:
- Around line 144-147: SetRelationDetector currently writes the relationDetector
field that StoreObservation reads without synchronization; either make access
concurrency-safe or document the init-only restriction. Update
SetRelationDetector and the relationDetector field comment to state that
SetRelationDetector must be called only once during initialization (before any
calls to StoreObservation), or alternatively protect reads/writes using a
mutex/atomic (e.g., add a sync.Mutex on ObservationStore and guard
relationDetector access in SetRelationDetector and StoreObservation); include
references to SetRelationDetector, relationDetector, and StoreObservation so
reviewers can locate the changes.

In `@internal/relation/detector.go`:
- Around line 328-351: The Jaccard calc in conceptOverlap can over-count when
b.Concepts contains duplicates; change the logic to treat both a.Concepts and
b.Concepts as sets: build setA (existing map[string]bool) and build setB
(map[string]bool) from b.Concepts, compute intersection by counting keys present
in both maps, compute union as len(setA)+len(setB)-intersection, and return
intersection/union; adjust references in the function conceptOverlap and
models.Observation usage accordingly.
- Around line 53-70: The NewDetector constructor currently accepts required
dependencies but does not validate them, causing later panics; update
NewDetector to validate that embedSvc, vectorClient, relationStore,
conflictStore, and observationStore are non-nil and fail fast with a clear panic
or error message naming the offending parameter(s). Locate the NewDetector
function and add nil checks for each parameter (embedSvc, vectorClient,
relationStore, conflictStore, observationStore) and return/raise an explicit
error/panic (e.g., "NewDetector: nil <ParamName>") before constructing the
Detector and creating the queue (queueSize/detectRequest remain unchanged).

In `@ui/src/utils/formatters.ts`:
- Around line 46-51: The ternary in safeDateFormat always returns value; update
the date construction to handle numeric Unix timestamps correctly: when value is
a number treat it as seconds and convert to milliseconds (value * 1000) before
passing to new Date, otherwise pass the string value directly; modify the new
Date call inside safeDateFormat (referencing safeDateFormat and
formatRelativeTime) to use this conversion and remove the redundant ternary.
- Around line 56-61: The ternary in safeAbsoluteDate is redundant (new
Date(typeof value === 'number' ? value : value)); update the Date construction
to pass value directly (e.g., new Date(value)) so that the date variable is
created correctly without the no-op ternary. Keep the existing early-return
checks and isNaN(date.getTime()) logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8d67bf13-fb86-4e64-bf14-6b9164941cd8

📥 Commits

Reviewing files that changed from the base of the PR and between 17dd9fa and 76ad6f2.

📒 Files selected for processing (10)
  • internal/db/gorm/observation_store.go
  • internal/relation/detector.go
  • internal/worker/service.go
  • plugin/openclaw-engram/package.json
  • plugin/openclaw-engram/src/config.ts
  • plugin/openclaw-engram/src/hooks/after-tool-call.ts
  • ui/src/utils/api.ts
  • ui/src/utils/formatters.ts
  • ui/src/views/AnalyticsView.vue
  • ui/src/views/VaultView.vue

Comment thread internal/relation/detector.go
Comment thread internal/relation/detector.go
@thebtf
Copy link
Copy Markdown
Owner Author

thebtf commented Mar 19, 2026

@codex review

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/relation/detector.go (1)

291-294: Рекомендация: добавить проверку временного порядка для supersedes.

Логика supersedes предполагает, что новое наблюдение вытесняет старое. Однако при высокой конкурентности векторный поиск может вернуть наблюдение, созданное позже текущего (если оно было быстро проиндексировано). Это приведёт к семантически некорректному отношению "A supersedes B", где B на самом деле новее A.

♻️ Предложенное улучшение
 // supersedes: very high similarity, same type, same project
-if similarity > supersedeSimilarityThreshold && newObs.Type == candidate.Type {
+if similarity > supersedeSimilarityThreshold && newObs.Type == candidate.Type && newObs.CreatedAt.After(candidate.CreatedAt) {
 	return models.RelationSupersedes, similarity
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/relation/detector.go` around lines 291 - 294, The supersedes branch
in detector.go currently only checks similarity and type; add a temporal-order
check so we only return models.RelationSupersedes when the new observation is
strictly newer than the candidate (e.g., compare newObs.Timestamp or CreatedAt
with candidate.Timestamp/CreatedAt using After/greater-than). Update the if
guarding similarity > supersedeSimilarityThreshold && newObs.Type ==
candidate.Type to also require newObs to be later than candidate (handle
nil/zero timestamps defensively), ensuring we preserve existing symbols: newObs,
candidate, supersedeSimilarityThreshold, and models.RelationSupersedes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/relation/detector.go`:
- Around line 76-88: The drain loop uses d.processRequest while ctx.Done() has
fired, but processRequest builds a ctx from d.parentCtx which is canceled,
causing immediate context.Canceled errors; add a new Detector method
processDrainRequest(req detectRequest) that creates an independent context via
context.WithTimeout(context.Background(), detectionTimeout) and calls d.Detect
with that fresh context, logging warnings on errors, then change the drain
branch (the case <-ctx.Done() loop) to call d.processDrainRequest(req) instead
of d.processRequest(req) so remaining items are processed with a live timeout
context.

---

Nitpick comments:
In `@internal/relation/detector.go`:
- Around line 291-294: The supersedes branch in detector.go currently only
checks similarity and type; add a temporal-order check so we only return
models.RelationSupersedes when the new observation is strictly newer than the
candidate (e.g., compare newObs.Timestamp or CreatedAt with
candidate.Timestamp/CreatedAt using After/greater-than). Update the if guarding
similarity > supersedeSimilarityThreshold && newObs.Type == candidate.Type to
also require newObs to be later than candidate (handle nil/zero timestamps
defensively), ensuring we preserve existing symbols: newObs, candidate,
supersedeSimilarityThreshold, and models.RelationSupersedes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31fcb349-c1e9-4885-87e6-cf0089e833dd

📥 Commits

Reviewing files that changed from the base of the PR and between 76ad6f2 and e321f03.

📒 Files selected for processing (2)
  • internal/relation/detector.go
  • internal/worker/service.go
✅ Files skipped from review due to trivial changes (1)
  • internal/worker/service.go

Comment on lines +76 to +88
case <-ctx.Done():
// Drain remaining items before exiting
for {
select {
case req := <-d.queue:
d.processRequest(req)
default:
log.Info().Msg("Relation detector stopped")
return
}
}
case req := <-d.queue:
d.processRequest(req)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Некорректный drain при завершении: родительский контекст уже отменён.

При срабатывании ctx.Done() код входит в цикл drain и вызывает processRequest(req). Однако processRequest создаёт контекст через context.WithTimeout(d.parentCtx, ...), а d.parentCtx — это тот же ctx, который уже отменён. В результате все вызовы Detect немедленно завершаются с ошибкой context.Canceled, и drain фактически не работает.

🐛 Предложенное исправление
 case <-ctx.Done():
 	// Drain remaining items before exiting
 	for {
 		select {
 		case req := <-d.queue:
-			d.processRequest(req)
+			d.processDrainRequest(req)
 		default:
 			log.Info().Msg("Relation detector stopped")
 			return
 		}
 	}

Добавьте отдельный метод для drain с независимым контекстом:

// processDrainRequest handles remaining requests during shutdown with a fresh context.
func (d *Detector) processDrainRequest(req detectRequest) {
	ctx, cancel := context.WithTimeout(context.Background(), detectionTimeout)
	defer cancel()

	if err := d.Detect(ctx, req.ObsID, req.Project); err != nil {
		log.Warn().Err(err).
			Int64("obs_id", req.ObsID).
			Str("project", req.Project).
			Msg("Relation detection failed during drain")
	}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/relation/detector.go` around lines 76 - 88, The drain loop uses
d.processRequest while ctx.Done() has fired, but processRequest builds a ctx
from d.parentCtx which is canceled, causing immediate context.Canceled errors;
add a new Detector method processDrainRequest(req detectRequest) that creates an
independent context via context.WithTimeout(context.Background(),
detectionTimeout) and calls d.Detect with that fresh context, logging warnings
on errors, then change the drain branch (the case <-ctx.Done() loop) to call
d.processDrainRequest(req) instead of d.processRequest(req) so remaining items
are processed with a live timeout context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant