PR2: Role-aware digest insight assembler — Same Brain, different lens per role - #104
Merged
Merged
Conversation
6 tasks
Contributor
Author
Stack review pointerReviewed together with #103 and #105 (no #106). Assembler unit suite (26) green; live tick on Full write-up + blocking findings on tip #105: #105 (comment) This PR (#104): assembler ranking/persona lens is directionally good; sparse Brain data on demo_shop mostly shared the same index insight — titles/format differed more than ranked content. Merge only as middle of stack after tip delivery fixes. |
5 tasks
venkateshsakamuri-lab
added a commit
that referenced
this pull request
Sep 9, 2026
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Summary **Same Brain, different lens per role.** This PR implements per-recipient Slack delivery for digests, consuming the `DigestAssemblyResult` API from PR2. Two users with different personas on the same connection now get different Slack digests for the same time window. ### Honesty-gap fix (per-user cron) UI schedule presets and `UserDigestPreference.cronExpression` (+ timezone) were stored but ignored: `SlackDailyDigestTaskConfig` only ran the global `slack.daily-digest.cron` and then delivered to every preference. Product Evangelist blocked marketing “your own schedule” until this was fixed. The scheduler now ticks every minute (`slack.daily-digest.tick-cron`, default `0 * * * * *`) via `processDigestTick()`: - **No enabled prefs** → legacy channel broadcast only when the global cron is due (UTC) - **Prefs exist** → each enabled `SLACK_DM` preference is evaluated with Spring `CronExpression` in that user’s timezone; already-logged fire windows are skipped (`SlackDigestLog` idempotency) - Blank preference cron still falls back to `slack.daily-digest.cron` - Manual/admin trigger still uses `sendDailyDigestHybrid()` (force all recipients) ## Changes ### Backend - **`SlackDailyDigestService`**: `sendPersonalizedDigests()` / `sendDailyDigestHybrid()` / **`processDigestTick()`** — per-recipient DMs, EXEC formatting, personalized logs, and per-user cron on the minute tick - **`DigestCronMatcher`**: Spring `CronExpression` due-window helper (timezone-aware) - **`DigestPreferenceSeedService`**: seed from singleton; persona inference; skips inactive users - **`SlackDailyDigestTaskConfig`**: minute tick → `processDigestTick()` - **`DigestPreferenceController`**: seed/status; refuses EMAIL ### Frontend - Digest preferences API + panel (persona, schedule presets, seed) + DigestSection bell icon ### Documentation - `docs/DIGEST_PREFERENCES.md` updated for tick vs global cron and accurate “your own schedule” ## Acceptance Criteria - [x] Two users, different roles/personas, same connection → different Slack digests same window - [x] Cron/config not singleton-only (**per-user cron honored on tick**) - [x] Tests for per-recipient delivery + seed/legacy + due-cron / timezone / idempotency - [x] UI prefs for role/schedule/persona - [x] Docs updated — “your own schedule” is accurate - [x] No fake EMAIL / WhatsApp delivery advertised ## Out of Scope (Follow-ups) 1. Idle-in-transaction insight when Brain has data 2. `join_collapse_limit` cliffs under CONFIG_TUNING 3. DOCUMENTATION_GAPS miner 4. COST_CAPACITY dedicated cost miner for EXEC 5. Hermes/WhatsApp = PR4 ## Dependencies - PR1 #103: per-user digest prefs - PR2 #104: role-aware insight assembler ## Testing ```bash cd backend ./mvnw test -Dtest=PerRecipientDigestDeliveryTest,PerRecipientDigestCronSchedulingTest,DigestCronMatcherTest,DigestPreferenceSeedServiceTest,SlackDailyDigestServiceTest ``` Key scenarios: different crons → only due user; timezone America/New_York 9AM = 13:00 UTC; idempotent window skip; EXEC 3 bullets; EMAIL skipped; seed idempotent; legacy when no enabled prefs. <!-- CURSOR_AGENT_PR_BODY_END --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
venkateshsakamuri-lab
added this pull request to stack #106
September 9, 2026 06:04
venkateshsakamuri-lab
marked this pull request as ready for review
September 9, 2026 06:05
PR1 for role-aware digests theme. This change migrates from singleton SlackDigestConfig to per-user UserDigestPreference with: Data Model: - PersonaTag enum: DBA, APP_ENG, DATA_ENG, EXEC for content prioritization - DigestDeliveryMethod enum: SLACK_DM, SLACK_CHANNEL, EMAIL - UserDigestPreference entity with per-user/per-connection preferences - Extended SlackDigestLog with recipient tracking fields Services: - UserDigestPreferenceService for CRUD and preference resolution - Minimal hooks in SlackDailyDigestService (isPerUserModeEnabled, getDigestRecipients) API: - /digest/preferences/* for user self-service management - /admin/slack/digest/preferences/* for admin management - Backward compatible: singleton still works when no prefs exist Migration: - V120 creates user_digest_preference table - Extends slack_digest_log with recipient/persona/delivery fields - Adds is_global_default to slack_digest_config Tests: - PersonaTagTest, DigestDeliveryMethodTest for enum parsing - UserDigestPreferenceTest for entity behavior - UserDigestPreferenceServiceTest for service logic Docs: - docs/DIGEST_PREFERENCES.md with API usage examples Full role-aware ranking/delivery is PR2 (this is data model + migration only). Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
Implements PR2 of the thinking database feature: role-aware, personalized
digest content assembly.
New components:
- DigestInsight: ranked insight model with severity, freshness, actionability
- InsightCategory: enum mapping insight types to persona relevance weights
- DigestAssemblyResult: assembled digest with metadata and executive summary
- DigestInsightAssemblerService: mines and ranks insights from Brain stores
Signal mining from existing stores:
- BrainV2Alert: workload changes, config drift, plan regressions
- BrainScore: low health score alerts
- BrainLearningProgress: tuning milestones
- IndexRecommendation: pending index recommendations by priority
- SchemaChange: breaking/critical schema changes
- GrowthAnomaly: table growth spikes
- PlaybookAlert: system alerts
- SlowQueryHistory: critical/high slow query counts
Ranking formula:
score = categoryWeight × severityMultiplier × freshnessMultiplier
× personaRelevance × roleMultiplier × actionabilityBonus
Persona-aware weighting (examples):
- DBA: 2x on QUERY_PERFORMANCE, INDEX_RECOMMENDATIONS, CONFIG_TUNING
- DATA_ENG: 2x on DOCUMENTATION_GAPS, SCHEMA_CHANGES
- APP_ENG: 2x on SCHEMA_CHANGES, 1.8x on QUERY_PERFORMANCE
- EXEC: 2x on COST_CAPACITY, limited to 5 insights with summary
Suppression logic:
- Filters acknowledged insights
- Suppresses duplicates from last digest (via signature key)
- High-severity insights (>=80) bypass suppression
Tests cover:
- Different personas get differently ranked digests
- Duplicate suppression across digests
- Acknowledged insight filtering
- Severity/freshness/actionability ranking
- Signal mining from all Brain stores
Depends on PR #103 (cursor/per-user-digest-prefs-3784) for UserDigestPreference,
PersonaTag, and SlackDigestLog models.
Part of: Thinking database — continuous-learning Brain + role-aware digests
Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
PR2 enhancement per Product Evangelist guidance: Concrete signal mapping (same Brain, different lens per role): - DBA: idle-in-txn, lock waits, blocking chains, autovacuum/bloat - APP_ENG: ACCESS EXCLUSIVE risk (DDL blocking), schema changes - DATA_ENG: documentation gaps (semantic drift), ETL/load patterns - EXEC: cost/capacity summaries (3 bullets max) Changes: - Add LOCK_CONCURRENCY InsightCategory for DBA operational signals - Update persona multipliers based on concrete use cases - Add mineLockContention() mining from LockContentionRepository - Add tests for lock contention mining and persona-based ranking - Inject LockContentionRepository into DigestInsightAssemblerService GTM: 'Same Brain, different lens per role' — not another generic dump. Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Summary **Same Brain, different lens per role.** This PR implements per-recipient Slack delivery for digests, consuming the `DigestAssemblyResult` API from PR2. Two users with different personas on the same connection now get different Slack digests for the same time window. ### Honesty-gap fix (per-user cron) UI schedule presets and `UserDigestPreference.cronExpression` (+ timezone) were stored but ignored: `SlackDailyDigestTaskConfig` only ran the global `slack.daily-digest.cron` and then delivered to every preference. Product Evangelist blocked marketing “your own schedule” until this was fixed. The scheduler now ticks every minute (`slack.daily-digest.tick-cron`, default `0 * * * * *`) via `processDigestTick()`: - **No enabled prefs** → legacy channel broadcast only when the global cron is due (UTC) - **Prefs exist** → each enabled `SLACK_DM` preference is evaluated with Spring `CronExpression` in that user’s timezone; already-logged fire windows are skipped (`SlackDigestLog` idempotency) - Blank preference cron still falls back to `slack.daily-digest.cron` - Manual/admin trigger still uses `sendDailyDigestHybrid()` (force all recipients) ## Changes ### Backend - **`SlackDailyDigestService`**: `sendPersonalizedDigests()` / `sendDailyDigestHybrid()` / **`processDigestTick()`** — per-recipient DMs, EXEC formatting, personalized logs, and per-user cron on the minute tick - **`DigestCronMatcher`**: Spring `CronExpression` due-window helper (timezone-aware) - **`DigestPreferenceSeedService`**: seed from singleton; persona inference; skips inactive users - **`SlackDailyDigestTaskConfig`**: minute tick → `processDigestTick()` - **`DigestPreferenceController`**: seed/status; refuses EMAIL ### Frontend - Digest preferences API + panel (persona, schedule presets, seed) + DigestSection bell icon ### Documentation - `docs/DIGEST_PREFERENCES.md` updated for tick vs global cron and accurate “your own schedule” ## Acceptance Criteria - [x] Two users, different roles/personas, same connection → different Slack digests same window - [x] Cron/config not singleton-only (**per-user cron honored on tick**) - [x] Tests for per-recipient delivery + seed/legacy + due-cron / timezone / idempotency - [x] UI prefs for role/schedule/persona - [x] Docs updated — “your own schedule” is accurate - [x] No fake EMAIL / WhatsApp delivery advertised ## Out of Scope (Follow-ups) 1. Idle-in-transaction insight when Brain has data 2. `join_collapse_limit` cliffs under CONFIG_TUNING 3. DOCUMENTATION_GAPS miner 4. COST_CAPACITY dedicated cost miner for EXEC 5. Hermes/WhatsApp = PR4 ## Dependencies - PR1 #103: per-user digest prefs - PR2 #104: role-aware insight assembler ## Testing ```bash cd backend ./mvnw test -Dtest=PerRecipientDigestDeliveryTest,PerRecipientDigestCronSchedulingTest,DigestCronMatcherTest,DigestPreferenceSeedServiceTest,SlackDailyDigestServiceTest ``` Key scenarios: different crons → only due user; timezone America/New_York 9AM = 13:00 UTC; idempotent window skip; EXEC 3 bullets; EMAIL skipped; seed idempotent; legacy when no enabled prefs. <!-- CURSOR_AGENT_PR_BODY_END --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
venkateshsakamuri-lab
force-pushed
the
cursor/per-user-digest-prefs-3784
branch
from
September 9, 2026 06:53
800d213 to
7b4dea1
Compare
venkateshsakamuri-lab
force-pushed
the
cursor/digest-insight-assembler-cc18
branch
from
September 9, 2026 06:53
c6585bb to
3d292d1
Compare
venkateshsakamuri-lab
removed this pull request from stack #106
September 9, 2026 07:09
venkateshsakamuri-lab
deleted the
cursor/digest-insight-assembler-cc18
branch
September 9, 2026 07:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Role-aware insight assembler that ranks Brain insights for specific users/personas. Same Brain, different lens per role — not another generic Slack dump.
Persona → Real Brain Signal Mapping
The assembler surfaces different signals per persona for the same window:
LOCK_CONCURRENCY,QUERY_PERFORMANCE,CONFIG_TUNINGSCHEMA_CHANGES,QUERY_PERFORMANCE,LOCK_CONCURRENCYDOCUMENTATION_GAPS,SCHEMA_CHANGES,GROWTH_ANOMALIESCOST_CAPACITY,SYSTEM_ALERTSImplementation
New Models
DigestInsight— ranked insight with category, severity, freshness, actionability, signature key for deduplicationInsightCategory— enum with persona-aware multipliers (0.4–2.0x)DigestAssemblyResult— assembled digest with executive summary for EXEC personaDigestInsightAssemblerServiceMines insights from existing Brain stores:
BrainV2AlertRepository— workload changes, config drift, plan regressionsBrainScoreRepository— overall health scoresBrainLearningProgressRepository— tuning readinessIndexRecommendationRepository— missing/unused index candidatesSchemaChangeRepository— breaking changes, DDL eventsGrowthAnomalyRepository— size spikes, bloatPlaybookAlertRepository— system alertsSlowQueryHistoryRepository— slow query analysisLockContentionRepository— lock waits, blocking chains, ACCESS EXCLUSIVERanking Formula
Suppression Logic
Testing
Comprehensive unit tests covering:
Notes
assembleDigest(username, connectionId, role, personaTag, sinceLast))Stacked on
PR #103 (
cursor/per-user-digest-prefs-3784) — per-user digest preferences