feat: Dashboard Analytics v2 — persistent analytics, bugfixes, verified facts TTL#26
Conversation
…misses, sessions 503
…TL fields - Migration 037: search_query_log table with indexes - Migration 038: retrieval_stats_log table with indexes - Migration 039: observations expires_at/ttl_days columns with partial index - SearchQueryLogStore: async insert, analytics aggregation, recent queries, cleanup - Observation model: ExpiresAt, TtlDays, IsExpired (computed) fields - toModelObservation: maps expires_at/ttl_days, computes is_expired at query time
… use DB, add 90-day cleanup
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
WalkthroughДобавлены персистентное логирование поисковых запросов и аналитика, миграции БД (включая TTL для наблюдений), поддержка TTL в хранении наблюдений и инструменте store_memory, расширены HTTP-обработчики и UI для выбора временных диапазонов; также переименована предпочитаемая env-переменная аутентификации. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Server
participant Worker
participant SearchStore as SearchQueryLogStore
participant DB
Client->>Server: Выполняет поиск (query)
Server->>Worker: Делегирует запрос поиска
Worker->>Worker: trackSearchQuery() (в памяти)
rect rgba(100,150,200,0.5)
Worker->>SearchStore: LogQuery(project, query, type, results, usedVector, latency)
SearchStore->>DB: INSERT INTO search_query_log ...
DB-->>SearchStore: OK
end
Worker->>Worker: Обновляет in-memory recent buffer
Worker-->>Client: Возвращает результаты поиска
Client->>Server: GET /search/analytics?since=...
Server->>SearchStore: GetAnalytics(since)
SearchStore->>DB: SELECT aggregate FROM search_query_log WHERE created_at >= since
DB-->>SearchStore: analytics rows
SearchStore-->>Server: SearchAnalytics
Server-->>Client: JSON ответ
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Comment |
Summary of ChangesHello, 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 introduces significant enhancements to the dashboard analytics by implementing persistent storage for search queries, which allows for more robust and long-term data analysis. It also greatly improves the user experience with a new time-range selection feature for analytics and critical bug fixes across various dashboard views. Additionally, a new mechanism for managing the lifecycle of 'verified' observations through Time-To-Live (TTL) settings has been implemented, contributing to better data management and relevance. Highlights
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
This pull request introduces significant enhancements to the dashboard analytics by making them persistent, adds TTL functionality for verified facts, and includes several important bug fixes. The introduction of a persistent search query log is a major improvement over the in-memory buffer. The changes are well-structured, with corresponding database migrations and updates across the backend and frontend. I've identified a couple of areas for improvement: one regarding the robustness of a database query and another related to an incomplete implementation in the new analytics feature.
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ui/src/composables/useSessions.ts (1)
25-27:⚠️ Potential issue | 🟠 Major
loadingможет сбрасываться раньше времени при отменённых конкурентных запросах.Когда старый запрос прерывается, его
finallyвсё равно делаетloading.value = false, даже если новый запрос уже активен. Это приводит к неверному состоянию загрузки (мигание/раннее скрытие индикатора).Предлагаемая правка через request-id guard
export function useSessions() { @@ let abortController: AbortController | null = null + let activeRequestId = 0 @@ async function loadSessions() { @@ + const requestId = ++activeRequestId @@ } finally { - loading.value = false + if (requestId === activeRequestId) { + loading.value = false + } } } @@ async function search() { @@ + const requestId = ++activeRequestId @@ } finally { - loading.value = false + if (requestId === activeRequestId) { + loading.value = false + } } }Also applies to: 41-46, 55-57, 66-70
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/composables/useSessions.ts` around lines 25 - 27, Concurrent request cancellation can cause loading.value to be reset by an earlier request's finally block; fix by adding a monotonically incremented request id (e.g., requestIdCounter and currentRequestId) inside loadSessions (and the analogous functions at 41-46, 55-57, 66-70) and capture the id in the async call, only updating state (sessions list and loading.value) if the captured id matches the latest currentRequestId; increment the counter and set currentRequestId when starting a request, and in each finally block check the id match before setting loading.value = false so aborted/older requests cannot flip the loading flag or overwrite results.
🧹 Nitpick comments (4)
internal/config/config.go (1)
587-590: Уберите дублирующий заголовок комментария надGetWorkerToken().На Line 587 и Line 588 повторяется описание функции; лучше оставить одну формулировку.
🧹 Предлагаемое упрощение комментария
-// GetWorkerToken returns the worker authentication token. -// GetWorkerToken returns the admin authentication token. -// Checks ENGRAM_AUTH_ADMIN_TOKEN first (preferred), falls back to ENGRAM_API_TOKEN (deprecated). +// GetWorkerToken returns the admin authentication token. +// Checks ENGRAM_AUTH_ADMIN_TOKEN first (preferred), falls back to ENGRAM_API_TOKEN (deprecated).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/config/config.go` around lines 587 - 590, Удалите дублирующий заголовок в комментарии над функцией GetWorkerToken: оставьте только одну строку описания (например "GetWorkerToken returns the worker authentication token. Checks ENGRAM_AUTH_ADMIN_TOKEN first (preferred), falls back to ENGRAM_API_TOKEN (deprecated).") и уберите повторяющуюся строку, чтобы комментарий был однозначным и не содержал двух одинаковых первых строк.ui/src/views/GraphView.vue (1)
162-168: Рассмотрите использование CSS-классов вместо прямой мутации style.Проверка на нулевые размеры контейнера — хорошая защита от проблем vis-network. Однако прямая мутация
element.styleв Vue-компоненте менее идиоматична. Рассмотрите возможность установки CSS-классов или использования привязки:styleдля более реактивного подхода.При этом текущая реализация функционально корректна и решает проблему рендеринга графа.
♻️ Альтернативный подход с реактивным стилем
Можно добавить ref для минимальных размеров и использовать его в шаблоне:
<!-- В template --> <div ref="graphContainer" :style="containerStyle" class="w-full h-full rounded-xl ..." />// В script const forceMinSize = ref(false) const containerStyle = computed(() => forceMinSize.value ? { minHeight: '500px', minWidth: '400px' } : {} )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/views/GraphView.vue` around lines 162 - 168, Replace direct DOM style mutation with a reactive class/style: introduce a ref forceMinSize and a computed containerStyle (or a CSS class) that returns { minHeight:'500px', minWidth:'400px' } when forceMinSize is true; keep the existing zero-size check using graphContainer (graphContainer.value.getBoundingClientRect()) but set forceMinSize.value = true instead of mutating graphContainer.value.style; then bind the computed containerStyle in the template on the same element (use :style="containerStyle" or a conditional class) so sizing is reactive and idiomatic in the GraphView component.internal/db/gorm/models.go (1)
86-87: Рекомендую добавить индекс наexpires_atдля TTL-мониторинга.На Line 86 поле
ExpiresAtдобавлено без индекса, а мониторинг уже считает просроченные записи поexpires_at < NOW(). На больших объёмах это станет дорогим full scan.💡 Предлагаемое изменение
- ExpiresAt sql.NullTime `gorm:"type:timestamptz"` + ExpiresAt sql.NullTime `gorm:"type:timestamptz;index:idx_observations_expires_at"`🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/db/gorm/models.go` around lines 86 - 87, The ExpiresAt field in the GORM model (ExpiresAt sql.NullTime) lacks an index which causes full-table scans for TTL checks; add an index on ExpiresAt (e.g., by adding a GORM tag like `gorm:"index:idx_expires_at,sort:asc"` or similar) to the ExpiresAt field in the model (and if needed create a matching DB migration to create the index) so TTL queries that use `expires_at < NOW()` can use the index; reference the ExpiresAt field and update the model and migrations accordingly.internal/mcp/server.go (1)
1155-1155: Добавьте нижнюю границу дляttl_daysв input schema.На Line 1155 сейчас допускаются
0и отрицательные значения. Лучше ограничить минимумом1, чтобы контракт инструмента соответствовал “TTL в днях”.✅ Небольшое улучшение схемы
- "ttl_days": map[string]any{"type": "integer", "description": "TTL in days for verified facts. Auto-computed from tags if not provided. Only applies to observations with 'verified' tag."}, + "ttl_days": map[string]any{"type": "integer", "minimum": 1, "description": "TTL in days for verified facts. Auto-computed from tags if not provided. Only applies to observations with 'verified' tag."},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/mcp/server.go` at line 1155, The schema entry for "ttl_days" currently allows 0 and negative values; update the input schema map for "ttl_days" (the map[string]any literal that contains "type" and "description") to include a minimum constraint so values <1 are rejected — e.g., add "minimum": 1 to that map so the schema enforces TTL in days must be at least 1.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude-plugin/marketplace.json:
- Line 17: Поле "source" содержит неверный путь "./engram"; замените значение
поля "source" на "./plugin/" в манифесте (ищите ключ "source") чтобы указать
правильную директорию плагина для интеграции в маркетплейс и восстановить
установку.
In `@internal/db/gorm/observation_store.go`:
- Around line 1352-1354: The FTS search code paths (SearchObservationsFTS,
SearchObservationsFTSFiltered, SearchObservationsFTSScored and their scanner
scanObservation) are not populating the new TTL fields even though
toModelObservation sets ExpiresAt, TtlDays and IsExpired; update the SELECTs
used by these FTS queries to include the ExpiresAt and TtlDays columns and
modify scanObservation (and any row->struct scan helpers used by the three FTS
functions) to read and populate ExpiresAt, TtlDays and compute/set IsExpired
consistently (e.g., using ExpiresAt.Valid && ExpiresAt.Time.Before(time.Now()))
so FTS results include the expiration metadata.
In `@internal/db/gorm/search_query_log_store.go`:
- Around line 37-53: The current LogQuery method in SearchQueryLogStore spawns
an unbounded fire-and-forget goroutine per call (creating SearchQueryLogEntry
and calling s.db.Create), which can overwhelm the process under load; replace
this by implementing a bounded background worker or batcher: add a buffered
channel on SearchQueryLogStore to enqueue SearchQueryLogEntry, start a fixed
worker goroutine (or a small pool) that reads from the channel, performs batched
writes using GORM's CreateInBatches or transactions (or retries on error), and
add shutdown tracking (a Close/Shutdown method and context/cancel to drain the
queue) so LogQuery only enqueues (non-blocking with a drop/metrics strategy when
full) instead of spawning goroutines directly; update uses of LogQuery to create
entries and push them to the new channel and remove the s.db.Create call from
LogQuery itself.
- Around line 90-121: The code currently ignores DB errors on each aggregate
which can produce mixed real/zero analytics; update the blocks that call
s.db.WithContext(...).Table("search_query_log") so you capture and check the
GORM result errors and Scan errors (e.g., assign result :=
s.db.WithContext(ctx)...Count(&analytics.SearchesToday); if result.Error != nil
{ return err } and similarly for the AVG query where
Row().Scan(&analytics.AvgLatencyMs) should check the returned error, for
zeroCount use result := zeroQ.Where(...).Count(&zeroCount); if result.Error !=
nil { return err }, and for vectorQ use result :=
vectorQ.Where(...).Count(&analytics.VectorSearches); if result.Error != nil {
return err }; on any error or cancelled context return the error rather than
returning partially filled analytics (refer to symbols: SearchesToday,
AvgLatencyMs, ZeroResultRate/zeroCount, VectorSearches, TotalSearches,
s.db.WithContext, Row().Scan).
- Around line 50-52: Не логируйте сырой пользовательский ввод из переменной
query в warning: вместо этого при ошибке вставки (в месте вызова
s.db.Create(&entry).Error) замените логирование полного запроса на безопасные
метаданные — например log.Warn().Err(err).Int("query_len",
len(query)).Str("query_hash", <sha256_hex_of_query>).Msg("failed to log search
query"); реализуйте хеширование (sha256) и длину рядом с существующим
сообщением, чтобы сохранить контекст для отладки без утечки PII/секретов, при
этом не меняя сохранение самого entry в БД.
In `@internal/maintenance/service.go`:
- Around line 214-216: The LIKE filter "concepts::text LIKE '%verified%'" in the
query that ends with Count(&expiredCount).Error is imprecise and causes
full-table scans; replace it with a JSONB containment check to match the exact
tag (for example use Where("concepts @> ?::jsonb", `["verified"]`) or equivalent
JSONB operator used in your codebase) so only rows whose concepts array contains
"verified" are counted, and add the suggested partial index migration (CREATE
INDEX CONCURRENTLY ... WHERE expires_at IS NOT NULL) to improve performance.
In `@internal/mcp/tools_memory.go`:
- Around line 188-205: The handler currently calls
s.observationStore.SetObservationTTL(...) after creating the observation but
still returns success and includes "ttl_days" in the result even when
SetObservationTTL fails; change the logic in the function handling observation
creation (where computeTTLDays, ttlDays and s.observationStore.SetObservationTTL
are used) so that on SetObservationTTL error you do not advertise the TTL:
either return an error response (fail the request) or at minimum omit "ttl_days"
from the result when SetObservationTTL returns an error; for a stronger fix
consider making creation + TTL update atomic (wrap both actions into a
transaction or a single store call) so the observation is only reported as
successful if the TTL was persisted.
- Around line 213-247: computeTTLDays currently returns the first matching
autoTTL entry (dependent on concepts order) which makes TTL non-deterministic;
change the logic in computeTTLDays to, after verifying the "verified" tag, scan
all concepts and collect all matching autoTTL values from the autoTTL map
(variable autoTTL) and return the minimum matched days (instead of returning on
first match); keep the explicit override check (explicit and *explicit > 0) and
the default return 30 for verified facts with no matching tag.
In `@internal/worker/service.go`:
- Around line 1918-1928: trackSearchQuery currently hardcodes latency_ms = 0
which corrupts analytics; instead propagate the real measured latency (or NULL
when unavailable) into the DB write. Update call sites to compute elapsed time
for searches and pass that value into trackSearchQuery, and change
trackSearchQuery to forward that measured latency to
s.searchQueryLogStore.LogQuery (replace the trailing 0 argument with the actual
latency or a nil/nullable sentinel supported by LogQuery). Ensure LogQuery’s
parameter contract is respected (make it accept a nullable/optional latency if
needed) and preserve the existing fire-and-forget behavior using the same
s.initMu and s.searchQueryLogStore symbols.
- Around line 896-897: The searchQueryLogStore is only created in
initializeAsync() and thus remains tied to the old store after
reinitializeDatabase(); update reinitializeDatabase() to (re)create and assign a
new searchQueryLogStore using gorm.NewSearchQueryLogStore(store.GetDB()) (same
way as in initializeAsync()), so handlers/functions like trackSearchQuery and
the /api/search/recent and /api/search/analytics endpoints use the fresh
store/DB after reinitialization; ensure any existing reference to the old
searchQueryLogStore (variable name: searchQueryLogStore) is replaced/updated
when the new store is created.
In `@pkg/models/observation.go`:
- Around line 219-221: Observation's new fields ExpiresAt, TtlDays and IsExpired
aren't emitted because MarshalJSON builds an ObservationJSON without those
fields; update the JSON path by adding ExpiresAt (sql.NullTime -> appropriate
nullable/time value), TtlDays (sql.NullInt32 -> int/null) and IsExpired (bool)
to the ObservationJSON type used in MarshalJSON or adjust MarshalJSON to copy
those fields from Observation into the output map/struct, preserving the json
tags `expires_at`, `ttl_days`, `is_expired` and handling NullTime/NullInt32
conversion to nullable JSON values.
In `@README.md`:
- Line 124: Update README examples to use the new environment variable name
consistently: replace occurrences of ENGRAM_API_TOKEN with
ENGRAM_AUTH_ADMIN_TOKEN in the Authorization header examples, CLI command
examples, and any env var snippets; keep a short note that ENGRAM_API_TOKEN is
deprecated (if present) and accept both env vars in examples only if you show
precedence or fallback, but do not leave mixed references that could break users
who set only ENGRAM_AUTH_ADMIN_TOKEN.
In `@ui/src/components/TimeRangeSelector.vue`:
- Around line 26-29: The comment is wrong because start.toISOString() returns
UTC (with Z) not an ISO string with local offset; update the 'today' branch to
serialize the local-start Date with its timezone offset instead of using
toISOString(): create a helper (e.g., toISOWithOffset(date)) that computes the
timezone offset, formats ±HH:MM, and appends it to the ISO datetime, then return
toISOWithOffset(start) from the case 'today' branch (reference the start
variable and the toISOWithOffset helper).
- Around line 4-6: The prop modelValue is defined but not used to initialize or
sync the internal selected state; update the component (TimeRangeSelector.vue)
to initialize selected from props.modelValue (when present), add a watcher on
modelValue to update selected when parent changes it, and ensure selected
changes emit the v-model update via emit('update:modelValue', selected) (use the
existing props/modelValue and the component's selected reactive ref and
defineEmits).
In `@ui/src/composables/useSessions.ts`:
- Around line 5-11: The check in resolveSessionError currently uses
err.message.startsWith('HTTP 503'); change it to use a substring check like
err.message.includes('503') (or includes('HTTP 503')) for consistency with other
API error checks; update the conditional in resolveSessionError so it detects
HTTP 503 via .includes('503') and otherwise returns err.message || fallback.
In `@ui/src/views/AnalyticsView.vue`:
- Around line 57-63: The component currently calls loadAll() twice on mount
because onMounted calls loadAll() and the TimeRangeSelector emits
update:modelValue with immediate:true which triggers watch(since) → loadAll();
to fix, remove the onMounted(() => loadAll()) call OR add an initialized flag
(e.g., isInitialized) checked/set inside the watch(since, ...) handler so the
first immediate watch invocation runs once and subsequent changes call
loadAll(); update references to abortController?.abort() remain as-is to cancel
in-flight requests.
---
Outside diff comments:
In `@ui/src/composables/useSessions.ts`:
- Around line 25-27: Concurrent request cancellation can cause loading.value to
be reset by an earlier request's finally block; fix by adding a monotonically
incremented request id (e.g., requestIdCounter and currentRequestId) inside
loadSessions (and the analogous functions at 41-46, 55-57, 66-70) and capture
the id in the async call, only updating state (sessions list and loading.value)
if the captured id matches the latest currentRequestId; increment the counter
and set currentRequestId when starting a request, and in each finally block
check the id match before setting loading.value = false so aborted/older
requests cannot flip the loading flag or overwrite results.
---
Nitpick comments:
In `@internal/config/config.go`:
- Around line 587-590: Удалите дублирующий заголовок в комментарии над функцией
GetWorkerToken: оставьте только одну строку описания (например "GetWorkerToken
returns the worker authentication token. Checks ENGRAM_AUTH_ADMIN_TOKEN first
(preferred), falls back to ENGRAM_API_TOKEN (deprecated).") и уберите
повторяющуюся строку, чтобы комментарий был однозначным и не содержал двух
одинаковых первых строк.
In `@internal/db/gorm/models.go`:
- Around line 86-87: The ExpiresAt field in the GORM model (ExpiresAt
sql.NullTime) lacks an index which causes full-table scans for TTL checks; add
an index on ExpiresAt (e.g., by adding a GORM tag like
`gorm:"index:idx_expires_at,sort:asc"` or similar) to the ExpiresAt field in the
model (and if needed create a matching DB migration to create the index) so TTL
queries that use `expires_at < NOW()` can use the index; reference the ExpiresAt
field and update the model and migrations accordingly.
In `@internal/mcp/server.go`:
- Line 1155: The schema entry for "ttl_days" currently allows 0 and negative
values; update the input schema map for "ttl_days" (the map[string]any literal
that contains "type" and "description") to include a minimum constraint so
values <1 are rejected — e.g., add "minimum": 1 to that map so the schema
enforces TTL in days must be at least 1.
In `@ui/src/views/GraphView.vue`:
- Around line 162-168: Replace direct DOM style mutation with a reactive
class/style: introduce a ref forceMinSize and a computed containerStyle (or a
CSS class) that returns { minHeight:'500px', minWidth:'400px' } when
forceMinSize is true; keep the existing zero-size check using graphContainer
(graphContainer.value.getBoundingClientRect()) but set forceMinSize.value = true
instead of mutating graphContainer.value.style; then bind the computed
containerStyle in the template on the same element (use :style="containerStyle"
or a conditional class) so sizing is reactive and idiomatic in the GraphView
component.
🪄 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: 9cf8a562-cf47-4b7a-9ffd-ff1c91ff31a7
📒 Files selected for processing (22)
.claude-plugin/marketplace.jsonREADME.mdinternal/config/config.gointernal/db/gorm/migrations.gointernal/db/gorm/models.gointernal/db/gorm/observation_store.gointernal/db/gorm/search_query_log_store.gointernal/maintenance/service.gointernal/mcp/server.gointernal/mcp/tools_memory.gointernal/worker/handlers_context.gointernal/worker/handlers_data.gointernal/worker/handlers_maintenance.gointernal/worker/service.gopkg/models/observation.goui/src/App.vueui/src/components/TimeRangeSelector.vueui/src/composables/useSessions.tsui/src/utils/api.tsui/src/views/AnalyticsView.vueui/src/views/GraphView.vueui/src/views/PatternsView.vue
|
@codex review |
…rors, security - Fix ObservationJSON/MarshalJSON missing TTL fields (expires_at, ttl_days, is_expired) - Add TTL columns to FTS raw SQL queries and scanObservation - Fix computeTTLDays to use minimum TTL from all matching tags (not first match) - Only confirm TTL in response if SetObservationTTL succeeded - Fix search_query_log_store: check errors on all aggregates, remove raw query from logs - Add real latency measurement to trackSearchQuery callers - Add searchQueryLogStore to reinitializeDatabase path - Fix marketplace.json source path regression (./engram -> ./plugin/engram) - Sync README token variable to ENGRAM_AUTH_ADMIN_TOKEN - Use JSONB containment operator for verified facts filter in maintenance - Add minimum:1 to ttl_days MCP schema - Fix double loadAll() in AnalyticsView, fix misleading comment in TimeRangeSelector
🤖 PR Review MCP State (auto-managed, do not edit){
"version": 2,
"parentChildren": {},
"resolvedNitpicks": {
"coderabbit-nitpick-5aa2c832-1155": {
"resolvedAt": "2026-03-20T00:46:46.062Z",
"resolvedBy": "agent"
},
"coderabbit-nitpick-21cd21d5-86": {
"resolvedAt": "2026-03-20T00:46:48.501Z",
"resolvedBy": "agent"
},
"coderabbit-outside-diff-155be32f-587": {
"resolvedAt": "2026-03-20T00:47:02.104Z",
"resolvedBy": "agent"
},
"coderabbit-outside-diff-650755dc-162": {
"resolvedAt": "2026-03-20T00:47:04.789Z",
"resolvedBy": "agent"
},
"coderabbit-outside-diff-21cd21d5-86": {
"resolvedAt": "2026-03-20T00:47:07.301Z",
"resolvedBy": "agent"
},
"coderabbit-outside-diff-5aa2c832-1155": {
"resolvedAt": "2026-03-20T00:47:09.809Z",
"resolvedBy": "agent"
},
"coderabbit-nitpick-f61b83eb-587": {
"resolvedAt": "2026-03-20T00:47:12.040Z",
"resolvedBy": "agent"
},
"coderabbit-nitpick-650755dc-162": {
"resolvedAt": "2026-03-20T00:47:14.812Z",
"resolvedBy": "agent"
}
},
"updatedAt": "2026-03-20T00:47:15.527Z"
} |
Summary
Changes
Phase 1: Fix Broken UI
occurrences→frequency, addlast_seen_atfieldsPhase 2: Persistent Search Query Log
search_query_logtable with indexesSearchQueryLogStore: async insert, analytics aggregation, recent queries, cleanup/api/search/analyticsand/api/search/recentto query from DBtrackSearchQuerynow persists to DB alongside in-memory ring bufferPhase 4: Enhanced Analytics Dashboard
TimeRangeSelectorcomponent (today/7d/30d/all)sinceparametersincesupportPhase 5: Verified Facts TTL
expires_at TIMESTAMPTZ NULL+ttl_days INT NULLon observationsstore_memoryMCP tool: newttl_daysparameter with auto-TTL by tagsis_expiredcomputed intoModelObservation(real-time, not materialized)Other
Migrations
search_query_logtableretrieval_stats_logtable (schema only — batched flusher deferred)observationsexpires_at/ttl_days columnsDeferred (Phase 3)
Test plan
Summary by CodeRabbit
Примечания к выпуску
Новые функции
Улучшения
Обновления конфигурации